Use thermal reliefs to guarantee connection to hatched zones.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/20447
This commit is contained in:
Jeff Young 2025-04-04 22:27:47 +01:00
parent 2c9c607b95
commit 8b33b25e83
5 changed files with 206 additions and 117 deletions

View File

@ -748,6 +748,25 @@ const BOX2I PCB_VIA::GetBoundingBox() const
} }
const BOX2I PCB_VIA::GetBoundingBox( PCB_LAYER_ID aLayer ) const
{
int radius = GetWidth( aLayer );
// via is round, this is its radius, rounded up
radius = ( radius + 1 ) / 2;
int ymax = m_Start.y + radius;
int xmax = m_Start.x + radius;
int ymin = m_Start.y - radius;
int xmin = m_Start.x - radius;
// return a rectangle which is [pos,dim) in nature. therefore the +1
return BOX2ISafe( VECTOR2I( xmin, ymin ),
VECTOR2L( (int64_t) xmax - xmin + 1, (int64_t) ymax - ymin + 1 ) );
}
double PCB_TRACK::GetLength() const double PCB_TRACK::GetLength() const
{ {
return m_Start.Distance( m_End ); return m_Start.Distance( m_End );

View File

@ -448,6 +448,7 @@ public:
void SetPadstack( const PADSTACK& aPadstack ) { m_padStack = aPadstack; } void SetPadstack( const PADSTACK& aPadstack ) { m_padStack = aPadstack; }
const BOX2I GetBoundingBox() const override; const BOX2I GetBoundingBox() const override;
const BOX2I GetBoundingBox( PCB_LAYER_ID aLayer ) const;
void SetWidth( int aWidth ) override; void SetWidth( int aWidth ) override;
int GetWidth() const override; int GetWidth() const override;

View File

@ -933,18 +933,19 @@ bool ZONE_FILLER::Fill( const std::vector<ZONE*>& aZones, bool aCheck, wxWindow*
/** /**
* Add a knockout for a pad. The knockout is 'aGap' larger than the pad (which might be * Add a knockout for a pad or via. The knockout is 'aGap' larger than the pad (which might be
* either the thermal clearance or the electrical clearance). * either the thermal clearance or the electrical clearance).
*/ */
void ZONE_FILLER::addKnockout( PAD* aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles ) void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles )
{ {
if( aPad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM ) if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
{ {
PAD* pad = static_cast<PAD*>( aItem );
SHAPE_POLY_SET poly; SHAPE_POLY_SET poly;
aPad->TransformShapeToPolygon( poly, aLayer, aGap, m_maxError, ERROR_OUTSIDE ); pad->TransformShapeToPolygon( poly, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
// the pad shape in zone can be its convex hull or the shape itself // the pad shape in zone can be its convex hull or the shape itself
if( aPad->GetCustomShapeInZoneOpt() == PADSTACK::CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL ) if( pad->GetCustomShapeInZoneOpt() == PADSTACK::CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL )
{ {
std::vector<VECTOR2I> convex_hull; std::vector<VECTOR2I> convex_hull;
BuildConvexHull( convex_hull, poly ); BuildConvexHull( convex_hull, poly );
@ -959,7 +960,7 @@ void ZONE_FILLER::addKnockout( PAD* aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_P
} }
else else
{ {
aPad->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE ); aItem->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
} }
} }
@ -973,6 +974,28 @@ void ZONE_FILLER::addHoleKnockout( PAD* aPad, int aGap, SHAPE_POLY_SET& aHoles )
} }
int getHatchFillThermalClearance( const ZONE* aZone, BOARD_ITEM* aItem, PCB_LAYER_ID aLayer )
{
int minorAxis = 0;
if( aItem->Type() == PCB_PAD_T )
{
PAD* pad = static_cast<PAD*>( aItem );
VECTOR2I padSize = pad->GetSize( aLayer );
minorAxis = std::min( padSize.x, padSize.y );
}
else if( aItem->Type() == PCB_VIA_T )
{
PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
minorAxis = via->GetWidth( aLayer );
}
return ( aZone->GetHatchGap() - aZone->GetHatchThickness() - minorAxis ) / 2;
}
/** /**
* Add a knockout for a graphic item. The knockout is 'aGap' larger than the item (which * Add a knockout for a graphic item. The knockout is 'aGap' larger than the item (which
* might be either the electrical clearance or the board edge clearance). * might be either the electrical clearance or the board edge clearance).
@ -1039,7 +1062,7 @@ void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap,
*/ */
void ZONE_FILLER::knockoutThermalReliefs( const ZONE* aZone, PCB_LAYER_ID aLayer, void ZONE_FILLER::knockoutThermalReliefs( const ZONE* aZone, PCB_LAYER_ID aLayer,
SHAPE_POLY_SET& aFill, SHAPE_POLY_SET& aFill,
std::vector<PAD*>& aThermalConnectionPads, std::vector<BOARD_ITEM*>& aThermalConnectionPads,
std::vector<PAD*>& aNoConnectionPads ) std::vector<PAD*>& aNoConnectionPads )
{ {
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
@ -1078,6 +1101,17 @@ void ZONE_FILLER::knockoutThermalReliefs( const ZONE* aZone, PCB_LAYER_ID aLayer
continue; continue;
} }
// We put thermal reliefs on all connected items in a hatch fill zone as a way of
// guaranteeing that they connect to the webbing. (The thermal gap is the hatch
// gap minus the pad/via size, making it impossible for the pad/via to be isolated
// within the center of a hole.)
if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
{
aThermalConnectionPads.push_back( pad );
addKnockout( pad, aLayer, getHatchFillThermalClearance( aZone, pad, aLayer ), holes );
continue;
}
if( aZone->IsTeardropArea() ) if( aZone->IsTeardropArea() )
{ {
connection = ZONE_CONNECTION::FULL; connection = ZONE_CONNECTION::FULL;
@ -1143,6 +1177,34 @@ void ZONE_FILLER::knockoutThermalReliefs( const ZONE* aZone, PCB_LAYER_ID aLayer
} }
} }
// We put thermal reliefs on all connected items in a hatch fill zone as a way of guaranteeing
// that they connect to the webbing. (The thermal gap is the hatch gap minus the pad/via size,
// making it impossible for the pad/via to be isolated within the center of a hole.)
if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
{
for( PCB_TRACK* track : m_board->Tracks() )
{
if( track->Type() == PCB_VIA_T )
{
PCB_VIA* via = static_cast<PCB_VIA*>( track );
BOX2I viaBBox = via->GetBoundingBox();
viaBBox.Inflate( m_worstClearance );
if( !viaBBox.Intersects( aZone->GetBoundingBox() ) )
continue;
bool noConnection = via->GetNetCode() != aZone->GetNetCode();
if( noConnection )
continue;
aThermalConnectionPads.push_back( via );
addKnockout( via, aLayer, getHatchFillThermalClearance( aZone, via, aLayer ), holes );
}
}
}
aFill.BooleanSubtract( holes ); aFill.BooleanSubtract( holes );
} }
@ -1680,7 +1742,7 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LA
CORNER_STRATEGY fastCornerStrategy = CORNER_STRATEGY::CHAMFER_ALL_CORNERS; CORNER_STRATEGY fastCornerStrategy = CORNER_STRATEGY::CHAMFER_ALL_CORNERS;
CORNER_STRATEGY cornerStrategy = CORNER_STRATEGY::ROUND_ALL_CORNERS; CORNER_STRATEGY cornerStrategy = CORNER_STRATEGY::ROUND_ALL_CORNERS;
std::vector<PAD*> thermalConnectionPads; std::vector<BOARD_ITEM*> thermalConnectionPads;
std::vector<PAD*> noConnectionPads; std::vector<PAD*> noConnectionPads;
std::deque<SHAPE_LINE_CHAIN> thermalSpokes; std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
SHAPE_POLY_SET clearanceHoles; SHAPE_POLY_SET clearanceHoles;
@ -1869,8 +1931,11 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LA
* islands * islands
*/ */
for( PAD* pad : thermalConnectionPads ) for( BOARD_ITEM* item : thermalConnectionPads )
addHoleKnockout( pad, 0, clearanceHoles ); {
if( item->Type() == PCB_PAD_T )
addHoleKnockout( static_cast<PAD*>( item ), 0, clearanceHoles );
}
aFillPolys.BooleanIntersection( aMaxExtents ); aFillPolys.BooleanIntersection( aMaxExtents );
DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In16_Cu, wxT( "after-trim-to-outline" ) ); DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In16_Cu, wxT( "after-trim-to-outline" ) );
@ -2047,7 +2112,7 @@ bool ZONE_FILLER::fillSingleZone( ZONE* aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_S
* Function buildThermalSpokes * Function buildThermalSpokes
*/ */
void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer, void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
const std::vector<PAD*>& aSpokedPadsList, const std::vector<BOARD_ITEM*>& aSpokedPadsList,
std::deque<SHAPE_LINE_CHAIN>& aSpokesList ) std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
{ {
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
@ -2061,36 +2126,80 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
// The boundary may be off by MaxError // The boundary may be off by MaxError
int epsilon = bds.m_MaxError; int epsilon = bds.m_MaxError;
for( PAD* pad : aSpokedPadsList ) for( BOARD_ITEM* item : aSpokedPadsList )
{ {
// We currently only connect to pads, not pad holes // We currently only connect to pads, not pad holes
if( !pad->IsOnLayer( aLayer ) ) if( !item->IsOnLayer( aLayer ) )
continue; continue;
constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer ); int thermalReliefGap = 0;
int thermalReliefGap = constraint.GetValue().Min(); int spoke_w = 0;
PAD* pad = nullptr;
PCB_VIA* via = nullptr;
bool circular = false;
constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, aZone, aLayer ); if( item->Type() == PCB_PAD_T )
int spoke_w = constraint.GetValue().Opt(); {
pad = static_cast<PAD*>( item );
VECTOR2I padSize = pad->GetSize( aLayer );
// Spoke width should ideally be smaller than the pad minor axis. if( pad->GetShape( aLayer) == PAD_SHAPE::CIRCLE
// Otherwise the thermal shape is not really a thermal relief, || ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL && padSize.x == padSize.y ) )
// and the algo to count the actual number of spokes can fail {
int spoke_max_allowed_w = std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y ); circular = true;
}
}
else if( item->Type() == PCB_VIA_T )
{
via = static_cast<PCB_VIA*>( item );
circular = true;
}
spoke_w = std::clamp( spoke_w, constraint.Value().Min(), constraint.Value().Max() ); // Thermal connections in a hatched zone are based on the hatch. Their primary function is to
// guarantee that pads/vias connect to the webbing. (The thermal gap is the hatch gap width minus
// the pad/via size, making it impossible for the pad/via to be isolated within the center of a
// hole.)
// ensure the spoke width is smaller than the pad minor size if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
spoke_w = std::min( spoke_w, spoke_max_allowed_w ); {
spoke_w = aZone->GetHatchThickness();
thermalReliefGap = getHatchFillThermalClearance( aZone, item, aLayer );
// Cannot create stubs having a width < zone min thickness if( thermalReliefGap < 0 )
if( spoke_w < aZone->GetMinThickness() ) continue;
}
else if( pad )
{
constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
thermalReliefGap = constraint.GetValue().Min();
constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, aZone, aLayer );
spoke_w = constraint.GetValue().Opt();
// Spoke width should ideally be smaller than the pad minor axis.
// Otherwise the thermal shape is not really a thermal relief,
// and the algo to count the actual number of spokes can fail
int spoke_max_allowed_w = std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y );
spoke_w = std::clamp( spoke_w, constraint.Value().Min(), constraint.Value().Max() );
// ensure the spoke width is smaller than the pad minor size
spoke_w = std::min( spoke_w, spoke_max_allowed_w );
// Cannot create stubs having a width < zone min thickness
if( spoke_w < aZone->GetMinThickness() )
continue;
}
else
{
// We don't currently support via thermal connections *except* in a hatched zone.
continue; continue;
}
int spoke_half_w = spoke_w / 2; int spoke_half_w = spoke_w / 2;
// Quick test here to possibly save us some work // Quick test here to possibly save us some work
BOX2I itemBB = pad->GetBoundingBox(); BOX2I itemBB = item->GetBoundingBox();
itemBB.Inflate( thermalReliefGap + epsilon ); itemBB.Inflate( thermalReliefGap + epsilon );
if( !( itemBB.Intersects( zoneBB ) ) ) if( !( itemBB.Intersects( zoneBB ) ) )
@ -2098,7 +2207,7 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
bool customSpokes = false; bool customSpokes = false;
if( pad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM ) if( pad && pad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
{ {
for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) ) for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
{ {
@ -2259,18 +2368,44 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
} }
else else
{ {
EDA_ANGLE thermalSpokeAngle;
if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
thermalSpokeAngle = aZone->GetHatchOrientation();
else if( pad )
thermalSpokeAngle = pad->GetThermalSpokeAngle();
BOX2I spokesBox;
VECTOR2I position;
EDA_ANGLE orientation;
// Since the bounding-box needs to be correclty rotated we use a dummy pad to keep // Since the bounding-box needs to be correclty rotated we use a dummy pad to keep
// from dirtying the real pad's cached shapes. // from dirtying the real pad's cached shapes.
PAD dummy_pad( *pad ); if( pad )
dummy_pad.SetOrientation( ANGLE_0 ); {
PAD dummy_pad( *pad );
dummy_pad.SetOrientation( ANGLE_0 );
// Spokes are from center of pad shape, not from hole. So the dummy pad has no shape // Spokes are from center of pad shape, not from hole. So the dummy pad has no shape
// offset and is at position 0,0 // offset and is at position 0,0
dummy_pad.SetPosition( VECTOR2I( 0, 0 ) ); dummy_pad.SetPosition( VECTOR2I( 0, 0 ) );
dummy_pad.SetOffset( aLayer, VECTOR2I( 0, 0 ) ); dummy_pad.SetOffset( aLayer, VECTOR2I( 0, 0 ) );
BOX2I spokesBox = dummy_pad.GetBoundingBox( aLayer ); spokesBox = dummy_pad.GetBoundingBox( aLayer );
VECTOR2I padSize = pad->GetSize( aLayer ); position = pad->ShapePos( aLayer );
orientation = pad->GetOrientation();
// Remove group membership from dummy item before deleting
dummy_pad.SetParentGroup( nullptr );
}
else if( via )
{
PCB_VIA dummy_via( *via );
dummy_via.SetPosition( VECTOR2I( 0, 0 ) );
spokesBox = dummy_via.GetBoundingBox( aLayer );
position = via->GetPosition();
}
// Add the half width of the zone mininum width to the inflate amount to account for // Add the half width of the zone mininum width to the inflate amount to account for
// the fact that the deflation procedure will shrink the results by half the half the // the fact that the deflation procedure will shrink the results by half the half the
@ -2280,33 +2415,29 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
// This is a touchy case because the bounding box for circles overshoots the mark // This is a touchy case because the bounding box for circles overshoots the mark
// when rotated at 45 degrees. So we just build spokes at 0 degrees and rotate // when rotated at 45 degrees. So we just build spokes at 0 degrees and rotate
// them later. // them later.
if( pad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE if( circular )
|| ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL && padSize.x == padSize.y ) )
{ {
buildSpokesFromOrigin( spokesBox, ANGLE_0 ); buildSpokesFromOrigin( spokesBox, ANGLE_0 );
if( pad->GetThermalSpokeAngle() != ANGLE_0 ) if( thermalSpokeAngle != ANGLE_0 )
{ {
//Rotate the last four elements of aspokeslist //Rotate the last four elements of aspokeslist
for( auto it = aSpokesList.rbegin(); it != aSpokesList.rbegin() + 4; ++it ) for( auto it = aSpokesList.rbegin(); it != aSpokesList.rbegin() + 4; ++it )
it->Rotate( pad->GetThermalSpokeAngle() ); it->Rotate( thermalSpokeAngle );
} }
} }
else else
{ {
buildSpokesFromOrigin( spokesBox, pad->GetThermalSpokeAngle() ); buildSpokesFromOrigin( spokesBox, thermalSpokeAngle );
} }
auto spokeIter = aSpokesList.rbegin(); auto spokeIter = aSpokesList.rbegin();
for( int ii = 0; ii < 4; ++ii, ++spokeIter ) for( int ii = 0; ii < 4; ++ii, ++spokeIter )
{ {
spokeIter->Rotate( pad->GetOrientation() ); spokeIter->Rotate( orientation );
spokeIter->Move( pad->ShapePos( aLayer ) ); spokeIter->Move( position );
} }
// Remove group membership from dummy item before deleting
dummy_pad.SetParentGroup( nullptr );
} }
} }
@ -2329,7 +2460,6 @@ bool ZONE_FILLER::addHatchFillTypeOnZone( const ZONE* aZone, PCB_LAYER_ID aLayer
int thickness = std::max( aZone->GetHatchThickness(), int thickness = std::max( aZone->GetHatchThickness(),
aZone->GetMinThickness() + pcbIUScale.mmToIU( 0.001 ) ); aZone->GetMinThickness() + pcbIUScale.mmToIU( 0.001 ) );
int linethickness = thickness - aZone->GetMinThickness();
int gridsize = thickness + aZone->GetHatchGap(); int gridsize = thickness + aZone->GetHatchGap();
int maxError = m_board->GetDesignSettings().m_MaxError; int maxError = m_board->GetDesignSettings().m_MaxError;
@ -2469,84 +2599,23 @@ bool ZONE_FILLER::addHatchFillTypeOnZone( const ZONE* aZone, PCB_LAYER_ID aLayer
DUMP_POLYS_TO_COPPER_LAYER( holes, In10_Cu, wxT( "hatch-holes" ) ); DUMP_POLYS_TO_COPPER_LAYER( holes, In10_Cu, wxT( "hatch-holes" ) );
int outline_margin = aZone->GetMinThickness() * 1.1; int deflated_thickness = aZone->GetHatchThickness() - aZone->GetMinThickness();
// Using GetHatchThickness() can look more consistent than GetMinThickness(). // Don't let thickness drop below maxError * 2 or it might not get reinflated.
if( aZone->GetHatchBorderAlgorithm() && aZone->GetHatchThickness() > outline_margin ) deflated_thickness = std::max( deflated_thickness, maxError * 2 );
outline_margin = aZone->GetHatchThickness();
// The fill has already been deflated to ensure GetMinThickness() so we just have to // The fill has already been deflated to ensure GetMinThickness() so we just have to
// account for anything beyond that. // account for anything beyond that.
SHAPE_POLY_SET deflatedFilledPolys = aFillPolys.CloneDropTriangulation(); SHAPE_POLY_SET deflatedFilledPolys = aFillPolys.CloneDropTriangulation();
deflatedFilledPolys.Deflate( outline_margin - aZone->GetMinThickness(), deflatedFilledPolys.Deflate( deflated_thickness, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
holes.BooleanIntersection( deflatedFilledPolys ); holes.BooleanIntersection( deflatedFilledPolys );
DUMP_POLYS_TO_COPPER_LAYER( holes, In11_Cu, wxT( "fill-clipped-hatch-holes" ) ); DUMP_POLYS_TO_COPPER_LAYER( holes, In11_Cu, wxT( "fill-clipped-hatch-holes" ) );
SHAPE_POLY_SET deflatedOutline = aZone->Outline()->CloneDropTriangulation(); SHAPE_POLY_SET deflatedOutline = aZone->Outline()->CloneDropTriangulation();
deflatedOutline.Deflate( outline_margin, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError ); deflatedOutline.Deflate( aZone->GetMinThickness(), CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
holes.BooleanIntersection( deflatedOutline ); holes.BooleanIntersection( deflatedOutline );
DUMP_POLYS_TO_COPPER_LAYER( holes, In12_Cu, wxT( "outline-clipped-hatch-holes" ) ); DUMP_POLYS_TO_COPPER_LAYER( holes, In12_Cu, wxT( "outline-clipped-hatch-holes" ) );
if( aZone->GetNetCode() != 0 )
{
// Vias and pads connected to the zone must not be allowed to become isolated inside
// one of the holes. Effectively this means their copper outline needs to be expanded
// to be at least as wide as the gap so that it is guaranteed to touch at least one
// edge.
BOX2I zone_boundingbox = aZone->GetBoundingBox();
SHAPE_POLY_SET aprons;
int min_apron_radius = ( aZone->GetHatchGap() * 10 ) / 19;
for( PCB_TRACK* track : m_board->Tracks() )
{
if( track->Type() == PCB_VIA_T )
{
PCB_VIA* via = static_cast<PCB_VIA*>( track );
if( via->GetNetCode() == aZone->GetNetCode()
&& via->IsOnLayer( aLayer )
&& via->GetBoundingBox().Intersects( zone_boundingbox ) )
{
int r = std::max( min_apron_radius,
via->GetDrillValue() / 2 + outline_margin );
TransformCircleToPolygon( aprons, via->GetPosition(), r, maxError,
ERROR_OUTSIDE );
}
}
}
for( FOOTPRINT* footprint : m_board->Footprints() )
{
for( PAD* pad : footprint->Pads() )
{
if( pad->GetNetCode() == aZone->GetNetCode()
&& pad->IsOnLayer( aLayer )
&& pad->GetBoundingBox().Intersects( zone_boundingbox ) )
{
// What we want is to bulk up the pad shape so that the narrowest bit of
// copper between the hole and the apron edge is at least outline_margin
// wide (and that the apron itself meets min_apron_radius. But that would
// take a lot of code and math, and the following approximation is close
// enough.
int pad_width = std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y );
int slot_width = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
int min_annular_ring_width = ( pad_width - slot_width ) / 2;
int clearance = std::max( min_apron_radius - pad_width / 2,
outline_margin - min_annular_ring_width );
clearance = std::max( 0, clearance - linethickness / 2 );
pad->TransformShapeToPolygon( aprons, aLayer, clearance, maxError,
ERROR_OUTSIDE );
}
}
}
holes.BooleanSubtract( aprons );
}
DUMP_POLYS_TO_COPPER_LAYER( holes, In13_Cu, wxT( "pad-via-clipped-hatch-holes" ) );
// Now filter truncated holes to avoid small holes in pattern // Now filter truncated holes to avoid small holes in pattern
// It happens for holes near the zone outline // It happens for holes near the zone outline
for( int ii = 0; ii < holes.OutlineCount(); ) for( int ii = 0; ii < holes.OutlineCount(); )

View File

@ -61,7 +61,7 @@ public:
private: private:
void addKnockout( PAD* aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles ); void addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles );
void addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap, bool aIgnoreLineWidth, void addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap, bool aIgnoreLineWidth,
SHAPE_POLY_SET& aHoles ); SHAPE_POLY_SET& aHoles );
@ -69,7 +69,7 @@ private:
void addHoleKnockout( PAD* aPad, int aGap, SHAPE_POLY_SET& aHoles ); void addHoleKnockout( PAD* aPad, int aGap, SHAPE_POLY_SET& aHoles );
void knockoutThermalReliefs( const ZONE* aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aFill, void knockoutThermalReliefs( const ZONE* aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aFill,
std::vector<PAD*>& aThermalConnectionPads, std::vector<BOARD_ITEM*>& aThermalConnectionPads,
std::vector<PAD*>& aNoConnectionPads ); std::vector<PAD*>& aNoConnectionPads );
void buildCopperItemClearances( const ZONE* aZone, PCB_LAYER_ID aLayer, void buildCopperItemClearances( const ZONE* aZone, PCB_LAYER_ID aLayer,
@ -101,7 +101,7 @@ private:
* Constructs a list of all thermal spokes for the given zone. * Constructs a list of all thermal spokes for the given zone.
*/ */
void buildThermalSpokes( const ZONE* box, PCB_LAYER_ID aLayer, void buildThermalSpokes( const ZONE* box, PCB_LAYER_ID aLayer,
const std::vector<PAD*>& aSpokedPadsList, const std::vector<BOARD_ITEM*>& aSpokedPadsList,
std::deque<SHAPE_LINE_CHAIN>& aSpokes ); std::deque<SHAPE_LINE_CHAIN>& aSpokes );
/** /**

View File

@ -54,7 +54,7 @@ ZONE_SETTINGS::ZONE_SETTINGS()
m_HatchOrientation = ANGLE_0; // Grid style: orientation of grid lines m_HatchOrientation = ANGLE_0; // Grid style: orientation of grid lines
m_HatchSmoothingLevel = 0; // Grid pattern smoothing type. 0 = no smoothing m_HatchSmoothingLevel = 0; // Grid pattern smoothing type. 0 = no smoothing
m_HatchSmoothingValue = 0.1; // Grid pattern chamfer value relative to the gap value m_HatchSmoothingValue = 0.1; // Grid pattern chamfer value relative to the gap value
m_HatchHoleMinArea = 0.3; // Min size before holes are dropped (ratio of hole size) m_HatchHoleMinArea = 0.15; // Min size before holes are dropped (ratio of hole size)
m_HatchBorderAlgorithm = 1; // 0 = use zone min thickness; 1 = use hatch width m_HatchBorderAlgorithm = 1; // 0 = use zone min thickness; 1 = use hatch width
m_NetcodeSelection = 0; // Net code selection for the current zone m_NetcodeSelection = 0; // Net code selection for the current zone
m_ZoneBorderDisplayStyle = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE; // Option to show the zone m_ZoneBorderDisplayStyle = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE; // Option to show the zone