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