Ejemplo n.º 1
0
    def __init__(self, refdef, component):
        """
        """

        self._refdef = refdef
        self._layer = component.get('layer') or 'top'

        self._rotate = component.get('rotate') or 0
        if self._layer == 'bottom':
            self._rotate *= -1

        self._rotate_point = utils.toPoint(
            component.get('rotate-point') or [0, 0])
        self._scale = component.get('scale') or 1
        self._location = component.get('location') or [0, 0]

        # Get footprint definition and shapes
        try:
            self._footprint_name = component['footprint']
        except:
            msg.error("Cannot find a 'footprint' name for refdef %s." % refdef)

        filename = self._footprint_name + '.json'

        paths = [
            os.path.join(config.cfg['base-dir'],
                         config.cfg['locations']['shapes'], filename),
            os.path.join(config.cfg['base-dir'],
                         config.cfg['locations']['components'], filename)
        ]

        footprint_dict = None
        for path in paths:
            if os.path.isfile(path):
                footprint_dict = utils.dictFromJsonFile(path)
                break

        if footprint_dict == None:
            fname_list = ""
            for path in paths:
                fname_list += " %s" % path
            msg.error("Couldn't find shape file. Looked for it here:\n%s" %
                      (fname_list))

        footprint = Footprint(footprint_dict)
        footprint_shapes = footprint.getShapes()

        #------------------------------------------------
        # Apply component-specific modifiers to footprint
        #------------------------------------------------
        for sheet in [
                'conductor', 'soldermask', 'solderpaste', 'pours',
                'silkscreen', 'assembly', 'drills'
        ]:
            for layer in config.stk['layer-names']:
                for shape in footprint_shapes[sheet].get(layer) or []:

                    # In order to apply the rotation we need to adust the location
                    shape.rotateLocation(self._rotate, self._rotate_point)

                    shape.transformPath(scale=self._scale,
                                        rotate=self._rotate,
                                        rotate_point=self._rotate_point,
                                        mirror=shape.getMirrorPlacement(),
                                        add=True)

        #--------------------------------------------------------------
        # Remove silkscreen and assembly shapes if instructed
        #--------------------------------------------------------------
        # If the 'show' flag is 'false then remove these items from the
        # shapes dictionary
        #--------------------------------------------------------------
        for sheet in ['silkscreen', 'assembly']:

            try:
                shapes_dict = component[sheet].get('shapes') or {}
            except:
                shapes_dict = {}

            # If the setting is to not show silkscreen shapes for the
            # component, delete the shapes from the shapes' dictionary
            if shapes_dict.get('show') == False:
                for pcb_layer in utils.getSurfaceLayers():
                    footprint_shapes[sheet][pcb_layer] = []

        #----------------------------------------------------------
        # Add silkscreen and assembly reference designator (refdef)
        #----------------------------------------------------------
        for sheet in ['silkscreen', 'assembly']:

            try:
                refdef_dict = component[sheet].get('refdef') or {}
            except:
                refdef_dict = {}

            if refdef_dict.get('show') != False:
                layer = refdef_dict.get('layer') or 'top'

                # Rotate the refdef; if unspecified the rotation is the same as
                # the rotation of the component
                refdef_dict['rotate'] = refdef_dict.get('rotate') or 0

                # Sometimes you'd want to keep all refdefs at the same angle
                # and not rotated with the component
                if refdef_dict.get('rotate-with-component') != False:
                    refdef_dict['rotate'] += self._rotate

                refdef_dict['rotate-point'] = utils.toPoint(
                    refdef_dict.get('rotate-point')) or self._rotate_point

                refdef_dict['location'] = refdef_dict.get('location') or [0, 0]
                refdef_dict['type'] = 'text'
                refdef_dict['value'] = refdef_dict.get('value') or refdef
                refdef_dict['font-family'] = (
                    refdef_dict.get('font-family')
                    or config.stl['layout'][sheet]['refdef'].get('font-family')
                    or config.stl['defaults']['font-family'])
                refdef_dict['font-size'] = (
                    refdef_dict.get('font-size')
                    or config.stl['layout'][sheet]['refdef'].get('font-size')
                    or "2mm")
                refdef_shape = Shape(refdef_dict)

                refdef_shape.is_refdef = True
                refdef_shape.rotateLocation(self._rotate, self._rotate_point)
                style = Style(refdef_dict, sheet, 'refdef')
                refdef_shape.setStyle(style)

                # Add the refdef to the silkscreen/assembly list. It's
                # important that this is added at the very end since the
                # placement process assumes the refdef is last
                try:
                    footprint_shapes[sheet][layer]
                except:
                    footprint_shapes[sheet][layer] = []

                footprint_shapes[sheet][layer].append(refdef_shape)

        #------------------------------------------------------
        # Invert layers
        #------------------------------------------------------
        # If the placement is on the bottom of the baord then we need
        # to invert the placement of all components. This affects the
        # surface laters but also internal layers

        if self._layer == 'bottom':
            layers = config.stk['layer-names']

            for sheet in [
                    'conductor', 'pours', 'soldermask', 'solderpaste',
                    'silkscreen', 'assembly'
            ]:
                sheet_dict = footprint_shapes[sheet]
                sheet_dict_new = {}
                for i, pcb_layer in enumerate(layers):
                    try:
                        sheet_dict_new[layers[len(layers) - i -
                                              1]] = copy.copy(
                                                  sheet_dict[pcb_layer])
                    except:
                        continue

                footprint_shapes[sheet] = copy.copy(sheet_dict_new)

        self._footprint_shapes = footprint_shapes
