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_hoverCandidates.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
330const std::vector<PCB_SHAPE*>& CONSTRAINT_EDIT_TOOL::hoverCandidates()
331{
333 return *m_hoverCandidates;
334
335 std::set<KIID> ids;
336 std::vector<PCB_SHAPE*> shapes;
337
338 auto collect =
339 [&]( const CONSTRAINTS& aConstraints )
340 {
341 for( PCB_CONSTRAINT* c : aConstraints )
342 {
343 for( const CONSTRAINT_MEMBER& m : c->GetMembers() )
344 {
345 if( ids.insert( m.m_item ).second )
346 {
347 if( PCB_SHAPE* shape =
348 dynamic_cast<PCB_SHAPE*>( board()->ResolveItem( m.m_item, true ) ) )
349 {
350 shapes.push_back( shape );
351 }
352 }
353 }
354 }
355 };
356
357 if( board() )
358 {
359 collect( board()->Constraints() );
360
361 for( FOOTPRINT* footprint : board()->Footprints() )
362 collect( footprint->Constraints() );
363 }
364
365 m_hoverCandidates = std::move( shapes );
366 return *m_hoverCandidates;
367}
368
369
371{
372 // Cheapest guards first: only HOVER mode with constraints on the board does any work. Waiting
373 // tools (selection, router, move) already saw this motion before the transition loop reached us,
374 // so there is nothing to forward.
375 if( !m_overlay || m_overlay->GetVisibilityMode() != OVERLAY_MODE::HOVER || !board()
376 || !BoardHasConstraints( board() ) )
377 {
378 return 0;
379 }
380
383
384 // Stay sticky while the cursor is still over the current shape or one of its badges, so a badge
385 // does not blink out as the pointer moves off the thin outline toward it.
386 if( m_overlay->GetHoverShape() != niluuid )
387 {
388 if( PCB_SHAPE* shape =
389 dynamic_cast<PCB_SHAPE*>( board()->ResolveItem( m_overlay->GetHoverShape(), true ) ) )
390 {
391 double worldPerPx = CONSTRAINT_OVERLAY::BadgeWorldPerPixel( getView()->GetGAL()->GetWorldScale() );
392 std::vector<VECTOR2D> layout = CONSTRAINT_OVERLAY::LayoutBadges( m_overlay->Badges(), worldPerPx );
393 double badgeTol = CONSTRAINT_OVERLAY::BadgeHitRadius() * worldPerPx;
394
395 bool overBadge = std::ranges::any_of( layout,
396 [&]( const VECTOR2D& aPos )
397 { return ( aPos - VECTOR2D( cursor ) ).EuclideanNorm() <= badgeTol; } );
398
399 if( overBadge || shape->HitTest( cursor, KiROUND( tol ) ) )
400 return 0; // keep the current hover
401 }
402 }
403
404 std::optional<KIID> hit = NearestConstrainedShape( hoverCandidates(), cursor, KiROUND( tol ) );
405
406 // Only the hover filter changed, not the model, so redraw just the overlay from the cached
407 // diagnosis -- the panel (its row selection) and the info bar are left untouched.
408 if( m_overlay->SetHoverShape( hit.value_or( niluuid ) ) )
409 m_overlay->Update( ensureDiagnosis() );
410
411 return 0;
412}
413
414
416{
417 if( !getView() || !getView()->GetPainter() )
418 return nullptr;
419
420 return dynamic_cast<KIGFX::PCB_RENDER_SETTINGS*>( getView()->GetPainter()->GetSettings() );
421}
422
423
425{
427
428 if( !rs )
429 return;
430
431 std::unordered_set<KIID> constrained;
432
433 for( const auto& entry : aDiag.shapeStates )
434 constrained.insert( entry.first );
435
436 // Shadow is a cached layer so re-cache each item whose constrained state changed
437 // Set the new set BEFORE the re-cache so ViewGetLOD reads fresh membership on redraw
438 std::unordered_set<KIID> previous = rs->GetConstrainedItems();
439 rs->SetConstrainedItems( constrained );
440 repaintShadowItems( previous, constrained );
441}
442
443
445{
447
448 if( !rs )
449 return;
450
451 std::unordered_set<KIID> members;
452
453 if( aConstraint )
454 {
455 for( const CONSTRAINT_MEMBER& member : aConstraint->GetMembers() )
456 members.insert( member.m_item );
457 }
458
459 // Re-cache members whose highlight changed so the brighter shadow follows the badge selection
460 // Set the new membership before the re-cache so the redraw reads it
461 std::unordered_set<KIID> previous = rs->GetHighlightedConstraintMembers();
462 rs->SetHighlightedConstraintMembers( members );
463 repaintShadowItems( previous, members );
464}
465
466
467void CONSTRAINT_EDIT_TOOL::repaintShadowItems( const std::unordered_set<KIID>& aOld,
468 const std::unordered_set<KIID>& aNew )
469{
470 if( !board() || !getView() )
471 return;
472
473 // Re-cache only the symmetric difference items rather than the whole board every time
474 // ALL forces the regen a shape first cached unconstrained needs before it has shadow geometry
475 auto repaint = [&]( const KIID& aId )
476 {
477 if( BOARD_ITEM* item = board()->ResolveItem( aId, true ) )
478 getView()->Update( item, KIGFX::ALL );
479 };
480
481 for( const KIID& id : aOld )
482 {
483 if( !aNew.contains( id ) )
484 repaint( id );
485 }
486
487 for( const KIID& id : aNew )
488 {
489 if( !aOld.contains( id ) )
490 repaint( id );
491 }
492}
493
494
496{
497 if( m_overlay )
498 m_overlay->Update( aDiag );
499
500 // The constraint shadow layer draws only for items in this set a constant time gate at draw time
501 updateConstrainedItems( aDiag );
502
503 if( PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( frame() ) )
504 {
505 if( PANEL_CONSTRAINTS* panel = pcbFrame->GetConstraintsPanel(); panel && panel->IsShownOnScreen() )
506 panel->RefreshList( aDiag );
507 }
508}
509
510
512{
513 // Remember the member shapes so their clusters can re-settle once the constraint is gone.
514 std::vector<PCB_SHAPE*> members;
515
516 for( const CONSTRAINT_MEMBER& member : aConstraint->GetMembers() )
517 {
518 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( board()->ResolveItem( member.m_item, true ) ) )
519 members.push_back( shape );
520 }
521
522 BOARD_COMMIT commit( this );
523 commit.Remove( aConstraint );
524 commit.Push( _( "Remove Geometric Constraint" ) );
525
526 SolveAfterMove( members );
527
529}
530
531
533{
534 if( !board() || aId == niluuid )
535 return nullptr;
536
537 return dynamic_cast<PCB_CONSTRAINT*>( board()->ResolveItem( aId, true ) );
538}
539
540
542{
543 if( PCB_CONSTRAINT* constraint = resolveConstraint( aId ) )
544 removeConstraint( constraint );
545}
546
547
549{
550 if( !aConstraint )
551 return;
552
553 BOARD_COMMIT commit( this );
554
555 if( EditConstraintValue( frame(), aConstraint, commit ) )
556 {
557 // The committed value becomes the remembered default for the next creation of this type
558 // matching what the creation dialog stores
559 if( aConstraint->HasValue() )
560 {
561 m_lastConstraintValue[aConstraint->GetConstraintType()] = *aConstraint->GetValue();
562 m_lastConstraintDriving[aConstraint->GetConstraintType()] = aConstraint->IsDriving();
563 }
564
566 }
567}
568
569
571{
572 PCB_CONSTRAINT* constraint = resolveConstraint( aId );
573
574 if( !constraint )
575 return;
576
577 // A valueless relation has nothing to edit, so locate its members on the canvas instead of
578 // ignoring the gesture, matching the modal list's double-click behavior.
579 if( !constraint->HasValue() )
580 {
582 return;
583 }
584
585 editConstraint( constraint );
586}
587
588
589void CONSTRAINT_EDIT_TOOL::HighlightConstraintMembers( const KIID& aId, int aMemberIndex )
590{
591 PCB_CONSTRAINT* constraint = resolveConstraint( aId );
592
593 if( !constraint )
594 return;
595
596 // Selecting the members is a board selection, so drop any badge selection to keep the two
597 // selection models mutually exclusive (Delete then targets the highlighted members).
598 setSelectedConstraint( nullptr );
599 m_selectionTool->ClearSelection();
600
601 const std::vector<CONSTRAINT_MEMBER>& members = constraint->GetMembers();
602
603 // A click on a blank item cell (index past the members) falls back to highlighting all.
604 bool highlightOne = aMemberIndex >= 0 && aMemberIndex < static_cast<int>( members.size() );
605
606 auto selectMember = [&]( const CONSTRAINT_MEMBER& aMember )
607 {
608 if( BOARD_ITEM* item = board()->ResolveItem( aMember.m_item, true ) )
609 m_selectionTool->select( item );
610 };
611
612 if( highlightOne )
613 selectMember( members[aMemberIndex] );
614 else
615 std::ranges::for_each( members, selectMember );
616
617 // Zoom to what we just selected so the affected items fill the view.
618 if( !m_selectionTool->GetSelection().Empty() )
620
621 // Set this after selecting, which clears the isolate.
622 if( m_overlay && m_overlay->SetIsolated( aId ) )
623 m_overlay->RefreshSelection();
624}
625
626
628{
629 if( !board() || !aConstraint || aConstraint->GetMembers().empty() )
630 return false;
631
632 return std::ranges::all_of( aConstraint->GetMembers(),
633 [&]( const CONSTRAINT_MEMBER& aMember )
634 {
635 BOARD_ITEM* item = board()->ResolveItem( aMember.m_item, true );
636 return item && ConstraintItemIsLocked( item );
637 } );
638}
639
640
642{
643 if( !aConstraint || !board() )
644 return false;
645
646 auto scan = [&]( const CONSTRAINTS& aList )
647 {
648 return std::ranges::any_of( aList,
649 [&]( const PCB_CONSTRAINT* aExisting )
650 {
651 return aExisting != aConstraint && ConstraintsAreDuplicate( *aExisting, *aConstraint );
652 } );
653 };
654
655 if( scan( board()->Constraints() ) )
656 return true;
657
658 return board()->GetFirstFootprint() && scan( board()->GetFirstFootprint()->Constraints() );
659}
660
661
663 const std::vector<PCB_CONSTRAINT*>& aAdded )
664{
665 aCommit.Push( _( "Add Geometric Constraint" ) );
666
667 // Solving any one constraint pins its first member and pulls the rest of the cluster into place.
668 if( !aAdded.empty() )
669 solveAddedConstraint( aAdded.front() );
670
672
673 if( aAdded.empty() || !frame() )
674 return;
675
676 // A locked shape is a fixed reference the solver may not move. If every referenced item is
677 // locked there is nothing it can adjust, so the relation is recorded but the geometry cannot
678 // snap; tell the user rather than appearing to silently do nothing. A fix asks for exactly
679 // that outcome, so on a locked item it is redundant, not thwarted.
680 if( aAdded.front()->GetConstraintType() != PCB_CONSTRAINT_TYPE::FIXED_POSITION
681 && allMembersLocked( aAdded.front() ) )
682 {
683 frame()->ShowInfoBarWarning( _( "All items referenced by this constraint are locked, so the constraint cannot "
684 "move any geometry." ),
685 true );
686 return;
687 }
688
689 // In the default configuration (hover overlay idle, panel hidden) nothing else surfaces the
690 // diagnosis, so an unsatisfiable new constraint must be called out here or the add appears to
691 // succeed silently. Mirrors the SolveAfterMove() warning for the same condition.
693
694 for( const PCB_CONSTRAINT* added : aAdded )
695 {
696 if( alg::contains( diag.conflicting, added->m_Uuid ) )
697 {
698 frame()->ShowInfoBarWarning( _( "The new geometric constraint conflicts with existing constraints and "
699 "could not be satisfied." ),
700 true );
701 return;
702 }
703
704 if( alg::contains( diag.errored, added->m_Uuid ) )
705 {
706 frame()->ShowInfoBarWarning( _( "The new geometric constraint could not be applied to the referenced "
707 "items." ),
708 true );
709 return;
710 }
711 }
712}
713
714
716{
717 // With no badge selected, fall through to the normal item-delete path.
718 if( !m_overlay || m_overlay->GetSelected() == niluuid )
719 return false;
720
721 PCB_CONSTRAINT* constraint = resolveConstraint( m_overlay->GetSelected() );
722
723 setSelectedConstraint( nullptr );
724
725 // The selected constraint vanished (undo / panel delete) but the badge selection lingered;
726 // consume the Delete so it does not fall through and remove a hovered board item.
727 if( !constraint )
728 return true;
729
730 removeConstraint( constraint );
731 return true;
732}
733
734
736{
737 // With no badge selected, fall through to the normal properties path.
738 if( !m_overlay || m_overlay->GetSelected() == niluuid )
739 return false;
740
741 PCB_CONSTRAINT* constraint = resolveConstraint( m_overlay->GetSelected() );
742
743 // The selected constraint vanished (undo / panel delete) but the badge selection lingered;
744 // clear it and fall through instead of consuming the key with no effect.
745 if( !constraint )
746 {
747 setSelectedConstraint( nullptr );
748 return false;
749 }
750
751 // editConstraint only opens a dialog for a valued constraint; others just consume the key.
752 editConstraint( constraint );
753 return true;
754}
755
756
758{
759 // A fix contributes no equation -- it is enforced by dropping its point from the unknowns -- so
760 // there is nothing to snap to. Re-solving anyway would only expose the pinned shape to the
761 // cluster's other constraints with its own stay-put pin lifted, which can drift it.
763 return;
764
765 // This solve runs after the add was pushed because SolveCluster gathers the cluster from
766 // board->Constraints(), and BOARD_COMMIT only makes the constraint live at Push time.
767 // APPEND_UNDO folds the snap into the add so creating a constraint is a single undoable action.
768 BOARD_COMMIT commit( this );
769 std::vector<PCB_SHAPE*> modified;
770
772 board(), aConstraint, &modified,
773 [&]( BOARD_ITEM* aItem )
774 {
775 commit.Modify( aItem );
776 },
777 ConstraintReferenceShapes( board(), aConstraint ) );
778
779 // Push if the snap moved a shape or re-measured a reference value in this cluster.
780 if( !commit.Empty() )
781 commit.Push( _( "Apply Geometric Constraint" ), APPEND_UNDO );
782}
783
784
785void CONSTRAINT_EDIT_TOOL::SolveAfterMove( const std::vector<PCB_SHAPE*>& aShapes )
786{
787 if( aShapes.empty() || !board() || !BoardHasConstraints( board() ) )
788 return;
789
790 BOARD_COMMIT commit( this );
791 std::vector<PCB_SHAPE*> modified;
792
793 ReSolveShapeClusters( board(), aShapes, &modified,
794 [&]( BOARD_ITEM* aItem )
795 {
796 commit.Modify( aItem );
797 } );
798
799 if( !commit.Empty() )
800 commit.Push( _( "Apply Geometric Constraint" ), APPEND_UNDO );
801
802 DiagnoseAfterMove( aShapes );
803}
804
805
806void CONSTRAINT_EDIT_TOOL::SolveAfterEdit( const std::vector<PCB_SHAPE*>& aShapes )
807{
808 if( aShapes.empty() || !board() || !BoardHasConstraints( board() ) )
809 return;
810
811 BOARD_COMMIT commit( this );
812 std::vector<PCB_SHAPE*> modified;
813
814 ReSolveShapeClustersHoldingEdited( board(), aShapes, &modified,
815 [&]( BOARD_ITEM* aItem )
816 {
817 commit.Modify( aItem );
818 } );
819
820 if( !commit.Empty() )
821 commit.Push( _( "Apply Geometric Constraint" ), APPEND_UNDO );
822
823 DiagnoseAfterMove( aShapes );
824}
825
826
827void CONSTRAINT_EDIT_TOOL::DiagnoseAfterMove( const std::vector<PCB_SHAPE*>& aShapes )
828{
829 if( aShapes.empty() || !board() || !BoardHasConstraints( board() ) )
830 return;
831
832 // Diagnose once and use it for both the views and the warning below, so a transform does not
833 // pay for two board-wide solves. Cache it so a following hover reuses this fresh result.
834 m_cachedDiag = m_diagnoser.Diagnose( board() );
835 m_diagDirty = false;
837 applyDiagnostics( diag );
838
839 // If the edit left a moved shape's constraint unsatisfiable, say so even when the overlay is off.
840 bool overConstrained = std::ranges::any_of( aShapes,
841 [&]( const PCB_SHAPE* aShape )
842 {
843 auto it = diag.shapeStates.find( aShape->m_Uuid );
844 return it != diag.shapeStates.end() && it->second == CONSTRAINT_STATE::OVER_CONSTRAINED;
845 } );
846
847 if( overConstrained && frame() )
848 frame()->ShowInfoBarWarning( _( "A geometric constraint could not be satisfied by this edit." ), true );
849}
850
851
853{
854 if( !m_overlay && board() && getView() )
855 m_overlay = std::make_unique<CONSTRAINT_OVERLAY>( board(), getView() );
856
857 if( m_overlay )
858 {
859 // The action, not a toggle, chooses the mode, so a freshly created overlay can never invert
860 // the clicked label. The overlay object itself stays alive either way.
861 bool always = aEvent.IsAction( &PCB_ACTIONS::showConstraints );
862 m_overlay->SetVisibilityMode( always ? OVERLAY_MODE::ALWAYS : OVERLAY_MODE::HOVER );
863
864 if( !always )
865 m_overlay->SetHoverShape( niluuid ); // hover mode starts hidden until a hover
866
867 // Remember the choice so it persists across board reloads and sessions.
868 if( frame() )
869 frame()->GetPcbNewSettings()->m_Display.m_ShowConstraints = always;
870 }
871
873 return 0;
874}
875
876
878{
879 // In the board editor the constraint list is a dockable pane; the footprint editor (which has
880 // no such pane) falls back to the modal list dialog.
881 if( PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( frame() ) )
882 {
883 pcbFrame->ToggleConstraintsPanel();
884 return 0;
885 }
886
887 auto highlight =
888 [&]( PCB_CONSTRAINT* aConstraint )
889 {
890 m_selectionTool->ClearSelection();
891
892 for( const CONSTRAINT_MEMBER& member : aConstraint->GetMembers() )
893 {
894 if( BOARD_ITEM* item = board()->ResolveItem( member.m_item, true ) )
895 m_selectionTool->select( item );
896 }
897 };
898
899 auto remove = [&]( PCB_CONSTRAINT* aConstraint ) { removeConstraint( aConstraint ); };
900
901 DIALOG_CONSTRAINT_LIST dlg( frame(), board(), highlight, remove );
902 dlg.ShowModal();
903
904 return 0;
905}
906
907
909{
910 if( frame()->IsType( FRAME_PCB_EDITOR ) )
911 {
914 }
915 else
916 {
919 }
920
921 return 0;
922}
923
924
926{
927 m_diagDirty = true; // the model changed, so the cached diagnosis is stale
929
930 aEvent.PassEvent();
931 return 0;
932}
933
934
936{
938
939 if( !m_selectionTool )
940 return false;
941
943
944 static const std::vector<KICAD_T> segmentType = { PCB_SHAPE_LOCATE_SEGMENT_T };
945
946 auto twoSegments = S_C::Count( 2 ) && S_C::OnlyTypes( segmentType );
947 auto oneSegment = S_C::Count( 1 ) && S_C::OnlyTypes( segmentType );
948
949 auto kindOf = []( const EDA_ITEM* aItem ) -> SHAPE_T
950 {
951 if( !aItem || aItem->Type() != PCB_SHAPE_T )
952 return SHAPE_T::UNDEFINED;
953
954 return static_cast<const PCB_SHAPE*>( aItem )->GetShape();
955 };
956
957 // A circle or arc has a radius; a closed or arc ellipse adds to that a centre only.
958 auto isRadial = []( SHAPE_T aShape )
959 {
960 return aShape == SHAPE_T::CIRCLE || aShape == SHAPE_T::ARC;
961 };
962
963 auto isCentered = [isRadial]( SHAPE_T aShape )
964 {
965 return isRadial( aShape ) || aShape == SHAPE_T::ELLIPSE || aShape == SHAPE_T::ELLIPSE_ARC;
966 };
967
968 auto oneRadial = [kindOf, isRadial]( const SELECTION& aSel )
969 {
970 return aSel.Size() == 1 && isRadial( kindOf( aSel[0] ) );
971 };
972
973 auto oneArc = [kindOf]( const SELECTION& aSel )
974 {
975 return aSel.Size() == 1 && kindOf( aSel[0] ) == SHAPE_T::ARC;
976 };
977
978 auto twoRadial = [kindOf, isRadial]( const SELECTION& aSel )
979 {
980 return aSel.Size() == 2 && isRadial( kindOf( aSel[0] ) ) && isRadial( kindOf( aSel[1] ) );
981 };
982
983 auto twoCentered = [kindOf, isCentered]( const SELECTION& aSel )
984 {
985 return aSel.Size() == 2 && isCentered( kindOf( aSel[0] ) ) && isCentered( kindOf( aSel[1] ) );
986 };
987
988 // Tangent joins a line with a curve, or two circles/arcs.
989 auto tangentPair = [kindOf, isCentered, isRadial]( const SELECTION& aSel )
990 {
991 if( aSel.Size() != 2 )
992 return false;
993
994 SHAPE_T a = kindOf( aSel[0] );
995 SHAPE_T b = kindOf( aSel[1] );
996
997 return ( a == SHAPE_T::SEGMENT && isCentered( b ) ) || ( b == SHAPE_T::SEGMENT && isCentered( a ) )
998 || ( isRadial( a ) && isRadial( b ) );
999 };
1000
1001 // Only offer Remove when something selected actually carries a constraint.
1002 auto selectionConstrained = [this]( const SELECTION& aSel ) -> bool
1003 {
1004 if( aSel.Empty() || !board() )
1005 return false;
1006
1007 std::set<KIID> ids;
1008
1009 for( EDA_ITEM* item : aSel )
1010 ids.insert( item->m_Uuid );
1011
1012 auto anyMember = [&]( const CONSTRAINTS& aList )
1013 {
1014 return std::ranges::any_of( aList,
1015 [&]( const PCB_CONSTRAINT* c )
1016 {
1017 return std::ranges::any_of( c->GetMembers(),
1018 [&]( const CONSTRAINT_MEMBER& m ) { return ids.contains( m.m_item ); } );
1019 } );
1020 };
1021
1022 if( anyMember( board()->Constraints() ) )
1023 return true;
1024
1026 && anyMember( board()->GetFirstFootprint()->Constraints() );
1027 };
1028
1029 // One "Constraints" submenu holds every constraint command. The add-type items are gated by
1030 // the current selection so only constraints valid for what is selected are offered; the
1031 // manage/show/remove items are always present.
1032 m_menu = new CONDITIONAL_MENU( this );
1033 m_menu->SetIcon( BITMAPS::measurement );
1034 m_menu->SetUntranslatedTitle( _HKI( "Constraints" ) );
1035
1036 // Gate each selection-based add-type by what it needs; the point-anchored families are authored
1037 // by clicking and need no prior selection, so they are absent here and default to ShowAlways.
1038 // The action list itself lives in PCB_ACTIONS::ConstraintAddActions() so the menubar's copy of
1039 // this submenu cannot drift from it.
1040 const std::map<const TOOL_ACTION*, SELECTION_CONDITION> gate = {
1041 { &PCB_ACTIONS::addConstraintParallel, twoSegments },
1043 { &PCB_ACTIONS::addConstraintEqualLength, twoSegments },
1044 { &PCB_ACTIONS::addConstraintCollinear, twoSegments },
1045 { &PCB_ACTIONS::addConstraintAngular, twoSegments },
1046 { &PCB_ACTIONS::addConstraintTangent, tangentPair },
1047 { &PCB_ACTIONS::addConstraintHorizontal, oneSegment },
1048 { &PCB_ACTIONS::addConstraintVertical, oneSegment },
1050 { &PCB_ACTIONS::addConstraintConcentric, twoCentered },
1054 };
1055
1056 bool pointGroupSeparated = false;
1057
1058 for( const TOOL_ACTION* action : PCB_ACTIONS::ConstraintAddActions() )
1059 {
1060 if( auto it = gate.find( action ); it != gate.end() )
1061 {
1062 m_menu->AddItem( *action, it->second );
1063 }
1064 else
1065 {
1066 if( !pointGroupSeparated )
1067 {
1068 m_menu->AddSeparator();
1069 pointGroupSeparated = true;
1070 }
1071
1072 m_menu->AddItem( *action, S_C::ShowAlways );
1073 }
1074 }
1075
1076 // Show while hidden, Hide while shown, so the label always names what the click will do.
1077 // "Shown" is the always-on mode; the hover mode reads as hidden and offers the Show action.
1078 // The ALWAYS/HOVER choice is a tool mode rather than an object visibility, which is why it
1079 // lives here and not in the Appearance panel's Objects tab.
1080 auto overlayShown = [this]( const SELECTION& )
1081 {
1082 return m_overlay && m_overlay->GetVisibilityMode() == OVERLAY_MODE::ALWAYS;
1083 };
1084 auto overlayHidden = [this]( const SELECTION& )
1085 {
1086 return !m_overlay || m_overlay->GetVisibilityMode() == OVERLAY_MODE::HOVER;
1087 };
1088
1089 m_menu->AddSeparator();
1090 m_menu->AddItem( PCB_ACTIONS::removeConstraints, selectionConstrained );
1091 m_menu->AddItem( PCB_ACTIONS::showConstraints, overlayHidden );
1092 m_menu->AddItem( PCB_ACTIONS::hideConstraints, overlayShown );
1094
1095 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
1096 selToolMenu.AddMenu( m_menu, S_C::ShowAlways, 100 );
1097
1098 return true;
1099}
1100
1101
1103{
1105 const PCB_SELECTION& selection = m_selectionTool->GetSelection();
1106
1107 std::vector<BOARD_ITEM*> items;
1108
1109 for( EDA_ITEM* item : selection )
1110 {
1111 if( item->IsBOARD_ITEM() )
1112 items.push_back( static_cast<BOARD_ITEM*>( item ) );
1113 }
1114
1115 std::unique_ptr<PCB_CONSTRAINT> constraint =
1117
1118 // A valid selection builds right away (the context-menu path). Otherwise drop into click-to-pick
1119 // so the toolbar and hotkeys can author a constraint by clicking the items on the canvas.
1120 if( !constraint )
1121 {
1122 // Switching to click-to-pick silently reads as the tool ignoring the selection, so say what
1123 // the type wanted. Only when something was selected: an empty one is the normal toolbar path.
1124 if( !items.empty() && frame() )
1125 {
1126 if( wxString hint = ConstraintSelectionHint( type ); !hint.IsEmpty() )
1127 frame()->ShowInfoBarWarning( hint );
1128 }
1129
1130 // Horizontal and vertical also accept two point anchors so levelling a corner needs no segment
1131 // The linear picker offers both paths
1133 return pickLinearConstraint( type, aEvent );
1134
1135 return pickShapeConstraint( type, aEvent );
1136 }
1137
1138 commitConstraint( std::move( constraint ) );
1139 return 0;
1140}
1141
1142
1143bool CONSTRAINT_EDIT_TOOL::commitConstraint( std::unique_ptr<PCB_CONSTRAINT> aConstraint )
1144{
1145 if( !aConstraint )
1146 return false;
1147
1148 if( isDuplicateConstraint( aConstraint.get() ) )
1149 {
1150 if( frame() )
1151 frame()->ShowInfoBarWarning( _( "An identical geometric constraint already exists." ) );
1152
1153 return false;
1154 }
1155
1156 // For a dimensional constraint, let the user confirm/override the measured value and choose
1157 // driving vs reference (issue #2329 step 7).
1158 if( aConstraint->HasValue() )
1159 {
1160 PCB_CONSTRAINT_TYPE type = aConstraint->GetConstraintType();
1161
1162 // Default to the shape measurement unless the user already set a value for this type this session
1163 // Reuse that so a run of same type constraints keeps one size
1164 double initial = InitialConstraintValue( type, *aConstraint->GetValue(), m_lastConstraintValue );
1165
1166 bool driving = aConstraint->IsDriving();
1167
1168 if( auto it = m_lastConstraintDriving.find( type ); it != m_lastConstraintDriving.end() )
1169 driving = it->second;
1170
1171 DIALOG_CONSTRAINT_VALUE dlg( frame(), type, initial, driving );
1172
1173 if( dlg.ShowModal() != wxID_OK )
1174 return false;
1175
1176 aConstraint->SetValue( dlg.GetConstraintValue() );
1177 aConstraint->SetDriving( dlg.GetDriving() );
1178
1180 m_lastConstraintDriving[type] = dlg.GetDriving();
1181 }
1182
1183 PCB_CONSTRAINT* added = aConstraint.get();
1184
1185 BOARD_COMMIT commit( this );
1186 commit.Add( aConstraint.release() );
1187 finishConstraintCommit( commit, { added } );
1188
1189 return true;
1190}
1191
1192
1194{
1195 int count = 2;
1196 bool allowCircle = false;
1197
1198 switch( aType )
1199 {
1203 count = 1;
1204 allowCircle = false;
1205 break;
1208 count = 1;
1209 allowCircle = true;
1210 break;
1214 count = 2;
1215 allowCircle = true;
1216 break;
1217 default:
1218 count = 2;
1219 allowCircle = false;
1220 break;
1221 }
1222
1223 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
1224
1225 if( !picker )
1226 return 0;
1227
1228 std::vector<KIID> picked;
1229 const double snapTol = pcbIUScale.mmToIU( 1.0 );
1230
1231 Activate();
1232 picker->SetCursor( KICURSOR::BULLSEYE );
1233 picker->SetSnapping( true );
1234 picker->ClearHandlers();
1235
1236 picker->SetClickHandler(
1237 [&]( const VECTOR2D& aPoint ) -> bool
1238 {
1239 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1240 std::optional<KIID> target = NearestOutlineShape( board(), pos, snapTol, allowCircle );
1241
1242 if( !target || alg::contains( picked, *target ) )
1243 return true; // nothing new snapped, keep picking
1244
1245 picked.push_back( *target );
1246
1247 if( static_cast<int>( picked.size() ) < count )
1248 {
1249 // Clear the preview of the just consumed target
1250 // Not advertised until the next motion event re-derives the pick
1251 if( m_overlay )
1252 m_overlay->ClearPickPreview();
1253
1254 return true; // need more
1255 }
1256
1257 std::vector<BOARD_ITEM*> items;
1258
1259 for( const KIID& id : picked )
1260 {
1261 if( BOARD_ITEM* item = board()->ResolveItem( id, true ) )
1262 items.push_back( item );
1263 }
1264
1265 std::unique_ptr<PCB_CONSTRAINT> constraint =
1266 BuildConstraintFromItems( constraintParent(), aType, items );
1267
1268 if( !constraint && frame() )
1269 {
1270 frame()->ShowInfoBarWarning( wxString::Format( _( "Cannot form a %s constraint from those items." ),
1271 ConstraintTypeLabel( aType ) ) );
1272 }
1273
1274 commitConstraint( std::move( constraint ) );
1275 picked.clear();
1276 return true; // stay active so more can be placed, like the draw tools
1277 } );
1278
1279 // Outline the element the next pick would take so the target is clear before clicking
1280 picker->SetMotionHandler(
1281 [&]( const VECTOR2D& aPoint )
1282 {
1283 if( !m_overlay )
1284 return;
1285
1286 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1287 std::optional<KIID> target = NearestOutlineShape( board(), pos, snapTol, allowCircle );
1288
1289 // Mirror the click handler rejection so an already picked shape is not advertised
1290 // as eligible for the remaining picks
1291 if( target && alg::contains( picked, *target ) )
1292 target.reset();
1293
1294 m_overlay->SetPickPreview( target.value_or( niluuid ), true, std::nullopt );
1295 } );
1296
1297 bool done = false;
1298
1299 picker->SetFinalizeHandler(
1300 [&]( const int& )
1301 {
1302 done = true;
1303 } );
1304
1305 m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
1306
1307 while( !done )
1308 {
1309 if( TOOL_EVENT* evt = Wait() )
1310 evt->SetPassEvent();
1311 else
1312 break;
1313 }
1314
1315 picker->ClearHandlers();
1316
1317 if( m_overlay )
1318 m_overlay->ClearPickPreview();
1319
1320 return 0;
1321}
1322
1323
1325{
1326 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
1327
1328 if( !picker )
1329 return 0;
1330
1331 // Empty until the first pick is a point then holds that point while the second is chosen
1332 std::vector<CONSTRAINT_MEMBER> members;
1333 const double snapTol = pcbIUScale.mmToIU( 1.0 );
1334
1335 Activate();
1336 picker->SetCursor( KICURSOR::BULLSEYE );
1337 picker->SetSnapping( true );
1338 picker->ClearHandlers();
1339
1340 auto commitPointPair = [&]()
1341 {
1342 std::unique_ptr<PCB_CONSTRAINT> constraint =
1343 std::make_unique<PCB_CONSTRAINT>( constraintParent(), aType );
1344
1345 for( const CONSTRAINT_MEMBER& member : members )
1346 constraint->AddMember( member.m_item, member.m_anchor, member.m_index );
1347
1348 commitConstraint( std::move( constraint ) );
1349 };
1350
1351 picker->SetClickHandler(
1352 [&]( const VECTOR2D& aPoint ) -> bool
1353 {
1354 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1355
1356 // A point under the cursor wins over its segment so clicking a corner starts the two point path
1357 // A segment middle has no anchor and takes the whole segment path
1358 std::optional<CONSTRAINT_MEMBER> anchor =
1359 NearestConstraintAnchor( board(), pos, snapTol, members );
1360
1361 if( anchor )
1362 {
1363 members.push_back( *anchor );
1364
1365 if( members.size() < 2 )
1366 {
1367 if( m_overlay )
1368 m_overlay->ClearPickPreview();
1369
1370 return true; // need the second point
1371 }
1372
1373 commitPointPair();
1374 members.clear();
1375 return true; // stay active so more can be placed, like the draw tools
1376 }
1377
1378 // No anchor snapped before the first point a whole segment authors immediately
1379 // Once a point is held only a second point completes the pair
1380 if( members.empty() )
1381 {
1382 if( std::optional<KIID> target = NearestOutlineShape( board(), pos, snapTol, false ) )
1383 {
1384 std::unique_ptr<PCB_CONSTRAINT> constraint =
1385 std::make_unique<PCB_CONSTRAINT>( constraintParent(), aType );
1386 constraint->AddMember( *target, CONSTRAINT_ANCHOR::WHOLE );
1387 commitConstraint( std::move( constraint ) );
1388 }
1389 }
1390
1391 return true;
1392 } );
1393
1394 picker->SetMotionHandler(
1395 [&]( const VECTOR2D& aPoint )
1396 {
1397 if( !m_overlay )
1398 return;
1399
1400 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1401
1402 // Mirror the click handler so the preview shows exactly what the next click takes.
1403 if( std::optional<CONSTRAINT_MEMBER> anchor =
1404 NearestConstraintAnchor( board(), pos, snapTol, members ) )
1405 {
1406 m_overlay->SetPickPreview( anchor->m_item, false,
1408 }
1409 else if( members.empty() )
1410 {
1411 if( std::optional<KIID> target = NearestOutlineShape( board(), pos, snapTol, false ) )
1412 m_overlay->SetPickPreview( *target, true, std::nullopt );
1413 else
1414 m_overlay->ClearPickPreview();
1415 }
1416 else
1417 {
1418 m_overlay->ClearPickPreview();
1419 }
1420 } );
1421
1422 bool done = false;
1423
1424 picker->SetFinalizeHandler(
1425 [&]( const int& )
1426 {
1427 done = true;
1428 } );
1429
1430 m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
1431
1432 while( !done )
1433 {
1434 if( TOOL_EVENT* evt = Wait() )
1435 evt->SetPassEvent();
1436 else
1437 break;
1438 }
1439
1440 picker->ClearHandlers();
1441
1442 if( m_overlay )
1443 m_overlay->ClearPickPreview();
1444
1445 return 0;
1446}
1447
1448
1450{
1452
1453 // In the pick plan, true means click a point anchor and false means click a whole segment.
1454 std::vector<bool> plan;
1455
1456 switch( type )
1457 {
1458 case PCB_CONSTRAINT_TYPE::FIXED_POSITION: plan = { true }; break;
1459 case PCB_CONSTRAINT_TYPE::COINCIDENT: plan = { true, true }; break;
1461 case PCB_CONSTRAINT_TYPE::MIDPOINT: plan = { true, false }; break;
1462 case PCB_CONSTRAINT_TYPE::SYMMETRIC: plan = { true, true, false }; break;
1463 default: return 0;
1464 }
1465
1466 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
1467
1468 if( !picker )
1469 return 0;
1470
1471 std::vector<CONSTRAINT_MEMBER> members;
1472 const double snapTol = pcbIUScale.mmToIU( 1.0 );
1473
1474 Activate();
1475 picker->SetCursor( KICURSOR::BULLSEYE );
1476 picker->SetSnapping( true );
1477 picker->ClearHandlers();
1478
1479 picker->SetClickHandler(
1480 [&]( const VECTOR2D& aPoint ) -> bool
1481 {
1482 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1483
1484 if( plan[members.size()] ) // wants a point anchor
1485 {
1486 // Exclude already picked handles so the same shape and anchor cannot repeat
1487 // while a coincident but distinct endpoint stays reachable
1488 std::optional<CONSTRAINT_MEMBER> anchor =
1489 NearestConstraintAnchor( board(), pos, snapTol, members );
1490
1491 if( !anchor )
1492 return true; // nothing snapped; keep picking
1493
1494 members.push_back( *anchor );
1495 }
1496 else // wants a whole shape
1497 {
1498 std::optional<KIID> target =
1500
1501 if( !target )
1502 return true;
1503
1504 members.emplace_back( *target, CONSTRAINT_ANCHOR::WHOLE );
1505 }
1506
1507 if( members.size() < plan.size() )
1508 {
1509 // Clear the preview of the just consumed target
1510 // Not advertised until the next motion event re-derives the pick
1511 if( m_overlay )
1512 m_overlay->ClearPickPreview();
1513
1514 return true; // need more picks
1515 }
1516
1517 std::unique_ptr<PCB_CONSTRAINT> constraint =
1518 std::make_unique<PCB_CONSTRAINT>( constraintParent(), type );
1519
1520 for( const CONSTRAINT_MEMBER& member : members )
1521 constraint->AddMember( member.m_item, member.m_anchor, member.m_index );
1522
1523 if( isDuplicateConstraint( constraint.get() ) )
1524 {
1525 if( frame() )
1526 frame()->ShowInfoBarWarning( _( "An identical geometric constraint already exists." ) );
1527
1528 return false; // done
1529 }
1530
1531 PCB_CONSTRAINT* added = constraint.get();
1532
1533 BOARD_COMMIT commit( this );
1534 commit.Add( constraint.release() );
1535 finishConstraintCommit( commit, { added } );
1536
1537 return false; // done
1538 } );
1539
1540 // Preview the next pick target before the click the point steps also mark the exact anchor
1541 picker->SetMotionHandler(
1542 [&]( const VECTOR2D& aPoint )
1543 {
1544 if( !m_overlay || members.size() >= plan.size() )
1545 return;
1546
1547 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1548
1549 KIID element = niluuid;
1550 bool whole = true;
1551 std::optional<VECTOR2I> anchorPos;
1552
1553 if( plan[members.size()] ) // wants a point anchor
1554 {
1555 if( std::optional<CONSTRAINT_MEMBER> anchor =
1556 NearestConstraintAnchor( board(), pos, snapTol, members ) )
1557 {
1558 element = anchor->m_item;
1559 whole = false;
1560 anchorPos = ConstraintAnchorPosition( board(), *anchor );
1561 }
1562 }
1563 else if( std::optional<KIID> target = NearestOutlineShape(
1564 board(), pos, snapTol, type == PCB_CONSTRAINT_TYPE::POINT_ON_LINE ) )
1565 {
1566 element = *target;
1567 }
1568
1569 m_overlay->SetPickPreview( element, whole, anchorPos );
1570 } );
1571
1572 bool done = false;
1573
1574 picker->SetFinalizeHandler(
1575 [&]( const int& aFinalState )
1576 {
1577 done = true;
1578 } );
1579
1580 m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
1581
1582 // RunAction returns before picking ends. Wait so the handlers' captures stay alive.
1583 while( !done )
1584 {
1585 if( TOOL_EVENT* evt = Wait() )
1586 evt->SetPassEvent();
1587 else
1588 break;
1589 }
1590
1591 picker->ClearHandlers();
1592
1593 if( m_overlay )
1594 m_overlay->ClearPickPreview();
1595
1596 return 0;
1597}
1598
1599
1601{
1602 PCB_SELECTION& selection = m_selectionTool->GetSelection();
1603 std::set<KIID> selectedIds;
1604
1605 for( EDA_ITEM* item : selection )
1606 selectedIds.insert( item->m_Uuid );
1607
1608 if( selectedIds.empty() )
1609 return 0;
1610
1611 BOARD_COMMIT commit( this );
1612 bool any = false;
1613
1614 auto removeReferencing =
1615 [&]( const CONSTRAINTS& aConstraints )
1616 {
1617 for( PCB_CONSTRAINT* constraint : aConstraints )
1618 {
1619 bool referenced = std::ranges::any_of( constraint->GetMembers(),
1620 [&]( const CONSTRAINT_MEMBER& aMember )
1621 { return selectedIds.contains( aMember.m_item ); } );
1622
1623 if( referenced )
1624 {
1625 commit.Remove( constraint );
1626 any = true;
1627 }
1628 }
1629 };
1630
1631 if( board() )
1632 {
1633 removeReferencing( board()->Constraints() );
1634
1635 if( IsFootprintEditor() && board()->GetFirstFootprint() )
1636 removeReferencing( board()->GetFirstFootprint()->Constraints() );
1637 }
1638
1639 if( any )
1640 commit.Push( _( "Remove Geometric Constraints" ) );
1641
1643
1644 return 0;
1645}
1646
1647
1649{
1663
1669
1675
1676 // Keep the diagnostics overlay current as the board changes underneath it. Undo/redo posts its
1677 // own event (not TA_MODEL_CHANGE), so listen for it too or a restored/removed constraint's badge
1678 // would not reappear/disappear.
1681
1682 // Show/refresh the endpoint markers as the selection changes.
1686
1687 // No other pcbnew tool registers a plain-motion transition (only one transition runs per mouse
1688 // event), and waiting tools receive motion before this loop, so claiming it here is safe.
1690}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
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:986
static TOOL_ACTION pickerTool
Definition actions.h:249
static TOOL_ACTION zoomFitSelection
Definition actions.h:140
virtual 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:83
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:599
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Definition board.cpp:1928
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:134
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).
std::optional< std::vector< PCB_SHAPE * > > m_hoverCandidates
Constrained shapes, per model.
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.
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.
BOARD_CONSTRAINT_DIAGNOSER m_diagnoser
Incremental board diagnoser for the hot edit path, so editing one shape re-solves only its own cluste...
const std::vector< PCB_SHAPE * > & hoverCandidates()
The shapes referenced by any constraint, the hover-hit candidate set (cached per model).
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:96
const KIID m_Uuid
Definition eda_item.h:531
static const TOOL_EVENT ClearedEvent
Definition actions.h:345
static const TOOL_EVENT SelectedEvent
Definition actions.h:343
static const TOOL_EVENT UndoRedoPostEvent
Definition actions.h:367
static const TOOL_EVENT UnselectedEvent
Definition actions.h:344
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:80
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:1835
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:534
PAINTER * GetPainter() const
Return the painter object used by the view for drawing #VIEW_ITEMS.
Definition view.h:225
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.
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
FOOTPRINT * footprint() const
void SetMotionHandler(MOTION_HANDLER aHandler)
Set a handler for mouse motion.
Definition picker_tool.h:88
void SetClickHandler(CLICK_HANDLER aHandler)
Set a handler for mouse click event.
Definition picker_tool.h:77
void SetSnapping(bool aSnap)
Definition picker_tool.h:62
void SetCursor(KICURSOR aCursor)
Definition picker_tool.h:60
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
Returns the instance of #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:469
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
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< KIID > NearestConstrainedShape(const std::vector< PCB_SHAPE * > &aCandidates, const VECTOR2I &aPos, int aMaxDist)
The candidate shape whose outline aPos hits within aMaxDist, or std::nullopt.
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< KIID > NearestOutlineShape(BOARD *aBoard, const VECTOR2I &aPos, double aMaxDist, bool aAllowCircle)
The shape whose outline is nearest aPos within aMaxDist.
std::optional< VECTOR2I > ConstraintAnchorPosition(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...
std::optional< CONSTRAINT_MEMBER > NearestConstraintAnchor(BOARD *aBoard, const VECTOR2I &aPos, double aMaxDist, const std::vector< CONSTRAINT_MEMBER > &aExclude)
Find the constrainable-item anchor (a shape's segment/arc endpoint or centre, or a dimension's featur...
@ BULLSEYE
Definition cursors.h:54
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:44
@ UNDEFINED
Definition eda_shape.h:45
@ ELLIPSE
Definition eda_shape.h:52
@ SEGMENT
Definition eda_shape.h:46
@ ELLIPSE_ARC
Definition eda_shape.h:53
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
KIID niluuid(0)
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:37
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:81
@ PCB_SHAPE_LOCATE_SEGMENT_T
Definition typeinfo.h:130
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682