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