Step exporter: export circular board outlines as cylinder

From master branch, commit 66651327.
This commit is contained in:
jean-pierre charras 2023-05-23 10:25:58 +02:00
parent b968bac318
commit 5db9a6af9d
8 changed files with 148 additions and 47 deletions

View File

@ -657,11 +657,6 @@ void BOARD_ADAPTER::InitSettings( REPORTER* aStatusReporter, REPORTER* aWarningR
}
extern bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines,
int aErrorMax, int aChainingEpsilon,
OUTLINE_ERROR_HANDLER* aErrorHandler = nullptr );
bool BOARD_ADAPTER::createBoardPolygon( wxString* aErrorMsg )
{
m_board_poly.RemoveAllContours();

View File

@ -2013,13 +2013,14 @@ ZONE* BOARD::AddArea( PICKED_ITEMS_LIST* aNewZonesList, int aNetcode, PCB_LAYER_
bool BOARD::GetBoardPolygonOutlines( SHAPE_POLY_SET& aOutlines,
OUTLINE_ERROR_HANDLER* aErrorHandler )
OUTLINE_ERROR_HANDLER* aErrorHandler,
bool aAllowUseArcsInPolygons )
{
// max dist from one endPt to next startPt: use the current value
int chainingEpsilon = GetOutlinesChainingEpsilon();
bool success = BuildBoardPolygonOutlines( this, aOutlines, GetDesignSettings().m_MaxError,
chainingEpsilon, aErrorHandler );
chainingEpsilon, aErrorHandler, aAllowUseArcsInPolygons );
// Make polygon strictly simple to avoid issues (especially in 3D viewer)
aOutlines.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );

View File

@ -664,10 +664,14 @@ public:
*
* @param aOutlines is the #SHAPE_POLY_SET to fill in with outlines/holes.
* @param aErrorHandler is an optional DRC_ITEM error handler.
* @param aAllowUseArcsInPolygons = an optional option to allow adding arcs in
* SHAPE_LINE_CHAIN polylines/polygons when building outlines from aShapeList
* This is mainly for export to STEP files
* @return true if success, false if a contour is not valid
*/
bool GetBoardPolygonOutlines( SHAPE_POLY_SET& aOutlines,
OUTLINE_ERROR_HANDLER* aErrorHandler = nullptr );
OUTLINE_ERROR_HANDLER* aErrorHandler = nullptr,
bool aAllowUseArcsInPolygons = false );
/**
* @return a epsilon value that is the max distance between 2 points to see them

View File

@ -160,9 +160,7 @@ bool isCopperOutside( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aShape )
}
/**
* Function ConvertOutlineToPolygon
* Build a polygon (with holes) from a PCB_SHAPE list, which is expected to be a closed main
/* Build a polygon (with holes) from a PCB_SHAPE list, which is expected to be a closed main
* outline with perhaps closed inner outlines. These closed inner outlines are considered as
* holes in the main outline.
* @param aShapeList the initial list of SHAPEs (only lines, circles and arcs).
@ -171,10 +169,12 @@ bool isCopperOutside( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aShape )
* @param aChainingEpsilon is the max error distance when polygonizing a curve (internal units)
* @param aAllowDisjoint indicates multiple top-level outlines are allowed
* @param aErrorHandler = an optional error handler
* @param aAllowUseArcsInPolygons = an optional option to allow adding arcs in
* SHAPE_LINE_CHAIN polylines/polygons when building outlines from aShapeList
*/
bool ConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SET& aPolygons,
int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint,
OUTLINE_ERROR_HANDLER* aErrorHandler )
OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons )
{
if( aShapeList.size() == 0 )
return true;
@ -249,30 +249,25 @@ bool ConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SE
}
else if( graphic->GetShape() == SHAPE_T::CIRCLE )
{
// make a circle by segments;
VECTOR2I center = graphic->GetCenter();
VECTOR2I start = center;
int radius = graphic->GetRadius();
int steps = GetArcToSegmentCount( radius, aErrorMax, FULL_CIRCLE );
VECTOR2I nextPt;
VECTOR2I start = center;
start.x += radius;
for( int step = 0; step < steps; ++step )
// Add 360 deg Arc in currContour
SHAPE_ARC arc360( center, start, ANGLE_360, 0 );
currContour.Append( arc360, aErrorMax );
currContour.SetClosed( true );
// set shapeOwners for currContour points created by appending the arc360:
for( int ii = 1; ii < currContour.PointCount(); ++ii )
{
nextPt = start;
RotatePoint( nextPt, center, ANGLE_360 * step / steps );
currContour.Append( nextPt );
if( firstPt )
firstPt = false;
else
shapeOwners[ std::make_pair( prevPt, nextPt ) ] = graphic;
prevPt = nextPt;
shapeOwners[ std::make_pair( currContour.CPoint( ii-1 ),
currContour.CPoint( ii ) ) ] = graphic;
}
currContour.SetClosed( true );
if( !aAllowUseArcsInPolygons )
currContour.ClearArcs();
}
else if( graphic->GetShape() == SHAPE_T::RECT )
{
@ -615,7 +610,8 @@ bool ConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SE
* All contours should be closed, i.e. valid closed polygon vertices
*/
bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aErrorMax,
int aChainingEpsilon, OUTLINE_ERROR_HANDLER* aErrorHandler )
int aChainingEpsilon, OUTLINE_ERROR_HANDLER* aErrorHandler,
bool aAllowUseArcsInPolygons )
{
PCB_TYPE_COLLECTOR items;
bool success = false;
@ -650,7 +646,7 @@ bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aE
false,
// don't report errors here; the second pass also
// gets an opportunity to use these segments
nullptr );
nullptr, aAllowUseArcsInPolygons );
// Here, we test to see if we should make holes or outlines. Holes are made if the footprint
// has copper outside of a single, closed outline. If there are multiple outlines, we assume
@ -688,7 +684,7 @@ bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aE
if( segList.size() )
{
success = ConvertOutlineToPolygon( segList, aOutlines, aErrorMax, aChainingEpsilon,
true, aErrorHandler );
true, aErrorHandler, aAllowUseArcsInPolygons );
}
if( !success || !aOutlines.OutlineCount() )

