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