Skip to content
This repository has been archived by the owner on Jun 19, 2024. It is now read-only.

Fix issue where D01 and D03 are implicit. #24

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion gerber/cam.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,11 @@ def settings(self):
def bounds(self):
""" File boundaries
"""
pass
bbox = outline.primitives[0].bounding_box ## will raise an exception if there is no primitives and that is ok
for i in outline.primitives:
b = i.bounding_box
bbox = ( (min(bbox[0][0],b[0][0]),max(bbox[1][0],b[1][0])), (min(bbox[0][1],b[0][1]),max(bbox[1][1],b[1][1])))
return bbox

def render(self, ctx, filename=None):
""" Generate image of layer.
Expand Down
4 changes: 4 additions & 0 deletions gerber/gerber_statements.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,10 @@ def build(self, modifiers=[[]]):
else:
self.primitives.append(AMUnsupportPrimitive.from_gerber(primitive))

## ok, in rs274x.py, the return value of build is assigned to GerberParser.macros[shape]
## shall it be self.primitives?
## what if a macro is built more than once?

def to_inch(self):
for primitive in self.primitives:
primitive.to_inch()
Expand Down
165 changes: 165 additions & 0 deletions gerber/nc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright 2014 Hamilton Kibbe <[email protected]>

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
NC File module
====================
**NC (PADS-alike) file classes**

This module provides NC file classes and parsing utilities
"""

import math

from .excellon_statements import *
from .cam import CamFile, FileSettings
from .primitives import Drill
from .utils import inch, metric
from .excellon import ExcellonFile


def read(filename, settings=None):
""" Read data from filename and return an ExcellonFile
Parameters
----------
filename : string
Filename of file to parse

settings : gerber.cam.FileSettings
NC Files does not include enough information on its format
so it have to be provided

Returns
-------
file : :class:`gerber.excellon.ExcellonFile`
An ExcellonFile created from the specified file.

"""

return ExcellonParser(settings).parse(filename)


class NcParser(object):
""" NC File Parser

Parameters
----------
settings : FileSettings or dict-like
Excellon file settings to use when interpreting the excellon file.
"""
def __init__(self, settings=None):
self.notation = 'absolute'
self.units = 'inch'
self.zeros = 'leading'
self.format = (2, 4)
self.state = 'INIT'
self.statements = []
self.tools = {}
self.hits = []
self.active_tool = None
self.pos = [0., 0.]
if settings is not None:
self.units = settings.units
self.zeros = settings.zeros
self.notation = settings.notation
self.format = settings.format


@property
def coordinates(self):
return [(stmt.x, stmt.y) for stmt in self.statements if isinstance(stmt, CoordinateStmt)]

@property
def bounds(self):
xmin = ymin = 100000000000
xmax = ymax = -100000000000
for x, y in self.coordinates:
if x is not None:
xmin = x if x < xmin else xmin
xmax = x if x > xmax else xmax
if y is not None:
ymin = y if y < ymin else ymin
ymax = y if y > ymax else ymax
return ((xmin, xmax), (ymin, ymax))

@property
def hole_sizes(self):
return [stmt.diameter for stmt in self.statements if isinstance(stmt, ExcellonTool)]

@property
def hole_count(self):
return len(self.hits)

def parse(self, filename):
with open(filename, 'r') as f:
for line in f:
self._parse(line.strip())
return ExcellonFile(self.statements, self.tools, self.hits,
self._settings(), filename)

def _parse(self, line):
# skip empty lines
if not line.strip():
return

if line[0] == '%':
self.statements.append(RewindStopStmt())
elif line[:3] == 'M30':
stmt = EndOfProgramStmt.from_excellon(line, self._settings())
self.statements.append(stmt)

elif line[0] == 'T':
tool = ExcellonTool.from_excellon(line, self._settings())
self.tools[tool.number] = tool
self.statements.append(tool)
stmt = ToolSelectionStmt.from_excellon("T%02d" % tool.number)
self.active_tool = tool
self.statements.append(stmt)

elif line[0] in ['X', 'Y']:
stmt = CoordinateStmt.from_excellon(line, self._settings())
x = stmt.x
y = stmt.y
self.statements.append(stmt)
self.hits.append((self.active_tool, (x,y)))
self.active_tool._hit()
else:
self.statements.append(UnknownStmt.from_excellon(line))

