KiCad PCB EDA Suite
Loading...
Searching...
No Matches
graphic_edit_tool.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_commit.h>
23#include <board.h>
25#include <gal/painter.h>
26#include <geometry/circle.h>
27#include <geometry/seg.h>
28#include <pcb_shape.h>
30#include <render_settings.h>
31#include <tool/actions.h>
32#include <tool/tool_manager.h>
34#include <tools/graphic_trim.h>
35#include <tools/hover_picker.h>
36#include <tools/pcb_actions.h>
39
40#include <algorithm>
41#include <cmath>
42#include <limits>
43
46static double outlineProximity( const BOARD_ITEM& aItem, const VECTOR2I& aPointer )
47{
48 const PCB_SHAPE* shape = GraphicEditShape( &aItem );
49
50 if( !shape )
51 return std::numeric_limits<double>::infinity();
52
53 switch( shape->GetShape() )
54 {
56 return SEG( shape->GetStart(), shape->GetEnd() ).Distance( aPointer );
57
58 case SHAPE_T::CIRCLE:
59 return std::abs( shape->GetCenter().Distance( aPointer ) - (double) shape->GetRadius() );
60
61 case SHAPE_T::ARC:
62 {
63 SHAPE_ARC arc = GraphicEditArc( *shape );
64
65 return arc.NearestPoint( aPointer ).Distance( aPointer );
66 }
67
69 {
70 std::vector<VECTOR2I> corners = shape->GetRectCorners();
71 double nearest = std::numeric_limits<double>::infinity();
72
73 for( size_t i = 0; i < corners.size(); i++ )
74 {
75 SEG side( corners[i], corners[( i + 1 ) % corners.size()] );
76 nearest = std::min( nearest, (double) side.Distance( aPointer ) );
77 }
78
79 return nearest;
80 }
81
82 default:
83 return std::numeric_limits<double>::infinity();
84 }
85}
86
87
88static void applyGeometry( PCB_SHAPE& aShape, const GRAPHIC_EDIT_GEOMETRY& aGeometry )
89{
90 if( aShape.GetShape() != aGeometry.m_Shape )
91 {
92 // A shape that has been opened up has no inside left to fill.
93 aShape.SetShape( aGeometry.m_Shape );
95 }
96
97 if( aGeometry.m_Shape == SHAPE_T::ARC )
98 {
99 aShape.SetArcGeometry( aGeometry.m_Start, aGeometry.m_Mid, aGeometry.m_End );
100 }
101 else
102 {
103 aShape.SetStart( aGeometry.m_Start );
104 aShape.SetEnd( aGeometry.m_End );
105 }
106}
107
108
110{
111 if( aGeometry.m_Shape == SHAPE_T::ARC )
112 return SHAPE_ARC( aGeometry.m_Start, aGeometry.m_Mid, aGeometry.m_End, 0 );
113
114 // A circle carries its centre in m_Start and a point on the rim in m_End.
115 if( aGeometry.m_Shape == SHAPE_T::CIRCLE )
116 return CIRCLE( aGeometry.m_Start, aGeometry.m_Start.Distance( aGeometry.m_End ) );
117
118 return SEG( aGeometry.m_Start, aGeometry.m_End );
119}
120
121
123static wxString refusalMessage( GRAPHIC_EDIT_REFUSAL aRefusal, const wxString& aNoResult )
124{
125 switch( aRefusal )
126 {
128 return _( "More than one shape meets this one here." );
130 return _( "That shape is too small or too near a full circle to work with." );
132 return _( "That shape is locked." );
133 default:
134 return aNoResult;
135 }
136}
137
138
140 PCB_TOOL_BASE( "pcbnew.GraphicEdit" )
141{
142}
143
144
146{
148
149 // Menu entries live in EDIT_TOOL's Shape Modification submenu.
150
151 return true;
152}
153
154
156{
157 return runInteractive( aEvent,
158 { .m_NoResult = _( "There is nothing to reach in that direction." ),
159 .m_CommitDescription = _( "Extend Line or Arc" ),
160 .m_Accepts = IsGraphicExtendSource,
161 .m_PreviewLayer = LAYER_AUX_ITEMS,
163 .m_Plan = GRAPHIC_EXTEND_PLANNER::Plan } );
164}
165
166
168{
169 return runInteractive( aEvent,
170 { .m_NoResult = _( "Nothing crosses the shape here." ),
171 .m_CommitDescription = _( "Trim Shape" ),
172 .m_Accepts = IsGraphicTrimSource,
173 .m_SuppressedSnaps = { SNAP_CANDIDATE_SUBTYPE::INTERSECTION,
175 .m_PreviewLayer = LAYER_DRC_ERROR,
176 .m_QueryBounds = nullptr,
177 .m_Plan = GRAPHIC_TRIM_PLANNER::Plan } );
178}
179
180
181int GRAPHIC_EDIT_TOOL::runInteractive( const TOOL_EVENT& aEvent, const OPERATION& aOperation )
182{
183 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
185 HOVER_PICKER hover( m_toolMgr );
186 bool done = false;
187
188 // Nothing is picked up front, so a leftover selection would only be a distraction.
190
191 // Only bounds the extension ray. A snapshot is enough if the board grows.
192 const BOX2I worldBounds = board()->GetBoundingBox();
193
194 // A locked shape is still hovered. The planner refuses it by name, which tells the user
195 // more than nothing lighting up.
196 auto hoveredSource =
197 [&]( const VECTOR2I& aPointer ) -> PCB_SHAPE*
198 {
199 auto accepts =
200 [&]( BOARD_ITEM& aItem )
201 {
202 const PCB_SHAPE* shape = GraphicEditShape( &aItem );
203
204 return shape && aOperation.m_Accepts( *shape );
205 };
206
207 return static_cast<PCB_SHAPE*>( hover.Pick( aPointer, accepts, outlineProximity ) );
208 };
209
210 auto collectBoundaries =
211 [&]( const BOX2I& aQueryBounds, const PCB_SHAPE& aSource )
212 {
213 std::set<const BOARD_ITEM*> seen;
214 std::vector<const BOARD_ITEM*> boundaries;
215 const PCB_LAYER_ID layer = aSource.GetLayer();
216
217 // Selectable() is asked for visibility only, because we do want to include footprint graphics,
218 // even though the board editor will not consider them selectable. Similarly, we also ignore
219 // the currently entered group (if any). Selectable() is the costliest test, so it goes last.
220 view()->Query( aQueryBounds,
221 [&]( KIGFX::VIEW_ITEM* aViewItem )
222 {
223 if( !aViewItem->IsBOARD_ITEM() )
224 return true;
225
226 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aViewItem );
227
228 if( item != &aSource
229 && item->GetLayer() == layer
230 && GraphicEditShape( item )
231 && view()->IsVisible( item )
232 && m_selectionTool->Selectable( item, true /* checkVisibilityOnly */ )
233 && seen.insert( item ).second )
234 {
235 boundaries.push_back( item );
236 }
237
238 return true;
239 } );
240
241 return boundaries;
242 };
243
244 auto resetPreview =
245 [&]()
246 {
247 hover.ClearBrightening();
248 preview.ClearDrawables();
249 view()->Update( &preview );
250 };
251
252 // Returns the source it planned against, so the click handler can commit to the same one.
253 auto updatePreview =
254 [&]( const VECTOR2I& aPointer, GRAPHIC_EDIT_RESULT& aResult ) -> PCB_SHAPE*
255 {
256 PCB_SHAPE* source = hoveredSource( aPointer );
257
258 preview.ClearDrawables();
259
260 if( !source )
261 {
262 aResult = {};
263 hover.ClearBrightening();
264 view()->Update( &preview );
265 return nullptr;
266 }
267
268 BOX2I queryBounds = source->GetBoundingBox();
269
270 if( aOperation.m_QueryBounds )
271 {
272 BOX2I rayBounds = worldBounds;
273
274 rayBounds.Merge( queryBounds );
275 queryBounds = aOperation.m_QueryBounds( *source, aPointer, rayBounds );
276 }
277
278 if( !queryBounds.IsValid() )
279 {
280 aResult = {};
281 hover.BrightenOnly( { source } );
282 view()->Update( &preview );
283 return source;
284 }
285
286 std::vector<const BOARD_ITEM*> candidates = collectBoundaries( queryBounds, *source );
287
288 aResult = aOperation.m_Plan( *source, aPointer, candidates );
289
290 // The shape under the pointer is worth marking whether or not it can be edited here.
291 std::vector<BOARD_ITEM*> lit{ source };
292
293 if( aResult )
294 {
295 const std::vector<GRAPHIC_EDIT_GEOMETRY>& shown = aResult.m_Preview.empty() ? aResult.m_Geometry
296 : aResult.m_Preview;
297
298 for( const GRAPHIC_EDIT_GEOMETRY& geometry : shown )
299 preview.AddDrawable( drawable( geometry ), false, 4 );
300
301 for( const BOARD_ITEM* boundary : aResult.m_Boundaries )
302 lit.push_back( const_cast<BOARD_ITEM*>( boundary ) );
303 }
304
305 hover.BrightenOnly( lit );
306 view()->Update( &preview );
307 return source;
308 };
309
311 KIGFX::COLOR4D previewColor = settings->GetLayerColor( aOperation.m_PreviewLayer );
312
313 // The borrowed colour is not always a good choice. Compare with the background.
314 if( view()->GetGAL()->GetClearColor().Distance( previewColor ) < 0.5 )
315 previewColor.Invert();
316
317 preview.SetColor( previewColor );
318
319 Activate();
320
321 // The selection tool arms its disambiguation on button-down unless a tool owns the stack.
322 // Without this the committing click also selects, which cancels the picker.
323 frame()->PushTool( aEvent );
324
325 view()->Add( &preview );
326 picker->SetCursor( KICURSOR::BULLSEYE );
327
328 // Snapping on is what makes the picker honour the modifiers that turn it off again, so
329 // <shift> and <ctrl> give a finer aim among crowded items.
330 picker->SetSnapping( true );
331
332 // Nothing here follows an extension, so the lines the snap system draws are just noise.
333 picker->SetConstructionGeometry( false );
334 picker->SetSuppressedSnaps( aOperation.m_SuppressedSnaps );
335 picker->ClearHandlers();
336 picker->SetMotionHandler(
337 [&]( const VECTOR2D& aPointer )
338 {
340
341 updatePreview( aPointer, result );
342 } );
343 picker->SetClickHandler(
344 [&]( const VECTOR2D& aPointer )
345 {
347 PCB_SHAPE* source = updatePreview( aPointer, result );
348
349 if( !source )
350 return true;
351
352 if( !result )
353 {
354 frame()->ShowInfoBarError( refusalMessage( result.m_Refusal, aOperation.m_NoResult ) );
355 return true;
356 }
357
358 // The commit may free the source, so let go of it first.
359 hover.ClearBrightening();
360 preview.ClearDrawables();
361 view()->Update( &preview );
362
363 BOARD_COMMIT commit( this );
364
365 if( result.m_Geometry.empty() )
366 {
367 commit.Remove( source );
368 }
369 else
370 {
371 std::vector<PCB_SHAPE*> pieces;
372
373 // Duplicate off the untouched source, before its own geometry is replaced.
374 for( size_t i = 1; i < result.m_Geometry.size(); i++ )
375 {
376 PCB_SHAPE* piece = static_cast<PCB_SHAPE*>( source->Duplicate( true, &commit ) );
377
378 piece->ClearSelected();
379 pieces.push_back( piece );
380 }
381
382 commit.Modify( source );
383 applyGeometry( *source, result.m_Geometry.front() );
384
385 for( size_t i = 0; i < pieces.size(); i++ )
386 {
387 applyGeometry( *pieces[i], result.m_Geometry[i + 1] );
388 commit.Add( pieces[i] );
389 }
390 }
391
392 commit.Push( aOperation.m_CommitDescription );
393
394 updatePreview( aPointer, result );
395 return true;
396 } );
397 picker->SetCancelHandler( resetPreview );
398 picker->SetFinalizeHandler(
399 [&]( const int& )
400 {
401 resetPreview();
402 done = true;
403 } );
404 m_toolMgr->RunAction( ACTIONS::pickerSubTool );
405
406 while( !done )
407 {
408 TOOL_EVENT* event = Wait();
409
410 if( !event )
411 break;
412
413 // Nothing is cached across events. Model changes and undo need no handling. The next
414 // motion queries the board as it stands.
415 event->SetPassEvent();
416 }
417
418 picker->ClearHandlers();
419 resetPreview();
420 view()->Remove( &preview );
421 frame()->PopTool( aEvent );
422 return 0;
423}
424
425
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
static TOOL_ACTION pickerSubTool
Definition actions.h:250
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition board.h:1274
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr bool IsValid() const
Definition box2.h:857
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
void ClearSelected()
Definition eda_item.h:153
int GetRadius() const
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
std::vector< VECTOR2I > GetRectCorners() const
void SetFillMode(FILL_T aFill)
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
int Trim(const TOOL_EVENT &aEvent)
PCB_SELECTION_TOOL * m_selectionTool
bool Init() override
Init() is called once upon a registration of the tool.
int Extend(const TOOL_EVENT &aEvent)
int runInteractive(const TOOL_EVENT &aEvent, const OPERATION &aOperation)
The board item under the pointer, and the highlight that follows it.
void BrightenOnly(const std::vector< BOARD_ITEM * > &aItems)
Light exactly these, leaving what is already lit alone.
void ClearBrightening()
Items go back by id, so a commit that replaced one still puts the original back.
BOARD_ITEM * Pick(const VECTOR2I &aPointer, const ACCEPTS &aAccepts, const PROXIMITY &aProximity=nullptr) const
The item a click would take, or null.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
COLOR4D & Invert()
Makes the color inverted, alpha remains the same.
Definition color4d.h:239
Shows construction geometry for things like line extensions, arc centers, etc.
std::variant< SEG, LINE, HALF_LINE, CIRCLE, SHAPE_ARC, VECTOR2I > DRAWABLE
virtual RENDER_SETTINGS * GetSettings()=0
Return a pointer to current settings that are going to be used when drawing items.
virtual void Update(const VIEW_ITEM *aItem, int aUpdateFlags) const override
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
Definition pcb_view.cpp:87
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1) override
Add a VIEW_ITEM to the view.
Definition pcb_view.cpp:53
virtual void Remove(VIEW_ITEM *aItem) override
Remove a VIEW_ITEM from the view.
Definition pcb_view.cpp:70
Container for all the knowledge about how graphical objects are drawn on any output surface/device.
const COLOR4D & GetLayerColor(int aLayer) const
Return the color used to draw a layer.
An abstract base class for deriving all objects that can be added to a VIEW.
Definition view_item.h:82
bool IsBOARD_ITEM() const
Definition view_item.h:98
int Query(const BOX2I &aRect, std::vector< LAYER_ITEM_PAIR > &aResult) const
Find all visible items that touch or are within the rectangle aRect.
Definition view.cpp:501
PAINTER * GetPainter() const
Return the painter object used by the view for drawing VIEW_ITEM objects.
Definition view.h:227
static TOOL_ACTION extendGraphic
Extend one graphical line or arc to the nearest boundary.
static TOOL_ACTION trimGraphic
Trim a section from one graphical line or arc.
Generic tool for picking an item.
void SetSuppressedSnaps(std::set< SNAP_CANDIDATE_SUBTYPE > aSubtypes)
Snap kinds this run never wants.
void SetConstructionGeometry(bool aEnable)
Whether the snap system may draw its explanatory geometry over the board this run.
void ClearHandlers()
Handlers only.
The selection tool: currently supports:
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:78
void SetShape(SHAPE_T aShape) override
Definition pcb_shape.h:209
void SetEnd(const VECTOR2I &aEnd) override
void SetStart(const VECTOR2I &aStart) override
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd) override
Set the three controlling points for an arc.
T * frame() const
KIGFX::PCB_VIEW * view() const
PCB_TOOL_BASE(TOOL_ID aId, const std::string &aName)
Constructor.
BOARD * board() const
void SetMotionHandler(MOTION_HANDLER aHandler)
Set a handler for mouse motion.
Definition picker_tool.h:92
void SetClickHandler(CLICK_HANDLER aHandler)
Set a handler for mouse click event.
Definition picker_tool.h:81
void SetSnapping(bool aSnap)
Definition picker_tool.h:65
void SetCursor(KICURSOR aCursor)
Definition picker_tool.h:63
void SetCancelHandler(CANCEL_HANDLER aHandler)
Set a handler for cancel events (ESC or context-menu Cancel).
void SetFinalizeHandler(FINALIZE_HANDLER aHandler)
Set a handler for the finalize event.
Definition seg.h:38
int Distance(const SEG &aSeg) const
Compute minimum Euclidean distance to segment aSeg.
Definition seg.cpp:668
VECTOR2I NearestPoint(const VECTOR2I &aP) const
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
Generic, UI-independent tool event.
Definition tool_event.h:167
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Define which state (aStateFunc) to go when a certain event arrives (aConditions).
TOOL_EVENT * Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
Suspend execution of the tool until an event specified in aEventList arrives.
void Activate()
Run the tool.
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:574
@ BULLSEYE
Definition cursors.h:54
#define _(s)
@ NO_FILL
Definition eda_fill.h:30
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
SHAPE_ARC GraphicEditArc(const PCB_SHAPE &aShape)
bool IsGraphicTrimSource(const PCB_SHAPE &aShape)
Trim also takes the closed shapes, which it opens up.
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.
GRAPHIC_EDIT_REFUSAL
Why a planner refused. Each one gets its own message.
static double outlineProximity(const BOARD_ITEM &aItem, const VECTOR2I &aPointer)
How far aPointer is from the shape's outline.
static KIGFX::CONSTRUCTION_GEOM::DRAWABLE drawable(const GRAPHIC_EDIT_GEOMETRY &aGeometry)
static void applyGeometry(PCB_SHAPE &aShape, const GRAPHIC_EDIT_GEOMETRY &aGeometry)
static wxString refusalMessage(GRAPHIC_EDIT_REFUSAL aRefusal, const wxString &aNoResult)
aNoResult carries wording only the operation can supply.
std::string source
@ LAYER_AUX_ITEMS
Auxiliary items (guides, rule, etc).
Definition layer_ids.h:279
@ LAYER_DRC_ERROR
Layer for DRC markers with RPT_SEVERITY_ERROR.
Definition layer_ids.h:273
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
BOX2I QueryBounds(const BOARD_ITEM &aSource, GRAPHIC_ENDPOINT aEndpoint, const BOX2I &aWorldBounds)
GRAPHIC_EDIT_RESULT Plan(const BOARD_ITEM &aSource, GRAPHIC_ENDPOINT aEndpoint, const std::vector< const BOARD_ITEM * > &aBoundaries)
GRAPHIC_EDIT_RESULT Plan(const BOARD_ITEM &aSource, const VECTOR2I &aPointer, const std::vector< const BOARD_ITEM * > &aBoundaries)
Plan removal of the part of aSource under aPointer.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
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.
What one edit operation gives the shared interactive loop.
std::set< SNAP_CANDIDATE_SUBTYPE > m_SuppressedSnaps
Snap kinds this operation cannot use.
bool(* m_Accepts)(const PCB_SHAPE &aShape)
Which shapes may be the source. Anything else is not worth hovering.
GRAPHIC_EDIT_RESULT(* m_Plan)(const BOARD_ITEM &aSource, const VECTOR2I &aPointer, const std::vector< const BOARD_ITEM * > &aBoundaries)
BOX2I(* m_QueryBounds)(const BOARD_ITEM &aSource, const VECTOR2I &aPointer, const BOX2I &aWorldBounds)
Null bounds the search by the source's own extent, which only cuts what it crosses.
int m_PreviewLayer
The colour role the preview borrows. Trim marks a removal, extend an addition.
static std::vector< int > candidates(const SEGMENT_INDEX &aIndex, const SEG &aQuery, int aPadding)
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707