def parse(self, filename, library_filename=None):
        """ Parse a kicad file into a design """

        design = Design()
        segments = set() # each wire segment
        junctions = set() # wire junction point (connects all wires under it)

        self.instance_names = []

        self.library = KiCADLibrary()

        if library_filename is None:
            directory, _ = split(filename)
            for dir_file in listdir(directory):
                if dir_file.endswith('.lib'):
                    self.library.parse(directory + '/' + dir_file)

        for cpt in self.library.components:
            design.add_component(cpt.name, cpt)

        with open(filename) as f:
            libs = []
            line = f.readline().strip()

            # parse the library references
            while line and line != "$EndDescr":
                if line.startswith('LIBS:'):
                    libs.extend(line.split(':', 1)[1].split(','))
                line = f.readline().strip()

            # Now parse wires and components, ignore connections, we get
            # connectivity from wire segments

            line = f.readline()

            while line:
                prefix = line.split()[0]

                if line.startswith('Wire Wire Line'):
                    self.parse_wire(f, segments)
                elif prefix == "Connection": # Store these to apply later
                    self.parse_connection(line, junctions)
                elif prefix == "Text":
                    design.design_attributes.add_annotation(
                        self.parse_text(f, line))
                elif prefix == "$Comp": # Component Instance
                    inst, comp = self.parse_component_instance(f)
                    design.add_component_instance(inst)
                    if comp is not None:
                        design.add_component(comp.name, comp)
                    self.ensure_component(design, inst.library_id, libs)

                line = f.readline()

        segments = self.divide(segments, junctions)
        design.nets = self.calc_nets(design, segments)

        design.scale(MULT)

        return design
Esempio n. 2
0
    def parse(self, filename, library_filename=None):
        """ Parse a kicad file into a design """

        design = Design()
        segments = set()  # each wire segment
        junctions = set()  # wire junction point (connects all wires under it)

        self.instance_names = []

        self.library = KiCADLibrary()

        if library_filename is None:
            directory, _ = split(filename)
            for dir_file in listdir(directory):
                if dir_file.endswith('.lib'):
                    self.library.parse(directory + '/' + dir_file)

        for cpt in self.library.components:
            design.add_component(cpt.name, cpt)

        with open(filename) as f:
            libs = []
            line = f.readline().strip()

            # parse the library references
            while line and line != "$EndDescr":
                if line.startswith('LIBS:'):
                    libs.extend(line.split(':', 1)[1].split(','))
                line = f.readline().strip()

            # Now parse wires and components, ignore connections, we get
            # connectivity from wire segments

            line = f.readline()

            while line:
                prefix = line.split()[0]

                if line.startswith('Wire Wire Line'):
                    self.parse_wire(f, segments)
                elif prefix == "Connection":  # Store these to apply later
                    self.parse_connection(line, junctions)
                elif prefix == "Text":
                    design.design_attributes.add_annotation(
                        self.parse_text(f, line))
                elif prefix == "$Comp":  # Component Instance
                    inst, comp = self.parse_component_instance(f)
                    design.add_component_instance(inst)
                    if comp is not None:
                        design.add_component(comp.name, comp)
                    self.ensure_component(design, inst.library_id, libs)

                line = f.readline()

        segments = self.divide(segments, junctions)
        design.nets = self.calc_nets(design, segments)

        design.scale(MULT)

        return design
