def test_bounds_all_elts(self):
        '''bounds() with all the elements competing'''
        net = Net('foo')
        mkbounds(net, 3, 3, -1, -2)
        self.des.add_net(net)

        annot = Annotation('foo', 3, 3, 0, True)
        mkbounds(annot, 3, 3, 3, 5)
        self.des.design_attributes.add_annotation(annot)

        libcomp = Component('bar')
        libcomp.add_symbol(Symbol())
        libcomp.symbols[0].add_body(Body())
        mkbounds(libcomp.symbols[0].bodies[0], 0, 0, 3, 3)
        self.des.add_component('foo', libcomp)

        compinst = ComponentInstance('bar', 'foo', 0)
        compinst.add_symbol_attribute(SymbolAttribute(3, 0, 0))
        self.des.add_component_instance(compinst)

        top_left, btm_right = self.des.bounds()
        self.assertEqual(top_left.x, -1)
        self.assertEqual(top_left.y, -2)
        self.assertEqual(btm_right.x, 6)
        self.assertEqual(btm_right.y, 5)
 def parse_components(self, components):
     """ Extract a component library. """
     for library_id, component in components.items():
         name = component.get('name')
         comp = Component(name)
         # Get attributes
         for key, value in component.get('attributes').items():
             comp.add_attribute(key, value)
         for symbol in component.get('symbols'):
             symb = self.parse_symbol(symbol)
             comp.add_symbol(symb)
         self.design.add_component(library_id, comp)
    def parse(self):
        """ Parses a component from the library, returns a Compenent. """
        part = Component(self.filename)
        part.add_symbol(Symbol())
        part.symbols[0].add_body(Body())
        
        tree = ViewDrawBase.parse(self)
        for attr in tree['attr']:
            part.add_attribute(*attr)
        for shape in tree['shape'] + sum(tree['lines'], []):
            part.symbols[0].bodies[0].add_shape(shape)
        for pin in tree['pin']:
            part.symbols[0].bodies[0].add_pin(pin)

        self.correct_y(part)
        return part
 def __init__(self, idref):
     self.component = Component(idref)
     self.next_pin_number = 0
     self.cid2termid = {} # connid -> termid
     self.termid2pin = {} # termid -> Pin
     self.terminals = set()
     self.width = 0.0
     self.height = 0.0
    def test_bounds_parts(self):
        '''test bounds() with just components in the design'''
        libcomp = Component('bar')
        libcomp.add_symbol(Symbol())
        libcomp.symbols[0].add_body(Body())
        mkbounds(libcomp.symbols[0].bodies[0], 0, 0, 10, 10)
        self.des.add_component('foo', libcomp)
        for (x, y) in ((1, 3), (3, 2), (5, 3), (3, 7)):
            compinst = ComponentInstance(str((x, y)), 'foo', 0)
            compinst.add_symbol_attribute(SymbolAttribute(x, y, 0))
            self.des.add_component_instance(compinst)

        top_left, btm_right = self.des.bounds()
        self.assertEqual(top_left.x, 1)
        self.assertEqual(top_left.y, 2)
        self.assertEqual(btm_right.x, 15)
        self.assertEqual(btm_right.y, 17)
