KiCad PCB EDA Suite
Loading...
Searching...
No Matches
constraint_edit_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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU 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
21
22#include <algorithm>
23#include <cmath>
24#include <ranges>
25#include <set>
26
27#include <bitmaps.h>
28#include <collectors.h>
29#include <core/kicad_algo.h>
30#include <tool/tool_manager.h>
34#include <pcb_shape.h>
35#include <board.h>
36#include <board_commit.h>
37#include <footprint.h>
38#include <tools/pcb_actions.h>
39#include <tools/pcb_selection.h>
43#include <view/view.h>
44#include <view/view_controls.h>
46#include <pcb_painter.h>
47#include <tool/actions.h>
48#include <tool/edit_points.h>
49#include <widgets/wx_infobar.h>
50#include <widgets/msgpanel.h>
51#include <pcb_base_frame.h>
55#include <pcb_edit_frame.h>
57#include <pcbnew_settings.h>
62
63#include <base_units.h>
64#include <geometry/seg.h>
65
66
68 PCB_TOOL_BASE( "pcbnew.ConstraintEditor" ),
69 m_selectionTool( nullptr ),
70 m_menu( nullptr )
71{
72}
73
74
76{
77 // The board (and its view) is being torn down or reloaded; drop the overlay so it does not
78 // dangle on a stale view.
79 m_overlay.reset();
80
81 // Reload may reuse a KIID for a new item
82 // Drop cached diagnosis so it never survives stale
83 m_diagnoser.Clear();
84
85 // Per-type values survive an ordinary redraw or canvas switch
86 // Only a model reload clears them
87 if( aReason == MODEL_RELOAD || aReason == SUPERMODEL_RELOAD )
88 {
91 }
92
93 // At shutdown the frame/info bar may already be tearing down, so don't touch UI then.
94 if( aReason == SHUTDOWN )
95 return;
96
97 // The overlay is long-lived so hover can reveal constraints at any time; the sticky setting only
98 // chooses whether it always shows everything (ALWAYS) or reveals on hover (HOVER).
99 if( frame() && board() && getView() )
100 {
101 m_overlay = std::make_unique<CONSTRAINT_OVERLAY>( board(), getView() );
102 m_overlay->SetVisibilityMode( frame()->GetPcbNewSettings()->m_Display.m_ShowConstraints
105 }
106
107 refreshDiagnostics(); // marks the diagnosis dirty and re-renders
108}
109
110
112{
113 // A real board selection supersedes a badge selection (and keeps Delete unambiguous). This
114 // does not fire for SelectConstraintAt's own ClearSelection, which leaves the board empty.
115 if( m_selectionTool && !m_selectionTool->GetSelection().Empty() )
116 setSelectedConstraint( nullptr );
117
118 // A canvas selection shows all constraints again.
119 if( m_overlay && m_overlay->SetIsolated( niluuid ) )
120 m_overlay->RefreshSelection();
121
122 return 0;
123}
124
125
127{
128 if( !m_overlay || !board() )
129 return nullptr;
130
131 // Hit-test against the exact positions the badges draw at (same LayoutBadges call), so a click
132 // and a glyph can never drift apart at any zoom.
133 double worldPerPx = CONSTRAINT_OVERLAY::BadgeWorldPerPixel( getView()->GetGAL()->GetWorldScale() );
134 double best = CONSTRAINT_OVERLAY::BadgeHitRadius() * worldPerPx;
135
136 const std::vector<CONSTRAINT_BADGE>& badges = m_overlay->Badges();
137 std::vector<VECTOR2D> layout = CONSTRAINT_OVERLAY::LayoutBadges( badges, worldPerPx );
138 PCB_CONSTRAINT* result = nullptr;
139
140 for( size_t i = 0; i < badges.size(); ++i )
141 {
142 double dist = ( layout[i] - VECTOR2D( aPos ) ).EuclideanNorm();
143
144 if( dist <= best )
145 {
146 best = dist;
147 result = dynamic_cast<PCB_CONSTRAINT*>( board()->ResolveItem( badges[i].constraint, true ) );
148 }
149 }
150
151 return result;
152}
153
154
156{
157 if( !m_overlay )
158 return;
159
160 if( m_overlay->SetSelected( aConstraint ? aConstraint->m_Uuid : niluuid ) )
161 m_overlay->RefreshSelection();
162
163 // Highlight members with a brighter thicker shadow
165
166 if( aConstraint )
167 {
168 if( PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( frame() ) )
169 {
170 if( PANEL_CONSTRAINTS* panel = pcbFrame->GetConstraintsPanel() )
171 panel->SelectConstraint( aConstraint->m_Uuid );
172 }
173 }
174
175 updateConstraintMsgPanel( aConstraint );
176}
177
178
180{
181 if( !frame() )
182 return;
183
184 if( !aConstraint || !board() )
185 {
186 // Restore the default board readout (Pads/Vias/Tracks), unless a board selection owns the
187 // panel now.
188 if( board() && m_selectionTool && m_selectionTool->GetSelection().Empty() )
189 frame()->SetMsgPanel( board() );
190
191 return;
192 }
193
194 std::vector<MSG_PANEL_ITEM> items;
195
196 items.emplace_back( _( "Constraint" ), ConstraintDisplayLabel( *aConstraint, frame()->GetUserUnits() ) );
197
198 wxString members;
199
200 for( const CONSTRAINT_MEMBER& member : aConstraint->GetMembers() )
201 {
202 if( !members.IsEmpty() )
203 members += wxT( ", " );
204
205 members += ConstraintMemberLabel( board()->ResolveItem( member.m_item, true ), member, frame() );
206 }
207
208 items.emplace_back( _( "Items" ), members );
209
211 wxString state = _( "OK" );
212
213 if( alg::contains( diag.errored, aConstraint->m_Uuid ) )
214 state = _( "Error (missing item)" );
215 else if( alg::contains( diag.conflicting, aConstraint->m_Uuid ) )
216 state = _( "Over-constrained" );
217 else if( alg::contains( diag.redundant, aConstraint->m_Uuid ) )
218 state = _( "Redundant" );
219
220 items.emplace_back( _( "State" ), state );
221
222 frame()->SetMsgPanel( items );
223}
224
225
227{
228 PCB_CONSTRAINT* constraint = hitTestBadge( aPos );
229
230 if( !constraint )
231 return false;
232
233 // A constraint is not a board selection; clear it so a following Delete targets the relation.
234 m_selectionTool->ClearSelection();
235 setSelectedConstraint( constraint );
236 return true;
237}
238
239
241{
242 PCB_CONSTRAINT* constraint = hitTestBadge( aPos );
243
244 if( !constraint )
245 return false;
246
247 setSelectedConstraint( constraint );
248 editConstraint( constraint );
249 return true;
250}
251
252
254{
255 bool wasSelected = m_overlay && m_overlay->GetSelected() != niluuid;
256 setSelectedConstraint( nullptr );
257 return wasSelected;
258}
259
260
262{
263 if( IsFootprintEditor() && board() )
264 return board()->GetFirstFootprint();
265
266 return board();
267}
268
269
271{
272 // Called when the model changed, so the cached diagnosis is stale. A bare re-render (hover) uses
273 // renderConstraintViews() directly and keeps the cache.
274 m_diagDirty = true;
275 m_hoverCandidateIds.reset(); // the constrained-shape set may have changed too
277}
278
279
281{
282 // The overlay tint/badges, the info bar, and the docked list all read the same board-wide
283 // diagnosis; solve it once here and hand the result to each so a model change costs one solve,
284 // not one per view. Refresh the panel only while it is shown so a hidden pane costs nothing.
285 PANEL_CONSTRAINTS* panel = nullptr;
286
287 if( PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( frame() ) )
288 panel = pcbFrame->GetConstraintsPanel();
289
290 bool panelShown = panel && panel->IsShownOnScreen();
291
292 // With a long-lived overlay, only actually solve when something reads the result: ALWAYS mode, an
293 // active hover, or the docked panel. A HOVER-idle canvas costs nothing.
294 bool overlayActive = m_overlay
295 && ( m_overlay->GetVisibilityMode() == OVERLAY_MODE::ALWAYS
296 || m_overlay->GetHoverShape() != niluuid );
297
298 if( !overlayActive && !panelShown )
299 {
300 if( m_overlay )
301 {
302 m_overlay->SetIsolated( niluuid ); // a panel isolation must not linger while hidden
303 m_overlay->Update( {} ); // draw nothing while hidden
304 }
305
306 // Hidden view computes no fresh diagnosis
307 // Drop the shadow sets too rather than leave stale ones under the now hidden items
310
311 return;
312 }
313
315}
316
317
319{
320 if( m_diagDirty )
321 {
322 m_cachedDiag = m_diagnoser.Diagnose( board() );
323 m_diagDirty = false;
324 }
325
326 return m_cachedDiag;
327}
328
329
331{
334
335 return *m_hoverCandidateIds;
336}
337
338
340{
341 // Cheapest guards first: only HOVER mode with constraints on the board does any work. Waiting
342 // tools (selection, router, move) already saw this motion before the transition loop reached us,
343 // so there is nothing to forward.
344 if( !m_overlay || m_overlay->GetVisibilityMode() != OVERLAY_MODE::HOVER || !board()
345 || !BoardHasConstraints( board() ) )
346 {
347 return 0;
348 }
349
352
353 // Stay sticky while the cursor is still over the current shape or one of its badges, so a badge
354 // does not blink out as the pointer moves off the thin outline toward it.
355 if( m_overlay->GetHoverShape() != niluuid )
356 {
357 if( PCB_SHAPE* shape =
358 dynamic_cast<PCB_SHAPE*>( board()->ResolveItem( m_overlay->GetHoverShape(), true ) ) )
359 {
360 double worldPerPx = CONSTRAINT_OVERLAY::BadgeWorldPerPixel( getView()->GetGAL()->GetWorldScale() );
361 std::vector<VECTOR2D> layout = CONSTRAINT_OVERLAY::LayoutBadges( m_overlay->Badges(), worldPerPx );
362 double badgeTol = CONSTRAINT_OVERLAY::BadgeHitRadius() * worldPerPx;
363
364 bool overBadge = std::ranges::any_of( layout,
365 [&]( const VECTOR2D& aPos )
366 { return ( aPos - VECTOR2D( cursor ) ).EuclideanNorm() <= badgeTol; } );
367
368 if( overBadge || shape->HitTest( cursor, KiROUND( tol ) ) )
369 return 0; // keep the current hover
370 }
371 }
372
373 std::optional<KIID> hit = NearestConstrainedShape( board(), hoverCandidates(), cursor, KiROUND( tol ) );
374
375 // Only the hover filter changed, not the model, so redraw just the overlay from the cached
376 // diagnosis -- the panel (its row selection) and the info bar are left untouched.
377 if( m_overlay->SetHoverShape( hit.value_or( niluuid ) ) )
378 m_overlay->Update( ensureDiagnosis() );
379
380 return 0;
381}
382
383
385{
386 if( !getView() || !getView()->GetPainter() )
387 return nullptr;
388
389 return dynamic_cast<KIGFX::PCB_RENDER_SETTINGS*>( getView()->GetPainter()->GetSettings() );
390}
391
392
394{
396
397 if( !rs )
398 return;
399
400 std::unordered_set<KIID> constrained;
401
402 for( const auto& entry : aDiag.shapeStates )
403 constrained.insert( entry.first );
404
405 // Shadow is a cached layer so re-cache each item whose constrained state changed
406 // Set the new set BEFORE the re-cache so ViewGetLOD reads fresh membership on redraw
407 std::unordered_set<KIID> previous = rs->GetConstrainedItems();
408 rs->SetConstrainedItems( constrained );
409 repaintShadowItems( previous, constrained );
410}
411
412
414{
416
417 if( !rs )
418 return;
419
420 std::unordered_set<KIID> members;
421
422 if( aConstraint )
423 {
424 for( const CONSTRAINT_MEMBER& member : aConstraint->GetMembers() )
425 members.insert( member.m_item );
426 }
427
428 // Re-cache members whose highlight changed so the brighter shadow follows the badge selection
429 // Set the new membership before the re-cache so the redraw reads it
430 std::unordered_set<KIID> previous = rs->GetHighlightedConstraintMembers();
431 rs->SetHighlightedConstraintMembers( members );
432 repaintShadowItems( previous, members );
433}
434
435
436void CONSTRAINT_EDIT_TOOL::repaintShadowItems( const std::unordered_set<KIID>& aOld,
437 const std::unordered_set<KIID>& aNew )
438{
439 if( !board() || !getView() )
440 return;
441
442 // Re-cache only the symmetric difference items rather than the whole board every time
443 // ALL forces the regen a shape first cached unconstrained needs before it has shadow geometry
444 auto repaint = [&]( const KIID& aId )
445 {
446 if( BOARD_ITEM* item = board()->ResolveItem( aId, true ) )
447 getView()->Update( item, KIGFX::ALL );
448 };
449
450 for( const KIID& id : aOld )
451 {
452 if( !aNew.contains( id ) )
453 repaint( id );
454 }
455
456 for( const KIID& id : aNew )
457 {
458 if( !aOld.contains( id ) )
459 repaint( id );
460 }
461}
462
463
465{
466 if( m_overlay )
467 m_overlay->Update( aDiag );
468
469 // The constraint shadow layer draws only for items in this set a constant time gate at draw time
470 updateConstrainedItems( aDiag );
471
472 if( PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( frame() ) )
473 {
474 if( PANEL_CONSTRAINTS* panel = pcbFrame->GetConstraintsPanel(); panel && panel->IsShownOnScreen() )
475 panel->RefreshList( aDiag );
476 }
477}
478
479
481{
482 // Remember the member shapes so their clusters can re-settle once the constraint is gone.
483 std::vector<PCB_SHAPE*> members;
484
485 for( const CONSTRAINT_MEMBER& member : aConstraint->GetMembers() )
486 {
487 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( board()->ResolveItem( member.m_item, true ) ) )
488 members.push_back( shape );
489 }
490
491 BOARD_COMMIT commit( this );
492 commit.Remove( aConstraint );
493 commit.Push( _( "Remove Geometric Constraint" ) );
494
495 SolveAfterMove( members );
496
498}
499
500
502{
503 if( !board() || aId == niluuid )
504 return nullptr;
505
506 return dynamic_cast<PCB_CONSTRAINT*>( board()->ResolveItem( aId, true ) );
507}
508
509
511{
512 if( PCB_CONSTRAINT* constraint = resolveConstraint( aId ) )
513 removeConstraint( constraint );
514}
515
516
518{
519 if( !aConstraint )
520 return;
521
522 BOARD_COMMIT commit( this );
523
524 if( EditConstraintValue( frame(), aConstraint, commit ) )
525 {
526 // The committed value becomes the remembered default for the next creation of this type
527 // matching what the creation dialog stores
528 if( aConstraint->HasValue() )
529 {
530 m_lastConstraintValue[aConstraint->GetConstraintType()] = *aConstraint->GetValue();
531 m_lastConstraintDriving[aConstraint->GetConstraintType()] = aConstraint->IsDriving();
532 }
533
535 }
536}
537
538
540{
541 PCB_CONSTRAINT* constraint = resolveConstraint( aId );
542
543 if( !constraint )
544 return;
545
546 // A valueless relation has nothing to edit, so locate its members on the canvas instead of
547 // ignoring the gesture, matching the modal list's double-click behavior.
548 if( !constraint->HasValue() )
549 {
551 return;
552 }
553
554 editConstraint( constraint );
555}
556
557
558void CONSTRAINT_EDIT_TOOL::HighlightConstraintMembers( const KIID& aId, int aMemberIndex )
559{
560 PCB_CONSTRAINT* constraint = resolveConstraint( aId );
561
562 if( !constraint )
563 return;
564
565 // Selecting the members is a board selection, so drop any badge selection to keep the two
566 // selection models mutually exclusive (Delete then targets the highlighted members).
567 setSelectedConstraint( nullptr );
568 m_selectionTool->ClearSelection();
569
570 const std::vector<CONSTRAINT_MEMBER>& members = constraint->GetMembers();
571
572 // A click on a blank item cell (index past the members) falls back to highlighting all.
573 bool highlightOne = aMemberIndex >= 0 && aMemberIndex < static_cast<int>( members.size() );
574
575 auto selectMember = [&]( const CONSTRAINT_MEMBER& aMember )
576 {
577 if( BOARD_ITEM* item = board()->ResolveItem( aMember.m_item, true ) )
578 m_selectionTool->select( item );
579 };
580
581 if( highlightOne )
582 selectMember( members[aMemberIndex] );
583 else
584 std::ranges::for_each( members, selectMember );
585
586 // Zoom to what we just selected so the affected items fill the view.
587 if( !m_selectionTool->GetSelection().Empty() )
589
590 // Set this after selecting, which clears the isolate.
591 if( m_overlay && m_overlay->SetIsolated( aId ) )
592 m_overlay->RefreshSelection();
593}
594
595
597{
598 if( !board() || !aConstraint || aConstraint->GetMembers().empty() )
599 return false;
600
601 return std::ranges::all_of( aConstraint->GetMembers(),
602 [&]( const CONSTRAINT_MEMBER& aMember )
603 {
604 BOARD_ITEM* item = board()->ResolveItem( aMember.m_item, true );
605 return item && ConstraintItemIsLocked( item );
606 } );
607}
608
609
611{
612 if( !aConstraint || !board() )
613 return false;
614
615 auto scan = [&]( const CONSTRAINTS& aList )
616 {
617 return std::ranges::any_of( aList,
618 [&]( const PCB_CONSTRAINT* aExisting )
619 {
620 return aExisting != aConstraint && ConstraintsAreDuplicate( *aExisting, *aConstraint );
621 } );
622 };
623
624 if( scan( board()->Constraints() ) )
625 return true;
626
627 return board()->GetFirstFootprint() && scan( board()->GetFirstFootprint()->Constraints() );
628}
629
630
632 const std::vector<PCB_CONSTRAINT*>& aAdded )
633{
634 aCommit.Push( _( "Add Geometric Constraint" ) );
635
636 // Solving any one constraint pins its first member and pulls the rest of the cluster into place.
637 if( !aAdded.empty() )
638 solveAddedConstraint( aAdded.front() );
639
641
642 if( aAdded.empty() || !frame() )
643 return;
644
645 // A locked shape is a fixed reference the solver may not move. If every referenced item is
646 // locked there is nothing it can adjust, so the relation is recorded but the geometry cannot
647 // snap; tell the user rather than appearing to silently do nothing. A fix asks for exactly
648 // that outcome, so on a locked item it is redundant, not thwarted.
649 if( aAdded.front()->GetConstraintType() != PCB_CONSTRAINT_TYPE::FIXED_POSITION
650 && allMembersLocked( aAdded.front() ) )
651 {
652 frame()->ShowInfoBarWarning( _( "All items referenced by this constraint are locked, so the constraint cannot "
653 "move any geometry." ),
654 true );
655 return;
656 }
657
658 // In the default configuration (hover overlay idle, panel hidden) nothing else surfaces the
659 // diagnosis, so an unsatisfiable new constraint must be called out here or the add appears to
660 // succeed silently. Mirrors the SolveAfterMove() warning for the same condition.
662
663 for( const PCB_CONSTRAINT* added : aAdded )
664 {
665 if( alg::contains( diag.conflicting, added->m_Uuid ) )
666 {
667 frame()->ShowInfoBarWarning( _( "The new geometric constraint conflicts with existing constraints and "
668 "could not be satisfied." ),
669 true );
670 return;
671 }
672
673 if( alg::contains( diag.errored, added->m_Uuid ) )
674 {
675 frame()->ShowInfoBarWarning( _( "The new geometric constraint could not be applied to the referenced "
676 "items." ),
677 true );
678 return;
679 }
680 }
681}
682
683
685{
686 // With no badge selected, fall through to the normal item-delete path.
687 if( !m_overlay || m_overlay->GetSelected() == niluuid )
688 return false;
689
690 PCB_CONSTRAINT* constraint = resolveConstraint( m_overlay->GetSelected() );
691
692 setSelectedConstraint( nullptr );
693
694 // The selected constraint vanished (undo / panel delete) but the badge selection lingered;
695 // consume the Delete so it does not fall through and remove a hovered board item.
696 if( !constraint )
697 return true;
698
699 removeConstraint( constraint );
700 return true;
701}
702
703
705{
706 // With no badge selected, fall through to the normal properties path.
707 if( !m_overlay || m_overlay->GetSelected() == niluuid )
708 return false;
709
710 PCB_CONSTRAINT* constraint = resolveConstraint( m_overlay->GetSelected() );
711
712 // The selected constraint vanished (undo / panel delete) but the badge selection lingered;
713 // clear it and fall through instead of consuming the key with no effect.
714 if( !constraint )
715 {
716 setSelectedConstraint( nullptr );
717 return false;
718 }
719
720 // editConstraint only opens a dialog for a valued constraint; others just consume the key.
721 editConstraint( constraint );
722 return true;
723}
724
725
727{
728 // A fix contributes no equation -- it is enforced by dropping its point from the unknowns -- so
729 // there is nothing to snap to. Re-solving anyway would only expose the pinned shape to the
730 // cluster's other constraints with its own stay-put pin lifted, which can drift it.
732 return;
733
734 // This solve runs after the add was pushed because SolveCluster gathers the cluster from
735 // board->Constraints(), and BOARD_COMMIT only makes the constraint live at Push time.
736 // APPEND_UNDO folds the snap into the add so creating a constraint is a single undoable action.
737 BOARD_COMMIT commit( this );
738 std::vector<PCB_SHAPE*> modified;
739
741 board(), aConstraint, &modified,
742 [&]( BOARD_ITEM* aItem )
743 {
744 commit.Modify( aItem );
745 },
746 ConstraintReferenceShapes( board(), aConstraint ) );
747
748 // Push if the snap moved a shape or re-measured a reference value in this cluster.
749 if( !commit.Empty() )
750 commit.Push( _( "Apply Geometric Constraint" ), APPEND_UNDO );
751}
752
753
754void CONSTRAINT_EDIT_TOOL::SolveAfterMove( const std::vector<PCB_SHAPE*>& aShapes )
755{
756 if( aShapes.empty() || !board() || !BoardHasConstraints( board() ) )
757 return;
758
759 BOARD_COMMIT commit( this );
760 std::vector<PCB_SHAPE*> modified;
761
762 ReSolveShapeClusters( board(), aShapes, &modified,
763 [&]( BOARD_ITEM* aItem )
764 {
765 commit.Modify( aItem );
766 } );
767
768 if( !commit.Empty() )
769 commit.Push( _( "Apply Geometric Constraint" ), APPEND_UNDO );
770
771 DiagnoseAfterMove( aShapes );
772}
773
774
775void CONSTRAINT_EDIT_TOOL::SolveAfterEdit( const std::vector<PCB_SHAPE*>& aShapes )
776{
777 if( aShapes.empty() || !board() || !BoardHasConstraints( board() ) )
778 return;
779
780 BOARD_COMMIT commit( this );
781 std::vector<PCB_SHAPE*> modified;
782
783 ReSolveShapeClustersHoldingEdited( board(), aShapes, &modified,
784 [&]( BOARD_ITEM* aItem )
785 {
786 commit.Modify( aItem );
787 } );
788
789 if( !commit.Empty() )
790 commit.Push( _( "Apply Geometric Constraint" ), APPEND_UNDO );
791
792 DiagnoseAfterMove( aShapes );
793}
794
795
796void CONSTRAINT_EDIT_TOOL::DiagnoseAfterMove( const std::vector<PCB_SHAPE*>& aShapes )
797{
798 if( aShapes.empty() || !board() || !BoardHasConstraints( board() ) )
799 return;
800
801 // Diagnose once and use it for both the views and the warning below, so a transform does not
802 // pay for two board-wide solves. Cache it so a following hover reuses this fresh result.
803 m_cachedDiag = m_diagnoser.Diagnose( board() );
804 m_diagDirty = false;
806 applyDiagnostics( diag );
807
808 // If the edit left a moved shape's constraint unsatisfiable, say so even when the overlay is off.
809 bool overConstrained = std::ranges::any_of( aShapes,
810 [&]( const PCB_SHAPE* aShape )
811 {
812 auto it = diag.shapeStates.find( aShape->m_Uuid );
813 return it != diag.shapeStates.end() && it->second == CONSTRAINT_STATE::OVER_CONSTRAINED;
814 } );
815
816 if( overConstrained && frame() )
817 frame()->ShowInfoBarWarning( _( "A geometric constraint could not be satisfied by this edit." ), true );
818}
819
820
822{
823 if( !m_overlay && board() && getView() )
824 m_overlay = std::make_unique<CONSTRAINT_OVERLAY>( board(), getView() );
825
826 if( m_overlay )
827 {
828 // The action, not a toggle, chooses the mode, so a freshly created overlay can never invert
829 // the clicked label. The overlay object itself stays alive either way.
830 bool always = aEvent.IsAction( &PCB_ACTIONS::showConstraints );
831 m_overlay->SetVisibilityMode( always ? OVERLAY_MODE::ALWAYS : OVERLAY_MODE::HOVER );
832
833 if( !always )
834 m_overlay->SetHoverShape( niluuid ); // hover mode starts hidden until a hover
835
836 // Remember the choice so it persists across board reloads and sessions.
837 if( frame() )
838 frame()->GetPcbNewSettings()->m_Display.m_ShowConstraints = always;
839 }
840
842 return 0;
843}
844
845
847{
848 // In the board editor the constraint list is a dockable pane; the footprint editor (which has
849 // no such pane) falls back to the modal list dialog.
850 if( PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( frame() ) )
851 {
852 pcbFrame->ToggleConstraintsPanel();
853 return 0;
854 }
855
856 auto highlight =
857 [&]( PCB_CONSTRAINT* aConstraint )
858 {
859 m_selectionTool->ClearSelection();
860
861 for( const CONSTRAINT_MEMBER& member : aConstraint->GetMembers() )
862 {
863 if( BOARD_ITEM* item = board()->ResolveItem( member.m_item, true ) )
864 m_selectionTool->select( item );
865 }
866 };
867
868 auto remove = [&]( PCB_CONSTRAINT* aConstraint ) { removeConstraint( aConstraint ); };
869
870 DIALOG_CONSTRAINT_LIST dlg( frame(), board(), highlight, remove );
871 dlg.ShowModal();
872
873 return 0;
874}
875
876
878{
879 if( frame()->IsType( FRAME_PCB_EDITOR ) )
880 {
883 }
884 else
885 {
888 }
889
890 return 0;
891}
892
893
895{
896 m_diagDirty = true; // the model changed, so the cached diagnosis is stale
898
899 aEvent.PassEvent();
900 return 0;
901}
902
903
905{
907
908 if( !m_selectionTool )
909 return false;
910
912
913 static const std::vector<KICAD_T> segmentType = { PCB_SHAPE_LOCATE_SEGMENT_T };
914
915 auto twoSegments = S_C::Count( 2 ) && S_C::OnlyTypes( segmentType );
916 auto oneSegment = S_C::Count( 1 ) && S_C::OnlyTypes( segmentType );
917
918 auto kindOf = []( const EDA_ITEM* aItem ) -> SHAPE_T
919 {
920 if( !aItem || aItem->Type() != PCB_SHAPE_T )
921 return SHAPE_T::UNDEFINED;
922
923 return static_cast<const PCB_SHAPE*>( aItem )->GetShape();
924 };
925
926 // A circle or arc has a radius; a closed or arc ellipse adds to that a centre only.
927 auto isRadial = []( SHAPE_T aShape )
928 {
929 return aShape == SHAPE_T::CIRCLE || aShape == SHAPE_T::ARC;
930 };
931
932 auto isCentered = [isRadial]( SHAPE_T aShape )
933 {
934 return isRadial( aShape ) || aShape == SHAPE_T::ELLIPSE || aShape == SHAPE_T::ELLIPSE_ARC;
935 };
936
937 auto oneRadial = [kindOf, isRadial]( const SELECTION& aSel )
938 {
939 return aSel.Size() == 1 && isRadial( kindOf( aSel[0] ) );
940 };
941
942 auto oneArc = [kindOf]( const SELECTION& aSel )
943 {
944 return aSel.Size() == 1 && kindOf( aSel[0] ) == SHAPE_T::ARC;
945 };
946
947 auto twoRadial = [kindOf, isRadial]( const SELECTION& aSel )
948 {
949 return aSel.Size() == 2 && isRadial( kindOf( aSel[0] ) ) && isRadial( kindOf( aSel[1] ) );
950 };
951
952 auto twoCentered = [kindOf, isCentered]( const SELECTION& aSel )
953 {
954 return aSel.Size() == 2 && isCentered( kindOf( aSel[0] ) ) && isCentered( kindOf( aSel[1] ) );
955 };
956
957 // Tangent joins a line with a curve, or two circles/arcs.
958 auto tangentPair = [kindOf, isCentered, isRadial]( const SELECTION& aSel )
959 {
960 if( aSel.Size() != 2 )
961 return false;
962
963 SHAPE_T a = kindOf( aSel[0] );
964 SHAPE_T b = kindOf( aSel[1] );
965
966 return ( a == SHAPE_T::SEGMENT && isCentered( b ) ) || ( b == SHAPE_T::SEGMENT && isCentered( a ) )
967 || ( isRadial( a ) && isRadial( b ) );
968 };
969
970 // Only offer Remove when something selected actually carries a constraint.
971 auto selectionConstrained = [this]( const SELECTION& aSel ) -> bool
972 {
973 if( aSel.Empty() || !board() )
974 return false;
975
976 std::set<KIID> ids;
977
978 for( EDA_ITEM* item : aSel )
979 ids.insert( item->m_Uuid );
980
981 auto anyMember = [&]( const CONSTRAINTS& aList )
982 {
983 return std::ranges::any_of( aList,
984 [&]( const PCB_CONSTRAINT* c )
985 {
986 return std::ranges::any_of( c->GetMembers(),
987 [&]( const CONSTRAINT_MEMBER& m ) { return ids.contains( m.m_item ); } );
988 } );
989 };
990
991 if( anyMember( board()->Constraints() ) )
992 return true;
993
995 && anyMember( board()->GetFirstFootprint()->Constraints() );
996 };
997
998 // One "Constraints" submenu holds every constraint command. The add-type items are gated by
999 // the current selection so only constraints valid for what is selected are offered; the
1000 // manage/show/remove items are always present.
1001 m_menu = new CONDITIONAL_MENU( this );
1002 m_menu->SetIcon( BITMAPS::measurement );
1003 m_menu->SetUntranslatedTitle( _HKI( "Constraints" ) );
1004
1005 // Gate each selection-based add-type by what it needs; the point-anchored families are authored
1006 // by clicking and need no prior selection, so they are absent here and default to ShowAlways.
1007 // The action list itself lives in PCB_ACTIONS::ConstraintAddActions() so the menubar's copy of
1008 // this submenu cannot drift from it.
1009 const std::map<const TOOL_ACTION*, SELECTION_CONDITION> gate = {
1010 { &PCB_ACTIONS::addConstraintParallel, twoSegments },
1012 { &PCB_ACTIONS::addConstraintEqualLength, twoSegments },
1013 { &PCB_ACTIONS::addConstraintCollinear, twoSegments },
1014 { &PCB_ACTIONS::addConstraintAngular, twoSegments },
1015 { &PCB_ACTIONS::addConstraintTangent, tangentPair },
1016 { &PCB_ACTIONS::addConstraintHorizontal, oneSegment },
1017 { &PCB_ACTIONS::addConstraintVertical, oneSegment },
1019 { &PCB_ACTIONS::addConstraintConcentric, twoCentered },
1023 };
1024
1025 bool pointGroupSeparated = false;
1026
1027 for( const TOOL_ACTION* action : PCB_ACTIONS::ConstraintAddActions() )
1028 {
1029 if( auto it = gate.find( action ); it != gate.end() )
1030 {
1031 m_menu->AddItem( *action, it->second );
1032 }
1033 else
1034 {
1035 if( !pointGroupSeparated )
1036 {
1037 m_menu->AddSeparator();
1038 pointGroupSeparated = true;
1039 }
1040
1041 m_menu->AddItem( *action, S_C::ShowAlways );
1042 }
1043 }
1044
1045 // Show while hidden, Hide while shown, so the label always names what the click will do.
1046 // "Shown" is the always-on mode; the hover mode reads as hidden and offers the Show action.
1047 // The ALWAYS/HOVER choice is a tool mode rather than an object visibility, which is why it
1048 // lives here and not in the Appearance panel's Objects tab.
1049 auto overlayShown = [this]( const SELECTION& )
1050 {
1051 return m_overlay && m_overlay->GetVisibilityMode() == OVERLAY_MODE::ALWAYS;
1052 };
1053 auto overlayHidden = [this]( const SELECTION& )
1054 {
1055 return !m_overlay || m_overlay->GetVisibilityMode() == OVERLAY_MODE::HOVER;
1056 };
1057
1058 m_menu->AddSeparator();
1059 m_menu->AddItem( PCB_ACTIONS::removeConstraints, selectionConstrained );
1060 m_menu->AddItem( PCB_ACTIONS::showConstraints, overlayHidden );
1061 m_menu->AddItem( PCB_ACTIONS::hideConstraints, overlayShown );
1063
1064 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
1065 selToolMenu.AddMenu( m_menu, S_C::ShowAlways, 100 );
1066
1067 return true;
1068}
1069
1070
1072{
1074 const PCB_SELECTION& selection = m_selectionTool->GetSelection();
1075
1076 std::vector<BOARD_ITEM*> items;
1077
1078 for( EDA_ITEM* item : selection )
1079 {
1080 if( item->IsBOARD_ITEM() )
1081 items.push_back( static_cast<BOARD_ITEM*>( item ) );
1082 }
1083
1084 std::unique_ptr<PCB_CONSTRAINT> constraint =
1086
1087 // A valid selection builds right away (the context-menu path). Otherwise drop into click-to-pick
1088 // so the toolbar and hotkeys can author a constraint by clicking the items on the canvas.
1089 if( !constraint )
1090 {
1091 // Switching to click-to-pick silently reads as the tool ignoring the selection, so say what
1092 // the type wanted. Only when something was selected: an empty one is the normal toolbar path.
1093 if( !items.empty() && frame() )
1094 {
1095 if( wxString hint = ConstraintSelectionHint( type ); !hint.IsEmpty() )
1096 frame()->ShowInfoBarWarning( hint );
1097 }
1098
1099 // Horizontal and vertical also accept two point anchors so levelling a corner needs no segment
1100 // The linear picker offers both paths
1102 return pickLinearConstraint( type, aEvent );
1103
1104 return pickShapeConstraint( type, aEvent );
1105 }
1106
1107 commitConstraint( std::move( constraint ) );
1108 return 0;
1109}
1110
1111
1112bool CONSTRAINT_EDIT_TOOL::commitConstraint( std::unique_ptr<PCB_CONSTRAINT> aConstraint )
1113{
1114 if( !aConstraint )
1115 return false;
1116
1117 if( isDuplicateConstraint( aConstraint.get() ) )
1118 {
1119 if( frame() )
1120 frame()->ShowInfoBarWarning( _( "An identical geometric constraint already exists." ) );
1121
1122 return false;
1123 }
1124
1125 // For a dimensional constraint, let the user confirm/override the measured value and choose
1126 // driving vs reference (issue #2329 step 7).
1127 if( aConstraint->HasValue() )
1128 {
1129 PCB_CONSTRAINT_TYPE type = aConstraint->GetConstraintType();
1130
1131 // Default to the shape measurement unless the user already set a value for this type this session
1132 // Reuse that so a run of same type constraints keeps one size
1133 double initial = InitialConstraintValue( type, *aConstraint->GetValue(), m_lastConstraintValue );
1134
1135 bool driving = aConstraint->IsDriving();
1136
1137 if( auto it = m_lastConstraintDriving.find( type ); it != m_lastConstraintDriving.end() )
1138 driving = it->second;
1139
1140 DIALOG_CONSTRAINT_VALUE dlg( frame(), type, initial, driving );
1141
1142 if( dlg.ShowModal() != wxID_OK )
1143 return false;
1144
1145 aConstraint->SetValue( dlg.GetConstraintValue() );
1146 aConstraint->SetDriving( dlg.GetDriving() );
1147
1149 m_lastConstraintDriving[type] = dlg.GetDriving();
1150 }
1151
1152 PCB_CONSTRAINT* added = aConstraint.get();
1153
1154 BOARD_COMMIT commit( this );
1155 commit.Add( aConstraint.release() );
1156 finishConstraintCommit( commit, { added } );
1157
1158 return true;
1159}
1160
1161
1163{
1164 int count = 2;
1165 bool allowCircle = false;
1166
1167 switch( aType )
1168 {
1172 count = 1;
1173 allowCircle = false;
1174 break;
1177 count = 1;
1178 allowCircle = true;
1179 break;
1183 count = 2;
1184 allowCircle = true;
1185 break;
1186 default:
1187 count = 2;
1188 allowCircle = false;
1189 break;
1190 }
1191
1192 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
1193
1194 if( !picker )
1195 return 0;
1196
1197 std::vector<KIID> picked;
1198 const double snapTol = pcbIUScale.mmToIU( 1.0 );
1199
1200 Activate();
1201 picker->SetCursor( KICURSOR::BULLSEYE );
1202 picker->SetSnapping( true );
1203 picker->ClearHandlers();
1204
1205 picker->SetClickHandler(
1206 [&]( const VECTOR2D& aPoint ) -> bool
1207 {
1208 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1209 std::optional<KIID> target = NearestOutlineShape( board(), pos, snapTol, allowCircle );
1210
1211 if( !target || alg::contains( picked, *target ) )
1212 return true; // nothing new snapped, keep picking
1213
1214 picked.push_back( *target );
1215
1216 if( static_cast<int>( picked.size() ) < count )
1217 {
1218 // Clear the preview of the just consumed target
1219 // Not advertised until the next motion event re-derives the pick
1220 if( m_overlay )
1221 m_overlay->ClearPickPreview();
1222
1223 return true; // need more
1224 }
1225
1226 std::vector<BOARD_ITEM*> items;
1227
1228 for( const KIID& id : picked )
1229 {
1230 if( BOARD_ITEM* item = board()->ResolveItem( id, true ) )
1231 items.push_back( item );
1232 }
1233
1234 std::unique_ptr<PCB_CONSTRAINT> constraint =
1235 BuildConstraintFromItems( constraintParent(), aType, items );
1236
1237 if( !constraint && frame() )
1238 {
1239 frame()->ShowInfoBarWarning( wxString::Format( _( "Cannot form a %s constraint from those items." ),
1240 ConstraintTypeLabel( aType ) ) );
1241 }
1242
1243 commitConstraint( std::move( constraint ) );
1244 picked.clear();
1245 return true; // stay active so more can be placed, like the draw tools
1246 } );
1247
1248 // Outline the element the next pick would take so the target is clear before clicking
1249 picker->SetMotionHandler(
1250 [&]( const VECTOR2D& aPoint )
1251 {
1252 if( !m_overlay )
1253 return;
1254
1255 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1256 std::optional<KIID> target = NearestOutlineShape( board(), pos, snapTol, allowCircle );
1257
1258 // Mirror the click handler rejection so an already picked shape is not advertised
1259 // as eligible for the remaining picks
1260 if( target && alg::contains( picked, *target ) )
1261 target.reset();
1262
1263 m_overlay->SetPickPreview( target.value_or( niluuid ), true, std::nullopt );
1264 } );
1265
1266 bool done = false;
1267
1268 picker->SetFinalizeHandler(
1269 [&]( const int& )
1270 {
1271 done = true;
1272 } );
1273
1274 m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
1275
1276 while( !done )
1277 {
1278 if( TOOL_EVENT* evt = Wait() )
1279 evt->SetPassEvent();
1280 else
1281 break;
1282 }
1283
1284 picker->ClearHandlers();
1285
1286 if( m_overlay )
1287 m_overlay->ClearPickPreview();
1288
1289 return 0;
1290}
1291
1292
1294{
1295 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
1296
1297 if( !picker )
1298 return 0;
1299
1300 // Empty until the first pick is a point then holds that point while the second is chosen
1301 std::vector<CONSTRAINT_MEMBER> members;
1302 const double snapTol = pcbIUScale.mmToIU( 1.0 );
1303
1304 Activate();
1305 picker->SetCursor( KICURSOR::BULLSEYE );
1306 picker->SetSnapping( true );
1307 picker->ClearHandlers();
1308
1309 auto commitPointPair = [&]()
1310 {
1311 std::unique_ptr<PCB_CONSTRAINT> constraint =
1312 std::make_unique<PCB_CONSTRAINT>( constraintParent(), aType );
1313
1314 for( const CONSTRAINT_MEMBER& member : members )
1315 constraint->AddMember( member.m_item, member.m_anchor, member.m_index );
1316
1317 commitConstraint( std::move( constraint ) );
1318 };
1319
1320 picker->SetClickHandler(
1321 [&]( const VECTOR2D& aPoint ) -> bool
1322 {
1323 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1324
1325 // A point under the cursor wins over its segment so clicking a corner starts the two point path
1326 // A segment middle has no anchor and takes the whole segment path
1327 std::optional<CONSTRAINT_MEMBER> anchor =
1328 NearestConstraintAnchor( board(), pos, snapTol, members );
1329
1330 if( anchor )
1331 {
1332 members.push_back( *anchor );
1333
1334 if( members.size() < 2 )
1335 {
1336 if( m_overlay )
1337 m_overlay->ClearPickPreview();
1338
1339 return true; // need the second point
1340 }
1341
1342 commitPointPair();
1343 members.clear();
1344 return true; // stay active so more can be placed, like the draw tools
1345 }
1346
1347 // No anchor snapped before the first point a whole segment authors immediately
1348 // Once a point is held only a second point completes the pair
1349 if( members.empty() )
1350 {
1351 if( std::optional<KIID> target = NearestOutlineShape( board(), pos, snapTol, false ) )
1352 {
1353 std::unique_ptr<PCB_CONSTRAINT> constraint =
1354 std::make_unique<PCB_CONSTRAINT>( constraintParent(), aType );
1355 constraint->AddMember( *target, CONSTRAINT_ANCHOR::WHOLE );
1356 commitConstraint( std::move( constraint ) );
1357 }
1358 }
1359
1360 return true;
1361 } );
1362
1363 picker->SetMotionHandler(
1364 [&]( const VECTOR2D& aPoint )
1365 {
1366 if( !m_overlay )
1367 return;
1368
1369 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1370
1371 // Mirror the click handler so the preview shows exactly what the next click takes.
1372 if( std::optional<CONSTRAINT_MEMBER> anchor =
1373 NearestConstraintAnchor( board(), pos, snapTol, members ) )
1374 {
1375 m_overlay->SetPickPreview( anchor->m_item, false,
1377 }
1378 else if( members.empty() )
1379 {
1380 if( std::optional<KIID> target = NearestOutlineShape( board(), pos, snapTol, false ) )
1381 m_overlay->SetPickPreview( *target, true, std::nullopt );
1382 else
1383 m_overlay->ClearPickPreview();
1384 }
1385 else
1386 {
1387 m_overlay->ClearPickPreview();
1388 }
1389 } );
1390
1391 bool done = false;
1392
1393 picker->SetFinalizeHandler(
1394 [&]( const int& )
1395 {
1396 done = true;
1397 } );
1398
1399 m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
1400
1401 while( !done )
1402 {
1403 if( TOOL_EVENT* evt = Wait() )
1404 evt->SetPassEvent();
1405 else
1406 break;
1407 }
1408
1409 picker->ClearHandlers();
1410
1411 if( m_overlay )
1412 m_overlay->ClearPickPreview();
1413
1414 return 0;
1415}
1416
1417
1419{
1421
1422 // In the pick plan, true means click a point anchor and false means click a whole segment.
1423 std::vector<bool> plan;
1424
1425 switch( type )
1426 {
1427 case PCB_CONSTRAINT_TYPE::FIXED_POSITION: plan = { true }; break;
1428 case PCB_CONSTRAINT_TYPE::COINCIDENT: plan = { true, true }; break;
1430 case PCB_CONSTRAINT_TYPE::MIDPOINT: plan = { true, false }; break;
1431 case PCB_CONSTRAINT_TYPE::SYMMETRIC: plan = { true, true, false }; break;
1432 default: return 0;
1433 }
1434
1435 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
1436
1437 if( !picker )
1438 return 0;
1439
1440 std::vector<CONSTRAINT_MEMBER> members;
1441 const double snapTol = pcbIUScale.mmToIU( 1.0 );
1442
1443 Activate();
1444 picker->SetCursor( KICURSOR::BULLSEYE );
1445 picker->SetSnapping( true );
1446 picker->ClearHandlers();
1447
1448 picker->SetClickHandler(
1449 [&]( const VECTOR2D& aPoint ) -> bool
1450 {
1451 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1452
1453 if( plan[members.size()] ) // wants a point anchor
1454 {
1455 // Exclude already picked handles so the same shape and anchor cannot repeat
1456 // while a coincident but distinct endpoint stays reachable
1457 std::optional<CONSTRAINT_MEMBER> anchor =
1458 NearestConstraintAnchor( board(), pos, snapTol, members );
1459
1460 if( !anchor )
1461 return true; // nothing snapped; keep picking
1462
1463 members.push_back( *anchor );
1464 }
1465 else // wants a whole shape
1466 {
1467 std::optional<KIID> target =
1469
1470 if( !target )
1471 return true;
1472
1473 members.emplace_back( *target, CONSTRAINT_ANCHOR::WHOLE );
1474 }
1475
1476 if( members.size() < plan.size() )
1477 {
1478 // Clear the preview of the just consumed target
1479 // Not advertised until the next motion event re-derives the pick
1480 if( m_overlay )
1481 m_overlay->ClearPickPreview();
1482
1483 return true; // need more picks
1484 }
1485
1486 std::unique_ptr<PCB_CONSTRAINT> constraint =
1487 std::make_unique<PCB_CONSTRAINT>( constraintParent(), type );
1488
1489 for( const CONSTRAINT_MEMBER& member : members )
1490 constraint->AddMember( member.m_item, member.m_anchor, member.m_index );
1491
1492 if( isDuplicateConstraint( constraint.get() ) )
1493 {
1494 if( frame() )
1495 frame()->ShowInfoBarWarning( _( "An identical geometric constraint already exists." ) );
1496
1497 return false; // done
1498 }
1499
1500 PCB_CONSTRAINT* added = constraint.get();
1501
1502 BOARD_COMMIT commit( this );
1503 commit.Add( constraint.release() );
1504 finishConstraintCommit( commit, { added } );
1505
1506 return false; // done
1507 } );
1508
1509 // Preview the next pick target before the click the point steps also mark the exact anchor
1510 picker->SetMotionHandler(
1511 [&]( const VECTOR2D& aPoint )
1512 {
1513 if( !m_overlay || members.size() >= plan.size() )
1514 return;
1515
1516 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1517
1518 KIID element = niluuid;
1519 bool whole = true;
1520 std::optional<VECTOR2I> anchorPos;
1521
1522 if( plan[members.size()] ) // wants a point anchor
1523 {
1524 if( std::optional<CONSTRAINT_MEMBER> anchor =
1525 NearestConstraintAnchor( board(), pos, snapTol, members ) )
1526 {
1527 element = anchor->m_item;
1528 whole = false;
1529 anchorPos = ConstraintAnchorPosition( board(), *anchor );
1530 }
1531 }
1532 else if( std::optional<KIID> target = NearestOutlineShape(
1533 board(), pos, snapTol, type == PCB_CONSTRAINT_TYPE::POINT_ON_LINE ) )
1534 {
1535 element = *target;
1536 }
1537
1538 m_overlay->SetPickPreview( element, whole, anchorPos );
1539 } );
1540
1541 bool done = false;
1542
1543 picker->SetFinalizeHandler(
1544 [&]( const int& aFinalState )
1545 {
1546 done = true;
1547 } );
1548
1549 m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
1550
1551 // RunAction returns before picking ends. Wait so the handlers' captures stay alive.
1552 while( !done )
1553 {
1554 if( TOOL_EVENT* evt = Wait() )
1555 evt->SetPassEvent();
1556 else
1557 break;
1558 }
1559
1560 picker->ClearHandlers();
1561
1562 if( m_overlay )
1563 m_overlay->ClearPickPreview();
1564
1565 return 0;
1566}
1567
1568
1570{
1571 PCB_SELECTION& selection = m_selectionTool->GetSelection();
1572 std::set<KIID> selectedIds;
1573
1574 for( EDA_ITEM* item : selection )
1575 selectedIds.insert( item->m_Uuid );
1576
1577 if( selectedIds.empty() )
1578 return 0;
1579
1580 BOARD_COMMIT commit( this );
1581 bool any = false;
1582
1583 auto removeReferencing =
1584 [&]( const CONSTRAINTS& aConstraints )
1585 {
1586 for( PCB_CONSTRAINT* constraint : aConstraints )
1587 {
1588 bool referenced = std::ranges::any_of( constraint->GetMembers(),
1589 [&]( const CONSTRAINT_MEMBER& aMember )
1590 { return selectedIds.contains( aMember.m_item ); } );
1591
1592 if( referenced )
1593 {
1594 commit.Remove( constraint );
1595 any = true;
1596 }
1597 }
1598 };
1599
1600 if( board() )
1601 {
1602 removeReferencing( board()->Constraints() );
1603
1604 if( IsFootprintEditor() && board()->GetFirstFootprint() )
1605 removeReferencing( board()->GetFirstFootprint()->Constraints() );
1606 }
1607
1608 if( any )
1609 commit.Push( _( "Remove Geometric Constraints" ) );
1610
1612
1613 return 0;
1614}
1615
1616
1618{
1632
1638
1644
1645 // Keep the diagnostics overlay current as the board changes underneath it. Undo/redo posts its
1646 // own event (not TA_MODEL_CHANGE), so listen for it too or a restored/removed constraint's badge
1647 // would not reappear/disappear.
1650
1651 // Show/refresh the endpoint markers as the selection changes.
1655
1656 // No other pcbnew tool registers a plain-motion transition (only one transition runs per mouse
1657 // event), and waiting tools receive motion before this loop, so claiming it here is safe.
1659}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
std::set< KIID > ConstraintReferenceShapes(BOARD *aBoard, const PCB_CONSTRAINT *aConstraint)
The shapes a just-authored constraint should treat as an immovable reference, for the caller to pass ...
bool BoardHasConstraints(BOARD *aBoard)
True if the board or any of its footprints carries at least one geometric constraint.
CONSTRAINT_DIAGNOSIS ApplyConstraintImmediately(BOARD *aBoard, const PCB_CONSTRAINT *aConstraint, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify, const std::set< KIID > &aFixedShapes)
Solve a just-created constraint's cluster so the geometry snaps to satisfy it (SolidWorks-style),...
void ReSolveShapeClusters(BOARD *aBoard, const std::vector< PCB_SHAPE * > &aShapes, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
Re-solve the clusters of shapes edited outside the solver, e.g.
bool ReSolveShapeClustersHoldingEdited(BOARD *aBoard, const std::vector< PCB_SHAPE * > &aEditedShapes, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
Re-solve clusters whose new geometry is authoritative holding every edited shape fully fixed so only ...
@ OVER_CONSTRAINED
In a cluster the solver reports as conflicting.
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
static TOOL_ACTION pickerTool
Definition actions.h:249
static TOOL_ACTION zoomFitSelection
Definition actions.h:140
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:713
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Definition board.cpp:2142
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
bool Empty() const
Definition commit.h:145
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
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
void AddMenu(ACTION_MENU *aMenu, const SELECTION_CONDITION &aCondition=SELECTION_CONDITIONS::ShowAlways, int aOrder=ANY_ORDER)
Add a submenu to the menu.
bool Init() override
Init() is called once upon a registration of the tool.
bool commitConstraint(std::unique_ptr< PCB_CONSTRAINT > aConstraint)
Confirm any value, reject a duplicate, and commit aConstraint. Returns true if it was added.
int pickShapeConstraint(PCB_CONSTRAINT_TYPE aType, const TOOL_EVENT &aEvent)
Author a whole-shape constraint by clicking its items on the canvas (the no-selection path).
void removeConstraint(PCB_CONSTRAINT *aConstraint)
Remove a constraint in its own commit and refresh the diagnostics.
std::unique_ptr< CONSTRAINT_OVERLAY > m_overlay
BOARD_CONSTRAINT_DIAGNOSTICS m_cachedDiag
Reused until the model changes.
int onHoverMotion(const TOOL_EVENT &aEvent)
In HOVER mode, reveal the constraints of the shape under the cursor (nothing when none).
int AddConstraint(const TOOL_EVENT &aEvent)
void updateConstrainedItems(const BOARD_CONSTRAINT_DIAGNOSTICS &aDiag)
Stash the constrained-item set (the shadow-layer gate) into the render settings and repaint the overl...
PCB_SELECTION_TOOL * m_selectionTool
PCB_CONSTRAINT * hitTestBadge(const VECTOR2I &aPos) const
The constraint whose badge is within the hit radius of aPos, or nullptr.
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
void updateConstraintMsgPanel(PCB_CONSTRAINT *aConstraint)
Show the selected constraint's type, items and state in the bottom message panel.
void DiagnoseAfterMove(const std::vector< PCB_SHAPE * > &aShapes)
Re-diagnose and refresh the overlay and info bar after the caller re-solves aShapes into its own comm...
int ToggleAutoConstraints(const TOOL_EVENT &aEvent)
void refreshDiagnostics()
Mark the diagnosis and candidate caches stale (the model changed) and re-render the views.
void renderConstraintViews()
Re-render the shown views from the cached diagnosis without invalidating it – for a bare visibility c...
void applyDiagnostics(const BOARD_CONSTRAINT_DIAGNOSTICS &aDiag)
Push an already-computed diagnosis into every shown view, so a caller that already solved does not so...
bool SelectConstraintAt(const VECTOR2I &aPos)
Select the constraint whose on-canvas badge is at aPos (enlarge it, highlight the panel row); returns...
PCB_CONSTRAINT * resolveConstraint(const KIID &aId) const
Resolve a constraint by KIID against the live board, or nullptr.
bool TryDeleteSelectedConstraint()
Delete the currently badge-selected constraint; returns true if one was selected and removed.
void repaintShadowItems(const std::unordered_set< KIID > &aOld, const std::unordered_set< KIID > &aNew)
Re-cache the items whose shadow-set membership changed between aOld and aNew, so the cached constrain...
void SolveAfterEdit(const std::vector< PCB_SHAPE * > &aShapes)
Re-solve after a panel or dialog edit holding aShapes fixed so typed values survive Only their constr...
CONDITIONAL_MENU * m_menu
int RemoveConstraints(const TOOL_EVENT &aEvent)
void setSelectedConstraint(PCB_CONSTRAINT *aConstraint)
Mark aConstraint selected on the overlay and in the panel (nullptr clears).
void RemoveConstraintById(const KIID &aId)
Intent entry points for the constraints panel, so it never mutates the board or the selection itself ...
std::map< PCB_CONSTRAINT_TYPE, bool > m_lastConstraintDriving
bool ClearConstraintSelection()
Clear any badge-selected constraint. Returns true if a constraint was selected and is now cleared.
bool allMembersLocked(const PCB_CONSTRAINT *aConstraint) const
True if every item aConstraint references resolves and is locked (nothing the solver may move),...
int ManageConstraints(const TOOL_EVENT &aEvent)
void finishConstraintCommit(BOARD_COMMIT &aCommit, const std::vector< PCB_CONSTRAINT * > &aAdded)
Push aCommit, snap the geometry for the added constraints, and refresh the overlays.
KIGFX::PCB_RENDER_SETTINGS * pcbRenderSettings() const
The active PCB render settings, or nullptr when no painter is attached (headless).
void HighlightConstraintMembers(const KIID &aId, int aMemberIndex)
const BOARD_CONSTRAINT_DIAGNOSTICS & ensureDiagnosis()
The cached board diagnosis, solved only when the model changed since the last call,...
bool TryEditSelectedConstraint()
Edit the currently badge-selected constraint's value; returns true if one was selected.
bool isDuplicateConstraint(const PCB_CONSTRAINT *aConstraint) const
True if the board already holds a constraint equal to aConstraint (same type and members),...
int pickLinearConstraint(PCB_CONSTRAINT_TYPE aType, const TOOL_EVENT &aEvent)
Author a horizontal or vertical constraint from a whole segment or two point anchors Clicking a point...
void EditConstraintById(const KIID &aId)
int refreshOverlay(const TOOL_EVENT &aEvent)
Refresh the diagnostics overlay if it is currently shown.
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
std::optional< std::vector< KIID > > m_hoverCandidateIds
Constrained shape ids, per model.
BOARD_ITEM * constraintParent() const
The owner a new constraint should be parented to (the footprint in the footprint editor).
void SolveAfterMove(const std::vector< PCB_SHAPE * > &aShapes)
Re-solve the clusters of aShapes after a whole-shape edit, folded into that edit's undo.
bool EditConstraintAt(const VECTOR2I &aPos)
Open the value dialog for the constraint badged at aPos; returns true if one was hit.
int ShowConstraints(const TOOL_EVENT &aEvent)
void editConstraint(PCB_CONSTRAINT *aConstraint)
Open the value dialog for a constraint and, on accept, commit + re-solve + refresh.
const std::vector< KIID > & hoverCandidates()
The ids of the shapes referenced by any constraint, the hover-hit candidate set (cached per model).
BOARD_CONSTRAINT_DIAGNOSER m_diagnoser
Incremental board diagnoser for the hot edit path, so editing one shape re-solves only its own cluste...
int onSelectionChanged(const TOOL_EVENT &aEvent)
Clear stale badge/isolation state when the board selection changes.
std::map< PCB_CONSTRAINT_TYPE, double > m_lastConstraintValue
Last value and driving choice per constraint type this session so same type runs keep one size Keyed ...
int AddPointConstraint(const TOOL_EVENT &aEvent)
void updateHighlightedConstraintMembers(PCB_CONSTRAINT *aConstraint)
Stash the selected constraint's members (the highlighted-shadow set) into the render settings and rep...
void solveAddedConstraint(PCB_CONSTRAINT *aConstraint)
Solve a just-added constraint so the geometry snaps to satisfy it, in its own commit.
static double BadgeHitRadius()
Click hit radius in screen pixels.
static double BadgeWorldPerPixel(double aWorldScale)
World units per screen pixel for badge sizing, capped at far zoom-out.
static std::vector< VECTOR2D > LayoutBadges(const std::vector< CONSTRAINT_BADGE > &aBadges, double aWorldPerPx)
The on-screen draw position (world units) of each badge at the given scale: the anchor offset by Badg...
Lists every geometric constraint on the board with its type and diagnostic state, the way the canvas ...
Value entry for a dimensional geometric constraint (issue #2329): a UNIT_BINDER for the length (or an...
bool GetDriving() const
True if the constraint should drive (lock) the geometry; false for a reference dimension.
double GetConstraintValue()
The entered value, in IU for a length/radius or in degrees for an angle.
int ShowModal() override
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
const KIID m_Uuid
Definition eda_item.h:599
static const TOOL_EVENT ClearedEvent
Definition actions.h:348
static const TOOL_EVENT SelectedEvent
Definition actions.h:346
static const TOOL_EVENT UndoRedoPostEvent
Definition actions.h:370
static const TOOL_EVENT UnselectedEvent
Definition actions.h:347
virtual RENDER_SETTINGS * GetSettings()=0
Return a pointer to current settings that are going to be used when drawing items.
PCB specific render settings.
Definition pcb_painter.h:94
void SetHighlightedConstraintMembers(std::unordered_set< KIID > aItems)
void SetConstrainedItems(std::unordered_set< KIID > aItems)
const std::unordered_set< KIID > & GetHighlightedConstraintMembers() const
const std::unordered_set< KIID > & GetConstrainedItems() const
virtual VECTOR2D GetMousePosition(bool aWorldCoordinates=true) const =0
Return the current mouse pointer position.
virtual void Update(const VIEW_ITEM *aItem, int aUpdateFlags) const
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
Definition view.cpp:2631
VECTOR2D ToWorld(const VECTOR2D &aCoord, bool aAbsolute=true) const
Converts a screen space point/vector to a point/vector in world space coordinates.
Definition view.cpp:791
PAINTER * GetPainter() const
Return the painter object used by the view for drawing VIEW_ITEM objects.
Definition view.h:242
Definition kiid.h:46
Dockable panel listing the board's geometric constraints (issue #2329).
static TOOL_ACTION hideConstraints
Same toggle, shown while the overlay is visible.
static TOOL_ACTION addConstraintVertical
static TOOL_ACTION addConstraintPerpendicular
static TOOL_ACTION addConstraintAngular
static TOOL_ACTION addConstraintArcAngle
static TOOL_ACTION manageConstraints
Open the constraint list dialog.
static TOOL_ACTION addConstraintHorizontal
static const std::vector< const TOOL_ACTION * > & ConstraintAddActions()
Canonical ordered list of the geometric-constraint "add" actions, shared by the context submenu (gate...
static TOOL_ACTION addConstraintCollinear
static TOOL_ACTION addConstraintEqualRadius
static TOOL_ACTION addConstraintTangent
static TOOL_ACTION addConstraintFixedLength
static TOOL_ACTION toggleAutoConstraints
Toggle authoring constraints automatically while drawing.
static TOOL_ACTION addConstraintSymmetric
static TOOL_ACTION addConstraintFixedPosition
Ground a point, and the cluster holding it.
static TOOL_ACTION addConstraintPointOnLine
static TOOL_ACTION addConstraintCoincident
static TOOL_ACTION showConstraints
Toggle the constraint diagnostics overlay.
static TOOL_ACTION addConstraintParallel
static TOOL_ACTION addConstraintFixedRadius
static TOOL_ACTION addConstraintEqualLength
static TOOL_ACTION removeConstraints
static TOOL_ACTION addConstraintMidpoint
static TOOL_ACTION addConstraintConcentric
A geometric constraint between board items (issue #2329).
const std::vector< CONSTRAINT_MEMBER > & GetMembers() const
std::optional< double > GetValue() const
bool IsDriving() const
A driving constraint forces its value; a reference (non-driving) one only measures it.
PCB_CONSTRAINT_TYPE GetConstraintType() const
bool HasValue() const
The main frame for Pcbnew.
Generic tool for picking an item.
void ClearHandlers()
Handlers only.
The selection tool: currently supports:
T * frame() const
bool IsFootprintEditor() const
PCB_TOOL_BASE(TOOL_ID aId, const std::string &aName)
Constructor.
BOARD * board() const
const PCB_SELECTION & selection() const
void SetMotionHandler(MOTION_HANDLER aHandler)
Set a handler for mouse motion.
Definition picker_tool.h:92
void SetClickHandler(CLICK_HANDLER aHandler)
Set a handler for mouse click event.
Definition picker_tool.h:81
void SetSnapping(bool aSnap)
Definition picker_tool.h:65
void SetCursor(KICURSOR aCursor)
Definition picker_tool.h:63
void SetFinalizeHandler(FINALIZE_HANDLER aHandler)
Set a handler for the finalize event.
Class that groups generic conditions for selected items.
static SELECTION_CONDITION Count(int aNumber)
Create a functor that tests if the number of selected items is equal to the value given as parameter.
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.
Represent a single user action.
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition tool_base.cpp:40
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
KIGFX::VIEW * getView() const
Return the instance of KIGFX::VIEW object used in the application.
Definition tool_base.cpp:34
RESET_REASON
Determine the reason of reset for a tool.
Definition tool_base.h:74
@ SHUTDOWN
Tool is being shut down.
Definition tool_base.h:80
@ MODEL_RELOAD
Model changes (the sheet for a schematic)
Definition tool_base.h:76
@ SUPERMODEL_RELOAD
For schematics, the entire schematic changed, not just the sheet.
Definition tool_base.h:77
Generic, UI-independent tool event.
Definition tool_event.h:167
bool PassEvent() const
These give a tool a method of informing the TOOL_MANAGER that a particular event should be passed on ...
Definition tool_event.h:251
bool IsAction(const TOOL_ACTION *aAction) const
Test if the event contains an action issued upon activation of the given TOOL_ACTION.
T Parameter() const
Return a parameter assigned to the event.
Definition tool_event.h:484
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Define which state (aStateFunc) to go when a certain event arrives (aConditions).
TOOL_EVENT * Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
Suspend execution of the tool until an event specified in aEventList arrives.
void Activate()
Run the tool.
A type-safe container of any type.
Definition ki_any.h:92
std::optional< KIID > NearestConstrainedShape(const BOARD *aBoard, const std::vector< KIID > &aCandidates, const VECTOR2I &aPos, int aMaxDist)
The candidate shape whose outline aPos hits within aMaxDist, or std::nullopt.
std::optional< KIID > NearestOutlineShape(const BOARD *aBoard, const VECTOR2I &aPos, double aMaxDist, bool aAllowCircle, PCB_LAYER_ID aLayer)
The shape whose outline is nearest aPos within aMaxDist.
std::vector< KIID > ConstrainedShapeIds(const BOARD *aBoard)
The member ids of every board and footprint constraint that are on aBoard, in first reference order: ...
double InitialConstraintValue(PCB_CONSTRAINT_TYPE aType, double aMeasured, const std::map< PCB_CONSTRAINT_TYPE, double > &aRemembered)
Value a freshly authored constraint dialog should open with.
std::optional< CONSTRAINT_MEMBER > NearestConstraintAnchor(const BOARD *aBoard, const VECTOR2I &aPos, double aMaxDist, const std::vector< CONSTRAINT_MEMBER > &aExclude, PCB_LAYER_ID aLayer)
Find the constrainable-item anchor (a shape's segment/arc endpoint or centre, or a dimension's featur...
wxString ConstraintSelectionHint(PCB_CONSTRAINT_TYPE aType)
A sentence naming what aType needs selected, for the moment a selection does not fit it and the tool ...
std::unique_ptr< PCB_CONSTRAINT > BuildConstraintFromItems(BOARD_ITEM *aParent, PCB_CONSTRAINT_TYPE aType, const std::vector< BOARD_ITEM * > &aItems)
Build a constraint of aType from a set of selected board items, or nullptr if the selection does not ...
std::optional< VECTOR2I > ConstraintAnchorPosition(const BOARD *aBoard, const CONSTRAINT_MEMBER &aMember)
Current location of a constraint member's anchor (its shape's START/END/CENTER, or a dimension's feat...
@ BULLSEYE
Definition cursors.h:55
bool EditConstraintValue(PCB_BASE_FRAME *aFrame, PCB_CONSTRAINT *aConstraint, BOARD_COMMIT &aCommit)
Show the value dialog for a valued constraint and stage the change in aCommit (the caller pushes).
#define _(s)
SHAPE_T
Definition eda_shape.h:54
@ UNDEFINED
Definition eda_shape.h:55
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
@ ELLIPSE_ARC
Definition eda_shape.h:63
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
KIID niluuid(0)
static std::vector< PENDING_PROPERTY > plan(const EDA_ITEM &aSource, const EDA_ITEM &aTarget, const std::set< wxString > &aEnabledKeys)
Target properties to write, paired with values read off the source.
Message panel definition file.
@ ALL
All except INITIAL_ADD.
Definition view_item.h:55
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
#define _HKI(x)
Definition page_info.cpp:40
bool ConstraintsAreDuplicate(const PCB_CONSTRAINT &aA, const PCB_CONSTRAINT &aB)
True if two constraints express the same relation, meaning the same type and the same members compare...
wxString ConstraintTypeLabel(PCB_CONSTRAINT_TYPE aType)
Human-readable name of a constraint type (e.g. "Parallel"), for menus and lists.
wxString ConstraintMemberLabel(BOARD_ITEM *aItem, const CONSTRAINT_MEMBER &aMember, UNITS_PROVIDER *aUnitsProvider)
Label for one constrained item in a list combining the item description with its anchored feature suc...
wxString ConstraintDisplayLabel(const PCB_CONSTRAINT &aConstraint, EDA_UNITS aUnits)
Display label for a constraint in lists and menus.
@ WHOLE
The item as a whole (a segment as a line, a circle).
PCB_CONSTRAINT_TYPE
The geometric relationship a PCB_CONSTRAINT enforces between its members.
@ CONCENTRIC
Two arcs/circles share a center.
@ SYMMETRIC
Two points are mirror images about an axis.
@ FIXED_POSITION
A point is locked at its current location.
@ VERTICAL
A segment (or two points) is vertical.
@ TANGENT
A line and a curve, or two curves, touch tangentially.
@ COINCIDENT
Two points are made to coincide.
@ FIXED_RADIUS
An arc/circle has a driving radius value.
@ HORIZONTAL
A segment (or two points) is horizontal.
@ EQUAL_RADIUS
Two arcs/circles have equal radius.
@ MIDPOINT
A point is the midpoint of a segment.
@ POINT_ON_LINE
A point lies on a segment's supporting line.
@ FIXED_LENGTH
A segment has a driving length value.
@ ARC_ANGLE
An arc has a driving or reference swept-angle value.
std::deque< PCB_CONSTRAINT * > CONSTRAINTS
#define APPEND_UNDO
Definition sch_commit.h:39
SCH_CONDITIONS S_C
T * GetAppSettings(const char *aFilename)
Board-wide diagnostics for the constraint overlay and info bar.
std::map< KIID, CONSTRAINT_STATE > shapeStates
std::vector< KIID > errored
Invalid constraints (member missing, deleted, or of a kind incompatible with the type).
One participant in a constraint: a referenced board item plus the feature of that item that participa...
wxString result
Test unit parsing edge cases and error handling.
@ TA_MODEL_CHANGE
Model has changed (partial update).
Definition tool_event.h:117
@ TA_MOUSE_MOTION
Definition tool_event.h:68
@ TC_MOUSE
Definition tool_event.h:51
@ TC_MESSAGE
Definition tool_event.h:54
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_SHAPE_LOCATE_SEGMENT_T
Definition typeinfo.h:129
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707