661 lines
27 KiB
Plaintext
661 lines
27 KiB
Plaintext
#usage "en: <b>PCB Quote Service</b\n>"
|
|
"<p>"
|
|
"Get a quote for manufacturing your PCB.<br>",
|
|
"de: <b>PCB-Angebots-Service</b>\n"
|
|
"<p>"
|
|
"Hiermit gelangen Sie zu einem Angebot zur Fertigung Ihres Boards.<p>"
|
|
"<author>Autor: support@cadsoft.de</author><p>"
|
|
|
|
// THIS PROGRAM IS PROVIDED AS IS AND WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED
|
|
|
|
#require 5.1000
|
|
int Version = 25; // Refer to versioning system. Needed because ULP version independent from EAGLE version
|
|
|
|
enum { false = 0, true = 1 };
|
|
|
|
// Debug stuff
|
|
int Dbg = 0;
|
|
string DbgFile;
|
|
string DbgMsg;
|
|
|
|
// Messages
|
|
string Warnings;
|
|
int Useless; // When a quote doesn't make sense (e.g empty board or such)
|
|
|
|
// Design rules
|
|
int Export = 1;
|
|
string DruFile;
|
|
|
|
// Units / scaling: Handling change of internal units V5 -> V6:
|
|
int UFactor = (EAGLE_VERSION < 6 && EAGLE_RELEASE < 12) ? 1 : 32;
|
|
int PrefUnit; // Preferred unit (representation in dialog)
|
|
|
|
// Fabrication parameters
|
|
string BoardName;
|
|
int NrLayers;
|
|
int CopperThicknessOutside = INT_MAX;
|
|
int CopperThicknessInside = INT_MAX;
|
|
int MaterialThickness;
|
|
int MinTraceWidth = INT_MAX;
|
|
int MinHoleSize = INT_MAX;
|
|
string SolderSides;
|
|
string SilkScreenSides;
|
|
int NrBlindBuriedTypes;
|
|
int MinPitch = INT_MAX;
|
|
int NrSmdPadsTop;
|
|
int NrSmdPadsBottom;
|
|
|
|
// Assembly parameters
|
|
int NrPackages;
|
|
int NrThruHoles;
|
|
int NrBGAs;
|
|
int NrQFNs;
|
|
int NrFinePitchs;
|
|
int NrOtherSMDs;
|
|
int HasSMDBothSides;
|
|
|
|
// For computing width and height
|
|
real XMin = REAL_MAX, XMax = -REAL_MAX, YMin = REAL_MAX, YMax = -REAL_MAX;
|
|
|
|
// Language support for dialogs: German/English
|
|
// Please keep to alphabetic sorting for better maintainability !
|
|
string Dictionary[] = {
|
|
"en\v"
|
|
"de\v",
|
|
"Assembly Parameters:\v"
|
|
"Bestückungs-Parameter:\v",
|
|
"Board length (dimension Y):\v"
|
|
"Boardlänge (Y-Richtung):\v",
|
|
"Board name:\v"
|
|
"Boardname:\v",
|
|
"Board thickness:\v"
|
|
"Boarddicke:\v",
|
|
"Board width (dimension X):\v"
|
|
"Boardbreite (X-Richtung):\v",
|
|
"Board width and length could not be determined from dimension layer. \v"
|
|
"Boardbreite und -länge konnten nicht bestimmt werden anhand des Dimension-Layers. \v",
|
|
"Both Sides\v"
|
|
"beidseitig\v",
|
|
"Bottom Side\v"
|
|
"unten\v",
|
|
"Please use the dimension layer to design the board outline.\v"
|
|
"Bitte benutzen Sie den Dimension-Layer, um die Boardumrisse festzulegen.\v",
|
|
"Calculating quote parameters...\v"
|
|
"Berechne Angebots-Parameter...\v",
|
|
"Change Country:\v"
|
|
"Land wechseln:\v",
|
|
"Close\v"
|
|
"Schliessen\v",
|
|
"+Continue...\v"
|
|
"+Weiter...\v",
|
|
"Copper thickness outer layers:\v"
|
|
"Kupferstärke aussen:\v",
|
|
"Copper thickness inner layers:\v"
|
|
"Kupferstärke innen:\v",
|
|
"Estimate based on outer layers failed. \v"
|
|
"Schätzung basierend auf Aussen-Layer fehlgeschlagen. \v",
|
|
"Estimation based on outer layers. \v"
|
|
"Schätzung basierend auf Aussen-Layer. \v",
|
|
"Fabrication Parameters:\v"
|
|
"Fertigungs-Parameter:\v",
|
|
"Get PCB Quote\v"
|
|
"Zum PCB-Angebot\v",
|
|
"A quote for this board doesn't make sense yet !\v"
|
|
"Ein Angebot für dieses Board macht noch keinen Sinn !\v",
|
|
"If you want to get a quote anyway, please follow the link.\v"
|
|
"Wenn Sie dennoch ein Angebot erhalten wollen, folgen Sie bitte dem Link.\v",
|
|
"Information:\v"
|
|
"Hinweise:\v",
|
|
"Minimum hole size:\v"
|
|
"Kleinster Bohrdurchmesser:\v",
|
|
"Minimum SMD pitch:\v"
|
|
"Kleinster SMD-Abstand:\v",
|
|
"Minimum trace width (track width):\v"
|
|
"Kleinste Bahnbreite:\v",
|
|
"Minimum trace width could not be determined.\v"
|
|
"Minimale Bahnbreite konnte nicht bestimmt werden.\v",
|
|
"No copper found in the signal layers. \v"
|
|
"Kein Kupfer in den Signal-Layern vorhanden. \v",
|
|
"No\v"
|
|
"Nein\v",
|
|
"None\v"
|
|
"keine\v",
|
|
"Number of\v"
|
|
"Anzahl von\v",
|
|
"Number of blind or buried hole types:\v"
|
|
"Anzahl von Blind- oder Buried-Bohrungsarten:\v",
|
|
"Number of different packages:\v"
|
|
"Anzahl verschiedener Packages:\v",
|
|
"Number of fine pitch packages:\v"
|
|
"Anzahl von Fine-Pitch-Packages:\v",
|
|
"Number of other SMDs:\v"
|
|
"Anzahl sonstiger SMDs:\v",
|
|
"Number of SMD pads on bottom:\v"
|
|
"Anzahl von SMD-Pads unten:\v",
|
|
"Number of SMD pads on top:\v"
|
|
"Anzahl von SMD-Pads oben:\v",
|
|
"Number of thru hole packages: \v"
|
|
"Anzahl von durchkontaktierten Packages: \v",
|
|
"Number of layers:\v"
|
|
"Anzahl Layer:\v",
|
|
"Wire(s) and/or polygon(s) with zero width found on signal layer(s). \v"
|
|
"Bahnen und/oder Polygone mit Breite 0 in Signal-Layer(n) gefunden. \v",
|
|
"None of the signal layers are used.\v"
|
|
"Keines der Signal-Layer wird verwendet.\v",
|
|
"Parameters:\v"
|
|
"Parameter:\v",
|
|
"PCB Quote Service\v"
|
|
"PCB-Angebots-Service\v",
|
|
"Please start from Layout editor !\v"
|
|
"Bitte starten Sie vom Layout-Editor aus !\v",
|
|
"Silkscreen sides:\v"
|
|
"Bestückungsdruck-Seiten:\v",
|
|
"SMDs on both sides:\v"
|
|
"SMDs beidseitig:\v",
|
|
"Solder sides:\v"
|
|
"Lötseiten:\v",
|
|
"There are still airwires left.\v"
|
|
"Es sind noch Luftlinien vorhanden.\v",
|
|
"Top Side\v"
|
|
"oben\v",
|
|
"undefined\v"
|
|
"unbestimmt\v",
|
|
"Warnings:\v"
|
|
"Warnungen:\v",
|
|
"Yes\v"
|
|
"Ja\v",
|
|
"Your board layout is incomplete:\v"
|
|
"Ihr Board-Layout ist unvollständig:\v"
|
|
};
|
|
|
|
string DlgLang = language();
|
|
if (DlgLang != "de") DlgLang = "en";
|
|
int LangIdx = strstr(Dictionary[0], DlgLang) / 3;
|
|
|
|
// Translate, based on dictionary
|
|
string tr(string s) {
|
|
string t = lookup(Dictionary, s, LangIdx, '\v');
|
|
return t ? t : s;
|
|
}
|
|
|
|
string Unknown = tr("undefined");
|
|
|
|
//- Auxiliary functions -------------------------------------------------------
|
|
|
|
string GetTargetURL() {
|
|
return "http://www.element14.com/community/community/knode/pcb_services";
|
|
}
|
|
|
|
// String handling
|
|
string I2Str(int i) { string str; sprintf(str, "%d", i); return str; }
|
|
string B2Str(int b) { return b ? "true" : "false"; }
|
|
|
|
// HTML stuff
|
|
string LItem(string txt) { return "<li>" + txt + "</li>"; }
|
|
string Bold(string txt) { return "<b>" + txt + "</b>"; }
|
|
|
|
// Unit handling
|
|
string U2MmStr(int i) { string str; sprintf(str, "%f", u2mm(i)); return str; }
|
|
string U2InStr(int i) { string str; sprintf(str, "%f", u2inch(i)); return str; }
|
|
string U2OzStr(int i) { string str; sprintf(str, "%f", i/(350.0 * UFactor)); return str; }// Todo: Check this !
|
|
// Unit to preferred unit as String with extension !
|
|
string U2PrefUStrExt(int i) { return (PrefUnit == GRID_UNIT_MM) ? U2MmStr(i) + " mm" : U2InStr(i) + " in"; }
|
|
|
|
int Str2U(string s) {
|
|
if (strstr(s, "mm") > 0) return int(strtod(s) * 10000 * UFactor);
|
|
if (strstr(s, "mil") > 0) return int(strtod(s) * 10 * UFactor * 25.4);
|
|
if (strstr(s, "in") > 0) return int(strtod(s) * 10000 * UFactor * 25.4);
|
|
if (strstr(s, "mic") > 0) return int(strtod(s) * 10 * UFactor);
|
|
dlgMessageBox("Unknown unit in design rules !"); exit(EXIT_FAILURE);
|
|
return 0;
|
|
}
|
|
|
|
//-------------------------------------------------------
|
|
// Get PCB parameters
|
|
//-------------------------------------------------------
|
|
|
|
// Means really within, not at the borders
|
|
int Within(int val, int l, int u) { return (val > l) && (val < u); }
|
|
|
|
int ValidBBox() {
|
|
return (XMin < REAL_MAX) && (XMax > -REAL_MAX) &&
|
|
(YMin < REAL_MAX) && (YMax > -REAL_MAX) &&
|
|
(XMax - XMin > 1000) && (YMax - YMin > 1000); // Could make this much bigger, but that's not crucial (currently 0.1 mm)
|
|
}
|
|
|
|
/*
|
|
int CheckIfBGA(UL_PACKAGE LBRPAC) {
|
|
// board(B) B.libraries(L) L.packages(PAC)
|
|
// if (PAC.name == pacName) {
|
|
int i;
|
|
LBRPAC.contacts(C) {
|
|
LbrPacConXs[i] = C.x;
|
|
LbrPacConYs[i++] = C.y;
|
|
// Get grid dimensions
|
|
ConXMin = min(ConXMin, C.x);
|
|
conXMax = max(conXMax, C.x);
|
|
ConYMin = min(ConYMin, C.y);
|
|
conYMax = max(conYMax, C.y);
|
|
if (Dbg) { sprintf(DbgMsg, "LbrPac: (%f %f)\n", u2mm(LbrPacConXs[i-1]), u2mm(LbrPacConYs[i-1])); DbgTxt += DbgMsg; }
|
|
if (C.smd) {
|
|
ConDiam = C.smd.dx;
|
|
// All dx/dy must be equal
|
|
conDiamMin = min(conDiamMin, C.smd.dx);
|
|
conDiamMin = min(conDiamMin, C.smd.dy);
|
|
conDiamMax = max(conDiamMax, C.smd.dx);
|
|
conDiamMax = max(conDiamMax, C.smd.dy);
|
|
if (C.smd.roundness - 100 > roundTol) errRound = true;
|
|
}
|
|
else {
|
|
errType = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
|
|
// Update bounding box with another box
|
|
void UpdateBBoxBox(int xmin, int ymin, int xmax, int ymax) {
|
|
XMin = min(XMin, xmin);
|
|
XMax = max(XMax, xmax);
|
|
YMin = min(YMin, ymin);
|
|
YMax = max(YMax, ymax);
|
|
}
|
|
|
|
void UpdateBBoxWire(UL_WIRE W, int layer) {
|
|
if (W.layer == layer) {
|
|
real w2 = W.width/2;
|
|
real xmin = min(W.x1 - w2, W.x2 - w2), ymin = min(W.y1 - w2, W.y2 - w2);
|
|
real xmax = max(W.x1 + w2, W.x2 + w2), ymax = max(W.y1 + w2, W.y2 + w2);
|
|
if (W.arc) {
|
|
if (W.arc.angle2 > 360)
|
|
xmax = W.arc.xc + W.arc.radius + w2;
|
|
if (((W.arc.angle1 < 90) && (W.arc.angle2 > 90)) || (W.arc.angle2 > 450))
|
|
ymax = W.arc.yc + W.arc.radius + w2;
|
|
if (((W.arc.angle1 < 180) && (W.arc.angle2 > 180)) || (W.arc.angle2 > 540))
|
|
xmin = W.arc.xc - W.arc.radius - w2;
|
|
if (((W.arc.angle1 < 270) && (W.arc.angle2 > 270)) || (W.arc.angle2 > 630))
|
|
ymin = W.arc.yc - W.arc.radius - w2;
|
|
}
|
|
UpdateBBoxBox(xmin, ymin, xmax, ymax);
|
|
}
|
|
}
|
|
|
|
void UpdateBBoxCircle(UL_CIRCLE C, int layer) {
|
|
if (C.layer == layer) {
|
|
real w2 = C.width / 2;
|
|
UpdateBBoxBox(C.x - C.radius - w2, C.y - C.radius - w2, C.x + C.radius + w2, C.y + C.radius + w2);
|
|
}
|
|
}
|
|
|
|
void UpdateBBox(int layer, int el_origin) {
|
|
board(B) {
|
|
B.wires(W) UpdateBBoxWire(W, layer);
|
|
B.circles(C) UpdateBBoxCircle(C, layer);
|
|
B.elements(E) {
|
|
if (el_origin) UpdateBBoxBox(E.x, E.y, E.x, E.y); // Elements are always on signal layers.
|
|
E.package.wires(W) UpdateBBoxWire(W, layer);
|
|
E.package.circles(C) UpdateBBoxCircle(C, layer);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GetDruParams() {
|
|
string lines[];
|
|
int nrlines = fileread(lines, DruFile);
|
|
if (nrlines == 0) dlgMessageBox("Error reading design rules !");
|
|
int layer_used[], nrlayers, bottom_idx;
|
|
|
|
for (int i = 0; i < nrlines; ++i) {
|
|
string words[];
|
|
int nrwords = strsplit(words, lines[i], ' ');
|
|
if (nrwords < 3) continue; // Empty line or some other stuff
|
|
string keyword = words[0];
|
|
if (keyword == "layerSetup")
|
|
for (int k = 1; k <= 16; ++k) {
|
|
string nr_pattern;
|
|
sprintf(nr_pattern, "[\\D]%d[\\D]", k); // Number, surrounded by 'no number character'
|
|
if (strxstr(words[2], nr_pattern) != -1) {
|
|
layer_used[k - 1] = true;
|
|
++NrLayers;
|
|
bottom_idx = k - 1;
|
|
}
|
|
}
|
|
else if (keyword == "mtCopper") {
|
|
if (nrwords != 18) dlgMessageBox("Unexpected design rules !");
|
|
int top_thickness = -1, bot_thickness;
|
|
for (int k = 2; k < nrwords; ++k) {
|
|
if (layer_used[k - 2]) {
|
|
int thickness = Str2U(words[k]);
|
|
if (top_thickness == -1)
|
|
top_thickness = thickness;
|
|
else if (k - 2 == bottom_idx)
|
|
bot_thickness = thickness;
|
|
else
|
|
CopperThicknessInside = min(thickness, CopperThicknessInside);
|
|
MaterialThickness += thickness;
|
|
}
|
|
}
|
|
CopperThicknessOutside = min(top_thickness, bot_thickness);
|
|
}
|
|
else if (keyword == "mtIsolate") {
|
|
if (nrwords != 17) dlgMessageBox("Unexpected design rules !");
|
|
// n used layers => n-1 isolate layers. If only 1 layer, also 1 isolate layer.
|
|
for (int k = 2; k < nrwords; ++k)
|
|
if (layer_used[k - 2])
|
|
MaterialThickness += Str2U(words[k]);
|
|
}
|
|
else if (keyword == "mdSmdSmd")
|
|
MinPitch = Str2U(words[2]);
|
|
}
|
|
}
|
|
|
|
void CalcMinTraceWidth() {
|
|
board(B) {
|
|
B.wires(W) if (W.layer <= 16) MinTraceWidth = min(W.width, MinTraceWidth);
|
|
B.signals(S) {
|
|
S.polygons(P) if (P.layer <= 16) MinTraceWidth = min(P.width, MinTraceWidth);
|
|
S.wires(W) if (W.layer <= 16) MinTraceWidth = min(W.width, MinTraceWidth);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckHoles() {
|
|
int idx[], start[], end[], k;
|
|
board(B) {
|
|
B.holes(H) MinHoleSize = min(MinHoleSize, H.drill);
|
|
B.signals(S) S.vias(V) {
|
|
MinHoleSize = min(MinHoleSize, V.drill);
|
|
// Collect blind/buried holes !
|
|
if ((V.start > 1) || (V.end < 16)) {
|
|
start[k] = V.start;
|
|
end[k] = V.end;
|
|
++k;
|
|
}
|
|
}
|
|
B.elements(E) {
|
|
E.package.contacts(C) if (C.pad)
|
|
MinHoleSize = min(MinHoleSize, C.pad.drill);
|
|
E.package.holes(H) MinHoleSize = min(MinHoleSize, H.drill);
|
|
}
|
|
}
|
|
// NrBlindBuriedTypes: Find out how many different ones exist !
|
|
sort(k, idx, start, end);
|
|
for (int l = 0, cur_s = 0, cur_e = 0; l < k; ++l)
|
|
if ((start[idx[l]] != cur_s) || (end[idx[l]] != cur_e)) {
|
|
++NrBlindBuriedTypes;
|
|
cur_s = start[idx[l]];
|
|
cur_e = end[idx[l]];
|
|
}
|
|
}
|
|
|
|
void GetPCBParams() {
|
|
// Check out layers:
|
|
int smask_top, smask_bottom, silk_top, silk_bottom;
|
|
int nr_used_layers;
|
|
board(B) {
|
|
BoardName = filename(B.name);
|
|
B.layers(L) if (L.used)
|
|
if (L.number <= 16) ++nr_used_layers;
|
|
else if (L.number == LAYER_TSTOP) smask_top = 1;
|
|
else if (L.number == LAYER_BSTOP) smask_bottom = 1;
|
|
else if (L.number == LAYER_TPLACE) silk_top = 1; // Could also take TNAMES/TVALUES. Leave the criterium less strict
|
|
else if (L.number == LAYER_BPLACE) silk_bottom = 1; // Could also take BNAMES/BVALUES. Leave the criterium less strict
|
|
}
|
|
SolderSides = smask_top ? (smask_bottom ? "Both Sides" : "Top Side") :
|
|
(smask_bottom ? "Bottom Side" : "None");
|
|
SilkScreenSides = (silk_top) ? (silk_bottom ? "Both Sides" : "Top Side") :
|
|
(silk_bottom ? "Bottom Side" : "None");
|
|
if (nr_used_layers == 0) {
|
|
Warnings += LItem(tr("None of the signal layers are used."));
|
|
Useless = 1;
|
|
}
|
|
|
|
// Board outlines:
|
|
UpdateBBox(LAYER_DIMENSION, 0);
|
|
if (!ValidBBox()) {
|
|
Warnings += "<li>" + tr("Board width and length could not be determined from dimension layer. ");
|
|
UpdateBBox(LAYER_TOP, 1);
|
|
UpdateBBox(LAYER_BOTTOM, 1);
|
|
if (!ValidBBox())
|
|
Warnings += tr("Estimate based on outer layers failed. ") +
|
|
tr("Please use the dimension layer to design the board outline.") + "</li>";
|
|
else
|
|
Warnings += tr("Estimation based on outer layers. ") + "</li>";
|
|
}
|
|
|
|
CalcMinTraceWidth();
|
|
if (MinTraceWidth == INT_MAX)
|
|
Warnings += LItem(tr("No copper found in the signal layers. ") +
|
|
tr("Minimum trace width could not be determined."));
|
|
if (MinTraceWidth == 0)
|
|
Warnings += LItem(tr("Wire(s) and/or polygon(s) with zero width found on signal layer(s). ") +
|
|
tr("Minimum trace width could not be determined."));
|
|
GetDruParams();
|
|
CheckHoles();
|
|
int air_wires = 0;
|
|
{ // Check if there are still airwires:
|
|
board(B) {
|
|
B.wires(W) if (W.layer == LAYER_UNROUTED) air_wires = 1;
|
|
B.signals(S) {
|
|
S.polygons(P) if (P.layer == LAYER_UNROUTED) air_wires = 1;
|
|
S.wires(W) if (W.layer == LAYER_UNROUTED) air_wires = 1;
|
|
}
|
|
}
|
|
}
|
|
if (air_wires)
|
|
Warnings += LItem(tr("There are still airwires left."));
|
|
|
|
// Analysis of elements
|
|
// Alternative idea for performance optimization:
|
|
// Run SMD analysis over all library packages to avoid multiple runs
|
|
// Keep results in Arrays
|
|
string keyBGA = "BGA", keyQFN = "QFN";
|
|
real sqLimitFinePitch = 0.5 * 0.5; // Square of the threshold for Fine Pitch (in mm)
|
|
board(B) B.elements(E) {
|
|
int isThruHole, isSMD;
|
|
E.package.contacts(C)
|
|
if (C.smd) {
|
|
isSMD = true;
|
|
if (C.smd.layer == LAYER_TOP) ++NrSmdPadsTop;
|
|
else ++NrSmdPadsBottom;
|
|
}
|
|
else
|
|
isThruHole = true; // Assuming there are no mixed versions
|
|
if (isThruHole) ++NrThruHoles;
|
|
else if (isSMD) { // There might also be the case of NO contacts !
|
|
// Collect contacts in untransformed state to make geometrical checks !
|
|
real conXs[], conYs[];
|
|
int nrCons;
|
|
B.libraries(L)
|
|
if (L.name == E.package.library) L.packages(P)
|
|
if (P.name == E.package.name) P.contacts(C) {
|
|
conXs[nrCons] = u2mm(C.x);
|
|
conYs[nrCons++] = u2mm(C.y);
|
|
}
|
|
if (strstr(E.name, keyBGA) >= 0 ||
|
|
strstr(E.package.name, keyBGA) >= 0 ||
|
|
strstr(E.package.description, keyBGA) >= 0) {
|
|
// Plausibility check
|
|
++NrBGAs;
|
|
}
|
|
else if (strstr(E.name, keyQFN) >= 0 ||
|
|
strstr(E.package.name, keyQFN) >= 0 ||
|
|
strstr(E.package.description, keyQFN) >= 0) {
|
|
// Plausibility check
|
|
++NrQFNs;
|
|
}
|
|
else {
|
|
// if there's a contact pair with distance <= fine pitch limit it's a FINE Pitch, else it's an other SMD
|
|
for (int isFP = false, i = 0; i < nrCons && !isFP; ++i)
|
|
for (int j = i + 1; j < nrCons && !isFP; ++j)
|
|
if ((conXs[j] - conXs[i]) * (conXs[j] - conXs[i]) +
|
|
(conYs[j] - conYs[i]) * (conYs[j] - conYs[i]) <= sqLimitFinePitch)
|
|
isFP = true;
|
|
if (isFP)
|
|
++NrFinePitchs;
|
|
else
|
|
++NrOtherSMDs;
|
|
}
|
|
}
|
|
}
|
|
HasSMDBothSides = NrSmdPadsTop * NrSmdPadsBottom > 0;
|
|
|
|
// Packages (number of unique elements)
|
|
// Only those with contacts !
|
|
board(B) B.libraries(L) L.packages(P) P.contacts(C) {
|
|
++NrPackages;
|
|
break;
|
|
}
|
|
}
|
|
|
|
string MakeParamSendString() {
|
|
// Fab parameters:
|
|
// - Parameters that always can be determined:
|
|
string send = "?source=SBPCAD" +
|
|
"&boardName=" + BoardName +
|
|
"&numLayers=" + I2Str(NrLayers) +
|
|
"&materialThicknessMm=" + U2MmStr(MaterialThickness) +
|
|
"&materialThicknessIn=" + U2InStr(MaterialThickness) +
|
|
"&copperThicknessOutsideMm=" + U2MmStr(CopperThicknessOutside) +
|
|
"&copperThicknessOutsideOz=" + U2OzStr(CopperThicknessOutside) +
|
|
"&solderSides=" + SolderSides +
|
|
"&silkScreenSides=" + SilkScreenSides +
|
|
"&blindBuriedHoles=" + I2Str(NrBlindBuriedTypes) +
|
|
"&numPadsTop=" + I2Str(NrSmdPadsTop) +
|
|
"&numPadsBottom=" + I2Str(NrSmdPadsBottom);
|
|
// - Parameters that might miss:
|
|
if (ValidBBox())
|
|
send += "&boardWidthMm=" + U2MmStr(XMax - XMin) +
|
|
"&boardWidthIn=" + U2InStr(XMax - XMin) +
|
|
"&boardLengthMm=" + U2MmStr(YMax - YMin) +
|
|
"&boardLengthIn=" + U2InStr(YMax - YMin);
|
|
if (Within(CopperThicknessInside, 0, INT_MAX))
|
|
send += "&copperThicknessInsideMm=" + U2MmStr(CopperThicknessInside) +
|
|
"&copperThicknessInsideOz=" + U2OzStr(CopperThicknessInside);
|
|
if (Within(MinTraceWidth, 0, INT_MAX))
|
|
send += "&minTraceWidthMm=" + U2MmStr(MinTraceWidth) +
|
|
"&minTraceWidthIn=" + U2InStr(MinTraceWidth);
|
|
if (MinPitch < INT_MAX)
|
|
send += "&minPitchMm=" + U2MmStr(MinPitch) +
|
|
"&minPitchIn=" + U2InStr(MinPitch);
|
|
if (MinHoleSize < INT_MAX)
|
|
send += "&minHoleSizeMm=" + U2MmStr(MinHoleSize) +
|
|
"&minHoleSizeIn=" + U2InStr(MinHoleSize);
|
|
// Assy parameters:
|
|
send += "&numPackages=" + I2Str(NrPackages) +
|
|
"&numBGAs=" + I2Str(NrBGAs) +
|
|
"&numQFNs=" + I2Str(NrQFNs) +
|
|
"&numFinePitch=" + I2Str(NrFinePitchs) +
|
|
"&numOtherSMDs=" + I2Str(NrOtherSMDs) +
|
|
"&numThruHoles=" + I2Str(NrThruHoles) +
|
|
"&hasSMDBothSides=" + B2Str(HasSMDBothSides);
|
|
|
|
if (Dbg) output(DbgFile, "wba") printf("%s\n%s\n", GetTargetURL(), send);
|
|
return send;
|
|
}
|
|
|
|
int Dialog() {
|
|
string Info[] = {
|
|
"Based on your board layout and the design rules, key parameters for manufacturing "
|
|
"your board like board size, minimum hole size etc. are determined. Your design "
|
|
"should be complete and have passed a DRC successfully.<br>"
|
|
"By following the link below you get to the PCB quote site on Element14 where these "
|
|
"parameters are transferred. This way with a few steps you get a quote for manufacturing "
|
|
"your board."
|
|
,
|
|
"Basierend auf Ihr Board-Layout und den Designregeln werden wichtige Parameter zur Fertigung "
|
|
"des Boards ermittelt wie zum Beispiel Boardgrösse, kleinster Bohrdurchmesser usw. "
|
|
"Ihr Design sollte möglichst vollständig und ein erfolgreicher DRC durchgeführt worden sein.<br>"
|
|
"Durch Klicken des Links unten gelangen Sie zur PCB-Angebotsseite auf Element14, wo diese "
|
|
"Parameter übernommen werden. Mit wenigen Schritten erhalten Sie so ein Angebot zur Fertigung "
|
|
"Ihres Boards."
|
|
};
|
|
string url = "<a href=\"" + GetTargetURL() + MakeParamSendString() + "\">";
|
|
return dlgDialog(tr("PCB Quote Service (Version " + I2Str(Version) + ")")) {
|
|
dlgHBoxLayout dlgSpacing(470);
|
|
dlgGroup(tr("Information:"))
|
|
dlgLabel(Info[LangIdx], 1);
|
|
if (Warnings)
|
|
dlgHBoxLayout
|
|
dlgGroup(tr("Warnings:"))
|
|
dlgLabel(Warnings);
|
|
if (!Useless) {
|
|
dlgGroup(tr("Fabrication Parameters:"))
|
|
dlgGridLayout {
|
|
int Row;
|
|
dlgCell(Row, 0) dlgLabel(tr("Number of layers:")); dlgCell(Row++, 1) dlgLabel(Bold(I2Str(NrLayers)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Board name:")); dlgCell(Row++, 1) dlgLabel(Bold(BoardName));
|
|
dlgCell(Row, 0) dlgLabel(tr("Board width (dimension X):")); dlgCell(Row++, 1) dlgLabel(Bold(ValidBBox() ? U2PrefUStrExt(XMax - XMin) : Unknown));
|
|
dlgCell(Row, 0) dlgLabel(tr("Board length (dimension Y):")); dlgCell(Row++, 1) dlgLabel(Bold(ValidBBox() ? U2PrefUStrExt(YMax - YMin) : Unknown));
|
|
dlgCell(Row, 0) dlgLabel(tr("Board thickness:")); dlgCell(Row++, 1) dlgLabel(Bold(U2PrefUStrExt(MaterialThickness)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Copper thickness outer layers:")); dlgCell(Row++, 1) dlgLabel(Bold(U2PrefUStrExt(CopperThicknessOutside)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Copper thickness inner layers:")); dlgCell(Row++, 1) dlgLabel(Bold(Within(CopperThicknessInside, 0, INT_MAX) ? U2PrefUStrExt(CopperThicknessInside) : Unknown));
|
|
dlgCell(Row, 0) dlgLabel(tr("Solder sides:")); dlgCell(Row++, 1) dlgLabel(Bold(tr(SolderSides)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Silkscreen sides:")); dlgCell(Row++, 1) dlgLabel(Bold(tr(SilkScreenSides)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Number of SMD pads on top:")); dlgCell(Row++, 1) dlgLabel(Bold(I2Str(NrSmdPadsTop)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Number of SMD pads on bottom:")); dlgCell(Row++, 1) dlgLabel(Bold(I2Str(NrSmdPadsBottom)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Number of blind or buried hole types:")); dlgCell(Row++, 1) dlgLabel(Bold(I2Str(NrBlindBuriedTypes)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Minimum trace width (track width):")); dlgCell(Row++, 1) dlgLabel(Bold(Within(MinTraceWidth, 0, INT_MAX) ? U2PrefUStrExt(MinTraceWidth) : Unknown));
|
|
dlgCell(Row, 0) dlgLabel(tr("Minimum SMD pitch:")); dlgCell(Row++, 1) dlgLabel(Bold(MinPitch < INT_MAX ? U2PrefUStrExt(MinPitch) : Unknown));
|
|
dlgCell(Row, 0) dlgLabel(tr("Minimum hole size:")); dlgCell(Row++, 1) dlgLabel(Bold(MinHoleSize < INT_MAX ? U2PrefUStrExt(MinHoleSize) : Unknown));
|
|
}
|
|
dlgGroup(tr("Assembly Parameters:"))
|
|
dlgGridLayout {
|
|
int Row;
|
|
dlgCell(Row, 0) dlgLabel(tr("Number of different packages:")); dlgCell(Row++, 1) dlgLabel(Bold(I2Str(NrPackages)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Number of") + " BGAs:"); dlgCell(Row++, 1) dlgLabel(Bold(I2Str(NrBGAs)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Number of") + " QFNs:"); dlgCell(Row++, 1) dlgLabel(Bold(I2Str(NrQFNs)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Number of fine pitch packages:")); dlgCell(Row++, 1) dlgLabel(Bold(I2Str(NrFinePitchs)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Number of other SMDs:")); dlgCell(Row++, 1) dlgLabel(Bold(I2Str(NrOtherSMDs)));
|
|
dlgCell(Row, 0) dlgLabel(tr("Number of thru hole packages: ")); dlgCell(Row++, 1) dlgLabel(Bold(I2Str(NrThruHoles)));
|
|
dlgCell(Row, 0) dlgLabel(tr("SMDs on both sides:")); dlgCell(Row++, 1) dlgLabel(Bold(HasSMDBothSides ? tr("Yes") : tr("No")));
|
|
}
|
|
}
|
|
dlgHBoxLayout {
|
|
dlgStretch(1);
|
|
if (!Useless)
|
|
dlgLabel("<b>" + url + tr("Get PCB Quote") + "</b>", 1);
|
|
dlgStretch(1);
|
|
dlgPushButton(tr("Close")) dlgReject();
|
|
}
|
|
};
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// main section
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Parse arguments
|
|
for (int i = 1; i <= argc; ++i) {
|
|
if (argv[i] == "-noexp") Export = 0;
|
|
}
|
|
|
|
if (board) {
|
|
string dir = filedir(argv[0]);
|
|
board(B) DruFile = dir + "dl-" + filename(filesetext(B.name, ".dru"));
|
|
if (Export)
|
|
exit("DRC SAVE '" + DruFile + "';\nRUN '" + argv[0] + "' -noexp;");
|
|
if (Dbg) board(B) {
|
|
DbgFile = filedir(B.name) + "/dbg-pcb-service.txt";
|
|
output(DbgFile, "wba") printf("\nBoard: %s\n", filename(B.name));
|
|
}
|
|
status(tr("Calculating quote parameters..."));
|
|
GetPCBParams();
|
|
if (Warnings) {
|
|
string tmp = tr("Your board layout is incomplete:") + "<ul>" + Warnings + "</ul>";
|
|
if (Useless) tmp += tr("A quote for this board doesn't make sense yet !");
|
|
else tmp += tr("If you want to get a quote anyway, please follow the link.");
|
|
Warnings = tmp;
|
|
}
|
|
if (Dbg && Warnings) output(DbgFile, "wba") printf("Warnings: \n%s\n", Warnings);
|
|
board(B) {
|
|
int grU = B.grid.unit;
|
|
PrefUnit = (grU == GRID_UNIT_MM || grU == GRID_UNIT_MIC) ? GRID_UNIT_MM : GRID_UNIT_INCH;
|
|
}
|
|
while (Dialog() == 1);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
else
|
|
dlgMessageBox(tr("Please start from board editor !"));
|