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