def __init__(self, pin_number, p1, p2, label=None): self.label = label # is a Label self.p1 = Point(p1) # null end self.p2 = Point(p2) # connect end self.pin_number = pin_number self.attributes = dict() self.styles = dict()
def _make_line(self, p1, p2, aperture): x1, y1 = float(p1[0]), float(p1[1]) x2, y2 = float(p2[0]), float(p2[1]) aperture = float(aperture) dx = x2 - x1 dy = y2 - y1 length = math.sqrt(dx * dx + dy * dy) if length == 0.0: return [] result = [] line1 = Line((x1 - aperture * (y2 - y1) / length, y1 - aperture * (x1 - x2) / length), (x2 - aperture * (y2 - y1) / length, y2 - aperture * (x1 - x2) / length)) line2 = Line((x1 + aperture * (y2 - y1) / length, y1 + aperture * (x1 - x2) / length), (x2 + aperture * (y2 - y1) / length, y2 + aperture * (x1 - x2) / length)) result.append(line1) result.append(line2) def make_arc(p1, p2, p0): start_angle = math.atan2(p1.y - p0.y, p1.x - p0.x) / math.pi end_angle = math.atan2(p2.y - p0.y, p2.x - p0.x) / math.pi return Arc(p0.x, p0.y, start_angle, end_angle, aperture) result.append(make_arc(line1.p1, line2.p1, Point(p1))) result.append(make_arc(line2.p2, line1.p2, Point(p2))) return result
def draw_schematic(self): """ Render the schematic into self.img """ # start off with all the component instances for inst in self.design.component_instances: comp = self.design.components.components[inst.library_id] for body, attr in zip(comp.symbols[inst.symbol_index].bodies, inst.symbol_attributes): # draw the appropriate body, at the position in attr pos = Point(attr.x, attr.y) self.draw_symbol(body, pos, attr.rotation, attr.flip) # draw in any annotations for ann in attr.annotations: if ann.visible: pos = self.base_xform.chain(Point(ann.x, ann.y)) self.canvas.text((pos.x, pos.y), ann.value, fill=self.options.style['annot']) for shape in self.design.shapes: draw_method = getattr(self, 'draw_shape_%s' % shape.type) draw_method(shape, self.base_xform, self.options.style['annot']) for net in self.design.nets: self.draw_net(net) for ann in self.design.design_attributes.annotations: if ann.visible: pos = self.base_xform.chain(Point(ann.x, ann.y)) self.canvas.text((pos.x, pos.y), ann.value, fill=self.options.style['annot'])
def bounds(self): """ Return the min and max point of a design """ bounds = [net.bounds() for net in self.nets] bounds.extend([anno.bounds() for anno in self.design_attributes.annotations]) offset_bounds = lambda (p1, p2), (xo, yo): [Point(p1.x + xo, p1.y + yo), Point(p2.x + xo, p2.y + yo)] for comp in self.component_instances: offsets = [(att.x, att.y) for att in comp.symbol_attributes] lib_comp = self.components.components[comp.library_id] bodybounds = [b.bounds() for b in lib_comp.symbols[comp.symbol_index].bodies] # the offsets in symbol_attributes will align and apply to the # library components bodies bounds.extend([offset_bounds(b, o) for b, o in zip(bodybounds, offsets)]) # flatten out bounds to just a list of Points bounds = sum(bounds, []) x_values = [pt.x for pt in bounds] y_values = [pt.y for pt in bounds] # by convention, an empty design will bound just the origin if len(x_values) == 0: x_values = [0] y_values = [0] return [Point(min(x_values), min(y_values)), Point(max(x_values), max(y_values))]
def __init__(self, pin_number, p, shapes, label=None): self.label = label # is a Label self.p = Point(p) self.pin_number = pin_number self.shapes = shapes self.attributes = dict() self.styles = dict()
def test_pin_bounds(self): '''Test bounds() for individual pins''' pin = Pin(0, Point(2, 5), Point(4, 3)) top_left, bottom_right = pin.bounds() self.assertEqual(top_left.x, 2) self.assertEqual(top_left.y, 3) self.assertEqual(bottom_right.x, 4) self.assertEqual(bottom_right.y, 5)
def subtest_pin(self): """ Common tests for both pin test cases """ k, v = self.sym.parse_pin('13 2 4 3 5 0 0 0') self.assertEqual(k, 'pin') self.assertEqual(v.p1, Point(3, 5)) self.assertEqual(v.p2, Point(2, 4)) self.assertEqual(v.pin_number, 13) return(v)
def test_line_one_seg(self): """One line segment, from (2,3) to (4,7)""" k, v = self.base.parse_line('2 1 3 4 7') self.assertEqual(k, 'lines') self.assertEqual(len(v), 1) self.assertEqual(v[0].type, 'line') p1, p2 = v[0].p1, v[0].p2 self.assertEqual(p1, Point(1, 3)) self.assertEqual(p2, Point(4, 7))
def bounds(self): """ Return the min and max points of a pin """ x_values = [self.p1.x, self.p2.x] y_values = [self.p1.y, self.p2.y] if self.label is not None: x_values.extend([pt.x for pt in self.label.bounds()]) y_values.extend([pt.y for pt in self.label.bounds()]) return [Point(min(x_values), min(y_values)), Point(max(x_values), max(y_values))]
def test_create_new_line(self): """ Test the creation of a new empty line. """ p1 = Point(0, 1) p2 = Point(2, 3) line = Line(p1, p2) assert line.p1.x == p1.x assert line.p1.y == p1.y assert line.p2.x == p2.x assert line.p2.y == p2.y
def parse_trace_segments(self, segments_json): if segments_json is None: return None for segment_json in segments_json: p1 = Point(segment_json['p1']['x'], segment_json['p1']['y']) p2 = Point(segment_json['p2']['x'], segment_json['p2']['y']) segment = Segment(segment_json['layer'], p1, p2, segment_json['width']) self.design.trace_segments.append(segment)
def test_point_equality(self): '''pt1 == pt2 iff (pt1.x == pt2.x and pt1.y == pt2.y)''' pta = Point(2, 3) ptb = Point(2, 3) ptc = Point(2, 4) ptd = Point(4, 3) pte = Point(4, 5) self.assertEqual(pta, ptb) for pt in (ptc, ptd, pte): self.assertNotEqual(pta, pt)
def bounds(self): """ Return the min and max points of the bounding box """ x_values = [p.x for p in self.points.values()] y_values = [p.y for p in self.points.values()] # get a list of all the bounding points for annotations bounds = sum([ann.bounds() for ann in self.annotations], []) x_values.extend([pt.x for pt in bounds]) y_values.extend([pt.y for pt in bounds]) return [Point(min(x_values), min(y_values)), Point(max(x_values), max(y_values))]
def test_pin_label_bounds(self): '''Test bounds() for a pin with a label''' lab = Label(0, 0, 'foo', align='left', rotation=0) mkbounds(lab, 1, 3, 2, 6) pin = Pin(0, Point(2, 2), Point(4, 3), lab) top_left, bottom_right = pin.bounds() self.assertEqual(top_left.x, 1) self.assertEqual(top_left.y, 2) self.assertEqual(bottom_right.x, 4) self.assertEqual(bottom_right.y, 6)
def draw_shape_arc(self, arc, xform, colour): """ draw an arc segment """ x, y, r = arc.x, arc.y, arc.radius # if the arc segment were extended to draw a full circle, box would # enclose that circle minpt, maxpt = [ xform.chain(Point(px, py)) for (px, py) in [(x - r, y - r), (x + r, y + r)] ] xs, ys = [minpt.x, maxpt.x], [minpt.y, maxpt.y] box = (min(xs), min(ys), max(xs), max(ys)) center = xform.chain(Point(x, y)) def pt_to_deg(pt): # given a point, give the angle w.r.t. to the xform'd center of the # arc (ie. where it will be when drawn) # 3 o'clock is angle of 0, angles increase clockwise opp, adj = pt.y - center.y, pt.x - center.x if adj == 0: if opp > 0: return 90 return 270 angle = 180 * atan(opp / float(adj)) / pi if pt.x < center.x: angle += 180 return int(angle % 360) # create a point in the middle of the arc (used to detect that the xform # has flipped the arc around. In that case, drawing from start_angle to # end_angle will go in the wrong direction, and draw out exactly the # wrong part of the circle) mid_ang = (arc.start_angle + arc.end_angle) / 2 if arc.start_angle > arc.end_angle: mid_ang = (mid_ang - 1) % 2 mid_pt = xform.chain( Point( cos((2 - mid_ang) * pi) * arc.radius + x, sin((2 - mid_ang) * pi) * arc.radius + y)) start, end = [xform.chain(pt) for pt in arc.ends()] if pt_to_deg(start) < pt_to_deg(end): if not (pt_to_deg(start) < pt_to_deg(mid_pt) < pt_to_deg(end)): # swap start and end so that the arc traces through the # transformed midpoint start, end = end, start elif (pt_to_deg(end) < pt_to_deg(mid_pt) < pt_to_deg(start)): # swap start and end so that the arc traces through the # transformed midpoint start, end = end, start # by using the arc.ends() points, any rotation in xform gets handled # properly. self.canvas.arc(box, pt_to_deg(start), pt_to_deg(end), fill=colour)
def bounds(self): """ Return the min and max points of the bounding box around a body """ points = sum([s.bounds() for s in self.shapes + self.pins], []) x_values = [pt.x for pt in points] y_values = [pt.y for pt in points] if len(x_values) == 0: # Empty body includes just the origin x_values = [0] y_values = [0] return [Point(min(x_values), min(y_values)), Point(max(x_values), max(y_values))]
def test_create_point_from_point(self): '''Test Point constructor when cloning another point''' oldpnt = Point(2, 3) newpnt = Point(oldpnt) self.assertFalse(oldpnt is newpnt) self.assertEqual(oldpnt.x, newpnt.x) self.assertEqual(oldpnt.y, newpnt.y) oldpnt.x = 4 oldpnt.y = 5 self.assertEqual(newpnt.x, 2) self.assertEqual(newpnt.y, 3)
def test_line_max_point(self): '''Test Line.max_point() in different situations''' line = Line(Point(2, 3), Point(4, 5)) bottom_right = line.max_point() self.assertEqual(bottom_right.x, 4) self.assertEqual(bottom_right.y, 5) line = Line(Point(2, 3), Point(-1, 4)) bottom_right = line.max_point() self.assertEqual(bottom_right.x, 2) self.assertEqual(bottom_right.y, 4)
def test_line_min_point(self): '''Test Line.min_point() in different situations''' line = Line(Point(2, 3), Point(4, 5)) top_left = line.min_point() self.assertEqual(top_left.x, 2) self.assertEqual(top_left.y, 3) line = Line(Point(2, 3), Point(-1, 4)) top_left = line.min_point() self.assertEqual(top_left.x, -1) self.assertEqual(top_left.y, 3)
def test_create_new_pin(self): """ Test the creation of a new empty pin. """ p1 = Point(0, 1) p2 = Point(2, 3) pin = Pin(0, p1, p2, 'abc') assert pin.label == 'abc' assert pin.p1.x == p1.x assert pin.p1.y == p1.y assert pin.p2.x == p2.x assert pin.p2.y == p2.y assert pin.pin_number == 0
def test_line_multi_seg(self): """A multi-segment line, should become many Line objects""" pts = [(2, 1), (4, 5), (8, 7), (9, 2)] pts_text = ' '.join([str(p1) + ' ' + str(p2) for p1, p2 in pts]) k, v = self.base.parse_line(str(len(pts)) + ' ' + pts_text) self.assertEqual(k, 'lines') self.assertEqual(len(v), len(pts) - 1) # line segments returned out of order, or with p1/p2 swapped, would be # acceptable. The test would need to change for that. for i, line in enumerate(v): self.assertEqual(line.type, 'line') self.assertEqual(Point(pts[i]), line.p1) self.assertEqual(Point(pts[i + 1]), line.p2)
def test_create_new_bezier_curve(self): """ Test the creation of a new empty bezier. """ control1 = Point(2, 9) control2 = Point(9, 8) p1 = Point(1, 1) p2 = Point(7, 2) assert self.curve.control1.x == control1.x assert self.curve.control1.y == control1.y assert self.curve.control2.x == control2.x assert self.curve.control2.y == control2.y assert self.curve.p1.x == p1.x assert self.curve.p1.y == p1.y assert self.curve.p2.x == p2.x assert self.curve.p2.y == p2.y
def _do_funct(self, block): """ Set drawing modes, fill terminators. """ code = int(block.code) if 'D' in block.type_: if code < 10: effect = {'draw': D_MAP[code]} # flash current pos/aperture if 'X' in block.type_: apertures = self.layer_buff.apertures aperture = apertures[self.status['aperture']] pos = Point(self.status['x'], self.status['y']) shape_inst = ShapeInstance(pos, aperture) self.img_buff.shape_instances.append(shape_inst) # terminate fill mid mode if (self.status['outline_fill'] and code == 2 and self.fill_buff): self.img_buff.fills.append(self._check_fill()) else: effect = {'aperture': block.code} else: effect = G_MAP[code] if code == 37: # terminate fill if D02 was not specified if self.fill_buff: self.img_buff.fills.append(self._check_fill()) return effect
def test_bounds_pins(self): '''Test bounds() with just pins included''' pins = [Pin(str(i), Point(0, 0), Point(0, 0)) for i in range(4)] # checking body.bounds(), not the pins, so override their bounds() # methods for i, pin in enumerate(pins): bounds = [3, 3, 3, 3] bounds[i] = 2 * i mkbounds(pin, bounds[0], bounds[1], bounds[2], bounds[3]) self.bod.add_pin(pin) top_left, bottom_right = self.bod.bounds() self.assertEqual(top_left.x, 0) self.assertEqual(top_left.y, 2) self.assertEqual(bottom_right.x, 4) self.assertEqual(bottom_right.y, 6)
def __init__(self, symbol_dirs=None): """ Constructs a new GEDA object and initialises it. *symbol_dirs* expects a list of directories. It will search for .sym files in all the specified directories. """ if symbol_dirs is None: symbol_dirs = [] symbol_dirs = symbol_dirs + \ [os.path.join(os.path.dirname(__file__), '..', 'library', 'geda')] self.known_symbols = find_symbols(symbol_dirs) ## offset used as bottom left origin as default ## in gEDA when starting new file self.offset = Point(0, 0) self.component_library = None ##NOTE: special attributes that are processed ## separately and will not be used as regular attributes self.ignored_attributes = [ '_prefix', '_suffix', '_refdes', '_name', '_geda_imported', ] self.project_dirs = { 'symbol': None, 'project': None, }
def draw_net(self, net): """ draw out a net """ # need a second dict so that removing nets as they are drawn does # not affect the actual design object. connects = dict([(pt.point_id, list(pt.connected_points)) for pt in net.points.values()]) for pid, connlist in connects.items(): pidpt = self.base_xform.chain(net.points[pid]) for junc in connlist: juncpt = self.base_xform.chain(net.points[junc]) # draw a line to each connected point from this junction self.canvas.line([(pidpt.x, pidpt.y), (juncpt.x, juncpt.y)], fill=self.options.style['net']) # don't need the connected point to draw a line back connects[junc].remove(pid) # TODO draw the connection to the component pin # (may actually be done) for pt in net.points.values(): if self.dot_at(pt, net): drawpt = self.base_xform.chain(pt) # arbitrarily, drawing the dot 4x the minimum dimension in the # design + 1 pixel. scale = self.options.scale * 2 # draw the actual solder dot self.canvas.ellipse((drawpt.x - scale, drawpt.y - scale, drawpt.x + scale, drawpt.y + scale), outline=self.options.style['net'], fill=self.options.style['net']) for ann in net.annotations: pos = self.base_xform.chain(Point(ann.x, ann.y)) self.canvas.text((pos.x, pos.y), ann.value, fill=self.options.style['annot'])
def parse_pours(self, pours_json): if pours_json is None: return None for pour_json in pours_json: points = [ Point(point_json['x'], point_json['y']) for point_json in pour_json['points'] ] layer = pour_json['layer'] subtractive_shapes = [] if 'subtractive_shapes' in pour_json: subtractive_shapes = [ self.parse_shape(shape_json) for shape_json in pour_json['subtractive_shapes'] ] if 'readded_shapes' in pour_json: readded_shapes = [ self.parse_shape(shape_json) for shape_json in pour_json['readded_shapes'] ] pour = Pour(layer, points, subtractive_shapes, readded_shapes) self.design.pours.append(pour)
def _convert_shapes(self, shapes, center=(0, 0), absolute=False): """ Convert shapes """ result = [] def fix_point(point): x, y = (point[0] + center[0], point[1] + center[1]) if absolute: # freerouter often creates points outside boundary, fix it if x > self.max_x: x = self.min_x + x - self.max_x elif x < self.min_x: x = self.max_x - x - self.min_x if y > self.max_y: y = self.min_y + y - self.max_y elif y < self.min_y: y = self.max_y - y - self.min_y return (x, y) for shape in shapes: if isinstance(shape, specctraobj.PolylinePath): points = [ fix_point(self.to_pixels(point)) for point in shape.vertex ] result.extend( self._convert_path(self.to_pixels(shape.aperture_width), points)) elif isinstance(shape, specctraobj.Path): points = [ fix_point(self.to_pixels(point)) for point in shape.vertex ] # Path has connected start and end points if points[0] != points[-1] and len(points) != 2: points.append(points[0]) result.extend( self._convert_path(self.to_pixels(shape.aperture_width), points)) elif isinstance(shape, specctraobj.Polygon): points = [ fix_point(self.to_pixels(point)) for point in shape.vertex ] points = [Point(point[0], point[1]) for point in points] result.append(Polygon(points)) elif isinstance(shape, specctraobj.Rectangle): x1, y1 = self.to_pixels(shape.vertex1) x2, y2 = self.to_pixels(shape.vertex2) width, height = abs(x1 - x2), abs(y1 - y2) x1, y1 = fix_point((min(x1, x2), max(y1, y2))) result.append(Rectangle(x1, y1, width, height)) elif isinstance(shape, specctraobj.Circle): point = fix_point(self.to_pixels(shape.vertex)) result.append( Circle(point[0], point[1], self.to_pixels(shape.diameter / 2.0))) return result
def max_point(self): """ Return the max point of the shape """ if len(self.points) < 1: # by convention x, y = 0, 0 else: x = max([pt.x for pt in self.points]) y = max([pt.y for pt in self.points]) return Point(x, y)
class Pin: """ Pins are the parts of Bodies (/symbols/components) that connect to nets. Basically a line segment, with a null end and a connect end """ def __init__(self, pin_number, p1, p2, label=None): self.label = label # is a Label self.p1 = Point(p1) # null end self.p2 = Point(p2) # connect end self.pin_number = pin_number self.attributes = dict() self.styles = dict() def add_attribute(self, key, value): """ Add attribute to a pin """ self.attributes[key] = value def bounds(self): """ Return the min and max points of a pin """ x_values = [self.p1.x, self.p2.x] y_values = [self.p1.y, self.p2.y] if self.label is not None: x_values.extend([pt.x for pt in self.label.bounds()]) y_values.extend([pt.y for pt in self.label.bounds()]) return [Point(min(x_values), min(y_values)), Point(max(x_values), max(y_values))] def json(self): """ Return a pin as JSON """ ret = { "pin_number":self.pin_number, "p1":self.p1.json(), "p2":self.p2.json(), "attributes" : self.attributes, "styles": self.styles, } if self.label is not None: ret["label"] = self.label.json() return ret
def draw_layout(self): """ Render the layout into self.img """ # start off with all the component instances for inst in self.design.component_instances: comp = self.design.components.components[inst.library_id] for body, attr in zip(comp.footprints[inst.footprint_index].bodies, inst.footprint_attributes): # draw the appropriate body, at the position in attr pos = Point(attr.x, attr.y) self.draw_footprint(body, pos, attr.rotation, attr.flip) # draw in any annotations for ann in attr.annotations: if ann.visible: pos = self.base_xform.chain(Point(ann.x, ann.y)) self.canvas.text((pos.x, pos.y), ann.value, fill=self.options.style['annot']) for trace in self.design.traces: self.draw_trace(trace)
class Pad: """ Pads are the parts of FBodies (/footprints/components) that connect to traces. Basically a set of shapes. """ def __init__(self, pin_number, p, shapes, label=None): self.label = label # is a Label self.p = Point(p) self.pin_number = pin_number self.shapes = shapes self.attributes = dict() self.styles = dict() def add_attribute(self, key, value): """ Add attribute to a pin """ self.attributes[key] = value def bounds(self): """ Return the min and max points of a pin """ pass def scale(self, factor): """ Scale the x & y coordinates in the pin. """ pass def shift(self, dx, dy): """ Shift the x & y coordinates in the pin. """ pass def rebase_y_axis(self, height): """ Rebase the y coordinate in the pin. """ pass def json(self): """ Return a pin as JSON """ ret = { "pin_number": self.pin_number, "p": self.p.json(), "shapes": [s.json() for s in self.shapes], "attributes": stringify_attributes(self.attributes), "styles": self.styles, } if self.label is not None: ret["label"] = self.label.json() return ret
def _get_ctr_and_radius(self, end_pts, offset): """ Apply gerber circular interpolation logic. """ start, end = end_pts radius = sqrt(offset['i']**2 + offset['j']**2) center = Point(x=start.x + offset['i'], y=start.y + offset['j']) # In single-quadrant mode, gerber requires implicit # determination of offset direction, so we find the # center through trial and error. if not self.status['multi_quadrant']: if not snap(center.dist(end), radius): center = Point(x=start.x - offset['i'], y=start.y - offset['j']) if not snap(center.dist(end), radius): center = Point(x=start.x + offset['i'], y=start.y - offset['j']) if not snap(center.dist(end), radius): center = Point(x=start.x - offset['i'], y=start.y + offset['j']) if not snap(center.dist(end), radius): raise ImpossibleGeometry return (center, radius)
class Pin: """ Pins are the parts of SBodies (/symbols/components) that connect to nets. Basically a line segment, with a null end and a connect end """ def __init__(self, pin_number, p1, p2, label=None): self.label = label # is a Label self.p1 = Point(p1) # null end self.p2 = Point(p2) # connect end self.pin_number = pin_number self.attributes = dict() self.styles = dict() def add_attribute(self, key, value): """ Add attribute to a pin """ self.attributes[key] = value def bounds(self): """ Return the min and max points of a pin """ x_values = [self.p1.x, self.p2.x] y_values = [self.p1.y, self.p2.y] if self.label is not None: x_values.extend([pt.x for pt in self.label.bounds()]) y_values.extend([pt.y for pt in self.label.bounds()]) return [Point(min(x_values), min(y_values)), Point(max(x_values), max(y_values))] def scale(self, factor): """ Scale the x & y coordinates in the pin. """ if self.label is not None: self.label.scale(factor) self.p1.scale(factor) self.p2.scale(factor) def shift(self, dx, dy): """ Shift the x & y coordinates in the pin. """ if self.label is not None: self.label.shift(dx, dy) self.p1.shift(dx, dy) self.p2.shift(dx, dy) def rebase_y_axis(self, height): """ Rebase the y coordinate in the pin. """ if self.label is not None: self.label.rebase_y_axis(height) self.p1.rebase_y_axis(height) self.p2.rebase_y_axis(height) def json(self): """ Return a pin as JSON """ ret = { "pin_number": self.pin_number, "p1": self.p1.json(), "p2": self.p2.json(), "attributes": stringify_attributes(self.attributes), "styles": self.styles, } if self.label is not None: ret["label"] = self.label.json() return ret
def _add_hole(self, parent_attr, body_attr, shape): if not isinstance(shape, Circle): log.error('holes must be circular, found %s', shape) return #raise Unwritable('all holes must be circular') pos = Point(shape.x, shape.y) pos.shift(body_attr.x, body_attr.y) if parent_attr: if parent_attr.rotation != 0: pos.rotate(parent_attr.rotation) if parent_attr.flip_horizontal: pos.flip(parent_attr.flip_horizontal) pos.shift(parent_attr.x, parent_attr.y) if body_attr.rotation != 0: if parent_attr and parent_attr.flip_horizontal: pos.rotate(-body_attr.rotation, in_place=True) else: pos.rotate(body_attr.rotation, in_place=True) if body_attr.flip_horizontal: shape.flip(body_attr.flip_horizontal) log.debug('adding %d hole at %d, %d', shape.radius * 2, pos.x, pos.y) if shape.radius not in self._holes: self._holes[shape.radius] = Hole(shape) self._holes[shape.radius].locations.append(pos)