Ejemplo n.º 2
0
def make_bom(quantity=None):
    """
    
    """

    def natural_key(string_):
        """See http://www.codinghorror.com/blog/archives/001018.html"""
        return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)]


     
    dnp_text = 'Do not populate'
    uncateg_text = 'Uncategorised'

    components_dict = config.brd['components']
    
    bom_dict = {}

    for refdef in components_dict:

        description = ''

        try:
            place = components_dict[refdef]['place']
        except:
            place = True

        try:
            ignore = components_dict[refdef]['bom']['ignore']
        except:
            ignore = False

        # If component isn't placed, ignore it
        if place == True and ignore == False:

            # Get footprint definition and shapes
            try:
                footprint_name = components_dict[refdef]['footprint']
            except:
                msg.error("Cannot find a 'footprint' name for refdef %s." % refdef)
            
            # Open footprint file
            fname = os.path.join(config.cfg['base-dir'],
                                 config.cfg['locations']['components'],
                                 footprint_name + '.json')
            footprint_dict = utils.dictFromJsonFile(fname)
 
            info_dict = footprint_dict.get('info') or {}
 
            try: 
                comp_bom_dict = components_dict[refdef]['bom']
            except:
                comp_bom_dict = {}
 
            try: 
                fp_bom_dict = footprint_dict['info']
            except:
                fp_bom_dict = {}
 
 
            # Override component BoM info on top of footprint info
            for key in comp_bom_dict:
                fp_bom_dict[key] = comp_bom_dict[key]

            description = fp_bom_dict.get('description') or uncateg_text

            try:
                dnp = components_dict[refdef]['bom']['dnp']
            except:
                dnp = False            

            if dnp == True:
                description = dnp_text
 
            if description not in bom_dict:
                bom_dict[description] = fp_bom_dict
                bom_dict[description]['refdefs'] = []
            bom_dict[description]['refdefs'].append(refdef)
            
            bom_dict[description]['placement'] = components_dict[refdef]['layer']
    

    try:
        bom_content = config.brd['bom']
    except:
        bom_content = [
            {
              "field": "line-item",
              "text": "#"
            },
            {
              "field": "quantity",
              "text": "Qty"
            },
            {
              "field": "designators",
               "text": "Designators"
            }, 
            {
              "field": "description",
              "text": "Description"
            }, 
            {
              "field": "package",
              "text": "Package"
            }, 
            {
              "field": "manufacturer",
              "text": "Manufacturer"
            }, 
            {
              "field": "part-number",
              "text": "Part #"
            },
            {
              "field": "suppliers",
              "text": "Suppliers",
              "suppliers": 
              [
                {
                  "field": "farnell",
                  "text": "Farnell #",
                  "search-url": "http://uk.farnell.com/catalog/Search?st="
                },
                { 
                  "field": "mouser",
                  "text": "Mouser #",
                  "search-url": "http://uk.mouser.com/Search/Refine.aspx?Keyword="
                },
                {
                  "field": "octopart",
                  "text": "Octopart",
                  "search-url": "https://octopart.com/search?q="
                }
              ]
            }, 
            {
              "field": "placement",
              "text": "Layer"
            }, 
            {
              "field": "notes",
              "text": "Notes"
            }
          ]



    # Set up the BoM file name
    bom_path = os.path.join(config.cfg['base-dir'],
                            config.cfg['locations']['build'],
                            'bom')
    # Create path if it doesn't exist already
    utils.create_dir(bom_path)

    board_name = config.cfg['name']
    board_revision = config.brd['config'].get('rev')
    base_name = "%s_rev_%s" % (board_name, board_revision)

    bom_html = os.path.join(bom_path, base_name + '_%s.html'% 'bom')
    bom_csv = os.path.join(bom_path, base_name + '_%s.csv'% 'bom')


    html = []
    csv = []


    html.append('<html>')
    html.append('<style type="text/css">')
    try:
        css = config.stl['layout']['bom']['css']
    except:
        css = []
    for line in css:
        html.append(line)
    html.append('</style>')
    html.append('<table class="tg">')

    header = []
    for item in bom_content:
        if item['field'] == 'suppliers':
            for supplier in item['suppliers']:
                header.append("%s" % supplier['text'])
        else:
            header.append("%s" % item['text'])
            if item['field'] == 'quantity' and quantity != None:
                header.append("@%s" % quantity)

    html.append('  <tr>')
    html.append('    <th class="tg-title" colspan="%s">Bill of materials -- %s rev %s</th>' % (len(header), board_name, board_revision))
    html.append('  </tr>') 
    html.append('  <tr>')
    for item in header:
        html.append('    <th class="tg-header">%s</th>' % item)
    html.append('  </tr>')
    

    uncateg_content = []
    dnp_content = []
    index = 1

    for desc in bom_dict:
        content = []
        for item in bom_content:
            if item['field'] == 'line-item':
                content.append("<strong>%s</strong>" % str(index))
            elif item['field'] == 'suppliers':
                for supplier in item['suppliers']:

                    try:
                        number = bom_dict[desc][item['field']][supplier['field']]
                    except:
                        number = ""

                    search_url = supplier.get('search-url')
                    if search_url != None:
                        content.append('<a href="%s%s">%s</a>' % (search_url, number, number))
                    else:
                        content.append(number)

            elif item['field'] == 'quantity':
                units = len(bom_dict[desc]['refdefs'])
                content.append("%s" % (str(units)))
                if quantity != None:
                    content.append("%s" % (str(units*int(quantity))))
            elif item['field'] == 'designators':
                # Natural/human sort the list of designators
                sorted_list = sorted(bom_dict[desc]['refdefs'], key=natural_key)

                refdefs = ''
                for refdef in sorted_list[:-1]:
                    refdefs += "%s " % refdef
                refdefs += "%s" % sorted_list[-1]
                content.append("%s " % refdefs)
            elif item['field'] == 'description':
                content.append("%s " % desc)
            else:
                try:
                    content.append(bom_dict[desc][item['field']])
                except:
                    content.append("")

        if desc == uncateg_text:
            uncateg_content = content
        elif desc == dnp_text:
            dnp_content = content
        else:
            html.append('  <tr>')
            for item in content:
                html.append('    <td class="tg-item-%s">%s</td>' % (('odd','even')[index%2==0], item))
            html.append('  </tr>')
            index += 1


    for content in (dnp_content, uncateg_content):
        html.append('  <tr class="tg-skip">')
        html.append('  </tr>')        
        html.append('  <tr>')
        if len(content) > 0:
            content[0] = index
        for item in content:
            html.append('    <td class="tg-item-%s">%s</td>' % (('odd','even')[index%2==0], item))
        html.append('  </tr>')
        index += 1


    html.append('</table>')

    html.append('<p>Generated by <a href="http://pcbmode.com">PCBmodE</a>, an open source PCB design software. PCBmodE was written and is maintained by <a href="http://boldport.com">Boldport</a>, creators of beautifully functional circuits.')

    html.append('</html>')

    with open(bom_html, "wb") as f:
        for line in html:
            f.write(line+'\n')
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:
                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.º 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 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.º 6
