KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_line_wire_bus_tool.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 (C) 2019 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
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 2
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
21#include <bitmaps.h>
22#include <tool/tool_manager.h>
24
25#include <wx/debug.h>
26#include <wx/gdicmn.h>
27#include <wx/string.h>
28#include <wx/translation.h>
29#include <algorithm>
30#include <cstdlib>
31#include <iterator>
32#include <memory>
33#include <utility>
34#include <vector>
35
36#include <layer_ids.h>
37#include <math/vector2d.h>
38#include <advanced_config.h>
42#include <view/view_controls.h>
43#include <tool/actions.h>
45#include <tool/selection.h>
47#include <tool/tool_event.h>
48#include <trigo.h>
49#include <eeschema_id.h>
50#include <sch_bus_entry.h>
51#include <sch_connection.h>
52#include <sch_edit_frame.h>
53#include <widgets/wx_infobar.h>
54#include <sch_junction.h>
55#include <sch_line.h>
56#include <sch_screen.h>
57#include <sch_sheet.h>
58#include <sch_group.h>
59#include <sch_sheet_pin.h>
60#include <schematic.h>
61#include <sch_commit.h>
62#include <sch_actions.h>
63#include <junction_helpers.h>
64#include <ee_grid_helper.h>
65#include <sch_selection.h>
66#include <sch_selection_tool.h>
67
68
69using BUS_GETTER = std::function<SCH_LINE*()>;
70
72{
73public:
79 ACTION_MENU( true ),
80 m_showTitle( false ),
81 m_busGetter( aBusGetter )
82 {
84 SetTitle( _( "Unfold from Bus" ) );
85 }
86
88 {
89 m_showTitle = true;
90 }
91
92 bool PassHelpTextToHandler() override { return true; }
93
94protected:
95 ACTION_MENU* create() const override
96 {
97 return new BUS_UNFOLD_MENU( m_busGetter );
98 }
99
100private:
101 struct MEMBER
102 {
103 wxString name;
104 bool bus = false;
105 std::vector<wxString> members;
106 };
107
108 static std::vector<MEMBER> memberEntries( const SCH_LINE& aBus )
109 {
111 std::vector<MEMBER> result;
112
113 if( ADVANCED_CFG::GetCfg().m_ConnectivityEngine )
114 {
115 SCHEMATIC* schematic = aBus.Schematic();
116
117 if( !schematic )
118 return result;
119
120 const auto connection = schematic->Connectivity().Connection( aBus.m_Uuid,
121 schematic->CurrentSheet().PathRef() );
122
123 if( !connection || !connection->IsBus() )
124 return result;
125
126 const SCH_CONNECTIVITY::BUS_MEMBERS members = connection->Members();
127
128 if( !members.tree || !members.schema )
129 return result;
130
131 const auto leafName = [&]( const BUS_SCHEMA::NODE& aNode )
132 {
133 const auto& leaves = members.schema->leaves;
134 return aNode.leaf && *aNode.leaf < leaves.size() ? leaves[*aNode.leaf].name : wxString();
135 };
136
137 // Leaf names carry their full group prefix; nested buses take the root group's prefix
138 const bool namedGroup = members.tree->kind == BUS_SCHEMA::NODE::KIND::GROUP
139 && !members.tree->prefix.IsEmpty();
140 const wxString prefix = namedGroup ? members.tree->prefix + wxS( "." ) : wxString();
141
142 for( const BUS_SCHEMA::NODE& node : members.tree->members )
143 {
144 MEMBER& entry = result.emplace_back();
145 entry.bus = node.kind != BUS_SCHEMA::NODE::KIND::NET;
146 entry.name = entry.bus ? prefix + node.text : leafName( node );
147
148 for( const BUS_SCHEMA::NODE& sub : node.members )
149 {
150 if( wxString name = leafName( sub ); !name.IsEmpty() )
151 entry.members.push_back( std::move( name ) );
152 }
153 }
154
155 return result;
156 }
157
158 const SCH_CONNECTION* connection = aBus.Connection();
159
160 if( !connection || !connection->IsBus() )
161 return result;
162
163 for( const std::shared_ptr<SCH_CONNECTION>& member : connection->Members() )
164 {
165 MEMBER& entry = result.emplace_back();
166 entry.name = member->FullLocalName();
167 entry.bus = member->Type() == CONNECTION_TYPE::BUS;
168
169 for( const std::shared_ptr<SCH_CONNECTION>& sub : member->Members() )
170 entry.members.push_back( sub->FullLocalName() );
171 }
172
173 return result;
174 }
175
176 void update() override
177 {
178 SCH_LINE* bus = m_busGetter();
179 Clear();
180 // Pick up the pointer again because it may have been changed by SchematicCleanUp
181 bus = m_busGetter();
182
183 if( !bus )
184 {
185 Append( ID_POPUP_SCH_UNFOLD_BUS, _( "No bus selected" ), wxEmptyString );
186 Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
187 return;
188 }
189
190 const std::vector<MEMBER> members = memberEntries( *bus );
191
192 if( members.empty() )
193 {
194 Append( ID_POPUP_SCH_UNFOLD_BUS, _( "Bus has no members" ), wxEmptyString );
195 Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
196 return;
197 }
198
199 int idx = 0;
200
201 if( m_showTitle )
202 {
203 Append( ID_POPUP_SCH_UNFOLD_BUS, _( "Unfold from Bus" ), wxEmptyString );
204 Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
205 }
206
207 std::unordered_map<wxString, ACTION_MENU*> diff_busses{};
208
209 for( const MEMBER& member : members )
210 {
211 int id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ );
212 wxString name = member.name;
213
214 if( member.bus )
215 {
216 ACTION_MENU* submenu = nullptr;
217 // If we are building the menu for suffixed bus vectors, we need to do some more massaging
218 if( ( name.ends_with( '+' ) || name.ends_with( '-' ) || name.ends_with( 'P' ) || name.ends_with( 'N' ) )
219 && !member.members.empty() )
220 {
221 wxString submenu_name = name.substr( 0, name.length() - 1 );
222 auto bus_submenu = diff_busses.find( submenu_name );
223
224 if( bus_submenu == diff_busses.end() )
225 {
226 submenu = new ACTION_MENU( true, m_tool );
227 diff_busses.emplace( submenu_name, submenu );
228 AppendSubMenu( submenu, SCH_CONNECTION::PrintBusForUI( submenu_name ), submenu_name );
229 }
230 else
231 {
232 submenu = bus_submenu->second;
233 }
234 }
235 else
236 {
237 // Otherwise we can set the submenu up like normal
238 submenu = new ACTION_MENU( true, m_tool );
239 AppendSubMenu( submenu, SCH_CONNECTION::PrintBusForUI( name ), name );
240 }
241
242 for( const wxString& subMemberName : member.members )
243 {
244 id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ );
245 name = subMemberName;
246
247 if( ( name.ends_with( '+' ) || name.ends_with( '-' ) || name.ends_with( 'P' )
248 || name.ends_with( 'N' ) ) )
249 {
250 wxString submenu_name = name.substr( 0, name.length() - 1 );
251 auto bus_submenu = diff_busses.find( submenu_name );
252
253 ACTION_MENU* diff_submenu = nullptr;
254 if( bus_submenu == diff_busses.end() )
255 {
256 diff_submenu = new ACTION_MENU( true, m_tool );
257 diff_busses.emplace( submenu_name, diff_submenu );
258 submenu->AppendSubMenu( diff_submenu, SCH_CONNECTION::PrintBusForUI( submenu_name ),
259 submenu_name );
260 }
261 else
262 {
263 diff_submenu = bus_submenu->second;
264 }
265 diff_submenu->Append( id, SCH_CONNECTION::PrintBusForUI( name ), name );
266 }
267 else
268 {
269 submenu->Append( id, SCH_CONNECTION::PrintBusForUI( name ), name );
270 }
271 }
272 }
273 else
274 {
275 Append( id, SCH_CONNECTION::PrintBusForUI( name ), name );
276 }
277 }
278 }
279
282};
283
284
292
293
297
298
300 SCH_TOOL_BASE<SCH_EDIT_FRAME>( "eeschema.InteractiveDrawingLineWireBus" ),
301 m_inDrawingTool( false )
302{
303 m_busUnfold = {};
304 m_wires.reserve( 16 );
305}
306
307
311
312
314{
316
317 const auto busGetter =
318 [this]()
319 {
320 return getBusForUnfolding();
321 };
322
323 std::shared_ptr<BUS_UNFOLD_MENU>
324 busUnfoldMenu = std::make_shared<BUS_UNFOLD_MENU>( busGetter );
325 busUnfoldMenu->SetTool( this );
326 m_menu->RegisterSubMenu( busUnfoldMenu );
327
328 std::shared_ptr<BUS_UNFOLD_MENU> selBusUnfoldMenu = std::make_shared<BUS_UNFOLD_MENU>( busGetter );
329 selBusUnfoldMenu->SetTool( m_selectionTool );
330 m_selectionTool->GetToolMenu().RegisterSubMenu( selBusUnfoldMenu );
331
332 auto wireOrBusTool =
333 [this]( const SELECTION& aSel )
334 {
335 return ( m_frame->IsCurrentTool( SCH_ACTIONS::drawWire )
336 || m_frame->IsCurrentTool( SCH_ACTIONS::drawBus ) );
337 };
338
339 auto lineTool =
340 [this]( const SELECTION& aSel )
341 {
342 return m_frame->IsCurrentTool( SCH_ACTIONS::drawLines );
343 };
344
345 auto belowRootSheetCondition =
346 [this]( const SELECTION& aSel )
347 {
348 return m_frame->GetCurrentSheet().Last() != &m_frame->Schematic().Root();
349 };
350
351 auto busSelection = SCH_CONDITIONS::MoreThan( 0 )
353
354 auto haveHighlight =
355 [this]( const SELECTION& sel )
356 {
357 SCH_EDIT_FRAME* editFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
358
359 return editFrame && !editFrame->GetHighlightedConnection().IsEmpty();
360 };
361
362 auto& ctxMenu = m_menu->GetMenu();
363
364 // Build the tool menu
365 //
366 ctxMenu.AddItem( SCH_ACTIONS::clearHighlight, haveHighlight && SCH_CONDITIONS::Idle, 1 );
367 ctxMenu.AddSeparator( haveHighlight && SCH_CONDITIONS::Idle, 1 );
368
369 ctxMenu.AddSeparator( 10 );
370 ctxMenu.AddItem( SCH_ACTIONS::drawWire, wireOrBusTool && SCH_CONDITIONS::Idle, 10 );
371 ctxMenu.AddItem( SCH_ACTIONS::drawBus, wireOrBusTool && SCH_CONDITIONS::Idle, 10 );
372 ctxMenu.AddItem( SCH_ACTIONS::drawLines, lineTool && SCH_CONDITIONS::Idle, 10 );
373
377
378 ctxMenu.AddMenu( busUnfoldMenu.get(), SCH_CONDITIONS::Idle, 10 );
379
380 ctxMenu.AddSeparator( 100 );
381 ctxMenu.AddItem( SCH_ACTIONS::placeJunction, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
382 ctxMenu.AddItem( SCH_ACTIONS::placeLabel, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
383 ctxMenu.AddItem( SCH_ACTIONS::placeClassLabel, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
384 ctxMenu.AddItem( SCH_ACTIONS::placeGlobalLabel, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
385 ctxMenu.AddItem( SCH_ACTIONS::placeHierLabel, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
386 ctxMenu.AddItem( SCH_ACTIONS::breakWire, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
387 ctxMenu.AddItem( SCH_ACTIONS::slice, ( wireOrBusTool || lineTool )
388 && SCH_CONDITIONS::Idle, 100 );
389 ctxMenu.AddItem( SCH_ACTIONS::leaveSheet, belowRootSheetCondition, 150 );
390
391 ctxMenu.AddSeparator( 200 );
392 ctxMenu.AddItem( SCH_ACTIONS::selectNode, wireOrBusTool && SCH_CONDITIONS::Idle, 200 );
393 ctxMenu.AddItem( SCH_ACTIONS::selectConnection, wireOrBusTool && SCH_CONDITIONS::Idle, 200 );
394
395 // Add bus unfolding to the selection tool
396 //
397 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
398
399 selToolMenu.AddMenu( selBusUnfoldMenu.get(), busSelection && SCH_CONDITIONS::Idle, 100 );
400
401 return true;
402}
403
404
406{
407 // NOTE: for immediate hotkeys, it is NOT required that the line, wire or bus tool
408 // be selected
409 SCH_ITEM* item = (SCH_ITEM*) aSelection.Front();
410 return item && item->IsNew() && item->Type() == SCH_LINE_T;
411}
412
413
415{
416 if( m_inDrawingTool )
417 return 0;
418
420
421 const DRAW_SEGMENT_EVENT_PARAMS* params = aEvent.Parameter<const DRAW_SEGMENT_EVENT_PARAMS*>();
422 SCH_COMMIT commit( m_toolMgr );
423
424 TOOL_EVENT originalEvent = aEvent; // This can change out from under us when the event loop runs
425 SCOPED_TOOL_PUSHER raii( m_frame, aEvent );
426
428
429 if( aEvent.HasPosition() )
430 {
432 GRID_HELPER_GRIDS gridType = ( params->layer == LAYER_NOTES ) ? GRID_GRAPHICS : GRID_WIRES;
433
434 grid.SetSnap( !aEvent.Modifier( MD_SHIFT ) );
435 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !aEvent.DisableGridSnapping() );
436
437 VECTOR2D cursorPos = grid.ResolveSnap( aEvent.Position(), gridType, nullptr ).position;
438 startSegments( commit, params->layer, cursorPos, params->sourceSegment );
439 }
440
441 return doDrawSegments( originalEvent, commit, params->layer, params->quitOnDraw );
442}
443
444
446{
447 if( m_inDrawingTool )
448 return 0;
449
451
452 SCH_COMMIT commit( m_toolMgr );
453 wxString* netPtr = aEvent.Parameter<wxString*>();
454 wxString net;
455 SCH_LINE* segment = nullptr;
456
457 TOOL_EVENT originalEvent = aEvent; // This can change out from under us when the event loop runs
458 SCOPED_TOOL_PUSHER raii( m_frame, originalEvent );
459 Activate();
460
461 if( netPtr )
462 {
463 net = *netPtr;
464 delete netPtr;
465 }
466 else
467 {
468 const auto busGetter =
469 [this]()
470 {
471 return getBusForUnfolding();
472 };
473
474 BUS_UNFOLD_MENU unfoldMenu( busGetter );
475 unfoldMenu.SetTool( this );
476 unfoldMenu.SetShowTitle();
477
478 SetContextMenu( &unfoldMenu, CMENU_NOW );
479
480 while( TOOL_EVENT* evt = Wait() )
481 {
482 if( evt->Action() == TA_CHOICE_MENU_CHOICE )
483 {
484 std::optional<int> id = evt->GetCommandId();
485
486 if( id && ( *id > 0 ) )
487 net = *evt->Parameter<wxString*>();
488
489 break;
490 }
491 else if( evt->Action() == TA_CHOICE_MENU_CLOSED )
492 {
493 break;
494 }
495 else
496 {
497 evt->SetPassEvent();
498 }
499 }
500 }
501
502 // Break a wire for the given net out of the bus
503 if( !net.IsEmpty() )
504 segment = doUnfoldBus( commit, net );
505
506 // If we have an unfolded wire to draw, then draw it
507 if( segment )
508 return doDrawSegments( originalEvent, commit, LAYER_WIRE, false );
509
510 return 0;
511}
512
513
515{
516 SCH_SELECTION& selection = m_selectionTool->RequestSelection( { SCH_ITEM_LOCATE_BUS_T } );
517 return static_cast<SCH_LINE*>( selection.Front() );
518}
519
520
522 const std::optional<VECTOR2I>& aPos )
523{
524 SCHEMATIC_SETTINGS& cfg = getModel<SCHEMATIC>()->Settings();
525 SCH_SCREEN* screen = m_frame->GetScreen();
526 // use the same function as the menu selector, so we choose the same bus segment
527 SCH_LINE* const bus = getBusForUnfolding();
528
529 if ( bus == nullptr )
530 {
531 wxASSERT_MSG( false, wxString::Format( "Couldn't find the originating bus line (but had a net: %s )",
532 aNet ) );
533 return nullptr;
534 }
535
536 VECTOR2I pos = aPos.value_or( static_cast<VECTOR2I>( getViewControls()->GetCursorPosition() ) );
537
538 // It is possible for the position to be near the bus, but not exactly on it, but
539 // we need the bus entry to be on the bus exactly to connect.
540 // If the bus segment is H or V, this will be on the selection grid, if it's not,
541 // it might not be, but it won't be a broken connection (and the user asked for it!)
542 pos = bus->GetSeg().NearestPoint( pos );
543
545
546 m_busUnfold.entry = new SCH_BUS_WIRE_ENTRY( pos );
547 m_busUnfold.entry->SetParent( screen );
548 m_frame->AddToScreen( m_busUnfold.entry, m_frame->GetScreen() );
549
550 m_busUnfold.label = new SCH_LABEL( m_busUnfold.entry->GetEnd(), aNet );
551 m_busUnfold.label->SetTextSize( VECTOR2I( cfg.m_DefaultTextSize, cfg.m_DefaultTextSize ) );
552 m_busUnfold.label->SetSpinStyle( busUnfoldPersistentSettings.label_spin_style );
553 m_busUnfold.label->SetParent( m_frame->GetScreen() );
554 m_busUnfold.label->SetFlags( IS_NEW | IS_MOVING );
555
556 m_busUnfold.in_progress = true;
557 m_busUnfold.origin = pos;
558 m_busUnfold.net_name = aNet;
559
560 getViewControls()->SetCrossHairCursorPosition( m_busUnfold.entry->GetEnd(), false );
561
562 std::vector<DANGLING_END_ITEM> endPointsByType;
563
564 for( SCH_ITEM* item : screen->Items().Overlapping( m_busUnfold.entry->GetBoundingBox() ) )
565 item->GetEndPoints( endPointsByType );
566
567 std::vector<DANGLING_END_ITEM> endPointsByPos = endPointsByType;
568 DANGLING_END_ITEM_HELPER::sort_dangling_end_items( endPointsByType, endPointsByPos );
569 m_busUnfold.entry->UpdateDanglingState( endPointsByType, endPointsByPos );
570 m_busUnfold.entry->SetEndDangling( false );
571 m_busUnfold.label->SetIsDangling( false );
572
573 return startSegments( aCommit, LAYER_WIRE, m_busUnfold.entry->GetEnd() );
574}
575
576
578{
579 SCH_SCREEN* screen = m_frame->GetScreen();
580
581 for( SCH_ITEM* item : screen->Items().Overlapping( SCH_SHEET_T, aPosition ) )
582 {
583 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
584
585 for( SCH_SHEET_PIN* pin : sheet->GetPins() )
586 {
587 if( pin->GetPosition() == aPosition )
588 return pin;
589 }
590 }
591
592 return nullptr;
593}
594
595
596void SCH_LINE_WIRE_BUS_TOOL::computeBreakPoint( const std::pair<SCH_LINE*, SCH_LINE*>& aSegments,
597 VECTOR2I& aPosition,
598 LINE_MODE mode,
599 bool posture )
600{
601 wxCHECK_RET( aSegments.first && aSegments.second,
602 wxT( "Cannot compute break point of NULL line segment." ) );
603
604 VECTOR2I midPoint;
605 SCH_LINE* segment = aSegments.first;
606 SCH_LINE* nextSegment = aSegments.second;
607
608 VECTOR2I delta = aPosition - segment->GetStartPoint();
609 int xDir = delta.x > 0 ? 1 : -1;
610 int yDir = delta.y > 0 ? 1 : -1;
611
612
613 bool preferHorizontal;
614 bool preferVertical;
615
616 if( ( mode == LINE_MODE_45 ) && posture )
617 {
618 preferHorizontal = ( nextSegment->GetEndPoint().x - nextSegment->GetStartPoint().x ) != 0;
619 preferVertical = ( nextSegment->GetEndPoint().y - nextSegment->GetStartPoint().y ) != 0;
620 }
621 else
622 {
623 preferHorizontal = ( segment->GetEndPoint().x - segment->GetStartPoint().x ) != 0;
624 preferVertical = ( segment->GetEndPoint().y - segment->GetStartPoint().y ) != 0;
625 }
626
627 // Check for times we need to force horizontal sheet pin connections
628 const SCH_SHEET_PIN* connectedPin = getSheetPin( segment->GetStartPoint() );
629 SHEET_SIDE force = connectedPin ? connectedPin->GetSide() : SHEET_SIDE::UNDEFINED;
630
631 if( force == SHEET_SIDE::LEFT || force == SHEET_SIDE::RIGHT )
632 {
633 if( aPosition.x == connectedPin->GetPosition().x ) // push outside sheet boundary
634 {
635 int direction = ( force == SHEET_SIDE::LEFT ) ? -1 : 1;
636 aPosition.x += KiROUND( getView()->GetGAL()->GetGridSize().x * direction );
637 }
638
639 preferHorizontal = true;
640 preferVertical = false;
641 }
642
643
644 auto breakVertical = [&]() mutable
645 {
646 switch( mode )
647 {
648 case LINE_MODE_45:
649 if( !posture )
650 {
651 midPoint.x = segment->GetStartPoint().x;
652 midPoint.y = aPosition.y - yDir * abs( delta.x );
653 }
654 else
655 {
656 midPoint.x = aPosition.x;
657 midPoint.y = segment->GetStartPoint().y + yDir * abs( delta.x );
658 }
659 break;
660 default:
661 midPoint.x = segment->GetStartPoint().x;
662 midPoint.y = aPosition.y;
663 }
664 };
665
666
667 auto breakHorizontal = [&]() mutable
668 {
669 switch( mode )
670 {
671 case LINE_MODE_45:
672 if( !posture )
673 {
674 midPoint.x = aPosition.x - xDir * abs( delta.y );
675 midPoint.y = segment->GetStartPoint().y;
676 }
677 else
678 {
679 midPoint.x = segment->GetStartPoint().x + xDir * abs( delta.y );
680 midPoint.y = aPosition.y;
681 }
682 break;
683 default:
684 midPoint.x = aPosition.x;
685 midPoint.y = segment->GetStartPoint().y;
686 }
687 };
688
689
690 // Maintain current line shape if we can, e.g. if we were originally moving
691 // vertically keep the first segment vertical
692 if( preferVertical )
693 breakVertical();
694 else if( preferHorizontal )
695 breakHorizontal();
696
697 // Check if our 45 degree angle is one of these shapes
698 // /
699 // /
700 // /
701 // /__________
702 VECTOR2I deltaMidpoint = midPoint - segment->GetStartPoint();
703
704 if( mode == LINE_MODE::LINE_MODE_45 && !posture
705 && ( ( alg::signbit( deltaMidpoint.x ) != alg::signbit( delta.x ) )
706 || ( alg::signbit( deltaMidpoint.y ) != alg::signbit( delta.y ) ) ) )
707 {
708 preferVertical = false;
709 preferHorizontal = false;
710 }
711 else if( mode == LINE_MODE::LINE_MODE_45 && posture
712 && ( ( abs( deltaMidpoint.x ) > abs( delta.x ) )
713 || ( abs( deltaMidpoint.y ) > abs( delta.y ) ) ) )
714 {
715 preferVertical = false;
716 preferHorizontal = false;
717 }
718
719 if( !preferHorizontal && !preferVertical )
720 {
721 if( std::abs( delta.x ) < std::abs( delta.y ) )
722 breakVertical();
723 else
724 breakHorizontal();
725 }
726
727 segment->SetEndPoint( midPoint );
728 nextSegment->SetStartPoint( midPoint );
729 nextSegment->SetEndPoint( aPosition );
730}
731
732
734 int aType, bool aQuitOnDraw )
735{
736 SCH_SCREEN* screen = m_frame->GetScreen();
737 SCH_LINE* segment = nullptr;
739 GRID_HELPER_GRIDS gridType = ( aType == LAYER_NOTES ) ? GRID_GRAPHICS : GRID_WIRES;
741 int lastMode = m_frame->eeconfig()->m_Drawing.line_mode;
742 static bool posture = false;
743
744 auto setCursor =
745 [&]()
746 {
747 if( aType == LAYER_WIRE )
748 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_WIRE );
749 else if( aType == LAYER_BUS )
750 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_BUS );
751 else if( aType == LAYER_NOTES )
752 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_GRAPHIC );
753 else
754 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_WIRE );
755 };
756
757 auto cleanup =
758 [&] ()
759 {
761
762 for( SCH_LINE* wire : m_wires )
763 delete wire;
764
765 m_wires.clear();
766 segment = nullptr;
767
768 if( m_busUnfold.entry )
769 m_frame->RemoveFromScreen( m_busUnfold.entry, screen );
770
771 if( m_busUnfold.label && !m_busUnfold.label_placed )
772 m_selectionTool->RemoveItemFromSel( m_busUnfold.label, true );
773
774 if( m_busUnfold.label && m_busUnfold.label_placed )
775 m_frame->RemoveFromScreen( m_busUnfold.label, screen );
776
777 delete m_busUnfold.entry;
778 delete m_busUnfold.label;
779 m_busUnfold = {};
780
781 m_view->ClearPreview();
782 m_view->ShowPreview( false );
783 };
784
785 Activate();
786 // Must be done after Activate() so that it gets set into the correct context
787 controls->ShowCursor( true );
788 // Set initial cursor
789 setCursor();
790
791 // Add the new label to the selection so the rotate command operates on it
792 if( m_busUnfold.label )
793 m_selectionTool->AddItemToSel( m_busUnfold.label, true );
794
795 // Continue the existing wires if we've started (usually by immediate action preference)
796 if( !m_wires.empty() )
797 segment = m_wires.back();
798
799 VECTOR2I contextMenuPos;
800
801 // Main loop: keep receiving events
802 while( TOOL_EVENT* evt = Wait() )
803 {
804 LINE_MODE currentMode = (LINE_MODE) m_frame->eeconfig()->m_Drawing.line_mode;
805 bool twoSegments = currentMode != LINE_MODE::LINE_MODE_FREE;
806
807 // The tool hotkey is interpreted as a click when drawing
808 bool isSyntheticClick = ( segment || m_busUnfold.in_progress ) && evt->IsActivate()
809 && evt->HasPosition() && evt->Matches( aTool );
810
811 setCursor();
812 grid.SetMask( GRID_HELPER::ALL );
813 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
814 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
815
816 if( segment )
817 {
818 if( segment->GetStartPoint().x == segment->GetEndPoint().x )
819 grid.ClearMaskFlag( GRID_HELPER::VERTICAL );
820
821 if( segment->GetStartPoint().y == segment->GetEndPoint().y )
822 grid.ClearMaskFlag( GRID_HELPER::HORIZONTAL );
823 }
824
825 VECTOR2D eventPosition = evt->HasPosition() ? evt->Position()
826 : controls->GetMousePosition();
827
828 VECTOR2I cursorPos = grid.ResolveSnap( eventPosition, gridType, segment ).position;
829 controls->ForceCursorPosition( true, cursorPos );
830
831 // Need to handle change in H/V mode while drawing
832 if( currentMode != lastMode )
833 {
834 // Need to delete extra segment if we have one
835 if( segment && currentMode == LINE_MODE::LINE_MODE_FREE && m_wires.size() >= 2 )
836 {
837 m_wires.pop_back();
838 m_selectionTool->RemoveItemFromSel( segment );
839 delete segment;
840
841 segment = m_wires.back();
842 segment->SetEndPoint( cursorPos );
843 }
844 // Add a segment so we can move orthogonally/45
845 else if( segment && lastMode == LINE_MODE::LINE_MODE_FREE )
846 {
847 segment->SetEndPoint( cursorPos );
848
849 // Create a new segment, and chain it after the current segment.
850 segment = static_cast<SCH_LINE*>( segment->Duplicate( true, &aCommit ) );
851 segment->SetFlags( IS_NEW | IS_MOVING );
852 segment->SetStartPoint( cursorPos );
853 m_wires.push_back( segment );
854
855 m_selectionTool->AddItemToSel( segment, true /*quiet mode*/ );
856 }
857
858 lastMode = currentMode;
859 }
860
861 //------------------------------------------------------------------------
862 // Handle cancel:
863 //
864 if( evt->IsCancelInteractive() )
865 {
866 m_frame->GetInfoBar()->Dismiss();
867
868 if( segment || m_busUnfold.in_progress )
869 {
870 cleanup();
871
872 if( aQuitOnDraw )
873 break;
874 }
875 else
876 {
877 break;
878 }
879 }
880 else if( evt->IsActivate() && !isSyntheticClick )
881 {
882 if( segment || m_busUnfold.in_progress )
883 {
884 m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel drawing." ) );
885 evt->SetPassEvent( false );
886 continue;
887 }
888
889 if( evt->IsMoveTool() )
890 {
891 // Make sure we come back after the move tool runs
892 m_frame->PushTool( aTool );
893 }
894
895 break;
896 }
897 //------------------------------------------------------------------------
898 // Handle finish:
899 //
900 else if( evt->IsAction( &ACTIONS::finishInteractive ) )
901 {
902 if( segment || m_busUnfold.in_progress )
903 {
904 finishSegments( aCommit );
905 segment = nullptr;
906
907 aCommit.Push( _( "Draw Wires" ) );
908
909 if( aQuitOnDraw )
910 break;
911 }
912 }
913 //------------------------------------------------------------------------
914 // Handle click:
915 //
916 else if( evt->IsClick( BUT_LEFT )
917 || evt->IsAction( &ACTIONS::cursorClick )
918 || ( segment && evt->IsDblClick( BUT_LEFT ) )
919 || ( segment && evt->IsAction( &ACTIONS::cursorDblClick ) )
920 || isSyntheticClick )
921 {
922 // First click when unfolding places the label and wire-to-bus entry
923 if( m_busUnfold.in_progress && !m_busUnfold.label_placed )
924 {
925 wxASSERT( aType == LAYER_WIRE );
926
927 m_frame->AddToScreen( m_busUnfold.label, screen );
928 m_selectionTool->RemoveItemFromSel( m_busUnfold.label, true );
929 m_busUnfold.label_placed = true;
930 }
931
932 if( !segment )
933 {
934 segment = startSegments( aCommit, aType, VECTOR2D( cursorPos ) );
935 }
936 // Create a new segment if we're out of previously-created ones
937 else if( !segment->IsNull()
938 || ( twoSegments && !m_wires[m_wires.size() - 2]->IsNull() ) )
939 {
940 // Terminate the command if the end point is on a pin, junction, label, or another
941 // wire or bus.
942 if( screen->IsTerminalPoint( cursorPos, segment->GetLayer() ) )
943 {
944 finishSegments( aCommit );
945 segment = nullptr;
946
947 aCommit.Push( _( "Draw Wires" ) );
948
949 if( aQuitOnDraw )
950 break;
951 }
952 else
953 {
954 int placedSegments = 1;
955
956 // When placing lines with the forty-five degree end, the user is
957 // targetting the endpoint with the angled portion, so it's more
958 // intuitive to place both segments at the same time.
959 if( currentMode == LINE_MODE::LINE_MODE_45 )
960 placedSegments++;
961
962 segment->SetEndPoint( cursorPos );
963
964 for( int i = 0; i < placedSegments; i++ )
965 {
966 // Create a new segment, and chain it after the current segment.
967 segment = static_cast<SCH_LINE*>( segment->Duplicate( true, &aCommit ) );
968 segment->SetFlags( IS_NEW | IS_MOVING );
969 segment->SetStartPoint( cursorPos );
970 m_wires.push_back( segment );
971
972 m_selectionTool->AddItemToSel( segment, true /*quiet mode*/ );
973 }
974 }
975 }
976
977 if( segment && ( evt->IsDblClick( BUT_LEFT )
978 || evt->IsAction( &ACTIONS::cursorDblClick ) ) )
979 {
980 if( twoSegments && m_wires.size() >= 2 )
981 computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture );
982
983 finishSegments( aCommit );
984 segment = nullptr;
985
986 aCommit.Push( _( "Draw Wires" ) );
987
988 if( aQuitOnDraw )
989 break;
990 }
991 }
992 //------------------------------------------------------------------------
993 // Handle motion:
994 //
995 else if( evt->IsMotion() || evt->IsAction( &ACTIONS::refreshPreview ) )
996 {
997 m_view->ClearPreview();
998
999 // Update the bus unfold posture based on the mouse movement
1000 if( m_busUnfold.in_progress && !m_busUnfold.label_placed )
1001 {
1002 VECTOR2I cursor_delta = cursorPos - m_busUnfold.origin;
1003 SCH_BUS_WIRE_ENTRY* entry = m_busUnfold.entry;
1004
1005 bool flipX = ( cursor_delta.x < 0 );
1006 bool flipY = ( cursor_delta.y < 0 );
1007
1008 // Erase and redraw if necessary
1009 if( flipX != m_busUnfold.flipX || flipY != m_busUnfold.flipY )
1010 {
1011 VECTOR2I size = entry->GetSize();
1012 int ySign = flipY ? -1 : 1;
1013 int xSign = flipX ? -1 : 1;
1014
1015 size.x = std::abs( size.x ) * xSign;
1016 size.y = std::abs( size.y ) * ySign;
1017 entry->SetSize( size );
1018
1019 m_busUnfold.flipY = flipY;
1020 m_busUnfold.flipX = flipX;
1021
1022 m_frame->UpdateItem( entry, false, true );
1023 m_wires.front()->SetStartPoint( entry->GetEnd() );
1024 }
1025
1026 // Update the label "ghost" position
1027 m_busUnfold.label->SetPosition( cursorPos );
1028 m_view->AddToPreview( m_busUnfold.label->Clone() );
1029
1030 // Ensure segment is non-null at the start of bus unfold
1031 if( !segment )
1032 segment = m_wires.back();
1033 }
1034
1035 if( segment )
1036 {
1037 // Coerce the line to vertical/horizontal/45 as necessary
1038 if( twoSegments && m_wires.size() >= 2 )
1039 computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture );
1040 else
1041 segment->SetEndPoint( cursorPos );
1042 }
1043
1044 for( SCH_LINE* wire : m_wires )
1045 {
1046 if( !wire->IsNull() )
1047 m_view->AddToPreview( wire->Clone() );
1048 }
1049
1050 std::vector<SCH_ITEM*> previewItems;
1051
1052 for( SCH_LINE* wire : m_wires )
1053 {
1054 if( !wire->IsNull() )
1055 previewItems.push_back( wire );
1056 }
1057
1058 if( m_busUnfold.entry )
1059 previewItems.push_back( m_busUnfold.entry );
1060
1061 for( SCH_JUNCTION* jct : JUNCTION_HELPERS::PreviewJunctions( m_frame->GetScreen(), previewItems ) )
1062 m_view->AddToPreview( jct, true );
1063 }
1064 else if( evt->IsAction( &SCH_ACTIONS::undoLastSegment )
1065 || evt->IsAction( &ACTIONS::doDelete )
1066 || evt->IsAction( &ACTIONS::undo ) )
1067 {
1068 if( ( currentMode == LINE_MODE::LINE_MODE_FREE && m_wires.size() > 1 )
1069 || ( LINE_MODE::LINE_MODE_90 && m_wires.size() > 2 ) )
1070 {
1071 m_view->ClearPreview();
1072
1073 m_wires.pop_back();
1074 m_selectionTool->RemoveItemFromSel( segment );
1075 delete segment;
1076
1077 segment = m_wires.back();
1078 cursorPos = segment->GetEndPoint();
1079 getViewControls()->WarpMouseCursor( cursorPos, true );
1080
1081 // Find new bend point for current mode
1082 if( twoSegments && m_wires.size() >= 2 )
1083 computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture );
1084 else
1085 segment->SetEndPoint( cursorPos );
1086
1087 for( SCH_LINE* wire : m_wires )
1088 {
1089 if( !wire->IsNull() )
1090 m_view->AddToPreview( wire->Clone() );
1091 }
1092 }
1093 else if( evt->IsAction( &ACTIONS::undo ) )
1094 {
1095 // Dispatch as normal undo event
1096 evt->SetPassEvent();
1097 }
1098 else
1099 {
1100 wxBell();
1101 }
1102 }
1103 else if( evt->IsAction( &SCH_ACTIONS::switchSegmentPosture ) && m_wires.size() >= 2 )
1104 {
1105 posture = !posture;
1106
1107 // The 90 degree mode doesn't have a forced posture like
1108 // the 45 degree mode and computeBreakPoint maintains existing 90s' postures.
1109 // Instead, just swap the 90 angle here.
1110 if( currentMode == LINE_MODE::LINE_MODE_90 )
1111 {
1112 m_view->ClearPreview();
1113
1114 SCH_LINE* line2 = m_wires[m_wires.size() - 1];
1115 SCH_LINE* line1 = m_wires[m_wires.size() - 2];
1116
1117 VECTOR2I delta2 = line2->GetEndPoint() - line2->GetStartPoint();
1118 VECTOR2I delta1 = line1->GetEndPoint() - line1->GetStartPoint();
1119
1120 line2->SetStartPoint(line2->GetEndPoint() - delta1);
1121 line1->SetEndPoint(line1->GetStartPoint() + delta2);
1122
1123 for( SCH_LINE* wire : m_wires )
1124 {
1125 if( !wire->IsNull() )
1126 m_view->AddToPreview( wire->Clone() );
1127 }
1128 }
1129 else
1130 {
1131 computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture );
1132
1133 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1134 }
1135 }
1136 //------------------------------------------------------------------------
1137 // Handle context menu:
1138 //
1139 else if( evt->IsClick( BUT_RIGHT ) )
1140 {
1141 // Warp after context menu only if dragging...
1142 if( !segment )
1143 m_toolMgr->VetoContextMenuMouseWarp();
1144
1145 contextMenuPos = cursorPos;
1146 m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
1147 }
1148 else if( evt->Category() == TC_COMMAND && evt->Action() == TA_CHOICE_MENU_CHOICE )
1149 {
1150 if( *evt->GetCommandId() >= ID_POPUP_SCH_UNFOLD_BUS
1151 && *evt->GetCommandId() <= ID_POPUP_SCH_UNFOLD_BUS_END )
1152 {
1153 wxASSERT_MSG( !segment, "Bus unfold event received when already drawing!" );
1154
1155 aType = LAYER_WIRE;
1156 wxString net = *evt->Parameter<wxString*>();
1157 segment = doUnfoldBus( aCommit, net, contextMenuPos );
1158 }
1159 }
1160 //------------------------------------------------------------------------
1161 // Handle TOOL_ACTION special cases
1162 //
1163 else if( evt->IsAction( &SCH_ACTIONS::rotateCW ) || evt->IsAction( &SCH_ACTIONS::rotateCCW ) )
1164 {
1165 if( m_busUnfold.in_progress )
1166 {
1167 m_busUnfold.label->Rotate90( evt->IsAction( &SCH_ACTIONS::rotateCW ) );
1168 busUnfoldPersistentSettings.label_spin_style = m_busUnfold.label->GetSpinStyle();
1169
1170 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1171 }
1172 else
1173 {
1174 wxBell();
1175 }
1176 }
1177 else if( evt->IsAction( &ACTIONS::redo ) )
1178 {
1179 wxBell();
1180 }
1181 else
1182 {
1183 evt->SetPassEvent();
1184 }
1185
1186 // Enable autopanning and cursor capture only when there is a segment to be placed
1187 controls->SetAutoPan( segment != nullptr );
1188 controls->CaptureCursor( segment != nullptr );
1189 }
1190
1191 controls->SetAutoPan( false );
1192 controls->CaptureCursor( false );
1193 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
1194 controls->ForceCursorPosition( false );
1195 return 0;
1196}
1197
1198
1200 SCH_LINE* aSegment )
1201{
1202 if( !aSegment )
1203 aSegment = m_frame->GetScreen()->GetLine( aPos, 0, aType );
1204
1205 if( !aSegment )
1206 {
1207 switch( aType )
1208 {
1209 default: aSegment = new SCH_LINE( aPos, LAYER_NOTES ); break;
1210 case LAYER_WIRE: aSegment = new SCH_LINE( aPos, LAYER_WIRE ); break;
1211 case LAYER_BUS: aSegment = new SCH_LINE( aPos, LAYER_BUS ); break;
1212 }
1213
1214 // Give segments a parent so they find the default line/wire/bus widths
1215 aSegment->SetParent( &m_frame->Schematic() );
1216 }
1217 else
1218 {
1219 aSegment = static_cast<SCH_LINE*>( aSegment->Duplicate( true, &aCommit ) );
1220 aSegment->SetStartPoint( aPos );
1221 }
1222
1223
1224 aSegment->SetFlags( IS_NEW | IS_MOVING );
1225 m_wires.push_back( aSegment );
1226
1227 m_selectionTool->AddItemToSel( aSegment, true /*quiet mode*/ );
1228
1229 // We need 2 segments to go from a given start pin to an end point when the
1230 // horizontal and vertical lines only switch is on.
1231 if( m_frame->eeconfig()->m_Drawing.line_mode )
1232 {
1233 aSegment = static_cast<SCH_LINE*>( aSegment->Duplicate( true, &aCommit ) );
1234 aSegment->SetFlags( IS_NEW | IS_MOVING );
1235 m_wires.push_back( aSegment );
1236
1237 m_selectionTool->AddItemToSel( aSegment, true /*quiet mode*/ );
1238 }
1239
1240 return aSegment;
1241}
1242
1243
1258{
1259 for( auto it = m_wires.begin(); it != m_wires.end(); )
1260 {
1261 SCH_LINE* line = *it;
1262
1263 if( line->IsNull() )
1264 {
1265 delete line;
1266 it = m_wires.erase( it );
1267 continue;
1268 }
1269
1270 auto next_it = it;
1271 ++next_it;
1272
1273 if( next_it == m_wires.end() )
1274 break;
1275
1276 SCH_LINE* next_line = *next_it;
1277
1278 if( SCH_LINE* merged = line->MergeOverlap( m_frame->GetScreen(), next_line, false ) )
1279 {
1280 delete line;
1281 delete next_line;
1282 it = m_wires.erase( it );
1283 *it = merged;
1284 }
1285
1286 ++it;
1287 }
1288}
1289
1290
1292{
1293 // Clear selection when done so that a new wire can be started.
1294 // NOTE: this must be done before simplifyWireList is called or we might end up with
1295 // freed selected items.
1296 m_toolMgr->RunAction( ACTIONS::selectionClear );
1297
1298 SCH_SCREEN* screen = m_frame->GetScreen();
1299
1300 // Remove segments backtracking over others
1302
1303 // Collect the possible connection points for the new lines
1304 std::vector<VECTOR2I> connections = screen->GetConnections();
1305 std::vector<VECTOR2I> new_ends;
1306
1307 // Check each new segment for possible junctions and add/split if needed
1308 for( SCH_LINE* wire : m_wires )
1309 {
1310 if( wire->HasFlag( SKIP_STRUCT ) )
1311 continue;
1312
1313 std::vector<VECTOR2I> tmpends = wire->GetConnectionPoints();
1314
1315 new_ends.insert( new_ends.end(), tmpends.begin(), tmpends.end() );
1316
1317 for( const VECTOR2I& pt : connections )
1318 {
1319 if( IsPointOnSegment( wire->GetStartPoint(), wire->GetEndPoint(), pt ) )
1320 new_ends.push_back( pt );
1321 }
1322
1323 aCommit.Added( wire, screen );
1324 }
1325
1326 if( m_busUnfold.in_progress && m_busUnfold.label_placed )
1327 {
1328 wxASSERT( m_busUnfold.entry && m_busUnfold.label );
1329
1330 aCommit.Added( m_busUnfold.entry, screen );
1331 m_frame->SaveCopyForRepeatItem( m_busUnfold.entry );
1332
1333 aCommit.Added( m_busUnfold.label, screen );
1334 m_frame->AddCopyForRepeatItem( m_busUnfold.label );
1335 m_busUnfold.label->ClearEditFlags();
1336
1337 if( !m_wires.empty() )
1338 m_frame->AddCopyForRepeatItem( m_wires[0] );
1339 }
1340 else if( !m_wires.empty() )
1341 {
1342 m_frame->SaveCopyForRepeatItem( m_wires[0] );
1343 }
1344
1345 for( size_t ii = 1; ii < m_wires.size(); ++ii )
1346 m_frame->AddCopyForRepeatItem( m_wires[ii] );
1347
1348 // Add the new wires
1349 for( SCH_LINE* wire : m_wires )
1350 {
1351 wire->ClearFlags( IS_NEW | IS_MOVING );
1352 m_frame->AddToScreen( wire, screen );
1353 }
1354
1355 m_wires.clear();
1356 m_view->ClearPreview();
1357 m_view->ShowPreview( false );
1358
1359 getViewControls()->CaptureCursor( false );
1360 getViewControls()->SetAutoPan( false );
1361
1362 // Correct and remove segments that need to be merged.
1363 m_frame->Schematic().CleanUp( &aCommit );
1364
1365 std::vector<SCH_ITEM*> symbols;
1366
1367 for( SCH_ITEM* symbol : m_frame->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
1368 symbols.push_back( symbol );
1369
1370 for( SCH_ITEM* symbol : symbols )
1371 {
1372 std::vector<VECTOR2I> pts = symbol->GetConnectionPoints();
1373
1374 if( pts.size() > 2 )
1375 continue;
1376
1377 for( auto pt = pts.begin(); pt != pts.end(); pt++ )
1378 {
1379 for( auto secondPt = pt + 1; secondPt != pts.end(); secondPt++ )
1380 m_frame->TrimWire( &aCommit, *pt, *secondPt );
1381 }
1382 }
1383
1384 for( const VECTOR2I& pt : new_ends )
1385 {
1386 if( m_frame->GetScreen()->IsExplicitJunctionNeeded( pt ) )
1387 AddJunction( &aCommit, m_frame->GetScreen(), pt );
1388 }
1389
1390 if( m_busUnfold.in_progress )
1391 m_busUnfold = {};
1392
1393 for( SCH_ITEM* item : m_frame->GetScreen()->Items() )
1394 item->ClearEditFlags();
1395}
1396
1397
1399{
1401 SCH_SCREEN* screen = sch->CurrentSheet().LastScreen();
1402
1403 std::set<SCH_LINE*> lines;
1404 BOX2I bb = aSelection->GetBoundingBox();
1405
1406 for( EDA_ITEM* item : screen->Items().Overlapping( SCH_LINE_T, bb ) )
1407 lines.insert( static_cast<SCH_LINE*>( item ) );
1408
1409 for( unsigned ii = 0; ii < aSelection->GetSize(); ii++ )
1410 {
1411 SCH_ITEM* item = dynamic_cast<SCH_ITEM*>( aSelection->GetItem( ii ) );
1412
1413 if( !item || !item->IsConnectable() || ( item->Type() == SCH_LINE_T ) )
1414 continue;
1415
1416 std::vector<VECTOR2I> pts = item->GetConnectionPoints();
1417
1420 for( SCH_LINE* line : lines )
1421 {
1422 std::vector<VECTOR2I> conn_pts;
1423
1424 for( const VECTOR2I& pt : pts )
1425 {
1426 if( IsPointOnSegment( line->GetStartPoint(), line->GetEndPoint(), pt ) )
1427 conn_pts.push_back( pt );
1428
1429 if( conn_pts.size() > 2 )
1430 break;
1431 }
1432
1433 if( conn_pts.size() == 2 )
1434 m_frame->TrimWire( aCommit, conn_pts[0], conn_pts[1] );
1435 }
1436 }
1437
1438 return 0;
1439}
1440
1441
1443{
1444 SCH_SCREEN* screen = m_frame->GetScreen();
1445 std::deque<EDA_ITEM*> allItems;
1446
1447 for( EDA_ITEM* item : aSelection->Items() )
1448 {
1449 allItems.push_back( item );
1450
1451 if( item->Type() == SCH_GROUP_T )
1452 {
1453 static_cast<SCH_GROUP*>( item )->RunOnChildren(
1454 [&]( SCH_ITEM* child )
1455 {
1456 allItems.push_back( child );
1458 }
1459 }
1460
1461 for( const VECTOR2I& point : screen->GetNeededJunctions( allItems ) )
1462 {
1463 wxLogTrace( "KICAD_SCH_MOVE", "AddJunctionsIfNeeded: adding junction at %s", point.Format().c_str() );
1464 AddJunction( aCommit, m_frame->GetScreen(), point );
1465 }
1466
1467 return 0;
1468}
1469
1470
1471void SCH_LINE_WIRE_BUS_TOOL::BreakSegment( SCH_COMMIT* aCommit, SCH_LINE* aSegment, const VECTOR2I& aPoint,
1472 SCH_LINE** aNewSegment, SCH_SCREEN* aScreen )
1473{
1474 // Save the copy of aSegment before breaking it
1475 aCommit->Modify( aSegment, aScreen );
1476
1477 SCH_LINE* newSegment = aSegment->BreakAt( aCommit, aPoint );
1478
1479 aSegment->SetFlags( IS_CHANGED | IS_BROKEN );
1480 newSegment->SetFlags( IS_NEW | IS_BROKEN );
1481 m_frame->AddToScreen( newSegment, aScreen );
1482
1483 aCommit->Added( newSegment, aScreen );
1484
1485 *aNewSegment = newSegment;
1486}
1487
1488
1490{
1491 bool brokenSegments = false;
1492 SCH_LINE* new_line;
1493
1494 for( SCH_LINE* wire : aScreen->GetBusesAndWires( aPos, true ) )
1495 {
1496 BreakSegment( aCommit, wire, aPos, &new_line, aScreen );
1497 brokenSegments = true;
1498 }
1499
1500 return brokenSegments;
1501}
1502
1503
1505{
1506 bool brokenSegments = false;
1507
1508 std::set<VECTOR2I> point_set;
1509
1510 for( SCH_ITEM* item : aScreen->Items().OfType( SCH_JUNCTION_T ) )
1511 point_set.insert( item->GetPosition() );
1512
1513 for( SCH_ITEM* item : aScreen->Items().OfType( SCH_BUS_WIRE_ENTRY_T ) )
1514 {
1515 SCH_BUS_WIRE_ENTRY* entry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
1516 point_set.insert( entry->GetPosition() );
1517 point_set.insert( entry->GetEnd() );
1518 }
1519
1520 for( const VECTOR2I& pt : point_set )
1521 {
1522 BreakSegments( aCommit, pt, aScreen );
1523 brokenSegments = true;
1524 }
1525
1526 return brokenSegments;
1527}
1528
1529
1531 const VECTOR2I& aPos )
1532{
1533 SCH_JUNCTION* junction = new SCH_JUNCTION( aPos );
1534
1535 if( aScreen->GetBus( aPos ) )
1536 junction->SetLayer( LAYER_BUS_JUNCTION );
1537
1538 m_frame->AddToScreen( junction, aScreen );
1539 aCommit->Added( junction, aScreen );
1540
1541 BreakSegments( aCommit, aPos, aScreen );
1542
1543 return junction;
1544}
1545
1546
const char * name
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
static TOOL_ACTION cursorDblClick
Definition actions.h:177
static TOOL_ACTION undo
Definition actions.h:71
static TOOL_ACTION doDelete
Definition actions.h:81
static TOOL_ACTION cursorClick
Definition actions.h:176
static TOOL_ACTION redo
Definition actions.h:72
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION refreshPreview
Definition actions.h:155
static TOOL_ACTION finishInteractive
Definition actions.h:69
ACTION_MENU(bool isContextMenu, TOOL_INTERACTIVE *aTool=nullptr)
Default constructor.
void Clear()
Remove all the entries from the menu (as well as its title).
void SetTitle(const wxString &aTitle) override
Set title for the menu.
void SetTool(TOOL_INTERACTIVE *aTool)
Set a tool that is the creator of the menu.
void SetIcon(BITMAPS aIcon)
Assign an icon for the entry.
TOOL_INTERACTIVE * m_tool
Creator of the menu.
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
ACTION_MENU * create() const override
Return an instance of this class. It has to be overridden in inheriting classes.
static std::vector< MEMBER > memberEntries(const SCH_LINE &aBus)
bool PassHelpTextToHandler() override
BUS_UNFOLD_MENU(BUS_GETTER aBusGetter)
void update() override
Update menu state stub.
COMMIT & Added(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Notify observers that aItem has been added.
Definition commit.h:80
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
void AddMenu(ACTION_MENU *aMenu, const SELECTION_CONDITION &aCondition=SELECTION_CONDITIONS::ShowAlways, int aOrder=ANY_ORDER)
Add a submenu to the menu.
static void sort_dangling_end_items(std::vector< DANGLING_END_ITEM > &aItemListByType, std::vector< DANGLING_END_ITEM > &aItemListByPos)
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:158
const KIID m_Uuid
Definition eda_item.h:599
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
bool IsNew() const
Definition eda_item.h:131
EE_TYPE Overlapping(const BOX2I &aRect) const
Definition sch_rtree.h:253
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
An interface for classes handling user events controlling the view behavior such as zooming,...
virtual void CaptureCursor(bool aEnabled)
Force the cursor to stay within the drawing panel area.
virtual void ForceCursorPosition(bool aEnabled, const VECTOR2D &aPosition=VECTOR2D(0, 0))
Place the cursor immediately at a given point.
virtual void ShowCursor(bool aEnabled)
Enable or disables display of cursor.
virtual void WarpMouseCursor(const VECTOR2D &aPosition, bool aWorldCoordinates=false, bool aWarpView=false)=0
If enabled (.
virtual void SetCrossHairCursorPosition(const VECTOR2D &aPosition, bool aWarpView=true)=0
Move the graphic crosshair cursor to the requested position expressed in world coordinates.
virtual VECTOR2D GetMousePosition(bool aWorldCoordinates=true) const =0
Return the current mouse pointer position.
virtual void SetAutoPan(bool aEnabled)
Turn on/off auto panning (this feature is used when there is a tool active (eg.
These are loaded from Eeschema settings but then overwritten by the project settings.
Holds all the data relating to one schematic.
Definition schematic.h:149
SCH_CONNECTIVITY::FACADE & Connectivity() const
Definition schematic.h:324
SCH_SHEET_PATH & CurrentSheet() const
Definition schematic.h:312
static TOOL_ACTION rotateCCW
static TOOL_ACTION placeClassLabel
Definition sch_actions.h:85
static TOOL_ACTION clearHighlight
static TOOL_ACTION placeGlobalLabel
Definition sch_actions.h:86
static TOOL_ACTION leaveSheet
static TOOL_ACTION breakWire
static TOOL_ACTION drawLines
static TOOL_ACTION placeHierLabel
Definition sch_actions.h:87
static TOOL_ACTION selectConnection
If current selection is a wire or bus, expand to entire connection.
Definition sch_actions.h:57
static TOOL_ACTION placeLabel
Definition sch_actions.h:84
static TOOL_ACTION drawWire
Definition sch_actions.h:78
static TOOL_ACTION rotateCW
static TOOL_ACTION placeJunction
Definition sch_actions.h:82
static TOOL_ACTION selectNode
Select the junction, wire or bus segment under the cursor.
Definition sch_actions.h:53
static TOOL_ACTION unfoldBus
Definition sch_actions.h:80
static TOOL_ACTION drawBus
Definition sch_actions.h:79
static TOOL_ACTION slice
static TOOL_ACTION undoLastSegment
static TOOL_ACTION switchSegmentPosture
VECTOR2I GetSize() const
void SetSize(const VECTOR2I &aSize)
VECTOR2I GetPosition() const override
VECTOR2I GetEnd() const
Class for a wire to bus entry.
Commit for the schematic and symbol editors.
Definition sch_commit.h:54
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
bool IsBus() const
const std::vector< std::shared_ptr< SCH_CONNECTION > > & Members() const
static wxString PrintBusForUI(const wxString &aString)
std::optional< ITEM_VIEW > Connection(const KIID &aItem, const KIID_PATH &aPath) const
Schematic editor (Eeschema) main window.
const wxString & GetHighlightedConnection() const
A set of SCH_ITEMs (i.e., without duplicates).
Definition sch_group.h:48
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
SCH_ITEM * Duplicate(bool addToParentGroup, SCH_COMMIT *aCommit=nullptr, bool aDoClone=false) const
Routine to create a new copy of given item.
Definition sch_item.cpp:191
virtual bool IsConnectable() const
Definition sch_item.h:550
SCHEMATIC * Schematic() const
Search the item hierarchy to find a SCHEMATIC.
Definition sch_item.cpp:302
void SetLayer(SCH_LAYER_ID aLayer)
Definition sch_item.h:354
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition sch_item.h:353
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:563
void computeBreakPoint(const std::pair< SCH_LINE *, SCH_LINE * > &aSegments, VECTOR2I &aPosition, LINE_MODE mode, bool posture)
Compute the middle coordinate for 2 segments from the start point to aPosition with the segments kept...
int DrawSegments(const TOOL_EVENT &aEvent)
bool BreakSegmentsOnJunctions(SCH_COMMIT *aCommit, SCH_SCREEN *aScreen)
Test all junctions and bus entries in the schematic for intersections with wires and buses and breaks...
void setTransitions() override
Set up handlers for various events.
int AddJunctionsIfNeeded(SCH_COMMIT *aCommit, SCH_SELECTION *aSelection)
Handle the addition of junctions to a selection of objects.
SCH_JUNCTION * AddJunction(SCH_COMMIT *aCommit, SCH_SCREEN *aScreen, const VECTOR2I &aPos)
int UnfoldBus(const TOOL_EVENT &aEvent)
SCH_LINE * startSegments(SCH_COMMIT &aCommit, int aType, const VECTOR2D &aPos, SCH_LINE *aSegment=nullptr)
bool Init() override
Init() is called once upon a registration of the tool.
int TrimOverLappingWires(SCH_COMMIT *aCommit, SCH_SELECTION *aSelection)
Logic to remove wires when overlapping correct items.
const SCH_SHEET_PIN * getSheetPin(const VECTOR2I &aPosition)
Search for a sheet pin at a location.
SCH_LINE * doUnfoldBus(SCH_COMMIT &aCommit, const wxString &aNet, const std::optional< VECTOR2I > &aPos=std::nullopt)
Unfold the given bus from the given position.
SCH_LINE * getBusForUnfolding()
Choose a bus to unfold based on the current tool selection.
bool BreakSegments(SCH_COMMIT *aCommit, const VECTOR2I &aPoint, SCH_SCREEN *aScreen)
Check every wire and bus for a intersection at aPoint and break into two segments at aPoint if an int...
std::vector< SCH_LINE * > m_wires
static bool IsDrawingLineWireOrBus(const SELECTION &aSelection)
void BreakSegment(SCH_COMMIT *aCommit, SCH_LINE *aSegment, const VECTOR2I &aPoint, SCH_LINE **aNewSegment, SCH_SCREEN *aScreen)
Break a single segment into two at the specified point.
void simplifyWireList()
Iterate over the wire list and removes the null segments and overlapping segments to create a simplif...
int doDrawSegments(const TOOL_EVENT &aTool, SCH_COMMIT &aCommit, int aType, bool aQuitOnDraw)
void finishSegments(SCH_COMMIT &aCommit)
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
void SetStartPoint(const VECTOR2I &aPosition)
Definition sch_line.h:137
VECTOR2I GetEndPoint() const
Definition sch_line.h:145
VECTOR2I GetStartPoint() const
Definition sch_line.h:136
SEG GetSeg() const
Get the geometric aspect of the wire as a SEG.
Definition sch_line.h:155
SCH_LINE * MergeOverlap(SCH_SCREEN *aScreen, SCH_LINE *aLine, bool aCheckJunctions)
Check line against aLine to see if it overlaps and merge if it does.
Definition sch_line.cpp:548
bool IsNull() const
Definition sch_line.h:134
SCH_LINE * BreakAt(SCH_COMMIT *aCommit, const VECTOR2I &aPoint)
Break this segment into two at the specified point.
Definition sch_line.cpp:671
void SetEndPoint(const VECTOR2I &aPosition)
Definition sch_line.h:146
std::vector< SCH_LINE * > GetBusesAndWires(const VECTOR2I &aPosition, bool aIgnoreEndpoints=false) const
Return buses and wires passing through aPosition.
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
bool IsTerminalPoint(const VECTOR2I &aPosition, int aLayer) const
Test if aPosition is a connection point on aLayer.
SCH_LINE * GetBus(const VECTOR2I &aPosition, int aAccuracy=0, SCH_LINE_TEST_T aSearchType=ENTIRE_LENGTH_T) const
Definition sch_screen.h:519
std::vector< VECTOR2I > GetNeededJunctions(const std::deque< EDA_ITEM * > &aItems) const
Return the unique set of points belonging to aItems where a junction is needed.
std::vector< VECTOR2I > GetConnections() const
Collect a unique list of all possible connection points in the schematic.
BOX2I GetBoundingBox() const override
SCH_SCREEN * LastScreen()
const KIID_PATH & PathRef() const
Borrow the cached path until this sheet path is modified or destroyed.
Define a sheet pin (label) used in sheets to create hierarchical schematics.
SHEET_SIDE GetSide() const
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition sch_sheet.h:249
VECTOR2I GetPosition() const override
Definition sch_text.h:143
bool Init() override
Init() is called once upon a registration of the tool.
SCH_TOOL_BASE(const std::string &aName)
SCH_SELECTION_TOOL * m_selectionTool
const VECTOR2I NearestPoint(const VECTOR2I &aP) const
Compute a point on the segment (this) that is closest to point aP.
Definition seg.cpp:599
static SELECTION_CONDITION MoreThan(int aNumber)
Create a functor that tests if the number of selected items is greater than the value given as parame...
static bool Idle(const SELECTION &aSelection)
Test if there no items selected or being edited.
static bool ShowAlways(const SELECTION &aSelection)
The default condition function (always returns true).
static SELECTION_CONDITION OnlyTypes(std::vector< KICAD_T > aTypes)
Create a functor that tests if the selected items are only of given types.
virtual KIGFX::VIEW_ITEM * GetItem(unsigned int aIdx) const override
Definition selection.cpp:75
virtual unsigned int GetSize() const override
Return the number of stored items.
Definition selection.h:104
EDA_ITEM * Front() const
Definition selection.h:176
std::deque< EDA_ITEM * > & Items()
Definition selection.h:181
Spin style for labels of all kinds on schematics.
Definition sch_label.h:38
SCH_EDIT_FRAME * getModel() const
Definition tool_base.h:195
KIGFX::VIEW_CONTROLS * getViewControls() const
Definition tool_base.cpp:40
KIGFX::VIEW * getView() const
Definition tool_base.cpp:34
Generic, UI-independent tool event.
Definition tool_event.h:167
bool HasPosition() const
Returns if it this event has a valid position (true for mouse events and context-menu or hotkey-based...
Definition tool_event.h:256
bool DisableGridSnapping() const
Definition tool_event.h:367
const VECTOR2D Position() const
Return mouse cursor position in world coordinates.
Definition tool_event.h:289
int Modifier(int aMask=MD_MODIFIER_MASK) const
Return information about key modifiers state (Ctrl, Alt, etc.).
Definition tool_event.h:362
T Parameter() const
Return a parameter assigned to the event.
Definition tool_event.h:484
void SetContextMenu(ACTION_MENU *aMenu, CONTEXT_MENU_TRIGGER aTrigger=CMENU_BUTTON)
void Go(int(SCH_EDIT_FRAME::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
std::unique_ptr< TOOL_MENU > m_menu
TOOL_EVENT * Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
@ LINE_BUS
Definition cursors.h:87
@ LINE_WIRE
Definition cursors.h:91
@ ARROW
Definition cursors.h:43
@ LINE_GRAPHIC
Definition cursors.h:89
#define _(s)
@ RECURSE
Definition eda_item.h:51
#define IS_CHANGED
Item was edited, and modified.
#define IS_NEW
New item, just created.
#define IS_BROKEN
Is a segment just broken by BreakSegment.
#define SKIP_STRUCT
flag indicating that the structure should be ignored
#define IS_MOVING
Item being moved.
@ ID_POPUP_SCH_UNFOLD_BUS_END
Definition eeschema_id.h:78
@ ID_POPUP_SCH_UNFOLD_BUS
Definition eeschema_id.h:77
@ LINE_MODE_90
@ LINE_MODE_45
@ LINE_MODE_FREE
GRID_HELPER_GRIDS
Definition grid_helper.h:55
@ GRID_GRAPHICS
Definition grid_helper.h:63
@ GRID_WIRES
Definition grid_helper.h:60
@ LAYER_WIRE
Definition layer_ids.h:480
@ LAYER_NOTES
Definition layer_ids.h:495
@ LAYER_BUS
Definition layer_ids.h:481
@ LAYER_BUS_JUNCTION
Definition layer_ids.h:526
std::vector< SCH_JUNCTION * > PreviewJunctions(const 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...
bool signbit(T v)
Integral version of std::signbit that works all compilers.
Definition kicad_algo.h:172
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
@ BUS
This item represents a bus vector.
Class to handle a set of SCH_ITEMs.
std::function< SCH_LINE *()> BUS_GETTER
static BUS_UNFOLD_PERSISTENT_SETTINGS busUnfoldPersistentSettings
SHEET_SIDE
Define the edge of the sheet that the sheet pin is positioned.
std::vector< wxString > members
Settings for bus unfolding that are persistent across invocations of the tool.
Query-time snapshot; reacquire after Update.
std::shared_ptr< const BUS_SCHEMA > schema
std::shared_ptr< const BUS_SCHEMA::NODE > tree
The parsed form of one bus text.
Definition conn_bus.h:48
KIBIS_PIN * pin
wxString result
Test unit parsing edge cases and error handling.
int delta
@ TA_CHOICE_MENU_CHOICE
Context menu choice.
Definition tool_event.h:94
@ TA_CHOICE_MENU_CLOSED
Context menu is closed, no matter whether anything has been chosen or not.
Definition tool_event.h:97
@ CMENU_NOW
Right now (after TOOL_INTERACTIVE::SetContextMenu).
Definition tool_event.h:152
@ MD_SHIFT
Definition tool_event.h:139
@ TC_COMMAND
Definition tool_event.h:53
@ BUT_LEFT
Definition tool_event.h:128
@ BUT_RIGHT
Definition tool_event.h:129
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:87
@ SCH_GROUP_T
Definition typeinfo.h:169
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_ITEM_LOCATE_BUS_T
Definition typeinfo.h:183
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:157
@ SCH_JUNCTION_T
Definition typeinfo.h:155
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707