def _placeOutline(self): """ """ # Place shape shape_group = et.SubElement(self._layers['outline']['layer'], 'g') shape_group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'module-shapes') place.placeShape(self._outline, shape_group) # Place a mask for the board's outline. This creates a buffer between # the board's edge and pours try: pour_buffer = self._module['distances']['from-pour-to']['outline'] except: pour_buffer = config.brd['distances']['from-pour-to']['outline'] for pcb_layer in utils.getSurfaceLayers(): if utils.checkForPoursInLayer(pcb_layer) is True: mask_element = place.placeShape(self._outline, self._masks[pcb_layer]) # Override style so that we get the desired effect # We stroke the outline with twice the size of the buffer, so # we get the actual distance between the outline and board style = "fill:none;stroke:#000;stroke-linejoin:round;stroke-width:%s;" % str(pour_buffer*2) mask_element.set('style', style) # Also override mask's gerber-lp and set to all clear path = self._outline.getOriginalPath().lower() segments = path.count('m') mask_element.set('{'+config.cfg['ns']['pcbmode']+'}gerber-lp', 'c'*segments)
def _placeOutline(self): """ """ # Place shape shape_group = et.SubElement(self._layers['outline']['layer'], 'g') shape_group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'module-shapes') place.placeShape(self._outline, shape_group) # Place a mask for the board's outline. This creates a buffer between # the board's edge and pours try: pour_buffer = self._module['distances']['from-pour-to']['outline'] except: pour_buffer = config.brd['distances']['from-pour-to']['outline'] for pcb_layer in utils.getSurfaceLayers(): if utils.checkForPoursInLayer(pcb_layer) is True: mask_element = place.placeShape(self._outline, self._masks[pcb_layer]) # Override style so that we get the desired effect # We stroke the outline with twice the size of the buffer, so # we get the actual distance between the outline and board style = "fill:none;stroke:#000;stroke-linejoin:round;stroke-width:%s;" % str( pour_buffer * 2) mask_element.set('style', style) # Also override mask's gerber-lp and set to all clear path = self._outline.getOriginalPath().lower() segments = path.count('m') mask_element.set( '{' + config.cfg['ns']['pcbmode'] + '}gerber-lp', 'c' * segments)
def _placeLayerIndex(self): """ Adds a drill index """ text_dict = config.stl['layout']['layer-index']['text'] text_dict['type'] = 'text' # Set the height (and width) of the rectangle (square) to the # size of the text rect_width = utils.parseDimension(text_dict['font-size'])[0] rect_height = rect_width rect_gap = 0.25 # Get location, or generate one try: location = config.brd['layer-index']['location'] except: # If not location is specified, put the drill index at the # top right of the board. The 'gap' defines the extra # spcae between the top of the largest drill and the # board's edge gap = 2 location = [self._width/2+gap, self._height/2-rect_height/2] location = utils.toPoint(location) rect_dict = {} rect_dict['type'] = 'rect' rect_dict['style'] = 'fill' rect_dict['width'] = rect_width rect_dict['height'] = rect_height # Create group for placing index for pcb_layer in utils.getSurfaceLayers(): for sheet in ['copper', 'soldermask', 'silkscreen', 'assembly', 'solderpaste']: layer = self._layers[pcb_layer][sheet]['layer'] transform = "translate(%s,%s)" % (location.x, config.cfg['invert-y']*location.y) group = et.SubElement(layer, 'g', transform=transform) group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'layer-index') rect_shape = Shape(rect_dict) style = Style(rect_dict, sheet) rect_shape.setStyle(style) place.placeShape(rect_shape, group) text_dict['value'] = "%s %s" % (pcb_layer, sheet) #text_dict['location'] = [rect_width+rect_gap+text_width, 0] text_shape = Shape(text_dict) text_width = text_shape.getWidth() style = Style(text_dict, sheet) text_shape.setStyle(style) element = place.placeShape(text_shape, group) element.set("transform", "translate(%s,%s)" % (rect_width/2+rect_gap+text_width/2, 0)) location.y += config.cfg['invert-y']*(rect_height+rect_gap) location.y += config.cfg['invert-y']*(rect_height+rect_gap*2)
def _placePours(self): """ """ try: pours = self._module_dict['shapes']['pours'] except: return shape_group = {} for pcb_layer in utils.getSurfaceLayers(): svg_layer = self._layers[pcb_layer]['copper']['pours']['layer'] shape_group[pcb_layer] = et.SubElement(svg_layer, 'g', mask='url(#mask-%s)' % pcb_layer) for pour_dict in pours: try: pour_type = pour_dict['type'] except: msg.error( "Cannot find a 'type' for a pour shape. Pours can be any 'shape', or simply 'type':'layer' to cover the entire layer." ) layers = pour_dict.get('layers') or ['top'] if pour_type == 'layer': # Get the outline shape dict new_pour_dict = self._module_dict['outline'].get( 'shape').copy() new_pour_dict['style'] = 'fill' shape = Shape(new_pour_dict) # Get the appropriate style from copper->pours style = Style(new_pour_dict, layer_name='copper', sub_item='pours') shape.setStyle(style) else: shape = Shape(pour_dict) # Get the appropriate style from copper->pours style = Style(pour_dict, layer_name='copper', sub_item='pours') shape.setStyle(style) # Place on all specified layers for layer in layers: place.placeShape(shape, shape_group[layer])
def _placePours(self): """ """ try: pours = self._module_dict['shapes']['pours'] except: return shape_group = {} for pcb_layer in utils.getSurfaceLayers(): svg_layer = self._layers[pcb_layer]['copper']['pours']['layer'] shape_group[pcb_layer] = et.SubElement(svg_layer, 'g', mask='url(#mask-%s)' % pcb_layer) for pour_dict in pours: try: pour_type = pour_dict['type'] except: msg.error("Cannot find a 'type' for a pour shape. Pours can be any 'shape', or simply 'type':'layer' to cover the entire layer.") layers = pour_dict.get('layers') or ['top'] if pour_type == 'layer': # Get the outline shape dict new_pour_dict = self._module_dict['outline'].get('shape').copy() new_pour_dict['style'] = 'fill' shape = Shape(new_pour_dict) # Get the appropriate style from copper->pours style = Style(new_pour_dict, layer_name='copper', sub_item='pours') shape.setStyle(style) else: shape = Shape(pour_dict) # Get the appropriate style from copper->pours style = Style(pour_dict, layer_name='copper', sub_item='pours') shape.setStyle(style) # Place on all specified layers for layer in layers: place.placeShape(shape, shape_group[layer])
def gerbers_to_svg(manufacturer='default'): """ Takes Gerber files as input and generates an SVG of them """ def normalise_gerber_number(gerber_number, axis, form): """ Takes a Gerber number and converts it into a float using the formatting defined in the Gerber header """ # TODO: actually support anything other than leading zeros number = gerber_number / pow(10.0, form[axis]['decimal']) return number def parsed_grammar_to_dict(parsed_grammar): """ Converts the Gerber parsing results to an SVG. """ gerber_dict = {} current_aperture = None new_shape = True for line in parsed_grammar: if line.dump(): if (line.format): if gerber_dict.get('format') is None: gerber_dict['format'] = {} tmp = gerber_dict['format'] tmp['notation'] = line['format']['notation'] tmp['zeros'] = line['format']['zeros'] tmp['x'] = {} tmp['x']['integer'] = line['format']['x']['integer'] tmp['x']['decimal'] = line['format']['x']['decimal'] tmp['y'] = {} tmp['y']['integer'] = line['format']['x']['integer'] tmp['y']['decimal'] = line['format']['x']['decimal'] elif (line.units): gerber_dict['units'] = line['units']['units'] elif (line.aperture_definition): tmp = {} if line['aperture_definition']['type'] == 'circle': tmp['type'] = 'circle' tmp['diameter'] = line['aperture_definition']['diameter'] tmp['number'] = line['aperture_definition']['number'] elif line['aperture_definition']['type'] == 'rect': tmp['type'] = 'rect' tmp['width'] = line['aperture_definition']['width'] tmp['height'] = line['aperture_definition']['height'] tmp['number'] = line['aperture_definition']['number'] else: print "ERROR: cannot recognise aperture definition type" if gerber_dict.get('aperture-definitions') is None: gerber_dict['aperture-definitions'] = [] gerber_dict['aperture-definitions'].append(tmp) elif line.polarity_change: if gerber_dict.get('features') is None: gerber_dict['features'] = [] polarity = line['polarity_change']['polarity'] polarity_dict = {} polarity_dict['polarity'] = polarity polarity_dict['shapes'] = [] gerber_dict['features'].append(polarity_dict) elif line.aperture_change: tmp = {} tmp['type'] = 'aperture-change' tmp['number'] = line.aperture_change['number'] #if len(gerber_dict['features'][-1]['shapes'] == 0): gerber_dict['features'][-1]['shapes'].append(tmp) #else: # gerber_dict['features'][-1]['shapes'].append(tmp) tmp = {} tmp['type'] = 'stroke' tmp['segments'] = [] gerber_dict['features'][-1]['shapes'].append(tmp) elif line.start_closed_shape: tmp = {} tmp['type'] = 'fill' tmp['segments'] = [] gerber_dict['features'][-1]['shapes'].append(tmp) elif line.move or line.draw or line.flash: # TODO: hack alert! (Got to get shit done, you know? Don't judge me!) if line.move: command_name = 'move' item = line.move if line.draw: command_name = 'draw' item = line.draw if line.flash: command_name = 'flash' item = line.flash point = Point(normalise_gerber_number(item['x'], 'x', gerber_dict['format']), normalise_gerber_number(item['y'], 'y', gerber_dict['format'])) tmp = {} tmp['type'] = command_name tmp['coord'] = point gerber_dict['features'][-1]['shapes'][-1]['segments'].append(tmp) elif line.end_closed_shape: new_shape = True return gerber_dict def create_gerber_svg_data(gerber_data): """ Returns an SVG element of the input Gerber data """ gerber_data_parsed = gerber_grammar.parseString(gerber_data) gerber_data_dict = parsed_grammar_to_dict(gerber_data_parsed) gerber_data_svg = svg.generate_svg_from_gerber_dict(gerber_data_dict) return gerber_data_svg # get the board's shape / outline board_shape_gerber_lp = None shape = config.brd['board_outline']['shape'] board_shape_type = shape.get('type') if board_shape_type in ['rect', 'rectangle']: offset = utils.to_Point(shape.get('offset') or [0, 0]) board_shape_path = svg.rect_to_path(shape) elif board_shape_type == 'path': board_shape_path = shape.get('value') board_shape_gerber_lp = shape.get('gerber_lp') if board_shape_path is None: print "ERROR: couldn't find a path under key 'value' for board outline" else: print "ERROR: unrecognised board shape type: %s. Possible options are 'rect' or 'path'" % board_shape_type # convert path to relative board_shape_path_relative = svg.absolute_to_relative_path(board_shape_path) # this will return a path having an origin at the center of the shape # defined by the path board_width, board_height, board_outline = svg.transform_path(board_shape_path_relative, True) display_width = board_width display_height = board_height #transform = 'translate(' + str(round((board_width)/2, SD)) + ' ' + str(round((board_height)/2, SD)) + ')' sig_dig = config.cfg['significant-digits'] #transform = 'translate(%s %s)' % (round(board_width/2, sig_dig), # round(board_height/2, sig_dig)) # extra buffer for display frame display_frame_buffer = config.cfg.get('display-frame-buffer') or 1.0 gerber = et.Element('svg', width=str(display_width) + config.brd['config']['units'], height=str(display_height) + config.brd['config']['units'], viewBox=str(-display_frame_buffer/2) + ' ' + str(-display_frame_buffer/2) + ' ' + str(board_width+display_frame_buffer) + ' ' + str(board_height + display_frame_buffer), version='1.1', nsmap=cfg['namespace'], fill='black') doc = et.ElementTree(gerber) gerber_layers = svg.create_layers_for_gerber_svg(gerber) # directory for where to expect the Gerbers within the build path # regardless of the source of the Gerbers, the PCBmodE directory # structure is assumed production_path = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], 'production') # get board information from configuration file pcbmode_version = config.cfg['version'] board_name = config.cfg['name'] board_revision = config.brd['config'].get('rev') base_name = "%s_rev_%s" % (board_name, board_revision) gerber_grammar = gerber_grammar_generator() for foil in ['outline']:#, 'documentation']: gerber_file = os.path.join(production_path, base_name + '_%s.ger'% (foil)) gerber_data = open(gerber_file, 'r').read() gerber_svg = create_gerber_svg_data(gerber_data) gerber_svg_layer = gerber_layers[foil]['layer'] gerber_svg_layer.append(gerber_svg) print foil for pcb_layer in utils.getSurfaceLayers(): for foil in ['copper', 'silkscreen', 'soldermask']: gerber_file = os.path.join(production_path, base_name + '_%s_%s.ger'% (pcb_layer, foil)) gerber_data = open(gerber_file, 'r').read() gerber_svg = create_gerber_svg_data(gerber_data) gerber_svg_layer = gerber_layers[pcb_layer][foil]['layer'] gerber_svg_layer.append(gerber_svg) print foil output_file = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], cfg['board_name'] + '_gerber.svg') try: f = open(output_file, 'wb') except IOError as e: print "I/O error({0}): {1}".format(e.errno, e.strerror) f.write(et.tostring(doc, pretty_print=True)) f.close() return
def __init__(self, module_dict, routing_dict, asmodule=False): """ """ self._module_dict = module_dict self._routing_dict = routing_dict self._outline = self._getOutline() self._width = self._outline.getWidth() self._height = self._outline.getHeight() # Get dictionary of component definitions components_dict = self._module_dict.get('components') or {} self._components = self._getComponents(components_dict) # Get dictionary of component definitions vias_dict = self._routing_dict.get('vias') or {} self._vias = self._getComponents(vias_dict) sig_dig = config.cfg['significant-digits'] self._transform = 'translate(%s %s)' % (round( self._width / 2, sig_dig), round(self._height / 2, sig_dig)) # Create the Inkscape SVG document self._module = self._getModuleElement() svg_doc = et.ElementTree(self._module) # Get a dictionary of SVG layers self._layers = svg.makeSvgLayers(self._module, self._transform) # Add a 'defs' element: # http://www.w3.org/TR/SVG/struct.html#Head # This is where masking elements that are used for pours are stored defs = et.SubElement(self._module, 'defs') self._masks = {} for pcb_layer in utils.getSurfaceLayers(): element = et.SubElement(defs, 'mask', id="mask-%s" % pcb_layer, transform=self._transform) # This will identify the masks for each PCB layer when # the layer is converted to Gerber element.set('{' + config.cfg['ns']['pcbmode'] + '}pcb-layer', pcb_layer) self._masks[pcb_layer] = element self._placeOutline() self._placeOutlineDimensions() msg.subInfo('Placing components:', newline=False) self._placeComponents(self._components, 'components', print_refdef=True) print msg.subInfo('Placing routes') self._placeRouting() msg.subInfo('Placing vias') self._placeComponents(self._vias, 'vias') msg.subInfo('Placing shapes') self._placeShapes() msg.subInfo('Placing pours') self._placePours() if config.tmp['no-docs'] == False: msg.subInfo('Placing documentation') self._placeDocs() if config.tmp['no-drill-index'] == False: msg.subInfo('Placing drill index') self._placeDrillIndex() if config.tmp['no-layer-index'] == False: msg.subInfo('Placing layer index') self._placeLayerIndex() # This 'cover' "enables" the mask shapes defined in the mask are # shown. It *must* be the last element in the mask definition; # any mask element after it won't show for pcb_layer in utils.getSurfaceLayers(): if utils.checkForPoursInLayer(pcb_layer) is True: mask_cover = et.SubElement(self._masks[pcb_layer], 'rect', x="%s" % str(-self._width / 2), y="%s" % str(-self._height / 2), width="%s" % self._width, height="%s" % self._height, style="fill:#fff;") # This tells the Gerber conversion to ignore this shape mask_cover.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'mask-cover') output_file = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], config.cfg['name'] + '.svg') try: f = open(output_file, 'wb') except IOError as e: print "I/O error({0}): {1}".format(e.errno, e.strerror) f.write(et.tostring(svg_doc, pretty_print=True)) f.close()
def extractComponents(svg_in): """ """ # Get copper refdef shape groups from SVG data xpath_expr_copper_pads = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="copper"]//svg:g[@pcbmode:sheet="pads"]//svg:g[@pcbmode:refdef]' xpath_expr_refdefs = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="silkscreen"]//svg:g[@pcbmode:type="refdef"][@pcbmode:refdef="%s"]' for pcb_layer in utils.getSurfaceLayers(): shapes = svg_in.findall(xpath_expr_copper_pads % pcb_layer, namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) for shape in shapes: transform_data = utils.parseTransform(shape.get('transform')) refdef = shape.get('{' + config.cfg['ns']['pcbmode'] + '}refdef') comp_dict = config.brd['components'][refdef] # Check if the copper shapes are on the same layer as placement. # Ignore if otherwise. While it is possible that a component is placed # on one layer but all its components are on another, it is very # unlikely, and doesn't make much sense. on_layer = comp_dict.get('layer') or 'top' if pcb_layer == on_layer: new_location = transform_data['location'] old_location = utils.toPoint( comp_dict.get('location') or [0, 0]) # Invert 'y' coordinate new_location.y *= config.cfg['invert-y'] # Change component location if needed if new_location != old_location: msg.subInfo("Component %s moved from %s to %s" % (refdef, old_location, new_location)) comp_dict['location'] = [new_location.x, new_location.y] # Change component rotation if needed if transform_data['type'] == 'matrix': old_rotate = comp_dict.get('rotate') or 0 new_rotate = transform_data['rotate'] comp_dict['rotate'] = round( (old_rotate + new_rotate) % 360, 4) msg.subInfo("Component %s rotated from %s to %s" % (refdef, old_rotate, comp_dict['rotate'])) refdef_texts = svg_in.findall(xpath_expr_refdefs % (pcb_layer, refdef), namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) for refdef_text in refdef_texts: # Get refdef group location transform_data = utils.parseTransform( refdef_text.get('transform')) group_loc = transform_data['location'] # Invert 'y' coordinate group_loc.y *= config.cfg['invert-y'] # Get the refdef text path inside the refdef group refdef_path = refdef_text.find( "svg:path", namespaces={'svg': config.cfg['ns']['svg']}) try: transform_data = utils.parseTransform( refdef_path.get('transform')) except: transform_data['location'] = Point(0, 0) refdef_loc = transform_data['location'] # Invert 'y' coordinate refdef_loc.y *= config.cfg['invert-y'] # Current ('old') location of the component current_component_loc = utils.toPoint( comp_dict.get('location', [0, 0])) try: current_refdef_loc = utils.toPoint( comp_dict['silkscreen']['refdef']['location']) except: current_refdef_loc = Point() # TODO: this is an embarassing mess, but I have too much of # a headache to tidy it up! if pcb_layer == 'bottom': current_refdef_loc.x = -current_refdef_loc.x diff = group_loc - current_component_loc new_refdef_loc = diff + current_refdef_loc if pcb_layer == 'bottom': new_refdef_loc.x = -new_refdef_loc.x # Change the location in the dictionary if new_refdef_loc != current_refdef_loc: try: tmp = comp_dict['silkscreen'] except: comp_dict['silkscreen'] = {} try: tmp = comp_dict['silkscreen']['refdef'] except: comp_dict['silkscreen']['refdef'] = {} comp_dict['silkscreen']['refdef']['location'] = [ new_refdef_loc.x, new_refdef_loc.y ] # Save board config to file (everything is saved, not only the # component data) filename = os.path.join(config.cfg['locations']['boards'], config.cfg['name'], config.cfg['name'] + '.json') try: with open(filename, 'wb') as f: f.write(json.dumps(config.brd, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % filename) return
def extractComponents(svg_in): """ """ # Get copper refdef shape groups from SVG data xpath_expr_copper_pads = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="copper"]//svg:g[@pcbmode:sheet="pads"]//svg:g[@pcbmode:refdef]' xpath_expr_refdefs = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="silkscreen"]//svg:g[@pcbmode:type="refdef"][@pcbmode:refdef="%s"]' for pcb_layer in utils.getSurfaceLayers(): shapes = svg_in.findall(xpath_expr_copper_pads % pcb_layer, namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) for shape in shapes: transform_data = utils.parseTransform(shape.get('transform')) refdef = shape.get('{'+config.cfg['ns']['pcbmode']+'}refdef') comp_dict = config.brd['components'][refdef] # Check if the copper shapes are on the same layer as placement. # Ignore if otherwise. While it is possible that a component is placed # on one layer but all its components are on another, it is very # unlikely, and doesn't make much sense. on_layer = comp_dict.get('layer') or 'top' if pcb_layer == on_layer: new_location = transform_data['location'] old_location = utils.toPoint(comp_dict.get('location') or [0, 0]) # Invert 'y' coordinate new_location.y *= config.cfg['invert-y'] # Change component location if needed if new_location != old_location: msg.subInfo("Component %s moved from %s to %s" % (refdef, old_location, new_location)) comp_dict['location'] = [new_location.x, new_location.y] # Change component rotation if needed if transform_data['type'] == 'matrix': old_rotate = comp_dict.get('rotate') or 0 new_rotate = transform_data['rotate'] comp_dict['rotate'] = round((old_rotate+new_rotate) % 360,4) msg.subInfo("Component %s rotated from %s to %s" % (refdef, old_rotate, comp_dict['rotate'])) refdef_texts = svg_in.findall(xpath_expr_refdefs % (pcb_layer, refdef), namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) for refdef_text in refdef_texts: # Get refdef group location transform_data = utils.parseTransform(refdef_text.get('transform')) group_loc = transform_data['location'] # Invert 'y' coordinate group_loc.y *= config.cfg['invert-y'] # Get the refdef text path inside the refdef group refdef_path = refdef_text.find("svg:path", namespaces={'svg':config.cfg['ns']['svg']}) try: transform_data = utils.parseTransform(refdef_path.get('transform')) except: transform_data['location'] = Point(0,0) refdef_loc = transform_data['location'] # Invert 'y' coordinate refdef_loc.y *= config.cfg['invert-y'] # Current ('old') location of the component current_component_loc = utils.toPoint(comp_dict.get('location', [0, 0])) try: current_refdef_loc = utils.toPoint(comp_dict['silkscreen']['refdef']['location']) except: current_refdef_loc = Point() # TODO: this is an embarassing mess, but I have too much of # a headache to tidy it up! if pcb_layer == 'bottom': current_refdef_loc.x = -current_refdef_loc.x diff = group_loc-current_component_loc new_refdef_loc = diff + current_refdef_loc if pcb_layer == 'bottom': new_refdef_loc.x = -new_refdef_loc.x # Change the location in the dictionary if new_refdef_loc != current_refdef_loc: try: tmp = comp_dict['silkscreen'] except: comp_dict['silkscreen'] = {} try: tmp = comp_dict['silkscreen']['refdef'] except: comp_dict['silkscreen']['refdef'] = {} comp_dict['silkscreen']['refdef']['location'] = [new_refdef_loc.x, new_refdef_loc.y] # Save board config to file (everything is saved, not only the # component data) filename = os.path.join(config.cfg['locations']['boards'], config.cfg['name'], config.cfg['name'] + '.json') try: with open(filename, 'wb') as f: f.write(json.dumps(config.brd, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % filename) return
def _placeLayerIndex(self): """ Adds a drill index """ text_dict = config.stl['layout']['layer-index']['text'] text_dict['type'] = 'text' # Set the height (and width) of the rectangle (square) to the # size of the text rect_width = utils.parseDimension(text_dict['font-size'])[0] rect_height = rect_width rect_gap = 0.25 # Get location, or generate one try: location = config.brd['layer-index']['location'] except: # If not location is specified, put the drill index at the # top right of the board. The 'gap' defines the extra # spcae between the top of the largest drill and the # board's edge gap = 2 location = [ self._width / 2 + gap, self._height / 2 - rect_height / 2 ] location = utils.toPoint(location) rect_dict = {} rect_dict['type'] = 'rect' rect_dict['style'] = 'fill' rect_dict['width'] = rect_width rect_dict['height'] = rect_height # Create group for placing index for pcb_layer in utils.getSurfaceLayers(): for sheet in [ 'copper', 'soldermask', 'silkscreen', 'assembly', 'solderpaste' ]: layer = self._layers[pcb_layer][sheet]['layer'] transform = "translate(%s,%s)" % ( location.x, config.cfg['invert-y'] * location.y) group = et.SubElement(layer, 'g', transform=transform) group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'layer-index') rect_shape = Shape(rect_dict) style = Style(rect_dict, sheet) rect_shape.setStyle(style) place.placeShape(rect_shape, group) text_dict['value'] = "%s %s" % (pcb_layer, sheet) #text_dict['location'] = [rect_width+rect_gap+text_width, 0] text_shape = Shape(text_dict) text_width = text_shape.getWidth() style = Style(text_dict, sheet) text_shape.setStyle(style) element = place.placeShape(text_shape, group) element.set( "transform", "translate(%s,%s)" % (rect_width / 2 + rect_gap + text_width / 2, 0)) location.y += config.cfg['invert-y'] * (rect_height + rect_gap) location.y += config.cfg['invert-y'] * (rect_height + rect_gap * 2)
def gerbers_to_svg(manufacturer='default'): """ Takes Gerber files as input and generates an SVG of them """ def normalise_gerber_number(gerber_number, axis, form): """ Takes a Gerber number and converts it into a float using the formatting defined in the Gerber header """ # TODO: actually support anything other than leading zeros number = gerber_number / pow(10.0, form[axis]['decimal']) return number def parsed_grammar_to_dict(parsed_grammar): """ Converts the Gerber parsing results to an SVG. """ gerber_dict = {} current_aperture = None new_shape = True for line in parsed_grammar: if line.dump(): if (line.format): if gerber_dict.get('format') is None: gerber_dict['format'] = {} tmp = gerber_dict['format'] tmp['notation'] = line['format']['notation'] tmp['zeros'] = line['format']['zeros'] tmp['x'] = {} tmp['x']['integer'] = line['format']['x']['integer'] tmp['x']['decimal'] = line['format']['x']['decimal'] tmp['y'] = {} tmp['y']['integer'] = line['format']['x']['integer'] tmp['y']['decimal'] = line['format']['x']['decimal'] elif (line.units): gerber_dict['units'] = line['units']['units'] elif (line.aperture_definition): tmp = {} if line['aperture_definition']['type'] == 'circle': tmp['type'] = 'circle' tmp['diameter'] = line['aperture_definition'][ 'diameter'] tmp['number'] = line['aperture_definition']['number'] elif line['aperture_definition']['type'] == 'rect': tmp['type'] = 'rect' tmp['width'] = line['aperture_definition']['width'] tmp['height'] = line['aperture_definition']['height'] tmp['number'] = line['aperture_definition']['number'] else: print "ERROR: cannot recognise aperture definition type" if gerber_dict.get('aperture-definitions') is None: gerber_dict['aperture-definitions'] = [] gerber_dict['aperture-definitions'].append(tmp) elif line.polarity_change: if gerber_dict.get('features') is None: gerber_dict['features'] = [] polarity = line['polarity_change']['polarity'] polarity_dict = {} polarity_dict['polarity'] = polarity polarity_dict['shapes'] = [] gerber_dict['features'].append(polarity_dict) elif line.aperture_change: tmp = {} tmp['type'] = 'aperture-change' tmp['number'] = line.aperture_change['number'] #if len(gerber_dict['features'][-1]['shapes'] == 0): gerber_dict['features'][-1]['shapes'].append(tmp) #else: # gerber_dict['features'][-1]['shapes'].append(tmp) tmp = {} tmp['type'] = 'stroke' tmp['segments'] = [] gerber_dict['features'][-1]['shapes'].append(tmp) elif line.start_closed_shape: tmp = {} tmp['type'] = 'fill' tmp['segments'] = [] gerber_dict['features'][-1]['shapes'].append(tmp) elif line.move or line.draw or line.flash: # TODO: hack alert! (Got to get shit done, you know? Don't judge me!) if line.move: command_name = 'move' item = line.move if line.draw: command_name = 'draw' item = line.draw if line.flash: command_name = 'flash' item = line.flash point = Point( normalise_gerber_number(item['x'], 'x', gerber_dict['format']), normalise_gerber_number(item['y'], 'y', gerber_dict['format'])) tmp = {} tmp['type'] = command_name tmp['coord'] = point gerber_dict['features'][-1]['shapes'][-1][ 'segments'].append(tmp) elif line.end_closed_shape: new_shape = True return gerber_dict def create_gerber_svg_data(gerber_data): """ Returns an SVG element of the input Gerber data """ gerber_data_parsed = gerber_grammar.parseString(gerber_data) gerber_data_dict = parsed_grammar_to_dict(gerber_data_parsed) gerber_data_svg = svg.generate_svg_from_gerber_dict(gerber_data_dict) return gerber_data_svg # get the board's shape / outline board_shape_gerber_lp = None shape = config.brd['board_outline']['shape'] board_shape_type = shape.get('type') if board_shape_type in ['rect', 'rectangle']: offset = utils.to_Point(shape.get('offset') or [0, 0]) board_shape_path = svg.rect_to_path(shape) elif board_shape_type == 'path': board_shape_path = shape.get('value') board_shape_gerber_lp = shape.get('gerber_lp') if board_shape_path is None: print "ERROR: couldn't find a path under key 'value' for board outline" else: print "ERROR: unrecognised board shape type: %s. Possible options are 'rect' or 'path'" % board_shape_type # convert path to relative board_shape_path_relative = svg.absolute_to_relative_path(board_shape_path) # this will return a path having an origin at the center of the shape # defined by the path board_width, board_height, board_outline = svg.transform_path( board_shape_path_relative, True) display_width = board_width display_height = board_height #transform = 'translate(' + str(round((board_width)/2, SD)) + ' ' + str(round((board_height)/2, SD)) + ')' sig_dig = config.cfg['significant-digits'] #transform = 'translate(%s %s)' % (round(board_width/2, sig_dig), # round(board_height/2, sig_dig)) # extra buffer for display frame display_frame_buffer = config.cfg.get('display-frame-buffer') or 1.0 gerber = et.Element( 'svg', width=str(display_width) + config.brd['config']['units'], height=str(display_height) + config.brd['config']['units'], viewBox=str(-display_frame_buffer / 2) + ' ' + str(-display_frame_buffer / 2) + ' ' + str(board_width + display_frame_buffer) + ' ' + str(board_height + display_frame_buffer), version='1.1', nsmap=cfg['namespace'], fill='black') doc = et.ElementTree(gerber) gerber_layers = svg.create_layers_for_gerber_svg(gerber) # directory for where to expect the Gerbers within the build path # regardless of the source of the Gerbers, the PCBmodE directory # structure is assumed production_path = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], 'production') # get board information from configuration file pcbmode_version = config.cfg['version'] board_name = config.cfg['name'] board_revision = config.brd['config'].get('rev') base_name = "%s_rev_%s" % (board_name, board_revision) gerber_grammar = gerber_grammar_generator() for foil in ['outline']: #, 'documentation']: gerber_file = os.path.join(production_path, base_name + '_%s.ger' % (foil)) gerber_data = open(gerber_file, 'r').read() gerber_svg = create_gerber_svg_data(gerber_data) gerber_svg_layer = gerber_layers[foil]['layer'] gerber_svg_layer.append(gerber_svg) print foil for pcb_layer in utils.getSurfaceLayers(): for foil in ['copper', 'silkscreen', 'soldermask']: gerber_file = os.path.join( production_path, base_name + '_%s_%s.ger' % (pcb_layer, foil)) gerber_data = open(gerber_file, 'r').read() gerber_svg = create_gerber_svg_data(gerber_data) gerber_svg_layer = gerber_layers[pcb_layer][foil]['layer'] gerber_svg_layer.append(gerber_svg) print foil output_file = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], cfg['board_name'] + '_gerber.svg') try: f = open(output_file, 'wb') except IOError as e: print "I/O error({0}): {1}".format(e.errno, e.strerror) f.write(et.tostring(doc, pretty_print=True)) f.close() return
def _placeShapes(self): """ """ mirror = False try: shapes_dict = self._module_dict['shapes'] except: return for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen']: try: shapes = shapes_dict[sheet] except KeyError: continue there_are_pours = {} shape_groups = {} for pcb_layer in utils.getSurfaceLayers(): there_are_pours[pcb_layer] = utils.checkForPoursInLayer( pcb_layer) shape_groups[pcb_layer] = et.SubElement( self._layers[pcb_layer][sheet]['layer'], 'g') shape_groups[pcb_layer].set( '{' + config.cfg['ns']['pcbmode'] + '}type', 'module-shapes') for shape_dict in shapes: pcb_layers = shape_dict.get('layers') or ['top'] for pcb_layer in pcb_layers: # Shapes placed on the bottom layer are not mirrored # unless they are text, in which case, it's the # expected behaviour and so it is mirrored by default # unless otherwise instructed. try: shape_mirror = shape_dict.get['mirror'] except: if shape_dict['type'] == 'text': shape_mirror = True else: shape_mirror = False if (pcb_layer == 'bottom') and (shape_mirror != False): shape_dict['location'][0] *= -1 mirror = True else: mirror = False shape = Shape(shape_dict) style = Style(shape_dict, sheet) shape.setStyle(style) place.placeShape(shape, shape_groups[pcb_layer], mirror) # Place mask for pour if copper shape if (sheet == 'copper') and (there_are_pours[pcb_layer] == True): location = shape.getLocation() transform = "translate(%s,%s)" % (location.x, location.y) mask_group = et.SubElement(self._masks[pcb_layer], 'g') self._placeMask(svg_layer=mask_group, shape=shape, kind='pad', original=False, mirror=mirror)
def _placeRouting(self): """ """ routing = config.rte routes = routing.get('routes') or {} vias = routing.get('vias') # Path effects are used for meandering paths, for example path_effects = routes.get('path_effects') xpath_expr = "//g[@inkscape:label='%s']//g[@inkscape:label='%s']" extra_attributes = [ 'inkscape:connector-curvature', 'inkscape:original-d', 'inkscape:path-effect' ] for pcb_layer in utils.getSurfaceLayers(): # Are there pours in the layer? This makes a difference for whether to place # masks there_are_pours = utils.checkForPoursInLayer(pcb_layer) # Define a group where masks are stored mask_group = et.SubElement(self._masks[pcb_layer], 'g') # Place defined routes on this SVG layer sheet = self._layers[pcb_layer]['copper']['routing']['layer'] for route_key in (routes.get(pcb_layer) or {}): shape_dict = routes[pcb_layer][route_key] shape = Shape(shape_dict) style = Style(shape_dict, 'copper') shape.setStyle(style) # Routes are a special case where they are used as-is # counting on Inkscapes 'optimised' setting to modify # the path such that placement is refleced in # it. Therefor we use the original path, not the # transformed one as usual use_original_path = True mirror_path = False route_element = place.placeShape(shape, sheet, mirror_path, use_original_path) route_element.set('style', shape.getStyleString()) # Set the key as pcbmode:id of the route. This is used # when extracting routing to offset the location of a # modified route route_element.set( '{' + config.cfg['ns']['pcbmode'] + '}%s' % ('id'), route_key) # Add a custom buffer definition if it exists custom_buffer = shape_dict.get('buffer-to-pour') if custom_buffer != None: route_element.set( '{' + config.cfg['namespace']['pcbmode'] + '}%s' % "buffer-to-pour", str(custom_buffer)) # TODO: can this be done more elegantly, and "general purpose"? for extra_attrib in extra_attributes: ea = shape_dict.get(extra_attrib) if ea is not None: route_element.set( '{' + config.cfg['namespace']['inkscape'] + '}%s' % (extra_attrib[extra_attrib.index(':') + 1:], ea)) # TODO: this needs to eventually go away or be done properly pcbmode_params = shape_dict.get('pcbmode') if pcbmode_params is not None: route_element.set('pcbmode', pcbmode_params) if ((there_are_pours == True) and (custom_buffer != "0")): self._placeMask(self._masks[pcb_layer], shape, 'route', use_original_path)
def gerberise(manufacturer='default'): """ Generate Gerbers for one or more layers """ # Open the board's SVG svg_in = utils.openBoardSVG() # Get Gerber generation settings gcd = config.brd['gerber'] decimals = gcd['decimals'] digits = gcd['digits'] steps = gcd['steps-per-segment'] length = gcd['min-segment-length'] # Get layer data xpath_regex = "" ns = {'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']} # Save to file base_dir = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], 'production') # Create directory if it doesn't exist already utils.create_dir(base_dir) base_name = "%s_rev_%s" % (config.brd['config']['name'], config.brd['config']['rev']) filename_info = config.cfg['manufacturers'][manufacturer]['filenames']['gerbers'] # Process Gerbers for PCB layers and sheets for pcb_layer in utils.getSurfaceLayers(): # Get the SVG layer corresponding to the PCB layer svg_layer = svg_in.find("//svg:g[@pcbmode:pcb-layer='%s']" % (pcb_layer), namespaces=ns) # Get masks (must be placed right after pours) mask_paths = svg_in.findall(".//svg:defs//svg:mask[@pcbmode:pcb-layer='%s']//svg:path" % pcb_layer, namespaces=ns) sheets = ['copper', 'soldermask', 'solderpaste', 'silkscreen'] for sheet in sheets: # Get the SVG layer corresponding to the 'sheet' sheet_layer = svg_layer.find(".//svg:g[@pcbmode:sheet='%s']" % (sheet), namespaces=ns) if sheet == 'copper': mask_paths_to_pass = mask_paths else: mask_paths_to_pass = [] # Create a Gerber object gerber = Gerber(sheet_layer, mask_paths_to_pass, decimals, digits, steps, length) try: ext = filename_info[pcb_layer][sheet]['ext'] except KeyError: ext = 'ger' add = '_%s_%s.%s' % (pcb_layer, sheet, ext) filename = os.path.join(base_dir, base_name + add) with open(filename, "wb") as f: for line in gerber.getGerber(): f.write(line) # Process module sheets sheets = ['outline', 'documentation'] for sheet in sheets: # Get the SVG layer corresponding to the 'sheet' sheet_layer = svg_in.find(".//svg:g[@pcbmode:sheet='%s']" % (sheet), namespaces=ns) # Create a Gerber object gerber = Gerber(sheet_layer, [], decimals, digits, steps, length) add = '_%s.%s' % (sheet, filename_info['other'][sheet].get('ext') or 'ger') filename = os.path.join(base_dir, base_name + add) with open(filename, "wb") as f: for line in gerber.getGerber(False): f.write(line) return ['bullshit']
def __init__(self, module_dict, routing_dict, asmodule=False): """ """ self._module_dict = module_dict self._routing_dict = routing_dict self._outline = self._getOutline() self._width = self._outline.getWidth() self._height = self._outline.getHeight() # Get dictionary of component definitions components_dict = self._module_dict.get('components') or {} self._components = self._getComponents(components_dict) # Get dictionary of component definitions vias_dict = self._routing_dict.get('vias') or {} self._vias = self._getComponents(vias_dict) sig_dig = config.cfg['significant-digits'] self._transform = 'translate(%s %s)' % (round(self._width/2, sig_dig), round(self._height/2, sig_dig)) # Create the Inkscape SVG document self._module = self._getModuleElement() svg_doc = et.ElementTree(self._module) # Get a dictionary of SVG layers self._layers = svg.makeSvgLayers(self._module, self._transform) # Add a 'defs' element: # http://www.w3.org/TR/SVG/struct.html#Head # This is where masking elements that are used for pours are stored defs = et.SubElement(self._module, 'defs') self._masks = {} for pcb_layer in utils.getSurfaceLayers(): element = et.SubElement(defs, 'mask', id="mask-%s" % pcb_layer, transform=self._transform) # This will identify the masks for each PCB layer when # the layer is converted to Gerber element.set('{'+config.cfg['ns']['pcbmode']+'}pcb-layer', pcb_layer) self._masks[pcb_layer] = element self._placeOutline() self._placeOutlineDimensions() msg.subInfo('Placing components:', newline=False) self._placeComponents(self._components, 'components', print_refdef=True) print msg.subInfo('Placing routes') self._placeRouting() msg.subInfo('Placing vias') self._placeComponents(self._vias, 'vias') msg.subInfo('Placing shapes') self._placeShapes() msg.subInfo('Placing pours') self._placePours() if config.tmp['no-docs'] == False: msg.subInfo('Placing documentation') self._placeDocs() if config.tmp['no-drill-index'] == False: msg.subInfo('Placing drill index') self._placeDrillIndex() if config.tmp['no-layer-index'] == False: msg.subInfo('Placing layer index') self._placeLayerIndex() # This 'cover' "enables" the mask shapes defined in the mask are # shown. It *must* be the last element in the mask definition; # any mask element after it won't show for pcb_layer in utils.getSurfaceLayers(): if utils.checkForPoursInLayer(pcb_layer) is True: mask_cover = et.SubElement(self._masks[pcb_layer], 'rect', x="%s" % str(-self._width/2), y="%s" % str(-self._height/2), width="%s" % self._width, height="%s" % self._height, style="fill:#fff;") # This tells the Gerber conversion to ignore this shape mask_cover.set('{'+config.cfg['ns']['pcbmode']+'}type', 'mask-cover') output_file = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], config.cfg['name'] + '.svg') try: f = open(output_file, 'wb') except IOError as e: print "I/O error({0}): {1}".format(e.errno, e.strerror) f.write(et.tostring(svg_doc, pretty_print=True)) f.close()
def _placeShapes(self): """ """ mirror = False try: shapes_dict = self._module_dict['shapes'] except: return for sheet in ['copper','soldermask','solderpaste','silkscreen']: try: shapes = shapes_dict[sheet] except KeyError: continue there_are_pours = {} shape_groups = {} for pcb_layer in utils.getSurfaceLayers(): there_are_pours[pcb_layer] = utils.checkForPoursInLayer(pcb_layer) shape_groups[pcb_layer] = et.SubElement(self._layers[pcb_layer][sheet]['layer'], 'g') shape_groups[pcb_layer].set('{'+config.cfg['ns']['pcbmode']+'}type', 'module-shapes') for shape_dict in shapes: pcb_layers = shape_dict.get('layers') or ['top'] for pcb_layer in pcb_layers: # Shapes placed on the bottom layer are not mirrored # unless they are text, in which case, it's the # expected behaviour and so it is mirrored by default # unless otherwise instructed. try: shape_mirror = shape_dict.get['mirror'] except: if shape_dict['type'] == 'text': shape_mirror = True else: shape_mirror = False if (pcb_layer == 'bottom') and (shape_mirror != False): mirror = True else: mirror = False shape = Shape(shape_dict) style = Style(shape_dict, sheet) shape.setStyle(style) place.placeShape(shape, shape_groups[pcb_layer], mirror) # Place mask for pour if copper shape if (sheet == 'copper') and (there_are_pours[pcb_layer] == True): location = shape.getLocation() transform = "translate(%s,%s)" % (location.x, location.y) mask_group = et.SubElement(self._masks[pcb_layer], 'g') #id="routing_masks") #transform=transform) self._placeMask(svg_layer=mask_group, shape=shape, kind='pad', original=False, mirror=False)
def _placeRouting(self): """ """ routing = config.rte routes = routing.get('routes') or {} vias = routing.get('vias') # Path effects are used for meandering paths, for example path_effects = routes.get('path_effects') xpath_expr = "//g[@inkscape:label='%s']//g[@inkscape:label='%s']" extra_attributes = ['inkscape:connector-curvature', 'inkscape:original-d', 'inkscape:path-effect'] for pcb_layer in utils.getSurfaceLayers(): # Are there pours in the layer? This makes a difference for whether to place # masks there_are_pours = utils.checkForPoursInLayer(pcb_layer) # Define a group where masks are stored mask_group = et.SubElement(self._masks[pcb_layer], 'g') # Place defined routes on this SVG layer sheet = self._layers[pcb_layer]['copper']['routing']['layer'] for route_key in (routes.get(pcb_layer) or {}): shape_dict = routes[pcb_layer][route_key] shape = Shape(shape_dict) style = Style(shape_dict, 'copper') shape.setStyle(style) # Routes are a special case where they are used as-is # counting on Inkscapes 'optimised' setting to modify # the path such that placement is refleced in # it. Therefor we use the original path, not the # transformed one as usual use_original_path = True mirror_path = False route_element = place.placeShape(shape, sheet, mirror_path, use_original_path) route_element.set('style', shape.getStyleString()) # Set the key as pcbmode:id of the route. This is used # when extracting routing to offset the location of a # modified route route_element.set('{'+config.cfg['ns']['pcbmode']+'}%s' % ('id'), route_key) # Add a custom buffer definition if it exists custom_buffer = shape_dict.get('buffer-to-pour') if custom_buffer != None: route_element.set('{'+config.cfg['namespace']['pcbmode']+'}%s' % "buffer-to-pour", str(custom_buffer)) # TODO: can this be done more elegantly, and "general purpose"? for extra_attrib in extra_attributes: ea = shape_dict.get(extra_attrib) if ea is not None: route_element.set('{'+config.cfg['namespace']['inkscape']+'}%s' % (extra_attrib[extra_attrib.index(':')+1:], ea)) # TODO: this needs to eventually go away or be done properly pcbmode_params = shape_dict.get('pcbmode') if pcbmode_params is not None: route_element.set('pcbmode', pcbmode_params) if ((there_are_pours == True) and (custom_buffer != "0")): self._placeMask(self._masks[pcb_layer], shape, 'route', use_original_path)
def _placeComponents(self, components, component_type, print_refdef=False): """ """ for component in components: shapes_dict = component.getShapes() location = component.getLocation() refdef = component.getRefdef() if print_refdef == True: print refdef, # If the component is placed on the bottom layer we need # to invert the shapes AND their 'x' coordinate. This is # done using the 'invert' indicator set below placement_layer = component.getPlacementLayer() if placement_layer == 'bottom': invert = True else: invert = False for pcb_layer in utils.getSurfaceLayers(): there_are_pours = utils.checkForPoursInLayer(pcb_layer) # Copper shapes = shapes_dict['copper'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['copper']['pads']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) if component_type == 'components': group.set('{'+config.cfg['ns']['pcbmode']+'}refdef', component.getRefdef()) elif component_type == 'vias': group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'via') group.set('{'+config.cfg['ns']['pcbmode']+'}via', component.getFootprintName()) else: pass for shape in shapes: place.placeShape(shape, group, invert) if there_are_pours == True: mask_group = et.SubElement(self._masks[pcb_layer], 'g', transform=transform) self._placeMask(mask_group, shape, 'pad', original=False, mirror=invert) # Add pin labels # There's a bit of a hack here that won't work in # all possible cases (where pads are placed on # bottom layer in a component that has pins placed # both on the top and on the bottom -- a rare # case). Good enough for now labels = shapes_dict['pin-labels']['top']#[pcb_layer] if labels != []: style = utils.dictToStyleText(config.stl['layout']['board']['pad-labels']) label_group = et.SubElement(group, 'g', transform="rotate(%s)" % (((1,-1)[invert])*component.getRotation()), style=style) for label in labels: t = et.SubElement(label_group, 'text', x=str(((1,-1)[invert])*label['location'][0]), y=str(config.cfg['invert-y']*label['location'][1])) t.text = label['text'] # Soldermask shapes = shapes_dict['soldermask'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['soldermask']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group, invert) # Solderpaste shapes = shapes_dict['solderpaste'][pcb_layer] svg_layer = self._layers[pcb_layer]['solderpaste']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group, invert) # Silkscreen shapes = shapes_dict['silkscreen'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['silkscreen']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) shape_group = et.SubElement(svg_layer, 'g', transform=transform) shape_group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'component-shapes') for shape in shapes: # Refdefs need to be in their own groups so that their # location can later be extracted, hence this... try: is_refdef = getattr(shape, 'is_refdef') except: is_refdef = False if is_refdef == True: refdef_group = et.SubElement(svg_layer, 'g', transform=transform) refdef_group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'refdef') refdef_group.set('{'+config.cfg['ns']['pcbmode']+'}refdef', refdef) placed_element = place.placeShape(shape, refdef_group, invert) else: placed_element = place.placeShape(shape, shape_group, invert) # Assembly shapes = shapes_dict['assembly'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['assembly']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) for shape in shapes: placed_element = place.placeShape(shape, group, invert) # Drills shapes = shapes_dict['drills'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers['drills']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group) placed_element.set('{'+config.cfg['ns']['pcbmode']+'}diameter', str(shape.getDiameter()))
def _placeComponents(self, components, component_type, print_refdef=False): """ """ for component in components: shapes_dict = component.getShapes() location = component.getLocation() refdef = component.getRefdef() if print_refdef == True: print refdef, # If the component is placed on the bottom layer we need # to invert the shapes AND their 'x' coordinate. This is # done using the 'invert' indicator set below placement_layer = component.getPlacementLayer() if placement_layer == 'bottom': invert = True else: invert = False for pcb_layer in utils.getSurfaceLayers(): there_are_pours = utils.checkForPoursInLayer(pcb_layer) # Copper shapes = shapes_dict['copper'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['copper']['pads'][ 'layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) if component_type == 'components': group.set( '{' + config.cfg['ns']['pcbmode'] + '}refdef', component.getRefdef()) elif component_type == 'vias': group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'via') group.set('{' + config.cfg['ns']['pcbmode'] + '}via', component.getFootprintName()) else: pass for shape in shapes: place.placeShape(shape, group, invert) if there_are_pours == True: mask_group = et.SubElement(self._masks[pcb_layer], 'g', transform=transform) self._placeMask(mask_group, shape, 'pad', original=False, mirror=invert) # Add pin labels # There's a bit of a hack here that won't work in # all possible cases (where pads are placed on # bottom layer in a component that has pins placed # both on the top and on the bottom -- a rare # case). Good enough for now labels = shapes_dict['pin-labels']['top'] #[pcb_layer] if labels != []: style = utils.dictToStyleText( config.stl['layout']['board']['pad-labels']) label_group = et.SubElement( group, 'g', transform="rotate(%s)" % (((1, -1)[invert]) * component.getRotation()), style=style) for label in labels: t = et.SubElement(label_group, 'text', x=str(((1, -1)[invert]) * label['location'][0]), y=str(config.cfg['invert-y'] * label['location'][1])) t.text = label['text'] # Soldermask shapes = shapes_dict['soldermask'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['soldermask']['layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group, invert) # Solderpaste shapes = shapes_dict['solderpaste'][pcb_layer] svg_layer = self._layers[pcb_layer]['solderpaste']['layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group, invert) # Silkscreen shapes = shapes_dict['silkscreen'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['silkscreen']['layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) shape_group = et.SubElement(svg_layer, 'g', transform=transform) shape_group.set( '{' + config.cfg['ns']['pcbmode'] + '}type', 'component-shapes') for shape in shapes: # Refdefs need to be in their own groups so that their # location can later be extracted, hence this... try: is_refdef = getattr(shape, 'is_refdef') except: is_refdef = False if is_refdef == True: refdef_group = et.SubElement(svg_layer, 'g', transform=transform) refdef_group.set( '{' + config.cfg['ns']['pcbmode'] + '}type', 'refdef') refdef_group.set( '{' + config.cfg['ns']['pcbmode'] + '}refdef', refdef) placed_element = place.placeShape( shape, refdef_group, invert) else: placed_element = place.placeShape( shape, shape_group, invert) # Assembly shapes = shapes_dict['assembly'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['assembly']['layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) for shape in shapes: placed_element = place.placeShape(shape, group, invert) # Drills shapes = shapes_dict['drills'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers['drills']['layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group, invert) placed_element.set( '{' + config.cfg['ns']['pcbmode'] + '}diameter', str(shape.getDiameter()))
def __init__(self, refdef, component): """ """ self._refdef = refdef self._layer = component.get('layer') or 'top' self._rotate = component.get('rotate') or 0 if self._layer=='bottom': self._rotate *= -1 self._rotate_point = utils.toPoint(component.get('rotate-point') or [0, 0]) self._scale = component.get('scale') or 1 self._location = component.get('location') or [0, 0] # Get footprint definition and shapes try: self._footprint_name = component['footprint'] except: msg.error("Cannot find a 'footprint' name for refdef %s." % refdef) filename = self._footprint_name + '.json' paths = [os.path.join(config.cfg['base-dir'], config.cfg['locations']['shapes'], filename), os.path.join(config.cfg['base-dir'], config.cfg['locations']['components'], filename)] footprint_dict = None for path in paths: if os.path.isfile(path): footprint_dict = utils.dictFromJsonFile(path) break if footprint_dict == None: fname_list = "" for path in paths: fname_list += " %s" % path msg.error("Couldn't find shape file. Looked for it here:\n%s" % (fname_list)) footprint = Footprint(footprint_dict) footprint_shapes = footprint.getShapes() #------------------------------------------------ # Apply component-specific modifiers to footprint #------------------------------------------------ for sheet in ['conductor', 'soldermask', 'solderpaste', 'pours', 'silkscreen', 'assembly', 'drills']: for layer in config.stk['layer-names']: for shape in footprint_shapes[sheet].get(layer) or []: # In order to apply the rotation we need to adust the location shape.rotateLocation(self._rotate, self._rotate_point) shape.transformPath(scale=self._scale, rotate=self._rotate, rotate_point=self._rotate_point, mirror=shape.getMirrorPlacement(), add=True) #-------------------------------------------------------------- # Remove silkscreen and assembly shapes if instructed #-------------------------------------------------------------- # If the 'show' flag is 'false then remove these items from the # shapes dictionary #-------------------------------------------------------------- for sheet in ['silkscreen','assembly']: try: shapes_dict = component[sheet].get('shapes') or {} except: shapes_dict = {} # If the setting is to not show silkscreen shapes for the # component, delete the shapes from the shapes' dictionary if shapes_dict.get('show') == False: for pcb_layer in utils.getSurfaceLayers(): footprint_shapes[sheet][pcb_layer] = [] #---------------------------------------------------------- # Add silkscreen and assembly reference designator (refdef) #---------------------------------------------------------- for sheet in ['silkscreen','assembly']: try: refdef_dict = component[sheet].get('refdef') or {} except: refdef_dict = {} if refdef_dict.get('show') != False: layer = refdef_dict.get('layer') or 'top' # Rotate the refdef; if unspecified the rotation is the same as # the rotation of the component refdef_dict['rotate'] = refdef_dict.get('rotate') or 0 # Sometimes you'd want to keep all refdefs at the same angle # and not rotated with the component if refdef_dict.get('rotate-with-component') != False: refdef_dict['rotate'] += self._rotate refdef_dict['rotate-point'] = utils.toPoint(refdef_dict.get('rotate-point')) or self._rotate_point refdef_dict['location'] = refdef_dict.get('location') or [0, 0] refdef_dict['type'] = 'text' refdef_dict['value'] = refdef_dict.get('value') or refdef refdef_dict['font-family'] = (refdef_dict.get('font-family') or config.stl['layout'][sheet]['refdef'].get('font-family') or config.stl['defaults']['font-family']) refdef_dict['font-size'] = (refdef_dict.get('font-size') or config.stl['layout'][sheet]['refdef'].get('font-size') or "2mm") refdef_shape = Shape(refdef_dict) refdef_shape.is_refdef = True refdef_shape.rotateLocation(self._rotate, self._rotate_point) style = Style(refdef_dict, sheet, 'refdef') refdef_shape.setStyle(style) # Add the refdef to the silkscreen/assembly list. It's # important that this is added at the very end since the # placement process assumes the refdef is last try: footprint_shapes[sheet][layer] except: footprint_shapes[sheet][layer] = [] footprint_shapes[sheet][layer].append(refdef_shape) #------------------------------------------------------ # Invert layers #------------------------------------------------------ # If the placement is on the bottom of the baord then we need # to invert the placement of all components. This affects the # surface laters but also internal layers if self._layer == 'bottom': layers = config.stk['layer-names'] for sheet in ['conductor', 'pours', 'soldermask', 'solderpaste', 'silkscreen', 'assembly']: sheet_dict = footprint_shapes[sheet] sheet_dict_new = {} for i, pcb_layer in enumerate(layers): try: sheet_dict_new[layers[len(layers)-i-1]] = copy.copy(sheet_dict[pcb_layer]) except: continue footprint_shapes[sheet] = copy.copy(sheet_dict_new) self._footprint_shapes = footprint_shapes
def __init__(self, refdef, component): """ """ self._refdef = refdef self._layer = component.get('layer') or 'top' self._rotate = component.get('rotate') or 0 self._rotate_point = utils.toPoint(component.get('rotate-point') or [0, 0]) self._scale = component.get('scale') or 1 self._location = component.get('location') or [0, 0] # Get footprint definition and shapes try: self._footprint_name = component['footprint'] except: msg.error("Cannot find a 'footprint' name for refdef %s." % refdef) fname = os.path.join(config.cfg['base-dir'], config.cfg['locations']['components'], self._footprint_name + '.json') footprint_dict = utils.dictFromJsonFile(fname) footprint = Footprint(footprint_dict) footprint_shapes = footprint.getShapes() #------------------------------------------------ # Apply component-specific modifiers to footprint #------------------------------------------------ for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly', 'drills']: for layer in utils.getSurfaceLayers() + utils.getInternalLayers(): for shape in footprint_shapes[sheet][layer]: # In order to apply the rotation we need to adust the location # of each element shape.rotateLocation(self._rotate, self._rotate_point) shape.transformPath(self._scale, self._rotate, self._rotate_point, False, True) #-------------------------------------- # Remove silkscreen and assembly shapes #-------------------------------------- for sheet in ['silkscreen','assembly']: try: shapes_dict = component[sheet].get('shapes') or {} except: shapes_dict = {} # If the setting is to not show silkscreen shapes for the # component, delete the shapes from the shapes' dictionary if shapes_dict.get('show') == False: for pcb_layer in utils.getSurfaceLayers(): footprint_shapes[sheet][pcb_layer] = [] #---------------------------------------------------------- # Add silkscreen and assembly reference designator (refdef) #---------------------------------------------------------- for sheet in ['silkscreen','assembly']: try: refdef_dict = component[sheet].get('refdef') or {} except: refdef_dict = {} if refdef_dict.get('show') != False: layer = refdef_dict.get('layer') or 'top' # Rotate the refdef; if unspecified the rotation is the same as # the rotation of the component refdef_dict['rotate'] = refdef_dict.get('rotate') or 0 # Sometimes you'd want to keep all refdefs at the same angle # and not rotated with the component if refdef_dict.get('rotate-with-component') != False: refdef_dict['rotate'] += self._rotate refdef_dict['rotate-point'] = utils.toPoint(refdef_dict.get('rotate-point')) or self._rotate_point refdef_dict['location'] = refdef_dict.get('location') or [0, 0] refdef_dict['type'] = 'text' refdef_dict['value'] = refdef_dict.get('value') or refdef refdef_dict['font-family'] = (refdef_dict.get('font-family') or config.stl['layout'][sheet]['refdef'].get('font-family') or config.stl['defaults']['font-family']) refdef_dict['font-size'] = (refdef_dict.get('font-size') or config.stl['layout'][sheet]['refdef'].get('font-size') or "2mm") refdef_shape = Shape(refdef_dict) refdef_shape.is_refdef = True refdef_shape.rotateLocation(self._rotate, self._rotate_point) style = Style(refdef_dict, sheet, 'refdef') refdef_shape.setStyle(style) # Add the refdef to the silkscreen/assembly list. It's # important that this is added at the very end since the # placement process assumes the refdef is last footprint_shapes[sheet][layer].append(refdef_shape) #------------------------------------------------------ # Invert 'top' and 'bottom' if layer is on the 'bottom' #------------------------------------------------------ if self._layer == 'bottom': layers = utils.getSurfaceLayers() layers_reversed = reversed(utils.getSurfaceLayers()) for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly']: sheet_dict = footprint_shapes[sheet] # TODO: this nasty hack won't work for more than two # layers, so when 2+ are supported this needs to be # revisited for i in range(0,len(layers)-1): sheet_dict['temp'] = sheet_dict.pop(layers[i]) sheet_dict[layers[i]] = sheet_dict.pop(layers[len(layers)-1-i]) sheet_dict[layers[len(layers)-1-i]] = sheet_dict.pop('temp') self._footprint_shapes = footprint_shapes
def __init__(self, refdef, component): """ """ self._refdef = refdef self._layer = component.get('layer') or 'top' self._rotate = component.get('rotate') or 0 self._rotate_point = utils.toPoint(component.get('rotate-point') or [0, 0]) self._scale = component.get('scale') or 1 self._location = component.get('location') or [0, 0] # Get footprint definition and shapes try: self._footprint_name = component['footprint'] except: msg.error("Cannot find a 'footprint' name for refdef %s." % refdef) fname = os.path.join(config.cfg['base-dir'], config.cfg['locations']['components'], self._footprint_name + '.json') footprint_dict = utils.dictFromJsonFile(fname) footprint = Footprint(footprint_dict) footprint_shapes = footprint.getShapes() #------------------------------------------------ # Apply component-specific modifiers to footprint #------------------------------------------------ for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly', 'drills']: for layer in utils.getSurfaceLayers() + utils.getInternalLayers(): for shape in footprint_shapes[sheet][layer]: # In order to apply the rotation we need to adust the location # of each element shape.rotateLocation(self._rotate, self._rotate_point) shape.transformPath(self._scale, self._rotate, self._rotate_point, False, True) #-------------------------------------- # Remove silkscreen and assembly shapes #-------------------------------------- for sheet in ['silkscreen','assembly']: try: shapes_dict = component[sheet].get('shapes') or {} except: shapes_dict = {} # If the setting is to not show silkscreen shapes for the # component, delete the shapes from the shapes' dictionary if shapes_dict.get('show') == False: for pcb_layer in utils.getSurfaceLayers(): footprint_shapes[sheet][pcb_layer] = [] #---------------------------------------------------------- # Add silkscreen and assembly reference designator (refdef) #---------------------------------------------------------- for sheet in ['silkscreen','assembly']: try: refdef_dict = component[sheet].get('refdef') or {} except: refdef_dict = {} if refdef_dict.get('show') != False: layer = refdef_dict.get('layer') or 'top' # Rotate the refdef; if unspecified the rotation is the same as # the rotation of the component refdef_dict['rotate'] = refdef_dict.get('rotate') or 0 # Sometimes you'd want to keep all refdefs at the same angle # and not rotated with the component if refdef_dict.get('rotate-with-component') != False: refdef_dict['rotate'] += self._rotate refdef_dict['rotate-point'] = utils.toPoint(refdef_dict.get('rotate-point')) or self._rotate_point refdef_dict['location'] = refdef_dict.get('location') or [0, 0] refdef_dict['type'] = 'text' refdef_dict['value'] = refdef refdef_dict['font-family'] = (config.stl['layout'][sheet]['refdef'].get('font-family') or config.stl['defaults']['font-family']) refdef_dict['font-size'] = (config.stl['layout'][sheet]['refdef'].get('font-size') or "2mm") refdef_shape = Shape(refdef_dict) refdef_shape.is_refdef = True refdef_shape.rotateLocation(self._rotate, self._rotate_point) style = Style(refdef_dict, sheet, 'refdef') refdef_shape.setStyle(style) # Add the refdef to the silkscreen/assembly list. It's # important that this is added at the very end since the # plcament process assumes the refdef is last footprint_shapes[sheet][layer].append(refdef_shape) #------------------------------------------------------ # Invert 'top' and 'bottom' if layer is on the 'bottom' #------------------------------------------------------ if self._layer == 'bottom': layers = utils.getSurfaceLayers() layers_reversed = reversed(utils.getSurfaceLayers()) for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly']: sheet_dict = footprint_shapes[sheet] # TODO: this nasty hack won't work for more than two # layers, so when 2+ are supported this needs to be # revisited for i in range(0,len(layers)-1): sheet_dict['temp'] = sheet_dict.pop(layers[i]) sheet_dict[layers[i]] = sheet_dict.pop(layers[len(layers)-1-i]) sheet_dict[layers[len(layers)-1-i]] = sheet_dict.pop('temp') self._footprint_shapes = footprint_shapes
def gerberise(manufacturer='default'): """ Generate Gerbers for one or more layers """ # Open the board's SVG svg_in = utils.openBoardSVG() # Get Gerber generation settings gcd = config.brd['gerber'] decimals = gcd['decimals'] digits = gcd['digits'] steps = gcd['steps-per-segment'] length = gcd['min-segment-length'] # Get layer data xpath_regex = "" ns = { 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] } # Save to file base_dir = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], 'production') # Create directory if it doesn't exist already utils.create_dir(base_dir) base_name = "%s_rev_%s" % (config.brd['config']['name'], config.brd['config']['rev']) filename_info = config.cfg['manufacturers'][manufacturer]['filenames'][ 'gerbers'] # Process Gerbers for PCB layers and sheets for pcb_layer in utils.getSurfaceLayers(): # Get the SVG layer corresponding to the PCB layer svg_layer = svg_in.find("//svg:g[@pcbmode:pcb-layer='%s']" % (pcb_layer), namespaces=ns) # Get masks (must be placed right after pours) mask_paths = svg_in.findall( ".//svg:defs//svg:mask[@pcbmode:pcb-layer='%s']//svg:path" % pcb_layer, namespaces=ns) sheets = ['copper', 'soldermask', 'solderpaste', 'silkscreen'] for sheet in sheets: # Get the SVG layer corresponding to the 'sheet' sheet_layer = svg_layer.find(".//svg:g[@pcbmode:sheet='%s']" % (sheet), namespaces=ns) if sheet == 'copper': mask_paths_to_pass = mask_paths else: mask_paths_to_pass = [] # Create a Gerber object gerber = Gerber(sheet_layer, mask_paths_to_pass, decimals, digits, steps, length) add = '_%s_%s.%s' % (pcb_layer, sheet, filename_info[pcb_layer][sheet].get('ext') or 'ger') filename = os.path.join(base_dir, base_name + add) with open(filename, "wb") as f: for line in gerber.getGerber(): f.write(line) # Process module sheets sheets = ['outline', 'documentation'] for sheet in sheets: # Get the SVG layer corresponding to the 'sheet' sheet_layer = svg_in.find(".//svg:g[@pcbmode:sheet='%s']" % (sheet), namespaces=ns) # Create a Gerber object gerber = Gerber(sheet_layer, [], decimals, digits, steps, length) add = '_%s.%s' % (sheet, filename_info['other'][sheet].get('ext') or 'ger') filename = os.path.join(base_dir, base_name + add) with open(filename, "wb") as f: for line in gerber.getGerber(False): f.write(line) return ['bullshit']
def _placeComponents(self, components, component_type, print_refdef=False): """ """ for component in components: shapes_dict = component.getShapes() location = component.getLocation() # If the component is placed on the bottom layer we need # to invert the shapes AND their 'x' coordinate. This is # sone using the 'invert' indicator set below refdef = component.getRefdef() if print_refdef == True: print refdef, placement_layer = component.getPlacementLayer() if placement_layer == 'bottom': invert = True else: invert = False for pcb_layer in utils.getSurfaceLayers(): there_are_pours = utils.checkForPoursInLayer(pcb_layer) # Copper shapes = shapes_dict['copper'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['copper']['pads'][ 'layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) if component_type == 'components': group.set( '{' + config.cfg['ns']['pcbmode'] + '}refdef', component.getRefdef()) elif component_type == 'vias': group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'via') group.set('{' + config.cfg['ns']['pcbmode'] + '}via', component.getFootprintName()) else: pass for shape in shapes: place.placeShape(shape, group) if there_are_pours == True: mask_group = et.SubElement(self._masks[pcb_layer], 'g', transform=transform) self._placeMask(mask_group, shape, 'pad') # Add pin labels labels = shapes_dict['pin-labels'][pcb_layer] if labels != []: style = utils.dictToStyleText( config.stl['layout']['board']['pad-labels']) label_group = et.SubElement(group, 'g', transform="rotate(%s)" % component.getRotation(), style=style) for label in labels: t = et.SubElement( label_group, 'text', x=str(label['location'][0]), # TODO: get rid of this hack y=str(-label['location'][1])) #y=str(-pin_location.y + pad_numbers_font_size/3), #refdef=self._refdef) t.text = label['text'] # Soldermask shapes = shapes_dict['soldermask'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['soldermask']['layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group) # Solderpaste shapes = shapes_dict['solderpaste'][pcb_layer] svg_layer = self._layers[pcb_layer]['solderpaste']['layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group) # Silkscreen shapes = shapes_dict['silkscreen'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['silkscreen']['layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) shape_group = et.SubElement(svg_layer, 'g', transform=transform) shape_group.set( '{' + config.cfg['ns']['pcbmode'] + '}type', 'component-shapes') for shape in shapes: # Refdefs need to be in their own groups so that their # location can later be extracted, hence this... try: is_refdef = getattr(shape, 'is_refdef') except: is_refdef = False if is_refdef == True: refdef_group = et.SubElement(svg_layer, 'g', transform=transform) refdef_group.set( '{' + config.cfg['ns']['pcbmode'] + '}type', 'refdef') refdef_group.set( '{' + config.cfg['ns']['pcbmode'] + '}refdef', refdef) placed_element = place.placeShape( shape, refdef_group) else: placed_element = place.placeShape( shape, shape_group) # Assembly shapes = shapes_dict['assembly'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['assembly']['layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) for shape in shapes: placed_element = place.placeShape(shape, group) # Drills shapes = shapes_dict['drills'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers['drills']['layer'] transform = "translate(%s,%s)" % ( location[0], config.cfg['invert-y'] * location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group) placed_element.set( '{' + config.cfg['ns']['pcbmode'] + '}diameter', str(shape.getDiameter()))
def extractRouting(svg_in): """ Extracts routing from the the 'routing' SVG layers of each PCB layer. Inkscape SVG layers for each PCB ('top', 'bottom', etc.) layer. """ # Open the routing file if it exists. The existing data is used # for stats displayed as PCBmodE is run. The file is then # overwritten. output_file = os.path.join(config.cfg['base-dir'], config.cfg['name'] + '_routing.json') try: routing_dict_old = utils.dictFromJsonFile(output_file, False) except: routing_dict_old = {'routes': {}} #--------------- # Extract routes #--------------- # Store extracted data here routing_dict = {} # The XPATH expression for extracting routes, but not vias xpath_expr = "//svg:g[@pcbmode:pcb-layer='%s']//svg:g[@pcbmode:sheet='routing']//svg:path[(@d) and not (@pcbmode:type='via')]" routes_dict = {'top': {}, 'bottom': {}} for pcb_layer in utils.getSurfaceLayers(): routes = svg_in.xpath(xpath_expr % pcb_layer, namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) for route in routes: route_dict = {} route_id = route.get('{'+config.cfg['ns']['pcbmode']+'}id') path = route.get('d') style_text = route.get('style') or '' # This hash digest provides a unique identifier for # the route based on its path, location, and style digest = utils.digest(path+ #str(location.x)+ #str(location.y)+ style_text) routes_dict[pcb_layer][digest] = {} routes_dict[pcb_layer][digest]['type'] = 'path' routes_dict[pcb_layer][digest]['value'] = path stroke_width = utils.getStyleAttrib(style_text, 'stroke-width') if stroke_width != None: routes_dict[pcb_layer][digest]['style'] = 'stroke' routes_dict[pcb_layer][digest]['stroke-width'] = round(float(stroke_width), 4) custom_buffer = route.get('{'+config.cfg['ns']['pcbmode']+'}buffer-to-pour') if custom_buffer != None: routes_dict[pcb_layer][digest]['buffer-to-pour'] = float(custom_buffer) gerber_lp = route.get('{'+config.cfg['ns']['pcbmode']+'}gerber-lp') if gerber_lp != None: routes_dict[pcb_layer][digest]['gerber-lp'] = gerber_lp routing_dict['routes'] = routes_dict # Create simple stats and display them total = 0 total_old = 0 new = 0 existing = 0 for pcb_layer in utils.getSurfaceLayers(): try: total += len(routing_dict['routes'][pcb_layer]) except: pass try: new_dict = routing_dict['routes'][pcb_layer] except: new_dict = {} try: old_dict = routing_dict_old['routes'][pcb_layer] except: old_dict = {} for key in new_dict: if key not in old_dict: new += 1 else: existing += 1 for pcb_layer in utils.getSurfaceLayers(): total_old += len(old_dict) message = "Extracted %s routes; %s new (or modified), %s existing" % (total, new, existing) if total_old > total: message += ", %s removed" % (total_old - total) msg.subInfo(message) #------------- # Extract vias #------------- # XPATH expression for extracting vias xpath_expr = "//svg:g[@pcbmode:pcb-layer='%s']//svg:g[@pcbmode:sheet='routing']//svg:path[@pcbmode:type='via']" # Get new vias; only search the top layer new_vias = svg_in.xpath(xpath_expr % 'top', namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) # XPATH expression for extracting vias xpath_expr = "//svg:g[@pcbmode:pcb-layer='%s']//svg:g[@pcbmode:sheet='pads']//svg:g[@pcbmode:type='via']" # Get new vias; only search the top layer vias = svg_in.xpath(xpath_expr % 'top', namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) vias_dict = {} for via in vias: transform = via.get('transform') if transform != None: transform_data = utils.parseTransform(transform) location = transform_data['location'] else: location = Point() # Invery 'y' axis if needed location.y *= config.cfg['invert-y'] digest = utils.digest("%s%s" % (location.x, location.y)) # Define a via, just like any other component, but disable # placement of refdef vias_dict[digest] = {} vias_dict[digest]['footprint'] = via.get('{'+config.cfg['ns']['pcbmode']+'}via') vias_dict[digest]['location'] = [location.x, location.y] vias_dict[digest]['silkscreen'] = {'refdef': {'show': False }} vias_dict[digest]['assembly'] = {'refdef': {'show': False }} vias_dict[digest]['layer'] = 'top' for via in new_vias: # A newly-defined via will have a location set through the # 'sodipodi' namespace and possible also through a transform try: sodipodi_loc = Point(via.get('{'+config.cfg['ns']['sodipodi']+'}cx'), via.get('{'+config.cfg['ns']['sodipodi']+'}cy')) except: sodipodi_loc = Pound() transform = via.get('transform') if transform != None: transform_data = utils.parseTransform(transform) location = transform_data['location'] else: location = Point() location += sodipodi_loc # Invery 'y' axis if needed location.y *= config.cfg['invert-y'] digest = utils.digest("%s%s" % (location.x, location.y)) # Define a via, just like any other component, but disable # placement of refdef vias_dict[digest] = {} vias_dict[digest]['footprint'] = via.get('{'+config.cfg['ns']['pcbmode']+'}via') vias_dict[digest]['location'] = [location.x, location.y] vias_dict[digest]['silkscreen'] = {'refdef': {'show': False }} vias_dict[digest]['assembly'] = {'refdef': {'show': False }} vias_dict[digest]['layer'] = 'top' routing_dict['vias'] = vias_dict # Display stats if len(vias_dict) == 0: msg.subInfo("No vias found") elif len(vias_dict) == 1: msg.subInfo("Extracted 1 via") else: msg.subInfo("Extracted %s vias" % (len(vias_dict))) # Save extracted routing into routing file try: with open(output_file, 'wb') as f: f.write(json.dumps(routing_dict, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % output_file) return
def extractRouting(svg_in): """ Extracts routing from the the 'routing' SVG layers of each PCB layer. Inkscape SVG layers for each PCB ('top', 'bottom', etc.) layer. """ # Open the routing file if it exists. The existing data is used # for stats displayed as PCBmodE is run. The file is then # overwritten. output_file = os.path.join(config.cfg['base-dir'], config.cfg['name'] + '_routing.json') try: routing_dict_old = utils.dictFromJsonFile(output_file, False) except: routing_dict_old = {'routes': {}} #--------------- # Extract routes #--------------- # Store extracted data here routing_dict = {} # The XPATH expression for extracting routes, but not vias xpath_expr = "//svg:g[@pcbmode:pcb-layer='%s']//svg:g[@pcbmode:sheet='routing']//svg:path[(@d) and not (@pcbmode:type='via')]" routes_dict = {'top': {}, 'bottom': {}} for pcb_layer in utils.getSurfaceLayers(): routes = svg_in.xpath(xpath_expr % pcb_layer, namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) for route in routes: route_dict = {} route_id = route.get('{' + config.cfg['ns']['pcbmode'] + '}id') path = route.get('d') style_text = route.get('style') or '' # This hash digest provides a unique identifier for # the route based on its path, location, and style digest = utils.digest(path + #str(location.x)+ #str(location.y)+ style_text) routes_dict[pcb_layer][digest] = {} routes_dict[pcb_layer][digest]['type'] = 'path' routes_dict[pcb_layer][digest]['value'] = path stroke_width = utils.getStyleAttrib(style_text, 'stroke-width') if stroke_width != None: # Sometimes Inkscape will add a 'px' suffix to the stroke-width #property pf a path; this removes it stroke_width = stroke_width.rstrip('px') routes_dict[pcb_layer][digest]['style'] = 'stroke' routes_dict[pcb_layer][digest]['stroke-width'] = round( float(stroke_width), 4) custom_buffer = route.get('{' + config.cfg['ns']['pcbmode'] + '}buffer-to-pour') if custom_buffer != None: routes_dict[pcb_layer][digest]['buffer-to-pour'] = float( custom_buffer) gerber_lp = route.get('{' + config.cfg['ns']['pcbmode'] + '}gerber-lp') if gerber_lp != None: routes_dict[pcb_layer][digest]['gerber-lp'] = gerber_lp routing_dict['routes'] = routes_dict # Create simple stats and display them total = 0 total_old = 0 new = 0 existing = 0 for pcb_layer in utils.getSurfaceLayers(): try: total += len(routing_dict['routes'][pcb_layer]) except: pass try: new_dict = routing_dict['routes'][pcb_layer] except: new_dict = {} try: old_dict = routing_dict_old['routes'][pcb_layer] except: old_dict = {} for key in new_dict: if key not in old_dict: new += 1 else: existing += 1 for pcb_layer in utils.getSurfaceLayers(): total_old += len(old_dict) message = "Extracted %s routes; %s new (or modified), %s existing" % ( total, new, existing) if total_old > total: message += ", %s removed" % (total_old - total) msg.subInfo(message) #------------- # Extract vias #------------- # XPATH expression for extracting vias xpath_expr = "//svg:g[@pcbmode:pcb-layer='%s']//svg:g[@pcbmode:sheet='routing']//svg:*[@pcbmode:type='via']" # Get new vias; only search the top layer new_vias = svg_in.xpath(xpath_expr % 'top', namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) # XPATH expression for extracting vias xpath_expr = "//svg:g[@pcbmode:pcb-layer='%s']//svg:g[@pcbmode:sheet='pads']//svg:g[@pcbmode:type='via']" # Get nexisting vias; only search the top layer vias = svg_in.xpath(xpath_expr % 'top', namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) vias_dict = {} for via in vias: transform = via.get('transform') if transform != None: transform_data = utils.parseTransform(transform) location = transform_data['location'] else: location = Point() # Invery 'y' axis if needed location.y *= config.cfg['invert-y'] digest = utils.digest("%s%s" % (location.x, location.y)) # Define a via, just like any other component, but disable # placement of refdef vias_dict[digest] = {} vias_dict[digest]['footprint'] = via.get('{' + config.cfg['ns']['pcbmode'] + '}via') vias_dict[digest]['location'] = [location.x, location.y] vias_dict[digest]['silkscreen'] = {'refdef': {'show': False}} vias_dict[digest]['assembly'] = {'refdef': {'show': False}} vias_dict[digest]['layer'] = 'top' for via in new_vias: # A newly-defined via will have a location set through the # 'sodipodi' namespace and possible also through a transform try: # The commented lines below wored fro Inkscape prior to 0.91 #sodipodi_loc = Point(via.get('{'+config.cfg['ns']['sodipodi']+'}cx'), # via.get('{'+config.cfg['ns']['sodipodi']+'}cy')) sodipodi_loc = Point(via.get('cx'), via.get('cy')) except: sodipodi_loc = Point() print sodipodi_loc transform = via.get('transform') if transform != None: transform_data = utils.parseTransform(transform) location = transform_data['location'] else: location = Point() location += sodipodi_loc # Invery 'y' axis if needed location.y *= config.cfg['invert-y'] digest = utils.digest("%s%s" % (location.x, location.y)) # Define a via, just like any other component, but disable # placement of refdef vias_dict[digest] = {} vias_dict[digest]['footprint'] = via.get('{' + config.cfg['ns']['pcbmode'] + '}via') vias_dict[digest]['location'] = [location.x, location.y] vias_dict[digest]['silkscreen'] = {'refdef': {'show': False}} vias_dict[digest]['assembly'] = {'refdef': {'show': False}} vias_dict[digest]['layer'] = 'top' routing_dict['vias'] = vias_dict # Display stats if len(vias_dict) == 0: msg.subInfo("No vias found") elif len(vias_dict) == 1: msg.subInfo("Extracted 1 via") else: msg.subInfo("Extracted %s vias" % (len(vias_dict))) # Save extracted routing into routing file try: with open(output_file, 'wb') as f: f.write(json.dumps(routing_dict, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % output_file) return
def __init__(self, refdef, component): """ """ self._refdef = refdef self._layer = component.get('layer') or 'top' self._rotate = component.get('rotate') or 0 if self._layer == 'bottom': self._rotate *= -1 self._rotate_point = utils.toPoint( component.get('rotate-point') or [0, 0]) self._scale = component.get('scale') or 1 self._location = component.get('location') or [0, 0] # Get footprint definition and shapes try: self._footprint_name = component['footprint'] except: msg.error("Cannot find a 'footprint' name for refdef %s." % refdef) filename = self._footprint_name + '.json' paths = [ os.path.join(config.cfg['base-dir'], config.cfg['locations']['shapes'], filename), os.path.join(config.cfg['base-dir'], config.cfg['locations']['components'], filename) ] footprint_dict = None for path in paths: if os.path.isfile(path): footprint_dict = utils.dictFromJsonFile(path) break if footprint_dict == None: fname_list = "" for path in paths: fname_list += " %s" % path msg.error("Couldn't find shape file. Looked for it here:\n%s" % (fname_list)) footprint = Footprint(footprint_dict) footprint_shapes = footprint.getShapes() #------------------------------------------------ # Apply component-specific modifiers to footprint #------------------------------------------------ for sheet in [ 'conductor', 'soldermask', 'solderpaste', 'pours', 'silkscreen', 'assembly', 'drills' ]: for layer in config.stk['layer-names']: for shape in footprint_shapes[sheet].get(layer) or []: # In order to apply the rotation we need to adust the location shape.rotateLocation(self._rotate, self._rotate_point) shape.transformPath(scale=self._scale, rotate=self._rotate, rotate_point=self._rotate_point, mirror=shape.getMirrorPlacement(), add=True) #-------------------------------------------------------------- # Remove silkscreen and assembly shapes if instructed #-------------------------------------------------------------- # If the 'show' flag is 'false then remove these items from the # shapes dictionary #-------------------------------------------------------------- for sheet in ['silkscreen', 'assembly']: try: shapes_dict = component[sheet].get('shapes') or {} except: shapes_dict = {} # If the setting is to not show silkscreen shapes for the # component, delete the shapes from the shapes' dictionary if shapes_dict.get('show') == False: for pcb_layer in utils.getSurfaceLayers(): footprint_shapes[sheet][pcb_layer] = [] #---------------------------------------------------------- # Add silkscreen and assembly reference designator (refdef) #---------------------------------------------------------- for sheet in ['silkscreen', 'assembly']: try: refdef_dict = component[sheet].get('refdef') or {} except: refdef_dict = {} if refdef_dict.get('show') != False: layer = refdef_dict.get('layer') or 'top' # Rotate the refdef; if unspecified the rotation is the same as # the rotation of the component refdef_dict['rotate'] = refdef_dict.get('rotate') or 0 # Sometimes you'd want to keep all refdefs at the same angle # and not rotated with the component if refdef_dict.get('rotate-with-component') != False: refdef_dict['rotate'] += self._rotate refdef_dict['rotate-point'] = utils.toPoint( refdef_dict.get('rotate-point')) or self._rotate_point refdef_dict['location'] = refdef_dict.get('location') or [0, 0] refdef_dict['type'] = 'text' refdef_dict['value'] = refdef_dict.get('value') or refdef refdef_dict['font-family'] = ( refdef_dict.get('font-family') or config.stl['layout'][sheet]['refdef'].get('font-family') or config.stl['defaults']['font-family']) refdef_dict['font-size'] = ( refdef_dict.get('font-size') or config.stl['layout'][sheet]['refdef'].get('font-size') or "2mm") refdef_shape = Shape(refdef_dict) refdef_shape.is_refdef = True refdef_shape.rotateLocation(self._rotate, self._rotate_point) style = Style(refdef_dict, sheet, 'refdef') refdef_shape.setStyle(style) # Add the refdef to the silkscreen/assembly list. It's # important that this is added at the very end since the # placement process assumes the refdef is last try: footprint_shapes[sheet][layer] except: footprint_shapes[sheet][layer] = [] footprint_shapes[sheet][layer].append(refdef_shape) #------------------------------------------------------ # Invert layers #------------------------------------------------------ # If the placement is on the bottom of the baord then we need # to invert the placement of all components. This affects the # surface laters but also internal layers if self._layer == 'bottom': layers = config.stk['layer-names'] for sheet in [ 'conductor', 'pours', 'soldermask', 'solderpaste', 'silkscreen', 'assembly' ]: sheet_dict = footprint_shapes[sheet] sheet_dict_new = {} for i, pcb_layer in enumerate(layers): try: sheet_dict_new[layers[len(layers) - i - 1]] = copy.copy( sheet_dict[pcb_layer]) except: continue footprint_shapes[sheet] = copy.copy(sheet_dict_new) self._footprint_shapes = footprint_shapes
def _placeComponents(self, components, component_type, print_refdef=False): """ """ for component in components: shapes_dict = component.getShapes() location = component.getLocation() # If the component is placed on the bottom layer we need # to invert the shapes AND their 'x' coordinate. This is # sone using the 'invert' indicator set below refdef = component.getRefdef() if print_refdef == True: print refdef, placement_layer = component.getPlacementLayer() if placement_layer == 'bottom': invert = True else: invert = False for pcb_layer in utils.getSurfaceLayers(): there_are_pours = utils.checkForPoursInLayer(pcb_layer) # Copper shapes = shapes_dict['copper'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['copper']['pads']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) if component_type == 'components': group.set('{'+config.cfg['ns']['pcbmode']+'}refdef', component.getRefdef()) elif component_type == 'vias': group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'via') group.set('{'+config.cfg['ns']['pcbmode']+'}via', component.getFootprintName()) else: pass for shape in shapes: place.placeShape(shape, group) if there_are_pours == True: mask_group = et.SubElement(self._masks[pcb_layer], 'g', transform=transform) self._placeMask(mask_group, shape, 'pad') # Add pin labels labels = shapes_dict['pin-labels'][pcb_layer] if labels != []: style = utils.dictToStyleText(config.stl['layout']['board']['pad-labels']) label_group = et.SubElement(group, 'g', transform="rotate(%s)" % component.getRotation(), style=style) for label in labels: t = et.SubElement(label_group, 'text', x=str(label['location'][0]), # TODO: get rid of this hack y=str(-label['location'][1])) #y=str(-pin_location.y + pad_numbers_font_size/3), #refdef=self._refdef) t.text = label['text'] # Soldermask shapes = shapes_dict['soldermask'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['soldermask']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group) # Solderpaste shapes = shapes_dict['solderpaste'][pcb_layer] svg_layer = self._layers[pcb_layer]['solderpaste']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group, invert) # Silkscreen shapes = shapes_dict['silkscreen'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['silkscreen']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) shape_group = et.SubElement(svg_layer, 'g', transform=transform) shape_group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'component-shapes') for shape in shapes: # Refdefs need to be in their own groups so that their # location can later be extracted, hence this... try: is_refdef = getattr(shape, 'is_refdef') except: is_refdef = False if is_refdef == True: refdef_group = et.SubElement(svg_layer, 'g', transform=transform) refdef_group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'refdef') refdef_group.set('{'+config.cfg['ns']['pcbmode']+'}refdef', refdef) placed_element = place.placeShape(shape, refdef_group) else: placed_element = place.placeShape(shape, shape_group) # Assembly shapes = shapes_dict['assembly'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers[pcb_layer]['assembly']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) for shape in shapes: placed_element = place.placeShape(shape, group) # Drills shapes = shapes_dict['drills'][pcb_layer] if len(shapes) > 0: svg_layer = self._layers['drills']['layer'] transform = "translate(%s,%s)" % (location[0], config.cfg['invert-y']*location[1]) group = et.SubElement(svg_layer, 'g', transform=transform) group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'component-shapes') for shape in shapes: placed_element = place.placeShape(shape, group) placed_element.set('{'+config.cfg['ns']['pcbmode']+'}diameter', str(shape.getDiameter()))