PCB Footprint Creation using Perl

Overview

I have created a Perl module to aide in the creation of PCB footprints. The tarball contains the Perl module, documentation and two example programs.

Creating Header Connectors

The example program below creates some Molex header connectors. The dimension data for the connectors is in the __DATA__ section.
#!/usr/bin/perl

# Copyright (C) 2007 John C. Luciani Jr.

# This program may be distributed or modified under the terms of
# version 0.2 of the No-Fee Software License published by
# John C. Luciani Jr.

# Creates the PCB elements for Molex 8624 header connectors

use strict;
use warnings;

use Pcb_9;

my $Pcb = Pcb_9 -> new(debug => 1); 

my @Fields = qw(circuits body_length pin_row_length);

my @Def; # definitions that are common to all components

while () {
    s/\#.*//; # Remove comments
    s/^\s*//; # Remove leading spaces
    s/\s*$//; # Revove trailing spaces
    next unless length; # Skip empty lines

    # Lines that contain an '=' are global definitions.

    push(@Def, $1, $2), next if /(\S+)\s*=\s*(\S.*)/;

    my @values = split /\s*\|\s*/;

    # hash for each footprint

    my %f = ( @Def,
	      map { $_ => shift(@values) } @Fields);

    $Pcb -> element_begin(description => 'TH',
			  output_file => 
                             "tmp/" . &package_name($f{circuits}, $f{pin_rows}),
			  dim   => 'mils',
			  pin_one_square => 1);

    my $pin_num = 1;
    my $pins_per_row = $f{circuits} / 2;

    # lower left corner is pin one

    my $x = -$f{pin_spacing} * ($pins_per_row - 1) / 2;
    my $y =  $f{row_spacing} / 2;

    # These header connectors consist of two rows of pins.  With pin
    # one in the lower left corner we will place pins from left to
    # right until half the pins are placed. At the halfway point we
    # will shift to the top row and place pins from right to left.

    while ($pin_num <= $f{circuits}) {
	$Pcb -> element_add_pin(x => $x, y => $y,
				thickness  => 66,
				drill_hole => 46,
				mask       => 10,
				clearance  => 10,
				pin_number => $pin_num);

	# If this is the last pin in the row then
	# update the y value otherwise update the x 
	# value. If we are past the halfway point move
	# left (-) instead of right (+).

	$y *= -1;
	$x += $f{pin_spacing} if $y > 0;
	$pin_num++;
    }

    # The length of the silkscreen body rectangle needs to be
    # increased by 6mils to conform to get a 10mil separation. For
    # this simple example example 6 mils is added to the
    # length. Ideally a boundingbox that encloses all copper would be
    # calculated and the outline would be placed outside the box with
    # a 10mil gap.

    $Pcb -> element_add_rectangle(width => $f{body_width},
				  length=> $f{body_length} + 6,
				  x => 0,
				  y => 0);


    $Pcb -> element_set_text_xy(x => -$f{body_length}/2,
				y => -$f{body_width}/2 - 20);


    $Pcb -> element_output();
}

sub package_name ($$) { 
    my ($circuits, $rows) = @_;
    sprintf("CON_HDR-254P-%iC-%iR-%iN__Molex_8624-Series", 
	    $circuits/$rows, 
	    $rows, 
	    $circuits);
}

__DATA__

body_width = 200
pin_spacing = 100
row_spacing = 100
pin_diameter = 35
pin_rows = 2

# circuits | body_length | pin_row_length

4  | 190 | 100 
6  | 290 | 200 
8  | 390 | 300 
10 | 490 | 400 
12 | 590 | 500 
14 | 690 | 600 
16 | 790 | 700 
18 | 890 | 800 
20 | 990 | 900 
22 | 1090 | 1000 
24 | 1190| 1100 
26 | 1290| 1200 
28 | 1390| 1300