0
    def __init__(self, refdef, component):
        """
        """

        self._refdef = refdef
        self._layer = component.get('layer') or 'top'

        self._rotate = component.get('rotate') or 0
        self._rotate_point = utils.toPoint(component.get('rotate-point') or [0, 0])
        self._scale = component.get('scale') or 1
        self._location = component.get('location') or [0, 0]

        # Get footprint definition and shapes
        try:
            self._footprint_name = component['footprint']
        except:
            msg.error("Cannot find a 'footprint' name for refdef %s." % refdef)

        fname = os.path.join(config.cfg['base-dir'],
                             config.cfg['locations']['components'],
                             self._footprint_name + '.json')
        footprint_dict = utils.dictFromJsonFile(fname)
        footprint = Footprint(footprint_dict)
        footprint_shapes = footprint.getShapes()

        #------------------------------------------------        
        # Apply component-specific modifiers to footprint
        #------------------------------------------------
        for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly', 'drills']:
            for layer in utils.getSurfaceLayers() + utils.getInternalLayers():
                for shape in footprint_shapes[sheet][layer]:
                    # In order to apply the rotation we need to adust the location
                    # of each element
                    shape.rotateLocation(self._rotate, self._rotate_point)

                    shape.transformPath(self._scale,
                                        self._rotate,
                                        self._rotate_point,
                                        False,
                                        True)


        #--------------------------------------
        # Remove silkscreen and assembly shapes
        #--------------------------------------
        for sheet in ['silkscreen','assembly']:
            
            try:
                shapes_dict = component[sheet].get('shapes') or {}
            except:
                shapes_dict = {}

            # If the setting is to not show silkscreen shapes for the
            # component, delete the shapes from the shapes' dictionary
            if shapes_dict.get('show') == False:
                for pcb_layer in utils.getSurfaceLayers():
                    footprint_shapes[sheet][pcb_layer] = []



        #----------------------------------------------------------
        # Add silkscreen and assembly reference designator (refdef)
        #----------------------------------------------------------
        for sheet in ['silkscreen','assembly']:
            
            try:
                refdef_dict = component[sheet].get('refdef') or {}
            except:
                refdef_dict = {}
     
            if refdef_dict.get('show') != False:
                layer = refdef_dict.get('layer') or 'top'
         
                # Rotate the refdef; if unspecified the rotation is the same as
                # the rotation of the component
                refdef_dict['rotate'] = refdef_dict.get('rotate') or 0
 
                # Sometimes you'd want to keep all refdefs at the same angle
                # and not rotated with the component
                if refdef_dict.get('rotate-with-component') != False:
                    refdef_dict['rotate'] += self._rotate

                refdef_dict['rotate-point'] = utils.toPoint(refdef_dict.get('rotate-point')) or self._rotate_point
     
                refdef_dict['location'] = refdef_dict.get('location') or [0, 0]
                refdef_dict['type'] = 'text'
                refdef_dict['value'] = refdef
                refdef_dict['font-family'] = (config.stl['layout'][sheet]['refdef'].get('font-family') or 
                                              config.stl['defaults']['font-family'])
                refdef_dict['font-size'] = (config.stl['layout'][sheet]['refdef'].get('font-size') or 
                                              "2mm")
                refdef_shape = Shape(refdef_dict)
                refdef_shape.is_refdef = True
                refdef_shape.rotateLocation(self._rotate, self._rotate_point)
                style = Style(refdef_dict, sheet, 'refdef')
                refdef_shape.setStyle(style)

                # Add the refdef to the silkscreen/assembly list. It's
                # important that this is added at the very end since the
                # plcament process assumes the refdef is last
                footprint_shapes[sheet][layer].append(refdef_shape)


        #------------------------------------------------------
        # Invert 'top' and 'bottom' if layer is on the 'bottom'
        #------------------------------------------------------
        if self._layer == 'bottom':
            layers = utils.getSurfaceLayers()
            layers_reversed = reversed(utils.getSurfaceLayers())
           
            for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly']:
                sheet_dict = footprint_shapes[sheet]
                # TODO: this nasty hack won't work for more than two
                # layers, so when 2+ are supported this needs to be
                # revisited
                for i in range(0,len(layers)-1):
                    sheet_dict['temp'] = sheet_dict.pop(layers[i])
                    sheet_dict[layers[i]] = sheet_dict.pop(layers[len(layers)-1-i])
                    sheet_dict[layers[len(layers)-1-i]] = sheet_dict.pop('temp')

        self._footprint_shapes = footprint_shapes
