From d5f382f4b413d73a96613dd86aa207bb9e665b0d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Mon, 23 Nov 2015 16:17:31 +0800 Subject: Render with cairo instead of cairocffi - I would like to make it use either, but for now, using the one that works with wxpython --- gerber/render/cairo_backend.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 345f331..e4a5eff 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -17,7 +17,7 @@ from .render import GerberContext -import cairocffi as cairo +import cairo from operator import mul import math @@ -52,7 +52,7 @@ class GerberCairoContext(GerberContext): end = map(mul, line.end, self.scale) if isinstance(line.aperture, Circle): width = line.aperture.diameter - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) @@ -61,7 +61,7 @@ class GerberCairoContext(GerberContext): self.ctx.stroke() elif isinstance(line.aperture, Rectangle): points = [tuple(map(mul, x, self.scale)) for x in line.vertices] - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.move_to(*points[0]) @@ -77,7 +77,7 @@ class GerberCairoContext(GerberContext): angle1 = arc.start_angle angle2 = arc.end_angle width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (arc.level_polarity == "dark" and not self.invert)else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) @@ -89,7 +89,8 @@ class GerberCairoContext(GerberContext): self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + #self.ctx.set_source_rgba(*color, alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (region.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) @@ -112,7 +113,7 @@ class GerberCairoContext(GerberContext): def _render_circle(self, circle, color): center = tuple(map(mul, circle.position, self.scale)) - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (circle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) @@ -121,7 +122,7 @@ class GerberCairoContext(GerberContext): def _render_rectangle(self, rectangle, color): ll = map(mul, rectangle.lower_left, self.scale) width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.rectangle(*ll,width=width, height=height) -- cgit From 8eede187f3f644c4f8a0de0dc5825dc4c00c7b8f Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Mon, 23 Nov 2015 22:22:30 +0800 Subject: More fixes to work with cairo --- gerber/render/cairo_backend.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index e4a5eff..81c5ce4 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -61,7 +61,7 @@ class GerberCairoContext(GerberContext): self.ctx.stroke() elif isinstance(line.aperture, Rectangle): points = [tuple(map(mul, x, self.scale)) for x in line.vertices] - self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.move_to(*points[0]) @@ -83,14 +83,13 @@ class GerberCairoContext(GerberContext): self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': - self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + self.ctx.arc(center[0], center[1], radius, angle1, angle2) else: - self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) + self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - #self.ctx.set_source_rgba(*color, alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (region.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) @@ -106,9 +105,9 @@ class GerberCairoContext(GerberContext): angle1 = p.start_angle angle2 = p.end_angle if p.direction == 'counterclockwise': - self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + self.ctx.arc(center[0], center[1], radius, angle1, angle2) else: - self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) + self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) self.ctx.fill() def _render_circle(self, circle, color): @@ -116,7 +115,7 @@ class GerberCairoContext(GerberContext): self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (circle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) - self.ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.arc(center[0], center[1], circle.radius * self.scale[0], 0, 2 * math.pi) self.ctx.fill() def _render_rectangle(self, rectangle, color): @@ -148,7 +147,7 @@ class GerberCairoContext(GerberContext): self.ctx.scale(1, -1) def _paint_inverted_layer(self): - self.ctx.set_source_rgba(*self.background_color) + self.ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2]) self.ctx.set_operator(cairo.OPERATOR_OVER) self.ctx.paint() self.ctx.set_operator(cairo.OPERATOR_CLEAR) @@ -156,7 +155,7 @@ class GerberCairoContext(GerberContext): def _paint_background(self): if not self.bg: self.bg = True - self.ctx.set_source_rgba(*self.background_color) + self.ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2]) self.ctx.paint() def dump(self, filename): -- cgit From d69f50e0f62570a4c327cb8fe4f886f439196010 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 2 Dec 2015 12:44:30 +0800 Subject: Make the hit accessible from the drawable Hit, fix crash with cario drawing rect --- gerber/render/cairo_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 81c5ce4..4a0724f 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -124,7 +124,7 @@ class GerberCairoContext(GerberContext): self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) - self.ctx.rectangle(*ll,width=width, height=height) + self.ctx.rectangle(ll[0], ll[1], width, height) self.ctx.fill() def _render_obround(self, obround, color): -- cgit From 206f4c57ab66f8a6753015340315991b40178c9b Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 16 Dec 2015 18:59:25 +0800 Subject: Fix drawing arcs. Dont crash for arcs with rectangular apertures. Fix crash with board size of zero for only one drill --- gerber/render/cairo_backend.py | 1 + 1 file changed, 1 insertion(+) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 4a0724f..4d71199 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -87,6 +87,7 @@ class GerberCairoContext(GerberContext): else: self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) self.ctx.move_to(*end) # ...lame + self.ctx.stroke() def _render_region(self, region, color): self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) -- cgit From 4a815bf25ddd1d378ec6ad5af008e5bbcd362b51 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 30 Dec 2015 14:05:00 +0800 Subject: First time any macro renders --- gerber/render/cairo_backend.py | 20 ++++++++++++++++++++ gerber/render/render.py | 5 +++++ 2 files changed, 25 insertions(+) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 4d71199..3ee38ae 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -122,11 +122,27 @@ class GerberCairoContext(GerberContext): def _render_rectangle(self, rectangle, color): ll = map(mul, rectangle.lower_left, self.scale) width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) + + if rectangle.rotation != 0: + self.ctx.save() + + center = map(mul, rectangle.position, self.scale) + matrix = cairo.Matrix() + matrix.translate(center[0], center[1]) + # For drawing, we already handles the translation + ll[0] = ll[0] - center[0] + ll[1] = ll[1] - center[1] + matrix.rotate(rectangle.rotation) + self.ctx.transform(matrix) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.rectangle(ll[0], ll[1], width, height) self.ctx.fill() + + if rectangle.rotation != 0: + self.ctx.restore() def _render_obround(self, obround, color): self._render_circle(obround.subshapes['circle1'], color) @@ -135,6 +151,10 @@ class GerberCairoContext(GerberContext): def _render_drill(self, circle, color): self._render_circle(circle, color) + + def _render_amgroup(self, amgroup, color): + for primitive in amgroup.primitives: + self.render(primitive) def _render_test_record(self, primitive, color): self.ctx.select_font_face('monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) diff --git a/gerber/render/render.py b/gerber/render/render.py index 8f49796..ac01e52 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -150,6 +150,8 @@ class GerberContext(object): self._render_polygon(primitive, color) elif isinstance(primitive, Drill): self._render_drill(primitive, self.drill_color) + elif isinstance(primitive, AMGroup): + self._render_amgroup(primitive, color) elif isinstance(primitive, TestRecord): self._render_test_record(primitive, color) else: @@ -178,6 +180,9 @@ class GerberContext(object): def _render_drill(self, primitive, color): pass + + def _render_amgroup(self, primitive, color): + pass def _render_test_record(self, primitive, color): pass -- cgit From 96692b22216fdfe11f2ded104ac0bdba3b7866a5 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 30 Dec 2015 15:32:44 +0800 Subject: Render primitives for some aperture macros --- gerber/render/render.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index ac01e52..b518385 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -152,6 +152,8 @@ class GerberContext(object): self._render_drill(primitive, self.drill_color) elif isinstance(primitive, AMGroup): self._render_amgroup(primitive, color) + elif isinstance(primitive, Outline): + self._render_region(primitive, color) elif isinstance(primitive, TestRecord): self._render_test_record(primitive, color) else: -- cgit From f61eee807f87c329f6f88645ecdb48f01b887c52 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 30 Dec 2015 18:44:07 +0800 Subject: Render polygon flashes --- gerber/render/cairo_backend.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 3ee38ae..68e9e98 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -148,6 +148,22 @@ class GerberCairoContext(GerberContext): self._render_circle(obround.subshapes['circle1'], color) self._render_circle(obround.subshapes['circle2'], color) self._render_rectangle(obround.subshapes['rectangle'], color) + + def _render_polygon(self, polygon, color): + vertices = polygon.vertices + + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (polygon.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + + # Start from before the end so it is easy to iterate and make sure it is closed + self.ctx.move_to(*map(mul, vertices[-1], self.scale)) + for v in vertices: + self.ctx.line_to(*map(mul, v, self.scale)) + + self.ctx.fill() + def _render_drill(self, circle, color): self._render_circle(circle, color) -- cgit From 83ae0670d11b5f5ef8ba3a6c362b7129a9e31ab3 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Fri, 8 Jan 2016 00:19:47 +0800 Subject: More stability fixes for poorly constructed files --- gerber/render/cairo_backend.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 68e9e98..fbc4271 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -76,7 +76,10 @@ class GerberCairoContext(GerberContext): radius = self.scale[0] * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle - width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 + if isinstance(arc.aperture, Circle): + width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 + else: + width = max(arc.aperture.width, arc.aperture.height, 0.001) self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (arc.level_polarity == "dark" and not self.invert)else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(width * self.scale[0]) @@ -163,7 +166,6 @@ class GerberCairoContext(GerberContext): self.ctx.line_to(*map(mul, v, self.scale)) self.ctx.fill() - def _render_drill(self, circle, color): self._render_circle(circle, color) -- cgit From 96bdd0f59dbda9b755b0eb28eb44cb9a6eae1410 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 31 Jan 2016 15:24:57 +0800 Subject: Keep track of quadrant mode so we can draw full circles --- gerber/render/cairo_backend.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index fbc4271..7be7e6a 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -76,6 +76,9 @@ class GerberCairoContext(GerberContext): radius = self.scale[0] * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle + if angle1 == angle2 and arc.quadrant_mode != 'single-quadrant': + # Make the angles slightly different otherwise Cario will draw nothing + angle2 -= 0.000000001 if isinstance(arc.aperture, Circle): width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 else: -- cgit 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') 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') 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') 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') 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') 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') 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/excellon_backend.py | 76 +++++++++++++++++++++++++++++++++++++++ gerber/render/rs274x_backend.py | 32 +++-------------- 2 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 gerber/render/excellon_backend.py (limited to 'gerber/render') diff --git a/gerber/render/excellon_backend.py b/gerber/render/excellon_backend.py new file mode 100644 index 0000000..bec8367 --- /dev/null +++ b/gerber/render/excellon_backend.py @@ -0,0 +1,76 @@ + +from .render import GerberContext +from ..excellon_statements import * + +class ExcellonContext(GerberContext): + + def __init__(self, settings): + GerberContext.__init__(self) + self.comments = [] + self.header = [] + self.tool_def = [] + self.body = [] + self.end = [EndOfProgramStmt()] + + self.handled_tools = set() + self.cur_tool = None + self.pos = (None, None) + + self.settings = settings + + self._start_header(settings) + + def _start_header(self, settings): + pass + + @property + def statements(self): + return self.comments + self.header + self.body + self.end + + def set_bounds(self, bounds): + pass + + def _paint_background(self): + pass + + def _render_line(self, line, color): + raise ValueError('Invalid Excellon object') + def _render_arc(self, arc, color): + raise ValueError('Invalid Excellon object') + + def _render_region(self, region, color): + raise ValueError('Invalid Excellon object') + + def _render_level_polarity(self, region): + raise ValueError('Invalid Excellon object') + + def _render_circle(self, circle, color): + raise ValueError('Invalid Excellon object') + + def _render_rectangle(self, rectangle, color): + raise ValueError('Invalid Excellon object') + + def _render_obround(self, obround, color): + raise ValueError('Invalid Excellon object') + + def _render_polygon(self, polygon, color): + raise ValueError('Invalid Excellon object') + + 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 _render_drill(self, drill, color): + + if not drill in self.handled_tools: + self.tool_def.append(drill.tool) + + if drill.tool != self.cur_tool: + self.body.append(ToolSelectionStmt(drill.tool.number)) + + point = self._simplify_point(drill.position) + self._pos = drill.position + self.body.append(CoordinateStmt.from_point()) + + def _render_inverted_layer(self): + pass + \ No newline at end of file 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') 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 d12f6385a434c02677bfbb7b075dd9d8e49627fe Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Thu, 24 Mar 2016 00:10:34 +0800 Subject: Basic rendering of excellon works, but still has issues --- gerber/render/excellon_backend.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/excellon_backend.py b/gerber/render/excellon_backend.py index bec8367..f5cec1a 100644 --- a/gerber/render/excellon_backend.py +++ b/gerber/render/excellon_backend.py @@ -10,11 +10,12 @@ class ExcellonContext(GerberContext): self.header = [] self.tool_def = [] self.body = [] + self.start = [HeaderBeginStmt()] self.end = [EndOfProgramStmt()] self.handled_tools = set() self.cur_tool = None - self.pos = (None, None) + self._pos = (None, None) self.settings = settings @@ -25,7 +26,7 @@ class ExcellonContext(GerberContext): @property def statements(self): - return self.comments + self.header + self.body + self.end + return self.start + self.comments + self.header + self.body + self.end def set_bounds(self, bounds): pass @@ -61,15 +62,17 @@ class ExcellonContext(GerberContext): def _render_drill(self, drill, color): - if not drill in self.handled_tools: - self.tool_def.append(drill.tool) + tool = drill.hit.tool + if not tool in self.handled_tools: + self.handled_tools.add(tool) + self.header.append(ExcellonTool.from_tool(tool)) - if drill.tool != self.cur_tool: - self.body.append(ToolSelectionStmt(drill.tool.number)) + if tool != self.cur_tool: + self.body.append(ToolSelectionStmt(tool.number)) point = self._simplify_point(drill.position) self._pos = drill.position - self.body.append(CoordinateStmt.from_point()) + self.body.append(CoordinateStmt.from_point(point)) def _render_inverted_layer(self): pass -- cgit From acde19f205898188c03a46e5d8a7a6a4d4637a2d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 26 Mar 2016 15:59:42 +0800 Subject: Support for the G85 slot statement --- gerber/render/cairo_backend.py | 14 ++++++++++++++ gerber/render/excellon_backend.py | 33 +++++++++++++++++++++++++++++---- gerber/render/render.py | 5 +++++ 3 files changed, 48 insertions(+), 4 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 7be7e6a..d895e5c 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -173,6 +173,20 @@ class GerberCairoContext(GerberContext): def _render_drill(self, circle, color): self._render_circle(circle, color) + def _render_slot(self, slot, color): + start = map(mul, slot.start, self.scale) + end = map(mul, slot.end, self.scale) + + width = slot.diameter + + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (slot.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(width * self.scale[0]) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) + self.ctx.line_to(*end) + self.ctx.stroke() + def _render_amgroup(self, amgroup, color): for primitive in amgroup.primitives: self.render(primitive) diff --git a/gerber/render/excellon_backend.py b/gerber/render/excellon_backend.py index f5cec1a..eb79f1b 100644 --- a/gerber/render/excellon_backend.py +++ b/gerber/render/excellon_backend.py @@ -9,6 +9,7 @@ class ExcellonContext(GerberContext): self.comments = [] self.header = [] self.tool_def = [] + self.body_start = [RewindStopStmt()] self.body = [] self.start = [HeaderBeginStmt()] self.end = [EndOfProgramStmt()] @@ -19,14 +20,22 @@ class ExcellonContext(GerberContext): self.settings = settings - self._start_header(settings) + self._start_header() + self._start_comments() - def _start_header(self, settings): - pass + def _start_header(self): + """Create the header from the settings""" + + self.header.append(UnitStmt.from_settings(self.settings)) + + def _start_comments(self): + + # Write the digits used - this isn't valid Excellon statement, so we write as a comment + self.comments.append(CommentStmt('FILE_FORMAT=%d:%d' % (self.settings.format[0], self.settings.format[1]))) @property def statements(self): - return self.start + self.comments + self.header + self.body + self.end + return self.start + self.comments + self.header + self.body_start + self.body + self.end def set_bounds(self, bounds): pass @@ -69,10 +78,26 @@ class ExcellonContext(GerberContext): if tool != self.cur_tool: self.body.append(ToolSelectionStmt(tool.number)) + self.cur_tool = tool point = self._simplify_point(drill.position) self._pos = drill.position self.body.append(CoordinateStmt.from_point(point)) + + def _render_slot(self, slot, color): + + tool = slot.hit.tool + if not tool in self.handled_tools: + self.handled_tools.add(tool) + self.header.append(ExcellonTool.from_tool(tool)) + + if tool != self.cur_tool: + self.body.append(ToolSelectionStmt(tool.number)) + self.cur_tool = tool + + # Slots don't use simplified points + self._pos = slot.end + self.body.append(SlotStmt.from_points(slot.start, slot.end)) def _render_inverted_layer(self): pass diff --git a/gerber/render/render.py b/gerber/render/render.py index b518385..a5ae38e 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -150,6 +150,8 @@ class GerberContext(object): self._render_polygon(primitive, color) elif isinstance(primitive, Drill): self._render_drill(primitive, self.drill_color) + elif isinstance(primitive, Slot): + self._render_slot(primitive, self.drill_color) elif isinstance(primitive, AMGroup): self._render_amgroup(primitive, color) elif isinstance(primitive, Outline): @@ -183,6 +185,9 @@ class GerberContext(object): def _render_drill(self, primitive, color): pass + def _render_slot(self, primitive, color): + pass + def _render_amgroup(self, primitive, color): pass -- cgit From 288f49955ecc1a811752aa4b1e713f9954e3033b Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 27 Mar 2016 14:24:11 +0800 Subject: Actually fix the rout rendering to be correct --- gerber/render/excellon_backend.py | 98 +++++++++++++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 9 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/excellon_backend.py b/gerber/render/excellon_backend.py index eb79f1b..b2c213f 100644 --- a/gerber/render/excellon_backend.py +++ b/gerber/render/excellon_backend.py @@ -1,21 +1,29 @@ from .render import GerberContext +from ..excellon import DrillSlot from ..excellon_statements import * class ExcellonContext(GerberContext): + MODE_DRILL = 1 + MODE_SLOT =2 + def __init__(self, settings): GerberContext.__init__(self) + + # Statements that we write self.comments = [] self.header = [] self.tool_def = [] self.body_start = [RewindStopStmt()] self.body = [] self.start = [HeaderBeginStmt()] - self.end = [EndOfProgramStmt()] + # Current tool and position self.handled_tools = set() self.cur_tool = None + self.drill_mode = ExcellonContext.MODE_DRILL + self.drill_down = False self._pos = (None, None) self.settings = settings @@ -33,9 +41,22 @@ class ExcellonContext(GerberContext): # Write the digits used - this isn't valid Excellon statement, so we write as a comment self.comments.append(CommentStmt('FILE_FORMAT=%d:%d' % (self.settings.format[0], self.settings.format[1]))) + def _get_end(self): + """How we end depends on our mode""" + + end = [] + + if self.drill_down: + end.append(RetractWithClampingStmt()) + end.append(RetractWithoutClampingStmt()) + + end.append(EndOfProgramStmt()) + + return end + @property def statements(self): - return self.start + self.comments + self.header + self.body_start + self.body + self.end + return self.start + self.comments + self.header + self.body_start + self.body + self._get_end() def set_bounds(self, bounds): pass @@ -71,6 +92,9 @@ class ExcellonContext(GerberContext): def _render_drill(self, drill, color): + if self.drill_mode != ExcellonContext.MODE_DRILL: + self._start_drill_mode() + tool = drill.hit.tool if not tool in self.handled_tools: self.handled_tools.add(tool) @@ -84,20 +108,76 @@ class ExcellonContext(GerberContext): self._pos = drill.position self.body.append(CoordinateStmt.from_point(point)) + def _start_drill_mode(self): + """ + If we are not in drill mode, then end the ROUT so we can do basic drilling + """ + + if self.drill_mode == ExcellonContext.MODE_SLOT: + + # Make sure we are retracted before changing modes + last_cmd = self.body[-1] + if self.drill_down: + self.body.append(RetractWithClampingStmt()) + self.body.append(RetractWithoutClampingStmt()) + self.drill_down = False + + # Switch to drill mode + self.body.append(DrillModeStmt()) + self.drill_mode = ExcellonContext.MODE_DRILL + + else: + raise ValueError('Should be in slot mode') + def _render_slot(self, slot, color): + # Set the tool first, before we might go into drill mode tool = slot.hit.tool if not tool in self.handled_tools: self.handled_tools.add(tool) self.header.append(ExcellonTool.from_tool(tool)) - - if tool != self.cur_tool: - self.body.append(ToolSelectionStmt(tool.number)) - self.cur_tool = tool - # Slots don't use simplified points - self._pos = slot.end - self.body.append(SlotStmt.from_points(slot.start, slot.end)) + if tool != self.cur_tool: + self.body.append(ToolSelectionStmt(tool.number)) + self.cur_tool = tool + + # Two types of drilling - normal drill and slots + if slot.hit.slot_type == DrillSlot.TYPE_ROUT: + + # For ROUT, setting the mode is part of the actual command. + + # Are we in the right position? + if slot.start != self._pos: + if self.drill_down: + # We need to move into the right position, so retract + self.body.append(RetractWithClampingStmt()) + self.drill_down = False + + # Move to the right spot + point = self._simplify_point(slot.start) + self._pos = slot.start + self.body.append(CoordinateStmt.from_point(point, mode="ROUT")) + + # Now we are in the right spot, so drill down + if not self.drill_down: + self.body.append(ZAxisRoutPositionStmt()) + self.drill_down = True + + # Do a linear move from our current position to the end position + point = self._simplify_point(slot.end) + self._pos = slot.end + self.body.append(CoordinateStmt.from_point(point, mode="LINEAR")) + + self.drill_down = ExcellonContext.MODE_SLOT + + else: + # This is a G85 slot, so do this in normally drilling mode + if self.drill_mode != ExcellonContext.MODE_DRILL: + self._start_drill_mode() + + # Slots don't use simplified points + self._pos = slot.end + self.body.append(SlotStmt.from_points(slot.start, slot.end)) def _render_inverted_layer(self): pass -- cgit From 2eac1e427ca3264cb6dc36e0712020c1ca73fa9c Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Tue, 5 Apr 2016 22:40:12 +0800 Subject: Fix converting values for excellon files. Give error for incremental mode --- gerber/render/excellon_backend.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'gerber/render') diff --git a/gerber/render/excellon_backend.py b/gerber/render/excellon_backend.py index b2c213f..c477036 100644 --- a/gerber/render/excellon_backend.py +++ b/gerber/render/excellon_backend.py @@ -36,6 +36,11 @@ class ExcellonContext(GerberContext): self.header.append(UnitStmt.from_settings(self.settings)) + if self.settings.notation == 'incremental': + raise NotImplementedError('Incremental mode is not implemented') + else: + self.body.append(AbsoluteModeStmt()) + def _start_comments(self): # Write the digits used - this isn't valid Excellon statement, so we write as a comment -- cgit From 199a0f3d3c5d4dbbc4ac6e8d1b4548523e44f00f Mon Sep 17 00:00:00 2001 From: Qau Lau Date: Fri, 8 Apr 2016 20:02:04 +0800 Subject: Update cairo_backend.py If cairo module import error use cairocffi --- gerber/render/cairo_backend.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index d895e5c..1d168ca 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -17,7 +17,10 @@ from .render import GerberContext -import cairo +try: + import cairo +except ImportError: + import cairocffi as cairo from operator import mul import math @@ -233,4 +236,4 @@ class GerberCairoContext(GerberContext): self.surface.finish() self.surface_buffer.flush() return self.surface_buffer.read() - \ No newline at end of file + -- cgit From 7fda8eb9f52b7be9cdf95807c036e3e1cfce3e7c Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 8 May 2016 22:13:08 +0800 Subject: Don't render null items --- gerber/render/render.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index a5ae38e..41b632c 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -132,6 +132,8 @@ class GerberContext(object): self._invert = invert def render(self, primitive): + if not primitive: + return color = (self.color if primitive.level_polarity == 'dark' else self.background_color) if isinstance(primitive, Line): -- 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') 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') 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') 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/excellon_backend.py | 10 +++++----- gerber/render/rs274x_backend.py | 13 +++++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/excellon_backend.py b/gerber/render/excellon_backend.py index c477036..da5b22b 100644 --- a/gerber/render/excellon_backend.py +++ b/gerber/render/excellon_backend.py @@ -142,9 +142,9 @@ class ExcellonContext(GerberContext): self.handled_tools.add(tool) self.header.append(ExcellonTool.from_tool(tool)) - if tool != self.cur_tool: - self.body.append(ToolSelectionStmt(tool.number)) - self.cur_tool = tool + if tool != self.cur_tool: + self.body.append(ToolSelectionStmt(tool.number)) + self.cur_tool = tool # Two types of drilling - normal drill and slots if slot.hit.slot_type == DrillSlot.TYPE_ROUT: @@ -172,8 +172,8 @@ class ExcellonContext(GerberContext): point = self._simplify_point(slot.end) self._pos = slot.end self.body.append(CoordinateStmt.from_point(point, mode="LINEAR")) - - self.drill_down = ExcellonContext.MODE_SLOT + + self.drill_mode = ExcellonContext.MODE_SLOT else: # This is a G85 slot, so do this in normally drilling mode 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 b01c4822b6da6b7be37becb73c58f60621f6366f Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 25 Jun 2016 12:27:28 +0800 Subject: Render aperture macros with clear regions --- gerber/render/cairo_backend.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 1d168ca..c1bd60c 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -191,8 +191,11 @@ class GerberCairoContext(GerberContext): self.ctx.stroke() def _render_amgroup(self, amgroup, color): + self.ctx.push_group() for primitive in amgroup.primitives: self.render(primitive) + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) def _render_test_record(self, primitive, color): self.ctx.select_font_face('monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) -- 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/cairo_backend.py | 15 +++++++++++++++ gerber/render/rs274x_backend.py | 26 ++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index c1bd60c..2b7c2ff 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -159,6 +159,9 @@ class GerberCairoContext(GerberContext): self._render_rectangle(obround.subshapes['rectangle'], color) def _render_polygon(self, polygon, color): + if polygon.hole_radius > 0: + self.ctx.push_group() + vertices = polygon.vertices self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) @@ -172,6 +175,18 @@ class GerberCairoContext(GerberContext): self.ctx.line_to(*map(mul, v, self.scale)) self.ctx.fill() + + if polygon.hole_radius > 0: + # Render the center clear + center = tuple(map(mul, polygon.position, self.scale)) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) + self.ctx.fill() + + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) def _render_drill(self, circle, color): self._render_circle(circle, color) 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/render.py | 21 +++++++++++++++++++-- gerber/render/rs274x_backend.py | 27 ++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index 41b632c..128496f 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -136,6 +136,9 @@ class GerberContext(object): return color = (self.color if primitive.level_polarity == 'dark' else self.background_color) + + self._pre_render_primitive(primitive) + if isinstance(primitive, Line): self._render_line(primitive, color) elif isinstance(primitive, Arc): @@ -160,8 +163,22 @@ class GerberContext(object): self._render_region(primitive, color) elif isinstance(primitive, TestRecord): self._render_test_record(primitive, color) - else: - return + + self._post_render_primitive(primitive) + + def _pre_render_primitive(self, primitive): + """ + Called before rendering a primitive. Use the callback to perform some action before rendering + a primitive, for example adding a comment. + """ + return + + def _post_render_primitive(self, primitive): + """ + Called after rendering a primitive. Use the callback to perform some action after rendering + a primitive + """ + return def _render_line(self, primitive, color): pass 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 f0585baefa54c5cd891ba04c81053956b1a59977 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 17 Jul 2016 13:14:54 +0800 Subject: Create first test that renders and validates the the rendered PNG is correct. --- gerber/render/cairo_backend.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 5a3c7a1..3c4a395 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -292,8 +292,7 @@ class GerberCairoContext(GerberContext): self.ctx.paint() def dump(self, filename): - is_svg = filename.lower().endswith(".svg") - if is_svg: + if filename and filename.lower().endswith(".svg"): self.surface.finish() self.surface_buffer.flush() with open(filename, "w") as f: @@ -301,7 +300,7 @@ class GerberCairoContext(GerberContext): f.write(self.surface_buffer.read()) f.flush() else: - self.surface.write_to_png(filename) + return self.surface.write_to_png(filename) def dump_svg_str(self): self.surface.finish() -- 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/cairo_backend.py | 105 ++++++++++++++++++++++++++++++++-------- gerber/render/rs274x_backend.py | 12 ++--- 2 files changed, 90 insertions(+), 27 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 0cb230b..78ccf34 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -20,13 +20,14 @@ try: except ImportError: import cairocffi as cairo -from operator import mul, div import math +from operator import mul, div import tempfile +from ..primitives import * from .render import GerberContext, RenderSettings from .theme import THEMES -from ..primitives import * + try: from cStringIO import StringIO @@ -219,15 +220,30 @@ class GerberCairoContext(GerberContext): center = tuple(map(mul, circle.position, self.scale)) if not self.invert: ctx = self.ctx - ctx.set_source_rgba(*color, alpha=self.alpha) + ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) ctx.set_operator(cairo.OPERATOR_OVER if circle.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: ctx = self.mask_ctx ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) ctx.set_operator(cairo.OPERATOR_CLEAR) + + if circle.hole_diameter > 0: + ctx.push_group() + ctx.set_line_width(0) ctx.arc(center[0], center[1], radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) - ctx.fill() + ctx.fill() + + if circle.hole_diameter > 0: + # Render the center clear + + ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + ctx.set_operator(cairo.OPERATOR_CLEAR) + ctx.arc(center[0], center[1], radius=circle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + ctx.fill() + + ctx.pop_group_to_source() + ctx.paint_with_alpha(1) def _render_rectangle(self, rectangle, color): ll = map(mul, rectangle.lower_left, self.scale) @@ -253,48 +269,95 @@ class GerberCairoContext(GerberContext): ll[1] = ll[1] - center[1] matrix.rotate(rectangle.rotation) ctx.transform(matrix) - + + if rectangle.hole_diameter > 0: + ctx.push_group() + ctx.set_line_width(0) ctx.rectangle(ll[0], ll[1], width, height) ctx.fill() + if rectangle.hole_diameter > 0: + # Render the center clear + ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + ctx.set_operator(cairo.OPERATOR_CLEAR) + center = map(mul, rectangle.position, self.scale) + ctx.arc(center[0], center[1], radius=rectangle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + ctx.fill() + + ctx.pop_group_to_source() + ctx.paint_with_alpha(1) + if rectangle.rotation != 0: ctx.restore() def _render_obround(self, obround, color): + + if not self.invert: + ctx = self.ctx + ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + ctx.set_operator(cairo.OPERATOR_OVER if obround.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + else: + ctx = self.mask_ctx + ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + ctx.set_operator(cairo.OPERATOR_CLEAR) + + if obround.hole_diameter > 0: + ctx.push_group() + self._render_circle(obround.subshapes['circle1'], color) self._render_circle(obround.subshapes['circle2'], color) self._render_rectangle(obround.subshapes['rectangle'], color) + if obround.hole_diameter > 0: + # Render the center clear + ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + ctx.set_operator(cairo.OPERATOR_CLEAR) + center = map(mul, obround.position, self.scale) + ctx.arc(center[0], center[1], radius=obround.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + ctx.fill() + + ctx.pop_group_to_source() + ctx.paint_with_alpha(1) + def _render_polygon(self, polygon, color): + + # TODO Ths does not handle rotation of a polygon + if not self.invert: + ctx = self.ctx + ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + ctx.set_operator(cairo.OPERATOR_OVER if polygon.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + else: + ctx = self.mask_ctx + ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + ctx.set_operator(cairo.OPERATOR_CLEAR) + if polygon.hole_radius > 0: - self.ctx.push_group() + ctx.push_group() vertices = polygon.vertices - - self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (polygon.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + + ctx.set_line_width(0) + ctx.set_line_cap(cairo.LINE_CAP_ROUND) # Start from before the end so it is easy to iterate and make sure it is closed - self.ctx.move_to(*map(mul, vertices[-1], self.scale)) + ctx.move_to(*map(mul, vertices[-1], self.scale)) for v in vertices: - self.ctx.line_to(*map(mul, v, self.scale)) + ctx.line_to(*map(mul, v, self.scale)) - self.ctx.fill() + ctx.fill() if polygon.hole_radius > 0: # Render the center clear center = tuple(map(mul, polygon.position, self.scale)) - self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) - self.ctx.fill() + ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + ctx.set_operator(cairo.OPERATOR_CLEAR) + ctx.set_line_width(0) + ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) + ctx.fill() - self.ctx.pop_group_to_source() - self.ctx.paint_with_alpha(1) + ctx.pop_group_to_source() + ctx.paint_with_alpha(1) def _render_drill(self, circle, color): self._render_circle(circle, color) 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') 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 8cd842a41a55ab3d8f558a2e3e198beba7da58a1 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Manually mere rendering changes --- gerber/render/cairo_backend.py | 395 +++++++++++++++++++++-------------------- gerber/render/render.py | 8 +- gerber/render/theme.py | 4 +- 3 files changed, 214 insertions(+), 193 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 78ccf34..349640a 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -12,7 +12,7 @@ # 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 +# See the License for the specific language governing permissions and # limitations under the License. try: @@ -21,7 +21,8 @@ except ImportError: import cairocffi as cairo import math -from operator import mul, div +from operator import mul, di + import tempfile from ..primitives import * @@ -36,11 +37,14 @@ except(ImportError): class GerberCairoContext(GerberContext): + def __init__(self, scale=300): - GerberContext.__init__(self) + super(GerberCairoContext, self).__init__() self.scale = (scale, scale) self.surface = None self.ctx = None + self.active_layer = None + self.output_ctx = None self.bg = False self.mask = None self.mask_ctx = None @@ -50,37 +54,40 @@ class GerberCairoContext(GerberContext): @property def origin_in_pixels(self): - return tuple(map(mul, self.origin_in_inch, self.scale)) if self.origin_in_inch is not None else (0.0, 0.0) + return (self.scale_point(self.origin_in_inch) + if self.origin_in_inch is not None else (0.0, 0.0)) @property def size_in_pixels(self): - return tuple(map(mul, self.size_in_inch, self.scale)) if self.size_in_inch is not None else (0.0, 0.0) + return (self.scale_point(self.size_in_inch) + if self.size_in_inch is not None else (0.0, 0.0)) def set_bounds(self, bounds, new_surface=False): origin_in_inch = (bounds[0][0], bounds[1][0]) - size_in_inch = (abs(bounds[0][1] - bounds[0][0]), abs(bounds[1][1] - bounds[1][0])) - size_in_pixels = tuple(map(mul, size_in_inch, self.scale)) + size_in_inch = (abs(bounds[0][1] - bounds[0][0]), + abs(bounds[1][1] - bounds[1][0])) + size_in_pixels = self.scale_point(size_in_inch) self.origin_in_inch = origin_in_inch if self.origin_in_inch is None else self.origin_in_inch self.size_in_inch = size_in_inch if self.size_in_inch is None else self.size_in_inch if (self.surface is None) or new_surface: self.surface_buffer = tempfile.NamedTemporaryFile() - self.surface = cairo.SVGSurface(self.surface_buffer, size_in_pixels[0], size_in_pixels[1]) - self.ctx = cairo.Context(self.surface) - self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) - self.ctx.scale(1, -1) - self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) - self.mask = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) - self.mask_ctx = cairo.Context(self.mask) - self.mask_ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) - self.mask_ctx.scale(1, -1) - self.mask_ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) - self._xform_matrix = cairo.Matrix(xx=1.0, yy=-1.0, x0=-self.origin_in_pixels[0], y0=self.size_in_pixels[1] + self.origin_in_pixels[1]) + self.surface = cairo.SVGSurface( + self.surface_buffer, size_in_pixels[0], size_in_pixels[1]) + self.output_ctx = cairo.Context(self.surface) + self.output_ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) + self.output_ctx.scale(1, -1) + self.output_ctx.translate(-(origin_in_inch[0] * self.scale[0]), + (-origin_in_inch[1] * self.scale[0]) - size_in_pixels[1]) + self._xform_matrix = cairo.Matrix(xx=1.0, yy=-1.0, + x0=-self.origin_in_pixels[0], + y0=self.size_in_pixels[1] + self.origin_in_pixels[1]) def render_layers(self, layers, filename, theme=THEMES['default']): """ Render a set of layers """ self.set_bounds(layers[0].bounds, True) self._paint_background(True) + for layer in layers: self._render_layer(layer, theme) self.dump(filename) @@ -117,46 +124,46 @@ class GerberCairoContext(GerberContext): self.color = settings.color self.alpha = settings.alpha self.invert = settings.invert + + # Get a new clean layer to render on + self._new_render_layer() if settings.mirror: raise Warning('mirrored layers aren\'t supported yet...') - if self.invert: - self._clear_mask() for prim in layer.primitives: self.render(prim) - if self.invert: - self._render_mask() + # Add layer to image + self._flatten() def _render_line(self, line, color): - start = map(mul, line.start, self.scale) - end = map(mul, line.end, self.scale) + start = [pos * scale for pos, scale in zip(line.start, self.scale)] + end = [pos * scale for pos, scale in zip(line.end, self.scale)] if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if line.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if line.level_polarity == "dark" + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) if isinstance(line.aperture, Circle): - width = line.aperture.diameter - ctx.set_line_width(width * self.scale[0]) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - - ctx.move_to(*start) - ctx.line_to(*end) - ctx.stroke() + width = line.aperture.diameter + self.ctx.set_line_width(width * self.scale[0]) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) + self.ctx.line_to(*end) + self.ctx.stroke() elif isinstance(line.aperture, Rectangle): - points = [tuple(map(mul, x, self.scale)) for x in line.vertices] - ctx.set_line_width(0) - ctx.move_to(*points[0]) + points = [self.scale_point(x) for x in line.vertices] + self.ctx.set_line_width(0) + self.ctx.move_to(*points[0]) for point in points[1:]: - ctx.line_to(*point) - ctx.fill() + self.ctx.line_to(*point) + self.ctx.fill() def _render_arc(self, arc, color): - center = map(mul, arc.center, self.scale) - start = map(mul, arc.start, self.scale) - end = map(mul, arc.end, self.scale) + center = self.scale_point(arc.center) + start = self.scale_point(arc.start) + end = self.scale_point(arc.end) radius = self.scale[0] * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle @@ -169,141 +176,137 @@ class GerberCairoContext(GerberContext): width = max(arc.aperture.width, arc.aperture.height, 0.001) if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if arc.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if arc.level_polarity == "dark"\ + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) - ctx.set_line_width(width * self.scale[0]) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - ctx.move_to(*start) # You actually have to do this... + self.ctx.set_line_width(width * self.scale[0]) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': - ctx.arc(center[0], center[1], radius, angle1, angle2) + self.ctx.arc(center[0], center[1], radius, angle1, angle2) else: - ctx.arc_negative(center[0], center[1], radius, angle1, angle2) - ctx.move_to(*end) # ...lame - ctx.stroke() + self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) + self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if region.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if region.level_polarity == "dark" + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) - - ctx.set_line_width(0) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - ctx.move_to(*tuple(map(mul, region.primitives[0].start, self.scale))) - for p in region.primitives: - if isinstance(p, Line): - ctx.line_to(*tuple(map(mul, p.end, self.scale))) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + + self.ctx.set_line_width(0) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*self.scale_point(region.primitives[0].start)) + for prim in region.primitives: + if isinstance(prim, Line): + self.ctx.line_to(*self.scale_point(prim.end)) else: - center = map(mul, p.center, self.scale) - start = map(mul, p.start, self.scale) - end = map(mul, p.end, self.scale) - radius = self.scale[0] * p.radius - angle1 = p.start_angle - angle2 = p.end_angle - if p.direction == 'counterclockwise': - ctx.arc(center[0], center[1], radius, angle1, angle2) + center = self.scale_point(prim.center) + radius = self.scale[0] * prim.radius + angle1 = prim.start_angle + angle2 = prim.end_angle + if prim.direction == 'counterclockwise': + self.ctx.arc(*center, radius=radius, + angle1=angle1, angle2=angle2) else: - ctx.arc_negative(center[0], center[1], radius, angle1, angle2) - ctx.fill() - + self.ctx.arc_negative(*center, radius=radius, + angle1=angle1, angle2=angle2) + self.ctx.fill() def _render_circle(self, circle, color): - center = tuple(map(mul, circle.position, self.scale)) + center = self.scale_point(circle.position) if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if circle.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if circle.level_polarity == "dark" + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) if circle.hole_diameter > 0: - ctx.push_group() + self.ctx.push_group() - ctx.set_line_width(0) - ctx.arc(center[0], center[1], radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) - ctx.fill() + self.ctx.set_line_width(0) + self.ctx.arc(center[0], center[1], radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() if circle.hole_diameter > 0: # Render the center clear - ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - ctx.set_operator(cairo.OPERATOR_CLEAR) - ctx.arc(center[0], center[1], radius=circle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) - ctx.fill() + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.arc(center[0], center[1], radius=circle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() - ctx.pop_group_to_source() - ctx.paint_with_alpha(1) + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) def _render_rectangle(self, rectangle, color): - ll = map(mul, rectangle.lower_left, self.scale) - width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) + lower_left = self.scale_point(rectangle.lower_left) + width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))]) if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if rectangle.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if rectangle.level_polarity == "dark" + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) if rectangle.rotation != 0: - ctx.save() + self.ctx.save() center = map(mul, rectangle.position, self.scale) matrix = cairo.Matrix() matrix.translate(center[0], center[1]) # For drawing, we already handles the translation - ll[0] = ll[0] - center[0] - ll[1] = ll[1] - center[1] + lower_left[0] = lower_left[0] - center[0] + lower_left[1] = lower_left[1] - center[1] matrix.rotate(rectangle.rotation) - ctx.transform(matrix) + self.ctx.transform(matrix) if rectangle.hole_diameter > 0: - ctx.push_group() + self.ctx.push_group() - ctx.set_line_width(0) - ctx.rectangle(ll[0], ll[1], width, height) - ctx.fill() + self.ctx.set_line_width(0) + self.ctx.rectangle(lower_left[0], lower_left[1], width, height) + self.ctx.fill() if rectangle.hole_diameter > 0: # Render the center clear - ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) center = map(mul, rectangle.position, self.scale) - ctx.arc(center[0], center[1], radius=rectangle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) - ctx.fill() + self.ctx.arc(center[0], center[1], radius=rectangle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() - ctx.pop_group_to_source() - ctx.paint_with_alpha(1) + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) if rectangle.rotation != 0: - ctx.restore() + self.ctx.restore() def _render_obround(self, obround, color): if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if obround.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if obround.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) if obround.hole_diameter > 0: - ctx.push_group() + self.ctx.push_group() self._render_circle(obround.subshapes['circle1'], color) self._render_circle(obround.subshapes['circle2'], color) @@ -311,55 +314,54 @@ class GerberCairoContext(GerberContext): if obround.hole_diameter > 0: # Render the center clear - ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) center = map(mul, obround.position, self.scale) - ctx.arc(center[0], center[1], radius=obround.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) - ctx.fill() + self.ctx.arc(center[0], center[1], radius=obround.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() - ctx.pop_group_to_source() - ctx.paint_with_alpha(1) + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) def _render_polygon(self, polygon, color): # TODO Ths does not handle rotation of a polygon if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if polygon.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if polygon.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) if polygon.hole_radius > 0: - ctx.push_group() + self.ctx.push_group() vertices = polygon.vertices - ctx.set_line_width(0) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.set_line_width(0) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) # Start from before the end so it is easy to iterate and make sure it is closed - ctx.move_to(*map(mul, vertices[-1], self.scale)) + self.ctx.move_to(*map(mul, vertices[-1], self.scale)) for v in vertices: - ctx.line_to(*map(mul, v, self.scale)) + self.ctx.line_to(*map(mul, v, self.scale)) - ctx.fill() + self.ctx.fill() if polygon.hole_radius > 0: # Render the center clear center = tuple(map(mul, polygon.position, self.scale)) - ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - ctx.set_operator(cairo.OPERATOR_CLEAR) - ctx.set_line_width(0) - ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) - ctx.fill() + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) + self.ctx.fill() - ctx.pop_group_to_source() - ctx.paint_with_alpha(1) + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) - def _render_drill(self, circle, color): + def _render_drill(self, circle, color=None): + color = color if color is not None else self.drill_color self._render_circle(circle, color) def _render_slot(self, slot, color): @@ -369,19 +371,17 @@ class GerberCairoContext(GerberContext): width = slot.diameter if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if slot.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if slot.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) - - ctx.set_line_width(width * self.scale[0]) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - ctx.move_to(*start) - ctx.line_to(*end) - ctx.stroke() + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + + self.ctx.set_line_width(width * self.scale[0]) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) + self.ctx.line_to(*end) + self.ctx.stroke() def _render_amgroup(self, amgroup, color): self.ctx.push_group() @@ -391,33 +391,52 @@ class GerberCairoContext(GerberContext): self.ctx.paint_with_alpha(1) def _render_test_record(self, primitive, color): - position = tuple(map(add, primitive.position, self.origin_in_inch)) + position = [pos + origin for pos, origin in zip(primitive.position, self.origin_in_inch)] self.ctx.set_operator(cairo.OPERATOR_OVER) - self.ctx.select_font_face('monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) + self.ctx.select_font_face( + 'monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) self.ctx.set_font_size(13) self._render_circle(Circle(position, 0.015), color) self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if primitive.level_polarity == "dark" else cairo.OPERATOR_CLEAR) - self.ctx.move_to(*[self.scale[0] * (coord + 0.015) for coord in position]) + self.ctx.set_operator( + cairo.OPERATOR_OVER if primitive.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) + self.ctx.move_to(*[self.scale[0] * (coord + 0.015) + for coord in position]) self.ctx.scale(1, -1) self.ctx.show_text(primitive.net_name) - self.ctx.scale(1, -1) - - def _clear_mask(self): - self.mask_ctx.set_operator(cairo.OPERATOR_OVER) - self.mask_ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=self.alpha) - self.mask_ctx.paint() - - def _render_mask(self): - self.ctx.set_operator(cairo.OPERATOR_OVER) - ptn = cairo.SurfacePattern(self.mask) + self.ctx.scale(1, -1) + + def _new_render_layer(self, color=None): + size_in_pixels = self.scale_point(self.size_in_inch) + layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) + ctx = cairo.Context(layer) + ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) + ctx.scale(1, -1) + ctx.translate(-(self.origin_in_inch[0] * self.scale[0]), + (-self.origin_in_inch[1] * self.scale[0]) + - size_in_pixels[1]) + if self.invert: + ctx.set_operator(cairo.OPERATOR_OVER) + ctx.set_source_rgba(*self.color, alpha=self.alpha) + ctx.paint() + self.ctx = ctx + self.active_layer = layer + + def _flatten(self): + self.output_ctx.set_operator(cairo.OPERATOR_OVER) + ptn = cairo.SurfacePattern(self.active_layer) ptn.set_matrix(self._xform_matrix) - self.ctx.set_source(ptn) - self.ctx.paint() + self.output_ctx.set_source(ptn) + self.output_ctx.paint() + self.ctx = None + self.active_layer = None def _paint_background(self, force=False): - if (not self.bg) or force: - self.bg = True - self.ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=1.0) - self.ctx.paint() - + if (not self.bg) or force: + self.bg = True + self.output_ctx.set_operator(cairo.OPERATOR_OVER) + self.output_ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=1.0) + self.output_ctx.paint() + + def scale_point(self, point): + return tuple([coord * scale for coord, scale in zip(point, self.scale)]) \ No newline at end of file diff --git a/gerber/render/render.py b/gerber/render/render.py index f521c44..7bd4c00 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -57,12 +57,14 @@ class GerberContext(object): alpha : float Rendering opacity. Between 0.0 (transparent) and 1.0 (opaque.) """ + def __init__(self, units='inch'): self._units = units self._color = (0.7215, 0.451, 0.200) self._background_color = (0.0, 0.0, 0.0) self._alpha = 1.0 self._invert = False + self.ctx = None @property def units(self): @@ -134,11 +136,10 @@ class GerberContext(object): def render(self, primitive): if not primitive: return - color = (self.color if primitive.level_polarity == 'dark' - else self.background_color) self._pre_render_primitive(primitive) + color = self.color if isinstance(primitive, Line): self._render_line(primitive, color) elif isinstance(primitive, Arc): @@ -180,6 +181,7 @@ class GerberContext(object): """ return + def _render_line(self, primitive, color): pass @@ -215,9 +217,9 @@ class GerberContext(object): class RenderSettings(object): + def __init__(self, color=(0.0, 0.0, 0.0), alpha=1.0, invert=False, mirror=False): self.color = color self.alpha = alpha self.invert = invert self.mirror = mirror - diff --git a/gerber/render/theme.py b/gerber/render/theme.py index e538df8..6135ccb 100644 --- a/gerber/render/theme.py +++ b/gerber/render/theme.py @@ -23,7 +23,7 @@ COLORS = { 'white': (1.0, 1.0, 1.0), 'red': (1.0, 0.0, 0.0), 'green': (0.0, 1.0, 0.0), - 'blue' : (0.0, 0.0, 1.0), + 'blue': (0.0, 0.0, 1.0), 'fr-4': (0.290, 0.345, 0.0), 'green soldermask': (0.0, 0.612, 0.396), 'blue soldermask': (0.059, 0.478, 0.651), @@ -36,6 +36,7 @@ COLORS = { class Theme(object): + def __init__(self, name=None, **kwargs): self.name = 'Default' if name is None else name self.background = kwargs.get('background', RenderSettings(COLORS['black'], alpha=0.0)) @@ -67,4 +68,3 @@ THEMES = { topmask=RenderSettings(COLORS['blue soldermask'], alpha=0.8, invert=True), bottommask=RenderSettings(COLORS['blue soldermask'], alpha=0.8, invert=True)), } - -- 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/cairo_backend.py | 2 +- gerber/render/rs274x_backend.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 349640a..dc39607 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -21,7 +21,7 @@ except ImportError: import cairocffi as cairo import math -from operator import mul, di +from operator import mul, div import tempfile 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 5af19af190c1fb0f0c5be029d46d63e657dde4d9 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Commit partial merge so I can work on the plane --- gerber/render/cairo_backend.py | 92 ++++++++++++++++++++++++++++++++++++++++-- gerber/render/render.py | 1 + 2 files changed, 90 insertions(+), 3 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index dc39607..8c7232f 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -12,6 +12,7 @@ # 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. @@ -22,14 +23,14 @@ except ImportError: import math from operator import mul, div - import tempfile +import cairocffi as cairo + from ..primitives import * from .render import GerberContext, RenderSettings from .theme import THEMES - try: from cStringIO import StringIO except(ImportError): @@ -138,20 +139,35 @@ class GerberCairoContext(GerberContext): start = [pos * scale for pos, scale in zip(line.start, self.scale)] end = [pos * scale for pos, scale in zip(line.end, self.scale)] if not self.invert: +<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if line.level_polarity == "dark" else cairo.OPERATOR_CLEAR) +======= + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if line.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) +>>>>>>> 5476da8... Fix a bunch of rendering bugs. else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) if isinstance(line.aperture, Circle): +<<<<<<< HEAD width = line.aperture.diameter +======= + width = line.aperture.diameter +>>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) self.ctx.line_to(*end) +<<<<<<< HEAD self.ctx.stroke() +======= + self.ctx.stroke() +>>>>>>> 5476da8... Fix a bunch of rendering bugs. elif isinstance(line.aperture, Rectangle): points = [self.scale_point(x) for x in line.vertices] self.ctx.set_line_width(0) @@ -176,6 +192,7 @@ class GerberCairoContext(GerberContext): width = max(arc.aperture.width, arc.aperture.height, 0.001) if not self.invert: +<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if arc.level_polarity == "dark"\ @@ -184,25 +201,50 @@ class GerberCairoContext(GerberContext): self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) +======= + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if arc.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + else: + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) +>>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': +<<<<<<< HEAD self.ctx.arc(center[0], center[1], radius, angle1, angle2) else: self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) +======= + self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + else: + self.ctx.arc_negative(*center, radius=radius, + angle1=angle1, angle2=angle2) +>>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): if not self.invert: +<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if region.level_polarity == "dark" +======= + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if region.level_polarity == 'dark' +>>>>>>> 5476da8... Fix a bunch of rendering bugs. else cairo.OPERATOR_CLEAR) else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) +<<<<<<< HEAD +======= +>>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.set_line_width(0) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*self.scale_point(region.primitives[0].start)) @@ -220,6 +262,7 @@ class GerberCairoContext(GerberContext): else: self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) +<<<<<<< HEAD self.ctx.fill() def _render_circle(self, circle, color): center = self.scale_point(circle.position) @@ -249,10 +292,28 @@ class GerberCairoContext(GerberContext): self.ctx.pop_group_to_source() self.ctx.paint_with_alpha(1) +======= + self.ctx.fill() + + def _render_circle(self, circle, color): + center = self.scale_point(circle.position) + if not self.invert: + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator( + cairo.OPERATOR_OVER if circle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) + else: + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.arc(*center, radius=circle.radius * + self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() +>>>>>>> 5476da8... Fix a bunch of rendering bugs. def _render_rectangle(self, rectangle, color): lower_left = self.scale_point(rectangle.lower_left) width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))]) +<<<<<<< HEAD if not self.invert: self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) @@ -295,6 +356,19 @@ class GerberCairoContext(GerberContext): if rectangle.rotation != 0: self.ctx.restore() +======= + + if not self.invert: + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator( + cairo.OPERATOR_OVER if rectangle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) + else: + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.rectangle(*lower_left, width=width, height=height) + self.ctx.fill() +>>>>>>> 5476da8... Fix a bunch of rendering bugs. def _render_obround(self, obround, color): @@ -424,7 +498,11 @@ class GerberCairoContext(GerberContext): def _flatten(self): self.output_ctx.set_operator(cairo.OPERATOR_OVER) +<<<<<<< HEAD ptn = cairo.SurfacePattern(self.active_layer) +======= + ptn = cairo.SurfacePattern(self.active_layer) +>>>>>>> 5476da8... Fix a bunch of rendering bugs. ptn.set_matrix(self._xform_matrix) self.output_ctx.set_source(ptn) self.output_ctx.paint() @@ -435,8 +513,16 @@ class GerberCairoContext(GerberContext): if (not self.bg) or force: self.bg = True self.output_ctx.set_operator(cairo.OPERATOR_OVER) +<<<<<<< HEAD self.output_ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=1.0) self.output_ctx.paint() def scale_point(self, point): - return tuple([coord * scale for coord, scale in zip(point, self.scale)]) \ No newline at end of file + return tuple([coord * scale for coord, scale in zip(point, self.scale)]) +======= + self.output_ctx.set_source_rgba(*self.background_color, alpha=1.0) + self.output_ctx.paint() + + def scale_point(self, point): + return tuple([coord * scale for coord, scale in zip(point, self.scale)]) +>>>>>>> 5476da8... Fix a bunch of rendering bugs. diff --git a/gerber/render/render.py b/gerber/render/render.py index 7bd4c00..b319648 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -182,6 +182,7 @@ class GerberContext(object): return + def _render_line(self, primitive, color): pass -- 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/render/cairo_backend.py | 167 +++++++++++------------------------------ 1 file changed, 43 insertions(+), 124 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 8c7232f..77d413e 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -12,15 +12,15 @@ # 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. - + +# See the License for the specific language governing permissions and +# limitations under the License. + try: import cairo except ImportError: import cairocffi as cairo - + import math from operator import mul, div import tempfile @@ -139,35 +139,20 @@ class GerberCairoContext(GerberContext): start = [pos * scale for pos, scale in zip(line.start, self.scale)] end = [pos * scale for pos, scale in zip(line.end, self.scale)] if not self.invert: -<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if line.level_polarity == "dark" - else cairo.OPERATOR_CLEAR) -======= - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER - if line.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) ->>>>>>> 5476da8... Fix a bunch of rendering bugs. + else cairo.OPERATOR_CLEAR) else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) if isinstance(line.aperture, Circle): -<<<<<<< HEAD - width = line.aperture.diameter -======= width = line.aperture.diameter ->>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) self.ctx.line_to(*end) -<<<<<<< HEAD - self.ctx.stroke() -======= self.ctx.stroke() ->>>>>>> 5476da8... Fix a bunch of rendering bugs. elif isinstance(line.aperture, Rectangle): points = [self.scale_point(x) for x in line.vertices] self.ctx.set_line_width(0) @@ -190,9 +175,8 @@ class GerberCairoContext(GerberContext): width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 else: width = max(arc.aperture.width, arc.aperture.height, 0.001) - + if not self.invert: -<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if arc.level_polarity == "dark"\ @@ -200,51 +184,26 @@ class GerberCairoContext(GerberContext): else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) - -======= - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER - if arc.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - else: - self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) ->>>>>>> 5476da8... Fix a bunch of rendering bugs. + self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': -<<<<<<< HEAD self.ctx.arc(center[0], center[1], radius, angle1, angle2) else: self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) -======= - self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) - else: - self.ctx.arc_negative(*center, radius=radius, - angle1=angle1, angle2=angle2) ->>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): if not self.invert: -<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if region.level_polarity == "dark" -======= - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER - if region.level_polarity == 'dark' ->>>>>>> 5476da8... Fix a bunch of rendering bugs. else cairo.OPERATOR_CLEAR) else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) -<<<<<<< HEAD -======= ->>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.set_line_width(0) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*self.scale_point(region.primitives[0].start)) @@ -262,8 +221,9 @@ class GerberCairoContext(GerberContext): else: self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) -<<<<<<< HEAD - self.ctx.fill() + + self.ctx.fill() + def _render_circle(self, circle, color): center = self.scale_point(circle.position) if not self.invert: @@ -274,47 +234,30 @@ class GerberCairoContext(GerberContext): else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) - + if circle.hole_diameter > 0: self.ctx.push_group() self.ctx.set_line_width(0) self.ctx.arc(center[0], center[1], radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) self.ctx.fill() - + if circle.hole_diameter > 0: # Render the center clear self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) self.ctx.arc(center[0], center[1], radius=circle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) self.ctx.fill() - - self.ctx.pop_group_to_source() - self.ctx.paint_with_alpha(1) -======= - self.ctx.fill() - def _render_circle(self, circle, color): - center = self.scale_point(circle.position) - if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator( - cairo.OPERATOR_OVER if circle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) - else: - self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.arc(*center, radius=circle.radius * - self.scale[0], angle1=0, angle2=2 * math.pi) - self.ctx.fill() ->>>>>>> 5476da8... Fix a bunch of rendering bugs. + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) def _render_rectangle(self, rectangle, color): lower_left = self.scale_point(rectangle.lower_left) width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))]) -<<<<<<< HEAD - + + if not self.invert: self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER @@ -323,10 +266,10 @@ class GerberCairoContext(GerberContext): else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) - + if rectangle.rotation != 0: self.ctx.save() - + center = map(mul, rectangle.position, self.scale) matrix = cairo.Matrix() matrix.translate(center[0], center[1]) @@ -335,14 +278,14 @@ class GerberCairoContext(GerberContext): lower_left[1] = lower_left[1] - center[1] matrix.rotate(rectangle.rotation) self.ctx.transform(matrix) - + if rectangle.hole_diameter > 0: self.ctx.push_group() self.ctx.set_line_width(0) self.ctx.rectangle(lower_left[0], lower_left[1], width, height) self.ctx.fill() - + if rectangle.hole_diameter > 0: # Render the center clear self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) @@ -350,42 +293,30 @@ class GerberCairoContext(GerberContext): center = map(mul, rectangle.position, self.scale) self.ctx.arc(center[0], center[1], radius=rectangle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) self.ctx.fill() - + self.ctx.pop_group_to_source() self.ctx.paint_with_alpha(1) - + if rectangle.rotation != 0: - self.ctx.restore() -======= + self.ctx.restore() - if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator( - cairo.OPERATOR_OVER if rectangle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) - else: - self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.rectangle(*lower_left, width=width, height=height) - self.ctx.fill() ->>>>>>> 5476da8... Fix a bunch of rendering bugs. def _render_obround(self, obround, color): - + if not self.invert: self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if obround.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) - + if obround.hole_diameter > 0: self.ctx.push_group() self._render_circle(obround.subshapes['circle1'], color) self._render_circle(obround.subshapes['circle2'], color) self._render_rectangle(obround.subshapes['rectangle'], color) - + if obround.hole_diameter > 0: # Render the center clear self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) @@ -393,12 +324,12 @@ class GerberCairoContext(GerberContext): center = map(mul, obround.position, self.scale) self.ctx.arc(center[0], center[1], radius=obround.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) self.ctx.fill() - + self.ctx.pop_group_to_source() self.ctx.paint_with_alpha(1) def _render_polygon(self, polygon, color): - + # TODO Ths does not handle rotation of a polygon if not self.invert: self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) @@ -406,44 +337,44 @@ class GerberCairoContext(GerberContext): else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) - + if polygon.hole_radius > 0: self.ctx.push_group() - - vertices = polygon.vertices + + vertices = polygon.vertices self.ctx.set_line_width(0) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - + # Start from before the end so it is easy to iterate and make sure it is closed self.ctx.move_to(*map(mul, vertices[-1], self.scale)) for v in vertices: self.ctx.line_to(*map(mul, v, self.scale)) self.ctx.fill() - + if polygon.hole_radius > 0: # Render the center clear center = tuple(map(mul, polygon.position, self.scale)) self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) self.ctx.fill() - + self.ctx.pop_group_to_source() self.ctx.paint_with_alpha(1) def _render_drill(self, circle, color=None): color = color if color is not None else self.drill_color self._render_circle(circle, color) - + def _render_slot(self, slot, color): start = map(mul, slot.start, self.scale) end = map(mul, slot.end, self.scale) - + width = slot.diameter - + if not self.invert: self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if slot.level_polarity == "dark" else cairo.OPERATOR_CLEAR) @@ -456,7 +387,7 @@ class GerberCairoContext(GerberContext): self.ctx.move_to(*start) self.ctx.line_to(*end) self.ctx.stroke() - + def _render_amgroup(self, amgroup, color): self.ctx.push_group() for primitive in amgroup.primitives: @@ -478,7 +409,7 @@ class GerberCairoContext(GerberContext): for coord in position]) self.ctx.scale(1, -1) self.ctx.show_text(primitive.net_name) - self.ctx.scale(1, -1) + self.ctx.scale(1, -1) def _new_render_layer(self, color=None): size_in_pixels = self.scale_point(self.size_in_inch) @@ -498,11 +429,7 @@ class GerberCairoContext(GerberContext): def _flatten(self): self.output_ctx.set_operator(cairo.OPERATOR_OVER) -<<<<<<< HEAD - ptn = cairo.SurfacePattern(self.active_layer) -======= ptn = cairo.SurfacePattern(self.active_layer) ->>>>>>> 5476da8... Fix a bunch of rendering bugs. ptn.set_matrix(self._xform_matrix) self.output_ctx.set_source(ptn) self.output_ctx.paint() @@ -510,19 +437,11 @@ class GerberCairoContext(GerberContext): self.active_layer = None def _paint_background(self, force=False): - if (not self.bg) or force: + if (not self.bg) or force: self.bg = True self.output_ctx.set_operator(cairo.OPERATOR_OVER) -<<<<<<< HEAD self.output_ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=1.0) self.output_ctx.paint() def scale_point(self, point): return tuple([coord * scale for coord, scale in zip(point, self.scale)]) -======= - self.output_ctx.set_source_rgba(*self.background_color, alpha=1.0) - self.output_ctx.paint() - - def scale_point(self, point): - return tuple([coord * scale for coord, scale in zip(point, self.scale)]) ->>>>>>> 5476da8... Fix a bunch of rendering bugs. -- cgit From d7a0f3ad2b62402f4af9abe2729f9a7c14814fe6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 6 Nov 2016 14:58:32 -0500 Subject: Remove debug print" --- gerber/render/cairo_backend.py | 1 - 1 file changed, 1 deletion(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 2ba022f..810351b 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -131,7 +131,6 @@ class GerberCairoContext(GerberContext): f.write(self.surface_buffer.read()) f.flush() else: - print("Wriitng To Png: filename: {}".format(filename)) return self.surface.write_to_png(filename) def dump_str(self): -- cgit From 6db0658e2336d6405fe1a6acd10dfab39ba8e7ff Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 6 Nov 2016 15:08:00 -0500 Subject: Fix tests on python3 --- gerber/render/cairo_backend.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 810351b..6e95446 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -21,8 +21,7 @@ try: except ImportError: import cairocffi as cairo -import math -from operator import mul, div +from operator import mul import tempfile import copy import os -- cgit