def gerbers_to_svg(manufacturer='default'): """ Takes Gerber files as input and generates an SVG of them """ def normalise_gerber_number(gerber_number, axis, form): """ Takes a Gerber number and converts it into a float using the formatting defined in the Gerber header """ # TODO: actually support anything other than leading zeros number = gerber_number / pow(10.0, form[axis]['decimal']) return number def parsed_grammar_to_dict(parsed_grammar): """ Converts the Gerber parsing results to an SVG. """ gerber_dict = {} current_aperture = None new_shape = True for line in parsed_grammar: if line.dump(): if (line.format): if gerber_dict.get('format') is None: gerber_dict['format'] = {} tmp = gerber_dict['format'] tmp['notation'] = line['format']['notation'] tmp['zeros'] = line['format']['zeros'] tmp['x'] = {} tmp['x']['integer'] = line['format']['x']['integer'] tmp['x']['decimal'] = line['format']['x']['decimal'] tmp['y'] = {} tmp['y']['integer'] = line['format']['x']['integer'] tmp['y']['decimal'] = line['format']['x']['decimal'] elif (line.units): gerber_dict['units'] = line['units']['units'] elif (line.aperture_definition): tmp = {} if line['aperture_definition']['type'] == 'circle': tmp['type'] = 'circle' tmp['diameter'] = line['aperture_definition'][ 'diameter'] tmp['number'] = line['aperture_definition']['number'] elif line['aperture_definition']['type'] == 'rect': tmp['type'] = 'rect' tmp['width'] = line['aperture_definition']['width'] tmp['height'] = line['aperture_definition']['height'] tmp['number'] = line['aperture_definition']['number'] else: print "ERROR: cannot recognise aperture definition type" if gerber_dict.get('aperture-definitions') is None: gerber_dict['aperture-definitions'] = [] gerber_dict['aperture-definitions'].append(tmp) elif line.polarity_change: if gerber_dict.get('features') is None: gerber_dict['features'] = [] polarity = line['polarity_change']['polarity'] polarity_dict = {} polarity_dict['polarity'] = polarity polarity_dict['shapes'] = [] gerber_dict['features'].append(polarity_dict) elif line.aperture_change: tmp = {} tmp['type'] = 'aperture-change' tmp['number'] = line.aperture_change['number'] #if len(gerber_dict['features'][-1]['shapes'] == 0): gerber_dict['features'][-1]['shapes'].append(tmp) #else: # gerber_dict['features'][-1]['shapes'].append(tmp) tmp = {} tmp['type'] = 'stroke' tmp['segments'] = [] gerber_dict['features'][-1]['shapes'].append(tmp) elif line.start_closed_shape: tmp = {} tmp['type'] = 'fill' tmp['segments'] = [] gerber_dict['features'][-1]['shapes'].append(tmp) elif line.move or line.draw or line.flash: # TODO: hack alert! (Got to get shit done, you know? Don't judge me!) if line.move: command_name = 'move' item = line.move if line.draw: command_name = 'draw' item = line.draw if line.flash: command_name = 'flash' item = line.flash point = Point( normalise_gerber_number(item['x'], 'x', gerber_dict['format']), normalise_gerber_number(item['y'], 'y', gerber_dict['format'])) tmp = {} tmp['type'] = command_name tmp['coord'] = point gerber_dict['features'][-1]['shapes'][-1][ 'segments'].append(tmp) elif line.end_closed_shape: new_shape = True return gerber_dict def create_gerber_svg_data(gerber_data): """ Returns an SVG element of the input Gerber data """ gerber_data_parsed = gerber_grammar.parseString(gerber_data) gerber_data_dict = parsed_grammar_to_dict(gerber_data_parsed) gerber_data_svg = svg.generate_svg_from_gerber_dict(gerber_data_dict) return gerber_data_svg # get the board's shape / outline board_shape_gerber_lp = None shape = config.brd['board_outline']['shape'] board_shape_type = shape.get('type') if board_shape_type in ['rect', 'rectangle']: offset = utils.to_Point(shape.get('offset') or [0, 0]) board_shape_path = svg.rect_to_path(shape) elif board_shape_type == 'path': board_shape_path = shape.get('value') board_shape_gerber_lp = shape.get('gerber_lp') if board_shape_path is None: print "ERROR: couldn't find a path under key 'value' for board outline" else: print "ERROR: unrecognised board shape type: %s. Possible options are 'rect' or 'path'" % board_shape_type # convert path to relative board_shape_path_relative = svg.absolute_to_relative_path(board_shape_path) # this will return a path having an origin at the center of the shape # defined by the path board_width, board_height, board_outline = svg.transform_path( board_shape_path_relative, True) display_width = board_width display_height = board_height #transform = 'translate(' + str(round((board_width)/2, SD)) + ' ' + str(round((board_height)/2, SD)) + ')' sig_dig = config.cfg['significant-digits'] #transform = 'translate(%s %s)' % (round(board_width/2, sig_dig), # round(board_height/2, sig_dig)) # extra buffer for display frame display_frame_buffer = config.cfg.get('display-frame-buffer') or 1.0 gerber = et.Element( 'svg', width=str(display_width) + config.brd['config']['units'], height=str(display_height) + config.brd['config']['units'], viewBox=str(-display_frame_buffer / 2) + ' ' + str(-display_frame_buffer / 2) + ' ' + str(board_width + display_frame_buffer) + ' ' + str(board_height + display_frame_buffer), version='1.1', nsmap=cfg['namespace'], fill='black') doc = et.ElementTree(gerber) gerber_layers = svg.create_layers_for_gerber_svg(gerber) # directory for where to expect the Gerbers within the build path # regardless of the source of the Gerbers, the PCBmodE directory # structure is assumed production_path = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], 'production') # get board information from configuration file pcbmode_version = config.cfg['version'] board_name = config.cfg['name'] board_revision = config.brd['config'].get('rev') base_name = "%s_rev_%s" % (board_name, board_revision) gerber_grammar = gerber_grammar_generator() for foil in ['outline']: #, 'documentation']: gerber_file = os.path.join(production_path, base_name + '_%s.ger' % (foil)) gerber_data = open(gerber_file, 'r').read() gerber_svg = create_gerber_svg_data(gerber_data) gerber_svg_layer = gerber_layers[foil]['layer'] gerber_svg_layer.append(gerber_svg) print foil for pcb_layer in utils.getSurfaceLayers(): for foil in ['copper', 'silkscreen', 'soldermask']: gerber_file = os.path.join( production_path, base_name + '_%s_%s.ger' % (pcb_layer, foil)) gerber_data = open(gerber_file, 'r').read() gerber_svg = create_gerber_svg_data(gerber_data) gerber_svg_layer = gerber_layers[pcb_layer][foil]['layer'] gerber_svg_layer.append(gerber_svg) print foil output_file = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], cfg['board_name'] + '_gerber.svg') try: f = open(output_file, 'wb') except IOError as e: print "I/O error({0}): {1}".format(e.errno, e.strerror) f.write(et.tostring(doc, pretty_print=True)) f.close() return
def gerbers_to_svg(manufacturer='default'): """ Takes Gerber files as input and generates an SVG of them """ def normalise_gerber_number(gerber_number, axis, form): """ Takes a Gerber number and converts it into a float using the formatting defined in the Gerber header """ # TODO: actually support anything other than leading zeros number = gerber_number / pow(10.0, form[axis]['decimal']) return number def parsed_grammar_to_dict(parsed_grammar): """ Converts the Gerber parsing results to an SVG. """ gerber_dict = {} current_aperture = None new_shape = True for line in parsed_grammar: if line.dump(): if (line.format): if gerber_dict.get('format') is None: gerber_dict['format'] = {} tmp = gerber_dict['format'] tmp['notation'] = line['format']['notation'] tmp['zeros'] = line['format']['zeros'] tmp['x'] = {} tmp['x']['integer'] = line['format']['x']['integer'] tmp['x']['decimal'] = line['format']['x']['decimal'] tmp['y'] = {} tmp['y']['integer'] = line['format']['x']['integer'] tmp['y']['decimal'] = line['format']['x']['decimal'] elif (line.units): gerber_dict['units'] = line['units']['units'] elif (line.aperture_definition): tmp = {} if line['aperture_definition']['type'] == 'circle': tmp['type'] = 'circle' tmp['diameter'] = line['aperture_definition']['diameter'] tmp['number'] = line['aperture_definition']['number'] elif line['aperture_definition']['type'] == 'rect': tmp['type'] = 'rect' tmp['width'] = line['aperture_definition']['width'] tmp['height'] = line['aperture_definition']['height'] tmp['number'] = line['aperture_definition']['number'] else: print "ERROR: cannot recognise aperture definition type" if gerber_dict.get('aperture-definitions') is None: gerber_dict['aperture-definitions'] = [] gerber_dict['aperture-definitions'].append(tmp) elif line.polarity_change: if gerber_dict.get('features') is None: gerber_dict['features'] = [] polarity = line['polarity_change']['polarity'] polarity_dict = {} polarity_dict['polarity'] = polarity polarity_dict['shapes'] = [] gerber_dict['features'].append(polarity_dict) elif line.aperture_change: tmp = {} tmp['type'] = 'aperture-change' tmp['number'] = line.aperture_change['number'] #if len(gerber_dict['features'][-1]['shapes'] == 0): gerber_dict['features'][-1]['shapes'].append(tmp) #else: # gerber_dict['features'][-1]['shapes'].append(tmp) tmp = {} tmp['type'] = 'stroke' tmp['segments'] = [] gerber_dict['features'][-1]['shapes'].append(tmp) elif line.start_closed_shape: tmp = {} tmp['type'] = 'fill' tmp['segments'] = [] gerber_dict['features'][-1]['shapes'].append(tmp) elif line.move or line.draw or line.flash: # TODO: hack alert! (Got to get shit done, you know? Don't judge me!) if line.move: command_name = 'move' item = line.move if line.draw: command_name = 'draw' item = line.draw if line.flash: command_name = 'flash' item = line.flash point = Point(normalise_gerber_number(item['x'], 'x', gerber_dict['format']), normalise_gerber_number(item['y'], 'y', gerber_dict['format'])) tmp = {} tmp['type'] = command_name tmp['coord'] = point gerber_dict['features'][-1]['shapes'][-1]['segments'].append(tmp) elif line.end_closed_shape: new_shape = True return gerber_dict def create_gerber_svg_data(gerber_data): """ Returns an SVG element of the input Gerber data """ gerber_data_parsed = gerber_grammar.parseString(gerber_data) gerber_data_dict = parsed_grammar_to_dict(gerber_data_parsed) gerber_data_svg = svg.generate_svg_from_gerber_dict(gerber_data_dict) return gerber_data_svg # get the board's shape / outline board_shape_gerber_lp = None shape = config.brd['board_outline']['shape'] board_shape_type = shape.get('type') if board_shape_type in ['rect', 'rectangle']: offset = utils.to_Point(shape.get('offset') or [0, 0]) board_shape_path = svg.rect_to_path(shape) elif board_shape_type == 'path': board_shape_path = shape.get('value') board_shape_gerber_lp = shape.get('gerber_lp') if board_shape_path is None: print "ERROR: couldn't find a path under key 'value' for board outline" else: print "ERROR: unrecognised board shape type: %s. Possible options are 'rect' or 'path'" % board_shape_type # convert path to relative board_shape_path_relative = svg.absolute_to_relative_path(board_shape_path) # this will return a path having an origin at the center of the shape # defined by the path board_width, board_height, board_outline = svg.transform_path(board_shape_path_relative, True) display_width = board_width display_height = board_height #transform = 'translate(' + str(round((board_width)/2, SD)) + ' ' + str(round((board_height)/2, SD)) + ')' sig_dig = config.cfg['significant-digits'] #transform = 'translate(%s %s)' % (round(board_width/2, sig_dig), # round(board_height/2, sig_dig)) # extra buffer for display frame display_frame_buffer = config.cfg.get('display-frame-buffer') or 1.0 gerber = et.Element('svg', width=str(display_width) + config.brd['config']['units'], height=str(display_height) + config.brd['config']['units'], viewBox=str(-display_frame_buffer/2) + ' ' + str(-display_frame_buffer/2) + ' ' + str(board_width+display_frame_buffer) + ' ' + str(board_height + display_frame_buffer), version='1.1', nsmap=cfg['namespace'], fill='black') doc = et.ElementTree(gerber) gerber_layers = svg.create_layers_for_gerber_svg(gerber) # directory for where to expect the Gerbers within the build path # regardless of the source of the Gerbers, the PCBmodE directory # structure is assumed production_path = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], 'production') # get board information from configuration file pcbmode_version = config.cfg['version'] board_name = config.cfg['name'] board_revision = config.brd['config'].get('rev') base_name = "%s_rev_%s" % (board_name, board_revision) gerber_grammar = gerber_grammar_generator() for foil in ['outline']:#, 'documentation']: gerber_file = os.path.join(production_path, base_name + '_%s.ger'% (foil)) gerber_data = open(gerber_file, 'r').read() gerber_svg = create_gerber_svg_data(gerber_data) gerber_svg_layer = gerber_layers[foil]['layer'] gerber_svg_layer.append(gerber_svg) print foil #for pcb_layer in utils.getSurfaceLayers(): for pcb_layer in config.stk['layer-names']: for foil in ['conductor', 'silkscreen', 'soldermask']: gerber_file = os.path.join(production_path, base_name + '_%s_%s.ger'% (pcb_layer, foil)) gerber_data = open(gerber_file, 'r').read() gerber_svg = create_gerber_svg_data(gerber_data) gerber_svg_layer = gerber_layers[pcb_layer][foil]['layer'] gerber_svg_layer.append(gerber_svg) print foil output_file = os.path.join(config.cfg['base-dir'], config.cfg['locations']['build'], cfg['board_name'] + '_gerber.svg') try: f = open(output_file, 'wb') except IOError as e: print "I/O error({0}): {1}".format(e.errno, e.strerror) f.write(et.tostring(doc, pretty_print=True)) f.close() return
def placeDrill(drill, layer, location, scale, soldermask_layers={}, mask_groups={}): """ Places the drilling point """ diameter = drill.get('diameter') offset = utils.to_Point(drill.get('offset') or [0, 0]) path = svg.drill_diameter_to_path(diameter) mask_path = svg.circle_diameter_to_path(diameter) sig_dig = config.cfg['significant-digits'] #translate = str(round((location.x + offset.x)*scale, sig_dig))+' '+str(round((-location.y - offset.y)*scale, sig_dig)) transform = 'translate(%s %s)' % (round((location.x + offset.x)*scale, sig_dig), round((-location.y - offset.y)*scale, sig_dig)) drill_element = et.SubElement(layer, 'path', transform=transform, d=path, id='pad_drill', diameter=str(diameter)) pour_buffer = 1.0 try: pour_buffer = board_cfg['distances']['buffer_from_pour_to'].get('drill') or 1.0 except: pass # add a mask buffer between pour and board outline if mask_groups != {}: for pcb_layer in surface_layers: mask_group = et.SubElement(mask_groups[pcb_layer], 'g', id="drill_masks") pour_mask = et.SubElement(mask_group, 'path', transform=transform, style=MASK_STYLE % str(pour_buffer*2), gerber_lp="c", d=mask_path) # place the size of the drill; id the drill element has a # "show_diameter": "no", then this can be suppressed # default to 'yes' show_diameter = drill.get('show_diameter') or 'yes' if show_diameter.lower() != 'no': text = "%s mm" % (str(diameter)) text_style = config.stl['layout']['drills'].get('text') or None if text_style is not None: text_style['font-size'] = str(diameter/10.0)+'px' text_style = utils.dict_to_style(text_style) t = et.SubElement(layer, 'text', x=str(location.x), # TODO: get rid of this hack y=str(-location.y-(diameter/4)), style=text_style) t.text = text # place soldermask unless specified otherwise # default is 'yes' add_soldermask = drill.get('add_soldermask') or 'yes' style = utils.dict_to_style(config.stl['layout']['soldermask'].get('fill')) possible_answers = ['yes', 'top', 'top only', 'bottom', 'bottom only', 'top and bottom'] if (add_soldermask.lower() in possible_answers) and (soldermask_layers != {}): # TODO: get this into a configuration parameter drill_soldermask_scale_factors = drill.get('soldermask_scale_factors') or {'top':1.2, 'bottom':1.2} path_top = svg.circle_diameter_to_path(diameter * drill_soldermask_scale_factors['top']) path_bottom = svg.circle_diameter_to_path(diameter * drill_soldermask_scale_factors['bottom']) if add_soldermask.lower() == 'yes' or add_soldermask.lower() == 'top and bottom': drill_element = et.SubElement(soldermask_layers['top'], 'path', transform=transform, style=style, d=path_top) drill_element = et.SubElement(soldermask_layers['bottom'], 'path', transform=transform, style=style, d=path_bottom) elif add_soldermask.lower() == 'top only' or add_soldermask.lower() == 'top': drill_element = et.SubElement(soldermask_layers['top'], 'path', transform=transform, style=style, d=path_top) elif add_soldermask.lower() == 'bottom only' or add_soldermask.lower() == 'bottom': drill_element = et.SubElement(soldermask_layers['bottom'], 'path', transform=transform, style=style, d=path_bottom) else: print "ERROR: unrecognised drills soldermask option" return
def placeDrill(drill, layer, location, scale, soldermask_layers={}, mask_groups={}): """ Places the drilling point """ diameter = drill.get('diameter') offset = utils.to_Point(drill.get('offset') or [0, 0]) path = svg.drill_diameter_to_path(diameter) mask_path = svg.circle_diameter_to_path(diameter) sig_dig = config.cfg['significant-digits'] transform = 'translate(%s %s)' % (round( (location.x + offset.x) * scale, sig_dig), round((-location.y - offset.y) * scale, sig_dig)) drill_element = et.SubElement(layer, 'path', transform=transform, d=path, id='pad_drill', diameter=str(diameter)) pour_buffer = 1.0 try: pour_buffer = board_cfg['distances']['buffer_from_pour_to'].get( 'drill') or 1.0 except: pass # add a mask buffer between pour and board outline if mask_groups != {}: for pcb_layer in surface_layers: mask_group = et.SubElement(mask_groups[pcb_layer], 'g', id="drill_masks") pour_mask = et.SubElement(mask_group, 'path', transform=transform, style=MASK_STYLE % str(pour_buffer * 2), gerber_lp="c", d=mask_path) # place the size of the drill; id the drill element has a # "show_diameter": "no", then this can be suppressed # default to 'yes' show_diameter = drill.get('show_diameter') or 'yes' if show_diameter.lower() != 'no': text = "%s mm" % (str(diameter)) text_style = config.stl['layout']['drills'].get('text') or None if text_style is not None: text_style['font-size'] = str(diameter / 10.0) + 'px' text_style = utils.dict_to_style(text_style) t = et.SubElement( layer, 'text', x=str(location.x), # TODO: get rid of this hack y=str(-location.y - (diameter / 4)), style=text_style) t.text = text # place soldermask unless specified otherwise # default is 'yes' add_soldermask = drill.get('add_soldermask') or 'yes' style = utils.dict_to_style(config.stl['layout']['soldermask'].get('fill')) possible_answers = [ 'yes', 'top', 'top only', 'bottom', 'bottom only', 'top and bottom' ] if (add_soldermask.lower() in possible_answers) and (soldermask_layers != {}): # TODO: get this into a configuration parameter drill_soldermask_scale_factors = drill.get( 'soldermask_scale_factors') or { 'top': 1.2, 'bottom': 1.2 } path_top = svg.circle_diameter_to_path( diameter * drill_soldermask_scale_factors['top']) path_bottom = svg.circle_diameter_to_path( diameter * drill_soldermask_scale_factors['bottom']) if add_soldermask.lower() == 'yes' or add_soldermask.lower( ) == 'top and bottom': drill_element = et.SubElement(soldermask_layers['top'], 'path', transform=transform, style=style, d=path_top) drill_element = et.SubElement(soldermask_layers['bottom'], 'path', transform=transform, style=style, d=path_bottom) elif add_soldermask.lower() == 'top only' or add_soldermask.lower( ) == 'top': drill_element = et.SubElement(soldermask_layers['top'], 'path', transform=transform, style=style, d=path_top) elif add_soldermask.lower() == 'bottom only' or add_soldermask.lower( ) == 'bottom': drill_element = et.SubElement(soldermask_layers['bottom'], 'path', transform=transform, style=style, d=path_bottom) else: print "ERROR: unrecognised drills soldermask option" return
def place_text(cfg, text_element, layer, layer_name, mirror=False, additional_rotate=0): """ Places a dict text element in the specified layer """ sig_dig = cfg['pcbmode']['significant_digits'] text_list = text_element.get('value') # Check if text is a string. Text fields used to be only a single line # are not a list of lines. So if the input is a string, we need to convert # into a single item list. # TODO: check if this can be removed after all designs have text input as a list if isinstance(text_list, basestring): text_list = [text_list] font = text_element.get('font') scale = text_element.get('scale') or text_element.get('scale') or 0.0009 rotate = text_element.get('rotate') or 0 rotate += additional_rotate location = utils.to_Point(text_element.get('location') or [0, 0]) offset = utils.to_Point(text_element.get('offset') or [0, 0]) location += offset if mirror is True: location.x = -location.x add_space = text_element.get('additional_space_between_glyphs') or 0 style = text_element.get('style') gerber_lp = (text_element.get('gerber-lp') or text_element.get('gerber_lp') or '') # create a group for the entire string string_group = et.SubElement(layer, 'g') # add the right style if style is not None: if style == 'outline': style = utils.dict_to_style(cfg['layout_style'][layer_name].get('outline')) elif style == 'fill': style = utils.dict_to_style(cfg['layout_style'][layer_name].get('fill')) else: print "ERROR: unrecognised style '%s' for text element" % style else: # default to fill style = utils.dict_to_style(cfg['layout_style'][layer_name].get('fill')) if style is not None: string_group.set('style', style) # place the glyphs' paths line_number = 0 for text in text_list: line_number += 1 # get the slyphs for the text paths, text_width, text_height = glyphs.get_glyphs(cfg, text, font, location, scale, line_number, # line number rotate, add_space) origin = utils.to_Point(paths[0]['location']) for path in paths: # TODO: hack? place = utils.to_Point(path['location']) d = place - origin d.rotate(rotate, Point()) place = origin + d if mirror is True: path['d'] = svg.mirror_path_over_axis(path['d'], 'x', 0) place.x = -place.x transform = 'translate(%s %s)' % (round(place.x, sig_dig), round(-place.y, sig_dig)) # add glyph subelement = et.SubElement(string_group, 'path', transform=transform, d=path['d'], letter=path['symbol'], gerber_lp=path['gerber-lp']) return