def makePngs(): """ Creates a PNG of the board using Inkscape """ # Directory for storing the Gerbers within the build path images_path = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], 'images') # Create it if it doesn't exist create_dir(images_path) # create individual PNG files for layers png_dpi = 600 msg.subInfo("Generating PNGs for each layer of the board") command = [ 'inkscape', '--without-gui', '--file=%s' % os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], config.cfg['name'] + '.svg'), '--export-png=%s' % os.path.join( images_path, config.cfg['name'] + '_rev_' + config.brd['config']['rev'] + '.png'), '--export-dpi=%s' % str(png_dpi), '--export-area-drawing', '--export-background=#FFFFFF' ] try: subp.call(command) except OSError as e: msg.error("Cannot find, or run, Inkscape in commandline mode") return
def makePngs(): """ Creates a PNG of the board using Inkscape """ # Directory for storing the Gerbers within the build path images_path = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], 'images') # Create it if it doesn't exist create_dir(images_path) # create individual PNG files for layers png_dpi = 600 msg.subInfo("Generating PNGs for each layer of the board") command = ['inkscape', '--without-gui', '--file=%s' % os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], config.cfg['name'] + '.svg'), '--export-png=%s' % os.path.join(images_path, config.cfg['name'] + '_rev_' + config.brd['config']['rev'] + '.png'), '--export-dpi=%s' % str(png_dpi), '--export-area-drawing', '--export-background=#FFFFFF'] try: subp.call(command) except OSError as e: msg.error("Cannot find, or run, Inkscape in commandline mode") return
def extractDocs(svg_in): """ Extracts the position of the documentation elements and updates the board's json """ # Get copper refdef shape groups from SVG data xpath_expr = '//svg:g[@pcbmode:sheet="documentation"]//svg:g[@pcbmode:type="module-shapes"]' docs = svg_in.findall(xpath_expr, namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) for doc in docs: doc_key = doc.get('{' + config.cfg['ns']['pcbmode'] + '}doc-key') translate_data = utils.parseTransform(doc.get('transform')) location = translate_data['location'] location.y *= config.cfg['invert-y'] current_location = utils.toPoint( config.brd['documentation'][doc_key]['location']) if current_location != location: config.brd['documentation'][doc_key]['location'] = [ location.x, location.y ] msg.subInfo("Found new location ([%s, %s]) for '%s'" % (location.x, location.y, doc_key)) # Extract drill index location xpath_expr = '//svg:g[@pcbmode:sheet="drills"]//svg:g[@pcbmode:type="drill-index"]' drill_index = svg_in.find(xpath_expr, namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) transform_dict = utils.parseTransform(drill_index.get('transform')) location = transform_dict['location'] location.y *= config.cfg['invert-y'] # Modify the location in the board's config file. If a # 'drill-index' field doesn't exist, create it drill_index_dict = config.brd.get('drill-index') if drill_index_dict == None: config.brd['drill-index'] = {} config.brd['drill-index']['location'] = [location.x, location.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)
def extractDocs(svg_in): """ Extracts the position of the documentation elements and updates the board's json """ # Get copper refdef shape groups from SVG data xpath_expr = '//svg:g[@pcbmode:sheet="documentation"]//svg:g[@pcbmode:type="module-shapes"]' docs = svg_in.findall(xpath_expr, namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) for doc in docs: doc_key = doc.get('{'+config.cfg['ns']['pcbmode']+'}doc-key') translate_data = utils.parseTransform(doc.get('transform')) location = translate_data['location'] location.y *= config.cfg['invert-y'] current_location = utils.toPoint(config.brd['documentation'][doc_key]['location']) if current_location != location: config.brd['documentation'][doc_key]['location'] = [location.x, location.y] msg.subInfo("Found new location ([%s, %s]) for '%s'" % (location.x, location.y, doc_key)) # Extract drill index location xpath_expr = '//svg:g[@pcbmode:sheet="drills"]//svg:g[@pcbmode:type="drill-index"]' drill_index = svg_in.find(xpath_expr, namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) transform_dict = utils.parseTransform(drill_index.get('transform')) location = transform_dict['location'] location.y *= config.cfg['invert-y'] # Modify the location in the board's config file. If a # 'drill-index' field doesn't exist, create it drill_index_dict = config.brd.get('drill-index') if drill_index_dict == None: config.brd['drill-index'] = {} config.brd['drill-index']['location'] = [location.x, location.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)
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 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 __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 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, 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) # Get dictionary of component definitions shapes_dict = self._module_dict.get('shapes') or {} self._shapes = self._getComponents(shapes_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 config.stk['layer-names']: 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(components=self._components, component_type='component', print_refdef=True) print msg.subInfo('Placing routes') self._placeRouting() msg.subInfo('Placing vias') self._placeComponents(components=self._vias, component_type='via', print_refdef=False) msg.subInfo('Placing shapes') self._placeComponents(components=self._shapes, component_type='shape', print_refdef=False) 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 config.stk['layer-names']: 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): """ """ xpath_expr_place = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="placement"]//svg:g[@pcbmode:type="%s"]' for pcb_layer in config.stk['surface-layer-names']: # Find all 'component' markers markers = svg_in.findall(xpath_expr_place % (pcb_layer, 'component'), namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) # Find all 'shape' markers markers += svg_in.findall(xpath_expr_place % (pcb_layer, 'shape'), namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) for marker in markers: transform_data = utils.parseTransform(marker.get('transform')) refdef = marker.get('{'+config.cfg['ns']['pcbmode']+'}refdef') marker_type = marker.get('{'+config.cfg['ns']['pcbmode']+'}type') if marker_type == 'component': comp_dict = config.brd['components'][refdef] elif marker_type == 'shape': comp_dict = config.brd['shapes'][refdef] else: continue 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: x1 = utils.niceFloat(old_location.x) y1 = utils.niceFloat(old_location.y) x2 = utils.niceFloat(new_location.x) y2 = utils.niceFloat(new_location.y) msg.subInfo("%s has moved from [%s,%s] to [%s,%s]" % (refdef,x1,y2,x2,y2)) # Apply new location comp_dict['location'] = [x2,y2] # 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'] = utils.niceFloat((old_rotate+new_rotate) % 360) msg.subInfo("Component %s rotated from %s to %s" % (refdef, old_rotate, comp_dict['rotate'])) # 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 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 = {} for pcb_layer in config.stk['layer-names']: 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) try: routes_dict[pcb_layer][digest] = {} except: routes_dict[pcb_layer] = {} 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 config.stk['layer-names']: 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 config.stk['layer-names']: 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_expr_place = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="placement"]//svg:g[@pcbmode:type="via"]' vias_dict = {} for pcb_layer in config.stk['surface-layer-names']: # Find all markers markers = svg_in.findall(xpath_expr_place % pcb_layer, namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) for marker in markers: transform_data = utils.parseTransform(marker.get('transform')) location = transform_data['location'] # Invert 'y' coordinate location.y *= config.cfg['invert-y'] # Change component rotation if needed if transform_data['type'] == 'matrix': rotate = transform_data['rotate'] rotate = utils.niceFloat((rotate) % 360) 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'] = marker.get('{'+config.cfg['ns']['pcbmode']+'}footprint') vias_dict[digest]['location'] = [utils.niceFloat(location.x), utils.niceFloat(location.y)] 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 extractComponents(svg_in): """ """ xpath_expr_place = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="placement"]//svg:g[@pcbmode:type="%s"]' for pcb_layer in config.stk['surface-layer-names']: # Find all 'component' markers markers = svg_in.findall(xpath_expr_place % (pcb_layer, 'component'), namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) # Find all 'shape' markers markers += svg_in.findall(xpath_expr_place % (pcb_layer, 'shape'), namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) for marker in markers: transform_data = utils.parseTransform(marker.get('transform')) refdef = marker.get('{' + config.cfg['ns']['pcbmode'] + '}refdef') marker_type = marker.get('{' + config.cfg['ns']['pcbmode'] + '}type') if marker_type == 'component': comp_dict = config.brd['components'][refdef] elif marker_type == 'shape': comp_dict = config.brd['shapes'][refdef] else: continue 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: x1 = utils.niceFloat(old_location.x) y1 = utils.niceFloat(old_location.y) x2 = utils.niceFloat(new_location.x) y2 = utils.niceFloat(new_location.y) msg.subInfo("%s has moved from [%s,%s] to [%s,%s]" % (refdef, x1, y2, x2, y2)) # Apply new location comp_dict['location'] = [x2, y2] # 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'] = utils.niceFloat( (old_rotate + new_rotate) % 360) msg.subInfo("Component %s rotated from %s to %s" % (refdef, old_rotate, comp_dict['rotate'])) # 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 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 = {} for pcb_layer in config.stk['layer-names']: 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) try: routes_dict[pcb_layer][digest] = {} except: routes_dict[pcb_layer] = {} 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 config.stk['layer-names']: 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 config.stk['layer-names']: 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_expr_place = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="placement"]//svg:g[@pcbmode:type="via"]' vias_dict = {} for pcb_layer in config.stk['surface-layer-names']: # Find all markers markers = svg_in.findall(xpath_expr_place % pcb_layer, namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) for marker in markers: transform_data = utils.parseTransform(marker.get('transform')) location = transform_data['location'] # Invert 'y' coordinate location.y *= config.cfg['invert-y'] # Change component rotation if needed if transform_data['type'] == 'matrix': rotate = transform_data['rotate'] rotate = utils.niceFloat((rotate) % 360) 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'] = marker.get( '{' + config.cfg['ns']['pcbmode'] + '}footprint') vias_dict[digest]['location'] = [ utils.niceFloat(location.x), utils.niceFloat(location.y) ] 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