Complete hookup of zone filler to new clearance engine.
This commit is contained in:
parent
d7bd4c9b04
commit
cec857c0f4
|
@ -134,6 +134,19 @@ int BOARD_CONNECTED_ITEM::GetClearance( BOARD_ITEM* aItem, wxString* aSource ) c
|
|||
}
|
||||
}
|
||||
|
||||
if( aItem && aItem->GetLayer() == Edge_Cuts )
|
||||
{
|
||||
int edgeClearance = bds.m_CopperEdgeClearance;
|
||||
|
||||
if( edgeClearance > clearance )
|
||||
{
|
||||
clearance = edgeClearance;
|
||||
|
||||
if( aSource )
|
||||
*aSource = _( "board edge clearance" );
|
||||
}
|
||||
}
|
||||
|
||||
return clearance;
|
||||
}
|
||||
|
||||
|
|
|
@ -1019,12 +1019,16 @@ int BOARD_DESIGN_SETTINGS::GetRuleClearance( const BOARD_ITEM* aItem, const NETC
|
|||
if( selector->m_Rule->m_Clearance > 0 )
|
||||
{
|
||||
clearance = std::max( clearance, selector->m_Rule->m_Clearance );
|
||||
*aSource = wxString::Format( _( "'%s' rule clearance" ), selector->m_Rule->m_Name );
|
||||
|
||||
if( aSource )
|
||||
*aSource = wxString::Format( _( "'%s' rule clearance" ), selector->m_Rule->m_Name );
|
||||
}
|
||||
else if( selector->m_Rule->m_Clearance < 0 )
|
||||
{
|
||||
clearance = std::min( clearance, abs( selector->m_Rule->m_Clearance ) );
|
||||
*aSource = wxString::Format( _( "'%s' rule clearance" ), selector->m_Rule->m_Name );
|
||||
|
||||
if( aSource )
|
||||
*aSource = wxString::Format( _( "'%s' rule clearance" ), selector->m_Rule->m_Name );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -463,22 +463,17 @@ int ZONE_CONTAINER::GetClearance( BOARD_ITEM* aItem, wxString* aSource ) const
|
|||
if( m_isKeepout )
|
||||
return 0;
|
||||
|
||||
// The actual zone clearance is the biggest of the zone netclass clearance
|
||||
// and the zone clearance setting in the zone properties dialog.
|
||||
int zoneClearance = m_ZoneClearance;
|
||||
int clearance = BOARD_CONNECTED_ITEM::GetClearance( aItem, aSource );
|
||||
|
||||
if( clearance > zoneClearance )
|
||||
{
|
||||
return clearance;
|
||||
}
|
||||
else
|
||||
if( m_ZoneClearance > clearance )
|
||||
{
|
||||
clearance = m_ZoneClearance;
|
||||
|
||||
if( aSource )
|
||||
*aSource = _( "zone clearance" );
|
||||
|
||||
return zoneClearance;
|
||||
}
|
||||
|
||||
return clearance;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1137,35 +1132,19 @@ double ZONE_CONTAINER::CalculateFilledArea()
|
|||
* Convert the zone filled areas polygons to polygons
|
||||
* inflated (optional) by max( aClearanceValue, the zone clearance)
|
||||
* and copy them in aCornerBuffer
|
||||
* @param aMinClearanceValue the min clearance around outlines
|
||||
* @param aUseNetClearance true to use a clearance which is the max value between
|
||||
* aMinClearanceValue and the net clearance
|
||||
* false to use aMinClearanceValue only
|
||||
* @param aPreserveCorners an optional set of corners which should not be chamfered/filleted
|
||||
* @param aClearance the clearance around outlines
|
||||
* @param aPreserveCorners an optional set of corners which should not be chamfered/filleted
|
||||
*/
|
||||
void ZONE_CONTAINER::TransformOutlinesShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
|
||||
int aMinClearanceValue,
|
||||
bool aUseNetClearance,
|
||||
std::set<VECTOR2I>* aPreserveCorners ) const
|
||||
int aClearance, std::set<VECTOR2I>* aPreserveCorners ) const
|
||||
{
|
||||
// Creates the zone outline polygon (with holes if any)
|
||||
SHAPE_POLY_SET polybuffer;
|
||||
BuildSmoothedPoly( polybuffer, aPreserveCorners );
|
||||
|
||||
// add clearance to outline
|
||||
int clearance = aMinClearanceValue;
|
||||
|
||||
if( aUseNetClearance && IsOnCopperLayer() )
|
||||
{
|
||||
clearance = GetClearance();
|
||||
|
||||
if( aMinClearanceValue > clearance )
|
||||
clearance = aMinClearanceValue;
|
||||
}
|
||||
|
||||
// Calculate the polygon with clearance
|
||||
// holes are linked to the main outline, so only one polygon is created.
|
||||
if( clearance )
|
||||
if( aClearance )
|
||||
{
|
||||
BOARD* board = GetBoard();
|
||||
int maxError = ARC_HIGH_DEF;
|
||||
|
@ -1173,8 +1152,8 @@ void ZONE_CONTAINER::TransformOutlinesShapeWithClearanceToPolygon( SHAPE_POLY_SE
|
|||
if( board )
|
||||
maxError = board->GetDesignSettings().m_MaxError;
|
||||
|
||||
int segCount = std::max( GetArcToSegmentCount( clearance, maxError, 360.0 ), 3 );
|
||||
polybuffer.Inflate( clearance, segCount );
|
||||
int segCount = std::max( GetArcToSegmentCount( aClearance, maxError, 360.0 ), 3 );
|
||||
polybuffer.Inflate( aClearance, segCount );
|
||||
}
|
||||
|
||||
polybuffer.Fracture( SHAPE_POLY_SET::PM_FAST );
|
||||
|
|
|
@ -316,8 +316,7 @@ public:
|
|||
* if both aMinClearanceValue = 0 and aUseNetClearance = false: create the zone outline polygon.
|
||||
*/
|
||||
void TransformOutlinesShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
|
||||
int aMinClearanceValue, bool aUseNetClearance,
|
||||
std::set<VECTOR2I>* aPreserveCorners = nullptr ) const;
|
||||
int aMinClearanceValue, std::set<VECTOR2I>* aPreserveCorners = nullptr ) const;
|
||||
|
||||
/**
|
||||
* Function TransformShapeWithClearanceToPolygon
|
||||
|
|
|
@ -66,7 +66,7 @@ DRC::DRC() :
|
|||
m_pcbEditorFrame( nullptr ),
|
||||
m_pcb( nullptr ),
|
||||
m_drcDialog( nullptr ),
|
||||
m_rulesFileLastMod( 0 )
|
||||
m_largestClearance( 0 )
|
||||
{
|
||||
// establish initial values for everything:
|
||||
m_doPad2PadTest = true; // enable pad to pad clearance tests
|
||||
|
@ -103,8 +103,6 @@ void DRC::Reset( RESET_REASON aReason )
|
|||
|
||||
m_pcb = m_pcbEditorFrame->GetBoard();
|
||||
}
|
||||
|
||||
loadRules();
|
||||
}
|
||||
|
||||
|
||||
|
@ -355,38 +353,32 @@ int DRC::testZoneToZoneOutlines( BOARD_COMMIT& aCommit )
|
|||
}
|
||||
|
||||
|
||||
void DRC::loadRules()
|
||||
void DRC::LoadRules()
|
||||
{
|
||||
wxString rulesFilepath = m_pcbEditorFrame->Prj().AbsolutePath( "drc-rules" );
|
||||
wxFileName rulesFile( rulesFilepath );
|
||||
|
||||
if( rulesFile.FileExists() )
|
||||
{
|
||||
wxLongLong lastMod = rulesFile.GetModificationTime().GetValue();
|
||||
m_ruleSelectors.clear();
|
||||
m_rules.clear();
|
||||
|
||||
if( lastMod > m_rulesFileLastMod )
|
||||
FILE* fp = wxFopen( rulesFilepath, wxT( "rt" ) );
|
||||
|
||||
if( fp )
|
||||
{
|
||||
m_rulesFileLastMod = lastMod;
|
||||
m_ruleSelectors.clear();
|
||||
m_rules.clear();
|
||||
|
||||
FILE* fp = wxFopen( rulesFilepath, wxT( "rt" ) );
|
||||
|
||||
if( fp )
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
DRC_RULES_PARSER parser( m_pcb, fp, rulesFilepath );
|
||||
parser.Parse( m_ruleSelectors, m_rules );
|
||||
}
|
||||
catch( PARSE_ERROR& pe )
|
||||
{
|
||||
// Don't leave possibly malformed stuff around for us to trip over
|
||||
m_ruleSelectors.clear();
|
||||
m_rules.clear();
|
||||
DRC_RULES_PARSER parser( m_pcb, fp, rulesFilepath );
|
||||
parser.Parse( m_ruleSelectors, m_rules );
|
||||
}
|
||||
catch( PARSE_ERROR& pe )
|
||||
{
|
||||
// Don't leave possibly malformed stuff around for us to trip over
|
||||
m_ruleSelectors.clear();
|
||||
m_rules.clear();
|
||||
|
||||
DisplayError( m_drcDialog, pe.What() );
|
||||
}
|
||||
DisplayError( m_drcDialog, pe.What() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -399,7 +391,8 @@ void DRC::loadRules()
|
|||
|
||||
void DRC::RunTests( wxTextCtrl* aMessages )
|
||||
{
|
||||
loadRules();
|
||||
// Make absolutely sure these are up-to-date
|
||||
LoadRules();
|
||||
|
||||
wxASSERT( m_pcb == m_pcbEditorFrame->GetBoard() );
|
||||
|
||||
|
|
|
@ -170,7 +170,6 @@ private:
|
|||
bool m_drcRun;
|
||||
bool m_footprintsTested;
|
||||
|
||||
wxLongLong m_rulesFileLastMod;
|
||||
std::vector<DRC_SELECTOR*> m_ruleSelectors;
|
||||
std::vector<DRC_RULE*> m_rules;
|
||||
|
||||
|
@ -189,8 +188,6 @@ private:
|
|||
*/
|
||||
void updatePointers();
|
||||
|
||||
void loadRules();
|
||||
|
||||
EDA_UNITS userUnits() const { return m_pcbEditorFrame->GetUserUnits(); }
|
||||
|
||||
/**
|
||||
|
@ -296,6 +293,11 @@ private:
|
|||
//-----</single tests>---------------------------------------------
|
||||
|
||||
public:
|
||||
/**
|
||||
* Load the DRC rules. Must be called after the netclasses have been read.
|
||||
*/
|
||||
void LoadRules();
|
||||
|
||||
/**
|
||||
* Test the board footprints against a netlist. Will report DRCE_MISSING_FOOTPRINT,
|
||||
* DRCE_DUPLICATE_FOOTPRINT and DRCE_EXTRA_FOOTPRINT errors in aDRCList.
|
||||
|
|
|
@ -102,8 +102,8 @@ void MatchSelectors( const std::vector<DRC_SELECTOR*>& aSelectors,
|
|||
if( !bItem )
|
||||
continue;
|
||||
|
||||
NETCLASS* firstNetclass = candidate->m_MatchNetclasses[0];
|
||||
NETCLASS* secondNetclass = candidate->m_MatchNetclasses[1];
|
||||
NETCLASS* firstNetclass = candidate->m_MatchNetclasses[0].get();
|
||||
NETCLASS* secondNetclass = candidate->m_MatchNetclasses[1].get();
|
||||
|
||||
if( !( aNetclass == firstNetclass && bNetclass == secondNetclass )
|
||||
&& !( aNetclass == secondNetclass && bNetclass == firstNetclass ) )
|
||||
|
@ -113,7 +113,7 @@ void MatchSelectors( const std::vector<DRC_SELECTOR*>& aSelectors,
|
|||
}
|
||||
else if( candidate->m_MatchNetclasses.size() == 1 )
|
||||
{
|
||||
NETCLASS* matchNetclass = candidate->m_MatchNetclasses[0];
|
||||
NETCLASS* matchNetclass = candidate->m_MatchNetclasses[0].get();
|
||||
|
||||
if( matchNetclass != aNetclass && !( bItem && matchNetclass == bNetclass ) )
|
||||
continue;
|
||||
|
|
|
@ -50,7 +50,7 @@ public:
|
|||
class DRC_SELECTOR
|
||||
{
|
||||
public:
|
||||
std::vector<NETCLASS*> m_MatchNetclasses;
|
||||
std::vector<NETCLASSPTR> m_MatchNetclasses;
|
||||
std::vector<KICAD_T> m_MatchTypes;
|
||||
std::vector<PCB_LAYER_ID> m_MatchLayers;
|
||||
std::vector<wxString> m_MatchAreas;
|
||||
|
|
|
@ -126,9 +126,24 @@ DRC_SELECTOR* DRC_RULES_PARSER::parseDRC_SELECTOR( wxString* aRuleName )
|
|||
switch( token )
|
||||
{
|
||||
case T_match_netclass:
|
||||
{
|
||||
NeedSYMBOL();
|
||||
selector->m_MatchNetclasses.push_back( netclasses.Find( FromUTF8() ).get() );
|
||||
NETCLASSPTR netclass = netclasses.Find( FromUTF8() );
|
||||
|
||||
if( netclass )
|
||||
{
|
||||
selector->m_MatchNetclasses.push_back( std::move( netclass ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interesting situation here: if we don't inform the user they may have a typo
|
||||
// and can't figure out why their rules don't work.
|
||||
// If we do tell them then it gets really noisy if they're using a single rule
|
||||
// file for a class of board.
|
||||
}
|
||||
|
||||
NeedRIGHT();
|
||||
}
|
||||
break;
|
||||
|
||||
case T_match_type:
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
#include <macros.h>
|
||||
#include <3d_viewer/eda_3d_viewer.h>
|
||||
#include <richio.h>
|
||||
#include <filter_reader.h>
|
||||
#include <pgm_base.h>
|
||||
#include <msgpanel.h>
|
||||
#include <fp_lib_table.h>
|
||||
|
@ -45,10 +44,9 @@
|
|||
#include <pcbnew_id.h>
|
||||
#include <io_mgr.h>
|
||||
#include <wildcards_and_files_ext.h>
|
||||
|
||||
#include <tool/tool_manager.h>
|
||||
#include <drc/drc.h>
|
||||
#include <class_board.h>
|
||||
#include <build_version.h> // LEGACY_BOARD_FILE_VERSION
|
||||
|
||||
#include <wx/stdpaths.h>
|
||||
#include <pcb_layer_widget.h>
|
||||
#include <wx/wupdlock.h>
|
||||
|
@ -597,6 +595,8 @@ bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
|
|||
|
||||
SetBoard( loadedBoard );
|
||||
|
||||
m_toolManager->GetTool<DRC>()->LoadRules();
|
||||
|
||||
// we should not ask PLUGINs to do these items:
|
||||
loadedBoard->BuildListOfNets();
|
||||
loadedBoard->SynchronizeNetsAndNetClasses();
|
||||
|
|
|
@ -859,10 +859,10 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
|
|||
zone->GetColinearCorners( aBoard, colinearCorners );
|
||||
|
||||
// add shapes inflated by aMinThickness/2 in areas
|
||||
zone->TransformOutlinesShapeWithClearanceToPolygon( areas, inflate + zone_margin, false,
|
||||
zone->TransformOutlinesShapeWithClearanceToPolygon( areas, inflate + zone_margin,
|
||||
&colinearCorners );
|
||||
// add shapes with their exact mask layer size in initialPolys
|
||||
zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys, zone_margin, false,
|
||||
zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys, zone_margin,
|
||||
&colinearCorners );
|
||||
}
|
||||
|
||||
|
|
|
@ -526,18 +526,13 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_
|
|||
int extra_margin = Millimeter2iu( 0.002 );
|
||||
|
||||
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
|
||||
int zone_clearance = aZone->GetClearance();
|
||||
int edge_clearance = aZone->GetClearance( &dummyEdge );
|
||||
int zone_clearance = aZone->GetClearance();
|
||||
EDA_RECT zone_boundingbox = aZone->GetBoundingBox();
|
||||
|
||||
if( bds.m_CopperEdgeClearance > edge_clearance )
|
||||
edge_clearance = bds.m_CopperEdgeClearance;
|
||||
|
||||
// items outside the zone bounding box are skipped
|
||||
// the bounding box is the zone bounding box + the biggest clearance found in Netclass list
|
||||
EDA_RECT zone_boundingbox = aZone->GetBoundingBox();
|
||||
int biggest_clearance = bds.GetBiggestClearanceValue();
|
||||
biggest_clearance = std::max( biggest_clearance, zone_clearance ) + extra_margin;
|
||||
zone_boundingbox.Inflate( biggest_clearance );
|
||||
// items outside the zone bounding box are skipped, so it needs to be inflated by
|
||||
// the largest clearance value found in the netclasses and rules
|
||||
int biggest_clearance = std::max( zone_clearance, bds.GetBiggestClearanceValue() );
|
||||
zone_boundingbox.Inflate( biggest_clearance + extra_margin );
|
||||
|
||||
// Use a dummy pad to calculate hole clearance when a pad has a hole but is not on the
|
||||
// zone's copper layer. The dummy pad has the size and shape of the original pad's hole.
|
||||
|
@ -564,25 +559,19 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_
|
|||
if( pad->GetNetCode() != aZone->GetNetCode() || pad->GetNetCode() <= 0
|
||||
|| aZone->GetPadConnection( pad ) == ZONE_CONNECTION::NONE )
|
||||
{
|
||||
// for pads having a netcode different from the zone, use the net clearance:
|
||||
int gap = std::max( zone_clearance, pad->GetClearance() );
|
||||
|
||||
// for pads having the same netcode as the zone, the net clearance has no
|
||||
// meaning (clearance between object of the same net is 0) and the
|
||||
// zone_clearance can be set to 0 (In this case the netclass clearance is used)
|
||||
// therefore use the antipad clearance (thermal clearance) or the
|
||||
// zone_clearance if bigger.
|
||||
if( pad->GetNetCode() > 0 && pad->GetNetCode() == aZone->GetNetCode() )
|
||||
if( pad->GetBoundingBox().Intersects( zone_boundingbox ) )
|
||||
{
|
||||
int thermalGap = aZone->GetThermalReliefGap( pad );
|
||||
gap = std::max( zone_clearance, thermalGap );;
|
||||
}
|
||||
int gap;
|
||||
|
||||
EDA_RECT item_boundingbox = pad->GetBoundingBox();
|
||||
item_boundingbox.Inflate( pad->GetClearance() );
|
||||
// for pads having the same netcode as the zone, the net clearance has no
|
||||
// meaning so use the greater of the zone clearance and the thermal relief
|
||||
if( pad->GetNetCode() > 0 && pad->GetNetCode() == aZone->GetNetCode() )
|
||||
gap = std::max( zone_clearance, aZone->GetThermalReliefGap( pad ) );
|
||||
else
|
||||
gap = aZone->GetClearance( pad );
|
||||
|
||||
if( item_boundingbox.Intersects( zone_boundingbox ) )
|
||||
addKnockout( pad, gap, aHoles );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -597,38 +586,32 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_
|
|||
if( track->GetNetCode() == aZone->GetNetCode() && ( aZone->GetNetCode() != 0) )
|
||||
continue;
|
||||
|
||||
int gap = std::max( zone_clearance, track->GetClearance() ) + extra_margin;
|
||||
EDA_RECT item_boundingbox = track->GetBoundingBox();
|
||||
if( track->GetBoundingBox().Intersects( zone_boundingbox ) )
|
||||
{
|
||||
int gap = aZone->GetClearance( track ) + extra_margin;
|
||||
|
||||
if( item_boundingbox.Intersects( zone_boundingbox ) )
|
||||
track->TransformShapeWithClearanceToPolygon( aHoles, gap, m_low_def );
|
||||
}
|
||||
}
|
||||
|
||||
// Add graphic item clearances. They are by definition unconnected, and have no clearance
|
||||
// definitions of their own.
|
||||
//
|
||||
auto doGraphicItem = [&]( BOARD_ITEM* aItem )
|
||||
{
|
||||
// A item on the Edge_Cuts is always seen as on any layer:
|
||||
if( !aItem->IsOnLayer( aZone->GetLayer() ) && !aItem->IsOnLayer( Edge_Cuts ) )
|
||||
return;
|
||||
auto doGraphicItem =
|
||||
[&]( BOARD_ITEM* aItem )
|
||||
{
|
||||
// A item on the Edge_Cuts is always seen as on any layer:
|
||||
if( !aItem->IsOnLayer( aZone->GetLayer() ) && !aItem->IsOnLayer( Edge_Cuts ) )
|
||||
return;
|
||||
|
||||
if( !aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
|
||||
return;
|
||||
if( aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
|
||||
{
|
||||
bool ignoreLineWidth = aItem->IsOnLayer( Edge_Cuts );
|
||||
int gap = aZone->GetClearance( aItem );
|
||||
|
||||
bool ignoreLineWidth = false;
|
||||
int gap = zone_clearance;
|
||||
|
||||
if( aItem->IsOnLayer( Edge_Cuts ) )
|
||||
{
|
||||
gap = edge_clearance;
|
||||
|
||||
// edge cuts by definition don't have a width
|
||||
ignoreLineWidth = true;
|
||||
}
|
||||
|
||||
addKnockout( aItem, gap, ignoreLineWidth, aHoles );
|
||||
};
|
||||
addKnockout( aItem, gap, ignoreLineWidth, aHoles );
|
||||
}
|
||||
};
|
||||
|
||||
for( auto module : m_board->Modules() )
|
||||
{
|
||||
|
@ -660,26 +643,18 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_
|
|||
// A higher priority zone or keepout area is found: remove this area
|
||||
EDA_RECT item_boundingbox = zone->GetBoundingBox();
|
||||
|
||||
if( !item_boundingbox.Intersects( zone_boundingbox ) )
|
||||
continue;
|
||||
|
||||
// Add the zone outline area. Don't use any clearance for keepouts, or for zones with
|
||||
// the same net (they will be connected but will honor their own clearance, thermal
|
||||
// connections, etc.).
|
||||
bool sameNet = aZone->GetNetCode() == zone->GetNetCode();
|
||||
bool useNetClearance = true;
|
||||
int minClearance = zone_clearance;
|
||||
|
||||
// The final clearance is obviously the max value of each zone clearance
|
||||
minClearance = std::max( minClearance, zone->GetClearance() );
|
||||
|
||||
if( zone->GetIsKeepout() || sameNet )
|
||||
if( item_boundingbox.Intersects( zone_boundingbox ) )
|
||||
{
|
||||
minClearance = 0;
|
||||
useNetClearance = false;
|
||||
}
|
||||
// Add the zone outline area. Don't use any clearance for keepouts, or for zones
|
||||
// with the same net (they will be connected but will honor their own clearance,
|
||||
// thermal connections, etc.).
|
||||
int gap = 0;
|
||||
|
||||
zone->TransformOutlinesShapeWithClearanceToPolygon( aHoles, minClearance, useNetClearance );
|
||||
if( !zone->GetIsKeepout() && aZone->GetNetCode() != zone->GetNetCode() )
|
||||
gap = aZone->GetClearance( zone );
|
||||
|
||||
zone->TransformOutlinesShapeWithClearanceToPolygon( aHoles, gap );
|
||||
}
|
||||
}
|
||||
|
||||
aHoles.Simplify( SHAPE_POLY_SET::PM_FAST );
|
||||
|
|
Loading…
Reference in New Issue