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, svg, mask_paths, decimals, digits, steps, length): """ """ self._ns = { 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] } self._svg = svg self._mask_paths = mask_paths self._decimals = decimals self._digits = digits self._steps = steps self._length = length self._grammar = self._getGerberGrammar() self._aperture_list = [] # Gerber apertures are defined at the begining of the file and # then refered to by number. 1--10 are reserved and cannot be # used, so wer start the design's apersute number at 20, # leaving 10 to define fixed apertures for closed shapesm # flashes, wtc.lets us have self._aperture_num = 20 self._closed_shape_aperture_num = 10 self._pad_flashes_aperture_num = 11 self._commands = [] self._apertures = {} self._paths = self._getPaths() for path in self._paths: tmp = {} style_string = path.get('style') tmp['style'] = path.get('{' + config.cfg['ns']['pcbmode'] + '}style') if tmp['style'] == 'stroke': tmp['stroke-width'] = utils.getStyleAttrib( style_string, 'stroke-width') # Build aperture list if tmp['stroke-width'] not in self._apertures: self._apertures[tmp['stroke-width']] = self._aperture_num self._aperture_num += 1 tmp['gerber-lp'] = path.get('{' + config.cfg['ns']['pcbmode'] + '}gerber-lp') # Get the absolute location location = self._getLocation(path) # Get path coordinates; each path segment as a list item tmp['coords'] = self._getCommandListOfPath(path, location) self._commands.append(tmp) self._flattenCoords() self._flashes = self._getFlashes() self._preamble = self._createPreamble() self._postamble = self._createPostamble()
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 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 __init__(self, svg, mask_paths, decimals, digits, steps, length): """ """ self._ns = {'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']} self._svg = svg self._mask_paths = mask_paths self._decimals = decimals self._digits = digits self._steps = steps self._length = length self._grammar = self._getGerberGrammar() self._aperture_list = [] # Gerber apertures are defined at the begining of the file and # then refered to by number. 1--10 are reserved and cannot be # used, so wer start the design's apersute number at 20, # leaving 10 to define fixed apertures for closed shapesm # flashes, wtc.lets us have self._aperture_num = 20 self._closed_shape_aperture_num = 10 self._pad_flashes_aperture_num = 11 self._commands = [] self._apertures = {} self._paths = self._getPaths() for path in self._paths: tmp = {} style_string = path.get('style') tmp['style'] = path.get('{'+config.cfg['ns']['pcbmode']+'}style') if tmp['style'] == 'stroke': tmp['stroke-width'] = utils.getStyleAttrib(style_string, 'stroke-width') # Build aperture list if tmp['stroke-width'] not in self._apertures: self._apertures[tmp['stroke-width']] = self._aperture_num self._aperture_num += 1 tmp['gerber-lp'] = path.get('{'+config.cfg['ns']['pcbmode']+'}gerber-lp') # Get the absolute location location = self._getLocation(path) # Get path coordinates; each path segment as a list item tmp['coords'] = self._getCommandListOfPath(path, location) self._commands.append(tmp) self._flattenCoords() self._flashes = self._getFlashes() self._preamble = self._createPreamble() self._postamble = self._createPostamble()
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