From 5476da8aa3f4ee424f56f4f2491e7af1c4b7b758 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Fix a bunch of rendering bugs. - 'clear' polarity primitives no longer erase background - Added aperture macro support for polygons - Added aperture macro rendring support - Renderer now creates a new surface for each layer and merges them instead of working directly on a single surface - Updated examples accordingly --- gerber/am_statements.py | 125 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 84 insertions(+), 41 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 38f4d71..f67b0db 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -17,9 +17,7 @@ # limitations under the License. from .utils import validate_coordinates, inch, metric - - -# TODO: Add support for aperture macro variables +from .primitives import * __all__ = ['AMPrimitive', 'AMCommentPrimitive', 'AMCirclePrimitive', 'AMVectorLinePrimitive', 'AMOutlinePrimitive', 'AMPolygonPrimitive', @@ -51,12 +49,14 @@ class AMPrimitive(object): ------ TypeError, ValueError """ + def __init__(self, code, exposure=None): VALID_CODES = (0, 1, 2, 4, 5, 6, 7, 20, 21, 22, 9999) if not isinstance(code, int): raise TypeError('Aperture Macro Primitive code must be an integer') elif code not in VALID_CODES: - raise ValueError('Invalid Code. Valid codes are %s.' % ', '.join(map(str, VALID_CODES))) + raise ValueError('Invalid Code. Valid codes are %s.' % + ', '.join(map(str, VALID_CODES))) if exposure is not None and exposure.lower() not in ('on', 'off'): raise ValueError('Exposure must be either on or off') self.code = code @@ -68,9 +68,15 @@ class AMPrimitive(object): def to_metric(self): raise NotImplementedError('Subclass must implement `to-metric`') + def to_primitive(self, position, level_polarity, units): + """ Return a Primitive instance based on the specified macro params. + """ + print('Rendering {}s is not supported yet.'.format(str(self.__class__))) + def __eq__(self, other): return self.__dict__ == other.__dict__ + class AMCommentPrimitive(AMPrimitive): """ Aperture Macro Comment primitive. Code 0 @@ -181,12 +187,19 @@ class AMCirclePrimitive(AMPrimitive): self.diameter = metric(self.diameter) self.position = tuple([metric(x) for x in self.position]) + def to_primitive(self, position, level_polarity, units): + # Offset the primitive from macro position + position = tuple([a + b for a , b in zip (position, self.position)]) + # Return a renderable primitive + return Circle(position, self.diameter, level_polarity=level_polarity, + units=units) + def to_gerber(self, settings=None): - data = dict(code = self.code, - exposure = '1' if self.exposure == 'on' else 0, - diameter = self.diameter, - x = self.position[0], - y = self.position[1]) + data = dict(code=self.code, + exposure='1' if self.exposure == 'on' else 0, + diameter=self.diameter, + x=self.position[0], + y=self.position[1]) return '{code},{exposure},{diameter},{x},{y}*'.format(**data) @@ -261,17 +274,24 @@ class AMVectorLinePrimitive(AMPrimitive): self.start = tuple([metric(x) for x in self.start]) self.end = tuple([metric(x) for x in self.end]) + def to_primitive(self, position, level_polarity, units): + # Offset the primitive from macro position + start = tuple([a + b for a , b in zip (position, self.start)]) + end = tuple([a + b for a , b in zip (position, self.end)]) + # Return a renderable primitive + ap = Rectangle((0, 0), self.width, self.width) + return Line(start, end, ap, level_polarity=level_polarity, units=units) def to_gerber(self, settings=None): fmtstr = '{code},{exp},{width},{startx},{starty},{endx},{endy},{rotation}*' - data = dict(code = self.code, - exp = 1 if self.exposure == 'on' else 0, - width = self.width, - startx = self.start[0], - starty = self.start[1], - endx = self.end[0], - endy = self.end[1], - rotation = self.rotation) + data = dict(code=self.code, + exp=1 if self.exposure == 'on' else 0, + width=self.width, + startx=self.start[0], + starty=self.start[1], + endx=self.end[0], + endy=self.end[1], + rotation=self.rotation) return fmtstr.format(**data) @@ -323,7 +343,8 @@ class AMOutlinePrimitive(AMPrimitive): start_point = (float(modifiers[3]), float(modifiers[4])) points = [] for i in range(n): - points.append((float(modifiers[5 + i*2]), float(modifiers[5 + i*2 + 1]))) + points.append((float(modifiers[5 + i * 2]), + float(modifiers[5 + i * 2 + 1]))) rotation = float(modifiers[-1]) return cls(code, exposure, start_point, points, rotation) @@ -416,7 +437,6 @@ class AMPolygonPrimitive(AMPrimitive): rotation = float(modifiers[6]) return cls(code, exposure, vertices, position, diameter, rotation) - def __init__(self, code, exposure, vertices, position, diameter, rotation): """ Initialize AMPolygonPrimitive """ @@ -439,13 +459,21 @@ class AMPolygonPrimitive(AMPrimitive): self.position = tuple([metric(x) for x in self.position]) self.diameter = metric(self.diameter) + def to_primitive(self, position, level_polarity, units): + # Offset the primitive from macro position + position = tuple([a + b for a , b in zip (position, self.position)]) + # Return a renderable primitive + return Polygon(position, vertices, self.diameter/2., + rotation=self.rotation, level_polarity=level_polarity, + units=units) + def to_gerber(self, settings=None): data = dict( code=self.code, exposure="1" if self.exposure == "on" else "0", vertices=self.vertices, position="%.4g,%.4g" % self.position, - diameter = '%.4g' % self.diameter, + diameter='%.4g' % self.diameter, rotation=str(self.rotation) ) fmt = "{code},{exposure},{vertices},{position},{diameter},{rotation}*" @@ -546,17 +574,16 @@ class AMMoirePrimitive(AMPrimitive): self.crosshair_thickness = metric(self.crosshair_thickness) self.crosshair_length = metric(self.crosshair_length) - def to_gerber(self, settings=None): data = dict( code=self.code, position="%.4g,%.4g" % self.position, - diameter = self.diameter, - ring_thickness = self.ring_thickness, - gap = self.gap, - max_rings = self.max_rings, - crosshair_thickness = self.crosshair_thickness, - crosshair_length = self.crosshair_length, + diameter=self.diameter, + ring_thickness=self.ring_thickness, + gap=self.gap, + max_rings=self.max_rings, + crosshair_thickness=self.crosshair_thickness, + crosshair_length=self.crosshair_length, rotation=self.rotation ) fmt = "{code},{position},{diameter},{ring_thickness},{gap},{max_rings},{crosshair_thickness},{crosshair_length},{rotation}*" @@ -608,7 +635,7 @@ class AMThermalPrimitive(AMPrimitive): code = int(modifiers[0]) position = (float(modifiers[1]), float(modifiers[2])) outer_diameter = float(modifiers[3]) - inner_diameter= float(modifiers[4]) + inner_diameter = float(modifiers[4]) gap = float(modifiers[5]) return cls(code, position, outer_diameter, inner_diameter, gap) @@ -628,7 +655,6 @@ class AMThermalPrimitive(AMPrimitive): self.inner_diameter = inch(self.inner_diameter) self.gap = inch(self.gap) - def to_metric(self): self.position = tuple([metric(x) for x in self.position]) self.outer_diameter = metric(self.outer_diameter) @@ -639,9 +665,9 @@ class AMThermalPrimitive(AMPrimitive): data = dict( code=self.code, position="%.4g,%.4g" % self.position, - outer_diameter = self.outer_diameter, - inner_diameter = self.inner_diameter, - gap = self.gap, + outer_diameter=self.outer_diameter, + inner_diameter=self.inner_diameter, + gap=self.gap, ) fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap}*" return fmt.format(**data) @@ -693,14 +719,14 @@ class AMCenterLinePrimitive(AMPrimitive): exposure = 'on' if float(modifiers[1]) == 1 else 'off' width = float(modifiers[2]) height = float(modifiers[3]) - center= (float(modifiers[4]), float(modifiers[5])) + center = (float(modifiers[4]), float(modifiers[5])) rotation = float(modifiers[6]) return cls(code, exposure, width, height, center, rotation) def __init__(self, code, exposure, width, height, center, rotation): if code != 21: raise ValueError('CenterLinePrimitive code is 21') - super (AMCenterLinePrimitive, self).__init__(code, exposure) + super(AMCenterLinePrimitive, self).__init__(code, exposure) self.width = width self.height = height validate_coordinates(center) @@ -717,12 +743,19 @@ class AMCenterLinePrimitive(AMPrimitive): self.width = metric(self.width) self.height = metric(self.height) + def to_primitive(self, position, level_polarity, units): + # Offset the primitive from macro position + position = tuple([a + b for a , b in zip (position, self.center)]) + # Return a renderable primitive + return Rectangle(position, self.width, self.height, + level_polarity=level_polarity, units=units) + def to_gerber(self, settings=None): data = dict( code=self.code, - exposure = '1' if self.exposure == 'on' else '0', - width = self.width, - height = self.height, + exposure='1' if self.exposure == 'on' else '0', + width=self.width, + height=self.height, center="%.4g,%.4g" % self.center, rotation=self.rotation ) @@ -782,7 +815,7 @@ class AMLowerLeftLinePrimitive(AMPrimitive): def __init__(self, code, exposure, width, height, lower_left, rotation): if code != 22: raise ValueError('LowerLeftLinePrimitive code is 22') - super (AMLowerLeftLinePrimitive, self).__init__(code, exposure) + super(AMLowerLeftLinePrimitive, self).__init__(code, exposure) self.width = width self.height = height validate_coordinates(lower_left) @@ -799,12 +832,21 @@ class AMLowerLeftLinePrimitive(AMPrimitive): self.width = metric(self.width) self.height = metric(self.height) + def to_primitive(self, position, level_polarity, units): + # Offset the primitive from macro position + position = tuple([a + b for a , b in zip (position, self.lower_left)]) + position = tuple([pos + offset for pos, offset in + zip(position, (self.width/2, self.height/2))]) + # Return a renderable primitive + return Rectangle(position, self.width, self.height, + level_polarity=level_polarity, units=units) + def to_gerber(self, settings=None): data = dict( code=self.code, - exposure = '1' if self.exposure == 'on' else '0', - width = self.width, - height = self.height, + exposure='1' if self.exposure == 'on' else '0', + width=self.width, + height=self.height, lower_left="%.4g,%.4g" % self.lower_left, rotation=self.rotation ) @@ -813,6 +855,7 @@ class AMLowerLeftLinePrimitive(AMPrimitive): class AMUnsupportPrimitive(AMPrimitive): + @classmethod def from_gerber(cls, primitive): return cls(primitive) -- cgit From 1d9270d80981b70376eff4a8f275226969d5ebfd Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Fri, 22 Jan 2016 03:24:50 -0200 Subject: Fix NameError on Polygon primitive rendering --- gerber/am_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index f67b0db..11a6187 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -463,7 +463,7 @@ class AMPolygonPrimitive(AMPrimitive): # Offset the primitive from macro position position = tuple([a + b for a , b in zip (position, self.position)]) # Return a renderable primitive - return Polygon(position, vertices, self.diameter/2., + return Polygon(position, self.vertices, self.diameter/2., rotation=self.rotation, level_polarity=level_polarity, units=units) -- cgit From 724c2b3bced319ed0b50c4302fed9b0e1aa9ce9c Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 5 Nov 2016 20:56:47 -0400 Subject: Finish Merge, most tests passing --- gerber/am_statements.py | 113 ++++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 56 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 726df2f..2e3fe3d 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -75,7 +75,7 @@ class AMPrimitive(object): def to_metric(self): raise NotImplementedError('Subclass must implement `to-metric`') - + @property def _level_polarity(self): if self.exposure == 'off': @@ -190,9 +190,9 @@ class AMCirclePrimitive(AMPrimitive): diameter = float(modifiers[2]) position = (float(modifiers[3]), float(modifiers[4])) return cls(code, exposure, diameter, position) - + @classmethod - def from_primitive(cls, primitive): + def from_primitive(cls, primitive): return cls(1, 'on', primitive.diameter, primitive.position) def __init__(self, code, exposure, diameter, position): @@ -262,11 +262,11 @@ class AMVectorLinePrimitive(AMPrimitive): ------ ValueError, TypeError """ - + @classmethod def from_primitive(cls, primitive): return cls(2, 'on', primitive.aperture.width, primitive.start, primitive.end, 0) - + @classmethod def from_gerber(cls, primitive): modifiers = primitive.strip(' *').split(',') @@ -310,27 +310,27 @@ class AMVectorLinePrimitive(AMPrimitive): endy=self.end[1], rotation=self.rotation) return fmtstr.format(**data) - + def to_primitive(self, units): """ Convert this to a primitive. We use the Outline to represent this (instead of Line) because the behaviour of the end caps is different for aperture macros compared to Lines when rotated. """ - + # Use a line to generate our vertices easily line = Line(self.start, self.end, Rectangle(None, self.width, self.width)) vertices = line.vertices - + aperture = Circle((0, 0), 0) - + lines = [] prev_point = rotate_point(vertices[-1], self.rotation, (0, 0)) for point in vertices: cur_point = rotate_point(point, self.rotation, (0, 0)) - + lines.append(Line(prev_point, cur_point, aperture)) - + return Outline(lines, units=units, level_polarity=self._level_polarity) @@ -372,19 +372,19 @@ class AMOutlinePrimitive(AMPrimitive): ------ ValueError, TypeError """ - + @classmethod def from_primitive(cls, primitive): - + start_point = (round(primitive.primitives[0].start[0], 6), round(primitive.primitives[0].start[1], 6)) points = [] for prim in primitive.primitives: points.append((round(prim.end[0], 6), round(prim.end[1], 6))) - + rotation = 0.0 - + return cls(4, 'on', start_point, points, rotation) - + @classmethod def from_gerber(cls, primitive): modifiers = primitive.strip(' *').split(",") @@ -434,25 +434,25 @@ class AMOutlinePrimitive(AMPrimitive): ) # TODO I removed a closing asterix - not sure if this works for items with multiple statements return "{code},{exposure},{n_points},{start_point},{points},\n{rotation}*".format(**data) - + def to_primitive(self, units): """ Convert this to a drawable primitive. This uses the Outline instead of Line primitive to handle differences in end caps when rotated. """ - + lines = [] prev_point = rotate_point(self.start_point, self.rotation) for point in self.points: cur_point = rotate_point(point, self.rotation) - + lines.append(Line(prev_point, cur_point, Circle((0,0), 0))) - + prev_point = cur_point - + if lines[0].start != lines[-1].end: raise ValueError('Outline must be closed') - + return Outline(lines, units=units, level_polarity=self._level_polarity) @@ -495,11 +495,11 @@ class AMPolygonPrimitive(AMPrimitive): ------ ValueError, TypeError """ - + @classmethod def from_primitive(cls, primitive): return cls(5, 'on', primitive.sides, primitive.position, primitive.diameter, primitive.rotation) - + @classmethod def from_gerber(cls, primitive): modifiers = primitive.strip(' *').split(",") @@ -548,7 +548,7 @@ class AMPolygonPrimitive(AMPrimitive): ) fmt = "{code},{exposure},{vertices},{position},{diameter},{rotation}*" return fmt.format(**data) - + def to_primitive(self, units): return Polygon(self.position, self.vertices, self.diameter / 2.0, 0, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity) @@ -663,7 +663,8 @@ class AMMoirePrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - raise NotImplementedError() + #raise NotImplementedError() + return None class AMThermalPrimitive(AMPrimitive): @@ -750,70 +751,70 @@ class AMThermalPrimitive(AMPrimitive): ) fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap},{rotation}*" return fmt.format(**data) - + def _approximate_arc_cw(self, start_angle, end_angle, radius, center): """ Get an arc as a series of points - + Parameters ---------- start_angle : The start angle in radians end_angle : The end angle in radians radius`: Radius of the arc center : The center point of the arc (x, y) tuple - + Returns ------- array of point tuples """ - + # The total sweep sweep_angle = end_angle - start_angle num_steps = 10 - + angle_step = sweep_angle / num_steps - + radius = radius center = center - + points = [] - + for i in range(num_steps + 1): current_angle = start_angle + (angle_step * i) - + nextx = (center[0] + math.cos(current_angle) * radius) nexty = (center[1] + math.sin(current_angle) * radius) - + points.append((nextx, nexty)) - + return points def to_primitive(self, units): - + # We start with calculating the top right section, then duplicate it - + inner_radius = self.inner_diameter / 2.0 outer_radius = self.outer_diameter / 2.0 - + # Calculate the start angle relative to the horizontal axis inner_offset_angle = asin(self.gap / 2.0 / inner_radius) outer_offset_angle = asin(self.gap / 2.0 / outer_radius) - + rotation_rad = math.radians(self.rotation) inner_start_angle = inner_offset_angle + rotation_rad inner_end_angle = math.pi / 2 - inner_offset_angle + rotation_rad - + outer_start_angle = outer_offset_angle + rotation_rad outer_end_angle = math.pi / 2 - outer_offset_angle + rotation_rad - + outlines = [] aperture = Circle((0, 0), 0) - + points = (self._approximate_arc_cw(inner_start_angle, inner_end_angle, inner_radius, self.position) + list(reversed(self._approximate_arc_cw(outer_start_angle, outer_end_angle, outer_radius, self.position)))) # Add in the last point since outlines should be closed points.append(points[0]) - + # There are four outlines at rotated sections for rotation in [0, 90.0, 180.0, 270.0]: @@ -821,11 +822,11 @@ class AMThermalPrimitive(AMPrimitive): prev_point = rotate_point(points[0], rotation, self.position) for point in points[1:]: cur_point = rotate_point(point, rotation, self.position) - + lines.append(Line(prev_point, cur_point, aperture)) - + prev_point = cur_point - + outlines.append(Outline(lines, units=units, level_polarity=self._level_polarity)) return outlines @@ -869,7 +870,7 @@ class AMCenterLinePrimitive(AMPrimitive): ------ ValueError, TypeError """ - + @classmethod def from_primitive(cls, primitive): width = primitive.width @@ -922,27 +923,27 @@ class AMCenterLinePrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - + x = self.center[0] y = self.center[1] half_width = self.width / 2.0 half_height = self.height / 2.0 - + points = [] points.append((x - half_width, y + half_height)) points.append((x - half_width, y - half_height)) points.append((x + half_width, y - half_height)) points.append((x + half_width, y + half_height)) - + aperture = Circle((0, 0), 0) - + lines = [] prev_point = rotate_point(points[3], self.rotation, self.center) for point in points: cur_point = rotate_point(point, self.rotation, self.center) - + lines.append(Line(prev_point, cur_point, aperture)) - + return Outline(lines, units=units, level_polarity=self._level_polarity) @@ -1057,4 +1058,4 @@ class AMUnsupportPrimitive(AMPrimitive): return self.primitive def to_primitive(self, units): - return None \ No newline at end of file + return None -- cgit