KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_constraint_reference.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 <cmath>
31#include <vector>
32
33#include <board.h>
34#include <pcb_shape.h>
35
36#include <geometry/seg.h>
37
41
43
44using namespace KI_TEST;
45
46namespace
47{
48double cornerAngleOf( const PCB_SHAPE* aA, const PCB_SHAPE* aB )
49{
50 return MeasureCornerAngle( SEG( aA->GetStart(), aA->GetEnd() ),
51 SEG( aB->GetStart(), aB->GetEnd() ) ).AsDegrees();
52}
53}
54
55
56BOOST_AUTO_TEST_SUITE( ConstraintReference )
57
58
59// A reference (non-driving) length does not force the segment; after a drag shortens it, its stored
60// value follows the new measured length.
61BOOST_AUTO_TEST_CASE( ReferenceLengthTracksGeometry )
62{
63 BOARD board;
64 PCB_SHAPE* seg = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
65
67 { { seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } }, 10.0 * MM );
68 len->SetDriving( false );
69
70 std::vector<BOARD_ITEM*> staged;
71
72 // Drag END straight up to (0, 6 mm); the pinned start end keeps the length at 6 mm.
73 SolveCluster( &board, { seg->m_Uuid, CONSTRAINT_ANCHOR::END }, { 0, 6 * MM }, nullptr,
74 [&]( BOARD_ITEM* i ) { staged.push_back( i ); } );
75
76 BOOST_REQUIRE( len->GetValue().has_value() );
77 BOOST_CHECK_LE( std::abs( *len->GetValue() - 6.0 * MM ), 5000.0 );
78 BOOST_CHECK( std::find( staged.begin(), staged.end(), len ) != staged.end() );
79}
80
81
82// The two point form has no owning segment so its value is re measured from the two member
83// anchors after a drag it must follow the new distance
84BOOST_AUTO_TEST_CASE( ReferenceTwoPointLengthTracksGeometry )
85{
86 BOARD board;
87 PCB_SHAPE* seg = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
88
92 10.0 * MM );
93 len->SetDriving( false );
94
95 std::vector<BOARD_ITEM*> staged;
96
97 // Drag END straight up to (0, 6 mm); the pinned start keeps the anchor distance at 6 mm.
98 SolveCluster( &board, { seg->m_Uuid, CONSTRAINT_ANCHOR::END }, { 0, 6 * MM }, nullptr,
99 [&]( BOARD_ITEM* i ) { staged.push_back( i ); } );
100
101 BOOST_REQUIRE( len->GetValue().has_value() );
102 BOOST_CHECK_LE( std::abs( *len->GetValue() - 6.0 * MM ), 5000.0 );
103 BOOST_CHECK( std::find( staged.begin(), staged.end(), len ) != staged.end() );
104}
105
106
107// A driving length forces the geometry, so the same drag leaves the segment 10 mm long and never
108// rewrites the stored value.
109BOOST_AUTO_TEST_CASE( DrivingLengthValueNotOverwritten )
110{
111 BOARD board;
112 PCB_SHAPE* seg = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
113
115 { { seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } }, 10.0 * MM );
116 BOOST_REQUIRE( len->IsDriving() );
117
118 std::vector<BOARD_ITEM*> staged;
119
120 SolveCluster( &board, { seg->m_Uuid, CONSTRAINT_ANCHOR::END }, { 0, 6 * MM }, nullptr,
121 [&]( BOARD_ITEM* i ) { staged.push_back( i ); } );
122
123 BOOST_REQUIRE( len->GetValue().has_value() );
124 BOOST_CHECK_EQUAL( *len->GetValue(), 10.0 * MM );
125 BOOST_CHECK( std::find( staged.begin(), staged.end(), len ) == staged.end() );
126 BOOST_CHECK_LE( std::abs( ( seg->GetEnd() - seg->GetStart() ).EuclideanNorm() - 10.0 * MM ),
127 5000.0 );
128}
129
130
131// A reference radius follows a resize; the resized circle is held, so the measured radius updates
132// the stored value.
133BOOST_AUTO_TEST_CASE( ReferenceRadiusTracksGeometry )
134{
135 BOARD board;
136 PCB_SHAPE* circle = addCircle( board, { 0, 0 }, 5 * MM );
137
139 { { circle->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } }, 5.0 * MM );
140 rad->SetDriving( false );
141
142 circle->SetRadius( 8 * MM ); // stand in for a radius-handle drag
143
144 std::vector<BOARD_ITEM*> staged;
145
146 ReSolveAfterShapeResize( &board, circle, nullptr,
147 [&]( BOARD_ITEM* i ) { staged.push_back( i ); } );
148
149 BOOST_REQUIRE( rad->GetValue().has_value() );
150 BOOST_CHECK_LE( std::abs( *rad->GetValue() - 8.0 * MM ), 5000.0 );
151 BOOST_CHECK( std::find( staged.begin(), staged.end(), rad ) != staged.end() );
152}
153
154
155// A solve that leaves the measured value where it was must not stage the constraint or churn undo.
156BOOST_AUTO_TEST_CASE( ReferenceValueUnchangedIsNotStaged )
157{
158 BOARD board;
159 PCB_SHAPE* seg = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
160
162 { { seg->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } }, 10.0 * MM );
163 len->SetDriving( false );
164
165 std::vector<BOARD_ITEM*> staged;
166
167 // Re-pin END where it already sits; the length is unchanged, so nothing is written back.
168 SolveCluster( &board, { seg->m_Uuid, CONSTRAINT_ANCHOR::END }, { 10 * MM, 0 }, nullptr,
169 [&]( BOARD_ITEM* i ) { staged.push_back( i ); } );
170
171 BOOST_CHECK( std::find( staged.begin(), staged.end(), len ) == staged.end() );
172 BOOST_CHECK_EQUAL( *len->GetValue(), 10.0 * MM );
173}
174
175
176// Driving a corner to 120 degrees from an 100 degree start must keep the obtuse corner, not collapse
177// to the 60 degree mirror -- the directed-target mapping picks the candidate nearest the current
178// configuration.
179BOOST_AUTO_TEST_CASE( DrivingObtuseCornerSurvivesSolve )
180{
181 BOARD board;
182 PCB_SHAPE* a = addSegment( board, { 0, 0 }, { 10 * MM, 0 } ); // ray +x
183 PCB_SHAPE* b = addSegment( board, { 0, 0 }, { -1736482, 9848078 } ); // ray at 100 deg
184
186 { { a->m_Uuid, CONSTRAINT_ANCHOR::START }, { b->m_Uuid, CONSTRAINT_ANCHOR::START } } );
188 { { a->m_Uuid, CONSTRAINT_ANCHOR::WHOLE }, { b->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } },
189 120.0 );
190
191 solveAndApply( board, { a, b } );
192
193 BOOST_CHECK_CLOSE( cornerAngleOf( a, b ), 120.0, 0.5 );
194}
195
196
197// A chained-polyline corner (end-to-start coincidence, odd endpoint parity) driven across the 90
198// degree midline must land on the requested corner, not its supplement -- the directed target is
199// chosen from the parity-correct candidate pair.
200BOOST_AUTO_TEST_CASE( DrivingChainedCornerCrossesNinety )
201{
202 BOARD board;
203 PCB_SHAPE* a = addSegment( board, { 0, 0 }, { 10 * MM, 0 } );
204 PCB_SHAPE* b = addSegment( board, { 10 * MM, 0 }, { 10 * MM, 10 * MM } ); // 90 deg chained corner
205
207 { { a->m_Uuid, CONSTRAINT_ANCHOR::END }, { b->m_Uuid, CONSTRAINT_ANCHOR::START } } );
209 { { a->m_Uuid, CONSTRAINT_ANCHOR::WHOLE }, { b->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } },
210 60.0 );
211
212 solveAndApply( board, { a, b } );
213
214 BOOST_CHECK_CLOSE( cornerAngleOf( a, b ), 60.0, 0.5 );
215}
216
217
218// A reference angular dimension tracks the corner it measures: reshape one segment, re-solve, and
219// the stored value follows to the new corner.
220BOOST_AUTO_TEST_CASE( ReferenceAngleTracksGeometry )
221{
222 BOARD board;
223 PCB_SHAPE* a = addSegment( board, { 0, 0 }, { 10 * MM, 0 } ); // ray +x
224 PCB_SHAPE* b = addSegment( board, { 0, 0 }, { 0, 10 * MM } ); // ray +y, a 90 deg corner
225
228 { { a->m_Uuid, CONSTRAINT_ANCHOR::WHOLE }, { b->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } }, 90.0 );
229 angle->SetDriving( false );
230
231 b->SetEnd( { -5 * MM, 8660254 } ); // open the corner to 120 deg
232
233 std::vector<BOARD_ITEM*> staged;
234 ReSolveShapeClusters( &board, { b }, nullptr, [&]( BOARD_ITEM* i ) { staged.push_back( i ); } );
235
236 BOOST_REQUIRE( angle->GetValue().has_value() );
237 BOOST_CHECK_CLOSE( *angle->GetValue(), 120.0, 0.5 );
238 BOOST_CHECK( std::find( staged.begin(), staged.end(), angle ) != staged.end() );
239}
240
241
void ReSolveAfterShapeResize(BOARD *aBoard, PCB_SHAPE *aShape, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
Re-solve after a resize, e.g. a circle radius edit. Holds aShape fixed so its neighbors adjust.
void ReSolveShapeClusters(BOARD *aBoard, const std::vector< PCB_SHAPE * > &aShapes, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
Re-solve the clusters of shapes edited outside the solver, e.g.
CONSTRAINT_DIAGNOSIS SolveCluster(BOARD *aBoard, const CONSTRAINT_MEMBER &aDragged, const VECTOR2I &aCursor, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify, bool aIncludeDragged, bool aStabilize, const std::set< KIID > &aEdited, const std::optional< std::pair< CONSTRAINT_MEMBER, VECTOR2I > > &aCoDragged)
Gather the cluster of shapes transitively constrained with the dragged shape, solve with the dragged ...
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:83
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
double AsDegrees() const
Definition eda_angle.h:116
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).
void SetDriving(bool aDriving)
Definition seg.h:38
EDA_ANGLE MeasureCornerAngle(const SEG &aA, const SEG &aB)
The corner angle between two segments, in the closed range [0, 180] degrees.
PCB_SHAPE * addSegment(BOARD &aBoard, const VECTOR2I &aStart, const VECTOR2I &aEnd)
PCB_SHAPE * addCircle(BOARD &aBoard, const VECTOR2I &aCenter, int aRadius)
void solveAndApply(BOARD &aBoard, const std::vector< PCB_SHAPE * > &aShapes)
Build the cluster of aShapes against every constraint on aBoard, solve it, and write the result back ...
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.
@ COINCIDENT
Two points are made to coincide.
@ FIXED_RADIUS
An arc/circle has a driving radius value.
@ FIXED_LENGTH
A segment has a driving length value.
@ ANGULAR_DIMENSION
An angle between members (driving or reference).
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(ReferenceLengthTracksGeometry)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
BOOST_CHECK_EQUAL(result, "25.4")