KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_constraint_duplicate.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <map>
21#include <memory>
22
24
25#include <board.h>
26#include <footprint.h>
27#include <pcb_group.h>
28#include <pcb_shape.h>
31
33
34using namespace KI_TEST;
35
36
37BOOST_AUTO_TEST_SUITE( ConstraintSolverDuplicate )
38
39
40namespace
41{
43PCB_SHAPE* duplicateShape( BOARD& aBoard, PCB_SHAPE* aOrig )
44{
45 PCB_SHAPE* dup = static_cast<PCB_SHAPE*>( aOrig->Clone() );
46 dup->ResetUuid();
47 aBoard.Add( dup );
48 return dup;
49}
50
51
52const PCB_CONSTRAINT* findByType( const std::vector<PCB_CONSTRAINT*>& aClones, PCB_CONSTRAINT_TYPE aType )
53{
54 for( const PCB_CONSTRAINT* c : aClones )
55 {
56 if( c->GetConstraintType() == aType )
57 return c;
58 }
59
60 return nullptr;
61}
62} // namespace
63
64
65// Duplicate carries constraints fully inside the set re pointed at the copies
66// a constraint touching an outside item is dropped
67BOOST_AUTO_TEST_CASE( DuplicateCarriesFullyContainedConstraints )
68{
69 auto board = std::make_unique<BOARD>();
70
71 PCB_SHAPE* seg1 = addSegment( *board, { 0, 0 }, { 10 * MM, 0 } );
72 PCB_SHAPE* seg2 = addSegment( *board, { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
73 PCB_SHAPE* arc = addArc( *board, { 0, 5 * MM }, { 2 * MM, 7 * MM }, { 4 * MM, 5 * MM } );
74
75 // seg3 stays behind, so the constraint touching it must not travel with the duplicate.
76 PCB_SHAPE* seg3 = addSegment( *board, { 0, 20 * MM }, { 10 * MM, 20 * MM } );
77
78 // A coincident between the touching endpoints of the two duplicated segments.
79 PCB_CONSTRAINT* coincident = addConstraint(
81 { { seg1->m_Uuid, CONSTRAINT_ANCHOR::END }, { seg2->m_Uuid, CONSTRAINT_ANCHOR::START } } );
82
83 // An internal constraint on the arc alone.
84 PCB_CONSTRAINT* arcAngle =
86 { { arc->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } }, 90.0 );
87 arcAngle->SetDriving( true );
88
89 // A constraint reaching outside the duplicated set; it must be dropped.
92 { seg3->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
93
94 PCB_SHAPE* dupSeg1 = duplicateShape( *board, seg1 );
95 PCB_SHAPE* dupSeg2 = duplicateShape( *board, seg2 );
96 PCB_SHAPE* dupArc = duplicateShape( *board, arc );
97
98 std::map<KIID, KIID> idMap;
99 idMap[seg1->m_Uuid] = dupSeg1->m_Uuid;
100 idMap[seg2->m_Uuid] = dupSeg2->m_Uuid;
101 idMap[arc->m_Uuid] = dupArc->m_Uuid;
102
103 std::vector<PCB_CONSTRAINT*> clones = CloneFullySelectedConstraints( board->Constraints(), idMap );
104
105 // Only the coincident and the arc-angle are fully contained; the parallel is dropped.
106 BOOST_REQUIRE_EQUAL( clones.size(), 2 );
107 BOOST_CHECK( findByType( clones, PCB_CONSTRAINT_TYPE::PARALLEL ) == nullptr );
108
109 const PCB_CONSTRAINT* coincidentClone = findByType( clones, PCB_CONSTRAINT_TYPE::COINCIDENT );
110 BOOST_REQUIRE( coincidentClone );
111 BOOST_REQUIRE_EQUAL( coincidentClone->GetMembers().size(), 2 );
112
113 // Members point at the duplicates, anchors preserved, and it is a distinct object.
114 BOOST_CHECK( coincidentClone->m_Uuid != coincident->m_Uuid );
115 BOOST_CHECK( coincidentClone->GetMembers()[0].m_item == dupSeg1->m_Uuid );
116 BOOST_CHECK( coincidentClone->GetMembers()[0].m_anchor == CONSTRAINT_ANCHOR::END );
117 BOOST_CHECK( coincidentClone->GetMembers()[1].m_item == dupSeg2->m_Uuid );
118 BOOST_CHECK( coincidentClone->GetMembers()[1].m_anchor == CONSTRAINT_ANCHOR::START );
119
120 const PCB_CONSTRAINT* arcAngleClone = findByType( clones, PCB_CONSTRAINT_TYPE::ARC_ANGLE );
121 BOOST_REQUIRE( arcAngleClone );
122 BOOST_REQUIRE_EQUAL( arcAngleClone->GetMembers().size(), 1 );
123
124 BOOST_CHECK( arcAngleClone->m_Uuid != arcAngle->m_Uuid );
125 BOOST_CHECK( arcAngleClone->GetMembers()[0].m_item == dupArc->m_Uuid );
126 BOOST_CHECK( arcAngleClone->IsDriving() );
127 BOOST_REQUIRE( arcAngleClone->GetValue().has_value() );
128 BOOST_CHECK_CLOSE( arcAngleClone->GetValue().value(), 90.0, 1e-9 );
129
130 for( PCB_CONSTRAINT* clone : clones )
131 delete clone;
132}
133
134
135// A constraint entirely outside the set yields no clone even if the set is non empty
136// map keys alone define the scope
137BOOST_AUTO_TEST_CASE( DuplicateDropsConstraintOutsideScope )
138{
139 auto board = std::make_unique<BOARD>();
140
141 PCB_SHAPE* seg1 = addSegment( *board, { 0, 0 }, { 10 * MM, 0 } );
142 PCB_SHAPE* seg2 = addSegment( *board, { 0, 5 * MM }, { 10 * MM, 5 * MM } );
143
145 { { seg1->m_Uuid, CONSTRAINT_ANCHOR::WHOLE },
146 { seg2->m_Uuid, CONSTRAINT_ANCHOR::WHOLE } } );
147
148 // Duplicate an unrelated segment only; the parallel touches neither duplicated item.
149 PCB_SHAPE* other = addSegment( *board, { 0, 30 * MM }, { 10 * MM, 30 * MM } );
150 PCB_SHAPE* dupOther = duplicateShape( *board, other );
151
152 std::map<KIID, KIID> idMap;
153 idMap[other->m_Uuid] = dupOther->m_Uuid;
154
155 std::vector<PCB_CONSTRAINT*> clones = CloneFullySelectedConstraints( board->Constraints(), idMap );
156
157 BOOST_CHECK( clones.empty() );
158}
159
160
161// Group duplicate must pair each member with its own copy so constraints land on matching duplicates
162// members iterate unordered so DeepDuplicate captures the mapping while cloning via its KIID map
163BOOST_AUTO_TEST_CASE( GroupDuplicatePairsMembersForConstraintRemap )
164{
165 auto board = std::make_unique<BOARD>();
166
167 // Distinct geometry so a swapped pairing is detectable.
168 PCB_SHAPE* seg1 = addSegment( *board, { 0, 0 }, { 10 * MM, 0 } );
169 PCB_SHAPE* seg2 = addSegment( *board, { 10 * MM, 0 }, { 10 * MM, 10 * MM } );
170
171 PCB_GROUP* group = new PCB_GROUP( board.get() );
172 group->AddItem( seg1 );
173 group->AddItem( seg2 );
174 board->Add( group );
175
177 { { seg1->m_Uuid, CONSTRAINT_ANCHOR::END }, { seg2->m_Uuid, CONSTRAINT_ANCHOR::START } } );
178
179 std::map<KIID, KIID> idMap;
180 PCB_GROUP* dupGroup = group->DeepDuplicate( IGNORE_PARENT_GROUP, nullptr, &idMap );
181
182 // Resolve each original to its paired duplicate and check geometry matches
183 // fails if the two groups are walked in uncorrelated order
184 auto dupeByOrig =
185 [&]( const PCB_SHAPE* aOrig ) -> const PCB_SHAPE*
186 {
187 KIID dupId = idMap.at( aOrig->m_Uuid );
188
189 for( EDA_ITEM* member : dupGroup->GetItems() )
190 {
191 if( member->m_Uuid == dupId )
192 return static_cast<const PCB_SHAPE*>( member );
193 }
194
195 return nullptr;
196 };
197
198 const PCB_SHAPE* dupSeg1 = dupeByOrig( seg1 );
199 const PCB_SHAPE* dupSeg2 = dupeByOrig( seg2 );
200
201 BOOST_REQUIRE( dupSeg1 );
202 BOOST_REQUIRE( dupSeg2 );
203 BOOST_CHECK( dupSeg1->GetStart() == seg1->GetStart() && dupSeg1->GetEnd() == seg1->GetEnd() );
204 BOOST_CHECK( dupSeg2->GetStart() == seg2->GetStart() && dupSeg2->GetEnd() == seg2->GetEnd() );
205
206 std::vector<PCB_CONSTRAINT*> clones = CloneFullySelectedConstraints( board->Constraints(), idMap );
207
208 BOOST_REQUIRE_EQUAL( clones.size(), 1 );
209
210 const PCB_CONSTRAINT* coincidentClone = clones.front();
211 BOOST_REQUIRE_EQUAL( coincidentClone->GetMembers().size(), 2 );
212 BOOST_CHECK( coincidentClone->GetMembers()[0].m_item == dupSeg1->m_Uuid );
213 BOOST_CHECK( coincidentClone->GetMembers()[1].m_item == dupSeg2->m_Uuid );
214
215 for( PCB_CONSTRAINT* clone : clones )
216 delete clone;
217
218 // DeepDuplicate does not add its copies to the board, so the test owns them.
219 for( EDA_ITEM* member : dupGroup->GetItems() )
220 delete member;
221
222 delete dupGroup;
223}
224
225
226// A footprint carries its own internal constraints and Duplicate resets every child UUID
227// clone constraints must re point to the duplicate children not the original footprint
228BOOST_AUTO_TEST_CASE( FootprintDuplicateRepointsInternalConstraints )
229{
230 auto board = std::make_unique<BOARD>();
231
232 FOOTPRINT* fp = new FOOTPRINT( board.get() );
233 board->Add( fp );
234
235 auto addFpSegment =
236 [&]( const VECTOR2I& aStart, const VECTOR2I& aEnd ) -> PCB_SHAPE*
237 {
238 PCB_SHAPE* seg = new PCB_SHAPE( fp, SHAPE_T::SEGMENT );
239 seg->SetStart( aStart );
240 seg->SetEnd( aEnd );
241 fp->Add( seg );
242 return seg;
243 };
244
245 PCB_SHAPE* a = addFpSegment( { 0, 0 }, { 10 * MM, 0 } );
246 PCB_SHAPE* b = addFpSegment( { 0, 5 * MM }, { 10 * MM, 6 * MM } );
247
249 c->AddMember( a->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
250 c->AddMember( b->m_Uuid, CONSTRAINT_ANCHOR::WHOLE );
251 fp->Add( c );
252
253 std::unique_ptr<FOOTPRINT> dupe( static_cast<FOOTPRINT*>( fp->Duplicate( IGNORE_PARENT_GROUP ) ) );
254
255 BOOST_REQUIRE_EQUAL( dupe->Constraints().size(), 1 );
256
257 std::set<KIID> dupeChildIds;
258 dupe->RunOnChildren( [&]( BOARD_ITEM* aChild ) { dupeChildIds.insert( aChild->m_Uuid ); },
260
261 const PCB_CONSTRAINT* dupeConstraint = dupe->Constraints().front();
262 BOOST_REQUIRE_EQUAL( dupeConstraint->GetMembers().size(), 2 );
263
264 for( const CONSTRAINT_MEMBER& member : dupeConstraint->GetMembers() )
265 {
266 // The member must resolve inside the duplicate, never back to the original footprint.
267 BOOST_CHECK( dupeChildIds.count( member.m_item ) > 0 );
268 BOOST_CHECK( member.m_item != a->m_Uuid );
269 BOOST_CHECK( member.m_item != b->m_Uuid );
270 }
271}
272
273
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:83
void ResetUuid()
Definition board_item.h:249
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
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:50
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:96
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
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
BOARD_ITEM * Duplicate(bool addToParentGroup, BOARD_COMMIT *aCommit=nullptr) const override
Create a copy of this BOARD_ITEM.
Definition kiid.h:44
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 SetDriving(bool aDriving)
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
void SetEnd(const VECTOR2I &aEnd) override
EDA_ITEM * Clone() const override
Create a duplicate of this item with linked list members set to NULL.
void SetStart(const VECTOR2I &aStart) override
std::vector< PCB_CONSTRAINT * > CloneFullySelectedConstraints(const CONSTRAINTS &aSource, const std::map< KIID, KIID > &aIdMap)
Clone the constraints in aSource that a duplicate should carry.
@ RECURSE
Definition eda_item.h:49
#define IGNORE_PARENT_GROUP
Definition eda_item.h:53
@ 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)
constexpr int MM
PCB_CONSTRAINT * addConstraint(BOARD &aBoard, PCB_CONSTRAINT_TYPE aType, const std::vector< CONSTRAINT_MEMBER > &aMembers, std::optional< double > aValue=std::nullopt)
@ 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.
PCB_CONSTRAINT_TYPE
The geometric relationship a PCB_CONSTRAINT enforces between its members.
@ COINCIDENT
Two points are made to coincide.
@ ARC_ANGLE
An arc has a driving or reference swept-angle value.
@ PARALLEL
Two segments are parallel.
Class to handle a set of BOARD_ITEMs.
One participant in a constraint: a referenced board item plus the feature of that item that participa...
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(DuplicateCarriesFullyContainedConstraints)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683