2023-10-18 11:14:43 +02:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2023 Alex Shvartzkop <dudesuchamazing@gmail.com>
|
2025-01-01 13:30:11 -08:00
|
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
2023-10-18 11:14:43 +02:00
|
|
|
*
|
|
|
|
* 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>
|
2025-08-24 09:34:34 -07:00
|
|
|
#include <algorithm>
|
2025-08-25 11:26:02 -07:00
|
|
|
#include <limits>
|
2023-09-04 06:44:22 +03:00
|
|
|
#include <pcb_shape.h>
|
|
|
|
#include <geometry/circle.h>
|
|
|
|
|
2025-08-25 11:26:02 -07:00
|
|
|
#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.
|
2025-08-25 11:26:02 -07:00
|
|
|
* @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.
|
|
|
|
*/
|
2025-08-25 11:26:02 -07:00
|
|
|
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
|
|
|
{
|
2025-08-25 11:26:02 -07:00
|
|
|
const double query_pt[2] = { static_cast<double>( aPoint.x ), static_cast<double>( aPoint.y ) };
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-25 11:26:02 -07:00
|
|
|
uint32_t indices[2];
|
|
|
|
double distances[2];
|
|
|
|
kdTree.knnSearch( query_pt, 2, indices, distances );
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-25 11:26:02 -07:00
|
|
|
if( distances[0] == std::numeric_limits<double>::max() )
|
|
|
|
return nullptr;
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-25 11:26:02 -07: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
|
|
|
{
|
2025-08-25 11:26:02 -07:00
|
|
|
if( distances[i] == std::numeric_limits<double>::max() )
|
2023-09-04 06:44:22 +03:00
|
|
|
continue;
|
|
|
|
|
2025-08-25 11:26:02 -07:00
|
|
|
PCB_SHAPE* candidate = adaptor.endpoints[indices[i]].second;
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-25 11:26:02 -07:00
|
|
|
if( candidate == aShape )
|
|
|
|
continue;
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-25 11:26:02 -07:00
|
|
|
if( candidate->GetFlags() & SKIP_STRUCT )
|
|
|
|
continue;
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-25 11:26:02 -07:00
|
|
|
if( distances[i] < closest_dist_sq )
|
2023-09-04 06:44:22 +03:00
|
|
|
{
|
2025-08-25 11:26:02 -07:00
|
|
|
closest_dist_sq = distances[i];
|
|
|
|
closest_graphic = candidate;
|
2023-09-04 06:44:22 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-25 11:26:02 -07:00
|
|
|
return closest_graphic;
|
2023-09-04 06:44:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-08-25 11:26:02 -07:00
|
|
|
void ConnectBoardShapes( std::vector<PCB_SHAPE*>& aShapeList, int aChainingEpsilon )
|
2023-09-04 06:44:22 +03:00
|
|
|
{
|
|
|
|
if( aShapeList.size() == 0 )
|
|
|
|
return;
|
|
|
|
|
2025-08-25 11:26:02 -07:00
|
|
|
// 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();
|
|
|
|
};
|
|
|
|
|
2023-10-17 10:27:59 +03:00
|
|
|
auto min_distance_sq = []( const VECTOR2I& aRef, const VECTOR2I& aFirst,
|
2023-10-31 04:25:16 +03:00
|
|
|
const VECTOR2I& aSecond ) -> SEG::ecoord
|
2023-10-17 10:27:59 +03:00
|
|
|
{
|
|
|
|
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() );
|
2025-08-24 09:34:34 -07:00
|
|
|
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 ) )
|
|
|
|
{
|
2025-08-24 09:34:34 -07:00
|
|
|
if( i0 == 0 )
|
2023-09-04 06:44:22 +03:00
|
|
|
aPrevShape->SetStart( *inter );
|
|
|
|
else
|
|
|
|
aPrevShape->SetEnd( *inter );
|
|
|
|
|
2025-08-24 09:34:34 -07:00
|
|
|
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 )
|
2023-09-04 20:48:34 +01:00
|
|
|
|| ( 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;
|
|
|
|
|
2025-08-24 09:34:34 -07:00
|
|
|
VECTOR2I arcPts[2] = { arcShape->GetStart(), arcShape->GetEnd() };
|
|
|
|
VECTOR2I segPts[2] = { segShape->GetStart(), segShape->GetEnd() };
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-24 09:34:34 -07: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
|
|
|
|
2025-08-24 09:34:34 -07:00
|
|
|
int idx = std::min_element( d, d + 4 ) - d;
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-24 09:34:34 -07:00
|
|
|
switch( idx )
|
2023-09-04 06:44:22 +03:00
|
|
|
{
|
2025-08-24 09:34:34 -07: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;
|
|
|
|
}
|
2023-10-17 10:27:59 +03:00
|
|
|
|
2025-08-24 09:34:34 -07:00
|
|
|
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
|
|
|
|
2025-08-24 09:34:34 -07:00
|
|
|
VECTOR2I bezPts[2] = { bezShape->GetStart(), bezShape->GetEnd() };
|
|
|
|
VECTOR2I arcPts[2] = { arcShape->GetStart(), arcShape->GetEnd() };
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-24 09:34:34 -07: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();
|
2023-10-17 10:27:59 +03:00
|
|
|
|
2025-08-24 09:34:34 -07:00
|
|
|
int idx = std::min_element( d, d + 4 ) - d;
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-24 09:34:34 -07: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
|
|
|
{
|
2025-08-24 09:34:34 -07: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
|
|
|
|
2025-08-24 09:34:34 -07: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
|
|
|
|
2025-08-24 09:34:34 -07:00
|
|
|
VECTOR2I bezPts[2] = { bezShape->GetStart(), bezShape->GetEnd() };
|
|
|
|
VECTOR2I segPts[2] = { segShape->GetStart(), segShape->GetEnd() };
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-24 09:34:34 -07: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
|
|
|
|
2025-08-24 09:34:34 -07: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
|
|
|
}
|
2025-08-24 09:34:34 -07:00
|
|
|
|
|
|
|
success = true;
|
2023-09-04 06:44:22 +03:00
|
|
|
}
|
2025-08-25 11:26:02 -07: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();
|
|
|
|
|
2023-09-07 13:41:25 +02:00
|
|
|
auto walkFrom = [&]( PCB_SHAPE* curr_graphic, VECTOR2I startPt )
|
2023-09-04 06:44:22 +03:00
|
|
|
{
|
|
|
|
VECTOR2I prevPt = startPt;
|
|
|
|
|
|
|
|
for( ;; )
|
|
|
|
{
|
|
|
|
// Get next closest segment.
|
2023-10-17 10:27:59 +03:00
|
|
|
PCB_SHAPE* nextGraphic =
|
2025-08-25 11:26:02 -07:00
|
|
|
findNext( curr_graphic, prevPt, kdTree, adaptor, aChainingEpsilon );
|
2023-09-04 06:44:22 +03:00
|
|
|
|
|
|
|
if( !nextGraphic )
|
|
|
|
break;
|
|
|
|
|
2025-08-24 09:34:34 -07:00
|
|
|
connectPair( curr_graphic, nextGraphic );
|
2023-09-04 06:44:22 +03:00
|
|
|
|
2025-08-24 09:34:34 -07:00
|
|
|
prevPt = closer_to_first( prevPt, nextGraphic->GetStart(), nextGraphic->GetEnd() )
|
|
|
|
? nextGraphic->GetEnd()
|
|
|
|
: nextGraphic->GetStart();
|
2023-09-07 13:41:25 +02:00
|
|
|
curr_graphic = nextGraphic;
|
|
|
|
curr_graphic->SetFlags( SKIP_STRUCT );
|
|
|
|
startCandidates.erase( curr_graphic );
|
2023-09-04 06:44:22 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-10-17 10:27:59 +03:00
|
|
|
const VECTOR2I ptEnd = graphic->GetEnd();
|
|
|
|
const VECTOR2I ptStart = graphic->GetStart();
|
|
|
|
|
2025-08-25 11:26:02 -07:00
|
|
|
PCB_SHAPE* grAtEnd = findNext( graphic, ptEnd, kdTree, adaptor, aChainingEpsilon );
|
|
|
|
PCB_SHAPE* grAtStart = findNext( graphic, ptStart, kdTree, adaptor, aChainingEpsilon );
|
2023-10-17 10:27:59 +03:00
|
|
|
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
}
|