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