1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
5  * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
6  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
24  */
25 
26 #include <bitmaps.h>
27 #include <geometry/geometry_utils.h>
28 #include <geometry/shape_null.h>
29 #include <core/mirror.h>
30 #include <advanced_config.h>
31 #include <pcb_edit_frame.h>
32 #include <pcb_screen.h>
33 #include <board.h>
34 #include <board_design_settings.h>
35 #include <pad.h>
36 #include <zone.h>
37 #include <string_utils.h>
38 #include <math_for_graphics.h>
39 #include <settings/color_settings.h>
40 #include <settings/settings_manager.h>
41 #include <trigo.h>
42 #include <i18n_utility.h>
43 
ZONE(BOARD_ITEM_CONTAINER * aParent,bool aInFP)44 ZONE::ZONE( BOARD_ITEM_CONTAINER* aParent, bool aInFP ) :
45         BOARD_CONNECTED_ITEM( aParent, aInFP ? PCB_FP_ZONE_T : PCB_ZONE_T ),
46         m_area( 0.0 )
47 {
48     m_CornerSelection = nullptr;                // no corner is selected
49     m_isFilled = false;                         // fill status : true when the zone is filled
50     m_fillMode = ZONE_FILL_MODE::POLYGONS;
51     m_borderStyle = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE;
52     m_borderHatchPitch = GetDefaultHatchPitch();
53     m_hv45 = false;
54     m_hatchThickness = 0;
55     m_hatchGap = 0;
56     m_hatchOrientation = 0.0;
57     m_hatchSmoothingLevel = 0;          // Grid pattern smoothing type. 0 = no smoothing
58     m_hatchSmoothingValue = 0.1;        // Grid pattern chamfer value relative to the gap value
59                                         // used only if m_hatchSmoothingLevel > 0
60     m_hatchHoleMinArea = 0.3;           // Min size before holes are dropped (ratio of hole size)
61     m_hatchBorderAlgorithm = 1;         // 0 = use zone min thickness; 1 = use hatch width
62     m_priority = 0;
63     m_cornerSmoothingType = ZONE_SETTINGS::SMOOTHING_NONE;
64     SetIsRuleArea( aInFP );             // Zones living in footprints have the rule area option
65     SetDoNotAllowCopperPour( false );   // has meaning only if m_isRuleArea == true
66     SetDoNotAllowVias( true );          // has meaning only if m_isRuleArea == true
67     SetDoNotAllowTracks( true );        // has meaning only if m_isRuleArea == true
68     SetDoNotAllowPads( true );          // has meaning only if m_isRuleArea == true
69     SetDoNotAllowFootprints( false );   // has meaning only if m_isRuleArea == true
70     m_cornerRadius = 0;
71     SetLocalFlags( 0 );                 // flags temporary used in zone calculations
72     m_Poly = new SHAPE_POLY_SET();      // Outlines
73     m_fillVersion = 5;                  // set the "old" way to build filled polygon areas (< 6.0.x)
74     m_islandRemovalMode = ISLAND_REMOVAL_MODE::ALWAYS;
75     aParent->GetZoneSettings().ExportSetting( *this );
76 
77     m_ZoneMinThickness = Mils2iu( ZONE_THICKNESS_MIL );
78     m_thermalReliefSpokeWidth = Mils2iu( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MIL );
79     m_thermalReliefGap = Mils2iu( ZONE_THERMAL_RELIEF_GAP_MIL );
80 
81     m_needRefill = false;   // True only after edits.
82 }
83 
84 
ZONE(const ZONE & aZone)85 ZONE::ZONE( const ZONE& aZone )
86         : BOARD_CONNECTED_ITEM( aZone ),
87         m_Poly( nullptr ),
88         m_CornerSelection( nullptr )
89 {
90     InitDataFromSrcInCopyCtor( aZone );
91 }
92 
93 
operator =(const ZONE & aOther)94 ZONE& ZONE::operator=( const ZONE& aOther )
95 {
96     BOARD_CONNECTED_ITEM::operator=( aOther );
97 
98     InitDataFromSrcInCopyCtor( aOther );
99 
100     return *this;
101 }
102 
103 
~ZONE()104 ZONE::~ZONE()
105 {
106     delete m_Poly;
107     delete m_CornerSelection;
108 }
109 
110 
InitDataFromSrcInCopyCtor(const ZONE & aZone)111 void ZONE::InitDataFromSrcInCopyCtor( const ZONE& aZone )
112 {
113     // members are expected non initialize in this.
114     // InitDataFromSrcInCopyCtor() is expected to be called
115     // only from a copy constructor.
116 
117     // Copy only useful EDA_ITEM flags:
118     m_flags                   = aZone.m_flags;
119     m_forceVisible            = aZone.m_forceVisible;
120 
121     // Replace the outlines for aZone outlines.
122     delete m_Poly;
123     m_Poly = new SHAPE_POLY_SET( *aZone.m_Poly );
124 
125     m_cornerSmoothingType     = aZone.m_cornerSmoothingType;
126     m_cornerRadius            = aZone.m_cornerRadius;
127     m_zoneName                = aZone.m_zoneName;
128     m_priority                = aZone.m_priority;
129     m_isRuleArea              = aZone.m_isRuleArea;
130     SetLayerSet( aZone.GetLayerSet() );
131 
132     m_doNotAllowCopperPour    = aZone.m_doNotAllowCopperPour;
133     m_doNotAllowVias          = aZone.m_doNotAllowVias;
134     m_doNotAllowTracks        = aZone.m_doNotAllowTracks;
135     m_doNotAllowPads          = aZone.m_doNotAllowPads;
136     m_doNotAllowFootprints    = aZone.m_doNotAllowFootprints;
137 
138     m_PadConnection           = aZone.m_PadConnection;
139     m_ZoneClearance           = aZone.m_ZoneClearance;     // clearance value
140     m_ZoneMinThickness        = aZone.m_ZoneMinThickness;
141     m_fillVersion             = aZone.m_fillVersion;
142     m_islandRemovalMode       = aZone.m_islandRemovalMode;
143     m_minIslandArea           = aZone.m_minIslandArea;
144 
145     m_isFilled                = aZone.m_isFilled;
146     m_needRefill              = aZone.m_needRefill;
147 
148     m_thermalReliefGap        = aZone.m_thermalReliefGap;
149     m_thermalReliefSpokeWidth = aZone.m_thermalReliefSpokeWidth;
150 
151     m_fillMode                = aZone.m_fillMode;         // solid vs. hatched
152     m_hatchThickness          = aZone.m_hatchThickness;
153     m_hatchGap                = aZone.m_hatchGap;
154     m_hatchOrientation        = aZone.m_hatchOrientation;
155     m_hatchSmoothingLevel     = aZone.m_hatchSmoothingLevel;
156     m_hatchSmoothingValue     = aZone.m_hatchSmoothingValue;
157     m_hatchBorderAlgorithm    = aZone.m_hatchBorderAlgorithm;
158     m_hatchHoleMinArea        = aZone.m_hatchHoleMinArea;
159 
160     // For corner moving, corner index to drag, or nullptr if no selection
161     delete m_CornerSelection;
162     m_CornerSelection         = nullptr;
163 
164     for( PCB_LAYER_ID layer : aZone.GetLayerSet().Seq() )
165     {
166         m_FilledPolysList[layer]  = aZone.m_FilledPolysList.at( layer );
167         m_RawPolysList[layer]     = aZone.m_RawPolysList.at( layer );
168         m_filledPolysHash[layer]  = aZone.m_filledPolysHash.at( layer );
169         m_FillSegmList[layer]     = aZone.m_FillSegmList.at( layer ); // vector <> copy
170         m_insulatedIslands[layer] = aZone.m_insulatedIslands.at( layer );
171     }
172 
173     m_borderStyle             = aZone.m_borderStyle;
174     m_borderHatchPitch        = aZone.m_borderHatchPitch;
175     m_borderHatchLines        = aZone.m_borderHatchLines;
176 
177     SetLocalFlags( aZone.GetLocalFlags() );
178 
179     m_netinfo                 = aZone.m_netinfo;
180 
181     m_hv45                    = aZone.m_hv45;
182     m_area                    = aZone.m_area;
183 }
184 
185 
Clone() const186 EDA_ITEM* ZONE::Clone() const
187 {
188     return new ZONE( *this );
189 }
190 
191 
UnFill()192 bool ZONE::UnFill()
193 {
194     bool change = false;
195 
196     for( std::pair<const PCB_LAYER_ID, SHAPE_POLY_SET>& pair : m_FilledPolysList )
197     {
198         change |= !pair.second.IsEmpty();
199         m_insulatedIslands[pair.first].clear();
200         pair.second.RemoveAllContours();
201     }
202 
203     for( std::pair<const PCB_LAYER_ID, std::vector<SEG> >& pair : m_FillSegmList )
204     {
205         change |= !pair.second.empty();
206         pair.second.clear();
207     }
208 
209     m_isFilled = false;
210     m_fillFlags.clear();
211 
212     return change;
213 }
214 
215 
GetPosition() const216 wxPoint ZONE::GetPosition() const
217 {
218     return (wxPoint) GetCornerPosition( 0 );
219 }
220 
221 
GetLayer() const222 PCB_LAYER_ID ZONE::GetLayer() const
223 {
224     return BOARD_ITEM::GetLayer();
225 }
226 
227 
IsOnCopperLayer() const228 bool ZONE::IsOnCopperLayer() const
229 {
230     return ( m_layerSet & LSET::AllCuMask() ).count() > 0;
231 }
232 
233 
CommonLayerExists(const LSET aLayerSet) const234 bool ZONE::CommonLayerExists( const LSET aLayerSet ) const
235 {
236     LSET common = GetLayerSet() & aLayerSet;
237 
238     return common.count() > 0;
239 }
240 
241 
SetLayer(PCB_LAYER_ID aLayer)242 void ZONE::SetLayer( PCB_LAYER_ID aLayer )
243 {
244     SetLayerSet( LSET( aLayer ) );
245 
246     m_layer = aLayer;
247 }
248 
249 
SetLayerSet(LSET aLayerSet)250 void ZONE::SetLayerSet( LSET aLayerSet )
251 {
252     if( GetIsRuleArea() )
253     {
254         // Rule areas can only exist on copper layers
255         aLayerSet &= LSET::AllCuMask();
256     }
257 
258     if( aLayerSet.count() == 0 )
259         return;
260 
261     if( m_layerSet != aLayerSet )
262     {
263         SetNeedRefill( true );
264 
265         UnFill();
266 
267         m_FillSegmList.clear();
268         m_FilledPolysList.clear();
269         m_RawPolysList.clear();
270         m_filledPolysHash.clear();
271         m_insulatedIslands.clear();
272 
273         for( PCB_LAYER_ID layer : aLayerSet.Seq() )
274         {
275             m_FillSegmList[layer]     = {};
276             m_FilledPolysList[layer]  = {};
277             m_RawPolysList[layer]     = {};
278             m_filledPolysHash[layer]  = {};
279             m_insulatedIslands[layer] = {};
280         }
281     }
282 
283     m_layerSet = aLayerSet;
284 
285     // Set the single layer parameter.  For zones that can be on many layers, this parameter
286     // is arbitrary at best, but some code still uses it.
287     // Priority is F_Cu then B_Cu then to the first selected layer
288     m_layer = aLayerSet.Seq()[0];
289 
290     if( m_layer != F_Cu && aLayerSet[B_Cu] )
291         m_layer = B_Cu;
292 }
293 
294 
GetLayerSet() const295 LSET ZONE::GetLayerSet() const
296 {
297     return m_layerSet;
298 }
299 
300 
ViewGetLayers(int aLayers[],int & aCount) const301 void ZONE::ViewGetLayers( int aLayers[], int& aCount ) const
302 {
303     LSEQ layers = m_layerSet.Seq();
304 
305     for( unsigned int idx = 0; idx < layers.size(); idx++ )
306         aLayers[idx] = LAYER_ZONE_START + layers[idx];
307 
308     aCount = layers.size();
309 }
310 
311 
ViewGetLOD(int aLayer,KIGFX::VIEW * aView) const312 double ZONE::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
313 {
314     constexpr double HIDE = std::numeric_limits<double>::max();
315 
316     return aView->IsLayerVisible( LAYER_ZONES ) ? 0.0 : HIDE;
317 }
318 
319 
IsOnLayer(PCB_LAYER_ID aLayer) const320 bool ZONE::IsOnLayer( PCB_LAYER_ID aLayer ) const
321 {
322     return m_layerSet.test( aLayer );
323 }
324 
325 
GetBoundingBox() const326 const EDA_RECT ZONE::GetBoundingBox() const
327 {
328     auto bb = m_Poly->BBox();
329 
330     EDA_RECT ret( (wxPoint) bb.GetOrigin(), wxSize( bb.GetWidth(), bb.GetHeight() ) );
331 
332     return ret;
333 }
334 
335 
GetThermalReliefGap(PAD * aPad,wxString * aSource) const336 int ZONE::GetThermalReliefGap( PAD* aPad, wxString* aSource ) const
337 {
338     if( aPad->GetEffectiveThermalGap() == 0 )
339     {
340         if( aSource )
341             *aSource = _( "zone" );
342 
343         return m_thermalReliefGap;
344     }
345 
346     return aPad->GetEffectiveThermalGap( aSource );
347 
348 }
349 
350 
GetThermalReliefSpokeWidth(PAD * aPad,wxString * aSource) const351 int ZONE::GetThermalReliefSpokeWidth( PAD* aPad, wxString* aSource ) const
352 {
353     if( aPad->GetEffectiveThermalSpokeWidth() == 0 )
354     {
355         if( aSource )
356             *aSource = _( "zone" );
357 
358         return m_thermalReliefSpokeWidth;
359     }
360 
361     return aPad->GetEffectiveThermalSpokeWidth( aSource );
362 }
363 
364 
SetCornerRadius(unsigned int aRadius)365 void ZONE::SetCornerRadius( unsigned int aRadius )
366 {
367     if( m_cornerRadius != aRadius )
368         SetNeedRefill( true );
369 
370     m_cornerRadius = aRadius;
371 }
372 
373 
GetFilledPolysUseThickness(PCB_LAYER_ID aLayer) const374 bool ZONE::GetFilledPolysUseThickness( PCB_LAYER_ID aLayer ) const
375 {
376     if( ADVANCED_CFG::GetCfg().m_DebugZoneFiller && LSET::InternalCuMask().Contains( aLayer ) )
377         return false;
378 
379     return GetFilledPolysUseThickness();
380 }
381 
382 
383 static SHAPE_POLY_SET g_nullPoly;
384 
385 
GetHashValue(PCB_LAYER_ID aLayer)386 MD5_HASH ZONE::GetHashValue( PCB_LAYER_ID aLayer )
387 {
388     if( !m_filledPolysHash.count( aLayer ) )
389         return g_nullPoly.GetHash();
390     else
391         return m_filledPolysHash.at( aLayer );
392 }
393 
394 
BuildHashValue(PCB_LAYER_ID aLayer)395 void ZONE::BuildHashValue( PCB_LAYER_ID aLayer )
396 {
397     if( !m_FilledPolysList.count( aLayer ) )
398         m_filledPolysHash[aLayer] = g_nullPoly.GetHash();
399     else
400         m_filledPolysHash[aLayer] = m_FilledPolysList.at( aLayer ).GetHash();
401 }
402 
403 
HitTest(const wxPoint & aPosition,int aAccuracy) const404 bool ZONE::HitTest( const wxPoint& aPosition, int aAccuracy ) const
405 {
406     // When looking for an "exact" hit aAccuracy will be 0 which works poorly for very thin
407     // lines.  Give it a floor.
408     int accuracy = std::max( aAccuracy, Millimeter2iu( 0.1 ) );
409 
410     return HitTestForCorner( aPosition, accuracy * 2 ) || HitTestForEdge( aPosition, accuracy );
411 }
412 
413 
SetSelectedCorner(const wxPoint & aPosition,int aAccuracy)414 void ZONE::SetSelectedCorner( const wxPoint& aPosition, int aAccuracy )
415 {
416     SHAPE_POLY_SET::VERTEX_INDEX corner;
417 
418     // If there is some corner to be selected, assign it to m_CornerSelection
419     if( HitTestForCorner( aPosition, aAccuracy * 2, corner )
420         || HitTestForEdge( aPosition, aAccuracy, corner ) )
421     {
422         if( m_CornerSelection == nullptr )
423             m_CornerSelection = new SHAPE_POLY_SET::VERTEX_INDEX;
424 
425         *m_CornerSelection = corner;
426     }
427 }
428 
HitTestForCorner(const wxPoint & refPos,int aAccuracy,SHAPE_POLY_SET::VERTEX_INDEX & aCornerHit) const429 bool ZONE::HitTestForCorner( const wxPoint& refPos, int aAccuracy,
430                              SHAPE_POLY_SET::VERTEX_INDEX& aCornerHit ) const
431 {
432     return m_Poly->CollideVertex( VECTOR2I( refPos ), aCornerHit, aAccuracy );
433 }
434 
435 
HitTestForCorner(const wxPoint & refPos,int aAccuracy) const436 bool ZONE::HitTestForCorner( const wxPoint& refPos, int aAccuracy ) const
437 {
438     SHAPE_POLY_SET::VERTEX_INDEX dummy;
439     return HitTestForCorner( refPos, aAccuracy, dummy );
440 }
441 
442 
HitTestForEdge(const wxPoint & refPos,int aAccuracy,SHAPE_POLY_SET::VERTEX_INDEX & aCornerHit) const443 bool ZONE::HitTestForEdge( const wxPoint& refPos, int aAccuracy,
444                            SHAPE_POLY_SET::VERTEX_INDEX& aCornerHit ) const
445 {
446     return m_Poly->CollideEdge( VECTOR2I( refPos ), aCornerHit, aAccuracy );
447 }
448 
449 
HitTestForEdge(const wxPoint & refPos,int aAccuracy) const450 bool ZONE::HitTestForEdge( const wxPoint& refPos, int aAccuracy ) const
451 {
452     SHAPE_POLY_SET::VERTEX_INDEX dummy;
453     return HitTestForEdge( refPos, aAccuracy, dummy );
454 }
455 
456 
HitTest(const EDA_RECT & aRect,bool aContained,int aAccuracy) const457 bool ZONE::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const
458 {
459     // Calculate bounding box for zone
460     EDA_RECT bbox = GetBoundingBox();
461     bbox.Normalize();
462 
463     EDA_RECT arect = aRect;
464     arect.Normalize();
465     arect.Inflate( aAccuracy );
466 
467     if( aContained )
468     {
469          return arect.Contains( bbox );
470     }
471     else
472     {
473         // Fast test: if aBox is outside the polygon bounding box, rectangles cannot intersect
474         if( !arect.Intersects( bbox ) )
475             return false;
476 
477         int count = m_Poly->TotalVertices();
478 
479         for( int ii = 0; ii < count; ii++ )
480         {
481             auto vertex = m_Poly->CVertex( ii );
482             auto vertexNext = m_Poly->CVertex( ( ii + 1 ) % count );
483 
484             // Test if the point is within the rect
485             if( arect.Contains( ( wxPoint ) vertex ) )
486                 return true;
487 
488             // Test if this edge intersects the rect
489             if( arect.Intersects( ( wxPoint ) vertex, ( wxPoint ) vertexNext ) )
490                 return true;
491         }
492 
493         return false;
494     }
495 }
496 
497 
GetLocalClearance(wxString * aSource) const498 int ZONE::GetLocalClearance( wxString* aSource ) const
499 {
500     if( m_isRuleArea )
501         return 0;
502 
503     if( aSource )
504         *aSource = _( "zone" );
505 
506     return m_ZoneClearance;
507 }
508 
509 
HitTestFilledArea(PCB_LAYER_ID aLayer,const wxPoint & aRefPos,int aAccuracy) const510 bool ZONE::HitTestFilledArea( PCB_LAYER_ID aLayer, const wxPoint &aRefPos, int aAccuracy ) const
511 {
512     // Rule areas have no filled area, but it's generally nice to treat their interior as if it were
513     // filled so that people don't have to select them by their outline (which is min-width)
514     if( GetIsRuleArea() )
515         return m_Poly->Contains( VECTOR2I( aRefPos.x, aRefPos.y ), -1, aAccuracy );
516 
517     if( !m_FilledPolysList.count( aLayer ) )
518         return false;
519 
520     return m_FilledPolysList.at( aLayer ).Contains( VECTOR2I( aRefPos.x, aRefPos.y ), -1,
521                                                     aAccuracy );
522 }
523 
524 
HitTestCutout(const VECTOR2I & aRefPos,int * aOutlineIdx,int * aHoleIdx) const525 bool ZONE::HitTestCutout( const VECTOR2I& aRefPos, int* aOutlineIdx, int* aHoleIdx ) const
526 {
527     // Iterate over each outline polygon in the zone and then iterate over
528     // each hole it has to see if the point is in it.
529     for( int i = 0; i < m_Poly->OutlineCount(); i++ )
530     {
531         for( int j = 0; j < m_Poly->HoleCount( i ); j++ )
532         {
533             if( m_Poly->Hole( i, j ).PointInside( aRefPos ) )
534             {
535                 if( aOutlineIdx )
536                     *aOutlineIdx = i;
537 
538                 if( aHoleIdx )
539                     *aHoleIdx = j;
540 
541                 return true;
542             }
543         }
544     }
545 
546     return false;
547 }
548 
549 
GetMsgPanelInfo(EDA_DRAW_FRAME * aFrame,std::vector<MSG_PANEL_ITEM> & aList)550 void ZONE::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
551 {
552     EDA_UNITS units = aFrame->GetUserUnits();
553     wxString  msg;
554 
555     if( GetIsRuleArea() )
556         msg = _( "Rule Area" );
557     else if( IsOnCopperLayer() )
558         msg = _( "Copper Zone" );
559     else
560         msg = _( "Non-copper Zone" );
561 
562     // Display Cutout instead of Outline for holes inside a zone (i.e. when num contour !=0).
563     // Check whether the selected corner is in a hole; i.e., in any contour but the first one.
564     if( m_CornerSelection != nullptr && m_CornerSelection->m_contour > 0 )
565         msg << wxT( " " ) << _( "Cutout" );
566 
567     aList.emplace_back( _( "Type" ), msg );
568 
569     if( GetIsRuleArea() )
570     {
571         msg.Empty();
572 
573         if( GetDoNotAllowVias() )
574             AccumulateDescription( msg, _( "No vias" ) );
575 
576         if( GetDoNotAllowTracks() )
577             AccumulateDescription( msg, _( "No tracks" ) );
578 
579         if( GetDoNotAllowPads() )
580             AccumulateDescription( msg, _( "No pads" ) );
581 
582         if( GetDoNotAllowCopperPour() )
583             AccumulateDescription( msg, _( "No copper zones" ) );
584 
585         if( GetDoNotAllowFootprints() )
586             AccumulateDescription( msg, _( "No footprints" ) );
587 
588         if( !msg.IsEmpty() )
589             aList.emplace_back( MSG_PANEL_ITEM( _( "Restrictions" ), msg ) );
590     }
591     else if( IsOnCopperLayer() )
592     {
593         if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
594         {
595             aList.emplace_back( _( "Net" ), UnescapeString( GetNetname() ) );
596 
597             aList.emplace_back( _( "Net Class" ), UnescapeString( GetNetClass()->GetName() ) );
598         }
599 
600         // Display priority level
601         aList.emplace_back( _( "Priority" ), wxString::Format( "%d", GetPriority() ) );
602     }
603 
604     if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
605     {
606         if( IsLocked() )
607             aList.emplace_back( _( "Status" ), _( "Locked" ) );
608     }
609 
610     wxString layerDesc;
611     int count = 0;
612 
613     for( PCB_LAYER_ID layer : m_layerSet.Seq() )
614     {
615         if( count == 0 )
616             layerDesc = GetBoard()->GetLayerName( layer );
617 
618         count++;
619     }
620 
621     if( count > 1 )
622         layerDesc.Printf( _( "%s and %d more" ), layerDesc, count - 1 );
623 
624     aList.emplace_back( _( "Layer" ), layerDesc );
625 
626     if( !m_zoneName.empty() )
627         aList.emplace_back( _( "Name" ), m_zoneName );
628 
629     switch( m_fillMode )
630     {
631     case ZONE_FILL_MODE::POLYGONS:      msg = _( "Solid" ); break;
632     case ZONE_FILL_MODE::HATCH_PATTERN: msg = _( "Hatched" ); break;
633     default:                            msg = _( "Unknown" ); break;
634     }
635 
636     aList.emplace_back( _( "Fill Mode" ), msg );
637 
638     msg = MessageTextFromValue( units, m_area, true, EDA_DATA_TYPE::AREA );
639     aList.emplace_back( _( "Filled Area" ), msg );
640 
641     wxString source;
642     int      clearance = GetOwnClearance( GetLayer(), &source );
643 
644     if( !source.IsEmpty() )
645     {
646         aList.emplace_back( wxString::Format( _( "Min Clearance: %s" ),
647                                               MessageTextFromValue( units, clearance ) ),
648                             wxString::Format( _( "(from %s)" ),
649                                               source ) );
650     }
651 
652     // Useful for statistics, especially when zones are complex the number of hatches
653     // and filled polygons can explain the display and DRC calculation time:
654     msg.Printf( wxT( "%d" ), (int) m_borderHatchLines.size() );
655     aList.emplace_back( MSG_PANEL_ITEM( _( "HatchBorder Lines" ), msg ) );
656 
657     PCB_LAYER_ID layer = m_layer;
658 
659     // NOTE: This brings in dependence on PCB_EDIT_FRAME to the qa tests, which isn't ideal.
660     // TODO: Figure out a way for items to know the active layer without the whole edit frame?
661 #if 0
662     if( PCB_EDIT_FRAME* pcbframe = dynamic_cast<PCB_EDIT_FRAME*>( aFrame ) )
663     {
664         if( m_FilledPolysList.count( pcbframe->GetActiveLayer() ) )
665             layer = pcbframe->GetActiveLayer();
666     }
667 #endif
668 
669     if( !GetIsRuleArea() )
670     {
671         auto layer_it = m_FilledPolysList.find( layer );
672 
673         if( layer_it == m_FilledPolysList.end() )
674             layer_it = m_FilledPolysList.begin();
675 
676         if( layer_it != m_FilledPolysList.end() )
677         {
678             msg.Printf( wxT( "%d" ), layer_it->second.TotalVertices() );
679             aList.emplace_back( MSG_PANEL_ITEM( _( "Corner Count" ), msg ) );
680         }
681     }
682 }
683 
684 
Move(const wxPoint & offset)685 void ZONE::Move( const wxPoint& offset )
686 {
687     /* move outlines */
688     m_Poly->Move( offset );
689 
690     HatchBorder();
691 
692     for( std::pair<const PCB_LAYER_ID, SHAPE_POLY_SET>& pair : m_FilledPolysList )
693         pair.second.Move( offset );
694 
695     for( std::pair<const PCB_LAYER_ID, std::vector<SEG> >& pair : m_FillSegmList )
696     {
697         for( SEG& seg : pair.second )
698         {
699             seg.A += VECTOR2I( offset );
700             seg.B += VECTOR2I( offset );
701         }
702     }
703 }
704 
705 
MoveEdge(const wxPoint & offset,int aEdge)706 void ZONE::MoveEdge( const wxPoint& offset, int aEdge )
707 {
708     int next_corner;
709 
710     if( m_Poly->GetNeighbourIndexes( aEdge, nullptr, &next_corner ) )
711     {
712         m_Poly->SetVertex( aEdge, m_Poly->CVertex( aEdge ) + VECTOR2I( offset ) );
713         m_Poly->SetVertex( next_corner, m_Poly->CVertex( next_corner ) + VECTOR2I( offset ) );
714         HatchBorder();
715 
716         SetNeedRefill( true );
717     }
718 }
719 
720 
Rotate(const wxPoint & aCentre,double aAngle)721 void ZONE::Rotate( const wxPoint& aCentre, double aAngle )
722 {
723     aAngle = -DECIDEG2RAD( aAngle );
724 
725     m_Poly->Rotate( aAngle, VECTOR2I( aCentre ) );
726     HatchBorder();
727 
728     /* rotate filled areas: */
729     for( std::pair<const PCB_LAYER_ID, SHAPE_POLY_SET>& pair : m_FilledPolysList )
730         pair.second.Rotate( aAngle, VECTOR2I( aCentre ) );
731 
732     for( std::pair<const PCB_LAYER_ID, std::vector<SEG> >& pair : m_FillSegmList )
733     {
734         for( SEG& seg : pair.second )
735         {
736             wxPoint a( seg.A );
737             RotatePoint( &a, aCentre, aAngle );
738             seg.A = a;
739             wxPoint b( seg.B );
740             RotatePoint( &b, aCentre, aAngle );
741             seg.B = a;
742         }
743     }
744 }
745 
746 
Flip(const wxPoint & aCentre,bool aFlipLeftRight)747 void ZONE::Flip( const wxPoint& aCentre, bool aFlipLeftRight )
748 {
749     Mirror( aCentre, aFlipLeftRight );
750     int copperLayerCount = GetBoard()->GetCopperLayerCount();
751 
752     if( GetIsRuleArea() )
753         SetLayerSet( FlipLayerMask( GetLayerSet(), copperLayerCount ) );
754     else
755         SetLayer( FlipLayer( GetLayer(), copperLayerCount ) );
756 }
757 
758 
Mirror(const wxPoint & aMirrorRef,bool aMirrorLeftRight)759 void ZONE::Mirror( const wxPoint& aMirrorRef, bool aMirrorLeftRight )
760 {
761     // ZONEs mirror about the x-axis (why?!?)
762     m_Poly->Mirror( aMirrorLeftRight, !aMirrorLeftRight, VECTOR2I( aMirrorRef ) );
763 
764     HatchBorder();
765 
766     for( std::pair<const PCB_LAYER_ID, SHAPE_POLY_SET>& pair : m_FilledPolysList )
767         pair.second.Mirror( aMirrorLeftRight, !aMirrorLeftRight, VECTOR2I( aMirrorRef ) );
768 
769     for( std::pair<const PCB_LAYER_ID, std::vector<SEG> >& pair : m_FillSegmList )
770     {
771         for( SEG& seg : pair.second )
772         {
773             if( aMirrorLeftRight )
774             {
775                 MIRROR( seg.A.x, aMirrorRef.x );
776                 MIRROR( seg.B.x, aMirrorRef.x );
777             }
778             else
779             {
780                 MIRROR( seg.A.y, aMirrorRef.y );
781                 MIRROR( seg.B.y, aMirrorRef.y );
782             }
783         }
784     }
785 }
786 
787 
GetPadConnection(PAD * aPad,wxString * aSource) const788 ZONE_CONNECTION ZONE::GetPadConnection( PAD* aPad, wxString* aSource ) const
789 {
790     if( aPad == nullptr || aPad->GetEffectiveZoneConnection() == ZONE_CONNECTION::INHERITED )
791     {
792         if( aSource )
793             *aSource = _( "zone" );
794 
795         return m_PadConnection;
796     }
797     else
798     {
799         return aPad->GetEffectiveZoneConnection( aSource );
800     }
801 }
802 
803 
RemoveCutout(int aOutlineIdx,int aHoleIdx)804 void ZONE::RemoveCutout( int aOutlineIdx, int aHoleIdx )
805 {
806     // Ensure the requested cutout is valid
807     if( m_Poly->OutlineCount() < aOutlineIdx || m_Poly->HoleCount( aOutlineIdx ) < aHoleIdx )
808         return;
809 
810     SHAPE_POLY_SET cutPoly( m_Poly->Hole( aOutlineIdx, aHoleIdx ) );
811 
812     // Add the cutout back to the zone
813     m_Poly->BooleanAdd( cutPoly, SHAPE_POLY_SET::PM_FAST );
814 
815     SetNeedRefill( true );
816 }
817 
818 
AddPolygon(const SHAPE_LINE_CHAIN & aPolygon)819 void ZONE::AddPolygon( const SHAPE_LINE_CHAIN& aPolygon )
820 {
821     wxASSERT( aPolygon.IsClosed() );
822 
823     // Add the outline as a new polygon in the polygon set
824     if( m_Poly->OutlineCount() == 0 )
825         m_Poly->AddOutline( aPolygon );
826     else
827         m_Poly->AddHole( aPolygon );
828 
829     SetNeedRefill( true );
830 }
831 
832 
AddPolygon(std::vector<wxPoint> & aPolygon)833 void ZONE::AddPolygon( std::vector< wxPoint >& aPolygon )
834 {
835     if( aPolygon.empty() )
836         return;
837 
838     SHAPE_LINE_CHAIN outline;
839 
840     // Create an outline and populate it with the points of aPolygon
841     for( const wxPoint& pt : aPolygon)
842         outline.Append( pt );
843 
844     outline.SetClosed( true );
845 
846     AddPolygon( outline );
847 }
848 
849 
AppendCorner(wxPoint aPosition,int aHoleIdx,bool aAllowDuplication)850 bool ZONE::AppendCorner( wxPoint aPosition, int aHoleIdx, bool aAllowDuplication )
851 {
852     // Ensure the main outline exists:
853     if( m_Poly->OutlineCount() == 0 )
854         m_Poly->NewOutline();
855 
856     // If aHoleIdx >= 0, the corner musty be added to the hole, index aHoleIdx.
857     // (remember: the index of the first hole is 0)
858     // Return error if if does dot exist.
859     if( aHoleIdx >= m_Poly->HoleCount( 0 ) )
860         return false;
861 
862     m_Poly->Append( aPosition.x, aPosition.y, -1, aHoleIdx, aAllowDuplication );
863 
864     SetNeedRefill( true );
865 
866     return true;
867 }
868 
869 
GetSelectMenuText(EDA_UNITS aUnits) const870 wxString ZONE::GetSelectMenuText( EDA_UNITS aUnits ) const
871 {
872     wxString layerDesc;
873     int      count = 0;
874 
875     for( PCB_LAYER_ID layer : m_layerSet.Seq() )
876     {
877         if( count == 0 )
878             layerDesc = GetBoard()->GetLayerName( layer );
879 
880         count++;
881     }
882 
883     if( count > 1 )
884         layerDesc.Printf( _( "%s and %d more" ), layerDesc, count - 1 );
885 
886     // Check whether the selected contour is a hole (contour index > 0)
887     if( m_CornerSelection != nullptr &&  m_CornerSelection->m_contour > 0 )
888     {
889         if( GetIsRuleArea() )
890             return wxString::Format( _( "Rule Area Cutout on %s" ), layerDesc  );
891         else
892             return wxString::Format( _( "Zone Cutout on %s" ), layerDesc  );
893     }
894     else
895     {
896         if( GetIsRuleArea() )
897             return wxString::Format( _( "Rule Area on %s" ), layerDesc );
898         else
899             return wxString::Format( _( "Zone %s on %s" ), GetNetnameMsg(), layerDesc );
900     }
901 }
902 
903 
GetBorderHatchPitch() const904 int ZONE::GetBorderHatchPitch() const
905 {
906     return m_borderHatchPitch;
907 }
908 
909 
SetBorderDisplayStyle(ZONE_BORDER_DISPLAY_STYLE aHatchStyle,int aHatchPitch,bool aRebuildHatch)910 void ZONE::SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE aHatchStyle, int aHatchPitch,
911                                   bool aRebuildHatch )
912 {
913     SetHatchPitch( aHatchPitch );
914     m_borderStyle = aHatchStyle;
915 
916     if( aRebuildHatch )
917         HatchBorder();
918 }
919 
920 
SetHatchPitch(int aPitch)921 void ZONE::SetHatchPitch( int aPitch )
922 {
923     m_borderHatchPitch = aPitch;
924 }
925 
926 
UnHatchBorder()927 void ZONE::UnHatchBorder()
928 {
929     m_borderHatchLines.clear();
930 }
931 
932 
933 // Creates hatch lines inside the outline of the complex polygon
934 // sort function used in ::HatchBorder to sort points by descending wxPoint.x values
sortEndsByDescendingX(const VECTOR2I & ref,const VECTOR2I & tst)935 bool sortEndsByDescendingX( const VECTOR2I& ref, const VECTOR2I& tst )
936 {
937     return tst.x < ref.x;
938 }
939 
940 
HatchBorder()941 void ZONE::HatchBorder()
942 {
943     UnHatchBorder();
944 
945     if( m_borderStyle == ZONE_BORDER_DISPLAY_STYLE::NO_HATCH
946             || m_borderHatchPitch == 0
947             || m_Poly->IsEmpty() )
948     {
949         return;
950     }
951 
952     // define range for hatch lines
953     int min_x = m_Poly->CVertex( 0 ).x;
954     int max_x = m_Poly->CVertex( 0 ).x;
955     int min_y = m_Poly->CVertex( 0 ).y;
956     int max_y = m_Poly->CVertex( 0 ).y;
957 
958     for( auto iterator = m_Poly->IterateWithHoles(); iterator; iterator++ )
959     {
960         if( iterator->x < min_x )
961             min_x = iterator->x;
962 
963         if( iterator->x > max_x )
964             max_x = iterator->x;
965 
966         if( iterator->y < min_y )
967             min_y = iterator->y;
968 
969         if( iterator->y > max_y )
970             max_y = iterator->y;
971     }
972 
973     // Calculate spacing between 2 hatch lines
974     int spacing;
975 
976     if( m_borderStyle == ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE )
977         spacing = m_borderHatchPitch;
978     else
979         spacing = m_borderHatchPitch * 2;
980 
981     // set the "length" of hatch lines (the length on horizontal axis)
982     int  hatch_line_len = m_borderHatchPitch;
983 
984     // To have a better look, give a slope depending on the layer
985     LAYER_NUM layer = GetLayer();
986     int     slope_flag = (layer & 1) ? 1 : -1;  // 1 or -1
987     double  slope = 0.707106 * slope_flag;      // 45 degrees slope
988     int     max_a, min_a;
989 
990     if( slope_flag == 1 )
991     {
992         max_a   = KiROUND( max_y - slope * min_x );
993         min_a   = KiROUND( min_y - slope * max_x );
994     }
995     else
996     {
997         max_a   = KiROUND( max_y - slope * max_x );
998         min_a   = KiROUND( min_y - slope * min_x );
999     }
1000 
1001     min_a = (min_a / spacing) * spacing;
1002 
1003     // calculate an offset depending on layer number,
1004     // for a better look of hatches on a multilayer board
1005     int offset = (layer * 7) / 8;
1006     min_a += offset;
1007 
1008     // loop through hatch lines
1009     #define MAXPTS 200      // Usually we store only few values per one hatch line
1010                             // depending on the complexity of the zone outline
1011 
1012     static std::vector<VECTOR2I> pointbuffer;
1013     pointbuffer.clear();
1014     pointbuffer.reserve( MAXPTS + 2 );
1015 
1016     for( int a = min_a; a < max_a; a += spacing )
1017     {
1018         // get intersection points for this hatch line
1019 
1020         // Note: because we should have an even number of intersections with the
1021         // current hatch line and the zone outline (a closed polygon,
1022         // or a set of closed polygons), if an odd count is found
1023         // we skip this line (should not occur)
1024         pointbuffer.clear();
1025 
1026         // Iterate through all vertices
1027         for( auto iterator = m_Poly->IterateSegmentsWithHoles(); iterator; iterator++ )
1028         {
1029             double x, y;
1030             bool   ok;
1031 
1032             SEG segment = *iterator;
1033 
1034             ok = FindLineSegmentIntersection( a, slope, segment.A.x, segment.A.y, segment.B.x,
1035                                               segment.B.y, x, y );
1036 
1037             if( ok )
1038             {
1039                 VECTOR2I point( KiROUND( x ), KiROUND( y ) );
1040                 pointbuffer.push_back( point );
1041             }
1042 
1043             if( pointbuffer.size() >= MAXPTS ) // overflow
1044             {
1045                 wxASSERT( 0 );
1046                 break;
1047             }
1048         }
1049 
1050         // ensure we have found an even intersection points count
1051         // because intersections are the ends of segments
1052         // inside the polygon(s) and a segment has 2 ends.
1053         // if not, this is a strange case (a bug ?) so skip this hatch
1054         if( pointbuffer.size() % 2 != 0 )
1055             continue;
1056 
1057         // sort points in order of descending x (if more than 2) to
1058         // ensure the starting point and the ending point of the same segment
1059         // are stored one just after the other.
1060         if( pointbuffer.size() > 2 )
1061             sort( pointbuffer.begin(), pointbuffer.end(), sortEndsByDescendingX );
1062 
1063         // creates lines or short segments inside the complex polygon
1064         for( unsigned ip = 0; ip < pointbuffer.size(); ip += 2 )
1065         {
1066             int dx = pointbuffer[ip + 1].x - pointbuffer[ip].x;
1067 
1068             // Push only one line for diagonal hatch,
1069             // or for small lines < twice the line length
1070             // else push 2 small lines
1071             if( m_borderStyle == ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_FULL
1072                 || std::abs( dx ) < 2 * hatch_line_len )
1073             {
1074                 m_borderHatchLines.emplace_back( SEG( pointbuffer[ip], pointbuffer[ ip + 1] ) );
1075             }
1076             else
1077             {
1078                 double dy = pointbuffer[ip + 1].y - pointbuffer[ip].y;
1079                 slope = dy / dx;
1080 
1081                 if( dx > 0 )
1082                     dx = hatch_line_len;
1083                 else
1084                     dx = -hatch_line_len;
1085 
1086                 int x1 = KiROUND( pointbuffer[ip].x + dx );
1087                 int x2 = KiROUND( pointbuffer[ip + 1].x - dx );
1088                 int y1 = KiROUND( pointbuffer[ip].y + dx * slope );
1089                 int y2 = KiROUND( pointbuffer[ip + 1].y - dx * slope );
1090 
1091                 m_borderHatchLines.emplace_back( SEG( pointbuffer[ip].x, pointbuffer[ip].y,
1092                                                       x1, y1 ) );
1093 
1094                 m_borderHatchLines.emplace_back( SEG( pointbuffer[ip+1].x, pointbuffer[ip+1].y,
1095                                                       x2, y2 ) );
1096             }
1097         }
1098     }
1099 }
1100 
1101 
GetDefaultHatchPitch()1102 int ZONE::GetDefaultHatchPitch()
1103 {
1104     return Mils2iu( 20 );
1105 }
1106 
1107 
GetMenuImage() const1108 BITMAPS ZONE::GetMenuImage() const
1109 {
1110     return BITMAPS::add_zone;
1111 }
1112 
1113 
SwapData(BOARD_ITEM * aImage)1114 void ZONE::SwapData( BOARD_ITEM* aImage )
1115 {
1116     assert( aImage->Type() == PCB_ZONE_T || aImage->Type() == PCB_FP_ZONE_T );
1117 
1118     std::swap( *((ZONE*) this), *((ZONE*) aImage) );
1119 }
1120 
1121 
CacheTriangulation(PCB_LAYER_ID aLayer)1122 void ZONE::CacheTriangulation( PCB_LAYER_ID aLayer )
1123 {
1124     if( aLayer == UNDEFINED_LAYER )
1125     {
1126         for( std::pair<const PCB_LAYER_ID, SHAPE_POLY_SET>& pair : m_FilledPolysList )
1127             pair.second.CacheTriangulation();
1128     }
1129     else
1130     {
1131         if( m_FilledPolysList.count( aLayer ) )
1132             m_FilledPolysList[ aLayer ].CacheTriangulation();
1133     }
1134 }
1135 
1136 
IsIsland(PCB_LAYER_ID aLayer,int aPolyIdx) const1137 bool ZONE::IsIsland( PCB_LAYER_ID aLayer, int aPolyIdx ) const
1138 {
1139     if( GetNetCode() < 1 )
1140         return true;
1141 
1142     if( !m_insulatedIslands.count( aLayer ) )
1143         return false;
1144 
1145     return m_insulatedIslands.at( aLayer ).count( aPolyIdx );
1146 }
1147 
1148 
GetInteractingZones(PCB_LAYER_ID aLayer,std::vector<ZONE * > * aZones) const1149 void ZONE::GetInteractingZones( PCB_LAYER_ID aLayer, std::vector<ZONE*>* aZones ) const
1150 {
1151     int epsilon = Millimeter2iu( 0.001 );
1152 
1153     for( ZONE* candidate : GetBoard()->Zones() )
1154     {
1155         if( candidate == this )
1156             continue;
1157 
1158         if( !candidate->GetLayerSet().test( aLayer ) )
1159             continue;
1160 
1161         if( candidate->GetIsRuleArea() )
1162             continue;
1163 
1164         if( candidate->GetNetCode() != GetNetCode() )
1165             continue;
1166 
1167         for( auto iter = m_Poly->CIterate(); iter; iter++ )
1168         {
1169             if( candidate->m_Poly->Collide( iter.Get(), epsilon ) )
1170             {
1171                 aZones->push_back( candidate );
1172                 break;
1173             }
1174         }
1175     }
1176 }
1177 
1178 
BuildSmoothedPoly(SHAPE_POLY_SET & aSmoothedPoly,PCB_LAYER_ID aLayer,SHAPE_POLY_SET * aBoardOutline,SHAPE_POLY_SET * aSmoothedPolyWithApron) const1179 bool ZONE::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly, PCB_LAYER_ID aLayer,
1180                               SHAPE_POLY_SET* aBoardOutline,
1181                               SHAPE_POLY_SET* aSmoothedPolyWithApron ) const
1182 {
1183     if( GetNumCorners() <= 2 )  // malformed zone. polygon calculations will not like it ...
1184         return false;
1185 
1186     // Processing of arc shapes in zones is not yet supported because Clipper can't do boolean
1187     // operations on them.  The poly outline must be flattened first.
1188     SHAPE_POLY_SET flattened = *m_Poly;
1189     flattened.ClearArcs();
1190 
1191     if( GetIsRuleArea() )
1192     {
1193         // We like keepouts just the way they are....
1194         aSmoothedPoly = flattened;
1195         return true;
1196     }
1197 
1198     const BOARD* board = GetBoard();
1199     int          maxError = ARC_HIGH_DEF;
1200     bool         keepExternalFillets = false;
1201 
1202     if( board )
1203     {
1204         BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
1205 
1206         maxError = bds.m_MaxError;
1207         keepExternalFillets = bds.m_ZoneKeepExternalFillets;
1208     }
1209 
1210     auto smooth = [&]( SHAPE_POLY_SET& aPoly )
1211                   {
1212                       switch( m_cornerSmoothingType )
1213                       {
1214                       case ZONE_SETTINGS::SMOOTHING_CHAMFER:
1215                           aPoly = aPoly.Chamfer( (int) m_cornerRadius );
1216                           break;
1217 
1218                       case ZONE_SETTINGS::SMOOTHING_FILLET:
1219                       {
1220                           aPoly = aPoly.Fillet( (int) m_cornerRadius, maxError );
1221                           break;
1222                       }
1223 
1224                       default:
1225                           break;
1226                       }
1227                   };
1228 
1229     std::vector<ZONE*> interactingZones;
1230     GetInteractingZones( aLayer, &interactingZones );
1231 
1232     SHAPE_POLY_SET* maxExtents = &flattened;
1233     SHAPE_POLY_SET  withFillets;
1234 
1235     aSmoothedPoly = flattened;
1236 
1237     // Should external fillets (that is, those applied to concave corners) be kept?  While it
1238     // seems safer to never have copper extend outside the zone outline, 5.1.x and prior did
1239     // indeed fill them so we leave the mode available.
1240     if( keepExternalFillets )
1241     {
1242         withFillets = flattened;
1243         smooth( withFillets );
1244         withFillets.BooleanAdd( flattened, SHAPE_POLY_SET::PM_FAST );
1245         maxExtents = &withFillets;
1246     }
1247 
1248     for( ZONE* zone : interactingZones )
1249     {
1250         SHAPE_POLY_SET flattened_outline = *zone->Outline();
1251         flattened_outline.ClearArcs();
1252         aSmoothedPoly.BooleanAdd( flattened_outline, SHAPE_POLY_SET::PM_FAST );
1253     }
1254 
1255     if( aBoardOutline )
1256     {
1257         SHAPE_POLY_SET poly = *aBoardOutline;
1258         aSmoothedPoly.BooleanIntersection( poly, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
1259     }
1260 
1261     smooth( aSmoothedPoly );
1262 
1263     if( aSmoothedPolyWithApron )
1264     {
1265         SHAPE_POLY_SET poly = *maxExtents;
1266         poly.Inflate( m_ZoneMinThickness, 64 );
1267         *aSmoothedPolyWithApron = aSmoothedPoly;
1268         aSmoothedPolyWithApron->BooleanIntersection( poly, SHAPE_POLY_SET::PM_FAST );
1269     }
1270 
1271     aSmoothedPoly.BooleanIntersection( *maxExtents, SHAPE_POLY_SET::PM_FAST );
1272 
1273     return true;
1274 }
1275 
1276 
CalculateFilledArea()1277 double ZONE::CalculateFilledArea()
1278 {
1279     m_area = 0.0;
1280 
1281     // Iterate over each outline polygon in the zone and then iterate over
1282     // each hole it has to compute the total area.
1283     for( std::pair<const PCB_LAYER_ID, SHAPE_POLY_SET>& pair : m_FilledPolysList )
1284     {
1285         SHAPE_POLY_SET& poly = pair.second;
1286 
1287         for( int i = 0; i < poly.OutlineCount(); i++ )
1288         {
1289             m_area += poly.Outline( i ).Area();
1290 
1291             for( int j = 0; j < poly.HoleCount( i ); j++ )
1292                 m_area -= poly.Hole( i, j ).Area();
1293         }
1294     }
1295 
1296     return m_area;
1297 }
1298 
1299 
TransformSmoothedOutlineToPolygon(SHAPE_POLY_SET & aCornerBuffer,int aClearance,SHAPE_POLY_SET * aBoardOutline) const1300 void ZONE::TransformSmoothedOutlineToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aClearance,
1301                                               SHAPE_POLY_SET* aBoardOutline ) const
1302 {
1303     // Creates the zone outline polygon (with holes if any)
1304     SHAPE_POLY_SET polybuffer;
1305     BuildSmoothedPoly( polybuffer, GetLayer(), aBoardOutline );
1306 
1307     // Calculate the polygon with clearance
1308     // holes are linked to the main outline, so only one polygon is created.
1309     if( aClearance )
1310     {
1311         const BOARD* board = GetBoard();
1312         int          maxError = ARC_HIGH_DEF;
1313 
1314         if( board )
1315             maxError = board->GetDesignSettings().m_MaxError;
1316 
1317         int segCount = GetArcToSegmentCount( aClearance, maxError, 360.0 );
1318         polybuffer.Inflate( aClearance, segCount );
1319     }
1320 
1321     polybuffer.Fracture( SHAPE_POLY_SET::PM_FAST );
1322     aCornerBuffer.Append( polybuffer );
1323 }
1324 
1325 
IsKeepout() const1326 bool ZONE::IsKeepout() const
1327 {
1328     return m_doNotAllowCopperPour || m_doNotAllowVias || m_doNotAllowTracks || m_doNotAllowPads ||
1329            m_doNotAllowFootprints;
1330 }
1331 
1332 
KeepoutAll() const1333 bool ZONE::KeepoutAll() const
1334 {
1335     return m_doNotAllowCopperPour && m_doNotAllowVias && m_doNotAllowTracks && m_doNotAllowPads &&
1336            m_doNotAllowFootprints;
1337 }
1338 
1339 
FP_ZONE(BOARD_ITEM_CONTAINER * aParent)1340 FP_ZONE::FP_ZONE( BOARD_ITEM_CONTAINER* aParent ) :
1341         ZONE( aParent, true )
1342 {
1343     // in a footprint, net classes are not managed.
1344     // so set the net to NETINFO_LIST::ORPHANED_ITEM
1345     SetNetCode( -1, true );
1346 }
1347 
1348 
FP_ZONE(const FP_ZONE & aZone)1349 FP_ZONE::FP_ZONE( const FP_ZONE& aZone ) :
1350         ZONE( aZone.GetParent(), true )
1351 {
1352     InitDataFromSrcInCopyCtor( aZone );
1353 }
1354 
1355 
operator =(const FP_ZONE & aOther)1356 FP_ZONE& FP_ZONE::operator=( const FP_ZONE& aOther )
1357 {
1358     ZONE::operator=( aOther );
1359     return *this;
1360 }
1361 
1362 
Clone() const1363 EDA_ITEM* FP_ZONE::Clone() const
1364 {
1365     return new FP_ZONE( *this );
1366 }
1367 
1368 
ViewGetLOD(int aLayer,KIGFX::VIEW * aView) const1369 double FP_ZONE::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
1370 {
1371     constexpr double HIDE = (double)std::numeric_limits<double>::max();
1372 
1373     if( !aView )
1374         return 0;
1375 
1376     if( !aView->IsLayerVisible( LAYER_ZONES ) )
1377         return HIDE;
1378 
1379     bool flipped = GetParent() && GetParent()->GetLayer() == B_Cu;
1380 
1381     // Handle Render tab switches
1382     if( !flipped && !aView->IsLayerVisible( LAYER_MOD_FR ) )
1383         return HIDE;
1384 
1385     if( flipped && !aView->IsLayerVisible( LAYER_MOD_BK ) )
1386         return HIDE;
1387 
1388     // Other layers are shown without any conditions
1389     return 0.0;
1390 }
1391 
1392 
GetEffectiveShape(PCB_LAYER_ID aLayer) const1393 std::shared_ptr<SHAPE> ZONE::GetEffectiveShape( PCB_LAYER_ID aLayer ) const
1394 {
1395     std::shared_ptr<SHAPE> shape;
1396 
1397     if( m_FilledPolysList.find( aLayer ) == m_FilledPolysList.end() )
1398     {
1399         shape = std::make_shared<SHAPE_NULL>();
1400     }
1401     else
1402     {
1403         shape.reset( m_FilledPolysList.at( aLayer ).Clone() );
1404     }
1405 
1406     return shape;
1407 }
1408 
1409 
TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET & aCornerBuffer,PCB_LAYER_ID aLayer,int aClearance,int aError,ERROR_LOC aErrorLoc,bool aIgnoreLineWidth) const1410 void ZONE::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
1411                                                  PCB_LAYER_ID aLayer, int aClearance, int aError,
1412                                                  ERROR_LOC aErrorLoc, bool aIgnoreLineWidth ) const
1413 {
1414     wxASSERT_MSG( !aIgnoreLineWidth, "IgnoreLineWidth has no meaning for zones." );
1415 
1416     if( !m_FilledPolysList.count( aLayer ) )
1417         return;
1418 
1419     aCornerBuffer = m_FilledPolysList.at( aLayer );
1420 
1421     int numSegs = GetArcToSegmentCount( aClearance, aError, 360.0 );
1422     aCornerBuffer.Inflate( aClearance, numSegs );
1423     aCornerBuffer.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
1424 }
1425 
1426 
TransformSolidAreasShapesToPolygon(PCB_LAYER_ID aLayer,SHAPE_POLY_SET & aCornerBuffer,int aError) const1427 void ZONE::TransformSolidAreasShapesToPolygon( PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aCornerBuffer,
1428                                                int aError ) const
1429 {
1430     if( !m_FilledPolysList.count( aLayer ) || m_FilledPolysList.at( aLayer ).IsEmpty() )
1431         return;
1432 
1433     // Just add filled areas if filled polygons outlines have no thickness
1434     if( !GetFilledPolysUseThickness() || GetMinThickness() == 0 )
1435     {
1436         const SHAPE_POLY_SET& polys = m_FilledPolysList.at( aLayer );
1437         aCornerBuffer.Append( polys );
1438         return;
1439     }
1440 
1441     // Filled areas have polygons with outline thickness.
1442     // we must create the polygons and add inflated polys
1443     SHAPE_POLY_SET polys = m_FilledPolysList.at( aLayer );
1444 
1445     auto board = GetBoard();
1446     int maxError = ARC_HIGH_DEF;
1447 
1448     if( board )
1449         maxError = board->GetDesignSettings().m_MaxError;
1450 
1451     int numSegs = GetArcToSegmentCount( GetMinThickness(), maxError, 360.0 );
1452 
1453     polys.InflateWithLinkedHoles( GetMinThickness()/2, numSegs, SHAPE_POLY_SET::PM_FAST );
1454 
1455     aCornerBuffer.Append( polys );
1456 }
1457 
1458 
1459 static struct ZONE_DESC
1460 {
ZONE_DESCZONE_DESC1461     ZONE_DESC()
1462     {
1463         ENUM_MAP<ZONE_CONNECTION>::Instance()
1464                 .Map( ZONE_CONNECTION::INHERITED,   _HKI( "Inherited" ) )
1465                 .Map( ZONE_CONNECTION::NONE,        _HKI( "None" ) )
1466                 .Map( ZONE_CONNECTION::THERMAL,     _HKI( "Thermal reliefs" ) )
1467                 .Map( ZONE_CONNECTION::FULL,        _HKI( "Solid" ) )
1468                 .Map( ZONE_CONNECTION::THT_THERMAL, _HKI( "Reliefs for PTH" ) );
1469 
1470         PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
1471         REGISTER_TYPE( ZONE );
1472         propMgr.InheritsAfter( TYPE_HASH( ZONE ), TYPE_HASH( BOARD_CONNECTED_ITEM ) );
1473         propMgr.AddProperty( new PROPERTY<ZONE, unsigned>( _HKI( "Priority" ),
1474                     &ZONE::SetPriority, &ZONE::GetPriority ) );
1475         //propMgr.AddProperty( new PROPERTY<ZONE, bool>( "Filled",
1476                     //&ZONE::SetIsFilled, &ZONE::IsFilled ) );
1477         propMgr.AddProperty( new PROPERTY<ZONE, wxString>( _HKI( "Name" ),
1478                     &ZONE::SetZoneName, &ZONE::GetZoneName ) );
1479         propMgr.AddProperty( new PROPERTY<ZONE, int>( _HKI( "Clearance Override" ),
1480                     &ZONE::SetLocalClearance, &ZONE::GetLocalClearance,
1481                     PROPERTY_DISPLAY::DISTANCE ) );
1482         propMgr.AddProperty( new PROPERTY<ZONE, int>( _HKI( "Min Width" ),
1483                     &ZONE::SetMinThickness, &ZONE::GetMinThickness,
1484                     PROPERTY_DISPLAY::DISTANCE ) );
1485         propMgr.AddProperty( new PROPERTY_ENUM<ZONE, ZONE_CONNECTION>( _HKI( "Pad Connections" ),
1486                     &ZONE::SetPadConnection, &ZONE::GetPadConnection ) );
1487         propMgr.AddProperty( new PROPERTY<ZONE, int>( _HKI( "Thermal Relief Gap" ),
1488                     &ZONE::SetThermalReliefGap, &ZONE::GetThermalReliefGap,
1489                     PROPERTY_DISPLAY::DISTANCE ) );
1490         propMgr.AddProperty( new PROPERTY<ZONE, int>( _HKI( "Thermal Relief Width" ),
1491                     &ZONE::SetThermalReliefSpokeWidth, &ZONE::GetThermalReliefSpokeWidth,
1492                     PROPERTY_DISPLAY::DISTANCE ) );
1493     }
1494 } _ZONE_DESC;
1495 
1496 ENUM_TO_WXANY( ZONE_CONNECTION );
1497