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)
    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
 def test_units(self):
     """ Capture absence of units. """
     layout = Layout()
     layout.units = None
     layout.layers.append(Layer())
     design = Design()
     design.layout = layout
     writer = Writer()
     writer.write(design)
 def test_units(self):
     """ Capture absence of units. """
     layout = Layout()
     layout.units = None
     layout.layers.append(Layer())
     design = Design()
     design.layout = layout
     writer = Writer()
     writer.write(design)
Exemple #5
0
    def parse(self):
        '''Returns a Design built up from a schematic file that represents one
        sheet of the original schematic'''
        tree = ViewDrawBase.parse(self)
        # tree['lines'] is a [list of [list of lines]]
        tree['shape'].extend(sum(tree['lines'], []))
        ckt = Design()
        # TODO little weak here, a copy instead?
        ckt.components = self.lib

        for net in tree['net']:
            ckt.add_net(net)
        for inst in tree['inst']:
            ckt.add_component_instance(inst)
            # hold on tight, this is ugly
            for (netid, netpt, pinid) in inst.conns:
                net = [n for n in ckt.nets if n.net_id == netid][0]
                comp = ConnectedComponent(inst.instance_id, pinid)
                net.ibpts[netpt - 1].add_connected_component(comp)
            del inst.conns
        for net in ckt.nets:
            del net.ibpts

        for shape in tree['shape']:
            ckt.add_shape(shape)
            if isinstance(shape, Label):
                ann = Annotation(shape.text, shape.x, shape.y, shape._rotation,
                                 True)
                ckt.design_attributes.add_annotation(ann)

        for k, v, annot in tree['attr']:
            ckt.design_attributes.add_attribute(k, v)
            ckt.design_attributes.add_annotation(annot)

        return ckt
 def test_images(self):
     """ Capture images with no data. """
     layer = Layer()
     layer.images.append(Image())
     layout = Layout()
     layout.units = 'mm'
     layout.layers.append(layer)
     design = Design()
     design.layout = layout
     writer = Writer()
     writer.write(design)
 def test_images(self):
     """ Capture images with no data. """
     layer = Layer()
     layer.images.append(Image())
     layout = Layout()
     layout.units = 'mm'
     layout.layers.append(layer)
     design = Design()
     design.layout = layout
     writer = Writer()
     writer.write(design)
    def parse(self, infile='.'):
        """ Parse tokens from gerber files into a design. """
        is_zip = infile.endswith('.zip')
        openarchive = ZipFile if is_zip else TarFile.open
        archive = batch_member = None
        try:
            # define multiple layers from folder
            if LAYERS_CFG in infile:
                archive = None
                cfg_name = infile
                cfg = open(cfg_name, 'r')

            # define multiple layers from archivea
            else:
                archive = openarchive(infile)
                batch = archive.namelist if is_zip else archive.getnames
                batch_member = archive.open if is_zip else archive.extractfile
                cfg_name = [n for n in batch() if LAYERS_CFG in n][0]
                cfg = batch_member(cfg_name)

        # define single layer from single gerber file
        except ReadError:
            name, ext = path.split(infile)[1].rsplit('.', 1)
            layer_defs = [
                LayerDef(ext.lower() == 'ger' and name or ext, 'unknown',
                         infile)
            ]
            self._gen_layers(layer_defs, None, None)

        # tidy up batch specs
        else:
            layer_defs = [
                LayerDef(rec[0], rec[1],
                         path.join(path.split(cfg_name)[0], rec[2]))
                for rec in csv.reader(cfg, skipinitialspace=True)
            ]
            cfg.close()
            self._gen_layers(layer_defs, archive, batch_member)

        # tidy up archive
        finally:
            if archive:
                archive.close()

        # compile design
        if DEBUG:
            self._debug_stdout()

        self.layout.units = (self.params['MO'] == 'IN' and 'inch' or 'mm')

        design = Design()
        design.layout = self.layout
        return design
Exemple #9
0
 def test_generating_geda_commands_for_toplevel_shapes(self):
     design = Design()
     design.shapes = [
         shape.Line((0, 0), (0, 50)),
         shape.Circle(0, 0, 300),
     ]
     design.pins = [
         components.Pin('E', (0, 0), (0, 30)),
         components.Pin('E', (0, 0), (0, 30)),
     ]
     commands = self.geda_writer.generate_body_commands(design)
     ## default pins require 6 commands, shapes require 1 command
     self.assertEquals(len(commands), 2 * 6 + 2 * 1)
 def test_generating_geda_commands_for_toplevel_shapes(self):
     design = Design()
     design.shapes = [
         shape.Line((0, 0), (0, 50)),
         shape.Circle(0, 0, 300),
     ]
     design.pins = [
         components.Pin('E', (0, 0), (0, 30)),
         components.Pin('E', (0, 0), (0, 30)),
     ]
     commands = self.geda_writer.generate_body_commands(design)
     ## default pins require 6 commands, shapes require 1 command
     self.assertEquals(len(commands), 2*6 + 2*1)
    def parse(self, infile='.'):
        """ Parse tokens from gerber files into a design. """
        is_zip = infile.endswith('.zip')
        openarchive = ZipFile if is_zip else TarFile.open
        archive = batch_member = None
        try:
            # define multiple layers from folder
            if LAYERS_CFG in infile:
                archive = None
                cfg_name = infile
                cfg = open(cfg_name, 'r')

            # define multiple layers from archivea 
            else:
                archive = openarchive(infile)
                batch = archive.namelist if is_zip else archive.getnames
                batch_member = archive.open if is_zip else archive.extractfile
                cfg_name = [n for n in batch() if LAYERS_CFG in n][0]
                cfg = batch_member(cfg_name)

        # define single layer from single gerber file
        except ReadError:
            name, ext = path.split(infile)[1].rsplit('.', 1)
            layer_defs = [LayerDef(ext.lower() == 'ger' and name or ext,
                                   'unknown', infile)]
            self._gen_layers(layer_defs, None, None)

        # tidy up batch specs
        else:
            layer_defs = [LayerDef(rec[0],
                                   rec[1],
                                   path.join(path.split(cfg_name)[0], rec[2]))
                          for rec in
                          csv.reader(cfg, skipinitialspace=True)]
            cfg.close()
            self._gen_layers(layer_defs, archive, batch_member)

        # tidy up archive
        finally:
            if archive:
                archive.close()

        # compile design
        if DEBUG:
            self._debug_stdout()

        self.layout.units = (self.params['MO'] == 'IN' and 'inch' or 'mm')

        design = Design()
        design.layout = self.layout
        return design
    def parse(self):
        '''Returns a Design built up from a schematic file that represents one
        sheet of the original schematic'''
        tree = ViewDrawBase.parse(self)
        # tree['lines'] is a [list of [list of lines]]
        tree['shape'].extend(sum(tree['lines'], []))
        ckt = Design()
        # TODO little weak here, a copy instead?
        ckt.components = self.lib

        for net in tree['net']:
            ckt.add_net(net)
        for inst in tree['inst']:
            ckt.add_component_instance(inst)
            # hold on tight, this is ugly
            for (netid, netpt, pinid) in inst.conns:
                net = [n for n in ckt.nets if n.net_id == netid][0]
                comp = ConnectedComponent(inst.instance_id, pinid)
                net.ibpts[netpt - 1].add_connected_component(comp)
            del inst.conns
        for net in ckt.nets:
            del net.ibpts

        for shape in tree['shape']:
            ckt.add_shape(shape)
            if isinstance(shape, Label):
                ann = Annotation(shape.text, shape.x, shape.y,
                                 shape._rotation, True)
                ckt.design_attributes.add_annotation(ann)

        for k, v, annot in tree['attr']:
            ckt.design_attributes.add_attribute(k, v)
            ckt.design_attributes.add_annotation(annot)

        return ckt
    def parse(self, filename):
        """ Parse a specctra file into a design """

        self.design = Design()

        with open(filename) as f:
            data = f.read()

        tree = DsnParser().parse(data)

        struct = self.walk(tree)
        self.resolution = struct.resolution
        self._convert(struct)

        return self.design
    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)
    def parse(self, inputfile):
        """ Parse a gEDA file into a design.

            Returns the design corresponding to the gEDA file.
        """
        inputfiles = []

        ## check if inputfile is in ZIP format
        if zipfile.is_zipfile(inputfile):
            self.geda_zip = zipfile.ZipFile(inputfile)
            for filename in self.geda_zip.namelist():
                if filename.endswith('.sch'):
                    inputfiles.append(filename)
        else:
            inputfiles = [inputfile]

        self.design = Design()

        ## parse frame data of first schematic to extract
        ## page size (assumes same frame for all files)
        with self._open_file_or_zip(inputfiles[0]) as stream:
            self._check_version(stream)

            for line in stream.readlines():
                if 'title' in line and line.startswith('C'):
                    obj_type, params = self._parse_command(StringIO(line))
                    assert(obj_type == 'C')

                    params['basename'], _ = os.path.splitext(
                        params['basename'],
                    )

                    log.debug("using title file: %s", params['basename'])

                    self._parse_title_frame(params)

        ## store offset values in design attributes
        self.design.design_attributes.attributes.update({
            '_geda_offset_x': str(self.offset.x),
            '_geda_offset_y': str(self.offset.y),
            '_geda_frame_width': str(self.frame_width),
            '_geda_frame_height': str(self.frame_height),
        })

        for filename in inputfiles:
            f_in = self._open_file_or_zip(filename)
            self._check_version(f_in)

            self.parse_schematic(f_in)

            basename, _ = os.path.splitext(os.path.basename(filename))
            self.design.design_attributes.metadata.set_name(basename)

            ## modify offset for next page to be shifted to the right
            self.offset.x = self.offset.x - self.frame_width

            f_in.close()

        return self.design
Exemple #16
0
    def __init__(self):
        self.design = Design()

        # This maps fritzing connector keys to (x, y) coordinates
        self.points = {}  # (index, connid) -> (x, y)

        # This maps fritzing component indices to ComponentInstances
        self.component_instances = {}  # index -> ComponentInstance

        # Map connector keys to the list of connector keys they
        # are connected to.
        self.connects = {}  # (index, connid) -> [(index, connid)]

        self.components = {}  # idref -> ComponentParser

        self.fritzing_version = None
        self.fzz_zipfile = None  # The ZipFile if we are parsing an fzz
    def test_write_header(self):
        """
        The write_header method produces the right string.
        """

        design = Design()
        design.design_attributes.metadata.updated_timestamp = 0
        writer = KiCAD()
        buf = StringIO()
        writer.write_header(buf, design)
        self.assertEqual(buf.getvalue()[:40], 'EESchema Schematic File Version 2  date ')
    def parse(self):
        '''Returns a Design built up from a schematic file that represents one
        sheet of the original schematic'''
        tree = ViewDrawBase.parse(self)
        # tree['lines'] is a [list of [list of lines]]
        tree['shape'].extend(sum(tree['lines'], []))
        ckt = Design()
        # TODO little weak here, a copy instead?
        ckt.components = self.lib
        
        for net in tree['net']:
            ckt.add_net(net)
        for inst in tree['inst']:
            ckt.add_component_instance(inst)
            # hold on tight, this is ugly
            for (netid, netpt, pinid) in inst.conns:
                net = [n for n in ckt.nets if n.net_id == netid][0]
                comp = ConnectedComponent(inst.instance_id, pinid)
                net.ibpts[netpt - 1].add_connected_component(comp)
            del inst.conns
        for net in ckt.nets:
            del net.ibpts

        # too bad designs don't have top-level shapes (yet?)
        #map(ckt.add_shape, tree['shape'])
        
        for lbl in [s for s in tree['shapes'] if isinstance(s, Label)]:
            ann = Annotation(lbl.text, lbl.x, lbl.y, lbl.rotation, True)
            ckt.design_attributes.add_annotation(ann)
        
        for k, v in tree['attr']:
            ckt.design_attributes.add_attribute(k, v)

        self.correct_y(ckt, tree['Dbounds'][0])
        return ckt
    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)

        if library_filename is None:
            library_filename = splitext(filename)[0] + '-cache.lib'
            if exists(library_filename):
                for cpt in parse_library(library_filename):
                    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 = self.parse_component_instance(f)
                    design.add_component_instance(inst)
                    if inst.library_id not in design.components.components:
                        cpt = lookup_part(inst.library_id, libs)
                        if cpt is not None:
                            design.components.add_component(cpt.name, cpt)

                line = f.readline()

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

        return design
    def parse_schematic(self, stream):
        """ Parse a gEDA schematic provided as a *stream* object into a
            design.

            Returns the design corresponding to the schematic.
        """
        # pylint: disable=R0912
        if self.design is None:
            self.design = Design()

        self.segments = set()
        self.net_points = dict()
        self.net_names = dict()

        obj_type, params = self._parse_command(stream)

        while obj_type is not None:

            objects = getattr(self, "_parse_%s" % obj_type)(stream, params)

            attributes = self._parse_environment(stream)
            self.design.design_attributes.attributes.update(attributes or {})

            self.add_objects_to_design(self.design, objects)

            obj_type, params = self._parse_command(stream)

        ## process net segments into nets & net points and add to design
        self.divide_segments()

        calculated_nets = self.calculate_nets()

        for cnet in sorted(calculated_nets, key=lambda n: n.net_id):
            self.design.add_net(cnet)

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

        # This maps fritzing connector keys to (x, y) coordinates
        self.points = {} # (index, connid) -> (x, y)

        # This maps fritzing component indices to ComponentInstances
        self.component_instances = {} # index -> ComponentInstance

        # Map connector keys to the list of connector keys they
        # are connected to.
        self.connects = {} # (index, connid) -> [(index, connid)]

        self.components = {} # idref -> ComponentParser

        self.fritzing_version = None
        self.fzz_zipfile = None # The ZipFile if we are parsing an fzz
    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)

        if library_filename is None:
            library_filename = splitext(filename)[0] + '-cache.lib'
            if exists(library_filename):
                for cpt in parse_library(library_filename):
                    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 = self.parse_component_instance(f)
                    design.add_component_instance(inst)
                    if inst.library_id not in design.components.components:
                        cpt = lookup_part(inst.library_id, libs)
                        if cpt is not None:
                            design.components.add_component(cpt.name, cpt)

                line = f.readline()

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

        return design
    def parse_schematic(self, stream):
        """ Parse a gEDA schematic provided as a *stream* object into a
            design.

            Returns the design corresponding to the schematic.
        """
        # pylint: disable=R0912
        if self.design is None:
            self.design = Design()

        self.segments = set()
        self.net_points = dict()
        self.net_names = dict()

        obj_type, params = self._parse_command(stream)

        while obj_type is not None:

            objects = getattr(self, "_parse_%s" % obj_type)(stream, params)

            attributes = self._parse_environment(stream)
            self.design.design_attributes.attributes.update(attributes or {})

            self.add_objects_to_design(self.design, objects)

            obj_type, params = self._parse_command(stream)

        ## process net segments into nets & net points and add to design
        self.divide_segments()

        calculated_nets = self.calculate_nets()

        for cnet in sorted(calculated_nets, key=lambda n : n.net_id):
            self.design.add_net(cnet)

        return self.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 DesignTests(unittest.TestCase):
    """ The tests of the core module design feature """

    def setUp(self):
        """ Setup the test case. """
        self.des = Design()

    def tearDown(self):
        """ Teardown the test case. """
        pass

    def test_create_new_design(self):
        """ Test the creation of a new empty design. """
        self.assertEqual(len(self.des.nets), 0)

    def test_empty_bounds(self):
        '''bounds() on an empty design is to include just the origin'''
        for point in self.des.bounds():
            self.assertEqual(point.x, 0)
            self.assertEqual(point.y, 0)

    def test_bounds_nets(self):
        '''Test bounds() with just the design's nets'''
        leftnet = Net('foo1')
        topnet = Net('foo2')
        rightnet = Net('foo3')
        botnet = Net('foo4')
        # limits minx=2, miny=1, maxx=7, maxy=9
        mkbounds(leftnet, 2, 3, 3, 3)
        mkbounds(topnet, 3, 1, 3, 3)
        mkbounds(rightnet, 3, 3, 7, 3)
        mkbounds(botnet, 3, 3, 3, 9)
        self.des.add_net(topnet)
        self.des.add_net(rightnet)
        self.des.add_net(leftnet)
        self.des.add_net(botnet)

        top_left, btm_right = self.des.bounds()
        self.assertEqual(top_left.x, 2)
        self.assertEqual(top_left.y, 1)
        self.assertEqual(btm_right.x, 7)
        self.assertEqual(btm_right.y, 9)

    def test_bounds_annots(self):
        '''Test bounds() with just Annotations added as design attributes'''
        left = Annotation('foo1', 3, 3, 0, True)
        top = Annotation('foo2', 3, 3, 0, True)
        right = Annotation('foo3', 3, 3, 0, True)
        bot = Annotation('foo4', 3, 3, 0, True)
        mkbounds(left, 2, 3, 3, 3)
        mkbounds(top, 3, 2, 3, 3)
        mkbounds(right, 3, 3, 5, 3)
        mkbounds(bot, 3, 3, 3, 6)
        for anno in (left, right, bot, top):
            self.des.design_attributes.add_annotation(anno)

        top_left, btm_right = self.des.bounds()
        self.assertEqual(top_left.x, 2)
        self.assertEqual(top_left.y, 2)
        self.assertEqual(btm_right.x, 5)
        self.assertEqual(btm_right.y, 6)

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

    def test_bounds_neg_coords(self):
        '''Test bounds() when the schematic is all negative coordinates'''
        net = Net('foo')
        mkbounds(net, -1, -2, -3, -4)
        self.des.add_net(net)

        top_left, btm_right = self.des.bounds()
        self.assertEqual(top_left.x, -3)
        self.assertEqual(top_left.y, -4)
        self.assertEqual(btm_right.x, -1)
        self.assertEqual(btm_right.y, -2)

    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, False))
        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)
