#usage "Teardrops - v1.05 (02/17/12)\n"
"
"
"This ULP allows creation of teardrop-shaped connections from a board's vias/pads to their attached wire segments. "
"Making these connections teardrop shaped enhances manufacturability and reduces board failure by ensuring "
"connectivity between the segment and via/pad in cases where the via hole is not accurately drilled and "
"would otherwise sever the segment."
"
"
"Original Author: Tad Artis (E3Eagle_removethis@E3Switch.com)
"
"Modifications: Bob Starr (rtzaudio@comcast.net)
"
"Modification: layer selectable (alf@cadsoft.de)
"
""
// THIS PROGRAM IS PROVIDED AS IS AND WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED
string HelpText = usage +
"
Implementation
"
"This ULP is a variant based on via_teardrops1.ulp by Tad Ardis originally. This version is designed to support pads as well. "
"An attempt has been made to implement teardrops in this ULP while maintaining DRC validity. "
"Teardrops are implemented as 'two' added wire segments attaching from the original signal, at a short radius from the via, to two tangential "
"points at the via's edge. These two attachments form a small triangle and then are further attached to the via's center in order to "
"avoid ratsnest dangling segment errors. The user can specify the radius from the via at which the teardrop begins."
"
Output
"
"The output of this program is an .scr script which may be run to add vias to all signals on the board. "
"This .scr file is also properly formatted to allow it to be read into a spreadsheet program as a .csv file which will be found to contain "
"detailed information about the signal and via to which each teardrop is being added. The spreadsheet "
"columns can be sorted and copied back to an .scr to simplify adding teardrops to only certain net classes, via sizes, layers, etc. "
"The spreadsheet format may also allow teardrops to be added to an unused signal layer which would facilitate cutting "
"them from undesired portions of the board and might create a cleaner situation for future board upgrades."
"
Warnings
"
"This ULP should be used with forethought or on a copy of your board file just before plotting.
"
"It may difficult to remove teardrops once added. It may be difficult to run the teardrop ULP a second time on a board which "
"already has teardrops. It may be possible to alleviate these concerns by using the spreadsheet output or a find/replace "
"on the .scr file to send the "
"teardrops to unused signal layers which are then included when the origin signal layer is plotted.
"
"One should be aware that there are small hexogonal points at the ends of the added teardrop segments which protrude beyond "
"the tangent of the via. In most cases these protrusions are tiny and won't present any design rule violations."
"
Limitations
"
"The current software operates using mm units for .scr and only accepts units in mils from the user and in generated spreadsheet values."
"
Copyright
"
"Enhancements to the ULP are welcome without the necessity of contacting the author."
"
"
"This program is provided AS IS and without warranty of any kind, expressed or implied."
"This program may be freely modified and distributed."
"
"
"
"
"All text in red shows differences with the previous version of this program."
"
"
;
#require 5.1200
string Release = "1.06";
string ReleaseDate = "April 04, 2012"; // alf@cadsoft.de
string HistoryText1 =
""
"This program was based on the original via_teardrops1.ulp work by Tad Artis (E3Eagle_removethis@E3Switch.com).
"
"An attempt has been made to add usable support for teardropping pads as well in this adaptation."
"
"
"The following is a history of this program (most recent first)."
"
"
"Version 1.0"
"
"
"- Initial version. Released on January 5, 2008.
"
"
"
;
string HistoryText2 =
""
"The following is a history of this program (most recent first)."
"
"
"Version History"
"
"
"- v1.00 - Initial release version.
"
"
- v1.01 - Added teardropping of pads support.
"
"
- v1.02 - Improved pad teardrop support.
"
"
- v1.03 - Teardropping of square pad types added support.
"
"
- v1.04 - Added fixes for Eagle v6.0 (Walter Mueller)
" //[MUEWA104]
"
- v1.05 - Modified pad teardrops to fill better
" //[RES105]
"
- v1.06 - Modified selectable layer
" //alfcadsoft.de
"
"
;
int User_Pads = 0;
int User_Vias = 1;
real User_Tear_Radius = 1.5;
real User_Ignore_Width = 25;
real User_Ignore_sdratio = 1.5;
real uiw_internal;
int via_count;
int aring;
real radius, radius_sq, tangent_radius; //[MUEWA104]
int in1,in2;
int x_cross, y_cross;
real x1, x2, y1, y2; //[MUEWA104]
real t; //[MUEWA104]
int tstep; //[MUEWA104]
int changes = 0;
real xstep, ystep, xc, yc;
real astart,aend,sin_of_astart,cos_of_astart,wstep,w;
real tearseg_delta_w, tearseg_w, tearseg_len;
int tearseg_xend, tearseg_yend;
string vcount, fname, params;
numeric string LayerName[];
int LayerNum[];
int LayerCount = 0;
int LayerSel = 0;
numeric string TargetLayer[]; // 2012-04-04
int tCount = 0;
int LayActiv[];
int IsLayer[];
int TeardropLayer[];
// Output in dual-use .scr and spreadsheet .csv format
void output_hdr(void) {
printf("# SCR Command, Signal Name, Signal Class, Layer, Width, Via Drill, Via Layer Diameter, Via X, Via Y, Ratio segment_width/Via_drill_size\n");
return;
}
void output_via_wire(int layer, int width, int x1, int y1, int x2, int y2, UL_VIA V, UL_WIRE W, UL_SIGNAL S) {
if (!LayActiv[layer]) return; // 201-04-04
int lnum = TeardropLayer[layer];
printf("LAYER %d; WIRE '%s' %.8f (%.9f %.9f) (%.9f %.9f);\n", // #129, %s, %s, %d, %.1f, %.1f, %.1f, %.3f, %.3f, %.2f\n", // alf@cadsoft.de
lnum, S.name, u2mm(width), u2mm(x1), u2mm(y1), u2mm(x2), u2mm(y2)
//S.name, S.class.name, layer, u2mil(width), u2mil(V.drill), u2mil(V.diameter[layer]), u2mil(V.x), u2mil(V.y), real(width)/V.drill
);
return;
}
void output_pad_wire(int layer, int width, int x1, int y1, int x2, int y2, UL_PAD P, UL_WIRE W, UL_SIGNAL S) {
if (!LayActiv[layer]) return; // 201-04-04
int lnum = TeardropLayer[layer];
printf("LAYER %d; WIRE '%s' %.8f (%.9f %.9f) (%.9f %.9f);\n", // #139, %s, %s, %d, %.1f, %.1f, %.1f, %.3f, %.3f, %.2f\n", // alf@cadsoft.de
lnum, S.name, u2mm(width), u2mm(x1), u2mm(y1), u2mm(x2), u2mm(y2)
//P.name, P.name, layer, u2mil(width), u2mil(P.drill), u2mil(P.diameter[layer]), u2mil(P.x), u2mil(P.y), real(width)/P.drill
);
return;
}
void do_pads(UL_SIGNAL S) {
int shape;
S.contactrefs(C) {
if (C.contact.pad) {
via_count++;
sprintf(vcount, "Processing Pad #%d -- Signal %s", via_count, C.contact.pad.name);
status(vcount);
// find wires starting within teardrop-apex radius of the via and ending outside it.
//radius = C.contact.pad.drill * (User_Tear_Radius - 0.25); //[RES105]
radius = C.contact.pad.drill * (User_Tear_Radius - 0.5);
radius_sq = radius * radius;
S.wires(W) {
// quick check first. See if either wire endpoint is within a square of size 2 x radius of the pad.
in1 = 0;
in2 = 0;
if ((W.width < uiw_internal) && (real(W.width)/C.contact.pad.drill < User_Ignore_sdratio)) {
if (W.layer >= 1 && W.layer <= 16) {
shape = C.contact.pad.shape[W.layer];
if (shape == PAD_SHAPE_ROUND || shape == PAD_SHAPE_OCTAGON) {
// Passed user specified wires to ignore
if (abs(W.x1-C.contact.pad.x) radius_sq) //[MUEWA104]
in1 = 0;
}
if (in2) {
if ((real(W.x2-C.contact.pad.x)*(W.x2-C.contact.pad.x)+real(W.y2-C.contact.pad.y)*(W.y2-C.contact.pad.y)) > radius_sq) //[MUEWA104]
in2 = 0;
}
}
}
}
}
if (in1 ^ in2) {
changes = 1;
// Calculate point where wire crosses radius. Move calculations relative to via's center.
x1 = W.x1-C.contact.pad.x;
x2 = W.x2-C.contact.pad.x;
y1 = W.y1-C.contact.pad.y;
y2 = W.y2-C.contact.pad.y;
// Make x1,y1 always the end closest to via center.
if (in2) {
// Swap start/end
t = x1;
x1 = x2;
x2 = t;
t = y1;
y1 = y2;
y2 = t;
}
// Perhaps the line and arc intersections equations could be solved directly without too much trouble,
// but didn't feel like dealing with it and the special cases.
if (!W.arc) {
// Easiest to represent the line parametrically and step up it.
tstep = max(abs(y2-y1),abs(x2-x1));
xstep = (x2-x1)/real(tstep);
ystep = (y2-y1)/real(tstep);
tstep = tstep >>1;
// We're double testing points here, but don't feel like fixing.
for (t = 0; tstep > 0; t = t+tstep) {
xc = x1+xstep*t;
yc = y1+ystep*t;
if ((xc*xc + yc*yc) > radius_sq) {
// backup a little and increase resolution
t = t-tstep;
tstep = tstep >> 1;
}
}
}
else {
// wire segment is an arc
// Get correct arc starting end angle for x1,y1
if ((x1+C.contact.pad.x) == W.arc.x1 && (y1+C.contact.pad.y) == W.arc.y1) {
astart = W.arc.angle1;
aend = W.arc.angle2;
}
else {
// Swap start/end
astart = W.arc.angle2;
aend = W.arc.angle1;
}
// wstep is the change in angle to the next point on the arc to test.
astart = PI/180 * astart;
aend = PI/180 * aend;
wstep = (aend-astart)/2; // make wstep 1/2 the difference
sin_of_astart = W.arc.radius*sin(astart);
cos_of_astart = W.arc.radius*cos(astart);
// dy = radius*(sin(astart+dw)-sin(astart))
// dx = radius*(cos(astart+dw)-cos(astart))
// w is the current angle on the arc being tested
// We're double testing points here, but don't feel like fixing.
for (w = astart+wstep; abs(wstep) > .00001; w = w+wstep) {
yc = y1+W.arc.radius*sin(w)-sin_of_astart;
xc = x1+W.arc.radius*cos(w)-cos_of_astart;
if ((xc*xc + yc*yc) > radius_sq) {
// backup a little and increase resolution
w = w-wstep;
wstep = wstep/2;
}
}
}
// Determine location of tangents of via back to wire.
// angular offset = sin (viaradius/(seg to via ctr distance))
int wmult = 1;
int width = W.width;
int aring = C.contact.pad.diameter[W.layer] - C.contact.pad.drill;
// printf("# width=%f, drill=%f, diameter=%f, aring=%f\n", u2mil(width), u2mil(C.contact.pad.drill), u2mil(C.contact.pad.diameter[W.layer]), u2mil(aring));
// [RES102] - Adjusted tangent divide ratios and added logic to determine
// if wire width is less than 10 mil or the annular ring is less than the wire width,
// double the wire width for better fill.
if ((u2mil(width) <= 10) || ((u2mil(aring) / 2.0) > u2mil(width)))
wmult = 2;
width = width * wmult;
//if (u2mil(C.contact.pad.diameter[W.layer]) >= 100)
// tangent_radius = (C.contact.pad.diameter[W.layer] - (W.width * wmult)) / 4; // [RES102] was 8
//else
// tangent_radius = (C.contact.pad.diameter[W.layer] - (W.width * wmult)) / 2; // [RES102] was 4
// [RES105]
tangent_radius = (C.contact.pad.diameter[W.layer] - (W.width * wmult)) / 2;
if (tangent_radius > 0) {
// don't bother if wire is bigger than via
tearseg_delta_w = asin(real(tangent_radius)/radius);
tearseg_len = sqrt(radius * radius - tangent_radius * tangent_radius);
if (xc == 0) {
if (yc < 0)
tearseg_w = PI*.5;
else
tearseg_w = -PI*.5;
}
else {
tearseg_w = atan (real(yc)/(xc));
}
// Get the right quadrant
if (xc > 0) tearseg_w = tearseg_w + PI;
tearseg_xend = C.contact.pad.x+xc + tearseg_len*cos(tearseg_w-tearseg_delta_w);
tearseg_yend = C.contact.pad.y+yc + tearseg_len*sin(tearseg_w-tearseg_delta_w);
// Suggest a script command.
output_pad_wire(W.layer, width, int(C.contact.pad.x+xc), int(C.contact.pad.y+yc), tearseg_xend, tearseg_yend, C.contact.pad, W, S);
// Now add segment to the center of the via so ratsnest doesn't show up as dangling.
output_pad_wire(W.layer, width, int(C.contact.pad.x), int(C.contact.pad.y), tearseg_xend, tearseg_yend, C.contact.pad, W, S);
// Now the other side of the tear
tearseg_xend = C.contact.pad.x+xc + tearseg_len*cos(tearseg_w+tearseg_delta_w);
tearseg_yend = C.contact.pad.y+yc + tearseg_len*sin(tearseg_w+tearseg_delta_w);
// Suggest a script command.
output_pad_wire(W.layer, width, int(C.contact.pad.x+xc), int(C.contact.pad.y+yc), tearseg_xend, tearseg_yend, C.contact.pad, W, S);
// Now add segment to the center of the via so ratsnest doesn't show up as dangling.
output_pad_wire(W.layer, width, int(C.contact.pad.x), int(C.contact.pad.y), tearseg_xend, tearseg_yend, C.contact.pad, W, S);
}
}
}
}
}
return;
}
void do_vias(UL_SIGNAL S) {
S.vias(V) {
via_count++;
sprintf (vcount, "Processing Via #%d -- Signal %s", via_count, S.name);
status (vcount);
// find wires starting within teardrop-apex radius of the via and ending outside it.
radius = V.drill * User_Tear_Radius;
radius_sq = radius * radius;
S.wires(W) {
// quick check first. See if either wire endpoint is within a square of size 2 x radius of the via.
in1 = 0;
in2 = 0;
if ((W.width < uiw_internal) && (real(W.width)/V.drill < User_Ignore_sdratio)) {
// Passed user specified wires to ignore
if (abs(W.x1-V.x)= V.start && W.layer <= V.end) {
if (in1) {
if ((real(W.x1-V.x)*(W.x1-V.x) + real(W.y1-V.y)*(W.y1-V.y)) > radius_sq) //[MUEWA104]
in1 = 0;
}
if (in2) {
if ((real(W.x2-V.x)*(W.x2-V.x) + real(W.y2-V.y)*(W.y2-V.y)) > radius_sq) //[MUEWA104]
in2 = 0;
}
}
else {
in1 = 0;
in2 = 0;
}
}
}
if (in1 ^ in2) {
changes = 1;
// Calculate point where wire crosses radius. Move calculations relative to via's center.
x1 = W.x1-V.x;
x2 = W.x2-V.x;
y1 = W.y1-V.y;
y2 = W.y2-V.y;
// Make x1,y1 always the end closest to via center.
if (in2) {
// Swap start/end
t = x1;
x1 = x2;
x2 = t;
t = y1;
y1 = y2;
y2 = t;
}
// Perhaps the line and arc intersections equations could be solved directly without too much trouble,
// but didn't feel like dealing with it and the special cases.
if (!W.arc) {
// Easiest to represent the line parametrically and step up it.
tstep = max(abs(y2-y1),abs(x2-x1));
xstep = (x2-x1)/real(tstep);
ystep = (y2-y1)/real(tstep);
tstep = tstep >>1;
// We're double testing points here, but don't feel like fixing.
for (t = 0; tstep > 0; t = t+tstep) {
xc = x1+xstep*t;
yc = y1+ystep*t;
if ((xc*xc + yc*yc) > radius_sq) {
// backup a little and increase resolution
t = t-tstep;
tstep = tstep >> 1;
}
}
}
else {
// wire segment is an arc
// Get correct arc starting end angle for x1,y1
if ((x1+V.x) == W.arc.x1 && (y1+V.y) == W.arc.y1) {
astart = W.arc.angle1;
aend = W.arc.angle2;
}
else {
// Swap start/end
astart = W.arc.angle2;
aend = W.arc.angle1;
}
// wstep is the change in angle to the next point on the arc to test.
astart = PI/180 * astart;
aend = PI/180 * aend;
wstep = (aend-astart)/2; // make wstep 1/2 the difference
sin_of_astart = W.arc.radius*sin(astart);
cos_of_astart = W.arc.radius*cos(astart);
// dy = radius*(sin(astart+dw)-sin(astart))
// dx = radius*(cos(astart+dw)-cos(astart))
// w is the current angle on the arc being tested
// We're double testing points here, but don't feel like fixing.
for (w = astart+wstep; abs(wstep) > .00001; w = w+wstep) {
yc = y1+W.arc.radius*sin(w)-sin_of_astart;
xc = x1+W.arc.radius*cos(w)-cos_of_astart;
if ((xc*xc + yc*yc) > radius_sq) {
// backup a little and increase resolution
w = w-wstep;
wstep = wstep/2;
}
}
}
// Determine location of tangents of via back to wire.
// angular offset = sin (viaradius/(seg to via ctr distance))
tangent_radius = (V.diameter[W.layer] - W.width)/2;
if (tangent_radius > 0) {
// don't bother if wire is bigger than via
tearseg_delta_w = asin(real(tangent_radius)/radius);
tearseg_len = sqrt (radius * radius - tangent_radius * tangent_radius);
if (xc == 0) {
if (yc < 0) tearseg_w = PI*.5;
else tearseg_w = -PI*.5;
}
else {
tearseg_w = atan (real(yc)/(xc));
}
// Get the right quadrant
if (xc > 0)
tearseg_w = tearseg_w + PI;
tearseg_xend = V.x+xc + tearseg_len*cos(tearseg_w-tearseg_delta_w);
tearseg_yend = V.y+yc + tearseg_len*sin(tearseg_w-tearseg_delta_w);
// Suggest a script command.
output_via_wire (W.layer, W.width, int(V.x+xc), int(V.y+yc), tearseg_xend, tearseg_yend, V, W, S);
// Now add segment to the center of the via so ratsnest doesn't show up as dangling.
output_via_wire (W.layer, W.width, int(V.x), int(V.y), tearseg_xend, tearseg_yend, V, W, S);
// Now the other side of the tear
tearseg_xend = V.x+xc + tearseg_len*cos(tearseg_w+tearseg_delta_w);
tearseg_yend = V.y+yc + tearseg_len*sin(tearseg_w+tearseg_delta_w);
// Suggest a script command.
output_via_wire (W.layer, W.width, int(V.x+xc), int(V.y+yc), tearseg_xend, tearseg_yend, V, W, S);
// Now add segment to the center of the via so ratsnest doesn't show up as dangling.
output_via_wire (W.layer, W.width, int(V.x), int(V.y), tearseg_xend, tearseg_yend, V, W, S);
}
}
}
}
return;
}
void setlayerstatus(void) { // 2012-04-04 set layer status
string s[];
for (int n = 0; n < LayerCount; n++) {
int cnt = strsplit(s, LayerName[n], '\t');
int num = strtol(s[0]);
int target = strtol(s[1]);
IsLayer[num] = num;
TeardropLayer[num] = target;
if (s[2] == "yes" ) LayActiv[num] = 1;
else LayActiv[num] = 0;
}
return;
}
void generate_tears(void) {
setlayerstatus();
board(B) {
uiw_internal = User_Ignore_Width/u2mil(1); // Convert user mil units to internal units.
via_count = 0;
fname = filesetext(B.name, "_AddTearDrops.scr");
if (!User_Vias && !User_Pads) {
dlgMessageBox("You must check vias and/or pads option.");
return;
}
output(fname, "wtD") {
printf("# Script generated to add teardrops to board vias.\n");
printf("# Script generated with '%s', *%s* at %s.\n", filename(argv[0]), EAGLE_SIGNATURE, t2string(time(), "Uyyyy-MM-dd hh:mm:ss"));
printf("# Script values in mm and spreadsheet values in mils:\n");
printf("# This script generated with the following user parameters:\n");
printf("# Teardrop Apex Radius Factor: %.2f. Ignoring segments >= %.1f mils. Ignoring segments with width/via_hole ratio >= %.1f. ",
User_Tear_Radius, User_Ignore_Width, User_Ignore_sdratio);
printf("\n");
output_hdr();
printf("SET UNDO_LOG OFF;\n");
printf("SET WIRE_BEND 2;\n"); // straight lines for drawing our teardrop wires.
printf("GRID MM;\n");
B.signals(S) {
if (User_Vias)
do_vias(S);
if (User_Pads)
do_pads(S);
}
printf("GRID LAST;\n");
printf("SET UNDO_LOG ON;\n");
if (changes) exit ("script '" + fname + "';\n");
else dlgMessageBox("No teardrops generated with current parameters");
}
}
return;
}
string settarget(string sellayer) { // 2012-04-04
string no_change = sellayer;
string l[];
strsplit(l, sellayer, '\t');
string s[];
strsplit(s, l[0], '-');
int activ;
if (l[2] == "yes") activ = 1;
int sel = strtol(l[1]) -1;
dlgDialog("Target Layer") {
dlgLabel("New layer for " + l[0]);
dlgGroup("Layer activ") {
dlgRadioButton("&no", activ);
dlgRadioButton("&yes", activ);
}
dlgComboBox(TargetLayer, sel) {
sellayer = s[0] + "\t" + TargetLayer[sel];
if (activ) sellayer += "\t" + "yes";
else sellayer += "\t" + "no";
}
dlgHBoxLayout {
dlgPushButton("+OK") {
sellayer = s[0] + "\t" + TargetLayer[sel];
if (activ) sellayer += "\t" + "yes";
else sellayer += "\t" + "no";
dlgAccept();
}
dlgPushButton("-Cancel") {
dlgReject();
sellayer = no_change;
}
}
};
return sellayer;
}
//*****************************
string mainscrtext =
"\nStatus bar will show progress while generating teardrops.\n\n"
"Apex Radius Factor sets the distance from the apex of a teardrop to the associated via's center. "
"The Radius Factor entered is multiplied by the via's drill-hole diameter to arrive at a physical distance."
"For example, with an entered radius factor of 1.5 operating on a via with a drill size of 10mils, the apex begins "
"15 mils from that particular via's center.";
if (!board) {
dlgMessageBox("You should run this ULP from an open board design.");
exit(0);
}
dlgDialog("Teardrops for Vias") {
if (!board) {
dlgMessageBox("You should run this ULP from an open board design.");
exit(-1);
}
board(B) {
B.layers(L) {
if (L.number <= 16) { // 2012-04-04
string lname;
if (L.used) {
sprintf(lname, "%d-%s\t%d-%s\tyes", L.number, L.name, L.number, L.name);
LayerName[LayerCount] = lname;
LayerNum[LayerCount] = L.number;
++LayerCount;
}
}
sprintf(TargetLayer[tCount], "%d-%s", L.number, L.name);
tCount++;
}
}
dlgHBoxLayout {
dlgTabWidget {
dlgTabPage("Processing") {
dlgHBoxLayout {
dlgGroup("Parameters") {
dlgSpacing(8);
dlgGridLayout {
dlgCell( 1, 0) dlgLabel("Teardrop Apex Radius Factor ");
dlgCell( 1, 1) dlgRealEdit(User_Tear_Radius, 0.5, 2.5);
dlgCell( 2, 0) dlgLabel("Ignore all segments >= ");
dlgCell( 2, 1) dlgRealEdit(User_Ignore_Width, 4.0, 400.0);
dlgCell( 2, 2) dlgLabel(" mils");
dlgCell( 4, 0) dlgLabel("Ignore all segments/drill ratios >= ");
dlgCell( 4, 1) dlgRealEdit(User_Ignore_sdratio, 0.1, 99.0);
}
dlgVBoxLayout {
dlgGroup("Options") {
dlgCheckBox("&Teardrop Vias", User_Vias);
dlgCheckBox("&Teardrop Pads", User_Pads);
dlgSpacing(8);
dlgLabel("Click on layer to select :");
dlgListView("Layer Source\tTeardrop Target\tactiv", LayerName, LayerSel) {
LayerName[LayerSel] = settarget(LayerName[LayerSel]);
}
}
dlgStretch(1);
dlgSpacing(10);
dlgHBoxLayout {
dlgPushButton("Teardrop Board") generate_tears();
dlgPushButton("-Cancel") dlgReject();
}
}
}
dlgTextView(mainscrtext);
}
} // tab page
dlgTabPage("Overview") {
dlgTextView(HelpText);
}
dlgTabPage("History") {
dlgTextView(Release + " " + ReleaseDate + " " + HistoryText2);
}
}
}
};