2017-03-19 15:38:30 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
|
|
|
|
* Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
|
2020-10-24 08:04:15 +00:00
|
|
|
* Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors.
|
2017-03-19 15:38:30 +00: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
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <trigo.h>
|
|
|
|
#include <macros.h>
|
|
|
|
|
2019-03-01 13:36:53 +00:00
|
|
|
#include <math/vector2d.h>
|
2020-10-04 23:34:59 +00:00
|
|
|
#include <pcb_shape.h>
|
2020-11-12 20:19:22 +00:00
|
|
|
#include <footprint.h>
|
2021-06-06 19:03:10 +00:00
|
|
|
#include <pad.h>
|
2017-03-19 15:38:30 +00:00
|
|
|
#include <base_units.h>
|
|
|
|
#include <convert_basic_shapes_to_polygon.h>
|
2019-03-01 13:36:53 +00:00
|
|
|
#include <geometry/shape_poly_set.h>
|
2018-03-08 12:48:29 +00:00
|
|
|
#include <geometry/geometry_utils.h>
|
2020-10-23 06:48:52 +00:00
|
|
|
#include <convert_drawsegment_list_to_polygon.h>
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
#include <wx/log.h>
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Flag to enable debug tracing for the board outline creation
|
|
|
|
*
|
|
|
|
* Use "KICAD_BOARD_OUTLINE" to enable.
|
|
|
|
*
|
|
|
|
* @ingroup trace_env_vars
|
|
|
|
*/
|
|
|
|
const wxChar* traceBoardOutline = wxT( "KICAD_BOARD_OUTLINE" );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Function close_enough
|
|
|
|
* is a local and tunable method of qualifying the proximity of two points.
|
|
|
|
*
|
|
|
|
* @param aLeft is the first point
|
|
|
|
* @param aRight is the second point
|
|
|
|
* @param aLimit is a measure of proximity that the caller knows about.
|
|
|
|
* @return bool - true if the two points are close enough, else false.
|
|
|
|
*/
|
2020-12-27 01:00:41 +00:00
|
|
|
bool close_enough( VECTOR2I aLeft, VECTOR2I aRight, unsigned aLimit )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-12-27 01:00:41 +00:00
|
|
|
return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit );
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-12-27 01:00:41 +00:00
|
|
|
* Function closer_to_first
|
2020-12-25 14:01:34 +00:00
|
|
|
* Local method which qualifies whether the start or end point of a segment is closest to a point.
|
2017-03-19 15:38:30 +00:00
|
|
|
*
|
2020-12-27 01:00:41 +00:00
|
|
|
* @param aRef is the reference point
|
2017-03-19 15:38:30 +00:00
|
|
|
* @param aFirst is the first point
|
|
|
|
* @param aSecond is the second point
|
2020-12-27 01:00:41 +00:00
|
|
|
* @return bool - true if the first point is closest to the reference, otherwise false.
|
2017-03-19 15:38:30 +00:00
|
|
|
*/
|
2020-12-27 01:00:41 +00:00
|
|
|
bool closer_to_first( VECTOR2I aRef, VECTOR2I aFirst, VECTOR2I aSecond )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-12-27 01:00:41 +00:00
|
|
|
return ( aRef - aFirst ).SquaredEuclideanNorm() < ( aRef - aSecond ).SquaredEuclideanNorm();
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2020-10-22 20:27:46 +00:00
|
|
|
* Searches for a PCB_SHAPE matching a given end point or start point in a list.
|
|
|
|
* @param aShape The starting shape.
|
2017-03-19 15:38:30 +00:00
|
|
|
* @param aPoint The starting or ending point to search for.
|
2017-06-17 08:40:38 +00:00
|
|
|
* @param aList The list to remove from.
|
2017-03-19 15:38:30 +00:00
|
|
|
* @param aLimit is the distance from \a aPoint that still constitutes a valid find.
|
2020-10-04 23:34:59 +00:00
|
|
|
* @return PCB_SHAPE* - The first PCB_SHAPE that has a start or end point matching
|
2017-03-19 15:38:30 +00:00
|
|
|
* aPoint, otherwise NULL if none.
|
|
|
|
*/
|
2020-10-22 20:27:46 +00:00
|
|
|
static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const wxPoint& aPoint,
|
|
|
|
const std::vector<PCB_SHAPE*>& aList, unsigned aLimit )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-12-27 01:00:41 +00:00
|
|
|
// Look for an unused, exact hit
|
|
|
|
for( PCB_SHAPE* graphic : aList )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-12-27 01:00:41 +00:00
|
|
|
if( graphic == aShape || ( graphic->GetFlags() & SKIP_STRUCT ) != 0 )
|
2020-10-22 20:27:46 +00:00
|
|
|
continue;
|
|
|
|
|
2017-03-19 15:38:30 +00:00
|
|
|
switch( graphic->GetShape() )
|
|
|
|
{
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::ARC:
|
2017-03-19 15:38:30 +00:00
|
|
|
if( aPoint == graphic->GetArcStart() || aPoint == graphic->GetArcEnd() )
|
|
|
|
return graphic;
|
|
|
|
|
2020-12-27 01:00:41 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
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;
|
|
|
|
|
|
|
|
switch( graphic->GetShape() )
|
|
|
|
{
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::ARC:
|
2020-12-27 01:00:41 +00:00
|
|
|
d_sq = ( pt - graphic->GetArcStart() ).SquaredEuclideanNorm();
|
|
|
|
|
|
|
|
if( d_sq < closest_dist_sq )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-12-27 01:00:41 +00:00
|
|
|
closest_dist_sq = d_sq;
|
|
|
|
closest_graphic = graphic;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
|
2020-12-27 01:00:41 +00:00
|
|
|
d_sq = ( pt - graphic->GetArcEnd() ).SquaredEuclideanNorm();
|
|
|
|
|
|
|
|
if( d_sq < closest_dist_sq )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-12-27 01:00:41 +00:00
|
|
|
closest_dist_sq = d_sq;
|
|
|
|
closest_graphic = graphic;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2020-12-27 01:00:41 +00:00
|
|
|
d_sq = ( pt - graphic->GetStart() ).SquaredEuclideanNorm();
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-27 01:00:41 +00:00
|
|
|
if( d_sq < closest_dist_sq )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-12-27 01:00:41 +00:00
|
|
|
closest_dist_sq = d_sq;
|
|
|
|
closest_graphic = graphic;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
|
2020-12-27 01:00:41 +00:00
|
|
|
d_sq = ( pt - graphic->GetEnd() ).SquaredEuclideanNorm();
|
|
|
|
|
|
|
|
if( d_sq < closest_dist_sq )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-12-27 01:00:41 +00:00
|
|
|
closest_dist_sq = d_sq;
|
|
|
|
closest_graphic = graphic;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-27 01:00:41 +00:00
|
|
|
return closest_graphic; // Note: will be nullptr if nothing within aLimit
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function ConvertOutlineToPolygon
|
2020-12-25 14:01:34 +00:00
|
|
|
* Build a polygon (with holes) from a PCB_SHAPE list, which is expected to be a closed main
|
|
|
|
* outline with perhaps closed inner outlines. These closed inner outlines are considered as
|
|
|
|
* holes in the main outline.
|
2017-03-19 15:38:30 +00:00
|
|
|
* @param aSegList the initial list of drawsegments (only lines, circles and arcs).
|
|
|
|
* @param aPolygons will contain the complex polygon.
|
2020-12-24 21:00:41 +00:00
|
|
|
* @param aErrorMax is the max error distance when polygonizing a curve (internal units)
|
|
|
|
* @param aChainingEpsilon is the max error distance when polygonizing a curve (internal units)
|
2020-11-21 20:42:27 +00:00
|
|
|
* @param aErrorHandler = an optional error handler
|
2017-03-19 15:38:30 +00:00
|
|
|
*/
|
2020-10-04 23:34:59 +00:00
|
|
|
bool ConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aSegList, SHAPE_POLY_SET& aPolygons,
|
2020-12-24 21:00:41 +00:00
|
|
|
int aErrorMax, int aChainingEpsilon,
|
|
|
|
OUTLINE_ERROR_HANDLER* aErrorHandler )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
|
|
|
if( aSegList.size() == 0 )
|
|
|
|
return true;
|
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
bool polygonComplete = false;
|
2020-12-26 10:59:06 +00:00
|
|
|
bool selfIntersecting = false;
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
wxString msg;
|
2020-12-26 10:59:06 +00:00
|
|
|
PCB_SHAPE* graphic = nullptr;
|
2020-10-22 20:27:46 +00:00
|
|
|
|
|
|
|
std::set<PCB_SHAPE*> startCandidates( aSegList.begin(), aSegList.end() );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
// Find edge point with minimum x, this should be in the outer polygon
|
2019-04-02 06:25:34 +00:00
|
|
|
// which will define the perimeter polygon polygon.
|
2017-03-19 15:38:30 +00:00
|
|
|
wxPoint xmin = wxPoint( INT_MAX, 0 );
|
|
|
|
int xmini = 0;
|
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
for( size_t i = 0; i < aSegList.size(); i++ )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-10-22 20:27:46 +00:00
|
|
|
graphic = (PCB_SHAPE*) aSegList[i];
|
|
|
|
graphic->ClearFlags( SKIP_STRUCT );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
switch( graphic->GetShape() )
|
|
|
|
{
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::RECT:
|
|
|
|
case PCB_SHAPE_TYPE::SEGMENT:
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
|
|
|
if( graphic->GetStart().x < xmin.x )
|
|
|
|
{
|
|
|
|
xmin = graphic->GetStart();
|
|
|
|
xmini = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( graphic->GetEnd().x < xmin.x )
|
|
|
|
{
|
|
|
|
xmin = graphic->GetEnd();
|
|
|
|
xmini = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::ARC:
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2018-03-08 18:03:56 +00:00
|
|
|
wxPoint pstart = graphic->GetArcStart();
|
|
|
|
wxPoint center = graphic->GetCenter();
|
|
|
|
double angle = -graphic->GetAngle();
|
2018-03-22 18:02:45 +00:00
|
|
|
double radius = graphic->GetRadius();
|
2020-12-24 21:00:41 +00:00
|
|
|
int steps = GetArcToSegmentCount( radius, aErrorMax, angle / 10.0 );
|
2018-03-08 18:03:56 +00:00
|
|
|
wxPoint pt;
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
for( int step = 1; step<=steps; ++step )
|
|
|
|
{
|
|
|
|
double rotation = ( angle * step ) / steps;
|
|
|
|
|
|
|
|
pt = pstart;
|
|
|
|
|
|
|
|
RotatePoint( &pt, center, rotation );
|
|
|
|
|
|
|
|
if( pt.x < xmin.x )
|
|
|
|
{
|
|
|
|
xmin = pt;
|
|
|
|
xmini = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::CIRCLE:
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
|
|
|
wxPoint pt = graphic->GetCenter();
|
|
|
|
|
|
|
|
// pt has minimum x point
|
|
|
|
pt.x -= graphic->GetRadius();
|
|
|
|
|
|
|
|
// when the radius <= 0, this is a mal-formed circle. Skip it
|
|
|
|
if( graphic->GetRadius() > 0 && pt.x < xmin.x )
|
|
|
|
{
|
|
|
|
xmin = pt;
|
|
|
|
xmini = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::CURVE:
|
2018-07-09 17:15:36 +00:00
|
|
|
{
|
|
|
|
graphic->RebuildBezierToSegmentsPointsList( graphic->GetWidth() );
|
|
|
|
|
2020-11-24 22:16:41 +00:00
|
|
|
for( const wxPoint& pt : graphic->GetBezierPoints() )
|
2018-07-09 17:15:36 +00:00
|
|
|
{
|
|
|
|
if( pt.x < xmin.x )
|
|
|
|
{
|
|
|
|
xmin = pt;
|
|
|
|
xmini = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::POLYGON:
|
2018-12-02 04:41:17 +00:00
|
|
|
{
|
2020-08-15 15:42:18 +00:00
|
|
|
const SHAPE_POLY_SET poly = graphic->GetPolyShape();
|
2020-11-07 17:35:24 +00:00
|
|
|
double orientation = 0.0;
|
|
|
|
VECTOR2I offset = VECTOR2I( 0, 0 );
|
|
|
|
|
2021-01-11 16:05:44 +00:00
|
|
|
if( graphic->GetParentFootprint() )
|
2020-11-07 17:35:24 +00:00
|
|
|
{
|
2021-01-11 16:05:44 +00:00
|
|
|
orientation = graphic->GetParentFootprint()->GetOrientation();
|
|
|
|
offset = graphic->GetParentFootprint()->GetPosition();
|
2020-11-07 17:35:24 +00:00
|
|
|
}
|
2018-12-02 04:41:17 +00:00
|
|
|
|
|
|
|
for( auto iter = poly.CIterate(); iter; iter++ )
|
|
|
|
{
|
2020-08-15 15:42:18 +00:00
|
|
|
VECTOR2I pt = *iter;
|
2018-12-02 15:33:12 +00:00
|
|
|
RotatePoint( pt, orientation );
|
|
|
|
pt += offset;
|
|
|
|
|
|
|
|
if( pt.x < xmin.x )
|
2018-12-02 04:41:17 +00:00
|
|
|
{
|
2018-12-02 15:33:12 +00:00
|
|
|
xmin.x = pt.x;
|
|
|
|
xmin.y = pt.y;
|
2018-12-02 04:41:17 +00:00
|
|
|
xmini = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2020-08-15 15:42:18 +00:00
|
|
|
|
2017-03-19 15:38:30 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-25 14:01:34 +00:00
|
|
|
// Keep a list of where the various segments came from so after doing our combined-polygon
|
|
|
|
// tests we can still report errors against the individual graphic items.
|
2020-11-21 20:42:27 +00:00
|
|
|
std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*> segOwners;
|
|
|
|
|
|
|
|
auto fetchOwner =
|
|
|
|
[&]( const SEG& seg ) -> PCB_SHAPE*
|
|
|
|
{
|
|
|
|
auto it = segOwners.find( std::make_pair( seg.A, seg.B ) );
|
|
|
|
return it == segOwners.end() ? nullptr : it->second;
|
|
|
|
};
|
|
|
|
|
2020-12-25 14:01:34 +00:00
|
|
|
// Grab the left most point, assume its on the board's perimeter, and see if we can put
|
|
|
|
// enough graphics together by matching endpoints to formulate a cohesive polygon.
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
PCB_SHAPE* prevGraphic = nullptr;
|
|
|
|
wxPoint prevPt;
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
graphic = (PCB_SHAPE*) aSegList[xmini];
|
2020-10-22 20:27:46 +00:00
|
|
|
graphic->SetFlags( SKIP_STRUCT );
|
|
|
|
startCandidates.erase( graphic );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2019-04-02 06:25:34 +00:00
|
|
|
// Output the outline perimeter as polygon.
|
2021-05-01 02:33:25 +00:00
|
|
|
if( graphic->GetShape() == PCB_SHAPE_TYPE::CIRCLE )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-10-13 10:55:24 +00:00
|
|
|
TransformCircleToPolygon( aPolygons, graphic->GetCenter(), graphic->GetRadius(),
|
|
|
|
ARC_LOW_DEF, ERROR_INSIDE );
|
2020-10-23 09:23:45 +00:00
|
|
|
polygonComplete = true;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
2021-05-01 02:33:25 +00:00
|
|
|
else if( graphic->GetShape() == PCB_SHAPE_TYPE::RECT )
|
2020-06-15 19:50:20 +00:00
|
|
|
{
|
2020-09-07 00:40:18 +00:00
|
|
|
std::vector<wxPoint> pts = graphic->GetRectCorners();
|
2020-06-15 19:50:20 +00:00
|
|
|
|
|
|
|
aPolygons.NewOutline();
|
|
|
|
|
|
|
|
for( const wxPoint& pt : pts )
|
|
|
|
aPolygons.Append( pt );
|
2020-10-23 09:23:45 +00:00
|
|
|
|
2020-12-25 13:11:03 +00:00
|
|
|
segOwners[ std::make_pair( pts[0], pts[1] ) ] = graphic;
|
|
|
|
segOwners[ std::make_pair( pts[1], pts[2] ) ] = graphic;
|
|
|
|
segOwners[ std::make_pair( pts[2], pts[3] ) ] = graphic;
|
|
|
|
segOwners[ std::make_pair( pts[3], pts[0] ) ] = graphic;
|
|
|
|
|
2020-10-23 09:23:45 +00:00
|
|
|
polygonComplete = true;
|
2020-06-15 19:50:20 +00:00
|
|
|
}
|
2021-05-01 02:33:25 +00:00
|
|
|
else if( graphic->GetShape() == PCB_SHAPE_TYPE::POLYGON )
|
2018-12-02 04:41:17 +00:00
|
|
|
{
|
2020-11-07 17:35:24 +00:00
|
|
|
double orientation = 0.0;
|
|
|
|
VECTOR2I offset = VECTOR2I( 0, 0 );
|
|
|
|
|
|
|
|
if( graphic->GetParentFootprint() )
|
|
|
|
{
|
|
|
|
orientation = graphic->GetParentFootprint()->GetOrientation();
|
|
|
|
offset = graphic->GetParentFootprint()->GetPosition();
|
|
|
|
}
|
2018-12-02 15:33:12 +00:00
|
|
|
|
|
|
|
aPolygons.NewOutline();
|
2020-11-21 20:42:27 +00:00
|
|
|
bool first = true;
|
2018-12-02 15:33:12 +00:00
|
|
|
|
|
|
|
for( auto it = graphic->GetPolyShape().CIterate( 0 ); it; it++ )
|
|
|
|
{
|
2020-11-07 17:35:24 +00:00
|
|
|
VECTOR2I pt = *it;
|
2018-12-02 15:33:12 +00:00
|
|
|
RotatePoint( pt, orientation );
|
|
|
|
pt += offset;
|
|
|
|
aPolygons.Append( pt );
|
2020-11-21 20:42:27 +00:00
|
|
|
|
|
|
|
if( first )
|
|
|
|
first = false;
|
|
|
|
else
|
|
|
|
segOwners[ std::make_pair( prevPt, pt ) ] = graphic;
|
|
|
|
|
|
|
|
prevPt = (wxPoint) pt;
|
2018-12-02 15:33:12 +00:00
|
|
|
}
|
2021-01-11 16:05:44 +00:00
|
|
|
|
|
|
|
polygonComplete = true;
|
2018-12-02 04:41:17 +00:00
|
|
|
}
|
2017-03-19 15:38:30 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// Polygon start point. Arbitrarily chosen end of the
|
|
|
|
// segment and build the poly from here.
|
|
|
|
|
2021-05-01 02:33:25 +00:00
|
|
|
wxPoint startPt = graphic->GetShape() == PCB_SHAPE_TYPE::ARC ? graphic->GetArcEnd()
|
2020-08-15 15:42:18 +00:00
|
|
|
: graphic->GetEnd();
|
|
|
|
|
|
|
|
prevPt = startPt;
|
2017-03-19 15:38:30 +00:00
|
|
|
aPolygons.NewOutline();
|
|
|
|
aPolygons.Append( prevPt );
|
|
|
|
|
|
|
|
// Do not append the other end point yet of this 'graphic', this first
|
2018-07-09 17:15:36 +00:00
|
|
|
// 'graphic' might be an arc or a curve.
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
switch( graphic->GetShape() )
|
|
|
|
{
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::RECT:
|
|
|
|
case PCB_SHAPE_TYPE::CIRCLE:
|
2020-12-26 10:59:06 +00:00
|
|
|
{
|
|
|
|
// As a non-first item, closed shapes can't be anything but self-intersecting
|
|
|
|
|
|
|
|
if( aErrorHandler )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-12-26 10:59:06 +00:00
|
|
|
wxASSERT( prevGraphic );
|
|
|
|
(*aErrorHandler)( _( "(self-intersecting)" ), prevGraphic, graphic, prevPt );
|
|
|
|
}
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
selfIntersecting = true;
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
// A closed shape will finish where it started, so no point in updating prevPt
|
|
|
|
}
|
|
|
|
break;
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::SEGMENT:
|
2020-12-26 10:59:06 +00:00
|
|
|
{
|
|
|
|
wxPoint nextPt;
|
|
|
|
|
|
|
|
// Use the line segment end point furthest away from prevPt as we assume
|
|
|
|
// the other end to be ON prevPt or very close to it.
|
|
|
|
|
2020-12-27 01:00:41 +00:00
|
|
|
if( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd()) )
|
2020-12-26 10:59:06 +00:00
|
|
|
nextPt = graphic->GetEnd();
|
|
|
|
else
|
|
|
|
nextPt = graphic->GetStart();
|
|
|
|
|
|
|
|
aPolygons.Append( nextPt );
|
|
|
|
segOwners[ std::make_pair( prevPt, nextPt ) ] = graphic;
|
|
|
|
prevPt = nextPt;
|
|
|
|
}
|
2017-03-19 15:38:30 +00:00
|
|
|
break;
|
|
|
|
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::ARC:
|
2020-12-26 10:59:06 +00:00
|
|
|
{
|
2020-08-15 15:42:18 +00:00
|
|
|
// We do not support arcs in polygons, so approximate an arc with a series of
|
|
|
|
// short lines and put those line segments into the !same! PATH.
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
wxPoint pstart = graphic->GetArcStart();
|
|
|
|
wxPoint pend = graphic->GetArcEnd();
|
|
|
|
wxPoint pcenter = graphic->GetCenter();
|
|
|
|
double angle = -graphic->GetAngle();
|
|
|
|
double radius = graphic->GetRadius();
|
|
|
|
int steps = GetArcToSegmentCount( radius, aErrorMax, angle / 10.0 );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
if( !close_enough( prevPt, pstart, aChainingEpsilon ) )
|
|
|
|
{
|
|
|
|
wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), aChainingEpsilon ) );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
angle = -angle;
|
|
|
|
std::swap( pstart, pend );
|
|
|
|
}
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
// Create intermediate points between start and end:
|
|
|
|
for( int step = 1; step < steps; ++step )
|
|
|
|
{
|
|
|
|
double rotation = ( angle * step ) / steps;
|
|
|
|
wxPoint pt = pstart;
|
|
|
|
RotatePoint( &pt, pcenter, rotation );
|
|
|
|
|
|
|
|
aPolygons.Append( pt );
|
|
|
|
segOwners[ std::make_pair( prevPt, pt ) ] = graphic;
|
|
|
|
prevPt = pt;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
2020-12-26 10:59:06 +00:00
|
|
|
|
|
|
|
// Append the last arc end point
|
|
|
|
aPolygons.Append( pend );
|
|
|
|
segOwners[ std::make_pair( prevPt, pend ) ] = graphic;
|
|
|
|
prevPt = pend;
|
|
|
|
}
|
2017-03-19 15:38:30 +00:00
|
|
|
break;
|
|
|
|
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::CURVE:
|
2020-12-26 10:59:06 +00:00
|
|
|
{
|
2020-08-15 15:42:18 +00:00
|
|
|
// We do not support Bezier curves in polygons, so approximate with a series
|
|
|
|
// of short lines and put those line segments into the !same! PATH.
|
2018-07-09 17:15:36 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
wxPoint nextPt;
|
|
|
|
bool first = true;
|
|
|
|
bool reverse = false;
|
2018-07-09 17:15:36 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
// Use the end point furthest away from
|
|
|
|
// prevPt as we assume the other end to be ON prevPt or
|
|
|
|
// very close to it.
|
2018-07-09 17:15:36 +00:00
|
|
|
|
2020-12-27 01:00:41 +00:00
|
|
|
if( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd()) )
|
2020-12-26 10:59:06 +00:00
|
|
|
{
|
|
|
|
nextPt = graphic->GetEnd();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
nextPt = graphic->GetStart();
|
|
|
|
reverse = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( reverse )
|
|
|
|
{
|
|
|
|
for( int jj = graphic->GetBezierPoints().size()-1; jj >= 0; jj-- )
|
2018-07-09 17:15:36 +00:00
|
|
|
{
|
2020-12-26 10:59:06 +00:00
|
|
|
const wxPoint& pt = graphic->GetBezierPoints()[jj];
|
|
|
|
aPolygons.Append( pt );
|
2020-12-25 13:11:03 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
if( first )
|
|
|
|
first = false;
|
|
|
|
else
|
|
|
|
segOwners[ std::make_pair( prevPt, pt ) ] = graphic;
|
2020-12-25 13:11:03 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
prevPt = pt;
|
2018-07-09 17:15:36 +00:00
|
|
|
}
|
2020-12-26 10:59:06 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for( const wxPoint& pt : graphic->GetBezierPoints() )
|
2018-07-09 17:15:36 +00:00
|
|
|
{
|
2020-12-26 10:59:06 +00:00
|
|
|
aPolygons.Append( pt );
|
2020-12-25 13:11:03 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
if( first )
|
|
|
|
first = false;
|
|
|
|
else
|
|
|
|
segOwners[ std::make_pair( prevPt, pt ) ] = graphic;
|
2020-12-25 13:11:03 +00:00
|
|
|
|
2020-12-26 10:59:06 +00:00
|
|
|
prevPt = pt;
|
2018-07-09 17:15:36 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-26 10:59:06 +00:00
|
|
|
|
|
|
|
prevPt = nextPt;
|
|
|
|
}
|
2018-07-09 17:15:36 +00:00
|
|
|
break;
|
|
|
|
|
2017-03-19 15:38:30 +00:00
|
|
|
default:
|
2020-10-22 20:27:46 +00:00
|
|
|
wxFAIL_MSG( "Unsupported PCB_SHAPE type "
|
|
|
|
+ BOARD_ITEM::ShowShape( graphic->GetShape() ) );
|
2017-03-20 12:05:38 +00:00
|
|
|
return false;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get next closest segment.
|
|
|
|
|
2020-12-24 21:00:41 +00:00
|
|
|
PCB_SHAPE* nextGraphic = findNext( graphic, prevPt, aSegList, aChainingEpsilon );
|
2020-10-22 20:27:46 +00:00
|
|
|
|
2020-11-21 20:42:27 +00:00
|
|
|
if( nextGraphic && !( nextGraphic->GetFlags() & SKIP_STRUCT ) )
|
2020-10-22 20:27:46 +00:00
|
|
|
{
|
2020-12-26 10:59:06 +00:00
|
|
|
prevGraphic = graphic;
|
2020-11-21 20:42:27 +00:00
|
|
|
graphic = nextGraphic;
|
2020-10-22 20:27:46 +00:00
|
|
|
graphic->SetFlags( SKIP_STRUCT );
|
|
|
|
startCandidates.erase( graphic );
|
|
|
|
continue;
|
|
|
|
}
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
// Finished, or ran into trouble...
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-24 21:00:41 +00:00
|
|
|
if( close_enough( startPt, prevPt, aChainingEpsilon ) )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-10-22 20:27:46 +00:00
|
|
|
polygonComplete = true;
|
|
|
|
break;
|
|
|
|
}
|
2020-11-21 20:42:27 +00:00
|
|
|
else if( nextGraphic ) // encountered already-used segment, but not at the start
|
2020-10-22 20:27:46 +00:00
|
|
|
{
|
2020-12-26 23:00:41 +00:00
|
|
|
if( aErrorHandler )
|
|
|
|
(*aErrorHandler)( _( "(self-intersecting)" ), graphic, nextGraphic, prevPt );
|
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
polygonComplete = false;
|
|
|
|
break;
|
|
|
|
}
|
2020-11-21 20:42:27 +00:00
|
|
|
else // encountered discontinuity
|
2020-10-22 20:27:46 +00:00
|
|
|
{
|
2020-11-21 20:42:27 +00:00
|
|
|
if( aErrorHandler )
|
|
|
|
(*aErrorHandler)( _( "(not a closed shape)" ), graphic, nullptr, prevPt );
|
2018-11-28 12:11:43 +00:00
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
polygonComplete = false;
|
2017-03-19 15:38:30 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-27 17:52:16 +00:00
|
|
|
int holeNum = -1;
|
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
while( startCandidates.size() )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-11-21 20:42:27 +00:00
|
|
|
int hole = aPolygons.NewHole();
|
|
|
|
bool firstPt = true;
|
2020-02-27 17:52:16 +00:00
|
|
|
holeNum++;
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
graphic = (PCB_SHAPE*) *startCandidates.begin();
|
|
|
|
graphic->SetFlags( SKIP_STRUCT );
|
|
|
|
startCandidates.erase( startCandidates.begin() );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2018-12-02 04:41:17 +00:00
|
|
|
// Both circles and polygons on the edge cuts layer are closed items that
|
|
|
|
// do not connect to other elements, so we process them independently
|
2021-05-01 02:33:25 +00:00
|
|
|
if( graphic->GetShape() == PCB_SHAPE_TYPE::POLYGON )
|
2018-12-02 04:41:17 +00:00
|
|
|
{
|
2020-11-07 17:35:24 +00:00
|
|
|
double orientation = 0.0;
|
|
|
|
VECTOR2I offset = VECTOR2I( 0, 0 );
|
|
|
|
|
|
|
|
if( graphic->GetParentFootprint() )
|
|
|
|
{
|
|
|
|
orientation = graphic->GetParentFootprint()->GetOrientation();
|
|
|
|
offset = graphic->GetParentFootprint()->GetPosition();
|
|
|
|
}
|
2018-12-02 15:33:12 +00:00
|
|
|
|
2018-12-02 04:41:17 +00:00
|
|
|
for( auto it = graphic->GetPolyShape().CIterate(); it; it++ )
|
|
|
|
{
|
2020-11-21 20:42:27 +00:00
|
|
|
VECTOR2I pt = *it;
|
|
|
|
RotatePoint( pt, orientation );
|
|
|
|
pt += offset;
|
2018-12-02 15:33:12 +00:00
|
|
|
|
2020-11-21 20:42:27 +00:00
|
|
|
aPolygons.Append( pt, -1, hole );
|
|
|
|
|
|
|
|
if( firstPt )
|
|
|
|
firstPt = false;
|
|
|
|
else
|
|
|
|
segOwners[ std::make_pair( prevPt, pt ) ] = graphic;
|
|
|
|
|
|
|
|
prevPt = (wxPoint) pt;
|
2018-12-02 04:41:17 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-01 02:33:25 +00:00
|
|
|
else if( graphic->GetShape() == PCB_SHAPE_TYPE::CIRCLE )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
|
|
|
// make a circle by segments;
|
2018-03-08 18:03:56 +00:00
|
|
|
wxPoint center = graphic->GetCenter();
|
|
|
|
double angle = 3600.0;
|
2018-03-22 18:02:45 +00:00
|
|
|
wxPoint start = center;
|
2018-03-08 18:03:56 +00:00
|
|
|
int radius = graphic->GetRadius();
|
2020-12-24 21:00:41 +00:00
|
|
|
int steps = GetArcToSegmentCount( radius, aErrorMax, 360.0 );
|
2018-03-08 18:03:56 +00:00
|
|
|
wxPoint nextPt;
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2018-03-08 18:03:56 +00:00
|
|
|
start.x += radius;
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2018-03-08 18:03:56 +00:00
|
|
|
for( int step = 0; step < steps; ++step )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2018-03-08 18:03:56 +00:00
|
|
|
double rotation = ( angle * step ) / steps;
|
2017-03-19 15:38:30 +00:00
|
|
|
nextPt = start;
|
|
|
|
RotatePoint( &nextPt.x, &nextPt.y, center.x, center.y, rotation );
|
|
|
|
aPolygons.Append( nextPt, -1, hole );
|
2020-11-21 20:42:27 +00:00
|
|
|
|
|
|
|
if( firstPt )
|
|
|
|
firstPt = false;
|
|
|
|
else
|
|
|
|
segOwners[ std::make_pair( prevPt, nextPt ) ] = graphic;
|
|
|
|
|
|
|
|
prevPt = nextPt;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-01 02:33:25 +00:00
|
|
|
else if( graphic->GetShape() == PCB_SHAPE_TYPE::RECT )
|
2020-06-15 19:50:20 +00:00
|
|
|
{
|
2020-09-07 00:40:18 +00:00
|
|
|
std::vector<wxPoint> pts = graphic->GetRectCorners();
|
2020-06-15 19:50:20 +00:00
|
|
|
|
|
|
|
for( const wxPoint& pt : pts )
|
2020-11-21 20:42:27 +00:00
|
|
|
{
|
2020-06-15 19:50:20 +00:00
|
|
|
aPolygons.Append( pt, -1, hole );
|
2020-11-21 20:42:27 +00:00
|
|
|
|
|
|
|
if( firstPt )
|
|
|
|
firstPt = false;
|
|
|
|
else
|
|
|
|
segOwners[ std::make_pair( prevPt, pt ) ] = graphic;
|
|
|
|
|
|
|
|
prevPt = (wxPoint) pt;
|
|
|
|
}
|
2020-06-15 19:50:20 +00:00
|
|
|
}
|
2017-03-19 15:38:30 +00:00
|
|
|
else
|
|
|
|
{
|
2020-12-25 14:01:34 +00:00
|
|
|
// Polygon start point. Arbitrarily chosen end of the segment and build the poly
|
|
|
|
// from here.
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
wxPoint startPt( graphic->GetEnd() );
|
|
|
|
prevPt = graphic->GetEnd();
|
|
|
|
aPolygons.Append( prevPt, -1, hole );
|
|
|
|
|
|
|
|
// do not append the other end point yet, this first 'graphic' might be an arc
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
switch( graphic->GetShape() )
|
|
|
|
{
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::SEGMENT:
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
|
|
|
wxPoint nextPt;
|
|
|
|
|
|
|
|
// Use the line segment end point furthest away from
|
|
|
|
// prevPt as we assume the other end to be ON prevPt or
|
|
|
|
// very close to it.
|
|
|
|
|
2020-12-27 01:00:41 +00:00
|
|
|
if( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd()) )
|
2017-03-19 15:38:30 +00:00
|
|
|
nextPt = graphic->GetEnd();
|
|
|
|
else
|
|
|
|
nextPt = graphic->GetStart();
|
|
|
|
|
2020-11-21 20:42:27 +00:00
|
|
|
aPolygons.Append( nextPt, -1, hole );
|
|
|
|
segOwners[ std::make_pair( prevPt, nextPt ) ] = graphic;
|
2017-03-19 15:38:30 +00:00
|
|
|
prevPt = nextPt;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::ARC:
|
2020-10-24 08:04:15 +00:00
|
|
|
// We do not support arcs in polygons, so approximate an arc with a series of
|
|
|
|
// short lines and put those line segments into the !same! PATH.
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2018-03-08 18:03:56 +00:00
|
|
|
wxPoint pstart = graphic->GetArcStart();
|
|
|
|
wxPoint pend = graphic->GetArcEnd();
|
|
|
|
wxPoint pcenter = graphic->GetCenter();
|
|
|
|
double angle = -graphic->GetAngle();
|
2018-03-22 18:02:45 +00:00
|
|
|
int radius = graphic->GetRadius();
|
2020-12-24 21:00:41 +00:00
|
|
|
int steps = GetArcToSegmentCount( radius, aErrorMax, angle / 10.0 );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-24 21:00:41 +00:00
|
|
|
if( !close_enough( prevPt, pstart, aChainingEpsilon ) )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-12-24 21:00:41 +00:00
|
|
|
wxASSERT( close_enough( prevPt, graphic->GetArcEnd(),
|
|
|
|
aChainingEpsilon ) );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
angle = -angle;
|
|
|
|
std::swap( pstart, pend );
|
|
|
|
}
|
|
|
|
|
2020-10-24 08:04:15 +00:00
|
|
|
// Create intermediate points between start and end:
|
|
|
|
for( int step = 1; step < steps; ++step )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-10-23 09:23:45 +00:00
|
|
|
double rotation = ( angle * step ) / steps;
|
2020-10-22 20:27:46 +00:00
|
|
|
wxPoint pt = pstart;
|
|
|
|
RotatePoint( &pt, pcenter, rotation );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
aPolygons.Append( pt, -1, hole );
|
2020-11-21 20:42:27 +00:00
|
|
|
segOwners[ std::make_pair( prevPt, pt ) ] = graphic;
|
|
|
|
prevPt = pt;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
|
2020-10-24 08:04:15 +00:00
|
|
|
// Append the last arc end point
|
|
|
|
aPolygons.Append( pend, -1, hole );
|
2020-11-21 20:42:27 +00:00
|
|
|
segOwners[ std::make_pair( prevPt, pend ) ] = graphic;
|
2020-10-22 20:27:46 +00:00
|
|
|
prevPt = pend;
|
2018-07-09 17:15:36 +00:00
|
|
|
}
|
2018-07-24 07:17:20 +00:00
|
|
|
break;
|
2018-07-09 17:15:36 +00:00
|
|
|
|
2021-05-01 02:33:25 +00:00
|
|
|
case PCB_SHAPE_TYPE::CURVE:
|
2020-12-25 14:01:34 +00:00
|
|
|
// We do not support Bezier curves in polygons, so approximate with a series
|
|
|
|
// of short lines and put those line segments into the !same! PATH.
|
2018-07-09 17:15:36 +00:00
|
|
|
{
|
2020-11-21 20:42:27 +00:00
|
|
|
wxPoint nextPt;
|
|
|
|
bool reverse = false;
|
2018-07-09 17:15:36 +00:00
|
|
|
|
|
|
|
// Use the end point furthest away from
|
|
|
|
// prevPt as we assume the other end to be ON prevPt or
|
|
|
|
// very close to it.
|
|
|
|
|
2020-12-27 01:00:41 +00:00
|
|
|
if( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd()) )
|
2020-11-21 20:42:27 +00:00
|
|
|
{
|
2018-07-09 17:15:36 +00:00
|
|
|
nextPt = graphic->GetEnd();
|
2020-11-21 20:42:27 +00:00
|
|
|
}
|
2018-07-09 17:15:36 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
nextPt = graphic->GetStart();
|
|
|
|
reverse = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( reverse )
|
|
|
|
{
|
2018-10-11 14:31:35 +00:00
|
|
|
for( int jj = graphic->GetBezierPoints().size()-1; jj >= 0; jj-- )
|
2020-11-21 20:42:27 +00:00
|
|
|
{
|
|
|
|
const wxPoint& pt = graphic->GetBezierPoints()[jj];
|
|
|
|
aPolygons.Append( pt, -1, hole );
|
|
|
|
segOwners[ std::make_pair( prevPt, pt ) ] = graphic;
|
|
|
|
prevPt = pt;
|
|
|
|
}
|
2018-07-09 17:15:36 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-11-21 20:42:27 +00:00
|
|
|
for( const wxPoint& pt : graphic->GetBezierPoints() )
|
|
|
|
{
|
2020-08-15 15:42:18 +00:00
|
|
|
aPolygons.Append( pt, -1, hole );
|
2020-11-21 20:42:27 +00:00
|
|
|
segOwners[ std::make_pair( prevPt, pt ) ] = graphic;
|
|
|
|
prevPt = pt;
|
|
|
|
}
|
2018-07-09 17:15:36 +00:00
|
|
|
}
|
|
|
|
|
2017-03-19 15:38:30 +00:00
|
|
|
prevPt = nextPt;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2020-10-22 20:27:46 +00:00
|
|
|
wxFAIL_MSG( "Unsupported PCB_SHAPE type "
|
|
|
|
+ BOARD_ITEM::ShowShape( graphic->GetShape() ) );
|
2018-11-28 12:11:43 +00:00
|
|
|
|
2017-03-20 12:05:38 +00:00
|
|
|
return false;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get next closest segment.
|
|
|
|
|
2020-12-24 21:00:41 +00:00
|
|
|
PCB_SHAPE* nextGraphic = findNext( graphic, prevPt, aSegList, aChainingEpsilon );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-11-21 20:42:27 +00:00
|
|
|
if( nextGraphic && !( nextGraphic->GetFlags() & SKIP_STRUCT ) )
|
2020-10-22 20:27:46 +00:00
|
|
|
{
|
2020-11-21 20:42:27 +00:00
|
|
|
graphic = nextGraphic;
|
2020-10-22 20:27:46 +00:00
|
|
|
graphic->SetFlags( SKIP_STRUCT );
|
|
|
|
startCandidates.erase( graphic );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finished, or ran into trouble...
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2020-12-24 21:00:41 +00:00
|
|
|
if( close_enough( startPt, prevPt, aChainingEpsilon ) )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2020-10-22 20:27:46 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-11-21 20:42:27 +00:00
|
|
|
else if( nextGraphic ) // encountered already-used segment, but not at the start
|
2020-10-22 20:27:46 +00:00
|
|
|
{
|
2020-12-26 23:00:41 +00:00
|
|
|
if( aErrorHandler )
|
|
|
|
(*aErrorHandler)( _( "(self-intersecting)" ), graphic, nextGraphic, prevPt );
|
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
polygonComplete = false;
|
|
|
|
break;
|
|
|
|
}
|
2020-11-21 20:42:27 +00:00
|
|
|
else // encountered discontinuity
|
2020-10-22 20:27:46 +00:00
|
|
|
{
|
2020-11-21 20:42:27 +00:00
|
|
|
if( aErrorHandler )
|
|
|
|
(*aErrorHandler)( _( "(not a closed shape)" ), graphic, nullptr, prevPt );
|
2018-11-28 12:11:43 +00:00
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
polygonComplete = false;
|
2017-03-19 15:38:30 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
if( !polygonComplete )
|
|
|
|
return false;
|
|
|
|
|
2020-12-25 14:01:34 +00:00
|
|
|
// All of the silliness that follows is to work around the segment iterator while checking
|
|
|
|
// for collisions.
|
2019-03-01 13:36:53 +00:00
|
|
|
// TODO: Implement proper segment and point iterators that follow std
|
2020-10-24 08:04:15 +00:00
|
|
|
|
2019-03-01 13:36:53 +00:00
|
|
|
for( auto seg1 = aPolygons.IterateSegmentsWithHoles(); seg1; seg1++ )
|
|
|
|
{
|
|
|
|
auto seg2 = seg1;
|
|
|
|
|
|
|
|
for( ++seg2; seg2; seg2++ )
|
|
|
|
{
|
2020-11-21 20:42:27 +00:00
|
|
|
// Check for exact overlapping segments.
|
|
|
|
if( *seg1 == *seg2 || ( ( *seg1 ).A == ( *seg2 ).B && ( *seg1 ).B == ( *seg2 ).A ) )
|
2019-03-01 13:36:53 +00:00
|
|
|
{
|
2020-11-21 20:42:27 +00:00
|
|
|
if( aErrorHandler )
|
|
|
|
{
|
|
|
|
BOARD_ITEM* a = fetchOwner( *seg1 );
|
|
|
|
BOARD_ITEM* b = fetchOwner( *seg2 );
|
2021-04-12 23:59:51 +00:00
|
|
|
|
|
|
|
if( a && b )
|
|
|
|
(*aErrorHandler)( _( "(self-intersecting)" ), a, b, (wxPoint) ( *seg1 ).A );
|
2020-11-21 20:42:27 +00:00
|
|
|
}
|
2019-03-01 13:36:53 +00:00
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
selfIntersecting = true;
|
2019-03-01 13:36:53 +00:00
|
|
|
}
|
|
|
|
|
2020-08-15 15:42:18 +00:00
|
|
|
if( boost::optional<VECTOR2I> pt = seg1.Get().Intersect( seg2.Get(), true ) )
|
2019-03-01 13:36:53 +00:00
|
|
|
{
|
2020-11-21 20:42:27 +00:00
|
|
|
if( aErrorHandler )
|
|
|
|
{
|
|
|
|
BOARD_ITEM* a = fetchOwner( *seg1 );
|
|
|
|
BOARD_ITEM* b = fetchOwner( *seg2 );
|
2021-04-12 23:59:51 +00:00
|
|
|
|
|
|
|
if( a && b )
|
|
|
|
(*aErrorHandler)( _( "(self-intersecting)" ), a, b, (wxPoint) pt.get() );
|
2020-11-21 20:42:27 +00:00
|
|
|
}
|
2019-03-01 13:36:53 +00:00
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
selfIntersecting = true;
|
2019-03-01 13:36:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-22 20:27:46 +00:00
|
|
|
return !selfIntersecting;
|
2017-03-20 12:05:38 +00:00
|
|
|
}
|
|
|
|
|
2020-11-21 20:42:27 +00:00
|
|
|
|
2020-11-12 20:19:22 +00:00
|
|
|
#include <board.h>
|
2017-03-20 12:05:38 +00:00
|
|
|
#include <collectors.h>
|
|
|
|
|
|
|
|
/* This function is used to extract a board outlines (3D view, automatic zones build ...)
|
|
|
|
* Any closed outline inside the main outline is a hole
|
|
|
|
* All contours should be closed, i.e. valid closed polygon vertices
|
|
|
|
*/
|
2020-12-24 21:00:41 +00:00
|
|
|
bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aErrorMax,
|
|
|
|
int aChainingEpsilon, OUTLINE_ERROR_HANDLER* aErrorHandler )
|
2017-03-20 12:05:38 +00:00
|
|
|
{
|
|
|
|
PCB_TYPE_COLLECTOR items;
|
2020-05-18 20:09:36 +00:00
|
|
|
bool success = false;
|
2017-03-20 12:05:38 +00:00
|
|
|
|
2020-11-13 02:57:11 +00:00
|
|
|
// Get all the PCB and FP shapes into 'items', then keep only those on layer == Edge_Cuts.
|
2020-10-04 14:19:33 +00:00
|
|
|
static const KICAD_T scan_graphics[] = { PCB_SHAPE_T, PCB_FP_SHAPE_T, EOT };
|
2017-03-20 12:05:38 +00:00
|
|
|
items.Collect( aBoard, scan_graphics );
|
|
|
|
|
|
|
|
// Make a working copy of aSegList, because the list is modified during calculations
|
2020-10-04 23:34:59 +00:00
|
|
|
std::vector<PCB_SHAPE*> segList;
|
2017-03-20 12:05:38 +00:00
|
|
|
|
|
|
|
for( int ii = 0; ii < items.GetCount(); ii++ )
|
|
|
|
{
|
|
|
|
if( items[ii]->GetLayer() == Edge_Cuts )
|
2020-10-04 23:34:59 +00:00
|
|
|
segList.push_back( static_cast<PCB_SHAPE*>( items[ii] ) );
|
2017-03-20 12:05:38 +00:00
|
|
|
}
|
|
|
|
|
2020-05-18 20:09:36 +00:00
|
|
|
if( segList.size() )
|
|
|
|
{
|
2020-12-24 21:00:41 +00:00
|
|
|
success = ConvertOutlineToPolygon( segList, aOutlines, aErrorMax, aChainingEpsilon,
|
|
|
|
aErrorHandler );
|
2020-08-17 20:07:27 +00:00
|
|
|
}
|
2017-03-20 12:05:38 +00:00
|
|
|
|
2017-03-22 10:16:45 +00:00
|
|
|
if( !success || !aOutlines.OutlineCount() )
|
2017-03-20 12:05:38 +00:00
|
|
|
{
|
2020-05-18 20:09:36 +00:00
|
|
|
// Couldn't create a valid polygon outline. Use the board edge cuts bounding box to
|
|
|
|
// create a rectangular outline, or, failing that, the bounding box of the items on
|
|
|
|
// the board.
|
2017-03-20 12:05:38 +00:00
|
|
|
|
|
|
|
EDA_RECT bbbox = aBoard->GetBoardEdgesBoundingBox();
|
|
|
|
|
2017-03-22 07:40:48 +00:00
|
|
|
// If null area, uses the global bounding box.
|
|
|
|
if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
|
|
|
|
bbbox = aBoard->ComputeBoundingBox();
|
|
|
|
|
2017-03-20 12:05:38 +00:00
|
|
|
// Ensure non null area. If happen, gives a minimal size.
|
|
|
|
if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
|
|
|
|
bbbox.Inflate( Millimeter2iu( 1.0 ) );
|
|
|
|
|
|
|
|
aOutlines.RemoveAllContours();
|
|
|
|
aOutlines.NewOutline();
|
|
|
|
|
|
|
|
wxPoint corner;
|
2017-03-22 07:40:48 +00:00
|
|
|
aOutlines.Append( bbbox.GetOrigin() );
|
2017-03-20 12:05:38 +00:00
|
|
|
|
|
|
|
corner.x = bbbox.GetOrigin().x;
|
|
|
|
corner.y = bbbox.GetEnd().y;
|
2017-03-22 07:40:48 +00:00
|
|
|
aOutlines.Append( corner );
|
2017-03-20 12:05:38 +00:00
|
|
|
|
2017-03-22 07:40:48 +00:00
|
|
|
aOutlines.Append( bbbox.GetEnd() );
|
2017-03-20 12:05:38 +00:00
|
|
|
|
|
|
|
corner.x = bbbox.GetEnd().x;
|
|
|
|
corner.y = bbbox.GetOrigin().y;
|
2017-03-22 07:40:48 +00:00
|
|
|
aOutlines.Append( corner );
|
2017-03-20 12:05:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
2020-02-27 17:51:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
2020-09-30 22:12:13 +00:00
|
|
|
* Get the complete bounding box of the board (including all items).
|
|
|
|
*
|
|
|
|
* The vertex numbers and segment numbers of the rectangle returned.
|
|
|
|
* 1
|
|
|
|
* *---------------*
|
|
|
|
* |1 2|
|
|
|
|
* 0| |2
|
|
|
|
* |0 3|
|
|
|
|
* *---------------*
|
|
|
|
* 3
|
2020-02-27 17:51:11 +00:00
|
|
|
*/
|
|
|
|
void buildBoardBoundingBoxPoly( const BOARD* aBoard, SHAPE_POLY_SET& aOutline )
|
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
EDA_RECT bbbox = aBoard->GetBoundingBox();
|
|
|
|
SHAPE_LINE_CHAIN chain;
|
2020-02-27 17:51:11 +00:00
|
|
|
|
|
|
|
// If null area, uses the global bounding box.
|
|
|
|
if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
|
|
|
|
bbbox = aBoard->ComputeBoundingBox();
|
|
|
|
|
|
|
|
// Ensure non null area. If happen, gives a minimal size.
|
|
|
|
if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
|
|
|
|
bbbox.Inflate( Millimeter2iu( 1.0 ) );
|
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
// Inflate slightly (by 1/10th the size of the box)
|
|
|
|
bbbox.Inflate( bbbox.GetWidth() / 10, bbbox.GetHeight() / 10 );
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
chain.Append( bbbox.GetOrigin() );
|
|
|
|
chain.Append( bbbox.GetOrigin().x, bbbox.GetEnd().y );
|
|
|
|
chain.Append( bbbox.GetEnd() );
|
|
|
|
chain.Append( bbbox.GetEnd().x, bbbox.GetOrigin().y );
|
|
|
|
chain.SetClosed( true );
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
aOutline.RemoveAllContours();
|
|
|
|
aOutline.AddOutline( chain );
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
bool isCopperOutside( const FOOTPRINT* aMod, SHAPE_POLY_SET& aShape )
|
2020-02-27 17:51:11 +00:00
|
|
|
{
|
|
|
|
bool padOutside = false;
|
|
|
|
|
2020-11-12 22:30:02 +00:00
|
|
|
for( PAD* pad : aMod->Pads() )
|
2020-02-27 17:51:11 +00:00
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
SHAPE_POLY_SET poly = aShape;
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
poly.BooleanIntersection( *pad->GetEffectivePolygon(), SHAPE_POLY_SET::PM_FAST );
|
|
|
|
|
|
|
|
if( poly.OutlineCount() == 0 )
|
2020-02-27 17:51:11 +00:00
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
wxPoint padPos = pad->GetPosition();
|
|
|
|
wxLogTrace( traceBoardOutline, "Tested pad (%d, %d): outside", padPos.x, padPos.y );
|
|
|
|
padOutside = true;
|
|
|
|
break;
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wxPoint padPos = pad->GetPosition();
|
2020-09-30 22:12:13 +00:00
|
|
|
wxLogTrace( traceBoardOutline, "Tested pad (%d, %d): not outside", padPos.x, padPos.y );
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return padOutside;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
VECTOR2I projectPointOnSegment( const VECTOR2I& aEndPoint, const SHAPE_POLY_SET& aOutline,
|
|
|
|
int aOutlineNum = 0 )
|
|
|
|
{
|
|
|
|
int minDistance = -1;
|
|
|
|
VECTOR2I projPoint;
|
|
|
|
|
|
|
|
for( auto it = aOutline.CIterateSegments( aOutlineNum ); it; it++ )
|
|
|
|
{
|
|
|
|
auto seg = it.Get();
|
|
|
|
int dis = seg.Distance( aEndPoint );
|
|
|
|
|
|
|
|
if( minDistance < 0 || ( dis < minDistance ) )
|
|
|
|
{
|
|
|
|
minDistance = dis;
|
|
|
|
projPoint = seg.NearestPoint( aEndPoint );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return projPoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
int findEndSegments( SHAPE_LINE_CHAIN& aChain, SEG& aStartSeg, SEG& aEndSeg )
|
|
|
|
{
|
|
|
|
int foundSegs = 0;
|
|
|
|
|
|
|
|
for( int i = 0; i < aChain.SegmentCount(); i++ )
|
|
|
|
{
|
|
|
|
SEG seg = aChain.Segment( i );
|
|
|
|
|
|
|
|
bool foundA = false;
|
|
|
|
bool foundB = false;
|
|
|
|
|
|
|
|
for( int j = 0; j < aChain.SegmentCount(); j++ )
|
|
|
|
{
|
|
|
|
// Don't test the segment against itself
|
|
|
|
if( i == j )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
SEG testSeg = aChain.Segment( j );
|
|
|
|
|
|
|
|
if( testSeg.Contains( seg.A ) )
|
|
|
|
foundA = true;
|
|
|
|
|
|
|
|
if( testSeg.Contains( seg.B ) )
|
|
|
|
foundB = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This segment isn't a start or end
|
|
|
|
if( foundA && foundB )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if( foundSegs == 0 )
|
|
|
|
{
|
|
|
|
// The first segment we encounter is the "start" segment
|
|
|
|
wxLogTrace( traceBoardOutline, "Found start segment: (%d, %d)-(%d, %d)",
|
|
|
|
seg.A.x, seg.A.y, seg.B.x, seg.B.y );
|
|
|
|
aStartSeg = seg;
|
|
|
|
foundSegs++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Once we find both start and end, we can stop
|
|
|
|
wxLogTrace( traceBoardOutline, "Found end segment: (%d, %d)-(%d, %d)",
|
|
|
|
seg.A.x, seg.A.y, seg.B.x, seg.B.y );
|
|
|
|
aEndSeg = seg;
|
|
|
|
foundSegs++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return foundSegs;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-27 17:51:11 +00:00
|
|
|
/**
|
|
|
|
* This function is used to extract a board outline for a footprint view.
|
|
|
|
*
|
|
|
|
* Notes:
|
2020-12-25 14:01:34 +00:00
|
|
|
* * Incomplete outlines will be closed by joining the end of the outline onto the bounding box
|
|
|
|
* (by simply projecting the end points) and then take the area that contains the copper.
|
|
|
|
* * If all copper lies inside a closed outline, than that outline will be treated as an external
|
|
|
|
* board outline.
|
|
|
|
* * If copper is located outside a closed outline, then that outline will be treated as a hole,
|
|
|
|
* and the outer edge will be formed using the bounding box.
|
2020-02-27 17:51:11 +00:00
|
|
|
*/
|
2020-12-24 21:00:41 +00:00
|
|
|
bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aErrorMax,
|
|
|
|
int aChainingEpsilon, OUTLINE_ERROR_HANDLER* aErrorHandler )
|
2020-11-21 20:42:27 +00:00
|
|
|
|
2020-02-27 17:51:11 +00:00
|
|
|
{
|
2021-02-10 12:35:09 +00:00
|
|
|
FOOTPRINT* footprint = aBoard->GetFirstFootprint();
|
|
|
|
|
|
|
|
// No footprint loaded
|
|
|
|
if( !footprint )
|
|
|
|
{
|
|
|
|
wxLogTrace( traceBoardOutline, "No footprint found on board" );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-02-27 17:51:11 +00:00
|
|
|
PCB_TYPE_COLLECTOR items;
|
2020-11-08 21:29:04 +00:00
|
|
|
SHAPE_POLY_SET outlines;
|
2021-02-10 12:35:09 +00:00
|
|
|
bool success = false;
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-11-08 21:29:04 +00:00
|
|
|
// Get all the SHAPEs into 'items', then keep only those on layer == Edge_Cuts.
|
|
|
|
static const KICAD_T scan_graphics[] = { PCB_SHAPE_T, PCB_FP_SHAPE_T, EOT };
|
2020-02-27 17:51:11 +00:00
|
|
|
items.Collect( aBoard, scan_graphics );
|
|
|
|
|
|
|
|
// Make a working copy of aSegList, because the list is modified during calculations
|
2020-10-04 23:34:59 +00:00
|
|
|
std::vector<PCB_SHAPE*> segList;
|
2020-02-27 17:51:11 +00:00
|
|
|
|
|
|
|
for( int ii = 0; ii < items.GetCount(); ii++ )
|
|
|
|
{
|
|
|
|
if( items[ii]->GetLayer() == Edge_Cuts )
|
2020-10-04 23:34:59 +00:00
|
|
|
segList.push_back( static_cast<PCB_SHAPE*>( items[ii] ) );
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
|
2021-02-10 12:35:09 +00:00
|
|
|
if( !segList.empty() )
|
2020-09-30 22:12:13 +00:00
|
|
|
{
|
2021-02-10 12:35:09 +00:00
|
|
|
success = ConvertOutlineToPolygon( segList, outlines, aErrorMax, aChainingEpsilon,
|
|
|
|
aErrorHandler );
|
2020-09-30 22:12:13 +00:00
|
|
|
}
|
|
|
|
|
2021-02-10 07:45:17 +00:00
|
|
|
// A closed outline was found on Edge_Cuts
|
2021-02-10 12:35:09 +00:00
|
|
|
if( success )
|
2020-02-27 17:51:11 +00:00
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
wxLogTrace( traceBoardOutline, "Closed outline found" );
|
|
|
|
|
2020-02-27 17:51:11 +00:00
|
|
|
// If copper is outside a closed polygon, treat it as a hole
|
2020-11-08 21:29:04 +00:00
|
|
|
if( isCopperOutside( footprint, outlines ) )
|
2020-02-27 17:51:11 +00:00
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
wxLogTrace( traceBoardOutline, "Treating outline as a hole" );
|
|
|
|
|
2020-02-27 17:51:11 +00:00
|
|
|
buildBoardBoundingBoxPoly( aBoard, aOutlines );
|
|
|
|
|
|
|
|
// Copy all outlines from the conversion as holes into the new outline
|
|
|
|
for( int i = 0; i < outlines.OutlineCount(); i++ )
|
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
SHAPE_LINE_CHAIN& out = outlines.Outline( i );
|
|
|
|
|
|
|
|
if( out.IsClosed() )
|
|
|
|
aOutlines.AddHole( out, -1 );
|
2020-02-27 17:51:11 +00:00
|
|
|
|
|
|
|
for( int j = 0; j < outlines.HoleCount( i ); j++ )
|
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
SHAPE_LINE_CHAIN& hole = outlines.Hole( i, j );
|
|
|
|
|
|
|
|
if( hole.IsClosed() )
|
|
|
|
aOutlines.AddHole( hole, -1 );
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If all copper is inside, then the computed outline is the board outline
|
|
|
|
else
|
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
wxLogTrace( traceBoardOutline, "Treating outline as board edge" );
|
2020-02-27 17:51:11 +00:00
|
|
|
aOutlines = outlines;
|
|
|
|
}
|
2020-09-30 22:12:13 +00:00
|
|
|
|
|
|
|
return true;
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
// No board outlines were found, so use the bounding box
|
2020-09-30 22:12:13 +00:00
|
|
|
else if( outlines.OutlineCount() == 0 )
|
2020-02-27 17:51:11 +00:00
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
wxLogTrace( traceBoardOutline, "Using footprint bounding box" );
|
2020-02-27 17:51:11 +00:00
|
|
|
buildBoardBoundingBoxPoly( aBoard, aOutlines );
|
2020-09-30 22:12:13 +00:00
|
|
|
|
|
|
|
return true;
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
// There is an outline present, but it is not closed
|
|
|
|
else
|
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
wxLogTrace( traceBoardOutline, "Trying to build outline" );
|
|
|
|
|
|
|
|
std::vector<SHAPE_LINE_CHAIN> closedChains;
|
|
|
|
std::vector<SHAPE_LINE_CHAIN> openChains;
|
|
|
|
|
2020-12-25 14:01:34 +00:00
|
|
|
// The ConvertOutlineToPolygon function returns only one main outline and the rest as
|
|
|
|
// holes, so we promote the holes and process them
|
2020-09-30 22:12:13 +00:00
|
|
|
openChains.push_back( outlines.Outline( 0 ) );
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
for( int j = 0; j < outlines.HoleCount( 0 ); j++ )
|
2020-02-27 17:51:11 +00:00
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
SHAPE_LINE_CHAIN hole = outlines.Hole( 0, j );
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
if( hole.IsClosed() )
|
2020-02-27 17:51:11 +00:00
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
wxLogTrace( traceBoardOutline, "Found closed hole" );
|
|
|
|
closedChains.push_back( hole );
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
wxLogTrace( traceBoardOutline, "Found open hole" );
|
|
|
|
openChains.push_back( hole );
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
2020-09-30 22:12:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SHAPE_POLY_SET bbox;
|
|
|
|
buildBoardBoundingBoxPoly( aBoard, bbox );
|
|
|
|
|
|
|
|
// Treat the open polys as the board edge
|
|
|
|
SHAPE_LINE_CHAIN chain = openChains[0];
|
|
|
|
SHAPE_LINE_CHAIN rect = bbox.Outline( 0 );
|
|
|
|
|
|
|
|
// We know the outline chain is open, so set to non-closed to get better segment count
|
|
|
|
chain.SetClosed( false );
|
|
|
|
|
|
|
|
SEG startSeg;
|
|
|
|
SEG endSeg;
|
|
|
|
|
|
|
|
// The two possible board outlines
|
|
|
|
SHAPE_LINE_CHAIN upper;
|
|
|
|
SHAPE_LINE_CHAIN lower;
|
|
|
|
|
|
|
|
findEndSegments( chain, startSeg, endSeg );
|
|
|
|
|
|
|
|
if( chain.SegmentCount() == 0 )
|
|
|
|
{
|
2020-11-13 02:57:11 +00:00
|
|
|
// Something is wrong, bail out with the overall footprint bounding box
|
2020-09-30 22:12:13 +00:00
|
|
|
wxLogTrace( traceBoardOutline, "No line segments in provided outline" );
|
|
|
|
aOutlines = bbox;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if( chain.SegmentCount() == 1 )
|
|
|
|
{
|
2020-12-25 14:01:34 +00:00
|
|
|
// This case means there is only 1 line segment making up the edge cuts of the
|
|
|
|
// footprint, so we just need to use it to cut the bounding box in half.
|
2020-09-30 22:12:13 +00:00
|
|
|
wxLogTrace( traceBoardOutline, "Only 1 line segment in provided outline" );
|
|
|
|
|
|
|
|
startSeg = chain.Segment( 0 );
|
|
|
|
|
|
|
|
// Intersect with all the sides of the rectangle
|
|
|
|
OPT_VECTOR2I inter0 = startSeg.IntersectLines( rect.Segment( 0 ) );
|
|
|
|
OPT_VECTOR2I inter1 = startSeg.IntersectLines( rect.Segment( 1 ) );
|
|
|
|
OPT_VECTOR2I inter2 = startSeg.IntersectLines( rect.Segment( 2 ) );
|
|
|
|
OPT_VECTOR2I inter3 = startSeg.IntersectLines( rect.Segment( 3 ) );
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
if( inter0 && inter2 && !inter1 && !inter3 )
|
2020-02-27 17:51:11 +00:00
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
// Intersects the vertical rectangle sides only
|
|
|
|
wxLogTrace( traceBoardOutline, "Segment intersects only vertical bbox sides" );
|
|
|
|
|
|
|
|
// The upper half
|
|
|
|
upper.Append( *inter0 );
|
|
|
|
upper.Append( rect.GetPoint( 1 ) );
|
|
|
|
upper.Append( rect.GetPoint( 2 ) );
|
|
|
|
upper.Append( *inter2 );
|
|
|
|
upper.SetClosed( true );
|
|
|
|
|
|
|
|
// The lower half
|
|
|
|
lower.Append( *inter0 );
|
|
|
|
lower.Append( rect.GetPoint( 0 ) );
|
|
|
|
lower.Append( rect.GetPoint( 3 ) );
|
|
|
|
lower.Append( *inter2 );
|
|
|
|
lower.SetClosed( true );
|
|
|
|
}
|
|
|
|
else if( inter1 && inter3 && !inter0 && !inter2 )
|
|
|
|
{
|
|
|
|
// Intersects the horizontal rectangle sides only
|
|
|
|
wxLogTrace( traceBoardOutline, "Segment intersects only horizontal bbox sides" );
|
|
|
|
|
|
|
|
// The left half
|
|
|
|
upper.Append( *inter1 );
|
|
|
|
upper.Append( rect.GetPoint( 1 ) );
|
|
|
|
upper.Append( rect.GetPoint( 0 ) );
|
|
|
|
upper.Append( *inter3 );
|
|
|
|
upper.SetClosed( true );
|
|
|
|
|
|
|
|
// The right half
|
|
|
|
lower.Append( *inter1 );
|
|
|
|
lower.Append( rect.GetPoint( 2 ) );
|
|
|
|
lower.Append( rect.GetPoint( 3 ) );
|
|
|
|
lower.Append( *inter3 );
|
|
|
|
lower.SetClosed( true );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Angled line segment that cuts across a corner
|
|
|
|
wxLogTrace( traceBoardOutline, "Segment intersects two perpendicular bbox sides" );
|
|
|
|
|
2020-12-25 14:01:34 +00:00
|
|
|
// Figure out which actual lines are intersected, since IntersectLines assumes
|
|
|
|
// an infinite line
|
2020-09-30 22:12:13 +00:00
|
|
|
bool hit0 = rect.Segment( 0 ).Contains( *inter0 );
|
|
|
|
bool hit1 = rect.Segment( 1 ).Contains( *inter1 );
|
|
|
|
bool hit2 = rect.Segment( 2 ).Contains( *inter2 );
|
|
|
|
bool hit3 = rect.Segment( 3 ).Contains( *inter3 );
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
if( hit0 && hit1 )
|
2020-02-27 17:51:11 +00:00
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
// Cut across the upper left corner
|
|
|
|
wxLogTrace( traceBoardOutline, "Segment cuts upper left corner" );
|
|
|
|
|
|
|
|
// The upper half
|
|
|
|
upper.Append( *inter0 );
|
|
|
|
upper.Append( rect.GetPoint( 1 ) );
|
|
|
|
upper.Append( *inter1 );
|
|
|
|
upper.SetClosed( true );
|
|
|
|
|
|
|
|
// The lower half
|
|
|
|
lower.Append( *inter0 );
|
|
|
|
lower.Append( rect.GetPoint( 0 ) );
|
|
|
|
lower.Append( rect.GetPoint( 3 ) );
|
|
|
|
lower.Append( rect.GetPoint( 2 ) );
|
|
|
|
lower.Append( *inter1 );
|
|
|
|
lower.SetClosed( true );
|
|
|
|
}
|
|
|
|
else if( hit1 && hit2 )
|
|
|
|
{
|
|
|
|
// Cut across the upper right corner
|
|
|
|
wxLogTrace( traceBoardOutline, "Segment cuts upper right corner" );
|
|
|
|
|
|
|
|
// The upper half
|
|
|
|
upper.Append( *inter1 );
|
|
|
|
upper.Append( rect.GetPoint( 2 ) );
|
|
|
|
upper.Append( *inter2 );
|
|
|
|
upper.SetClosed( true );
|
|
|
|
|
|
|
|
// The lower half
|
|
|
|
lower.Append( *inter1 );
|
|
|
|
lower.Append( rect.GetPoint( 1 ) );
|
|
|
|
lower.Append( rect.GetPoint( 0 ) );
|
|
|
|
lower.Append( rect.GetPoint( 3 ) );
|
|
|
|
lower.Append( *inter2 );
|
|
|
|
lower.SetClosed( true );
|
|
|
|
}
|
|
|
|
else if( hit2 && hit3 )
|
|
|
|
{
|
|
|
|
// Cut across the lower right corner
|
|
|
|
wxLogTrace( traceBoardOutline, "Segment cuts lower right corner" );
|
|
|
|
|
|
|
|
// The upper half
|
|
|
|
upper.Append( *inter2 );
|
|
|
|
upper.Append( rect.GetPoint( 2 ) );
|
|
|
|
upper.Append( rect.GetPoint( 1 ) );
|
|
|
|
upper.Append( rect.GetPoint( 0 ) );
|
|
|
|
upper.Append( *inter3 );
|
|
|
|
upper.SetClosed( true );
|
|
|
|
|
|
|
|
// The bottom half
|
|
|
|
lower.Append( *inter2 );
|
|
|
|
lower.Append( rect.GetPoint( 3 ) );
|
|
|
|
lower.Append( *inter3 );
|
|
|
|
lower.SetClosed( true );
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-09-30 22:12:13 +00:00
|
|
|
// Cut across the lower left corner
|
|
|
|
wxLogTrace( traceBoardOutline, "Segment cuts upper left corner" );
|
|
|
|
|
|
|
|
// The upper half
|
|
|
|
upper.Append( *inter0 );
|
|
|
|
upper.Append( rect.GetPoint( 1 ) );
|
|
|
|
upper.Append( rect.GetPoint( 2 ) );
|
|
|
|
upper.Append( rect.GetPoint( 3 ) );
|
|
|
|
upper.Append( *inter3 );
|
|
|
|
upper.SetClosed( true );
|
|
|
|
|
|
|
|
// The bottom half
|
|
|
|
lower.Append( *inter0 );
|
|
|
|
lower.Append( rect.GetPoint( 0 ) );
|
|
|
|
lower.Append( *inter3 );
|
|
|
|
lower.SetClosed( true );
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-30 22:12:13 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// More than 1 segment
|
|
|
|
wxLogTrace( traceBoardOutline, "Multiple segments in outline" );
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
// Just a temporary thing
|
|
|
|
aOutlines = bbox;
|
|
|
|
return true;
|
|
|
|
}
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
// Figure out which is the correct outline
|
|
|
|
SHAPE_POLY_SET poly1;
|
|
|
|
SHAPE_POLY_SET poly2;
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
poly1.NewOutline();
|
|
|
|
poly1.Append( upper );
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
poly2.NewOutline();
|
|
|
|
poly2.Append( lower );
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-11-08 21:29:04 +00:00
|
|
|
if( isCopperOutside( footprint, poly1 ) )
|
2020-09-30 22:12:13 +00:00
|
|
|
{
|
|
|
|
wxLogTrace( traceBoardOutline, "Using lower shape" );
|
|
|
|
aOutlines = poly2;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
wxLogTrace( traceBoardOutline, "Using upper shape" );
|
|
|
|
aOutlines = poly1;
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
// Add all closed polys as holes to the main outline
|
|
|
|
for( SHAPE_LINE_CHAIN& closedChain : closedChains )
|
|
|
|
{
|
|
|
|
wxLogTrace( traceBoardOutline, "Adding hole to main outline" );
|
|
|
|
aOutlines.AddHole( closedChain, -1 );
|
|
|
|
}
|
2020-02-27 17:51:11 +00:00
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
return true;
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|
|
|
|
|
2020-09-30 22:12:13 +00:00
|
|
|
// We really shouldn't reach this point
|
|
|
|
return false;
|
2020-02-27 17:51:11 +00:00
|
|
|
}
|