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, bool aCutCopper, bool aCutBody )
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( aCutCopper )
932 {
933 if( MakeShapeAsThickSegment( copperHole, aShape.GetSeg().A, aShape.GetSeg().B, copperDrill,
934 holeZsize, bottom - margin, aOrigin ) )
935 {
936 m_copperCutouts.push_back( copperHole );
937 }
938 else
939 {
940 return false;
941 }
942 }
943
944 if( aCutBody )
945 {
946 if( MakeShapeAsThickSegment( boardHole, aShape.GetSeg().A, aShape.GetSeg().B, boardDrill,
947 holeZsize, bottom - margin, aOrigin ) )
948 {
949 m_boardCutouts.push_back( boardHole );
950 }
951 else
952 {
953 return false;
954 }
955 }
956
957 return true;
958}
959
960
962 PCB_LAYER_ID aLayerBot, bool aVia, const VECTOR2D& aOrigin )
963{
964 double f_pos, f_thickness;
965 double b_pos, b_thickness;
966 getLayerZPlacement( aLayerTop, f_pos, f_thickness );
967 getLayerZPlacement( aLayerBot, b_pos, b_thickness );
968 double top = std::max( f_pos, f_pos + f_thickness );
969 double bottom = std::min( b_pos, b_pos + b_thickness );
970
971 TopoDS_Shape plating;
972
973 if( !MakeShapeAsThickSegment( plating, aShape.GetSeg().A, aShape.GetSeg().B, aShape.GetWidth(),
974 ( top - bottom ), bottom, aOrigin ) )
975 {
976 return false;
977 }
978
979 if( aVia )
980 m_board_copper_vias.push_back( plating );
981 else
982 m_board_copper_pads.push_back( plating );
983
984 return true;
985}
986
987
988void STEP_PCB_MODEL::getLayerZPlacement( const PCB_LAYER_ID aLayer, double& aZPos,
989 double& aThickness )
990{
991 // Offsets above copper in mm
992 static const double c_silkscreenAboveCopper = 0.04;
993 static const double c_soldermaskAboveCopper = 0.015;
994
995 if( IsCopperLayer( aLayer ) )
996 {
997 getCopperLayerZPlacement( aLayer, aZPos, aThickness );
998 }
999 else if( IsFrontLayer( aLayer ) )
1000 {
1001 double f_pos, f_thickness;
1002 getCopperLayerZPlacement( F_Cu, f_pos, f_thickness );
1003 double top = std::max( f_pos, f_pos + f_thickness );
1004
1005 if( aLayer == F_SilkS )
1006 aZPos = top + c_silkscreenAboveCopper;
1007 else
1008 aZPos = top + c_soldermaskAboveCopper;
1009
1010 aThickness = 0.0; // Normal points up
1011 }
1012 else if( IsBackLayer( aLayer ) )
1013 {
1014 double b_pos, b_thickness;
1015 getCopperLayerZPlacement( B_Cu, b_pos, b_thickness );
1016 double bottom = std::min( b_pos, b_pos + b_thickness );
1017
1018 if( aLayer == B_SilkS )
1019 aZPos = bottom - c_silkscreenAboveCopper;
1020 else
1021 aZPos = bottom - c_soldermaskAboveCopper;
1022
1023 aThickness = -0.0; // Normal points down
1024 }
1025}
1026
1027
1029 double& aThickness )
1030{
1031 int z = 0;
1032 int thickness = 0;
1033 bool wasPrepreg = false;
1034
1035 const std::vector<BOARD_STACKUP_ITEM*>& materials = m_stackup.GetList();
1036
1037 // Iterate from bottom to top
1038 for( auto it = materials.rbegin(); it != materials.rend(); ++it )
1039 {
1040 const BOARD_STACKUP_ITEM* item = *it;
1041
1042 if( item->GetType() == BS_ITEM_TYPE_COPPER )
1043 {
1044 if( aLayer == B_Cu )
1045 {
1046 // This is the first encountered layer
1047 thickness = -item->GetThickness();
1048 break;
1049 }
1050
1051 // Inner copper position is usually inside prepreg
1052 if( wasPrepreg && item->GetBrdLayerId() != F_Cu )
1053 {
1054 z += item->GetThickness();
1055 thickness = -item->GetThickness();
1056 }
1057 else
1058 {
1059 thickness = item->GetThickness();
1060 }
1061
1062 if( item->GetBrdLayerId() == aLayer )
1063 break;
1064
1065 if( !wasPrepreg && item->GetBrdLayerId() != B_Cu )
1066 z += item->GetThickness();
1067 }
1068 else if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1069 {
1070 wasPrepreg = ( item->GetTypeName() == KEY_PREPREG );
1071
1072 // Dielectric can have sub-layers. Layer 0 is the main layer
1073 // Not frequent, but possible
1074 thickness = 0;
1075 for( int idx = 0; idx < item->GetSublayersCount(); idx++ )
1076 thickness += item->GetThickness( idx );
1077
1078 z += thickness;
1079 }
1080 }
1081
1082 aZPos = pcbIUScale.IUTomm( z );
1083 aThickness = pcbIUScale.IUTomm( thickness );
1084}
1085
1086
1087void STEP_PCB_MODEL::getBoardBodyZPlacement( double& aZPos, double& aThickness )
1088{
1089 double f_pos, f_thickness;
1090 double b_pos, b_thickness;
1091 getLayerZPlacement( F_Cu, f_pos, f_thickness );
1092 getLayerZPlacement( B_Cu, b_pos, b_thickness );
1093 double top = std::min( f_pos, f_pos + f_thickness );
1094 double bottom = std::max( b_pos, b_pos + b_thickness );
1095
1096 aThickness = ( top - bottom );
1097 aZPos = bottom;
1098
1099 wxASSERT( aZPos == 0.0 );
1100}
1101
1102
1104 const VECTOR2D& aOrigin )
1105{
1106 bool success = true;
1107
1108 if( aPolyShapes->IsEmpty() )
1109 return true;
1110
1111 if( !m_enabledLayers.Contains( aLayer ) )
1112 return true;
1113
1114 double z_pos, thickness;
1115 getLayerZPlacement( aLayer, z_pos, thickness );
1116
1117 std::vector<TopoDS_Shape>& targetVec = IsCopperLayer( aLayer ) ? m_board_copper
1118 : aLayer == F_SilkS || aLayer == B_SilkS
1121
1122 if( !MakeShapes( targetVec, *aPolyShapes, m_simplifyShapes, thickness, z_pos, aOrigin ) )
1123 {
1125 wxString::Format( wxT( "Could not add shape (%d points) to copper layer on %s.\n" ),
1126 aPolyShapes->FullPointCount(), LayerName( aLayer ) ) );
1127
1128 success = false;
1129 }
1130
1131 return success;
1132}
1133
1134
1135bool STEP_PCB_MODEL::AddComponent( const std::string& aFileNameUTF8, const std::string& aRefDes,
1136 bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset,
1137 VECTOR3D aOrientation, VECTOR3D aScale, bool aSubstituteModels )
1138{
1139 if( aFileNameUTF8.empty() )
1140 {
1141 ReportMessage( wxString::Format( wxT( "No model defined for component %s.\n" ), aRefDes ) );
1142 return false;
1143 }
1144
1145 wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
1146 ReportMessage( wxString::Format( wxT( "Adding component %s.\n" ), aRefDes ) );
1147
1148 // first retrieve a label
1149 TDF_Label lmodel;
1150 wxString errorMessage;
1151
1152 if( !getModelLabel( aFileNameUTF8, aScale, lmodel, aSubstituteModels, &errorMessage ) )
1153 {
1154 if( errorMessage.IsEmpty() )
1155 ReportMessage( wxString::Format( wxT( "No model for filename '%s'.\n" ), fileName ) );
1156 else
1157 ReportMessage( errorMessage );
1158
1159 return false;
1160 }
1161
1162 // calculate the Location transform
1163 TopLoc_Location toploc;
1164
1165 if( !getModelLocation( aBottom, aPosition, aRotation, aOffset, aOrientation, toploc ) )
1166 {
1168 wxString::Format( wxT( "No location data for filename '%s'.\n" ), fileName ) );
1169 return false;
1170 }
1171
1172 // add the located sub-assembly
1173 TDF_Label llabel = m_assy->AddComponent( m_assy_label, lmodel, toploc );
1174
1175 if( llabel.IsNull() )
1176 {
1177 ReportMessage( wxString::Format( wxT( "Could not add component with filename '%s'.\n" ),
1178 fileName ) );
1179 return false;
1180 }
1181
1182 // attach the RefDes name
1183 TCollection_ExtendedString refdes( aRefDes.c_str() );
1184 TDataStd_Name::Set( llabel, refdes );
1185
1186 return true;
1187}
1188
1189
1191{
1192 m_enabledLayers = aLayers;
1193}
1194
1195
1197{
1198 m_fuseShapes = aValue;
1199}
1200
1201
1203{
1204 m_simplifyShapes = aValue;
1205}
1206
1207
1209{
1210 m_stackup = aStackup;
1211}
1212
1213
1214void STEP_PCB_MODEL::SetNetFilter( const wxString& aFilter )
1215{
1216 m_netFilter = aFilter;
1217}
1218
1219
1220void STEP_PCB_MODEL::SetCopperColor( double r, double g, double b )
1221{
1222 m_copperColor[0] = r;
1223 m_copperColor[1] = g;
1224 m_copperColor[2] = b;
1225}
1226
1227
1228void STEP_PCB_MODEL::SetPadColor( double r, double g, double b )
1229{
1230 m_padColor[0] = r;
1231 m_padColor[1] = g;
1232 m_padColor[2] = b;
1233}
1234
1235
1237{
1238 // Ensure a minimal value (in mm)
1239 m_mergeOCCMaxDist = aDistance;
1240}
1241
1242
1244{
1245 return m_pcb_labels.size() > 0;
1246}
1247
1248
1250 VECTOR2D aStartPoint, VECTOR2D aEndPoint,
1251 double aWidth, double aThickness,
1252 double aZposition, const VECTOR2D& aOrigin )
1253{
1254 // make a wide segment from 2 lines and 2 180 deg arcs
1255 // We need 6 points (3 per arcs)
1256 VECTOR2D coords[6];
1257
1258 // We build a horizontal segment, and after rotate it
1259 double len = ( aEndPoint - aStartPoint ).EuclideanNorm();
1260 double h_width = aWidth/2.0;
1261 // First is end point of first arc, and also start point of first line
1262 coords[0] = VECTOR2D{ 0.0, h_width };
1263
1264 // end point of first line and start point of second arc
1265 coords[1] = VECTOR2D{ len, h_width };
1266
1267 // middle point of second arc
1268 coords[2] = VECTOR2D{ len + h_width, 0.0 };
1269
1270 // start point of second line and end point of second arc
1271 coords[3] = VECTOR2D{ len, -h_width };
1272
1273 // end point of second line and start point of first arc
1274 coords[4] = VECTOR2D{ 0, -h_width };
1275
1276 // middle point of first arc
1277 coords[5] = VECTOR2D{ -h_width, 0.0 };
1278
1279 // Rotate and move to segment position
1280 EDA_ANGLE seg_angle( aEndPoint - aStartPoint );
1281
1282 for( int ii = 0; ii < 6; ii++ )
1283 {
1284 RotatePoint( coords[ii], VECTOR2D{ 0, 0 }, -seg_angle ),
1285 coords[ii] += aStartPoint;
1286 }
1287
1288
1289 // Convert to 3D points
1290 gp_Pnt coords3D[ 6 ];
1291
1292 for( int ii = 0; ii < 6; ii++ )
1293 {
1294 coords3D[ii] = gp_Pnt( pcbIUScale.IUTomm( coords[ii].x - aOrigin.x ),
1295 -pcbIUScale.IUTomm( coords[ii].y - aOrigin.y ), aZposition );
1296 }
1297
1298 // Build OpenCascade shape outlines
1299 BRepBuilderAPI_MakeWire wire;
1300 bool success = true;
1301
1302 // Short segments (distance between end points < m_mergeOCCMaxDist(in mm)) must be
1303 // skipped because OCC merge end points, and a null shape is created
1304 bool short_seg = pcbIUScale.IUTomm( len ) <= m_mergeOCCMaxDist;
1305
1306 try
1307 {
1308 TopoDS_Edge edge;
1309
1310 if( short_seg )
1311 {
1312 Handle( Geom_Circle ) circle = GC_MakeCircle( coords3D[1], // arc1 start point
1313 coords3D[2], // arc1 mid point
1314 coords3D[5] // arc2 mid point
1315 );
1316
1317 edge = BRepBuilderAPI_MakeEdge( circle );
1318 wire.Add( edge );
1319 }
1320 else
1321 {
1322 edge = BRepBuilderAPI_MakeEdge( coords3D[0], coords3D[1] );
1323 wire.Add( edge );
1324
1325 Handle( Geom_TrimmedCurve ) arcOfCircle =
1326 GC_MakeArcOfCircle( coords3D[1], // start point
1327 coords3D[2], // mid point
1328 coords3D[3] // end point
1329 );
1330 edge = BRepBuilderAPI_MakeEdge( arcOfCircle );
1331 wire.Add( edge );
1332
1333 edge = BRepBuilderAPI_MakeEdge( coords3D[3], coords3D[4] );
1334 wire.Add( edge );
1335
1336 Handle( Geom_TrimmedCurve ) arcOfCircle2 =
1337 GC_MakeArcOfCircle( coords3D[4], // start point
1338 coords3D[5], // mid point
1339 coords3D[0] // end point
1340 );
1341 edge = BRepBuilderAPI_MakeEdge( arcOfCircle2 );
1342 wire.Add( edge );
1343 }
1344 }
1345 catch( const Standard_Failure& e )
1346 {
1347 ReportMessage( wxString::Format( wxT( "build shape segment: OCC exception: %s\n" ),
1348 e.GetMessageString() ) );
1349 return false;
1350 }
1351
1352 BRepBuilderAPI_MakeFace face;
1353
1354 try
1355 {
1356 gp_Pln plane( coords3D[0], gp::DZ() );
1357 face = BRepBuilderAPI_MakeFace( plane, wire );
1358 }
1359 catch( const Standard_Failure& e )
1360 {
1361 ReportMessage( wxString::Format( wxT( "MakeShapeThickSegment: OCC exception: %s\n" ),
1362 e.GetMessageString() ) );
1363 return false;
1364 }
1365
1366 if( aThickness != 0.0 )
1367 {
1368 aShape = BRepPrimAPI_MakePrism( face, gp_Vec( 0, 0, aThickness ) );
1369
1370 if( aShape.IsNull() )
1371 {
1372 ReportMessage( wxT( "failed to create a prismatic shape\n" ) );
1373 return false;
1374 }
1375 }
1376 else
1377 {
1378 aShape = face;
1379 }
1380
1381 return success;
1382}
1383
1384
1385static wxString formatBBox( const BOX2I& aBBox )
1386{
1387 wxString str;
1389
1390 str << "x0: " << up.StringFromValue( aBBox.GetLeft(), false ) << "; ";
1391 str << "y0: " << up.StringFromValue( aBBox.GetTop(), false ) << "; ";
1392 str << "x1: " << up.StringFromValue( aBBox.GetRight(), false ) << "; ";
1393 str << "y1: " << up.StringFromValue( aBBox.GetBottom(), false );
1394
1395 return str;
1396}
1397
1398
1399static bool makeWireFromChain( BRepLib_MakeWire& aMkWire, const SHAPE_LINE_CHAIN& aChain,
1400 double aMergeOCCMaxDist, double aZposition, const VECTOR2D& aOrigin )
1401{
1402 auto toPoint = [&]( const VECTOR2D& aKiCoords ) -> gp_Pnt
1403 {
1404 return gp_Pnt( pcbIUScale.IUTomm( aKiCoords.x - aOrigin.x ),
1405 -pcbIUScale.IUTomm( aKiCoords.y - aOrigin.y ), aZposition );
1406 };
1407
1408 try
1409 {
1410 auto addSegment = [&]( const VECTOR2I& aPt0, const VECTOR2I& aPt1 ) -> bool
1411 {
1412 if( aPt0 == aPt1 )
1413 return false;
1414
1415 gp_Pnt start = toPoint( aPt0 );
1416 gp_Pnt end = toPoint( aPt1 );
1417
1418 BRepBuilderAPI_MakeEdge mkEdge( start, end );
1419
1420 if( !mkEdge.IsDone() || mkEdge.Edge().IsNull() )
1421 {
1422 ReportMessage( wxString::Format( wxT( "failed to make segment edge at (%d "
1423 "%d) -> (%d %d), skipping\n" ),
1424 aPt0.x, aPt0.y, aPt1.x, aPt1.y ) );
1425 }
1426 else
1427 {
1428 aMkWire.Add( mkEdge.Edge() );
1429
1430 if( aMkWire.Error() != BRepLib_WireDone )
1431 {
1432 ReportMessage( wxString::Format( wxT( "failed to add segment edge "
1433 "at (%d %d) -> (%d %d)\n" ),
1434 aPt0.x, aPt0.y, aPt1.x, aPt1.y ) );
1435 return false;
1436 }
1437 }
1438
1439 return true;
1440 };
1441
1442 auto addArc = [&]( const VECTOR2I& aPt0, const SHAPE_ARC& aArc ) -> bool
1443 {
1444 // Do not export too short segments: they create broken shape because OCC thinks
1445 Handle( Geom_Curve ) curve;
1446
1447 if( aArc.GetCentralAngle() == ANGLE_360 )
1448 {
1449 gp_Ax2 axis = gp::XOY();
1450 axis.SetLocation( toPoint( aArc.GetCenter() ) );
1451
1452 curve = GC_MakeCircle( axis, pcbIUScale.IUTomm( aArc.GetRadius() ) ).Value();
1453 }
1454 else
1455 {
1456 curve = GC_MakeArcOfCircle( toPoint( aPt0 ), toPoint( aArc.GetArcMid() ),
1457 toPoint( aArc.GetP1() ) )
1458 .Value();
1459 }
1460
1461 if( curve.IsNull() )
1462 return false;
1463
1464 aMkWire.Add( BRepBuilderAPI_MakeEdge( curve ) );
1465
1466 if( !aMkWire.IsDone() )
1467 {
1468 ReportMessage( wxString::Format(
1469 wxT( "failed to add arc curve from (%d %d), arc p0 "
1470 "(%d %d), mid (%d %d), p1 (%d %d)\n" ),
1471 aPt0.x, aPt0.y, aArc.GetP0().x, aArc.GetP0().y, aArc.GetArcMid().x,
1472 aArc.GetArcMid().y, aArc.GetP1().x, aArc.GetP1().y ) );
1473 return false;
1474 }
1475
1476 return true;
1477 };
1478
1479 VECTOR2I firstPt;
1480 VECTOR2I lastPt;
1481 bool isFirstShape = true;
1482
1483 for( int i = 0; i <= aChain.PointCount() && i != -1; i = aChain.NextShape( i ) )
1484 {
1485 if( i == 0 )
1486 {
1487 if( aChain.IsArcSegment( 0 ) && aChain.IsArcSegment( aChain.PointCount() - 1 )
1488 && aChain.ArcIndex( 0 ) == aChain.ArcIndex( aChain.PointCount() - 1 ) )
1489 {
1490 // Skip first arc (we should encounter it later)
1491 int nextShape = aChain.NextShape( i );
1492
1493 // If nextShape points to the end, then we have a circle.
1494 if( nextShape != -1 )
1495 i = nextShape;
1496 }
1497 }
1498
1499 if( isFirstShape )
1500 lastPt = aChain.CPoint( i );
1501
1502 bool isArc = aChain.IsArcSegment( i );
1503
1504 if( aChain.IsArcStart( i ) )
1505 {
1506 const SHAPE_ARC& currentArc = aChain.Arc( aChain.ArcIndex( i ) );
1507
1508 if( isFirstShape )
1509 {
1510 firstPt = currentArc.GetP0();
1511 lastPt = firstPt;
1512 }
1513
1514 if( addSegment( lastPt, currentArc.GetP0() ) )
1515 lastPt = currentArc.GetP0();
1516
1517 if( addArc( lastPt, currentArc ) )
1518 lastPt = currentArc.GetP1();
1519 }
1520 else if( !isArc )
1521 {
1522 const SEG& seg = aChain.CSegment( i );
1523
1524 if( isFirstShape )
1525 {
1526 firstPt = seg.A;
1527 lastPt = firstPt;
1528 }
1529
1530 if( addSegment( lastPt, seg.A ) )
1531 lastPt = seg.A;
1532
1533 if( addSegment( lastPt, seg.B ) )
1534 lastPt = seg.B;
1535 }
1536
1537 isFirstShape = false;
1538 }
1539
1540 if( lastPt != firstPt && !addSegment( lastPt, firstPt ) )
1541 {
1543 wxString::Format( wxT( "** Failed to close wire at %d, %d -> %d, %d **\n" ),
1544 lastPt.x, lastPt.y, firstPt.x, firstPt.y ) );
1545
1546 return false;
1547 }
1548 }
1549 catch( const Standard_Failure& e )
1550 {
1551 ReportMessage( wxString::Format( wxT( "makeWireFromChain: OCC exception: %s\n" ),
1552 e.GetMessageString() ) );
1553 return false;
1554 }
1555
1556 return true;
1557}
1558
1559
1560bool STEP_PCB_MODEL::MakeShapes( std::vector<TopoDS_Shape>& aShapes, const SHAPE_POLY_SET& aPolySet, bool aConvertToArcs,
1561 double aThickness, double aZposition, const VECTOR2D& aOrigin )
1562{
1563 SHAPE_POLY_SET workingPoly = aPolySet;
1565
1566 SHAPE_POLY_SET fallbackPoly = workingPoly;
1567
1568 if( aConvertToArcs )
1569 {
1570 SHAPE_POLY_SET approximated = workingPoly;
1571
1572 for( size_t polyId = 0; polyId < approximated.CPolygons().size(); polyId++ )
1573 {
1574 SHAPE_POLY_SET::POLYGON& polygon = approximated.Polygon( polyId );
1575
1576 for( size_t contId = 0; contId < polygon.size(); contId++ )
1577 {
1578 SHAPE_LINE_CHAIN approxChain = approximateLineChainWithArcs( polygon[contId] );
1579 polygon[contId] = approxChain;
1580 }
1581 }
1582
1583 fallbackPoly = workingPoly;
1584 workingPoly = approximated;
1585
1586 // TODO: this is not accurate because it doesn't check arcs.
1587 /*if( approximated.IsSelfIntersecting() )
1588 {
1589 ReportMessage( wxString::Format( _( "\nApproximated polygon self-intersection check "
1590 "failed\n" ) ) );
1591
1592 ReportMessage( wxString::Format( _( "z: %g; bounding box: %s\n" ), aZposition,
1593 formatBBox( workingPoly.BBox() ) ) );
1594 }
1595 else
1596 {
1597 fallbackPoly = workingPoly;
1598 workingPoly = approximated;
1599 }*/
1600 }
1601
1602 #if 0 // No longer in use
1603 auto toPoint = [&]( const VECTOR2D& aKiCoords ) -> gp_Pnt
1604 {
1605 return gp_Pnt( pcbIUScale.IUTomm( aKiCoords.x - aOrigin.x ),
1606 -pcbIUScale.IUTomm( aKiCoords.y - aOrigin.y ), aZposition );
1607 };
1608 #endif
1609
1610 gp_Pln basePlane( gp_Pnt( 0.0, 0.0, aZposition ),
1611 std::signbit( aThickness ) ? -gp::DZ() : gp::DZ() );
1612
1613 for( size_t polyId = 0; polyId < workingPoly.CPolygons().size(); polyId++ )
1614 {
1615 SHAPE_POLY_SET::POLYGON& polygon = workingPoly.Polygon( polyId );
1616
1617 auto tryMakeWire = [this, &aZposition,
1618 &aOrigin]( const SHAPE_LINE_CHAIN& aContour ) -> TopoDS_Wire
1619 {
1620 TopoDS_Wire wire;
1621 BRepLib_MakeWire mkWire;
1622
1623 makeWireFromChain( mkWire, aContour, m_mergeOCCMaxDist, aZposition, aOrigin );
1624
1625 if( mkWire.IsDone() )
1626 {
1627 wire = mkWire.Wire();
1628 }
1629 else
1630 {
1632 wxString::Format( _( "Wire not done (contour points %d): OCC error %d\n" ),
1633 static_cast<int>( aContour.PointCount() ),
1634 static_cast<int>( mkWire.Error() ) ) );
1635
1636 ReportMessage( wxString::Format( _( "z: %g; bounding box: %s\n" ), aZposition,
1637 formatBBox( aContour.BBox() ) ) );
1638 }
1639
1640 if( !wire.IsNull() )
1641 {
1642 BRepAlgoAPI_Check check( wire, false, true );
1643
1644 if( !check.IsValid() )
1645 {
1646 ReportMessage( wxString::Format( _( "\nWire self-interference check "
1647 "failed\n" ) ) );
1648
1649 ReportMessage( wxString::Format( _( "z: %g; bounding box: %s\n" ), aZposition,
1650 formatBBox( aContour.BBox() ) ) );
1651
1652 wire.Nullify();
1653 }
1654 }
1655
1656 return wire;
1657 };
1658
1659 BRepBuilderAPI_MakeFace mkFace;
1660
1661 for( size_t contId = 0; contId < polygon.size(); contId++ )
1662 {
1663 try
1664 {
1665 TopoDS_Wire wire = tryMakeWire( polygon[contId] );
1666
1667 if( aConvertToArcs && wire.IsNull() )
1668 {
1669 ReportMessage( wxString::Format( _( "Using non-simplified polygon.\n" ) ) );
1670
1671 // Fall back to original shape
1672 wire = tryMakeWire( fallbackPoly.CPolygon( polyId )[contId] );
1673 }
1674
1675 if( contId == 0 ) // Outline
1676 {
1677 if( !wire.IsNull() )
1678 {
1679 if( basePlane.Axis().Direction().Z() < 0 )
1680 wire.Reverse();
1681
1682 mkFace = BRepBuilderAPI_MakeFace( basePlane, wire );
1683 }
1684 else
1685 {
1686 ReportMessage( wxString::Format( wxT( "\n** Outline skipped **\n" ) ) );
1687
1688 ReportMessage( wxString::Format( wxT( "z: %g; bounding box: %s\n" ),
1689 aZposition,
1690 formatBBox( polygon[contId].BBox() ) ) );
1691
1692 break;
1693 }
1694 }
1695 else // Hole
1696 {
1697 if( !wire.IsNull() )
1698 {
1699 if( basePlane.Axis().Direction().Z() > 0 )
1700 wire.Reverse();
1701
1702 mkFace.Add( wire );
1703 }
1704 else
1705 {
1706 ReportMessage( wxString::Format( wxT( "\n** Hole skipped **\n" ) ) );
1707
1708 ReportMessage( wxString::Format( wxT( "z: %g; bounding box: %s\n" ),
1709 aZposition,
1710 formatBBox( polygon[contId].BBox() ) ) );
1711 }
1712 }
1713 }
1714 catch( const Standard_Failure& e )
1715 {
1717 wxString::Format( wxT( "MakeShapes (contour %d): OCC exception: %s\n" ),
1718 static_cast<int>( contId ), e.GetMessageString() ) );
1719 return false;
1720 }
1721 }
1722
1723 if( mkFace.IsDone() )
1724 {
1725 TopoDS_Shape faceShape = mkFace.Shape();
1726
1727 if( aThickness != 0.0 )
1728 {
1729 TopoDS_Shape prism = BRepPrimAPI_MakePrism( faceShape, gp_Vec( 0, 0, aThickness ) );
1730 aShapes.push_back( prism );
1731
1732 if( prism.IsNull() )
1733 {
1734 ReportMessage( _( "Failed to create a prismatic shape\n" ) );
1735 return false;
1736 }
1737 }
1738 else
1739 {
1740 aShapes.push_back( faceShape );
1741 }
1742 }
1743 else
1744 {
1745 ReportMessage( wxString::Format( _( "** Face skipped **\n" ) ) );
1746 }
1747 }
1748
1749 return true;
1750}
1751
1752
1753// These colors are based on 3D viewer's colors and are different to "gbrjobColors"
1754static std::vector<FAB_LAYER_COLOR> s_soldermaskColors = {
1755 { NotSpecifiedPrm(), wxColor( 20, 51, 36 ) }, // Not specified, not in .gbrjob file
1756 { _HKI( "Green" ), wxColor( 20, 51, 36 ) }, // used in .gbrjob file
1757 { _HKI( "Red" ), wxColor( 181, 19, 21 ) }, // used in .gbrjob file
1758 { _HKI( "Blue" ), wxColor( 2, 59, 162 ) }, // used in .gbrjob file
1759 { _HKI( "Purple" ), wxColor( 32, 2, 53 ) }, // used in .gbrjob file
1760 { _HKI( "Black" ), wxColor( 11, 11, 11 ) }, // used in .gbrjob file
1761 { _HKI( "White" ), wxColor( 245, 245, 245 ) }, // used in .gbrjob file
1762 { _HKI( "Yellow" ), wxColor( 194, 195, 0 ) }, // used in .gbrjob file
1763 { _HKI( "User defined" ), wxColor( 128, 128, 128 ) } // Free; the name is a dummy name here
1764};
1765
1766
1767static bool colorFromStackup( BOARD_STACKUP_ITEM_TYPE aType, const wxString& aColorStr,
1768 COLOR4D& aColorOut )
1769{
1770 if( !IsPrmSpecified( aColorStr ) )
1771 return false;
1772
1773 if( aColorStr.StartsWith( wxT( "#" ) ) ) // User defined color
1774 {
1775 aColorOut = COLOR4D( aColorStr );
1776 return true;
1777 }
1778 else
1779 {
1780 const std::vector<FAB_LAYER_COLOR>& colors =
1781 ( aType == BS_ITEM_TYPE_SOLDERMASK || aType == BS_ITEM_TYPE_SILKSCREEN )
1783 : GetStandardColors( aType );
1784
1785 for( const FAB_LAYER_COLOR& fabColor : colors )
1786 {
1787 if( fabColor.GetName() == aColorStr )
1788 {
1789 aColorOut = fabColor.GetColor( aType );
1790 return true;
1791 }
1792 }
1793 }
1794
1795 return false;
1796}
1797
1798
1799bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin, bool aPushBoardBody )
1800{
1801 if( m_hasPCB )
1802 {
1803 if( !isBoardOutlineValid() )
1804 return false;
1805
1806 return true;
1807 }
1808
1809 Handle( XCAFDoc_VisMaterialTool ) visMatTool =
1810 XCAFDoc_DocumentTool::VisMaterialTool( m_doc->Main() );
1811
1812 m_hasPCB = true; // whether or not operations fail we note that CreatePCB has been invoked
1813
1814 // Support for more than one main outline (more than one board)
1815 ReportMessage( wxString::Format( wxT( "Build board outlines (%d outlines) with %d points.\n" ),
1816 aOutline.OutlineCount(), aOutline.FullPointCount() ) );
1817
1818 double boardThickness;
1819 double boardZPos;
1820 getBoardBodyZPlacement( boardZPos, boardThickness );
1821
1822#if 1
1823 // This code should work, and it is working most of time
1824 // However there are issues if the main outline is a circle with holes:
1825 // holes from vias and pads are not working
1826 // see bug https://gitlab.com/kicad/code/kicad/-/issues/17446
1827 // (Holes are missing from STEP export with circular PCB outline)
1828 // Hard to say if the bug is in our code or in OCC 7.7
1829 if( !MakeShapes( m_board_outlines, aOutline, false, boardThickness, boardZPos, aOrigin ) )
1830 {
1831 // Error
1832 ReportMessage( wxString::Format(
1833 wxT( "OCC error creating main outline.\n" ) ) );
1834 }
1835#else
1836 // Workaround for bug #17446 Holes are missing from STEP export with circular PCB outline
1837 for( const SHAPE_POLY_SET::POLYGON& polygon : aOutline.CPolygons() )
1838 {
1839 for( size_t contId = 0; contId < polygon.size(); contId++ )
1840 {
1841 const SHAPE_LINE_CHAIN& contour = polygon[contId];
1842 SHAPE_POLY_SET polyset;
1843 polyset.Append( contour );
1844
1845 if( contId == 0 ) // main Outline
1846 {
1847 if( !MakeShapes( m_board_outlines, polyset, false, boardThickness, boardZPos,
1848 aOrigin ) )
1849 {
1850 ReportMessage( wxT( "OCC error creating main outline.\n" ) );
1851 }
1852 }
1853 else // Hole inside the main outline
1854 {
1855 if( !MakeShapes( m_boardCutouts, polyset, false, boardThickness, boardZPos,
1856 aOrigin ) )
1857 {
1858 ReportMessage( wxT( "OCC error creating hole in main outline.\n" ) );
1859 }
1860 }
1861 }
1862 }
1863#endif
1864
1865 // Even if we've disabled board body export, we still need the shapes for bounding box calculations.
1866 Bnd_Box brdBndBox;
1867
1868 for( const TopoDS_Shape& brdShape : m_board_outlines )
1869 BRepBndLib::Add( brdShape, brdBndBox );
1870
1871 // subtract cutouts (if any)
1872 ReportMessage( wxString::Format( wxT( "Build board cutouts and holes (%d hole(s)).\n" ),
1873 (int) ( m_boardCutouts.size() + m_copperCutouts.size() ) ) );
1874
1875 auto buildBSB = [&brdBndBox]( std::vector<TopoDS_Shape>& input, Bnd_BoundSortBox& bsbHoles )
1876 {
1877 // We need to encompass every location we'll need to test in the global bbox,
1878 // otherwise Bnd_BoundSortBox doesn't work near the boundaries.
1879 Bnd_Box brdWithHolesBndBox = brdBndBox;
1880
1881 Handle( Bnd_HArray1OfBox ) holeBoxSet = new Bnd_HArray1OfBox( 0, input.size() - 1 );
1882
1883 for( size_t i = 0; i < input.size(); i++ )
1884 {
1885 Bnd_Box bbox;
1886 BRepBndLib::Add( input[i], bbox );
1887 brdWithHolesBndBox.Add( bbox );
1888 ( *holeBoxSet )[i] = bbox;
1889 }
1890
1891 bsbHoles.Initialize( brdWithHolesBndBox, holeBoxSet );
1892 };
1893
1894 auto subtractShapes = []( const wxString& aWhat, std::vector<TopoDS_Shape>& aShapesList,
1895 std::vector<TopoDS_Shape>& aHolesList, Bnd_BoundSortBox& aBSBHoles )
1896 {
1897 // Remove holes for each item (board body or bodies, one can have more than one board)
1898 int cnt = 0;
1899 for( TopoDS_Shape& shape : aShapesList )
1900 {
1901 Bnd_Box shapeBbox;
1902 BRepBndLib::Add( shape, shapeBbox );
1903
1904 const TColStd_ListOfInteger& indices = aBSBHoles.Compare( shapeBbox );
1905
1906 TopTools_ListOfShape holelist;
1907
1908 for( const Standard_Integer& index : indices )
1909 holelist.Append( aHolesList[index] );
1910
1911 if( cnt == 0 )
1912 ReportMessage( wxString::Format( _( "Build holes for %s\n" ), aWhat ) );
1913
1914 cnt++;
1915
1916 if( cnt % 10 == 0 )
1917 ReportMessage( wxString::Format( _( "Cutting %d/%d %s\n" ), cnt,
1918 (int) aShapesList.size(), aWhat ) );
1919
1920 if( holelist.IsEmpty() )
1921 continue;
1922
1923 TopTools_ListOfShape cutArgs;
1924 cutArgs.Append( shape );
1925
1926 BRepAlgoAPI_Cut cut;
1927
1928 cut.SetRunParallel( true );
1929 cut.SetToFillHistory( false );
1930
1931 cut.SetArguments( cutArgs );
1932 cut.SetTools( holelist );
1933 cut.Build();
1934
1935 if( cut.HasErrors() || cut.HasWarnings() )
1936 {
1937 ReportMessage( wxString::Format(
1938 _( "\n** Got problems while cutting %s number %d **\n" ), aWhat, cnt ) );
1939 shapeBbox.Dump();
1940
1941 if( cut.HasErrors() )
1942 {
1943 ReportMessage( _( "Errors:\n" ) );
1944 cut.DumpErrors( std::cout );
1945 }
1946
1947 if( cut.HasWarnings() )
1948 {
1949 ReportMessage( _( "Warnings:\n" ) );
1950 cut.DumpWarnings( std::cout );
1951 }
1952
1953 std::cout << "\n";
1954 }
1955
1956 shape = cut.Shape();
1957 }
1958 };
1959
1960 if( m_boardCutouts.size() )
1961 {
1962 Bnd_BoundSortBox bsbHoles;
1963 buildBSB( m_boardCutouts, bsbHoles );
1964
1965 subtractShapes( _( "shapes" ), m_board_outlines, m_boardCutouts, bsbHoles );
1966 }
1967
1968 if( m_copperCutouts.size() )
1969 {
1970 Bnd_BoundSortBox bsbHoles;
1971 buildBSB( m_copperCutouts, bsbHoles );
1972
1973 subtractShapes( _( "pads" ), m_board_copper_pads, m_copperCutouts, bsbHoles );
1974 subtractShapes( _( "vias" ), m_board_copper_vias, m_copperCutouts, bsbHoles );
1975 }
1976
1977 if( m_fuseShapes )
1978 {
1979 ReportMessage( wxT( "Fusing shapes\n" ) );
1980
1981 TopTools_ListOfShape shapesToFuse;
1982
1983 for( TopoDS_Shape& shape : m_board_copper )
1984 shapesToFuse.Append( shape );
1985
1986 for( TopoDS_Shape& shape : m_board_copper_pads )
1987 shapesToFuse.Append( shape );
1988
1989 for( TopoDS_Shape& shape : m_board_copper_vias )
1990 shapesToFuse.Append( shape );
1991
1992 TopoDS_Shape fusedShape = fuseShapesOrCompound( shapesToFuse );
1993
1994 if( !fusedShape.IsNull() )
1995 {
1996 m_board_copper_fused.emplace_back( fusedShape );
1997
1998 m_board_copper.clear();
1999 m_board_copper_pads.clear();
2000 m_board_copper_vias.clear();
2001 }
2002 }
2003
2004 // push the board to the data structure
2005 ReportMessage( wxT( "\nGenerate board full shape.\n" ) );
2006
2007 // AddComponent adds a label that has a reference (not a parent/child relation) to the real
2008 // label. We need to extract that real label to name it for the STEP output cleanly
2009 // Why are we trying to name the bare board? Because CAD tools like SolidWorks do fun things
2010 // like "deduplicate" imported STEPs by swapping STEP assembly components with already
2011 // identically named assemblies. So we want to avoid having the PCB be generally defaulted
2012 // to "Component" or "Assembly".
2013
2014 auto pushToAssembly = [&]( std::vector<TopoDS_Shape>& aShapesList, Quantity_ColorRGBA aColor,
2015 const TDF_Label& aVisMatLabel, const wxString& aShapeName,
2016 bool aCompound )
2017 {
2018 if( aShapesList.empty() )
2019 return;
2020
2021 std::vector<TopoDS_Shape> newList;
2022
2023 if( aCompound )
2024 newList.emplace_back( makeCompound( aShapesList ) );
2025 else
2026 newList = aShapesList;
2027
2028 int i = 1;
2029 for( TopoDS_Shape& shape : newList )
2030 {
2031 Handle( TDataStd_TreeNode ) node;
2032
2033 // Dont expand the component or else coloring it gets hard
2034 TDF_Label lbl = m_assy->AddComponent( m_assy_label, shape, false );
2035 m_pcb_labels.push_back( lbl );
2036
2037 if( m_pcb_labels.back().IsNull() )
2038 return;
2039
2040 lbl.FindAttribute( XCAFDoc::ShapeRefGUID(), node );
2041 TDF_Label shpLbl = node->Father()->Label();
2042 if( !shpLbl.IsNull() )
2043 {
2044 if( visMatTool && !aVisMatLabel.IsNull() )
2045 visMatTool->SetShapeMaterial( shpLbl, aVisMatLabel );
2046
2047 wxString shapeName;
2048
2049 if( newList.size() > 1 )
2050 {
2051 shapeName = wxString::Format( wxT( "%s_%s_%d" ), m_pcbName, aShapeName, i );
2052 }
2053 else
2054 {
2055 shapeName = wxString::Format( wxT( "%s_%s" ), m_pcbName, aShapeName );
2056 }
2057
2058
2059 TCollection_ExtendedString partname( shapeName.ToUTF8().data() );
2060 TDataStd_Name::Set( shpLbl, partname );
2061 }
2062
2063 i++;
2064 }
2065 };
2066
2067 auto makeMaterial = [&]( const TCollection_AsciiString& aName,
2068 const Quantity_ColorRGBA& aBaseColor, double aMetallic,
2069 double aRoughness ) -> TDF_Label
2070 {
2071 Handle( XCAFDoc_VisMaterial ) vismat = new XCAFDoc_VisMaterial;
2072 XCAFDoc_VisMaterialPBR pbr;
2073 pbr.BaseColor = aBaseColor;
2074 pbr.Metallic = aMetallic;
2075 pbr.Roughness = aRoughness;
2076 vismat->SetPbrMaterial( pbr );
2077 return visMatTool->AddMaterial( vismat, aName );
2078 };
2079
2080 // Init colors for the board items
2081 Quantity_ColorRGBA copper_color( m_copperColor[0], m_copperColor[1], m_copperColor[2], 1.0 );
2082 Quantity_ColorRGBA pad_color( m_padColor[0], m_padColor[1], m_padColor[2], 1.0 );
2083
2084 Quantity_ColorRGBA board_color( 0.3f, 0.3f, 0.3f, 1.0f );
2085 Quantity_ColorRGBA silk_color( 1.0f, 1.0f, 1.0f, 0.9f );
2086 Quantity_ColorRGBA mask_color( 0.08f, 0.2f, 0.14f, 0.83f );
2087
2088 // Get colors from stackup
2089 for( const BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
2090 {
2091 COLOR4D col;
2092
2093 if( !colorFromStackup( item->GetType(), item->GetColor(), col ) )
2094 continue;
2095
2096 if( item->GetBrdLayerId() == F_Mask || item->GetBrdLayerId() == B_Mask )
2097 {
2098 col.Darken( 0.2 );
2099 mask_color.SetValues( col.r, col.g, col.b, col.a );
2100 }
2101
2102 if( item->GetBrdLayerId() == F_SilkS || item->GetBrdLayerId() == B_SilkS )
2103 silk_color.SetValues( col.r, col.g, col.b, col.a );
2104
2105 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC && item->GetTypeName() == KEY_CORE )
2106 board_color.SetValues( col.r, col.g, col.b, col.a );
2107 }
2108
2110 {
2111 board_color = mask_color;
2112 board_color.SetAlpha( 1.0 );
2113 }
2114
2115 TDF_Label mask_mat = makeMaterial( "soldermask", mask_color, 0.0, 0.6 );
2116 TDF_Label silk_mat = makeMaterial( "silkscreen", silk_color, 0.0, 0.9 );
2117 TDF_Label copper_mat = makeMaterial( "copper", copper_color, 1.0, 0.4 );
2118 TDF_Label pad_mat = makeMaterial( "pad", pad_color, 1.0, 0.4 );
2119 TDF_Label board_mat = makeMaterial( "board", board_color, 0.0, 0.8 );
2120
2121 pushToAssembly( m_board_copper, copper_color, copper_mat, "copper", true );
2122 pushToAssembly( m_board_copper_pads, pad_color, pad_mat, "pad", true );
2123 pushToAssembly( m_board_copper_vias, copper_color, copper_mat, "via", true );
2124 pushToAssembly( m_board_copper_fused, copper_color, copper_mat, "copper", true );
2125 pushToAssembly( m_board_silkscreen, silk_color, silk_mat, "silkscreen", true );
2126 pushToAssembly( m_board_soldermask, mask_color, mask_mat, "soldermask", true );
2127
2128 if( aPushBoardBody )
2129 pushToAssembly( m_board_outlines, board_color, board_mat, "PCB", false );
2130
2131#if( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 )
2132 m_assy->UpdateAssemblies();
2133#endif
2134
2135 return true;
2136}
2137
2138
2139#ifdef SUPPORTS_IGES
2140// write the assembly model in IGES format
2141bool STEP_PCB_MODEL::WriteIGES( const wxString& aFileName )
2142{
2143 if( !isBoardOutlineValid() )
2144 {
2145 ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
2146 "'%s'.\n" ),
2147 aFileName ) );
2148 return false;
2149 }
2150
2151 m_outFmt = OUTPUT_FORMAT::FMT_OUT_IGES;
2152
2153 wxFileName fn( aFileName );
2154 IGESControl_Controller::Init();
2155 IGESCAFControl_Writer writer;
2156 writer.SetColorMode( Standard_True );
2157 writer.SetNameMode( Standard_True );
2158 IGESData_GlobalSection header = writer.Model()->GlobalSection();
2159 header.SetFileName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
2160 header.SetSendName( new TCollection_HAsciiString( "KiCad electronic assembly" ) );
2161 header.SetAuthorName(
2162 new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.author" ) ) );
2163 header.SetCompanyName(
2164 new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.company" ) ) );
2165 writer.Model()->SetGlobalSection( header );
2166
2167 if( Standard_False == writer.Perform( m_doc, aFileName.c_str() ) )
2168 return false;
2169
2170 return true;
2171}
2172#endif
2173
2174
2175bool STEP_PCB_MODEL::WriteSTEP( const wxString& aFileName, bool aOptimize )
2176{
2177 if( !isBoardOutlineValid() )
2178 {
2179 ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
2180 "'%s'.\n" ),
2181 aFileName ) );
2182 return false;
2183 }
2184
2185 m_outFmt = OUTPUT_FORMAT::FMT_OUT_STEP;
2186
2187 wxFileName fn( aFileName );
2188
2189 STEPCAFControl_Writer writer;
2190 writer.SetColorMode( Standard_True );
2191 writer.SetNameMode( Standard_True );
2192
2193 // This must be set before we "transfer" the document.
2194 // Should default to kicad_pcb.general.title_block.title,
2195 // but in the meantime, defaulting to the basename of the output
2196 // target is still better than "open cascade step translter v..."
2197 // UTF8 should be ok from ISO 10303-21:2016, but... older stuff? use boring ascii
2198 if( !Interface_Static::SetCVal( "write.step.product.name", fn.GetName().ToAscii() ) )
2199 ReportMessage( wxT( "Failed to set step product name, but will attempt to continue." ) );
2200
2201 // Setting write.surfacecurve.mode to 0 reduces file size and write/read times.
2202 // But there are reports that this mode might be less compatible in some cases.
2203 if( !Interface_Static::SetIVal( "write.surfacecurve.mode", aOptimize ? 0 : 1 ) )
2204 ReportMessage( wxT( "Failed to set surface curve mode, but will attempt to continue." ) );
2205
2206 if( Standard_False == writer.Transfer( m_doc, STEPControl_AsIs ) )
2207 return false;
2208
2209 APIHeaderSection_MakeHeader hdr( writer.ChangeWriter().Model() );
2210
2211 // Note: use only Ascii7 chars, non Ascii7 chars (therefore UFT8 chars)
2212 // are creating issues in the step file
2213 hdr.SetName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
2214
2215 // TODO: how to control and ensure consistency with IGES?
2216 hdr.SetAuthorValue( 1, new TCollection_HAsciiString( "Pcbnew" ) );
2217 hdr.SetOrganizationValue( 1, new TCollection_HAsciiString( "Kicad" ) );
2218 hdr.SetOriginatingSystem( new TCollection_HAsciiString( "KiCad to STEP converter" ) );
2219 hdr.SetDescriptionValue( 1, new TCollection_HAsciiString( "KiCad electronic assembly" ) );
2220
2221 bool success = true;
2222
2223 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
2224 wxString currCWD = wxGetCwd();
2225 wxString workCWD = fn.GetPath();
2226
2227 if( !workCWD.IsEmpty() )
2228 wxSetWorkingDirectory( workCWD );
2229
2230 char tmpfname[] = "$tempfile$.step";
2231
2232 if( Standard_False == writer.Write( tmpfname ) )
2233 success = false;
2234
2235 if( success )
2236 {
2237
2238 // Preserve the permissions of the current file
2239 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpfname );
2240
2241 if( !wxRenameFile( tmpfname, fn.GetFullName(), true ) )
2242 {
2243 ReportMessage( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
2244 tmpfname,
2245 fn.GetFullName() ) );
2246 success = false;
2247 }
2248 }
2249
2250 wxSetWorkingDirectory( currCWD );
2251
2252 return success;
2253}
2254
2255
2256bool STEP_PCB_MODEL::WriteBREP( const wxString& aFileName )
2257{
2258 if( !isBoardOutlineValid() )
2259 {
2260 ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
2261 "'%s'.\n" ),
2262 aFileName ) );
2263 return false;
2264 }
2265
2266 m_outFmt = OUTPUT_FORMAT::FMT_OUT_BREP;
2267
2268 // s_assy = shape tool for the source
2269 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( m_doc->Main() );
2270
2271 // retrieve assembly as a single shape
2272 TopoDS_Shape shape = getOneShape( s_assy );
2273
2274 wxFileName fn( aFileName );
2275
2276 wxFFileOutputStream ffStream( fn.GetFullPath() );
2277 wxStdOutputStream stdStream( ffStream );
2278
2279#if OCC_VERSION_HEX >= 0x070600
2280 BRepTools::Write( shape, stdStream, false, false, TopTools_FormatVersion_VERSION_1 );
2281#else
2282 BRepTools::Write( shape, stdStream );
2283#endif
2284
2285 return true;
2286}
2287
2288
2289bool STEP_PCB_MODEL::WriteXAO( const wxString& aFileName )
2290{
2291 wxFileName fn( aFileName );
2292
2293 wxFFileOutputStream ffStream( fn.GetFullPath() );
2294 wxStdOutputStream file( ffStream );
2295
2296 if( !ffStream.IsOk() )
2297 {
2298 ReportMessage( wxString::Format( "Could not open file '%s'", fn.GetFullPath() ) );
2299 return false;
2300 }
2301
2302 m_outFmt = OUTPUT_FORMAT::FMT_OUT_XAO;
2303
2304 // s_assy = shape tool for the source
2305 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( m_doc->Main() );
2306
2307 // retrieve assembly as a single shape
2308 const TopoDS_Shape shape = getOneShape( s_assy );
2309
2310 std::map<wxString, std::vector<int>> groups[4];
2311 std::map<wxString, double> groupAreas;
2312 TopExp_Explorer exp;
2313 int faceIndex = 0;
2314
2315 for( exp.Init( shape, TopAbs_FACE ); exp.More(); exp.Next() )
2316 {
2317 TopoDS_Shape subShape = exp.Current();
2318
2319 Bnd_Box bbox;
2320 BRepBndLib::Add( subShape, bbox );
2321
2322 for( const auto& [padKey, pair] : m_pad_points )
2323 {
2324 const auto& [point, padTestShape] = pair;
2325
2326 if( bbox.IsOut( point ) )
2327 continue;
2328
2329 BRepAdaptor_Surface surface( TopoDS::Face( subShape ) );
2330
2331 if( surface.GetType() != GeomAbs_Plane )
2332 continue;
2333
2334 BRepExtrema_DistShapeShape dist( padTestShape, subShape );
2335 dist.Perform();
2336
2337 if( !dist.IsDone() )
2338 continue;
2339
2340 if( dist.Value() < Precision::Approximation() )
2341 {
2342 // Push as a face group
2343 groups[2][padKey].push_back( faceIndex );
2344
2345 GProp_GProps system;
2346 BRepGProp::SurfaceProperties( subShape, system );
2347
2348 double surfaceArea = system.Mass() / 1e6; // Convert to meters^2
2349 groupAreas[padKey] = surfaceArea;
2350 }
2351 }
2352
2353 faceIndex++;
2354 }
2355
2356 // Based on Gmsh code
2357 file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
2358 file << "<XAO version=\"1.0\" author=\"KiCad\">" << std::endl;
2359 file << " <geometry name=\"" << fn.GetName() << "\">" << std::endl;
2360 file << " <shape format=\"BREP\"><![CDATA[";
2361#if OCC_VERSION_HEX < 0x070600
2362 BRepTools::Write( shape, file );
2363#else
2364 BRepTools::Write( shape, file, Standard_True, Standard_True, TopTools_FormatVersion_VERSION_1 );
2365#endif
2366 file << "]]></shape>" << std::endl;
2367 file << " <topology>" << std::endl;
2368
2369 TopTools_IndexedMapOfShape mainMap;
2370 TopExp::MapShapes( shape, mainMap );
2371 std::set<int> topo[4];
2372
2373 static const TopAbs_ShapeEnum c_dimShapeTypes[] = { TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE,
2374 TopAbs_SOLID };
2375
2376 static const std::string c_dimLabel[] = { "vertex", "edge", "face", "solid" };
2377 static const std::string c_dimLabels[] = { "vertices", "edges", "faces", "solids" };
2378
2379 for( int dim = 0; dim < 4; dim++ )
2380 {
2381 for( exp.Init( shape, c_dimShapeTypes[dim] ); exp.More(); exp.Next() )
2382 {
2383 TopoDS_Shape subShape = exp.Current();
2384 int idx = mainMap.FindIndex( subShape );
2385
2386 if( idx && !topo[dim].count( idx ) )
2387 topo[dim].insert( idx );
2388 }
2389 }
2390
2391 for( int dim = 0; dim <= 3; dim++ )
2392 {
2393 std::string labels = c_dimLabels[dim];
2394 std::string label = c_dimLabel[dim];
2395
2396 file << " <" << labels << " count=\"" << topo[dim].size() << "\">" << std::endl;
2397 int index = 0;
2398
2399 for( auto p : topo[dim] )
2400 {
2401 std::string name( "" );
2402 file << " <" << label << " index=\"" << index << "\" "
2403 << "name=\"" << name << "\" "
2404 << "reference=\"" << p << "\"/>" << std::endl;
2405
2406 index++;
2407 }
2408 file << " </" << labels << ">" << std::endl;
2409 }
2410
2411 file << " </topology>" << std::endl;
2412 file << " </geometry>" << std::endl;
2413 file << " <groups count=\""
2414 << groups[0].size() + groups[1].size() + groups[2].size() + groups[3].size() << "\">"
2415 << std::endl;
2416
2417 int groupNumber = 1;
2418
2419 ReportMessage( "Pad definitions:\n" );
2420 ReportMessage( "Number\tName\tArea (m^2)\n" );
2421
2422 for( int dim = 0; dim <= 3; dim++ )
2423 {
2424 std::string label = c_dimLabel[dim];
2425
2426 for( auto g : groups[dim] )
2427 {
2428 //std::string name = model->getPhysicalName( dim, g.first );
2429 wxString name = g.first;
2430
2431 if( name.empty() )
2432 { // create same unique name as for MED export
2433 std::ostringstream gs;
2434 gs << "G_" << dim << "D_" << g.first;
2435 name = gs.str();
2436 }
2437 file << " <group name=\"" << name << "\" dimension=\"" << label;
2438//#if 1
2439// // Gmsh XAO extension: also save the physical tag, so that XAO can be used
2440// // to serialize OCC geometries, ready to be used by GetDP, GmshFEM & co
2441// file << "\" tag=\"" << g.first;
2442//#endif
2443 file << "\" count=\"" << g.second.size() << "\">" << std::endl;
2444 for( auto index : g.second )
2445 {
2446 file << " <element index=\"" << index << "\"/>" << std::endl;
2447 }
2448 file << " </group>" << std::endl;
2449
2450 ReportMessage( wxString::Format( "%d\t%s\t%g\n", groupNumber, name, groupAreas[name] ) );
2451
2452 groupNumber++;
2453 }
2454 }
2455
2456 ReportMessage( "\n" );
2457
2458 file << " </groups>" << std::endl;
2459 file << " <fields count=\"0\"/>" << std::endl;
2460 file << "</XAO>" << std::endl;
2461
2462 return true;
2463}
2464
2465
2466bool STEP_PCB_MODEL::getModelLabel( const std::string& aFileNameUTF8, VECTOR3D aScale, TDF_Label& aLabel,
2467 bool aSubstituteModels, wxString* aErrorMessage )
2468{
2469 std::string model_key = aFileNameUTF8 + "_" + std::to_string( aScale.x )
2470 + "_" + std::to_string( aScale.y ) + "_" + std::to_string( aScale.z );
2471
2472 MODEL_MAP::const_iterator mm = m_models.find( model_key );
2473
2474 if( mm != m_models.end() )
2475 {
2476 aLabel = mm->second;
2477 return true;
2478 }
2479
2480 aLabel.Nullify();
2481
2482 Handle( TDocStd_Document ) doc;
2483 m_app->NewDocument( "MDTV-XCAF", doc );
2484
2485 wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
2486 MODEL3D_FORMAT_TYPE modelFmt = fileType( aFileNameUTF8.c_str() );
2487
2488 switch( modelFmt )
2489 {
2490 case FMT_IGES:
2491 if( !readIGES( doc, aFileNameUTF8.c_str() ) )
2492 {
2493 ReportMessage( wxString::Format( wxT( "readIGES() failed on filename '%s'.\n" ),
2494 fileName ) );
2495 return false;
2496 }
2497 break;
2498
2499 case FMT_STEP:
2500 if( !readSTEP( doc, aFileNameUTF8.c_str() ) )
2501 {
2502 ReportMessage( wxString::Format( wxT( "readSTEP() failed on filename '%s'.\n" ),
2503 fileName ) );
2504 return false;
2505 }
2506 break;
2507
2508 case FMT_STEPZ:
2509 {
2510 // To export a compressed step file (.stpz or .stp.gz file), the best way is to
2511 // decaompress it in a temporaty file and load this temporary file
2512 wxFFileInputStream ifile( fileName );
2513 wxFileName outFile( fileName );
2514
2515 outFile.SetPath( wxStandardPaths::Get().GetTempDir() );
2516 outFile.SetExt( wxT( "step" ) );
2517 wxFileOffset size = ifile.GetLength();
2518
2519 if( size == wxInvalidOffset )
2520 {
2521 ReportMessage( wxString::Format( wxT( "getModelLabel() failed on filename '%s'.\n" ),
2522 fileName ) );
2523 return false;
2524 }
2525
2526 {
2527 bool success = false;
2528 wxFFileOutputStream ofile( outFile.GetFullPath() );
2529
2530 if( !ofile.IsOk() )
2531 return false;
2532
2533 char* buffer = new char[size];
2534
2535 ifile.Read( buffer, size );
2536 std::string expanded;
2537
2538 try
2539 {
2540 expanded = gzip::decompress( buffer, size );
2541 success = true;
2542 }
2543 catch( ... )
2544 {
2545 ReportMessage( wxString::Format( wxT( "failed to decompress '%s'.\n" ),
2546 fileName ) );
2547 }
2548
2549 if( expanded.empty() )
2550 {
2551 ifile.Reset();
2552 ifile.SeekI( 0 );
2553 wxZipInputStream izipfile( ifile );
2554 std::unique_ptr<wxZipEntry> zip_file( izipfile.GetNextEntry() );
2555
2556 if( zip_file && !zip_file->IsDir() && izipfile.CanRead() )
2557 {
2558 izipfile.Read( ofile );
2559 success = true;
2560 }
2561 }
2562 else
2563 {
2564 ofile.Write( expanded.data(), expanded.size() );
2565 }
2566
2567 delete[] buffer;
2568 ofile.Close();
2569
2570 if( success )
2571 {
2572 std::string altFileNameUTF8 = TO_UTF8( outFile.GetFullPath() );
2573 success =
2574 getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false );
2575 }
2576
2577 return success;
2578 }
2579
2580 break;
2581 }
2582
2583 case FMT_WRL:
2584 case FMT_WRZ:
2585 /* WRL files are preferred for internal rendering, due to superior material properties, etc.
2586 * However they are not suitable for MCAD export.
2587 *
2588 * If a .wrl file is specified, attempt to locate a replacement file for it.
2589 *
2590 * If a valid replacement file is found, the label for THAT file will be associated with
2591 * the .wrl file
2592 */
2593 if( aSubstituteModels )
2594 {
2595 wxFileName wrlName( fileName );
2596
2597 wxString basePath = wrlName.GetPath();
2598 wxString baseName = wrlName.GetName();
2599
2600 // List of alternate files to look for
2601 // Given in order of preference
2602 // (Break if match is found)
2603 wxArrayString alts;
2604
2605 // Step files
2606 alts.Add( wxT( "stp" ) );
2607 alts.Add( wxT( "step" ) );
2608 alts.Add( wxT( "STP" ) );
2609 alts.Add( wxT( "STEP" ) );
2610 alts.Add( wxT( "Stp" ) );
2611 alts.Add( wxT( "Step" ) );
2612 alts.Add( wxT( "stpz" ) );
2613 alts.Add( wxT( "stpZ" ) );
2614 alts.Add( wxT( "STPZ" ) );
2615 alts.Add( wxT( "step.gz" ) );
2616 alts.Add( wxT( "stp.gz" ) );
2617
2618 // IGES files
2619 alts.Add( wxT( "iges" ) );
2620 alts.Add( wxT( "IGES" ) );
2621 alts.Add( wxT( "igs" ) );
2622 alts.Add( wxT( "IGS" ) );
2623
2624 //TODO - Other alternative formats?
2625
2626 for( const auto& alt : alts )
2627 {
2628 wxFileName altFile( basePath, baseName + wxT( "." ) + alt );
2629
2630 if( altFile.IsOk() && altFile.FileExists() )
2631 {
2632 std::string altFileNameUTF8 = TO_UTF8( altFile.GetFullPath() );
2633
2634 // When substituting a STEP/IGS file for VRML, do not apply the VRML scaling
2635 // to the new STEP model. This process of auto-substitution is janky as all
2636 // heck so let's not mix up un-displayed scale factors with potentially
2637 // mis-matched files. And hope that the user doesn't have multiples files
2638 // named "model.wrl" and "model.stp" referring to different parts.
2639 // TODO: Fix model handling in v7. Default models should only be STP.
2640 // Have option to override this in DISPLAY.
2641 if( getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false ) )
2642 {
2643 return true;
2644 }
2645 }
2646 }
2647
2648 // VRML models only work when exporting to glTF
2649 // Also OCCT < 7.9.0 fail to load most VRML 2.0 models because of Switch nodes
2650 if( m_outFmt == OUTPUT_FORMAT::FMT_OUT_GLTF )
2651 {
2652 if( readVRML( doc, aFileNameUTF8.c_str() ) )
2653 {
2654 Handle( XCAFDoc_ShapeTool ) shapeTool =
2655 XCAFDoc_DocumentTool::ShapeTool( doc->Main() );
2656
2657 prefixNames( shapeTool->Label(),
2658 TCollection_ExtendedString( baseName.c_str().AsChar() ) );
2659 }
2660 else
2661 {
2662 ReportMessage( wxString::Format( wxT( "readVRML() failed on filename '%s'.\n" ),
2663 fileName ) );
2664 return false;
2665 }
2666 }
2667 }
2668 else // Substitution is not allowed
2669 {
2670 if( aErrorMessage )
2671 aErrorMessage->Printf( wxT( "Cannot load any VRML model for this export.\n" ) );
2672
2673 return false;
2674 }
2675
2676 break;
2677
2678 // TODO: implement IDF and EMN converters
2679
2680 default:
2681 ReportMessage( wxString::Format( wxT( "Cannot identify actual file type for '%s'.\n" ),
2682 fileName ) );
2683 return false;
2684 }
2685
2686 aLabel = transferModel( doc, m_doc, aScale );
2687
2688 if( aLabel.IsNull() )
2689 {
2690 ReportMessage( wxString::Format( wxT( "Could not transfer model data from file '%s'.\n" ),
2691 fileName ) );
2692 return false;
2693 }
2694
2695 // attach the PART NAME ( base filename: note that in principle
2696 // different models may have the same base filename )
2697 wxFileName afile( fileName );
2698 std::string pname( afile.GetName().ToUTF8() );
2699 TCollection_ExtendedString partname( pname.c_str() );
2700 TDataStd_Name::Set( aLabel, partname );
2701
2702 m_models.insert( MODEL_DATUM( model_key, aLabel ) );
2703 ++m_components;
2704 return true;
2705}
2706
2707
2708bool STEP_PCB_MODEL::getModelLocation( bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset, VECTOR3D aOrientation,
2709 TopLoc_Location& aLocation )
2710{
2711 // Order of operations:
2712 // a. aOrientation is applied -Z*-Y*-X
2713 // b. aOffset is applied
2714 // Top ? add thickness to the Z offset
2715 // c. Bottom ? Rotate on X axis (in contrast to most ECAD which mirror on Y),
2716 // then rotate on +Z
2717 // Top ? rotate on -Z
2718 // d. aPosition is applied
2719 //
2720 // Note: Y axis is inverted in KiCad
2721
2722 gp_Trsf lPos;
2723 lPos.SetTranslation( gp_Vec( aPosition.x, -aPosition.y, 0.0 ) );
2724
2725 // Offset board thickness
2726 aOffset.z += BOARD_OFFSET;
2727
2728 double boardThickness;
2729 double boardZPos;
2730 getBoardBodyZPlacement( boardZPos, boardThickness );
2731 double top = std::max( boardZPos, boardZPos + boardThickness );
2732 double bottom = std::min( boardZPos, boardZPos + boardThickness );
2733
2734 // 3D step models are placed on the top of copper layers.
2735 // This is true for SMD shapes, and perhaps not always true for TH shapes,
2736 // but we use this Z position for any 3D shape.
2737 double f_pos, f_thickness;
2738 getLayerZPlacement( F_Cu, f_pos, f_thickness );
2739 top += f_thickness;
2740 getLayerZPlacement( B_Cu, f_pos, f_thickness );
2741 bottom += f_thickness; // f_thickness is < 0 for B_Cu layer
2742
2743 gp_Trsf lRot;
2744
2745 if( aBottom )
2746 {
2747 aOffset.z -= bottom;
2748 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
2749 lPos.Multiply( lRot );
2750 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), M_PI );
2751 lPos.Multiply( lRot );
2752 }
2753 else
2754 {
2755 aOffset.z += top;
2756 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
2757 lPos.Multiply( lRot );
2758 }
2759
2760 gp_Trsf lOff;
2761 lOff.SetTranslation( gp_Vec( aOffset.x, aOffset.y, aOffset.z ) );
2762 lPos.Multiply( lOff );
2763
2764 gp_Trsf lOrient;
2765 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ),
2766 -aOrientation.z );
2767 lPos.Multiply( lOrient );
2768 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 1.0, 0.0 ) ),
2769 -aOrientation.y );
2770 lPos.Multiply( lOrient );
2771 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ),
2772 -aOrientation.x );
2773 lPos.Multiply( lOrient );
2774
2775 aLocation = TopLoc_Location( lPos );
2776 return true;
2777}
2778
2779
2780bool STEP_PCB_MODEL::readIGES( Handle( TDocStd_Document )& doc, const char* fname )
2781{
2782 IGESControl_Controller::Init();
2783 IGESCAFControl_Reader reader;
2784 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
2785
2786 if( stat != IFSelect_RetDone )
2787 return false;
2788
2789 // Enable user-defined shape precision
2790 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
2791 return false;
2792
2793 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
2794 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
2795 return false;
2796
2797 // set other translation options
2798 reader.SetColorMode( true ); // use model colors
2799 reader.SetNameMode( false ); // don't use IGES label names
2800 reader.SetLayerMode( false ); // ignore LAYER data
2801
2802 if( !reader.Transfer( doc ) )
2803 {
2804 if( doc->CanClose() == CDM_CCS_OK )
2805 doc->Close();
2806
2807 return false;
2808 }
2809
2810 // are there any shapes to translate?
2811 if( reader.NbShapes() < 1 )
2812 {
2813 if( doc->CanClose() == CDM_CCS_OK )
2814 doc->Close();
2815
2816 return false;
2817 }
2818
2819 return true;
2820}
2821
2822
2823bool STEP_PCB_MODEL::readSTEP( Handle( TDocStd_Document )& doc, const char* fname )
2824{
2825 STEPCAFControl_Reader reader;
2826 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
2827
2828 if( stat != IFSelect_RetDone )
2829 return false;
2830
2831 // Enable user-defined shape precision
2832 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
2833 return false;
2834
2835 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
2836 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
2837 return false;
2838
2839 // set other translation options
2840 reader.SetColorMode( true ); // use model colors
2841 reader.SetNameMode( true ); // use label names
2842 reader.SetLayerMode( false ); // ignore LAYER data
2843
2844 if( !reader.Transfer( doc ) )
2845 {
2846 if( doc->CanClose() == CDM_CCS_OK )
2847 doc->Close();
2848
2849 return false;
2850 }
2851
2852 // are there any shapes to translate?
2853 if( reader.NbRootsForTransfer() < 1 )
2854 {
2855 if( doc->CanClose() == CDM_CCS_OK )
2856 doc->Close();
2857
2858 return false;
2859 }
2860
2861 return true;
2862}
2863
2864
2865bool STEP_PCB_MODEL::readVRML( Handle( TDocStd_Document ) & doc, const char* fname )
2866{
2867#if OCC_VERSION_HEX >= 0x070700
2868 VrmlAPI_CafReader reader;
2869 RWMesh_CoordinateSystemConverter conv;
2870 conv.SetInputLengthUnit( 2.54 );
2871 reader.SetCoordinateSystemConverter( conv );
2872 reader.SetDocument( doc );
2873
2874 if( !reader.Perform( TCollection_AsciiString( fname ), Message_ProgressRange() ) )
2875 return false;
2876
2877 return true;
2878#else
2879 return false;
2880#endif
2881}
2882
2883
2884TDF_Label STEP_PCB_MODEL::transferModel( Handle( TDocStd_Document ) & source,
2885 Handle( TDocStd_Document ) & dest, VECTOR3D aScale )
2886{
2887 // transfer data from Source into a top level component of Dest
2888 // s_assy = shape tool for the source
2889 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( source->Main() );
2890
2891 // retrieve all free shapes within the assembly
2892 TDF_LabelSequence frshapes;
2893 s_assy->GetFreeShapes( frshapes );
2894
2895 // d_assy = shape tool for the destination
2896 Handle( XCAFDoc_ShapeTool ) d_assy = XCAFDoc_DocumentTool::ShapeTool( dest->Main() );
2897
2898 // create a new shape within the destination and set the assembly tool to point to it
2899 TDF_Label d_targetLabel = d_assy->NewShape();
2900
2901 if( frshapes.Size() == 1 )
2902 {
2903 TDocStd_XLinkTool link;
2904 link.Copy( d_targetLabel, frshapes.First() );
2905 }
2906 else
2907 {
2908 // Rare case
2909 for( TDF_Label& s_shapeLabel : frshapes )
2910 {
2911 TDF_Label d_component = d_assy->NewShape();
2912
2913 TDocStd_XLinkTool link;
2914 link.Copy( d_component, s_shapeLabel );
2915
2916 d_assy->AddComponent( d_targetLabel, d_component, TopLoc_Location() );
2917 }
2918 }
2919
2920 if( aScale.x != 1.0 || aScale.y != 1.0 || aScale.z != 1.0 )
2921 rescaleShapes( d_targetLabel, gp_XYZ( aScale.x, aScale.y, aScale.z ) );
2922
2923 return d_targetLabel;
2924}
2925
2926
2927bool STEP_PCB_MODEL::performMeshing( Handle( XCAFDoc_ShapeTool ) & aShapeTool )
2928{
2929 TDF_LabelSequence freeShapes;
2930 aShapeTool->GetFreeShapes( freeShapes );
2931
2932 ReportMessage( wxT( "Meshing model\n" ) );
2933
2934 // GLTF is a mesh format, we have to trigger opencascade to mesh the shapes we composited into the asesmbly
2935 // To mesh models, lets just grab the free shape root and execute on them
2936 for( Standard_Integer i = 1; i <= freeShapes.Length(); ++i )
2937 {
2938 TDF_Label label = freeShapes.Value( i );
2939 TopoDS_Shape shape;
2940 aShapeTool->GetShape( label, shape );
2941
2942 // These deflection values basically affect the accuracy of the mesh generated, a tighter
2943 // deflection will result in larger meshes
2944 // We could make this a tunable parameter, but for now fix it
2945 const Standard_Real linearDeflection = 0.14;
2946 const Standard_Real angularDeflection = DEG2RAD( 30.0 );
2947 BRepMesh_IncrementalMesh mesh( shape, linearDeflection, Standard_False, angularDeflection,
2948 Standard_True );
2949 }
2950
2951 return true;
2952}
2953
2954
2955bool STEP_PCB_MODEL::WriteGLTF( const wxString& aFileName )
2956{
2957 if( !isBoardOutlineValid() )
2958 {
2959 ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
2960 "'%s'.\n" ),
2961 aFileName ) );
2962 return false;
2963 }
2964
2965 m_outFmt = OUTPUT_FORMAT::FMT_OUT_GLTF;
2966
2967 performMeshing( m_assy );
2968
2969 wxFileName fn( aFileName );
2970
2971 const char* tmpGltfname = "$tempfile$.glb";
2972 RWGltf_CafWriter cafWriter( tmpGltfname, true );
2973
2974 cafWriter.SetTransformationFormat( RWGltf_WriterTrsfFormat_Compact );
2975 cafWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit( 0.001 );
2976 cafWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(
2977 RWMesh_CoordinateSystem_Zup );
2978#if OCC_VERSION_HEX >= 0x070700
2979 cafWriter.SetParallel( true );
2980#endif
2981 TColStd_IndexedDataMapOfStringString metadata;
2982
2983 metadata.Add( TCollection_AsciiString( "pcb_name" ),
2984 TCollection_ExtendedString( fn.GetName().wc_str() ) );
2985 metadata.Add( TCollection_AsciiString( "source_pcb_file" ),
2986 TCollection_ExtendedString( fn.GetFullName().wc_str() ) );
2987 metadata.Add( TCollection_AsciiString( "generator" ),
2988 TCollection_AsciiString( wxString::Format( wxS( "KiCad %s" ), GetSemanticVersion() ).ToAscii() ) );
2989 metadata.Add( TCollection_AsciiString( "generated_at" ),
2990 TCollection_AsciiString( GetISO8601CurrentDateTime().ToAscii() ) );
2991
2992 bool success = true;
2993
2994 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
2995 wxString currCWD = wxGetCwd();
2996 wxString workCWD = fn.GetPath();
2997
2998 if( !workCWD.IsEmpty() )
2999 wxSetWorkingDirectory( workCWD );
3000
3001 success = cafWriter.Perform( m_doc, metadata, Message_ProgressRange() );
3002
3003 if( success )
3004 {
3005 // Preserve the permissions of the current file
3006 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpGltfname );
3007
3008 if( !wxRenameFile( tmpGltfname, fn.GetFullName(), true ) )
3009 {
3010 ReportMessage( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
3011 tmpGltfname, fn.GetFullName() ) );
3012 success = false;
3013 }
3014 }
3015
3016 wxSetWorkingDirectory( currCWD );
3017
3018 return success;
3019}
3020
3021
3022bool STEP_PCB_MODEL::WritePLY( const wxString& aFileName )
3023{
3024#if OCC_VERSION_HEX < 0x070700
3025 ReportMessage( wxT( "PLY export is not supported before OCCT 7.7.0\n" ) );
3026 return false;
3027#else
3028
3029 if( !isBoardOutlineValid() )
3030 {
3031 ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
3032 "'%s'.\n" ),
3033 aFileName ) );
3034 return false;
3035 }
3036
3037 m_outFmt = OUTPUT_FORMAT::FMT_OUT_PLY;
3038
3039 performMeshing( m_assy );
3040
3041 wxFileName fn( aFileName );
3042
3043 const char* tmpFname = "$tempfile$.ply";
3044 RWPly_CafWriter cafWriter( tmpFname );
3045
3046 cafWriter.SetFaceId( true ); // TODO: configurable SetPartId/SetFaceId
3047 cafWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit( 0.001 );
3048 cafWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(
3049 RWMesh_CoordinateSystem_Zup );
3050
3051 TColStd_IndexedDataMapOfStringString metadata;
3052
3053 metadata.Add( TCollection_AsciiString( "pcb_name" ),
3054 TCollection_ExtendedString( fn.GetName().wc_str() ) );
3055 metadata.Add( TCollection_AsciiString( "source_pcb_file" ),
3056 TCollection_ExtendedString( fn.GetFullName().wc_str() ) );
3057 metadata.Add( TCollection_AsciiString( "generator" ),
3058 TCollection_AsciiString(
3059 wxString::Format( wxS( "KiCad %s" ), GetSemanticVersion() ).ToAscii() ) );
3060 metadata.Add( TCollection_AsciiString( "generated_at" ),
3061 TCollection_AsciiString( GetISO8601CurrentDateTime().ToAscii() ) );
3062
3063 bool success = true;
3064
3065 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
3066 wxString currCWD = wxGetCwd();
3067 wxString workCWD = fn.GetPath();
3068
3069 if( !workCWD.IsEmpty() )
3070 wxSetWorkingDirectory( workCWD );
3071
3072 success = cafWriter.Perform( m_doc, metadata, Message_ProgressRange() );
3073
3074 if( success )
3075 {
3076 // Preserve the permissions of the current file
3077 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname );
3078
3079 if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
3080 {
3081 ReportMessage( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
3082 tmpFname, fn.GetFullName() ) );
3083 success = false;
3084 }
3085 }
3086
3087 wxSetWorkingDirectory( currCWD );
3088
3089 return success;
3090#endif
3091}
3092
3093
3094bool STEP_PCB_MODEL::WriteSTL( const wxString& aFileName )
3095{
3096 if( !isBoardOutlineValid() )
3097 {
3098 ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
3099 "'%s'.\n" ),
3100 aFileName ) );
3101 return false;
3102 }
3103
3104 m_outFmt = OUTPUT_FORMAT::FMT_OUT_STL;
3105
3106 performMeshing( m_assy );
3107
3108 wxFileName fn( aFileName );
3109
3110 const char* tmpFname = "$tempfile$.stl";
3111
3112 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
3113 wxString currCWD = wxGetCwd();
3114 wxString workCWD = fn.GetPath();
3115
3116 if( !workCWD.IsEmpty() )
3117 wxSetWorkingDirectory( workCWD );
3118
3119 bool success = StlAPI_Writer().Write( getOneShape( m_assy ), tmpFname );
3120
3121 if( success )
3122 {
3123 // Preserve the permissions of the current file
3124 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname );
3125
3126 if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
3127 {
3128 ReportMessage( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
3129 tmpFname, fn.GetFullName() ) );
3130 success = false;
3131 }
3132 }
3133
3134 wxSetWorkingDirectory( currCWD );
3135
3136 return success;
3137}
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:298
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:631
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:420
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition: lset.h:62
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:1876
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
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 AddHole(const SHAPE_SEGMENT &aShape, int aPlatingThickness, PCB_LAYER_ID aLayerTop, PCB_LAYER_ID aLayerBot, bool aVia, const VECTOR2D &aOrigin, bool aCutCopper, bool aCutBody)
bool 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:632
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition: layer_ids.h:655
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