kicad/pcbnew/board_items_to_polygon_shap...

755 lines
26 KiB
C++
Raw Normal View History

2014-10-23 17:53:38 +00:00
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2009-2021 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
2014-10-23 17:53:38 +00:00
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <vector>
#include <bezier_curves.h>
#include <board_design_settings.h>
#include <trigo.h>
#include <board.h>
#include <pad.h>
2021-06-11 16:59:28 +00:00
#include <pcb_dimension.h>
2021-06-11 21:07:02 +00:00
#include <pcb_track.h>
#include <string_utils.h>
#include <pcb_shape.h>
#include <pcb_text.h>
#include <zone.h>
#include <footprint.h>
#include <fp_shape.h>
#include <convert_basic_shapes_to_polygon.h>
#include <geometry/geometry_utils.h>
#include <geometry/shape_segment.h>
#include <geometry/shape_circle.h>
#include <geometry/shape_rect.h>
#include <geometry/shape_line_chain.h>
// A helper struct for the callback function
// These variables are parameters used in addTextSegmToPoly.
// But addTextSegmToPoly is a call-back function,
// so we cannot send them as arguments.
struct TSEGM_2_POLY_PRMS
{
int m_textWidth;
int m_error;
SHAPE_POLY_SET* m_cornerBuffer;
};
2021-07-19 23:56:05 +00:00
TSEGM_2_POLY_PRMS prms;
// This is a call back function, used by GRText to draw the 3D text shape:
static void addTextSegmToPoly( int x0, int y0, int xf, int yf, void* aData )
{
TSEGM_2_POLY_PRMS* prm = static_cast<TSEGM_2_POLY_PRMS*>( aData );
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
TransformOvalToPolygon( *prm->m_cornerBuffer, wxPoint( x0, y0 ), wxPoint( xf, yf ),
prm->m_textWidth, prm->m_error, ERROR_INSIDE );
}
2021-07-19 23:56:05 +00:00
void BOARD::ConvertBrdLayerToPolygonalContours( PCB_LAYER_ID aLayer,
SHAPE_POLY_SET& aOutlines ) const
2014-05-17 19:29:15 +00:00
{
int maxError = GetDesignSettings().m_MaxError;
2014-05-17 19:29:15 +00:00
// convert tracks and vias:
2021-06-11 21:07:02 +00:00
for( const PCB_TRACK* track : m_tracks )
2014-05-17 19:29:15 +00:00
{
if( !track->IsOnLayer( aLayer ) )
continue;
track->TransformShapeWithClearanceToPolygon( aOutlines, aLayer, 0, maxError,
ERROR_INSIDE );
2014-05-17 19:29:15 +00:00
}
// convert pads and other copper items in footprints
for( const FOOTPRINT* footprint : m_footprints )
2014-05-17 19:29:15 +00:00
{
2020-11-13 00:43:45 +00:00
footprint->TransformPadsWithClearanceToPolygon( aOutlines, aLayer, 0, maxError,
ERROR_INSIDE );
2014-05-17 19:29:15 +00:00
// Micro-wave footprints may have items on copper layers
2020-11-13 00:43:45 +00:00
footprint->TransformFPShapesWithClearanceToPolygon( aOutlines, aLayer, 0, maxError,
ERROR_INSIDE,
true, /* include text */
true /* include shapes */ );
for( const ZONE* zone : footprint->Zones() )
{
if( zone->GetLayerSet().test( aLayer ) )
zone->TransformSolidAreasShapesToPolygon( aLayer, aOutlines );
}
2014-05-17 19:29:15 +00:00
}
// convert copper zones
for( const ZONE* zone : Zones() )
2014-05-17 19:29:15 +00:00
{
if( zone->GetLayerSet().test( aLayer ) )
zone->TransformSolidAreasShapesToPolygon( aLayer, aOutlines );
2014-05-17 19:29:15 +00:00
}
// convert graphic items on copper layers (texts)
for( const BOARD_ITEM* item : m_drawings )
2014-05-17 19:29:15 +00:00
{
if( !item->IsOnLayer( aLayer ) )
continue;
switch( item->Type() )
{
case PCB_SHAPE_T:
{
const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( item );
shape->TransformShapeWithClearanceToPolygon( aOutlines, aLayer, 0, maxError,
ERROR_INSIDE );
2014-05-17 19:29:15 +00:00
break;
2021-07-19 23:56:05 +00:00
}
2014-05-17 19:29:15 +00:00
case PCB_TEXT_T:
{
const PCB_TEXT* text = static_cast<const PCB_TEXT*>( item );
text->TransformTextShapeWithClearanceToPolygon( aOutlines, aLayer, 0, maxError,
ERROR_INSIDE );
2014-05-17 19:29:15 +00:00
break;
2021-07-19 23:56:05 +00:00
}
2014-05-17 19:29:15 +00:00
default:
break;
}
}
}
2020-11-13 15:15:52 +00:00
void FOOTPRINT::TransformPadsWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
PCB_LAYER_ID aLayer, int aClearance,
int aMaxError, ERROR_LOC aErrorLoc,
bool aSkipNPTHPadsWihNoCopper,
bool aSkipPlatedPads,
bool aSkipNonPlatedPads ) const
{
for( const PAD* pad : m_pads )
{
if( aLayer != UNDEFINED_LAYER && !pad->IsOnLayer(aLayer) )
continue;
if( !pad->FlashLayer( aLayer ) && IsCopperLayer( aLayer ) )
continue;
// NPTH pads are not drawn on layers if the shape size and pos is the same
// as their hole:
if( aSkipNPTHPadsWihNoCopper && pad->GetAttribute() == PAD_ATTRIB::NPTH )
{
if( pad->GetDrillSize() == pad->GetSize() && pad->GetOffset() == wxPoint( 0, 0 ) )
{
switch( pad->GetShape() )
{
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::CIRCLE:
if( pad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE )
continue;
2021-07-19 23:56:05 +00:00
break;
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::OVAL:
if( pad->GetDrillShape() != PAD_DRILL_SHAPE_CIRCLE )
continue;
2021-07-19 23:56:05 +00:00
break;
default:
break;
}
}
}
const bool isPlated = ( ( aLayer == F_Cu ) && pad->FlashLayer( F_Mask ) ) ||
( ( aLayer == B_Cu ) && pad->FlashLayer( B_Mask ) );
if( aSkipPlatedPads && isPlated )
continue;
if( aSkipNonPlatedPads && !isPlated )
continue;
wxSize clearance( aClearance, aClearance );
switch( aLayer )
{
case F_Mask:
case B_Mask:
clearance.x += pad->GetSolderMaskMargin();
clearance.y += pad->GetSolderMaskMargin();
break;
case F_Paste:
case B_Paste:
clearance += pad->GetSolderPasteMargin();
break;
default:
break;
}
// Our standard TransformShapeWithClearanceToPolygon() routines can't handle differing
// x:y clearance values (which get generated when a relative paste margin is used with
// an oblong pad). So we apply this huge hack and fake a larger pad to run the transform
// on.
// Of course being a hack it falls down when dealing with custom shape pads (where the
// size is only the size of the anchor), so for those we punt and just use clearance.x.
if( ( clearance.x < 0 || clearance.x != clearance.y )
2021-05-01 12:22:35 +00:00
&& pad->GetShape() != PAD_SHAPE::CUSTOM )
{
2020-11-12 22:30:02 +00:00
PAD dummy( *pad );
dummy.SetSize( pad->GetSize() + clearance + clearance );
dummy.TransformShapeWithClearanceToPolygon( aCornerBuffer, aLayer, 0,
aMaxError, aErrorLoc );
}
else
{
pad->TransformShapeWithClearanceToPolygon( aCornerBuffer, aLayer, clearance.x,
aMaxError, aErrorLoc );
}
}
}
/**
* Generate shapes of graphic items (outlines) as polygons added to a buffer.
* @aCornerBuffer = the buffer to store polygons
* @aInflateValue = a value to inflate shapes
* @aError = the maximum error to allow when approximating curves with segments
* @aIncludeText = indicates footprint text items (reference, value, etc.) should be included
* in the outline
*/
2020-11-13 15:15:52 +00:00
void FOOTPRINT::TransformFPShapesWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
PCB_LAYER_ID aLayer, int aClearance,
int aError, ERROR_LOC aErrorLoc,
bool aIncludeText,
bool aIncludeShapes ) const
{
std::vector<FP_TEXT*> texts; // List of FP_TEXT to convert
for( BOARD_ITEM* item : GraphicalItems() )
{
if( item->Type() == PCB_FP_TEXT_T && aIncludeText )
{
FP_TEXT* text = static_cast<FP_TEXT*>( item );
if( aLayer != UNDEFINED_LAYER && text->GetLayer() == aLayer && text->IsVisible() )
texts.push_back( text );
}
2020-11-13 02:57:11 +00:00
if( item->Type() == PCB_FP_SHAPE_T && aIncludeShapes )
{
const FP_SHAPE* outline = static_cast<FP_SHAPE*>( item );
if( aLayer != UNDEFINED_LAYER && outline->GetLayer() == aLayer )
{
outline->TransformShapeWithClearanceToPolygon( aCornerBuffer, aLayer, 0,
aError, aErrorLoc );
}
}
}
if( aIncludeText )
{
if( Reference().GetLayer() == aLayer && Reference().IsVisible() )
texts.push_back( &Reference() );
if( Value().GetLayer() == aLayer && Value().IsVisible() )
texts.push_back( &Value() );
}
for( const FP_TEXT* text : texts )
{
text->TransformTextShapeWithClearanceToPolygon( aCornerBuffer, aLayer, aClearance,
aError, aErrorLoc );
}
}
void FP_TEXT::TransformTextShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
PCB_LAYER_ID aLayer, int aClearance,
int aError, ERROR_LOC aErrorLoc ) const
{
prms.m_cornerBuffer = &aCornerBuffer;
prms.m_textWidth = GetEffectiveTextPenWidth() + ( 2 * aClearance );
prms.m_error = aError;
wxSize size = GetTextSize();
int penWidth = GetEffectiveTextPenWidth();
if( IsMirrored() )
size.x = -size.x;
2021-07-19 23:56:05 +00:00
GRText( nullptr, GetTextPos(), BLACK, GetShownText(), GetDrawRotation(), size,
GetHorizJustify(), GetVertJustify(), penWidth, IsItalic(), IsBold(),
addTextSegmToPoly, &prms );
}
void FP_TEXT::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
PCB_LAYER_ID aLayer, int aClearance,
int aError, ERROR_LOC aErrorLoc,
bool aIgnoreLineWidth ) const
{
EDA_TEXT::TransformBoundingBoxWithClearanceToPolygon( &aCornerBuffer, aClearance );
const FOOTPRINT* parentFootprint = static_cast<const FOOTPRINT*>( m_parent );
if( parentFootprint )
aCornerBuffer.Rotate( DECIDEG2RAD( GetDrawRotation() ), GetTextPos() );
}
void ZONE::TransformSolidAreasShapesToPolygon( PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aCornerBuffer,
int aError ) const
{
if( !m_FilledPolysList.count( aLayer ) || m_FilledPolysList.at( aLayer ).IsEmpty() )
return;
// Just add filled areas if filled polygons outlines have no thickness
if( !GetFilledPolysUseThickness() || GetMinThickness() == 0 )
{
const SHAPE_POLY_SET& polys = m_FilledPolysList.at( aLayer );
aCornerBuffer.Append( polys );
return;
}
// Filled areas have polygons with outline thickness.
// we must create the polygons and add inflated polys
SHAPE_POLY_SET polys = m_FilledPolysList.at( aLayer );
auto board = GetBoard();
int maxError = ARC_HIGH_DEF;
if( board )
maxError = board->GetDesignSettings().m_MaxError;
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
int numSegs = GetArcToSegmentCount( GetMinThickness(), maxError, 360.0 );
polys.InflateWithLinkedHoles( GetMinThickness()/2, numSegs, SHAPE_POLY_SET::PM_FAST );
aCornerBuffer.Append( polys );
}
void EDA_TEXT::TransformBoundingBoxWithClearanceToPolygon( SHAPE_POLY_SET* aCornerBuffer,
int aClearanceValue ) const
{
if( GetText().Length() == 0 )
return;
wxPoint corners[4]; // Buffer of polygon corners
2020-04-14 12:25:00 +00:00
EDA_RECT rect = GetTextBox();
rect.Inflate( aClearanceValue + Millimeter2iu( DEFAULT_TEXT_WIDTH ) );
corners[0].x = rect.GetOrigin().x;
corners[0].y = rect.GetOrigin().y;
corners[1].y = corners[0].y;
corners[1].x = rect.GetRight();
corners[2].x = corners[1].x;
corners[2].y = rect.GetBottom();
corners[3].y = corners[2].y;
corners[3].x = corners[0].x;
aCornerBuffer->NewOutline();
for( wxPoint& corner : corners )
{
// Rotate polygon
RotatePoint( &corner.x, &corner.y, GetTextPos().x, GetTextPos().y, GetTextAngle() );
aCornerBuffer->Append( corner.x, corner.y );
}
}
2012-01-29 19:29:19 +00:00
void PCB_TEXT::TransformTextShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
2021-07-19 23:56:05 +00:00
PCB_LAYER_ID aLayer, int aClearanceValue,
int aError, ERROR_LOC aErrorLoc ) const
{
wxSize size = GetTextSize();
2012-01-29 19:29:19 +00:00
if( IsMirrored() )
size.x = -size.x;
2012-01-29 19:29:19 +00:00
2020-04-14 12:25:00 +00:00
int penWidth = GetEffectiveTextPenWidth();
prms.m_cornerBuffer = &aCornerBuffer;
2020-04-14 12:25:00 +00:00
prms.m_textWidth = GetEffectiveTextPenWidth() + ( 2 * aClearanceValue );
prms.m_error = aError;
COLOR4D color; // not actually used, but needed by GRText
2012-01-29 19:29:19 +00:00
2021-07-19 23:56:05 +00:00
GRText( nullptr, GetTextPos(), color, GetShownText(), GetTextAngle(), size, GetHorizJustify(),
GetVertJustify(), penWidth, IsItalic(), IsBold(), addTextSegmToPoly, &prms );
2012-01-29 19:29:19 +00:00
}
void PCB_TEXT::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
2021-07-19 23:56:05 +00:00
PCB_LAYER_ID aLayer, int aClearance,
int aError, ERROR_LOC aErrorLoc,
bool aIgnoreLineWidth ) const
{
EDA_TEXT::TransformBoundingBoxWithClearanceToPolygon( &aCornerBuffer, aClearance );
}
void PCB_SHAPE::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
PCB_LAYER_ID aLayer, int aClearanceValue,
int aError, ERROR_LOC aErrorLoc,
bool ignoreLineWidth ) const
{
2020-11-13 21:00:28 +00:00
int width = ignoreLineWidth ? 0 : m_width;
width += 2 * aClearanceValue;
2020-11-13 21:00:28 +00:00
switch( m_shape )
{
case SHAPE_T::CIRCLE:
2020-11-14 01:16:02 +00:00
if( IsFilled() )
{
2020-11-14 01:16:02 +00:00
TransformCircleToPolygon( aCornerBuffer, GetCenter(), GetRadius() + width / 2, aError,
aErrorLoc );
}
else
{
TransformRingToPolygon( aCornerBuffer, GetCenter(), GetRadius(), width, aError,
aErrorLoc );
}
2021-07-19 23:56:05 +00:00
break;
case SHAPE_T::RECT:
{
std::vector<wxPoint> pts = GetRectCorners();
2020-11-14 01:16:02 +00:00
if( IsFilled() )
{
aCornerBuffer.NewOutline();
for( const wxPoint& pt : pts )
aCornerBuffer.Append( pt );
}
2020-11-14 01:16:02 +00:00
if( width > 0 || !IsFilled() )
{
// Add in segments
TransformOvalToPolygon( aCornerBuffer, pts[0], pts[1], width, aError, aErrorLoc );
TransformOvalToPolygon( aCornerBuffer, pts[1], pts[2], width, aError, aErrorLoc );
TransformOvalToPolygon( aCornerBuffer, pts[2], pts[3], width, aError, aErrorLoc );
TransformOvalToPolygon( aCornerBuffer, pts[3], pts[0], width, aError, aErrorLoc );
}
2021-07-19 23:56:05 +00:00
break;
2021-07-19 23:56:05 +00:00
}
case SHAPE_T::ARC:
TransformArcToPolygon( aCornerBuffer, GetArcStart(), GetArcMid(), GetArcEnd(), width,
aError, aErrorLoc );
break;
case SHAPE_T::SEGMENT:
2020-11-13 21:00:28 +00:00
TransformOvalToPolygon( aCornerBuffer, m_start, m_end, width, aError, aErrorLoc );
break;
case SHAPE_T::POLY:
2020-11-14 01:16:02 +00:00
{
if( !IsPolyShapeValid() )
break;
2020-11-14 01:16:02 +00:00
// The polygon is expected to be a simple polygon; not self intersecting, no hole.
FOOTPRINT* footprint = GetParentFootprint();
double orientation = footprint ? footprint->GetOrientation() : 0.0;
wxPoint offset;
2020-11-14 01:16:02 +00:00
if( footprint )
offset = footprint->GetPosition();
2020-11-14 01:16:02 +00:00
// Build the polygon with the actual position and orientation:
std::vector<wxPoint> poly;
2020-11-14 01:16:02 +00:00
poly = BuildPolyPointsList();
2020-11-14 01:16:02 +00:00
for( wxPoint& point : poly )
{
RotatePoint( &point, orientation );
point += offset;
}
2020-11-14 01:16:02 +00:00
if( IsFilled() )
{
aCornerBuffer.NewOutline();
for( const wxPoint& point : poly )
2020-11-14 01:16:02 +00:00
aCornerBuffer.Append( point.x, point.y );
}
2020-11-14 01:16:02 +00:00
if( width > 0 || !IsFilled() )
{
wxPoint pt1( poly[ poly.size() - 1] );
for( const wxPoint& pt2 : poly )
2020-11-14 01:16:02 +00:00
{
if( pt2 != pt1 )
TransformOvalToPolygon( aCornerBuffer, pt1, pt2, width, aError, aErrorLoc );
pt1 = pt2;
}
}
2021-07-19 23:56:05 +00:00
break;
2021-07-19 23:56:05 +00:00
}
case SHAPE_T::BEZIER: // Bezier curve
2020-11-14 01:16:02 +00:00
{
std::vector<wxPoint> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
BEZIER_POLY converter( ctrlPoints );
std::vector< wxPoint> poly;
converter.GetPoly( poly, m_width );
2020-11-14 01:16:02 +00:00
for( unsigned ii = 1; ii < poly.size(); ii++ )
{
2021-07-19 23:56:05 +00:00
TransformOvalToPolygon( aCornerBuffer, poly[ii - 1], poly[ii], width, aError,
aErrorLoc );
}
2021-07-19 23:56:05 +00:00
break;
2021-07-19 23:56:05 +00:00
}
default:
wxFAIL_MSG( "PCB_SHAPE::TransformShapeWithClearanceToPolygon no implementation for "
+ SHAPE_T_asString( m_shape ) );
break;
}
}
2021-06-11 21:07:02 +00:00
void PCB_TRACK::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
PCB_LAYER_ID aLayer, int aClearanceValue,
int aError, ERROR_LOC aErrorLoc,
bool ignoreLineWidth ) const
{
wxASSERT_MSG( !ignoreLineWidth, "IgnoreLineWidth has no meaning for tracks." );
switch( Type() )
{
case PCB_VIA_T:
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
{
int radius = ( m_Width / 2 ) + aClearanceValue;
TransformCircleToPolygon( aCornerBuffer, m_Start, radius, aError, aErrorLoc );
break;
2021-07-19 23:56:05 +00:00
}
case PCB_ARC_T:
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
{
2021-06-11 21:07:02 +00:00
const PCB_ARC* arc = static_cast<const PCB_ARC*>( this );
int width = m_Width + ( 2 * aClearanceValue );
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
TransformArcToPolygon( aCornerBuffer, arc->GetStart(), arc->GetMid(),
arc->GetEnd(), width, aError, aErrorLoc );
break;
2021-07-19 23:56:05 +00:00
}
default:
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
{
int width = m_Width + ( 2 * aClearanceValue );
TransformOvalToPolygon( aCornerBuffer, m_Start, m_End, width, aError, aErrorLoc );
break;
}
2021-07-19 23:56:05 +00:00
}
}
2020-11-12 22:30:02 +00:00
void PAD::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
PCB_LAYER_ID aLayer, int aClearanceValue,
int aError, ERROR_LOC aErrorLoc,
bool ignoreLineWidth ) const
{
wxASSERT_MSG( !ignoreLineWidth, "IgnoreLineWidth has no meaning for pads." );
// minimal segment count to approximate a circle to create the polygonal pad shape
// This minimal value is mainly for very small pads, like SM0402.
// Most of time pads are using the segment count given by aError value.
const int pad_min_seg_per_circle_count = 16;
double angle = m_orient;
int dx = m_size.x / 2;
int dy = m_size.y / 2;
wxPoint padShapePos = ShapePos(); // Note: for pad having a shape offset,
// the pad position is NOT the shape position
switch( GetShape() )
{
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::CIRCLE:
case PAD_SHAPE::OVAL:
// Note: dx == dy is not guaranteed for circle pads in legacy boards
if( dx == dy || ( GetShape() == PAD_SHAPE::CIRCLE ) )
{
TransformCircleToPolygon( aCornerBuffer, padShapePos, dx + aClearanceValue, aError,
aErrorLoc, pad_min_seg_per_circle_count );
}
else
{
int half_width = std::min( dx, dy );
wxPoint delta( dx - half_width, dy - half_width );
RotatePoint( &delta, angle );
TransformOvalToPolygon( aCornerBuffer, padShapePos - delta, padShapePos + delta,
( half_width + aClearanceValue ) * 2, aError, aErrorLoc,
pad_min_seg_per_circle_count );
}
break;
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::TRAPEZOID:
case PAD_SHAPE::RECT:
{
2021-05-01 12:22:35 +00:00
int ddx = GetShape() == PAD_SHAPE::TRAPEZOID ? m_deltaSize.x / 2 : 0;
int ddy = GetShape() == PAD_SHAPE::TRAPEZOID ? m_deltaSize.y / 2 : 0;
SHAPE_POLY_SET outline;
TransformTrapezoidToPolygon( outline, padShapePos, m_size, angle, ddx, ddy,
aClearanceValue, aError, aErrorLoc );
2016-04-06 18:15:49 +00:00
aCornerBuffer.Append( outline );
break;
2021-07-19 23:56:05 +00:00
}
2016-04-06 18:15:49 +00:00
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::CHAMFERED_RECT:
case PAD_SHAPE::ROUNDRECT:
2016-04-06 18:15:49 +00:00
{
bool doChamfer = GetShape() == PAD_SHAPE::CHAMFERED_RECT;
SHAPE_POLY_SET outline;
TransformRoundChamferedRectToPolygon( outline, padShapePos, m_size, angle,
GetRoundRectCornerRadius(),
doChamfer ? GetChamferRectRatio() : 0,
doChamfer ? GetChamferPositions() : 0,
aClearanceValue, aError, aErrorLoc );
aCornerBuffer.Append( outline );
break;
2021-07-19 23:56:05 +00:00
}
2021-05-01 12:22:35 +00:00
case PAD_SHAPE::CUSTOM:
{
SHAPE_POLY_SET outline;
MergePrimitivesAsPolygon( &outline, aLayer, aErrorLoc );
outline.Rotate( -DECIDEG2RAD( m_orient ) );
outline.Move( VECTOR2I( m_pos ) );
if( aClearanceValue )
{
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
int numSegs = std::max( GetArcToSegmentCount( aClearanceValue, aError, 360.0 ),
pad_min_seg_per_circle_count );
int clearance = aClearanceValue;
if( aErrorLoc == ERROR_OUTSIDE )
{
int actual_error = CircleToEndSegmentDeltaRadius( clearance, numSegs );
clearance += GetCircleToPolyCorrection( actual_error );
}
outline.Inflate( clearance, numSegs );
2020-09-22 21:05:09 +00:00
outline.Simplify( SHAPE_POLY_SET::PM_FAST );
outline.Fracture( SHAPE_POLY_SET::PM_FAST );
}
aCornerBuffer.Append( outline );
break;
2021-07-19 23:56:05 +00:00
}
default:
2020-11-12 22:30:02 +00:00
wxFAIL_MSG( "PAD::TransformShapeWithClearanceToPolygon no implementation for "
+ PAD_SHAPE_T_asString( GetShape() ) );
break;
}
}
2016-04-06 18:15:49 +00:00
2020-11-12 22:30:02 +00:00
bool PAD::TransformHoleWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aInflateValue,
int aError, ERROR_LOC aErrorLoc ) const
{
wxSize drillsize = GetDrillSize();
2014-05-17 19:29:15 +00:00
if( !drillsize.x || !drillsize.y )
return false;
const SHAPE_SEGMENT* seg = GetEffectiveHoleShape();
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
TransformOvalToPolygon( aCornerBuffer, (wxPoint) seg->GetSeg().A, (wxPoint) seg->GetSeg().B,
seg->GetWidth() + aInflateValue * 2, aError, aErrorLoc );
return true;
}
void ZONE::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
PCB_LAYER_ID aLayer, int aClearance, int aError,
ERROR_LOC aErrorLoc, bool aIgnoreLineWidth ) const
{
wxASSERT_MSG( !aIgnoreLineWidth, "IgnoreLineWidth has no meaning for zones." );
if( !m_FilledPolysList.count( aLayer ) )
return;
aCornerBuffer = m_FilledPolysList.at( aLayer );
int numSegs = GetArcToSegmentCount( aClearance, aError, 360.0 );
aCornerBuffer.Inflate( aClearance, numSegs );
aCornerBuffer.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
}
2021-06-11 16:59:28 +00:00
void PCB_DIMENSION_BASE::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
PCB_LAYER_ID aLayer, int aClearance,
int aError, ERROR_LOC aErrorLoc,
bool aIgnoreLineWidth ) const
{
wxASSERT_MSG( !aIgnoreLineWidth, "IgnoreLineWidth has no meaning for dimensions." );
for( const std::shared_ptr<SHAPE>& shape : m_shapes )
{
const SHAPE_CIRCLE* circle = dynamic_cast<const SHAPE_CIRCLE*>( shape.get() );
const SHAPE_SEGMENT* seg = dynamic_cast<const SHAPE_SEGMENT*>( shape.get() );
if( circle )
{
TransformCircleToPolygon( aCornerBuffer, (wxPoint) circle->GetCenter(),
circle->GetRadius() + m_lineThickness / 2 + aClearance,
aError, aErrorLoc );
}
else if( seg )
{
TransformOvalToPolygon( aCornerBuffer, (wxPoint) seg->GetSeg().A,
(wxPoint) seg->GetSeg().B, m_lineThickness + 2 * aClearance,
aError, aErrorLoc );
}
else
{
2021-07-19 23:56:05 +00:00
wxFAIL_MSG( "PCB_DIMENSION_BASE::TransformShapeWithClearanceToPolygon unexpected "
"shape type." );
}
}
}