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