View File

@ -1,7 +1,7 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -43,19 +43,47 @@ const std::function<void( const wxString& msg, BOARD_ITEM* itemA, BOARD_ITEM* it
* @param aChainingEpsilon is the max distance from one endPt to the next startPt (internal units)
* @param aAllowDisjoint indicates multiple top-level outlines are allowed
* @param aErrorHandler = an optional error handler
* @param aAllowUseArcsInPolygons = an optional option to allow adding arcs in
* SHAPE_LINE_CHAIN polylines/polygons when building outlines from aShapeList
* This is mainly for export to STEP files
* @return true if success, false if a contour is not valid (self intersecting)
*/
bool ConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SET& aPolygons,
int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint,
OUTLINE_ERROR_HANDLER* aErrorHandler );
OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons = false );
/**
* Extracts the board outlines and build a closed polygon from lines, arcs and circle items on
* edge cut layer. Any closed outline inside the main outline is a hole. All contours should be
* closed, i.e. are valid vertices for a closed polygon.
* @param aBoard is the board to build outlines
* @param aOutlines will contain the outlines ( complex polygons ).
* @param aErrorMax is the max error distance when polygonizing a curve (internal units)
* @param aChainingEpsilon is the max distance from one endPt to the next startPt (internal units)
* @param aErrorHandler = an optional error handler
* @param aAllowUseArcsInPolygons = an optional option to allow adding arcs in
* SHAPE_LINE_CHAIN polylines/polygons when building outlines from aShapeList
* This is mainly for export to STEP files
* @return true if success, false if a contour is not valid
*/
extern bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines,
int aErrorMax, int aChainingEpsilon,
OUTLINE_ERROR_HANDLER* aErrorHandler = nullptr );
OUTLINE_ERROR_HANDLER* aErrorHandler = nullptr,
bool aAllowUseArcsInPolygons = false );
/**
* This function is used to extract a board outline for a footprint view.
*
* Notes:
* * Incomplete outlines will be closed by joining the end of the outline onto the bounding box
* (by simply projecting the end points) and then take the area that contains the copper.
* * If all copper lies inside a closed outline, than that outline will be treated as an external
* board outline.
* * If copper is located outside a closed outline, then that outline will be treated as a hole,
* and the outer edge will be formed using the bounding box.
*/
bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines,
int aErrorMax, int aChainingEpsilon,
OUTLINE_ERROR_HANDLER* aErrorHandler = nullptr );

View File

@ -245,7 +245,9 @@ bool EXPORTER_STEP::composePCB()
SHAPE_POLY_SET pcbOutlines; // stores the board main outlines
if( !m_board->GetBoardPolygonOutlines( pcbOutlines ) )
if( !m_board->GetBoardPolygonOutlines( pcbOutlines,
/* error handler */ nullptr,
/* allows use arcs in outlines */ true ) )
{
wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) );
}

View File

