1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
5 * Copyright (C) 2020-2021 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25 #include "kicadpcb.h"
26
27 #include "kicadcurve.h"
28 #include "kicadfootprint.h"
29 #include "oce_utils.h"
30
31 #include <sexpr/sexpr.h>
32 #include <sexpr/sexpr_parser.h>
33
34 #include <wx/filename.h>
35 #include <wx/wxcrtvararg.h>
36
37 #include <memory>
38 #include <string>
39
40
41
42
KICADPCB(const wxString & aPcbName)43 KICADPCB::KICADPCB( const wxString& aPcbName )
44 {
45 m_resolver.Set3DConfigDir( "" );
46 m_thickness = 1.6;
47 m_pcb_model = nullptr;
48 m_minDistance = MIN_DISTANCE;
49 m_useGridOrigin = false;
50 m_useDrillOrigin = false;
51 m_hasGridOrigin = false;
52 m_hasDrillOrigin = false;
53 m_pcbName = aPcbName;
54 }
55
56
~KICADPCB()57 KICADPCB::~KICADPCB()
58 {
59 for( KICADFOOTPRINT* i : m_footprints )
60 delete i;
61
62 for( KICADCURVE* i : m_curves )
63 delete i;
64
65 delete m_pcb_model;
66
67 return;
68 }
69
70
ReadFile(const wxString & aFileName)71 bool KICADPCB::ReadFile( const wxString& aFileName )
72 {
73 wxFileName fname( aFileName );
74
75 if( fname.GetExt() != "kicad_pcb" )
76 {
77 ReportMessage( wxString::Format( "expecting extension kicad_pcb, got %s\n",
78 fname.GetExt() ) );
79 return false;
80 }
81
82 if( !fname.FileExists() )
83 {
84 ReportMessage( wxString::Format( "No such file: %s\n", aFileName ) );
85 return false;
86 }
87
88 fname.Normalize();
89 m_filename = fname.GetFullPath();
90
91 try
92 {
93 SEXPR::PARSER parser;
94 std::string infile( fname.GetFullPath().ToUTF8() );
95 std::unique_ptr<SEXPR::SEXPR> data( parser.ParseFromFile( infile ) );
96
97 if( !data )
98 {
99 ReportMessage( wxString::Format( "No data in file: %s\n", aFileName ) );
100 return false;
101 }
102
103 if( !parsePCB( data.get() ) )
104 return false;
105 }
106 catch( std::exception& e )
107 {
108 ReportMessage( wxString::Format( "error reading file: %s\n%s\n", aFileName, e.what() ) );
109 return false;
110 }
111 catch( ... )
112 {
113 ReportMessage( wxString::Format( "unexpected exception while reading file: %s\n",
114 aFileName ) );
115 return false;
116 }
117
118 return true;
119 }
120
121
WriteSTEP(const wxString & aFileName)122 bool KICADPCB::WriteSTEP( const wxString& aFileName )
123 {
124 if( m_pcb_model )
125 {
126 return m_pcb_model->WriteSTEP( aFileName );
127 }
128
129 return false;
130 }
131
132
133 #ifdef SUPPORTS_IGES
WriteIGES(const wxString & aFileName)134 bool KICADPCB::WriteIGES( const wxString& aFileName )
135 {
136 if( m_pcb_model )
137 {
138 return m_pcb_model->WriteIGES( aFileName );
139 }
140
141 return false;
142 }
143 #endif
144
145
parsePCB(SEXPR::SEXPR * data)146 bool KICADPCB::parsePCB( SEXPR::SEXPR* data )
147 {
148 if( NULL == data )
149 return false;
150
151 if( data->IsList() )
152 {
153 size_t nc = data->GetNumberOfChildren();
154 SEXPR::SEXPR* child = data->GetChild( 0 );
155 std::string name = child->GetSymbol();
156
157 bool result = true;
158
159 for( size_t i = 1; i < nc && result; ++i )
160 {
161 child = data->GetChild( i );
162
163 if( !child->IsList() )
164 {
165 ReportMessage( wxString::Format( "corrupt PCB file (line %d)\n",
166 child->GetLineNumber() ) );
167 return false;
168 }
169
170 std::string symname( child->GetChild( 0 )->GetSymbol() );
171
172 if( symname == "general" )
173 result = result && parseGeneral( child );
174 else if( symname == "setup" )
175 result = result && parseSetup( child );
176 else if( symname == "layers" )
177 result = result && parseLayers( child );
178 else if( symname == "module" )
179 result = result && parseModule( child );
180 else if( symname == "footprint" )
181 result = result && parseModule( child );
182 else if( symname == "gr_arc" )
183 result = result && parseCurve( child, CURVE_ARC );
184 else if( symname == "gr_line" )
185 result = result && parseCurve( child, CURVE_LINE );
186 else if( symname == "gr_rect" )
187 result = result && parseRect( child );
188 else if( symname == "gr_poly" )
189 result = result && parsePolygon( child );
190 else if( symname == "gr_circle" )
191 result = result && parseCurve( child, CURVE_CIRCLE );
192 else if( symname == "gr_curve" )
193 result = result && parseCurve( child, CURVE_BEZIER );
194 }
195
196 return result;
197 }
198
199 ReportMessage( wxString::Format( "data is not a valid PCB file: %s\n", m_filename ) );
200 return false;
201 }
202
203
parseGeneral(SEXPR::SEXPR * data)204 bool KICADPCB::parseGeneral( SEXPR::SEXPR* data )
205 {
206 size_t nc = data->GetNumberOfChildren();
207 SEXPR::SEXPR* child = NULL;
208
209 for( size_t i = 1; i < nc; ++i )
210 {
211 child = data->GetChild( i );
212
213 if( !child->IsList() )
214 {
215 ReportMessage( wxString::Format( "corrupt PCB file (line %d)\n",
216 child->GetLineNumber() ) );
217 return false;
218 }
219
220 // at the moment only the thickness is of interest in the general section
221 if( child->GetChild( 0 )->GetSymbol() != "thickness" )
222 continue;
223
224 m_thickness = child->GetChild( 1 )->GetDouble();
225 return true;
226 }
227
228 ReportMessage( wxString::Format( "corrupt PCB file (line %d)\n"
229 "no PCB thickness specified in general section\n",
230 child->GetLineNumber() ) );
231 return false;
232 }
233
234
parseLayers(SEXPR::SEXPR * data)235 bool KICADPCB::parseLayers( SEXPR::SEXPR* data )
236 {
237 size_t nc = data->GetNumberOfChildren();
238 SEXPR::SEXPR* child = NULL;
239
240 // Read the layername and the corresponding layer id list:
241 for( size_t i = 1; i < nc; ++i )
242 {
243 child = data->GetChild( i );
244
245 if( !child->IsList() )
246 {
247 ReportMessage( wxString::Format( "corrupt PCB file (line %d)\n",
248 child->GetLineNumber() ) );
249 return false;
250 }
251 std::string ref;
252
253 if( child->GetChild( 1 )->IsSymbol() )
254 ref = child->GetChild( 1 )->GetSymbol();
255 else
256 ref = child->GetChild( 1 )->GetString();
257
258 m_layersNames[ref] = child->GetChild( 0 )->GetInteger();
259 }
260
261 return true;
262 }
263
264
GetLayerId(std::string & aLayerName)265 int KICADPCB::GetLayerId( std::string& aLayerName )
266 {
267 int lid = -1;
268 auto item = m_layersNames.find( aLayerName );
269
270 if( item != m_layersNames.end() )
271 lid = item->second;
272
273 return lid;
274 }
275
276
parseSetup(SEXPR::SEXPR * data)277 bool KICADPCB::parseSetup( SEXPR::SEXPR* data )
278 {
279 size_t nc = data->GetNumberOfChildren();
280 SEXPR::SEXPR* child = NULL;
281
282 for( size_t i = 1; i < nc; ++i )
283 {
284 child = data->GetChild( i );
285
286 if( !child->IsList() )
287 {
288 ReportMessage( wxString::Format( "corrupt PCB file (line %d)\n",
289 child->GetLineNumber() ) );
290 return false;
291 }
292
293 // at the moment only the Grid and Drill origins are of interest in the setup section
294 if( child->GetChild( 0 )->GetSymbol() == "grid_origin" )
295 {
296 if( child->GetNumberOfChildren() != 3 )
297 {
298 ReportMessage( wxString::Format( "corrupt PCB file (line %d): grid_origin has "
299 "%d children (expected: 3)\n",
300 child->GetLineNumber(),
301 child->GetNumberOfChildren() ) );
302 return false;
303 }
304
305 m_gridOrigin.x = child->GetChild( 1 )->GetDouble();
306 m_gridOrigin.y = child->GetChild( 2 )->GetDouble();
307 m_hasGridOrigin = true;
308 }
309 else if( child->GetChild( 0 )->GetSymbol() == "aux_axis_origin" )
310 {
311 if( child->GetNumberOfChildren() != 3 )
312 {
313 ReportMessage( wxString::Format( "corrupt PCB file (line %d): aux_axis_origin has"
314 " %d children (expected: 3)\n",
315 child->GetLineNumber(),
316 child->GetNumberOfChildren() ) );
317 return false;
318 }
319
320 m_drillOrigin.x = child->GetChild( 1 )->GetDouble();
321 m_drillOrigin.y = child->GetChild( 2 )->GetDouble();
322 m_hasDrillOrigin = true;
323 }
324
325 }
326
327 return true;
328 }
329
330
parseModule(SEXPR::SEXPR * data)331 bool KICADPCB::parseModule( SEXPR::SEXPR* data )
332 {
333 KICADFOOTPRINT* footprint = new KICADFOOTPRINT( this );
334
335 if( !footprint->Read( data ) )
336 {
337 delete footprint;
338 return false;
339 }
340
341 m_footprints.push_back( footprint );
342 return true;
343 }
344
345
parseRect(SEXPR::SEXPR * data)346 bool KICADPCB::parseRect( SEXPR::SEXPR* data )
347 {
348 KICADCURVE* rect = new KICADCURVE();
349
350 if( !rect->Read( data, CURVE_LINE ) )
351 {
352 delete rect;
353 return false;
354 }
355
356 // reject any curves not on the Edge.Cuts layer
357 if( rect->GetLayer() != LAYER_EDGE )
358 {
359 delete rect;
360 return true;
361 }
362
363 KICADCURVE* top = new KICADCURVE( *rect );
364 KICADCURVE* right = new KICADCURVE( *rect );
365 KICADCURVE* bottom = new KICADCURVE( *rect );
366 KICADCURVE* left = new KICADCURVE( *rect );
367 delete rect;
368
369 top->m_end.y = right->m_start.y;
370 m_curves.push_back( top );
371
372 right->m_start.x = bottom->m_end.x;
373 m_curves.push_back( right );
374
375 bottom->m_start.y = left->m_end.y;
376 m_curves.push_back( bottom );
377
378 left->m_end.x = top->m_start.x;
379 m_curves.push_back( left );
380
381 return true;
382 }
383
384
parsePolygon(SEXPR::SEXPR * data)385 bool KICADPCB::parsePolygon( SEXPR::SEXPR* data )
386 {
387 KICADCURVE* poly = new KICADCURVE();
388
389 if( !poly->Read( data, CURVE_POLYGON ) )
390 {
391 delete poly;
392 return false;
393 }
394
395 // reject any curves not on the Edge.Cuts layer
396 if( poly->GetLayer() != LAYER_EDGE )
397 {
398 delete poly;
399 return true;
400 }
401
402 auto pts = poly->m_poly;
403
404 for( std::size_t ii = 1; ii < pts.size(); ++ii )
405 {
406 KICADCURVE* seg = new KICADCURVE();
407 seg->m_form = CURVE_LINE;
408 seg->m_layer = poly->GetLayer();
409 seg->m_start = pts[ii - 1];
410 seg->m_end = pts[ii];
411 m_curves.push_back( seg );
412 }
413
414 KICADCURVE* seg = new KICADCURVE();
415 seg->m_form = CURVE_LINE;
416 seg->m_layer = poly->GetLayer();
417 seg->m_start = pts.back();
418 seg->m_end = pts.front();
419 m_curves.push_back( seg );
420
421
422 return true;
423 }
424
425
parseCurve(SEXPR::SEXPR * data,CURVE_TYPE aCurveType)426 bool KICADPCB::parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType )
427 {
428 KICADCURVE* curve = new KICADCURVE();
429
430 if( !curve->Read( data, aCurveType ) )
431 {
432 delete curve;
433 return false;
434 }
435
436 // reject any curves not on the Edge.Cuts layer
437 if( curve->GetLayer() != LAYER_EDGE )
438 {
439 delete curve;
440 return true;
441 }
442
443 m_curves.push_back( curve );
444 return true;
445 }
446
447
ComposePCB(bool aComposeVirtual,bool aSubstituteModels)448 bool KICADPCB::ComposePCB( bool aComposeVirtual, bool aSubstituteModels )
449 {
450 if( m_pcb_model )
451 return true;
452
453 if( m_footprints.empty() && m_curves.empty() )
454 {
455 ReportMessage( "Error: no PCB data (no footprint, no outline) to render\n" );
456 return false;
457 }
458
459 DOUBLET origin;
460
461 // Determine the coordinate system reference:
462 // Precedence of reference point is Drill Origin > Grid Origin > User Offset
463 if( m_useDrillOrigin && m_hasDrillOrigin )
464 {
465 origin = m_drillOrigin;
466 }
467 else if( m_useGridOrigin && m_hasDrillOrigin )
468 {
469 origin = m_gridOrigin;
470 }
471 else
472 {
473 origin = m_origin;
474 }
475
476 m_pcb_model = new PCBMODEL( m_pcbName );
477 m_pcb_model->SetPCBThickness( m_thickness );
478 m_pcb_model->SetMinDistance( m_minDistance );
479
480 for( auto i : m_curves )
481 {
482 if( CURVE_NONE == i->m_form || LAYER_EDGE != i->m_layer )
483 continue;
484
485 // adjust the coordinate system
486 // Note: we negate the Y coordinates due to the fact in Pcbnew the Y axis
487 // is from top to bottom.
488 KICADCURVE lcurve = *i;
489 lcurve.m_start.y = -( lcurve.m_start.y - origin.y );
490 lcurve.m_end.y = -( lcurve.m_end.y - origin.y );
491 lcurve.m_start.x -= origin.x;
492 lcurve.m_end.x -= origin.x;
493 // used in bezier curves:
494 lcurve.m_bezierctrl1.y = -( lcurve.m_bezierctrl1.y - origin.y );
495 lcurve.m_bezierctrl1.x -= origin.x;
496 lcurve.m_bezierctrl2.y = -( lcurve.m_bezierctrl2.y - origin.y );
497 lcurve.m_bezierctrl2.x -= origin.x;
498
499 if( CURVE_ARC == lcurve.m_form )
500 lcurve.m_angle = -lcurve.m_angle;
501
502 m_pcb_model->AddOutlineSegment( &lcurve );
503 }
504
505 for( auto i : m_footprints )
506 i->ComposePCB( m_pcb_model, &m_resolver, origin, aComposeVirtual, aSubstituteModels );
507
508 ReportMessage( "Create PCB solid model\n" );
509
510 if( !m_pcb_model->CreatePCB() )
511 {
512 ReportMessage( "could not create PCB solid model\n" );
513 delete m_pcb_model;
514 m_pcb_model = NULL;
515 return false;
516 }
517
518 return true;
519 }
520