30 | 1490 | 1400 
32 | 1590 | 1500 
34 | 1690 | 1600 
36 | 1790 | 1700 
38 | 1890 | 1800 
40 | 1990 | 1900
42 | 2090 | 2000
44 | 2190 | 2100 
46 | 2290 | 2200
48 | 2390 | 2300 
50 | 2490 | 2400 
52 | 2590 | 2500 
54 | 2690 | 2600

56 | 2790 | 2700 
58 | 2890 | 2800 
60 | 2990 | 2900 
62 | 3090 | 3000 
64 | 3190 | 3100 
66 | 3290 | 3200 
68 | 3390 | 3300 
70 | 3490 | 3400 
72 | 3590 | 3500 
74 | 3690 | 3600 
76 | 3790 | 3700 
78 | 3890 | 3800 
80 | 3990 | 3900


    

Another way to Create Header Connectors

This program creates header connectors with different pin numbering options. The three different pin numbering schemes are --- header, dip and power.
Pin Numbering SchemeEight Pin Connector Example
header
24 68
13 57
dip
87 65
12 34
power
56 78
12 34
The dimension data for the connectors is in the __DATA__ section.
#!/usr/bin/perl

# Copyright (C) 2007 John C. Luciani Jr.

# This program may be distributed or modified under the terms of
# version 0.2 of the No-Fee Software License published by
# John C. Luciani Jr.

# Creates the PCB elements for Molex 8624 header connectors

use strict;
use warnings;

use Pcb_9;

my $Pcb = Pcb_9 -> new(debug => 0);

my @Fields = qw(circuits body_length pin_row_length);

my @Def; # definitions that are common to all components

while () {
    s/\#.*//; # Remove comments
    s/^\s*//; # Remove leading spaces
    s/\s*$//; # Revove trailing spaces
    next unless length; # Skip empty lines

    # Lines that contain an '=' are global definitions.

    push(@Def, $1, $2), next if /(\S+)\s*=\s*(\S.*)/;

    my @values = split /\s*\|\s*/;

    # hash for each footprint

    my %f = ( @Def,
	      map { $_ => shift(@values) } @Fields);

    $Pcb -> element_begin(description => 'TH',
			  output_file => 
                            "tmp/" . &package_name($f{circuits}, $f{pin_rows}),
			  dim   => 'mils',
			  pin_one_square => 1);

    my $pin_num = 1;
    my $pins_per_row = $f{circuits} / 2;

    # lower left corner is pin one

    my $x0 = -$f{pin_spacing} * ($pins_per_row - 1) / 2;
    my $y0 =  $f{row_spacing} / 2;

    my $x = $x0;
    my $y = $y0;

    # These header connectors consist of two rows of pins.  With pin
    # one in the lower left corner we will place pins from left to
    # right until half the pins are placed. At the halfway point we
    # will shift to the top row and place pins from right to left.

    while ($pin_num <= $f{circuits}) {
	$Pcb -> element_add_pin(x => $x, y => $y,
				thickness  => $f{pad_thickness},
				drill_hole => $f{drill_hole},
				mask       => 10,
				clearance  => 10,
				pin_number => $pin_num);

	# Header connectors usually have pins numbered from left to
	# right with odd numbers on the bottom and even numbers on the
	# top. Since this example program could be used for connectors
	# other than headers three pin-numbering options are provided.

	# header - two rows of pins. numbers increase from left to right.
	#          odd numbered pins on the bottom, even on the top.

        # dip    - two rows of pins. starting in the lower left corner
        #          numbers increase left to right along the bottom row
	#          and right to left along the top row.

	# power  - two rows of pins. numbers increase from left to right
        #          starting on the bottom row and then continue left to right
        #          along the top row.

	if ($f{pin_numbering_scheme} eq 'header') {
	    $y *= -1;
	    $x += $f{pin_spacing} if $y > 0;
	} elsif ($f{pin_numbering_scheme} eq 'dip') {
	    if ($pin_num == $pins_per_row) {
		$y -= $f{row_spacing};
	    } else {
		$x += $pin_num > $pins_per_row 
		    ? -$f{pin_spacing}
		    : $f{pin_spacing};
	    }
	} elsif ($f{pin_numbering_scheme} eq 'power') {
	    if ($pin_num == $pins_per_row) {
		$y -= $f{row_spacing};
		$x = $x0;
	    } else {
		$x += $f{pin_spacing}
	    }
	} else {
	    die "unknown pin numbering scheme |$f{pin_numbering_scheme}|";
	}
	$pin_num++;
    }

    $Pcb -> element_add_rectangle(width => $f{body_width} + $f{body_width_adj},
				  length=> $f{body_length} + $f{body_length_adj},
				  x => 0,
				  y => 0);


    $Pcb -> element_set_text_xy(x => -$f{body_length}/2,
				y => -$f{body_width}/2 - 20);


    $Pcb -> element_output();
}

