Commit 92ab71c2 authored by charras's avatar charras

Correct handling of arcs and circles (when used in pcb edges) in zone filling.

parent a1c52822
...@@ -51,6 +51,11 @@ void AddRoundedEndsSegmentPolygon( Bool_Engine* aBooleng, ...@@ -51,6 +51,11 @@ void AddRoundedEndsSegmentPolygon( Bool_Engine* aBooleng,
void AddTextBoxWithClearancePolygon( Bool_Engine* aBooleng, void AddTextBoxWithClearancePolygon( Bool_Engine* aBooleng,
TEXTE_PCB* aText, int aClearanceValue ); TEXTE_PCB* aText, int aClearanceValue );
static void AddRingPolygon( Bool_Engine* aBooleng,
wxPoint aCentre,
wxPoint aStart,
wxPoint aEnd,
int aWidth );
// Local Variables: // Local Variables:
/* how many segments are used to create a polygon from a circle: */ /* how many segments are used to create a polygon from a circle: */
...@@ -222,10 +227,38 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb ) ...@@ -222,10 +227,38 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb )
switch( item->Type() ) switch( item->Type() )
{ {
case TYPE_DRAWSEGMENT: case TYPE_DRAWSEGMENT:
AddRoundedEndsSegmentPolygon( booleng,
( (DRAWSEGMENT*) item )->m_Start, switch( ( (DRAWSEGMENT*) item )->m_Shape )
( (DRAWSEGMENT*) item )->m_End, {
( (DRAWSEGMENT*) item )->m_Width + (2 * m_ZoneClearance) ); case S_CIRCLE:
AddRingPolygon( booleng, ( (DRAWSEGMENT*) item )->m_Start,
( (DRAWSEGMENT*) item )->m_End, ( (DRAWSEGMENT*) item )->m_End,
( (DRAWSEGMENT*) item )->m_Width + (2 * m_ZoneClearance) );
break;
case S_ARC:
{
wxPoint arc_start, arc_end;
arc_start = ( (DRAWSEGMENT*) item )->m_End;
arc_end = ( (DRAWSEGMENT*) item )->m_End;
RotatePoint( &arc_end,
( (DRAWSEGMENT*) item )->m_Start,
-( (DRAWSEGMENT*) item )->m_Angle );
AddRingPolygon( booleng, ( (DRAWSEGMENT*) item )->m_Start, arc_start, arc_end,
( (DRAWSEGMENT*) item )->m_Width + (2 * m_ZoneClearance) );
}
break;
default:
AddRoundedEndsSegmentPolygon( booleng,
( (DRAWSEGMENT*) item )->m_Start,
( (DRAWSEGMENT*) item )->m_End,
( (DRAWSEGMENT*) item )->m_Width +
(2 * m_ZoneClearance) );
break;
}
break; break;
case TYPE_TEXTE: case TYPE_TEXTE:
...@@ -239,19 +272,19 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb ) ...@@ -239,19 +272,19 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb )
} }
} }
/* calculates copper areas */ /* calculates copper areas */
booleng->Do_Operation( BOOL_A_SUB_B ); booleng->Do_Operation( BOOL_A_SUB_B );
/* put these areas in m_FilledPolysList */ /* put these areas in m_FilledPolysList */
m_FilledPolysList.clear(); m_FilledPolysList.clear();
CopyPolygonsFromBoolengineToFilledPolysList( booleng ); CopyPolygonsFromBoolengineToFilledPolysList( booleng );
delete booleng; delete booleng;
// Remove insulated islands: // Remove insulated islands:
if( GetNet() > 0 ) if( GetNet() > 0 )
Test_For_Copper_Island_And_Remove_Insulated_Islands( aPcb ); Test_For_Copper_Island_And_Remove_Insulated_Islands( aPcb );
// Remove thermal symbols // Remove thermal symbols
if( m_PadOption == THERMAL_PAD ) if( m_PadOption == THERMAL_PAD )
{ {
booleng = new Bool_Engine(); booleng = new Bool_Engine();
...@@ -295,7 +328,7 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb ) ...@@ -295,7 +328,7 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb )
delete booleng; delete booleng;
} }
// Remove insulated islands: // Remove insulated islands:
if( GetNet() > 0 ) if( GetNet() > 0 )
Test_For_Copper_Island_And_Remove_Insulated_Islands( aPcb ); Test_For_Copper_Island_And_Remove_Insulated_Islands( aPcb );
...@@ -306,21 +339,21 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb ) ...@@ -306,21 +339,21 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb )
#define REMOVE_UNUSED_THERMAL_STUBS // Can be commented to skip unused thermal stubs calculations #define REMOVE_UNUSED_THERMAL_STUBS // Can be commented to skip unused thermal stubs calculations
#ifdef REMOVE_UNUSED_THERMAL_STUBS #ifdef REMOVE_UNUSED_THERMAL_STUBS
/* Add the main (corrected) polygon (i.e. the filled area using only one outline) /* 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 * 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 * All areas to remove will be put in GroupB in Bool_Engine
*/ */
booleng = new Bool_Engine(); booleng = new Bool_Engine();
ArmBoolEng( booleng, true ); ArmBoolEng( booleng, true );
/* Add the main corrected polygon (i.e. the filled area using only one outline) /* Add the main corrected polygon (i.e. the filled area using only one outline)
* in GroupA in Bool_Engine * in GroupA in Bool_Engine
*/ */
CopyPolygonsFromFilledPolysListToBoolengine( booleng, GROUP_A ); CopyPolygonsFromFilledPolysListToBoolengine( booleng, GROUP_A );
/* /*
* Test and add polygons to remove thermal stubs. * Test and add polygons to remove thermal stubs.
*/ */
for( MODULE* module = aPcb->m_Modules; module; module = module->Next() ) for( MODULE* module = aPcb->m_Modules; module; module = module->Next() )
{ {
for( D_PAD* pad = module->m_Pads; pad != NULL; pad = pad->Next() ) for( D_PAD* pad = module->m_Pads; pad != NULL; pad = pad->Next() )
...@@ -343,7 +376,7 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb ) ...@@ -343,7 +376,7 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb )
( pad->m_Size.y / 2 ) + m_ThermalReliefGapValue; ( pad->m_Size.y / 2 ) + m_ThermalReliefGapValue;
// This is CIRCLE pad tweak (for circle pads the thermal stubs are at 45 deg) // This is CIRCLE pad tweak (for circle pads the thermal stubs are at 45 deg)
int fAngle = pad->m_Orient; int fAngle = pad->m_Orient;
if( pad->m_PadShape == PAD_CIRCLE ) if( pad->m_PadShape == PAD_CIRCLE )
{ {
dx = (int) ( dx * s_Correction ); dx = (int) ( dx * s_Correction );
...@@ -427,15 +460,15 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb ) ...@@ -427,15 +460,15 @@ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb )
} }
} }
/* compute copper areas */ /* compute copper areas */
booleng->Do_Operation( BOOL_A_SUB_B ); booleng->Do_Operation( BOOL_A_SUB_B );
/* put these areas in m_FilledPolysList */ /* put these areas in m_FilledPolysList */
m_FilledPolysList.clear(); m_FilledPolysList.clear();
CopyPolygonsFromBoolengineToFilledPolysList( booleng ); CopyPolygonsFromBoolengineToFilledPolysList( booleng );
delete booleng; delete booleng;
// Remove insulated islands, if any: // Remove insulated islands, if any:
if( GetNet() > 0 ) if( GetNet() > 0 )
Test_For_Copper_Island_And_Remove_Insulated_Islands( aPcb ); Test_For_Copper_Island_And_Remove_Insulated_Islands( aPcb );
#endif #endif
...@@ -467,7 +500,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng, ...@@ -467,7 +500,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng,
for( ii = 0; ii < s_CircleToSegmentsCount; ii++ ) for( ii = 0; ii < s_CircleToSegmentsCount; ii++ )
{ {
corner_position = wxPoint( dx, 0 ); corner_position = wxPoint( dx, 0 );
RotatePoint( &corner_position, (1800/s_CircleToSegmentsCount) ); // Half increment offset to get more space between RotatePoint( &corner_position, (1800 / s_CircleToSegmentsCount) ); // Half increment offset to get more space between
angle = ii * delta; angle = ii * delta;
RotatePoint( &corner_position, angle ); RotatePoint( &corner_position, angle );
corner_position += PadShapePos; corner_position += PadShapePos;
...@@ -488,7 +521,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng, ...@@ -488,7 +521,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng,
for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ ) // Half circle end cap... for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ ) // Half circle end cap...
{ {
corner_position = wxPoint( dx, 0 ); // Coordinate translation +dx corner_position = wxPoint( dx, 0 ); // Coordinate translation +dx
RotatePoint( &corner_position, (1800/s_CircleToSegmentsCount) ); RotatePoint( &corner_position, (1800 / s_CircleToSegmentsCount) );
RotatePoint( &corner_position, angle ); RotatePoint( &corner_position, angle );
angle_pg = ii * delta; angle_pg = ii * delta;
RotatePoint( &corner_position, angle_pg ); RotatePoint( &corner_position, angle_pg );
...@@ -499,7 +532,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng, ...@@ -499,7 +532,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng,
for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ ) // Second half circle end cap... for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ ) // Second half circle end cap...
{ {
corner_position = wxPoint( -dx, 0 ); // Coordinate translation -dx corner_position = wxPoint( -dx, 0 ); // Coordinate translation -dx
RotatePoint( &corner_position, (1800/s_CircleToSegmentsCount) ); RotatePoint( &corner_position, (1800 / s_CircleToSegmentsCount) );
RotatePoint( &corner_position, angle ); RotatePoint( &corner_position, angle );
angle_pg = ii * delta; angle_pg = ii * delta;
RotatePoint( &corner_position, angle_pg ); RotatePoint( &corner_position, angle_pg );
...@@ -519,7 +552,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng, ...@@ -519,7 +552,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng,
for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ ) for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ )
{ {
corner_position = wxPoint( 0, dy ); corner_position = wxPoint( 0, dy );
RotatePoint( &corner_position, (1800/s_CircleToSegmentsCount) ); RotatePoint( &corner_position, (1800 / s_CircleToSegmentsCount) );
RotatePoint( &corner_position, angle ); RotatePoint( &corner_position, angle );
angle_pg = ii * delta; angle_pg = ii * delta;
RotatePoint( &corner_position, angle_pg ); RotatePoint( &corner_position, angle_pg );
...@@ -530,7 +563,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng, ...@@ -530,7 +563,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng,
for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ ) for( ii = 0; ii < s_CircleToSegmentsCount / 2 + 1; ii++ )
{ {
corner_position = wxPoint( 0, -dy ); corner_position = wxPoint( 0, -dy );
RotatePoint( &corner_position, (1800/s_CircleToSegmentsCount) ); RotatePoint( &corner_position, (1800 / s_CircleToSegmentsCount) );
RotatePoint( &corner_position, angle ); RotatePoint( &corner_position, angle );
angle_pg = ii * delta; angle_pg = ii * delta;
RotatePoint( &corner_position, angle_pg ); RotatePoint( &corner_position, angle_pg );
...@@ -549,19 +582,19 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng, ...@@ -549,19 +582,19 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng,
for( int i = 0; i < s_CircleToSegmentsCount / 4 + 1; i++ ) for( int i = 0; i < s_CircleToSegmentsCount / 4 + 1; i++ )
{ {
corner_position = wxPoint( 0, -rounding_radius ); corner_position = wxPoint( 0, -rounding_radius );
RotatePoint( &corner_position, (1800/s_CircleToSegmentsCount) ); // Start at half increment offset RotatePoint( &corner_position, (1800 / s_CircleToSegmentsCount) ); // Start at half increment offset
angle_pg = i * delta; angle_pg = i * delta;
RotatePoint( &corner_position, angle_pg ); // Rounding vector rotation RotatePoint( &corner_position, angle_pg ); // Rounding vector rotation
corner_position -= aPad.m_Size / 2; // Rounding vector + Pad corner offset corner_position -= aPad.m_Size / 2; // Rounding vector + Pad corner offset
RotatePoint( &corner_position, angle ); // Rotate according to module orientation RotatePoint( &corner_position, angle ); // Rotate according to module orientation
corner_position += PadShapePos; // Shift origin to position corner_position += PadShapePos; // Shift origin to position
aBooleng->AddPoint( corner_position.x, corner_position.y ); aBooleng->AddPoint( corner_position.x, corner_position.y );
} }
for( int i = 0; i < s_CircleToSegmentsCount / 4 + 1; i++ ) for( int i = 0; i < s_CircleToSegmentsCount / 4 + 1; i++ )
{ {
corner_position = wxPoint( -rounding_radius, 0 ); corner_position = wxPoint( -rounding_radius, 0 );
RotatePoint( &corner_position, (1800/s_CircleToSegmentsCount) ); RotatePoint( &corner_position, (1800 / s_CircleToSegmentsCount) );
angle_pg = i * delta; angle_pg = i * delta;
RotatePoint( &corner_position, angle_pg ); RotatePoint( &corner_position, angle_pg );
corner_position -= wxPoint( aPad.m_Size.x / 2, -aPad.m_Size.y / 2 ); corner_position -= wxPoint( aPad.m_Size.x / 2, -aPad.m_Size.y / 2 );
...@@ -573,7 +606,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng, ...@@ -573,7 +606,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng,
for( int i = 0; i < s_CircleToSegmentsCount / 4 + 1; i++ ) for( int i = 0; i < s_CircleToSegmentsCount / 4 + 1; i++ )
{ {
corner_position = wxPoint( 0, rounding_radius ); corner_position = wxPoint( 0, rounding_radius );
RotatePoint( &corner_position, (1800/s_CircleToSegmentsCount) ); RotatePoint( &corner_position, (1800 / s_CircleToSegmentsCount) );
angle_pg = i * delta; angle_pg = i * delta;
RotatePoint( &corner_position, angle_pg ); RotatePoint( &corner_position, angle_pg );
corner_position += aPad.m_Size / 2; corner_position += aPad.m_Size / 2;
...@@ -585,7 +618,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng, ...@@ -585,7 +618,7 @@ void AddPadWithClearancePolygon( Bool_Engine* aBooleng,
for( int i = 0; i < s_CircleToSegmentsCount / 4 + 1; i++ ) for( int i = 0; i < s_CircleToSegmentsCount / 4 + 1; i++ )
{ {
corner_position = wxPoint( rounding_radius, 0 ); corner_position = wxPoint( rounding_radius, 0 );
RotatePoint( &corner_position, (1800/s_CircleToSegmentsCount) ); RotatePoint( &corner_position, (1800 / s_CircleToSegmentsCount) );
angle_pg = i * delta; angle_pg = i * delta;
RotatePoint( &corner_position, angle_pg ); RotatePoint( &corner_position, angle_pg );
corner_position -= wxPoint( -aPad.m_Size.x / 2, aPad.m_Size.y / 2 ); corner_position -= wxPoint( -aPad.m_Size.x / 2, aPad.m_Size.y / 2 );
...@@ -753,7 +786,7 @@ void AddThermalReliefPadPolygon( Bool_Engine* aBooleng, ...@@ -753,7 +786,7 @@ void AddThermalReliefPadPolygon( Bool_Engine* aBooleng,
} }
} }
} }
break; break;
case PAD_OVAL: case PAD_OVAL:
{ {
...@@ -886,7 +919,7 @@ void AddThermalReliefPadPolygon( Bool_Engine* aBooleng, ...@@ -886,7 +919,7 @@ void AddThermalReliefPadPolygon( Bool_Engine* aBooleng,
} }
} }
} }
break; break;
case PAD_RECT: // draw 4 Holes case PAD_RECT: // draw 4 Holes
{ {
...@@ -919,11 +952,11 @@ void AddThermalReliefPadPolygon( Bool_Engine* aBooleng, ...@@ -919,11 +952,11 @@ void AddThermalReliefPadPolygon( Bool_Engine* aBooleng,
// The first point of polygon buffer is left lower corner, second the crosspoint of thermal spoke sides, // The first point of polygon buffer is left lower corner, second the crosspoint of thermal spoke sides,
// the third is upper right corner and the rest are rounding vertices going anticlockwise. Note the inveted Y-axis in CG. // the third is upper right corner and the rest are rounding vertices going anticlockwise. Note the inveted Y-axis in CG.
corners_buffer.push_back( wxPoint( -dx , -(aThermalGap / 4 + copper_thickness.y / 2) ) ); // Adds small miters to zone corners_buffer.push_back( wxPoint( -dx, -(aThermalGap / 4 + copper_thickness.y / 2) ) ); // Adds small miters to zone
corners_buffer.push_back( wxPoint( -(dx - aThermalGap / 4) , -copper_thickness.y / 2 ) ); // fill and spoke corner corners_buffer.push_back( wxPoint( -(dx - aThermalGap / 4), -copper_thickness.y / 2 ) ); // fill and spoke corner
corners_buffer.push_back( wxPoint( -copper_thickness.x / 2, -copper_thickness.y / 2 ) ); corners_buffer.push_back( wxPoint( -copper_thickness.x / 2, -copper_thickness.y / 2 ) );
corners_buffer.push_back( wxPoint( -copper_thickness.x / 2, -(dy - aThermalGap / 4) ) ); corners_buffer.push_back( wxPoint( -copper_thickness.x / 2, -(dy - aThermalGap / 4) ) );
corners_buffer.push_back( wxPoint( -(aThermalGap / 4 + copper_thickness.x / 2), -dy ) ); corners_buffer.push_back( wxPoint( -(aThermalGap / 4 + copper_thickness.x / 2), -dy ) );
angle = aPad.m_Orient; angle = aPad.m_Orient;
int rounding_radius = (int) ( aThermalGap * s_Correction ); // Corner rounding radius int rounding_radius = (int) ( aThermalGap * s_Correction ); // Corner rounding radius
...@@ -932,10 +965,10 @@ void AddThermalReliefPadPolygon( Bool_Engine* aBooleng, ...@@ -932,10 +965,10 @@ void AddThermalReliefPadPolygon( Bool_Engine* aBooleng,
for( int i = 0; i < s_CircleToSegmentsCount / 4 + 1; i++ ) for( int i = 0; i < s_CircleToSegmentsCount / 4 + 1; i++ )
{ {
wxPoint corner_position = wxPoint( 0, -rounding_radius ); wxPoint corner_position = wxPoint( 0, -rounding_radius );
RotatePoint( &corner_position, (1800/s_CircleToSegmentsCount) ); // Start at half increment offset RotatePoint( &corner_position, (1800 / s_CircleToSegmentsCount) ); // Start at half increment offset
angle_pg = i * delta; angle_pg = i * delta;
RotatePoint( &corner_position, angle_pg ); // Rounding vector rotation RotatePoint( &corner_position, angle_pg ); // Rounding vector rotation
corner_position -= aPad.m_Size / 2; // Rounding vector + Pad corner offset corner_position -= aPad.m_Size / 2; // Rounding vector + Pad corner offset
corners_buffer.push_back( wxPoint( corner_position.x, corner_position.y ) ); corners_buffer.push_back( wxPoint( corner_position.x, corner_position.y ) );
} }
...@@ -1014,7 +1047,7 @@ void AddTrackWithClearancePolygon( Bool_Engine* aBooleng, ...@@ -1014,7 +1047,7 @@ void AddTrackWithClearancePolygon( Bool_Engine* aBooleng,
for( ii = 0; ii < s_CircleToSegmentsCount; ii++ ) for( ii = 0; ii < s_CircleToSegmentsCount; ii++ )
{ {
corner_position = wxPoint( dx, 0 ); corner_position = wxPoint( dx, 0 );
RotatePoint( &corner_position, (1800/s_CircleToSegmentsCount) ); RotatePoint( &corner_position, (1800 / s_CircleToSegmentsCount) );
angle = ii * delta; angle = ii * delta;
RotatePoint( &corner_position, angle ); RotatePoint( &corner_position, angle );
corner_position += aTrack.m_Start; corner_position += aTrack.m_Start;
...@@ -1108,6 +1141,52 @@ void AddRoundedEndsSegmentPolygon( Bool_Engine* aBooleng, ...@@ -1108,6 +1141,52 @@ void AddRoundedEndsSegmentPolygon( Bool_Engine* aBooleng,
} }
/** Function AddRingPolygon
* Add a polygon cutout for an Arc in a zone area
* Convert arcs to multiple straight segments
* For a cicle, aStart = aEnd
*/
void AddRingPolygon( Bool_Engine* aBooleng, wxPoint aCentre,
wxPoint aStart, wxPoint aEnd,
int aWidth )
{
wxPoint start, end;
int arc_angle;
int delta = 3600 / s_CircleToSegmentsCount; // rot angle in 0.1 degree
if( aEnd == aStart )
arc_angle = 3600;
else
{
double angle = atan2( aEnd.y - aCentre.y, aEnd.x - aCentre.x ) -
atan2( aStart.y - aCentre.y, aStart.x - aCentre.x );
// delta_angle is in 0.1 degrees
arc_angle = (int) round ( 1800.0 / M_PI * angle );
NEGATE ( arc_angle ); // Due to reverse orientation of Y axis,
// angles are negated, comparing to the mathematical Y orient.
}
if( arc_angle < 0 )
{
EXCHG( aStart, aEnd );
NEGATE( arc_angle );
}
// Compute the ends of segments and creates poly
end = start = aStart;
for( int ii = delta; ii < arc_angle; ii += delta )
{
end = aStart;
RotatePoint( &end, aCentre, ii );
AddRoundedEndsSegmentPolygon( aBooleng, start, end, aWidth );
start = end;
}
if( end != aEnd )
AddRoundedEndsSegmentPolygon( aBooleng, end, aEnd, aWidth );
}
/** function AddTextBoxWithClearancePolygon /** function AddTextBoxWithClearancePolygon
* creates a polygon containing the text and add it to bool engine * creates a polygon containing the text and add it to bool engine
*/ */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment