KiCad PCB EDA Suite
Loading...
Searching...
No Matches
graphic_extend.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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 <board_item.h>
23#include <geometry/half_line.h>
25#include <geometry/seg.h>
28#include <pcb_shape.h>
30
31#include <cmath>
32#include <limits>
33
40static HALF_LINE extensionRay( const PCB_SHAPE& aSource, GRAPHIC_ENDPOINT aEndpoint )
41{
42 if( aEndpoint == GRAPHIC_ENDPOINT::START )
43 return HALF_LINE( aSource.GetEnd(), aSource.GetStart() );
44
45 return HALF_LINE( aSource.GetStart(), aSource.GetEnd() );
46}
47
48
50{
51 const PCB_SHAPE* source = GraphicEditShape( &aSource );
52
53 if( !source )
55
56 // Distance() widens before subtracting. A far-corner pointer cannot overflow.
57 return source->GetStart().Distance( aPointer ) < source->GetEnd().Distance( aPointer ) ? GRAPHIC_ENDPOINT::START
59}
60
61
63 const BOX2I& aWorldBounds )
64{
65 const PCB_SHAPE* source = GraphicEditShape( &aSource );
66
67 if( !source || !IsGraphicExtendSource( *source ) )
68 return {};
69
70 if( source->GetShape() == SHAPE_T::ARC )
71 {
72 SHAPE_ARC arc = GraphicEditArc( *source );
73
74 if( !IsGraphicEditArcUsable( arc ) )
75 return {};
76
78
79 // Nothing outside the world is a boundary. Keeps a large arc local.
80 return circle.Intersect( aWorldBounds );
81 }
82
83 const HALF_LINE ray = extensionRay( *source, aEndpoint );
84 std::optional<SEG> swept = KIGEOM::ClipHalfLineToBox( ray, aWorldBounds );
85
86 if( !swept )
87 return {};
88
89 // ClipHalfLineToBox reports crossings in box-edge order. It substitutes the ray origin if
90 // the ray starts inside. The exit is whichever is farther along.
91 const VECTOR2I exit =
92 ray.GetStart().Distance( swept->A ) > ray.GetStart().Distance( swept->B ) ? swept->A : swept->B;
93 BOX2I bounds;
94
95 bounds.SetOrigin( aEndpoint == GRAPHIC_ENDPOINT::START ? source->GetStart() : source->GetEnd() );
96 bounds.Merge( exit );
97
98 return bounds;
99}
100
101
103 const std::vector<const BOARD_ITEM*>& aBoundaries )
104{
106 const PCB_SHAPE* source = GraphicEditSource( aSource, IsGraphicExtendSource, result );
107
108 if( !source )
109 return result;
110
111 const bool segment = source->GetShape() == SHAPE_T::SEGMENT;
112 const VECTOR2I selected = aEndpoint == GRAPHIC_ENDPOINT::START ? source->GetStart() : source->GetEnd();
113 const VECTOR2I fixed = aEndpoint == GRAPHIC_ENDPOINT::START ? source->GetEnd() : source->GetStart();
114
115 double bestDistance = std::numeric_limits<double>::infinity();
116 VECTOR2I bestPoint;
117 const BOARD_ITEM* bestBoundary = nullptr;
118 double sourceSweep = 0.0;
119 double sourceRadius = 0.0;
120 bool sourceClockwise = false;
121 VECTOR2I sourceCenter;
122 INTERSECTABLE_GEOM sourceGeometry = SEG();
123
124 if( segment )
125 {
126 if( fixed == selected )
127 {
129 return result;
130 }
131
132 sourceGeometry = extensionRay( *source, aEndpoint );
133 }
134 else
135 {
136 SHAPE_ARC arc = GraphicEditArc( *source );
137
138 if( !IsGraphicEditArcUsable( arc ) )
139 {
141 return result;
142 }
143
144 sourceSweep = arc.GetCentralAngle().AsDegrees();
145 sourceClockwise = arc.IsClockwise();
146 sourceCenter = arc.GetCenter();
147 sourceRadius = arc.GetRadius();
148
149 // An arc grows either way. The whole circle is in play.
150 sourceGeometry = CIRCLE( sourceCenter, KiROUND( arc.GetRadius() ) );
151 }
152
153 for( const BOARD_ITEM* item : aBoundaries )
154 {
155 const PCB_SHAPE* boundary = GraphicEditBoundary( item, *source );
156
157 if( !boundary )
158 continue;
159
160 std::optional<INTERSECTABLE_GEOM> boundaryGeometry = BoardItemIntersectable( *boundary );
161
162 if( !boundaryGeometry )
163 continue;
164
165 std::vector<VECTOR2I> intersections;
166 INTERSECTION_CONTACT contact;
167
168 std::visit( INTERSECTION_VISITOR( *boundaryGeometry, intersections, contact ), sourceGeometry );
169
170 // A shared run gives nothing to stop at. A graze gives exactly one point, so it counts.
171 if( contact.m_Overlapping )
172 continue;
173
174 for( const VECTOR2I& point : intersections )
175 {
176 if( GraphicEditCoincident( point, selected ) )
177 continue;
178
179 double distance;
180
181 if( segment )
182 {
183 // Candidates all lie on the ray from the fixed end. Anything short of the
184 // moving end is nearer the fixed one. Distance() widens, so no overflow.
185 if( fixed.Distance( point ) <= fixed.Distance( selected ) )
186 continue;
187
188 distance = selected.Distance( point );
189 }
190 else
191 {
192 SHAPE_ARC extension;
193
194 if( aEndpoint == GRAPHIC_ENDPOINT::END )
195 extension.ConstructFromStartEndCenter( selected, point, sourceCenter, sourceClockwise );
196 else
197 extension.ConstructFromStartEndCenter( point, selected, sourceCenter, sourceClockwise );
198
199 distance = std::abs( extension.GetCentralAngle().AsDegrees() );
200
201 // Growing back over the source closes the circle.
203 || std::abs( sourceSweep ) + distance >= 360.0 - GRAPHIC_EDIT_ANGLE_EPSILON )
204 {
205 continue;
206 }
207 }
208
209 // One ray, or one circle traversed one way. Equal distance means the same point.
210 if( distance < bestDistance )
211 {
212 bestDistance = distance;
213 bestPoint = point;
214 bestBoundary = item;
215 }
216 }
217 }
218
219 if( !bestBoundary )
220 {
221 // An arc that reaches nothing keeps going, all the way round. A line has no such end.
222 if( segment )
223 return result;
224
226
227 closed.m_Shape = SHAPE_T::CIRCLE;
228 closed.m_Start = sourceCenter;
229 closed.m_End = sourceCenter + VECTOR2I( KiROUND( sourceRadius ), 0 );
230
232 result.m_Geometry.push_back( closed );
233 return result;
234 }
235
236 GRAPHIC_EDIT_GEOMETRY extended;
237 const VECTOR2I start = aEndpoint == GRAPHIC_ENDPOINT::START ? bestPoint : source->GetStart();
238 const VECTOR2I end = aEndpoint == GRAPHIC_ENDPOINT::END ? bestPoint : source->GetEnd();
239
240 extended.m_Shape = segment ? SHAPE_T::SEGMENT : SHAPE_T::ARC;
241
242 if( segment )
243 {
244 extended.m_Start = start;
245 extended.m_End = end;
246 }
247 else
248 {
249 // Build from the two endpoints, not a sweep. The intersection is rounded, so it sits a
250 // few IU off the source radius. Sweeping that radius misses a large arc by tens of IU.
251 SHAPE_ARC arc;
252 arc.ConstructFromStartEndCenter( start, end, sourceCenter, sourceClockwise );
253
254 extended.m_Start = arc.GetP0();
255 extended.m_Mid = arc.GetArcMid();
256 extended.m_End = arc.GetP1();
257 }
258
260 result.m_Geometry.push_back( extended );
261 result.m_Boundaries.push_back( bestBoundary );
262 return result;
263}
264
265
267 const BOX2I& aWorldBounds )
268{
269 return QueryBounds( aSource, NearestEndpoint( aSource, aPointer ), aWorldBounds );
270}
271
272
274 const std::vector<const BOARD_ITEM*>& aBoundaries )
275{
276 return Plan( aSource, NearestEndpoint( aSource, aPointer ), aBoundaries );
277}
std::optional< INTERSECTABLE_GEOM > BoardItemIntersectable(const BOARD_ITEM &aItem)
The kimath primitive a board item is made of.
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
constexpr void SetOrigin(const Vec &pos)
Definition box2.h:234
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:653
double AsDegrees() const
Definition eda_angle.h:116
SHAPE_T GetShape() const
Definition eda_shape.h:175
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
const VECTOR2I & GetStart() const
Get the start point of the ray.
Definition half_line.h:53
Definition seg.h:38
EDA_ANGLE GetCentralAngle() const
Get the "central angle" of the arc - this is the angle at the point of the "pie slice".
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:116
bool IsClockwise() const
Definition shape_arc.h:319
SHAPE_ARC & ConstructFromStartEndCenter(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCenter, bool aClockwise=false, double aWidth=0)
Constructs this arc from the given start, end and center.
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
double GetRadius() const
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
const VECTOR2I & GetCenter() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:549
@ SEGMENT
Definition eda_shape.h:56
SHAPE_ARC GraphicEditArc(const PCB_SHAPE &aShape)
bool IsGraphicEditArcUsable(const SHAPE_ARC &aArc)
False for radii and sweeps the planners refuse. Check before planning an arc.
const PCB_SHAPE * GraphicEditBoundary(const BOARD_ITEM *aBoundary, const PCB_SHAPE &aSource)
A boundary usable against aSource. Null if it is the source, off-layer or unusable.
const PCB_SHAPE * GraphicEditSource(const BOARD_ITEM &aSource, bool(*aAccepts)(const PCB_SHAPE &), GRAPHIC_EDIT_RESULT &aResult)
The shape to edit, or null with the reason in aResult. aAccepts decides the kinds.
bool GraphicEditCoincident(const VECTOR2I &aA, const VECTOR2I &aB)
Points built by different routes land a rounding step apart.
bool IsGraphicExtendSource(const PCB_SHAPE &aShape)
Extend needs two ends to work with.
const PCB_SHAPE * GraphicEditShape(const EDA_ITEM *aItem)
Any graphical shape a planner or a boundary search might use. Anything else gives null.
constexpr double GRAPHIC_EDIT_ANGLE_EPSILON
Sweeps this near zero or a full turn are ambiguous.
static HALF_LINE extensionRay(const PCB_SHAPE &aSource, GRAPHIC_ENDPOINT aEndpoint)
The ray the shape grows along.
GRAPHIC_ENDPOINT
std::variant< LINE, HALF_LINE, SEG, CIRCLE, SHAPE_ARC, SHAPE_ELLIPSE, BOX2I > INTERSECTABLE_GEOM
A variant type that can hold any of the supported geometry types for intersection calculations.
BOX2I QueryBounds(const BOARD_ITEM &aSource, GRAPHIC_ENDPOINT aEndpoint, const BOX2I &aWorldBounds)
GRAPHIC_ENDPOINT NearestEndpoint(const BOARD_ITEM &aSource, const VECTOR2I &aPointer)
GRAPHIC_EDIT_RESULT Plan(const BOARD_ITEM &aSource, GRAPHIC_ENDPOINT aEndpoint, const std::vector< const BOARD_ITEM * > &aBoundaries)
std::optional< SEG > ClipHalfLineToBox(const HALF_LINE &aRay, const BOX2I &aBox)
Get the segment of a half-line that is inside a box, if any.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
Utility functions for working with shapes.
SHAPE_T m_Shape
A result need not be the same kind as the source.
VECTOR2I m_Mid
Arcs only. Everything else leaves it default.
How two geometries meet, beyond where they cross.
bool m_Overlapping
Collinear or concentric, more than one point shared.
A visitor that visits INTERSECTABLE_GEOM variant objects with another (which is held as state: m_othe...
VECTOR2I end
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683