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