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 <schematic.h>
25#include <sch_line.h>
26
27#include <algorithm>
28#include <limits>
29
30#include <eda_item.h>
31#include <sch_connection.h>
32#include <sch_edit_frame.h>
33#include <sch_item.h>
34#include <sch_junction.h>
35#include <sch_screen.h>
36#include <sch_selection.h>
37#include <sch_sheet_path.h>
38#include <view/view.h>
39#include <view/view_overlay.h>
40#include <gal/color4d.h>
41#include <layer_ids.h>
43#include <eeschema_settings.h>
44
56
57
62
63
65{
66 wxLogTrace( traceSchDragNetCollision, "Initialize: Starting initialization" );
67
68 m_itemNetCodes.clear();
70 m_sheetPath = m_frame->GetCurrentSheet();
71 m_hasCollision = false;
72
73 const auto record = [&]( SCH_ITEM* item )
74 {
75 recordItemNet( item );
76 item->RunOnChildren( [&]( SCH_ITEM* child ) { recordItemNet( child ); }, RECURSE_MODE::NO_RECURSE );
77 };
78
79 for( SCH_ITEM* item : m_frame->GetScreen()->Items() )
80 record( item );
81
82 for( EDA_ITEM* item : aSelection )
83 record( static_cast<SCH_ITEM*>( item ) );
84
85 recordOriginalConnections( aSelection );
86}
87
88
89bool SCH_DRAG_NET_COLLISION_MONITOR::Update( const std::vector<SCH_JUNCTION*>& aJunctions,
90 const SCH_SELECTION& aSelection )
91{
92 std::vector<COLLISION_MARKER> markers;
93
94 for( SCH_JUNCTION* junction : aJunctions )
95 {
96 if( auto marker = analyzeJunction( junction, aSelection ) )
97 markers.push_back( *marker );
98 }
99
100 const auto disconnections = collectDisconnectedMarkers( aSelection );
101
102 if( markers.empty() && disconnections.empty() )
103 {
104 clearOverlay();
105 m_hasCollision = false;
106 return false;
107 }
108
109 wxLogTrace( traceSchDragNetCollision, "Update: Drawing %zu collision markers and %zu disconnection markers",
110 markers.size(), disconnections.size() );
111
113 m_overlay->Clear();
114
115 COLOR4D baseColor( 1.0, 0.0, 0.0, 0.8 );
116
117 if( COLOR_SETTINGS* colorSettings = m_frame->GetColorSettings() )
118 {
119 COLOR4D themeColor = colorSettings->GetColor( LAYER_DRAG_NET_COLLISION );
120
121 if( themeColor != COLOR4D::UNSPECIFIED )
122 baseColor = themeColor;
123 }
124
125 double baseAlpha = baseColor.a;
126
127 if( baseAlpha <= 0.0 )
128 baseAlpha = 1.0;
129
130 double fillAlpha = std::clamp( baseAlpha * 0.35, 0.05, 1.0 );
131 double strokeAlpha = std::clamp( baseAlpha, 0.05, 1.0 );
132
133 m_overlay->SetIsFill( true );
134 m_overlay->SetFillColor( baseColor.WithAlpha( fillAlpha ) );
135 m_overlay->SetIsStroke( true );
136 m_overlay->SetStrokeColor( baseColor.WithAlpha( strokeAlpha ) );
137
138 int lineWidthPixels = 4;
139
140 if( EESCHEMA_SETTINGS* cfg = m_frame->eeconfig() )
141 lineWidthPixels = std::max( cfg->m_Selection.drag_net_collision_width, 1 );
142
143 double lineWidth = m_view->ToWorld( lineWidthPixels );
144
145 if( lineWidth <= 0.0 )
146 lineWidth = 1.0;
147
148 m_overlay->SetLineWidth( lineWidth );
149
150 for( const COLLISION_MARKER& marker : markers )
151 m_overlay->Circle( marker.position, marker.radius );
152
153 for( const DISCONNECTION_MARKER& marker : disconnections )
154 {
155 m_overlay->Circle( marker.pointA, marker.radius );
156 m_overlay->Circle( marker.pointB, marker.radius );
157 m_overlay->Line( VECTOR2D( marker.pointA ), VECTOR2D( marker.pointB ) );
158 }
159
160 m_view->Update( m_overlay.get() );
161 m_hasCollision = true;
162 return true;
163}
164
165
167{
168 clearOverlay();
169 m_itemNetCodes.clear();
170 m_originalConnections.clear();
171 m_hasCollision = false;
172}
173
174
176{
177 if( m_hasCollision )
178 return KICURSOR::WARNING;
179
180 return aBaseCursor;
181}
182
183
184std::optional<SCH_DRAG_NET_COLLISION_MONITOR::COLLISION_MARKER>
186 const SCH_SELECTION& aSelection ) const
187{
188 if( !aJunction )
189 return std::nullopt;
190
191 const VECTOR2I position = aJunction->GetPosition();
192 std::optional<int> firstNet;
193 bool differentNets = false;
194 bool movedNet = false;
195 const auto accumulate = [&]( SCH_ITEM* item )
196 {
197 const auto found = m_itemNetCodes.find( item );
198
199 if( found == m_itemNetCodes.end() || !found->second )
200 return;
201
202 if( !item->IsConnected( position )
203 && !( item->Type() == SCH_LINE_T && item->HitTest( position ) ) )
204 {
205 return;
206 }
207
208 if( firstNet && firstNet != found->second )
209 differentNets = true;
210
211 firstNet = found->second;
212 movedNet |= item->IsSelected() || aSelection.Contains( item )
213 || aSelection.Contains( item->GetParent() );
214 };
215 const auto visit = [&]( SCH_ITEM* item )
216 {
217 accumulate( item );
218 item->RunOnChildren( accumulate, RECURSE_MODE::NO_RECURSE );
219 };
220
221 for( SCH_ITEM* candidate : m_frame->GetScreen()->Items().Overlapping( position ) )
222 visit( candidate );
223
224 // Moved geometry may not yet be reflected in the screen's spatial index.
225 for( EDA_ITEM* selected : aSelection )
226 visit( static_cast<SCH_ITEM*>( selected ) );
227
228 if( !movedNet || !differentNets )
229 return std::nullopt;
230
231 return COLLISION_MARKER{ position, std::max( aJunction->GetEffectiveDiameter() * 1.5, 800.0 ) };
232}
233
234
236{
237 if( !aItem || !aItem->IsConnectable() || m_itemNetCodes.contains( aItem ) )
238 return;
239
240 std::optional<int> netCode;
241
242 if( const SCH_CONNECTION* connection = aItem->Connection( &m_sheetPath ) )
243 {
244 if( connection->IsNet() && !connection->IsUnconnected() && connection->NetCode() > 0 )
245 netCode = connection->NetCode();
246 }
247
248 m_itemNetCodes.emplace( aItem, netCode );
249}
250
251
253{
254 wxLogTrace( traceSchDragNetCollision, "recordOriginalConnections: Recording connections for %d items",
255 aSelection.GetSize() );
256
257 // Don't record original connections for new or pasted items (duplicates, pastes)
258 // as they weren't previously connected to anything
259 bool hasNewOrPastedItems = false;
260
261 for( EDA_ITEM* edaItem : aSelection )
262 {
263 if( edaItem->IsNew() || ( edaItem->GetFlags() & IS_PASTED ) )
264 {
265 hasNewOrPastedItems = true;
266 break;
267 }
268 }
269
270 if( hasNewOrPastedItems )
271 {
272 wxLogTrace( traceSchDragNetCollision,
273 "recordOriginalConnections: Skipping - selection contains new or pasted items" );
274 return;
275 }
276
277 EE_RTREE& items = m_frame->GetScreen()->Items();
278
279 for( EDA_ITEM* edaItem : aSelection )
280 {
281 SCH_ITEM* item = static_cast<SCH_ITEM*>( edaItem );
282
283 if( !item || !item->IsConnectable() )
284 continue;
285
286 std::vector<VECTOR2I> points = item->GetConnectionPoints();
287
288 for( size_t index = 0; index < points.size(); ++index )
289 {
290 const VECTOR2I& point = points[index];
291
292 for( SCH_ITEM* candidate : items.Overlapping( point ) )
293 {
294 if( candidate == item || !candidate->IsConnectable() )
295 continue;
296
297 if( !candidate->CanConnect( item ) )
298 continue;
299
300 if( !candidate->IsConnected( point )
301 && !( candidate->IsType( { SCH_LINE_T } ) && candidate->HitTest( point ) ) )
302 {
303 continue;
304 }
305
306 std::vector<VECTOR2I> candidatePoints = candidate->GetConnectionPoints();
307 size_t candidateIndex = std::numeric_limits<size_t>::max();
308
309 for( size_t candidatePos = 0; candidatePos < candidatePoints.size(); ++candidatePos )
310 {
311 if( candidatePoints[candidatePos] == point )
312 {
313 candidateIndex = candidatePos;
314 break;
315 }
316 }
317
318 if( candidateIndex == std::numeric_limits<size_t>::max() )
319 continue;
320
321 SCH_ITEM* firstItem = item;
322 size_t firstIndex = index;
323 SCH_ITEM* secondItem = candidate;
324 size_t secondIndex = candidateIndex;
325
326 if( secondItem < firstItem || ( secondItem == firstItem && secondIndex < firstIndex ) )
327 {
328 std::swap( firstItem, secondItem );
329 std::swap( firstIndex, secondIndex );
330 }
331
332 if( firstItem == secondItem )
333 continue;
334
335 bool firstSelected = firstItem->IsSelected() || aSelection.Contains( firstItem );
336 bool secondSelected = secondItem->IsSelected() || aSelection.Contains( secondItem );
337
338 if( !firstSelected && !secondSelected )
339 continue;
340
341 auto existing = std::find_if( m_originalConnections.begin(), m_originalConnections.end(),
342 [&]( const ORIGINAL_CONNECTION& connection )
343 {
344 return connection.itemA == firstItem && connection.indexA == firstIndex
345 && connection.itemB == secondItem && connection.indexB == secondIndex;
346 } );
347
348 if( existing != m_originalConnections.end() )
349 continue;
350
351 m_originalConnections.push_back( { firstItem, firstIndex, secondItem, secondIndex } );
352 }
353 }
354 }
355
356 wxLogTrace( traceSchDragNetCollision, "recordOriginalConnections: Tracked %zu connections",
357 m_originalConnections.size() );
358}
359
360
361std::vector<SCH_DRAG_NET_COLLISION_MONITOR::DISCONNECTION_MARKER>
363{
364 std::vector<DISCONNECTION_MARKER> markers;
365
366 for( const ORIGINAL_CONNECTION& connection : m_originalConnections )
367 {
368 SCH_ITEM* itemA = connection.itemA;
369 SCH_ITEM* itemB = connection.itemB;
370
371 if( !itemA || !itemB )
372 continue;
373
374 if( !itemA->IsConnectable() || !itemB->IsConnectable() )
375 continue;
376
377 std::vector<VECTOR2I> pointsA = itemA->GetConnectionPoints();
378 std::vector<VECTOR2I> pointsB = itemB->GetConnectionPoints();
379
380 if( connection.indexA >= pointsA.size() || connection.indexB >= pointsB.size() )
381 continue;
382
383 VECTOR2I pointA = pointsA[ connection.indexA ];
384 VECTOR2I pointB = pointsB[ connection.indexB ];
385
386 // Check if the connection is still valid. Points match exactly.
387 bool stillConnected = ( pointA == pointB );
388
389 // For lines, connection is valid if the point is anywhere on the line
390 if( !stillConnected && itemB->IsType( { SCH_LINE_T } ) && itemB->HitTest( pointA, 0 ) )
391 stillConnected = true;
392
393 if( !stillConnected && itemA->IsType( { SCH_LINE_T } ) && itemA->HitTest( pointB, 0 ) )
394 stillConnected = true;
395
396 if( stillConnected )
397 continue;
398
399 bool relevant = itemA->IsSelected() || aSelection.Contains( itemA )
400 || itemB->IsSelected() || aSelection.Contains( itemB );
401
402 if( !relevant )
403 continue;
404
405 double radius = std::max( { 800.0,
406 static_cast<double>( itemA->GetPenWidth() ),
407 static_cast<double>( itemB->GetPenWidth() ) } );
408
410 marker.pointA = pointA;
411 marker.pointB = pointB;
412 marker.radius = radius;
413 markers.push_back( marker );
414 }
415
416 if( !markers.empty() )
417 {
418 wxLogTrace( traceSchDragNetCollision,
419 "collectDisconnectedMarkers: Identified %zu disconnections", markers.size() );
420 }
421
422 return markers;
423}
424
425
427{
428 if( !m_overlay )
429 m_overlay = m_view->MakeOverlay();
430}
431
432
434{
435 if( m_overlay )
436 {
437 m_overlay->Clear();
438 m_view->Update( m_overlay.get() );
439 }
440}
int index
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:399
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:308
double a
Alpha component.
Definition color4d.h:393
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:165
virtual bool IsConnectable() const
Definition sch_item.h:531
virtual int GetPenWidth() const
Definition sch_item.h:358
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:503
virtual std::vector< VECTOR2I > GetConnectionPoints() const
Add all the connection points for this item to aPoints.
Definition sch_item.h:546
bool IsType(const std::vector< KICAD_T > &aScanTypes) const override
Check whether the item is one of the listed types.
Definition sch_item.h:180
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
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:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682