KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_drag_net_collision.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 * Copyright (C) 2025 VUT Brno, Faculty of Electrical Engineering and Communication
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
22
23#include <trace_helpers.h>
24#include <advanced_config.h>
26#include <schematic.h>
27#include <sch_line.h>
28
29#include <algorithm>
30#include <limits>
31
32#include <eda_item.h>
33#include <sch_connection.h>
34#include <sch_edit_frame.h>
35#include <sch_item.h>
36#include <sch_junction.h>
37#include <sch_screen.h>
38#include <sch_selection.h>
39#include <sch_sheet_path.h>
40#include <view/view.h>
41#include <view/view_overlay.h>
42#include <gal/color4d.h>
43#include <layer_ids.h>
45#include <eeschema_settings.h>
46
58
59
64
65
67{
68 wxLogTrace( traceSchDragNetCollision, "Initialize: Starting initialization" );
69
70 m_itemNetCodes.clear();
72 m_sheetPath = m_frame->GetCurrentSheet();
73 m_hasCollision = false;
74
75 if( ADVANCED_CFG::GetCfg().m_ConnectivityEngine )
76 {
78 }
79 else
80 {
81 const auto record = [&]( SCH_ITEM* item )
82 {
83 recordItemNet( item );
84 item->RunOnChildren( [&]( SCH_ITEM* child ) { recordItemNet( child ); }, RECURSE_MODE::NO_RECURSE );
85 };
86
87 for( SCH_ITEM* item : m_frame->GetScreen()->Items() )
88 record( item );
89
90 for( EDA_ITEM* item : aSelection )
91 record( static_cast<SCH_ITEM*>( item ) );
92 }
93
94 recordOriginalConnections( aSelection );
95}
96
97
98bool SCH_DRAG_NET_COLLISION_MONITOR::Update( const std::vector<SCH_JUNCTION*>& aJunctions,
99 const SCH_SELECTION& aSelection )
100{
101 std::vector<COLLISION_MARKER> markers;
102
103 for( SCH_JUNCTION* junction : aJunctions )
104 {
105 if( auto marker = analyzeJunction( junction, aSelection ) )
106 markers.push_back( *marker );
107 }
108
109 const auto disconnections = collectDisconnectedMarkers( aSelection );
110
111 if( markers.empty() && disconnections.empty() )
112 {
113 clearOverlay();
114 m_hasCollision = false;
115 return false;
116 }
117
118 wxLogTrace( traceSchDragNetCollision, "Update: Drawing %zu collision markers and %zu disconnection markers",
119 markers.size(), disconnections.size() );
120
122 m_overlay->Clear();
123
124 COLOR4D baseColor( 1.0, 0.0, 0.0, 0.8 );
125
126 if( COLOR_SETTINGS* colorSettings = m_frame->GetColorSettings() )
127 {
128 COLOR4D themeColor = colorSettings->GetColor( LAYER_DRAG_NET_COLLISION );
129
130 if( themeColor != COLOR4D::UNSPECIFIED )
131 baseColor = themeColor;
132 }
133
134 double baseAlpha = baseColor.a;
135
136 if( baseAlpha <= 0.0 )
137 baseAlpha = 1.0;
138
139 double fillAlpha = std::clamp( baseAlpha * 0.35, 0.05, 1.0 );
140 double strokeAlpha = std::clamp( baseAlpha, 0.05, 1.0 );
141
142 m_overlay->SetIsFill( true );
143 m_overlay->SetFillColor( baseColor.WithAlpha( fillAlpha ) );
144 m_overlay->SetIsStroke( true );
145 m_overlay->SetStrokeColor( baseColor.WithAlpha( strokeAlpha ) );
146
147 int lineWidthPixels = 4;
148
149 if( EESCHEMA_SETTINGS* cfg = m_frame->eeconfig() )
150 lineWidthPixels = std::max( cfg->m_Selection.drag_net_collision_width, 1 );
151
152 double lineWidth = m_view->ToWorld( lineWidthPixels );
153
154 if( lineWidth <= 0.0 )
155 lineWidth = 1.0;
156
157 m_overlay->SetLineWidth( lineWidth );
158
159 for( const COLLISION_MARKER& marker : markers )
160 m_overlay->Circle( marker.position, marker.radius );
161
162 for( const DISCONNECTION_MARKER& marker : disconnections )
163 {
164 m_overlay->Circle( marker.pointA, marker.radius );
165 m_overlay->Circle( marker.pointB, marker.radius );
166 m_overlay->Line( VECTOR2D( marker.pointA ), VECTOR2D( marker.pointB ) );
167 }
168
169 m_view->Update( m_overlay.get() );
170 m_hasCollision = true;
171 return true;
172}
173
174
176{
177 clearOverlay();
178 m_itemNetCodes.clear();
179 m_originalConnections.clear();
180 m_hasCollision = false;
181}
182
183
185{
186 if( m_hasCollision )
187 return KICURSOR::WARNING;
188
189 return aBaseCursor;
190}
191
192
193std::optional<SCH_DRAG_NET_COLLISION_MONITOR::COLLISION_MARKER>
195 const SCH_SELECTION& aSelection ) const
196{
197 if( !aJunction )
198 return std::nullopt;
199
200 const VECTOR2I position = aJunction->GetPosition();
201 std::optional<int> firstNet;
202 bool differentNets = false;
203 bool movedNet = false;
204 const auto accumulate = [&]( SCH_ITEM* item )
205 {
206 const auto found = m_itemNetCodes.find( item );
207
208 if( found == m_itemNetCodes.end() || !found->second )
209 return;
210
211 if( !item->IsConnected( position )
212 && !( item->Type() == SCH_LINE_T && item->HitTest( position ) ) )
213 {
214 return;
215 }
216
217 if( firstNet && firstNet != found->second )
218 differentNets = true;
219
220 firstNet = found->second;
221 movedNet |= item->IsSelected() || aSelection.Contains( item )
222 || aSelection.Contains( item->GetParent() );
223 };
224 const auto visit = [&]( SCH_ITEM* item )
225 {
226 accumulate( item );
227 item->RunOnChildren( accumulate, RECURSE_MODE::NO_RECURSE );
228 };
229
230 for( SCH_ITEM* candidate : m_frame->GetScreen()->Items().Overlapping( position ) )
231 visit( candidate );
232
233 // Moved geometry may not yet be reflected in the screen's spatial index.
234 for( EDA_ITEM* selected : aSelection )
235 visit( static_cast<SCH_ITEM*>( selected ) );
236
237 if( !movedNet || !differentNets )
238 return std::nullopt;
239
240 return COLLISION_MARKER{ position, std::max( aJunction->GetEffectiveDiameter() * 1.5, 800.0 ) };
241}
242
243
245{
246 if( !aItem || !aItem->IsConnectable() || m_itemNetCodes.contains( aItem ) )
247 return;
248
249 std::optional<int> netCode;
250
251 if( const SCH_CONNECTION* connection = aItem->Connection( &m_sheetPath ) )
252 {
253 if( connection->IsNet() && !connection->IsUnconnected() && connection->NetCode() > 0 )
254 netCode = connection->NetCode();
255 }
256
257 m_itemNetCodes.emplace( aItem, netCode );
258}
259
260
262{
263 wxLogTrace( traceSchDragNetCollision, "recordOriginalConnections: Recording connections for %d items",
264 aSelection.GetSize() );
265
266 // Don't record original connections for new or pasted items (duplicates, pastes)
267 // as they weren't previously connected to anything
268 bool hasNewOrPastedItems = false;
269
270 for( EDA_ITEM* edaItem : aSelection )
271 {
272 if( edaItem->IsNew() || ( edaItem->GetFlags() & IS_PASTED ) )
273 {
274 hasNewOrPastedItems = true;
275 break;
276 }
277 }
278
279 if( hasNewOrPastedItems )
280 {
281 wxLogTrace( traceSchDragNetCollision,
282 "recordOriginalConnections: Skipping - selection contains new or pasted items" );
283 return;
284 }
285
286 EE_RTREE& items = m_frame->GetScreen()->Items();
287
288 for( EDA_ITEM* edaItem : aSelection )
289 {
290 SCH_ITEM* item = static_cast<SCH_ITEM*>( edaItem );
291
292 if( !item || !item->IsConnectable() )
293 continue;
294
295 std::vector<VECTOR2I> points = item->GetConnectionPoints();
296
297 for( size_t index = 0; index < points.size(); ++index )
298 {
299 const VECTOR2I& point = points[index];
300
301 for( SCH_ITEM* candidate : items.Overlapping( point ) )
302 {
303 if( candidate == item || !candidate->IsConnectable() )
304 continue;
305
306 if( !candidate->CanConnect( item ) )
307 continue;
308
309 if( !candidate->IsConnected( point )
310 && !( candidate->IsType( { SCH_LINE_T } ) && candidate->HitTest( point ) ) )
311 {
312 continue;
313 }
314
315 std::vector<VECTOR2I> candidatePoints = candidate->GetConnectionPoints();
316 size_t candidateIndex = std::numeric_limits<size_t>::max();
317
318 for( size_t candidatePos = 0; candidatePos < candidatePoints.size(); ++candidatePos )
319 {
320 if( candidatePoints[candidatePos] == point )
321 {
322 candidateIndex = candidatePos;
323 break;
324 }
325 }
326
327 if( candidateIndex == std::numeric_limits<size_t>::max() )
328 continue;
329
330 SCH_ITEM* firstItem = item;
331 size_t firstIndex = index;
332 SCH_ITEM* secondItem = candidate;
333 size_t secondIndex = candidateIndex;
334
335 if( secondItem < firstItem || ( secondItem == firstItem && secondIndex < firstIndex ) )
336 {
337 std::swap( firstItem, secondItem );
338 std::swap( firstIndex, secondIndex );
339 }
340
341 if( firstItem == secondItem )
342 continue;
343
344 bool firstSelected = firstItem->IsSelected() || aSelection.Contains( firstItem );
345 bool secondSelected = secondItem->IsSelected() || aSelection.Contains( secondItem );
346
347 if( !firstSelected && !secondSelected )
348 continue;
349
350 auto existing = std::find_if( m_originalConnections.begin(), m_originalConnections.end(),
351 [&]( const ORIGINAL_CONNECTION& connection )
352 {
353 return connection.itemA == firstItem && connection.indexA == firstIndex
354 && connection.itemB == secondItem && connection.indexB == secondIndex;
355 } );
356
357 if( existing != m_originalConnections.end() )
358 continue;
359
360 m_originalConnections.push_back( { firstItem, firstIndex, secondItem, secondIndex } );
361 }
362 }
363 }
364
365 wxLogTrace( traceSchDragNetCollision, "recordOriginalConnections: Tracked %zu connections",
366 m_originalConnections.size() );
367}
368
369
370std::vector<SCH_DRAG_NET_COLLISION_MONITOR::DISCONNECTION_MARKER>
372{
373 std::vector<DISCONNECTION_MARKER> markers;
374
375 for( const ORIGINAL_CONNECTION& connection : m_originalConnections )
376 {
377 SCH_ITEM* itemA = connection.itemA;
378 SCH_ITEM* itemB = connection.itemB;
379
380 if( !itemA || !itemB )
381 continue;
382
383 if( !itemA->IsConnectable() || !itemB->IsConnectable() )
384 continue;
385
386 std::vector<VECTOR2I> pointsA = itemA->GetConnectionPoints();
387 std::vector<VECTOR2I> pointsB = itemB->GetConnectionPoints();
388
389 if( connection.indexA >= pointsA.size() || connection.indexB >= pointsB.size() )
390 continue;
391
392 VECTOR2I pointA = pointsA[ connection.indexA ];
393 VECTOR2I pointB = pointsB[ connection.indexB ];
394
395 // Check if the connection is still valid. Points match exactly.
396 bool stillConnected = ( pointA == pointB );
397
398 // For lines, connection is valid if the point is anywhere on the line
399 if( !stillConnected && itemB->IsType( { SCH_LINE_T } ) && itemB->HitTest( pointA, 0 ) )
400 stillConnected = true;
401
402 if( !stillConnected && itemA->IsType( { SCH_LINE_T } ) && itemA->HitTest( pointB, 0 ) )
403 stillConnected = true;
404
405 if( stillConnected )
406 continue;
407
408 bool relevant = itemA->IsSelected() || aSelection.Contains( itemA )
409 || itemB->IsSelected() || aSelection.Contains( itemB );
410
411 if( !relevant )
412 continue;
413
414 double radius = std::max( { 800.0,
415 static_cast<double>( itemA->GetPenWidth() ),
416 static_cast<double>( itemB->GetPenWidth() ) } );
417
419 marker.pointA = pointA;
420 marker.pointB = pointB;
421 marker.radius = radius;
422 markers.push_back( marker );
423 }
424
425 if( !markers.empty() )
426 {
427 wxLogTrace( traceSchDragNetCollision,
428 "collectDisconnectedMarkers: Identified %zu disconnections", markers.size() );
429 }
430
431 return markers;
432}
433
434
436{
437 if( !m_overlay )
438 m_overlay = m_view->MakeOverlay();
439}
440
441
443{
444 if( m_overlay )
445 {
446 m_overlay->Clear();
447 m_view->Update( m_overlay.get() );
448 }
449}
int index
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:400
Color settings are a bit different than most of the settings objects in that there can be more than o...
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
bool IsSelected() const
Definition eda_item.h:134
virtual bool HitTest(const VECTOR2I &aPosition, int aAccuracy=0) const
Test if aPosition is inside or on the boundary of this item.
Definition eda_item.h:309
Implement an R-tree for fast spatial and type indexing of schematic items.
Definition sch_rtree.h:37
EE_TYPE Overlapping(const BOX2I &aRect) const
Definition sch_rtree.h:253
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
COLOR4D WithAlpha(double aAlpha) const
Return a color with the same color, but the given alpha.
Definition color4d.h:309
double a
Alpha component.
Definition color4d.h:394
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition view.h:63
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
std::vector< ORIGINAL_CONNECTION > m_originalConnections
std::vector< DISCONNECTION_MARKER > collectDisconnectedMarkers(const SCH_SELECTION &aSelection) const
std::optional< COLLISION_MARKER > analyzeJunction(SCH_JUNCTION *aJunction, const SCH_SELECTION &aSelection) const
std::unordered_map< const SCH_ITEM *, std::optional< int > > m_itemNetCodes
bool Update(const std::vector< SCH_JUNCTION * > &aJunctions, const SCH_SELECTION &aSelection)
void recordOriginalConnections(const SCH_SELECTION &aSelection)
KICURSOR AdjustCursor(KICURSOR aBaseCursor) const
std::shared_ptr< KIGFX::VIEW_OVERLAY > m_overlay
SCH_DRAG_NET_COLLISION_MONITOR(SCH_EDIT_FRAME *aFrame, KIGFX::VIEW *aView)
void Initialize(const SCH_SELECTION &aSelection)
Schematic editor (Eeschema) main window.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
virtual bool IsConnectable() const
Definition sch_item.h:545
virtual int GetPenWidth() const
Definition sch_item.h:372
SCH_CONNECTION * Connection(const SCH_SHEET_PATH *aSheet=nullptr) const
Retrieve the connection associated with this object in the given sheet.
Definition sch_item.cpp:580
virtual std::vector< VECTOR2I > GetConnectionPoints() const
Add all the connection points for this item to aPoints.
Definition sch_item.h:558
bool IsType(const std::vector< KICAD_T > &aScanTypes) const override
Check whether the item is one of the listed types.
Definition sch_item.h:185
int GetEffectiveDiameter() const
VECTOR2I GetPosition() const override
virtual unsigned int GetSize() const override
Return the number of stored items.
Definition selection.h:104
bool Contains(EDA_ITEM *aItem) const
Definition selection.cpp:84
KICURSOR
Definition cursors.h:40
@ WARNING
Definition cursors.h:46
@ NO_RECURSE
Definition eda_item.h:52
#define IS_PASTED
Modifier on IS_NEW which indicates it came from clipboard.
const wxChar *const traceSchDragNetCollision
Flag to enable debug output of schematic drag net collision detection.
@ LAYER_DRAG_NET_COLLISION
Definition layer_ids.h:516
PREVIEW_NET_CODES CapturePreviewNetCodes(const FACADE &aFacade, const SCH_SHEET_PATH &aPath)
Owned local net assignments for advisory drag feedback; unavailable rows remain unknown.
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
int radius
wxLogTrace helper definitions.
@ SCH_LINE_T
Definition typeinfo.h:159
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707