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
37#include <decompress.hpp>
38
39#include <footprint.h>
40#include <pad.h>
41#include <pcb_track.h>
42#include <kiplatform/io.h>
43#include <string_utils.h>
44#include <build_version.h>
46
47#include "step_pcb_model.h"
48#include "streamwrapper.h"
49
50#include <IGESCAFControl_Reader.hxx>
51#include <IGESCAFControl_Writer.hxx>
52#include <IGESControl_Controller.hxx>
53#include <IGESData_GlobalSection.hxx>
54#include <IGESData_IGESModel.hxx>
55#include <Interface_Static.hxx>
56#include <Quantity_Color.hxx>
57#include <STEPCAFControl_Reader.hxx>
58#include <STEPCAFControl_Writer.hxx>
59#include <APIHeaderSection_MakeHeader.hxx>
60#include <Standard_Version.hxx>
61#include <TCollection_ExtendedString.hxx>
62#include <TDataStd_Name.hxx>
63#include <TDataStd_TreeNode.hxx>
64#include <TDF_LabelSequence.hxx>
65#include <TDF_ChildIterator.hxx>
66#include <TopExp_Explorer.hxx>
67#include <XCAFDoc.hxx>
68#include <XCAFDoc_DocumentTool.hxx>
69#include <XCAFDoc_ColorTool.hxx>
70
71#include <BRep_Tool.hxx>
72#include <BRepMesh_IncrementalMesh.hxx>
73#include <BRepBuilderAPI.hxx>
74#include <BRepBuilderAPI_MakeEdge.hxx>
75#include <BRepBuilderAPI_Transform.hxx>
76#include <BRepBuilderAPI_GTransform.hxx>
77#include <BRepBuilderAPI_MakeFace.hxx>
78#include <BRepPrimAPI_MakePrism.hxx>
79#include <BRepPrimAPI_MakeCylinder.hxx>
80#include <BRepAlgoAPI_Cut.hxx>
81
82#include <BRepBndLib.hxx>
83#include <Bnd_BoundSortBox.hxx>
84
85#include <TopoDS.hxx>
86#include <TopoDS_Wire.hxx>
87#include <TopoDS_Face.hxx>
88#include <TopoDS_Compound.hxx>
89#include <TopoDS_Builder.hxx>
90#include <Standard_Failure.hxx>
91
92#include <Geom_Curve.hxx>
93#include <Geom_BezierCurve.hxx>
94#include <Geom_TrimmedCurve.hxx>
95
96#include <gp_Ax2.hxx>
97#include <gp_Circ.hxx>
98#include <gp_Dir.hxx>
99#include <gp_Pnt.hxx>
100#include <GC_MakeArcOfCircle.hxx>
101#include <GC_MakeCircle.hxx>
102
103#include <RWGltf_CafWriter.hxx>
104
105#include <macros.h>
106
107static constexpr double USER_PREC = 1e-4;
108static constexpr double USER_ANGLE_PREC = 1e-6;
109
110// nominal offset from the board
111static constexpr double BOARD_OFFSET = 0.05;
112
113// supported file types for 3D models
115{
123 FMT_WRZ
125
126
127MODEL3D_FORMAT_TYPE fileType( const char* aFileName )
128{
129 wxFileName lfile( wxString::FromUTF8Unchecked( aFileName ) );
130
131 if( !lfile.FileExists() )
132 {
133 wxString msg;
134 msg.Printf( wxT( " * fileType(): no such file: %s\n" ),
135 wxString::FromUTF8Unchecked( aFileName ) );
136
137 ReportMessage( msg );
138 return FMT_NONE;
139 }
140
141 wxString ext = lfile.GetExt().Lower();
142
143 if( ext == wxT( "wrl" ) )
144 return FMT_WRL;
145
146 if( ext == wxT( "wrz" ) )
147 return FMT_WRZ;
148
149 if( ext == wxT( "idf" ) )
150 return FMT_IDF; // component outline
151
152 if( ext == wxT( "emn" ) )
153 return FMT_EMN; // PCB assembly
154
155 if( ext == wxT( "stpz" ) || ext == wxT( "gz" ) )
156 return FMT_STEPZ;
157
158 OPEN_ISTREAM( ifile, aFileName );
159
160 if( ifile.fail() )
161 return FMT_NONE;
162
163 char iline[82];
164 memset( iline, 0, 82 );
165 ifile.getline( iline, 82 );
166 CLOSE_STREAM( ifile );
167 iline[81] = 0; // ensure NULL termination when string is too long
168
169 // check for STEP in Part 21 format
170 // (this can give false positives since Part 21 is not exclusively STEP)
171 if( !strncmp( iline, "ISO-10303-21;", 13 ) )
172 return FMT_STEP;
173
174 std::string fstr = iline;
175
176 // check for STEP in XML format
177 // (this can give both false positive and false negatives)
178 if( fstr.find( "urn:oid:1.0.10303." ) != std::string::npos )
179 return FMT_STEP;
180
181 // Note: this is a very simple test which can yield false positives; the only
182 // sure method for determining if a file *not* an IGES model is to attempt
183 // to load it.
184 if( iline[72] == 'S' && ( iline[80] == 0 || iline[80] == 13 || iline[80] == 10 ) )
185 return FMT_IGES;
186
187 return FMT_NONE;
188}
189
190
191STEP_PCB_MODEL::STEP_PCB_MODEL( const wxString& aPcbName )
192{
193 m_app = XCAFApp_Application::GetApplication();
194 m_app->NewDocument( "MDTV-XCAF", m_doc );
195 m_assy = XCAFDoc_DocumentTool::ShapeTool( m_doc->Main() );
196 m_assy_label = m_assy->NewShape();
197 m_hasPCB = false;
198 m_components = 0;
204 m_minx = 1.0e10; // absurdly large number; any valid PCB X value will be smaller
205 m_pcbName = aPcbName;
207}
208
209
211{
212 if( m_doc->CanClose() == CDM_CCS_OK )
213 m_doc->Close();
214}
215
216bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin )
217{
218 const std::shared_ptr<SHAPE_POLY_SET>& pad_shape = aPad->GetEffectivePolygon( ERROR_INSIDE );
219 bool success = true;
220 VECTOR2I pos = aPad->GetPosition();
221
222 for( PCB_LAYER_ID pcb_layer = F_Cu; ; pcb_layer = B_Cu )
223 {
224 TopoDS_Shape curr_shape;
225 double Zpos = pcb_layer == F_Cu ? m_boardThickness : -m_copperThickness;
226
227 if( aPad->IsOnLayer( pcb_layer ) )
228 {
229 // Make a shape on top/bottom copper layer: a cylinder for rond shapes (pad or via)
230 // and a polygon for other shapes:
231 if( aPad->GetShape() == PAD_SHAPE::CIRCLE )
232 {
233 curr_shape = BRepPrimAPI_MakeCylinder(
234 pcbIUScale.IUTomm( aPad->GetSizeX() ) * 0.5, m_copperThickness ).Shape();
235 gp_Trsf shift;
236 shift.SetTranslation( gp_Vec( pcbIUScale.IUTomm( pos.x - aOrigin.x ),
237 -pcbIUScale.IUTomm( pos.y - aOrigin.y ),
238 Zpos ) );
239 BRepBuilderAPI_Transform round_shape( curr_shape, shift );
240 m_board_copper_pads.push_back( round_shape.Shape() );
241 }
242 else
243 {
244 success = MakeShapes( m_board_copper_pads, *pad_shape, m_copperThickness, Zpos,
245 aOrigin );
246 }
247 }
248
249 if( pcb_layer == B_Cu )
250 break;
251 }
252
253 if( aPad->GetAttribute() == PAD_ATTRIB::PTH && aPad->IsOnLayer( F_Cu )
254 && aPad->IsOnLayer( B_Cu ) )
255 {
256 TopoDS_Shape plating;
257
258 std::shared_ptr<SHAPE_SEGMENT> seg_hole = aPad->GetEffectiveHoleShape();
259 double width = std::min( aPad->GetDrillSize().x, aPad->GetDrillSize().y );
260
261 if( MakeShapeAsThickSegment( plating, seg_hole->GetSeg().A, seg_hole->GetSeg().B, width,
263 aOrigin ) )
264 {
265 m_board_copper_pads.push_back( plating );
266 }
267 else
268 {
269 success = false;
270 }
271 }
272
273 if( !success ) // Error
274 ReportMessage( wxT( "OCC error adding pad/via polygon.\n" ) );
275
276 return success;
277}
278
279
280bool STEP_PCB_MODEL::AddViaShape( const PCB_VIA* aVia, const VECTOR2D& aOrigin )
281{
282 // A via is very similar to a round pad. So, for now, used AddPadHole() to
283 // create a via+hole shape
284 PAD dummy( nullptr );
285 int hole = aVia->GetDrillValue();
286 dummy.SetDrillSize( VECTOR2I( hole, hole ) );
287 dummy.SetPosition( aVia->GetStart() );
288 dummy.SetSize( VECTOR2I( aVia->GetWidth(), aVia->GetWidth() ) );
289
290 if( AddPadHole( &dummy, aOrigin ) )
291 {
292 if( !AddPadShape( &dummy, aOrigin ) )
293 return false;
294 }
295
296 return true;
297}
298
299
300bool STEP_PCB_MODEL::AddTrackSegment( const PCB_TRACK* aTrack, const VECTOR2D& aOrigin )
301{
302 PCB_LAYER_ID pcblayer = aTrack->GetLayer();
303
304 if( pcblayer != F_Cu && pcblayer != B_Cu )
305 return false;
306
307 TopoDS_Shape shape;
308 double zposition = pcblayer == F_Cu ? m_boardThickness : -m_copperThickness;
309
310 bool success = MakeShapeAsThickSegment( shape, aTrack->GetStart(), aTrack->GetEnd(),
311 aTrack->GetWidth(), m_copperThickness,
312 zposition, aOrigin );
313
314 if( success )
315 m_board_copper_tracks.push_back( shape );
316
317 return success;
318}
319
320
321bool STEP_PCB_MODEL::AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, bool aOnTop,
322 const VECTOR2D& aOrigin, bool aTrack )
323{
324 bool success = true;
325
326 if( aPolyShapes->IsEmpty() )
327 return true;
328
329 double z_pos = aOnTop ? m_boardThickness : -m_copperThickness;
330
331 if( !MakeShapes( aTrack ? m_board_copper_tracks : m_board_copper_zones, *aPolyShapes,
332 m_copperThickness, z_pos, aOrigin ) )
333 {
334 ReportMessage( wxString::Format(
335 wxT( "Could not add shape (%d points) to copper layer on %s.\n" ),
336 aPolyShapes->FullPointCount(), aOnTop ? wxT( "top" ) : wxT( "bottom" ) ) );
337
338 success = false;
339 }
340
341 return success;
342}
343
344
345bool STEP_PCB_MODEL::AddPadHole( const PAD* aPad, const VECTOR2D& aOrigin )
346{
347 if( aPad == nullptr || !aPad->GetDrillSize().x )
348 return false;
349
350 // TODO: make configurable
351 int platingThickness = aPad->GetAttribute() == PAD_ATTRIB::PTH ? pcbIUScale.mmToIU( 0.025 ) : 0;
352
353 const double margin = 0.01; // a small margin on the Z axix to be sure the hole
354 // is bigger than the board with copper
355 // must be > OCC_MAX_DISTANCE_TO_MERGE_POINTS
356 double holeZsize = m_boardThickness + ( m_copperThickness * 2 ) + ( margin * 2 );
357
358 std::shared_ptr<SHAPE_SEGMENT> seg_hole = aPad->GetEffectiveHoleShape();
359
360 double boardDrill = std::min( aPad->GetDrillSize().x, aPad->GetDrillSize().y );
361 double copperDrill = boardDrill - platingThickness * 2;
362
363 TopoDS_Shape copperHole, boardHole;
364
365 if( MakeShapeAsThickSegment( copperHole, seg_hole->GetSeg().A, seg_hole->GetSeg().B,
366 copperDrill, holeZsize, -m_copperThickness - margin, aOrigin ) )
367 {
368 m_copperCutouts.push_back( copperHole );
369 }
370 else
371 {
372 return false;
373 }
374
375 if( MakeShapeAsThickSegment( boardHole, seg_hole->GetSeg().A, seg_hole->GetSeg().B, boardDrill,
376 holeZsize, -m_copperThickness - margin, aOrigin ) )
377 {
378 m_boardCutouts.push_back( boardHole );
379 }
380 else
381 {
382 return false;
383 }
384
385 return true;
386}
387
388
389bool STEP_PCB_MODEL::AddComponent( const std::string& aFileNameUTF8, const std::string& aRefDes,
390 bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset,
391 VECTOR3D aOrientation, VECTOR3D aScale, bool aSubstituteModels )
392{
393 if( aFileNameUTF8.empty() )
394 {
395 ReportMessage( wxString::Format( wxT( "No model defined for component %s.\n" ), aRefDes ) );
396 return false;
397 }
398
399 wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
400 ReportMessage( wxString::Format( wxT( "Add component %s.\n" ), aRefDes ) );
401
402 // first retrieve a label
403 TDF_Label lmodel;
404 wxString errorMessage;
405
406 if( !getModelLabel( aFileNameUTF8, aScale, lmodel, aSubstituteModels, &errorMessage ) )
407 {
408 if( errorMessage.IsEmpty() )
409 ReportMessage( wxString::Format( wxT( "No model for filename '%s'.\n" ), fileName ) );
410 else
411 ReportMessage( errorMessage );
412
413 return false;
414 }
415
416 // calculate the Location transform
417 TopLoc_Location toploc;
418
419 if( !getModelLocation( aBottom, aPosition, aRotation, aOffset, aOrientation, toploc ) )
420 {
422 wxString::Format( wxT( "No location data for filename '%s'.\n" ), fileName ) );
423 return false;
424 }
425
426 // add the located sub-assembly
427 TDF_Label llabel = m_assy->AddComponent( m_assy_label, lmodel, toploc );
428
429 if( llabel.IsNull() )
430 {
431 ReportMessage( wxString::Format( wxT( "Could not add component with filename '%s'.\n" ),
432 fileName ) );
433 return false;
434 }
435
436 // attach the RefDes name
437 TCollection_ExtendedString refdes( aRefDes.c_str() );
438 TDataStd_Name::Set( llabel, refdes );
439
440 return true;
441}
442
443
444void STEP_PCB_MODEL::SetPCBThickness( double aThickness )
445{
446 if( aThickness < 0.0 )
448 else if( aThickness < BOARD_THICKNESS_MIN_MM )
450 else
451 m_boardThickness = aThickness;
452}
453
454
455void STEP_PCB_MODEL::SetBoardColor( double r, double g, double b )
456{
457 m_boardColor[0] = r;
458 m_boardColor[1] = g;
459 m_boardColor[2] = b;
460}
461
462
463void STEP_PCB_MODEL::SetCopperColor( double r, double g, double b )
464{
465 m_copperColor[0] = r;
466 m_copperColor[1] = g;
467 m_copperColor[2] = b;
468}
469
470
472{
473 // Ensure a minimal value (in mm)
474 m_mergeOCCMaxDist = aDistance;
475}
476
477
479{
480 return m_pcb_labels.size() > 0;
481}
482
483
484// A helper function to know if a SHAPE_LINE_CHAIN is encoding a circle (now unused)
485#if 0
486static bool IsChainCircle( const SHAPE_LINE_CHAIN& aChain )
487{
488 // If aChain is a circle it
489 // - contains only one arc
490 // - this arc has the same start and end point
491 const std::vector<SHAPE_ARC>& arcs = aChain.CArcs();
492
493 if( arcs.size() == 1 )
494 {
495 const SHAPE_ARC& arc = arcs[0];
496
497 if( arc. GetP0() == arc.GetP1() )
498 return true;
499 }
500
501 return false;
502}
503#endif
504
505
506bool STEP_PCB_MODEL::MakeShapeAsCylinder( TopoDS_Shape& aShape,
507 const SHAPE_LINE_CHAIN& aChain, double aThickness,
508 double aZposition, const VECTOR2D& aOrigin )
509{
510 if( !aShape.IsNull() )
511 return false; // there is already data in the shape object
512
513 if( !aChain.IsClosed() )
514 return false; // the loop is not closed
515
516 const std::vector<SHAPE_ARC>& arcs = aChain.CArcs();
517 const SHAPE_ARC& arc = arcs[0];
518
519 TopoDS_Shape base_shape;
520 base_shape = BRepPrimAPI_MakeCylinder(
521 pcbIUScale.IUTomm( arc.GetRadius() ), aThickness ).Shape();
522 gp_Trsf shift;
523 shift.SetTranslation( gp_Vec( pcbIUScale.IUTomm( arc.GetCenter().x - aOrigin.x ),
524 -pcbIUScale.IUTomm( arc.GetCenter().y - aOrigin.y ),
525 aZposition ) );
526 BRepBuilderAPI_Transform round_shape( base_shape, shift );
527 aShape = round_shape;
528
529 if( aShape.IsNull() )
530 {
531 ReportMessage( wxT( "failed to create a cylinder vertical shape\n" ) );
532 return false;
533 }
534
535 return true;
536}
537
538
540 VECTOR2D aStartPoint, VECTOR2D aEndPoint,
541 double aWidth, double aThickness,
542 double aZposition, const VECTOR2D& aOrigin )
543{
544 // make a wide segment from 2 lines and 2 180 deg arcs
545 // We need 6 points (3 per arcs)
546 VECTOR2D coords[6];
547
548 // We build a horizontal segment, and after rotate it
549 double len = ( aEndPoint - aStartPoint ).EuclideanNorm();
550 double h_width = aWidth/2.0;
551 // First is end point of first arc, and also start point of first line
552 coords[0] = VECTOR2D{ 0.0, h_width };
553
554 // end point of first line and start point of second arc
555 coords[1] = VECTOR2D{ len, h_width };
556
557 // middle point of second arc
558 coords[2] = VECTOR2D{ len + h_width, 0.0 };
559
560 // start point of second line and end point of second arc
561 coords[3] = VECTOR2D{ len, -h_width };
562
563 // end point of second line and start point of first arc
564 coords[4] = VECTOR2D{ 0, -h_width };
565
566 // middle point of first arc
567 coords[5] = VECTOR2D{ -h_width, 0.0 };
568
569 // Rotate and move to segment position
570 EDA_ANGLE seg_angle( aEndPoint - aStartPoint );
571
572 for( int ii = 0; ii < 6; ii++ )
573 {
574 RotatePoint( coords[ii], VECTOR2D{ 0, 0 }, -seg_angle ),
575 coords[ii] += aStartPoint;
576 }
577
578
579 // Convert to 3D points
580 gp_Pnt coords3D[ 6 ];
581
582 for( int ii = 0; ii < 6; ii++ )
583 {
584 coords3D[ii] = gp_Pnt( pcbIUScale.IUTomm( coords[ii].x - aOrigin.x ),
585 -pcbIUScale.IUTomm( coords[ii].y - aOrigin.y ), aZposition );
586 }
587
588 // Build OpenCascade shape outlines
589 BRepBuilderAPI_MakeWire wire;
590 bool success = true;
591
592 // Short segments (distance between end points < m_mergeOCCMaxDist(in mm)) must be
593 // skipped because OCC merge end points, and a null shape is created
594 bool short_seg = pcbIUScale.IUTomm( len ) <= m_mergeOCCMaxDist;
595
596 try
597 {
598 TopoDS_Edge edge;
599
600 if( short_seg )
601 {
602 Handle( Geom_Circle ) circle = GC_MakeCircle( coords3D[1], // arc1 start point
603 coords3D[2], // arc1 mid point
604 coords3D[5] // arc2 mid point
605 );
606
607 edge = BRepBuilderAPI_MakeEdge( circle );
608 wire.Add( edge );
609 }
610 else
611 {
612 edge = BRepBuilderAPI_MakeEdge( coords3D[0], coords3D[1] );
613 wire.Add( edge );
614
615 Handle( Geom_TrimmedCurve ) arcOfCircle =
616 GC_MakeArcOfCircle( coords3D[1], // start point
617 coords3D[2], // mid point
618 coords3D[3] // end point
619 );
620 edge = BRepBuilderAPI_MakeEdge( arcOfCircle );
621 wire.Add( edge );
622
623 edge = BRepBuilderAPI_MakeEdge( coords3D[3], coords3D[4] );
624 wire.Add( edge );
625
626 Handle( Geom_TrimmedCurve ) arcOfCircle2 =
627 GC_MakeArcOfCircle( coords3D[4], // start point
628 coords3D[5], // mid point
629 coords3D[0] // end point
630 );
631 edge = BRepBuilderAPI_MakeEdge( arcOfCircle2 );
632 wire.Add( edge );
633 }
634 }
635 catch( const Standard_Failure& e )
636 {
637 ReportMessage( wxString::Format( wxT( "build shape segment: OCC exception: %s\n" ),
638 e.GetMessageString() ) );
639 return false;
640 }
641
642
643 BRepBuilderAPI_MakeFace face;
644
645 try
646 {
647 gp_Pln plane( coords3D[0], gp::DZ() );
648 face = BRepBuilderAPI_MakeFace( plane, wire );
649 }
650 catch( const Standard_Failure& e )
651 {
653 wxString::Format( wxT( "MakeShapeThickSegment: OCC exception: %s\n" ),
654 e.GetMessageString() ) );
655 return false;
656 }
657
658 aShape = BRepPrimAPI_MakePrism( face, gp_Vec( 0, 0, aThickness ) );
659
660 if( aShape.IsNull() )
661 {
662 ReportMessage( wxT( "failed to create a prismatic shape\n" ) );
663 return false;
664 }
665
666 return success;
667}
668
669
670bool STEP_PCB_MODEL::MakeShapes( std::vector<TopoDS_Shape>& aShapes, const SHAPE_POLY_SET& aPolySet,
671 double aThickness, double aZposition, const VECTOR2D& aOrigin )
672{
673 SHAPE_POLY_SET simplified = aPolySet;
675
676 auto toPoint = [&]( const VECTOR2D& aKiCoords ) -> gp_Pnt
677 {
678 return gp_Pnt( pcbIUScale.IUTomm( aKiCoords.x - aOrigin.x ),
679 -pcbIUScale.IUTomm( aKiCoords.y - aOrigin.y ), aZposition );
680 };
681
682 for( const SHAPE_POLY_SET::POLYGON& polygon : simplified.CPolygons() )
683 {
684 auto makeWireFromChain = [&]( BRepLib_MakeWire& aMkWire,
685 const SHAPE_LINE_CHAIN& aChain ) -> bool
686 {
687 try
688 {
689 auto addSegment = [&]( const VECTOR2I& aPt0, const VECTOR2I& aPt1 ) -> bool
690 {
691 if( aPt0 == aPt1 )
692 return false;
693
694 gp_Pnt start = toPoint( aPt0 );
695 gp_Pnt end = toPoint( aPt1 );
696
697 // Do not export too short segments: they create broken shape because OCC thinks
698 // start point and end point are at the same place
699 double seg_len = std::hypot( end.X() - start.X(), end.Y() - start.Y() );
700
701 if( seg_len <= m_mergeOCCMaxDist )
702 return false;
703
704 BRepBuilderAPI_MakeEdge mkEdge( start, end );
705
706 if( !mkEdge.IsDone() || mkEdge.Edge().IsNull() )
707 {
708 ReportMessage( wxString::Format( wxT( "failed to make segment edge at (%d "
709 "%d) -> (%d %d), skipping\n" ),
710 aPt0.x, aPt0.y, aPt1.x, aPt1.y ) );
711 }
712 else
713 {
714 aMkWire.Add( mkEdge.Edge() );
715
716 if( aMkWire.Error() != BRepLib_WireDone )
717 {
718 ReportMessage( wxString::Format( wxT( "failed to add segment edge "
719 "at (%d %d) -> (%d %d)\n" ),
720 aPt0.x, aPt0.y, aPt1.x, aPt1.y ) );
721 return false;
722 }
723 }
724
725 return true;
726 };
727
728 auto addArc = [&]( const VECTOR2I& aPt0, const SHAPE_ARC& aArc ) -> bool
729 {
730 // Do not export too short segments: they create broken shape because OCC thinks
731 Handle( Geom_Curve ) curve;
732
733 if( aArc.GetCentralAngle() == ANGLE_360 )
734 {
735 gp_Ax2 axis = gp::XOY();
736 axis.SetLocation( toPoint( aArc.GetCenter() ) );
737
738 curve = GC_MakeCircle( axis, pcbIUScale.IUTomm( aArc.GetRadius() ) )
739 .Value();
740 }
741 else
742 {
743 curve = GC_MakeArcOfCircle( toPoint( aPt0 ),
744 toPoint( aArc.GetArcMid() ),
745 toPoint( aArc.GetP1() ) )
746 .Value();
747 }
748
749 if( curve.IsNull() )
750 return false;
751
752 aMkWire.Add( BRepBuilderAPI_MakeEdge( curve ) );
753
754 if( !aMkWire.IsDone() )
755 {
756 ReportMessage( wxString::Format(
757 wxT( "failed to add arc curve from (%d %d), arc p0 "
758 "(%d %d), mid (%d %d), p1 (%d %d)\n" ),
759 aPt0.x, aPt0.y, aArc.GetP0().x, aArc.GetP0().y, aArc.GetArcMid().x,
760 aArc.GetArcMid().y, aArc.GetP1().x, aArc.GetP1().y ) );
761 return false;
762 }
763
764 return true;
765 };
766
767 VECTOR2I firstPt;
768 VECTOR2I lastPt;
769 bool isFirstShape = true;
770
771 for( int i = 0; i <= aChain.PointCount() && i != -1; i = aChain.NextShape( i ) )
772 {
773 if( i == 0 )
774 {
775 if( aChain.IsArcSegment( 0 )
776 && aChain.IsArcSegment( aChain.PointCount() - 1 )
777 && aChain.ArcIndex( 0 ) == aChain.ArcIndex( aChain.PointCount() - 1 ) )
778 {
779 // Skip first arc (we should encounter it later)
780 int nextShape = aChain.NextShape( i );
781
782 // If nextShape points to the end, then we have a circle.
783 if( nextShape != -1 )
784 i = nextShape;
785 }
786 }
787
788 if( isFirstShape )
789 lastPt = aChain.CPoint( i );
790
791 bool isArc = aChain.IsArcSegment( i );
792
793 if( aChain.IsArcStart( i ) )
794 {
795 const SHAPE_ARC& currentArc = aChain.Arc( aChain.ArcIndex( i ) );
796
797 if( isFirstShape )
798 {
799 firstPt = currentArc.GetP0();
800 lastPt = firstPt;
801 }
802
803 if( addSegment( lastPt, currentArc.GetP0() ) )
804 lastPt = currentArc.GetP0();
805
806 if( addArc( lastPt, currentArc ) )
807 lastPt = currentArc.GetP1();
808 }
809 else if( !isArc )
810 {
811 const SEG& seg = aChain.CSegment( i );
812
813 if( isFirstShape )
814 {
815 firstPt = seg.A;
816 lastPt = firstPt;
817 }
818
819 if( addSegment( lastPt, seg.A ) )
820 lastPt = seg.A;
821
822 if( addSegment( lastPt, seg.B ) )
823 lastPt = seg.B;
824 }
825
826 isFirstShape = false;
827 }
828
829 if( lastPt != firstPt )
830 addSegment( lastPt, firstPt );
831 }
832 catch( const Standard_Failure& e )
833 {
834 ReportMessage( wxString::Format( wxT( "makeWireFromChain: OCC exception: %s\n" ),
835 e.GetMessageString() ) );
836 return false;
837 }
838
839 return true;
840 };
841
842 BRepBuilderAPI_MakeFace mkFace;
843
844 for( size_t contId = 0; contId < polygon.size(); contId++ )
845 {
846 const SHAPE_LINE_CHAIN& contour = polygon[contId];
847 BRepLib_MakeWire mkWire;
848
849 try
850 {
851 if( !makeWireFromChain( mkWire, contour ) )
852 continue;
853
854 wxASSERT( mkWire.IsDone() );
855
856 if( contId == 0 ) // Outline
857 mkFace = BRepBuilderAPI_MakeFace( mkWire.Wire() );
858 else // Hole
859 mkFace.Add( mkWire );
860 }
861 catch( const Standard_Failure& e )
862 {
864 wxString::Format( wxT( "MakeShapes (contour %d): OCC exception: %s\n" ),
865 contId, e.GetMessageString() ) );
866 return false;
867 }
868 }
869
870 if( mkFace.IsDone() )
871 {
872 TopoDS_Shape prism = BRepPrimAPI_MakePrism( mkFace, gp_Vec( 0, 0, aThickness ) );
873 aShapes.push_back( prism );
874
875 if( prism.IsNull() )
876 {
877 ReportMessage( wxT( "Failed to create a prismatic shape\n" ) );
878 return false;
879 }
880 }
881 else
882 {
883 wxASSERT( false );
884 }
885 }
886
887 return true;
888}
889
890
892{
893 if( m_hasPCB )
894 {
895 if( !isBoardOutlineValid() )
896 return false;
897
898 return true;
899 }
900
901 Handle( XCAFDoc_ColorTool ) colorTool = XCAFDoc_DocumentTool::ColorTool( m_doc->Main() );
902 m_hasPCB = true; // whether or not operations fail we note that CreatePCB has been invoked
903
904
905 // Support for more than one main outline (more than one board)
906 ReportMessage( wxString::Format( wxT( "Build board outlines (%d outlines) with %d points.\n" ),
907 aOutline.OutlineCount(), aOutline.FullPointCount() ) );
908#if 0
909 // This code should work, and it is working most of time
910 // However there are issues if the main outline is a circle with holes:
911 // holes from vias and pads are not working
912 // see bug https://gitlab.com/kicad/code/kicad/-/issues/17446
913 // (Holes are missing from STEP export with circular PCB outline)
914 // Hard to say if the bug is in our code or in OCC 7.7
915 if( !MakeShapes( m_board_outlines, aOutline, m_boardThickness, 0.0, aOrigin ) )
916 {
917 // Error
918 ReportMessage( wxString::Format(
919 wxT( "OCC error creating main outline.\n" ) ) );
920 }
921#else
922 // Workaround for bug #17446 Holes are missing from STEP export with circular PCB outline
923 for( const SHAPE_POLY_SET::POLYGON& polygon : aOutline.CPolygons() )
924 {
925 for( size_t contId = 0; contId < polygon.size(); contId++ )
926 {
927 const SHAPE_LINE_CHAIN& contour = polygon[contId];
928 SHAPE_POLY_SET polyset;
929 polyset.Append( contour );
930
931 if( contId == 0 ) // main Outline
932 {
933 if( !MakeShapes( m_board_outlines, polyset, m_boardThickness, 0.0, aOrigin ) )
934 ReportMessage( wxT( "OCC error creating main outline.\n" ) );
935 }
936 else // Hole inside the main outline
937 {
938 if( !MakeShapes( m_boardCutouts, polyset, m_boardThickness, 0.0, aOrigin ) )
939 ReportMessage( wxT( "OCC error creating hole in main outline.\n" ) );
940 }
941 }
942 }
943#endif
944
945 Bnd_Box brdBndBox;
946
947 for( const TopoDS_Shape& brdShape : m_board_outlines )
948 BRepBndLib::Add( brdShape, brdBndBox );
949
950 // subtract cutouts (if any)
951 ReportMessage( wxString::Format( wxT( "Build board cutouts and holes (%d hole(s)).\n" ),
952 (int) ( m_boardCutouts.size() + m_copperCutouts.size() ) ) );
953
954 auto buildBSB = [&brdBndBox]( std::vector<TopoDS_Shape>& input, Bnd_BoundSortBox& bsbHoles )
955 {
956 // We need to encompass every location we'll need to test in the global bbox,
957 // otherwise Bnd_BoundSortBox doesn't work near the boundaries.
958 Bnd_Box brdWithHolesBndBox = brdBndBox;
959
960 Handle( Bnd_HArray1OfBox ) holeBoxSet = new Bnd_HArray1OfBox( 0, input.size() - 1 );
961
962 for( size_t i = 0; i < input.size(); i++ )
963 {
964 Bnd_Box bbox;
965 BRepBndLib::Add( input[i], bbox );
966 brdWithHolesBndBox.Add( bbox );
967 ( *holeBoxSet )[i] = bbox;
968 }
969
970 bsbHoles.Initialize( brdWithHolesBndBox, holeBoxSet );
971 };
972
973 auto subtractShapes = []( const wxString& aWhat, std::vector<TopoDS_Shape>& aShapesList,
974 std::vector<TopoDS_Shape>& aHolesList, Bnd_BoundSortBox& aBSBHoles )
975 {
976 // Remove holes for each item (board body or bodies, one can have more than one board)
977 int cnt = 0;
978 for( TopoDS_Shape& shape : aShapesList )
979 {
980 Bnd_Box shapeBbox;
981 BRepBndLib::Add( shape, shapeBbox );
982
983 const TColStd_ListOfInteger& indices = aBSBHoles.Compare( shapeBbox );
984
985 TopTools_ListOfShape holelist;
986
987 for( const Standard_Integer& index : indices )
988 holelist.Append( aHolesList[index] );
989
990 if( cnt == 0 )
991 ReportMessage( wxString::Format( _( "Build holes for %s\n" ), aWhat ) );
992
993 cnt++;
994
995 if( cnt % 10 == 0 )
996 ReportMessage( wxString::Format( _( "Cutting %d/%d %s\n" ), cnt,
997 (int) aShapesList.size(), aWhat ) );
998
999 if( holelist.IsEmpty() )
1000 continue;
1001
1002 TopTools_ListOfShape cutArgs;
1003 cutArgs.Append( shape );
1004
1005 BRepAlgoAPI_Cut cut;
1006
1007 // This helps cutting circular holes in zones where a hole is already cut in Clipper
1008 cut.SetFuzzyValue( 0.0005 );
1009 cut.SetArguments( cutArgs );
1010
1011 cut.SetTools( holelist );
1012 cut.Build();
1013
1014 shape = cut.Shape();
1015 }
1016 };
1017
1018 if( m_boardCutouts.size() )
1019 {
1020 Bnd_BoundSortBox bsbHoles;
1021 buildBSB( m_boardCutouts, bsbHoles );
1022
1023 subtractShapes( _( "shapes" ), m_board_outlines, m_boardCutouts, bsbHoles );
1024 }
1025
1026 if( m_copperCutouts.size() )
1027 {
1028 Bnd_BoundSortBox bsbHoles;
1029 buildBSB( m_copperCutouts, bsbHoles );
1030
1031 subtractShapes( _( "pads" ), m_board_copper_pads, m_copperCutouts, bsbHoles );
1032 subtractShapes( _( "tracks" ), m_board_copper_tracks, m_copperCutouts, bsbHoles );
1033 subtractShapes( _( "zones" ), m_board_copper_zones, m_copperCutouts, bsbHoles );
1034 }
1035
1036 // push the board to the data structure
1037 ReportMessage( wxT( "\nGenerate board full shape.\n" ) );
1038
1039 auto pushToAssembly = [&]( std::vector<TopoDS_Shape>& aShapesList, Quantity_Color aColor,
1040 const wxString& aShapeName )
1041 {
1042 int i = 1;
1043 for( TopoDS_Shape& shape : aShapesList )
1044 {
1045 Handle( TDataStd_TreeNode ) node;
1046
1047 // Dont expand the component or else coloring it gets hard
1048 TDF_Label lbl = m_assy->AddComponent( m_assy_label, shape, false );
1049 m_pcb_labels.push_back( lbl );
1050
1051 if( m_pcb_labels.back().IsNull() )
1052 break;
1053
1054 lbl.FindAttribute( XCAFDoc::ShapeRefGUID(), node );
1055 TDF_Label shpLbl = node->Father()->Label();
1056 if( !shpLbl.IsNull() )
1057 {
1058 colorTool->SetColor( shpLbl, aColor, XCAFDoc_ColorSurf );
1059 wxString shapeName;
1060
1061 if( aShapesList.size() > 1 )
1062 {
1063 shapeName = wxString::Format( wxT( "%s_%s_%d" ), m_pcbName, aShapeName, i );
1064 }
1065 else
1066 {
1067 shapeName = wxString::Format( wxT( "%s_%s" ), m_pcbName, aShapeName );
1068 }
1069
1070
1071 TCollection_ExtendedString partname( shapeName.ToUTF8().data() );
1072 TDataStd_Name::Set( shpLbl, partname );
1073 }
1074
1075 i++;
1076 }
1077 };
1078
1079 // AddComponent adds a label that has a reference (not a parent/child relation) to the real
1080 // label. We need to extract that real label to name it for the STEP output cleanly
1081 // Why are we trying to name the bare board? Because CAD tools like SolidWorks do fun things
1082 // like "deduplicate" imported STEPs by swapping STEP assembly components with already
1083 // identically named assemblies. So we want to avoid having the PCB be generally defaulted
1084 // to "Component" or "Assembly".
1085
1086 // Init colors for the board body and the copper items (if any)
1087 Quantity_Color board_color( m_boardColor[0], m_boardColor[1], m_boardColor[2],
1088 Quantity_TOC_RGB );
1089 Quantity_Color copper_color( m_copperColor[0], m_copperColor[1], m_copperColor[2],
1090 Quantity_TOC_RGB );
1091
1092 pushToAssembly( m_board_copper_tracks, copper_color, "track" );
1093 pushToAssembly( m_board_copper_zones, copper_color, "zone" );
1094 pushToAssembly( m_board_copper_pads, copper_color, "pad" );
1095 pushToAssembly( m_board_outlines, board_color, "PCB" );
1096
1097#if( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 )
1098 m_assy->UpdateAssemblies();
1099#endif
1100
1101 return true;
1102}
1103
1104
1105#ifdef SUPPORTS_IGES
1106// write the assembly model in IGES format
1107bool STEP_PCB_MODEL::WriteIGES( const wxString& aFileName )
1108{
1109 if( !isBoardOutlineValid() )
1110 {
1111 ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
1112 "'%s'.\n" ),
1113 aFileName ) );
1114 return false;
1115 }
1116
1117 wxFileName fn( aFileName );
1118 IGESControl_Controller::Init();
1119 IGESCAFControl_Writer writer;
1120 writer.SetColorMode( Standard_True );
1121 writer.SetNameMode( Standard_True );
1122 IGESData_GlobalSection header = writer.Model()->GlobalSection();
1123 header.SetFileName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
1124 header.SetSendName( new TCollection_HAsciiString( "KiCad electronic assembly" ) );
1125 header.SetAuthorName(
1126 new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.author" ) ) );
1127 header.SetCompanyName(
1128 new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.company" ) ) );
1129 writer.Model()->SetGlobalSection( header );
1130
1131 if( Standard_False == writer.Perform( m_doc, aFileName.c_str() ) )
1132 return false;
1133
1134 return true;
1135}
1136#endif
1137
1138
1139bool STEP_PCB_MODEL::WriteSTEP( const wxString& aFileName, bool aOptimize )
1140{
1141 if( !isBoardOutlineValid() )
1142 {
1143 ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
1144 "'%s'.\n" ),
1145 aFileName ) );
1146 return false;
1147 }
1148
1149 wxFileName fn( aFileName );
1150
1151 STEPCAFControl_Writer writer;
1152 writer.SetColorMode( Standard_True );
1153 writer.SetNameMode( Standard_True );
1154
1155 // This must be set before we "transfer" the document.
1156 // Should default to kicad_pcb.general.title_block.title,
1157 // but in the meantime, defaulting to the basename of the output
1158 // target is still better than "open cascade step translter v..."
1159 // UTF8 should be ok from ISO 10303-21:2016, but... older stuff? use boring ascii
1160 if( !Interface_Static::SetCVal( "write.step.product.name", fn.GetName().ToAscii() ) )
1161 ReportMessage( wxT( "Failed to set step product name, but will attempt to continue." ) );
1162
1163 // Setting write.surfacecurve.mode to 0 reduces file size and write/read times.
1164 // But there are reports that this mode might be less compatible in some cases.
1165 if( !Interface_Static::SetIVal( "write.surfacecurve.mode", aOptimize ? 0 : 1 ) )
1166 ReportMessage( wxT( "Failed to set surface curve mode, but will attempt to continue." ) );
1167
1168 if( Standard_False == writer.Transfer( m_doc, STEPControl_AsIs ) )
1169 return false;
1170
1171 APIHeaderSection_MakeHeader hdr( writer.ChangeWriter().Model() );
1172
1173 // Note: use only Ascii7 chars, non Ascii7 chars (therefore UFT8 chars)
1174 // are creating issues in the step file
1175 hdr.SetName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
1176
1177 // TODO: how to control and ensure consistency with IGES?
1178 hdr.SetAuthorValue( 1, new TCollection_HAsciiString( "Pcbnew" ) );
1179 hdr.SetOrganizationValue( 1, new TCollection_HAsciiString( "Kicad" ) );
1180 hdr.SetOriginatingSystem( new TCollection_HAsciiString( "KiCad to STEP converter" ) );
1181 hdr.SetDescriptionValue( 1, new TCollection_HAsciiString( "KiCad electronic assembly" ) );
1182
1183 bool success = true;
1184
1185 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
1186 wxString currCWD = wxGetCwd();
1187 wxString workCWD = fn.GetPath();
1188
1189 if( !workCWD.IsEmpty() )
1190 wxSetWorkingDirectory( workCWD );
1191
1192 char tmpfname[] = "$tempfile$.step";
1193
1194 if( Standard_False == writer.Write( tmpfname ) )
1195 success = false;
1196
1197 if( success )
1198 {
1199
1200 // Preserve the permissions of the current file
1201 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpfname );
1202
1203 if( !wxRenameFile( tmpfname, fn.GetFullName(), true ) )
1204 {
1205 ReportMessage( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
1206 tmpfname,
1207 fn.GetFullName() ) );
1208 success = false;
1209 }
1210 }
1211
1212 wxSetWorkingDirectory( currCWD );
1213
1214 return success;
1215}
1216
1217
1218bool STEP_PCB_MODEL::getModelLabel( const std::string& aFileNameUTF8, VECTOR3D aScale, TDF_Label& aLabel,
1219 bool aSubstituteModels, wxString* aErrorMessage )
1220{
1221 std::string model_key = aFileNameUTF8 + "_" + std::to_string( aScale.x )
1222 + "_" + std::to_string( aScale.y ) + "_" + std::to_string( aScale.z );
1223
1224 MODEL_MAP::const_iterator mm = m_models.find( model_key );
1225
1226 if( mm != m_models.end() )
1227 {
1228 aLabel = mm->second;
1229 return true;
1230 }
1231
1232 aLabel.Nullify();
1233
1234 Handle( TDocStd_Document ) doc;
1235 m_app->NewDocument( "MDTV-XCAF", doc );
1236
1237 wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
1238 MODEL3D_FORMAT_TYPE modelFmt = fileType( aFileNameUTF8.c_str() );
1239
1240 switch( modelFmt )
1241 {
1242 case FMT_IGES:
1243 if( !readIGES( doc, aFileNameUTF8.c_str() ) )
1244 {
1245 ReportMessage( wxString::Format( wxT( "readIGES() failed on filename '%s'.\n" ),
1246 fileName ) );
1247 return false;
1248 }
1249 break;
1250
1251 case FMT_STEP:
1252 if( !readSTEP( doc, aFileNameUTF8.c_str() ) )
1253 {
1254 ReportMessage( wxString::Format( wxT( "readSTEP() failed on filename '%s'.\n" ),
1255 fileName ) );
1256 return false;
1257 }
1258 break;
1259
1260 case FMT_STEPZ:
1261 {
1262 // To export a compressed step file (.stpz or .stp.gz file), the best way is to
1263 // decaompress it in a temporaty file and load this temporary file
1264 wxFFileInputStream ifile( fileName );
1265 wxFileName outFile( fileName );
1266
1267 outFile.SetPath( wxStandardPaths::Get().GetTempDir() );
1268 outFile.SetExt( wxT( "step" ) );
1269 wxFileOffset size = ifile.GetLength();
1270
1271 if( size == wxInvalidOffset )
1272 {
1273 ReportMessage( wxString::Format( wxT( "getModelLabel() failed on filename '%s'.\n" ),
1274 fileName ) );
1275 return false;
1276 }
1277
1278 {
1279 bool success = false;
1280 wxFFileOutputStream ofile( outFile.GetFullPath() );
1281
1282 if( !ofile.IsOk() )
1283 return false;
1284
1285 char* buffer = new char[size];
1286
1287 ifile.Read( buffer, size );
1288 std::string expanded;
1289
1290 try
1291 {
1292 expanded = gzip::decompress( buffer, size );
1293 success = true;
1294 }
1295 catch( ... )
1296 {
1297 ReportMessage( wxString::Format( wxT( "failed to decompress '%s'.\n" ),
1298 fileName ) );
1299 }
1300
1301 if( expanded.empty() )
1302 {
1303 ifile.Reset();
1304 ifile.SeekI( 0 );
1305 wxZipInputStream izipfile( ifile );
1306 std::unique_ptr<wxZipEntry> zip_file( izipfile.GetNextEntry() );
1307
1308 if( zip_file && !zip_file->IsDir() && izipfile.CanRead() )
1309 {
1310 izipfile.Read( ofile );
1311 success = true;
1312 }
1313 }
1314 else
1315 {
1316 ofile.Write( expanded.data(), expanded.size() );
1317 }
1318
1319 delete[] buffer;
1320 ofile.Close();
1321
1322 if( success )
1323 {
1324 std::string altFileNameUTF8 = TO_UTF8( outFile.GetFullPath() );
1325 success =
1326 getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false );
1327 }
1328
1329 return success;
1330 }
1331
1332 break;
1333 }
1334
1335 case FMT_WRL:
1336 case FMT_WRZ:
1337 /* WRL files are preferred for internal rendering, due to superior material properties, etc.
1338 * However they are not suitable for MCAD export.
1339 *
1340 * If a .wrl file is specified, attempt to locate a replacement file for it.
1341 *
1342 * If a valid replacement file is found, the label for THAT file will be associated with
1343 * the .wrl file
1344 */
1345 if( aSubstituteModels )
1346 {
1347 wxFileName wrlName( fileName );
1348
1349 wxString basePath = wrlName.GetPath();
1350 wxString baseName = wrlName.GetName();
1351
1352 // List of alternate files to look for
1353 // Given in order of preference
1354 // (Break if match is found)
1355 wxArrayString alts;
1356
1357 // Step files
1358 alts.Add( wxT( "stp" ) );
1359 alts.Add( wxT( "step" ) );
1360 alts.Add( wxT( "STP" ) );
1361 alts.Add( wxT( "STEP" ) );
1362 alts.Add( wxT( "Stp" ) );
1363 alts.Add( wxT( "Step" ) );
1364 alts.Add( wxT( "stpz" ) );
1365 alts.Add( wxT( "stpZ" ) );
1366 alts.Add( wxT( "STPZ" ) );
1367 alts.Add( wxT( "step.gz" ) );
1368 alts.Add( wxT( "stp.gz" ) );
1369
1370 // IGES files
1371 alts.Add( wxT( "iges" ) );
1372 alts.Add( wxT( "IGES" ) );
1373 alts.Add( wxT( "igs" ) );
1374 alts.Add( wxT( "IGS" ) );
1375
1376 //TODO - Other alternative formats?
1377
1378 for( const auto& alt : alts )
1379 {
1380 wxFileName altFile( basePath, baseName + wxT( "." ) + alt );
1381
1382 if( altFile.IsOk() && altFile.FileExists() )
1383 {
1384 std::string altFileNameUTF8 = TO_UTF8( altFile.GetFullPath() );
1385
1386 // When substituting a STEP/IGS file for VRML, do not apply the VRML scaling
1387 // to the new STEP model. This process of auto-substitution is janky as all
1388 // heck so let's not mix up un-displayed scale factors with potentially
1389 // mis-matched files. And hope that the user doesn't have multiples files
1390 // named "model.wrl" and "model.stp" referring to different parts.
1391 // TODO: Fix model handling in v7. Default models should only be STP.
1392 // Have option to override this in DISPLAY.
1393 if( getModelLabel( altFileNameUTF8, VECTOR3D( 1.0, 1.0, 1.0 ), aLabel, false ) )
1394 {
1395 return true;
1396 }
1397 }
1398 }
1399
1400 return false; // No replacement model found
1401 }
1402 else // Substitution is not allowed
1403 {
1404 if( aErrorMessage )
1405 aErrorMessage->Printf( wxT( "Cannot load any VRML model for this export.\n" ) );
1406
1407 return false;
1408 }
1409
1410 break;
1411
1412 // TODO: implement IDF and EMN converters
1413
1414 default:
1415 return false;
1416 }
1417
1418 aLabel = transferModel( doc, m_doc, aScale );
1419
1420 if( aLabel.IsNull() )
1421 {
1422 ReportMessage( wxString::Format( wxT( "Could not transfer model data from file '%s'.\n" ),
1423 fileName ) );
1424 return false;
1425 }
1426
1427 // attach the PART NAME ( base filename: note that in principle
1428 // different models may have the same base filename )
1429 wxFileName afile( fileName );
1430 std::string pname( afile.GetName().ToUTF8() );
1431 TCollection_ExtendedString partname( pname.c_str() );
1432 TDataStd_Name::Set( aLabel, partname );
1433
1434 m_models.insert( MODEL_DATUM( model_key, aLabel ) );
1435 ++m_components;
1436 return true;
1437}
1438
1439
1440bool STEP_PCB_MODEL::getModelLocation( bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset, VECTOR3D aOrientation,
1441 TopLoc_Location& aLocation )
1442{
1443 // Order of operations:
1444 // a. aOrientation is applied -Z*-Y*-X
1445 // b. aOffset is applied
1446 // Top ? add thickness to the Z offset
1447 // c. Bottom ? Rotate on X axis (in contrast to most ECAD which mirror on Y),
1448 // then rotate on +Z
1449 // Top ? rotate on -Z
1450 // d. aPosition is applied
1451 //
1452 // Note: Y axis is inverted in KiCad
1453
1454 gp_Trsf lPos;
1455 lPos.SetTranslation( gp_Vec( aPosition.x, -aPosition.y, 0.0 ) );
1456
1457 // Offset board thickness
1458 aOffset.z += BOARD_OFFSET;
1459
1460 gp_Trsf lRot;
1461
1462 if( aBottom )
1463 {
1464 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
1465 lPos.Multiply( lRot );
1466 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), M_PI );
1467 lPos.Multiply( lRot );
1468 }
1469 else
1470 {
1471 aOffset.z += m_boardThickness;
1472 lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
1473 lPos.Multiply( lRot );
1474 }
1475
1476 gp_Trsf lOff;
1477 lOff.SetTranslation( gp_Vec( aOffset.x, aOffset.y, aOffset.z ) );
1478 lPos.Multiply( lOff );
1479
1480 gp_Trsf lOrient;
1481 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ),
1482 -aOrientation.z );
1483 lPos.Multiply( lOrient );
1484 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 1.0, 0.0 ) ),
1485 -aOrientation.y );
1486 lPos.Multiply( lOrient );
1487 lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ),
1488 -aOrientation.x );
1489 lPos.Multiply( lOrient );
1490
1491 aLocation = TopLoc_Location( lPos );
1492 return true;
1493}
1494
1495
1496bool STEP_PCB_MODEL::readIGES( Handle( TDocStd_Document )& doc, const char* fname )
1497{
1498 IGESControl_Controller::Init();
1499 IGESCAFControl_Reader reader;
1500 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
1501
1502 if( stat != IFSelect_RetDone )
1503 return false;
1504
1505 // Enable user-defined shape precision
1506 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
1507 return false;
1508
1509 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
1510 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
1511 return false;
1512
1513 // set other translation options
1514 reader.SetColorMode( true ); // use model colors
1515 reader.SetNameMode( false ); // don't use IGES label names
1516 reader.SetLayerMode( false ); // ignore LAYER data
1517
1518 if( !reader.Transfer( doc ) )
1519 {
1520 if( doc->CanClose() == CDM_CCS_OK )
1521 doc->Close();
1522
1523 return false;
1524 }
1525
1526 // are there any shapes to translate?
1527 if( reader.NbShapes() < 1 )
1528 {
1529 if( doc->CanClose() == CDM_CCS_OK )
1530 doc->Close();
1531
1532 return false;
1533 }
1534
1535 return true;
1536}
1537
1538
1539bool STEP_PCB_MODEL::readSTEP( Handle( TDocStd_Document )& doc, const char* fname )
1540{
1541 STEPCAFControl_Reader reader;
1542 IFSelect_ReturnStatus stat = reader.ReadFile( fname );
1543
1544 if( stat != IFSelect_RetDone )
1545 return false;
1546
1547 // Enable user-defined shape precision
1548 if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
1549 return false;
1550
1551 // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
1552 if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
1553 return false;
1554
1555 // set other translation options
1556 reader.SetColorMode( true ); // use model colors
1557 reader.SetNameMode( true ); // use label names
1558 reader.SetLayerMode( false ); // ignore LAYER data
1559
1560 if( !reader.Transfer( doc ) )
1561 {
1562 if( doc->CanClose() == CDM_CCS_OK )
1563 doc->Close();
1564
1565 return false;
1566 }
1567
1568 // are there any shapes to translate?
1569 if( reader.NbRootsForTransfer() < 1 )
1570 {
1571 if( doc->CanClose() == CDM_CCS_OK )
1572 doc->Close();
1573
1574 return false;
1575 }
1576
1577 return true;
1578}
1579
1580
1581TDF_Label STEP_PCB_MODEL::transferModel( Handle( TDocStd_Document )& source,
1582 Handle( TDocStd_Document )& dest, VECTOR3D aScale )
1583{
1584 // transfer data from Source into a top level component of Dest
1585 gp_GTrsf scale_transform;
1586 scale_transform.SetVectorialPart( gp_Mat( aScale.x, 0, 0,
1587 0, aScale.y, 0,
1588 0, 0, aScale.z ) );
1589 BRepBuilderAPI_GTransform brep( scale_transform );
1590
1591 // s_assy = shape tool for the source
1592 Handle(XCAFDoc_ShapeTool) s_assy = XCAFDoc_DocumentTool::ShapeTool( source->Main() );
1593
1594 // retrieve all free shapes within the assembly
1595 TDF_LabelSequence frshapes;
1596 s_assy->GetFreeShapes( frshapes );
1597
1598 // d_assy = shape tool for the destination
1599 Handle( XCAFDoc_ShapeTool ) d_assy = XCAFDoc_DocumentTool::ShapeTool ( dest->Main() );
1600
1601 // create a new shape within the destination and set the assembly tool to point to it
1602 TDF_Label component = d_assy->NewShape();
1603
1604 int nshapes = frshapes.Length();
1605 int id = 1;
1606 Handle( XCAFDoc_ColorTool ) scolor = XCAFDoc_DocumentTool::ColorTool( source->Main() );
1607 Handle( XCAFDoc_ColorTool ) dcolor = XCAFDoc_DocumentTool::ColorTool( dest->Main() );
1608 TopExp_Explorer dtop;
1609 TopExp_Explorer stop;
1610
1611 while( id <= nshapes )
1612 {
1613 const TDF_Label& s_shapeLabel = frshapes.Value( id );
1614 TopoDS_Shape shape = s_assy->GetShape( s_shapeLabel );
1615
1616 if( !shape.IsNull() )
1617 {
1618 Handle( TDataStd_Name ) s_nameAttr;
1619 s_shapeLabel.FindAttribute( TDataStd_Name::GetID(), s_nameAttr );
1620
1621 TCollection_ExtendedString s_labelName =
1622 s_nameAttr ? s_nameAttr->Get() : TCollection_ExtendedString();
1623
1624 TopoDS_Shape scaled_shape( shape );
1625
1626 if( aScale.x != 1.0 || aScale.y != 1.0 || aScale.z != 1.0 )
1627 {
1628 brep.Perform( shape, Standard_False );
1629
1630 if( brep.IsDone() )
1631 {
1632 scaled_shape = brep.Shape();
1633 }
1634 else
1635 {
1636 ReportMessage( wxT( " * transfertModel(): failed to scale model\n" ) );
1637
1638 scaled_shape = shape;
1639 }
1640 }
1641
1642 TDF_Label d_shapeLabel = d_assy->AddShape( scaled_shape, Standard_False );
1643
1644 if( s_labelName.Length() > 0 )
1645 TDataStd_Name::Set( d_shapeLabel, s_labelName );
1646
1647 TDF_Label niulab =
1648 d_assy->AddComponent( component, d_shapeLabel, scaled_shape.Location() );
1649
1650 // check for per-surface colors
1651 stop.Init( shape, TopAbs_FACE );
1652 dtop.Init( d_assy->GetShape( niulab ), TopAbs_FACE );
1653
1654 while( stop.More() && dtop.More() )
1655 {
1656 Quantity_Color face_color;
1657
1658 TDF_Label tl;
1659
1660 // give priority to the base shape's color
1661 if( s_assy->FindShape( stop.Current(), tl ) )
1662 {
1663 if( scolor->GetColor( tl, XCAFDoc_ColorSurf, face_color )
1664 || scolor->GetColor( tl, XCAFDoc_ColorGen, face_color )
1665 || scolor->GetColor( tl, XCAFDoc_ColorCurv, face_color ) )
1666 {
1667 dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf );
1668 }
1669 }
1670 else if( scolor->GetColor( stop.Current(), XCAFDoc_ColorSurf, face_color )
1671 || scolor->GetColor( stop.Current(), XCAFDoc_ColorGen, face_color )
1672 || scolor->GetColor( stop.Current(), XCAFDoc_ColorCurv, face_color ) )
1673 {
1674 dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf );
1675 }
1676
1677 stop.Next();
1678 dtop.Next();
1679 }
1680
1681 // check for per-solid colors
1682 stop.Init( shape, TopAbs_SOLID );
1683 dtop.Init( d_assy->GetShape( niulab ), TopAbs_SOLID, TopAbs_FACE );
1684
1685 while( stop.More() && dtop.More() )
1686 {
1687 Quantity_Color face_color;
1688
1689 TDF_Label tl;
1690
1691 // give priority to the base shape's color
1692 if( s_assy->FindShape( stop.Current(), tl ) )
1693 {
1694 if( scolor->GetColor( tl, XCAFDoc_ColorSurf, face_color )
1695 || scolor->GetColor( tl, XCAFDoc_ColorGen, face_color )
1696 || scolor->GetColor( tl, XCAFDoc_ColorCurv, face_color ) )
1697 {
1698 dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorGen );
1699 }
1700 }
1701 else if( scolor->GetColor( stop.Current(), XCAFDoc_ColorSurf, face_color )
1702 || scolor->GetColor( stop.Current(), XCAFDoc_ColorGen, face_color )
1703 || scolor->GetColor( stop.Current(), XCAFDoc_ColorCurv, face_color ) )
1704 {
1705 dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf );
1706 }
1707
1708 stop.Next();
1709 dtop.Next();
1710 }
1711 }
1712
1713 ++id;
1714 };
1715
1716 return component;
1717}
1718
1719
1720bool STEP_PCB_MODEL::WriteGLTF( const wxString& aFileName )
1721{
1722 if( !isBoardOutlineValid() )
1723 {
1724 ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
1725 "'%s'.\n" ),
1726 aFileName ) );
1727 return false;
1728 }
1729
1730 TDF_LabelSequence freeShapes;
1731 m_assy->GetFreeShapes( freeShapes );
1732
1733 ReportMessage( wxT( "Meshing model\n" ) );
1734
1735 // GLTF is a mesh format, we have to trigger opencascade to mesh the shapes we composited into the asesmbly
1736 // To mesh models, lets just grab the free shape root and execute on them
1737 for( Standard_Integer i = 1; i <= freeShapes.Length(); ++i )
1738 {
1739 TDF_Label label = freeShapes.Value( i );
1740 TopoDS_Shape shape;
1741 m_assy->GetShape( label, shape );
1742
1743 // These deflection values basically affect the accuracy of the mesh generated, a tighter
1744 // deflection will result in larger meshes
1745 // We could make this a tunable parameter, but for now fix it
1746 const Standard_Real linearDeflection = 0.01;
1747 const Standard_Real angularDeflection = 0.5;
1748 BRepMesh_IncrementalMesh mesh( shape, linearDeflection, Standard_False, angularDeflection,
1749 Standard_True );
1750 }
1751
1752 wxFileName fn( aFileName );
1753
1754 const char* tmpGltfname = "$tempfile$.glb";
1755 RWGltf_CafWriter cafWriter( tmpGltfname, true );
1756
1757 cafWriter.SetTransformationFormat( RWGltf_WriterTrsfFormat_Compact );
1758 cafWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit( 0.001 );
1759 cafWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(
1760 RWMesh_CoordinateSystem_Zup );
1761#if OCC_VERSION_HEX >= 0x070700
1762 cafWriter.SetParallel( true );
1763#endif
1764 TColStd_IndexedDataMapOfStringString metadata;
1765
1766 metadata.Add( TCollection_AsciiString( "pcb_name" ),
1767 TCollection_ExtendedString( fn.GetName().wc_str() ) );
1768 metadata.Add( TCollection_AsciiString( "source_pcb_file" ),
1769 TCollection_ExtendedString( fn.GetFullName().wc_str() ) );
1770 metadata.Add( TCollection_AsciiString( "generator" ),
1771 TCollection_AsciiString( wxString::Format( wxS( "KiCad %s" ), GetSemanticVersion() ).ToAscii() ) );
1772 metadata.Add( TCollection_AsciiString( "generated_at" ),
1773 TCollection_AsciiString( GetISO8601CurrentDateTime().ToAscii() ) );
1774
1775 bool success = true;
1776
1777 // Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
1778 wxString currCWD = wxGetCwd();
1779 wxString workCWD = fn.GetPath();
1780
1781 if( !workCWD.IsEmpty() )
1782 wxSetWorkingDirectory( workCWD );
1783
1784 success = cafWriter.Perform( m_doc, metadata, Message_ProgressRange() );
1785
1786 if( success )
1787 {
1788 // Preserve the permissions of the current file
1789 KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpGltfname );
1790
1791 if( !wxRenameFile( tmpGltfname, fn.GetFullName(), true ) )
1792 {
1793 ReportMessage( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
1794 tmpGltfname, fn.GetFullName() ) );
1795 success = false;
1796 }
1797 }
1798
1799 wxSetWorkingDirectory( currCWD );
1800
1801 return success;
1802}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:108
wxString GetSemanticVersion()
Get the semantic version string for KiCad defined inside the KiCadVersion.cmake file in the variable ...
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition: board_item.h:226
Definition: pad.h:59
int GetSizeX() const
Definition: pad.h:246
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
Definition: pad.h:618
const VECTOR2I & GetDrillSize() const
Definition: pad.h:254
PAD_ATTRIB GetAttribute() const
Definition: pad.h:374
VECTOR2I GetPosition() const override
Definition: pad.h:198
const std::shared_ptr< SHAPE_POLY_SET > & GetEffectivePolygon(ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Definition: pad.cpp:356
PAD_SHAPE GetShape() const
Definition: pad.h:190
std::shared_ptr< SHAPE_SEGMENT > GetEffectiveHoleShape() const override
Return a SHAPE_SEGMENT object representing the pad's hole.
Definition: pad.cpp:402
int GetWidth() const
Definition: pcb_track.h:107
const VECTOR2I & GetStart() const
Definition: pcb_track.h:113
const VECTOR2I & GetEnd() const
Definition: pcb_track.h:110
int GetDrillValue() const
Calculate the drill value for vias (m_drill if > 0, or default drill value for the board).
Definition: pcb_track.cpp:383
Definition: seg.h:42
VECTOR2I A
Definition: seg.h:49
VECTOR2I B
Definition: seg.h:50
const VECTOR2I & GetP1() const
Definition: shape_arc.h:113
double GetRadius() const
Definition: shape_arc.cpp:505
VECTOR2I GetCenter() const
Definition: shape_arc.cpp:474
const VECTOR2I & GetP0() const
Definition: shape_arc.h:112
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
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.
const std::vector< SHAPE_ARC > & CArcs() const
int NextShape(int aPointIndex) const
Return the vertex index of the next shape in the chain, or -1 if aPointIndex is the last shape.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
bool IsArcSegment(size_t aSegment) const
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.
const std::vector< POLYGON > & CPolygons() const
void SetCopperColor(double r, double g, double b)
bool isBoardOutlineValid()
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
double m_boardThickness
TDF_Label m_assy_label
bool AddPadShape(const PAD *aPad, const VECTOR2D &aOrigin)
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 ...
STEP_PCB_MODEL(const wxString &aPcbName)
std::vector< TopoDS_Shape > m_board_outlines
bool readIGES(Handle(TDocStd_Document)&m_doc, const char *fname)
void SetBoardColor(double r, double g, double b)
bool AddViaShape(const PCB_VIA *aVia, const VECTOR2D &aOrigin)
bool CreatePCB(SHAPE_POLY_SET &aOutline, VECTOR2D aOrigin)
bool readSTEP(Handle(TDocStd_Document)&m_doc, const char *fname)
Handle(XCAFApp_Application) m_app
double m_copperThickness
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
bool MakeShapes(std::vector< TopoDS_Shape > &aShapes, const SHAPE_POLY_SET &aPolySet, 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 WriteSTEP(const wxString &aFileName, bool aOptimize)
std::vector< TopoDS_Shape > m_copperCutouts
std::vector< TopoDS_Shape > m_board_copper_pads
bool AddCopperPolygonShapes(const SHAPE_POLY_SET *aPolyShapes, bool aOnTop, const VECTOR2D &aOrigin, bool aTrack)
std::vector< TopoDS_Shape > m_board_copper_zones
void SetPCBThickness(double aThickness)
MODEL_MAP m_models
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)
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
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:60
@ B_Cu
Definition: layer_ids.h:96
@ F_Cu
Definition: layer_ids.h:65
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
#define USER_PREC
static bool addSegment(VRML_LAYER &model, IDF_SEGMENT *seg, int icont, int iseg)
std::vector< FAB_LAYER_COLOR > dummy
MODEL3D_FORMAT_TYPE
@ FMT_STEP
@ FMT_IGES
@ FMT_IDF
@ FMT_WRZ
@ FMT_STEPZ
@ FMT_NONE
@ FMT_WRL
@ FMT_EMN
static constexpr double BOARD_OFFSET
MODEL3D_FORMAT_TYPE fileType(const char *aFileName)
static constexpr double USER_ANGLE_PREC
static constexpr double USER_PREC
static constexpr double COPPER_THICKNESS_DEFAULT_MM
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 ...
static constexpr double BOARD_THICKNESS_DEFAULT_MM
std::pair< std::string, TDF_Label > MODEL_DATUM
static constexpr double ARC_TO_SEGMENT_MAX_ERROR_MM
static constexpr double BOARD_THICKNESS_MIN_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:588
VECTOR3< double > VECTOR3D
Definition: vector3.h:205