class GEDA:
    """ The GEDA Format Parser """

    DELIMITER = ' '
    SCALE_FACTOR = 10.0  # maps 1000 MILS to 10 pixels

    OBJECT_TYPES = {
        'v': geda_commands.GEDAVersionCommand(),
        'L': geda_commands.GEDALineCommand(),
        'B': geda_commands.GEDABoxCommand(),
        'V': geda_commands.GEDACircleCommand(),
        'A': geda_commands.GEDAArcCommand(),
        'T': geda_commands.GEDATextCommand(),
        'N': geda_commands.GEDASegmentCommand(),
        'U': geda_commands.GEDABusCommand(),
        'P': geda_commands.GEDAPinCommand(),
        'C': geda_commands.GEDAComponentCommand(),
        'H': geda_commands.GEDAPathCommand(),
        ## valid types but are ignored
        'G': geda_commands.GEDAPictureCommand(),
        ## environments
        '{': geda_commands.GEDAEmbeddedEnvironmentCommand(),
        '}': [],  # attributes
        '[': geda_commands.GEDAAttributeEnvironmentCommand(),
        ']': [],  # embedded component
    }

    def __init__(self, symbol_dirs=None):
        """ Constuct a gEDA parser object. Specifying a list of symbol
            directories in *symbol_dir* will provide a symbol file
            lookup in the specified directories. The lookup will be
            generated instantly examining each directory (if it exists).

            Kwargs:
                symbol_dirs (list): List of directories containing .sym
                    files
        """
        self.offset = shape.Point(40000, 40000)
        ## Initialise frame size with largest possible size
        self.frame_width = 0
        self.frame_height = 0

        # initialise PIN counter
        self.pin_counter = itertools.count(0)
        # initialise  PATH counter
        self.path_counter = itertools.count(0)

        ## add flag to allow for auto inclusion
        if symbol_dirs is None:
            symbol_dirs = []

        symbol_dirs = symbol_dirs + \
            [os.path.join(os.path.dirname(__file__), '..', 'library', 'geda')]

        self.known_symbols = find_symbols(symbol_dirs)

        self.design = None
        self.segments = None
        self.net_points = None
        self.net_names = None
        self.geda_zip = None

    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an geda schematic """
        with open(filename, 'rU') as f:
            data = f.read()
        confidence = 0
        if data[0:2] == 'v ':
            confidence += 0.51
        if 'package=' in data:
            confidence += 0.25
        if 'footprint=' in data:
            confidence += 0.25
        if 'refdes=' in data:
            confidence += 0.25
        if 'netname=' in data:
            confidence += 0.25
        return confidence

    def set_offset(self, point):
        """ Set the offset point for the gEDA output. As OpenJSON
            positions the origin in the center of the viewport and
            gEDA usually uses (40'000, 40'000) as page origin, this
            allows for translating from one coordinate system to
            another. It expects a *point* object providing a *x* and
            *y* attribute.
        """
        ## create an offset of 5 grid squares from origin (0,0)
        self.offset.x = point.x
        self.offset.y = point.y

    def parse(self, inputfile):
        """ Parse a gEDA file into a design.

            Returns the design corresponding to the gEDA file.
        """
        inputfiles = []

        ## check if inputfile is in ZIP format
        if zipfile.is_zipfile(inputfile):
            self.geda_zip = zipfile.ZipFile(inputfile)
            for filename in self.geda_zip.namelist():
                if filename.endswith('.sch'):
                    inputfiles.append(filename)
        else:
            inputfiles = [inputfile]

        self.design = Design()

        ## parse frame data of first schematic to extract
        ## page size (assumes same frame for all files)
        with self._open_file_or_zip(inputfiles[0]) as stream:
            self._check_version(stream)

            for line in stream.readlines():
                if 'title' in line and line.startswith('C'):
                    obj_type, params = self._parse_command(StringIO(line))
                    assert(obj_type == 'C')

                    params['basename'], _ = os.path.splitext(
                        params['basename'],
                    )

                    log.debug("using title file: %s", params['basename'])

                    self._parse_title_frame(params)

        ## store offset values in design attributes
        self.design.design_attributes.attributes.update({
            '_geda_offset_x': str(self.offset.x),
            '_geda_offset_y': str(self.offset.y),
            '_geda_frame_width': str(self.frame_width),
            '_geda_frame_height': str(self.frame_height),
        })

        for filename in inputfiles:
            f_in = self._open_file_or_zip(filename)
            self._check_version(f_in)

            self.parse_schematic(f_in)

            basename, _ = os.path.splitext(os.path.basename(filename))
            self.design.design_attributes.metadata.set_name(basename)

            ## modify offset for next page to be shifted to the right
            self.offset.x = self.offset.x - self.frame_width

            f_in.close()

        return self.design

    def _parse_v(self, stream, params):
        """
        Only required to be callable when 'v' command is found.
        Returns without any processing.
        """
        return

    def _parse_G(self, stream, params):
        """
        Parse picture command 'G'. Returns without any processing but
        logs a warning.
        """
        log.warn("ignoring picture/font in gEDA file. Not supported!")
        return

    def parse_schematic(self, stream):
        """ Parse a gEDA schematic provided as a *stream* object into a
            design.

            Returns the design corresponding to the schematic.
        """
        # pylint: disable=R0912
        if self.design is None:
            self.design = Design()

        self.segments = set()
        self.net_points = dict()
        self.net_names = dict()

        obj_type, params = self._parse_command(stream)

        while obj_type is not None:

            objects = getattr(self, "_parse_%s" % obj_type)(stream, params)

            attributes = self._parse_environment(stream)
            self.design.design_attributes.attributes.update(attributes or {})

            self.add_objects_to_design(self.design, objects)

            obj_type, params = self._parse_command(stream)

        ## process net segments into nets & net points and add to design
        self.divide_segments()

        calculated_nets = self.calculate_nets()

        for cnet in sorted(calculated_nets, key=lambda n : n.net_id):
            self.design.add_net(cnet)

        return self.design

    def _parse_title_frame(self, params):
        """ Parse the frame component in *params* to extract the
            page size to be used in the design. The offset is adjusted
            according to the bottom-left position of the frame.
        """
        ## set offset based on bottom-left corner of frame
        self.offset.x = params['x']
        self.offset.y = params['y']

        filename = self.known_symbols.get(params['basename'])
        if not filename or not os.path.exists(filename):
            log.warn("could not find title symbol '%s'" % params['basename'])

            self.frame_width = 46800
            self.frame_height = 34000
            return

        ## store title component name in design
        self.design.design_attributes.add_attribute(
            '_geda_titleframe', params['basename'],
        )

        with open(filename, 'rU') as stream:
            obj_type, params = self._parse_command(stream)

            while obj_type is not None:

                if obj_type == 'B':
                    if params['width'] > self.frame_width:
                        self.frame_width = params['width']

                    if params['height'] > self.frame_height:
                        self.frame_height = params['height']

                ## skip commands covering multiple lines
                elif obj_type in ['T', 'H']:
                    for _ in range(params['num_lines']):
                        stream.readline()

                obj_type, params = self._parse_command(stream)

            ## set width to estimated max value when no box was found
            if self.frame_width == 0:
                self.frame_width = 46800

            ## set height to estimated max value when no box was found
            if self.frame_height == 0:
                self.frame_height = 34000

    def _create_ripper_segment(self, params):
        """ Creates a new segement from the busripper provided
            in gEDA. The busripper is a graphical feature that
            provides a nicer look for a part of a net. The bus
            rippers are turned into net segments according to the
            length and orientation in *params*.

            Returns a tuple of two NetPoint objects for the segment.
        """
        x, y = params['x'], params['y']
        angle, mirror = params['angle'], params['mirror']

        if mirror:
            angle = (angle + 90) % 360

        x, y = self.conv_coords(x, y)
        pt_a = self.get_netpoint(x, y)

        ripper_size = self.to_px(200)

        ## create second point for busripper segment on bus
        if angle == 0:
            pt_b = self.get_netpoint(pt_a.x+ripper_size, pt_a.y+ripper_size)
        elif angle == 90:
            pt_b = self.get_netpoint(pt_a.x-ripper_size, pt_a.y+ripper_size)
        elif angle == 180:
            pt_b = self.get_netpoint(pt_a.x-ripper_size, pt_a.y-ripper_size)
        elif angle == 270:
            pt_b = self.get_netpoint(pt_a.x+ripper_size, pt_a.y-ripper_size)
        else:
            raise GEDAError(
                "invalid angle in component '%s'" % params['basename']
            )

        return pt_a, pt_b

    def _parse_component(self, stream, params):
        """ Creates a component instance according to the component *params*.
            If the component is not known in the library, a the component
            will be created according to its description in the embedded
            environment ``[]`` or a symbol file. The component is added
            to the library automatically if necessary.
            An instance of this component will be created and added to
            the design.
            A GEDAError is raised when either the component file
            is invalid or the referenced symbol file cannot be found
            in the known directories.

            Returns a tuple of Component and ComponentInstance objects.
        """
        basename, _ = os.path.splitext(params['basename'])

        component_name = basename
        if params.get('mirror'):
            component_name += '_MIRRORED'

        if component_name in self.design.components.components:
            component = self.design.components.components[component_name]

            ## skipping embedded data might be required
            self.skip_embedded_section(stream)

        else:
            ##check if sym file is embedded or referenced
            if basename.startswith('EMBEDDED'):
                ## embedded only has to be processed when NOT in symbol lookup
                if basename not in self.known_symbols:
                    component = self.parse_component_data(stream, params)
            else:
                if basename not in self.known_symbols:
                    log.warn("referenced symbol file '%s' unknown" % basename)
                    ## create a unknown symbol reference
                    component = self.parse_component_data(
                        StringIO(UNKNOWN_COMPONENT % basename),
                        params
                    )
                    ## parse optional attached environment before continuing
                    self._parse_environment(stream)
                    return None, None

                ## requires parsing of referenced symbol file
                with open(self.known_symbols[basename], "rU") as f_in:
                    self._check_version(f_in)
                    component = self.parse_component_data(f_in, params)

            self.design.add_component(component_name, component)

        ## get all attributes assigned to component instance
        attributes = self._parse_environment(stream)

        ## refdes attribute is name of component (mandatory as of gEDA doc)
        ## examples if gaf repo have components without refdes, use part of
        ## basename
        if attributes is not None:
            instance = ComponentInstance(
                attributes.get('_refdes', component.name),
                component.name, 0
            )
            for key, value in attributes.items():
                instance.add_attribute(key, value)

        else:
            instance = ComponentInstance(
                component.name, component.name, 0
            )

        ## generate a component instance using attributes
        self.design.add_component_instance(instance)

        symbol = SymbolAttribute(
            self.x_to_px(params['x']),
            self.y_to_px(params['y']),
            self.conv_angle(params['angle'],
            False)
        )
        instance.add_symbol_attribute(symbol)

        ## add annotation for special attributes
        for idx, attribute_key in enumerate(['_refdes', 'device']):
            if attribute_key in component.attributes \
               or attribute_key in instance.attributes:

                symbol.add_annotation(
                    Annotation(
                        '{{%s}}' % attribute_key,
                        0, 0+idx*10, 0.0, 'true'
                    )
                )

        return component, instance

    def _check_version(self, stream):
        """ Check next line in *stream* for gEDA version data
            starting with ``v``. Raises ``GEDAError`` when no version
            data can be found.
        """
        typ, _ = self._parse_command(stream)
        if typ != 'v':
            raise GEDAError(
                "cannot convert file, not in gEDA format"
            )
        return True

    def _is_mirrored_command(self, params):
        return bool(params.get('mirror', False))

    def parse_component_data(self, stream, params):
        """ Creates a component from the component *params* and the
            following commands in the stream. If the component data
            is embedded in the schematic file, all coordinates will
            be translated into the origin first.
            Only a single symbol/body is created for each component
            since gEDA symbols contain exactly one description.

            Returns the newly created Component object.
        """
        # pylint: disable=R0912
        basename = os.path.splitext(params['basename'])[0]

        saved_offset = self.offset
        self.offset = shape.Point(0, 0)

        ## retrieve if component is mirrored around Y-axis
        mirror = self._is_mirrored_command(params)
        if mirror:
            basename += '_MIRRORED'

        move_to = None
        if basename.startswith('EMBEDDED'):
            move_to = (params['x'], params['y'])

        ## grab next line (should be '['
        typ, params = self._parse_command(stream, move_to)

        if typ == '[':
            typ, params = self._parse_command(stream, move_to)

        component = components.Component(basename)
        symbol = components.Symbol()
        component.add_symbol(symbol)
        body = components.Body()
        symbol.add_body(body)

        ##NOTE: adding this attribute to make parsing UPV data easier
        ## when using re-exported UPV.
        component.add_attribute('_geda_imported', 'true')
        self.pin_counter = itertools.count(0)

        while typ is not None:

            params['mirror'] = mirror
            objects = getattr(self, "_parse_%s" % typ)(stream, params)

            attributes = self._parse_environment(stream)
            component.attributes.update(attributes or {})

            self.add_objects_to_component(component, objects)

            typ, params = self._parse_command(stream, move_to)

        self.offset = saved_offset

        return component

    def divide_segments(self):
        """ Checks all net segments for intersecting points of
            all other net segments. If an intersection is detected
            the net segment is divided into two segments with the
            intersecting point. This method has been adapted from
            a similar method in the kiCAD parser.
        """
        ## check if segments need to be divided
        add_segs = set()
        rem_segs = set()
        for segment in self.segments:
            for point in self.net_points.values():
                if self.intersects_segment(segment, point):
                    pt_a, pt_b = segment
                    rem_segs.add(segment)
                    add_segs.add((pt_a, point))
                    add_segs.add((point, pt_b))

        self.segments -= rem_segs
        self.segments |= add_segs

    def skip_embedded_section(self, stream):
        """ Reads the *stream* line by line until the end of an
            embedded section (``]``) is found. This method is used
            to skip over embedded sections of already known
            components.
        """
        pos = stream.tell()
        typ = stream.readline().split(self.DELIMITER, 1)[0].strip()

        ## return with stream reset to previous position if not
        ## an embedded section
        if typ != '[':
            stream.seek(pos)
            return

        while typ != ']':
            typ = stream.readline().split(self.DELIMITER, 1)[0].strip()

    def get_netpoint(self, x, y):
        """ Creates a new NetPoint at coordinates *x*,*y* and stores
            it in the net point lookup table. If a NetPoint does already
            exist, the existing point is returned.
            Returns a NetPoint object at coordinates *x*,*y*
        """
        if (x, y) not in self.net_points:
            self.net_points[(x, y)] = net.NetPoint('%da%d' % (x, y), x, y)
        return self.net_points[(x, y)]

    @staticmethod
    def intersects_segment(segment, pt_c):
        """ Checks if point *pt_c* lays on the *segment*. This code is
            adapted from the kiCAD parser.
            Returns True if *pt_c* is on *segment*, False otherwise.
        """
        pt_a, pt_b = segment

        #check vertical segment
        if pt_a.x == pt_b.x == pt_c.x:
            if min(pt_a.y, pt_b.y) < pt_c.y < max(pt_a.y, pt_b.y):
                return True
        #check vertical segment
        elif pt_a.y == pt_b.y == pt_c.y:
            if min(pt_a.x, pt_b.x) < pt_c.x < max(pt_a.x, pt_b.x):
                return True
        #check diagonal segment
        elif (pt_c.x-pt_a.x)*(pt_b.y-pt_a.y) \
              == (pt_b.x-pt_a.x)*(pt_c.y-pt_a.y):
            if min(pt_a.x, pt_b.x) < pt_c.x < max(pt_a.x, pt_b.x):
                return True
        ## point C not on segment
        return False

    def _parse_environment(self, stream):
        """ Checks if attribute environment starts in the next line
            (marked by '{'). Environment only contains text elements
            interpreted as text.
            Returns a dictionary of attributes.
        """
        current_pos = stream.tell()
        typ, params = self._parse_command(stream)

        #go back to previous position when no environment in stream
        if typ != '{':
            stream.seek(current_pos)
            return None

        typ, params = self._parse_command(stream)

        attributes = {}
        while typ is not None:
            if typ == 'T':
                geda_text = self._parse_T(stream, params)

                if geda_text.is_attribute():
                    attributes[geda_text.attribute] = geda_text.content
                else:
                    log.warn("normal text in environemnt does not comply "
                             "with GEDA format specification: %s", geda_text.content)

            typ, params = self._parse_command(stream)

        return attributes

    def calculate_nets(self):
        """ Calculate connected nets from previously stored segments
            and netpoints. The code has been adapted from the kiCAD
            parser since the definition of segments in the schematic
            file are similar. The segments are checked against
            existing nets and added when they touch it. For this
            to work, it is required that intersecting segments are
            divided prior to this method.

            Returns a list of valid nets and its net points.
        """
        nets = []

        # Iterate over the segments, removing segments when added to a net
        while self.segments:
            seg = self.segments.pop() # pick a point

            net_name = ''
            pt_a, pt_b = seg
            if pt_a.point_id in self.net_names:
                net_name = self.net_names[pt_a.point_id]
            elif pt_b.point_id in self.net_names:
                net_name = self.net_names[pt_b.point_id]

            new_net = net.Net(net_name)
            new_net.connect(seg)
            found = True

            if net_name:
                new_net.attributes['_name'] = net_name

            while found:
                found = set()

                for seg in self.segments: # iterate over segments
                    if new_net.connected(seg): # segment touching the net
                        new_net.connect(seg) # add the segment
                        found.add(seg)

                for seg in found:
                    self.segments.remove(seg)

            nets.append(new_net)

        # check if names are available for calculated nets
        for net_obj in nets:
            for point_id in net_obj.points:
                ## check for stored net names based on pointIDs
                if point_id in self.net_names:
                    net_obj.net_id = self.net_names[point_id]
                    net_obj.attributes['_name'] = self.net_names[point_id]

            if '_name' in net_obj.attributes:
                annotation = Annotation(
                    "{{_name}}", ## annotation referencing attribute '_name'
                    0, 0,
                    self.conv_angle(0.0),
                    self.conv_bool(1),
                )
                net_obj.add_annotation(annotation)

        for net_obj in nets:
            if not net_obj.net_id:
                net_obj.net_id = min(net_obj.points)

        return nets

    def _open_file_or_zip(self, filename, mode='rU'):
        """
        Open the file with *filename* and return a file
        handle for it. If the current file is a ZIP file
        the filename will be treated as compressed file in
        this ZIP file.
        """
        if self.geda_zip is not None:
            temp_dir = tempfile.mkdtemp()
            self.geda_zip.extract(filename, temp_dir)
            filename = os.path.join(temp_dir, filename)

        return open(filename, mode)

    def add_text_to_component(self, component, geda_text):
        """
        Add the content of a ``GEDAText`` instance to the
        component. If *geda_text* contains ``refdes``, ``prefix``
        or ``suffix`` attributes it will be stored as special
        attribute in the component. *geda_text* that is not an
        attribute will be added as ``Label`` to the components
        body.
        """
        if geda_text.is_text():
            component.symbols[0].bodies[0].add_shape(geda_text.as_label())

        elif geda_text.attribute == '_refdes' \
             and '?' in geda_text.content:

            prefix, suffix = geda_text.content.split('?')
            component.add_attribute('_prefix', prefix)
            component.add_attribute('_suffix', suffix)
        else:
            component.add_attribute(
                geda_text.attribute,
                geda_text.content
            )

    def add_objects_to_component(self, component, objs):
        """
        Add a GEDA object to the component. Valid
        objects are subclasses of ``Shape``, ``Pin`` or
        ``GEDAText``. *objs* is expected to be an iterable
        and will be added to the correct component properties
        according to their type.
        """
        if not objs:
            return

        try:
            iter(objs)
        except TypeError:
            objs = [objs]

        for obj in objs:
            obj_cls = obj.__class__
            if issubclass(obj_cls, shape.Shape):
                component.symbols[0].bodies[0].add_shape(obj)
            elif issubclass(obj_cls, components.Pin):
                component.symbols[0].bodies[0].add_pin(obj)
            elif issubclass(obj_cls, GEDAText):
                self.add_text_to_component(component, obj)

    def add_text_to_design(self, design, geda_text):
        """
        Add the content of a ``GEDAText`` instance to the
        design. If *geda_text* contains ``use_license`` it will
        be added to the design's metadata ``license`` other
        attributes are added to ``design_attributes``.
        *geda_text* that is not an attribute will be added as
        ``Label`` to the components body.
        """
        if geda_text.is_text():
            design.add_shape(geda_text.as_label())
        elif geda_text.attribute == 'use_license':
            metadata = design.design_attributes.metadata
            metadata.license = geda_text.content
        else:
            design.design_attributes.add_attribute(
                geda_text.attribute,
                geda_text.content,
            )

    def add_objects_to_design(self, design, objs):
        """
        Add a GEDA object to the design. Valid
        objects are subclasses of ``Shape``, ``Pin`` or
        ``GEDAText``. *objs* is expected to be an iterable
        and will be added to the correct component properties
        according to their type.
        """
        if not objs:
            return

        try:
            iter(objs)
        except TypeError:
            objs = [objs]

        for obj in objs:
            obj_cls = obj.__class__
            if issubclass(obj_cls, shape.Shape):
                design.add_shape(obj)
            elif issubclass(obj_cls, components.Pin):
                design.add_pin(obj)
            elif issubclass(obj_cls, GEDAText):
                self.add_text_to_design(design, obj)

    def _parse_U(self, stream, params):
        """ Processing a bus instance with start end end coordinates
            at (x1, y1) and (x2, y2). *color* is ignored. *ripperdir*
            defines the direction in which the bus rippers are oriented
            relative to the direction of the bus.
        """
        x1, x2 = params['x1'], params['x2']
        y1, y2 = params['y1'], params['y2']

        ## ignore bus when length is zero
        if x1 == x2 and y1 == y2:
            return

        pta_x, pta_y = self.conv_coords(x1, y1)
        ptb_x, ptb_y = self.conv_coords(x2, y2)

        self.segments.add((
            self.get_netpoint(pta_x, pta_y),
            self.get_netpoint(ptb_x, ptb_y)
        ))

    def _parse_L(self, stream, params):
        """ Creates a Line object from the parameters in *params*. All
            style related parameters are ignored.
            Returns a Line object.
        """
        line_x1 = params['x1']
        line_x2 = params['x2']

        if self._is_mirrored_command(params):
            line_x1 = 0 - params['x1']
            line_x2 = 0 - params['x2']

        line = shape.Line(
            self.conv_coords(line_x1, params['y1']),
            self.conv_coords(line_x2, params['y2']),
        )
        ## store style data for line in 'style' dict
        self._save_parameters_to_object(line, params)
        return line

    def _parse_B(self, stream, params):
        """ Creates rectangle from gEDA box with origin in bottom left
            corner. All style related values are ignored.
            Returns a Rectangle object.
        """
        rect_x = params['x']
        if self._is_mirrored_command(params):
            rect_x = 0-(rect_x+params['width'])

        rect = shape.Rectangle(
            self.x_to_px(rect_x),
            self.y_to_px(params['y']+params['height']),
            self.to_px(params['width']),
            self.to_px(params['height'])
        )
        ## store style data for rect in 'style' dict
        self._save_parameters_to_object(rect, params)
        return rect

    def _parse_V(self, stream, params):
        """ Creates a Circle object from the gEDA parameters in *params. All
            style related parameters are ignored.
            Returns a Circle object.
        """
        vertex_x = params['x']
        if self._is_mirrored_command(params):
            vertex_x = 0-vertex_x

        circle = shape.Circle(
            self.x_to_px(vertex_x),
            self.y_to_px(params['y']),
            self.to_px(params['radius']),
        )
        ## store style data for arc in 'style' dict
        self._save_parameters_to_object(circle, params)
        return circle

    def _parse_A(self, stream, params):
        """ Creates an Arc object from the parameter in *params*. All
            style related parameters are ignored.
            Returns Arc object.
        """
        arc_x = params['x']
        start_angle = params['startangle']
        sweep_angle = params['sweepangle']

        if self._is_mirrored_command(params):
            arc_x = 0 - arc_x

            start_angle = start_angle + sweep_angle
            if start_angle <= 180:
                start_angle = 180 - start_angle
            else:
                start_angle = (360 - start_angle) + 180

        arc = shape.Arc(
            self.x_to_px(arc_x),
            self.y_to_px(params['y']),
            self.conv_angle(start_angle),
            self.conv_angle(start_angle+sweep_angle),
            self.to_px(params['radius']),
        )
        ## store style data for arc in 'style' dict
        self._save_parameters_to_object(arc, params)
        return arc

    def _parse_T(self, stream, params):
        """ Parses text element and determins if text is a text object
            or an attribute.
            Returns a tuple (key, value). If text is an annotation key is None.
        """
        params['x'] = self.x_to_px(params['x'])
        params['y'] = self.y_to_px(params['y'])
        params['angle'] = self.conv_angle(params['angle'])

        geda_text = GEDAText.from_command(stream, params)

        ## text can have environemnt attached: parse & ignore
        self._parse_environment(stream)
        return geda_text

    def _parse_N(self, stream, params):
        """ Creates a segment from the command *params* and
            stores it in the global segment list for further
            processing in :py:method:divide_segments and
            :py:method:calculate_nets. It also extracts the
            net name from the attribute environment if
            present.
        """
        ## store segement for processing later
        x1, y1 = self.conv_coords(params['x1'], params['y1'])
        x2, y2 = self.conv_coords(params['x2'], params['y2'])

        ## store segment points in global point list
        pt_a = self.get_netpoint(x1, y1)
        pt_b = self.get_netpoint(x2, y2)

        ## add segment to global list for later processing
        self.segments.add((pt_a, pt_b))

        attributes = self._parse_environment(stream)
        if attributes is not None:
            ## create net with name in attributes
            if '_netname' in attributes:
                net_name = attributes['_netname']
                if net_name not in self.net_names.values():
                    self.net_names[pt_a.point_id] = net_name

    def _parse_P(self, stream, params, pinnumber=0):
        """ Creates a Pin object from the parameters in *param* and
            text attributes provided in the following environment. The
            environment is enclosed in ``{}`` and is required. If no
            attributes can be extracted form *stream* an GEDAError
            is raised.
            The *pin_id* is retrieved from the 'pinnumber' attribute and
            all other attributes are ignored. The conneted end of the
            pin is taken from the 'whichend' parameter as defined in
            the gEDA documentation.

            Returns a Pin object.
        """
        ## pin requires an attribute enviroment, so parse it first
        attributes = self._parse_environment(stream)

        if attributes is None:
            log.warn('mandatory pin attributes missing')
            attributes = {
                '_pinnumber': pinnumber,
            }

        if '_pinnumber' not in attributes:
            attributes['_pinnumber'] = pinnumber
            log.warn("mandatory attribute '_pinnumber' not assigned to pin")

        whichend = params['whichend']

        pin_x1, pin_x2 = params['x1'], params['x2']
        if self._is_mirrored_command(params):
            pin_x1 = 0-pin_x1
            pin_x2 = 0-pin_x2

        ## determine wich end of the pin is the connected end
        ## 0: first point is connector
        ## 1: second point is connector
        if whichend == 0:
            connect_end = self.conv_coords(pin_x1, params['y1'])
            null_end = self.conv_coords(pin_x2, params['y2'])
        else:
            null_end = self.conv_coords(pin_x1, params['y1'])
            connect_end = self.conv_coords(pin_x2, params['y2'])

        label = None
        if '_pinlabel' in attributes:
            label = shape.Label(
                connect_end[0],
                connect_end[1],
                attributes.get('_pinlabel'),
                'left',
                0.0
            )

        pin = components.Pin(
            attributes['_pinnumber'], #pin number
            null_end,
            connect_end,
            label=label
        )
        ## store style parameters in shape's style dict
        self._save_parameters_to_object(pin, params)
        return pin

    def _parse_C(self, stream, params):
        """
        Parse component command 'C'. *stream* is the file stream
        pointing to the line after the component command. *params*
        are the parsed parameters from the component command.
        The method checks if component is a title and ignores it
        if that is the case due to previous processing. If the
        component is a busripper, it is converted into a net
        segment. Otherwise, the component is parsed as a regular
        component and added to the library and design.
        """
        ## ignore title since it only defines the blueprint frame
        if params['basename'].startswith('title'):
            self._parse_environment(stream)

        ## busripper are virtual components that need separate
        ## processing
        elif 'busripper' in params['basename']:
            self.segments.add(self._create_ripper_segment(params))

            ## make sure following environments are ignored
            self.skip_embedded_section(stream)
            self._parse_environment(stream)
        else:
            self._parse_component(stream, params)

    def _parse_H(self, stream, params):
        """ Parses a SVG-like path provided path into a list
            of simple shapes. The gEDA formats allows only line
            and curve segments with absolute coordinates. Hence,
            shapes are either Line or BezierCurve objects.
            The method processes the stream data according to
            the number of lines in *params*.
            Returns a list of Line and BezierCurve shapes.
        """
        params['extra_id'] = self.path_counter.next()
        num_lines = params['num_lines']
        mirrored = self._is_mirrored_command(params)
        command = stream.readline().strip().split(self.DELIMITER)

        if command[0] != 'M':
            raise GEDAError('found invalid path in gEDA file')

        def get_coords(string, mirrored):
            """ Get coordinates from string with comma-sparated notation."""
            x, y = [int(value) for value in string.strip().split(',')]

            if mirrored:
                x = -x

            return (self.x_to_px(x), self.y_to_px(y))

        shapes = []
        current_pos = initial_pos = (get_coords(command[1], mirrored))

        ## loop over the remaining lines of commands (after 'M')
        for _ in range(num_lines-1):
            command = stream.readline().strip().split(self.DELIMITER)

            ## draw line from current to given position
            if command[0] == 'L':
                assert(len(command) == 2)
                end_pos = get_coords(command[1], mirrored)

                shape_ = shape.Line(current_pos, end_pos)
                current_pos = end_pos

            ## draw curve from current to given position
            elif command[0] == 'C':
                assert(len(command) == 4)
                control1 = get_coords(command[1], mirrored)
                control2 = get_coords(command[2], mirrored)
                end_pos = get_coords(command[3], mirrored)

                shape_ = shape.BezierCurve(
                    control1,
                    control2,
                    current_pos,
                    end_pos
                )
                current_pos = end_pos

            ## end of sub-path, straight line from current to initial position
            elif command[0] in ['z', 'Z']:
                shape_ = shape.Line(current_pos, initial_pos)

            else:
                raise GEDAError(
                    "invalid command type in path '%s'" % command[0]
                )

            ## store style parameters in shape's style dict
            self._save_parameters_to_object(shape_, params)
            shapes.append(shape_)

        return shapes

    def _save_parameters_to_object(self, obj, params):
        """
        Save all ``style`` and ``extra`` parameters to the
        objects ``styles`` dictionary. If *obj* does not have
        a ``styles`` property, a ``GEDAError`` is raised.
        """
        parameter_types = [
            geda_commands.GEDAStyleParameter.TYPE,
            geda_commands.GEDAExtraParameter.TYPE,
        ]

        try:
            for key, value in params.items():
                if key.split('_')[0] in parameter_types:
                    obj.styles[key] = value
        except AttributeError:
            log.exception(
                "tried saving style data to '%s' without styles dict.",
                obj.__class__.__name__
            )

    def _parse_command(self, stream, move_to=None):
        """ Parse the next command in *stream*. The object type is check
            for validity and its parameters are parsed and converted to
            the expected typs in the parsers lookup table. If *move_to*
            is provided it is used to translate all coordinates into by
            the given coordinate.
            Returns a tuple (*object type*, *parameters*) where *parameters*
                is a dictionary of paramter name and value.

            Raises GEDAError when object type is not known.
        """
        line = stream.readline()

        while line.startswith('#') or line == '\n':
            line = stream.readline()

        command_data = line.strip().split(self.DELIMITER)

        if len(command_data[0]) == 0 or command_data[0] in [']', '}']:
            return None, []

        object_type, command_data = command_data[0].strip(), command_data[1:]

        if object_type not in self.OBJECT_TYPES:
            raise GEDAError("unknown type '%s' in file" % object_type)

        params = {}
        geda_command = self.OBJECT_TYPES[object_type]
        for idx, parameter in enumerate(geda_command.parameters()):
            if idx >= len(command_data):
                ## prevent text commands of version 1 from breaking
                params[parameter.name] = parameter.default
            else:
                datatype = parameter.datatype
                params[parameter.name] = datatype(command_data[idx])

        assert(len(params) == len(geda_command.parameters()))

        if move_to is not None:
            ## element in EMBEDDED component need to be moved
            ## to origin (0, 0) from component origin
            if object_type in ['T', 'B', 'C', 'A']:
                params['x'] = params['x'] - move_to[0]
                params['y'] = params['y'] - move_to[1]
            elif object_type in ['L', 'P']:
                params['x1'] = params['x1'] - move_to[0]
                params['y1'] = params['y1'] - move_to[1]
                params['x2'] = params['x2'] - move_to[0]
                params['y2'] = params['y2'] - move_to[1]

        return object_type, params

    @classmethod
    def to_px(cls, value):
        """ Converts value in MILS to pixels using the parsers
            scale factor.
            Returns an integer value converted to pixels.
        """
        return int(value / cls.SCALE_FACTOR)

    def x_to_px(self, x_mils):
        """ Convert *px* from MILS to pixels using the scale
            factor and translating it allong the X-axis in
            offset.

            Returns translated and converted X coordinate.
        """
        return int(float(x_mils - self.offset.x) / self.SCALE_FACTOR)

    def y_to_px(self, y_mils):
        """ Convert *py* from MILS to pixels using the scale
            factor and translating it allong the Y-axis in
            offset.

            Returns translated and converted Y coordinate.
        """
        return int(float(y_mils - self.offset.y) / self.SCALE_FACTOR)

    def conv_coords(self, orig_x, orig_y):
        """ Converts coordinats *orig_x* and *orig_y* from MILS
            to pixel units based on scale factor. The converted
            coordinates are in multiples of 10px.
        """
        orig_x, orig_y = int(orig_x), int(orig_y)
        return (
            self.x_to_px(orig_x),
            self.y_to_px(orig_y)
        )

    @staticmethod
    def conv_bool(value):
        """ Converts *value* into string representing boolean
            'true' or 'false'. *value* can be of any numeric or
            boolean type.
        """
        if value in ['true', 'false']:
            return value
        return str(bool(int(value)) is True).lower()

    @staticmethod
    def conv_angle(angle):
        """ Converts *angle* (in degrees) to pi radians. gEDA
            sets degree angles counter-clockwise whereas upverter
            uses pi radians clockwise. Therefore the direction of
            *angle* is therefore adjusted first.
        """
        angle = angle % 360.0
        if angle > 0:
            angle = abs(360 - angle)
        return round(angle/180.0, 1)
Exemple #27
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
     """

    MULT = 90 / 25.4 # mm to 90 dpi

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

        # map components to gate names to symbol indices
        self.cpt2gate2symbol_index = 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)

        return self.design


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

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


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

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

        for gate in get_subattr(deviceset, 'gates.gate'):
            symbol = Symbol()
            cpt.add_symbol(symbol)
            self.cpt2gate2symbol_index[cpt][gate.name] = len(cpt.symbols) - 1
            symbol.add_body(self.make_body_from_symbol(lib, gate.symbol))

        return cpt


    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 make_component_instances(self, root):
        """ Construct openjson component instances for an eagle model. """

        parts = dict((p.name, p) for p
                     in get_subattr(root, 'drawing.schematic.parts.part', ()))

        for sheet in get_subattr(root, 'drawing.schematic.sheets.sheet', ()):
            for instance in get_subattr(sheet, 'instances.instance', ()):
                inst = self.make_component_instance(parts, instance)
                self.design.add_component_instance(inst)


    def make_component_instance(self, parts, instance):
        """ Construct an openjson component instance for an eagle instance. """

        part = parts[instance.part]

        library_id = part.library + ':' + part.deviceset

        # TODO pick correct symbol index
        inst = ComponentInstance(instance.part, library_id, 0)

        # TODO handle mirror
        # TODO handle smashed?
        attr = SymbolAttribute(self.make_length(instance.x),
                               self.make_length(instance.y),
                               self.make_angle(instance.rot or '0'))

        inst.add_symbol_attribute(attr)

        return inst


    def make_length(self, value):
        """ Make an openjson length measurement from an eagle length. """

        return int(round(float(value) * self.MULT))


    def make_angle(self, value):
        """ Make an openjson angle measurement from an eagle angle. """

        return float(value.lstrip('MSR')) / 180