Ejemplo n.º 7
0
    def __init__(self, refdef, component):
        """
        """

        self._refdef = refdef
        self._layer = component.get('layer') or 'top'

        self._rotate = component.get('rotate') or 0
        self._rotate_point = utils.toPoint(component.get('rotate-point') or [0, 0])
        self._scale = component.get('scale') or 1
        self._location = component.get('location') or [0, 0]

        # Get footprint definition and shapes
        try:
            self._footprint_name = component['footprint']
        except:
            msg.error("Cannot find a 'footprint' name for refdef %s." % refdef)

        fname = os.path.join(config.cfg['base-dir'],
                             config.cfg['locations']['components'],
                             self._footprint_name + '.json')
        footprint_dict = utils.dictFromJsonFile(fname)
        footprint = Footprint(footprint_dict)
        footprint_shapes = footprint.getShapes()

        #------------------------------------------------        
        # Apply component-specific modifiers to footprint
        #------------------------------------------------
        for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly', 'drills']:
            for layer in utils.getSurfaceLayers() + utils.getInternalLayers():
                for shape in footprint_shapes[sheet][layer]:
                    # In order to apply the rotation we need to adust the location
                    # of each element
                    shape.rotateLocation(self._rotate, self._rotate_point)

                    shape.transformPath(self._scale,
                                        self._rotate,
                                        self._rotate_point,
                                        False,
                                        True)

        #--------------------------------------
        # Remove silkscreen and assembly shapes
        #--------------------------------------
        for sheet in ['silkscreen','assembly']:
            
            try:
                shapes_dict = component[sheet].get('shapes') or {}
            except:
                shapes_dict = {}

            # If the setting is to not show silkscreen shapes for the
            # component, delete the shapes from the shapes' dictionary
            if shapes_dict.get('show') == False:
                for pcb_layer in utils.getSurfaceLayers():
                    footprint_shapes[sheet][pcb_layer] = []



        #----------------------------------------------------------
        # Add silkscreen and assembly reference designator (refdef)
        #----------------------------------------------------------
        for sheet in ['silkscreen','assembly']:
            
            try:
                refdef_dict = component[sheet].get('refdef') or {}
            except:
                refdef_dict = {}
     
            if refdef_dict.get('show') != False:
                layer = refdef_dict.get('layer') or 'top'
         
                # Rotate the refdef; if unspecified the rotation is the same as
                # the rotation of the component
                refdef_dict['rotate'] = refdef_dict.get('rotate') or 0
 
                # Sometimes you'd want to keep all refdefs at the same angle
                # and not rotated with the component
                if refdef_dict.get('rotate-with-component') != False:
                    refdef_dict['rotate'] += self._rotate

                refdef_dict['rotate-point'] = utils.toPoint(refdef_dict.get('rotate-point')) or self._rotate_point
     
                refdef_dict['location'] = refdef_dict.get('location') or [0, 0]
                refdef_dict['type'] = 'text'
                refdef_dict['value'] = refdef_dict.get('value') or refdef
                refdef_dict['font-family'] = (refdef_dict.get('font-family') or
                                              config.stl['layout'][sheet]['refdef'].get('font-family') or 
                                              config.stl['defaults']['font-family'])
                refdef_dict['font-size'] = (refdef_dict.get('font-size') or 
                                            config.stl['layout'][sheet]['refdef'].get('font-size') or 
                                            "2mm")
                refdef_shape = Shape(refdef_dict)

                refdef_shape.is_refdef = True
                refdef_shape.rotateLocation(self._rotate, self._rotate_point)
                style = Style(refdef_dict, sheet, 'refdef')
                refdef_shape.setStyle(style)

                # Add the refdef to the silkscreen/assembly list. It's
                # important that this is added at the very end since the
                # placement process assumes the refdef is last
                footprint_shapes[sheet][layer].append(refdef_shape)


        #------------------------------------------------------
        # Invert 'top' and 'bottom' if layer is on the 'bottom'
        #------------------------------------------------------
        if self._layer == 'bottom':
            layers = utils.getSurfaceLayers()
            layers_reversed = reversed(utils.getSurfaceLayers())
           
            for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly']:
                sheet_dict = footprint_shapes[sheet]
                # TODO: this nasty hack won't work for more than two
                # layers, so when 2+ are supported this needs to be
                # revisited
                for i in range(0,len(layers)-1):
                    sheet_dict['temp'] = sheet_dict.pop(layers[i])
                    sheet_dict[layers[i]] = sheet_dict.pop(layers[len(layers)-1-i])
                    sheet_dict[layers[len(layers)-1-i]] = sheet_dict.pop('temp')

        self._footprint_shapes = footprint_shapes