@ -3,7 +3,7 @@
*
* Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2016-2022 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -240,7 +240,7 @@ bool STEP_PCB_MODEL::AddPadHole( const PAD* aPad, const VECTOR2D& aOrigin )
if( holeOutlines.OutlineCount() > 0 )
{
if( MakeShape( hole, holeOutlines.COutline( 0 ), m_thickness, aOrigin ) )
if( MakeShape( hole, holeOutlines.COutline( 0 ), m_thickness, 0.0, aOrigin ) )
{
m_cutouts.push_back( hole );
}
@ -343,9 +343,28 @@ bool STEP_PCB_MODEL::isBoardOutlineValid()
return m_pcb_labels.size() > 0;
}
// A helper function to know if a SHAPE_LINE_CHAIN is encoding a circle
static bool IsChainCircle( const SHAPE_LINE_CHAIN& aChain )
{
// If aChain is a circle it
// - contains only one arc
// - this arc has the same start and end point
const std::vector<SHAPE_ARC>& arcs = aChain.CArcs();
bool STEP_PCB_MODEL::MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aChain,
double aThickness, const VECTOR2D& aOrigin )
if( arcs.size() == 1 )
{
const SHAPE_ARC& arc = arcs[0];
if( arc. GetP0() == arc.GetP1() )
return true;
}
return false;
}
bool STEP_PCB_MODEL::MakeShapeAsCylinder( TopoDS_Shape& aShape,
const SHAPE_LINE_CHAIN& aChain, double aThickness,
double aZposition, const VECTOR2D& aOrigin )
{
if( !aShape.IsNull() )
return false; // there is already data in the shape object
@ -353,12 +372,47 @@ bool STEP_PCB_MODEL::MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aC
if( !aChain.IsClosed() )
return false; // the loop is not closed
const std::vector<SHAPE_ARC>& arcs = aChain.CArcs();
const SHAPE_ARC& arc = arcs[0];
TopoDS_Shape base_shape;
base_shape = BRepPrimAPI_MakeCylinder(
pcbIUScale.IUTomm( arc.GetRadius() ), aThickness ).Shape();
gp_Trsf shift;
shift.SetTranslation( gp_Vec( pcbIUScale.IUTomm( arc.GetCenter().x - aOrigin.x ),
-pcbIUScale.IUTomm( arc.GetCenter().y - aOrigin.y ),
aZposition ) );
BRepBuilderAPI_Transform round_shape( base_shape, shift );
aShape = round_shape;
if( aShape.IsNull() )
{
ReportMessage( wxT( "failed to create a cylinder vertical shape\n" ) );
return false;
}
return true;
}
bool STEP_PCB_MODEL::MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aChain,
double aThickness, double aZposition, const VECTOR2D& aOrigin )
{
if( !aShape.IsNull() )
return false; // there is already data in the shape object
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;
TopoDS_Edge edge;
bool success = true;
gp_Pnt start = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( 0 ).x - aOrigin.x ),
-pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), 0.0 );
-pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), aZposition );
for( int j = 0; j < aChain.PointCount(); j++ )
{
@ -369,12 +423,12 @@ bool STEP_PCB_MODEL::MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aC
if( next >= aChain.PointCount() )
{
end = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( 0 ).x - aOrigin.x ),
-pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), 0.0 );
-pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), aZposition );
}
else
{
end = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( next ).x - aOrigin.x ),
-pcbIUScale.IUTomm( aChain.CPoint( next ).y - aOrigin.y ), 0.0 );
-pcbIUScale.IUTomm( aChain.CPoint( next ).y - aOrigin.y ), aZposition );
}
// Do not export very small segments: they can create broken outlines
@ -460,7 +514,7 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin )
TopoDS_Shape curr_brd;
if( !MakeShape( curr_brd, outline, m_thickness, aOrigin ) )
if( !MakeShape( curr_brd, outline, m_thickness, 0.0, aOrigin ) )
{
// Error
ReportMessage( wxString::Format(
@ -482,7 +536,7 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin )
const SHAPE_LINE_CHAIN& holeOutline = aOutline.Hole( cnt, ii );
TopoDS_Shape hole;
if( MakeShape( hole, holeOutline, m_thickness, aOrigin ) )
if( MakeShape( hole, holeOutline, m_thickness, 0.0, aOrigin ) )
{
m_cutouts.push_back( hole );
}

View File

@ -83,8 +83,29 @@ public:
// create the PCB model using the current outlines and drill holes
bool CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin );
bool MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& chain, double aThickness,
const 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)
* @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 );
/**
* Convert a SHAPE_LINE_CHAIN containing only one 360 deg arc to a TopoDS_Shape
* ( vertical cylinder)
* it is a specialized version of MakeShape()
* @param aShape is the TopoDS_Shape to initialize (must be empty)
* @param aChain is a closed SHAPE_LINE_CHAIN, image of a circle: containing one 360 deg arc
* @param aThickness is the height of the created cylinder
* @param aOrigin is the origin of the coordinates
* @return true if success
*/
bool MakeShapeAsCylinder( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aChain,
double aThickness, double aZposition, const VECTOR2D& aOrigin );
#ifdef SUPPORTS_IGES
// write the assembly model in IGES format