20#include <boost/test/unit_test.hpp>
49 return { aPoint.
y, aPoint.
x };
73 default:
return aKind;
87 guide.
end = transpose( guide.
end );
94void checkTransposeEquivalent( std::vector<SNAP_CANDIDATE> aExpected, std::vector<SNAP_CANDIDATE> aTransposed )
97 candidate = transpose( std::move( candidate ) );
101 return aLeft.
id < aRight.id;
103 std::sort( aExpected.begin(), aExpected.end(), byId );
104 std::sort( aTransposed.begin(), aTransposed.end(), byId );
105 BOOST_REQUIRE_EQUAL( aExpected.size(), aTransposed.size() );
107 for(
size_t i = 0; i < aExpected.size(); ++i )
123 BOOST_REQUIRE_EQUAL(
expected.guides.size(),
actual.guides.size() );
125 for(
size_t guide = 0; guide <
expected.guides.size(); ++guide )
129 BOOST_CHECK(
expected.guides[guide].style ==
actual.guides[guide].style );
155 provider.
AddPath( { featureId(
"horizontal",
"edge" ),
SEG( { 0, 0 }, { 10, 0 } ) } );
156 provider.
AddPath( { featureId(
"vertical",
"edge" ),
SEG( { 20, -10 }, { 20, 10 } ) } );
162 BOOST_CHECK( std::none_of( finite.begin(), finite.end(),
165 return aCandidate.subtype == SNAP_CANDIDATE_SUBTYPE::INTERSECTION;
170 BOOST_CHECK( std::any_of( extended.begin(), extended.end(),
173 return aCandidate.subtype == SNAP_CANDIDATE_SUBTYPE::INTERSECTION;
188 const auto intersectionIds = [&](
const VECTOR2I& aCursor )
192 std::map<VECTOR2I, SNAP_STABLE_ID> ids;
205 auto upperCursor = intersectionIds( { 0, 10 } );
206 auto lowerCursor = intersectionIds( { 0, -10 } );
207 BOOST_REQUIRE_EQUAL( upperCursor.size(), 2 );
208 BOOST_CHECK( upperCursor == lowerCursor );
210 BOOST_CHECK( upperCursor.begin()->second.target
218 provider.
AddPath( { featureId(
"first",
"circle" ),
CIRCLE( { -3, 0 }, 5 ) } );
219 provider.
AddPath( { featureId(
"second",
"circle" ),
CIRCLE( { 3, 0 }, 5 ) } );
224 const auto upperIntersectionId = [&](
int aRadius )
230 return std::optional<SNAP_STABLE_ID>( candidate.id );
234 return std::optional<SNAP_STABLE_ID>();
237 std::optional<SNAP_STABLE_ID> isolated = upperIntersectionId( 1 );
238 std::optional<SNAP_STABLE_ID> withSibling = upperIntersectionId( 10 );
241 BOOST_CHECK( *isolated == *withSibling );
259 BOOST_REQUIRE_EQUAL( candidates.size(), 1 );
264 resolver.AddCandidate( candidates.front() );
273 provider.
AddPath( { featureId(
"polygon",
"moving-edge" ),
SEG( { 0, 0 }, { 10, 0 } ) } );
274 provider.
AddPath( { featureId(
"polygon",
"fixed-edge" ),
SEG( { 0, 10 }, { 10, 10 } ) } );
278 context.
movingFeatures.push_back( featureId(
"polygon",
"moving-edge" ) );
283 BOOST_CHECK( std::none_of( candidates.begin(), candidates.end(),
286 return aCandidate.id == featureId(
"polygon",
"moving-edge" );
288 BOOST_CHECK( std::any_of( candidates.begin(), candidates.end(),
291 return aCandidate.id == featureId(
"polygon",
"fixed-edge" );
299 provider.
AddPath( { featureId(
"circle",
"outline" ),
CIRCLE( { 0, 0 }, 5 ) } );
305 std::vector<SNAP_CANDIDATE> candidates = provider.
CollectTangentNormal( context, 20,
false,
true );
307 BOOST_REQUIRE_EQUAL( candidates.size(), 2 );
308 BOOST_CHECK( std::any_of( candidates.begin(), candidates.end(),
311 return aCandidate.origin == VECTOR2D( 5.0, 0.0 );
313 BOOST_CHECK( std::any_of( candidates.begin(), candidates.end(),
316 return aCandidate.origin == VECTOR2D( -5.0, 0.0 );
324 provider.
AddPath( { featureId(
"circle",
"outline" ),
CIRCLE( { 0, 0 }, 5 ) } );
330 const auto positiveNormalId = [&](
bool aTangentEnabled )
336 return std::optional<SNAP_STABLE_ID>( candidate.id );
340 return std::optional<SNAP_STABLE_ID>();
343 std::optional<SNAP_STABLE_ID> withoutTangents = positiveNormalId(
false );
344 std::optional<SNAP_STABLE_ID> withTangents = positiveNormalId(
true );
347 BOOST_CHECK( *withoutTangents == *withTangents );
360 std::vector<SNAP_CANDIDATE> candidates = provider.
CollectTangentNormal( context, 20,
false,
true );
362 BOOST_REQUIRE_EQUAL( candidates.size(), 1 );
363 BOOST_CHECK_GT( candidates.front().origin.x, 0.0 );
367 resolver.AddCandidate( candidates.front() );
377 provider.
AddPath( { featureId(
"circle",
"outline" ),
CIRCLE( { 0, 0 }, 5 ) } );
385 resolver.AddCandidate( std::move( candidate ) );
393 BOOST_CHECK(
result.Accepted( featureId(
"circle",
"outline" ) ) );
394 BOOST_CHECK(
result.Accepted( featureId(
"grid",
"y" ) ) );
402 provider.
AddBounds( { featureId(
"target",
"bounds" ),
BOX2I( { 0, 0 }, { 20, 20 } ) } );
408 std::vector<SNAP_CANDIDATE> candidates = provider.
CollectAlignment( context, 50 );
410 BOOST_CHECK( std::any_of( candidates.begin(), candidates.end(),
413 return aCandidate.relation == SNAP_RELATION::BBOX_ALIGNMENT
414 && aCandidate.origin.x == 5.0;
416 BOOST_CHECK( std::any_of( candidates.begin(), candidates.end(),
419 return aCandidate.relation == SNAP_RELATION::BBOX_ALIGNMENT
420 && aCandidate.origin.y == 25.0;
424 std::find_if( candidates.begin(), candidates.end(),
427 return aCandidate.relation == SNAP_RELATION::BBOX_ALIGNMENT && aCandidate.origin.x == 5.0;
431 resolver.AddCandidate( *xCandidate );
439 provider.
AddBounds( { featureId(
"target",
"bounds" ),
BOX2I( { 0, 0 }, { 20, 20 } ) } );
446 std::vector<SNAP_CANDIDATE> candidates = provider.
CollectAlignment( context, 50 );
447 auto centerToCenter =
448 std::find_if( candidates.begin(), candidates.end(),
451 return aCandidate.id.kind == SNAP_ID_KIND::BOUNDS_Y && aCandidate.id.featureIndex == 4;
458 resolver.SetStickyCandidates( { centerToCenter->id } );
459 resolver.AddCandidate( *centerToCenter );
468 provider.
AddBounds( { featureId(
"target",
"bounds" ),
BOX2I( { 0, 0 }, { 20, 20 } ) } );
475 std::vector<SNAP_CANDIDATE> candidates = provider.
CollectAlignment( context, 50 );
476 auto centerToCenter =
477 std::find_if( candidates.begin(), candidates.end(),
480 return aCandidate.id.kind == SNAP_ID_KIND::BOUNDS_Y && aCandidate.id.featureIndex == 4;
484 BOOST_REQUIRE_EQUAL( centerToCenter->guides.size(), 1 );
493 provider.
AddBounds( { featureId(
"target",
"bounds" ),
BOX2I( { 0, 0 }, { 20, 20 } ) } );
500 std::vector<SNAP_CANDIDATE> candidates = provider.
CollectAlignment( context, 50 );
501 auto centerToCenter =
502 std::find_if( candidates.begin(), candidates.end(),
505 return aCandidate.id.kind == SNAP_ID_KIND::BOUNDS_X && aCandidate.id.featureIndex == 4;
508 std::find_if( candidates.begin(), candidates.end(),
511 return aCandidate.id.kind == SNAP_ID_KIND::BOUNDS_X && aCandidate.id.featureIndex == 0;
517 BOOST_CHECK_GT( leftToLeft->referenceAffinity, centerToCenter->referenceAffinity );
524 provider.
AddBounds( { featureId(
"target",
"bounds" ),
BOX2I( { 0, 0 }, { 20, 20 } ) } );
526 { featureId(
"target-pad",
"center" ), { 0, 10 } } );
533 std::vector<SNAP_CANDIDATE> candidates = provider.
CollectAlignment( context, 50 );
534 auto padCenter = std::find_if( candidates.begin(), candidates.end(),
537 return aCandidate.id.kind == SNAP_ID_KIND::ANCHOR_POINT_X;
539 auto boundsFallback = std::find_if( candidates.begin(), candidates.end(),
542 return aCandidate.id.kind == SNAP_ID_KIND::BOUNDS_X;
549 BOOST_CHECK_GT( boundsFallback->referenceAffinity, padCenter->referenceAffinity );
556 provider.
AddBounds( { featureId(
"target",
"bounds" ),
BOX2I( { 0, 0 }, { 20, 20 } ) } );
566 resolver.AddCandidate( std::move( candidate ) );
589 provider.
AddBounds( { featureId( std::to_string(
index ).c_str(),
"bounds" ),
603 const std::array<SNAP_OBJECT_BOUNDS, 2> bounds = {
610 const auto makeProvider = [&](
bool aTranspose )
617 bound.bounds = transpose( bound.bounds );
619 provider.
AddBounds( std::move( bound ) );
654 checkTransposeEquivalent( original.
CollectAlignment( makeContext( reference,
false ), 100 ),
660 std::vector<SNAP_CANDIDATE> originalSpacing =
662 std::vector<SNAP_CANDIDATE> transposedSpacing =
665 for(
int branch : { -1, 0, 1 } )
667 BOOST_CHECK( std::any_of( originalSpacing.begin(), originalSpacing.end(),
670 return aCandidate.id.solutionBranch == branch
671 && ( aCandidate.id.kind == SNAP_ID_KIND::EQUAL_GAP_X
672 || aCandidate.id.kind == SNAP_ID_KIND::COPY_GAP_X );
676 checkTransposeEquivalent( std::move( originalSpacing ), std::move( transposedSpacing ) );
683 provider.
AddBounds( { featureId(
"left",
"bounds" ),
BOX2I( { 0, 0 }, { 10, 10 } ) } );
684 provider.
AddBounds( { featureId(
"right",
"bounds" ),
BOX2I( { 30, 0 }, { 10, 10 } ) } );
692 BOOST_CHECK( std::any_of( candidates.begin(), candidates.end(),
695 return aCandidate.id.kind == SNAP_ID_KIND::EQUAL_GAP_X
696 && aCandidate.relation == SNAP_RELATION::BBOX_EQUAL_GAP
697 && aCandidate.origin.x == 20.0;
700 auto equalGap = std::find_if( candidates.begin(), candidates.end(),
703 return aCandidate.id.kind == SNAP_ID_KIND::EQUAL_GAP_X;
715 provider.
AddBounds( { featureId(
"left",
"bounds" ),
BOX2I( { 0, 0 }, { 10, 10 } ) } );
716 provider.
AddBounds( { featureId(
"right",
"bounds" ),
BOX2I( { 30, 0 }, { 10, 10 } ) } );
723 auto equalGap = std::find_if( candidates.begin(), candidates.end(),
726 return aCandidate.id.kind == SNAP_ID_KIND::EQUAL_GAP_X;
730 BOOST_REQUIRE_EQUAL( equalGap->guides.size(), 2 );
743 provider.
AddBounds( { featureId(
"top",
"bounds" ),
BOX2I( { 0, 0 }, { 10, 10 } ) } );
744 provider.
AddBounds( { featureId(
"bottom",
"bounds" ),
BOX2I( { 0, 30 }, { 10, 10 } ) } );
751 auto equalGap = std::find_if( candidates.begin(), candidates.end(),
754 return aCandidate.id.kind == SNAP_ID_KIND::EQUAL_GAP_Y;
758 BOOST_REQUIRE_EQUAL( equalGap->guides.size(), 2 );
771 provider.
AddBounds( { featureId(
"left",
"bounds" ),
BOX2I( { 0, 0 }, { 10, 10 } ) } );
772 provider.
AddBounds( { featureId(
"right",
"bounds" ),
BOX2I( { 30, 0 }, { 10, 10 } ) } );
780 BOOST_CHECK( std::any_of( candidates.begin(), candidates.end(),
783 return aCandidate.id.kind == SNAP_ID_KIND::EQUAL_GAP_X && aCandidate.origin.x == 17.0;
791 provider.
AddBounds( { featureId(
"left",
"bounds" ),
BOX2I( { 0, 0 }, { 10, 10 } ) } );
792 provider.
AddBounds( { featureId(
"right",
"bounds" ),
BOX2I( { 30, 0 }, { 10, 10 } ) } );
800 auto equalGap = std::find_if( candidates.begin(), candidates.end(),
803 return aCandidate.id.kind == SNAP_ID_KIND::EQUAL_GAP_X;
815 provider.
AddBounds( { featureId(
"first",
"bounds" ),
BOX2I( { 0, 0 }, { 10, 10 } ) } );
816 provider.
AddBounds( { featureId(
"second",
"bounds" ),
BOX2I( { 20, 0 }, { 10, 10 } ) } );
824 auto copiedGap = std::find_if( candidates.begin(), candidates.end(),
827 return aCandidate.id.kind == SNAP_ID_KIND::COPY_GAP_X
828 && aCandidate.id.solutionBranch > 0 && aCandidate.origin.x == 45.0;
832 BOOST_REQUIRE_EQUAL( copiedGap->guides.size(), 2 );
843 provider.
AddBounds( { featureId(
"first",
"bounds" ),
BOX2I( { 0, 0 }, { 10, 10 } ) } );
844 provider.
AddBounds( { featureId(
"second",
"bounds" ),
BOX2I( { 20, 0 }, { 10, 10 } ) } );
851 auto copiedGap = std::find_if( candidates.begin(), candidates.end(),
854 return aCandidate.id.kind == SNAP_ID_KIND::COPY_GAP_X
855 && aCandidate.id.solutionBranch < 0 && aCandidate.origin.x == -15.0;
859 BOOST_REQUIRE_EQUAL( copiedGap->guides.size(), 2 );
874 { featureId( std::to_string(
index ).c_str(),
"bounds" ),
BOX2I( {
index * 30, 0 }, { 10, 10 } ) } );
888 provider.
AddBounds( { featureId(
"child",
"bounds" ),
BOX2I( { 0, 0 }, { 10, 10 } ),
889 featureId(
"selection",
"bounds" ).target } );
894 context.
movingItem = featureId(
"selection",
"bounds" );
905 for(
int i = 0; i < 10000; ++i )
907 provider.
AddPath( { featureId( std::to_string( i ).c_str(),
"edge" ),
913 std::vector<std::chrono::microseconds> samples;
915 for(
int run = 0; run < 20; ++run )
917 std::clock_t start = std::clock();
919 samples.emplace_back( ( std::clock() - start ) * 1000000 / CLOCKS_PER_SEC );
923 std::sort( samples.begin(), samples.end() );
924 std::chrono::microseconds p95 = samples[18];
925 BOOST_TEST_MESSAGE(
"10k-object candidate generation p95: " << p95.count() <<
" us" );
926 BOOST_CHECK_LE( p95.count(), 4000 );
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
constexpr const Vec & GetOrigin() const
constexpr const SizeVec & GetSize() const
std::array< uint8_t, 16 > AsBytes() const
static KIID FromName(const std::string &aName)
Return a KIID derived from a name, the same name always gives the same KIID.
std::vector< SNAP_CANDIDATE > CollectObjectGeometry(const SNAP_SOURCE_CONTEXT &aContext, int aRadius) const
std::vector< SNAP_CANDIDATE > CollectEqualSpacing(const SNAP_SOURCE_CONTEXT &aContext, int aRadius) const
void AddBounds(SNAP_OBJECT_BOUNDS aBounds)
void ActivateExtension(const SNAP_STABLE_ID &aId)
std::vector< SNAP_CANDIDATE > CollectTangentNormal(const SNAP_SOURCE_CONTEXT &aContext, int aRadius, bool aTangentEnabled, bool aNormalEnabled) const
void AddPath(SNAP_OBJECT_PATH aPath)
void AddAlignmentPoint(SNAP_ALIGNMENT_POINT aPoint)
std::vector< SNAP_CANDIDATE > CollectAlignment(const SNAP_SOURCE_CONTEXT &aContext, int aRadius) const
Define a general 2D-vector/point.
static constexpr EDA_ANGLE ANGLE_90
static FILENAME_RESOLVER * resolver
SNAP_STABLE_ID MakeIntersectionSnapId(const SNAP_STABLE_ID &aFirst, const SNAP_STABLE_ID &aSecond, int aSolutionBranch)
std::array< uint8_t, 16 > SNAP_TARGET_ID
A named point a parent object offers for alignment, such as a pad center or a pin end.
static SNAP_CANDIDATE AxisY(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, int aCoordinate, double aResidual)
std::vector< SNAP_GUIDE > guides
bool constructionExtensions
bool alignmentDistribution
std::optional< VECTOR2I > stationarySourceLeg
std::vector< SNAP_STABLE_ID > movingFeatures
std::optional< BOX2I > movingBounds
std::optional< VECTOR2I > movingReferencePoint
std::optional< SNAP_STABLE_ID > movingItem
std::vector< SNAP_STABLE_ID > stationarySelfFeatures
SNAP_REFERENCE_PREFERENCE referencePreference
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_AUTO_TEST_CASE(GroupDefaultsPreserveConservativeInference)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
VECTOR2< double > VECTOR2D