KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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 The 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/sstream.h>
34#include <wx/stdpaths.h>
35#include <wx/wfstream.h>
36#include <wx/zipstrm.h>
37#include <wx/stdstream.h>
38#include <wx/crt.h>
39
40#include <decompress.hpp>
41
42#include <thread_pool.h>
43#include <footprint.h>
44#include <pad.h>
45#include <pcb_track.h>
46#include <kiplatform/io.h>
47#include <string_utils.h>
48#include <build_version.h>
53#include <reporter.h>
54
55#include "step_pcb_model.h"
56#include "streamwrapper.h"
57
58#include <IGESCAFControl_Reader.hxx>
59#include <IGESCAFControl_Writer.hxx>
60#include <IGESControl_Controller.hxx>
61#include <IGESData_GlobalSection.hxx>
62#include <IGESData_IGESModel.hxx>
63#include <Interface_Static.hxx>
64#include <Quantity_Color.hxx>
65#include <STEPCAFControl_Reader.hxx>
66#include <STEPCAFControl_Writer.hxx>
67#include <APIHeaderSection_MakeHeader.hxx>
68#include <Standard_Failure.hxx>
69#include <Standard_Handle.hxx>
70#include <Standard_Version.hxx>
71#include <TCollection_ExtendedString.hxx>
72#include <TDocStd_Document.hxx>
73#include <TDocStd_XLinkTool.hxx>
74#include <TDataStd_Name.hxx>
75#include <TDataStd_TreeNode.hxx>
76#include <TDF_LabelSequence.hxx>
77#include <TDF_Tool.hxx>
78#include <TopExp_Explorer.hxx>
79#include <TopoDS.hxx>
80#include <XCAFApp_Application.hxx>
81#include <XCAFDoc.hxx>
82#include <XCAFDoc_DocumentTool.hxx>
83#include <XCAFDoc_ColorTool.hxx>
84#include <XCAFDoc_ShapeTool.hxx>
85#include <XCAFDoc_VisMaterialTool.hxx>
86#include <XCAFDoc_Area.hxx>
87#include <XCAFDoc_Centroid.hxx>
88#include <XCAFDoc_Location.hxx>
89#include <XCAFDoc_Volume.hxx>
90
91#include "KI_XCAFDoc_AssemblyGraph.hxx"
92
93#include <BRep_Tool.hxx>
94#include <BRepMesh_IncrementalMesh.hxx>
95#include <BRepBuilderAPI_GTransform.hxx>
96#include <BRepBuilderAPI_MakeEdge.hxx>
97#include <BRepBuilderAPI_MakeWire.hxx>
98#include <BRepBuilderAPI_MakeFace.hxx>
99#include <BRepExtrema_DistShapeShape.hxx>
100#include <BRepPrimAPI_MakePrism.hxx>
101#include <BRepTools.hxx>
102#include <BRepLib_MakeWire.hxx>
103#include <BRepAdaptor_Surface.hxx>
104#include <BRepAlgoAPI_Check.hxx>
105#include <BRepAlgoAPI_Cut.hxx>
106#include <BRepAlgoAPI_Fuse.hxx>
107#include <ShapeUpgrade_UnifySameDomain.hxx>
108
109#include <BRepBndLib.hxx>
110#include <Bnd_BoundSortBox.hxx>
111#include <GProp_GProps.hxx>
112#include <BRepGProp.hxx>
113
114#include <Geom_Curve.hxx>
115#include <Geom_TrimmedCurve.hxx>
116
117#include <gp_Ax2.hxx>
118#include <gp_Dir.hxx>
119#include <gp_Pnt.hxx>
120#include <GC_MakeArcOfCircle.hxx>
121#include <GC_MakeCircle.hxx>
122
123#include <RWGltf_CafWriter.hxx>
124#include <StlAPI_Writer.hxx>
125
126#if OCC_VERSION_HEX >= 0x070700
127#include <VrmlAPI_CafReader.hxx>
128#include <RWPly_CafWriter.hxx>
129#endif
130
131#include <macros.h>
133
134static constexpr double USER_PREC = 1e-4;
135static constexpr double USER_ANGLE_PREC = 1e-6;
136
137// nominal offset from the board
138static constexpr double BOARD_OFFSET = 0.05;
139
140// supported file types for 3D models
142{
150 FMT_WRZ
152
153
154MODEL3D_FORMAT_TYPE fileType( const char* aFileName )
155{
156 wxFileName lfile( wxString::FromUTF8Unchecked( aFileName ) );
157
158 if( !lfile.FileExists() )
159 return FMT_NONE;
160
161 wxString ext = lfile.GetExt().Lower();
162
163 if( ext == wxT( "wrl" ) )
164 return FMT_WRL;
165
166 if( ext == wxT( "wrz" ) )
167 return FMT_WRZ;
168
169 if( ext == wxT( "idf" ) )
170 return FMT_IDF; // component outline
171
172 if( ext == wxT( "emn" ) )
173 return FMT_EMN; // PCB assembly
174
175 if( ext == wxT( "stpz" ) || ext == wxT( "gz" ) )
176 return FMT_STEPZ;
177
178 OPEN_ISTREAM( ifile, aFileName );
179
180 if( ifile.fail() )
181 return FMT_NONE;
182
183 char iline[82];
184 MODEL3D_FORMAT_TYPE format_type = FMT_NONE;
185
186 // The expected header should be the first line.
187 // However some files can have a comment at the beginning of the file
188 // So read up to max_line_count lines to try to find the actual header
189 const int max_line_count = 3;
190
191 for( int ii = 0; ii < max_line_count; ii++ )
192 {
193 memset( iline, 0, 82 );
194 ifile.getline( iline, 82 );
195
196 iline[81] = 0; // ensure NULL termination when string is too long
197
198 // check for STEP in Part 21 format
199 // (this can give false positives since Part 21 is not exclusively STEP)
200 if( !strncmp( iline, "ISO-10303-21;", 13 ) )
201 {
202 format_type = FMT_STEP;
203 break;
204 }
205
206 std::string fstr = iline;
207
208 // check for STEP in XML format
209 // (this can give both false positive and false negatives)
210 if( fstr.find( "urn:oid:1.0.10303." ) != std::string::npos )
211 {
212 format_type = FMT_STEP;
213 break;
214 }
215
216 // Note: this is a very simple test which can yield false positives; the only
217 // sure method for determining if a file *not* an IGES model is to attempt
218 // to load it.
219 if( iline[72] == 'S' && ( iline[80] == 0 || iline[80] == 13 || iline[80] == 10 ) )
220 {
221 format_type = FMT_IGES;
222 break;
223 }
224
225 // Only a comment (starting by "/*") is allowed as header
226 if( strncmp( iline, "/*", 2 ) != 0 ) // not a comment
227 break;
228 }
229
230 CLOSE_STREAM( ifile );
231
232 return format_type;
233}
234
235
237 const VECTOR2D& p3 )
238{
240
241 // Move coordinate origin to p2, to simplify calculations
242 VECTOR2D b = p1 - p2;
243 VECTOR2D d = p3 - p2;
244 double bc = ( b.x * b.x + b.y * b.y ) / 2.0;
245 double cd = ( -d.x * d.x - d.y * d.y ) / 2.0;
246 double det = -b.x * d.y + d.x * b.y;
247
248 // We're fine with divisions by 0
249 det = 1.0 / det;
250 center.x = ( -bc * d.y - cd * b.y ) * det;
251 center.y = ( b.x * cd + d.x * bc ) * det;
252 center += p2;
253
254 return center;
255}
256
257
258#define APPROX_DBG( stmt )
259//#define APPROX_DBG( stmt ) stmt
260
262{
263 // An algo that takes 3 points, calculates a circle center,
264 // then tries to find as many points fitting the circle.
265
266 static const double c_radiusDeviation = 1000.0;
267 static const double c_arcCenterDeviation = 1000.0;
268 static const double c_relLengthDeviation = 0.8;
269 static const int c_last_none = -1000; // Meaning the arc cannot be constructed
270 // Allow larger angles for segments below this size
271 static const double c_smallSize = pcbIUScale.mmToIU( 0.1 );
272 static const double c_circleCloseGap = pcbIUScale.mmToIU( 1.0 );
273
274 APPROX_DBG( std::cout << std::endl );
275
276 if( aSrc.PointCount() < 4 )
277 return aSrc;
278
279 if( !aSrc.IsClosed() )
280 return aSrc; // non-closed polygons are not supported
281
283
284 int jEndIdx = aSrc.PointCount() - 3;
285
286 for( int i = 0; i < aSrc.PointCount(); i++ )
287 {
288 int first = i - 3;
289 int last = c_last_none;
290
291 VECTOR2D p0 = aSrc.CPoint( i - 3 );
292 VECTOR2D p1 = aSrc.CPoint( i - 2 );
293 VECTOR2D p2 = aSrc.CPoint( i - 1 );
294
295 APPROX_DBG( std::cout << i << " " << aSrc.CPoint( i ) << " " << ( i - 3 ) << " "
296 << VECTOR2I( p0 ) << " " << ( i - 2 ) << " " << VECTOR2I( p1 ) << " "
297 << ( i - 1 ) << " " << VECTOR2I( p2 ) << std::endl );
298
299 VECTOR2D v01 = p1 - p0;
300 VECTOR2D v12 = p2 - p1;
301
302 bool defective = false;
303
304 double d01 = v01.EuclideanNorm();
305 double d12 = v12.EuclideanNorm();
306
307 // Check distance differences between 3 first points
308 defective |= std::abs( d01 - d12 ) > ( std::max( d01, d12 ) * c_relLengthDeviation );
309
310 if( !defective )
311 {
312 // Check angles between 3 first points
313 EDA_ANGLE a01( v01 );
314 EDA_ANGLE a12( v12 );
315
316 double a_diff = ( a01 - a12 ).Normalize180().AsDegrees();
317 defective |= std::abs( a_diff ) < 0.1;
318
319 // Larger angles are allowed for smaller geometry
320 double maxAngleDiff = std::max( d01, d12 ) < c_smallSize ? 46.0 : 30.0;
321 defective |= std::abs( a_diff ) >= maxAngleDiff;
322 }
323
324 if( !defective )
325 {
326 // Find last point lying on the circle created from 3 first points
328 double radius = ( p0 - center ).EuclideanNorm();
329 VECTOR2D p_prev = p2;
330 EDA_ANGLE a_prev( v12 );
331
332 for( int j = i; j <= jEndIdx; j++ )
333 {
334 VECTOR2D p_test = aSrc.CPoint( j );
335
336 EDA_ANGLE a_test( p_test - p_prev );
337 double rad_test = ( p_test - center ).EuclideanNorm();
338 double d_tl = ( p_test - p_prev ).EuclideanNorm();
339 double rad_dev = std::abs( radius - rad_test );
340
341 APPROX_DBG( std::cout << " " << j << " " << aSrc.CPoint( j ) << " rad "
342 << int64_t( rad_test ) << " ref " << int64_t( radius )
343 << std::endl );
344
345 if( rad_dev > c_radiusDeviation )
346 {
347 APPROX_DBG( std::cout << " " << j
348 << " Radius deviation too large: " << int64_t( rad_dev )
349 << " > " << c_radiusDeviation << std::endl );
350 break;
351 }
352
353 // Larger angles are allowed for smaller geometry
354 double maxAngleDiff =
355 std::max( std::max( d01, d12 ), d_tl ) < c_smallSize ? 46.0 : 30.0;
356
357 double a_diff_test = ( a_prev - a_test ).Normalize180().AsDegrees();
358 if( std::abs( a_diff_test ) >= maxAngleDiff )
359 {
360 APPROX_DBG( std::cout << " " << j << " Angles differ too much " << a_diff_test
361 << std::endl );
362 break;
363 }
364
365 if( std::abs( d_tl - d01 ) > ( std::max( d_tl, d01 ) * c_relLengthDeviation ) )
366 {
367 APPROX_DBG( std::cout << " " << j << " Lengths differ too much " << d_tl
368 << "; " << d01 << std::endl );
369 break;
370 }
371
372 last = j;
373 p_prev = p_test;
374 a_prev = a_test;
375 }
376 }
377
378 if( last != c_last_none )
379 {
380 // Try to add an arc, testing for self-interference
381 SHAPE_ARC arc( aSrc.CPoint( first ), aSrc.CPoint( ( first + last ) / 2 ),
382 aSrc.CPoint( last ), 0 );
383
384 if( last > aSrc.PointCount() - 3 && !dst.IsArcSegment( 0 ) )
385 {
386 // If we've found an arc at the end, but already added segments at the start, remove them.
387 int toRemove = last - ( aSrc.PointCount() - 3 );
388
389 while( toRemove )
390 {
391 dst.RemoveShape( 0 );
392 toRemove--;
393 }
394 }
395
396 SHAPE_LINE_CHAIN testChain = dst;
397
398 testChain.Append( arc );
399 testChain.Append( aSrc.Slice( last, std::max( last, aSrc.PointCount() - 3 ) ) );
400 testChain.SetClosed( aSrc.IsClosed() );
401
402 if( !testChain.SelfIntersectingWithArcs() )
403 {
404 // Add arc
405 dst.Append( arc );
406
407 APPROX_DBG( std::cout << " Add arc start " << arc.GetP0() << " mid "
408 << arc.GetArcMid() << " end " << arc.GetP1() << std::endl );
409
410 i = last + 3;
411 }
412 else
413 {
414 // Self-interference
415 last = c_last_none;
416
417 APPROX_DBG( std::cout << " Self-intersection check failed" << std::endl );
418 }
419 }
420
421 if( last == c_last_none )
422 {
423 if( first < 0 )
424 jEndIdx = first + aSrc.PointCount();
425
426 // Add point
427 dst.Append( p0 );
428 APPROX_DBG( std::cout << " Add pt " << VECTOR2I( p0 ) << std::endl );
429 }
430 }
431
432 dst.SetClosed( true );
433
434 // Try to merge arcs
435 int iarc0 = dst.ArcIndex( 0 );
436 int iarc1 = dst.ArcIndex( dst.GetSegmentCount() - 1 );
437
438 if( iarc0 != -1 && iarc1 != -1 )
439 {
440 APPROX_DBG( std::cout << "Final arcs " << iarc0 << " " << iarc1 << std::endl );
441
442 if( iarc0 == iarc1 )
443 {
444 SHAPE_ARC arc = dst.Arc( iarc0 );
445
446 VECTOR2D p0 = arc.GetP0();
447 VECTOR2D p1 = arc.GetP1();
448
449 // If we have only one arc and the gap is small, make it a circle
450 if( ( p1 - p0 ).EuclideanNorm() < c_circleCloseGap )
451 {
452 dst.Clear();
453 dst.Append( SHAPE_ARC( arc.GetCenter(), arc.GetP0(), ANGLE_360 ) );
454 }
455 }
456 else
457 {
458 // Merge first and last arcs if they are similar
459 SHAPE_ARC arc0 = dst.Arc( iarc0 );
460 SHAPE_ARC arc1 = dst.Arc( iarc1 );
461
462 VECTOR2D ac0 = arc0.GetCenter();
463 VECTOR2D ac1 = arc1.GetCenter();
464
465 double ar0 = arc0.GetRadius();
466 double ar1 = arc1.GetRadius();
467
468 if( std::abs( ar0 - ar1 ) <= c_radiusDeviation
469 && ( ac0 - ac1 ).EuclideanNorm() <= c_arcCenterDeviation )
470 {
471 dst.RemoveShape( 0 );
472 dst.RemoveShape( -1 );
473
474 SHAPE_ARC merged( arc1.GetP0(), arc1.GetArcMid(), arc0.GetP1(), 0 );
475
476 dst.Append( merged );
477 }
478 }
479 }
480
481 return dst;
482}
483
484
485static TopoDS_Shape getOneShape( Handle( XCAFDoc_ShapeTool ) aShapeTool )
486{
487 TDF_LabelSequence theLabels;
488 aShapeTool->GetFreeShapes( theLabels );
489
490 TopoDS_Shape aShape;
491
492 if( theLabels.Length() == 1 )
493 return aShapeTool->GetShape( theLabels.Value( 1 ) );
494
495 TopoDS_Compound aCompound;
496 BRep_Builder aBuilder;
497 aBuilder.MakeCompound( aCompound );
498
499 for( TDF_LabelSequence::Iterator anIt( theLabels ); anIt.More(); anIt.Next() )
500 {
501 TopoDS_Shape aFreeShape;
502
503 if( !aShapeTool->GetShape( anIt.Value(), aFreeShape ) )
504 continue;
505
506 aBuilder.Add( aCompound, aFreeShape );
507 }
508
509 if( aCompound.NbChildren() > 0 )
510 aShape = aCompound;
511
512 return aShape;
513}
514
515
516// Apply scaling to shapes within theLabel.
517// Based on XCAFDoc_Editor::RescaleGeometry
518static Standard_Boolean rescaleShapes( const TDF_Label& theLabel, const gp_XYZ& aScale )
519{
520 if( theLabel.IsNull() )
521 {
522 Message::SendFail( "Null label." );
523 return Standard_False;
524 }
525
526 if( Abs( aScale.X() ) <= gp::Resolution() || Abs( aScale.Y() ) <= gp::Resolution()
527 || Abs( aScale.Z() ) <= gp::Resolution() )
528 {
529 Message::SendFail( "Scale factor is too small." );
530 return Standard_False;
531 }
532
533 Handle( XCAFDoc_ShapeTool ) aShapeTool = XCAFDoc_DocumentTool::ShapeTool( theLabel );
534
535 if( aShapeTool.IsNull() )
536 {
537 Message::SendFail( "Couldn't find XCAFDoc_ShapeTool attribute." );
538 return Standard_False;
539 }
540
541 Handle( KI_XCAFDoc_AssemblyGraph ) aG = new KI_XCAFDoc_AssemblyGraph( theLabel );
542
543 if( aG.IsNull() )
544 {
545 Message::SendFail( "Couldn't create assembly graph." );
546 return Standard_False;
547 }
548
549 Standard_Boolean anIsDone = Standard_True;
550
551 // clang-format off
552 gp_GTrsf aGTrsf;
553 aGTrsf.SetVectorialPart( gp_Mat( aScale.X(), 0, 0,
554 0, aScale.Y(), 0,
555 0, 0, aScale.Z() ) );
556 // clang-format on
557
558 BRepBuilderAPI_GTransform aBRepTrsf( aGTrsf );
559
560 for( Standard_Integer idx = 1; idx <= aG->NbNodes(); idx++ )
561 {
562 const KI_XCAFDoc_AssemblyGraph::NodeType aNodeType = aG->GetNodeType( idx );
563
564 if( ( aNodeType != KI_XCAFDoc_AssemblyGraph::NodeType_Part )
565 && ( aNodeType != KI_XCAFDoc_AssemblyGraph::NodeType_Occurrence ) )
566 {
567 continue;
568 }
569
570 const TDF_Label& aLabel = aG->GetNode( idx );
571
572 if( aNodeType == KI_XCAFDoc_AssemblyGraph::NodeType_Part )
573 {
574 const TopoDS_Shape aShape = aShapeTool->GetShape( aLabel );
575 aBRepTrsf.Perform( aShape, Standard_True );
576 if( !aBRepTrsf.IsDone() )
577 {
578 Standard_SStream aSS;
579 TCollection_AsciiString anEntry;
580 TDF_Tool::Entry( aLabel, anEntry );
581 aSS << "Shape " << anEntry << " is not scaled!";
582 Message::SendFail( aSS.str().c_str() );
583 anIsDone = Standard_False;
584 return Standard_False;
585 }
586 TopoDS_Shape aScaledShape = aBRepTrsf.Shape();
587 aShapeTool->SetShape( aLabel, aScaledShape );
588
589 // Update sub-shapes
590 TDF_LabelSequence aSubshapes;
591 aShapeTool->GetSubShapes( aLabel, aSubshapes );
592 for( TDF_LabelSequence::Iterator anItSs( aSubshapes ); anItSs.More(); anItSs.Next() )
593 {
594 const TDF_Label& aLSs = anItSs.Value();
595 const TopoDS_Shape aSs = aShapeTool->GetShape( aLSs );
596 const TopoDS_Shape aSs1 = aBRepTrsf.ModifiedShape( aSs );
597 aShapeTool->SetShape( aLSs, aSs1 );
598 }
599
600 // These attributes will be recomputed eventually, but clear them just in case
601 aLabel.ForgetAttribute( XCAFDoc_Area::GetID() );
602 aLabel.ForgetAttribute( XCAFDoc_Centroid::GetID() );
603 aLabel.ForgetAttribute( XCAFDoc_Volume::GetID() );
604 }
605 else if( aNodeType == KI_XCAFDoc_AssemblyGraph::NodeType_Occurrence )
606 {
607 TopLoc_Location aLoc = aShapeTool->GetLocation( aLabel );
608 gp_Trsf aTrsf = aLoc.Transformation();
609 aTrsf.SetTranslationPart( aTrsf.TranslationPart().Multiplied( aScale ) );
610 XCAFDoc_Location::Set( aLabel, aTrsf );
611 }
612 }
613
614 if( !anIsDone )
615 {
616 return Standard_False;
617 }
618
619 aShapeTool->UpdateAssemblies();
620
621 return anIsDone;
622}
623
624
625static bool fuseShapes( auto& aInputShapes, TopoDS_Shape& aOutShape, REPORTER* aReporter )
626{
627 BRepAlgoAPI_Fuse mkFuse;
628 TopTools_ListOfShape shapeArguments, shapeTools;
629
630 for( TopoDS_Shape& sh : aInputShapes )
631 {
632 if( sh.IsNull() )
633 continue;
634
635 if( shapeArguments.IsEmpty() )
636 shapeArguments.Append( sh );
637 else
638 shapeTools.Append( sh );
639 }
640
641 mkFuse.SetRunParallel( true );
642 mkFuse.SetToFillHistory( false );
643 mkFuse.SetArguments( shapeArguments );
644 mkFuse.SetTools( shapeTools );
645 mkFuse.Build();
646
647 if( mkFuse.HasErrors() || mkFuse.HasWarnings() )
648 {
649 aReporter->Report( _( "** Got problems while fusing shapes **\n" ), RPT_SEVERITY_ERROR );
650
651 if( mkFuse.HasErrors() )
652 {
653 wxString msg = _( "Errors:\n" );
654 wxStringOutputStream os_stream( &msg );
655 wxStdOutputStream out( os_stream );
656
657 mkFuse.DumpErrors( out );
658 aReporter->Report( msg, RPT_SEVERITY_ERROR );
659 }
660
661 if( mkFuse.HasWarnings() )
662 {
663 wxString msg = _( "Warnings:\n" );
664 wxStringOutputStream os_stream( &msg );
665 wxStdOutputStream out( os_stream );
666
667 mkFuse.DumpWarnings( out );
668 aReporter->Report( msg, RPT_SEVERITY_WARNING );
669 }
670 }
671
672 if( mkFuse.IsDone() )
673 {
674 TopoDS_Shape fusedShape = mkFuse.Shape();
675
676 ShapeUpgrade_UnifySameDomain unify( fusedShape, true, true, false );
677 unify.History() = nullptr;
678 unify.Build();
679
680 TopoDS_Shape unifiedShapes = unify.Shape();
681
682 if( unifiedShapes.IsNull() )
683 {
684 aReporter->Report( _( "** ShapeUpgrade_UnifySameDomain produced a null shape **\n" ),
686 }
687 else
688 {
689 aOutShape = unifiedShapes;
690 return true;
691 }
692 }
693
694 return false;
695}
696
697
698static TopoDS_Compound makeCompound( const auto& aInputShapes )
699{
700 TopoDS_Compound compound;
701 BRep_Builder builder;
702 builder.MakeCompound( compound );
703
704 for( const TopoDS_Shape& shape : aInputShapes )
705 builder.Add( compound, shape );
706
707 return compound;
708}
709
710
711// Try to fuse shapes. If that fails, just add them to a compound
712static TopoDS_Shape fuseShapesOrCompound( const TopTools_ListOfShape& aInputShapes,
713 REPORTER* aReporter )
714{
715 TopoDS_Shape outShape;
716
717 if( aInputShapes.Size() == 1 )
718 return aInputShapes.First();
719
720 if( fuseShapes( aInputShapes, outShape, aReporter ) )
721 return outShape;
722
723 return makeCompound( aInputShapes );
724}
725
726
727// Sets names in assembly to <aPrefix> (<old name>), or to <aPrefix>
728static Standard_Boolean prefixNames( const TDF_Label& aLabel,
729 const TCollection_ExtendedString& aPrefix )
730{
731 Handle( KI_XCAFDoc_AssemblyGraph ) aG = new KI_XCAFDoc_AssemblyGraph( aLabel );
732
733 if( aG.IsNull() )
734 {
735 Message::SendFail( "Couldn't create assembly graph." );
736 return Standard_False;
737 }
738
739 Standard_Boolean anIsDone = Standard_True;
740
741 for( Standard_Integer idx = 1; idx <= aG->NbNodes(); idx++ )
742 {
743 const TDF_Label& lbl = aG->GetNode( idx );
744 Handle( TDataStd_Name ) nameHandle;
745
746 if( lbl.FindAttribute( TDataStd_Name::GetID(), nameHandle ) )
747 {
748 TCollection_ExtendedString name;
749
750 name += aPrefix;
751 name += " (";
752 name += nameHandle->Get();
753 name += ")";
754
755 TDataStd_Name::Set( lbl, name );
756 }
757 else
758 {
759 TDataStd_Name::Set( lbl, aPrefix );
760 }
761 }
762
763 return anIsDone;
764}
765
766
767STEP_PCB_MODEL::STEP_PCB_MODEL( const wxString& aPcbName, REPORTER* aReporter ) :
768 m_reporter( aReporter )
769{
770 m_app = XCAFApp_Application::GetApplication();
771 m_app->NewDocument( "MDTV-XCAF", m_doc );
772 m_assy = XCAFDoc_DocumentTool::ShapeTool( m_doc->Main() );
773 m_assy_label = m_assy->NewShape();
774 m_hasPCB = false;
775 m_simplifyShapes = true;
776 m_components = 0;
780 m_minx = 1.0e10; // absurdly large number; any valid PCB X value will be smaller
781 m_pcbName = aPcbName;
783 m_fuseShapes = false;
784 m_outFmt = OUTPUT_FORMAT::FMT_OUT_UNKNOWN;
785}
786
787
789{
790 if( m_doc->CanClose() == CDM_CCS_OK )
791 m_doc->Close();
792}
793
794
795bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin, bool aVia,
796 SHAPE_POLY_SET* aClipPolygon )
797{
798 const double c_padExtraThickness = 0.005;
799 bool success = true;
800 std::vector<TopoDS_Shape> padShapes;
801 bool castellated = aClipPolygon && aPad->GetProperty() == PAD_PROP::CASTELLATED;
802
803 for( PCB_LAYER_ID pcb_layer : aPad->GetLayerSet().Seq() )
804 {
805 if( !m_enabledLayers.Contains( pcb_layer ) )
806 continue;
807
808 if( pcb_layer == F_Mask || pcb_layer == B_Mask )
809 continue;
810
811 if( !aPad->FlashLayer( pcb_layer ) )
812 continue;
813
814 double Zpos, thickness;
815 getLayerZPlacement( pcb_layer, Zpos, thickness );
816
817 if( !aVia )
818 {
819 // Pad surface as a separate face for FEM simulations.
820 if( pcb_layer == F_Cu )
821 thickness += c_padExtraThickness;
822 else if( pcb_layer == B_Cu )
823 thickness -= c_padExtraThickness;
824 }
825
826 TopoDS_Shape testShape;
827
828 // Make a shape on copper layers
829 SHAPE_POLY_SET polySet;
830 aPad->TransformShapeToPolygon( polySet, pcb_layer, 0, ARC_HIGH_DEF, ERROR_INSIDE );
831
832 if( castellated )
833 {
834 polySet.ClearArcs();
835 polySet.BooleanIntersection( *aClipPolygon );
836 }
837
838 success &= MakeShapes( padShapes, polySet, m_simplifyShapes, thickness, Zpos, aOrigin );
839
840 if( testShape.IsNull() )
841 {
842 std::vector<TopoDS_Shape> testShapes;
843
844 MakeShapes( testShapes, polySet, m_simplifyShapes, 0.0, Zpos + thickness, aOrigin );
845
846 if( testShapes.size() > 0 )
847 testShape = testShapes.front();
848 }
849
850 if( !aVia && !testShape.IsNull() )
851 {
852 if( pcb_layer == F_Cu || pcb_layer == B_Cu )
853 {
854 wxString name;
855
856 name << "Pad_";
857
858 if( pcb_layer == F_Cu )
859 name << 'F' << '_';
860 else if( pcb_layer == B_Cu )
861 name << 'B' << '_';
862
863 name << aPad->GetParentFootprint()->GetReferenceAsString() << '_'
864 << aPad->GetNumber() << '_' << aPad->GetShortNetname();
865
866 gp_Pnt point( pcbIUScale.IUTomm( aPad->GetX() - aOrigin.x ),
867 -pcbIUScale.IUTomm( aPad->GetY() - aOrigin.y ), Zpos + thickness );
868
869 m_pad_points[name].emplace_back( point, testShape );
870 }
871 }
872 }
873
874 if( aPad->GetAttribute() == PAD_ATTRIB::PTH && aPad->IsOnLayer( F_Cu )
875 && aPad->IsOnLayer( B_Cu ) )
876 {
877 double f_pos, f_thickness;
878 double b_pos, b_thickness;
879 getLayerZPlacement( F_Cu, f_pos, f_thickness );
880 getLayerZPlacement( B_Cu, b_pos, b_thickness );
881
882 if( !aVia )
883 {
884 // Pad surface is slightly thicker
885 f_thickness += c_padExtraThickness;
886 b_thickness -= c_padExtraThickness;
887 }
888
889 double top = std::max( f_pos, f_pos + f_thickness );
890 double bottom = std::min( b_pos, b_pos + b_thickness );
891 double hole_height = top - bottom;
892
893 TopoDS_Shape plating;
894
895 std::shared_ptr<SHAPE_SEGMENT> seg_hole = aPad->GetEffectiveHoleShape();
896 double width = std::min( aPad->GetDrillSize().x, aPad->GetDrillSize().y );
897
898 if( !castellated )
899 {
900 if( MakeShapeAsThickSegment( plating, seg_hole->GetSeg().A, seg_hole->GetSeg().B, width,
901 hole_height, bottom, aOrigin ) )
902 {
903 padShapes.push_back( plating );
904 }
905 else
906 {
907 success = false;
908 }
909 }
910 else
911 {
912 // Note:
913 // the truncated hole shape is exported as a vertical filled shape. The hole itself
914 // will be removed later, when all holes are removed from the board
915 SHAPE_POLY_SET polyHole;
916
917 if( seg_hole->GetSeg().A == seg_hole->GetSeg().B ) // Hole is a circle
918 {
919 TransformCircleToPolygon( polyHole, seg_hole->GetSeg().A, width/2, ARC_HIGH_DEF,
921
922 }
923 else
924 {
925 TransformOvalToPolygon( polyHole, seg_hole->GetSeg().A, seg_hole->GetSeg().B,
926 width, ARC_HIGH_DEF, ERROR_OUTSIDE );
927 }
928
929 polyHole.ClearArcs();
930 polyHole.BooleanIntersection( *aClipPolygon );
931
932 if( MakePolygonAsWall( plating, polyHole, hole_height, bottom, aOrigin ) )
933 {
934 padShapes.push_back( plating );
935 }
936 else
937 {
938 success = false;
939 }
940 }
941 }
942
943 if( !success ) // Error
944 m_reporter->Report( _( "OCC error adding pad/via polygon.\n" ), RPT_SEVERITY_ERROR );
945
946 if( !padShapes.empty() )
947 {
948 // Fuse pad shapes here before fusing them with tracks because OCCT sometimes has trouble
949 if( m_fuseShapes )
950 {
951 TopTools_ListOfShape padShapesList;
952
953 for( const TopoDS_Shape& shape : padShapes )
954 padShapesList.Append( shape );
955
956 m_board_copper_pads[aPad->GetNetname()].push_back( fuseShapesOrCompound( padShapesList, m_reporter ) );
957 }
958 else
959 {
960 for( const TopoDS_Shape& shape : padShapes )
961 m_board_copper_pads[aPad->GetNetname()].push_back( shape );
962 }
963 }
964
965 return success;
966}
967
968
969bool STEP_PCB_MODEL::AddHole( const SHAPE_SEGMENT& aShape, int aPlatingThickness,
970 PCB_LAYER_ID aLayerTop, PCB_LAYER_ID aLayerBot, bool aVia,
971 const VECTOR2D& aOrigin, bool aCutCopper, bool aCutBody )
972{
973 double margin = 0.001; // a small margin on the Z axix to be sure the hole
974 // is bigger than the board with copper
975 // must be > OCC_MAX_DISTANCE_TO_MERGE_POINTS
976
977 // Pads are taller by 0.01 mm
978 if( !aVia )
979 margin += 0.01;
980
981 double f_pos, f_thickness;
982 double b_pos, b_thickness;
983 getLayerZPlacement( aLayerTop, f_pos, f_thickness );
984 getLayerZPlacement( aLayerBot, b_pos, b_thickness );
985 double top = std::max( f_pos, f_pos + f_thickness );
986 double bottom = std::min( b_pos, b_pos + b_thickness );
987
988 double holeZsize = ( top - bottom ) + ( margin * 2 );
989
990 double boardDrill = aShape.GetWidth();
991 double copperDrill = boardDrill - aPlatingThickness * 2;
992
993 TopoDS_Shape copperHole, boardHole;
994
995 if( aCutCopper )
996 {
997 if( MakeShapeAsThickSegment( copperHole, aShape.GetSeg().A, aShape.GetSeg().B, copperDrill,
998 holeZsize, bottom - margin, aOrigin ) )
999 {
1000 m_copperCutouts.push_back( copperHole );
1001 }
1002 else
1003 {
1004 return false;
1005 }
1006 }
1007
1008 if( aCutBody )
1009 {
1010 if( MakeShapeAsThickSegment( boardHole, aShape.GetSeg().A, aShape.GetSeg().B, boardDrill,
1011 holeZsize, bottom - margin, aOrigin ) )
1012 {
1013 m_boardCutouts.push_back( boardHole );
1014 }
1015 else
1016 {
1017 return false;
1018 }
1019 }
1020
1021 return true;
1022}
1023
1024
1026 PCB_LAYER_ID aLayerBot, bool aVia, const VECTOR2D& aOrigin,
1027 const wxString& aNetname )
1028{
1029 double f_pos, f_thickness;
1030 double b_pos, b_thickness;
1031 getLayerZPlacement( aLayerTop, f_pos, f_thickness );
1032 getLayerZPlacement( aLayerBot, b_pos, b_thickness );
1033 double top = std::max( f_pos, f_pos + f_thickness );
1034 double bottom = std::min( b_pos, b_pos + b_thickness );
1035
1036 TopoDS_Shape plating;
1037
1038 if( !MakeShapeAsThickSegment( plating, aShape.GetSeg().A, aShape.GetSeg().B, aShape.GetWidth(),
1039 ( top - bottom ), bottom, aOrigin ) )
1040 {
1041 return false;
1042 }
1043
1044 if( aVia )
1045 m_board_copper_vias[aNetname].push_back( plating );
1046 else
1047 m_board_copper_pads[aNetname].push_back( plating );
1048
1049 return true;
1050}
1051
1052
1053void STEP_PCB_MODEL::getLayerZPlacement( const PCB_LAYER_ID aLayer, double& aZPos,
1054 double& aThickness )
1055{
1056 // Offsets above copper in mm
1057 static const double c_silkscreenAboveCopper = 0.04;
1058 static const double c_soldermaskAboveCopper = 0.015;
1059
1060 if( IsCopperLayer( aLayer ) )
1061 {
1062 getCopperLayerZPlacement( aLayer, aZPos, aThickness );
1063 }
1064 else if( IsFrontLayer( aLayer ) )
1065 {
1066 double f_pos, f_thickness;
1067 getCopperLayerZPlacement( F_Cu, f_pos, f_thickness );
1068 double top = std::max( f_pos, f_pos + f_thickness );
1069
1070 if( aLayer == F_SilkS )
1071 aZPos = top + c_silkscreenAboveCopper;
1072 else
1073 aZPos = top + c_soldermaskAboveCopper;
1074
1075 aThickness = 0.0; // Normal points up
1076 }
1077 else if( IsBackLayer( aLayer ) )
1078 {
1079 double b_pos, b_thickness;
1080 getCopperLayerZPlacement( B_Cu, b_pos, b_thickness );
1081 double bottom = std::min( b_pos, b_pos + b_thickness );
1082
1083 if( aLayer == B_SilkS )
1084 aZPos = bottom - c_silkscreenAboveCopper;
1085 else
1086 aZPos = bottom - c_soldermaskAboveCopper;
1087
1088 aThickness = -0.0; // Normal points down
1089 }
1090}
1091
1092
1094 double& aThickness )
1095{
1096 int z = 0;
1097 int thickness = 0;
1098 bool wasPrepreg = false;
1099
1100 const std::vector<BOARD_STACKUP_ITEM*>& materials = m_stackup.GetList();
1101
1102 // Iterate from bottom to top
1103 for( auto it = materials.rbegin(); it != materials.rend(); ++it )
1104 {
1105 const BOARD_STACKUP_ITEM* item = *it;
1106
1107 if( item->GetType() == BS_ITEM_TYPE_COPPER )
1108 {
1109 if( aLayer == B_Cu )
1110 {
1111 // This is the first encountered layer
1112 thickness = -item->GetThickness();
1113 break;
1114 }
1115
1116 // Inner copper position is usually inside prepreg
1117 if( wasPrepreg && item->GetBrdLayerId() != F_Cu )
1118 {
1119 z += item->GetThickness();
1120 thickness = -item->GetThickness();
1121 }
1122 else
1123 {
1124 thickness = item->GetThickness();
1125 }
1126
1127 if( item->GetBrdLayerId() == aLayer )
1128 break;
1129
1130 if( !wasPrepreg && item->GetBrdLayerId() != B_Cu )
1131 z += item->GetThickness();
1132 }
1133 else if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1134 {
1135 wasPrepreg = ( item->GetTypeName() == KEY_PREPREG );
1136
1137 // Dielectric can have sub-layers. Layer 0 is the main layer
1138 // Not frequent, but possible
1139 thickness = 0;
1140 for( int idx = 0; idx < item->GetSublayersCount(); idx++ )
1141 thickness += item->GetThickness( idx );
1142
1143 z += thickness;
1144 }
1145 }
1146
1147 aZPos = pcbIUScale.IUTomm( z );
1148 aThickness = pcbIUScale.IUTomm( thickness );
1149}
1150
1151
1152void STEP_PCB_MODEL::getBoardBodyZPlacement( double& aZPos, double& aThickness )
1153{
1154 double f_pos, f_thickness;
1155 double b_pos, b_thickness;
1156 getLayerZPlacement( F_Cu, f_pos, f_thickness );
1157 getLayerZPlacement( B_Cu, b_pos, b_thickness );
1158 double top = std::min( f_pos, f_pos + f_thickness );
1159 double bottom = std::max( b_pos, b_pos + b_thickness );
1160
1161 aThickness = ( top - bottom );
1162 aZPos = bottom;
1163
1164 wxASSERT( aZPos == 0.0 );
1165}
1166
1167
1169 const VECTOR2D& aOrigin, const wxString& aNetname )
1170{
1171 bool success = true;
1172
1173 if( aPolyShapes->IsEmpty() )
1174 return true;
1175
1176 if( !m_enabledLayers.Contains( aLayer ) )
1177 return true;
1178
1179 double z_pos, thickness;
1180 getLayerZPlacement( aLayer, z_pos, thickness );
1181
1182 std::vector<TopoDS_Shape>& targetVec = IsCopperLayer( aLayer ) ? m_board_copper[aNetname]
1183 : aLayer == F_SilkS || aLayer == B_SilkS
1186
1187 if( !MakeShapes( targetVec, *aPolyShapes, m_simplifyShapes, thickness, z_pos, aOrigin ) )
1188 {
1189 m_reporter->Report( wxString::Format( _( "Could not add shape (%d points) to copper layer %s.\n" ),
1190 aPolyShapes->FullPointCount(),
1191 LayerName( aLayer ) ),
1193
1194 success = false;
1195 }
1196
1197 return success;
1198}
1199
1200
1201bool STEP_PCB_MODEL::AddComponent( const std::string& aFileNameUTF8, const std::string& aRefDes,
1202 bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset,
1203 VECTOR3D aOrientation, VECTOR3D aScale, bool aSubstituteModels )
1204{
1205 if( aFileNameUTF8.empty() )
1206 {
1207 m_reporter->Report( wxString::Format( _( "No model defined for %s.\n" ), aRefDes ),
1209 return false;
1210 }
1211
1212 wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
1213 m_reporter->Report( wxString::Format( wxT( "Adding component %s.\n" ), aRefDes ), RPT_SEVERITY_DEBUG );
1214
1215 // first retrieve a label
1216 TDF_Label lmodel;
1217 wxString errorMessage;
1218
1219 if( !getModelLabel( aFileNameUTF8, aScale, lmodel, aSubstituteModels, &errorMessage ) )
1220 {
1221 if( errorMessage.IsEmpty() )
1222 errorMessage.Printf( _( "No model for filename '%s'.\n" ), fileName );
1223
1224 m_reporter->Report( errorMessage, RPT_SEVERITY_ERROR );
1225 return false;
1226 }
1227
1228 // calculate the Location transform
1229 TopLoc_Location toploc;
1230
1231 if( !getModelLocation( aBottom, aPosition, aRotation, aOffset, aOrientation, toploc ) )
1232 {
1233 m_reporter->Report( wxString::Format( _( "No location data for filename '%s'.\n" ), fileName ),
1235 return false;
1236 }
1237
1238 // add the located sub-assembly
1239 TDF_Label llabel = m_assy->AddComponent( m_assy_label, lmodel, toploc );
1240
1241 if( llabel.IsNull() )
1242 {
1243 m_reporter->Report( wxString::Format( _( "Could not add component with filename '%s'.\n" ), fileName ),
1245 return false;
1246 }
1247
1248 // attach the RefDes name
1249 TCollection_ExtendedString refdes( aRefDes.c_str() );
1250 TDataStd_Name::Set( llabel, refdes );
1251
1252 return true;
1253}
1254
1255
1257{
1258 m_enabledLayers = aLayers;
1259}
1260
1261
1263{
1264 m_fuseShapes = aValue;
1265}
1266
1267
1269{
1270 m_simplifyShapes = aValue;
1271}
1272
1273
1275{
1276 m_stackup = aStackup;
1277}
1278
1279
1280void STEP_PCB_MODEL::SetNetFilter( const wxString& aFilter )
1281{
1282 m_netFilter = aFilter;
1283}
1284
1285
1286void STEP_PCB_MODEL::SetCopperColor( double r, double g, double b )
1287{
1288 m_copperColor[0] = r;
1289 m_copperColor[1] = g;
1290 m_copperColor[2] = b;
1291}
1292
1293
1294void STEP_PCB_MODEL::SetPadColor( double r, double g, double b )
1295{
1296 m_padColor[0] = r;
1297 m_padColor[1] = g;
1298 m_padColor[2] = b;
1299}
1300
1301
1303{
1304 // Ensure a minimal value (in mm)
1305 m_mergeOCCMaxDist = aDistance;
1306}
1307
1308
1310{
1311 return m_pcb_labels.size() > 0;
1312}
1313
1314
1316 VECTOR2D aStartPoint, VECTOR2D aEndPoint,
1317 double aWidth, double aThickness,
1318 double aZposition, const VECTOR2D& aOrigin )
1319{
1320 // make a wide segment from 2 lines and 2 180 deg arcs
1321 // We need 6 points (3 per arcs)
1322 VECTOR2D coords[6];
1323
1324 // We build a horizontal segment, and after rotate it
1325 double len = ( aEndPoint - aStartPoint ).EuclideanNorm();
1326 double h_width = aWidth/2.0;
1327 // First is end point of first arc, and also start point of first line
1328 coords[0] = VECTOR2D{ 0.0, h_width };
1329
1330 // end point of first line and start point of second arc
1331 coords[1] = VECTOR2D{ len, h_width };
1332
1333 // middle point of second arc
1334 coords[2] = VECTOR2D{ len + h_width, 0.0 };
1335
1336 // start point of second line and end point of second arc
1337 coords[3] = VECTOR2D{ len, -h_width };
1338
1339 // end point of second line and start point of first arc
1340 coords[4] = VECTOR2D{ 0, -h_width };
1341
1342 // middle point of first arc
1343 coords[5] = VECTOR2D{ -h_width, 0.0 };
1344
1345 // Rotate and move to segment position
1346 EDA_ANGLE seg_angle( aEndPoint - aStartPoint );
1347
1348 for( int ii = 0; ii < 6; ii++ )
1349 {
1350 RotatePoint( coords[ii], VECTOR2D{ 0, 0 }, -seg_angle ),
1351 coords[ii] += aStartPoint;
1352 }
1353
1354
1355 // Convert to 3D points
1356 gp_Pnt coords3D[ 6 ];
1357
1358 for( int ii = 0; ii < 6; ii++ )
1359 {
1360 coords3D[ii] = gp_Pnt( pcbIUScale.IUTomm( coords[ii].x - aOrigin.x ),
1361 -pcbIUScale.IUTomm( coords[ii].y - aOrigin.y ), aZposition );
1362 }
1363
1364 // Build OpenCascade shape outlines
1365 BRepBuilderAPI_MakeWire wire;
1366 bool success = true;
1367
1368 // Short segments (distance between end points < m_mergeOCCMaxDist(in mm)) must be
1369 // skipped because OCC merge end points, and a null shape is created
1370 bool short_seg = pcbIUScale.IUTomm( len ) <= m_mergeOCCMaxDist;
1371
1372 try
1373 {
1374 TopoDS_Edge edge;
1375
1376 if( short_seg )
1377 {
1378 Handle( Geom_Circle ) circle = GC_MakeCircle( coords3D[1], // arc1 start point
1379 coords3D[2], // arc1 mid point
1380 coords3D[5] // arc2 mid point
1381 );
1382
1383 edge = BRepBuilderAPI_MakeEdge( circle );
1384 wire.Add( edge );
1385 }
1386 else
1387 {
1388 edge = BRepBuilderAPI_MakeEdge( coords3D[0], coords3D[1] );
1389 wire.Add( edge );
1390
1391 Handle( Geom_TrimmedCurve ) arcOfCircle =
1392 GC_MakeArcOfCircle( coords3D[1], // start point
1393 coords3D[2], // mid point
1394 coords3D[3] // end point
1395 );
1396 edge = BRepBuilderAPI_MakeEdge( arcOfCircle );
1397 wire.Add( edge );
1398
1399 edge = BRepBuilderAPI_MakeEdge( coords3D[3], coords3D[4] );
1400 wire.Add( edge );
1401
1402 Handle( Geom_TrimmedCurve ) arcOfCircle2 =
1403 GC_MakeArcOfCircle( coords3D[4], // start point
1404 coords3D[5], // mid point
1405 coords3D[0] // end point
1406 );
1407 edge = BRepBuilderAPI_MakeEdge( arcOfCircle2 );
1408 wire.Add( edge );
1409 }
1410 }
1411 catch( const Standard_Failure& e )
1412 {
1413 m_reporter->Report( wxString::Format( _( "OCC exception building shape segment: %s\n" ),
1414 e.GetMessageString() ),
1416 return false;
1417 }
1418
1419 BRepBuilderAPI_MakeFace face;
1420
1421 try
1422 {
1423 gp_Pln plane( coords3D[0], gp::DZ() );
1424 face = BRepBuilderAPI_MakeFace( plane, wire );
1425 }
1426 catch( const Standard_Failure& e )
1427 {
1428 m_reporter->Report( wxString::Format( _( "OCC exception building face: %s\n" ),
1429 e.GetMessageString() ),
1431 return false;
1432 }
1433
1434 if( aThickness != 0.0 )
1435 {
1436 aShape = BRepPrimAPI_MakePrism( face, gp_Vec( 0, 0, aThickness ) );
1437
1438 if( aShape.IsNull() )
1439 {
1440 m_reporter->Report( _( "Failed to create a prismatic shape\n" ),
1442 return false;
1443 }
1444 }
1445 else
1446 {
1447 aShape = face;
1448 }
1449
1450 return success;
1451}
1452
1453
1454bool STEP_PCB_MODEL::MakePolygonAsWall( TopoDS_Shape& aShape,
1455 SHAPE_POLY_SET& aPolySet,
1456 double aHeight,
1457 double aZposition, const VECTOR2D& aOrigin )
1458{
1459 std::vector<TopoDS_Shape> testShapes;
1460
1461 bool success = MakeShapes( testShapes, aPolySet, m_simplifyShapes,
1462 aHeight, aZposition, aOrigin );
1463
1464 if( testShapes.size() > 0 )
1465 aShape = testShapes.front();
1466 else
1467 success = false;
1468
1469 return success;
1470}
1471
1472
1473static wxString formatBBox( const BOX2I& aBBox )
1474{
1475 wxString str;
1476 UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::MM );
1477
1478 str << "x0: " << unitsProvider.StringFromValue( aBBox.GetLeft(), false ) << "; ";
1479 str << "y0: " << unitsProvider.StringFromValue( aBBox.GetTop(), false ) << "; ";
1480 str << "x1: " << unitsProvider.StringFromValue( aBBox.GetRight(), false ) << "; ";
1481 str << "y1: " << unitsProvider.StringFromValue( aBBox.GetBottom(), false );
1482
1483 return str;
1484}
1485
1486
1487static bool makeWireFromChain( BRepLib_MakeWire& aMkWire, const SHAPE_LINE_CHAIN& aChain,
1488 double aMergeOCCMaxDist, double aZposition, const VECTOR2D& aOrigin,
1489 REPORTER* aReporter )
1490{
1491 auto toPoint =
1492 [&]( const VECTOR2D& aKiCoords ) -> gp_Pnt
1493 {
1494 return gp_Pnt( pcbIUScale.IUTomm( aKiCoords.x - aOrigin.x ),
1495 -pcbIUScale.IUTomm( aKiCoords.y - aOrigin.y ), aZposition );
1496 };
1497
1498 try
1499 {
1500 auto addSegment = [&]( const VECTOR2I& aPt0, const VECTOR2I& aPt1 ) -> bool
1501 {
1502 if( aPt0 == aPt1 )
1503 return false;
1504
1505 gp_Pnt start = toPoint( aPt0 );
1506 gp_Pnt end = toPoint( aPt1 );
1507
1508 BRepBuilderAPI_MakeEdge mkEdge( start, end );
1509
1510 if( !mkEdge.IsDone() || mkEdge.Edge().IsNull() )
1511 {
1512 aReporter->Report( wxString::Format( _( "Failed to make segment edge (%d %d) -> (%d %d), "
1513 "skipping\n" ),
1514 aPt0.x, aPt0.y,
1515 aPt1.x, aPt1.y ),
1517 }
1518 else
1519 {
1520 aMkWire.Add( mkEdge.Edge() );
1521
1522 if( aMkWire.Error() != BRepLib_WireDone )
1523 {
1524 aReporter->Report( wxString::Format( _( "Failed to add segment edge (%d %d) -> (%d %d)\n" ),
1525 aPt0.x, aPt0.y,
1526 aPt1.x, aPt1.y ),
1528 return false;
1529 }
1530 }
1531
1532 return true;
1533 };
1534
1535 auto addArc = [&]( const VECTOR2I& aPt0, const SHAPE_ARC& aArc ) -> bool
1536 {
1537 // Do not export too short segments: they create broken shape because OCC thinks
1538 Handle( Geom_Curve ) curve;
1539
1540 if( aArc.GetCentralAngle() == ANGLE_360 )
1541 {
1542 gp_Ax2 axis = gp::XOY();
1543 axis.SetLocation( toPoint( aArc.GetCenter() ) );
1544
1545 curve = GC_MakeCircle( axis, pcbIUScale.IUTomm( aArc.GetRadius() ) ).Value();
1546 }
1547 else
1548 {
1549 curve = GC_MakeArcOfCircle( toPoint( aPt0 ), toPoint( aArc.GetArcMid() ),
1550 toPoint( aArc.GetP1() ) ).Value();
1551 }
1552
1553 if( curve.IsNull() )
1554 return false;
1555
1556 aMkWire.Add( BRepBuilderAPI_MakeEdge( curve ) );
1557
1558 if( !aMkWire.IsDone() )
1559 {
1560 aReporter->Report( wxString::Format( _( "Failed to add arc curve from (%d %d), arc p0 "
1561 "(%d %d), mid (%d %d), p1 (%d %d)\n" ),
1562 aPt0.x, aPt0.y,
1563 aArc.GetP0().x, aArc.GetP0().y,
1564 aArc.GetArcMid().x, aArc.GetArcMid().y,
1565 aArc.GetP1().x, aArc.GetP1().y ),
1567 return false;
1568 }
1569
1570 return true;
1571 };
1572
1573 VECTOR2I firstPt;
1574 VECTOR2I lastPt;
1575 bool isFirstShape = true;
1576
1577 for( int i = 0; i <= aChain.PointCount() && i != -1; i = aChain.NextShape( i ) )
1578 {
1579 if( i == 0 )
1580 {
1581 if( aChain.IsArcSegment( 0 ) && aChain.IsArcSegment( aChain.PointCount() - 1 )
1582 && aChain.ArcIndex( 0 ) == aChain.ArcIndex( aChain.PointCount() - 1 ) )
1583 {
1584 // Skip first arc (we should encounter it later)
1585 int nextShape = aChain.NextShape( i );
1586
1587 // If nextShape points to the end, then we have a circle.
1588 if( nextShape != -1 )
1589 i = nextShape;
1590 }
1591 }
1592
1593 if( isFirstShape )
1594 lastPt = aChain.CPoint( i );
1595
1596 bool isArc = aChain.IsArcSegment( i );
1597
1598 if( aChain.IsArcStart( i ) )
1599 {
1600 const SHAPE_ARC& currentArc = aChain.Arc( aChain.ArcIndex( i ) );
1601
1602 if( isFirstShape )
1603 {
1604 firstPt = currentArc.GetP0();
1605 lastPt = firstPt;
1606 }
1607
1608 if( addSegment( lastPt, currentArc.GetP0() ) )
1609 lastPt = currentArc.GetP0();
1610
1611 if( addArc( lastPt, currentArc ) )
1612 lastPt = currentArc.GetP1();
1613 }
1614 else if( !isArc )
1615 {
1616 const SEG& seg = aChain.CSegment( i );
1617
1618 if( isFirstShape )
1619 {
1620 firstPt = seg.A;
1621 lastPt = firstPt;
1622 }
1623
1624 if( addSegment( lastPt, seg.A ) )
1625 lastPt = seg.A;
1626
1627 if( addSegment( lastPt, seg.B ) )
1628 lastPt = seg.B;
1629 }
1630
1631 isFirstShape = false;
1632 }
1633
1634 if( lastPt != firstPt && !addSegment( lastPt, firstPt ) )
1635 {
1636 aReporter->Report( wxString::Format( _( "Failed to close wire at %d, %d -> %d, %d **\n" ),
1637 lastPt.x, lastPt.y,
1638 firstPt.x, firstPt.y ),
1640
1641 return false;
1642 }
1643 }
1644 catch( const Standard_Failure& e )
1645 {
1646 aReporter->Report( wxString::Format( _( "OCC exception creating wire: %s\n" ),
1647 e.GetMessageString() ),
1649 return false;
1650 }
1651
1652 return true;
1653}
1654
1655
1656bool STEP_PCB_MODEL::MakeShapes( std::vector<TopoDS_Shape>& aShapes, const SHAPE_POLY_SET& aPolySet,
1657 bool aConvertToArcs, double aThickness, double aZposition,
1658 const VECTOR2D& aOrigin )
1659{
1660 SHAPE_POLY_SET workingPoly = aPolySet;
1661 workingPoly.Simplify();
1662
1663 SHAPE_POLY_SET fallbackPoly = workingPoly;
1664
1665 if( aConvertToArcs )
1666 {
1667 SHAPE_POLY_SET approximated = workingPoly;
1668
1669 for( size_t polyId = 0; polyId < approximated.CPolygons().size(); polyId++ )
1670 {
1671 SHAPE_POLY_SET::POLYGON& polygon = approximated.Polygon( polyId );
1672
1673 for( size_t contId = 0; contId < polygon.size(); contId++ )
1674 {
1675 SHAPE_LINE_CHAIN approxChain = approximateLineChainWithArcs( polygon[contId] );
1676 polygon[contId] = approxChain;
1677 }
1678 }
1679
1680 fallbackPoly = workingPoly;
1681 workingPoly = approximated;
1682
1683 // TODO: this is not accurate because it doesn't check arcs.
1684 /*if( approximated.IsSelfIntersecting() )
1685 {
1686 m_reporter->Report( wxString::Format( _( "Approximated polygon self-intersection check failed\n"
1687 "z: %g; bounding box: %s\n" ) ),
1688 aZposition,
1689 formatBBox( workingPoly.BBox() ) ),
1690 RPT_SEVERITY_ERROR );
1691 }
1692 else
1693 {
1694 fallbackPoly = workingPoly;
1695 workingPoly = approximated;
1696 }*/
1697 }
1698
1699#if 0 // No longer in use
1700 auto toPoint = [&]( const VECTOR2D& aKiCoords ) -> gp_Pnt
1701 {
1702 return gp_Pnt( pcbIUScale.IUTomm( aKiCoords.x - aOrigin.x ),
1703 -pcbIUScale.IUTomm( aKiCoords.y - aOrigin.y ), aZposition );
1704 };
1705#endif
1706
1707 gp_Pln basePlane( gp_Pnt( 0.0, 0.0, aZposition ),
1708 std::signbit( aThickness ) ? -gp::DZ() : gp::DZ() );
1709
1710 for( size_t polyId = 0; polyId < workingPoly.CPolygons().size(); polyId++ )
1711 {
1712 SHAPE_POLY_SET::POLYGON& polygon = workingPoly.Polygon( polyId );
1713
1714 auto tryMakeWire = [this, &aZposition,
1715 &aOrigin]( const SHAPE_LINE_CHAIN& aContour ) -> TopoDS_Wire
1716 {
1717 TopoDS_Wire wire;
1718 BRepLib_MakeWire mkWire;
1719
1720 makeWireFromChain( mkWire, aContour, m_mergeOCCMaxDist, aZposition, aOrigin, m_reporter );
1721
1722 if( mkWire.IsDone() )
1723 {
1724 wire = mkWire.Wire();
1725 }
1726 else
1727 {
1728 m_reporter->Report( wxString::Format( _( "Wire not done (contour points %d): OCC error %d\n"
1729 "z: %g; bounding box: %s\n" ),
1730 static_cast<int>( aContour.PointCount() ),
1731 static_cast<int>( mkWire.Error() ),
1732 formatBBox( aContour.BBox() ) ),
1734 }
1735
1736 if( !wire.IsNull() )
1737 {
1738 BRepAlgoAPI_Check check( wire, false, true );
1739
1740 if( !check.IsValid() )
1741 {
1742 m_reporter->Report( wxString::Format( _( "Wire self-interference check failed\n"
1743 "z: %g; bounding box: %s\n" ),
1744 aZposition,
1745 formatBBox( aContour.BBox() ) ),
1747 wire.Nullify();
1748 }
1749 }
1750
1751 return wire;
1752 };
1753
1754 BRepBuilderAPI_MakeFace mkFace;
1755
1756 for( size_t contId = 0; contId < polygon.size(); contId++ )
1757 {
1758 try
1759 {
1760 TopoDS_Wire wire = tryMakeWire( polygon[contId] );
1761
1762 if( aConvertToArcs && wire.IsNull() )
1763 {
1764 m_reporter->Report( wxString::Format( _( "Using non-simplified polygon.\n" ) ),
1766
1767 // Fall back to original shape
1768 wire = tryMakeWire( fallbackPoly.CPolygon( polyId )[contId] );
1769 }
1770
1771 if( contId == 0 ) // Outline
1772 {
1773 if( !wire.IsNull() )
1774 {
1775 if( basePlane.Axis().Direction().Z() < 0 )
1776 wire.Reverse();
1777
1778 mkFace = BRepBuilderAPI_MakeFace( basePlane, wire );
1779 }
1780 else
1781 {
1782 m_reporter->Report( wxString::Format( wxT( "** Outline skipped **\n"
1783 "z: %g; bounding box: %s\n" ),
1784 aZposition,
1785 formatBBox( polygon[contId].BBox() ) ),
1787 break;
1788 }
1789 }
1790 else // Hole
1791 {
1792 if( !wire.IsNull() )
1793 {
1794 if( basePlane.Axis().Direction().Z() > 0 )
1795 wire.Reverse();
1796
1797 mkFace.Add( wire );
1798 }
1799 else
1800 {
1801 m_reporter->Report( wxString::Format( wxT( "** Hole skipped **\n"
1802 "z: %g; bounding box: %s\n" ),
1803 aZposition,
1804 formatBBox( polygon[contId].BBox() ) ),
1806 }
1807 }
1808 }
1809 catch( const Standard_Failure& e )
1810 {
1811 m_reporter->Report( wxString::Format( _( "OCC exception creating contour %d: %s\n" ),
1812 static_cast<int>( contId ),
1813 e.GetMessageString() ),
1815 return false;
1816 }
1817 }
1818
1819 if( mkFace.IsDone() )
1820 {
1821 TopoDS_Shape faceShape = mkFace.Shape();
1822
1823 if( aThickness != 0.0 )
1824 {
1825 TopoDS_Shape prism = BRepPrimAPI_MakePrism( faceShape, gp_Vec( 0, 0, aThickness ) );
1826 aShapes.push_back( prism );
1827
1828 if( prism.IsNull() )
1829 {
1830 m_reporter->Report( _( "Failed to create a prismatic shape\n" ), RPT_SEVERITY_ERROR );
1831 return false;
1832 }
1833 }
1834 else
1835 {
1836 aShapes.push_back( faceShape );
1837 }
1838 }
1839 else
1840 {
1841 m_reporter->Report( _( "** Face skipped **\n" ), RPT_SEVERITY_DEBUG );
1842 }
1843 }
1844
1845 return true;
1846}
1847
1848
1849// These colors are based on 3D viewer's colors and are different to "gbrjobColors"
1850static std::vector<FAB_LAYER_COLOR> s_soldermaskColors = {
1851 { NotSpecifiedPrm(), wxColor( 20, 51, 36 ) }, // Not specified, not in .gbrjob file
1852 { _HKI( "Green" ), wxColor( 20, 51, 36 ) }, // used in .gbrjob file
1853 { _HKI( "Red" ), wxColor( 181, 19, 21 ) }, // used in .gbrjob file
1854 { _HKI( "Blue" ), wxColor( 2, 59, 162 ) }, // used in .gbrjob file
1855 { _HKI( "Purple" ), wxColor( 32, 2, 53 ) }, // used in .gbrjob file
1856 { _HKI( "Black" ), wxColor( 11, 11, 11 ) }, // used in .gbrjob file
1857 { _HKI( "White" ), wxColor( 245, 245, 245 ) }, // used in .gbrjob file
1858 { _HKI( "Yellow" ), wxColor( 194, 195, 0 ) }, // used in .gbrjob file
1859 { _HKI( "User defined" ), wxColor( 128, 128, 128 ) } // Free; the name is a dummy name here
1860};
1861
1862
1863static bool colorFromStackup( BOARD_STACKUP_ITEM_TYPE aType, const wxString& aColorStr,
1864 COLOR4D& aColorOut )
1865{
1866 if( !IsPrmSpecified( aColorStr ) )
1867 return false;
1868
1869 if( aColorStr.StartsWith( wxT( "#" ) ) ) // User defined color
1870 {
1871 aColorOut = COLOR4D( aColorStr );
1872 return true;
1873 }
1874 else
1875 {
1876 const std::vector<FAB_LAYER_COLOR>& colors =
1877 ( aType == BS_ITEM_TYPE_SOLDERMASK || aType == BS_ITEM_TYPE_SILKSCREEN )
1879 : GetStandardColors( aType );
1880
1881 for( const FAB_LAYER_COLOR& fabColor : colors )
1882 {
1883 if( fabColor.GetName() == aColorStr )
1884 {
1885 aColorOut = fabColor.GetColor( aType );
1886 return true;
1887 }
1888 }
1889 }
1890
1891 return false;
1892}
1893
1894
1895bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin, bool aPushBoardBody )
1896{
1897 if( m_hasPCB )
1898 {
1899 if( !isBoardOutlineValid() )
1900 return false;
1901
1902 return true;
1903 }
1904
1906
1907 Handle( XCAFDoc_VisMaterialTool ) visMatTool = XCAFDoc_DocumentTool::VisMaterialTool( m_doc->Main() );
1908
1909 m_hasPCB = true; // whether or not operations fail we note that CreatePCB has been invoked
1910
1911 // Support for more than one main outline (more than one board)
1912 m_reporter->Report( wxString::Format( wxT( "Build board outlines (%d outlines) with %d points.\n" ),
1913 aOutline.OutlineCount(),
1914 aOutline.FullPointCount() ),
1916
1917 double boardThickness;
1918 double boardZPos;
1919 getBoardBodyZPlacement( boardZPos, boardThickness );
1920
1921#if 1
1922 // This code should work, and it is working most of time
1923 // However there are issues if the main outline is a circle with holes:
1924 // holes from vias and pads are not working
1925 // see bug https://gitlab.com/kicad/code/kicad/-/issues/17446
1926 // (Holes are missing from STEP export with circular PCB outline)
1927 // Hard to say if the bug is in our code or in OCC 7.7
1928 if( !MakeShapes( m_board_outlines, aOutline, false, boardThickness, boardZPos, aOrigin ) )
1929 {
1930 // Error
1931 m_reporter->Report( _( "OCC error creating main outline.\n" ), RPT_SEVERITY_ERROR );
1932 }
1933#else
1934 // Workaround for bug #17446 Holes are missing from STEP export with circular PCB outline
1935 for( const SHAPE_POLY_SET::POLYGON& polygon : aOutline.CPolygons() )
1936 {
1937 for( size_t contId = 0; contId < polygon.size(); contId++ )
1938 {
1939 const SHAPE_LINE_CHAIN& contour = polygon[contId];
1940 SHAPE_POLY_SET polyset;
1941 polyset.Append( contour );
1942
1943 if( contId == 0 ) // main Outline
1944 {
1945 if( !MakeShapes( m_board_outlines, polyset, false, boardThickness, boardZPos,
1946 aOrigin ) )
1947 {
1948 m_reporter->Report( _( "OCC error creating main outline.\n" ),
1950 }
1951 }
1952 else // Hole inside the main outline
1953 {
1954 if( !MakeShapes( m_boardCutouts, polyset, false, boardThickness, boardZPos,
1955 aOrigin ) )
1956 {
1957 m_reporter->Report( _( "OCC error creating hole in main outline.\n" ),
1959 }
1960 }
1961 }
1962 }
1963#endif
1964
1965 // Even if we've disabled board body export, we still need the shapes for bounding box calculations.
1966 Bnd_Box brdBndBox;
1967
1968 for( const TopoDS_Shape& brdShape : m_board_outlines )
1969 BRepBndLib::Add( brdShape, brdBndBox );
1970
1971 // subtract cutouts (if any)
1972 m_reporter->Report( wxString::Format( wxT( "Build board cutouts and holes (%d hole(s)).\n" ),
1973 (int) ( m_boardCutouts.size() + m_copperCutouts.size() ) ),
1975
1976 auto buildBSB =
1977 [&brdBndBox]( std::vector<TopoDS_Shape>& input, Bnd_BoundSortBox& bsbHoles )
1978 {
1979 // We need to encompass every location we'll need to test in the global bbox,
1980 // otherwise Bnd_BoundSortBox doesn't work near the boundaries.
1981 Bnd_Box brdWithHolesBndBox = brdBndBox;
1982
1983 Handle( Bnd_HArray1OfBox ) holeBoxSet = new Bnd_HArray1OfBox( 0, input.size() - 1 );
1984
1985 for( size_t i = 0; i < input.size(); i++ )
1986 {
1987 Bnd_Box bbox;
1988 BRepBndLib::Add( input[i], bbox );
1989 brdWithHolesBndBox.Add( bbox );
1990 ( *holeBoxSet )[i] = bbox;
1991 }
1992
1993 bsbHoles.Initialize( brdWithHolesBndBox, holeBoxSet );
1994 };
1995
1996 auto subtractShapesMap =
1997 [&tp, this]( const wxString& aWhat, std::map<wxString, std::vector<TopoDS_Shape>>& aShapesMap,
1998 std::vector<TopoDS_Shape>& aHolesList, Bnd_BoundSortBox& aBSBHoles )
1999 {
2000 m_reporter->Report( wxString::Format( _( "Subtracting holes for %s\n" ), aWhat ),
2002
2003 for( auto& [netname, vec] : aShapesMap )
2004 {
2005 std::mutex mutex;
2006
2007 auto subtractLoopFn = [&]( const int a, const int b )
2008 {
2009 for( int shapeId = a; shapeId < b; shapeId++ )
2010 {
2011 TopoDS_Shape& shape = vec[shapeId];
2012
2013 Bnd_Box shapeBbox;
2014 BRepBndLib::Add( shape, shapeBbox );
2015
2016 TopTools_ListOfShape holelist;
2017
2018 {
2019 std::unique_lock lock( mutex );
2020
2021 const TColStd_ListOfInteger& indices = aBSBHoles.Compare( shapeBbox );
2022
2023 for( const Standard_Integer& index : indices )
2024 holelist.Append( aHolesList[index] );
2025 }
2026
2027 if( holelist.IsEmpty() )
2028 continue;
2029
2030 TopTools_ListOfShape cutArgs;
2031 cutArgs.Append( shape );
2032
2033 BRepAlgoAPI_Cut cut;
2034
2035 cut.SetRunParallel( true );
2036 cut.SetToFillHistory( false );
2037
2038 cut.SetArguments( cutArgs );
2039 cut.SetTools( holelist );
2040 cut.Build();
2041
2042 if( cut.HasErrors() || cut.HasWarnings() )
2043 {
2044 m_reporter->Report( wxString::Format( _( "** Got problems while cutting "
2045 "%s net '%s' **\n" ),
2046 aWhat,
2047 UnescapeString( netname ) ),
2049 shapeBbox.Dump();
2050
2051 if( cut.HasErrors() )
2052 {
2053 wxString msg = _( "Errors:\n" );
2054 wxStringOutputStream os_stream( &msg );
2055 wxStdOutputStream out( os_stream );
2056
2057 cut.DumpErrors( out );
2059 }
2060
2061 if( cut.HasWarnings() )
2062 {
2063 wxString msg = _( "Warnings:\n" );
2064 wxStringOutputStream os_stream( &msg );
2065 wxStdOutputStream out( os_stream );
2066
2067 cut.DumpWarnings( out );
2069 }
2070 }
2071
2072 shape = cut.Shape();
2073 }
2074 };
2075
2076 tp.parallelize_loop( vec.size(), subtractLoopFn ).wait();
2077 }
2078 };
2079
2080 auto subtractShapes =
2081 [subtractShapesMap]( const wxString& aWhat, std::vector<TopoDS_Shape>& aShapesList,
2082 std::vector<TopoDS_Shape>& aHolesList, Bnd_BoundSortBox& aBSBHoles )
2083 {
2084 std::map<wxString, std::vector<TopoDS_Shape>> aShapesMap{ { wxEmptyString, aShapesList } };
2085
2086 subtractShapesMap( aWhat, aShapesMap, aHolesList, aBSBHoles );
2087 aShapesList = aShapesMap[wxEmptyString];
2088 };
2089
2090
2091 if( m_boardCutouts.size() )
2092 {
2093 Bnd_BoundSortBox bsbHoles;
2094 buildBSB( m_boardCutouts, bsbHoles );
2095
2096 subtractShapes( _( "shapes" ), m_board_outlines, m_boardCutouts, bsbHoles );
2097 }
2098
2099 if( m_copperCutouts.size() )
2100 {
2101 Bnd_BoundSortBox bsbHoles;
2102 buildBSB( m_copperCutouts, bsbHoles );
2103
2104 subtractShapesMap( _( "pads" ), m_board_copper_pads, m_copperCutouts, bsbHoles );
2105 subtractShapesMap( _( "vias" ), m_board_copper_vias, m_copperCutouts, bsbHoles );
2106 }
2107
2108 if( m_fuseShapes )
2109 {
2110 std::map<wxString, TopTools_ListOfShape> shapesToFuseMap;
2111
2112 auto addShapes = [&shapesToFuseMap]( const wxString& aNetname,
2113 const std::vector<TopoDS_Shape>& aShapes )
2114 {
2115 for( const TopoDS_Shape& shape : aShapes )
2116 shapesToFuseMap[aNetname].Append( shape );
2117 };
2118
2119 for( const auto& [netname, shapes] : m_board_copper )
2120 addShapes( netname, shapes );
2121
2122 for( const auto& [netname, shapes] : m_board_copper_pads )
2123 addShapes( netname, shapes );
2124
2125 for( const auto& [netname, shapes] : m_board_copper_vias )
2126 addShapes( netname, shapes );
2127
2128 m_reporter->Report( wxT( "Fusing shapes\n" ), RPT_SEVERITY_DEBUG );
2129
2130 // Do fusing in parallel
2131 std::mutex mutex;
2132
2133 auto fuseLoopFn = [&]( const wxString& aNetname )
2134 {
2135 auto& toFuse = shapesToFuseMap[aNetname];
2136 TopoDS_Shape fusedShape = fuseShapesOrCompound( toFuse, m_reporter );
2137
2138 if( !fusedShape.IsNull() )
2139 {
2140 std::unique_lock lock( mutex );
2141
2142 m_board_copper_fused[aNetname].emplace_back( fusedShape );
2143
2144 m_board_copper[aNetname].clear();
2145 m_board_copper_pads[aNetname].clear();
2146 m_board_copper_vias[aNetname].clear();
2147 }
2148 };
2149
2150 BS::multi_future<void> mf;
2151
2152 for( const auto& [netname, _] : shapesToFuseMap )
2153 mf.push_back( tp.submit( fuseLoopFn, netname ) );
2154
2155 mf.wait();
2156 }
2157
2158 // push the board to the data structure
2159 m_reporter->Report( wxT( "Generate board full shape.\n" ), RPT_SEVERITY_DEBUG );
2160
2161 // AddComponent adds a label that has a reference (not a parent/child relation) to the real
2162 // label. We need to extract that real label to name it for the STEP output cleanly
2163 // Why are we trying to name the bare board? Because CAD tools like SolidWorks do fun things
2164 // like "deduplicate" imported STEPs by swapping STEP assembly components with already
2165 // identically named assemblies. So we want to avoid having the PCB be generally defaulted
2166 // to "Component" or "Assembly".
2167
2168 // aCompoundNets will place all geometry within a net into one compound.
2169 // aCompoundAll will place all geometry into one compound.
2170 auto pushToAssemblyMap =
2171 [&]( const std::map<wxString, std::vector<TopoDS_Shape>>& aShapesMap,
2172 const TDF_Label& aVisMatLabel, const wxString& aShapeName, bool aCompoundNets,
2173 bool aCompoundAll )
2174 {
2175 std::map<wxString, std::vector<TopoDS_Shape>> shapesMap;
2176
2177 if( aCompoundAll )
2178 {
2179 std::vector<TopoDS_Shape> allShapes;
2180
2181 for( const auto& [netname, shapesList] : aShapesMap )
2182 allShapes.insert( allShapes.end(), shapesList.begin(), shapesList.end() );
2183
2184 if( !allShapes.empty() )
2185 shapesMap[wxEmptyString].emplace_back( makeCompound( allShapes ) );
2186 }
2187 else
2188 {
2189 shapesMap = aShapesMap;
2190 }
2191
2192 for( const auto& [netname, shapesList] : shapesMap )
2193 {
2194 std::vector<TopoDS_Shape> newList;
2195
2196 if( aCompoundNets )
2197 newList.emplace_back( makeCompound( shapesList ) );
2198 else
2199 newList = shapesList;
2200
2201 int i = 1;
2202
2203 for( TopoDS_Shape& shape : newList )
2204 {
2205 Handle( TDataStd_TreeNode ) node;
2206
2207 // Dont expand the component or else coloring it gets hard
2208 TDF_Label lbl = m_assy->AddComponent( m_assy_label, shape, false );
2209 m_pcb_labels.push_back( lbl );
2210
2211 if( m_pcb_labels.back().IsNull() )
2212 return;
2213
2214 lbl.FindAttribute( XCAFDoc::ShapeRefGUID(), node );
2215 TDF_Label shpLbl = node->Father()->Label();
2216
2217 if( !shpLbl.IsNull() )
2218 {
2219 if( visMatTool && !aVisMatLabel.IsNull() )
2220 visMatTool->SetShapeMaterial( shpLbl, aVisMatLabel );
2221
2222 wxString shapeName;
2223
2224 shapeName << m_pcbName;
2225 shapeName << '_';
2226 shapeName << aShapeName;
2227
2228 if( !netname.empty() )
2229 {
2230 shapeName << '_';
2231 shapeName << netname;
2232 }
2233
2234 if( newList.size() > 1 )
2235 {
2236 shapeName << '_';
2237 shapeName << i;
2238 }
2239
2240 TCollection_ExtendedString partname( shapeName.ToUTF8().data() );
2241 TDataStd_Name::Set( shpLbl, partname );
2242 }
2243
2244 i++;
2245 }
2246 }
2247 };
2248
2249 auto pushToAssembly =
2250 [&]( const std::vector<TopoDS_Shape>& aShapesList, const TDF_Label& aVisMatLabel,
2251 const wxString& aShapeName, bool aCompound )
2252 {
2253 const std::map<wxString, std::vector<TopoDS_Shape>> shapesMap{ { wxEmptyString, aShapesList } };
2254
2255 pushToAssemblyMap( shapesMap, aVisMatLabel, aShapeName, aCompound, aCompound );
2256 };
2257
2258 auto makeMaterial =
2259 [&]( const TCollection_AsciiString& aName, const Quantity_ColorRGBA& aBaseColor,
2260 double aMetallic, double aRoughness ) -> TDF_Label
2261 {
2262 Handle( XCAFDoc_VisMaterial ) vismat = new XCAFDoc_VisMaterial;
2263 XCAFDoc_VisMaterialPBR pbr;
2264 pbr.BaseColor = aBaseColor;
2265 pbr.Metallic = aMetallic;
2266 pbr.Roughness = aRoughness;
2267 vismat->SetPbrMaterial( pbr );
2268 return visMatTool->AddMaterial( vismat, aName );
2269 };
2270
2271 // Init colors for the board items
2272 Quantity_ColorRGBA copper_color( m_copperColor[0], m_copperColor[1], m_copperColor[2], 1.0 );
2273 Quantity_ColorRGBA pad_color( m_padColor[0], m_padColor[1], m_padColor[2], 1.0 );
2274
2275 Quantity_ColorRGBA board_color( 0.3f, 0.3f, 0.3f, 1.0f );
2276 Quantity_ColorRGBA silk_color( 1.0f, 1.0f, 1.0f, 0.9f );
2277 Quantity_ColorRGBA mask_color( 0.08f, 0.2f, 0.14f, 0.83f );
2278
2279 // Get colors from stackup
2280 for( const BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
2281 {
2282 COLOR4D col;
2283
2284 if( !colorFromStackup( item->GetType(), item->GetColor(), col ) )
2285 continue;
2286
2287 if( item->GetBrdLayerId() == F_Mask || item->GetBrdLayerId() == B_Mask )
2288 {
2289 col.Darken( 0.2 );
2290 mask_color.SetValues( col.r, col.g, col.b, col.a );
2291 }
2292
2293 if( item->GetBrdLayerId() == F_SilkS || item->GetBrdLayerId() == B_SilkS )
2294 silk_color.SetValues( col.r, col.g, col.b, col.a );
2295
2296 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC && item->GetTypeName() == KEY_CORE )
2297 board_color.SetValues( col.r, col.g, col.b, col.a );
2298 }
2299
2301 {
2302 board_color = mask_color;
2303 board_color.SetAlpha( 1.0 );
2304 }
2305
2306 TDF_Label mask_mat = makeMaterial( "soldermask", mask_color, 0.0, 0.6 );
2307 TDF_Label silk_mat = makeMaterial( "silkscreen", silk_color, 0.0, 0.9 );
2308 TDF_Label copper_mat = makeMaterial( "copper", copper_color, 1.0, 0.4 );
2309 TDF_Label pad_mat = makeMaterial( "pad", pad_color, 1.0, 0.4 );
2310 TDF_Label board_mat = makeMaterial( "board", board_color, 0.0, 0.8 );
2311
2312 pushToAssemblyMap( m_board_copper, copper_mat, "copper", true, true );
2313 pushToAssemblyMap( m_board_copper_pads, pad_mat, "pad", true, true );
2314 pushToAssemblyMap( m_board_copper_vias, copper_mat, "via", true, true );
2315 pushToAssemblyMap( m_board_copper_fused, copper_mat, "copper", true, true );
2316 pushToAssembly( m_board_silkscreen, silk_mat, "silkscreen", true );
2317 pushToAssembly( m_board_soldermask, mask_mat, "soldermask", true );
2318
2319 if( aPushBoardBody )
2320 pushToAssembly( m_board_outlines, board_mat, "PCB", false );
2321
2322#if( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 )
2323 m_assy->UpdateAssemblies();
2324#endif
2325
2326 return true;
2327}
2328
2329
2330#ifdef SUPPORTS_IGES
2331// write the assembly model in IGES format
2332bool STEP_PCB_MODEL::WriteIGES( const wxString& aFileName )
2333{
2334 if( !isBoardOutlineValid() )
2335 {
2336 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ),
2337 aFileName ),
2339 return false;
2340 }
2341
2342 m_outFmt = OUTPUT_FORMAT::FMT_OUT_IGES;
2343
2344 wxFileName fn( aFileName );
2345 IGESControl_Controller::Init();
2346 IGESCAFControl_Writer writer;
2347 writer.SetColorMode( Standard_True );
2348 writer.SetNameMode( Standard_True );
2349 IGESData_GlobalSection header = writer.Model()->GlobalSection();
2350 header.SetFileName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
2351 header.SetSendName( new TCollection_HAsciiString( "KiCad electronic assembly" ) );
2352 header.SetAuthorName( new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.author" ) ) );
2353 header.SetCompanyName( new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.company" ) ) );
2354 writer.Model()->SetGlobalSection( header );
2355
2356 if( Standard_False == writer.Perform( m_doc, aFileName.c_str() ) )
2357 return false;
2358
2359 return true;
2360}
2361#endif
2362
2363
2364bool STEP_PCB_MODEL::WriteSTEP( const wxString& aFileName, bool aOptimize )
2365{
2366 if( !isBoardOutlineValid() )
2367 {
2368 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ),
2369 aFileName ),
2371 return false;
2372 }
2373
2374 m_outFmt = OUTPUT_FORMAT::FMT_OUT_STEP;
2375
2376 wxFileName fn( aFileName );
2377
2378 STEPCAFControl_Writer writer;
2379 writer.SetColorMode( Standard_True );
2380 writer.SetNameMode( Standard_True );
2381
2382 // This must be set before we "transfer" the document.
2383 // Should default to kicad_pcb.general.title_block.title,
2384 // but in the meantime, defaulting to the basename of the output
2385 // target is still better than "open cascade step translter v..."
2386 // UTF8 should be ok from ISO 10303-21:2016, but... older stuff? use boring ascii
2387 if( !Interface_Static::SetCVal( "write.step.product.name", fn.GetName().ToAscii() ) )
2388 {
2389 m_reporter->Report( _( "Failed to set STEP product name, but will attempt to continue." ),
2391 }
2392
2393 // Setting write.surfacecurve.mode to 0 reduces file size and write/read times.
2394 // But there are reports that this mode might be less compatible in some cases.
2395 if( !Interface_Static::SetIVal( "write.surfacecurve.mode", aOptimize ? 0 : 1 ) )
2396 {
2397 m_reporter->Report( _( "Failed to set surface curve mode, but will attempt to continue." ),
2399 }
2400
2401 if( Standard_False == writer.Transfer( m_doc, STEPControl_AsIs ) )
2402 return false;
2403
2404 APIHeaderSection_MakeHeader hdr( writer.ChangeWriter().Model() );
2405
2406 // Note: use only Ascii7 chars, non Ascii7 chars (therefore UFT8 chars)
2407 // are creating issues in the step file
2408 hdr.SetName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
2409
2410 // TODO: how to control and ensure consistency with IGES?
2411 hdr.SetAuthorValue( 1, new TCollection_HAsciiString( "Pcbnew" ) );
2412 hdr.SetOrganizationValue( 1, new TCollection_HAsciiString( "Kicad" ) );
2413 hdr.SetOriginatingSystem( new TCollection_HAsciiString( "KiCad to STEP converter" ) );
2414 hdr.SetDescriptionValue( 1, new TCollection_HAsciiString( "KiCad electronic assembly" ) );
2415
2416 bool success = true;
2417
2418 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
2419 wxString currCWD = wxGetCwd();
2420 wxString workCWD = fn.GetPath();
2421
2422 if( !workCWD.IsEmpty() )
2423 wxSetWorkingDirectory( workCWD );
2424
2425 char tmpfname[] = "$tempfile$.step";
2426
2427 if( Standard_False == writer.Write( tmpfname ) )
2428 success = false;
2429
2430 if( success )
2431 {
2432
2433 // Preserve the permissions of the current file
2434 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpfname );
2435
2436 if( !wxRenameFile( tmpfname, fn.GetFullName(), true ) )
2437 {
2438 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'.\n" ),
2439 tmpfname,
2440 fn.GetFullName() ),
2442 success = false;
2443 }
2444 }
2445
2446 wxSetWorkingDirectory( currCWD );
2447
2448 return success;
2449}
2450
2451
2452bool STEP_PCB_MODEL::WriteBREP( const wxString& aFileName )
2453{
2454 if( !isBoardOutlineValid() )
2455 {
2456 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ),
2457 aFileName ),
2459 return false;
2460 }
2461
2462 m_outFmt = OUTPUT_FORMAT::FMT_OUT_BREP;
2463
2464 // s_assy = shape tool for the source
2465 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( m_doc->Main() );
2466
2467 // retrieve assembly as a single shape
2468 TopoDS_Shape shape = getOneShape( s_assy );
2469
2470 wxFileName fn( aFileName );
2471
2472 wxFFileOutputStream ffStream( fn.GetFullPath() );
2473 wxStdOutputStream stdStream( ffStream );
2474
2475#if OCC_VERSION_HEX >= 0x070600
2476 BRepTools::Write( shape, stdStream, false, false, TopTools_FormatVersion_VERSION_1 );
2477#else
2478 BRepTools::Write( shape, stdStream );
2479#endif
2480
2481 return true;
2482}
2483
2484
2485bool STEP_PCB_MODEL::WriteXAO( const wxString& aFileName )
2486{
2487 wxFileName fn( aFileName );
2488
2489 wxFFileOutputStream ffStream( fn.GetFullPath() );
2490 wxStdOutputStream file( ffStream );
2491
2492 if( !ffStream.IsOk() )
2493 {
2494 m_reporter->Report( wxString::Format( "Could not open file '%s'", fn.GetFullPath() ),
2496 return false;
2497 }
2498
2499 m_outFmt = OUTPUT_FORMAT::FMT_OUT_XAO;
2500
2501 // s_assy = shape tool for the source
2502 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( m_doc->Main() );
2503
2504 // retrieve assembly as a single shape
2505 const TopoDS_Shape shape = getOneShape( s_assy );
2506
2507 std::map<wxString, std::vector<int>> groups[4];
2508 std::map<wxString, double> groupAreas;
2509 TopExp_Explorer exp;
2510 int faceIndex = 0;
2511
2512 for( exp.Init( shape, TopAbs_FACE ); exp.More(); exp.Next() )
2513 {
2514 TopoDS_Shape subShape = exp.Current();
2515
2516 Bnd_Box bbox;
2517 BRepBndLib::Add( subShape, bbox );
2518
2519 for( const auto& [padKey, pairs] : m_pad_points )
2520 {
2521 for( const auto& pair : pairs )
2522 {
2523 const auto& [point, padTestShape] = pair;
2524
2525 if( bbox.IsOut( point ) )
2526 continue;
2527
2528 BRepAdaptor_Surface surface( TopoDS::Face( subShape ) );
2529
2530 if( surface.GetType() != GeomAbs_Plane )
2531 continue;
2532
2533 BRepExtrema_DistShapeShape dist( padTestShape, subShape );
2534 dist.Perform();
2535
2536 if( !dist.IsDone() )
2537 continue;
2538
2539 if( dist.Value() < Precision::Approximation() )
2540 {
2541 // Push as a face group
2542 groups[2][padKey].push_back( faceIndex );
2543
2544 GProp_GProps system;
2545 BRepGProp::SurfaceProperties( subShape, system );
2546
2547 double surfaceArea = system.Mass() / 1e6; // Convert to meters^2
2548 groupAreas[padKey] += surfaceArea;
2549 }
2550 }
2551 }
2552
2553 faceIndex++;
2554 }
2555
2556 // Based on Gmsh code
2557 file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
2558 file << "<XAO version=\"1.0\" author=\"KiCad\">" << std::endl;
2559 file << " <geometry name=\"" << fn.GetName() << "\">" << std::endl;
2560 file << " <shape format=\"BREP\"><![CDATA[";
2561#if OCC_VERSION_HEX < 0x070600
2562 BRepTools::Write( shape, file );
2563#else
2564 BRepTools::Write( shape, file, Standard_True, Standard_True, TopTools_FormatVersion_VERSION_1 );
2565#endif
2566 file << "]]></shape>" << std::endl;
2567 file << " <topology>" << std::endl;
2568
2569 TopTools_IndexedMapOfShape mainMap;
2570 TopExp::MapShapes( shape, mainMap );
2571 std::set<int> topo[4];
2572
2573 static const TopAbs_ShapeEnum c_dimShapeTypes[] = { TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE,
2574 TopAbs_SOLID };
2575
2576 static const std::string c_dimLabel[] = { "vertex", "edge", "face", "solid" };
2577 static const std::string c_dimLabels[] = { "vertices", "edges", "faces", "solids" };
2578
2579 for( int dim = 0; dim < 4; dim++ )
2580 {
2581 for( exp.Init( shape, c_dimShapeTypes[dim] ); exp.More(); exp.Next() )
2582 {
2583 TopoDS_Shape subShape = exp.Current();
2584 int idx = mainMap.FindIndex( subShape );
2585
2586 if( idx && !topo[dim].count( idx ) )
2587 topo[dim].insert( idx );
2588 }
2589 }
2590
2591 for( int dim = 0; dim <= 3; dim++ )
2592 {
2593 std::string labels = c_dimLabels[dim];
2594 std::string label = c_dimLabel[dim];
2595
2596 file << " <" << labels << " count=\"" << topo[dim].size() << "\">" << std::endl;
2597 int index = 0;
2598
2599 for( auto p : topo[dim] )
2600 {
2601 std::string name( "" );
2602 file << " <" << label << " index=\"" << index << "\" "
2603 << "name=\"" << name << "\" "
2604 << "reference=\"" << p << "\"/>" << std::endl;
2605
2606 index++;
2607 }
2608 file << " </" << labels << ">" << std::endl;
2609 }
2610
2611 file << " </topology>" << std::endl;
2612 file << " </geometry>" << std::endl;
2613 file << " <groups count=\""
2614 << groups[0].size() + groups[1].size() + groups[2].size() + groups[3].size() << "\">"
2615 << std::endl;
2616
2617 int groupNumber = 1;
2618
2619 m_reporter->Report( wxT( "Pad definitions:\n" ), RPT_SEVERITY_DEBUG );
2620 m_reporter->Report( wxT( "Number\tName\tArea (m^2)\n" ), RPT_SEVERITY_DEBUG );
2621
2622 for( int dim = 0; dim <= 3; dim++ )
2623 {
2624 std::string label = c_dimLabel[dim];
2625
2626 for( auto g : groups[dim] )
2627 {
2628 //std::string name = model->getPhysicalName( dim, g.first );
2629 wxString name = g.first;
2630
2631 if( name.empty() )
2632 { // create same unique name as for MED export
2633 std::ostringstream gs;
2634 gs << "G_" << dim << "D_" << g.first;
2635 name = gs.str();
2636 }
2637 file << " <group name=\"" << name << "\" dimension=\"" << label;
2638//#if 1
2639// // Gmsh XAO extension: also save the physical tag, so that XAO can be used
2640// // to serialize OCC geometries, ready to be used by GetDP, GmshFEM & co
2641// file << "\" tag=\"" << g.first;
2642//#endif
2643 file << "\" count=\"" << g.second.size() << "\">" << std::endl;
2644
2645 for( auto index : g.second )
2646 file << " <element index=\"" << index << "\"/>" << std::endl;
2647
2648 file << " </group>" << std::endl;
2649
2650 m_reporter->Report( wxString::Format( "%d\t%s\t%g\n",
2651 groupNumber,
2652 name,
2653 groupAreas[name] ),
2655
2656 groupNumber++;
2657 }
2658 }
2659
2660 m_reporter->Report( wxT( "\n" ), RPT_SEVERITY_DEBUG );
2661
2662 file << " </groups>" << std::endl;
2663 file << " <fields count=\"0\"/>" << std::endl;
2664 file << "</XAO>" << std::endl;
2665
2666 return true;
2667}
2668
2669
2670bool STEP_PCB_MODEL::getModelLabel( const std::string& aFileNameUTF8, VECTOR3D aScale, TDF_Label& aLabel,
2671 bool aSubstituteModels, wxString* aErrorMessage )
2672{
2673 std::string model_key = aFileNameUTF8 + "_" + std::to_string( aScale.x )
2674 + "_" + std::to_string( aScale.y ) + "_" + std::to_string( aScale.z );
2675
2676 MODEL_MAP::const_iterator mm = m_models.find( model_key );
2677
2678 if( mm != m_models.end() )
2679 {
2680 aLabel = mm->second;
2681 return true;
2682 }
2683
2684 aLabel.Nullify();
2685
2686 Handle( TDocStd_Document ) doc;
2687 m_app->NewDocument( "MDTV-XCAF", doc );
2688
2689 wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
2690 MODEL3D_FORMAT_TYPE modelFmt = fileType( aFileNameUTF8.c_str() );
2691
2692 switch( modelFmt )
2693 {
2694 case FMT_IGES:
2695 if( !readIGES( doc, aFileNameUTF8.c_str() ) )
2696 {
2697 m_reporter->Report( wxString::Format( wxT( "readIGES() failed on filename '%s'.\n" ),
2698 fileName ),
2700 return false;
2701 }
2702 break;
2703
2704 case FMT_STEP:
2705 if( !readSTEP( doc, aFileNameUTF8.c_str() ) )
2706 {
2707 m_reporter->Report( wxString::Format( wxT( "readSTEP() failed on filename '%s'.\n" ),
2708 fileName ),
2710 return false;
2711 }
2712 break;
2713
2714 case FMT_STEPZ:
2715 {
2716 // To export a compressed step file (.stpz or .stp.gz file), the best way is to
2717 // decaompress it in a temporaty file and load this temporary file
2718 wxFFileInputStream ifile( fileName );
2719 wxFileName outFile( fileName );
2720
2721 outFile.SetPath( wxStandardPaths::Get().GetTempDir() );
2722 outFile.SetExt( wxT( "step" ) );
2723 wxFileOffset size = ifile.GetLength();
2724
2725 if( size == wxInvalidOffset )
2726 {
2727 m_reporter->Report( wxString::Format( wxT( "getModelLabel() failed on filename '%s'.\n" ),
2728 fileName ),
2730 return false;
2731 }
2732
2733 {
2734 bool success = false;
2735 wxFFileOutputStream ofile( outFile.GetFullPath() );
2736
2737 if( !ofile.IsOk() )
2738 return false;
2739
2740 char* buffer = new char[size];
2741
2742 ifile.Read( buffer, size );
2743 std::string expanded;
2744
2745 try
2746 {
2747 expanded = gzip::decompress( buffer, size );
2748 success = true;
2749 }
2750 catch( ... )
2751 {
2752 m_reporter->Report( wxString::Format( wxT( "failed to decompress '%s'.\n" ),
2753 fileName ),
2755 }
2756
2757 if( expanded.empty() )
2758 {
2759 ifile.Reset();
2760 ifile.SeekI( 0 );
2761 wxZipInputStream izipfile( ifile );
2762 std::unique_ptr<wxZipEntry> zip_file( izipfile.GetNextEntry() );
2763
2764 if( zip_file && !zip_file->IsDir() && izipfile.CanRead() )
2765 {
2766 izipfile.Read( ofile );
2767 success = true;
2768 }
2769 }
2770 else
2771 {
2772 ofile.Write( expanded.data(), expanded.size() );
2773 }
2774
2775 delete[] buffer;
2776 ofile.Close();
2777
2778 if( success )
2779 {
2780 std::string altFileNameUTF8 = TO_UTF8( outFile.GetFullPath() );
2781 success = getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false );
2782 }
2783
2784 return success;
2785 }
2786
2787 break;
2788 }
2789
2790 case FMT_WRL:
2791 case FMT_WRZ:
2792 /* WRL files are preferred for internal rendering, due to superior material properties, etc.
2793 * However they are not suitable for MCAD export.
2794 *
2795 * If a .wrl file is specified, attempt to locate a replacement file for it.
2796 *
2797 * If a valid replacement file is found, the label for THAT file will be associated with
2798 * the .wrl file
2799 */
2800 if( aSubstituteModels )
2801 {
2802 wxFileName wrlName( fileName );
2803
2804 wxString basePath = wrlName.GetPath();
2805 wxString baseName = wrlName.GetName();
2806
2807 // List of alternate files to look for
2808 // Given in order of preference
2809 // (Break if match is found)
2810 wxArrayString alts;
2811
2812 // Step files
2813 alts.Add( wxT( "stp" ) );
2814 alts.Add( wxT( "step" ) );
2815 alts.Add( wxT( "STP" ) );
2816 alts.Add( wxT( "STEP" ) );
2817 alts.Add( wxT( "Stp" ) );
2818 alts.Add( wxT( "Step" ) );
2819 alts.Add( wxT( "stpz" ) );
2820 alts.Add( wxT( "stpZ" ) );
2821 alts.Add( wxT( "STPZ" ) );
2822 alts.Add( wxT( "step.gz" ) );
2823 alts.Add( wxT( "stp.gz" ) );
2824
2825 // IGES files
2826 alts.Add( wxT( "iges" ) );
2827 alts.Add( wxT( "IGES" ) );
2828 alts.Add( wxT( "igs" ) );
2829 alts.Add( wxT( "IGS" ) );
2830
2831 //TODO - Other alternative formats?
2832
2833 for( const auto& alt : alts )
2834 {
2835 wxFileName altFile( basePath, baseName + wxT( "." ) + alt );
2836
2837 if( altFile.IsOk() && altFile.FileExists() )
2838 {
2839 std::string altFileNameUTF8 = TO_UTF8( altFile.GetFullPath() );
2840
2841 // When substituting a STEP/IGS file for VRML, do not apply the VRML scaling
2842 // to the new STEP model. This process of auto-substitution is janky as all
2843 // heck so let's not mix up un-displayed scale factors with potentially
2844 // mis-matched files. And hope that the user doesn't have multiples files
2845 // named "model.wrl" and "model.stp" referring to different parts.
2846 // TODO: Fix model handling in v7. Default models should only be STP.
2847 // Have option to override this in DISPLAY.
2848 if( getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false ) )
2849 {
2850 return true;
2851 }
2852 }
2853 }
2854
2855 // VRML models only work when exporting to glTF
2856 // Also OCCT < 7.9.0 fail to load most VRML 2.0 models because of Switch nodes
2857 if( m_outFmt == OUTPUT_FORMAT::FMT_OUT_GLTF )
2858 {
2859 if( readVRML( doc, aFileNameUTF8.c_str() ) )
2860 {
2861 Handle( XCAFDoc_ShapeTool ) shapeTool =
2862 XCAFDoc_DocumentTool::ShapeTool( doc->Main() );
2863
2864 prefixNames( shapeTool->Label(),
2865 TCollection_ExtendedString( baseName.c_str().AsChar() ) );
2866 }
2867 else
2868 {
2869 m_reporter->Report( wxString::Format( wxT( "readVRML() failed on filename '%s'.\n" ),
2870 fileName ),
2872 return false;
2873 }
2874 }
2875 }
2876 else // Substitution is not allowed
2877 {
2878 if( aErrorMessage )
2879 aErrorMessage->Printf( wxT( "Cannot load any VRML model for this export.\n" ) );
2880
2881 return false;
2882 }
2883
2884 break;
2885
2886 // TODO: implement IDF and EMN converters
2887
2888 default:
2889 m_reporter->Report( wxString::Format( _( "Cannot identify actual file type for '%s'.\n" ),
2890 fileName ),
2892 return false;
2893 }
2894
2895 aLabel = transferModel( doc, m_doc, aScale );
2896
2897 if( aLabel.IsNull() )
2898 {
2899 m_reporter->Report( wxString::Format( _( "Could not transfer model data from file '%s'.\n" ),
2900 fileName ),
2902 return false;
2903 }
2904
2905 // attach the PART NAME ( base filename: note that in principle
2906 // different models may have the same base filename )
2907 wxFileName afile( fileName );
2908 std::string pname( afile.GetName().ToUTF8() );
2909 TCollection_ExtendedString partname( pname.c_str() );
2910 TDataStd_Name::Set( aLabel, partname );
2911
2912 m_models.insert( MODEL_DATUM( model_key, aLabel ) );
2913 ++m_components;
2914 return true;
2915}
2916
2917
2918bool STEP_PCB_MODEL::getModelLocation( bool aBottom, VECTOR2D aPosition, double aRotation,
2919 VECTOR3D aOffset, VECTOR3D aOrientation,
2920 TopLoc_Location& aLocation )
2921{
2922 // Order of operations:
2923 // a. aOrientation is applied -Z*-Y*-X
2924 // b. aOffset is applied
2925 // Top ? add thickness to the Z offset
2926 // c. Bottom ? Rotate on X axis (in contrast to most ECAD which mirror on Y),
2927 // then rotate on +Z
2928 // Top ? rotate on -Z
2929 // d. aPosition is applied
2930 //
2931 // Note: Y axis is inverted in KiCad
2932
2933 gp_Trsf lPos;
2934 lPos.SetTranslation( gp_Vec( aPosition.x, -aPosition.y, 0.0 ) );
2935
2936 // Offset board thickness
2937 aOffset.z += BOARD_OFFSET;
2938
2939 double boardThickness;
2940 double boardZPos;
2941 getBoardBodyZPlacement( boardZPos, boardThickness );
2942 double top = std::max( boardZPos, boardZPos + boardThickness );
2943 double bottom = std::min( boardZPos, boardZPos + boardThickness );
2944
2945 // 3D step models are placed on the top of copper layers.
2946 // This is true for SMD shapes, and perhaps not always true for TH shapes,
2947 // but we use this Z position for any 3D shape.
2948 double f_pos, f_thickness;
2949 getLayerZPlacement( F_Cu, f_pos, f_thickness );
2950 top += f_thickness;
2951 getLayerZPlacement( B_Cu, f_pos, f_thickness );
2952 bottom += f_thickness; // f_thickness is < 0 for B_Cu layer
2953
2954 gp_Trsf lRot;
2955
2956 if( aBottom )
2957 {
2958 aOffset.z -= bottom;
2959 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
2960 lPos.Multiply( lRot );
2961 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), M_PI );
2962 lPos.Multiply( lRot );
2963 }
2964 else
2965 {
2966 aOffset.z += top;
2967 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
2968 lPos.Multiply( lRot );
2969 }
2970
2971 gp_Trsf lOff;
2972 lOff.SetTranslation( gp_Vec( aOffset.x, aOffset.y, aOffset.z ) );
2973 lPos.Multiply( lOff );
2974
2975 gp_Trsf lOrient;
2976 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), -aOrientation.z );
2977 lPos.Multiply( lOrient );
2978 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 1.0, 0.0 ) ), -aOrientation.y );
2979 lPos.Multiply( lOrient );
2980 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), -aOrientation.x );
2981 lPos.Multiply( lOrient );
2982
2983 aLocation = TopLoc_Location( lPos );
2984 return true;
2985}
2986
2987
2988bool STEP_PCB_MODEL::readIGES( Handle( TDocStd_Document )& doc, const char* fname )
2989{
2990 IGESControl_Controller::Init();
2991 IGESCAFControl_Reader reader;
2992 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
2993
2994 if( stat != IFSelect_RetDone )
2995 return false;
2996
2997 // Enable user-defined shape precision
2998 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
2999 return false;
3000
3001 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
3002 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
3003 return false;
3004
3005 // set other translation options
3006 reader.SetColorMode( true ); // use model colors
3007 reader.SetNameMode( false ); // don't use IGES label names
3008 reader.SetLayerMode( false ); // ignore LAYER data
3009
3010 if( !reader.Transfer( doc ) )
3011 {
3012 if( doc->CanClose() == CDM_CCS_OK )
3013 doc->Close();
3014
3015 return false;
3016 }
3017
3018 // are there any shapes to translate?
3019 if( reader.NbShapes() < 1 )
3020 {
3021 if( doc->CanClose() == CDM_CCS_OK )
3022 doc->Close();
3023
3024 return false;
3025 }
3026
3027 return true;
3028}
3029
3030
3031bool STEP_PCB_MODEL::readSTEP( Handle( TDocStd_Document )& doc, const char* fname )
3032{
3033 STEPCAFControl_Reader reader;
3034 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
3035
3036 if( stat != IFSelect_RetDone )
3037 return false;
3038
3039 // Enable user-defined shape precision
3040 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
3041 return false;
3042
3043 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
3044 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
3045 return false;
3046
3047 // set other translation options
3048 reader.SetColorMode( true ); // use model colors
3049 reader.SetNameMode( true ); // use label names
3050 reader.SetLayerMode( false ); // ignore LAYER data
3051
3052 if( !reader.Transfer( doc ) )
3053 {
3054 if( doc->CanClose() == CDM_CCS_OK )
3055 doc->Close();
3056
3057 return false;
3058 }
3059
3060 // are there any shapes to translate?
3061 if( reader.NbRootsForTransfer() < 1 )
3062 {
3063 if( doc->CanClose() == CDM_CCS_OK )
3064 doc->Close();
3065
3066 return false;
3067 }
3068
3069 return true;
3070}
3071
3072
3073bool STEP_PCB_MODEL::readVRML( Handle( TDocStd_Document ) & doc, const char* fname )
3074{
3075#if OCC_VERSION_HEX >= 0x070700
3076 VrmlAPI_CafReader reader;
3077 RWMesh_CoordinateSystemConverter conv;
3078 conv.SetInputLengthUnit( 2.54 );
3079 reader.SetCoordinateSystemConverter( conv );
3080 reader.SetDocument( doc );
3081
3082 if( !reader.Perform( TCollection_AsciiString( fname ), Message_ProgressRange() ) )
3083 return false;
3084
3085 return true;
3086#else
3087 return false;
3088#endif
3089}
3090
3091
3092TDF_Label STEP_PCB_MODEL::transferModel( Handle( TDocStd_Document ) & source,
3093 Handle( TDocStd_Document ) & dest, VECTOR3D aScale )
3094{
3095 // transfer data from Source into a top level component of Dest
3096 // s_assy = shape tool for the source
3097 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( source->Main() );
3098
3099 // retrieve all free shapes within the assembly
3100 TDF_LabelSequence frshapes;
3101 s_assy->GetFreeShapes( frshapes );
3102
3103 // d_assy = shape tool for the destination
3104 Handle( XCAFDoc_ShapeTool ) d_assy = XCAFDoc_DocumentTool::ShapeTool( dest->Main() );
3105
3106 // create a new shape within the destination and set the assembly tool to point to it
3107 TDF_Label d_targetLabel = d_assy->NewShape();
3108
3109 if( frshapes.Size() == 1 )
3110 {
3111 TDocStd_XLinkTool link;
3112 link.Copy( d_targetLabel, frshapes.First() );
3113 }
3114 else
3115 {
3116 // Rare case
3117 for( TDF_Label& s_shapeLabel : frshapes )
3118 {
3119 TDF_Label d_component = d_assy->NewShape();
3120
3121 TDocStd_XLinkTool link;
3122 link.Copy( d_component, s_shapeLabel );
3123
3124 d_assy->AddComponent( d_targetLabel, d_component, TopLoc_Location() );
3125 }
3126 }
3127
3128 if( aScale.x != 1.0 || aScale.y != 1.0 || aScale.z != 1.0 )
3129 rescaleShapes( d_targetLabel, gp_XYZ( aScale.x, aScale.y, aScale.z ) );
3130
3131 return d_targetLabel;
3132}
3133
3134
3135bool STEP_PCB_MODEL::performMeshing( Handle( XCAFDoc_ShapeTool ) & aShapeTool )
3136{
3137 TDF_LabelSequence freeShapes;
3138 aShapeTool->GetFreeShapes( freeShapes );
3139
3140 m_reporter->Report( wxT( "Meshing model\n" ), RPT_SEVERITY_DEBUG );
3141
3142 // GLTF is a mesh format, we have to trigger opencascade to mesh the shapes we composited into the asesmbly
3143 // To mesh models, lets just grab the free shape root and execute on them
3144 for( Standard_Integer i = 1; i <= freeShapes.Length(); ++i )
3145 {
3146 TDF_Label label = freeShapes.Value( i );
3147 TopoDS_Shape shape;
3148 aShapeTool->GetShape( label, shape );
3149
3150 // These deflection values basically affect the accuracy of the mesh generated, a tighter
3151 // deflection will result in larger meshes
3152 // We could make this a tunable parameter, but for now fix it
3153 const Standard_Real linearDeflection = 0.14;
3154 const Standard_Real angularDeflection = DEG2RAD( 30.0 );
3155 BRepMesh_IncrementalMesh mesh( shape, linearDeflection, Standard_False, angularDeflection,
3156 Standard_True );
3157 }
3158
3159 return true;
3160}
3161
3162
3163bool STEP_PCB_MODEL::WriteGLTF( const wxString& aFileName )
3164{
3165 if( !isBoardOutlineValid() )
3166 {
3167 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ),
3168 aFileName ),
3170 return false;
3171 }
3172
3173 m_outFmt = OUTPUT_FORMAT::FMT_OUT_GLTF;
3174
3175 performMeshing( m_assy );
3176
3177 wxFileName fn( aFileName );
3178
3179 const char* tmpGltfname = "$tempfile$.glb";
3180 RWGltf_CafWriter cafWriter( tmpGltfname, true );
3181
3182 cafWriter.SetTransformationFormat( RWGltf_WriterTrsfFormat_Compact );
3183 cafWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit( 0.001 );
3184 cafWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(
3185 RWMesh_CoordinateSystem_Zup );
3186#if OCC_VERSION_HEX >= 0x070700
3187 cafWriter.SetParallel( true );
3188#endif
3189 TColStd_IndexedDataMapOfStringString metadata;
3190
3191 metadata.Add( TCollection_AsciiString( "pcb_name" ),
3192 TCollection_ExtendedString( fn.GetName().wc_str() ) );
3193 metadata.Add( TCollection_AsciiString( "source_pcb_file" ),
3194 TCollection_ExtendedString( fn.GetFullName().wc_str() ) );
3195 metadata.Add( TCollection_AsciiString( "generator" ),
3196 TCollection_AsciiString( wxString::Format( wxS( "KiCad %s" ), GetSemanticVersion() ).ToAscii() ) );
3197 metadata.Add( TCollection_AsciiString( "generated_at" ),
3198 TCollection_AsciiString( GetISO8601CurrentDateTime().ToAscii() ) );
3199
3200 bool success = true;
3201
3202 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
3203 wxString currCWD = wxGetCwd();
3204 wxString workCWD = fn.GetPath();
3205
3206 if( !workCWD.IsEmpty() )
3207 wxSetWorkingDirectory( workCWD );
3208
3209 success = cafWriter.Perform( m_doc, metadata, Message_ProgressRange() );
3210
3211 if( success )
3212 {
3213 // Preserve the permissions of the current file
3214 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpGltfname );
3215
3216 if( !wxRenameFile( tmpGltfname, fn.GetFullName(), true ) )
3217 {
3218 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'.\n" ),
3219 tmpGltfname,
3220 fn.GetFullName() ),
3222 success = false;
3223 }
3224 }
3225
3226 wxSetWorkingDirectory( currCWD );
3227
3228 return success;
3229}
3230
3231
3232bool STEP_PCB_MODEL::WritePLY( const wxString& aFileName )
3233{
3234#if OCC_VERSION_HEX < 0x070700
3235 m_reporter->Report( wxT( "PLY export is not supported before OCCT 7.7.0\n" ), RPT_SEVERITY_ERROR );
3236 return false;
3237#else
3238
3239 if( !isBoardOutlineValid() )
3240 {
3241 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ),
3242 aFileName ),
3244 return false;
3245 }
3246
3247 m_outFmt = OUTPUT_FORMAT::FMT_OUT_PLY;
3248
3249 performMeshing( m_assy );
3250
3251 wxFileName fn( aFileName );
3252
3253 const char* tmpFname = "$tempfile$.ply";
3254 RWPly_CafWriter cafWriter( tmpFname );
3255
3256 cafWriter.SetFaceId( true ); // TODO: configurable SetPartId/SetFaceId
3257 cafWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit( 0.001 );
3258 cafWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem( RWMesh_CoordinateSystem_Zup );
3259
3260 TColStd_IndexedDataMapOfStringString metadata;
3261
3262 metadata.Add( TCollection_AsciiString( "pcb_name" ),
3263 TCollection_ExtendedString( fn.GetName().wc_str() ) );
3264 metadata.Add( TCollection_AsciiString( "source_pcb_file" ),
3265 TCollection_ExtendedString( fn.GetFullName().wc_str() ) );
3266 metadata.Add( TCollection_AsciiString( "generator" ),
3267 TCollection_AsciiString( wxString::Format( wxS( "KiCad %s" ),
3268 GetSemanticVersion() ).ToAscii() ) );
3269 metadata.Add( TCollection_AsciiString( "generated_at" ),
3270 TCollection_AsciiString( GetISO8601CurrentDateTime().ToAscii() ) );
3271
3272 bool success = true;
3273
3274 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
3275 wxString currCWD = wxGetCwd();
3276 wxString workCWD = fn.GetPath();
3277
3278 if( !workCWD.IsEmpty() )
3279 wxSetWorkingDirectory( workCWD );
3280
3281 success = cafWriter.Perform( m_doc, metadata, Message_ProgressRange() );
3282
3283 if( success )
3284 {
3285 // Preserve the permissions of the current file
3286 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname );
3287
3288 if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
3289 {
3290 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'.\n" ),
3291 tmpFname,
3292 fn.GetFullName() ),
3294 success = false;
3295 }
3296 }
3297
3298 wxSetWorkingDirectory( currCWD );
3299
3300 return success;
3301#endif
3302}
3303
3304
3305bool STEP_PCB_MODEL::WriteSTL( const wxString& aFileName )
3306{
3307 if( !isBoardOutlineValid() )
3308 {
3309 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ),
3310 aFileName ),
3312 return false;
3313 }
3314
3315 m_outFmt = OUTPUT_FORMAT::FMT_OUT_STL;
3316
3317 performMeshing( m_assy );
3318
3319 wxFileName fn( aFileName );
3320
3321 const char* tmpFname = "$tempfile$.stl";
3322
3323 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
3324 wxString currCWD = wxGetCwd();
3325 wxString workCWD = fn.GetPath();
3326
3327 if( !workCWD.IsEmpty() )
3328 wxSetWorkingDirectory( workCWD );
3329
3330 bool success = StlAPI_Writer().Write( getOneShape( m_assy ), tmpFname );
3331
3332 if( success )
3333 {
3334 // Preserve the permissions of the current file
3335 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname );
3336
3337 if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
3338 {
3339 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'.\n" ),
3340 tmpFname,
3341 fn.GetFullName() ),
3343 success = false;
3344 }
3345 }
3346
3347 wxSetWorkingDirectory( currCWD );
3348
3349 return success;
3350}
const char * name
Definition: DXF_plotter.cpp:62
@ ERROR_OUTSIDE
Definition: approximation.h:33
@ ERROR_INSIDE
Definition: approximation.h:34
constexpr int ARC_HIGH_DEF
Definition: base_units.h:122
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:110
bool IsPrmSpecified(const wxString &aPrmValue)
BOARD_STACKUP_ITEM_TYPE
Definition: board_stackup.h:43
@ BS_ITEM_TYPE_COPPER
Definition: board_stackup.h:45
@ BS_ITEM_TYPE_SILKSCREEN
Definition: board_stackup.h:51
@ BS_ITEM_TYPE_DIELECTRIC
Definition: board_stackup.h:46
@ BS_ITEM_TYPE_SOLDERMASK
Definition: board_stackup.h:49
wxString GetSemanticVersion()
Get the semantic version string for KiCad defined inside the KiCadVersion.cmake file in the variable ...
const wxString & GetShortNetname() const
int GetY() const
Definition: board_item.h:98
int GetX() const
Definition: board_item.h:92
FOOTPRINT * GetParentFootprint() const
Definition: board_item.cpp:299
Manage one layer needed to make a physical board.
Definition: board_stackup.h:96
wxString GetTypeName() const
int GetSublayersCount() const
PCB_LAYER_ID GetBrdLayerId() const
int GetThickness(int aDielectricSubLayer=0) const
BOARD_STACKUP_ITEM_TYPE GetType() const
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
constexpr coord_type GetLeft() const
Definition: box2.h:228
constexpr coord_type GetRight() const
Definition: box2.h:217
constexpr coord_type GetTop() const
Definition: box2.h:229
constexpr coord_type GetBottom() const
Definition: box2.h:222
wxString GetReferenceAsString() const
Definition: footprint.h:630
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:104
double r
Red component.
Definition: color4d.h:392
double g
Green component.
Definition: color4d.h:393
COLOR4D & Darken(double aFactor)
Makes the color darker by a given factor.
Definition: color4d.h:226
double a
Alpha component.
Definition: color4d.h:395
double b
Blue component.
Definition: color4d.h:394
LSET is a set of PCB_LAYER_IDs.
Definition: lset.h:37
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition: lset.cpp:297
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition: lset.h:63
Definition: pad.h:54
PAD_PROP GetProperty() const
Definition: pad.h:443
LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition: pad.h:437
bool FlashLayer(int aLayer, bool aOnlyCheckIfPermitted=false) const
Check to see whether the pad should be flashed on the specific layer.
Definition: pad.cpp:357
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
Definition: pad.h:788
const VECTOR2I & GetDrillSize() const
Definition: pad.h:305
PAD_ATTRIB GetAttribute() const
Definition: pad.h:440
const wxString & GetNumber() const
Definition: pad.h:136
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc=ERROR_INSIDE, bool ignoreLineWidth=false) const override
Convert the pad shape to a closed polygon.
Definition: pad.cpp:1970
std::shared_ptr< SHAPE_SEGMENT > GetEffectiveHoleShape() const override
Return a SHAPE_SEGMENT object representing the pad's hole.
Definition: pad.cpp:584
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition: reporter.h:102
Definition: seg.h:42
VECTOR2I A
Definition: seg.h:49
VECTOR2I B
Definition: seg.h:50
const VECTOR2I & GetArcMid() const
Definition: shape_arc.h:118
const VECTOR2I & GetP1() const
Definition: shape_arc.h:117
double GetRadius() const
Definition: shape_arc.cpp:880
const VECTOR2I & GetP0() const
Definition: shape_arc.h:116
const VECTOR2I & GetCenter() const
Definition: shape_arc.cpp:849
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_ARC & Arc(size_t aArc) const
bool IsClosed() const override
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
void Clear()
Remove all points from the line chain.
const SHAPE_LINE_CHAIN Slice(int aStartIndex, int aEndIndex=-1) const
Return a subset of this line chain containing the [start_index, end_index] range of points.
const std::optional< INTERSECTION > SelfIntersectingWithArcs() const
Check if the line chain is self-intersecting.
int NextShape(int aPointIndex) const
Return the vertex index of the next shape in the chain, or -1 if aPointIndex is the last shape.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
virtual size_t GetSegmentCount() const override
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
bool IsArcSegment(size_t aSegment) const
void RemoveShape(int aPointIndex)
Remove the shape at the given index from the line chain.
bool IsArcStart(size_t aIndex) const
Represent a set of closed polygons.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
int FullPointCount() const
Return the number of points in the shape poly set.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int OutlineCount() const
Return the number of outlines in the set.
const POLYGON & CPolygon(int aIndex) const
const std::vector< POLYGON > & CPolygons() const
const SEG & GetSeg() const
int GetWidth() const
OUTPUT_FORMAT m_outFmt
The current output format for created file.
void SetCopperColor(double r, double g, double b)
std::vector< TopoDS_Shape > m_board_silkscreen
wxString m_netFilter
bool CreatePCB(SHAPE_POLY_SET &aOutline, VECTOR2D aOrigin, bool aPushBoardBody)
bool MakeShapeAsThickSegment(TopoDS_Shape &aShape, VECTOR2D aStartPoint, VECTOR2D aEndPoint, double aWidth, double aThickness, double aZposition, const VECTOR2D &aOrigin)
Make a segment shape based on start and end point.
bool WritePLY(const wxString &aFileName)
std::map< wxString, std::vector< TopoDS_Shape > > m_board_copper_vias
std::map< wxString, std::vector< TopoDS_Shape > > m_board_copper_pads
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)
wxString m_pcbName
Name of the PCB, which will most likely be the file name of the path.
std::vector< TopoDS_Shape > m_boardCutouts
std::vector< TopoDS_Shape > m_board_soldermask
bool AddPolygonShapes(const SHAPE_POLY_SET *aPolyShapes, PCB_LAYER_ID aLayer, const VECTOR2D &aOrigin, const wxString &aNetname)
void getBoardBodyZPlacement(double &aZPos, double &aThickness)
TDF_Label m_assy_label
void SetFuseShapes(bool aValue)
bool WriteXAO(const wxString &aFileName)
bool WriteGLTF(const wxString &aFileName)
Write the assembly in binary GLTF Format.
REPORTER * m_reporter
double m_mergeOCCMaxDist
std::vector< TDF_Label > m_pcb_labels
STEP_PCB_MODEL(const wxString &aPcbName, REPORTER *aReporter)
void getLayerZPlacement(const PCB_LAYER_ID aLayer, double &aZPos, double &aThickness)
std::vector< TopoDS_Shape > m_board_outlines
void SetSimplifyShapes(bool aValue)
bool readVRML(Handle(TDocStd_Document) &aDoc, const char *aFname)
void SetPadColor(double r, double g, double b)
void SetEnabledLayers(const LSET &aLayers)
bool performMeshing(Handle(XCAFDoc_ShapeTool) &aShapeTool)
bool MakePolygonAsWall(TopoDS_Shape &aShape, SHAPE_POLY_SET &aPolySet, double aHeight, double aZposition, const VECTOR2D &aOrigin)
Make a polygonal shape to create a vertical wall.
bool readSTEP(Handle(TDocStd_Document) &aDoc, const char *aFname)
Handle(XCAFApp_Application) m_app
bool getModelLocation(bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset, VECTOR3D aOrientation, TopLoc_Location &aLocation)
virtual ~STEP_PCB_MODEL()
double m_copperColor[3]
bool WriteSTL(const wxString &aFileName)
void SetStackup(const BOARD_STACKUP &aStackup)
void SetNetFilter(const wxString &aFilter)
double m_padColor[3]
std::map< wxString, std::vector< TopoDS_Shape > > m_board_copper
bool MakeShapes(std::vector< TopoDS_Shape > &aShapes, const SHAPE_POLY_SET &aPolySet, bool aConvertToArcs, double aThickness, double aZposition, const VECTOR2D &aOrigin)
Convert a SHAPE_POLY_SET to TopoDS_Shape's (polygonal vertical prisms, or flat faces)
bool AddBarrel(const SHAPE_SEGMENT &aShape, PCB_LAYER_ID aLayerTop, PCB_LAYER_ID aLayerBot, bool aVia, const VECTOR2D &aOrigin, const wxString &aNetname)
bool getModelLabel(const std::string &aFileNameUTF8, VECTOR3D aScale, TDF_Label &aLabel, bool aSubstituteModels, wxString *aErrorMessage=nullptr)
Load a 3D model data.
bool readIGES(Handle(TDocStd_Document) &aDoc, const char *aFname)
bool AddHole(const SHAPE_SEGMENT &aShape, int aPlatingThickness, PCB_LAYER_ID aLayerTop, PCB_LAYER_ID aLayerBot, bool aVia, const VECTOR2D &aOrigin, bool aCutCopper, bool aCutBody)
bool WriteSTEP(const wxString &aFileName, bool aOptimize)
std::vector< TopoDS_Shape > m_copperCutouts
MODEL_MAP m_models
std::map< wxString, std::vector< std::pair< gp_Pnt, TopoDS_Shape > > > m_pad_points
void getCopperLayerZPlacement(const PCB_LAYER_ID aLayer, double &aZPos, double &aThickness)
TDF_Label transferModel(Handle(TDocStd_Document)&source, Handle(TDocStd_Document) &dest, VECTOR3D aScale)
bool AddPadShape(const PAD *aPad, const VECTOR2D &aOrigin, bool aVia, SHAPE_POLY_SET *aClipPolygon=nullptr)
std::map< wxString, std::vector< TopoDS_Shape > > m_board_copper_fused
void OCCSetMergeMaxDistance(double aDistance=OCC_MAX_DISTANCE_TO_MERGE_POINTS)
BOARD_STACKUP m_stackup
bool WriteBREP(const wxString &aFileName)
wxString StringFromValue(double aValue, bool aAddUnitLabel=false, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
Converts aValue in internal units into a united string.
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition: vector2d.h:283
T y
Definition: vector3.h:64
T z
Definition: vector3.h:65
T x
Definition: vector3.h:63
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a oblong shape to a polygon, using multiple segments.
#define _HKI(x)
#define _(s)
static constexpr EDA_ANGLE ANGLE_360
Definition: eda_angle.h:410
wxString LayerName(int aLayer)
Returns the default display name for a given layer.
Definition: layer_id.cpp:31
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition: layer_ids.h:765
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition: layer_ids.h:788
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition: layer_ids.h:663
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:60
@ B_Mask
Definition: layer_ids.h:98
@ B_Cu
Definition: layer_ids.h:65
@ F_Mask
Definition: layer_ids.h:97
@ F_SilkS
Definition: layer_ids.h:100
@ B_SilkS
Definition: layer_ids.h:101
@ F_Cu
Definition: layer_ids.h:64
This file contains miscellaneous commonly used macros and functions.
bool DuplicatePermissions(const wxString &aSrc, const wxString &aDest)
Duplicates the file security data from one file to another ensuring that they are the same between bo...
Definition: unix/io.cpp:47
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:393
#define USER_PREC
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_DEBUG
static bool addSegment(VRML_LAYER &model, IDF_SEGMENT *seg, int icont, int iseg)
const std::vector< FAB_LAYER_COLOR > & GetStandardColors(BOARD_STACKUP_ITEM_TYPE aType)
wxString NotSpecifiedPrm()
#define KEY_PREPREG
#define KEY_CORE
MODEL3D_FORMAT_TYPE
@ FMT_STEP
@ FMT_IGES
@ FMT_IDF
@ FMT_WRZ
@ FMT_STEPZ
@ FMT_NONE
@ FMT_WRL
@ FMT_EMN
static TopoDS_Shape fuseShapesOrCompound(const TopTools_ListOfShape &aInputShapes, REPORTER *aReporter)
static bool colorFromStackup(BOARD_STACKUP_ITEM_TYPE aType, const wxString &aColorStr, COLOR4D &aColorOut)
static bool makeWireFromChain(BRepLib_MakeWire &aMkWire, const SHAPE_LINE_CHAIN &aChain, double aMergeOCCMaxDist, double aZposition, const VECTOR2D &aOrigin, REPORTER *aReporter)
static Standard_Boolean prefixNames(const TDF_Label &aLabel, const TCollection_ExtendedString &aPrefix)
#define APPROX_DBG(stmt)
static Standard_Boolean rescaleShapes(const TDF_Label &theLabel, const gp_XYZ &aScale)
static constexpr double BOARD_OFFSET
static wxString formatBBox(const BOX2I &aBBox)
static bool fuseShapes(auto &aInputShapes, TopoDS_Shape &aOutShape, REPORTER *aReporter)
static TopoDS_Compound makeCompound(const auto &aInputShapes)
static std::vector< FAB_LAYER_COLOR > s_soldermaskColors
MODEL3D_FORMAT_TYPE fileType(const char *aFileName)
static VECTOR2D CircleCenterFrom3Points(const VECTOR2D &p1, const VECTOR2D &p2, const VECTOR2D &p3)
static SHAPE_LINE_CHAIN approximateLineChainWithArcs(const SHAPE_LINE_CHAIN &aSrc)
static constexpr double USER_ANGLE_PREC
static constexpr double USER_PREC
static TopoDS_Shape getOneShape(Handle(XCAFDoc_ShapeTool) aShapeTool)
static constexpr double OCC_MAX_DISTANCE_TO_MERGE_POINTS
Default distance between points to treat them as separate ones (mm) 0.001 mm or less is a reasonable ...
std::pair< std::string, TDF_Label > MODEL_DATUM
static constexpr double ARC_TO_SEGMENT_MAX_ERROR_MM
#define CLOSE_STREAM(var)
#define OPEN_ISTREAM(var, name)
wxString UnescapeString(const wxString &aSource)
wxString GetISO8601CurrentDateTime()
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:429
constexpr double IUTomm(int iu) const
Definition: base_units.h:88
constexpr int mmToIU(double mm) const
Definition: base_units.h:90
VECTOR2I center
int radius
VECTOR2I end
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
Definition: thread_pool.cpp:30
static thread_pool * tp
Definition: thread_pool.cpp:28
BS::thread_pool thread_pool
Definition: thread_pool.h:31
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition: trigo.cpp:229
double DEG2RAD(double deg)
Definition: trigo.h:166
VECTOR2< int32_t > VECTOR2I
Definition: vector2d.h:695
VECTOR3< double > VECTOR3D
Definition: vector3.h:230