From 5245fb925684b4ebe056e6509bfeca6b167903b5 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 5 Jun 2018 08:57:37 -0400 Subject: Fix hard requirement of cairo per #83, and add stubs for required subclass methods to GerberContext per #84 --- examples/cairo_example.py | 3 +- examples/pcb_example.py | 3 +- gerber/cam.py | 10 +++-- gerber/render/__init__.py | 2 - gerber/render/cairo_backend.py | 13 +++--- gerber/render/excellon_backend.py | 91 +++++++++++++++++++-------------------- gerber/render/render.py | 27 ++++++++++-- gerber/render/rs274x_backend.py | 10 ++--- 8 files changed, 90 insertions(+), 69 deletions(-) diff --git a/examples/cairo_example.py b/examples/cairo_example.py index fcd7a44..ecfed4d 100644 --- a/examples/cairo_example.py +++ b/examples/cairo_example.py @@ -25,7 +25,8 @@ a .png file. import os from gerber import load_layer -from gerber.render import GerberCairoContext, RenderSettings, theme +from gerber.render import RenderSettings, theme +from gerber.render.cairo_backend import GerberCairoContext GERBER_FOLDER = os.path.abspath(os.path.join(os.path.dirname(__file__), 'gerbers')) diff --git a/examples/pcb_example.py b/examples/pcb_example.py index 9e066a8..34afee6 100644 --- a/examples/pcb_example.py +++ b/examples/pcb_example.py @@ -22,7 +22,8 @@ images using the PCB interface import os from gerber import PCB -from gerber.render import GerberCairoContext, theme +from gerber.render import theme +from gerber.render.cairo_backend import GerberCairoContext GERBER_FOLDER = os.path.abspath(os.path.join(os.path.dirname(__file__), 'gerbers')) diff --git a/gerber/cam.py b/gerber/cam.py index 15b801a..4f20283 100644 --- a/gerber/cam.py +++ b/gerber/cam.py @@ -250,6 +250,10 @@ class CamFile(object): """ pass + @property + def bounding_box(self): + pass + def to_inch(self): pass @@ -271,12 +275,12 @@ class CamFile(object): from .render import GerberCairoContext ctx = GerberCairoContext() ctx.set_bounds(self.bounding_box) - ctx._paint_background() + ctx.paint_background() ctx.invert = invert - ctx._new_render_layer() + ctx.new_render_layer() for p in self.primitives: ctx.render(p) - ctx._flatten() + ctx.flatten() if filename is not None: ctx.dump(filename) diff --git a/gerber/render/__init__.py b/gerber/render/__init__.py index 3598c4d..fe08d50 100644 --- a/gerber/render/__init__.py +++ b/gerber/render/__init__.py @@ -23,6 +23,4 @@ This module provides contexts for rendering images of gerber layers. Currently SVG is the only supported format. """ - -from .cairo_backend import GerberCairoContext from .render import RenderSettings diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 0e3a721..e1d1408 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -91,7 +91,7 @@ class GerberCairoContext(GerberContext): self.set_bounds(bounds) else: self.set_bounds(layer.bounds) - self._paint_background(bgsettings) + self.paint_background(bgsettings) if verbose: print('[Render]: Rendering {} Layer.'.format(layer.layer_class)) self._render_count += 1 @@ -193,11 +193,11 @@ class GerberCairoContext(GerberContext): def _render_layer(self, layer, settings): self.invert = settings.invert # Get a new clean layer to render on - self._new_render_layer(mirror=settings.mirror) + self.new_render_layer(mirror=settings.mirror) for prim in layer.primitives: self.render(prim) # Add layer to image - self._flatten(settings.color, settings.alpha) + self.flatten(settings.color, settings.alpha) def _render_line(self, line, color): start = self.scale_point(line.start) @@ -530,7 +530,7 @@ class GerberCairoContext(GerberContext): self.ctx.show_text(primitive.net_name) self.ctx.scale(1, -1) - def _new_render_layer(self, color=None, mirror=False): + def new_render_layer(self, color=None, mirror=False): size_in_pixels = self.scale_point(self.size_in_inch) matrix = copy.copy(self._xform_matrix) layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) @@ -548,8 +548,7 @@ class GerberCairoContext(GerberContext): self.active_layer = layer self.active_matrix = matrix - - def _flatten(self, color=None, alpha=None): + def flatten(self, color=None, alpha=None): color = color if color is not None else self.color alpha = alpha if alpha is not None else self.alpha self.output_ctx.set_source_rgba(color[0], color[1], color[2], alpha) @@ -558,7 +557,7 @@ class GerberCairoContext(GerberContext): self.active_layer = None self.active_matrix = None - def _paint_background(self, settings=None): + def paint_background(self, settings=None): color = settings.color if settings is not None else self.background_color alpha = settings.alpha if settings is not None else 1.0 if not self.has_bg: diff --git a/gerber/render/excellon_backend.py b/gerber/render/excellon_backend.py index da5b22b..765d68c 100644 --- a/gerber/render/excellon_backend.py +++ b/gerber/render/excellon_backend.py @@ -4,13 +4,13 @@ from ..excellon import DrillSlot from ..excellon_statements import * class ExcellonContext(GerberContext): - + MODE_DRILL = 1 - MODE_SLOT =2 - + MODE_SLOT =2 + def __init__(self, settings): GerberContext.__init__(self) - + # Statements that we write self.comments = [] self.header = [] @@ -18,57 +18,57 @@ class ExcellonContext(GerberContext): self.body_start = [RewindStopStmt()] self.body = [] self.start = [HeaderBeginStmt()] - + # 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 self._start_header() self._start_comments() - + def _start_header(self): """Create the header from the settings""" - + 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 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._get_end() - - def set_bounds(self, bounds): + + def set_bounds(self, bounds, *args, **kwargs): pass - - def _paint_background(self): + + def paint_background(self): pass - + def _render_line(self, line, color): raise ValueError('Invalid Excellon object') def _render_arc(self, arc, color): @@ -76,7 +76,7 @@ class ExcellonContext(GerberContext): def _render_region(self, region, color): raise ValueError('Invalid Excellon object') - + def _render_level_polarity(self, region): raise ValueError('Invalid Excellon object') @@ -85,105 +85,104 @@ class ExcellonContext(GerberContext): 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 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) self.header.append(ExcellonTool.from_tool(tool)) - + 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 _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 - + # 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_mode = 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 - \ No newline at end of file diff --git a/gerber/render/render.py b/gerber/render/render.py index 79f43d6..580a7ea 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -139,7 +139,7 @@ class GerberContext(object): if not primitive: return - self._pre_render_primitive(primitive) + self.pre_render_primitive(primitive) color = self.color if isinstance(primitive, Line): @@ -167,16 +167,35 @@ class GerberContext(object): elif isinstance(primitive, TestRecord): self._render_test_record(primitive, color) - self._post_render_primitive(primitive) + self.post_render_primitive(primitive) - def _pre_render_primitive(self, primitive): + def set_bounds(self, bounds, *args, **kwargs): + """Called by the renderer to set the extents of the file to render. + + Parameters + ---------- + bounds: Tuple[Tuple[float, float], Tuple[float, float]] + ( (x_min, x_max), (y_min, y_max) + """ + pass + + def paint_background(self): + pass + + def new_render_layer(self): + pass + + def flatten(self): + pass + + 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): + def post_render_primitive(self, primitive): """ Called after rendering a primitive. Use the callback to perform some action after rendering a primitive diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index d32602a..30048c4 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -148,10 +148,10 @@ class Rs274xContext(GerberContext): def statements(self): return self.comments + self.header + self.body + self.end - def set_bounds(self, bounds): + def set_bounds(self, bounds, *args, **kwargs): pass - def _paint_background(self): + def paint_background(self): pass def _select_aperture(self, aperture): @@ -173,7 +173,7 @@ class Rs274xContext(GerberContext): self.body.append(ApertureStmt(aper.d)) self._dcode = aper.d - def _pre_render_primitive(self, primitive): + def pre_render_primitive(self, primitive): if hasattr(primitive, 'comment'): self.body.append(CommentStmt(primitive.comment)) @@ -489,11 +489,11 @@ class Rs274xContext(GerberContext): def _render_inverted_layer(self): pass - def _new_render_layer(self): + def new_render_layer(self): # TODO Might need to implement this pass - def _flatten(self): + def flatten(self): # TODO Might need to implement this pass -- cgit