KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_snap_resolver.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 3
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 <boost/test/unit_test.hpp>
21
22#include <kiid.h>
23#include <snap/snap_resolver.h>
24
25#include <algorithm>
26#include <chrono>
27#include <iomanip>
28#include <sstream>
29
30
31namespace
32{
33SNAP_TARGET_ID targetId( const char* aName )
34{
35 return KIID::FromName( aName ).AsBytes();
36}
37
38
39SNAP_STABLE_ID id( const char* aFeature )
40{
41 return { SNAP_ID_KIND::ITEM_GEOMETRY, targetId( aFeature ), 0, 0 };
42}
43
44
45std::string targetBytes( const SNAP_STABLE_ID& aId )
46{
47 std::ostringstream stream;
48
49 for( uint8_t byte : aId.target )
50 stream << std::hex << std::setfill( '0' ) << std::setw( 2 ) << static_cast<int>( byte );
51
52 return stream.str();
53}
54
55
56std::string resultSignature( const SNAP_RESULT& aResult )
57{
58 std::ostringstream stream;
59 stream << static_cast<int>( aResult.status ) << ':' << aResult.position.x << ',' << aResult.position.y << ':'
60 << aResult.remainingDof;
61
62 for( const SNAP_STABLE_ID& accepted : aResult.accepted )
63 {
64 stream << ':' << static_cast<int>( accepted.kind ) << ',' << targetBytes( accepted ) << ','
65 << accepted.featureIndex << ',' << accepted.solutionBranch;
66 }
67
68 stream << std::hexfloat;
69
70 for( double residual : aResult.quantizedResiduals )
71 stream << ':' << residual;
72
73 for( const SNAP_GUIDE& guide : aResult.guides )
74 {
75 stream << ':' << guide.start.x << ',' << guide.start.y << ',' << guide.end.x << ',' << guide.end.y << ','
76 << static_cast<int>( guide.style );
77 }
78
79 return stream.str();
80}
81} // namespace
82
83
84BOOST_AUTO_TEST_SUITE( SnapResolver )
85
86
87BOOST_AUTO_TEST_CASE( StableIdentityUsesTypedKindAndTarget )
88{
89 const SNAP_TARGET_ID target = targetId( "target" );
90 const SNAP_STABLE_ID first{ SNAP_ID_KIND::ITEM_GEOMETRY, target, 2, 1 };
91 const SNAP_STABLE_ID same{ SNAP_ID_KIND::ITEM_GEOMETRY, target, 2, 1 };
92 const SNAP_STABLE_ID differentKind{ SNAP_ID_KIND::INTRINSIC_ANCHOR, target, 2, 1 };
93
94 BOOST_CHECK( first == same );
95 BOOST_CHECK( first != differentKind );
96 BOOST_CHECK( SNAP_STABLE_ID{}.target == SNAP_TARGET_ID{} );
97 BOOST_CHECK( KIID::FromBytes( target ) == KIID::FromName( "target" ) );
98}
99
100
101BOOST_AUTO_TEST_CASE( PointIdentityPreservesCoordinateOrder )
102{
103 BOOST_CHECK( MakePointSnapId( SNAP_ID_KIND::CONSTRUCTION, { 1, 2 } )
105}
106
107
108BOOST_AUTO_TEST_CASE( IntersectionIdentityIsSourceOrderIndependent )
109{
110 const SNAP_STABLE_ID first{ SNAP_ID_KIND::ITEM_GEOMETRY, KIID( "00000000-0000-0000-0000-000000000001" ).AsBytes(),
111 1, 2 };
112 const SNAP_STABLE_ID second{ SNAP_ID_KIND::ITEM_GEOMETRY, KIID( "00000000-0000-0000-0000-000000000002" ).AsBytes(),
113 3, 4 };
114
115 BOOST_CHECK( MakeIntersectionSnapId( first, second, 5 ) == MakeIntersectionSnapId( second, first, 5 ) );
116}
117
118
119BOOST_AUTO_TEST_CASE( CompositeIdentityIsOrderIndependentWithoutXorCancellation )
120{
121 const SNAP_TARGET_ID first = KIID( "00000000-0000-0000-0000-000000000001" ).AsBytes();
122 const SNAP_TARGET_ID second = KIID( "00000000-0000-0000-0000-000000000002" ).AsBytes();
123
124 BOOST_CHECK( MakeCompositeSnapId( SNAP_ID_KIND::INTRINSIC_ANCHOR, { first, second }, 3 )
125 == MakeCompositeSnapId( SNAP_ID_KIND::INTRINSIC_ANCHOR, { second, first }, 3 ) );
126 BOOST_CHECK( MakeCompositeSnapId( SNAP_ID_KIND::INTRINSIC_ANCHOR, { first, first }, 3 ).target
127 != SNAP_TARGET_ID{} );
128}
129
130
131BOOST_AUTO_TEST_CASE( IdentityConstructionHasStableGoldenBytes )
132{
133 const SNAP_STABLE_ID first{ SNAP_ID_KIND::ITEM_GEOMETRY, KIID( "00000000-0000-0000-0000-000000000001" ).AsBytes(),
134 1, 2 };
136 KIID( "00000000-0000-0000-0000-000000000002" ).AsBytes(), 3, 4 };
137
138 BOOST_CHECK_EQUAL( targetBytes( MakePointSnapId( SNAP_ID_KIND::CONSTRUCTION, { 123, -456 }, 7 ) ),
139 "b1fa72f98d0eaaf396ce1f1030e5f274" );
140 BOOST_CHECK_EQUAL( targetBytes( MakeDerivedSnapId( SNAP_ID_KIND::BOUNDS_X, first, 8, 9 ) ),
141 "38ec5cc638db882f25564f708cd908d4" );
142 BOOST_CHECK_EQUAL( targetBytes( MakeIntersectionSnapId( first, second, 5 ) ), "b260a931293228a311feb6c704737605" );
144 { KIID( "00000000-0000-0000-0000-000000000002" ).AsBytes(),
145 KIID( "00000000-0000-0000-0000-000000000001" ).AsBytes() },
146 3 ) ),
147 "9084556ddbcdd0543f07abd73d7fb746" );
148}
149
150
151BOOST_AUTO_TEST_CASE( AuthoredRelationWinsEveryTransientTier )
152{
153 SNAP_SOURCE_CONTEXT context;
154 context.sourcePoint = { 18, 19 };
155
157 resolver.AddCandidate( SNAP_CANDIDATE::Point( id( "cursor" ), SNAP_PRIORITY_TIER::CURSOR,
158 SNAP_CANDIDATE_SUBTYPE::CURSOR, { 18, 19 }, 0.0 ) );
159 resolver.AddCandidate( SNAP_CANDIDATE::Point( id( "object" ), SNAP_PRIORITY_TIER::OBJECT,
160 SNAP_CANDIDATE_SUBTYPE::FINITE_MANIFOLD, { 20, 20 }, 1.0 ) );
162 SNAP_CANDIDATE_SUBTYPE::INTRINSIC_ANCHOR, { 10, 10 }, 20.0 ) );
163
164 SNAP_RESULT result = resolver.Resolve( context );
165
166 BOOST_REQUIRE_EQUAL( result.status, SNAP_RESULT_STATUS::SUCCESS );
167 BOOST_CHECK_EQUAL( result.position, VECTOR2I( 10, 10 ) );
168 BOOST_REQUIRE_EQUAL( result.accepted.size(), 1 );
169 BOOST_CHECK( result.accepted.front() == id( "authored" ) );
170}
171
172
173BOOST_AUTO_TEST_CASE( AngleRejectsIncompatibleObjectWithoutRelaxing )
174{
175 SNAP_SOURCE_CONTEXT context;
176 context.sourcePoint = { 12, 4 };
177
179 resolver.AddCandidate( SNAP_CANDIDATE::Line( id( "angle" ), SNAP_PRIORITY_TIER::ANGLE,
180 SNAP_CANDIDATE_SUBTYPE::ANGLE_BRANCH, { 0, 0 }, { 1.0, 0.0 }, 4.0 ) );
181 resolver.AddCandidate( SNAP_CANDIDATE::Point( id( "object" ), SNAP_PRIORITY_TIER::OBJECT,
183 resolver.AddCandidate( SNAP_CANDIDATE::AxisX( id( "grid-x" ), SNAP_PRIORITY_TIER::GRID,
185
186 SNAP_RESULT result = resolver.Resolve( context );
187
188 BOOST_REQUIRE_EQUAL( result.status, SNAP_RESULT_STATUS::SUCCESS );
189 BOOST_CHECK_EQUAL( result.position, VECTOR2I( 10, 0 ) );
190 BOOST_CHECK( result.Accepted( id( "angle" ) ) );
191 BOOST_CHECK( !result.Accepted( id( "object" ) ) );
192 BOOST_CHECK( result.Accepted( id( "grid-x" ) ) );
193}
194
195
196BOOST_AUTO_TEST_CASE( CompatibleObjectAxesCompose )
197{
198 SNAP_SOURCE_CONTEXT context;
199 context.sourcePoint = { 14, 17 };
200
202 resolver.AddCandidate( SNAP_CANDIDATE::AxisX( id( "align-x" ), SNAP_PRIORITY_TIER::OBJECT,
204 resolver.AddCandidate( SNAP_CANDIDATE::AxisY( id( "align-y" ), SNAP_PRIORITY_TIER::OBJECT,
206
207 SNAP_RESULT result = resolver.Resolve( context );
208
209 BOOST_REQUIRE_EQUAL( result.status, SNAP_RESULT_STATUS::SUCCESS );
210 BOOST_CHECK_EQUAL( result.position, VECTOR2I( 10, 20 ) );
211 BOOST_CHECK( result.Accepted( id( "align-x" ) ) );
212 BOOST_CHECK( result.Accepted( id( "align-y" ) ) );
213 BOOST_CHECK_EQUAL( result.remainingDof, 0 );
214}
215
216
217BOOST_AUTO_TEST_CASE( ConflictingGridAxisLeavesCompatibleAxisAccepted )
218{
219 SNAP_SOURCE_CONTEXT context;
220 context.sourcePoint = { 9, 13 };
221
223 resolver.AddCandidate( SNAP_CANDIDATE::Line( id( "angle" ), SNAP_PRIORITY_TIER::ANGLE,
224 SNAP_CANDIDATE_SUBTYPE::ANGLE_BRANCH, { 0, 0 }, { 1.0, 1.0 }, 3.0 ) );
225 resolver.AddCandidate( SNAP_CANDIDATE::AxisX( id( "grid-x" ), SNAP_PRIORITY_TIER::GRID,
227 resolver.AddCandidate( SNAP_CANDIDATE::AxisY( id( "grid-y" ), SNAP_PRIORITY_TIER::GRID,
229
230 SNAP_RESULT result = resolver.Resolve( context );
231
232 BOOST_REQUIRE_EQUAL( result.status, SNAP_RESULT_STATUS::SUCCESS );
233 BOOST_CHECK_EQUAL( result.position, VECTOR2I( 10, 10 ) );
234 BOOST_CHECK( result.Accepted( id( "grid-x" ) ) );
235 BOOST_CHECK( !result.Accepted( id( "grid-y" ) ) );
236}
237
238
239BOOST_AUTO_TEST_CASE( StableIdentityBreaksExactRankTie )
240{
241 SNAP_SOURCE_CONTEXT context;
242 context.sourcePoint = { 0, 0 };
243
249
250 SNAP_RESULT result = resolver.Resolve( context );
251
252 BOOST_REQUIRE_EQUAL( result.accepted.size(), 1 );
253 BOOST_CHECK( result.accepted.front() == std::min( id( "a" ), id( "z" ) ) );
254}
255
256
257BOOST_AUTO_TEST_CASE( FixedCursorPathIsIndependentOfCandidateStorageOrder )
258{
259 const std::vector<VECTOR2I> cursorPath = { { 0, 0 }, { 3, 5 }, { 7, 9 }, { 11, 13 } };
260
261 const auto resolvePath = [&]( bool aReverse )
262 {
263 std::vector<std::string> signatures;
264
265 for( const VECTOR2I& cursor : cursorPath )
266 {
267 std::vector<SNAP_CANDIDATE> candidates = {
269 cursor.x + 2, 0.2 ),
271 cursor.x + 1, 0.2 ),
273 cursor.y - 1, 0.1 )
274 };
275
276 if( aReverse )
277 std::reverse( candidates.begin(), candidates.end() );
278
279 SNAP_SOURCE_CONTEXT context;
280 context.sourcePoint = cursor;
282
283 for( SNAP_CANDIDATE& candidate : candidates )
284 resolver.AddCandidate( std::move( candidate ) );
285
286 signatures.push_back( resultSignature( resolver.Resolve( context ) ) );
287 }
288
289 return signatures;
290 };
291
292 BOOST_CHECK( resolvePath( false ) == resolvePath( true ) );
293}
294
295
296BOOST_AUTO_TEST_CASE( ReferenceAffinityOutranksDistance )
297{
298 SNAP_SOURCE_CONTEXT context;
299 context.sourcePoint = { 0, 0 };
300
303 preferred.referenceAffinity = 0;
304
307 fallback.referenceAffinity = 1;
308
310 resolver.AddCandidate( std::move( fallback ) );
311 resolver.AddCandidate( std::move( preferred ) );
312
313 SNAP_RESULT result = resolver.Resolve( context );
314
315 BOOST_CHECK( result.Accepted( id( "preferred" ) ) );
316 BOOST_CHECK( !result.Accepted( id( "fallback" ) ) );
317}
318
319
320BOOST_AUTO_TEST_CASE( InfeasiblePreferredReferenceFallsBack )
321{
322 SNAP_SOURCE_CONTEXT context;
323 context.sourcePoint = { 0, 0 };
324
327 preferred.referenceAffinity = 0;
328
331 fallback.referenceAffinity = 1;
332
334 resolver.AddCandidate( std::move( fallback ) );
335 resolver.AddCandidate( std::move( preferred ) );
336 resolver.SetFeasibilityCallback(
337 [&]( const SNAP_SOURCE_CONTEXT&, const std::vector<SNAP_CANDIDATE>& aTrial )
338 {
340 result.position = context.sourcePoint;
341
342 if( aTrial.empty() )
343 return result;
344
345 if( aTrial.back().id == id( "preferred" ) )
346 {
348 return result;
349 }
350
351 result.position.x = KiROUND( aTrial.back().origin.x );
352 result.remainingDof = 1;
353 return result;
354 } );
355
356 SNAP_RESULT result = resolver.Resolve( context );
357
358 BOOST_CHECK( !result.Accepted( id( "preferred" ) ) );
359 BOOST_CHECK( result.Accepted( id( "fallback" ) ) );
360 BOOST_CHECK_EQUAL( result.position.x, 1 );
361}
362
363
364BOOST_AUTO_TEST_CASE( FullyConstrainedAnchorSuppressesRedundantLayoutFeedback )
365{
366 SNAP_SOURCE_CONTEXT context;
367 context.sourcePoint = { 9, 9 };
368
370 resolver.AddCandidate( SNAP_CANDIDATE::Point( id( "anchor" ), SNAP_PRIORITY_TIER::OBJECT,
371 SNAP_CANDIDATE_SUBTYPE::INTRINSIC_ANCHOR, { 10, 10 }, 1.0 ) );
372
375 resolver.AddCandidate( std::move( alignment ) );
376
377 SNAP_RESULT result = resolver.Resolve( context );
378
379 BOOST_REQUIRE_EQUAL( result.accepted.size(), 1 );
380 BOOST_CHECK( result.Accepted( id( "anchor" ) ) );
381 BOOST_CHECK( !result.Accepted( id( "alignment" ) ) );
382}
383
384
385BOOST_AUTO_TEST_CASE( AdjacentAngleBranchesRemainAlternatives )
386{
387 SNAP_SOURCE_CONTEXT context;
388 context.sourcePoint = { 10, 4 };
389
391 resolver.AddCandidate( SNAP_CANDIDATE::Line( id( "horizontal" ), SNAP_PRIORITY_TIER::ANGLE,
392 SNAP_CANDIDATE_SUBTYPE::ANGLE_BRANCH, { 0, 0 }, { 1.0, 0.0 }, 4.0 ) );
393 resolver.AddCandidate( SNAP_CANDIDATE::Line( id( "vertical" ), SNAP_PRIORITY_TIER::ANGLE,
394 SNAP_CANDIDATE_SUBTYPE::ANGLE_BRANCH, { 0, 0 }, { 0.0, 1.0 }, 10.0 ) );
395
396 SNAP_RESULT result = resolver.Resolve( context );
397
398 BOOST_CHECK_EQUAL( result.position, VECTOR2I( 10, 0 ) );
399 BOOST_CHECK( result.Accepted( id( "horizontal" ) ) );
400 BOOST_CHECK( !result.Accepted( id( "vertical" ) ) );
401}
402
403
404BOOST_AUTO_TEST_CASE( JointFeasibilityRunsFromCommonBaseForEveryTrial )
405{
406 SNAP_SOURCE_CONTEXT context;
407 context.sourcePoint = { 12, 4 };
408 int calls = 0;
409
411 resolver.AddCandidate( SNAP_CANDIDATE::Line( id( "angle" ), SNAP_PRIORITY_TIER::ANGLE,
412 SNAP_CANDIDATE_SUBTYPE::ANGLE_BRANCH, { 0, 0 }, { 1.0, 0.0 }, 4.0 ) );
413 resolver.AddCandidate( SNAP_CANDIDATE::Point( id( "object" ), SNAP_PRIORITY_TIER::OBJECT,
415 resolver.AddCandidate( SNAP_CANDIDATE::AxisX( id( "grid-x" ), SNAP_PRIORITY_TIER::GRID,
417 resolver.SetFeasibilityCallback(
418 [&]( const SNAP_SOURCE_CONTEXT&, const std::vector<SNAP_CANDIDATE>& aTrial )
419 {
420 ++calls;
422
423 if( aTrial.empty() )
424 {
425 result.position = context.sourcePoint;
426 return result;
427 }
428
429 if( aTrial.back().id == id( "object" ) )
430 {
432 return result;
433 }
434
435 result.position = aTrial.back().id == id( "grid-x" ) ? VECTOR2I( 10, 0 ) : VECTOR2I( 12, 0 );
436 result.remainingDof = aTrial.size() == 1 ? 1 : 0;
437 return result;
438 } );
439
440 SNAP_RESULT result = resolver.Resolve( context );
441
442 BOOST_CHECK_EQUAL( calls, 4 );
443 BOOST_CHECK_EQUAL( result.position, VECTOR2I( 10, 0 ) );
444 BOOST_CHECK( result.Accepted( id( "angle" ) ) );
445 BOOST_CHECK( !result.Accepted( id( "object" ) ) );
446 BOOST_CHECK( result.Accepted( id( "grid-x" ) ) );
447}
448
449
450BOOST_AUTO_TEST_CASE( MandatoryPrefixCompletesAfterDeadline )
451{
452 using CLOCK = std::chrono::steady_clock;
453
454 SNAP_SOURCE_CONTEXT context;
455 context.sourcePoint = { 8, 3 };
456 CLOCK::time_point now;
457 int optionalCalls = 0;
458
460 resolver.SetClock(
461 [&]
462 {
463 return now;
464 } );
465 resolver.SetDeadline( std::chrono::milliseconds( 8 ) );
466 resolver.SetRetainedCandidate( id( "retained" ) );
467 resolver.AddCandidate( SNAP_CANDIDATE::Line( id( "angle-a" ), SNAP_PRIORITY_TIER::ANGLE,
468 SNAP_CANDIDATE_SUBTYPE::ANGLE_BRANCH, { 0, 0 }, { 1.0, 0.0 }, 3.0 ) );
469 resolver.AddCandidate( SNAP_CANDIDATE::Line( id( "angle-b" ), SNAP_PRIORITY_TIER::ANGLE,
470 SNAP_CANDIDATE_SUBTYPE::ANGLE_BRANCH, { 0, 0 }, { 1.0, 1.0 }, 4.0 ) );
471 resolver.AddCandidate( SNAP_CANDIDATE::Point( id( "retained" ), SNAP_PRIORITY_TIER::OBJECT,
473 resolver.AddCandidate( SNAP_CANDIDATE::AxisX( id( "intrinsic" ), SNAP_PRIORITY_TIER::OBJECT,
475 resolver.AddCandidate( SNAP_CANDIDATE::AxisY( id( "optional" ), SNAP_PRIORITY_TIER::GRID,
477 resolver.SetFeasibilityCallback(
478 [&]( const SNAP_SOURCE_CONTEXT&, const std::vector<SNAP_CANDIDATE>& aTrial )
479 {
481 result.position = context.sourcePoint;
482
483 if( !aTrial.empty() )
484 {
485 now += std::chrono::milliseconds( 3 );
486
487 if( aTrial.back().id == id( "optional" ) )
488 ++optionalCalls;
489 }
490
491 return result;
492 } );
493
494 SNAP_RESULT result = resolver.Resolve( context );
495
496 BOOST_CHECK_EQUAL( optionalCalls, 0 );
498 BOOST_CHECK( result.Accepted( id( "angle-a" ) ) );
499 BOOST_CHECK( result.Accepted( id( "retained" ) ) );
500 BOOST_CHECK( result.Accepted( id( "intrinsic" ) ) );
501}
502
503
504BOOST_AUTO_TEST_CASE( RetainedAngleBranchUsesFivePixelHysteresis )
505{
506 SNAP_SOURCE_CONTEXT context;
507 context.sourcePoint = { 10, 4 };
508
510 resolver.SetRetainedCandidate( id( "horizontal" ) );
511 resolver.AddCandidate( SNAP_CANDIDATE::Line( id( "horizontal" ), SNAP_PRIORITY_TIER::ANGLE,
512 SNAP_CANDIDATE_SUBTYPE::ANGLE_BRANCH, { 0, 0 }, { 1.0, 0.0 }, 0.16 ) );
513 resolver.AddCandidate( SNAP_CANDIDATE::Line( id( "diagonal" ), SNAP_PRIORITY_TIER::ANGLE,
514 SNAP_CANDIDATE_SUBTYPE::ANGLE_BRANCH, { 0, 0 }, { 1.0, 1.0 }, 0.02 ) );
515
516 SNAP_RESULT result = resolver.Resolve( context );
517
518 BOOST_CHECK( result.Accepted( id( "horizontal" ) ) );
519 BOOST_CHECK( !result.Accepted( id( "diagonal" ) ) );
520}
521
522
523BOOST_AUTO_TEST_CASE( StickyCandidateHoldsWithinHysteresis )
524{
525 // A snap accepted on the previous resolve keeps winning even when a competitor for the same
526 // axis is nearer, as long as the gap is inside the ranking hysteresis. Without this the
527 // equal-spacing solutions jitter frame to frame.
528 SNAP_SOURCE_CONTEXT context;
529 context.sourcePoint = { 4, 0 };
530
532 resolver.SetStickyCandidates( { id( "held" ) } );
533 resolver.AddCandidate( SNAP_CANDIDATE::AxisX( id( "held" ), SNAP_PRIORITY_TIER::OBJECT,
535 resolver.AddCandidate( SNAP_CANDIDATE::AxisX( id( "nearer" ), SNAP_PRIORITY_TIER::OBJECT,
537
538 SNAP_RESULT result = resolver.Resolve( context );
539
540 BOOST_CHECK( result.Accepted( id( "held" ) ) );
541 BOOST_CHECK( !result.Accepted( id( "nearer" ) ) );
542}
543
544
545BOOST_AUTO_TEST_CASE( StickyCandidateReleasesBeyondHysteresis )
546{
547 // Once a competitor is clearly nearer (beyond the hysteresis margin) the sticky snap yields, so
548 // the hysteresis stabilizes selection without trapping the cursor on a stale solution.
549 SNAP_SOURCE_CONTEXT context;
550 context.sourcePoint = { 4, 0 };
551
553 resolver.SetStickyCandidates( { id( "held" ) } );
554 resolver.AddCandidate( SNAP_CANDIDATE::AxisX( id( "held" ), SNAP_PRIORITY_TIER::OBJECT,
556 resolver.AddCandidate( SNAP_CANDIDATE::AxisX( id( "nearer" ), SNAP_PRIORITY_TIER::OBJECT,
558
559 SNAP_RESULT result = resolver.Resolve( context );
560
561 BOOST_CHECK( result.Accepted( id( "nearer" ) ) );
562 BOOST_CHECK( !result.Accepted( id( "held" ) ) );
563}
564
565
566BOOST_AUTO_TEST_CASE( TraceCallbackReportsRankingTrialsAndResult )
567{
568 SNAP_SOURCE_CONTEXT context;
569 context.sourcePoint = { 4, 0 };
570 std::vector<std::string> trace;
571
573 resolver.SetTraceCallback(
574 [&]( const std::string& aMessage )
575 {
576 trace.push_back( aMessage );
577 } );
578 resolver.SetStickyCandidates( { id( "held" ) } );
581 resolver.AddCandidate( std::move( held ) );
582 resolver.AddCandidate( SNAP_CANDIDATE::AxisX( id( "nearer" ), SNAP_PRIORITY_TIER::OBJECT,
584
585 resolver.Resolve( context );
586
587 const auto contains = [&]( const char* aText )
588 {
589 return std::any_of( trace.begin(), trace.end(),
590 [&]( const std::string& aMessage )
591 {
592 return aMessage.find( aText ) != std::string::npos;
593 } );
594 };
595
596 BOOST_CHECK( contains( "rank index=0" ) );
597 BOOST_CHECK( contains( "sticky=1" ) );
598 BOOST_CHECK( contains( "trial id=" ) );
599 BOOST_CHECK( contains( "accepted=1" ) );
600 BOOST_CHECK( contains( "result status=SUCCESS position=(0,0) accepted=[" ) );
601}
602
603
604BOOST_AUTO_TEST_CASE( IntrinsicObjectOutranksIncompatibleRetainedTarget )
605{
606 SNAP_SOURCE_CONTEXT context;
607 context.sourcePoint = { 3, 4 };
608
610 resolver.SetRetainedCandidate( id( "retained" ) );
611 resolver.AddCandidate( SNAP_CANDIDATE::Point( id( "retained" ), SNAP_PRIORITY_TIER::OBJECT,
612 SNAP_CANDIDATE_SUBTYPE::FINITE_MANIFOLD, { 10, 10 }, 0.0 ) );
613 resolver.AddCandidate( SNAP_CANDIDATE::Point( id( "intrinsic" ), SNAP_PRIORITY_TIER::OBJECT,
615
616 SNAP_RESULT result = resolver.Resolve( context );
617
618 BOOST_CHECK( result.Accepted( id( "intrinsic" ) ) );
619 BOOST_CHECK( !result.Accepted( id( "retained" ) ) );
620 BOOST_CHECK_EQUAL( result.position, VECTOR2I( 2, 2 ) );
621}
622
623
624BOOST_AUTO_TEST_CASE( RunningTrialFinishesButNoFurtherTrialStartsAfterOverrun )
625{
626 using CLOCK = std::chrono::steady_clock;
627
628 SNAP_SOURCE_CONTEXT context;
629 context.sourcePoint = { 4, 6 };
630 CLOCK::time_point now;
631 int trials = 0;
632
634 resolver.SetClock(
635 [&]
636 {
637 return now;
638 } );
639 resolver.SetDeadline( std::chrono::milliseconds( 8 ) );
640 resolver.AddCandidate( SNAP_CANDIDATE::AxisX( id( "first" ), SNAP_PRIORITY_TIER::OBJECT,
642 resolver.AddCandidate( SNAP_CANDIDATE::AxisY( id( "second" ), SNAP_PRIORITY_TIER::OBJECT,
644 resolver.SetFeasibilityCallback(
645 [&]( const SNAP_SOURCE_CONTEXT&, const std::vector<SNAP_CANDIDATE>& )
646 {
648 result.position = context.sourcePoint;
649 ++trials;
650
651 if( trials == 2 )
652 now += std::chrono::milliseconds( 9 );
653
654 return result;
655 } );
656
657 SNAP_RESULT result = resolver.Resolve( context );
658
659 BOOST_CHECK_EQUAL( trials, 2 );
661 BOOST_CHECK( result.Accepted( std::min( id( "first" ), id( "second" ) ) ) );
662 BOOST_CHECK( !result.Accepted( std::max( id( "first" ), id( "second" ) ) ) );
663}
664
665
666BOOST_AUTO_TEST_CASE( AcceptedCandidateForwardsAllGuides )
667{
668 SNAP_SOURCE_CONTEXT context;
669 context.sourcePoint = { 10, 10 };
670
673 accepted.guides = { { { 0, 0 }, { 5, 0 }, SNAP_GUIDE_STYLE::DIMENSION_BRACKET },
674 { { 10, 0 }, { 15, 0 }, SNAP_GUIDE_STYLE::DIMENSION_BRACKET } };
675
678 rejected.guides = { { { 20, 0 }, { 25, 0 }, SNAP_GUIDE_STYLE::DIMENSION_BRACKET } };
679
681 resolver.AddCandidate( std::move( rejected ) );
682 resolver.AddCandidate( std::move( accepted ) );
683
684 SNAP_RESULT result = resolver.Resolve( context );
685
686 BOOST_REQUIRE_EQUAL( result.guides.size(), 2 );
687 BOOST_CHECK_EQUAL( result.guides[0].start, VECTOR2I( 0, 0 ) );
688 BOOST_CHECK_EQUAL( result.guides[0].end, VECTOR2I( 5, 0 ) );
689 BOOST_CHECK_EQUAL( result.guides[1].start, VECTOR2I( 10, 0 ) );
690 BOOST_CHECK_EQUAL( result.guides[1].end, VECTOR2I( 15, 0 ) );
691}
692
693
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
Definition kiid.h:46
std::array< uint8_t, 16 > AsBytes() const
Definition kiid.cpp:276
static KIID FromBytes(const std::array< uint8_t, 16 > &aBytes)
Definition kiid.cpp:250
static KIID FromName(const std::string &aName)
Return a KIID derived from a name, the same name always gives the same KIID.
Definition kiid.cpp:237
static FILENAME_RESOLVER * resolver
std::chrono::steady_clock CLOCK
SNAP_STABLE_ID MakeIntersectionSnapId(const SNAP_STABLE_ID &aFirst, const SNAP_STABLE_ID &aSecond, int aSolutionBranch)
SNAP_STABLE_ID MakePointSnapId(SNAP_ID_KIND aKind, const VECTOR2I &aPoint, int aFeatureIndex=0)
SNAP_STABLE_ID MakeCompositeSnapId(SNAP_ID_KIND aKind, const std::vector< SNAP_TARGET_ID > &aTargets, int aFeatureIndex=0)
SNAP_STABLE_ID MakeDerivedSnapId(SNAP_ID_KIND aKind, const SNAP_STABLE_ID &aSource, int aFeatureIndex=0, int aSolutionBranch=0)
std::array< uint8_t, 16 > SNAP_TARGET_ID
static SNAP_CANDIDATE Point(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, const VECTOR2I &aPoint, double aResidual)
static SNAP_CANDIDATE Line(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, const VECTOR2I &aOrigin, const VECTOR2D &aDirection, double aResidual)
static SNAP_CANDIDATE AxisY(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, int aCoordinate, double aResidual)
static SNAP_CANDIDATE AxisX(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, int aCoordinate, double aResidual)
std::vector< SNAP_GUIDE > guides
SNAP_GUIDE_STYLE style
VECTOR2I end
VECTOR2I start
std::vector< double > quantizedResiduals
VECTOR2I position
std::vector< SNAP_GUIDE > guides
std::vector< SNAP_STABLE_ID > accepted
SNAP_RESULT_STATUS status
SNAP_ID_KIND kind
SNAP_TARGET_ID target
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(StableIdentityUsesTypedKindAndTarget)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683