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
114 template <typename Func>
115 void InspectPendingProposal( Func&& aFunc ) const
116 {
117 std::lock_guard<std::mutex> lock( m_mutex );
118
119 // The callback observes protected state and must not retain it or re-enter this helper.
121 aFunc( m_lastProposal );
122 }
123
124private:
128 void onTimerExpiry( wxTimerEvent& aEvent )
129 {
131 }
132
134 {
135 std::unique_lock<std::mutex> lock( m_mutex );
136
138 {
140 m_pendingProposalTag.reset();
141
142 // Move out from the locked variable
143 T proposalToAccept = std::move( m_lastProposal );
144 lock.unlock();
145
146 // Call the callback (outside the lock)
147 // This is all in the UI thread now, so it won't be concurrent
148 m_callback( std::move( proposalToAccept ) );
149 }
150 }
151
152 mutable std::mutex m_mutex;
153
155 std::chrono::milliseconds m_timeout;
156
158 std::optional<std::size_t> m_pendingProposalTag;
159
161 std::optional<std::size_t> m_lastAcceptedProposalTag;
162
165
168
169 wxTimer m_timer;
170};
171
172
178
179
181 m_viewHandler( aHelper ),
186{
187 const std::chrono::milliseconds acceptanceTimeout(
188 ADVANCED_CFG::GetCfg().m_ExtensionSnapTimeoutMs );
189
190 m_activationHelper = std::make_unique<ACTIVATION_HELPER<std::unique_ptr<PENDING_BATCH>>>(
191 acceptanceTimeout,
192 [this]( std::unique_ptr<PENDING_BATCH>&& aAccepted )
193 {
194 // This shouldn't be possible (probably indicates a race in destruction of something)
195 // but at least avoid blowing up acceptConstructionItems.
196 wxCHECK_MSG( aAccepted != nullptr, void(), "Null proposal accepted" );
197
198 acceptConstructionItems( std::move( aAccepted ) );
199 } );
200}
201
202
206
207
211static std::size_t
213 bool aIsPersistent )
214{
215 std::size_t hash = hash_val( aIsPersistent );
216
217 for( const CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM& item : aBatch )
218 {
219 hash_combine( hash, item.Source, item.Item );
220 }
221 return hash;
222}
223
224
226 std::unique_ptr<CONSTRUCTION_ITEM_BATCH> aBatch, bool aIsPersistent )
227{
228 if( aBatch->empty() )
229 {
230 // There's no point in proposing an empty batch
231 // It would just clear existing construction items for nothing new
232 return;
233 }
234
235 auto pendingBatch =
236 std::make_unique<PENDING_BATCH>( PENDING_BATCH{ std::move( *aBatch ), aIsPersistent } );
237 const std::size_t hash = HashConstructionBatchSources( pendingBatch->Batch, aIsPersistent );
238
239 // Immediate or not, propose the batch via the activation helper as this handles duplicates
240 m_activationHelper->ProposeActivation( std::move( pendingBatch ), hash, aIsPersistent );
241}
242
243
245{
246 m_activationHelper->CancelProposal();
247}
248
249
251{
252 // We only keep up to one previous temporary batch and the current one
253 // we could make this a setting if we want to keep more, but it gets cluttered
254 return 2;
255}
256
257
258void CONSTRUCTION_MANAGER::acceptConstructionItems( std::unique_ptr<PENDING_BATCH> aAcceptedBatch )
259{
260 const auto getInvolved = [&]( const CONSTRUCTION_ITEM_BATCH& aBatchToAdd )
261 {
262 for( const CONSTRUCTION_ITEM& item : aBatchToAdd )
263 {
264 // Only show the item if it's not already involved
265 // (avoid double-drawing the same item)
266 if( m_involvedItems.count( item.Item ) == 0 )
267 {
268 m_involvedItems.insert( item.Item );
269 }
270 }
271 };
272
273 // Copies for use outside the lock
274 std::vector<CONSTRUCTION_ITEM_BATCH> persistentBatches, temporaryBatches;
275 {
276 std::lock_guard<std::mutex> lock( m_batchesMutex );
277
278 if( aAcceptedBatch->IsPersistent )
279 {
280 // We only keep one previous persistent batch for the moment
281 m_persistentConstructionBatch = std::move( aAcceptedBatch->Batch );
282 }
283 else
284 {
285 bool anyNewItems = false;
286 for( CONSTRUCTION_ITEM& item : aAcceptedBatch->Batch )
287 {
288 if( m_involvedItems.count( item.Item ) == 0 )
289 {
290 anyNewItems = true;
291 break;
292 }
293 }
294
295 // If there are no new items involved, don't bother adding the batch
296 if( !anyNewItems )
297 {
298 return;
299 }
300
302 {
304 }
305
306 m_temporaryConstructionBatches.emplace_back( std::move( aAcceptedBatch->Batch ) );
307 }
308
309 m_involvedItems.clear();
310
311 // Copy the batches for use outside the lock
313 {
314 getInvolved( *m_persistentConstructionBatch );
315 persistentBatches.push_back( *m_persistentConstructionBatch );
316 }
317
319 {
320 getInvolved( batch );
321 temporaryBatches.push_back( batch );
322 }
323 }
324
325 KIGFX::CONSTRUCTION_GEOM& geom = m_viewHandler.GetViewItem();
326 geom.ClearDrawables();
327
328 const auto addDrawables =
329 [&]( const std::vector<CONSTRUCTION_ITEM_BATCH>& aBatches, bool aIsPersistent )
330 {
331 for( const CONSTRUCTION_ITEM_BATCH& batch : aBatches )
332 {
333 for( const CONSTRUCTION_ITEM& item : batch )
334 {
335 for( const CONSTRUCTION_ITEM::DRAWABLE_ENTRY& drawable : item.Constructions )
336 {
337 geom.AddDrawable( drawable.Drawable, aIsPersistent, drawable.LineWidth );
338 }
339 }
340 }
341 };
342
343 addDrawables( persistentBatches, true );
344 addDrawables( temporaryBatches, false );
345
346 m_viewHandler.updateView();
347}
348
349
350bool CONSTRUCTION_MANAGER::InvolvesAllGivenRealItems( const std::vector<EDA_ITEM*>& aItems ) const
351{
352 for( EDA_ITEM* item : aItems )
353 {
354 // Null items (i.e. construction items) are always considered involved
355 if( item && m_involvedItems.count( item ) == 0 )
356 {
357 return false;
358 }
359 }
360
361 return true;
362}
363
364
366 std::vector<CONSTRUCTION_ITEM_BATCH>& aToExtend ) const
367{
368 std::lock_guard<std::mutex> lock( m_batchesMutex );
370 {
371 aToExtend.push_back( *m_persistentConstructionBatch );
372 }
373
375 {
376 aToExtend.push_back( batch );
377 }
378}
379
380
381void CONSTRUCTION_MANAGER::GetPendingConstructionItems( std::vector<CONSTRUCTION_ITEM_BATCH>& aToExtend ) const
382{
383 m_activationHelper->InspectPendingProposal(
384 [&]( const std::unique_ptr<PENDING_BATCH>& aPending )
385 {
386 if( aPending )
387 aToExtend.push_back( aPending->Batch );
388 } );
389}
390
391
393{
394 std::lock_guard<std::mutex> lock( m_batchesMutex );
396}
397
398
400 m_viewHandler( aViewHandler ), m_snapManager( static_cast<SNAP_MANAGER*>( &aViewHandler ) )
401{
402 wxASSERT( m_snapManager );
403 SetDirections( { VECTOR2I( 1, 0 ), VECTOR2I( 0, 1 ) } );
404}
405
406
408{
409 if( aDir.x == 0 && aDir.y == 0 )
410 return VECTOR2I( 0, 0 );
411
412 int dx = aDir.x;
413 int dy = aDir.y;
414
415 int gcd = std::gcd( std::abs( dx ), std::abs( dy ) );
416
417 if( gcd > 0 )
418 {
419 dx /= gcd;
420 dy /= gcd;
421 }
422
423 if( dx < 0 || ( dx == 0 && dy < 0 ) )
424 {
425 dx = -dx;
426 dy = -dy;
427 }
428
429 return VECTOR2I( dx, dy );
430}
431
432
433static std::optional<int> findDirectionIndex( const std::vector<VECTOR2I>& aDirections,
434 const VECTOR2I& aDelta )
435{
436 VECTOR2I normalized = normalizeDirection( aDelta );
437
438 if( normalized.x == 0 && normalized.y == 0 )
439 return std::nullopt;
440
441 for( size_t i = 0; i < aDirections.size(); ++i )
442 {
443 if( aDirections[i] == normalized )
444 return static_cast<int>( i );
445 }
446
447 return std::nullopt;
448}
449
450
451void SNAP_LINE_MANAGER::SetDirections( const std::vector<VECTOR2I>& aDirections )
452{
453 std::vector<VECTOR2I> uniqueDirections;
454 uniqueDirections.reserve( aDirections.size() );
455
456 for( const VECTOR2I& direction : aDirections )
457 {
458 VECTOR2I normalized = normalizeDirection( direction );
459
460 if( normalized.x == 0 && normalized.y == 0 )
461 continue;
462
463 if( std::find( uniqueDirections.begin(), uniqueDirections.end(), normalized )
464 == uniqueDirections.end() )
465 {
466 uniqueDirections.push_back( normalized );
467 }
468 }
469
470 if( uniqueDirections != m_directions )
471 {
472 m_directions = std::move( uniqueDirections );
473 m_activeDirection.reset();
474
476 {
478 m_snapLineEnd.reset();
479 }
480
481 if( m_directions.empty() )
482 {
484 return;
485 }
486
488 }
489}
490
491
493{
494 if( m_snapLineOrigin && *m_snapLineOrigin == aOrigin && !m_snapLineEnd )
495 {
497 return;
498 }
499
500 m_snapLineOrigin = aOrigin;
501 m_snapLineEnd.reset();
502 m_activeDirection.reset();
503 m_viewHandler.GetViewItem().ClearSnapLine();
505}
506
507
509{
510 if( m_snapLineOrigin && aSnapEnd != m_snapLineEnd )
511 {
512 m_snapLineEnd = aSnapEnd;
513
514 if( m_snapLineEnd )
516 else
517 m_activeDirection.reset();
518
519 if( m_snapLineEnd )
520 m_viewHandler.GetViewItem().SetSnapLine( SEG{ *m_snapLineOrigin, *m_snapLineEnd } );
521 else
522 m_viewHandler.GetViewItem().ClearSnapLine();
523
525 }
526}
527
528
530{
531 m_snapLineOrigin.reset();
532 m_snapLineEnd.reset();
533 m_activeDirection.reset();
534 m_viewHandler.GetViewItem().ClearSnapLine();
536}
537
538
540{
541 if( m_snapLineOrigin.has_value() )
542 {
543 if( findDirectionIndex( m_directions, aAnchorPos - *m_snapLineOrigin ) )
544 {
545 SetSnapLineEnd( aAnchorPos );
546 }
547 else
548 {
549 // Snapped to something that is not the snap line origin, so
550 // this anchor is now the new snap line origin
551 SetSnapLineOrigin( aAnchorPos );
552 }
553 }
554 else
555 {
556 // If there's no snap line, start one
557 SetSnapLineOrigin( aAnchorPos );
558 }
559}
560
561
563 const VECTOR2I& aNearestGrid,
564 std::optional<int> aDistToNearest,
565 int aSnapRange,
566 const VECTOR2D& aGridSize,
567 const VECTOR2I& aGridOrigin ) const
568{
569 wxLogTrace( traceSnap, "GetNearestSnapLinePoint: cursor=(%d, %d), nearestGrid=(%d, %d), distToNearest=%s, snapRange=%d",
570 aCursor.x, aCursor.y, aNearestGrid.x, aNearestGrid.y,
571 aDistToNearest ? wxString::Format( "%d", *aDistToNearest ) : wxString( "none" ), aSnapRange );
572
573 if( !m_snapLineOrigin || m_directions.empty() )
574 {
575 wxLogTrace( traceSnap, " No snap line origin or no directions, returning nullopt" );
576 return std::nullopt;
577 }
578
579 const bool gridBetterThanNearest = !aDistToNearest || *aDistToNearest > aSnapRange;
580 const bool gridActive = aGridSize.x > 0 && aGridSize.y > 0;
581
582 wxLogTrace( traceSnap, " snapLineOrigin=(%d, %d), directions count=%zu, gridBetterThanNearest=%d, gridActive=%d",
583 m_snapLineOrigin->x, m_snapLineOrigin->y, m_directions.size(), gridBetterThanNearest, gridActive );
584
585 if( !gridBetterThanNearest )
586 {
587 wxLogTrace( traceSnap, " Grid not better than nearest, returning nullopt" );
588 return std::nullopt;
589 }
590
591 const int escapeRange = 2 * aSnapRange;
592 const EDA_ANGLE longRangeEscapeAngle( 4, DEGREES_T );
593
594 wxLogTrace( traceSnap, " escapeRange=%d, longRangeEscapeAngle=%.1f deg",
595 escapeRange, longRangeEscapeAngle.AsDegrees() );
596
597 const VECTOR2D origin( *m_snapLineOrigin );
598 const VECTOR2D cursor( aCursor );
599 const VECTOR2D delta = cursor - origin;
600
601 double bestPerpDistance = std::numeric_limits<double>::max();
602 std::optional<VECTOR2I> bestSnapPoint;
603
604 for( size_t ii = 0; ii < m_directions.size(); ++ii )
605 {
606 const VECTOR2I& direction = m_directions[ii];
607 VECTOR2D dirVector( direction );
608 double dirLength = dirVector.EuclideanNorm();
609
610 if( dirLength == 0.0 )
611 {
612 wxLogTrace( traceSnap, " Direction %zu: zero length, skipping", ii );
613 continue;
614 }
615
616 VECTOR2D dirUnit = dirVector / dirLength;
617
618 double distanceAlong = delta.Dot( dirUnit );
619 VECTOR2D projection = origin + dirUnit * distanceAlong;
620 VECTOR2D offset = delta - dirUnit * distanceAlong;
621 double perpDistance = offset.EuclideanNorm();
622
623 wxLogTrace( traceSnap, " Direction %zu: dir=(%d, %d), perpDist=%.1f, distAlong=%.1f",
624 ii, direction.x, direction.y, perpDistance, distanceAlong );
625
626 if( perpDistance > aSnapRange )
627 {
628 wxLogTrace( traceSnap, " perpDistance > snapRange, skipping" );
629 continue;
630 }
631
632 bool escaped = false;
633
634 if( perpDistance >= escapeRange )
635 {
636 EDA_ANGLE deltaAngle( delta );
637 EDA_ANGLE directionAngle( dirVector );
638 double angleDiff = ( deltaAngle - directionAngle ).Normalize180().AsDegrees();
639
640 wxLogTrace( traceSnap, " In escape range: deltaAngle=%.1f, dirAngle=%.1f, angleDiff=%.1f",
641 deltaAngle.AsDegrees(), directionAngle.AsDegrees(), angleDiff );
642
643 if( std::abs( angleDiff ) > longRangeEscapeAngle.AsDegrees() )
644 {
645 escaped = true;
646 wxLogTrace( traceSnap, " ESCAPED (angle diff too large)" );
647 }
648 }
649
650 if( escaped )
651 {
652 wxLogTrace( traceSnap, " Not updating (escaped)" );
653 continue;
654 }
655
656 // Now snap the projection to the grid if the grid is active
657 VECTOR2D snapPoint = projection;
658
659 if( gridActive )
660 {
661 // For horizontal/vertical lines, snap to grid intersections
662 if( direction.x == 0 && direction.y != 0 )
663 {
664 // Vertical line: keep origin X, snap Y to grid
665 snapPoint.x = origin.x;
666 snapPoint.y = aNearestGrid.y;
667 wxLogTrace( traceSnap, " Vertical line: snapping to grid Y, snapPoint=(%.1f, %.1f)",
668 snapPoint.x, snapPoint.y );
669 }
670 else if( direction.y == 0 && direction.x != 0 )
671 {
672 // Horizontal line: snap X to grid, keep origin Y
673 snapPoint.x = aNearestGrid.x;
674 snapPoint.y = origin.y;
675 wxLogTrace( traceSnap, " Horizontal line: snapping to grid X, snapPoint=(%.1f, %.1f)",
676 snapPoint.x, snapPoint.y );
677 }
678 else
679 {
680 // Diagonal line: find nearest grid intersection along the line
681 VECTOR2D gridOriginD( aGridOrigin );
682 VECTOR2D relProjection = projection - gridOriginD;
683
684 // Find nearby grid points (check 3x3 grid around projection)
685 double bestGridScore = std::numeric_limits<double>::max();
686 VECTOR2D bestGridPoint = projection;
687
688 for( int dx = -1; dx <= 1; ++dx )
689 {
690 for( int dy = -1; dy <= 1; ++dy )
691 {
692 double gridX = std::round( relProjection.x / aGridSize.x ) * aGridSize.x + dx * aGridSize.x;
693 double gridY = std::round( relProjection.y / aGridSize.y ) * aGridSize.y + dy * aGridSize.y;
694 VECTOR2D gridPt( gridX + gridOriginD.x, gridY + gridOriginD.y );
695
696 // Calculate perpendicular distance from grid point to construction line
697 VECTOR2D gridDelta = gridPt - origin;
698 double gridDistAlong = gridDelta.Dot( dirUnit );
699 VECTOR2D gridProjection = origin + dirUnit * gridDistAlong;
700 double gridPerpDist = ( gridPt - gridProjection ).EuclideanNorm();
701
702 // Also consider distance from cursor
703 double distFromCursor = ( gridPt - cursor ).EuclideanNorm();
704
705 // Prefer grid points that are close to the line and close to cursor
706 double score = gridPerpDist + distFromCursor * 0.1;
707
708 if( score < bestGridScore )
709 {
710 bestGridScore = score;
711 bestGridPoint = gridPt;
712 }
713 }
714 }
715
716 snapPoint = bestGridPoint;
717 wxLogTrace( traceSnap, " Diagonal line: snapping to grid intersection, snapPoint=(%.1f, %.1f)",
718 snapPoint.x, snapPoint.y );
719 }
720 }
721 else
722 {
723 wxLogTrace( traceSnap, " Grid not active, using projection" );
724 }
725
726 if( perpDistance < bestPerpDistance )
727 {
728 bestPerpDistance = perpDistance;
729 bestSnapPoint = KiROUND( snapPoint );
730 wxLogTrace( traceSnap, " NEW BEST: perpDist=%.1f, snapPoint=(%d, %d)",
731 bestPerpDistance, bestSnapPoint->x, bestSnapPoint->y );
732 }
733 else
734 {
735 wxLogTrace( traceSnap, " Not updating (perpDist=%.1f >= bestPerp=%.1f)",
736 perpDistance, bestPerpDistance );
737 }
738 }
739
740 if( bestSnapPoint )
741 {
742 wxLogTrace( traceSnap, " RETURNING bestSnapPoint=(%d, %d)", bestSnapPoint->x, bestSnapPoint->y );
743 return *bestSnapPoint;
744 }
745
746 wxLogTrace( traceSnap, " RETURNING nullopt (no valid snap found)" );
747 return std::nullopt;
748}
749
750
757
758
760{
761 if( m_updateCallback )
762 {
763 bool showAnything = m_constructionManager.HasActiveConstruction()
764 || m_snapLineManager.HasCompleteSnapLine()
766 || ( m_snapLineManager.GetSnapLineOrigin()
767 && !m_snapLineManager.GetDirections().empty() );
768
769 m_updateCallback( showAnything );
770 }
771}
772
773
774void SNAP_MANAGER::SetDimensionBrackets( std::vector<SEG> aBrackets )
775{
776 // Every resolve calls this, so an unchanged set must not force a canvas refresh.
777 if( aBrackets == GetViewItem().DimensionBrackets() )
778 return;
779
780 GetViewItem().SetDimensionBrackets( std::move( aBrackets ) );
781 updateView();
782}
783
784
786{
787 m_snapGuideColor = aBase;
788 m_snapGuideHighlightColor = aHighlight;
790}
791
792
794{
795 std::vector<KIGFX::CONSTRUCTION_GEOM::SNAP_GUIDE> guides;
796
797 const OPT_VECTOR2I& origin = m_snapLineManager.GetSnapLineOrigin();
798 const std::vector<VECTOR2I>& directions = m_snapLineManager.GetDirections();
799
800 if( origin && !directions.empty() )
801 {
802 const std::optional<int> activeDirection = m_snapLineManager.GetActiveDirection();
803 const int guideLength = 500000;
804
805 for( size_t ii = 0; ii < directions.size(); ++ii )
806 {
807 const VECTOR2I& direction = directions[ii];
808
809 if( direction.x == 0 && direction.y == 0 )
810 continue;
811
812 VECTOR2I scaled = direction * guideLength;
813
815 guide.Segment = SEG( *origin - scaled, *origin + scaled );
816
817 if( activeDirection && *activeDirection == static_cast<int>( ii ) )
818 {
819 guide.LineWidth = 5;
821 }
822 else
823 {
824 guide.LineWidth = 1;
825 guide.Color = m_snapGuideColor;
826 }
827
828 guides.push_back( guide );
829 }
830 }
831
832 GetViewItem().SetSnapGuides( std::move( guides ) );
833 updateView();
834}
835
836
838{
839 if( m_snapManager )
840 m_snapManager->UpdateSnapGuides();
841}
842
843
844std::vector<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH>
846{
847 std::vector<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH> batches;
848
849 m_constructionManager.GetConstructionItems( batches );
850
851 if( const OPT_VECTOR2I& snapLineOrigin = m_snapLineManager.GetSnapLineOrigin();
852 snapLineOrigin.has_value() )
853 {
855
857 batch.emplace_back( CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM{
859 nullptr,
860 {},
861 } );
862
863 const std::vector<VECTOR2I>& directions = m_snapLineManager.GetDirections();
864 const std::optional<int> activeDirection = m_snapLineManager.GetActiveDirection();
865
866 for( size_t ii = 0; ii < directions.size(); ++ii )
867 {
868 const VECTOR2I& direction = directions[ii];
869
870 VECTOR2I scaledDirection = direction * 100000;
871
873 entry.Drawable = LINE{ *snapLineOrigin, *snapLineOrigin + scaledDirection };
874 entry.LineWidth = ( activeDirection && *activeDirection == static_cast<int>( ii ) ) ? 2 : 1;
875
876 snapPointItem.Constructions.push_back( entry );
877 }
878
879 if( !snapPointItem.Constructions.empty() )
880 batches.push_back( std::move( batch ) );
881 }
882
883 return batches;
884}
885
886
888{
889 std::lock_guard<std::mutex> lock( m_batchesMutex );
890
893 m_involvedItems.clear();
895}
896
897
899{
900 m_snapLineManager.ClearSnapLine();
901 m_constructionManager.Clear();
904}
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)
void InspectPendingProposal(Func &&aFunc) const
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 GetPendingConstructionItems(std::vector< CONSTRUCTION_ITEM_BATCH > &aToExtend) const
Get the construction proposal awaiting activation.
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)
void SetDimensionBrackets(std::vector< SEG > aBrackets)
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)
void SetDimensionBrackets(std::vector< SEG > aBrackets)
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