diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index bcfd584339..a061a8c534 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -350,6 +350,7 @@ set( COMMON_SRCS tool/zoom_tool.cpp geometry/convex_hull.cpp + geometry/geometry_utils.cpp geometry/seg.cpp geometry/shape.cpp geometry/shape_collisions.cpp diff --git a/common/geometry/geometry_utils.cpp b/common/geometry/geometry_utils.cpp new file mode 100644 index 0000000000..a3c6296ec6 --- /dev/null +++ b/common/geometry/geometry_utils.cpp @@ -0,0 +1,65 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr + * Copyright (C) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file geometry_utils.cpp + * @brief a few functions useful in geometry calculations. + */ + +#include +#include +#include + + +int GetArcToSegmentCount( int aRadius, int aErrorMax, double aArcAngleDegree ) +{ + // calculate the number of segments to approximate a circle by segments + // given the max distance between the middle of a segment and the circle + + // error relative to the radius value: + double rel_error = (double)aErrorMax / aRadius; + // minimal arc increment in degrees: + double step = 180 / M_PI * acos( 1.0 - rel_error ) * 2; + // the minimal seg count for a arc + int segCount = KiROUND( fabs( aArcAngleDegree ) / step ); + + // Ensure at least one segment is used + return std::max( segCount, 1 ); +} + + +double GetCircletoPolyCorrectionFactor( int aSegCountforCircle ) +{ + /* calculates the coeff to compensate radius reduction of circle + * due to the segment approx. + * For a circle the min radius is radius * cos( 2PI / aSegCountforCircle / 2) + * this is the distance between the center and the middle of the segment. + * therfore, to move the middle of the segment to the circle (distance = radius) + * the correctionFactor is 1 /cos( PI/aSegCountforCircle ) + */ + double correctionFactor = 1.0 / cos( M_PI / aSegCountforCircle ); + + return correctionFactor; +} + diff --git a/include/geometry/geometry_utils.h b/include/geometry/geometry_utils.h new file mode 100644 index 0000000000..0c5cc3915b --- /dev/null +++ b/include/geometry/geometry_utils.h @@ -0,0 +1,58 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr + * Copyright (C) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file geometry_utils.h + * @brief a few functions useful in geometry calculations. + */ + +#ifndef GEOMETRY_UTILS_H +#define GEOMETRY_UTILS_H + +/** + * @return the number of segments to approximate a arc by segments + * with a given max error (this number is >= 1) + * @param aRadius is the radius od the circle or arc + * @param aErrorMax is the max error + * This is the max distance between the middle of a segment and the circle. + * @param aArcAngleDegree is the arc angle in degrees + */ +int GetArcToSegmentCount( int aRadius, int aErrorMax, double aArcAngleDegree ); + +/** + * @return the correction factor to approximate a circle by segùments + * @param aSegCountforCircle is the number of segments to approximate the circle + * + * When creating a polygon from a circle, the polygon is inside the circle. + * Only corners are on the circle. + * This is incorrect when building clearance areas of circles, that need to build + * the equivalent polygon outside the circle + * The correction factor is a scaling factor to apply to the radius to build a + * polygon outside the circle (only the middle of each segment is on the circle + */ +double GetCircletoPolyCorrectionFactor( int aSegCountforCircle ); + + +#endif // #ifndef GEOMETRY_UTILS_H + diff --git a/pcbnew/board_items_to_polygon_shape_transform.cpp b/pcbnew/board_items_to_polygon_shape_transform.cpp index 68c2fb50a3..085ffbc3bc 100644 --- a/pcbnew/board_items_to_polygon_shape_transform.cpp +++ b/pcbnew/board_items_to_polygon_shape_transform.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2009-2016 Jean-Pierre Charras, jp.charras at wanadoo.fr - * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -47,6 +47,7 @@ #include #include #include +#include // These variables are parameters used in addTextSegmToPoly. // But addTextSegmToPoly is a call-back function, @@ -55,6 +56,11 @@ static int s_textWidth; static int s_textCircle2SegmentCount; static SHAPE_POLY_SET* s_cornerBuffer; +// The max error is the distance between the middle of a segment, and the circle +// for circle/arc to segment approximation. +// Warning: too small values can create very long calculation time in zone filling +double s_error_max = Millimeter2iu( 0.01 ); + // This is a call back function, used by DrawGraphicText to draw the 3D text shape: static void addTextSegmToPoly( int x0, int y0, int xf, int yf ) { @@ -68,7 +74,7 @@ void BOARD::ConvertBrdLayerToPolygonalContours( PCB_LAYER_ID aLayer, SHAPE_POLY_ { // Number of segments to convert a circle to a polygon const int segcountforcircle = 18; - double correctionFactor = 1.0 / cos( M_PI / (segcountforcircle * 2) ); + double correctionFactor = GetCircletoPolyCorrectionFactor( segcountforcircle ); // convert tracks and vias: for( TRACK* track = m_Track; track != NULL; track = track->Next() ) @@ -504,25 +510,12 @@ void DRAWSEGMENT::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerB if( m_Shape == S_CIRCLE || m_Shape == S_ARC ) { - // this is an ugly hack, but I don't want to change the file format now that we are in feature freeze. - // the circle to segment count parameter is essentially useless for larger circle/arc shapes as - // it doesn't provide sufficient approximation accuracy. - // Here we compute the necessary number of segments based on error between circle and segments - // The max error is the distance between the middle of a segment, and the circle - // - // Warning: too small values can create very long calculation time in zone filling - double error_max = Millimeter2iu( 0.05 ); - // error relative to the radius value - double rel_error = error_max / GetRadius(); - // best arc increment - double step = 180 / M_PI * acos( 1.0 - rel_error ); - // the best seg count for a circle - int segCount = KiROUND( 360.0 / step ); + int segCount = GetArcToSegmentCount( GetRadius(), s_error_max, 360.0 ); if( segCount > aCircleToSegmentsCount ) { aCircleToSegmentsCount = segCount; - aCorrectionFactor = 1.0 / cos( M_PI / (aCircleToSegmentsCount * 2) ); + aCorrectionFactor = GetCircletoPolyCorrectionFactor( aCircleToSegmentsCount ); } } diff --git a/pcbnew/convert_drawsegment_list_to_polygon.cpp b/pcbnew/convert_drawsegment_list_to_polygon.cpp index 12364ff77e..26a6eed19c 100644 --- a/pcbnew/convert_drawsegment_list_to_polygon.cpp +++ b/pcbnew/convert_drawsegment_list_to_polygon.cpp @@ -35,6 +35,7 @@ #include #include #include +#include /** @@ -167,16 +168,11 @@ static DRAWSEGMENT* findPoint( const wxPoint& aPoint, std::vector< DRAWSEGMENT* } +// Error max when converting a circle or arc to segments. +// Avoid too small values that create a very long calculation time +// in zone fillings #define ARC_ACCURACY ( 0.05 * IU_PER_MM ) -int calcArcSteps( int aRadius, int aSweptAngle ) -{ - int circleSteps = M_PI / acos( ( aRadius - ARC_ACCURACY ) / aRadius ); - int arcSteps = circleSteps * fabs( aSweptAngle ) / 3600.0; - - return std::max( arcSteps, 1 ); -} - /** * Function ConvertOutlineToPolygon @@ -238,7 +234,8 @@ bool ConvertOutlineToPolygon( std::vector< DRAWSEGMENT* >& aSegList, wxPoint pstart = graphic->GetArcStart(); wxPoint center = graphic->GetCenter(); double angle = -graphic->GetAngle(); - int steps = calcArcSteps( graphic->GetRadius(), angle ); + int steps = GetArcToSegmentCount( graphic->GetRadius(), ARC_ACCURACY, + (double)angle / 10.0 ); wxPoint pt; for( int step = 1; step<=steps; ++step ) @@ -296,9 +293,8 @@ bool ConvertOutlineToPolygon( std::vector< DRAWSEGMENT* >& aSegList, // Output the Edge.Cuts perimeter as circle or polygon. if( graphic->GetShape() == S_CIRCLE ) { - TransformCircleToPolygon( aPolygons, graphic->GetCenter(), - graphic->GetRadius(), - calcArcSteps( graphic->GetRadius(), 3600 ) ); + int steps = GetArcToSegmentCount( graphic->GetRadius(), ARC_ACCURACY, 360.0 ); + TransformCircleToPolygon( aPolygons, graphic->GetCenter(), graphic->GetRadius(), steps ); } else { @@ -344,7 +340,8 @@ bool ConvertOutlineToPolygon( std::vector< DRAWSEGMENT* >& aSegList, wxPoint pend = graphic->GetArcEnd(); wxPoint pcenter = graphic->GetCenter(); double angle = -graphic->GetAngle(); - int steps = calcArcSteps( graphic->GetRadius(), angle ); + int steps = GetArcToSegmentCount( graphic->GetRadius(), ARC_ACCURACY, + (double)angle / 10.0 ); if( !close_enough( prevPt, pstart, prox ) ) { @@ -436,7 +433,7 @@ bool ConvertOutlineToPolygon( std::vector< DRAWSEGMENT* >& aSegList, double angle = 3600.0; wxPoint start = center; int radius = graphic->GetRadius(); - int steps = calcArcSteps( radius, angle ); + int steps = GetArcToSegmentCount( graphic->GetRadius(), ARC_ACCURACY, 360.0 ); wxPoint nextPt; start.x += radius; @@ -494,7 +491,8 @@ bool ConvertOutlineToPolygon( std::vector< DRAWSEGMENT* >& aSegList, wxPoint pend = graphic->GetArcEnd(); wxPoint pcenter = graphic->GetCenter(); double angle = -graphic->GetAngle(); - int steps = calcArcSteps( graphic->GetRadius(), angle ); + int steps = GetArcToSegmentCount( graphic->GetRadius(), ARC_ACCURACY, + (double)angle / 10.0 ); if( !close_enough( prevPt, pstart, prox ) ) { diff --git a/pcbnew/specctra_import_export/specctra_export.cpp b/pcbnew/specctra_import_export/specctra_export.cpp index f87eaf83b2..dccf7f07f1 100644 --- a/pcbnew/specctra_import_export/specctra_export.cpp +++ b/pcbnew/specctra_import_export/specctra_export.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include "specctra.h" @@ -765,11 +766,13 @@ IMAGE* SPECCTRA_DB::makeIMAGE( BOARD* aBoard, MODULE* aModule ) double radius = GetLineLength( graphic->GetStart(), graphic->GetEnd() ); - const int SEG_PER_CIRCLE = 36; // seg count to approximate circle by line segments + // seg count to approximate circle by line segments + int err_max = Millimeter2iu( 0.05 ); + int seg_per_circle = GetArcToSegmentCount( radius, err_max, 360.0 ); - for( int ii = 0; ii < SEG_PER_CIRCLE; ++ii ) + for( int ii = 0; ii < seg_per_circle; ++ii ) { - double radians = 2*M_PI / SEG_PER_CIRCLE * ii; + double radians = 2*M_PI / seg_per_circle * ii; wxPoint point( KiROUND( radius * cos( radians ) ), KiROUND( radius * sin( radians ) ) ); diff --git a/pcbnew/zone_filler.cpp b/pcbnew/zone_filler.cpp index c21c7bc0a7..8e5367ada6 100644 --- a/pcbnew/zone_filler.cpp +++ b/pcbnew/zone_filler.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include "zone_filler.h" @@ -299,10 +300,10 @@ void ZONE_FILLER::buildZoneFeatureHoleList( const ZONE_CONTAINER* aZone, /* 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) - * correctionFactor is 1 /cos( PI/s_CircleToSegmentsCount ) + * For a circle the min radius is radius * cos( 2PI / segsPerCircle / 2) + * correctionFactor is 1 /cos( PI/segsPerCircle ) */ - correctionFactor = 1.0 / cos( M_PI / (double) segsPerCircle ); + correctionFactor = GetCircletoPolyCorrectionFactor( segsPerCircle ); aFeatures.RemoveAllContours(); @@ -683,11 +684,8 @@ void ZONE_FILLER::computeRawFilledAreas( const ZONE_CONTAINER* aZone, segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_LOW_DEF; /* 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 ) */ - correctionFactor = 1.0 / cos( M_PI / (double) segsPerCircle ); + correctionFactor = GetCircletoPolyCorrectionFactor( segsPerCircle ); if( s_DumpZonesWhenFilling ) dumper->BeginGroup( "clipper-zone" );