sub package_name ($$) { 
    my ($circuits, $rows) = @_;
    sprintf("CON_HDR-254P-%iC-%iR-%iN__Molex_8624-Series", 
	    $circuits/$rows, 
	    $rows, 
	    $circuits);
}

__DATA__

pad_thickness = 66
drill_hole = 46
pin_numbering_scheme = header
body_width = 200
pin_spacing = 100
row_spacing = 100
pin_diameter = 35
pin_rows = 2

# These adjustments are chosen so that the extents of the silkscreen
# body rectangle are at least as large as the connector body AND there
# is at leat 10mils of space between the copper and the silkscreen.

# For this simple example program the values are hardcoded.

body_width_adj  = -4
body_length_adj =  6

# circuits | body_length | pin_row_length

4  | 190 | 100 
6  | 290 | 200 
8  | 390 | 300 
10 | 490 | 400 
12 | 590 | 500 
14 | 690 | 600 
16 | 790 | 700 
18 | 890 | 800 
20 | 990 | 900 
22 | 1090 | 1000 
24 | 1190| 1100 
26 | 1290| 1200 
28 | 1390| 1300

30 | 1490 | 1400 
32 | 1590 | 1500 
34 | 1690 | 1600 
36 | 1790 | 1700 
38 | 1890 | 1800 
40 | 1990 | 1900
42 | 2090 | 2000
44 | 2190 | 2100 
46 | 2290 | 2200
48 | 2390 | 2300 
50 | 2490 | 2400 
52 | 2590 | 2500 
54 | 2690 | 2600

56 | 2790 | 2700 
58 | 2890 | 2800 
60 | 2990 | 2900 
62 | 3090 | 3000 
64 | 3190 | 3100 
66 | 3290 | 3200 
68 | 3390 | 3300 
70 | 3490 | 3400 
72 | 3590 | 3500 
74 | 3690 | 3600 
76 | 3790 | 3700 
78 | 3890 | 3800 
80 | 3990 | 3900


    

Creating Two Terminal SMD Devices

The example program below creates some Molex header connectors. The dimension data for the connectors is in the __DATA__ section.
#!/usr/bin/perl

# Copyright (C) 2007 John C. Luciani Jr.

# This program may be distributed or modified under the terms of
# version 0.2 of the No-Fee Software License published by
# John C. Luciani Jr.

# Creates the PCB elements specified in the DATA section.  The
# footprints are for the SMD packages 0402, 0603, 0805, 1206, 1210,
# 2010, 2512, 0402, 0504, 0603, 0805, 1206, 1210, 1812, 1825

use strict;
use warnings;

use Pcb_9;

my $Pcb = Pcb_9 -> new(debug => 1);

my @Fields = qw(land_pattern_length  land_row_distance 
		land_width   	     land_length 
		land_row_centers     grid);

