Permalink
Cannot retrieve contributors at this time
917 lines (802 sloc)
41.6 KB
| // KicadModuleToGEDA - a utility for turning kicad modules to gEDA PCB footprints | |
| // Pad.java v1.0 | |
| // Copyright (C) 2015 Erich S. Heinzle, a1039181@gmail.com | |
| // see LICENSE-gpl-v2.txt for software license | |
| // see README.txt | |
| // | |
| // This program is free software; you can redistribute it and/or | |
| // modify it under the terms of the GNU General Public License | |
| // as published by the Free Software Foundation; either version 2 | |
| // of the License, or (at your option) any later version. | |
| // | |
| // This program is distributed in the hope that it will be useful, | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| // GNU General Public License for more details. | |
| // | |
| // You should have received a copy of the GNU General Public License | |
| // along with this program; if not, write to the Free Software | |
| // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
| // | |
| // KicadModuleToGEDA Copyright (C) 2015 Erich S. Heinzle a1039181@gmail.com | |
| /** | |
| * | |
| * This class is used to store and process a pad/pin definition string from a Kicad footprint | |
| * allowing it then generate a gEDA PCB compatible footprint | |
| * Oblong hole definitions are processed sensibly, to produce a pair of joined pins, but | |
| * slots implemented this way as a pair of pins with joining pads on both sides of the board | |
| * require gerbers to be post-processed with a G85 directive to join the pins to make a slot | |
| * | |
| */ | |
| import java.util.Scanner; | |
| public class Pad extends FootprintElementArchetype | |
| { | |
| String kicadShapePadName = "not defined"; | |
| String kicadShapeNetName = "not defined yet"; | |
| long kicadShapeXsizeNm = 0; | |
| long kicadShapeYsizeNm = 0; | |
| long kicadShapeXdeltaNm = 0; // this is used to define trapezoidal pads | |
| // -ve xDelta = decrease in left edge length vs right | |
| // +ve xDelta = increase in right edge length vs left | |
| long kicadShapeYdeltaNm = 0; // this is used to define trapezoidal pads | |
| // -ve yDelta = decrease in top edge length vs bottom | |
| // +ve yDelta = increase in bottom edge length vs top | |
| // Note: one or both of xDelta, yDelta, must be zero | |
| long kicadShapeOrientation = 0; // this is specified in decidegrees by kicad | |
| char kicadDrillShape = '0'; | |
| long kicadDrillOneSizeNm = 0; | |
| long kicadDrillOneXoffsetNm = 0; | |
| long kicadDrillOneYoffsetNm = 0; | |
| char kicadDrillShapeTwo = '0' ; | |
| long kicadDrillSlotWidthNm = 0; | |
| long kicadDrillSlotHeightNm = 0; | |
| long kicadPadPositionXNm = 0; | |
| long kicadPadPositionYNm = 0; | |
| String kicadPadAttributeType = "null"; | |
| long gEDAdefaultMetalClearance = 20; // NB defined here in thousandths of an inch = mils | |
| // (clearance/2) = minimum distance from pad/pin metal | |
| // to nearest copper | |
| // this gets multiplied by 100 for 0.01 mil units in output | |
| long gEDAdefaultSolderMaskRelief = 8; // NB defined here in thousandths of an inch = mils | |
| // solder mask relief | |
| // = ((thickness of mask aperture) - (pad or pin thickness)) | |
| // this gets multiplied by 100 for 0.01 mil units in output | |
| long gEDAdefaultMinimumDrillSizeNm = 0; // 300000 is big enough to be vaguely sane for vias | |
| // this can be imposed when creating the module by passing | |
| // a minimum drill/via size to the setter/constructor | |
| String topLayerPad = ""; // we use these temporary variables when building slots | |
| String bottomLayerPad = ""; // and obroid pads | |
| Boolean equilateralPad = true; // also used when processing obroid, round and circular pads | |
| // Pins and SMD pads have been converted from Kicad foot prints which do not have solder | |
| // mask relief or clearances specified. Default values used for solder mask relief and | |
| // clearance are as specified above. | |
| // | |
| // Users of the foot print must ensure that the solder mask reliefs and clearances are | |
| // compatible with the PCB manufacturer's process tolerances | |
| String gEDAflag = "blah"; // hex values now deprecated i.e. "0x0000" | |
| public Pad() // the default constructor simply creates a simple default pad for testing | |
| { | |
| kicadShapePadName = "1"; | |
| kicadShapeNetName = "GND"; | |
| kicadShapeXsizeNm = 800*2540; | |
| kicadShapeYsizeNm = 800*2540; | |
| kicadShapeXdeltaNm = 0; | |
| kicadShapeYdeltaNm = 0; | |
| kicadShapeOrientation = 0; | |
| kicadDrillShape = 'C'; | |
| kicadDrillOneSizeNm = 600; | |
| kicadDrillOneXoffsetNm = 0; | |
| kicadDrillOneYoffsetNm = 0; | |
| kicadDrillShapeTwo = 'C'; | |
| kicadDrillSlotWidthNm = 0; | |
| kicadDrillSlotHeightNm = 0; | |
| kicadPadPositionXNm = 1000*2540; | |
| kicadPadPositionYNm = 1000*2540; | |
| kicadPadAttributeType = "STD"; | |
| } | |
| public void populateElement(String arg, boolean metric, long minimumViaAndDrillSizeNM) | |
| { | |
| gEDAdefaultMinimumDrillSizeNm = minimumViaAndDrillSizeNM; | |
| String parseString = ""; | |
| String[] tokens; | |
| // System.out.println("Constructor has been passed:" + arg); | |
| float parsedValue = 0; | |
| Scanner padDefinition = new Scanner(arg); | |
| // the while statement takes care of legacy pad definitions | |
| // will need to fork here with an if, either legacy or s-file | |
| // since s-file fits almost all of it on one line | |
| while (padDefinition.hasNextLine()) | |
| { | |
| // System.out.println("Now in while loop, processing: " + parseString); | |
| parseString = padDefinition.nextLine(); | |
| tokens = parseString.split(" "); | |
| if (tokens[0].startsWith("Sh")) | |
| { | |
| kicadShapePadName = tokens[1]; | |
| // we get rid of leading and trailing "" | |
| if (kicadShapePadName.length() >= 2) { | |
| if (kicadShapePadName.startsWith("\"") && kicadShapePadName.endsWith("\"")) { | |
| kicadShapePadName = | |
| kicadShapePadName.substring(1,kicadShapePadName.length()-1); | |
| // System.out.println("trimmed pad name: " + kicadShapePadName); | |
| } | |
| } | |
| // we get rid of odd characters that may interfere with pad naming: | |
| kicadShapePadName = kicadShapePadName.replaceAll("[^a-zA-Z0-9.-]", "_"); | |
| kicadDrillShape = tokens[2].charAt(0); | |
| // System.out.println("Drillshape: " + drillShape); | |
| parsedValue = Float.parseFloat(tokens[3]); | |
| kicadShapeXsizeNm = convertToNanometres(parsedValue, metric); | |
| parsedValue = Float.parseFloat(tokens[4]); | |
| kicadShapeYsizeNm = convertToNanometres(parsedValue, metric); | |
| // System.out.println(shapeXsize + " " + shapeYsize); | |
| parsedValue = Float.parseFloat(tokens[5]); | |
| kicadShapeXdeltaNm = convertToNanometres(parsedValue, metric); | |
| parsedValue = Float.parseFloat(tokens[6]); | |
| kicadShapeYdeltaNm = convertToNanometres(parsedValue, metric); | |
| kicadShapeOrientation = Integer.parseInt(tokens[7]); | |
| // this is the rotation of the pad in decidegrees | |
| // we need orientation to process obround pads | |
| } | |
| if (tokens[0].startsWith("Po")) | |
| { | |
| parsedValue = Float.parseFloat(tokens[1]); | |
| kicadPadPositionXNm = convertToNanometres(parsedValue, metric); | |
| // System.out.println(padPositionX); | |
| parsedValue = Float.parseFloat(tokens[2]); | |
| kicadPadPositionYNm = convertToNanometres(parsedValue, metric); | |
| // System.out.println(padPositionY); | |
| } | |
| if (tokens[0].startsWith("Ne")) | |
| { | |
| kicadShapeNetName = tokens[2]; | |
| // we get rid of leading and trailing "" | |
| if (kicadShapeNetName.length() >= 2) { | |
| if (kicadShapeNetName.startsWith("\"") && kicadShapeNetName.endsWith("\"")) { | |
| kicadShapeNetName = | |
| kicadShapeNetName.substring(1,kicadShapeNetName.length()-1); | |
| } | |
| } | |
| // we now need to cleanse the NetName of nasties like '$' which sometimes occur | |
| kicadShapeNetName = kicadShapeNetName.replaceAll("[^a-zA-Z0-9.-]", "_"); | |
| // System.out.println("Shape's Net name: " + shapeNetName); | |
| } | |
| if (tokens[0].startsWith("At")) | |
| { | |
| kicadPadAttributeType = tokens[1]; | |
| // System.out.println("Pad attribute type: " + padAttributeType); | |
| } | |
| if (tokens[0].startsWith("Dr")) | |
| { | |
| parsedValue = Float.parseFloat(tokens[1]); | |
| kicadDrillOneSizeNm = convertToNanometres(parsedValue, metric); | |
| // System.out.println("Drill size: " + drillOneSize); | |
| // we can capture drill x and y offset, but it may not be useful | |
| parsedValue = Float.parseFloat(tokens[2]); | |
| kicadDrillOneXoffsetNm = convertToNanometres(parsedValue, metric); | |
| parsedValue = Float.parseFloat(tokens[3]); | |
| kicadDrillOneYoffsetNm = convertToNanometres(parsedValue, metric); | |
| // System.out.println("First hole X and Y offsets: " + | |
| // drillOneXoffset + " " + drillOneYoffset); | |
| // and now we figure out if there is a second hole defined, i.e. slot | |
| if (tokens.length > 5) | |
| { | |
| // System.out.print("hey, there's a second hole"); | |
| // System.out.print(" and it is defined as "); | |
| kicadDrillShapeTwo = tokens[4].charAt(0); | |
| // System.out.println(drillShapeTwo); | |
| parsedValue = Float.parseFloat(tokens[5]); | |
| kicadDrillSlotWidthNm = convertToNanometres(parsedValue, metric); | |
| parsedValue = Float.parseFloat(tokens[6]); | |
| kicadDrillSlotHeightNm = convertToNanometres(parsedValue, metric); | |
| // System.out.println("DrillTwoX and DrillTwoY are: " + drillTwoX + " " + drillTwoY); | |
| } | |
| else // this captures scenarios where the pad is repopulated | |
| { // with a pin rather than a slot | |
| kicadDrillShapeTwo = '0'; | |
| } | |
| } | |
| if (parseString.startsWith("pad")) // we move onto dedicated s-file parsing | |
| { | |
| metric = true; | |
| // System.out.println("Parsing s-file pad description"); | |
| // while (padDefinition.hasNextLine()) | |
| // { | |
| // parseString = parseString + " " + padDefinition.nextLine(); | |
| // } | |
| tokens = parseString.split(" "); | |
| // for (int counter = 0; counter < tokens.length; counter++) | |
| // { | |
| // System.out.println("Pad token #: " + counter + " : " + tokens[counter]); | |
| // } | |
| // we first grab the pad name | |
| kicadShapePadName = tokens[1].replaceAll("[^a-zA-Z0-9.-]", "_"); | |
| // and rid the Pad Name of nasties like '$' which sometimes occur | |
| // next we glean the type of pad | |
| if (tokens[2].startsWith("thru_hole")) | |
| { | |
| kicadPadAttributeType = "STD"; | |
| } | |
| else if (tokens[2].startsWith("smd")) | |
| { | |
| kicadPadAttributeType = "SMD"; | |
| } | |
| else if (tokens[2].startsWith("connect")) | |
| { | |
| kicadPadAttributeType = "CONN"; | |
| } | |
| else if (tokens[2].startsWith("np_thru")) | |
| { | |
| kicadPadAttributeType = "HOLE"; | |
| } | |
| // next comes the shape of the pad or hole | |
| if (tokens[3].startsWith("circle")) | |
| { | |
| kicadDrillShape = 'C'; | |
| } | |
| else if (tokens[3].startsWith("rect")) | |
| { | |
| kicadDrillShape = 'R'; | |
| } | |
| else if (tokens[3].startsWith("oval")) | |
| { | |
| kicadDrillShape = 'O'; | |
| } | |
| else if (tokens[3].startsWith("trapezoid")) | |
| { | |
| kicadDrillShape = 'T'; | |
| } | |
| // now we parse the less predictable remaining attributes | |
| for (int parseIndex = 4; parseIndex < tokens.length; parseIndex++) | |
| { | |
| if (tokens[parseIndex].startsWith("at")) | |
| { | |
| parseIndex++; | |
| parsedValue = Float.parseFloat(tokens[parseIndex]); | |
| kicadPadPositionXNm = convertToNanometres(parsedValue, metric); | |
| // System.out.println(padPositionX); | |
| parseIndex++; | |
| parsedValue = Float.parseFloat(tokens[parseIndex]); | |
| kicadPadPositionYNm = convertToNanometres(parsedValue, metric); | |
| // System.out.println(padPositionY); | |
| kicadShapeOrientation = 0; // set a default value | |
| // we now look to see if orientation is specified | |
| if (!tokens[parseIndex + 1].startsWith("size")) | |
| { | |
| // them tricksy kicadians went and changed | |
| // from decidegrees to degrees in the s-file | |
| // format without telling anyone.... | |
| // hence the multiplication by 10...... | |
| kicadShapeOrientation = 10*Integer.parseInt(tokens[parseIndex+1]); | |
| parseIndex++; | |
| } | |
| } | |
| else if (tokens[parseIndex].startsWith("size")) | |
| { | |
| parseIndex++; | |
| parsedValue = Float.parseFloat(tokens[parseIndex]); | |
| kicadShapeXsizeNm = convertToNanometres(parsedValue, metric); | |
| parseIndex++; | |
| parsedValue = Float.parseFloat(tokens[parseIndex]); | |
| kicadShapeYsizeNm = convertToNanometres(parsedValue, metric); | |
| } | |
| else if (tokens[parseIndex].startsWith("rect_delta")) | |
| { | |
| parseIndex++; | |
| parsedValue = Float.parseFloat(tokens[parseIndex]); | |
| kicadShapeXdeltaNm = convertToNanometres(parsedValue, metric); | |
| parseIndex++; | |
| parsedValue = Float.parseFloat(tokens[parseIndex]); | |
| kicadShapeYdeltaNm = convertToNanometres(parsedValue, metric); | |
| } | |
| else if (tokens[parseIndex].startsWith("drill")) | |
| { | |
| parseIndex++; | |
| // we look to see if it is an oval hole | |
| if (tokens[parseIndex].startsWith("o")) | |
| { | |
| kicadDrillShapeTwo = 'O'; | |
| parseIndex++; | |
| parsedValue = Float.parseFloat(tokens[parseIndex]); | |
| kicadDrillSlotWidthNm = convertToNanometres(parsedValue, metric); | |
| parseIndex++; | |
| parsedValue = Float.parseFloat(tokens[parseIndex]); | |
| kicadDrillSlotHeightNm = convertToNanometres(parsedValue, metric); | |
| } | |
| else // it isn't an oval hole | |
| { | |
| parsedValue = Float.parseFloat(tokens[parseIndex]); | |
| kicadDrillOneSizeNm = convertToNanometres(parsedValue, metric); | |
| kicadDrillShapeTwo = '0';// flag lack of hole 2 | |
| } | |
| // we now look for x,y offset of hole, if specified | |
| if ((parseIndex < (tokens.length - 2)) && tokens[parseIndex + 1].startsWith("offset")) | |
| { | |
| parseIndex++; // we step past "offset" | |
| parseIndex++; // and get to offsetX | |
| parsedValue = Float.parseFloat(tokens[parseIndex]); | |
| kicadDrillOneXoffsetNm = convertToNanometres(parsedValue, metric); | |
| parseIndex++; // and then onto offsetY | |
| parsedValue = Float.parseFloat(tokens[parseIndex]); | |
| kicadDrillOneYoffsetNm = convertToNanometres(parsedValue, metric); | |
| } | |
| } | |
| else if (tokens[parseIndex].startsWith("net")) | |
| { | |
| parseIndex++; | |
| // we will skip the net number | |
| parseIndex++; | |
| kicadShapeNetName = tokens[parseIndex]; | |
| // we now need to cleanse the NetName of nasties like '$' which sometimes occur | |
| kicadShapeNetName = kicadShapeNetName.replaceAll("[^a-zA-Z0-9.-]", "_"); | |
| } | |
| } | |
| } | |
| } | |
| // might be good to default populate the net name | |
| // if empty | |
| // System.out.println("NetName : " + kicadShapeNetName); | |
| // System.out.println("PadName : " + kicadShapePadName); | |
| if (kicadShapeNetName.length() == 0) { | |
| kicadShapeNetName = | |
| kicadShapePadName; | |
| } | |
| // System.out.println("finished populating pad object"); | |
| } | |
| public String generateGEDAelement(long xOffsetNm, long yOffsetNm, float magnificationRatio) | |
| // offsets supplied in Nm, magnificationRatio supplied as float, not used as of yet | |
| { | |
| // System.out.println("about to generate a gEDA pad element"); | |
| String output = "For some reason, the pad object was not populated by the constructor"; | |
| /** | |
| * the first task is to establish if the pad is a pin, pad, hole or square | |
| * octogons are not supported in kicad | |
| */ | |
| String oblongSlotFlag = ""; // the default setting of rounded ends for a slot = "" | |
| // we now check to see if we have had a minimum drill and via size imposed | |
| // by the Footprint that constructed this pad | |
| // Note: could have a distinct method to change this, would be useful | |
| if (kicadDrillOneSizeNm != 0) | |
| { | |
| if (kicadDrillOneSizeNm < gEDAdefaultMinimumDrillSizeNm) | |
| { | |
| kicadDrillOneSizeNm = gEDAdefaultMinimumDrillSizeNm; | |
| } | |
| } | |
| // one of the first things to do is to establish if | |
| // the pad is a boring round or square pad, as this will | |
| // affect our processing of rectangular and obroid pads | |
| // but before we even do this, we have to catch an annoying deviation | |
| // from the file format, namely, when the pad shape is given as 'R' | |
| // and a kicadShapeX size is given but the given kicadShapeY is zero | |
| // presumably kicad automagically assumes it is square | |
| // this is an unusual problem, and is probably the result of an | |
| // eagle-kicad conversion tool quirk | |
| if (kicadShapeYsizeNm == 0) | |
| { | |
| kicadShapeYsizeNm = kicadShapeXsizeNm; | |
| } // with that out of the way, we can now decide if the pad is "equilateral" | |
| if (kicadShapeXsizeNm != kicadShapeYsizeNm) | |
| { | |
| equilateralPad = false; | |
| } | |
| else | |
| { | |
| equilateralPad = true; | |
| } | |
| switch (kicadDrillShape) | |
| { | |
| case 'O': // an obround pad shape can be done as a circle for now | |
| // probably will need to implement as a pin + a pad on | |
| // top layer plus, on the bottom "onsolder" layer | |
| // plus take care of the orientation stuff too | |
| // when determining stop, start of pad direction | |
| // which might make the centre pin round or square, too | |
| // .......but obround pins are a little rare, though | |
| case 'C': if (kicadPadAttributeType.startsWith("STD")) // = plate through | |
| { | |
| gEDAflag = ""; // WAS "pin"; // "0x0001" now deprecated | |
| } | |
| else if (kicadPadAttributeType.startsWith("HOLE")) // = NPTH, round | |
| { | |
| gEDAflag = "hole"; // "0x0008" now deprecated | |
| } | |
| break; | |
| case 'R': if (kicadPadAttributeType.startsWith("STD")) // = plate through | |
| { | |
| if (equilateralPad) // it is square, let it be square | |
| { | |
| gEDAflag = "square"; // "0x0100" now deprecated | |
| } | |
| else | |
| { | |
| gEDAflag = ""; // make obroid pad pins rounded | |
| } | |
| // i.e. we don't want "square" set for square ended obroid pads | |
| oblongSlotFlag = "square"; | |
| } | |
| else if (kicadPadAttributeType.startsWith("HOLE")) | |
| { | |
| gEDAflag = "hole"; // "0x0008" now deprecated | |
| } | |
| else if (kicadPadAttributeType.startsWith("SMD") || | |
| kicadPadAttributeType.startsWith("CONN")) | |
| { | |
| gEDAflag = "square"; // "0x0000" now deprecated | |
| } | |
| break; | |
| /** | |
| * | |
| * additional pad "shape" options in, for example, 'Sh "2" C 1500 1500 0 0 2700' definition | |
| * include 'O' = oblong, 'T' = trapeze | |
| * | |
| * this is to be distinguished from from "drillshape" options, such as 'Dr 600 0 0 O 600 650 | |
| * which can be used to specify an oblong 'O' hole which seems to be a slot | |
| * | |
| */ | |
| default: gEDAflag = "blah"; | |
| break; | |
| } | |
| // further refinements would include the addition of a track to make more complicated | |
| // non square or non round pads, with onsolder and top layer elements | |
| // here we implement rudimentary support for pad orientation | |
| // we do this by noting that orientations of 900 or 2700 can be achieved | |
| // by exchanging the value of kicadShapeXsizeNm and kicadShapeYsizeNm | |
| // which will work for simple pads with only one, centred hole | |
| // for this to work for pads with more than one hole, slot widths and heights | |
| // will need to be translated as well | |
| while (kicadShapeOrientation < 0) // we aim to produce 0 =< orientation values =< 3599 | |
| { | |
| kicadShapeOrientation = 3600 + kicadShapeOrientation; | |
| } | |
| if (((kicadShapeOrientation >= 450) && (kicadShapeOrientation <= 1350)) || ((kicadShapeOrientation >= 2250) && (kicadShapeOrientation <= 3150))) | |
| { | |
| long tempVal = kicadShapeXsizeNm; | |
| kicadShapeXsizeNm = kicadShapeYsizeNm; | |
| kicadShapeYsizeNm = tempVal; | |
| } | |
| // simple support for rotation has been effected with a range of orientation values, | |
| // quantised into either horizontal of vertical | |
| // To support it more rigorously again, a rotation matrix would be needed | |
| // This rudimentary effort could break obround slots which rely on x and y offsets for the slot | |
| // in the form of slot height and width values, so we can test for those. | |
| // also, drill offsets in x and y would need translation | |
| /** | |
| * | |
| * the simplest scenario of all is an SMD pad or edge connector and we deal with this first | |
| * // ? have reworked the dimensions in the copied and pasted code for pins, ok so far | |
| * | |
| * also, the problem of pins defined with zero hole size are caught here and treated as a pad | |
| */ | |
| if ((kicadPadAttributeType.startsWith("SMD") || kicadPadAttributeType.startsWith("CONN")) || (kicadPadAttributeType.startsWith("STD") && (kicadDrillOneSizeNm == 0))) | |
| { | |
| // scenario with wider SMD pad than tall, which determines which dimension is used for thickness | |
| // i.e. shapeYsize is equivalent to gEDA's "thickness" attribute for a pad | |
| if (kicadShapeXsizeNm >= kicadShapeYsizeNm) | |
| { | |
| output = "Pad[" + | |
| ((xOffsetNm + kicadPadPositionXNm - kicadShapeXsizeNm/2 + kicadShapeYsizeNm/2)/254) + " " + | |
| ((yOffsetNm + kicadPadPositionYNm)/254) + " " + | |
| ((xOffsetNm + kicadPadPositionXNm + kicadShapeXsizeNm/2 - kicadShapeYsizeNm/2)/254) + " " + | |
| ((yOffsetNm + kicadPadPositionYNm)/254) + " " + | |
| (kicadShapeYsizeNm/254) + " " + | |
| (100*gEDAdefaultMetalClearance) + " " + | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeYsizeNm/254)) + " " + | |
| '"' + kicadShapeNetName + "\" " + | |
| '"' + kicadShapePadName + "\" " + | |
| '"' + | |
| gEDAflag + // the flag is useful, square vs rounded ends of SMD pad | |
| '"' + "]\n"; | |
| } | |
| // scenario with taller SMD pad than wide, which determines which dimension is used for thickness | |
| // i.e. shapeXsize is equivalent to gEDA's "thickness" attribute for a pad | |
| else | |
| { | |
| output = "Pad[" + | |
| ((xOffsetNm + kicadPadPositionXNm)/254) + " " + | |
| ((yOffsetNm + kicadPadPositionYNm - kicadShapeYsizeNm/2 + kicadShapeXsizeNm/2)/254) + " " + | |
| ((xOffsetNm + kicadPadPositionXNm)/254) + " " + | |
| ((yOffsetNm + kicadPadPositionYNm + kicadShapeYsizeNm/2 - kicadShapeXsizeNm/2)/254) + " " + | |
| (kicadShapeXsizeNm/254) + " " + | |
| (100*gEDAdefaultMetalClearance) + " " + | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeXsizeNm/254)) + " " + | |
| '"' + kicadShapeNetName + "\" " + | |
| '"' + kicadShapePadName + "\" " + | |
| '"' + | |
| gEDAflag + // sets rounded or square pad | |
| '"' + "]\n"; | |
| } | |
| } | |
| /** | |
| * | |
| * the next simplest scenario captures a pin = "HOLE", with no additional hole, so, not an unplated | |
| * slot, and not a plated hole pin = "STD" | |
| * | |
| */ | |
| else if (kicadPadAttributeType.startsWith("HOLE") && (kicadDrillShapeTwo == '0')) | |
| { | |
| output = "Pin[" + // square bracket indicates 1/100 mil resolution | |
| ((kicadPadPositionXNm + xOffsetNm)/254) + " " + | |
| ((kicadPadPositionYNm + yOffsetNm)/254) + " " + | |
| (kicadShapeXsizeNm/254) + " " + // pin outer diam., if round = shapeXsize = shapeYsize | |
| (100*gEDAdefaultMetalClearance) + " " + // gEDA: clearance is specified per pad/pin | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeXsizeNm/254)) + " " + | |
| (kicadDrillOneSizeNm/254) + " " + // drill hole size in 0.01mil units | |
| '"' + kicadShapeNetName + "\" " + // arbitrary label for pin | |
| '"' + kicadShapePadName + "\" " + // pin number for attaching nets | |
| '"' + gEDAflag + '"' + "]\n"; // gEDAflag has already been set to "hole" | |
| } | |
| /** | |
| * | |
| * the next simplest scenario captures a standard pin = "STD" with no additional hole, so, not a slot | |
| * and also is an equilateralPad = true, so not oblong, | |
| * and it is a plated hole rather than a mechanical "HOLE" | |
| * This also captures kicadDrillShape = 'T' for trapezoidal pad shapes, which could be dealt with | |
| * in another bit of code if support is desirable | |
| * | |
| */ | |
| else if (kicadPadAttributeType.startsWith("STD") && (kicadDrillShapeTwo == '0') && (equilateralPad)) // was: && kicadDrillShape != 'O')) | |
| { | |
| output = "Pin[" + // square bracket indicates 1/100 mil resolution | |
| ((kicadPadPositionXNm + xOffsetNm)/254) + " " + | |
| ((kicadPadPositionYNm + yOffsetNm)/254) + " " + | |
| (kicadShapeXsizeNm/254) + " " + // pin outer diam., if round = shapeXsize = shapeYsize | |
| (100*gEDAdefaultMetalClearance) + " " + // gEDA: clearance is specified per pad/pin | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeXsizeNm/254)) + " " + | |
| (kicadDrillOneSizeNm/254) + " " + // drill hole size in 0.01mil units | |
| '"' + kicadShapeNetName + "\" " + // arbitrary label for pin | |
| '"' + kicadShapePadName + "\" " + // pin number for attaching nets | |
| '"' + gEDAflag + '"' + "]\n"; // square bracket indicates 1/100 mil resolution | |
| } | |
| /** | |
| * | |
| * the next simplest scenario captures a standard pin = "STD" with no additional hole, so, not a slot, | |
| * but kicadDrillShape = 'O', 'T' or 'R' and not an equilateral pad, making it an obroid pad, | |
| * and it is a plated hole rather than a mechanical "HOLE" | |
| * | |
| * - we also need to consider orientation of the pad in due course with rotational | |
| * translation of the pad | |
| * - in the first instance, rotation of 900 or 2700 decigrees can be supported by applying | |
| * horizontal pad processing for vertical pads, and vice versa | |
| * | |
| * we need to process kicadDrillShape = 'R' or 'O' or 'T' and at the same time !equilateralPad" | |
| * | |
| * The code produces a standard "STD" plate through hole, round, pin, with the addition | |
| * of a suitable pad on the top and on the bottom (="onsolder") layers of necessary dimensions | |
| * | |
| * | |
| * There is also the problem of some modules specifiying shape 'R' pads with arbitrary X size | |
| * but zero Y size, presumably KiCad defaults to a sqaure pad in these cases - We tested for this | |
| * prior and let y size = x size if y size = 0 | |
| * | |
| */ | |
| else if (kicadPadAttributeType.startsWith("STD") && (kicadDrillShapeTwo == '0') && (!equilateralPad) ) | |
| { | |
| // if (kicadShapeYsizeNm == 0) | |
| // { | |
| // kicadShapeYsizeNm = kicadShapeXsizeNm; | |
| // }// this could perhaps be done during parsing in case other scenarios exist | |
| if (kicadShapeXsizeNm >= kicadShapeYsizeNm) // we have a horizontal obroid pad | |
| { | |
| output = "Pin[" + // square bracket indicates 1/100 mil resolution | |
| ((kicadPadPositionXNm + xOffsetNm)/254) + " " + | |
| ((kicadPadPositionYNm + yOffsetNm)/254) + " " + | |
| (kicadShapeYsizeNm/254) + " " + | |
| // pin outer diam., if horizontal obroad pad = shapeYsize | |
| (100*gEDAdefaultMetalClearance) + " " + | |
| // gEDA: clearance is specified per pad/pin | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeXsizeNm/254)) + " " + | |
| (kicadDrillOneSizeNm/254) + " " + // drill hole size in 0.01mil units | |
| '"' + kicadShapeNetName + "\" " + // arbitrary label for pin | |
| '"' + kicadShapePadName + "\" " + // pin number for attaching nets | |
| '"' + gEDAflag + '"' + "]\n"; | |
| // square bracket indicates 1/100 mil resolution | |
| // rotational transformation could maybe be applied here for | |
| // kicadPadPositionXNm and kicadPadPositionYNm | |
| topLayerPad = "Pad[" + | |
| ((xOffsetNm + kicadPadPositionXNm - kicadShapeXsizeNm/2 + (kicadShapeYsizeNm)/2)/254) + " " + // drillTwoY is the slot height | |
| ((yOffsetNm + kicadPadPositionYNm)/254) + " " + | |
| ((xOffsetNm + kicadPadPositionXNm + kicadShapeXsizeNm/2 - (kicadShapeYsizeNm)/2)/254) + // drillTwoY is the slot height | |
| // it seems that oblong slot's drillTwoX and drillTwoY are absolute slot dimensions | |
| // not delta x,y vs first hole | |
| " " + | |
| ((yOffsetNm + kicadPadPositionYNm)/254) + " " + | |
| (kicadShapeYsizeNm/254) + " " + | |
| (100*gEDAdefaultMetalClearance) + " " + | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeYsizeNm/254)) + " " + | |
| '"' + kicadShapeNetName + "\" " + | |
| '"' + kicadShapePadName + "\" " + | |
| '"' + | |
| oblongSlotFlag; | |
| bottomLayerPad = topLayerPad + ",onsolder"; | |
| output = output + topLayerPad + "\"]\n" + bottomLayerPad + "\"]\n"; | |
| } | |
| else if (kicadShapeXsizeNm < kicadShapeYsizeNm) // a vertical obroid pad | |
| { | |
| output = "Pin[" + // square bracket indicates 1/100 mil resolution | |
| ((kicadPadPositionXNm + xOffsetNm)/254) + " " + | |
| ((kicadPadPositionYNm + yOffsetNm)/254) + " " + | |
| (kicadShapeXsizeNm/254) + " " + | |
| // pin outer diam., if vertical obroid pad = shapeXsize | |
| (100*gEDAdefaultMetalClearance) + " " + | |
| // gEDA: clearance is specified per pad/pin | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeXsizeNm/254)) + " " + | |
| (kicadDrillOneSizeNm/254) + " " + // drill hole size in 0.01mil units | |
| '"' + kicadShapeNetName + "\" " + // arbitrary label for pin | |
| '"' + kicadShapePadName + "\" " + // pin number for attaching nets | |
| '"' + gEDAflag + '"' + "]\n"; | |
| // square bracket indicates 1/100 mil resolution | |
| // rotational transformation could maybe be applied here for | |
| // kicadPadPositionXNm and kicadPadPositionYNm | |
| topLayerPad = "Pad[" + | |
| ((xOffsetNm + kicadPadPositionXNm)/254) + " " + | |
| ((yOffsetNm + kicadPadPositionYNm - kicadShapeYsizeNm/2 + (kicadShapeXsizeNm)/2)/254) + " " + | |
| ((xOffsetNm + kicadPadPositionXNm)/254) + " " + | |
| ((yOffsetNm + kicadPadPositionYNm + kicadShapeYsizeNm/2 - (kicadShapeXsizeNm)/2)/254) + | |
| " " + | |
| (kicadShapeXsizeNm/254) + " " + | |
| (100*gEDAdefaultMetalClearance) + " " + | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeXsizeNm/254)) + " " + | |
| '"' + kicadShapeNetName + "\" " + | |
| '"' + kicadShapePadName + "\" " + | |
| '"' + | |
| oblongSlotFlag; | |
| bottomLayerPad = topLayerPad + ",onsolder"; | |
| output = output + topLayerPad + "\"]\n" + bottomLayerPad + "\"]\n"; | |
| } | |
| } | |
| /** | |
| * | |
| * the next section deals with oblong pins which are "STD" and drillShapeTwo = oblong "O" | |
| * by generating two pins the appropriate distance apart | |
| * | |
| * we really want the pins to be rounded, not square, if the drill offsets are | |
| * on a diagonal, and we first test to see if the oblong hole is on a diagonal | |
| * | |
| * we then join the two pins with a pad of the right length and with rounded ends | |
| * | |
| */ | |
| else if (kicadPadAttributeType.startsWith("STD") && (kicadDrillShapeTwo == 'O')) // not zero, but "O" for "Obround" or oval, and a slot | |
| { | |
| if (((kicadDrillSlotWidthNm - kicadDrillOneXoffsetNm) != 0) && ((kicadDrillSlotHeightNm - kicadDrillOneYoffsetNm) != 0)) // we capture (hmm, not diagonal, if slot not rotated) slots here | |
| { | |
| oblongSlotFlag = ""; // and make the slot ends rounded != "square" | |
| // i'm starting to think this only rounds off the ends for all slots | |
| } | |
| // next, we require an if statement to fork for the vertical or horizontal slot cases | |
| if (kicadShapeXsizeNm >= kicadShapeYsizeNm) // horizontal slot | |
| { | |
| output = "Pin[" + // square bracket indicates 1/100 mil resolution | |
| ((kicadPadPositionXNm + kicadDrillOneXoffsetNm - kicadDrillSlotWidthNm/2 + xOffsetNm + kicadDrillOneYoffsetNm/2)/254) + " " + | |
| ((kicadPadPositionYNm + kicadDrillOneYoffsetNm - yOffsetNm)/254) + | |
| " " + | |
| (kicadShapeYsizeNm/254) + " " + // pin outer diam., slot pad height = shapeYsize | |
| (100*gEDAdefaultMetalClearance) + " " + // gEDA: clearance specified per pad/pin | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeYsizeNm/254)) + " " + | |
| (kicadDrillSlotHeightNm/254) + " " + // drill two Y size = slot height | |
| '"' + kicadShapeNetName + "\" " + // arbitrary label for pin | |
| '"' + kicadShapePadName + "\" " + // pin number for attaching nets | |
| '"' + | |
| oblongSlotFlag + // gEDAflag + | |
| '"' + "]\n" + // square bracket indicates 1/100 ml resolution | |
| "Pin[" + | |
| ((kicadPadPositionXNm + kicadDrillOneXoffsetNm + xOffsetNm + kicadDrillSlotWidthNm/2 - kicadDrillOneXoffsetNm/2)/254) + " " + | |
| ((kicadPadPositionYNm + kicadDrillOneYoffsetNm + yOffsetNm)/254) + | |
| " " + | |
| (kicadShapeYsizeNm/254) + " " + // pin outer diameter | |
| (100*gEDAdefaultMetalClearance) + " " + | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeYsizeNm/254)) + " " + | |
| (kicadDrillSlotHeightNm/254) + " " + // drill two Y equals slot height | |
| '"' + kicadShapeNetName + "\" " + | |
| '"' + kicadShapePadName + "\" " + | |
| '"' + | |
| oblongSlotFlag + // gEDAflag + | |
| '"' + "]\n"; | |
| } | |
| else if (kicadShapeYsizeNm > kicadShapeXsizeNm) // vertical slot | |
| { | |
| output = "Pin[" + // square bracket indicates 1/100 mil resolution | |
| ((kicadPadPositionXNm + kicadDrillOneXoffsetNm + xOffsetNm)/254) + " " + | |
| ((kicadPadPositionYNm + kicadDrillOneYoffsetNm - kicadDrillSlotHeightNm/2 + yOffsetNm + kicadDrillOneXoffsetNm/2)/254) + " " + | |
| (kicadShapeXsizeNm/254) + " " + // pin outer diam., if round = shapeXsize = slot pad width | |
| (100*gEDAdefaultMetalClearance) + " " + // gEDA: clearance specified per pad/pin | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeXsizeNm/254)) + " " + | |
| (kicadDrillSlotWidthNm/254) + " " + // drill one hole size | |
| '"' + kicadShapeNetName + "\" " + // arbitrary label for pin | |
| '"' + kicadShapePadName + "\" " + // pin number for attaching nets | |
| '"' + | |
| oblongSlotFlag + // gEDAflag + | |
| '"' + "]\n" + // square bracket indicates 1/100 ml resolution | |
| "Pin[" + | |
| ((kicadPadPositionXNm + kicadDrillOneXoffsetNm + xOffsetNm)/254) + " " + | |
| ((kicadPadPositionYNm + kicadDrillOneYoffsetNm + yOffsetNm + kicadDrillSlotHeightNm/2- kicadDrillOneXoffsetNm/2)/254) + " " + | |
| (kicadShapeXsizeNm/254) + " " + // pin outer diameter | |
| (100*gEDAdefaultMetalClearance) + " " + | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeXsizeNm/254)) + " " + | |
| (kicadDrillSlotWidthNm/254) + " " + | |
| '"' + kicadShapeNetName + "\" " + | |
| '"' + kicadShapePadName + "\" " + | |
| '"' + | |
| oblongSlotFlag + // gEDAflag + | |
| '"' + "]\n"; | |
| } | |
| /** | |
| * | |
| * If we are to join two holes with a pad, the pad will need rounded ends, and a thickness | |
| * that increases in proportion to the sine of the angle/2, so for now we will simply | |
| * skip the gEDAflag that would probably be "square", and use the oblongSlotFlag instead | |
| * to keep the ends round - it is simpler this way. | |
| * | |
| */ | |
| /** | |
| * | |
| * the first case addresses the scenario of a horizontally aligned, rectangular pad | |
| * or square with two holes making a slot | |
| * | |
| */ | |
| if (kicadShapeXsizeNm >= kicadShapeYsizeNm) // i.e. horizontal slot | |
| { | |
| topLayerPad = "Pad[" + | |
| ((xOffsetNm + kicadDrillOneXoffsetNm + kicadPadPositionXNm - kicadShapeXsizeNm/2 + (kicadShapeYsizeNm - kicadDrillSlotHeightNm)/2)/254) + " " + // drillTwoY is the slot height | |
| ((yOffsetNm + kicadDrillOneYoffsetNm + kicadPadPositionYNm)/254) + " " + | |
| ((xOffsetNm + kicadDrillOneXoffsetNm + kicadPadPositionXNm + kicadShapeXsizeNm/2 - (kicadShapeYsizeNm - kicadDrillSlotHeightNm)/2)/254) + // drillTwoY is the slot height | |
| // it seems that oblong slot's drillTwoX and drillTwoY are absolute slot dimensions | |
| // not delta x,y vs first hole | |
| " " + | |
| ((yOffsetNm + kicadDrillOneYoffsetNm + kicadPadPositionYNm)/254) + " " + | |
| (kicadShapeYsizeNm/254) + " " + | |
| (100*gEDAdefaultMetalClearance) + " " + | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeYsizeNm/254)) + " " + | |
| '"' + kicadShapeNetName + "\" " + | |
| '"' + kicadShapePadName + "\" " + | |
| '"' + | |
| oblongSlotFlag; | |
| bottomLayerPad = topLayerPad + ",onsolder"; | |
| output = output + topLayerPad + "\"]\n" + bottomLayerPad + "\"]\n"; | |
| } | |
| /** | |
| * | |
| * versus the remaining alternative of a vertically aligned rectangular pad with a hole | |
| * at each end to make a slot - as of yet nothing done for non orthogonal rectangular or | |
| * square slot pads, it has been implemented with round pads for diagonal slots | |
| * | |
| */ | |
| else // vertical slot | |
| { | |
| topLayerPad = "Pad[" + | |
| ((xOffsetNm + kicadPadPositionXNm + kicadDrillOneXoffsetNm)/254) + " " + | |
| ((yOffsetNm + kicadPadPositionYNm + kicadDrillOneYoffsetNm - kicadShapeYsizeNm/2 + (kicadShapeXsizeNm - kicadDrillSlotWidthNm)/2)/254) + " " + | |
| ((xOffsetNm + kicadPadPositionXNm + kicadDrillOneXoffsetNm)/254) + " " + | |
| ((yOffsetNm + kicadPadPositionYNm + kicadDrillOneYoffsetNm + kicadShapeYsizeNm/2 - (kicadShapeXsizeNm - kicadDrillSlotWidthNm)/2)/254) + | |
| " " + | |
| (kicadShapeXsizeNm/254) + " " + | |
| (100*gEDAdefaultMetalClearance) + " " + | |
| (100*gEDAdefaultSolderMaskRelief + (kicadShapeXsizeNm/254)) + " " + | |
| '"' + kicadShapeNetName + "\" " + | |
| '"' + kicadShapePadName + "\" " + | |
| '"' + | |
| oblongSlotFlag; | |
| bottomLayerPad = topLayerPad + ",onsolder"; | |
| output = output + topLayerPad + "\"]\n" + bottomLayerPad + "\"]\n"; | |
| } | |
| /** | |
| * | |
| * having assembled a slotted oblong pair of pins with a pad between them | |
| * we return the string containing the gEDA compatible definition | |
| * | |
| */ | |
| } | |
| return output; | |
| } | |
| private long convertToNanometres(float rawValue, Boolean metricSystem) | |
| { | |
| if (metricSystem) | |
| { | |
| return (long)(rawValue * 1000000); // 1,000,000 nm per mm | |
| } | |
| else | |
| { | |
| return (long)(rawValue * 2540); // 2540 nm per 0.1 mil | |
| } | |
| } | |
| } |