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 (C) 2023-2024 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>
31#include <trigo.h>
32
33namespace
34{
35
39SEG GetBisectorOfCornerSegments( const SEG& aSegA, const SEG& aSegB, int aLength )
40{
41 // Use the "parallelogram" method to find the bisector
42
43 // The intersection point of the two lines is the one that is shared by both segments
44 const std::optional<VECTOR2I> corner = KIGEOM::GetSharedEndpoint( aSegA, aSegB );
45
46 // Get the vector of a segment pointing away from a point
47 const auto getSegVectorPointingAwayFrom = []( const SEG& aSeg,
48 const VECTOR2I& aPoint ) -> VECTOR2I
49 {
50 const int distA = ( aSeg.A - aPoint ).EuclideanNorm();
51 const int distB = ( aSeg.B - aPoint ).EuclideanNorm();
52
53 // If A is closer the segment is already pointing away from the corner
54 // If B is closer, we need to reverse the segment
55 return ( distA < distB ) ? ( aSeg.B - aSeg.A ) : ( aSeg.A - aSeg.B );
56 };
57
58 // The vectors have to be the same length
59 const int maxLen = std::max( aSegA.Length(), aSegB.Length() );
60 const VECTOR2I aVecOutward = getSegVectorPointingAwayFrom( aSegA, *corner ).Resize( maxLen );
61 const VECTOR2I bVecOutward = getSegVectorPointingAwayFrom( aSegB, *corner ).Resize( maxLen );
62 const VECTOR2I bisectorOutward = aVecOutward + bVecOutward;
63
64 return SEG( *corner, *corner + bisectorOutward.Resize( aLength ) );
65}
66
67} // namespace
68
69std::optional<CHAMFER_RESULT> ComputeChamferPoints( const SEG& aSegA, const SEG& aSegB,
70 const CHAMFER_PARAMS& aChamferParams )
71{
72 const int line_a_setback = aChamferParams.m_chamfer_setback_a;
73 const int line_b_setback = aChamferParams.m_chamfer_setback_b;
74
75 if( line_a_setback == 0 && line_b_setback == 0 )
76 {
77 // No chamfer to do
78 // In theory a chamfer of 0 on one side is kind-of valid (adds a collinear point)
79 // so allow it (using an and above, not an or)
80 return std::nullopt;
81 }
82
83 if( aSegA.Length() < line_a_setback || aSegB.Length() < line_b_setback )
84 {
85 // Chamfer is too big for the line segments
86 return std::nullopt;
87 }
88
89 // We only support the case where the lines intersect at the end points
90 // otherwise we would need to decide which inside corner to chamfer
91
92 // Figure out which end points are the ones at the intersection
93 const std::optional<VECTOR2I> corner = KIGEOM::GetSharedEndpoint( aSegA, aSegB );
94
95 if( !corner )
96 {
97 // The lines are not coterminous
98 return std::nullopt;
99 }
100
101 // These are the other existing line points (the ones that are not the intersection)
102 const VECTOR2I& a_end_pt = KIGEOM::GetOtherEnd( aSegA, *corner );
103 const VECTOR2I& b_end_pt = KIGEOM::GetOtherEnd( aSegB, *corner );
104
105 // Now, construct segment of the set-back lengths, that begins
106 // at the intersection point and is parallel to each line segments
107 SEG setback_a( *corner, *corner + VECTOR2I( a_end_pt - *corner ).Resize( line_a_setback ) );
108 SEG setback_b( *corner, *corner + VECTOR2I( b_end_pt - *corner ).Resize( line_b_setback ) );
109
110 // The chamfer segment then goes between the end points of the set-back segments
111 SEG chamfer( setback_a.B, setback_b.B );
112
113 // The adjusted segments go from the old end points to the chamfer ends
114
115 std::optional<SEG> new_a;
116
117 if( a_end_pt != chamfer.A )
118 new_a = SEG{ a_end_pt, chamfer.A };
119
120 std::optional<SEG> new_b;
121
122 if( b_end_pt != chamfer.B )
123 new_b = SEG{ b_end_pt, chamfer.B };
124
125 return CHAMFER_RESULT{ chamfer, new_a, new_b };
126}
127
128
129std::optional<DOGBONE_RESULT> ComputeDogbone( const SEG& aSegA, const SEG& aSegB,
130 int aDogboneRadius, bool aAddSlots )
131{
132 const std::optional<VECTOR2I> corner = KIGEOM::GetSharedEndpoint( aSegA, aSegB );
133
134 // Cannot handle parallel lines
135 if( !corner || aSegA.Angle( aSegB ).IsHorizontal() )
136 {
137 return std::nullopt;
138 }
139
140 const SEG bisector = GetBisectorOfCornerSegments( aSegA, aSegB, aDogboneRadius );
141
142 // The dogbone center is the end of the bisector
143 const VECTOR2I dogboneCenter = bisector.B;
144
145 // We can find the ends of the arc by considering the corner angle,
146 // but it's easier to just intersect the circle with the original segments.
147
148 const CIRCLE circle( dogboneCenter, aDogboneRadius );
149
150 const std::vector<VECTOR2I> segAIntersections = circle.Intersect( aSegA );
151 const std::vector<VECTOR2I> segBIntersections = circle.Intersect( aSegB );
152
153 // There can be a little bit of error in the intersection calculation
154 static int s_epsilon = 8;
155
156 const auto getPointNotOnCorner =
157 [&]( const std::vector<VECTOR2I>& aIntersections, const VECTOR2I& aCorner )
158 {
159 std::optional<VECTOR2I> result;
160 for( const VECTOR2I& pt : aIntersections )
161 {
162 if( aCorner.Distance( pt ) > s_epsilon )
163 {
164 result = pt;
165 break;
166 }
167 }
168 return result;
169 };
170
171 const std::optional<VECTOR2I> ptOnSegA = getPointNotOnCorner( segAIntersections, *corner );
172 const std::optional<VECTOR2I> ptOnSegB = getPointNotOnCorner( segBIntersections, *corner );
173
174 // The arc doesn't cross one or both of the segments
175 if( !ptOnSegA || !ptOnSegB )
176 {
177 return std::nullopt;
178 }
179
180 SHAPE_ARC arc( *ptOnSegA, *corner, *ptOnSegB, 0 );
181
182 const VECTOR2I& aOtherPtA = KIGEOM::GetOtherEnd( aSegA, *corner );
183 const VECTOR2I& aOtherPtB = KIGEOM::GetOtherEnd( aSegB, *corner );
184
185 const EDA_ANGLE angle_epsilon( 1e-3, EDA_ANGLE_T::DEGREES_T );
186 const bool small_arc_mouth = std::abs( arc.GetCentralAngle() ) > ( ANGLE_180 + angle_epsilon );
187
188 {
189 // See if we need to update the original segments
190 // or if the dogbone consumed them
191 std::optional<SEG> new_a, new_b;
192 if( aOtherPtA != *ptOnSegA )
193 new_a = SEG{ aOtherPtA, *ptOnSegA };
194
195 if( aOtherPtB != *ptOnSegB )
196 new_b = SEG{ aOtherPtB, *ptOnSegB };
197
198 // Nice and easy
199 if( !small_arc_mouth || !aAddSlots )
200 {
201 return DOGBONE_RESULT{
202 arc,
203 new_a,
204 new_b,
205 small_arc_mouth,
206 };
207 }
208 }
209
210 // If it's a small mouth, we can try to work out the minimal slot to allow
211
212 // First the arc will be pulled back to 180 degrees
213 SHAPE_ARC slotArc = SHAPE_ARC( GetRotated( *corner, dogboneCenter, ANGLE_90 ), *corner,
214 GetRotated( *corner, dogboneCenter, -ANGLE_90 ), 0 );
215
216 // Make sure P0 is still the 'A' end
217 if( !KIGEOM::PointsAreInSameDirection( slotArc.GetP0(), arc.GetP0(), dogboneCenter ) )
218 {
219 slotArc.Reverse();
220 }
221
222 // Take the bisector and glue it to the arc ends
223 const HALF_LINE arc_extension_a{
224 slotArc.GetP0(),
225 slotArc.GetP0() + ( dogboneCenter - *corner ),
226 };
227 const HALF_LINE arc_extension_b{
228 slotArc.GetP1(),
229 slotArc.GetP1() + ( dogboneCenter - *corner ),
230 };
231
232 const OPT_VECTOR2I ext_a_intersect = arc_extension_a.Intersect( aSegA );
233 const OPT_VECTOR2I ext_b_intersect = arc_extension_b.Intersect( aSegB );
234
235 if( !ext_a_intersect || !ext_b_intersect )
236 {
237 // The arc extensions don't intersect the original segments
238 return std::nullopt;
239 }
240
241 {
242 // See if we need to update the original segments
243 // or if the dogbone consumed them
244 std::optional<SEG> new_a, new_b;
245 if( aOtherPtA != *ext_a_intersect )
246 new_a = SEG{ aOtherPtA, *ext_a_intersect };
247
248 if( aOtherPtB != *ext_b_intersect )
249 new_b = SEG{ aOtherPtB, *ext_b_intersect };
250
251 return DOGBONE_RESULT{
252 slotArc,
253 new_a,
254 new_b,
255 small_arc_mouth,
256 };
257 }
258}
Represent basic circle geometry with utility geometry functions.
Definition: circle.h:33
std::vector< VECTOR2I > Intersect(const CIRCLE &aCircle) const
Compute the intersection points between this circle and aCircle.
Definition: circle.cpp:221
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:538
const VECTOR2I & GetP1() const
Definition: shape_arc.h:115
void Reverse()
Definition: shape_arc.cpp:675
const VECTOR2I & GetP0() const
Definition: shape_arc.h:114
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
@ DEGREES_T
Definition: eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_180
Definition: eda_angle.h:405
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:50
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:44
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:390
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.
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:691
Supplemental functions for working with vectors and simple objects that interact with vectors.