949 lines
36 KiB
C++
949 lines
36 KiB
C++
/*******************************************/
|
|
/* zones_convert_brd_items_to_polygons.cpp */
|
|
/*******************************************/
|
|
|
|
/* Functions to convert some board items to polygons
|
|
(pads, tracks ..)
|
|
This is used to calculate filled areas in copper zones.
|
|
Filled areas are the full zone area sub all polygons calculated from these items and the clearance area
|
|
*/
|
|
|
|
using namespace std;
|
|
|
|
#include <math.h>
|
|
#include <vector>
|
|
|
|
#include "fctsys.h"
|
|
#include "common.h"
|
|
#include "pcbnew.h"
|
|
#include "trigo.h"
|
|
|
|
#include "zones.h"
|
|
|
|
|
|
#include "PolyLine.h"
|
|
|
|
extern void Test_For_Copper_Island_And_Remove( BOARD* aPcb, ZONE_CONTAINER* aZone_container );
|
|
|
|
|
|
// Local Functions:
|
|
void AddTrackWithClearancePolygon( Bool_Engine* aBooleng,
|
|
TRACK& aTrack, int aClearanceValue );
|
|
void AddPadWithClearancePolygon( Bool_Engine* aBooleng, D_PAD& aPad, int aClearanceValue );
|
|
void AddThermalReliefPadPolygon( Bool_Engine* aBooleng,
|
|
D_PAD& aPad,
|
|
int aThermalGap,
|
|
int aCopperThickness, int aMinThicknessValue );
|
|
void AddRoundedEndsSegmentPolygon( Bool_Engine* aBooleng,
|
|
wxPoint aStart, wxPoint aEnd,
|
|
int aWidth );
|
|
void AddTextBoxWithClearancePolygon( Bool_Engine* aBooleng,
|
|
TEXTE_PCB* aText, int aClearanceValue );
|
|
|
|
|
|
// Local Variables:
|
|
/* how many segments are used to create a polygon from a circle: */
|
|
static int s_CircleToSegmentsCount = 16; /* default value. the real value will be changed to 32
|
|
* if g_Zone_Arc_Approximation == 1
|
|
*/
|
|
double s_Correction; /* mult coeff used to enlarge rounded and oval pads (and vias)
|
|
* because the segment approximation for arcs and circles
|
|
* create a smaller gap than a true circle
|
|
*/
|
|
|
|
/** function AddClearanceAreasPolygonsToPolysList
|
|
* Supports a min thickness area constraint.
|
|
* Add non copper areas polygons (pads and tracks with clearence)
|
|
* to the filled copper area found
|
|
* in BuildFilledPolysListData after calculating filled areas in a zone
|
|
* Non filled copper areas are pads and track and their clearance areas
|
|
* The filled copper area must be computed just before.
|
|
* BuildFilledPolysListData() call this function just after creating the
|
|
* filled copper area polygon (without clearence areas
|
|
* to do that this function:
|
|
* 1 - creates a Bool_Engine,with option: holes are linked to outer contours by double overlapping segments
|
|
* this means the created polygons have no holes (hole are linked to outer outline by double overlapped segments
|
|
* and are therefore compatible with draw functions (DC draw polygons and Gerber or PS outputs)
|
|
* 2 - Add the main outline (zone outline) in group A
|
|
* 3 - Creates a correction using BOOL_CORRECTION operation to inflate the resulting area
|
|
* with m_ZoneMinThickness/2 value.
|
|
* The result is areas with a margin of m_ZoneMinThickness/2
|
|
* When drawing outline with segments having a thickness of m_ZoneMinThickness, the outlines wilm
|
|
* match exactly the initial outlines
|
|
* 4 - recreates the same Bool_Engine, with no correction
|
|
* 3 - Add the main outline (zone outline) in group A
|
|
* 4 - Add all non filled areas (pads, tracks) in group B with a clearance of m_Clearance + m_ZoneMinThickness/2
|
|
* 5 - calculates the polygon A - B
|
|
* 6 - put resulting list of polygons (filled areas) in m_FilledPolysList
|
|
* 7 - Remove insulated copper islands
|
|
*/
|
|
void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb )
|
|
{
|
|
// Set the number of segments in arc approximations
|
|
if( m_ArcToSegmentsCount == 32 )
|
|
s_CircleToSegmentsCount = 32;
|
|
else
|
|
s_CircleToSegmentsCount = 16;
|
|
|
|
/* calculates the coeff to compensate radius reduction of holes clearance
|
|
* due to the segment approx.
|
|
* For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2)
|
|
* s_Correction is 1 /cos( PI/s_CircleToSegmentsCount )
|
|
*/
|
|
s_Correction = 1.0 / cos( 3.14159265 / s_CircleToSegmentsCount );
|
|
|
|
/* Uses a kbool engine to add holes in the m_FilledPolysList polygon.
|
|
* Because this function is called just after creating the m_FilledPolysList,
|
|
* only one polygon is in list.
|
|
* (initial holes in zones are linked into outer contours by double overlapping segments).
|
|
* because after adding holes, many polygons could be exist in this list.
|
|
*/
|
|
|
|
Bool_Engine* booleng = new Bool_Engine();
|
|
ArmBoolEng( booleng, true );
|
|
|
|
/* First, Add the main polygon (i.e. the filled area using only one outline)
|
|
* in GroupA in Bool_Engine to do a BOOL_CORRECTION operation
|
|
* to reserve a m_ZoneMinThickness/2 margind around the outlines and holes
|
|
* the margind will be filled when redraw outilnes with segments having a whidth set to
|
|
* m_ZoneMinThickness
|
|
* so m_ZoneMinThickness is the min thickness of the filled zones areas
|
|
*/
|
|
unsigned corners_count = m_FilledPolysList.size();
|
|
unsigned ic = 0;
|
|
if( booleng->StartPolygonAdd( GROUP_A ) )
|
|
{
|
|
for( ; ic < corners_count; ic++ )
|
|
{
|
|
CPolyPt* corner = &m_FilledPolysList[ic];
|
|
booleng->AddPoint( corner->x, corner->y );
|
|
if( corner->end_contour )
|
|
break;
|
|
}
|
|
|
|
booleng->EndPolygonAdd();
|
|
}
|
|
booleng->SetCorrectionFactor( (double) -m_ZoneMinThickness/2 );
|
|
booleng->Do_Operation( BOOL_CORRECTION );
|
|
|
|
/* No copy the new outline in m_FilledPolysList */
|
|
m_FilledPolysList.clear();
|
|
while( booleng->StartPolygonGet() )
|
|
{
|
|
CPolyPt corner( 0, 0, false );
|
|
while( booleng->PolygonHasMorePoints() )
|
|
{
|
|
corner.x = (int) booleng->GetPolygonXPoint();
|
|
corner.y = (int) booleng->GetPolygonYPoint();
|
|
corner.end_contour = false;
|
|
m_FilledPolysList.push_back( corner );
|
|
}
|
|
|
|
corner.end_contour = true;
|
|
m_FilledPolysList.pop_back();
|
|
m_FilledPolysList.push_back( corner );
|
|
booleng->EndPolygonGet();
|
|
}
|
|
delete booleng;
|
|
|
|
/* Second, Add the main (corrected) polygon (i.e. the filled area using only one outline)
|
|
* in GroupA in Bool_Engine to do a BOOL_A_SUB_B operation
|
|
* All areas to remove will be put in GroupB in Bool_Engine
|
|
*/
|
|
booleng = new Bool_Engine();
|
|
ArmBoolEng( booleng, true );
|
|
|
|
/* Add the main corrected polygon (i.e. the filled area using only one outline)
|
|
* in GroupA in Bool_Engine
|
|
*/
|
|
corners_count = m_FilledPolysList.size();
|
|
ic = 0;
|
|
if( booleng->StartPolygonAdd( GROUP_A ) )
|
|
{
|
|
for( ; ic < corners_count; ic++ )
|
|
{
|
|
CPolyPt* corner = &m_FilledPolysList[ic];
|
|
booleng->AddPoint( corner->x, corner->y );
|
|
if( corner->end_contour )
|
|
break;
|
|
}
|
|
|
|
booleng->EndPolygonAdd();
|
|
}
|
|
|
|
|
|
// Calculates the clearance value that meet DRC requirements
|
|
int clearance = max( m_ZoneClearance, g_DesignSettings.m_TrackClearence );
|
|
clearance += m_ZoneMinThickness/2;
|
|
|
|
|
|
/* Add holes (i.e. tracks and pads areas as polygons outlines)
|
|
* in GroupB in Bool_Engine
|
|
*/
|
|
/* items ouside the zone bounding box are skipped */
|
|
EDA_Rect item_boundingbox;
|
|
EDA_Rect zone_boundingbox = GetBoundingBox();
|
|
zone_boundingbox.Inflate( m_ZoneClearance, clearance );
|
|
|
|
/*
|
|
* First : Add pads
|
|
*/
|
|
for( MODULE* module = aPcb->m_Modules; module; module = module->Next() )
|
|
{
|
|
for( D_PAD* pad = module->m_Pads; pad != NULL; pad = pad->Next() )
|
|
{
|
|
if( !pad->IsOnLayer( GetLayer() ) )
|
|
continue;
|
|
|
|
if( pad->GetNet() != GetNet() )
|
|
{
|
|
item_boundingbox = pad->GetBoundingBox();
|
|
if( item_boundingbox.Intersects( zone_boundingbox ) )
|
|
AddPadWithClearancePolygon( booleng, *pad, clearance );
|
|
continue;
|
|
}
|
|
|
|
switch( m_PadOption )
|
|
{
|
|
case PAD_NOT_IN_ZONE:
|
|
item_boundingbox = pad->GetBoundingBox();
|
|
if( item_boundingbox.Intersects( zone_boundingbox ) )
|
|
AddPadWithClearancePolygon( booleng, *pad, clearance );
|
|
break;
|
|
|
|
case THERMAL_PAD:
|
|
item_boundingbox = pad->GetBoundingBox();
|
|
item_boundingbox.Inflate( m_ThermalReliefGapValue, m_ThermalReliefGapValue );
|
|
if( item_boundingbox.Intersects( zone_boundingbox ) )
|
|
AddThermalReliefPadPolygon( booleng, *pad,
|
|
m_ThermalReliefGapValue,
|
|
m_ThermalReliefCopperBridgeValue,
|
|
m_ZoneMinThickness);
|
|
break;
|
|
|
|
case PAD_IN_ZONE:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add holes (i.e. tracks and pads areas as polygons outlines)
|
|
* in GroupB in Bool_Engine
|
|
* Next : Add tracks and vias
|
|
*/
|
|
for( TRACK* track = aPcb->m_Track; track; track = track->Next() )
|
|
{
|
|
if( !track->IsOnLayer( GetLayer() ) )
|
|
continue;
|
|
if( track->GetNet() == GetNet() )
|
|
continue;
|
|
item_boundingbox = track->GetBoundingBox();
|
|
if( item_boundingbox.Intersects( zone_boundingbox ) )
|
|
AddTrackWithClearancePolygon( booleng, *track, clearance );
|
|
}
|
|
|
|
// Draw graphic items (copper texts) and board edges
|
|
// zone clearance is used here regardless of the g_DesignSettings.m_TrackClearence value
|
|
for( BOARD_ITEM* item = aPcb->m_Drawings; item; item = item->Next() )
|
|
{
|
|
if( item->GetLayer() != GetLayer() && item->GetLayer() != EDGE_N )
|
|
continue;
|
|
|
|
switch( item->Type() )
|
|
{
|
|
case TYPEDRAWSEGMENT:
|
|
AddRoundedEndsSegmentPolygon( booleng,
|
|
( (DRAWSEGMENT*) item )->m_Start,
|
|
( (DRAWSEGMENT*) item )->m_End,
|
|
( (DRAWSEGMENT*) item )->m_Width + (2 * m_ZoneClearance) );
|
|
break;
|
|
|
|
case TYPETEXTE:
|
|
if( ( (TEXTE_PCB*) item )->GetLength() == 0 )
|
|
break;
|
|
AddTextBoxWithClearancePolygon( booleng, (TEXTE_PCB*) item, m_ZoneClearance );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* compute copper areas */
|
|
booleng->Do_Operation( BOOL_A_SUB_B );
|
|
|
|
/* put these areas in m_FilledPolysList */
|
|
m_FilledPolysList.clear();
|
|
while( booleng->StartPolygonGet() )
|
|
{
|
|
CPolyPt corner( 0, 0, false );
|
|
while( booleng->PolygonHasMorePoints() )
|
|
{
|
|
corner.x = (int) booleng->GetPolygonXPoint();
|
|
corner.y = (int) booleng->GetPolygonYPoint();
|
|
corner.end_contour = false;
|
|
m_FilledPolysList.push_back( corner );
|
|
}
|
|
|
|
corner.end_contour = true;
|
|
m_FilledPolysList.pop_back();
|
|
m_FilledPolysList.push_back( corner );
|
|
booleng->EndPolygonGet();
|
|
}
|
|
|
|
delete booleng;
|
|
|
|
// Remove insulated islands:
|
|
if( GetNet() > 0 )
|
|
Test_For_Copper_Island_And_Remove_Insulated_Islands( aPcb );
|
|
}
|
|
|
|
|
|
/** Function AddPadPolygonWithPadClearance
|
|
* Add a polygon cutout for a pad in a zone area
|
|
* Convert arcs and circles to multiple straight lines
|
|
*/
|
|
void AddPadWithClearancePolygon( Bool_Engine* aBooleng,
|
|
D_PAD& aPad, int aClearanceValue )
|
|
{
|
|
if( aBooleng->StartPolygonAdd( GROUP_B ) == 0 )
|
|
return;
|
|
wxPoint corner_position;
|
|
int ii, angle;
|
|
int dx = (aPad.m_Size.x / 2) + aClearanceValue;
|
|
int dy = (aPad.m_Size.y / 2) + aClearanceValue;
|
|
|
|
int delta = 3600 / s_CircleToSegmentsCount; // rot angle in 0.1 degree
|
|
wxPoint PadShapePos = aPad.ReturnShapePos(); /* Note: for pad having a shape offset,
|
|
* the pad position is NOT the shape position */
|
|
|
|
switch( aPad.m_PadShape )
|
|
{
|
|
case PAD_CIRCLE:
|
|
dx = (int) (dx * s_Correction);
|
|
for( ii = 0; ii < s_CircleToSegmentsCount; ii++ )
|
|
{
|
|
corner_position = wxPoint( dx, 0 );
|
|
angle = ii * delta;
|
|
RotatePoint( &corner_position, angle );
|
|
corner_position += PadShapePos;
|
|
aBooleng->AddPoint( corner_position.x, corner_position.y );
|
|
}
|
|
|
|
break;
|
|
|
|
case PAD_OVAL:
|
|
angle = aPad.m_Orient;
|
|
if( dy > dx ) // Oval pad X/Y ratio for choosing translation axles
|
|
{
|
|
dy = (int) (dy * s_Correction);
|
|
int angle_pg; // Polygon angle
|
|
wxPoint shape_offset = wxPoint( 0, (dy - dx) );
|
|
RotatePoint( &shape_offset, angle ); // Rotating shape offset vector with component
|
|
|
|
for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ ) // Half circle end cap...
|
|
{
|
|
corner_position = wxPoint( dx, 0 ); // Coordinate translation +dx
|
|
RotatePoint( &corner_position, angle );
|
|
angle_pg = ii * delta;
|
|
RotatePoint( &corner_position, angle_pg );
|
|
corner_position += PadShapePos - shape_offset;
|
|
aBooleng->AddPoint( corner_position.x, corner_position.y );
|
|
}
|
|
|
|
for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ ) // Second half circle end cap...
|
|
{
|
|
corner_position = wxPoint( -dx, 0 ); // Coordinate translation -dx
|
|
RotatePoint( &corner_position, angle );
|
|
angle_pg = ii * delta;
|
|
RotatePoint( &corner_position, angle_pg );
|
|
corner_position += PadShapePos + shape_offset;
|
|
aBooleng->AddPoint( corner_position.x, corner_position.y );
|
|
}
|
|
|
|
break;
|
|
}
|
|
else //if( dy <= dx )
|
|
{
|
|
dx = (int) (dx * s_Correction);
|
|
int angle_pg; // Polygon angle
|
|
wxPoint shape_offset = wxPoint( (dy - dx), 0 );
|
|
RotatePoint( &shape_offset, angle );
|
|
|
|
for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ )
|
|
{
|
|
corner_position = wxPoint( 0, dy );
|
|
RotatePoint( &corner_position, angle );
|
|
angle_pg = ii * delta;
|
|
RotatePoint( &corner_position, angle_pg );
|
|
corner_position += PadShapePos - shape_offset;
|
|
aBooleng->AddPoint( corner_position.x, corner_position.y );
|
|
}
|
|
|
|
for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ )
|
|
{
|
|
corner_position = wxPoint( 0, -dy );
|
|
RotatePoint( &corner_position, angle );
|
|
angle_pg = ii * delta;
|
|
RotatePoint( &corner_position, angle_pg );
|
|
corner_position += PadShapePos + shape_offset;
|
|
aBooleng->AddPoint( corner_position.x, corner_position.y );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PAD_RECT:
|
|
angle = aPad.m_Orient;
|
|
corner_position = wxPoint( -dx, -dy );
|
|
RotatePoint( &corner_position, angle );
|
|
corner_position += PadShapePos;
|
|
aBooleng->AddPoint( corner_position.x, corner_position.y );
|
|
|
|
corner_position = wxPoint( -dx, +dy );
|
|
RotatePoint( &corner_position, angle );
|
|
corner_position += aPad.ReturnShapePos();
|
|
aBooleng->AddPoint( corner_position.x, corner_position.y );
|
|
|
|
corner_position = wxPoint( +dx, +dy );
|
|
RotatePoint( &corner_position, angle );
|
|
corner_position += PadShapePos;
|
|
aBooleng->AddPoint( corner_position.x, corner_position.y );
|
|
|
|
corner_position = wxPoint( +dx, -dy );
|
|
RotatePoint( &corner_position, angle );
|
|
corner_position += PadShapePos;
|
|
aBooleng->AddPoint( corner_position.x, corner_position.y );
|
|
break;
|
|
}
|
|
|
|
aBooleng->EndPolygonAdd();
|
|
}
|
|
|
|
|
|
/** function AddThermalReliefPadPolygon
|
|
* Add holes around a pad to create a thermal relief
|
|
* copper tickness is min (dx/2, aCopperWitdh) or min (dy/2, aCopperWitdh)
|
|
* @param aBooleng = current Bool_Engine
|
|
* @param aPad = the current pad used to create the thermal shape
|
|
* @param aThermalGap = gap in thermal shape
|
|
* @param aMinThicknessValue = min copper thickness allowed
|
|
*/
|
|
|
|
/* thermal reliefs are created as 4 polygons.
|
|
* each corner of a polygon if calculated for a pad at position 0, 0, orient 0,
|
|
* and then moved and rotated acroding to the pad position and orientation
|
|
*/
|
|
|
|
/* WARNING:
|
|
* When Kbool calculates the filled areas :
|
|
* i.e when substarcting holes (thermal shapes) to the full zone area
|
|
* under certains circumstances kboll drop some holes.
|
|
* These circumstances are:
|
|
* some identical holes (same thermal shape and size) are *exactly* on the same vertical line
|
|
* And
|
|
* nothing else between holes
|
|
* And
|
|
* angles less than 90 deg between 2 consecutive lines in hole outline
|
|
* And a hole above the identical holes
|
|
*
|
|
* In fact, it is easy to find these conditions in pad arrays.
|
|
* So to avoid this, the workaround is do not use holes outlines that include
|
|
* angles less than 90 deg between 2 consecutive lines
|
|
* this is made in round and oblong thermal reliefs
|
|
*
|
|
* Note: polygons are drawm using outlines witk a thickness = aMinThicknessValue
|
|
* so shapes must keep in account this outline thickness
|
|
*/
|
|
void AddThermalReliefPadPolygon( Bool_Engine* aBooleng,
|
|
D_PAD& aPad,
|
|
int aThermalGap,
|
|
int aCopperThickness, int aMinThicknessValue )
|
|
{
|
|
wxPoint corner, corner_end;
|
|
wxPoint PadShapePos = aPad.ReturnShapePos(); /* Note: for pad having a shape offset,
|
|
* the pad position is NOT the shape position */
|
|
int angle = 0;
|
|
wxSize copper_tickness;
|
|
int dx = aPad.m_Size.x / 2;
|
|
int dy = aPad.m_Size.y / 2;
|
|
|
|
int delta = 3600 / s_CircleToSegmentsCount; // rot angle in 0.1 degree
|
|
|
|
/* Keep in account the polygon outline thickness
|
|
* aThermalGap must be increased by aMinThicknessValue/2 because drawing external outline
|
|
* with a thickness of aMinThicknessValue will reduce gap by aMinThicknessValue/2
|
|
*/
|
|
aThermalGap += aMinThicknessValue/2;
|
|
|
|
/* Keep in account the polygon outline thickness
|
|
* copper_tickness must be decreased by aMinThicknessValue because drawing outlines
|
|
* with a thickness of aMinThicknessValue will increase thickness by aMinThicknessValue
|
|
*/
|
|
aCopperThickness -= aMinThicknessValue;
|
|
if ( aCopperThickness < 0 )
|
|
aCopperThickness = 0;
|
|
|
|
copper_tickness.x = min( dx, aCopperThickness );
|
|
copper_tickness.y = min( dy, aCopperThickness );
|
|
|
|
if ( copper_tickness.x < aMinThicknessValue )
|
|
copper_tickness.x = 0;
|
|
if ( copper_tickness.y < aMinThicknessValue )
|
|
copper_tickness.y = 0;
|
|
|
|
switch( aPad.m_PadShape )
|
|
{
|
|
case PAD_CIRCLE: // Add 4 similar holes
|
|
{
|
|
/* we create 4 copper holes and put them in position 1, 2, 3 and 4
|
|
* here is the area of the rectangular pad + its thermal gap
|
|
* the 4 copper holes remove the copper in order to create the thermal gap
|
|
* 4 ------ 1
|
|
* | |
|
|
* | |
|
|
* | |
|
|
* | |
|
|
* 3 ------ 2
|
|
* holes 2, 3, 4 are the same as hole 1, rotated 90, 180, 270 deg
|
|
*/
|
|
|
|
// Build the hole pattern, for the hole in the X >0, Y > 0 plane:
|
|
// The pattern roughtly is a 90 deg arc pie
|
|
std::vector <wxPoint> corners_buffer;
|
|
|
|
// Radius of outer arcs of the shape:
|
|
int outer_radius = dx + aThermalGap; // The radius of the outer arc is pad radius + aThermalGap
|
|
|
|
// Crosspoint of thermal spoke sides, the first point of polygon buffer
|
|
corners_buffer.push_back( wxPoint( copper_tickness.x / 2, copper_tickness.y / 2 ) );
|
|
|
|
// Add an intermediate point on spoke sides, to allow a > 90 deg angle between side and first seg of arc approx
|
|
corner.x = copper_tickness.x / 2;
|
|
int y = outer_radius - (aThermalGap / 4);
|
|
corner.y = (int) sqrt( ( ( (double) y * y ) - (double) corner.x * corner.x ) );
|
|
corners_buffer.push_back( corner );
|
|
|
|
// calculate the starting point of the outter arc
|
|
corner.x = copper_tickness.x / 2;
|
|
double dtmp = sqrt( ( (double) outer_radius * outer_radius ) - ( (double) corner.x * corner.x ) );
|
|
corner.y = (int) dtmp;
|
|
RotatePoint( &corner, 90 );
|
|
|
|
// calculate the ending point of the outter arc
|
|
corner_end.x = corner.y;
|
|
corner_end.y = corner.x;
|
|
|
|
// calculate intermediate points (y coordinate from corner.y to corner_end.y
|
|
while( (corner.y > corner_end.y) && (corner.x < corner_end.x) )
|
|
{
|
|
corners_buffer.push_back( corner );
|
|
RotatePoint( &corner, delta );
|
|
}
|
|
|
|
corners_buffer.push_back( corner_end );
|
|
|
|
/* add an intermediate point, to avoid angles < 90 deg between last arc approx line and radius line
|
|
*/
|
|
corner.x = corners_buffer[1].y;
|
|
corner.y = corners_buffer[1].x;
|
|
corners_buffer.push_back( corner );
|
|
|
|
// Now, add the 4 holes ( each is the pattern, rotated by 0, 90, 180 and 270 deg
|
|
// WARNING: problems with kbool if angle = 0 (in fact when angle < 200):
|
|
// bad filled polygon on some cases, when pads are on a same vertical line
|
|
// this seems a bug in kbool polygon (exists in 1.9 kbool version)
|
|
// angle = 450 (45.0 degrees orientation) seems work fine.
|
|
// angle = 0 with thermal shapes without angle < 90 deg has problems in rare circumstances
|
|
angle = 450;
|
|
int angle_pad = aPad.m_Orient; // Pad orientation
|
|
for( unsigned ihole = 0; ihole < 4; ihole++ )
|
|
{
|
|
if( aBooleng->StartPolygonAdd( GROUP_B ) )
|
|
{
|
|
for( unsigned ii = 0; ii < corners_buffer.size(); ii++ )
|
|
{
|
|
corner = corners_buffer[ii];
|
|
RotatePoint( &corner, angle + angle_pad ); // Rotate by segment angle and pad orientation
|
|
corner += PadShapePos;
|
|
aBooleng->AddPoint( corner.x, corner.y );
|
|
}
|
|
|
|
aBooleng->EndPolygonAdd();
|
|
|
|
angle += 900; // Note: angle in in 0.1 deg.
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PAD_OVAL:
|
|
{
|
|
// Oval pad support along the lines of round and rectangular pads
|
|
std::vector <wxPoint> corners_buffer; // Polygon buffer as vector
|
|
|
|
int dx = (aPad.m_Size.x / 2) + aThermalGap; // Cutout radius x
|
|
int dy = (aPad.m_Size.y / 2) + aThermalGap; // Cutout radius y
|
|
|
|
wxPoint shape_offset;
|
|
|
|
// We want to calculate an oval shape with dx > dy.
|
|
// if this is not the case, exchange dx and dy, and rotate the shape 90 deg.
|
|
int supp_angle = 0;
|
|
if( dx < dy )
|
|
{
|
|
EXCHG( dx, dy );
|
|
supp_angle = 900;
|
|
EXCHG( copper_tickness.x, copper_tickness.y);
|
|
}
|
|
int deltasize = dx - dy; // = distance between shape position and the 2 demi-circle ends centre
|
|
// here we have dx > dy
|
|
// Radius of outer arcs of the shape:
|
|
int outer_radius = dy; // The radius of the outer arc is radius end + aThermalGap
|
|
|
|
// Some coordinate fiddling, depending on the shape offset direction
|
|
shape_offset = wxPoint( deltasize, 0 );
|
|
|
|
// Crosspoint of thermal spoke sides, the first point of polygon buffer
|
|
corners_buffer.push_back( wxPoint( copper_tickness.x / 2, copper_tickness.y / 2 ) );
|
|
|
|
// Arc start point calculation, the intersecting point of cutout arc and thermal spoke edge
|
|
if( copper_tickness.x > deltasize ) // If copper thickness is more than shape offset, we need to calculate arc intercept point.
|
|
{
|
|
corner.x = copper_tickness.x / 2;
|
|
corner.y =
|
|
(int) sqrt( ((double)outer_radius * outer_radius ) -
|
|
( (double)( corner.x - delta ) * ( corner.x - deltasize ) ) );
|
|
corner.x -= deltasize;
|
|
|
|
/* creates an intermediate point, to have a > 90 deg angle
|
|
* between the side and the first segment of arc approximation
|
|
*/
|
|
wxPoint intpoint = corner;
|
|
intpoint.y -= aThermalGap/4;
|
|
corners_buffer.push_back( intpoint + shape_offset );
|
|
RotatePoint( &corner, 90 );
|
|
}
|
|
else
|
|
{
|
|
corner.x = copper_tickness.x / 2;
|
|
corner.y = outer_radius;
|
|
corners_buffer.push_back( corner );
|
|
corner.x = ( deltasize - copper_tickness.x ) / 2;
|
|
}
|
|
|
|
// Add an intermediate point on spoke sides, to allow a > 90 deg angle between side and first seg of arc approx
|
|
wxPoint last_corner;
|
|
last_corner.y = copper_tickness.y / 2;
|
|
int px = outer_radius - (aThermalGap / 4);
|
|
last_corner.x = (int) sqrt( ( ( (double) px * px ) - (double) last_corner.y * last_corner.y ) );
|
|
|
|
// Arc stop point calculation, the intersecting point of cutout arc and thermal spoke edge
|
|
corner_end.y = copper_tickness.y / 2;
|
|
corner_end.x = (int) sqrt( ( (double) outer_radius * outer_radius ) - ( (double) corner_end.y * corner_end.y ) );
|
|
RotatePoint( &corner_end, -90 );
|
|
|
|
// calculate intermediate arc points till limit is reached
|
|
while( (corner.y > corner_end.y) && (corner.x < corner_end.x) )
|
|
{
|
|
corners_buffer.push_back( corner + shape_offset );
|
|
RotatePoint( &corner, delta );
|
|
}
|
|
|
|
//corners_buffer.push_back(corner + shape_offset); // TODO: about one mil geometry error forms somewhere.
|
|
corners_buffer.push_back( corner_end + shape_offset );
|
|
corners_buffer.push_back( last_corner + shape_offset ); // Enabling the line above shows intersection point.
|
|
|
|
/* Create 2 holes, rotated by pad rotation.
|
|
*/
|
|
angle = aPad.m_Orient + supp_angle;
|
|
for( int irect = 0; irect < 2; irect++ )
|
|
{
|
|
if( aBooleng->StartPolygonAdd( GROUP_B ) )
|
|
{
|
|
for( unsigned ic = 0; ic < corners_buffer.size(); ic++ )
|
|
{
|
|
wxPoint cpos = corners_buffer[ic];
|
|
RotatePoint( &cpos, angle );
|
|
cpos += PadShapePos;
|
|
aBooleng->AddPoint( cpos.x, cpos.y );
|
|
}
|
|
|
|
aBooleng->EndPolygonAdd();
|
|
angle += 1800; // this is calculate hole 3
|
|
if( angle >= 3600 )
|
|
angle -= 3600;
|
|
}
|
|
}
|
|
|
|
// Create holes, that are the mirrored from the previous holes
|
|
for( unsigned ic = 0; ic < corners_buffer.size(); ic++ )
|
|
{
|
|
wxPoint swap = corners_buffer[ic];
|
|
swap.x = -swap.x;
|
|
corners_buffer[ic] = swap;
|
|
}
|
|
|
|
// Now add corner 4 and 2 (2 is the corner 4 rotated by 180 deg
|
|
angle = aPad.m_Orient + supp_angle;
|
|
for( int irect = 0; irect < 2; irect++ )
|
|
{
|
|
if( aBooleng->StartPolygonAdd( GROUP_B ) )
|
|
{
|
|
for( unsigned ic = 0; ic < corners_buffer.size(); ic++ )
|
|
{
|
|
wxPoint cpos = corners_buffer[ic];
|
|
RotatePoint( &cpos, angle );
|
|
cpos += PadShapePos;
|
|
aBooleng->AddPoint( cpos.x, cpos.y );
|
|
}
|
|
|
|
aBooleng->EndPolygonAdd();
|
|
angle += 1800;
|
|
if( angle >= 3600 )
|
|
angle -= 3600;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PAD_RECT: // draw 4 Holes
|
|
{
|
|
/* we create 4 copper holes and put them in position 1, 2, 3 and 4
|
|
* here is the area of the rectangular pad + its thermal gap
|
|
* the 4 copper holes remove the copper in order to create the thermal gap
|
|
* 4 ------ 1
|
|
* | |
|
|
* | |
|
|
* | |
|
|
* | |
|
|
* 3 ------ 2
|
|
* hole 3 is the same as hole 1, rotated 180 deg
|
|
* hole 4 is the same as hole 2, rotated 180 deg and is the same as hole 1, mirrored
|
|
*/
|
|
|
|
// First, create a rectangular hole for position 1 :
|
|
// 2 ------- 3
|
|
// | |
|
|
// | |
|
|
// | |
|
|
// 1 -------4
|
|
wxPoint corners_hole[4]; // buffer for 4 corners
|
|
// Create 1 hole, for a pad centered at 0,0, orient 0
|
|
// Calculate coordinates for corner 1 to corner 4:
|
|
corners_hole[0] = wxPoint( copper_tickness.x / 2, -copper_tickness.y / 2 );
|
|
corners_hole[1] = wxPoint( (copper_tickness.x / 2), -dy - aThermalGap );
|
|
corners_hole[2] = wxPoint( dx + aThermalGap, -dy - aThermalGap );
|
|
corners_hole[3] = wxPoint( dx + aThermalGap, -(copper_tickness.y / 2) );
|
|
|
|
/* Create 2 holes, rotated by pad rotation.
|
|
*/
|
|
angle = aPad.m_Orient;
|
|
for( int irect = 0; irect < 2; irect++ )
|
|
{
|
|
if( aBooleng->StartPolygonAdd( GROUP_B ) )
|
|
{
|
|
for( int ic = 0; ic < 4; ic++ )
|
|
{
|
|
wxPoint cpos = corners_hole[ic];
|
|
RotatePoint( &cpos, angle );
|
|
cpos += PadShapePos;
|
|
aBooleng->AddPoint( cpos.x, cpos.y );
|
|
}
|
|
|
|
aBooleng->EndPolygonAdd();
|
|
angle += 1800; // this is calculate hole 3
|
|
if( angle >= 3600 )
|
|
angle -= 3600;
|
|
}
|
|
}
|
|
|
|
// Create a holes, that is the mirrored of the previous hole
|
|
corners_hole[0].x = -corners_hole[0].x;
|
|
corners_hole[1].x = -corners_hole[1].x;
|
|
corners_hole[2].x = -corners_hole[2].x;
|
|
corners_hole[3].x = -corners_hole[3].x;
|
|
|
|
// Now add corner 4 and 2 (2 is the corner 4 rotated by 180 deg
|
|
angle = aPad.m_Orient;
|
|
for( int irect = 0; irect < 2; irect++ )
|
|
{
|
|
if( aBooleng->StartPolygonAdd( GROUP_B ) )
|
|
{
|
|
for( int ic = 0; ic < 4; ic++ )
|
|
{
|
|
wxPoint cpos = corners_hole[ic];
|
|
RotatePoint( &cpos, angle );
|
|
cpos += PadShapePos;
|
|
aBooleng->AddPoint( cpos.x, cpos.y );
|
|
}
|
|
|
|
aBooleng->EndPolygonAdd();
|
|
angle += 1800;
|
|
if( angle >= 3600 )
|
|
angle -= 3600;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/** Function AddTrackWithClearancePolygon
|
|
* Add a polygon cutout for a track in a zone area
|
|
* Convert arcs and circles to multiple straight lines
|
|
*/
|
|
void AddTrackWithClearancePolygon( Bool_Engine* aBooleng,
|
|
TRACK& aTrack, int aClearanceValue )
|
|
{
|
|
wxPoint corner_position;
|
|
int ii, angle;
|
|
int dx = (aTrack.m_Width / 2) + aClearanceValue;
|
|
|
|
int delta = 3600 / s_CircleToSegmentsCount; // rot angle in 0.1 degree
|
|
|
|
switch( aTrack.Type() )
|
|
{
|
|
case TYPEVIA:
|
|
if( aBooleng->StartPolygonAdd( GROUP_B ) )
|
|
{
|
|
dx = (int) (dx * s_Correction);
|
|
for( ii = 0; ii < s_CircleToSegmentsCount; ii++ )
|
|
{
|
|
corner_position = wxPoint( dx, 0 );
|
|
angle = ii * delta;
|
|
RotatePoint( &corner_position, angle );
|
|
corner_position += aTrack.m_Start;
|
|
aBooleng->AddPoint( corner_position.x, corner_position.y );
|
|
}
|
|
|
|
aBooleng->EndPolygonAdd();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
AddRoundedEndsSegmentPolygon( aBooleng,
|
|
aTrack.m_Start, aTrack.m_End,
|
|
aTrack.m_Width + (2 * aClearanceValue) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/** Function AddRoundedEndsSegmentPolygon
|
|
* Add a polygon cutout for a segment (with rounded ends) in a zone area
|
|
* Convert arcs to multiple straight lines
|
|
*/
|
|
void AddRoundedEndsSegmentPolygon( Bool_Engine* aBooleng,
|
|
wxPoint aStart, wxPoint aEnd,
|
|
int aWidth )
|
|
{
|
|
int rayon = aWidth / 2;
|
|
wxPoint endp = aEnd - aStart; // end point coordinate for the same segment starting at (0,0)
|
|
wxPoint startp = aStart;
|
|
wxPoint corner;
|
|
int seg_len;
|
|
|
|
// normalize the position in order to have endp.x >= 0;
|
|
if( endp.x < 0 )
|
|
{
|
|
endp = aStart - aEnd;
|
|
startp = aEnd;
|
|
}
|
|
int delta_angle = ArcTangente( endp.y, endp.x ); // delta_angle is in 0.1 degrees
|
|
seg_len = (int) sqrt( ( (double) endp.y * endp.y ) + ( (double) endp.x * endp.x ) );
|
|
|
|
if( !aBooleng->StartPolygonAdd( GROUP_B ) )
|
|
return; // error!
|
|
|
|
int delta = 3600 / s_CircleToSegmentsCount; // rot angle in 0.1 degree
|
|
|
|
// Compute the outlines of the segment, and creates a polygon
|
|
corner = wxPoint( 0, rayon );
|
|
RotatePoint( &corner, -delta_angle );
|
|
corner += startp;
|
|
aBooleng->AddPoint( corner.x, corner.y );
|
|
|
|
corner = wxPoint( seg_len, rayon );
|
|
RotatePoint( &corner, -delta_angle );
|
|
corner += startp;
|
|
aBooleng->AddPoint( corner.x, corner.y );
|
|
|
|
// add right rounded end:
|
|
for( int ii = delta; ii < 1800; ii += delta )
|
|
{
|
|
corner = wxPoint( 0, rayon );
|
|
RotatePoint( &corner, ii );
|
|
corner.x += seg_len;
|
|
RotatePoint( &corner, -delta_angle );
|
|
corner += startp;
|
|
aBooleng->AddPoint( corner.x, corner.y );
|
|
}
|
|
|
|
corner = wxPoint( seg_len, -rayon );
|
|
RotatePoint( &corner, -delta_angle );
|
|
corner += startp;
|
|
aBooleng->AddPoint( corner.x, corner.y );
|
|
|
|
corner = wxPoint( 0, -rayon );
|
|
RotatePoint( &corner, -delta_angle );
|
|
corner += startp;
|
|
aBooleng->AddPoint( corner.x, corner.y );
|
|
|
|
// add left rounded end:
|
|
for( int ii = delta; ii < 1800; ii += delta )
|
|
{
|
|
corner = wxPoint( 0, -rayon );
|
|
RotatePoint( &corner, ii );
|
|
RotatePoint( &corner, -delta_angle );
|
|
corner += startp;
|
|
aBooleng->AddPoint( corner.x, corner.y );
|
|
}
|
|
|
|
aBooleng->EndPolygonAdd();
|
|
}
|
|
|
|
|
|
/** function AddTextBoxWithClearancePolygon
|
|
* creates a polygon containing the text and add it to bool engine
|
|
*/
|
|
void AddTextBoxWithClearancePolygon( Bool_Engine* aBooleng,
|
|
TEXTE_PCB* aText, int aClearanceValue )
|
|
{
|
|
int corners[8]; // Buffer of coordinates
|
|
int ii;
|
|
|
|
int dx = aText->Pitch() * aText->GetLength();
|
|
int dy = aText->m_Size.y + aText->m_Width;
|
|
|
|
/* Creates bounding box (rectangle) for an horizontal text */
|
|
dx /= 2; dy /= 2; /* dx et dy = demi dimensionx X et Y */
|
|
dx += aClearanceValue;
|
|
dy += aClearanceValue;
|
|
corners[0] = aText->m_Pos.x - dx;
|
|
corners[1] = aText->m_Pos.y - dy;
|
|
corners[2] = aText->m_Pos.x + dx;
|
|
corners[3] = aText->m_Pos.y - dy;
|
|
corners[4] = aText->m_Pos.x + dx;
|
|
corners[5] = aText->m_Pos.y + dy;
|
|
corners[6] = aText->m_Pos.x - dx;
|
|
corners[7] = aText->m_Pos.y + dy;
|
|
|
|
// Rotate rectangle
|
|
RotatePoint( &corners[0], &corners[1], aText->m_Pos.x, aText->m_Pos.y, aText->m_Orient );
|
|
RotatePoint( &corners[2], &corners[3], aText->m_Pos.x, aText->m_Pos.y, aText->m_Orient );
|
|
RotatePoint( &corners[4], &corners[5], aText->m_Pos.x, aText->m_Pos.y, aText->m_Orient );
|
|
RotatePoint( &corners[6], &corners[7], aText->m_Pos.x, aText->m_Pos.y, aText->m_Orient );
|
|
|
|
if( aBooleng->StartPolygonAdd( GROUP_B ) )
|
|
{
|
|
for( ii = 0; ii < 8; ii += 2 )
|
|
{
|
|
aBooleng->AddPoint( corners[ii], corners[ii + 1] );
|
|
}
|
|
|
|
aBooleng->EndPolygonAdd();
|
|
}
|
|
}
|