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