KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_via_stitch.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <pcb_via_stitch.h>
25
27#include <pcb_base_edit_frame.h>
28#include <tool/tool_manager.h>
30#include <board_commit.h>
31
32#include <cmath>
33#include <map>
34#include <vector>
35
36#include <boost/random/mersenne_twister.hpp>
37#include <boost/random/uniform_real_distribution.hpp>
38
40#include <geometry/seg.h>
42#include <geometry/shape_rect.h>
44#include <gal/painter.h>
45#include <tool/action_menu.h>
46#include <tools/pcb_actions.h>
47#include <view/view.h>
48#include <zone.h>
49#include <pad.h>
50
51#include <pcb_track.h>
52#include <board.h>
55#include <footprint.h>
57#include <drc/drc_engine.h>
58#include <drc/drc_rtree.h>
59#include <drc/drc_rule.h>
60#include <properties/property.h>
63
64const wxString PCB_VIA_STITCH::DISPLAY_NAME = _HKI( "Via Stitching" );
65const wxString PCB_VIA_STITCH::GENERATOR_TYPE = wxS( "via_stitch" );
66
67
68const std::vector<VECTOR2D>& PCB_VIA_STITCH::bakedPoissonTile()
69{
70 static const std::vector<VECTOR2D> samples = POISSON_DISK::ToroidalUnitTile(
72 return samples;
73}
74
75
77 PCB_GENERATOR_POLY( aParent, F_Cu ),
78 m_viaTemplate( std::make_unique<PCB_VIA>( this ) )
79{
82
83
84 m_viaTemplate->SetIsFree( true );
85}
86
87
89 PCB_GENERATOR_POLY( aOther ),
90 m_pitch( aOther.m_pitch ),
91 m_layout( aOther.m_layout ),
92 m_mode( aOther.m_mode ),
93 m_seed( aOther.m_seed ),
94 m_viaTemplate( std::make_unique<PCB_VIA>( *aOther.m_viaTemplate ) ),
96 m_netCode( aOther.m_netCode ),
103{
104 // m_childContextMenu is intentionally not copied
105}
106
107
108PCB_VIA_STITCH::PCB_VIA_STITCH( PCB_VIA_STITCH&& aOther ) noexcept = default;
109
111{
112 if( this != &aOther )
113 {
115 m_pitch = aOther.m_pitch;
116 m_layout = aOther.m_layout;
117 m_mode = aOther.m_mode;
118 m_seed = aOther.m_seed;
119 m_viaTemplate = std::make_unique<PCB_VIA>( *aOther.m_viaTemplate );
121 m_netCode = aOther.m_netCode;
128 // m_childContextMenu is intentionally not copied.
129 m_childContextMenu.reset();
130 }
131 return *this;
132}
133
134PCB_VIA_STITCH& PCB_VIA_STITCH::operator=( PCB_VIA_STITCH&& aOther ) noexcept = default;
135
137
138
140{
141 return m_viaTemplate->GetWidth( PADSTACK::ALL_LAYERS );
142}
143
144
146{
147 m_viaTemplate->SetWidth( PADSTACK::ALL_LAYERS, aVal );
148 MarkDirty();
149}
150
151
153{
154 int d = m_viaTemplate->GetDrillValue();
155 return d > 0 ? d : 0;
156}
157
158
160{
161 m_viaTemplate->SetDrill( aVal );
162 MarkDirty();
163}
164
165
166std::vector<std::pair<wxString, const BOARD_ITEM*>> PCB_VIA_STITCH::GetTemplateItems() const
167{
168 return { { wxS( "via" ), m_viaTemplate.get() } };
169}
170
171
172void PCB_VIA_STITCH::SetTemplateItem( const wxString& aName, std::unique_ptr<BOARD_ITEM> aItem )
173{
174 if( aName != wxS( "via" ) || !aItem || aItem->Type() != PCB_VIA_T )
175 return;
176
177 m_viaTemplate.reset( static_cast<PCB_VIA*>( aItem.release() ) );
178 m_viaTemplate->SetParent( this );
179 m_viaTemplate->SetIsFree( true );
180}
181
182void PCB_VIA_STITCH::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
183{
184 if( m_outline.OutlineCount() == 0 )
185 return;
186
187 // Only paint on the outline layer. ViewGetLayers() also lists LAYER_VIAS /
188 // LAYER_VIA_HOLES / LAYER_ANCHOR for the produced vias' rendering, but those are
189 // handled by the via children themselves; we don't want to redraw the outline
190 // multiple times per frame.
191 if( aLayer != LAYER_VIA_STITCHING )
192 return;
193
194 KIGFX::GAL* gal = aView->GetGAL();
195
196 // We are drawing perpendicular ticks along the outline to indicate the stitch zone
197 // It kind of looks like a sewing pattern with the ticks, so it fits.
198 KIGFX::RENDER_SETTINGS* settings = aView->GetPainter()->GetSettings();
199 const KIGFX::COLOR4D borderColor = settings->GetColor( this, LAYER_VIA_STITCHING );
200
201 const SHAPE_LINE_CHAIN& outline = m_outline.COutline( 0 );
202 const int n = outline.PointCount();
203
204 gal->SetIsFill( false );
205 gal->SetIsStroke( true );
206 gal->SetStrokeColor( borderColor );
207 gal->SetLineWidth( pcbIUScale.mmToIU( 0.1 ) );
208 gal->DrawPolyline( outline );
209
210 const double tickSpacing = pcbIUScale.mmToIU( 2.0 );
211 const double tickLength = pcbIUScale.mmToIU( 0.5 );
212
213 // Approximate centroid — used to resolve which perpendicular direction is inward
214 // regardless of polygon winding order.
215 VECTOR2D centroid( 0.0, 0.0 );
216 for( int i = 0; i < n; ++i )
217 {
218 centroid.x += outline.CPoint( i ).x;
219 centroid.y += outline.CPoint( i ).y;
220 }
221 centroid.x /= n;
222 centroid.y /= n;
223
224 gal->SetLineWidth( 1 ); // hairline ticks
225
226 for( int i = 0; i < n; ++i )
227 {
228 const VECTOR2D p1( outline.CPoint( i ) );
229 const VECTOR2D p2( outline.CPoint( ( i + 1 ) % n ) );
230
231 const VECTOR2D edgeVec = p2 - p1;
232 const double edgeLen = edgeVec.EuclideanNorm();
233
234 if( edgeLen < 1.0 )
235 continue;
236
237 const VECTOR2D edgeDir = edgeVec / edgeLen;
238
239 // Pick the perpendicular that points toward the centroid
240 const VECTOR2D edgeMid = ( p1 + p2 ) / 2.0;
241 const VECTOR2D perp = VECTOR2D( -edgeDir.y, edgeDir.x );
242 const VECTOR2D toCentr = centroid - edgeMid;
243 const bool perpToward = ( perp.x * toCentr.x + perp.y * toCentr.y >= 0.0 );
244 const VECTOR2D inward = perpToward ? perp : VECTOR2D( -perp.x, -perp.y );
245
246 for( double t = tickSpacing; t < edgeLen; t += tickSpacing )
247 {
248 const VECTOR2D base = p1 + edgeDir * t;
249 gal->DrawLine( base, base + inward * tickLength );
250 }
251 }
252}
253
254
256{
258
259 // An empty outline has no vertex 0 (e.g. a freshly constructed generator before the
260 // outline is set); fall back to the anchor origin rather than throwing.
261 if( !m_outline.GetRelativeIndices( 0, &index ) )
262 return m_origin;
263
264 return m_outline.CVertex( index );
265}
266
267
269{
270 if( m_viaTemplate->GetWidth( PADSTACK::ALL_LAYERS ) <= 0 )
271 m_viaTemplate->SetWidth( PADSTACK::ALL_LAYERS, defaultViaSize( aBoard ) );
272
273 if( m_viaTemplate->GetDrillValue() <= 0 )
274 m_viaTemplate->SetDrill( defaultViaDrill( aBoard ) );
275
276 if( m_pitch <= 0 )
277 {
278 m_pitch = defaultPitch( aBoard );
279
281 }
282}
283
284
286{
287 if( aCommit )
288 {
289 if( IsNew() )
290 aCommit->Add( this );
291 else
292 aCommit->Modify( this );
293 }
294
295 // Initialize property defaults on first edit
296 InitializeDefaults( aBoard );
297
298 // Resolve net code from saved name (SetProperties has no board reference)
299 if( m_netCode == 0 && !m_lastNetName.empty() && aBoard )
300 {
301 if( NETINFO_ITEM* net = aBoard->FindNet( m_lastNetName ) )
302 m_netCode = net->GetNetCode();
303 }
304
305 if( m_guardedNetCode == 0 && !m_lastGuardedNetName.empty() && aBoard )
306 {
307 if( NETINFO_ITEM* net = aBoard->FindNet( m_lastGuardedNetName ) )
308 m_guardedNetCode = net->GetNetCode();
309 }
310
311 SetFlags( IN_EDIT );
312}
313
314
316{
317 int mm2 = pcbIUScale.mmToIU( 2.0 );
318 int viaSize = m_viaTemplate->GetWidth( PADSTACK::TEMP_ALL_LAYERS ) > 0 ? m_viaTemplate->GetWidth( PADSTACK::TEMP_ALL_LAYERS )
319 : defaultViaSize( aBoard );
320 int pitch = std::max( mm2, viaSize * 2 );
321 return pitch;
322}
323
324
326{
327 if( !aBoard )
328 return pcbIUScale.mmToIU( 0.6 );
329
331 int val = ds.GetCurrentViaSize();
332 if( val <= 0 )
333 val = pcbIUScale.mmToIU( 0.6 );
334 return val;
335}
336
338{
339 if( !aBoard )
340 return pcbIUScale.mmToIU( 0.3 );
341
343 int val = ds.GetCurrentViaDrill();
344 if( val <= 0 )
345 val = pcbIUScale.mmToIU( 0.3 );
346 return val;
347}
348
349
351{
352 if( !( GetFlags() & IN_EDIT ) )
353 return false;
354
355 if( !aBoard || !aCommit )
356 return false;
357
358 // Resolve net code from the saved name in case EditStart() was not called
359 // (e.g. programmatic regeneration after load)
360 if( m_netCode == 0 && !m_lastNetName.empty() )
361 {
362 if( NETINFO_ITEM* net = aBoard->FindNet( m_lastNetName ) )
363 m_netCode = net->GetNetCode();
364 }
365
366 if( m_guardedNetCode == 0 && !m_lastGuardedNetName.empty() )
367 {
368 if( NETINFO_ITEM* net = aBoard->FindNet( m_lastGuardedNetName ) )
369 m_guardedNetCode = net->GetNetCode();
370 }
371
372 int detectPitch = m_pitch > 0 ? m_pitch : defaultPitch( aBoard );
373
374 // Avoid accidentally calculating a grid offset change because we just changed layout modes
375 const GRID_CONFIG currentConfig{ m_layout, m_mode, detectPitch };
376 const bool childrenAreOnThisGrid = m_childGridConfig == currentConfig;
377
378 m_childGridConfig = currentConfig;
379
380 // Try to detect origin offset change due to moved via
381 if( usesGridCells() && childrenAreOnThisGrid )
382 {
383 for( BOARD_ITEM* it : GetBoardItems() )
384 {
385 if( it->Type() != PCB_VIA_T )
386 continue;
387
388 VECTOR2I p = static_cast<PCB_VIA*>( it )->GetPosition();
389 VECTOR2I expectedPos = positionForCell( cellForPosition( p ), detectPitch );
390
391 if( expectedPos != p )
392 {
393 int offsetY = ( ( p.y % detectPitch ) + detectPitch ) % detectPitch;
394
395 // The dragged via defines the new grid origin. On a staggered layout an odd
396 // row carries an extra half-pitch x shift, which must be removed before
397 // computing the origin offset or the whole grid re-anchors half a pitch off.
398 // (p.y - offsetY) is an exact multiple of the pitch, so this row index matches
399 // what cellForPosition()/positionForCell() will derive after the re-anchor.
400 int row = ( p.y - offsetY ) / detectPitch;
402 && ( ( row % 2 + 2 ) % 2 != 0 ) ) ? detectPitch / 2 : 0;
403
405 VECTOR2I( ( ( ( p.x - xShift ) % detectPitch ) + detectPitch ) % detectPitch,
406 offsetY );
407 break;
408 }
409 }
410 }
411
412 // Take snapshot of existing vias, we then selectively remove vias only if they should be removed
413 // we want to reduce file churn of KIIDs
414 std::vector<PCB_VIA*> existingVias;
415
416 for( BOARD_ITEM* item : GetBoardItems() )
417 {
418 if( item->Type() == PCB_VIA_T )
419 existingVias.push_back( static_cast<PCB_VIA*>( item ) );
420 }
421
422 bool changed = false;
423
424 auto removeVia =
425 [&]( PCB_VIA* aVia )
426 {
427 RemoveItem( aVia );
428 m_pendingRemovals.insert( aVia );
429 changed = true;
430 };
431
432 if( m_outline.IsEmpty() )
433 {
434 for( PCB_VIA* via : existingVias )
435 removeVia( via );
436
437 m_lastUpdateChangedVias = changed;
438 ClearDirty();
439 return false;
440 }
441
442 int pitch = m_pitch > 0 ? m_pitch : defaultPitch( aBoard );
443
444 if( m_viaTemplate->GetWidth( PADSTACK::ALL_LAYERS ) <= 0 )
445 m_viaTemplate->SetWidth( PADSTACK::ALL_LAYERS, defaultViaSize( aBoard ) );
446 if( m_viaTemplate->GetDrillValue() <= 0 )
447 m_viaTemplate->SetDrill( defaultViaDrill( aBoard ) );
448
449 // Compute the set of grid cells where a via fits cleanly (DRC + same-net zone overlap).
450 // TODO, we will use a cache grid
451 std::set<VECTOR2I> placementCells = buildPlacementCells( aBoard );
452
453 // Guard mode currently has to exclude by a proximity heuristic since we aren't using a grid.
454 const int64_t guardExcludeRadiusSq = ( (int64_t) pitch / 4 ) * ( pitch / 4 );
455
456 auto isExcluded =
457 [&]( const VECTOR2I& cell ) -> bool
458 {
459 // Guard vias are walked along the guarded net's envelope, so a stored position
460 // will rarely land exactly on a regenerated via. Match by proximity instead.
462 {
463 for( const VECTOR2I& excluded : m_excludedPositions )
464 {
465 VECTOR2I d = excluded - cell;
466
467 if( (int64_t) d.x * d.x + (int64_t) d.y * d.y < guardExcludeRadiusSq )
468 return true;
469 }
470
471 return false;
472 }
473
474 return usesGridCells() ? m_excludedCells.count( cell ) > 0
475 : m_excludedPositions.count( cell ) > 0;
476 };
477
478 // Get the new list of desired via cells
479 std::map<VECTOR2I, VECTOR2I> desired;
480
481 for( const VECTOR2I& cell : placementCells )
482 {
483 if( !isExcluded( cell ) )
484 desired.emplace( cell, positionForCell( cell, pitch ) );
485 }
486
487 // Determine existing vias we can keep
488 std::map<VECTOR2I, PCB_VIA*> kept;
489
490 for( PCB_VIA* via : existingVias )
491 {
492 VECTOR2I cell = cellForPosition( via->GetPosition() );
493
494 if( desired.count( cell ) && kept.emplace( cell, via ).second )
495 continue;
496
497 removeVia( via );
498 }
499
500 // Vias retired earlier may need to be unretired, this usually occurs when someone
501 // is shrinking the outline and then changes their mind in the same drag
502 std::map<VECTOR2I, PCB_VIA*> resurrectable;
503
505 resurrectable.try_emplace( cellForPosition( via->GetPosition() ), via );
506
507 // Now create a reference via to compare against, we need to see if existing vias need to be nuked
508 // anyway on a property change. In which case we will just regen rather than play games
509 std::unique_ptr<PCB_VIA> ref( static_cast<PCB_VIA*>( m_viaTemplate->Clone() ) );
510 ref->SetParent( aBoard );
511 ref->SetIsFree( true );
512
513 if( m_netCode )
514 ref->SetNetCode( m_netCode );
515
516 auto matchesRef =
517 [&]( const PCB_VIA* aVia ) -> bool
518 {
519 // The padstack carries size, drill, layer span, mask/paste attributes,
520 // backdrill and post-machining. That covers everything the via dialog
521 // can edit except the net and teardrop settings, checked separately.
522 return aVia->GetPosition() == ref->GetPosition()
523 && aVia->GetLayer() == ref->GetLayer()
524 && aVia->GetViaType() == ref->GetViaType()
525 && aVia->Padstack() == ref->Padstack()
526 && aVia->GetNetCode() == ref->GetNetCode()
527 && aVia->GetTeardropParams() == ref->GetTeardropParams();
528 };
529
530 for( const auto& [cell, pt] : desired )
531 {
532 auto it = kept.find( cell );
533
534 if( it != kept.end() )
535 {
536 PCB_VIA* via = it->second;
537
538 ref->SetPosition( pt );
539
540 if( matchesRef( via ) )
541 continue; // unchanged — no commit entry, UUID preserved
542
543 // Just nudge the via
544 ref->SetPosition( via->GetPosition() );
545
546 if( matchesRef( via ) )
547 {
548 aCommit->Modify( via );
549 via->SetPosition( pt );
550 changed = true;
551 continue;
552 }
553
554 removeVia( via );
555 }
556
557 // Resurrect a via retired earlier
558 auto rit = resurrectable.find( cell );
559
560 if( rit != resurrectable.end() )
561 {
562 PCB_VIA* via = rit->second;
563
564 ref->SetPosition( pt );
565
566 if( matchesRef( via ) )
567 {
568 m_pendingRemovals.erase( via );
569 AddItem( via );
570 changed = true;
571 continue;
572 }
573 }
574
575 PCB_VIA* via = static_cast<PCB_VIA*>( m_viaTemplate->Clone() );
576 via->ResetUuidDirect();
577
578 via->SetParent( aBoard );
579 via->SetPosition( pt );
580
581 if( m_netCode )
582 via->SetNetCode( m_netCode );
583
584 // Vias must be marked free to avoid connectivity stealing them away
585 via->SetIsFree( true );
586
587 // If the generator is selected (point editing), make sure to mark new vias
588 // as selected to match
589 if( IsSelected() )
590 via->SetSelected();
591
592 AddItem( via );
593 aCommit->Add( via );
594 changed = true;
595 }
596
597 m_lastUpdateChangedVias = changed;
598 ClearDirty();
599 return true;
600}
601
602
604{
606
607 props.set_iu( "pitch", m_pitch );
608 props.set( "layout", static_cast<int>( m_layout ) );
609 props.set( "mode", static_cast<int>( m_mode ) );
610 props.set( "seed", static_cast<int>( m_seed ) );
611 props.set_iu( "origin_offset_x", m_originOffset.x );
612 props.set_iu( "origin_offset_y", m_originOffset.y );
613
614 // Save the net by name so it survives across sessions (net codes are session-local)
615 if( !m_lastNetName.empty() )
616 props.set( "net_name", m_lastNetName );
617
618 if( !m_lastGuardedNetName.empty() )
619 props.set( "guarded_net_name", m_lastGuardedNetName );
620
621 if( !m_excludedCells.empty() )
622 {
623 std::vector<VECTOR2I> cells( m_excludedCells.begin(), m_excludedCells.end() );
624 props.set( "excluded_grid_cells", wxAny( cells ) );
625 }
626
627 if( !m_excludedPositions.empty() )
628 {
630 for( const VECTOR2I& pos : m_excludedPositions )
631 chain.Append( pos );
632 props.set( "excluded_positions", wxAny( chain ) );
633 }
634
635 // Serialize the single zone outline as a SHAPE_LINE_CHAIN.
636 // The file format natively supports SHAPE_LINE_CHAIN but not SHAPE_POLY_SET directly.
637 if( m_outline.OutlineCount() > 0 )
638 props.set( "outline", wxAny( m_outline.COutline( 0 ) ) );
639
640 return props;
641}
642
643
645{
647
648 aProps.get_to_iu( "pitch", m_pitch );
649
650 if( auto layout = aProps.get_opt<int>( "layout" ) )
651 m_layout = static_cast<PCB_VIA_STITCH_LAYOUT>( *layout );
652
653 if( auto mode = aProps.get_opt<int>( "mode" ) )
654 m_mode = static_cast<PCB_VIA_STITCH_MODE>( *mode );
655
656 if( auto seed = aProps.get_opt<int>( "seed" ) )
657 m_seed = static_cast<uint32_t>( *seed );
658
659 aProps.get_to_iu( "origin_offset_x", m_originOffset.x );
660 aProps.get_to_iu( "origin_offset_y", m_originOffset.y );
661
663
664 // Restore net name; net code is resolved from the board in EditStart()/Update()
665 aProps.get_to( "net_name", m_lastNetName );
666 aProps.get_to( "guarded_net_name", m_lastGuardedNetName );
667
668 m_excludedCells.clear();
669 m_excludedPositions.clear();
670
671
672 if( auto cells = aProps.get_opt<std::vector<VECTOR2I>>( "excluded_grid_cells" ) )
673 m_excludedCells.insert( cells->begin(), cells->end() );
674
675 if( auto chain = aProps.get_opt<SHAPE_LINE_CHAIN>( "excluded_positions" ) )
676 {
677 for( int i = 0; i < chain->PointCount(); ++i )
678 m_excludedPositions.insert( chain->CPoint( i ) );
679 }
680
681 // Restore the single zone outline
682 m_outline.RemoveAllContours();
683
684 if( auto outline = aProps.get_opt<SHAPE_LINE_CHAIN>( "outline" ) )
685 {
686 // A chain parsed from the file is a bare point list; the closed flag is not
687 // serialized and AddOutline() asserts on open chains
688 outline->SetClosed( true );
689 m_outline.AddOutline( *outline );
690 }
691}
692
693
695{
696 if( !( GetFlags() & IN_EDIT ) )
697 return;
698
699 // The edit session is over, any vias still pending removal can now be committed
700 // for removal.
701 if( aCommit )
702 {
704 aCommit->Remove( via );
705 }
706
707 m_pendingRemovals.clear();
708
710}
711
712
714{
715 if( !( GetFlags() & IN_EDIT ) )
716 return;
717
718 // The commit is being reverted; re-attach retired vias so group membership matches
719 // the pre-edit state they revert to.
721 AddItem( via );
722
723 m_pendingRemovals.clear();
724
726}
727
728
730{
731 if( !m_childContextMenu )
732 {
733 m_childContextMenu = std::make_unique<ACTION_MENU>( true, aTool );
735 }
736
737 return m_childContextMenu.get();
738}
739
740
742{
743 if( !aCommit )
744 return;
745
746 for( BOARD_ITEM* item : GetBoardItems() )
747 aCommit->Remove( item );
748
749 // Retired-but-unpushed vias are detached from the group and would otherwise be
750 // orphaned on the board.
752 aCommit->Remove( via );
753
754 m_pendingRemovals.clear();
755
756 aCommit->Remove( this );
757}
758
759
761{
762 int pitch = m_pitch > 0 ? m_pitch : 1;
763
764 auto roundDiv = []( int a, int b ) -> int
765 {
766 return ( a >= 0 ) ? ( a + b / 2 ) / b : -( ( -a + b / 2 ) / b );
767 };
768
769 // Poisson layout and GUARD mode both place at arbitrary positions, not a grid —
770 // cell index == absolute position.
771 if( !usesGridCells() )
772 return aPos;
773
774 int row = roundDiv( aPos.y - m_originOffset.y, pitch );
775 int xShift = ( m_layout == PCB_VIA_STITCH_LAYOUT::STAGGERED && ( ( row % 2 + 2 ) % 2 != 0 ) )
776 ? pitch / 2
777 : 0;
778 int col = roundDiv( aPos.x - m_originOffset.x - xShift, pitch );
779
780 return VECTOR2I( col, row );
781}
782
783
784VECTOR2I PCB_VIA_STITCH::positionForCell( const VECTOR2I& aCell, int aPitch ) const
785{
786 if( !usesGridCells() )
787 return aCell;
788
789 int xShift = ( m_layout == PCB_VIA_STITCH_LAYOUT::STAGGERED && ( ( aCell.y % 2 + 2 ) % 2 != 0 ) )
790 ? aPitch / 2
791 : 0;
792 return VECTOR2I( aCell.x * aPitch + m_originOffset.x + xShift,
793 aCell.y * aPitch + m_originOffset.y );
794}
795
796
797std::set<VECTOR2I> PCB_VIA_STITCH::buildPlacementCells( BOARD* aBoard ) const
798{
799 std::set<VECTOR2I> cells;
800
801 if( !aBoard || m_outline.IsEmpty() )
802 return cells;
803
804 int pitch = m_pitch > 0 ? m_pitch : defaultPitch( aBoard );
805 int viaSize = m_viaTemplate->GetWidth( PADSTACK::ALL_LAYERS ) > 0 ? m_viaTemplate->GetWidth( PADSTACK::ALL_LAYERS )
806 : defaultViaSize( aBoard );
807
808 if( GetNetCode() == 0 )
809 return cells;
810
811 DRC_ENGINE* drcEngine = aBoard->GetDesignSettings().m_DRCEngine.get();
812
813 DRC_CACHE_GENERATOR cacheGenerator;
814 cacheGenerator.SetDRCEngine( drcEngine );
815 cacheGenerator.Run();
816
817 // Pre-clip every zone fill to the stitch outline bbox before adding it to perLayerFills.
818 // Without this, a board-wide GND zone would drag in an entire complex polygon when we may
819 // only be stitching part of it.
820 // This also helps us skip same net zones entirely if they aren't in here
821 BOX2I stitchBBox = m_outline.BBox();
822
823 SHAPE_POLY_SET stitchClip;
824 stitchClip.NewOutline();
825 stitchClip.Append( stitchBBox.GetLeft(), stitchBBox.GetTop() );
826 stitchClip.Append( stitchBBox.GetRight(), stitchBBox.GetTop() );
827 stitchClip.Append( stitchBBox.GetRight(), stitchBBox.GetBottom() );
828 stitchClip.Append( stitchBBox.GetLeft(), stitchBBox.GetBottom() );
829
830 std::map<PCB_LAYER_ID, SHAPE_POLY_SET> perLayerFills;
831
832 for( ZONE* zone : collectAllZones( aBoard ) )
833 {
834 if( zone->GetIsRuleArea() )
835 continue;
836
837 if( zone->GetNetCode() != GetNetCode() )
838 continue;
839
840 if( !zone->GetBoundingBox().Intersects( stitchBBox ) )
841 continue;
842
843 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
844 {
845 SHAPE_POLY_SET clipped;
846
848 {
849 clipped = zone->GetBoardOutline();
850 }
851 else
852 {
853 SHAPE_POLY_SET* fill = zone->GetFill( layer );
854
855 if( !fill || fill->IsEmpty() )
856 continue;
857
858 clipped = *fill;
859 }
860
861 if( clipped.IsEmpty() )
862 continue;
863
864 clipped.BooleanIntersection( stitchClip );
865
866 if( clipped.OutlineCount() == 0 )
867 continue;
868
869 perLayerFills[layer].BooleanAdd( clipped );
870 }
871 }
872
873 // Extract the via copper layers, the layer stack may contain the mask layers
874 LSET boardCopperLayers = LSET::AllCuMask( aBoard->GetCopperLayerCount() );
875 LSET viaCopperLayers = m_viaTemplate->GetLayerSet() & boardCopperLayers;
876
877 // Filter down to just those via layers that actually have same-net copper on them.
878 std::vector<const SHAPE_POLY_SET*> viaLayerFills;
879
880 for( PCB_LAYER_ID layer : viaCopperLayers )
881 {
882 auto it = perLayerFills.find( layer );
883
884 if( it != perLayerFills.end() && !it->second.IsEmpty() )
885 viaLayerFills.push_back( &it->second );
886 }
887
888 // Is there even enough layers left to stitch?
889 if( viaLayerFills.size() < 2 )
890 return cells;
891
892 // Now build the region where a via would stitch at least two layers together.
893 SHAPE_POLY_SET allowedRegion;
894 SHAPE_POLY_SET coveredSoFar = *viaLayerFills[0];
895
896 for( size_t i = 1; i < viaLayerFills.size(); ++i )
897 {
898 SHAPE_POLY_SET overlap = *viaLayerFills[i];
899 overlap.BooleanIntersection( coveredSoFar );
900
901 if( !overlap.IsEmpty() )
902 allowedRegion.BooleanAdd( overlap );
903
904 if( i + 1 < viaLayerFills.size() )
905 coveredSoFar.BooleanAdd( *viaLayerFills[i] );
906 }
907
908 if( allowedRegion.OutlineCount() == 0 )
909 return cells;
910
911 // Now intersect it with the via stitch outline
912 allowedRegion.BooleanIntersection( m_outline );
913
914 if( allowedRegion.OutlineCount() == 0 )
915 return cells;
916
917 // Generate a obstacle mask for via placement
918 // Rather than DRC-probing every possible placement cell against the r-tree
919 // to test for collisions. We instead create a keep-out polygon with obstacles
920 // inflated by the via-radius and clearance we want. The result is now
921 // we only have to do a point-in-polygon test for placement
922
923 const int polyApproxError = pcbIUScale.mmToIU( 0.005 );
924
925 // Require the whole via, not just its center, to land on same-net copper inside the
926 // outline
927 allowedRegion.Deflate( viaSize / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, polyApproxError );
928
929 if( allowedRegion.OutlineCount() == 0 )
930 return cells;
931
932 // Probe used for clearance evaluation; it must carry the stitch net because clearance
933 // rules can key off the net class.
934 std::unique_ptr<PCB_VIA> probe( static_cast<PCB_VIA*>( m_viaTemplate->Clone() ) );
935 probe->SetParent( const_cast<BOARD*>( aBoard ) );
936
937 if( m_netCode )
938 probe->SetNetCode( m_netCode );
939
940 const int worstClearance = aBoard->m_DRCMaxClearance;
941
942 BOX2I queryBBox = stitchBBox;
943 queryBBox.Inflate( worstClearance + viaSize );
944
945 SHAPE_POLY_SET queryClip;
946 queryClip.NewOutline();
947 queryClip.Append( queryBBox.GetLeft(), queryBBox.GetTop() );
948 queryClip.Append( queryBBox.GetRight(), queryBBox.GetTop() );
949 queryClip.Append( queryBBox.GetRight(), queryBBox.GetBottom() );
950 queryClip.Append( queryBBox.GetLeft(), queryBBox.GetBottom() );
951
952 SHAPE_POLY_SET obstacles;
953
954 int viaDrill = m_viaTemplate->GetDrillValue() > 0 ? m_viaTemplate->GetDrillValue()
955 : defaultViaDrill( aBoard );
956
957 auto evalConstraint =
958 [&]( DRC_CONSTRAINT_T aType, BOARD_ITEM* aOther, PCB_LAYER_ID aLayer ) -> int
959 {
960 if( !drcEngine )
961 return 0;
962
963 DRC_CONSTRAINT constraint =
964 drcEngine->EvalRules( aType, probe.get(), aOther, aLayer );
965 return constraint.GetValue().Min();
966 };
967
968 auto isSameNet =
969 [&]( BOARD_ITEM* aOther ) -> bool
970 {
971 BOARD_CONNECTED_ITEM* cItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( aOther );
972 return cItem && cItem->GetNetCode() == GetNetCode();
973 };
974
975 // Drilled holes are obstacles as we need to respect the hole to hole constraint
976 std::set<BOARD_ITEM*> holeSeen;
977
978 auto addHoleObstacle =
979 [&]( BOARD_ITEM* aOther, PCB_LAYER_ID aLayer )
980 {
981 if( !aOther->HasHole() || !holeSeen.insert( aOther ).second )
982 return;
983
984 std::shared_ptr<SHAPE_SEGMENT> hole = aOther->GetEffectiveHoleShape();
985
986 if( !hole )
987 return;
988
989 int margin = viaDrill / 2
990 + evalConstraint( HOLE_TO_HOLE_CONSTRAINT, aOther, UNDEFINED_LAYER );
991
992 if( !isSameNet( aOther ) )
993 {
994 margin = std::max( margin,
995 viaSize / 2 + evalConstraint( HOLE_CLEARANCE_CONSTRAINT,
996 aOther, aLayer ) );
997 }
998
999 TransformOvalToPolygon( obstacles, hole->GetSeg().A, hole->GetSeg().B,
1000 hole->GetWidth() + 2 * margin, polyApproxError,
1001 ERROR_OUTSIDE );
1002 };
1003
1004 SHAPE_RECT queryRect( queryBBox.GetPosition(), queryBBox.GetWidth(), queryBBox.GetHeight() );
1005
1006 for( PCB_LAYER_ID layer : viaCopperLayers )
1007 {
1008 std::set<BOARD_ITEM*> seen;
1009
1010 auto enumerate =
1011 [&]( BOARD_ITEM* aOther ) -> bool
1012 {
1013 if( !seen.insert( aOther ).second )
1014 return false;
1015
1016 // Our own children are regenerated along with us and must not block
1017 // their own cells.
1018 if( aOther->GetParentGroup() == static_cast<const EDA_GROUP*>( this ) )
1019 return false;
1020
1021 // Same for children retired during the same edit: they are
1022 // detached from the group but stay on the board until the commit is
1023 // pushed, and their cells must stay placeable so they can resurrect.
1024 if( aOther->Type() == PCB_VIA_T
1025 && m_pendingRemovals.count( static_cast<PCB_VIA*>( aOther ) ) )
1026 {
1027 return false;
1028 }
1029
1030 addHoleObstacle( aOther, layer );
1031
1032 bool sameNet = isSameNet( aOther );
1033
1034 if( aOther->Type() == PCB_PAD_T )
1035 {
1036 // A copper-zone can be placed with thermal reliefs turned off
1037 // We don't want to accidentally place vias on top of that pad
1038 // So process the PAD as a obstacle we need to clear
1039 PAD* pad = static_cast<PAD*>( aOther );
1040
1041 if( !pad->FlashLayer( layer ) )
1042 return false;
1043 }
1044 else if( aOther->Type() != PCB_VIA_T && sameNet )
1045 {
1046 return false;
1047 }
1048
1049 // Our copper against theirs.
1050 int margin = viaSize / 2 + evalConstraint( CLEARANCE_CONSTRAINT, aOther, layer );
1051
1052 // Our drill against their copper (hole clearance), cross-net only.
1053 if( !sameNet )
1054 {
1055 margin = std::max( margin,
1056 viaDrill / 2 + evalConstraint( HOLE_CLEARANCE_CONSTRAINT,
1057 aOther, layer ) );
1058 }
1059
1060 aOther->TransformShapeToPolygon( obstacles, layer, margin, polyApproxError,
1061 ERROR_OUTSIDE );
1062 return false;
1063 };
1064
1065 if( aBoard->m_CopperItemRTreeCache )
1066 {
1067 aBoard->m_CopperItemRTreeCache->CheckColliding( &queryRect, layer, worstClearance,
1068 enumerate );
1069 }
1070 }
1071
1072 // Add NPTH mounting holes which have no copper
1073 for( FOOTPRINT* footprint : aBoard->Footprints() )
1074 {
1075 if( !footprint->GetBoundingBox().Intersects( queryBBox ) )
1076 continue;
1077
1078 for( PAD* pad : footprint->Pads() )
1079 {
1080 if( pad->GetBoundingBox().Intersects( queryBBox ) )
1081 addHoleObstacle( pad, F_Cu );
1082 }
1083 }
1084
1085 obstacles.Simplify();
1086
1087 for( ZONE* zone : collectAllZones( aBoard ) )
1088 {
1089 if( !zone->GetBoundingBox().Intersects( queryBBox ) )
1090 continue;
1091
1092 if( zone->GetIsRuleArea() )
1093 {
1094 // Honor via keepouts (any net).
1095 if( !zone->GetDoNotAllowVias() || !( zone->GetLayerSet() & viaCopperLayers ).any() )
1096 continue;
1097
1098 SHAPE_POLY_SET area = zone->GetBoardOutline();
1099 area.BooleanIntersection( queryClip );
1100
1101 if( !area.IsEmpty() )
1102 {
1104 polyApproxError );
1105 obstacles.BooleanAdd( area );
1106 }
1107
1108 continue;
1109 }
1110
1111 if( zone->GetNetCode() == GetNetCode() )
1112 continue;
1113
1114 // A different-net zone only blocks near its fill outside its own outline.
1115 // A via dropped inside the zone is fine because the refill punches an anti-pad around it.
1116 for( PCB_LAYER_ID layer : LSET( zone->GetLayerSet() & viaCopperLayers ) )
1117 {
1118 SHAPE_POLY_SET* fill = zone->GetFill( layer );
1119
1120 if( !fill || fill->IsEmpty() )
1121 continue;
1122
1123 SHAPE_POLY_SET nearFill = *fill;
1124 nearFill.BooleanIntersection( queryClip );
1125
1126 if( nearFill.IsEmpty() )
1127 continue;
1128
1129 int zoneMargin = std::max(
1130 viaSize / 2 + evalConstraint( CLEARANCE_CONSTRAINT, zone, layer ),
1131 viaDrill / 2 + evalConstraint( HOLE_CLEARANCE_CONSTRAINT, zone, layer ) );
1132
1133 nearFill.Inflate( zoneMargin, CORNER_STRATEGY::ROUND_ALL_CORNERS, polyApproxError );
1134
1135 SHAPE_POLY_SET interior = zone->GetBoardOutline();
1136 interior.BooleanIntersection( queryClip );
1137 nearFill.BooleanSubtract( interior );
1138
1139 if( !nearFill.IsEmpty() )
1140 obstacles.BooleanAdd( nearFill );
1141 }
1142 }
1143
1144 allowedRegion.BooleanSubtract( obstacles );
1145
1146 if( allowedRegion.OutlineCount() == 0 )
1147 return cells;
1148
1149 allowedRegion.BuildBBoxCaches();
1150
1151 BOX2I bbox = allowedRegion.BBox();
1152
1153 auto isValid =
1154 [&]( const VECTOR2I& pt ) -> bool
1155 {
1156 return allowedRegion.Contains( pt, -1, 0, true );
1157 };
1158
1160 {
1161 // Walk the perimeter of each guarded-net item's clearance envelope, dropping
1162 // candidate vias every `pitch` of arc length. The envelope is the item's shape
1163 // inflated by (viaRadius + DRC clearance), so a via dropped on it sits at the
1164 // closest legal distance from the trace
1165 if( m_guardedNetCode == 0 )
1166 return cells;
1167
1168 // Merge every guarded-net item's clearance envelope into one polygon, and their bare
1169 // shapes into another so the sampler can tell facing vias from same-side ones.
1170 SHAPE_POLY_SET mergedEnvelope;
1171 SHAPE_POLY_SET mergedGuarded;
1172
1173 for( PCB_TRACK* track : aBoard->Tracks() )
1174 {
1175 if( track->GetNetCode() != m_guardedNetCode )
1176 continue;
1177
1178 if( !track->GetBoundingBox().Intersects( stitchBBox ) )
1179 continue;
1180
1181 PCB_LAYER_ID layer = track->GetLayer();
1182
1183 if( track->Type() == PCB_VIA_T )
1184 layer = F_Cu; // arbitrary copper layer for clearance evaluation
1185
1186 int requiredClearance = evalConstraint( CLEARANCE_CONSTRAINT, track, layer );
1187
1188 // Safety margin so envelope samples land outside the obstacle mask. The
1189 // guarded items are themselves obstacles, polygonized outward with up to
1190 // polyApproxError of overshoot, so the envelope must sit at least that much
1191 // further out; the extra 2µm absorbs the sample coordinates' integer rounding.
1192 const int safetyMargin = polyApproxError + pcbIUScale.mmToIU( 0.002 );
1193 int margin = viaSize / 2 + requiredClearance + safetyMargin;
1194
1195 SHAPE_POLY_SET envelope;
1196 track->TransformShapeToPolygon( envelope, layer, margin, polyApproxError,
1197 ERROR_OUTSIDE );
1198
1199 mergedEnvelope.BooleanAdd( envelope );
1200
1201 track->TransformShapeToPolygon( mergedGuarded, layer, 0, polyApproxError,
1202 ERROR_OUTSIDE );
1203 }
1204
1205 mergedEnvelope.Simplify();
1206 mergedGuarded.Simplify();
1207
1208 for( const VECTOR2I& pt : SampleGuardEnvelope( mergedEnvelope, mergedGuarded, pitch,
1209 isValid ) )
1210 {
1211 cells.insert( pt );
1212 }
1213
1214 return cells;
1215 }
1216
1218 {
1219 // We tile a one time generated toroidal Poisson pattern
1220 // across the bbox at (pitch * POISSON_TILE_PITCHES) per tile. The tiling is
1221 // anchored to the global (0, 0) origin plus a per-seed sub-tile shift.
1222 // This gives us a deterministic but non-grid distribution
1223 const std::vector<VECTOR2D>& tile = bakedPoissonTile();
1224 const int tileSize = std::max( 1, pitch * POISSON_TILE_PITCHES );
1225
1226 // Per-seed sub-tile origin shift in [0, tileSize).
1227 boost::random::mt19937 seedRng( m_seed );
1228 boost::random::uniform_real_distribution<double> uniform( 0.0, 1.0 );
1229 const double offsetX = uniform( seedRng ) * tileSize;
1230 const double offsetY = uniform( seedRng ) * tileSize;
1231
1232 // Range of tiles that intersect bbox. Tile (tx, ty) covers
1233 // [tx*tileSize + offset, (tx+1)*tileSize + offset).
1234 const int firstTileX = (int) std::floor( ( bbox.GetX() - offsetX ) / (double) tileSize );
1235 const int firstTileY = (int) std::floor( ( bbox.GetY() - offsetY ) / (double) tileSize );
1236 const int lastTileX = (int) std::floor( ( bbox.GetRight() - offsetX ) / (double) tileSize );
1237 const int lastTileY = (int) std::floor( ( bbox.GetBottom() - offsetY ) / (double) tileSize );
1238
1239 for( int ty = firstTileY; ty <= lastTileY; ++ty )
1240 {
1241 for( int tx = firstTileX; tx <= lastTileX; ++tx )
1242 {
1243 for( const VECTOR2D& s : tile )
1244 {
1245 VECTOR2I pt(
1246 (int) std::round( offsetX + ( tx + s.x ) * tileSize ),
1247 (int) std::round( offsetY + ( ty + s.y ) * tileSize ) );
1248
1249 if( !bbox.Contains( pt ) )
1250 continue;
1251
1252 if( isValid( pt ) )
1253 cells.insert( pt ); // Poisson "cell" == absolute position
1254 }
1255 }
1256 }
1257
1258 return cells;
1259 }
1260 else
1261 {
1262 // Anchor the grid to global (0, 0) + m_originOffset.
1263 int startRow = (int) std::floor( double( bbox.GetY() - m_originOffset.y ) / pitch );
1264 int startCol = (int) std::floor( double( bbox.GetX() - m_originOffset.x ) / pitch );
1265 VECTOR2I origin( startCol * pitch + m_originOffset.x, startRow * pitch + m_originOffset.y );
1266
1267 for( int row = startRow, y = origin.y; y <= bbox.GetBottom(); y += pitch, ++row )
1268 {
1269 // Odd rows are shifted right by half the pitch when stagger is enabled.
1270 int xOffset =
1271 ( m_layout == PCB_VIA_STITCH_LAYOUT::STAGGERED && ( ( row % 2 + 2 ) % 2 != 0 ) ) ? pitch / 2 : 0;
1272
1273 for( int col = startCol, x = origin.x + xOffset; x <= bbox.GetRight(); x += pitch, ++col )
1274 {
1275 VECTOR2I pt( x, y );
1276
1277 if( isValid( pt ) )
1278 cells.insert( VECTOR2I( col, row ) );
1279 }
1280 }
1281
1282 return cells;
1283 }
1284}
1285
1286
1288{
1289 std::vector<ZONE*> result;
1290
1291 // A regeneration that added, moved, or removed nothing can't have changed any
1292 // foreign-net anti-pads, so no refill is needed.
1294 return result;
1295
1296 const BOARD* brd = GetBoard();
1297
1298 if( !brd )
1299 return result;
1300
1301 BOX2I myBBox = GetBoundingBox();
1302
1303 // Lets return all intersecting zones with different nets
1304 for( ZONE* zone : collectAllZones( brd ) )
1305 {
1306 if( zone->GetIsRuleArea() )
1307 continue;
1308
1309 if( zone->GetNetCode() == m_netCode )
1310 continue;
1311
1312 if( !zone->GetBoundingBox().Intersects( myBBox ) )
1313 continue;
1314
1315 result.push_back( zone );
1316 }
1317
1318 return result;
1319}
1320
1321
1322void PCB_VIA_STITCH::OnZoneFillChanged( const std::vector<ZONE*>& aZones )
1323{
1324 if( m_netCode == 0 )
1325 return;
1326
1327 BOX2I myBBox = GetBoundingBox();
1328
1329 for( ZONE* zone : aZones )
1330 {
1331 if( zone->GetNetCode() != m_netCode )
1332 continue;
1333
1334 if( !zone->GetBoundingBox().Intersects( myBBox ) )
1335 continue;
1336
1337 MarkDirty();
1338 return;
1339 }
1340}
1341
1342
1344{
1345 BOARD_COMMIT commit( aEditFrame );
1346 GENERATOR_TOOL* genTool = aEditFrame->GetToolManager()->GetTool<GENERATOR_TOOL>();
1347
1348 commit.Modify( this );
1349
1350 DIALOG_VIA_STITCH_PROPERTIES dlg( aEditFrame, this );
1351
1352 if( dlg.ShowModal() != wxID_OK )
1353 return;
1354
1355 EditStart( genTool, GetBoard(), &commit );
1356 Update( genTool, GetBoard(), &commit );
1357 EditFinish( genTool, GetBoard(), &commit );
1358
1359 commit.Push( _( "Edit Via Stitching" ) );
1360}
1361
1362
1364{
1365 if( usesGridCells() )
1366 m_excludedCells.insert( cellForPosition( aPos ) );
1367 else
1368 m_excludedPositions.insert( aPos );
1369
1370 MarkDirty();
1371}
1372
1373
1375{
1376 if( usesGridCells() )
1377 m_excludedCells.erase( cellForPosition( aPos ) );
1378 else
1379 m_excludedPositions.erase( aPos );
1380
1381 MarkDirty();
1382}
1383
1384
1386{
1387 m_excludedCells.clear();
1388 m_excludedPositions.clear();
1389 MarkDirty();
1390}
1391
1392
1394{
1395 if( m_netCode == 0 && !m_lastNetName.empty() )
1396 {
1397 if( const BOARD* board = GetBoard() )
1398 {
1399 if( NETINFO_ITEM* net = board->FindNet( m_lastNetName ) )
1400 m_netCode = net->GetNetCode();
1401 }
1402 }
1403
1404 return m_netCode;
1405}
1406
1407
1409{
1410 if( m_guardedNetCode == 0 && !m_lastGuardedNetName.empty() )
1411 {
1412 if( const BOARD* board = GetBoard() )
1413 {
1414 if( NETINFO_ITEM* net = board->FindNet( m_lastGuardedNetName ) )
1415 m_guardedNetCode = net->GetNetCode();
1416 }
1417 }
1418
1419 return m_guardedNetCode;
1420}
1421
1422
1423void PCB_VIA_STITCH::SetNetCode( int aNetCode )
1424{
1425 m_netCode = aNetCode;
1426 if( BOARD* board = GetBoard() )
1427 {
1428 if( NETINFO_ITEM* net = board->FindNet( aNetCode ) )
1429 m_lastNetName = net->GetNetname();
1430 else
1431 m_lastNetName.clear();
1432 }
1433
1434 for( BOARD_ITEM* item : GetBoardItems() )
1435 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
1436 bci->SetNetCode( aNetCode );
1437
1438 MarkDirty();
1439}
1440
1441
1443{
1444 m_guardedNetCode = aNetCode;
1445
1446 if( BOARD* board = GetBoard() )
1447 {
1448 if( NETINFO_ITEM* net = board->FindNet( aNetCode ) )
1449 m_lastGuardedNetName = net->GetNetname();
1450 else
1451 m_lastGuardedNetName.clear();
1452 }
1453
1454 MarkDirty();
1455}
1456
1457
1458std::vector<ZONE*> PCB_VIA_STITCH::collectAllZones( const BOARD* aBoard )
1459{
1460 std::vector<ZONE*> zones;
1461
1462 if( !aBoard )
1463 return zones;
1464
1465 zones.reserve( aBoard->Zones().size() );
1466
1467 for( ZONE* zone : aBoard->Zones() )
1468 zones.push_back( zone );
1469
1470 for( FOOTPRINT* footprint : aBoard->Footprints() )
1471 {
1472 for( ZONE* zone : footprint->Zones() )
1473 zones.push_back( zone );
1474 }
1475
1476 return zones;
1477}
1478
1479
1480std::vector<VECTOR2I>
1482 const SHAPE_POLY_SET& aGuarded, int aPitch,
1483 const std::function<bool( const VECTOR2I& )>& aIsValid )
1484{
1485 std::vector<VECTOR2I> accepted;
1486
1487 if( aPitch <= 0 )
1488 return accepted;
1489
1490 // Keeps adjacent same-side vias from doubling up where the walk rounds a corner or an
1491 // end cap. Arc length will report back a larger than expected value in this case.
1492 const int64_t minDistSq = (int64_t) ( aPitch * 0.7 ) * (int64_t) ( aPitch * 0.7 );
1493
1494 auto farEnough =
1495 [&]( const VECTOR2I& pt ) -> bool
1496 {
1497 for( const VECTOR2I& other : accepted )
1498 {
1499 VECTOR2I d = other - pt;
1500
1501 if( (int64_t) d.x * d.x + (int64_t) d.y * d.y >= minDistSq )
1502 continue;
1503
1504 if( aGuarded.Collide( SEG( pt, other ) ) )
1505 continue;
1506
1507 return false;
1508 }
1509
1510 return true;
1511 };
1512
1513 auto walkChain =
1514 [&]( const SHAPE_LINE_CHAIN& chain )
1515 {
1516 double cursor = 0.0;
1517 double nextSample = aPitch / 2.0;
1518
1519 for( int i = 0; i < chain.SegmentCount(); ++i )
1520 {
1521 SEG seg = chain.CSegment( i );
1522 double segLen = ( VECTOR2D( seg.B ) - VECTOR2D( seg.A ) ).EuclideanNorm();
1523
1524 if( segLen < 1.0 )
1525 continue;
1526
1527 while( nextSample <= cursor + segLen )
1528 {
1529 double t = ( nextSample - cursor ) / segLen;
1530 VECTOR2I pt( (int) std::round( seg.A.x + t * ( seg.B.x - seg.A.x ) ),
1531 (int) std::round( seg.A.y + t * ( seg.B.y - seg.A.y ) ) );
1532
1533 nextSample += aPitch;
1534
1535 if( aIsValid( pt ) && farEnough( pt ) )
1536 accepted.push_back( pt );
1537 }
1538
1539 cursor += segLen;
1540 }
1541 };
1542
1543 for( int o = 0; o < aEnvelope.OutlineCount(); ++o )
1544 walkChain( aEnvelope.COutline( o ) );
1545
1546 return accepted;
1547}
1548
1549
1551{
1553 {
1560
1561 propMgr.AddProperty( new PROPERTY<PCB_VIA_STITCH, int>( _HKI( "Size" ),
1563
1564 propMgr.AddProperty( new PROPERTY<PCB_VIA_STITCH, int>( _HKI( "Drill" ),
1566
1567 propMgr.AddProperty( new PROPERTY<PCB_VIA_STITCH, int>( _HKI( "Pitch" ),
1569
1571 .Undefined( PCB_VIA_STITCH_LAYOUT::PLAIN )
1572 .Map( PCB_VIA_STITCH_LAYOUT::PLAIN, _HKI( "Plain grid" ) )
1573 .Map( PCB_VIA_STITCH_LAYOUT::STAGGERED, _HKI( "Staggered grid" ) )
1574 .Map( PCB_VIA_STITCH_LAYOUT::POISSON, _HKI( "Poisson disk" ) );
1575
1578 .SetAvailableFunc( []( INSPECTABLE* aItem ) -> bool
1579 {
1580 if( PCB_VIA_STITCH* stitch = dynamic_cast<PCB_VIA_STITCH*>( aItem ) )
1581 return stitch->GetMode() == PCB_VIA_STITCH_MODE::STITCH;
1582
1583 return true;
1584 } );
1585
1587 .Undefined( PCB_VIA_STITCH_MODE::STITCH )
1588 .Map( PCB_VIA_STITCH_MODE::STITCH, _HKI( "Stitch" ) )
1589 .Map( PCB_VIA_STITCH_MODE::GUARD, _HKI( "Guard" ) );
1590
1593
1594 propMgr.AddProperty( new PROPERTY<PCB_VIA_STITCH, uint32_t>( _HKI( "Seed" ),
1596 .SetAvailableFunc( []( INSPECTABLE* aItem ) -> bool
1597 {
1598 if( PCB_VIA_STITCH* stitch = dynamic_cast<PCB_VIA_STITCH*>( aItem ) )
1599 {
1600 return stitch->GetMode() == PCB_VIA_STITCH_MODE::STITCH
1601 && stitch->GetLayout() == PCB_VIA_STITCH_LAYOUT::POISSON;
1602 }
1603 return true;
1604 } );
1605
1608
1609 propMgr.AddProperty( new PROPERTY_ENUM<PCB_VIA_STITCH, int>( _HKI( "Guarded Net" ),
1611 .SetAvailableFunc( []( INSPECTABLE* aItem ) -> bool
1612 {
1613 if( PCB_VIA_STITCH* stitch = dynamic_cast<PCB_VIA_STITCH*>( aItem ) )
1614 return stitch->GetMode() == PCB_VIA_STITCH_MODE::GUARD;
1615
1616 return true;
1617 } );
1618
1619 // The stitch lives on its own GAL layer and the vias are multi-layer configured separately
1620 propMgr.Mask( TYPE_HASH( PCB_VIA_STITCH ), TYPE_HASH( BOARD_ITEM ), _HKI( "Layer" ) );
1621 }
1623
1624
1627
1628
int index
@ ERROR_OUTSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
Define the structure of a menu based on ACTIONs.
Definition action_menu.h:43
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
Container for design settings for a BOARD object.
std::shared_ptr< DRC_ENGINE > m_DRCEngine
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
BOARD_ITEM & operator=(const BOARD_ITEM &aOther)
Definition board_item.h:103
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
NETINFO_ITEM * FindNet(int aNetcode) const
Search for a net with the given netcode.
Definition board.cpp:2980
const ZONES & Zones() const
Definition board.h:467
int GetCopperLayerCount() const
Definition board.cpp:1131
const FOOTPRINTS & Footprints() const
Definition board.h:463
const TRACKS & Tracks() const
Definition board.h:461
std::shared_ptr< DRC_RTREE > m_CopperItemRTreeCache
Definition board.h:1833
int m_DRCMaxClearance
Definition board.h:1858
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1299
constexpr const Vec & GetPosition() const
Definition box2.h:208
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr coord_type GetY() const
Definition box2.h:205
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr coord_type GetX() const
Definition box2.h:204
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:165
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
int ShowModal() override
Modal properties editor for a PCB_VIA_STITCH generator.
virtual bool Run() override
Discard the board's run-time DRC caches and regenerate them from scratch.
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:200
Design Rule Checker object that performs all the DRC tests.
Definition drc_engine.h:129
DRC_CONSTRAINT EvalRules(DRC_CONSTRAINT_T aConstraintType, const BOARD_ITEM *a, const BOARD_ITEM *b, PCB_LAYER_ID aLayer, REPORTER *aReporter=nullptr)
bool CheckColliding(SHAPE *aRefShape, PCB_LAYER_ID aTargetLayer, int aClearance=0, std::function< bool(BOARD_ITEM *)> aFilter=nullptr) const
Definition drc_rtree.h:186
void SetDRCEngine(DRC_ENGINE *engine)
A set of EDA_ITEMs (i.e., without duplicates).
Definition eda_group.h:43
wxString m_name
Definition eda_group.h:92
void RemoveItem(EDA_ITEM *aItem)
Remove item from group.
Definition eda_group.cpp:77
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:58
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:158
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition eda_item.h:160
bool IsSelected() const
Definition eda_item.h:134
EDA_ITEM_FLAGS GetFlags() const
Definition eda_item.h:167
bool IsNew() const
Definition eda_item.h:131
static ENUM_MAP< T > & Instance()
Definition property.h:770
Handle actions specific to filling copper zones.
Class that other classes need to inherit from, in order to be inspectable.
Definition inspectable.h:39
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
Abstract interface for drawing on a 2D-surface.
virtual void SetIsFill(bool aIsFillEnabled)
Enable/disable fill.
virtual void SetLineWidth(float aLineWidth)
Set the line width.
virtual void DrawPolyline(const std::deque< VECTOR2D > &aPointList)
Draw a polyline.
virtual void SetStrokeColor(const COLOR4D &aColor)
Set the stroke color.
virtual void SetIsStroke(bool aIsStrokeEnabled)
Enable/disable stroked outlines.
virtual void DrawLine(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint)
Draw a line.
virtual RENDER_SETTINGS * GetSettings()=0
Return a pointer to current settings that are going to be used when drawing items.
Container for all the knowledge about how graphical objects are drawn on any output surface/device.
virtual COLOR4D GetColor(const VIEW_ITEM *aItem, int aLayer) const =0
Returns the color that should be used to draw the specific VIEW_ITEM on the specific layer using curr...
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition view.h:63
GAL * GetGAL() const
Return the GAL this view is using to draw graphical primitives.
Definition view.h:207
PAINTER * GetPainter() const
Return the painter object used by the view for drawing #VIEW_ITEMS.
Definition view.h:225
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
T Min() const
Definition minoptmax.h:29
Handle the data for a net.
Definition netinfo.h:50
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
static constexpr PCB_LAYER_ID TEMP_ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:176
Definition pad.h:61
static TOOL_ACTION excludeStitchVia
Exclude selected stitching vias from their parent via-stitch generator.
Common, abstract interface for edit frames.
PCB_GENERATOR_POLY(BOARD_ITEM *aParent, PCB_LAYER_ID aLayer)
SHAPE_POLY_SET m_outline
virtual void SetProperties(const STRING_ANY_MAP &aProps)
wxString m_generatorType
VECTOR2I m_origin
virtual const STRING_ANY_MAP GetProperties() const
std::unordered_set< BOARD_ITEM * > GetBoardItems() const
void ViewDraw(int aLayer, KIGFX::VIEW *aView) const override final
Draw the parts of the object belonging to layer aLayer.
PCB_VIA_STITCH_MODE m_mode
int GetViaSize() const
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
~PCB_VIA_STITCH() override
static std::vector< VECTOR2I > SampleGuardEnvelope(const SHAPE_POLY_SET &aEnvelope, const SHAPE_POLY_SET &aGuarded, int aPitch, const std::function< bool(const VECTOR2I &)> &aIsValid)
Determine via positions around the perimeter of a guard envelope.
void EditCancel(GENERATOR_TOOL *, BOARD *, BOARD_COMMIT *) override
void SetTemplateItem(const wxString &aName, std::unique_ptr< BOARD_ITEM > aItem) override
Insert a template item (from board file loading)
int GetViaDrill() const
std::set< VECTOR2I > m_excludedPositions
void SetGuardedNetCode(int aNetCode)
int GetNetCode() const
void SetMode(PCB_VIA_STITCH_MODE aVal)
void EditStart(GENERATOR_TOOL *, BOARD *, BOARD_COMMIT *) override
bool usesGridCells() const
PCB_VIA_STITCH_LAYOUT GetLayout() const
std::vector< ZONE * > GetZonesNeedingRefillAfterUpdate() const override
List of zones that cross out stitch zone, they'll need to be refilled for the punches.
void SetViaSize(int aVal)
std::unique_ptr< PCB_VIA > m_viaTemplate
void SetPitch(int aVal)
PCB_VIA_STITCH & operator=(const PCB_VIA_STITCH &aOther)
std::vector< std::pair< wxString, const BOARD_ITEM * > > GetTemplateItems() const override
Named template items used by the generator.
PCB_VIA_STITCH(BOARD_ITEM *aParent=nullptr)
std::unique_ptr< ACTION_MENU > m_childContextMenu
static constexpr uint32_t POISSON_TILE_SEED
Fixed seed for baking the toroidal Poisson pattern.
int defaultPitch(BOARD *aBoard) const
void ShowPropertiesDialog(PCB_BASE_EDIT_FRAME *aEditFrame) override
void ClearAllExclusions()
Drop all manual exclusions.
VECTOR2I cellForPosition(const VECTOR2I &aPos) const
Convert an absolute board position to a (col, row) grid cell index.
void ClearExclusion(const VECTOR2I &aPos)
Remove a board position from the exclusion list for the current layout/mode.
ACTION_MENU * GetChildContextMenu(TOOL_INTERACTIVE *aTool) const override
Get a context menu when interacting with a generator child.
const STRING_ANY_MAP GetProperties() const override
void SetViaDrill(int aVal)
PCB_VIA_STITCH_MODE GetMode() const
std::set< VECTOR2I > buildPlacementCells(BOARD *aBoard) const
Walk the placement grid, run DRC clearance tests, and return the set of (col, row) cells where a via ...
static std::vector< ZONE * > collectAllZones(const BOARD *aBoard)
Grabs all zones we need to consider for via placement.
static const std::vector< VECTOR2D > & bakedPoissonTile()
Helper method to cache the poisson tile we pattern.
VECTOR2I GetPosition() const override
static constexpr int POISSON_TILE_PITCHES
Tile size for the POISSON layout, in units of pitch.
VECTOR2I m_originOffset
bool Update(GENERATOR_TOOL *, BOARD *, BOARD_COMMIT *) override
int defaultViaSize(BOARD *aBoard) const
int GetGuardedNetCode() const
Net being guarded in GUARD mode.
void SetNetCode(int aNetCode)
static const wxString GENERATOR_TYPE
int GetPitch() const
void EditFinish(GENERATOR_TOOL *, BOARD *, BOARD_COMMIT *) override
int defaultViaDrill(BOARD *aBoard) const
std::set< PCB_VIA * > m_pendingRemovals
void Remove(GENERATOR_TOOL *, BOARD *, BOARD_COMMIT *) override
static const wxString DISPLAY_NAME
wxString m_lastGuardedNetName
uint32_t GetSeed() const
void SetProperties(const STRING_ANY_MAP &aProps) override
void SetLayout(PCB_VIA_STITCH_LAYOUT aVal)
PCB_VIA_STITCH_LAYOUT m_layout
void ExcludePosition(const VECTOR2I &aPos)
Add a board position to the exclusion set so the next Update() skips it.
void OnZoneFillChanged(const std::vector< ZONE * > &aZones) override
Callback to be informed zones have changed.
wxString m_lastNetName
void SetSeed(uint32_t aVal)
GRID_CONFIG m_childGridConfig
void InitializeDefaults(BOARD *aBoard)
Fill in any unset via-template and pitch values from the board's design settings.
std::set< VECTOR2I > m_excludedCells
VECTOR2I positionForCell(const VECTOR2I &aCell, int aPitch) const
Convert a (col, row) grid cell index to an absolute board position, including any stagger shift on od...
PROPERTY_BASE & SetAvailableFunc(std::function< bool(INSPECTABLE *)> aFunc)
Set a callback function to determine whether an object provides this property.
Definition property.h:263
Provide class metadata.Helper macro to map type hashes to names.
void InheritsAfter(TYPE_ID aDerived, TYPE_ID aBase)
Declare an inheritance relationship between types.
void Mask(TYPE_ID aDerived, TYPE_ID aBase, const wxString &aName)
Sets a base class property as masked in a derived class.
static PROPERTY_MANAGER & Instance()
PROPERTY_BASE & AddProperty(PROPERTY_BASE *aProperty, const wxString &aGroup=wxEmptyString)
Register a property.
void AddTypeCast(TYPE_CAST_BASE *aCast)
Register a type converter.
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
Represent a set of closed polygons.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void Deflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError)
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
void BuildBBoxCaches() const
Construct BBoxCaches for Contains(), below.
int OutlineCount() const
Return the number of outlines in the set.
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
A name/value tuple with unique names and wxAny values.
void set_iu(const std::string &aKey, const T &aVar)
bool get_to(const std::string &aKey, T &aVar) const
std::optional< T > get_opt(const std::string &aKey) const
void set(const std::string &aKey, const T &aVar)
bool get_to_iu(const std::string &aKey, T &aVar) const
TOOL_MANAGER * GetToolManager() const
Return the MVC controller.
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:279
Handle a list of polygons defining a copper zone.
Definition zone.h:70
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a oblong shape to a polygon, using multiple segments.
@ ROUND_ALL_CORNERS
All angles are rounded.
DRC_CONSTRAINT_T
Definition drc_rule.h:49
@ CLEARANCE_CONSTRAINT
Definition drc_rule.h:51
@ HOLE_CLEARANCE_CONSTRAINT
Definition drc_rule.h:53
@ HOLE_TO_HOLE_CONSTRAINT
Definition drc_rule.h:54
#define _(s)
#define IN_EDIT
Item currently edited.
@ LAYER_VIA_STITCHING
Outline of via stitching generators.
Definition layer_ids.h:326
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ F_Cu
Definition layer_ids.h:60
std::vector< VECTOR2D > ToroidalUnitTile(double aMinDist, uint32_t aSeed)
Bridson's "Fast Poisson Disk Sampling in Arbitrary Dimensions" with toroidal boundary conditions on t...
STL namespace.
#define _HKI(x)
Definition page_info.cpp:40
static GENERATORS_MGR::REGISTER< PCB_TUNING_PATTERN > registerMe
static struct PCB_VIA_STITCH_DESC _PCB_VIA_STITCH_DESC
PCB_VIA_STITCH_MODE
@ STITCH
Fill the outline with vias with a pattern.
@ GUARD
Place vias to guard a net contained within.
PCB_VIA_STITCH_LAYOUT
@ POISSON
Tiled poisson distribution.
@ STAGGERED
Odd rows shifted by half the pitch.
@ PLAIN
Regular row/column grid.
#define TYPE_HASH(x)
Definition property.h:74
#define ENUM_TO_WXANY(type)
Macro to define read-only fields (no setter method available)
Definition property.h:877
@ PT_SIZE
Size expressed in distance units (mm/inch)
Definition property.h:63
@ PT_NET
Net selection property.
Definition property.h:70
#define REGISTER_TYPE(x)
Static helper to register a generator.
Structure to hold the necessary information in order to index a vertex on a SHAPE_POLY_SET object: th...
const SHAPE_LINE_CHAIN chain
wxString result
Test unit parsing edge cases and error handling.
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682