kicad-source/pcbnew/fix_board_shape.cpp

454 lines
16 KiB
C++
Raw Normal View History

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Alex Shvartzkop <dudesuchamazing@gmail.com>
* Copyright The 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
*/
2023-09-04 06:44:22 +03:00
#include "fix_board_shape.h"
#include <vector>
#include <algorithm>
#include <limits>
2023-09-04 06:44:22 +03:00
#include <pcb_shape.h>
#include <geometry/circle.h>
#include <nanoflann.hpp>
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 */ >;
2023-09-04 06:44:22 +03:00
/**
* Searches 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 kdTree The KD-tree for efficient nearest neighbor search.
* @param adaptor The adaptor containing the endpoints data.
* @param aChainingEpsilon is the distance from \a aPoint that still constitutes a valid find.
2023-09-04 06:44:22 +03:00
* @return PCB_SHAPE* - The first PCB_SHAPE that has a start or end point matching
* aPoint, otherwise NULL if none.
*/
static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const VECTOR2I& aPoint, const KDTree& kdTree,
const PCB_SHAPE_ENDPOINTS_ADAPTOR& adaptor, double aChainingEpsilon )
2023-09-04 06:44:22 +03:00
{
const double query_pt[2] = { static_cast<double>( aPoint.x ), static_cast<double>( aPoint.y ) };
2023-09-04 06:44:22 +03:00
uint32_t indices[2];
double distances[2];
kdTree.knnSearch( query_pt, 2, indices, distances );
2023-09-04 06:44:22 +03:00
if( distances[0] == std::numeric_limits<double>::max() )
return nullptr;
2023-09-04 06:44:22 +03:00
// Find the closest valid candidate
PCB_SHAPE* closest_graphic = nullptr;
double closest_dist_sq = aChainingEpsilon * aChainingEpsilon;
for( size_t i = 0; i < 2; ++i )
2023-09-04 06:44:22 +03:00
{
if( distances[i] == std::numeric_limits<double>::max() )
2023-09-04 06:44:22 +03:00
continue;
PCB_SHAPE* candidate = adaptor.endpoints[indices[i]].second;
2023-09-04 06:44:22 +03:00
if( candidate == aShape )
continue;
2023-09-04 06:44:22 +03:00
if( candidate->GetFlags() & SKIP_STRUCT )
continue;
2023-09-04 06:44:22 +03:00
if( distances[i] < closest_dist_sq )
2023-09-04 06:44:22 +03:00
{
closest_dist_sq = distances[i];
closest_graphic = candidate;
2023-09-04 06:44:22 +03:00
}
}
return closest_graphic;
2023-09-04 06:44:22 +03:00
}
void ConnectBoardShapes( std::vector<PCB_SHAPE*>& aShapeList, int aChainingEpsilon )
2023-09-04 06:44:22 +03:00
{
if( aShapeList.size() == 0 )
return;
// Pre-build KD-tree
PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( aShapeList );
KDTree kdTree( 2, adaptor );
2023-09-04 06:44:22 +03:00
auto closer_to_first = []( const VECTOR2I& aRef, const VECTOR2I& aFirst,
const VECTOR2I& aSecond ) -> bool
{
return ( aRef - aFirst ).SquaredEuclideanNorm() < ( aRef - aSecond ).SquaredEuclideanNorm();
};
auto min_distance_sq = []( const VECTOR2I& aRef, const VECTOR2I& aFirst,
2023-10-31 04:25:16 +03:00
const VECTOR2I& aSecond ) -> SEG::ecoord
{
return std::min( ( aRef - aFirst ).SquaredEuclideanNorm(),
( aRef - aSecond ).SquaredEuclideanNorm() );
};
2023-09-04 06:44:22 +03:00
auto connectPair = [&]( PCB_SHAPE* aPrevShape, PCB_SHAPE* aShape )
{
bool success = false;
SHAPE_T shape0 = aPrevShape->GetShape();
SHAPE_T shape1 = aShape->GetShape();
if( shape0 == SHAPE_T::SEGMENT && shape1 == SHAPE_T::SEGMENT )
{
SEG seg0( aPrevShape->GetStart(), aPrevShape->GetEnd() );
SEG seg1( aShape->GetStart(), aShape->GetEnd() );
SEG::ecoord d[4];
d[0] = ( seg0.A - seg1.A ).SquaredEuclideanNorm();
d[1] = ( seg0.A - seg1.B ).SquaredEuclideanNorm();
d[2] = ( seg0.B - seg1.A ).SquaredEuclideanNorm();
d[3] = ( seg0.B - seg1.B ).SquaredEuclideanNorm();
int idx = std::min_element( d, d + 4 ) - d;
int i0 = idx / 2;
int i1 = idx % 2;
2023-09-04 06:44:22 +03:00
if( seg0.Intersects( seg1 ) || seg0.Angle( seg1 ) > ANGLE_45 )
{
if( OPT_VECTOR2I inter = seg0.IntersectLines( seg1 ) )
{
if( i0 == 0 )
2023-09-04 06:44:22 +03:00
aPrevShape->SetStart( *inter );
else
aPrevShape->SetEnd( *inter );
if( i1 == 0 )
2023-09-04 06:44:22 +03:00
aShape->SetStart( *inter );
else
aShape->SetEnd( *inter );
success = true;
}
}
}
else if( ( shape0 == SHAPE_T::ARC && shape1 == SHAPE_T::SEGMENT )
|| ( shape0 == SHAPE_T::SEGMENT && shape1 == SHAPE_T::ARC ) )
2023-09-04 06:44:22 +03:00
{
PCB_SHAPE* arcShape = shape0 == SHAPE_T::ARC ? aPrevShape : aShape;
PCB_SHAPE* segShape = shape0 == SHAPE_T::SEGMENT ? aPrevShape : aShape;
VECTOR2I arcPts[2] = { arcShape->GetStart(), arcShape->GetEnd() };
VECTOR2I segPts[2] = { segShape->GetStart(), segShape->GetEnd() };
2023-09-04 06:44:22 +03:00
SEG::ecoord d[4];
d[0] = ( segPts[0] - arcPts[0] ).SquaredEuclideanNorm();
d[1] = ( segPts[0] - arcPts[1] ).SquaredEuclideanNorm();
d[2] = ( segPts[1] - arcPts[0] ).SquaredEuclideanNorm();
d[3] = ( segPts[1] - arcPts[1] ).SquaredEuclideanNorm();
2023-09-04 06:44:22 +03:00
int idx = std::min_element( d, d + 4 ) - d;
2023-09-04 06:44:22 +03:00
switch( idx )
2023-09-04 06:44:22 +03:00
{
case 0: segShape->SetStart( arcPts[0] ); break;
case 1: segShape->SetStart( arcPts[1] ); break;
case 2: segShape->SetEnd( arcPts[0] ); break;
case 3: segShape->SetEnd( arcPts[1] ); break;
}
success = true;
}
else if( shape0 == SHAPE_T::ARC && shape1 == SHAPE_T::ARC )
{
PCB_SHAPE* arc0 = aPrevShape;
PCB_SHAPE* arc1 = aShape;
VECTOR2I pts0[2] = { arc0->GetStart(), arc0->GetEnd() };
VECTOR2I pts1[2] = { arc1->GetStart(), arc1->GetEnd() };
SEG::ecoord d[4];
d[0] = ( pts0[0] - pts1[0] ).SquaredEuclideanNorm();
d[1] = ( pts0[0] - pts1[1] ).SquaredEuclideanNorm();
d[2] = ( pts0[1] - pts1[0] ).SquaredEuclideanNorm();
d[3] = ( pts0[1] - pts1[1] ).SquaredEuclideanNorm();
int idx = std::min_element( d, d + 4 ) - d;
int i0 = idx / 2;
int i1 = idx % 2;
VECTOR2I middle = ( pts0[i0] + pts1[i1] ) / 2;
if( i0 == 0 )
arc0->SetArcGeometry( middle, arc0->GetArcMid(), arc0->GetEnd() );
else
arc0->SetArcGeometry( arc0->GetStart(), arc0->GetArcMid(), middle );
if( i1 == 0 )
arc1->SetArcGeometry( middle, arc1->GetArcMid(), arc1->GetEnd() );
else
arc1->SetArcGeometry( arc1->GetStart(), arc1->GetArcMid(), middle );
success = true;
}
else if( ( shape0 == SHAPE_T::BEZIER && shape1 == SHAPE_T::ARC )
|| ( shape0 == SHAPE_T::ARC && shape1 == SHAPE_T::BEZIER ) )
{
PCB_SHAPE* bezShape = shape0 == SHAPE_T::BEZIER ? aPrevShape : aShape;
PCB_SHAPE* arcShape = shape0 == SHAPE_T::ARC ? aPrevShape : aShape;
2023-09-04 06:44:22 +03:00
VECTOR2I bezPts[2] = { bezShape->GetStart(), bezShape->GetEnd() };
VECTOR2I arcPts[2] = { arcShape->GetStart(), arcShape->GetEnd() };
2023-09-04 06:44:22 +03:00
SEG::ecoord d[4];
d[0] = ( bezPts[0] - arcPts[0] ).SquaredEuclideanNorm();
d[1] = ( bezPts[0] - arcPts[1] ).SquaredEuclideanNorm();
d[2] = ( bezPts[1] - arcPts[0] ).SquaredEuclideanNorm();
d[3] = ( bezPts[1] - arcPts[1] ).SquaredEuclideanNorm();
int idx = std::min_element( d, d + 4 ) - d;
2023-09-04 06:44:22 +03:00
switch( idx )
{
case 0:
{
VECTOR2I delta = arcPts[0] - bezPts[0];
bezShape->SetStart( arcPts[0] );
bezShape->SetBezierC1( bezShape->GetBezierC1() + delta );
break;
}
case 1:
2023-09-04 06:44:22 +03:00
{
VECTOR2I delta = arcPts[1] - bezPts[0];
bezShape->SetStart( arcPts[1] );
bezShape->SetBezierC1( bezShape->GetBezierC1() + delta );
break;
}
case 2:
{
VECTOR2I delta = arcPts[0] - bezPts[1];
bezShape->SetEnd( arcPts[0] );
bezShape->SetBezierC2( bezShape->GetBezierC2() + delta );
break;
}
case 3:
{
VECTOR2I delta = arcPts[1] - bezPts[1];
bezShape->SetEnd( arcPts[1] );
bezShape->SetBezierC2( bezShape->GetBezierC2() + delta );
break;
}
}
2023-09-04 06:44:22 +03:00
success = true;
}
else if( ( shape0 == SHAPE_T::BEZIER && shape1 == SHAPE_T::SEGMENT )
|| ( shape0 == SHAPE_T::SEGMENT && shape1 == SHAPE_T::BEZIER ) )
{
PCB_SHAPE* bezShape = shape0 == SHAPE_T::BEZIER ? aPrevShape : aShape;
PCB_SHAPE* segShape = shape0 == SHAPE_T::SEGMENT ? aPrevShape : aShape;
2023-09-04 06:44:22 +03:00
VECTOR2I bezPts[2] = { bezShape->GetStart(), bezShape->GetEnd() };
VECTOR2I segPts[2] = { segShape->GetStart(), segShape->GetEnd() };
2023-09-04 06:44:22 +03:00
SEG::ecoord d[4];
d[0] = ( segPts[0] - bezPts[0] ).SquaredEuclideanNorm();
d[1] = ( segPts[0] - bezPts[1] ).SquaredEuclideanNorm();
d[2] = ( segPts[1] - bezPts[0] ).SquaredEuclideanNorm();
d[3] = ( segPts[1] - bezPts[1] ).SquaredEuclideanNorm();
2023-09-04 06:44:22 +03:00
int idx = std::min_element( d, d + 4 ) - d;
switch( idx )
{
case 0: segShape->SetStart( bezPts[0] ); break;
case 1: segShape->SetStart( bezPts[1] ); break;
case 2: segShape->SetEnd( bezPts[0] ); break;
case 3: segShape->SetEnd( bezPts[1] ); break;
2023-09-04 06:44:22 +03:00
}
success = true;
2023-09-04 06:44:22 +03:00
}
else if( shape0 == SHAPE_T::BEZIER && shape1 == SHAPE_T::BEZIER )
{
PCB_SHAPE* bez0 = aPrevShape;
PCB_SHAPE* bez1 = aShape;
VECTOR2I pts0[2] = { bez0->GetStart(), bez0->GetEnd() };
VECTOR2I pts1[2] = { bez1->GetStart(), bez1->GetEnd() };
SEG::ecoord d[4];
d[0] = ( pts0[0] - pts1[0] ).SquaredEuclideanNorm();
d[1] = ( pts0[0] - pts1[1] ).SquaredEuclideanNorm();
d[2] = ( pts0[1] - pts1[0] ).SquaredEuclideanNorm();
d[3] = ( pts0[1] - pts1[1] ).SquaredEuclideanNorm();
int idx = std::min_element( d, d + 4 ) - d;
int i0 = idx / 2;
int i1 = idx % 2;
VECTOR2I middle = ( pts0[i0] + pts1[i1] ) / 2;
// Adjust first bezier curve
if( i0 == 0 )
{
VECTOR2I delta = middle - bez0->GetStart();
bez0->SetStart( middle );
bez0->SetBezierC1( bez0->GetBezierC1() + delta );
}
else
{
VECTOR2I delta = middle - bez0->GetEnd();
bez0->SetEnd( middle );
bez0->SetBezierC2( bez0->GetBezierC2() + delta );
}
// Adjust second bezier curve
if( i1 == 0 )
{
VECTOR2I delta = middle - bez1->GetStart();
bez1->SetStart( middle );
bez1->SetBezierC1( bez1->GetBezierC1() + delta );
}
else
{
VECTOR2I delta = middle - bez1->GetEnd();
bez1->SetEnd( middle );
bez1->SetBezierC2( bez1->GetBezierC2() + delta );
}
success = true;
}
2023-09-04 06:44:22 +03:00
return success;
};
PCB_SHAPE* graphic = nullptr;
std::set<PCB_SHAPE*> startCandidates;
for( PCB_SHAPE* shape : aShapeList )
{
if( shape->GetShape() == SHAPE_T::SEGMENT || shape->GetShape() == SHAPE_T::ARC
|| shape->GetShape() == SHAPE_T::BEZIER )
{
shape->ClearFlags( SKIP_STRUCT );
startCandidates.emplace( shape );
}
}
while( startCandidates.size() )
{
graphic = *startCandidates.begin();
auto walkFrom = [&]( PCB_SHAPE* curr_graphic, VECTOR2I startPt )
2023-09-04 06:44:22 +03:00
{
VECTOR2I prevPt = startPt;
for( ;; )
{
// Get next closest segment.
PCB_SHAPE* nextGraphic =
findNext( curr_graphic, prevPt, kdTree, adaptor, aChainingEpsilon );
2023-09-04 06:44:22 +03:00
if( !nextGraphic )
break;
connectPair( curr_graphic, nextGraphic );
2023-09-04 06:44:22 +03:00
prevPt = closer_to_first( prevPt, nextGraphic->GetStart(), nextGraphic->GetEnd() )
? nextGraphic->GetEnd()
: nextGraphic->GetStart();
curr_graphic = nextGraphic;
curr_graphic->SetFlags( SKIP_STRUCT );
startCandidates.erase( curr_graphic );
2023-09-04 06:44:22 +03:00
}
};
const VECTOR2I ptEnd = graphic->GetEnd();
const VECTOR2I ptStart = graphic->GetStart();
PCB_SHAPE* grAtEnd = findNext( graphic, ptEnd, kdTree, adaptor, aChainingEpsilon );
PCB_SHAPE* grAtStart = findNext( graphic, ptStart, kdTree, adaptor, aChainingEpsilon );
bool beginFromEndPt = true;
// We need to start walking from a point that is closest to a point of another shape.
if( grAtEnd && grAtStart )
{
SEG::ecoord dAtEnd = min_distance_sq( ptEnd, grAtEnd->GetStart(), grAtEnd->GetEnd() );
SEG::ecoord dAtStart =
min_distance_sq( ptStart, grAtStart->GetStart(), grAtStart->GetEnd() );
beginFromEndPt = dAtEnd <= dAtStart;
}
else if( grAtEnd )
beginFromEndPt = true;
else if( grAtStart )
beginFromEndPt = false;
if( beginFromEndPt )
{
// Do not inline GetEnd / GetStart as endpoints may update
walkFrom( graphic, graphic->GetEnd() );
walkFrom( graphic, graphic->GetStart() );
}
else
{
walkFrom( graphic, graphic->GetStart() );
walkFrom( graphic, graphic->GetEnd() );
}
2023-09-04 06:44:22 +03:00
startCandidates.erase( graphic );
}
}