Exemplo n.º 1
0
 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)
Exemplo n.º 2
0
 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)
Exemplo n.º 3
0
    def parse(self, infile='.'):
        """ Parse tokens from gerber files into a design. """
        zip_ = infile.endswith('zip')
        openarchive = zip_ and ZipFile or 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 archive
            else:
                archive = openarchive(infile)
                batch = zip_ and archive.namelist or archive.getnames
                batch_member = zip_ and archive.open or 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, filename):
     """ Parse an Altium file into a design """
     design = Design()
     f = open(filename, "w")
     #TODO: Read!
     f.close()
     return design
Exemplo n.º 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

        # 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
Exemplo n.º 6
0
    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) 
        stream = self._open_file_or_zip(inputfiles[0])
        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'], dummy = 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, dummy = 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
Exemplo n.º 7
0
 def parse(self, filename):
     """ Parse a kicad file into a design """
     # Rough'n'dirty parsing, assume nothing useful comes before the description
     circuit = Design()
     segments = set() # each wire segment
     junctions = set() # wire junction point (connects all wires under it)
     f = open(filename)
     # Read until the end of the description
     line = ""
     while line.strip() != "$EndDescr":
         line = f.readline()
     # Now parse wires and components, ignore connections, we get connectivity from wire segments
     line = f.readline()
     while line != '': # loop til end of file
         element = line.split()[0] # whats next on the list
         if element == "Wire": # Wire Segment, coords on 2nd line
             line = f.readline() # Read the second line with the coordinates
             x1,y1,x2,y2 = [int(i) for i in line.split()]
             if not(x1 == x2 and y1 == y2): # ignore zero-length segments
                 segments.add(((x1,y1),(x2,y2)))
         elif element == "Connection": # Store these to apply later
             x,y = [int(i) for i in line.split()[2:4]]
             junctions.add((x,y))
         elif element == "$Comp": # Component
             # TODO(ajray): probably should can by leading letter, instead of assuming they're what we expect
             compnames = f.readline()
             name,reference = compnames.split()[1:3]
             unused_timestamp = f.readline()
             positions = f.readline()
             compx,compy = [int(i) for i in f.readline()[1:3]]
             # TODO(ajray): ignore all the fields for now, probably could make these annotations
             line = f.readline()
             while line.strip() != "$EndComp":
                 line = f.readline()
         line = f.readline()
     segments = self.divide(segments,junctions)
     nets = self.calc_nets(segments)
     circuit.nets = nets
     return circuit
Exemplo n.º 8
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)

        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)

        f = open(filename)

        libs = []
        line = f.readline().strip()

        # parse the library references
        while 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()

        f.close()

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

        return design
Exemplo n.º 9
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
Exemplo n.º 10
0
 def test_layers(self):
     """ Capture absence of layers. """
     design = Design()
     design.layout = Layout()
     writer = Writer()
     writer.write(design)
