1 /*
2 * LibrePCB - Professional EDA for everyone!
3 * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4 * https://librepcb.org/
5 *
6 * This program 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 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /*******************************************************************************
21 * Includes
22 ******************************************************************************/
23 #include "gerberattribute.h"
24
25 #include "../uuid.h"
26
27 #include <QtCore>
28
29 /*******************************************************************************
30 * Namespace
31 ******************************************************************************/
32 namespace librepcb {
33
34 /*******************************************************************************
35 * Constructors / Destructor
36 ******************************************************************************/
37
GerberAttribute()38 GerberAttribute::GerberAttribute() noexcept
39 : mType(Type::Invalid), mKey(), mValues() {
40 }
41
GerberAttribute(Type type,const QString & key,const QStringList & values)42 GerberAttribute::GerberAttribute(Type type, const QString& key,
43 const QStringList& values) noexcept
44 : mType(type), mKey(key), mValues(values) {
45 }
46
GerberAttribute(const GerberAttribute & other)47 GerberAttribute::GerberAttribute(const GerberAttribute& other) noexcept
48 : mType(other.mType), mKey(other.mKey), mValues(other.mValues) {
49 }
50
~GerberAttribute()51 GerberAttribute::~GerberAttribute() noexcept {
52 }
53
54 /*******************************************************************************
55 * General Methods
56 ******************************************************************************/
57
toGerberString() const58 QString GerberAttribute::toGerberString() const noexcept {
59 // Use G04 comments since some PCB fabricators fail to parse X2 attributes.
60 // Some day we might provide an option to use real X2 attributes. However,
61 // maybe this is not needed at all so let's do it only if it has clear
62 // advantages.
63 return "G04 #@! " % toString() % "*\n";
64 }
65
toExcellonString() const66 QString GerberAttribute::toExcellonString() const noexcept {
67 return "; #@! " % toString() % "\n";
68 }
69
70 /*******************************************************************************
71 * Operator Overloadings
72 ******************************************************************************/
73
operator =(const GerberAttribute & rhs)74 GerberAttribute& GerberAttribute::operator=(
75 const GerberAttribute& rhs) noexcept {
76 mType = rhs.mType;
77 mKey = rhs.mKey;
78 mValues = rhs.mValues;
79 return *this;
80 }
81
operator ==(const GerberAttribute & rhs) const82 bool GerberAttribute::operator==(const GerberAttribute& rhs) const noexcept {
83 return (mType == rhs.mType) && (mKey == rhs.mKey) && (mValues == rhs.mValues);
84 }
85
86 /*******************************************************************************
87 * Private Methods
88 ******************************************************************************/
89
toString() const90 QString GerberAttribute::toString() const noexcept {
91 QString s = "T";
92 switch (mType) {
93 case Type::File: {
94 s += "F";
95 break;
96 }
97 case Type::Aperture: {
98 s += "A";
99 break;
100 }
101 case Type::Object: {
102 s += "O";
103 break;
104 }
105 case Type::Delete: {
106 s += "D";
107 break;
108 }
109 default: { return QString(); }
110 }
111 s += mKey;
112 foreach (const QString& value, mValues) { s += "," + escapeValue(value); }
113 return s;
114 }
115
116 /*******************************************************************************
117 * Static Methods
118 ******************************************************************************/
119
unset(const QString & key)120 GerberAttribute GerberAttribute::unset(const QString& key) noexcept {
121 return GerberAttribute(Type::Delete, key, {});
122 }
123
fileGenerationSoftware(const QString & vendor,const QString & application,const QString & version)124 GerberAttribute GerberAttribute::fileGenerationSoftware(
125 const QString& vendor, const QString& application,
126 const QString& version) noexcept {
127 QStringList values = {vendor, application};
128 if (!version.isEmpty()) {
129 values.append(version);
130 }
131 return GerberAttribute(Type::File, ".GenerationSoftware", values);
132 }
133
fileCreationDate(const QDateTime & date)134 GerberAttribute GerberAttribute::fileCreationDate(
135 const QDateTime& date) noexcept {
136 return GerberAttribute(Type::File, ".CreationDate",
137 {date.toString(Qt::ISODate)});
138 }
139
fileProjectId(const QString & name,const Uuid & uuid,const QString & revision)140 GerberAttribute GerberAttribute::fileProjectId(
141 const QString& name, const Uuid& uuid, const QString& revision) noexcept {
142 return GerberAttribute(Type::File, ".ProjectId",
143 {name, uuid.toStr(), revision});
144 }
145
filePartSingle()146 GerberAttribute GerberAttribute::filePartSingle() noexcept {
147 return GerberAttribute(Type::File, ".Part", {"Single"});
148 }
149
fileSameCoordinates(const QString & identifier)150 GerberAttribute GerberAttribute::fileSameCoordinates(
151 const QString& identifier) noexcept {
152 QStringList values;
153 if (!identifier.isEmpty()) {
154 values.append(identifier);
155 }
156 return GerberAttribute(Type::File, ".SameCoordinates", values);
157 }
158
fileFunctionProfile(bool plated)159 GerberAttribute GerberAttribute::fileFunctionProfile(bool plated) noexcept {
160 return GerberAttribute(Type::File, ".FileFunction",
161 {"Profile", plated ? "P" : "NP"});
162 }
163
fileFunctionCopper(int layer,CopperSide side)164 GerberAttribute GerberAttribute::fileFunctionCopper(int layer,
165 CopperSide side) noexcept {
166 QStringList values = {"Copper", "L" % QString::number(layer)};
167 switch (side) {
168 case CopperSide::Top: {
169 values.append("Top");
170 break;
171 }
172 case CopperSide::Inner: {
173 values.append("Inr");
174 break;
175 }
176 case CopperSide::Bottom: {
177 values.append("Bot");
178 break;
179 }
180 default: {
181 qCritical() << "Unknown Gerber copper side:" << static_cast<int>(side);
182 return GerberAttribute();
183 }
184 }
185 return GerberAttribute(Type::File, ".FileFunction", values);
186 }
187
fileFunctionSolderMask(BoardSide side)188 GerberAttribute GerberAttribute::fileFunctionSolderMask(
189 BoardSide side) noexcept {
190 switch (side) {
191 case BoardSide::Top: {
192 return GerberAttribute(Type::File, ".FileFunction",
193 {"Soldermask", "Top"});
194 }
195 case BoardSide::Bottom: {
196 return GerberAttribute(Type::File, ".FileFunction",
197 {"Soldermask", "Bot"});
198 }
199 default: {
200 qCritical() << "Unknown Gerber board side:" << static_cast<int>(side);
201 return GerberAttribute();
202 }
203 }
204 }
205
fileFunctionLegend(BoardSide side)206 GerberAttribute GerberAttribute::fileFunctionLegend(BoardSide side) noexcept {
207 switch (side) {
208 case BoardSide::Top: {
209 return GerberAttribute(Type::File, ".FileFunction", {"Legend", "Top"});
210 }
211 case BoardSide::Bottom: {
212 return GerberAttribute(Type::File, ".FileFunction", {"Legend", "Bot"});
213 }
214 default: {
215 qCritical() << "Unknown Gerber board side:" << static_cast<int>(side);
216 return GerberAttribute();
217 }
218 }
219 }
220
fileFunctionPaste(BoardSide side)221 GerberAttribute GerberAttribute::fileFunctionPaste(BoardSide side) noexcept {
222 switch (side) {
223 case BoardSide::Top: {
224 return GerberAttribute(Type::File, ".FileFunction", {"Paste", "Top"});
225 }
226 case BoardSide::Bottom: {
227 return GerberAttribute(Type::File, ".FileFunction", {"Paste", "Bot"});
228 }
229 default: {
230 qCritical() << "Unknown Gerber board side:" << static_cast<int>(side);
231 return GerberAttribute();
232 }
233 }
234 }
235
fileFunctionPlatedThroughHole(int fromLayer,int toLayer)236 GerberAttribute GerberAttribute::fileFunctionPlatedThroughHole(
237 int fromLayer, int toLayer) noexcept {
238 return GerberAttribute(
239 Type::File, ".FileFunction",
240 {"Plated", QString::number(fromLayer), QString::number(toLayer), "PTH"});
241 }
242
fileFunctionNonPlatedThroughHole(int fromLayer,int toLayer)243 GerberAttribute GerberAttribute::fileFunctionNonPlatedThroughHole(
244 int fromLayer, int toLayer) noexcept {
245 return GerberAttribute(Type::File, ".FileFunction",
246 {"NonPlated", QString::number(fromLayer),
247 QString::number(toLayer), "NPTH"});
248 }
249
fileFunctionMixedPlating(int fromLayer,int toLayer)250 GerberAttribute GerberAttribute::fileFunctionMixedPlating(
251 int fromLayer, int toLayer) noexcept {
252 // Note that "MixedPlating" is actually not an official Gerber attribute (yet)
253 // because Gerber specs say that NPTH and PTH must be separate files. However,
254 // some PCB fabricators require to send a single drill file with NPTH and PTH
255 // mixed (totally stupid), and in this case, Ucamco recommends to use the
256 // "FixedPlating" file function (not publicly documented, I guess).
257 return GerberAttribute(
258 Type::File, ".FileFunction",
259 {"MixedPlating", QString::number(fromLayer), QString::number(toLayer)});
260 }
261
filePolarity(Polarity polarity)262 GerberAttribute GerberAttribute::filePolarity(Polarity polarity) noexcept {
263 switch (polarity) {
264 case Polarity::Positive: {
265 return GerberAttribute(Type::File, ".FilePolarity", {"Positive"});
266 }
267 case Polarity::Negative: {
268 return GerberAttribute(Type::File, ".FilePolarity", {"Negative"});
269 }
270 default: {
271 qCritical() << "Unknown Gerber file polarity:"
272 << static_cast<int>(polarity);
273 return GerberAttribute();
274 }
275 }
276 }
277
fileMd5(const QString & md5)278 GerberAttribute GerberAttribute::fileMd5(const QString& md5) noexcept {
279 return GerberAttribute(Type::File, ".MD5", {md5});
280 }
281
apertureFunction(ApertureFunction function)282 GerberAttribute GerberAttribute::apertureFunction(
283 ApertureFunction function) noexcept {
284 switch (function) {
285 case ApertureFunction::Profile: {
286 return GerberAttribute(Type::Aperture, ".AperFunction", {"Profile"});
287 }
288 case ApertureFunction::ViaDrill: {
289 return GerberAttribute(Type::Aperture, ".AperFunction", {"ViaDrill"});
290 }
291 case ApertureFunction::ComponentDrill: {
292 return GerberAttribute(Type::Aperture, ".AperFunction",
293 {"ComponentDrill"});
294 }
295 case ApertureFunction::MechanicalDrill: {
296 return GerberAttribute(Type::Aperture, ".AperFunction",
297 {"MechanicalDrill"});
298 }
299 case ApertureFunction::Conductor: {
300 return GerberAttribute(Type::Aperture, ".AperFunction", {"Conductor"});
301 }
302 case ApertureFunction::NonConductor: {
303 return GerberAttribute(Type::Aperture, ".AperFunction", {"NonConductor"});
304 }
305 case ApertureFunction::ComponentPad: {
306 return GerberAttribute(Type::Aperture, ".AperFunction", {"ComponentPad"});
307 }
308 case ApertureFunction::SmdPadCopperDefined: {
309 return GerberAttribute(Type::Aperture, ".AperFunction",
310 {"SMDPad", "CuDef"});
311 }
312 case ApertureFunction::SmdPadSolderMaskDefined: {
313 return GerberAttribute(Type::Aperture, ".AperFunction",
314 {"SMDPad", "SMDef"});
315 }
316 case ApertureFunction::ViaPad: {
317 return GerberAttribute(Type::Aperture, ".AperFunction", {"ViaPad"});
318 }
319 default: {
320 qCritical() << "Unknown Gerber aperture function attribute:"
321 << static_cast<int>(function);
322 return GerberAttribute();
323 }
324 }
325 }
326
apertureFunctionMixedPlatingDrill(bool plated,ApertureFunction function)327 GerberAttribute GerberAttribute::apertureFunctionMixedPlatingDrill(
328 bool plated, ApertureFunction function) noexcept {
329 // Note: This function shall only be used in mixed-plating Excellon files!
330 // See comment in fileFunctionMixedPlating() for details.
331 GerberAttribute a = apertureFunction(function);
332 if (plated) {
333 a.mValues.prepend("PTH");
334 a.mValues.prepend("Plated");
335 } else {
336 a.mValues.prepend("NPTH");
337 a.mValues.prepend("NonPlated");
338 }
339 return a;
340 }
341
objectNet(const QString & net)342 GerberAttribute GerberAttribute::objectNet(const QString& net) noexcept {
343 return GerberAttribute(Type::Object, ".N", {net});
344 }
345
objectComponent(const QString & component)346 GerberAttribute GerberAttribute::objectComponent(
347 const QString& component) noexcept {
348 return GerberAttribute(Type::Object, ".C", {component});
349 }
350
objectPin(const QString & component,const QString & pin,const QString & signal)351 GerberAttribute GerberAttribute::objectPin(const QString& component,
352 const QString& pin,
353 const QString& signal) noexcept {
354 QStringList values = {component, pin};
355 if (!signal.isEmpty()) {
356 values.append(signal);
357 }
358 return GerberAttribute(Type::Object, ".P", values);
359 }
360
361 /*******************************************************************************
362 * Private Methods
363 ******************************************************************************/
364
escapeValue(const QString & value)365 QString GerberAttribute::escapeValue(const QString& value) noexcept {
366 // perform compatibility decomposition (NFKD)
367 QString ret = value.normalized(QString::NormalizationForm_KD);
368 // replace newlines by spaces
369 ret = ret.replace('\n', ' ');
370 // remove all invalid characters
371 // Note: Even if backslashes are allowed, we will remove them because we
372 // haven't implemented proper escaping. Escaping of unicode characters is also
373 // missing here.
374 QString validChars("-a-zA-Z0-9_+/!?<>\"'(){}.|&@# ;$:="); // No ',' in attrs!
375 ret.remove(QRegularExpression(QString("[^%1]").arg(validChars)));
376 // limit length to 65535 characters
377 ret.truncate(65535);
378 return ret;
379 }
380
381 /*******************************************************************************
382 * End of File
383 ******************************************************************************/
384
385 } // namespace librepcb
386