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_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. """ 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
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, 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(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
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
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_layers(self): """ Capture absence of layers. """ design = Design() design.layout = Layout() writer = Writer() writer.write(design)
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
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
def setUp(self): """ Setup the test case. """ self.des = Design()
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)
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(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
def __init__(self): self.design = Design()
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)
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)
# 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