Fix hard requirement of cairo per #83, and add stubs for required subclass methods to GerberContext per #84

This commit is contained in:
Hamilton Kibbe 2018-06-05 08:57:37 -04:00
parent 31062ba2ce
commit 5245fb9256
8 changed files with 90 additions and 69 deletions

View file

@ -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'))

View file

@ -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'))

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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