1 /*******************************************************************
2
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2014 Fachhochschule Potsdam - http://fh-potsdam.de
5
6 Fritzing is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Fritzing is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Fritzing. If not, see <http://www.gnu.org/licenses/>.
18
19 ********************************************************************
20
21 $Revision: 6904 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-02-26 16:26:03 +0100 (Di, 26. Feb 2013) $
24
25 ********************************************************************/
26
27 #include "kicadmodule2svg.h"
28 #include "../utils/textutils.h"
29 #include "../debugdialog.h"
30 #include "../viewlayer.h"
31 #include "../fsvgrenderer.h"
32
33 #include <QFile>
34 #include <QFileInfo>
35 #include <QTextStream>
36 #include <QObject>
37 #include <QDomDocument>
38 #include <QDomElement>
39 #include <QDateTime>
40 #include <qmath.h>
41 #include <limits>
42
43 #define KicadSilkscreenTop 21
44 #define KicadSilkscreenBottom 20
45
46 // TODO:
47 // non-centered drill holes?
48 // trapezoidal pads (shape delta may or may not be a separate issue)?
49 // non-copper holes?
50 // find true bounding box of arcs instead of using the whole circle
51
checkStrokeWidth(double w)52 double checkStrokeWidth(double w) {
53 if (w >= 0) return w;
54
55 DebugDialog::debug("stroke width < 0");
56 return 0;
57 }
58
KicadModule2Svg()59 KicadModule2Svg::KicadModule2Svg() : Kicad2Svg() {
60 }
61
listModules(const QString & filename)62 QStringList KicadModule2Svg::listModules(const QString & filename) {
63 QStringList modules;
64
65 QFile file(filename);
66 if (!file.open(QFile::ReadOnly)) return modules;
67
68 QTextStream textStream(&file);
69 bool gotIndex = false;
70 while (true) {
71 QString line = textStream.readLine();
72 if (line.isNull()) break;
73
74 if (line.compare("$INDEX") == 0) {
75 gotIndex = true;
76 break;
77 }
78 }
79
80 if (!gotIndex) return modules;
81 while (true) {
82 QString line = textStream.readLine();
83 if (line.isNull()) break;
84
85 if (line.compare("$EndINDEX") == 0) {
86 return modules;
87 }
88
89 modules.append(line);
90 }
91
92 modules.clear();
93 return modules;
94 }
95
convert(const QString & filename,const QString & moduleName,bool allowPadsAndPins)96 QString KicadModule2Svg::convert(const QString & filename, const QString & moduleName, bool allowPadsAndPins)
97 {
98 m_nonConnectorNumber = 0;
99 initLimits();
100
101 QFile file(filename);
102 if (!file.open(QFile::ReadOnly)) {
103 throw QObject::tr("unable to open %1").arg(filename);
104 }
105
106 QString text;
107 QTextStream textStream(&file);
108
109 QString metadata = makeMetadata(filename, "module", moduleName);
110
111
112 bool gotModule = false;
113 while (true) {
114 QString line = textStream.readLine();
115 if (line.isNull()) {
116 break;
117 }
118
119 if (line.contains("$MODULE") && line.contains(moduleName, Qt::CaseInsensitive)) {
120 gotModule = true;
121 break;
122 }
123 }
124
125 if (!gotModule) {
126 throw QObject::tr("footprint %1 not found in %2").arg(moduleName).arg(filename);
127 }
128
129 bool gotT0;
130 QString line;
131 while (true) {
132 line = textStream.readLine();
133 if (line.isNull()) {
134 throw QObject::tr("unexpected end of file in footprint %1 in file %2").arg(moduleName).arg(filename);
135 }
136
137 if (line.startsWith("T0") || line.startsWith("DS") || line.startsWith("DA") || line.startsWith("DC")) {
138 gotT0 = true;
139 break;
140 }
141 else if (line.startsWith("Cd")) {
142 metadata += m_comment.arg(TextUtils::stripNonValidXMLCharacters(TextUtils::escapeAnd(line.remove(0,3))));
143 }
144 else if (line.startsWith("Kw")) {
145 QStringList keywords = line.split(" ");
146 for (int i = 1; i < keywords.count(); i++) {
147 metadata += m_attribute.arg("keyword").arg(TextUtils::stripNonValidXMLCharacters(TextUtils::escapeAnd(keywords[i])));
148 }
149 }
150 }
151
152 metadata += endMetadata();
153
154 if (!gotT0) {
155 throw QObject::tr("unexpected format (1) in %1 from %2").arg(moduleName).arg(filename);
156 }
157
158 while (line.startsWith("T")) {
159 line = textStream.readLine();
160 if (line.isNull()) {
161 throw QObject::tr("unexpected end of file in footprint %1 in file %2").arg(moduleName).arg(filename);
162 }
163 }
164
165 bool done = false;
166 QString copper0;
167 QString copper1;
168 QString silkscreen0;
169 QString silkscreen1;
170
171 while (true) {
172 if (line.startsWith("$PAD")) break;
173 if (line.startsWith("$EndMODULE")) {
174 done = true;
175 break;
176 }
177
178 int layer = 0;
179 QString svgElement;
180 if (line.startsWith("DS")) {
181 layer = drawDSegment(line, svgElement);
182 }
183 else if (line.startsWith("DA")) {
184 layer = drawDArc(line, svgElement);
185 }
186 else if (line.startsWith("DC")) {
187 layer = drawDCircle(line, svgElement);
188 }
189 switch (layer) {
190 case KicadSilkscreenTop:
191 silkscreen1 += svgElement;
192 break;
193 case KicadSilkscreenBottom:
194 silkscreen0 += svgElement;
195 break;
196 default:
197 break;
198 }
199
200 line = textStream.readLine();
201 if (line.isNull()) {
202 throw QObject::tr("unexpected end of file in footprint %1 in file %2").arg(moduleName).arg(filename);
203 }
204 }
205
206 if (!done) {
207 QList<int> numbers;
208 for (int i = 0; i < 512; i++) {
209 numbers << i;
210 }
211 int pads = 0;
212 int pins = 0;
213 while (!done) {
214 try {
215 QString pad;
216 PadLayer padLayer = convertPad(textStream, pad, numbers);
217 switch (padLayer) {
218 case ToCopper0:
219 copper0 += pad;
220 pins++;
221 break;
222 case ToCopper1:
223 copper1 += pad;
224 pads++;
225 break;
226 default:
227 break;
228 }
229 }
230 catch (const QString & msg) {
231 DebugDialog::debug(QString("kicad pad %1 conversion failed in %2: %3").arg(moduleName).arg(filename).arg(msg));
232 }
233
234 while (true) {
235 line = textStream.readLine();
236 if (line.isNull()) {
237 throw QObject::tr("unexpected end of file in footprint %1 in file %2").arg(moduleName).arg(filename);
238 }
239
240 if (line.contains("$SHAPE3D")) {
241 done = true;
242 break;
243 }
244 if (line.contains("$EndMODULE")) {
245 done = true;
246 break;
247 }
248 if (line.contains("$PAD")) {
249 break;
250 }
251 }
252 }
253
254 if (!allowPadsAndPins && pins > 0 && pads > 0) {
255 throw QObject::tr("Sorry, Fritzing can't yet handle both pins and pads together (in %1 in %2)").arg(moduleName).arg(filename);
256 }
257
258 }
259
260 if (!copper0.isEmpty()) {
261 copper0 = offsetMin("\n<g id='copper0'><g id='copper1'>" + copper0 + "</g></g>\n");
262 }
263 if (!copper1.isEmpty()) {
264 copper1 = offsetMin("\n<g id='copper1'>" + copper1 + "</g>\n");
265 }
266 if (!silkscreen1.isEmpty()) {
267 silkscreen1 = offsetMin("\n<g id='silkscreen'>" + silkscreen1 + "</g>\n");
268 }
269 if (!silkscreen0.isEmpty()) {
270 silkscreen0 = offsetMin("\n<g id='silkscreen0'>" + silkscreen0 + "</g>\n");
271 }
272
273 QString svg = TextUtils::makeSVGHeader(10000, 10000, m_maxX - m_minX, m_maxY - m_minY)
274 + m_title + m_description + metadata + copper0 + copper1 + silkscreen0 + silkscreen1 + "</svg>";
275
276 return svg;
277 }
278
drawDCircle(const QString & ds,QString & circle)279 int KicadModule2Svg::drawDCircle(const QString & ds, QString & circle) {
280 // DC Xcentre Ycentre Xend Yend width layer
281 QStringList params = ds.split(" ");
282 if (params.count() < 7) return -1;
283
284 int cx = params.at(1).toInt();
285 int cy = params.at(2).toInt();
286 int x2 = params.at(3).toInt();
287 int y2 = params.at(4).toInt();
288 double radius = qSqrt((cx - x2) * (cx - x2) + (cy - y2) * (cy - y2));
289
290 int w = params.at(5).toInt();
291 double halfWidth = w / 2.0;
292
293 checkXLimit(cx + radius + halfWidth);
294 checkXLimit(cx - radius - halfWidth);
295 checkYLimit(cy + radius + halfWidth);
296 checkYLimit(cy - radius - halfWidth);
297
298
299 int layer = params.at(6).toInt();
300
301 circle = QString("<circle cx='%1' cy='%2' r='%3' stroke-width='%4' stroke='white' fill='none' />")
302 .arg(cx)
303 .arg(cy)
304 .arg(radius)
305 .arg(checkStrokeWidth(w));
306
307 return layer;
308 }
309
drawDSegment(const QString & ds,QString & line)310 int KicadModule2Svg::drawDSegment(const QString & ds, QString & line) {
311 // DS Xstart Ystart Xend Yend Width Layer
312 QStringList params = ds.split(" ");
313 if (params.count() < 7) return -1;
314
315 int x1 = params.at(1).toInt();
316 int y1 = params.at(2).toInt();
317 int x2 = params.at(3).toInt();
318 int y2 = params.at(4).toInt();
319 checkXLimit(x1);
320 checkXLimit(x2);
321 checkYLimit(y1);
322 checkYLimit(y2);
323
324 int layer = params.at(6).toInt();
325
326 line = QString("<line x1='%1' y1='%2' x2='%3' y2='%4' stroke-width='%5' stroke='white' fill='none' />")
327 .arg(x1)
328 .arg(y1)
329 .arg(x2)
330 .arg(y2)
331 .arg(checkStrokeWidth(params.at(5).toDouble()));
332
333 return layer;
334 }
335
drawDArc(const QString & ds,QString & arc)336 int KicadModule2Svg::drawDArc(const QString & ds, QString & arc) {
337 //DA x0 y0 x1 y1 angle width layer
338
339 QStringList params = ds.split(" ");
340 if (params.count() < 8) return -1;
341
342 int cx = params.at(1).toInt();
343 int cy = params.at(2).toInt();
344 int x2 = params.at(3).toInt();
345 int y2 = params.at(4).toInt();
346 int width = params.at(6).toInt();
347 double diffAngle = (params.at(5).toInt() % 3600) / 10.0;
348 double radius = qSqrt((cx - x2) * (cx - x2) + (cy - y2) * (cy - y2));
349 double endAngle = asin((y2 - cy) / radius);
350 if (x2 < cx) {
351 endAngle += M_PI;
352 }
353 double startAngle = endAngle + (diffAngle * M_PI / 180.0);
354 double x1 = (radius * cos(startAngle)) + cx;
355 double y1 = (radius * sin(startAngle)) + cy;
356
357 // TODO: figure out bounding box for circular arc and set min and max accordingly
358
359 /*
360 You have radius R, start angle S, end angle T, and I'll
361 assume that the arc is swept counterclockwise from S to T.
362
363 start.x = R * cos(S)
364 start.y = R * sin(S)
365 end.x = R * cos(T)
366 end.y = R * sin(T)
367
368 Determine the axis crossings by analyzing the start and
369 end angles. For discussion sake, I'll describe angles
370 using degrees. Provide a function, wrap(angle), that
371 returns an angle in the range [0 to 360).
372
373 cross0 = wrap(S) > wrap(T)
374 cross90 = wrap(S-90) > wrap(T-90)
375 cross180 = wrap(S-180) > wrap(T-180)
376 cross270 = wrap(S-270) > wrap(T-270)
377
378 Now the axis aligned bounding box is defined by:
379
380 right = cross0 ? +R : max(start.x, end.x)
381 top = cross90 ? +R : max(start.y, end.y)
382 left = cross180 ? -R : min(start.x, end.x)
383 bottom = cross270 ? -R : min(start.y, end.y)
384
385 */
386
387
388 checkXLimit(cx + radius);
389 checkXLimit(cx - radius);
390 checkYLimit(cy + radius);
391 checkYLimit(cy - radius);
392
393 int layer = params.at(7).toInt();
394
395 arc = QString("<path stroke-width='%1' stroke='white' d='M%2,%3a%4,%5 0 %6,%7 %8,%9' fill='none' />")
396 .arg(checkStrokeWidth(width / 2.0))
397 .arg(x1)
398 .arg(y1)
399 .arg(radius)
400 .arg(radius)
401 .arg(qAbs(diffAngle) >= 180 ? 1 : 0)
402 .arg(diffAngle > 0 ? 0 : 1)
403 .arg(x2 - x1)
404 .arg(y2 - y1);
405
406 return layer;
407 }
408
convertPad(QTextStream & stream,QString & pad,QList<int> & numbers)409 KicadModule2Svg::PadLayer KicadModule2Svg::convertPad(QTextStream & stream, QString & pad, QList<int> & numbers) {
410 PadLayer padLayer = UnableToTranslate;
411
412 QStringList padStrings;
413 while (true) {
414 QString line = stream.readLine();
415 if (line.isNull()) {
416 throw QObject::tr("unexpected end of file");
417 }
418 if (line.contains("$EndPAD")) {
419 break;
420 }
421
422 padStrings.append(line);
423 }
424
425 QString shape;
426 QString drill;
427 QString attributes;
428 QString position;
429
430 foreach (QString string, padStrings) {
431 if (string.startsWith("Sh")) {
432 shape = string;
433 }
434 else if (string.startsWith("Po")) {
435 position = string;
436 }
437 else if (string.startsWith("At")) {
438 attributes = string;
439 }
440 else if (string.startsWith("Dr")) {
441 drill = string;
442 }
443 }
444
445 if (drill.isEmpty()) {
446 throw QObject::tr("pad missing drill");
447 }
448 if (attributes.isEmpty()) {
449 throw QObject::tr("pad missing attributes");
450 }
451 if (position.isEmpty()) {
452 throw QObject::tr("pad missing position");
453 }
454 if (shape.isEmpty()) {
455 throw QObject::tr("pad missing shape");
456 }
457
458 QStringList positionStrings = position.split(" ");
459 if (positionStrings.count() < 3) {
460 throw QObject::tr("position missing params");
461 }
462
463 int posX = positionStrings.at(1).toInt();
464 int posY = positionStrings.at(2).toInt();
465
466 QStringList drillStrings = drill.split(" ");
467 if (drillStrings.count() < 4) {
468 throw QObject::tr("drill missing params");
469 }
470
471 int drillX = drillStrings.at(1).toInt();
472 int drillXOffset = drillStrings.at(2).toInt();
473 int drillYOffset = drillStrings.at(3).toInt();
474 int drillY = drillX;
475
476 if (drillXOffset != 0 || drillYOffset != 0) {
477 throw QObject::tr("drill offset not implemented");
478 }
479
480 if (drillStrings.count() > 4) {
481 if (drillStrings.at(4) == "O") {
482 if (drillStrings.count() < 7) {
483 throw QObject::tr("drill missing ellipse params");
484 }
485 drillY = drillStrings.at(6).toInt();
486 }
487 }
488
489 QStringList attributeStrings = attributes.split(" ");
490 if (attributeStrings.count() < 4) {
491 throw QObject::tr("attributes missing params");
492 }
493
494 bool ok;
495 int layerMask = attributeStrings.at(3).toInt(&ok, 16);
496 if (!ok) {
497 throw QObject::tr("bad layer mask parameter");
498 }
499
500 QString padType = attributeStrings.at(1);
501 if (padType == "MECA") {
502 // seems to be the same thing
503 padType = "STD";
504 }
505
506 if (padType == "STD") {
507 padLayer = ToCopper0;
508 }
509 else if (padType == "SMD") {
510 padLayer = ToCopper1;
511 }
512 else if (padType == "CONN") {
513 if (layerMask & 1) {
514 padLayer = ToCopper0;
515 }
516 else {
517 padLayer = ToCopper1;
518 }
519 }
520 else if (padType == "HOLE") {
521 padLayer = ToCopper0;
522 }
523 else {
524 throw QObject::tr("Sorry, can't handle pad type %1").arg(padType);
525 }
526
527 QStringList shapeStrings = shape.split(" ");
528 if (shapeStrings.count() < 8) {
529 throw QObject::tr("pad shape missing params");
530 }
531
532 QString padName = unquote(shapeStrings.at(1));
533 int padNumber = padName.toInt(&ok) - 1;
534 if (!ok) {
535 padNumber = padName.isEmpty() ? -1 : numbers.takeFirst();
536 //DebugDialog::debug(QString("name:%1 padnumber %2").arg(padName).arg(padNumber));
537 }
538 else {
539 numbers.removeOne(padNumber);
540 }
541
542
543 QString shapeIdentifier = shapeStrings.at(2);
544 int xSize = shapeStrings.at(3).toInt();
545 int ySize = shapeStrings.at(4).toInt();
546 if (ySize <= 0) {
547 DebugDialog::debug(QString("ySize is zero %1").arg(padName));
548 ySize = xSize;
549 }
550 if (xSize <= 0) {
551 throw QObject::tr("pad shape size is invalid");
552 }
553
554 int xDelta = shapeStrings.at(5).toInt();
555 int yDelta = shapeStrings.at(6).toInt();
556 int orientation = shapeStrings.at(7).toInt();
557
558 if (shapeIdentifier == "T") {
559 throw QObject::tr("trapezoidal pads not implemented");
560 // eventually polygon?
561 }
562
563 if (xDelta != 0 || yDelta != 0) {
564 // note: so far, all cases of non-zero delta go with shape "T"
565 throw QObject::tr("shape delta not implemented");
566 }
567
568 if (padType == "HOLE") {
569 if (shapeIdentifier != "C") {
570 throw QObject::tr("non-circular holes not implemented");
571 }
572
573 if (drillX == xSize) {
574 throw QObject::tr("non-copper holes not implemented");
575 }
576 }
577
578 if (shapeIdentifier == "C") {
579 checkLimits(posX, xSize, posY, ySize);
580 pad += drawCPad(posX, posY, xSize, ySize, drillX, drillY, padName, padNumber, padType, padLayer);
581 }
582 else if (shapeIdentifier == "R") {
583 checkLimits(posX, xSize, posY, ySize);
584 pad += drawRPad(posX, posY, xSize, ySize, drillX, drillY, padName, padNumber, padType, padLayer);
585 }
586 else if (shapeIdentifier == "O") {
587 checkLimits(posX, xSize, posY, ySize);
588 QString id = getID(padNumber, padLayer);
589 pad += QString("<g %1 connectorname='%2'>")
590 .arg(id).arg(padName)
591 + drawOblong(posX, posY, xSize, ySize, drillX, drillY, padType, padLayer)
592 + "</g>";
593 }
594 else {
595 throw QObject::tr("unable to handle pad shape %1").arg(shapeIdentifier);
596 }
597
598 if (orientation != 0) {
599 if (orientation < 0) {
600 orientation = (orientation % 3600) + 3600;
601 }
602 orientation = 3600 - (orientation % 3600);
603 QTransform t = QTransform().translate(-posX, -posY) *
604 QTransform().rotate(orientation / 10.0) *
605 QTransform().translate(posX, posY);
606 pad = TextUtils::svgTransform(pad, t, true, QString("_x='%1' _y='%2' _r='%3'").arg(posX).arg(posY).arg(orientation / 10.0));
607 }
608
609 return padLayer;
610 }
611
drawVerticalOblong(int posX,int posY,double xSize,double ySize,int drillX,int drillY,const QString & padType,KicadModule2Svg::PadLayer padLayer)612 QString KicadModule2Svg::drawVerticalOblong(int posX, int posY, double xSize, double ySize, int drillX, int drillY, const QString & padType, KicadModule2Svg::PadLayer padLayer) {
613
614 QString color = getColor(padLayer);
615 double rad = xSize / 4.0;
616
617 QString bot;
618
619 if (drillX == drillY) {
620 bot = QString("<path d='M%1,%2a%3,%3 0 0 1 %4,0' fill='%5' stroke-width='0' />")
621 .arg(posX - rad - rad)
622 .arg(posY - (ySize / 2.0) + (xSize / 2.0))
623 .arg(rad * 2)
624 .arg(rad * 4)
625 .arg(color);
626 bot += QString("<path d='M%1,%2a%3,%3 0 1 1 %4,0' fill='%5' stroke-width='0' />")
627 .arg(posX + rad + rad)
628 .arg(posY + (ySize / 2.0) - (xSize / 2.0))
629 .arg(rad * 2)
630 .arg(-rad * 4)
631 .arg(color);
632 }
633 else {
634 double w = (ySize - drillY) / 2.0;
635 double newrad = rad - w / 4;
636 bot = QString("<g id='oblong' stroke-width='%1'>").arg(checkStrokeWidth(drillX));
637 bot += QString("<path d='M%1,%2a%3,%3 0 0 1 %4,0' fill='none' stroke='%5' stroke-width='%6' />")
638 .arg(posX - rad - rad + (w / 2))
639 .arg(posY - (ySize / 2.0) + (xSize / 2.0))
640 .arg(newrad * 2)
641 .arg(newrad * 4)
642 .arg(color)
643 .arg(checkStrokeWidth(w));
644 bot += QString("<path d='M%1,%2a%3,%3 0 1 1 %4,0' fill='none' stroke='%5' stroke-width='%6' />")
645 .arg(posX + rad + rad - (w / 2))
646 .arg(posY + (ySize / 2.0) - (xSize / 2.0))
647 .arg(newrad * 2)
648 .arg(-newrad * 4)
649 .arg(color)
650 .arg(checkStrokeWidth(w));
651 bot += QString("<line fill='none' stroke-width='0' x1='%1' y1='%2' x2='%3' y2='%4' />")
652 .arg(posX).arg(posY - (ySize / 2.0) + (xSize / 2.0)).arg(posX).arg(posY + (ySize / 2.0) - (xSize / 2.0));
653 bot += "</g>";
654 }
655
656 QString middle;
657
658 if (padType == "SMD") {
659 middle = QString("<rect x='%1' y='%2' width='%3' height='%4' stroke-width='0' fill='%5' />")
660 .arg(posX - (xSize / 2.0))
661 .arg(posY - (ySize / 2.0) + (xSize / 2.0))
662 .arg(xSize)
663 .arg(ySize - xSize)
664 .arg(color);
665 }
666 else {
667 if (drillX == drillY) {
668 middle = QString("<circle fill='none' cx='%1' cy='%2' r='%3' stroke-width='%4' stroke='%5' />")
669 .arg(posX)
670 .arg(posY)
671 .arg((qMin(xSize, ySize) / 2.0) - (drillX / 4.0))
672 .arg(checkStrokeWidth(drillX / 2.0))
673 .arg(color);
674 }
675
676 middle += QString("<line x1='%1' y1='%2' x2='%1' y2='%3' fill='none' stroke-width='%4' stroke='%5' />")
677 .arg(posX - (xSize / 2.0) + (drillX / 4.0))
678 .arg(posY - (ySize / 2.0) + (xSize / 2.0))
679 .arg(posY + (ySize / 2.0) - (xSize / 2.0))
680 .arg(checkStrokeWidth(drillX / 2.0))
681 .arg(color);
682 middle += QString("<line x1='%1' y1='%2' x2='%1' y2='%3' fill='none' stroke-width='%4' stroke='%5' />")
683 .arg(posX + (xSize / 2.0) - (drillX / 4.0))
684 .arg(posY - (ySize / 2.0) + (xSize / 2.0))
685 .arg(posY + (ySize / 2.0) - (xSize / 2.0))
686 .arg(checkStrokeWidth(drillX / 2.0))
687 .arg(color);
688 }
689
690 return middle + bot;
691 }
692
drawHorizontalOblong(int posX,int posY,double xSize,double ySize,int drillX,int drillY,const QString & padType,KicadModule2Svg::PadLayer padLayer)693 QString KicadModule2Svg::drawHorizontalOblong(int posX, int posY, double xSize, double ySize, int drillX, int drillY, const QString & padType, KicadModule2Svg::PadLayer padLayer) {
694
695 QString color = getColor(padLayer);
696 double rad = ySize / 4.0;
697
698 QString bot;
699
700 if (drillX == drillY) {
701 bot = QString("<path d='M%1,%2a%3,%3 0 0 0 0,%4' fill='%5' stroke-width='0' />")
702 .arg(posX - (xSize / 2.0) + (ySize / 2.0))
703 .arg(posY - rad - rad)
704 .arg(rad * 2)
705 .arg(rad * 4)
706 .arg(color);
707 bot += QString("<path d='M%1,%2a%3,%3 0 1 0 0,%4' fill='%5' stroke-width='0' />")
708 .arg(posX + (xSize / 2.0) - (ySize / 2.0))
709 .arg(posY + rad + rad)
710 .arg(rad * 2)
711 .arg(-rad * 4)
712 .arg(color);
713 }
714 else {
715 double w = (xSize - drillX) / 2.0;
716 double newrad = rad - w / 4;
717 bot = QString("<g id='oblong' stroke-width='%1'>").arg(checkStrokeWidth(drillY));
718 bot += QString("<path d='M%1,%2a%3,%3 0 0 0 0,%4' fill='none' stroke='%5' stroke-width='%6' />")
719 .arg(posX - (xSize / 2.0) + (ySize / 2.0))
720 .arg(posY - rad - rad + (w / 2))
721 .arg(newrad * 2)
722 .arg(newrad * 4)
723 .arg(color)
724 .arg(checkStrokeWidth(w));
725 bot += QString("<path d='M%1,%2a%3,%3 0 1 0 0,%4' fill='none' stroke='%5' stroke-width='%6' />")
726 .arg(posX + (xSize / 2.0) - (ySize / 2.0))
727 .arg(posY + rad + rad - (w / 2))
728 .arg(newrad * 2)
729 .arg(-newrad * 4)
730 .arg(color)
731 .arg(checkStrokeWidth(w));
732 bot += QString("<line fill='none' stroke-width='0' x1='%1' y1='%2' x2='%3' y2='%4' />")
733 .arg(posX - (xSize / 2.0) + (ySize / 2.0)).arg(posY).arg(posX + (xSize / 2.0) - (ySize / 2.0)).arg(posY);
734 bot += "</g>";
735 }
736
737 QString middle;
738 bool gotID = false;
739
740 if (padType == "SMD") {
741 middle = QString("<rect x='%1' y='%2' width='%3' height='%4' stroke-width='0' fill='%5' />")
742 .arg(posX - (xSize / 2.0) + (ySize / 2.0))
743 .arg(posY - (ySize / 2.0))
744 .arg(xSize - ySize)
745 .arg(ySize)
746 .arg(color);
747 }
748 else {
749 if (drillX == drillY) {
750 gotID = true;
751 middle = QString("<circle fill='none' cx='%1' cy='%2' r='%3' stroke-width='%4' stroke='%5' />")
752 .arg(posX)
753 .arg(posY)
754 .arg((qMin(xSize, ySize) / 2.0) - (drillY / 4.0))
755 .arg(checkStrokeWidth(drillY / 2.0))
756 .arg(color);
757 }
758
759 middle += QString("<line x1='%1' y1='%2' x2='%3' y2='%2' fill='none' stroke-width='%4' stroke='%5' />")
760 .arg(posX - (xSize / 2.0) + (ySize / 2.0))
761 .arg(posY - (ySize / 2.0) + (drillY / 4.0))
762 .arg(posX + (xSize / 2.0) - (ySize / 2.0))
763 .arg(checkStrokeWidth(drillY / 2.0))
764 .arg(color);
765 middle += QString("<line x1='%1' y1='%2' x2='%3' y2='%2' fill='none' stroke-width='%4' stroke='%5' />")
766 .arg(posX - (xSize / 2.0) + (ySize / 2.0))
767 .arg(posY + (ySize / 2.0) - (drillY / 4.0))
768 .arg(posX + (xSize / 2.0) - (ySize / 2.0))
769 .arg(checkStrokeWidth(drillY / 2.0))
770 .arg(color);
771 }
772
773 return middle + bot;
774 }
775
checkLimits(int posX,int xSize,int posY,int ySize)776 void KicadModule2Svg::checkLimits(int posX, int xSize, int posY, int ySize) {
777 checkXLimit(posX - (xSize / 2.0));
778 checkXLimit(posX + (xSize / 2.0));
779 checkYLimit(posY - (ySize / 2.0));
780 checkYLimit(posY + (ySize / 2.0));
781 }
782
drawCPad(int posX,int posY,int xSize,int ySize,int drillX,int drillY,const QString & padName,int padNumber,const QString & padType,KicadModule2Svg::PadLayer padLayer)783 QString KicadModule2Svg::drawCPad(int posX, int posY, int xSize, int ySize, int drillX, int drillY, const QString & padName, int padNumber, const QString & padType, KicadModule2Svg::PadLayer padLayer)
784 {
785 QString color = getColor(padLayer);
786 QString id = getID(padNumber, padLayer);
787
788 Q_UNUSED(ySize);
789 if (padType == "SMD") {
790 return QString("<circle cx='%1' cy='%2' r='%3' %4 fill='%5' stroke-width='0' connectorname='%6'/>")
791 .arg(posX)
792 .arg(posY)
793 .arg(xSize / 2.0)
794 .arg(id)
795 .arg(color)
796 .arg(padName);
797 }
798
799 if (drillX == drillY) {
800 double w = (xSize - drillX) / 2.0;
801 QString pad = QString("<g %1 connectorname='%2'>").arg(id).arg(padName);
802 pad += QString("<circle cx='%1' cy='%2' r='%3' stroke-width='%4' stroke='%5' fill='none' />")
803 .arg(posX)
804 .arg(posY)
805 .arg((drillX / 2.0) + (w / 2))
806 .arg(checkStrokeWidth(w))
807 .arg(color);
808 if (drillX > 500) {
809 pad += QString("<circle cx='%1' cy='%2' r='%3' stroke-width='0' fill='black' drill='0' />")
810 .arg(posX)
811 .arg(posY)
812 .arg(drillX / 2.0);
813 }
814 pad += "</g>";
815 return pad;
816 }
817
818
819 QString pad = QString("<g %1>").arg(id);
820 double w = (xSize - qMax(drillX, drillY)) / 2.0;
821 pad += QString("<circle cx='%1' cy='%2' r='%3' stroke-width='%4' stroke='%5' fill='none' drill='0' />")
822 .arg(posX)
823 .arg(posY)
824 .arg((xSize / 2.0) - (w / 2))
825 .arg(checkStrokeWidth(w))
826 .arg(color);
827 pad += drawOblong(posX, posY, drillX + w, drillY + w, drillX, drillY, "", padLayer);
828
829 // now fill the gaps between the oblong and the circle
830 if (drillX >= drillY) {
831 double angle = asin(((drillY + w) / 2) / (ySize / 2.0));
832 double opp = (ySize / 2.0) * cos(angle);
833 pad += QString("<polygon stroke-width='0' fill='%1' points='%2,%3,%4,%5,%6,%7' />")
834 .arg(color)
835 .arg(posX)
836 .arg(posY - (ySize / 2.0))
837 .arg(posX - opp)
838 .arg(posY - (drillY / 2.0))
839 .arg(posX + opp)
840 .arg(posY - (drillY / 2.0));
841 pad += QString("<polygon stroke-width='0' fill='%1' points='%2,%3,%4,%5,%6,%7' />")
842 .arg(color)
843 .arg(posX)
844 .arg(posY + (ySize / 2.0))
845 .arg(posX - opp)
846 .arg(posY + (drillY / 2.0))
847 .arg(posX + opp)
848 .arg(posY + (drillY / 2.0));
849 }
850 else {
851 double angle = acos(((drillX + w) / 2) / (xSize / 2.0));
852 double adj = (xSize / 2.0) * sin(angle);
853 pad += QString("<polygon stroke-width='0' fill='%1' points='%2,%3,%4,%5,%6,%7' />")
854 .arg(color)
855 .arg(posX - (xSize / 2.0))
856 .arg(posY)
857 .arg(posX - (drillX / 2.0))
858 .arg(posY - adj)
859 .arg(posX - (drillX / 2.0))
860 .arg(posY + adj);
861 pad += QString("<polygon stroke-width='0' fill='%1' points='%2,%3,%4,%5,%6,%7' />")
862 .arg(color)
863 .arg(posX + (xSize / 2.0))
864 .arg(posY)
865 .arg(posX + (drillX / 2.0))
866 .arg(posY - adj)
867 .arg(posX + (drillX / 2.0))
868 .arg(posY + adj);
869 }
870
871 pad += "</g>";
872
873 return pad;
874 }
875
drawRPad(int posX,int posY,int xSize,int ySize,int drillX,int drillY,const QString & padName,int padNumber,const QString & padType,KicadModule2Svg::PadLayer padLayer)876 QString KicadModule2Svg::drawRPad(int posX, int posY, int xSize, int ySize, int drillX, int drillY, const QString & padName, int padNumber, const QString & padType, KicadModule2Svg::PadLayer padLayer)
877 {
878 QString color = getColor(padLayer);
879 QString id = getID(padNumber, padLayer);
880
881 if (padType == "SMD") {
882 return QString("<rect x='%1' y='%2' width='%3' height='%4' %5 stroke-width='0' fill='%6' connectorname='%7'/>")
883 .arg(posX - (xSize / 2.0))
884 .arg(posY - (ySize / 2.0))
885 .arg(xSize)
886 .arg(ySize)
887 .arg(id)
888 .arg(color)
889 .arg(padName);
890 }
891
892 QString pad = QString("<g %1 connectorname='%2'>").arg(id).arg(padName);
893 if (drillX == drillY) {
894 double w = (qMin(xSize, ySize) - drillX) / 2.0;
895 pad += QString("<circle fill='none' cx='%1' cy='%2' r='%3' stroke-width='%4' stroke='%5' />")
896 .arg(posX)
897 .arg(posY)
898 .arg((w / 2) + (drillX / 2.0))
899 .arg(checkStrokeWidth(w))
900 .arg(color);
901 }
902 else {
903 double w = (drillX >= drillY) ? (xSize - drillX) / 2.0 : (ySize - drillY) / 2.0 ;
904 pad += QString("<circle fill='none' cx='%1' cy='%2' r='%3' stroke-width='%4' stroke='%5' />")
905 .arg(posX)
906 .arg(posY)
907 .arg((w / 2) + (qMax(drillX, drillY) / 2.0))
908 .arg(checkStrokeWidth(w))
909 .arg(color);
910 pad += drawOblong(posX, posY, drillX + w, drillY + w, drillX, drillY, "", padLayer);
911 }
912
913 // draw 4 lines otherwise there may be gaps if one pair of sides is much longer than the other pair of sides
914
915 double w = (ySize - drillY) / 2.0;
916 double tlx = posX - xSize / 2.0;
917 double tly = posY - ySize / 2.0;
918 pad += QString("<line x1='%1' y1='%2' x2='%3' y2='%2' fill='none' stroke-width='%4' stroke='%5' />")
919 .arg(tlx)
920 .arg(tly + w / 2)
921 .arg(tlx + xSize)
922 .arg(checkStrokeWidth(w))
923 .arg(color);
924 pad += QString("<line x1='%1' y1='%2' x2='%3' y2='%2' fill='none' stroke-width='%4' stroke='%5' />")
925 .arg(tlx)
926 .arg(tly + ySize - w / 2)
927 .arg(tlx + xSize)
928 .arg(checkStrokeWidth(w))
929 .arg(color);
930
931 w = (xSize - drillX) / 2.0;
932 pad += QString("<line x1='%1' y1='%2' x2='%1' y2='%3' fill='none' stroke-width='%4' stroke='%5' />")
933 .arg(tlx + w / 2)
934 .arg(tly)
935 .arg(tly + ySize)
936 .arg(checkStrokeWidth(w))
937 .arg(color);
938 pad += QString("<line x1='%1' y1='%2' x2='%1' y2='%3' fill='none' stroke-width='%4' stroke='%5' />")
939 .arg(tlx + xSize - w / 2)
940 .arg(tly)
941 .arg(tly + ySize)
942 .arg(checkStrokeWidth(w))
943 .arg(color);
944 pad += "</g>";
945 return pad;
946 }
947
drawOblong(int posX,int posY,double xSize,double ySize,int drillX,int drillY,const QString & padType,KicadModule2Svg::PadLayer padLayer)948 QString KicadModule2Svg::drawOblong(int posX, int posY, double xSize, double ySize, int drillX, int drillY, const QString & padType, KicadModule2Svg::PadLayer padLayer) {
949 if (xSize <= ySize) {
950 return drawVerticalOblong(posX, posY, xSize, ySize, drillX, drillY, padType, padLayer);
951 }
952 else {
953 return drawHorizontalOblong(posX, posY, xSize, ySize, drillX, drillY, padType, padLayer);
954 }
955 }
956
getID(int padNumber,KicadModule2Svg::PadLayer padLayer)957 QString KicadModule2Svg::getID(int padNumber, KicadModule2Svg::PadLayer padLayer) {
958 if (padNumber < 0) {
959 return QString("id='%1%2'").arg(FSvgRenderer::NonConnectorName).arg(m_nonConnectorNumber++);
960 }
961
962 return QString("id='connector%1%2'").arg(padNumber).arg((padLayer == ToCopper1) ? "pad" : "pin");
963 }
964
getColor(KicadModule2Svg::PadLayer padLayer)965 QString KicadModule2Svg::getColor(KicadModule2Svg::PadLayer padLayer) {
966 switch (padLayer) {
967 case ToCopper0:
968 return ViewLayer::Copper0Color;
969 break;
970 case ToCopper1:
971 return ViewLayer::Copper1Color;
972 break;
973 default:
974 DebugDialog::debug("kicad getcolor with unknown layer");
975 return "#FF0000";
976 }
977 }
978