while () {
    s/\#.*//; # Remove comments
    s/^\s*//; # Remove leading spaces
    s/\s*$//; # Revove trailing spaces
    next unless length; # Skip empty lines
    my ($type, @values) = split /\s*\|\s*/;

    # hash for each footprint

    my %f = map { $_ => shift(@values) } @Fields;

    $Pcb -> element_begin(description => 'SMD',
			  output_file => "tmp/$type",
			  dim   => 'mm');

    my $x = -$f{land_row_centers} / 2;
    foreach my $pin_num (1..2) {
	$Pcb -> element_add_pad_rectangle(width => $f{land_width},
					  length=> $f{land_length},
					  x => $x,
					  y => 0,
					  name => 'input',
					  mask => '10mils',
					  clearance => '10mils',
					  pin_number => $pin_num);
	$x += $f{land_row_centers};
    }

    # Draw a silkscreen rectangle around the component.  A silkscreen
    # specification that all PCB vendors should be able to meet is
    # 10mil line width and 10mil spacing. The silkscreen line width
    # defaults to 10mils. To achieve the proper spacing we add 
    # 30mils (0.762mm) to the maximum extents of the copper pads 
    # (10mils on either side of the copper and 2*5 mils for the 
    # silkscreen line).

    my $length = $f{land_pattern_length} + 0.762;
    my $width  = $f{land_width} + 0.762;

    $Pcb -> element_add_rectangle(width => $width,
				  length=> $length,
				  x => 0,
				  y => 0);

    # Place the refdes slightly (0.5mm) above the upper left corner of
    # the outline rectangle.

    $Pcb -> element_set_text_xy(x => -$length/2,
				y => -$width/2 - 0.5);

    $Pcb -> element_output();

}


__DATA__

# type   package name
#  Z     overall length of land pattern
#  G     distance between land rows 
#  X     land width 
#  Y     land length
#  C     center-to-center spacing between land rows
#  Grid  number of 0.5mm by 0.5mm elements 

# type     Z        G       X         Y        C     Grid

 0402 |   2.20 |   0.40 |   0.70 |   0.90 |   1.30 | 2x6
 0504 |   2.40 |   0.40 |   1.30 |   1.00 |   1.40 | 4x6
 0603 |   2.80 |   0.60 |   1.00 |   1.10 |   1.70 | 4x6
 0805 |   3.20 |   0.60 |   1.50 |   1.30 |   1.90 | 4x8
 1206 |   4.40 |   1.20 |   1.80 |   1.60 |   2.80 | 4x10
 1210 |   4.40 |   1.20 |   2.70 |   1.60 |   2.80 | 6x10
 1812 |   5.80 |   2.00 |   3.40 |   1.90 |   3.90 | 8x12
 1825 |   5.80 |   2.00 |   6.80 |   1.90 |   3.90 | 14x12
 2010 |   6.20 |   2.60 |   2.70 |   1.80 |   4.40 | 6x14
 2512 |   7.40 |   3.80 |   3.20 |   1.80 |   5.60 | 8x16
    

Creating TQFN Devices

The example program below creates footprints for the MAXIM TQFN devices defined in Maxim 21-0140 Rev G and Maxim 21-10159 Rev A. The dimension data is contained in the __DATA__ section.
#!/usr/bin/perl

# Copyright (C) 2007 John C. Luciani Jr.

# This program may be distributed or modified under the terms of
# version 0.2 of the No-Fee Software License published by
# John C. Luciani Jr.

# Creates Maxim TQFN style packages.  

# Data is from the Maxim 21-0140 Rev G and Maxim 21-10159 Rev A
# specifications.

# The TQFN (thin quad flat no-lead) packages have solder terminations
# on four sides and a thermal pad in the center. The two denser
# packages (T3255-2 and T4055-1) require smaller pads on the corner
# terminations.

use strict;
use warnings;
use Carp;

use Pcb_9; # routines to create PCB elements (packages)

my $Pcb = Pcb_9 -> new(debug => 0);

# The specifications to generate these symbols is in the __DATA__
# section of this file. Each line can be either blank, contain a global
# definition or contain a package data record.

# Global definitions are saved in @Def.
# The field names for the package data record are in @Fields.

my @Def; # Global definitions saved as key-value pairs.
my @Fields = qw(package_code
		pin_count     
		pad_spacing   
		pad_width     
		pad_length    
		corner_pad_length
		thermal_pad_width
		thermal_pad_length);

# Read the __DATA__ section and output a PCB footprint everytime a
# package data record is read. 

