KiCad PCB EDA Suite
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
corner_operations.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include <geometry/circle.h>
27#include <geometry/half_line.h>
28#include <geometry/shape_arc.h>
33#include <trigo.h>
34
35namespace
36{
37
41SEG GetBisectorOfCornerSegments( const SEG& aSegA, const SEG& aSegB, int aLength )
42{
43 // Use the "parallelogram" method to find the bisector
44
45 // The intersection point of the two lines is the one that is shared by both segments
46 const std::optional<VECTOR2I> corner = KIGEOM::GetSharedEndpoint( aSegA, aSegB );
47
48 // Get the vector of a segment pointing away from a point
49 const auto getSegVectorPointingAwayFrom = []( const SEG& aSeg,
50 const VECTOR2I& aPoint ) -> VECTOR2I
51 {
52 const int distA = ( aSeg.A - aPoint ).EuclideanNorm();
53 const int distB = ( aSeg.B - aPoint ).EuclideanNorm();
54
55 // If A is closer the segment is already pointing away from the corner
56 // If B is closer, we need to reverse the segment
57 return ( distA < distB ) ? ( aSeg.B - aSeg.A ) : ( aSeg.A - aSeg.B );
58 };
59
60 // The vectors have to be the same length
61 const int maxLen = std::max( aSegA.Length(), aSegB.Length() );
62 const VECTOR2I aVecOutward = getSegVectorPointingAwayFrom( aSegA, *corner ).Resize( maxLen );
63 const VECTOR2I bVecOutward = getSegVectorPointingAwayFrom( aSegB, *corner ).Resize( maxLen );
64 const VECTOR2I bisectorOutward = aVecOutward + bVecOutward;
65
66 return SEG( *corner, *corner + bisectorOutward.Resize( aLength ) );
67}
68
69} // namespace
70
71std::optional<CHAMFER_RESULT> ComputeChamferPoints( const SEG& aSegA, const SEG& aSegB,
72 const CHAMFER_PARAMS& aChamferParams )
73{
74 const int line_a_setback = aChamferParams.m_chamfer_setback_a;
75 const int line_b_setback = aChamferParams.m_chamfer_setback_b;
76
77 if( line_a_setback == 0 && line_b_setback == 0 )
78 {
79 // No chamfer to do
80 // In theory a chamfer of 0 on one side is kind-of valid (adds a collinear point)
81 // so allow it (using an and above, not an or)
82 return std::nullopt;
83 }
84
85 if( aSegA.Length() < line_a_setback || aSegB.Length() < line_b_setback )
86 {
87 // Chamfer is too big for the line segments
88 return std::nullopt;
89 }
90
91 // We only support the case where the lines intersect at the end points
92 // otherwise we would need to decide which inside corner to chamfer
93
94 // Figure out which end points are the ones at the intersection
95 const std::optional<VECTOR2I> corner = KIGEOM::GetSharedEndpoint( aSegA, aSegB );
96
97 if( !corner )
98 {
99 // The lines are not coterminous
100 return std::nullopt;
101 }
102
103 // These are the other existing line points (the ones that are not the intersection)
104 const VECTOR2I& a_end_pt = KIGEOM::GetOtherEnd( aSegA, *corner );
105 const VECTOR2I& b_end_pt = KIGEOM::GetOtherEnd( aSegB, *corner );
106
107 // Now, construct segment of the set-back lengths, that begins
108 // at the intersection point and is parallel to each line segments
109 SEG setback_a( *corner, *corner + VECTOR2I( a_end_pt - *corner ).Resize( line_a_setback ) );
110 SEG setback_b( *corner, *corner + VECTOR2I( b_end_pt - *corner ).Resize( line_b_setback ) );
111
112 // The chamfer segment then goes between the end points of the set-back segments
113 SEG chamfer( setback_a.B, setback_b.B );
114
115 // The adjusted segments go from the old end points to the chamfer ends
116
117 std::optional<SEG> new_a;
118
119 if( a_end_pt != chamfer.A )
120 new_a = SEG{ a_end_pt, chamfer.A };
121
122 std::optional<SEG> new_b;
123
124 if( b_end_pt != chamfer.B )
125 new_b = SEG{ b_end_pt, chamfer.B };
126
127 return CHAMFER_RESULT{ chamfer, new_a, new_b };
128}
129
130
131std::optional<DOGBONE_RESULT> ComputeDogbone( const SEG& aSegA, const SEG& aSegB,
132 int aDogboneRadius, bool aAddSlots )
133{
134 const std::optional<VECTOR2I> corner = KIGEOM::GetSharedEndpoint( aSegA, aSegB );
135
136 // Cannot handle parallel lines
137 if( !corner || aSegA.Angle( aSegB ).IsHorizontal() )
138 {
139 return std::nullopt;
140 }
141
142 const SEG bisector = GetBisectorOfCornerSegments( aSegA, aSegB, aDogboneRadius );
143
144 // The dogbone center is the end of the bisector
145 const VECTOR2I dogboneCenter = bisector.B;
146
147 // We can find the ends of the arc by considering the corner angle,
148 // but it's easier to just intersect the circle with the original segments.
149
150 const CIRCLE circle( dogboneCenter, aDogboneRadius );
151
152 const std::vector<VECTOR2I> segAIntersections = circle.Intersect( aSegA );
153 const std::vector<VECTOR2I> segBIntersections = circle.Intersect( aSegB );
154
155 // There can be a little bit of error in the intersection calculation
156 static int s_epsilon = 8;
157
158 const auto getPointNotOnCorner =
159 [&]( const std::vector<VECTOR2I>& aIntersections, const VECTOR2I& aCorner )
160 {
161 std::optional<VECTOR2I> result;
162 for( const VECTOR2I& pt : aIntersections )
163 {
164 if( aCorner.Distance( pt ) > s_epsilon )
165 {
166 result = pt;
167 break;
168 }
169 }
170 return result;
171 };
172
173 const std::optional<VECTOR2I> ptOnSegA = getPointNotOnCorner( segAIntersections, *corner );
174 const std::optional<VECTOR2I> ptOnSegB = getPointNotOnCorner( segBIntersections, *corner );
175
176 // The arc doesn't cross one or both of the segments
177 if( !ptOnSegA || !ptOnSegB )
178 {
179 return std::nullopt;
180 }
181
182 SHAPE_ARC arc( *ptOnSegA, *corner, *ptOnSegB, 0 );
183
184 const VECTOR2I& aOtherPtA = KIGEOM::GetOtherEnd( aSegA, *corner );
185 const VECTOR2I& aOtherPtB = KIGEOM::GetOtherEnd( aSegB, *corner );
186
187 const EDA_ANGLE angle_epsilon( 1e-3, EDA_ANGLE_T::DEGREES_T );
188 const bool small_arc_mouth = std::abs( arc.GetCentralAngle() ) > ( ANGLE_180 + angle_epsilon );
189
190 {
191 // See if we need to update the original segments
192 // or if the dogbone consumed them
193 std::optional<SEG> new_a, new_b;
194 if( aOtherPtA != *ptOnSegA )
195 new_a = SEG{ aOtherPtA, *ptOnSegA };
196
197 if( aOtherPtB != *ptOnSegB )
198 new_b = SEG{ aOtherPtB, *ptOnSegB };
199
200 // Nice and easy
201 if( !small_arc_mouth || !aAddSlots )
202 {
203 return DOGBONE_RESULT{
204 arc,
205 new_a,
206 new_b,
207 small_arc_mouth,
208 };
209 }
210 }
211
212 // If it's a small mouth, we can try to work out the minimal slot to allow
213
214 // First the arc will be pulled back to 180 degrees
215 SHAPE_ARC slotArc = SHAPE_ARC( GetRotated( *corner, dogboneCenter, ANGLE_90 ), *corner,
216 GetRotated( *corner, dogboneCenter, -ANGLE_90 ), 0 );
217
218 // Make sure P0 is still the 'A' end
219 if( !KIGEOM::PointsAreInSameDirection( slotArc.GetP0(), arc.GetP0(), dogboneCenter ) )
220 {
221 slotArc.Reverse();
222 }
223
224 // Take the bisector and glue it to the arc ends
225 const HALF_LINE arc_extension_a{
226 slotArc.GetP0(),
227 slotArc.GetP0() + ( dogboneCenter - *corner ),
228 };
229 const HALF_LINE arc_extension_b{
230 slotArc.GetP1(),
231 slotArc.GetP1() + ( dogboneCenter - *corner ),
232 };
233
234 const OPT_VECTOR2I ext_a_intersect = arc_extension_a.Intersect( aSegA );
235 const OPT_VECTOR2I ext_b_intersect = arc_extension_b.Intersect( aSegB );
236
237 if( !ext_a_intersect || !ext_b_intersect )
238 {
239 // The arc extensions don't intersect the original segments
240 return std::nullopt;
241 }
242
243 {
244 // See if we need to update the original segments
245 // or if the dogbone consumed them
246 std::optional<SEG> new_a, new_b;
247 if( aOtherPtA != *ext_a_intersect )
248 new_a = SEG{ aOtherPtA, *ext_a_intersect };
249
250 if( aOtherPtB != *ext_b_intersect )
251 new_b = SEG{ aOtherPtB, *ext_b_intersect };
252
253 return DOGBONE_RESULT{
254 slotArc,
255 new_a,
256 new_b,
257 small_arc_mouth,
258 };
259 }
260}
261
262
264 unsigned int aDistance,
265 int aIndex, int aErrorMax )
266{
267 // Null segments create serious issues in calculations. Remove them:
269
270 SHAPE_POLY_SET::POLYGON currentPoly = Polygon( aIndex );
272
273 // If the chamfering distance is zero, then the polygon remain intact.
274 if( aDistance == 0 )
275 {
276 return currentPoly;
277 }
278
279 // Iterate through all the contours (outline and holes) of the polygon.
280 for( SHAPE_LINE_CHAIN& currContour : currentPoly )
281 {
282 // Generate a new contour in the new polygon
283 SHAPE_LINE_CHAIN newContour;
284
285 // Iterate through the vertices of the contour
286 for( int currVertex = 0; currVertex < currContour.PointCount(); currVertex++ )
287 {
288 // Current vertex
289 int x1 = currContour.CPoint( currVertex ).x;
290 int y1 = currContour.CPoint( currVertex ).y;
291
292 // Indices for previous and next vertices.
293 int prevVertex;
294 int nextVertex;
295
296 // Previous and next vertices indices computation. Necessary to manage the edge cases.
297
298 // Previous vertex is the last one if the current vertex is the first one
299 prevVertex = currVertex == 0 ? currContour.PointCount() - 1 : currVertex - 1;
300
301 // next vertex is the first one if the current vertex is the last one.
302 nextVertex = currVertex == currContour.PointCount() - 1 ? 0 : currVertex + 1;
303
304 // Previous vertex computation
305 double xa = currContour.CPoint( prevVertex ).x - x1;
306 double ya = currContour.CPoint( prevVertex ).y - y1;
307
308 // Next vertex computation
309 double xb = currContour.CPoint( nextVertex ).x - x1;
310 double yb = currContour.CPoint( nextVertex ).y - y1;
311
312 // Avoid segments that will generate nans below
313 if( std::abs( xa + xb ) < std::numeric_limits<double>::epsilon()
314 && std::abs( ya + yb ) < std::numeric_limits<double>::epsilon() )
315 {
316 continue;
317 }
318
319 // Compute the new distances
320 double lena = hypot( xa, ya );
321 double lenb = hypot( xb, yb );
322
323 // Make the final computations depending on the mode selected, chamfered or filleted.
324 if( aMode == CORNER_MODE::CHAMFERED )
325 {
326 double distance = aDistance;
327
328 // Chamfer one half of an edge at most
329 if( 0.5 * lena < distance )
330 distance = 0.5 * lena;
331
332 if( 0.5 * lenb < distance )
333 distance = 0.5 * lenb;
334
335 int nx1 = KiROUND( distance * xa / lena );
336 int ny1 = KiROUND( distance * ya / lena );
337
338 newContour.Append( x1 + nx1, y1 + ny1 );
339
340 int nx2 = KiROUND( distance * xb / lenb );
341 int ny2 = KiROUND( distance * yb / lenb );
342
343 newContour.Append( x1 + nx2, y1 + ny2 );
344 }
345 else // CORNER_MODE = FILLETED
346 {
347 double cosine = ( xa * xb + ya * yb ) / ( lena * lenb );
348
349 double radius = aDistance;
350 double denom = sqrt( 2.0 / ( 1 + cosine ) - 1 );
351
352 // Do nothing in case of parallel edges
353 if( std::isinf( denom ) )
354 continue;
355
356 // Limit rounding distance to one half of an edge
357 if( 0.5 * lena * denom < radius )
358 radius = 0.5 * lena * denom;
359
360 if( 0.5 * lenb * denom < radius )
361 radius = 0.5 * lenb * denom;
362
363 // Calculate fillet arc absolute center point (xc, yx)
364 double k = radius / sqrt( .5 * ( 1 - cosine ) );
365 double lenab = sqrt( ( xa / lena + xb / lenb ) * ( xa / lena + xb / lenb ) +
366 ( ya / lena + yb / lenb ) * ( ya / lena + yb / lenb ) );
367 double xc = x1 + k * ( xa / lena + xb / lenb ) / lenab;
368 double yc = y1 + k * ( ya / lena + yb / lenb ) / lenab;
369
370 // Calculate arc start and end vectors
371 k = radius / sqrt( 2 / ( 1 + cosine ) - 1 );
372 double xs = x1 + k * xa / lena - xc;
373 double ys = y1 + k * ya / lena - yc;
374 double xe = x1 + k * xb / lenb - xc;
375 double ye = y1 + k * yb / lenb - yc;
376
377 // Cosine of arc angle
378 double argument = ( xs * xe + ys * ye ) / ( radius * radius );
379
380 // Make sure the argument is in [-1,1], interval in which the acos function is
381 // defined
382 if( argument < -1 )
383 argument = -1;
384 else if( argument > 1 )
385 argument = 1;
386
387 double arcAngle = acos( argument );
388 int segments = GetArcToSegmentCount( radius, aErrorMax,
389 EDA_ANGLE( arcAngle, RADIANS_T ) );
390
391 double deltaAngle = arcAngle / segments;
392 double startAngle = atan2( -ys, xs );
393
394 // Flip arc for inner corners
395 if( xa * yb - ya * xb <= 0 )
396 deltaAngle *= -1;
397
398 double nx = xc + xs;
399 double ny = yc + ys;
400
401 if( std::isnan( nx ) || std::isnan( ny ) )
402 continue;
403
404 newContour.Append( KiROUND( nx ), KiROUND( ny ) );
405
406 // Store the previous added corner to make a sanity check
407 int prevX = KiROUND( nx );
408 int prevY = KiROUND( ny );
409
410 for( int j = 0; j < segments; j++ )
411 {
412 nx = xc + cos( startAngle + ( j + 1 ) * deltaAngle ) * radius;
413 ny = yc - sin( startAngle + ( j + 1 ) * deltaAngle ) * radius;
414
415 if( std::isnan( nx ) || std::isnan( ny ) )
416 continue;
417
418 // Sanity check: the rounding can produce repeated corners; do not add them.
419 if( KiROUND( nx ) != prevX || KiROUND( ny ) != prevY )
420 {
421 newContour.Append( KiROUND( nx ), KiROUND( ny ) );
422 prevX = KiROUND( nx );
423 prevY = KiROUND( ny );
424 }
425 }
426 }
427 }
428
429 // Close the current contour and add it the new polygon
430 newContour.SetClosed( true );
431 newPoly.push_back( newContour );
432 }
433
434 return newPoly;
435}
436
437
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition: box2.h:990
Represent basic circle geometry with utility geometry functions.
Definition: circle.h:33
bool IsHorizontal() const
Definition: eda_angle.h:138
Definition: seg.h:42
VECTOR2I A
Definition: seg.h:49
VECTOR2I B
Definition: seg.h:50
int Length() const
Return the length (this).
Definition: seg.h:333
EDA_ANGLE Angle(const SEG &aOther) const
Determine the smallest angle between two segments.
Definition: seg.cpp:97
EDA_ANGLE GetCentralAngle() const
Definition: shape_arc.cpp:864
const VECTOR2I & GetP1() const
Definition: shape_arc.h:117
void Reverse()
Definition: shape_arc.cpp:1000
const VECTOR2I & GetP0() const
Definition: shape_arc.h:116
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
CORNER_MODE
Operation ChamferPolygon and FilletPolygon are computed under the private chamferFillet method; this ...
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
int RemoveNullSegments()
Look for null segments; ie, segments whose ends are exactly the same and deletes them.
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
POLYGON chamferFilletPolygon(CORNER_MODE aMode, unsigned int aDistance, int aIndex, int aErrorMax)
Return the chamfered or filleted version of the aIndex-th polygon in the set, depending on the aMode ...
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition: vector2d.h:385
std::optional< CHAMFER_RESULT > ComputeChamferPoints(const SEG &aSegA, const SEG &aSegB, const CHAMFER_PARAMS &aChamferParams)
Compute the chamfer points for a given line pair and chamfer parameters.
std::optional< DOGBONE_RESULT > ComputeDogbone(const SEG &aSegA, const SEG &aSegB, int aDogboneRadius, bool aAddSlots)
Compute the dogbone geometry for a given line pair and dogbone parameters.
static constexpr EDA_ANGLE ANGLE_90
Definition: eda_angle.h:403
@ RADIANS_T
Definition: eda_angle.h:32
@ DEGREES_T
Definition: eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_180
Definition: eda_angle.h:405
a few functions useful in geometry calculations.
int GetArcToSegmentCount(int aRadius, int aErrorMax, const EDA_ANGLE &aArcAngle)
OPT_VECTOR2I GetSharedEndpoint(const SEG &aSegA, const SEG &aSegB)
Get the shared endpoint of two segments, if it exists, or std::nullopt if the segments are not connec...
Definition: shape_utils.cpp:51
bool PointsAreInSameDirection(const VECTOR2< T > &aPointA, const VECTOR2< T > &aPointB, const VECTOR2< T > &aFrom)
Check that the two given points are in the same direction from some other point.
Definition: vector_utils.h:70
const VECTOR2I & GetOtherEnd(const SEG &aSeg, const VECTOR2I &aPoint)
Get the end point of the segment that is not the given point.
Definition: shape_utils.cpp:45
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:390
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
std::optional< VECTOR2I > OPT_VECTOR2I
Definition: seg.h:39
Utility functions for working with shapes.
Parameters that define a simple chamfer operation.
int m_chamfer_setback_b
Chamfer set-back distance along the second line.
int m_chamfer_setback_a
Chamfer set-back distance along the first line.
int radius
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
VECTOR2I GetRotated(const VECTOR2I &aVector, const EDA_ANGLE &aAngle)
Return a new VECTOR2I that is the result of rotating aVector by aAngle.
Definition: trigo.h:77
VECTOR2< int32_t > VECTOR2I
Definition: vector2d.h:695
Supplemental functions for working with vectors and simple objects that interact with vectors.