Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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