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 <cmath>
22#include <vector>
23
25#include <tool/tool_manager.h>
27
28#include <board.h>
29#include <board_commit.h>
30#include <pcb_shape.h>
31#include <geometry/shape_arc.h>
35
36
37BOOST_AUTO_TEST_SUITE( ConstraintSolverDrag )
38
39
40namespace
41{
42constexpr int MM = 1000000;
43
44
45struct DRAG_FIXTURE
46{
47 BOARD board;
48 TOOL_MANAGER mgr;
49 KI_TEST::DUMMY_TOOL* tool;
50
51 DRAG_FIXTURE() : tool( new KI_TEST::DUMMY_TOOL() )
52 {
53 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
54 mgr.RegisterTool( tool );
55 }
56
57 PCB_SHAPE* addSegment( const VECTOR2I& aStart, const VECTOR2I& aEnd )
58 {
59 PCB_SHAPE* seg = new PCB_SHAPE( &board, SHAPE_T::SEGMENT );
60 seg->SetStart( aStart );
61 seg->SetEnd( aEnd );
62 board.Add( seg );
63 return seg;
64 }
65
66 PCB_SHAPE* addCircle( const VECTOR2I& aCenter, int aRadius )
67 {
68 PCB_SHAPE* circle = new PCB_SHAPE( &board, SHAPE_T::CIRCLE );
69 circle->SetCenter( aCenter );
70 circle->SetRadius( aRadius );
71 board.Add( circle );
72 return circle;
73 }
74
75 PCB_SHAPE* addArc( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
76 {
77 PCB_SHAPE* arc = new PCB_SHAPE( &board, SHAPE_T::ARC );
78 arc->SetArcGeometry( aStart, aMid, aEnd );
79 board.Add( arc );
80 return arc;
81 }
82
83 PCB_SHAPE* addRect( const VECTOR2I& aStart, const VECTOR2I& aEnd )
84 {
85 PCB_SHAPE* rect = new PCB_SHAPE( &board, SHAPE_T::RECTANGLE );
86 rect->SetStart( aStart );
87 rect->SetEnd( aEnd );
88 board.Add( rect );
89 return rect;
90 }
91
92 PCB_SHAPE* addPoly( const std::vector<VECTOR2I>& aPoints )
93 {
94 PCB_SHAPE* poly = new PCB_SHAPE( &board, SHAPE_T::POLY );
95 poly->SetPolyPoints( aPoints );
96 board.Add( poly );
97 return poly;
98 }
99
100 PCB_CONSTRAINT* addCoincident( PCB_SHAPE* aA, CONSTRAINT_ANCHOR aAnchorA, PCB_SHAPE* aB,
101 CONSTRAINT_ANCHOR aAnchorB )
102 {
103 PCB_CONSTRAINT* c = new PCB_CONSTRAINT( &board, PCB_CONSTRAINT_TYPE::COINCIDENT );
104 c->AddMember( aA->m_Uuid, aAnchorA );
105 c->AddMember( aB->m_Uuid, aAnchorB );
106 board.Add( c );
107 return c;
108 }
109};
110
111
112// Mirrors PCB_POINT_EDITOR updateItem staging dragged shape moving it then rederiving constrained cluster
113// staging each neighbor before it changes
114void simulateDrag( BOARD_COMMIT& aCommit, BOARD* aBoard, PCB_SHAPE* aShape,
115 CONSTRAINT_ANCHOR aAnchor, const VECTOR2I& aCursor,
116 std::vector<PCB_SHAPE*>* aModified )
117{
118 aCommit.Modify( aShape );
119
120 if( aAnchor == CONSTRAINT_ANCHOR::START )
121 aShape->SetStart( aCursor );
122 else if( aAnchor == CONSTRAINT_ANCHOR::END )
123 aShape->SetEnd( aCursor );
124 else if( aAnchor == CONSTRAINT_ANCHOR::CENTER )
125 aShape->SetCenter( aCursor );
126
127 SolveCluster( aBoard, { aShape->m_Uuid, aAnchor }, aCursor, aModified,
128 [&]( BOARD_ITEM* aNeighbor ) { aCommit.Modify( aNeighbor ); } );
129}
130}
131
132
133// Dragging one end of a corner re-derives the coincident neighbor; reverting the same commit
134// restores both shapes (one undoable transaction).
135BOOST_FIXTURE_TEST_CASE( DragReDerivesNeighborRevertRestores, DRAG_FIXTURE )
136{
137 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
138 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
139
141 c->AddMember( a->m_Uuid, CONSTRAINT_ANCHOR::END );
142 c->AddMember( b->m_Uuid, CONSTRAINT_ANCHOR::START );
143 board.Add( c );
144
145 const VECTOR2I aEnd0 = a->GetEnd();
146 const VECTOR2I bStart0 = b->GetStart();
147
148 std::vector<PCB_SHAPE*> modified;
149
150 BOARD_COMMIT commit( tool );
151 simulateDrag( commit, &board, a, CONSTRAINT_ANCHOR::END, { 12 * MM, 3 * MM }, &modified );
152
153 // The neighbor followed the dragged corner and is reported as modified.
154 BOOST_CHECK_LE( ( a->GetEnd() - b->GetStart() ).EuclideanNorm(), 100 );
155 BOOST_CHECK( std::find( modified.begin(), modified.end(), b ) != modified.end() );
156 BOOST_CHECK( a->GetEnd() != aEnd0 );
157
158 // Revert restores every shape the transaction touched.
159 commit.Revert();
160 BOOST_CHECK_EQUAL( a->GetEnd(), aEnd0 );
161 BOOST_CHECK_EQUAL( b->GetStart(), bStart0 );
162}
163
164
165// Dragging one circle's centre re-derives a concentric neighbor (centre-anchor drag on a
166// non-segment shape).
167BOOST_FIXTURE_TEST_CASE( DragCircleCentreMovesConcentricNeighbor, DRAG_FIXTURE )
168{
169 PCB_SHAPE* a = addCircle( { 0, 0 }, 5 * MM );
170 PCB_SHAPE* b = addCircle( { 0, 0 }, 8 * MM );
171
173 c->AddMember( a->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
174 c->AddMember( b->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
175 board.Add( c );
176
177 std::vector<PCB_SHAPE*> modified;
178
179 BOARD_COMMIT commit( tool );
180 simulateDrag( commit, &board, a, CONSTRAINT_ANCHOR::CENTER, { 4 * MM, 3 * MM }, &modified );
181
182 // The concentric neighbor's centre followed the dragged centre.
183 BOOST_CHECK_LE( ( a->GetCenter() - b->GetCenter() ).EuclideanNorm(), 2000 );
184 BOOST_CHECK( std::find( modified.begin(), modified.end(), b ) != modified.end() );
185
186 commit.Revert();
187}
188
189
190// When the cluster cannot be solved (here, an unmapped constraint family), neighbors are left
191// untouched -- nothing is half-moved or staged.
192BOOST_FIXTURE_TEST_CASE( UnsolvableClusterLeavesNeighborUntouched, DRAG_FIXTURE )
193{
194 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
195 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
196
197 // Concentric needs circles; given two segments the adapter cannot map it, so Build() leaves it
198 // unenforced and the solve is skipped, leaving the neighbor where it was.
200 c->AddMember( a->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
201 c->AddMember( b->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
202 board.Add( c );
203
204 const VECTOR2I bStart0 = b->GetStart();
205 const VECTOR2I bEnd0 = b->GetEnd();
206
207 std::vector<PCB_SHAPE*> modified;
208
209 BOARD_COMMIT commit( tool );
210 simulateDrag( commit, &board, a, CONSTRAINT_ANCHOR::END, { 12 * MM, 3 * MM }, &modified );
211
212 BOOST_CHECK( modified.empty() );
213 BOOST_CHECK_EQUAL( b->GetStart(), bStart0 );
214 BOOST_CHECK_EQUAL( b->GetEnd(), bEnd0 );
215
216 commit.Revert();
217}
218
219
220// Deleting a referenced shape leaves the constraint in place (in an error state), it is not
221// cascade-deleted (Zulip "Geometry Constraint Solver", 2026-06-18: deleting an object should put
222// the constraint in an error state, not delete it).
223BOOST_FIXTURE_TEST_CASE( DeleteShapeLeavesConstraintInErrorState, DRAG_FIXTURE )
224{
225 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
226 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
227
228 KIID aId = a->m_Uuid;
229
232 c->AddMember( b->m_Uuid, CONSTRAINT_ANCHOR::START );
233 board.Add( c );
234
235 BOOST_REQUIRE_EQUAL( board.Constraints().size(), 1 );
236
237 BOARD_COMMIT commit( tool );
238 commit.Remove( a );
239 commit.Push( wxT( "delete segment" ) );
240
241 // The constraint survives and still holds the now-dangling reference to the deleted shape.
242 BOOST_REQUIRE_EQUAL( board.Constraints().size(), 1 );
243 PCB_CONSTRAINT* survivor = board.Constraints().front();
244 BOOST_REQUIRE_EQUAL( survivor->GetMembers().size(), 2 );
245
246 bool referencesDeleted = false;
247
248 for( const CONSTRAINT_MEMBER& m : survivor->GetMembers() )
249 {
250 if( m.m_item == aId )
251 referencesDeleted = true;
252 }
253
254 BOOST_CHECK( referencesDeleted );
255 BOOST_CHECK( board.ResolveItem( aId, true ) == nullptr ); // the shape is gone
256}
257
258
259// A whole-shape move breaks a point-on-line relation. ReSolveShapeClusters restores it with the
260// moved shape pinned where it was dropped.
261BOOST_FIXTURE_TEST_CASE( MoveReSolvesCluster, DRAG_FIXTURE )
262{
263 PCB_SHAPE* line = addSegment( { 0, 0 }, { 20 * MM, 0 } );
264 PCB_SHAPE* seg = addSegment( { 2 * MM, 5 * MM }, { 5 * MM, 0 } );
265
267 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::END );
268 c->AddMember( line->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
269 board.Add( c );
270
273 board.Add( f1 );
274
277 board.Add( f2 );
278
279 seg->Move( { 0, 3 * MM } );
280 BOOST_CHECK_EQUAL( seg->GetEnd().y, 3 * MM );
281
282 std::vector<PCB_SHAPE*> modified;
283 ReSolveShapeClusters( &board, { seg }, &modified );
284
285 // The end is back on the line, the dropped start held, and the fixed line did not move.
286 BOOST_CHECK_LE( std::abs( seg->GetEnd().y ), 5000 );
287 BOOST_CHECK_LE( ( seg->GetStart() - VECTOR2I( 2 * MM, 8 * MM ) ).EuclideanNorm(), 5000.0 );
288 BOOST_CHECK_EQUAL( line->GetStart(), VECTOR2I( 0, 0 ) );
289 BOOST_CHECK_EQUAL( line->GetEnd(), VECTOR2I( 20 * MM, 0 ) );
290 BOOST_CHECK( std::find( modified.begin(), modified.end(), seg ) != modified.end() );
291}
292
293
294// The live move drag stages every neighbor it pulls along into the drag commit, so cancelling the
295// move (localCommit.Revert) must restore both the moved shape and its solved neighbor.
296BOOST_FIXTURE_TEST_CASE( MoveReSolveStagesNeighborsRevertRestores, DRAG_FIXTURE )
297{
298 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
299 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
300
301 addCoincident( a, CONSTRAINT_ANCHOR::END, b, CONSTRAINT_ANCHOR::START );
302
303 const VECTOR2I aStart0 = a->GetStart();
304 const VECTOR2I aEnd0 = a->GetEnd();
305 const VECTOR2I bStart0 = b->GetStart();
306
307 // Mirrors drag loop staging moved shape translating it then resolving cluster into same commit
308 // staging each neighbor the solver touches
309 BOARD_COMMIT commit( tool );
310 std::vector<PCB_SHAPE*> modified;
311
312 commit.Modify( a );
313 a->Move( { 0, 4 * MM } );
314
315 ReSolveShapeClusters( &board, { a }, &modified,
316 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
317
318 // The neighbor followed the moved corner.
319 BOOST_CHECK_LE( ( a->GetEnd() - b->GetStart() ).EuclideanNorm(), 100 );
320 BOOST_CHECK( b->GetStart() != bStart0 );
321
322 // Cancelling the move restores every shape the drag commit staged.
323 commit.Revert();
324 BOOST_CHECK_EQUAL( a->GetStart(), aStart0 );
325 BOOST_CHECK_EQUAL( a->GetEnd(), aEnd0 );
326 BOOST_CHECK_EQUAL( b->GetStart(), bStart0 );
327}
328
329
330// The caller-owned, Pack and Duplicate move paths pass no constraint shapes, so the drag collects
331// none and the settle-solve runs over an empty set. That must stage nothing and touch no neighbor,
332// mirroring the guard that keeps those commits free of constraint side effects.
333BOOST_FIXTURE_TEST_CASE( MoveEmptyShapesStagesNoNeighbor, DRAG_FIXTURE )
334{
335 PCB_SHAPE* a = addSegment( { 0, 0 }, { 10 * MM, 0 } );
336 PCB_SHAPE* b = addSegment( { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
337
338 addCoincident( a, CONSTRAINT_ANCHOR::END, b, CONSTRAINT_ANCHOR::START );
339
340 const VECTOR2I bStart0 = b->GetStart();
341
342 BOARD_COMMIT commit( tool );
343 std::vector<PCB_SHAPE*> modified;
344 int staged = 0;
345
346 ReSolveShapeClusters( &board, {}, &modified,
347 [&]( BOARD_ITEM* ) { staged++; } );
348
349 BOOST_CHECK_EQUAL( staged, 0 );
350 BOOST_CHECK( modified.empty() );
351 BOOST_CHECK_EQUAL( b->GetStart(), bStart0 );
352 BOOST_CHECK( commit.Empty() );
353}
354
355
356// A fixed-length segment dragged by one endpoint rotates about the other end, which stays put.
357// Without pinning the far end the solver is free to translate the whole segment (Zulip "Constraint
358// solver", 2026-07-10: "we need to pin the other end of the line when moving, not only the length").
359BOOST_FIXTURE_TEST_CASE( FixedLengthDragPinsFarEnd, DRAG_FIXTURE )
360{
361 PCB_SHAPE* seg = addSegment( { 0, 0 }, { 10 * MM, 0 } );
362
364 len->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
365 len->SetValue( 10.0 * MM );
366 board.Add( len );
367
368 const VECTOR2I start0 = seg->GetStart();
369
370 std::vector<PCB_SHAPE*> modified;
371
372 BOARD_COMMIT commit( tool );
373
374 // Drag END to a 6-8-10 point, so the held 10 mm length lands it exactly on the cursor.
375 simulateDrag( commit, &board, seg, CONSTRAINT_ANCHOR::END, { 6 * MM, 8 * MM }, &modified );
376
377 // The far (start) end held where it was, and the length is unchanged.
378 BOOST_CHECK_LE( ( seg->GetStart() - start0 ).EuclideanNorm(), 5000.0 );
379 BOOST_CHECK_LE( std::abs( ( seg->GetEnd() - seg->GetStart() ).EuclideanNorm() - 10.0 * MM ), 5000.0 );
380
381 // The dragged end reached the cursor, which sat on the length circle.
382 BOOST_CHECK_LE( ( seg->GetEnd() - VECTOR2I( 6 * MM, 8 * MM ) ).EuclideanNorm(), 20000.0 );
383
384 commit.Revert();
385}
386
387
388// A fixed-length segment dragged by one end holds the far end even when the cursor is off the length
389// circle. The far end used to drift to split the pin error between the two ends.
390BOOST_FIXTURE_TEST_CASE( FixedLengthDragOffCircleHoldsFarEnd, DRAG_FIXTURE )
391{
392 PCB_SHAPE* seg = addSegment( { 0, 0 }, { 10 * MM, 0 } );
393
395 len->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
396 len->SetValue( 10.0 * MM );
397 board.Add( len );
398
399 const VECTOR2I start0 = seg->GetStart();
400
401 std::vector<PCB_SHAPE*> modified;
402 BOARD_COMMIT commit( tool );
403
404 // Cursor 20 mm out on +x, off the 10 mm circle. Far end holds, dragged end lands at {10 mm, 0}.
405 simulateDrag( commit, &board, seg, CONSTRAINT_ANCHOR::END, { 20 * MM, 0 }, &modified );
406
407 BOOST_CHECK_LE( ( seg->GetStart() - start0 ).EuclideanNorm(), 5000.0 );
408 BOOST_CHECK_LE( std::abs( ( seg->GetEnd() - seg->GetStart() ).EuclideanNorm() - 10.0 * MM ), 5000.0 );
409 BOOST_CHECK_LE( ( seg->GetEnd() - VECTOR2I( 10 * MM, 0 ) ).EuclideanNorm(), 20000.0 );
410
411 commit.Revert();
412}
413
414
415// Dragging one endpoint of a constrained arc holds the circle (centre + radius) and the far
416// endpoint, so only the dragged endpoint sweeps -- the arc does not drift or balloon.
417BOOST_FIXTURE_TEST_CASE( ArcEndpointDragHoldsCircleAndFarEnd, DRAG_FIXTURE )
418{
419 PCB_SHAPE* arc = addArc( { 10 * MM, 0 }, { 7071068, 7071068 }, { 0, 10 * MM } ); // 90 deg, r 10
420 PCB_SHAPE* seg = addSegment( { 0, 10 * MM }, { 5 * MM, 15 * MM } );
421 addCoincident( arc, CONSTRAINT_ANCHOR::END, seg, CONSTRAINT_ANCHOR::START );
422
423 const VECTOR2I center0 = arc->GetCenter();
424 const int radius0 = arc->GetRadius();
425 const VECTOR2I end0 = arc->GetEnd();
426
427 // Even an off-circle target is projected onto the held circle inside the adapter, so the centre,
428 // radius and far end stay put and only the dragged endpoint sweeps.
429 std::vector<PCB_SHAPE*> modified;
430 BOARD_COMMIT commit( tool );
431 SolveCluster( &board, { arc->m_Uuid, CONSTRAINT_ANCHOR::START }, { 12 * MM, 3 * MM }, &modified,
432 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
433
434 BOOST_CHECK_LE( ( arc->GetCenter() - center0 ).EuclideanNorm(), 20000.0 );
435 BOOST_CHECK_LE( std::abs( arc->GetRadius() - radius0 ), 20000 );
436 BOOST_CHECK_LE( ( arc->GetEnd() - end0 ).EuclideanNorm(), 20000.0 );
437 BOOST_CHECK( ( arc->GetStart() - VECTOR2I( 10 * MM, 0 ) ).EuclideanNorm() > 20000.0 );
438}
439
440
441// Real FIXED_RADIUS on arc overrides temporary radius hold
442// Dragging endpoint keeps driven radius so far end moves off old spot to stay on the now larger circle
443BOOST_FIXTURE_TEST_CASE( ArcEndpointDragYieldsToFixedRadius, DRAG_FIXTURE )
444{
445 PCB_SHAPE* arc = addArc( { 10 * MM, 0 }, { 7071068, 7071068 }, { 0, 10 * MM } ); // r 10
446
448 r->AddMember( arc->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
449 r->SetValue( 12.0 * MM ); // drive the radius larger than the current 10 mm
450 board.Add( r );
451
452 std::vector<PCB_SHAPE*> modified;
453 BOARD_COMMIT commit( tool );
454 SolveCluster( &board, { arc->m_Uuid, CONSTRAINT_ANCHOR::START }, { 12 * MM, 0 }, &modified,
455 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
456
457 // The driving radius wins over the temporary hold.
458 BOOST_CHECK_LE( std::abs( arc->GetRadius() - 12 * MM ), 20000 );
459}
460
461
462// Dragging a rectangle corner resizes about the diagonally opposite corner, which
463// pinDraggedShapeRest holds. The rect stores its corners swapped (start is the bottom-right), so a
464// broken canonical corner-role mapping would drive or hold the wrong corner and fail both checks.
465BOOST_FIXTURE_TEST_CASE( RectCornerDragHoldsOppositeCorner, DRAG_FIXTURE )
466{
467 PCB_SHAPE* rect = addRect( { 10 * MM, 10 * MM }, { 0, 0 } ); // swapped storage
468 PCB_SHAPE* seg = addSegment( { 10 * MM, 0 }, { 15 * MM, -5 * MM } );
469
470 // Tie the dragged corner (canonical index 1, the corner at (10mm, 0)) to a segment end so the
471 // cluster contains a mappable constraint and the solve runs.
473 c->AddMember( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 );
474 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
475 board.Add( c );
476
477 const VECTOR2I cursor( 12 * MM, -2 * MM );
478
479 std::vector<PCB_SHAPE*> modified;
480 BOARD_COMMIT commit( tool );
481 SolveCluster( &board, { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 }, cursor, &modified,
482 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
483
484 VECTOR2I tl( std::min( rect->GetStart().x, rect->GetEnd().x ),
485 std::min( rect->GetStart().y, rect->GetEnd().y ) );
486 VECTOR2I br( std::max( rect->GetStart().x, rect->GetEnd().x ),
487 std::max( rect->GetStart().y, rect->GetEnd().y ) );
488
489 // The dragged top-right corner landed on the cursor and pulled the coincident segment along.
490 BOOST_CHECK_LE( ( VECTOR2I( br.x, tl.y ) - cursor ).EuclideanNorm(), 5000.0 );
491 BOOST_CHECK_LE( ( seg->GetStart() - cursor ).EuclideanNorm(), 5000.0 );
492
493 // The opposite (bottom-left) corner held its position.
494 BOOST_CHECK_LE( ( VECTOR2I( tl.x, br.y ) - VECTOR2I( 0, 10 * MM ) ).EuclideanNorm(), 5000.0 );
495}
496
497
498// Dragging one polygon vertex moves only that vertex; pinDraggedShapeRest holds every other vertex
499// so the rest of the outline does not drift.
500BOOST_FIXTURE_TEST_CASE( PolyVertexDragHoldsOtherVertices, DRAG_FIXTURE )
501{
502 const std::vector<VECTOR2I> points{ { 0, 0 },
503 { 10 * MM, 0 },
504 { 13 * MM, 8 * MM },
505 { 5 * MM, 14 * MM },
506 { -3 * MM, 8 * MM } };
507
508 PCB_SHAPE* poly = addPoly( points );
509 PCB_SHAPE* seg = addSegment( { 0, 0 }, { -5 * MM, -5 * MM } );
510
511 // Tie one vertex to a segment start so the cluster contains a mappable constraint.
513 c->AddMember( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 );
514 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
515 board.Add( c );
516
517 const VECTOR2I cursor( 16 * MM, 9 * MM );
518
519 std::vector<PCB_SHAPE*> modified;
520 BOARD_COMMIT commit( tool );
521 SolveCluster( &board, { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 2 }, cursor, &modified,
522 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
523
524 const SHAPE_LINE_CHAIN& outline = poly->GetPolyShape().COutline( 0 );
525
526 BOOST_REQUIRE_EQUAL( outline.PointCount(), 5 );
527 BOOST_CHECK_LE( ( outline.CPoint( 2 ) - cursor ).EuclideanNorm(), 5000.0 );
528
529 for( int i : { 0, 1, 3, 4 } )
530 BOOST_CHECK_LE( ( outline.CPoint( i ) - points[i] ).EuclideanNorm(), 5000.0 );
531}
532
533
534// The point-editor bridge maps a dragged rectangle corner handle to its canonical min/max corner,
535// independent of which diagonal the shape stores; ordinals past the corners map to nothing, so the
536// centre and radius handles fall through to the authoritative-shape re-solve instead.
537BOOST_FIXTURE_TEST_CASE( VertexForRectCornerMapsCanonicalIndex, DRAG_FIXTURE )
538{
539 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
540 PCB_SHAPE* swapped = addRect( { 10 * MM, 10 * MM }, { 0, 0 } ); // swapped storage
541
542 for( PCB_SHAPE* shape : { rect, swapped } )
543 {
544 const std::vector<VECTOR2I> corners{ { 0, 0 },
545 { 10 * MM, 0 },
546 { 10 * MM, 10 * MM },
547 { 0, 10 * MM } };
548
549 for( size_t i = 0; i < corners.size(); ++i )
550 {
551 std::optional<CONSTRAINT_ANCHOR_POINT> vertex = ConstraintShapeVertex( shape, (int) i );
552
553 BOOST_REQUIRE( vertex.has_value() );
554 BOOST_CHECK( vertex->anchor == CONSTRAINT_ANCHOR::VERTEX );
555 BOOST_CHECK_EQUAL( vertex->index, (int) i );
556 BOOST_CHECK( vertex->pos == corners[i] );
557 }
558 }
559
560 BOOST_CHECK( !ConstraintShapeVertex( rect, 4 ) );
561 BOOST_CHECK( !ConstraintShapeVertex( rect, -1 ) );
562
563 // A segment exposes endpoint anchors, not vertices, so an ordinal on it maps to nothing.
564 PCB_SHAPE* seg = addSegment( { 0, 0 }, { 10 * MM, 0 } );
565 BOOST_CHECK( !ConstraintShapeVertex( seg, 0 ) );
566}
567
568
569// The point-editor bridge maps a dragged polygon vertex handle to its outline ordinal; an ordinal
570// past the outline, or any ordinal on an arc-bearing polygon, maps to nothing.
571BOOST_FIXTURE_TEST_CASE( VertexForPolyMapsOrdinal, DRAG_FIXTURE )
572{
573 const std::vector<VECTOR2I> points{ { 0, 0 },
574 { 10 * MM, 0 },
575 { 13 * MM, 8 * MM },
576 { 5 * MM, 14 * MM },
577 { -3 * MM, 8 * MM } };
578
579 PCB_SHAPE* poly = addPoly( points );
580
581 for( size_t i = 0; i < points.size(); ++i )
582 {
583 std::optional<CONSTRAINT_ANCHOR_POINT> vertex = ConstraintShapeVertex( poly, (int) i );
584
585 BOOST_REQUIRE( vertex.has_value() );
586 BOOST_CHECK( vertex->anchor == CONSTRAINT_ANCHOR::VERTEX );
587 BOOST_CHECK_EQUAL( vertex->index, (int) i );
588 BOOST_CHECK( vertex->pos == points[i] );
589 }
590
591 BOOST_CHECK( !ConstraintShapeVertex( poly, (int) points.size() ) );
592
593 PCB_SHAPE* arcPoly = new PCB_SHAPE( &board, SHAPE_T::POLY );
594
596 chain.Append( VECTOR2I( 10 * MM, 10 * MM ) );
597 chain.Append( VECTOR2I( 50 * MM, 10 * MM ) );
598 chain.Append( SHAPE_ARC( { 50 * MM, 10 * MM }, { 55 * MM, 25 * MM }, { 50 * MM, 40 * MM }, 0 ) );
599 chain.Append( VECTOR2I( 10 * MM, 40 * MM ) );
600 chain.SetClosed( true );
601
602 arcPoly->GetPolyShape().AddOutline( chain );
603 board.Add( arcPoly );
604
605 BOOST_REQUIRE_GT( arcPoly->GetPolyShape().COutline( 0 ).ArcCount(), 0 );
606
607 // The solver never ingests an arc-bearing outline, so its vertices must not map to members.
608 BOOST_CHECK( !ConstraintShapeVertex( arcPoly, 0 ) );
609}
610
611
612// Mimics point editor end to end for rectangle corner
613// Behavior moves corner first bridge maps dragged handle ordinal to canonical corner and solve pulls coincident segment along
614BOOST_FIXTURE_TEST_CASE( RectCornerDragMapsThenSolves, DRAG_FIXTURE )
615{
616 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
617 PCB_SHAPE* seg = addSegment( { 10 * MM, 0 }, { 15 * MM, -5 * MM } );
618
620 c->AddMember( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 );
621 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
622 board.Add( c );
623
624 // Drag the top-right corner as RECTANGLE_POINT_EDIT_BEHAVIOR::UpdateItem would.
625 const VECTOR2I cursor( 12 * MM, -2 * MM );
626 rect->SetTop( cursor.y );
627 rect->SetRight( cursor.x );
628
629 std::optional<CONSTRAINT_ANCHOR_POINT> vertex = ConstraintShapeVertex( rect, 1 );
630
631 BOOST_REQUIRE( vertex.has_value() );
632 BOOST_CHECK( vertex->pos == cursor );
633
634 std::vector<PCB_SHAPE*> modified;
635 BOARD_COMMIT commit( tool );
636 SolveCluster( &board, { rect->m_Uuid, vertex->anchor, vertex->index }, vertex->pos, &modified,
637 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
638
639 BOOST_CHECK_LE( ( seg->GetStart() - cursor ).EuclideanNorm(), 5000.0 );
640}
641
642
643// PinEditedCorner clamps a corner drag at minimum size so shape holds clamped corner while edit point keeps raw cursor
644// Mapping by handle ordinal and solving toward post clamp corner keeps neighbor riding its real position
645BOOST_FIXTURE_TEST_CASE( ClampedRectCornerDragSolvesToClampedCorner, DRAG_FIXTURE )
646{
647 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
648 PCB_SHAPE* seg = addSegment( { 10 * MM, 0 }, { 15 * MM, -5 * MM } );
649
651 c->AddMember( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 );
652 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
653 board.Add( c );
654
655 // Drag the top-right corner far past the left edge; the behavior clamps x to the minimum
656 // width while y follows the cursor, so the shape corner and the raw cursor diverge.
657 const VECTOR2I rawCursor( -5 * MM, -2 * MM );
658 const int minWidth = 25400; // 1 mil floor applied by PinEditedCorner
659 const VECTOR2I clamped( minWidth, rawCursor.y );
660
661 rect->SetTop( clamped.y );
662 rect->SetRight( clamped.x );
663
664 std::optional<CONSTRAINT_ANCHOR_POINT> vertex = ConstraintShapeVertex( rect, 1 );
665
666 BOOST_REQUIRE( vertex.has_value() );
667 BOOST_CHECK( vertex->pos == clamped );
668 BOOST_CHECK( vertex->pos != rawCursor );
669
670 std::vector<PCB_SHAPE*> modified;
671 BOARD_COMMIT commit( tool );
672 SolveCluster( &board, { rect->m_Uuid, vertex->anchor, vertex->index }, vertex->pos, &modified,
673 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
674
675 BOOST_CHECK_LE( ( seg->GetStart() - clamped ).EuclideanNorm(), 5000.0 );
676}
677
678
679namespace
680{
681VECTOR2I dragRectTopLeft( const PCB_SHAPE* aRect )
682{
683 return VECTOR2I( std::min( aRect->GetStart().x, aRect->GetEnd().x ),
684 std::min( aRect->GetStart().y, aRect->GetEnd().y ) );
685}
686
687
688VECTOR2I dragRectBotRight( const PCB_SHAPE* aRect )
689{
690 return VECTOR2I( std::max( aRect->GetStart().x, aRect->GetEnd().x ),
691 std::max( aRect->GetStart().y, aRect->GetEnd().y ) );
692}
693
694
695PCB_CONSTRAINT* addDrivingVertexLength( BOARD& aBoard, PCB_SHAPE* aShape, int aIndexA, int aIndexB,
696 double aLengthIU )
697{
699 c->AddMember( aShape->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, aIndexA );
700 c->AddMember( aShape->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, aIndexB );
701 c->SetValue( aLengthIU );
702 c->SetDriving( true );
703 aBoard.Add( c );
704 return c;
705}
706}
707
708
709// Driving width on top side must survive adjacent side drag where side handles used to bypass solver and violate length
710// Pinning side canonical corner routes it through same drag solve as corner drag so hard length wins and rectangle translates
711BOOST_FIXTURE_TEST_CASE( RectSideDragEnforcesDrivingWidth, DRAG_FIXTURE )
712{
713 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
714
715 addDrivingVertexLength( board, rect, 0, 1, 10.0 * MM ); // TL-TR width
716
717 // Drag the right side 5 mm out, as RECTANGLE_POINT_EDIT_BEHAVIOR::UpdateItem would.
718 rect->SetRight( 15 * MM );
719
720 // Side RECT_RIGHT (1) maps to canonical corner 1 (TR), read back post-move.
721 std::optional<CONSTRAINT_ANCHOR_POINT> corner = ConstraintShapeVertex( rect, 1 );
722
723 BOOST_REQUIRE( corner.has_value() );
724 BOOST_CHECK( corner->pos == VECTOR2I( 15 * MM, 0 ) );
725
726 std::vector<PCB_SHAPE*> modified;
727 BOARD_COMMIT commit( tool );
728 SolveCluster( &board, { rect->m_Uuid, corner->anchor, corner->index }, corner->pos, &modified,
729 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
730
731 const VECTOR2I tl = dragRectTopLeft( rect );
732 const VECTOR2I br = dragRectBotRight( rect );
733
734 // The driving width held against the drag and the height stayed untouched.
735 BOOST_CHECK_LE( std::abs( ( br.x - tl.x ) - 10 * MM ), 20000 );
736 BOOST_CHECK_LE( std::abs( ( br.y - tl.y ) - 10 * MM ), 20000 );
737
738 // Drag was not simply refused rect moved toward cursor strictly between original and requested span
739 BOOST_CHECK_GT( tl.x, 1 * MM );
740 BOOST_CHECK_LT( br.x, 14 * MM );
741
742 BOOST_TEST_MESSAGE( "side drag under driving width settled TL at " << tl.x << "," << tl.y );
743
744 // The four enumerated corners agree with the solved extremes, so the rect stayed axis-aligned.
745 std::vector<CONSTRAINT_ANCHOR_POINT> corners = ConstraintShapeAnchors( rect );
746
747 BOOST_REQUIRE_EQUAL( corners.size(), 4 );
748 BOOST_CHECK_EQUAL( corners[0].pos, tl );
749 BOOST_CHECK_EQUAL( corners[2].pos, br );
750}
751
752
753// Dragging bottom side leaves top side width constraint satisfiable so resize applies exactly
754// Height follows handle while held top left corner and driven width stay intact
755BOOST_FIXTURE_TEST_CASE( RectSideDragOffConstrainedAxisResizes, DRAG_FIXTURE )
756{
757 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
758
759 addDrivingVertexLength( board, rect, 0, 1, 10.0 * MM ); // TL-TR width
760
761 // Drag the bottom side 5 mm down; side RECT_BOT (2) maps to canonical corner 2 (BR).
762 rect->SetBottom( 15 * MM );
763
764 std::optional<CONSTRAINT_ANCHOR_POINT> corner = ConstraintShapeVertex( rect, 2 );
765
766 BOOST_REQUIRE( corner.has_value() );
767 BOOST_CHECK( corner->pos == VECTOR2I( 10 * MM, 15 * MM ) );
768
769 std::vector<PCB_SHAPE*> modified;
770 BOARD_COMMIT commit( tool );
771 SolveCluster( &board, { rect->m_Uuid, corner->anchor, corner->index }, corner->pos, &modified,
772 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
773
774 const VECTOR2I tl = dragRectTopLeft( rect );
775 const VECTOR2I br = dragRectBotRight( rect );
776
777 BOOST_CHECK_LE( tl.EuclideanNorm(), 5000.0 );
778 BOOST_CHECK_LE( std::abs( ( br.x - tl.x ) - 10 * MM ), 5000 );
779 BOOST_CHECK_LE( std::abs( ( br.y - tl.y ) - 15 * MM ), 5000 );
780}
781
782
783// A side drag on a rectangle whose own dimensions are unconstrained resizes exactly as the handle
784// placed it; the coincident neighbor on an unmoved corner stays put.
785BOOST_FIXTURE_TEST_CASE( UnconstrainedRectSideDragResizesExactly, DRAG_FIXTURE )
786{
787 PCB_SHAPE* rect = addRect( { 0, 0 }, { 10 * MM, 10 * MM } );
788 PCB_SHAPE* seg = addSegment( { 0, 0 }, { -5 * MM, -5 * MM } );
789
791 c->AddMember( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 );
792 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
793 board.Add( c );
794
795 rect->SetRight( 15 * MM );
796
797 std::optional<CONSTRAINT_ANCHOR_POINT> corner = ConstraintShapeVertex( rect, 1 );
798
799 BOOST_REQUIRE( corner.has_value() );
800
801 std::vector<PCB_SHAPE*> modified;
802 BOARD_COMMIT commit( tool );
803 SolveCluster( &board, { rect->m_Uuid, corner->anchor, corner->index }, corner->pos, &modified,
804 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
805
806 BOOST_CHECK_LE( dragRectTopLeft( rect ).EuclideanNorm(), 5000.0 );
807 BOOST_CHECK_LE( ( dragRectBotRight( rect ) - VECTOR2I( 15 * MM, 10 * MM ) ).EuclideanNorm(), 5000.0 );
808 BOOST_CHECK_LE( seg->GetStart().EuclideanNorm(), 5000.0 );
809}
810
811
812// Driving length on polygon edge must survive a drag of that same edge
813// Both endpoints ride co dragged pins that yield to hard length so edge lands at driven length and unbound vertices hold
814BOOST_FIXTURE_TEST_CASE( PolyEdgeDragEnforcesDrivingLength, DRAG_FIXTURE )
815{
816 const std::vector<VECTOR2I> points{ { 0, 0 },
817 { 10 * MM, 0 },
818 { 13 * MM, 8 * MM },
819 { 5 * MM, 14 * MM },
820 { -3 * MM, 8 * MM } };
821
822 PCB_SHAPE* poly = addPoly( points );
823 const double edgeLen = std::hypot( 3.0, 8.0 ) * MM; // the 1-2 edge's current length
824
825 addDrivingVertexLength( board, poly, 1, 2, edgeLen );
826
827 // Drag the 1-2 edge so both vertices move and its length would stretch.
828 std::vector<VECTOR2I> dragged = points;
829 dragged[1] = { 8 * MM, -2 * MM };
830 dragged[2] = { 16 * MM, 9 * MM };
831 poly->SetPolyPoints( dragged );
832
833 std::vector<PCB_SHAPE*> modified;
834 BOARD_COMMIT commit( tool );
835 SolveCluster( &board, { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 }, dragged[1], &modified,
836 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); },
837 /* aIncludeDragged */ false, /* aStabilize */ false, {},
838 std::pair{ CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 2 ), dragged[2] } );
839
840 const SHAPE_LINE_CHAIN& outline = poly->GetPolyShape().COutline( 0 );
841
842 double solvedLen = ( outline.CPoint( 2 ) - outline.CPoint( 1 ) ).EuclideanNorm();
843
844 BOOST_CHECK_LE( std::abs( solvedLen - edgeLen ), 20000.0 );
845
846 // Drag was not simply refused edge midpoint moved out of solver noise toward requested midpoint
847 // Length constrains only vertex separation so midpoint is free to follow landing closer than start
848 const VECTOR2I midBefore = ( points[1] + points[2] ) / 2;
849 const VECTOR2I midTarget = ( dragged[1] + dragged[2] ) / 2;
850 const VECTOR2I midSolved = ( outline.CPoint( 1 ) + outline.CPoint( 2 ) ) / 2;
851
852 BOOST_CHECK_LT( ( midSolved - midTarget ).EuclideanNorm(), ( midBefore - midTarget ).EuclideanNorm() );
853 BOOST_CHECK_GT( ( midSolved - midBefore ).EuclideanNorm(), 250000.0 );
854
855 for( int i : { 0, 3, 4 } )
856 BOOST_CHECK_LE( ( outline.CPoint( i ) - points[i] ).EuclideanNorm(), 5000.0 );
857}
858
859
860// An unconstrained polygon edge drag lands both vertices exactly where the handle placed them; the
861// vertex-bound segment follows and the unbound vertices hold.
862BOOST_FIXTURE_TEST_CASE( UnconstrainedPolyEdgeDragMovesBothVertices, DRAG_FIXTURE )
863{
864 const std::vector<VECTOR2I> points{ { 0, 0 },
865 { 10 * MM, 0 },
866 { 13 * MM, 8 * MM },
867 { 5 * MM, 14 * MM },
868 { -3 * MM, 8 * MM } };
869
870 PCB_SHAPE* poly = addPoly( points );
871 PCB_SHAPE* seg = addSegment( points[1], { 25 * MM, -5 * MM } );
872
874 c->AddMember( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 );
875 c->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
876 board.Add( c );
877
878 // Translate the 1-2 edge 3 mm right, as the edge handle would.
879 std::vector<VECTOR2I> dragged = points;
880 dragged[1] += VECTOR2I( 3 * MM, 0 );
881 dragged[2] += VECTOR2I( 3 * MM, 0 );
882 poly->SetPolyPoints( dragged );
883
884 std::vector<PCB_SHAPE*> modified;
885 BOARD_COMMIT commit( tool );
886 SolveCluster( &board, { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 }, dragged[1], &modified,
887 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); },
888 /* aIncludeDragged */ false, /* aStabilize */ false, {},
889 std::pair{ CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 2 ), dragged[2] } );
890
891 const SHAPE_LINE_CHAIN& outline = poly->GetPolyShape().COutline( 0 );
892
893 BOOST_CHECK_LE( ( outline.CPoint( 1 ) - dragged[1] ).EuclideanNorm(), 5000.0 );
894 BOOST_CHECK_LE( ( outline.CPoint( 2 ) - dragged[2] ).EuclideanNorm(), 5000.0 );
895 BOOST_CHECK_LE( ( seg->GetStart() - dragged[1] ).EuclideanNorm(), 5000.0 );
896 BOOST_CHECK( std::find( modified.begin(), modified.end(), seg ) != modified.end() );
897
898 for( int i : { 0, 3, 4 } )
899 BOOST_CHECK_LE( ( outline.CPoint( i ) - points[i] ).EuclideanNorm(), 5000.0 );
900}
901
902
903// Dragging an arc's centre holds both endpoints, so the centre moves and the radius adapts.
904BOOST_FIXTURE_TEST_CASE( ArcCentreDragHoldsEndpoints, DRAG_FIXTURE )
905{
906 PCB_SHAPE* arc = addArc( { 10 * MM, 0 }, { 7071068, 7071068 }, { 0, 10 * MM } );
907 PCB_SHAPE* seg = addSegment( { 0, 10 * MM }, { 5 * MM, 15 * MM } );
908 addCoincident( arc, CONSTRAINT_ANCHOR::END, seg, CONSTRAINT_ANCHOR::START );
909
910 const VECTOR2I start0 = arc->GetStart();
911 const VECTOR2I end0 = arc->GetEnd();
912
913 std::vector<PCB_SHAPE*> modified;
914 BOARD_COMMIT commit( tool );
915 SolveCluster( &board, { arc->m_Uuid, CONSTRAINT_ANCHOR::CENTER }, { 1 * MM, 1 * MM }, &modified,
916 [&]( BOARD_ITEM* aItem ) { commit.Modify( aItem ); } );
917
918 BOOST_CHECK_LE( ( arc->GetStart() - start0 ).EuclideanNorm(), 20000.0 );
919 BOOST_CHECK_LE( ( arc->GetEnd() - end0 ).EuclideanNorm(), 20000.0 );
920 BOOST_CHECK( ( arc->GetCenter() - VECTOR2I( 0, 0 ) ).EuclideanNorm() > 100000.0 );
921}
922
923
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.
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)
Gather the cluster of shapes transitively constrained with the dragged shape, solve with the dragged ...
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.
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
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: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
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:44
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...
@ SEGMENT
Definition eda_shape.h:46
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:47
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.
@ 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)
One participant in a constraint: a referenced board item plus the feature of that item that participa...
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_FIXTURE_TEST_CASE(DragReDerivesNeighborRevertRestores, DRAG_FIXTURE)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
static const long long MM
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
const SHAPE_LINE_CHAIN chain
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683