class Fritzing(object):
    """ The Fritzing Format Parser

    Connection points in a fritzing file are identified by a 'module
    index' which references a component instance or a wire, and a
    'connector id' which references a specific pin. Together the
    (index, connid) tuple uniquely identifies a connection point.
    """

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

        # This maps fritzing connector keys to (x, y) coordinates
        self.connkey2xy = {} # (index, connid) -> (x, y)

        # This maps fritzing component indices to ComponentInstances
        self.component_instances = {} # index -> ComponentInstance

        # Map connector keys to the list of connector keys they
        # are connected to.
        self.connects = {} # (index, connid) -> [(index, connid)]

        self.components = {} # idref -> ComponentParser

        self.fritzing_version = None
        self.fzz_zipfile = None # The ZipFile if we are parsing an fzz


    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an fritzing file """
        with open(filename, 'r') as f:
            data = f.read(4096)
        confidence = 0
        if 'fritzingVersion' in data:
            confidence += 0.9
        elif filename.endswith('.fzz'):
            confidence += 0.9
        if confidence == 0 and zipfile.is_zipfile(filename):
            zip_file = zipfile.ZipFile(filename)
            for name in zip_file.namelist():
                if name.endswith('.fz'):
                    confidence += 0.9
                    break
            zip_file.close()
        return confidence


    def parse(self, filename):
        """ Parse a Fritzing file into a design """

        tree = self.make_tree(filename)

        self.fritzing_version = tree.getroot().get('fritzingVersion', '0')

        for element in tree.findall('instances/instance'):
            self.parse_instance(element)

        for idref, cpt_parser in self.components.iteritems():
            self.design.add_component(idref, cpt_parser.component)

        for cptinst in self.component_instances.itervalues():
            self.design.add_component_instance(cptinst)

        for net in self.build_nets():
            self.design.add_net(net)

        return self.design


    def make_tree(self, filename):
        """
        Return an ElementTree for the given file name.
        """

        if zipfile.is_zipfile(filename):
            self.fzz_zipfile = zipfile.ZipFile(filename)
            fz_name = [name for name in self.fzz_zipfile.namelist()
                       if name.endswith('.fz')][0]
            fz_file = self.fzz_zipfile.open(fz_name)
        else:
            fz_file = filename

        return ElementTree(file=fz_file)

    def parse_instance(self, instance):
        """ Parse a Fritzing instance block """

        if instance.get('moduleIdRef') == 'WireModuleID':
            self.parse_wire(instance)
        else:
            self.parse_component_instance(instance)


    def parse_wire(self, inst):
        """ Parse a Fritzing wire instance """

        view = inst.find('views/schematicView')

        if view is None:
            return

        index = inst.get('modelIndex')
        geom = view.find('geometry')

        origin_x, origin_y = get_x(geom), get_y(geom)

        conn_keys = []

        for connects in view.findall('connectors/connector/connects/connect'):
            if connects.get('layer') == 'breadboardbreadboard':
                return

        for i, connector in enumerate(view.findall('connectors/connector'), 1):
            cid = connector.get('connectorId')
            self.connkey2xy[index, cid] = (origin_x + get_x(geom, 'x%d' % i),
                                           origin_y + get_y(geom, 'y%d' % i))

            conn_keys.append((index, cid))

            self.connects[index, cid] = \
                [(c.get('modelIndex'), c.get('connectorId'))
                 for c in connector.findall('connects/connect')]

        # connect wire ends to each other
        if len(conn_keys) >= 2:
            self.connects[conn_keys[0]].append(conn_keys[1])
            self.connects[conn_keys[1]].append(conn_keys[0])


    def ensure_component(self, inst):
        """ If we have not already done so, create the Component the
        given Fritzing instance is an instance of. Return the
        Component, or None if we cannot load it"""

        idref = inst.get('moduleIdRef')

        if idref in self.components:
            return self.components[idref]

        fzp_path = inst.get('path')
        if not fzp_path:
            return None

        if exists(fzp_path):
            fzp_file = fzp_path
        else:
            fzp_file = self.lookup_fzz_file(fzp_path, 'part')

        if not fzp_file:
            fzp_file = lookup_part(fzp_path, self.fritzing_version)
            if fzp_file is not None:
                fzp_path = fzp_file

        if not fzp_file:
            return None

        parser = ComponentParser(idref)
        parser.parse_fzp(fzp_file)

        if parser.image is not None:
            svg_file = self.lookup_fzz_file(parser.image, 'svg.schematic')

            if svg_file is None:
                fzp_dir = dirname(fzp_path)
                parts_dir = dirname(fzp_dir)
                svg_path = join(parts_dir, 'svg', basename(fzp_dir),
                                parser.image)

                if exists(svg_path):
                    svg_file = svg_path

            if svg_file is not None:
                parser.parse_svg(svg_file)

        self.components[idref] = parser

        return parser


    def lookup_fzz_file(self, path, prefix):
        """ Find a file in our fzz archive, if any """

        if not self.fzz_zipfile:
            return None

        fzz_name = prefix + '.' + basename(path)

        try:
            self.fzz_zipfile.getinfo(fzz_name)
        except KeyError:
            return None
        else:
            return self.fzz_zipfile.open(fzz_name)


    def parse_component_instance(self, inst):
        """ Parse a Fritzing non-wire instance into a ComponentInstance """

        view = inst.find('views/schematicView')

        if view is None:
            return

        if view.get('layer') == 'breadboardbreadboard':
            return

        cpt = self.ensure_component(inst)

        if cpt is None:
            return

        index = inst.get('modelIndex')
        idref = inst.get('moduleIdRef')
        title = inst.find('title').text
        geom = view.find('geometry')
        xform = geom.find('transform')

        x, y = float(geom.get('x', 0)), float(geom.get('y', 0))

        if xform is None:
            rotation = 0.0
        else:
            matrix = tuple(int(float(xform.get(key, 0)))
                           for key in ('m11', 'm12', 'm21', 'm22'))
            x, y = rotate_component(cpt, matrix, x, y)
            rotation = MATRIX2ROTATION.get(matrix, 0.0)

        compinst = ComponentInstance(title, cpt, idref, 0)

        compinst.add_symbol_attribute(
            SymbolAttribute(make_x(x), make_y(y), rotation, False))

        self.component_instances[index] = compinst


    def build_nets(self):
        """ Build the nets from the connects, points, and instances """

        xy2point = {} # x, y -> NetPoint

        def get_point(connkey):
            """ Return a new or existing NetPoint for an (x,y) coordinate """
            x, y = self.connkey2xy[connkey]
            if (x, y) not in xy2point:
                xy2point[x, y] = NetPoint('%da%d' % (x, y), x, y)
            return xy2point[x, y]

        # connector key -> NetPoint
        connkey2point = dict((ck, get_point(ck)) for ck in self.connkey2xy)

        todo = set(self.connects) # set([(connector  key)])
        point2net = {} # NetPoint -> Net
        nets = set()

        def get_net(point):
            """ Return a new or existing Net for a NetPoint. """
            if point not in point2net:
                point2net[point] = Net(str(len(nets)))
                nets.add(point2net[point])
            return point2net[point]

        def combine_nets(n1, n2):
            """Add net n2 into n1, get rid of n1."""
            for point in n2.points.itervalues():
                n1.add_point(point)
                point2net[point] = n1
            nets.discard(n2)

        def connect(p1, p2):
            """ Connect two points in a net, maybe the same point """

            net = get_net(p1)
            point2net[p1] = net
            net.add_point(p1)

            if point2net.get(p2, net) is not net:
                combine_nets(net, point2net[p2])
            else:
                net.add_point(p2)
                point2net[p2] = net

            if p1 is p2:
                return

            if p2.point_id not in p1.connected_points:
                p1.connected_points.append(p2.point_id)
            if p1.point_id not in p2.connected_points:
                p2.connected_points.append(p1.point_id)

        def add_to_net(main_key):
            """ Update a net with a new set of connects """

            todo.discard(main_key)

            main_point = connkey2point[main_key]
            connect(main_point, main_point)

            remaining = []

            for conn_key in self.connects[main_key]:
                if conn_key in todo:
                    remaining.append(conn_key)

                if conn_key in connkey2point:
                    connect(main_point, connkey2point[conn_key])
                elif conn_key[0] in self.component_instances:
                    inst = self.component_instances[conn_key[0]]
                    cpt_parser = self.components[inst.library_id]
                    cpt_parser.connect_point(conn_key[1], inst, main_point)

            return remaining

        while todo:
            remaining = [todo.pop()]
            while remaining:
                remaining.extend(add_to_net(remaining.pop(0)))

        nets = sorted(nets, key = lambda n : int(n.net_id))

        for i, net in enumerate(nets):
            net.net_id = str(i)

        return nets
class Fritzing(object):
    """ The Fritzing Format Parser

    Connection points in a fritzing file are identified by a 'module
    index' which references a component instance or a wire, and a
    'connector id' which references a specific pin. Together the
    (index, connid) tuple uniquely identifies a connection point.
    """

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

        # This maps fritzing connector keys to (x, y) coordinates
        self.connkey2xy = {} # (index, connid) -> (x, y)

        # This maps fritzing component indices to ComponentInstances
        self.component_instances = {} # index -> ComponentInstance

        # Map connector keys to the list of connector keys they
        # are connected to.
        self.connects = {} # (index, connid) -> [(index, connid)]

        self.components = {} # idref -> ComponentParser

        self.fritzing_version = None
        self.fzz_zipfile = None # The ZipFile if we are parsing an fzz


    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an fritzing file """
        with open(filename, 'r') as f:
            data = f.read(4096)
        confidence = 0
        if 'fritzingVersion' in data:
            confidence += 0.9
        elif filename.endswith('.fzz'):
            confidence += 0.9
        if confidence == 0 and zipfile.is_zipfile(filename):
            zip_file = zipfile.ZipFile(filename)
            for name in zip_file.namelist():
                if name.endswith('.fz'):
                    confidence += 0.9
                    break
            zip_file.close()
        return confidence


    def parse(self, filename):
        """ Parse a Fritzing file into a design """

        tree = self.make_tree(filename)

        self.fritzing_version = tree.getroot().get('fritzingVersion', '0')

        for element in tree.findall('instances/instance'):
            self.parse_instance(element)

        for idref, cpt_parser in self.components.iteritems():
            self.design.add_component(idref, cpt_parser.component)

        for cptinst in self.component_instances.itervalues():
            self.design.add_component_instance(cptinst)

        for net in self.build_nets():
            self.design.add_net(net)

        return self.design


    def make_tree(self, filename):
        """
        Return an ElementTree for the given file name.
        """

        if zipfile.is_zipfile(filename):
            self.fzz_zipfile = zipfile.ZipFile(filename)
            fz_name = [name for name in self.fzz_zipfile.namelist()
                       if name.endswith('.fz')][0]
            fz_file = self.fzz_zipfile.open(fz_name)
        else:
            fz_file = filename

        return ElementTree(file=fz_file)

    def parse_instance(self, instance):
        """ Parse a Fritzing instance block """

        if instance.get('moduleIdRef') == 'WireModuleID':
            self.parse_wire(instance)
        else:
            self.parse_component_instance(instance)


    def parse_wire(self, inst):
        """ Parse a Fritzing wire instance """

        view = inst.find('views/schematicView')

        if view is None:
            return

        #view2 = inst.find('views/breadboardView')

        #if view2 is not None:
        #    return

        index = inst.get('modelIndex')
        geom = view.find('geometry')

        origin_x, origin_y = get_x(geom), get_y(geom)

        conn_keys = []

        for connects in view.findall('connectors/connector/connects/connect'):
            if connects.get('layer') == 'breadboardbreadboard':
                return

        for i, connector in enumerate(view.findall('connectors/connector'), 1):
            cid = connector.get('connectorId')
            self.connkey2xy[index, cid] = (origin_x + get_x(geom, 'x%d' % i),
                                           origin_y + get_y(geom, 'y%d' % i))

            conn_keys.append((index, cid))

            self.connects[index, cid] = \
                [(c.get('modelIndex'), c.get('connectorId'))
                 for c in connector.findall('connects/connect')]

        # connect wire ends to each other
        if len(conn_keys) >= 2:
            self.connects[conn_keys[0]].append(conn_keys[1])
            self.connects[conn_keys[1]].append(conn_keys[0])


    def ensure_component(self, inst):
        """ If we have not already done so, create the Component the
        given Fritzing instance is an instance of. Return the
        Component, or None if we cannot load it"""

        idref = inst.get('moduleIdRef')

        if idref in self.components:
            return self.components[idref]

        fzp_path = inst.get('path')
        if not fzp_path:
           # print "Path not found", idref
            return None

        if exists(fzp_path):
            fzp_file = fzp_path
        else:
            fzp_file = self.lookup_fzz_file(fzp_path, 'part')

        if not fzp_file:
            fzp_file = lookup_part(fzp_path, self.fritzing_version)
            if fzp_file is not None:
                fzp_path = fzp_file

        if not fzp_file:
          # print "File not found", idref
            return None

        parser = ComponentParser(idref)
        parser.parse_fzp(fzp_file)

        if parser.image is not None:
            #print "Image Path found", idref
            svg_file = self.lookup_fzz_file(parser.image, 'svg.schematic')
			
            if svg_file is None:
               # print "Image not in fzz", idref
                #print fzp_path
                fzp_dir = dirname(fzp_path)
                #print fzp_dir
                pdb_dir = dirname(fzp_dir)
                #print pdb_dir
                fritzingroot_dir = dirname(pdb_dir)
                parts_dir = fritzingroot_dir + '/parts'
                #print parts_dir
                svg_path = join(parts_dir, 'svg', basename(fzp_dir),parser.image)
                #print basename(fzp_dir)			   
                if exists(svg_path):
                    svg_file = svg_path
                    #print svg_file

            if svg_file is not None:
                parser.parse_svg(svg_file)

        self.components[idref] = parser
        return parser


    def lookup_fzz_file(self, path, prefix):
        """ Find a file in our fzz archive, if any """

        if not self.fzz_zipfile:
            return None

        fzz_name = prefix + '.' + basename(path)

        try:
            self.fzz_zipfile.getinfo(fzz_name)
        except KeyError:
            return None
        else:
            return self.fzz_zipfile.open(fzz_name)


    def parse_component_instance(self, inst):
        """ Parse a Fritzing non-wire instance into a ComponentInstance """

        view = inst.find('views/schematicView')

        if view is None:
            return

        if view.get('layer') == 'breadboardbreadboard':
            return

        cpt = self.ensure_component(inst)

        if cpt is None:
            return

        index = inst.get('modelIndex')
        idref = inst.get('moduleIdRef')
        title = inst.find('title').text
        geom = view.find('geometry')
        xform = geom.find('transform')

        x, y = float(geom.get('x', 0)), float(geom.get('y', 0))

        if xform is None:
            rotation = 0.0
        else:
            matrix = tuple(int(float(xform.get(key, 0)))
                           for key in ('m11', 'm12', 'm21', 'm22'))
            x, y = rotate_component(cpt, matrix, x, y)
            rotation = MATRIX2ROTATION.get(matrix, 0.0)

        compinst = ComponentInstance(title, cpt, idref, 0)

        compinst.add_symbol_attribute(
            SymbolAttribute(make_x(x), make_y(y), rotation, False))

        self.component_instances[index] = compinst

    def build_nets(self):
        """ Build the nets from the connects, points, and instances """

        xy2point = {} # x, y -> NetPoint

        def get_point(connkey):
            """ Return a new or existing NetPoint for an (x,y) coordinate """
            x, y = self.connkey2xy[connkey]
            if (x, y) not in xy2point:
                xy2point[x, y] = NetPoint('%da%d' % (x, y), x, y)
            return xy2point[x, y]

        # connector key -> NetPoint
        connkey2point = dict((ck, get_point(ck)) for ck in self.connkey2xy)

        todo = set(self.connects) # set([(connector  key)])
        point2net = {} # NetPoint -> Net
        nets = set()

        def get_net(point):
            """ Return a new or existing Net for a NetPoint. """
            if point not in point2net:
                point2net[point] = Net(str(len(nets)))
                nets.add(point2net[point])
            return point2net[point]

        def combine_nets(n1, n2):
            """Add net n2 into n1, get rid of n1."""
            for point in n2.points.itervalues():
                n1.add_point(point)
                point2net[point] = n1
            nets.discard(n2)

        def connect(p1, p2):
            """ Connect two points in a net, maybe the same point """

            net = get_net(p1)
            point2net[p1] = net
            net.add_point(p1)

            if point2net.get(p2, net) is not net:
                combine_nets(net, point2net[p2])
            else:
                net.add_point(p2)
                point2net[p2] = net

            if p1 is p2:
                return

            if p2.point_id not in p1.connected_points:
                p1.connected_points.append(p2.point_id)
            if p1.point_id not in p2.connected_points:
                p2.connected_points.append(p1.point_id)

        def add_to_net(main_key):
            """ Update a net with a new set of connects """

            todo.discard(main_key)

            main_point = connkey2point[main_key]
            connect(main_point, main_point)

            remaining = []

            for conn_key in self.connects[main_key]:
                if conn_key in todo:
                    remaining.append(conn_key)

                if conn_key in connkey2point:
                    connect(main_point, connkey2point[conn_key])
                elif conn_key[0] in self.component_instances:
                    inst = self.component_instances[conn_key[0]]
                    cpt_parser = self.components[inst.library_id]
                    cpt_parser.connect_point(conn_key[1], inst, main_point)

            return remaining

        while todo:
            remaining = [todo.pop()]
            while remaining:
                remaining.extend(add_to_net(remaining.pop(0)))

        nets = sorted(nets, key = lambda n : int(n.net_id))

        for i, net in enumerate(nets):
            net.net_id = str(i)

        return nets
 def test_layout(self):
     """ Capture absence of a layout. """
     design = Design()
     writer = Writer()
     writer.write(design)
    def parse(self, file_path):
        """ Parse an Altium file into a design """
        design = Design()

        # Open the file in read-binary mode and only proceed if it was properly opened.
        in_file = open(file_path, "rb")
        if in_file:
            # Read the entire contents, omitting the interrupting blocks.
            input = in_file.read(0x200)
            # Skip the first 0x200 interrupting block.
            temp = in_file.read(0x200)
            while temp:
                # Read the next 0x10000 minus 0x200.
                temp = in_file.read(0xFE00)
                input += temp
                # Skip the next 0x200 interrupting block.
                temp = in_file.read(0x200)
            in_file.close()
            # Store all the headers, though they are not used.
            cursor_start = 0
            self.first_header = input[cursor_start:cursor_start + 0x200]
            cursor_start += 0x200
            self.root_entry = input[cursor_start:cursor_start + 0x80]
            cursor_start += 0x80
            self.file_header = input[cursor_start:cursor_start + 0x80]
            cursor_start += 0x80
            self.storage = input[cursor_start:cursor_start + 0x80]
            cursor_start += 0x80
            self.additional = input[cursor_start:cursor_start + 0x80]
            cursor_start += 0x80
            self.last_header = input[cursor_start:cursor_start + 0x200]
            cursor_start += 0x200
            # Now prepare to read each of the parts.  Initialize an "end" cursor.
            cursor_end = 0
            # Get the size of the next part block.
            next_size = struct.unpack("<I",
                                      input[cursor_start:cursor_start + 4])[0]
            # Advance the "start" cursor.
            cursor_start += 4
            # Create a list to store all the parts.
            self.parts = []
            # Loop through until the "next size" is 0, which is the end of the parts list.
            while next_size != 0:
                cursor_end = input.find("\x00", cursor_start)
                # Create a dictionary to store all the property:value pairs.
                result = {}
                # Get a list of pairs by splitting on the separator character "|".
                property_list = input[cursor_start:cursor_end].split("|")
                # For each one, copy out whatever is before any "=" as property, and whatever is
                # after any "=" as value.
                for prop in property_list:
                    if prop:
                        property_val = p.split("=")[0]
                        # The negative list index is to handle the cases with "==" instead of "=".
                        value = p.split("=")[-1]
                        # Add the property to the result dictionary.
                        result[property_val] = value
                # Add the dictionary to the list of parts.
                self.parts.append(result)
                # Set things up for the next iteration of the loop.
                cursor_start = cursor_end + 1
                next_size = struct.unpack(
                    "<I", input[cursor_start:cursor_start + 4])[0]
                cursor_start += 4
            # Here the footers could be found and stored, but I don't think they're important.

        return design
