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