while () {
    last if /^__END__$/; 
    s/\#.*//; # Remove comments
    s/^\s*//; # Remove leading spaces
    s/\s*$//; # Revove trailing spaces
    next unless length; # Skip empty lines

    # Lines that contain an '=' are global definitions.  The key (lhs)
    # and value (rhs) are pushed onto @Def. 

    push(@Def, $1, $2), next if /(\S+)\s*=\s*(\S.*)/;

    # A line with non-zero length that is not a global definition is a
    # package data record. We split the package record and create a
    # hash %p that contains key-value pairs for all of the global
    # definitions and the current record.

    my @values = split /\s*\|\s*/; 
    my %p = ( @Def,
	      map { $_ => shift(@values) } @Fields);

    # Create a simple id using the package name, package code and pin
    # count and then start a new element.

    $p{id} = join('-', map { $p{$_} } qw(package_name package_code pin_count));
    $Pcb -> element_begin(description => $p{id},
			  output_file => "tmp/$p{id}",
			  dim   => 'mm');
    print "$p{id}\n";

    # Create a few convenient specifications from data in the package
    # data record hash. The convenetions for these packages is part
    # centroid at (0,0) and pin one is in the lower left corner.

    # Corner pads on some of the parts are shorter. This condition is
    # handled by creating a new pad length and some pad center
    # offsets.

    $p{num_pads_per_side} = $p{pin_count} / 4; # leads on four sides
    $p{corner_pad_length}  = $p{pad_length} if $p{corner_pad_length} eq '';

    my $row_center = ($p{body_width_max} - $p{pad_length}) / 2;
    my $row_end    = ($p{num_pads_per_side} - 1) * $p{pad_spacing} / 2;
    my $corner_offset = ($p{pad_length} - $p{corner_pad_length}) / 2;

    # @xy contains the staring locations for a row of pads.
    # @inc contains increment values for x and y and offsets for the
    # the corner pads. for each row of pads either x or y is incremented.

    my @xy = (x => -$row_center, y => -$row_end,    
	      x => -$row_end,    y =>  $row_center, 
	      x =>  $row_center, y =>  $row_end,    
	      x =>  $row_end,    y => -$row_center);

    my @inc= (yinc =>  $p{pad_spacing}, xoffset => -$corner_offset,
	      xinc =>  $p{pad_spacing}, yoffset =>  $corner_offset,
	      yinc => -$p{pad_spacing}, xoffset =>  $corner_offset,
	      xinc => -$p{pad_spacing}, yoffset => -$corner_offset);

    # create the rows of pads
    
    &set_pin_num(1);

    while (@xy) {
	my %xy = (splice(@inc, 0, 4),
		  map { $_ => $p{$_} } qw(pad_spacing pad_width pad_length));

	&draw_row_of_pads(splice(@xy, 0, 4),
			  %xy,
			  pad_length => $p{corner_pad_length},
			  num_pads => 1);

	# no offsets for pads that aren't on the corners

	&draw_row_of_pads(%xy,
			  xoffset => 0,
			  yoffset => 0,
			  num_pads => $p{num_pads_per_side} - 2);

	&draw_row_of_pads(%xy,
			  pad_length => $p{corner_pad_length},
			  num_pads => 1);
    }

    # Add the thermal pad

    $Pcb -> element_add_pad_rectangle(x => 0,
				      y => 0,
				      length => $p{thermal_pad_length}, 
				      width  => $p{thermal_pad_width},
				      name => '',
				      pin_number => $p{pin_count} + 1);

    # add the pin one dot

    my $dot_pos = $row_center + 0.254; #$p{pad_length} / 2;

    $Pcb -> element_add_arc(x => -$dot_pos,
			    y => -$dot_pos,
			    width => $p{pad_width} / 2,
			    height=> $p{pad_width} / 2,
			    start_angle => 0,
			    delta_angle => 360,
			    thickness => 0.254); # 10 mil lines

    # draw a silksreen rectangle around the package body.

    $Pcb -> element_add_rectangle(x => 0,
				  y => 0,
				  width => $p{body_width_max} + 1,
				  length=> $p{body_length_max} + 1);

    # Set the position of the reference designator to the upper left corner

    $Pcb -> element_set_text_xy(x => -$p{body_length_max} / 2 - 1, 
				y => -$p{body_width_max}  / 2 - 1);

    # Set the centroid mark and output the element

    $Pcb -> element_output;

}

