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 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
Exemple #8
0
    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)
Exemple #10
0
    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
Exemple #11
0
 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)
Exemple #12
0
    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)
Exemple #19
0
    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)
Exemple #25
0
 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))
Exemple #26
0
 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)