1687 lines
52 KiB
C++
1687 lines
52 KiB
C++
// PolyLine.cpp ... implementation of CPolyLine class from FreePCB.
|
|
|
|
//
|
|
// implementation for kicad and kbool polygon clipping library
|
|
//
|
|
#include <math.h>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
#include <fctsys.h>
|
|
#include <config.h> // to define KICAD_NANOMETRE
|
|
|
|
#include <PolyLine.h>
|
|
#include <bezier_curves.h>
|
|
#include <polygon_test_point_inside.h>
|
|
|
|
#if defined(KICAD_NANOMETRE)
|
|
#define PCBU_PER_MIL (1000.0*25.4)
|
|
#else
|
|
#define PCBU_PER_MIL 10
|
|
#endif
|
|
|
|
#define to_int( x ) wxRound( (x) )
|
|
|
|
#ifndef MIN
|
|
#define MIN( x1, x2 ) ( (x1) > (x2) ? (x2) : (x1) )
|
|
#endif
|
|
#ifndef MAX
|
|
#define MAX( x1, x2 ) ( (x1) > (x2) ? (x1) : (x2) )
|
|
#endif
|
|
|
|
#define pi M_PI
|
|
|
|
CPolyLine::CPolyLine()
|
|
{
|
|
m_HatchStyle = 0;
|
|
m_Width = 0;
|
|
utility = 0;
|
|
m_Kbool_Poly_Engine = NULL;
|
|
}
|
|
|
|
|
|
// destructor, removes display elements
|
|
//
|
|
CPolyLine::~CPolyLine()
|
|
{
|
|
UnHatch();
|
|
if( m_Kbool_Poly_Engine )
|
|
delete m_Kbool_Poly_Engine;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function NormalizeWithKbool
|
|
* Use the Kbool Library to clip contours: if outlines are crossing, the self-crossing polygon
|
|
* is converted to non self-crossing polygon by adding extra points at the crossing locations
|
|
* and reordering corners
|
|
* if more than one outside contour are found, extra CPolyLines will be created
|
|
* because copper areas have only one outside contour
|
|
* Therefore, if this results in new CPolyLines, return them as std::vector pa
|
|
* @param aExtraPolyList: pointer on a std::vector<CPolyLine*> to store extra CPolyLines
|
|
* @param bRetainArcs == true, try to retain arcs in polys
|
|
* @return number of external contours, or -1 if error
|
|
*/
|
|
int CPolyLine::NormalizeWithKbool( std::vector<CPolyLine*> * aExtraPolyList, bool bRetainArcs )
|
|
{
|
|
std::vector<CArc> arc_array;
|
|
std::vector <void*> hole_array; // list of holes
|
|
std::vector<int> * hole; // used to store corners for a given hole
|
|
CPolyLine* polyline;
|
|
int n_ext_cont = 0; // CPolyLine count
|
|
|
|
/* Creates a bool engine from this CPolyLine.
|
|
* Normalized outlines and holes will be in m_Kbool_Poly_Engine
|
|
* If some polygons are self crossing, after running the Kbool Engine, self crossing polygons
|
|
* will be converted in non self crossing polygons by inserting extra points at the crossing locations
|
|
* True holes are combined if possible
|
|
*/
|
|
if( bRetainArcs )
|
|
MakeKboolPoly( -1, -1, &arc_array );
|
|
else
|
|
MakeKboolPoly( -1, -1, NULL );
|
|
|
|
UnHatch();
|
|
|
|
/* now, recreate polys
|
|
* if more than one outside contour are found, extra CPolyLines will be created
|
|
* because copper areas have only one outside contour
|
|
* the first outside contour found is the new "this" outside contour
|
|
* if others outside contours are found we create new CPolyLines
|
|
* Note: if there are holes in polygons, we must store them
|
|
* and when all outside contours are found, search the corresponding outside contour for each hole
|
|
*/
|
|
while( m_Kbool_Poly_Engine->StartPolygonGet() )
|
|
{
|
|
// See if the current polygon is flagged as a hole
|
|
if( m_Kbool_Poly_Engine->GetPolygonPointEdgeType() == KB_INSIDE_EDGE )
|
|
{
|
|
hole = new std::vector<int>;
|
|
hole_array.push_back( hole );
|
|
while( m_Kbool_Poly_Engine->PolygonHasMorePoints() ) // store hole
|
|
{
|
|
int x = (int) m_Kbool_Poly_Engine->GetPolygonXPoint();
|
|
int y = (int) m_Kbool_Poly_Engine->GetPolygonYPoint();
|
|
hole->push_back( x );
|
|
hole->push_back( y );
|
|
}
|
|
|
|
m_Kbool_Poly_Engine->EndPolygonGet();
|
|
}
|
|
else if( n_ext_cont == 0 )
|
|
{
|
|
// first external contour, replace this poly
|
|
corner.clear();
|
|
side_style.clear();
|
|
bool first = true;
|
|
while( m_Kbool_Poly_Engine->PolygonHasMorePoints() )
|
|
{ // foreach point in the polygon
|
|
int x = (int) m_Kbool_Poly_Engine->GetPolygonXPoint();
|
|
int y = (int) m_Kbool_Poly_Engine->GetPolygonYPoint();
|
|
if( first )
|
|
{
|
|
first = false;
|
|
Start( GetLayer(), x, y, GetHatchStyle() );
|
|
}
|
|
else
|
|
AppendCorner( x, y );
|
|
}
|
|
|
|
m_Kbool_Poly_Engine->EndPolygonGet();
|
|
Close();
|
|
n_ext_cont++;
|
|
}
|
|
else if( aExtraPolyList ) // a new outside contour is found: create a new CPolyLine
|
|
{
|
|
polyline = new CPolyLine; // create new poly
|
|
aExtraPolyList->push_back( polyline ); // put it in array
|
|
bool first = true;
|
|
while( m_Kbool_Poly_Engine->PolygonHasMorePoints() ) // read next external contour
|
|
{
|
|
int x = (int) m_Kbool_Poly_Engine->GetPolygonXPoint();
|
|
int y = (int) m_Kbool_Poly_Engine->GetPolygonYPoint();
|
|
if( first )
|
|
{
|
|
first = false;
|
|
polyline->Start( GetLayer(), x, y, GetHatchStyle() );
|
|
}
|
|
else
|
|
polyline->AppendCorner( x, y );
|
|
}
|
|
|
|
m_Kbool_Poly_Engine->EndPolygonGet();
|
|
polyline->Close( STRAIGHT, false );
|
|
n_ext_cont++;
|
|
}
|
|
}
|
|
|
|
// now add cutouts to the corresponding CPolyLine(s)
|
|
for( unsigned ii = 0; ii < hole_array.size(); ii++ )
|
|
{
|
|
hole = (std::vector<int> *)hole_array[ii];
|
|
polyline = NULL;
|
|
if( n_ext_cont == 1 )
|
|
{
|
|
polyline = this;
|
|
}
|
|
else
|
|
{
|
|
// find the polygon that contains this hole
|
|
// testing one corner inside is enought because a hole is entirely inside the polygon
|
|
// so we test only the first corner
|
|
int x = (*hole)[0];
|
|
int y = (*hole)[1];
|
|
if( TestPointInside( x, y ) )
|
|
polyline = this;
|
|
else if( aExtraPolyList )
|
|
{
|
|
for( int ext_ic = 0; ext_ic<n_ext_cont - 1; ext_ic++ )
|
|
{
|
|
if( (*aExtraPolyList)[ext_ic]->TestPointInside( x, y ) )
|
|
{
|
|
polyline = (*aExtraPolyList)[ext_ic];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !polyline )
|
|
wxASSERT( 0 );
|
|
else
|
|
{
|
|
for( unsigned ii = 0; ii< (*hole).size(); ii++ )
|
|
{
|
|
int x = (*hole)[ii]; ii++;
|
|
int y = (*hole)[ii];
|
|
polyline->AppendCorner( x, y, STRAIGHT, false );
|
|
}
|
|
|
|
polyline->Close( STRAIGHT, false );
|
|
}
|
|
}
|
|
|
|
if( bRetainArcs )
|
|
RestoreArcs( &arc_array, aExtraPolyList );
|
|
|
|
delete m_Kbool_Poly_Engine;
|
|
m_Kbool_Poly_Engine = NULL;
|
|
|
|
// free hole list
|
|
for( unsigned ii = 0; ii < hole_array.size(); ii++ )
|
|
delete (std::vector<int> *)hole_array[ii];
|
|
|
|
return n_ext_cont;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function AddPolygonsToBoolEng
|
|
* Add a CPolyLine to a kbool engine, preparing a boolean op between polygons
|
|
* @param aStart_contour: starting contour number (-1 = all, 0 is the outlines of zone, > 1 = holes in zone
|
|
* @param aEnd_contour: ending contour number (-1 = all after aStart_contour)
|
|
* @param arc_array: arc converted to poly segments (NULL if not exists)
|
|
* @param aBooleng : pointer on a bool engine (handle a set of polygons)
|
|
* @param aGroup : group to fill (aGroup = GROUP_A or GROUP_B) operations are made between GROUP_A and GROUP_B
|
|
*/
|
|
int CPolyLine::AddPolygonsToBoolEng( Bool_Engine* aBooleng,
|
|
GroupType aGroup,
|
|
int aStart_contour,
|
|
int aEnd_contour,
|
|
std::vector<CArc> * arc_array )
|
|
{
|
|
int count = 0;
|
|
|
|
if( (aGroup != GROUP_A) && (aGroup != GROUP_B ) )
|
|
return 0; //Error !
|
|
|
|
/* Convert the current polyline contour to a kbool polygon: */
|
|
MakeKboolPoly( aStart_contour, aEnd_contour, arc_array );
|
|
|
|
/* add the resulting kbool set of polygons to the current kcool engine */
|
|
while( m_Kbool_Poly_Engine->StartPolygonGet() )
|
|
{
|
|
if( aBooleng->StartPolygonAdd( GROUP_A ) )
|
|
{
|
|
while( m_Kbool_Poly_Engine->PolygonHasMorePoints() )
|
|
{
|
|
int x = (int) m_Kbool_Poly_Engine->GetPolygonXPoint();
|
|
int y = (int) m_Kbool_Poly_Engine->GetPolygonYPoint();
|
|
aBooleng->AddPoint( x, y );
|
|
count++;
|
|
}
|
|
|
|
aBooleng->EndPolygonAdd();
|
|
}
|
|
m_Kbool_Poly_Engine->EndPolygonGet();
|
|
}
|
|
|
|
delete m_Kbool_Poly_Engine;
|
|
m_Kbool_Poly_Engine = NULL;
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function MakeKboolPoly
|
|
* fill a kbool engine with a closed polyline contour
|
|
* approximates arcs with multiple straight-line segments
|
|
* @param aStart_contour: starting contour number (-1 = all, 0 is the outlines of zone, > 1 = holes in zone
|
|
* @param aEnd_contour: ending contour number (-1 = all after aStart_contour)
|
|
* combining intersecting contours if possible
|
|
* @param arc_array : return corners computed from arcs approximations in arc_array
|
|
* @param aConvertHoles = mode for holes when a boolean operation is made
|
|
* true: holes are linked into outer contours by double overlapping segments
|
|
* false: holes are not linked: in this mode contours are added clockwise
|
|
* and polygons added counter clockwise are holes (default)
|
|
* @return error: 0 if Ok, 1 if error
|
|
*/
|
|
int CPolyLine::MakeKboolPoly( int aStart_contour, int aEnd_contour, std::vector<CArc> * arc_array,
|
|
bool aConvertHoles )
|
|
{
|
|
if( m_Kbool_Poly_Engine )
|
|
{
|
|
delete m_Kbool_Poly_Engine;
|
|
m_Kbool_Poly_Engine = NULL;
|
|
}
|
|
if( !GetClosed() && (aStart_contour == (GetNumContours() - 1) || aStart_contour == -1) )
|
|
return 1; // error
|
|
|
|
int n_arcs = 0;
|
|
|
|
int first_contour = aStart_contour;
|
|
int last_contour = aEnd_contour;
|
|
if( aStart_contour == -1 )
|
|
{
|
|
first_contour = 0;
|
|
last_contour = GetNumContours() - 1;
|
|
}
|
|
if( aEnd_contour == -1 )
|
|
{
|
|
last_contour = GetNumContours() - 1;
|
|
}
|
|
if( arc_array )
|
|
arc_array->clear();
|
|
int iarc = 0;
|
|
for( int icont = first_contour; icont<=last_contour; icont++ )
|
|
{
|
|
// Fill a kbool engine for this contour,
|
|
// and combine it with previous contours
|
|
Bool_Engine* booleng = new Bool_Engine();
|
|
ArmBoolEng( booleng, aConvertHoles );
|
|
|
|
if( m_Kbool_Poly_Engine ) // a previous contour exists. Put it in new engine
|
|
{
|
|
while( m_Kbool_Poly_Engine->StartPolygonGet() )
|
|
{
|
|
if( booleng->StartPolygonAdd( GROUP_A ) )
|
|
{
|
|
while( m_Kbool_Poly_Engine->PolygonHasMorePoints() )
|
|
{
|
|
int x = (int) m_Kbool_Poly_Engine->GetPolygonXPoint();
|
|
int y = (int) m_Kbool_Poly_Engine->GetPolygonYPoint();
|
|
booleng->AddPoint( x, y );
|
|
}
|
|
|
|
booleng->EndPolygonAdd();
|
|
}
|
|
m_Kbool_Poly_Engine->EndPolygonGet();
|
|
}
|
|
}
|
|
|
|
// first, calculate number of vertices in contour
|
|
int n_vertices = 0;
|
|
int ic_st = GetContourStart( icont );
|
|
int ic_end = GetContourEnd( icont );
|
|
if( !booleng->StartPolygonAdd( GROUP_B ) )
|
|
{
|
|
wxASSERT( 0 );
|
|
return 1; //error
|
|
}
|
|
for( int ic = ic_st; ic<=ic_end; ic++ )
|
|
{
|
|
int style = side_style[ic];
|
|
int x1 = corner[ic].x;
|
|
int y1 = corner[ic].y;
|
|
int x2, y2;
|
|
if( ic < ic_end )
|
|
{
|
|
x2 = corner[ic + 1].x;
|
|
y2 = corner[ic + 1].y;
|
|
}
|
|
else
|
|
{
|
|
x2 = corner[ic_st].x;
|
|
y2 = corner[ic_st].y;
|
|
}
|
|
if( style == STRAIGHT )
|
|
n_vertices++;
|
|
else
|
|
{
|
|
// style is ARC_CW or ARC_CCW
|
|
int n; // number of steps for arcs
|
|
n = ( abs( x2 - x1 ) + abs( y2 - y1 ) ) / (CArc::MAX_STEP);
|
|
n = MAX( n, CArc::MIN_STEPS ); // or at most 5 degrees of arc
|
|
n_vertices += n;
|
|
n_arcs++;
|
|
}
|
|
}
|
|
|
|
// now enter this contour to booleng
|
|
int ivtx = 0;
|
|
for( int ic = ic_st; ic<=ic_end; ic++ )
|
|
{
|
|
int style = side_style[ic];
|
|
int x1 = corner[ic].x;
|
|
int y1 = corner[ic].y;
|
|
int x2, y2;
|
|
if( ic < ic_end )
|
|
{
|
|
x2 = corner[ic + 1].x;
|
|
y2 = corner[ic + 1].y;
|
|
}
|
|
else
|
|
{
|
|
x2 = corner[ic_st].x;
|
|
y2 = corner[ic_st].y;
|
|
}
|
|
if( style == STRAIGHT )
|
|
{
|
|
booleng->AddPoint( x1, y1 );
|
|
ivtx++;
|
|
}
|
|
else
|
|
{
|
|
// style is arc_cw or arc_ccw
|
|
int n; // number of steps for arcs
|
|
n = ( abs( x2 - x1 ) + abs( y2 - y1 ) ) / (CArc::MAX_STEP);
|
|
n = MAX( n, CArc::MIN_STEPS ); // or at most 5 degrees of arc
|
|
double xo, yo, theta1, theta2, a, b;
|
|
a = fabs( (double) (x1 - x2) );
|
|
b = fabs( (double) (y1 - y2) );
|
|
if( style == CPolyLine::ARC_CW )
|
|
{
|
|
// clockwise arc (ie.quadrant of ellipse)
|
|
if( x2 > x1 && y2 > y1 )
|
|
{
|
|
// first quadrant, draw second quadrant of ellipse
|
|
xo = x2;
|
|
yo = y1;
|
|
theta1 = pi;
|
|
theta2 = pi / 2.0;
|
|
}
|
|
else if( x2 < x1 && y2 > y1 )
|
|
{
|
|
// second quadrant, draw third quadrant of ellipse
|
|
xo = x1;
|
|
yo = y2;
|
|
theta1 = 3.0 * pi / 2.0;
|
|
theta2 = pi;
|
|
}
|
|
else if( x2 < x1 && y2 < y1 )
|
|
{
|
|
// third quadrant, draw fourth quadrant of ellipse
|
|
xo = x2;
|
|
yo = y1;
|
|
theta1 = 2.0 * pi;
|
|
theta2 = 3.0 * pi / 2.0;
|
|
}
|
|
else
|
|
{
|
|
xo = x1; // fourth quadrant, draw first quadrant of ellipse
|
|
yo = y2;
|
|
theta1 = pi / 2.0;
|
|
theta2 = 0.0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// counter-clockwise arc
|
|
if( x2 > x1 && y2 > y1 )
|
|
{
|
|
xo = x1; // first quadrant, draw fourth quadrant of ellipse
|
|
yo = y2;
|
|
theta1 = 3.0 * pi / 2.0;
|
|
theta2 = 2.0 * pi;
|
|
}
|
|
else if( x2 < x1 && y2 > y1 )
|
|
{
|
|
xo = x2; // second quadrant
|
|
yo = y1;
|
|
theta1 = 0.0;
|
|
theta2 = pi / 2.0;
|
|
}
|
|
else if( x2 < x1 && y2 < y1 )
|
|
{
|
|
xo = x1; // third quadrant
|
|
yo = y2;
|
|
theta1 = pi / 2.0;
|
|
theta2 = pi;
|
|
}
|
|
else
|
|
{
|
|
xo = x2; // fourth quadrant
|
|
yo = y1;
|
|
theta1 = pi;
|
|
theta2 = 3.0 * pi / 2.0;
|
|
}
|
|
}
|
|
|
|
// now write steps for arc
|
|
if( arc_array )
|
|
{
|
|
CArc new_arc;
|
|
new_arc.style = style;
|
|
new_arc.n_steps = n;
|
|
new_arc.xi = x1;
|
|
new_arc.yi = y1;
|
|
new_arc.xf = x2;
|
|
new_arc.yf = y2;
|
|
arc_array->push_back( new_arc );
|
|
iarc++;
|
|
}
|
|
for( int is = 0; is<n; is++ )
|
|
{
|
|
double theta = theta1 + ( (theta2 - theta1) * (double) is ) / n;
|
|
double x = xo + a* cos( theta );
|
|
double y = yo + b* sin( theta );
|
|
if( is == 0 )
|
|
{
|
|
x = x1;
|
|
y = y1;
|
|
}
|
|
booleng->AddPoint( x, y );
|
|
ivtx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( n_vertices != ivtx )
|
|
{
|
|
wxASSERT( 0 );
|
|
}
|
|
|
|
// close list added to the bool engine
|
|
booleng->EndPolygonAdd();
|
|
|
|
/* now combine polygon to the previous polygons.
|
|
* note: the first polygon is the outline contour, and others are holes inside the first polygon
|
|
* The first polygon is ORed with nothing, but is is a trick to sort corners (vertex)
|
|
* clockwise with the kbool engine.
|
|
* Others polygons are substract to the outline and corners will be ordered counter clockwise
|
|
* by the kbool engine
|
|
*/
|
|
if( aStart_contour <= 0 && icont != 0 ) // substract hole to outside ( if the outline contour is take in account)
|
|
{
|
|
booleng->Do_Operation( BOOL_A_SUB_B );
|
|
}
|
|
else // add outside or add holes if we do not use the outline contour
|
|
{
|
|
booleng->Do_Operation( BOOL_OR );
|
|
}
|
|
|
|
// now use result as new polygon (delete the old one if exists)
|
|
if( m_Kbool_Poly_Engine )
|
|
delete m_Kbool_Poly_Engine;
|
|
m_Kbool_Poly_Engine = booleng;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function ArmBoolEng
|
|
* Initialise parameters used in kbool
|
|
* @param aBooleng = pointer to the Bool_Engine to initialise
|
|
* @param aConvertHoles = mode for holes when a boolean operation is made
|
|
* true: in resulting polygon, holes are linked into outer contours by double overlapping segments
|
|
* false: in resulting polygons, holes are not linked: they are separate polygons
|
|
*/
|
|
void ArmBoolEng( Bool_Engine* aBooleng, bool aConvertHoles )
|
|
{
|
|
// set some global vals to arm the boolean engine
|
|
|
|
// input points are scaled up with GetDGrid() * GetGrid()
|
|
|
|
// DGRID is only meant to make fractional parts of input data which
|
|
/*
|
|
The input data scaled up with DGrid is related to the accuracy the user has in his input data.
|
|
User data with a minimum accuracy of 0.001, means set the DGrid to 1000.
|
|
The input data may contain data with a minimum accuracy much smaller, but by setting the DGrid
|
|
everything smaller than 1/DGrid is rounded.
|
|
|
|
DGRID is only meant to make fractional parts of input data which can be
|
|
doubles, part of the integers used in vertexes within the boolean algorithm.
|
|
And therefore DGRID bigger than 1 is not usefull, you would only loose accuracy.
|
|
Within the algorithm all input data is multiplied with DGRID, and the result
|
|
is rounded to an integer.
|
|
*/
|
|
double DGRID = 1000.0; // round coordinate X or Y value in calculations to this (initial value = 1000.0 in kbool example)
|
|
// kbool uses DGRID to convert float user units to integer
|
|
// kbool unit = (int)(user unit * DGRID)
|
|
// Note: in kicad, coordinates are already integer so DGRID could be set to 1
|
|
// we can choose 1.0,
|
|
// but choose DGRID = 1000.0 solves some filling problems
|
|
// (perhaps because this allows a better precision in kbool internal calculations
|
|
|
|
double MARGE = 1.0/DGRID; // snap with in this range points to lines in the intersection routines
|
|
// should always be >= 1/DGRID a MARGE >= 10/DGRID is ok
|
|
// this is also used to remove small segments and to decide when
|
|
// two segments are in line. ( initial value = 0.001 )
|
|
// For kicad we choose MARGE = 1/DGRID
|
|
|
|
double CORRECTIONFACTOR = 0.0; // correct the polygons by this number: used in BOOL_CORRECTION operation
|
|
// this operation shrinks a polygon if CORRECTIONFACTOR < 0
|
|
// or stretch it if CORRECTIONFACTOR > 0
|
|
// the size change is CORRECTIONFACTOR (holes are correctly handled)
|
|
double CORRECTIONABER = 1.0; // the accuracy for the rounded shapes used in correction
|
|
double ROUNDFACTOR = 1.5; // when will we round the correction shape to a circle
|
|
double SMOOTHABER = 10.0; // accuracy when smoothing a polygon
|
|
double MAXLINEMERGE = 1000.0; // leave as is, segments of this length in smoothen
|
|
|
|
|
|
/*
|
|
Grid makes sure that the integer data used within the algorithm has room for extra intersections
|
|
smaller than the smallest number within the input data.
|
|
The input data scaled up with DGrid is related to the accuracy the user has in his input data.
|
|
Another scaling with Grid is applied on top of it to create space in the integer number for
|
|
even smaller numbers.
|
|
*/
|
|
int GRID = (int) ( 10000.0 / DGRID ); // initial value = 10000 in kbool example but we use
|
|
// 10000/DGRID because the scaling is made by DGRID
|
|
// on integer pcbnew units and the global scaling
|
|
// ( GRID*DGRID) must be < 30000 to avoid overflow
|
|
// in calculations (made in long long in kbool)
|
|
if ( GRID <= 1 ) // Cannot be null!
|
|
GRID = 1;
|
|
|
|
aBooleng->SetMarge( MARGE );
|
|
aBooleng->SetGrid( GRID );
|
|
aBooleng->SetDGrid( DGRID );
|
|
aBooleng->SetCorrectionFactor( CORRECTIONFACTOR );
|
|
aBooleng->SetCorrectionAber( CORRECTIONABER );
|
|
aBooleng->SetSmoothAber( SMOOTHABER );
|
|
aBooleng->SetMaxlinemerge( MAXLINEMERGE );
|
|
aBooleng->SetRoundfactor( ROUNDFACTOR );
|
|
aBooleng->SetWindingRule( true ); // This is the default kbool value
|
|
|
|
if( aConvertHoles )
|
|
{
|
|
#if 1 // Can be set to 1 for kbool version >= 2.1, must be set to 0 for previous versions
|
|
// SetAllowNonTopHoleLinking() exists only in kbool >= 2.1
|
|
aBooleng->SetAllowNonTopHoleLinking( false ); // Default = true, but i have problems (filling errors) when true
|
|
#endif
|
|
aBooleng->SetLinkHoles( true ); // holes will be connected by double overlapping segments
|
|
aBooleng->SetOrientationEntryMode( false ); // all polygons are contours, not holes
|
|
}
|
|
else
|
|
{
|
|
aBooleng->SetLinkHoles( false ); // holes will not be connected by double overlapping segments
|
|
aBooleng->SetOrientationEntryMode( true ); // holes are entered counter clockwise
|
|
}
|
|
}
|
|
|
|
|
|
int CPolyLine::NormalizeAreaOutlines( std::vector<CPolyLine*> * pa, bool bRetainArcs )
|
|
{
|
|
return NormalizeWithKbool( pa, bRetainArcs );
|
|
}
|
|
|
|
|
|
// Restore arcs to a polygon where they were replaced with steps
|
|
// If pa != NULL, also use polygons in pa array
|
|
//
|
|
int CPolyLine::RestoreArcs( std::vector<CArc> * arc_array, std::vector<CPolyLine*> * pa )
|
|
{
|
|
// get poly info
|
|
int n_polys = 1;
|
|
|
|
if( pa )
|
|
n_polys += pa->size();
|
|
CPolyLine* poly;
|
|
|
|
// undraw polys and clear utility flag for all corners
|
|
for( int ip = 0; ip<n_polys; ip++ )
|
|
{
|
|
if( ip == 0 )
|
|
poly = this;
|
|
else
|
|
poly = (*pa)[ip - 1];
|
|
poly->UnHatch();
|
|
for( int ic = 0; ic<poly->GetNumCorners(); ic++ )
|
|
poly->SetUtility( ic, 0 );
|
|
|
|
// clear utility flag
|
|
}
|
|
|
|
// find arcs and replace them
|
|
bool bFound;
|
|
int arc_start = 0;
|
|
int arc_end = 0;
|
|
for( unsigned iarc = 0; iarc<arc_array->size(); iarc++ )
|
|
{
|
|
int arc_xi = (*arc_array)[iarc].xi;
|
|
int arc_yi = (*arc_array)[iarc].yi;
|
|
int arc_xf = (*arc_array)[iarc].xf;
|
|
int arc_yf = (*arc_array)[iarc].yf;
|
|
int n_steps = (*arc_array)[iarc].n_steps;
|
|
int style = (*arc_array)[iarc].style;
|
|
bFound = false;
|
|
|
|
// loop through polys
|
|
for( int ip = 0; ip<n_polys; ip++ )
|
|
{
|
|
if( ip == 0 )
|
|
poly = this;
|
|
else
|
|
poly = (*pa)[ip - 1];
|
|
for( int icont = 0; icont<poly->GetNumContours(); icont++ )
|
|
{
|
|
int ic_start = poly->GetContourStart( icont );
|
|
int ic_end = poly->GetContourEnd( icont );
|
|
if( (ic_end - ic_start) > n_steps )
|
|
{
|
|
for( int ic = ic_start; ic<=ic_end; ic++ )
|
|
{
|
|
int ic_next = ic + 1;
|
|
if( ic_next > ic_end )
|
|
ic_next = ic_start;
|
|
int xi = poly->GetX( ic );
|
|
int yi = poly->GetY( ic );
|
|
if( xi == arc_xi && yi == arc_yi )
|
|
{
|
|
// test for forward arc
|
|
int ic2 = ic + n_steps;
|
|
if( ic2 > ic_end )
|
|
ic2 = ic2 - ic_end + ic_start - 1;
|
|
int xf = poly->GetX( ic2 );
|
|
int yf = poly->GetY( ic2 );
|
|
if( xf == arc_xf && yf == arc_yf )
|
|
{
|
|
// arc from ic to ic2
|
|
bFound = true;
|
|
arc_start = ic;
|
|
arc_end = ic2;
|
|
}
|
|
else
|
|
{
|
|
// try reverse arc
|
|
ic2 = ic - n_steps;
|
|
if( ic2 < ic_start )
|
|
ic2 = ic2 - ic_start + ic_end + 1;
|
|
xf = poly->GetX( ic2 );
|
|
yf = poly->GetY( ic2 );
|
|
if( xf == arc_xf && yf == arc_yf )
|
|
{
|
|
// arc from ic2 to ic
|
|
bFound = true;
|
|
arc_start = ic2;
|
|
arc_end = ic;
|
|
style = 3 - style;
|
|
}
|
|
}
|
|
if( bFound )
|
|
{
|
|
poly->side_style[arc_start] = style;
|
|
|
|
// mark corners for deletion from arc_start+1 to arc_end-1
|
|
for( int i = arc_start + 1; i!=arc_end; )
|
|
{
|
|
if( i > ic_end )
|
|
i = ic_start;
|
|
poly->SetUtility( i, 1 );
|
|
if( i == ic_end )
|
|
i = ic_start;
|
|
else
|
|
i++;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
if( bFound )
|
|
break;
|
|
}
|
|
}
|
|
if( bFound )
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( bFound )
|
|
(*arc_array)[iarc].bFound = true;
|
|
}
|
|
|
|
// now delete all marked corners
|
|
for( int ip = 0; ip<n_polys; ip++ )
|
|
{
|
|
if( ip == 0 )
|
|
poly = this;
|
|
else
|
|
poly = (*pa)[ip - 1];
|
|
for( int ic = poly->GetNumCorners() - 1; ic>=0; ic-- )
|
|
{
|
|
if( poly->GetUtility( ic ) )
|
|
poly->DeleteCorner( ic, false );
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// initialize new polyline
|
|
// set layer, width, selection box size, starting point, id and pointer
|
|
//
|
|
// if sel_box = 0, don't create selection elements at all
|
|
//
|
|
// if polyline is board outline, enter with:
|
|
// id.type = ID_BOARD
|
|
// id.st = ID_BOARD_OUTLINE
|
|
// id.i = 0
|
|
// ptr = NULL
|
|
//
|
|
// if polyline is copper area, enter with:
|
|
// id.type = ID_NET;
|
|
// id.st = ID_AREA
|
|
// id.i = index to area
|
|
// ptr = pointer to net
|
|
//
|
|
void CPolyLine::Start( int layer, int x, int y, int hatch )
|
|
{
|
|
m_layer = layer;
|
|
m_HatchStyle = hatch;
|
|
CPolyPt poly_pt( x, y );
|
|
poly_pt.end_contour = false;
|
|
|
|
corner.push_back( poly_pt );
|
|
side_style.push_back( 0 );
|
|
}
|
|
|
|
|
|
// add a corner to unclosed polyline
|
|
//
|
|
void CPolyLine::AppendCorner( int x, int y, int style, bool bDraw )
|
|
{
|
|
UnHatch();
|
|
CPolyPt poly_pt( x, y );
|
|
poly_pt.end_contour = false;
|
|
|
|
// add entries for new corner and side
|
|
corner.push_back( poly_pt );
|
|
side_style.push_back( style );
|
|
if( corner.size() > 0 && !corner[corner.size() - 1].end_contour )
|
|
side_style[corner.size() - 1] = style;
|
|
if( bDraw )
|
|
Hatch();
|
|
}
|
|
|
|
|
|
// close last polyline contour
|
|
//
|
|
void CPolyLine::Close( int style, bool bDraw )
|
|
{
|
|
if( GetClosed() )
|
|
{
|
|
wxASSERT( 0 );
|
|
}
|
|
UnHatch();
|
|
side_style[corner.size() - 1] = style;
|
|
corner[corner.size() - 1].end_contour = true;
|
|
if( bDraw )
|
|
Hatch();
|
|
}
|
|
|
|
|
|
// move corner of polyline
|
|
//
|
|
void CPolyLine::MoveCorner( int ic, int x, int y )
|
|
{
|
|
UnHatch();
|
|
corner[ic].x = x;
|
|
corner[ic].y = y;
|
|
Hatch();
|
|
}
|
|
|
|
|
|
// delete corner and adjust arrays
|
|
//
|
|
void CPolyLine::DeleteCorner( int ic, bool bDraw )
|
|
{
|
|
UnHatch();
|
|
int icont = GetContour( ic );
|
|
int istart = GetContourStart( icont );
|
|
int iend = GetContourEnd( icont );
|
|
bool bClosed = icont < GetNumContours() - 1 || GetClosed();
|
|
|
|
if( !bClosed )
|
|
{
|
|
// open contour, must be last contour
|
|
corner.erase( corner.begin() + ic );
|
|
|
|
if( ic != istart )
|
|
side_style.erase( side_style.begin() + ic - 1 );
|
|
}
|
|
else
|
|
{
|
|
// closed contour
|
|
corner.erase( corner.begin() + ic );
|
|
side_style.erase( side_style.begin() + ic );
|
|
if( ic == iend )
|
|
corner[ic - 1].end_contour = true;
|
|
}
|
|
if( bClosed && GetContourSize( icont ) < 3 )
|
|
{
|
|
// delete the entire contour
|
|
RemoveContour( icont );
|
|
}
|
|
if( bDraw )
|
|
Hatch();
|
|
}
|
|
|
|
|
|
/******************************************/
|
|
void CPolyLine::RemoveContour( int icont )
|
|
/******************************************/
|
|
|
|
/**
|
|
* Function RemoveContour
|
|
* @param icont = contour number to remove
|
|
* remove a contour only if there is more than 1 contour
|
|
*/
|
|
{
|
|
UnHatch();
|
|
int istart = GetContourStart( icont );
|
|
int iend = GetContourEnd( icont );
|
|
|
|
if( icont == 0 && GetNumContours() == 1 )
|
|
{
|
|
// remove the only contour
|
|
wxASSERT( 0 );
|
|
}
|
|
else if( icont == GetNumContours() - 1 )
|
|
{
|
|
// remove last contour
|
|
corner.erase( corner.begin() + istart, corner.end() );
|
|
side_style.erase( side_style.begin() + istart, side_style.end() );
|
|
}
|
|
else
|
|
{
|
|
// remove closed contour
|
|
for( int ic = iend; ic>=istart; ic-- )
|
|
{
|
|
corner.erase( corner.begin() + ic );
|
|
side_style.erase( side_style.begin() + ic );
|
|
}
|
|
}
|
|
Hatch();
|
|
}
|
|
|
|
|
|
CPolyLine* CPolyLine::Chamfer( unsigned int aDistance )
|
|
{
|
|
CPolyLine* newPoly = new CPolyLine;
|
|
|
|
if( !aDistance )
|
|
{
|
|
newPoly->Copy( this );
|
|
return newPoly;
|
|
}
|
|
|
|
for( int contour = 0; contour < GetNumContours(); contour++ )
|
|
{
|
|
unsigned int startIndex = GetContourStart( contour );
|
|
unsigned int endIndex = GetContourEnd( contour );
|
|
|
|
for( unsigned int index = startIndex; index <= endIndex; index++ )
|
|
{
|
|
int x1, y1, nx, ny;
|
|
long long xa, ya, xb, yb;
|
|
|
|
x1 = corner[index].x;
|
|
y1 = corner[index].y;
|
|
|
|
if( index == startIndex )
|
|
{
|
|
xa = corner[endIndex].x - x1;
|
|
ya = corner[endIndex].y - y1;
|
|
}
|
|
else
|
|
{
|
|
xa = corner[index-1].x - x1;
|
|
ya = corner[index-1].y - y1;
|
|
}
|
|
|
|
if( index == endIndex )
|
|
{
|
|
xb = corner[startIndex].x - x1;
|
|
yb = corner[startIndex].y - y1;
|
|
}
|
|
else
|
|
{
|
|
xb = corner[index+1].x - x1;
|
|
yb = corner[index+1].y - y1;
|
|
}
|
|
|
|
unsigned int lena = (unsigned int)sqrt( (double)(xa*xa + ya*ya) );
|
|
unsigned int lenb = (unsigned int)sqrt( (double)(xb*xb + yb*yb) );
|
|
unsigned int distance = aDistance;
|
|
|
|
// Chamfer one half of an edge at most
|
|
if( 0.5*lena < distance )
|
|
distance = (unsigned int)(0.5*(double)lena);
|
|
|
|
if( 0.5*lenb < distance )
|
|
distance = (unsigned int)(0.5*(double)lenb);
|
|
|
|
nx = (int) ( (double) (distance*xa)/sqrt( (double) (xa*xa + ya*ya) ) );
|
|
ny = (int) ( (double) (distance*ya)/sqrt( (double) (xa*xa + ya*ya) ) );
|
|
|
|
if( index == startIndex )
|
|
newPoly->Start( GetLayer(), x1 + nx, y1 + ny, GetHatchStyle() );
|
|
else
|
|
newPoly->AppendCorner( x1 + nx, y1 + ny );
|
|
|
|
nx = (int) ( (double) (distance*xb)/sqrt( (double) (xb*xb + yb*yb) ) );
|
|
ny = (int) ( (double) (distance*yb)/sqrt( (double) (xb*xb + yb*yb) ) );
|
|
newPoly->AppendCorner( x1 + nx, y1 + ny );
|
|
}
|
|
newPoly->Close();
|
|
}
|
|
|
|
return newPoly;
|
|
}
|
|
|
|
|
|
CPolyLine* CPolyLine::Fillet( unsigned int aRadius, unsigned int aSegments )
|
|
{
|
|
CPolyLine* newPoly = new CPolyLine;
|
|
|
|
if( !aRadius )
|
|
{
|
|
newPoly->Copy( this );
|
|
return newPoly;
|
|
}
|
|
|
|
for( int contour = 0; contour < GetNumContours(); contour++ )
|
|
{
|
|
unsigned int startIndex = GetContourStart( contour );
|
|
unsigned int endIndex = GetContourEnd( contour );
|
|
|
|
for( unsigned int index = startIndex; index <= endIndex; index++ )
|
|
{
|
|
|
|
int x1, y1; // Current vertex
|
|
long long xa, ya; // Previous vertex
|
|
long long xb, yb; // Next vertex
|
|
double nx, ny;
|
|
|
|
x1 = corner[index].x;
|
|
y1 = corner[index].y;
|
|
|
|
if( index == startIndex )
|
|
{
|
|
xa = corner[endIndex].x - x1;
|
|
ya = corner[endIndex].y - y1;
|
|
}
|
|
else
|
|
{
|
|
xa = corner[index-1].x - x1;
|
|
ya = corner[index-1].y - y1;
|
|
}
|
|
|
|
if( index == endIndex )
|
|
{
|
|
xb = corner[startIndex].x - x1;
|
|
yb = corner[startIndex].y - y1;
|
|
}
|
|
else
|
|
{
|
|
xb = corner[index+1].x - x1;
|
|
yb = corner[index+1].y - y1;
|
|
}
|
|
|
|
double lena = sqrt( (double) (xa*xa + ya*ya) );
|
|
double lenb = sqrt( (double) (xb*xb + yb*yb) );
|
|
double cosine = ( xa*xb + ya*yb )/( lena*lenb );
|
|
|
|
unsigned int radius = aRadius;
|
|
double denom = sqrt( 2.0/( 1+cosine )-1 );
|
|
|
|
// Limit rounding distance to one half of an edge
|
|
if( 0.5*lena*denom < radius )
|
|
radius = (unsigned int)(0.5*lena*denom);
|
|
|
|
if( 0.5*lenb*denom < radius )
|
|
radius = (unsigned int)(0.5*lenb*denom);
|
|
|
|
// Calculate fillet arc absolute center point (xc, yx)
|
|
double k = radius / sqrt( .5*( 1-cosine ) );
|
|
double lenab = sqrt( ( xa/lena + xb/lenb )*( xa/lena + xb/lenb ) +
|
|
( ya/lena + yb/lenb )*( ya/lena + yb/lenb ) );
|
|
double xc = x1 + k*( xa/lena + xb/lenb )/lenab;
|
|
double yc = y1 + k*( ya/lena + yb/lenb )/lenab;
|
|
|
|
// Calculate arc start and end vectors
|
|
k = radius / sqrt( 2/( 1+cosine )-1 );
|
|
double xs = x1 + k*xa/lena - xc;
|
|
double ys = y1 + k*ya/lena - yc;
|
|
double xe = x1 + k*xb/lenb - xc;
|
|
double ye = y1 + k*yb/lenb - yc;
|
|
|
|
// Cosine of arc angle
|
|
double argument = ( xs*xe + ys*ye ) / ( radius*radius );
|
|
|
|
if( argument < -1 ) // Just in case...
|
|
argument = -1;
|
|
else if( argument > 1 )
|
|
argument = 1;
|
|
|
|
double arcAngle = acos( argument );
|
|
|
|
// Calculate the number of segments
|
|
double tempSegments = (double)aSegments * ( arcAngle / ( 2*M_PI ) );
|
|
|
|
if( tempSegments - (int)tempSegments > 0 )
|
|
tempSegments++;
|
|
unsigned int segments = (unsigned int) tempSegments;
|
|
|
|
double deltaAngle = arcAngle / segments;
|
|
double startAngle = atan2( -ys, xs );
|
|
|
|
// Flip arc for inner corners
|
|
if( xa*yb - ya*xb <= 0 )
|
|
deltaAngle *= -1;
|
|
|
|
nx = xc + xs + 0.5;
|
|
ny = yc + ys + 0.5;
|
|
if( index == startIndex )
|
|
newPoly->Start( GetLayer(), (int)nx, (int)ny, GetHatchStyle() );
|
|
else
|
|
newPoly->AppendCorner( (int)nx, (int)ny );
|
|
|
|
unsigned int nVertices = 0;
|
|
for( unsigned int j = 0; j < segments; j++ )
|
|
{
|
|
nx = xc + cos( startAngle + (j+1)*deltaAngle )*radius + 0.5;
|
|
ny = yc - sin( startAngle + (j+1)*deltaAngle )*radius + 0.5;
|
|
newPoly->AppendCorner( (int)nx, (int)ny );
|
|
nVertices++;
|
|
}
|
|
}
|
|
newPoly->Close();
|
|
}
|
|
return newPoly;
|
|
}
|
|
|
|
|
|
/******************************************/
|
|
void CPolyLine::RemoveAllContours( void )
|
|
/******************************************/
|
|
|
|
/**
|
|
* function RemoveAllContours
|
|
* removes all corners from the lists.
|
|
* Others params are not chnaged
|
|
*/
|
|
{
|
|
corner.clear();
|
|
side_style.clear();
|
|
}
|
|
|
|
|
|
/**
|
|
* Function InsertCorner
|
|
* insert a new corner between two existing corners
|
|
* @param ic = index for the insertion point: the corner is inserted AFTER ic
|
|
* @param x, y = coordinates corner to insert
|
|
*/
|
|
void CPolyLine::InsertCorner( int ic, int x, int y )
|
|
{
|
|
UnHatch();
|
|
if( (unsigned) (ic) >= corner.size() )
|
|
{
|
|
corner.push_back( CPolyPt( x, y ) );
|
|
side_style.push_back( STRAIGHT );
|
|
}
|
|
else
|
|
{
|
|
corner.insert( corner.begin() + ic + 1, CPolyPt( x, y ) );
|
|
side_style.insert( side_style.begin() + ic + 1, STRAIGHT );
|
|
}
|
|
|
|
if( (unsigned) (ic + 1) < corner.size() )
|
|
{
|
|
if( corner[ic].end_contour )
|
|
{
|
|
corner[ic + 1].end_contour = true;
|
|
corner[ic].end_contour = false;
|
|
}
|
|
}
|
|
Hatch();
|
|
}
|
|
|
|
|
|
// undraw polyline by removing all graphic elements from display list
|
|
//
|
|
void CPolyLine::UnHatch()
|
|
{
|
|
m_HatchLines.clear();
|
|
}
|
|
|
|
|
|
int CPolyLine::GetX( int ic )
|
|
{
|
|
return corner[ic].x;
|
|
}
|
|
|
|
|
|
int CPolyLine::GetY( int ic )
|
|
{
|
|
return corner[ic].y;
|
|
}
|
|
|
|
|
|
int CPolyLine::GetEndContour( int ic )
|
|
{
|
|
return corner[ic].end_contour;
|
|
}
|
|
|
|
|
|
CRect CPolyLine::GetBounds()
|
|
{
|
|
CRect r = GetCornerBounds();
|
|
|
|
r.left -= m_Width / 2;
|
|
r.right += m_Width / 2;
|
|
r.bottom -= m_Width / 2;
|
|
r.top += m_Width / 2;
|
|
return r;
|
|
}
|
|
|
|
|
|
CRect CPolyLine::GetCornerBounds()
|
|
{
|
|
CRect r;
|
|
|
|
r.left = r.bottom = INT_MAX;
|
|
r.right = r.top = INT_MIN;
|
|
for( unsigned i = 0; i<corner.size(); i++ )
|
|
{
|
|
r.left = min( r.left, corner[i].x );
|
|
r.right = max( r.right, corner[i].x );
|
|
r.bottom = min( r.bottom, corner[i].y );
|
|
r.top = max( r.top, corner[i].y );
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
CRect CPolyLine::GetCornerBounds( int icont )
|
|
{
|
|
CRect r;
|
|
|
|
r.left = r.bottom = INT_MAX;
|
|
r.right = r.top = INT_MIN;
|
|
int istart = GetContourStart( icont );
|
|
int iend = GetContourEnd( icont );
|
|
for( int i = istart; i<=iend; i++ )
|
|
{
|
|
r.left = MIN( r.left, corner[i].x );
|
|
r.right = MAX( r.right, corner[i].x );
|
|
r.bottom = MIN( r.bottom, corner[i].y );
|
|
r.top = MAX( r.top, corner[i].y );
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
int CPolyLine::GetNumCorners()
|
|
{
|
|
return corner.size();
|
|
}
|
|
|
|
|
|
int CPolyLine::GetNumSides()
|
|
{
|
|
if( GetClosed() )
|
|
return corner.size();
|
|
else
|
|
return corner.size() - 1;
|
|
}
|
|
|
|
|
|
int CPolyLine::GetNumContours()
|
|
{
|
|
int ncont = 0;
|
|
|
|
if( !corner.size() )
|
|
return 0;
|
|
|
|
for( unsigned ic = 0; ic<corner.size(); ic++ )
|
|
if( corner[ic].end_contour )
|
|
ncont++;
|
|
|
|
if( !corner[corner.size() - 1].end_contour )
|
|
ncont++;
|
|
return ncont;
|
|
}
|
|
|
|
|
|
int CPolyLine::GetContour( int ic )
|
|
{
|
|
int ncont = 0;
|
|
|
|
for( int i = 0; i<ic; i++ )
|
|
{
|
|
if( corner[i].end_contour )
|
|
ncont++;
|
|
}
|
|
|
|
return ncont;
|
|
}
|
|
|
|
|
|
int CPolyLine::GetContourStart( int icont )
|
|
{
|
|
if( icont == 0 )
|
|
return 0;
|
|
|
|
int ncont = 0;
|
|
for( unsigned i = 0; i<corner.size(); i++ )
|
|
{
|
|
if( corner[i].end_contour )
|
|
{
|
|
ncont++;
|
|
if( ncont == icont )
|
|
return i + 1;
|
|
}
|
|
}
|
|
|
|
wxASSERT( 0 );
|
|
return 0;
|
|
}
|
|
|
|
|
|
int CPolyLine::GetContourEnd( int icont )
|
|
{
|
|
if( icont < 0 )
|
|
return 0;
|
|
|
|
if( icont == GetNumContours() - 1 )
|
|
return corner.size() - 1;
|
|
|
|
int ncont = 0;
|
|
for( unsigned i = 0; i<corner.size(); i++ )
|
|
{
|
|
if( corner[i].end_contour )
|
|
{
|
|
if( ncont == icont )
|
|
return i;
|
|
ncont++;
|
|
}
|
|
}
|
|
|
|
wxASSERT( 0 );
|
|
return 0;
|
|
}
|
|
|
|
|
|
int CPolyLine::GetContourSize( int icont )
|
|
{
|
|
return GetContourEnd( icont ) - GetContourStart( icont ) + 1;
|
|
}
|
|
|
|
|
|
void CPolyLine::SetSideStyle( int is, int style )
|
|
{
|
|
UnHatch();
|
|
CPoint p1, p2;
|
|
if( is == (int) (corner.size() - 1) )
|
|
{
|
|
p1.x = corner[corner.size() - 1].x;
|
|
p1.y = corner[corner.size() - 1].y;
|
|
p2.x = corner[0].x;
|
|
p2.y = corner[0].y;
|
|
}
|
|
else
|
|
{
|
|
p1.x = corner[is].x;
|
|
p1.y = corner[is].y;
|
|
p2.x = corner[is + 1].x;
|
|
p2.y = corner[is + 1].y;
|
|
}
|
|
if( p1.x == p2.x || p1.y == p2.y )
|
|
side_style[is] = STRAIGHT;
|
|
else
|
|
side_style[is] = style;
|
|
Hatch();
|
|
}
|
|
|
|
|
|
int CPolyLine::GetSideStyle( int is )
|
|
{
|
|
return side_style[is];
|
|
}
|
|
|
|
|
|
int CPolyLine::GetClosed()
|
|
{
|
|
if( corner.size() == 0 )
|
|
return 0;
|
|
else
|
|
return corner[corner.size() - 1].end_contour;
|
|
}
|
|
|
|
|
|
// Creates hatch lines inside the outline of the complex polygon
|
|
//
|
|
// sort function used in ::Hatch to sort points by descending CPoint.x values
|
|
bool sort_ends_by_descending_X( const CPoint& ref, const CPoint& tst )
|
|
{
|
|
return tst.x < ref.x;
|
|
}
|
|
void CPolyLine::Hatch()
|
|
{
|
|
m_HatchLines.clear();
|
|
|
|
if( m_HatchStyle == NO_HATCH )
|
|
return;
|
|
|
|
if( !GetClosed() ) // If not closed, the poly is beeing created and not finalised. Not not hatch
|
|
return;
|
|
|
|
// define range for hatch lines
|
|
int min_x = corner[0].x;
|
|
int max_x = corner[0].x;
|
|
int min_y = corner[0].y;
|
|
int max_y = corner[0].y;
|
|
for( unsigned ic = 1; ic < corner.size(); ic++ )
|
|
{
|
|
if( corner[ic].x < min_x )
|
|
min_x = corner[ic].x;
|
|
if( corner[ic].x > max_x )
|
|
max_x = corner[ic].x;
|
|
if( corner[ic].y < min_y )
|
|
min_y = corner[ic].y;
|
|
if( corner[ic].y > max_y )
|
|
max_y = corner[ic].y;
|
|
}
|
|
|
|
// Calculate spacing betwwen 2 hatch lines
|
|
int spacing;
|
|
if( m_HatchStyle == DIAGONAL_EDGE )
|
|
spacing = 20 * PCBU_PER_MIL;
|
|
else
|
|
spacing = 50 * PCBU_PER_MIL;
|
|
// set the "lenght" of hatch lines (the lenght on horizontal axis)
|
|
double hatch_line_len = 20 * PCBU_PER_MIL;
|
|
|
|
// To have a better look, give a slope depending on the layer
|
|
int layer = GetLayer();
|
|
int slope_flag = (layer & 1) ? 1 : -1; // 1 or -1
|
|
double slope = 0.707106 * slope_flag; // 45 degrees slope
|
|
int max_a, min_a;
|
|
if( slope_flag == 1 )
|
|
{
|
|
max_a = (int) (max_y - slope * min_x);
|
|
min_a = (int) (min_y - slope * max_x);
|
|
}
|
|
else
|
|
{
|
|
max_a = (int) (max_y - slope * max_x);
|
|
min_a = (int) (min_y - slope * min_x);
|
|
}
|
|
min_a = (min_a / spacing) * spacing;
|
|
|
|
// calculate an offset depending on layer number,
|
|
// for a better look of hatches on a multilayer board
|
|
int offset = (layer * 7) / 8;
|
|
min_a += offset;
|
|
|
|
// now calculate and draw hatch lines
|
|
int nc = corner.size();
|
|
|
|
// loop through hatch lines
|
|
#define MAXPTS 200 // Usually we store only few values
|
|
// depending on the compexity of the zone outline
|
|
static std::vector <CPoint> pointbuffer;
|
|
pointbuffer.clear();
|
|
pointbuffer.reserve(MAXPTS+2);
|
|
for( int a = min_a; a < max_a; a += spacing )
|
|
{
|
|
// get intersection points for this hatch line
|
|
|
|
// Note: because we should have an even number of intersections with the
|
|
// current hatch line and the zone outline (a closed polygon,
|
|
// or a set of closed polygons), if an odd count is found
|
|
// we skip this line (should not occur)
|
|
pointbuffer.clear();
|
|
int i_start_contour = 0;
|
|
for( int ic = 0; ic<nc; ic++ )
|
|
{
|
|
double x, y, x2, y2;
|
|
int ok;
|
|
if( corner[ic].end_contour || ( ic == (int) (corner.size() - 1) ) )
|
|
{
|
|
ok = FindLineSegmentIntersection( a, slope,
|
|
corner[ic].x, corner[ic].y,
|
|
corner[i_start_contour].x,
|
|
corner[i_start_contour].y,
|
|
side_style[ic],
|
|
&x, &y, &x2, &y2 );
|
|
i_start_contour = ic + 1;
|
|
}
|
|
else
|
|
{
|
|
ok = FindLineSegmentIntersection( a, slope,
|
|
corner[ic].x, corner[ic].y,
|
|
corner[ic + 1].x, corner[ic + 1].y,
|
|
side_style[ic],
|
|
&x, &y, &x2, &y2 );
|
|
}
|
|
if( ok )
|
|
{
|
|
CPoint point( (int) x, (int) y);
|
|
pointbuffer.push_back( point );
|
|
}
|
|
if( ok == 2 )
|
|
{
|
|
CPoint point( (int) x2, (int) y2);
|
|
pointbuffer.push_back( point );
|
|
}
|
|
if( pointbuffer.size() >= MAXPTS ) // overflow
|
|
{
|
|
wxASSERT( 0 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ensure we have found an even intersection points count
|
|
// because intersections are the ends of segments
|
|
// inside the polygon(s) and a segment has 2 ends.
|
|
// if not, this is a strange case (a bug ?) so skip this hatch
|
|
if( pointbuffer.size() % 2 != 0 )
|
|
continue;
|
|
|
|
// sort points in order of descending x (if more than 2) to
|
|
// ensure the starting point and the ending point of the same segment
|
|
// are stored one just after the other.
|
|
if( pointbuffer.size() > 2 )
|
|
sort( pointbuffer.begin(), pointbuffer.end(), sort_ends_by_descending_X );
|
|
|
|
// creates lines or short segments inside the complex polygon
|
|
for( unsigned ip = 0; ip < pointbuffer.size(); ip += 2 )
|
|
{
|
|
double dx = pointbuffer[ip + 1].x - pointbuffer[ip].x;
|
|
// Push only one line for diagonal hatch,
|
|
// or for small lines < twice the line len
|
|
// else push 2 small lines
|
|
if( m_HatchStyle == DIAGONAL_FULL || fabs( dx ) < 2 * hatch_line_len )
|
|
{
|
|
m_HatchLines.push_back( CSegment( pointbuffer[ip].x,
|
|
pointbuffer[ip].y,
|
|
pointbuffer[ip + 1].x,
|
|
pointbuffer[ip + 1].y ) );
|
|
}
|
|
else
|
|
{
|
|
double dy = pointbuffer[ip + 1].y - pointbuffer[ip].y;
|
|
double slope = dy / dx;
|
|
if( dx > 0 )
|
|
dx = hatch_line_len;
|
|
else
|
|
dx = -hatch_line_len;
|
|
double x1 = pointbuffer[ip].x + dx;
|
|
double x2 = pointbuffer[ip + 1].x - dx;
|
|
double y1 = pointbuffer[ip].y + dx * slope;
|
|
double y2 = pointbuffer[ip + 1].y - dx * slope;
|
|
m_HatchLines.push_back( CSegment( pointbuffer[ip].x,
|
|
pointbuffer[ip].y,
|
|
to_int( x1 ), to_int( y1 ) ) );
|
|
m_HatchLines.push_back( CSegment( pointbuffer[ip + 1].x,
|
|
pointbuffer[ip + 1].y,
|
|
to_int( x2 ), to_int( y2 ) ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// test to see if a point is inside polyline
|
|
//
|
|
bool CPolyLine::TestPointInside( int px, int py )
|
|
{
|
|
if( !GetClosed() )
|
|
{
|
|
wxASSERT( 0 );
|
|
}
|
|
|
|
// Test all polygons.
|
|
// Since the first is the main outline, and other are hole,
|
|
// if the tested point is inside only one contour, it is inside the whole polygon
|
|
// (in fact inside the main outline, and outside all holes).
|
|
// if inside 2 contours (the main outline + an hole), it is outside the poly.
|
|
int polycount = GetNumContours();
|
|
bool inside = false;
|
|
for( int icont = 0; icont < polycount; icont++ )
|
|
{
|
|
int istart = GetContourStart( icont );
|
|
int iend = GetContourEnd( icont );
|
|
// Test this polygon:
|
|
if( TestPointInsidePolygon( corner, istart, iend, px, py) ) // test point inside the current polygon
|
|
inside = not inside;
|
|
}
|
|
|
|
return inside;
|
|
}
|
|
|
|
// copy data from another poly, but don't draw it
|
|
//
|
|
void CPolyLine::Copy( CPolyLine* src )
|
|
{
|
|
UnHatch();
|
|
m_HatchStyle = src->m_HatchStyle;
|
|
// copy corners, using vector copy
|
|
corner = src->corner;
|
|
// copy side styles, using vector copy
|
|
side_style = src->side_style;
|
|
}
|
|
|
|
|
|
/*******************************************/
|
|
bool CPolyLine::IsCutoutContour( int icont )
|
|
/*******************************************/
|
|
|
|
/*
|
|
* return true if the corner icont is inside the outline (i.e it is a hole)
|
|
*/
|
|
{
|
|
int ncont = GetContour( icont );
|
|
|
|
if( ncont == 0 ) // the first contour is the main outline, not an hole
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
void CPolyLine::MoveOrigin( int x_off, int y_off )
|
|
{
|
|
UnHatch();
|
|
for( int ic = 0; ic < GetNumCorners(); ic++ )
|
|
{
|
|
SetX( ic, GetX( ic ) + x_off );
|
|
SetY( ic, GetY( ic ) + y_off );
|
|
}
|
|
|
|
Hatch();
|
|
}
|
|
|
|
|
|
// Set various parameters:
|
|
// the calling function should UnHatch() before calling them,
|
|
// and Draw() after
|
|
//
|
|
void CPolyLine::SetX( int ic, int x )
|
|
{
|
|
corner[ic].x = x;
|
|
}
|
|
|
|
|
|
void CPolyLine::SetY( int ic, int y )
|
|
{
|
|
corner[ic].y = y;
|
|
}
|
|
|
|
|
|
void CPolyLine::SetEndContour( int ic, bool end_contour )
|
|
{
|
|
corner[ic].end_contour = end_contour;
|
|
}
|
|
|
|
|
|
void CPolyLine::AppendArc( int xi, int yi, int xf, int yf, int xc, int yc, int num )
|
|
{
|
|
// get radius
|
|
double r = sqrt( (double) (xi - xc) * (xi - xc) + (double) (yi - yc) * (yi - yc) );
|
|
|
|
// get angles of start and finish
|
|
double th_i = atan2( (double) (yi - yc), (double) (xi - xc) );
|
|
double th_f = atan2( (double) (yf - yc), (double) (xf - xc) );
|
|
double th_d = (th_f - th_i) / (num - 1);
|
|
double theta = th_i;
|
|
|
|
// generate arc
|
|
for( int ic = 0; ic<num; ic++ )
|
|
{
|
|
int x = to_int( xc + r * cos( theta ) );
|
|
int y = to_int( yc + r * sin( theta ) );
|
|
AppendCorner( x, y, STRAIGHT, 0 );
|
|
theta += th_d;
|
|
}
|
|
|
|
Close( STRAIGHT );
|
|
}
|
|
|
|
// Bezier Support
|
|
void CPolyLine::AppendBezier(int x1, int y1, int x2, int y2, int x3, int y3) {
|
|
std::vector<wxPoint> bezier_points;
|
|
|
|
bezier_points = Bezier2Poly(x1,y1,x2,y2,x3,y3);
|
|
for( unsigned int i = 0; i < bezier_points.size() ; i++)
|
|
AppendCorner( bezier_points[i].x, bezier_points[i].y);
|
|
}
|
|
|
|
void CPolyLine::AppendBezier(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4){
|
|
std::vector<wxPoint> bezier_points;
|
|
|
|
bezier_points = Bezier2Poly(x1,y1,x2,y2,x3,y3,x4,y4);
|
|
for( unsigned int i = 0; i < bezier_points.size() ; i++)
|
|
AppendCorner( bezier_points[i].x, bezier_points[i].y);
|
|
}
|
|
|