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>
|
2019-03-01 13:36:53 +00:00
|
|
|
* Copyright (C) 1992-2019 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
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @file convert_drawsegment_list_to_polygon.cpp
|
|
|
|
* @brief functions to convert a shape built with DRAWSEGMENTS to a polygon.
|
|
|
|
* expecting the shape describes shape similar to a polygon
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <trigo.h>
|
|
|
|
#include <macros.h>
|
|
|
|
|
2019-03-01 13:36:53 +00:00
|
|
|
#include <math/vector2d.h>
|
2017-03-19 15:38:30 +00:00
|
|
|
#include <class_drawsegment.h>
|
2018-12-02 15:33:12 +00:00
|
|
|
#include <class_module.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>
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function close_ness
|
|
|
|
* is a non-exact distance (also called Manhattan distance) used to approximate
|
|
|
|
* the distance between two points.
|
|
|
|
* The distance is very in-exact, but can be helpful when used
|
|
|
|
* to pick between alternative neighboring points.
|
|
|
|
* @param aLeft is the first point
|
|
|
|
* @param aRight is the second point
|
|
|
|
* @return unsigned - a measure of proximity that the caller knows about, in BIU,
|
|
|
|
* but remember it is only an approximation.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static unsigned close_ness( const wxPoint& aLeft, const wxPoint& aRight )
|
|
|
|
{
|
|
|
|
// Don't need an accurate distance calculation, just something
|
|
|
|
// approximating it, for relative ordering.
|
|
|
|
return unsigned( std::abs( aLeft.x - aRight.x ) + abs( aLeft.y - aRight.y ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
inline bool close_enough( const wxPoint& aLeft, const wxPoint& aRight, unsigned aLimit )
|
|
|
|
{
|
|
|
|
// We don't use an accurate distance calculation, just something
|
|
|
|
// approximating it, since aLimit is non-exact anyway except when zero.
|
|
|
|
return close_ness( aLeft, aRight ) <= aLimit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function close_st
|
|
|
|
* is a local method of qualifying if either the start of end point of a segment is closest to a point.
|
|
|
|
*
|
|
|
|
* @param aReference is the reference point
|
|
|
|
* @param aFirst is the first point
|
|
|
|
* @param aSecond is the second point
|
|
|
|
* @return bool - true if the the first point is closest to the reference, otherwise false.
|
|
|
|
*/
|
|
|
|
inline bool close_st( const wxPoint& aReference, const wxPoint& aFirst, const wxPoint& aSecond )
|
|
|
|
{
|
|
|
|
// We don't use an accurate distance calculation, just something
|
|
|
|
// approximating to find the closest to the reference.
|
|
|
|
return close_ness( aReference, aFirst ) <= close_ness( aReference, aSecond );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2017-06-17 08:40:38 +00:00
|
|
|
* Searches for a DRAWSEGMENT matching a given end point or start point in a list, and
|
2017-03-19 15:38:30 +00:00
|
|
|
* if found, removes it from the TYPE_COLLECTOR and returns it, else returns NULL.
|
|
|
|
* @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.
|
|
|
|
* @return DRAWSEGMENT* - The first DRAWSEGMENT that has a start or end point matching
|
|
|
|
* aPoint, otherwise NULL if none.
|
|
|
|
*/
|
|
|
|
static DRAWSEGMENT* findPoint( const wxPoint& aPoint, std::vector< DRAWSEGMENT* >& aList, unsigned aLimit )
|
|
|
|
{
|
|
|
|
unsigned min_d = INT_MAX;
|
|
|
|
int ndx_min = 0;
|
|
|
|
|
|
|
|
// find the point closest to aPoint and perhaps exactly matching aPoint.
|
|
|
|
for( size_t i = 0; i < aList.size(); ++i )
|
|
|
|
{
|
|
|
|
DRAWSEGMENT* graphic = aList[i];
|
|
|
|
unsigned d;
|
|
|
|
|
|
|
|
switch( graphic->GetShape() )
|
|
|
|
{
|
|
|
|
case S_ARC:
|
|
|
|
if( aPoint == graphic->GetArcStart() || aPoint == graphic->GetArcEnd() )
|
|
|
|
{
|
|
|
|
aList.erase( aList.begin() + i );
|
|
|
|
return graphic;
|
|
|
|
}
|
|
|
|
|
|
|
|
d = close_ness( aPoint, graphic->GetArcStart() );
|
|
|
|
if( d < min_d )
|
|
|
|
{
|
|
|
|
min_d = d;
|
|
|
|
ndx_min = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
d = close_ness( aPoint, graphic->GetArcEnd() );
|
|
|
|
if( d < min_d )
|
|
|
|
{
|
|
|
|
min_d = d;
|
|
|
|
ndx_min = i;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
if( aPoint == graphic->GetStart() || aPoint == graphic->GetEnd() )
|
|
|
|
{
|
|
|
|
aList.erase( aList.begin() + i );
|
|
|
|
return graphic;
|
|
|
|
}
|
|
|
|
|
|
|
|
d = close_ness( aPoint, graphic->GetStart() );
|
|
|
|
if( d < min_d )
|
|
|
|
{
|
|
|
|
min_d = d;
|
|
|
|
ndx_min = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
d = close_ness( aPoint, graphic->GetEnd() );
|
|
|
|
if( d < min_d )
|
|
|
|
{
|
|
|
|
min_d = d;
|
|
|
|
ndx_min = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( min_d <= aLimit )
|
|
|
|
{
|
|
|
|
DRAWSEGMENT* graphic = aList[ndx_min];
|
|
|
|
aList.erase( aList.begin() + ndx_min );
|
|
|
|
return graphic;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function ConvertOutlineToPolygon
|
|
|
|
* build a polygon (with holes) from a DRAWSEGMENT list, which is expected to be
|
|
|
|
* a outline, therefore a closed main outline with perhaps closed inner outlines.
|
|
|
|
* These closed inner outlines are considered as holes in the main outline
|
|
|
|
* @param aSegList the initial list of drawsegments (only lines, circles and arcs).
|
|
|
|
* @param aPolygons will contain the complex polygon.
|
2020-08-15 15:42:18 +00:00
|
|
|
* @param aTolerance is the max distance between points that is still accepted as connected
|
|
|
|
* (internal units)
|
2017-03-20 12:05:38 +00:00
|
|
|
* @param aErrorText is a wxString to return error message.
|
2018-11-28 12:11:43 +00:00
|
|
|
* @param aErrorLocation is the optional position of the error in the outline
|
2017-03-19 15:38:30 +00:00
|
|
|
*/
|
2018-06-18 15:34:43 +00:00
|
|
|
bool ConvertOutlineToPolygon( std::vector<DRAWSEGMENT*>& aSegList, SHAPE_POLY_SET& aPolygons,
|
2020-08-15 15:42:18 +00:00
|
|
|
wxString* aErrorText, unsigned int aTolerance,
|
|
|
|
wxPoint* aErrorLocation )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
|
|
|
if( aSegList.size() == 0 )
|
|
|
|
return true;
|
|
|
|
|
2017-03-20 12:05:38 +00:00
|
|
|
wxString msg;
|
|
|
|
|
2017-03-19 15:38:30 +00:00
|
|
|
// Make a working copy of aSegList, because the list is modified during calculations
|
|
|
|
std::vector< DRAWSEGMENT* > segList = aSegList;
|
|
|
|
|
|
|
|
DRAWSEGMENT* graphic;
|
|
|
|
wxPoint prevPt;
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
for( size_t i = 0; i < segList.size(); i++ )
|
|
|
|
{
|
|
|
|
graphic = (DRAWSEGMENT*) segList[i];
|
|
|
|
|
|
|
|
switch( graphic->GetShape() )
|
|
|
|
{
|
2020-06-15 19:50:20 +00:00
|
|
|
case S_RECT:
|
2017-03-19 15:38:30 +00:00
|
|
|
case S_SEGMENT:
|
|
|
|
{
|
|
|
|
if( graphic->GetStart().x < xmin.x )
|
|
|
|
{
|
|
|
|
xmin = graphic->GetStart();
|
|
|
|
xmini = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( graphic->GetEnd().x < xmin.x )
|
|
|
|
{
|
|
|
|
xmin = graphic->GetEnd();
|
|
|
|
xmini = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case S_ARC:
|
|
|
|
{
|
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-08-15 15:42:18 +00:00
|
|
|
int steps = GetArcToSegmentCount( radius, aTolerance, 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;
|
|
|
|
|
|
|
|
case S_CIRCLE:
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2018-07-09 17:15:36 +00:00
|
|
|
case S_CURVE:
|
|
|
|
{
|
|
|
|
graphic->RebuildBezierToSegmentsPointsList( graphic->GetWidth() );
|
|
|
|
|
2020-08-15 15:42:18 +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;
|
|
|
|
|
2018-12-02 04:41:17 +00:00
|
|
|
case S_POLYGON:
|
|
|
|
{
|
2020-08-15 15:42:18 +00:00
|
|
|
const SHAPE_POLY_SET poly = graphic->GetPolyShape();
|
|
|
|
MODULE* module = aSegList[0]->GetParentModule();
|
|
|
|
double orientation = module ? module->GetOrientation() : 0.0;
|
|
|
|
VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 );
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
graphic = (DRAWSEGMENT*) segList[xmini];
|
|
|
|
|
|
|
|
// The first DRAWSEGMENT is in 'graphic', ok to remove it from 'items'
|
|
|
|
segList.erase( segList.begin() + xmini );
|
|
|
|
|
2019-04-02 06:25:34 +00:00
|
|
|
// Output the outline perimeter as polygon.
|
2017-03-19 15:38:30 +00:00
|
|
|
if( graphic->GetShape() == S_CIRCLE )
|
|
|
|
{
|
2019-05-22 13:33:48 +00:00
|
|
|
TransformCircleToPolygon( aPolygons, graphic->GetCenter(), graphic->GetRadius(), aTolerance );
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
2020-06-15 19:50:20 +00:00
|
|
|
else if( graphic->GetShape() == S_RECT )
|
|
|
|
{
|
|
|
|
std::vector<wxPoint> pts;
|
|
|
|
graphic->GetRectCorners( &pts );
|
|
|
|
|
|
|
|
aPolygons.NewOutline();
|
|
|
|
|
|
|
|
for( const wxPoint& pt : pts )
|
|
|
|
aPolygons.Append( pt );
|
|
|
|
}
|
2018-12-02 04:41:17 +00:00
|
|
|
else if( graphic->GetShape() == S_POLYGON )
|
|
|
|
{
|
2018-12-02 15:33:12 +00:00
|
|
|
MODULE* module = graphic->GetParentModule(); // NULL for items not in footprints
|
|
|
|
double orientation = module ? module->GetOrientation() : 0.0;
|
|
|
|
VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 );
|
|
|
|
|
|
|
|
aPolygons.NewOutline();
|
|
|
|
|
|
|
|
for( auto it = graphic->GetPolyShape().CIterate( 0 ); it; it++ )
|
|
|
|
{
|
|
|
|
auto pt = *it;
|
|
|
|
RotatePoint( pt, orientation );
|
|
|
|
pt += offset;
|
|
|
|
aPolygons.Append( pt );
|
|
|
|
}
|
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.
|
|
|
|
|
2020-08-15 15:42:18 +00:00
|
|
|
wxPoint startPt = graphic->GetShape() == S_ARC ? graphic->GetArcEnd()
|
|
|
|
: 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() )
|
|
|
|
{
|
|
|
|
case S_SEGMENT:
|
|
|
|
{
|
|
|
|
wxPoint nextPt;
|
|
|
|
|
2020-08-15 15:42:18 +00:00
|
|
|
// 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.
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
|
|
|
|
nextPt = graphic->GetEnd();
|
|
|
|
else
|
|
|
|
nextPt = graphic->GetStart();
|
|
|
|
|
|
|
|
aPolygons.Append( nextPt );
|
|
|
|
prevPt = nextPt;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case S_ARC:
|
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
|
|
|
{
|
|
|
|
wxPoint pstart = graphic->GetArcStart();
|
|
|
|
wxPoint pend = graphic->GetArcEnd();
|
|
|
|
wxPoint pcenter = graphic->GetCenter();
|
2018-03-08 18:03:56 +00:00
|
|
|
double angle = -graphic->GetAngle();
|
2018-03-22 18:02:45 +00:00
|
|
|
double radius = graphic->GetRadius();
|
2019-03-01 13:36:53 +00:00
|
|
|
int steps = GetArcToSegmentCount( radius, aTolerance, angle / 10.0 );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2018-06-18 15:34:43 +00:00
|
|
|
if( !close_enough( prevPt, pstart, aTolerance ) )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2018-06-18 15:34:43 +00:00
|
|
|
wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), aTolerance ) );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
angle = -angle;
|
|
|
|
std::swap( pstart, pend );
|
|
|
|
}
|
|
|
|
|
|
|
|
wxPoint nextPt;
|
|
|
|
|
|
|
|
for( int step = 1; step<=steps; ++step )
|
|
|
|
{
|
|
|
|
double rotation = ( angle * step ) / steps;
|
|
|
|
nextPt = pstart;
|
|
|
|
RotatePoint( &nextPt, pcenter, rotation );
|
|
|
|
|
|
|
|
aPolygons.Append( nextPt );
|
|
|
|
}
|
|
|
|
|
|
|
|
prevPt = nextPt;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2018-07-09 17:15:36 +00:00
|
|
|
case S_CURVE:
|
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-08-15 15:42:18 +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.
|
|
|
|
|
|
|
|
if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
|
|
|
|
nextPt = graphic->GetEnd();
|
|
|
|
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-- )
|
2018-07-09 17:15:36 +00:00
|
|
|
aPolygons.Append( graphic->GetBezierPoints()[jj] );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-10-11 14:31:35 +00:00
|
|
|
for( size_t jj = 0; jj < graphic->GetBezierPoints().size(); jj++ )
|
2018-07-09 17:15:36 +00:00
|
|
|
aPolygons.Append( graphic->GetBezierPoints()[jj] );
|
|
|
|
}
|
|
|
|
|
|
|
|
prevPt = nextPt;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2017-03-19 15:38:30 +00:00
|
|
|
default:
|
2017-03-20 12:05:38 +00:00
|
|
|
if( aErrorText )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2018-11-11 09:46:26 +00:00
|
|
|
msg.Printf( "Unsupported DRAWSEGMENT type %s.",
|
|
|
|
BOARD_ITEM::ShowShape( graphic->GetShape() ) );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2017-03-20 12:05:38 +00:00
|
|
|
*aErrorText << msg << "\n";
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
2017-03-20 12:05:38 +00:00
|
|
|
|
2018-11-28 12:11:43 +00:00
|
|
|
if( aErrorLocation )
|
|
|
|
*aErrorLocation = graphic->GetPosition();
|
|
|
|
|
2017-03-20 12:05:38 +00:00
|
|
|
return false;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get next closest segment.
|
|
|
|
|
2018-06-18 15:34:43 +00:00
|
|
|
graphic = findPoint( prevPt, segList, aTolerance );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
// If there are no more close segments, check if the board
|
|
|
|
// outline polygon can be closed.
|
|
|
|
|
|
|
|
if( !graphic )
|
|
|
|
{
|
2018-06-18 15:34:43 +00:00
|
|
|
if( close_enough( startPt, prevPt, aTolerance ) )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
|
|
|
// Close the polygon back to start point
|
|
|
|
// aPolygons.Append( startPt ); // not needed
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-03-20 12:05:38 +00:00
|
|
|
if( aErrorText )
|
|
|
|
{
|
2020-08-17 20:07:27 +00:00
|
|
|
msg.Printf( _( "Unable to find edge with an endpoint of (%s, %s)." ),
|
|
|
|
StringFromValue( EDA_UNITS::MILLIMETRES, prevPt.x, true ),
|
|
|
|
StringFromValue( EDA_UNITS::MILLIMETRES, prevPt.y, true ) );
|
2017-03-20 12:05:38 +00:00
|
|
|
|
|
|
|
*aErrorText << msg << "\n";
|
|
|
|
}
|
|
|
|
|
2018-11-28 12:11:43 +00:00
|
|
|
if( aErrorLocation )
|
|
|
|
*aErrorLocation = prevPt;
|
|
|
|
|
2017-03-19 15:38:30 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while( segList.size() )
|
|
|
|
{
|
|
|
|
// emit a signal layers keepout for every interior polygon left...
|
|
|
|
int hole = aPolygons.NewHole();
|
|
|
|
|
|
|
|
graphic = (DRAWSEGMENT*) segList[0];
|
|
|
|
segList.erase( segList.begin() );
|
|
|
|
|
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
|
|
|
|
if( graphic->GetShape() == S_POLYGON )
|
|
|
|
{
|
2018-12-02 15:33:12 +00:00
|
|
|
MODULE* module = graphic->GetParentModule(); // NULL for items not in footprints
|
|
|
|
double orientation = module ? module->GetOrientation() : 0.0;
|
|
|
|
VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 );
|
|
|
|
|
2018-12-02 04:41:17 +00:00
|
|
|
for( auto it = graphic->GetPolyShape().CIterate(); it; it++ )
|
|
|
|
{
|
2018-12-02 15:33:12 +00:00
|
|
|
auto val = *it;
|
|
|
|
RotatePoint( val, orientation );
|
|
|
|
val += offset;
|
|
|
|
|
|
|
|
aPolygons.Append( val, -1, hole );
|
2018-12-02 04:41:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( graphic->GetShape() == S_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();
|
2019-04-01 05:12:51 +00:00
|
|
|
int steps = std::max<int>( 4, GetArcToSegmentCount( radius, aTolerance, 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-06-15 19:50:20 +00:00
|
|
|
else if( graphic->GetShape() == S_RECT )
|
|
|
|
{
|
|
|
|
std::vector<wxPoint> pts;
|
|
|
|
graphic->GetRectCorners( &pts );
|
|
|
|
|
|
|
|
for( const wxPoint& pt : pts )
|
|
|
|
aPolygons.Append( pt, -1, hole );
|
|
|
|
}
|
2017-03-19 15:38:30 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// Polygon start point. Arbitrarily chosen end of the
|
|
|
|
// segment and build the poly from here.
|
|
|
|
|
|
|
|
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() )
|
|
|
|
{
|
|
|
|
case S_SEGMENT:
|
|
|
|
{
|
|
|
|
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.
|
|
|
|
|
|
|
|
if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
|
|
|
|
nextPt = graphic->GetEnd();
|
|
|
|
else
|
|
|
|
nextPt = graphic->GetStart();
|
|
|
|
|
|
|
|
prevPt = nextPt;
|
|
|
|
aPolygons.Append( prevPt, -1, hole );
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case S_ARC:
|
|
|
|
// Freerouter does not yet understand arcs, so approximate
|
|
|
|
// an arc with a series of short lines and put those
|
|
|
|
// line segments into the !same! PATH.
|
|
|
|
{
|
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();
|
2019-03-01 13:36:53 +00:00
|
|
|
int steps = GetArcToSegmentCount( radius, aTolerance, angle / 10.0 );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2018-06-18 15:34:43 +00:00
|
|
|
if( !close_enough( prevPt, pstart, aTolerance ) )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2018-06-18 15:34:43 +00:00
|
|
|
wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), aTolerance ) );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
angle = -angle;
|
|
|
|
std::swap( pstart, pend );
|
|
|
|
}
|
|
|
|
|
|
|
|
wxPoint nextPt;
|
|
|
|
|
2017-03-20 12:05:38 +00:00
|
|
|
for( int step = 1; step <= steps; ++step )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
|
|
|
double rotation = ( angle * step ) / steps;
|
|
|
|
|
|
|
|
nextPt = pstart;
|
|
|
|
RotatePoint( &nextPt, pcenter, rotation );
|
|
|
|
|
|
|
|
aPolygons.Append( nextPt, -1, hole );
|
|
|
|
}
|
|
|
|
|
2018-07-09 17:15:36 +00:00
|
|
|
prevPt = nextPt;
|
|
|
|
}
|
2018-07-24 07:17:20 +00:00
|
|
|
break;
|
2018-07-09 17:15:36 +00:00
|
|
|
|
|
|
|
case S_CURVE:
|
|
|
|
// 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.
|
|
|
|
{
|
|
|
|
wxPoint nextPt;
|
|
|
|
bool reverse = false;
|
|
|
|
|
|
|
|
// Use the end point furthest away from
|
|
|
|
// prevPt as we assume the other end to be ON prevPt or
|
|
|
|
// very close to it.
|
|
|
|
|
|
|
|
if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
|
|
|
|
nextPt = graphic->GetEnd();
|
|
|
|
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-- )
|
2018-07-09 17:15:36 +00:00
|
|
|
aPolygons.Append( graphic->GetBezierPoints()[jj], -1, hole );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-08-15 15:42:18 +00:00
|
|
|
for( const wxPoint& pt : graphic->GetBezierPoints())
|
|
|
|
aPolygons.Append( pt, -1, hole );
|
2018-07-09 17:15:36 +00:00
|
|
|
}
|
|
|
|
|
2017-03-19 15:38:30 +00:00
|
|
|
prevPt = nextPt;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2017-03-20 12:05:38 +00:00
|
|
|
if( aErrorText )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
2018-11-11 09:46:26 +00:00
|
|
|
msg.Printf( "Unsupported DRAWSEGMENT type %s.",
|
2018-10-11 14:31:35 +00:00
|
|
|
BOARD_ITEM::ShowShape( graphic->GetShape() ) );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2017-03-20 12:05:38 +00:00
|
|
|
*aErrorText << msg << "\n";
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
2017-03-20 12:05:38 +00:00
|
|
|
|
2018-11-28 12:11:43 +00:00
|
|
|
if( aErrorLocation )
|
|
|
|
*aErrorLocation = graphic->GetPosition();
|
|
|
|
|
2017-03-20 12:05:38 +00:00
|
|
|
return false;
|
2017-03-19 15:38:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get next closest segment.
|
|
|
|
|
2018-06-18 15:34:43 +00:00
|
|
|
graphic = findPoint( prevPt, segList, aTolerance );
|
2017-03-19 15:38:30 +00:00
|
|
|
|
|
|
|
// If there are no more close segments, check if polygon
|
|
|
|
// can be closed.
|
|
|
|
|
|
|
|
if( !graphic )
|
|
|
|
{
|
2018-06-18 15:34:43 +00:00
|
|
|
if( close_enough( startPt, prevPt, aTolerance ) )
|
2017-03-19 15:38:30 +00:00
|
|
|
{
|
|
|
|
// Close the polygon back to start point
|
|
|
|
// aPolygons.Append( startPt, -1, hole ); // not needed
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-03-20 12:05:38 +00:00
|
|
|
if( aErrorText )
|
|
|
|
{
|
2020-08-17 20:07:27 +00:00
|
|
|
msg.Printf( _( "Unable to find edge with an endpoint of (%s, %s)." ),
|
2020-08-15 15:42:18 +00:00
|
|
|
StringFromValue( EDA_UNITS::MILLIMETRES, prevPt.x, true ),
|
|
|
|
StringFromValue( EDA_UNITS::MILLIMETRES, prevPt.y, true ) );
|
2017-03-20 12:05:38 +00:00
|
|
|
|
|
|
|
*aErrorText << msg << "\n";
|
|
|
|
}
|
2017-03-19 15:38:30 +00:00
|
|
|
|
2018-11-28 12:11:43 +00:00
|
|
|
if( aErrorLocation )
|
|
|
|
*aErrorLocation = prevPt;
|
|
|
|
|
2017-03-19 15:38:30 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-01 13:36:53 +00:00
|
|
|
// All of the silliness that follows is to work around the segment iterator
|
|
|
|
// while checking for collisions.
|
|
|
|
// TODO: Implement proper segment and point iterators that follow std
|
|
|
|
for( auto seg1 = aPolygons.IterateSegmentsWithHoles(); seg1; seg1++ )
|
|
|
|
{
|
|
|
|
auto seg2 = seg1;
|
|
|
|
|
|
|
|
for( ++seg2; seg2; seg2++ )
|
|
|
|
{
|
|
|
|
// Check for exact overlapping segments. This is not viewed
|
|
|
|
// as an intersection below
|
|
|
|
if( *seg1 == *seg2 ||
|
|
|
|
( ( *seg1 ).A == ( *seg2 ).B && ( *seg1 ).B == ( *seg2 ).A ) )
|
|
|
|
{
|
|
|
|
if( aErrorLocation )
|
|
|
|
{
|
|
|
|
aErrorLocation->x = ( *seg1 ).A.x;
|
|
|
|
aErrorLocation->y = ( *seg1 ).A.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
|
|
|
if( aErrorLocation )
|
|
|
|
{
|
|
|
|
aErrorLocation->x = pt->x;
|
|
|
|
aErrorLocation->y = pt->y;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-19 15:38:30 +00:00
|
|
|
return true;
|
2017-03-20 12:05:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#include <class_board.h>
|
|
|
|
#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-08-17 20:07:27 +00:00
|
|
|
bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, wxString* aErrorText,
|
|
|
|
unsigned int aTolerance, wxPoint* aErrorLocation )
|
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
|
|
|
|
|
|
|
// Get all the DRAWSEGMENTS and module graphics into 'items',
|
|
|
|
// then keep only those on layer == Edge_Cuts.
|
|
|
|
static const KICAD_T scan_graphics[] = { PCB_LINE_T, PCB_MODULE_EDGE_T, EOT };
|
|
|
|
items.Collect( aBoard, scan_graphics );
|
|
|
|
|
|
|
|
// Make a working copy of aSegList, because the list is modified during calculations
|
|
|
|
std::vector< DRAWSEGMENT* > segList;
|
|
|
|
|
|
|
|
for( int ii = 0; ii < items.GetCount(); ii++ )
|
|
|
|
{
|
|
|
|
if( items[ii]->GetLayer() == Edge_Cuts )
|
|
|
|
segList.push_back( static_cast< DRAWSEGMENT* >( items[ii] ) );
|
|
|
|
}
|
|
|
|
|
2020-05-18 20:09:36 +00:00
|
|
|
if( segList.size() )
|
|
|
|
{
|
|
|
|
success = ConvertOutlineToPolygon( segList, aOutlines, aErrorText, aTolerance,
|
|
|
|
aErrorLocation );
|
|
|
|
}
|
2020-08-21 12:55:22 +00:00
|
|
|
else if( aErrorText )
|
2020-08-17 20:07:27 +00:00
|
|
|
{
|
|
|
|
*aErrorText = _( "No edges found on Edge.Cuts layer." );
|
|
|
|
}
|
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;
|
|
|
|
}
|