def set_symbol_attribute(self, instance, openjson_inst): """ Fill out an openjson symbol attribute from an eagle instance and an openjson instance. """ cpt = self.design.components.components[openjson_inst.library_id] attr = openjson_inst.symbol_attributes[self.cptgate2body_index[ cpt, instance.gate]] attr.x = self.make_length(instance.x) attr.y = self.make_length(instance.y) attr.flip = is_mirrored(instance.rot) attr.rotation = make_angle(instance.rot or '0') if instance.smashed == 'yes': annotations = self.iter_instance_annotations(instance) else: annotations = sorted( self.cptgate2ann_map.get((cpt, instance.gate)).items()) for name, ann in annotations: ann = Annotation('', ann.x, ann.y, ann.rotation, ann.visible) if name == 'name': ann.value = openjson_inst.instance_id if len(openjson_inst.symbol_attributes) > 1: ann.value += instance.gate or '' attr.add_annotation(ann) elif name == 'value' and 'value' in openjson_inst.attributes: ann.value = openjson_inst.attributes['value'] attr.add_annotation(ann) self.part2gate2symattr[instance.part][instance.gate] = attr
def set_symbol_attribute(self, instance, openjson_inst): """ Fill out an openjson symbol attribute from an eagle instance and an openjson instance. """ # TODO: handle mirror cpt = self.design.components.components[openjson_inst.library_id] attr = openjson_inst.symbol_attributes[self.cptgate2body_index[cpt, instance.gate]] attr.x = self.make_length(instance.x) attr.y = self.make_length(instance.y) attr.rotation = self.make_angle(instance.rot or '0') if instance.smashed == 'yes': annotations = self.iter_instance_annotations(instance) else: annotations = sorted(self.cptgate2ann_map.get((cpt, instance.gate)).items()) for name, ann in annotations: ann = Annotation('', ann.x, ann.y, ann.rotation, ann.visible) if name == 'name': ann.value = openjson_inst.instance_id attr.add_annotation(ann) elif name == 'value' and 'value' in openjson_inst.attributes: ann.value = openjson_inst.attributes['value'] attr.add_annotation(ann) self.part2gate2symattr[instance.part][instance.gate] = attr
def test_annotation_bounds(self): '''Test .bounds()''' annot = Annotation('foo', 3, 6, 0, True) top_left, bottom_right = annot.bounds() # bounds() will give a square with sides 20 units long, centered on the # annotation self.assertEqual(top_left.x, 3 - 10) self.assertEqual(top_left.y, 6 - 10) self.assertEqual(bottom_right.x, 3 + 10) self.assertEqual(bottom_right.y, 6 + 10)
def test_bounds_all_elts(self): '''bounds() with all the elements competing''' net = Net('foo') mkbounds(net, 3, 3, -1, -2) self.des.add_net(net) annot = Annotation('foo', 3, 3, 0, True) mkbounds(annot, 3, 3, 3, 5) self.des.design_attributes.add_annotation(annot) libcomp = Component('bar') libcomp.add_symbol(Symbol()) libcomp.symbols[0].add_body(Body()) mkbounds(libcomp.symbols[0].bodies[0], 0, 0, 3, 3) self.des.add_component('foo', libcomp) compinst = ComponentInstance('bar', 'foo', 0) compinst.add_symbol_attribute(SymbolAttribute(3, 0, 0, False)) self.des.add_component_instance(compinst) top_left, btm_right = self.des.bounds() self.assertEqual(top_left.x, -1) self.assertEqual(top_left.y, -2) self.assertEqual(btm_right.x, 6) self.assertEqual(btm_right.y, 5)
def parse_text(self, f, line): """ Parse a Text line """ parts = line.split() x, y, rotation = int(parts[2]), int(parts[3]), int(parts[4]) rotation = rotation / 2.0 value = f.readline().decode('utf-8', 'replace').strip() return Annotation(value, x, -y, rotation, 'true')
def parse_annot(self, args): """ Returns a parsed annotation. """ x, y, _font_size, _rot, _anchor, viz, val = args.split(' ', 6) # anchor is 1,2,3: bottom,mid,top respectively # visibility is 0,1,2,3: invis, vis, name only, val only # FIXME use rotation subdata = defaultdict(list) for phrase in self.stream: cmd, _sep, args = phrase.partition(' ') if cmd not in ('Q'): self.stream.push(phrase) break # Q cmd is ignored for now anyway, but need to get it out of the way k, v = self.parsenode(cmd)(args) subdata[k].append(v) display = True if viz == '1': value = val elif viz == '2': value = val.split('=')[0] elif viz == '3': value = val.split('=', 1)[-1] else: value = val display = False return ('annot', Annotation(value, int(x), int(y), 0, display))
def parse(self): '''Returns a Design built up from a schematic file that represents one sheet of the original schematic''' tree = ViewDrawBase.parse(self) # tree['lines'] is a [list of [list of lines]] tree['shape'].extend(sum(tree['lines'], [])) ckt = Design() # TODO little weak here, a copy instead? ckt.components = self.lib for net in tree['net']: ckt.add_net(net) for inst in tree['inst']: ckt.add_component_instance(inst) # hold on tight, this is ugly for (netid, netpt, pinid) in inst.conns: net = [n for n in ckt.nets if n.net_id == netid][0] comp = ConnectedComponent(inst.instance_id, pinid) net.ibpts[netpt - 1].add_connected_component(comp) del inst.conns for net in ckt.nets: del net.ibpts for shape in tree['shape']: ckt.add_shape(shape) if isinstance(shape, Label): ann = Annotation(shape.text, shape.x, shape.y, shape._rotation, True) ckt.design_attributes.add_annotation(ann) for k, v, annot in tree['attr']: ckt.design_attributes.add_attribute(k, v) ckt.design_attributes.add_annotation(annot) return ckt
def make_body_from_symbol(self, lib, symbol_name): """ Construct an openjson Body from an eagle symbol in a library. """ body = Body() symbol = [ s for s in get_subattr(lib, 'symbols.symbol') if s.name == symbol_name ][0] for wire in symbol.wire: body.add_shape( Line((self.make_length(wire.x1), self.make_length(wire.y1)), (self.make_length(wire.x2), self.make_length(wire.y2)))) for rect in symbol.rectangle: x = self.make_length(rect.x1) y = self.make_length(rect.y1) width = self.make_length(rect.x2) - x height = self.make_length(rect.y2) - y body.add_shape(Rectangle(x, y + height, width, height)) pin_map = {} for pin in symbol.pin: connect_point = (self.make_length(pin.x), self.make_length(pin.y)) null_point = self.get_pin_null_point(connect_point, pin.length, pin.rot) label = self.get_pin_label(pin, null_point) pin_map[pin.name] = Pin(pin.name, null_point, connect_point, label) body.add_pin(pin_map[pin.name]) ann_map = {} for text in symbol.text: x = self.make_length(text.x) y = self.make_length(text.y) content = '' if text.valueOf_ is None else text.valueOf_ rotation = self.make_angle('0' if text.rot is None else text.rot) if content == '>NAME': ann_map['name'] = Annotation(content, x, y, rotation, 'true') elif content == '>VALUE': ann_map['value'] = Annotation(content, x, y, rotation, 'true') else: body.add_shape(Label(x, y, content, 'left', rotation)) return body, pin_map, ann_map
def test_create_new_annotation(self): """ Test the creation of a new empty annotation. """ anno = Annotation('abc', 0, 1, 2, False) assert anno.value == 'abc' assert anno.x == 0 assert anno.y == 1 assert anno.rotation == 2 assert anno.visible != True
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 parse_field(self, compx, compy, line): """ Parse a field (F) line in a component block """ parts = line.rsplit('"', 1) value = parts[0].split('"', 1)[1].decode('utf-8', 'replace') parts = parts[1].strip().split() return Annotation(value, make_length(int(parts[1]) - compx), -make_length(int(parts[2]) - compy), 0 if parts[0] == 'H' else 1, 'true')
def parse_label(self, args): """ Returns a parsed label. """ x, y, _font_size, _rot, _c, _d, _e, _f, text = args.split(' ', 8) # treat them as annotations for now, I guess. # suspect that c, e are anchor and vis, as in parse_annot # According to other research, d is scope (0=local, 1=global) and f # might be logic sense (for overbars, 0=normal, 1=inverted) # FIXME use rot and vis return ('annot', Annotation(text, int(x), int(y), 0, True))
def test_write_annotation(self): """ The write_annotation method produces the correct string. """ writer = KiCAD() buf = StringIO() ann = Annotation('test', 1, 2, .5, 'true') writer.write_annotation(buf, ann) self.assertEqual(buf.getvalue(), 'Text Label 11 -22 900 60 ~ 0\ntest\n')
def parse_label(self, args): """ Returns a parsed label. """ args = args.split(' ', 8) x, y, _font_size, rot, _anchor, _scope, _vis, _sense, text = args # treat them as annotations for now, I guess. # suspect that anchor and vis are as in parse_annot # According to other research, _scope is (0=local, 1=global) and _sense # might be logic sense (for overbars, 0=normal, 1=inverted) # FIXME use vis rot, _flip = self.rot_and_flip(rot) return ('annot', Annotation(text, int(x), int(y), rot, True))
def iter_instance_annotations(self, instance): """ Return an iterator over (name, Annotation) for the annotations in an eagle instance. """ for ob in instance.attribute: name = ob.name.lower() x = self.make_length(ob.x) - self.make_length(instance.x) y = self.make_length(ob.y) - self.make_length(instance.y) rotation = make_angle('0' if ob.rot is None else ob.rot) ann = Annotation(ob.name, x, y, rotation, 'true') yield (name, ann)
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_annot(self, args): """ Returns a parsed annotation. """ x, y, _font_size, rot, _anchor, viz, val = args.split(' ', 6) # anchor is 1,2,3: bottom,mid,top respectively # visibility is 0,1,2,3: invis, vis, name only, val only self.sub_nodes(['Q']) # Q cmd is ignored for now anyway, but need to get it out of the way display = True if viz == '1': value = val elif viz == '2': value = val.split('=')[0] elif viz == '3': value = val.split('=', 1)[-1] else: value = val display = False rot, _flip = self.rot_and_flip(rot) return ('annot', Annotation(value, int(x), int(y), rot, display))
def parse_annotation(self, annotation): """ Extract an annotation. """ value = annotation.get('value') x = int(annotation.get('x')) y = int(annotation.get('y')) label = self.parse_label(annotation.get('label')) layer = annotation.get('layer', 'default') rotation = float(annotation.get('rotation')) flip_horizontal = annotation.get('flip', False) visible = annotation.get('visible') if visible is not None and visible.lower() == 'false': visible = 'false' else: visible = 'true' return Annotation(value, x, y, rotation, visible, layer=layer, flip_horizontal=flip_horizontal, label=label)
def make_body_from_symbol(self, lib, symbol_name, pin_number_lookup): """ Construct an openjson SBody from an eagle symbol in a library. """ body = SBody() symbol = [ s for s in get_subattr(lib, 'symbols.symbol') if s.name == symbol_name ][0] for wire in symbol.wire: body.add_shape(self.make_shape_for_wire(wire)) for rect in symbol.rectangle: rotation = make_angle('0' if rect.rot is None else rect.rot) x1, y1 = rotate_point( (self.make_length(rect.x1), self.make_length(rect.y1)), rotation) x2, y2 = rotate_point( (self.make_length(rect.x2), self.make_length(rect.y2)), rotation) ux, uy = min(x1, x2), max(y1, y2) lx, ly = max(x1, x2), min(y1, y2) body.add_shape(Rectangle(ux, uy, lx - ux, uy - ly)) for poly in symbol.polygon: map(body.add_shape, self.make_shapes_for_poly(poly)) for circ in symbol.circle: body.add_shape(self.make_shape_for_circle(circ)) pin_map = {} for pin in symbol.pin: connect_point = (self.make_length(pin.x), self.make_length(pin.y)) null_point = self.get_pin_null_point(connect_point, pin.length, pin.rot) label = self.get_pin_label(pin, null_point) pin_map[pin.name] = Pin(pin_number_lookup(pin.name), null_point, connect_point, label) if pin.direction: pin_map[pin.name].add_attribute('eaglexml_direction', pin.direction) if pin.visible: pin_map[pin.name].add_attribute('eaglexml_visible', pin.visible) body.add_pin(pin_map[pin.name]) ann_map = {} for text in symbol.text: x = self.make_length(text.x) y = self.make_length(text.y) content = '' if text.valueOf_ is None else text.valueOf_ rotation = make_angle('0' if text.rot is None else text.rot) align = 'right' if is_mirrored(text.rot) else 'left' if rotation == 0.5: rotation = 1.5 if content.lower() == '>name': ann_map['name'] = Annotation(content, x, y, rotation, 'true') elif content.lower() == '>value': ann_map['value'] = Annotation(content, x, y, rotation, 'true') else: body.add_shape( Label(x, y, content, align=align, rotation=rotation)) return body, pin_map, ann_map
def calculate_nets(self): """ Calculate connected nets from previously stored segments and netpoints. The code has been adapted from the kiCAD parser since the definition of segments in the schematic file are similar. The segments are checked against existing nets and added when they touch it. For this to work, it is required that intersecting segments are divided prior to this method. Returns a list of valid nets and its net points. """ nets = [] # Iterate over the segments, removing segments when added to a net while self.segments: seg = self.segments.pop() # pick a point net_name = '' pt_a, pt_b = seg if pt_a.point_id in self.net_names: net_name = self.net_names[pt_a.point_id] elif pt_b.point_id in self.net_names: net_name = self.net_names[pt_b.point_id] new_net = net.Net(net_name) new_net.connect(seg) found = True if net_name: new_net.attributes['_name'] = net_name while found: found = set() for seg in self.segments: # iterate over segments if new_net.connected(seg): # segment touching the net new_net.connect(seg) # add the segment found.add(seg) for seg in found: self.segments.remove(seg) nets.append(new_net) # check if names are available for calculated nets for net_obj in nets: for point_id in net_obj.points: ## check for stored net names based on pointIDs if point_id in self.net_names: net_obj.net_id = self.net_names[point_id] net_obj.attributes['_name'] = self.net_names[point_id] if '_name' in net_obj.attributes: annotation = Annotation( "{{_name}}", ## annotation referencing attribute '_name' 0, 0, self.conv_angle(0.0), self.conv_bool(1), ) net_obj.add_annotation(annotation) for net_obj in nets: if not net_obj.net_id: net_obj.net_id = min(net_obj.points) return nets
def _parse_component(self, stream, params): """ Creates a component instance according to the component *params*. If the component is not known in the library, a the component will be created according to its description in the embedded environment ``[]`` or a symbol file. The component is added to the library automatically if necessary. An instance of this component will be created and added to the design. A GEDAError is raised when either the component file is invalid or the referenced symbol file cannot be found in the known directories. Returns a tuple of Component and ComponentInstance objects. """ basename, _ = os.path.splitext(params['basename']) component_name = basename if params.get('mirror'): component_name += '_MIRRORED' if component_name in self.design.components.components: component = self.design.components.components[component_name] ## skipping embedded data might be required self.skip_embedded_section(stream) else: ##check if sym file is embedded or referenced if basename.startswith('EMBEDDED'): ## embedded only has to be processed when NOT in symbol lookup if basename not in self.known_symbols: component = self.parse_component_data(stream, params) else: if basename not in self.known_symbols: log.warn("referenced symbol file '%s' unknown" % basename) ## create a unknown symbol reference component = self.parse_component_data( StringIO(UNKNOWN_COMPONENT % basename), params) ## parse optional attached environment before continuing self._parse_environment(stream) return None, None ## requires parsing of referenced symbol file with open(self.known_symbols[basename], "rU") as f_in: self._check_version(f_in) component = self.parse_component_data(f_in, params) self.design.add_component(component_name, component) ## get all attributes assigned to component instance attributes = self._parse_environment(stream) ## refdes attribute is name of component (mandatory as of gEDA doc) ## examples if gaf repo have components without refdes, use part of ## basename if attributes is not None: instance = ComponentInstance( attributes.get('_refdes', component.name), component.name, 0) for key, value in attributes.items(): instance.add_attribute(key, value) else: instance = ComponentInstance(component.name, component.name, 0) ## generate a component instance using attributes self.design.add_component_instance(instance) symbol = SymbolAttribute(self.x_to_px(params['x']), self.y_to_px(params['y']), self.conv_angle(params['angle'], False)) instance.add_symbol_attribute(symbol) ## add annotation for special attributes for idx, attribute_key in enumerate(['_refdes', 'device']): if attribute_key in component.attributes \ or attribute_key in instance.attributes: symbol.add_annotation( Annotation('{{%s}}' % attribute_key, 0, 0 + idx * 10, 0.0, 'true')) return component, instance
def parse_rev(self, args): """ Returns the file revision date, parsed into an annotation. """ # File revision date. Gahh, ugly. return ('annot', Annotation('rev=' + args, 0, 0, 0, False))