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
{
return m_Start.Distance( m_End );

View File

@ -448,6 +448,7 @@ public:
void SetPadstack( const PADSTACK& aPadstack ) { m_padStack = aPadstack; }
const BOX2I GetBoundingBox() const override;
const BOX2I GetBoundingBox( PCB_LAYER_ID aLayer ) const;
void SetWidth( int aWidth ) 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).
*/
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;
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
if( aPad->GetCustomShapeInZoneOpt() == PADSTACK::CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL )
if( pad->GetCustomShapeInZoneOpt() == PADSTACK::CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL )
{
std::vector<VECTOR2I> convex_hull;
BuildConvexHull( convex_hull, poly );
@ -959,7 +960,7 @@ void ZONE_FILLER::addKnockout( PAD* aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_P
}
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
* 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,
SHAPE_POLY_SET& aFill,
std::vector<PAD*>& aThermalConnectionPads,
std::vector<BOARD_ITEM*>& aThermalConnectionPads,
std::vector<PAD*>& aNoConnectionPads )
{
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
@ -1078,6 +1101,17 @@ void ZONE_FILLER::knockoutThermalReliefs( const ZONE* aZone, PCB_LAYER_ID aLayer
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() )
{
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 );
}
@ -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 cornerStrategy = CORNER_STRATEGY::ROUND_ALL_CORNERS;
std::vector<PAD*> thermalConnectionPads;
std::vector<BOARD_ITEM*> thermalConnectionPads;
std::vector<PAD*> noConnectionPads;
std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
SHAPE_POLY_SET clearanceHoles;
@ -1869,8 +1931,11 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LA
* islands
*/
for( PAD* pad : thermalConnectionPads )
addHoleKnockout( pad, 0, clearanceHoles );
for( BOARD_ITEM* item : thermalConnectionPads )
{
if( item->Type() == PCB_PAD_T )
addHoleKnockout( static_cast<PAD*>( item ), 0, clearanceHoles );
}
aFillPolys.BooleanIntersection( aMaxExtents );
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
*/
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 )
{
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
int epsilon = bds.m_MaxError;
for( PAD* pad : aSpokedPadsList )
for( BOARD_ITEM* item : aSpokedPadsList )
{
// We currently only connect to pads, not pad holes
if( !pad->IsOnLayer( aLayer ) )
if( !item->IsOnLayer( aLayer ) )
continue;
constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
int thermalReliefGap = constraint.GetValue().Min();
int thermalReliefGap = 0;
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 );
int spoke_w = constraint.GetValue().Opt();
if( item->Type() == PCB_PAD_T )
{
pad = static_cast<PAD*>( item );
VECTOR2I padSize = pad->GetSize( aLayer );
// 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 );
if( pad->GetShape( aLayer) == PAD_SHAPE::CIRCLE
|| ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL && padSize.x == padSize.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
spoke_w = std::min( spoke_w, spoke_max_allowed_w );
if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
{
spoke_w = aZone->GetHatchThickness();
thermalReliefGap = getHatchFillThermalClearance( aZone, item, aLayer );
// Cannot create stubs having a width < zone min thickness
if( spoke_w < aZone->GetMinThickness() )
if( thermalReliefGap < 0 )
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;
}
int spoke_half_w = spoke_w / 2;
// Quick test here to possibly save us some work
BOX2I itemBB = pad->GetBoundingBox();
BOX2I itemBB = item->GetBoundingBox();
itemBB.Inflate( thermalReliefGap + epsilon );
if( !( itemBB.Intersects( zoneBB ) ) )
@ -2098,7 +2207,7 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
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 ) )
{
@ -2259,18 +2368,44 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
}
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
// from dirtying the real pad's cached shapes.
PAD dummy_pad( *pad );
dummy_pad.SetOrientation( ANGLE_0 );
if( pad )
{
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
// offset and is at position 0,0
dummy_pad.SetPosition( VECTOR2I( 0, 0 ) );
dummy_pad.SetOffset( aLayer, VECTOR2I( 0, 0 ) );
// Spokes are from center of pad shape, not from hole. So the dummy pad has no shape
// offset and is at position 0,0
dummy_pad.SetPosition( VECTOR2I( 0, 0 ) );
dummy_pad.SetOffset( aLayer, VECTOR2I( 0, 0 ) );
BOX2I spokesBox = dummy_pad.GetBoundingBox( aLayer );
VECTOR2I padSize = pad->GetSize( aLayer );
spokesBox = dummy_pad.GetBoundingBox( 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
// 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
// when rotated at 45 degrees. So we just build spokes at 0 degrees and rotate
// them later.
if( pad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE
|| ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL && padSize.x == padSize.y ) )
if( circular )
{
buildSpokesFromOrigin( spokesBox, ANGLE_0 );
if( pad->GetThermalSpokeAngle() != ANGLE_0 )
if( thermalSpokeAngle != ANGLE_0 )
{
//Rotate the last four elements of aspokeslist
for( auto it = aSpokesList.rbegin(); it != aSpokesList.rbegin() + 4; ++it )
it->Rotate( pad->GetThermalSpokeAngle() );
it->Rotate( thermalSpokeAngle );
}
}
else
{
buildSpokesFromOrigin( spokesBox, pad->GetThermalSpokeAngle() );
buildSpokesFromOrigin( spokesBox, thermalSpokeAngle );
}
auto spokeIter = aSpokesList.rbegin();
for( int ii = 0; ii < 4; ++ii, ++spokeIter )
{
spokeIter->Rotate( pad->GetOrientation() );
spokeIter->Move( pad->ShapePos( aLayer ) );
spokeIter->Rotate( orientation );
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(),
aZone->GetMinThickness() + pcbIUScale.mmToIU( 0.001 ) );
int linethickness = thickness - aZone->GetMinThickness();
int gridsize = thickness + aZone->GetHatchGap();
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" ) );
int outline_margin = aZone->GetMinThickness() * 1.1;
int deflated_thickness = aZone->GetHatchThickness() - aZone->GetMinThickness();
// Using GetHatchThickness() can look more consistent than GetMinThickness().
if( aZone->GetHatchBorderAlgorithm() && aZone->GetHatchThickness() > outline_margin )
outline_margin = aZone->GetHatchThickness();
// Don't let thickness drop below maxError * 2 or it might not get reinflated.
deflated_thickness = std::max( deflated_thickness, maxError * 2 );
// The fill has already been deflated to ensure GetMinThickness() so we just have to
// account for anything beyond that.
SHAPE_POLY_SET deflatedFilledPolys = aFillPolys.CloneDropTriangulation();
deflatedFilledPolys.Deflate( outline_margin - aZone->GetMinThickness(),
CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
deflatedFilledPolys.Deflate( deflated_thickness, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
holes.BooleanIntersection( deflatedFilledPolys );
DUMP_POLYS_TO_COPPER_LAYER( holes, In11_Cu, wxT( "fill-clipped-hatch-holes" ) );
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 );
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
// It happens for holes near the zone outline
for( int ii = 0; ii < holes.OutlineCount(); )

View File

@ -61,7 +61,7 @@ public:
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,
SHAPE_POLY_SET& aHoles );
@ -69,7 +69,7 @@ private:
void addHoleKnockout( PAD* aPad, int aGap, SHAPE_POLY_SET& aHoles );
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 );
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.
*/
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 );
/**

View File

@ -54,7 +54,7 @@ ZONE_SETTINGS::ZONE_SETTINGS()
m_HatchOrientation = ANGLE_0; // Grid style: orientation of grid lines
m_HatchSmoothingLevel = 0; // Grid pattern smoothing type. 0 = no smoothing
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_NetcodeSelection = 0; // Net code selection for the current zone
m_ZoneBorderDisplayStyle = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE; // Option to show the zone