Implement SHAPE_LINE_CHAIN::DetectArcs and add qa test
This commit is contained in:
parent
14c3d9055e
commit
3ee8b4825c
|
@ -196,6 +196,12 @@ public:
|
|||
const SHAPE_LINE_CHAIN ConvertToPolyline( double aAccuracy = DefaultAccuracyForPCB(),
|
||||
double* aEffectiveAccuracy = nullptr ) const;
|
||||
|
||||
bool operator==( SHAPE_ARC const& aArc ) const
|
||||
{
|
||||
return ( aArc.m_start == m_start ) && ( aArc.m_end == m_end ) && ( aArc.m_mid == m_mid )
|
||||
&& ( aArc.m_width == m_width );
|
||||
}
|
||||
|
||||
private:
|
||||
bool ccw( const VECTOR2I& aA, const VECTOR2I& aB, const VECTOR2I& aC ) const
|
||||
{
|
||||
|
@ -214,4 +220,7 @@ private:
|
|||
BOX2I m_bbox;
|
||||
};
|
||||
|
||||
// Required for Boost Test BOOST_CHECK_EQUAL:
|
||||
std::ostream& operator<<( std::ostream& aStream, const SHAPE_ARC& aArc );
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Copyright (C) 2013 CERN
|
||||
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
||||
* Copyright (C) 2013-2020 KiCad Developers, see AUTHORS.txt for contributors.
|
||||
* Copyright (C) 2013-2021 KiCad Developers, see AUTHORS.txt for contributors.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
@ -612,21 +612,12 @@ public:
|
|||
/**
|
||||
* Detects arcs in a chain and converts them from segments to true arcs by adding an arc
|
||||
* and the associated references. The original segment points are not changed.
|
||||
* @return reference to self
|
||||
*/
|
||||
SHAPE_LINE_CHAIN& DetectArcs();
|
||||
|
||||
/**
|
||||
* Convert an arc to only a point chain by removing the arc and references.
|
||||
*
|
||||
* @param aArcIndex is an index of the arc to convert to points.
|
||||
* @param aArcs List of possible arcs that might be in this chain
|
||||
* @param aMargin Maximum acceptable deviation from the found points to the arc (defaults
|
||||
* to ARC_HIGH_DEF)
|
||||
*/
|
||||
void convertArc( ssize_t aArcIndex );
|
||||
|
||||
/**
|
||||
* Create a new Clipper path from the SHAPE_LINE_CHAIN in a given orientation.
|
||||
*/
|
||||
ClipperLib::Path convertToClipper( bool aRequiredOrientation ) const;
|
||||
void DetectArcs( const std::vector<SHAPE_ARC>& aArcs, int aMargin = PCB_IU_PER_MM * 0.005 );
|
||||
|
||||
/**
|
||||
* Find the segment nearest the given point.
|
||||
|
@ -755,6 +746,21 @@ public:
|
|||
virtual size_t GetPointCount() const override { return PointCount(); }
|
||||
virtual size_t GetSegmentCount() const override { return SegmentCount(); }
|
||||
|
||||
protected:
|
||||
friend class SHAPE_POLY_SET;
|
||||
|
||||
/**
|
||||
* Convert an arc to only a point chain by removing the arc and references
|
||||
*
|
||||
* @param aArcIndex index of the arc to convert to points
|
||||
*/
|
||||
void convertArc( ssize_t aArcIndex );
|
||||
|
||||
/**
|
||||
* Create a new Clipper path from the SHAPE_LINE_CHAIN in a given orientation
|
||||
*/
|
||||
ClipperLib::Path convertToClipper( bool aRequiredOrientation ) const;
|
||||
|
||||
private:
|
||||
|
||||
constexpr static ssize_t SHAPE_IS_PT = -1;
|
||||
|
|
|
@ -30,6 +30,14 @@
|
|||
#include <trigo.h>
|
||||
|
||||
|
||||
std::ostream& operator<<( std::ostream& aStream, const SHAPE_ARC& aArc )
|
||||
{
|
||||
aStream << "Arc( P0=" << aArc.GetP0() << " P1=" << aArc.GetP1() << " Mid=" << aArc.GetArcMid()
|
||||
<< " Width=" << aArc.GetWidth() << " )";
|
||||
return aStream;
|
||||
}
|
||||
|
||||
|
||||
SHAPE_ARC::SHAPE_ARC( const VECTOR2I& aArcCenter, const VECTOR2I& aArcStartPoint,
|
||||
double aCenterAngle, int aWidth ) :
|
||||
SHAPE( SH_ARC ), m_width( aWidth )
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
|
||||
*
|
||||
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
||||
* Copyright (C) 2013-2021 KiCad Developers, see AUTHORS.txt for contributors.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
@ -31,10 +32,12 @@
|
|||
|
||||
#include <clipper.hpp>
|
||||
#include <geometry/seg.h> // for SEG, OPT_VECTOR2I
|
||||
#include <geometry/circle.h> // for CIRCLE
|
||||
#include <geometry/shape_line_chain.h>
|
||||
#include <math/box2.h> // for BOX2I
|
||||
#include <math/util.h> // for rescale
|
||||
#include <math/vector2d.h> // for VECTOR2, VECTOR2I
|
||||
#include <trigo.h> // for RAD2DECIDEG, GetArcAngle
|
||||
|
||||
class SHAPE;
|
||||
|
||||
|
@ -79,19 +82,6 @@ ClipperLib::Path SHAPE_LINE_CHAIN::convertToClipper( bool aRequiredOrientation )
|
|||
}
|
||||
|
||||
|
||||
void SHAPE_LINE_CHAIN::ConvertToArcs( const std::vector<SHAPE_ARC>& aArcs )
|
||||
{
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int direction = 1;
|
||||
|
||||
for( SHAPE_ARC& arc :aArcs )
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//TODO(SH): Adjust this into two functions: one to convert and one to split the arc into two arcs
|
||||
void SHAPE_LINE_CHAIN::convertArc( ssize_t aArcIndex )
|
||||
{
|
||||
|
@ -236,17 +226,103 @@ bool SHAPE_LINE_CHAIN_BASE::Collide( const SEG& aSeg, int aClearance, int* aActu
|
|||
}
|
||||
|
||||
|
||||
SHAPE_LINE_CHAIN& SHAPE_LINE_CHAIN::DetectArcs()
|
||||
void SHAPE_LINE_CHAIN::DetectArcs( const std::vector<SHAPE_ARC>& aArcs, int aMargin )
|
||||
{
|
||||
for( int ii = 0; ii < PointCount(); ++ii )
|
||||
{
|
||||
if( ssize_t ind = ArcIndex( ii ) >= 0 )
|
||||
// Remove all arcs
|
||||
m_arcs.clear();
|
||||
std::fill( m_shapes.begin(), m_shapes.end(), ssize_t( SHAPE_IS_PT ) );
|
||||
|
||||
auto emplaceArc =
|
||||
[&]( point_iter aStart, point_iter aEnd, SHAPE_ARC aReplacementArc )
|
||||
{
|
||||
const SHAPE_ARC& arc = Arc( ind );
|
||||
size_t startIndex = aStart - m_points.begin();
|
||||
size_t endIndex = aEnd - m_points.begin();
|
||||
|
||||
for( int ii = startIndex; ii <= endIndex; ++ii )
|
||||
m_shapes[ii] = m_arcs.size();
|
||||
|
||||
m_arcs.push_back( aReplacementArc );
|
||||
};
|
||||
|
||||
|
||||
for( point_iter it = m_points.begin(); it != m_points.end(); ++it )
|
||||
{
|
||||
for( const SHAPE_ARC& arc : aArcs )
|
||||
{
|
||||
SHAPE_ARC givenArcThin( arc );
|
||||
givenArcThin.SetWidth( 0 );
|
||||
|
||||
// Need at least this point and the next one to be on the arc
|
||||
point_iter it2 = it;
|
||||
++it2;
|
||||
|
||||
if( it2 == m_points.end() )
|
||||
return;
|
||||
|
||||
if( givenArcThin.Collide( *it, aMargin ) && givenArcThin.Collide( *it2 , aMargin ) )
|
||||
{
|
||||
VECTOR2I arcStart = *it;
|
||||
VECTOR2I arcEnd = *it2;
|
||||
|
||||
int numPts = 2;
|
||||
point_iter it3 = it2;
|
||||
VECTOR2I arcApproxMid = arcEnd; // Determine arc mid point from found points
|
||||
// (it might not be exactly in the middle)
|
||||
|
||||
while( (++it2 != m_points.end()) && givenArcThin.Collide( *it2, aMargin) )
|
||||
{
|
||||
if( ++numPts % 2 )
|
||||
arcApproxMid = *it3++;
|
||||
|
||||
arcEnd = *it2;
|
||||
}
|
||||
|
||||
--it2;
|
||||
|
||||
if( arcStart == givenArcThin.GetP0() && arcEnd == givenArcThin.GetP1() )
|
||||
{
|
||||
// Simplest case: found arc is equal to given arc
|
||||
emplaceArc( it, it2, givenArcThin );
|
||||
}
|
||||
else if( arcStart == givenArcThin.GetP1() && arcEnd == givenArcThin.GetP0() )
|
||||
{
|
||||
// Found arc was just reversed
|
||||
emplaceArc( it, it2, givenArcThin.Reversed() );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Some of the points are part of the original arc, but we will need to alter
|
||||
// the start and end points.
|
||||
VECTOR2I arcCenter = givenArcThin.GetCenter();
|
||||
int arcRadius = KiROUND( givenArcThin.GetRadius() );
|
||||
CIRCLE tempCircle( arcCenter, arcRadius );
|
||||
|
||||
if( arcEnd == arcApproxMid )
|
||||
{
|
||||
// Only 2 points found of the given arc
|
||||
// Need to derive the approximate location of the mid point using the
|
||||
// given arc's centre
|
||||
VECTOR2I startLine = arcStart - arcCenter;
|
||||
VECTOR2I endLine = arcEnd - arcCenter;
|
||||
double angle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() );
|
||||
RotatePoint( arcApproxMid, arcCenter, angle * 10.0 / 2.0 );
|
||||
}
|
||||
|
||||
// Project all the points so that they sit on the known arc, but don't
|
||||
// alter the actual points in m_points
|
||||
arcStart = tempCircle.NearestPoint( arcStart );
|
||||
arcEnd = tempCircle.NearestPoint( arcEnd );
|
||||
arcApproxMid = tempCircle.NearestPoint( arcApproxMid );
|
||||
|
||||
SHAPE_ARC tempArc( arcStart, arcApproxMid, arcEnd, 0 );
|
||||
emplaceArc( it, it2, tempArc );
|
||||
}
|
||||
|
||||
it = --it2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
#include <geometry/shape_arc.h>
|
||||
#include <geometry/shape_line_chain.h>
|
||||
#include <trigo.h>
|
||||
|
||||
#include <qa_utils/geometry/geometry.h>
|
||||
#include <qa_utils/numeric.h>
|
||||
|
@ -59,4 +60,239 @@ BOOST_AUTO_TEST_CASE( ArcToPolyline )
|
|||
}
|
||||
|
||||
|
||||
struct ARC_FOUND_PROPERTIES
|
||||
{
|
||||
VECTOR2I m_Start;
|
||||
VECTOR2I m_End;
|
||||
VECTOR2I m_Center;
|
||||
double m_Radius;
|
||||
|
||||
ARC_FOUND_PROPERTIES()
|
||||
{
|
||||
m_Start = { 0, 0 };
|
||||
m_End = { 0, 0 };
|
||||
m_Center = { 0, 0 };
|
||||
m_Radius = 0.0;
|
||||
}
|
||||
|
||||
ARC_FOUND_PROPERTIES( SHAPE_ARC aArc )
|
||||
{
|
||||
m_Start = aArc.GetP0();
|
||||
m_End = aArc.GetP1();
|
||||
m_Center = aArc.GetCenter();
|
||||
m_Radius = aArc.GetRadius();
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
struct DETECT_ARCS_CASE
|
||||
{
|
||||
/// The text context name
|
||||
std::string m_ctx_name;
|
||||
|
||||
/// Chain with arcs
|
||||
SHAPE_LINE_CHAIN m_chain;
|
||||
|
||||
/// Ars that are used as hints in DetectArcs function
|
||||
std::vector<SHAPE_ARC> m_arcs_to_find;
|
||||
|
||||
/// Expected properties of the arcs found in m_chain
|
||||
std::vector<ARC_FOUND_PROPERTIES> m_expected_arc_props;
|
||||
};
|
||||
|
||||
|
||||
static std::vector<DETECT_ARCS_CASE> DetectArcsTestCases( int aTolerance, SHAPE_ARC aArc )
|
||||
{
|
||||
std::vector<DETECT_ARCS_CASE> retval;
|
||||
|
||||
SHAPE_LINE_CHAIN base_chain(
|
||||
{ VECTOR2I( 0, 0 ), VECTOR2I( 0, 1000000 ), VECTOR2I( 1000000, 0 ) } );
|
||||
int num_pts = base_chain.GetPointCount();
|
||||
SHAPE_ARC appended_arc1 = SHAPE_ARC( VECTOR2I( 0, 0 ), VECTOR2I( 0, -200000 ), 45 );
|
||||
SHAPE_ARC appended_arc2 = aArc;
|
||||
|
||||
// Add the arcs to the chain as individual line segments, ensuring the chain has no knowledge
|
||||
// of the arcs that were added to it.
|
||||
SHAPE_LINE_CHAIN poly_arc1 = appended_arc1.ConvertToPolyline( aTolerance );
|
||||
SHAPE_LINE_CHAIN poly_arc2 = appended_arc2.ConvertToPolyline( aTolerance );
|
||||
base_chain.Append( poly_arc1 );
|
||||
base_chain.Append( poly_arc2 );
|
||||
|
||||
wxASSERT_MSG( poly_arc2.PointCount() > 3, "Test arc is too small" );
|
||||
|
||||
// Simple case: Arcs in the chain are as created by ConvertToPolyline
|
||||
SHAPE_LINE_CHAIN simple_chain( base_chain );
|
||||
retval.push_back( { "Simple case",
|
||||
simple_chain,
|
||||
{ appended_arc1, appended_arc2 },
|
||||
{ appended_arc1, appended_arc2 } } );
|
||||
|
||||
// Reversed case: Points are reversed
|
||||
SHAPE_LINE_CHAIN reversed_chain( base_chain.Reverse() );
|
||||
retval.push_back( { "Reversed case",
|
||||
reversed_chain,
|
||||
{ appended_arc1, appended_arc2 },
|
||||
{ appended_arc2.Reversed(), appended_arc1.Reversed() } } );
|
||||
|
||||
// Complex Case 1: Half of the end points of arc 2 removed
|
||||
{
|
||||
SHAPE_LINE_CHAIN truncated_chain( base_chain );
|
||||
|
||||
int start_index = truncated_chain.GetPointCount()
|
||||
- std::max( poly_arc2.GetPointCount() / 2, (size_t) 1 );
|
||||
int end_index = truncated_chain.GetPointCount() - 1;
|
||||
|
||||
truncated_chain.Remove( start_index, end_index );
|
||||
|
||||
ARC_FOUND_PROPERTIES expected_arc2( appended_arc2 );
|
||||
expected_arc2.m_End = truncated_chain.GetPoint( truncated_chain.GetPointCount() - 1 );
|
||||
|
||||
retval.push_back( { "Complex Case 1: End Point Changed",
|
||||
truncated_chain,
|
||||
{ appended_arc1, appended_arc2 },
|
||||
{ appended_arc1, expected_arc2 } } );
|
||||
}
|
||||
|
||||
// Complex Case 2: Half of the START points of arc 2 removed
|
||||
{
|
||||
SHAPE_LINE_CHAIN truncated_chain( base_chain );
|
||||
|
||||
int start_index = truncated_chain.GetPointCount() - poly_arc2.GetPointCount();
|
||||
int end_index = truncated_chain.GetPointCount()
|
||||
- std::max( poly_arc2.GetPointCount() / 2, (size_t) 1 );
|
||||
|
||||
ARC_FOUND_PROPERTIES expected_arc2( appended_arc2 );
|
||||
expected_arc2.m_Start = truncated_chain.GetPoint( end_index );
|
||||
|
||||
truncated_chain.Remove( start_index, end_index - 1 );
|
||||
|
||||
retval.push_back( { "Complex Case 2: Start Point Changed",
|
||||
truncated_chain,
|
||||
{ appended_arc1, appended_arc2 },
|
||||
{ appended_arc1, expected_arc2 } } );
|
||||
}
|
||||
|
||||
// Complex Case 3: Start and end points removed
|
||||
{
|
||||
SHAPE_LINE_CHAIN truncated_chain( base_chain );
|
||||
|
||||
// Remove some points at the start:
|
||||
int start_index = truncated_chain.GetPointCount() - poly_arc2.GetPointCount();
|
||||
int end_index = truncated_chain.GetPointCount()
|
||||
- std::max( ( 3 * poly_arc2.GetPointCount() ) / 4, (size_t) 1 );
|
||||
|
||||
ARC_FOUND_PROPERTIES expected_arc2( appended_arc2 );
|
||||
expected_arc2.m_Start = truncated_chain.GetPoint( end_index );
|
||||
|
||||
truncated_chain.Remove( start_index, end_index - 1 );
|
||||
|
||||
// Remove some points at the end:
|
||||
start_index = truncated_chain.GetPointCount()
|
||||
- std::max( poly_arc2.GetPointCount() / 4, (size_t) 1 );
|
||||
end_index = truncated_chain.GetPointCount() - 1;
|
||||
|
||||
truncated_chain.Remove( start_index, end_index );
|
||||
|
||||
expected_arc2.m_End = truncated_chain.GetPoint( truncated_chain.GetPointCount() - 1 );
|
||||
|
||||
retval.push_back( { "Complex Case 3: Start and End Points Changed",
|
||||
truncated_chain,
|
||||
{ appended_arc1, appended_arc2 },
|
||||
{ appended_arc1, expected_arc2 } } );
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE( DetectArcs )
|
||||
{
|
||||
std::vector<int> tolerance_cases = { 10, 50, 100, 500, 1000, 5000 };
|
||||
|
||||
std::vector<std::pair<VECTOR2I, VECTOR2I>> center_start_pts_cases =
|
||||
{
|
||||
{ VECTOR2I( 0, 500000 ), VECTOR2I( 0, 400000 ) },
|
||||
{ VECTOR2I( 0, 40505 ), VECTOR2I( 45205, 4245 ) },
|
||||
{ VECTOR2I( 47244, 4005 ), VECTOR2I( 94504, 5550 ) },
|
||||
{ VECTOR2I( 5000, 0 ), VECTOR2I( 0, 4000 ) }
|
||||
};
|
||||
|
||||
std::vector<double> arc_angle_cases = { 15, 45, 90, 135, 180, 225, 270, 305, 360 };
|
||||
|
||||
std::vector<SHAPE_ARC> arc_cases;
|
||||
|
||||
for( std::pair<VECTOR2I, VECTOR2I> center_start : center_start_pts_cases )
|
||||
{
|
||||
for( double angle : arc_angle_cases )
|
||||
arc_cases.emplace_back( center_start.first, center_start.second, angle );
|
||||
}
|
||||
|
||||
// SHAPE_ARC does not define arc centre and radius. These are calculated parameters based on the
|
||||
// three points of the arc. We want to ensure the centre and radius are somewhat close to the
|
||||
// original arc, but accept that there will be rounding errors when calculating.
|
||||
const int maxTolDerived = 450;
|
||||
|
||||
for( int& tol : tolerance_cases )
|
||||
{
|
||||
for( SHAPE_ARC& arc_case : arc_cases )
|
||||
{
|
||||
std::vector<DETECT_ARCS_CASE> test_cases = DetectArcsTestCases( tol, arc_case );
|
||||
|
||||
for( DETECT_ARCS_CASE& tc : test_cases )
|
||||
{
|
||||
BOOST_TEST_CONTEXT( tc.m_ctx_name << " -> Tolerance IU: " << tol
|
||||
<< " -> Arc Tested: { center=" << arc_case.GetCenter()
|
||||
<< " start=" << arc_case.GetP0()
|
||||
<< " angle=" << arc_case.GetCentralAngle() << "}" )
|
||||
{
|
||||
BOOST_REQUIRE_MESSAGE( tc.m_arcs_to_find.size()
|
||||
== tc.m_expected_arc_props.size(),
|
||||
"\nMalformed test case. m_arcs_to_find.size()"
|
||||
" should equal m_expected_arc_props.size()." );
|
||||
|
||||
BOOST_REQUIRE_MESSAGE( tc.m_chain.ArcCount() == 0,
|
||||
"\nMalformed test case. The SHAPE_LINE_CHAIN to test "
|
||||
"(m_chain) should have no arcs to start with. I.e. "
|
||||
"m_chain.ArcCount() should equal 0." );
|
||||
|
||||
tc.m_chain.DetectArcs( tc.m_arcs_to_find, tol );
|
||||
|
||||
BOOST_CHECK_EQUAL( tc.m_chain.ArcCount(), tc.m_expected_arc_props.size() );
|
||||
|
||||
BOOST_REQUIRE_MESSAGE( tc.m_chain.ArcCount() == tc.m_expected_arc_props.size(),
|
||||
"\nConvertToArcs failed: "
|
||||
"\n Expected arcs to be found: "
|
||||
<< tc.m_expected_arc_props.size()
|
||||
<< "\n Actual number found: "
|
||||
<< tc.m_chain.ArcCount() );
|
||||
int i = 0;
|
||||
int td = maxTolDerived;
|
||||
|
||||
for( ARC_FOUND_PROPERTIES& exp : tc.m_expected_arc_props )
|
||||
{
|
||||
BOOST_TEST_CONTEXT( "Arc Index: " << i )
|
||||
{
|
||||
BOOST_CHECK_PREDICATE(
|
||||
KI_TEST::IsVecWithinTol<VECTOR2I>,
|
||||
( tc.m_chain.Arc( i ).GetP0() )( exp.m_Start )( tol / 2 ) );
|
||||
BOOST_CHECK_PREDICATE(
|
||||
KI_TEST::IsVecWithinTol<VECTOR2I>,
|
||||
( tc.m_chain.Arc( i ).GetP1() )( exp.m_End )( tol / 2 ) );
|
||||
BOOST_CHECK_PREDICATE(
|
||||
KI_TEST::IsVecWithinTol<VECTOR2I>,
|
||||
( tc.m_chain.Arc( i ).GetCenter() )( exp.m_Center )( td ) );
|
||||
BOOST_CHECK_PREDICATE(
|
||||
KI_TEST::IsWithin<double>,
|
||||
( tc.m_chain.Arc( i ).GetRadius() )( exp.m_Radius )( td ) );
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
Loading…
Reference in New Issue