KiCad PCB EDA Suite
Loading...
Searching...
No Matches
construction_manager.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
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
26#include <chrono>
27
28#include <wx/timer.h>
29
30#include <advanced_config.h>
31#include <hash.h>
32
33
45template <typename T>
47{
48public:
49 using ACTIVATION_CALLBACK = std::function<void( T&& )>;
50
51 ACTIVATION_HELPER( std::chrono::milliseconds aTimeout, ACTIVATION_CALLBACK aCallback ) :
52 m_timeout( aTimeout ),
53 m_callback( std::move( aCallback ) )
54 {
55 m_timer.Bind( wxEVT_TIMER, &ACTIVATION_HELPER::onTimerExpiry, this );
56 }
57
59 {
60 // Hold the lock while shutting down to prevent a propoal being accepted
61 // while state is being destroyed.
62 std::unique_lock<std::mutex> lock( m_mutex );
63 m_timer.Stop();
64 m_timer.Unbind( wxEVT_TIMER, &ACTIVATION_HELPER::onTimerExpiry, this );
65
66 // Should be redundant to inhibiting timer callbacks, but make it explicit.
68 }
69
70 void ProposeActivation( T&& aProposal, std::size_t aProposalTag, bool aAcceptImmediately )
71 {
72 std::unique_lock<std::mutex> lock( m_mutex );
73
74 if( m_lastAcceptedProposalTag.has_value() && aProposalTag == *m_lastAcceptedProposalTag )
75 {
76 // This proposal was accepted last time
77 // (could be made optional if we want to allow re-accepting the same proposal)
78 return;
79 }
80
81 if( m_pendingProposalTag.has_value() && aProposalTag == *m_pendingProposalTag )
82 {
83 // This proposal is already pending
84 return;
85 }
86
87 m_pendingProposalTag = aProposalTag;
88 m_lastProposal = std::move( aProposal );
89
90 if( aAcceptImmediately )
91 {
92 // Synchonously accept the proposal
93 lock.unlock();
95 }
96 else
97 {
98 m_timer.Start( m_timeout.count(), wxTIMER_ONE_SHOT );
99 }
100 }
101
103 {
104 std::lock_guard<std::mutex> lock( m_mutex );
105 m_pendingProposalTag.reset();
106 m_timer.Stop();
107 }
108
109private:
113 void onTimerExpiry( wxTimerEvent& aEvent )
114 {
116 }
117
119 {
120 std::unique_lock<std::mutex> lock( m_mutex );
121
123 {
125 m_pendingProposalTag.reset();
126
127 // Move out from the locked variable
128 T proposalToAccept = std::move( m_lastProposal );
129 lock.unlock();
130
131 // Call the callback (outside the lock)
132 // This is all in the UI thread now, so it won't be concurrent
133 m_callback( std::move( proposalToAccept ) );
134 }
135 }
136
137 mutable std::mutex m_mutex;
138
140 std::chrono::milliseconds m_timeout;
141
143 std::optional<std::size_t> m_pendingProposalTag;
144
146 std::optional<std::size_t> m_lastAcceptedProposalTag;
147
150
153
154 wxTimer m_timer;
155};
156
157
159{
162};
163
164
166 m_viewHandler( aHelper ),
167 m_persistentConstructionBatch(),
168 m_temporaryConstructionBatches(),
169 m_involvedItems(),
170 m_batchesMutex()
171{
172 const std::chrono::milliseconds acceptanceTimeout(
173 ADVANCED_CFG::GetCfg().m_ExtensionSnapTimeoutMs );
174
175 m_activationHelper = std::make_unique<ACTIVATION_HELPER<std::unique_ptr<PENDING_BATCH>>>(
176 acceptanceTimeout,
177 [this]( std::unique_ptr<PENDING_BATCH>&& aAccepted )
178 {
179 // This shouldn't be possible (probably indicates a race in destruction of something)
180 // but at least avoid blowing up acceptConstructionItems.
181 wxCHECK_MSG( aAccepted != nullptr, void(), "Null proposal accepted" );
182
183 acceptConstructionItems( std::move( aAccepted ) );
184 } );
185}
186
187
189{
190}
191
192
196static std::size_t
198 bool aIsPersistent )
199{
200 std::size_t hash = hash_val( aIsPersistent );
201
202 for( const CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM& item : aBatch )
203 {
204 hash_combine( hash, item.Source, item.Item );
205 }
206 return hash;
207}
208
209
211 std::unique_ptr<CONSTRUCTION_ITEM_BATCH> aBatch, bool aIsPersistent )
212{
213 if( aBatch->empty() )
214 {
215 // There's no point in proposing an empty batch
216 // It would just clear existing construction items for nothing new
217 return;
218 }
219
220 bool acceptImmediately = false;
221
222 {
223 std::lock_guard<std::mutex> lock( m_batchesMutex );
224
225 if( aIsPersistent )
226 {
227 acceptImmediately = true;
228 }
229 else
230 {
231 // If the batch is temporary, we can accept it immediately if there's room
232 acceptImmediately = m_temporaryConstructionBatches.size() < getMaxTemporaryBatches();
233 }
234 }
235
236 auto pendingBatch =
237 std::make_unique<PENDING_BATCH>( PENDING_BATCH{ std::move( *aBatch ), aIsPersistent } );
238 const std::size_t hash = HashConstructionBatchSources( pendingBatch->Batch, aIsPersistent );
239
240 // Immediate or not, propose the batch via the activation helper as this handles duplicates
241 m_activationHelper->ProposeActivation( std::move( pendingBatch ), hash, acceptImmediately );
242}
243
244
246{
247 m_activationHelper->CancelProposal();
248}
249
250
252{
253 // We only keep up to one previous temporary batch and the current one
254 // we could make this a setting if we want to keep more, but it gets cluttered
255 return 2;
256}
257
258
259void CONSTRUCTION_MANAGER::acceptConstructionItems( std::unique_ptr<PENDING_BATCH> aAcceptedBatch )
260{
261 const auto getInvolved = [&]( const CONSTRUCTION_ITEM_BATCH& aBatchToAdd )
262 {
263 for( const CONSTRUCTION_ITEM& item : aBatchToAdd )
264 {
265 // Only show the item if it's not already involved
266 // (avoid double-drawing the same item)
267 if( m_involvedItems.count( item.Item ) == 0 )
268 {
269 m_involvedItems.insert( item.Item );
270 }
271 }
272 };
273
274 // Copies for use outside the lock
275 std::vector<CONSTRUCTION_ITEM_BATCH> persistentBatches, temporaryBatches;
276 {
277 std::lock_guard<std::mutex> lock( m_batchesMutex );
278
279 if( aAcceptedBatch->IsPersistent )
280 {
281 // We only keep one previous persistent batch for the moment
282 m_persistentConstructionBatch = std::move( aAcceptedBatch->Batch );
283 }
284 else
285 {
286 bool anyNewItems = false;
287 for( CONSTRUCTION_ITEM& item : aAcceptedBatch->Batch )
288 {
289 if( m_involvedItems.count( item.Item ) == 0 )
290 {
291 anyNewItems = true;
292 break;
293 }
294 }
295
296 // If there are no new items involved, don't bother adding the batch
297 if( !anyNewItems )
298 {
299 return;
300 }
301
303 {
305 }
306
307 m_temporaryConstructionBatches.emplace_back( std::move( aAcceptedBatch->Batch ) );
308 }
309
310 m_involvedItems.clear();
311
312 // Copy the batches for use outside the lock
314 {
315 getInvolved( *m_persistentConstructionBatch );
316 persistentBatches.push_back( *m_persistentConstructionBatch );
317 }
318
320 {
321 getInvolved( batch );
322 temporaryBatches.push_back( batch );
323 }
324 }
325
327 geom.ClearDrawables();
328
329 const auto addDrawables =
330 [&]( const std::vector<CONSTRUCTION_ITEM_BATCH>& aBatches, bool aIsPersistent )
331 {
332 for( const CONSTRUCTION_ITEM_BATCH& batch : aBatches )
333 {
334 for( const CONSTRUCTION_ITEM& item : batch )
335 {
336 for( const KIGFX::CONSTRUCTION_GEOM::DRAWABLE& drawable : item.Constructions )
337 {
338 geom.AddDrawable( drawable, aIsPersistent );
339 }
340 }
341 }
342 };
343
344 addDrawables( persistentBatches, true );
345 addDrawables( temporaryBatches, false );
346
348}
349
350
351bool CONSTRUCTION_MANAGER::InvolvesAllGivenRealItems( const std::vector<EDA_ITEM*>& aItems ) const
352{
353 for( EDA_ITEM* item : aItems )
354 {
355 // Null items (i.e. construction items) are always considered involved
356 if( item && m_involvedItems.count( item ) == 0 )
357 {
358 return false;
359 }
360 }
361
362 return true;
363}
364
365
367 std::vector<CONSTRUCTION_ITEM_BATCH>& aToExtend ) const
368{
369 std::lock_guard<std::mutex> lock( m_batchesMutex );
371 {
372 aToExtend.push_back( *m_persistentConstructionBatch );
373 }
374
376 {
377 aToExtend.push_back( batch );
378 }
379}
380
381
383{
384 std::lock_guard<std::mutex> lock( m_batchesMutex );
386}
387
388
390 m_viewHandler( aViewHandler )
391{
392}
393
394
396{
397 // Setting the origin clears the snap line as the end point is no longer valid
399 m_snapLineOrigin = aOrigin;
400}
401
402
404{
405 if( m_snapLineOrigin && aSnapEnd != m_snapLineEnd )
406 {
407 m_snapLineEnd = aSnapEnd;
408
409 if( m_snapLineEnd )
411 else
413
415 }
416}
417
418
420{
421 m_snapLineOrigin.reset();
422 m_snapLineEnd.reset();
425}
426
427
429{
430 if( m_snapLineOrigin.has_value() )
431 {
432 if( aAnchorPos.x == m_snapLineOrigin->x || aAnchorPos.y == m_snapLineOrigin->y )
433 {
434 SetSnapLineEnd( aAnchorPos );
435 }
436 else
437 {
438 // Snapped to something that is not the snap line origin, so
439 // this anchor is now the new snap line origin
440 SetSnapLineOrigin( aAnchorPos );
441 }
442 }
443 else
444 {
445 // If there's no snap line, start one
446 SetSnapLineOrigin( aAnchorPos );
447 }
448}
449
450
458static bool pointHasEscapedSnapLineX( const VECTOR2I& aCursor, const VECTOR2I& aSnapLineOrigin,
459 int aEscapeRange, EDA_ANGLE aLongRangeEscapeAngle )
460{
461 if( std::abs( aCursor.x - aSnapLineOrigin.x ) < aEscapeRange )
462 {
463 return false;
464 }
465 EDA_ANGLE angle = EDA_ANGLE( aCursor - aSnapLineOrigin ) + EDA_ANGLE( 90, DEGREES_T );
466 return std::abs( angle.Normalize90() ) > aLongRangeEscapeAngle;
467}
468
469
473static bool pointHasEscapedSnapLineY( const VECTOR2I& aCursor, const VECTOR2I& aSnapLineOrigin,
474 int aEscapeRange, EDA_ANGLE aLongRangeEscapeAngle )
475{
476 if( std::abs( aCursor.y - aSnapLineOrigin.y ) < aEscapeRange )
477 {
478 return false;
479 }
480 EDA_ANGLE angle = EDA_ANGLE( aCursor - aSnapLineOrigin );
481 return std::abs( angle.Normalize90() ) > aLongRangeEscapeAngle;
482}
483
484
486 const VECTOR2I& aNearestGrid,
487 std::optional<int> aDistToNearest,
488 int aSnapRange ) const
489{
490 // return std::nullopt;
491 if( m_snapLineOrigin )
492 {
493 bool snapLine = false;
494 VECTOR2I bestSnapPoint = aNearestGrid;
495
496 // If there's no snap anchor, or it's too far away, prefer the grid
497 const bool gridBetterThanNearest = !aDistToNearest || *aDistToNearest > aSnapRange;
498
499 // The escape range is how far you go before the snap line is de-activated.
500 // Make this a bit more forgiving than the snap range, as you can easily cancel
501 // deliberately with a mouse move.
502 // These are both a bit arbitrary, and can be adjusted as preferred
503 const int escapeRange = 2 * aSnapRange;
504 const EDA_ANGLE longRangeEscapeAngle( 4, DEGREES_T );
505
506 const bool escapedX = pointHasEscapedSnapLineX( aCursor, *m_snapLineOrigin, escapeRange,
507 longRangeEscapeAngle );
508 const bool escapedY = pointHasEscapedSnapLineY( aCursor, *m_snapLineOrigin, escapeRange,
509 longRangeEscapeAngle );
510
513 if( !escapedX && gridBetterThanNearest )
514 {
515 bestSnapPoint.x = m_snapLineOrigin->x;
516 snapLine = true;
517 }
518
519 if( !escapedY && gridBetterThanNearest )
520 {
521 bestSnapPoint.y = m_snapLineOrigin->y;
522 snapLine = true;
523 }
524
525 if( snapLine )
526 {
527 return bestSnapPoint;
528 }
529 }
530
531 return std::nullopt;
532}
533
534
536 CONSTRUCTION_VIEW_HANDLER( aHelper ), m_snapLineManager( *this ),
537 m_constructionManager( *this )
538{
539}
540
541
543{
544 if( m_updateCallback )
545 {
546 bool showAnything = m_constructionManager.HasActiveConstruction()
548
549 m_updateCallback( showAnything );
550 }
551}
552
553
554std::vector<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH>
556{
557 std::vector<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH> batches;
558
560
561 if( const OPT_VECTOR2I& snapLineOrigin = m_snapLineManager.GetSnapLineOrigin();
562 snapLineOrigin.has_value() )
563 {
565
567 batch.emplace_back( CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM{
569 nullptr,
570 {},
571 } );
572
573 // One horizontal and one vertical infinite line from the snap point
574 snapPointItem.Constructions.push_back(
575 LINE{ *snapLineOrigin, *snapLineOrigin + VECTOR2I( 100000, 0 ) } );
576 snapPointItem.Constructions.push_back(
577 LINE{ *snapLineOrigin, *snapLineOrigin + VECTOR2I( 0, 100000 ) } );
578
579 batches.push_back( std::move( batch ) );
580 }
581
582 return batches;
583}
A helper class to manage the activation of a "proposal" after a timeout.
void onTimerExpiry(wxTimerEvent &aEvent)
Timer expiry callback in the UI thread.
ACTIVATION_HELPER(std::chrono::milliseconds aTimeout, ACTIVATION_CALLBACK aCallback)
std::optional< std::size_t > m_lastAcceptedProposalTag
The last proposal that was accepted.
ACTIVATION_CALLBACK m_callback
Callback to call when the proposal is accepted.
std::chrono::milliseconds m_timeout
Activation timeout in milliseconds.
void ProposeActivation(T &&aProposal, std::size_t aProposalTag, bool aAcceptImmediately)
std::optional< std::size_t > m_pendingProposalTag
The last proposal tag that was made.
T m_lastProposal
The most recently-proposed item.
std::function< void(T &&)> ACTIVATION_CALLBACK
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
void GetConstructionItems(std::vector< CONSTRUCTION_ITEM_BATCH > &aToExtend) const
Get the list of additional geometry items that should be considered.
void ProposeConstructionItems(std::unique_ptr< CONSTRUCTION_ITEM_BATCH > aBatch, bool aIsPersistent)
Add a batch of construction items to the helper.
CONSTRUCTION_VIEW_HANDLER & m_viewHandler
CONSTRUCTION_MANAGER(CONSTRUCTION_VIEW_HANDLER &aViewHandler)
void CancelProposal()
Cancel outstanding proposals for new geometry.
std::deque< CONSTRUCTION_ITEM_BATCH > m_temporaryConstructionBatches
Temporary construction items are added and removed as needed.
std::vector< CONSTRUCTION_ITEM > CONSTRUCTION_ITEM_BATCH
std::optional< CONSTRUCTION_ITEM_BATCH > m_persistentConstructionBatch
Within one "operation", there is one set of construction items that are "persistent",...
std::unique_ptr< ACTIVATION_HELPER< std::unique_ptr< PENDING_BATCH > > > m_activationHelper
unsigned getMaxTemporaryBatches() const
How many batches of temporary construction items can be active at once.
std::mutex m_batchesMutex
Protects the persistent and temporary construction batches.
bool InvolvesAllGivenRealItems(const std::vector< EDA_ITEM * > &aItems) const
Check if all 'real' (non-null = constructed) the items in the batch are in the list of items currentl...
void acceptConstructionItems(std::unique_ptr< PENDING_BATCH > aAcceptedBatchHash)
std::set< EDA_ITEM * > m_involvedItems
Set of all items for which construction geometry has been added.
Interface wrapper for the construction geometry preview with a callback to signal the view owner that...
virtual void updateView()=0
KIGFX::CONSTRUCTION_GEOM & GetViewItem()
EDA_ANGLE Normalize90()
Definition: eda_angle.h:257
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:98
Shows construction geometry for things like line extensions, arc centers, etc.
void AddDrawable(const DRAWABLE &aItem, bool aIsPersistent)
std::variant< SEG, LINE, HALF_LINE, CIRCLE, SHAPE_ARC, VECTOR2I > DRAWABLE
void SetSnapLine(const SEG &aLine)
Definition: line.h:36
Definition: seg.h:42
void SetSnappedAnchor(const VECTOR2I &aAnchorPos)
Inform this manager that an anchor snap has been made.
OPT_VECTOR2I GetNearestSnapLinePoint(const VECTOR2I &aCursor, const VECTOR2I &aNearestGrid, std::optional< int > aDistToNearest, int snapRange) const
If the snap line is active, return the best snap point that is closest to the cursor.
CONSTRUCTION_VIEW_HANDLER & m_viewHandler
void ClearSnapLine()
Clear the snap line origin and end points.
bool HasCompleteSnapLine() const
SNAP_LINE_MANAGER(CONSTRUCTION_VIEW_HANDLER &aViewHandler)
void SetSnapLineOrigin(const VECTOR2I &aOrigin)
The snap point is a special point that is located at the last point the cursor snapped to.
void SetSnapLineEnd(const OPT_VECTOR2I &aSnapPoint)
Set the end point of the snap line.
const OPT_VECTOR2I & GetSnapLineOrigin() const
OPT_VECTOR2I m_snapLineOrigin
GFX_UPDATE_CALLBACK m_updateCallback
CONSTRUCTION_MANAGER m_constructionManager
std::vector< CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH > GetConstructionItems() const
Get a list of all the active construction geometry, computed from the combined state of the snap line...
void updateView() override
SNAP_LINE_MANAGER m_snapLineManager
SNAP_MANAGER(KIGFX::CONSTRUCTION_GEOM &aHelper)
static std::size_t HashConstructionBatchSources(const CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH &aBatch, bool aIsPersistent)
Construct a hash based on the sources of the items in the batch.
static bool pointHasEscapedSnapLineX(const VECTOR2I &aCursor, const VECTOR2I &aSnapLineOrigin, int aEscapeRange, EDA_ANGLE aLongRangeEscapeAngle)
Check if the cursor has moved far enough away from the snap line origin to escape snapping in the X d...
static bool pointHasEscapedSnapLineY(const VECTOR2I &aCursor, const VECTOR2I &aSnapLineOrigin, int aEscapeRange, EDA_ANGLE aLongRangeEscapeAngle)
As above, but for the Y direction.
@ DEGREES_T
Definition: eda_angle.h:31
static constexpr void hash_combine(std::size_t &seed)
This is a dummy function to take the final case of hash_combine below.
Definition: hash.h:32
static constexpr std::size_t hash_val(const Types &... args)
Definition: hash.h:51
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:400
std::optional< VECTOR2I > OPT_VECTOR2I
Definition: seg.h:39
Items to be used for the construction of "virtual" anchors, for example, when snapping to a point inv...
std::vector< KIGFX::CONSTRUCTION_GEOM::DRAWABLE > Constructions
VECTOR2< int32_t > VECTOR2I
Definition: vector2d.h:695