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 parse_line(self, rect):
        """ Parse a line element """

        return [Line((get_x(rect, 'x1', self.svg_mult),
                      get_y(rect, 'y1', self.svg_mult)),
                     (get_x(rect, 'x2', self.svg_mult),
                      get_y(rect, 'y2', self.svg_mult)))]
 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_z(self, data, is_relative):
        """ Parse a Z or z (closepath) segment. """

        self.shapes.append(
            Line(make_point(self.cur_point, self.svg_mult),
                 make_point(self.start_point, self.svg_mult)))
        self.cur_point = self.start_point

        return data
示例#5
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
示例#6
0
 def parse_line(self, args):
     """ Returns a parsed line. """
     numpts, _sep, pts = args.partition(' ')
     pts = [int(p) for p in pts.split()]
     numpts = int(numpts)
     # this next bit would be much easier if open polygons were
     # explicitly acceptable
     # TODO yuck, and callers need to special-case this
     return ('lines', [Line((pts[i], pts[i + 1]),(pts[i + 2], pts[i + 3]))
                       for i in range(0, (numpts - 1) * 2, 2)])
示例#7
0
 def parse_p_line(self, parts):
     """ Parse a P (Polyline) line """
     num_points = int(parts[1])
     lines = []
     last_point = None
     for i in xrange(num_points):
         point = int(parts[5 + 2 * i]), int(parts[6 + 2 * i])
         if last_point is not None:
             lines.append(Line(last_point, point))
         last_point = point
     return lines
    def parse_l(self, data, is_relative):
        """ Parse an L or l (lineto) segment. """

        points, data = self.parse_points(data)

        for point in points:
            point = self.get_path_point(point, is_relative)
            self.shapes.append(
                Line(make_point(self.cur_point, self.svg_mult),
                     make_point(point, self.svg_mult)))
            self.cur_point = point

        return data
    def parse_h(self, data, is_relative):
        """ Parse an H or h (horizontal line) segment. """

        nums, data = self.parse_nums(data)

        for num in nums:
            point = (num, 0 if is_relative else self.cur_point[1])
            point = self.get_path_point(point, is_relative)
            self.shapes.append(
                Line(make_point(self.cur_point, self.svg_mult),
                     make_point(point, self.svg_mult)))
            self.cur_point = point

        return data
    def parse_v(self, data, is_relative):
        """ Parse a V or v (vertical line) segment. """

        nums, data = self.parse_nums(data)

        for num in nums:
            point = (0 if is_relative else self.cur_point[0], num)
            point = self.get_path_point(point, is_relative)
            self.shapes.append(
                Line(make_point(self.cur_point, self.svg_mult),
                     make_point(point, self.svg_mult)))
            self.cur_point = point

        return data
    def make_shape_for_wire(self, wire):
        """ Generate an openjson shape for an eaglexml wire. """

        if wire.curve is None:
            return Line((self.make_length(wire.x1), self.make_length(wire.y1)),
                        (self.make_length(wire.x2), self.make_length(wire.y2)))

        curve, x1, y1, x2, y2 = map(
            float, (wire.curve, wire.x1, wire.y1, wire.x2, wire.y2))

        if curve < 0:
            curve = -curve
            negative = True
            mult = -1.0
        else:
            negative = False
            mult = 1.0

        if curve > 180.0:
            major_arc = True
            curve = 360.0 - curve
            mult *= -1.0
        else:
            major_arc = False

        chordlen = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2))

        radius = chordlen / (2.0 * sin(radians(curve) / 2))

        mx, my = (x1 + x2) / 2, (y1 + y2) / 2  # midpoint between arc points

        h = sqrt(pow(radius, 2) - pow(chordlen / 2, 2))  # height of isoceles

        # calculate center point
        cx = mx + mult * h * (y1 - y2) / chordlen
        cy = my + mult * h * (x2 - x1) / chordlen

        if negative:
            start_angle = atan2(y2 - cy, x2 - cx)
            end_angle = start_angle + radians(curve) - (pi
                                                        if major_arc else 0.0)
        else:
            start_angle = atan2(y1 - cy, x1 - cx)
            end_angle = start_angle + radians(curve) + (pi
                                                        if major_arc else 0.0)

        return Arc(self.make_length(cx), self.make_length(cy),
                   round(start_angle / pi, 3) % 2.0,
                   round(end_angle / pi, 3) % 2.0, self.make_length(radius))
    def parse_polyline(self, poly):
        """ Parse a polyline element """

        shapes = []
        last_point = None

        for point in poly.get('points', '').split():
            if point:
                x, y = point.split(',')
                point = (make_x(x, self.svg_mult), make_y(y, self.svg_mult))
                if last_point is not None:
                    shapes.append(Line(last_point, point))
                last_point = point

        return shapes
    def make_body_from_symbol(self, lib, symbol_name):
        """ Contruct an openjson Body from an eagle symbol in a library. """

        body = Body()

        symbol = [
            s for s in get_subattr(lib, 'symbols.symbol')
            if s.name == symbol_name
        ][0]

        for wire in symbol.wire:
            body.add_shape(
                Line((self.make_length(wire.x1), self.make_length(wire.y1)),
                     (self.make_length(wire.x2), self.make_length(wire.y2))))

        return body
    def parse_m(self, data, is_relative):
        """ Parse an M or m (moveto) segment. """

        points, data = self.parse_points(data)

        for i, point in enumerate(points):
            point = self.get_path_point(point, is_relative)
            if i == 0:
                self.start_point = self.cur_point = point
            else: # subsequent moves are lineto's
                self.shapes.append(
                    Line(make_point(self.cur_point, self.svg_mult),
                         make_point(point, self.svg_mult)))
                self.cur_point = point

        return data
