KiCad PCB EDA Suite
Loading...
Searching...
No Matches
step_pcb_model.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2022 Mark Roszko <[email protected]>
5 * Copyright (C) 2016 Cirilo Bernardo <[email protected]>
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26#include <algorithm>
27#include <cmath>
28#include <functional>
29#include <sstream>
30#include <string>
31#include <utility>
32#include <wx/file.h>
33#include <wx/filename.h>
34#include <wx/filefn.h>
35#include <wx/sstream.h>
36#include <wx/stdpaths.h>
37#include <wx/stream.h>
38#include <wx/string.h>
39#include <wx/zstream.h>
40#include <wx/wfstream.h>
41#include <wx/zipstrm.h>
42#include <wx/stdstream.h>
43#include <wx/crt.h>
44
45#include <decompress.hpp>
46
47#include <thread_pool.h>
48#include <trace_helpers.h>
49#include <board.h>
51#include <footprint.h>
52#include <pad.h>
53#include <pcb_track.h>
54#include <kiplatform/io.h>
55#include <string_utils.h>
56#include <build_version.h>
61#include <reporter.h>
62
64
66#include <pcb_painter.h>
67
68#include "step_pcb_model.h"
69#include "streamwrapper.h"
70
71#include <IGESCAFControl_Reader.hxx>
72#include <IGESCAFControl_Writer.hxx>
73#include <IGESControl_Controller.hxx>
74#include <IGESData_GlobalSection.hxx>
75#include <IGESData_IGESModel.hxx>
76#include <Interface_Static.hxx>
77#include <Quantity_Color.hxx>
78#include <STEPCAFControl_Reader.hxx>
79#include <STEPCAFControl_Writer.hxx>
80#include <APIHeaderSection_MakeHeader.hxx>
81#include <Standard_Failure.hxx>
82#include <Standard_Handle.hxx>
83#include <Standard_Version.hxx>
84#include <TCollection_ExtendedString.hxx>
85#include <TDocStd_Document.hxx>
86#include <TDocStd_XLinkTool.hxx>
87#include <TDataStd_Name.hxx>
88#include <TDataStd_TreeNode.hxx>
89#include <TDF_ChildIterator.hxx>
90#include <TDF_LabelSequence.hxx>
91#include <TDF_Tool.hxx>
92#include <TopExp_Explorer.hxx>
93#include <TopoDS.hxx>
94#include <XCAFApp_Application.hxx>
95#include <XCAFDoc.hxx>
96#include <XCAFDoc_DocumentTool.hxx>
97#include <XCAFDoc_ColorTool.hxx>
98#include <XCAFDoc_ShapeTool.hxx>
99#include <XCAFDoc_VisMaterialTool.hxx>
100#include <XCAFDoc_Area.hxx>
101#include <XCAFDoc_Centroid.hxx>
102#include <XCAFDoc_Location.hxx>
103#include <XCAFDoc_Volume.hxx>
104#include "kicad3d_info.h"
105
106#include "KI_XCAFDoc_AssemblyGraph.hxx"
107
108#include <BRep_Tool.hxx>
109#include <BRepMesh_IncrementalMesh.hxx>
110#include <BRepBuilderAPI_GTransform.hxx>
111#include <BRepBuilderAPI_MakeEdge.hxx>
112#include <BRepBuilderAPI_MakeWire.hxx>
113#include <BRepBuilderAPI_MakeFace.hxx>
114#include <BRepExtrema_DistShapeShape.hxx>
115#include <BRepPrimAPI_MakeCone.hxx>
116#include <BRepPrimAPI_MakeCylinder.hxx>
117#include <BRepPrimAPI_MakePrism.hxx>
118#include <BRepTools.hxx>
119#include <BRepLib_MakeWire.hxx>
120#include <BRepAdaptor_Surface.hxx>
121#include <BRepAlgoAPI_Check.hxx>
122#include <BRepAlgoAPI_Cut.hxx>
123#include <BRepAlgoAPI_Fuse.hxx>
124#include <ShapeUpgrade_UnifySameDomain.hxx>
125
126#include <BRepBndLib.hxx>
127#include <Bnd_BoundSortBox.hxx>
128#include <GProp_GProps.hxx>
129#include <BRepGProp.hxx>
130
131#include <Geom_Curve.hxx>
132#include <Geom_TrimmedCurve.hxx>
133
134#include <gp_Ax2.hxx>
135#include <gp_Dir.hxx>
136#include <gp_Pnt.hxx>
137#include <GC_MakeArcOfCircle.hxx>
138#include <GC_MakeCircle.hxx>
139
140#include <RWGltf_CafWriter.hxx>
141#include <StlAPI_Writer.hxx>
142
143#if OCC_VERSION_HEX >= 0x070700
144#include <VrmlAPI_CafReader.hxx>
145#include <RWPly_CafWriter.hxx>
146#endif
147
148#include <macros.h>
150
151static constexpr double USER_PREC = 1e-4;
152static constexpr double USER_ANGLE_PREC = 1e-6;
153
154// nominal offset from the board
155static constexpr double BOARD_OFFSET = 0.05;
156
157// supported file types for 3D models
169
170
171MODEL3D_FORMAT_TYPE fileType( const char* aFileName )
172{
173 wxFileName lfile( wxString::FromUTF8Unchecked( aFileName ) );
174
175 if( !lfile.FileExists() )
176 return FMT_NONE;
177
178 wxString ext = lfile.GetExt().Lower();
179
180 if( ext == wxT( "wrl" ) )
181 return FMT_WRL;
182
183 if( ext == wxT( "wrz" ) )
184 return FMT_WRZ;
185
186 if( ext == wxT( "idf" ) )
187 return FMT_IDF; // component outline
188
189 if( ext == wxT( "emn" ) )
190 return FMT_EMN; // PCB assembly
191
192 if( ext == wxT( "stpz" ) || ext == wxT( "gz" ) )
193 return FMT_STEPZ;
194
195 OPEN_ISTREAM( ifile, aFileName );
196
197 if( ifile.fail() )
198 return FMT_NONE;
199
200 char iline[82];
201 MODEL3D_FORMAT_TYPE format_type = FMT_NONE;
202
203 // The expected header should be the first line.
204 // However some files can have a comment at the beginning of the file
205 // So read up to max_line_count lines to try to find the actual header
206 const int max_line_count = 3;
207
208 for( int ii = 0; ii < max_line_count; ii++ )
209 {
210 memset( iline, 0, 82 );
211 ifile.getline( iline, 82 );
212
213 iline[81] = 0; // ensure NULL termination when string is too long
214
215 // check for STEP in Part 21 format
216 // (this can give false positives since Part 21 is not exclusively STEP)
217 if( !strncmp( iline, "ISO-10303-21;", 13 ) )
218 {
219 format_type = FMT_STEP;
220 break;
221 }
222
223 std::string fstr = iline;
224
225 // check for STEP in XML format
226 // (this can give both false positive and false negatives)
227 if( fstr.find( "urn:oid:1.0.10303." ) != std::string::npos )
228 {
229 format_type = FMT_STEP;
230 break;
231 }
232
233 // Note: this is a very simple test which can yield false positives; the only
234 // sure method for determining if a file *not* an IGES model is to attempt
235 // to load it.
236 if( iline[72] == 'S' && ( iline[80] == 0 || iline[80] == 13 || iline[80] == 10 ) )
237 {
238 format_type = FMT_IGES;
239 break;
240 }
241
242 // Only a comment (starting by "/*") is allowed as header
243 if( strncmp( iline, "/*", 2 ) != 0 ) // not a comment
244 break;
245 }
246
247 CLOSE_STREAM( ifile );
248
249 return format_type;
250}
251
252
254 const VECTOR2D& p3 )
255{
257
258 // Move coordinate origin to p2, to simplify calculations
259 VECTOR2D b = p1 - p2;
260 VECTOR2D d = p3 - p2;
261 double bc = ( b.x * b.x + b.y * b.y ) / 2.0;
262 double cd = ( -d.x * d.x - d.y * d.y ) / 2.0;
263 double det = -b.x * d.y + d.x * b.y;
264
265 // We're fine with divisions by 0
266 det = 1.0 / det;
267 center.x = ( -bc * d.y - cd * b.y ) * det;
268 center.y = ( b.x * cd + d.x * bc ) * det;
269 center += p2;
270
271 return center;
272}
273
274
275#define APPROX_DBG( stmt )
276//#define APPROX_DBG( stmt ) stmt
277
279{
280 // An algo that takes 3 points, calculates a circle center,
281 // then tries to find as many points fitting the circle.
282
283 static const double c_radiusDeviation = 1000.0;
284 static const double c_arcCenterDeviation = 1000.0;
285 static const double c_relLengthDeviation = 0.8;
286 static const int c_last_none = -1000; // Meaning the arc cannot be constructed
287 // Allow larger angles for segments below this size
288 static const double c_smallSize = pcbIUScale.mmToIU( 0.1 );
289 static const double c_circleCloseGap = pcbIUScale.mmToIU( 1.0 );
290
291 APPROX_DBG( std::cout << std::endl );
292
293 if( aSrc.PointCount() < 4 )
294 return aSrc;
295
296 if( !aSrc.IsClosed() )
297 return aSrc; // non-closed polygons are not supported
298
300
301 int jEndIdx = aSrc.PointCount() - 3;
302
303 for( int i = 0; i < aSrc.PointCount(); i++ )
304 {
305 int first = i - 3;
306 int last = c_last_none;
307
308 VECTOR2D p0 = aSrc.CPoint( i - 3 );
309 VECTOR2D p1 = aSrc.CPoint( i - 2 );
310 VECTOR2D p2 = aSrc.CPoint( i - 1 );
311
312 APPROX_DBG( std::cout << i << " " << aSrc.CPoint( i ) << " " << ( i - 3 ) << " "
313 << VECTOR2I( p0 ) << " " << ( i - 2 ) << " " << VECTOR2I( p1 ) << " "
314 << ( i - 1 ) << " " << VECTOR2I( p2 ) << std::endl );
315
316 VECTOR2D v01 = p1 - p0;
317 VECTOR2D v12 = p2 - p1;
318
319 bool defective = false;
320
321 double d01 = v01.EuclideanNorm();
322 double d12 = v12.EuclideanNorm();
323
324 // Check distance differences between 3 first points
325 defective |= std::abs( d01 - d12 ) > ( std::max( d01, d12 ) * c_relLengthDeviation );
326
327 if( !defective )
328 {
329 // Check angles between 3 first points
330 EDA_ANGLE a01( v01 );
331 EDA_ANGLE a12( v12 );
332
333 double a_diff = ( a01 - a12 ).Normalize180().AsDegrees();
334 defective |= std::abs( a_diff ) < 0.1;
335
336 // Larger angles are allowed for smaller geometry
337 double maxAngleDiff = std::max( d01, d12 ) < c_smallSize ? 46.0 : 30.0;
338 defective |= std::abs( a_diff ) >= maxAngleDiff;
339 }
340
341 if( !defective )
342 {
343 // Find last point lying on the circle created from 3 first points
345 double radius = ( p0 - center ).EuclideanNorm();
346 VECTOR2D p_prev = p2;
347 EDA_ANGLE a_prev( v12 );
348
349 for( int j = i; j <= jEndIdx; j++ )
350 {
351 VECTOR2D p_test = aSrc.CPoint( j );
352
353 EDA_ANGLE a_test( p_test - p_prev );
354 double rad_test = ( p_test - center ).EuclideanNorm();
355 double d_tl = ( p_test - p_prev ).EuclideanNorm();
356 double rad_dev = std::abs( radius - rad_test );
357
358 APPROX_DBG( std::cout << " " << j << " " << aSrc.CPoint( j ) << " rad "
359 << int64_t( rad_test ) << " ref " << int64_t( radius )
360 << std::endl );
361
362 if( rad_dev > c_radiusDeviation )
363 {
364 APPROX_DBG( std::cout << " " << j
365 << " Radius deviation too large: " << int64_t( rad_dev )
366 << " > " << c_radiusDeviation << std::endl );
367 break;
368 }
369
370 // Larger angles are allowed for smaller geometry
371 double maxAngleDiff =
372 std::max( std::max( d01, d12 ), d_tl ) < c_smallSize ? 46.0 : 30.0;
373
374 double a_diff_test = ( a_prev - a_test ).Normalize180().AsDegrees();
375 if( std::abs( a_diff_test ) >= maxAngleDiff )
376 {
377 APPROX_DBG( std::cout << " " << j << " Angles differ too much " << a_diff_test
378 << std::endl );
379 break;
380 }
381
382 if( std::abs( d_tl - d01 ) > ( std::max( d_tl, d01 ) * c_relLengthDeviation ) )
383 {
384 APPROX_DBG( std::cout << " " << j << " Lengths differ too much " << d_tl
385 << "; " << d01 << std::endl );
386 break;
387 }
388
389 last = j;
390 p_prev = p_test;
391 a_prev = a_test;
392 }
393 }
394
395 if( last != c_last_none )
396 {
397 // Try to add an arc, testing for self-interference
398 SHAPE_ARC arc( aSrc.CPoint( first ), aSrc.CPoint( ( first + last ) / 2 ),
399 aSrc.CPoint( last ), 0 );
400
401 if( last > aSrc.PointCount() - 3 && !dst.IsArcSegment( 0 ) )
402 {
403 // If we've found an arc at the end, but already added segments at the start, remove them.
404 int toRemove = last - ( aSrc.PointCount() - 3 );
405
406 while( toRemove )
407 {
408 dst.RemoveShape( 0 );
409 toRemove--;
410 }
411 }
412
413 SHAPE_LINE_CHAIN testChain = dst;
414
415 testChain.Append( arc );
416 testChain.Append( aSrc.Slice( last, std::max( last, aSrc.PointCount() - 3 ) ) );
417 testChain.SetClosed( aSrc.IsClosed() );
418
419 if( !testChain.SelfIntersectingWithArcs() )
420 {
421 // Add arc
422 dst.Append( arc );
423
424 APPROX_DBG( std::cout << " Add arc start " << arc.GetP0() << " mid "
425 << arc.GetArcMid() << " end " << arc.GetP1() << std::endl );
426
427 i = last + 3;
428 }
429 else
430 {
431 // Self-interference
432 last = c_last_none;
433
434 APPROX_DBG( std::cout << " Self-intersection check failed" << std::endl );
435 }
436 }
437
438 if( last == c_last_none )
439 {
440 if( first < 0 )
441 jEndIdx = first + aSrc.PointCount();
442
443 // Add point
444 dst.Append( p0 );
445 APPROX_DBG( std::cout << " Add pt " << VECTOR2I( p0 ) << std::endl );
446 }
447 }
448
449 dst.SetClosed( true );
450
451 // Try to merge arcs
452 int iarc0 = dst.ArcIndex( 0 );
453 int iarc1 = dst.ArcIndex( dst.GetSegmentCount() - 1 );
454
455 if( iarc0 != -1 && iarc1 != -1 )
456 {
457 APPROX_DBG( std::cout << "Final arcs " << iarc0 << " " << iarc1 << std::endl );
458
459 if( iarc0 == iarc1 )
460 {
461 SHAPE_ARC arc = dst.Arc( iarc0 );
462
463 VECTOR2D p0 = arc.GetP0();
464 VECTOR2D p1 = arc.GetP1();
465
466 // If we have only one arc and the gap is small, make it a circle
467 if( ( p1 - p0 ).EuclideanNorm() < c_circleCloseGap )
468 {
469 dst.Clear();
470 dst.Append( SHAPE_ARC( arc.GetCenter(), arc.GetP0(), ANGLE_360 ) );
471 }
472 }
473 else
474 {
475 // Merge first and last arcs if they are similar
476 SHAPE_ARC arc0 = dst.Arc( iarc0 );
477 SHAPE_ARC arc1 = dst.Arc( iarc1 );
478
479 VECTOR2D ac0 = arc0.GetCenter();
480 VECTOR2D ac1 = arc1.GetCenter();
481
482 double ar0 = arc0.GetRadius();
483 double ar1 = arc1.GetRadius();
484
485 if( std::abs( ar0 - ar1 ) <= c_radiusDeviation
486 && ( ac0 - ac1 ).EuclideanNorm() <= c_arcCenterDeviation )
487 {
488 dst.RemoveShape( 0 );
489 dst.RemoveShape( -1 );
490
491 SHAPE_ARC merged( arc1.GetP0(), arc1.GetArcMid(), arc0.GetP1(), 0 );
492
493 dst.Append( merged );
494 }
495 }
496 }
497
498 return dst;
499}
500
501
502static TopoDS_Shape getOneShape( Handle( XCAFDoc_ShapeTool ) aShapeTool )
503{
504 TDF_LabelSequence theLabels;
505 aShapeTool->GetFreeShapes( theLabels );
506
507 TopoDS_Shape aShape;
508
509 if( theLabels.Length() == 1 )
510 return aShapeTool->GetShape( theLabels.Value( 1 ) );
511
512 TopoDS_Compound aCompound;
513 BRep_Builder aBuilder;
514 aBuilder.MakeCompound( aCompound );
515
516 for( TDF_LabelSequence::Iterator anIt( theLabels ); anIt.More(); anIt.Next() )
517 {
518 TopoDS_Shape aFreeShape;
519
520 if( !aShapeTool->GetShape( anIt.Value(), aFreeShape ) )
521 continue;
522
523 aBuilder.Add( aCompound, aFreeShape );
524 }
525
526 if( aCompound.NbChildren() > 0 )
527 aShape = aCompound;
528
529 return aShape;
530}
531
532
533// Apply scaling to shapes within theLabel.
534// Based on XCAFDoc_Editor::RescaleGeometry
535static Standard_Boolean rescaleShapes( const TDF_Label& theLabel, const gp_XYZ& aScale )
536{
537 if( theLabel.IsNull() )
538 {
539 Message::SendFail( "Null label." );
540 return Standard_False;
541 }
542
543 if( Abs( aScale.X() ) <= gp::Resolution() || Abs( aScale.Y() ) <= gp::Resolution()
544 || Abs( aScale.Z() ) <= gp::Resolution() )
545 {
546 Message::SendFail( "Scale factor is too small." );
547 return Standard_False;
548 }
549
550 Handle( XCAFDoc_ShapeTool ) aShapeTool = XCAFDoc_DocumentTool::ShapeTool( theLabel );
551
552 if( aShapeTool.IsNull() )
553 {
554 Message::SendFail( "Couldn't find XCAFDoc_ShapeTool attribute." );
555 return Standard_False;
556 }
557
558 Handle( KI_XCAFDoc_AssemblyGraph ) aG = new KI_XCAFDoc_AssemblyGraph( theLabel );
559
560 if( aG.IsNull() )
561 {
562 Message::SendFail( "Couldn't create assembly graph." );
563 return Standard_False;
564 }
565
566 Standard_Boolean anIsDone = Standard_True;
567
568 // clang-format off
569 gp_GTrsf aGTrsf;
570 aGTrsf.SetVectorialPart( gp_Mat( aScale.X(), 0, 0,
571 0, aScale.Y(), 0,
572 0, 0, aScale.Z() ) );
573 // clang-format on
574
575 BRepBuilderAPI_GTransform aBRepTrsf( aGTrsf );
576
577 for( Standard_Integer idx = 1; idx <= aG->NbNodes(); idx++ )
578 {
579 const KI_XCAFDoc_AssemblyGraph::NodeType aNodeType = aG->GetNodeType( idx );
580
581 if( ( aNodeType != KI_XCAFDoc_AssemblyGraph::NodeType_Part )
582 && ( aNodeType != KI_XCAFDoc_AssemblyGraph::NodeType_Occurrence ) )
583 {
584 continue;
585 }
586
587 const TDF_Label& aLabel = aG->GetNode( idx );
588
589 if( aNodeType == KI_XCAFDoc_AssemblyGraph::NodeType_Part )
590 {
591 const TopoDS_Shape aShape = aShapeTool->GetShape( aLabel );
592 aBRepTrsf.Perform( aShape, Standard_True );
593 if( !aBRepTrsf.IsDone() )
594 {
595 Standard_SStream aSS;
596 TCollection_AsciiString anEntry;
597 TDF_Tool::Entry( aLabel, anEntry );
598 aSS << "Shape " << anEntry << " is not scaled!";
599 Message::SendFail( aSS.str().c_str() );
600 anIsDone = Standard_False;
601 return Standard_False;
602 }
603 TopoDS_Shape aScaledShape = aBRepTrsf.Shape();
604 aShapeTool->SetShape( aLabel, aScaledShape );
605
606 // Update sub-shapes
607 TDF_LabelSequence aSubshapes;
608 aShapeTool->GetSubShapes( aLabel, aSubshapes );
609 for( TDF_LabelSequence::Iterator anItSs( aSubshapes ); anItSs.More(); anItSs.Next() )
610 {
611 const TDF_Label& aLSs = anItSs.Value();
612 const TopoDS_Shape aSs = aShapeTool->GetShape( aLSs );
613 const TopoDS_Shape aSs1 = aBRepTrsf.ModifiedShape( aSs );
614 aShapeTool->SetShape( aLSs, aSs1 );
615 }
616
617 // These attributes will be recomputed eventually, but clear them just in case
618 aLabel.ForgetAttribute( XCAFDoc_Area::GetID() );
619 aLabel.ForgetAttribute( XCAFDoc_Centroid::GetID() );
620 aLabel.ForgetAttribute( XCAFDoc_Volume::GetID() );
621 }
622 else if( aNodeType == KI_XCAFDoc_AssemblyGraph::NodeType_Occurrence )
623 {
624 TopLoc_Location aLoc = aShapeTool->GetLocation( aLabel );
625 gp_Trsf aTrsf = aLoc.Transformation();
626 aTrsf.SetTranslationPart( aTrsf.TranslationPart().Multiplied( aScale ) );
627 XCAFDoc_Location::Set( aLabel, aTrsf );
628 }
629 }
630
631 if( !anIsDone )
632 {
633 return Standard_False;
634 }
635
636 aShapeTool->UpdateAssemblies();
637
638 return anIsDone;
639}
640
641
642static bool fuseShapes( auto& aInputShapes, TopoDS_Shape& aOutShape, REPORTER* aReporter )
643{
644 BRepAlgoAPI_Fuse mkFuse;
645 TopTools_ListOfShape shapeArguments, shapeTools;
646
647 for( TopoDS_Shape& sh : aInputShapes )
648 {
649 if( sh.IsNull() )
650 continue;
651
652 if( shapeArguments.IsEmpty() )
653 shapeArguments.Append( sh );
654 else
655 shapeTools.Append( sh );
656 }
657
658 mkFuse.SetRunParallel( true );
659 mkFuse.SetToFillHistory( false );
660 mkFuse.SetArguments( shapeArguments );
661 mkFuse.SetTools( shapeTools );
662 mkFuse.Build();
663
664 if( mkFuse.HasErrors() || mkFuse.HasWarnings() )
665 {
666 aReporter->Report( _( "** Got problems while fusing shapes **" ), RPT_SEVERITY_ERROR );
667
668 if( mkFuse.HasErrors() )
669 {
670 wxString msg = _( "Errors:\n" );
671 wxStringOutputStream os_stream( &msg );
672 wxStdOutputStream out( os_stream );
673
674 mkFuse.DumpErrors( out );
675 aReporter->Report( msg, RPT_SEVERITY_ERROR );
676 }
677
678 if( mkFuse.HasWarnings() )
679 {
680 wxString msg = _( "Warnings:\n" );
681 wxStringOutputStream os_stream( &msg );
682 wxStdOutputStream out( os_stream );
683
684 mkFuse.DumpWarnings( out );
685 aReporter->Report( msg, RPT_SEVERITY_WARNING );
686 }
687 }
688
689 if( mkFuse.IsDone() )
690 {
691 TopoDS_Shape fusedShape = mkFuse.Shape();
692
693 ShapeUpgrade_UnifySameDomain unify( fusedShape, true, true, false );
694 unify.History() = nullptr;
695 unify.Build();
696
697 TopoDS_Shape unifiedShapes = unify.Shape();
698
699 if( unifiedShapes.IsNull() )
700 {
701 aReporter->Report( _( "** ShapeUpgrade_UnifySameDomain produced a null shape **" ),
703 }
704 else
705 {
706 aOutShape = unifiedShapes;
707 return true;
708 }
709 }
710
711 return false;
712}
713
714
715static TopoDS_Compound makeCompound( const auto& aInputShapes )
716{
717 TopoDS_Compound compound;
718 BRep_Builder builder;
719 builder.MakeCompound( compound );
720
721 for( const TopoDS_Shape& shape : aInputShapes )
722 builder.Add( compound, shape );
723
724 return compound;
725}
726
727
728// Try to fuse shapes. If that fails, just add them to a compound
729static TopoDS_Shape fuseShapesOrCompound( const TopTools_ListOfShape& aInputShapes,
730 REPORTER* aReporter )
731{
732 TopoDS_Shape outShape;
733
734 if( aInputShapes.Size() == 1 )
735 return aInputShapes.First();
736
737 if( fuseShapes( aInputShapes, outShape, aReporter ) )
738 return outShape;
739
740 return makeCompound( aInputShapes );
741}
742
743
744// Sets names in assembly to <aPrefix> (<old name>), or to <aPrefix>
745static Standard_Boolean prefixNames( const TDF_Label& aLabel,
746 const TCollection_ExtendedString& aPrefix )
747{
748 Handle( KI_XCAFDoc_AssemblyGraph ) aG = new KI_XCAFDoc_AssemblyGraph( aLabel );
749
750 if( aG.IsNull() )
751 {
752 Message::SendFail( "Couldn't create assembly graph." );
753 return Standard_False;
754 }
755
756 Standard_Boolean anIsDone = Standard_True;
757
758 for( Standard_Integer idx = 1; idx <= aG->NbNodes(); idx++ )
759 {
760 const TDF_Label& lbl = aG->GetNode( idx );
761 Handle( TDataStd_Name ) nameHandle;
762
763 if( lbl.FindAttribute( TDataStd_Name::GetID(), nameHandle ) )
764 {
765 TCollection_ExtendedString name;
766
767 name += aPrefix;
768 name += " (";
769 name += nameHandle->Get();
770 name += ")";
771
772 TDataStd_Name::Set( lbl, name );
773 }
774 else
775 {
776 TDataStd_Name::Set( lbl, aPrefix );
777 }
778 }
779
780 return anIsDone;
781}
782
783
784STEP_PCB_MODEL::STEP_PCB_MODEL( const wxString& aPcbName, REPORTER* aReporter ) :
785 m_reporter( aReporter )
786{
787 m_app = XCAFApp_Application::GetApplication();
788 m_app->NewDocument( "MDTV-XCAF", m_doc );
789 m_assy = XCAFDoc_DocumentTool::ShapeTool( m_doc->Main() );
790 m_assy_label = m_assy->NewShape();
791 m_hasPCB = false;
792 m_simplifyShapes = true;
793 m_components = 0;
797 m_minx = 1.0e10; // absurdly large number; any valid PCB X value will be smaller
798 m_pcbName = aPcbName;
799 m_fuseShapes = false;
800 m_extraPadThickness = true;
802}
803
804
806{
807 if( m_doc->CanClose() == CDM_CCS_OK )
808 m_doc->Close();
809}
810
811
812bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin, bool aVia,
813 SHAPE_POLY_SET* aClipPolygon )
814{
815 const double c_padExtraThickness = 0.005;
816 bool success = true;
817 std::vector<TopoDS_Shape> padShapes;
818 bool castellated = aClipPolygon && aPad->GetProperty() == PAD_PROP::CASTELLATED;
819
820 for( PCB_LAYER_ID pcb_layer : aPad->GetLayerSet().Seq() )
821 {
822 if( !m_enabledLayers.Contains( pcb_layer ) )
823 continue;
824
825 if( pcb_layer == F_Mask || pcb_layer == B_Mask )
826 continue;
827
828 if( !aPad->FlashLayer( pcb_layer ) )
829 continue;
830
831 double Zpos, thickness;
832 getLayerZPlacement( pcb_layer, Zpos, thickness );
833
834 if( !aVia && m_extraPadThickness )
835 {
836 // Pad surface as a separate face for FEM simulations.
837 if( pcb_layer == F_Cu )
838 thickness += c_padExtraThickness;
839 else if( pcb_layer == B_Cu )
840 thickness -= c_padExtraThickness;
841 }
842
843 TopoDS_Shape testShape;
844
845 // Make a shape on copper layers
846 SHAPE_POLY_SET polySet;
847 aPad->TransformShapeToPolygon( polySet, pcb_layer, 0, aPad->GetMaxError(), ERROR_INSIDE );
848
849 if( castellated )
850 {
851 polySet.ClearArcs();
852 polySet.BooleanIntersection( *aClipPolygon );
853 }
854
855 success &= MakeShapes( padShapes, polySet, m_simplifyShapes, thickness, Zpos, aOrigin );
856
857 if( testShape.IsNull() )
858 {
859 std::vector<TopoDS_Shape> testShapes;
860
861 MakeShapes( testShapes, polySet, m_simplifyShapes, 0.0, Zpos + thickness, aOrigin );
862
863 if( testShapes.size() > 0 )
864 testShape = testShapes.front();
865 }
866
867 if( !aVia && m_extraPadThickness && !testShape.IsNull() )
868 {
869 if( pcb_layer == F_Cu || pcb_layer == B_Cu )
870 {
871 wxString name;
872
873 name << "Pad_";
874
875 if( pcb_layer == F_Cu )
876 name << 'F' << '_';
877 else if( pcb_layer == B_Cu )
878 name << 'B' << '_';
879
880 name << aPad->GetParentFootprint()->GetReferenceAsString() << '_'
881 << aPad->GetNumber() << '_' << aPad->GetShortNetname();
882
883 gp_Pnt point( pcbIUScale.IUTomm( aPad->GetX() - aOrigin.x ),
884 -pcbIUScale.IUTomm( aPad->GetY() - aOrigin.y ), Zpos + thickness );
885
886 m_pad_points[name].emplace_back( point, testShape );
887 }
888 }
889 }
890
891 if( aPad->GetAttribute() == PAD_ATTRIB::PTH && aPad->IsOnLayer( F_Cu ) && aPad->IsOnLayer( B_Cu ) )
892 {
893 double f_pos, f_thickness;
894 double b_pos, b_thickness;
895 getLayerZPlacement( F_Cu, f_pos, f_thickness );
896 getLayerZPlacement( B_Cu, b_pos, b_thickness );
897
898 if( !aVia && m_extraPadThickness )
899 {
900 // Pad surface is slightly thicker
901 f_thickness += c_padExtraThickness;
902 b_thickness -= c_padExtraThickness;
903 }
904
905 double top = std::max( f_pos, f_pos + f_thickness );
906 double bottom = std::min( b_pos, b_pos + b_thickness );
907 double hole_height = top - bottom;
908
909 TopoDS_Shape plating;
910
911 std::shared_ptr<SHAPE_SEGMENT> seg_hole = aPad->GetEffectiveHoleShape();
912 double width = std::min( aPad->GetDrillSize().x, aPad->GetDrillSize().y );
913
914 if( !castellated )
915 {
916 if( MakeShapeAsThickSegment( plating, seg_hole->GetSeg().A, seg_hole->GetSeg().B, width,
917 hole_height, bottom, aOrigin ) )
918 {
919 padShapes.push_back( plating );
920 }
921 else
922 {
923 success = false;
924 }
925 }
926 else
927 {
928 // Note:
929 // the truncated hole shape is exported as a vertical filled shape. The hole itself
930 // will be removed later, when all holes are removed from the board
931 SHAPE_POLY_SET polyHole;
932
933 if( seg_hole->GetSeg().A == seg_hole->GetSeg().B ) // Hole is a circle
934 {
935 TransformCircleToPolygon( polyHole, seg_hole->GetSeg().A, width/2,
936 aPad->GetMaxError(), ERROR_OUTSIDE );
937
938 }
939 else
940 {
941 TransformOvalToPolygon( polyHole, seg_hole->GetSeg().A, seg_hole->GetSeg().B, width,
942 aPad->GetMaxError(), ERROR_OUTSIDE );
943 }
944
945 polyHole.ClearArcs();
946 polyHole.BooleanIntersection( *aClipPolygon );
947
948 if( MakePolygonAsWall( plating, polyHole, hole_height, bottom, aOrigin ) )
949 {
950 padShapes.push_back( plating );
951 }
952 else
953 {
954 success = false;
955 }
956 }
957 }
958
959 if( !success ) // Error
960 m_reporter->Report( _( "OCC error adding pad/via polygon." ), RPT_SEVERITY_ERROR );
961
962 if( !padShapes.empty() )
963 {
964 // Fuse pad shapes here before fusing them with tracks because OCCT sometimes has trouble
965 if( m_fuseShapes )
966 {
967 TopTools_ListOfShape padShapesList;
968
969 for( const TopoDS_Shape& shape : padShapes )
970 padShapesList.Append( shape );
971
972 m_board_copper_pads[aPad->GetNetname()].push_back( fuseShapesOrCompound( padShapesList, m_reporter ) );
973 }
974 else
975 {
976 for( const TopoDS_Shape& shape : padShapes )
977 m_board_copper_pads[aPad->GetNetname()].push_back( shape );
978 }
979 }
980
981 return success;
982}
983
984
985bool STEP_PCB_MODEL::AddHole( const SHAPE_SEGMENT& aShape, int aPlatingThickness,
986 PCB_LAYER_ID aLayerTop, PCB_LAYER_ID aLayerBot, bool aVia,
987 const VECTOR2D& aOrigin, bool aCutCopper, bool aCutBody )
988{
989 double margin = 0.001; // a small margin on the Z axix to be sure the hole
990 // is bigger than the board with copper
991 // must be > OCC_MAX_DISTANCE_TO_MERGE_POINTS
992
993 // Pads are taller by 0.01 mm
994 if( !aVia && m_extraPadThickness)
995 margin += 0.01;
996
997 double f_pos, f_thickness;
998 double b_pos, b_thickness;
999 getLayerZPlacement( aLayerTop, f_pos, f_thickness );
1000 getLayerZPlacement( aLayerBot, b_pos, b_thickness );
1001 double top = std::max( f_pos, f_pos + f_thickness );
1002 double bottom = std::min( b_pos, b_pos + b_thickness );
1003
1004 double holeZsize = ( top - bottom ) + ( margin * 2 );
1005
1006 double boardDrill = aShape.GetWidth();
1007 double copperDrill = boardDrill - aPlatingThickness * 2;
1008
1009 TopoDS_Shape copperHole, boardHole;
1010
1011 if( aCutCopper )
1012 {
1013 if( MakeShapeAsThickSegment( copperHole, aShape.GetSeg().A, aShape.GetSeg().B, copperDrill,
1014 holeZsize, bottom - margin, aOrigin ) )
1015 {
1016 m_copperCutouts.push_back( copperHole );
1017 }
1018 else
1019 {
1020 return false;
1021 }
1022 }
1023
1024 if( aCutBody )
1025 {
1026 if( MakeShapeAsThickSegment( boardHole, aShape.GetSeg().A, aShape.GetSeg().B, boardDrill,
1027 holeZsize, bottom - margin, aOrigin ) )
1028 {
1029 m_boardCutouts.push_back( boardHole );
1030 }
1031 else
1032 {
1033 return false;
1034 }
1035 }
1036
1037 return true;
1038}
1039
1040
1042 PCB_LAYER_ID aLayerBot, bool aVia, const VECTOR2D& aOrigin,
1043 const wxString& aNetname )
1044{
1045 double f_pos, f_thickness;
1046 double b_pos, b_thickness;
1047 getLayerZPlacement( aLayerTop, f_pos, f_thickness );
1048 getLayerZPlacement( aLayerBot, b_pos, b_thickness );
1049 double top = std::max( f_pos, f_pos + f_thickness );
1050 double bottom = std::min( b_pos, b_pos + b_thickness );
1051
1052 TopoDS_Shape plating;
1053
1054 if( !MakeShapeAsThickSegment( plating, aShape.GetSeg().A, aShape.GetSeg().B, aShape.GetWidth(),
1055 ( top - bottom ), bottom, aOrigin ) )
1056 {
1057 return false;
1058 }
1059
1060 if( aVia )
1061 m_board_copper_vias[aNetname].push_back( plating );
1062 else
1063 m_board_copper_pads[aNetname].push_back( plating );
1064
1065 return true;
1066}
1067
1068
1070 PCB_LAYER_ID aLayerEnd, const VECTOR2D& aOrigin )
1071{
1072 // A backdrill removes board material and copper plating between two layers.
1073 // The backdrill typically starts from an outer layer and drills into an inner layer.
1074 // For example, a top backdrill starts at F_Cu and ends at an inner layer.
1075 // A bottom backdrill starts at B_Cu and ends at an inner layer.
1076
1077 double margin = 0.001; // a small margin on the Z axis to ensure the hole
1078 // is bigger than the board section being removed
1079
1080 // Extra margin to extend past outer copper layers to ensure complete annular ring removal
1081 double copperMargin = 0.5; // 0.5mm extra to cut through any copper/pad thickness
1082
1083 double start_pos, start_thickness;
1084 double end_pos, end_thickness;
1085 getLayerZPlacement( aLayerStart, start_pos, start_thickness );
1086 getLayerZPlacement( aLayerEnd, end_pos, end_thickness );
1087
1088 // Calculate the Z extent of the backdrill
1089 double top = std::max( { start_pos, start_pos + start_thickness,
1090 end_pos, end_pos + end_thickness } );
1091 double bottom = std::min( { start_pos, start_pos + start_thickness,
1092 end_pos, end_pos + end_thickness } );
1093
1094 // Extend past outer copper layers if the backdrill reaches them
1095 if( aLayerStart == F_Cu || aLayerEnd == F_Cu )
1096 top += copperMargin;
1097 if( aLayerStart == B_Cu || aLayerEnd == B_Cu )
1098 bottom -= copperMargin;
1099
1100 double holeZsize = ( top - bottom ) + ( margin * 2 );
1101 double holeZpos = bottom - margin;
1102
1103 double backdrillDiameter = aShape.GetWidth();
1104
1105 TopoDS_Shape backdrillHole;
1106
1107 // Create the backdrill hole shape - this cuts the board body
1108 if( MakeShapeAsThickSegment( backdrillHole, aShape.GetSeg().A, aShape.GetSeg().B,
1109 backdrillDiameter, holeZsize, holeZpos, aOrigin ) )
1110 {
1111 m_boardCutouts.push_back( backdrillHole );
1112
1113 // This removes annular rings and barrel copper between the backdrill layers.
1114 m_copperCutouts.push_back( backdrillHole );
1115 }
1116 else
1117 {
1118 return false;
1119 }
1120
1121 return true;
1122}
1123
1124
1125bool STEP_PCB_MODEL::AddCounterbore( const VECTOR2I& aPosition, int aDiameter, int aDepth,
1126 bool aFrontSide, const VECTOR2D& aOrigin )
1127{
1128 wxLogTrace( traceKiCad2Step, wxT( "AddCounterbore: pos=(%d,%d) diameter=%d depth=%d frontSide=%d origin=(%f,%f)" ),
1129 aPosition.x, aPosition.y, aDiameter, aDepth, aFrontSide ? 1 : 0, aOrigin.x, aOrigin.y );
1130
1131 // A counterbore is a cylindrical recess from the top or bottom of the board
1132 if( aDiameter <= 0 || aDepth <= 0 )
1133 {
1134 wxLogTrace( traceKiCad2Step, wxT( "AddCounterbore: REJECTED - invalid diameter=%d or depth=%d" ),
1135 aDiameter, aDepth );
1136 return false;
1137 }
1138
1139 double margin = 0.001; // small margin to ensure clean cuts
1140
1141 // Extra margin to extend past outer copper layers to ensure complete annular ring removal
1142 double copperMargin = 0.5; // 0.5mm extra to cut through any copper/pad thickness
1143
1144 // Get board body position (between copper layers)
1145 double boardZpos, boardThickness;
1146 getBoardBodyZPlacement( boardZpos, boardThickness );
1147
1148 // Get copper layer positions - these extend beyond the board body
1149 double f_pos, f_thickness, b_pos, b_thickness;
1150 getLayerZPlacement( F_Cu, f_pos, f_thickness );
1151 getLayerZPlacement( B_Cu, b_pos, b_thickness );
1152
1153 // Calculate actual outer surfaces including copper
1154 // F_Cu: f_pos is inner surface, f_pos + f_thickness is outer surface (copper extends upward)
1155 // B_Cu: b_pos is inner surface, b_pos + b_thickness is outer surface (thickness is negative, copper extends downward)
1156 double topOuterSurface = std::max( f_pos, f_pos + f_thickness );
1157 double bottomOuterSurface = std::min( b_pos, b_pos + b_thickness );
1158
1159 wxLogTrace( traceKiCad2Step, wxT( "AddCounterbore: boardZpos=%f boardThickness=%f f_pos=%f f_thickness=%f topOuter=%f bottomOuter=%f" ),
1160 boardZpos, boardThickness, f_pos, f_thickness, topOuterSurface, bottomOuterSurface );
1161
1162 // Convert dimensions to mm
1163 double diameter_mm = pcbIUScale.IUTomm( aDiameter );
1164 double depth_mm = pcbIUScale.IUTomm( aDepth );
1165 double radius_mm = diameter_mm / 2.0;
1166
1167 wxLogTrace( traceKiCad2Step, wxT( "AddCounterbore: diameter_mm=%f depth_mm=%f radius_mm=%f" ),
1168 diameter_mm, depth_mm, radius_mm );
1169
1170 // Calculate cylinder position based on which side
1171 // The cylinder must extend past the outer surface to ensure complete copper removal
1172 double cylinderZpos;
1173 double cylinderHeight;
1174
1175 if( aFrontSide )
1176 {
1177 // Counterbore from top - cylinder extends from above outer copper surface down to depth
1178 // Add copperMargin above the surface to ensure complete annular ring removal
1179 cylinderZpos = topOuterSurface - depth_mm - margin;
1180 cylinderHeight = depth_mm + copperMargin + 2 * margin;
1181 }
1182 else
1183 {
1184 // Counterbore from bottom - cylinder extends from below outer copper surface up to depth
1185 // Add copperMargin below the surface to ensure complete annular ring removal
1186 cylinderZpos = bottomOuterSurface - copperMargin - margin;
1187 cylinderHeight = depth_mm + copperMargin + 2 * margin;
1188 }
1189
1190 // Convert position to mm
1191 double posX_mm = pcbIUScale.IUTomm( aPosition.x - aOrigin.x );
1192 double posY_mm = -pcbIUScale.IUTomm( aPosition.y - aOrigin.y );
1193
1194 wxLogTrace( traceKiCad2Step, wxT( "AddCounterbore: posX_mm=%f posY_mm=%f cylinderZpos=%f cylinderHeight=%f" ),
1195 posX_mm, posY_mm, cylinderZpos, cylinderHeight );
1196
1197 try
1198 {
1199 // Create coordinate system for the cylinder
1200 // The cylinder axis is along Z, positioned at the counterbore center
1201 gp_Ax2 axis( gp_Pnt( posX_mm, posY_mm, cylinderZpos ), gp::DZ() );
1202
1203 TopoDS_Shape cylinder = BRepPrimAPI_MakeCylinder( axis, radius_mm, cylinderHeight );
1204
1205 if( cylinder.IsNull() )
1206 {
1207 wxLogTrace( traceKiCad2Step, wxT( "AddCounterbore: FAILED - cylinder shape is null" ) );
1208 m_reporter->Report( _( "Failed to create counterbore cylinder shape" ),
1210 return false;
1211 }
1212
1213 // Add to both board and copper cutouts
1214 m_boardCutouts.push_back( cylinder );
1215 m_copperCutouts.push_back( cylinder );
1216
1217 wxLogTrace( traceKiCad2Step, wxT( "AddCounterbore: SUCCESS - added cylinder. boardCutouts=%zu copperCutouts=%zu" ),
1218 m_boardCutouts.size(), m_copperCutouts.size() );
1219 }
1220 catch( const Standard_Failure& e )
1221 {
1222 wxLogTrace( traceKiCad2Step, wxT( "AddCounterbore: EXCEPTION - %s" ), e.GetMessageString() );
1223 m_reporter->Report( wxString::Format( _( "OCC exception creating counterbore: %s" ),
1224 e.GetMessageString() ),
1226 return false;
1227 }
1228
1229 return true;
1230}
1231
1232
1233bool STEP_PCB_MODEL::AddCountersink( const VECTOR2I& aPosition, int aDiameter, int aDepth,
1234 int aAngle, bool aFrontSide, const VECTOR2D& aOrigin )
1235{
1236 wxLogTrace( traceKiCad2Step, wxT( "AddCountersink: pos=(%d,%d) diameter=%d depth=%d angle=%d frontSide=%d origin=(%f,%f)" ),
1237 aPosition.x, aPosition.y, aDiameter, aDepth, aAngle, aFrontSide ? 1 : 0, aOrigin.x, aOrigin.y );
1238
1239 // A countersink is a conical recess from the top or bottom of the board
1240 // The angle parameter is the total cone angle in decidegrees
1241 // (angle between opposite sides of the cone)
1242 if( aDiameter <= 0 || aAngle <= 0 )
1243 {
1244 wxLogTrace( traceKiCad2Step, wxT( "AddCountersink: REJECTED - invalid diameter=%d or angle=%d" ),
1245 aDiameter, aAngle );
1246 return false;
1247 }
1248
1249 double margin = 0.001; // small margin to ensure clean cuts
1250
1251 // Extra margin to extend past outer copper layers to ensure complete annular ring removal
1252 double copperMargin = 0.5; // 0.5mm extra to cut through any copper/pad thickness
1253
1254 // Get board body position (between copper layers)
1255 double boardZpos, boardThickness;
1256 getBoardBodyZPlacement( boardZpos, boardThickness );
1257
1258 // Get copper layer positions - these extend beyond the board body
1259 double f_pos, f_thickness, b_pos, b_thickness;
1260 getLayerZPlacement( F_Cu, f_pos, f_thickness );
1261 getLayerZPlacement( B_Cu, b_pos, b_thickness );
1262
1263 // Calculate actual outer surfaces including copper
1264 double topOuterSurface = std::max( f_pos, f_pos + f_thickness );
1265 double bottomOuterSurface = std::min( b_pos, b_pos + b_thickness );
1266
1267 wxLogTrace( traceKiCad2Step, wxT( "AddCountersink: boardZpos=%f boardThickness=%f f_pos=%f f_thickness=%f topOuter=%f bottomOuter=%f" ),
1268 boardZpos, boardThickness, f_pos, f_thickness, topOuterSurface, bottomOuterSurface );
1269
1270 // Convert dimensions to mm
1271 double diameter_mm = pcbIUScale.IUTomm( aDiameter );
1272 double radius_mm = diameter_mm / 2.0;
1273
1274 // Convert angle from decidegrees to radians
1275 // aAngle is the total cone angle, so half-angle is used for geometry
1276 double halfAngleRad = ( aAngle / 10.0 ) * M_PI / 180.0 / 2.0;
1277
1278 // If depth is not specified, calculate it from the diameter and angle
1279 // The countersink depth is the full cone height: depth = radius / tan(halfAngle)
1280 double depth_mm;
1281 if( aDepth <= 0 )
1282 {
1283 // Calculate depth from diameter and angle
1284 depth_mm = radius_mm / tan( halfAngleRad );
1285 wxLogTrace( traceKiCad2Step, wxT( "AddCountersink: depth not specified, calculated depth_mm=%f from radius=%f and angle" ),
1286 depth_mm, radius_mm );
1287 }
1288 else
1289 {
1290 depth_mm = pcbIUScale.IUTomm( aDepth );
1291 }
1292
1293 wxLogTrace( traceKiCad2Step, wxT( "AddCountersink: diameter_mm=%f depth_mm=%f radius_mm=%f halfAngleRad=%f (deg=%f)" ),
1294 diameter_mm, depth_mm, radius_mm, halfAngleRad, halfAngleRad * 180.0 / M_PI );
1295
1296 // Calculate the cone geometry
1297 // For a countersink, R1 (bottom radius) may be 0 (sharp point) or non-zero
1298 // R2 (top radius) is at the surface
1299 // The cone depth determines how deep it goes
1300
1301 // Calculate bottom radius based on depth and angle
1302 // tan(halfAngle) = (R2 - R1) / depth
1303 // If we want the surface radius to be radius_mm and depth to be depth_mm:
1304 // R1 = R2 - depth * tan(halfAngle)
1305 double bottomRadius_mm = radius_mm - depth_mm * tan( halfAngleRad );
1306
1307 wxLogTrace( traceKiCad2Step, wxT( "AddCountersink: bottomRadius_mm=%f (before clamp), tan(halfAngle)=%f" ),
1308 bottomRadius_mm, tan( halfAngleRad ) );
1309
1310 if( bottomRadius_mm < 0 )
1311 bottomRadius_mm = 0; // Cone comes to a point before reaching full depth
1312
1313 // Calculate position based on which side
1314 // Extend the cone past the outer surface by copperMargin to ensure complete copper removal
1315 double coneZpos;
1316 double coneHeight = depth_mm + copperMargin + margin;
1317 double r1, r2; // bottom and top radii for BRepPrimAPI_MakeCone
1318
1319 // Convert position to mm
1320 double posX_mm = pcbIUScale.IUTomm( aPosition.x - aOrigin.x );
1321 double posY_mm = -pcbIUScale.IUTomm( aPosition.y - aOrigin.y );
1322
1323 try
1324 {
1325 TopoDS_Shape cone;
1326
1327 if( aFrontSide )
1328 {
1329 // Countersink from top - cone apex points down
1330 // In OCC, cone is built from z=0 to z=H with R1 at z=0 and R2 at z=H
1331 // For a top countersink, we want large radius at top, small at bottom
1332 coneZpos = topOuterSurface - depth_mm - margin;
1333 r1 = bottomRadius_mm; // smaller radius at bottom (deeper into board)
1334 // Extend the top radius to account for the copperMargin extension above the surface
1335 r2 = radius_mm + ( copperMargin + margin ) * tan( halfAngleRad );
1336
1337 wxLogTrace( traceKiCad2Step, wxT( "AddCountersink: FRONT - coneZpos=%f r1=%f r2=%f coneHeight=%f" ),
1338 coneZpos, r1, r2, coneHeight );
1339
1340 gp_Ax2 axis( gp_Pnt( posX_mm, posY_mm, coneZpos ), gp::DZ() );
1341 cone = BRepPrimAPI_MakeCone( axis, r1, r2, coneHeight );
1342 }
1343 else
1344 {
1345 // Countersink from bottom - cone apex points up
1346 // For bottom countersink, large radius at bottom, small at top
1347 // Extend below the surface by copperMargin
1348 coneZpos = bottomOuterSurface - copperMargin - margin;
1349 // Extend the bottom radius to account for the copperMargin extension below the surface
1350 r1 = radius_mm + ( copperMargin + margin ) * tan( halfAngleRad );
1351 r2 = bottomRadius_mm; // smaller radius at top (deeper into board)
1352
1353 wxLogTrace( traceKiCad2Step, wxT( "AddCountersink: BACK - coneZpos=%f r1=%f r2=%f coneHeight=%f" ),
1354 coneZpos, r1, r2, coneHeight );
1355
1356 gp_Ax2 axis( gp_Pnt( posX_mm, posY_mm, coneZpos ), gp::DZ() );
1357 cone = BRepPrimAPI_MakeCone( axis, r1, r2, coneHeight );
1358 }
1359
1360 if( cone.IsNull() )
1361 {
1362 wxLogTrace( traceKiCad2Step, wxT( "AddCountersink: FAILED - cone shape is null" ) );
1363 m_reporter->Report( _( "Failed to create countersink cone shape" ),
1365 return false;
1366 }
1367
1368 // Add to both board and copper cutouts
1369 m_boardCutouts.push_back( cone );
1370 m_copperCutouts.push_back( cone );
1371
1372 wxLogTrace( traceKiCad2Step, wxT( "AddCountersink: SUCCESS - added cone. boardCutouts=%zu copperCutouts=%zu" ),
1373 m_boardCutouts.size(), m_copperCutouts.size() );
1374 }
1375 catch( const Standard_Failure& e )
1376 {
1377 wxLogTrace( traceKiCad2Step, wxT( "AddCountersink: EXCEPTION - %s" ), e.GetMessageString() );
1378 m_reporter->Report( wxString::Format( _( "OCC exception creating countersink: %s" ),
1379 e.GetMessageString() ),
1381 return false;
1382 }
1383
1384 return true;
1385}
1386
1387
1388std::map<PCB_LAYER_ID, int> STEP_PCB_MODEL::GetCopperLayerKnockouts( int aDiameter, int aDepth,
1389 int aAngle, bool aFrontSide )
1390{
1391 std::map<PCB_LAYER_ID, int> knockouts;
1392
1393 // Get the outer surface positions (including copper)
1394 double f_pos, f_thickness, b_pos, b_thickness;
1395 getLayerZPlacement( F_Cu, f_pos, f_thickness );
1396 getLayerZPlacement( B_Cu, b_pos, b_thickness );
1397
1398 double topOuterSurface = std::max( f_pos, f_pos + f_thickness );
1399 double bottomOuterSurface = std::min( b_pos, b_pos + b_thickness );
1400
1401 // Convert dimensions to mm
1402 double diameter_mm = pcbIUScale.IUTomm( aDiameter );
1403 double radius_mm = diameter_mm / 2.0;
1404
1405 // Calculate depth in mm
1406 double depth_mm;
1407 double halfAngleRad = 0.0;
1408
1409 if( aAngle > 0 )
1410 {
1411 // Countersink - calculate half angle
1412 halfAngleRad = ( aAngle / 10.0 ) * M_PI / 180.0 / 2.0;
1413
1414 // If depth is not specified for countersink, calculate from diameter and angle
1415 if( aDepth <= 0 )
1416 depth_mm = radius_mm / tan( halfAngleRad );
1417 else
1418 depth_mm = pcbIUScale.IUTomm( aDepth );
1419 }
1420 else
1421 {
1422 // Counterbore - use specified depth
1423 depth_mm = pcbIUScale.IUTomm( aDepth );
1424 }
1425
1426 // Determine the Z range of the feature
1427 double featureTop, featureBottom;
1428
1429 if( aFrontSide )
1430 {
1431 featureTop = topOuterSurface;
1432 featureBottom = topOuterSurface - depth_mm;
1433 }
1434 else
1435 {
1436 featureBottom = bottomOuterSurface;
1437 featureTop = bottomOuterSurface + depth_mm;
1438 }
1439
1440 wxLogTrace( traceKiCad2Step, wxT( "GetCopperLayerKnockouts: featureTop=%f featureBottom=%f depth_mm=%f frontSide=%d" ),
1441 featureTop, featureBottom, depth_mm, aFrontSide ? 1 : 0 );
1442
1443 // Iterate through all copper layers and check if they fall within the feature range
1444 for( const BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
1445 {
1446 if( item->GetType() != BS_ITEM_TYPE_COPPER )
1447 continue;
1448
1449 PCB_LAYER_ID layer = item->GetBrdLayerId();
1450 double layerZ, layerThickness;
1451 getLayerZPlacement( layer, layerZ, layerThickness );
1452
1453 // Get the Z range of this copper layer (both inner and outer surfaces)
1454 double layerTop = std::max( layerZ, layerZ + layerThickness );
1455 double layerBottom = std::min( layerZ, layerZ + layerThickness );
1456
1457 // Check if this layer overlaps with the feature Z range
1458 // A layer is affected if any part of it is within the feature range
1459 bool layerInRange = ( layerTop >= featureBottom && layerBottom <= featureTop );
1460
1461 wxLogTrace( traceKiCad2Step, wxT( "GetCopperLayerKnockouts: layer %d Z=[%f, %f] feature=[%f, %f] inRange=%d" ),
1462 static_cast<int>( layer ), layerBottom, layerTop, featureBottom, featureTop, layerInRange ? 1 : 0 );
1463
1464 if( !layerInRange )
1465 continue;
1466
1467 int knockoutDiameter;
1468
1469 if( aAngle > 0 )
1470 {
1471 // Countersink - calculate diameter at this layer's Z level
1472 // Use the layer surface that's closest to the feature origin surface
1473 double layerSurfaceZ;
1474 if( aFrontSide )
1475 {
1476 // For front-side countersink, use the top surface of the layer
1477 layerSurfaceZ = layerTop;
1478 }
1479 else
1480 {
1481 // For back-side countersink, use the bottom surface of the layer
1482 layerSurfaceZ = layerBottom;
1483 }
1484
1485 // Distance from the surface determines the radius at this Z
1486 double distanceFromSurface;
1487 if( aFrontSide )
1488 distanceFromSurface = topOuterSurface - layerSurfaceZ;
1489 else
1490 distanceFromSurface = layerSurfaceZ - bottomOuterSurface;
1491
1492 // Radius at this depth: r = R - d * tan(halfAngle)
1493 double radiusAtLayer_mm = radius_mm - distanceFromSurface * tan( halfAngleRad );
1494
1495 if( radiusAtLayer_mm <= 0 )
1496 {
1497 wxLogTrace( traceKiCad2Step, wxT( "GetCopperLayerKnockouts: layer %d - countersink tapers to point before this layer" ),
1498 static_cast<int>( layer ) );
1499 continue; // Cone tapers to a point before reaching this layer
1500 }
1501
1502 knockoutDiameter = pcbIUScale.mmToIU( radiusAtLayer_mm * 2.0 );
1503 wxLogTrace( traceKiCad2Step, wxT( "GetCopperLayerKnockouts: layer %d (countersink) - distFromSurface=%f radiusAtLayer=%f diameter=%d" ),
1504 static_cast<int>( layer ), distanceFromSurface, radiusAtLayer_mm, knockoutDiameter );
1505 }
1506 else
1507 {
1508 // Counterbore - constant diameter
1509 knockoutDiameter = aDiameter;
1510 wxLogTrace( traceKiCad2Step, wxT( "GetCopperLayerKnockouts: layer %d (counterbore) - diameter=%d" ),
1511 static_cast<int>( layer ), knockoutDiameter );
1512 }
1513
1514 knockouts[layer] = knockoutDiameter;
1515 }
1516
1517 return knockouts;
1518}
1519
1520
1521void STEP_PCB_MODEL::getLayerZPlacement( const PCB_LAYER_ID aLayer, double& aZPos,
1522 double& aThickness )
1523{
1524 // Offsets above copper in mm
1525 static const double c_silkscreenAboveCopper = 0.04;
1526 static const double c_soldermaskAboveCopper = 0.015;
1527
1528 if( IsCopperLayer( aLayer ) )
1529 {
1530 getCopperLayerZPlacement( aLayer, aZPos, aThickness );
1531 }
1532 else if( IsFrontLayer( aLayer ) )
1533 {
1534 double f_pos, f_thickness;
1535 getCopperLayerZPlacement( F_Cu, f_pos, f_thickness );
1536 double top = std::max( f_pos, f_pos + f_thickness );
1537
1538 if( aLayer == F_SilkS )
1539 aZPos = top + c_silkscreenAboveCopper;
1540 else
1541 aZPos = top + c_soldermaskAboveCopper;
1542
1543 aThickness = 0.0; // Normal points up
1544 }
1545 else if( IsBackLayer( aLayer ) )
1546 {
1547 double b_pos, b_thickness;
1548 getCopperLayerZPlacement( B_Cu, b_pos, b_thickness );
1549 double bottom = std::min( b_pos, b_pos + b_thickness );
1550
1551 if( aLayer == B_SilkS )
1552 aZPos = bottom - c_silkscreenAboveCopper;
1553 else
1554 aZPos = bottom - c_soldermaskAboveCopper;
1555
1556 aThickness = -0.0; // Normal points down
1557 }
1558}
1559
1560
1562 double& aThickness )
1563{
1564 int z = 0;
1565 int thickness = 0;
1566 bool wasPrepreg = false;
1567
1568 const std::vector<BOARD_STACKUP_ITEM*>& materials = m_stackup.GetList();
1569
1570 // Iterate from bottom to top
1571 for( auto it = materials.rbegin(); it != materials.rend(); ++it )
1572 {
1573 const BOARD_STACKUP_ITEM* item = *it;
1574
1575 if( item->GetType() == BS_ITEM_TYPE_COPPER )
1576 {
1577 if( aLayer == B_Cu )
1578 {
1579 // This is the first encountered layer
1580 thickness = -item->GetThickness();
1581 break;
1582 }
1583
1584 // Inner copper position is usually inside prepreg
1585 if( wasPrepreg && item->GetBrdLayerId() != F_Cu )
1586 {
1587 z += item->GetThickness();
1588 thickness = -item->GetThickness();
1589 }
1590 else
1591 {
1592 thickness = item->GetThickness();
1593 }
1594
1595 if( item->GetBrdLayerId() == aLayer )
1596 break;
1597
1598 if( !wasPrepreg && item->GetBrdLayerId() != B_Cu )
1599 z += item->GetThickness();
1600 }
1601 else if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1602 {
1603 wasPrepreg = ( item->GetTypeName() == KEY_PREPREG );
1604
1605 // Dielectric can have sub-layers. Layer 0 is the main layer
1606 // Not frequent, but possible
1607 thickness = 0;
1608 for( int idx = 0; idx < item->GetSublayersCount(); idx++ )
1609 thickness += item->GetThickness( idx );
1610
1611 z += thickness;
1612 }
1613 }
1614
1615 aZPos = pcbIUScale.IUTomm( z );
1616 aThickness = pcbIUScale.IUTomm( thickness );
1617}
1618
1619
1620void STEP_PCB_MODEL::getBoardBodyZPlacement( double& aZPos, double& aThickness )
1621{
1622 double f_pos, f_thickness;
1623 double b_pos, b_thickness;
1624 getLayerZPlacement( F_Cu, f_pos, f_thickness );
1625 getLayerZPlacement( B_Cu, b_pos, b_thickness );
1626 double top = std::min( f_pos, f_pos + f_thickness );
1627 double bottom = std::max( b_pos, b_pos + b_thickness );
1628
1629 aThickness = ( top - bottom );
1630 aZPos = bottom;
1631
1632 wxASSERT( aZPos == 0.0 );
1633}
1634
1635
1637 const VECTOR2D& aOrigin, const wxString& aNetname )
1638{
1639 bool success = true;
1640
1641 if( aPolyShapes->IsEmpty() )
1642 return true;
1643
1644 if( !m_enabledLayers.Contains( aLayer ) )
1645 return true;
1646
1647 double z_pos, thickness;
1648 getLayerZPlacement( aLayer, z_pos, thickness );
1649
1650 std::vector<TopoDS_Shape>* targetVec = nullptr;
1651
1652 if( IsCopperLayer( aLayer ) )
1653 targetVec = &m_board_copper[aNetname];
1654 else if( aLayer == F_SilkS )
1655 targetVec = &m_board_front_silk;
1656 else if( aLayer == B_SilkS )
1657 targetVec = &m_board_back_silk;
1658 else if( aLayer == F_Mask )
1659 targetVec = &m_board_front_mask;
1660 else
1661 targetVec = &m_board_back_mask;
1662
1663 if( !MakeShapes( *targetVec, *aPolyShapes, m_simplifyShapes, thickness, z_pos, aOrigin ) )
1664 {
1665 m_reporter->Report( wxString::Format( _( "Could not add shape (%d points) to copper layer %s." ),
1666 aPolyShapes->FullPointCount(),
1667 LayerName( aLayer ) ),
1669
1670 success = false;
1671 }
1672
1673 return success;
1674}
1675
1676
1677bool STEP_PCB_MODEL::AddComponent( const std::string& aFileNameUTF8, const std::string& aRefDes,
1678 bool aBottom, const VECTOR2D& aPosition, double aRotation, const VECTOR3D& aOffset,
1679 const VECTOR3D& aOrientation, const VECTOR3D& aScale, bool aSubstituteModels )
1680{
1681 if( aFileNameUTF8.empty() )
1682 {
1683 m_reporter->Report( wxString::Format( _( "No model defined for %s." ), aRefDes ),
1685 return false;
1686 }
1687
1688 wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
1689 m_reporter->Report( wxString::Format( wxT( "Adding component %s." ), aRefDes ), RPT_SEVERITY_DEBUG );
1690
1691 // first retrieve a label
1692 TDF_Label lmodel;
1693 wxString errorMessage;
1694
1695 if( !getModelLabel( aFileNameUTF8, aScale, lmodel, aSubstituteModels, &errorMessage ) )
1696 {
1697 if( errorMessage.IsEmpty() )
1698 errorMessage.Printf( _( "No model for filename '%s'." ), fileName );
1699
1700 m_reporter->Report( errorMessage, RPT_SEVERITY_ERROR );
1701 return false;
1702 }
1703
1704 // calculate the Location transform
1705 TopLoc_Location toploc;
1706
1707 if( !getModelLocation( aBottom, aPosition, aRotation, aOffset, aOrientation, toploc ) )
1708 {
1709 m_reporter->Report( wxString::Format( _( "No location data for filename '%s'." ), fileName ),
1711 return false;
1712 }
1713
1714 // add the located sub-assembly
1715 TDF_Label llabel = m_assy->AddComponent( m_assy_label, lmodel, toploc );
1716
1717 if( llabel.IsNull() )
1718 {
1719 m_reporter->Report( wxString::Format( _( "Could not add component with filename '%s'." ), fileName ),
1721 return false;
1722 }
1723
1724 // attach the RefDes name
1725 TCollection_ExtendedString refdes( aRefDes.c_str() );
1726 TDataStd_Name::Set( llabel, refdes );
1727
1728 KICAD3D_INFO::Set( llabel, KICAD3D_MODEL_TYPE::COMPONENT, aRefDes );
1729
1730 return true;
1731}
1732
1733
1735{
1736 m_enabledLayers = aLayers;
1737}
1738
1739
1741{
1742 m_fuseShapes = aValue;
1743}
1744
1745
1747{
1748 m_simplifyShapes = aValue;
1749}
1750
1751
1753{
1754 m_stackup = aStackup;
1755}
1756
1757
1758void STEP_PCB_MODEL::SetNetFilter( const wxString& aFilter )
1759{
1760 m_netFilter = aFilter;
1761}
1762
1763
1765{
1766 m_extraPadThickness = aValue;
1767}
1768
1769
1770void STEP_PCB_MODEL::SetCopperColor( double r, double g, double b )
1771{
1772 m_copperColor[0] = r;
1773 m_copperColor[1] = g;
1774 m_copperColor[2] = b;
1775}
1776
1777
1778void STEP_PCB_MODEL::SetPadColor( double r, double g, double b )
1779{
1780 m_padColor[0] = r;
1781 m_padColor[1] = g;
1782 m_padColor[2] = b;
1783}
1784
1785
1787{
1788 // Ensure a minimal value (in mm)
1789 m_mergeOCCMaxDist = aDistance;
1790}
1791
1792
1794{
1795 return m_pcb_labels.size() > 0;
1796}
1797
1798
1799bool STEP_PCB_MODEL::MakeShapeAsThickSegment( TopoDS_Shape& aShape, const VECTOR2D& aStartPoint,
1800 const VECTOR2D& aEndPoint, double aWidth, double aThickness,
1801 double aZposition, const VECTOR2D& aOrigin )
1802{
1803 // make a wide segment from 2 lines and 2 180 deg arcs
1804 // We need 6 points (3 per arcs)
1805 VECTOR2D coords[6];
1806
1807 // We build a horizontal segment, and after rotate it
1808 double len = ( aEndPoint - aStartPoint ).EuclideanNorm();
1809 double h_width = aWidth/2.0;
1810 // First is end point of first arc, and also start point of first line
1811 coords[0] = VECTOR2D{ 0.0, h_width };
1812
1813 // end point of first line and start point of second arc
1814 coords[1] = VECTOR2D{ len, h_width };
1815
1816 // middle point of second arc
1817 coords[2] = VECTOR2D{ len + h_width, 0.0 };
1818
1819 // start point of second line and end point of second arc
1820 coords[3] = VECTOR2D{ len, -h_width };
1821
1822 // end point of second line and start point of first arc
1823 coords[4] = VECTOR2D{ 0, -h_width };
1824
1825 // middle point of first arc
1826 coords[5] = VECTOR2D{ -h_width, 0.0 };
1827
1828 // Rotate and move to segment position
1829 EDA_ANGLE seg_angle( aEndPoint - aStartPoint );
1830
1831 for( int ii = 0; ii < 6; ii++ )
1832 {
1833 RotatePoint( coords[ii], VECTOR2D{ 0, 0 }, -seg_angle ),
1834 coords[ii] += aStartPoint;
1835 }
1836
1837
1838 // Convert to 3D points
1839 gp_Pnt coords3D[ 6 ];
1840
1841 for( int ii = 0; ii < 6; ii++ )
1842 {
1843 coords3D[ii] = gp_Pnt( pcbIUScale.IUTomm( coords[ii].x - aOrigin.x ),
1844 -pcbIUScale.IUTomm( coords[ii].y - aOrigin.y ), aZposition );
1845 }
1846
1847 // Build OpenCascade shape outlines
1848 BRepBuilderAPI_MakeWire wire;
1849 bool success = true;
1850
1851 // Short segments (distance between end points < m_mergeOCCMaxDist(in mm)) must be
1852 // skipped because OCC merge end points, and a null shape is created
1853 bool short_seg = pcbIUScale.IUTomm( len ) <= m_mergeOCCMaxDist;
1854
1855 try
1856 {
1857 TopoDS_Edge edge;
1858
1859 if( short_seg )
1860 {
1861 Handle( Geom_Circle ) circle = GC_MakeCircle( coords3D[1], // arc1 start point
1862 coords3D[2], // arc1 mid point
1863 coords3D[5] // arc2 mid point
1864 );
1865
1866 edge = BRepBuilderAPI_MakeEdge( circle );
1867 wire.Add( edge );
1868 }
1869 else
1870 {
1871 edge = BRepBuilderAPI_MakeEdge( coords3D[0], coords3D[1] );
1872 wire.Add( edge );
1873
1874 Handle( Geom_TrimmedCurve ) arcOfCircle =
1875 GC_MakeArcOfCircle( coords3D[1], // start point
1876 coords3D[2], // mid point
1877 coords3D[3] // end point
1878 );
1879 edge = BRepBuilderAPI_MakeEdge( arcOfCircle );
1880 wire.Add( edge );
1881
1882 edge = BRepBuilderAPI_MakeEdge( coords3D[3], coords3D[4] );
1883 wire.Add( edge );
1884
1885 Handle( Geom_TrimmedCurve ) arcOfCircle2 =
1886 GC_MakeArcOfCircle( coords3D[4], // start point
1887 coords3D[5], // mid point
1888 coords3D[0] // end point
1889 );
1890 edge = BRepBuilderAPI_MakeEdge( arcOfCircle2 );
1891 wire.Add( edge );
1892 }
1893 }
1894 catch( const Standard_Failure& e )
1895 {
1896 m_reporter->Report( wxString::Format( _( "OCC exception building shape segment: %s" ),
1897 e.GetMessageString() ),
1899 return false;
1900 }
1901
1902 BRepBuilderAPI_MakeFace face;
1903
1904 try
1905 {
1906 gp_Pln plane( coords3D[0], gp::DZ() );
1907 face = BRepBuilderAPI_MakeFace( plane, wire );
1908 }
1909 catch( const Standard_Failure& e )
1910 {
1911 m_reporter->Report( wxString::Format( _( "OCC exception building face: %s" ),
1912 e.GetMessageString() ),
1914 return false;
1915 }
1916
1917 if( aThickness != 0.0 )
1918 {
1919 aShape = BRepPrimAPI_MakePrism( face, gp_Vec( 0, 0, aThickness ) );
1920
1921 if( aShape.IsNull() )
1922 {
1923 m_reporter->Report( _( "Failed to create a prismatic shape" ),
1925 return false;
1926 }
1927 }
1928 else
1929 {
1930 aShape = face;
1931 }
1932
1933 return success;
1934}
1935
1936
1937bool STEP_PCB_MODEL::MakePolygonAsWall( TopoDS_Shape& aShape,
1938 SHAPE_POLY_SET& aPolySet,
1939 double aHeight,
1940 double aZposition, const VECTOR2D& aOrigin )
1941{
1942 std::vector<TopoDS_Shape> testShapes;
1943
1944 bool success = MakeShapes( testShapes, aPolySet, m_simplifyShapes,
1945 aHeight, aZposition, aOrigin );
1946
1947 if( testShapes.size() > 0 )
1948 aShape = testShapes.front();
1949 else
1950 success = false;
1951
1952 return success;
1953}
1954
1955
1956static wxString formatBBox( const BOX2I& aBBox )
1957{
1958 wxString str;
1959 UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::MM );
1960
1961 str << "x0: " << unitsProvider.StringFromValue( aBBox.GetLeft(), false ) << "; ";
1962 str << "y0: " << unitsProvider.StringFromValue( aBBox.GetTop(), false ) << "; ";
1963 str << "x1: " << unitsProvider.StringFromValue( aBBox.GetRight(), false ) << "; ";
1964 str << "y1: " << unitsProvider.StringFromValue( aBBox.GetBottom(), false );
1965
1966 return str;
1967}
1968
1969
1970static bool makeWireFromChain( BRepLib_MakeWire& aMkWire, const SHAPE_LINE_CHAIN& aChain,
1971 double aMergeOCCMaxDist, double aZposition, const VECTOR2D& aOrigin,
1972 REPORTER* aReporter )
1973{
1974 auto toPoint =
1975 [&]( const VECTOR2D& aKiCoords ) -> gp_Pnt
1976 {
1977 return gp_Pnt( pcbIUScale.IUTomm( aKiCoords.x - aOrigin.x ),
1978 -pcbIUScale.IUTomm( aKiCoords.y - aOrigin.y ), aZposition );
1979 };
1980
1981 try
1982 {
1983 auto addSegment = [&]( const VECTOR2I& aPt0, const VECTOR2I& aPt1 ) -> bool
1984 {
1985 if( aPt0 == aPt1 )
1986 return false;
1987
1988 gp_Pnt start = toPoint( aPt0 );
1989 gp_Pnt end = toPoint( aPt1 );
1990
1991 BRepBuilderAPI_MakeEdge mkEdge( start, end );
1992
1993 if( !mkEdge.IsDone() || mkEdge.Edge().IsNull() )
1994 {
1995 aReporter->Report( wxString::Format( _( "Failed to make segment edge (%d %d) -> (%d %d), "
1996 "skipping" ),
1997 aPt0.x, aPt0.y,
1998 aPt1.x, aPt1.y ),
2000 }
2001 else
2002 {
2003 aMkWire.Add( mkEdge.Edge() );
2004
2005 if( aMkWire.Error() != BRepLib_WireDone )
2006 {
2007 aReporter->Report( wxString::Format( _( "Failed to add segment edge (%d %d) -> (%d %d)" ),
2008 aPt0.x, aPt0.y,
2009 aPt1.x, aPt1.y ),
2011 return false;
2012 }
2013 }
2014
2015 return true;
2016 };
2017
2018 auto addArc = [&]( const VECTOR2I& aPt0, const SHAPE_ARC& aArc ) -> bool
2019 {
2020 // Do not export too short segments: they create broken shape because OCC thinks
2021 Handle( Geom_Curve ) curve;
2022
2023 if( aArc.GetCentralAngle() == ANGLE_360 )
2024 {
2025 gp_Ax2 axis = gp::XOY();
2026 axis.SetLocation( toPoint( aArc.GetCenter() ) );
2027
2028 curve = GC_MakeCircle( axis, pcbIUScale.IUTomm( aArc.GetRadius() ) ).Value();
2029 }
2030 else
2031 {
2032 curve = GC_MakeArcOfCircle( toPoint( aPt0 ), toPoint( aArc.GetArcMid() ),
2033 toPoint( aArc.GetP1() ) ).Value();
2034 }
2035
2036 if( curve.IsNull() )
2037 return false;
2038
2039 aMkWire.Add( BRepBuilderAPI_MakeEdge( curve ) );
2040
2041 if( !aMkWire.IsDone() )
2042 {
2043 aReporter->Report( wxString::Format( _( "Failed to add arc curve from (%d %d), arc p0 "
2044 "(%d %d), mid (%d %d), p1 (%d %d)" ),
2045 aPt0.x, aPt0.y,
2046 aArc.GetP0().x, aArc.GetP0().y,
2047 aArc.GetArcMid().x, aArc.GetArcMid().y,
2048 aArc.GetP1().x, aArc.GetP1().y ),
2050 return false;
2051 }
2052
2053 return true;
2054 };
2055
2056 VECTOR2I firstPt;
2057 VECTOR2I lastPt;
2058 bool isFirstShape = true;
2059
2060 for( int i = 0; i <= aChain.PointCount() && i != -1; i = aChain.NextShape( i ) )
2061 {
2062 if( i == 0 )
2063 {
2064 if( aChain.IsArcSegment( 0 ) && aChain.IsArcSegment( aChain.PointCount() - 1 )
2065 && aChain.ArcIndex( 0 ) == aChain.ArcIndex( aChain.PointCount() - 1 ) )
2066 {
2067 // Skip first arc (we should encounter it later)
2068 int nextShape = aChain.NextShape( i );
2069
2070 // If nextShape points to the end, then we have a circle.
2071 if( nextShape != -1 )
2072 i = nextShape;
2073 }
2074 }
2075
2076 if( isFirstShape )
2077 lastPt = aChain.CPoint( i );
2078
2079 bool isArc = aChain.IsArcSegment( i );
2080
2081 if( aChain.IsArcStart( i ) )
2082 {
2083 const SHAPE_ARC& currentArc = aChain.Arc( aChain.ArcIndex( i ) );
2084
2085 if( isFirstShape )
2086 {
2087 firstPt = currentArc.GetP0();
2088 lastPt = firstPt;
2089 }
2090
2091 if( addSegment( lastPt, currentArc.GetP0() ) )
2092 lastPt = currentArc.GetP0();
2093
2094 if( addArc( lastPt, currentArc ) )
2095 lastPt = currentArc.GetP1();
2096 }
2097 else if( !isArc )
2098 {
2099 const SEG& seg = aChain.CSegment( i );
2100
2101 if( isFirstShape )
2102 {
2103 firstPt = seg.A;
2104 lastPt = firstPt;
2105 }
2106
2107 if( addSegment( lastPt, seg.A ) )
2108 lastPt = seg.A;
2109
2110 if( addSegment( lastPt, seg.B ) )
2111 lastPt = seg.B;
2112 }
2113
2114 isFirstShape = false;
2115 }
2116
2117 if( lastPt != firstPt && !addSegment( lastPt, firstPt ) )
2118 {
2119 aReporter->Report( wxString::Format( _( "Failed to close wire at %d, %d -> %d, %d **" ),
2120 lastPt.x, lastPt.y,
2121 firstPt.x, firstPt.y ),
2123
2124 return false;
2125 }
2126 }
2127 catch( const Standard_Failure& e )
2128 {
2129 aReporter->Report( wxString::Format( _( "OCC exception creating wire: %s" ),
2130 e.GetMessageString() ),
2132 return false;
2133 }
2134
2135 return true;
2136}
2137
2138
2139bool STEP_PCB_MODEL::MakeShapes( std::vector<TopoDS_Shape>& aShapes, const SHAPE_POLY_SET& aPolySet,
2140 bool aConvertToArcs, double aThickness, double aZposition,
2141 const VECTOR2D& aOrigin )
2142{
2143 SHAPE_POLY_SET workingPoly = aPolySet;
2144 workingPoly.Simplify();
2145
2146 SHAPE_POLY_SET fallbackPoly = workingPoly;
2147
2148 if( aConvertToArcs )
2149 {
2150 SHAPE_POLY_SET approximated = workingPoly;
2151
2152 for( size_t polyId = 0; polyId < approximated.CPolygons().size(); polyId++ )
2153 {
2154 SHAPE_POLY_SET::POLYGON& polygon = approximated.Polygon( polyId );
2155
2156 for( size_t contId = 0; contId < polygon.size(); contId++ )
2157 polygon[contId] = approximateLineChainWithArcs( polygon[contId] );
2158 }
2159
2160 fallbackPoly = workingPoly;
2161 workingPoly = approximated;
2162
2163 // TODO: this is not accurate because it doesn't check arcs.
2164 /*if( approximated.IsSelfIntersecting() )
2165 {
2166 m_reporter->Report( wxString::Format( _( "Approximated polygon self-intersection check failed\n"
2167 "z: %g; bounding box: %s" ) ),
2168 aZposition,
2169 formatBBox( workingPoly.BBox() ) ),
2170 RPT_SEVERITY_ERROR );
2171 }
2172 else
2173 {
2174 fallbackPoly = workingPoly;
2175 workingPoly = approximated;
2176 }*/
2177 }
2178
2179#if 0 // No longer in use
2180 auto toPoint = [&]( const VECTOR2D& aKiCoords ) -> gp_Pnt
2181 {
2182 return gp_Pnt( pcbIUScale.IUTomm( aKiCoords.x - aOrigin.x ),
2183 -pcbIUScale.IUTomm( aKiCoords.y - aOrigin.y ), aZposition );
2184 };
2185#endif
2186
2187 gp_Pln basePlane( gp_Pnt( 0.0, 0.0, aZposition ),
2188 std::signbit( aThickness ) ? -gp::DZ() : gp::DZ() );
2189
2190 for( size_t polyId = 0; polyId < workingPoly.CPolygons().size(); polyId++ )
2191 {
2192 SHAPE_POLY_SET::POLYGON& polygon = workingPoly.Polygon( polyId );
2193
2194 auto tryMakeWire = [this, &aZposition,
2195 &aOrigin]( const SHAPE_LINE_CHAIN& aContour, bool aAllowRetry ) -> TopoDS_Wire
2196 {
2197 TopoDS_Wire wire;
2198 BRepLib_MakeWire mkWire;
2199
2200 makeWireFromChain( mkWire, aContour, m_mergeOCCMaxDist, aZposition, aOrigin, m_reporter );
2201
2202 if( mkWire.IsDone() )
2203 {
2204 wire = mkWire.Wire();
2205 }
2206 else
2207 {
2208 m_reporter->Report(
2209 wxString::Format( _( "Wire not done (contour points %d): OCC error %d\n"
2210 "z: %g; bounding box: %s" ),
2211 static_cast<int>( aContour.PointCount() ),
2212 static_cast<int>( mkWire.Error() ),
2213 formatBBox( aContour.BBox() ) ),
2215 }
2216
2217 if( !wire.IsNull() )
2218 {
2219 BRepAlgoAPI_Check check( wire, false, true );
2220
2221 if( !check.IsValid() )
2222 {
2223 m_reporter->Report( wxString::Format( _( "Wire self-interference check failed\n"
2224 "z: %g; bounding box: %s" ),
2225 aZposition,
2226 formatBBox( aContour.BBox() ) ),
2228
2229 wire.Nullify();
2230 }
2231 }
2232
2233 return wire;
2234 };
2235
2236 BRepBuilderAPI_MakeFace mkFace;
2237
2238 for( size_t contId = 0; contId < polygon.size(); contId++ )
2239 {
2240 try
2241 {
2242 // We allow retry when trying to convert polygon[contId] when a convert error
2243 // happens, using an equivalent polygon shape.
2244 bool allow_retry = aConvertToArcs ? true : false;
2245
2246 TopoDS_Wire wire = tryMakeWire( polygon[contId], allow_retry );
2247
2248 if( aConvertToArcs && wire.IsNull() )
2249 {
2250 m_reporter->Report( wxString::Format( _( "Using non-simplified polygon." ) ),
2252
2253 // Fall back to original shape. Do not allow retry
2254 allow_retry = false;
2255 wire = tryMakeWire( fallbackPoly.CPolygon( polyId )[contId], allow_retry );
2256 }
2257
2258 if( contId == 0 ) // Outline
2259 {
2260 if( !wire.IsNull() )
2261 {
2262 if( basePlane.Axis().Direction().Z() < 0 )
2263 wire.Reverse();
2264
2265 mkFace = BRepBuilderAPI_MakeFace( basePlane, wire );
2266 }
2267 else
2268 {
2269 m_reporter->Report( wxString::Format( wxT( "** Outline skipped **\n"
2270 "z: %g; bounding box: %s" ),
2271 aZposition,
2272 formatBBox( polygon[contId].BBox() ) ),
2274 break;
2275 }
2276 }
2277 else // Hole
2278 {
2279 if( !wire.IsNull() )
2280 {
2281 if( basePlane.Axis().Direction().Z() > 0 )
2282 wire.Reverse();
2283
2284 mkFace.Add( wire );
2285 }
2286 else
2287 {
2288 m_reporter->Report( wxString::Format( wxT( "** Hole skipped **\n"
2289 "z: %g; bounding box: %s" ),
2290 aZposition,
2291 formatBBox( polygon[contId].BBox() ) ),
2293 }
2294 }
2295 }
2296 catch( const Standard_Failure& e )
2297 {
2298 m_reporter->Report( wxString::Format( _( "OCC exception creating contour %d: %s" ),
2299 static_cast<int>( contId ),
2300 e.GetMessageString() ),
2302 return false;
2303 }
2304 }
2305
2306 if( mkFace.IsDone() )
2307 {
2308 TopoDS_Shape faceShape = mkFace.Shape();
2309
2310 if( aThickness != 0.0 )
2311 {
2312 TopoDS_Shape prism = BRepPrimAPI_MakePrism( faceShape, gp_Vec( 0, 0, aThickness ) );
2313 aShapes.push_back( prism );
2314
2315 if( prism.IsNull() )
2316 {
2317 m_reporter->Report( _( "Failed to create a prismatic shape" ), RPT_SEVERITY_ERROR );
2318 return false;
2319 }
2320 }
2321 else
2322 {
2323 aShapes.push_back( faceShape );
2324 }
2325 }
2326 else
2327 {
2328 m_reporter->Report( _( "** Face skipped **" ), RPT_SEVERITY_DEBUG );
2329 }
2330 }
2331
2332 return true;
2333}
2334
2335
2336// These colors are based on 3D viewer's colors and are different to "gbrjobColors"
2337static std::vector<FAB_LAYER_COLOR> s_soldermaskColors = {
2338 { NotSpecifiedPrm(), wxColor( 20, 51, 36 ) }, // Not specified, not in .gbrjob file
2339 { _HKI( "Green" ), wxColor( 20, 51, 36 ) }, // used in .gbrjob file
2340 { _HKI( "Red" ), wxColor( 181, 19, 21 ) }, // used in .gbrjob file
2341 { _HKI( "Blue" ), wxColor( 2, 59, 162 ) }, // used in .gbrjob file
2342 { _HKI( "Purple" ), wxColor( 32, 2, 53 ) }, // used in .gbrjob file
2343 { _HKI( "Black" ), wxColor( 11, 11, 11 ) }, // used in .gbrjob file
2344 { _HKI( "White" ), wxColor( 245, 245, 245 ) }, // used in .gbrjob file
2345 { _HKI( "Yellow" ), wxColor( 194, 195, 0 ) }, // used in .gbrjob file
2346 { _HKI( "User defined" ), wxColor( 128, 128, 128 ) } // Free; the name is a dummy name here
2347};
2348
2349
2350static bool colorFromStackup( BOARD_STACKUP_ITEM_TYPE aType, const wxString& aColorStr,
2351 COLOR4D& aColorOut )
2352{
2353 if( !IsPrmSpecified( aColorStr ) )
2354 return false;
2355
2356 if( aColorStr.StartsWith( wxT( "#" ) ) ) // User defined color
2357 {
2358 aColorOut = COLOR4D( aColorStr );
2359 return true;
2360 }
2361 else
2362 {
2363 const std::vector<FAB_LAYER_COLOR>& colors =
2364 ( aType == BS_ITEM_TYPE_SOLDERMASK || aType == BS_ITEM_TYPE_SILKSCREEN )
2366 : GetStandardColors( aType );
2367
2368 for( const FAB_LAYER_COLOR& fabColor : colors )
2369 {
2370 if( fabColor.GetName() == aColorStr )
2371 {
2372 aColorOut = fabColor.GetColor( aType );
2373 return true;
2374 }
2375 }
2376 }
2377
2378 return false;
2379}
2380
2381
2382bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, const VECTOR2D& aOrigin, bool aPushBoardBody )
2383{
2384 if( m_hasPCB )
2385 {
2386 if( !isBoardOutlineValid() )
2387 return false;
2388
2389 return true;
2390 }
2391
2393
2394 Handle( XCAFDoc_VisMaterialTool ) visMatTool = XCAFDoc_DocumentTool::VisMaterialTool( m_doc->Main() );
2395
2396 m_hasPCB = true; // whether or not operations fail we note that CreatePCB has been invoked
2397
2398 // Support for more than one main outline (more than one board)
2399 m_reporter->Report( wxString::Format( wxT( "Build board outlines (%d outlines) with %d points." ),
2400 aOutline.OutlineCount(),
2401 aOutline.FullPointCount() ),
2403
2404 double boardThickness;
2405 double boardZPos;
2406 getBoardBodyZPlacement( boardZPos, boardThickness );
2407
2408#if 1
2409 // This code should work, and it is working most of time
2410 // However there are issues if the main outline is a circle with holes:
2411 // holes from vias and pads are not working
2412 // see bug https://gitlab.com/kicad/code/kicad/-/issues/17446
2413 // (Holes are missing from STEP export with circular PCB outline)
2414 // Hard to say if the bug is in our code or in OCC 7.7
2415 if( !MakeShapes( m_board_outlines, aOutline, false, boardThickness, boardZPos, aOrigin ) )
2416 {
2417 // Error
2418 m_reporter->Report( _( "OCC error creating main outline." ), RPT_SEVERITY_ERROR );
2419 }
2420#else
2421 // Workaround for bug #17446 Holes are missing from STEP export with circular PCB outline
2422 for( const SHAPE_POLY_SET::POLYGON& polygon : aOutline.CPolygons() )
2423 {
2424 for( size_t contId = 0; contId < polygon.size(); contId++ )
2425 {
2426 const SHAPE_LINE_CHAIN& contour = polygon[contId];
2427 SHAPE_POLY_SET polyset;
2428 polyset.Append( contour );
2429
2430 if( contId == 0 ) // main Outline
2431 {
2432 if( !MakeShapes( m_board_outlines, polyset, false, boardThickness, boardZPos,
2433 aOrigin ) )
2434 {
2435 m_reporter->Report( _( "OCC error creating main outline." ),
2437 }
2438 }
2439 else // Hole inside the main outline
2440 {
2441 if( !MakeShapes( m_boardCutouts, polyset, false, boardThickness, boardZPos,
2442 aOrigin ) )
2443 {
2444 m_reporter->Report( _( "OCC error creating hole in main outline." ),
2446 }
2447 }
2448 }
2449 }
2450#endif
2451
2452 // Even if we've disabled board body export, we still need the shapes for bounding box calculations.
2453 Bnd_Box brdBndBox;
2454
2455 for( const TopoDS_Shape& brdShape : m_board_outlines )
2456 BRepBndLib::Add( brdShape, brdBndBox );
2457
2458 // subtract cutouts (if any)
2459 m_reporter->Report( wxString::Format( wxT( "Build board cutouts and holes (%d hole(s))." ),
2460 (int) ( m_boardCutouts.size() + m_copperCutouts.size() ) ),
2462
2463 auto buildBSB =
2464 [&brdBndBox]( std::vector<TopoDS_Shape>& input, Bnd_BoundSortBox& bsbHoles )
2465 {
2466 // We need to encompass every location we'll need to test in the global bbox,
2467 // otherwise Bnd_BoundSortBox doesn't work near the boundaries.
2468 Bnd_Box brdWithHolesBndBox = brdBndBox;
2469
2470 Handle( Bnd_HArray1OfBox ) holeBoxSet = new Bnd_HArray1OfBox( 0, input.size() - 1 );
2471
2472 for( size_t i = 0; i < input.size(); i++ )
2473 {
2474 Bnd_Box bbox;
2475 BRepBndLib::Add( input[i], bbox );
2476 brdWithHolesBndBox.Add( bbox );
2477 ( *holeBoxSet )[i] = bbox;
2478 }
2479
2480 bsbHoles.Initialize( brdWithHolesBndBox, holeBoxSet );
2481 };
2482
2483 auto subtractShapesMap =
2484 [&tp, this]( const wxString& aWhat, std::map<wxString, std::vector<TopoDS_Shape>>& aShapesMap,
2485 std::vector<TopoDS_Shape>& aHolesList, Bnd_BoundSortBox& aBSBHoles )
2486 {
2487 m_reporter->Report( wxString::Format( _( "Subtracting holes for %s" ), aWhat ),
2489
2490 for( auto& [netname, vec] : aShapesMap )
2491 {
2492 std::mutex mutex;
2493
2494 auto subtractLoopFn = [&]( const int shapeId )
2495 {
2496 TopoDS_Shape& shape = vec[shapeId];
2497
2498 Bnd_Box shapeBbox;
2499 BRepBndLib::Add( shape, shapeBbox );
2500
2501 TopTools_ListOfShape holelist;
2502
2503 {
2504 std::unique_lock lock( mutex );
2505
2506 const TColStd_ListOfInteger& indices = aBSBHoles.Compare( shapeBbox );
2507
2508 for( const Standard_Integer& index : indices )
2509 holelist.Append( aHolesList[index] );
2510 }
2511
2512 if( holelist.IsEmpty() )
2513 return; // nothing to cut for this shape
2514
2515 TopTools_ListOfShape cutArgs;
2516 cutArgs.Append( shape );
2517
2518 BRepAlgoAPI_Cut cut;
2519
2520 cut.SetRunParallel( true );
2521 cut.SetToFillHistory( false );
2522
2523 cut.SetArguments( cutArgs );
2524 cut.SetTools( holelist );
2525 cut.Build();
2526
2527 if( cut.HasErrors() || cut.HasWarnings() )
2528 {
2529 m_reporter->Report( wxString::Format( _( "** Got problems while cutting "
2530 "%s net '%s' **" ),
2531 aWhat,
2532 UnescapeString( netname ) ),
2534 shapeBbox.Dump();
2535
2536 if( cut.HasErrors() )
2537 {
2538 wxString msg = _( "Errors:\n" );
2539 wxStringOutputStream os_stream( &msg );
2540 wxStdOutputStream out( os_stream );
2541
2542 cut.DumpErrors( out );
2543 m_reporter->Report( msg, RPT_SEVERITY_WARNING);
2544 }
2545
2546 if( cut.HasWarnings() )
2547 {
2548 wxString msg = _( "Warnings:\n" );
2549 wxStringOutputStream os_stream( &msg );
2550 wxStdOutputStream out( os_stream );
2551
2552 cut.DumpWarnings( out );
2553 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2554 }
2555 }
2556
2557 shape = cut.Shape();
2558 };
2559
2560 tp.submit_loop( 0, vec.size(), subtractLoopFn ).wait();
2561 }
2562 };
2563
2564 auto subtractShapes =
2565 [subtractShapesMap]( const wxString& aWhat, std::vector<TopoDS_Shape>& aShapesList,
2566 std::vector<TopoDS_Shape>& aHolesList, Bnd_BoundSortBox& aBSBHoles )
2567 {
2568 std::map<wxString, std::vector<TopoDS_Shape>> aShapesMap{ { wxEmptyString, aShapesList } };
2569
2570 subtractShapesMap( aWhat, aShapesMap, aHolesList, aBSBHoles );
2571 aShapesList = aShapesMap[wxEmptyString];
2572 };
2573
2574
2575 if( m_boardCutouts.size() )
2576 {
2577 Bnd_BoundSortBox bsbHoles;
2578 buildBSB( m_boardCutouts, bsbHoles );
2579
2580 subtractShapes( _( "shapes" ), m_board_outlines, m_boardCutouts, bsbHoles );
2581 }
2582
2583 if( m_copperCutouts.size() )
2584 {
2585 Bnd_BoundSortBox bsbHoles;
2586 buildBSB( m_copperCutouts, bsbHoles );
2587
2588 subtractShapesMap( _( "pads" ), m_board_copper_pads, m_copperCutouts, bsbHoles );
2589 subtractShapesMap( _( "vias" ), m_board_copper_vias, m_copperCutouts, bsbHoles );
2590 }
2591
2592 if( m_fuseShapes )
2593 {
2594 std::map<wxString, TopTools_ListOfShape> shapesToFuseMap;
2595
2596 auto addShapes = [&shapesToFuseMap]( const wxString& aNetname,
2597 const std::vector<TopoDS_Shape>& aShapes )
2598 {
2599 for( const TopoDS_Shape& shape : aShapes )
2600 shapesToFuseMap[aNetname].Append( shape );
2601 };
2602
2603 for( const auto& [netname, shapes] : m_board_copper )
2604 addShapes( netname, shapes );
2605
2606 for( const auto& [netname, shapes] : m_board_copper_pads )
2607 addShapes( netname, shapes );
2608
2609 for( const auto& [netname, shapes] : m_board_copper_vias )
2610 addShapes( netname, shapes );
2611
2612 m_reporter->Report( wxT( "Fusing shapes" ), RPT_SEVERITY_DEBUG );
2613
2614 // Do fusing in parallel
2615 std::mutex mutex;
2616
2617 auto fuseLoopFn = [&]( const wxString& aNetname )
2618 {
2619 auto& toFuse = shapesToFuseMap[aNetname];
2620 TopoDS_Shape fusedShape = fuseShapesOrCompound( toFuse, m_reporter );
2621
2622 if( !fusedShape.IsNull() )
2623 {
2624 std::unique_lock lock( mutex );
2625
2626 m_board_copper_fused[aNetname].emplace_back( fusedShape );
2627
2628 m_board_copper[aNetname].clear();
2629 m_board_copper_pads[aNetname].clear();
2630 m_board_copper_vias[aNetname].clear();
2631 }
2632 };
2633
2634 BS::multi_future<void> mf;
2635
2636 for( const auto& [netname, _] : shapesToFuseMap )
2637 mf.push_back( tp.submit_task( [&, netname]() { fuseLoopFn( netname ); } ) );
2638
2639 mf.wait();
2640 }
2641
2642 // push the board to the data structure
2643 m_reporter->Report( wxT( "Generate board full shape." ), RPT_SEVERITY_DEBUG );
2644
2645 // AddComponent adds a label that has a reference (not a parent/child relation) to the real
2646 // label. We need to extract that real label to name it for the STEP output cleanly
2647 // Why are we trying to name the bare board? Because CAD tools like SolidWorks do fun things
2648 // like "deduplicate" imported STEPs by swapping STEP assembly components with already
2649 // identically named assemblies. So we want to avoid having the PCB be generally defaulted
2650 // to "Component" or "Assembly".
2651
2652 // aCompoundNets will place all geometry within a net into one compound.
2653 // aCompoundAll will place all geometry into one compound.
2654 auto pushToAssemblyMap =
2655 [&]( const std::map<wxString, std::vector<TopoDS_Shape>>& aShapesMap,
2656 const TDF_Label& aVisMatLabel, const wxString& aShapeName, bool aCompoundNets,
2657 bool aCompoundAll, const wxString& aNiceName )
2658 {
2659 std::map<wxString, std::vector<TopoDS_Shape>> shapesMap;
2660
2661 if( aCompoundAll )
2662 {
2663 std::vector<TopoDS_Shape> allShapes;
2664
2665 for( const auto& [netname, shapesList] : aShapesMap )
2666 allShapes.insert( allShapes.end(), shapesList.begin(), shapesList.end() );
2667
2668 if( !allShapes.empty() )
2669 shapesMap[wxEmptyString].emplace_back( makeCompound( allShapes ) );
2670 }
2671 else
2672 {
2673 shapesMap = aShapesMap;
2674 }
2675
2676 for( const auto& [netname, shapesList] : shapesMap )
2677 {
2678 std::vector<TopoDS_Shape> newList;
2679
2680 if( aCompoundNets )
2681 newList.emplace_back( makeCompound( shapesList ) );
2682 else
2683 newList = shapesList;
2684
2685 int i = 1;
2686
2687 for( TopoDS_Shape& shape : newList )
2688 {
2689 Handle( TDataStd_TreeNode ) node;
2690
2691 // Dont expand the component or else coloring it gets hard
2692 TDF_Label lbl = m_assy->AddComponent( m_assy_label, shape, false );
2693 KICAD3D_INFO::Set( lbl, KICAD3D_MODEL_TYPE::BOARD, aNiceName.ToStdString() );
2694 m_pcb_labels.push_back( lbl );
2695
2696 if( m_pcb_labels.back().IsNull() )
2697 return;
2698
2699 lbl.FindAttribute( XCAFDoc::ShapeRefGUID(), node );
2700 TDF_Label shpLbl = node->Father()->Label();
2701
2702 if( !shpLbl.IsNull() )
2703 {
2704 if( visMatTool && !aVisMatLabel.IsNull() )
2705 visMatTool->SetShapeMaterial( shpLbl, aVisMatLabel );
2706
2707 wxString shapeName;
2708
2709 shapeName << m_pcbName;
2710 shapeName << '_';
2711 shapeName << aShapeName;
2712
2713 if( !netname.empty() )
2714 {
2715 shapeName << '_';
2716 shapeName << netname;
2717 }
2718
2719 if( newList.size() > 1 )
2720 {
2721 shapeName << '_';
2722 shapeName << i;
2723 }
2724
2725 TCollection_ExtendedString partname( shapeName.ToUTF8().data() );
2726 TDataStd_Name::Set( shpLbl, partname );
2727 }
2728
2729 i++;
2730 }
2731 }
2732 };
2733
2734 auto pushToAssembly =
2735 [&]( const std::vector<TopoDS_Shape>& aShapesList, const TDF_Label& aVisMatLabel,
2736 const wxString& aShapeName, bool aCompound, const wxString& aNiceName )
2737 {
2738 const std::map<wxString, std::vector<TopoDS_Shape>> shapesMap{ { wxEmptyString, aShapesList } };
2739
2740 pushToAssemblyMap( shapesMap, aVisMatLabel, aShapeName, aCompound, aCompound, aNiceName );
2741 };
2742
2743 auto makeMaterial =
2744 [&]( const TCollection_AsciiString& aName, const Quantity_ColorRGBA& aBaseColor,
2745 double aMetallic, double aRoughness ) -> TDF_Label
2746 {
2747 Handle( XCAFDoc_VisMaterial ) vismat = new XCAFDoc_VisMaterial;
2748 XCAFDoc_VisMaterialPBR pbr;
2749 pbr.BaseColor = aBaseColor;
2750 pbr.Metallic = aMetallic;
2751 pbr.Roughness = aRoughness;
2752 vismat->SetPbrMaterial( pbr );
2753 return visMatTool->AddMaterial( vismat, aName );
2754 };
2755
2756 // Init colors for the board items
2757 Quantity_ColorRGBA copper_color( m_copperColor[0], m_copperColor[1], m_copperColor[2], 1.0 );
2758 Quantity_ColorRGBA pad_color( m_padColor[0], m_padColor[1], m_padColor[2], 1.0 );
2759
2760 Quantity_ColorRGBA board_color( 0.42f, 0.45f, 0.29f, 0.98f );
2761 Quantity_ColorRGBA front_silk_color( 1.0f, 1.0f, 1.0f, 0.9f );
2762 Quantity_ColorRGBA back_silk_color = front_silk_color;
2763 Quantity_ColorRGBA front_mask_color( 0.08f, 0.2f, 0.14f, 0.83f );
2764 Quantity_ColorRGBA back_mask_color = front_mask_color;
2765
2766 // Get colors from stackup
2767 for( const BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
2768 {
2769 COLOR4D col;
2770
2771 if( !colorFromStackup( item->GetType(), item->GetColor(), col ) )
2772 continue;
2773
2774 if( item->GetBrdLayerId() == F_Mask || item->GetBrdLayerId() == B_Mask )
2775 {
2776 col.Darken( 0.2 );
2777
2778 if( item->GetBrdLayerId() == F_Mask )
2779 front_mask_color.SetValues( col.r, col.g, col.b, col.a );
2780 else
2781 back_mask_color.SetValues( col.r, col.g, col.b, col.a );
2782 }
2783
2784 if( item->GetBrdLayerId() == F_SilkS )
2785 front_silk_color.SetValues( col.r, col.g, col.b, col.a );
2786 else if( item->GetBrdLayerId() == B_SilkS )
2787 back_silk_color.SetValues( col.r, col.g, col.b, col.a );
2788
2789 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC && item->GetTypeName() == KEY_CORE )
2790 board_color.SetValues( col.r, col.g, col.b, col.a );
2791 }
2792
2793 // Paint board body in soldermask colors if soldermask is not exported as a layer
2794 if( !m_enabledLayers.Contains( F_Mask ) && !m_enabledLayers.Contains( B_Mask ) )
2795 {
2796 board_color = front_mask_color;
2797 board_color.SetAlpha( 1.0 );
2798 }
2799
2800 TDF_Label front_mask_mat = makeMaterial( "soldermask", front_mask_color, 0.0, 0.6 );
2801 TDF_Label back_mask_mat = makeMaterial( "soldermask", back_mask_color, 0.0, 0.6 );
2802 TDF_Label front_silk_mat = makeMaterial( "silkscreen", front_silk_color, 0.0, 0.9 );
2803 TDF_Label back_silk_mat = makeMaterial( "silkscreen", back_silk_color, 0.0, 0.9 );
2804 TDF_Label copper_mat = makeMaterial( "copper", copper_color, 1.0, 0.4 );
2805 TDF_Label pad_mat = makeMaterial( "pad", pad_color, 1.0, 0.4 );
2806 TDF_Label board_mat = makeMaterial( "board", board_color, 0.0, 0.8 );
2807
2808 pushToAssemblyMap( m_board_copper, copper_mat, "copper", true, true, "Copper" );
2809 pushToAssemblyMap( m_board_copper_pads, pad_mat, "pad", true, true, "Pads" );
2810 pushToAssemblyMap( m_board_copper_vias, copper_mat, "via", true, true, "Via" );
2811 pushToAssemblyMap( m_board_copper_fused, copper_mat, "copper", true, true, "Copper" );
2812 pushToAssembly( m_board_front_silk, front_silk_mat, "silkscreen", true, "Top Silkscreen" );
2813 pushToAssembly( m_board_back_silk, back_silk_mat, "silkscreen", true, "Bottom Silkscreen" );
2814 pushToAssembly( m_board_front_mask, front_mask_mat, "soldermask", true, "Top Soldermask" );
2815 pushToAssembly( m_board_back_mask, back_mask_mat, "soldermask", true, "Bottom Soldermask" );
2816
2817 if( aPushBoardBody )
2818 pushToAssembly( m_board_outlines, board_mat, "PCB", false, "Body" );
2819
2820#if( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 )
2821 m_assy->UpdateAssemblies();
2822#endif
2823
2824 return true;
2825}
2826
2827
2828#ifdef SUPPORTS_IGES
2829// write the assembly model in IGES format
2830bool STEP_PCB_MODEL::WriteIGES( const wxString& aFileName )
2831{
2832 if( !isBoardOutlineValid() )
2833 {
2834 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
2835 aFileName ),
2837 return false;
2838 }
2839
2841
2842 wxFileName fn( aFileName );
2843 IGESControl_Controller::Init();
2844 IGESCAFControl_Writer writer;
2845 writer.SetColorMode( Standard_True );
2846 writer.SetNameMode( Standard_True );
2847 IGESData_GlobalSection header = writer.Model()->GlobalSection();
2848 header.SetFileName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
2849 header.SetSendName( new TCollection_HAsciiString( "KiCad electronic assembly" ) );
2850 header.SetAuthorName( new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.author" ) ) );
2851 header.SetCompanyName( new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.company" ) ) );
2852 writer.Model()->SetGlobalSection( header );
2853
2854 if( Standard_False == writer.Perform( m_doc, aFileName.c_str() ) )
2855 return false;
2856
2857 return true;
2858}
2859#endif
2860
2861bool STEP_PCB_MODEL::CompressSTEP( wxString& inputFile, wxString& outputFile )
2862{
2863 wxFileInputStream input( inputFile );
2864 wxFileOutputStream output( outputFile );
2865
2866 if( !input.IsOk() )
2867 {
2868 m_reporter->Report( wxString::Format( _( "Cannot create input stream '%s'.\n" ), inputFile ) );
2869 return false;
2870 }
2871
2872 if( !output.IsOk() )
2873 {
2874 m_reporter->Report( wxString::Format( _( "Cannot create output stream '%s'.\n" ), outputFile ) );
2875 return false;
2876 }
2877
2878 wxZlibOutputStream zlibStream( output, -1, wxZLIB_GZIP );
2879
2880 if( !zlibStream.IsOk() )
2881 {
2882 m_reporter->Report( _( "Impossible create compress stream" ) );
2883 return false;
2884 }
2885
2886 input.Read( zlibStream );
2887
2888 if( input.LastRead() == 0 || zlibStream.LastWrite() == 0 )
2889 {
2890 m_reporter->Report( _( "Compress read or write error" ) );
2891 return false;
2892 }
2893
2894 zlibStream.Close();
2895 output.Close();
2896
2897 return true;
2898}
2899
2900bool STEP_PCB_MODEL::WriteSTEP( const wxString& aFileName, bool aOptimize, bool compress )
2901{
2902 if( !isBoardOutlineValid() )
2903 {
2904 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
2905 aFileName ),
2907 return false;
2908 }
2909
2911
2912 wxFileName fn( aFileName );
2913
2914 STEPCAFControl_Writer writer;
2915 writer.SetColorMode( Standard_True );
2916 writer.SetNameMode( Standard_True );
2917
2918 // This must be set before we "transfer" the document.
2919 // Should default to kicad_pcb.general.title_block.title,
2920 // but in the meantime, defaulting to the basename of the output
2921 // target is still better than "open cascade step translter v..."
2922 // UTF8 should be ok from ISO 10303-21:2016, but... older stuff? use boring ascii
2923 if( !Interface_Static::SetCVal( "write.step.product.name", fn.GetName().ToAscii() ) )
2924 {
2925 m_reporter->Report( _( "Failed to set STEP product name, but will attempt to continue." ),
2927 }
2928
2929 // Setting write.surfacecurve.mode to 0 reduces file size and write/read times.
2930 // But there are reports that this mode might be less compatible in some cases.
2931 if( !Interface_Static::SetIVal( "write.surfacecurve.mode", aOptimize ? 0 : 1 ) )
2932 {
2933 m_reporter->Report( _( "Failed to set surface curve mode, but will attempt to continue." ),
2935 }
2936
2937 if( Standard_False == writer.Transfer( m_doc, STEPControl_AsIs ) )
2938 return false;
2939
2940 APIHeaderSection_MakeHeader hdr( writer.ChangeWriter().Model() );
2941
2942 // Note: use only Ascii7 chars, non Ascii7 chars (therefore UFT8 chars)
2943 // are creating issues in the step file
2944 hdr.SetName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
2945
2946 // TODO: how to control and ensure consistency with IGES?
2947 hdr.SetAuthorValue( 1, new TCollection_HAsciiString( "Pcbnew" ) );
2948 hdr.SetOrganizationValue( 1, new TCollection_HAsciiString( "Kicad" ) );
2949 hdr.SetOriginatingSystem( new TCollection_HAsciiString( "KiCad to STEP converter" ) );
2950 hdr.SetDescriptionValue( 1, new TCollection_HAsciiString( "KiCad electronic assembly" ) );
2951
2952 bool success = true;
2953
2954 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
2955 wxString currCWD = wxGetCwd();
2956 wxString workCWD = fn.GetPath();
2957
2958 if( !workCWD.IsEmpty() )
2959 wxSetWorkingDirectory( workCWD );
2960
2961 wxString tmpfname( "$tempfile$.step" );
2962
2963 if( Standard_False == writer.Write( tmpfname.c_str() ) )
2964 success = false;
2965
2966 if( compress && success )
2967 {
2968 wxString srcTmp( tmpfname );
2969 wxString dstTmp( "$tempfile$.stpz" );
2970
2971 success = STEP_PCB_MODEL::CompressSTEP( srcTmp, dstTmp );
2972 wxRemoveFile( srcTmp );
2973
2974 tmpfname = dstTmp;
2975 }
2976
2977 if( success )
2978 {
2979
2980 // Preserve the permissions of the current file
2981 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpfname.c_str() );
2982
2983 if( !wxRenameFile( tmpfname, fn.GetFullName(), true ) )
2984 {
2985 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'." ),
2986 tmpfname,
2987 fn.GetFullName() ),
2989 success = false;
2990 }
2991 }
2992
2993 wxSetWorkingDirectory( currCWD );
2994
2995 return success;
2996}
2997
2998
2999bool STEP_PCB_MODEL::WriteBREP( const wxString& aFileName )
3000{
3001 if( !isBoardOutlineValid() )
3002 {
3003 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
3004 aFileName ),
3006 return false;
3007 }
3008
3010
3011 // s_assy = shape tool for the source
3012 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( m_doc->Main() );
3013
3014 // retrieve assembly as a single shape
3015 TopoDS_Shape shape = getOneShape( s_assy );
3016
3017 wxFileName fn( aFileName );
3018
3019 wxFFileOutputStream ffStream( fn.GetFullPath() );
3020 wxStdOutputStream stdStream( ffStream );
3021
3022#if OCC_VERSION_HEX >= 0x070600
3023 BRepTools::Write( shape, stdStream, false, false, TopTools_FormatVersion_VERSION_1 );
3024#else
3025 BRepTools::Write( shape, stdStream );
3026#endif
3027
3028 return true;
3029}
3030
3031
3032bool STEP_PCB_MODEL::WriteXAO( const wxString& aFileName )
3033{
3034 wxFileName fn( aFileName );
3035
3036 wxFFileOutputStream ffStream( fn.GetFullPath() );
3037 wxStdOutputStream file( ffStream );
3038
3039 if( !ffStream.IsOk() )
3040 {
3041 m_reporter->Report( wxString::Format( "Could not open file '%s'", fn.GetFullPath() ),
3043 return false;
3044 }
3045
3047
3048 // s_assy = shape tool for the source
3049 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( m_doc->Main() );
3050
3051 // retrieve assembly as a single shape
3052 const TopoDS_Shape shape = getOneShape( s_assy );
3053
3054 std::map<wxString, std::vector<int>> groups[4];
3055 std::map<wxString, double> groupAreas;
3056 TopExp_Explorer exp;
3057 int faceIndex = 0;
3058
3059 for( exp.Init( shape, TopAbs_FACE ); exp.More(); exp.Next() )
3060 {
3061 TopoDS_Shape subShape = exp.Current();
3062
3063 Bnd_Box bbox;
3064 BRepBndLib::Add( subShape, bbox );
3065
3066 for( const auto& [padKey, pairs] : m_pad_points )
3067 {
3068 for( const auto& pair : pairs )
3069 {
3070 const auto& [point, padTestShape] = pair;
3071
3072 if( bbox.IsOut( point ) )
3073 continue;
3074
3075 BRepAdaptor_Surface surface( TopoDS::Face( subShape ) );
3076
3077 if( surface.GetType() != GeomAbs_Plane )
3078 continue;
3079
3080 BRepExtrema_DistShapeShape dist( padTestShape, subShape );
3081 dist.Perform();
3082
3083 if( !dist.IsDone() )
3084 continue;
3085
3086 if( dist.Value() < Precision::Approximation() )
3087 {
3088 // Push as a face group
3089 groups[2][padKey].push_back( faceIndex );
3090
3091 GProp_GProps system;
3092 BRepGProp::SurfaceProperties( subShape, system );
3093
3094 double surfaceArea = system.Mass() / 1e6; // Convert to meters^2
3095 groupAreas[padKey] += surfaceArea;
3096 }
3097 }
3098 }
3099
3100 faceIndex++;
3101 }
3102
3103 // Based on Gmsh code
3104 file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
3105 file << "<XAO version=\"1.0\" author=\"KiCad\">" << std::endl;
3106 file << " <geometry name=\"" << fn.GetName() << "\">" << std::endl;
3107 file << " <shape format=\"BREP\"><![CDATA[";
3108#if OCC_VERSION_HEX < 0x070600
3109 BRepTools::Write( shape, file );
3110#else
3111 BRepTools::Write( shape, file, Standard_True, Standard_True, TopTools_FormatVersion_VERSION_1 );
3112#endif
3113 file << "]]></shape>" << std::endl;
3114 file << " <topology>" << std::endl;
3115
3116 TopTools_IndexedMapOfShape mainMap;
3117 TopExp::MapShapes( shape, mainMap );
3118 std::set<int> topo[4];
3119
3120 static const TopAbs_ShapeEnum c_dimShapeTypes[] = { TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE,
3121 TopAbs_SOLID };
3122
3123 static const std::string c_dimLabel[] = { "vertex", "edge", "face", "solid" };
3124 static const std::string c_dimLabels[] = { "vertices", "edges", "faces", "solids" };
3125
3126 for( int dim = 0; dim < 4; dim++ )
3127 {
3128 for( exp.Init( shape, c_dimShapeTypes[dim] ); exp.More(); exp.Next() )
3129 {
3130 TopoDS_Shape subShape = exp.Current();
3131 int idx = mainMap.FindIndex( subShape );
3132
3133 if( idx && !topo[dim].count( idx ) )
3134 topo[dim].insert( idx );
3135 }
3136 }
3137
3138 for( int dim = 0; dim <= 3; dim++ )
3139 {
3140 std::string labels = c_dimLabels[dim];
3141 std::string label = c_dimLabel[dim];
3142
3143 file << " <" << labels << " count=\"" << topo[dim].size() << "\">" << std::endl;
3144 int index = 0;
3145
3146 for( auto p : topo[dim] )
3147 {
3148 std::string name( "" );
3149 file << " <" << label << " index=\"" << index << "\" "
3150 << "name=\"" << name << "\" "
3151 << "reference=\"" << p << "\"/>" << std::endl;
3152
3153 index++;
3154 }
3155 file << " </" << labels << ">" << std::endl;
3156 }
3157
3158 file << " </topology>" << std::endl;
3159 file << " </geometry>" << std::endl;
3160 file << " <groups count=\""
3161 << groups[0].size() + groups[1].size() + groups[2].size() + groups[3].size() << "\">"
3162 << std::endl;
3163
3164 int groupNumber = 1;
3165
3166 m_reporter->Report( wxT( "Pad definitions:" ), RPT_SEVERITY_DEBUG );
3167 m_reporter->Report( wxT( "Number\tName\tArea (m^2)" ), RPT_SEVERITY_DEBUG );
3168
3169 for( int dim = 0; dim <= 3; dim++ )
3170 {
3171 std::string label = c_dimLabel[dim];
3172
3173 for( auto g : groups[dim] )
3174 {
3175 //std::string name = model->getPhysicalName( dim, g.first );
3176 wxString name = g.first;
3177
3178 if( name.empty() )
3179 { // create same unique name as for MED export
3180 std::ostringstream gs;
3181 gs << "G_" << dim << "D_" << g.first;
3182 name = gs.str();
3183 }
3184 file << " <group name=\"" << name << "\" dimension=\"" << label;
3185//#if 1
3186// // Gmsh XAO extension: also save the physical tag, so that XAO can be used
3187// // to serialize OCC geometries, ready to be used by GetDP, GmshFEM & co
3188// file << "\" tag=\"" << g.first;
3189//#endif
3190 file << "\" count=\"" << g.second.size() << "\">" << std::endl;
3191
3192 for( auto index : g.second )
3193 file << " <element index=\"" << index << "\"/>" << std::endl;
3194
3195 file << " </group>" << std::endl;
3196
3197 m_reporter->Report( wxString::Format( "%d\t%s\t%g",
3198 groupNumber,
3199 name,
3200 groupAreas[name] ),
3202
3203 groupNumber++;
3204 }
3205 }
3206
3207 m_reporter->Report( wxT( "" ), RPT_SEVERITY_DEBUG );
3208
3209 file << " </groups>" << std::endl;
3210 file << " <fields count=\"0\"/>" << std::endl;
3211 file << "</XAO>" << std::endl;
3212
3213 return true;
3214}
3215
3216
3217bool STEP_PCB_MODEL::getModelLabel( const std::string& aFileNameUTF8, const VECTOR3D& aScale, TDF_Label& aLabel,
3218 bool aSubstituteModels, wxString* aErrorMessage )
3219{
3220 std::string model_key = aFileNameUTF8 + "_" + std::to_string( aScale.x )
3221 + "_" + std::to_string( aScale.y ) + "_" + std::to_string( aScale.z );
3222
3223 MODEL_MAP::const_iterator mm = m_models.find( model_key );
3224
3225 if( mm != m_models.end() )
3226 {
3227 aLabel = mm->second;
3228 return true;
3229 }
3230
3231 aLabel.Nullify();
3232
3233 Handle( TDocStd_Document ) doc;
3234 m_app->NewDocument( "MDTV-XCAF", doc );
3235
3236 wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
3237 MODEL3D_FORMAT_TYPE modelFmt = fileType( aFileNameUTF8.c_str() );
3238
3239 wxFileName modelFile( fileName );
3240 std::string pname( modelFile.GetName().ToUTF8() );
3241 TCollection_ExtendedString partname( pname.c_str() );
3242
3243 switch( modelFmt )
3244 {
3245 case FMT_IGES:
3246 if( !readIGES( doc, aFileNameUTF8.c_str() ) )
3247 {
3248 m_reporter->Report( wxString::Format( wxT( "readIGES() failed on filename '%s'." ),
3249 fileName ),
3251 return false;
3252 }
3253
3254 break;
3255
3256 case FMT_STEP:
3257 if( !readSTEP( doc, aFileNameUTF8.c_str() ) )
3258 {
3259 m_reporter->Report( wxString::Format( wxT( "readSTEP() failed on filename '%s'." ),
3260 fileName ),
3262 return false;
3263 }
3264
3265 break;
3266
3267 case FMT_STEPZ:
3268 {
3269 // To export a compressed step file (.stpz or .stp.gz file), the best way is to
3270 // decaompress it in a temporaty file and load this temporary file
3271 wxFFileInputStream ifile( fileName );
3272 wxFileName outFile( fileName );
3273
3274 outFile.SetPath( wxStandardPaths::Get().GetTempDir() );
3275 outFile.SetExt( wxT( "step" ) );
3276 wxFileOffset size = ifile.GetLength();
3277
3278 if( size == wxInvalidOffset )
3279 {
3280 m_reporter->Report( wxString::Format( wxT( "getModelLabel() failed on filename '%s'." ),
3281 fileName ),
3283 return false;
3284 }
3285
3286 {
3287 bool success = false;
3288 wxFFileOutputStream ofile( outFile.GetFullPath() );
3289
3290 if( !ofile.IsOk() )
3291 return false;
3292
3293 char* buffer = new char[size];
3294
3295 ifile.Read( buffer, size );
3296 std::string expanded;
3297
3298 try
3299 {
3300 expanded = gzip::decompress( buffer, size );
3301 success = true;
3302 }
3303 catch( ... )
3304 {
3305 m_reporter->Report( wxString::Format( wxT( "failed to decompress '%s'." ),
3306 fileName ),
3308 }
3309
3310 if( expanded.empty() )
3311 {
3312 ifile.Reset();
3313 ifile.SeekI( 0 );
3314 wxZipInputStream izipfile( ifile );
3315 std::unique_ptr<wxZipEntry> zip_file( izipfile.GetNextEntry() );
3316
3317 if( zip_file && !zip_file->IsDir() && izipfile.CanRead() )
3318 {
3319 izipfile.Read( ofile );
3320 success = true;
3321 }
3322 }
3323 else
3324 {
3325 ofile.Write( expanded.data(), expanded.size() );
3326 }
3327
3328 delete[] buffer;
3329 ofile.Close();
3330
3331 if( success )
3332 {
3333 std::string altFileNameUTF8 = TO_UTF8( outFile.GetFullPath() );
3334 success = getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false );
3335 }
3336
3337 return success;
3338 }
3339
3340 break;
3341 }
3342
3343 case FMT_WRL:
3344 case FMT_WRZ:
3345 /* WRL files are preferred for internal rendering, due to superior material properties, etc.
3346 * However they are not suitable for MCAD export.
3347 *
3348 * If a .wrl file is specified, attempt to locate a replacement file for it.
3349 *
3350 * If a valid replacement file is found, the label for THAT file will be associated with
3351 * the .wrl file
3352 */
3353 if( aSubstituteModels )
3354 {
3355 wxFileName wrlName( fileName );
3356
3357 wxString basePath = wrlName.GetPath();
3358 wxString baseName = wrlName.GetName();
3359
3360 // List of alternate files to look for
3361 // Given in order of preference
3362 // (Break if match is found)
3363 wxArrayString alts;
3364
3365 // Step files
3366 alts.Add( wxT( "stp" ) );
3367 alts.Add( wxT( "step" ) );
3368 alts.Add( wxT( "STP" ) );
3369 alts.Add( wxT( "STEP" ) );
3370 alts.Add( wxT( "Stp" ) );
3371 alts.Add( wxT( "Step" ) );
3372 alts.Add( wxT( "stpz" ) );
3373 alts.Add( wxT( "stpZ" ) );
3374 alts.Add( wxT( "STPZ" ) );
3375 alts.Add( wxT( "step.gz" ) );
3376 alts.Add( wxT( "stp.gz" ) );
3377
3378 // IGES files
3379 alts.Add( wxT( "iges" ) );
3380 alts.Add( wxT( "IGES" ) );
3381 alts.Add( wxT( "igs" ) );
3382 alts.Add( wxT( "IGS" ) );
3383
3384 //TODO - Other alternative formats?
3385
3386 for( const auto& alt : alts )
3387 {
3388 wxFileName altFile( basePath, baseName + wxT( "." ) + alt );
3389
3390 if( altFile.IsOk() && altFile.FileExists() )
3391 {
3392 std::string altFileNameUTF8 = TO_UTF8( altFile.GetFullPath() );
3393
3394 // When substituting a STEP/IGS file for VRML, do not apply the VRML scaling
3395 // to the new STEP model. This process of auto-substitution is janky as all
3396 // heck so let's not mix up un-displayed scale factors with potentially
3397 // mis-matched files. And hope that the user doesn't have multiples files
3398 // named "model.wrl" and "model.stp" referring to different parts.
3399 // TODO: Fix model handling in v7. Default models should only be STP.
3400 // Have option to override this in DISPLAY.
3401 if( getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false ) )
3402 {
3403 return true;
3404 }
3405 }
3406 }
3407 }
3408
3409 // VRML models only work when exporting to mesh formats
3410 // Also OCCT < 7.9.0 fails to load most VRML 2.0 models because of Switch nodes
3414 {
3415 if( readVRML( doc, aFileNameUTF8.c_str() ) )
3416 {
3417 Handle( XCAFDoc_ShapeTool ) shapeTool = XCAFDoc_DocumentTool::ShapeTool( doc->Main() );
3418
3419 prefixNames( shapeTool->Label(), partname );
3420 }
3421 else
3422 {
3423 m_reporter->Report( wxString::Format( wxT( "readVRML() failed on filename '%s'." ), fileName ),
3425 return false;
3426 }
3427 }
3428 else
3429 {
3430 if( aErrorMessage )
3431 aErrorMessage->Printf( _( "Cannot use VRML models when exporting to non-mesh formats." ) );
3432
3433 return false;
3434 }
3435
3436 break;
3437
3438 // TODO: implement IDF and EMN converters
3439
3440 default:
3441 m_reporter->Report( wxString::Format( _( "Cannot identify actual file type for '%s'." ),
3442 fileName ),
3444 return false;
3445 }
3446
3447 aLabel = transferModel( doc, m_doc, aScale );
3448
3449 if( aLabel.IsNull() )
3450 {
3451 m_reporter->Report( wxString::Format( _( "Could not transfer model data from file '%s'." ),
3452 fileName ),
3454 return false;
3455 }
3456
3457 // attach the PART NAME ( base filename: note that in principle
3458 // different models may have the same base filename )
3459 TDataStd_Name::Set( aLabel, partname );
3460
3461 m_models.insert( MODEL_DATUM( model_key, aLabel ) );
3462 ++m_components;
3463 return true;
3464}
3465
3466
3467bool STEP_PCB_MODEL::getModelLocation( bool aBottom, const VECTOR2D& aPosition, double aRotation,
3468 const VECTOR3D& aOffset, const VECTOR3D& aOrientation,
3469 TopLoc_Location& aLocation )
3470{
3471 // Order of operations:
3472 // a. aOrientation is applied -Z*-Y*-X
3473 // b. aOffset is applied
3474 // Top ? add thickness to the Z offset
3475 // c. Bottom ? Rotate on X axis (in contrast to most ECAD which mirror on Y),
3476 // then rotate on +Z
3477 // Top ? rotate on -Z
3478 // d. aPosition is applied
3479 //
3480 // Note: Y axis is inverted in KiCad
3481
3482 gp_Trsf lPos;
3483 lPos.SetTranslation( gp_Vec( aPosition.x, -aPosition.y, 0.0 ) );
3484
3485 // Offset board thickness
3486 VECTOR3D offset( aOffset );
3487 offset.z += BOARD_OFFSET;
3488
3489 double boardThickness;
3490 double boardZPos;
3491 getBoardBodyZPlacement( boardZPos, boardThickness );
3492 double top = std::max( boardZPos, boardZPos + boardThickness );
3493 double bottom = std::min( boardZPos, boardZPos + boardThickness );
3494
3495 // 3D step models are placed on the top of copper layers.
3496 // This is true for SMD shapes, and perhaps not always true for TH shapes,
3497 // but we use this Z position for any 3D shape.
3498 double f_pos, f_thickness;
3499 getLayerZPlacement( F_Cu, f_pos, f_thickness );
3500 top += f_thickness;
3501 getLayerZPlacement( B_Cu, f_pos, f_thickness );
3502 bottom += f_thickness; // f_thickness is < 0 for B_Cu layer
3503
3504 gp_Trsf lRot;
3505
3506 if( aBottom )
3507 {
3508 offset.z -= bottom;
3509 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
3510 lPos.Multiply( lRot );
3511 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), M_PI );
3512 lPos.Multiply( lRot );
3513 }
3514 else
3515 {
3516 offset.z += top;
3517 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
3518 lPos.Multiply( lRot );
3519 }
3520
3521 gp_Trsf lOff;
3522 lOff.SetTranslation( gp_Vec( offset.x, offset.y, offset.z ) );
3523 lPos.Multiply( lOff );
3524
3525 gp_Trsf lOrient;
3526 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), -aOrientation.z );
3527 lPos.Multiply( lOrient );
3528 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 1.0, 0.0 ) ), -aOrientation.y );
3529 lPos.Multiply( lOrient );
3530 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), -aOrientation.x );
3531 lPos.Multiply( lOrient );
3532
3533 aLocation = TopLoc_Location( lPos );
3534 return true;
3535}
3536
3537
3538bool STEP_PCB_MODEL::readIGES( Handle( TDocStd_Document )& doc, const char* fname )
3539{
3540 IGESControl_Controller::Init();
3541 IGESCAFControl_Reader reader;
3542 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
3543
3544 if( stat != IFSelect_RetDone )
3545 return false;
3546
3547 // Enable user-defined shape precision
3548 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
3549 return false;
3550
3551 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
3552 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
3553 return false;
3554
3555 // set other translation options
3556 reader.SetColorMode( true ); // use model colors
3557 reader.SetNameMode( false ); // don't use IGES label names
3558 reader.SetLayerMode( false ); // ignore LAYER data
3559
3560 if( !reader.Transfer( doc ) )
3561 {
3562 if( doc->CanClose() == CDM_CCS_OK )
3563 doc->Close();
3564
3565 return false;
3566 }
3567
3568 // are there any shapes to translate?
3569 if( reader.NbShapes() < 1 )
3570 {
3571 if( doc->CanClose() == CDM_CCS_OK )
3572 doc->Close();
3573
3574 return false;
3575 }
3576
3577 return true;
3578}
3579
3580
3581bool STEP_PCB_MODEL::readSTEP( Handle( TDocStd_Document )& doc, const char* fname )
3582{
3583 STEPCAFControl_Reader reader;
3584 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
3585
3586 if( stat != IFSelect_RetDone )
3587 return false;
3588
3589 // Enable user-defined shape precision
3590 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
3591 return false;
3592
3593 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
3594 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
3595 return false;
3596
3597 // set other translation options
3598 reader.SetColorMode( true ); // use model colors
3599 reader.SetNameMode( true ); // use label names
3600 reader.SetLayerMode( false ); // ignore LAYER data
3601
3602 if( !reader.Transfer( doc ) )
3603 {
3604 if( doc->CanClose() == CDM_CCS_OK )
3605 doc->Close();
3606
3607 return false;
3608 }
3609
3610 // are there any shapes to translate?
3611 if( reader.NbRootsForTransfer() < 1 )
3612 {
3613 if( doc->CanClose() == CDM_CCS_OK )
3614 doc->Close();
3615
3616 return false;
3617 }
3618
3619 return true;
3620}
3621
3622
3623bool STEP_PCB_MODEL::readVRML( Handle( TDocStd_Document ) & doc, const char* fname )
3624{
3625#if OCC_VERSION_HEX >= 0x070700
3626 VrmlAPI_CafReader reader;
3627 RWMesh_CoordinateSystemConverter conv;
3628 conv.SetInputLengthUnit( 2.54 );
3629 reader.SetCoordinateSystemConverter( conv );
3630 reader.SetDocument( doc );
3631
3632 if( !reader.Perform( TCollection_AsciiString( fname ), Message_ProgressRange() ) )
3633 return false;
3634
3635 return true;
3636#else
3637 return false;
3638#endif
3639}
3640
3641
3642void STEP_PCB_MODEL::transferColors( Handle( XCAFDoc_ShapeTool )& aSrcShapeTool,
3643 Handle( XCAFDoc_ColorTool )& aSrcColorTool,
3644 Handle( XCAFDoc_ShapeTool )& aDstShapeTool,
3645 Handle( XCAFDoc_ColorTool )& aDstColorTool )
3646{
3647 // Get all shapes from the source document
3648 TDF_LabelSequence srcLabels;
3649 aSrcShapeTool->GetShapes( srcLabels );
3650
3651 for( Standard_Integer i = 1; i <= srcLabels.Length(); i++ )
3652 {
3653 TDF_Label srcLabel = srcLabels.Value( i );
3654 TopoDS_Shape srcShape = aSrcShapeTool->GetShape( srcLabel );
3655
3656 if( srcShape.IsNull() )
3657 continue;
3658
3659 // Try to find the same shape in the destination document
3660 TDF_Label dstLabel;
3661
3662 if( !aDstShapeTool->Search( srcShape, dstLabel, Standard_True, Standard_True, Standard_False ) )
3663 continue;
3664
3665 // Transfer surface color
3666 Quantity_ColorRGBA surfColor;
3667
3668 if( aSrcColorTool->GetColor( srcLabel, XCAFDoc_ColorSurf, surfColor ) )
3669 aDstColorTool->SetColor( dstLabel, surfColor, XCAFDoc_ColorSurf );
3670
3671 // Transfer curve color
3672 Quantity_ColorRGBA curvColor;
3673
3674 if( aSrcColorTool->GetColor( srcLabel, XCAFDoc_ColorCurv, curvColor ) )
3675 aDstColorTool->SetColor( dstLabel, curvColor, XCAFDoc_ColorCurv );
3676
3677 // Transfer generic color
3678 Quantity_ColorRGBA genColor;
3679
3680 if( aSrcColorTool->GetColor( srcLabel, XCAFDoc_ColorGen, genColor ) )
3681 aDstColorTool->SetColor( dstLabel, genColor, XCAFDoc_ColorGen );
3682
3683 // Also check for colors on individual faces
3684 if( aSrcShapeTool->IsSimpleShape( srcLabel ) )
3685 {
3686 TopoDS_Shape shape = aSrcShapeTool->GetShape( srcLabel );
3687
3688 for( TopExp_Explorer exp( shape, TopAbs_FACE ); exp.More(); exp.Next() )
3689 {
3690 TopoDS_Face face = TopoDS::Face( exp.Current() );
3691 Quantity_ColorRGBA faceColor;
3692
3693 if( aSrcColorTool->GetColor( face, XCAFDoc_ColorSurf, faceColor ) )
3694 aDstColorTool->SetColor( face, faceColor, XCAFDoc_ColorSurf );
3695 else if( aSrcColorTool->GetColor( face, XCAFDoc_ColorGen, faceColor ) )
3696 aDstColorTool->SetColor( face, faceColor, XCAFDoc_ColorGen );
3697 }
3698 }
3699 }
3700
3701 // Also iterate through subshapes and components recursively
3702 TDF_LabelSequence srcFreeShapes;
3703 aSrcShapeTool->GetFreeShapes( srcFreeShapes );
3704
3705 std::function<void( const TDF_Label& )> transferColorsRecursive = [&]( const TDF_Label& aLabel )
3706 {
3707 TopoDS_Shape shape = aSrcShapeTool->GetShape( aLabel );
3708
3709 if( shape.IsNull() )
3710 return;
3711
3712 // Find this shape in destination
3713 TDF_Label dstLabel;
3714
3715 if( aDstShapeTool->Search( shape, dstLabel, Standard_True, Standard_True, Standard_False ) )
3716 {
3717 Quantity_ColorRGBA color;
3718
3719 if( aSrcColorTool->GetColor( aLabel, XCAFDoc_ColorSurf, color ) )
3720 aDstColorTool->SetColor( dstLabel, color, XCAFDoc_ColorSurf );
3721
3722 if( aSrcColorTool->GetColor( aLabel, XCAFDoc_ColorCurv, color ) )
3723 aDstColorTool->SetColor( dstLabel, color, XCAFDoc_ColorCurv );
3724
3725 if( aSrcColorTool->GetColor( aLabel, XCAFDoc_ColorGen, color ) )
3726 aDstColorTool->SetColor( dstLabel, color, XCAFDoc_ColorGen );
3727 }
3728
3729 // Process children
3730 for( TDF_ChildIterator it( aLabel ); it.More(); it.Next() )
3731 transferColorsRecursive( it.Value() );
3732
3733 // Process components if this is an assembly
3734 if( aSrcShapeTool->IsAssembly( aLabel ) )
3735 {
3736 TDF_LabelSequence components;
3737 aSrcShapeTool->GetComponents( aLabel, components );
3738
3739 for( Standard_Integer j = 1; j <= components.Length(); j++ )
3740 {
3741 TDF_Label compLabel = components.Value( j );
3742 TDF_Label refLabel;
3743
3744 if( aSrcShapeTool->GetReferredShape( compLabel, refLabel ) )
3745 transferColorsRecursive( refLabel );
3746 }
3747 }
3748 };
3749
3750 for( Standard_Integer i = 1; i <= srcFreeShapes.Length(); i++ )
3751 transferColorsRecursive( srcFreeShapes.Value( i ) );
3752}
3753
3754
3755TDF_Label STEP_PCB_MODEL::transferModel( Handle( TDocStd_Document ) & source,
3756 Handle( TDocStd_Document ) & dest, const VECTOR3D& aScale )
3757{
3758 // transfer data from Source into a top level component of Dest
3759 // s_assy = shape tool for the source
3760 Handle( XCAFDoc_ShapeTool ) s_assy = XCAFDoc_DocumentTool::ShapeTool( source->Main() );
3761 Handle( XCAFDoc_ColorTool ) s_color = XCAFDoc_DocumentTool::ColorTool( source->Main() );
3762
3763 // retrieve all free shapes within the assembly
3764 TDF_LabelSequence frshapes;
3765 s_assy->GetFreeShapes( frshapes );
3766
3767 // d_assy = shape tool for the destination
3768 Handle( XCAFDoc_ShapeTool ) d_assy = XCAFDoc_DocumentTool::ShapeTool( dest->Main() );
3769 Handle( XCAFDoc_ColorTool ) d_color = XCAFDoc_DocumentTool::ColorTool( dest->Main() );
3770
3771 // create a new shape within the destination and set the assembly tool to point to it
3772 TDF_Label d_targetLabel = d_assy->NewShape();
3773
3774 auto copyLabel = [&]( TDF_Label& d_label, const TDF_Label& s_label ) -> bool
3775 {
3776 // TDocStd_XLinkTool::Copy requires the source to be "self-contained", meaning it has
3777 // no external references. Some STEP files (e.g. from Fusion 360 with linked components)
3778 // may contain internal references that violate this constraint. In such cases, we fall
3779 // back to extracting just the geometric shape without the full XDE document structure.
3780 if( TDF_Tool::IsSelfContained( s_label ) )
3781 {
3782 TDocStd_XLinkTool link;
3783 link.Copy( d_label, s_label );
3784 return true;
3785 }
3786 else
3787 {
3788 // The source label is not self-contained. Extract the shape directly.
3789 TopoDS_Shape shape = s_assy->GetShape( s_label );
3790
3791 if( shape.IsNull() )
3792 return false;
3793
3794 // Add the shape directly without the XDE structure. This loses some metadata
3795 // like colors and names, but allows the model to be successfully transferred.
3796 d_assy->SetShape( d_label, shape );
3797
3798 m_reporter->Report( wxT( "Model contains non-self-contained data; some metadata may be lost." ),
3800 return true;
3801 }
3802 };
3803
3804 if( frshapes.Size() == 1 )
3805 {
3806 if( !copyLabel( d_targetLabel, frshapes.First() ) )
3807 {
3808 m_reporter->Report( wxT( "Failed to transfer model." ), RPT_SEVERITY_ERROR );
3809 return TDF_Label();
3810 }
3811 }
3812 else
3813 {
3814 // Rare case with multiple free shapes
3815 for( TDF_Label& s_shapeLabel : frshapes )
3816 {
3817 TDF_Label d_component = d_assy->NewShape();
3818
3819 if( !copyLabel( d_component, s_shapeLabel ) )
3820 {
3821 m_reporter->Report( wxT( "Failed to transfer model component." ), RPT_SEVERITY_ERROR );
3822 return TDF_Label();
3823 }
3824
3825 d_assy->AddComponent( d_targetLabel, d_component, TopLoc_Location() );
3826 }
3827 }
3828
3829 // Transfer colors from source to destination document
3830 // This is necessary because TDocStd_XLinkTool::Copy may not properly transfer
3831 // color associations which are stored separately in the ColorTool section
3832 transferColors( s_assy, s_color, d_assy, d_color );
3833
3834 if( aScale.x != 1.0 || aScale.y != 1.0 || aScale.z != 1.0 )
3835 rescaleShapes( d_targetLabel, gp_XYZ( aScale.x, aScale.y, aScale.z ) );
3836
3837 return d_targetLabel;
3838}
3839
3840
3841bool STEP_PCB_MODEL::performMeshing( Handle( XCAFDoc_ShapeTool ) & aShapeTool )
3842{
3843 TDF_LabelSequence freeShapes;
3844 aShapeTool->GetFreeShapes( freeShapes );
3845
3846 m_reporter->Report( wxT( "Meshing model" ), RPT_SEVERITY_DEBUG );
3847
3848 // GLTF is a mesh format, we have to trigger opencascade to mesh the shapes we composited into the asesmbly
3849 // To mesh models, lets just grab the free shape root and execute on them
3850 for( Standard_Integer i = 1; i <= freeShapes.Length(); ++i )
3851 {
3852 TDF_Label label = freeShapes.Value( i );
3853 TopoDS_Shape shape;
3854 aShapeTool->GetShape( label, shape );
3855
3856 // These deflection values basically affect the accuracy of the mesh generated, a tighter
3857 // deflection will result in larger meshes
3858 // We could make this a tunable parameter, but for now fix it
3859 const Standard_Real linearDeflection = 0.14;
3860 const Standard_Real angularDeflection = DEG2RAD( 30.0 );
3861 BRepMesh_IncrementalMesh mesh( shape, linearDeflection, Standard_False, angularDeflection,
3862 Standard_True );
3863 }
3864
3865 return true;
3866}
3867
3868
3869bool STEP_PCB_MODEL::WriteGLTF( const wxString& aFileName )
3870{
3871 /*if( !isBoardOutlineValid() )
3872 {
3873 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
3874 aFileName ),
3875 RPT_SEVERITY_ERROR );
3876 return false;
3877 }*/
3878
3880
3881 performMeshing( m_assy );
3882
3883 wxFileName fn( aFileName );
3884
3885 const char* tmpGltfname = "$tempfile$.glb";
3886 RWGltf_CafWriter cafWriter( tmpGltfname, true );
3887
3888 cafWriter.SetTransformationFormat( RWGltf_WriterTrsfFormat_Compact );
3889 cafWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit( 0.001 );
3890 cafWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(
3891 RWMesh_CoordinateSystem_Zup );
3892#if OCC_VERSION_HEX >= 0x070700
3893 cafWriter.SetParallel( true );
3894#endif
3895 TColStd_IndexedDataMapOfStringString metadata;
3896
3897 metadata.Add( TCollection_AsciiString( "pcb_name" ),
3898 TCollection_ExtendedString( fn.GetName().wc_str() ) );
3899 metadata.Add( TCollection_AsciiString( "source_pcb_file" ),
3900 TCollection_ExtendedString( fn.GetFullName().wc_str() ) );
3901 metadata.Add( TCollection_AsciiString( "generator" ),
3902 TCollection_AsciiString( wxString::Format( wxS( "KiCad %s" ), GetSemanticVersion() ).ToAscii() ) );
3903 metadata.Add( TCollection_AsciiString( "generated_at" ),
3904 TCollection_AsciiString( GetISO8601CurrentDateTime().ToAscii() ) );
3905
3906 bool success = true;
3907
3908 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
3909 wxString currCWD = wxGetCwd();
3910 wxString workCWD = fn.GetPath();
3911
3912 if( !workCWD.IsEmpty() )
3913 wxSetWorkingDirectory( workCWD );
3914
3915 success = cafWriter.Perform( m_doc, metadata, Message_ProgressRange() );
3916
3917 if( success )
3918 {
3919 // Preserve the permissions of the current file
3920 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpGltfname );
3921
3922 if( !wxRenameFile( tmpGltfname, fn.GetFullName(), true ) )
3923 {
3924 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'." ),
3925 tmpGltfname,
3926 fn.GetFullName() ),
3928 success = false;
3929 }
3930 }
3931
3932 wxSetWorkingDirectory( currCWD );
3933
3934 return success;
3935}
3936
3937
3938bool STEP_PCB_MODEL::WritePLY( const wxString& aFileName )
3939{
3940#if OCC_VERSION_HEX < 0x070700
3941 m_reporter->Report( wxT( "PLY export is not supported before OCCT 7.7.0" ), RPT_SEVERITY_ERROR );
3942 return false;
3943#else
3944
3945 if( !isBoardOutlineValid() )
3946 {
3947 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
3948 aFileName ),
3950 return false;
3951 }
3952
3954
3955 performMeshing( m_assy );
3956
3957 wxFileName fn( aFileName );
3958
3959 const char* tmpFname = "$tempfile$.ply";
3960 RWPly_CafWriter cafWriter( tmpFname );
3961
3962 cafWriter.SetFaceId( true ); // TODO: configurable SetPartId/SetFaceId
3963 cafWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit( 0.001 );
3964 cafWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem( RWMesh_CoordinateSystem_Zup );
3965
3966 TColStd_IndexedDataMapOfStringString metadata;
3967
3968 metadata.Add( TCollection_AsciiString( "pcb_name" ),
3969 TCollection_ExtendedString( fn.GetName().wc_str() ) );
3970 metadata.Add( TCollection_AsciiString( "source_pcb_file" ),
3971 TCollection_ExtendedString( fn.GetFullName().wc_str() ) );
3972 metadata.Add( TCollection_AsciiString( "generator" ),
3973 TCollection_AsciiString( wxString::Format( wxS( "KiCad %s" ),
3974 GetSemanticVersion() ).ToAscii() ) );
3975 metadata.Add( TCollection_AsciiString( "generated_at" ),
3976 TCollection_AsciiString( GetISO8601CurrentDateTime().ToAscii() ) );
3977
3978 bool success = true;
3979
3980 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
3981 wxString currCWD = wxGetCwd();
3982 wxString workCWD = fn.GetPath();
3983
3984 if( !workCWD.IsEmpty() )
3985 wxSetWorkingDirectory( workCWD );
3986
3987 success = cafWriter.Perform( m_doc, metadata, Message_ProgressRange() );
3988
3989 if( success )
3990 {
3991 // Preserve the permissions of the current file
3992 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname );
3993
3994 if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
3995 {
3996 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'." ),
3997 tmpFname,
3998 fn.GetFullName() ),
4000 success = false;
4001 }
4002 }
4003
4004 wxSetWorkingDirectory( currCWD );
4005
4006 return success;
4007#endif
4008}
4009
4010
4011bool STEP_PCB_MODEL::WriteSTL( const wxString& aFileName )
4012{
4013 if( !isBoardOutlineValid() )
4014 {
4015 m_reporter->Report( wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'." ),
4016 aFileName ),
4018 return false;
4019 }
4020
4022
4023 performMeshing( m_assy );
4024
4025 wxFileName fn( aFileName );
4026
4027 const char* tmpFname = "$tempfile$.stl";
4028
4029 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
4030 wxString currCWD = wxGetCwd();
4031 wxString workCWD = fn.GetPath();
4032
4033 if( !workCWD.IsEmpty() )
4034 wxSetWorkingDirectory( workCWD );
4035
4036 bool success = StlAPI_Writer().Write( getOneShape( m_assy ), tmpFname );
4037
4038 if( success )
4039 {
4040 // Preserve the permissions of the current file
4041 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname );
4042
4043 if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
4044 {
4045 m_reporter->Report( wxString::Format( _( "Cannot rename temporary file '%s' to '%s'." ),
4046 tmpFname,
4047 fn.GetFullName() ),
4049 success = false;
4050 }
4051 }
4052
4053 wxSetWorkingDirectory( currCWD );
4054
4055 return success;
4056}
4057
4058
4059
4060bool STEP_PCB_MODEL::WriteU3D( const wxString& aFileName )
4061{
4062 if( !isBoardOutlineValid() )
4063 {
4064 m_reporter->Report(
4065 wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ), aFileName ),
4067 return false;
4068 }
4069
4071
4072 performMeshing( m_assy );
4073
4074 wxFileName fn( aFileName );
4075
4076 const char* tmpFname = "$tempfile$.u3d";
4077
4078 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
4079 wxString currCWD = wxGetCwd();
4080 wxString workCWD = fn.GetPath();
4081
4082 if( !workCWD.IsEmpty() )
4083 wxSetWorkingDirectory( workCWD );
4084
4085 U3D::WRITER writer( tmpFname );
4086 bool success = writer.Perform( m_doc );
4087 if( success )
4088 {
4089 // Preserve the permissions of the current file
4090 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname );
4091
4092 if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
4093 {
4094 m_reporter->Report( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ), tmpFname,
4095 fn.GetFullName() ),
4097 success = false;
4098 }
4099 }
4100
4101 wxSetWorkingDirectory( currCWD );
4102
4103 return success;
4104}
4105
4106
4107bool STEP_PCB_MODEL::WritePDF( const wxString& aFileName )
4108{
4109 if( !isBoardOutlineValid() )
4110 {
4111 m_reporter->Report(
4112 wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ), aFileName ),
4114 return false;
4115 }
4116
4118
4119 performMeshing( m_assy );
4120
4121 wxFileName fn( aFileName );
4122
4123 wxFileName u3dTmpfn = wxFileName::CreateTempFileName( "" );
4124 wxFileName pdfTmpfn = wxFileName::CreateTempFileName( "" );
4125
4126 U3D::WRITER writer( u3dTmpfn.GetFullPath().ToStdString() );
4127 bool success = writer.Perform( m_doc );
4128
4129 // PDF test
4130 std::unique_ptr<PDF_PLOTTER> plotter = std::make_unique<PDF_PLOTTER>();
4131
4132 plotter->SetColorMode( true );
4133 plotter->Set3DExport( true );
4134 plotter->SetCreator( wxT( "Mark's awesome 3d exporter" ) );
4135 KIGFX::PCB_RENDER_SETTINGS renderSettings;
4136 plotter->SetRenderSettings( &renderSettings );
4137
4138 if( !plotter->OpenFile( pdfTmpfn.GetFullPath() ) )
4139 {
4140 m_reporter->Report( wxString::Format( wxT( "Cannot open temporary file '%s'.\n" ), pdfTmpfn.GetFullPath() ),
4142 success = false;
4143 }
4144 else
4145 {
4146 plotter->StartPlot( "1", "3D Model" );
4147 double fov_degrees = 16.5f;
4148
4149 // kind of an arbitrary distance determination
4150 float distance = sqrt( writer.GetMeshBoundingBox().SquareExtent() ) * 3;
4151
4152 std::vector<PDF_3D_VIEW> views;
4153
4154 VECTOR3D camTarget = writer.GetCenter();
4155
4156
4157 std::vector<float> c2wMatrix =
4158 PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 180.0f, -75.0f, 25.0f );
4159
4160 views.emplace_back( PDF_3D_VIEW{
4161 .m_name = "Default",
4162 .m_cameraMatrix = c2wMatrix,
4163 .m_cameraCenter = (float) distance,
4164 .m_fov = (float) fov_degrees,
4165 } );
4166
4167
4168
4169 c2wMatrix = PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 180.0, 0.0f, 0.0f );
4170
4171 views.emplace_back( PDF_3D_VIEW{
4172 .m_name = "Top",
4173 .m_cameraMatrix = c2wMatrix,
4174 .m_cameraCenter = (float) distance,
4175 .m_fov = (float) fov_degrees,
4176 } );
4177
4178
4179
4180 c2wMatrix = PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 0.0, 0.0f, 0.0f );
4181
4182 views.emplace_back( PDF_3D_VIEW{
4183 .m_name = "Bottom",
4184 .m_cameraMatrix = c2wMatrix,
4185 .m_cameraCenter = (float) distance,
4186 .m_fov = (float) fov_degrees,
4187 } );
4188
4189
4190
4191 c2wMatrix = PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 90.0f, -90.0f, 90.0f );
4192
4193 views.emplace_back( PDF_3D_VIEW{
4194 .m_name = "Front",
4195 .m_cameraMatrix = c2wMatrix,
4196 .m_cameraCenter = (float) distance,
4197 .m_fov = (float) fov_degrees,
4198 } );
4199
4200 plotter->Plot3DModel( u3dTmpfn.GetFullPath(), views );
4201 plotter->EndPlot();
4202 }
4203
4204 if( success )
4205 {
4206 // Preserve the permissions of the current file
4207 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), pdfTmpfn.GetFullPath() );
4208
4209 if( !wxRenameFile( pdfTmpfn.GetFullPath(), fn.GetFullPath(), true ) )
4210 {
4211 m_reporter->Report( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
4212 pdfTmpfn.GetFullPath(), fn.GetFullPath() ),
4214 success = false;
4215 }
4216 }
4217
4218 wxRemoveFile( u3dTmpfn.GetFullPath() );
4219
4220 return success;
4221}
int index
const char * name
@ ERROR_OUTSIDE
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
bool IsPrmSpecified(const wxString &aPrmValue)
BOARD_STACKUP_ITEM_TYPE
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_SILKSCREEN
@ BS_ITEM_TYPE_DIELECTRIC
@ BS_ITEM_TYPE_SOLDERMASK
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
wxString GetSemanticVersion()
Get the semantic version string for KiCad defined inside the KiCadVersion.cmake file in the variable ...
const wxString & GetShortNetname() const
int GetY() const
Definition board_item.h:105
int GetX() const
Definition board_item.h:99
FOOTPRINT * GetParentFootprint() const
int GetMaxError() const
Manage one layer needed to make a physical board.
wxString GetTypeName() const
int GetSublayersCount() const
PCB_LAYER_ID GetBrdLayerId() const
int GetThickness(int aDielectricSubLayer=0) const
BOARD_STACKUP_ITEM_TYPE GetType() const
Manage layers needed to make a physical board.
constexpr coord_type GetLeft() const
Definition box2.h:228
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr coord_type GetTop() const
Definition box2.h:229
constexpr coord_type GetBottom() const
Definition box2.h:222
wxString GetReferenceAsString() const
Definition footprint.h:750
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
double r
Red component.
Definition color4d.h:393
double g
Green component.
Definition color4d.h:394
COLOR4D & Darken(double aFactor)
Makes the color darker by a given factor.
Definition color4d.h:227
double a
Alpha component.
Definition color4d.h:396
double b
Blue component.
Definition color4d.h:395
PCB specific render settings.
Definition pcb_painter.h:82
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:296
Definition pad.h:55
PAD_PROP GetProperty() const
Definition pad.h:566
LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition pad.h:560
bool FlashLayer(int aLayer, bool aOnlyCheckIfPermitted=false) const
Check to see whether the pad should be flashed on the specific layer.
Definition pad.cpp:408
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
Definition pad.h:906
const VECTOR2I & GetDrillSize() const
Definition pad.h:317
PAD_ATTRIB GetAttribute() const
Definition pad.h:563
const wxString & GetNumber() const
Definition pad.h:137
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:2479
std::shared_ptr< SHAPE_SEGMENT > GetEffectiveHoleShape() const override
Return a SHAPE_SEGMENT object representing the pad's hole.
Definition pad.cpp:1031
static std::vector< float > CreateC2WMatrixFromAngles(const VECTOR3D &aTargetPosition, float aCameraDistance, float aYawDegrees, float aPitchDegrees, float aRollDegrees)
Generates the camera to world matrix for use with a 3D View.
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I B
Definition seg.h:50
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:120
const VECTOR2I & GetP1() const
Definition shape_arc.h:119
double GetRadius() const
const VECTOR2I & GetP0() const
Definition shape_arc.h:118
const VECTOR2I & GetCenter() const
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_ARC & Arc(size_t aArc) const
bool IsClosed() const override
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
void Clear()
Remove all points from the line chain.
const std::optional< INTERSECTION > SelfIntersectingWithArcs() const
Check if the line chain is self-intersecting.
int NextShape(int aPointIndex) const
Return the vertex index of the next shape in the chain, or -1 if aPointIndex is the last shape.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
const SHAPE_LINE_CHAIN Slice(int aStartIndex, int aEndIndex) const
Return a subset of this line chain containing the [start_index, end_index] range of points.
virtual size_t GetSegmentCount() const override
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
bool IsArcSegment(size_t aSegment) const
void RemoveShape(int aPointIndex)
Remove the shape at the given index from the line chain.
bool IsArcStart(size_t aIndex) const
Represent a set of closed polygons.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
int FullPointCount() const
Return the number of points in the shape poly set.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int OutlineCount() const
Return the number of outlines in the set.
const POLYGON & CPolygon(int aIndex) const
const std::vector< POLYGON > & CPolygons() const
const SEG & GetSeg() const
int GetWidth() const override
bool MakeShapeAsThickSegment(TopoDS_Shape &aShape, const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint, double aWidth, double aThickness, double aZposition, const VECTOR2D &aOrigin)
Make a segment shape based on start and end point.
OUTPUT_FORMAT m_outFmt
The current output format for created file.
void SetCopperColor(double r, double g, double b)
bool WritePLY(const wxString &aFileName)
bool AddCountersink(const VECTOR2I &aPosition, int aDiameter, int aDepth, int aAngle, bool aFrontSide, const VECTOR2D &aOrigin)
Add a countersink shape to remove board material from the top or bottom of a hole.
std::map< wxString, std::vector< TopoDS_Shape > > m_board_copper_vias
std::map< wxString, std::vector< TopoDS_Shape > > m_board_copper_pads
bool WriteSTEP(const wxString &aFileName, bool aOptimize, bool compress)
std::vector< TopoDS_Shape > m_board_back_mask
wxString m_pcbName
Name of the PCB, which will most likely be the file name of the path.
std::vector< TopoDS_Shape > m_boardCutouts
bool AddPolygonShapes(const SHAPE_POLY_SET *aPolyShapes, PCB_LAYER_ID aLayer, const VECTOR2D &aOrigin, const wxString &aNetname)
bool CreatePCB(SHAPE_POLY_SET &aOutline, const VECTOR2D &aOrigin, bool aPushBoardBody)
void getBoardBodyZPlacement(double &aZPos, double &aThickness)
TDF_Label transferModel(Handle(TDocStd_Document)&source, Handle(TDocStd_Document) &dest, const VECTOR3D &aScale)
TDF_Label m_assy_label
bool getModelLocation(bool aBottom, const VECTOR2D &aPosition, double aRotation, const VECTOR3D &aOffset, const VECTOR3D &aOrientation, TopLoc_Location &aLocation)
void SetFuseShapes(bool aValue)
bool WriteXAO(const wxString &aFileName)
bool WriteGLTF(const wxString &aFileName)
Write the assembly in binary GLTF Format.
REPORTER * m_reporter
std::vector< TDF_Label > m_pcb_labels
STEP_PCB_MODEL(const wxString &aPcbName, REPORTER *aReporter)
void transferColors(Handle(XCAFDoc_ShapeTool)&aSrcShapeTool, Handle(XCAFDoc_ColorTool)&aSrcColorTool, Handle(XCAFDoc_ShapeTool)&aDstShapeTool, Handle(XCAFDoc_ColorTool)&aDstColorTool)
Transfer color information from source document to destination document.
bool AddBackdrill(const SHAPE_SEGMENT &aShape, PCB_LAYER_ID aLayerStart, PCB_LAYER_ID aLayerEnd, const VECTOR2D &aOrigin)
Add a backdrill hole shape to remove board material and copper plating.
void getLayerZPlacement(PCB_LAYER_ID aLayer, double &aZPos, double &aThickness)
std::vector< TopoDS_Shape > m_board_outlines
void SetSimplifyShapes(bool aValue)
bool WriteU3D(const wxString &aFileName)
bool readVRML(Handle(TDocStd_Document) &aDoc, const char *aFname)
void SetPadColor(double r, double g, double b)
bool AddCounterbore(const VECTOR2I &aPosition, int aDiameter, int aDepth, bool aFrontSide, const VECTOR2D &aOrigin)
Add a counterbore shape to remove board material from the top or bottom of a hole.
void SetEnabledLayers(const LSET &aLayers)
void SetExtraPadThickness(bool aValue)
bool performMeshing(Handle(XCAFDoc_ShapeTool) &aShapeTool)
bool MakePolygonAsWall(TopoDS_Shape &aShape, SHAPE_POLY_SET &aPolySet, double aHeight, double aZposition, const VECTOR2D &aOrigin)
Make a polygonal shape to create a vertical wall.
bool readSTEP(Handle(TDocStd_Document) &aDoc, const char *aFname)
std::vector< TopoDS_Shape > m_board_front_silk
Handle(XCAFApp_Application) m_app
bool WritePDF(const wxString &aFileName)
virtual ~STEP_PCB_MODEL()
std::vector< TopoDS_Shape > m_board_front_mask
double m_copperColor[3]
bool WriteSTL(const wxString &aFileName)
void SetStackup(const BOARD_STACKUP &aStackup)
void SetNetFilter(const wxString &aFilter)
double m_padColor[3]
std::map< wxString, std::vector< TopoDS_Shape > > m_board_copper
std::map< PCB_LAYER_ID, int > GetCopperLayerKnockouts(int aDiameter, int aDepth, int aAngle, bool aFrontSide)
Get the knockout diameters for copper layers that a counterbore or countersink crosses.
bool MakeShapes(std::vector< TopoDS_Shape > &aShapes, const SHAPE_POLY_SET &aPolySet, bool aConvertToArcs, double aThickness, double aZposition, const VECTOR2D &aOrigin)
Convert a SHAPE_POLY_SET to TopoDS_Shape's (polygonal vertical prisms, or flat faces)
bool AddBarrel(const SHAPE_SEGMENT &aShape, PCB_LAYER_ID aLayerTop, PCB_LAYER_ID aLayerBot, bool aVia, const VECTOR2D &aOrigin, const wxString &aNetname)
bool readIGES(Handle(TDocStd_Document) &aDoc, const char *aFname)
bool AddHole(const SHAPE_SEGMENT &aShape, int aPlatingThickness, PCB_LAYER_ID aLayerTop, PCB_LAYER_ID aLayerBot, bool aVia, const VECTOR2D &aOrigin, bool aCutCopper, bool aCutBody)
bool getModelLabel(const std::string &aFileNameUTF8, const VECTOR3D &aScale, TDF_Label &aLabel, bool aSubstituteModels, wxString *aErrorMessage=nullptr)
Load a 3D model data.
std::vector< TopoDS_Shape > m_copperCutouts
bool CompressSTEP(wxString &inputFile, wxString &outputFile)
std::vector< TopoDS_Shape > m_board_back_silk
void getCopperLayerZPlacement(PCB_LAYER_ID aLayer, double &aZPos, double &aThickness)
std::map< wxString, std::vector< std::pair< gp_Pnt, TopoDS_Shape > > > m_pad_points
bool AddPadShape(const PAD *aPad, const VECTOR2D &aOrigin, bool aVia, SHAPE_POLY_SET *aClipPolygon=nullptr)
bool AddComponent(const std::string &aFileName, const std::string &aRefDes, bool aBottom, const VECTOR2D &aPosition, double aRotation, const VECTOR3D &aOffset, const VECTOR3D &aOrientation, const VECTOR3D &aScale, bool aSubstituteModels=true)
std::map< wxString, std::vector< TopoDS_Shape > > m_board_copper_fused
void OCCSetMergeMaxDistance(double aDistance=OCC_MAX_DISTANCE_TO_MERGE_POINTS)
BOARD_STACKUP m_stackup
bool WriteBREP(const wxString &aFileName)
bool Perform(const Handle(TDocStd_Document) &aDocument)
Definition writer.cpp:967
const VECTOR3D & GetCenter() const
Definition writer.h:209
const Bnd_Box & GetMeshBoundingBox() const
Definition writer.h:210
wxString StringFromValue(double aValue, bool aAddUnitLabel=false, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
Converts aValue in internal units into a united string.
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:283
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a oblong shape to a polygon, using multiple segments.
#define _(s)
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:417
const wxChar *const traceKiCad2Step
Flag to enable KiCad2Step debug tracing.
Handle(KICAD3D_INFO) KICAD3D_INFO
wxString LayerName(int aLayer)
Returns the default display name for a given layer.
Definition layer_id.cpp:31
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:780
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:803
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:677
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ F_Mask
Definition layer_ids.h:97
@ F_SilkS
Definition layer_ids.h:100
@ B_SilkS
Definition layer_ids.h:101
@ F_Cu
Definition layer_ids.h:64
This file contains miscellaneous commonly used macros and functions.
bool DuplicatePermissions(const wxString &aSrc, const wxString &aDest)
Duplicates the file security data from one file to another ensuring that they are the same between bo...
Definition unix/io.cpp:47
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ CASTELLATED
a pad with a castellated through hole
Definition padstack.h:121
#define _HKI(x)
Definition page_info.cpp:44
#define USER_PREC
Plotting engines similar to ps (PostScript, Gerber, svg)
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_DEBUG
@ RPT_SEVERITY_INFO
static bool addSegment(VRML_LAYER &model, IDF_SEGMENT *seg, int icont, int iseg)
const std::vector< FAB_LAYER_COLOR > & GetStandardColors(BOARD_STACKUP_ITEM_TYPE aType)
wxString NotSpecifiedPrm()
#define KEY_PREPREG
#define KEY_CORE
MODEL3D_FORMAT_TYPE
@ FMT_STEP
@ FMT_IGES
@ FMT_IDF
@ FMT_WRZ
@ FMT_STEPZ
@ FMT_NONE
@ FMT_WRL
@ FMT_EMN
static TopoDS_Shape fuseShapesOrCompound(const TopTools_ListOfShape &aInputShapes, REPORTER *aReporter)
static bool colorFromStackup(BOARD_STACKUP_ITEM_TYPE aType, const wxString &aColorStr, COLOR4D &aColorOut)
static bool makeWireFromChain(BRepLib_MakeWire &aMkWire, const SHAPE_LINE_CHAIN &aChain, double aMergeOCCMaxDist, double aZposition, const VECTOR2D &aOrigin, REPORTER *aReporter)
static Standard_Boolean prefixNames(const TDF_Label &aLabel, const TCollection_ExtendedString &aPrefix)
#define APPROX_DBG(stmt)
static Standard_Boolean rescaleShapes(const TDF_Label &theLabel, const gp_XYZ &aScale)
static constexpr double BOARD_OFFSET
static wxString formatBBox(const BOX2I &aBBox)
static bool fuseShapes(auto &aInputShapes, TopoDS_Shape &aOutShape, REPORTER *aReporter)
static TopoDS_Compound makeCompound(const auto &aInputShapes)
static std::vector< FAB_LAYER_COLOR > s_soldermaskColors
MODEL3D_FORMAT_TYPE fileType(const char *aFileName)
static VECTOR2D CircleCenterFrom3Points(const VECTOR2D &p1, const VECTOR2D &p2, const VECTOR2D &p3)
static SHAPE_LINE_CHAIN approximateLineChainWithArcs(const SHAPE_LINE_CHAIN &aSrc)
static constexpr double USER_ANGLE_PREC
static TopoDS_Shape getOneShape(Handle(XCAFDoc_ShapeTool) aShapeTool)
static constexpr double OCC_MAX_DISTANCE_TO_MERGE_POINTS
Default distance between points to treat them as separate ones (mm) 0.001 mm or less is a reasonable ...
std::pair< std::string, TDF_Label > MODEL_DATUM
#define CLOSE_STREAM(var)
#define OPEN_ISTREAM(var, name)
wxString UnescapeString(const wxString &aSource)
wxString GetISO8601CurrentDateTime()
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
KIBIS top(path, &reporter)
VECTOR2I center
int radius
VECTOR2I end
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
#define M_PI
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:31
wxLogTrace helper definitions.
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
double DEG2RAD(double deg)
Definition trigo.h:166
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694
VECTOR3< double > VECTOR3D
Definition vector3.h:230