KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_constraint_drag.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
20#include <algorithm>
21#include <array>
22#include <cmath>
23#include <vector>
24
26#include <tool/tool_manager.h>
28
29#include <board.h>
30#include <board_commit.h>
31#include <pcb_shape.h>
32#include <geometry/shape_arc.h>
36
37
38BOOST_AUTO_TEST_SUITE( ConstraintSolverDrag )
39
40
41namespace
42{
43constexpr int MM = 1000000;
44
45
46SNAP_TARGET_ID targetId( const char* aName )
47{
48 return KIID::FromName( aName ).AsBytes();
49}
50
51
52SNAP_STABLE_ID testSnapId( const char* aName, SNAP_ID_KIND aKind = SNAP_ID_KIND::ITEM_GEOMETRY )
53{
54 return { aKind, targetId( aName ) };
55}
56
57
58struct DRAG_FIXTURE
59{
60 BOARD board;
61 TOOL_MANAGER mgr;
62 KI_TEST::DUMMY_TOOL* tool;
63
64 DRAG_FIXTURE() :
65 tool( new KI_TEST::DUMMY_TOOL() )
66 {
67 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
68 mgr.RegisterTool( tool );
69 }
70
71 PCB_SHAPE* addSegment( const VECTOR2I& aStart, const VECTOR2I& aEnd )
72 {
73 PCB_SHAPE* seg = new PCB_SHAPE( &board, SHAPE_T::SEGMENT );
74 seg->SetStart( aStart );
75 seg->SetEnd( aEnd );
76 board.Add( seg );
77 return seg;
78 }
79
80 PCB_SHAPE* addCircle( const VECTOR2I& aCenter, int aRadius )
81 {
82 PCB_SHAPE* circle = new PCB_SHAPE( &board, SHAPE_T::CIRCLE );
83 circle->SetCenter( aCenter );
84 circle->SetRadius( aRadius );
85 board.Add( circle );
86 return circle;
87 }
88
89 PCB_SHAPE* addArc( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
90 {
91 PCB_SHAPE* arc = new PCB_SHAPE( &board, SHAPE_T::ARC );
92 arc->SetArcGeometry( aStart, aMid, aEnd );
93 board.Add( arc );
94 return arc;
95 }
96
97 PCB_SHAPE* addRect( const VECTOR2I& aStart, const VECTOR2I& aEnd )
98 {
99 PCB_SHAPE* rect = new PCB_SHAPE( &board, SHAPE_T::RECTANGLE );
100 rect->SetStart( aStart );
101 rect->SetEnd( aEnd );
102 board.Add( rect );
103 return rect;
104 }
105
106 PCB_SHAPE* addPoly( const std::vector<VECTOR2I>& aPoints )
107 {
108 PCB_SHAPE* poly = new PCB_SHAPE( &board, SHAPE_T::POLY );
109 poly->SetPolyPoints( aPoints );
110 board.Add( poly );
111 return poly;
112 }
113
114 PCB_CONSTRAINT* addCoincident( PCB_SHAPE* aA, CONSTRAINT_ANCHOR aAnchorA, PCB_SHAPE* aB,
115 CONSTRAINT_ANCHOR aAnchorB )
116 {
117 PCB_CONSTRAINT* c = new PCB_CONSTRAINT( &board, PCB_CONSTRAINT_TYPE::COINCIDENT );
118 c->AddMember( aA->m_Uuid, aAnchorA );
119 c->AddMember( aB->m_Uuid, aAnchorB );
120 board.Add( c );
121 return c;
122 }
123};
124
125
126// Mirrors PCB_POINT_EDITOR updateItem staging dragged shape moving it then rederiving constrained cluster
127// staging each neighbor before it changes
128void simulateDrag( BOARD_COMMIT& aCommit, BOARD* aBoard, PCB_SHAPE* aShape,
129 CONSTRAINT_ANCHOR aAnchor, const VECTOR2I& aCursor,
130 std::vector<PCB_SHAPE*>* aModified )
131{
132 aCommit.Modify( aShape );
133
134 if( aAnchor == CONSTRAINT_ANCHOR::START )
135 aShape->SetStart( aCursor );
136 else if( aAnchor == CONSTRAINT_ANCHOR::END )
137 aShape->SetEnd( aCursor );
138 else if( aAnchor == CONSTRAINT_ANCHOR::CENTER )
139 aShape->SetCenter( aCursor );
140
141 SolveCluster( aBoard, { aShape->m_Uuid, aAnchor }, aCursor, aModified,
142 [&]( BOARD_ITEM* aNeighbor )
143 {
144 aCommit.Modify( aNeighbor );
145 } );
146}
147} // namespace
148
149
150// Dragging one end of a corner re-derives the coincident neighbor; reverting the same commit
151// restores both shapes (one undoable transaction).
152BOOST_FIXTURE_TEST_CASE( DragReDerivesNeighborRevertRestores, DRAG_FIXTURE )
153{
154 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
155 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
156
158 c->AddMember( a->m_Uuid, CONSTRAINT_ANCHOR::END );
159 c->AddMember( b->m_Uuid, CONSTRAINT_ANCHOR::START );
160 board.Add( c );
161
162 const VECTOR2I aEnd0 = a->GetEnd();
163 const VECTOR2I bStart0 = b->GetStart();
164
165 std::vector<PCB_SHAPE*> modified;
166
167 BOARD_COMMIT commit( tool );
168 simulateDrag( commit, &board, a, CONSTRAINT_ANCHOR::END, { 12 * MM, 3 * MM }, &modified );
169
170 // The neighbor followed the dragged corner and is reported as modified.
171 BOOST_CHECK_LE( ( a->GetEnd() - b->GetStart() ).EuclideanNorm(), 100 );
172 BOOST_CHECK( std::find( modified.begin(), modified.end(), b ) != modified.end() );
173 BOOST_CHECK( a->GetEnd() != aEnd0 );
174
175 // Revert restores every shape the transaction touched.
176 commit.Revert();
177 BOOST_CHECK_EQUAL( a->GetEnd(), aEnd0 );
178 BOOST_CHECK_EQUAL( b->GetStart(), bStart0 );
179}
180
181
182BOOST_FIXTURE_TEST_CASE( PersistentDragSessionWarmStartsSuccessivePreviewFrames, DRAG_FIXTURE )
183{
184 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
185 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
186 addCoincident( a, CONSTRAINT_ANCHOR::END, b, CONSTRAINT_ANCHOR::START );
187
190 std::vector<PCB_SHAPE*> modified;
191 BOARD_COMMIT commit( tool );
192 const VECTOR2I originalAEnd = a->GetEnd();
193 const VECTOR2I originalBStart = b->GetStart();
194
195 BOOST_REQUIRE( session.Build( &board, dragged ) );
196 BOOST_CHECK( session.Matches( dragged ) );
197 commit.Modify( a );
198
199 CONSTRAINT_DIAGNOSIS first = session.Solve(
200 { 12 * MM, 2 * MM }, &modified,
201 [&]( BOARD_ITEM* aItem )
202 {
203 commit.Modify( aItem );
204 },
205 false, false );
206 BOOST_REQUIRE( first.solved );
207 BOOST_CHECK_LE( ( a->GetEnd() - b->GetStart() ).EuclideanNorm(), 100 );
208
209 CONSTRAINT_DIAGNOSIS second = session.Solve(
210 { 14 * MM, 4 * MM }, &modified,
211 [&]( BOARD_ITEM* aItem )
212 {
213 commit.Modify( aItem );
214 },
215 false, false );
216 BOOST_REQUIRE( second.solved );
217 BOOST_CHECK_LE( ( a->GetEnd() - b->GetStart() ).EuclideanNorm(), 100 );
218 BOOST_CHECK_LE( ( a->GetEnd() - VECTOR2I( 14 * MM, 4 * MM ) ).EuclideanNorm(), 100 );
219
220 commit.Revert();
221 BOOST_CHECK_EQUAL( a->GetEnd(), originalAEnd );
222 BOOST_CHECK_EQUAL( b->GetStart(), originalBStart );
223}
224
225
226BOOST_FIXTURE_TEST_CASE( PersistentDragSessionRejectsInexactSnapTarget, DRAG_FIXTURE )
227{
228 PCB_SHAPE* seg = addSegment( { 0, 0 }, { 10 * MM, 0 } );
229
231 length->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
232 length->SetValue( 10.0 * MM );
233 board.Add( length );
234
237 BOOST_CHECK( session.IsExactFeasible( { 10 * MM, 0 } ) );
238 BOOST_CHECK( session.IsExactFeasible( { 10 * MM, 0 } ) );
239 BOOST_CHECK( !session.IsExactFeasible( { 20 * MM, 0 } ) );
240 BOOST_CHECK_EQUAL( seg->GetEnd(), VECTOR2I( 10 * MM, 0 ) );
241}
242
243
244BOOST_FIXTURE_TEST_CASE( PersistentDragSessionExactifiesEqualGapTarget, DRAG_FIXTURE )
245{
246 PCB_SHAPE* seg = addSegment( { 0, 0 }, { 10 * MM, 0 } );
247
249 length->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
250 length->SetValue( 10.0 * MM );
251 board.Add( length );
252
254 horizontal->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
255 board.Add( horizontal );
256
259
260 SNAP_SOURCE_CONTEXT context;
261 context.sourcePoint = { 10 * MM, 0 };
262 SNAP_CANDIDATE gap =
266
267 SNAP_RESULT result = session.ResolveCandidates( context, { gap } );
269 BOOST_REQUIRE_EQUAL( result.quantizedResiduals.size(), 1 );
270 BOOST_CHECK_EQUAL( result.quantizedResiduals.front(), 0.0 );
271 BOOST_CHECK_EQUAL( seg->GetEnd(), VECTOR2I( 10 * MM, 0 ) );
272}
273
274
275BOOST_FIXTURE_TEST_CASE( PersistentMoveSessionArbitratesBeforeApplying, DRAG_FIXTURE )
276{
277 PCB_SHAPE* moved = addSegment( { 0, 0 }, { 10 * MM, 0 } );
278 PCB_SHAPE* neighbor = addSegment( { 10 * MM, 0 }, { 20 * MM, 0 } );
279 addCoincident( moved, CONSTRAINT_ANCHOR::END, neighbor, CONSTRAINT_ANCHOR::START );
280
282 BOOST_REQUIRE( session.Build( &board, { moved }, { 0, 0 } ) );
283
284 SNAP_SOURCE_CONTEXT context;
285 context.sourcePoint = { 5 * MM, 0 };
286 SNAP_CANDIDATE target = SNAP_CANDIDATE::Point( testSnapId( "target" ), SNAP_PRIORITY_TIER::OBJECT,
288
289 // A feasibility callback reports the residual and the freedom consumed; SNAP_RESOLVER owns
290 // the accepted list, so honouring a two-degree candidate shows up as zero freedom left.
291 SNAP_RESULT result = session.ResolveCandidates( context, { target } );
293 BOOST_CHECK_EQUAL( result.position, context.sourcePoint );
294 BOOST_REQUIRE_EQUAL( result.quantizedResiduals.size(), 1 );
295 BOOST_CHECK_EQUAL( result.quantizedResiduals.front(), 0.0 );
296 BOOST_CHECK_EQUAL( result.remainingDof, 0 );
297 BOOST_CHECK_EQUAL( moved->GetStart(), VECTOR2I( 0, 0 ) );
298 BOOST_CHECK_EQUAL( neighbor->GetStart(), VECTOR2I( 10 * MM, 0 ) );
299
300 std::vector<PCB_SHAPE*> modified;
301 BOOST_REQUIRE( session.Solve( result.position, &modified, {} ) );
302 BOOST_CHECK_EQUAL( moved->GetStart(), VECTOR2I( 5 * MM, 0 ) );
303 BOOST_CHECK_EQUAL( moved->GetEnd(), VECTOR2I( 15 * MM, 0 ) );
304 BOOST_CHECK_EQUAL( neighbor->GetStart(), VECTOR2I( 15 * MM, 0 ) );
305}
306
307
308BOOST_FIXTURE_TEST_CASE( RigidTranslationPreservesRoundShapeRadii, DRAG_FIXTURE )
309{
310 PCB_SHAPE* circle = addCircle( { 0, 0 }, 4 * MM );
311 PCB_SHAPE* arc = addArc( { 20 * MM, 0 }, { 17 * MM, 3 * MM }, { 14 * MM, 0 } );
312 PCB_SHAPE* ellipse = new PCB_SHAPE( &board, SHAPE_T::ELLIPSE );
313 ellipse->SetEllipseCenter( { 30 * MM, 0 } );
314 ellipse->SetEllipseMajorRadius( 6 * MM );
315 ellipse->SetEllipseMinorRadius( 3 * MM );
316 board.Add( ellipse );
317
318 const int circleRadius = circle->GetRadius();
319 const int arcRadius = arc->GetRadius();
320 const int ellipseMinorRadius = ellipse->GetEllipseMinorRadius();
321
323 BOOST_REQUIRE( adapter.Build( { circle, arc, ellipse }, {} ) );
325 adapter.SolveRigidTranslation( { circle->m_Uuid, arc->m_Uuid, ellipse->m_Uuid }, { 3 * MM, 2 * MM } ) );
326 adapter.Apply();
327
328 BOOST_CHECK_EQUAL( circle->GetRadius(), circleRadius );
329 BOOST_CHECK_EQUAL( arc->GetRadius(), arcRadius );
330 BOOST_CHECK_EQUAL( ellipse->GetEllipseMinorRadius(), ellipseMinorRadius );
331}
332
333
334BOOST_AUTO_TEST_CASE( UnbuiltMoveSessionRejectsCandidates )
335{
337 SNAP_SOURCE_CONTEXT context;
338 context.sourcePoint = { 10, 20 };
339 SNAP_CANDIDATE candidate = SNAP_CANDIDATE::Point( testSnapId( "point" ), SNAP_PRIORITY_TIER::OBJECT,
341
342 SNAP_RESULT result = session.ResolveCandidates( context, { candidate } );
343
344 BOOST_CHECK( result.status == SNAP_RESULT_STATUS::INCOMPATIBLE );
345}
346
347
348BOOST_FIXTURE_TEST_CASE( PersistentMoveSessionRejectsIncompatibleTarget, DRAG_FIXTURE )
349{
350 PCB_SHAPE* moved = addSegment( { 0, 0 }, { 10 * MM, 0 } );
351 PCB_SHAPE* locked = addSegment( { 10 * MM, 0 }, { 20 * MM, 0 } );
352 locked->SetLocked( true );
354
356 BOOST_REQUIRE( session.Build( &board, { moved }, { 0, 0 } ) );
357
358 SNAP_SOURCE_CONTEXT context;
359 context.sourcePoint = { 5 * MM, 0 };
360 SNAP_CANDIDATE target = SNAP_CANDIDATE::Point( testSnapId( "blocked" ), SNAP_PRIORITY_TIER::OBJECT,
362
363 BOOST_CHECK( session.ResolveCandidates( context, { target } ).status == SNAP_RESULT_STATUS::INCOMPATIBLE );
364 BOOST_CHECK_EQUAL( moved->GetStart(), VECTOR2I( 0, 0 ) );
365 BOOST_CHECK_EQUAL( locked->GetStart(), VECTOR2I( 10 * MM, 0 ) );
366}
367
368
369BOOST_FIXTURE_TEST_CASE( PersistentMoveSessionFallsBackAfterRejectedObject, DRAG_FIXTURE )
370{
371 PCB_SHAPE* moved = addSegment( { 0, 0 }, { 10 * MM, 0 } );
372 PCB_SHAPE* locked = addSegment( { 10 * MM, 0 }, { 20 * MM, 0 } );
373 locked->SetLocked( true );
375
376 auto session = std::make_shared<BOARD_CONSTRAINT_MOVE_SESSION>();
377 BOOST_REQUIRE( session->Build( &board, { moved }, { 0, 0 } ) );
378
379 SNAP_SOURCE_CONTEXT context;
380 context.sourcePoint = { 5 * MM, 0 };
381 SNAP_STABLE_ID objectId = testSnapId( "blocked" );
382 SNAP_STABLE_ID gridId = testSnapId( "grid-x", SNAP_ID_KIND::GRID_X );
384 resolver.SetFeasibilityCallback(
385 [session]( const SNAP_SOURCE_CONTEXT& aContext, const std::vector<SNAP_CANDIDATE>& aCandidates )
386 {
387 return session->ResolveCandidates( aContext, aCandidates );
388 } );
391 0.0 ) );
392 resolver.AddCandidate(
394
395 SNAP_RESULT result = resolver.Resolve( context );
397 BOOST_CHECK( !result.Accepted( objectId ) );
398 BOOST_CHECK( result.Accepted( gridId ) );
399 BOOST_CHECK_EQUAL( result.position, VECTOR2I( 0, 0 ) );
400}
401
402
403BOOST_FIXTURE_TEST_CASE( PersistentMoveSessionReportsEachAcceptedSnapOnce, DRAG_FIXTURE )
404{
405 PCB_SHAPE* moved = addSegment( { 0, 0 }, { 10 * MM, 0 } );
406 PCB_SHAPE* neighbor = addSegment( { 10 * MM, 0 }, { 20 * MM, 0 } );
407 addCoincident( moved, CONSTRAINT_ANCHOR::END, neighbor, CONSTRAINT_ANCHOR::START );
408
409 auto session = std::make_shared<BOARD_CONSTRAINT_MOVE_SESSION>();
410 BOOST_REQUIRE( session->Build( &board, { moved }, { 0, 0 } ) );
411
412 SNAP_SOURCE_CONTEXT context;
413 context.sourcePoint = { 5 * MM, 0 };
414 SNAP_STABLE_ID targetId = testSnapId( "target" );
417 target.guides.push_back( { { 0, 0 }, context.sourcePoint } );
418
420 resolver.SetFeasibilityCallback(
421 [session]( const SNAP_SOURCE_CONTEXT& aContext, const std::vector<SNAP_CANDIDATE>& aCandidates )
422 {
423 return session->ResolveCandidates( aContext, aCandidates );
424 } );
425 resolver.AddCandidate( target );
426
427 // The resolver appends the id and the guides itself. A session that reported them too would
428 // enter the snap in the sticky set twice and draw its guide twice every frame.
429 SNAP_RESULT result = resolver.Resolve( context );
431 BOOST_CHECK_EQUAL( std::count( result.accepted.begin(), result.accepted.end(), targetId ), 1 );
432 BOOST_CHECK_EQUAL( result.guides.size(), 1 );
433}
434
435
436BOOST_FIXTURE_TEST_CASE( PersistentMoveSessionJointlySolvesAuthoredAndObjectManifolds, DRAG_FIXTURE )
437{
438 PCB_SHAPE* moved = addSegment( { 0, 0 }, { 10 * MM, 0 } );
439 PCB_SHAPE* guide = addSegment( { -20 * MM, 0 }, { 20 * MM, 0 } );
440 guide->SetLocked( true );
441
443 onLine->AddMember( moved->m_Uuid, CONSTRAINT_ANCHOR::START );
444 onLine->AddMember( guide->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
445 board.Add( onLine );
446
448 BOOST_REQUIRE( session.Build( &board, { moved }, { 0, 0 } ) );
449
450 SNAP_SOURCE_CONTEXT context;
451 context.sourcePoint = { 5 * MM, 3 * MM };
452 SNAP_CANDIDATE vertical = SNAP_CANDIDATE::Line( testSnapId( "vertical" ), SNAP_PRIORITY_TIER::OBJECT,
454 { 0.0, 40.0 * MM }, 0.0 );
455
456 SNAP_RESULT base = session.ResolveCandidates( context, {} );
458 BOOST_CHECK_EQUAL( base.position, VECTOR2I( 5 * MM, 0 ) );
459
460 SNAP_RESULT result = session.ResolveCandidates( context, { vertical } );
462 BOOST_REQUIRE_EQUAL( result.quantizedResiduals.size(), 1 );
463 BOOST_CHECK_EQUAL( result.quantizedResiduals.front(), 0.0 );
464 BOOST_CHECK_EQUAL( result.remainingDof, 1 );
465 BOOST_CHECK_EQUAL( result.position, VECTOR2I( 5 * MM, 0 ) );
466}
467
468
469BOOST_FIXTURE_TEST_CASE( PersistentMoveSessionJointlySolvesDisconnectedClusters, DRAG_FIXTURE )
470{
471 PCB_SHAPE* first = addSegment( { 0, 0 }, { 10 * MM, 0 } );
472 PCB_SHAPE* firstGuide = addSegment( { -100 * MM, 0 }, { 100 * MM, 0 } );
473 PCB_SHAPE* second = addSegment( { 0, 10 * MM }, { 10 * MM, 10 * MM } );
474 PCB_SHAPE* secondGuide = addSegment( { -100 * MM, 9 * MM }, { 100 * MM, 11 * MM } );
475 firstGuide->SetLocked( true );
476 secondGuide->SetLocked( true );
477
479 firstOnLine->AddMember( first->m_Uuid, CONSTRAINT_ANCHOR::START );
480 firstOnLine->AddMember( firstGuide->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
481 board.Add( firstOnLine );
482
484 secondOnLine->AddMember( second->m_Uuid, CONSTRAINT_ANCHOR::START );
485 secondOnLine->AddMember( secondGuide->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
486 board.Add( secondOnLine );
487
489 BOOST_REQUIRE( session.Build( &board, { first, second }, { 0, 0 } ) );
490
491 SNAP_SOURCE_CONTEXT context;
492 context.sourcePoint = { 20 * MM, 5 * MM };
493 SNAP_RESULT result = session.ResolveCandidates( context, {} );
494
496 BOOST_CHECK_EQUAL( result.position, VECTOR2I( 0, 0 ) );
497}
498
499
500BOOST_FIXTURE_TEST_CASE( PersistentDragSessionFindsJointlyFeasibleManifoldPoint, DRAG_FIXTURE )
501{
502 PCB_SHAPE* seg = addSegment( { 0, 0 }, { 10 * MM, 0 } );
503
505 horizontal->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
506 board.Add( horizontal );
507
510
511 SNAP_SOURCE_CONTEXT context;
512 context.sourcePoint = { 6 * MM, 7 * MM };
513 SNAP_CANDIDATE manifold = SNAP_CANDIDATE::Line( testSnapId( "vertical" ), SNAP_PRIORITY_TIER::OBJECT,
515 { 0.0, 40.0 * MM }, 0.0 );
516
517 SNAP_RESULT result = session.ResolveCandidates( context, { manifold } );
518
520 BOOST_CHECK_EQUAL( result.position.x, 6 * MM );
521 BOOST_CHECK_LE( std::abs( result.position.y ), 1000 );
522 BOOST_CHECK_EQUAL( seg->GetEnd(), VECTOR2I( 10 * MM, 0 ) );
523
524 manifold.origin = { -20 * MM, 20 * MM };
525 manifold.direction = { 40.0 * MM, 0.0 };
526 BOOST_CHECK( session.ResolveCandidates( context, { manifold } ).status == SNAP_RESULT_STATUS::INCOMPATIBLE );
527}
528
529
530BOOST_FIXTURE_TEST_CASE( PersistentDragSessionPreservesGeometryOnBaseConflict, DRAG_FIXTURE )
531{
532 PCB_SHAPE* seg = addSegment( { 0, 0 }, { 12 * MM, 0 } );
533
535 length->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
536 length->SetValue( 10.0 * MM );
537 board.Add( length );
538
541
542 seg->SetEnd( { 20 * MM, 0 } );
543 CONSTRAINT_DIAGNOSIS diagnosis = session.Solve( { 20 * MM, 0 }, nullptr, {}, false, false );
544
545 BOOST_CHECK( !diagnosis.solved );
546 BOOST_CHECK_EQUAL( seg->GetEnd(), VECTOR2I( 12 * MM, 0 ) );
547 BOOST_CHECK( !session.IsExactFeasible( { 12 * MM, 0 } ) );
548}
549
550
551// Dragging one circle's centre re-derives a concentric neighbor (centre-anchor drag on a
552// non-segment shape).
553BOOST_FIXTURE_TEST_CASE( DragCircleCentreMovesConcentricNeighbor, DRAG_FIXTURE )
554{
555 PCB_SHAPE* a = addCircle( { 0, 0 }, 5 * MM );
556 PCB_SHAPE* b = addCircle( { 0, 0 }, 8 * MM );
557
559 c->AddMember( a->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
560 c->AddMember( b->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
561 board.Add( c );
562
563 std::vector<PCB_SHAPE*> modified;
564
565 BOARD_COMMIT commit( tool );
566 simulateDrag( commit, &board, a, CONSTRAINT_ANCHOR::CENTER, { 4 * MM, 3 * MM }, &modified );
567
568 // The concentric neighbor's centre followed the dragged centre.
569 BOOST_CHECK_LE( ( a->GetCenter() - b->GetCenter() ).EuclideanNorm(), 2000 );
570 BOOST_CHECK( std::find( modified.begin(), modified.end(), b ) != modified.end() );
571
572 commit.Revert();
573}
574
575
576// When the cluster cannot be solved (here, an unmapped constraint family), neighbors are left
577// untouched -- nothing is half-moved or staged.
578BOOST_FIXTURE_TEST_CASE( UnsolvableClusterLeavesNeighborUntouched, DRAG_FIXTURE )
579{
580 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
581 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
582
583 // Concentric needs circles; given two segments the adapter cannot map it, so Build() leaves it
584 // unenforced and the solve is skipped, leaving the neighbor where it was.
586 c->AddMember( a->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
587 c->AddMember( b->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
588 board.Add( c );
589
590 const VECTOR2I bStart0 = b->GetStart();
591 const VECTOR2I bEnd0 = b->GetEnd();
592
593 std::vector<PCB_SHAPE*> modified;
594
595 BOARD_COMMIT commit( tool );
596 simulateDrag( commit, &board, a, CONSTRAINT_ANCHOR::END, { 12 * MM, 3 * MM }, &modified );
597
598 BOOST_CHECK( modified.empty() );
599 BOOST_CHECK_EQUAL( b->GetStart(), bStart0 );
600 BOOST_CHECK_EQUAL( b->GetEnd(), bEnd0 );
601
602 commit.Revert();
603}
604
605
606// Deleting a referenced shape leaves the constraint in place (in an error state), it is not
607// cascade-deleted (Zulip "Geometry Constraint Solver", 2026-06-18: deleting an object should put
608// the constraint in an error state, not delete it).
609BOOST_FIXTURE_TEST_CASE( DeleteShapeLeavesConstraintInErrorState, DRAG_FIXTURE )
610{
611 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
612 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
613
614 KIID aId = a->m_Uuid;
615
618 c->AddMember( b->m_Uuid, CONSTRAINT_ANCHOR::START );
619 board.Add( c );
620
621 BOOST_REQUIRE_EQUAL( board.Constraints().size(), 1 );
622
623 BOARD_COMMIT commit( tool );
624 commit.Remove( a );
625 commit.Push( wxT( "delete segment" ) );
626
627 // The constraint survives and still holds the now-dangling reference to the deleted shape.
628 BOOST_REQUIRE_EQUAL( board.Constraints().size(), 1 );
629 PCB_CONSTRAINT* survivor = board.Constraints().front();
630 BOOST_REQUIRE_EQUAL( survivor->GetMembers().size(), 2 );
631
632 bool referencesDeleted = false;
633
634 for( const CONSTRAINT_MEMBER& m : survivor->GetMembers() )
635 {
636 if( m.m_item == aId )
637 referencesDeleted = true;
638 }
639
640 BOOST_CHECK( referencesDeleted );
641 BOOST_CHECK( board.ResolveItem( aId, true ) == nullptr ); // the shape is gone
642}
643
644
645// A whole-shape move breaks a point-on-line relation. ReSolveShapeClusters restores it with the
646// moved shape pinned where it was dropped.
647BOOST_FIXTURE_TEST_CASE( MoveReSolvesCluster, DRAG_FIXTURE )
648{
649 PCB_SHAPE* line = addSegment( { 0, 0 }, { 20 * MM, 0 } );
650 PCB_SHAPE* seg = addSegment( { 2 * MM, 5 * MM }, { 5 * MM, 0 } );
651
653 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::END );
654 c->AddMember( line->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
655 board.Add( c );
656
659 board.Add( f1 );
660
663 board.Add( f2 );
664
665 seg->Move( { 0, 3 * MM } );
666 BOOST_CHECK_EQUAL( seg->GetEnd().y, 3 * MM );
667
668 std::vector<PCB_SHAPE*> modified;
669 ReSolveShapeClusters( &board, { seg }, &modified );
670
671 // The end is back on the line, the dropped start held, and the fixed line did not move.
672 BOOST_CHECK_LE( std::abs( seg->GetEnd().y ), 5000 );
673 BOOST_CHECK_LE( ( seg->GetStart() - VECTOR2I( 2 * MM, 8 * MM ) ).EuclideanNorm(), 5000.0 );
674 BOOST_CHECK_EQUAL( line->GetStart(), VECTOR2I( 0, 0 ) );
675 BOOST_CHECK_EQUAL( line->GetEnd(), VECTOR2I( 20 * MM, 0 ) );
676 BOOST_CHECK( std::find( modified.begin(), modified.end(), seg ) != modified.end() );
677}
678
679
680// The live move drag stages every neighbor it pulls along into the drag commit, so cancelling the
681// move (localCommit.Revert) must restore both the moved shape and its solved neighbor.
682BOOST_FIXTURE_TEST_CASE( MoveReSolveStagesNeighborsRevertRestores, DRAG_FIXTURE )
683{
684 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
685 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
686
687 addCoincident( a, CONSTRAINT_ANCHOR::END, b, CONSTRAINT_ANCHOR::START );
688
689 const VECTOR2I aStart0 = a->GetStart();
690 const VECTOR2I aEnd0 = a->GetEnd();
691 const VECTOR2I bStart0 = b->GetStart();
692
693 // Mirrors drag loop staging moved shape translating it then resolving cluster into same commit
694 // staging each neighbor the solver touches
695 BOARD_COMMIT commit( tool );
696 std::vector<PCB_SHAPE*> modified;
697
698 commit.Modify( a );
699 a->Move( { 0, 4 * MM } );
700
701 ReSolveShapeClusters( &board, { a }, &modified,
702 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
703
704 // The neighbor followed the moved corner.
705 BOOST_CHECK_LE( ( a->GetEnd() - b->GetStart() ).EuclideanNorm(), 100 );
706 BOOST_CHECK( b->GetStart() != bStart0 );
707
708 // Cancelling the move restores every shape the drag commit staged.
709 commit.Revert();
710 BOOST_CHECK_EQUAL( a->GetStart(), aStart0 );
711 BOOST_CHECK_EQUAL( a->GetEnd(), aEnd0 );
712 BOOST_CHECK_EQUAL( b->GetStart(), bStart0 );
713}
714
715
716// Mouse-up pushes the last painted preview without another solve. The pushed transaction must
717// therefore preserve every coordinate that was visible in the final preview frame.
718BOOST_FIXTURE_TEST_CASE( MoveCommitEqualsLastPaintedPreview, DRAG_FIXTURE )
719{
720 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
721 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
722
723 addCoincident( a, CONSTRAINT_ANCHOR::END, b, CONSTRAINT_ANCHOR::START );
724
725 BOARD_COMMIT commit( tool );
726 std::vector<PCB_SHAPE*> modified;
727
728 commit.Modify( a );
729 a->Move( { 2 * MM, 4 * MM } );
730
731 ReSolveShapeClusters( &board, { a }, &modified,
732 [&]( BOARD_ITEM* aItem )
733 {
734 commit.Modify( aItem );
735 } );
736
737 const std::array<VECTOR2I, 4> painted = { a->GetStart(), a->GetEnd(), b->GetStart(), b->GetEnd() };
738
739 commit.Push( wxT( "Move" ) );
740
741 const std::array<VECTOR2I, 4> committed = { a->GetStart(), a->GetEnd(), b->GetStart(), b->GetEnd() };
742
743 BOOST_CHECK_EQUAL_COLLECTIONS( committed.begin(), committed.end(), painted.begin(), painted.end() );
744}
745
746
747// The caller-owned, Pack and Duplicate move paths pass no constraint shapes, so the drag collects
748// none and the settle-solve runs over an empty set. That must stage nothing and touch no neighbor,
749// mirroring the guard that keeps those commits free of constraint side effects.
750BOOST_FIXTURE_TEST_CASE( MoveEmptyShapesStagesNoNeighbor, DRAG_FIXTURE )
751{
752 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
753 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
754
755 addCoincident( a, CONSTRAINT_ANCHOR::END, b, CONSTRAINT_ANCHOR::START );
756
757 const VECTOR2I bStart0 = b->GetStart();
758
759 BOARD_COMMIT commit( tool );
760 std::vector<PCB_SHAPE*> modified;
761 int staged = 0;
762
763 ReSolveShapeClusters( &board, {}, &modified,
764 [&]( BOARD_ITEM* ) { staged++; } );
765
766 BOOST_CHECK_EQUAL( staged, 0 );
767 BOOST_CHECK( modified.empty() );
768 BOOST_CHECK_EQUAL( b->GetStart(), bStart0 );
769 BOOST_CHECK( commit.Empty() );
770}
771
772
773// A fixed-length segment dragged by one endpoint rotates about the other end, which stays put.
774// Without pinning the far end the solver is free to translate the whole segment (Zulip "Constraint
775// solver", 2026-07-10: "we need to pin the other end of the line when moving, not only the length").
776BOOST_FIXTURE_TEST_CASE( FixedLengthDragPinsFarEnd, DRAG_FIXTURE )
777{
778 PCB_SHAPE* seg = addSegment( { 0, 0 }, { 10 * MM, 0 } );
779
781 len->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
782 len->SetValue( 10.0 * MM );
783 board.Add( len );
784
785 const VECTOR2I start0 = seg->GetStart();
786
787 std::vector<PCB_SHAPE*> modified;
788
789 BOARD_COMMIT commit( tool );
790
791 // Drag END to a 6-8-10 point, so the held 10 mm length lands it exactly on the cursor.
792 simulateDrag( commit, &board, seg, CONSTRAINT_ANCHOR::END, { 6 * MM, 8 * MM }, &modified );
793
794 // The far (start) end held where it was, and the length is unchanged.
795 BOOST_CHECK_LE( ( seg->GetStart() - start0 ).EuclideanNorm(), 5000.0 );
796 BOOST_CHECK_LE( std::abs( ( seg->GetEnd() - seg->GetStart() ).EuclideanNorm() - 10.0 * MM ), 5000.0 );
797
798 // The dragged end reached the cursor, which sat on the length circle.
799 BOOST_CHECK_LE( ( seg->GetEnd() - VECTOR2I( 6 * MM, 8 * MM ) ).EuclideanNorm(), 20000.0 );
800
801 commit.Revert();
802}
803
804
805// A fixed-length segment dragged by one end holds the far end even when the cursor is off the length
806// circle. The far end used to drift to split the pin error between the two ends.
807BOOST_FIXTURE_TEST_CASE( FixedLengthDragOffCircleHoldsFarEnd, DRAG_FIXTURE )
808{
809 PCB_SHAPE* seg = addSegment( { 0, 0 }, { 10 * MM, 0 } );
810
812 len->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
813 len->SetValue( 10.0 * MM );
814 board.Add( len );
815
816 const VECTOR2I start0 = seg->GetStart();
817
818 std::vector<PCB_SHAPE*> modified;
819 BOARD_COMMIT commit( tool );
820
821 // Cursor 20 mm out on +x, off the 10 mm circle. Far end holds, dragged end lands at {10 mm, 0}.
822 simulateDrag( commit, &board, seg, CONSTRAINT_ANCHOR::END, { 20 * MM, 0 }, &modified );
823
824 BOOST_CHECK_LE( ( seg->GetStart() - start0 ).EuclideanNorm(), 5000.0 );
825 BOOST_CHECK_LE( std::abs( ( seg->GetEnd() - seg->GetStart() ).EuclideanNorm() - 10.0 * MM ), 5000.0 );
826 BOOST_CHECK_LE( ( seg->GetEnd() - VECTOR2I( 10 * MM, 0 ) ).EuclideanNorm(), 20000.0 );
827
828 commit.Revert();
829}
830
831
832// Dragging one endpoint of a constrained arc holds the circle (centre + radius) and the far
833// endpoint, so only the dragged endpoint sweeps -- the arc does not drift or balloon.
834BOOST_FIXTURE_TEST_CASE( ArcEndpointDragHoldsCircleAndFarEnd, DRAG_FIXTURE )
835{
836 PCB_SHAPE* arc = addArc( { 10 * MM, 0 }, { 7071068, 7071068 }, { 0, 10 * MM } ); // 90 deg, r 10
837 PCB_SHAPE* seg = addSegment( { 0, 10 * MM }, { 5 * MM, 15 * MM } );
838 addCoincident( arc, CONSTRAINT_ANCHOR::END, seg, CONSTRAINT_ANCHOR::START );
839
840 const VECTOR2I center0 = arc->GetCenter();
841 const int radius0 = arc->GetRadius();
842 const VECTOR2I end0 = arc->GetEnd();
843
844 // Even an off-circle target is projected onto the held circle inside the adapter, so the centre,
845 // radius and far end stay put and only the dragged endpoint sweeps.
846 std::vector<PCB_SHAPE*> modified;
847 BOARD_COMMIT commit( tool );
848 SolveCluster( &board, { arc->m_Uuid, CONSTRAINT_ANCHOR::START }, { 12 * MM, 3 * MM }, &modified,
849 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
850
851 BOOST_CHECK_LE( ( arc->GetCenter() - center0 ).EuclideanNorm(), 20000.0 );
852 BOOST_CHECK_LE( std::abs( arc->GetRadius() - radius0 ), 20000 );
853 BOOST_CHECK_LE( ( arc->GetEnd() - end0 ).EuclideanNorm(), 20000.0 );
854 BOOST_CHECK( ( arc->GetStart() - VECTOR2I( 10 * MM, 0 ) ).EuclideanNorm() > 20000.0 );
855}
856
857
858// Real FIXED_RADIUS on arc overrides temporary radius hold
859// Dragging endpoint keeps driven radius so far end moves off old spot to stay on the now larger circle
860BOOST_FIXTURE_TEST_CASE( ArcEndpointDragYieldsToFixedRadius, DRAG_FIXTURE )
861{
862 PCB_SHAPE* arc = addArc( { 10 * MM, 0 }, { 7071068, 7071068 }, { 0, 10 * MM } ); // r 10
863
865 r->AddMember( arc->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
866 r->SetValue( 12.0 * MM ); // drive the radius larger than the current 10 mm
867 board.Add( r );
868
869 std::vector<PCB_SHAPE*> modified;
870 BOARD_COMMIT commit( tool );
871 SolveCluster( &board, { arc->m_Uuid, CONSTRAINT_ANCHOR::START }, { 12 * MM, 0 }, &modified,
872 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
873
874 // The driving radius wins over the temporary hold.
875 BOOST_CHECK_LE( std::abs( arc->GetRadius() - 12 * MM ), 20000 );
876}
877
878
879// Dragging a rectangle corner resizes about the diagonally opposite corner, which
880// pinDraggedShapeRest holds. The rect stores its corners swapped (start is the bottom-right), so a
881// broken canonical corner-role mapping would drive or hold the wrong corner and fail both checks.
882BOOST_FIXTURE_TEST_CASE( RectCornerDragHoldsOppositeCorner, DRAG_FIXTURE )
883{
884 PCB_SHAPE* rect = addRect( { 10 * MM, 10 * MM }, { 0, 0 } ); // swapped storage
885 PCB_SHAPE* seg = addSegment( { 10 * MM, 0 }, { 15 * MM, -5 * MM } );
886
887 // Tie the dragged corner (canonical index 1, the corner at (10mm, 0)) to a segment end so the
888 // cluster contains a mappable constraint and the solve runs.
890 c->AddMember( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 );
891 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
892 board.Add( c );
893
894 const VECTOR2I cursor( 12 * MM, -2 * MM );
895
896 std::vector<PCB_SHAPE*> modified;
897 BOARD_COMMIT commit( tool );
898 SolveCluster( &board, { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 }, cursor, &modified,
899 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
900
901 VECTOR2I tl( std::min( rect->GetStart().x, rect->GetEnd().x ),
902 std::min( rect->GetStart().y, rect->GetEnd().y ) );
903 VECTOR2I br( std::max( rect->GetStart().x, rect->GetEnd().x ),
904 std::max( rect->GetStart().y, rect->GetEnd().y ) );
905
906 // The dragged top-right corner landed on the cursor and pulled the coincident segment along.
907 BOOST_CHECK_LE( ( VECTOR2I( br.x, tl.y ) - cursor ).EuclideanNorm(), 5000.0 );
908 BOOST_CHECK_LE( ( seg->GetStart() - cursor ).EuclideanNorm(), 5000.0 );
909
910 // The opposite (bottom-left) corner held its position.
911 BOOST_CHECK_LE( ( VECTOR2I( tl.x, br.y ) - VECTOR2I( 0, 10 * MM ) ).EuclideanNorm(), 5000.0 );
912}
913
914
915// Dragging one polygon vertex moves only that vertex; pinDraggedShapeRest holds every other vertex
916// so the rest of the outline does not drift.
917BOOST_FIXTURE_TEST_CASE( PolyVertexDragHoldsOtherVertices, DRAG_FIXTURE )
918{
919 const std::vector<VECTOR2I> points{ { 0, 0 },
920 { 10 * MM, 0 },
921 { 13 * MM, 8 * MM },
922 { 5 * MM, 14 * MM },
923 { -3 * MM, 8 * MM } };
924
925 PCB_SHAPE* poly = addPoly( points );
926 PCB_SHAPE* seg = addSegment( { 0, 0 }, { -5 * MM, -5 * MM } );
927
928 // Tie one vertex to a segment start so the cluster contains a mappable constraint.
930 c->AddMember( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 );
931 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
932 board.Add( c );
933
934 const VECTOR2I cursor( 16 * MM, 9 * MM );
935
936 std::vector<PCB_SHAPE*> modified;
937 BOARD_COMMIT commit( tool );
938 SolveCluster( &board, { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 2 }, cursor, &modified,
939 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
940
941 const SHAPE_LINE_CHAIN& outline = poly->GetPolyShape().COutline( 0 );
942
943 BOOST_REQUIRE_EQUAL( outline.PointCount(), 5 );
944 BOOST_CHECK_LE( ( outline.CPoint( 2 ) - cursor ).EuclideanNorm(), 5000.0 );
945
946 for( int i : { 0, 1, 3, 4 } )
947 BOOST_CHECK_LE( ( outline.CPoint( i ) - points[i] ).EuclideanNorm(), 5000.0 );
948}
949
950
951// The point-editor bridge maps a dragged rectangle corner handle to its canonical min/max corner,
952// independent of which diagonal the shape stores; ordinals past the corners map to nothing, so the
953// centre and radius handles fall through to the authoritative-shape re-solve instead.
954BOOST_FIXTURE_TEST_CASE( VertexForRectCornerMapsCanonicalIndex, DRAG_FIXTURE )
955{
956 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
957 PCB_SHAPE* swapped = addRect( { 10 * MM, 10 * MM }, { 0, 0 } ); // swapped storage
958
959 for( PCB_SHAPE* shape : { rect, swapped } )
960 {
961 const std::vector<VECTOR2I> corners{ { 0, 0 },
962 { 10 * MM, 0 },
963 { 10 * MM, 10 * MM },
964 { 0, 10 * MM } };
965
966 for( size_t i = 0; i < corners.size(); ++i )
967 {
968 std::optional<CONSTRAINT_ANCHOR_POINT> vertex = ConstraintShapeVertex( shape, (int) i );
969
970 BOOST_REQUIRE( vertex.has_value() );
971 BOOST_CHECK( vertex->anchor == CONSTRAINT_ANCHOR::VERTEX );
972 BOOST_CHECK_EQUAL( vertex->index, (int) i );
973 BOOST_CHECK( vertex->pos == corners[i] );
974 }
975 }
976
977 BOOST_CHECK( !ConstraintShapeVertex( rect, 4 ) );
978 BOOST_CHECK( !ConstraintShapeVertex( rect, -1 ) );
979
980 // A segment exposes endpoint anchors, not vertices, so an ordinal on it maps to nothing.
981 PCB_SHAPE* seg = addSegment( { 0, 0 }, { 10 * MM, 0 } );
982 BOOST_CHECK( !ConstraintShapeVertex( seg, 0 ) );
983}
984
985
986// The point-editor bridge maps a dragged polygon vertex handle to its outline ordinal; an ordinal
987// past the outline, or any ordinal on an arc-bearing polygon, maps to nothing.
988BOOST_FIXTURE_TEST_CASE( VertexForPolyMapsOrdinal, DRAG_FIXTURE )
989{
990 const std::vector<VECTOR2I> points{ { 0, 0 },
991 { 10 * MM, 0 },
992 { 13 * MM, 8 * MM },
993 { 5 * MM, 14 * MM },
994 { -3 * MM, 8 * MM } };
995
996 PCB_SHAPE* poly = addPoly( points );
997
998 for( size_t i = 0; i < points.size(); ++i )
999 {
1000 std::optional<CONSTRAINT_ANCHOR_POINT> vertex = ConstraintShapeVertex( poly, (int) i );
1001
1002 BOOST_REQUIRE( vertex.has_value() );
1003 BOOST_CHECK( vertex->anchor == CONSTRAINT_ANCHOR::VERTEX );
1004 BOOST_CHECK_EQUAL( vertex->index, (int) i );
1005 BOOST_CHECK( vertex->pos == points[i] );
1006 }
1007
1008 BOOST_CHECK( !ConstraintShapeVertex( poly, (int) points.size() ) );
1009
1010 PCB_SHAPE* arcPoly = new PCB_SHAPE( &board, SHAPE_T::POLY );
1011
1013 chain.Append( VECTOR2I( 10 * MM, 10 * MM ) );
1014 chain.Append( VECTOR2I( 50 * MM, 10 * MM ) );
1015 chain.Append( SHAPE_ARC( { 50 * MM, 10 * MM }, { 55 * MM, 25 * MM }, { 50 * MM, 40 * MM }, 0 ) );
1016 chain.Append( VECTOR2I( 10 * MM, 40 * MM ) );
1017 chain.SetClosed( true );
1018
1019 arcPoly->GetPolyShape().AddOutline( chain );
1020 board.Add( arcPoly );
1021
1022 BOOST_REQUIRE_GT( arcPoly->GetPolyShape().COutline( 0 ).ArcCount(), 0 );
1023
1024 // The solver never ingests an arc-bearing outline, so its vertices must not map to members.
1025 BOOST_CHECK( !ConstraintShapeVertex( arcPoly, 0 ) );
1026}
1027
1028
1029// Mimics point editor end to end for rectangle corner
1030// Behavior moves corner first bridge maps dragged handle ordinal to canonical corner and solve pulls coincident segment along
1031BOOST_FIXTURE_TEST_CASE( RectCornerDragMapsThenSolves, DRAG_FIXTURE )
1032{
1033 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
1034 PCB_SHAPE* seg = addSegment( { 10 * MM, 0 }, { 15 * MM, -5 * MM } );
1035
1037 c->AddMember( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 );
1038 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
1039 board.Add( c );
1040
1041 // Drag the top-right corner as RECTANGLE_POINT_EDIT_BEHAVIOR::UpdateItem would.
1042 const VECTOR2I cursor( 12 * MM, -2 * MM );
1043 rect->SetTop( cursor.y );
1044 rect->SetRight( cursor.x );
1045
1046 std::optional<CONSTRAINT_ANCHOR_POINT> vertex = ConstraintShapeVertex( rect, 1 );
1047
1048 BOOST_REQUIRE( vertex.has_value() );
1049 BOOST_CHECK( vertex->pos == cursor );
1050
1051 std::vector<PCB_SHAPE*> modified;
1052 BOARD_COMMIT commit( tool );
1053 SolveCluster( &board, { rect->m_Uuid, vertex->anchor, vertex->index }, vertex->pos, &modified,
1054 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
1055
1056 BOOST_CHECK_LE( ( seg->GetStart() - cursor ).EuclideanNorm(), 5000.0 );
1057}
1058
1059
1060// PinEditedCorner clamps a corner drag at minimum size so shape holds clamped corner while edit point keeps raw cursor
1061// Mapping by handle ordinal and solving toward post clamp corner keeps neighbor riding its real position
1062BOOST_FIXTURE_TEST_CASE( ClampedRectCornerDragSolvesToClampedCorner, DRAG_FIXTURE )
1063{
1064 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
1065 PCB_SHAPE* seg = addSegment( { 10 * MM, 0 }, { 15 * MM, -5 * MM } );
1066
1068 c->AddMember( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 );
1069 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
1070 board.Add( c );
1071
1072 // Drag the top-right corner far past the left edge; the behavior clamps x to the minimum
1073 // width while y follows the cursor, so the shape corner and the raw cursor diverge.
1074 const VECTOR2I rawCursor( -5 * MM, -2 * MM );
1075 const int minWidth = 25400; // 1 mil floor applied by PinEditedCorner
1076 const VECTOR2I clamped( minWidth, rawCursor.y );
1077
1078 rect->SetTop( clamped.y );
1079 rect->SetRight( clamped.x );
1080
1081 std::optional<CONSTRAINT_ANCHOR_POINT> vertex = ConstraintShapeVertex( rect, 1 );
1082
1083 BOOST_REQUIRE( vertex.has_value() );
1084 BOOST_CHECK( vertex->pos == clamped );
1085 BOOST_CHECK( vertex->pos != rawCursor );
1086
1087 std::vector<PCB_SHAPE*> modified;
1088 BOARD_COMMIT commit( tool );
1089 SolveCluster( &board, { rect->m_Uuid, vertex->anchor, vertex->index }, vertex->pos, &modified,
1090 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
1091
1092 BOOST_CHECK_LE( ( seg->GetStart() - clamped ).EuclideanNorm(), 5000.0 );
1093}
1094
1095
1096namespace
1097{
1098VECTOR2I dragRectTopLeft( const PCB_SHAPE* aRect )
1099{
1100 return VECTOR2I( std::min( aRect->GetStart().x, aRect->GetEnd().x ),
1101 std::min( aRect->GetStart().y, aRect->GetEnd().y ) );
1102}
1103
1104
1105VECTOR2I dragRectBotRight( const PCB_SHAPE* aRect )
1106{
1107 return VECTOR2I( std::max( aRect->GetStart().x, aRect->GetEnd().x ),
1108 std::max( aRect->GetStart().y, aRect->GetEnd().y ) );
1109}
1110
1111
1112PCB_CONSTRAINT* addDrivingVertexLength( BOARD& aBoard, PCB_SHAPE* aShape, int aIndexA, int aIndexB,
1113 double aLengthIU )
1114{
1116 c->AddMember( aShape->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, aIndexA );
1117 c->AddMember( aShape->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, aIndexB );
1118 c->SetValue( aLengthIU );
1119 c->SetDriving( true );
1120 aBoard.Add( c );
1121 return c;
1122}
1123} // namespace
1124
1125
1126// Driving width on top side must survive adjacent side drag where side handles used to bypass solver and violate length
1127// Pinning side canonical corner routes it through same drag solve as corner drag so hard length wins and rectangle translates
1128BOOST_FIXTURE_TEST_CASE( RectSideDragEnforcesDrivingWidth, DRAG_FIXTURE )
1129{
1130 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
1131
1132 addDrivingVertexLength( board, rect, 0, 1, 10.0 * MM ); // TL-TR width
1133
1134 // Drag the right side 5 mm out, as RECTANGLE_POINT_EDIT_BEHAVIOR::UpdateItem would.
1135 rect->SetRight( 15 * MM );
1136
1137 // Side RECT_RIGHT (1) maps to canonical corner 1 (TR), read back post-move.
1138 std::optional<CONSTRAINT_ANCHOR_POINT> corner = ConstraintShapeVertex( rect, 1 );
1139
1140 BOOST_REQUIRE( corner.has_value() );
1141 BOOST_CHECK( corner->pos == VECTOR2I( 15 * MM, 0 ) );
1142
1143 std::vector<PCB_SHAPE*> modified;
1144 BOARD_COMMIT commit( tool );
1145 SolveCluster( &board, { rect->m_Uuid, corner->anchor, corner->index }, corner->pos, &modified,
1146 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
1147
1148 const VECTOR2I tl = dragRectTopLeft( rect );
1149 const VECTOR2I br = dragRectBotRight( rect );
1150
1151 // The driving width held against the drag and the height stayed untouched.
1152 BOOST_CHECK_LE( std::abs( ( br.x - tl.x ) - 10 * MM ), 20000 );
1153 BOOST_CHECK_LE( std::abs( ( br.y - tl.y ) - 10 * MM ), 20000 );
1154
1155 // Drag was not simply refused rect moved toward cursor strictly between original and requested span
1156 BOOST_CHECK_GT( tl.x, 1 * MM );
1157 BOOST_CHECK_LT( br.x, 14 * MM );
1158
1159 BOOST_TEST_MESSAGE( "side drag under driving width settled TL at " << tl.x << "," << tl.y );
1160
1161 // The four enumerated corners agree with the solved extremes, so the rect stayed axis-aligned.
1162 std::vector<CONSTRAINT_ANCHOR_POINT> corners = ConstraintShapeAnchors( rect );
1163
1164 BOOST_REQUIRE_EQUAL( corners.size(), 4 );
1165 BOOST_CHECK_EQUAL( corners[0].pos, tl );
1166 BOOST_CHECK_EQUAL( corners[2].pos, br );
1167}
1168
1169
1170// Dragging bottom side leaves top side width constraint satisfiable so resize applies exactly
1171// Height follows handle while held top left corner and driven width stay intact
1172BOOST_FIXTURE_TEST_CASE( RectSideDragOffConstrainedAxisResizes, DRAG_FIXTURE )
1173{
1174 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
1175
1176 addDrivingVertexLength( board, rect, 0, 1, 10.0 * MM ); // TL-TR width
1177
1178 // Drag the bottom side 5 mm down; side RECT_BOT (2) maps to canonical corner 2 (BR).
1179 rect->SetBottom( 15 * MM );
1180
1181 std::optional<CONSTRAINT_ANCHOR_POINT> corner = ConstraintShapeVertex( rect, 2 );
1182
1183 BOOST_REQUIRE( corner.has_value() );
1184 BOOST_CHECK( corner->pos == VECTOR2I( 10 * MM, 15 * MM ) );
1185
1186 std::vector<PCB_SHAPE*> modified;
1187 BOARD_COMMIT commit( tool );
1188 SolveCluster( &board, { rect->m_Uuid, corner->anchor, corner->index }, corner->pos, &modified,
1189 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
1190
1191 const VECTOR2I tl = dragRectTopLeft( rect );
1192 const VECTOR2I br = dragRectBotRight( rect );
1193
1194 BOOST_CHECK_LE( tl.EuclideanNorm(), 5000.0 );
1195 BOOST_CHECK_LE( std::abs( ( br.x - tl.x ) - 10 * MM ), 5000 );
1196 BOOST_CHECK_LE( std::abs( ( br.y - tl.y ) - 15 * MM ), 5000 );
1197}
1198
1199
1200// A side drag on a rectangle whose own dimensions are unconstrained resizes exactly as the handle
1201// placed it; the coincident neighbor on an unmoved corner stays put.
1202BOOST_FIXTURE_TEST_CASE( UnconstrainedRectSideDragResizesExactly, DRAG_FIXTURE )
1203{
1204 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
1205 PCB_SHAPE* seg = addSegment( { 0, 0 }, { -5 * MM, -5 * MM } );
1206
1208 c->AddMember( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 );
1209 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
1210 board.Add( c );
1211
1212 rect->SetRight( 15 * MM );
1213
1214 std::optional<CONSTRAINT_ANCHOR_POINT> corner = ConstraintShapeVertex( rect, 1 );
1215
1216 BOOST_REQUIRE( corner.has_value() );
1217
1218 std::vector<PCB_SHAPE*> modified;
1219 BOARD_COMMIT commit( tool );
1220 SolveCluster( &board, { rect->m_Uuid, corner->anchor, corner->index }, corner->pos, &modified,
1221 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
1222
1223 BOOST_CHECK_LE( dragRectTopLeft( rect ).EuclideanNorm(), 5000.0 );
1224 BOOST_CHECK_LE( ( dragRectBotRight( rect ) - VECTOR2I( 15 * MM, 10 * MM ) ).EuclideanNorm(), 5000.0 );
1225 BOOST_CHECK_LE( seg->GetStart().EuclideanNorm(), 5000.0 );
1226}
1227
1228
1229// Driving length on polygon edge must survive a drag of that same edge
1230// Both endpoints ride co dragged pins that yield to hard length so edge lands at driven length and unbound vertices hold
1231BOOST_FIXTURE_TEST_CASE( PolyEdgeDragEnforcesDrivingLength, DRAG_FIXTURE )
1232{
1233 const std::vector<VECTOR2I> points{ { 0, 0 },
1234 { 10 * MM, 0 },
1235 { 13 * MM, 8 * MM },
1236 { 5 * MM, 14 * MM },
1237 { -3 * MM, 8 * MM } };
1238
1239 PCB_SHAPE* poly = addPoly( points );
1240 const double edgeLen = std::hypot( 3.0, 8.0 ) * MM; // the 1-2 edge's current length
1241
1242 addDrivingVertexLength( board, poly, 1, 2, edgeLen );
1243
1244 // Drag the 1-2 edge so both vertices move and its length would stretch.
1245 std::vector<VECTOR2I> dragged = points;
1246 dragged[1] = { 8 * MM, -2 * MM };
1247 dragged[2] = { 16 * MM, 9 * MM };
1248 poly->SetPolyPoints( dragged );
1249
1250 std::vector<PCB_SHAPE*> modified;
1251 BOARD_COMMIT commit( tool );
1252 SolveCluster( &board, { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 }, dragged[1], &modified,
1253 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); },
1254 /* aIncludeDragged */ false, /* aStabilize */ false, {},
1255 std::pair{ CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 2 ), dragged[2] } );
1256
1257 const SHAPE_LINE_CHAIN& outline = poly->GetPolyShape().COutline( 0 );
1258
1259 double solvedLen = ( outline.CPoint( 2 ) - outline.CPoint( 1 ) ).EuclideanNorm();
1260
1261 BOOST_CHECK_LE( std::abs( solvedLen - edgeLen ), 20000.0 );
1262
1263 // Drag was not simply refused edge midpoint moved out of solver noise toward requested midpoint
1264 // Length constrains only vertex separation so midpoint is free to follow landing closer than start
1265 const VECTOR2I midBefore = ( points[1] + points[2] ) / 2;
1266 const VECTOR2I midTarget = ( dragged[1] + dragged[2] ) / 2;
1267 const VECTOR2I midSolved = ( outline.CPoint( 1 ) + outline.CPoint( 2 ) ) / 2;
1268
1269 BOOST_CHECK_LT( ( midSolved - midTarget ).EuclideanNorm(), ( midBefore - midTarget ).EuclideanNorm() );
1270 BOOST_CHECK_GT( ( midSolved - midBefore ).EuclideanNorm(), 250000.0 );
1271
1272 for( int i : { 0, 3, 4 } )
1273 BOOST_CHECK_LE( ( outline.CPoint( i ) - points[i] ).EuclideanNorm(), 5000.0 );
1274}
1275
1276
1277// An unconstrained polygon edge drag lands both vertices exactly where the handle placed them; the
1278// vertex-bound segment follows and the unbound vertices hold.
1279BOOST_FIXTURE_TEST_CASE( UnconstrainedPolyEdgeDragMovesBothVertices, DRAG_FIXTURE )
1280{
1281 const std::vector<VECTOR2I> points{ { 0, 0 },
1282 { 10 * MM, 0 },
1283 { 13 * MM, 8 * MM },
1284 { 5 * MM, 14 * MM },
1285 { -3 * MM, 8 * MM } };
1286
1287 PCB_SHAPE* poly = addPoly( points );
1288 PCB_SHAPE* seg = addSegment( points[1], { 25 * MM, -5 * MM } );
1289
1291 c->AddMember( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 );
1292 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
1293 board.Add( c );
1294
1295 // Translate the 1-2 edge 3 mm right, as the edge handle would.
1296 std::vector<VECTOR2I> dragged = points;
1297 dragged[1] += VECTOR2I( 3 * MM, 0 );
1298 dragged[2] += VECTOR2I( 3 * MM, 0 );
1299 poly->SetPolyPoints( dragged );
1300
1301 std::vector<PCB_SHAPE*> modified;
1302 BOARD_COMMIT commit( tool );
1303 SolveCluster( &board, { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 }, dragged[1], &modified,
1304 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); },
1305 /* aIncludeDragged */ false, /* aStabilize */ false, {},
1306 std::pair{ CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 2 ), dragged[2] } );
1307
1308 const SHAPE_LINE_CHAIN& outline = poly->GetPolyShape().COutline( 0 );
1309
1310 BOOST_CHECK_LE( ( outline.CPoint( 1 ) - dragged[1] ).EuclideanNorm(), 5000.0 );
1311 BOOST_CHECK_LE( ( outline.CPoint( 2 ) - dragged[2] ).EuclideanNorm(), 5000.0 );
1312 BOOST_CHECK_LE( ( seg->GetStart() - dragged[1] ).EuclideanNorm(), 5000.0 );
1313 BOOST_CHECK( std::find( modified.begin(), modified.end(), seg ) != modified.end() );
1314
1315 for( int i : { 0, 3, 4 } )
1316 BOOST_CHECK_LE( ( outline.CPoint( i ) - points[i] ).EuclideanNorm(), 5000.0 );
1317}
1318
1319
1320// Dragging an arc's centre holds both endpoints, so the centre moves and the radius adapts.
1321BOOST_FIXTURE_TEST_CASE( ArcCentreDragHoldsEndpoints, DRAG_FIXTURE )
1322{
1323 PCB_SHAPE* arc = addArc( { 10 * MM, 0 }, { 7071068, 7071068 }, { 0, 10 * MM } );
1324 PCB_SHAPE* seg = addSegment( { 0, 10 * MM }, { 5 * MM, 15 * MM } );
1325 addCoincident( arc, CONSTRAINT_ANCHOR::END, seg, CONSTRAINT_ANCHOR::START );
1326
1327 const VECTOR2I start0 = arc->GetStart();
1328 const VECTOR2I end0 = arc->GetEnd();
1329 const int radius0 = arc->GetRadius();
1330
1331 std::vector<PCB_SHAPE*> modified;
1332 BOARD_COMMIT commit( tool );
1333 SolveCluster( &board, { arc->m_Uuid, CONSTRAINT_ANCHOR::CENTER }, { 1 * MM, 1 * MM }, &modified,
1334 [&]( BOARD_ITEM* aItem )
1335 {
1336 commit.Modify( aItem );
1337 } );
1338
1339 BOOST_CHECK_LE( ( arc->GetStart() - start0 ).EuclideanNorm(), 20000.0 );
1340 BOOST_CHECK_LE( ( arc->GetEnd() - end0 ).EuclideanNorm(), 20000.0 );
1341 BOOST_CHECK( ( arc->GetCenter() - VECTOR2I( 0, 0 ) ).EuclideanNorm() > 100000.0 );
1342 BOOST_CHECK_GT( std::abs( arc->GetRadius() - radius0 ), 100000 );
1343}
1344
1345
CONSTRAINT_DIAGNOSIS SolveCluster(BOARD *aBoard, const CONSTRAINT_MEMBER &aDragged, const VECTOR2I &aCursor, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify, bool aIncludeDragged, bool aStabilize, const std::set< KIID > &aEdited, const std::optional< std::pair< CONSTRAINT_MEMBER, VECTOR2I > > &aCoDragged, const std::set< KIID > &aFixedShapes, bool aHoldDraggedRigid)
Gather the cluster of shapes transitively constrained with the dragged shape, solve with the dragged ...
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.
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
virtual void Revert() override
Revert the commit by restoring the modified items state.
Translates KiCad board geometry to and from the planegcs solver (issue #2329).
bool SolveRigidTranslation(const std::set< KIID > &aEditedShapes, const VECTOR2I &aTranslation)
bool Build(const std::vector< PCB_SHAPE * > &aShapes, const std::vector< PCB_CONSTRAINT * > &aConstraints, const std::set< KIID > *aFixedShapes=nullptr, const std::vector< PCB_DIMENSION_BASE * > &aDimensions={})
Translate a cluster into a planegcs system.
std::vector< PCB_SHAPE * > Apply(const std::function< void(BOARD_ITEM *)> &aBeforeWrite={})
Write the solved coordinates back into the shapes, de-normalized to IU.
bool IsExactFeasible(const VECTOR2I &aTarget)
bool Build(BOARD *aBoard, const CONSTRAINT_MEMBER &aDragged)
CONSTRAINT_DIAGNOSIS Solve(const VECTOR2I &aCursor, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify, bool aIncludeDragged, bool aStabilize, const std::set< KIID > &aEdited={}, const std::optional< std::pair< CONSTRAINT_MEMBER, VECTOR2I > > &aCoDragged=std::nullopt)
SNAP_RESULT ResolveCandidates(const SNAP_SOURCE_CONTEXT &aContext, const std::vector< SNAP_CANDIDATE > &aCandidates)
bool Matches(const CONSTRAINT_MEMBER &aDragged) const
bool Solve(const VECTOR2I &aTarget, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
bool Build(BOARD *aBoard, const std::vector< PCB_SHAPE * > &aEditedShapes, const VECTOR2I &aReference)
SNAP_RESULT ResolveCandidates(const SNAP_SOURCE_CONTEXT &aContext, const std::vector< SNAP_CANDIDATE > &aCandidates)
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:83
void SetLocked(bool aLocked) override
Definition board_item.h:386
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1355
const CONSTRAINTS & Constraints() const
Geometric constraints (#2329) owned by this board.
Definition board.h:465
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
const KIID m_Uuid
Definition eda_item.h:531
void SetCenter(const VECTOR2I &aCenter)
SHAPE_POLY_SET & GetPolyShape()
int GetRadius() const
virtual void SetBottom(int val)
Definition eda_shape.h:277
virtual void SetTop(int val)
Definition eda_shape.h:274
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:240
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:190
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
virtual void SetRight(int val)
Definition eda_shape.h:276
Definition kiid.h:46
std::array< uint8_t, 16 > AsBytes() const
Definition kiid.cpp:276
static KIID FromName(const std::string &aName)
Return a KIID derived from a name, the same name always gives the same KIID.
Definition kiid.cpp:237
A geometric constraint between board items (issue #2329).
const std::vector< CONSTRAINT_MEMBER > & GetMembers() const
void AddMember(const KIID &aItem, CONSTRAINT_ANCHOR aAnchor=CONSTRAINT_ANCHOR::WHOLE, int aIndex=-1)
void SetValue(std::optional< double > aValue)
void SetDriving(bool aDriving)
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:78
void SetEnd(const VECTOR2I &aEnd) override
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
void Move(const VECTOR2I &aMoveVector) override
Move this object.
void SetStart(const VECTOR2I &aStart) override
void SetRadius(int aRadius)
void SetCenter(const VECTOR2I &aCenter)
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
size_t ArcCount() const
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
std::optional< CONSTRAINT_ANCHOR_POINT > ConstraintShapeVertex(const PCB_SHAPE *aShape, int aIndex)
VERTEX anchor at ordinal aIndex of a rectangle or eligible polygon or std::nullopt if the shape has n...
std::vector< CONSTRAINT_ANCHOR_POINT > ConstraintShapeAnchors(const PCB_SHAPE *aShape)
Enumerate a shape constraint anchors with positions segment and arc endpoints arc centre circle centr...
@ ELLIPSE
Definition eda_shape.h:52
@ SEGMENT
Definition eda_shape.h:46
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:47
static FILENAME_RESOLVER * resolver
PCB_SHAPE * addPoly(BOARD &aBoard, const std::vector< VECTOR2I > &aPoints)
PCB_SHAPE * addArc(BOARD &aBoard, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
PCB_SHAPE * addCircle(BOARD &aBoard, const VECTOR2I &aCenter, int aRadius)
PCB_SHAPE * addRect(BOARD &aBoard, const VECTOR2I &aStart, const VECTOR2I &aEnd)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
CONSTRAINT_ANCHOR
Which feature of a referenced board item participates in a constraint.
@ VERTEX
An indexed rectangle corner or polygon outline vertex; pairs with CONSTRAINT_MEMBER::m_index.
@ WHOLE
The item as a whole (a segment as a line, a circle).
@ START
First endpoint of a segment or arc.
@ END
Second endpoint of a segment or arc.
@ CENTER
Center of an arc or circle.
@ CONCENTRIC
Two arcs/circles share a center.
@ FIXED_POSITION
A point is locked at its current location.
@ 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.
@ POINT_ON_LINE
A point lies on a segment's supporting line.
@ FIXED_LENGTH
A segment has a driving length value.
static bool addSegment(VRML_LAYER &model, IDF_SEGMENT *seg, int icont, int iseg)
SNAP_ID_KIND
std::array< uint8_t, 16 > SNAP_TARGET_ID
The outcome of a constraint solve, in plain data so callers need not know planegcs.
bool solved
Solver reached Success or Converged.
One participant in a constraint: a referenced board item plus the feature of that item that participa...
static SNAP_CANDIDATE Point(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, const VECTOR2I &aPoint, double aResidual)
static SNAP_CANDIDATE Line(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, const VECTOR2I &aOrigin, const VECTOR2D &aDirection, double aResidual)
static SNAP_CANDIDATE AxisX(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, int aCoordinate, double aResidual)
std::vector< SNAP_GUIDE > guides
SNAP_RELATION relation
SNAP_RESULT_STATUS status
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_FIXTURE_TEST_CASE(DragReDerivesNeighborRevertRestores, DRAG_FIXTURE)
BOOST_AUTO_TEST_CASE(UnbuiltMoveSessionRejectsCandidates)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
bool moved
static const long long MM
const SHAPE_LINE_CHAIN chain
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683