def parse(eagle_obj):
    design = Design()
    def __init__(self):
        self.design = Design()

        # map components to gate names to symbol indices
        self.cpt2gate2symbol_index = defaultdict(dict)
class JSON(object):
    """ The Open JSON Format Parser
    This is mostly for sanity checks, it reads in the Open JSON format,
    and then outputs it. """

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


    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an openjson file """
        with open(filename, 'r') as f:
            data = f.read()
        confidence = 0
        if 'component_instances' in data:
            confidence += 0.3
        if 'design_attributes' in data:
            confidence += 0.6
        return confidence


    def parse(self, filename):
        """ Parse the openjson file into the core. """
        log.debug('Starting parse of %s', filename)
        with open(filename) as f:
            read = json.loads(f.read())

        self.parse_components(read.get('components'))
        self.parse_component_instances(read.get('component_instances'))
        if read.get('shapes') is not None:
            self.parse_sch_shapes(read.get('shapes'))
        self.parse_design_attributes(read.get('design_attributes'))
        self.parse_nets(read.get('nets'))
        self.parse_version(read.get('version'))

        # layout aspects
        self.parse_layer_options(read.get('layer_options'))
        self.parse_trace_segments(read.get('trace_segments'))
        self.parse_layout_objects(read.get('gen_objs'))
        self.parse_paths(read.get('paths'))
        self.parse_pours(read.get('pours'))
        self.parse_pcb_text(read.get('text'))

        return self.design


    def parse_version(self, version):
        """ Extract the file version. """
        file_version = version.get('file_version')
        exporter = version.get('exporter')
        self.design.set_version(file_version, exporter)


    def parse_layer_options(self, layer_options_json):
        if layer_options_json is None:
            return None

        for layer_option_json in layer_options_json:
            self.design.layer_options.append(Layer(layer_option_json['name']))


    def parse_trace_segments(self, segments_json):
        if segments_json is None:
            return None

        for segment_json in segments_json:
            p1 = Point(segment_json['p1']['x'], segment_json['p1']['y'])
            p2 = Point(segment_json['p2']['x'], segment_json['p2']['y'])
            segment = Segment(segment_json['layer'], p1, p2, segment_json['width'])
            self.design.trace_segments.append(segment)


    def parse_paths(self, paths_json):
        if paths_json is None:
            return None

        for path_json in paths_json:
            points = [Point(point_json['x'], point_json['y']) for point_json in path_json['points']]
            width = path_json['width']
            is_closed = path_json['is_closed']
            layer = path_json['layer']
            path = Path(layer, points, width, is_closed)
            self.design.paths.append(path)


    def parse_pours(self, pours_json):
        if pours_json is None:
            return None

        for pour_json in pours_json:
            points = [Point(point_json['x'], point_json['y']) for point_json in pour_json['points']]

            layer = pour_json['layer']
            subtractive_shapes = [];
            if 'subtractive_shapes' in pour_json:
                subtractive_shapes = [self.parse_shape(shape_json) for shape_json in pour_json['subtractive_shapes']]
            if 'readded_shapes' in pour_json:
                readded_shapes = [self.parse_shape(shape_json) for shape_json in pour_json['readded_shapes']]

            pour = Pour(layer, points, subtractive_shapes, readded_shapes)
            self.design.pours.append(pour)


    def parse_pcb_text(self, text_json):
        if text_json is None:
            return None

        for text_instance_json in text_json:
            anno = self.parse_annotation(text_instance_json)
            self.design.pcb_text.append(anno)


    def parse_layout_objects(self, gen_objs_json):
        if gen_objs_json is None:
            return None

        for gen_obj_json in gen_objs_json:
            gen_obj = parse_gen_obj_json(gen_obj_json)
            self.design.layout_objects.append(gen_obj)


    def parse_component_instances(self, component_instances):
        """ Extract the component instances. """

        if component_instances is None:
            return None

        for instance in component_instances:
            # Get instance_id, library_id and symbol_index
            instance_id = instance.get('instance_id')
            library_id = instance.get('library_id')
            symbol_index = int(instance.get('symbol_index'))
            footprint_index = int(instance.get('footprint_index'))
            # Make the ComponentInstance()
            inst = ComponentInstance(instance_id, self.design.components.components[library_id], library_id, symbol_index, footprint_index)

            # Get the SymbolAttributes
            for symbol_attribute in instance.get('symbol_attributes', []):
                attr = self.parse_symbol_attribute(symbol_attribute)
                inst.add_symbol_attribute(attr)

            # TODO(shamer) footprint_pos, fleb and genobj positions are relative to the footprint_pos
            for footprint_attribute in instance.get('footprint_attributes', []):
                attr = self.parse_footprint_attribute(footprint_attribute)
                inst.add_footprint_attribute(attr)
            for gen_obj_attribute in instance.get('gen_obj_attributes', []):
                attr = self.parse_gen_obj_attribute(gen_obj_attribute)
                inst.add_gen_obj_attribute(attr)

            footprint_json = instance.get('footprint_pos')
            if footprint_json:
                footprint_pos = self.parse_footprint_pos(footprint_json)
            else:
                footprint_pos = None
            inst.set_footprint_pos(footprint_pos)

            # Get the Attributes
            for key, value in instance.get('attributes').items():
                inst.add_attribute(key, value)

            # Add the ComponentInstance
            self.design.add_component_instance(inst)


    def parse_symbol_attribute(self, symbol_attribute):
        """ Extract attributes from a symbol. """
        x = int(symbol_attribute.get('x') or 0)
        y = int(symbol_attribute.get('y') or 0)

        rotation = float(symbol_attribute.get('rotation'))
        try:
            flip = (symbol_attribute.get('flip').lower() == "true")
        except:
            flip = False

        # Make SymbolAttribute
        symbol_attr = SymbolAttribute(x, y, rotation, flip)

        # Add Annotations
        for annotation in symbol_attribute.get('annotations'):
            anno = self.parse_annotation(annotation)
            symbol_attr.add_annotation(anno)

        # Return SymbolAttribute to be added to its ComponentInstance
        return symbol_attr


    def parse_footprint_attribute(self, footprint_attribute):
        """ Extract attributes from a footprint. """
        x = int(footprint_attribute.get('x') or 0)
        y = int(footprint_attribute.get('y') or 0)

        rotation = float(footprint_attribute.get('rotation'))
        try:
            flip = (footprint_attribute.get('flip').lower() == "true")
        except:
            flip = False

        layer = footprint_attribute.get('layer')

        footprint_attr = FootprintAttribute(x, y, rotation, flip, layer)

        return footprint_attr


    def parse_gen_obj_attribute(self, gen_obj_attribute):
        """ Extract attributes from a gen_obj. """
        x = int(gen_obj_attribute.get('x') or 0)
        y = int(gen_obj_attribute.get('y') or 0)

        rotation = float(gen_obj_attribute.get('rotation'))
        try:
            flip = (gen_obj_attribute.get('flip').lower() == "true")
        except:
            flip = False

        layer = gen_obj_attribute.get('layer')

        gen_obj_attr = GenObjAttribute(x, y, rotation, flip, layer)

        for key, value in gen_obj_attribute.get('attributes').items():
            gen_obj_attr.add_attribute(key, value)

        return gen_obj_attr


    def parse_footprint_pos(self, footprint_pos_json):
        """ Extract footprint pos. """
        x = int(footprint_pos_json.get('x') or 0)
        y = int(footprint_pos_json.get('y') or 0)

        rotation = float(footprint_pos_json.get('rotation', 0))
        flip = footprint_pos_json.get('flip')
        side = footprint_pos_json.get('side')

        return FootprintPos(x, y, rotation, flip, side)


    def parse_annotation(self, annotation):
        """ Extract an annotation. """
        value = annotation.get('value')
        x = int(annotation.get('x'))
        y = int(annotation.get('y'))
        label = self.parse_label(annotation.get('label'))
        layer = annotation.get('layer', 'default')
        rotation = float(annotation.get('rotation'))
        flip_horizontal = annotation.get('flip', False)
        visible = annotation.get('visible')
        if visible is not None and visible.lower() == 'false':
            visible = 'false'
        else:
            visible = 'true'
        return Annotation(value, x, y, rotation, visible, layer=layer, flip_horizontal=flip_horizontal, label=label)


    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_json in component.get('symbols', []):
                symbol = self.parse_symbol(symbol_json)
                comp.add_symbol(symbol)
            for footprint_json in component.get('footprints', []):
                footprint = self.parse_footprint(footprint_json)
                comp.add_footprint(footprint)
            self.design.add_component(library_id, comp)


    def parse_sch_shapes(self, shapes):
        """ Extract shapes drawn directly on the schematic. """
        for shape in shapes:
            self.design.add_shape(self.parse_shape(shape))


    def parse_symbol(self, symbol_json):
        """ Extract a symbol. """
        symb = Symbol()
        for body in symbol_json.get('bodies'):
            bdy = self.parse_symbol_body(body)
            symb.add_body(bdy)
        return symb


    def parse_footprint(self, footprint_json):
        """ Extract the bodies for a footprint. """
        footprint = Footprint()
        for body_json in footprint_json.get('bodies'):
            body = self.parse_footprint_body(body_json)
            footprint.add_body(body)
        for gen_obj_json in footprint_json.get('gen_objs'):
            gen_obj = self.parse_gen_obj(gen_obj_json)
            footprint.add_gen_obj(gen_obj)
        return footprint


    def parse_gen_obj(self, gen_obj_json):
        """ Extract the generated object. """
        gen_obj = parse_gen_obj_json(gen_obj_json)
        return gen_obj


    def parse_footprint_body(self, body_json):
        """ Extract a body of a symbol. """
        body = FBody()
        for shape in body_json.get('shapes'):
            parsed_shape = self.parse_shape(shape)
            body.add_shape(parsed_shape)
        body.layer = body_json.get('layer')
        body.rotation = body_json.get('rotation', 0)
        body.flip_horizontal = body_json.get('flip_horizontal', False)
        return body


    def parse_symbol_body(self, body):
        """ Extract a body of a symbol. """
        bdy = SBody()
        pins = body.get('pins')
        if (pins != None):
            for pin in body.get('pins'):
                parsed_pin = self.parse_pin(pin)
                bdy.add_pin(parsed_pin)
        shapes = body.get('shapes')
        if (shapes != None):
            for shape in body.get('shapes'):
                parsed_shape = self.parse_shape(shape)
                bdy.add_shape(parsed_shape)
        return bdy


    def parse_pin(self, pin):
        """ Extract a pin of a body. """
        pin_number = pin.get('pin_number')
        p1 = self.parse_point(pin.get('p1'))
        p2 = self.parse_point(pin.get('p2'))
        parsed_pin = Pin(pin_number, p1, p2)
        if pin.get('label') is not None:
            parsed_pin.label = self.parse_label(pin.get('label'))
        parsed_pin.styles = pin.get('styles') or {}
        return parsed_pin

    def parse_point(self, point):
        """ Extract a point. """
        x = int(point.get('x'))
        y = int(point.get('y'))
        return Point(x, y)

    def parse_label(self, label):
        """ Extract a label. """
        if label is None:
            return None
        x = int(label.get('x'))
        y = int(label.get('y'))
        text = label.get('text')
        font_size = label.get('font_size')
        font_family = label.get('font_family')
        align = label.get('align')
        baseline = label.get('baseline')
        rotation = float(label.get('rotation'))
        parsed_label = Label(x, y, text, font_size, font_family, align, baseline, rotation)
        parsed_label.styles = label.get('styles') or {}
        return parsed_label

    def parse_shape(self, shape):
        """ Extract a shape. """
        # pylint: disable=R0914
        # pylint: disable=R0911

        rotation = shape.get('rotation', 0.0)
        flip_horizontal = shape.get('flip_horizontal', False)

        shape_type = shape.get('type')
        if 'rectangle' == shape_type:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            height = int(shape.get('height'))
            width = int(shape.get('width'))
            parsed_shape = Rectangle(x, y, width, height)
        elif 'rounded_rectangle' == shape_type:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            height = int(shape.get('height'))
            width = int(shape.get('width'))
            radius = int(shape.get('radius'))
            parsed_shape = RoundedRectangle(x, y, width, height, radius)
        elif 'arc' == shape_type:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            start_angle = float(shape.get('start_angle'))
            end_angle = float(shape.get('end_angle'))
            radius = int(shape.get('radius'))
            parsed_shape = Arc(x, y, start_angle, end_angle, radius)
        elif 'circle' == shape_type:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            radius = int(shape.get('radius'))
            parsed_shape = Circle(x, y, radius)
        elif 'label' == shape_type:
            parsed_shape = self.parse_label(shape)
        elif 'line' == shape_type:
            p1 = self.parse_point(shape.get('p1'))
            p2 = self.parse_point(shape.get('p2'))
            parsed_shape = Line(p1, p2)
        elif 'polygon' == shape_type:
            parsed_shape = Polygon()
            for point in shape.get('points'):
                parsed_shape.add_point(self.parse_point(point))
        elif 'bezier' == shape_type:
            control1 = self.parse_point(shape.get('control1'))
            control2 = self.parse_point(shape.get('control2'))
            p1 = self.parse_point(shape.get('p1'))
            p2 = self.parse_point(shape.get('p2'))
            parsed_shape = BezierCurve(control1, control2, p1, p2)
        elif 'rounded_segment' == shape_type:
            p1 = self.parse_point(shape.get('p1'))
            p2 = self.parse_point(shape.get('p2'))
            width = int(shape.get('width'))
            parsed_shape = RoundedSegment(p1, p2, width)

        parsed_shape.rotation = rotation
        parsed_shape.flip_horizontal = flip_horizontal

        parsed_shape.styles = shape.get('styles') or {}
        parsed_shape.attributes = shape.get('attributes') or {}
        return parsed_shape

    def parse_design_attributes(self, design_attributes):
        """ Extract design attributes. """
        attrs = DesignAttributes()
        # Get the Annotations
        for annotation in design_attributes.get('annotations'):
            anno = self.parse_annotation(annotation)
            attrs.add_annotation(anno)

        # Get the Attributes
        for key, value in design_attributes.get('attributes').items():
            attrs.add_attribute(key, value)

        # Get the Metadata
        meta = self.parse_metadata(design_attributes.get('metadata'))
        attrs.set_metadata(meta)
        self.design.set_design_attributes(attrs)


    def parse_metadata(self, metadata):
        """ Extract design meta-data. """
        meta = Metadata()
        meta.set_name(metadata.get('name'))
        meta.set_license(metadata.get('license'))
        meta.set_owner(metadata.get('owner'))
        meta.set_updated_timestamp(metadata.get('updated_timestamp'))
        meta.set_design_id(metadata.get('design_id'))
        meta.set_description(metadata.get('description'))
        meta.set_slug(metadata.get('slug'))
        for attached_url in metadata.get('attached_urls'):
            meta.add_attached_url(attached_url)
        return meta


    def parse_nets(self, nets):
        """ Extract nets. """
        for net in nets:
            net_id = net.get('net_id')
            ret_net = Net(net_id)
            # Add Annotations
            for annotation in net.get('annotations'):
                anno = self.parse_annotation(annotation)
                ret_net.add_annotation(anno)
            # Get the Attributes
            for key, value in net.get('attributes').items():
                ret_net.add_attribute(key, value)
            # Get the Points
            for net_point in net.get('points'):
                npnt = self.parse_net_point(net_point)
                ret_net.add_point(npnt)
            self.design.add_net(ret_net)


    def parse_net_point(self, net_point):
        """ Extract a net point. """
        point_id = net_point.get('point_id')
        x = int(net_point.get('x'))
        y = int(net_point.get('y'))
        npnt = NetPoint(point_id, x, y)
        # Get the connected points
        for point in net_point.get('connected_points'):
            npnt.add_connected_point(point)
        # Get the ConnectedComponents
        comps = net_point.get('connected_components')
        if (comps != None):
            for connectedcomponent in comps:
                conn_comp = self.parse_connected_component(connectedcomponent)
                npnt.add_connected_component(conn_comp)
        return npnt


    def parse_connected_component(self, connectedcomponent):
        """ Extract a connected component. """
        instance_id = connectedcomponent.get('instance_id')
        pin_number = connectedcomponent.get('pin_number')
        return ConnectedComponent(instance_id, pin_number)
 def setUp(self):
     """ Setup the test case. """
     self.des = Design()
    def __init__(self):
        self.design = Design()

        # map components to gate names to symbol indices
        self.cpt2gate2symbol_index = defaultdict(dict)
    def parse_schematic(self, stream):
        """ Parse a gEDA schematic provided as a *stream* object into a 
            design.
            
            Returns the design corresponding to the schematic.
        """
        # pylint: disable=R0912
        if self.design is None:
            self.design = Design()

        self.segments = set()
        self.net_points = dict() 
        self.net_names = dict() 

        obj_type, params = self._parse_command(stream)

        while obj_type is not None:

            if obj_type == 'T': ##Convert regular text or attribute
                key, value = self._parse_text(stream, params)
                
                if key is None: ## text is annotation
                    self.design.design_attributes.add_annotation(
                        self._create_annotation(value, params)
                    )

                elif key == 'use_license':
                    self.design.design_attributes.metadata.license = value
                else:
                    self.design.design_attributes.add_attribute(key, value)

            elif obj_type == 'G' : ## picture type is not supported
                log.warn("ignoring picture/font in gEDA file. Not supported!")
            elif obj_type == 'C': ## command for component found
                basename = params['basename']

                ## ignore title since it only defines the blueprint frame
                if basename.startswith('title'):
                    self._parse_environment(stream)

                ## busripper are virtual components that need separate 
                ## processing 
                elif 'busripper' in basename:
                    self.segments.add(
                        self._create_ripper_segment(params)
                    )

                    ## make sure following environments are ignored
                    self.skip_embedded_section(stream)
                    self._parse_environment(stream)

                else:
                    self.parse_component(stream, params)

            elif obj_type == 'N': ## net segment (in schematic ONLY)
                self._parse_segment(stream, params)

            elif obj_type == 'H': ## SVG-like path
                log.warn('ommiting path outside of component.')
                ## skip description of path
                num_lines = params['num_lines']
                for _ in range(num_lines):
                    stream.readline()

            elif obj_type == 'U': ## bus (only graphical feature NOT component)
                self._parse_bus(params)

            obj_type, params = self._parse_command(stream)

        ## process net segments into nets & net points and add to design
        self.divide_segments()
        calculated_nets = self.calculate_nets()

        for cnet in calculated_nets:
            self.design.add_net(cnet)

        return self.design
    def parse(self, inputfile):
        """ Parse a gEDA file into a design.

            Returns the design corresponding to the gEDA file.
        """
        inputfiles = []

        ## check if inputfile is in ZIP format
        if zipfile.is_zipfile(inputfile):
            self.geda_zip = zipfile.ZipFile(inputfile)
            for filename in self.geda_zip.namelist():
                if filename.endswith('.sch'):
                    inputfiles.append(filename)
        else:
            inputfiles = [inputfile]

        self.design = Design()
        self.unassigned_body = components.Body()

        ## parse frame data of first schematic to extract
        ## page size (assumes same frame for all files)
        with self._open_file_or_zip(inputfiles[0]) as stream:
            self._check_version(stream)

            for line in stream.readlines():
                if 'title' in line and line.startswith('C'):
                    obj_type, params = self._parse_command(StringIO(line))
                    assert(obj_type == 'C')

                    params['basename'], _ = os.path.splitext(
                        params['basename'],
                    )

                    log.debug("using title file: %s", params['basename'])

                    self._parse_title_frame(params)

        for filename in inputfiles:
            f_in = self._open_file_or_zip(filename)
            self._check_version(f_in)

            self.parse_schematic(f_in)

            basename, _ = os.path.splitext(os.path.basename(filename))
            self.design.design_attributes.metadata.set_name(basename)

            ## modify offset for next page to be shifted to the right
            self.offset.x = self.offset.x - self.frame_width

            f_in.close()

        ## if unassigned shapes have been found during parsing add a new
        ## component to the design
        if len(self.unassigned_body.shapes + self.unassigned_body.pins) > 0:
            component = components.Component("UNASSIGNED_SHAPES")
            symbol = components.Symbol()
            component.add_symbol(symbol)
            symbol.add_body(self.unassigned_body)

            instance = ComponentInstance(component.name, component.name, 0)
            symbol = SymbolAttribute(0, 0, 0)
            instance.add_symbol_attribute(symbol)

            self.design.add_component(component.name, component)
            self.design.add_component_instance(instance)

        return self.design
class JSON(object):
    """ The Open JSON Format Parser
    This is mostly for sanity checks, it reads in the Open JSON format,
    and then outputs it. """
    def __init__(self):
        self.design = Design()

    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an openjson file """
        with open(filename, 'r') as f:
            data = f.read()
        confidence = 0
        if 'component_instances' in data:
            confidence += 0.3
        if 'design_attributes' in data:
            confidence += 0.6
        return confidence

    def parse(self, filename):
        """ Parse the openjson file into the core. """
        log.debug('Starting parse of %s', filename)
        with open(filename) as f:
            read = json.loads(f.read())

        self.parse_components(read.get('components'))
        self.parse_component_instances(read.get('component_instances'))
        if read.get('shapes') is not None:
            self.parse_sch_shapes(read.get('shapes'))
        self.parse_design_attributes(read.get('design_attributes'))
        self.parse_nets(read.get('nets'))
        self.parse_version(read.get('version'))

        # layout aspects
        self.parse_layer_options(read.get('layer_options'))
        self.parse_trace_segments(read.get('trace_segments'))
        self.parse_layout_objects(read.get('gen_objs'))
        self.parse_paths(read.get('paths'))
        self.parse_pours(read.get('pours'))
        self.parse_pcb_text(read.get('text'))

        return self.design

    def parse_version(self, version):
        """ Extract the file version. """
        file_version = version.get('file_version')
        exporter = version.get('exporter')
        self.design.set_version(file_version, exporter)

    def parse_layer_options(self, layer_options_json):
        if layer_options_json is None:
            return None

        for layer_option_json in layer_options_json:
            self.design.layer_options.append(Layer(layer_option_json['name']))

    def parse_trace_segments(self, segments_json):
        if segments_json is None:
            return None

        for segment_json in segments_json:
            p1 = Point(segment_json['p1']['x'], segment_json['p1']['y'])
            p2 = Point(segment_json['p2']['x'], segment_json['p2']['y'])
            segment = Segment(segment_json['layer'], p1, p2,
                              segment_json['width'])
            self.design.trace_segments.append(segment)

    def parse_paths(self, paths_json):
        if paths_json is None:
            return None

        for path_json in paths_json:
            points = [
                Point(point_json['x'], point_json['y'])
                for point_json in path_json['points']
            ]
            width = path_json['width']
            is_closed = path_json['is_closed']
            layer = path_json['layer']
            path = Path(layer, points, width, is_closed)
            self.design.paths.append(path)

    def parse_pours(self, pours_json):
        if pours_json is None:
            return None

        for pour_json in pours_json:
            points = [
                Point(point_json['x'], point_json['y'])
                for point_json in pour_json['points']
            ]

            layer = pour_json['layer']
            subtractive_shapes = []
            if 'subtractive_shapes' in pour_json:
                subtractive_shapes = [
                    self.parse_shape(shape_json)
                    for shape_json in pour_json['subtractive_shapes']
                ]
            if 'readded_shapes' in pour_json:
                readded_shapes = [
                    self.parse_shape(shape_json)
                    for shape_json in pour_json['readded_shapes']
                ]

            pour = Pour(layer, points, subtractive_shapes, readded_shapes)
            self.design.pours.append(pour)

    def parse_pcb_text(self, text_json):
        if text_json is None:
            return None

        for text_instance_json in text_json:
            anno = self.parse_annotation(text_instance_json)
            self.design.pcb_text.append(anno)

    def parse_layout_objects(self, gen_objs_json):
        if gen_objs_json is None:
            return None

        for gen_obj_json in gen_objs_json:
            gen_obj = parse_gen_obj_json(gen_obj_json)
            self.design.layout_objects.append(gen_obj)

    def parse_component_instances(self, component_instances):
        """ Extract the component instances. """

        if component_instances is None:
            return None

        for instance in component_instances:
            # Get instance_id, library_id and symbol_index
            instance_id = instance.get('instance_id')
            library_id = instance.get('library_id')
            symbol_index = int(instance.get('symbol_index'))
            footprint_index = int(instance.get('footprint_index'))
            # Make the ComponentInstance()
            inst = ComponentInstance(
                instance_id, self.design.components.components[library_id],
                library_id, symbol_index, footprint_index)

            # Get the SymbolAttributes
            for symbol_attribute in instance.get('symbol_attributes', []):
                attr = self.parse_symbol_attribute(symbol_attribute)
                inst.add_symbol_attribute(attr)

            # TODO(shamer) footprint_pos, fleb and genobj positions are relative to the footprint_pos
            for footprint_attribute in instance.get('footprint_attributes',
                                                    []):
                attr = self.parse_footprint_attribute(footprint_attribute)
                inst.add_footprint_attribute(attr)
            for gen_obj_attribute in instance.get('gen_obj_attributes', []):
                attr = self.parse_gen_obj_attribute(gen_obj_attribute)
                inst.add_gen_obj_attribute(attr)

            footprint_json = instance.get('footprint_pos')
            if footprint_json:
                footprint_pos = self.parse_footprint_pos(footprint_json)
            else:
                footprint_pos = None
            inst.set_footprint_pos(footprint_pos)

            # Get the Attributes
            for key, value in instance.get('attributes').items():
                inst.add_attribute(key, value)

            # Add the ComponentInstance
            self.design.add_component_instance(inst)

    def parse_symbol_attribute(self, symbol_attribute):
        """ Extract attributes from a symbol. """
        x = int(symbol_attribute.get('x') or 0)
        y = int(symbol_attribute.get('y') or 0)

        rotation = float(symbol_attribute.get('rotation'))
        try:
            flip = (symbol_attribute.get('flip').lower() == "true")
        except:
            flip = False

        # Make SymbolAttribute
        symbol_attr = SymbolAttribute(x, y, rotation, flip)

        # Add Annotations
        for annotation in symbol_attribute.get('annotations'):
            anno = self.parse_annotation(annotation)
            symbol_attr.add_annotation(anno)

        # Return SymbolAttribute to be added to its ComponentInstance
        return symbol_attr

    def parse_footprint_attribute(self, footprint_attribute):
        """ Extract attributes from a footprint. """
        x = int(footprint_attribute.get('x') or 0)
        y = int(footprint_attribute.get('y') or 0)

        rotation = float(footprint_attribute.get('rotation'))
        try:
            flip = (footprint_attribute.get('flip').lower() == "true")
        except:
            flip = False

        layer = footprint_attribute.get('layer')

        footprint_attr = FootprintAttribute(x, y, rotation, flip, layer)

        return footprint_attr

    def parse_gen_obj_attribute(self, gen_obj_attribute):
        """ Extract attributes from a gen_obj. """
        x = int(gen_obj_attribute.get('x') or 0)
        y = int(gen_obj_attribute.get('y') or 0)

        rotation = float(gen_obj_attribute.get('rotation'))
        try:
            flip = (gen_obj_attribute.get('flip').lower() == "true")
        except:
            flip = False

        layer = gen_obj_attribute.get('layer')

        gen_obj_attr = GenObjAttribute(x, y, rotation, flip, layer)

        for key, value in gen_obj_attribute.get('attributes').items():
            gen_obj_attr.add_attribute(key, value)

        return gen_obj_attr

    def parse_footprint_pos(self, footprint_pos_json):
        """ Extract footprint pos. """
        x = int(footprint_pos_json.get('x') or 0)
        y = int(footprint_pos_json.get('y') or 0)

        rotation = float(footprint_pos_json.get('rotation', 0))
        flip = footprint_pos_json.get('flip')
        side = footprint_pos_json.get('side')

        return FootprintPos(x, y, rotation, flip, side)

    def parse_annotation(self, annotation):
        """ Extract an annotation. """
        value = annotation.get('value')
        x = int(annotation.get('x'))
        y = int(annotation.get('y'))
        label = self.parse_label(annotation.get('label'))
        layer = annotation.get('layer', 'default')
        rotation = float(annotation.get('rotation'))
        flip_horizontal = annotation.get('flip', False)
        visible = annotation.get('visible')
        if visible is not None and visible.lower() == 'false':
            visible = 'false'
        else:
            visible = 'true'
        return Annotation(value,
                          x,
                          y,
                          rotation,
                          visible,
                          layer=layer,
                          flip_horizontal=flip_horizontal,
                          label=label)

    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_json in component.get('symbols', []):
                symbol = self.parse_symbol(symbol_json)
                comp.add_symbol(symbol)
            for footprint_json in component.get('footprints', []):
                footprint = self.parse_footprint(footprint_json)
                comp.add_footprint(footprint)
            self.design.add_component(library_id, comp)

    def parse_sch_shapes(self, shapes):
        """ Extract shapes drawn directly on the schematic. """
        for shape in shapes:
            self.design.add_shape(self.parse_shape(shape))

    def parse_symbol(self, symbol_json):
        """ Extract a symbol. """
        symb = Symbol()
        for body in symbol_json.get('bodies'):
            bdy = self.parse_symbol_body(body)
            symb.add_body(bdy)
        return symb

    def parse_footprint(self, footprint_json):
        """ Extract the bodies for a footprint. """
        footprint = Footprint()
        for body_json in footprint_json.get('bodies'):
            body = self.parse_footprint_body(body_json)
            footprint.add_body(body)
        for gen_obj_json in footprint_json.get('gen_objs'):
            gen_obj = self.parse_gen_obj(gen_obj_json)
            footprint.add_gen_obj(gen_obj)
        return footprint

    def parse_gen_obj(self, gen_obj_json):
        """ Extract the generated object. """
        gen_obj = parse_gen_obj_json(gen_obj_json)
        return gen_obj

    def parse_footprint_body(self, body_json):
        """ Extract a body of a symbol. """
        body = FBody()
        for shape in body_json.get('shapes'):
            parsed_shape = self.parse_shape(shape)
            body.add_shape(parsed_shape)
        body.layer = body_json.get('layer')
        body.rotation = body_json.get('rotation', 0)
        body.flip_horizontal = body_json.get('flip_horizontal', False)
        return body

    def parse_symbol_body(self, body):
        """ Extract a body of a symbol. """
        bdy = SBody()
        for pin in body.get('pins'):
            parsed_pin = self.parse_pin(pin)
            bdy.add_pin(parsed_pin)
        for shape in body.get('shapes'):
            parsed_shape = self.parse_shape(shape)
            bdy.add_shape(parsed_shape)
        return bdy

    def parse_pin(self, pin):
        """ Extract a pin of a body. """
        pin_number = pin.get('pin_number')
        p1 = self.parse_point(pin.get('p1'))
        p2 = self.parse_point(pin.get('p2'))
        parsed_pin = Pin(pin_number, p1, p2)
        if pin.get('label') is not None:
            parsed_pin.label = self.parse_label(pin.get('label'))
        parsed_pin.styles = pin.get('styles') or {}
        return parsed_pin

    def parse_point(self, point):
        """ Extract a point. """
        x = int(point.get('x'))
        y = int(point.get('y'))
        return Point(x, y)

    def parse_label(self, label):
        """ Extract a label. """
        if label is None:
            return None
        x = int(label.get('x'))
        y = int(label.get('y'))
        text = label.get('text')
        font_size = label.get('font_size')
        font_family = label.get('font_family')
        align = label.get('align')
        baseline = label.get('baseline')
        rotation = float(label.get('rotation'))
        parsed_label = Label(x, y, text, font_size, font_family, align,
                             baseline, rotation)
        parsed_label.styles = label.get('styles') or {}
        return parsed_label

    def parse_shape(self, shape):
        """ Extract a shape. """
        # pylint: disable=R0914
        # pylint: disable=R0911

        rotation = shape.get('rotation', 0.0)
        flip_horizontal = shape.get('flip_horizontal', False)

        shape_type = shape.get('type')
        if 'rectangle' == shape_type:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            height = int(shape.get('height'))
            width = int(shape.get('width'))
            parsed_shape = Rectangle(x, y, width, height)
        elif 'rounded_rectangle' == shape_type:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            height = int(shape.get('height'))
            width = int(shape.get('width'))
            radius = int(shape.get('radius'))
            parsed_shape = RoundedRectangle(x, y, width, height, radius)
        elif 'arc' == shape_type:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            start_angle = float(shape.get('start_angle'))
            end_angle = float(shape.get('end_angle'))
            radius = int(shape.get('radius'))
            parsed_shape = Arc(x, y, start_angle, end_angle, radius)
        elif 'circle' == shape_type:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            radius = int(shape.get('radius'))
            parsed_shape = Circle(x, y, radius)
        elif 'label' == shape_type:
            parsed_shape = self.parse_label(shape)
        elif 'line' == shape_type:
            p1 = self.parse_point(shape.get('p1'))
            p2 = self.parse_point(shape.get('p2'))
            parsed_shape = Line(p1, p2)
        elif 'polygon' == shape_type:
            parsed_shape = Polygon()
            for point in shape.get('points'):
                parsed_shape.add_point(self.parse_point(point))
        elif 'bezier' == shape_type:
            control1 = self.parse_point(shape.get('control1'))
            control2 = self.parse_point(shape.get('control2'))
            p1 = self.parse_point(shape.get('p1'))
            p2 = self.parse_point(shape.get('p2'))
            parsed_shape = BezierCurve(control1, control2, p1, p2)
        elif 'rounded_segment' == shape_type:
            p1 = self.parse_point(shape.get('p1'))
            p2 = self.parse_point(shape.get('p2'))
            width = int(shape.get('width'))
            parsed_shape = RoundedSegment(p1, p2, width)

        parsed_shape.rotation = rotation
        parsed_shape.flip_horizontal = flip_horizontal

        parsed_shape.styles = shape.get('styles') or {}
        parsed_shape.attributes = shape.get('attributes') or {}
        return parsed_shape

    def parse_design_attributes(self, design_attributes):
        """ Extract design attributes. """
        attrs = DesignAttributes()
        # Get the Annotations
        for annotation in design_attributes.get('annotations'):
            anno = self.parse_annotation(annotation)
            attrs.add_annotation(anno)

        # Get the Attributes
        for key, value in design_attributes.get('attributes').items():
            attrs.add_attribute(key, value)

        # Get the Metadata
        meta = self.parse_metadata(design_attributes.get('metadata'))
        attrs.set_metadata(meta)
        self.design.set_design_attributes(attrs)

    def parse_metadata(self, metadata):
        """ Extract design meta-data. """
        meta = Metadata()
        meta.set_name(metadata.get('name'))
        meta.set_license(metadata.get('license'))
        meta.set_owner(metadata.get('owner'))
        meta.set_updated_timestamp(metadata.get('updated_timestamp'))
        meta.set_design_id(metadata.get('design_id'))
        meta.set_description(metadata.get('description'))
        meta.set_slug(metadata.get('slug'))
        for attached_url in metadata.get('attached_urls'):
            meta.add_attached_url(attached_url)
        return meta

    def parse_nets(self, nets):
        """ Extract nets. """
        for net in nets:
            net_id = net.get('net_id')
            ret_net = Net(net_id)
            # Add Annotations
            for annotation in net.get('annotations'):
                anno = self.parse_annotation(annotation)
                ret_net.add_annotation(anno)
            # Get the Attributes
            for key, value in net.get('attributes').items():
                ret_net.add_attribute(key, value)
            # Get the Points
            for net_point in net.get('points'):
                npnt = self.parse_net_point(net_point)
                ret_net.add_point(npnt)
            self.design.add_net(ret_net)

    def parse_net_point(self, net_point):
        """ Extract a net point. """
        point_id = net_point.get('point_id')
        x = int(net_point.get('x'))
        y = int(net_point.get('y'))
        npnt = NetPoint(point_id, x, y)
        # Get the connected points
        for point in net_point.get('connected_points'):
            npnt.add_connected_point(point)
        # Get the ConnectedComponents
        for connectedcomponent in net_point.get('connected_components'):
            conn_comp = self.parse_connected_component(connectedcomponent)
            npnt.add_connected_component(conn_comp)
        return npnt

    def parse_connected_component(self, connectedcomponent):
        """ Extract a connected component. """
        instance_id = connectedcomponent.get('instance_id')
        pin_number = connectedcomponent.get('pin_number')
        return ConnectedComponent(instance_id, pin_number)
