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