Ejemplo n.º 8
0
    def __init__(self, refdef, component):
        """
        """

        self._refdef = refdef
        self._layer = component.get('layer') or 'top'

        self._rotate = component.get('rotate') or 0
        if self._layer=='bottom':
            self._rotate *= -1

        self._rotate_point = utils.toPoint(component.get('rotate-point') or [0, 0])
        self._scale = component.get('scale') or 1
        self._location = component.get('location') or [0, 0]

        # Get footprint definition and shapes
        try:
            self._footprint_name = component['footprint']
        except:
            msg.error("Cannot find a 'footprint' name for refdef %s." % refdef)

        filename = self._footprint_name + '.json'

        paths = [os.path.join(config.cfg['base-dir'],
                             config.cfg['locations']['shapes'],
                             filename),
                   os.path.join(config.cfg['base-dir'],
                             config.cfg['locations']['components'],
                             filename)]

        footprint_dict = None
        for path in paths:
            if os.path.isfile(path):
                footprint_dict = utils.dictFromJsonFile(path)
                break

        if footprint_dict == None:
            fname_list = ""
            for path in paths:
                fname_list += " %s" % path
            msg.error("Couldn't find shape file. Looked for it here:\n%s" % (fname_list))

        footprint = Footprint(footprint_dict)
        footprint_shapes = footprint.getShapes()

        #------------------------------------------------        
        # Apply component-specific modifiers to footprint
        #------------------------------------------------
        for sheet in ['conductor', 'soldermask', 'solderpaste', 'pours', 'silkscreen', 'assembly', 'drills']:
            for layer in config.stk['layer-names']:
                for shape in footprint_shapes[sheet].get(layer) or []:

                    # In order to apply the rotation we need to adust the location
                    shape.rotateLocation(self._rotate, self._rotate_point)

                    shape.transformPath(scale=self._scale,
                                        rotate=self._rotate,
                                        rotate_point=self._rotate_point,
                                        mirror=shape.getMirrorPlacement(),
                                        add=True)

        #-------------------------------------------------------------- 
        # Remove silkscreen and assembly shapes if instructed 
        #-------------------------------------------------------------- 
        # If the 'show' flag is 'false then remove these items from the
        # shapes dictionary 
        #--------------------------------------------------------------
        for sheet in ['silkscreen','assembly']:
            
            try:
                shapes_dict = component[sheet].get('shapes') or {}
            except:
                shapes_dict = {}

            # If the setting is to not show silkscreen shapes for the
            # component, delete the shapes from the shapes' dictionary
            if shapes_dict.get('show') == False:
                for pcb_layer in utils.getSurfaceLayers():
                    footprint_shapes[sheet][pcb_layer] = []



        #----------------------------------------------------------
        # Add silkscreen and assembly reference designator (refdef)
        #----------------------------------------------------------
        for sheet in ['silkscreen','assembly']:
            
            try:
                refdef_dict = component[sheet].get('refdef') or {}
            except:
                refdef_dict = {}
     
            if refdef_dict.get('show') != False:
                layer = refdef_dict.get('layer') or 'top'
         
                # Rotate the refdef; if unspecified the rotation is the same as
                # the rotation of the component
                refdef_dict['rotate'] = refdef_dict.get('rotate') or 0
 
                # Sometimes you'd want to keep all refdefs at the same angle
                # and not rotated with the component
                if refdef_dict.get('rotate-with-component') != False:
                    refdef_dict['rotate'] += self._rotate

                refdef_dict['rotate-point'] = utils.toPoint(refdef_dict.get('rotate-point')) or self._rotate_point
     
                refdef_dict['location'] = refdef_dict.get('location') or [0, 0]
                refdef_dict['type'] = 'text'
                refdef_dict['value'] = refdef_dict.get('value') or refdef
                refdef_dict['font-family'] = (refdef_dict.get('font-family') or
                                              config.stl['layout'][sheet]['refdef'].get('font-family') or 
                                              config.stl['defaults']['font-family'])
                refdef_dict['font-size'] = (refdef_dict.get('font-size') or 
                                            config.stl['layout'][sheet]['refdef'].get('font-size') or 
                                            "2mm")
                refdef_shape = Shape(refdef_dict)

                refdef_shape.is_refdef = True
                refdef_shape.rotateLocation(self._rotate, self._rotate_point)
                style = Style(refdef_dict, sheet, 'refdef')
                refdef_shape.setStyle(style)

                # Add the refdef to the silkscreen/assembly list. It's
                # important that this is added at the very end since the
                # placement process assumes the refdef is last
                try:
                    footprint_shapes[sheet][layer]
                except:
                    footprint_shapes[sheet][layer] = []

                footprint_shapes[sheet][layer].append(refdef_shape)
                    

        #------------------------------------------------------
        # Invert layers
        #------------------------------------------------------
        # If the placement is on the bottom of the baord then we need
        # to invert the placement of all components. This affects the
        # surface laters but also internal layers

        if self._layer == 'bottom':
            layers = config.stk['layer-names']
           
            for sheet in ['conductor', 'pours', 'soldermask', 'solderpaste', 'silkscreen', 'assembly']:
                sheet_dict = footprint_shapes[sheet]
                sheet_dict_new = {}
                for i, pcb_layer in enumerate(layers):
                    try:
                        sheet_dict_new[layers[len(layers)-i-1]] = copy.copy(sheet_dict[pcb_layer])
                    except:
                        continue

                footprint_shapes[sheet] = copy.copy(sheet_dict_new)    

        self._footprint_shapes = footprint_shapes