class Specctra(object):
    """ The Specctra DSN Format Parser """
    def __init__(self):
        self.design = None
        self.resolution = None
        self.nets = {}
        self.net_points = {}
        self._id = 10
        self.min_x = maxint
        self.max_x = -(maxint - 1)
        self.min_y = maxint
        self.max_y = -(maxint - 1)

    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an specctra schematic """
        with open(filename, 'r') as f:
            data = f.read(4096)
        confidence = 0
        if '(pcb ' in data or '(PCB ' in data:
            confidence += 0.75
        return confidence

    def parse(self, filename):
        """ Parse a specctra file into a design """

        self.design = Design()

        with open(filename) as f:
            data = f.read()

        tree = DsnParser().parse(data)

        struct = self.walk(tree)
        self.resolution = struct.resolution
        self._convert(struct)

        return self.design

    def _convert(self, struct):
        for bound in struct.structure.boundary:
            if bound.rectangle.layer_id == 'pcb':
                vertex1, vertex2 = bound.rectangle.vertex1, bound.rectangle.vertex2
                self.min_x = self.to_pixels(min(vertex1[0], vertex2[0]))
                self.max_x = self.to_pixels(max(vertex1[0], vertex2[0]))
                self.min_y = self.to_pixels(min(vertex1[1], vertex2[1]))
                self.max_y = self.to_pixels(max(vertex1[1], vertex2[1]))
                break

        self._convert_library(struct)
        self._convert_components(struct)
        self._convert_nets(struct)

    def _convert_library(self, struct):
        """ Convert library """
        for image in struct.library.image:
            component = Component(image.image_id)
            self.design.add_component(image.image_id, component)
            fpt = Footprint()
            body = FBody()
            component.add_footprint(fpt)
            fpt.add_body(body)
            for pad in image.pin:
                body.add_pad(Pad(pad.pad_id, self.to_pixels(pad.vertex), self.to_pixels(pad.vertex)))
                for padstack in struct.library.padstack:
                    if padstack.padstack_id == pad.padstack_id:
                        shapes = [shape.shape for shape in padstack.shape]
                        for shape in self._convert_shapes(shapes, self.to_pixels(pad.vertex)):
                            body.add_shape(shape)
                        break

            for outline in image.outline:
                for shape in self._convert_shapes([outline.shape]):
                    body.add_shape(shape)

    def _convert_components(self, struct):
        """ Convert component """
        for component in struct.placement.component:
            library_id = component.image_id
            for place in component.place:
                # Outside PCB boundary
                if not place.vertex:
                    continue

                mirror = {90:270, 270:90}
                if place.side == 'back':
                    rotation = place.rotation
                else:
                    rotation = mirror.get(int(place.rotation), place.rotation)
                inst = ComponentInstance(place.component_id, component, library_id, 0)
                v = self.to_pixels(place.vertex)
                symbattr = FootprintAttribute(v[0], v[1], to_piradians(rotation), False)
                inst.add_symbol_attribute(symbattr) 
                self.design.add_component_instance(inst)

    def _get_point(self, net_id, point_id, x, y):
        if net_id not in self.nets:
            n = Net(net_id)
            self.design.add_net(n)
            self.nets[n.net_id] = n
        else:
            n = self.nets[net_id]

        key = (x, y)
        if key not in self.net_points:
            if not point_id:
                point_id = str(self._id)
                self._id += 1
            np = NetPoint(net_id + '-' + point_id, x, y)
            n.add_point(np)
            self.net_points[key] = np
        else:
            np = self.net_points[key]

        return np
 
    def _convert_wires(self, struct):
        if struct.wiring:
            for wire in struct.wiring.wire:
                lines = self._convert_shapes([wire.shape], absolute=True)
                for line in lines:
                    try:
                        np1 = self._get_point(wire.net.net_id, None, line.p1.x, line.p1.y)
                        np2 = self._get_point(wire.net.net_id, None, line.p2.x, line.p2.y)

                        np1.add_connected_point(np2.point_id)
                        np2.add_connected_point(np1.point_id)
                    except: 
                        pass

    def _convert_nets(self, struct):
        """ Convert nets """
        # FIXME polyline_path is not documented and no success with reverse engineering yet
        self._convert_wires(struct)

        if struct.network:
            for net in struct.network.net:
                if net.pins is None:
                    continue

                prev_point = None
                for pin_ref in net.pins.pin_reference:
                    # pin_ref like A1-"-" is valid (parsed to A1--)
                    component_id, pin_id = pin_ref[:pin_ref.index('-')], pin_ref[pin_ref.index('-') + 1:]  
                    point = self.get_component_pin(component_id, pin_id)
                    if point is None:
                        print 'Could not find net %s pin ref %s' % (net.net_id, pin_ref)
                        continue
                    cc = ConnectedComponent(component_id, pin_id)
                    np = self._get_point(net.net_id, pin_ref, point[0], point[1])
                    np.add_connected_component(cc)

                    if prev_point is not None:
                        # XXX if point is already connected assume wiring had routed network, don't do it here
                        if len(prev_point.connected_points) == 0:
                            prev_point.add_connected_point(np.point_id)
                        if len(np.connected_points) == 0:
                            np.add_connected_point(prev_point.point_id)
                    prev_point = np

    def get_component_pin(self, component_id, pin_id):
        for component_instance in self.design.component_instances:
            symbattr = component_instance.symbol_attributes[0]
            if component_instance.instance_id == component_id: 
                component = self.design.components.components[component_instance.library_id]
                for pin in component.symbols[0].bodies[0].pins:
                    if pin.pin_number == pin_id:
                        x, y = rotate((pin.p1.x, pin.p1.y), symbattr.rotation)
                        return (symbattr.x + x, symbattr.y + y)

    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 _convert_path(self, aperture, points):
        """ Convert path """
        result = []
        prev = points[0]
        for point in points[1:]:
            line = self._make_line(prev, point, float(aperture) / 2.0)
            result.extend(line)
            prev = point
        return result

    def _convert_shapes(self, shapes, center = (0, 0), absolute=False):
        """ Convert shapes """
        result = []

        def fix_point(point):
            x, y = (point[0] + center[0], point[1] + center[1])
            if absolute:
                # freerouter often creates points outside boundary, fix it
                if x > self.max_x:
                    x = self.min_x + x - self.max_x
                elif x < self.min_x:
                    x = self.max_x - x - self.min_x
                if y > self.max_y:
                    y = self.min_y + y - self.max_y
                elif y < self.min_y:
                    y = self.max_y - y - self.min_y

            return (x, y)


        for shape in shapes:
            if isinstance(shape, specctraobj.PolylinePath):
                points = [fix_point(self.to_pixels(point)) for point in shape.vertex]
                result.extend(self._convert_path(self.to_pixels(shape.aperture_width), points))

            elif isinstance(shape, specctraobj.Path):
                points = [fix_point(self.to_pixels(point)) for point in shape.vertex]
                # Path has connected start and end points
                if points[0] != points[-1] and len(points) != 2:
                    points.append(points[0])
                result.extend(self._convert_path(self.to_pixels(shape.aperture_width), points))

            elif isinstance(shape, specctraobj.Polygon):
                points = [fix_point(self.to_pixels(point)) for point in shape.vertex]
                points = [Point(point[0], point[1]) for point in points]
                result.append(Polygon(points))

            elif isinstance(shape, specctraobj.Rectangle):
                x1, y1 = self.to_pixels(shape.vertex1)
                x2, y2 = self.to_pixels(shape.vertex2)
                width, height = abs(x1 - x2), abs(y1 - y2)
                x1, y1 = fix_point((min(x1, x2), max(y1, y2)))

                result.append(Rectangle(x1, y1, width, height))
            elif isinstance(shape, specctraobj.Circle):
                point = fix_point(self.to_pixels(shape.vertex))
                result.append(Circle(point[0], point[1], self.to_pixels(shape.diameter / 2.0)))
        return result

    def to_pixels(self, vertex):
        return self.resolution.to_pixels(vertex)

    def walk(self, elem):
        if isinstance(elem, list) and len(elem) > 0:
            elemx = [self.walk(x) for x in elem]
            func = specctraobj.lookup(elemx[0])
            if func:
                f = func()
                f.parse(elemx[1:])
                return f
            else:
#print 'Unhandled element', elemx[0]
                return elemx
        else:
            return elem
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
 def test_layers(self):
     """ Capture absence of layers. """
     design = Design()
     design.layout = Layout()
     writer = Writer()
     writer.write(design)
class GEDA:
    """ The GEDA Format Parser """

    DELIMITER = ' '
    SCALE_FACTOR = 10.0  # maps 1000 MILS to 10 pixels

    OBJECT_TYPES = {
        'v': geda_commands.GEDAVersionCommand(),
        'L': geda_commands.GEDALineCommand(),
        'B': geda_commands.GEDABoxCommand(),
        'V': geda_commands.GEDACircleCommand(),
        'A': geda_commands.GEDAArcCommand(),
        'T': geda_commands.GEDATextCommand(),
        'N': geda_commands.GEDASegmentCommand(),
        'U': geda_commands.GEDABusCommand(),
        'P': geda_commands.GEDAPinCommand(),
        'C': geda_commands.GEDAComponentCommand(),
        'H': geda_commands.GEDAPathCommand(),
        ## valid types but are ignored
        'G': geda_commands.GEDAPictureCommand(),
        ## environments
        '{': geda_commands.GEDAEmbeddedEnvironmentCommand(),
        '}': [],  # attributes
        '[': geda_commands.GEDAAttributeEnvironmentCommand(),
        ']': [],  # embedded component
    }

    def __init__(self, symbol_dirs=None):
        """ Constuct a gEDA parser object. Specifying a list of symbol
            directories in *symbol_dir* will provide a symbol file
            lookup in the specified directories. The lookup will be
            generated instantly examining each directory (if it exists).

            Kwargs:
                symbol_dirs (list): List of directories containing .sym
                    files
        """
        self.offset = shape.Point(40000, 40000)
        ## Initialise frame size with largest possible size
        self.frame_width = 0
        self.frame_height = 0

        # initialise PIN counter
        self.pin_counter = itertools.count(0)
        # initialise  PATH counter
        self.path_counter = itertools.count(0)

        ## add flag to allow for auto inclusion
        if symbol_dirs is None:
            symbol_dirs = []

        symbol_dirs = symbol_dirs + \
            [os.path.join(os.path.dirname(__file__), '..', 'library', 'geda')]

        self.known_symbols = find_symbols(symbol_dirs)

        self.design = None
        self.segments = None
        self.net_points = None
        self.net_names = None
        self.geda_zip = None

    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an geda schematic """
        with open(filename, 'rU') as f:
            data = f.read()
        confidence = 0
        if data[0:2] == 'v ':
            confidence += 0.51
        if 'package=' in data:
            confidence += 0.25
        if 'footprint=' in data:
            confidence += 0.25
        if 'refdes=' in data:
            confidence += 0.25
        if 'netname=' in data:
            confidence += 0.25
        return confidence

    def set_offset(self, point):
        """ Set the offset point for the gEDA output. As OpenJSON
            positions the origin in the center of the viewport and
            gEDA usually uses (40'000, 40'000) as page origin, this
            allows for translating from one coordinate system to
            another. It expects a *point* object providing a *x* and
            *y* attribute.
        """
        ## create an offset of 5 grid squares from origin (0,0)
        self.offset.x = point.x
        self.offset.y = point.y

    def parse(self, inputfile):
        """ Parse a gEDA file into a design.

            Returns the design corresponding to the gEDA file.
        """
        inputfiles = []

        ## check if inputfile is in ZIP format
        if zipfile.is_zipfile(inputfile):
            self.geda_zip = zipfile.ZipFile(inputfile)
            for filename in self.geda_zip.namelist():
                if filename.endswith('.sch'):
                    inputfiles.append(filename)
        else:
            inputfiles = [inputfile]

        self.design = Design()

        ## parse frame data of first schematic to extract
        ## page size (assumes same frame for all files)
        with self._open_file_or_zip(inputfiles[0]) as stream:
            self._check_version(stream)

            for line in stream.readlines():
                if 'title' in line and line.startswith('C'):
                    obj_type, params = self._parse_command(StringIO(line))
                    assert (obj_type == 'C')

                    params['basename'], _ = os.path.splitext(
                        params['basename'], )

                    log.debug("using title file: %s", params['basename'])

                    self._parse_title_frame(params)

        ## store offset values in design attributes
        self.design.design_attributes.attributes.update({
            '_geda_offset_x':
            str(self.offset.x),
            '_geda_offset_y':
            str(self.offset.y),
            '_geda_frame_width':
            str(self.frame_width),
            '_geda_frame_height':
            str(self.frame_height),
        })

        for filename in inputfiles:
            f_in = self._open_file_or_zip(filename)
            self._check_version(f_in)

            self.parse_schematic(f_in)

            basename, _ = os.path.splitext(os.path.basename(filename))
            self.design.design_attributes.metadata.set_name(basename)

            ## modify offset for next page to be shifted to the right
            self.offset.x = self.offset.x - self.frame_width

            f_in.close()

        return self.design

    def _parse_v(self, stream, params):
        """
        Only required to be callable when 'v' command is found.
        Returns without any processing.
        """
        return

    def _parse_G(self, stream, params):
        """
        Parse picture command 'G'. Returns without any processing but
        logs a warning.
        """
        log.warn("ignoring picture/font in gEDA file. Not supported!")
        return

    def parse_schematic(self, stream):
        """ Parse a gEDA schematic provided as a *stream* object into a
            design.

            Returns the design corresponding to the schematic.
        """
        # pylint: disable=R0912
        if self.design is None:
            self.design = Design()

        self.segments = set()
        self.net_points = dict()
        self.net_names = dict()

        obj_type, params = self._parse_command(stream)

        while obj_type is not None:

            objects = getattr(self, "_parse_%s" % obj_type)(stream, params)

            attributes = self._parse_environment(stream)
            self.design.design_attributes.attributes.update(attributes or {})

            self.add_objects_to_design(self.design, objects)

            obj_type, params = self._parse_command(stream)

        ## process net segments into nets & net points and add to design
        self.divide_segments()

        calculated_nets = self.calculate_nets()

        for cnet in sorted(calculated_nets, key=lambda n: n.net_id):
            self.design.add_net(cnet)

        return self.design

    def _parse_title_frame(self, params):
        """ Parse the frame component in *params* to extract the
            page size to be used in the design. The offset is adjusted
            according to the bottom-left position of the frame.
        """
        ## set offset based on bottom-left corner of frame
        self.offset.x = params['x']
        self.offset.y = params['y']

        filename = self.known_symbols.get(params['basename'])
        if not filename or not os.path.exists(filename):
            log.warn("could not find title symbol '%s'" % params['basename'])

            self.frame_width = 46800
            self.frame_height = 34000
            return

        ## store title component name in design
        self.design.design_attributes.add_attribute(
            '_geda_titleframe',
            params['basename'],
        )

        with open(filename, 'rU') as stream:
            obj_type, params = self._parse_command(stream)

            while obj_type is not None:

                if obj_type == 'B':
                    if params['width'] > self.frame_width:
                        self.frame_width = params['width']

                    if params['height'] > self.frame_height:
                        self.frame_height = params['height']

                ## skip commands covering multiple lines
                elif obj_type in ['T', 'H']:
                    for _ in range(params['num_lines']):
                        stream.readline()

                obj_type, params = self._parse_command(stream)

            ## set width to estimated max value when no box was found
            if self.frame_width == 0:
                self.frame_width = 46800

            ## set height to estimated max value when no box was found
            if self.frame_height == 0:
                self.frame_height = 34000

    def _create_ripper_segment(self, params):
        """ Creates a new segement from the busripper provided
            in gEDA. The busripper is a graphical feature that
            provides a nicer look for a part of a net. The bus
            rippers are turned into net segments according to the
            length and orientation in *params*.

            Returns a tuple of two NetPoint objects for the segment.
        """
        x, y = params['x'], params['y']
        angle, mirror = params['angle'], params['mirror']

        if mirror:
            angle = (angle + 90) % 360

        x, y = self.conv_coords(x, y)
        pt_a = self.get_netpoint(x, y)

        ripper_size = self.to_px(200)

        ## create second point for busripper segment on bus
        if angle == 0:
            pt_b = self.get_netpoint(pt_a.x + ripper_size,
                                     pt_a.y + ripper_size)
        elif angle == 90:
            pt_b = self.get_netpoint(pt_a.x - ripper_size,
                                     pt_a.y + ripper_size)
        elif angle == 180:
            pt_b = self.get_netpoint(pt_a.x - ripper_size,
                                     pt_a.y - ripper_size)
        elif angle == 270:
            pt_b = self.get_netpoint(pt_a.x + ripper_size,
                                     pt_a.y - ripper_size)
        else:
            raise GEDAError("invalid angle in component '%s'" %
                            params['basename'])

        return pt_a, pt_b

    def _parse_component(self, stream, params):
        """ Creates a component instance according to the component *params*.
            If the component is not known in the library, a the component
            will be created according to its description in the embedded
            environment ``[]`` or a symbol file. The component is added
            to the library automatically if necessary.
            An instance of this component will be created and added to
            the design.
            A GEDAError is raised when either the component file
            is invalid or the referenced symbol file cannot be found
            in the known directories.

            Returns a tuple of Component and ComponentInstance objects.
        """
        basename, _ = os.path.splitext(params['basename'])

        component_name = basename
        if params.get('mirror'):
            component_name += '_MIRRORED'

        if component_name in self.design.components.components:
            component = self.design.components.components[component_name]

            ## skipping embedded data might be required
            self.skip_embedded_section(stream)

        else:
            ##check if sym file is embedded or referenced
            if basename.startswith('EMBEDDED'):
                ## embedded only has to be processed when NOT in symbol lookup
                if basename not in self.known_symbols:
                    component = self.parse_component_data(stream, params)
            else:
                if basename not in self.known_symbols:
                    log.warn("referenced symbol file '%s' unknown" % basename)
                    ## create a unknown symbol reference
                    component = self.parse_component_data(
                        StringIO(UNKNOWN_COMPONENT % basename), params)
                    ## parse optional attached environment before continuing
                    self._parse_environment(stream)
                    return None, None

                ## requires parsing of referenced symbol file
                with open(self.known_symbols[basename], "rU") as f_in:
                    self._check_version(f_in)
                    component = self.parse_component_data(f_in, params)

            self.design.add_component(component_name, component)

        ## get all attributes assigned to component instance
        attributes = self._parse_environment(stream)

        ## refdes attribute is name of component (mandatory as of gEDA doc)
        ## examples if gaf repo have components without refdes, use part of
        ## basename
        if attributes is not None:
            instance = ComponentInstance(
                attributes.get('_refdes', component.name), component.name, 0)
            for key, value in attributes.items():
                instance.add_attribute(key, value)

        else:
            instance = ComponentInstance(component.name, component.name, 0)

        ## generate a component instance using attributes
        self.design.add_component_instance(instance)

        symbol = SymbolAttribute(self.x_to_px(params['x']),
                                 self.y_to_px(params['y']),
                                 self.conv_angle(params['angle'], False))
        instance.add_symbol_attribute(symbol)

        ## add annotation for special attributes
        for idx, attribute_key in enumerate(['_refdes', 'device']):
            if attribute_key in component.attributes \
               or attribute_key in instance.attributes:

                symbol.add_annotation(
                    Annotation('{{%s}}' % attribute_key, 0, 0 + idx * 10, 0.0,
                               'true'))

        return component, instance

    def _check_version(self, stream):
        """ Check next line in *stream* for gEDA version data
            starting with ``v``. Raises ``GEDAError`` when no version
            data can be found.
        """
        typ, _ = self._parse_command(stream)
        if typ != 'v':
            raise GEDAError("cannot convert file, not in gEDA format")
        return True

    def _is_mirrored_command(self, params):
        return bool(params.get('mirror', False))

    def parse_component_data(self, stream, params):
        """ Creates a component from the component *params* and the
            following commands in the stream. If the component data
            is embedded in the schematic file, all coordinates will
            be translated into the origin first.
            Only a single symbol/body is created for each component
            since gEDA symbols contain exactly one description.

            Returns the newly created Component object.
        """
        # pylint: disable=R0912
        basename = os.path.splitext(params['basename'])[0]

        saved_offset = self.offset
        self.offset = shape.Point(0, 0)

        ## retrieve if component is mirrored around Y-axis
        mirror = self._is_mirrored_command(params)
        if mirror:
            basename += '_MIRRORED'

        move_to = None
        if basename.startswith('EMBEDDED'):
            move_to = (params['x'], params['y'])

        ## grab next line (should be '['
        typ, params = self._parse_command(stream, move_to)

        if typ == '[':
            typ, params = self._parse_command(stream, move_to)

        component = components.Component(basename)
        symbol = components.Symbol()
        component.add_symbol(symbol)
        body = components.Body()
        symbol.add_body(body)

        ##NOTE: adding this attribute to make parsing UPV data easier
        ## when using re-exported UPV.
        component.add_attribute('_geda_imported', 'true')
        self.pin_counter = itertools.count(0)

        while typ is not None:

            params['mirror'] = mirror
            objects = getattr(self, "_parse_%s" % typ)(stream, params)

            attributes = self._parse_environment(stream)
            component.attributes.update(attributes or {})

            self.add_objects_to_component(component, objects)

            typ, params = self._parse_command(stream, move_to)

        self.offset = saved_offset

        return component

    def divide_segments(self):
        """ Checks all net segments for intersecting points of
            all other net segments. If an intersection is detected
            the net segment is divided into two segments with the
            intersecting point. This method has been adapted from
            a similar method in the kiCAD parser.
        """
        ## check if segments need to be divided
        add_segs = set()
        rem_segs = set()
        for segment in self.segments:
            for point in self.net_points.values():
                if self.intersects_segment(segment, point):
                    pt_a, pt_b = segment
                    rem_segs.add(segment)
                    add_segs.add((pt_a, point))
                    add_segs.add((point, pt_b))

        self.segments -= rem_segs
        self.segments |= add_segs

    def skip_embedded_section(self, stream):
        """ Reads the *stream* line by line until the end of an
            embedded section (``]``) is found. This method is used
            to skip over embedded sections of already known
            components.
        """
        pos = stream.tell()
        typ = stream.readline().split(self.DELIMITER, 1)[0].strip()

        ## return with stream reset to previous position if not
        ## an embedded section
        if typ != '[':
            stream.seek(pos)
            return

        while typ != ']':
            typ = stream.readline().split(self.DELIMITER, 1)[0].strip()

    def get_netpoint(self, x, y):
        """ Creates a new NetPoint at coordinates *x*,*y* and stores
            it in the net point lookup table. If a NetPoint does already
            exist, the existing point is returned.
            Returns a NetPoint object at coordinates *x*,*y*
        """
        if (x, y) not in self.net_points:
            self.net_points[(x, y)] = net.NetPoint('%da%d' % (x, y), x, y)
        return self.net_points[(x, y)]

    @staticmethod
    def intersects_segment(segment, pt_c):
        """ Checks if point *pt_c* lays on the *segment*. This code is
            adapted from the kiCAD parser.
            Returns True if *pt_c* is on *segment*, False otherwise.
        """
        pt_a, pt_b = segment

        #check vertical segment
        if pt_a.x == pt_b.x == pt_c.x:
            if min(pt_a.y, pt_b.y) < pt_c.y < max(pt_a.y, pt_b.y):
                return True
        #check vertical segment
        elif pt_a.y == pt_b.y == pt_c.y:
            if min(pt_a.x, pt_b.x) < pt_c.x < max(pt_a.x, pt_b.x):
                return True
        #check diagonal segment
        elif (pt_c.x-pt_a.x)*(pt_b.y-pt_a.y) \
              == (pt_b.x-pt_a.x)*(pt_c.y-pt_a.y):
            if min(pt_a.x, pt_b.x) < pt_c.x < max(pt_a.x, pt_b.x):
                return True
        ## point C not on segment
        return False

    def _parse_environment(self, stream):
        """ Checks if attribute environment starts in the next line
            (marked by '{'). Environment only contains text elements
            interpreted as text.
            Returns a dictionary of attributes.
        """
        current_pos = stream.tell()
        typ, params = self._parse_command(stream)

        #go back to previous position when no environment in stream
        if typ != '{':
            stream.seek(current_pos)
            return None

        typ, params = self._parse_command(stream)

        attributes = {}
        while typ is not None:
            if typ == 'T':
                geda_text = self._parse_T(stream, params)

                if geda_text.is_attribute():
                    attributes[geda_text.attribute] = geda_text.content
                else:
                    log.warn(
                        "normal text in environemnt does not comply "
                        "with GEDA format specification: %s",
                        geda_text.content)

            typ, params = self._parse_command(stream)

        return attributes

    def calculate_nets(self):
        """ Calculate connected nets from previously stored segments
            and netpoints. The code has been adapted from the kiCAD
            parser since the definition of segments in the schematic
            file are similar. The segments are checked against
            existing nets and added when they touch it. For this
            to work, it is required that intersecting segments are
            divided prior to this method.

            Returns a list of valid nets and its net points.
        """
        nets = []

        # Iterate over the segments, removing segments when added to a net
        while self.segments:
            seg = self.segments.pop()  # pick a point

            net_name = ''
            pt_a, pt_b = seg
            if pt_a.point_id in self.net_names:
                net_name = self.net_names[pt_a.point_id]
            elif pt_b.point_id in self.net_names:
                net_name = self.net_names[pt_b.point_id]

            new_net = net.Net(net_name)
            new_net.connect(seg)
            found = True

            if net_name:
                new_net.attributes['_name'] = net_name

            while found:
                found = set()

                for seg in self.segments:  # iterate over segments
                    if new_net.connected(seg):  # segment touching the net
                        new_net.connect(seg)  # add the segment
                        found.add(seg)

                for seg in found:
                    self.segments.remove(seg)

            nets.append(new_net)

        # check if names are available for calculated nets
        for net_obj in nets:
            for point_id in net_obj.points:
                ## check for stored net names based on pointIDs
                if point_id in self.net_names:
                    net_obj.net_id = self.net_names[point_id]
                    net_obj.attributes['_name'] = self.net_names[point_id]

            if '_name' in net_obj.attributes:
                annotation = Annotation(
                    "{{_name}}",  ## annotation referencing attribute '_name'
                    0,
                    0,
                    self.conv_angle(0.0),
                    self.conv_bool(1),
                )
                net_obj.add_annotation(annotation)

        for net_obj in nets:
            if not net_obj.net_id:
                net_obj.net_id = min(net_obj.points)

        return nets

    def _open_file_or_zip(self, filename, mode='rU'):
        """
        Open the file with *filename* and return a file
        handle for it. If the current file is a ZIP file
        the filename will be treated as compressed file in
        this ZIP file.
        """
        if self.geda_zip is not None:
            temp_dir = tempfile.mkdtemp()
            self.geda_zip.extract(filename, temp_dir)
            filename = os.path.join(temp_dir, filename)

        return open(filename, mode)

    def add_text_to_component(self, component, geda_text):
        """
        Add the content of a ``GEDAText`` instance to the
        component. If *geda_text* contains ``refdes``, ``prefix``
        or ``suffix`` attributes it will be stored as special
        attribute in the component. *geda_text* that is not an
        attribute will be added as ``Label`` to the components
        body.
        """
        if geda_text.is_text():
            component.symbols[0].bodies[0].add_shape(geda_text.as_label())

        elif geda_text.attribute == '_refdes' \
             and '?' in geda_text.content:

            prefix, suffix = geda_text.content.split('?')
            component.add_attribute('_prefix', prefix)
            component.add_attribute('_suffix', suffix)
        else:
            component.add_attribute(geda_text.attribute, geda_text.content)

    def add_objects_to_component(self, component, objs):
        """
        Add a GEDA object to the component. Valid
        objects are subclasses of ``Shape``, ``Pin`` or
        ``GEDAText``. *objs* is expected to be an iterable
        and will be added to the correct component properties
        according to their type.
        """
        if not objs:
            return

        try:
            iter(objs)
        except TypeError:
            objs = [objs]

        for obj in objs:
            obj_cls = obj.__class__
            if issubclass(obj_cls, shape.Shape):
                component.symbols[0].bodies[0].add_shape(obj)
            elif issubclass(obj_cls, components.Pin):
                component.symbols[0].bodies[0].add_pin(obj)
            elif issubclass(obj_cls, GEDAText):
                self.add_text_to_component(component, obj)

    def add_text_to_design(self, design, geda_text):
        """
        Add the content of a ``GEDAText`` instance to the
        design. If *geda_text* contains ``use_license`` it will
        be added to the design's metadata ``license`` other
        attributes are added to ``design_attributes``.
        *geda_text* that is not an attribute will be added as
        ``Label`` to the components body.
        """
        if geda_text.is_text():
            design.add_shape(geda_text.as_label())
        elif geda_text.attribute == 'use_license':
            metadata = design.design_attributes.metadata
            metadata.license = geda_text.content
        else:
            design.design_attributes.add_attribute(
                geda_text.attribute,
                geda_text.content,
            )

    def add_objects_to_design(self, design, objs):
        """
        Add a GEDA object to the design. Valid
        objects are subclasses of ``Shape``, ``Pin`` or
        ``GEDAText``. *objs* is expected to be an iterable
        and will be added to the correct component properties
        according to their type.
        """
        if not objs:
            return

        try:
            iter(objs)
        except TypeError:
            objs = [objs]

        for obj in objs:
            obj_cls = obj.__class__
            if issubclass(obj_cls, shape.Shape):
                design.add_shape(obj)
            elif issubclass(obj_cls, components.Pin):
                design.add_pin(obj)
            elif issubclass(obj_cls, GEDAText):
                self.add_text_to_design(design, obj)

    def _parse_U(self, stream, params):
        """ Processing a bus instance with start end end coordinates
            at (x1, y1) and (x2, y2). *color* is ignored. *ripperdir*
            defines the direction in which the bus rippers are oriented
            relative to the direction of the bus.
        """
        x1, x2 = params['x1'], params['x2']
        y1, y2 = params['y1'], params['y2']

        ## ignore bus when length is zero
        if x1 == x2 and y1 == y2:
            return

        pta_x, pta_y = self.conv_coords(x1, y1)
        ptb_x, ptb_y = self.conv_coords(x2, y2)

        self.segments.add(
            (self.get_netpoint(pta_x, pta_y), self.get_netpoint(ptb_x, ptb_y)))

    def _parse_L(self, stream, params):
        """ Creates a Line object from the parameters in *params*. All
            style related parameters are ignored.
            Returns a Line object.
        """
        line_x1 = params['x1']
        line_x2 = params['x2']

        if self._is_mirrored_command(params):
            line_x1 = 0 - params['x1']
            line_x2 = 0 - params['x2']

        line = shape.Line(
            self.conv_coords(line_x1, params['y1']),
            self.conv_coords(line_x2, params['y2']),
        )
        ## store style data for line in 'style' dict
        self._save_parameters_to_object(line, params)
        return line

    def _parse_B(self, stream, params):
        """ Creates rectangle from gEDA box with origin in bottom left
            corner. All style related values are ignored.
            Returns a Rectangle object.
        """
        rect_x = params['x']
        if self._is_mirrored_command(params):
            rect_x = 0 - (rect_x + params['width'])

        rect = shape.Rectangle(self.x_to_px(rect_x),
                               self.y_to_px(params['y'] + params['height']),
                               self.to_px(params['width']),
                               self.to_px(params['height']))
        ## store style data for rect in 'style' dict
        self._save_parameters_to_object(rect, params)
        return rect

    def _parse_V(self, stream, params):
        """ Creates a Circle object from the gEDA parameters in *params. All
            style related parameters are ignored.
            Returns a Circle object.
        """
        vertex_x = params['x']
        if self._is_mirrored_command(params):
            vertex_x = 0 - vertex_x

        circle = shape.Circle(
            self.x_to_px(vertex_x),
            self.y_to_px(params['y']),
            self.to_px(params['radius']),
        )
        ## store style data for arc in 'style' dict
        self._save_parameters_to_object(circle, params)
        return circle

    def _parse_A(self, stream, params):
        """ Creates an Arc object from the parameter in *params*. All
            style related parameters are ignored.
            Returns Arc object.
        """
        arc_x = params['x']
        start_angle = params['startangle']
        sweep_angle = params['sweepangle']

        if self._is_mirrored_command(params):
            arc_x = 0 - arc_x

            start_angle = start_angle + sweep_angle
            if start_angle <= 180:
                start_angle = 180 - start_angle
            else:
                start_angle = (360 - start_angle) + 180

        arc = shape.Arc(
            self.x_to_px(arc_x),
            self.y_to_px(params['y']),
            self.conv_angle(start_angle),
            self.conv_angle(start_angle + sweep_angle),
            self.to_px(params['radius']),
        )
        ## store style data for arc in 'style' dict
        self._save_parameters_to_object(arc, params)
        return arc

    def _parse_T(self, stream, params):
        """ Parses text element and determins if text is a text object
            or an attribute.
            Returns a tuple (key, value). If text is an annotation key is None.
        """
        params['x'] = self.x_to_px(params['x'])
        params['y'] = self.y_to_px(params['y'])
        params['angle'] = self.conv_angle(params['angle'])

        geda_text = GEDAText.from_command(stream, params)

        ## text can have environemnt attached: parse & ignore
        self._parse_environment(stream)
        return geda_text

    def _parse_N(self, stream, params):
        """ Creates a segment from the command *params* and
            stores it in the global segment list for further
            processing in :py:method:divide_segments and
            :py:method:calculate_nets. It also extracts the
            net name from the attribute environment if
            present.
        """
        ## store segement for processing later
        x1, y1 = self.conv_coords(params['x1'], params['y1'])
        x2, y2 = self.conv_coords(params['x2'], params['y2'])

        ## store segment points in global point list
        pt_a = self.get_netpoint(x1, y1)
        pt_b = self.get_netpoint(x2, y2)

        ## add segment to global list for later processing
        self.segments.add((pt_a, pt_b))

        attributes = self._parse_environment(stream)
        if attributes is not None:
            ## create net with name in attributes
            if '_netname' in attributes:
                net_name = attributes['_netname']
                if net_name not in self.net_names.values():
                    self.net_names[pt_a.point_id] = net_name

    def _parse_P(self, stream, params, pinnumber=0):
        """ Creates a Pin object from the parameters in *param* and
            text attributes provided in the following environment. The
            environment is enclosed in ``{}`` and is required. If no
            attributes can be extracted form *stream* an GEDAError
            is raised.
            The *pin_id* is retrieved from the 'pinnumber' attribute and
            all other attributes are ignored. The conneted end of the
            pin is taken from the 'whichend' parameter as defined in
            the gEDA documentation.

            Returns a Pin object.
        """
        ## pin requires an attribute enviroment, so parse it first
        attributes = self._parse_environment(stream)

        if attributes is None:
            log.warn('mandatory pin attributes missing')
            attributes = {
                '_pinnumber': pinnumber,
            }

        if '_pinnumber' not in attributes:
            attributes['_pinnumber'] = pinnumber
            log.warn("mandatory attribute '_pinnumber' not assigned to pin")

        whichend = params['whichend']

        pin_x1, pin_x2 = params['x1'], params['x2']
        if self._is_mirrored_command(params):
            pin_x1 = 0 - pin_x1
            pin_x2 = 0 - pin_x2

        ## determine wich end of the pin is the connected end
        ## 0: first point is connector
        ## 1: second point is connector
        if whichend == 0:
            connect_end = self.conv_coords(pin_x1, params['y1'])
            null_end = self.conv_coords(pin_x2, params['y2'])
        else:
            null_end = self.conv_coords(pin_x1, params['y1'])
            connect_end = self.conv_coords(pin_x2, params['y2'])

        label = None
        if '_pinlabel' in attributes:
            label = shape.Label(connect_end[0], connect_end[1],
                                attributes.get('_pinlabel'), 'left', 0.0)

        pin = components.Pin(
            attributes['_pinnumber'],  #pin number
            null_end,
            connect_end,
            label=label)
        ## store style parameters in shape's style dict
        self._save_parameters_to_object(pin, params)
        return pin

    def _parse_C(self, stream, params):
        """
        Parse component command 'C'. *stream* is the file stream
        pointing to the line after the component command. *params*
        are the parsed parameters from the component command.
        The method checks if component is a title and ignores it
        if that is the case due to previous processing. If the
        component is a busripper, it is converted into a net
        segment. Otherwise, the component is parsed as a regular
        component and added to the library and design.
        """
        ## ignore title since it only defines the blueprint frame
        if params['basename'].startswith('title'):
            self._parse_environment(stream)

        ## busripper are virtual components that need separate
        ## processing
        elif 'busripper' in params['basename']:
            self.segments.add(self._create_ripper_segment(params))

            ## make sure following environments are ignored
            self.skip_embedded_section(stream)
            self._parse_environment(stream)
        else:
            self._parse_component(stream, params)

    def _parse_H(self, stream, params):
        """ Parses a SVG-like path provided path into a list
            of simple shapes. The gEDA formats allows only line
            and curve segments with absolute coordinates. Hence,
            shapes are either Line or BezierCurve objects.
            The method processes the stream data according to
            the number of lines in *params*.
            Returns a list of Line and BezierCurve shapes.
        """
        params['extra_id'] = self.path_counter.next()
        num_lines = params['num_lines']
        mirrored = self._is_mirrored_command(params)
        command = stream.readline().strip().split(self.DELIMITER)

        if command[0] != 'M':
            raise GEDAError('found invalid path in gEDA file')

        def get_coords(string, mirrored):
            """ Get coordinates from string with comma-sparated notation."""
            x, y = [int(value) for value in string.strip().split(',')]

            if mirrored:
                x = -x

            return (self.x_to_px(x), self.y_to_px(y))

        shapes = []
        current_pos = initial_pos = (get_coords(command[1], mirrored))

        ## loop over the remaining lines of commands (after 'M')
        for _ in range(num_lines - 1):
            command = stream.readline().strip().split(self.DELIMITER)

            ## draw line from current to given position
            if command[0] == 'L':
                assert (len(command) == 2)
                end_pos = get_coords(command[1], mirrored)

                shape_ = shape.Line(current_pos, end_pos)
                current_pos = end_pos

            ## draw curve from current to given position
            elif command[0] == 'C':
                assert (len(command) == 4)
                control1 = get_coords(command[1], mirrored)
                control2 = get_coords(command[2], mirrored)
                end_pos = get_coords(command[3], mirrored)

                shape_ = shape.BezierCurve(control1, control2, current_pos,
                                           end_pos)
                current_pos = end_pos

            ## end of sub-path, straight line from current to initial position
            elif command[0] in ['z', 'Z']:
                shape_ = shape.Line(current_pos, initial_pos)

            else:
                raise GEDAError("invalid command type in path '%s'" %
                                command[0])

            ## store style parameters in shape's style dict
            self._save_parameters_to_object(shape_, params)
            shapes.append(shape_)

        return shapes

    def _save_parameters_to_object(self, obj, params):
        """
        Save all ``style`` and ``extra`` parameters to the
        objects ``styles`` dictionary. If *obj* does not have
        a ``styles`` property, a ``GEDAError`` is raised.
        """
        parameter_types = [
            geda_commands.GEDAStyleParameter.TYPE,
            geda_commands.GEDAExtraParameter.TYPE,
        ]

        try:
            for key, value in params.items():
                if key.split('_')[0] in parameter_types:
                    obj.styles[key] = value
        except AttributeError:
            log.exception(
                "tried saving style data to '%s' without styles dict.",
                obj.__class__.__name__)

    def _parse_command(self, stream, move_to=None):
        """ Parse the next command in *stream*. The object type is check
            for validity and its parameters are parsed and converted to
            the expected typs in the parsers lookup table. If *move_to*
            is provided it is used to translate all coordinates into by
            the given coordinate.
            Returns a tuple (*object type*, *parameters*) where *parameters*
                is a dictionary of paramter name and value.

            Raises GEDAError when object type is not known.
        """
        line = stream.readline()

        while line.startswith('#') or line == '\n':
            line = stream.readline()

        command_data = line.strip().split(self.DELIMITER)

        if len(command_data[0]) == 0 or command_data[0] in [']', '}']:
            return None, []

        object_type, command_data = command_data[0].strip(), command_data[1:]

        if object_type not in self.OBJECT_TYPES:
            raise GEDAError("unknown type '%s' in file" % object_type)

        params = {}
        geda_command = self.OBJECT_TYPES[object_type]
        for idx, parameter in enumerate(geda_command.parameters()):
            if idx >= len(command_data):
                ## prevent text commands of version 1 from breaking
                params[parameter.name] = parameter.default
            else:
                datatype = parameter.datatype
                params[parameter.name] = datatype(command_data[idx])

        assert (len(params) == len(geda_command.parameters()))

        if move_to is not None:
            ## element in EMBEDDED component need to be moved
            ## to origin (0, 0) from component origin
            if object_type in ['T', 'B', 'C', 'A']:
                params['x'] = params['x'] - move_to[0]
                params['y'] = params['y'] - move_to[1]
            elif object_type in ['L', 'P']:
                params['x1'] = params['x1'] - move_to[0]
                params['y1'] = params['y1'] - move_to[1]
                params['x2'] = params['x2'] - move_to[0]
                params['y2'] = params['y2'] - move_to[1]

        return object_type, params

    @classmethod
    def to_px(cls, value):
        """ Converts value in MILS to pixels using the parsers
            scale factor.
            Returns an integer value converted to pixels.
        """
        return int(value / cls.SCALE_FACTOR)

    def x_to_px(self, x_mils):
        """ Convert *px* from MILS to pixels using the scale
            factor and translating it allong the X-axis in
            offset.

            Returns translated and converted X coordinate.
        """
        return int(float(x_mils - self.offset.x) / self.SCALE_FACTOR)

    def y_to_px(self, y_mils):
        """ Convert *py* from MILS to pixels using the scale
            factor and translating it allong the Y-axis in
            offset.

            Returns translated and converted Y coordinate.
        """
        return int(float(y_mils - self.offset.y) / self.SCALE_FACTOR)

    def conv_coords(self, orig_x, orig_y):
        """ Converts coordinats *orig_x* and *orig_y* from MILS
            to pixel units based on scale factor. The converted
            coordinates are in multiples of 10px.
        """
        orig_x, orig_y = int(orig_x), int(orig_y)
        return (self.x_to_px(orig_x), self.y_to_px(orig_y))

    @staticmethod
    def conv_bool(value):
        """ Converts *value* into string representing boolean
            'true' or 'false'. *value* can be of any numeric or
            boolean type.
        """
        if value in ['true', 'false']:
            return value
        return str(bool(int(value)) is True).lower()

    @staticmethod
    def conv_angle(angle):
        """ Converts *angle* (in degrees) to pi radians. gEDA
            sets degree angles counter-clockwise whereas upverter
            uses pi radians clockwise. Therefore the direction of
            *angle* is therefore adjusted first.
        """
        angle = angle % 360.0
        if angle > 0:
            angle = abs(360 - angle)
        return round(angle / 180.0, 1)
