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, you may find one here:
19 * http://www.gnu.org/licenses/gpl-3.0.html
20 * or you may search the http://www.gnu.org website for the version 3 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
26
27#include <algorithm>
28#include <unordered_set>
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
55
56
61
62
64{
65 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "Initialize: Starting initialization" );
66
67 m_itemNetCodes.clear();
68 m_sheetPath = m_frame->GetCurrentSheet();
69 m_hasCollision = false;
70
71 EE_RTREE& items = m_frame->GetScreen()->Items();
72
73 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "Initialize: Recording nets for %zu screen items",
74 items.size() );
75
76 for( SCH_ITEM* item : items )
77 recordItemNet( item );
78
79 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "Initialize: Recording nets for %d selected items",
80 aSelection.GetSize() );
81
82 for( EDA_ITEM* edaItem : aSelection )
83 recordItemNet( static_cast<SCH_ITEM*>( edaItem ) );
84
85 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "Initialize: Complete. Tracked %zu items with net codes",
86 m_itemNetCodes.size() );
87}
88
89
90bool SCH_DRAG_NET_COLLISION_MONITOR::Update( const std::vector<SCH_JUNCTION*>& aJunctions,
91 const SCH_SELECTION& aSelection,
92 std::span<const PREVIEW_NET_ASSIGNMENT> aPreviewAssignments )
93{
94 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "Update: Called with %zu junctions, %d selected items, %zu preview assignments",
95 aJunctions.size(), aSelection.GetSize(), aPreviewAssignments.size() );
96
97 if( aJunctions.empty() )
98 {
99 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "Update: No junctions to check, clearing overlay" );
100 clearOverlay();
101 m_hasCollision = false;
102 return false;
103 }
104
106 m_overlay->Clear();
107
108 std::unordered_map<const SCH_ITEM*, std::optional<int>> previewNetCodes;
109
110 previewNetCodes.reserve( aPreviewAssignments.size() );
111
112 for( const PREVIEW_NET_ASSIGNMENT& assignment : aPreviewAssignments )
113 {
114 if( !assignment.item )
115 continue;
116
117 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "Update: Preview assignment - item %p, netCode %s",
118 assignment.item,
119 assignment.netCode.has_value() ? std::to_string( *assignment.netCode ).c_str() : "none" );
120
121 previewNetCodes[ assignment.item ] = assignment.netCode;
122 }
123
124 std::vector<COLLISION_MARKER> markers;
125
126 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "Update: Analyzing %zu junctions", aJunctions.size() );
127
128 for( SCH_JUNCTION* junction : aJunctions )
129 {
130 if( auto marker = analyzeJunction( junction, aSelection, previewNetCodes ) )
131 {
132 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "Update: Junction at (%d, %d) has collision",
133 marker->position.x, marker->position.y );
134 markers.push_back( *marker );
135 }
136 }
137
138 if( markers.empty() )
139 {
140 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "Update: No collision markers found" );
141 m_hasCollision = false;
142 m_view->Update( m_overlay.get() );
143 return false;
144 }
145
146 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "Update: Drawing %zu collision markers", markers.size() );
147
148 COLOR4D baseColor( 1.0, 0.0, 0.0, 0.8 );
149
150 if( COLOR_SETTINGS* colorSettings = m_frame->GetColorSettings() )
151 {
152 COLOR4D themeColor = colorSettings->GetColor( LAYER_DRAG_NET_COLLISION );
153
154 if( themeColor != COLOR4D::UNSPECIFIED )
155 baseColor = themeColor;
156 }
157
158 double baseAlpha = baseColor.a;
159
160 if( baseAlpha <= 0.0 )
161 baseAlpha = 1.0;
162
163 double fillAlpha = std::clamp( baseAlpha * 0.35, 0.05, 1.0 );
164 double strokeAlpha = std::clamp( baseAlpha, 0.05, 1.0 );
165
166 m_overlay->SetIsFill( true );
167 m_overlay->SetFillColor( baseColor.WithAlpha( fillAlpha ) );
168 m_overlay->SetIsStroke( true );
169 m_overlay->SetStrokeColor( baseColor.WithAlpha( strokeAlpha ) );
170
171 int lineWidthPixels = 4;
172
173 if( EESCHEMA_SETTINGS* cfg = m_frame->eeconfig() )
174 lineWidthPixels = std::max( cfg->m_Selection.drag_net_collision_width, 1 );
175
176 double lineWidth = m_view->ToWorld( lineWidthPixels );
177
178 if( lineWidth <= 0.0 )
179 lineWidth = 1.0;
180
181 m_overlay->SetLineWidth( lineWidth );
182
183 for( const COLLISION_MARKER& marker : markers )
184 m_overlay->Circle( marker.position, marker.radius );
185
186 m_view->Update( m_overlay.get() );
187 m_hasCollision = true;
188 return true;
189}
190
191
193{
194 clearOverlay();
195 m_itemNetCodes.clear();
196 m_hasCollision = false;
197}
198
199
201{
202 if( m_hasCollision )
203 return KICURSOR::WARNING;
204
205 return aBaseCursor;
206}
207
208
209std::optional<int> SCH_DRAG_NET_COLLISION_MONITOR::GetNetCode( const SCH_ITEM* aItem ) const
210{
211 if( !aItem )
212 return std::nullopt;
213
214 auto it = m_itemNetCodes.find( aItem );
215
216 if( it != m_itemNetCodes.end() )
217 return it->second;
218
219 if( SCH_CONNECTION* connection = aItem->Connection( &m_sheetPath ) )
220 {
221 if( connection->IsNet() && !connection->IsUnconnected() )
222 {
223 int netCode = connection->NetCode();
224
225 if( netCode > 0 )
226 return netCode;
227 }
228 }
229
230 return std::nullopt;
231}
232
233
234std::optional<SCH_DRAG_NET_COLLISION_MONITOR::COLLISION_MARKER> SCH_DRAG_NET_COLLISION_MONITOR::analyzeJunction(
235 SCH_JUNCTION* aJunction, const SCH_SELECTION& aSelection,
236 const std::unordered_map<const SCH_ITEM*, std::optional<int>>& aPreviewNetCodes ) const
237{
238 if( !aJunction )
239 return std::nullopt;
240
241 VECTOR2I position = aJunction->GetPosition();
242 EE_RTREE& items = m_frame->GetScreen()->Items();
243
244 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "analyzeJunction: Checking junction at (%d, %d)",
245 position.x, position.y );
246
247 std::unordered_set<int> allNetCodes;
248 std::unordered_set<int> movedNetCodes;
249 std::unordered_set<int> originalNetCodes;
250 std::unordered_set<int> movedOriginalNetCodes;
251 std::unordered_set<int> stationaryOriginalNetCodes;
252
253 auto accumulateNet = [&]( SCH_ITEM* item )
254 {
255 if( !item )
256 {
257 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " accumulateNet: null item" );
258 return;
259 }
260
261 if( !item->IsConnectable() )
262 {
263 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " accumulateNet: item %p (%s) not connectable",
264 item, item->GetClass().c_str() );
265 return;
266 }
267
268 if( !item->IsConnected( position ) && !( item->IsType( { SCH_LINE_T } ) && item->HitTest( position ) ) )
269 {
270 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " accumulateNet: item %p (%s) not connected at (%d, %d)",
271 item, item->GetClass().c_str(), position.x, position.y );
272 return;
273 }
274
275 auto previewIt = aPreviewNetCodes.find( item );
276 auto originalIt = m_itemNetCodes.find( item );
277 std::optional<int> netCodeOpt;
278 std::optional<int> originalNetOpt;
279
280 if( originalIt != m_itemNetCodes.end() )
281 originalNetOpt = originalIt->second;
282
283 if( previewIt != aPreviewNetCodes.end() )
284 {
285 netCodeOpt = previewIt->second;
286 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " accumulateNet: item %p (%s) using preview net %s",
287 item, item->GetClass().c_str(),
288 netCodeOpt.has_value() ? std::to_string( *netCodeOpt ).c_str() : "none" );
289 }
290 else if( originalIt != m_itemNetCodes.end() )
291 {
292 netCodeOpt = originalIt->second;
293 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " accumulateNet: item %p (%s) using cached net %s",
294 item, item->GetClass().c_str(),
295 netCodeOpt.has_value() ? std::to_string( *netCodeOpt ).c_str() : "none" );
296 }
297 else
298 {
299 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " accumulateNet: item %p (%s) has no net code",
300 item, item->GetClass().c_str() );
301 }
302
303 bool isSelectionItem = item->IsSelected() || aSelection.Contains( item );
304 bool isMoved = ( previewIt != aPreviewNetCodes.end() ) || isSelectionItem;
305
306 if( !netCodeOpt )
307 {
308 if( originalNetOpt )
309 {
310 originalNetCodes.insert( *originalNetOpt );
311
312 if( isSelectionItem )
313 movedOriginalNetCodes.insert( *originalNetOpt );
314 else
315 stationaryOriginalNetCodes.insert( *originalNetOpt );
316 }
317
318 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " accumulateNet: item %p (%s) netCode is nullopt",
319 item, item->GetClass().c_str() );
320 return;
321 }
322
323 int netCode = *netCodeOpt;
324 allNetCodes.insert( netCode );
325
326 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " accumulateNet: item %p (%s) net %d, moved=%s",
327 item, item->GetClass().c_str(), netCode, isMoved ? "yes" : "no" );
328
329 if( isMoved )
330 movedNetCodes.insert( netCode );
331
332 if( originalNetOpt )
333 {
334 originalNetCodes.insert( *originalNetOpt );
335
336 if( isSelectionItem )
337 movedOriginalNetCodes.insert( *originalNetOpt );
338 else
339 stationaryOriginalNetCodes.insert( *originalNetOpt );
340 }
341 };
342
343 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "analyzeJunction: Checking items overlapping position" );
344
345 int candidateCount = 0;
346
347 for( SCH_ITEM* candidate : items.Overlapping( position ) )
348 {
349 candidateCount++;
350 accumulateNet( candidate );
351 }
352
353 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "analyzeJunction: Checked %d overlapping items", candidateCount );
354 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "analyzeJunction: Checking %d selected items", aSelection.GetSize() );
355
356 for( EDA_ITEM* selected : aSelection )
357 accumulateNet( static_cast<SCH_ITEM*>( selected ) );
358
359 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "analyzeJunction: Found %zu unique nets, %zu moved nets",
360 allNetCodes.size(), movedNetCodes.size() );
361
362 if( !movedNetCodes.empty() )
363 {
364 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "analyzeJunction: Moved nets:" );
365
366 for( int netCode : movedNetCodes )
367 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " - Net %d", netCode );
368 }
369
370 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION",
371 "analyzeJunction: Original nets=%zu, moved originals=%zu, stationary originals=%zu",
372 originalNetCodes.size(), movedOriginalNetCodes.size(), stationaryOriginalNetCodes.size() );
373
374 if( !movedOriginalNetCodes.empty() )
375 {
376 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "analyzeJunction: Moved original nets:" );
377
378 for( int netCode : movedOriginalNetCodes )
379 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " - Net %d", netCode );
380 }
381
382 if( !stationaryOriginalNetCodes.empty() )
383 {
384 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "analyzeJunction: Stationary original nets:" );
385
386 for( int netCode : stationaryOriginalNetCodes )
387 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " - Net %d", netCode );
388 }
389
390 if( allNetCodes.size() >= 2 )
391 {
392 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "analyzeJunction: All nets at junction:" );
393
394 for( int netCode : allNetCodes )
395 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", " - Net %d", netCode );
396 }
397
398 bool previewCollision = !movedNetCodes.empty() && allNetCodes.size() >= 2;
399
400 bool originalCollision = false;
401
402 if( !movedOriginalNetCodes.empty() && !stationaryOriginalNetCodes.empty() )
403 {
404 for( int movedNet : movedOriginalNetCodes )
405 {
406 for( int stationaryNet : stationaryOriginalNetCodes )
407 {
408 if( movedNet != stationaryNet )
409 {
410 originalCollision = true;
411 break;
412 }
413 }
414
415 if( originalCollision )
416 break;
417 }
418 }
419
420 if( !previewCollision && !originalCollision )
421 {
422 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "analyzeJunction: No collision (movedNets=%zu, allNets=%zu)",
423 movedNetCodes.size(), allNetCodes.size() );
424 return std::nullopt;
425 }
426
427 if( originalCollision && !previewCollision )
428 {
429 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION",
430 "analyzeJunction: Original net mismatch detected under moved endpoints" );
431 }
432
433 COLLISION_MARKER marker;
434 marker.position = position;
435 double base = static_cast<double>( aJunction->GetEffectiveDiameter() );
436 marker.radius = std::max( base * 1.5, 800.0 );
437
438 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "analyzeJunction: COLLISION DETECTED at (%d, %d) with radius %.1f",
439 position.x, position.y, marker.radius );
440
441 return marker;
442}
443
444
446{
447 if( !aItem )
448 return;
449
450 if( !aItem->IsConnectable() )
451 return;
452
453 if( m_itemNetCodes.find( aItem ) != m_itemNetCodes.end() )
454 return;
455
456 if( SCH_CONNECTION* connection = aItem->Connection( &m_sheetPath ) )
457 {
458 if( connection->IsNet() && !connection->IsUnconnected() )
459 {
460 int netCode = connection->NetCode();
461
462 if( netCode > 0 )
463 {
464 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "recordItemNet: Item %p (%s) at (%d, %d) -> net %d (%s)",
465 aItem, aItem->GetClass().c_str(),
466 aItem->GetPosition().x, aItem->GetPosition().y,
467 netCode, connection->Name().c_str() );
468 m_itemNetCodes.emplace( aItem, netCode );
469 }
470 else
471 {
472 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "recordItemNet: Item %p (%s) has invalid netCode %d",
473 aItem, aItem->GetClass().c_str(), netCode );
474 m_itemNetCodes.emplace( aItem, std::nullopt );
475 }
476 }
477 else
478 {
479 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "recordItemNet: Item %p (%s) connection not a net or unconnected",
480 aItem, aItem->GetClass().c_str() );
481 m_itemNetCodes.emplace( aItem, std::nullopt );
482 }
483 }
484 else
485 {
486 wxLogTrace( "KICAD_SCH_DRAG_NET_COLLISION", "recordItemNet: Item %p (%s) has no connection",
487 aItem, aItem->GetClass().c_str() );
488 m_itemNetCodes.emplace( aItem, std::nullopt );
489 }
490}
491
492
494{
495 if( !m_overlay )
496 m_overlay = m_view->MakeOverlay();
497}
498
499
501{
502 if( m_overlay )
503 {
504 m_overlay->Clear();
505 m_view->Update( m_overlay.get() );
506 }
507}
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:398
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
virtual VECTOR2I GetPosition() const
Definition eda_item.h:272
Implement an R-tree for fast spatial and type indexing of schematic items.
Definition sch_rtree.h:40
size_t size() const
Return the number of items in the tree.
Definition sch_rtree.h:174
EE_TYPE Overlapping(const BOX2I &aRect) const
Definition sch_rtree.h:246
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:104
COLOR4D WithAlpha(double aAlpha) const
Return a color with the same color, but the given alpha.
Definition color4d.h:311
double a
Alpha component.
Definition color4d.h:395
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition view.h:66
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
std::optional< COLLISION_MARKER > analyzeJunction(SCH_JUNCTION *aJunction, const SCH_SELECTION &aSelection, const std::unordered_map< const SCH_ITEM *, std::optional< int > > &aPreviewNetCodes) const
std::unordered_map< const SCH_ITEM *, std::optional< int > > m_itemNetCodes
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)
std::optional< int > GetNetCode(const SCH_ITEM *aItem) const
bool Update(const std::vector< SCH_JUNCTION * > &aJunctions, const SCH_SELECTION &aSelection, std::span< const PREVIEW_NET_ASSIGNMENT > aPreviewAssignments={})
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:167
virtual bool IsConnectable() const
Definition sch_item.h:505
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:352
wxString GetClass() const override
Return the class name.
Definition sch_item.h:177
int GetEffectiveDiameter() const
VECTOR2I GetPosition() const override
virtual unsigned int GetSize() const override
Return the number of stored items.
Definition selection.h:105
bool Contains(EDA_ITEM *aItem) const
Definition selection.cpp:84
KICURSOR
Definition cursors.h:44
@ WARNING
Definition cursors.h:50
@ LAYER_DRAG_NET_COLLISION
Definition layer_ids.h:494
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695