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>
59
60#include <base_units.h>
61#include <geometry/seg.h>
62
63
64namespace
65{
66// The whole shape whose outline is nearest aPos within aMaxDist. Circles and arcs count only when
67// aAllowCircle is set (point-on-line targets), never for midpoint or symmetry-axis picks.
68std::optional<KIID> nearestOutline( BOARD* aBoard, const VECTOR2I& aPos, double aMaxDist, bool aAllowCircle )
69{
70 double best = aMaxDist;
71 std::optional<KIID> result;
72
73 for( PCB_SHAPE* shape : CollectConstraintShapes( aBoard ) )
74 {
75 const SHAPE_T shapeType = shape->GetShape();
76 double dist = 0;
77
78 if( shapeType == SHAPE_T::SEGMENT )
79 {
80 dist = SEG( shape->GetStart(), shape->GetEnd() ).Distance( aPos );
81 }
82 else if( aAllowCircle && ( shapeType == SHAPE_T::CIRCLE || shapeType == SHAPE_T::ARC ) )
83 {
84 dist = std::abs( ( aPos - shape->GetCenter() ).EuclideanNorm() - shape->GetRadius() );
85 }
86 else if( aAllowCircle && ( shapeType == SHAPE_T::ELLIPSE || shapeType == SHAPE_T::ELLIPSE_ARC ) )
87 {
88 // Radial distance to the outline at the click's polar angle in the ellipse frame.
89 // Not the exact outline distance, but exact on the outline, which is all a snap needs.
90 double a = shape->GetEllipseMajorRadius();
91 double b = shape->GetEllipseMinorRadius();
92 double phi = shape->GetEllipseRotation().AsRadians();
93 VECTOR2D d = VECTOR2D( aPos - shape->GetEllipseCenter() );
94 double lx = d.x * std::cos( phi ) + d.y * std::sin( phi );
95 double ly = -d.x * std::sin( phi ) + d.y * std::cos( phi );
96 double r = std::hypot( lx, ly );
97
98 if( a <= 0 || b <= 0 )
99 continue;
100
101 double theta = std::atan2( ly, lx );
102 double re = a * b / std::hypot( b * std::cos( theta ), a * std::sin( theta ) );
103
104 dist = std::abs( r - re );
105 }
106 else
107 {
108 continue;
109 }
110
111 if( dist <= best )
112 {
113 best = dist;
114 result = shape->m_Uuid;
115 }
116 }
117
118 return result;
119}
120}
121
122
124 PCB_TOOL_BASE( "pcbnew.ConstraintEditor" ),
125 m_selectionTool( nullptr ),
126 m_menu( nullptr )
127{
128}
129
130
132{
133 // The board (and its view) is being torn down or reloaded; drop the overlay so it does not
134 // dangle on a stale view.
135 m_overlay.reset();
136
137 // Reload may reuse a KIID for a new item
138 // Drop cached diagnosis so it never survives stale
139 m_diagnoser.Clear();
140
141 // Per-type values survive an ordinary redraw or canvas switch
142 // Only a model reload clears them
143 if( aReason == MODEL_RELOAD || aReason == SUPERMODEL_RELOAD )
144 {
145 m_lastConstraintValue.clear();
147 }
148
149 // At shutdown the frame/info bar may already be tearing down, so don't touch UI then.
150 if( aReason == SHUTDOWN )
151 return;
152
153 // The overlay is long-lived so hover can reveal constraints at any time; the sticky setting only
154 // chooses whether it always shows everything (ALWAYS) or reveals on hover (HOVER).
155 if( frame() && board() && getView() )
156 {
157 m_overlay = std::make_unique<CONSTRAINT_OVERLAY>( board(), getView() );
158 m_overlay->SetVisibilityMode( frame()->GetPcbNewSettings()->m_Display.m_ShowConstraints
161 }
162
163 refreshDiagnostics(); // marks the diagnosis dirty and re-renders
164}
165
166
168{
169 // A real board selection supersedes a badge selection (and keeps Delete unambiguous). This
170 // does not fire for SelectConstraintAt's own ClearSelection, which leaves the board empty.
171 if( m_selectionTool && !m_selectionTool->GetSelection().Empty() )
172 setSelectedConstraint( nullptr );
173
174 // A canvas selection shows all constraints again.
175 if( m_overlay && m_overlay->SetIsolated( niluuid ) )
176 m_overlay->RefreshSelection();
177
178 return 0;
179}
180
181
183{
184 if( !m_overlay || !board() )
185 return nullptr;
186
187 // Hit-test against the exact positions the badges draw at (same LayoutBadges call), so a click
188 // and a glyph can never drift apart at any zoom.
189 double worldPerPx = CONSTRAINT_OVERLAY::BadgeWorldPerPixel( getView()->GetGAL()->GetWorldScale() );
190 double best = CONSTRAINT_OVERLAY::BadgeHitRadius() * worldPerPx;
191
192 const std::vector<CONSTRAINT_BADGE>& badges = m_overlay->Badges();
193 std::vector<VECTOR2D> layout = CONSTRAINT_OVERLAY::LayoutBadges( badges, worldPerPx );
194 PCB_CONSTRAINT* result = nullptr;
195
196 for( size_t i = 0; i < badges.size(); ++i )
197 {
198 double dist = ( layout[i] - VECTOR2D( aPos ) ).EuclideanNorm();
199
200 if( dist <= best )
201 {
202 best = dist;
203 result = dynamic_cast<PCB_CONSTRAINT*>( board()->ResolveItem( badges[i].constraint, true ) );
204 }
205 }
206
207 return result;
208}
209
210
212{
213 if( !m_overlay )
214 return;
215
216 if( m_overlay->SetSelected( aConstraint ? aConstraint->m_Uuid : niluuid ) )
217 m_overlay->RefreshSelection();
218
219 // Highlight members with a brighter thicker shadow
221
222 if( aConstraint )
223 {
224 if( PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( frame() ) )
225 {
226 if( PANEL_CONSTRAINTS* panel = pcbFrame->GetConstraintsPanel() )
227 panel->SelectConstraint( aConstraint->m_Uuid );
228 }
229 }
230
231 updateConstraintMsgPanel( aConstraint );
232}
233
234
236{
237 if( !frame() )
238 return;
239
240 if( !aConstraint || !board() )
241 {
242 // Restore the default board readout (Pads/Vias/Tracks), unless a board selection owns the
243 // panel now.
244 if( board() && m_selectionTool && m_selectionTool->GetSelection().Empty() )
245 frame()->SetMsgPanel( board() );
246
247 return;
248 }
249
250 std::vector<MSG_PANEL_ITEM> items;
251
252 items.emplace_back( _( "Constraint" ), ConstraintDisplayLabel( *aConstraint, frame()->GetUserUnits() ) );
253
254 wxString members;
255
256 for( const CONSTRAINT_MEMBER& member : aConstraint->GetMembers() )
257 {
258 if( !members.IsEmpty() )
259 members += wxT( ", " );
260
261 members += ConstraintMemberLabel( board()->ResolveItem( member.m_item, true ), member, frame() );
262 }
263
264 items.emplace_back( _( "Items" ), members );
265
267 wxString state = _( "OK" );
268
269 if( alg::contains( diag.errored, aConstraint->m_Uuid ) )
270 state = _( "Error (missing item)" );
271 else if( alg::contains( diag.conflicting, aConstraint->m_Uuid ) )
272 state = _( "Over-constrained" );
273 else if( alg::contains( diag.redundant, aConstraint->m_Uuid ) )
274 state = _( "Redundant" );
275
276 items.emplace_back( _( "State" ), state );
277
278 frame()->SetMsgPanel( items );
279}
280
281
283{
284 PCB_CONSTRAINT* constraint = hitTestBadge( aPos );
285
286 if( !constraint )
287 return false;
288
289 // A constraint is not a board selection; clear it so a following Delete targets the relation.
290 m_selectionTool->ClearSelection();
291 setSelectedConstraint( constraint );
292 return true;
293}
294
295
297{
298 PCB_CONSTRAINT* constraint = hitTestBadge( aPos );
299
300 if( !constraint )
301 return false;
302
303 setSelectedConstraint( constraint );
304 editConstraint( constraint );
305 return true;
306}
307
308
310{
311 bool wasSelected = m_overlay && m_overlay->GetSelected() != niluuid;
312 setSelectedConstraint( nullptr );
313 return wasSelected;
314}
315
316
318{
319 if( IsFootprintEditor() && board() )
320 return board()->GetFirstFootprint();
321
322 return board();
323}
324
325
327{
328 // Called when the model changed, so the cached diagnosis is stale. A bare re-render (hover) uses
329 // renderConstraintViews() directly and keeps the cache.
330 m_diagDirty = true;
331 m_hoverCandidates.reset(); // the constrained-shape set may have changed too
333}
334
335
337{
338 // The overlay tint/badges, the info bar, and the docked list all read the same board-wide
339 // diagnosis; solve it once here and hand the result to each so a model change costs one solve,
340 // not one per view. Refresh the panel only while it is shown so a hidden pane costs nothing.
341 PANEL_CONSTRAINTS* panel = nullptr;
342
343 if( PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( frame() ) )
344 panel = pcbFrame->GetConstraintsPanel();
345
346 bool panelShown = panel && panel->IsShownOnScreen();
347
348 // With a long-lived overlay, only actually solve when something reads the result: ALWAYS mode, an
349 // active hover, or the docked panel. A HOVER-idle canvas costs nothing.
350 bool overlayActive = m_overlay
351 && ( m_overlay->GetVisibilityMode() == OVERLAY_MODE::ALWAYS
352 || m_overlay->GetHoverShape() != niluuid );
353
354 if( !overlayActive && !panelShown )
355 {
356 if( m_overlay )
357 {
358 m_overlay->SetIsolated( niluuid ); // a panel isolation must not linger while hidden
359 m_overlay->Update( {} ); // draw nothing while hidden
360 }
361
362 // Hidden view computes no fresh diagnosis
363 // Drop the shadow sets too rather than leave stale ones under the now hidden items
366
367 return;
368 }
369
371}
372
373
375{
376 if( m_diagDirty )
377 {
378 m_cachedDiag = m_diagnoser.Diagnose( board() );
379 m_diagDirty = false;
380 }
381
382 return m_cachedDiag;
383}
384
385
386const std::vector<PCB_SHAPE*>& CONSTRAINT_EDIT_TOOL::hoverCandidates()
387{
389 return *m_hoverCandidates;
390
391 std::set<KIID> ids;
392 std::vector<PCB_SHAPE*> shapes;
393
394 auto collect =
395 [&]( const CONSTRAINTS& aConstraints )
396 {
397 for( PCB_CONSTRAINT* c : aConstraints )
398 {
399 for( const CONSTRAINT_MEMBER& m : c->GetMembers() )
400 {
401 if( ids.insert( m.m_item ).second )
402 {
403 if( PCB_SHAPE* shape =
404 dynamic_cast<PCB_SHAPE*>( board()->ResolveItem( m.m_item, true ) ) )
405 {
406 shapes.push_back( shape );
407 }
408 }
409 }
410 }
411 };
412
413 if( board() )
414 {
415 collect( board()->Constraints() );
416
417 for( FOOTPRINT* footprint : board()->Footprints() )
418 collect( footprint->Constraints() );
419 }
420
421 m_hoverCandidates = std::move( shapes );
422 return *m_hoverCandidates;
423}
424
425
427{
428 // Cheapest guards first: only HOVER mode with constraints on the board does any work. Waiting
429 // tools (selection, router, move) already saw this motion before the transition loop reached us,
430 // so there is nothing to forward.
431 if( !m_overlay || m_overlay->GetVisibilityMode() != OVERLAY_MODE::HOVER || !board()
432 || !BoardHasConstraints( board() ) )
433 {
434 return 0;
435 }
436
439
440 // Stay sticky while the cursor is still over the current shape or one of its badges, so a badge
441 // does not blink out as the pointer moves off the thin outline toward it.
442 if( m_overlay->GetHoverShape() != niluuid )
443 {
444 if( PCB_SHAPE* shape =
445 dynamic_cast<PCB_SHAPE*>( board()->ResolveItem( m_overlay->GetHoverShape(), true ) ) )
446 {
447 double worldPerPx = CONSTRAINT_OVERLAY::BadgeWorldPerPixel( getView()->GetGAL()->GetWorldScale() );
448 std::vector<VECTOR2D> layout = CONSTRAINT_OVERLAY::LayoutBadges( m_overlay->Badges(), worldPerPx );
449 double badgeTol = CONSTRAINT_OVERLAY::BadgeHitRadius() * worldPerPx;
450
451 bool overBadge = std::ranges::any_of( layout,
452 [&]( const VECTOR2D& aPos )
453 { return ( aPos - VECTOR2D( cursor ) ).EuclideanNorm() <= badgeTol; } );
454
455 if( overBadge || shape->HitTest( cursor, KiROUND( tol ) ) )
456 return 0; // keep the current hover
457 }
458 }
459
460 std::optional<KIID> hit = NearestConstrainedShape( hoverCandidates(), cursor, KiROUND( tol ) );
461
462 // Only the hover filter changed, not the model, so redraw just the overlay from the cached
463 // diagnosis -- the panel (its row selection) and the info bar are left untouched.
464 if( m_overlay->SetHoverShape( hit.value_or( niluuid ) ) )
465 m_overlay->Update( ensureDiagnosis() );
466
467 return 0;
468}
469
470
472{
473 if( !getView() || !getView()->GetPainter() )
474 return nullptr;
475
476 return dynamic_cast<KIGFX::PCB_RENDER_SETTINGS*>( getView()->GetPainter()->GetSettings() );
477}
478
479
481{
483
484 if( !rs )
485 return;
486
487 std::unordered_set<KIID> constrained;
488
489 for( const auto& entry : aDiag.shapeStates )
490 constrained.insert( entry.first );
491
492 // Shadow is a cached layer so re-cache each item whose constrained state changed
493 // Set the new set BEFORE the re-cache so ViewGetLOD reads fresh membership on redraw
494 std::unordered_set<KIID> previous = rs->GetConstrainedItems();
495 rs->SetConstrainedItems( constrained );
496 repaintShadowItems( previous, constrained );
497}
498
499
501{
503
504 if( !rs )
505 return;
506
507 std::unordered_set<KIID> members;
508
509 if( aConstraint )
510 {
511 for( const CONSTRAINT_MEMBER& member : aConstraint->GetMembers() )
512 members.insert( member.m_item );
513 }
514
515 // Re-cache members whose highlight changed so the brighter shadow follows the badge selection
516 // Set the new membership before the re-cache so the redraw reads it
517 std::unordered_set<KIID> previous = rs->GetHighlightedConstraintMembers();
518 rs->SetHighlightedConstraintMembers( members );
519 repaintShadowItems( previous, members );
520}
521
522
523void CONSTRAINT_EDIT_TOOL::repaintShadowItems( const std::unordered_set<KIID>& aOld,
524 const std::unordered_set<KIID>& aNew )
525{
526 if( !board() || !getView() )
527 return;
528
529 // Re-cache only the symmetric difference items rather than the whole board every time
530 // ALL forces the regen a shape first cached unconstrained needs before it has shadow geometry
531 auto repaint = [&]( const KIID& aId )
532 {
533 if( BOARD_ITEM* item = board()->ResolveItem( aId, true ) )
534 getView()->Update( item, KIGFX::ALL );
535 };
536
537 for( const KIID& id : aOld )
538 {
539 if( !aNew.contains( id ) )
540 repaint( id );
541 }
542
543 for( const KIID& id : aNew )
544 {
545 if( !aOld.contains( id ) )
546 repaint( id );
547 }
548}
549
550
552{
553 if( m_overlay )
554 m_overlay->Update( aDiag );
555
556 // The constraint shadow layer draws only for items in this set a constant time gate at draw time
557 updateConstrainedItems( aDiag );
558
559 if( PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( frame() ) )
560 {
561 if( PANEL_CONSTRAINTS* panel = pcbFrame->GetConstraintsPanel(); panel && panel->IsShownOnScreen() )
562 panel->RefreshList( aDiag );
563 }
564}
565
566
568{
569 // Remember the member shapes so their clusters can re-settle once the constraint is gone.
570 std::vector<PCB_SHAPE*> members;
571
572 for( const CONSTRAINT_MEMBER& member : aConstraint->GetMembers() )
573 {
574 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( board()->ResolveItem( member.m_item, true ) ) )
575 members.push_back( shape );
576 }
577
578 BOARD_COMMIT commit( this );
579 commit.Remove( aConstraint );
580 commit.Push( _( "Remove Geometric Constraint" ) );
581
582 SolveAfterMove( members );
583
585}
586
587
589{
590 if( !board() || aId == niluuid )
591 return nullptr;
592
593 return dynamic_cast<PCB_CONSTRAINT*>( board()->ResolveItem( aId, true ) );
594}
595
596
598{
599 if( PCB_CONSTRAINT* constraint = resolveConstraint( aId ) )
600 removeConstraint( constraint );
601}
602
603
605{
606 if( !aConstraint )
607 return;
608
609 BOARD_COMMIT commit( this );
610
611 if( EditConstraintValue( frame(), aConstraint, commit ) )
612 {
613 // The committed value becomes the remembered default for the next creation of this type
614 // matching what the creation dialog stores
615 if( aConstraint->HasValue() )
616 {
617 m_lastConstraintValue[aConstraint->GetConstraintType()] = *aConstraint->GetValue();
618 m_lastConstraintDriving[aConstraint->GetConstraintType()] = aConstraint->IsDriving();
619 }
620
622 }
623}
624
625
627{
628 PCB_CONSTRAINT* constraint = resolveConstraint( aId );
629
630 if( !constraint )
631 return;
632
633 // A valueless relation has nothing to edit, so locate its members on the canvas instead of
634 // ignoring the gesture, matching the modal list's double-click behavior.
635 if( !constraint->HasValue() )
636 {
638 return;
639 }
640
641 editConstraint( constraint );
642}
643
644
645void CONSTRAINT_EDIT_TOOL::HighlightConstraintMembers( const KIID& aId, int aMemberIndex )
646{
647 PCB_CONSTRAINT* constraint = resolveConstraint( aId );
648
649 if( !constraint )
650 return;
651
652 // Selecting the members is a board selection, so drop any badge selection to keep the two
653 // selection models mutually exclusive (Delete then targets the highlighted members).
654 setSelectedConstraint( nullptr );
655 m_selectionTool->ClearSelection();
656
657 const std::vector<CONSTRAINT_MEMBER>& members = constraint->GetMembers();
658
659 // A click on a blank item cell (index past the members) falls back to highlighting all.
660 bool highlightOne = aMemberIndex >= 0 && aMemberIndex < static_cast<int>( members.size() );
661
662 auto selectMember = [&]( const CONSTRAINT_MEMBER& aMember )
663 {
664 if( BOARD_ITEM* item = board()->ResolveItem( aMember.m_item, true ) )
665 m_selectionTool->select( item );
666 };
667
668 if( highlightOne )
669 selectMember( members[aMemberIndex] );
670 else
671 std::ranges::for_each( members, selectMember );
672
673 // Zoom to what we just selected so the affected items fill the view.
674 if( !m_selectionTool->GetSelection().Empty() )
676
677 // Set this after selecting, which clears the isolate.
678 if( m_overlay && m_overlay->SetIsolated( aId ) )
679 m_overlay->RefreshSelection();
680}
681
682
684{
685 if( !board() || !aConstraint || aConstraint->GetMembers().empty() )
686 return false;
687
688 return std::ranges::all_of( aConstraint->GetMembers(),
689 [&]( const CONSTRAINT_MEMBER& aMember )
690 {
691 BOARD_ITEM* item = board()->ResolveItem( aMember.m_item, true );
692 return item && ConstraintItemIsLocked( item );
693 } );
694}
695
696
698{
699 if( !aConstraint || !board() )
700 return false;
701
702 auto scan = [&]( const CONSTRAINTS& aList )
703 {
704 return std::ranges::any_of( aList,
705 [&]( const PCB_CONSTRAINT* aExisting )
706 {
707 return aExisting != aConstraint && ConstraintsAreDuplicate( *aExisting, *aConstraint );
708 } );
709 };
710
711 if( scan( board()->Constraints() ) )
712 return true;
713
714 return board()->GetFirstFootprint() && scan( board()->GetFirstFootprint()->Constraints() );
715}
716
717
719 const std::vector<PCB_CONSTRAINT*>& aAdded )
720{
721 aCommit.Push( _( "Add Geometric Constraint" ) );
722
723 // Solving any one constraint pins its first member and pulls the rest of the cluster into place.
724 if( !aAdded.empty() )
725 solveAddedConstraint( aAdded.front() );
726
728
729 if( aAdded.empty() || !frame() )
730 return;
731
732 // A locked shape is a fixed reference the solver may not move. If every referenced item is
733 // locked there is nothing it can adjust, so the relation is recorded but the geometry cannot
734 // snap; tell the user rather than appearing to silently do nothing.
735 if( allMembersLocked( aAdded.front() ) )
736 {
737 frame()->ShowInfoBarWarning( _( "All items referenced by this constraint are locked, so the constraint cannot "
738 "move any geometry." ),
739 true );
740 return;
741 }
742
743 // In the default configuration (hover overlay idle, panel hidden) nothing else surfaces the
744 // diagnosis, so an unsatisfiable new constraint must be called out here or the add appears to
745 // succeed silently. Mirrors the SolveAfterMove() warning for the same condition.
747
748 for( const PCB_CONSTRAINT* added : aAdded )
749 {
750 if( alg::contains( diag.conflicting, added->m_Uuid ) )
751 {
752 frame()->ShowInfoBarWarning( _( "The new geometric constraint conflicts with existing constraints and "
753 "could not be satisfied." ),
754 true );
755 return;
756 }
757
758 if( alg::contains( diag.errored, added->m_Uuid ) )
759 {
760 frame()->ShowInfoBarWarning( _( "The new geometric constraint could not be applied to the referenced "
761 "items." ),
762 true );
763 return;
764 }
765 }
766}
767
768
770{
771 // With no badge selected, fall through to the normal item-delete path.
772 if( !m_overlay || m_overlay->GetSelected() == niluuid )
773 return false;
774
775 PCB_CONSTRAINT* constraint = resolveConstraint( m_overlay->GetSelected() );
776
777 setSelectedConstraint( nullptr );
778
779 // The selected constraint vanished (undo / panel delete) but the badge selection lingered;
780 // consume the Delete so it does not fall through and remove a hovered board item.
781 if( !constraint )
782 return true;
783
784 removeConstraint( constraint );
785 return true;
786}
787
788
790{
791 // With no badge selected, fall through to the normal properties path.
792 if( !m_overlay || m_overlay->GetSelected() == niluuid )
793 return false;
794
795 PCB_CONSTRAINT* constraint = resolveConstraint( m_overlay->GetSelected() );
796
797 // The selected constraint vanished (undo / panel delete) but the badge selection lingered;
798 // clear it and fall through instead of consuming the key with no effect.
799 if( !constraint )
800 {
801 setSelectedConstraint( nullptr );
802 return false;
803 }
804
805 // editConstraint only opens a dialog for a valued constraint; others just consume the key.
806 editConstraint( constraint );
807 return true;
808}
809
810
812{
813 // This solve runs after the add was pushed because SolveCluster gathers the cluster from
814 // board->Constraints(), and BOARD_COMMIT only makes the constraint live at Push time.
815 // APPEND_UNDO folds the snap into the add so creating a constraint is a single undoable action.
816 BOARD_COMMIT commit( this );
817 std::vector<PCB_SHAPE*> modified;
818
819 ApplyConstraintImmediately( board(), aConstraint, &modified,
820 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
821
822 // Push if the snap moved a shape or re-measured a reference value in this cluster.
823 if( !commit.Empty() )
824 commit.Push( _( "Apply Geometric Constraint" ), APPEND_UNDO );
825}
826
827
828void CONSTRAINT_EDIT_TOOL::SolveAfterMove( const std::vector<PCB_SHAPE*>& aShapes )
829{
830 if( aShapes.empty() || !board() || !BoardHasConstraints( board() ) )
831 return;
832
833 BOARD_COMMIT commit( this );
834 std::vector<PCB_SHAPE*> modified;
835
836 ReSolveShapeClusters( board(), aShapes, &modified,
837 [&]( BOARD_ITEM* aItem )
838 {
839 commit.Modify( aItem );
840 } );
841
842 if( !commit.Empty() )
843 commit.Push( _( "Apply Geometric Constraint" ), APPEND_UNDO );
844
845 DiagnoseAfterMove( aShapes );
846}
847
848
849void CONSTRAINT_EDIT_TOOL::SolveAfterEdit( const std::vector<PCB_SHAPE*>& aShapes )
850{
851 if( aShapes.empty() || !board() || !BoardHasConstraints( board() ) )
852 return;
853
854 BOARD_COMMIT commit( this );
855 std::vector<PCB_SHAPE*> modified;
856
857 ReSolveShapeClustersHoldingEdited( board(), aShapes, &modified,
858 [&]( BOARD_ITEM* aItem )
859 {
860 commit.Modify( aItem );
861 } );
862
863 if( !commit.Empty() )
864 commit.Push( _( "Apply Geometric Constraint" ), APPEND_UNDO );
865
866 DiagnoseAfterMove( aShapes );
867}
868
869
870void CONSTRAINT_EDIT_TOOL::DiagnoseAfterMove( const std::vector<PCB_SHAPE*>& aShapes )
871{
872 if( aShapes.empty() || !board() || !BoardHasConstraints( board() ) )
873 return;
874
875 // Diagnose once and use it for both the views and the warning below, so a transform does not
876 // pay for two board-wide solves. Cache it so a following hover reuses this fresh result.
877 m_cachedDiag = m_diagnoser.Diagnose( board() );
878 m_diagDirty = false;
880 applyDiagnostics( diag );
881
882 // If the edit left a moved shape's constraint unsatisfiable, say so even when the overlay is off.
883 bool overConstrained = std::ranges::any_of( aShapes,
884 [&]( const PCB_SHAPE* aShape )
885 {
886 auto it = diag.shapeStates.find( aShape->m_Uuid );
887 return it != diag.shapeStates.end() && it->second == CONSTRAINT_STATE::OVER_CONSTRAINED;
888 } );
889
890 if( overConstrained && frame() )
891 frame()->ShowInfoBarWarning( _( "A geometric constraint could not be satisfied by this edit." ), true );
892}
893
894
896{
897 if( !m_overlay && board() && getView() )
898 m_overlay = std::make_unique<CONSTRAINT_OVERLAY>( board(), getView() );
899
900 if( m_overlay )
901 {
902 // The action, not a toggle, chooses the mode, so a freshly created overlay can never invert
903 // the clicked label. The overlay object itself stays alive either way.
904 bool always = aEvent.IsAction( &PCB_ACTIONS::showConstraints );
905 m_overlay->SetVisibilityMode( always ? OVERLAY_MODE::ALWAYS : OVERLAY_MODE::HOVER );
906
907 if( !always )
908 m_overlay->SetHoverShape( niluuid ); // hover mode starts hidden until a hover
909
910 // Remember the choice so it persists across board reloads and sessions.
911 if( frame() )
912 frame()->GetPcbNewSettings()->m_Display.m_ShowConstraints = always;
913 }
914
916 return 0;
917}
918
919
921{
922 // In the board editor the constraint list is a dockable pane; the footprint editor (which has
923 // no such pane) falls back to the modal list dialog.
924 if( PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( frame() ) )
925 {
926 pcbFrame->ToggleConstraintsPanel();
927 return 0;
928 }
929
930 auto highlight =
931 [&]( PCB_CONSTRAINT* aConstraint )
932 {
933 m_selectionTool->ClearSelection();
934
935 for( const CONSTRAINT_MEMBER& member : aConstraint->GetMembers() )
936 {
937 if( BOARD_ITEM* item = board()->ResolveItem( member.m_item, true ) )
938 m_selectionTool->select( item );
939 }
940 };
941
942 auto remove = [&]( PCB_CONSTRAINT* aConstraint ) { removeConstraint( aConstraint ); };
943
944 DIALOG_CONSTRAINT_LIST dlg( frame(), board(), highlight, remove );
945 dlg.ShowModal();
946
947 return 0;
948}
949
950
952{
953 m_diagDirty = true; // the model changed, so the cached diagnosis is stale
955
956 aEvent.PassEvent();
957 return 0;
958}
959
960
962{
964
965 if( !m_selectionTool )
966 return false;
967
969
970 static const std::vector<KICAD_T> segmentType = { PCB_SHAPE_LOCATE_SEGMENT_T };
971
972 auto twoSegments = S_C::Count( 2 ) && S_C::OnlyTypes( segmentType );
973 auto oneSegment = S_C::Count( 1 ) && S_C::OnlyTypes( segmentType );
974
975 auto kindOf = []( const EDA_ITEM* aItem ) -> SHAPE_T
976 {
977 if( !aItem || aItem->Type() != PCB_SHAPE_T )
978 return SHAPE_T::UNDEFINED;
979
980 return static_cast<const PCB_SHAPE*>( aItem )->GetShape();
981 };
982
983 // A circle or arc has a radius; a closed or arc ellipse adds to that a centre only.
984 auto isRadial = []( SHAPE_T aShape )
985 {
986 return aShape == SHAPE_T::CIRCLE || aShape == SHAPE_T::ARC;
987 };
988
989 auto isCentered = [isRadial]( SHAPE_T aShape )
990 {
991 return isRadial( aShape ) || aShape == SHAPE_T::ELLIPSE || aShape == SHAPE_T::ELLIPSE_ARC;
992 };
993
994 auto oneRadial = [kindOf, isRadial]( const SELECTION& aSel )
995 {
996 return aSel.Size() == 1 && isRadial( kindOf( aSel[0] ) );
997 };
998
999 auto oneArc = [kindOf]( const SELECTION& aSel )
1000 {
1001 return aSel.Size() == 1 && kindOf( aSel[0] ) == SHAPE_T::ARC;
1002 };
1003
1004 auto twoRadial = [kindOf, isRadial]( const SELECTION& aSel )
1005 {
1006 return aSel.Size() == 2 && isRadial( kindOf( aSel[0] ) ) && isRadial( kindOf( aSel[1] ) );
1007 };
1008
1009 auto twoCentered = [kindOf, isCentered]( const SELECTION& aSel )
1010 {
1011 return aSel.Size() == 2 && isCentered( kindOf( aSel[0] ) ) && isCentered( kindOf( aSel[1] ) );
1012 };
1013
1014 // Tangent joins a line with a curve, or two circles/arcs.
1015 auto tangentPair = [kindOf, isCentered, isRadial]( const SELECTION& aSel )
1016 {
1017 if( aSel.Size() != 2 )
1018 return false;
1019
1020 SHAPE_T a = kindOf( aSel[0] );
1021 SHAPE_T b = kindOf( aSel[1] );
1022
1023 return ( a == SHAPE_T::SEGMENT && isCentered( b ) ) || ( b == SHAPE_T::SEGMENT && isCentered( a ) )
1024 || ( isRadial( a ) && isRadial( b ) );
1025 };
1026
1027 // Only offer Remove when something selected actually carries a constraint.
1028 auto selectionConstrained = [this]( const SELECTION& aSel ) -> bool
1029 {
1030 if( aSel.Empty() || !board() )
1031 return false;
1032
1033 std::set<KIID> ids;
1034
1035 for( EDA_ITEM* item : aSel )
1036 ids.insert( item->m_Uuid );
1037
1038 auto anyMember = [&]( const CONSTRAINTS& aList )
1039 {
1040 return std::ranges::any_of( aList,
1041 [&]( const PCB_CONSTRAINT* c )
1042 {
1043 return std::ranges::any_of( c->GetMembers(),
1044 [&]( const CONSTRAINT_MEMBER& m ) { return ids.contains( m.m_item ); } );
1045 } );
1046 };
1047
1048 if( anyMember( board()->Constraints() ) )
1049 return true;
1050
1052 && anyMember( board()->GetFirstFootprint()->Constraints() );
1053 };
1054
1055 // One "Constraints" submenu holds every constraint command. The add-type items are gated by
1056 // the current selection so only constraints valid for what is selected are offered; the
1057 // manage/show/remove items are always present.
1058 m_menu = new CONDITIONAL_MENU( this );
1059 m_menu->SetIcon( BITMAPS::measurement );
1060 m_menu->SetUntranslatedTitle( _HKI( "Constraints" ) );
1061
1062 // Gate each selection-based add-type by what it needs; the point-anchored families are authored
1063 // by clicking and need no prior selection, so they are absent here and default to ShowAlways.
1064 // The action list itself lives in PCB_ACTIONS::ConstraintAddActions() so the menubar's copy of
1065 // this submenu cannot drift from it.
1066 const std::map<const TOOL_ACTION*, SELECTION_CONDITION> gate = {
1067 { &PCB_ACTIONS::addConstraintParallel, twoSegments },
1069 { &PCB_ACTIONS::addConstraintEqualLength, twoSegments },
1070 { &PCB_ACTIONS::addConstraintCollinear, twoSegments },
1071 { &PCB_ACTIONS::addConstraintAngular, twoSegments },
1072 { &PCB_ACTIONS::addConstraintTangent, tangentPair },
1073 { &PCB_ACTIONS::addConstraintHorizontal, oneSegment },
1074 { &PCB_ACTIONS::addConstraintVertical, oneSegment },
1076 { &PCB_ACTIONS::addConstraintConcentric, twoCentered },
1080 };
1081
1082 bool pointGroupSeparated = false;
1083
1084 for( const TOOL_ACTION* action : PCB_ACTIONS::ConstraintAddActions() )
1085 {
1086 if( auto it = gate.find( action ); it != gate.end() )
1087 {
1088 m_menu->AddItem( *action, it->second );
1089 }
1090 else
1091 {
1092 if( !pointGroupSeparated )
1093 {
1094 m_menu->AddSeparator();
1095 pointGroupSeparated = true;
1096 }
1097
1098 m_menu->AddItem( *action, S_C::ShowAlways );
1099 }
1100 }
1101
1102 // Show while hidden, Hide while shown, so the label always names what the click will do.
1103 // "Shown" is the always-on mode; the hover mode reads as hidden and offers the Show action.
1104 // The ALWAYS/HOVER choice is a tool mode rather than an object visibility, which is why it
1105 // lives here and not in the Appearance panel's Objects tab.
1106 auto overlayShown = [this]( const SELECTION& )
1107 {
1108 return m_overlay && m_overlay->GetVisibilityMode() == OVERLAY_MODE::ALWAYS;
1109 };
1110 auto overlayHidden = [this]( const SELECTION& )
1111 {
1112 return !m_overlay || m_overlay->GetVisibilityMode() == OVERLAY_MODE::HOVER;
1113 };
1114
1115 m_menu->AddSeparator();
1116 m_menu->AddItem( PCB_ACTIONS::removeConstraints, selectionConstrained );
1117 m_menu->AddItem( PCB_ACTIONS::showConstraints, overlayHidden );
1118 m_menu->AddItem( PCB_ACTIONS::hideConstraints, overlayShown );
1120
1121 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
1122 selToolMenu.AddMenu( m_menu, S_C::ShowAlways, 100 );
1123
1124 return true;
1125}
1126
1127
1129{
1131 const PCB_SELECTION& selection = m_selectionTool->GetSelection();
1132
1133 std::vector<BOARD_ITEM*> items;
1134
1135 for( EDA_ITEM* item : selection )
1136 {
1137 if( item->IsBOARD_ITEM() )
1138 items.push_back( static_cast<BOARD_ITEM*>( item ) );
1139 }
1140
1141 std::unique_ptr<PCB_CONSTRAINT> constraint =
1143
1144 // A valid selection builds right away (the context-menu path). Otherwise drop into click-to-pick
1145 // so the toolbar and hotkeys can author a constraint by clicking the items on the canvas.
1146 if( !constraint )
1147 {
1148 // Horizontal and vertical also accept two point anchors so levelling a corner needs no segment
1149 // The linear picker offers both paths
1151 return pickLinearConstraint( type, aEvent );
1152
1153 return pickShapeConstraint( type, aEvent );
1154 }
1155
1156 commitConstraint( std::move( constraint ) );
1157 return 0;
1158}
1159
1160
1161bool CONSTRAINT_EDIT_TOOL::commitConstraint( std::unique_ptr<PCB_CONSTRAINT> aConstraint )
1162{
1163 if( !aConstraint )
1164 return false;
1165
1166 if( isDuplicateConstraint( aConstraint.get() ) )
1167 {
1168 if( frame() )
1169 frame()->ShowInfoBarWarning( _( "An identical geometric constraint already exists." ) );
1170
1171 return false;
1172 }
1173
1174 // For a dimensional constraint, let the user confirm/override the measured value and choose
1175 // driving vs reference (issue #2329 step 7).
1176 if( aConstraint->HasValue() )
1177 {
1178 PCB_CONSTRAINT_TYPE type = aConstraint->GetConstraintType();
1179
1180 // Default to the shape measurement unless the user already set a value for this type this session
1181 // Reuse that so a run of same type constraints keeps one size
1182 double initial = InitialConstraintValue( type, *aConstraint->GetValue(), m_lastConstraintValue );
1183
1184 bool driving = aConstraint->IsDriving();
1185
1186 if( auto it = m_lastConstraintDriving.find( type ); it != m_lastConstraintDriving.end() )
1187 driving = it->second;
1188
1189 DIALOG_CONSTRAINT_VALUE dlg( frame(), type, initial, driving );
1190
1191 if( dlg.ShowModal() != wxID_OK )
1192 return false;
1193
1194 aConstraint->SetValue( dlg.GetConstraintValue() );
1195 aConstraint->SetDriving( dlg.GetDriving() );
1196
1198 m_lastConstraintDriving[type] = dlg.GetDriving();
1199 }
1200
1201 PCB_CONSTRAINT* added = aConstraint.get();
1202
1203 BOARD_COMMIT commit( this );
1204 commit.Add( aConstraint.release() );
1205 finishConstraintCommit( commit, { added } );
1206
1207 return true;
1208}
1209
1210
1212{
1213 int count = 2;
1214 bool allowCircle = false;
1215
1216 switch( aType )
1217 {
1221 count = 1;
1222 allowCircle = false;
1223 break;
1226 count = 1;
1227 allowCircle = true;
1228 break;
1232 count = 2;
1233 allowCircle = true;
1234 break;
1235 default:
1236 count = 2;
1237 allowCircle = false;
1238 break;
1239 }
1240
1241 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
1242
1243 if( !picker )
1244 return 0;
1245
1246 std::vector<KIID> picked;
1247 const double snapTol = pcbIUScale.mmToIU( 1.0 );
1248
1249 Activate();
1250 picker->SetCursor( KICURSOR::BULLSEYE );
1251 picker->SetSnapping( true );
1252 picker->ClearHandlers();
1253
1254 picker->SetClickHandler(
1255 [&]( const VECTOR2D& aPoint ) -> bool
1256 {
1257 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1258 std::optional<KIID> target = nearestOutline( board(), pos, snapTol, allowCircle );
1259
1260 if( !target || alg::contains( picked, *target ) )
1261 return true; // nothing new snapped, keep picking
1262
1263 picked.push_back( *target );
1264
1265 if( static_cast<int>( picked.size() ) < count )
1266 {
1267 // Clear the preview of the just consumed target
1268 // Not advertised until the next motion event re-derives the pick
1269 if( m_overlay )
1270 m_overlay->ClearPickPreview();
1271
1272 return true; // need more
1273 }
1274
1275 std::vector<BOARD_ITEM*> items;
1276
1277 for( const KIID& id : picked )
1278 {
1279 if( BOARD_ITEM* item = board()->ResolveItem( id, true ) )
1280 items.push_back( item );
1281 }
1282
1283 std::unique_ptr<PCB_CONSTRAINT> constraint =
1284 BuildConstraintFromItems( constraintParent(), aType, items );
1285
1286 if( !constraint && frame() )
1287 {
1288 frame()->ShowInfoBarWarning( wxString::Format( _( "Cannot form a %s constraint from those items." ),
1289 ConstraintTypeLabel( aType ) ) );
1290 }
1291
1292 commitConstraint( std::move( constraint ) );
1293 picked.clear();
1294 return true; // stay active so more can be placed, like the draw tools
1295 } );
1296
1297 // Outline the element the next pick would take so the target is clear before clicking
1298 picker->SetMotionHandler(
1299 [&]( const VECTOR2D& aPoint )
1300 {
1301 if( !m_overlay )
1302 return;
1303
1304 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1305 std::optional<KIID> target = nearestOutline( board(), pos, snapTol, allowCircle );
1306
1307 // Mirror the click handler rejection so an already picked shape is not advertised
1308 // as eligible for the remaining picks
1309 if( target && alg::contains( picked, *target ) )
1310 target.reset();
1311
1312 m_overlay->SetPickPreview( target.value_or( niluuid ), true, std::nullopt );
1313 } );
1314
1315 bool done = false;
1316
1317 picker->SetFinalizeHandler(
1318 [&]( const int& )
1319 {
1320 done = true;
1321 } );
1322
1323 m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
1324
1325 while( !done )
1326 {
1327 if( TOOL_EVENT* evt = Wait() )
1328 evt->SetPassEvent();
1329 else
1330 break;
1331 }
1332
1333 picker->ClearHandlers();
1334
1335 if( m_overlay )
1336 m_overlay->ClearPickPreview();
1337
1338 return 0;
1339}
1340
1341
1343{
1344 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
1345
1346 if( !picker )
1347 return 0;
1348
1349 // Empty until the first pick is a point then holds that point while the second is chosen
1350 std::vector<CONSTRAINT_MEMBER> members;
1351 const double snapTol = pcbIUScale.mmToIU( 1.0 );
1352
1353 Activate();
1354 picker->SetCursor( KICURSOR::BULLSEYE );
1355 picker->SetSnapping( true );
1356 picker->ClearHandlers();
1357
1358 auto commitPointPair = [&]()
1359 {
1360 std::unique_ptr<PCB_CONSTRAINT> constraint =
1361 std::make_unique<PCB_CONSTRAINT>( constraintParent(), aType );
1362
1363 for( const CONSTRAINT_MEMBER& member : members )
1364 constraint->AddMember( member.m_item, member.m_anchor, member.m_index );
1365
1366 commitConstraint( std::move( constraint ) );
1367 };
1368
1369 picker->SetClickHandler(
1370 [&]( const VECTOR2D& aPoint ) -> bool
1371 {
1372 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1373
1374 // A point under the cursor wins over its segment so clicking a corner starts the two point path
1375 // A segment middle has no anchor and takes the whole segment path
1376 std::optional<CONSTRAINT_MEMBER> anchor =
1377 NearestConstraintAnchor( board(), pos, snapTol, members );
1378
1379 if( anchor )
1380 {
1381 members.push_back( *anchor );
1382
1383 if( members.size() < 2 )
1384 {
1385 if( m_overlay )
1386 m_overlay->ClearPickPreview();
1387
1388 return true; // need the second point
1389 }
1390
1391 commitPointPair();
1392 members.clear();
1393 return true; // stay active so more can be placed, like the draw tools
1394 }
1395
1396 // No anchor snapped before the first point a whole segment authors immediately
1397 // Once a point is held only a second point completes the pair
1398 if( members.empty() )
1399 {
1400 if( std::optional<KIID> target = nearestOutline( board(), pos, snapTol, false ) )
1401 {
1402 std::unique_ptr<PCB_CONSTRAINT> constraint =
1403 std::make_unique<PCB_CONSTRAINT>( constraintParent(), aType );
1404 constraint->AddMember( *target, CONSTRAINT_ANCHOR::WHOLE );
1405 commitConstraint( std::move( constraint ) );
1406 }
1407 }
1408
1409 return true;
1410 } );
1411
1412 picker->SetMotionHandler(
1413 [&]( const VECTOR2D& aPoint )
1414 {
1415 if( !m_overlay )
1416 return;
1417
1418 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1419
1420 // Mirror the click handler so the preview shows exactly what the next click takes.
1421 if( std::optional<CONSTRAINT_MEMBER> anchor =
1422 NearestConstraintAnchor( board(), pos, snapTol, members ) )
1423 {
1424 m_overlay->SetPickPreview( anchor->m_item, false,
1426 }
1427 else if( members.empty() )
1428 {
1429 if( std::optional<KIID> target = nearestOutline( board(), pos, snapTol, false ) )
1430 m_overlay->SetPickPreview( *target, true, std::nullopt );
1431 else
1432 m_overlay->ClearPickPreview();
1433 }
1434 else
1435 {
1436 m_overlay->ClearPickPreview();
1437 }
1438 } );
1439
1440 bool done = false;
1441
1442 picker->SetFinalizeHandler(
1443 [&]( const int& )
1444 {
1445 done = true;
1446 } );
1447
1448 m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
1449
1450 while( !done )
1451 {
1452 if( TOOL_EVENT* evt = Wait() )
1453 evt->SetPassEvent();
1454 else
1455 break;
1456 }
1457
1458 picker->ClearHandlers();
1459
1460 if( m_overlay )
1461 m_overlay->ClearPickPreview();
1462
1463 return 0;
1464}
1465
1466
1468{
1470
1471 // In the pick plan, true means click a point anchor and false means click a whole segment.
1472 std::vector<bool> plan;
1473
1474 switch( type )
1475 {
1476 case PCB_CONSTRAINT_TYPE::COINCIDENT: plan = { true, true }; break;
1478 case PCB_CONSTRAINT_TYPE::MIDPOINT: plan = { true, false }; break;
1479 case PCB_CONSTRAINT_TYPE::SYMMETRIC: plan = { true, true, false }; break;
1480 default: return 0;
1481 }
1482
1483 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
1484
1485 if( !picker )
1486 return 0;
1487
1488 std::vector<CONSTRAINT_MEMBER> members;
1489 const double snapTol = pcbIUScale.mmToIU( 1.0 );
1490
1491 Activate();
1492 picker->SetCursor( KICURSOR::BULLSEYE );
1493 picker->SetSnapping( true );
1494 picker->ClearHandlers();
1495
1496 picker->SetClickHandler(
1497 [&]( const VECTOR2D& aPoint ) -> bool
1498 {
1499 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1500
1501 if( plan[members.size()] ) // wants a point anchor
1502 {
1503 // Exclude already picked handles so the same shape and anchor cannot repeat
1504 // while a coincident but distinct endpoint stays reachable
1505 std::optional<CONSTRAINT_MEMBER> anchor =
1506 NearestConstraintAnchor( board(), pos, snapTol, members );
1507
1508 if( !anchor )
1509 return true; // nothing snapped; keep picking
1510
1511 members.push_back( *anchor );
1512 }
1513 else // wants a whole shape
1514 {
1515 std::optional<KIID> target =
1516 nearestOutline( board(), pos, snapTol, type == PCB_CONSTRAINT_TYPE::POINT_ON_LINE );
1517
1518 if( !target )
1519 return true;
1520
1521 members.emplace_back( *target, CONSTRAINT_ANCHOR::WHOLE );
1522 }
1523
1524 if( members.size() < plan.size() )
1525 {
1526 // Clear the preview of the just consumed target
1527 // Not advertised until the next motion event re-derives the pick
1528 if( m_overlay )
1529 m_overlay->ClearPickPreview();
1530
1531 return true; // need more picks
1532 }
1533
1534 std::unique_ptr<PCB_CONSTRAINT> constraint =
1535 std::make_unique<PCB_CONSTRAINT>( constraintParent(), type );
1536
1537 for( const CONSTRAINT_MEMBER& member : members )
1538 constraint->AddMember( member.m_item, member.m_anchor, member.m_index );
1539
1540 if( isDuplicateConstraint( constraint.get() ) )
1541 {
1542 if( frame() )
1543 frame()->ShowInfoBarWarning( _( "An identical geometric constraint already exists." ) );
1544
1545 return false; // done
1546 }
1547
1548 PCB_CONSTRAINT* added = constraint.get();
1549
1550 BOARD_COMMIT commit( this );
1551 commit.Add( constraint.release() );
1552 finishConstraintCommit( commit, { added } );
1553
1554 return false; // done
1555 } );
1556
1557 // Preview the next pick target before the click the point steps also mark the exact anchor
1558 picker->SetMotionHandler(
1559 [&]( const VECTOR2D& aPoint )
1560 {
1561 if( !m_overlay || members.size() >= plan.size() )
1562 return;
1563
1564 VECTOR2I pos( KiROUND( aPoint.x ), KiROUND( aPoint.y ) );
1565
1566 KIID element = niluuid;
1567 bool whole = true;
1568 std::optional<VECTOR2I> anchorPos;
1569
1570 if( plan[members.size()] ) // wants a point anchor
1571 {
1572 if( std::optional<CONSTRAINT_MEMBER> anchor =
1573 NearestConstraintAnchor( board(), pos, snapTol, members ) )
1574 {
1575 element = anchor->m_item;
1576 whole = false;
1577 anchorPos = ConstraintAnchorPosition( board(), *anchor );
1578 }
1579 }
1580 else if( std::optional<KIID> target = nearestOutline( board(), pos, snapTol,
1582 {
1583 element = *target;
1584 }
1585
1586 m_overlay->SetPickPreview( element, whole, anchorPos );
1587 } );
1588
1589 bool done = false;
1590
1591 picker->SetFinalizeHandler(
1592 [&]( const int& aFinalState )
1593 {
1594 done = true;
1595 } );
1596
1597 m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
1598
1599 // RunAction returns before picking ends. Wait so the handlers' captures stay alive.
1600 while( !done )
1601 {
1602 if( TOOL_EVENT* evt = Wait() )
1603 evt->SetPassEvent();
1604 else
1605 break;
1606 }
1607
1608 picker->ClearHandlers();
1609
1610 if( m_overlay )
1611 m_overlay->ClearPickPreview();
1612
1613 return 0;
1614}
1615
1616
1618{
1619 PCB_SELECTION& selection = m_selectionTool->GetSelection();
1620 std::set<KIID> selectedIds;
1621
1622 for( EDA_ITEM* item : selection )
1623 selectedIds.insert( item->m_Uuid );
1624
1625 if( selectedIds.empty() )
1626 return 0;
1627
1628 BOARD_COMMIT commit( this );
1629 bool any = false;
1630
1631 auto removeReferencing =
1632 [&]( const CONSTRAINTS& aConstraints )
1633 {
1634 for( PCB_CONSTRAINT* constraint : aConstraints )
1635 {
1636 bool referenced = std::ranges::any_of( constraint->GetMembers(),
1637 [&]( const CONSTRAINT_MEMBER& aMember )
1638 { return selectedIds.contains( aMember.m_item ); } );
1639
1640 if( referenced )
1641 {
1642 commit.Remove( constraint );
1643 any = true;
1644 }
1645 }
1646 };
1647
1648 if( board() )
1649 {
1650 removeReferencing( board()->Constraints() );
1651
1652 if( IsFootprintEditor() && board()->GetFirstFootprint() )
1653 removeReferencing( board()->GetFirstFootprint()->Constraints() );
1654 }
1655
1656 if( any )
1657 commit.Push( _( "Remove Geometric Constraints" ) );
1658
1660
1661 return 0;
1662}
1663
1664
1666{
1680
1685
1690
1691 // Keep the diagnostics overlay current as the board changes underneath it. Undo/redo posts its
1692 // own event (not TA_MODEL_CHANGE), so listen for it too or a restored/removed constraint's badge
1693 // would not reappear/disappear.
1696
1697 // Show/refresh the endpoint markers as the selection changes.
1701
1702 // No other pcbnew tool registers a plain-motion transition (only one transition runs per mouse
1703 // event), and waiting tools receive motion before this loop, so claiming it here is safe.
1705}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
bool BoardHasConstraints(BOARD *aBoard)
True if the board or any of its footprints carries at least one geometric constraint.
void 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 ...
CONSTRAINT_DIAGNOSIS ApplyConstraintImmediately(BOARD *aBoard, const PCB_CONSTRAINT *aConstraint, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
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.
@ 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
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
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:1913
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...
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:44
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 addConstraintSymmetric
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.
Definition seg.h:38
int Distance(const SEG &aSeg) const
Compute minimum Euclidean distance to segment aSeg.
Definition seg.cpp:698
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
std::vector< PCB_SHAPE * > CollectConstraintShapes(BOARD *aBoard)
Every PCB_SHAPE on the board (drawings plus footprint graphics) – the candidates constraints can refe...
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.
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(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
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
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
#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.
@ 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
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