KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_constraint_dimension.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
26
28
29#include <algorithm>
30#include <vector>
31
32#include <board.h>
33#include <footprint.h>
34#include <geometry/shape_arc.h>
36#include <i18n_utility.h>
37#include <pcb_dimension.h>
38#include <pcb_shape.h>
39#include <properties/property.h>
41#include <trigo.h>
42
46
48
50
51using namespace KI_TEST;
52
53namespace
54{
55PCB_DIM_ALIGNED* addAlignedDim( BOARD& aBoard, const VECTOR2I& aStart, const VECTOR2I& aEnd )
56{
58 dim->SetStart( aStart );
59 dim->SetEnd( aEnd );
60 dim->Update();
61 aBoard.Add( dim );
62 return dim;
63}
64
65
66PCB_DIM_RADIAL* addRadialDim( BOARD& aBoard, const VECTOR2I& aCenter, const VECTOR2I& aRim )
67{
68 PCB_DIM_RADIAL* dim = new PCB_DIM_RADIAL( &aBoard );
69 dim->SetStart( aCenter );
70 dim->SetEnd( aRim );
71 dim->Update();
72 aBoard.Add( dim );
73 return dim;
74}
75
76
77PCB_DIM_ORTHOGONAL* addOrthogonalDim( BOARD& aBoard, const VECTOR2I& aStart, const VECTOR2I& aEnd,
78 PCB_DIM_ORTHOGONAL::DIR aOrientation )
79{
80 PCB_DIM_ORTHOGONAL* dim = new PCB_DIM_ORTHOGONAL( &aBoard );
81 dim->SetOrientation( aOrientation );
82 dim->SetStart( aStart );
83 dim->SetEnd( aEnd );
84 dim->Update();
85 aBoard.Add( dim );
86 return dim;
87}
88
89
90PCB_CONSTRAINT* addDrivingLength( BOARD& aBoard, const KIID& aDimension, double aLengthIU )
91{
94 { { aDimension, CONSTRAINT_ANCHOR::START }, { aDimension, CONSTRAINT_ANCHOR::END } }, aLengthIU );
95 c->SetDriving( true );
96 return c;
97}
98
99
100// Dim-first member order matches draw-time auto-constrain
101PCB_CONSTRAINT* bindEndpoint( BOARD& aBoard, const KIID& aDimension, CONSTRAINT_ANCHOR aDimAnchor,
102 const CONSTRAINT_MEMBER& aTarget )
103{
104 return addConstraint( aBoard, PCB_CONSTRAINT_TYPE::COINCIDENT, { { aDimension, aDimAnchor }, aTarget } );
105}
106
107
108// Physical TL and BR corners regardless of stored start end order
109VECTOR2I rectTopLeft( const PCB_SHAPE* aRect )
110{
111 return VECTOR2I( std::min( aRect->GetStart().x, aRect->GetEnd().x ),
112 std::min( aRect->GetStart().y, aRect->GetEnd().y ) );
113}
114
115
116VECTOR2I rectBottomRight( const PCB_SHAPE* aRect )
117{
118 return VECTOR2I( std::max( aRect->GetStart().x, aRect->GetEnd().x ),
119 std::max( aRect->GetStart().y, aRect->GetEnd().y ) );
120}
121}
122
123
124BOOST_AUTO_TEST_SUITE( ConstraintDimension )
125
126
127// An aligned dimension exposes its two feature points; a leader (a control-point family) exposes
128// only its start.
129BOOST_AUTO_TEST_CASE( DimensionAnchorsPerFamily )
130{
131 BOARD board;
132 PCB_DIM_ALIGNED* aligned = addAlignedDim( board, { 0, 0 }, { 10 * MM, 0 } );
133
134 std::vector<CONSTRAINT_ANCHOR_POINT> anchors = ConstraintItemAnchors( aligned );
135 BOOST_REQUIRE_EQUAL( anchors.size(), 2 );
136 BOOST_CHECK( anchors[0].anchor == CONSTRAINT_ANCHOR::START );
137 BOOST_CHECK( anchors[1].anchor == CONSTRAINT_ANCHOR::END );
138
139 PCB_DIM_LEADER* leader = new PCB_DIM_LEADER( &board );
140 leader->SetStart( { 0, 0 } );
141 leader->SetEnd( { 5 * MM, 5 * MM } );
142 board.Add( leader );
143
144 BOOST_CHECK_EQUAL( ConstraintItemAnchors( leader ).size(), 1 ); // start only
145}
146
147
148// A coincident constraint binds a dimension's start to a segment endpoint; dragging that endpoint
149// pulls the dimension start along, while the dimension's unbound end stays put.
150BOOST_AUTO_TEST_CASE( CoincidentDimensionFollowsDraggedShape )
151{
152 BOARD board;
153 PCB_SHAPE* seg = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
154
155 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 10 * MM, 0 }, { 10 * MM, 5 * MM } );
156
158 { { seg->m_Uuid, CONSTRAINT_ANCHOR::END }, { dim->m_Uuid, CONSTRAINT_ANCHOR::START } } );
159
160 // A bound dimension carries its cluster's verdict, so the overlay can mark it.
162 auto segState = diag.shapeStates.find( seg->m_Uuid );
163 auto dimState = diag.shapeStates.find( dim->m_Uuid );
164
165 BOOST_REQUIRE( segState != diag.shapeStates.end() );
166 BOOST_REQUIRE( dimState != diag.shapeStates.end() );
167 BOOST_CHECK( dimState->second == segState->second );
168
169 const VECTOR2I dimEnd0 = dim->GetEnd();
170
171 std::vector<BOARD_ITEM*> staged;
172 SolveCluster( &board, { seg->m_Uuid, CONSTRAINT_ANCHOR::END }, { 12 * MM, 3 * MM }, nullptr,
173 [&]( BOARD_ITEM* i ) { staged.push_back( i ); } );
174
175 // The dimension start tracked the segment end; its unbound end did not move.
176 BOOST_CHECK_LE( ( dim->GetStart() - seg->GetEnd() ).EuclideanNorm(), 5000.0 );
177 BOOST_CHECK( ( dim->GetStart() - VECTOR2I( 10 * MM, 0 ) ).EuclideanNorm() > 20000.0 );
178 BOOST_CHECK_LE( ( dim->GetEnd() - dimEnd0 ).EuclideanNorm(), 5000.0 );
179 BOOST_CHECK( std::find( staged.begin(), staged.end(), dim ) != staged.end() );
180}
181
182
183// A dimension whose bound shape is deleted leaves the constraint in an error state, not deleted.
184BOOST_AUTO_TEST_CASE( DeletedShapeErrorsDimensionConstraint )
185{
186 BOARD board;
187 PCB_SHAPE* seg = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
188 KIID segId = seg->m_Uuid;
189
190 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 10 * MM, 0 }, { 10 * MM, 5 * MM } );
191
193 { { segId, CONSTRAINT_ANCHOR::END }, { dim->m_Uuid, CONSTRAINT_ANCHOR::START } } );
194
195 board.Remove( seg );
196 delete seg;
197
199 BOOST_CHECK_EQUAL( diag.errored.size(), 1 );
200}
201
202
203// The board-wide anchor picker offers a dimension's feature point, so coincident authoring can bind
204// a dimension by clicking near it.
205BOOST_AUTO_TEST_CASE( PickerOffersDimensionAnchor )
206{
207 BOARD board;
208 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 10 * MM, 0 }, { 10 * MM, 5 * MM } );
209
210 std::optional<CONSTRAINT_MEMBER> hit =
211 NearestConstraintAnchor( &board, VECTOR2I( 10 * MM, 100 ), MM );
212
213 BOOST_REQUIRE( hit.has_value() );
214 BOOST_CHECK( hit->m_item == dim->m_Uuid );
215 BOOST_CHECK( hit->m_anchor == CONSTRAINT_ANCHOR::START );
216}
217
218
219// Authoring a coincident with the dimension as the first member (the apply-on-create path pins the
220// first member) snaps the dimension's bound point onto the shape point.
221BOOST_AUTO_TEST_CASE( ApplyCoincidentDimensionFirstMember )
222{
223 BOARD board;
224 PCB_SHAPE* seg = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
225
226 // The dimension start sits 1 mm off the segment end it will bind to.
227 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 11 * MM, 0 }, { 11 * MM, 5 * MM } );
228
231 { { dim->m_Uuid, CONSTRAINT_ANCHOR::START }, { seg->m_Uuid, CONSTRAINT_ANCHOR::END } } );
232
233 std::vector<PCB_SHAPE*> modified;
234 ApplyConstraintImmediately( &board, c, &modified );
235
236 // The first member (the dimension start) is pinned, so the segment end snaps onto it.
237 BOOST_CHECK_LE( ( seg->GetEnd() - dim->GetStart() ).EuclideanNorm(), 5000.0 );
238}
239
240
241// Moving a whole shape re-solves its cluster, so a coincident-bound dimension follows the shape.
242BOOST_AUTO_TEST_CASE( WholeShapeMoveDragsBoundDimension )
243{
244 BOARD board;
245 PCB_SHAPE* seg = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
246
247 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 10 * MM, 0 }, { 10 * MM, 5 * MM } );
248
250 { { seg->m_Uuid, CONSTRAINT_ANCHOR::END }, { dim->m_Uuid, CONSTRAINT_ANCHOR::START } } );
251
252 seg->Move( { 0, 3 * MM } ); // whole-shape move
253 ReSolveShapeClusters( &board, { seg } );
254
255 // The dimension start followed the moved segment end.
256 BOOST_CHECK_LE( ( dim->GetStart() - seg->GetEnd() ).EuclideanNorm(), 5000.0 );
257 BOOST_CHECK( dim->GetStart().y > 1 * MM ); // it actually moved up
258}
259
260
261// A leader has no bindable END (its end is a control point), so a coincident on it cannot map and
262// is flagged errored rather than moving the control point.
263BOOST_AUTO_TEST_CASE( LeaderEndIsNotBindable )
264{
265 BOARD board;
266 PCB_SHAPE* seg = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
267
268 PCB_DIM_LEADER* leader = new PCB_DIM_LEADER( &board );
269 leader->SetStart( { 10 * MM, 0 } );
270 leader->SetEnd( { 15 * MM, 5 * MM } );
271 board.Add( leader );
272
275 { { seg->m_Uuid, CONSTRAINT_ANCHOR::END }, { leader->m_Uuid, CONSTRAINT_ANCHOR::END } } );
276
277 std::vector<PCB_CONSTRAINT*> constraints = { c };
279 BOOST_REQUIRE( adapter.Build( { seg }, constraints, nullptr, { leader } ) );
280
281 BOOST_CHECK( std::find( adapter.UnmappedConstraints().begin(), adapter.UnmappedConstraints().end(),
282 c->m_Uuid )
283 != adapter.UnmappedConstraints().end() );
284}
285
286
287// Driving length on centre-to-rim radial drives circle radius centre pinned
288BOOST_AUTO_TEST_CASE( RadialDrivingLengthDrivesCircleRadius )
289{
290 BOARD board;
291 PCB_SHAPE* circle = addCircle( board, { 0, 0 }, 5 * MM );
292
293 PCB_DIM_RADIAL* dim = addRadialDim( board, { 0, 0 }, { 5 * MM, 0 } );
294
295 // Bind as draw-time auto-constrain does centre coincident rim on outline
297 { { dim->m_Uuid, CONSTRAINT_ANCHOR::START }, { circle->m_Uuid, CONSTRAINT_ANCHOR::CENTER } } );
299 { { dim->m_Uuid, CONSTRAINT_ANCHOR::END }, { circle->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
300
301 PCB_CONSTRAINT* driving = addDrivingLength( board, dim->m_Uuid, 8 * MM );
302
303 ApplyConstraintImmediately( &board, driving );
304
305 BOOST_CHECK_LE( std::abs( circle->GetRadius() - 8 * MM ), 5000 );
306 BOOST_CHECK_LE( ( circle->GetCenter() - VECTOR2I( 0, 0 ) ).EuclideanNorm(), 5000.0 );
307}
308
309
310// Radial text is hand placed so a driving radius change must carry it with the leader knee
311BOOST_AUTO_TEST_CASE( RadialDrivingLengthCarriesTextWithKnee )
312{
313 BOARD board;
314 PCB_SHAPE* circle = addCircle( board, { 0, 0 }, 5 * MM );
315
316 PCB_DIM_RADIAL* dim = addRadialDim( board, { 0, 0 }, { 5 * MM, 0 } );
317
318 const VECTOR2I textOffset( 3 * MM, -2 * MM );
319
320 dim->SetTextPos( dim->GetKnee() + textOffset );
321 dim->Update();
322
324 { { dim->m_Uuid, CONSTRAINT_ANCHOR::START }, { circle->m_Uuid, CONSTRAINT_ANCHOR::CENTER } } );
326 { { dim->m_Uuid, CONSTRAINT_ANCHOR::END }, { circle->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
327
328 PCB_CONSTRAINT* driving = addDrivingLength( board, dim->m_Uuid, 12 * MM );
329
330 ApplyConstraintImmediately( &board, driving );
331
332 BOOST_CHECK_LE( std::abs( circle->GetRadius() - 12 * MM ), 5000 );
333 BOOST_CHECK_LE( std::abs( ( dim->GetEnd() - dim->GetStart() ).EuclideanNorm() - 12 * MM ), 5000.0 );
334
335 // Knee moved with the rim point so the text must have moved the same way
336 BOOST_CHECK_LE( ( ( dim->GetTextPos() - dim->GetKnee() ) - textOffset ).EuclideanNorm(), 5000.0 );
337}
338
339
340// Horizontal driving orthogonal fixes x gap only y stays free
341BOOST_AUTO_TEST_CASE( OrthogonalHorizontalDrivingSetsXAxisOnly )
342{
343 BOARD board;
344
345 PCB_DIM_ORTHOGONAL* dim =
346 addOrthogonalDim( board, { 0, 0 }, { 4 * MM, 3 * MM }, PCB_DIM_ORTHOGONAL::DIR::HORIZONTAL );
347
348 PCB_CONSTRAINT* driving = addDrivingLength( board, dim->m_Uuid, 10 * MM );
349
350 ApplyConstraintImmediately( &board, driving );
351
352 BOOST_CHECK_LE( std::abs( ( dim->GetEnd().x - dim->GetStart().x ) - 10 * MM ), 5000 );
353 BOOST_CHECK_LE( std::abs( dim->GetEnd().y - 3 * MM ), 5000 ); // y untouched
354}
355
356
357// Vertical driving orthogonal fixes y gap only x stays free
358BOOST_AUTO_TEST_CASE( OrthogonalVerticalDrivingSetsYAxisOnly )
359{
360 BOARD board;
361
362 PCB_DIM_ORTHOGONAL* dim =
363 addOrthogonalDim( board, { 0, 0 }, { 3 * MM, 4 * MM }, PCB_DIM_ORTHOGONAL::DIR::VERTICAL );
364
365 PCB_CONSTRAINT* driving = addDrivingLength( board, dim->m_Uuid, 10 * MM );
366
367 ApplyConstraintImmediately( &board, driving );
368
369 BOOST_CHECK_LE( std::abs( ( dim->GetEnd().y - dim->GetStart().y ) - 10 * MM ), 5000 );
370 BOOST_CHECK_LE( std::abs( dim->GetEnd().x - 3 * MM ), 5000 ); // x untouched
371}
372
373
374// Radial only binds when one circle or arc holds both centre and rim
375BOOST_AUTO_TEST_CASE( RadialBindingRequiresSameArcCenterAndOutline )
376{
377 BOARD board;
378 PCB_SHAPE* circle = addCircle( board, { 0, 0 }, 5 * MM );
379 const double tol = 10000; // 0.01 mm
380
381 PCB_DIM_RADIAL* dim = addRadialDim( board, { 0, 0 }, { 5 * MM, 0 } );
382
383 std::optional<KIID> hit =
384 SelectRadialDimensionTarget( &board, dim->m_Uuid, { 0, 0 }, { 5 * MM, 0 }, tol );
385 BOOST_REQUIRE( hit.has_value() );
386 BOOST_CHECK( *hit == circle->m_Uuid );
387
388 // Rim 3mm off a 5mm circle no binding
389 BOOST_CHECK( !SelectRadialDimensionTarget( &board, dim->m_Uuid, { 0, 0 }, { 3 * MM, 0 }, tol ) );
390
391 // Centre off circle centre no binding
392 BOOST_CHECK( !SelectRadialDimensionTarget( &board, dim->m_Uuid, { 2 * MM, 0 }, { 7 * MM, 0 }, tol ) );
393}
394
395
396// Centre on one circle rim on another binds neither
397BOOST_AUTO_TEST_CASE( RadialBindingRejectsCrossObjectCenterAndOutline )
398{
399 BOARD board;
400 const double tol = 10000;
401
402 addCircle( board, { 0, 0 }, 5 * MM ); // centre object
403 addCircle( board, { 50 * MM, 0 }, 5 * MM ); // outline object 55mm from first centre
404
405 BOOST_CHECK( !SelectRadialDimensionTarget( &board, KIID(), { 0, 0 }, { 55 * MM, 0 }, tol ) );
406}
407
408
409// Corner to corner dim binds both endpoints as indexed VERTEX anchors in canonical TL to BL order
410BOOST_AUTO_TEST_CASE( DimensionBindsRectCornerToCorner )
411{
412 BOARD board;
413 PCB_SHAPE* rect = addRect( board, { 0, 0 }, { 10 * MM, 10 * MM } );
414
415 KIID fakeDim; // dimension uuid not on the board as during interactive draw
416 std::vector<ENDPOINT_BINDING> bindings =
417 SelectEndpointBindings( &board, fakeDim, { 0, 0 }, VECTOR2I( 10 * MM, 10 * MM ), 1000.0 );
418
419 BOOST_REQUIRE_EQUAL( bindings.size(), 2 );
420 BOOST_CHECK( bindings[0].sourceAnchor == CONSTRAINT_ANCHOR::START );
421 BOOST_CHECK( bindings[0].target == CONSTRAINT_MEMBER( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 ) );
422 BOOST_CHECK( bindings[1].sourceAnchor == CONSTRAINT_ANCHOR::END );
423 BOOST_CHECK( bindings[1].target == CONSTRAINT_MEMBER( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 2 ) );
424
425 // Rect stored end before start still enumerates canonically index 0 always TL
426 PCB_SHAPE* reversed = addRect( board, { 30 * MM, 30 * MM }, { 20 * MM, 20 * MM } );
427 std::vector<CONSTRAINT_ANCHOR_POINT> anchors = ConstraintShapeAnchors( reversed );
428
429 BOOST_REQUIRE_EQUAL( anchors.size(), 4 );
430 BOOST_CHECK_EQUAL( anchors[0].index, 0 );
431 BOOST_CHECK_EQUAL( anchors[0].pos, VECTOR2I( 20 * MM, 20 * MM ) );
432 BOOST_CHECK_EQUAL( anchors[2].index, 2 );
433 BOOST_CHECK_EQUAL( anchors[2].pos, VECTOR2I( 30 * MM, 30 * MM ) );
434}
435
436
437// Drawn segment starting on a committed segment end binds coincident there the free end binds nothing
438BOOST_AUTO_TEST_CASE( ShapeEndpointBindsAtDrawTolerance )
439{
440 BOARD board;
441 PCB_SHAPE* first = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
442
443 const double tol = 0.01 * MM;
444
445 KIID newSeg; // uuid not on the board as during interactive draw
446 std::vector<ENDPOINT_BINDING> bindings =
447 SelectEndpointBindings( &board, newSeg, { 10 * MM, 0 }, VECTOR2I( 10 * MM, 20 * MM ), tol );
448
449 BOOST_REQUIRE_EQUAL( bindings.size(), 1 );
450 BOOST_CHECK( bindings[0].sourceAnchor == CONSTRAINT_ANCHOR::START );
451 BOOST_CHECK( bindings[0].target == CONSTRAINT_MEMBER( first->m_Uuid, CONSTRAINT_ANCHOR::END ) );
452
453 // Near the corner but outside draw tolerance binds nothing
454 bindings = SelectEndpointBindings( &board, newSeg, VECTOR2I( 10 * MM + MM / 10, 0 ), VECTOR2I( 30 * MM, 20 * MM ),
455 tol );
456
457 BOOST_CHECK( bindings.empty() );
458}
459
460
461// Dim across pentagon vertices binds by outline order VERTEX resolves to own index not vertex 0
462BOOST_AUTO_TEST_CASE( DimensionBindsPolygonVertices )
463{
464 BOARD board;
465
466 std::vector<VECTOR2I> pts = { { 0, 0 },
467 { 20 * MM, 5 * MM },
468 { 15 * MM, 20 * MM },
469 { 5 * MM, 20 * MM },
470 { -5 * MM, 10 * MM } };
471 PCB_SHAPE* poly = addPoly( board, pts );
472
473 KIID fakeDim;
474 std::vector<ENDPOINT_BINDING> bindings = SelectEndpointBindings( &board, fakeDim, pts[1], pts[3], 1000.0 );
475
476 BOOST_REQUIRE_EQUAL( bindings.size(), 2 );
477 BOOST_CHECK( bindings[0].sourceAnchor == CONSTRAINT_ANCHOR::START );
478 BOOST_CHECK( bindings[0].target == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 ) );
479 BOOST_CHECK( bindings[1].sourceAnchor == CONSTRAINT_ANCHOR::END );
480 BOOST_CHECK( bindings[1].target == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 3 ) );
481
482 // Anchor lookup must honour vertex index not just the VERTEX tag
483 std::optional<VECTOR2I> pos =
485
486 BOOST_REQUIRE( pos.has_value() );
487 BOOST_CHECK_EQUAL( *pos, pts[2] );
488}
489
490
491// Single polygon reaching both ends preferred over splitting onto a closer segment
492BOOST_AUTO_TEST_CASE( DimensionBindingPrefersSinglePolygonOverSplit )
493{
494 BOARD board;
495
496 PCB_SHAPE* poly = addPoly( board, { { 0, 0 },
497 { 10 * MM, 0 },
498 { 10 * MM, 10 * MM },
499 { 0, 10 * MM } } );
500 PCB_SHAPE* b = addSegment( board, { 2 * MM, 1 * MM }, { 2 * MM, 20 * MM } );
501
502 KIID fakeDim;
503 std::vector<ENDPOINT_BINDING> bindings =
504 SelectEndpointBindings( &board, fakeDim, { 0, 0 }, VECTOR2I( 2 * MM, 0 ), 12.0 * MM );
505
506 BOOST_REQUIRE_EQUAL( bindings.size(), 2 );
507 BOOST_CHECK( bindings[0].target == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 ) );
508 BOOST_CHECK( bindings[1].target == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 ) );
509
510 // Decoy segment never enters binding despite its start being nearest END
511 for( const ENDPOINT_BINDING& binding : bindings )
512 BOOST_CHECK( binding.target.m_item != b->m_Uuid );
513}
514
515
516// Arc-bearing outline not ingested by adapter so it offers no anchors
517BOOST_AUTO_TEST_CASE( ArcOutlinePolyOffersNoAnchors )
518{
519 BOARD board;
520
521 PCB_SHAPE* poly = new PCB_SHAPE( &board, SHAPE_T::POLY );
522
524 chain.Append( VECTOR2I( 10 * MM, 10 * MM ) );
525 chain.Append( VECTOR2I( 50 * MM, 10 * MM ) );
526 chain.Append( SHAPE_ARC( { 50 * MM, 10 * MM }, { 55 * MM, 25 * MM }, { 50 * MM, 40 * MM }, 0 ) );
527 chain.Append( VECTOR2I( 10 * MM, 40 * MM ) );
528 chain.SetClosed( true );
529
530 poly->GetPolyShape().AddOutline( chain );
531 board.Add( poly );
532
533 BOOST_REQUIRE_GT( poly->GetPolyShape().COutline( 0 ).ArcCount(), 0 );
534
535 BOOST_CHECK( ConstraintShapeAnchors( poly ).empty() );
536 BOOST_CHECK( !NearestConstraintAnchor( &board, VECTOR2I( 10 * MM, 10 * MM ), MM ) );
537}
538
539
540// Driving aligned dim widens rect to entered length height untouched endpoints ride corners
541BOOST_AUTO_TEST_CASE( AlignedDrivingLengthDrivesRectWidth )
542{
543 BOARD board;
544 PCB_SHAPE* rect = addRect( board, { 0, 0 }, { 10 * MM, 10 * MM } );
545
546 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 0, 0 }, { 10 * MM, 0 } ); // TL to TR
547
548 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::START,
549 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 } );
550 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::END,
551 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } );
552
553 PCB_CONSTRAINT* driving = addDrivingLength( board, dim->m_Uuid, 15 * MM );
554
555 CONSTRAINT_DIAGNOSIS diag = ApplyConstraintImmediately( &board, driving );
556 BOOST_REQUIRE( diag.solved );
557
558 const VECTOR2I tl = rectTopLeft( rect );
559 const VECTOR2I br = rectBottomRight( rect );
560
561 BOOST_CHECK_LE( std::abs( ( br.x - tl.x ) - 15 * MM ), 5000 ); // width driven to the value
562 BOOST_CHECK_LE( std::abs( ( br.y - tl.y ) - 10 * MM ), 5000 ); // height untouched
563 BOOST_CHECK_LE( tl.EuclideanNorm(), 5000.0 ); // pinned TL corner held
564
565 // All four enumerated corners agree with solved extremes rectangle stayed coherent
566 std::vector<CONSTRAINT_ANCHOR_POINT> corners = ConstraintShapeAnchors( rect );
567 BOOST_REQUIRE_EQUAL( corners.size(), 4 );
568 BOOST_CHECK_EQUAL( corners[0].pos, tl );
569 BOOST_CHECK_EQUAL( corners[1].pos, VECTOR2I( br.x, tl.y ) );
570 BOOST_CHECK_EQUAL( corners[2].pos, br );
571 BOOST_CHECK_EQUAL( corners[3].pos, VECTOR2I( tl.x, br.y ) );
572
573 // Dimension endpoints followed corners through coincident bindings
574 BOOST_CHECK_LE( ( dim->GetStart() - corners[0].pos ).EuclideanNorm(), 5000.0 );
575 BOOST_CHECK_LE( ( dim->GetEnd() - corners[1].pos ).EuclideanNorm(), 5000.0 );
576}
577
578
579// Driving horizontal orthogonal on pentagon vertices forces x separation y and unbound vertices held
580BOOST_AUTO_TEST_CASE( OrthogonalDrivingLengthDrivesPolygonAxis )
581{
582 BOARD board;
583
584 std::vector<VECTOR2I> pts = { { 0, 0 },
585 { 20 * MM, 5 * MM },
586 { 15 * MM, 20 * MM },
587 { 5 * MM, 20 * MM },
588 { -5 * MM, 10 * MM } };
589 PCB_SHAPE* poly = addPoly( board, pts );
590
591 PCB_DIM_ORTHOGONAL* dim =
592 addOrthogonalDim( board, pts[1], pts[3], PCB_DIM_ORTHOGONAL::DIR::HORIZONTAL );
593
594 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::START,
595 { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } );
596 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::END,
597 { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 3 } );
598
599 PCB_CONSTRAINT* driving = addDrivingLength( board, dim->m_Uuid, 20 * MM );
600
601 CONSTRAINT_DIAGNOSIS diag = ApplyConstraintImmediately( &board, driving );
602 BOOST_REQUIRE( diag.solved );
603
604 const SHAPE_LINE_CHAIN& outline = poly->GetPolyShape().COutline( 0 );
605
606 BOOST_CHECK_LE( std::abs( std::abs( outline.CPoint( 3 ).x - outline.CPoint( 1 ).x ) - 20 * MM ), 5000 );
607 BOOST_CHECK_LE( std::abs( outline.CPoint( 1 ).y - pts[1].y ), 5000 ); // y is not the driven axis
608 BOOST_CHECK_LE( std::abs( outline.CPoint( 3 ).y - pts[3].y ), 5000 );
609
610 for( int i : { 0, 2, 4 } )
611 BOOST_CHECK_LE( ( outline.CPoint( i ) - pts[i] ).EuclideanNorm(), 5000.0 ); // unbound vertices held
612
613 // Dimension endpoints followed the vertices they bind
614 BOOST_CHECK_LE( ( dim->GetStart() - outline.CPoint( 1 ) ).EuclideanNorm(), 5000.0 );
615 BOOST_CHECK_LE( ( dim->GetEnd() - outline.CPoint( 3 ) ).EuclideanNorm(), 5000.0 );
616}
617
618
619// Driven reference dim follows rect resize through move-tool re-solve length re-measures
620BOOST_AUTO_TEST_CASE( DrivenDimensionRemeasuresAfterRectResize )
621{
622 BOARD board;
623 PCB_SHAPE* rect = addRect( board, { 0, 0 }, { 10 * MM, 10 * MM } );
624
625 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 0, 0 }, { 10 * MM, 0 } );
626
627 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::START,
628 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 } );
629 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::END,
630 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } );
631
632 // Driven mode carries a non-driving length that only mirrors measured geometry
633 PCB_CONSTRAINT* reference = addConstraint(
635 { { dim->m_Uuid, CONSTRAINT_ANCHOR::START }, { dim->m_Uuid, CONSTRAINT_ANCHOR::END } },
636 10.0 * MM );
637 reference->SetDriving( false );
638
639 rect->SetEnd( { 15 * MM, 10 * MM } ); // widen by 5mm edited outside the solver
640 ReSolveShapeClusters( &board, { rect } );
641
642 // Resize survived re-solve dimension endpoints tracked corners
643 BOOST_CHECK_LE( ( rectBottomRight( rect ) - VECTOR2I( 15 * MM, 10 * MM ) ).EuclideanNorm(), 5000.0 );
644 BOOST_CHECK_LE( ( dim->GetStart() - VECTOR2I( 0, 0 ) ).EuclideanNorm(), 5000.0 );
645 BOOST_CHECK_LE( ( dim->GetEnd() - VECTOR2I( 15 * MM, 0 ) ).EuclideanNorm(), 5000.0 );
646
647 BOOST_REQUIRE( reference->GetValue().has_value() );
648 BOOST_CHECK_LE( std::abs( *reference->GetValue() - 15.0 * MM ), 5000.0 );
649}
650
651
652// Properties edit is authoritative hold-edited re-solve keeps typed width and pulls bound items along
653BOOST_AUTO_TEST_CASE( HoldingEditedRectResizeFollowsBindings )
654{
655 BOARD board;
656 PCB_SHAPE* rect = addRect( board, { 0, 0 }, { 10 * MM, 10 * MM } );
657 PCB_SHAPE* seg = addSegment( board, { 10 * MM, 0 }, { 20 * MM, -5 * MM } );
658
660 { { seg->m_Uuid, CONSTRAINT_ANCHOR::START }, { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } } );
661
662 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 0, 0 }, { 10 * MM, 0 } ); // TL to TR
663
664 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::START,
665 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 } );
666 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::END,
667 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } );
668
669 PCB_CONSTRAINT* reference = addConstraint(
671 { { dim->m_Uuid, CONSTRAINT_ANCHOR::START }, { dim->m_Uuid, CONSTRAINT_ANCHOR::END } },
672 10.0 * MM );
673 reference->SetDriving( false );
674
675 rect->SetEnd( { 15 * MM, 10 * MM } ); // widen by 5mm as a typed width edit would
676
677 std::vector<PCB_SHAPE*> modified;
678 ReSolveShapeClustersHoldingEdited( &board, { rect }, &modified );
679
680 // Edited geometry survived corner-bound segment start followed TR corner
681 BOOST_CHECK_LE( ( rectBottomRight( rect ) - VECTOR2I( 15 * MM, 10 * MM ) ).EuclideanNorm(), 5000.0 );
682 BOOST_CHECK_LE( ( rectTopLeft( rect ) - VECTOR2I( 0, 0 ) ).EuclideanNorm(), 5000.0 );
683 BOOST_CHECK_LE( ( seg->GetStart() - VECTOR2I( 15 * MM, 0 ) ).EuclideanNorm(), 5000.0 );
684 BOOST_CHECK( std::find( modified.begin(), modified.end(), seg ) != modified.end() );
685
686 // Both dimension endpoints ride the corners they bind Driven value re-measured
687 BOOST_CHECK_LE( ( dim->GetStart() - VECTOR2I( 0, 0 ) ).EuclideanNorm(), 5000.0 );
688 BOOST_CHECK_LE( ( dim->GetEnd() - VECTOR2I( 15 * MM, 0 ) ).EuclideanNorm(), 5000.0 );
689
690 BOOST_REQUIRE( reference->GetValue().has_value() );
691 BOOST_CHECK_LE( std::abs( *reference->GetValue() - 15.0 * MM ), 5000.0 );
692}
693
694
695// Stale vertex index left unmapped by Build does not poison solve Driving gate reports undrivable
696BOOST_AUTO_TEST_CASE( StaleVertexBindingDegradesAndGatesDriving )
697{
698 BOARD board;
699
700 std::vector<VECTOR2I> pts = { { 0, 0 },
701 { 20 * MM, 5 * MM },
702 { 15 * MM, 20 * MM },
703 { 5 * MM, 20 * MM },
704 { -5 * MM, 10 * MM } };
705 PCB_SHAPE* poly = addPoly( board, pts );
706
707 PCB_DIM_ALIGNED* dim = addAlignedDim( board, pts[1], pts[4] );
708
709 PCB_CONSTRAINT* startBinding = bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::START,
710 { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } );
711 PCB_CONSTRAINT* endBinding = bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::END,
712 { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 4 } );
713
714 BOOST_CHECK( DimensionEndpointsBound( &board, dim ) );
715
716 poly->SetPolyPoints( { pts[0], pts[1], pts[2] } ); // vertex 4 no longer exists
717
718 std::vector<PCB_CONSTRAINT*> constraints( board.Constraints().begin(), board.Constraints().end() );
720
721 BOOST_REQUIRE( adapter.Build( { poly }, constraints, nullptr, { dim } ) );
722
723 const std::vector<KIID>& unmapped = adapter.UnmappedConstraints();
724 BOOST_CHECK( std::find( unmapped.begin(), unmapped.end(), endBinding->m_Uuid ) != unmapped.end() );
725 BOOST_CHECK( std::find( unmapped.begin(), unmapped.end(), startBinding->m_Uuid ) == unmapped.end() );
726
727 // Surviving binding still solves settled cluster stays put
728 BOOST_CHECK( adapter.Solve() );
729 BOOST_CHECK( adapter.Apply().empty() );
730
731 const SHAPE_LINE_CHAIN& outline = poly->GetPolyShape().COutline( 0 );
732
733 for( int i : { 0, 1, 2 } )
734 BOOST_CHECK_EQUAL( outline.CPoint( i ), pts[i] );
735
736 // Dialog offers Driving only through this predicate stale binding must fail it
737 BOOST_CHECK( !DimensionEndpointsBound( &board, dim ) );
738
739 // Restoring outline revives binding gate discriminates stale index from structurally broken
740 poly->SetPolyPoints( pts );
741 BOOST_CHECK( DimensionEndpointsBound( &board, dim ) );
742
743 // Deleting the bound item entirely fails gate through deleted-target leg
744 board.Remove( poly );
745 delete poly;
746 BOOST_CHECK( !DimensionEndpointsBound( &board, dim ) );
747}
748
749
750// Radial Driving gate needs centre and rim on one live circle or arc split legs never offer Driving
751BOOST_AUTO_TEST_CASE( RadialDrivingGateRequiresSingleMappableTarget )
752{
753 BOARD board;
754 PCB_SHAPE* circleA = addCircle( board, { 0, 0 }, 5 * MM );
755 PCB_SHAPE* circleB = addCircle( board, { 50 * MM, 0 }, 5 * MM );
756 PCB_SHAPE* seg = addSegment( board, { 0, 30 * MM }, { 10 * MM, 30 * MM } );
757
758 // Centre and rim both on circle A exactly as authored gates drivable
759 PCB_DIM_RADIAL* bound = addRadialDim( board, { 0, 0 }, { 5 * MM, 0 } );
761 { { bound->m_Uuid, CONSTRAINT_ANCHOR::START }, { circleA->m_Uuid, CONSTRAINT_ANCHOR::CENTER } } );
763 { { bound->m_Uuid, CONSTRAINT_ANCHOR::END }, { circleA->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
764
765 BOOST_CHECK( DimensionEndpointsBound( &board, bound ) );
766
767 // Centre on A rim on B is not a radius of either object
768 PCB_DIM_RADIAL* split = addRadialDim( board, { 0, 0 }, { 55 * MM, 0 } );
770 { { split->m_Uuid, CONSTRAINT_ANCHOR::START }, { circleA->m_Uuid, CONSTRAINT_ANCHOR::CENTER } } );
772 { { split->m_Uuid, CONSTRAINT_ANCHOR::END }, { circleB->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
773
774 BOOST_CHECK( !DimensionEndpointsBound( &board, split ) );
775
776 // Both legs share a segment but a segment carries no radius to drive
777 PCB_DIM_RADIAL* onSegment = addRadialDim( board, { 5 * MM, 30 * MM }, { 10 * MM, 30 * MM } );
779 { { onSegment->m_Uuid, CONSTRAINT_ANCHOR::START }, { seg->m_Uuid, CONSTRAINT_ANCHOR::CENTER } } );
781 { { onSegment->m_Uuid, CONSTRAINT_ANCHOR::END }, { seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
782
783 BOOST_CHECK( !DimensionEndpointsBound( &board, onSegment ) );
784}
785
786
787// Rect stored end before start still binds VERTEX 0 to physical top-left not stored-start corner
788BOOST_AUTO_TEST_CASE( SwappedRectVertexZeroIsPhysicalTopLeft )
789{
790 BOARD board;
791 PCB_SHAPE* rect = addRect( board, { 10 * MM, 10 * MM }, { 0, 0 } ); // start is BR
792
793 PCB_SHAPE* seg = addSegment( board, { 20 * MM, 20 * MM }, { 2 * MM, 1 * MM } );
794
797 { { seg->m_Uuid, CONSTRAINT_ANCHOR::END }, { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 } } );
798
800 BOOST_REQUIRE( diag.solved );
801
802 // First member the segment end is pinned rect physical top-left snapped onto it opposite corner held
803 BOOST_CHECK_LE( ( seg->GetEnd() - VECTOR2I( 2 * MM, 1 * MM ) ).EuclideanNorm(), 5000.0 );
804 BOOST_CHECK_LE( ( rectTopLeft( rect ) - VECTOR2I( 2 * MM, 1 * MM ) ).EuclideanNorm(), 5000.0 );
805 BOOST_CHECK_LE( ( rectBottomRight( rect ) - VECTOR2I( 10 * MM, 10 * MM ) ).EuclideanNorm(), 5000.0 );
806}
807
808
809// Insert ahead of bound vertex shifts persisted index member keeps its corner footprint constraints remap too
810BOOST_AUTO_TEST_CASE( VertexInsertShiftsMembersPastInsertionPoint )
811{
812 BOARD board;
813
814 std::vector<VECTOR2I> pts = { { 0, 0 },
815 { 20 * MM, 5 * MM },
816 { 15 * MM, 20 * MM },
817 { 5 * MM, 20 * MM },
818 { -5 * MM, 10 * MM } };
819 PCB_SHAPE* poly = addPoly( board, pts );
820 PCB_SHAPE* seg = addSegment( board, pts[3], { 30 * MM, 0 } );
821
824 { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 3 } } );
825
826 FOOTPRINT* fp = new FOOTPRINT( &board );
827 board.Add( fp );
828
830 onFootprint->AddMember( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 4 );
831 fp->Add( onFootprint );
832
833 // Mutate outline the way addCorner does then remap with same edit description
834 poly->GetPolyShape().InsertVertex( 2, ( pts[1] + pts[2] ) / 2 );
835
836 std::vector<BOARD_ITEM*> modified;
837 std::vector<BOARD_ITEM*> removed;
838
839 RemapPolygonVertexMembers( &board, poly->m_Uuid, 2, 1,
840 [&]( BOARD_ITEM* aItem ) { modified.push_back( aItem ); },
841 [&]( BOARD_ITEM* aItem ) { removed.push_back( aItem ); } );
842
843 BOOST_CHECK( onBoard->GetMembers()[1] == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 4 ) );
844 BOOST_CHECK( onFootprint->GetMembers()[0] == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 5 ) );
845
846 // Remapped members resolve to the corners they were authored on
847 std::optional<VECTOR2I> boardPos = ConstraintAnchorPosition( &board, onBoard->GetMembers()[1] );
848 std::optional<VECTOR2I> fpPos = ConstraintAnchorPosition( &board, onFootprint->GetMembers()[0] );
849
850 BOOST_REQUIRE( boardPos.has_value() );
851 BOOST_CHECK_EQUAL( *boardPos, pts[3] );
852 BOOST_REQUIRE( fpPos.has_value() );
853 BOOST_CHECK_EQUAL( *fpPos, pts[4] );
854
855 // Both re-indexed constraints were staged an insertion never retires one
856 BOOST_CHECK_EQUAL( modified.size(), 2 );
857 BOOST_CHECK( removed.empty() );
858}
859
860
861// Delete vertex below bound one shifts index down still same physical corner
862BOOST_AUTO_TEST_CASE( VertexDeleteBelowShiftsMemberDown )
863{
864 BOARD board;
865
866 std::vector<VECTOR2I> pts = { { 0, 0 },
867 { 20 * MM, 5 * MM },
868 { 15 * MM, 20 * MM },
869 { 5 * MM, 20 * MM },
870 { -5 * MM, 10 * MM } };
871 PCB_SHAPE* poly = addPoly( board, pts );
872 PCB_SHAPE* seg = addSegment( board, pts[3], { 30 * MM, 0 } );
873
876 { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 3 } } );
877
878 // Mutate outline the way removeCorner does
879 poly->GetPolyShape().RemoveVertex( 1 );
880
881 std::vector<BOARD_ITEM*> removed;
882
883 RemapPolygonVertexMembers( &board, poly->m_Uuid, 1, -1,
884 []( BOARD_ITEM* ) {},
885 [&]( BOARD_ITEM* aItem ) { removed.push_back( aItem ); } );
886
887 BOOST_CHECK( c->GetMembers()[1] == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 2 ) );
888
889 std::optional<VECTOR2I> pos = ConstraintAnchorPosition( &board, c->GetMembers()[1] );
890
891 BOOST_REQUIRE( pos.has_value() );
892 BOOST_CHECK_EQUAL( *pos, pts[3] );
893 BOOST_CHECK( removed.empty() );
894}
895
896
897// Delete bound vertex itself leaves nothing to express whole constraint staged for removal
898BOOST_AUTO_TEST_CASE( VertexDeleteOfBoundVertexRetiresConstraint )
899{
900 BOARD board;
901
902 std::vector<VECTOR2I> pts = { { 0, 0 },
903 { 20 * MM, 5 * MM },
904 { 15 * MM, 20 * MM },
905 { 5 * MM, 20 * MM },
906 { -5 * MM, 10 * MM } };
907 PCB_SHAPE* poly = addPoly( board, pts );
908 PCB_SHAPE* seg = addSegment( board, pts[3], { 30 * MM, 0 } );
909
912 { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 3 } } );
913
914 poly->GetPolyShape().RemoveVertex( 3 );
915
916 std::vector<BOARD_ITEM*> modified;
917 std::vector<BOARD_ITEM*> removed;
918
919 RemapPolygonVertexMembers( &board, poly->m_Uuid, 3, -1,
920 [&]( BOARD_ITEM* aItem ) { modified.push_back( aItem ); },
921 [&]( BOARD_ITEM* aItem ) { removed.push_back( aItem ); } );
922
923 // Retiring constraint staged whole never edited undo keeps authored members
924 BOOST_REQUIRE_EQUAL( removed.size(), 1 );
925 BOOST_CHECK( removed[0] == c );
926 BOOST_CHECK( modified.empty() );
927 BOOST_CHECK( c->GetMembers()[1] == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 3 ) );
928
929 // Complete the edit the way the commit would
930 board.Remove( c );
931 delete c;
932
933 BOOST_CHECK( board.Constraints().empty() );
934}
935
936
937// Chamfer composes insert then delete order is load-bearing reversed order nets wrong shift
938BOOST_AUTO_TEST_CASE( ChamferRemapOrderNetsOneHigherPastTheCorner )
939{
940 BOARD board;
941
942 std::vector<VECTOR2I> pts = { { 0, 0 },
943 { 20 * MM, 5 * MM },
944 { 15 * MM, 20 * MM },
945 { 5 * MM, 20 * MM },
946 { -5 * MM, 10 * MM } };
947 PCB_SHAPE* poly = addPoly( board, pts );
948
949 const int k = 2;
950
952 { { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, k - 1 } } );
954 { { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, k } } );
956 { { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, k + 1 } } );
958 { { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, k },
959 { poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, k + 2 } } );
960
961 // Mutate outline the way chamferCorner does replacing corner k with two chamfer points
962 VECTOR2I a = pts[k] + ( pts[k - 1] - pts[k] ) / 4;
963 VECTOR2I b = pts[k] + ( pts[k + 1] - pts[k] ) / 4;
964
965 poly->GetPolyShape().RemoveVertex( k );
966 poly->GetPolyShape().InsertVertex( k, b );
967 poly->GetPolyShape().InsertVertex( k, a );
968
969 std::vector<BOARD_ITEM*> modified;
970 std::vector<BOARD_ITEM*> removed;
971
972 auto modify = [&]( BOARD_ITEM* aItem ) { modified.push_back( aItem ); };
973 auto remove = [&]( BOARD_ITEM* aItem ) { removed.push_back( aItem ); };
974
975 RemapPolygonVertexMembers( &board, poly->m_Uuid, k + 1, 2, modify, remove );
976 RemapPolygonVertexMembers( &board, poly->m_Uuid, k, -1, modify, remove );
977
978 BOOST_CHECK( below->GetMembers()[0] == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, k - 1 ) );
979 BOOST_CHECK( above->GetMembers()[0] == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, k + 2 ) );
980
981 // Netted member still resolves to its own corner one past the chamfer points
982 std::optional<VECTOR2I> pos = ConstraintAnchorPosition( &board, above->GetMembers()[0] );
983
984 BOOST_REQUIRE( pos.has_value() );
985 BOOST_CHECK_EQUAL( *pos, pts[k + 1] );
986
987 // Both constraints on the chamfered corner retire mixed one was Modify-staged first by insert pass
988 BOOST_REQUIRE_EQUAL( removed.size(), 2 );
989 BOOST_CHECK_EQUAL( std::ranges::count( removed, onCorner ), 1 );
990 BOOST_CHECK_EQUAL( std::ranges::count( removed, both ), 1 );
991 BOOST_CHECK_EQUAL( std::ranges::count( modified, both ), 1 );
992 BOOST_CHECK_EQUAL( std::ranges::count( modified, onCorner ), 0 );
993 BOOST_CHECK_EQUAL( std::ranges::count( modified, below ), 0 );
994 BOOST_CHECK( both->GetMembers()[0] == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, k ) );
995 BOOST_CHECK( both->GetMembers()[1] == CONSTRAINT_MEMBER( poly->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, k + 4 ) );
996}
997
998
999// Remapping scoped to edited polygon other shapes and lower indices never move
1000BOOST_AUTO_TEST_CASE( VertexRemapLeavesOtherShapesAndLowerIndicesAlone )
1001{
1002 BOARD board;
1003
1004 std::vector<VECTOR2I> pts = { { 0, 0 },
1005 { 20 * MM, 5 * MM },
1006 { 15 * MM, 20 * MM },
1007 { 5 * MM, 20 * MM },
1008 { -5 * MM, 10 * MM } };
1009 PCB_SHAPE* edited = addPoly( board, pts );
1010
1011 std::vector<VECTOR2I> otherPts = { { 40 * MM, 0 }, { 60 * MM, 0 }, { 60 * MM, 20 * MM }, { 40 * MM, 20 * MM } };
1012 PCB_SHAPE* other = addPoly( board, otherPts );
1013 PCB_SHAPE* rect = addRect( board, { 0, 40 * MM }, { 10 * MM, 50 * MM } );
1014 PCB_SHAPE* seg = addSegment( board, { 0, 60 * MM }, { 10 * MM, 60 * MM } );
1015
1017 { { other->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 3 } } );
1019 { { seg->m_Uuid, CONSTRAINT_ANCHOR::START },
1020 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 2 } } );
1022 { { seg->m_Uuid, CONSTRAINT_ANCHOR::END },
1023 { edited->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } } );
1024
1025 edited->GetPolyShape().InsertVertex( 2, ( pts[1] + pts[2] ) / 2 );
1026
1027 std::vector<BOARD_ITEM*> modified;
1028 std::vector<BOARD_ITEM*> removed;
1029
1030 RemapPolygonVertexMembers( &board, edited->m_Uuid, 2, 1,
1031 [&]( BOARD_ITEM* aItem ) { modified.push_back( aItem ); },
1032 [&]( BOARD_ITEM* aItem ) { removed.push_back( aItem ); } );
1033
1034 BOOST_CHECK( onOther->GetMembers()[0] == CONSTRAINT_MEMBER( other->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 3 ) );
1035 BOOST_CHECK( onRect->GetMembers()[1] == CONSTRAINT_MEMBER( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 2 ) );
1036 BOOST_CHECK( below->GetMembers()[1] == CONSTRAINT_MEMBER( edited->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 ) );
1037
1038 // No constraint needed re-indexing so nothing was staged
1039 BOOST_CHECK( modified.empty() );
1040 BOOST_CHECK( removed.empty() );
1041}
1042
1043
1044// Cardinal rotation keeps RECTANGLE corner roles re-canonicalize VERTEX 0 resolves to new top-left
1045BOOST_AUTO_TEST_CASE( CardinalRotationRecanonicalizesRectRoles )
1046{
1047 BOARD board;
1048 PCB_SHAPE* rect = addRect( board, { 2 * MM, 1 * MM }, { 12 * MM, 5 * MM } );
1049
1050 std::vector<CONSTRAINT_ANCHOR_POINT> before = ConstraintShapeAnchors( rect );
1051 BOOST_REQUIRE_EQUAL( before.size(), 4 );
1052
1053 const VECTOR2I pivot( 0, 0 );
1054 const EDA_ANGLE angle( 90, DEGREES_T );
1055
1056 rect->Rotate( pivot, angle );
1058
1059 // Physical corners after the turn from pre-rotation enumeration
1060 std::vector<VECTOR2I> rotated;
1061
1062 for( const CONSTRAINT_ANCHOR_POINT& a : before )
1063 {
1064 VECTOR2I p = a.pos;
1065 RotatePoint( p, pivot, angle );
1066 rotated.push_back( p );
1067 }
1068
1069 VECTOR2I tl = rotated[0];
1070 VECTOR2I br = rotated[0];
1071
1072 for( const VECTOR2I& p : rotated )
1073 {
1074 tl.x = std::min( tl.x, p.x );
1075 tl.y = std::min( tl.y, p.y );
1076 br.x = std::max( br.x, p.x );
1077 br.y = std::max( br.y, p.y );
1078 }
1079
1080 std::vector<CONSTRAINT_ANCHOR_POINT> after = ConstraintShapeAnchors( rect );
1081 BOOST_REQUIRE_EQUAL( after.size(), 4 );
1082 BOOST_CHECK_EQUAL( after[0].pos, tl );
1083 BOOST_CHECK_EQUAL( after[1].pos, VECTOR2I( br.x, tl.y ) );
1084 BOOST_CHECK_EQUAL( after[2].pos, br );
1085 BOOST_CHECK_EQUAL( after[3].pos, VECTOR2I( tl.x, br.y ) );
1086
1087 // Role semantics not corner tracking member resolves to new top-left a different physical corner
1088 std::optional<VECTOR2I> v0 =
1090
1091 BOOST_REQUIRE( v0.has_value() );
1092 BOOST_CHECK_EQUAL( *v0, tl );
1093 BOOST_CHECK( *v0 != rotated[0] );
1094}
1095
1096
1097// Non-cardinal rotation converts rect to a 4-vertex POLY under same KIID canonical order keeps bindings
1098BOOST_AUTO_TEST_CASE( NonCardinalRotationKeepsRectVertexBindings )
1099{
1100 BOARD board;
1101
1102 auto checkRect = [&]( PCB_SHAPE* rect )
1103 {
1104 std::vector<CONSTRAINT_ANCHOR_POINT> before = ConstraintShapeAnchors( rect );
1105 BOOST_REQUIRE_EQUAL( before.size(), 4 );
1106
1107 PCB_DIM_ALIGNED* dim = addAlignedDim( board, before[0].pos, before[1].pos );
1108
1109 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::START,
1110 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 } );
1111 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::END,
1112 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } );
1113
1114 BOOST_CHECK( DimensionEndpointsBound( &board, dim ) );
1115
1116 const VECTOR2I pivot = rect->GetCenter();
1117 const EDA_ANGLE angle( 30, DEGREES_T );
1118
1119 rect->Rotate( pivot, angle );
1120
1121 BOOST_REQUIRE( rect->GetShape() == SHAPE_T::POLY );
1122
1123 const SHAPE_LINE_CHAIN& outline = rect->GetPolyShape().COutline( 0 );
1124 BOOST_REQUIRE_EQUAL( outline.PointCount(), 4 );
1125 BOOST_CHECK_EQUAL( outline.ArcCount(), 0 );
1126
1127 for( int i = 0; i < 4; ++i )
1128 {
1129 VECTOR2I expected = before[i].pos;
1130 RotatePoint( expected, pivot, angle );
1131
1132 std::optional<VECTOR2I> pos =
1133 ConstraintAnchorPosition( &board, { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, i } );
1134
1135 BOOST_REQUIRE( pos.has_value() );
1136 BOOST_CHECK_LE( ( *pos - expected ).EuclideanNorm(), 2.0 ); // integer rounding only
1137 }
1138
1139 BOOST_CHECK( DimensionEndpointsBound( &board, dim ) );
1140 };
1141
1142 // One rect stored start at TL one end before start conversion normalizes both to same order
1143 checkRect( addRect( board, { 0, 0 }, { 10 * MM, 4 * MM } ) );
1144 checkRect( addRect( board, { 40 * MM, 4 * MM }, { 30 * MM, 0 } ) );
1145}
1146
1147
1148// Rounded rect rotated off-cardinal becomes arc-bearing outline gate rejects members degrade to unmapped
1149BOOST_AUTO_TEST_CASE( RoundedRectNonCardinalRotationDegradesToUnmapped )
1150{
1151 BOARD board;
1152 PCB_SHAPE* rect = addRect( board, { 0, 0 }, { 10 * MM, 6 * MM } );
1153 rect->SetCornerRadius( 1 * MM );
1154
1155 std::vector<CONSTRAINT_ANCHOR_POINT> before = ConstraintShapeAnchors( rect );
1156 BOOST_REQUIRE_EQUAL( before.size(), 4 );
1157
1158 PCB_DIM_ALIGNED* dim = addAlignedDim( board, before[0].pos, before[1].pos );
1159
1160 PCB_CONSTRAINT* startBinding = bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::START,
1161 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 } );
1162 PCB_CONSTRAINT* endBinding = bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::END,
1163 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } );
1164
1165 BOOST_CHECK( DimensionEndpointsBound( &board, dim ) );
1166
1167 rect->Rotate( { 0, 0 }, EDA_ANGLE( 30, DEGREES_T ) );
1168
1169 BOOST_REQUIRE( rect->GetShape() == SHAPE_T::POLY );
1170 BOOST_CHECK_GT( rect->GetPolyShape().COutline( 0 ).ArcCount(), 0 );
1171
1172 // No anchors no resolution no Driving offer
1173 BOOST_CHECK( ConstraintShapeAnchors( rect ).empty() );
1174 BOOST_CHECK( !ConstraintAnchorPosition( &board, { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 } ) );
1175 BOOST_CHECK( !DimensionEndpointsBound( &board, dim ) );
1176
1177 // Adapter leaves both bindings unmapped rather than poisoning the build
1178 std::vector<PCB_CONSTRAINT*> constraints( board.Constraints().begin(), board.Constraints().end() );
1180
1181 BOOST_REQUIRE( adapter.Build( { rect }, constraints, nullptr, { dim } ) );
1182
1183 const std::vector<KIID>& unmapped = adapter.UnmappedConstraints();
1184 BOOST_CHECK( std::find( unmapped.begin(), unmapped.end(), startBinding->m_Uuid ) != unmapped.end() );
1185 BOOST_CHECK( std::find( unmapped.begin(), unmapped.end(), endBinding->m_Uuid ) != unmapped.end() );
1186}
1187
1188
1189// Driving gate scans footprint-parented constraints too stale footprint binding revokes it
1190BOOST_AUTO_TEST_CASE( FootprintParentedBindingGatesDriving )
1191{
1192 BOARD board;
1193 FOOTPRINT* fp = new FOOTPRINT( &board );
1194 board.Add( fp );
1195
1196 PCB_SHAPE* rect = new PCB_SHAPE( fp, SHAPE_T::RECTANGLE );
1197 rect->SetStart( { 0, 0 } );
1198 rect->SetEnd( { 10 * MM, 10 * MM } );
1199 fp->Add( rect );
1200
1201 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 0, 0 }, { 10 * MM, 0 } );
1202
1203 auto bindOnFootprint = [&]( CONSTRAINT_ANCHOR aDimAnchor, int aCorner )
1204 {
1206 c->AddMember( dim->m_Uuid, aDimAnchor );
1207 c->AddMember( rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, aCorner );
1208 fp->Add( c );
1209 return c;
1210 };
1211
1212 bindOnFootprint( CONSTRAINT_ANCHOR::START, 0 );
1213 PCB_CONSTRAINT* endBinding = bindOnFootprint( CONSTRAINT_ANCHOR::END, 1 );
1214
1215 // Nothing lives at board level drivable verdict can only come from footprint scan
1216 BOOST_CHECK( board.Constraints().empty() );
1217 BOOST_CHECK( DimensionEndpointsBound( &board, dim ) );
1218
1219 // Stale vertex index on footprint-parented binding no longer resolves gate reports undrivable
1220 endBinding->Members()[1].m_index = 7;
1221 BOOST_CHECK( !DimensionEndpointsBound( &board, dim ) );
1222}
1223
1224
1225namespace
1226{
1227// Staging callbacks a live commit would provide recording touched items and completing add remove
1228struct MODE_STAGING
1229{
1230 BOARD& board;
1231 std::vector<BOARD_ITEM*> modified;
1232 std::vector<BOARD_ITEM*> added;
1233 std::vector<BOARD_ITEM*> removed;
1234
1235 std::function<void( BOARD_ITEM* )> modify()
1236 {
1237 return [this]( BOARD_ITEM* aItem ) { modified.push_back( aItem ); };
1238 }
1239
1240 std::function<void( BOARD_ITEM* )> add()
1241 {
1242 return [this]( BOARD_ITEM* aItem )
1243 {
1244 added.push_back( aItem );
1245 board.Add( aItem );
1246 };
1247 }
1248
1249 std::function<void( BOARD_ITEM* )> remove()
1250 {
1251 return [this]( BOARD_ITEM* aItem )
1252 {
1253 removed.push_back( aItem );
1254 board.Remove( aItem );
1255 delete aItem;
1256 };
1257 }
1258};
1259}
1260
1261
1262// Value mode derives from board state Driven default Arbitrary on override Driving needs self length
1263// Only aligned orthogonal radial dimensions carry a mode
1264BOOST_AUTO_TEST_CASE( ValueModeDerivation )
1265{
1266 BOARD board;
1267 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 0, 0 }, { 10 * MM, 0 } );
1268
1269 BOOST_CHECK( DimensionValueMode( &board, dim ) == DIM_VALUE_MODE::DRIVEN );
1270 BOOST_CHECK( dim->GetValueMode() == DIM_VALUE_MODE::DRIVEN );
1271
1272 dim->ChangeOverrideText( wxS( "custom" ) );
1273 BOOST_CHECK( DimensionValueMode( &board, dim ) == DIM_VALUE_MODE::ARBITRARY );
1274
1275 PCB_CONSTRAINT* reference = addConstraint(
1278 10.0 * MM );
1279 reference->SetDriving( false );
1280
1281 BOOST_CHECK( FindDimensionLengthConstraint( &board, dim ) == reference );
1282 BOOST_CHECK( DimensionValueMode( &board, dim ) == DIM_VALUE_MODE::ARBITRARY );
1283
1284 dim->SetOverrideTextEnabled( false );
1285 BOOST_CHECK( DimensionValueMode( &board, dim ) == DIM_VALUE_MODE::DRIVEN );
1286
1287 reference->SetDriving( true );
1288 BOOST_CHECK( DimensionValueMode( &board, dim ) == DIM_VALUE_MODE::DRIVING );
1289
1290 // Only value-bearing families carry the mode
1291 PCB_DIM_LEADER* leader = new PCB_DIM_LEADER( &board );
1292 board.Add( leader );
1293
1294 BOOST_CHECK( DimensionHasValueMode( dim ) );
1295 BOOST_CHECK( !DimensionHasValueMode( leader ) );
1296 BOOST_CHECK( !DimensionHasValueMode( nullptr ) );
1297}
1298
1299
1300// Mode transitions through shared setter Driven to Driving creates length Arbitrary installs text
1301// Driven again clears override every touched item goes through staging callbacks
1302BOOST_AUTO_TEST_CASE( SetValueModeTransitions )
1303{
1304 BOARD board;
1305 PCB_SHAPE* rect = addRect( board, { 0, 0 }, { 10 * MM, 10 * MM } );
1306
1307 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 0, 0 }, { 10 * MM, 0 } );
1308
1309 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::START,
1310 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 } );
1311 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::END,
1312 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } );
1313
1314 BOOST_REQUIRE( DimensionCanDrive( &board, dim ) );
1315
1316 // Driven to Driving creates the self driving length with the seeded value
1317 MODE_STAGING toDriving{ board };
1318 PCB_CONSTRAINT* driving =
1319 SetDimensionValueMode( &board, dim, DIM_VALUE_MODE::DRIVING, 12 * MM, std::nullopt,
1320 toDriving.modify(), toDriving.add(), toDriving.remove() );
1321
1322 BOOST_REQUIRE( driving );
1323 BOOST_CHECK( FindDimensionLengthConstraint( &board, dim ) == driving );
1324 BOOST_CHECK( driving->IsDriving() );
1325 BOOST_REQUIRE( driving->GetValue().has_value() );
1326 BOOST_CHECK_EQUAL( *driving->GetValue(), 12 * MM );
1327 BOOST_CHECK( DimensionValueMode( &board, dim ) == DIM_VALUE_MODE::DRIVING );
1328 BOOST_CHECK_EQUAL( toDriving.added.size(), 1 );
1329 BOOST_CHECK( toDriving.added[0] == driving );
1330 BOOST_CHECK( toDriving.removed.empty() );
1331
1332 // Driving to Arbitrary retires the driving length and installs custom text
1333 MODE_STAGING toArbitrary{ board };
1335 SetDimensionValueMode( &board, dim, DIM_VALUE_MODE::ARBITRARY, std::nullopt,
1336 wxString( wxS( "REF" ) ), toArbitrary.modify(), toArbitrary.add(),
1337 toArbitrary.remove() );
1338
1339 BOOST_CHECK( !result );
1340 BOOST_CHECK( !FindDimensionLengthConstraint( &board, dim ) );
1341 BOOST_CHECK( DimensionValueMode( &board, dim ) == DIM_VALUE_MODE::ARBITRARY );
1342 BOOST_CHECK_EQUAL( dim->GetOverrideText(), wxS( "REF" ) );
1343 BOOST_CHECK_EQUAL( toArbitrary.removed.size(), 1 );
1344
1345 // Arbitrary to Driven just clears the override bindings stay
1346 MODE_STAGING toDriven{ board };
1347 SetDimensionValueMode( &board, dim, DIM_VALUE_MODE::DRIVEN, std::nullopt, std::nullopt,
1348 toDriven.modify(), toDriven.add(), toDriven.remove() );
1349
1350 BOOST_CHECK( DimensionValueMode( &board, dim ) == DIM_VALUE_MODE::DRIVEN );
1351 BOOST_CHECK( !dim->GetOverrideTextEnabled() );
1352 BOOST_CHECK( toDriven.added.empty() );
1353 BOOST_CHECK( toDriven.removed.empty() );
1354 BOOST_CHECK( DimensionEndpointsBound( &board, dim ) );
1355}
1356
1357
1358// Driving transition rejected board untouched when unbound or length absent or non-positive
1359BOOST_AUTO_TEST_CASE( SetValueModeDrivingRejections )
1360{
1361 BOARD board;
1362 PCB_DIM_ALIGNED* unbound = addAlignedDim( board, { 0, 0 }, { 10 * MM, 0 } );
1363
1364 MODE_STAGING staging{ board };
1365
1366 BOOST_CHECK( !SetDimensionValueMode( &board, unbound, DIM_VALUE_MODE::DRIVING, 12 * MM,
1367 std::nullopt, staging.modify(), staging.add(),
1368 staging.remove() ) );
1369
1370 PCB_SHAPE* rect = addRect( board, { 0, 0 }, { 10 * MM, 10 * MM } );
1371 PCB_DIM_ALIGNED* bound = addAlignedDim( board, { 0, 0 }, { 10 * MM, 0 } );
1372
1373 bindEndpoint( board, bound->m_Uuid, CONSTRAINT_ANCHOR::START,
1374 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 } );
1375 bindEndpoint( board, bound->m_Uuid, CONSTRAINT_ANCHOR::END,
1376 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } );
1377
1378 BOOST_CHECK( !SetDimensionValueMode( &board, bound, DIM_VALUE_MODE::DRIVING, std::nullopt,
1379 std::nullopt, staging.modify(), staging.add(),
1380 staging.remove() ) );
1381 BOOST_CHECK( !SetDimensionValueMode( &board, bound, DIM_VALUE_MODE::DRIVING, 0, std::nullopt,
1382 staging.modify(), staging.add(), staging.remove() ) );
1383 BOOST_CHECK( !SetDimensionValueMode( &board, bound, DIM_VALUE_MODE::DRIVING, -5 * MM,
1384 std::nullopt, staging.modify(), staging.add(),
1385 staging.remove() ) );
1386
1387 // A leader never takes a value mode at all
1388 PCB_DIM_LEADER* leader = new PCB_DIM_LEADER( &board );
1389 board.Add( leader );
1390
1391 BOOST_CHECK( !SetDimensionValueMode( &board, leader, DIM_VALUE_MODE::DRIVING, 12 * MM,
1392 std::nullopt, staging.modify(), staging.add(),
1393 staging.remove() ) );
1394
1395 BOOST_CHECK( staging.modified.empty() );
1396 BOOST_CHECK( staging.added.empty() );
1397 BOOST_CHECK( staging.removed.empty() );
1398 BOOST_CHECK( !FindDimensionLengthConstraint( &board, unbound ) );
1399 BOOST_CHECK( !FindDimensionLengthConstraint( &board, bound ) );
1400 BOOST_CHECK( DimensionValueMode( &board, unbound ) == DIM_VALUE_MODE::DRIVEN );
1401 BOOST_CHECK( DimensionValueMode( &board, bound ) == DIM_VALUE_MODE::DRIVEN );
1402}
1403
1404
1405// Panel Driving flow end to end shared setter authors length solving drives rect to width
1406BOOST_AUTO_TEST_CASE( SetValueModeDrivingResolvesGeometry )
1407{
1408 BOARD board;
1409 PCB_SHAPE* rect = addRect( board, { 0, 0 }, { 10 * MM, 10 * MM } );
1410
1411 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 0, 0 }, { 10 * MM, 0 } ); // TL to TR
1412
1413 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::START,
1414 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 } );
1415 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::END,
1416 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } );
1417
1418 MODE_STAGING staging{ board };
1419 PCB_CONSTRAINT* driving =
1420 SetDimensionValueMode( &board, dim, DIM_VALUE_MODE::DRIVING, 15 * MM, std::nullopt,
1421 staging.modify(), staging.add(), staging.remove() );
1422
1423 BOOST_REQUIRE( driving );
1424
1425 CONSTRAINT_DIAGNOSIS diag = ApplyConstraintImmediately( &board, driving );
1426 BOOST_REQUIRE( diag.solved );
1427
1428 const VECTOR2I tl = rectTopLeft( rect );
1429 const VECTOR2I br = rectBottomRight( rect );
1430
1431 BOOST_CHECK_LE( std::abs( ( br.x - tl.x ) - 15 * MM ), 5000 ); // width driven to the value
1432 BOOST_CHECK_LE( std::abs( ( br.y - tl.y ) - 10 * MM ), 5000 ); // height untouched
1433 BOOST_CHECK_LE( tl.EuclideanNorm(), 5000.0 ); // pinned TL corner held
1434
1435 // Dimension endpoints ride the corners value field text reflects driving length not stale measurement
1436 BOOST_CHECK_LE( ( dim->GetStart() - tl ).EuclideanNorm(), 5000.0 );
1437 BOOST_CHECK_LE( ( dim->GetEnd() - VECTOR2I( br.x, tl.y ) ).EuclideanNorm(), 5000.0 );
1438 BOOST_CHECK( dim->GetValueMode() == DIM_VALUE_MODE::DRIVING );
1439}
1440
1441
1442// Property surface the docked panel drives Value Mode and Value exist only for value-bearing dims
1443// Value read-only while Driven validators gate an unbound Driving switch and non-positive length
1444BOOST_AUTO_TEST_CASE( ValueModePropertySurface )
1445{
1446 BOARD board;
1447 PCB_SHAPE* rect = addRect( board, { 0, 0 }, { 10 * MM, 10 * MM } );
1448
1449 PCB_DIM_ALIGNED* dim = addAlignedDim( board, { 0, 0 }, { 10 * MM, 0 } );
1450
1451 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::START,
1452 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 0 } );
1453 bindEndpoint( board, dim->m_Uuid, CONSTRAINT_ANCHOR::END,
1454 { rect->m_Uuid, CONSTRAINT_ANCHOR::VERTEX, 1 } );
1455
1456 PCB_DIM_LEADER* leader = new PCB_DIM_LEADER( &board );
1457 board.Add( leader );
1458
1460 propMgr.Rebuild();
1461
1462 PROPERTY_BASE* modeProp = propMgr.GetProperty( TYPE_HASH( PCB_DIMENSION_BASE ), _HKI( "Value Mode" ) );
1463 PROPERTY_BASE* valueProp = propMgr.GetProperty( TYPE_HASH( PCB_DIMENSION_BASE ), _HKI( "Value" ) );
1464
1465 BOOST_REQUIRE( modeProp );
1466 BOOST_REQUIRE( valueProp );
1467
1468 BOOST_CHECK( propMgr.IsAvailableFor( TYPE_HASH( PCB_DIM_ALIGNED ), modeProp, dim ) );
1469 BOOST_CHECK( propMgr.IsAvailableFor( TYPE_HASH( PCB_DIM_ALIGNED ), valueProp, dim ) );
1470 BOOST_CHECK( !propMgr.IsAvailableFor( TYPE_HASH( PCB_DIM_LEADER ), modeProp, leader ) );
1471 BOOST_CHECK( !propMgr.IsAvailableFor( TYPE_HASH( PCB_DIM_LEADER ), valueProp, leader ) );
1472
1473 // Driven shows the measured value read-only
1474 BOOST_CHECK( !propMgr.IsWriteableFor( TYPE_HASH( PCB_DIM_ALIGNED ), valueProp, dim ) );
1475 BOOST_CHECK_EQUAL( dim->GetValueFieldText(), dim->GetValueText() );
1476
1477 // A bound dimension may switch to Driving same switch on an unbound one is vetoed
1478 BOOST_CHECK( !modeProp->Validate( wxAny( static_cast<int>( DIM_VALUE_MODE::DRIVING ) ), dim ) );
1479
1480 PCB_DIM_ALIGNED* unbound = addAlignedDim( board, { 0, 20 * MM }, { 10 * MM, 20 * MM } );
1481
1482 BOOST_CHECK( modeProp->Validate( wxAny( static_cast<int>( DIM_VALUE_MODE::DRIVING ) ), unbound ) );
1483 BOOST_CHECK( !modeProp->Validate( wxAny( static_cast<int>( DIM_VALUE_MODE::ARBITRARY ) ), unbound ) );
1484
1485 // Arbitrary makes the value writable and verbatim through the property setter
1486 dim->ChangeValueMode( DIM_VALUE_MODE::ARBITRARY );
1487
1488 BOOST_CHECK( propMgr.IsWriteableFor( TYPE_HASH( PCB_DIM_ALIGNED ), valueProp, dim ) );
1489
1490 dim->ChangeValueFieldText( wxS( "10 mm MAX" ) );
1491 BOOST_CHECK_EQUAL( dim->GetValueFieldText(), wxS( "10 mm MAX" ) );
1492 BOOST_CHECK_EQUAL( dim->GetOverrideText(), wxS( "10 mm MAX" ) );
1493
1494 // Driving gates the value to a positive length and shows the constraint value
1495 dim->ChangeValueMode( DIM_VALUE_MODE::DRIVEN );
1496
1497 MODE_STAGING staging{ board };
1498 SetDimensionValueMode( &board, dim, DIM_VALUE_MODE::DRIVING, 12 * MM, std::nullopt,
1499 staging.modify(), staging.add(), staging.remove() );
1500
1501 BOOST_CHECK( valueProp->Validate( wxAny( wxString( wxS( "0" ) ) ), dim ) );
1502 BOOST_CHECK( valueProp->Validate( wxAny( wxString( wxS( "-3" ) ) ), dim ) );
1503 BOOST_CHECK( valueProp->Validate( wxAny( wxString( wxS( "text" ) ) ), dim ) );
1504 BOOST_CHECK( !valueProp->Validate( wxAny( wxString( wxS( "15" ) ) ), dim ) );
1505
1506 // A Driven dimension accepts anything the setter will ignore so no veto there
1507 BOOST_CHECK( !valueProp->Validate( wxAny( wxString( wxS( "0" ) ) ), unbound ) );
1508}
1509
1510
1511// Filleting a constrained corner puts an arc between the lines so each line binds to the arc
1512BOOST_AUTO_TEST_CASE( FilletRebindsCornerCoincidentToArc )
1513{
1514 BOARD board;
1515
1516 PCB_SHAPE* lineA = addSegment( board, VECTOR2I( 0, 0 ), VECTOR2I( 10 * MM, 0 ) );
1517 PCB_SHAPE* lineB = addSegment( board, VECTOR2I( 10 * MM, 0 ), VECTOR2I( 10 * MM, 10 * MM ) );
1518
1519 const CONSTRAINT_MEMBER cornerA( lineA->m_Uuid, CONSTRAINT_ANCHOR::END );
1520 const CONSTRAINT_MEMBER cornerB( lineB->m_Uuid, CONSTRAINT_ANCHOR::START );
1521
1522 addConstraint( board, PCB_CONSTRAINT_TYPE::COINCIDENT, { cornerB, cornerA } );
1523
1524 PCB_SHAPE* arc = nullptr;
1525
1527 [&]( std::unique_ptr<BOARD_ITEM> aItem )
1528 {
1529 if( aItem->Type() == PCB_SHAPE_T )
1530 arc = static_cast<PCB_SHAPE*>( aItem.get() );
1531
1532 board.Add( aItem.release() );
1533 },
1534 []( BOARD_ITEM& )
1535 {
1536 },
1537 [&]( BOARD_ITEM& aItem )
1538 {
1539 board.Remove( &aItem );
1540 } );
1541
1542 LINE_FILLET_ROUTINE fillet( &board, handler, 1 * MM );
1543 fillet.ProcessLinePair( *lineA, *lineB );
1544
1545 BOOST_REQUIRE( arc );
1546
1547 auto heldTogether = [&]( const CONSTRAINT_MEMBER& aFirst, const CONSTRAINT_MEMBER& aSecond )
1548 {
1549 for( const PCB_CONSTRAINT* constraint : board.Constraints() )
1550 {
1551 if( constraint->GetConstraintType() != PCB_CONSTRAINT_TYPE::COINCIDENT )
1552 continue;
1553
1554 const std::vector<CONSTRAINT_MEMBER>& members = constraint->GetMembers();
1555
1556 if( members.size() != 2 )
1557 continue;
1558
1559 if( ( members[0] == aFirst && members[1] == aSecond ) || ( members[0] == aSecond && members[1] == aFirst ) )
1560 {
1561 return true;
1562 }
1563 }
1564
1565 return false;
1566 };
1567
1568 const CONSTRAINT_MEMBER arcStart( arc->m_Uuid, CONSTRAINT_ANCHOR::START );
1569 const CONSTRAINT_MEMBER arcEnd( arc->m_Uuid, CONSTRAINT_ANCHOR::END );
1570
1571 // Which end of the arc meets which line is the routine's business
1572 BOOST_CHECK( ( heldTogether( arcStart, cornerA ) && heldTogether( arcEnd, cornerB ) )
1573 || ( heldTogether( arcStart, cornerB ) && heldTogether( arcEnd, cornerA ) ) );
1574
1575 // The two lines no longer touch so a constraint still joining them is false
1576 BOOST_CHECK( !heldTogether( cornerA, cornerB ) );
1577}
1578
1579
1580// Chamfering a constrained corner puts a segment between the lines so each line binds to it
1581BOOST_AUTO_TEST_CASE( ChamferRebindsCornerCoincidentToChamfer )
1582{
1583 BOARD board;
1584
1585 PCB_SHAPE* lineA = addSegment( board, VECTOR2I( 0, 0 ), VECTOR2I( 10 * MM, 0 ) );
1586 PCB_SHAPE* lineB = addSegment( board, VECTOR2I( 10 * MM, 0 ), VECTOR2I( 10 * MM, 10 * MM ) );
1587
1588 const CONSTRAINT_MEMBER cornerA( lineA->m_Uuid, CONSTRAINT_ANCHOR::END );
1589 const CONSTRAINT_MEMBER cornerB( lineB->m_Uuid, CONSTRAINT_ANCHOR::START );
1590
1591 addConstraint( board, PCB_CONSTRAINT_TYPE::COINCIDENT, { cornerB, cornerA } );
1592
1593 PCB_SHAPE* chamfer = nullptr;
1594
1596 [&]( std::unique_ptr<BOARD_ITEM> aItem )
1597 {
1598 if( aItem->Type() == PCB_SHAPE_T )
1599 chamfer = static_cast<PCB_SHAPE*>( aItem.get() );
1600
1601 board.Add( aItem.release() );
1602 },
1603 []( BOARD_ITEM& )
1604 {
1605 },
1606 [&]( BOARD_ITEM& aItem )
1607 {
1608 board.Remove( &aItem );
1609 } );
1610
1611 LINE_CHAMFER_ROUTINE chamferRoutine( &board, handler, CHAMFER_PARAMS{ 1 * MM, 1 * MM } );
1612 chamferRoutine.ProcessLinePair( *lineA, *lineB );
1613
1615
1616 auto heldTogether = [&]( const CONSTRAINT_MEMBER& aFirst, const CONSTRAINT_MEMBER& aSecond )
1617 {
1618 for( const PCB_CONSTRAINT* constraint : board.Constraints() )
1619 {
1620 if( constraint->GetConstraintType() != PCB_CONSTRAINT_TYPE::COINCIDENT )
1621 continue;
1622
1623 const std::vector<CONSTRAINT_MEMBER>& members = constraint->GetMembers();
1624
1625 if( members.size() != 2 )
1626 continue;
1627
1628 if( ( members[0] == aFirst && members[1] == aSecond ) || ( members[0] == aSecond && members[1] == aFirst ) )
1629 {
1630 return true;
1631 }
1632 }
1633
1634 return false;
1635 };
1636
1637 // The chamfer leaves each line running from its far end to the chamfer
1638 const CONSTRAINT_MEMBER newCornerA( lineA->m_Uuid, CONSTRAINT_ANCHOR::END );
1639 const CONSTRAINT_MEMBER newCornerB( lineB->m_Uuid, CONSTRAINT_ANCHOR::END );
1640
1641 BOOST_CHECK( heldTogether( { chamfer->m_Uuid, CONSTRAINT_ANCHOR::START }, newCornerA ) );
1642 BOOST_CHECK( heldTogether( { chamfer->m_Uuid, CONSTRAINT_ANCHOR::END }, newCornerB ) );
1643
1644 BOOST_CHECK( !heldTogether( cornerA, cornerB ) );
1645}
1646
1647
1648// A dogbone corner reaches each line with an arc, or with a slot segment on a narrow mouth
1649BOOST_AUTO_TEST_CASE( DogboneRebindsCornerCoincidentToInsertedShape )
1650{
1651 const auto runDogbone = []( const VECTOR2I& aFarB, bool aAddSlots, std::vector<PCB_SHAPE*>& aInserted,
1652 BOARD& aBoard ) -> std::pair<PCB_SHAPE*, PCB_SHAPE*>
1653 {
1654 PCB_SHAPE* lineA = addSegment( aBoard, VECTOR2I( 0, 0 ), VECTOR2I( 20 * MM, 0 ) );
1655 PCB_SHAPE* lineB = addSegment( aBoard, VECTOR2I( 20 * MM, 0 ), aFarB );
1656
1658 { { lineB->m_Uuid, CONSTRAINT_ANCHOR::START }, { lineA->m_Uuid, CONSTRAINT_ANCHOR::END } } );
1659
1661 [&]( std::unique_ptr<BOARD_ITEM> aItem )
1662 {
1663 if( aItem->Type() == PCB_SHAPE_T )
1664 aInserted.push_back( static_cast<PCB_SHAPE*>( aItem.get() ) );
1665
1666 aBoard.Add( aItem.release() );
1667 },
1668 []( BOARD_ITEM& )
1669 {
1670 },
1671 [&]( BOARD_ITEM& aItem )
1672 {
1673 aBoard.Remove( &aItem );
1674 } );
1675
1676 DOGBONE_CORNER_ROUTINE dogbone( &aBoard, handler, { 1 * MM, aAddSlots } );
1677 dogbone.ProcessLinePair( *lineA, *lineB );
1678
1679 return { lineA, lineB };
1680 };
1681
1682 auto boundPosition = []( BOARD& aBoard, const CONSTRAINT_MEMBER& aMember ) -> std::optional<VECTOR2I>
1683 {
1684 for( const PCB_CONSTRAINT* constraint : aBoard.Constraints() )
1685 {
1686 if( constraint->GetConstraintType() != PCB_CONSTRAINT_TYPE::COINCIDENT )
1687 continue;
1688
1689 const std::vector<CONSTRAINT_MEMBER>& members = constraint->GetMembers();
1690
1691 if( members.size() != 2 )
1692 continue;
1693
1694 if( members[0] == aMember )
1695 return ConstraintAnchorPosition( &aBoard, members[1] );
1696
1697 if( members[1] == aMember )
1698 return ConstraintAnchorPosition( &aBoard, members[0] );
1699 }
1700
1701 return std::nullopt;
1702 };
1703
1704 // A right angle needs no slot and gets a bare arc, a 45 degree corner gets two slots as well
1705 struct CORNER
1706 {
1707 VECTOR2I farB;
1708 bool addSlots;
1709 size_t inserted;
1710 };
1711
1712 const std::vector<CORNER> corners = { { { 20 * MM, 20 * MM }, false, 1 }, { { 10 * MM, 10 * MM }, true, 3 } };
1713
1714 for( const CORNER& corner : corners )
1715 {
1716 BOARD board;
1717 std::vector<PCB_SHAPE*> inserted;
1718
1719 auto [lineA, lineB] = runDogbone( corner.farB, corner.addSlots, inserted, board );
1720
1721 BOOST_REQUIRE_EQUAL( inserted.size(), corner.inserted );
1722
1723 // Whatever shape the corner inserted, it must be held to the line it reaches
1724 std::optional<VECTOR2I> atA = boundPosition( board, { lineA->m_Uuid, CONSTRAINT_ANCHOR::END } );
1725 std::optional<VECTOR2I> atB = boundPosition( board, { lineB->m_Uuid, CONSTRAINT_ANCHOR::END } );
1726
1727 BOOST_CHECK( atA && *atA == lineA->GetEnd() );
1728 BOOST_CHECK( atB && *atB == lineB->GetEnd() );
1729 }
1730}
1731
1732
int index
CONSTRAINT_DIAGNOSIS ApplyConstraintImmediately(BOARD *aBoard, const PCB_CONSTRAINT *aConstraint, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify, const std::set< KIID > &aFixedShapes)
Solve a just-created constraint's cluster so the geometry snaps to satisfy it (SolidWorks-style),...
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.
bool ReSolveShapeClustersHoldingEdited(BOARD *aBoard, const std::vector< PCB_SHAPE * > &aEditedShapes, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
Re-solve clusters whose new geometry is authoritative holding every edited shape fully fixed so only ...
BOARD_CONSTRAINT_DIAGNOSTICS DiagnoseBoardConstraints(BOARD *aBoard)
Diagnose every constraint cluster on the board (validate only – geometry is not changed) and return t...
Translates KiCad board geometry to and from the planegcs solver (issue #2329).
bool Solve(const CONSTRAINT_MEMBER &aDragged, const VECTOR2I &aCursor, bool aStabilize=false, const std::set< KIID > &aEdited={}, const std::optional< std::pair< CONSTRAINT_MEMBER, VECTOR2I > > &aCoDragged=std::nullopt, bool aHoldDraggedRigid=false)
Solve the system, pinning a dragged anchor to a cursor position.
const std::vector< KIID > & UnmappedConstraints() const
Constraints from the last Build() that could not be mapped onto a solver primitive (wrong member coun...
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.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
const CONSTRAINTS & Constraints() const
Geometric constraints (#2329) owned by this board.
Definition board.h:522
void Remove(BOARD_ITEM *aBoardItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
Definition board.cpp:1691
Pairwise add dogbone corners to an internal corner.
void ProcessLinePair(PCB_SHAPE &aLineA, PCB_SHAPE &aLineB) override
Perform the action on the pair of lines given.
const KIID m_Uuid
Definition eda_item.h:599
SHAPE_POLY_SET & GetPolyShape()
SHAPE_T GetShape() const
Definition eda_shape.h:177
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:333
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:283
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
A handler that is based on a set of callbacks provided by the user of the ITEM_MODIFICATION_ROUTINE.
Definition kiid.h:46
Pairwise line tool that adds a chamfer between the lines.
void ProcessLinePair(PCB_SHAPE &aLineA, PCB_SHAPE &aLineB) override
Perform the action on the pair of lines given.
Pairwise line tool that adds a fillet to the lines.
A geometric constraint between board items (issue #2329).
const std::vector< CONSTRAINT_MEMBER > & GetMembers() const
std::optional< double > GetValue() const
bool IsDriving() const
A driving constraint forces its value; a reference (non-driving) one only measures it.
void AddMember(const KIID &aItem, CONSTRAINT_ANCHOR aAnchor=CONSTRAINT_ANCHOR::WHOLE, int aIndex=-1)
void SetDriving(bool aDriving)
std::vector< CONSTRAINT_MEMBER > & Members()
Abstract dimension API.
void Update()
Update the dimension's cached text and geometry.
virtual void SetEnd(const VECTOR2I &aPoint)
virtual void SetStart(const VECTOR2I &aPoint)
void ChangeOverrideText(const wxString &aValue)
void SetOverrideTextEnabled(bool aOverride)
DIM_VALUE_MODE GetValueMode() const
Value mode from board state via DimensionValueMode.
virtual VECTOR2I GetEnd() const
virtual VECTOR2I GetStart() const
The dimension's origin is the first feature point for the dimension.
For better understanding of the points that make a dimension:
A leader is a dimension-like object pointing to a specific point.
An orthogonal dimension is like an aligned dimension, but the extension lines are locked to the X or ...
void SetOrientation(DIR aOrientation)
Set the orientation of the dimension line (so, perpendicular to the feature lines).
A radial dimension indicates either the radius or diameter of an arc or circle.
void SetCornerRadius(int aCornerRadius) override
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
void SetEnd(const VECTOR2I &aEnd) override
void Move(const VECTOR2I &aMoveVector) override
Move this object.
void SetStart(const VECTOR2I &aStart) override
VALIDATOR_RESULT Validate(const wxAny &&aValue, EDA_ITEM *aItem)
Definition property.h:386
Provide class metadata.
bool IsWriteableFor(TYPE_ID aItemClass, PROPERTY_BASE *aProp, INSPECTABLE *aItem)
Checks overriden availability and original availability of a property, returns false if the property ...
static PROPERTY_MANAGER & Instance()
void Rebuild()
Rebuild the list of all registered properties.
PROPERTY_BASE * GetProperty(TYPE_ID aType, const wxString &aProperty) const
Return a property for a specific type.
bool IsAvailableFor(TYPE_ID aItemClass, PROPERTY_BASE *aProp, INSPECTABLE *aItem)
Checks overriden availability and original availability of a property, returns false if the property ...
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
size_t ArcCount() const
void InsertVertex(int aGlobalIndex, const VECTOR2I &aNewVertex)
Adds a vertex in the globally indexed position aGlobalIndex.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
void RemoveVertex(int aGlobalIndex)
Delete the aGlobalIndex-th vertex.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
std::vector< ENDPOINT_BINDING > SelectEndpointBindings(const BOARD *aBoard, const KIID &aItem, const VECTOR2I &aStart, const std::optional< VECTOR2I > &aEnd, double aMaxDist, PCB_LAYER_ID aLayer)
Choose the coincident bindings a freshly drawn item endpoints should take so it tracks the geometry i...
void RemapPolygonVertexMembers(BOARD *aBoard, const KIID &aPoly, int aChangedIndex, int aDelta, const std::function< void(BOARD_ITEM *)> &aBeforeModify, const std::function< void(BOARD_ITEM *)> &aBeforeRemove)
Repoint persisted VERTEX constraint members after an outline edit of polygon aPoly inserts or removes...
bool DimensionHasValueMode(const PCB_DIMENSION_BASE *aDimension)
True for dimension types with a measured value aligned orthogonal or radial offering the Driven Drivi...
std::optional< CONSTRAINT_MEMBER > NearestConstraintAnchor(const BOARD *aBoard, const VECTOR2I &aPos, double aMaxDist, const std::vector< CONSTRAINT_MEMBER > &aExclude, PCB_LAYER_ID aLayer)
Find the constrainable-item anchor (a shape's segment/arc endpoint or centre, or a dimension's featur...
std::vector< CONSTRAINT_ANCHOR_POINT > ConstraintItemAnchors(const BOARD_ITEM *aItem)
The constraint anchors an item exposes.
bool DimensionCanDrive(const BOARD *aBoard, const PCB_DIMENSION_BASE *aDimension)
True when Driving mode may be offered for aDimension needs both endpoints bound via DimensionEndpoint...
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...
std::optional< KIID > SelectRadialDimensionTarget(const BOARD *aBoard, const KIID &aDimension, const VECTOR2I &aCenter, const VECTOR2I &aRim, double aMaxDist)
Single circle or arc a radial dimension binds to or std::nullopt.
DIM_VALUE_MODE DimensionValueMode(const BOARD *aBoard, const PCB_DIMENSION_BASE *aDimension)
The value mode aDimension is in, derived from state: a self driving length means Driving,...
PCB_CONSTRAINT * FindDimensionLengthConstraint(const BOARD *aBoard, const PCB_DIMENSION_BASE *aDimension)
Self FIXED_LENGTH constraint whose members are exactly aDimension START and END or nullptr the drivin...
std::optional< VECTOR2I > ConstraintAnchorPosition(const BOARD *aBoard, const CONSTRAINT_MEMBER &aMember)
Current location of a constraint member's anchor (its shape's START/END/CENTER, or a dimension's feat...
bool DimensionEndpointsBound(const BOARD *aBoard, const PCB_DIMENSION_BASE *aDimension)
True when both of aDimension measured endpoints are bound to anchors that still resolve a coincident ...
PCB_CONSTRAINT * SetDimensionValueMode(BOARD *aBoard, PCB_DIMENSION_BASE *aDimension, DIM_VALUE_MODE aMode, std::optional< int > aDrivingLengthIU, const std::optional< wxString > &aOverrideText, const std::function< void(BOARD_ITEM *)> &aBeforeModify, const std::function< void(BOARD_ITEM *)> &aStageAdd, const std::function< void(BOARD_ITEM *)> &aBeforeRemove)
Apply a value mode transition to aDimension Driving creates or updates the driving length with aDrivi...
static bool empty(const wxTextEntryBase *aCtrl)
@ DEGREES_T
Definition eda_angle.h:30
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
Some functions to handle hotkeys in KiCad.
Test utilities for timestamps.
PCB_SHAPE * addSegment(BOARD &aBoard, const VECTOR2I &aStart, const VECTOR2I &aEnd)
PCB_SHAPE * addPoly(BOARD &aBoard, const std::vector< VECTOR2I > &aPoints)
PCB_SHAPE * addCircle(BOARD &aBoard, const VECTOR2I &aCenter, int aRadius)
constexpr int MM
PCB_CONSTRAINT * addConstraint(BOARD &aBoard, PCB_CONSTRAINT_TYPE aType, const std::vector< CONSTRAINT_MEMBER > &aMembers, std::optional< double > aValue=std::nullopt)
PCB_SHAPE * addRect(BOARD &aBoard, const VECTOR2I &aStart, const VECTOR2I &aEnd)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
#define _HKI(x)
Definition page_info.cpp:40
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.
@ FIXED_POSITION
A point is locked at its current location.
@ COINCIDENT
Two points are made to coincide.
@ POINT_ON_LINE
A point lies on a segment's supporting line.
@ FIXED_LENGTH
A segment has a driving length value.
#define TYPE_HASH(x)
Macro to generate unique identifier for a type.
Definition property.h:74
static std::vector< std::string > split(const std::string &aStr, const std::string &aDelim)
Split the input string into a vector of output strings.
Board-wide diagnostics for the constraint overlay and info bar.
std::vector< KIID > errored
Invalid constraints (member missing, deleted, or of a kind incompatible with the type).
Parameters that define a simple chamfer operation.
A selectable feature of a shape (a segment endpoint, arc centre, ...) and its location.
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...
One of a drawn item feature points bound coincident to an object anchor by draw time auto constrain s...
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(DimensionAnchorsPerFamily)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
const SHAPE_LINE_CHAIN chain
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:227
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:94
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708