KiCad PCB EDA Suite
Loading...
Searching...
No Matches
geom_test_utils.h
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#ifndef GEOM_TEST_UTILS_H
21#define GEOM_TEST_UTILS_H
22
23#include <cmath>
24
26#include <geometry/seg.h>
29
30#include <qa_utils/numeric.h>
33
37namespace GEOM_TEST
38{
39
49enum class QUADRANT {
51};
52
56template<typename T>
57bool IsInQuadrant( const VECTOR2<T>& aPoint, QUADRANT aQuadrant )
58{
59 bool isInQuad = false;
60
61 switch( aQuadrant )
62 {
63 case QUADRANT::Q1:
64 isInQuad = aPoint.x >= 0 && aPoint.y >= 0;
65 break;
66 case QUADRANT::Q2:
67 isInQuad = aPoint.x <= 0 && aPoint.y >= 0;
68 break;
69 case QUADRANT::Q3:
70 isInQuad = aPoint.x <= 0 && aPoint.y <= 0;
71 break;
72 case QUADRANT::Q4:
73 isInQuad = aPoint.x >= 0 && aPoint.y <= 0;
74 break;
75 }
76
77 return isInQuad;
78}
79
83inline bool SegmentCompletelyInQuadrant( const SEG& aSeg, QUADRANT aQuadrant )
84{
85 return IsInQuadrant( aSeg.A, aQuadrant)
86 && IsInQuadrant( aSeg.B, aQuadrant );
87}
88
92inline bool SegmentEndsInQuadrant( const SEG& aSeg, QUADRANT aQuadrant )
93{
94 return IsInQuadrant( aSeg.A, aQuadrant )
95 || IsInQuadrant( aSeg.B, aQuadrant );
96}
97
101inline bool SegmentCompletelyWithinRadius( const SEG& aSeg, const VECTOR2I& aPt, const int aRadius )
102{
103 // This is true iff both ends of the segment are within the radius
104 return ( ( aSeg.A - aPt ).EuclideanNorm() < aRadius )
105 && ( ( aSeg.B - aPt ).EuclideanNorm() < aRadius );
106}
107
117template <typename T>
118bool IsPointAtDistance( const VECTOR2<T>& aPtA, const VECTOR2<T>& aPtB, T aExpDist, T aTol )
119{
120 const int dist = ( aPtB - aPtA ).EuclideanNorm();
121 const bool ok = KI_TEST::IsWithin( dist, aExpDist, aTol );
122
123 if( !ok )
124 {
125 BOOST_TEST_INFO( "Points not at expected distance: distance is " << dist << ", expected "
126 << aExpDist );
127 }
128
129 return ok;
130}
131
142template <typename T>
144 const std::vector<VECTOR2<T>>& aPoints, const VECTOR2<T>& aCentre, T aRad, T aTol )
145{
146 bool ok = true;
147
148 for( unsigned i = 0; i < aPoints.size(); ++i )
149 {
150 if( !IsPointAtDistance( aPoints[i], aCentre, aRad, aTol ) )
151 {
152 BOOST_TEST_INFO( "Point " << i << " " << aPoints[i] << " is not within tolerance ("
153 << aTol << ") of radius (" << aRad << ") from centre point "
154 << aCentre );
155 ok = false;
156 }
157 }
158
159 return ok;
160}
161
170
171template<typename T>
172bool ArePerpendicular( const VECTOR2<T>& a, const VECTOR2<T>& b, const EDA_ANGLE& aTolerance )
173{
174 EDA_ANGLE angle = std::abs( EDA_ANGLE( a ) - EDA_ANGLE( b ) );
175
176 // Normalise: angles of 3*pi/2 are also perpendicular
177 if (angle > ANGLE_180)
178 angle -= ANGLE_180;
179
180 return KI_TEST::IsWithin( angle.AsRadians(), ANGLE_90.AsRadians(), aTolerance.AsRadians() );
181}
182
186inline SHAPE_POLY_SET FilletPolySet( SHAPE_POLY_SET& aPolySet, int aRadius, int aError )
187{
188 SHAPE_POLY_SET filletedPolySet;
189
190 for ( int i = 0; i < aPolySet.OutlineCount(); ++i )
191 {
192 const auto filleted = aPolySet.FilletPolygon( aRadius, aError, i );
193
194 filletedPolySet.AddOutline( filleted[0] );
195 }
196
197 return filletedPolySet;
198}
199
208inline bool IsOutlineValid( const SHAPE_LINE_CHAIN& aChain )
209{
210 ssize_t prevArcIdx = -1;
211 std::set<size_t> testedArcs;
212
213 if( aChain.PointCount() > 0 && !aChain.IsClosed() && aChain.IsSharedPt( 0 ) )
214 return false; //can't have first point being shared on an open chain
215
216 for( int i = 0; i < aChain.PointCount(); i++ )
217 {
218 ssize_t arcIdx = aChain.ArcIndex( i );
219
220 if( arcIdx >= 0 )
221 {
222 // Point on arc, lets make sure it collides with the arc shape and we haven't
223 // previously seen the same arc index
224
225 if( prevArcIdx != arcIdx && testedArcs.count( arcIdx ) )
226 return false; // we've already seen this arc before, not contiguous
227
228 if( !aChain.Arc( arcIdx ).Collide( aChain.CPoint( i ),
230 {
231 return false;
232 }
233
234 testedArcs.insert( arcIdx );
235 }
236
237 if( prevArcIdx != arcIdx )
238 {
239 // we have changed arc shapes, run a few extra tests
240
241 if( prevArcIdx >= 0 )
242 {
243 // prev point on arc, test that the last arc point on the chain
244 // matches the end point of the arc
245 VECTOR2I pointToTest = aChain.CPoint( i );
246
247 if( !aChain.IsSharedPt( i ) )
248 pointToTest = aChain.CPoint( i - 1 );
249
250 SHAPE_ARC lastArc = aChain.Arc( prevArcIdx );
251
252 if( lastArc.GetP1() != pointToTest )
253 return false;
254 }
255
256 if( arcIdx >= 0 )
257 {
258 // new arc, test that the start point of the arc matches the point on the chain
259 VECTOR2I pointToTest = aChain.CPoint( i );
260 SHAPE_ARC currentArc = aChain.Arc( arcIdx );
261
262 if( currentArc.GetP0() != pointToTest )
263 return false;
264 }
265 }
266
267 prevArcIdx = arcIdx;
268 }
269
270 // Make sure last arc point matches the end of the arc
271 if( prevArcIdx >= 0 )
272 {
273 if( aChain.IsClosed() && aChain.IsSharedPt( 0 ) )
274 {
275 if( aChain.CShapes()[0].first != prevArcIdx )
276 return false;
277
278 if( aChain.Arc( prevArcIdx ).GetP1() != aChain.CPoint( 0 ) )
279 return false;
280 }
281 else
282 {
283 if( aChain.Arc( prevArcIdx ).GetP1() != aChain.CLastPoint() )
284 return false;
285 }
286 }
287
288 return true;
289}
290
298inline bool IsPolySetValid( const SHAPE_POLY_SET& aSet )
299{
300 for( int i = 0; i < aSet.OutlineCount(); i++ )
301 {
302 if( !IsOutlineValid( aSet.Outline( i ) ) )
303 return false;
304
305 for( int j = 0; j < aSet.HoleCount( i ); j++ )
306 {
307 if( !IsOutlineValid( aSet.CHole( i, j ) ) )
308 return false;
309 }
310 }
311
312 return true;
313}
314
320inline bool SegmentsHaveSameEndPoints( const SEG& aSeg1, const SEG& aSeg2 )
321{
322 return ( aSeg1.A == aSeg2.A && aSeg1.B == aSeg2.B )
323 || ( aSeg1.A == aSeg2.B && aSeg1.B == aSeg2.A );
324}
325
326
328inline double TriangleMinAngleDeg( const VECTOR2I& a, const VECTOR2I& b, const VECTOR2I& c )
329{
330 auto angleAt = []( const VECTOR2I& v, const VECTOR2I& p, const VECTOR2I& q )
331 {
332 VECTOR2D u( p.x - v.x, p.y - v.y );
333 VECTOR2D w( q.x - v.x, q.y - v.y );
334
335 return std::atan2( std::abs( u.x * w.y - u.y * w.x ), u.x * w.x + u.y * w.y ) * 180.0 / M_PI;
336 };
337
338 return std::min( { angleAt( a, b, c ), angleAt( b, c, a ), angleAt( c, a, b ) } );
339}
340
341} // namespace GEOM_TEST
342
343
344// Not clear why boost_test_print_type doesn't work on Debian specifically for this type,
345// but this works on all platforms
346std::ostream& operator<<( std::ostream& os, const TYPED_POINT2I& c );
347
348#endif // GEOM_TEST_UTILS_H
double AsRadians() const
Definition eda_angle.h:119
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
bool Collide(const SEG &aSeg, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the segment aSeg than aClearance,...
static int DefaultAccuracyForPCB()
Definition shape_arc.h:283
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_ARC & Arc(size_t aArc) const
bool IsClosed() const override
int PointCount() const
Return the number of points (vertices) in this line chain.
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
const std::vector< std::pair< ssize_t, ssize_t > > & CShapes() const
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
bool IsSharedPt(size_t aIndex) const
Test if a point is shared between multiple shapes.
Represent a set of closed polygons.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
const SHAPE_LINE_CHAIN & CHole(int aOutline, int aHole) const
POLYGON FilletPolygon(unsigned int aRadius, int aErrorMax, int aIndex)
Return a filleted version of the aIndex-th polygon.
int OutlineCount() const
Return the number of outlines in the set.
Define a general 2D-vector/point.
Definition vector2d.h:67
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:452
std::ostream & operator<<(std::ostream &os, const TYPED_POINT2I &c)
Utility functions for testing geometry functions.
bool ArePointsNearCircle(const std::vector< VECTOR2< T > > &aPoints, const VECTOR2< T > &aCentre, T aRad, T aTol)
Predicate for checking a set of points is within a certain tolerance of a circle.
bool IsOutlineValid(const SHAPE_LINE_CHAIN &aChain)
Verify that a SHAPE_LINE_CHAIN has been assembled correctly by ensuring that the arc start and end po...
bool SegmentCompletelyWithinRadius(const SEG &aSeg, const VECTOR2I &aPt, const int aRadius)
Check if a segment is entirely within a certain radius of a point.
bool IsPointAtDistance(const VECTOR2< T > &aPtA, const VECTOR2< T > &aPtB, T aExpDist, T aTol)
Check that two points are the given distance apart, within the given tolerance.
bool SegmentEndsInQuadrant(const SEG &aSeg, QUADRANT aQuadrant)
Check if at least one end of the segment is in Quadrant 1.
SHAPE_POLY_SET FilletPolySet(SHAPE_POLY_SET &aPolySet, int aRadius, int aError)
Fillet every polygon in a set and return a new set.
bool IsInQuadrant(const VECTOR2< T > &aPoint, QUADRANT aQuadrant)
Check value in Quadrant 1 (x and y both >= 0)
bool SegmentCompletelyInQuadrant(const SEG &aSeg, QUADRANT aQuadrant)
Check if both ends of a segment are in Quadrant 1.
double TriangleMinAngleDeg(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
The smallest interior angle of a triangle, in degrees; near zero for a sliver.
bool ArePerpendicular(const VECTOR2< T > &a, const VECTOR2< T > &b, const EDA_ANGLE &aTolerance)
Check if two vectors are perpendicular.
QUADRANT
Geometric quadrants, from top-right, anti-clockwise.
bool SegmentsHaveSameEndPoints(const SEG &aSeg1, const SEG &aSeg2)
Check that two SEGs have the same end points, in either order.
bool IsPolySetValid(const SHAPE_POLY_SET &aSet)
Verify that a SHAPE_POLY_SET has been assembled correctly by verifying each of the outlines and holes...
bool IsWithin(T aValue, T aNominal, T aError)
Check if a value is within a tolerance of a nominal value.
Definition numeric.h:57
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
Numerical test predicates.
BOOST_TEST_INFO("Two-port Series .op current = "<< iDevice)
#define M_PI
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707