KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_constraint_overlay.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
28
31
32#include <algorithm>
33#include <memory>
34#include <set>
35
36#include <board.h>
37#include <footprint.h>
38#include <math/box2.h>
39#include <pcb_shape.h>
41#include <view/view.h>
42
46
48
49BOOST_AUTO_TEST_SUITE( ConstraintOverlay )
50
51
52BOOST_AUTO_TEST_CASE( OverlayRegistersOncePerLayerAndUnregistersFully )
53{
54 BOARD board;
55 KIGFX::VIEW view;
56
57 BOX2I maxBox;
58 maxBox.SetMaximum();
59
60 std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> baseline;
61 view.Query( maxBox, baseline );
62
63 {
64 CONSTRAINT_OVERLAY overlay( &board, &view );
65
66 std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> withOverlay;
67 view.Query( maxBox, withOverlay );
68
69 // Each item may appear once per layer it occupies. A duplicate (item, layer) pair means
70 // the same VIEW_ITEM was added to the view twice and can only be removed once.
71 std::set<KIGFX::VIEW::LAYER_ITEM_PAIR> unique( withOverlay.begin(), withOverlay.end() );
72 BOOST_CHECK_EQUAL( unique.size(), withOverlay.size() );
73
74 // The overlay registers two items: the world-space tint overlay and the screen-constant
75 // badge item.
76 BOOST_CHECK_EQUAL( withOverlay.size(), baseline.size() + 2 );
77 }
78
79 // After destruction the view must hold no leftover (dangling) reference to the overlay. The
80 // pair query only collects pointers, so this is safe even when the overlay has been freed.
81 std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> afterDestruction;
82 view.Query( maxBox, afterDestruction );
83
84 BOOST_CHECK_EQUAL( afterDestruction.size(), baseline.size() );
85}
86
87
89
90
91BOOST_AUTO_TEST_SUITE( ConstraintBadges )
92
93
94BOOST_AUTO_TEST_CASE( BadgesFanOutAndSelectionToggles )
95{
96 constexpr int MM = 1000000;
97
98 BOARD board;
99 KIGFX::VIEW view;
100
101 PCB_SHAPE* seg = new PCB_SHAPE( &board, SHAPE_T::SEGMENT );
102 seg->SetStart( VECTOR2I( 0, 0 ) );
103 seg->SetEnd( VECTOR2I( 10 * MM, 0 ) );
104 board.Add( seg );
105
106 // Two constraints whose first member resolves to the same point: their badges must not stack.
107 auto* c1 = new PCB_CONSTRAINT( &board, PCB_CONSTRAINT_TYPE::FIXED_POSITION );
108 c1->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
109 board.Add( c1 );
110
111 auto* c2 = new PCB_CONSTRAINT( &board, PCB_CONSTRAINT_TYPE::FIXED_POSITION );
112 c2->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
113 board.Add( c2 );
114
115 CONSTRAINT_OVERLAY overlay( &board, &view );
116 overlay.Update( DiagnoseBoardConstraints( &board ) );
117
118 BOOST_REQUIRE_EQUAL( overlay.Badges().size(), 2 );
119
120 // The badges store their (shared) anchor; the fan-out lives in the screen-space layout.
121 BOOST_CHECK_EQUAL( overlay.Badges()[0].pos, overlay.Badges()[1].pos );
122
123 // LayoutBadges separates same-anchor badges by at least the fan step at any zoom, and the
124 // separation scales with world-per-pixel -- the regression against the old fixed 1.5 mm fan.
125 for( double worldPerPx : { 200.0, 8000.0 } )
126 {
127 std::vector<VECTOR2D> layout = CONSTRAINT_OVERLAY::LayoutBadges( overlay.Badges(), worldPerPx );
128
129 BOOST_REQUIRE_EQUAL( layout.size(), 2 );
130 double dist = ( layout[0] - layout[1] ).EuclideanNorm();
131 BOOST_TEST( dist >= 18.0 * worldPerPx - 1.0 );
132 }
133
134 // Selection state changes only on a real change.
135 BOOST_TEST( overlay.SetSelected( c1->m_Uuid ) );
136 BOOST_TEST( !overlay.SetSelected( c1->m_Uuid ) );
137 BOOST_CHECK( overlay.GetSelected() == c1->m_Uuid );
138 BOOST_TEST( overlay.SetSelected( niluuid ) );
139}
140
141
142// Badges on DIFFERENT anchors that project within a glyph of each other at far zoom-out are still
143// pushed apart by the layout (the cross-anchor de-overlap the old world-space fan also did).
144BOOST_AUTO_TEST_CASE( LayoutDeOverlapsNearbyAnchors )
145{
146 constexpr int MM = 1000000;
147
148 BOARD board;
149 KIGFX::VIEW view;
150
151 PCB_SHAPE* seg = new PCB_SHAPE( &board, SHAPE_T::SEGMENT );
152 seg->SetStart( VECTOR2I( 0, 0 ) );
153 seg->SetEnd( VECTOR2I( MM / 10, 0 ) ); // endpoints only 0.1 mm apart
154 board.Add( seg );
155
156 auto* c1 = new PCB_CONSTRAINT( &board, PCB_CONSTRAINT_TYPE::FIXED_POSITION );
157 c1->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::START );
158 board.Add( c1 );
159
160 auto* c2 = new PCB_CONSTRAINT( &board, PCB_CONSTRAINT_TYPE::FIXED_POSITION );
161 c2->AddMember( seg->m_Uuid, CONSTRAINT_ANCHOR::END );
162 board.Add( c2 );
163
164 CONSTRAINT_OVERLAY overlay( &board, &view );
165 overlay.Update( DiagnoseBoardConstraints( &board ) );
166 BOOST_REQUIRE_EQUAL( overlay.Badges().size(), 2 );
167
168 const double worldPerPx = 8000.0;
169 std::vector<VECTOR2D> layout = CONSTRAINT_OVERLAY::LayoutBadges( overlay.Badges(), worldPerPx );
170
171 BOOST_TEST( ( layout[0] - layout[1] ).EuclideanNorm() >= 18.0 * worldPerPx - 1.0 );
172}
173
174
175// The visibility mode and hover filter are orthogonal: ALWAYS shows every badge, HOVER with nothing
176// hovered shows none, HOVER with a shape shows only its constraints, and a panel isolation wins.
177BOOST_AUTO_TEST_CASE( VisibilityModeAndHoverFilter )
178{
179 constexpr int MM = 1000000;
180
181 BOARD board;
182 KIGFX::VIEW view;
183
184 PCB_SHAPE* seg1 = new PCB_SHAPE( &board, SHAPE_T::SEGMENT );
185 seg1->SetStart( VECTOR2I( 0, 0 ) );
186 seg1->SetEnd( VECTOR2I( 10 * MM, 0 ) );
187 board.Add( seg1 );
188
189 PCB_SHAPE* seg2 = new PCB_SHAPE( &board, SHAPE_T::SEGMENT );
190 seg2->SetStart( VECTOR2I( 0, 10 * MM ) );
191 seg2->SetEnd( VECTOR2I( 10 * MM, 10 * MM ) );
192 board.Add( seg2 );
193
194 auto* c1 = new PCB_CONSTRAINT( &board, PCB_CONSTRAINT_TYPE::FIXED_POSITION );
195 c1->AddMember( seg1->m_Uuid, CONSTRAINT_ANCHOR::START );
196 board.Add( c1 );
197
198 auto* c2 = new PCB_CONSTRAINT( &board, PCB_CONSTRAINT_TYPE::FIXED_POSITION );
199 c2->AddMember( seg2->m_Uuid, CONSTRAINT_ANCHOR::START );
200 board.Add( c2 );
201
203 CONSTRAINT_OVERLAY overlay( &board, &view );
204
205 overlay.SetVisibilityMode( OVERLAY_MODE::ALWAYS );
206 overlay.Update( diag );
207 BOOST_CHECK_EQUAL( overlay.Badges().size(), 2 );
208
209 overlay.SetVisibilityMode( OVERLAY_MODE::HOVER );
210 overlay.Update( diag );
211 BOOST_CHECK_EQUAL( overlay.Badges().size(), 0 ); // nothing hovered -> hidden
212
213 overlay.SetHoverShape( seg1->m_Uuid );
214 overlay.Update( diag );
215 BOOST_REQUIRE_EQUAL( overlay.Badges().size(), 1 );
216 BOOST_CHECK( overlay.Badges()[0].constraint == c1->m_Uuid );
217
218 overlay.SetIsolated( c2->m_Uuid ); // panel focus wins over hover
219 overlay.Update( diag );
220 BOOST_REQUIRE_EQUAL( overlay.Badges().size(), 1 );
221 BOOST_CHECK( overlay.Badges()[0].constraint == c2->m_Uuid );
222}
223
224
225BOOST_AUTO_TEST_CASE( NearestConstrainedShapeUsesCandidatesOnly )
226{
227 constexpr int MM = 1000000;
228
229 BOARD board;
230 PCB_SHAPE* seg = new PCB_SHAPE( &board, SHAPE_T::SEGMENT );
231 seg->SetStart( VECTOR2I( 0, 0 ) );
232 seg->SetEnd( VECTOR2I( 10 * MM, 0 ) );
233 board.Add( seg );
234
235 std::vector<KIID> candidates = { seg->m_Uuid };
236
237 std::optional<KIID> hit = NearestConstrainedShape( &board, candidates, VECTOR2I( 5 * MM, 1000 ), 5000 );
238 BOOST_REQUIRE( hit.has_value() );
239 BOOST_CHECK( *hit == seg->m_Uuid );
240
241 BOOST_CHECK( !NearestConstrainedShape( &board, candidates, VECTOR2I( 5 * MM, 5 * MM ), 5000 ).has_value() );
242 BOOST_CHECK( !NearestConstrainedShape( &board, {}, VECTOR2I( 0, 0 ), 5000 ).has_value() );
243
244 board.Remove( seg );
245 delete seg;
246
247 BOOST_CHECK( !NearestConstrainedShape( &board, candidates, VECTOR2I( 5 * MM, 1000 ), 5000 ).has_value() );
248}
249
250
251BOOST_AUTO_TEST_CASE( ConstrainedShapeIdsDropsMissingMembers )
252{
253 SETTINGS_MANAGER settingsManager;
254 std::unique_ptr<BOARD> board;
255 KI_TEST::LoadBoard( settingsManager, wxS( "api_kitchen_sink" ), board );
256
257 // The file's footprint constraints name shapes the footprint no longer has
258 auto hasConstraints = []( const FOOTPRINT* aFootprint ) { return !aFootprint->Constraints().empty(); };
259 auto fpIt = std::ranges::find_if( board->Footprints(), hasConstraints );
260 BOOST_REQUIRE( fpIt != board->Footprints().end() );
261
262 FOOTPRINT* footprint = *fpIt;
263 PCB_CONSTRAINT* dangling = footprint->Constraints().front();
264 BOOST_REQUIRE( !dangling->GetMembers().empty() );
265 BOOST_REQUIRE( !board->ResolveItem( dangling->GetMembers().front().m_item, true ) );
266
267 auto shapeIt = std::ranges::find_if( footprint->GraphicalItems(),
268 []( BOARD_ITEM* aItem ) { return aItem->Type() == PCB_SHAPE_T; } );
269 BOOST_REQUIRE( shapeIt != footprint->GraphicalItems().end() );
270
271 PCB_CONSTRAINT* live = static_cast<PCB_CONSTRAINT*>( dangling->Clone() );
272 live->ResetUuid();
273 live->RemapKIIDs( { { dangling->GetMembers().front().m_item, ( *shapeIt )->m_Uuid } } );
274 footprint->Add( live );
275
276 std::vector<KIID> ids = ConstrainedShapeIds( board.get() );
277 BOOST_REQUIRE_EQUAL( ids.size(), 1 );
278 BOOST_CHECK( ids.front() == ( *shapeIt )->m_Uuid );
279}
280
281
BOARD_CONSTRAINT_DIAGNOSTICS DiagnoseBoardConstraints(BOARD *aBoard)
Diagnose every constraint cluster on the board (validate only – geometry is not changed) and return t...
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
void ResetUuid()
Definition board_item.h:281
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
void Remove(BOARD_ITEM *aBoardItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
Definition board.cpp:1691
constexpr void SetMaximum()
Definition box2.h:77
static std::vector< VECTOR2D > LayoutBadges(const std::vector< CONSTRAINT_BADGE > &aBadges, double aWorldPerPx)
The on-screen draw position (world units) of each badge at the given scale: the anchor offset by Badg...
const KIID m_Uuid
Definition eda_item.h:599
CONSTRAINTS & Constraints()
Definition footprint.h:367
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
DRAWINGS & GraphicalItems()
Definition footprint.h:358
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition view.h:68
int Query(const BOX2I &aRect, std::vector< LAYER_ITEM_PAIR > &aResult) const
Find all visible items that touch or are within the rectangle aRect.
Definition view.cpp:744
A geometric constraint between board items (issue #2329).
const std::vector< CONSTRAINT_MEMBER > & GetMembers() const
void RemapKIIDs(const std::map< KIID, KIID > &aIdMap) override
Remap KIIDs this item stores to reference other items (e.g.
EDA_ITEM * Clone() const override
Create a duplicate of this item with linked list members set to NULL.
void SetEnd(const VECTOR2I &aEnd) override
void SetStart(const VECTOR2I &aStart) override
std::optional< KIID > NearestConstrainedShape(const BOARD *aBoard, const std::vector< KIID > &aCandidates, const VECTOR2I &aPos, int aMaxDist)
The candidate shape whose outline aPos hits within aMaxDist, or std::nullopt.
std::vector< KIID > ConstrainedShapeIds(const BOARD *aBoard)
The member ids of every board and footprint constraint that are on aBoard, in first reference order: ...
@ SEGMENT
Definition eda_shape.h:56
KIID niluuid(0)
void LoadBoard(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< BOARD > &aBoard)
@ START
First endpoint of a segment or arc.
@ END
Second endpoint of a segment or arc.
@ FIXED_POSITION
A point is locked at its current location.
std::shared_ptr< PNS_LOG_VIEWER_OVERLAY > overlay
Board-wide diagnostics for the constraint overlay and info bar.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(OverlayRegistersOncePerLayerAndUnregistersFully)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST(netlist.find("R_G1 ARM_OUT1 DIE_B R='0.001 / ((SW_STATE)") !=std::string::npos)
static const long long MM
static std::vector< int > candidates(const SEGMENT_INDEX &aIndex, const SEG &aQuery, int aPadding)
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708