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