class ComponentParser(object):
    """I parse components from Fritzing libraries."""

    # The svg files in fritzing libraries are specified in pixels that
    # are 72dpi. The schematics are in 90dpi.
    svg_mult = 90.0 / 72.0

    def __init__(self, idref):
        self.component = Component(idref)
        self.next_pin_number = 0
        self.cid2termid = {} # connid -> termid
        self.termid2pin = {} # termid -> Pin
        self.terminals = set()
        self.width = 0.0
        self.height = 0.0


    def parse_fzp(self, fzp_file):
        """ Parse the Fritzing component file """

        tree = ElementTree(file=fzp_file)

        try:
            prefix = tree.find('label').text
        except AttributeError:
            pass
        else:
            self.component.add_attribute('_prefix', prefix)

        symbol = Symbol()
        self.component.add_symbol(symbol)

        self.body = Body()
        symbol.add_body(self.body)

        self.cid2termid.update(self.parse_terminals(tree))
        self.terminals.update(self.cid2termid.values())

        layers = tree.find('views/schematicView/layers')
        if layers is None:
            self.image = None
        else:
            self.image = layers.get('image')


    def connect_point(self, cid, inst, point):
        """ Given a connector id, instance id, and a NetPoint,
        add the appropriate ConnectedComponent to the point """

        termid = self.cid2termid.get(cid)
        pin = self.termid2pin.get(termid)

        if pin is not None:
            ccpt = ConnectedComponent(inst.instance_id, pin.pin_number)
            point.add_connected_component(ccpt)


    def get_next_pin_number(self):
        """ Return the next pin number """

        nextpn = self.next_pin_number
        self.next_pin_number += 1
        return str(nextpn)


    def parse_terminals(self, tree):
        """ Return a dictionary mapping connector id's to terminal id's """

        cid2termid = {}

        for conn in tree.findall('connectors/connector'):
            plug = conn.find('views/schematicView/p')
            if plug is None:
                continue

            termid = plug.get('terminalId')
            if termid is None:
                termid = plug.get('svgId')

            if termid is not None:
                cid2termid[conn.get('id')] = termid

        return cid2termid


    def parse_svg(self, svg_file):
        """ Parse the shapes and pins from an svg file """

        tree = ElementTree(file=svg_file)
        viewbox = tree.getroot().get('viewBox')

        if viewbox != None:
            self.width, self.height = [float(v) for v in viewbox.split()[-2:]]
            self.width *= self.svg_mult
            self.height *= self.svg_mult

        _iter = tree.getroot().getiterator()
        for element in _iter:
            for shape in self.parse_shapes(element):
                self.body.add_shape(shape)
                if element.get('id') in self.terminals:
                    pin = get_pin(shape)
                    if pin is not None:
                        pin.pin_number = self.get_next_pin_number()
                        self.termid2pin[element.get('id')] = pin
                        self.body.add_pin(pin)


    def parse_shapes(self, element):
        """ Parse a list of shapes from an svg element """

        tag = element.tag.rsplit('}', -1)[-1]

        if tag == 'circle':
            return self.parse_circle(element)
        elif tag == 'rect':
            return self.parse_rect(element)
        elif tag == 'line':
            return self.parse_line(element)
        elif tag == 'path':
            return self.parse_path(element)
        elif tag == 'polygon':
            return self.parse_polygon(element)
        elif tag == 'polyline':
            return self.parse_polyline(element)
        else:
            return []

    def parse_rect(self, rect):
        """ Parse a rect element """

        x, y = (get_x(rect, mult=self.svg_mult),
                get_y(rect, mult=self.svg_mult))
        width, height = (get_length(rect, 'width', self.svg_mult),
                         get_length(rect, 'height', self.svg_mult))
        return [Rectangle(x, y, width, height)]


    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 parse_path(self, path):
        """ Parse a path element """

        return PathParser(path).parse()


    def parse_polygon(self, poly):
        """ Parse a polygon element """

        shape = Polygon()

        for point in poly.get('points', '').split():
            if point:
                x, y = point.split(',')
                shape.add_point(make_x(x, self.svg_mult),
                                make_y(y, self.svg_mult))

        if shape.points:
            shape.add_point(shape.points[0].x, shape.points[0].y)

        return [shape]


    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 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 parse_library(self, filename, circuit):
        """
        Parse the library file and add the components to the given
        circuit.
        """

        f = open(filename)

        for line in f:
            parts = line.strip().split()
            prefix = parts[0]

            if prefix == 'DEF':
                component = Component(parts[1])
                component.add_attribute('_prefix', parts[2])
                symbol = Symbol()
                component.add_symbol(symbol)
                body = Body()
                symbol.add_body(body)
            elif prefix == 'A':  # Arc
                x, y, radius, start, end = [int(i) for i in parts[1:6]]
                # convert tenths of degrees to pi radians
                start = round(start / 1800.0, 1)
                end = round(end / 1800.0, 1)
                body.add_shape(shape.Arc(x, y, start, end, radius))
            elif prefix == 'C':  # Circle
                x, y, radius = [int(i) for i in parts[1:4]]
                body.add_shape(shape.Circle(x, y, radius))
            elif prefix == 'P':  # Polyline
                num_points = int(parts[1])
                poly = shape.Polygon()
                for i in xrange(num_points):
                    x, y = int(parts[5 + 2 * i]), int(parts[6 + 2 * i])
                    poly.addPoint(x, y)
                body.add_shape(poly)
            elif prefix == 'S':  # Rectangle
                x, y, x2, y2 = [int(i) for i in parts[1:5]]
                rec = shape.Rectangle(x, y, x2 - x, y2 - y)
                body.add_shape(rec)
            elif prefix == 'T':  # Text
                angle, x, y = [int(i) for i in parts[1:4]]
                angle = round(angle / 1800.0, 1)
                text = parts[8].replace('~', ' ')
                body.add_shape(shape.Label(x, y, text, 'left', angle))
            elif prefix == 'X':  # Pin
                num, direction = int(parts[2]), parts[6]
                p2x, p2y, pinlen = int(parts[3]), int(parts[4]), int(parts[5])
                if direction == 'U':  # up
                    p1x = p2x
                    p1y = p2y - pinlen
                elif direction == 'D':  # down
                    p1x = p2x
                    p1y = p2y + pinlen
                elif direction == 'L':  # left
                    p1x = p2x - pinlen
                    p1y = p2y
                elif direction == 'R':  # right
                    p1x = p2x + pinlen
                    p1y = p2y
                else:
                    raise ValueError('unexpected pin direction', direction)
                # TODO: label?
                body.add_pin(Pin(num, (p1x, p1y), (p2x, p2y)))
            elif prefix == 'ENDDEF':
                circuit.add_component(component.name, component)

        f.close()
    def parse_library(self, filename, circuit):
        """
        Parse the library file and add the components to the given
        circuit.
        """

        f = open(filename)

        for line in f:
            parts = line.strip().split()
            prefix = parts[0]

            if prefix == "DEF":
                component = Component(parts[1])
                component.add_attribute("_prefix", parts[2])
                symbol = Symbol()
                component.add_symbol(symbol)
                body = Body()
                symbol.add_body(body)
            elif prefix == "A":  # Arc
                x, y, radius, start, end = [int(i) for i in parts[1:6]]
                # convert tenths of degrees to pi radians
                start = round(start / 1800.0, 1)
                end = round(end / 1800.0, 1)
                body.add_shape(shape.Arc(x, y, start, end, radius))
            elif prefix == "C":  # Circle
                x, y, radius = [int(i) for i in parts[1:4]]
                body.add_shape(shape.Circle(x, y, radius))
            elif prefix == "P":  # Polyline
                num_points = int(parts[1])
                poly = shape.Polygon()
                for i in xrange(num_points):
                    x, y = int(parts[5 + 2 * i]), int(parts[6 + 2 * i])
                    poly.addPoint(x, y)
                body.add_shape(poly)
            elif prefix == "S":  # Rectangle
                x, y, x2, y2 = [int(i) for i in parts[1:5]]
                rec = shape.Rectangle(x, y, x2 - x, y2 - y)
                body.add_shape(rec)
            elif prefix == "T":  # Text
                angle, x, y = [int(i) for i in parts[1:4]]
                angle = round(angle / 1800.0, 1)
                text = parts[8].replace("~", " ")
                body.add_shape(shape.Label(x, y, text, "left", angle))
            elif prefix == "X":  # Pin
                num, direction = int(parts[2]), parts[6]
                p2x, p2y, pinlen = int(parts[3]), int(parts[4]), int(parts[5])
                if direction == "U":  # up
                    p1x = p2x
                    p1y = p2y - pinlen
                elif direction == "D":  # down
                    p1x = p2x
                    p1y = p2y + pinlen
                elif direction == "L":  # left
                    p1x = p2x - pinlen
                    p1y = p2y
                elif direction == "R":  # right
                    p1x = p2x + pinlen
                    p1y = p2y
                else:
                    raise ValueError("unexpected pin direction", direction)
                # TODO: label?
                body.add_pin(Pin(num, (p1x, p1y), (p2x, p2y)))
            elif prefix == "ENDDEF":
                circuit.add_component(component.name, component)

        f.close()
 def __init__(self, line):
     parts = line.split()
     self.component = Component(parts[1])
     self.component.add_attribute('_prefix', parts[2])
     self.num_units = max(int(parts[7]), 1)
