KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pns_block_dragger.cpp
Go to the documentation of this file.
1/*
2 * KiRouter - a push-and-(sometimes-)shove PCB router
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <memory>
21
22#include "pns_arc.h"
23#include "pns_joint.h"
24#include "pns_line.h"
25#include "pns_node.h"
26#include "pns_segment.h"
27#include "pns_solid.h"
28#include "pns_via.h"
29#include "pns_router.h"
30
31#include "pns_block_dragger.h"
32#include "pns_debug_decorator.h"
33
34namespace PNS
35{
36
38 DRAG_ALGO( aRouter ),
39 m_dragStatus( false ),
40 m_currentNode( nullptr ),
42{
43}
44
45
49
50
51bool BLOCK_DRAGGER::Start( const VECTOR2I& aP, ITEM_SET& aPrimitives )
52{
53 assert( m_world );
54
55 m_currentNode = nullptr;
56 m_p0 = aP;
57 m_rigidItems.clear();
58 m_bridgeItems.clear();
59 m_conns.clear();
60 m_anchoredLines.clear();
61 m_selectedLinks.clear();
62 m_seenLinks.clear();
63 m_carriedJoints.clear();
64
65 for( ITEM* item : aPrimitives.Items() )
66 {
67 if( item->OfKind( ITEM::SOLID_T | ITEM::VIA_T ) )
68 m_rigidItems.insert( item );
69 else if( item->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) )
70 m_selectedLinks.insert( item );
71 }
72
73 for( ITEM* item : aPrimitives.Items() )
74 {
75 if( m_selectedLinks.count( item ) )
76 classifySelectedLink( static_cast<LINKED_ITEM*>( item ) );
77 }
78
79 for( ITEM* item : aPrimitives.Items() )
80 {
81 if( m_rigidItems.count( item ) )
82 scanRigidItem( item );
83 }
84
85 for( const JOINT* joint : m_carriedJoints )
86 scanJoint( joint );
87
88 return !m_rigidItems.empty() || !m_bridgeItems.empty() || !m_conns.empty() || !m_anchoredLines.empty();
89}
90
91
93{
94 // Locked copper the user selected still moves
95 return aItem->IsLocked() && !m_selectedLinks.count( aItem ) && !m_rigidItems.count( aItem );
96}
97
98
99bool BLOCK_DRAGGER::isPinned( const JOINT* aJoint, const LINE& aOwnLine ) const
100{
101 for( ITEM* link : aJoint->LinkList() )
102 {
103 // Virtual vias only mark width changes, T junctions and locked ends
104 if( link->IsVirtual() || m_rigidItems.count( link ) )
105 continue;
106
107 if( link->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T )
108 && aOwnLine.ContainsLink( static_cast<LINKED_ITEM*>( link ) ) )
109 {
110 continue;
111 }
112
113 if( link->OfKind( ITEM::VIA_T | ITEM::SOLID_T ) || isFixedByLock( link ) )
114 return true;
115 }
116
117 return coveredBySolid( aJoint, false );
118}
119
120
121bool BLOCK_DRAGGER::coveredBySolid( const JOINT* aJoint, bool aSelected ) const
122{
123 const ITEM_SET hits = m_world->HitTest( aJoint->Pos() );
124
125 for( const ITEM* item : hits.CItems() )
126 {
127 if( !item->OfKind( ITEM::SOLID_T | ITEM::VIA_T ) )
128 continue;
129
130 if( ( m_rigidItems.count( const_cast<ITEM*>( item ) ) != 0 ) != aSelected )
131 continue;
132
133 if( item->Net() == aJoint->Net() && item->Layers().Overlaps( aJoint->Layers() ) )
134 return true;
135 }
136
137 return false;
138}
139
140
141bool BLOCK_DRAGGER::isHeldByBlock( const JOINT* aJoint ) const
142{
143 for( ITEM* link : aJoint->LinkList() )
144 {
145 if( !link->IsVirtual() && m_rigidItems.count( link ) )
146 return true;
147 }
148
149 return coveredBySolid( aJoint, true );
150}
151
152
153bool BLOCK_DRAGGER::isBlockBoundary( const JOINT* aJoint ) const
154{
155 if( !aJoint )
156 return false;
157
158 if( m_carriedJoints.count( aJoint ) )
159 return true;
160
161 for( ITEM* link : aJoint->LinkList() )
162 {
163 if( !link->IsVirtual() && ( m_selectedLinks.count( link ) || m_rigidItems.count( link ) ) )
164 return true;
165 }
166
167 return false;
168}
169
170
172{
173 LINE line;
174 line.SetWidth( aLink->Width() );
175 line.SetLayers( aLink->Layers() );
176 line.SetNet( aLink->Net() );
177 line.SetSourceItem( aLink->GetSourceItem() );
178
179 if( aLink->Kind() == ITEM::ARC_T )
180 {
182 chain.Append( static_cast<ARC*>( aLink )->Arc() );
183 line.SetShape( chain );
184 }
185 else
186 {
187 line.SetShape( SHAPE_LINE_CHAIN( { aLink->Anchor( 0 ), aLink->Anchor( 1 ) } ) );
188 }
189
190 line.Link( aLink );
191 return line;
192}
193
194
196{
197 if( m_seenLinks.count( aLink ) )
198 return;
199
200 m_seenLinks.insert( aLink );
201
202 // Wrapped on its own, so no unselected copper is assembled into it
203 LINE line = wrapLink( aLink );
204
205 const VECTOR2I& start = line.CLine().CPoint( 0 );
206 const VECTOR2I& end = line.CLine().CLastPoint();
207
208 const JOINT* jointA = m_world->FindJoint( start, aLink->Layer(), aLink->Net() );
209 const JOINT* jointB = m_world->FindJoint( end, aLink->Layer(), aLink->Net() );
210
211 bool carriedA = !jointA || !isPinned( jointA, line );
212 bool carriedB = !jointB || !isPinned( jointB, line );
213
214 if( carriedA && jointA )
215 m_carriedJoints.insert( jointA );
216
217 if( carriedB && jointB )
218 m_carriedJoints.insert( jointB );
219
220 if( carriedA && carriedB )
221 m_bridgeItems.insert( aLink );
222 else if( carriedA || carriedB )
223 m_conns.push_back( { line, carriedA ? start : end } );
224 else if( aLink->Kind() == ITEM::ARC_T )
225 m_anchoredLines.push_back( line ); // an arc has no body drag
226 else
227 m_conns.push_back( { line, ( start + end ) / 2, true } );
228}
229
230
231void BLOCK_DRAGGER::clipToBlock( LINE& aLine, const VECTOR2I& aEntry, LINKED_ITEM* aLink ) const
232{
233 const SHAPE_LINE_CHAIN& chain = aLine.CLine();
234 const int last = chain.PointCount() - 1;
235 const int entry = chain.Find( aEntry );
236 const VECTOR2I& otherEnd = aLink->Anchor( 0 ) == aEntry ? aLink->Anchor( 1 ) : aLink->Anchor( 0 );
237 const int other = chain.Find( otherEnd );
238
239 if( entry < 0 || other < 0 || other == entry || last < 1 )
240 {
241 wxFAIL;
242 return;
243 }
244
245 // Keep the side of the entry that aLink is on, up to the first block joint
246 int lo = 0;
247 int hi = last;
248
249 if( other > entry )
250 {
251 lo = entry;
252
253 for( hi = entry + 1; hi < last; hi++ )
254 {
255 if( isBlockBoundary( m_world->FindJoint( chain.CPoint( hi ), &aLine ) ) )
256 break;
257 }
258 }
259 else
260 {
261 hi = entry;
262
263 for( lo = entry - 1; lo > 0; lo-- )
264 {
265 if( isBlockBoundary( m_world->FindJoint( chain.CPoint( lo ), &aLine ) ) )
266 break;
267 }
268 }
269
270 if( lo == 0 && hi == last )
271 return;
272
273 // ClipVertexRange() keeps the right links only when the kept range starts at 0
274 if( hi < last )
275 aLine.ClipVertexRange( 0, hi );
276
277 if( lo > 0 )
278 {
279 aLine.Reverse();
280 aLine.ClipVertexRange( 0, hi - lo );
281 aLine.Reverse();
282 }
283}
284
285
287{
288 if( m_seenLinks.count( aLink ) || isFixedByLock( aLink ) )
289 return;
290
291 m_seenLinks.insert( aLink );
292
293 LINE line = m_world->AssembleLine( aLink );
294 clipToBlock( line, aJoint->Pos(), aLink );
295
296 for( LINKED_ITEM* lineLink : line.Links() )
297 m_seenLinks.insert( lineLink );
298
299 const VECTOR2I& start = line.CLine().CPoint( 0 );
300 const VECTOR2I& end = line.CLine().CLastPoint();
301 const JOINT* farJoint = m_world->FindJoint( start == aJoint->Pos() ? end : start, aLink );
302
303 bool farCarried = false;
304
305 if( farJoint )
306 farCarried = m_carriedJoints.count( farJoint ) || ( isHeldByBlock( farJoint ) && !isPinned( farJoint, line ) );
307
308 if( farCarried )
309 {
310 for( LINKED_ITEM* lineLink : line.Links() )
311 m_bridgeItems.insert( lineLink );
312 }
313 else
314 {
315 m_conns.push_back( { line, aJoint->Pos() } );
316 }
317}
318
319
320void BLOCK_DRAGGER::scanJoint( const JOINT* aJoint )
321{
322 if( !aJoint )
323 return;
324
325 for( ITEM* link : aJoint->LinkList() )
326 {
327 if( link->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) )
328 classifyNeighbour( aJoint, static_cast<LINKED_ITEM*>( link ) );
329 }
330}
331
332
334{
335 if( aItem->Kind() == ITEM::VIA_T )
336 {
337 scanJoint( m_world->FindJoint( static_cast<VIA*>( aItem )->Pos(), aItem ) );
338 return;
339 }
340
341 SOLID* solid = static_cast<SOLID*>( aItem );
342
343 if( !solid->IsRoutable() )
344 return;
345
346 scanJoint( m_world->FindJoint( solid->Pos(), solid ) );
347
348 // A track ending on the pad away from its centre follows too
349 std::vector<JOINT*> joints;
350 m_world->QueryJoints( solid->Hull().BBox(), joints, solid->Layers(), ITEM::SEGMENT_T | ITEM::ARC_T );
351
352 for( JOINT* joint : joints )
353 {
354 if( joint->Net() != solid->Net() || joint->LinkCount() != 1 )
355 continue;
356
357 LINKED_ITEM* link = static_cast<LINKED_ITEM*>( joint->LinkList().front() );
358
359 if( const SHAPE* padShape = solid->Shape( solid->Layer() ) )
360 {
361 if( padShape->Collide( joint->Pos() ) )
362 classifyNeighbour( joint, link );
363 }
364 }
365}
366
367
368void BLOCK_DRAGGER::displaceItem( ITEM* aItem, const VECTOR2I& aDelta )
369{
370 switch( aItem->Kind() )
371 {
372 case ITEM::SOLID_T:
373 {
374 SOLID* solid = static_cast<SOLID*>( aItem );
375 std::unique_ptr<SOLID> clone( static_cast<SOLID*>( solid->Clone() ) );
376
377 clone->SetPos( solid->Pos() + aDelta );
378 m_currentNode->Remove( aItem );
379 m_draggedItems.Add( clone.get() );
380 m_currentNode->Add( std::move( clone ) );
381 break;
382 }
383
384 case ITEM::VIA_T:
385 {
386 VIA* via = static_cast<VIA*>( aItem );
387 std::unique_ptr<VIA> clone( static_cast<VIA*>( via->Clone() ) );
388
389 clone->SetPos( via->Pos() + aDelta );
390 m_currentNode->Remove( aItem );
391 m_draggedItems.Add( clone.get() );
392 m_currentNode->Add( std::move( clone ) );
393 break;
394 }
395
396 // Add() destroys a clone it refuses, so keep the pointer only once it is in. Redundant
397 // copper is allowed, or one of two coincident selected tracks vanishes on commit.
398 case ITEM::SEGMENT_T:
399 {
400 SEGMENT* seg = static_cast<SEGMENT*>( aItem );
401 std::unique_ptr<SEGMENT> clone( seg->Clone() );
402 SEGMENT* raw = clone.get();
403
404 SEG orig = seg->Seg();
405 clone->SetEnds( orig.A + aDelta, orig.B + aDelta );
406 m_currentNode->Remove( aItem );
407
408 if( m_currentNode->Add( std::move( clone ), true ) )
409 m_draggedItems.Add( raw );
410
411 break;
412 }
413
414 case ITEM::ARC_T:
415 {
416 ARC* arc = static_cast<ARC*>( aItem );
417 std::unique_ptr<ARC> clone( arc->Clone() );
418 ARC* raw = clone.get();
419
420 clone->Arc().Move( aDelta );
421 m_currentNode->Remove( aItem );
422
423 if( m_currentNode->Add( std::move( clone ), true ) )
424 m_draggedItems.Add( raw );
425
426 break;
427 }
428
429 default: wxFAIL; break;
430 }
431}
432
433
435{
436 assert( m_world );
437
438 m_world->KillChildren();
439 m_currentNode = m_world->Branch();
440 m_draggedItems.Clear();
441
442 const VECTOR2I delta = aP - m_p0;
443
444 for( ITEM* rigid : m_rigidItems )
445 displaceItem( rigid, delta );
446
447 for( ITEM* bridge : m_bridgeItems )
448 displaceItem( bridge, delta );
449
450 bool freeAngle = ( m_mode & DM_FREE_ANGLE ) != 0;
451
452 for( DRAGGED_CONNECTION& cn : m_conns )
453 {
454 LINE l_new( cn.origLine );
455 l_new.Unmark();
456 l_new.ClearLinks();
457
458 if( cn.dragBody )
459 l_new.DragSegment( cn.p_orig + delta, 0, false );
460 else
461 l_new.DragCorner( cn.p_orig + delta, l_new.CLine().Find( cn.p_orig ), freeAngle );
462
463 PNS_DBG( Dbg(), AddItem, &l_new, BLUE, 0, wxT( "bdrag-new-fanout" ) );
464 m_draggedItems.Add( l_new );
465
466 LINE l_orig( cn.origLine );
467 m_currentNode->Remove( l_orig );
468
469 // Redundant copper is allowed here for the same reason as in displaceItem()
470 m_currentNode->Add( l_new, true );
471 }
472
473 m_dragStatus = !m_currentNode->CheckColliding( m_draggedItems );
474
475 return true;
476}
477
478
479bool BLOCK_DRAGGER::FixRoute( bool aForceCommit )
480{
481 NODE* node = CurrentNode();
482
483 if( node )
484 {
485 if( Settings().AllowDRCViolations() || aForceCommit || !node->CheckColliding( m_draggedItems ) )
486 {
487 Router()->CommitRouting( node );
488 return true;
489 }
490 }
491
492 return false;
493}
494
495
500
501
503{
504 return m_draggedItems;
505}
506
507}; // namespace PNS
ROUTER * Router() const
Return the instance of our router.
ROUTING_SETTINGS & Settings() const
Return current router settings.
DEBUG_DECORATOR * Dbg() const
ARC * Clone() const override
Return a deep copy of the item.
Definition pns_arc.cpp:34
void clipToBlock(LINE &aLine, const VECTOR2I &aEntry, LINKED_ITEM *aLink) const
Cut an assembled line back to the stretch between aEntry and the first block joint.
BLOCK_DRAGGER(ROUTER *aRouter)
std::set< const JOINT * > m_carriedJoints
Ends of selected copper that travel.
bool Start(const VECTOR2I &aP, ITEM_SET &aPrimitives) override
Sort the selection and the unselected copper touching it.
bool isBlockBoundary(const JOINT *aJoint) const
Copper at this joint is already sorted, so a walk stops here.
void scanRigidItem(ITEM *aItem)
Sort the copper touching a selected pad or via.
bool isHeldByBlock(const JOINT *aJoint) const
A selected pad or via sits on this joint.
bool isPinned(const JOINT *aJoint, const LINE &aOwnLine) const
Held in place by something outside the block. Links of aOwnLine do not count.
std::vector< DRAGGED_CONNECTION > m_conns
Copper rerouted to follow the block.
std::unordered_set< ITEM * > m_selectedLinks
Selected segments and arcs.
std::vector< LINE > m_anchoredLines
Selected arcs that cannot move.
static LINE wrapLink(LINKED_ITEM *aLink)
A one link line, so no unselected copper is assembled into a selected segment.
void classifyNeighbour(const JOINT *aJoint, LINKED_ITEM *aLink)
Sort unselected copper that touches the block at aJoint.
std::set< ITEM * > m_bridgeItems
Copper displaced whole with the block.
bool isFixedByLock(ITEM *aItem) const
Unselected and locked, so it neither moves nor is rerouted.
bool Drag(const VECTOR2I &aP) override
Rebuild the block at aP: displace rigid and bridge items, reroute connections.
void displaceItem(ITEM *aItem, const VECTOR2I &aDelta)
Replace aItem in the current branch with a copy moved by aDelta.
NODE * CurrentNode() const override
The current branch, or the world before the first Drag().
std::set< ITEM * > m_rigidItems
Selected pads and vias.
void classifySelectedLink(LINKED_ITEM *aLink)
Sort a selected segment or arc by what holds its ends.
const ITEM_SET Traces() override
Everything displaced or rerouted by the last Drag().
void scanJoint(const JOINT *aJoint)
Sort every unselected segment and arc on a joint.
std::unordered_set< LINKED_ITEM * > m_seenLinks
Already sorted.
bool FixRoute(bool aForceCommit) override
Commit the current branch if it is collision free or aForceCommit is set.
bool coveredBySolid(const JOINT *aJoint, bool aSelected) const
A pad covers more than its anchor, so ask what really sits on the joint.
DRAG_ALGO(ROUTER *aRouter)
std::vector< ITEM * > & Items()
Definition pns_itemset.h:95
const std::vector< ITEM * > & CItems() const
Definition pns_itemset.h:96
Base class for PNS router board items.
Definition pns_item.h:98
void SetLayers(const PNS_LAYER_RANGE &aLayers)
Definition pns_item.h:213
void SetSourceItem(BOARD_ITEM *aSourceItem)
Definition pns_item.h:201
const PNS_LAYER_RANGE & Layers() const
Definition pns_item.h:212
virtual NET_HANDLE Net() const
Definition pns_item.h:210
PnsKind Kind() const
Return the type (kind) of the item.
Definition pns_item.h:173
void SetNet(NET_HANDLE aNet)
Definition pns_item.h:209
BOARD_ITEM * GetSourceItem() const
Definition pns_item.h:202
virtual int Layer() const
Definition pns_item.h:216
bool IsRoutable() const
Definition pns_item.h:288
virtual VECTOR2I Anchor(int n) const
Definition pns_item.h:272
bool IsLocked() const
Definition pns_item.h:282
A 2D point on a given set of layers and belonging to a certain net, that links together a number of b...
Definition pns_joint.h:43
const std::vector< ITEM * > & LinkList() const
Definition pns_joint.h:307
NET_HANDLE Net() const override
Definition pns_joint.h:302
const VECTOR2I & Pos() const
Definition pns_joint.h:297
Represents a track on a PCB, connecting two non-trivial joints (that is, vias, pads,...
Definition pns_line.h:62
void ClipVertexRange(int aStart, int aEnd)
Clip the line to a given range of vertices.
void SetShape(const SHAPE_LINE_CHAIN &aLine)
Assign a shape to the line (a polyline/line chain).
Definition pns_line.h:138
const SHAPE_LINE_CHAIN & CLine() const
Definition pns_line.h:149
void DragCorner(const VECTOR2I &aP, int aIndex, bool aFreeAngle=false, DIRECTION_45 aPreferredEndingDirection=DIRECTION_45())
Definition pns_line.cpp:878
virtual void Unmark(int aMarker=-1) const override
Definition pns_line.cpp:178
void SetWidth(int aWidth)
Set line width.
Definition pns_line.h:162
void DragSegment(const VECTOR2I &aP, int aIndex, bool aFreeAngle=false)
Definition pns_line.cpp:892
void Reverse()
Reverse the point/vertex order.
virtual int Width() const
Keep the router "world" - i.e.
Definition pns_node.h:244
OPT_OBSTACLE CheckColliding(const ITEM *aItem, int aKindMask=ITEM::ANY_T)
Check if the item collides with anything else in the world, and if found, returns the obstacle.
Definition pns_node.cpp:498
void CommitRouting()
const SEG & Seg() const
SEGMENT * Clone() const override
Return a deep copy of the item.
Definition pns_line.cpp:198
const SHAPE_LINE_CHAIN Hull(int aClearance=0, int aWalkaroundThickness=0, int aLayer=-1) const override
Definition pns_solid.cpp:39
const VECTOR2I & Pos() const
Definition pns_solid.h:119
ITEM * Clone() const override
Return a deep copy of the item.
Definition pns_solid.cpp:74
const SHAPE * Shape(int aLayer) const override
Return the geometrical shape of the item.
Definition pns_solid.h:107
const VECTOR2I & Pos() const
Definition pns_via.h:206
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...
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
int Find(const VECTOR2I &aP, int aThreshold=0) const
Search for point aP.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
An abstract shape on 2D plane.
Definition shape.h:124
@ BLUE
Definition color4d.h:52
Push and Shove diff pair dimensions (gap) settings dialog.
@ DM_FREE_ANGLE
Definition pns_router.h:81
@ DM_BLOCK
Definition pns_router.h:85
#define PNS_DBG(dbg, method,...)
const SHAPE_LINE_CHAIN chain
VECTOR2I end
int delta
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708