def _placeDocs(self): """ Places documentation blocks on the documentation layer """ try: docs_dict = config.brd['documentation'] except: return for key in docs_dict: location = utils.toPoint(docs_dict[key]['location']) docs_dict[key]['location'] = [0, 0] shape_group = et.SubElement(self._layers['documentation']['layer'], 'g') shape_group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'module-shapes') shape_group.set('{' + config.cfg['ns']['pcbmode'] + '}doc-key', key) shape_group.set( 'transform', "translate(%s,%s)" % (location.x, config.cfg['invert-y'] * location.y)) location = docs_dict[key]['location'] docs_dict[key]['location'] = [0, 0] shape = Shape(docs_dict[key]) style = Style(docs_dict[key], 'documentation') shape.setStyle(style) element = place.placeShape(shape, shape_group)
def _placeDocs(self): """ Places documentation blocks on the documentation layer """ try: docs_dict = config.brd['documentation'] except: return for key in docs_dict: location = utils.toPoint(docs_dict[key]['location']) docs_dict[key]['location'] = [0, 0] shape_group = et.SubElement(self._layers['documentation']['layer'], 'g') shape_group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'module-shapes') shape_group.set('{'+config.cfg['ns']['pcbmode']+'}doc-key', key) shape_group.set('transform', "translate(%s,%s)" % (location.x, config.cfg['invert-y']*location.y)) location = docs_dict[key]['location'] docs_dict[key]['location'] = [0, 0] shape = Shape(docs_dict[key]) style = Style(docs_dict[key], 'documentation') shape.setStyle(style) element = place.placeShape(shape, shape_group)
def _placeLayerIndex(self): """ Adds a drill index """ text_dict = config.stl['layout']['layer-index']['text'] text_dict['type'] = 'text' # Set the height (and width) of the rectangle (square) to the # size of the text rect_width = utils.parseDimension(text_dict['font-size'])[0] rect_height = rect_width rect_gap = 0.25 # Get location, or generate one try: location = config.brd['layer-index']['location'] except: # If not location is specified, put the drill index at the # top right of the board. The 'gap' defines the extra # spcae between the top of the largest drill and the # board's edge gap = 2 location = [self._width/2+gap, self._height/2-rect_height/2] location = utils.toPoint(location) rect_dict = {} rect_dict['type'] = 'rect' rect_dict['style'] = 'fill' rect_dict['width'] = rect_width rect_dict['height'] = rect_height # Create group for placing index for pcb_layer in utils.getSurfaceLayers(): for sheet in ['copper', 'soldermask', 'silkscreen', 'assembly', 'solderpaste']: layer = self._layers[pcb_layer][sheet]['layer'] transform = "translate(%s,%s)" % (location.x, config.cfg['invert-y']*location.y) group = et.SubElement(layer, 'g', transform=transform) group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'layer-index') rect_shape = Shape(rect_dict) style = Style(rect_dict, sheet) rect_shape.setStyle(style) place.placeShape(rect_shape, group) text_dict['value'] = "%s %s" % (pcb_layer, sheet) #text_dict['location'] = [rect_width+rect_gap+text_width, 0] text_shape = Shape(text_dict) text_width = text_shape.getWidth() style = Style(text_dict, sheet) text_shape.setStyle(style) element = place.placeShape(text_shape, group) element.set("transform", "translate(%s,%s)" % (rect_width/2+rect_gap+text_width/2, 0)) location.y += config.cfg['invert-y']*(rect_height+rect_gap) location.y += config.cfg['invert-y']*(rect_height+rect_gap*2)
def extractDocs(svg_in): """ Extracts the position of the documentation elements and updates the board's json """ # Get copper refdef shape groups from SVG data xpath_expr = '//svg:g[@pcbmode:sheet="documentation"]//svg:g[@pcbmode:type="module-shapes"]' docs = svg_in.findall(xpath_expr, namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) for doc in docs: doc_key = doc.get('{' + config.cfg['ns']['pcbmode'] + '}doc-key') translate_data = utils.parseTransform(doc.get('transform')) location = translate_data['location'] location.y *= config.cfg['invert-y'] current_location = utils.toPoint( config.brd['documentation'][doc_key]['location']) if current_location != location: config.brd['documentation'][doc_key]['location'] = [ location.x, location.y ] msg.subInfo("Found new location ([%s, %s]) for '%s'" % (location.x, location.y, doc_key)) # Extract drill index location xpath_expr = '//svg:g[@pcbmode:sheet="drills"]//svg:g[@pcbmode:type="drill-index"]' drill_index = svg_in.find(xpath_expr, namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) transform_dict = utils.parseTransform(drill_index.get('transform')) location = transform_dict['location'] location.y *= config.cfg['invert-y'] # Modify the location in the board's config file. If a # 'drill-index' field doesn't exist, create it drill_index_dict = config.brd.get('drill-index') if drill_index_dict == None: config.brd['drill-index'] = {} config.brd['drill-index']['location'] = [location.x, location.y] # Save board config to file (everything is saved, not only the # component data) filename = os.path.join(config.cfg['locations']['boards'], config.cfg['name'], config.cfg['name'] + '.json') try: with open(filename, 'wb') as f: f.write(json.dumps(config.brd, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % filename)
def extractDocs(svg_in): """ Extracts the position of the documentation elements and updates the board's json """ # Get copper refdef shape groups from SVG data xpath_expr = '//svg:g[@pcbmode:sheet="documentation"]//svg:g[@pcbmode:type="module-shapes"]' docs = svg_in.findall(xpath_expr, namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) for doc in docs: doc_key = doc.get('{'+config.cfg['ns']['pcbmode']+'}doc-key') translate_data = utils.parseTransform(doc.get('transform')) location = translate_data['location'] location.y *= config.cfg['invert-y'] current_location = utils.toPoint(config.brd['documentation'][doc_key]['location']) if current_location != location: config.brd['documentation'][doc_key]['location'] = [location.x, location.y] msg.subInfo("Found new location ([%s, %s]) for '%s'" % (location.x, location.y, doc_key)) # Extract drill index location xpath_expr = '//svg:g[@pcbmode:sheet="drills"]//svg:g[@pcbmode:type="drill-index"]' drill_index = svg_in.find(xpath_expr, namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) transform_dict = utils.parseTransform(drill_index.get('transform')) location = transform_dict['location'] location.y *= config.cfg['invert-y'] # Modify the location in the board's config file. If a # 'drill-index' field doesn't exist, create it drill_index_dict = config.brd.get('drill-index') if drill_index_dict == None: config.brd['drill-index'] = {} config.brd['drill-index']['location'] = [location.x, location.y] # Save board config to file (everything is saved, not only the # component data) filename = os.path.join(config.cfg['locations']['boards'], config.cfg['name'], config.cfg['name'] + '.json') try: with open(filename, 'wb') as f: f.write(json.dumps(config.brd, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % filename)
def _processPins(self): """ Converts pins into 'shapes' """ pins = self._footprint.get('pins') or {} for pin in pins: pin_location = pins[pin]['layout']['location'] or [0, 0] try: pad_name = pins[pin]['layout']['pad'] except: msg.error("Each defined 'pin' must have a 'pad' name that is defined in the 'pads' dection of the footprint.") try: pad_dict = self._footprint['pads'][pad_name] except: msg.error("There doesn't seem to be a pad definition for pad '%s'." % pad_name) # Get the pin's rotation, if any pin_rotate = pins[pin]['layout'].get('rotate') or 0 shapes = pad_dict.get('shapes') or [] for shape_dict in shapes: shape_dict = shape_dict.copy() # Which layer(s) to place the shape on layers = utils.getExtendedLayerList(shape_dict.get('layers') or ['top']) # Add the pin's location to the pad's location shape_location = shape_dict.get('location') or [0, 0] shape_dict['location'] = [shape_location[0] + pin_location[0], shape_location[1] + pin_location[1]] # Add the pin's rotation to the pad's rotation shape_dict['rotate'] = (shape_dict.get('rotate') or 0) + pin_rotate # Determine if and which label to show show_name = pins[pin]['layout'].get('show-label') or True if show_name == True: pin_label = pins[pin]['layout'].get('label') or pin for layer in layers: shape = Shape(shape_dict) style = Style(shape_dict, 'conductor') shape.setStyle(style) try: self._shapes['conductor'][layer].append(shape) except: self._shapes['conductor'][layer] = [] self._shapes['conductor'][layer].append(shape) for stype in ['soldermask','solderpaste']: # Get a custom shape specification if it exists sdict_list = shape_dict.get(stype) # Not defined; default if sdict_list == None: # Use default settings for shape based on # the pad shape sdict = shape_dict.copy() # Which shape type is the pad? shape_type = shape.getType() # Apply modifier based on shape type if shape_type == 'path': sdict['scale'] = shape.getScale()*config.brd['distances'][stype]['path-scale'] elif shape_type in ['rect', 'rectangle']: sdict['width'] += config.brd['distances'][stype]['rect-buffer'] sdict['height'] += config.brd['distances'][stype]['rect-buffer'] elif shape_type in ['circ', 'circle']: sdict['diameter'] += config.brd['distances'][stype]['circle-buffer'] else: pass # Create shape based on new dictionary sshape = Shape(sdict) # Define style sstyle = Style(sdict, stype) # Apply style sshape.setStyle(sstyle) # Add shape to footprint's shape dictionary #self._shapes[stype][layer].append(sshape) try: self._shapes[stype][layer].append(sshape) except: self._shapes[stype][layer] = [] self._shapes[stype][layer].append(sshape) # Do not place shape elif (sdict_list == {}) or (sdict_list == []): pass # Custom shape definition else: # If dict (as before support of multiple # shapes) then append to a single element # list if type(sdict_list) is dict: sdict_list = [sdict_list] # Process list of shapes for sdict_ in sdict_list: sdict = sdict_.copy() shape_loc = utils.toPoint(sdict.get('location') or [0, 0]) # Apply rotation sdict['rotate'] = (sdict.get('rotate') or 0) + pin_rotate # Rotate location shape_loc.rotate(pin_rotate, Point()) sdict['location'] = [shape_loc.x + pin_location[0], shape_loc.y + pin_location[1]] # Create new shape sshape = Shape(sdict) # Create new style sstyle = Style(sdict, stype) # Apply style sshape.setStyle(sstyle) # Add shape to footprint's shape dictionary #self._shapes[stype][layer].append(sshape) try: self._shapes[stype][layer].append(sshape) except: self._shapes[stype][layer] = [] self._shapes[stype][layer].append(sshape) # Add pin label if (pin_label != None): shape.setLabel(pin_label) drills = pad_dict.get('drills') or [] for drill_dict in drills: drill_dict = drill_dict.copy() drill_dict['type'] = drill_dict.get('type') or 'drill' drill_location = drill_dict.get('location') or [0, 0] drill_dict['location'] = [drill_location[0] + pin_location[0], drill_location[1] + pin_location[1]] shape = Shape(drill_dict) style = Style(drill_dict, 'drills') shape.setStyle(style) try: self._shapes['drills']['top'].append(shape) except: self._shapes['drills']['top'] = [] self._shapes['drills']['top'].append(shape)
def extractComponents(svg_in): """ """ # Get copper refdef shape groups from SVG data xpath_expr_copper_pads = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="copper"]//svg:g[@pcbmode:sheet="pads"]//svg:g[@pcbmode:refdef]' xpath_expr_refdefs = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="silkscreen"]//svg:g[@pcbmode:type="refdef"][@pcbmode:refdef="%s"]' for pcb_layer in utils.getSurfaceLayers(): shapes = svg_in.findall(xpath_expr_copper_pads % pcb_layer, namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) for shape in shapes: transform_data = utils.parseTransform(shape.get('transform')) refdef = shape.get('{'+config.cfg['ns']['pcbmode']+'}refdef') comp_dict = config.brd['components'][refdef] # Check if the copper shapes are on the same layer as placement. # Ignore if otherwise. While it is possible that a component is placed # on one layer but all its components are on another, it is very # unlikely, and doesn't make much sense. on_layer = comp_dict.get('layer') or 'top' if pcb_layer == on_layer: new_location = transform_data['location'] old_location = utils.toPoint(comp_dict.get('location') or [0, 0]) # Invert 'y' coordinate new_location.y *= config.cfg['invert-y'] # Change component location if needed if new_location != old_location: msg.subInfo("Component %s moved from %s to %s" % (refdef, old_location, new_location)) comp_dict['location'] = [new_location.x, new_location.y] # Change component rotation if needed if transform_data['type'] == 'matrix': old_rotate = comp_dict.get('rotate') or 0 new_rotate = transform_data['rotate'] comp_dict['rotate'] = round((old_rotate+new_rotate) % 360,4) msg.subInfo("Component %s rotated from %s to %s" % (refdef, old_rotate, comp_dict['rotate'])) refdef_texts = svg_in.findall(xpath_expr_refdefs % (pcb_layer, refdef), namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) for refdef_text in refdef_texts: # Get refdef group location transform_data = utils.parseTransform(refdef_text.get('transform')) group_loc = transform_data['location'] # Invert 'y' coordinate group_loc.y *= config.cfg['invert-y'] # Get the refdef text path inside the refdef group refdef_path = refdef_text.find("svg:path", namespaces={'svg':config.cfg['ns']['svg']}) try: transform_data = utils.parseTransform(refdef_path.get('transform')) except: transform_data['location'] = Point(0,0) refdef_loc = transform_data['location'] # Invert 'y' coordinate refdef_loc.y *= config.cfg['invert-y'] # Current ('old') location of the component current_component_loc = utils.toPoint(comp_dict.get('location', [0, 0])) try: current_refdef_loc = utils.toPoint(comp_dict['silkscreen']['refdef']['location']) except: current_refdef_loc = Point() # TODO: this is an embarassing mess, but I have too much of # a headache to tidy it up! if pcb_layer == 'bottom': current_refdef_loc.x = -current_refdef_loc.x diff = group_loc-current_component_loc new_refdef_loc = diff + current_refdef_loc if pcb_layer == 'bottom': new_refdef_loc.x = -new_refdef_loc.x # Change the location in the dictionary if new_refdef_loc != current_refdef_loc: try: tmp = comp_dict['silkscreen'] except: comp_dict['silkscreen'] = {} try: tmp = comp_dict['silkscreen']['refdef'] except: comp_dict['silkscreen']['refdef'] = {} comp_dict['silkscreen']['refdef']['location'] = [new_refdef_loc.x, new_refdef_loc.y] # Save board config to file (everything is saved, not only the # component data) filename = os.path.join(config.cfg['locations']['boards'], config.cfg['name'], config.cfg['name'] + '.json') try: with open(filename, 'wb') as f: f.write(json.dumps(config.brd, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % filename) return
def _placeDrillIndex(self): """ Adds a drill index """ # Get the drills sheet / SVG layer drill_layer = self._layers['drills']['layer'] ns = { 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] } drills = drill_layer.findall(".//*[@pcbmode:diameter]", namespaces=ns) drills_dict = {} largest_drill = 0 drill_count = 0 for drill in drills: diameter = drill.get('{' + config.cfg['ns']['pcbmode'] + '}diameter') diameter = round(float(diameter), 2) if diameter not in drills_dict: drills_dict[diameter] = 1 else: drills_dict[diameter] += 1 if diameter > largest_drill: largest_drill = diameter drill_count += 1 # Get location, or generate one try: location = config.brd['drill-index']['location'] except: # If not location is specified, put the drill index at the # bottom left of the board. The 'gap' defines the extra # spcae between the top of the largest drill and the # board's edge gap = 2 location = [-self._width / 2, -(self._height / 2 + gap)] location = utils.toPoint(location) # Create group for placing index transform = "translate(%s,%s)" % (location.x, config.cfg['invert-y'] * location.y) group = et.SubElement(drill_layer, 'g', transform=transform) group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'drill-index') text_style_dict = config.stl['layout']['drill-index'].get('text') text_style = utils.dict_to_style(text_style_dict) count_style_dict = config.stl['layout']['drill-index'].get( 'count-text') count_style = utils.dict_to_style(count_style_dict) count_style_dict['font-size'] /= 2 drill_size_style = utils.dict_to_style(count_style_dict) if drill_count == 0: text = 'No drills' elif drill_count == 1: text = '1 drill: ' else: text = '%s drills: ' % drill_count t = et.SubElement(group, 'text', x=str(0), y=str(0), style=text_style) t.text = text # "new line" location.y = -(largest_drill / 2 + 0.5) location.x = largest_drill / 2 gap = 2 for diameter in reversed(sorted(drills_dict)): path = svg.drillPath(diameter) transform = "translate(%s,%s)" % ( location.x, config.cfg['invert-y'] * location.y) element = et.SubElement(group, 'path', d=path, transform=transform) element.set("fill-rule", "evenodd") t = et.SubElement(group, 'text', x=str(location.x), y=str(-location.y), dy="%s" % (config.cfg['invert-y'] * 0.25), style=count_style) t.text = str(drills_dict[diameter]) t = et.SubElement(group, 'text', x=str(location.x), y=str(-location.y), dy="%s" % (config.cfg['invert-y'] * -0.5), style=drill_size_style) t.text = "%s mm" % diameter location.x += max(diameter, 2.5)
def _placeLayerIndex(self): """ Adds a drill index """ text_dict = config.stl['layout']['layer-index']['text'] text_dict['type'] = 'text' # Set the height (and width) of the rectangle (square) to the # size of the text rect_width = utils.parseDimension(text_dict['font-size'])[0] rect_height = rect_width rect_gap = 0.25 # Get location, or generate one try: location = config.brd['layer-index']['location'] except: # If not location is specified, put the drill index at the # top right of the board. The 'gap' defines the extra # spcae between the top of the largest drill and the # board's edge gap = 2 location = [ self._width / 2 + gap, self._height / 2 - rect_height / 2 ] location = utils.toPoint(location) rect_dict = {} rect_dict['type'] = 'rect' rect_dict['style'] = 'fill' rect_dict['width'] = rect_width rect_dict['height'] = rect_height # Create group for placing index for pcb_layer in utils.getSurfaceLayers(): for sheet in [ 'copper', 'soldermask', 'silkscreen', 'assembly', 'solderpaste' ]: layer = self._layers[pcb_layer][sheet]['layer'] transform = "translate(%s,%s)" % ( location.x, config.cfg['invert-y'] * location.y) group = et.SubElement(layer, 'g', transform=transform) group.set('{' + config.cfg['ns']['pcbmode'] + '}type', 'layer-index') rect_shape = Shape(rect_dict) style = Style(rect_dict, sheet) rect_shape.setStyle(style) place.placeShape(rect_shape, group) text_dict['value'] = "%s %s" % (pcb_layer, sheet) #text_dict['location'] = [rect_width+rect_gap+text_width, 0] text_shape = Shape(text_dict) text_width = text_shape.getWidth() style = Style(text_dict, sheet) text_shape.setStyle(style) element = place.placeShape(text_shape, group) element.set( "transform", "translate(%s,%s)" % (rect_width / 2 + rect_gap + text_width / 2, 0)) location.y += config.cfg['invert-y'] * (rect_height + rect_gap) location.y += config.cfg['invert-y'] * (rect_height + rect_gap * 2)
def extractComponents(svg_in): """ """ xpath_expr_place = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="placement"]//svg:g[@pcbmode:type="%s"]' for pcb_layer in config.stk['surface-layer-names']: # Find all 'component' markers markers = svg_in.findall(xpath_expr_place % (pcb_layer, 'component'), namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) # Find all 'shape' markers markers += svg_in.findall(xpath_expr_place % (pcb_layer, 'shape'), namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) for marker in markers: transform_data = utils.parseTransform(marker.get('transform')) refdef = marker.get('{' + config.cfg['ns']['pcbmode'] + '}refdef') marker_type = marker.get('{' + config.cfg['ns']['pcbmode'] + '}type') if marker_type == 'component': comp_dict = config.brd['components'][refdef] elif marker_type == 'shape': comp_dict = config.brd['shapes'][refdef] else: continue new_location = transform_data['location'] old_location = utils.toPoint(comp_dict.get('location') or [0, 0]) # Invert 'y' coordinate new_location.y *= config.cfg['invert-y'] # Change component location if needed if new_location != old_location: x1 = utils.niceFloat(old_location.x) y1 = utils.niceFloat(old_location.y) x2 = utils.niceFloat(new_location.x) y2 = utils.niceFloat(new_location.y) msg.subInfo("%s has moved from [%s,%s] to [%s,%s]" % (refdef, x1, y2, x2, y2)) # Apply new location comp_dict['location'] = [x2, y2] # Change component rotation if needed if transform_data['type'] == 'matrix': old_rotate = comp_dict.get('rotate') or 0 new_rotate = transform_data['rotate'] comp_dict['rotate'] = utils.niceFloat( (old_rotate + new_rotate) % 360) msg.subInfo("Component %s rotated from %s to %s" % (refdef, old_rotate, comp_dict['rotate'])) # Save board config to file (everything is saved, not only the # component data) filename = os.path.join(config.cfg['locations']['boards'], config.cfg['name'], config.cfg['name'] + '.json') try: with open(filename, 'wb') as f: f.write(json.dumps(config.brd, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % filename) return
def extractComponents(svg_in): """ """ # Get copper refdef shape groups from SVG data xpath_expr_copper_pads = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="copper"]//svg:g[@pcbmode:sheet="pads"]//svg:g[@pcbmode:refdef]' xpath_expr_refdefs = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="silkscreen"]//svg:g[@pcbmode:type="refdef"][@pcbmode:refdef="%s"]' for pcb_layer in utils.getSurfaceLayers(): shapes = svg_in.findall(xpath_expr_copper_pads % pcb_layer, namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) for shape in shapes: transform_data = utils.parseTransform(shape.get('transform')) refdef = shape.get('{' + config.cfg['ns']['pcbmode'] + '}refdef') comp_dict = config.brd['components'][refdef] # Check if the copper shapes are on the same layer as placement. # Ignore if otherwise. While it is possible that a component is placed # on one layer but all its components are on another, it is very # unlikely, and doesn't make much sense. on_layer = comp_dict.get('layer') or 'top' if pcb_layer == on_layer: new_location = transform_data['location'] old_location = utils.toPoint( comp_dict.get('location') or [0, 0]) # Invert 'y' coordinate new_location.y *= config.cfg['invert-y'] # Change component location if needed if new_location != old_location: msg.subInfo("Component %s moved from %s to %s" % (refdef, old_location, new_location)) comp_dict['location'] = [new_location.x, new_location.y] # Change component rotation if needed if transform_data['type'] == 'matrix': old_rotate = comp_dict.get('rotate') or 0 new_rotate = transform_data['rotate'] comp_dict['rotate'] = round( (old_rotate + new_rotate) % 360, 4) msg.subInfo("Component %s rotated from %s to %s" % (refdef, old_rotate, comp_dict['rotate'])) refdef_texts = svg_in.findall(xpath_expr_refdefs % (pcb_layer, refdef), namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) for refdef_text in refdef_texts: # Get refdef group location transform_data = utils.parseTransform( refdef_text.get('transform')) group_loc = transform_data['location'] # Invert 'y' coordinate group_loc.y *= config.cfg['invert-y'] # Get the refdef text path inside the refdef group refdef_path = refdef_text.find( "svg:path", namespaces={'svg': config.cfg['ns']['svg']}) try: transform_data = utils.parseTransform( refdef_path.get('transform')) except: transform_data['location'] = Point(0, 0) refdef_loc = transform_data['location'] # Invert 'y' coordinate refdef_loc.y *= config.cfg['invert-y'] # Current ('old') location of the component current_component_loc = utils.toPoint( comp_dict.get('location', [0, 0])) try: current_refdef_loc = utils.toPoint( comp_dict['silkscreen']['refdef']['location']) except: current_refdef_loc = Point() # TODO: this is an embarassing mess, but I have too much of # a headache to tidy it up! if pcb_layer == 'bottom': current_refdef_loc.x = -current_refdef_loc.x diff = group_loc - current_component_loc new_refdef_loc = diff + current_refdef_loc if pcb_layer == 'bottom': new_refdef_loc.x = -new_refdef_loc.x # Change the location in the dictionary if new_refdef_loc != current_refdef_loc: try: tmp = comp_dict['silkscreen'] except: comp_dict['silkscreen'] = {} try: tmp = comp_dict['silkscreen']['refdef'] except: comp_dict['silkscreen']['refdef'] = {} comp_dict['silkscreen']['refdef']['location'] = [ new_refdef_loc.x, new_refdef_loc.y ] # Save board config to file (everything is saved, not only the # component data) filename = os.path.join(config.cfg['locations']['boards'], config.cfg['name'], config.cfg['name'] + '.json') try: with open(filename, 'wb') as f: f.write(json.dumps(config.brd, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % filename) return
def extractRefdefs(svg_in): """ """ xpath_refdefs = '//svg:g[@pcbmode:sheet="silkscreen"]//svg:g[@pcbmode:type="refdef"]' refdefs_elements = svg_in.findall(xpath_refdefs, namespaces={ 'pcbmode': config.cfg['ns']['pcbmode'], 'svg': config.cfg['ns']['svg'] }) for refdef_element in refdefs_elements: # Get refdef group location group_trans_data = utils.parseTransform( refdef_element.get('transform')) group_loc = group_trans_data['location'] # Invert 'y' coordinate because Inkscape group_loc.y *= config.cfg['invert-y'] # Get reference designator refdef = refdef_element.get('{' + config.cfg['ns']['pcbmode'] + '}refdef') # Get component dictionary refdef_dict = config.brd['components'].get(refdef) # Get component placement layer comp_loc = utils.toPoint(refdef_dict.get('location', [0, 0])) if comp_loc != group_loc: # Get location of the refdef from the component dict try: loc_old = utils.toPoint( refdef_dict['silkscreen']['refdef']['location']) except: loc_old = Point() # Get component placement layer comp_layer = refdef_dict.get('layer', 'top') # Get component rotation comp_rotation = refdef_dict.get('rotate', 0) difference = group_loc - comp_loc difference.rotate(-comp_rotation, Point()) if comp_layer == 'bottom': difference.x *= -1 loc_new = loc_old + difference try: tmp = refdef_dict['silkscreen'] except: comp_dict['silkscreen'] = {} try: tmp = refdef_dict['silkscreen']['refdef'] except: refdef_dict['silkscreen']['refdef'] = {} x = utils.niceFloat(loc_new.x) y = utils.niceFloat(loc_new.y) refdef_dict['silkscreen']['refdef']['location'] = [x, y] # Save board config to file (everything is saved, not only the # component data) filename = os.path.join(config.cfg['locations']['boards'], config.cfg['name'], config.cfg['name'] + '.json') try: with open(filename, 'wb') as f: f.write(json.dumps(config.brd, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % filename) return
def __init__(self, shape): gerber_lp = None mirror = False self._shape_dict = shape # Invert rotation so it's clock-wise. Inkscape is counter-clockwise and # it's unclear to ma what's the "right" direction. clockwise makse more # sense to me. This should be the only place to make the change. self._inv_rotate = -1 try: self._type = shape.get('type') except: msg.error("Shapes must have a 'type' defined") # A 'layer' type is a copy of the outline. Here we copy the # outline shape and override the type if self._type in ['layer']: self._shape_dict = config.brd['outline'].get('shape').copy() self._type = self._shape_dict.get('type') self._place_mirrored = shape.get('mirror') or False self._rotate = shape.get('rotate') or 0 self._rotate *= self._inv_rotate self._rotate_point = shape.get('rotate-point') or Point(0, 0) self._scale = shape.get('scale') or 1 self._pour_buffer = shape.get('buffer-to-pour') # A general purpose label field; intended for use for pad # labels self._label = None if self._type in ['rect', 'rectangle']: path = svg.width_and_height_to_path(self._shape_dict['width'], self._shape_dict['height'], self._shape_dict.get('radii')) elif self._type in ['circ', 'circle', 'round']: path = svg.circle_diameter_to_path(self._shape_dict['diameter']) elif self._type in ['drill']: self._diameter = self._shape_dict['diameter'] path = svg.drillPath(self._diameter) elif self._type in ['text', 'string']: try: self._text = self._shape_dict['value'] except KeyError: msg.error( "Could not find the text to display. The text to be displayed should be defined in the 'value' field, for example, 'value': 'DEADBEEF\\nhar\\nhar'" ) # Get the font's name font = self._shape_dict.get('font-family') or config.stl['layout'][ 'defaults']['font-family'] # Search for the font SVG in these paths paths = [ os.path.join(config.cfg['base-dir']), os.path.join(os.path.dirname(os.path.realpath(__file__)), '..') ] font_filename = "%s.svg" % font filenames = '' font_data = None for path in paths: filename = os.path.join(path, config.cfg['locations']['fonts'], font_filename) filenames += " %s \n" % filename if os.path.isfile(filename): font_data = et.ElementTree(file=filename) break if font_data == None: msg.error( "Couldn't find style file %s. Looked for it here:\n%s" % (font_filename, filenames)) try: fs = self._shape_dict['font-size'] except: msg.error( "A 'font-size' attribute must be specified for a 'text' type" ) ls = self._shape_dict.get('letter-spacing') or '0mm' lh = self._shape_dict.get('line-height') or fs font_size, letter_spacing, line_height = utils.getTextParams( fs, ls, lh) # With the units-per-em we can figure out the scale factor # to use for the desired font size units_per_em = float( font_data.find("//n:font-face", namespaces={ 'n': config.cfg['namespace']['svg'] }).get('units-per-em')) or 1000 self._scale = font_size / units_per_em # Get the path to use. This returns the path without # scaling, which will be applied later, in the same manner # as to the other shape types path, gerber_lp = utils.textToPath(font_data, self._text, letter_spacing, line_height, self._scale) # In the case where the text is an outline/stroke instead # of a fill we get rid of the gerber_lp if self._shape_dict.get('style') == 'stroke': gerber_lp = None self._rotate += 180 elif self._type in ['path']: path = self._shape_dict.get('value') else: msg.error("'%s' is not a recongnised shape type" % self._type) self._path = SvgPath(path, gerber_lp) self._path.transform(scale=self._scale, rotate_angle=self._rotate, rotate_point=self._rotate_point, mirror=self._place_mirrored) self._gerber_lp = (shape.get('gerber-lp') or shape.get('gerber_lp') or gerber_lp or None) self._location = utils.toPoint(shape.get('location', [0, 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
def extractComponents(svg_in): """ """ xpath_expr_place = '//svg:g[@pcbmode:pcb-layer="%s"]//svg:g[@pcbmode:sheet="placement"]//svg:g[@pcbmode:type="%s"]' for pcb_layer in config.stk['surface-layer-names']: # Find all 'component' markers markers = svg_in.findall(xpath_expr_place % (pcb_layer, 'component'), namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) # Find all 'shape' markers markers += svg_in.findall(xpath_expr_place % (pcb_layer, 'shape'), namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) for marker in markers: transform_data = utils.parseTransform(marker.get('transform')) refdef = marker.get('{'+config.cfg['ns']['pcbmode']+'}refdef') marker_type = marker.get('{'+config.cfg['ns']['pcbmode']+'}type') if marker_type == 'component': comp_dict = config.brd['components'][refdef] elif marker_type == 'shape': comp_dict = config.brd['shapes'][refdef] else: continue new_location = transform_data['location'] old_location = utils.toPoint(comp_dict.get('location') or [0, 0]) # Invert 'y' coordinate new_location.y *= config.cfg['invert-y'] # Change component location if needed if new_location != old_location: x1 = utils.niceFloat(old_location.x) y1 = utils.niceFloat(old_location.y) x2 = utils.niceFloat(new_location.x) y2 = utils.niceFloat(new_location.y) msg.subInfo("%s has moved from [%s,%s] to [%s,%s]" % (refdef,x1,y2,x2,y2)) # Apply new location comp_dict['location'] = [x2,y2] # Change component rotation if needed if transform_data['type'] == 'matrix': old_rotate = comp_dict.get('rotate') or 0 new_rotate = transform_data['rotate'] comp_dict['rotate'] = utils.niceFloat((old_rotate+new_rotate) % 360) msg.subInfo("Component %s rotated from %s to %s" % (refdef, old_rotate, comp_dict['rotate'])) # Save board config to file (everything is saved, not only the # component data) filename = os.path.join(config.cfg['locations']['boards'], config.cfg['name'], config.cfg['name'] + '.json') try: with open(filename, 'wb') as f: f.write(json.dumps(config.brd, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % filename) return
def extractRefdefs(svg_in): """ """ xpath_refdefs = '//svg:g[@pcbmode:sheet="silkscreen"]//svg:g[@pcbmode:type="refdef"]' refdefs_elements = svg_in.findall(xpath_refdefs, namespaces={'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']}) for refdef_element in refdefs_elements: # Get refdef group location group_trans_data = utils.parseTransform(refdef_element.get('transform')) group_loc = group_trans_data['location'] # Invert 'y' coordinate because Inkscape group_loc.y *= config.cfg['invert-y'] # Get reference designator refdef = refdef_element.get('{'+config.cfg['ns']['pcbmode']+'}refdef') # Get component dictionary refdef_dict = config.brd['components'].get(refdef) # Get component placement layer comp_loc = utils.toPoint(refdef_dict.get('location', [0,0])) if comp_loc != group_loc: # Get location of the refdef from the component dict try: loc_old = utils.toPoint(refdef_dict['silkscreen']['refdef']['location']) except: loc_old = Point() # Get component placement layer comp_layer = refdef_dict.get('layer', 'top') # Get component rotation comp_rotation = refdef_dict.get('rotate', 0) difference = group_loc-comp_loc difference.rotate(-comp_rotation, Point()) if comp_layer == 'bottom': difference.x *= -1 loc_new = loc_old+difference try: tmp = refdef_dict['silkscreen'] except: comp_dict['silkscreen'] = {} try: tmp = refdef_dict['silkscreen']['refdef'] except: refdef_dict['silkscreen']['refdef'] = {} x = utils.niceFloat(loc_new.x) y = utils.niceFloat(loc_new.y) refdef_dict['silkscreen']['refdef']['location'] = [x,y] # Save board config to file (everything is saved, not only the # component data) filename = os.path.join(config.cfg['locations']['boards'], config.cfg['name'], config.cfg['name'] + '.json') try: with open(filename, 'wb') as f: f.write(json.dumps(config.brd, sort_keys=True, indent=2)) except: msg.error("Cannot save file %s" % filename) return
def __init__(self, refdef, component): """ """ self._refdef = refdef self._layer = component.get('layer') or 'top' self._rotate = component.get('rotate') or 0 self._rotate_point = utils.toPoint(component.get('rotate-point') or [0, 0]) self._scale = component.get('scale') or 1 self._location = component.get('location') or [0, 0] # Get footprint definition and shapes try: self._footprint_name = component['footprint'] except: msg.error("Cannot find a 'footprint' name for refdef %s." % refdef) fname = os.path.join(config.cfg['base-dir'], config.cfg['locations']['components'], self._footprint_name + '.json') footprint_dict = utils.dictFromJsonFile(fname) footprint = Footprint(footprint_dict) footprint_shapes = footprint.getShapes() #------------------------------------------------ # Apply component-specific modifiers to footprint #------------------------------------------------ for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly', 'drills']: for layer in utils.getSurfaceLayers() + utils.getInternalLayers(): for shape in footprint_shapes[sheet][layer]: # In order to apply the rotation we need to adust the location # of each element shape.rotateLocation(self._rotate, self._rotate_point) shape.transformPath(self._scale, self._rotate, self._rotate_point, False, True) #-------------------------------------- # Remove silkscreen and assembly shapes #-------------------------------------- for sheet in ['silkscreen','assembly']: try: shapes_dict = component[sheet].get('shapes') or {} except: shapes_dict = {} # If the setting is to not show silkscreen shapes for the # component, delete the shapes from the shapes' dictionary if shapes_dict.get('show') == False: for pcb_layer in utils.getSurfaceLayers(): footprint_shapes[sheet][pcb_layer] = [] #---------------------------------------------------------- # Add silkscreen and assembly reference designator (refdef) #---------------------------------------------------------- for sheet in ['silkscreen','assembly']: try: refdef_dict = component[sheet].get('refdef') or {} except: refdef_dict = {} if refdef_dict.get('show') != False: layer = refdef_dict.get('layer') or 'top' # Rotate the refdef; if unspecified the rotation is the same as # the rotation of the component refdef_dict['rotate'] = refdef_dict.get('rotate') or 0 # Sometimes you'd want to keep all refdefs at the same angle # and not rotated with the component if refdef_dict.get('rotate-with-component') != False: refdef_dict['rotate'] += self._rotate refdef_dict['rotate-point'] = utils.toPoint(refdef_dict.get('rotate-point')) or self._rotate_point refdef_dict['location'] = refdef_dict.get('location') or [0, 0] refdef_dict['type'] = 'text' refdef_dict['value'] = refdef refdef_dict['font-family'] = (config.stl['layout'][sheet]['refdef'].get('font-family') or config.stl['defaults']['font-family']) refdef_dict['font-size'] = (config.stl['layout'][sheet]['refdef'].get('font-size') or "2mm") refdef_shape = Shape(refdef_dict) refdef_shape.is_refdef = True refdef_shape.rotateLocation(self._rotate, self._rotate_point) style = Style(refdef_dict, sheet, 'refdef') refdef_shape.setStyle(style) # Add the refdef to the silkscreen/assembly list. It's # important that this is added at the very end since the # plcament process assumes the refdef is last footprint_shapes[sheet][layer].append(refdef_shape) #------------------------------------------------------ # Invert 'top' and 'bottom' if layer is on the 'bottom' #------------------------------------------------------ if self._layer == 'bottom': layers = utils.getSurfaceLayers() layers_reversed = reversed(utils.getSurfaceLayers()) for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly']: sheet_dict = footprint_shapes[sheet] # TODO: this nasty hack won't work for more than two # layers, so when 2+ are supported this needs to be # revisited for i in range(0,len(layers)-1): sheet_dict['temp'] = sheet_dict.pop(layers[i]) sheet_dict[layers[i]] = sheet_dict.pop(layers[len(layers)-1-i]) sheet_dict[layers[len(layers)-1-i]] = sheet_dict.pop('temp') self._footprint_shapes = footprint_shapes
def __init__(self, refdef, component): """ """ self._refdef = refdef self._layer = component.get('layer') or 'top' self._rotate = component.get('rotate') or 0 self._rotate_point = utils.toPoint(component.get('rotate-point') or [0, 0]) self._scale = component.get('scale') or 1 self._location = component.get('location') or [0, 0] # Get footprint definition and shapes try: self._footprint_name = component['footprint'] except: msg.error("Cannot find a 'footprint' name for refdef %s." % refdef) fname = os.path.join(config.cfg['base-dir'], config.cfg['locations']['components'], self._footprint_name + '.json') footprint_dict = utils.dictFromJsonFile(fname) footprint = Footprint(footprint_dict) footprint_shapes = footprint.getShapes() #------------------------------------------------ # Apply component-specific modifiers to footprint #------------------------------------------------ for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly', 'drills']: for layer in utils.getSurfaceLayers() + utils.getInternalLayers(): for shape in footprint_shapes[sheet][layer]: # In order to apply the rotation we need to adust the location # of each element shape.rotateLocation(self._rotate, self._rotate_point) shape.transformPath(self._scale, self._rotate, self._rotate_point, False, True) #-------------------------------------- # Remove silkscreen and assembly shapes #-------------------------------------- for sheet in ['silkscreen','assembly']: try: shapes_dict = component[sheet].get('shapes') or {} except: shapes_dict = {} # If the setting is to not show silkscreen shapes for the # component, delete the shapes from the shapes' dictionary if shapes_dict.get('show') == False: for pcb_layer in utils.getSurfaceLayers(): footprint_shapes[sheet][pcb_layer] = [] #---------------------------------------------------------- # Add silkscreen and assembly reference designator (refdef) #---------------------------------------------------------- for sheet in ['silkscreen','assembly']: try: refdef_dict = component[sheet].get('refdef') or {} except: refdef_dict = {} if refdef_dict.get('show') != False: layer = refdef_dict.get('layer') or 'top' # Rotate the refdef; if unspecified the rotation is the same as # the rotation of the component refdef_dict['rotate'] = refdef_dict.get('rotate') or 0 # Sometimes you'd want to keep all refdefs at the same angle # and not rotated with the component if refdef_dict.get('rotate-with-component') != False: refdef_dict['rotate'] += self._rotate refdef_dict['rotate-point'] = utils.toPoint(refdef_dict.get('rotate-point')) or self._rotate_point refdef_dict['location'] = refdef_dict.get('location') or [0, 0] refdef_dict['type'] = 'text' refdef_dict['value'] = refdef_dict.get('value') or refdef refdef_dict['font-family'] = (refdef_dict.get('font-family') or config.stl['layout'][sheet]['refdef'].get('font-family') or config.stl['defaults']['font-family']) refdef_dict['font-size'] = (refdef_dict.get('font-size') or config.stl['layout'][sheet]['refdef'].get('font-size') or "2mm") refdef_shape = Shape(refdef_dict) refdef_shape.is_refdef = True refdef_shape.rotateLocation(self._rotate, self._rotate_point) style = Style(refdef_dict, sheet, 'refdef') refdef_shape.setStyle(style) # Add the refdef to the silkscreen/assembly list. It's # important that this is added at the very end since the # placement process assumes the refdef is last footprint_shapes[sheet][layer].append(refdef_shape) #------------------------------------------------------ # Invert 'top' and 'bottom' if layer is on the 'bottom' #------------------------------------------------------ if self._layer == 'bottom': layers = utils.getSurfaceLayers() layers_reversed = reversed(utils.getSurfaceLayers()) for sheet in ['copper', 'soldermask', 'solderpaste', 'silkscreen', 'assembly']: sheet_dict = footprint_shapes[sheet] # TODO: this nasty hack won't work for more than two # layers, so when 2+ are supported this needs to be # revisited for i in range(0,len(layers)-1): sheet_dict['temp'] = sheet_dict.pop(layers[i]) sheet_dict[layers[i]] = sheet_dict.pop(layers[len(layers)-1-i]) sheet_dict[layers[len(layers)-1-i]] = sheet_dict.pop('temp') self._footprint_shapes = footprint_shapes
def __init__(self, refdef, component): """ """ self._refdef = refdef self._layer = component.get('layer') or 'top' self._rotate = component.get('rotate') or 0 if self._layer=='bottom': self._rotate *= -1 self._rotate_point = utils.toPoint(component.get('rotate-point') or [0, 0]) self._scale = component.get('scale') or 1 self._location = component.get('location') or [0, 0] # Get footprint definition and shapes try: self._footprint_name = component['footprint'] except: msg.error("Cannot find a 'footprint' name for refdef %s." % refdef) filename = self._footprint_name + '.json' paths = [os.path.join(config.cfg['base-dir'], config.cfg['locations']['shapes'], filename), os.path.join(config.cfg['base-dir'], config.cfg['locations']['components'], filename)] footprint_dict = None for path in paths: if os.path.isfile(path): footprint_dict = utils.dictFromJsonFile(path) break if footprint_dict == None: fname_list = "" for path in paths: fname_list += " %s" % path msg.error("Couldn't find shape file. Looked for it here:\n%s" % (fname_list)) footprint = Footprint(footprint_dict) footprint_shapes = footprint.getShapes() #------------------------------------------------ # Apply component-specific modifiers to footprint #------------------------------------------------ for sheet in ['conductor', 'soldermask', 'solderpaste', 'pours', 'silkscreen', 'assembly', 'drills']: for layer in config.stk['layer-names']: for shape in footprint_shapes[sheet].get(layer) or []: # In order to apply the rotation we need to adust the location shape.rotateLocation(self._rotate, self._rotate_point) shape.transformPath(scale=self._scale, rotate=self._rotate, rotate_point=self._rotate_point, mirror=shape.getMirrorPlacement(), add=True) #-------------------------------------------------------------- # Remove silkscreen and assembly shapes if instructed #-------------------------------------------------------------- # If the 'show' flag is 'false then remove these items from the # shapes dictionary #-------------------------------------------------------------- for sheet in ['silkscreen','assembly']: try: shapes_dict = component[sheet].get('shapes') or {} except: shapes_dict = {} # If the setting is to not show silkscreen shapes for the # component, delete the shapes from the shapes' dictionary if shapes_dict.get('show') == False: for pcb_layer in utils.getSurfaceLayers(): footprint_shapes[sheet][pcb_layer] = [] #---------------------------------------------------------- # Add silkscreen and assembly reference designator (refdef) #---------------------------------------------------------- for sheet in ['silkscreen','assembly']: try: refdef_dict = component[sheet].get('refdef') or {} except: refdef_dict = {} if refdef_dict.get('show') != False: layer = refdef_dict.get('layer') or 'top' # Rotate the refdef; if unspecified the rotation is the same as # the rotation of the component refdef_dict['rotate'] = refdef_dict.get('rotate') or 0 # Sometimes you'd want to keep all refdefs at the same angle # and not rotated with the component if refdef_dict.get('rotate-with-component') != False: refdef_dict['rotate'] += self._rotate refdef_dict['rotate-point'] = utils.toPoint(refdef_dict.get('rotate-point')) or self._rotate_point refdef_dict['location'] = refdef_dict.get('location') or [0, 0] refdef_dict['type'] = 'text' refdef_dict['value'] = refdef_dict.get('value') or refdef refdef_dict['font-family'] = (refdef_dict.get('font-family') or config.stl['layout'][sheet]['refdef'].get('font-family') or config.stl['defaults']['font-family']) refdef_dict['font-size'] = (refdef_dict.get('font-size') or config.stl['layout'][sheet]['refdef'].get('font-size') or "2mm") refdef_shape = Shape(refdef_dict) refdef_shape.is_refdef = True refdef_shape.rotateLocation(self._rotate, self._rotate_point) style = Style(refdef_dict, sheet, 'refdef') refdef_shape.setStyle(style) # Add the refdef to the silkscreen/assembly list. It's # important that this is added at the very end since the # placement process assumes the refdef is last try: footprint_shapes[sheet][layer] except: footprint_shapes[sheet][layer] = [] footprint_shapes[sheet][layer].append(refdef_shape) #------------------------------------------------------ # Invert layers #------------------------------------------------------ # If the placement is on the bottom of the baord then we need # to invert the placement of all components. This affects the # surface laters but also internal layers if self._layer == 'bottom': layers = config.stk['layer-names'] for sheet in ['conductor', 'pours', 'soldermask', 'solderpaste', 'silkscreen', 'assembly']: sheet_dict = footprint_shapes[sheet] sheet_dict_new = {} for i, pcb_layer in enumerate(layers): try: sheet_dict_new[layers[len(layers)-i-1]] = copy.copy(sheet_dict[pcb_layer]) except: continue footprint_shapes[sheet] = copy.copy(sheet_dict_new) self._footprint_shapes = footprint_shapes
def __init__(self, shape): gerber_lp = None mirror = False # Invert rotation so it's clock-wise. Inkscape is counter-clockwise and # it's unclear to ma what's the "right" direction. clockwise makse more # sense to me. This should be the only place to make the change. self._inv_rotate = -1 self._rotate = shape.get('rotate') or 0 self._rotate *= self._inv_rotate self._rotate_point = shape.get('rotate-point') or Point(0,0) self._scale = shape.get('scale') or 1 self._pour_buffer = shape.get('buffer-to-pour') try: self._type = shape.get('type') except: msg.error("Shapes must have a 'type' defined") if self._type in ['rect', 'rectangle']: path = svg.width_and_height_to_path(shape['width'], shape['height'], shape.get('radii')) elif self._type in ['circ', 'circle', 'round']: path = svg.circle_diameter_to_path(shape['diameter']) elif self._type in ['drill']: self._diameter = shape['diameter'] path = svg.drillPath(self._diameter) elif self._type in ['text', 'string']: try: self._text = shape['value'] except KeyError: msg.error("Could not find the text to display. The text to be displayed should be defined in the 'value' field, for example, 'value': 'DEADBEEF\\nhar\\nhar'") # Get the fon'ts name font = shape.get('font-family') or config.stl['layout']['defaults']['font-family'] # Search for the font SVG in these paths paths = [os.path.join(config.cfg['base-dir']), os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')] font_filename = "%s.svg" % font filenames = '' font_data = None for path in paths: filename = os.path.join(path, config.cfg['locations']['fonts'], font_filename) filenames += " %s \n" % filename if os.path.isfile(filename): font_data = et.ElementTree(file=filename) break if font_data == None: msg.error("Couldn't find style file %s. Looked for it here:\n%s" % (font_filename, filenames)) try: fs = shape['font-size'] except: msg.error("A 'font-size' attribute must be specified for a 'text' type") ls = shape.get('letter-spacing') or '0mm' lh = shape.get('line-height') or fs font_size, letter_spacing, line_height = utils.getTextParams(fs, ls, lh) # With the units-per-em we can figure out the scale factor # to use for the desired font size units_per_em = float(font_data.find("//n:font-face", namespaces={'n': config.cfg['namespace']['svg']}).get('units-per-em')) or 1000 self._scale = font_size/units_per_em # Get the path to use. This returns the path without # scaling, which will be applied later, in the same manner # as to the other shape types path, gerber_lp = utils.textToPath(font_data, self._text, letter_spacing, line_height, self._scale) # In the case where the text is an outline/stroke instead # of a fill we get rid of the gerber_lp if shape.get('style') == 'stroke': gerber_lp = None self._rotate += 180 elif self._type in ['path']: path = shape.get('value') else: msg.error("'%s' is not a recongnised shape type" % self._type) self._path = SvgPath(path, gerber_lp) self._path.transform(self._scale, self._rotate, self._rotate_point, True) self._gerber_lp = (shape.get('gerber-lp') or shape.get('gerber_lp') or gerber_lp or None) self._location = utils.toPoint(shape.get('location', [0, 0]))
def _placeDrillIndex(self): """ Adds a drill index """ # Get the drills sheet / SVG layer drill_layer = self._layers['drills']['layer'] ns = {'pcbmode':config.cfg['ns']['pcbmode'], 'svg':config.cfg['ns']['svg']} drills = drill_layer.findall(".//*[@pcbmode:diameter]", namespaces=ns) drills_dict = {} longest_text = 0 largest_drill = 0 drill_count = 0 for drill in drills: diameter = drill.get('{'+config.cfg['ns']['pcbmode']+'}diameter') diameter = round(float(diameter), 2) if diameter not in drills_dict: drills_dict[diameter] = 1 else: drills_dict[diameter] += 1 if diameter > largest_drill: largest_drill = diameter drill_count += 1 if len(str(diameter)) > longest_text: longest_text = len(str(diameter)) # Get location, or generate one try: location = config.brd['drill-index']['location'] except: # If not location is specified, put the drill index at the # bottom left of the board. The 'gap' defines the extra # spcae between the top of the largest drill and the # board's edge gap = 2 location = [-self._width/2, -(self._height/2+gap)] location = utils.toPoint(location) # Create group for placing index transform = "translate(%s,%s)" % (location.x, config.cfg['invert-y']*location.y) group = et.SubElement(drill_layer, 'g', transform=transform) group.set('{'+config.cfg['ns']['pcbmode']+'}type', 'drill-index') text_style_dict = config.stl['layout']['drill-index'].get('text') text_style = utils.dictToStyleText(text_style_dict) count_style_dict = config.stl['layout']['drill-index'].get('count-text') count_style = utils.dictToStyleText(count_style_dict) count_style_dict['font-size'] /= 2 drill_size_style = utils.dictToStyleText(count_style_dict) if drill_count == 0: text = 'No drills' elif drill_count == 1: text = '1 drill: ' else: text = '%s drills: ' % drill_count t = et.SubElement(group, 'text', x=str(0), y=str(0), style=text_style) t.text = text # "new line" location.y = -(largest_drill/2 + 1.5) # TODO: this hack'ish thing for aligning the text isn't going # to work when the font is changed in the stylesheet if float(longest_text*0.5) > largest_drill: location.x = longest_text*0.3 else: location.x = largest_drill/2 gap = 2 for diameter in reversed(sorted(drills_dict)): path = svg.drillPath(diameter) transform = "translate(%s,%s)" % (location.x, config.cfg['invert-y']*location.y) element = et.SubElement(group, 'path', d=path, transform=transform) element.set("fill-rule", "evenodd") t = et.SubElement(group, 'text', x=str(location.x), y=str(-location.y), dy="%s" % (config.cfg['invert-y']*0.25), style=count_style) t.text = str(drills_dict[diameter]) t = et.SubElement(group, 'text', x=str(location.x), y=str(-location.y), dy="%s" % (config.cfg['invert-y']*-0.5), style=drill_size_style) t.text = "%s mm" % diameter location.x += max(diameter, 2.5)