Ejemplo n.º 9
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.º 10
0
def make_bom(quantity=None):
    """
    
    """
    def natural_key(string_):
        """See http://www.codinghorror.com/blog/archives/001018.html"""
        return [
            int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)
        ]

    dnp_text = 'Do not populate'
    uncateg_text = 'Uncategorised'

    components_dict = config.brd['components']

    bom_dict = {}

    for refdef in components_dict:

        description = ''

        try:
            place = components_dict[refdef]['place']
        except:
            place = True

        try:
            ignore = components_dict[refdef]['bom']['ignore']
        except:
            ignore = False

        # If component isn't placed, ignore it
        if place == True and ignore == False:

            # Get footprint definition and shapes
            try:
                footprint_name = components_dict[refdef]['footprint']
            except:
                msg.error("Cannot find a 'footprint' name for refdef %s." %
                          refdef)

            # Open footprint file
            fname = os.path.join(config.cfg['base-dir'],
                                 config.cfg['locations']['components'],
                                 footprint_name + '.json')
            footprint_dict = utils.dictFromJsonFile(fname)

            info_dict = footprint_dict.get('info') or {}

            try:
                comp_bom_dict = components_dict[refdef]['bom']
            except:
                comp_bom_dict = {}

            try:
                fp_bom_dict = footprint_dict['info']
            except:
                fp_bom_dict = {}

            # Override component BoM info on top of footprint info
            for key in comp_bom_dict:
                fp_bom_dict[key] = comp_bom_dict[key]

            description = fp_bom_dict.get('description') or uncateg_text

            try:
                dnp = components_dict[refdef]['bom']['dnp']
            except:
                dnp = False

            if dnp == True:
                description = dnp_text

            if description not in bom_dict:
                bom_dict[description] = fp_bom_dict
                bom_dict[description]['refdefs'] = []
            bom_dict[description]['refdefs'].append(refdef)

            bom_dict[description]['placement'] = components_dict[refdef][
                'layer']

    try:
        bom_content = config.brd['bom']
    except:
        bom_content = [{
            "field": "line-item",
            "text": "#"
        }, {
            "field": "quantity",
            "text": "Qty"
        }, {
            "field": "designators",
            "text": "Designators"
        }, {
            "field": "description",
            "text": "Description"
        }, {
            "field": "package",
            "text": "Package"
        }, {
            "field": "manufacturer",
            "text": "Manufacturer"
        }, {
            "field": "part-number",
            "text": "Part #"
        }, {
            "field":
            "suppliers",
            "text":
            "Suppliers",
            "suppliers": [{
                "field":
                "farnell",
                "text":
                "Farnell #",
                "search-url":
                "http://uk.farnell.com/catalog/Search?st="
            }, {
                "field":
                "mouser",
                "text":
                "Mouser #",
                "search-url":
                "http://uk.mouser.com/Search/Refine.aspx?Keyword="
            }, {
                "field": "octopart",
                "text": "Octopart",
                "search-url": "https://octopart.com/search?q="
            }]
        }, {
            "field": "placement",
            "text": "Layer"
        }, {
            "field": "notes",
            "text": "Notes"
        }]

    # Set up the BoM file name
    bom_path = os.path.join(config.cfg['base-dir'],
                            config.cfg['locations']['build'], 'bom')
    # Create path if it doesn't exist already
    utils.create_dir(bom_path)

    board_name = config.cfg['name']
    board_revision = config.brd['config'].get('rev')
    base_name = "%s_rev_%s" % (board_name, board_revision)

    bom_html = os.path.join(bom_path, base_name + '_%s.html' % 'bom')
    bom_csv = os.path.join(bom_path, base_name + '_%s.csv' % 'bom')

    html = []
    csv = []

    html.append('<html>')
    html.append('<style type="text/css">')
    try:
        css = config.stl['layout']['bom']['css']
    except:
        css = []
    for line in css:
        html.append(line)
    html.append('</style>')
    html.append('<table class="tg">')

    header = []
    for item in bom_content:
        if item['field'] == 'suppliers':
            for supplier in item['suppliers']:
                header.append("%s" % supplier['text'])
        else:
            header.append("%s" % item['text'])
            if item['field'] == 'quantity' and quantity != None:
                header.append("@%s" % quantity)

    html.append('  <tr>')
    html.append(
        '    <th class="tg-title" colspan="%s">Bill of materials -- %s rev %s</th>'
        % (len(header), board_name, board_revision))
    html.append('  </tr>')
    html.append('  <tr>')
    for item in header:
        html.append('    <th class="tg-header">%s</th>' % item)
    html.append('  </tr>')

    uncateg_content = []
    dnp_content = []
    index = 1

    for desc in bom_dict:
        content = []
        for item in bom_content:
            if item['field'] == 'line-item':
                content.append("<strong>%s</strong>" % str(index))
            elif item['field'] == 'suppliers':
                for supplier in item['suppliers']:

                    try:
                        number = bom_dict[desc][item['field']][
                            supplier['field']]
                    except:
                        number = ""

                    search_url = supplier.get('search-url')
                    if search_url != None:
                        content.append('<a href="%s%s">%s</a>' %
                                       (search_url, number, number))
                    else:
                        content.append(number)

            elif item['field'] == 'quantity':
                units = len(bom_dict[desc]['refdefs'])
                content.append("%s" % (str(units)))
                if quantity != None:
                    content.append("%s" % (str(units * int(quantity))))
            elif item['field'] == 'designators':
                # Natural/human sort the list of designators
                sorted_list = sorted(bom_dict[desc]['refdefs'],
                                     key=natural_key)

                refdefs = ''
                for refdef in sorted_list[:-1]:
                    refdefs += "%s " % refdef
                refdefs += "%s" % sorted_list[-1]
                content.append("%s " % refdefs)
            elif item['field'] == 'description':
                content.append("%s " % desc)
            else:
                try:
                    content.append(bom_dict[desc][item['field']])
                except:
                    content.append("")

        if desc == uncateg_text:
            uncateg_content = content
        elif desc == dnp_text:
            dnp_content = content
        else:
            html.append('  <tr>')
            for item in content:
                html.append('    <td class="tg-item-%s">%s</td>' %
                            (('odd', 'even')[index % 2 == 0], item))
            html.append('  </tr>')
            index += 1

    for content in (dnp_content, uncateg_content):
        html.append('  <tr class="tg-skip">')
        html.append('  </tr>')
        html.append('  <tr>')
        if len(content) > 0:
            content[0] = index
        for item in content:
            html.append('    <td class="tg-item-%s">%s</td>' %
                        (('odd', 'even')[index % 2 == 0], item))
        html.append('  </tr>')
        index += 1

    html.append('</table>')

    html.append(
        '<p>Generated by <a href="http://pcbmode.com">PCBmodE</a>, an open source PCB design software. PCBmodE was written and is maintained by <a href="http://boldport.com">Boldport</a>, creators of beautifully functional circuits.'
    )

    html.append('</html>')

    with open(bom_html, "wb") as f:
        for line in html:
            f.write(line + '\n')