KiCad PCB EDA Suite
Loading...
Searching...
No Matches
item_modification_routine.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 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
26
27namespace
28{
29
33bool SegmentsShareEndpoint( const SEG& aSegA, const SEG& aSegB )
34{
35 return ( aSegA.A == aSegB.A || aSegA.A == aSegB.B || aSegA.B == aSegB.A || aSegA.B == aSegB.B );
36}
37
38} // namespace
39
40
42{
43 wxASSERT_MSG( aLine.GetShape() == SHAPE_T::SEGMENT, "Can only modify segments" );
44
45 const bool removed = aSeg.Length() == 0;
46 if( !removed )
47 {
48 // Mark modified, then change it
50 aLine.SetStart( aSeg.A );
51 aLine.SetEnd( aSeg.B );
52 }
53 else
54 {
55 // The line has become zero length - delete it
56 GetHandler().DeleteItem( aLine );
57 }
58 return removed;
59}
60
61
63{
64 return _( "Fillet Lines" );
65}
66
67
68std::optional<wxString> LINE_FILLET_ROUTINE::GetStatusMessage() const
69{
70 if( GetSuccesses() == 0 )
71 {
72 return _( "Unable to fillet the selected lines." );
73 }
74 else if( GetFailures() > 0 )
75 {
76 return _( "Some of the lines could not be filleted." );
77 }
78 return std::nullopt;
79}
80
81
83{
84 if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 )
85 return;
86
87 SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() );
88 SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() );
89 VECTOR2I* a_pt;
90 VECTOR2I* b_pt;
91
92 if( seg_a.A == seg_b.A )
93 {
94 a_pt = &seg_a.A;
95 b_pt = &seg_b.A;
96 }
97 else if( seg_a.A == seg_b.B )
98 {
99 a_pt = &seg_a.A;
100 b_pt = &seg_b.B;
101 }
102 else if( seg_a.B == seg_b.A )
103 {
104 a_pt = &seg_a.B;
105 b_pt = &seg_b.A;
106 }
107 else if( seg_a.B == seg_b.B )
108 {
109 a_pt = &seg_a.B;
110 b_pt = &seg_b.B;
111 }
112 else
113 // Nothing to do
114 return;
115
116 if( seg_a.Angle( seg_b ).IsHorizontal() )
117 return;
118
119 SHAPE_ARC sArc( seg_a, seg_b, m_filletRadiusIU );
120 VECTOR2I t1newPoint, t2newPoint;
121
122 auto setIfPointOnSeg = []( VECTOR2I& aPointToSet, SEG aSegment, VECTOR2I aVecToTest )
123 {
124 VECTOR2I segToVec = aSegment.NearestPoint( aVecToTest ) - aVecToTest;
125
126 // Find out if we are on the segment (minimum precision)
128 {
129 aPointToSet.x = aVecToTest.x;
130 aPointToSet.y = aVecToTest.y;
131 return true;
132 }
133
134 return false;
135 };
136
137 //Do not draw a fillet if the end points of the arc are not within the track segments
138 if( !setIfPointOnSeg( t1newPoint, seg_a, sArc.GetP0() )
139 && !setIfPointOnSeg( t2newPoint, seg_b, sArc.GetP0() ) )
140 {
141 AddFailure();
142 return;
143 }
144
145 if( !setIfPointOnSeg( t1newPoint, seg_a, sArc.GetP1() )
146 && !setIfPointOnSeg( t2newPoint, seg_b, sArc.GetP1() ) )
147 {
148 AddFailure();
149 return;
150 }
151
152 auto tArc = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::ARC );
153
154 tArc->SetArcGeometry( sArc.GetP0(), sArc.GetArcMid(), sArc.GetP1() );
155
156 // Copy properties from one of the source lines
157 tArc->SetWidth( aLineA.GetWidth() );
158 tArc->SetLayer( aLineA.GetLayer() );
159 tArc->SetLocked( aLineA.IsLocked() );
160
161 CHANGE_HANDLER& handler = GetHandler();
162
163 handler.AddNewItem( std::move( tArc ) );
164
165 *a_pt = t1newPoint;
166 *b_pt = t2newPoint;
167
168 ModifyLineOrDeleteIfZeroLength( aLineA, seg_a );
169 ModifyLineOrDeleteIfZeroLength( aLineB, seg_b );
170
171 AddSuccess();
172}
173
175{
176 return _( "Chamfer Lines" );
177}
178
179
180std::optional<wxString> LINE_CHAMFER_ROUTINE::GetStatusMessage() const
181{
182 if( GetSuccesses() == 0 )
183 {
184 return _( "Unable to chamfer the selected lines." );
185 }
186 else if( GetFailures() > 0 )
187 {
188 return _( "Some of the lines could not be chamfered." );
189 }
190 return std::nullopt;
191}
192
193
195{
196 if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 )
197 return;
198
199 SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() );
200 SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() );
201
202 // If the segments share an endpoint, we won't try to chamfer them
203 // (we could extend to the intersection point, but this gets complicated
204 // and inconsistent when you select more than two lines)
205 if( !SegmentsShareEndpoint( seg_a, seg_b ) )
206 {
207 // not an error, lots of lines in a 2+ line selection will not intersect
208 return;
209 }
210
211 std::optional<CHAMFER_RESULT> chamfer_result =
212 ComputeChamferPoints( seg_a, seg_b, m_chamferParams );
213
214 if( !chamfer_result )
215 {
216 AddFailure();
217 return;
218 }
219
220 auto tSegment = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::SEGMENT );
221
222 tSegment->SetStart( chamfer_result->m_chamfer.A );
223 tSegment->SetEnd( chamfer_result->m_chamfer.B );
224
225 // Copy properties from one of the source lines
226 tSegment->SetWidth( aLineA.GetWidth() );
227 tSegment->SetLayer( aLineA.GetLayer() );
228 tSegment->SetLocked( aLineA.IsLocked() );
229
230 CHANGE_HANDLER& handler = GetHandler();
231
232 handler.AddNewItem( std::move( tSegment ) );
233
234 ModifyLineOrDeleteIfZeroLength( aLineA, *chamfer_result->m_updated_seg_a );
235 ModifyLineOrDeleteIfZeroLength( aLineB, *chamfer_result->m_updated_seg_b );
236
237 AddSuccess();
238}
239
240
242{
243 return _( "Extend Lines to Meet" );
244}
245
246
247std::optional<wxString> LINE_EXTENSION_ROUTINE::GetStatusMessage() const
248{
249 if( GetSuccesses() == 0 )
250 {
251 return _( "Unable to extend the selected lines to meet." );
252 }
253 else if( GetFailures() > 0 )
254 {
255 return _( "Some of the lines could not be extended to meet." );
256 }
257 return std::nullopt;
258}
259
260
262{
263 if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 )
264 return;
265
266 SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() );
267 SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() );
268
269 if( seg_a.Intersects( seg_b ) )
270 {
271 // already intersecting, nothing to do
272 return;
273 }
274
275 OPT_VECTOR2I intersection = seg_a.IntersectLines( seg_b );
276
277 if( !intersection )
278 {
279 // This might be an error, but it's also possible that the lines are
280 // parallel and don't intersect. We'll just ignore this case.
281 return;
282 }
283
284 CHANGE_HANDLER& handler = GetHandler();
285
286 const auto line_extender = [&]( const SEG& aSeg, PCB_SHAPE& aLine )
287 {
288 // If the intersection point is not already n the line, we'll extend to it
289 if( !aSeg.Contains( *intersection ) )
290 {
291 const int dist_start = ( *intersection - aSeg.A ).EuclideanNorm();
292 const int dist_end = ( *intersection - aSeg.B ).EuclideanNorm();
293
294 const VECTOR2I& furthest_pt = ( dist_start < dist_end ) ? aSeg.B : aSeg.A;
295 // Note, the drawing tool has COORDS_PADDING of 20mm, but we need a larger buffer
296 // or we are not able to select the generated segments
297 unsigned int edge_padding = static_cast<unsigned>( pcbIUScale.mmToIU( 200 ) );
298 VECTOR2I new_end = GetClampedCoords( *intersection, edge_padding );
299
300 handler.MarkItemModified( aLine );
301 aLine.SetStart( furthest_pt );
302 aLine.SetEnd( new_end );
303 }
304 };
305
306 line_extender( seg_a, aLineA );
307 line_extender( seg_b, aLineB );
308
309 AddSuccess();
310}
311
312
314{
315 std::unique_ptr<SHAPE_POLY_SET> poly;
316
317 switch( aPcbShape.GetShape() )
318 {
319 case SHAPE_T::POLY:
320 {
321 poly = std::make_unique<SHAPE_POLY_SET>( aPcbShape.GetPolyShape() );
322 break;
323 }
324 case SHAPE_T::RECTANGLE:
325 {
326 SHAPE_POLY_SET rect_poly;
327
328 const std::vector<VECTOR2I> rect_pts = aPcbShape.GetRectCorners();
329
330 rect_poly.NewOutline();
331
332 for( const VECTOR2I& pt : rect_pts )
333 {
334 rect_poly.Append( pt );
335 }
336
337 poly = std::make_unique<SHAPE_POLY_SET>( std::move( rect_poly ) );
338 break;
339 }
340 default:
341 {
342 break;
343 }
344 }
345
346 if( !poly )
347 {
348 // Not a polygon or rectangle, nothing to do
349 return;
350 }
351
352 if( !m_workingPolygon )
353 {
354 auto initial = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::POLY );
355 initial->SetPolyShape( *poly );
356
357 // Copy properties
358 initial->SetLayer( aPcbShape.GetLayer() );
359 initial->SetWidth( aPcbShape.GetWidth() );
360
361 // Keep the pointer
362 m_workingPolygon = initial.get();
363 // Hand over ownership
364 GetHandler().AddNewItem( std::move( initial ) );
365
366 // And remove the shape
367 GetHandler().DeleteItem( aPcbShape );
368 }
369 else
370 {
371 if( ProcessSubsequentPolygon( *poly ) )
372 {
373 // If we could process the polygon, delete the source
374 GetHandler().DeleteItem( aPcbShape );
375 AddSuccess();
376 }
377 else
378 {
379 AddFailure();
380 }
381 }
382}
383
384
386{
387 return _( "Merge polygons." );
388}
389
390
391std::optional<wxString> POLYGON_MERGE_ROUTINE::GetStatusMessage() const
392{
393 if( GetSuccesses() == 0 )
394 {
395 return _( "Unable to merge the selected polygons." );
396 }
397 else if( GetFailures() > 0 )
398 {
399 return _( "Some of the polygons could not be merged." );
400 }
401 return std::nullopt;
402}
403
404
406{
408
410 working_copy.BooleanAdd( aPolygon, poly_mode );
411
412 // Check it's not disjoint - this doesn't work well in the UI
413 if( working_copy.OutlineCount() != 1 )
414 {
415 return false;
416 }
417
418 GetWorkingPolygon()->SetPolyShape( working_copy );
419 return true;
420}
421
422
424{
425 return _( "Subtract polygons." );
426}
427
428
429std::optional<wxString> POLYGON_SUBTRACT_ROUTINE::GetStatusMessage() const
430{
431 if( GetSuccesses() == 0 )
432 {
433 return _( "Unable to subtract the selected polygons." );
434 }
435 else if( GetFailures() > 0 )
436 {
437 return _( "Some of the polygons could not be subtracted." );
438 }
439 return std::nullopt;
440}
441
442
444{
446
448 working_copy.BooleanSubtract( aPolygon, poly_mode );
449
450 // Subtraction can create holes or delete the polygon
451 // In theory we can allow holes as the EDA_SHAPE will fracture for us, but that's
452 // probably not what the user has in mind (?)
453 if( working_copy.OutlineCount() != 1 || working_copy.HoleCount( 0 ) > 0
454 || working_copy.VertexCount( 0 ) == 0 )
455 {
456 // If that happens, just skip the operation
457 return false;
458 }
459
460 GetWorkingPolygon()->SetPolyShape( working_copy );
461 return true;
462}
463
465{
466 return _( "Intersect polygons." );
467}
468
469
470std::optional<wxString> POLYGON_INTERSECT_ROUTINE::GetStatusMessage() const
471{
472 if( GetSuccesses() == 0 )
473 {
474 return _( "Unable to intersect the selected polygons." );
475 }
476 else if( GetFailures() > 0 )
477 {
478 return _( "Some of the polygons could not be intersected." );
479 }
480 return std::nullopt;
481}
482
483
485{
487
489 working_copy.BooleanIntersection( aPolygon, poly_mode );
490
491 // Is there anything left?
492 if( working_copy.OutlineCount() == 0 )
493 {
494 // There was no intersection. Rather than deleting the working polygon, we'll skip
495 // and report a failure.
496 return false;
497 }
498
499 GetWorkingPolygon()->SetPolyShape( working_copy );
500 return true;
501}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:108
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.
Definition: chamfer.cpp:26
virtual bool IsLocked() const
Definition: board_item.cpp:74
bool IsHorizontal() const
Definition: eda_angle.h:180
SHAPE_POLY_SET & GetPolyShape()
Definition: eda_shape.h:262
SHAPE_T GetShape() const
Definition: eda_shape.h:120
void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition: eda_shape.h:270
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition: eda_shape.h:150
void SetStart(const VECTOR2I &aStart)
Definition: eda_shape.h:129
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition: eda_shape.h:125
std::vector< VECTOR2I > GetRectCorners() const
Definition: eda_shape.cpp:1139
void SetEnd(const VECTOR2I &aEnd)
Definition: eda_shape.h:154
double GetLength() const
Definition: eda_shape.cpp:123
virtual void DeleteItem(PCB_SHAPE &aItem)=0
Report that the tool has deleted an item on the board.
virtual void MarkItemModified(PCB_SHAPE &aItem)=0
Report that the tool has modified an item on the board.
virtual void AddNewItem(std::unique_ptr< PCB_SHAPE > aItem)=0
Report that the tools wants to add a new item to the board.
void AddFailure()
Mark that one of the actions failed.
void AddSuccess()
Mark that one of the actions succeeded.
bool ModifyLineOrDeleteIfZeroLength(PCB_SHAPE &aItem, const SEG &aSeg)
Helper function useful for multiple tools: modify a line or delete it if it has zero length.
BOARD_ITEM * GetBoard() const
The BOARD used when creating new shapes.
CHANGE_HANDLER & GetHandler()
Access the handler for making changes to the board.
wxString GetCommitDescription() const override
void ProcessLinePair(PCB_SHAPE &aLineA, PCB_SHAPE &aLineB) override
Perform the action on the pair of lines given.
const CHAMFER_PARAMS m_chamferParams
std::optional< wxString > GetStatusMessage() const override
Get a status message to show when the routine is complete.
wxString GetCommitDescription() const override
std::optional< wxString > GetStatusMessage() const override
Get a status message to show when the routine is complete.
void ProcessLinePair(PCB_SHAPE &aLineA, PCB_SHAPE &aLineB) override
Perform the action on the pair of lines given.
wxString GetCommitDescription() const override
void ProcessLinePair(PCB_SHAPE &aLineA, PCB_SHAPE &aLineB) override
Perform the action on the pair of lines given.
std::optional< wxString > GetStatusMessage() const override
Get a status message to show when the routine is complete.
int GetWidth() const override
Definition: pcb_shape.cpp:367
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition: pcb_shape.h:70
PCB_SHAPE * GetWorkingPolygon() const
void ProcessShape(PCB_SHAPE &aPcbShape)
virtual bool ProcessSubsequentPolygon(const SHAPE_POLY_SET &aPolygon)=0
std::optional< wxString > GetStatusMessage() const override
Get a status message to show when the routine is complete.
wxString GetCommitDescription() const override
bool ProcessSubsequentPolygon(const SHAPE_POLY_SET &aPolygon) override
bool ProcessSubsequentPolygon(const SHAPE_POLY_SET &aPolygon) override
wxString GetCommitDescription() const override
std::optional< wxString > GetStatusMessage() const override
Get a status message to show when the routine is complete.
wxString GetCommitDescription() const override
std::optional< wxString > GetStatusMessage() const override
Get a status message to show when the routine is complete.
bool ProcessSubsequentPolygon(const SHAPE_POLY_SET &aPolygon) override
Definition: seg.h:42
VECTOR2I A
Definition: seg.h:49
VECTOR2I B
Definition: seg.h:50
bool Intersects(const SEG &aSeg) const
Definition: seg.cpp:190
int Length() const
Return the length (this).
Definition: seg.h:326
OPT_VECTOR2I IntersectLines(const SEG &aSeg) const
Compute the intersection point of lines passing through ends of (this) and aSeg.
Definition: seg.h:210
bool Contains(const SEG &aSeg) const
Definition: seg.h:307
EDA_ANGLE Angle(const SEG &aOther) const
Determine the smallest angle between two segments.
Definition: seg.cpp:97
const VECTOR2I & GetArcMid() const
Definition: shape_arc.h:115
const VECTOR2I & GetP1() const
Definition: shape_arc.h:114
const VECTOR2I & GetP0() const
Definition: shape_arc.h:113
Represent a set of closed polygons.
void BooleanSubtract(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset difference For aFastMode meaning, see function booleanOp.
POLYGON_MODE
Operations on polygons use a aFastMode param if aFastMode is PM_FAST (true) the result can be a weak ...
int VertexCount(int aOutline=-1, int aHole=-1) const
Return the number of vertices in a given outline/hole.
void BooleanAdd(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset union For aFastMode meaning, see function booleanOp.
void BooleanIntersection(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset intersection For aFastMode meaning, see function booleanOp.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
static const int MIN_PRECISION_IU
This is the minimum precision for all the points in a shape.
Definition: shape.h:131
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition: vector2d.h:265
#define _(s)
a few functions useful in geometry calculations.
VECTOR2< ret_type > GetClampedCoords(const VECTOR2< in_type > &aCoords, pad_type aPadding=1u)
Clamps a vector to values that can be negated, respecting numeric limits of coordinates data type wit...
std::optional< VECTOR2I > OPT_VECTOR2I
Definition: seg.h:39
constexpr int mmToIU(double mm) const
Definition: base_units.h:88
double EuclideanNorm(const VECTOR2I &vector)
Definition: trigo.h:128