mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-13 17:53:11 +02:00
One more pass at optimizing the board outline gen
Instead of iterating through the segment list each time, we use a kdTree structure to efficiently query a 2d point cloud for the nearest neighbors Fixes https://gitlab.com/kicad/code/kicad/-/issues/21352
This commit is contained in:
parent
32f083e066
commit
4dab336f95
@ -916,6 +916,7 @@ target_link_libraries( pcbcommon PUBLIC
|
||||
delaunator
|
||||
kimath
|
||||
kiplatform
|
||||
nanoflann
|
||||
)
|
||||
|
||||
message( STATUS "Including 3Dconnexion SpaceMouse navigation support in pcbcommon" )
|
||||
|
@ -736,6 +736,7 @@ target_link_libraries( pcbnew_kiface_objects
|
||||
nlohmann_json
|
||||
rectpack2d
|
||||
gzip-hpp
|
||||
nanoflann
|
||||
Boost::boost
|
||||
ZLIB::ZLIB
|
||||
${OCC_LIBRARIES}
|
||||
|
@ -40,6 +40,8 @@
|
||||
#include <board.h>
|
||||
#include <collectors.h>
|
||||
|
||||
#include <nanoflann.hpp>
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
|
||||
@ -96,61 +98,6 @@ static bool closer_to_first( VECTOR2I aRef, VECTOR2I aFirst, VECTOR2I aSecond )
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search for a #PCB_SHAPE matching a given end point or start point in a list.
|
||||
*
|
||||
* @param aShape The starting shape.
|
||||
* @param aPoint The starting or ending point to search for.
|
||||
* @param aList The list to remove from.
|
||||
* @param aLimit is the distance from \a aPoint that still constitutes a valid find.
|
||||
* @return The first #PCB_SHAPE that has a start or end point matching aPoint, otherwise nullptr.
|
||||
*/
|
||||
static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const VECTOR2I& aPoint,
|
||||
const std::vector<PCB_SHAPE*>& aList, unsigned aLimit )
|
||||
{
|
||||
// Look for an unused, exact hit
|
||||
for( PCB_SHAPE* graphic : aList )
|
||||
{
|
||||
if( graphic == aShape || ( graphic->GetFlags() & SKIP_STRUCT ) != 0 )
|
||||
continue;
|
||||
|
||||
if( aPoint == graphic->GetStart() || aPoint == graphic->GetEnd() )
|
||||
return graphic;
|
||||
}
|
||||
|
||||
// Search again for anything that's close, even something already used. (The latter is
|
||||
// important for error reporting.)
|
||||
VECTOR2I pt( aPoint );
|
||||
SEG::ecoord closest_dist_sq = SEG::Square( aLimit );
|
||||
PCB_SHAPE* closest_graphic = nullptr;
|
||||
SEG::ecoord d_sq;
|
||||
|
||||
for( PCB_SHAPE* graphic : aList )
|
||||
{
|
||||
if( graphic == aShape )
|
||||
continue;
|
||||
|
||||
d_sq = ( pt - graphic->GetStart() ).SquaredEuclideanNorm();
|
||||
|
||||
if( d_sq < closest_dist_sq )
|
||||
{
|
||||
closest_dist_sq = d_sq;
|
||||
closest_graphic = graphic;
|
||||
}
|
||||
|
||||
d_sq = ( pt - graphic->GetEnd() ).SquaredEuclideanNorm();
|
||||
|
||||
if( d_sq < closest_dist_sq )
|
||||
{
|
||||
closest_dist_sq = d_sq;
|
||||
closest_graphic = graphic;
|
||||
}
|
||||
}
|
||||
|
||||
return closest_graphic; // Note: will be nullptr if nothing within aLimit
|
||||
}
|
||||
|
||||
|
||||
static bool isCopperOutside( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aShape )
|
||||
{
|
||||
bool padOutside = false;
|
||||
@ -187,6 +134,81 @@ static bool isCopperOutside( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aShape
|
||||
}
|
||||
|
||||
|
||||
struct PCB_SHAPE_ENDPOINTS_ADAPTOR
|
||||
{
|
||||
std::vector<std::pair<VECTOR2I, PCB_SHAPE*>> endpoints;
|
||||
|
||||
PCB_SHAPE_ENDPOINTS_ADAPTOR( const std::vector<PCB_SHAPE*>& shapes )
|
||||
{
|
||||
endpoints.reserve( shapes.size() * 2 );
|
||||
|
||||
for( PCB_SHAPE* shape : shapes )
|
||||
{
|
||||
endpoints.emplace_back( shape->GetStart(), shape );
|
||||
endpoints.emplace_back( shape->GetEnd(), shape );
|
||||
}
|
||||
}
|
||||
|
||||
// Required by nanoflann
|
||||
size_t kdtree_get_point_count() const { return endpoints.size(); }
|
||||
|
||||
// Returns the dim'th component of the idx'th point
|
||||
double kdtree_get_pt( const size_t idx, const size_t dim ) const
|
||||
{
|
||||
if( dim == 0 )
|
||||
return static_cast<double>( endpoints[idx].first.x );
|
||||
else
|
||||
return static_cast<double>( endpoints[idx].first.y );
|
||||
}
|
||||
|
||||
template <class BBOX>
|
||||
bool kdtree_get_bbox( BBOX& ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
using KDTree = nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double, PCB_SHAPE_ENDPOINTS_ADAPTOR>,
|
||||
PCB_SHAPE_ENDPOINTS_ADAPTOR,
|
||||
2 /* dim */ >;
|
||||
|
||||
// Helper function to find next shape using KD-tree
|
||||
static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const VECTOR2I& aPoint, const KDTree& kdTree,
|
||||
const PCB_SHAPE_ENDPOINTS_ADAPTOR& adaptor, unsigned aLimit )
|
||||
{
|
||||
const double query_pt[2] = { static_cast<double>( aPoint.x ), static_cast<double>( aPoint.y ) };
|
||||
|
||||
// Search for points within a very small radius for exact matches
|
||||
std::vector<nanoflann::ResultItem<size_t, double>> matches;
|
||||
double search_radius_sq = static_cast<double>( aLimit * aLimit );
|
||||
nanoflann::RadiusResultSet<double, size_t> radiusResultSet( search_radius_sq, matches );
|
||||
|
||||
kdTree.findNeighbors( radiusResultSet, query_pt );
|
||||
|
||||
if( matches.empty() )
|
||||
return nullptr;
|
||||
|
||||
// Find the closest valid candidate
|
||||
PCB_SHAPE* closest_graphic = nullptr;
|
||||
double closest_dist_sq = search_radius_sq;
|
||||
|
||||
for( const auto& match : matches )
|
||||
{
|
||||
PCB_SHAPE* candidate = adaptor.endpoints[match.first].second;
|
||||
|
||||
if( candidate == aShape )
|
||||
continue;
|
||||
|
||||
if( match.second < closest_dist_sq && !candidate->HasFlag( SKIP_STRUCT ) )
|
||||
{
|
||||
closest_dist_sq = match.second;
|
||||
closest_graphic = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return closest_graphic;
|
||||
}
|
||||
|
||||
bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SET& aPolygons,
|
||||
int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint,
|
||||
OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons,
|
||||
@ -200,6 +222,10 @@ bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_
|
||||
|
||||
std::set<PCB_SHAPE*> startCandidates( aShapeList.begin(), aShapeList.end() );
|
||||
|
||||
// Pre-build KD-tree
|
||||
PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( aShapeList );
|
||||
KDTree kdTree( 2, adaptor, nanoflann::KDTreeSingleIndexAdaptorParams( 10 ) );
|
||||
|
||||
// Keep a list of where the various shapes came from so after doing our combined-polygon
|
||||
// tests we can still report errors against the individual graphic items.
|
||||
std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*> shapeOwners;
|
||||
@ -434,8 +460,7 @@ bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get next closest segment.
|
||||
PCB_SHAPE* nextGraphic = findNext( graphic, prevPt, aShapeList, aChainingEpsilon );
|
||||
PCB_SHAPE* nextGraphic = findNext( graphic, prevPt, kdTree, adaptor, aChainingEpsilon );
|
||||
|
||||
if( nextGraphic && !( nextGraphic->GetFlags() & SKIP_STRUCT ) )
|
||||
{
|
||||
|
1
thirdparty/CMakeLists.txt
vendored
1
thirdparty/CMakeLists.txt
vendored
@ -56,6 +56,7 @@ add_subdirectory( libcontext )
|
||||
add_subdirectory( libpopcnt )
|
||||
add_subdirectory( magic_enum )
|
||||
add_subdirectory( markdown2html )
|
||||
add_subdirectory( nanoflann )
|
||||
add_subdirectory( nanodbc )
|
||||
add_subdirectory( nanosvg )
|
||||
add_subdirectory( rectpack2d )
|
||||
|
7
thirdparty/nanoflann/CMakeLists.txt
vendored
Normal file
7
thirdparty/nanoflann/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
add_library( nanoflann INTERFACE )
|
||||
|
||||
target_include_directories( nanoflann INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} )
|
||||
|
||||
target_sources( nanoflann INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nanoflann.hpp
|
||||
)
|
28
thirdparty/nanoflann/LICENSE.BSD
vendored
Normal file
28
thirdparty/nanoflann/LICENSE.BSD
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
Software License Agreement (BSD License)
|
||||
|
||||
Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
|
||||
Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
|
||||
Copyright 2011 Jose L. Blanco (joseluisblancoc@gmail.com). All rights reserved.
|
||||
|
||||
THE BSD LICENSE
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
2741
thirdparty/nanoflann/nanoflann.hpp
vendored
Normal file
2741
thirdparty/nanoflann/nanoflann.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user