Exemplo n.º 11
0
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()

        # 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


    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an fritzing file """
        f = open(filename, 'r')
        data = f.read(4096)
        f.close()
        confidence = 0
        if 'fritzingVersion' in data:
            confidence += 0.9
        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 filename.endswith('.fzz'):
            self.fzz_zipfile = zipfile.ZipFile(filename)
            fz_file = self.fzz_zipfile.open(basename(filename[:-1]))
        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 connect in view.findall('connectors/connector/connects/connect'):
            if connect.get('layer') == 'breadboardbreadboard':
                return

        for i, connector in enumerate(view.findall('connectors/connector'), 1):
            cid = connector.get('connectorId')
            self.points[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, idref, 0)

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

        self.component_instances[index] = compinst


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

        todo = set(self.connects) # set([(index, cid)])
        points = {} # (x, y) -> NetPoint
        nets = []

        def get_point(point):
            """ Return a new or existing NetPoint for an (x,y) point """
            if point not in points:
                points[point] = NetPoint('%da%d' % point, point[0], point[1])
            return points[point]

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

            todo.discard(main_key)

            main_point = get_point(self.points[main_key])
            net.add_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 self.points:
                    connect(net, main_point, get_point(self.points[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:
            net = Net(str(len(nets)))
            nets.append(net)
            remaining = [todo.pop()]

            while remaining:
                remaining.extend(update_net(net, remaining.pop(0)))

        return nets
Exemplo n.º 12
0
    def parse(self, filename, library_filename=None):
        """ Parse a kicad file into a design """

        # Rough'n'dirty parsing, assume nothing useful comes before
        # the description
        circuit = 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):
                self.parse_library(library_filename, circuit)

        f = open(filename)

        # Read until the end of the description
        while f.readline().strip() != "$EndDescr":
            pass

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

        line = f.readline()
        while line:
            element = line.split()[0]  # whats next on the list
            if element == "Wire":  # Wire Segment, coords on 2nd line
                x1, y1, x2, y2 = [int(i) for i in f.readline().split()]
                if not (x1 == x2 and y1 == y2):  # ignore zero-length segments
                    segments.add(((x1, y1), (x2, y2)))
            elif element == "Connection":  # Store these to apply later
                x, y = [int(i) for i in line.split()[2:4]]
                junctions.add((x, y))
            elif element == "$Comp":  # Component Instance
                # name & reference
                prefix, name, reference = f.readline().split()
                assert prefix == 'L'

                # timestamp
                prefix, _ = f.readline().split(None, 1)
                assert prefix == 'U'

                # position
                prefix, compx, compy = f.readline().split()
                assert prefix == 'P'
                compx, compy = int(compx), int(compy)

                # TODO(ajray): ignore all the fields for now, probably
                # could make these annotations

                while f.readline().strip() not in ("$EndComp", ''):
                    pass

                # TODO: calculate rotation
                inst = ComponentInstance(reference, name, 0)
                inst.add_symbol_attribute(SymbolAttribute(compx, compy, 0))

                circuit.add_component_instance(inst)

            line = f.readline()

        f.close()

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

        return circuit
Exemplo n.º 13
0
 def setUp(self):
     """ Setup the test case. """
     self.des = Design()
Exemplo n.º 14
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))
            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))
        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)
Exemplo n.º 15
0
    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 segement (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 dummy 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)
Exemplo n.º 16
0
    def parse(self, filename, library_filename=None):
        """ Parse a kicad file into a design """

        # Rough'n'dirty parsing, assume nothing useful comes before
        # the description
        circuit = 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):
                self.parse_library(library_filename, circuit)

        f = open(filename)

        # Read until the end of the description
        while f.readline().strip() != "$EndDescr":
            pass

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

        line = f.readline()
        while line:
            element = line.split()[0]  # whats next on the list
            if element == "Wire":  # Wire Segment, coords on 2nd line
                x1, y1, x2, y2 = [int(i) for i in f.readline().split()]
                if not (x1 == x2 and y1 == y2):  # ignore zero-length segments
                    segments.add(((x1, y1), (x2, y2)))
            elif element == "Connection":  # Store these to apply later
                x, y = [int(i) for i in line.split()[2:4]]
                junctions.add((x, y))
            elif element == "$Comp":  # Component Instance
                # name & reference
                prefix, name, reference = f.readline().split()
                assert prefix == "L"

                # timestamp
                prefix, _ = f.readline().split(None, 1)
                assert prefix == "U"

                # position
                prefix, compx, compy = f.readline().split()
                assert prefix == "P"
                compx, compy = int(compx), int(compy)

                # TODO(ajray): ignore all the fields for now, probably
                # could make these annotations

                while f.readline().strip() not in ("$EndComp", ""):
                    pass

                # TODO: calculate rotation
                inst = ComponentInstance(reference, name, 0)
                inst.add_symbol_attribute(SymbolAttribute(compx, compy, 0))

                circuit.add_component_instance(inst)

            line = f.readline()

        f.close()

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

        return circuit
Exemplo n.º 17
0
 def __init__(self):
     self.design = Design()
Exemplo n.º 18
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 """
        f = open(filename, 'r')
        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. """
        f = open(filename)
        read = json.loads(f.read())
        f.close()

        self.parse_component_instances(read.get('component_instances'))
        self.parse_components(read.get('components'))
        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'))
        y = int(symbol_attribute.get('y'))
        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_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'))
        if pin.get('label') is not None:
            pin_label = self.parse_label(pin.get('label'))
            return Pin(pin_number, p1, p2, pin_label)
        return Pin(pin_number, p1, p2)


    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'))
        return Label(x, y, text, align, rotation)

    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'))
            return 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'))
            return 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'))
            return 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'))
            return 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')
            return Label(x, y, text, align, rotation)
        elif 'line' == typ:
            p1 = self.parse_point(shape.get('p1'))
            p2 = self.parse_point(shape.get('p2'))
            return Line(p1, p2)
        elif 'polygon' == typ:
            poly = Polygon()
            for point in shape.get('points'):
                poly.add_point(self.parse_point(point))
            return poly
        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'))
            return BezierCurve(control1, control2, p1, p2)


    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)
Exemplo n.º 19
0
class GEDA:
    """ The GEDA Format Parser """

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


    OBJECT_TYPES = { 
        'v': ( # gEDA version
            ('version', int, None),
            ('fileformat_version', int, None),
        ),
        'C': ( #component
            ('x', int, None),
            ('y', int, None),
            ('selectable', int, None),
            ('angle', int, None),
            ('mirror', int, None),
            ('basename', str, None),
        ),
        'N': ( # net segment
            ('x1', int, None),
            ('y1', int, None),
            ('x2', int, None),
            ('y2', int, None),
        ),
        'U': ( # bus (only graphical aid, not a component)
            ('x1', int, None),
            ('y1', int, None),
            ('x2', int, None),
            ('y2', int, None),
            ('color', int, None),
            ('ripperdir', int, None),
        ),
        'T': ( # text or attribute (context)
            ('x', int, None),
            ('y', int, None),
            ('color', int, None),
            ('size', int, None),
            ('visibility', int, None),
            ('show_name_value', int, None),
            ('angle', int, None),
            ('alignment', int, None),
            ('num_lines', int, 1),
        ),
        'P': ( # pin (in sym)
            ('x1', int, None),
            ('y1', int, None),
            ('x2', int, None),
            ('y2', int, None),
            ('color', int, None),
            ('pintype', int, None),
            ('whichend', int, None),
        ),
        'L': ( # line
            ('x1', int, None),
            ('y1', int, None),
            ('x2', int, None),
            ('y2', int, None),
        ),
        'B': ( # box
            ('x', int, None),
            ('y', int, None),
            ('width', int, None),
            ('height', int, None),
        ),
        'V': ( # circle
            ('x', int, None),
            ('y', int, None),
            ('radius', int, None),
        ),
        'A': ( # arc
            ('x', int, None),
            ('y', int, None),
            ('radius', int, None),
            ('startangle', int, None),
            ('sweepangle', int, None),
        ),
        'H': ( # SVG-like path
            ('color', int, None),
            ('width', int, None),
            ('capstyle', int, None),
            ('dashstyle', int, None),
            ('dashlength', int, None),
            ('dashspace', int, None),
            ('filltype', int, None),
            ('fillwidth', int, None),
            ('angle1', int, None),
            ('pitch1', int, None),
            ('angle2', int, None),
            ('pitch2', int, None),
            ('num_lines', int, None),         
        ),
        ## environments
        '{': [], 
        '}': [], # attributes
        '[': [], 
        ']': [], # embedded component
        ## valid types but are ignored
        'G': [], #picture
    }

    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 

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

        symbol_dirs += [
            'library/geda',
        ]

        self.instance_counter = itertools.count()
        self.known_symbols = {}

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

        self.known_symbols = find_symbols(symbol_dirs)
        self.geda_zip = None

    @staticmethod
    def auto_detect(filename):
        """ Return our confidence that the given file is an geda schematic """
        f = open(filename, 'rU')
        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) 
        stream = self._open_file_or_zip(inputfiles[0])
        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'], dummy = 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, dummy = 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_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 segement (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 dummy 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)

    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

        stream = open(filename, 'rU')

        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 dummy 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 
        
        stream.close()

    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, dummy = os.path.splitext(params['basename'])

        component_name = basename
        if params['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)
                    ## parse optional attached environment before continuing
                    self._parse_environment(stream)
                    return None, None

                ## requires parsing of referenced symbol file
                f_in = open(self.known_symbols[basename], "rU")
                self._check_version(f_in)

                component = self.parse_component_data(f_in, params) 

                f_in.close()
                        
            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'])
        )
        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, dummy = self._parse_command(stream)
        if typ != 'v':
            raise GEDAError(
                "cannot convert file, not in gEDA format"
            )
        return True

    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 
        mirrored = bool(params.get('mirror', False))
        if mirrored:
            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')

        while typ is not None:

            if typ == 'T':
                key, value = self._parse_text(stream, params)
                if key is None:
                    body.add_shape(
                        self._create_label(value, params, mirrored)
                    )

                elif key == '_refdes' and '?' in value:
                    prefix, suffix = value.split('?') 
                    component.add_attribute('_prefix', prefix)
                    component.add_attribute('_suffix', suffix)
                else:
                    #assert(key not in ['_refdes', 'refdes'])
                    component.add_attribute(key, value)
            elif typ == 'L':
                body.add_shape(
                    self._parse_line(params, mirrored)
                )

            elif typ == 'B':
                body.add_shape(
                    self._parse_box(params, mirrored)
                )

            elif typ == 'V':
                body.add_shape(
                    self._parse_circle(params, mirrored)
                )

            elif typ == 'A':
                body.add_shape(
                    self._parse_arc(params, mirrored)
                )

            elif typ == 'P':
                body.add_pin(
                    self._parse_pin(stream, params, mirrored)
                )
            elif typ == 'H':
                for new_shape in self._parse_path(stream, params, mirrored):
                    body.add_shape(new_shape)

            elif typ == 'G':
                log.warn("ignoring picture/font in gEDA file. Not supported!")

            else:
                pass

            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 _parse_text(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. 
        """
        num_lines = params['num_lines']

        text = []
        for dummy in range(int(num_lines)):
            text.append(stream.readline())

        text_str = ''.join(text).strip()
        ## escape special parameter sequence '\_'
        text_str = text_str.replace("\_", '')

        if num_lines == 1 and '=' in text_str:
            return self._parse_attribute(text_str, params['visibility'])

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

        return None, text_str
    
    def _create_annotation(self, text, params):
        """ Creates an Annotation object from *text* using position,
            rotation and visibility from *params*.
            Returns an Annotation object.
        """
        return Annotation(
            text,
            self.x_to_px(params['x']),
            self.y_to_px(params['y']),
            self.conv_angle(params['angle']),
            self.conv_bool(params['visibility']),
        )

    def _create_label(self, text, params, mirrored=False):
        """ Create a ``shape.Label`` instance using the *text* string. The
            location of the Label is determined by the ``x`` and ``y`` 
            coordinates in *params*. If *mirrored* is true, the bottom left
            corner of the text (anchor) is mirrored at the Y-axis.

            Returns a ``shape.Label`` object
        """
        text_x = params['x']

        if mirrored:
            text_x = 0 - text_x

        return shape.Label(
            self.x_to_px(text_x),
            self.y_to_px(params['y']),
            text,
            'left',
            self.conv_angle(params['angle']),
        )

    @staticmethod
    def _parse_attribute(text, visibility):
        """ Creates a tuple of (key, value) from the attribute text.
            If visibility is '0' the attribute key is prefixed with '_'
            to make it a hidden attribute.
            Returns a tuple of key and value.
        """
        key, value = text.split('=', 1)
        ## prefix attributes that are marked as invisible
        if visibility == 0:
            key = "_"+key
        ## these are special attributes that are treated differently
        elif key in ['netname', 'pinnumber', 'pinlabel', 'refdes']:
            key = "_"+key

        return key.strip(), value.strip()

    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':
                key, value = self._parse_text(stream, params)
                attributes[key] = value

            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 segements 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.attributes['_name'] = net_name
            new_net.connect(seg)
            found = True

            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)

        return nets

    def _open_file_or_zip(self, filename, mode='rU'):
        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 _parse_bus(self, 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_segment(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 attributes.has_key('_netname'):
                net_name = attributes['_netname']
                if net_name not in self.net_names.values():
                    self.net_names[pt_a.point_id] = net_name

    def _parse_path(self, stream, params, mirrored=False):
        """ 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.
        """
        num_lines = params['num_lines']
        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 = 0-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 dummy 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)

                line = shape.Line(current_pos, end_pos)
                shapes.append(line)

                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)

                curve = shape.BezierCurve(
                    control1,
                    control2,
                    current_pos,
                    end_pos
                )
                shapes.append(curve)

                current_pos = end_pos

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

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

        return shapes 
    
    def _parse_arc(self, params, mirrored=False):
        """ 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 mirrored:
            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

        return 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']),
        )

    def _parse_line(self, params, mirrored=None):
        """ 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 mirrored:
            line_x1 = 0 - params['x1']
            line_x2 = 0 - params['x2']

        return shape.Line(
            self.conv_coords(line_x1, params['y1']),
            self.conv_coords(line_x2, params['y2']),
        )

    def _parse_box(self, params, mirrored=False):
        """ 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 mirrored:
            rect_x = 0-(rect_x+params['width'])

        return 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'])
        )

    def _parse_circle(self, params, mirrored=False):
        """ 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 mirrored:
            vertex_x = 0-vertex_x

        return shape.Circle(
            self.x_to_px(vertex_x),
            self.y_to_px(params['y']),
            self.to_px(params['radius']),
        )

    def _parse_pin(self, stream, params, mirrored=False):
        """ 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:
            raise GEDAError('mandatory pin attributes missing')

        if '_pinnumber' not in attributes:
            raise GEDAError(
                "mandatory attribute '_pinnumber' not assigned to pin"
            )

        whichend = params['whichend']

        pin_x1, pin_x2 = params['x1'], params['x2']
        if mirrored:
            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
            )

        return components.Pin(
            attributes['_pinnumber'], #pin number
            null_end,
            connect_end,
            label=label
        )

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

        line = line.strip()

        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:]

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

        assert(len(params) == len(self.OBJECT_TYPES[object_type]))

        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]

        if object_type not in self.OBJECT_TYPES:
            raise GEDAError("unknown type '%s' in file" % object_type)
        
        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)
Exemplo n.º 20
0
# 0) ...
# 1) ???
# 2) Profit!!!
from core.design import Design
from xml.etree.ElementTree import ElementTree


class Eagle:
    """ The Eagle Format Parser """

    def __init__(self):
        pass


    def parse(self, filename):
        """ Parse an Eagle file into a design """
<<<<<<< HEAD
        #design = design()
        #import an xmltree from the file provided
       	xmltree = ElementTree(file=filename)
        xmlroot = xmltree.getroot()
		
        return xmltree
=======
        design = Design()
        f = open(filename, "w")
        #TODO: Read!
        f.close()
        return design
>>>>>>> 5abb89ac932f010195dea31c734cf01a4d7fff5f