class EagleXML(object):
    """ The Eagle XML Format Parser.

    This parser uses code generated by generateDS.py which converts an xsd
    file to a set of python objects with parse and export functions.
    That code is in generated.py. It was created by the following steps:

      1. Started with eagle.dtd from Eagle 6.2.0.
      2. Removed inline comments in dtd (was breaking conversion to xsd).
         The dtd is also stored in this directory.
      3. Converted to eagle.xsd using dtd2xsd.pl from w3c.
         The xsd is also stored in this directory.
      4. Run a modified version of generateDS.py with the following arguments:
           --silence --external-encoding=utf-8 -o generated.py
     """

    SCALE = 2.0
    MULT = 90 / 25.4  # mm to 90 dpi

    def __init__(self):
        self.design = Design()

        # map (component, gate name) to body indices
        self.cptgate2body_index = {}

        # map (component, gate name) to pin maps, dicts from strings
        # (pin names) to Pins. These are used during pinref processing
        # in segments.
        self.cptgate2pin_map = defaultdict(dict)

        # map (component, gate names) to annotation maps, dicts from
        # strings (name|value) to Annotations. These represent the
        # >NAME and >VALUE texts on eagle components, which must be
        # converted into component instance annotations since their
        # contents depend on the component instance name and value.
        self.cptgate2ann_map = defaultdict(dict)

        # map part names to component instances. These are used during
        # pinref processing in segments.
        self.part2inst = {}

        # map part names to gate names to symbol attributes. These
        # are used during pinref processing in segments.
        self.part2gate2symattr = defaultdict(dict)

    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an
        eagle xml schematic """

        with open(filename, 'r') as f:
            data = f.read(4096)
        confidence = 0.0
        if 'eagle.dtd' in data:
            confidence += 0.9
        return confidence

    def parse(self, filename):
        """ Parse an Eagle XML file into a design """

        root = parse(filename)

        self.make_components(root)
        self.make_component_instances(root)
        self.make_nets(root)
        self.design.scale(EAGLE_SCALE)

        return self.design

    def make_components(self, root):
        """ Construct openjson components from an eagle model. """

        for lib in get_subattr(root, 'drawing.schematic.libraries.library',
                               ()):
            for deviceset in get_subattr(lib, 'devicesets.deviceset', ()):
                for cpt in self.make_deviceset_components(lib, deviceset):
                    self.design.components.add_component(cpt.name, cpt)

    def make_deviceset_components(self, lib, deviceset):
        """ Construct openjson components for each device in an
        eaglexml deviceset in a library."""

        for device in deviceset.devices.device:
            yield self.make_device_component(lib, deviceset, device)

    def make_device_component(self, lib, deviceset, device):
        """ Construct an openjson component for a device in a deviceset. """

        cpt = Component(lib.name + ':' + deviceset.name + ':' + device.name)

        cpt.add_attribute('eaglexml_library', lib.name)
        cpt.add_attribute('eaglexml_deviceset', deviceset.name)
        cpt.add_attribute('eaglexml_device', device.name)

        symbol = Symbol()
        cpt.add_symbol(symbol)

        assignment = PinNumberAssignment(device)

        for i, gate in enumerate(get_subattr(deviceset, 'gates.gate')):
            body, pin_map, ann_map = self.make_body_from_symbol(
                lib, gate.symbol, assignment.get_pin_number_lookup(gate.name))
            symbol.add_body(body)
            cpt.add_attribute('eaglexml_symbol_%d' % i, gate.symbol)
            cpt.add_attribute('eaglexml_gate_%d' % i, gate.name)
            self.cptgate2body_index[cpt, gate.name] = len(symbol.bodies) - 1
            self.cptgate2pin_map[cpt, gate.name] = pin_map
            self.cptgate2ann_map[cpt, gate.name] = ann_map

        return cpt

    def make_body_from_symbol(self, lib, symbol_name, pin_number_lookup):
        """ Construct an openjson SBody from an eagle symbol in a library. """

        body = SBody()

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

        for wire in symbol.wire:
            body.add_shape(self.make_shape_for_wire(wire))

        for rect in symbol.rectangle:
            rotation = make_angle('0' if rect.rot is None else rect.rot)
            x1, y1 = rotate_point(
                (self.make_length(rect.x1), self.make_length(rect.y1)),
                rotation)
            x2, y2 = rotate_point(
                (self.make_length(rect.x2), self.make_length(rect.y2)),
                rotation)
            ux, uy = min(x1, x2), max(y1, y2)
            lx, ly = max(x1, x2), min(y1, y2)
            body.add_shape(Rectangle(ux, uy, lx - ux, uy - ly))

        for poly in symbol.polygon:
            map(body.add_shape, self.make_shapes_for_poly(poly))

        for circ in symbol.circle:
            body.add_shape(self.make_shape_for_circle(circ))

        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_number_lookup(pin.name), null_point,
                                    connect_point, label)
            if pin.direction:
                pin_map[pin.name].add_attribute('eaglexml_direction',
                                                pin.direction)
            if pin.visible:
                pin_map[pin.name].add_attribute('eaglexml_visible',
                                                pin.visible)
            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 = make_angle('0' if text.rot is None else text.rot)
            align = 'right' if is_mirrored(text.rot) else 'left'
            if rotation == 0.5:
                rotation = 1.5
            if content.lower() == '>name':
                ann_map['name'] = Annotation(content, x, y, rotation, 'true')
            elif content.lower() == '>value':
                ann_map['value'] = Annotation(content, x, y, rotation, 'true')
            else:
                body.add_shape(
                    Label(x, y, content, align=align, rotation=rotation))

        return body, pin_map, ann_map

    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 make_shapes_for_poly(self, poly):
        """ Generate openjson shapes for an eaglexml polygon. """

        # TODO: handle curves
        opoly = Polygon()
        for vertex in poly.vertex:
            opoly.add_point(self.make_length(vertex.x),
                            self.make_length(vertex.y))
        yield opoly

    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 get_pin_null_point(self, (x, y), length, rotation):
        """ Return the null point of a pin given its connect point,
        length, and rotation. """

        if length == 'long':
            distance = int(27 * self.SCALE)  # .3 inches
        elif length == 'middle':
            distance = int(18 * self.SCALE)  # .2 inches
        elif length == 'short':
            distance = int(9 * self.SCALE)  # .1 inches
        else:  # point
            distance = 0

        if rotation is None:
            rotation = ""

        if rotation.endswith('R90'):
            coords = (x, y + distance)
        elif rotation.endswith('R180'):
            coords = (x - distance, y)
        elif rotation.endswith('R270'):
            coords = (x, y - distance)
        else:
            coords = (x + distance, y)

        if is_mirrored(rotation):
            x, y = coords
            coords = (-x, y)

        return coords
class EagleXML(object):
    """ The Eagle XML Format Parser.

    This parser uses code generated by generateDS.py which converts an xsd
    file to a set of python objects with parse and export functions.
    That code is in generated.py. It was created by the following steps:

      1. Started with eagle.dtd from Eagle 6.2.0.
      2. Removed inline comments in dtd (was breaking conversion to xsd).
         The dtd is also stored in this directory.
      3. Converted to eagle.xsd using dtd2xsd.pl from w3c.
         The xsd is also stored in this directory.
      4. Run a modified version of generateDS.py with the following arguments:
           --silence --external-encoding=utf-8 -o generated.py
     """

    SCALE = 2.0
    MULT =  90 / 25.4 # mm to 90 dpi

    def __init__(self):
        self.design = Design()

        # map (component, gate name) to body indices
        self.cptgate2body_index = {}

        # map (component, gate name) to pin maps, dicts from strings
        # (pin names) to Pins. These are used during pinref processing
        # in segments.
        self.cptgate2pin_map = defaultdict(dict)

        # map (component, gate names) to annotation maps, dicts from
        # strings (name|value) to Annotations. These represent the
        # >NAME and >VALUE texts on eagle components, which must be
        # converted into component instance annotations since their
        # contents depend on the component instance name and value.
        self.cptgate2ann_map = defaultdict(dict)

        # map part names to component instances. These are used during
        # pinref processing in segments.
        self.part2inst = {}

        # map part names to gate names to symbol attributes. These
        # are used during pinref processing in segments.
        self.part2gate2symattr = defaultdict(dict)


    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an
        eagle xml schematic """

        with open(filename, 'r') as f:
            data = f.read(4096)
        confidence = 0.0
        if 'eagle.dtd' in data:
            confidence += 0.9
        return confidence


    def parse(self, filename):
        """ Parse an Eagle XML file into a design """

        root = parse(filename)

        self.make_components(root)
        self.make_component_instances(root)
        self.make_nets(root)
        self.design.scale(EAGLE_SCALE)

        return self.design


    def make_components(self, root):
        """ Construct openjson components from an eagle model. """

        for lib in get_subattr(root, 'drawing.schematic.libraries.library', ()):
            for deviceset in get_subattr(lib, 'devicesets.deviceset', ()):
                cpt = self.make_deviceset_component(lib, deviceset)
                self.design.components.add_component(cpt.name, cpt)


    def make_deviceset_component(self, lib, deviceset):
        """ Construct an openjson component for an eaglexml deviceset
        in a library."""

        cpt = Component(lib.name + ':' + deviceset.name + ':logical')

        cpt.add_attribute('eaglexml_type', 'logical')
        cpt.add_attribute('eaglexml_library', lib.name)
        cpt.add_attribute('eaglexml_deviceset', deviceset.name)

        symbol = Symbol()
        cpt.add_symbol(symbol)

        for i, gate in enumerate(get_subattr(deviceset, 'gates.gate')):
            body, pin_map, ann_map = self.make_body_from_symbol(lib, gate.symbol)
            symbol.add_body(body)
            cpt.add_attribute('eaglexml_symbol_%d' % i, gate.symbol)
            cpt.add_attribute('eaglexml_gate_%d' % i, gate.name)
            self.cptgate2body_index[cpt, gate.name] = len(symbol.bodies) - 1
            self.cptgate2pin_map[cpt, gate.name] = pin_map
            self.cptgate2ann_map[cpt, gate.name] = ann_map

        return cpt


    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))

        for poly in symbol.polygon:
            map(body.add_shape, self.make_shapes_for_poly(poly))

        for circ in symbol.circle:
            body.add_shape(self.make_shape_for_circle(circ))

        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)
            if pin.direction:
                pin_map[pin.name].add_attribute('eaglexml_direction', pin.direction)
            if pin.visible:
                pin_map[pin.name].add_attribute('eaglexml_visible', pin.visible)
            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


    def make_shapes_for_poly(self, poly):
        """ Generate openjson shapes for an eaglexml polygon. """

        # TODO: handle curves
        opoly = Polygon()
        for vertex in poly.vertex:
            opoly.add_point(self.make_length(vertex.x),
                            self.make_length(vertex.y))
        yield opoly


    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 get_pin_null_point(self, (x, y), length, rotation):
        """ Return the null point of a pin given its connect point,
        length, and rotation. """

        if length == 'long':
            distance = int(27 * self.SCALE) # .3 inches
        elif length == 'middle':
            distance = int(18 * self.SCALE) # .2 inches
        elif length == 'short':
            distance = int(9 * self.SCALE) # .1 inches
        else: # point
            distance = 0

        if rotation is None:
            rotation = ""

        if rotation.endswith('R90'):
            coords = (x, y + distance)
        elif rotation.endswith('R180'):
            coords = (x - distance, y)
        elif rotation.endswith('R270'):
            coords = (x, y - distance)
        else:
            coords = (x + distance, y)

        if rotation.startswith('M'):
            x, y = coords
            coords = (-x, y)

        return coords
