KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drill_chart_template.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (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 <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <algorithm>
23#include <set>
24#include <fstream>
25
26#include <json_common.h>
27
28#include <wx/translation.h>
29
30
32{
33 return m_Id == aOther.m_Id && m_Heading == aOther.m_Heading && m_Align == aOther.m_Align
34 && m_Width == aOther.m_Width;
35}
36
37
39{
40 return m_Plated == aOther.m_Plated && m_NonPlated == aOther.m_NonPlated
41 && m_Vias == aOther.m_Vias && m_Slots == aOther.m_Slots
42 && m_Backdrills == aOther.m_Backdrills && m_Castellated == aOther.m_Castellated;
43}
44
45
53
54
55bool ValidateDrillChartColumns( std::vector<DRILL_CHART_COLUMN>& aColumns )
56{
57 std::set<DRILL_CHART_COLUMN_ID> seen;
58 int64_t total = 0;
59
60 for( DRILL_CHART_COLUMN& col : aColumns )
61 {
62 // A repeat adds no information and multiplies the generated cell count
63 if( !seen.insert( col.m_Id ).second )
64 return false;
65
66 col.m_Width = std::clamp( col.m_Width, 0, DRILL_CHART_MAX_COLUMN_WIDTH );
67 total += col.m_Width;
68 }
69
70 return !aColumns.empty() && total <= DRILL_CHART_MAX_TOTAL_WIDTH;
71}
72
73
75{
76 // Drops mask, paste, adhesive and silk, which print on the finished board, plus the
77 // courtyard layers, which DRC reads as constraints
78 LSET layers = LSET::AllNonCuMask() & ~LSET::AllBoardTechMask();
79
80 layers.reset( Edge_Cuts );
81 layers.reset( Margin );
82 layers.reset( F_CrtYd );
83 layers.reset( B_CrtYd );
84 layers.reset( Rescue );
85
86 return layers;
87}
88
89
90namespace
91{
92
93// Headings are stored as literals rather than translated at build time, so regenerating a
94// chart under a different UI language cannot alter an approved drawing
95const struct DEFAULT_COLUMN
96{
98 const char* heading;
100} g_defaultColumns[] = {
107};
108
109} // namespace
110
111
113{
114 for( const DEFAULT_COLUMN& def : g_defaultColumns )
115 {
116 if( def.id != aId )
117 continue;
118
119 aColumn.m_Id = def.id;
120 aColumn.m_Heading = wxString::FromUTF8( def.heading );
121 aColumn.m_Align = def.align;
122 return true;
123 }
124
125 return false;
126}
127
128
130{
132
133 tmpl.SetName( wxT( "Default" ) );
134
135 for( const DEFAULT_COLUMN& def : g_defaultColumns )
136 {
138 DrillChartDefaultColumn( def.id, col );
139 tmpl.Columns().push_back( col );
140 }
141
142 return tmpl;
143}
144
145
146namespace
147{
148struct CHART_TOKEN
149{
150 const char* token;
151 int value;
152};
153
154const CHART_TOKEN unitsTokens[] = {
155 { "mm", (int) DRILL_CHART_UNITS::MM },
156 { "inch", (int) DRILL_CHART_UNITS::INCH },
157};
158
159const CHART_TOKEN columnTokens[] = {
160 { "symbol", (int) DRILL_CHART_COLUMN_ID::SYMBOL },
161 { "drill_diameter", (int) DRILL_CHART_COLUMN_ID::DRILL_DIAMETER },
162 { "slot_size", (int) DRILL_CHART_COLUMN_ID::SLOT_SIZE },
163 { "plating", (int) DRILL_CHART_COLUMN_ID::PLATING },
164 { "operation_count", (int) DRILL_CHART_COLUMN_ID::OPERATION_COUNT },
165 { "site_count", (int) DRILL_CHART_COLUMN_ID::SITE_COUNT },
166 { "layer_span", (int) DRILL_CHART_COLUMN_ID::LAYER_SPAN },
167 { "operation", (int) DRILL_CHART_COLUMN_ID::OPERATION },
168 { "protection", (int) DRILL_CHART_COLUMN_ID::PROTECTION },
169 { "backdrill_stub", (int) DRILL_CHART_COLUMN_ID::BACKDRILL_STUB },
170 { "aspect_ratio", (int) DRILL_CHART_COLUMN_ID::ASPECT_RATIO },
171 { "description", (int) DRILL_CHART_COLUMN_ID::DESCRIPTION },
172};
173
174const CHART_TOKEN alignTokens[] = {
175 { "left", (int) DRILL_CHART_ALIGN::LEFT },
176 { "center", (int) DRILL_CHART_ALIGN::CENTER },
177 { "right", (int) DRILL_CHART_ALIGN::RIGHT },
178};
179
180template <size_t N>
181const char* chartTokenFor( const CHART_TOKEN ( &aMap )[N], int aValue )
182{
183 for( const CHART_TOKEN& entry : aMap )
184 {
185 if( entry.value == aValue )
186 return entry.token;
187 }
188
189 return aMap[0].token;
190}
191
192template <size_t N>
193bool chartValueFor( const CHART_TOKEN ( &aMap )[N], const wxString& aToken, int& aValue )
194{
195 for( const CHART_TOKEN& entry : aMap )
196 {
197 if( aToken.IsSameAs( wxString::FromUTF8( entry.token ) ) )
198 {
199 aValue = entry.value;
200 return true;
201 }
202 }
203
204 return false;
205}
206} // namespace
207
208
210{
211 return chartTokenFor( unitsTokens, (int) aUnits );
212}
213
214
216{
217 return chartTokenFor( columnTokens, (int) aId );
218}
219
220
222{
223 return chartTokenFor( alignTokens, (int) aAlign );
224}
225
226
227bool DrillChartUnitsFromToken( const wxString& aToken, DRILL_CHART_UNITS& aUnits )
228{
229 int v = 0;
230
231 if( !chartValueFor( unitsTokens, aToken, v ) )
232 return false;
233
234 aUnits = (DRILL_CHART_UNITS) v;
235 return true;
236}
237
238
239bool DrillChartColumnFromToken( const wxString& aToken, DRILL_CHART_COLUMN_ID& aId )
240{
241 int v = 0;
242
243 if( !chartValueFor( columnTokens, aToken, v ) )
244 return false;
245
246 aId = (DRILL_CHART_COLUMN_ID) v;
247 return true;
248}
249
250
251bool DrillChartAlignFromToken( const wxString& aToken, DRILL_CHART_ALIGN& aAlign )
252{
253 int v = 0;
254
255 if( !chartValueFor( alignTokens, aToken, v ) )
256 return false;
257
258 aAlign = (DRILL_CHART_ALIGN) v;
259 return true;
260}
261
262
263bool DRILL_CHART_TEMPLATE::SaveToFile( const wxString& aPath, wxString* aError ) const
264{
265 nlohmann::json js;
266 js["name"] = m_name.ToUTF8();
267 js["version"] = m_version;
268 js["units"] = DrillChartUnitsToken( m_units );
269 js["precision"] = m_precision;
270 js["show_totals"] = m_showTotals;
271 js["columns"] = nlohmann::json::array();
272
273 for( const DRILL_CHART_COLUMN& col : m_columns )
274 {
275 nlohmann::json entry;
276 entry["id"] = DrillChartColumnToken( col.m_Id );
277 entry["heading"] = col.m_Heading.ToUTF8();
278 entry["align"] = DrillChartAlignToken( col.m_Align );
279 entry["width"] = col.m_Width;
280 js["columns"].push_back( entry );
281 }
282
283 try
284 {
285 std::ofstream out( aPath.fn_str() );
286
287 if( !out.is_open() )
288 {
289 if( aError )
290 *aError = wxString::Format( _( "Cannot write '%s'." ), aPath );
291
292 return false;
293 }
294
295 out << js.dump( 2 ) << std::endl;
296
297 // failbit does not throw by default, so a full disk would otherwise leave a
298 // truncated template and report success
299 if( !out.good() )
300 {
301 if( aError )
302 *aError = wxString::Format( _( "Error writing '%s'." ), aPath );
303
304 return false;
305 }
306 }
307 catch( const std::exception& e )
308 {
309 if( aError )
310 *aError = wxString::FromUTF8( e.what() );
311
312 return false;
313 }
314
315 return true;
316}
317
318
319bool DRILL_CHART_TEMPLATE::LoadFromFile( const wxString& aPath, wxString* aError )
320{
322
323 // Typed accessors inside the try. Nlohmann throws on a wrong-typed field and it would
324 // otherwise escape through a dialog handler
325 try
326 {
327 std::ifstream in( aPath.fn_str() );
328
329 if( !in.is_open() )
330 {
331 if( aError )
332 *aError = wxString::Format( _( "Cannot read '%s'." ), aPath );
333
334 return false;
335 }
336
337 nlohmann::json js;
338 in >> js;
339
340 if( !js.is_object() )
341 {
342 if( aError )
343 *aError = _( "Template is not a JSON object." );
344
345 return false;
346 }
347
348 if( js.contains( "name" ) && js["name"].is_string() )
349 loaded.m_name = wxString::FromUTF8( js["name"].get<std::string>() );
350
351 if( js.contains( "version" ) && js["version"].is_number_integer() )
352 loaded.m_version = js["version"].get<int>();
353
354 if( js.contains( "units" ) && js["units"].is_string() )
355 {
356 DrillChartUnitsFromToken( wxString::FromUTF8( js["units"].get<std::string>() ),
357 loaded.m_units );
358 }
359
360 if( js.contains( "precision" ) && js["precision"].is_number_integer() )
361 loaded.m_precision = std::clamp( js["precision"].get<int>(), 0, 6 );
362
363 if( js.contains( "show_totals" ) && js["show_totals"].is_boolean() )
364 loaded.m_showTotals = js["show_totals"].get<bool>();
365
366 if( js.contains( "columns" ) && js["columns"].is_array() )
367 {
368 for( const nlohmann::json& entry : js["columns"] )
369 {
370 if( !entry.is_object() || !entry.contains( "id" ) || !entry["id"].is_string() )
371 continue;
372
374
375 // Skipped rather than defaulted, so a template written by a newer KiCad does
376 // not silently turn an unknown column into the symbol column
377 if( !DrillChartColumnFromToken( wxString::FromUTF8( entry["id"].get<std::string>() ),
378 col.m_Id ) )
379 {
380 continue;
381 }
382
383 if( entry.contains( "heading" ) && entry["heading"].is_string() )
384 col.m_Heading = wxString::FromUTF8( entry["heading"].get<std::string>() );
385
386 if( entry.contains( "align" ) && entry["align"].is_string() )
387 {
388 DrillChartAlignFromToken( wxString::FromUTF8( entry["align"].get<std::string>() ),
389 col.m_Align );
390 }
391
392 if( entry.contains( "width" ) && entry["width"].is_number_integer() )
393 col.m_Width = entry["width"].get<int>();
394
395 loaded.m_columns.push_back( col );
396 }
397 }
398 }
399 catch( const std::exception& e )
400 {
401 if( aError )
402 *aError = wxString::FromUTF8( e.what() );
403
404 return false;
405 }
406
407 if( !ValidateDrillChartColumns( loaded.m_columns ) )
408 {
409 if( aError )
410 *aError = _( "Template has no usable columns." );
411
412 return false;
413 }
414
415 *this = std::move( loaded );
416 return true;
417}
BASE_SET & reset(size_t pos)
Definition base_set.h:153
bool SaveToFile(const wxString &aPath, wxString *aError) const
Read and write a template as JSON.
void SetName(const wxString &aName)
wxString m_name
Identify the template file to whoever opens it.
bool LoadFromFile(const wxString &aPath, wxString *aError)
std::vector< DRILL_CHART_COLUMN > & Columns()
static DRILL_CHART_TEMPLATE MakeDefault()
std::vector< DRILL_CHART_COLUMN > m_columns
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllNonCuMask()
Return a mask holding all layer minus CU layers.
Definition lset.cpp:623
const char * DrillChartColumnToken(DRILL_CHART_COLUMN_ID aId)
bool DrillChartAlignFromToken(const wxString &aToken, DRILL_CHART_ALIGN &aAlign)
bool DrillChartDefaultColumn(DRILL_CHART_COLUMN_ID aId, DRILL_CHART_COLUMN &aColumn)
The heading and alignment a column starts with, so the writer can leave them out of the file and the ...
bool ValidateDrillChartColumns(std::vector< DRILL_CHART_COLUMN > &aColumns)
Reject a column set that repeats an id or is implausibly wide.
bool DrillChartUnitsFromToken(const wxString &aToken, DRILL_CHART_UNITS &aUnits)
const char * DrillChartUnitsToken(DRILL_CHART_UNITS aUnits)
Stable file tokens, independent of enum ordering so inserting a value later cannot change what an exi...
LSET DrillDocumentationLayers()
Layers a chart or map may live on.
bool DrillChartColumnFromToken(const wxString &aToken, DRILL_CHART_COLUMN_ID &aId)
const char * DrillChartAlignToken(DRILL_CHART_ALIGN aAlign)
DRILL_CHART_ALIGN
const char * DrillChartColumnToken(DRILL_CHART_COLUMN_ID aId)
bool DrillChartAlignFromToken(const wxString &aToken, DRILL_CHART_ALIGN &aAlign)
constexpr int DRILL_CHART_MAX_COLUMN_WIDTH
A chart column can be no wider than this, and no chart wider than that in total.
DRILL_CHART_COLUMN_ID
bool DrillChartDefaultColumn(DRILL_CHART_COLUMN_ID aId, DRILL_CHART_COLUMN &aColumn)
The heading and alignment a column starts with, so the writer can leave them out of the file and the ...
bool ValidateDrillChartColumns(std::vector< DRILL_CHART_COLUMN > &aColumns)
Reject a column set that repeats an id or is implausibly wide.
constexpr int64_t DRILL_CHART_MAX_TOTAL_WIDTH
bool DrillChartUnitsFromToken(const wxString &aToken, DRILL_CHART_UNITS &aUnits)
const char * DrillChartUnitsToken(DRILL_CHART_UNITS aUnits)
Stable file tokens, independent of enum ordering so inserting a value later cannot change what an exi...
DRILL_CHART_UNITS
bool DrillChartColumnFromToken(const wxString &aToken, DRILL_CHART_COLUMN_ID &aId)
const char * DrillChartAlignToken(DRILL_CHART_ALIGN aAlign)
#define _(s)
@ F_CrtYd
Definition layer_ids.h:112
@ Edge_Cuts
Definition layer_ids.h:108
@ Margin
Definition layer_ids.h:109
@ B_CrtYd
Definition layer_ids.h:111
@ Rescue
Definition layer_ids.h:117
bool operator==(const DRILL_CHART_COLUMN &aOther) const
DRILL_CHART_COLUMN_ID m_Id
DRILL_CHART_ALIGN m_Align
Which holes a chart reports on.
bool operator==(const DRILL_CHART_FILTER &aOther) const