Exemple #46
0
class JSON:
    """ The Open JSON Format Parser
    This is mostly for sanity checks, it reads in the Open JSON format,
    and then outputs it. """

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


    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an openjson file """
        with open(filename, 'r') as f:
            data = f.read()
        confidence = 0
        if 'component_instances' in data:
            confidence += 0.3
        if 'design_attributes' in data:
            confidence += 0.6
        return confidence


    def parse(self, filename):
        """ Parse the openjson file into the core. """
        with open(filename) as f:
            read = json.loads(f.read())

        self.parse_component_instances(read.get('component_instances'))
        self.parse_components(read.get('components'))
        if read.get('shapes') is not None:
            self.parse_sch_shapes(read.get('shapes'))
        self.parse_design_attributes(read.get('design_attributes'))
        self.parse_nets(read.get('nets'))
        self.parse_version(read.get('version'))

        return self.design


    def parse_version(self, version):
        """ Extract the file version. """
        file_version = version.get('file_version')
        exporter = version.get('exporter')
        self.design.set_version(file_version, exporter)


    def parse_component_instances(self, component_instances):
        """ Extract the component instances. """
        for instance in component_instances:
            # Get instance_id, library_id and symbol_index
            instance_id = instance.get('instance_id')
            library_id = instance.get('library_id')
            symbol_index = int(instance.get('symbol_index'))
            # Make the ComponentInstance()
            inst = ComponentInstance(instance_id, library_id, symbol_index)

            # Get the SymbolAttributes
            for symbol_attribute in instance.get('symbol_attributes'):
                attr = self.parse_symbol_attribute(symbol_attribute)
                inst.add_symbol_attribute(attr)

            # Get the Attributes
            for key, value in instance.get('attributes').items():
                inst.add_attribute(key, value)

            # Add the ComponentInstance
            self.design.add_component_instance(inst)


    def parse_symbol_attribute(self, symbol_attribute):
        """ Extract attributes from a symbol. """
        x = int(symbol_attribute.get('x') or 0)
        y = int(symbol_attribute.get('y') or 0)

        rotation = float(symbol_attribute.get('rotation'))

        # Make SymbolAttribute
        symbol_attr = SymbolAttribute(x, y, rotation)

        # Add Annotations
        for annotation in symbol_attribute.get('annotations'):
            anno = self.parse_annotation(annotation)
            symbol_attr.add_annotation(anno)

        # Return SymbolAttribute to be added to it's ComponentInstance
        return symbol_attr


    def parse_annotation(self, annotation):
        """ Extract an annotation. """
        value = annotation.get('value')
        x = int(annotation.get('x'))
        y = int(annotation.get('y'))
        rotation = float(annotation.get('rotation'))
        visible = annotation.get('visible')
        if visible is not None and visible.lower() == 'false':
            visible = 'false'
        else:
            visible = 'true'
        return Annotation(value, x, y, rotation, visible)


    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_sch_shapes(self, shapes):
        """ Extract shapes drawn directly on the schematic. """
        for sh in shapes:
            self.design.add_shape(self.parse_shape(sh))


    def parse_symbol(self, symbol):
        """ Extract a symbol. """
        symb = Symbol()
        for body in symbol.get('bodies'):
            bdy = self.parse_body(body)
            symb.add_body(bdy)
        return symb


    def parse_body(self, body):
        """ Extract a body of a symbol. """
        bdy = Body()
        for pin in body.get('pins'):
            parsed_pin = self.parse_pin(pin)
            bdy.add_pin(parsed_pin)
        for shape in body.get('shapes'):
            parsed_shape = self.parse_shape(shape)
            bdy.add_shape(parsed_shape)
        return bdy


    def parse_pin(self, pin):
        """ Extract a pin of a body. """
        pin_number = pin.get('pin_number')
        p1 = self.parse_point(pin.get('p1'))
        p2 = self.parse_point(pin.get('p2'))
        parsed_pin = Pin(pin_number, p1, p2)
        if pin.get('label') is not None:
            parsed_pin.label = self.parse_label(pin.get('label'))
        parsed_pin.styles = pin.get('styles') or {}
        return parsed_pin

    def parse_point(self, point):
        """ Extract a point. """
        x = int(point.get('x'))
        y = int(point.get('y'))
        return Point(x, y)

    def parse_label(self, label):
        """ Extract a label. """
        x = int(label.get('x'))
        y = int(label.get('y'))
        text = label.get('text')
        align = label.get('align')
        rotation = float(label.get('rotation'))
        parsed_label = Label(x, y, text, align, rotation)
        parsed_label.styles = label.get('styles') or {}
        return parsed_label

    def parse_shape(self, shape):
        """ Extract a shape. """
        # pylint: disable=R0914
        # pylint: disable=R0911

        typ = shape.get('type')
        if 'rectangle' == typ:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            height = int(shape.get('height'))
            width = int(shape.get('width'))
            parsed_shape = Rectangle(x, y, width, height)
        elif 'rounded_rectangle' == typ:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            height = int(shape.get('height'))
            width = int(shape.get('width'))
            radius = int(shape.get('radius'))
            parsed_shape = RoundedRectangle(x, y, width, height, radius)
        elif 'arc' == typ:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            start_angle = float(shape.get('start_angle'))
            end_angle = float(shape.get('end_angle'))
            radius = int(shape.get('radius'))
            parsed_shape = Arc(x, y, start_angle, end_angle, radius)
        elif 'circle' == typ:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            radius = int(shape.get('radius'))
            parsed_shape = Circle(x, y, radius)
        elif 'label' == typ:
            x = int(shape.get('x'))
            y = int(shape.get('y'))
            rotation = float(shape.get('rotation'))
            text = shape.get('text')
            align = shape.get('align')
            parsed_shape = Label(x, y, text, align, rotation)
        elif 'line' == typ:
            p1 = self.parse_point(shape.get('p1'))
            p2 = self.parse_point(shape.get('p2'))
            parsed_shape = Line(p1, p2)
        elif 'polygon' == typ:
            parsed_shape = Polygon()
            for point in shape.get('points'):
                parsed_shape.add_point(self.parse_point(point))
        elif 'bezier' == typ:
            control1 = self.parse_point(shape.get('control1'))
            control2 = self.parse_point(shape.get('control2'))
            p1 = self.parse_point(shape.get('p1'))
            p2 = self.parse_point(shape.get('p2'))
            parsed_shape = BezierCurve(control1, control2, p1, p2)

        parsed_shape.styles = shape.get('styles') or {}
        parsed_shape.attributes = shape.get('attributes') or {}
        return parsed_shape

    def parse_design_attributes(self, design_attributes):
        """ Extract design attributes. """
        attrs = DesignAttributes()
        # Get the Annotations
        for annotation in design_attributes.get('annotations'):
            anno = self.parse_annotation(annotation)
            attrs.add_annotation(anno)

        # Get the Attributes
        for key, value in design_attributes.get('attributes').items():
            attrs.add_attribute(key, value)

        # Get the Metadata
        meta = self.parse_metadata(design_attributes.get('metadata'))
        attrs.set_metadata(meta)
        self.design.set_design_attributes(attrs)


    def parse_metadata(self, metadata):
        """ Extract design meta-data. """
        meta = Metadata()
        meta.set_name(metadata.get('name'))
        meta.set_license(metadata.get('license'))
        meta.set_owner(metadata.get('owner'))
        meta.set_updated_timestamp(metadata.get('updated_timestamp'))
        meta.set_design_id(metadata.get('design_id'))
        meta.set_description(metadata.get('description'))
        meta.set_slug(metadata.get('slug'))
        for attached_url in metadata.get('attached_urls'):
            meta.add_attached_url(attached_url)
        return meta


    def parse_nets(self, nets):
        """ Extract nets. """
        for net in nets:
            net_id = net.get('net_id')
            ret_net = Net(net_id)
            # Add Annotations
            for annotation in net.get('annotations'):
                anno = self.parse_annotation(annotation)
                ret_net.add_annotation(anno)
            # Get the Attributes
            for key, value in net.get('attributes').items():
                ret_net.add_attribute(key, value)
            # Get the Points
            for net_point in net.get('points'):
                npnt = self.parse_net_point(net_point)
                ret_net.add_point(npnt)
            self.design.add_net(ret_net)


    def parse_net_point(self, net_point):
        """ Extract a net point. """
        point_id = net_point.get('point_id')
        x = int(net_point.get('x'))
        y = int(net_point.get('y'))
        npnt = NetPoint(point_id, x, y)
        # Get the connected points
        for point in net_point.get('connected_points'):
            npnt.add_connected_point(point)
        # Get the ConnectedComponents
        for connectedcomponent in net_point.get('connected_components'):
            conn_comp = self.parse_connected_component(connectedcomponent)
            npnt.add_connected_component(conn_comp)
        return npnt


    def parse_connected_component(self, connectedcomponent):
        """ Extract a connected component. """
        instance_id = connectedcomponent.get('instance_id')
        pin_number = connectedcomponent.get('pin_number')
        return ConnectedComponent(instance_id, pin_number)
Exemple #47
0
 def setUp(self):
     """ Setup the test case. """
     self.des = Design()
    def parse(self, inputfile):
        """ Parse a gEDA file into a design.

            Returns the design corresponding to the gEDA file.
        """
        inputfiles = []

        ## check if inputfile is in ZIP format
        if zipfile.is_zipfile(inputfile):
            self.geda_zip = zipfile.ZipFile(inputfile)
            for filename in self.geda_zip.namelist():
                if filename.endswith('.sch'):
                    inputfiles.append(filename)
        else:
            inputfiles = [inputfile]

        self.design = Design()

        ## parse frame data of first schematic to extract
        ## page size (assumes same frame for all files)
        with self._open_file_or_zip(inputfiles[0]) as stream:
            self._check_version(stream)

            for line in stream.readlines():
                if 'title' in line and line.startswith('C'):
                    obj_type, params = self._parse_command(StringIO(line))
                    assert (obj_type == 'C')

                    params['basename'], _ = os.path.splitext(
                        params['basename'], )

                    log.debug("using title file: %s", params['basename'])

                    self._parse_title_frame(params)

        ## store offset values in design attributes
        self.design.design_attributes.attributes.update({
            '_geda_offset_x':
            str(self.offset.x),
            '_geda_offset_y':
            str(self.offset.y),
            '_geda_frame_width':
            str(self.frame_width),
            '_geda_frame_height':
            str(self.frame_height),
        })

        for filename in inputfiles:
            f_in = self._open_file_or_zip(filename)
            self._check_version(f_in)

            self.parse_schematic(f_in)

            basename, _ = os.path.splitext(os.path.basename(filename))
            self.design.design_attributes.metadata.set_name(basename)

            ## modify offset for next page to be shifted to the right
            self.offset.x = self.offset.x - self.frame_width

            f_in.close()

        return self.design
 def __init__(self):
     self.design = Design()
 def test_layers(self):
     """ Capture absence of layers. """
     design = Design()
     design.layout = Layout()
     writer = Writer()
     writer.write(design)
Exemple #51
0
class DesignTests(unittest.TestCase):
    """ The tests of the core module design feature """
    def setUp(self):
        """ Setup the test case. """
        self.des = Design()

    def tearDown(self):
        """ Teardown the test case. """
        pass

    def test_create_new_design(self):
        """ Test the creation of a new empty design. """
        self.assertEqual(len(self.des.nets), 0)

    def test_empty_bounds(self):
        '''bounds() on an empty design is to include just the origin'''
        for point in self.des.bounds():
            self.assertEqual(point.x, 0)
            self.assertEqual(point.y, 0)

    def test_bounds_nets(self):
        '''Test bounds() with just the design's nets'''
        leftnet = Net('foo1')
        topnet = Net('foo2')
        rightnet = Net('foo3')
        botnet = Net('foo4')
        # limits minx=2, miny=1, maxx=7, maxy=9
        mkbounds(leftnet, 2, 3, 3, 3)
        mkbounds(topnet, 3, 1, 3, 3)
        mkbounds(rightnet, 3, 3, 7, 3)
        mkbounds(botnet, 3, 3, 3, 9)
        self.des.add_net(topnet)
        self.des.add_net(rightnet)
        self.des.add_net(leftnet)
        self.des.add_net(botnet)

        top_left, btm_right = self.des.bounds()
        self.assertEqual(top_left.x, 2)
        self.assertEqual(top_left.y, 1)
        self.assertEqual(btm_right.x, 7)
        self.assertEqual(btm_right.y, 9)

    def test_bounds_annots(self):
        '''Test bounds() with just Annotations added as design attributes'''
        left = Annotation('foo1', 3, 3, 0, True)
        top = Annotation('foo2', 3, 3, 0, True)
        right = Annotation('foo3', 3, 3, 0, True)
        bot = Annotation('foo4', 3, 3, 0, True)
        mkbounds(left, 2, 3, 3, 3)
        mkbounds(top, 3, 2, 3, 3)
        mkbounds(right, 3, 3, 5, 3)
        mkbounds(bot, 3, 3, 3, 6)
        for anno in (left, right, bot, top):
            self.des.design_attributes.add_annotation(anno)

        top_left, btm_right = self.des.bounds()
        self.assertEqual(top_left.x, 2)
        self.assertEqual(top_left.y, 2)
        self.assertEqual(btm_right.x, 5)
        self.assertEqual(btm_right.y, 6)

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

    def test_bounds_neg_coords(self):
        '''Test bounds() when the schematic is all negative coordinates'''
        net = Net('foo')
        mkbounds(net, -1, -2, -3, -4)
        self.des.add_net(net)

        top_left, btm_right = self.des.bounds()
        self.assertEqual(top_left.x, -3)
        self.assertEqual(top_left.y, -4)
        self.assertEqual(btm_right.x, -1)
        self.assertEqual(btm_right.y, -2)

    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, False))
        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)
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
     """

    MULT = 90 / 25.4  # mm to 90 dpi

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

        # map components to gate names to symbol indices
        self.cpt2gate2symbol_index = 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)

        return self.design

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

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

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

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

        for gate in get_subattr(deviceset, 'gates.gate'):
            symbol = Symbol()
            cpt.add_symbol(symbol)
            self.cpt2gate2symbol_index[cpt][gate.name] = len(cpt.symbols) - 1
            symbol.add_body(self.make_body_from_symbol(lib, gate.symbol))

        return cpt

    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 make_component_instances(self, root):
        """ Construct openjson component instances for an eagle model. """

        parts = dict(
            (p.name, p)
            for p in get_subattr(root, 'drawing.schematic.parts.part', ()))

        for sheet in get_subattr(root, 'drawing.schematic.sheets.sheet', ()):
            for instance in get_subattr(sheet, 'instances.instance', ()):
                inst = self.make_component_instance(parts, instance)
                self.design.add_component_instance(inst)

    def make_component_instance(self, parts, instance):
        """ Construct an openjson component instance for an eagle instance. """

        part = parts[instance.part]

        library_id = part.library + ':' + part.deviceset

        # TODO pick correct symbol index
        inst = ComponentInstance(instance.part, library_id, 0)

        # TODO handle mirror
        # TODO handle smashed?
        attr = SymbolAttribute(self.make_length(instance.x),
                               self.make_length(instance.y),
                               self.make_angle(instance.rot or '0'))

        inst.add_symbol_attribute(attr)

        return inst

    def make_length(self, value):
        """ Make an openjson length measurement from an eagle length. """

        return int(round(float(value) * self.MULT))

    def make_angle(self, value):
        """ Make an openjson angle measurement from an eagle angle. """

        return float(value.lstrip('MSR')) / 180
 def __init__(self):
     self.design = Design()