From 60f65e68c1be975ce71071db5c593184fded7f06 Mon Sep 17 00:00:00 2001 From: jean-pierre charras <jp.charras@wanadoo.fr> Date: Mon, 17 Mar 2025 11:14:53 +0100 Subject: [PATCH] Step export: handle castellated pads. Only pads having the fab property "Castellated" are handled. --- pcbnew/exporters/step/exporter_step.cpp | 15 +++-- pcbnew/exporters/step/exporter_step.h | 2 +- pcbnew/exporters/step/step_pcb_model.cpp | 77 ++++++++++++++++++++++-- pcbnew/exporters/step/step_pcb_model.h | 22 ++++++- 4 files changed, 101 insertions(+), 15 deletions(-) diff --git a/pcbnew/exporters/step/exporter_step.cpp b/pcbnew/exporters/step/exporter_step.cpp index 115e6662a5..0934de7a41 100644 --- a/pcbnew/exporters/step/exporter_step.cpp +++ b/pcbnew/exporters/step/exporter_step.cpp @@ -155,7 +155,8 @@ EXPORTER_STEP::~EXPORTER_STEP() } -bool EXPORTER_STEP::buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOrigin ) +bool EXPORTER_STEP::buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOrigin, + SHAPE_POLY_SET* aClipPolygon ) { bool hasdata = false; std::vector<PAD*> padsMatchingNetFilter; @@ -164,6 +165,7 @@ bool EXPORTER_STEP::buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOri // Dump the pad holes into the PCB for( PAD* pad : aFootprint->Pads() ) { + bool castellated = pad->GetProperty() == PAD_PROP::CASTELLATED; std::shared_ptr<SHAPE_SEGMENT> holeShape = pad->GetEffectiveHoleShape(); SHAPE_POLY_SET holePoly; @@ -198,7 +200,8 @@ bool EXPORTER_STEP::buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOri if( m_params.m_ExportPads ) { - if( m_pcbModel->AddPadShape( pad, aOrigin, false ) ) + if( m_pcbModel->AddPadShape( pad, aOrigin, false, + castellated ? aClipPolygon : nullptr) ) hasdata = true; if( m_params.m_ExportSoldermask ) @@ -603,6 +606,9 @@ bool EXPORTER_STEP::buildBoard3DShapes() wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) ); } + SHAPE_POLY_SET pcbOutlinesNoArcs = pcbOutlines; + pcbOutlinesNoArcs.ClearArcs(); + VECTOR2D origin; // Determine the coordinate system reference: @@ -640,7 +646,7 @@ bool EXPORTER_STEP::buildBoard3DShapes() // For copper layers, only pads and tracks are added, because adding everything on copper // generate unreasonable file sizes and take a unreasonable calculation time. for( FOOTPRINT* fp : m_board->Footprints() ) - buildFootprint3DShapes( fp, origin ); + buildFootprint3DShapes( fp, origin, &pcbOutlinesNoArcs ); for( PCB_TRACK* track : m_board->Tracks() ) buildTrack3DShape( track, origin ); @@ -653,9 +659,6 @@ bool EXPORTER_STEP::buildBoard3DShapes() buildZones3DShape( origin ); } - SHAPE_POLY_SET pcbOutlinesNoArcs = pcbOutlines; - pcbOutlinesNoArcs.ClearArcs(); - for( PCB_LAYER_ID pcblayer : m_layersToExport.Seq() ) { SHAPE_POLY_SET poly = m_poly_shapes[pcblayer]; diff --git a/pcbnew/exporters/step/exporter_step.h b/pcbnew/exporters/step/exporter_step.h index 556ecac4b1..dcd798ac85 100644 --- a/pcbnew/exporters/step/exporter_step.h +++ b/pcbnew/exporters/step/exporter_step.h @@ -58,7 +58,7 @@ public: private: bool buildBoard3DShapes(); - bool buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOrigin ); + bool buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOrigin, SHAPE_POLY_SET* aClipPolygon ); bool buildTrack3DShape( PCB_TRACK* aTrack, VECTOR2D aOrigin ); void buildZones3DShape( VECTOR2D aOrigin ); bool buildGraphic3DShape( BOARD_ITEM* aItem, VECTOR2D aOrigin ); diff --git a/pcbnew/exporters/step/step_pcb_model.cpp b/pcbnew/exporters/step/step_pcb_model.cpp index 1550623c89..1be3acd4ce 100644 --- a/pcbnew/exporters/step/step_pcb_model.cpp +++ b/pcbnew/exporters/step/step_pcb_model.cpp @@ -125,6 +125,7 @@ #endif #include <macros.h> +#include <convert_basic_shapes_to_polygon.h> static constexpr double USER_PREC = 1e-4; static constexpr double USER_ANGLE_PREC = 1e-6; @@ -783,10 +784,12 @@ STEP_PCB_MODEL::~STEP_PCB_MODEL() } -bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin, bool aVia ) +bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin, bool aVia, + SHAPE_POLY_SET* aClipPolygon ) { bool success = true; std::vector<TopoDS_Shape> padShapes; + bool castellated = aClipPolygon && aPad->GetProperty() == PAD_PROP::CASTELLATED; for( PCB_LAYER_ID pcb_layer : aPad->GetLayerSet().Seq() ) { @@ -817,6 +820,12 @@ bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin, bool SHAPE_POLY_SET polySet; aPad->TransformShapeToPolygon( polySet, pcb_layer, 0, ARC_HIGH_DEF, ERROR_INSIDE ); + if( castellated ) + { + polySet.ClearArcs(); + polySet.BooleanIntersection( *aClipPolygon ); + } + success &= MakeShapes( padShapes, polySet, m_simplifyShapes, thickness, Zpos, aOrigin ); if( testShape.IsNull() ) @@ -862,20 +871,56 @@ bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin, bool getLayerZPlacement( B_Cu, b_pos, b_thickness ); double top = std::max( f_pos, f_pos + f_thickness ); double bottom = std::min( b_pos, b_pos + b_thickness ); + double hole_height = top - bottom; TopoDS_Shape plating; std::shared_ptr<SHAPE_SEGMENT> seg_hole = aPad->GetEffectiveHoleShape(); double width = std::min( aPad->GetDrillSize().x, aPad->GetDrillSize().y ); - if( MakeShapeAsThickSegment( plating, seg_hole->GetSeg().A, seg_hole->GetSeg().B, width, - ( top - bottom ), bottom, aOrigin ) ) + if( !castellated ) { - padShapes.push_back( plating ); + if( MakeShapeAsThickSegment( plating, seg_hole->GetSeg().A, seg_hole->GetSeg().B, width, + hole_height, bottom, aOrigin ) ) + { + padShapes.push_back( plating ); + } + else + { + success = false; + } } else { - success = false; + // Note: + // the truncated hole shape is exported as a vertical filled shape. The hole itself + // will be removed later, when all holes are removed from the board + SHAPE_POLY_SET polyHole; + + if( seg_hole->GetSeg().A == seg_hole->GetSeg().B ) // Hole is a circle + { + TransformCircleToPolygon( polyHole, seg_hole->GetSeg().A, width/2, + ARC_HIGH_DEF, ERROR_OUTSIDE ); + + } + else + { + TransformOvalToPolygon( polyHole, + seg_hole->GetSeg().A, seg_hole->GetSeg().B, + width, ARC_HIGH_DEF, ERROR_OUTSIDE ); + } + + polyHole.ClearArcs(); + polyHole.BooleanIntersection( *aClipPolygon ); + + if( MakePolygonAsWall( plating, polyHole, hole_height, bottom, aOrigin ) ) + { + padShapes.push_back( plating ); + } + else + { + success = false; + } } } @@ -1382,6 +1427,25 @@ bool STEP_PCB_MODEL::MakeShapeAsThickSegment( TopoDS_Shape& aShape, } +bool STEP_PCB_MODEL::MakePolygonAsWall( TopoDS_Shape& aShape, + SHAPE_POLY_SET& aPolySet, + double aHeight, + double aZposition, const VECTOR2D& aOrigin ) +{ + std::vector<TopoDS_Shape> testShapes; + + bool success = MakeShapes( testShapes, aPolySet, m_simplifyShapes, + aHeight, aZposition, aOrigin ); + + if( testShapes.size() > 0 ) + aShape = testShapes.front(); + else + success = false; + + return success; +} + + static wxString formatBBox( const BOX2I& aBBox ) { wxString str; @@ -1557,7 +1621,8 @@ static bool makeWireFromChain( BRepLib_MakeWire& aMkWire, const SHAPE_LINE_CHAIN } -bool STEP_PCB_MODEL::MakeShapes( std::vector<TopoDS_Shape>& aShapes, const SHAPE_POLY_SET& aPolySet, bool aConvertToArcs, +bool STEP_PCB_MODEL::MakeShapes( std::vector<TopoDS_Shape>& aShapes, const SHAPE_POLY_SET& aPolySet, + bool aConvertToArcs, double aThickness, double aZposition, const VECTOR2D& aOrigin ) { SHAPE_POLY_SET workingPoly = aPolySet; diff --git a/pcbnew/exporters/step/step_pcb_model.h b/pcbnew/exporters/step/step_pcb_model.h index a88c062c02..b4da8bb728 100644 --- a/pcbnew/exporters/step/step_pcb_model.h +++ b/pcbnew/exporters/step/step_pcb_model.h @@ -97,7 +97,10 @@ public: void SpecializeVariant( OUTPUT_FORMAT aVariant ) { m_outFmt = aVariant; } // add a pad shape (must be in final position) - bool AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin, bool aVia ); + // if aClipPolygon is not nullptr, the pad shape will be clipped by aClipPolygon + // (usually aClipPolygon is the board outlines and use for castelleted pads) + bool AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin, bool aVia, + SHAPE_POLY_SET* aClipPolygon = nullptr ); // add a pad hole or slot (must be in final position) bool AddHole( const SHAPE_SEGMENT& aShape, int aPlatingThickness, PCB_LAYER_ID aLayerTop, @@ -138,7 +141,7 @@ public: /** * Convert a SHAPE_POLY_SET to TopoDS_Shape's (polygonal vertical prisms, or flat faces) * @param aShapes is the TopoDS_Shape list to append to - * @param aPolySet is a polygon set + * @param aPolySet is the polygon set * @param aConvertToArcs set to approximate with arcs * @param aThickness is the height of the created prism, or 0.0: flat face pointing up, -0.0: down. * @param aOrigin is the origin of the coordinates @@ -164,6 +167,21 @@ public: double aWidth, double aThickness, double aZposition, const VECTOR2D& aOrigin ); + /** + * Make a polygonal shape to create a vertical wall. + * It is a specialized version of MakeShape() + * @param aShape is the TopoDS_Shape to initialize (must be empty) + * @param aPolySet is the outline of the wall + * @param aHeight is the height of the wall. + * @param aZposition is the Z postion of the wall + * @param aOrigin is the origin of the coordinates + * @return true if success + */ + bool MakePolygonAsWall( TopoDS_Shape& aShape, + SHAPE_POLY_SET& aPolySet, + double aHeight, + double aZposition, const VECTOR2D& aOrigin ); + #ifdef SUPPORTS_IGES // write the assembly model in IGES format bool WriteIGES( const wxString& aFileName );