# $v{x}       current x location
# $v{y}       current y location
# $v{pin_num} current pin number

my %v;   # values for draw_row_of_pads

sub set_pin_num ($) { 
    $v{pin_num} = shift;
} 

sub draw_row_of_pads { 
    my %p = (xoffset => 0,
	     yoffset => 0,
	     @_);

    foreach (qw(pin_num x y)) {
	$v{$_} = $p{$_} if defined $p{$_};
    }

    # swap pad length and width for horizontal rows

    ($p{pad_width}, $p{pad_length}) = ($p{pad_length}, $p{pad_width})
	if defined $p{xinc};

    foreach (1..$p{num_pads}) {
	$Pcb -> element_add_pad_rectangle(x      => $v{x} + $p{xoffset},
					  y      => $v{y} + $p{yoffset},
					  width  => $p{pad_width},
					  length => $p{pad_length},
					  name   => '', 
					  pin_number => $v{pin_num}++);
	$v{x} += $p{xinc} if defined $p{xinc};
	$v{y} += $p{yinc} if defined $p{yinc};
    }
}




1;



__DATA__
body_width_min  = 4.9  # E
body_width      = 5.0  # E
body_width_max  = 5.1  # E
body_length_min = 4.9  # D
body_length     = 5.0  # D
body_length_max = 5.1  # D

component_type     = ic
package_name       = TQFN-Maxim-5x5

# Final pad_length = pad_lenth + body_width_max - body_width_min

# T1655-1   e (nom)  b, L, E2, D2 (max)
# T2055-2   e (nom)  b (max) L, E2, D2 (nom)
# T2055-5   e (nom)  b (max) L (min) E2, D2 (nom)

# T2855-1   e (nom)  b (max) L (min) E2, D2 (nom)
# T2855-2   e (nom)  b (max) L (min) E2, D2 (max)

# T3255-2   e (nom)  b (max) L (max) E2, D2 (max)
# T4055-1   e (nom)  b (min) L (nom) E2, D2 (min)


#                 e      b      L     L1      E2     D2
 T1655-1  | 16 | 0.8  | 0.35 | 0.5  |      | 3.2  | 3.2 
 T2055-2  | 20 | 0.65 | 0.35 | 0.55 |      | 3.10 | 3.10
 T2055-5  | 20 | 0.65 | 0.35 | 0.45 |      | 3.25 | 3.25

 T2855-1  | 28 | 0.50 | 0.30 | 0.45 |      | 3.25 | 3.25
 T2855-2  | 28 | 0.50 | 0.30 | 0.45 |      | 2.8  | 2.8 


 T3255-2  | 32 | 0.50 | 0.30 | 0.5  | 0.25 | 3.2  | 3.2 
 T4055-1  | 40 | 0.40 | 0.2  | 0.5  | 0.25 | 3.2  | 3.2 

# Style (adapted from the Perl Cookbook, First Edition, Recipe 12.4)

# 1. Names of functions and local variables are all lowercase.
# 2. The program's persistent variables (either file lexicals
#    or package globals) are capitalized.
# 3. Identifiers with multiple words have each of these
#    separated by an underscore to make it easier to read.
# 4. Constants are all uppercase.
# 5. If the arrow operator (->) is followed by either a
#    method name or a variable containing a method name then
#    there is a space before and after the operator.


    

Creating a 1/4 Watt Resistor

The example program below creates the footprint for a 1/4 Watt Resistor. All of the component specification data is hard-coded in the script.
#!/usr/bin/perl

# Copyright (C) 2005 John C. Luciani Jr.

# This program may be distributed or modified under the terms of
# version 0.2 of the No-Fee Software License published by
# John C. Luciani Jr.

