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 // Paint board body in soldermask colors if soldermask is not exported as a layer
2330 if( !m_enabledLayers.Contains( F_Mask ) && !m_enabledLayers.Contains( B_Mask ) )
2331 {
2332 board_color = front_mask_color;
2333 board_color.SetAlpha( 1.0 );
2334 }
2335
2336 TDF_Label front_mask_mat = makeMaterial( "soldermask", front_mask_color, 0.0, 0.6 );
2337 TDF_Label back_mask_mat = makeMaterial( "soldermask", back_mask_color, 0.0, 0.6 );
2338 TDF_Label front_silk_mat = makeMaterial( "silkscreen", front_silk_color, 0.0, 0.9 );
2339 TDF_Label back_silk_mat = makeMaterial( "silkscreen", back_silk_color, 0.0, 0.9 );
2340 TDF_Label copper_mat = makeMaterial( "copper", copper_color, 1.0, 0.4 );
2341 TDF_Label pad_mat = makeMaterial( "pad", pad_color, 1.0, 0.4 );
2342 TDF_Label board_mat = makeMaterial( "board", board_color, 0.0, 0.8 );
2343
2344 pushToAssemblyMap( m_board_copper, copper_mat, "copper", true, true, "Copper" );
2345 pushToAssemblyMap( m_board_copper_pads, pad_mat, "pad", true, true, "Pads" );
2346 pushToAssemblyMap( m_board_copper_vias, copper_mat, "via", true, true, "Via" );
2347 pushToAssemblyMap( m_board_copper_fused, copper_mat, "copper", true, true, "Copper" );
2348 pushToAssembly( m_board_front_silk, front_silk_mat, "silkscreen", true, "Top Silkscreen" );
2349 pushToAssembly( m_board_back_silk, back_silk_mat, "silkscreen", true, "Bottom Silkscreen" );
2350 pushToAssembly( m_board_front_mask, front_mask_mat, "soldermask", true, "Top Soldermask" );
2351 pushToAssembly( m_board_back_mask, back_mask_mat, "soldermask", true, "Bottom Soldermask" );
2352
2353 if( aPushBoardBody )
2354 pushToAssembly( m_board_outlines, board_mat, "PCB", false, "Body" );
2355
2356#if( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 )
2357 m_assy->UpdateAssemblies();
2358#endif
2359
2360 return true;
2361}
2362
2363
2364#ifdef SUPPORTS_IGES
2365// write the assembly model in IGES format
2366bool STEP_PCB_MODEL::WriteIGES( const wxString& aFileName )
2367{
2368 if( !isBoardOutlineValid() )
2369 {
2370 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
2371 aFileName ),
2373 return false;
2374 }
2375
2377
2378 wxFileName fn( aFileName );
2379 IGESControl_Controller::Init();
2380 IGESCAFControl_Writer writer;
2381 writer.SetColorMode( Standard_True );
2382 writer.SetNameMode( Standard_True );
2383 IGESData_GlobalSection header = writer.Model()->GlobalSection();
2384 header.SetFileName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
2385 header.SetSendName( new TCollection_HAsciiString( "KiCad electronic assembly" ) );
2386 header.SetAuthorName( new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.author" ) ) );
2387 header.SetCompanyName( new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.company" ) ) );
2388 writer.Model()->SetGlobalSection( header );
2389
2390 if( Standard_False == writer.Perform( m_doc, aFileName.c_str() ) )
2391 return false;
2392
2393 return true;
2394}
2395#endif
2396
2397bool STEP_PCB_MODEL::CompressSTEP( wxString& inputFile, wxString& outputFile )
2398{
2399 wxFileInputStream input( inputFile );
2400 wxFileOutputStream output( outputFile );
2401
2402 if( !input.IsOk() )
2403 {
2404 m_reporter->Report( wxString::Format( _( "Cannot create input stream '%s'.\n" ), inputFile ) );
2405 return false;
2406 }
2407
2408 if( !output.IsOk() )
2409 {
2410 m_reporter->Report( wxString::Format( _( "Cannot create output stream '%s'.\n" ), outputFile ) );
2411 return false;
2412 }
2413
2414 wxZlibOutputStream zlibStream( output, -1, wxZLIB_GZIP );
2415
2416 if( !zlibStream.IsOk() )
2417 {
2418 m_reporter->Report( _( "Impossible create compress stream" ) );
2419 return false;
2420 }
2421
2422 input.Read( zlibStream );
2423
2424 if( input.LastRead() == 0 || zlibStream.LastWrite() == 0 )
2425 {
2426 m_reporter->Report( _( "Compress read or write error" ) );
2427 return false;
2428 }
2429
2430 zlibStream.Close();
2431 output.Close();
2432
2433 return true;
2434}
2435
2436bool STEP_PCB_MODEL::WriteSTEP( const wxString& aFileName, bool aOptimize, bool compress )
2437{
2438 if( !isBoardOutlineValid() )
2439 {
2440 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
2441 aFileName ),
2443 return false;
2444 }
2445
2447
2448 wxFileName fn( aFileName );
2449
2450 STEPCAFControl_Writer writer;
2451 writer.SetColorMode( Standard_True );
2452 writer.SetNameMode( Standard_True );
2453
2454 // This must be set before we "transfer" the document.
2455 // Should default to kicad_pcb.general.title_block.title,
2456 // but in the meantime, defaulting to the basename of the output
2457 // target is still better than "open cascade step translter v..."
2458 // UTF8 should be ok from ISO 10303-21:2016, but... older stuff? use boring ascii
2459 if( !Interface_Static::SetCVal( "write.step.product.name", fn.GetName().ToAscii() ) )
2460 {
2461 m_reporter->Report( _( "Failed to set STEP product name, but will attempt to continue." ),
2463 }
2464
2465 // Setting write.surfacecurve.mode to 0 reduces file size and write/read times.
2466 // But there are reports that this mode might be less compatible in some cases.
2467 if( !Interface_Static::SetIVal( "write.surfacecurve.mode", aOptimize ? 0 : 1 ) )
2468 {
2469 m_reporter->Report( _( "Failed to set surface curve mode, but will attempt to continue." ),
2471 }
2472
2473 if( Standard_False == writer.Transfer( m_doc, STEPControl_AsIs ) )
2474 return false;
2475
2476 APIHeaderSection_MakeHeader hdr( writer.ChangeWriter().Model() );
2477
2478 // Note: use only Ascii7 chars, non Ascii7 chars (therefore UFT8 chars)
2479 // are creating issues in the step file
2480 hdr.SetName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
2481
2482 // TODO: how to control and ensure consistency with IGES?
2483 hdr.SetAuthorValue( 1, new TCollection_HAsciiString( "Pcbnew" ) );
2484 hdr.SetOrganizationValue( 1, new TCollection_HAsciiString( "Kicad" ) );
2485 hdr.SetOriginatingSystem( new TCollection_HAsciiString( "KiCad to STEP converter" ) );
2486 hdr.SetDescriptionValue( 1, new TCollection_HAsciiString( "KiCad electronic assembly" ) );
2487
2488 bool success = true;
2489
2490 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
2491 wxString currCWD = wxGetCwd();
2492 wxString workCWD = fn.GetPath();
2493
2494 if( !workCWD.IsEmpty() )
2495 wxSetWorkingDirectory( workCWD );
2496
2497 wxString tmpfname( "$tempfile$.step" );
2498
2499 if( Standard_False == writer.Write( tmpfname.c_str() ) )
2500 success = false;
2501
2502 if( compress && success )
2503 {
2504 wxString srcTmp( tmpfname );
2505 wxString dstTmp( "$tempfile$.stpz" );
2506
2507 success = STEP_PCB_MODEL::CompressSTEP( srcTmp, dstTmp );
2508 wxRemoveFile( srcTmp );
2509
2510 tmpfname = dstTmp;
2511 }
2512
2513 if( success )
2514 {
2515
2516 // Preserve the permissions of the current file
2517 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpfname.c_str() );
2518
2519 if( !wxRenameFile( tmpfname, fn.GetFullName(), true ) )
2520 {
2521 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'." ),
2522 tmpfname,
2523 fn.GetFullName() ),
2525 success = false;
2526 }
2527 }
2528
2529 wxSetWorkingDirectory( currCWD );
2530
2531 return success;
2532}
2533
2534
2535bool STEP_PCB_MODEL::WriteBREP( const wxString& aFileName )
2536{
2537 if( !isBoardOutlineValid() )
2538 {
2539 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
2540 aFileName ),
2542 return false;
2543 }
2544
2546
2547 // s_assy = shape tool for the source
2548 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( m_doc->Main() );
2549
2550 // retrieve assembly as a single shape
2551 TopoDS_Shape shape = getOneShape( s_assy );
2552
2553 wxFileName fn( aFileName );
2554
2555 wxFFileOutputStream ffStream( fn.GetFullPath() );
2556 wxStdOutputStream stdStream( ffStream );
2557
2558#if OCC_VERSION_HEX >= 0x070600
2559 BRepTools::Write( shape, stdStream, false, false, TopTools_FormatVersion_VERSION_1 );
2560#else
2561 BRepTools::Write( shape, stdStream );
2562#endif
2563
2564 return true;
2565}
2566
2567
2568bool STEP_PCB_MODEL::WriteXAO( const wxString& aFileName )
2569{
2570 wxFileName fn( aFileName );
2571
2572 wxFFileOutputStream ffStream( fn.GetFullPath() );
2573 wxStdOutputStream file( ffStream );
2574
2575 if( !ffStream.IsOk() )
2576 {
2577 m_reporter->Report( wxString::Format( "Could not open file '%s'", fn.GetFullPath() ),
2579 return false;
2580 }
2581
2583
2584 // s_assy = shape tool for the source
2585 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( m_doc->Main() );
2586
2587 // retrieve assembly as a single shape
2588 const TopoDS_Shape shape = getOneShape( s_assy );
2589
2590 std::map<wxString, std::vector<int>> groups[4];
2591 std::map<wxString, double> groupAreas;
2592 TopExp_Explorer exp;
2593 int faceIndex = 0;
2594
2595 for( exp.Init( shape, TopAbs_FACE ); exp.More(); exp.Next() )
2596 {
2597 TopoDS_Shape subShape = exp.Current();
2598
2599 Bnd_Box bbox;
2600 BRepBndLib::Add( subShape, bbox );
2601
2602 for( const auto& [padKey, pairs] : m_pad_points )
2603 {
2604 for( const auto& pair : pairs )
2605 {
2606 const auto& [point, padTestShape] = pair;
2607
2608 if( bbox.IsOut( point ) )
2609 continue;
2610
2611 BRepAdaptor_Surface surface( TopoDS::Face( subShape ) );
2612
2613 if( surface.GetType() != GeomAbs_Plane )
2614 continue;
2615
2616 BRepExtrema_DistShapeShape dist( padTestShape, subShape );
2617 dist.Perform();
2618
2619 if( !dist.IsDone() )
2620 continue;
2621
2622 if( dist.Value() < Precision::Approximation() )
2623 {
2624 // Push as a face group
2625 groups[2][padKey].push_back( faceIndex );
2626
2627 GProp_GProps system;
2628 BRepGProp::SurfaceProperties( subShape, system );
2629
2630 double surfaceArea = system.Mass() / 1e6; // Convert to meters^2
2631 groupAreas[padKey] += surfaceArea;
2632 }
2633 }
2634 }
2635
2636 faceIndex++;
2637 }
2638
2639 // Based on Gmsh code
2640 file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
2641 file << "<XAO version=\"1.0\" author=\"KiCad\">" << std::endl;
2642 file << " <geometry name=\"" << fn.GetName() << "\">" << std::endl;
2643 file << " <shape format=\"BREP\"><![CDATA[";
2644#if OCC_VERSION_HEX < 0x070600
2645 BRepTools::Write( shape, file );
2646#else
2647 BRepTools::Write( shape, file, Standard_True, Standard_True, TopTools_FormatVersion_VERSION_1 );
2648#endif
2649 file << "]]></shape>" << std::endl;
2650 file << " <topology>" << std::endl;
2651
2652 TopTools_IndexedMapOfShape mainMap;
2653 TopExp::MapShapes( shape, mainMap );
2654 std::set<int> topo[4];
2655
2656 static const TopAbs_ShapeEnum c_dimShapeTypes[] = { TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE,
2657 TopAbs_SOLID };
2658
2659 static const std::string c_dimLabel[] = { "vertex", "edge", "face", "solid" };
2660 static const std::string c_dimLabels[] = { "vertices", "edges", "faces", "solids" };
2661
2662 for( int dim = 0; dim < 4; dim++ )
2663 {
2664 for( exp.Init( shape, c_dimShapeTypes[dim] ); exp.More(); exp.Next() )
2665 {
2666 TopoDS_Shape subShape = exp.Current();
2667 int idx = mainMap.FindIndex( subShape );
2668
2669 if( idx && !topo[dim].count( idx ) )
2670 topo[dim].insert( idx );
2671 }
2672 }
2673
2674 for( int dim = 0; dim <= 3; dim++ )
2675 {
2676 std::string labels = c_dimLabels[dim];
2677 std::string label = c_dimLabel[dim];
2678
2679 file << " <" << labels << " count=\"" << topo[dim].size() << "\">" << std::endl;
2680 int index = 0;
2681
2682 for( auto p : topo[dim] )
2683 {
2684 std::string name( "" );
2685 file << " <" << label << " index=\"" << index << "\" "
2686 << "name=\"" << name << "\" "
2687 << "reference=\"" << p << "\"/>" << std::endl;
2688
2689 index++;
2690 }
2691 file << " </" << labels << ">" << std::endl;
2692 }
2693
2694 file << " </topology>" << std::endl;
2695 file << " </geometry>" << std::endl;
2696 file << " <groups count=\""
2697 << groups[0].size() + groups[1].size() + groups[2].size() + groups[3].size() << "\">"
2698 << std::endl;
2699
2700 int groupNumber = 1;
2701
2702 m_reporter->Report( wxT( "Pad definitions:" ), RPT_SEVERITY_DEBUG );
2703 m_reporter->Report( wxT( "Number\tName\tArea (m^2)" ), RPT_SEVERITY_DEBUG );
2704
2705 for( int dim = 0; dim <= 3; dim++ )
2706 {
2707 std::string label = c_dimLabel[dim];
2708
2709 for( auto g : groups[dim] )
2710 {
2711 //std::string name = model->getPhysicalName( dim, g.first );
2712 wxString name = g.first;
2713
2714 if( name.empty() )
2715 { // create same unique name as for MED export
2716 std::ostringstream gs;
2717 gs << "G_" << dim << "D_" << g.first;
2718 name = gs.str();
2719 }
2720 file << " <group name=\"" << name << "\" dimension=\"" << label;
2721//#if 1
2722// // Gmsh XAO extension: also save the physical tag, so that XAO can be used
2723// // to serialize OCC geometries, ready to be used by GetDP, GmshFEM & co
2724// file << "\" tag=\"" << g.first;
2725//#endif
2726 file << "\" count=\"" << g.second.size() << "\">" << std::endl;
2727
2728 for( auto index : g.second )
2729 file << " <element index=\"" << index << "\"/>" << std::endl;
2730
2731 file << " </group>" << std::endl;
2732
2733 m_reporter->Report( wxString::Format( "%d\t%s\t%g",
2734 groupNumber,
2735 name,
2736 groupAreas[name] ),
2738
2739 groupNumber++;
2740 }
2741 }
2742
2743 m_reporter->Report( wxT( "" ), RPT_SEVERITY_DEBUG );
2744
2745 file << " </groups>" << std::endl;
2746 file << " <fields count=\"0\"/>" << std::endl;
2747 file << "</XAO>" << std::endl;
2748
2749 return true;
2750}
2751
2752
2753bool STEP_PCB_MODEL::getModelLabel( const std::string& aFileNameUTF8, const VECTOR3D& aScale, TDF_Label& aLabel,
2754 bool aSubstituteModels, wxString* aErrorMessage )
2755{
2756 std::string model_key = aFileNameUTF8 + "_" + std::to_string( aScale.x )
2757 + "_" + std::to_string( aScale.y ) + "_" + std::to_string( aScale.z );
2758
2759 MODEL_MAP::const_iterator mm = m_models.find( model_key );
2760
2761 if( mm != m_models.end() )
2762 {
2763 aLabel = mm->second;
2764 return true;
2765 }
2766
2767 aLabel.Nullify();
2768
2769 Handle( TDocStd_Document ) doc;
2770 m_app->NewDocument( "MDTV-XCAF", doc );
2771
2772 wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
2773 MODEL3D_FORMAT_TYPE modelFmt = fileType( aFileNameUTF8.c_str() );
2774
2775 switch( modelFmt )
2776 {
2777 case FMT_IGES:
2778 if( !readIGES( doc, aFileNameUTF8.c_str() ) )
2779 {
2780 m_reporter->Report( wxString::Format( wxT( "readIGES() failed on filename '%s'." ),
2781 fileName ),
2783 return false;
2784 }
2785 break;
2786
2787 case FMT_STEP:
2788 if( !readSTEP( doc, aFileNameUTF8.c_str() ) )
2789 {
2790 m_reporter->Report( wxString::Format( wxT( "readSTEP() failed on filename '%s'." ),
2791 fileName ),
2793 return false;
2794 }
2795 break;
2796
2797 case FMT_STEPZ:
2798 {
2799 // To export a compressed step file (.stpz or .stp.gz file), the best way is to
2800 // decaompress it in a temporaty file and load this temporary file
2801 wxFFileInputStream ifile( fileName );
2802 wxFileName outFile( fileName );
2803
2804 outFile.SetPath( wxStandardPaths::Get().GetTempDir() );
2805 outFile.SetExt( wxT( "step" ) );
2806 wxFileOffset size = ifile.GetLength();
2807
2808 if( size == wxInvalidOffset )
2809 {
2810 m_reporter->Report( wxString::Format( wxT( "getModelLabel() failed on filename '%s'." ),
2811 fileName ),
2813 return false;
2814 }
2815
2816 {
2817 bool success = false;
2818 wxFFileOutputStream ofile( outFile.GetFullPath() );
2819
2820 if( !ofile.IsOk() )
2821 return false;
2822
2823 char* buffer = new char[size];
2824
2825 ifile.Read( buffer, size );
2826 std::string expanded;
2827
2828 try
2829 {
2830 expanded = gzip::decompress( buffer, size );
2831 success = true;
2832 }
2833 catch( ... )
2834 {
2835 m_reporter->Report( wxString::Format( wxT( "failed to decompress '%s'." ),
2836 fileName ),
2838 }
2839
2840 if( expanded.empty() )
2841 {
2842 ifile.Reset();
2843 ifile.SeekI( 0 );
2844 wxZipInputStream izipfile( ifile );
2845 std::unique_ptr<wxZipEntry> zip_file( izipfile.GetNextEntry() );
2846
2847 if( zip_file && !zip_file->IsDir() && izipfile.CanRead() )
2848 {
2849 izipfile.Read( ofile );
2850 success = true;
2851 }
2852 }
2853 else
2854 {
2855 ofile.Write( expanded.data(), expanded.size() );
2856 }
2857
2858 delete[] buffer;
2859 ofile.Close();
2860
2861 if( success )
2862 {
2863 std::string altFileNameUTF8 = TO_UTF8( outFile.GetFullPath() );
2864 success = getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false );
2865 }
2866
2867 return success;
2868 }
2869
2870 break;
2871 }
2872
2873 case FMT_WRL:
2874 case FMT_WRZ:
2875 /* WRL files are preferred for internal rendering, due to superior material properties, etc.
2876 * However they are not suitable for MCAD export.
2877 *
2878 * If a .wrl file is specified, attempt to locate a replacement file for it.
2879 *
2880 * If a valid replacement file is found, the label for THAT file will be associated with
2881 * the .wrl file
2882 */
2883 if( aSubstituteModels )
2884 {
2885 wxFileName wrlName( fileName );
2886
2887 wxString basePath = wrlName.GetPath();
2888 wxString baseName = wrlName.GetName();
2889
2890 // List of alternate files to look for
2891 // Given in order of preference
2892 // (Break if match is found)
2893 wxArrayString alts;
2894
2895 // Step files
2896 alts.Add( wxT( "stp" ) );
2897 alts.Add( wxT( "step" ) );
2898 alts.Add( wxT( "STP" ) );
2899 alts.Add( wxT( "STEP" ) );
2900 alts.Add( wxT( "Stp" ) );
2901 alts.Add( wxT( "Step" ) );
2902 alts.Add( wxT( "stpz" ) );
2903 alts.Add( wxT( "stpZ" ) );
2904 alts.Add( wxT( "STPZ" ) );
2905 alts.Add( wxT( "step.gz" ) );
2906 alts.Add( wxT( "stp.gz" ) );
2907
2908 // IGES files
2909 alts.Add( wxT( "iges" ) );
2910 alts.Add( wxT( "IGES" ) );
2911 alts.Add( wxT( "igs" ) );
2912 alts.Add( wxT( "IGS" ) );
2913
2914 //TODO - Other alternative formats?
2915
2916 for( const auto& alt : alts )
2917 {
2918 wxFileName altFile( basePath, baseName + wxT( "." ) + alt );
2919
2920 if( altFile.IsOk() && altFile.FileExists() )
2921 {
2922 std::string altFileNameUTF8 = TO_UTF8( altFile.GetFullPath() );
2923
2924 // When substituting a STEP/IGS file for VRML, do not apply the VRML scaling
2925 // to the new STEP model. This process of auto-substitution is janky as all
2926 // heck so let's not mix up un-displayed scale factors with potentially
2927 // mis-matched files. And hope that the user doesn't have multiples files
2928 // named "model.wrl" and "model.stp" referring to different parts.
2929 // TODO: Fix model handling in v7. Default models should only be STP.
2930 // Have option to override this in DISPLAY.
2931 if( getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false ) )
2932 {
2933 return true;
2934 }
2935 }
2936 }
2937
2938 // VRML models only work when exporting to glTF
2939 // Also OCCT < 7.9.0 fail to load most VRML 2.0 models because of Switch nodes
2941 {
2942 if( readVRML( doc, aFileNameUTF8.c_str() ) )
2943 {
2944 Handle( XCAFDoc_ShapeTool ) shapeTool =
2945 XCAFDoc_DocumentTool::ShapeTool( doc->Main() );
2946
2947 prefixNames( shapeTool->Label(),
2948 TCollection_ExtendedString( baseName.c_str().AsChar() ) );
2949 }
2950 else
2951 {
2952 m_reporter->Report( wxString::Format( wxT( "readVRML() failed on filename '%s'." ),
2953 fileName ),
2955 return false;
2956 }
2957 }
2958 }
2959 else // Substitution is not allowed
2960 {
2961 if( aErrorMessage )
2962 aErrorMessage->Printf( wxT( "Cannot load any VRML model for this export." ) );
2963
2964 return false;
2965 }
2966
2967 break;
2968
2969 // TODO: implement IDF and EMN converters
2970
2971 default:
2972 m_reporter->Report( wxString::Format( _( "Cannot identify actual file type for '%s'." ),
2973 fileName ),
2975 return false;
2976 }
2977
2978 aLabel = transferModel( doc, m_doc, aScale );
2979
2980 if( aLabel.IsNull() )
2981 {
2982 m_reporter->Report( wxString::Format( _( "Could not transfer model data from file '%s'." ),
2983 fileName ),
2985 return false;
2986 }
2987
2988 // attach the PART NAME ( base filename: note that in principle
2989 // different models may have the same base filename )
2990 wxFileName afile( fileName );
2991 std::string pname( afile.GetName().ToUTF8() );
2992 TCollection_ExtendedString partname( pname.c_str() );
2993 TDataStd_Name::Set( aLabel, partname );
2994
2995 m_models.insert( MODEL_DATUM( model_key, aLabel ) );
2996 ++m_components;
2997 return true;
2998}
2999
3000
3001bool STEP_PCB_MODEL::getModelLocation( bool aBottom, const VECTOR2D& aPosition, double aRotation,
3002 const VECTOR3D& aOffset, const VECTOR3D& aOrientation,
3003 TopLoc_Location& aLocation )
3004{
3005 // Order of operations:
3006 // a. aOrientation is applied -Z*-Y*-X
3007 // b. aOffset is applied
3008 // Top ? add thickness to the Z offset
3009 // c. Bottom ? Rotate on X axis (in contrast to most ECAD which mirror on Y),
3010 // then rotate on +Z
3011 // Top ? rotate on -Z
3012 // d. aPosition is applied
3013 //
3014 // Note: Y axis is inverted in KiCad
3015
3016 gp_Trsf lPos;
3017 lPos.SetTranslation( gp_Vec( aPosition.x, -aPosition.y, 0.0 ) );
3018
3019 // Offset board thickness
3020 VECTOR3D offset( aOffset );
3021 offset.z += BOARD_OFFSET;
3022
3023 double boardThickness;
3024 double boardZPos;
3025 getBoardBodyZPlacement( boardZPos, boardThickness );
3026 double top = std::max( boardZPos, boardZPos + boardThickness );
3027 double bottom = std::min( boardZPos, boardZPos + boardThickness );
3028
3029 // 3D step models are placed on the top of copper layers.
3030 // This is true for SMD shapes, and perhaps not always true for TH shapes,
3031 // but we use this Z position for any 3D shape.
3032 double f_pos, f_thickness;
3033 getLayerZPlacement( F_Cu, f_pos, f_thickness );
3034 top += f_thickness;
3035 getLayerZPlacement( B_Cu, f_pos, f_thickness );
3036 bottom += f_thickness; // f_thickness is < 0 for B_Cu layer
3037
3038 gp_Trsf lRot;
3039
3040 if( aBottom )
3041 {
3042 offset.z -= bottom;
3043 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
3044 lPos.Multiply( lRot );
3045 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), M_PI );
3046 lPos.Multiply( lRot );
3047 }
3048 else
3049 {
3050 offset.z += top;
3051 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
3052 lPos.Multiply( lRot );
3053 }
3054
3055 gp_Trsf lOff;
3056 lOff.SetTranslation( gp_Vec( offset.x, offset.y, offset.z ) );
3057 lPos.Multiply( lOff );
3058
3059 gp_Trsf lOrient;
3060 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), -aOrientation.z );
3061 lPos.Multiply( lOrient );
3062 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 1.0, 0.0 ) ), -aOrientation.y );
3063 lPos.Multiply( lOrient );
3064 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), -aOrientation.x );
3065 lPos.Multiply( lOrient );
3066
3067 aLocation = TopLoc_Location( lPos );
3068 return true;
3069}
3070
3071
3072bool STEP_PCB_MODEL::readIGES( Handle( TDocStd_Document )& doc, const char* fname )
3073{
3074 IGESControl_Controller::Init();
3075 IGESCAFControl_Reader reader;
3076 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
3077
3078 if( stat != IFSelect_RetDone )
3079 return false;
3080
3081 // Enable user-defined shape precision
3082 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
3083 return false;
3084
3085 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
3086 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
3087 return false;
3088
3089 // set other translation options
3090 reader.SetColorMode( true ); // use model colors
3091 reader.SetNameMode( false ); // don't use IGES label names
3092 reader.SetLayerMode( false ); // ignore LAYER data
3093
3094 if( !reader.Transfer( doc ) )
3095 {
3096 if( doc->CanClose() == CDM_CCS_OK )
3097 doc->Close();
3098
3099 return false;
3100 }
3101
3102 // are there any shapes to translate?
3103 if( reader.NbShapes() < 1 )
3104 {
3105 if( doc->CanClose() == CDM_CCS_OK )
3106 doc->Close();
3107
3108 return false;
3109 }
3110
3111 return true;
3112}
3113
3114
3115bool STEP_PCB_MODEL::readSTEP( Handle( TDocStd_Document )& doc, const char* fname )
3116{
3117 STEPCAFControl_Reader reader;
3118 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
3119
3120 if( stat != IFSelect_RetDone )
3121 return false;
3122
3123 // Enable user-defined shape precision
3124 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
3125 return false;
3126
3127 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
3128 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
3129 return false;
3130
3131 // set other translation options
3132 reader.SetColorMode( true ); // use model colors
3133 reader.SetNameMode( true ); // use label names
3134 reader.SetLayerMode( false ); // ignore LAYER data
3135
3136 if( !reader.Transfer( doc ) )
3137 {
3138 if( doc->CanClose() == CDM_CCS_OK )
3139 doc->Close();
3140
3141 return false;
3142 }
3143
3144 // are there any shapes to translate?
3145 if( reader.NbRootsForTransfer() < 1 )
3146 {
3147 if( doc->CanClose() == CDM_CCS_OK )
3148 doc->Close();
3149
3150 return false;
3151 }
3152
3153 return true;
3154}
3155
3156
3157bool STEP_PCB_MODEL::readVRML( Handle( TDocStd_Document ) & doc, const char* fname )
3158{
3159#if OCC_VERSION_HEX >= 0x070700
3160 VrmlAPI_CafReader reader;
3161 RWMesh_CoordinateSystemConverter conv;
3162 conv.SetInputLengthUnit( 2.54 );
3163 reader.SetCoordinateSystemConverter( conv );
3164 reader.SetDocument( doc );
3165
3166 if( !reader.Perform( TCollection_AsciiString( fname ), Message_ProgressRange() ) )
3167 return false;
3168
3169 return true;
3170#else
3171 return false;
3172#endif
3173}
3174
3175
3176TDF_Label STEP_PCB_MODEL::transferModel( Handle( TDocStd_Document ) & source,
3177 Handle( TDocStd_Document ) & dest, const VECTOR3D& aScale )
3178{
3179 // transfer data from Source into a top level component of Dest
3180 // s_assy = shape tool for the source
3181 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( source->Main() );
3182
3183 // retrieve all free shapes within the assembly
3184 TDF_LabelSequence frshapes;
3185 s_assy->GetFreeShapes( frshapes );
3186
3187 // d_assy = shape tool for the destination
3188 Handle( XCAFDoc_ShapeTool ) d_assy = XCAFDoc_DocumentTool::ShapeTool( dest->Main() );
3189
3190 // create a new shape within the destination and set the assembly tool to point to it
3191 TDF_Label d_targetLabel = d_assy->NewShape();
3192
3193 if( frshapes.Size() == 1 )
3194 {
3195 TDocStd_XLinkTool link;
3196 link.Copy( d_targetLabel, frshapes.First() );
3197 }
3198 else
3199 {
3200 // Rare case
3201 for( TDF_Label& s_shapeLabel : frshapes )
3202 {
3203 TDF_Label d_component = d_assy->NewShape();
3204
3205 TDocStd_XLinkTool link;
3206 link.Copy( d_component, s_shapeLabel );
3207
3208 d_assy->AddComponent( d_targetLabel, d_component, TopLoc_Location() );
3209 }
3210 }
3211
3212 if( aScale.x != 1.0 || aScale.y != 1.0 || aScale.z != 1.0 )
3213 rescaleShapes( d_targetLabel, gp_XYZ( aScale.x, aScale.y, aScale.z ) );
3214
3215 return d_targetLabel;
3216}
3217
3218
3219bool STEP_PCB_MODEL::performMeshing( Handle( XCAFDoc_ShapeTool ) & aShapeTool )
3220{
3221 TDF_LabelSequence freeShapes;
3222 aShapeTool->GetFreeShapes( freeShapes );
3223
3224 m_reporter->Report( wxT( "Meshing model" ), RPT_SEVERITY_DEBUG );
3225
3226 // GLTF is a mesh format, we have to trigger opencascade to mesh the shapes we composited into the asesmbly
3227 // To mesh models, lets just grab the free shape root and execute on them
3228 for( Standard_Integer i = 1; i <= freeShapes.Length(); ++i )
3229 {
3230 TDF_Label label = freeShapes.Value( i );
3231 TopoDS_Shape shape;
3232 aShapeTool->GetShape( label, shape );
3233
3234 // These deflection values basically affect the accuracy of the mesh generated, a tighter
3235 // deflection will result in larger meshes
3236 // We could make this a tunable parameter, but for now fix it
3237 const Standard_Real linearDeflection = 0.14;
3238 const Standard_Real angularDeflection = DEG2RAD( 30.0 );
3239 BRepMesh_IncrementalMesh mesh( shape, linearDeflection, Standard_False, angularDeflection,
3240 Standard_True );
3241 }
3242
3243 return true;
3244}
3245
3246
3247bool STEP_PCB_MODEL::WriteGLTF( const wxString& aFileName )
3248{
3249 /*if( !isBoardOutlineValid() )
3250 {
3251 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
3252 aFileName ),
3253 RPT_SEVERITY_ERROR );
3254 return false;
3255 }*/
3256
3258
3259 performMeshing( m_assy );
3260
3261 wxFileName fn( aFileName );
3262
3263 const char* tmpGltfname = "$tempfile$.glb";
3264 RWGltf_CafWriter cafWriter( tmpGltfname, true );
3265
3266 cafWriter.SetTransformationFormat( RWGltf_WriterTrsfFormat_Compact );
3267 cafWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit( 0.001 );
3268 cafWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(
3269 RWMesh_CoordinateSystem_Zup );
3270#if OCC_VERSION_HEX >= 0x070700
3271 cafWriter.SetParallel( true );
3272#endif
3273 TColStd_IndexedDataMapOfStringString metadata;
3274
3275 metadata.Add( TCollection_AsciiString( "pcb_name" ),
3276 TCollection_ExtendedString( fn.GetName().wc_str() ) );
3277 metadata.Add( TCollection_AsciiString( "source_pcb_file" ),
3278 TCollection_ExtendedString( fn.GetFullName().wc_str() ) );
3279 metadata.Add( TCollection_AsciiString( "generator" ),
3280 TCollection_AsciiString( wxString::Format( wxS( "KiCad %s" ), GetSemanticVersion() ).ToAscii() ) );
3281 metadata.Add( TCollection_AsciiString( "generated_at" ),
3282 TCollection_AsciiString( GetISO8601CurrentDateTime().ToAscii() ) );
3283
3284 bool success = true;
3285
3286 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
3287 wxString currCWD = wxGetCwd();
3288 wxString workCWD = fn.GetPath();
3289
3290 if( !workCWD.IsEmpty() )
3291 wxSetWorkingDirectory( workCWD );
3292
3293 success = cafWriter.Perform( m_doc, metadata, Message_ProgressRange() );
3294
3295 if( success )
3296 {
3297 // Preserve the permissions of the current file
3298 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpGltfname );
3299
3300 if( !wxRenameFile( tmpGltfname, fn.GetFullName(), true ) )
3301 {
3302 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'." ),
3303 tmpGltfname,
3304 fn.GetFullName() ),
3306 success = false;
3307 }
3308 }
3309
3310 wxSetWorkingDirectory( currCWD );
3311
3312 return success;
3313}
3314
3315
3316bool STEP_PCB_MODEL::WritePLY( const wxString& aFileName )
3317{
3318#if OCC_VERSION_HEX < 0x070700
3319 m_reporter->Report( wxT( "PLY export is not supported before OCCT 7.7.0" ), RPT_SEVERITY_ERROR );
3320 return false;
3321#else
3322
3323 if( !isBoardOutlineValid() )
3324 {
3325 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
3326 aFileName ),
3328 return false;
3329 }
3330
3332
3333 performMeshing( m_assy );
3334
3335 wxFileName fn( aFileName );
3336
3337 const char* tmpFname = "$tempfile$.ply";
3338 RWPly_CafWriter cafWriter( tmpFname );
3339
3340 cafWriter.SetFaceId( true ); // TODO: configurable SetPartId/SetFaceId
3341 cafWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit( 0.001 );
3342 cafWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem( RWMesh_CoordinateSystem_Zup );
3343
3344 TColStd_IndexedDataMapOfStringString metadata;
3345
3346 metadata.Add( TCollection_AsciiString( "pcb_name" ),
3347 TCollection_ExtendedString( fn.GetName().wc_str() ) );
3348 metadata.Add( TCollection_AsciiString( "source_pcb_file" ),
3349 TCollection_ExtendedString( fn.GetFullName().wc_str() ) );
3350 metadata.Add( TCollection_AsciiString( "generator" ),
3351 TCollection_AsciiString( wxString::Format( wxS( "KiCad %s" ),
3352 GetSemanticVersion() ).ToAscii() ) );
3353 metadata.Add( TCollection_AsciiString( "generated_at" ),
3354 TCollection_AsciiString( GetISO8601CurrentDateTime().ToAscii() ) );
3355
3356 bool success = true;
3357
3358 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
3359 wxString currCWD = wxGetCwd();
3360 wxString workCWD = fn.GetPath();
3361
3362 if( !workCWD.IsEmpty() )
3363 wxSetWorkingDirectory( workCWD );
3364
3365 success = cafWriter.Perform( m_doc, metadata, Message_ProgressRange() );
3366
3367 if( success )
3368 {
3369 // Preserve the permissions of the current file
3370 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname );
3371
3372 if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
3373 {
3374 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'." ),
3375 tmpFname,
3376 fn.GetFullName() ),
3378 success = false;
3379 }
3380 }
3381
3382 wxSetWorkingDirectory( currCWD );
3383
3384 return success;
3385#endif
3386}
3387
3388
3389bool STEP_PCB_MODEL::WriteSTL( const wxString& aFileName )
3390{
3391 if( !isBoardOutlineValid() )
3392 {
3393 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
3394 aFileName ),
3396 return false;
3397 }
3398
3400
3401 performMeshing( m_assy );
3402
3403 wxFileName fn( aFileName );
3404
3405 const char* tmpFname = "$tempfile$.stl";
3406
3407 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
3408 wxString currCWD = wxGetCwd();
3409 wxString workCWD = fn.GetPath();
3410
3411 if( !workCWD.IsEmpty() )
3412 wxSetWorkingDirectory( workCWD );
3413
3414 bool success = StlAPI_Writer().Write( getOneShape( m_assy ), tmpFname );
3415
3416 if( success )
3417 {
3418 // Preserve the permissions of the current file
3419 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname );
3420
3421 if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
3422 {
3423 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'." ),
3424 tmpFname,
3425 fn.GetFullName() ),
3427 success = false;
3428 }
3429 }
3430
3431 wxSetWorkingDirectory( currCWD );
3432
3433 return success;
3434}
3435
3436
3437
3438bool STEP_PCB_MODEL::WriteU3D( const wxString& aFileName )
3439{
3440 if( !isBoardOutlineValid() )
3441 {
3442 m_reporter->Report(
3443 wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ), aFileName ),
3445 return false;
3446 }
3447
3449
3450 performMeshing( m_assy );
3451
3452 wxFileName fn( aFileName );
3453
3454 const char* tmpFname = "$tempfile$.u3d";
3455
3456 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
3457 wxString currCWD = wxGetCwd();
3458 wxString workCWD = fn.GetPath();
3459
3460 if( !workCWD.IsEmpty() )
3461 wxSetWorkingDirectory( workCWD );
3462
3463 U3D::WRITER writer( tmpFname );
3464 bool success = writer.Perform( m_doc );
3465 if( success )
3466 {
3467 // Preserve the permissions of the current file
3468 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname );
3469
3470 if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
3471 {
3472 m_reporter->Report( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ), tmpFname,
3473 fn.GetFullName() ),
3475 success = false;
3476 }
3477 }
3478
3479 wxSetWorkingDirectory( currCWD );
3480
3481 return success;
3482}
3483
3484
3485bool STEP_PCB_MODEL::WritePDF( const wxString& aFileName )
3486{
3487 if( !isBoardOutlineValid() )
3488 {
3489 m_reporter->Report(
3490 wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ), aFileName ),
3492 return false;
3493 }
3494
3496
3497 performMeshing( m_assy );
3498
3499 wxFileName fn( aFileName );
3500
3501 wxFileName u3dTmpfn = wxFileName::CreateTempFileName( "" );
3502 wxFileName pdfTmpfn = wxFileName::CreateTempFileName( "" );
3503
3504 U3D::WRITER writer( u3dTmpfn.GetFullPath().ToStdString() );
3505 bool success = writer.Perform( m_doc );
3506
3507 // PDF test
3508 std::unique_ptr<PDF_PLOTTER> plotter = std::make_unique<PDF_PLOTTER>();
3509
3510 plotter->SetColorMode( true );
3511 plotter->Set3DExport( true );
3512 plotter->SetCreator( wxT( "Mark's awesome 3d exporter" ) );
3513 KIGFX::PCB_RENDER_SETTINGS renderSettings;
3514 plotter->SetRenderSettings( &renderSettings );
3515
3516 if( !plotter->OpenFile( pdfTmpfn.GetFullPath() ) )
3517 {
3518 m_reporter->Report( wxString::Format( wxT( "Cannot open temporary file '%s'.\n" ), pdfTmpfn.GetFullPath() ),
3520 success = false;
3521 }
3522 else
3523 {
3524 plotter->StartPlot( "1", "3D Model" );
3525 double fov_degrees = 16.5f;
3526
3527 // kind of an arbitrary distance determination
3528 float distance = sqrt( writer.GetMeshBoundingBox().SquareExtent() ) * 3;
3529
3530 std::vector<PDF_3D_VIEW> views;
3531
3532 VECTOR3D camTarget = writer.GetCenter();
3533
3534
3535 std::vector<float> c2wMatrix =
3536 PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 180.0f, -75.0f, 25.0f );
3537
3538 views.emplace_back( PDF_3D_VIEW{
3539 .m_name = "Default",
3540 .m_cameraMatrix = c2wMatrix,
3541 .m_cameraCenter = (float) distance,
3542 .m_fov = (float) fov_degrees,
3543 } );
3544
3545
3546
3547 c2wMatrix = PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 180.0, 0.0f, 0.0f );
3548
3549 views.emplace_back( PDF_3D_VIEW{
3550 .m_name = "Top",
3551 .m_cameraMatrix = c2wMatrix,
3552 .m_cameraCenter = (float) distance,
3553 .m_fov = (float) fov_degrees,
3554 } );
3555
3556
3557
3558 c2wMatrix = PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 0.0, 0.0f, 0.0f );
3559
3560 views.emplace_back( PDF_3D_VIEW{
3561 .m_name = "Bottom",
3562 .m_cameraMatrix = c2wMatrix,
3563 .m_cameraCenter = (float) distance,
3564 .m_fov = (float) fov_degrees,
3565 } );
3566
3567
3568
3569 c2wMatrix = PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 90.0f, -90.0f, 90.0f );
3570
3571 views.emplace_back( PDF_3D_VIEW{
3572 .m_name = "Front",
3573 .m_cameraMatrix = c2wMatrix,
3574 .m_cameraCenter = (float) distance,
3575 .m_fov = (float) fov_degrees,
3576 } );
3577
3578 plotter->Plot3DModel( u3dTmpfn.GetFullPath(), views );
3579 plotter->EndPlot();
3580 }
3581
3582 if( success )
3583 {
3584 // Preserve the permissions of the current file
3585 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), pdfTmpfn.GetFullPath() );
3586
3587 if( !wxRenameFile( pdfTmpfn.GetFullPath(), fn.GetFullPath(), true ) )
3588 {
3589 m_reporter->Report( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
3590 pdfTmpfn.GetFullPath(), fn.GetFullPath() ),
3592 success = false;
3593 }
3594 }
3595
3596 wxRemoveFile( u3dTmpfn.GetFullPath() );
3597
3598 return success;
3599}
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:82
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:779
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:802
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:676
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