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