# Creates a 1/4 Watt resistor

# From Yageo CFR-25 Specification
#   body diameter 2.4mm ( 95mils)
#   body length   6.3mm (248mils)

use strict;
use warnings;

use Pcb_9;

my $Pcb = Pcb_9 -> new(debug => 1);

$Pcb -> element_begin(description => 'resistor',
		      output_file => 'tmp/RES_1016P-630L-240D',
		      dim   => 'mils');

# the resistor centroid is at (0,0) and the pins are placed 400 mils
# apart

my $Body_width =   95;          # y direction
my $Body_length = 248;        # x direction
my @Pins = (-200, 0, 200, 0); # x,y pin centers
my $Pin_num = 1;

while (@Pins) {
    my ($x, $y) = splice @Pins, 0, 2;
    $Pcb -> element_add_pin(x => $x, y => $y,
			    thickness  => 55,
			    drill_hole => 35,
			    mask => 10,
			    clearance => 10,
			    pin_number => $Pin_num++);
}

$Pcb -> element_add_rectangle(width => $Body_width,
			      length=> $Body_length,
			      thickness => 10,
			      x => 0,
			      y => 0);

foreach my $sign (-1, 1) {
    $Pcb -> element_add_line(x1 => $sign * $Body_length / 2,
			     y1 => 0,
			     x2 => $sign * ($Body_length / 2 + 30),
			     y2 => 0,
			     thickness => 10);
}


$Pcb -> element_set_text_xy(x => -$Body_length/2,
			    y => -$Body_width/2 - 20);


$Pcb -> element_output();

    

Tube Socket

The example program below creates a footprint for Belton VT8 series tube socket. All of the component specification data is in the DATA section of the script.
#!/usr/bin/perl

# Copyright (C) 2007 John C. Luciani Jr.

# This program may be distributed or modified under the terms of
# version 0.2 of the No-Fee Software License published by
# John C. Luciani Jr.

# Creates a footprint for a Belton VT8 series tube socket

use strict;
use warnings;
use Math::Trig;

use Pcb_9 qw(:DEFAULT PIN_MOUNTING_HOLE);

use constant LINE_THICKNESS => 0.254; # 10 mils
use constant MASK_CLEARANCE => 0.254;
use constant COPPER_CLEARANCE => 0.254;

my $Pcb = Pcb_9 -> new(debug => 0);

my @Fields; # field names(defined in the data section)
my @Def;    # definitions that are common to all components

while () {
    s/\#.*//; # Remove comments
    s/^\s*//; # Remove leading spaces
    s/\s*$//; # Revove trailing spaces
    next unless length;    # Skip empty lines
    if (s/\\\s*$//) {      # Remove the continuation backslash and 
	$_ .= ;      # append the next line to $_ then 
	redo unless eof;   # restart the loop block after the conditional
    }

    # Lines that contain an '=' are global definitions.

    @Fields = split(/\s+/, $1), next if /^\s*fields\s*=\s*(\S.*)/;
    push(@Def, $1, $2), next if /(\S+)\s*=\s*(\S.*)/;

    my @values = split /\s*\|\s*/;

    # hash for each footprint

    my %f = ( @Def, 
	      map { $_ => shift(@values) } @Fields);

    $Pcb -> element_begin(description => 'TH',
			  output_file => "tmp/" . &package_name(%f),
			  dim   => 'mm',
			  pin_one_square => 1);

    my $angle = $f{start_angle};
    my $pin_num;
    foreach (1..$f{pin_count}) {
	$pin_num = $_;
	&place_pin(radius => $f{pin_placement_diameter}/2,
		   angle => $angle,
		   pad_thickness => $f{pad_thickness},
		   drill_hole => $f{drill_hole},
		   pin_number => $pin_num);
	$angle += $f{delta_angle};
    }

    # body circle

    $Pcb -> element_add_arc(x => 0,
 			    y => 0,
 			    width => $f{body_diameter}/2,
 			    height=> $f{body_diameter}/2,
 			    start_angle => 0,
 			    delta_angle => 360,
 			    thickness => LINE_THICKNESS);

    # add the tabs and the mounting holes.

    foreach my $y (-$f{mounting_hole_spacing}/2, $f{mounting_hole_spacing}/2) {
	&add_tab(%f, x => 0, y => $y);
	$Pcb -> element_add_pin(x => 0, y => $y,
				thickness  => $f{mounting_hole_diameter},
				drill_hole => $f{mounting_hole_diameter},
				mask       => MASK_CLEARANCE, 
				clearance  => COPPER_CLEARANCE, 
				pin_number => $pin_num++,
				pin_flags  => PIN_MOUNTING_HOLE);
	$Pcb -> element_add_arc(x => 0, y => $y,
				width      => $f{mounting_hole_diameter}/2 
				                 + LINE_THICKNESS,
				height     => $f{mounting_hole_diameter}/2 
                                                 + LINE_THICKNESS,
				start_angle => 0,
				delta_angle => 360,
				thickness => LINE_THICKNESS);
    }

    $Pcb -> element_set_text_xy(x => $f{body_diameter}/4,
 				y => -$f{mounting_hole_spacing}/2);

    $Pcb -> element_output();
}

