Fix oval pad snapping
Previously the snap points computed for oval pads didn't get all the points correct. This breaks out the "find snap points for ovals" into a function, reworks the logic, adds some tests. Also adds "extremum points" when the oval isn't exactly H/V. Fixes: https://gitlab.com/kicad/code/kicad/-/issues/15594
This commit is contained in:
parent
08ffb17489
commit
78c8de9b08
|
@ -17,6 +17,7 @@ set( KIMATH_SRCS
|
|||
src/geometry/convex_hull.cpp
|
||||
src/geometry/direction_45.cpp
|
||||
src/geometry/geometry_utils.cpp
|
||||
src/geometry/oval.cpp
|
||||
src/geometry/seg.cpp
|
||||
src/geometry/shape.cpp
|
||||
src/geometry/shape_arc.cpp
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2023 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
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, you may find one here:
|
||||
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||||
* or you may search the http://www.gnu.org website for the version 2 license,
|
||||
* or you may write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#ifndef GEOMETRY_OVAL_H_
|
||||
#define GEOMETRY_OVAL_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <math/vector2d.h>
|
||||
#include <geometry/eda_angle.h>
|
||||
|
||||
enum OVAL_KEY_POINTS
|
||||
{
|
||||
OVAL_CENTER = 1 << 0,
|
||||
OVAL_CAP_TIPS = 1 << 1,
|
||||
OVAL_CAP_CENTERS = 1 << 2,
|
||||
OVAL_SIDE_MIDPOINTS = 1 << 3,
|
||||
OVAL_SIDE_ENDS = 1 << 4,
|
||||
OVAL_CARDINAL_EXTREMES = 1 << 5,
|
||||
OVAL_ALL_KEY_POINTS = 0xFF
|
||||
};
|
||||
|
||||
using OVAL_KEY_POINT_FLAGS = unsigned int;
|
||||
|
||||
/**
|
||||
* @brief Get a list of interesting points on an oval (rectangle
|
||||
* with semicircular end caps)
|
||||
*
|
||||
* This may includes:
|
||||
* - The middles of the sides
|
||||
* - The tips of the end caps
|
||||
* - The extreme cardinal points of the whole oval (if rotated non-cardinally)
|
||||
*
|
||||
* @param aOvalSize - The size of the oval (overall length and width)
|
||||
* @param aRotation - The rotation of the oval
|
||||
* @param aFlags - The flags indicating which points to return
|
||||
*
|
||||
* @return std::vector<VECTOR2I> - The list of points
|
||||
*/
|
||||
std::vector<VECTOR2I> GetOvalKeyPoints( const VECTOR2I& aOvalSize, const EDA_ANGLE& aRotation,
|
||||
OVAL_KEY_POINT_FLAGS aFlags );
|
||||
|
||||
#endif /* GEOMETRY_OVAL_H_ */
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2014 CERN
|
||||
* Copyright (C) 2018-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
||||
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, you may find one here:
|
||||
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||||
* or you may search the http://www.gnu.org website for the version 2 license,
|
||||
* or you may write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include "geometry/oval.h"
|
||||
|
||||
#include <trigo.h> // for RotatePoint
|
||||
|
||||
|
||||
std::vector<VECTOR2I> GetOvalKeyPoints( const VECTOR2I& aOvalSize, const EDA_ANGLE& aRotation,
|
||||
OVAL_KEY_POINT_FLAGS aFlags )
|
||||
{
|
||||
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 );
|
||||
|
||||
// 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<VECTOR2I> pts;
|
||||
|
||||
if ( aFlags & OVAL_CENTER )
|
||||
{
|
||||
// Centre is easy
|
||||
pts.emplace_back( 0, 0 );
|
||||
};
|
||||
|
||||
if ( aFlags & OVAL_SIDE_MIDPOINTS )
|
||||
{
|
||||
// Side midpoints
|
||||
pts.emplace_back( half_width, 0 );
|
||||
pts.emplace_back( -half_width, 0 );
|
||||
}
|
||||
|
||||
if ( aFlags & OVAL_CAP_TIPS )
|
||||
{
|
||||
// Cap ends
|
||||
pts.emplace_back( 0, half_len );
|
||||
pts.emplace_back( 0, -half_len );
|
||||
}
|
||||
|
||||
// Distance from centre to cap centres
|
||||
const int d_centre_to_cap_centre = half_len - half_width;
|
||||
|
||||
if ( aFlags & OVAL_CAP_CENTERS )
|
||||
{
|
||||
// Cap centres
|
||||
pts.emplace_back( 0, d_centre_to_cap_centre );
|
||||
pts.emplace_back( 0, -d_centre_to_cap_centre );
|
||||
}
|
||||
|
||||
if ( aFlags & OVAL_SIDE_ENDS )
|
||||
{
|
||||
// End points of flat sides (always vertical)
|
||||
pts.emplace_back( half_width, d_centre_to_cap_centre );
|
||||
pts.emplace_back( half_width, -d_centre_to_cap_centre );
|
||||
pts.emplace_back( -half_width, d_centre_to_cap_centre );
|
||||
pts.emplace_back( -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() )
|
||||
{
|
||||
// We need to find two perpendicular lines from the centres
|
||||
// of each cap to the cap edge, which will hit the points
|
||||
// where the cap is tangent to H/V lines when rotated into place.
|
||||
//
|
||||
// Because we know the oval is always vertical, this means the
|
||||
// two lines are formed between _|, through \/ to |_
|
||||
// where the apex is the cap centre.
|
||||
|
||||
// The vector from a cap centre to the tip (i.e. vertical)
|
||||
const VECTOR2I cap_radial = { 0, half_width };
|
||||
|
||||
// Rotate in the opposite direction to the oval's rotation
|
||||
// (that will be unwound later)
|
||||
EDA_ANGLE radial_line_rotation = -rotation;
|
||||
|
||||
radial_line_rotation.Normalize90();
|
||||
|
||||
VECTOR2I cap_radial_to_x_axis = cap_radial;
|
||||
RotatePoint( cap_radial_to_x_axis, radial_line_rotation );
|
||||
|
||||
// Find the other line - it's 90 degrees away, but re-normalise
|
||||
// as it could be to the left or right
|
||||
radial_line_rotation -= ANGLE_90;
|
||||
radial_line_rotation.Normalize90();
|
||||
|
||||
VECTOR2I cap_radial_to_y_axis = cap_radial;
|
||||
RotatePoint( cap_radial_to_y_axis, radial_line_rotation );
|
||||
|
||||
// The quadrant points are then the relevant offsets from each cap centre
|
||||
pts.emplace_back( VECTOR2I{ 0, d_centre_to_cap_centre } + cap_radial_to_y_axis );
|
||||
pts.emplace_back( VECTOR2I{ 0, d_centre_to_cap_centre } + cap_radial_to_x_axis );
|
||||
// The opposite cap offsets go from the other cap centre, the other way
|
||||
pts.emplace_back( VECTOR2I{ 0, -d_centre_to_cap_centre } - cap_radial_to_y_axis );
|
||||
pts.emplace_back( VECTOR2I{ 0, -d_centre_to_cap_centre } - cap_radial_to_x_axis );
|
||||
}
|
||||
|
||||
for( VECTOR2I& pt : pts )
|
||||
{
|
||||
// Transform to the actual orientation
|
||||
// Already includes the extra 90 to swap x/y if needed
|
||||
RotatePoint( pt, rotation );
|
||||
}
|
||||
|
||||
return pts;
|
||||
}
|
|
@ -23,7 +23,10 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include "pcb_grid_helper.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <pcb_dimension.h>
|
||||
#include <pcb_shape.h>
|
||||
#include <footprint.h>
|
||||
|
@ -31,6 +34,7 @@
|
|||
#include <pcb_group.h>
|
||||
#include <pcb_track.h>
|
||||
#include <zone.h>
|
||||
#include <geometry/oval.h>
|
||||
#include <geometry/shape_circle.h>
|
||||
#include <geometry/shape_line_chain.h>
|
||||
#include <geometry/shape_rect.h>
|
||||
|
@ -43,8 +47,6 @@
|
|||
#include <tool/tool_manager.h>
|
||||
#include <tools/pcb_tool_base.h>
|
||||
#include <view/view.h>
|
||||
#include "pcb_grid_helper.h"
|
||||
|
||||
|
||||
PCB_GRID_HELPER::PCB_GRID_HELPER( TOOL_MANAGER* aToolMgr, MAGNETIC_SETTINGS* aMagneticSettings ) :
|
||||
GRID_HELPER( aToolMgr ),
|
||||
|
@ -547,6 +549,22 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos
|
|||
const std::set<int>& activeLayers = settings->GetHighContrastLayers();
|
||||
bool isHighContrast = settings->GetHighContrast();
|
||||
|
||||
// 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;
|
||||
|
||||
// The key points of a circle centred around (0, 0) with the given radius
|
||||
const auto getCircleKeyPoints = [] ( int radius )
|
||||
{
|
||||
return std::vector<VECTOR2I>{
|
||||
{0, 0},
|
||||
{ -radius, 0 },
|
||||
{ radius, 0 },
|
||||
{ 0, -radius },
|
||||
{ 0, radius }
|
||||
};
|
||||
};
|
||||
|
||||
auto handlePadShape =
|
||||
[&]( PAD* aPad )
|
||||
{
|
||||
|
@ -563,39 +581,26 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos
|
|||
int r = aPad->GetSizeX() / 2;
|
||||
VECTOR2I center = aPad->ShapePos();
|
||||
|
||||
addAnchor( center + VECTOR2I( -r, 0 ), OUTLINE | SNAPPABLE, aPad );
|
||||
addAnchor( center + VECTOR2I( r, 0 ), OUTLINE | SNAPPABLE, aPad );
|
||||
addAnchor( center + VECTOR2I( 0, -r ), OUTLINE | SNAPPABLE, aPad );
|
||||
addAnchor( center + VECTOR2I( 0, r ), OUTLINE | SNAPPABLE, aPad );
|
||||
const std::vector<VECTOR2I> circle_pts = getCircleKeyPoints( r );
|
||||
|
||||
for ( const VECTOR2I& pt: circle_pts ) {
|
||||
// Transform to the pad positon
|
||||
addAnchor( center + pt, OUTLINE | SNAPPABLE, aPad );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PAD_SHAPE::OVAL:
|
||||
{
|
||||
VECTOR2I pos = aPad->ShapePos();
|
||||
VECTOR2I half_size = aPad->GetSize() / 2;
|
||||
int half_width = std::min( half_size.x, half_size.y );
|
||||
VECTOR2I half_len( half_size.x - half_width, half_size.y - half_width );
|
||||
const VECTOR2I pos = aPad->ShapePos();
|
||||
|
||||
RotatePoint( half_len, aPad->GetOrientation() );
|
||||
const std::vector<VECTOR2I> oval_pts = GetOvalKeyPoints(
|
||||
aPad->GetSize(), aPad->GetOrientation(), ovalKeyPointFlags );
|
||||
|
||||
VECTOR2I a( pos - half_len );
|
||||
VECTOR2I b( pos + half_len );
|
||||
VECTOR2I normal = b - a;
|
||||
normal.Resize( half_width );
|
||||
RotatePoint( normal, ANGLE_90 );
|
||||
|
||||
addAnchor( a + normal, OUTLINE | SNAPPABLE, aPad );
|
||||
addAnchor( a - normal, OUTLINE | SNAPPABLE, aPad );
|
||||
addAnchor( b + normal, OUTLINE | SNAPPABLE, aPad );
|
||||
addAnchor( b - normal, OUTLINE | SNAPPABLE, aPad );
|
||||
addAnchor( pos + normal, OUTLINE | SNAPPABLE, aPad );
|
||||
addAnchor( pos - normal, OUTLINE | SNAPPABLE, aPad );
|
||||
|
||||
RotatePoint( normal, -ANGLE_90 );
|
||||
|
||||
addAnchor( a - normal, OUTLINE | SNAPPABLE, aPad );
|
||||
addAnchor( b + normal, OUTLINE | SNAPPABLE, aPad );
|
||||
for ( const VECTOR2I& pt: oval_pts ) {
|
||||
// Transform to the pad positon
|
||||
addAnchor( pos + pt, OUTLINE | SNAPPABLE, aPad );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ set( QA_KIMATH_SRCS
|
|||
geometry/test_ellipse_to_bezier.cpp
|
||||
geometry/test_fillet.cpp
|
||||
geometry/test_circle.cpp
|
||||
geometry/test_oval.cpp
|
||||
geometry/test_segment.cpp
|
||||
geometry/test_shape_compound_collision.cpp
|
||||
geometry/test_shape_arc.cpp
|
||||
|
|
|
@ -343,6 +343,7 @@ struct print_log_value<SHAPE_LINE_CHAIN>
|
|||
os << "]";
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
BOOST_TEST_PRINT_NAMESPACE_CLOSE
|
||||
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2023 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
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, you may find one here:
|
||||
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||||
* or you may search the http://www.gnu.org website for the version 2 license,
|
||||
* or you may write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <geometry/oval.h>
|
||||
|
||||
#include "geom_test_utils.h"
|
||||
|
||||
/**
|
||||
* @brief Check that two collections contain the same elements, ignoring order.
|
||||
*
|
||||
* I.e. expected contains everything in actual and vice versa.
|
||||
*
|
||||
* The collections lengths are also checked to weed out unexpected duplicates.
|
||||
*
|
||||
* @param expected a collection of expected elements
|
||||
* @param actual a collection of actual elements
|
||||
*/
|
||||
template <typename T>
|
||||
void CHECK_COLLECTIONS_SAME_UNORDERED(const T& expected, const T& actual) {
|
||||
|
||||
for( const auto& p : expected )
|
||||
{
|
||||
BOOST_CHECK_MESSAGE( std::find( actual.begin(), actual.end(), p ) != actual.end(),
|
||||
"Expected item not found: " << p );
|
||||
}
|
||||
|
||||
for( const auto& p : actual )
|
||||
{
|
||||
BOOST_CHECK_MESSAGE( std::find( expected.begin(), expected.end(), p ) != expected.end(),
|
||||
"Unexpected item: " << p );
|
||||
}
|
||||
|
||||
BOOST_CHECK_EQUAL( expected.size(), actual.size() );
|
||||
}
|
||||
|
||||
struct OvalFixture
|
||||
{
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE( Oval, OvalFixture )
|
||||
|
||||
struct OVAL_POINTS_TEST_CASE
|
||||
{
|
||||
VECTOR2I m_size;
|
||||
EDA_ANGLE m_rotation;
|
||||
std::vector<VECTOR2I> m_expected_points;
|
||||
};
|
||||
|
||||
void DoOvalPointTestChecks( const OVAL_POINTS_TEST_CASE& testcase )
|
||||
{
|
||||
const auto sort_vectors_x_then_y = []( const VECTOR2I& a, const VECTOR2I& b ) {
|
||||
return LexicographicalCompare<VECTOR2I::coord_type>( a, b ) > 0;
|
||||
};
|
||||
|
||||
std::vector<VECTOR2I> expected_points = testcase.m_expected_points;
|
||||
std::vector<VECTOR2I> actual_points =
|
||||
GetOvalKeyPoints( testcase.m_size, testcase.m_rotation, OVAL_ALL_KEY_POINTS );
|
||||
|
||||
CHECK_COLLECTIONS_SAME_UNORDERED( expected_points, actual_points );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( SimpleOvalVertical )
|
||||
{
|
||||
const OVAL_POINTS_TEST_CASE testcase
|
||||
{
|
||||
{ 1000, 3000 },
|
||||
{ 0, DEGREES_T },
|
||||
{
|
||||
{ 0, 0 },
|
||||
// Main points
|
||||
{ 0, 1500 },
|
||||
{ 0, -1500 },
|
||||
{ 500, 0 },
|
||||
{ -500, 0 },
|
||||
// Cap centres
|
||||
{ 0, 1000 },
|
||||
{ 0, -1000 },
|
||||
// Side segment ends
|
||||
{ 500, 1000 },
|
||||
{ 500, -1000 },
|
||||
{ -500, 1000 },
|
||||
{ -500, -1000 },
|
||||
},
|
||||
};
|
||||
|
||||
DoOvalPointTestChecks( testcase );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( SimpleOvalHorizontal )
|
||||
{
|
||||
const OVAL_POINTS_TEST_CASE testcase
|
||||
{
|
||||
{ 3000, 1000 },
|
||||
{ 0, DEGREES_T },
|
||||
{
|
||||
{ 0, 0 },
|
||||
// Main points
|
||||
{ 0, 500 },
|
||||
{ 0, -500 },
|
||||
{ 1500, 0 },
|
||||
{ -1500, 0 },
|
||||
// Cap centres
|
||||
{ 1000, 0 },
|
||||
{ -1000, 0 },
|
||||
// Side segment ends
|
||||
{ 1000, 500 },
|
||||
{ 1000, -500 },
|
||||
{ -1000, 500 },
|
||||
{ -1000, -500 },
|
||||
},
|
||||
};
|
||||
|
||||
DoOvalPointTestChecks( testcase );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( SimpleOval45Degrees )
|
||||
{
|
||||
// In this case, it's useful to keep in mind the hypotenuse of
|
||||
// isoceles right-angled triangles is sqrt(2) times the length of the sides
|
||||
// 500 / sqrt(2) = 354
|
||||
// 1000 / sqrt(2) = 707
|
||||
// 1500 / sqrt(2) = 1061
|
||||
// 2000 / sqrt(2) = 1414
|
||||
|
||||
const OVAL_POINTS_TEST_CASE testcase
|
||||
{
|
||||
{ 4000, 1000 },
|
||||
{ 45, DEGREES_T },
|
||||
{
|
||||
{ 0, 0 },
|
||||
// Main points
|
||||
{ 1414, -1414 },
|
||||
{ -1414, 1414 },
|
||||
{ 354, 354 },
|
||||
{ -354, -354 },
|
||||
// Side segment ends
|
||||
{ -1414, 707 },
|
||||
{ 1414, -707 },
|
||||
{ -707, 1414 },
|
||||
{ 707, -1414 },
|
||||
// Cap centres
|
||||
{ 1061, -1061 },
|
||||
{ -1061, 1061 },
|
||||
// Extremum points (always one of NSEW of a cap centre because 45 degrees)
|
||||
{ -1061 - 500, 1061 },
|
||||
{ -1061, 1061 + 500 },
|
||||
{ 1061 + 500, -1061 },
|
||||
{ 1061, -1061 - 500 },
|
||||
},
|
||||
};
|
||||
|
||||
DoOvalPointTestChecks( testcase );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
Loading…
Reference in New Issue