def make_shape_for_circle(self, circ): """ Generate an openjson shape for an eaglexml circle. """ ocirc = Circle(self.make_length(circ.x), self.make_length(circ.y), self.make_length(circ.radius)) ocirc.add_attribute('eaglexml_width', circ.width) return ocirc
def _extract_ad(self, tok): """ Extract aperture definition into shapes dict. """ tok = tok if ',' in tok else tok + ',' code_end = 4 if tok[3].isdigit() else 3 code = tok[1:code_end] ap_type, mods = tok[code_end:].split(',') if mods: mods = [float(m) for m in mods.split('X') if m] # An aperture can use any of the 4 standard types, # (with or without a central hole), or a previously # defined macro. if ap_type == 'C': shape = Circle(0, 0, mods[0] / 2) hole_defs = len(mods) > 1 and mods[1:] elif ap_type == 'R': if len(mods) == 1: shape = Rectangle(-mods[0] / 2, mods[0] / 2, mods[0], mods[0]) else: shape = Rectangle(-mods[0] / 2, mods[1] / 2, mods[0], mods[1]) hole_defs = len(mods) > 2 and mods[2:] elif ap_type == 'O': shape = Obround(0, 0, mods[0], mods[1]) hole_defs = len(mods) > 2 and mods[2:] elif ap_type == 'P': if len(mods) < 3: mods.append(0) shape = RegularPolygon(0, 0, mods[0], mods[1], mods[2]) hole_defs = len(mods) > 3 and mods[3:] else: # macro shape = ap_type if shape in self.macro_buff: macro = self.macro_buff[shape].instantiate(mods) counter = 0 # pick a unique name for the macro while mods and macro.name in self.layer_buff.macros: macro.name = shape + str(counter) counter += 1 self.layer_buff.macros[macro.name] = macro hole_defs = None if hole_defs and (len(hole_defs) > 1): hole = Rectangle(-hole_defs[0] / 2, hole_defs[1] / 2, hole_defs[0], hole_defs[1]) elif hole_defs: hole = Circle(0, 0, hole_defs[0] / 2) else: hole = None self.layer_buff.apertures.update({code: Aperture(code, shape, hole)})
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 parse_circle(self, circle): """ Parse a circle element """ return [ Circle(get_x(circle, 'cx', self.svg_mult), get_y(circle, 'cy', self.svg_mult), get_length(circle, 'r', self.svg_mult)) ]
def test_eq(self): c1 = Circle(0, 0, 5) c2 = Circle(0, 0, 1) self.assertEqual(Aperture('a', c1, c1), Aperture('b', c1, c1)) self.assertEqual(Aperture('a', c1, c2), Aperture('b', c1, c2)) self.assertEqual(Aperture('a', c1, None), Aperture('b', c1, None)) self.assertNotEqual(Aperture('a', c1, None), Aperture('b', c2, None)) self.assertNotEqual(Aperture('a', c1, c1), Aperture('b', c1, c2)) self.assertNotEqual(Aperture('a', c1, None), Aperture('b', c1, c2)) self.assertNotEqual(Aperture('a', c1, c1), Aperture('b', c1, None))
def bodies(self, offset, instance_attributes): """ Generated the bodies for the Via with instance attribute overrides. Returns placment attribute and body pairs. """ pos = Point(self.x, self.y) attached_layers = self.get_attr('attached_layers', '', instance_attributes).split(',') solder_mask_expansion = self.get_int_attr('solder_mask_expansion', 0, instance_attributes) plating_diameter = self.get_int_attr('plating_diameter', 0, instance_attributes) internal_diameter = self.get_int_attr('internal_diameter', 0, instance_attributes) solder_mask_radius = solder_mask_expansion + (plating_diameter / 2) # placment attribute + body pairs making up the generated object bodies = [] top_solder_mask = FBody() top_solder_mask.add_shape(Circle(pos.x, pos.y, solder_mask_radius)) bodies.append((FootprintAttribute(0, 0, 0, False, 'top solder mask'), top_solder_mask)) bottom_solder_mask = FBody() bottom_solder_mask.add_shape(Circle(pos.x, pos.y, solder_mask_radius)) bodies.append( (FootprintAttribute(0, 0, 0, False, 'bottom solder mask'), bottom_solder_mask)) # circle of diameter 'internal_diameter' on the hole layer hole = FBody() hole.add_shape(Circle(pos.x, pos.y, internal_diameter / 2)) bodies.append((FootprintAttribute(0, 0, 0, False, 'hole'), hole)) # circles of diameter 'plating_diameter' on each connection layer for layer_name in attached_layers: connected_layer = FBody() connected_layer.add_shape( Circle(pos.x, pos.y, plating_diameter / 2)) bodies.append((FootprintAttribute(0, 0, 0, False, layer_name), connected_layer)) return bodies
def test_circle(self): """ Convert circle shape """ circle = Circle(10, 20, 10) writer = Specctra() obj = writer._convert_shape(circle) self.assertEqual( to_string(writer, obj), '( (circle signal 208.333333 104.166667 208.333333) )')
def _gen_trace(self, trace): """ Traces are connected lines and arcs. """ shape = Circle(0 , 0, trace.width / 2.0) select = self._select_aperture(shape, None) if select: yield LINE.format(select) for seg in trace.segments: for block in self._draw_seg(seg): yield LINE.format(block)
def parse_shape(self, shape): """ Extract a shape. """ # pylint: disable=R0914 # pylint: disable=R0911 typ = shape.get('type') if 'rectangle' == typ: x = int(shape.get('x')) y = int(shape.get('y')) height = int(shape.get('height')) width = int(shape.get('width')) parsed_shape = Rectangle(x, y, width, height) elif 'rounded_rectangle' == typ: x = int(shape.get('x')) y = int(shape.get('y')) height = int(shape.get('height')) width = int(shape.get('width')) radius = int(shape.get('radius')) parsed_shape = RoundedRectangle(x, y, width, height, radius) elif 'arc' == typ: x = int(shape.get('x')) y = int(shape.get('y')) start_angle = float(shape.get('start_angle')) end_angle = float(shape.get('end_angle')) radius = int(shape.get('radius')) parsed_shape = Arc(x, y, start_angle, end_angle, radius) elif 'circle' == typ: x = int(shape.get('x')) y = int(shape.get('y')) radius = int(shape.get('radius')) parsed_shape = Circle(x, y, radius) elif 'label' == typ: x = int(shape.get('x')) y = int(shape.get('y')) rotation = float(shape.get('rotation')) text = shape.get('text') align = shape.get('align') parsed_shape = Label(x, y, text, align, rotation) elif 'line' == typ: p1 = self.parse_point(shape.get('p1')) p2 = self.parse_point(shape.get('p2')) parsed_shape = Line(p1, p2) elif 'polygon' == typ: parsed_shape = Polygon() for point in shape.get('points'): parsed_shape.add_point(self.parse_point(point)) elif 'bezier' == typ: control1 = self.parse_point(shape.get('control1')) control2 = self.parse_point(shape.get('control2')) p1 = self.parse_point(shape.get('p1')) p2 = self.parse_point(shape.get('p2')) parsed_shape = BezierCurve(control1, control2, p1, p2) parsed_shape.styles = shape.get('styles') or {} parsed_shape.attributes = shape.get('attributes') or {} return parsed_shape
def _define_apertures(self): """ Build the apertures needed to make shapes. """ for image in self.images: for trace in image.traces: shape = Circle(0, 0, trace.width / 2.0) self._add_aperture(shape, None) for smear in image.smears: self._add_aperture(smear.shape, None) for shape_instance in image.shape_instances: self._add_aperture(shape_instance.shape, shape_instance.hole)
def test_circle(self): """ Convert circle shape """ circle = Circle(10, 20, 10) writer = Specctra() writer.resolution = specctraobj.Resolution() writer.resolution.unit = 'mil' writer.resolution.resolution = 10 obj = writer._convert_shape(circle) self.assertEqual( to_string(writer, obj), '( (circle signal 208.333333 104.166667 208.333333) )')
def instantiate(self, values): """ Return a core.layout.Primitive with a set of fixed shapes given a dict mapping variable numbers to values, or None if there is no corresponding shape. """ shape_type = self.shape_type mods = [m.evaluate(values) for m in self.modifiers] if shape_type == 'ignore': return None is_additive = True if shape_type in ('moire', 'thermal') \ else bool(mods[0]) rotation = 0 if shape_type in ('circle', 'moire', 'thermal') \ else mods[-1]/180 if shape_type == 'circle': shape = Circle(x=mods[2], y=mods[3], radius=mods[1] / 2) elif shape_type == 'line-vector': shape, rotation = self._vector_to_rect(mods, rotation) elif shape_type == 'line-center': shape = Rectangle(x=mods[3] - mods[1] / 2, y=mods[4] + mods[2] / 2, width=mods[1], height=mods[2]) elif shape_type == 'line-lower-left': shape = Rectangle(x=mods[3], y=mods[4] + mods[2], width=mods[1], height=mods[2]) elif shape_type == 'outline': points = [ Point(mods[i], mods[i + 1]) for i in range(2, len(mods[:-1]), 2) ] shape = Polygon(points) elif shape_type == 'polygon': shape = RegularPolygon(x=mods[2], y=mods[3], outer=mods[4], vertices=mods[1]) elif shape_type == 'moire': mods[8] = 2 - mods[8] / 180 shape = Moire(*mods[0:9]) elif shape_type == 'thermal': mods[5] = 2 - mods[5] / 180 shape = Thermal(*mods[0:6]) return Primitive(is_additive, rotation, shape)
def test_eq(self): circle1 = Circle(0, 0, 5) circle2 = Circle(0, 0, 1) self.assertEqual(Aperture('a', circle1, circle1), Aperture('b', circle1, circle1)) self.assertEqual(Aperture('a', circle1, circle2), Aperture('b', circle1, circle2)) self.assertEqual(Aperture('a', circle1, None), Aperture('b', circle1, None)) self.assertNotEqual(Aperture('a', circle1, None), Aperture('b', circle2, None)) self.assertNotEqual(Aperture('a', circle1, circle1), Aperture('b', circle1, circle2)) self.assertNotEqual(Aperture('a', circle1, None), Aperture('b', circle1, circle2)) self.assertNotEqual(Aperture('a', circle1, circle1), Aperture('b', circle1, None))
def test_get_pin(self): """ The get_pin function returns the correct Pins """ shape = Rectangle(0, 0, 4, 8) pin = get_pin(shape) self.assertEqual(pin.p1.x, 2) self.assertEqual(pin.p1.y, 4) self.assertEqual(pin.p2.x, pin.p1.x) self.assertEqual(pin.p2.y, pin.p1.y) shape = Circle(0, 0, 4) pin = get_pin(shape) self.assertEqual(pin.p1.x, 0) self.assertEqual(pin.p1.y, 0) self.assertEqual(pin.p2.x, pin.p1.x) self.assertEqual(pin.p2.y, pin.p1.y) self.assertEqual(get_pin(Shape()), None)
def bodies(self, offset, instance_attributes): bodies = [] attached_layers = self.get_attr('attached_layers', '', instance_attributes).split(',') width = self.get_int_attr('width', 0, instance_attributes) height = self.get_int_attr('height', 0, instance_attributes) radius = self.get_int_attr('radius', 0, instance_attributes) shape_type = self.get_attr('shape', '', instance_attributes) pos = Point(self.x, self.y) for layer_name in attached_layers: layer_name = layer_name pad = FBody() # invert top/bottom if the footprint is on the bottom of the board if offset.side == 'bottom': rev_sides = {'top': 'bottom', 'bottom': 'top'} layer_name = ' '.join([ rev_sides.get(piece, piece) for piece in layer_name.split(' ') ]) if shape_type == 'rectangle': pad.add_shape( Rectangle((width / 2), -(height / 2), width, height)) elif shape_type == 'rounded rectangle': pad.add_shape( RoundedRectangle((width / 2), -(height / 2), width, height, radius)) elif shape_type == 'circle': pad.add_shape(Circle(0, 0, radius)) else: raise ValueError('unexpected shape type for padstack') pad.rotate(self.rotation) pad.shift(pos.x, pos.y) bodies.append((FootprintAttribute(0, 0, 0, False, layer_name), pad)) return bodies
def parse_shape(self, shape): """ Extract a shape. """ # pylint: disable=R0914 # pylint: disable=R0911 rotation = shape.get('rotation', 0.0) flip_horizontal = shape.get('flip_horizontal', False) shape_type = shape.get('type') if 'rectangle' == shape_type: x = int(shape.get('x')) y = int(shape.get('y')) height = int(shape.get('height')) width = int(shape.get('width')) parsed_shape = Rectangle(x, y, width, height) elif 'rounded_rectangle' == shape_type: x = int(shape.get('x')) y = int(shape.get('y')) height = int(shape.get('height')) width = int(shape.get('width')) radius = int(shape.get('radius')) parsed_shape = RoundedRectangle(x, y, width, height, radius) elif 'arc' == shape_type: x = int(shape.get('x')) y = int(shape.get('y')) start_angle = float(shape.get('start_angle')) end_angle = float(shape.get('end_angle')) radius = int(shape.get('radius')) parsed_shape = Arc(x, y, start_angle, end_angle, radius) elif 'circle' == shape_type: x = int(shape.get('x')) y = int(shape.get('y')) radius = int(shape.get('radius')) parsed_shape = Circle(x, y, radius) elif 'label' == shape_type: parsed_shape = self.parse_label(shape) elif 'line' == shape_type: p1 = self.parse_point(shape.get('p1')) p2 = self.parse_point(shape.get('p2')) parsed_shape = Line(p1, p2) elif 'polygon' == shape_type: parsed_shape = Polygon() for point in shape.get('points'): parsed_shape.add_point(self.parse_point(point)) elif 'bezier' == shape_type: control1 = self.parse_point(shape.get('control1')) control2 = self.parse_point(shape.get('control2')) p1 = self.parse_point(shape.get('p1')) p2 = self.parse_point(shape.get('p2')) parsed_shape = BezierCurve(control1, control2, p1, p2) elif 'rounded_segment' == shape_type: p1 = self.parse_point(shape.get('p1')) p2 = self.parse_point(shape.get('p2')) width = int(shape.get('width')) parsed_shape = RoundedSegment(p1, p2, width) parsed_shape.rotation = rotation parsed_shape.flip_horizontal = flip_horizontal parsed_shape.styles = shape.get('styles') or {} parsed_shape.attributes = shape.get('attributes') or {} return parsed_shape
def test_circle_min_point(self): '''Test Circle.min_point()''' cir = Circle(2, 3, 4) top_left = cir.min_point() self.assertEqual(top_left.x, -2) self.assertEqual(top_left.y, -1)
def _define_images(self, design, layer_name): """ Define the images that make up the layer information. """ log.debug('creating images for layer "%s"', layer_name) # trace segments on this layer traces_image = Image(layer_name + '_traces', font_renderer=self.face) for segment in design.trace_segments: if segment.layer != layer_name: continue log.debug('Creating smear for trace: %s', segment) # Assumes segment is rounded, straignt trace_smear = Smear(Line(segment.p1, segment.p2), Circle(0, 0, segment.width / 2.0)) traces_image.smears.append(trace_smear) # Generated objects in the design (vias, PTHs) zero_pos = FootprintPos(0, 0, 0.0, False, 'top') for gen_obj in design.layout_objects: # XXX(shamer): body attr is only being used to hold the layer, other placement details are contained # elsewhere for body_attr, body in gen_obj.bodies(zero_pos, {}): if body_attr.layer == layer_name: for shape in body.shapes: traces_image.add_shape(shape, design, zero_pos, body_attr) self.images.append(traces_image) # Pours on this layer for pour in design.pours: log.debug('adding body for pour: %s points, %s', len(pour.points), pour.layer) if layer_name == pour.layer: log.debug('adding body for pour: %s points, %s subtractive shapes', len(pour.points), len(pour.subtractive_shapes)) fill_image = Image('pour fill', font_renderer=self.face) fill_image.fills.append(Fill(pour.points)) self.images.append(fill_image) subtractive_image = Image('pour subtractive shapes', font_renderer=self.face, is_additive=False) for shape in pour.subtractive_shapes: if shape.type == 'rounded_segment': trace_smear = Smear(Line(shape.p1, shape.p2), Circle(0, 0, shape.width / 2.0)) subtractive_image.smears.append(trace_smear) else: subtractive_image.add_shape(shape, None, FootprintPos(0, 0, 0.0, False, ''), FootprintPos(0, 0, 0.0, False, '')) self.images.append(subtractive_image) readded_image = Image('pour readded shapes', font_renderer=self.face, is_additive=True) for shape in pour.readded_shapes: if shape.type == 'rounded_segment': trace_smear = Smear(Line(shape.p1, shape.p2), Circle(0, 0, shape.width / 2.0)) readded_image.smears.append(trace_smear) else: readded_image.add_shape(shape, None, FootprintPos(0, 0, 0.0, False, ''), FootprintPos(0, 0, 0.0, False, '')) self.images.append(readded_image) # trace segments on this layer traces_image = Image(layer_name + '_traces', font_renderer=self.face) for segment in design.trace_segments: if segment.layer != layer_name: continue log.debug('Creating smear for trace: %s', segment) # Assumes segment is rounded, straignt trace_smear = Smear(Line(segment.p1, segment.p2), Circle(0, 0, segment.width / 2.0)) traces_image.smears.append(trace_smear) # Generated objects in the design (vias, PTHs) zero_pos = FootprintPos(0, 0, 0.0, False, 'top') for gen_obj in design.layout_objects: # XXX(shamer): body attr is only being used to hold the layer, other placement details are contained # elsewhere for body_attr, body in gen_obj.bodies(zero_pos, {}): if body_attr.layer == layer_name: for shape in body.shapes: traces_image.add_shape(shape, design, zero_pos, body_attr) self.images.append(traces_image) # Component aspects on this layer # a separate image is used for each component for component_instance in design.component_instances: component = design.components.components[component_instance.library_id] component_image = Image(layer_name + ' component ' + component_instance.instance_id, font_renderer=self.face) footprint_pos = component_instance.footprint_pos if footprint_pos.side is None: continue for idx, footprint_attr in enumerate(component_instance.footprint_attributes): log.debug('footprint pos: %s, side %s, flip %s', footprint_attr.layer, footprint_pos.side, footprint_pos.flip_horizontal) fp_attr_cpy = copy.deepcopy(footprint_attr) if footprint_attr.layer: if footprint_pos.side == 'bottom': rev_sides = {'top': 'bottom', 'bottom': 'top'} fp_attr_cpy.layer = ' '.join([rev_sides.get(piece, piece) for piece in footprint_attr.layer.split(' ')]) if fp_attr_cpy.layer == layer_name: footprint_body = component.footprints[component_instance.footprint_index].bodies[idx] log.debug('adding footprint attribute: %s, %d shapes', fp_attr_cpy, len(footprint_body.shapes)) for shape in footprint_body.shapes: component_image.add_shape(shape, component_instance, footprint_pos, fp_attr_cpy) for idx, gen_obj_attr in enumerate(component_instance.gen_obj_attributes): gen_obj = component.footprints[component_instance.footprint_index].gen_objs[idx] # FIXME(shamer): check for unplaced generated objects. # XXX(shamer): body attr is only being used to hold the layer, other placement details are contained # elsewhere for body_attr, body in gen_obj.bodies(footprint_pos, gen_obj_attr.attributes): if body_attr.layer == layer_name: log.debug('adding body for generated object: %s, %s', footprint_pos, gen_obj_attr) for shape in body.shapes: component_image.add_shape(shape, component_instance, footprint_pos, body_attr) if component_image.not_empty(): self.images.append(component_image) # paths on the layer for path in design.paths: if layer_name == path.layer: log.debug('adding body for path: %s points, %s, %s, is closed: %s', len(path.points), path.width, path.layer, path.is_closed) path_image = Image('path', font_renderer=self.face) start = path.points[0] for point in path.points[1:]: path_image.add_shape(Line(start, point), Circle(0, 0, path.width), zero_pos, zero_pos) start = point if path.is_closed: path_image.add_shape(Line(path.points[0], path.points[-1]), Circle(0, 0, path.width), zero_pos, zero_pos) self.images.append(path_image) # stand alone text on the layer text_image = Image('text', font_renderer=self.face) for text in design.pcb_text: if layer_name == text.layer: log.debug('adding body for text: "%s"', text.value) text_image.add_shape(text.label, design, text, zero_pos) if text_image.not_empty(): self.images.append(text_image)
def parse_c_line(self, parts): """ Parse a C (Circle) line """ x, y, radius = [int(i) for i in parts[1:4]] return Circle(make_length(x), make_length(y), make_length(radius))
def bodies(self, offset, instance_attributes): """ Generated the bodies for the Via with instance attribute overrides. Returns placment attribute and body pairs. """ attached_layers = self.get_attr('attached_layers', '', instance_attributes).split(',') internal_diameter = self.get_float_attr('internal_diameter', 0, instance_attributes) plating_shape = self.get_attr('plating_shape', '', instance_attributes) # Local vars for use in closures to generate shapes # XXX(shamer): The assignment of width and lenght are reversed from the javascript. Not sure why this is. plating_width = self.get_float_attr('plating_length', 0, instance_attributes) plating_height = self.get_float_attr('plating_width', 0, instance_attributes) plating_radius = self.get_float_attr('plating_radius', 0, instance_attributes) plating_diameter = self.get_float_attr('plating_diameter', 0, instance_attributes) solder_mask_expansion = self.get_float_attr('solder_mask_expansion', 0, instance_attributes) #thermal_inner_diameter = self.get_float_attr('thermal_inner_diameter', 0, instance_attributes) #thermal_spoke_width = self.get_float_attr('thermal_spoke_width', 0, instance_attributes) #antipad_diameter = self.get_float_attr('antipad_diameter', 0, instance_attributes) # placment attribute + body pairs making up the generated object bodies = [] pad_pos = Point(self.x, self.y) sme_pos = Point(self.x, self.y) # Debugging marker for displaying the placment position for generated objects. #marker = FBody() #marker.add_shape(Circle(pad_pos.x, pad_pos.y, 1000000)) #bodies.append((FootprintAttribute(0, 0, 0, False, 'top silkscreen'), marker)) if plating_shape == 'square': solder_mask_width = (solder_mask_expansion * 2) + plating_diameter create_shape = lambda: Rectangle( pad_pos.x, pad_pos.y, plating_diameter, plating_diameter) create_solder_mask_expansion = lambda: Rectangle( sme_pos.x, sme_pos.y, solder_mask_width, solder_mask_width) elif plating_shape == 'circle': create_shape = lambda: Circle(pad_pos.x, pad_pos.y, plating_diameter / 2) solder_mask_radius = solder_mask_expansion + (plating_diameter / 2) create_solder_mask_expansion = lambda: Circle( sme_pos.x, sme_pos.y, solder_mask_radius) elif plating_shape == 'rectangle': solder_mask_width = (solder_mask_expansion * 2) + plating_width solder_mask_height = (solder_mask_expansion * 2) + plating_height create_shape = lambda: Rectangle(pad_pos.x, pad_pos.y, plating_width, plating_height) create_solder_mask_expansion = lambda: Rectangle( sme_pos.x, sme_pos.y, solder_mask_width, solder_mask_height) elif plating_shape == 'rounded rectangle': solder_mask_width = (solder_mask_expansion * 2) + plating_width solder_mask_height = (solder_mask_expansion * 2) + plating_height create_shape = lambda: RoundedRectangle( pad_pos.x, pad_pos.y, plating_width, plating_height, plating_radius) create_solder_mask_expansion = lambda: RoundedRectangle( sme_pos.x, sme_pos.y, solder_mask_width, solder_mask_height, plating_radius) else: raise ValueError( 'unexpected shape for plated through hole "{0}"'.format( plating_shape)) # cirle of radius 'solder_mask_expansion' + ('plating_diameter' / 2) in the top and bottom silkscreen layers solder_mask_radius = solder_mask_expansion + (plating_diameter / 2) top_solder_mask = FBody() top_solder_mask.add_shape(create_solder_mask_expansion()) bodies.append((FootprintAttribute(0, 0, 0, False, 'top solder mask'), top_solder_mask)) bottom_solder_mask = FBody() bottom_solder_mask.add_shape(create_solder_mask_expansion()) bodies.append( (FootprintAttribute(0, 0, 0, False, 'bottom solder mask'), bottom_solder_mask)) # circle of diameter 'internal_diameter' on the hole layer hole = FBody() hole.add_shape(Circle(pad_pos.x, pad_pos.y, internal_diameter / 2)) bodies.append((FootprintAttribute(0, 0, 0, False, 'hole'), hole)) # circles of diameter 'plating_diameter' on each connection layer for layer_name in attached_layers: connected_layer = FBody() if layer_name == 'top copper' or layer_name == 'bottom copper': connected_layer.add_shape(create_shape()) else: connected_layer.add_shape( Circle(pad_pos.x, pad_pos.y, plating_diameter / 2)) bodies.append((FootprintAttribute(0, 0, 0, False, layer_name), connected_layer)) return bodies
def test_create_new_circle(self): """ Test the creation of a new empty circle. """ cir = Circle(0, 1, 2) assert cir.x == 0 assert cir.y == 1 assert cir.radius == 2
def add_shape(self, shape, parent, parent_offset, offset): """ Add a shape to the image. (might be added as a smear or fill) """ # Copy the shape so it can be mutated without affecting other instances shapecpy = copy.deepcopy(shape) # XXX(shamer): a label needs to be rendered before in-place rotations are made so the bounding box for the shape # are known if isinstance(shapecpy, Label): self.face.set_char_size(int(shapecpy.font_size)) label_contours = [] x_offset = 0 y_offset = 0 label_text = self.resolve_text(shapecpy.text, parent.get_attribute) for i, c in enumerate(label_text): self.face.load_char(c, flags=freetype.ft_enums.FT_LOAD_NO_BITMAP) slot = self.face.glyph outline = slot.outline glyph_contours = [] start, end = 0, 0 # Iterate over each contour separately. Characters like 'g' have multiple contours as they cannot be # walked with a contiguous set of arcs. The contours list contains the index of the point that the # contour starts on. for contour_idx in range(len(outline.contours)): end = outline.contours[contour_idx] points = [ Point(t[0], t[1]) for t in outline.points[start:end + 1] ] tags = outline.tags[start:end + 1] # Close the contour by repeating the last point. points.append(copy.deepcopy(points[0])) tags.append(copy.deepcopy(tags[0])) segments = [ [ points[0], ], ] # Group points into segments. The tag identifies real vs control points. for point_idx in range(1, len(points)): segments[-1].append(points[point_idx]) if tags[point_idx] & (1 << 0) and point_idx < ( len(points) - 1): segments.append([ copy.deepcopy(points[point_idx]), ]) # take the fist and last points of each segment (the non-control points). To approximate the curves # using straight lines. glyph_contours.append([[segment[0], segment[-1]] for segment in segments]) start = end + 1 # update the segments in the glyph with the x_offset for contour_segments in glyph_contours: for segments in contour_segments: for point in segments: point.x += x_offset point.y += y_offset label_contours.extend(glyph_contours) x_offset += slot.advance.x # adjust amount to advance with kerning offset if i + 1 < len(label_text): next_c = label_text[i + 1] x_offset += self.face.get_kerning(c, next_c).x # Update the segments for pre-render shifts, rotates, alignment for contour_segments in label_contours: for segments in contour_segments: if shapecpy.align == 'center': segments[0].shift(-(x_offset / 2), 0) segments[1].shift(-(x_offset / 2), 0) if shapecpy.rotation: segments[0].rotate(shapecpy.rotation) segments[1].rotate(shapecpy.rotation) if shapecpy.flip_horizontal: segments[0].flip(shapecpy.flip_horizontal) segments[1].flip(shapecpy.flip_horizontal) shapecpy._segments.append(segments) # Calculate the bounding fox the label from the segments min_point = [2**100, 2**100] max_point = [-2**100, -2**100] for contour_segments in label_contours: for segments in contour_segments: min_point[0] = min(segments[0].x, segments[1].x, min_point[0]) max_point[0] = max(segments[0].x, segments[1].x, max_point[0]) min_point[1] = min(segments[0].y, segments[1].y, min_point[1]) max_point[1] = max(segments[0].y, segments[1].y, max_point[1]) shapecpy._min_point = Point(min_point[0], min_point[1]) shapecpy._max_point = Point(max_point[0], max_point[1]) shapecpy.shift(offset.x, offset.y) if parent_offset.rotation != 0: shapecpy.rotate(parent_offset.rotation) if parent_offset.flip_horizontal: shapecpy.flip(parent_offset.flip_horizontal) shapecpy.shift(parent_offset.x, parent_offset.y) if offset.rotation != 0: if parent_offset.flip_horizontal: shapecpy.rotate(-offset.rotation, in_place=True) else: shapecpy.rotate(offset.rotation, in_place=True) if offset.flip_horizontal: shapecpy.flip(offset.flip_horizontal) if isinstance(shapecpy, Line): # FIXME(shamer): line doesn't have an explicit width. Gets used for outlines. Defaulted to 0.15mm self.smears.append(Smear(shapecpy, Circle(0, 0, 0.15 * 1000000))) elif isinstance(shapecpy, Circle): self.shape_instances.append( ShapeInstance(Point(shapecpy.x, shapecpy.y), Aperture(None, shapecpy, None))) elif isinstance(shapecpy, Rectangle): #shapecpy.x += shapecpy.width #shapecpy.y -= shapecpy.height / 2 shapecpy.width = abs(shapecpy.width) shapecpy.height = abs(shapecpy.height) if shapecpy.rotation != 0: instance_name = 'Rect-W{width}-H{height}-RO{rotation}'.format( height=shapecpy.height, width=shapecpy.width, rotation=shapecpy.rotation) # XXX(shamer): additional copy is made so the x, y can be reset for use as a ComplexInstance shapecpycpy = copy.deepcopy(shapecpy) shapecpycpy.x = 0 shapecpycpy.y = 0 shapecpycpy.is_centered = True primitives = [Primitive(1, 0.0, shapecpycpy)] self.complex_instances.append( ComplexInstance(instance_name, Point(shapecpy.x, shapecpy.y), primitives)) else: self.shape_instances.append( ShapeInstance(Point(shapecpy.x, shapecpy.y), Aperture(None, shapecpy, None))) elif isinstance(shapecpy, RoundedRectangle): # Rounded rectangle is added as a macro with two rectangles to fill out the body and four circles to make up # the corners. `roundrect` is assumed to already be centered. primitives = [] radius = abs(shapecpy.radius) inner_height = abs(shapecpy.height) - (radius * 2) inner_width = abs(shapecpy.width) - (radius * 2) half_width = inner_width / 2.0 half_height = inner_height / 2.0 high_rect = Rectangle(0, 0, abs(inner_width), abs(shapecpy.height), is_centered=True) wide_rect = Rectangle(0, 0, abs(shapecpy.width), abs(inner_height), is_centered=True) primitives.append(Primitive(1, 0.0, high_rect)) primitives.append(Primitive(1, 0.0, wide_rect)) primitives.append( Primitive(1, 0.0, Circle(-half_width, half_height, shapecpy.radius))) primitives.append( Primitive(1, 0.0, Circle(half_width, half_height, shapecpy.radius))) primitives.append( Primitive(1, 0.0, Circle(half_width, -half_height, shapecpy.radius))) primitives.append( Primitive(1, 0.0, Circle(-half_width, -half_height, shapecpy.radius))) # rotate the positioning of the rounded corners (the circles) for primitive in primitives: primitive.shape.rotate(shapecpy.rotation) instance_name = 'RR-H{height}-W{width}-R{radius}-RO{rotation}'.format( height=abs(shapecpy.height), width=abs(shapecpy.width), radius=radius, rotation=shapecpy.rotation) self.complex_instances.append( ComplexInstance(instance_name, Point(shapecpy.x, shapecpy.y), primitives)) elif isinstance(shapecpy, Label): # TODO(shamer): cache positions segments for glyphs # FIXME((shamer): make baseline shift # TODO(shamer) select the correct font based off of the label.font_family # Debugging, used to show the anchor point of the label #self.shape_instances.append(ShapeInstance(Point(shapecpy.x, shapecpy.y), Aperture(None, Circle(0, 0, 1000000 / 5), None))) for segments in shapecpy._segments: line = Line(segments[0], segments[1]) line.shift(shapecpy.x, shapecpy.y) self.smears.append(Smear(line, Circle(0, 0, 0.1016 * 1000000))) # 4 Mils
def test_circle_max_point(self): '''Test Circle.max_point()''' cir = Circle(2, 3, 4) bottom_right = cir.max_point() self.assertEqual(bottom_right.x, 6) self.assertEqual(bottom_right.y, 7)
def parse_circle(self, args): """ Returns a parsed circle. """ x, y, rad = [int(a) for a in args.split()] return ('shape', Circle(x, y, rad))
def parse_c_line(self, parts): """ Parse a C (Circle) line """ x, y, radius = [int(i) for i in parts[1:4]] return Circle(x, y, radius)