sub add_tab {
    my %v = @_;
    my $y_sign = $v{y} > 0 ? 1 : -1;
    my $tab_radius = ($v{body_length} - $v{mounting_hole_spacing}) / 2;
    my $body_radius = $v{body_diameter} / 2;
    $Pcb -> element_add_arc(x => $v{x},
			    y => $v{y},
			    width => $tab_radius,
			    height=> $tab_radius,
			    start_angle => $y_sign > 0 ? 30 : 210,
			    delta_angle => 120+2,
			    thickness => LINE_THICKNESS);

    my ($x1, $y1) = &xy_pos(angle => 30,
 			    radius => $tab_radius);
    my ($x2, $y2) = &xy_pos(angle => 30,
			    radius => $body_radius);

    foreach my $x_sign (-1, 1) { 
	$Pcb -> element_add_line(x1 => $x_sign * $x1,
				 y1 => $y_sign * ($y1-$v{mounting_hole_spacing}/2),
				 x2 => $x_sign * $x2,
				 y2 => $y_sign * $y2,
				 thickness => LINE_THICKNESS);
    }

}


sub xy_pos  { 
    my %v = @_;
    my $angle = deg2rad($v{angle});
    return(  $v{radius} * cos($angle),
	    -$v{radius} * sin($angle));
}

sub place_pin  { 
    my %v = @_;
    my ($x, $y) = &xy_pos(%v);
    $Pcb -> element_add_pin(x => $x, y => $y,
			    thickness  => $v{pad_thickness},
			    drill_hole => $v{drill_hole},
			    mask       => MASK_CLEARANCE, 
			    clearance  => COPPER_CLEARANCE, 
			    pin_number => $v{pin_number});
}


sub package_name (%) { 
    my %f = @_;
    sprintf("CON_TUBE__%s", 
	    $f{mfg_pn});
}

__DATA__

drill_hole = 1.7018   # 67 mils
pad_thickness = 2.54  # 100 mils

# the tube socket consists of a circular body with tabs.
# the body_length is tab-to-tab. the body_diameter is the diameter
# of the circular body.

# mfg_pn  
# pin_count  .............. number of pins
# pin_placement_diameter .. diameter that the pins are centered on
# start_angle ............. for pin one 
#                           (0deg = positive X-axis, angle increases ccw)
# delta_angle .............   angle between pins
# body_diameter ........... diameter of the circular body
# body_length ,,,,,........ tab-to-tab
# mounting_hole_spacing ... 
# mounting_hole_diameter .. 

fields = mfg_pn pin_count pin_placement_diameter start_angle delta_angle\ 
         body_diameter body_length mounting_hole_spacing mounting_hole_diameter

CON_TUBE__Belton_VT8-PT | 8 | 17.2 | 70 | 45 |\
   30 | 50 | 40 | 3.5