From 458d5fbeb84b3d8247c1ac4a3fc259535cd9287d Mon Sep 17 00:00:00 2001 From: Alex Shvartzkop Date: Thu, 24 Aug 2023 03:47:51 +0300 Subject: [PATCH] STEP export: use unfractured polygons. Fixes https://gitlab.com/kicad/code/kicad/-/issues/15486 --- pcbnew/exporters/step/exporter_step.cpp | 5 +- pcbnew/exporters/step/step_pcb_model.cpp | 324 ++++++++++++----------- pcbnew/exporters/step/step_pcb_model.h | 10 +- 3 files changed, 170 insertions(+), 169 deletions(-) diff --git a/pcbnew/exporters/step/exporter_step.cpp b/pcbnew/exporters/step/exporter_step.cpp index b593a94581..3f68f451a6 100644 --- a/pcbnew/exporters/step/exporter_step.cpp +++ b/pcbnew/exporters/step/exporter_step.cpp @@ -333,7 +333,7 @@ void EXPORTER_STEP::buildZones3DShape( VECTOR2D aOrigin ) { SHAPE_POLY_SET copper_shape; zone->TransformSolidAreasShapesToPolygon( layer, copper_shape ); - copper_shape.Fracture( SHAPE_POLY_SET::PM_FAST ); + copper_shape.Unfracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); m_pcbModel->AddCopperPolygonShapes( &copper_shape, layer == F_Cu, aOrigin, false ); } @@ -427,9 +427,6 @@ bool EXPORTER_STEP::buildBoard3DShapes() buildGraphic3DShape( item, origin ); } - m_top_copper_shapes.Fracture( SHAPE_POLY_SET::PM_FAST ); - m_bottom_copper_shapes.Fracture( SHAPE_POLY_SET::PM_FAST ); - m_pcbModel->AddCopperPolygonShapes( &m_top_copper_shapes, true, origin, true ); m_pcbModel->AddCopperPolygonShapes( &m_bottom_copper_shapes, false, origin, true ); diff --git a/pcbnew/exporters/step/step_pcb_model.cpp b/pcbnew/exporters/step/step_pcb_model.cpp index c37bec93f5..7e6ec7e9c1 100644 --- a/pcbnew/exporters/step/step_pcb_model.cpp +++ b/pcbnew/exporters/step/step_pcb_model.cpp @@ -234,10 +234,8 @@ bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin ) } else { - success = MakeShape( curr_shape, pad_shape.get()->COutline(0), m_copperThickness, Zpos, aOrigin ); - - if( success ) - m_board_copper_pads.push_back( curr_shape ); + success = MakeShapes( m_board_copper_pads, *pad_shape, m_copperThickness, Zpos, + aOrigin ); } } @@ -257,25 +255,16 @@ bool STEP_PCB_MODEL::AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, { bool success = true; - for( int ii = 0; ii < aPolyShapes->OutlineCount(); ii++ ) - { - TopoDS_Shape copper_shape; - double z_pos = aOnTop ? m_boardThickness : -m_copperThickness; + double z_pos = aOnTop ? m_boardThickness : -m_copperThickness; - if( MakeShape( copper_shape, aPolyShapes->COutline( ii ), m_copperThickness, z_pos, aOrigin ) ) - { - if( aTrack ) - m_board_copper_tracks.push_back( copper_shape ); - else - m_board_copper_zones.push_back( copper_shape ); - } - else - { - ReportMessage( wxString::Format( wxT( "Could not add shape (%d points) to copper layer on %s.\n" ), - aPolyShapes->COutline( ii ).PointCount(), - aOnTop ? wxT( "top" ) : wxT( "bottom" ) ) ); - success = false; - } + if( !MakeShapes( aTrack ? m_board_copper_tracks : m_board_copper_zones, *aPolyShapes, + m_copperThickness, z_pos, aOrigin ) ) + { + ReportMessage( wxString::Format( + wxT( "Could not add shape (%d points) to copper layer on %s.\n" ), + aPolyShapes->FullPointCount(), aOnTop ? wxT( "top" ) : wxT( "bottom" ) ) ); + + success = false; } return success; @@ -606,95 +595,136 @@ bool STEP_PCB_MODEL::MakeShapeAsThickSegment( TopoDS_Shape& aShape, } -bool STEP_PCB_MODEL::MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aChain, - double aThickness, double aZposition, const VECTOR2D& aOrigin ) +bool STEP_PCB_MODEL::MakeShapes( std::vector& aShapes, const SHAPE_POLY_SET& aPolySet, + double aThickness, double aZposition, const VECTOR2D& aOrigin ) { - if( !aShape.IsNull() ) - return false; // there is already data in the shape object + SHAPE_POLY_SET simplified = aPolySet; + simplified.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); - if( !aChain.IsClosed() ) - return false; // the loop is not closed - - // a SHAPE_LINE_CHAIN that is in fact a circle (one 360deg arc) is exported as cylinder - if( IsChainCircle( aChain ) ) - return MakeShapeAsCylinder( aShape, aChain, aThickness, aZposition, aOrigin ); - - BRepBuilderAPI_MakeWire wire; - bool success = true; - - gp_Pnt start = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( 0 ).x - aOrigin.x ), - -pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), aZposition ); - - int items_count = 0; - - for( int j = 0; j < aChain.PointCount(); j++ ) + for( const SHAPE_POLY_SET::POLYGON& polygon : simplified.CPolygons() ) { - int next = j+1; - - gp_Pnt end; - - if( next >= aChain.PointCount() ) - next = 0; - - end = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( next ).x - aOrigin.x ), - -pcbIUScale.IUTomm( aChain.CPoint( next ).y - aOrigin.y ), aZposition ); - - // Do not export too short segments: they create broken shape because OCC thinks - // start point and end point are at the same place - double seg_len = std::hypot( end.X() - start.X(), end.Y() - start.Y() ); - - if( seg_len <= m_mergeOCCMaxDist ) - continue; - try + auto makeWireFromChain = [&]( BRepLib_MakeWire& aMkWire, + const SHAPE_LINE_CHAIN& aChain ) -> bool { - TopoDS_Edge edge; - edge = BRepBuilderAPI_MakeEdge( start, end ); - wire.Add( edge ); - - if( wire.Error() != BRepBuilderAPI_WireDone ) + try { - ReportMessage( wxT( "failed to add curve\n" ) ); + // a SHAPE_LINE_CHAIN that is in fact a circle (one 360deg arc) is exported as cylinder + if( IsChainCircle( aChain ) ) + { + const SHAPE_ARC& arc = aChain.Arc( 0 ); + + std::vector coords = { arc.GetP0(), arc.GetArcMid(), arc.GetP1() }; + std::vector coords3D( coords.size() ); + + // Convert to 3D points + for( int ii = 0; ii < coords.size(); ii++ ) + { + coords3D[ii] = gp_Pnt( pcbIUScale.IUTomm( coords[ii].x - aOrigin.x ), + -pcbIUScale.IUTomm( coords[ii].y - aOrigin.y ), + aZposition ); + } + + Handle( Geom_TrimmedCurve ) arcOfCircle = + GC_MakeArcOfCircle( coords3D[0], // start point + coords3D[1], // mid point + coords3D[2] // end point + ); + + aMkWire.Add( BRepBuilderAPI_MakeEdge( arcOfCircle ) ); + + if( aMkWire.Error() != BRepBuilderAPI_WireDone ) + { + ReportMessage( wxT( "failed to add curve\n" ) ); + return false; + } + + return true; + } + + gp_Pnt start = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( 0 ).x - aOrigin.x ), + -pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), + aZposition ); + + for( int j = 0; j < aChain.PointCount(); j++ ) + { + int next = ( j + 1 ) % aChain.PointCount(); + + gp_Pnt end = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( next ).x - aOrigin.x ), + -pcbIUScale.IUTomm( aChain.CPoint( next ).y - aOrigin.y ), + aZposition ); + + // Do not export too short segments: they create broken shape because OCC thinks + // start point and end point are at the same place + double seg_len = std::hypot( end.X() - start.X(), end.Y() - start.Y() ); + + if( seg_len <= m_mergeOCCMaxDist ) + continue; + + BRepBuilderAPI_MakeEdge mkEdge( start, end ); + aMkWire.Add( mkEdge ); + + if( aMkWire.Error() != BRepBuilderAPI_WireDone ) + { + ReportMessage( wxT( "failed to add curve\n" ) ); + return false; + } + + start = end; + } + } + catch( const Standard_Failure& e ) + { + ReportMessage( wxString::Format( wxT( "makeWireFromChain: OCC exception: %s\n" ), + e.GetMessageString() ) ); return false; } - items_count++; - } - catch( const Standard_Failure& e ) + return true; + }; + + BRepBuilderAPI_MakeFace mkFace; + + for( int contId = 0; contId < polygon.size(); contId++ ) { - ReportMessage( wxString::Format( wxT( "MakeShape1: OCC exception: %s\n" ), - e.GetMessageString() ) ); - success = false; + const SHAPE_LINE_CHAIN& contour = polygon[contId]; + BRepLib_MakeWire mkWire; + + try + { + if( !makeWireFromChain( mkWire, contour ) ) + continue; + + wxASSERT( mkWire.IsDone() ); + + if( contId == 0 ) // Outline + mkFace = BRepBuilderAPI_MakeFace( mkWire.Wire() ); + else // Hole + mkFace.Add( mkWire ); + } + catch( const Standard_Failure& e ) + { + ReportMessage( + wxString::Format( wxT( "MakeShapes (contour %d): OCC exception: %s\n" ), + contId, e.GetMessageString() ) ); + return false; + } } - if( !success ) + if( mkFace.IsDone() ) { - ReportMessage( wxS( "failed to add edge\n" ) ); - return false; + TopoDS_Shape prism = BRepPrimAPI_MakePrism( mkFace, gp_Vec( 0, 0, aThickness ) ); + aShapes.push_back( prism ); + + if( prism.IsNull() ) + { + ReportMessage( wxT( "Failed to create a prismatic shape\n" ) ); + return false; + } + } + else + { + wxASSERT( false ); } - - start = end; - } - - BRepBuilderAPI_MakeFace face; - - try - { - face = BRepBuilderAPI_MakeFace( wire ); - } - catch( const Standard_Failure& e ) - { - ReportMessage( - wxString::Format( wxT( "MakeShape2 (items_count %d): OCC exception: %s\n" ), - items_count, e.GetMessageString() ) ); - return false; - } - - aShape = BRepPrimAPI_MakePrism( face, gp_Vec( 0, 0, aThickness ) ); - - if( aShape.IsNull() ) - { - ReportMessage( wxT( "failed to create a prismatic shape\n" ) ); - return false; } return true; @@ -721,37 +751,14 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) const SHAPE_LINE_CHAIN& outline = aOutline.COutline( cnt ); ReportMessage( wxString::Format( wxT( "Build board main outline %d with %d points.\n" ), - cnt+1, outline.PointCount() ) ); + cnt + 1, outline.PointCount() ) ); - TopoDS_Shape curr_brd; - - if( !MakeShape( curr_brd, outline, m_boardThickness, 0.0, aOrigin ) ) + if( !MakeShapes( m_board_outlines, aOutline, m_boardThickness, 0.0, aOrigin ) ) { // Error ReportMessage( wxString::Format( - wxT( "OCC error adding main outline polygon %d with %d points.\n" ), - cnt+1, outline.PointCount() ) ); - } - else - m_board_outlines.push_back( curr_brd ); - - // Generate board cutouts in current main outline: - if( aOutline.HoleCount( cnt ) > 0 ) - { - ReportMessage( wxString::Format( wxT( "Add cutouts in outline %d (%d cutout(s)).\n" ), - cnt+1, aOutline.HoleCount( cnt ) ) ); - } - - for( int ii = 0; ii < aOutline.HoleCount( cnt ); ii++ ) - { - const SHAPE_LINE_CHAIN& holeOutline = aOutline.Hole( cnt, ii ); - TopoDS_Shape hole; - - if( MakeShape( hole, holeOutline, m_boardThickness + (m_copperThickness*4), - -m_copperThickness*2, aOrigin ) ) - { - m_cutouts.push_back( hole ); - } + wxT( "OCC error adding main outline polygon %d with %d points.\n" ), cnt + 1, + outline.PointCount() ) ); } } @@ -805,48 +812,45 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) // push the board to the data structure ReportMessage( wxT( "\nGenerate board full shape.\n" ) ); - auto pushToAssembly = [&]( std::vector& aShapesList, Quantity_Color aColor, const wxString& aShapeName ) - { - int i = 1; - for( TopoDS_Shape& shape : aShapesList ) - { - Handle( TDataStd_TreeNode ) node; + auto pushToAssembly = [&]( std::vector& aShapesList, Quantity_Color aColor, + const wxString& aShapeName ) + { + int i = 1; + for( TopoDS_Shape& shape : aShapesList ) + { + Handle( TDataStd_TreeNode ) node; - // Dont expand the component or else coloring it gets hard - TDF_Label lbl = - m_assy->AddComponent( m_assy_label, shape, false ); - m_pcb_labels.push_back( lbl ); + // Dont expand the component or else coloring it gets hard + TDF_Label lbl = m_assy->AddComponent( m_assy_label, shape, false ); + m_pcb_labels.push_back( lbl ); - if( m_pcb_labels.back().IsNull() ) - break; + if( m_pcb_labels.back().IsNull() ) + break; - lbl.FindAttribute( XCAFDoc::ShapeRefGUID(), node ); - TDF_Label shpLbl = node->Father()->Label(); - if( !shpLbl.IsNull() ) - { - colorTool->SetColor( shpLbl, aColor, XCAFDoc_ColorSurf ); - wxString shapeName; + lbl.FindAttribute( XCAFDoc::ShapeRefGUID(), node ); + TDF_Label shpLbl = node->Father()->Label(); + if( !shpLbl.IsNull() ) + { + colorTool->SetColor( shpLbl, aColor, XCAFDoc_ColorSurf ); + wxString shapeName; - if( aShapesList.size() > 1 ) - { - shapeName = wxString::Format( - wxT( "%s_%s_%d" ), m_pcbName, aShapeName, i ); - } - else - { - shapeName = wxString::Format( - wxT( "%s_%s" ), m_pcbName, aShapeName ); - } + if( aShapesList.size() > 1 ) + { + shapeName = wxString::Format( wxT( "%s_%s_%d" ), m_pcbName, aShapeName, i ); + } + else + { + shapeName = wxString::Format( wxT( "%s_%s" ), m_pcbName, aShapeName ); + } - TCollection_ExtendedString partname( - shapeName.ToUTF8().data() ); - TDataStd_Name::Set( shpLbl, partname ); - } + TCollection_ExtendedString partname( shapeName.ToUTF8().data() ); + TDataStd_Name::Set( shpLbl, partname ); + } - i++; - } - }; + i++; + } + }; // AddComponent adds a label that has a reference (not a parent/child relation) to the real // label. We need to extract that real label to name it for the STEP output cleanly diff --git a/pcbnew/exporters/step/step_pcb_model.h b/pcbnew/exporters/step/step_pcb_model.h index 69d6f7273f..7f42063d2c 100644 --- a/pcbnew/exporters/step/step_pcb_model.h +++ b/pcbnew/exporters/step/step_pcb_model.h @@ -114,15 +114,15 @@ public: bool CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ); /** - * Convert a SHAPE_LINE_CHAIN (closed) to a TopoDS_Shape (polygonal vertical prism) - * @param aShape is the TopoDS_Shape to initialize (must be empty) - * @param aChain is a closed SHAPE_LINE_CHAIN (a polygon) + * Convert a SHAPE_POLY_SET to TopoDS_Shape's (polygonal vertical prisms) + * @param aShapes is the TopoDS_Shape list to append to + * @param aPolySet is a polygon set * @param aThickness is the height of the created prism * @param aOrigin is the origin of the coordinates * @return true if success */ - bool MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aChain, double aThickness, - double aZposition, const VECTOR2D& aOrigin ); + bool MakeShapes( std::vector& aShapes, const SHAPE_POLY_SET& aPolySet, + double aThickness, double aZposition, const VECTOR2D& aOrigin ); /** * Convert a SHAPE_LINE_CHAIN containing only one 360 deg arc to a TopoDS_Shape