class EagleXML(object):
    """ The Eagle XML Format Parser.

    This parser uses code generated by generateDS.py which converts an xsd
    file to a set of python objects with parse and export functions.
    That code is in generated.py. It was created by the following steps:

      1. Started with eagle.dtd from Eagle 6.2.0.
      2. Removed inline comments in dtd (was breaking conversion to xsd).
         The dtd is also stored in this directory.
      3. Converted to eagle.xsd using dtd2xsd.pl from w3c.
         The xsd is also stored in this directory.
      4. Run a modified version of generateDS.py with the following arguments:
           --silence --external-encoding=utf-8 -o generated.py
     """

    SCALE = 2.0
    MULT =  90 / 25.4 # mm to 90 dpi

    def __init__(self):
        self.design = Design()

        # map (component, gate name) to body indices
        self.cptgate2body_index = {}

        # map (component, gate name) to pin maps, dicts from strings
        # (pin names) to Pins. These are used during pinref processing
        # in segments.
        self.cptgate2pin_map = defaultdict(dict)

        # map (component, gate names) to annotation maps, dicts from
        # strings (name|value) to Annotations. These represent the
        # >NAME and >VALUE texts on eagle components, which must be
        # converted into component instance annotations since their
        # contents depend on the component instance name and value.
        self.cptgate2ann_map = defaultdict(dict)

        # map part names to component instances. These are used during
        # pinref processing in segments.
        self.part2inst = {}

        # map part names to gate names to symbol attributes. These
        # are used during pinref processing in segments.
        self.part2gate2symattr = defaultdict(dict)


    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an
        eagle xml schematic """

        with open(filename, 'r') as f:
            data = f.read(4096)
        confidence = 0.0
        if 'eagle.dtd' in data:
            confidence += 0.9
        return confidence


    def parse(self, filename):
        """ Parse an Eagle XML file into a design """

        root = parse(filename)

        self.make_components(root)
        self.make_component_instances(root)
        self.make_nets(root)
        self.design.scale(EAGLE_SCALE)

        return self.design


    def make_components(self, root):
        """ Construct openjson components from an eagle model. """

        for lib in get_subattr(root, 'drawing.schematic.libraries.library', ()):
            for deviceset in get_subattr(lib, 'devicesets.deviceset', ()):
                for cpt in self.make_deviceset_components(lib, deviceset):
                    self.design.components.add_component(cpt.name, cpt)


    def make_deviceset_components(self, lib, deviceset):
        """ Construct openjson components for each device in an
        eaglexml deviceset in a library."""

        for device in deviceset.devices.device:
            yield self.make_device_component(lib, deviceset, device)


    def make_device_component(self, lib, deviceset, device):
        """ Construct an openjson component for a device in a deviceset. """

        cpt = Component(lib.name + ':' + deviceset.name + ':' + device.name)

        cpt.add_attribute('eaglexml_library', lib.name)
        cpt.add_attribute('eaglexml_deviceset', deviceset.name)
        cpt.add_attribute('eaglexml_device', device.name)

        symbol = Symbol()
        cpt.add_symbol(symbol)

        assignment = PinNumberAssignment(device)

        for i, gate in enumerate(get_subattr(deviceset, 'gates.gate')):
            body, pin_map, ann_map = self.make_body_from_symbol(
                lib, gate.symbol, assignment.get_pin_number_lookup(gate.name))
            symbol.add_body(body)
            cpt.add_attribute('eaglexml_symbol_%d' % i, gate.symbol)
            cpt.add_attribute('eaglexml_gate_%d' % i, gate.name)
            self.cptgate2body_index[cpt, gate.name] = len(symbol.bodies) - 1
            self.cptgate2pin_map[cpt, gate.name] = pin_map
            self.cptgate2ann_map[cpt, gate.name] = ann_map

        return cpt


    def make_body_from_symbol(self, lib, symbol_name, pin_number_lookup):
        """ Construct an openjson SBody from an eagle symbol in a library. """

        body = SBody()

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

        for wire in symbol.wire:
            body.add_shape(self.make_shape_for_wire(wire))

        for rect in symbol.rectangle:
            rotation = make_angle('0' if rect.rot is None else rect.rot)
            x1, y1 = rotate_point((self.make_length(rect.x1),
                                   self.make_length(rect.y1)), rotation)
            x2, y2 = rotate_point((self.make_length(rect.x2),
                                   self.make_length(rect.y2)), rotation)
            ux, uy = min(x1, x2), max(y1, y2)
            lx, ly = max(x1, x2), min(y1, y2)
            body.add_shape(Rectangle(ux, uy, lx - ux, uy - ly))

        for poly in symbol.polygon:
            map(body.add_shape, self.make_shapes_for_poly(poly))

        for circ in symbol.circle:
            body.add_shape(self.make_shape_for_circle(circ))

        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_number_lookup(pin.name), null_point,
                                    connect_point, label)
            if pin.direction:
                pin_map[pin.name].add_attribute('eaglexml_direction', pin.direction)
            if pin.visible:
                pin_map[pin.name].add_attribute('eaglexml_visible', pin.visible)
            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 = make_angle('0' if text.rot is None else text.rot)
            align = 'right' if is_mirrored(text.rot) else 'left'
            if rotation == 0.5:
                rotation = 1.5
            if content.lower() == '>name':
                ann_map['name'] = Annotation(content, x, y, rotation, 'true')
            elif content.lower() == '>value':
                ann_map['value'] = Annotation(content, x, y, rotation, 'true')
            else:
                body.add_shape(Label(x, y, content, align=align,
                                     rotation=rotation))

        return body, pin_map, ann_map


    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 make_shapes_for_poly(self, poly):
        """ Generate openjson shapes for an eaglexml polygon. """

        # TODO: handle curves
        opoly = Polygon()
        for vertex in poly.vertex:
            opoly.add_point(self.make_length(vertex.x),
                            self.make_length(vertex.y))
        yield opoly


    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 get_pin_null_point(self, (x, y), length, rotation):
        """ Return the null point of a pin given its connect point,
        length, and rotation. """

        if length == 'long':
            distance = int(27 * self.SCALE) # .3 inches
        elif length == 'middle':
            distance = int(18 * self.SCALE) # .2 inches
        elif length == 'short':
            distance = int(9 * self.SCALE) # .1 inches
        else: # point
            distance = 0

        if rotation is None:
            rotation = ""

        if rotation.endswith('R90'):
            coords = (x, y + distance)
        elif rotation.endswith('R180'):
            coords = (x - distance, y)
        elif rotation.endswith('R270'):
            coords = (x, y - distance)
        else:
            coords = (x + distance, y)

        if is_mirrored(rotation):
            x, y = coords
            coords = (-x, y)

        return coords