KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_constraint_auto.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
24
26
27#include <memory>
28#include <vector>
29
30#include <board.h>
31#include <pcb_shape.h>
32
35
37
38using namespace KI_TEST;
39
40namespace
41{
42// Offsets sized against the production tolerances 0.01mm bind and 0.25mm corridor
43constexpr int NEAR_MISS = MM / 10; // inside the corridor but outside the bind tolerance
44constexpr int FAR_MISS = MM / 2; // outside the corridor
45
46
47// The drawn shape stays off the board as during interactive draw
48std::unique_ptr<PCB_SHAPE> drawnSegment( BOARD& aBoard, const VECTOR2I& aStart, const VECTOR2I& aEnd )
49{
50 auto s = std::make_unique<PCB_SHAPE>( &aBoard, SHAPE_T::SEGMENT );
51 s->SetStart( aStart );
52 s->SetEnd( aEnd );
53 return s;
54}
55
56
57std::unique_ptr<PCB_SHAPE> drawnArc( BOARD& aBoard, const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
58{
59 auto s = std::make_unique<PCB_SHAPE>( &aBoard, SHAPE_T::ARC );
60 s->SetArcGeometry( aStart, aMid, aEnd );
61 return s;
62}
63
64
65std::unique_ptr<PCB_SHAPE> drawnCircle( BOARD& aBoard, const VECTOR2I& aCenter, int aRadius )
66{
67 auto s = std::make_unique<PCB_SHAPE>( &aBoard, SHAPE_T::CIRCLE );
68 s->SetCenter( aCenter );
69 s->SetRadius( aRadius );
70 return s;
71}
72} // namespace
73
74
75BOOST_AUTO_TEST_SUITE( ConstraintAuto )
76
77
78// A segment drawn from an existing endpoint binds one coincident and needs no solve
79BOOST_AUTO_TEST_CASE( EndpointBindsCoincident )
80{
81 BOARD board;
82 PCB_SHAPE* target = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
83
84 auto drawn = drawnSegment( board, { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
85 auto picks = SelectShapeAutoConstraints( &board, drawn.get(), &board, false );
86
87 BOOST_REQUIRE_EQUAL( picks.size(), 1 );
88 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::COINCIDENT );
89 BOOST_CHECK( !picks[0].needsSolve );
90
91 const std::vector<CONSTRAINT_MEMBER>& members = picks[0].constraint->GetMembers();
92 BOOST_REQUIRE_EQUAL( members.size(), 2 );
93 BOOST_CHECK( members[0] == CONSTRAINT_MEMBER( drawn->m_Uuid, CONSTRAINT_ANCHOR::START ) );
94 BOOST_CHECK( members[1] == CONSTRAINT_MEMBER( target->m_Uuid, CONSTRAINT_ANCHOR::END ) );
95}
96
97
98// An endpoint on a segment midpoint binds midpoint not point on line
99BOOST_AUTO_TEST_CASE( MidpointUpgrade )
100{
101 BOARD board;
102 PCB_SHAPE* target = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
103
104 auto drawn = drawnSegment( board, { 5 * MM, 0 }, { 5 * MM, 8 * MM } );
105 auto picks = SelectShapeAutoConstraints( &board, drawn.get(), &board, false );
106
107 BOOST_REQUIRE_EQUAL( picks.size(), 1 );
108 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::MIDPOINT );
109
110 const std::vector<CONSTRAINT_MEMBER>& members = picks[0].constraint->GetMembers();
111 BOOST_CHECK( members[0] == CONSTRAINT_MEMBER( drawn->m_Uuid, CONSTRAINT_ANCHOR::START ) );
112 BOOST_CHECK( members[1] == CONSTRAINT_MEMBER( target->m_Uuid, CONSTRAINT_ANCHOR::WHOLE ) );
113}
114
115
116// An endpoint elsewhere on the outline binds point on line
117BOOST_AUTO_TEST_CASE( PointOnLineFallback )
118{
119 BOARD board;
120 PCB_SHAPE* target = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
121
122 auto drawn = drawnSegment( board, { 3 * MM, 0 }, { 3 * MM, 8 * MM } );
123 auto picks = SelectShapeAutoConstraints( &board, drawn.get(), &board, false );
124
125 BOOST_REQUIRE_EQUAL( picks.size(), 1 );
126 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::POINT_ON_LINE );
127 BOOST_CHECK( picks[0].constraint->GetMembers()[1]
129}
130
131
132// An arc leaving a segment end near tangency adds a tangent that needs the snap solve
133BOOST_AUTO_TEST_CASE( TangentWithinThreshold )
134{
135 BOARD board;
136 PCB_SHAPE* target = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
137
138 // Centre above the shared point so the arc leaves horizontally exactly tangent
139 // The mid sits 45 degrees around the 5mm radius from the centre at (10, 5) mm
140 auto drawn = drawnArc( board, { 10 * MM, 0 }, { 10 * MM + 3536000, 5 * MM - 3536000 }, { 15 * MM, 5 * MM } );
141 auto picks = SelectShapeAutoConstraints( &board, drawn.get(), &board, false );
142
143 BOOST_REQUIRE_EQUAL( picks.size(), 2 );
144 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::COINCIDENT );
145 BOOST_CHECK( picks[1].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::TANGENT );
146 BOOST_CHECK( picks[1].needsSolve );
147
148 // Target first so the snap solve pins the board and rotates the drawn arc
149 const std::vector<CONSTRAINT_MEMBER>& members = picks[1].constraint->GetMembers();
150 BOOST_CHECK( members[0] == CONSTRAINT_MEMBER( target->m_Uuid, CONSTRAINT_ANCHOR::WHOLE ) );
151 BOOST_CHECK( members[1] == CONSTRAINT_MEMBER( drawn->m_Uuid, CONSTRAINT_ANCHOR::WHOLE ) );
152}
153
154
155// An arc leaving well off tangency binds only the coincident
156BOOST_AUTO_TEST_CASE( TangentRejectedBeyondThreshold )
157{
158 BOARD board;
159 addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
160
161 // Centre at (13, 4) mm makes the leave angle about 37 degrees off the segment
162 auto drawn = drawnArc( board, { 10 * MM, 0 }, { 13 * MM, -1 * MM }, { 18 * MM, 4 * MM } );
163 auto picks = SelectShapeAutoConstraints( &board, drawn.get(), &board, false );
164
165 BOOST_REQUIRE_EQUAL( picks.size(), 1 );
166 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::COINCIDENT );
167}
168
169
170// Collinear chained segments never read as tangent
171BOOST_AUTO_TEST_CASE( SegmentsNeverTangent )
172{
173 BOARD board;
174 addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
175
176 auto drawn = drawnSegment( board, { 10 * MM, 0 }, { 20 * MM, 0 } );
177 auto picks = SelectShapeAutoConstraints( &board, drawn.get(), &board, false );
178
179 BOOST_REQUIRE_EQUAL( picks.size(), 1 );
180 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::COINCIDENT );
181}
182
183
184// An anchor a near miss inside the corridor binds point on line and needs the snap solve
185BOOST_AUTO_TEST_CASE( CorridorBindsNearMiss )
186{
187 BOARD board;
188 PCB_SHAPE* target = addSegment( board, { 5 * MM, 5 * MM }, { 5 * MM, 15 * MM } );
189
190 auto drawn = drawnSegment( board, { 0, 15 * MM + NEAR_MISS }, { 10 * MM, 15 * MM + NEAR_MISS } );
191 auto picks = SelectShapeAutoConstraints( &board, drawn.get(), &board, false );
192
193 BOOST_REQUIRE_EQUAL( picks.size(), 1 );
194 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::POINT_ON_LINE );
195 BOOST_CHECK( picks[0].needsSolve );
196
197 // The existing anchor first so the snap solve pins it and pulls the drawn line through
198 const std::vector<CONSTRAINT_MEMBER>& members = picks[0].constraint->GetMembers();
199 BOOST_CHECK( members[0] == CONSTRAINT_MEMBER( target->m_Uuid, CONSTRAINT_ANCHOR::END ) );
200 BOOST_CHECK( members[1] == CONSTRAINT_MEMBER( drawn->m_Uuid, CONSTRAINT_ANCHOR::WHOLE ) );
201}
202
203
204// Beyond the corridor nothing binds
205BOOST_AUTO_TEST_CASE( CorridorIgnoresFarMiss )
206{
207 BOARD board;
208 addSegment( board, { 5 * MM, 5 * MM }, { 5 * MM, 15 * MM } );
209
210 auto drawn = drawnSegment( board, { 0, 15 * MM + FAR_MISS }, { 10 * MM, 15 * MM + FAR_MISS } );
211 auto picks = SelectShapeAutoConstraints( &board, drawn.get(), &board, false );
212
213 BOOST_CHECK( picks.empty() );
214}
215
216
217// An anchor near the drawn endpoint belongs to the coincident pass not the corridor
218BOOST_AUTO_TEST_CASE( CorridorExcludesDrawnEndpoints )
219{
220 BOARD board;
221 addSegment( board, { 5 * MM, 5 * MM }, { 5 * MM, 15 * MM } );
222
223 // Starts 0.1mm from the target end which is too far to coincide and excluded from the corridor
224 auto drawn = drawnSegment( board, { 5 * MM + NEAR_MISS, 15 * MM }, { 15 * MM, 15 * MM } );
225 auto picks = SelectShapeAutoConstraints( &board, drawn.get(), &board, false );
226
227 BOOST_CHECK( picks.empty() );
228}
229
230
231// Axis aligned segments keep their axis only when the mode asks for it
232BOOST_AUTO_TEST_CASE( AxisAlignedBindsInConstrainedMode )
233{
234 BOARD board;
235
236 auto horizontal = drawnSegment( board, { 0, 0 }, { 10 * MM, 0 } );
237 auto picks = SelectShapeAutoConstraints( &board, horizontal.get(), &board, true );
238
239 BOOST_REQUIRE_EQUAL( picks.size(), 1 );
240 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::HORIZONTAL );
241
242 auto vertical = drawnSegment( board, { 0, 0 }, { 0, 10 * MM } );
243 picks = SelectShapeAutoConstraints( &board, vertical.get(), &board, true );
244
245 BOOST_REQUIRE_EQUAL( picks.size(), 1 );
246 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::VERTICAL );
247
248 picks = SelectShapeAutoConstraints( &board, horizontal.get(), &board, false );
249 BOOST_CHECK( picks.empty() );
250
251 auto diagonal = drawnSegment( board, { 0, 0 }, { 10 * MM, 10 * MM } );
252 picks = SelectShapeAutoConstraints( &board, diagonal.get(), &board, true );
253 BOOST_CHECK( picks.empty() );
254}
255
256
257// A circle centre binds concentric on a centre coincident on an anchor and point on line on an outline
258BOOST_AUTO_TEST_CASE( CentreCascade )
259{
260 BOARD board;
261 PCB_SHAPE* circle = addCircle( board, { 0, 0 }, 5 * MM );
262 PCB_SHAPE* segment = addSegment( board, { 20 * MM, 0 }, { 30 * MM, 0 } );
263
264 auto onCentre = drawnCircle( board, { 0, 0 }, 8 * MM );
265 auto picks = SelectShapeAutoConstraints( &board, onCentre.get(), &board, false );
266
267 BOOST_REQUIRE_EQUAL( picks.size(), 1 );
268 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::CONCENTRIC );
269 BOOST_CHECK( picks[0].constraint->GetMembers()[0]
271
272 auto onEndpoint = drawnCircle( board, { 30 * MM, 0 }, 2 * MM );
273 picks = SelectShapeAutoConstraints( &board, onEndpoint.get(), &board, false );
274
275 BOOST_REQUIRE_EQUAL( picks.size(), 1 );
276 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::COINCIDENT );
277 BOOST_CHECK( picks[0].constraint->GetMembers()[0]
278 == CONSTRAINT_MEMBER( onEndpoint->m_Uuid, CONSTRAINT_ANCHOR::CENTER ) );
279
280 auto onOutline = drawnCircle( board, { 23 * MM, 0 }, 2 * MM );
281 picks = SelectShapeAutoConstraints( &board, onOutline.get(), &board, false );
282
283 BOOST_REQUIRE_EQUAL( picks.size(), 1 );
284 BOOST_CHECK( picks[0].constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::POINT_ON_LINE );
285 BOOST_CHECK( picks[0].constraint->GetMembers()[1]
286 == CONSTRAINT_MEMBER( segment->m_Uuid, CONSTRAINT_ANCHOR::WHOLE ) );
287}
288
289
290// A chord drawn across a shallow arc binds both ends but authors the tangent only once
291BOOST_AUTO_TEST_CASE( ChordAuthorsSingleTangent )
292{
293 BOARD board;
294
295 // Radius 60mm over a 20mm chord leaves both ends about 9.6 degrees off the chord
296 // The mid sags by 60 - sqrt(60^2 - 10^2) mm below the chord
297 const int sagitta = 839200;
298
299 addArc( board, { 0, 0 }, { 10 * MM, -sagitta }, { 20 * MM, 0 } );
300
301 auto drawn = drawnSegment( board, { 0, 0 }, { 20 * MM, 0 } );
302 auto picks = SelectShapeAutoConstraints( &board, drawn.get(), &board, false );
303
304 int coincident = 0;
305 int tangent = 0;
306
307 for( const AUTO_CONSTRAINT& pick : picks )
308 {
309 if( pick.constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::COINCIDENT )
310 coincident++;
311 else if( pick.constraint->GetConstraintType() == PCB_CONSTRAINT_TYPE::TANGENT )
312 tangent++;
313 }
314
315 BOOST_CHECK_EQUAL( coincident, 2 );
316 BOOST_CHECK_EQUAL( tangent, 1 );
317}
318
319
320// A binding equal to one already on the board is not authored again
321BOOST_AUTO_TEST_CASE( DuplicateNotReauthored )
322{
323 BOARD board;
324 PCB_SHAPE* target = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
325
326 auto drawn = drawnSegment( board, { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
327
329 existing->AddMember( drawn->m_Uuid, CONSTRAINT_ANCHOR::START );
330 existing->AddMember( target->m_Uuid, CONSTRAINT_ANCHOR::END );
331 board.Add( existing );
332
333 auto picks = SelectShapeAutoConstraints( &board, drawn.get(), &board, false );
334
335 BOOST_CHECK( picks.empty() );
336}
337
338
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1355
const KIID m_Uuid
Definition eda_item.h:531
A geometric constraint between board items (issue #2329).
std::vector< AUTO_CONSTRAINT > SelectShapeAutoConstraints(BOARD *aBoard, const PCB_SHAPE *aShape, BOARD_ITEM *aParent, bool aAxisConstraint)
Choose the constraints a freshly drawn shape should get from what its features landed on.
@ SEGMENT
Definition eda_shape.h:46
PCB_SHAPE * addSegment(BOARD &aBoard, const VECTOR2I &aStart, const VECTOR2I &aEnd)
PCB_SHAPE * addArc(BOARD &aBoard, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
PCB_SHAPE * addCircle(BOARD &aBoard, const VECTOR2I &aCenter, int aRadius)
constexpr int MM
@ WHOLE
The item as a whole (a segment as a line, a circle).
@ START
First endpoint of a segment or arc.
@ END
Second endpoint of a segment or arc.
@ CENTER
Center of an arc or circle.
@ CONCENTRIC
Two arcs/circles share a center.
@ VERTICAL
A segment (or two points) is vertical.
@ TANGENT
A line and a curve, or two curves, touch tangentially.
@ COINCIDENT
Two points are made to coincide.
@ HORIZONTAL
A segment (or two points) is horizontal.
@ MIDPOINT
A point is the midpoint of a segment.
@ POINT_ON_LINE
A point lies on a segment's supporting line.
One constraint chosen for a freshly drawn shape.
One participant in a constraint: a referenced board item plus the feature of that item that participa...
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(EndpointBindsCoincident)
BOOST_AUTO_TEST_SUITE_END()
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