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, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <algorithm>
23#include <chrono>
24#include <cmath>
25#include <limits>
26#include <numeric>
27#include <utility>
28
29#include <wx/timer.h>
30#include <wx/debug.h>
31#include <wx/log.h>
32
33#include <advanced_config.h>
34#include <math/util.h>
35#include <hash.h>
36#include <trace_helpers.h>
37
38
50template <typename T>
52{
53public:
54 using ACTIVATION_CALLBACK = std::function<void( T&& )>;
55
56 ACTIVATION_HELPER( std::chrono::milliseconds aTimeout, ACTIVATION_CALLBACK aCallback ) :
57 m_timeout( aTimeout ),
58 m_callback( std::move( aCallback ) )
59 {
60 m_timer.Bind( wxEVT_TIMER, &ACTIVATION_HELPER::onTimerExpiry, this );
61 }
62
64 {
65 // Hold the lock while shutting down to prevent a propoal being accepted
66 // while state is being destroyed.
67 std::unique_lock<std::mutex> lock( m_mutex );
68 m_timer.Stop();
69 m_timer.Unbind( wxEVT_TIMER, &ACTIVATION_HELPER::onTimerExpiry, this );
70
71 // Should be redundant to inhibiting timer callbacks, but make it explicit.
73 }
74
75 void ProposeActivation( T&& aProposal, std::size_t aProposalTag, bool aAcceptImmediately )
76 {
77 std::unique_lock<std::mutex> lock( m_mutex );
78
79 if( m_lastAcceptedProposalTag.has_value() && aProposalTag == *m_lastAcceptedProposalTag )
80 {
81 // This proposal was accepted last time
82 // (could be made optional if we want to allow re-accepting the same proposal)
83 return;
84 }
85
86 if( m_pendingProposalTag.has_value() && aProposalTag == *m_pendingProposalTag )
87 {
88 // This proposal is already pending
89 return;
90 }
91
92 m_pendingProposalTag = aProposalTag;
93 m_lastProposal = std::move( aProposal );
94
95 if( aAcceptImmediately )
96 {
97 // Synchonously accept the proposal
98 lock.unlock();
100 }
101 else
102 {
103 m_timer.Start( m_timeout.count(), wxTIMER_ONE_SHOT );
104 }
105 }
106
108 {
109 std::lock_guard<std::mutex> lock( m_mutex );
110 m_pendingProposalTag.reset();
111 m_timer.Stop();
112 }
113
114private:
118 void onTimerExpiry( wxTimerEvent& aEvent )
119 {
121 }
122
124 {
125 std::unique_lock<std::mutex> lock( m_mutex );
126
128 {
130 m_pendingProposalTag.reset();
131
132 // Move out from the locked variable
133 T proposalToAccept = std::move( m_lastProposal );
134 lock.unlock();
135
136 // Call the callback (outside the lock)
137 // This is all in the UI thread now, so it won't be concurrent
138 m_callback( std::move( proposalToAccept ) );
139 }
140 }
141
142 mutable std::mutex m_mutex;
143
145 std::chrono::milliseconds m_timeout;
146
148 std::optional<std::size_t> m_pendingProposalTag;
149
151 std::optional<std::size_t> m_lastAcceptedProposalTag;
152
155
158
159 wxTimer m_timer;
160};
161
162
168
169
171 m_viewHandler( aHelper ),
176{
177 const std::chrono::milliseconds acceptanceTimeout(
178 ADVANCED_CFG::GetCfg().m_ExtensionSnapTimeoutMs );
179
180 m_activationHelper = std::make_unique<ACTIVATION_HELPER<std::unique_ptr<PENDING_BATCH>>>(
181 acceptanceTimeout,
182 [this]( std::unique_ptr<PENDING_BATCH>&& aAccepted )
183 {
184 // This shouldn't be possible (probably indicates a race in destruction of something)
185 // but at least avoid blowing up acceptConstructionItems.
186 wxCHECK_MSG( aAccepted != nullptr, void(), "Null proposal accepted" );
187
188 acceptConstructionItems( std::move( aAccepted ) );
189 } );
190}
191
192
196
197
201static std::size_t
203 bool aIsPersistent )
204{
205 std::size_t hash = hash_val( aIsPersistent );
206
207 for( const CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM& item : aBatch )
208 {
209 hash_combine( hash, item.Source, item.Item );
210 }
211 return hash;
212}
213
214
216 std::unique_ptr<CONSTRUCTION_ITEM_BATCH> aBatch, bool aIsPersistent )
217{
218 if( aBatch->empty() )
219 {
220 // There's no point in proposing an empty batch
221 // It would just clear existing construction items for nothing new
222 return;
223 }
224
225 bool acceptImmediately = false;
226
227 {
228 std::lock_guard<std::mutex> lock( m_batchesMutex );
229
230 if( aIsPersistent )
231 {
232 acceptImmediately = true;
233 }
234 else
235 {
236 // If the batch is temporary, we can accept it immediately if there's room
237 acceptImmediately = m_temporaryConstructionBatches.size() < getMaxTemporaryBatches();
238 }
239 }
240
241 auto pendingBatch =
242 std::make_unique<PENDING_BATCH>( PENDING_BATCH{ std::move( *aBatch ), aIsPersistent } );
243 const std::size_t hash = HashConstructionBatchSources( pendingBatch->Batch, aIsPersistent );
244
245 // Immediate or not, propose the batch via the activation helper as this handles duplicates
246 m_activationHelper->ProposeActivation( std::move( pendingBatch ), hash, acceptImmediately );
247}
248
249
251{
252 m_activationHelper->CancelProposal();
253}
254
255
257{
258 // We only keep up to one previous temporary batch and the current one
259 // we could make this a setting if we want to keep more, but it gets cluttered
260 return 2;
261}
262
263
264void CONSTRUCTION_MANAGER::acceptConstructionItems( std::unique_ptr<PENDING_BATCH> aAcceptedBatch )
265{
266 const auto getInvolved = [&]( const CONSTRUCTION_ITEM_BATCH& aBatchToAdd )
267 {
268 for( const CONSTRUCTION_ITEM& item : aBatchToAdd )
269 {
270 // Only show the item if it's not already involved
271 // (avoid double-drawing the same item)
272 if( m_involvedItems.count( item.Item ) == 0 )
273 {
274 m_involvedItems.insert( item.Item );
275 }
276 }
277 };
278
279 // Copies for use outside the lock
280 std::vector<CONSTRUCTION_ITEM_BATCH> persistentBatches, temporaryBatches;
281 {
282 std::lock_guard<std::mutex> lock( m_batchesMutex );
283
284 if( aAcceptedBatch->IsPersistent )
285 {
286 // We only keep one previous persistent batch for the moment
287 m_persistentConstructionBatch = std::move( aAcceptedBatch->Batch );
288 }
289 else
290 {
291 bool anyNewItems = false;
292 for( CONSTRUCTION_ITEM& item : aAcceptedBatch->Batch )
293 {
294 if( m_involvedItems.count( item.Item ) == 0 )
295 {
296 anyNewItems = true;
297 break;
298 }
299 }
300
301 // If there are no new items involved, don't bother adding the batch
302 if( !anyNewItems )
303 {
304 return;
305 }
306
308 {
310 }
311
312 m_temporaryConstructionBatches.emplace_back( std::move( aAcceptedBatch->Batch ) );
313 }
314
315 m_involvedItems.clear();
316
317 // Copy the batches for use outside the lock
319 {
320 getInvolved( *m_persistentConstructionBatch );
321 persistentBatches.push_back( *m_persistentConstructionBatch );
322 }
323
325 {
326 getInvolved( batch );
327 temporaryBatches.push_back( batch );
328 }
329 }
330
331 KIGFX::CONSTRUCTION_GEOM& geom = m_viewHandler.GetViewItem();
332 geom.ClearDrawables();
333
334 const auto addDrawables =
335 [&]( const std::vector<CONSTRUCTION_ITEM_BATCH>& aBatches, bool aIsPersistent )
336 {
337 for( const CONSTRUCTION_ITEM_BATCH& batch : aBatches )
338 {
339 for( const CONSTRUCTION_ITEM& item : batch )
340 {
341 for( const CONSTRUCTION_ITEM::DRAWABLE_ENTRY& drawable : item.Constructions )
342 {
343 geom.AddDrawable( drawable.Drawable, aIsPersistent, drawable.LineWidth );
344 }
345 }
346 }
347 };
348
349 addDrawables( persistentBatches, true );
350 addDrawables( temporaryBatches, false );
351
352 m_viewHandler.updateView();
353}
354
355
356bool CONSTRUCTION_MANAGER::InvolvesAllGivenRealItems( const std::vector<EDA_ITEM*>& aItems ) const
357{
358 for( EDA_ITEM* item : aItems )
359 {
360 // Null items (i.e. construction items) are always considered involved
361 if( item && m_involvedItems.count( item ) == 0 )
362 {
363 return false;
364 }
365 }
366
367 return true;
368}
369
370
372 std::vector<CONSTRUCTION_ITEM_BATCH>& aToExtend ) const
373{
374 std::lock_guard<std::mutex> lock( m_batchesMutex );
376 {
377 aToExtend.push_back( *m_persistentConstructionBatch );
378 }
379
381 {
382 aToExtend.push_back( batch );
383 }
384}
385
386
388{
389 std::lock_guard<std::mutex> lock( m_batchesMutex );
391}
392
393
395 m_viewHandler( aViewHandler ), m_snapManager( static_cast<SNAP_MANAGER*>( &aViewHandler ) )
396{
397 wxASSERT( m_snapManager );
398 SetDirections( { VECTOR2I( 1, 0 ), VECTOR2I( 0, 1 ) } );
399}
400
401
403{
404 if( aDir.x == 0 && aDir.y == 0 )
405 return VECTOR2I( 0, 0 );
406
407 int dx = aDir.x;
408 int dy = aDir.y;
409
410 int gcd = std::gcd( std::abs( dx ), std::abs( dy ) );
411
412 if( gcd > 0 )
413 {
414 dx /= gcd;
415 dy /= gcd;
416 }
417
418 if( dx < 0 || ( dx == 0 && dy < 0 ) )
419 {
420 dx = -dx;
421 dy = -dy;
422 }
423
424 return VECTOR2I( dx, dy );
425}
426
427
428static std::optional<int> findDirectionIndex( const std::vector<VECTOR2I>& aDirections,
429 const VECTOR2I& aDelta )
430{
431 VECTOR2I normalized = normalizeDirection( aDelta );
432
433 if( normalized.x == 0 && normalized.y == 0 )
434 return std::nullopt;
435
436 for( size_t i = 0; i < aDirections.size(); ++i )
437 {
438 if( aDirections[i] == normalized )
439 return static_cast<int>( i );
440 }
441
442 return std::nullopt;
443}
444
445
446void SNAP_LINE_MANAGER::SetDirections( const std::vector<VECTOR2I>& aDirections )
447{
448 std::vector<VECTOR2I> uniqueDirections;
449 uniqueDirections.reserve( aDirections.size() );
450
451 for( const VECTOR2I& direction : aDirections )
452 {
453 VECTOR2I normalized = normalizeDirection( direction );
454
455 if( normalized.x == 0 && normalized.y == 0 )
456 continue;
457
458 if( std::find( uniqueDirections.begin(), uniqueDirections.end(), normalized )
459 == uniqueDirections.end() )
460 {
461 uniqueDirections.push_back( normalized );
462 }
463 }
464
465 if( uniqueDirections != m_directions )
466 {
467 m_directions = std::move( uniqueDirections );
468 m_activeDirection.reset();
469
471 {
473 m_snapLineEnd.reset();
474 }
475
476 if( m_directions.empty() )
477 {
479 return;
480 }
481
483 }
484}
485
486
488{
489 if( m_snapLineOrigin && *m_snapLineOrigin == aOrigin && !m_snapLineEnd )
490 {
492 return;
493 }
494
495 m_snapLineOrigin = aOrigin;
496 m_snapLineEnd.reset();
497 m_activeDirection.reset();
498 m_viewHandler.GetViewItem().ClearSnapLine();
500}
501
502
504{
505 if( m_snapLineOrigin && aSnapEnd != m_snapLineEnd )
506 {
507 m_snapLineEnd = aSnapEnd;
508
509 if( m_snapLineEnd )
511 else
512 m_activeDirection.reset();
513
514 if( m_snapLineEnd )
515 m_viewHandler.GetViewItem().SetSnapLine( SEG{ *m_snapLineOrigin, *m_snapLineEnd } );
516 else
517 m_viewHandler.GetViewItem().ClearSnapLine();
518
520 }
521}
522
523
525{
526 m_snapLineOrigin.reset();
527 m_snapLineEnd.reset();
528 m_activeDirection.reset();
529 m_viewHandler.GetViewItem().ClearSnapLine();
531}
532
533
535{
536 if( m_snapLineOrigin.has_value() )
537 {
538 if( findDirectionIndex( m_directions, aAnchorPos - *m_snapLineOrigin ) )
539 {
540 SetSnapLineEnd( aAnchorPos );
541 }
542 else
543 {
544 // Snapped to something that is not the snap line origin, so
545 // this anchor is now the new snap line origin
546 SetSnapLineOrigin( aAnchorPos );
547 }
548 }
549 else
550 {
551 // If there's no snap line, start one
552 SetSnapLineOrigin( aAnchorPos );
553 }
554}
555
556
558 const VECTOR2I& aNearestGrid,
559 std::optional<int> aDistToNearest,
560 int aSnapRange,
561 const VECTOR2D& aGridSize,
562 const VECTOR2I& aGridOrigin ) const
563{
564 wxLogTrace( traceSnap, "GetNearestSnapLinePoint: cursor=(%d, %d), nearestGrid=(%d, %d), distToNearest=%s, snapRange=%d",
565 aCursor.x, aCursor.y, aNearestGrid.x, aNearestGrid.y,
566 aDistToNearest ? wxString::Format( "%d", *aDistToNearest ) : wxString( "none" ), aSnapRange );
567
568 if( !m_snapLineOrigin || m_directions.empty() )
569 {
570 wxLogTrace( traceSnap, " No snap line origin or no directions, returning nullopt" );
571 return std::nullopt;
572 }
573
574 const bool gridBetterThanNearest = !aDistToNearest || *aDistToNearest > aSnapRange;
575 const bool gridActive = aGridSize.x > 0 && aGridSize.y > 0;
576
577 wxLogTrace( traceSnap, " snapLineOrigin=(%d, %d), directions count=%zu, gridBetterThanNearest=%d, gridActive=%d",
578 m_snapLineOrigin->x, m_snapLineOrigin->y, m_directions.size(), gridBetterThanNearest, gridActive );
579
580 if( !gridBetterThanNearest )
581 {
582 wxLogTrace( traceSnap, " Grid not better than nearest, returning nullopt" );
583 return std::nullopt;
584 }
585
586 const int escapeRange = 2 * aSnapRange;
587 const EDA_ANGLE longRangeEscapeAngle( 4, DEGREES_T );
588
589 wxLogTrace( traceSnap, " escapeRange=%d, longRangeEscapeAngle=%.1f deg",
590 escapeRange, longRangeEscapeAngle.AsDegrees() );
591
592 const VECTOR2D origin( *m_snapLineOrigin );
593 const VECTOR2D cursor( aCursor );
594 const VECTOR2D delta = cursor - origin;
595
596 double bestPerpDistance = std::numeric_limits<double>::max();
597 std::optional<VECTOR2I> bestSnapPoint;
598
599 for( size_t ii = 0; ii < m_directions.size(); ++ii )
600 {
601 const VECTOR2I& direction = m_directions[ii];
602 VECTOR2D dirVector( direction );
603 double dirLength = dirVector.EuclideanNorm();
604
605 if( dirLength == 0.0 )
606 {
607 wxLogTrace( traceSnap, " Direction %zu: zero length, skipping", ii );
608 continue;
609 }
610
611 VECTOR2D dirUnit = dirVector / dirLength;
612
613 double distanceAlong = delta.Dot( dirUnit );
614 VECTOR2D projection = origin + dirUnit * distanceAlong;
615 VECTOR2D offset = delta - dirUnit * distanceAlong;
616 double perpDistance = offset.EuclideanNorm();
617
618 wxLogTrace( traceSnap, " Direction %zu: dir=(%d, %d), perpDist=%.1f, distAlong=%.1f",
619 ii, direction.x, direction.y, perpDistance, distanceAlong );
620
621 if( perpDistance > aSnapRange )
622 {
623 wxLogTrace( traceSnap, " perpDistance > snapRange, skipping" );
624 continue;
625 }
626
627 bool escaped = false;
628
629 if( perpDistance >= escapeRange )
630 {
631 EDA_ANGLE deltaAngle( delta );
632 EDA_ANGLE directionAngle( dirVector );
633 double angleDiff = ( deltaAngle - directionAngle ).Normalize180().AsDegrees();
634
635 wxLogTrace( traceSnap, " In escape range: deltaAngle=%.1f, dirAngle=%.1f, angleDiff=%.1f",
636 deltaAngle.AsDegrees(), directionAngle.AsDegrees(), angleDiff );
637
638 if( std::abs( angleDiff ) > longRangeEscapeAngle.AsDegrees() )
639 {
640 escaped = true;
641 wxLogTrace( traceSnap, " ESCAPED (angle diff too large)" );
642 }
643 }
644
645 if( escaped )
646 {
647 wxLogTrace( traceSnap, " Not updating (escaped)" );
648 continue;
649 }
650
651 // Now snap the projection to the grid if the grid is active
652 VECTOR2D snapPoint = projection;
653
654 if( gridActive )
655 {
656 // For horizontal/vertical lines, snap to grid intersections
657 if( direction.x == 0 && direction.y != 0 )
658 {
659 // Vertical line: keep origin X, snap Y to grid
660 snapPoint.x = origin.x;
661 snapPoint.y = aNearestGrid.y;
662 wxLogTrace( traceSnap, " Vertical line: snapping to grid Y, snapPoint=(%.1f, %.1f)",
663 snapPoint.x, snapPoint.y );
664 }
665 else if( direction.y == 0 && direction.x != 0 )
666 {
667 // Horizontal line: snap X to grid, keep origin Y
668 snapPoint.x = aNearestGrid.x;
669 snapPoint.y = origin.y;
670 wxLogTrace( traceSnap, " Horizontal line: snapping to grid X, snapPoint=(%.1f, %.1f)",
671 snapPoint.x, snapPoint.y );
672 }
673 else
674 {
675 // Diagonal line: find nearest grid intersection along the line
676 VECTOR2D gridOriginD( aGridOrigin );
677 VECTOR2D relProjection = projection - gridOriginD;
678
679 // Find nearby grid points (check 3x3 grid around projection)
680 double bestGridScore = std::numeric_limits<double>::max();
681 VECTOR2D bestGridPoint = projection;
682
683 for( int dx = -1; dx <= 1; ++dx )
684 {
685 for( int dy = -1; dy <= 1; ++dy )
686 {
687 double gridX = std::round( relProjection.x / aGridSize.x ) * aGridSize.x + dx * aGridSize.x;
688 double gridY = std::round( relProjection.y / aGridSize.y ) * aGridSize.y + dy * aGridSize.y;
689 VECTOR2D gridPt( gridX + gridOriginD.x, gridY + gridOriginD.y );
690
691 // Calculate perpendicular distance from grid point to construction line
692 VECTOR2D gridDelta = gridPt - origin;
693 double gridDistAlong = gridDelta.Dot( dirUnit );
694 VECTOR2D gridProjection = origin + dirUnit * gridDistAlong;
695 double gridPerpDist = ( gridPt - gridProjection ).EuclideanNorm();
696
697 // Also consider distance from cursor
698 double distFromCursor = ( gridPt - cursor ).EuclideanNorm();
699
700 // Prefer grid points that are close to the line and close to cursor
701 double score = gridPerpDist + distFromCursor * 0.1;
702
703 if( score < bestGridScore )
704 {
705 bestGridScore = score;
706 bestGridPoint = gridPt;
707 }
708 }
709 }
710
711 snapPoint = bestGridPoint;
712 wxLogTrace( traceSnap, " Diagonal line: snapping to grid intersection, snapPoint=(%.1f, %.1f)",
713 snapPoint.x, snapPoint.y );
714 }
715 }
716 else
717 {
718 wxLogTrace( traceSnap, " Grid not active, using projection" );
719 }
720
721 if( perpDistance < bestPerpDistance )
722 {
723 bestPerpDistance = perpDistance;
724 bestSnapPoint = KiROUND( snapPoint );
725 wxLogTrace( traceSnap, " NEW BEST: perpDist=%.1f, snapPoint=(%d, %d)",
726 bestPerpDistance, bestSnapPoint->x, bestSnapPoint->y );
727 }
728 else
729 {
730 wxLogTrace( traceSnap, " Not updating (perpDist=%.1f >= bestPerp=%.1f)",
731 perpDistance, bestPerpDistance );
732 }
733 }
734
735 if( bestSnapPoint )
736 {
737 wxLogTrace( traceSnap, " RETURNING bestSnapPoint=(%d, %d)", bestSnapPoint->x, bestSnapPoint->y );
738 return *bestSnapPoint;
739 }
740
741 wxLogTrace( traceSnap, " RETURNING nullopt (no valid snap found)" );
742 return std::nullopt;
743}
744
745
752
753
755{
756 if( m_updateCallback )
757 {
758 bool showAnything = m_constructionManager.HasActiveConstruction()
759 || m_snapLineManager.HasCompleteSnapLine()
760 || ( m_snapLineManager.GetSnapLineOrigin()
761 && !m_snapLineManager.GetDirections().empty() );
762
763 m_updateCallback( showAnything );
764 }
765}
766
767
769{
770 m_snapGuideColor = aBase;
771 m_snapGuideHighlightColor = aHighlight;
773}
774
775
777{
778 std::vector<KIGFX::CONSTRUCTION_GEOM::SNAP_GUIDE> guides;
779
780 const OPT_VECTOR2I& origin = m_snapLineManager.GetSnapLineOrigin();
781 const std::vector<VECTOR2I>& directions = m_snapLineManager.GetDirections();
782
783 if( origin && !directions.empty() )
784 {
785 const std::optional<int> activeDirection = m_snapLineManager.GetActiveDirection();
786 const int guideLength = 500000;
787
788 for( size_t ii = 0; ii < directions.size(); ++ii )
789 {
790 const VECTOR2I& direction = directions[ii];
791
792 if( direction.x == 0 && direction.y == 0 )
793 continue;
794
795 VECTOR2I scaled = direction * guideLength;
796
798 guide.Segment = SEG( *origin - scaled, *origin + scaled );
799
800 if( activeDirection && *activeDirection == static_cast<int>( ii ) )
801 {
802 guide.LineWidth = 5;
804 }
805 else
806 {
807 guide.LineWidth = 1;
808 guide.Color = m_snapGuideColor;
809 }
810
811 guides.push_back( guide );
812 }
813 }
814
815 GetViewItem().SetSnapGuides( std::move( guides ) );
816 updateView();
817}
818
819
821{
822 if( m_snapManager )
823 m_snapManager->UpdateSnapGuides();
824}
825
826
827std::vector<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH>
829{
830 std::vector<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH> batches;
831
832 m_constructionManager.GetConstructionItems( batches );
833
834 if( const OPT_VECTOR2I& snapLineOrigin = m_snapLineManager.GetSnapLineOrigin();
835 snapLineOrigin.has_value() )
836 {
838
840 batch.emplace_back( CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM{
842 nullptr,
843 {},
844 } );
845
846 const std::vector<VECTOR2I>& directions = m_snapLineManager.GetDirections();
847 const std::optional<int> activeDirection = m_snapLineManager.GetActiveDirection();
848
849 for( size_t ii = 0; ii < directions.size(); ++ii )
850 {
851 const VECTOR2I& direction = directions[ii];
852
853 VECTOR2I scaledDirection = direction * 100000;
854
856 entry.Drawable = LINE{ *snapLineOrigin, *snapLineOrigin + scaledDirection };
857 entry.LineWidth = ( activeDirection && *activeDirection == static_cast<int>( ii ) ) ? 2 : 1;
858
859 snapPointItem.Constructions.push_back( entry );
860 }
861
862 if( !snapPointItem.Constructions.empty() )
863 batches.push_back( std::move( batch ) );
864 }
865
866 return batches;
867}
868
869
871{
872 std::lock_guard<std::mutex> lock( m_batchesMutex );
873
876 m_involvedItems.clear();
878}
879
880
882{
883 m_snapLineManager.ClearSnapLine();
884 m_constructionManager.Clear();
886}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
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.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
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.
void Clear()
Clear all construction items.
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...
CONSTRUCTION_VIEW_HANDLER(KIGFX::CONSTRUCTION_GEOM &aHelper)
KIGFX::CONSTRUCTION_GEOM & GetViewItem()
double AsDegrees() const
Definition eda_angle.h:116
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:96
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
Shows construction geometry for things like line extensions, arc centers, etc.
void AddDrawable(const DRAWABLE &aItem, bool aIsPersistent, int aLineWidth=1)
void SetSnapGuides(std::vector< SNAP_GUIDE > aGuides)
Definition line.h:32
Definition seg.h:38
OPT_VECTOR2I GetNearestSnapLinePoint(const VECTOR2I &aCursor, const VECTOR2I &aNearestGrid, std::optional< int > aDistToNearest, int snapRange, const VECTOR2D &aGridSize=VECTOR2D(0, 0), const VECTOR2I &aGridOrigin=VECTOR2I(0, 0)) const
If the snap line is active, return the best snap point that is closest to the cursor.
void SetDirections(const std::vector< VECTOR2I > &aDirections)
void SetSnappedAnchor(const VECTOR2I &aAnchorPos)
Inform this manager that an anchor snap has been made.
CONSTRUCTION_VIEW_HANDLER & m_viewHandler
void ClearSnapLine()
Clear the snap line origin and end points.
std::vector< VECTOR2I > m_directions
SNAP_MANAGER * m_snapManager
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.
std::optional< int > m_activeDirection
void SetSnapLineEnd(const OPT_VECTOR2I &aSnapPoint)
Set the end point of the snap line.
A SNAP_MANAGER glues together the snap line manager and construction manager., along with some other ...
void SetSnapGuideColors(const KIGFX::COLOR4D &aBase, const KIGFX::COLOR4D &aHighlight)
KIGFX::COLOR4D m_snapGuideColor
KIGFX::COLOR4D m_snapGuideHighlightColor
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)
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:279
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:542
@ WHITE
Definition color4d.h:44
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 VECTOR2I normalizeDirection(const VECTOR2I &aDir)
static std::optional< int > findDirectionIndex(const std::vector< VECTOR2I > &aDirections, const VECTOR2I &aDelta)
@ DEGREES_T
Definition eda_angle.h:31
const wxChar *const traceSnap
Flag to enable snap/grid helper debug tracing.
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:28
static constexpr std::size_t hash_val(const Types &... args)
Definition hash.h:47
The Cairo implementation of the graphics abstraction layer.
Definition eda_group.h:29
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:35
KIGFX::CONSTRUCTION_GEOM::DRAWABLE Drawable
int LineWidth
Items to be used for the construction of "virtual" anchors, for example, when snapping to a point inv...
std::vector< DRAWABLE_ENTRY > Constructions
int delta
wxLogTrace helper definitions.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682