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.hasBusAtPoint = false;
47
48 bool breakLines[2] = { false };
49 std::unordered_set<int> exitAngles[2];
50 std::vector<const SCH_LINE*> midPointLines[2];
51
52 EE_RTREE filtered;
53 std::list<std::unique_ptr<SCH_LINE>> mergedLines;
54
55 // Ignore items that are currently being moved or flagged to skip
56 // and temporarily merge collinear wires before analyzing the point.
57 for( SCH_ITEM* item : aItems.Overlapping( aPosition ) )
58 {
59 if( item->GetEditFlags() & ( SKIP_STRUCT | STRUCT_DELETED ) )
60 continue;
61
62 switch( item->Type() )
63 {
64 case SCH_LINE_T:
65 {
66 SCH_LINE* line = static_cast<SCH_LINE*>( item );
67
68 if( line->IsConnectable() )
69 mergedLines.emplace_back( new SCH_LINE( *line ) );
70
71 break;
72 }
73
74 case SCH_JUNCTION_T:
75 if( item->HitTest( aPosition, -1 ) )
76 info.hasExplicitJunctionDot = true;
77
78 filtered.insert( item );
79 break;
80
82 info.hasBusEntry = true;
83 filtered.insert( item );
84 break;
85
86 case SCH_SHEET_T:
87 case SCH_SYMBOL_T:
88 case SCH_LABEL_T:
91 filtered.insert( item );
92 break;
93
94 default:
95 break;
96 }
97 }
98
99 if( mergedLines.size() + filtered.size() < 2 )
100 return info;
101
102 // Skip collinear merging when enough distinct endpoints already meet here.
103 // Merging would turn an N-way stub junction (e.g. LTspice four-wire cross:
104 // two opposite stubs on each axis) into an unmarked mid-segment crossing
105 // and hide the needed junction.
106 std::unordered_set<int> preMergeWireExits;
107 std::unordered_set<int> preMergeBusExits;
108
109 for( const auto& line : mergedLines )
110 {
111 if( line->GetStartPoint() == line->GetEndPoint() )
112 continue;
113
114 if( !line->IsConnected( aPosition ) )
115 continue;
116
117 if( line->GetLayer() == LAYER_WIRE )
118 preMergeWireExits.insert( line->GetAngleFrom( aPosition ) );
119 else if( line->GetLayer() == LAYER_BUS )
120 preMergeBusExits.insert( line->GetAngleFrom( aPosition ) );
121 }
122
123 const bool keepStubJunction = preMergeWireExits.size() >= 3 || preMergeBusExits.size() >= 3;
124
125 // Merge collinear wire segments
126 bool merged = false;
127
128 do
129 {
130 if( info.hasExplicitJunctionDot || aBreakCrossings || keepStubJunction )
131 break;
132
133 merged = false;
134
135 for( auto it_i = mergedLines.begin(); it_i != mergedLines.end() && !merged; ++it_i )
136 {
137 for( auto it_j = std::next( it_i ); it_j != mergedLines.end(); ++it_j )
138 {
139 if( auto* line = ( *it_i )->MergeOverlap( nullptr, it_j->get(), false ) )
140 {
141 it_i->reset( line );
142 mergedLines.erase( it_j );
143 merged = true;
144 break;
145 }
146 }
147 }
148 } while( merged );
149
150 for( const auto& line : mergedLines )
151 filtered.insert( line.get() );
152
153
154 // A pin at 90° still shouldn't match a line at 90° so just give pins unique numbers
155 int uniqueAngle = 10000;
156
157 for( const SCH_ITEM* item : filtered )
158 {
159 if( item->GetEditFlags() & STRUCT_DELETED )
160 continue;
161
162 switch( item->Type() )
163 {
164 case SCH_JUNCTION_T:
165 if( item->HitTest( aPosition, -1 ) )
166 info.hasExplicitJunctionDot = true;
167
168 break;
169
170 case SCH_LINE_T:
171 {
172 const SCH_LINE* line = static_cast<const SCH_LINE*>( item );
173 int layer;
174
175 if( line->GetStartPoint() == line->GetEndPoint() )
176 break;
177 else if( line->GetLayer() == LAYER_WIRE )
178 layer = WIRES;
179 else if( line->GetLayer() == LAYER_BUS )
180 layer = BUSES;
181 else
182 break;
183
184 if( line->IsConnected( aPosition ) )
185 {
186 breakLines[layer] = true;
187 exitAngles[layer].insert( line->GetAngleFrom( aPosition ) );
188 }
189 else if( line->HitTest( aPosition, -1 ) )
190 {
191 if( aBreakCrossings )
192 breakLines[layer] = true;
193
194 // Defer any line midpoints until we know whether or not we're breaking them
195 midPointLines[layer].push_back( line );
196 }
197
198 if( layer == BUSES && line->HitTest( aPosition, -1 ) )
199 info.hasBusAtPoint = true;
200 }
201 break;
202
204 if( item->IsConnected( aPosition ) )
205 {
206 breakLines[BUSES] = true;
207 exitAngles[BUSES].insert( uniqueAngle++ );
208 breakLines[WIRES] = true;
209 exitAngles[WIRES].insert( uniqueAngle++ );
210 info.hasBusEntry = true;
211 }
212
213 break;
214
215 case SCH_SYMBOL_T:
216 case SCH_SHEET_T:
217 if( item->IsConnected( aPosition ) )
218 {
219 breakLines[WIRES] = true;
220 exitAngles[WIRES].insert( uniqueAngle++ );
221 }
222
223 break;
224
225 case SCH_LABEL_T:
226 if( item->IsConnected( aPosition ) )
227 {
228 if( SCH_CONNECTION::IsBusLabel( static_cast<const SCH_LABEL*>( item )->GetText() ) )
229 breakLines[BUSES] = true;
230 else
231 breakLines[WIRES] = true;
232 }
233
234 break;
235
236 case SCH_HIER_LABEL_T:
238 if( item->IsConnected( aPosition ) )
239 breakLines[WIRES] = true;
240
241 break;
242
243 default:
244 break;
245 }
246 }
247
248 for( int layer : { WIRES, BUSES } )
249 {
250 if( breakLines[layer] )
251 {
252 for( const SCH_LINE* line : midPointLines[layer] )
253 {
254 exitAngles[layer].insert( line->GetAngleFrom( aPosition ) );
255 exitAngles[layer].insert( line->GetReverseAngleFrom( aPosition ) );
256 }
257 }
258 }
259
260 if( info.hasBusEntry )
261 {
262 // The bus entry and one wire is 2 wires, and the one entry is exactly one bus
263 // Any more wires must be multiple wires, but any more buses means a wire
264 // crossing at the bus entry root.
265 info.hasBusEntryToMultipleWires = exitAngles[WIRES].size() > 2 && exitAngles[BUSES].size() == 1;
266 }
267
268 // Any three things of the same type is a junction of some sort
269 info.isJunction = exitAngles[WIRES].size() >= 3 || exitAngles[BUSES].size() >= 3;
270
271 return info;
272}
273
274
275std::vector<SCH_JUNCTION*> JUNCTION_HELPERS::PreviewJunctions( const SCH_SCREEN* aScreen,
276 const std::vector<SCH_ITEM*>& aItems )
277{
279 std::unordered_set<const SCH_ITEM*> previewSet( aItems.begin(), aItems.end() );
280
281 // Existing items, skipping any that are also in aItems to avoid double-counting
282 for( const SCH_ITEM* item : aScreen->Items() )
283 {
284 if( !item->IsConnectable() )
285 continue;
286
287 if( previewSet.count( item ) )
288 continue;
289
290 combined.insert( const_cast<SCH_ITEM*>( item ) );
291 }
292
293 // Temporary/preview items
294 for( SCH_ITEM* item : aItems )
295 {
296 if( !item || !item->IsConnectable() )
297 continue;
298
299 combined.insert( item );
300 }
301
302 std::vector<VECTOR2I> connections = aScreen->GetConnections();
303 std::vector<VECTOR2I> pts;
304
305 for( SCH_ITEM* item : aItems )
306 {
307 if( !item || !item->IsConnectable() )
308 continue;
309
310 std::vector<VECTOR2I> new_pts = item->GetConnectionPoints();
311 pts.insert( pts.end(), new_pts.begin(), new_pts.end() );
312
313 if( item->Type() == SCH_LINE_T )
314 {
315 SCH_LINE* line = static_cast<SCH_LINE*>( item );
316
317 for( const VECTOR2I& pt : connections )
318 {
319 if( IsPointOnSegment( line->GetStartPoint(), line->GetEndPoint(), pt ) )
320 pts.push_back( pt );
321 }
322 }
323 }
324
325 std::sort( pts.begin(), pts.end(),
326 []( const VECTOR2I& a, const VECTOR2I& b )
327 {
328 return a.x < b.x || ( a.x == b.x && a.y < b.y );
329 } );
330
331 pts.erase( std::unique( pts.begin(), pts.end() ), pts.end() );
332
333 std::vector<SCH_JUNCTION*> jcts;
334
335 for( const VECTOR2I& pt : pts )
336 {
337 POINT_INFO info = AnalyzePoint( combined, pt, false );
338
339 if( info.isJunction && ( !info.hasBusEntry || info.hasBusEntryToMultipleWires ) )
340 {
341 SCH_JUNCTION* junction = new SCH_JUNCTION( pt );
342
343 if( info.hasBusAtPoint )
344 junction->SetLayer( LAYER_BUS_JUNCTION );
345
346 jcts.push_back( junction );
347 }
348 }
349
350 return jcts;
351}
Implement an R-tree for fast spatial and type indexing of schematic items.
Definition sch_rtree.h:34
size_t size() const
Return the number of items in the tree.
Definition sch_rtree.h:150
EE_TYPE Overlapping(const BOX2I &aRect) const
Definition sch_rtree.h:226
void insert(SCH_ITEM *aItem)
Insert an item into the tree.
Definition sch_rtree.h:45
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:162
void SetLayer(SCH_LAYER_ID aLayer)
Definition sch_item.h:339
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition sch_item.h:338
bool IsConnected(const VECTOR2I &aPoint) const
Test the item to see if it is connected to aPoint.
Definition sch_item.cpp:478
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:38
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:855
int GetAngleFrom(const VECTOR2I &aPoint) const
Definition sch_line.cpp:456
VECTOR2I GetEndPoint() const
Definition sch_line.h:144
VECTOR2I GetStartPoint() const
Definition sch_line.h:135
bool IsConnectable() const override
Definition sch_line.cpp:703
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:115
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:458
@ LAYER_BUS
Definition layer_ids.h:459
@ LAYER_BUS_JUNCTION
Definition layer_ids.h:504
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:160
@ SCH_SYMBOL_T
Definition typeinfo.h:169
@ SCH_LABEL_T
Definition typeinfo.h:164
@ SCH_SHEET_T
Definition typeinfo.h:172
@ SCH_HIER_LABEL_T
Definition typeinfo.h:166
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:158
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:165
@ SCH_JUNCTION_T
Definition typeinfo.h:156
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683