KiCad PCB EDA Suite
step_pcb_model.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 (C) 2022 Mark Roszko <[email protected]>
5 * Copyright (C) 2016 Cirilo Bernardo <[email protected]>
6 * Copyright (C) 2016-2022 KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26#include <algorithm>
27#include <cmath>
28#include <sstream>
29#include <string>
30#include <utility>
31#include <wx/filename.h>
32#include <wx/filefn.h>
33#include <wx/stdpaths.h>
34#include <wx/wfstream.h>
35#include <wx/zipstrm.h>
36
37#include <decompress.hpp>
38
39#include <footprint.h>
40#include <pad.h>
41
42#include "step_pcb_model.h"
43#include "streamwrapper.h"
44
45#include <IGESCAFControl_Reader.hxx>
46#include <IGESCAFControl_Writer.hxx>
47#include <IGESControl_Controller.hxx>
48#include <IGESData_GlobalSection.hxx>
49#include <IGESData_IGESModel.hxx>
50#include <Interface_Static.hxx>
51#include <Quantity_Color.hxx>
52#include <STEPCAFControl_Reader.hxx>
53#include <STEPCAFControl_Writer.hxx>
54#include <APIHeaderSection_MakeHeader.hxx>
55#include <Standard_Version.hxx>
56#include <TCollection_ExtendedString.hxx>
57#include <TDataStd_Name.hxx>
58#include <TDataStd_TreeNode.hxx>
59#include <TDF_LabelSequence.hxx>
60#include <TDF_ChildIterator.hxx>
61#include <TopExp_Explorer.hxx>
62#include <XCAFDoc.hxx>
63#include <XCAFDoc_DocumentTool.hxx>
64#include <XCAFDoc_ColorTool.hxx>
65
66#include <BRep_Tool.hxx>
67#include <BRepMesh_IncrementalMesh.hxx>
68#include <BRepBuilderAPI.hxx>
69#include <BRepBuilderAPI_MakeEdge.hxx>
70#include <BRepBuilderAPI_Transform.hxx>
71#include <BRepBuilderAPI_GTransform.hxx>
72#include <BRepBuilderAPI_MakeFace.hxx>
73#include <BRepPrimAPI_MakePrism.hxx>
74#include <BRepPrimAPI_MakeCylinder.hxx>
75#include <BRepAlgoAPI_Cut.hxx>
76
77#include <TopoDS.hxx>
78#include <TopoDS_Wire.hxx>
79#include <TopoDS_Face.hxx>
80#include <TopoDS_Compound.hxx>
81#include <TopoDS_Builder.hxx>
82
83#include <Standard_Failure.hxx>
84
85#include <gp_Ax2.hxx>
86#include <gp_Circ.hxx>
87#include <gp_Dir.hxx>
88#include <gp_Pnt.hxx>
89#include <Geom_BezierCurve.hxx>
90
91#include <macros.h>
92
93static constexpr double USER_PREC = 1e-4;
94static constexpr double USER_ANGLE_PREC = 1e-6;
95
96// minimum PCB thickness in mm (2 microns assumes a very thin polyimide film)
97static constexpr double THICKNESS_MIN = 0.002;
98
99// default PCB thickness in mm
100static constexpr double THICKNESS_DEFAULT = 1.6;
101
102// nominal offset from the board
103static constexpr double BOARD_OFFSET = 0.05;
104
105// min. length**2 below which 2 points are considered coincident
107
108
109// supported file types
111{
119 FMT_WRZ
121
122
123FormatType fileType( const char* aFileName )
124{
125 wxFileName lfile( wxString::FromUTF8Unchecked( aFileName ) );
126
127 if( !lfile.FileExists() )
128 {
129 wxString msg;
130 msg.Printf( wxT( " * fileType(): no such file: %s\n" ),
131 wxString::FromUTF8Unchecked( aFileName ) );
132
133 ReportMessage( msg );
134 return FMT_NONE;
135 }
136
137 wxString ext = lfile.GetExt().Lower();
138
139 if( ext == wxT( "wrl" ) )
140 return FMT_WRL;
141
142 if( ext == wxT( "wrz" ) )
143 return FMT_WRZ;
144
145 if( ext == wxT( "idf" ) )
146 return FMT_IDF; // component outline
147
148 if( ext == wxT( "emn" ) )
149 return FMT_EMN; // PCB assembly
150
151 if( ext == wxT( "stpz" ) || ext == wxT( "gz" ) )
152 return FMT_STEPZ;
153
154 OPEN_ISTREAM( ifile, aFileName );
155
156 if( ifile.fail() )
157 return FMT_NONE;
158
159 char iline[82];
160 memset( iline, 0, 82 );
161 ifile.getline( iline, 82 );
162 CLOSE_STREAM( ifile );
163 iline[81] = 0; // ensure NULL termination when string is too long
164
165 // check for STEP in Part 21 format
166 // (this can give false positives since Part 21 is not exclusively STEP)
167 if( !strncmp( iline, "ISO-10303-21;", 13 ) )
168 return FMT_STEP;
169
170 std::string fstr = iline;
171
172 // check for STEP in XML format
173 // (this can give both false positive and false negatives)
174 if( fstr.find( "urn:oid:1.0.10303." ) != std::string::npos )
175 return FMT_STEP;
176
177 // Note: this is a very simple test which can yield false positives; the only
178 // sure method for determining if a file *not* an IGES model is to attempt
179 // to load it.
180 if( iline[72] == 'S' && ( iline[80] == 0 || iline[80] == 13 || iline[80] == 10 ) )
181 return FMT_IGES;
182
183 return FMT_NONE;
184}
185
186
187STEP_PCB_MODEL::STEP_PCB_MODEL( const wxString& aPcbName )
188{
189 m_app = XCAFApp_Application::GetApplication();
190 m_app->NewDocument( "MDTV-XCAF", m_doc );
191 m_assy = XCAFDoc_DocumentTool::ShapeTool ( m_doc->Main() );
192 m_assy_label = m_assy->NewShape();
193 m_hasPCB = false;
194 m_components = 0;
199 m_minx = 1.0e10; // absurdly large number; any valid PCB X value will be smaller
200 m_pcbName = aPcbName;
201 BRepBuilderAPI::Precision( STEPEXPORT_MIN_DISTANCE );
202 m_maxError = 5000; // 5 microns
203}
204
205
207{
208 m_doc->Close();
209}
210
211
212bool STEP_PCB_MODEL::AddPadHole( const PAD* aPad, const VECTOR2D& aOrigin )
213{
214 if( NULL == aPad || !aPad->GetDrillSize().x )
215 return false;
216
217 VECTOR2I pos = aPad->GetPosition();
218
220 {
221 TopoDS_Shape s =
222 BRepPrimAPI_MakeCylinder( pcbIUScale.IUTomm( aPad->GetDrillSize().x ) * 0.5, m_thickness * 2.0 ).Shape();
223 gp_Trsf shift;
224 shift.SetTranslation( gp_Vec( pcbIUScale.IUTomm( pos.x - aOrigin.x ),
225 -pcbIUScale.IUTomm( pos.y - aOrigin.y ),
226 -m_thickness * 0.5 ) );
227 BRepBuilderAPI_Transform hole( s, shift );
228 m_cutouts.push_back( hole.Shape() );
229 return true;
230 }
231
232 // slotted hole
233 SHAPE_POLY_SET holeOutlines;
234 if( !aPad->TransformHoleToPolygon( holeOutlines, 0, m_maxError, ERROR_INSIDE ) )
235 {
236 return false;
237 }
238
239 TopoDS_Shape hole;
240
241 if( holeOutlines.OutlineCount() > 0 )
242 {
243 if( MakeShape( hole, holeOutlines.COutline( 0 ), m_thickness, aOrigin ) )
244 {
245 m_cutouts.push_back( hole );
246 }
247 }
248 else
249 {
250 return false;
251 }
252
253 return true;
254}
255
256
257bool STEP_PCB_MODEL::AddComponent( const std::string& aFileNameUTF8, const std::string& aRefDes,
258 bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset,
259 VECTOR3D aOrientation, VECTOR3D aScale, bool aSubstituteModels )
260{
261 if( aFileNameUTF8.empty() )
262 {
263 ReportMessage( wxString::Format( wxT( "No model defined for component %s.\n" ), aRefDes ) );
264 return false;
265 }
266
267 wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
268 ReportMessage( wxString::Format( wxT( "Add component %s.\n" ), aRefDes ) );
269
270 // first retrieve a label
271 TDF_Label lmodel;
272 wxString errorMessage;
273
274 if( !getModelLabel( aFileNameUTF8, aScale, lmodel, aSubstituteModels, &errorMessage ) )
275 {
276 if( errorMessage.IsEmpty() )
277 ReportMessage( wxString::Format( wxT( "No model for filename '%s'.\n" ), fileName ) );
278 else
280
281 return false;
282 }
283
284 // calculate the Location transform
285 TopLoc_Location toploc;
286
287 if( !getModelLocation( aBottom, aPosition, aRotation, aOffset, aOrientation, toploc ) )
288 {
290 wxString::Format( wxT( "No location data for filename '%s'.\n" ), fileName ) );
291 return false;
292 }
293
294 // add the located sub-assembly
295 TDF_Label llabel = m_assy->AddComponent( m_assy_label, lmodel, toploc );
296
297 if( llabel.IsNull() )
298 {
299 ReportMessage( wxString::Format( wxT( "Could not add component with filename '%s'.\n" ),
300 fileName ) );
301 return false;
302 }
303
304 // attach the RefDes name
305 TCollection_ExtendedString refdes( aRefDes.c_str() );
306 TDataStd_Name::Set( llabel, refdes );
307
308 return true;
309}
310
311
312void STEP_PCB_MODEL::SetPCBThickness( double aThickness )
313{
314 if( aThickness < 0.0 )
316 else if( aThickness < THICKNESS_MIN )
318 else
319 m_thickness = aThickness;
320}
321
322
323void STEP_PCB_MODEL::SetBoardColor( double r, double g, double b )
324{
325 m_boardColor[0] = r;
326 m_boardColor[1] = g;
327 m_boardColor[2] = b;
328}
329
330
331void STEP_PCB_MODEL::SetMinDistance( double aDistance )
332{
333 // Ensure a minimal value (in mm)
334 aDistance = std::max( aDistance, STEPEXPORT_MIN_ACCEPTABLE_DISTANCE );
335
336 // m_minDistance2 keeps a squared distance value
337 m_minDistance2 = aDistance * aDistance;
338 BRepBuilderAPI::Precision( aDistance );
339}
340
342{
343 return m_pcb_labels.size() > 0;
344}
345
346
347bool STEP_PCB_MODEL::MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aChain,
348 double aThickness, const VECTOR2D& aOrigin )
349{
350 if( !aShape.IsNull() )
351 return false; // there is already data in the shape object
352
353 if( !aChain.IsClosed() )
354 return false; // the loop is not closed
355
356 BRepBuilderAPI_MakeWire wire;
357 TopoDS_Edge edge;
358 bool success = true;
359
360 gp_Pnt start = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( 0 ).x - aOrigin.x ),
361 -pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), 0.0 );
362
363 for( int j = 0; j < aChain.PointCount(); j++ )
364 {
365 int next = j+1;
366
367 gp_Pnt end;
368
369 if( next >= aChain.PointCount() )
370 {
371 end = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( 0 ).x - aOrigin.x ),
372 -pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), 0.0 );
373 }
374 else
375 {
376 end = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( next ).x - aOrigin.x ),
377 -pcbIUScale.IUTomm( aChain.CPoint( next ).y - aOrigin.y ), 0.0 );
378 }
379
380 // Do not export very small segments: they can create broken outlines
381 double seg_len = std::abs( end.X() - start.X()) + std::abs(start.Y() - end.Y() );
382 double min_len = 0.0001; // In mm, i.e. 0.1 micron
383
384 if( seg_len < min_len )
385 continue;
386
387 try
388 {
389 edge = BRepBuilderAPI_MakeEdge( start, end );
390 wire.Add( edge );
391
392 if( BRepBuilderAPI_WireDone != wire.Error() )
393 {
394 ReportMessage( wxT( "failed to add curve\n" ) );
395 return false;
396 }
397 }
398 catch( const Standard_Failure& e )
399 {
401 wxString::Format( wxT( "OCC exception: %s\n" ), e.GetMessageString() ) );
402 success = false;
403 }
404
405 if( !success )
406 {
407 ReportMessage( wxS( "failed to add edge\n" ) );
408 return false;
409 }
410
411 start = end;
412 }
413
414 BRepBuilderAPI_MakeFace face;
415
416 try
417 {
418 face = BRepBuilderAPI_MakeFace( wire );
419 }
420 catch( const Standard_Failure& e )
421 {
423 wxString::Format( wxT( "OCC exception: %s\n" ), e.GetMessageString() ) );
424 return false;
425 }
426
427 aShape = BRepPrimAPI_MakePrism( face, gp_Vec( 0, 0, aThickness ) );
428
429 if( aShape.IsNull() )
430 {
431 ReportMessage( wxT( "failed to create a prismatic shape\n" ) );
432 return false;
433 }
434
435 return true;
436}
437
438
440{
441 if( m_hasPCB )
442 {
443 if( !isBoardOutlineValid() )
444 return false;
445
446 return true;
447 }
448
449 m_hasPCB = true; // whether or not operations fail we note that CreatePCB has been invoked
450
451 // Support for more than one main outline (more than one board)
452 std::vector<TopoDS_Shape> board_outlines;
453
454 for( int cnt = 0; cnt < aOutline.OutlineCount(); cnt++ )
455 {
456 const SHAPE_LINE_CHAIN& outline = aOutline.COutline( cnt );
457
458 ReportMessage( wxString::Format( wxT( "Build board main outline %d with %d points.\n" ),
459 cnt+1, outline.PointCount() ) );
460
461 TopoDS_Shape curr_brd;
462
463 if( !MakeShape( curr_brd, outline, m_thickness, aOrigin ) )
464 {
465 // Error
467 wxT( "OCC error adding main outline polygon %d with %d points.\n" ),
468 cnt+1, outline.PointCount() ) );
469 }
470 else
471 board_outlines.push_back( curr_brd );
472
473 // Generate board cutouts in current main outline:
474 if( aOutline.HoleCount( cnt ) > 0 )
475 {
476 ReportMessage( wxString::Format( wxT( "Add cutouts in outline %d (%d cutout(s)).\n" ),
477 cnt+1, aOutline.HoleCount( cnt ) ) );
478 }
479
480 for( int ii = 0; ii < aOutline.HoleCount( cnt ); ii++ )
481 {
482 const SHAPE_LINE_CHAIN& holeOutline = aOutline.Hole( cnt, ii );
483 TopoDS_Shape hole;
484
485 if( MakeShape( hole, holeOutline, m_thickness, aOrigin ) )
486 {
487 m_cutouts.push_back( hole );
488 }
489 }
490 }
491
492 // subtract cutouts (if any)
493 if( m_cutouts.size() )
494 {
495 ReportMessage( wxString::Format( wxT( "Build board cutouts and holes (%d hole(s)).\n" ),
496 (int) m_cutouts.size() ) );
497
498 TopTools_ListOfShape holelist;
499
500 for( TopoDS_Shape& hole : m_cutouts )
501 holelist.Append( hole );
502
503 // Remove holes for each board (usually there is only one board
504 for( TopoDS_Shape& board: board_outlines )
505 {
506 BRepAlgoAPI_Cut Cut;
507 TopTools_ListOfShape mainbrd;
508
509 mainbrd.Append( board );
510
511 Cut.SetArguments( mainbrd );
512
513 Cut.SetTools( holelist );
514 Cut.Build();
515
516 board = Cut.Shape();
517 }
518 }
519
520 // push the board to the data structure
521 ReportMessage( wxT( "\nGenerate board full shape.\n" ) );
522
523 // Dont expand the component or else coloring it gets hard
524 for( TopoDS_Shape& board: board_outlines )
525 {
526 m_pcb_labels.push_back( m_assy->AddComponent( m_assy_label, board, false ) );
527
528 if( m_pcb_labels.back().IsNull() )
529 return false;
530 }
531
532 // AddComponent adds a label that has a reference (not a parent/child relation) to the real
533 // label. We need to extract that real label to name it for the STEP output cleanly
534 // Why are we trying to name the bare board? Because CAD tools like SolidWorks do fun things
535 // like "deduplicate" imported STEPs by swapping STEP assembly components with already
536 // identically named assemblies. So we want to avoid having the PCB be generally defaulted
537 // to "Component" or "Assembly".
538
539 // color the PCB
540 Handle( XCAFDoc_ColorTool ) colorTool = XCAFDoc_DocumentTool::ColorTool( m_doc->Main() );
541 Quantity_Color color( m_boardColor[0], m_boardColor[1], m_boardColor[2], Quantity_TOC_RGB );
542
543 int pcbIdx = 1;
544
545 for( TDF_Label& pcb_label : m_pcb_labels )
546 {
547 colorTool->SetColor( pcb_label, color, XCAFDoc_ColorSurf );
548
549 Handle( TDataStd_TreeNode ) node;
550
551 if( pcb_label.FindAttribute( XCAFDoc::ShapeRefGUID(), node ) )
552 {
553 // Gives a name to each board object
554 TDF_Label label = node->Father()->Label();
555
556 if( !label.IsNull() )
557 {
558 wxString pcbName;
559
560 if( m_pcb_labels.size() == 1 )
561 pcbName = wxT( "PCB" );
562 else
563 pcbName = wxString::Format( wxT( "PCB%d" ), pcbIdx++ );
564
565 std::string pcbNameStdString( pcbName.ToUTF8() );
566 TCollection_ExtendedString partname( pcbNameStdString.c_str() );
567 TDataStd_Name::Set( label, partname );
568 }
569 }
570
571 TopExp_Explorer topex;
572 // color the PCB
573 topex.Init( m_assy->GetShape( pcb_label ), TopAbs_SOLID );
574
575 while( topex.More() )
576 {
577 colorTool->SetColor( topex.Current(), color, XCAFDoc_ColorSurf );
578 topex.Next();
579 }
580 }
581
582#if( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 )
583 m_assy->UpdateAssemblies();
584#endif
585
586 return true;
587}
588
589
590#ifdef SUPPORTS_IGES
591// write the assembly model in IGES format
592bool STEP_PCB_MODEL::WriteIGES( const wxString& aFileName )
593{
594 if( !isBoardOutlineValid() )
595 {
596 ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
597 "'%s'.\n" ),
598 aFileName ) );
599 return false;
600 }
601
602 wxFileName fn( aFileName );
604 IGESCAFControl_Writer writer;
605 writer.SetColorMode( Standard_True );
606 writer.SetNameMode( Standard_True );
607 IGESData_GlobalSection header = writer.Model()->GlobalSection();
608 header.SetFileName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
609 header.SetSendName( new TCollection_HAsciiString( "KiCad electronic assembly" ) );
610 header.SetAuthorName(
611 new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.author" ) ) );
612 header.SetCompanyName(
613 new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.company" ) ) );
614 writer.Model()->SetGlobalSection( header );
615
616 if( Standard_False == writer.Perform( m_doc, aFileName.c_str() ) )
617 return false;
618
619 return true;
620}
621#endif
622
623
624bool STEP_PCB_MODEL::WriteSTEP( const wxString& aFileName )
625{
626 if( !isBoardOutlineValid() )
627 {
628 ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
629 "'%s'.\n" ),
630 aFileName ) );
631 return false;
632 }
633
634 wxFileName fn( aFileName );
635
636 STEPCAFControl_Writer writer;
637 writer.SetColorMode( Standard_True );
638 writer.SetNameMode( Standard_True );
639
640 // This must be set before we "transfer" the document.
641 // Should default to kicad_pcb.general.title_block.title,
642 // but in the meantime, defaulting to the basename of the output
643 // target is still better than "open cascade step translter v..."
644 // UTF8 should be ok from ISO 10303-21:2016, but... older stuff? use boring ascii
645 if( !Interface_Static::SetCVal( "write.step.product.name", fn.GetName().ToAscii() ) )
646 ReportMessage( wxT( "Failed to set step product name, but will attempt to continue." ) );
647
648 if( Standard_False == writer.Transfer( m_doc, STEPControl_AsIs ) )
649 return false;
650
651 APIHeaderSection_MakeHeader hdr( writer.ChangeWriter().Model() );
652
653 // Note: use only Ascii7 chars, non Ascii7 chars (therefore UFT8 chars)
654 // are creating issues in the step file
655 hdr.SetName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
656
657 // TODO: how to control and ensure consistency with IGES?
658 hdr.SetAuthorValue( 1, new TCollection_HAsciiString( "Pcbnew" ) );
659 hdr.SetOrganizationValue( 1, new TCollection_HAsciiString( "Kicad" ) );
660 hdr.SetOriginatingSystem( new TCollection_HAsciiString( "KiCad to STEP converter" ) );
661 hdr.SetDescriptionValue( 1, new TCollection_HAsciiString( "KiCad electronic assembly" ) );
662
663 bool success = true;
664
665 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
666 wxString currCWD = wxGetCwd();
667 wxString workCWD = fn.GetPath();
668
669 if( !workCWD.IsEmpty() )
670 wxSetWorkingDirectory( workCWD );
671
672 char tmpfname[] = "$tempfile$.step";
673
674 if( Standard_False == writer.Write( tmpfname ) )
675 success = false;
676
677 if( success )
678 {
679 if( !wxRenameFile( tmpfname, fn.GetFullName(), true ) )
680 {
681 ReportMessage( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
682 tmpfname,
683 fn.GetFullName() ) );
684 success = false;
685 }
686 }
687
688 wxSetWorkingDirectory( currCWD );
689
690 return success;
691}
692
693
694bool STEP_PCB_MODEL::getModelLabel( const std::string& aFileNameUTF8, VECTOR3D aScale, TDF_Label& aLabel,
695 bool aSubstituteModels, wxString* aErrorMessage )
696{
697 std::string model_key = aFileNameUTF8 + "_" + std::to_string( aScale.x )
698 + "_" + std::to_string( aScale.y ) + "_" + std::to_string( aScale.z );
699
700 MODEL_MAP::const_iterator mm = m_models.find( model_key );
701
702 if( mm != m_models.end() )
703 {
704 aLabel = mm->second;
705 return true;
706 }
707
708 aLabel.Nullify();
709
710 Handle( TDocStd_Document ) doc;
711 m_app->NewDocument( "MDTV-XCAF", doc );
712
713 wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
714 FormatType modelFmt = fileType( aFileNameUTF8.c_str() );
715
716 switch( modelFmt )
717 {
718 case FMT_IGES:
719 if( !readIGES( doc, aFileNameUTF8.c_str() ) )
720 {
721 ReportMessage( wxString::Format( wxT( "readIGES() failed on filename '%s'.\n" ),
722 fileName ) );
723 return false;
724 }
725 break;
726
727 case FMT_STEP:
728 if( !readSTEP( doc, aFileNameUTF8.c_str() ) )
729 {
730 ReportMessage( wxString::Format( wxT( "readSTEP() failed on filename '%s'.\n" ),
731 fileName ) );
732 return false;
733 }
734 break;
735
736 case FMT_STEPZ:
737 {
738 // To export a compressed step file (.stpz or .stp.gz file), the best way is to
739 // decaompress it in a temporaty file and load this temporary file
740 wxFFileInputStream ifile( fileName );
741 wxFileName outFile( fileName );
742
743 outFile.SetPath( wxStandardPaths::Get().GetTempDir() );
744 outFile.SetExt( wxT( "step" ) );
745 wxFileOffset size = ifile.GetLength();
746
747 if( size == wxInvalidOffset )
748 {
749 ReportMessage( wxString::Format( wxT( "getModelLabel() failed on filename '%s'.\n" ),
750 fileName ) );
751 return false;
752 }
753
754 {
755 bool success = false;
756 wxFFileOutputStream ofile( outFile.GetFullPath() );
757
758 if( !ofile.IsOk() )
759 return false;
760
761 char* buffer = new char[size];
762
763 ifile.Read( buffer, size );
764 std::string expanded;
765
766 try
767 {
768 expanded = gzip::decompress( buffer, size );
769 success = true;
770 }
771 catch( ... )
772 {
773 ReportMessage( wxString::Format( wxT( "failed to decompress '%s'.\n" ),
774 fileName ) );
775 }
776
777 if( expanded.empty() )
778 {
779 ifile.Reset();
780 ifile.SeekI( 0 );
781 wxZipInputStream izipfile( ifile );
782 std::unique_ptr<wxZipEntry> zip_file( izipfile.GetNextEntry() );
783
784 if( zip_file && !zip_file->IsDir() && izipfile.CanRead() )
785 {
786 izipfile.Read( ofile );
787 success = true;
788 }
789 }
790 else
791 {
792 ofile.Write( expanded.data(), expanded.size() );
793 }
794
795 delete[] buffer;
796 ofile.Close();
797
798 if( success )
799 {
800 std::string altFileNameUTF8 = TO_UTF8( outFile.GetFullPath() );
801 success =
802 getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false );
803 }
804
805 return success;
806 }
807
808 break;
809 }
810
811 case FMT_WRL:
812 case FMT_WRZ:
813 /* WRL files are preferred for internal rendering, due to superior material properties, etc.
814 * However they are not suitable for MCAD export.
815 *
816 * If a .wrl file is specified, attempt to locate a replacement file for it.
817 *
818 * If a valid replacement file is found, the label for THAT file will be associated with
819 * the .wrl file
820 */
821 if( aSubstituteModels )
822 {
823 wxFileName wrlName( fileName );
824
825 wxString basePath = wrlName.GetPath();
826 wxString baseName = wrlName.GetName();
827
828 // List of alternate files to look for
829 // Given in order of preference
830 // (Break if match is found)
831 wxArrayString alts;
832
833 // Step files
834 alts.Add( wxT( "stp" ) );
835 alts.Add( wxT( "step" ) );
836 alts.Add( wxT( "STP" ) );
837 alts.Add( wxT( "STEP" ) );
838 alts.Add( wxT( "Stp" ) );
839 alts.Add( wxT( "Step" ) );
840 alts.Add( wxT( "stpz" ) );
841 alts.Add( wxT( "stpZ" ) );
842 alts.Add( wxT( "STPZ" ) );
843 alts.Add( wxT( "step.gz" ) );
844 alts.Add( wxT( "stp.gz" ) );
845
846 // IGES files
847 alts.Add( wxT( "iges" ) );
848 alts.Add( wxT( "IGES" ) );
849 alts.Add( wxT( "igs" ) );
850 alts.Add( wxT( "IGS" ) );
851
852 //TODO - Other alternative formats?
853
854 for( const auto& alt : alts )
855 {
856 wxFileName altFile( basePath, baseName + wxT( "." ) + alt );
857
858 if( altFile.IsOk() && altFile.FileExists() )
859 {
860 std::string altFileNameUTF8 = TO_UTF8( altFile.GetFullPath() );
861
862 // When substituting a STEP/IGS file for VRML, do not apply the VRML scaling
863 // to the new STEP model. This process of auto-substitution is janky as all
864 // heck so let's not mix up un-displayed scale factors with potentially
865 // mis-matched files. And hope that the user doesn't have multiples files
866 // named "model.wrl" and "model.stp" referring to different parts.
867 // TODO: Fix model handling in v7. Default models should only be STP.
868 // Have option to override this in DISPLAY.
869 if( getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false ) )
870 {
871 return true;
872 }
873 }
874 }
875
876 return false; // No replacement model found
877 }
878 else // Substitution is not allowed
879 {
880 if( aErrorMessage )
881 aErrorMessage->Printf( wxT( "Cannot add a VRML model to a STEP file.\n" ) );
882
883 return false;
884 }
885
886 break;
887
888 // TODO: implement IDF and EMN converters
889
890 default:
891 return false;
892 }
893
894 aLabel = transferModel( doc, m_doc, aScale );
895
896 if( aLabel.IsNull() )
897 {
898 ReportMessage( wxString::Format( wxT( "Could not transfer model data from file '%s'.\n" ),
899 fileName ) );
900 return false;
901 }
902
903 // attach the PART NAME ( base filename: note that in principle
904 // different models may have the same base filename )
905 wxFileName afile( fileName );
906 std::string pname( afile.GetName().ToUTF8() );
907 TCollection_ExtendedString partname( pname.c_str() );
908 TDataStd_Name::Set( aLabel, partname );
909
910 m_models.insert( MODEL_DATUM( model_key, aLabel ) );
911 ++m_components;
912 return true;
913}
914
915
916bool STEP_PCB_MODEL::getModelLocation( bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset, VECTOR3D aOrientation,
917 TopLoc_Location& aLocation )
918{
919 // Order of operations:
920 // a. aOrientation is applied -Z*-Y*-X
921 // b. aOffset is applied
922 // Top ? add thickness to the Z offset
923 // c. Bottom ? Rotate on X axis (in contrast to most ECAD which mirror on Y),
924 // then rotate on +Z
925 // Top ? rotate on -Z
926 // d. aPosition is applied
927 //
928 // Note: Y axis is inverted in KiCad
929
930 gp_Trsf lPos;
931 lPos.SetTranslation( gp_Vec( aPosition.x, -aPosition.y, 0.0 ) );
932
933 // Offset board thickness
934 aOffset.z += BOARD_OFFSET;
935
936 gp_Trsf lRot;
937
938 if( aBottom )
939 {
940 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
941 lPos.Multiply( lRot );
942 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), M_PI );
943 lPos.Multiply( lRot );
944 }
945 else
946 {
947 aOffset.z += m_thickness;
948 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
949 lPos.Multiply( lRot );
950 }
951
952 gp_Trsf lOff;
953 lOff.SetTranslation( gp_Vec( aOffset.x, aOffset.y, aOffset.z ) );
954 lPos.Multiply( lOff );
955
956 gp_Trsf lOrient;
957 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ),
958 -aOrientation.z );
959 lPos.Multiply( lOrient );
960 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 1.0, 0.0 ) ),
961 -aOrientation.y );
962 lPos.Multiply( lOrient );
963 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ),
964 -aOrientation.x );
965 lPos.Multiply( lOrient );
966
967 aLocation = TopLoc_Location( lPos );
968 return true;
969}
970
971
972bool STEP_PCB_MODEL::readIGES( Handle( TDocStd_Document )& doc, const char* fname )
973{
975 IGESCAFControl_Reader reader;
976 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
977
978 if( stat != IFSelect_RetDone )
979 return false;
980
981 // Enable user-defined shape precision
982 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
983 return false;
984
985 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
986 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
987 return false;
988
989 // set other translation options
990 reader.SetColorMode( true ); // use model colors
991 reader.SetNameMode( false ); // don't use IGES label names
992 reader.SetLayerMode( false ); // ignore LAYER data
993
994 if( !reader.Transfer( doc ) )
995 {
996 doc->Close();
997 return false;
998 }
999
1000 // are there any shapes to translate?
1001 if( reader.NbShapes() < 1 )
1002 {
1003 doc->Close();
1004 return false;
1005 }
1006
1007 return true;
1008}
1009
1010
1011bool STEP_PCB_MODEL::readSTEP( Handle( TDocStd_Document )& doc, const char* fname )
1012{
1013 STEPCAFControl_Reader reader;
1014 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
1015
1016 if( stat != IFSelect_RetDone )
1017 return false;
1018
1019 // Enable user-defined shape precision
1020 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
1021 return false;
1022
1023 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
1024 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
1025 return false;
1026
1027 // set other translation options
1028 reader.SetColorMode( true ); // use model colors
1029 reader.SetNameMode( false ); // don't use label names
1030 reader.SetLayerMode( false ); // ignore LAYER data
1031
1032 if( !reader.Transfer( doc ) )
1033 {
1034 doc->Close();
1035 return false;
1036 }
1037
1038 // are there any shapes to translate?
1039 if( reader.NbRootsForTransfer() < 1 )
1040 {
1041 doc->Close();
1042 return false;
1043 }
1044
1045 return true;
1046}
1047
1048
1049TDF_Label STEP_PCB_MODEL::transferModel( Handle( TDocStd_Document )& source,
1050 Handle( TDocStd_Document )& dest, VECTOR3D aScale )
1051{
1052 // transfer data from Source into a top level component of Dest
1053 gp_GTrsf scale_transform;
1054 scale_transform.SetVectorialPart( gp_Mat( aScale.x, 0, 0,
1055 0, aScale.y, 0,
1056 0, 0, aScale.z ) );
1057 BRepBuilderAPI_GTransform brep( scale_transform );
1058
1059 // s_assy = shape tool for the source
1060 Handle(XCAFDoc_ShapeTool) s_assy = XCAFDoc_DocumentTool::ShapeTool( source->Main() );
1061
1062 // retrieve all free shapes within the assembly
1063 TDF_LabelSequence frshapes;
1064 s_assy->GetFreeShapes( frshapes );
1065
1066 // d_assy = shape tool for the destination
1067 Handle( XCAFDoc_ShapeTool ) d_assy = XCAFDoc_DocumentTool::ShapeTool ( dest->Main() );
1068
1069 // create a new shape within the destination and set the assembly tool to point to it
1070 TDF_Label component = d_assy->NewShape();
1071
1072 int nshapes = frshapes.Length();
1073 int id = 1;
1074 Handle( XCAFDoc_ColorTool ) scolor = XCAFDoc_DocumentTool::ColorTool( source->Main() );
1075 Handle( XCAFDoc_ColorTool ) dcolor = XCAFDoc_DocumentTool::ColorTool( dest->Main() );
1076 TopExp_Explorer dtop;
1077 TopExp_Explorer stop;
1078
1079 while( id <= nshapes )
1080 {
1081 TopoDS_Shape shape = s_assy->GetShape( frshapes.Value( id ) );
1082
1083 if( !shape.IsNull() )
1084 {
1085 TopoDS_Shape scaled_shape( shape );
1086
1087 if( aScale.x != 1.0 || aScale.y != 1.0 || aScale.z != 1.0 )
1088 {
1089 brep.Perform( shape, Standard_False );
1090
1091 if( brep.IsDone() )
1092 {
1093 scaled_shape = brep.Shape();
1094 }
1095 else
1096 {
1097 ReportMessage( wxT( " * transfertModel(): failed to scale model\n" ) );
1098
1099 scaled_shape = shape;
1100 }
1101 }
1102
1103 TDF_Label niulab = d_assy->AddComponent( component, scaled_shape, Standard_False );
1104
1105 // check for per-surface colors
1106 stop.Init( shape, TopAbs_FACE );
1107 dtop.Init( d_assy->GetShape( niulab ), TopAbs_FACE );
1108
1109 while( stop.More() && dtop.More() )
1110 {
1111 Quantity_Color face_color;
1112
1113 TDF_Label tl;
1114
1115 // give priority to the base shape's color
1116 if( s_assy->FindShape( stop.Current(), tl ) )
1117 {
1118 if( scolor->GetColor( tl, XCAFDoc_ColorSurf, face_color )
1119 || scolor->GetColor( tl, XCAFDoc_ColorGen, face_color )
1120 || scolor->GetColor( tl, XCAFDoc_ColorCurv, face_color ) )
1121 {
1122 dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf );
1123 }
1124 }
1125 else if( scolor->GetColor( stop.Current(), XCAFDoc_ColorSurf, face_color )
1126 || scolor->GetColor( stop.Current(), XCAFDoc_ColorGen, face_color )
1127 || scolor->GetColor( stop.Current(), XCAFDoc_ColorCurv, face_color ) )
1128 {
1129 dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf );
1130 }
1131
1132 stop.Next();
1133 dtop.Next();
1134 }
1135
1136 // check for per-solid colors
1137 stop.Init( shape, TopAbs_SOLID );
1138 dtop.Init( d_assy->GetShape( niulab ), TopAbs_SOLID, TopAbs_FACE );
1139
1140 while( stop.More() && dtop.More() )
1141 {
1142 Quantity_Color face_color;
1143
1144 TDF_Label tl;
1145
1146 // give priority to the base shape's color
1147 if( s_assy->FindShape( stop.Current(), tl ) )
1148 {
1149 if( scolor->GetColor( tl, XCAFDoc_ColorSurf, face_color )
1150 || scolor->GetColor( tl, XCAFDoc_ColorGen, face_color )
1151 || scolor->GetColor( tl, XCAFDoc_ColorCurv, face_color ) )
1152 {
1153 dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorGen );
1154 }
1155 }
1156 else if( scolor->GetColor( stop.Current(), XCAFDoc_ColorSurf, face_color )
1157 || scolor->GetColor( stop.Current(), XCAFDoc_ColorGen, face_color )
1158 || scolor->GetColor( stop.Current(), XCAFDoc_ColorCurv, face_color ) )
1159 {
1160 dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf );
1161 }
1162
1163 stop.Next();
1164 dtop.Next();
1165 }
1166 }
1167
1168 ++id;
1169 };
1170
1171 return component;
1172}
int color
Definition: DXF_plotter.cpp:57
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:109
Definition: pad.h:59
PAD_DRILL_SHAPE_T GetDrillShape() const
Definition: pad.h:383
const VECTOR2I & GetDrillSize() const
Definition: pad.h:267
bool TransformHoleToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc) const
Build the corner list of the polygonal drill shape in the board coordinate system.
Definition: pad.cpp:1569
VECTOR2I GetPosition() const override
Definition: pad.h:202
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool IsClosed() const override
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
Represent a set of closed polygons.
int HoleCount(int aOutline) const
Return the reference to aIndex-th outline in the set.
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Return the aIndex-th subpolygon in the set.
int OutlineCount() const
Return the number of vertices in a given outline/hole.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
bool isBoardOutlineValid()
bool AddComponent(const std::string &aFileName, const std::string &aRefDes, bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset, VECTOR3D aOrientation, VECTOR3D aScale, bool aSubstituteModels=true)
double m_boardColor[3]
wxString m_pcbName
Name of the PCB, which will most likely be the file name of the path.
TDF_Label m_assy_label
bool AddPadHole(const PAD *aPad, const VECTOR2D &aOrigin)
std::vector< TDF_Label > m_pcb_labels
STEP_PCB_MODEL(const wxString &aPcbName)
bool readIGES(Handle(TDocStd_Document)&m_doc, const char *fname)
void SetBoardColor(double r, double g, double b)
void SetMinDistance(double aDistance)
bool CreatePCB(SHAPE_POLY_SET &aOutline, VECTOR2D aOrigin)
bool readSTEP(Handle(TDocStd_Document)&m_doc, const char *fname)
Handle(XCAFApp_Application) m_app
bool getModelLocation(bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset, VECTOR3D aOrientation, TopLoc_Location &aLocation)
virtual ~STEP_PCB_MODEL()
bool WriteSTEP(const wxString &aFileName)
bool getModelLabel(const std::string &aFileNameUTF8, VECTOR3D aScale, TDF_Label &aLabel, bool aSubstituteModels, wxString *aErrorMessage=nullptr)
Load a 3D model data.
std::vector< TopoDS_Shape > m_cutouts
bool MakeShape(TopoDS_Shape &aShape, const SHAPE_LINE_CHAIN &chain, double aThickness, const VECTOR2D &aOrigin)
void SetPCBThickness(double aThickness)
MODEL_MAP m_models
TDF_Label transferModel(Handle(TDocStd_Document)&source, Handle(TDocStd_Document) &dest, VECTOR3D aScale)
T y
Definition: vector3.h:62
T z
Definition: vector3.h:63
T x
Definition: vector3.h:61
void ReportMessage(const wxString &aMessage)
@ ERROR_INSIDE
This file contains miscellaneous commonly used macros and functions.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: macros.h:96
bool Init()
Perform application-specific initialization tasks.
Definition: gtk/app.cpp:40
constexpr const char * errorMessage
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:412
@ PAD_DRILL_SHAPE_CIRCLE
Definition: pad_shapes.h:70
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
CITER next(CITER it)
Definition: ptree.cpp:126
static constexpr double BOARD_OFFSET
static constexpr double MIN_LENGTH2
FormatType
@ FMT_STEP
@ FMT_IGES
@ FMT_IDF
@ FMT_WRZ
@ FMT_STEPZ
@ FMT_NONE
@ FMT_WRL
@ FMT_EMN
static constexpr double THICKNESS_DEFAULT
FormatType fileType(const char *aFileName)
static constexpr double USER_ANGLE_PREC
static constexpr double THICKNESS_MIN
static constexpr double USER_PREC
std::pair< std::string, TDF_Label > MODEL_DATUM
static constexpr double STEPEXPORT_MIN_DISTANCE
< Default minimum distance between points to treat them as separate ones (mm)
static constexpr double STEPEXPORT_MIN_ACCEPTABLE_DISTANCE
#define CLOSE_STREAM(var)
#define OPEN_ISTREAM(var, name)
constexpr double IUTomm(int iu) const
Definition: base_units.h:87
VECTOR3< double > VECTOR3D
Definition: vector3.h:204