Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
    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()
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
    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()
Ejemplo n.º 6
0
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