def _settings(self):
return FileSettings(units=self.units, format=self.format,
zeros=self.zeros, notation=self.notation)


def detect_nc_format(filename):
""" Detect NC file format.

Parameters
----------
filename : string
Name of the file to parse. This does only check if the file is actually
an NC file.

Returns
-------
True or False
Unfortunately, NC files does not include settings information.
"""
with open(filename) as f:
lines = f.readlines()
# suppose there is at least one tool
return lines[0][0] == '%' and lines[1][0] == 'T' and lines[-1].strip() == 'M30'
57 changes: 56 additions & 1 deletion gerber/primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import math
from operator import add, sub

from .utils import validate_coordinates, inch, metric
from .utils import validate_coordinates, inch, metric, mirror_yaxis


class Primitive(object):
Expand Down Expand Up @@ -59,6 +59,10 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
pass

def mirror_yaxis(self):
"""mirror will 'mirror' the image using the y-axis"""
pass

def __eq__(self, other):
return self.__dict__ == other.__dict__

Expand Down Expand Up @@ -154,6 +158,9 @@ def offset(self, x_offset=0, y_offset=0):
self.start = tuple(map(add, self.start, (x_offset, y_offset)))
self.end = tuple(map(add, self.end, (x_offset, y_offset)))

def mirror_yaxis(self):
self.start = mirror_yaxis(self.start)
self.end = mirror_yaxis(self.end)

class Arc(Primitive):
"""
Expand Down Expand Up @@ -248,6 +255,11 @@ def offset(self, x_offset=0, y_offset=0):
self.end = tuple(map(add, self.end, (x_offset, y_offset)))
self.center = tuple(map(add, self.center, (x_offset, y_offset)))

def mirror_yaxis(self):
self.start = mirror_yaxis(self.start)
self.end = mirror_yaxis(self.end)
self.center = mirror_yaxis(self.center)
self.direction = self.direction == 'clockwise' and 'counterclockwise' or 'clockwise'

class Circle(Primitive):
"""
Expand Down Expand Up @@ -283,6 +295,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class Ellipse(Primitive):
"""
Expand Down Expand Up @@ -322,6 +337,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class Rectangle(Primitive):
"""
Expand Down Expand Up @@ -369,6 +387,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class Diamond(Primitive):
"""
Expand Down Expand Up @@ -416,6 +437,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class ChamferRectangle(Primitive):
"""
Expand Down Expand Up @@ -467,6 +491,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class RoundRectangle(Primitive):
"""
Expand Down Expand Up @@ -518,6 +545,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class Obround(Primitive):
"""
Expand Down Expand Up @@ -587,6 +617,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class Polygon(Primitive):
"""
Expand Down Expand Up @@ -617,6 +650,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class Region(Primitive):
"""
Expand Down Expand Up @@ -644,6 +680,9 @@ def offset(self, x_offset=0, y_offset=0):
self.points = [tuple(map(add, point, (x_offset, y_offset)))
for point in self.points]

def mirror_yaxis(self):
self.points = [ (-x,y) for x,y in self.points ]


class RoundButterfly(Primitive):
""" A circle with two diagonally-opposite quadrants removed
Expand Down Expand Up @@ -677,6 +716,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class SquareButterfly(Primitive):
""" A square with two diagonally-opposite quadrants removed
Expand Down Expand Up @@ -707,6 +749,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class Donut(Primitive):
""" A Shape with an identical concentric shape removed from its center
Expand Down Expand Up @@ -765,6 +810,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class SquareRoundDonut(Primitive):
""" A Square with a circular cutout in the center
Expand Down Expand Up @@ -807,6 +855,9 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class Drill(Primitive):
""" A drill hole
Expand Down Expand Up @@ -840,6 +891,10 @@ def to_metric(self):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))

def mirror_yaxis(self):
self.position = mirror_yaxis(self.position)


class TestRecord(Primitive):
""" Netlist Test record
"""
Expand Down
2 changes: 2 additions & 0 deletions gerber/render/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@

from .svgwrite_backend import GerberSvgContext
from .cairo_backend import GerberCairoContext
from .gerber_backend import GerberRs274xContext
from .excellon_backend import GerberExcellonContext
Loading