KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_constraint_apply.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
25
27
28#include <algorithm>
29
30#include <board.h>
31#include <pcb_shape.h>
32
35
37
38using namespace KI_TEST;
39
40
41BOOST_AUTO_TEST_SUITE( ConstraintApply )
42
43
44BOOST_AUTO_TEST_CASE( CoincidentSnapsSecondPointToFirst )
45{
46 BOARD board;
47
48 PCB_SHAPE* a = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
49 PCB_SHAPE* b = addSegment( board, { 11 * MM, 1 * MM }, { 20 * MM, 0 } );
50
51 // Bind a's END to b's START; pinning a's END should pull b's START onto it.
54 { b->m_Uuid, CONSTRAINT_ANCHOR::START } } );
55
56 std::vector<PCB_SHAPE*> modified;
57 CONSTRAINT_DIAGNOSIS diag = ApplyConstraintImmediately( &board, c, &modified );
58
59 BOOST_TEST( diag.solved );
60 BOOST_CHECK_EQUAL( a->GetEnd().x, 10 * MM ); // pinned, unmoved
61 BOOST_CHECK_EQUAL( a->GetEnd().y, 0 );
62 BOOST_CHECK_EQUAL( b->GetStart().x, 10 * MM ); // pulled onto a's END
63 BOOST_CHECK_EQUAL( b->GetStart().y, 0 );
64}
65
66
67BOOST_AUTO_TEST_CASE( SingleShapeFixedLengthStagesThePinnedShape )
68{
69 BOARD board;
70
71 // A 12mm segment driven to 8mm: pinning START keeps it put, END moves in. The pinned shape is
72 // the only shape, so it must be reported as modified (else the move would not be committed).
73 PCB_SHAPE* seg = addSegment( board, { 0, 0 }, { 12 * MM, 0 } );
75 { { seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } },
76 8.0 * MM );
77
78 std::vector<PCB_SHAPE*> modified;
79 CONSTRAINT_DIAGNOSIS diag = ApplyConstraintImmediately( &board, c, &modified );
80
81 BOOST_TEST( diag.solved );
82 BOOST_TEST( ( std::find( modified.begin(), modified.end(), seg ) != modified.end() ) );
83 BOOST_CHECK_EQUAL( seg->GetStart().x, 0 );
84 BOOST_CHECK_EQUAL( seg->GetEnd().x, 8 * MM );
85}
86
87
88BOOST_AUTO_TEST_CASE( CircleFirstMemberPinsCenterAndSolves )
89{
90 BOARD board;
91
92 // Concentric circles authored as WHOLE members: WHOLE must pin the circle's CENTER (its only
93 // anchor), not a non-existent START, or the solve silently does nothing.
94 PCB_SHAPE* a = addCircle( board, { 0, 0 }, 5 * MM );
95 PCB_SHAPE* b = addCircle( board, { 3 * MM, 0 }, 2 * MM );
98 { b->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
99
100 std::vector<PCB_SHAPE*> modified;
101 CONSTRAINT_DIAGNOSIS diag = ApplyConstraintImmediately( &board, c, &modified );
102
103 BOOST_TEST( diag.solved );
104 BOOST_CHECK_EQUAL( a->GetCenter().x, 0 ); // pinned
105 BOOST_CHECK_EQUAL( b->GetCenter().x, 0 ); // moved concentric with a
106 BOOST_CHECK_EQUAL( b->GetCenter().y, 0 );
107}
108
109
110// Issue 25049: creating "point on line" moved the line to the point. The picker always takes the
111// point first, so the line is member 1 and ConstraintReferenceShapes names it the frozen reference.
112BOOST_AUTO_TEST_CASE( PointOnLineMovesThePoint )
113{
114 BOARD board;
115
116 // Oblique, so a stretch and a travel land the far end in different places.
117 PCB_SHAPE* line = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
118 PCB_SHAPE* seg = addSegment( board, { 5 * MM, 3 * MM }, { 8 * MM, 7 * MM } );
119
120 PCB_CONSTRAINT* c =
122 { { seg->m_Uuid, CONSTRAINT_ANCHOR::START }, { line->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
123
124 std::vector<PCB_SHAPE*> modified;
126 ApplyConstraintImmediately( &board, c, &modified, {}, ConstraintReferenceShapes( &board, c ) );
127
128 BOOST_TEST( diag.solved );
129 BOOST_CHECK_EQUAL( line->GetStart(), VECTOR2I( 0, 0 ) ); // frozen, unmoved
130 BOOST_CHECK_EQUAL( line->GetEnd(), VECTOR2I( 10 * MM, 0 ) );
131 // The whole segment travels: same angle, same length, not stretched to reach.
132 BOOST_CHECK_LE( ( seg->GetStart() - VECTOR2I( 5 * MM, 0 ) ).EuclideanNorm(), 1000 );
133 BOOST_CHECK_LE( ( seg->GetEnd() - VECTOR2I( 8 * MM, 4 * MM ) ).EuclideanNorm(), 1000 );
134
135 BOOST_TEST( ( std::find( modified.begin(), modified.end(), seg ) != modified.end() ) );
136 BOOST_TEST( ( std::find( modified.begin(), modified.end(), line ) == modified.end() ) );
137}
138
139
140// Issue 25049, the same for "midpoint": the segment used to move so its midpoint met the point.
141BOOST_AUTO_TEST_CASE( MidpointMovesThePoint )
142{
143 BOARD board;
144
145 PCB_SHAPE* line = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
146 PCB_SHAPE* seg = addSegment( board, { 2 * MM, 4 * MM }, { 2 * MM, 9 * MM } );
147
148 PCB_CONSTRAINT* c =
150 { { seg->m_Uuid, CONSTRAINT_ANCHOR::START }, { line->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
151
152 std::vector<PCB_SHAPE*> modified;
154 ApplyConstraintImmediately( &board, c, &modified, {}, ConstraintReferenceShapes( &board, c ) );
155
156 BOOST_TEST( diag.solved );
157 BOOST_CHECK_EQUAL( line->GetStart(), VECTOR2I( 0, 0 ) ); // frozen, unmoved
158 BOOST_CHECK_EQUAL( line->GetEnd(), VECTOR2I( 10 * MM, 0 ) );
159
160 // The midpoint is solver-derived, so allow the same slack the sibling adapter tests use.
161 BOOST_CHECK_LE( ( seg->GetStart() - VECTOR2I( 5 * MM, 0 ) ).EuclideanNorm(), 1000 );
162 BOOST_CHECK_LE( ( seg->GetEnd() - VECTOR2I( 5 * MM, 5 * MM ) ).EuclideanNorm(), 1000 );
163
164 BOOST_TEST( ( std::find( modified.begin(), modified.end(), line ) == modified.end() ) );
165}
166
167
168// A circle reference freezes centre and radius, so the point lands on the circumference.
169BOOST_AUTO_TEST_CASE( PointOnCircleMovesThePoint )
170{
171 BOARD board;
172
173 PCB_SHAPE* circle = addCircle( board, { 0, 0 }, 5 * MM );
174 PCB_SHAPE* seg = addSegment( board, { 8 * MM, 0 }, { 12 * MM, 0 } );
175
178 { { seg->m_Uuid, CONSTRAINT_ANCHOR::START }, { circle->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
179
180 std::vector<PCB_SHAPE*> modified;
182 ApplyConstraintImmediately( &board, c, &modified, {}, ConstraintReferenceShapes( &board, c ) );
183
184 BOOST_TEST( diag.solved );
185 BOOST_CHECK_EQUAL( circle->GetCenter(), VECTOR2I( 0, 0 ) ); // frozen, unmoved
186 BOOST_CHECK_EQUAL( circle->GetRadius(), 5 * MM );
187 BOOST_CHECK_LE( std::abs( ( seg->GetStart() - circle->GetCenter() ).EuclideanNorm() - 5.0 * MM ), 1000.0 );
188
189 // Travelled whole, so it kept its 4mm length.
190 BOOST_CHECK_LE( std::abs( ( seg->GetEnd() - seg->GetStart() ).EuclideanNorm() - 4.0 * MM ), 1000.0 );
191}
192
193
194// A corridor pin looks just like an authored constraint, but its line is the segment just drawn.
195// The drawing tool names no reference, so the plain apply still pins member 0 and pulls the drawn
196// line onto the existing anchor.
197BOOST_AUTO_TEST_CASE( AutoBindingKeepsPinningTheFirstMember )
198{
199 BOARD board;
200
201 PCB_SHAPE* existing = addSegment( board, { 5 * MM, 0 }, { 5 * MM, 10 * MM } );
202 PCB_SHAPE* drawn = addSegment( board, { 0, 1 * MM }, { 10 * MM, 1 * MM } );
203
206 { { existing->m_Uuid, CONSTRAINT_ANCHOR::START }, { drawn->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
207
208 std::vector<PCB_SHAPE*> modified;
209 CONSTRAINT_DIAGNOSIS diag = ApplyConstraintImmediately( &board, c, &modified );
210
211 // Soft-pinned rather than frozen, so allow the solver's slack here.
212 BOOST_TEST( diag.solved );
213 BOOST_CHECK_LE( ( existing->GetStart() - VECTOR2I( 5 * MM, 0 ) ).EuclideanNorm(), 1000 );
214 BOOST_CHECK_LE( ( existing->GetEnd() - VECTOR2I( 5 * MM, 10 * MM ) ).EuclideanNorm(), 1000 );
215 BOOST_CHECK_LE( std::abs( drawn->GetStart().y ), 1000 ); // drawn line pulled onto it
216 BOOST_CHECK_LE( std::abs( drawn->GetEnd().y ), 1000 );
217}
218
219
220// Holding the moved shape whole pushes the give onto a neighbour tied to it, which stretches.
221BOOST_AUTO_TEST_CASE( CoincidentNeighbourTakesTheStretch )
222{
223 BOARD board;
224
225 PCB_SHAPE* line = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
226 PCB_SHAPE* seg = addSegment( board, { 5 * MM, 3 * MM }, { 5 * MM, 8 * MM } );
227 PCB_SHAPE* neighbour = addSegment( board, { 5 * MM, 8 * MM }, { 9 * MM, 8 * MM } );
228
230 { { seg->m_Uuid, CONSTRAINT_ANCHOR::END }, { neighbour->m_Uuid, CONSTRAINT_ANCHOR::START } } );
231
232 PCB_CONSTRAINT* c =
234 { { seg->m_Uuid, CONSTRAINT_ANCHOR::START }, { line->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
235
237 ApplyConstraintImmediately( &board, c, nullptr, {}, ConstraintReferenceShapes( &board, c ) );
238
239 BOOST_TEST( diag.solved );
240 BOOST_CHECK_LE( ( seg->GetStart() - VECTOR2I( 5 * MM, 0 ) ).EuclideanNorm(), 1000 );
241
242 // seg kept its length, so the corner came down with it and the neighbour follows.
243 BOOST_CHECK_LE( ( seg->GetEnd() - VECTOR2I( 5 * MM, 5 * MM ) ).EuclideanNorm(), 1000 );
244 BOOST_CHECK_LE( ( neighbour->GetStart() - seg->GetEnd() ).EuclideanNorm(), 1000 );
245}
246
247
248// Freezing the line would leave nothing able to move, so these keep the old behaviour instead.
249BOOST_AUTO_TEST_CASE( NoReferenceWhenThePointCannotMove )
250{
251 BOARD board;
252
253 PCB_SHAPE* line = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
254 PCB_SHAPE* locked = addSegment( board, { 5 * MM, 3 * MM }, { 5 * MM, 8 * MM } );
255 PCB_SHAPE* pinned = addSegment( board, { 2 * MM, 3 * MM }, { 2 * MM, 8 * MM } );
256
257 locked->SetLocked( true );
259
260 PCB_CONSTRAINT* onLocked = addConstraint(
262 { { locked->m_Uuid, CONSTRAINT_ANCHOR::START }, { line->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
263 PCB_CONSTRAINT* onPinned = addConstraint(
265 { { pinned->m_Uuid, CONSTRAINT_ANCHOR::START }, { line->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
266
267 BOOST_TEST( ConstraintReferenceShapes( &board, onLocked ).empty() );
268 BOOST_TEST( ConstraintReferenceShapes( &board, onPinned ).empty() );
269}
270
271
272// Every other constraint keeps pinning its first member, and so does a self-referencing one.
273BOOST_AUTO_TEST_CASE( NoReferenceForOtherConstraints )
274{
275 BOARD board;
276
277 PCB_SHAPE* a = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
278 PCB_SHAPE* b = addSegment( board, { 0, 5 * MM }, { 10 * MM, 6 * MM } );
279
280 PCB_CONSTRAINT* parallel =
282 { { a->m_Uuid, CONSTRAINT_ANCHOR::WHOLE }, { b->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
283 PCB_CONSTRAINT* oneMember =
285 PCB_CONSTRAINT* itself =
288
289 BOOST_TEST( ConstraintReferenceShapes( &board, parallel ).empty() );
290 BOOST_TEST( ConstraintReferenceShapes( &board, oneMember ).empty() );
291 BOOST_TEST( ConstraintReferenceShapes( &board, itself ).empty() );
292}
293
294
295BOOST_AUTO_TEST_CASE( FailedOrEmptyClusterLeavesGeometry )
296{
297 BOARD board;
298
299 // A constraint with no members cannot pin or solve anything.
301
302 std::vector<PCB_SHAPE*> modified;
303 CONSTRAINT_DIAGNOSIS diag = ApplyConstraintImmediately( &board, &empty, &modified );
304
305 BOOST_TEST( !diag.solved );
306 BOOST_TEST( modified.empty() );
307}
308
309
std::set< KIID > ConstraintReferenceShapes(BOARD *aBoard, const PCB_CONSTRAINT *aConstraint)
The shapes a just-authored constraint should treat as an immovable reference, for the caller to pass ...
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),...
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
const KIID m_Uuid
Definition eda_item.h:531
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
A geometric constraint between board items (issue #2329).
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:78
static bool empty(const wxTextEntryBase *aCtrl)
PCB_SHAPE * addSegment(BOARD &aBoard, const VECTOR2I &aStart, const VECTOR2I &aEnd)
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)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ 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.
@ CONCENTRIC
Two arcs/circles share a center.
@ FIXED_POSITION
A point is locked at its current location.
@ COINCIDENT
Two points are made to coincide.
@ MIDPOINT
A point is the midpoint of a segment.
@ POINT_ON_LINE
A point lies on a segment's supporting line.
@ FIXED_LENGTH
A segment has a driving length value.
@ PARALLEL
Two segments are parallel.
The outcome of a constraint solve, in plain data so callers need not know planegcs.
bool solved
Solver reached Success or Converged.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(CoincidentSnapsSecondPointToFirst)
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST(netlist.find("R_G1 ARM_OUT1 DIE_B R='0.001 / ((SW_STATE)") !=std::string::npos)
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