From 29c0d82bf53907030d11df9eb09471b716a0be2e Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 27 Feb 2016 15:24:36 +0800 Subject: RS274X backend for rendering. Incompelte still --- gerber/render/rs274x_backend.py | 290 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 gerber/render/rs274x_backend.py (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py new file mode 100644 index 0000000..0094192 --- /dev/null +++ b/gerber/render/rs274x_backend.py @@ -0,0 +1,290 @@ + +from .render import GerberContext +from ..gerber_statements import * +from ..primitives import AMGroup, Arc, Circle, Line, Rectangle + +class Rs274xContext(GerberContext): + + def __init__(self, settings): + GerberContext.__init__(self) + self.header = [] + self.body = [] + self.end = [EofStmt()] + + # Current values so we know if we have to execute + # moves, levey changes before anything else + self._level_polarity = None + self._pos = (None, None) + self._func = None + self._quadrant_mode = None + self._dcode = None + + self._next_dcode = 10 + self._rects = {} + self._circles = {} + self._macros = {} + + self._i_none = 0 + self._j_none = 0 + + self._define_dcodes() + + + def _define_dcodes(self): + + self._get_circle(.1575, 10) + self._get_circle(.035, 17) + self._get_rectangle(0.1575, 0.1181, 15) + self._get_rectangle(0.0492, 0.0118, 16) + self._get_circle(.0197, 11) + self._get_rectangle(0.0236, 0.0591, 12) + self._get_circle(.005, 18) + self._get_circle(.008, 19) + self._get_circle(.009, 20) + self._get_circle(.01, 21) + self._get_circle(.02, 22) + self._get_circle(.006, 23) + self._get_circle(.015, 24) + self._get_rectangle(0.1678, 0.1284, 26) + self._get_rectangle(0.0338, 0.0694, 25) + + def _simplify_point(self, point): + return (point[0] if point[0] != self._pos[0] else None, point[1] if point[1] != self._pos[1] else None) + + def _simplify_offset(self, point, offset): + + if point[0] != offset[0]: + xoffset = point[0] - offset[0] + else: + xoffset = self._i_none + + if point[1] != offset[1]: + yoffset = point[1] - offset[1] + else: + yoffset = self._j_none + + return (xoffset, yoffset) + + @property + def statements(self): + return self.header + self.body + self.end + + def set_bounds(self, bounds): + pass + + def _paint_background(self): + pass + + def _select_aperture(self, aperture): + + # Select the right aperture if not already selected + if aperture: + if isinstance(aperture, Circle): + aper = self._get_circle(aperture.diameter) + elif isinstance(aperture, Rectangle): + aper = self._get_rectangle(aperture.width, aperture.height) + else: + raise NotImplementedError('Line with invalid aperture type') + + if aper.d != self._dcode: + self.body.append(ApertureStmt(aper.d)) + self._dcode = aper.d + + def _render_line(self, line, color): + + self._select_aperture(line.aperture) + + # Get the right function + if self._func != CoordStmt.FUNC_LINEAR: + func = CoordStmt.FUNC_LINEAR + else: + func = None + self._func = CoordStmt.FUNC_LINEAR + + if self._pos != line.start: + self.body.append(CoordStmt.move(func, self._simplify_point(line.start))) + self._pos = line.start + # We already set the function, so the next command doesn't require that + func = None + + self.body.append(CoordStmt.line(func, self._simplify_point(line.end))) + self._pos = line.end + + def _render_arc(self, arc, color): + + # Optionally set the quadrant mode if it has changed: + if arc.quadrant_mode != self._quadrant_mode: + + if arc.quadrant_mode != 'multi-quadrant': + self.body.append(QuadrantModeStmt.single()) + else: + self.body.append(QuadrantModeStmt.multi()) + + self._quadrant_mode = arc.quadrant_mode + + # Select the right aperture if not already selected + self._select_aperture(arc.aperture) + + # Find the right movement mode. Always set to be sure it is really right + dir = arc.direction + if dir == 'clockwise': + func = CoordStmt.FUNC_ARC_CW + self._func = CoordStmt.FUNC_ARC_CW + elif dir == 'counterclockwise': + func = CoordStmt.FUNC_ARC_CCW + self._func = CoordStmt.FUNC_ARC_CCW + else: + raise ValueError('Invalid circular interpolation mode') + + if self._pos != arc.start: + # TODO I'm not sure if this is right + self.body.append(CoordStmt.move(CoordStmt.FUNC_LINEAR, self._simplify_point(arc.start))) + self._pos = arc.start + + center = self._simplify_offset(arc.center, arc.start) + end = self._simplify_point(arc.end) + self.body.append(CoordStmt.arc(func, end, center)) + self._pos = arc.end + + def _render_region(self, region, color): + + self._render_level_polarity(region) + + self.body.append(RegionModeStmt.on()) + + for p in region.primitives: + + if isinstance(p, Line): + self._render_line(p, color) + else: + self._render_arc(p, color) + + + self.body.append(RegionModeStmt.off()) + + def _render_level_polarity(self, region): + if region.level_polarity != self._level_polarity: + self._level_polarity = region.level_polarity + self.body.append(LPParamStmt.from_region(region)) + + def _render_flash(self, primitive, aperture): + + if aperture.d != self._dcode: + self.body.append(ApertureStmt(aperture.d)) + self._dcode = aperture.d + + self.body.append(CoordStmt.flash( self._simplify_point(primitive.position))) + self._pos = primitive.position + + def _get_circle(self, diameter, dcode = None): + '''Define a circlar aperture''' + + aper = self._circles.get(diameter, None) + + if not aper: + if not dcode: + dcode = self._next_dcode + self._next_dcode += 1 + else: + self._next_dcode = max(dcode + 1, self._next_dcode) + + aper = ADParamStmt.circle(dcode, diameter) + self._circles[diameter] = aper + self.header.append(aper) + + return aper + + def _render_circle(self, circle, color): + + aper = self._get_circle(circle.diameter) + self._render_flash(circle, aper) + + def _get_rectangle(self, width, height, dcode = None): + '''Get a rectanglar aperture. If it isn't defined, create it''' + + key = (width, height) + aper = self._rects.get(key, None) + + if not aper: + if not dcode: + dcode = self._next_dcode + self._next_dcode += 1 + else: + self._next_dcode = max(dcode + 1, self._next_dcode) + + aper = ADParamStmt.rect(dcode, width, height) + self._rects[(width, height)] = aper + self.header.append(aper) + + return aper + + def _render_rectangle(self, rectangle, color): + + aper = self._get_rectangle(rectangle.width, rectangle.height) + self._render_flash(rectangle, aper) + + def _render_obround(self, obround, color): + pass + + def _render_polygon(self, polygon, color): + pass + + def _render_drill(self, circle, color): + pass + + def _hash_amacro(self, amgroup): + '''Calculate a very quick hash code for deciding if we should even check AM groups for comparision''' + + hash = '' + for primitive in amgroup.primitives: + + hash += primitive.__class__.__name__[0] + if hasattr(primitive, 'primitives'): + hash += str(len(primitive.primitives)) + + return hash + + def _get_amacro(self, amgroup, dcode = None): + # Macros are a little special since we don't have a good way to compare them quickly + # but in most cases, this should work + + hash = self._hash_amacro(amgroup) + macro = self._macros.get(hash, None) + + if not macro: + # This is a new macro, so define it + if not dcode: + dcode = self._next_dcode + self._next_dcode += 1 + else: + self._next_dcode = max(dcode + 1, self._next_dcode) + + # Create the statements + # TODO + statements = [] + aperdef = ADParamStmt.macro(dcode, hash) + + # Store the dcode and the original so we can check if it really is the same + macro = (aperdef, amgroup) + self._macros[hash] = macro + + else: + # We hae a definition, but check that the groups actually are the same + offset = (amgroup.position[0] - macro[1].position[0], amgroup.position[1] - macro[1].position[1]) + if not amgroup.equivalent(macro[1], offset): + raise ValueError('Two AMGroup have the same hash but are not equivalent') + + return macro[0] + + def _render_amgroup(self, amgroup, color): + + aper = self._get_amacro(amgroup) + self._render_flash(amgroup, aper) + + def _render_inverted_layer(self): + pass + + def post_render_primitives(self): + '''No more primitives, so set the end marker''' + + self.body.append() \ No newline at end of file -- cgit From 223a010831f0d9dae4bd6d2e626a603a78eb0b1d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 27 Feb 2016 18:18:04 +0800 Subject: Fix critical issue with rotatin points (when the angle is zero the y would be flipped). Render AM with outline to gerber --- gerber/render/rs274x_backend.py | 61 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 0094192..bdb77f4 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -1,12 +1,49 @@ from .render import GerberContext +from ..am_statements import * from ..gerber_statements import * -from ..primitives import AMGroup, Arc, Circle, Line, Rectangle +from ..primitives import AMGroup, Arc, Circle, Line, Outline, Rectangle +from copy import deepcopy + +class AMGroupContext(object): + '''A special renderer to generate aperature macros from an AMGroup''' + + def __init__(self): + self.statements = [] + + def render(self, amgroup, name): + + # Clone ourselves, then offset by the psotion so that + # our render doesn't have to consider offset. Just makes things simplder + nooffset_group = deepcopy(amgroup) + nooffset_group.position = (0, 0) + + # Now draw the shapes + for primitive in nooffset_group.primitives: + if isinstance(primitive, Outline): + self._render_outline(primitive) + + statement = AMParamStmt('AM', name, self._statements_to_string()) + return statement + + def _statements_to_string(self): + macro = '' + + for statement in self.statements: + macro += statement.to_gerber() + + return macro + + def _render_outline(self, outline): + self.statements.append(AMOutlinePrimitive.from_primitive(outline)) + + class Rs274xContext(GerberContext): def __init__(self, settings): GerberContext.__init__(self) + self.comments = [] self.header = [] self.body = [] self.end = [EofStmt()] @@ -27,8 +64,13 @@ class Rs274xContext(GerberContext): self._i_none = 0 self._j_none = 0 + self.settings = settings + + self._start_header(settings) self._define_dcodes() + def _start_header(self, settings): + self.header.append(MOParamStmt.from_units(settings.units)) def _define_dcodes(self): @@ -67,7 +109,7 @@ class Rs274xContext(GerberContext): @property def statements(self): - return self.header + self.body + self.end + return self.comments + self.header + self.body + self.end def set_bounds(self, bounds): pass @@ -93,6 +135,8 @@ class Rs274xContext(GerberContext): def _render_line(self, line, color): self._select_aperture(line.aperture) + + self._render_level_polarity(line) # Get the right function if self._func != CoordStmt.FUNC_LINEAR: @@ -125,6 +169,8 @@ class Rs274xContext(GerberContext): # Select the right aperture if not already selected self._select_aperture(arc.aperture) + self._render_level_polarity(arc) + # Find the right movement mode. Always set to be sure it is really right dir = arc.direction if dir == 'clockwise': @@ -243,7 +289,7 @@ class Rs274xContext(GerberContext): hash += str(len(primitive.primitives)) return hash - + def _get_amacro(self, amgroup, dcode = None): # Macros are a little special since we don't have a good way to compare them quickly # but in most cases, this should work @@ -261,8 +307,13 @@ class Rs274xContext(GerberContext): # Create the statements # TODO - statements = [] + amrenderer = AMGroupContext() + statement = amrenderer.render(amgroup, hash) + + self.header.append(statement) + aperdef = ADParamStmt.macro(dcode, hash) + self.header.append(aperdef) # Store the dcode and the original so we can check if it really is the same macro = (aperdef, amgroup) @@ -281,6 +332,8 @@ class Rs274xContext(GerberContext): aper = self._get_amacro(amgroup) self._render_flash(amgroup, aper) + + def _render_inverted_layer(self): pass -- cgit From 20a9af279ac2217a39b73903ff94b916a3025be2 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Tue, 1 Mar 2016 00:06:14 +0800 Subject: More rendering of AMGroup to statements --- gerber/render/rs274x_backend.py | 72 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 7 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index bdb77f4..2a0420e 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -22,6 +22,14 @@ class AMGroupContext(object): for primitive in nooffset_group.primitives: if isinstance(primitive, Outline): self._render_outline(primitive) + elif isinstance(primitive, Circle): + self._render_circle(primitive) + elif isinstance(primitive, Rectangle): + self._render_rectangle(primitive) + elif isinstance(primitive, Line): + self._render_line(primitive) + else: + raise ValueError('amgroup') statement = AMParamStmt('AM', name, self._statements_to_string()) return statement @@ -33,10 +41,21 @@ class AMGroupContext(object): macro += statement.to_gerber() return macro + + def _render_circle(self, circle): + self.statements.append(AMCirclePrimitive.from_primitive(circle)) + + def _render_rectangle(self, rectangle): + self.statements.append(AMCenterLinePrimitive.from_primitive(rectangle)) + + def _render_line(self, line): + self.statements.append(AMVectorLinePrimitive.from_primitive(line)) def _render_outline(self, outline): self.statements.append(AMOutlinePrimitive.from_primitive(outline)) - + + def _render_thermal(self, thermal): + pass class Rs274xContext(GerberContext): @@ -59,6 +78,8 @@ class Rs274xContext(GerberContext): self._next_dcode = 10 self._rects = {} self._circles = {} + self._obrounds = {} + self._polygons = {} self._macros = {} self._i_none = 0 @@ -67,9 +88,10 @@ class Rs274xContext(GerberContext): self.settings = settings self._start_header(settings) - self._define_dcodes() + #self._define_dcodes() def _start_header(self, settings): + self.header.append(FSParamStmt.from_settings(settings)) self.header.append(MOParamStmt.from_units(settings.units)) def _define_dcodes(self): @@ -151,8 +173,12 @@ class Rs274xContext(GerberContext): # We already set the function, so the next command doesn't require that func = None - self.body.append(CoordStmt.line(func, self._simplify_point(line.end))) - self._pos = line.end + point = self._simplify_point(line.end) + + # In some files, we see a lot of duplicated ponts, so omit those + if point[0] != None or point[1] != None: + self.body.append(CoordStmt.line(func, self._simplify_point(line.end))) + self._pos = line.end def _render_arc(self, arc, color): @@ -269,10 +295,33 @@ class Rs274xContext(GerberContext): aper = self._get_rectangle(rectangle.width, rectangle.height) self._render_flash(rectangle, aper) + def _get_obround(self, width, height, dcode = None): + + key = (width, height) + aper = self._obrounds.get(key, None) + + if not aper: + if not dcode: + dcode = self._next_dcode + self._next_dcode += 1 + else: + self._next_dcode = max(dcode + 1, self._next_dcode) + + aper = ADParamStmt.obround(dcode, width, height) + self._obrounds[(width, height)] = aper + self.header.append(aper) + + return aper + def _render_obround(self, obround, color): + + aper = self._get_obround(obround.width, obround.height) + self._render_flash(obround, aper) + pass def _render_polygon(self, polygon, color): + raise NotImplementedError('Not implemented yet') pass def _render_drill(self, circle, color): @@ -285,8 +334,19 @@ class Rs274xContext(GerberContext): for primitive in amgroup.primitives: hash += primitive.__class__.__name__[0] + + bbox = primitive.bounding_box + hash += str((bbox[0][1] - bbox[0][0]) * 100000)[0:2] + hash += str((bbox[1][1] - bbox[1][0]) * 100000)[0:2] + if hasattr(primitive, 'primitives'): hash += str(len(primitive.primitives)) + + if isinstance(primitive, Rectangle): + hash += str(primitive.width * 1000000)[0:2] + hash += str(primitive.height * 1000000)[0:2] + elif isinstance(primitive, Circle): + hash += str(primitive.diameter * 1000000)[0:2] return hash @@ -331,9 +391,7 @@ class Rs274xContext(GerberContext): aper = self._get_amacro(amgroup) self._render_flash(amgroup, aper) - - - + def _render_inverted_layer(self): pass -- cgit From 7b88509c4acb4edbbe1a39762758bf28efecfc7f Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 5 Mar 2016 09:24:54 +0800 Subject: Make writer resilient to similar macro defs --- gerber/render/rs274x_backend.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 2a0420e..d4456e2 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -355,8 +355,19 @@ class Rs274xContext(GerberContext): # but in most cases, this should work hash = self._hash_amacro(amgroup) - macro = self._macros.get(hash, None) + macro = None + macroinfo = self._macros.get(hash, None) + if macroinfo: + + # We hae a definition, but check that the groups actually are the same + for macro in macroinfo: + offset = (amgroup.position[0] - macro[1].position[0], amgroup.position[1] - macro[1].position[1]) + if amgroup.equivalent(macro[1], offset): + break + macro = None + + # Did we find one in the group0 if not macro: # This is a new macro, so define it if not dcode: @@ -377,13 +388,11 @@ class Rs274xContext(GerberContext): # Store the dcode and the original so we can check if it really is the same macro = (aperdef, amgroup) - self._macros[hash] = macro - - else: - # We hae a definition, but check that the groups actually are the same - offset = (amgroup.position[0] - macro[1].position[0], amgroup.position[1] - macro[1].position[1]) - if not amgroup.equivalent(macro[1], offset): - raise ValueError('Two AMGroup have the same hash but are not equivalent') + + if macroinfo: + macroinfo.append(macro) + else: + self._macros[hash] = [macro] return macro[0] -- cgit From 7f47aea332ee1df45c87baa304d95ed03cc59865 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 5 Mar 2016 10:04:58 +0800 Subject: Write polygons to macros --- gerber/render/rs274x_backend.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index d4456e2..04ecbe6 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -2,7 +2,7 @@ from .render import GerberContext from ..am_statements import * from ..gerber_statements import * -from ..primitives import AMGroup, Arc, Circle, Line, Outline, Rectangle +from ..primitives import AMGroup, Arc, Circle, Line, Outline, Polygon, Rectangle from copy import deepcopy class AMGroupContext(object): @@ -28,6 +28,8 @@ class AMGroupContext(object): self._render_rectangle(primitive) elif isinstance(primitive, Line): self._render_line(primitive) + elif isinstance(primitive, Polygon): + self._render_polygon(primitive) else: raise ValueError('amgroup') @@ -53,6 +55,9 @@ class AMGroupContext(object): def _render_outline(self, outline): self.statements.append(AMOutlinePrimitive.from_primitive(outline)) + + def _render_polygon(self, polygon): + self.statements.append(AMPolygonPrimitive.from_primitive(polygon)) def _render_thermal(self, thermal): pass -- cgit From 97355475686dd4bdad1b0bd9a307843ea3c234f2 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 5 Mar 2016 10:28:38 +0800 Subject: Make rendering more robust for bad gerber files --- gerber/render/rs274x_backend.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 04ecbe6..5a15fe5 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -2,7 +2,7 @@ from .render import GerberContext from ..am_statements import * from ..gerber_statements import * -from ..primitives import AMGroup, Arc, Circle, Line, Outline, Polygon, Rectangle +from ..primitives import AMGroup, Arc, Circle, Line, Obround, Outline, Polygon, Rectangle from copy import deepcopy class AMGroupContext(object): @@ -152,6 +152,10 @@ class Rs274xContext(GerberContext): aper = self._get_circle(aperture.diameter) elif isinstance(aperture, Rectangle): aper = self._get_rectangle(aperture.width, aperture.height) + elif isinstance(aperture, Obround): + aper = self._get_obround(aperture.width, aperture.height) + elif isinstance(aperture, AMGroup): + aper = self._get_amacro(aperture) else: raise NotImplementedError('Line with invalid aperture type') @@ -367,7 +371,15 @@ class Rs274xContext(GerberContext): # We hae a definition, but check that the groups actually are the same for macro in macroinfo: - offset = (amgroup.position[0] - macro[1].position[0], amgroup.position[1] - macro[1].position[1]) + + # Macros should have positions, right? But if the macro is selected for non-flashes + # then it won't have a position. This is of course a bad gerber, but they do exist + if amgroup.position: + position = amgroup.position + else: + position = (0, 0) + + offset = (position[0] - macro[1].position[0], position[1] - macro[1].position[1]) if amgroup.equivalent(macro[1], offset): break macro = None -- cgit From 0f1d1c3a29017ea82e1f0f7795798405ef346706 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 5 Mar 2016 14:56:08 +0800 Subject: Remove some testing code from gerber writer. More implementation for excellon writer - not working yet --- gerber/render/rs274x_backend.py | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 5a15fe5..81e86f2 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -93,30 +93,11 @@ class Rs274xContext(GerberContext): self.settings = settings self._start_header(settings) - #self._define_dcodes() def _start_header(self, settings): self.header.append(FSParamStmt.from_settings(settings)) self.header.append(MOParamStmt.from_units(settings.units)) - def _define_dcodes(self): - - self._get_circle(.1575, 10) - self._get_circle(.035, 17) - self._get_rectangle(0.1575, 0.1181, 15) - self._get_rectangle(0.0492, 0.0118, 16) - self._get_circle(.0197, 11) - self._get_rectangle(0.0236, 0.0591, 12) - self._get_circle(.005, 18) - self._get_circle(.008, 19) - self._get_circle(.009, 20) - self._get_circle(.01, 21) - self._get_circle(.02, 22) - self._get_circle(.006, 23) - self._get_circle(.015, 24) - self._get_rectangle(0.1678, 0.1284, 26) - self._get_rectangle(0.0338, 0.0694, 25) - def _simplify_point(self, point): return (point[0] if point[0] != self._pos[0] else None, point[1] if point[1] != self._pos[1] else None) @@ -330,11 +311,10 @@ class Rs274xContext(GerberContext): pass def _render_polygon(self, polygon, color): - raise NotImplementedError('Not implemented yet') - pass + raise ValueError('Polygons can only exist in the context of aperture macro') - def _render_drill(self, circle, color): - pass + def _render_drill(self, drill, color): + raise ValueError('Drills are not valid in RS274X files') def _hash_amacro(self, amgroup): '''Calculate a very quick hash code for deciding if we should even check AM groups for comparision''' @@ -420,8 +400,4 @@ class Rs274xContext(GerberContext): def _render_inverted_layer(self): pass - - def post_render_primitives(self): - '''No more primitives, so set the end marker''' - - self.body.append() \ No newline at end of file + \ No newline at end of file -- cgit From 97924d188bf8fcc7d7537007464e65cbdc8c7bbb Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 5 Mar 2016 16:26:30 +0800 Subject: More robust writing, even for bad files. Remove accidentally added imports --- gerber/render/rs274x_backend.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 81e86f2..48a55e7 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -384,6 +384,9 @@ class Rs274xContext(GerberContext): self.header.append(aperdef) # Store the dcode and the original so we can check if it really is the same + # If it didn't have a postition, set it to 0, 0 + if amgroup.position == None: + amgroup.position = (0, 0) macro = (aperdef, amgroup) if macroinfo: -- cgit From 74c638c7181e7a8ca4d0f791545bbf5db8b86c2a Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Thu, 19 May 2016 23:19:28 +0800 Subject: Fix issue where did not always switch into the G01 mode after G03 when the point was unchanged --- gerber/render/rs274x_backend.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 48a55e7..3dc8c1a 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -169,6 +169,8 @@ class Rs274xContext(GerberContext): if point[0] != None or point[1] != None: self.body.append(CoordStmt.line(func, self._simplify_point(line.end))) self._pos = line.end + elif func: + self.body.append(CoordStmt.mode(func)) def _render_arc(self, arc, color): -- cgit From c9c1313d598d5afa8cb387a2cfcd4a4281086e01 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 28 May 2016 12:36:31 +0800 Subject: Fix units statement. Keep track of original macro statement in the AMGroup --- gerber/render/rs274x_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 3dc8c1a..43695c3 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -14,7 +14,7 @@ class AMGroupContext(object): def render(self, amgroup, name): # Clone ourselves, then offset by the psotion so that - # our render doesn't have to consider offset. Just makes things simplder + # our render doesn't have to consider offset. Just makes things simpler nooffset_group = deepcopy(amgroup) nooffset_group.position = (0, 0) -- cgit From 3fc296918e7d0d343840c5daa08eb6d564660a29 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 28 May 2016 13:06:08 +0800 Subject: Use the known macro statement to render. Fix thermal not setting rotation --- gerber/render/rs274x_backend.py | 54 ++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 22 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 43695c3..2ca7014 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -13,28 +13,38 @@ class AMGroupContext(object): def render(self, amgroup, name): - # Clone ourselves, then offset by the psotion so that - # our render doesn't have to consider offset. Just makes things simpler - nooffset_group = deepcopy(amgroup) - nooffset_group.position = (0, 0) - - # Now draw the shapes - for primitive in nooffset_group.primitives: - if isinstance(primitive, Outline): - self._render_outline(primitive) - elif isinstance(primitive, Circle): - self._render_circle(primitive) - elif isinstance(primitive, Rectangle): - self._render_rectangle(primitive) - elif isinstance(primitive, Line): - self._render_line(primitive) - elif isinstance(primitive, Polygon): - self._render_polygon(primitive) - else: - raise ValueError('amgroup') - - statement = AMParamStmt('AM', name, self._statements_to_string()) - return statement + if amgroup.stmt: + # We know the statement it was generated from, so use that to create the AMParamStmt + # It will give a much better result + + stmt = deepcopy(amgroup.stmt) + stmt.name = name + + return stmt + + else: + # Clone ourselves, then offset by the psotion so that + # our render doesn't have to consider offset. Just makes things simpler + nooffset_group = deepcopy(amgroup) + nooffset_group.position = (0, 0) + + # Now draw the shapes + for primitive in nooffset_group.primitives: + if isinstance(primitive, Outline): + self._render_outline(primitive) + elif isinstance(primitive, Circle): + self._render_circle(primitive) + elif isinstance(primitive, Rectangle): + self._render_rectangle(primitive) + elif isinstance(primitive, Line): + self._render_line(primitive) + elif isinstance(primitive, Polygon): + self._render_polygon(primitive) + else: + raise ValueError('amgroup') + + statement = AMParamStmt('AM', name, self._statements_to_string()) + return statement def _statements_to_string(self): macro = '' -- cgit From ea97d9d0376db6ff7afcc7669eec84a228f8d201 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 28 May 2016 17:03:40 +0800 Subject: Fix issue with switching between ROUT and normal drill modes --- gerber/render/rs274x_backend.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 2ca7014..bb784b1 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -331,7 +331,11 @@ class Rs274xContext(GerberContext): def _hash_amacro(self, amgroup): '''Calculate a very quick hash code for deciding if we should even check AM groups for comparision''' - hash = '' + # We always start with an X because this forms part of the name + # Basically, in some cases, the name might start with a C, R, etc. That can appear + # to conflict with normal aperture definitions. Technically, it shouldn't because normal + # aperture definitions should have a comma, but in some cases the commit is omitted + hash = 'X' for primitive in amgroup.primitives: hash += primitive.__class__.__name__[0] @@ -348,6 +352,11 @@ class Rs274xContext(GerberContext): hash += str(primitive.height * 1000000)[0:2] elif isinstance(primitive, Circle): hash += str(primitive.diameter * 1000000)[0:2] + + if len(hash) > 20: + # The hash might actually get quite complex, so stop before + # it gets too long + break return hash @@ -361,7 +370,7 @@ class Rs274xContext(GerberContext): if macroinfo: - # We hae a definition, but check that the groups actually are the same + # We have a definition, but check that the groups actually are the same for macro in macroinfo: # Macros should have positions, right? But if the macro is selected for non-flashes -- cgit From ccb6eb7a766bd6edf314978f3ec4fc0dcd61652d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 25 Jun 2016 16:46:44 +0800 Subject: Add support for polygon apertures --- gerber/render/rs274x_backend.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index bb784b1..c37529e 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -310,7 +310,7 @@ class Rs274xContext(GerberContext): self._next_dcode = max(dcode + 1, self._next_dcode) aper = ADParamStmt.obround(dcode, width, height) - self._obrounds[(width, height)] = aper + self._obrounds[key] = aper self.header.append(aper) return aper @@ -320,10 +320,28 @@ class Rs274xContext(GerberContext): aper = self._get_obround(obround.width, obround.height) self._render_flash(obround, aper) - pass - def _render_polygon(self, polygon, color): - raise ValueError('Polygons can only exist in the context of aperture macro') + + aper = self._get_polygon(polygon.radius, polygon.sides, polygon.rotation, polygon.hole_radius) + self._render_flash(polygon, aper) + + def _get_polygon(self, radius, num_vertices, rotation, hole_radius, dcode = None): + + key = (radius, num_vertices, rotation, hole_radius) + aper = self._polygons.get(key, None) + + if not aper: + if not dcode: + dcode = self._next_dcode + self._next_dcode += 1 + else: + self._next_dcode = max(dcode + 1, self._next_dcode) + + aper = ADParamStmt.polygon(dcode, radius * 2, num_vertices, rotation, hole_radius * 2) + self._polygons[key] = aper + self.header.append(aper) + + return aper def _render_drill(self, drill, color): raise ValueError('Drills are not valid in RS274X files') -- cgit From 9b0d3b1122ffc3b7c2211b0cdc2cb6de6be9b242 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 10 Jul 2016 15:07:17 +0800 Subject: Fix issue with chaning region mode via flash. Add options for controlling output from rendered gerber --- gerber/render/rs274x_backend.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index c37529e..972edcb 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -90,6 +90,17 @@ class Rs274xContext(GerberContext): self._quadrant_mode = None self._dcode = None + # Primarily for testing and comarison to files, should we write + # flashes as a single statement or a move plus flash? Set to true + # to do in a single statement. Normally this can be false + self.condensed_flash = True + + # When closing a region, force a D02 staement to close a region. + # This is normally not necessary because regions are closed with a G37 + # staement, but this will add an extra statement for doubly close + # the region + self.explicit_region_move_end = False + self._next_dcode = 10 self._rects = {} self._circles = {} @@ -153,6 +164,11 @@ class Rs274xContext(GerberContext): if aper.d != self._dcode: self.body.append(ApertureStmt(aper.d)) self._dcode = aper.d + + def _pre_render_primitive(self, primitive): + + if hasattr(primitive, 'comment'): + self.body.append(CommentStmt(primitive.comment)) def _render_line(self, line, color): @@ -233,6 +249,8 @@ class Rs274xContext(GerberContext): else: self._render_arc(p, color) + if self.explicit_region_move_end: + self.body.append(CoordStmt.move(None, None)) self.body.append(RegionModeStmt.off()) @@ -243,11 +261,18 @@ class Rs274xContext(GerberContext): def _render_flash(self, primitive, aperture): + self._render_level_polarity(primitive) + if aperture.d != self._dcode: self.body.append(ApertureStmt(aperture.d)) self._dcode = aperture.d - self.body.append(CoordStmt.flash( self._simplify_point(primitive.position))) + if self.condensed_flash: + self.body.append(CoordStmt.flash(self._simplify_point(primitive.position))) + else: + self.body.append(CoordStmt.move(None, self._simplify_point(primitive.position))) + self.body.append(CoordStmt.flash(None)) + self._pos = primitive.position def _get_circle(self, diameter, dcode = None): -- cgit From 7cd6acf12670f3773113f67ed2acb35cb21c32a0 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 24 Jul 2016 17:08:47 +0800 Subject: Add many render tests based on the Umaco gerger specification. Fix multiple rendering bugs, especially related to holes in flashed apertures --- gerber/render/rs274x_backend.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 972edcb..15e9154 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -151,7 +151,7 @@ class Rs274xContext(GerberContext): # Select the right aperture if not already selected if aperture: if isinstance(aperture, Circle): - aper = self._get_circle(aperture.diameter) + aper = self._get_circle(aperture.diameter, aperture.hole_diameter) elif isinstance(aperture, Rectangle): aper = self._get_rectangle(aperture.width, aperture.height) elif isinstance(aperture, Obround): @@ -275,10 +275,10 @@ class Rs274xContext(GerberContext): self._pos = primitive.position - def _get_circle(self, diameter, dcode = None): + def _get_circle(self, diameter, hole_diameter, dcode = None): '''Define a circlar aperture''' - aper = self._circles.get(diameter, None) + aper = self._circles.get((diameter, hole_diameter), None) if not aper: if not dcode: @@ -287,15 +287,15 @@ class Rs274xContext(GerberContext): else: self._next_dcode = max(dcode + 1, self._next_dcode) - aper = ADParamStmt.circle(dcode, diameter) - self._circles[diameter] = aper + aper = ADParamStmt.circle(dcode, diameter, hole_diameter) + self._circles[(diameter, hole_diameter)] = aper self.header.append(aper) return aper def _render_circle(self, circle, color): - aper = self._get_circle(circle.diameter) + aper = self._get_circle(circle.diameter, circle.hole_diameter) self._render_flash(circle, aper) def _get_rectangle(self, width, height, dcode = None): -- cgit From 965d3ce23b92f8aff1063debd6d3364de15791fe Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 24 Jul 2016 22:08:31 +0800 Subject: Add more tests for rendering to PNG. Start adding tests for rendering to Gerber format. Changed definition of no hole to use None instead of 0 so we can differentiate when writing to Gerber format. Makde polygon use hole diameter instead of hole radius to match other primitives --- gerber/render/rs274x_backend.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 15e9154..5ab74f0 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -1,9 +1,17 @@ +"""Renders an in-memory Gerber file to statements which can be written to a string +""" +from copy import deepcopy +try: + from cStringIO import StringIO +except(ImportError): + from io import StringIO + from .render import GerberContext from ..am_statements import * from ..gerber_statements import * from ..primitives import AMGroup, Arc, Circle, Line, Obround, Outline, Polygon, Rectangle -from copy import deepcopy + class AMGroupContext(object): '''A special renderer to generate aperature macros from an AMGroup''' @@ -467,4 +475,13 @@ class Rs274xContext(GerberContext): def _render_inverted_layer(self): pass + + def dump(self): + """Write the rendered file to a StringIO steam""" + statements = map(lambda stmt: stmt.to_gerber(self.settings), self.statements) + stream = StringIO() + for statement in statements: + stream.write(statement + '\n') + + return stream \ No newline at end of file -- cgit From 8d5e782ccf220d77f0aad5a4e5605dc5cbe0f410 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 6 Aug 2016 09:51:58 +0800 Subject: Fix multiple problems with the merge. There are still errors, but I will intentionally leave them because future merges might resolve them --- gerber/render/rs274x_backend.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 5ab74f0..b4b4612 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -476,6 +476,14 @@ class Rs274xContext(GerberContext): def _render_inverted_layer(self): pass + def _new_render_layer(self): + # TODO Might need to implement this + pass + + def _flatten(self): + # TODO Might need to implement this + pass + def dump(self): """Write the rendered file to a StringIO steam""" statements = map(lambda stmt: stmt.to_gerber(self.settings), self.statements) -- cgit From 0ae5c48a65d59df8624a17c2b5a6aabff4c05e25 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 18 Nov 2016 08:10:32 -0500 Subject: Fix rs274x output bugs --- gerber/render/rs274x_backend.py | 51 ++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 19 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 13e871c..d32602a 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -159,7 +159,7 @@ class Rs274xContext(GerberContext): # Select the right aperture if not already selected if aperture: if isinstance(aperture, Circle): - aper = self._get_circle(aperture.diameter, aperture.hole_diameter) + aper = self._get_circle(aperture.diameter, aperture.hole_diameter, aperture.hole_width, aperture.hole_height) elif isinstance(aperture, Rectangle): aper = self._get_rectangle(aperture.width, aperture.height) elif isinstance(aperture, Obround): @@ -283,10 +283,12 @@ class Rs274xContext(GerberContext): self._pos = primitive.position - def _get_circle(self, diameter, hole_diameter, dcode = None): + def _get_circle(self, diameter, hole_diameter=None, hole_width=None, + hole_height=None, dcode = None): '''Define a circlar aperture''' - aper = self._circles.get((diameter, hole_diameter), None) + key = (diameter, hole_diameter, hole_width, hole_height) + aper = self._circles.get(key, None) if not aper: if not dcode: @@ -295,21 +297,22 @@ class Rs274xContext(GerberContext): else: self._next_dcode = max(dcode + 1, self._next_dcode) - aper = ADParamStmt.circle(dcode, diameter, hole_diameter) - self._circles[(diameter, hole_diameter)] = aper + aper = ADParamStmt.circle(dcode, diameter, hole_diameter, hole_width, hole_height) + self._circles[(diameter, hole_diameter, hole_width, hole_height)] = aper self.header.append(aper) return aper def _render_circle(self, circle, color): - aper = self._get_circle(circle.diameter, circle.hole_diameter) + aper = self._get_circle(circle.diameter, circle.hole_diameter, circle.hole_width, circle.hole_height) self._render_flash(circle, aper) - def _get_rectangle(self, width, height, dcode = None): + def _get_rectangle(self, width, height, hole_diameter=None, hole_width=None, + hole_height=None, dcode = None): '''Get a rectanglar aperture. If it isn't defined, create it''' - key = (width, height) + key = (width, height, hole_diameter, hole_width, hole_height) aper = self._rects.get(key, None) if not aper: @@ -319,20 +322,23 @@ class Rs274xContext(GerberContext): else: self._next_dcode = max(dcode + 1, self._next_dcode) - aper = ADParamStmt.rect(dcode, width, height) - self._rects[(width, height)] = aper + aper = ADParamStmt.rect(dcode, width, height, hole_diameter, hole_width, hole_height) + self._rects[(width, height, hole_diameter, hole_width, hole_height)] = aper self.header.append(aper) return aper def _render_rectangle(self, rectangle, color): - aper = self._get_rectangle(rectangle.width, rectangle.height) + aper = self._get_rectangle(rectangle.width, rectangle.height, + rectangle.hole_diameter, + rectangle.hole_width, rectangle.hole_height) self._render_flash(rectangle, aper) - def _get_obround(self, width, height, dcode = None): + def _get_obround(self, width, height, hole_diameter=None, hole_width=None, + hole_height=None, dcode = None): - key = (width, height) + key = (width, height, hole_diameter, hole_width, hole_height) aper = self._obrounds.get(key, None) if not aper: @@ -342,7 +348,7 @@ class Rs274xContext(GerberContext): else: self._next_dcode = max(dcode + 1, self._next_dcode) - aper = ADParamStmt.obround(dcode, width, height) + aper = ADParamStmt.obround(dcode, width, height, hole_diameter, hole_width, hole_height) self._obrounds[key] = aper self.header.append(aper) @@ -350,17 +356,22 @@ class Rs274xContext(GerberContext): def _render_obround(self, obround, color): - aper = self._get_obround(obround.width, obround.height) + aper = self._get_obround(obround.width, obround.height, + obround.hole_diameter, obround.hole_width, + obround.hole_height) self._render_flash(obround, aper) def _render_polygon(self, polygon, color): - aper = self._get_polygon(polygon.radius, polygon.sides, polygon.rotation, polygon.hole_radius) + aper = self._get_polygon(polygon.radius, polygon.sides, + polygon.rotation, polygon.hole_diameter, + polygon.hole_width, polygon.hole_height) self._render_flash(polygon, aper) - def _get_polygon(self, radius, num_vertices, rotation, hole_radius, dcode = None): + def _get_polygon(self, radius, num_vertices, rotation, hole_diameter=None, + hole_width=None, hole_height=None, dcode = None): - key = (radius, num_vertices, rotation, hole_radius) + key = (radius, num_vertices, rotation, hole_diameter, hole_width, hole_height) aper = self._polygons.get(key, None) if not aper: @@ -370,7 +381,9 @@ class Rs274xContext(GerberContext): else: self._next_dcode = max(dcode + 1, self._next_dcode) - aper = ADParamStmt.polygon(dcode, radius * 2, num_vertices, rotation, hole_radius * 2) + aper = ADParamStmt.polygon(dcode, radius * 2, num_vertices, + rotation, hole_diameter, hole_width, + hole_height) self._polygons[key] = aper self.header.append(aper) -- cgit From 5245fb925684b4ebe056e6509bfeca6b167903b5 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 5 Jun 2018 08:57:37 -0400 Subject: Fix hard requirement of cairo per #83, and add stubs for required subclass methods to GerberContext per #84 --- gerber/render/rs274x_backend.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index d32602a..30048c4 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -148,10 +148,10 @@ class Rs274xContext(GerberContext): def statements(self): return self.comments + self.header + self.body + self.end - def set_bounds(self, bounds): + def set_bounds(self, bounds, *args, **kwargs): pass - def _paint_background(self): + def paint_background(self): pass def _select_aperture(self, aperture): @@ -173,7 +173,7 @@ class Rs274xContext(GerberContext): self.body.append(ApertureStmt(aper.d)) self._dcode = aper.d - def _pre_render_primitive(self, primitive): + def pre_render_primitive(self, primitive): if hasattr(primitive, 'comment'): self.body.append(CommentStmt(primitive.comment)) @@ -489,11 +489,11 @@ class Rs274xContext(GerberContext): def _render_inverted_layer(self): pass - def _new_render_layer(self): + def new_render_layer(self): # TODO Might need to implement this pass - def _flatten(self): + def flatten(self): # TODO Might need to implement this pass -- cgit From a7a5981e0eb2b112a57c6ea1151eb2b88f798857 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 3 Feb 2019 13:42:44 +0900 Subject: Make primitives with unset level polarity inherit from region This fixes region rendering with programatically generated primitives such that clear level polarity works in an intuitive way. This is useful for e.g. cutouts in regions. Before, the renderer would set level polarity twice, both when starting the region and then again once for each region primitive (line or arc). The problem was that the primitives in a region with "clear" polarity would when constructed with unset polarity default to "dark". Thus the renderer would emit something like LPC (clear polarity) -> G36 (start region) -> LPD (dark polarity) -> {lines...} instead of LPC -> G36 -> {lines...}. After this commit, Line and Arc will retain None as level polarity when created with unset level polarity, and region rendering will override None with the region's polarity. Outside regions, the old dark default remains unchanged. Note on verification: Somehow, gEDA gerbv would still render the broken regions the way one would have intended, but other viewers (KiCAD gerbview, the online EasyEDA one and whatever JLC uses to make their silkscreens) would not. --- gerber/render/rs274x_backend.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'gerber/render/rs274x_backend.py') diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 30048c4..c7af2ea 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -178,11 +178,11 @@ class Rs274xContext(GerberContext): if hasattr(primitive, 'comment'): self.body.append(CommentStmt(primitive.comment)) - def _render_line(self, line, color): + def _render_line(self, line, color, default_polarity='dark'): self._select_aperture(line.aperture) - self._render_level_polarity(line) + self._render_level_polarity(line, default_polarity) # Get the right function if self._func != CoordStmt.FUNC_LINEAR: @@ -206,7 +206,7 @@ class Rs274xContext(GerberContext): elif func: self.body.append(CoordStmt.mode(func)) - def _render_arc(self, arc, color): + def _render_arc(self, arc, color, default_polarity='dark'): # Optionally set the quadrant mode if it has changed: if arc.quadrant_mode != self._quadrant_mode: @@ -221,7 +221,7 @@ class Rs274xContext(GerberContext): # Select the right aperture if not already selected self._select_aperture(arc.aperture) - self._render_level_polarity(arc) + self._render_level_polarity(arc, default_polarity) # Find the right movement mode. Always set to be sure it is really right dir = arc.direction @@ -252,20 +252,23 @@ class Rs274xContext(GerberContext): for p in region.primitives: + # Make programmatically generated primitives within a region with + # unset level polarity inherit the region's level polarity if isinstance(p, Line): - self._render_line(p, color) + self._render_line(p, color, default_polarity=region.level_polarity) else: - self._render_arc(p, color) + self._render_arc(p, color, default_polarity=region.level_polarity) if self.explicit_region_move_end: self.body.append(CoordStmt.move(None, None)) self.body.append(RegionModeStmt.off()) - def _render_level_polarity(self, region): - if region.level_polarity != self._level_polarity: - self._level_polarity = region.level_polarity - self.body.append(LPParamStmt.from_region(region)) + def _render_level_polarity(self, obj, default='dark'): + obj_polarity = obj.level_polarity if obj.level_polarity is not None else default + if obj_polarity != self._level_polarity: + self._level_polarity = obj_polarity + self.body.append(LPParamStmt('LP', obj_polarity)) def _render_flash(self, primitive, aperture): -- cgit