Browse Source

Geom: add a simple OVAL type

Makes it easier to reason about oval shapes in geometric terms.

For now, this isn't a SHAPE, but it could be (and it's a
fairly common primitive, so it could be useful, though the
obvious use (clearance) is equivalent to a SEG with a clearance,
which is already a function that exists.
jobs
John Beard 1 year ago
parent
commit
1d2fb3ec82
  1. 86
      libs/kimath/include/geometry/oval.h
  2. 83
      libs/kimath/src/geometry/oval.cpp
  3. 20
      pcbnew/tools/pcb_grid_helper.cpp
  4. 30
      qa/tests/libs/kimath/geometry/test_oval.cpp

86
libs/kimath/include/geometry/oval.h

@ -21,14 +21,91 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef GEOMETRY_OVAL_H_
#define GEOMETRY_OVAL_H_
#pragma once
#include <vector>
#include <math/box2.h>
#include <math/vector2d.h>
#include <geometry/approximation.h>
#include <geometry/eda_angle.h>
#include <geometry/point_types.h>
#include <geometry/seg.h>
class SHAPE_LINE_CHAIN;
/**
* Class that represents an oval shape (rectangle with semicircular end caps)
*
* This is not a full-blown SHAPE (yet), but can be used for some simple
* purposes, as well as for type-based logic.
*/
class OVAL
{
public:
/**
* Create an oval from the segment joining the centers of the semicircles
* and the diameter of the semicircles.
*/
OVAL( const SEG& aSeg, int aWidth );
/**
* Create an oval from the overall size, the center of the oval, and the rotation.
*
* The shorter dimension is the width of the semicircles.
*/
OVAL( const VECTOR2I& aOverallSize, const VECTOR2I& aCenter, const EDA_ANGLE& aRotation );
/**
* Get the bounding box of the oval
*/
BOX2I BBox( int aClearance ) const;
/**
* Get the width of the oval (diameter of the semicircles)
*/
int GetWidth() const { return m_width; }
/**
* Get the overall length of the oval from endcap tip to endcap tip
*/
int GetLength() const { return m_seg.Length() + m_width; }
/**
* Get the side length of the oval (=length between the centers of the semicircles)
*/
int GetSideLength() const { return m_seg.Length(); }
/**
* Get the center point of the oval.
*/
VECTOR2I GetCenter() const { return m_seg.Center(); }
/**
* Get the central segment of the oval
*
* (Endpoint are the centers of the semicircles)
*/
const SEG& GetSegment() const { return m_seg; }
/**
* Get the angle of the oval's central segment.
*
* The direction is aligned with the segment start/end
*/
EDA_ANGLE GetAngle() const { return EDA_ANGLE( m_seg.B - m_seg.A ); }
private:
SEG m_seg;
int m_width;
};
namespace KIGEOM
{
SHAPE_LINE_CHAIN ConvertToChain( const OVAL& aOval );
enum OVAL_KEY_POINTS
{
@ -58,7 +135,6 @@ using OVAL_KEY_POINT_FLAGS = unsigned int;
*
* @return std::vector<TYPED_POINT2I> - The list of points and their geomtrical types
*/
std::vector<TYPED_POINT2I> GetOvalKeyPoints( const VECTOR2I& aOvalSize, const EDA_ANGLE& aRotation,
OVAL_KEY_POINT_FLAGS aFlags );
std::vector<TYPED_POINT2I> GetOvalKeyPoints( const OVAL& aOval, OVAL_KEY_POINT_FLAGS aFlags );
#endif /* GEOMETRY_OVAL_H_ */
} // namespace KIGEOM

83
libs/kimath/src/geometry/oval.cpp

@ -26,20 +26,72 @@
#include "geometry/oval.h"
#include <trigo.h> // for RotatePoint
#include <geometry/shape_arc.h>
#include <geometry/shape_circle.h>
#include <geometry/shape_line_chain.h>
using namespace KIGEOM;
std::vector<TYPED_POINT2I> GetOvalKeyPoints( const VECTOR2I& aOvalSize, const EDA_ANGLE& aRotation,
OVAL_KEY_POINT_FLAGS aFlags )
OVAL::OVAL( const SEG& aSeg, int aWidth ) : m_seg( aSeg ), m_width( aWidth )
{
const VECTOR2I half_size = aOvalSize / 2;
const int half_width = std::min( half_size.x, half_size.y );
const int half_len = std::max( half_size.x, half_size.y );
// A negative width is meaningless
wxASSERT( aWidth > 0 );
}
OVAL::OVAL( const VECTOR2I& aOverallSize, const VECTOR2I& aCenter, const EDA_ANGLE& aRotation )
{
VECTOR2I segVec{};
// Find the major axis, without endcaps
if( aOverallSize.x > aOverallSize.y )
segVec.x = ( aOverallSize.x - aOverallSize.y );
else
segVec.y = ( aOverallSize.y - aOverallSize.x );
RotatePoint( segVec, aRotation );
m_seg = SEG( aCenter - segVec / 2, aCenter + segVec / 2 );
m_width = std::min( aOverallSize.x, aOverallSize.y );
}
BOX2I OVAL::BBox( int aClearance ) const
{
const int rad = m_width / 2 + aClearance;
const VECTOR2I& topleft = LexicographicalMin( m_seg.A, m_seg.B );
const VECTOR2I& bottomright = LexicographicalMax( m_seg.A, m_seg.B );
return BOX2I::ByCorners( topleft - VECTOR2I( rad, rad ), bottomright + VECTOR2I( rad, rad ) );
}
SHAPE_LINE_CHAIN KIGEOM::ConvertToChain( const OVAL& aOval )
{
const SEG& seg = aOval.GetSegment();
const VECTOR2I perp = GetRotated( seg.B - seg.A, ANGLE_90 ).Resize( aOval.GetWidth() / 2 );
SHAPE_LINE_CHAIN chain;
chain.Append( seg.A - perp );
chain.Append( SHAPE_ARC( seg.A, seg.A - perp, ANGLE_180 ) );
chain.Append( seg.B + perp );
chain.Append( SHAPE_ARC( seg.B, seg.B + perp, ANGLE_180 ) );
return chain;
}
std::vector<TYPED_POINT2I> KIGEOM::GetOvalKeyPoints( const OVAL& aOval,
OVAL_KEY_POINT_FLAGS aFlags )
{
const int half_width = aOval.GetWidth() / 2;
const int half_len = aOval.GetLength() / 2;
const EDA_ANGLE rotation = aOval.GetAngle() - ANGLE_90;
// Points on a non-rotated pad at the origin, long-axis is y
// (so for now, width is left/right, len is up/down)
std::vector<TYPED_POINT2I> pts;
if ( aFlags & OVAL_CENTER )
if( aFlags & OVAL_CENTER )
{
// Centre is easy
pts.emplace_back( VECTOR2I{ 0, 0 }, POINT_TYPE::PT_CENTER );
@ -55,7 +107,7 @@ std::vector<TYPED_POINT2I> GetOvalKeyPoints( const VECTOR2I& aOvalSize, const ED
if( aFlags & OVAL_CAP_TIPS )
{
// If the oval is square-on, the tips are quadrants
const POINT_TYPE pt_type = aRotation.IsCardinal() ? PT_QUADRANT : PT_END;
const POINT_TYPE pt_type = rotation.IsCardinal() ? PT_QUADRANT : PT_END;
// Cap ends
pts.emplace_back( VECTOR2I{ 0, half_len }, pt_type );
@ -65,14 +117,14 @@ std::vector<TYPED_POINT2I> GetOvalKeyPoints( const VECTOR2I& aOvalSize, const ED
// Distance from centre to cap centres
const int d_centre_to_cap_centre = half_len - half_width;
if ( aFlags & OVAL_CAP_CENTERS )
if( aFlags & OVAL_CAP_CENTERS )
{
// Cap centres
pts.emplace_back( VECTOR2I{ 0, d_centre_to_cap_centre }, POINT_TYPE::PT_CENTER );
pts.emplace_back( VECTOR2I{ 0, -d_centre_to_cap_centre }, POINT_TYPE::PT_CENTER );
}
if ( aFlags & OVAL_SIDE_ENDS )
if( aFlags & OVAL_SIDE_ENDS )
{
const auto add_end = [&]( const VECTOR2I& aPt )
{
@ -86,11 +138,6 @@ std::vector<TYPED_POINT2I> GetOvalKeyPoints( const VECTOR2I& aOvalSize, const ED
add_end( { -half_width, -d_centre_to_cap_centre } );
}
// If the pad is horizontal (i.e. x > y), we'll rotate the whole thing
// 90 degrees and work with it as if it was vertical
const bool swap_xy = half_size.x > half_size.y;
const EDA_ANGLE rotation = aRotation + ( swap_xy ? -ANGLE_90 : ANGLE_0 );
// Add the quadrant points to the caps only if rotated
// (otherwise they're just the tips)
if( ( aFlags & OVAL_CARDINAL_EXTREMES ) && !rotation.IsCardinal() )
@ -108,7 +155,7 @@ std::vector<TYPED_POINT2I> GetOvalKeyPoints( const VECTOR2I& aOvalSize, const ED
// Rotate in the opposite direction to the oval's rotation
// (that will be unwound later)
EDA_ANGLE radial_line_rotation = -rotation;
EDA_ANGLE radial_line_rotation = rotation;
radial_line_rotation.Normalize90();
@ -139,8 +186,10 @@ std::vector<TYPED_POINT2I> GetOvalKeyPoints( const VECTOR2I& aOvalSize, const ED
for( TYPED_POINT2I& pt : pts )
{
// Transform to the actual orientation
// Already includes the extra 90 to swap x/y if needed
RotatePoint( pt.m_point, rotation );
RotatePoint( pt.m_point, -rotation );
// Translate to the actual position
pt.m_point += aOval.GetCenter();
}
return pts;

20
pcbnew/tools/pcb_grid_helper.cpp

@ -1053,10 +1053,9 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos
};
// As defaults, these are probably reasonable to avoid spamming key points
const OVAL_KEY_POINT_FLAGS ovalKeyPointFlags = OVAL_CENTER
| OVAL_CAP_TIPS
| OVAL_SIDE_MIDPOINTS
| OVAL_CARDINAL_EXTREMES;
const KIGEOM::OVAL_KEY_POINT_FLAGS ovalKeyPointFlags =
KIGEOM::OVAL_CENTER | KIGEOM::OVAL_CAP_TIPS | KIGEOM::OVAL_SIDE_MIDPOINTS
| KIGEOM::OVAL_CARDINAL_EXTREMES;
// The key points of a circle centred around (0, 0) with the given radius
auto getCircleKeyPoints = []( int radius, bool aIncludeCenter )
@ -1094,15 +1093,15 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos
break;
case PAD_SHAPE::OVAL:
for( const TYPED_POINT2I& pt :
GetOvalKeyPoints( aPad->GetSize(), aPad->GetOrientation(), ovalKeyPointFlags ) )
{
const OVAL oval( aPad->GetSize(), aPad->GetPosition(), aPad->GetOrientation() );
for( const TYPED_POINT2I& pt : KIGEOM::GetOvalKeyPoints( oval, ovalKeyPointFlags ) )
{
// Transform to the pad positon
addAnchor( aPad->ShapePos() + pt.m_point, OUTLINE | SNAPPABLE, aPad, pt.m_types );
addAnchor( pt.m_point, OUTLINE | SNAPPABLE, aPad, pt.m_types );
}
break;
}
case PAD_SHAPE::RECTANGLE:
case PAD_SHAPE::TRAPEZOID:
case PAD_SHAPE::ROUNDRECT:
@ -1172,7 +1171,8 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos
// For now there's no way to have an off-angle hole, so this is the
// same as the pad. In future, this may not be true:
// https://gitlab.com/kicad/code/kicad/-/issues/4124
snap_pts = GetOvalKeyPoints( hole_size, aPad->GetOrientation(), ovalKeyPointFlags );
const OVAL oval( hole_size, { 0, 0 }, aPad->GetOrientation() );
snap_pts = KIGEOM::GetOvalKeyPoints( oval, ovalKeyPointFlags );
}
for( const TYPED_POINT2I& snap_pt : snap_pts )

30
qa/tests/libs/kimath/geometry/test_oval.cpp

@ -55,16 +55,11 @@ void CHECK_COLLECTIONS_SAME_UNORDERED(const T& expected, const T& actual) {
BOOST_CHECK_EQUAL( expected.size(), actual.size() );
}
struct OvalFixture
{
};
BOOST_FIXTURE_TEST_SUITE( Oval, OvalFixture )
BOOST_AUTO_TEST_SUITE( Oval )
struct OVAL_POINTS_TEST_CASE
{
VECTOR2I m_size;
EDA_ANGLE m_rotation;
OVAL m_oval;
std::vector<TYPED_POINT2I> m_expected_points;
};
@ -75,8 +70,9 @@ void DoOvalPointTestChecks( const OVAL_POINTS_TEST_CASE& testcase )
};
std::vector<TYPED_POINT2I> expected_points = testcase.m_expected_points;
std::vector<TYPED_POINT2I> actual_points =
GetOvalKeyPoints( testcase.m_size, testcase.m_rotation, OVAL_ALL_KEY_POINTS );
KIGEOM::GetOvalKeyPoints( testcase.m_oval, KIGEOM::OVAL_ALL_KEY_POINTS );
CHECK_COLLECTIONS_SAME_UNORDERED( expected_points, actual_points );
}
@ -85,8 +81,10 @@ BOOST_AUTO_TEST_CASE( SimpleOvalVertical )
{
const OVAL_POINTS_TEST_CASE testcase
{
{ 1000, 3000 },
{ 0, DEGREES_T },
{
SEG{ { 0, -1000 }, { 0, 1000 } },
1000,
},
{
{ { 0, 0 }, PT_CENTER },
// Main points
@ -113,8 +111,10 @@ BOOST_AUTO_TEST_CASE( SimpleOvalHorizontal )
{
const OVAL_POINTS_TEST_CASE testcase
{
{ 3000, 1000 },
{ 0, DEGREES_T },
{
SEG{ { -1000, 0 }, { 1000, 0 } },
1000,
},
{
{ { 0, 0 }, PT_CENTER },
// Main points
@ -148,8 +148,10 @@ BOOST_AUTO_TEST_CASE( SimpleOval45Degrees )
const OVAL_POINTS_TEST_CASE testcase
{
{ 4000, 1000 },
{ 45, DEGREES_T },
{
SEG{ GetRotated( { -1500, 0 }, ANGLE_45 ), GetRotated( { 1500, 0 }, ANGLE_45 ) },
1000,
},
{
{ { 0, 0 }, PT_CENTER },
// Main points

Loading…
Cancel
Save