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