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