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