class ComponentParser(object):
    """I parse components from KiCAD libraries."""

    # the column positions of the unit and convert fields
    unit_cols = dict(A=6, C=4, P=2, S=5, T=6, X=9)
    convert_cols = dict((k, v+1) for k, v in unit_cols.items())

    def __init__(self, line):
        parts = line.split()
        self.component = Component(parts[1])
        self.component.add_attribute('_prefix', parts[2])
        self.num_units = max(int(parts[7]), 1)


    def build_symbols(self, has_convert):
        """ Build all Symbols and Bodies for this component. The
        has_convert argument should be True if there are DeMorgan
        convert bodies. """

        for _ in range(2 if has_convert else 1):
            symbol = Symbol()
            for _ in range(self.num_units):
                symbol.add_body(Body())
            self.component.add_symbol(symbol)


    def iter_bodies(self, unit, convert, has_convert):
        """ Return an iterator over all the bodies implied by the
        given unit and convert options. A unit of 0 means all units
        for the given convert. A convert of 0 means both converts for
        the given unit. If both are 0 it applies to all bodies."""

        if convert == 0 and has_convert:
            symbol_indices = [0, 1] # both regular and convert
        elif convert in (0, 1):
            symbol_indices = [0] # just regular
        else:
            symbol_indices = [1] # just convert

        if unit == 0:
            body_indices = range(self.num_units) # all bodies
        else:
            body_indices = [unit-1] # one body

        for symbol_index in symbol_indices:
            for body_index in body_indices:
                try:
                    yield self.component.symbols[symbol_index].bodies[body_index]
                except IndexError:
                    pass

    def parse(self, f):
        """ Parse a DEF block and return the Component """

        draw_lines = [] # (unit, convert, prefix, parts)

        for line in f:
            parts = line.split()
            prefix = parts[0]

            if prefix in ('A', 'C', 'P', 'S', 'T', 'X'):
                draw_lines.append((int(parts[self.unit_cols[prefix]]),
                                   int(parts[self.convert_cols[prefix]]),
                                   prefix, parts))
            elif prefix == 'ENDDEF':
                break

        has_convert = any(convert == 2 for _, convert, _, _ in draw_lines)

        self.build_symbols(has_convert)

        for unit, convert, prefix, parts in draw_lines:
            method = getattr(self, 'parse_%s_line' % (prefix.lower(),))

            for body in self.iter_bodies(unit, convert, has_convert):
                obj = method(parts)

                if prefix == 'X':
                    body.add_pin(obj)
                else:
                    if isinstance(obj, (list, tuple)):
                        [body.add_shape(o) for o in obj]
                    else:
                        body.add_shape(obj)

        return self.component


    def parse_a_line(self, parts):
        """ Parse an A (Arc) line """
        x, y, radius, start, end = [int(i) for i in parts[1:6]]
        # convert tenths of degrees to pi radians
        start = start / 1800.0
        end = end / 1800.0
        return Arc(make_length(x), make_length(y),
                   end, start, make_length(radius))


    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 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):
            x, y = int(parts[5 + 2 * i]), int(parts[6 + 2 * i])
            point = (make_length(x), make_length(y))
            if last_point is not None:
                lines.append(Line(last_point, point))
            last_point = point
        return lines


    def parse_s_line(self, parts):
        """ Parse an S (Rectangle) line """
        x, y, x2, y2 = [int(i) for i in parts[1:5]]
        return Rectangle(make_length(x), make_length(y),
                         make_length(x2 - x), make_length(y - y2))


    def parse_t_line(self, parts):
        """ Parse a T (Text) line """
        angle, x, y = [int(i) for i in parts[1:4]]
        angle = angle / 1800.0
        text = parts[8].replace('~', ' ')

        if len(parts) >= 12:
            align = {'C': 'center', 'L': 'left', 'R': 'right'}.get(parts[11])
        else:
            align = 'left'

        return Label(make_length(x), make_length(y), text, align, angle)


    def parse_x_line(self, parts):
        """ Parse an X (Pin) line """
        name, num, direction = parts[1], parts[2], parts[6]
        p2x, p2y, pinlen = int(parts[3]), int(parts[4]), int(parts[5])

        if direction == 'U': # up
            p1x = p2x
            p1y = p2y + pinlen
            label_x = p2x - 20
            label_y = p2y + int(pinlen / 2)
            label_rotation = 1.5
        elif direction == 'D': # down
            p1x = p2x
            p1y = p2y - pinlen
            label_x = p2x - 20
            label_y = p2y - int(pinlen / 2)
            label_rotation = 1.5
        elif direction == 'L': # left
            p1x = p2x - pinlen
            p1y = p2y
            label_x = p2x - int(pinlen / 2)
            label_y = p2y + 20
            label_rotation = 0
        elif direction == 'R': # right
            p1x = p2x + pinlen
            p1y = p2y
            label_x = p2x + int(pinlen / 2)
            label_y = p2y + 20
            label_rotation = 0
        else:
            raise ValueError('unexpected pin direction', direction)

        if name == '~':
            label = None
        else:
            label = Label(make_length(label_x), make_length(label_y),
                          name, 'center', label_rotation)

        return Pin(num,
                   (make_length(p1x), make_length(p1y)),
                   (make_length(p2x), make_length(p2y)), label)