示例#15
0
    def make_body_from_symbol(self, lib, symbol_name):
        """ Construct an openjson Body from an eagle symbol in a library. """

        body = Body()

        symbol = [
            s for s in get_subattr(lib, 'symbols.symbol')
            if s.name == symbol_name
        ][0]

        for wire in symbol.wire:
            body.add_shape(
                Line((self.make_length(wire.x1), self.make_length(wire.y1)),
                     (self.make_length(wire.x2), self.make_length(wire.y2))))

        for rect in symbol.rectangle:
            x = self.make_length(rect.x1)
            y = self.make_length(rect.y1)
            width = self.make_length(rect.x2) - x
            height = self.make_length(rect.y2) - y
            body.add_shape(Rectangle(x, y + height, width, height))

        pin_map = {}

        for pin in symbol.pin:
            connect_point = (self.make_length(pin.x), self.make_length(pin.y))
            null_point = self.get_pin_null_point(connect_point, pin.length,
                                                 pin.rot)
            label = self.get_pin_label(pin, null_point)
            pin_map[pin.name] = Pin(pin.name, null_point, connect_point, label)
            body.add_pin(pin_map[pin.name])

        ann_map = {}

        for text in symbol.text:
            x = self.make_length(text.x)
            y = self.make_length(text.y)
            content = '' if text.valueOf_ is None else text.valueOf_
            rotation = self.make_angle('0' if text.rot is None else text.rot)
            if content == '>NAME':
                ann_map['name'] = Annotation(content, x, y, rotation, 'true')
            elif content == '>VALUE':
                ann_map['value'] = Annotation(content, x, y, rotation, 'true')
            else:
                body.add_shape(Label(x, y, content, 'left', rotation))

        return body, pin_map, ann_map
示例#16
0
    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)
示例#17
0
    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)
示例#18
0
    def _move(self, block):
        """ Draw a shape, or a segment of a trace or fill. """
        start = tuple([self.status[k] for k in ('x', 'y')])
        end = self._target_pos(block)
        ends = (Point(start), Point(end))
        apertures = self.layer_buff.apertures

        if self.status['draw'] == 'ON':
            # generate segment
            if self.status['interpolation'] == 'LINEAR':
                seg = Line(start, end)
            else:
                ctr_offset = block[2:]
                seg = self._draw_arc(ends, ctr_offset)

            # append segment to fill
            if self.status['outline_fill']:
                self.fill_buff.append((ends, seg))
            else:
                aperture = apertures[self.status['aperture']]
                if isinstance(aperture.shape, Rectangle):
                    # construct a smear
                    self._check_smear(seg, aperture.shape)
                    self.img_buff.smears.append(Smear(seg, aperture.shape))
                else:
                    wid = aperture.shape.radius * 2
                    trace = self.trace_buff.get_trace(wid, seg)
                    if trace is None:
                        # FIXME(shamer): fill into segments not old Trace class
                        #trace = Trace(wid)
                        self.img_buff.traces.append(trace)
                    trace.segments.append(seg)
                    self.trace_buff.add_segment(seg, trace)

        elif self.status['draw'] == 'FLASH':
            aperture = apertures[self.status['aperture']]
            shape_inst = ShapeInstance(ends[1], aperture)
            self.img_buff.shape_instances.append(shape_inst)

        return {'x': end[0], 'y': end[1]}
示例#19
0
    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
示例#20
0
    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
示例#21
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)
示例#22
0
    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