diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 08467f88e5..0ed71f2aab 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -284,6 +284,7 @@ set( PCBNEW_CLASS_SRCS edit.cpp edit_track_width.cpp files.cpp + fix_board_shape.cpp footprint_info_impl.cpp footprint_wizard.cpp footprint_editor_utils.cpp diff --git a/pcbnew/convert_shape_list_to_polygon.cpp b/pcbnew/convert_shape_list_to_polygon.cpp index e0ad590896..1e399351d7 100644 --- a/pcbnew/convert_shape_list_to_polygon.cpp +++ b/pcbnew/convert_shape_list_to_polygon.cpp @@ -57,7 +57,7 @@ const wxChar* traceBoardOutline = wxT( "KICAD_BOARD_OUTLINE" ); * @param aLimit is a measure of proximity that the caller knows about. * @return bool - true if the two points are close enough, else false. */ -bool close_enough( VECTOR2I aLeft, VECTOR2I aRight, unsigned aLimit ) +static bool close_enough( VECTOR2I aLeft, VECTOR2I aRight, unsigned aLimit ) { return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit ); } @@ -71,7 +71,7 @@ bool close_enough( VECTOR2I aLeft, VECTOR2I aRight, unsigned aLimit ) * @param aSecond is the second point * @return bool - true if the first point is closest to the reference, otherwise false. */ -bool closer_to_first( VECTOR2I aRef, VECTOR2I aFirst, VECTOR2I aSecond ) +static bool closer_to_first( VECTOR2I aRef, VECTOR2I aFirst, VECTOR2I aSecond ) { return ( aRef - aFirst ).SquaredEuclideanNorm() < ( aRef - aSecond ).SquaredEuclideanNorm(); } @@ -132,7 +132,7 @@ static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const VECTOR2I& aPoint, } -bool isCopperOutside( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aShape ) +static bool isCopperOutside( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aShape ) { bool padOutside = false; diff --git a/pcbnew/fix_board_shape.cpp b/pcbnew/fix_board_shape.cpp new file mode 100644 index 0000000000..66b9f4e7c8 --- /dev/null +++ b/pcbnew/fix_board_shape.cpp @@ -0,0 +1,251 @@ +#include "fix_board_shape.h" + +#include +#include +#include + + +/** + * 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 aList The list to remove from. + * @param aLimit is the distance from \a aPoint that still constitutes a valid find. + * @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 std::vector& 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 || ( graphic->GetFlags() & SKIP_STRUCT ) != 0 ) + 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 +} + + +void ConnectBoardShapes( std::vector& aShapeList, + std::vector>& aNewShapes, int aChainingEpsilon ) +{ + if( aShapeList.size() == 0 ) + return; + + auto close_enough = []( const VECTOR2I& aLeft, const VECTOR2I& aRight, unsigned aLimit ) -> bool + { + return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit ); + }; + + auto closer_to_first = []( const VECTOR2I& aRef, const VECTOR2I& aFirst, + const VECTOR2I& aSecond ) -> bool + { + return ( aRef - aFirst ).SquaredEuclideanNorm() < ( aRef - aSecond ).SquaredEuclideanNorm(); + }; + + auto addSegment = [&]( const VECTOR2I start, const VECTOR2I end, int width, PCB_LAYER_ID layer ) + { + std::unique_ptr seg = std::make_unique( nullptr, SHAPE_T::SEGMENT ); + seg->SetStart( start ); + seg->SetEnd( end ); + seg->SetWidth( width ); + seg->SetLayer( layer ); + + aNewShapes.emplace_back( std::move( seg ) ); + }; + + 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() ); + + if( seg0.Intersects( seg1 ) || seg0.Angle( seg1 ) > ANGLE_45 ) + { + if( OPT_VECTOR2I inter = seg0.IntersectLines( seg1 ) ) + { + if( closer_to_first( *inter, seg0.A, seg0.B ) ) + aPrevShape->SetStart( *inter ); + else + aPrevShape->SetEnd( *inter ); + + if( closer_to_first( *inter, seg1.A, seg1.B ) ) + 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 ) + { + PCB_SHAPE* arcShape = shape0 == SHAPE_T::ARC ? aPrevShape : aShape; + PCB_SHAPE* segShape = shape0 == SHAPE_T::SEGMENT ? aPrevShape : aShape; + + SHAPE_ARC arc = + SHAPE_ARC( arcShape->GetStart(), arcShape->GetArcMid(), arcShape->GetEnd(), 0 ); + + EDA_ANGLE extAngle( 20, DEGREES_T ); + if( arc.IsClockwise() ) + extAngle = -extAngle; + + VECTOR2D arcStart = arc.GetP0(); + EDA_ANGLE arcAngle = arc.GetCentralAngle(); + + RotatePoint( arcStart, arc.GetCenter(), extAngle ); + arcAngle += extAngle * 2; + + arcAngle = std::clamp( arcAngle, -ANGLE_360, ANGLE_360 ); + + SHAPE_ARC extarc( arc.GetCenter(), arcStart, arcAngle ); + SEG seg( segShape->GetStart(), segShape->GetEnd() ); + + std::vector ips; + std::vector onSeg; + extarc.IntersectLine( seg, &ips ); + + for( const VECTOR2I& ip : ips ) + { + if( seg.Distance( ip ) <= aChainingEpsilon ) + { + if( closer_to_first( ip, seg.A, seg.B ) ) + segShape->SetStart( ip ); + else + segShape->SetEnd( ip ); + + if( closer_to_first( ip, arc.GetP0(), arc.GetP1() ) ) + arcShape->SetArcGeometry( ip, arc.GetArcMid(), arc.GetP1() ); + else + arcShape->SetArcGeometry( arc.GetP0(), arc.GetArcMid(), ip ); + + success = true; + } + } + + if( !success ) + { + VECTOR2I lineProj = seg.LineProject( arc.GetCenter() ); + + if( closer_to_first( lineProj, seg.A, seg.B ) ) + segShape->SetStart( lineProj ); + else + segShape->SetEnd( lineProj ); + + CIRCLE circ( arc.GetCenter(), arc.GetRadius() ); + VECTOR2I circProj = circ.NearestPoint( lineProj ); + + if( closer_to_first( circProj, arc.GetP0(), arc.GetP1() ) ) + arcShape->SetArcGeometry( circProj, arc.GetArcMid(), arc.GetP1() ); + else + arcShape->SetArcGeometry( arc.GetP0(), arc.GetArcMid(), circProj ); + + + addSegment( circProj, lineProj, segShape->GetWidth(), segShape->GetLayer() ); + success = true; + } + } + + return success; + }; + + PCB_SHAPE* graphic = nullptr; + + std::set 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 ); + } + } + + PCB_SHAPE* prevGraphic = nullptr; + + while( startCandidates.size() ) + { + graphic = *startCandidates.begin(); + + auto walkFrom = [&]( PCB_SHAPE* graphic, VECTOR2I startPt ) + { + VECTOR2I prevPt = startPt; + + for( ;; ) + { + // Get next closest segment. + PCB_SHAPE* nextGraphic = findNext( graphic, prevPt, aShapeList, aChainingEpsilon ); + + if( !nextGraphic ) + break; + + VECTOR2I nstart = nextGraphic->GetStart(); + VECTOR2I nend = nextGraphic->GetEnd(); + + if( !closer_to_first( prevPt, nstart, nend ) ) + std::swap( nstart, nend ); + + if( !connectPair( graphic, nextGraphic ) ) + addSegment( prevPt, nstart, graphic->GetWidth(), graphic->GetLayer() ); + + // Shape might've changed + nstart = nextGraphic->GetStart(); + nend = nextGraphic->GetEnd(); + + if( !closer_to_first( prevPt, nstart, nend ) ) + std::swap( nstart, nend ); + + prevPt = nend; + graphic = nextGraphic; + graphic->SetFlags( SKIP_STRUCT ); + startCandidates.erase( graphic ); + } + }; + + walkFrom( graphic, graphic->GetEnd() ); + walkFrom( graphic, graphic->GetStart() ); + startCandidates.erase( graphic ); + } +} diff --git a/pcbnew/fix_board_shape.h b/pcbnew/fix_board_shape.h new file mode 100644 index 0000000000..44e8f53185 --- /dev/null +++ b/pcbnew/fix_board_shape.h @@ -0,0 +1,41 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2023 Alex Shvartzkop + * 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 FIX_BOARD_SHAPE_H_ +#define FIX_BOARD_SHAPE_H_ + +#include +#include +#include + +/** + * Connects shapes to each other, making continious contours (adjacent shapes will have a common vertex) + * aChainingEpsilon is the max distance between vertices of different shapes to connect. + * Modifies original shapes, or creates new line segments and stores them in aNewShapes. + */ +void ConnectBoardShapes( std::vector& aShapeList, + std::vector>& aNewShapes, + int aChainingEpsilon ); + +#endif // FIX_BOARD_SHAPE_H_ \ No newline at end of file