KiCad PCB EDA Suite
Loading...
Searching...
No Matches
junction_helpers.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 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 "junction_helpers.h"
21
22#include <sch_line.h>
23#include <sch_screen.h>
24#include <sch_junction.h>
25#include <sch_item.h>
26#include <trigo.h>
27
28#include <unordered_set>
29
30using namespace JUNCTION_HELPERS;
31
33 bool aBreakCrossings )
34{
35 enum layers
36 {
37 WIRES = 0,
38 BUSES
39 };
40
42 info.hasBusEntry = false;
43 info.hasExplicitJunctionDot = false;
44 info.isJunction = false;
45 info.hasBusEntryToMultipleWires = false;
46 info.hasBusEntryToMultipleBuses = false;
47 info.hasBusAtPoint = false;
48
49 bool breakLines[2] = { false };
50 std::unordered_set<int> exitAngles[2];
51 std::vector<const SCH_LINE*> midPointLines[2];
52
53 // Synthetic bus angles injected by entries, subtracted later to find genuine bus directions
54 int busEntryBusAngles = 0;
55
56 // Bus segments terminating here; a real fork ends at least one bus, a crossing ends none
57 int busEndpointSegments = 0;
58
59 EE_RTREE filtered;
60 std::list<std::unique_ptr<SCH_LINE>> mergedLines;
61
62 // Ignore items that are currently being moved or flagged to skip
63 // and temporarily merge collinear wires before analyzing the point.
64 for( SCH_ITEM* item : aItems.Overlapping( aPosition ) )
65 {
66 if( item->GetEditFlags() & ( SKIP_STRUCT | STRUCT_DELETED ) )
67 continue;
68
69 switch( item->Type() )
70 {
71 case SCH_LINE_T:
72 {
73 SCH_LINE* line = static_cast<SCH_LINE*>( item );
74
75 if( line->IsConnectable() )
76 mergedLines.emplace_back( new SCH_LINE( *line ) );
77
78 break;
79 }
80
81 case SCH_JUNCTION_T:
82 if( item->HitTest( aPosition, -1 ) )
83 info.hasExplicitJunctionDot = true;
84
85 filtered.insert( item );
86 break;
87
89 info.hasBusEntry = true;
90 filtered.insert( item );
91 break;
92
93 case SCH_SHEET_T:
94 case SCH_SYMBOL_T:
95 case SCH_LABEL_T:
98 filtered.insert( item );
99 break;
100
101 default:
102 break;
103 }
104 }
105
106 if( mergedLines.size() + filtered.size() < 2 )
107 return info;
108
109 // Skip collinear merging when enough distinct endpoints already meet here.
110 // Merging would turn an N-way stub junction (e.g. LTspice four-wire cross:
111 // two opposite stubs on each axis) into an unmarked mid-segment crossing
112 // and hide the needed junction.
113 std::unordered_set<int> preMergeWireExits;
114 std::unordered_set<int> preMergeBusExits;
115
116 for( const auto& line : mergedLines )
117 {
118 if( line->GetStartPoint() == line->GetEndPoint() )
119 continue;
120
121 if( !line->IsConnected( aPosition ) )
122 continue;
123
124 if( line->GetLayer() == LAYER_WIRE )
125 preMergeWireExits.insert( line->GetAngleFrom( aPosition ) );
126 else if( line->GetLayer() == LAYER_BUS )
127 preMergeBusExits.insert( line->GetAngleFrom( aPosition ) );
128 }
129
130 const bool keepStubJunction = preMergeWireExits.size() >= 3 || preMergeBusExits.size() >= 3;
131
132 // Merge collinear wire segments
133 bool merged = false;
134
135 do
136 {
137 if( info.hasExplicitJunctionDot || aBreakCrossings || keepStubJunction )
138 break;
139
140 merged = false;
141
142 for( auto it_i = mergedLines.begin(); it_i != mergedLines.end() && !merged; ++it_i )
143 {
144 for( auto it_j = std::next( it_i ); it_j != mergedLines.end(); ++it_j )
145 {
146 if( auto* line = ( *it_i )->MergeOverlap( nullptr, it_j->get(), false ) )
147 {
148 it_i->reset( line );
149 mergedLines.erase( it_j );
150 merged = true;
151 break;
152 }
153 }
154 }
155 } while( merged );
156
157 for( const auto& line : mergedLines )
158 filtered.insert( line.get() );
159
160
161 // A pin at 90° still shouldn't match a line at 90° so just give pins unique numbers
162 int uniqueAngle = 10000;
163
164 for( const SCH_ITEM* item : filtered )
165 {
166 if( item->GetEditFlags() & STRUCT_DELETED )
167 continue;
168
169 switch( item->Type() )
170 {
171 case SCH_JUNCTION_T:
172 if( item->HitTest( aPosition, -1 ) )
173 info.hasExplicitJunctionDot = true;
174
175 break;
176
177 case SCH_LINE_T:
178 {
179 const SCH_LINE* line = static_cast<const SCH_LINE*>( item );
180 int layer;
181
182 if( line->GetStartPoint() == line->GetEndPoint() )
183 break;
184 else if( line->GetLayer() == LAYER_WIRE )
185 layer = WIRES;
186 else if( line->GetLayer() == LAYER_BUS )
187 layer = BUSES;
188 else
189 break;
190
191 if( line->IsConnected( aPosition ) )
192 {
193 breakLines[layer] = true;
194 exitAngles[layer].insert( line->GetAngleFrom( aPosition ) );
195
196 if( layer == BUSES )
197 busEndpointSegments++;
198 }
199 else if( line->HitTest( aPosition, -1 ) )
200 {
201 if( aBreakCrossings )
202 breakLines[layer] = true;
203
204 // Defer any line midpoints until we know whether or not we're breaking them
205 midPointLines[layer].push_back( line );
206 }
207
208 if( layer == BUSES && line->HitTest( aPosition, -1 ) )
209 info.hasBusAtPoint = true;
210 }
211 break;
212
214 if( item->IsConnected( aPosition ) )
215 {
216 breakLines[BUSES] = true;
217 exitAngles[BUSES].insert( uniqueAngle++ );
218 busEntryBusAngles++;
219 breakLines[WIRES] = true;
220 exitAngles[WIRES].insert( uniqueAngle++ );
221 info.hasBusEntry = true;
222 }
223
224 break;
225
226 case SCH_SYMBOL_T:
227 case SCH_SHEET_T:
228 if( item->IsConnected( aPosition ) )
229 {
230 breakLines[WIRES] = true;
231 exitAngles[WIRES].insert( uniqueAngle++ );
232 }
233
234 break;
235
236 case SCH_LABEL_T:
237 if( item->IsConnected( aPosition ) )
238 {
239 if( SCH_CONNECTION::IsBusLabel( static_cast<const SCH_LABEL*>( item )->GetText() ) )
240 breakLines[BUSES] = true;
241 else
242 breakLines[WIRES] = true;
243 }
244
245 break;
246
247 case SCH_HIER_LABEL_T:
249 if( item->IsConnected( aPosition ) )
250 breakLines[WIRES] = true;
251
252 break;
253
254 default:
255 break;
256 }
257 }
258
259 for( int layer : { WIRES, BUSES } )
260 {
261 if( breakLines[layer] )
262 {
263 for( const SCH_LINE* line : midPointLines[layer] )
264 {
265 exitAngles[layer].insert( line->GetAngleFrom( aPosition ) );
266 exitAngles[layer].insert( line->GetReverseAngleFrom( aPosition ) );
267 }
268 }
269 }
270
271 if( info.hasBusEntry )
272 {
273 // The bus entry and one wire is 2 wires, and the one entry is exactly one bus
274 // Any more wires must be multiple wires, but any more buses means a wire
275 // crossing at the bus entry root.
276 info.hasBusEntryToMultipleWires = exitAngles[WIRES].size() > 2 && exitAngles[BUSES].size() == 1;
277
278 // Drop the entry's own synthetic angle; three real directions with a terminating bus is
279 // a fork, while crossings with no terminating bus must not be auto-joined
280 const int realBusAngles = static_cast<int>( exitAngles[BUSES].size() ) - busEntryBusAngles;
281 info.hasBusEntryToMultipleBuses = realBusAngles >= 3 && busEndpointSegments >= 1;
282 }
283
284 // Any three things of the same type is a junction of some sort
285 info.isJunction = exitAngles[WIRES].size() >= 3 || exitAngles[BUSES].size() >= 3;
286
287 return info;
288}
289
290
291std::vector<SCH_JUNCTION*> JUNCTION_HELPERS::PreviewJunctions( const SCH_SCREEN* aScreen,
292 const std::vector<SCH_ITEM*>& aItems )
293{
295 std::unordered_set<const SCH_ITEM*> previewSet( aItems.begin(), aItems.end() );
296
297 // Existing items, skipping any that are also in aItems to avoid double-counting
298 for( const SCH_ITEM* item : aScreen->Items() )
299 {
300 if( !item->IsConnectable() )
301 continue;
302
303 if( previewSet.count( item ) )
304 continue;
305
306 combined.insert( const_cast<SCH_ITEM*>( item ) );
307 }
308
309 // Temporary/preview items
310 for( SCH_ITEM* item : aItems )
311 {
312 if( !item || !item->IsConnectable() )
313 continue;
314
315 combined.insert( item );
316 }
317
318 std::vector<VECTOR2I> connections = aScreen->GetConnections();
319 std::vector<VECTOR2I> pts;
320
321 for( SCH_ITEM* item : aItems )
322 {
323 if( !item || !item->IsConnectable() )
324 continue;
325
326 std::vector<VECTOR2I> new_pts = item->GetConnectionPoints();
327 pts.insert( pts.end(), new_pts.begin(), new_pts.end() );
328
329 if( item->Type() == SCH_LINE_T )
330 {
331 SCH_LINE* line = static_cast<SCH_LINE*>( item );
332
333 for( const VECTOR2I& pt : connections )
334 {
335 if( IsPointOnSegment( line->GetStartPoint(), line->GetEndPoint(), pt ) )
336 pts.push_back( pt );
337 }
338 }
339 }
340
341 std::sort( pts.begin(), pts.end(),
342 []( const VECTOR2I& a, const VECTOR2I& b )
343 {
344 return a.x < b.x || ( a.x == b.x && a.y < b.y );
345 } );
346
347 pts.erase( std::unique( pts.begin(), pts.end() ), pts.end() );
348
349 std::vector<SCH_JUNCTION*> jcts;
350
351 for( const VECTOR2I& pt : pts )
352 {
353 POINT_INFO info = AnalyzePoint( combined, pt, false );
354
355 if( info.AllowsExplicitJunction() )
356 {
357 SCH_JUNCTION* junction = new SCH_JUNCTION( pt );
358
359 if( info.hasBusAtPoint )
360 junction->SetLayer( LAYER_BUS_JUNCTION );
361
362 jcts.push_back( junction );
363 }
364 }
365
366 return jcts;
367}
Implement an R-tree for fast spatial and type indexing of schematic items.
Definition sch_rtree.h:37
size_t size() const
Return the number of items in the tree.
Definition sch_rtree.h:177
EE_TYPE Overlapping(const BOX2I &aRect) const
Definition sch_rtree.h:253
void insert(SCH_ITEM *aItem)
Insert an item into the tree.
Definition sch_rtree.h:70
static bool IsBusLabel(const wxString &aLabel)
Test if aLabel has a bus notation.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
void SetLayer(SCH_LAYER_ID aLayer)
Definition sch_item.h:346
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition sch_item.h:345
bool IsConnected(const VECTOR2I &aPoint) const
Test the item to see if it is connected to aPoint.
Definition sch_item.cpp:494
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
bool HitTest(const VECTOR2I &aPosition, int aAccuracy=0) const override
Test if aPosition is inside or on the boundary of this item.
Definition sch_line.cpp:906
int GetAngleFrom(const VECTOR2I &aPoint) const
Definition sch_line.cpp:508
VECTOR2I GetEndPoint() const
Definition sch_line.h:145
VECTOR2I GetStartPoint() const
Definition sch_line.h:136
bool IsConnectable() const override
Definition sch_line.cpp:755
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:118
std::vector< VECTOR2I > GetConnections() const
Collect a unique list of all possible connection points in the schematic.
#define STRUCT_DELETED
flag indication structures to be erased
#define SKIP_STRUCT
flag indicating that the structure should be ignored
@ LAYER_WIRE
Definition layer_ids.h:474
@ LAYER_BUS
Definition layer_ids.h:475
@ LAYER_BUS_JUNCTION
Definition layer_ids.h:520
std::vector< SCH_JUNCTION * > PreviewJunctions(const class SCH_SCREEN *aScreen, const std::vector< class SCH_ITEM * > &aItems)
Determine the points where explicit junctions would be required if the given temporary items were com...
POINT_INFO AnalyzePoint(const EE_RTREE &aItem, const VECTOR2I &aPosition, bool aBreakCrossings)
Check a tree of items for a confluence at a given point and work out what kind of junction it is,...
A selection of information about a point in the schematic that might be eligible for turning into a j...
bool IsPointOnSegment(const VECTOR2I &aSegStart, const VECTOR2I &aSegEnd, const VECTOR2I &aTestPoint)
Test if aTestPoint is on line defined by aSegStart and aSegEnd.
Definition trigo.cpp:85
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_LABEL_T
Definition typeinfo.h:163
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_HIER_LABEL_T
Definition typeinfo.h:165
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:157
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:164
@ SCH_JUNCTION_T
Definition typeinfo.h:155
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683