KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_point_editor.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 (C) 2013-2021 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <[email protected]>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <functional>
23#include <memory>
24#include <algorithm>
25#include <limits>
26
27using namespace std::placeholders;
28#include <advanced_config.h>
29#include <kiplatform/ui.h>
30#include <view/view_controls.h>
34#include <geometry/seg.h>
36#include <math/util.h>
37#include <confirm.h>
38#include <tool/tool_manager.h>
42#include <tools/pcb_actions.h>
48#include <board_commit.h>
49#include <pcb_edit_frame.h>
50#include <pcb_reference_image.h>
51#include <pcb_generator.h>
52#include <pcb_group.h>
53#include <pcb_dimension.h>
54#include <pcb_barcode.h>
55#include <pcb_grid_item.h>
56#include <pcb_textbox.h>
57#include <pcb_tablecell.h>
58#include <pcb_table.h>
59#include <pad.h>
60#include <zone.h>
61#include <footprint.h>
62#include <board.h>
67#include <progress_reporter.h>
68#include <layer_ids.h>
70
71const unsigned int PCB_POINT_EDITOR::COORDS_PADDING = pcbIUScale.mmToIU( 20 );
72
73static void appendDirection( std::vector<VECTOR2I>& aDirections, const VECTOR2I& aDirection )
74{
75 if( aDirection.x != 0 || aDirection.y != 0 )
76 aDirections.push_back( aDirection );
77}
78
79static std::vector<VECTOR2I> getConstraintDirections( EDIT_RELATION* aRelation )
80{
81 std::vector<VECTOR2I> directions;
82
83 if( !aRelation )
84 return directions;
85
86 switch( aRelation->Kind() )
87 {
89 appendDirection( directions, VECTOR2I( 1, 0 ) );
90 appendDirection( directions, VECTOR2I( 0, 1 ) );
91 break;
92
94 appendDirection( directions, VECTOR2I( 1, 0 ) );
95 appendDirection( directions, VECTOR2I( 0, 1 ) );
96 appendDirection( directions, VECTOR2I( 1, 1 ) );
97 appendDirection( directions, VECTOR2I( 1, -1 ) );
98 break;
99
101 appendDirection( directions, VECTOR2I( 0, 1 ) );
102 break;
103
105 appendDirection( directions, VECTOR2I( 1, 0 ) );
106 break;
107
109 appendDirection( directions, aRelation->Direction() );
110 break;
111
112 default:
113 break;
114 }
115
116 return directions;
117}
118
119// Few constants to avoid using bare numbers for point indices
131
132
137
138
139static std::optional<CONSTRAINT_MEMBER> constraintMemberForEditPoint(
140 PCB_SHAPE* aShape, EDIT_POINTS* aPoints, EDIT_POINT* aEditedPoint )
141{
142 if( !aShape || !aPoints || !aEditedPoint )
143 return std::nullopt;
144
145 SHAPE_T type = aShape->GetShape();
146 VECTOR2I position = aEditedPoint->GetPosition();
147
148 if( type == SHAPE_T::SEGMENT || type == SHAPE_T::ARC || type == SHAPE_T::BEZIER )
149 {
150 if( position == aShape->GetStart() )
152
153 if( position == aShape->GetEnd() )
155
156 if( type == SHAPE_T::ARC && position == aShape->GetCenter() )
158 }
159 else if( type == SHAPE_T::CIRCLE && position == aShape->GetCenter() )
160 {
162 }
163 else if( type == SHAPE_T::RECTANGLE && aPoints->PointsSize() >= RECT_MAX_POINTS )
164 {
165 for( unsigned i = RECT_TOP_LEFT; i <= RECT_BOT_LEFT; ++i )
166 {
167 if( aEditedPoint == &aPoints->Point( i ) )
169 }
170
171 for( unsigned i = 0; i < aPoints->LinesSize() && i < 4; ++i )
172 {
173 if( aEditedPoint == &aPoints->Line( i ) )
175 }
176 }
177 else if( type == SHAPE_T::POLY && ConstraintPolygonIsModelable( aShape ) )
178 {
179 for( unsigned i = 0; i < aPoints->PointsSize(); ++i )
180 {
181 if( aEditedPoint == &aPoints->Point( i ) )
183 }
184
185 for( unsigned i = 0; i < aPoints->LinesSize(); ++i )
186 {
187 if( aEditedPoint == &aPoints->Line( i ) )
189 }
190 }
191
192 return std::nullopt;
193}
194
195
210
211
218
219
221{
222public:
223 RECT_RADIUS_TEXT_ITEM( const EDA_IU_SCALE& aIuScale, EDA_UNITS aUnits ) :
225 m_iuScale( aIuScale ),
226 m_units( aUnits ),
227 m_radius( 0 ),
228 m_corner(),
229 m_quadrant( -1, 1 ),
230 m_visible( false )
231 {
232 }
233
234 const BOX2I ViewBBox() const override
235 {
236 BOX2I tmp;
237 tmp.SetMaximum();
238 return tmp;
239 }
240
241 std::vector<int> ViewGetLayers() const override
242 {
244 }
245
246 void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override
247 {
248 if( !m_visible )
249 return;
250
251 wxArrayString strings;
252 strings.push_back( KIGFX::PREVIEW::DimensionLabel( "r", m_radius, m_iuScale, m_units ) );
254 aLayer == LAYER_SELECT_OVERLAY );
255 }
256
257 void Set( int aRadius, const VECTOR2I& aCorner, const VECTOR2I& aQuadrant, EDA_UNITS aUnits )
258 {
259 m_radius = aRadius;
260 m_corner = aCorner;
261 m_quadrant = aQuadrant;
262 m_units = aUnits;
263 m_visible = true;
264 }
265
266 void Hide()
267 {
268 m_visible = false;
269 }
270
271 wxString GetClass() const override
272 {
273 return wxT( "RECT_RADIUS_TEXT_ITEM" );
274 }
275
276private:
283};
284
285
287{
288public:
290 m_rectangle( aRectangle )
291 {
292 wxASSERT( m_rectangle.GetShape() == SHAPE_T::RECTANGLE );
293 }
294
299 static void MakePoints( const PCB_SHAPE& aRectangle, EDIT_POINTS& aPoints )
300 {
301 wxCHECK( aRectangle.GetShape() == SHAPE_T::RECTANGLE, /* void */ );
302
303 VECTOR2I topLeft = aRectangle.GetTopLeft();
304 VECTOR2I botRight = aRectangle.GetBotRight();
305
306 aPoints.SetSwapX( topLeft.x > botRight.x );
307 aPoints.SetSwapY( topLeft.y > botRight.y );
308
309 if( aPoints.SwapX() )
310 std::swap( topLeft.x, botRight.x );
311
312 if( aPoints.SwapY() )
313 std::swap( topLeft.y, botRight.y );
314
315 aPoints.AddPoint( topLeft );
316 aPoints.AddPoint( VECTOR2I( botRight.x, topLeft.y ) );
317 aPoints.AddPoint( botRight );
318 aPoints.AddPoint( VECTOR2I( topLeft.x, botRight.y ) );
319 aPoints.AddPoint( aRectangle.GetCenter() );
320 aPoints.AddPoint( VECTOR2I( botRight.x - aRectangle.GetCornerRadius(), topLeft.y ) );
321 aPoints.Point( RECT_RADIUS ).SetDrawCircle();
322
323 aPoints.AddLine( aPoints.Point( RECT_TOP_LEFT ), aPoints.Point( RECT_TOP_RIGHT ) );
324 aPoints.Line( RECT_TOP ).SetRelation(
326 aPoints.AddLine( aPoints.Point( RECT_TOP_RIGHT ), aPoints.Point( RECT_BOT_RIGHT ) );
327 aPoints.Line( RECT_RIGHT ).SetRelation(
329 aPoints.AddLine( aPoints.Point( RECT_BOT_RIGHT ), aPoints.Point( RECT_BOT_LEFT ) );
330 aPoints.Line( RECT_BOT ).SetRelation(
332 aPoints.AddLine( aPoints.Point( RECT_BOT_LEFT ), aPoints.Point( RECT_TOP_LEFT ) );
333 aPoints.Line( RECT_LEFT ).SetRelation(
335 }
336
337 static void UpdateItem( PCB_SHAPE& aRectangle, const EDIT_POINT& aEditedPoint,
338 EDIT_POINTS& aPoints, const VECTOR2I& aMinSize = { 0, 0 } )
339 {
340 // You can have more points if your item wants to have more points
341 // (this class assumes the rect points come first, but that can be changed)
343
344 auto setLeft =
345 [&]( int left )
346 {
347 aPoints.SwapX() ? aRectangle.SetRight( left ) : aRectangle.SetLeft( left );
348 };
349 auto setRight =
350 [&]( int right )
351 {
352 aPoints.SwapX() ? aRectangle.SetLeft( right ) : aRectangle.SetRight( right );
353 };
354 auto setTop =
355 [&]( int top )
356 {
357 aPoints.SwapY() ? aRectangle.SetBottom( top ) : aRectangle.SetTop( top );
358 };
359 auto setBottom =
360 [&]( int bottom )
361 {
362 aPoints.SwapY() ? aRectangle.SetTop( bottom ) : aRectangle.SetBottom( bottom );
363 };
364
365 VECTOR2I topLeft = aPoints.Point( RECT_TOP_LEFT ).GetPosition();
366 VECTOR2I topRight = aPoints.Point( RECT_TOP_RIGHT ).GetPosition();
367 VECTOR2I botLeft = aPoints.Point( RECT_BOT_LEFT ).GetPosition();
368 VECTOR2I botRight = aPoints.Point( RECT_BOT_RIGHT ).GetPosition();
369
370 PinEditedCorner( aEditedPoint, aPoints, topLeft, topRight, botLeft, botRight,
371 { 0, 0 }, { 0, 0 }, aMinSize );
372
373 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_LEFT ) )
374 || isModified( aEditedPoint, aPoints.Point( RECT_TOP_RIGHT ) )
375 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) )
376 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_LEFT ) ) )
377 {
378 setTop( topLeft.y );
379 setLeft( topLeft.x );
380 setRight( botRight.x );
381 setBottom( botRight.y );
382 }
383 else if( isModified( aEditedPoint, aPoints.Point( RECT_CENTER ) ) )
384 {
385 const VECTOR2I moveVector = aPoints.Point( RECT_CENTER ).GetPosition() - aRectangle.GetCenter();
386 aRectangle.Move( moveVector );
387 }
388 else if( isModified( aEditedPoint, aPoints.Point( RECT_RADIUS ) ) )
389 {
390 int width = std::abs( botRight.x - topLeft.x );
391 int height = std::abs( botRight.y - topLeft.y );
392 int maxRadius = std::min( width, height ) / 2;
393 int x = aPoints.Point( RECT_RADIUS ).GetX();
394 x = std::clamp( x, botRight.x - maxRadius, botRight.x );
395 aPoints.Point( RECT_RADIUS ).SetPosition( x, topLeft.y );
396 aRectangle.SetCornerRadius( botRight.x - x );
397 }
398 else if( isModified( aEditedPoint, aPoints.Line( RECT_TOP ) ) )
399 {
400 // Only top changes; keep others from previous full-local bbox
401 setTop( topLeft.y );
402 }
403 else if( isModified( aEditedPoint, aPoints.Line( RECT_LEFT ) ) )
404 {
405 // Only left changes; keep others from previous full-local bbox
406 setLeft( topLeft.x );
407 }
408 else if( isModified( aEditedPoint, aPoints.Line( RECT_BOT ) ) )
409 {
410 // Only bottom changes; keep others from previous full-local bbox
411 setBottom( botRight.y );
412 }
413 else if( isModified( aEditedPoint, aPoints.Line( RECT_RIGHT ) ) )
414 {
415 // Only right changes; keep others from previous full-local bbox
416 setRight( botRight.x );
417 }
418
419 for( unsigned i = 0; i < aPoints.LinesSize(); ++i )
420 {
421 if( !isModified( aEditedPoint, aPoints.Line( i ) ) )
422 aPoints.Line( i ).SetRelation(
424 }
425 }
426
427 static void UpdatePoints( const PCB_SHAPE& aRectangle, EDIT_POINTS& aPoints )
428 {
429 wxCHECK( aPoints.PointsSize() >= RECT_MAX_POINTS, /* void */ );
430
431 VECTOR2I topLeft = aRectangle.GetTopLeft();
432 VECTOR2I botRight = aRectangle.GetBotRight();
433
434 aPoints.SetSwapX( topLeft.x > botRight.x );
435 aPoints.SetSwapY( topLeft.y > botRight.y );
436
437 if( aPoints.SwapX() )
438 std::swap( topLeft.x, botRight.x );
439
440 if( aPoints.SwapY() )
441 std::swap( topLeft.y, botRight.y );
442
443 aPoints.Point( RECT_TOP_LEFT ).SetPosition( topLeft );
444 aPoints.Point( RECT_RADIUS ).SetPosition( botRight.x - aRectangle.GetCornerRadius(), topLeft.y );
445 aPoints.Point( RECT_TOP_RIGHT ).SetPosition( botRight.x, topLeft.y );
446 aPoints.Point( RECT_BOT_RIGHT ).SetPosition( botRight );
447 aPoints.Point( RECT_BOT_LEFT ).SetPosition( topLeft.x, botRight.y );
448 aPoints.Point( RECT_CENTER ).SetPosition( aRectangle.GetCenter() );
449 }
450
451 void MakePoints( EDIT_POINTS& aPoints ) override
452 {
453 // Just call the static helper
454 MakePoints( m_rectangle, aPoints );
455 }
456
457 bool UpdatePoints( EDIT_POINTS& aPoints ) override
458 {
459 // Careful; rectangle shape is mutable between cardinal and non-cardinal rotations...
460 if( m_rectangle.GetShape() != SHAPE_T::RECTANGLE || aPoints.PointsSize() == 0 )
461 return false;
462
463 UpdatePoints( m_rectangle, aPoints );
464 return true;
465 }
466
467 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
468 std::vector<EDA_ITEM*>& aUpdatedItems ) override
469 {
470 UpdateItem( m_rectangle, aEditedPoint, aPoints );
471 }
472
487 static void PinEditedCorner( const EDIT_POINT& aEditedPoint, const EDIT_POINTS& aEditPoints,
488 VECTOR2I& aTopLeft, VECTOR2I& aTopRight, VECTOR2I& aBotLeft, VECTOR2I& aBotRight,
489 const VECTOR2I& aHole = { 0, 0 }, const VECTOR2I& aHoleSize = { 0, 0 },
490 const VECTOR2I& aMinSize = { 0, 0 } )
491 {
492 int minWidth = std::max( EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 ), aMinSize.x );
493 int minHeight = std::max( EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 ), aMinSize.y );
494
495 if( isModified( aEditedPoint, aEditPoints.Point( RECT_TOP_LEFT ) ) )
496 {
497 if( aHoleSize.x )
498 {
499 // pin edited point to the top/left of the hole
500 aTopLeft.x = std::min( aTopLeft.x, aHole.x - aHoleSize.x / 2 - minWidth );
501 aTopLeft.y = std::min( aTopLeft.y, aHole.y - aHoleSize.y / 2 - minHeight );
502 }
503 else
504 {
505 // pin edited point within opposite corner
506 aTopLeft.x = std::min( aTopLeft.x, aBotRight.x - minWidth );
507 aTopLeft.y = std::min( aTopLeft.y, aBotRight.y - minHeight );
508 }
509
510 // push edited point edges to adjacent corners
511 aTopRight.y = aTopLeft.y;
512 aBotLeft.x = aTopLeft.x;
513 }
514 else if( isModified( aEditedPoint, aEditPoints.Point( RECT_TOP_RIGHT ) ) )
515 {
516 if( aHoleSize.x )
517 {
518 // pin edited point to the top/right of the hole
519 aTopRight.x = std::max( aTopRight.x, aHole.x + aHoleSize.x / 2 + minWidth );
520 aTopRight.y = std::min( aTopRight.y, aHole.y - aHoleSize.y / 2 - minHeight );
521 }
522 else
523 {
524 // pin edited point within opposite corner
525 aTopRight.x = std::max( aTopRight.x, aBotLeft.x + minWidth );
526 aTopRight.y = std::min( aTopRight.y, aBotLeft.y - minHeight );
527 }
528
529 // push edited point edges to adjacent corners
530 aTopLeft.y = aTopRight.y;
531 aBotRight.x = aTopRight.x;
532 }
533 else if( isModified( aEditedPoint, aEditPoints.Point( RECT_BOT_LEFT ) ) )
534 {
535 if( aHoleSize.x )
536 {
537 // pin edited point to the bottom/left of the hole
538 aBotLeft.x = std::min( aBotLeft.x, aHole.x - aHoleSize.x / 2 - minWidth );
539 aBotLeft.y = std::max( aBotLeft.y, aHole.y + aHoleSize.y / 2 + minHeight );
540 }
541 else
542 {
543 // pin edited point within opposite corner
544 aBotLeft.x = std::min( aBotLeft.x, aTopRight.x - minWidth );
545 aBotLeft.y = std::max( aBotLeft.y, aTopRight.y + minHeight );
546 }
547
548 // push edited point edges to adjacent corners
549 aBotRight.y = aBotLeft.y;
550 aTopLeft.x = aBotLeft.x;
551 }
552 else if( isModified( aEditedPoint, aEditPoints.Point( RECT_BOT_RIGHT ) ) )
553 {
554 if( aHoleSize.x )
555 {
556 // pin edited point to the bottom/right of the hole
557 aBotRight.x = std::max( aBotRight.x, aHole.x + aHoleSize.x / 2 + minWidth );
558 aBotRight.y = std::max( aBotRight.y, aHole.y + aHoleSize.y / 2 + minHeight );
559 }
560 else
561 {
562 // pin edited point within opposite corner
563 aBotRight.x = std::max( aBotRight.x, aTopLeft.x + minWidth );
564 aBotRight.y = std::max( aBotRight.y, aTopLeft.y + minHeight );
565 }
566
567 // push edited point edges to adjacent corners
568 aBotLeft.y = aBotRight.y;
569 aTopRight.x = aBotRight.x;
570 }
571 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_TOP ) ) )
572 {
573 aTopLeft.y = std::min( aTopLeft.y, aBotRight.y - minHeight );
574 }
575 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_LEFT ) ) )
576 {
577 aTopLeft.x = std::min( aTopLeft.x, aBotRight.x - minWidth );
578 }
579 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_BOT ) ) )
580 {
581 aBotRight.y = std::max( aBotRight.y, aTopLeft.y + minHeight );
582 }
583 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_RIGHT ) ) )
584 {
585 aBotRight.x = std::max( aBotRight.x, aTopLeft.x + minWidth );
586 }
587 }
588
589private:
591};
592
593
595{
596public:
598 POLYGON_POINT_EDIT_BEHAVIOR( *aZone.Outline() ),
599 m_zone( aZone )
600 {}
601
602 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
603 std::vector<EDA_ITEM*>& aUpdatedItems ) override
604 {
605 m_zone.UnFill();
606
607 // Defer to the base class to update the polygon
608 POLYGON_POINT_EDIT_BEHAVIOR::UpdateItem( aEditedPoint, aPoints, aCommit, aUpdatedItems );
609
610 m_zone.HatchBorder();
611 }
612
613private:
615};
616
617
619{
620public:
622 POLYGON_POINT_EDIT_BEHAVIOR( aGen.Outline() ),
623 m_gen( aGen )
624 {
625 }
626
627 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
628 std::vector<EDA_ITEM*>& aUpdatedItems ) override
629 {
630 // Defer to the base class to update the polygon
631 POLYGON_POINT_EDIT_BEHAVIOR::UpdateItem( aEditedPoint, aPoints, aCommit, aUpdatedItems );
632
633 // Flag the generator has to be updated based on potential outline change
634 m_gen.MarkDirty();
635 }
636
637private:
639};
640
641
643{
644public:
646 m_barcode( aBarcode )
647 {}
648
650 {
652 dummy.SetStart( m_barcode.GetCenter() - VECTOR2I( m_barcode.GetWidth() / 2, m_barcode.GetHeight() / 2 ) );
653 dummy.SetEnd( dummy.GetStart() + VECTOR2I( m_barcode.GetWidth(), m_barcode.GetHeight() ) );
654 dummy.Rotate( m_barcode.GetPosition(), m_barcode.GetAngle() );
655 return dummy;
656 }
657
658 void MakePoints( EDIT_POINTS& aPoints ) override
659 {
660 if( !m_barcode.GetAngle().IsCardinal() )
661 {
662 // Non-cardinal barcode point-editing isn't useful enough to support.
663 return;
664 }
665
666 auto set45Constraint =
667 [&]( int a, int b )
668 {
669 aPoints.Point( a ).SetRelation( EDIT_RELATION::Angle45( aPoints.Point( b ) ) );
670 };
671
673
674 if( m_barcode.KeepSquare() )
675 {
676 set45Constraint( RECT_TOP_LEFT, RECT_BOT_RIGHT );
677 set45Constraint( RECT_TOP_RIGHT, RECT_BOT_LEFT );
678 set45Constraint( RECT_BOT_RIGHT, RECT_TOP_LEFT );
679 set45Constraint( RECT_BOT_LEFT, RECT_TOP_RIGHT );
680 }
681 }
682
683 bool UpdatePoints( EDIT_POINTS& aPoints ) override
684 {
685 const unsigned target = m_barcode.GetAngle().IsCardinal() ? RECT_MAX_POINTS : 0;
686
687 if( aPoints.PointsSize() != target )
688 return false;
689
691 return true;
692 }
693
694 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
695 std::vector<EDA_ITEM*>& aUpdatedItems ) override
696 {
697 if( m_barcode.GetAngle().IsCardinal() )
698 {
700 RECTANGLE_POINT_EDIT_BEHAVIOR::UpdateItem( dummy, aEditedPoint, aPoints );
701 dummy.Rotate( dummy.GetCenter(), -m_barcode.GetAngle() );
702
703 m_barcode.SetPosition( dummy.GetCenter() );
704 m_barcode.SetWidth( dummy.GetRectangleWidth() );
705 m_barcode.SetHeight( dummy.GetRectangleHeight() );
706 m_barcode.AssembleBarcode();
707 }
708 }
709
710private:
712};
713
714
716{
717public:
719 m_gridItem( aGridItem )
720 {
721 }
722
723 void MakePoints( EDIT_POINTS& aPoints ) override
724 {
725 // Index 0: centre (drag = translate).
726 aPoints.AddPoint( m_gridItem.GetPosition() );
727
728 switch( m_gridItem.GetGridItemType() )
729 {
731 aPoints.AddPoint( toWorld( radiusEndLocal() ) );
732 aPoints.AddPoint( toWorld( arcEndLocal() ) );
733 aPoints.AddPoint( toWorld( arcMidLocal() ) );
734 break;
735
737 aPoints.AddPoint( toWorld( cornerLocal( 0, 0 ) ) );
738 aPoints.AddPoint( toWorld( cornerLocal( 1, 0 ) ) );
739 aPoints.AddPoint( toWorld( cornerLocal( 1, 1 ) ) );
740 aPoints.AddPoint( toWorld( cornerLocal( 0, 1 ) ) );
741 break;
742
743 default:
744 wxFAIL_MSG( wxT( "MakePoints: unhandled PCB_GRID_TYPE" ) );
745 break;
746 }
747 }
748
749 bool UpdatePoints( EDIT_POINTS& aPoints ) override
750 {
751 switch( m_gridItem.GetGridItemType() )
752 {
754 if( aPoints.PointsSize() != 4 )
755 return false;
756
757 aPoints.Point( 0 ).SetPosition( m_gridItem.GetPosition() );
758 aPoints.Point( 1 ).SetPosition( toWorld( radiusEndLocal() ) );
759 aPoints.Point( 2 ).SetPosition( toWorld( arcEndLocal() ) );
760 aPoints.Point( 3 ).SetPosition( toWorld( arcMidLocal() ) );
761 return true;
762
764 if( aPoints.PointsSize() != 5 )
765 return false;
766
767 aPoints.Point( 0 ).SetPosition( m_gridItem.GetPosition() );
768 aPoints.Point( 1 ).SetPosition( toWorld( cornerLocal( 0, 0 ) ) );
769 aPoints.Point( 2 ).SetPosition( toWorld( cornerLocal( 1, 0 ) ) );
770 aPoints.Point( 3 ).SetPosition( toWorld( cornerLocal( 1, 1 ) ) );
771 aPoints.Point( 4 ).SetPosition( toWorld( cornerLocal( 0, 1 ) ) );
772 return true;
773
774 default:
775 wxFAIL_MSG( wxT( "UpdatePoints: unhandled PCB_GRID_TYPE" ) );
776 return false;
777 }
778 }
779
780 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
781 std::vector<EDA_ITEM*>& aUpdatedItems ) override
782 {
783 // Centre handle: translate the whole grid.
784 if( isModified( aEditedPoint, aPoints.Point( 0 ) ) )
785 {
786 m_gridItem.SetPosition( aEditedPoint.GetPosition() );
787 return;
788 }
789
790 const VECTOR2I local = toLocal( aEditedPoint.GetPosition() );
791
792 switch( m_gridItem.GetGridItemType() )
793 {
795 {
796 // World offset from grid centre.
797 const VECTOR2I worldOff = aEditedPoint.GetPosition() - m_gridItem.GetPosition();
798 const double dragAngle = std::atan2( worldOff.y, worldOff.x );
799 const int newRadius = std::max( 1, KiROUND( std::hypot( worldOff.x, worldOff.y ) ) );
800 const double orientOld = m_gridItem.GetOrientation().AsRadians();
801 const double phiMaxOld = m_gridItem.GetPhiExtent().AsRadians();
802
803 // KiCad screen rotation: a local point at phi lands at world math-angle
804 // (phi - orient). Each handle drag pins exactly one endpoint and follows
805 // the (already-snapped) cursor; the unmoved endpoint stays put.
806 const auto wrap2pi = []( double a )
807 {
808 while( a < 0 )
809 a += 2 * M_PI;
810 while( a >= 2 * M_PI )
811 a -= 2 * M_PI;
812 return a;
813 };
814
815 if( isModified( aEditedPoint, aPoints.Point( 1 ) ) ) // radius end (phi=0)
816 {
817 // phi=0 follows the cursor; the phi=phiMax end stays at world angle
818 // (phiMaxOld - orientOld).
819 const double newOrient = -dragAngle;
820 const double newPhiMax = wrap2pi( phiMaxOld - orientOld - dragAngle );
821
822 m_gridItem.SetOrientation( EDA_ANGLE( newOrient, RADIANS_T ) );
823 m_gridItem.SetPhiExtentDegrees( newPhiMax * 180.0 / M_PI );
824 }
825 else if( isModified( aEditedPoint, aPoints.Point( 2 ) ) ) // arc end (phi=phiMax)
826 {
827 // phi=phiMax follows the cursor; the phi=0 end (orient direction) stays.
828 const double newPhiMax = wrap2pi( dragAngle + orientOld );
829
830 m_gridItem.SetPhiExtentDegrees( newPhiMax * 180.0 / M_PI );
831 }
832 else if( isModified( aEditedPoint, aPoints.Point( 3 ) ) ) // arc mid (bisector)
833 {
834 // Bisector follows the cursor; phiMax unchanged (whole wedge rotates).
835 const double newOrient = phiMaxOld / 2.0 - dragAngle;
836
837 m_gridItem.SetOrientation( EDA_ANGLE( newOrient, RADIANS_T ) );
838 }
839
840 m_gridItem.SetRadiusExtent( newRadius );
841 break;
842 }
843
845 {
846 // Dragging a corner resizes symmetrically about the centre.
847 const int newHalfX = std::max( 1, std::abs( local.x ) );
848 const int newHalfY = std::max( 1, std::abs( local.y ) );
849
850 // SetExtent takes size/2 directly, bypassing the property panel's
851 // total-width 2x / /2 accessor dance.
852 m_gridItem.SetExtent( VECTOR2I( newHalfX, newHalfY ) );
853 break;
854 }
855
856 default:
857 wxFAIL_MSG( wxT( "UpdateItem: unhandled PCB_GRID_TYPE" ) );
858 break;
859 }
860 }
861
862private:
863 VECTOR2I toLocal( const VECTOR2I& aWorld ) const
864 {
865 VECTOR2I local = aWorld - m_gridItem.GetPosition();
866 RotatePoint( local, -m_gridItem.GetOrientation() );
867 return local;
868 }
869
870 VECTOR2I toWorld( const VECTOR2I& aLocal ) const
871 {
872 VECTOR2I world = aLocal;
873 RotatePoint( world, m_gridItem.GetOrientation() );
874 return m_gridItem.GetPosition() + world;
875 }
876
877 // aX / aY in {0, 1}: 0 = negative half, 1 = positive half.
878 VECTOR2I cornerLocal( int aX, int aY ) const
879 {
880 const int sx = ( aX == 0 ) ? -1 : +1;
881 const int sy = ( aY == 0 ) ? -1 : +1;
882 return VECTOR2I( sx * m_gridItem.GetExtent().x, sy * m_gridItem.GetExtent().y );
883 }
884
885 VECTOR2I radiusEndLocal() const { return VECTOR2I( m_gridItem.GetRadiusExtent(), 0 ); }
886
888 {
889 const double phi = m_gridItem.GetPhiExtent().AsRadians();
890 const int r = m_gridItem.GetRadiusExtent();
891 return VECTOR2I( KiROUND( r * std::cos( phi ) ), KiROUND( r * std::sin( phi ) ) );
892 }
893
895 {
896 const double phi = m_gridItem.GetPhiExtent().AsRadians() / 2.0;
897 const int r = m_gridItem.GetRadiusExtent();
898 return VECTOR2I( KiROUND( r * std::cos( phi ) ), KiROUND( r * std::sin( phi ) ) );
899 }
900
902};
903
904
906{
907public:
912
913 void MakePoints( EDIT_POINTS& aPoints ) override
914 {
915 aPoints.AddPoint( edgeMidpoint( COL_WIDTH ) );
916 aPoints.AddPoint( edgeMidpoint( ROW_HEIGHT ) );
917 }
918
919 bool UpdatePoints( EDIT_POINTS& aPoints ) override
920 {
921 wxCHECK( aPoints.PointsSize() == TABLECELL_MAX_POINTS, false );
922
925
926 return true;
927 }
928
929 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
930 std::vector<EDA_ITEM*>& aUpdatedItems ) override
931 {
933
934 PCB_TABLE& table = static_cast<PCB_TABLE&>( *m_cell.GetParent() );
935 aCommit.Modify( &table );
936 aUpdatedItems.push_back( &table );
937
938 if( isModified( aEditedPoint, aPoints.Point( COL_WIDTH ) ) )
939 {
940 m_cell.SetEnd( VECTOR2I( unturned( aPoints.Point( COL_WIDTH ).GetPosition() ).x, m_cell.GetEndY() ) );
941
942 int colWidth = m_cell.GetRectangleWidth();
943
944 for( int ii = 0; ii < m_cell.GetColSpan() - 1; ++ii )
945 colWidth -= table.GetColWidth( m_cell.GetColumn() + ii );
946
947 table.SetColWidth( m_cell.GetColumn() + m_cell.GetColSpan() - 1, colWidth );
948 }
949 else if( isModified( aEditedPoint, aPoints.Point( ROW_HEIGHT ) ) )
950 {
951 m_cell.SetEnd( VECTOR2I( m_cell.GetEndX(), unturned( aPoints.Point( ROW_HEIGHT ).GetPosition() ).y ) );
952
953 int rowHeight = m_cell.GetRectangleHeight();
954
955 for( int ii = 0; ii < m_cell.GetRowSpan() - 1; ++ii )
956 rowHeight -= table.GetRowHeight( m_cell.GetRow() + ii );
957
958 table.SetRowHeight( m_cell.GetRow() + m_cell.GetRowSpan() - 1, rowHeight );
959 }
960
961 table.Normalize();
962 }
963
964private:
965 // A cell keeps its rectangle square to the board and carries the turn in its text angle, so
966 // the handles belong on the edges it is drawn with, not on the stored rectangle.
968 {
969 std::vector<VECTOR2I> corners = m_cell.GetCorners();
970
971 return aPoint == COL_WIDTH ? ( corners[1] + corners[2] ) / 2 : ( corners[2] + corners[3] ) / 2;
972 }
973
974 // Bring a dragged handle back into the frame the rectangle is stored in.
975 VECTOR2I unturned( const VECTOR2I& aPoint ) const
976 {
977 BOX2I box;
978 box.Merge( m_cell.GetStart() );
979 box.Merge( m_cell.GetEnd() );
980
981 VECTOR2I pt = aPoint;
982 RotatePoint( pt, box.GetCenter(), -m_cell.GetDrawRotation() );
983
984 return pt;
985 }
986
988};
989
990
992{
993public:
995 m_pad( aPad ),
996 m_layer( aLayer )
997 {}
998
999 void MakePoints( EDIT_POINTS& aPoints ) override
1000 {
1001 VECTOR2I shapePos = m_pad.ShapePos( m_layer );
1002 VECTOR2I halfSize( m_pad.GetSize( m_layer ).x / 2, m_pad.GetSize( m_layer ).y / 2 );
1003
1004 if( m_pad.IsLocked() )
1005 return;
1006
1007 switch( m_pad.GetShape( m_layer ) )
1008 {
1009 case PAD_SHAPE::CIRCLE:
1010 aPoints.AddPoint( VECTOR2I( shapePos.x + halfSize.x, shapePos.y ) );
1011 break;
1012
1013 case PAD_SHAPE::OVAL:
1018 {
1019 if( !m_pad.GetOrientation().IsCardinal() )
1020 break;
1021
1022 if( m_pad.GetOrientation().IsVertical() )
1023 std::swap( halfSize.x, halfSize.y );
1024
1025 // It's important to fill these according to the RECT indices
1026 aPoints.AddPoint( shapePos - halfSize );
1027 aPoints.AddPoint( VECTOR2I( shapePos.x + halfSize.x, shapePos.y - halfSize.y ) );
1028 aPoints.AddPoint( shapePos + halfSize );
1029 aPoints.AddPoint( VECTOR2I( shapePos.x - halfSize.x, shapePos.y + halfSize.y ) );
1030 }
1031 break;
1032
1033 default: // suppress warnings
1034 break;
1035 }
1036 }
1037
1038 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1039 {
1040 bool locked = m_pad.GetParent() && m_pad.IsLocked();
1041 VECTOR2I shapePos = m_pad.ShapePos( m_layer );
1042 VECTOR2I halfSize( m_pad.GetSize( m_layer ).x / 2, m_pad.GetSize( m_layer ).y / 2 );
1043
1044 switch( m_pad.GetShape( m_layer ) )
1045 {
1046 case PAD_SHAPE::CIRCLE:
1047 {
1048 int target = locked ? 0 : 1;
1049
1050 // Careful; pad shape is mutable...
1051 if( int( aPoints.PointsSize() ) != target )
1052 {
1053 aPoints.Clear();
1054 MakePoints( aPoints );
1055 }
1056 else if( target == 1 )
1057 {
1058 shapePos.x += halfSize.x;
1059 aPoints.Point( 0 ).SetPosition( shapePos );
1060 }
1061 }
1062 break;
1063
1064 case PAD_SHAPE::OVAL:
1069 {
1070 // Careful; pad shape and orientation are mutable...
1071 int target = locked || !m_pad.GetOrientation().IsCardinal() ? 0 : 4;
1072
1073 if( int( aPoints.PointsSize() ) != target )
1074 {
1075 aPoints.Clear();
1076 MakePoints( aPoints );
1077 }
1078 else if( target == 4 )
1079 {
1080 if( m_pad.GetOrientation().IsVertical() )
1081 std::swap( halfSize.x, halfSize.y );
1082
1083 aPoints.Point( RECT_TOP_LEFT ).SetPosition( shapePos - halfSize );
1084 aPoints.Point( RECT_TOP_RIGHT ).SetPosition( VECTOR2I( shapePos.x + halfSize.x,
1085 shapePos.y - halfSize.y ) );
1086 aPoints.Point( RECT_BOT_RIGHT ).SetPosition( shapePos + halfSize );
1087 aPoints.Point( RECT_BOT_LEFT ).SetPosition( VECTOR2I( shapePos.x - halfSize.x,
1088 shapePos.y + halfSize.y ) );
1089 }
1090
1091 break;
1092 }
1093
1094 default: // suppress warnings
1095 break;
1096 }
1097
1098 return true;
1099 }
1100
1101 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1102 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1103 {
1104 switch( m_pad.GetShape( m_layer ) )
1105 {
1106 case PAD_SHAPE::CIRCLE:
1107 {
1108 VECTOR2I end = aPoints.Point( 0 ).GetPosition();
1109 int diameter = 2 * ( end - m_pad.GetPosition() ).EuclideanNorm();
1110
1111 m_pad.SetSize( m_layer, VECTOR2I( diameter, diameter ) );
1112 break;
1113 }
1114
1115 case PAD_SHAPE::OVAL:
1120 {
1121 VECTOR2I topLeft = aPoints.Point( RECT_TOP_LEFT ).GetPosition();
1122 VECTOR2I topRight = aPoints.Point( RECT_TOP_RIGHT ).GetPosition();
1123 VECTOR2I botLeft = aPoints.Point( RECT_BOT_LEFT ).GetPosition();
1124 VECTOR2I botRight = aPoints.Point( RECT_BOT_RIGHT ).GetPosition();
1125 VECTOR2I holeCenter = m_pad.GetPosition();
1126 VECTOR2I holeSize = m_pad.GetDrillSize();
1127
1128 RECTANGLE_POINT_EDIT_BEHAVIOR::PinEditedCorner( aEditedPoint, aPoints, topLeft, topRight,
1129 botLeft, botRight, holeCenter, holeSize );
1130
1131 if( ( m_pad.GetOffset( m_layer ).x || m_pad.GetOffset( m_layer ).y )
1132 || ( m_pad.GetDrillSize().x && m_pad.GetDrillSize().y ) )
1133 {
1134 // Keep hole pinned at the current location; adjust the pad around the hole
1135
1136 VECTOR2I center = m_pad.GetPosition();
1137 int dist[4];
1138
1139 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_LEFT ) )
1140 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) ) )
1141 {
1142 dist[0] = center.x - topLeft.x;
1143 dist[1] = center.y - topLeft.y;
1144 dist[2] = botRight.x - center.x;
1145 dist[3] = botRight.y - center.y;
1146 }
1147 else
1148 {
1149 dist[0] = center.x - botLeft.x;
1150 dist[1] = center.y - topRight.y;
1151 dist[2] = topRight.x - center.x;
1152 dist[3] = botLeft.y - center.y;
1153 }
1154
1155 VECTOR2I padSize( dist[0] + dist[2], dist[1] + dist[3] );
1156 VECTOR2I deltaOffset( padSize.x / 2 - dist[2], padSize.y / 2 - dist[3] );
1157
1158 if( m_pad.GetOrientation().IsVertical() )
1159 std::swap( padSize.x, padSize.y );
1160
1161 RotatePoint( deltaOffset, -m_pad.GetOrientation() );
1162
1163 m_pad.SetSize( m_layer, padSize );
1164 m_pad.SetOffset( m_layer, -deltaOffset );
1165 }
1166 else
1167 {
1168 // Keep pad position at the center of the pad shape
1169
1170 int left, top, right, bottom;
1171
1172 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_LEFT ) )
1173 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) ) )
1174 {
1175 left = topLeft.x;
1176 top = topLeft.y;
1177 right = botRight.x;
1178 bottom = botRight.y;
1179 }
1180 else
1181 {
1182 left = botLeft.x;
1183 top = topRight.y;
1184 right = topRight.x;
1185 bottom = botLeft.y;
1186 }
1187
1188 VECTOR2I padSize( abs( right - left ), abs( bottom - top ) );
1189
1190 if( m_pad.GetOrientation().IsVertical() )
1191 std::swap( padSize.x, padSize.y );
1192
1193 m_pad.SetSize( m_layer, padSize );
1194 m_pad.SetPosition( VECTOR2I( ( left + right ) / 2, ( top + bottom ) / 2 ) );
1195 }
1196 break;
1197 }
1198 default: // suppress warnings
1199 break;
1200 }
1201 }
1202
1203private:
1206};
1207
1208
1215{
1216public:
1218 m_generator( aGenerator )
1219 {}
1220
1221 void MakePoints( EDIT_POINTS& aPoints ) override
1222 {
1223 m_generator.MakeEditPoints( aPoints );
1224 }
1225
1226 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1227 {
1228 m_generator.UpdateEditPoints( aPoints );
1229 return true;
1230 }
1231
1232 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1233 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1234 {
1235 m_generator.UpdateFromEditPoints( aPoints );
1236 }
1237
1238private:
1240};
1241
1242
1252{
1253public:
1255 m_dimension( aDimension ),
1256 m_originalTextPos( aDimension.GetTextPos() ),
1257 m_oldCrossBar( SEG{ aDimension.GetCrossbarStart(), aDimension.GetCrossbarEnd() } )
1258 {}
1259
1261 {
1262 const SEG newCrossBar{ m_dimension.GetCrossbarStart(), m_dimension.GetCrossbarEnd() };
1263
1264 if( newCrossBar == m_oldCrossBar )
1265 {
1266 // Crossbar didn't change, text doesn't need to change
1267 return;
1268 }
1269
1270 const VECTOR2I newTextPos = getDimensionNewTextPosition();
1271 m_dimension.SetTextPos( newTextPos );
1272
1273 const GR_TEXT_H_ALIGN_T oldJustify = m_dimension.GetHorizJustify();
1274
1275 // We may need to update the justification if we go past vertical.
1278 {
1279 const VECTOR2I oldProject = m_oldCrossBar.LineProject( m_originalTextPos );
1280 const VECTOR2I newProject = newCrossBar.LineProject( newTextPos );
1281
1282 const VECTOR2I oldProjectedOffset =
1283 oldProject - m_oldCrossBar.NearestPoint( oldProject );
1284 const VECTOR2I newProjectedOffset = newProject - newCrossBar.NearestPoint( newProject );
1285
1286 const bool textWasLeftOf = oldProjectedOffset.x < 0
1287 || ( oldProjectedOffset.x == 0 && oldProjectedOffset.y > 0 );
1288 const bool textIsLeftOf = newProjectedOffset.x < 0
1289 || ( newProjectedOffset.x == 0 && newProjectedOffset.y > 0 );
1290
1291 if( textWasLeftOf != textIsLeftOf )
1292 {
1293 // Flip whatever the user had set
1294 m_dimension.SetHorizJustify( ( oldJustify == GR_TEXT_H_ALIGN_T::GR_TEXT_H_ALIGN_LEFT )
1297 }
1298 }
1299
1300 // Update the dimension (again) to ensure the text knockouts are correct
1301 m_dimension.Update();
1302 }
1303
1304private:
1306 {
1307 const SEG newCrossBar{ m_dimension.GetCrossbarStart(), m_dimension.GetCrossbarEnd() };
1308
1309 const EDA_ANGLE oldAngle = EDA_ANGLE( m_oldCrossBar.B - m_oldCrossBar.A );
1310 const EDA_ANGLE newAngle = EDA_ANGLE( newCrossBar.B - newCrossBar.A );
1311 const EDA_ANGLE rotation = oldAngle - newAngle;
1312
1313 // There are two modes - when the text is between the crossbar points, and when it's not.
1315 {
1317 const VECTOR2I rotTextOffsetFromCbCenter = GetRotated( m_originalTextPos - m_oldCrossBar.Center(),
1318 rotation );
1319 const VECTOR2I rotTextOffsetFromCbEnd = GetRotated( m_originalTextPos - cbNearestEndToText, rotation );
1320
1321 // Which of the two crossbar points is now in the right direction? They could be swapped over now.
1322 // If zero-length, doesn't matter, they're the same thing
1323 const bool startIsInOffsetDirection = KIGEOM::PointIsInDirection( m_dimension.GetCrossbarStart(),
1324 rotTextOffsetFromCbCenter,
1325 newCrossBar.Center() );
1326
1327 const VECTOR2I& newCbRefPt = startIsInOffsetDirection ? m_dimension.GetCrossbarStart()
1328 : m_dimension.GetCrossbarEnd();
1329
1330 // Apply the new offset to the correct crossbar point
1331 return newCbRefPt + rotTextOffsetFromCbEnd;
1332 }
1333
1334 // If the text was between the crossbar points, it should stay there, but we need to find a
1335 // good place for it. Keep it the same distance from the crossbar line, but rotated as needed.
1336
1337 const VECTOR2I origTextPointProjected = m_oldCrossBar.NearestPoint( m_originalTextPos );
1338 const double oldRatio = KIGEOM::GetLengthRatioFromStart( origTextPointProjected, m_oldCrossBar );
1339
1340 // Perpendicular from the crossbar line to the text position
1341 // We need to keep this length constant
1342 const VECTOR2I rotCbNormalToText = GetRotated( m_originalTextPos - origTextPointProjected, rotation );
1343
1344 const VECTOR2I newProjected = newCrossBar.A + ( newCrossBar.B - newCrossBar.A ) * oldRatio;
1345 return newProjected + rotCbNormalToText;
1346 }
1347
1351};
1352
1353
1358{
1359public:
1361 m_dimension( aDimension )
1362 {}
1363
1364 void MakePoints( EDIT_POINTS& aPoints ) override
1365 {
1366 aPoints.AddPoint( m_dimension.GetStart() );
1367 aPoints.AddPoint( m_dimension.GetEnd() );
1368 aPoints.AddPoint( m_dimension.GetTextPos() );
1369 aPoints.AddPoint( m_dimension.GetCrossbarStart() );
1370 aPoints.AddPoint( m_dimension.GetCrossbarEnd() );
1371
1374
1375 if( m_dimension.Type() == PCB_DIM_ALIGNED_T )
1376 {
1377 // Dimension height setting - edit points should move only along the feature lines
1378 aPoints.Point( DIM_CROSSBARSTART )
1380 aPoints.Point( DIM_CROSSBARSTART ), aPoints.Point( DIM_START ) ) );
1381 aPoints.Point( DIM_CROSSBAREND )
1383 aPoints.Point( DIM_CROSSBAREND ), aPoints.Point( DIM_END ) ) );
1384 }
1385 }
1386
1387 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1388 {
1389 wxCHECK( aPoints.PointsSize() == DIM_ALIGNED_MAX, false );
1390
1391 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1392 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1393 aPoints.Point( DIM_TEXT ).SetPosition( m_dimension.GetTextPos() );
1394 aPoints.Point( DIM_CROSSBARSTART ).SetPosition( m_dimension.GetCrossbarStart() );
1395 aPoints.Point( DIM_CROSSBAREND ).SetPosition( m_dimension.GetCrossbarEnd() );
1396 return true;
1397 }
1398
1399 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1400 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1401 {
1403
1404 if( m_dimension.Type() == PCB_DIM_ALIGNED_T )
1405 updateAlignedDimension( aEditedPoint, aPoints );
1406 else
1407 updateOrthogonalDimension( aEditedPoint, aPoints );
1408 }
1409
1410 OPT_VECTOR2I Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const override
1411 {
1412 // Constraint for crossbar
1413 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1414 return aPoints.Point( DIM_END ).GetPosition();
1415
1416 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1417 return aPoints.Point( DIM_START ).GetPosition();
1418
1419 // No constraint
1420 return aEditedPoint.GetPosition();
1421 }
1422
1423private:
1427 void updateAlignedDimension( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints )
1428 {
1429 DIM_ALIGNED_TEXT_UPDATER textPositionUpdater( m_dimension );
1430
1431 // Check which point is currently modified and updated dimension's points respectively
1432 if( isModified( aEditedPoint, aPoints.Point( DIM_CROSSBARSTART ) ) )
1433 {
1434 VECTOR2D featureLine( aEditedPoint.GetPosition() - m_dimension.GetStart() );
1435 VECTOR2D crossBar( m_dimension.GetEnd() - m_dimension.GetStart() );
1436
1437 if( featureLine.Cross( crossBar ) > 0 )
1438 m_dimension.SetHeight( -featureLine.EuclideanNorm() );
1439 else
1440 m_dimension.SetHeight( featureLine.EuclideanNorm() );
1441
1442 m_dimension.Update();
1443 }
1444 else if( isModified( aEditedPoint, aPoints.Point( DIM_CROSSBAREND ) ) )
1445 {
1446 VECTOR2D featureLine( aEditedPoint.GetPosition() - m_dimension.GetEnd() );
1447 VECTOR2D crossBar( m_dimension.GetEnd() - m_dimension.GetStart() );
1448
1449 if( featureLine.Cross( crossBar ) > 0 )
1450 m_dimension.SetHeight( -featureLine.EuclideanNorm() );
1451 else
1452 m_dimension.SetHeight( featureLine.EuclideanNorm() );
1453
1454 m_dimension.Update();
1455 }
1456 else if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1457 {
1458 m_dimension.SetStart( aEditedPoint.GetPosition() );
1459 m_dimension.Update();
1460
1461 aPoints.Point( DIM_CROSSBARSTART )
1463 aPoints.Point( DIM_CROSSBARSTART ), aPoints.Point( DIM_START ) ) );
1464 aPoints.Point( DIM_CROSSBAREND )
1466 aPoints.Point( DIM_CROSSBAREND ), aPoints.Point( DIM_END ) ) );
1467 }
1468 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1469 {
1470 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1471 m_dimension.Update();
1472
1473 aPoints.Point( DIM_CROSSBARSTART )
1475 aPoints.Point( DIM_CROSSBARSTART ), aPoints.Point( DIM_START ) ) );
1476 aPoints.Point( DIM_CROSSBAREND )
1478 aPoints.Point( DIM_CROSSBAREND ), aPoints.Point( DIM_END ) ) );
1479 }
1480 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1481 {
1482 // Force manual mode if we weren't already in it
1483 m_dimension.SetTextPositionMode( DIM_TEXT_POSITION::MANUAL );
1484 m_dimension.SetTextPos( aEditedPoint.GetPosition() );
1485 m_dimension.Update();
1486 }
1487
1488 textPositionUpdater.UpdateTextAfterChange();
1489 }
1490
1494 void updateOrthogonalDimension( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints )
1495 {
1496 DIM_ALIGNED_TEXT_UPDATER textPositionUpdater( m_dimension );
1497 PCB_DIM_ORTHOGONAL& orthDimension = static_cast<PCB_DIM_ORTHOGONAL&>( m_dimension );
1498
1499 if( isModified( aEditedPoint, aPoints.Point( DIM_CROSSBARSTART ) )
1500 || isModified( aEditedPoint, aPoints.Point( DIM_CROSSBAREND ) ) )
1501 {
1502 BOX2I bounds( m_dimension.GetStart(), m_dimension.GetEnd() - m_dimension.GetStart() );
1503
1504 const VECTOR2I& cursorPos = aEditedPoint.GetPosition();
1505
1506 // Find vector from nearest dimension point to edit position
1507 VECTOR2I directionA( cursorPos - m_dimension.GetStart() );
1508 VECTOR2I directionB( cursorPos - m_dimension.GetEnd() );
1509 VECTOR2I direction = directionA.SquaredEuclideanNorm() < directionB.SquaredEuclideanNorm() ? directionA
1510 : directionB;
1511
1512 bool vert;
1513 VECTOR2D featureLine( cursorPos - m_dimension.GetStart() );
1514
1515 // Only change the orientation when we move outside the bounds
1516 if( !bounds.Contains( cursorPos ) )
1517 {
1518 // If the dimension is horizontal or vertical, set correct orientation
1519 // otherwise, test if we're left/right of the bounding box or above/below it
1520 if( bounds.GetWidth() == 0 )
1521 vert = true;
1522 else if( bounds.GetHeight() == 0 )
1523 vert = false;
1524 else if( cursorPos.x > bounds.GetLeft() && cursorPos.x < bounds.GetRight() )
1525 vert = false;
1526 else if( cursorPos.y > bounds.GetTop() && cursorPos.y < bounds.GetBottom() )
1527 vert = true;
1528 else
1529 vert = std::abs( direction.y ) < std::abs( direction.x );
1530
1533 }
1534 else
1535 {
1536 vert = orthDimension.GetOrientation() == PCB_DIM_ORTHOGONAL::DIR::VERTICAL;
1537 }
1538
1539 m_dimension.SetHeight( vert ? featureLine.x : featureLine.y );
1540 }
1541 else if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1542 {
1543 m_dimension.SetStart( aEditedPoint.GetPosition() );
1544 }
1545 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1546 {
1547 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1548 }
1549 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1550 {
1551 // Force manual mode if we weren't already in it
1552 m_dimension.SetTextPositionMode( DIM_TEXT_POSITION::MANUAL );
1553 m_dimension.SetTextPos( VECTOR2I( aEditedPoint.GetPosition() ) );
1554 }
1555
1556 m_dimension.Update();
1557
1558 // After recompute, find the new text position
1559 textPositionUpdater.UpdateTextAfterChange();
1560 }
1561
1563};
1564
1565
1567{
1568public:
1570 m_dimension( aDimension )
1571 {}
1572
1573 void MakePoints( EDIT_POINTS& aPoints ) override
1574 {
1575 aPoints.AddPoint( m_dimension.GetStart() );
1576 aPoints.AddPoint( m_dimension.GetEnd() );
1577
1579
1580 aPoints.Point( DIM_END ).SetRelation( EDIT_RELATION::Angle45( aPoints.Point( DIM_START ) ) );
1582 }
1583
1584 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1585 {
1586 wxCHECK( aPoints.PointsSize() == DIM_CENTER_MAX, false );
1587
1588 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1589 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1590 return true;
1591 }
1592
1593 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1594 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1595 {
1597
1598 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1599 m_dimension.SetStart( aEditedPoint.GetPosition() );
1600 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1601 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1602
1603 m_dimension.Update();
1604 }
1605
1606 OPT_VECTOR2I Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const override
1607 {
1608 if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1609 return aPoints.Point( DIM_START ).GetPosition();
1610
1611 return std::nullopt;
1612 }
1613
1614private:
1616};
1617
1618
1620{
1621public:
1623 m_dimension( aDimension )
1624 {}
1625
1626 void MakePoints( EDIT_POINTS& aPoints ) override
1627 {
1628 aPoints.AddPoint( m_dimension.GetStart() );
1629 aPoints.AddPoint( m_dimension.GetEnd() );
1630 aPoints.AddPoint( m_dimension.GetTextPos() );
1631 aPoints.AddPoint( m_dimension.GetKnee() );
1632
1635
1636 aPoints.Point( DIM_KNEE )
1638 aPoints.Point( DIM_END ) ) );
1640
1641 aPoints.Point( DIM_TEXT ).SetRelation( EDIT_RELATION::Angle45( aPoints.Point( DIM_KNEE ) ) );
1643 }
1644
1645 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1646 {
1647 wxCHECK( aPoints.PointsSize() == DIM_RADIAL_MAX, false );
1648
1649 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1650 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1651 aPoints.Point( DIM_TEXT ).SetPosition( m_dimension.GetTextPos() );
1652 aPoints.Point( DIM_KNEE ).SetPosition( m_dimension.GetKnee() );
1653 return true;
1654 }
1655
1656 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1657 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1658 {
1660
1661 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1662 {
1663 m_dimension.SetStart( aEditedPoint.GetPosition() );
1664 m_dimension.Update();
1665
1666 aPoints.Point( DIM_KNEE )
1668 aPoints.Point( DIM_END ) ) );
1669 }
1670 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1671 {
1672 VECTOR2I oldKnee = m_dimension.GetKnee();
1673
1674 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1675 m_dimension.Update();
1676
1677 VECTOR2I kneeDelta = m_dimension.GetKnee() - oldKnee;
1678 m_dimension.SetTextPos( m_dimension.GetTextPos() + kneeDelta );
1679 m_dimension.Update();
1680
1681 aPoints.Point( DIM_KNEE )
1683 aPoints.Point( DIM_END ) ) );
1684 }
1685 else if( isModified( aEditedPoint, aPoints.Point( DIM_KNEE ) ) )
1686 {
1687 VECTOR2I oldKnee = m_dimension.GetKnee();
1688 VECTOR2I arrowVec = aPoints.Point( DIM_KNEE ).GetPosition() - aPoints.Point( DIM_END ).GetPosition();
1689
1690 m_dimension.SetLeaderLength( arrowVec.EuclideanNorm() );
1691 m_dimension.Update();
1692
1693 VECTOR2I kneeDelta = m_dimension.GetKnee() - oldKnee;
1694 m_dimension.SetTextPos( m_dimension.GetTextPos() + kneeDelta );
1695 m_dimension.Update();
1696 }
1697 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1698 {
1699 m_dimension.SetTextPos( aEditedPoint.GetPosition() );
1700 m_dimension.Update();
1701 }
1702 }
1703
1704 OPT_VECTOR2I Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const override
1705 {
1706 if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1707 return aPoints.Point( DIM_KNEE ).GetPosition();
1708
1709 return std::nullopt;
1710 }
1711
1712private:
1714};
1715
1716
1718{
1719public:
1721 m_dimension( aDimension )
1722 {}
1723
1724 void MakePoints( EDIT_POINTS& aPoints ) override
1725 {
1726 aPoints.AddPoint( m_dimension.GetStart() );
1727 aPoints.AddPoint( m_dimension.GetEnd() );
1728 aPoints.AddPoint( m_dimension.GetTextPos() );
1729
1732
1733 aPoints.Point( DIM_TEXT ).SetRelation( EDIT_RELATION::Angle45( aPoints.Point( DIM_END ) ) );
1735 }
1736
1737 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1738 {
1739 wxCHECK( aPoints.PointsSize() == DIM_LEADER_MAX, false );
1740
1741 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1742 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1743 aPoints.Point( DIM_TEXT ).SetPosition( m_dimension.GetTextPos() );
1744 return true;
1745 }
1746
1747 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1748 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1749 {
1751
1752 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1753 {
1754 m_dimension.SetStart( aEditedPoint.GetPosition() );
1755 }
1756 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1757 {
1758 const VECTOR2I newPoint( aEditedPoint.GetPosition() );
1759 const VECTOR2I delta = newPoint - m_dimension.GetEnd();
1760
1761 m_dimension.SetEnd( newPoint );
1762 m_dimension.SetTextPos( m_dimension.GetTextPos() + delta );
1763 }
1764 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1765 {
1766 m_dimension.SetTextPos( aEditedPoint.GetPosition() );
1767 }
1768
1769 m_dimension.Update();
1770 }
1771
1772private:
1774};
1775
1776
1781{
1782public:
1784 m_textbox( aTextbox )
1785 {}
1786
1787 void MakePoints( EDIT_POINTS& aPoints ) override
1788 {
1789 if( m_textbox.GetShape() == SHAPE_T::RECTANGLE )
1791
1792 // Rotated textboxes are implemented as polygons and these aren't currently editable.
1793 }
1794
1795 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1796 {
1797 // Careful; textbox shape is mutable between cardinal and non-cardinal rotations...
1798 const unsigned target = m_textbox.GetShape() == SHAPE_T::RECTANGLE ? RECT_MAX_POINTS : 0;
1799
1800 if( aPoints.PointsSize() != target )
1801 return false;
1802
1804 return true;
1805 }
1806
1807 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1808 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1809 {
1810 if( m_textbox.GetShape() == SHAPE_T::RECTANGLE )
1811 {
1812 m_textbox.ClearBoundingBoxCache();
1813 VECTOR2I minSize = m_textbox.GetMinSize();
1815 }
1816 }
1817
1818private:
1820};
1821
1823{
1824public:
1826 m_group( &aGroup ),
1827 m_parent( &aGroup ),
1828 m_pinned( std::nullopt )
1829 {
1830 for( BOARD_ITEM* item : aGroup.GetBoardItems() )
1831 {
1832 if( item->Type() == PCB_SHAPE_T )
1833 {
1834 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
1835 m_shapes.push_back( shape );
1836 m_originalWidths[shape] = static_cast<double>( shape->GetWidth() );
1837 }
1838 }
1839 }
1840
1841 SHAPE_GROUP_POINT_EDIT_BEHAVIOR( std::vector<PCB_SHAPE*> aShapes, BOARD_ITEM* aParent,
1842 const std::optional<VECTOR2I>& aPinned ) :
1843 m_group( nullptr ),
1844 m_shapes( std::move( aShapes ) ),
1845 m_parent( aParent ),
1846 m_pinned( aPinned )
1847 {
1848 for( PCB_SHAPE* shape : m_shapes )
1849 m_originalWidths[shape] = static_cast<double>( shape->GetWidth() );
1850 }
1851
1852 void MakePoints( EDIT_POINTS& aPoints ) override
1853 {
1854 BOX2I bbox = getBoundingBox();
1855 VECTOR2I tl = bbox.GetOrigin();
1856 VECTOR2I br = bbox.GetEnd();
1857
1858 aPoints.AddPoint( tl );
1859 aPoints.AddPoint( VECTOR2I( br.x, tl.y ) );
1860 aPoints.AddPoint( br );
1861 aPoints.AddPoint( VECTOR2I( tl.x, br.y ) );
1862 aPoints.AddPoint( bbox.Centre() );
1863
1864 aPoints.AddIndicatorLine( aPoints.Point( RECT_TOP_LEFT ), aPoints.Point( RECT_TOP_RIGHT ) );
1865 aPoints.AddIndicatorLine( aPoints.Point( RECT_TOP_RIGHT ), aPoints.Point( RECT_BOT_RIGHT ) );
1866 aPoints.AddIndicatorLine( aPoints.Point( RECT_BOT_RIGHT ), aPoints.Point( RECT_BOT_LEFT ) );
1867 aPoints.AddIndicatorLine( aPoints.Point( RECT_BOT_LEFT ), aPoints.Point( RECT_TOP_LEFT ) );
1868 }
1869
1870 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1871 {
1872 BOX2I bbox = getBoundingBox();
1873 VECTOR2I tl = bbox.GetOrigin();
1874 VECTOR2I br = bbox.GetEnd();
1875
1876 aPoints.Point( RECT_TOP_LEFT ).SetPosition( tl );
1877 aPoints.Point( RECT_TOP_RIGHT ).SetPosition( br.x, tl.y );
1878 aPoints.Point( RECT_BOT_RIGHT ).SetPosition( br );
1879 aPoints.Point( RECT_BOT_LEFT ).SetPosition( tl.x, br.y );
1880 aPoints.Point( RECT_CENTER ).SetPosition( bbox.Centre() );
1881 return true;
1882 }
1883
1884 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1885 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1886 {
1887 BOX2I oldBox = getBoundingBox();
1888 VECTOR2I oldCenter = oldBox.Centre();
1889
1890 if( isModified( aEditedPoint, aPoints.Point( RECT_CENTER ) ) )
1891 {
1892 if( m_pinned )
1893 {
1894 UpdatePoints( aPoints );
1895 return;
1896 }
1897
1898 VECTOR2I delta = aPoints.Point( RECT_CENTER ).GetPosition() - oldCenter;
1899
1900 if( m_group )
1901 {
1902 aCommit.Modify( m_group, nullptr, RECURSE_MODE::RECURSE );
1903 m_group->Move( delta );
1904 }
1905 else
1906 {
1907 for( PCB_SHAPE* shape : m_shapes )
1908 {
1909 aCommit.Modify( shape );
1910 shape->Move( delta );
1911 }
1912 }
1913
1914 for( PCB_SHAPE* shape : m_shapes )
1915 aUpdatedItems.push_back( shape );
1916
1917 UpdatePoints( aPoints );
1918 return;
1919 }
1920
1921 VECTOR2I tl = aPoints.Point( RECT_TOP_LEFT ).GetPosition();
1922 VECTOR2I tr = aPoints.Point( RECT_TOP_RIGHT ).GetPosition();
1923 VECTOR2I bl = aPoints.Point( RECT_BOT_LEFT ).GetPosition();
1924 VECTOR2I br = aPoints.Point( RECT_BOT_RIGHT ).GetPosition();
1925
1926 RECTANGLE_POINT_EDIT_BEHAVIOR::PinEditedCorner( aEditedPoint, aPoints, tl, tr, bl, br );
1927
1928 VECTOR2I origin = oldCenter;
1929 double scale;
1930
1931 // A scale holds exactly one point still, so a pinned selection has to scale about the pin
1932 if( m_pinned )
1933 {
1934 VECTOR2I oldCorner( oldBox.GetLeft(), oldBox.GetTop() );
1935
1936 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_RIGHT ) ) )
1937 oldCorner = VECTOR2I( oldBox.GetRight(), oldBox.GetTop() );
1938 else if( isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) ) )
1939 oldCorner = VECTOR2I( oldBox.GetRight(), oldBox.GetBottom() );
1940 else if( isModified( aEditedPoint, aPoints.Point( RECT_BOT_LEFT ) ) )
1941 oldCorner = VECTOR2I( oldBox.GetLeft(), oldBox.GetBottom() );
1942
1943 // Below this share of the selection diagonal a corner is too near the pin to scale by
1944 const double MIN_ARM_RATIO = 0.1;
1945
1946 VECTOR2I oldArm = oldCorner - *m_pinned;
1947 VECTOR2I newArm = aEditedPoint.GetPosition() - *m_pinned;
1948 VECTOR2I diagonal( oldBox.GetWidth(), oldBox.GetHeight() );
1949 double span = static_cast<double>( oldArm.SquaredEuclideanNorm() );
1950 double floor = static_cast<double>( diagonal.SquaredEuclideanNorm() ) * MIN_ARM_RATIO * MIN_ARM_RATIO;
1951
1952 // A corner on the pin has no leverage, so any drag of it would scale wildly
1953 if( span < floor )
1954 {
1955 UpdatePoints( aPoints );
1956 return;
1957 }
1958
1959 // Measured along the arm, so a corner level with the pin stays controllable
1960 origin = *m_pinned;
1961 scale = static_cast<double>( oldArm.Dot( newArm ) ) / span;
1962 }
1963 else
1964 {
1965 double sx = static_cast<double>( br.x - tl.x ) / static_cast<double>( oldBox.GetWidth() );
1966 double sy = static_cast<double>( br.y - tl.y ) / static_cast<double>( oldBox.GetHeight() );
1967
1968 scale = ( sx + sy ) / 2.0;
1969 }
1970
1971 // Prevent scaling below a minimum threshold to avoid precision loss when shapes
1972 // are scaled to near-zero size. Also prevent negative scaling which would flip
1973 // shapes when dragging past the center point.
1974 const double MIN_SCALE = 0.01;
1975
1976 if( scale < MIN_SCALE )
1977 scale = MIN_SCALE;
1978
1979 for( PCB_SHAPE* shape : m_shapes )
1980 {
1981 aCommit.Modify( shape );
1982 shape->Move( -origin );
1983 shape->Scale( scale );
1984 shape->Move( origin );
1985
1986 if( auto shapeIt = m_originalWidths.find( shape ); shapeIt != m_originalWidths.end() )
1987 {
1988 shapeIt->second = shapeIt->second * scale;
1989 shape->SetWidth( KiROUND( shapeIt->second ) );
1990 }
1991 else
1992 {
1993 shape->SetWidth( KiROUND( shape->GetWidth() * scale ) );
1994 }
1995
1996 aUpdatedItems.push_back( shape );
1997 }
1998
1999 UpdatePoints( aPoints );
2000 }
2001
2002 BOARD_ITEM* GetParent() const { return m_parent; }
2003
2004 const std::vector<PCB_SHAPE*>& GetShapes() const { return m_shapes; }
2005
2006private:
2008 {
2009 BOX2I bbox;
2010
2011 for( const PCB_SHAPE* shape : m_shapes )
2012 bbox.Merge( shape->GetBoundingBox() );
2013
2014 return bbox;
2015 }
2016
2017private:
2019 std::vector<PCB_SHAPE*> m_shapes;
2021 std::optional<VECTOR2I> m_pinned;
2022 std::unordered_map<PCB_SHAPE*, double> m_originalWidths;
2023};
2024
2025
2027 PCB_TOOL_BASE( "pcbnew.PointEditor" ),
2028 m_frame( nullptr ),
2029 m_selectionTool( nullptr ),
2030 m_editedPoint( nullptr ),
2031 m_hoveredPoint( nullptr ),
2032 m_original( VECTOR2I( 0, 0 ) ),
2034 m_radiusHelper( nullptr ),
2035 m_altConstrainer( VECTOR2I( 0, 0 ) ),
2036 m_inPointEditorTool( false ),
2037 m_angleSnapPos( VECTOR2I( 0, 0 ) ),
2038 m_stickyDisplacement( VECTOR2I( 0, 0 ) ),
2039 m_angleSnapActive( false )
2040{}
2041
2042
2044{
2046
2047 if( KIGFX::VIEW* view = getView() )
2048 {
2049 if( m_angleItem && view->HasItem( m_angleItem.get() ) )
2050 view->Remove( m_angleItem.get() );
2051
2052 if( m_editPoints && view->HasItem( m_editPoints.get() ) )
2053 view->Remove( m_editPoints.get() );
2054
2055 if( view->HasItem( &m_preview ) )
2056 view->Remove( &m_preview );
2057 }
2058
2059 m_angleItem.reset();
2060 m_editPoints.reset();
2061 m_altConstraint.reset();
2062 getViewControls()->SetAutoPan( false );
2063 m_angleSnapActive = false;
2065}
2066
2067
2069{
2070 const KICAD_T type = aItem.Type();
2071
2072 if( type == PCB_ZONE_T )
2073 return true;
2074
2075 if( type == PCB_SHAPE_T )
2076 {
2077 const PCB_SHAPE& shape = static_cast<const PCB_SHAPE&>( aItem );
2078 const SHAPE_T shapeType = shape.GetShape();
2079 return shapeType == SHAPE_T::SEGMENT || shapeType == SHAPE_T::POLY || shapeType == SHAPE_T::ARC;
2080 }
2081
2082 return false;
2083}
2084
2085
2087{
2088 const auto type = aItem.Type();
2089
2090 if( type == PCB_ZONE_T )
2091 return true;
2092
2093 if( type == PCB_SHAPE_T )
2094 {
2095 const PCB_SHAPE& shape = static_cast<const PCB_SHAPE&>( aItem );
2096 const SHAPE_T shapeType = shape.GetShape();
2097 return shapeType == SHAPE_T::POLY;
2098 }
2099
2100 return false;
2101}
2102
2103
2104static VECTOR2I snapCorner( const VECTOR2I& aPrev, const VECTOR2I& aNext, const VECTOR2I& aGuess,
2105 double aAngleDeg )
2106{
2107 double angleRad = aAngleDeg * M_PI / 180.0;
2108 VECTOR2D prev( aPrev );
2109 VECTOR2D next( aNext );
2110 double chord = ( next - prev ).EuclideanNorm();
2111 double sinA = sin( angleRad );
2112
2113 if( chord == 0.0 || fabs( sinA ) < 1e-9 )
2114 return aGuess;
2115
2116 double radius = chord / ( 2.0 * sinA );
2117 VECTOR2D mid = ( prev + next ) / 2.0;
2118 VECTOR2D dir = next - prev;
2119 VECTOR2D normal( -dir.y, dir.x );
2120 normal = normal.Resize( 1 );
2121 double h_sq = radius * radius - ( chord * chord ) / 4.0;
2122 double h = h_sq > 0.0 ? sqrt( h_sq ) : 0.0;
2123
2124 VECTOR2D center1 = mid + normal * h;
2125 VECTOR2D center2 = mid - normal * h;
2126
2127 auto project =
2128 [&]( const VECTOR2D& center )
2129 {
2130 VECTOR2D v = VECTOR2D( aGuess ) - center;
2131
2132 if( v.EuclideanNorm() == 0.0 )
2133 v = prev - center;
2134
2135 v = v.Resize( 1 );
2136 VECTOR2D p = center + v * radius;
2137 return KiROUND( p );
2138 };
2139
2140 VECTOR2I p1 = project( center1 );
2141 VECTOR2I p2 = project( center2 );
2142
2143 double d1 = ( VECTOR2D( aGuess ) - VECTOR2D( p1 ) ).EuclideanNorm();
2144 double d2 = ( VECTOR2D( aGuess ) - VECTOR2D( p2 ) ).EuclideanNorm();
2145
2146 return d1 < d2 ? p1 : p2;
2147}
2148
2149
2151{
2152 // Find the selection tool, so they can cooperate
2154
2155 wxASSERT_MSG( m_selectionTool, wxT( "pcbnew.InteractiveSelection tool is not available" ) );
2156
2157 const auto arcIsEdited =
2158 []( const SELECTION& aSelection ) -> bool
2159 {
2160 const EDA_ITEM* item = aSelection.Front();
2161 return ( item != nullptr ) && ( item->Type() == PCB_SHAPE_T )
2162 && static_cast<const PCB_SHAPE*>( item )->GetShape() == SHAPE_T::ARC;
2163 };
2164
2165 using S_C = SELECTION_CONDITIONS;
2166
2167 auto& menu = m_selectionTool->GetToolMenu().GetMenu();
2168
2169 menu.AddItem( PCB_ACTIONS::cycleArcEditMode, S_C::Count( 1 ) && arcIsEdited );
2170
2171 return true;
2172}
2173
2174
2175std::shared_ptr<EDIT_POINTS> PCB_POINT_EDITOR::makePoints( EDA_ITEM* aItem )
2176{
2177 std::shared_ptr<EDIT_POINTS> points = std::make_shared<EDIT_POINTS>( aItem );
2178
2179 if( !aItem )
2180 return points;
2181
2182 // Reset the behaviour and we'll make a new one
2183 m_editorBehavior = nullptr;
2184
2185 switch( aItem->Type() )
2186 {
2188 {
2189 PCB_REFERENCE_IMAGE& refImage = static_cast<PCB_REFERENCE_IMAGE&>( *aItem );
2190 m_editorBehavior = std::make_unique<REFERENCE_IMAGE_POINT_EDIT_BEHAVIOR>( refImage.GetReferenceImage(),
2191 pcbIUScale.mmToIU( 1.0 ) );
2192 break;
2193 }
2194 case PCB_BARCODE_T:
2195 {
2196 PCB_BARCODE& barcode = static_cast<PCB_BARCODE&>( *aItem );
2197 m_editorBehavior = std::make_unique<BARCODE_POINT_EDIT_BEHAVIOR>( barcode );
2198 break;
2199 }
2200 case PCB_GRID_ITEM_T:
2201 {
2202 PCB_GRID_ITEM& grid = static_cast<PCB_GRID_ITEM&>( *aItem );
2203 m_editorBehavior = std::make_unique<GRID_POINT_EDIT_BEHAVIOR>( grid );
2204 break;
2205 }
2206 case PCB_TEXTBOX_T:
2207 {
2208 PCB_TEXTBOX& textbox = static_cast<PCB_TEXTBOX&>( *aItem );
2209 m_editorBehavior = std::make_unique<TEXTBOX_POINT_EDIT_BEHAVIOR>( textbox );
2210 break;
2211 }
2212 case PCB_SHAPE_T:
2213 {
2214 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
2215
2216 switch( shape->GetShape() )
2217 {
2218 case SHAPE_T::SEGMENT:
2219 m_editorBehavior = std::make_unique<EDA_SEGMENT_POINT_EDIT_BEHAVIOR>( *shape );
2220 break;
2221
2222 case SHAPE_T::RECTANGLE:
2223 m_editorBehavior = std::make_unique<RECTANGLE_POINT_EDIT_BEHAVIOR>( *shape );
2224 break;
2225
2226 case SHAPE_T::ARC:
2227 m_editorBehavior = std::make_unique<EDA_ARC_POINT_EDIT_BEHAVIOR>( *shape, m_arcEditMode,
2228 *getViewControls(),
2229 pcbIUScale );
2230 break;
2231
2232 case SHAPE_T::CIRCLE:
2233 m_editorBehavior = std::make_unique<EDA_CIRCLE_POINT_EDIT_BEHAVIOR>( *shape );
2234 break;
2235
2236 case SHAPE_T::POLY:
2237 m_editorBehavior = std::make_unique<EDA_POLYGON_POINT_EDIT_BEHAVIOR>( *shape );
2238 break;
2239
2240 case SHAPE_T::BEZIER:
2241 m_editorBehavior = std::make_unique<EDA_BEZIER_POINT_EDIT_BEHAVIOR>( *shape,
2242 shape->GetMaxError() );
2243 break;
2244
2245 case SHAPE_T::ELLIPSE:
2247 m_editorBehavior = std::make_unique<EDA_ELLIPSE_POINT_EDIT_BEHAVIOR>( *shape );
2248 break;
2249
2250 default: // suppress warnings
2251 break;
2252 }
2253
2254 break;
2255 }
2256
2257 case PCB_GROUP_T:
2258 {
2259 PCB_GROUP* group = static_cast<PCB_GROUP*>( aItem );
2260 bool shapesOnly = true;
2261
2262 for( BOARD_ITEM* child : group->GetBoardItems() )
2263 {
2264 if( child->Type() != PCB_SHAPE_T )
2265 {
2266 shapesOnly = false;
2267 break;
2268 }
2269 }
2270
2271 if( shapesOnly )
2272 m_editorBehavior = std::make_unique<SHAPE_GROUP_POINT_EDIT_BEHAVIOR>( *group );
2273 else
2274 points.reset();
2275
2276 break;
2277 }
2278
2279 case PCB_TABLECELL_T:
2280 {
2281 PCB_TABLECELL* cell = static_cast<PCB_TABLECELL*>( aItem );
2282
2283 // No support for point-editing of a rotated table
2284 if( cell->GetShape() == SHAPE_T::RECTANGLE )
2285 m_editorBehavior = std::make_unique<PCB_TABLECELL_POINT_EDIT_BEHAVIOR>( *cell );
2286
2287 break;
2288 }
2289
2290 case PCB_PAD_T:
2291 {
2292 // Pad edit only for the footprint editor
2294 {
2295 PAD& pad = static_cast<PAD&>( *aItem );
2296 PCB_LAYER_ID activeLayer = m_frame ? m_frame->GetActiveLayer() : PADSTACK::TEMP_ALL_LAYERS;
2297
2298 // Point editor only handles copper shape changes
2299 if( !IsCopperLayer( activeLayer ) )
2300 activeLayer = IsFrontLayer( activeLayer ) ? F_Cu : B_Cu;
2301
2302 m_editorBehavior = std::make_unique<PAD_POINT_EDIT_BEHAVIOR>( pad, activeLayer );
2303 }
2304 break;
2305 }
2306
2307 case PCB_ZONE_T:
2308 {
2309 ZONE& zone = static_cast<ZONE&>( *aItem );
2310 m_editorBehavior = std::make_unique<ZONE_POINT_EDIT_BEHAVIOR>( zone );
2311 break;
2312 }
2313
2314 case PCB_GENERATOR_T:
2315 {
2316 if( dynamic_cast<PCB_GENERATOR_POLY*>( aItem ) )
2317 {
2318 PCB_GENERATOR_POLY* generator = static_cast<PCB_GENERATOR_POLY*>( aItem );
2319 m_editorBehavior = std::make_unique<GENERATOR_POLY_POINT_EDIT_BEHAVIOR>( *generator );
2320 break;
2321 }
2322 else
2323 {
2324 PCB_GENERATOR* generator = static_cast<PCB_GENERATOR*>( aItem );
2325 m_editorBehavior = std::make_unique<GENERATOR_POINT_EDIT_BEHAVIOR>( *generator );
2326 }
2327 break;
2328 }
2329
2330 case PCB_DIM_ALIGNED_T:
2332 {
2333 PCB_DIM_ALIGNED& dimension = static_cast<PCB_DIM_ALIGNED&>( *aItem );
2334 m_editorBehavior = std::make_unique<ALIGNED_DIMENSION_POINT_EDIT_BEHAVIOR>( dimension );
2335 break;
2336 }
2337
2338 case PCB_DIM_CENTER_T:
2339 {
2340 PCB_DIM_CENTER& dimension = static_cast<PCB_DIM_CENTER&>( *aItem );
2341 m_editorBehavior = std::make_unique<DIM_CENTER_POINT_EDIT_BEHAVIOR>( dimension );
2342 break;
2343 }
2344
2345 case PCB_DIM_RADIAL_T:
2346 {
2347 PCB_DIM_RADIAL& dimension = static_cast<PCB_DIM_RADIAL&>( *aItem );
2348 m_editorBehavior = std::make_unique<DIM_RADIAL_POINT_EDIT_BEHAVIOR>( dimension );
2349 break;
2350 }
2351
2352 case PCB_DIM_LEADER_T:
2353 {
2354 PCB_DIM_LEADER& dimension = static_cast<PCB_DIM_LEADER&>( *aItem );
2355 m_editorBehavior = std::make_unique<DIM_LEADER_POINT_EDIT_BEHAVIOR>( dimension );
2356 break;
2357 }
2358
2359 default:
2360 points.reset();
2361 break;
2362 }
2363
2364 if( m_editorBehavior )
2365 m_editorBehavior->MakePoints( *points );
2366
2367 return points;
2368}
2369
2370
2372{
2373 EDIT_POINT* point;
2374 EDIT_POINT* hovered = nullptr;
2375
2376 if( aEvent.IsMotion() )
2377 {
2378 point = m_editPoints->FindPoint( aEvent.Position(), getView() );
2379 hovered = point;
2380 }
2381 else if( aEvent.IsDrag( BUT_LEFT ) )
2382 {
2383 point = m_editPoints->FindPoint( aEvent.DragOrigin(), getView() );
2384 }
2385 else
2386 {
2387 point = m_editPoints->FindPoint( getViewControls()->GetCursorPosition(), getView() );
2388 }
2389
2390 if( hovered )
2391 {
2392 if( m_hoveredPoint != hovered )
2393 {
2394 if( m_hoveredPoint )
2395 m_hoveredPoint->SetHover( false );
2396
2397 m_hoveredPoint = hovered;
2398 m_hoveredPoint->SetHover();
2399 }
2400 }
2401 else if( m_hoveredPoint )
2402 {
2403 m_hoveredPoint->SetHover( false );
2404 m_hoveredPoint = nullptr;
2405 }
2406
2407 if( m_editedPoint != point )
2408 setEditedPoint( point );
2409}
2410
2411
2413{
2415 return 0;
2416
2418 return 0;
2419
2421
2423 const PCB_SELECTION& selection = m_selectionTool->GetSelection();
2424
2425 if( selection.Size() == 0 )
2426 return 0;
2427
2428 for( EDA_ITEM* selItem : selection )
2429 {
2430 if( selItem->GetEditFlags() || !selItem->IsBOARD_ITEM() )
2431 return 0;
2432 }
2433
2434 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
2435 bool overrideLocks = editFrame->GetOverrideLocks();
2436
2437 if( !item || ( item->IsLocked() && !overrideLocks ) )
2438 return 0;
2439
2440 Activate();
2441 // Must be done after Activate() so that it gets set into the correct context
2442 getViewControls()->ShowCursor( true );
2443
2445 grid.SetPointEditProfile( true );
2447
2448 // Use the original object as a construction item
2449 std::vector<std::unique_ptr<BOARD_ITEM>> clones;
2450
2451 m_editorBehavior.reset();
2452
2453 if( selection.Size() > 1 )
2454 {
2455 // Multi-selection: check if all items are shapes
2456 std::vector<PCB_SHAPE*> shapes;
2457 std::set<KIID> shapeIds;
2458 bool allShapes = true;
2459 bool anyLocked = false;
2460
2461 for( EDA_ITEM* selItem : selection )
2462 {
2463 if( selItem->Type() == PCB_SHAPE_T )
2464 {
2465 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( selItem );
2466 shapes.push_back( shape );
2467 shapeIds.insert( shape->m_Uuid );
2468
2469 if( shape->IsLocked() )
2470 anyLocked = true;
2471 }
2472 else
2473 {
2474 allShapes = false;
2475 }
2476 }
2477
2478 std::vector<VECTOR2I> pinned;
2479
2480 if( allShapes && shapes.size() > 1 )
2481 pinned = ConstraintPinnedPositions( board(), shapeIds );
2482
2483 // Two pins in different places leave a scale no fixed point to work from
2484 if( allShapes && shapes.size() > 1 && ( !anyLocked || overrideLocks ) && pinned.size() < 2 )
2485 {
2486 std::optional<VECTOR2I> pin;
2487
2488 if( !pinned.empty() )
2489 pin = pinned.front();
2490
2491 m_editorBehavior = std::make_unique<SHAPE_GROUP_POINT_EDIT_BEHAVIOR>( std::move( shapes ), item, pin );
2492 m_editPoints = std::make_shared<EDIT_POINTS>( item );
2493 m_editorBehavior->MakePoints( *m_editPoints );
2494 }
2495 else
2496 {
2497 return 0;
2498 }
2499 }
2500 else
2501 {
2502 // Single selection: use existing makePoints logic
2503 m_editPoints = makePoints( item );
2504 }
2505
2506 if( !m_editPoints )
2507 return 0;
2508
2509 PCB_SHAPE* graphicItem = dynamic_cast<PCB_SHAPE*>( item );
2510
2511 // Only add the angle_item if we are editing a polygon or zone
2512 if( item->Type() == PCB_ZONE_T || ( graphicItem && graphicItem->GetShape() == SHAPE_T::POLY ) )
2513 {
2514 m_angleItem = std::make_unique<KIGFX::PREVIEW::ANGLE_ITEM>( m_editPoints );
2515 }
2516
2517 m_preview.FreeItems();
2518 m_radiusHelper = nullptr;
2519 getView()->Add( &m_preview );
2520
2523
2524 getView()->Add( m_editPoints.get() );
2525
2526 if( m_angleItem )
2527 getView()->Add( m_angleItem.get() );
2528
2529 setEditedPoint( nullptr );
2530 updateEditedPoint( aEvent );
2531 bool inDrag = false;
2532 bool isConstrained = false;
2533 bool haveSnapLineDirections = false;
2534
2535 auto updateSnapLineDirections =
2536 [&]()
2537 {
2538 std::vector<VECTOR2I> directions;
2539
2540 if( inDrag && m_editedPoint )
2541 {
2542 EDIT_RELATION* relation = nullptr;
2543
2544 if( m_altConstraint )
2545 relation = m_altConstraint.get();
2546 else if( m_editedPoint->IsConstrained() )
2547 relation = m_editedPoint->GetRelation();
2548
2549 directions = getConstraintDirections( relation );
2550 }
2551
2552 if( directions.empty() )
2553 {
2554 grid.SetSnapLineDirections( {} );
2555 grid.SetSnapLineEnd( std::nullopt );
2556 haveSnapLineDirections = false;
2557 }
2558 else
2559 {
2560 VECTOR2I origin = m_altConstraint ? m_altConstrainer.GetPosition() : m_original.GetPosition();
2561
2562 grid.SetSnapLineDirections( directions );
2563 grid.SetSnapLineOrigin( origin );
2564 grid.SetSnapLineEnd( std::nullopt );
2565 haveSnapLineDirections = true;
2566 }
2567 };
2568
2569 BOARD_COMMIT commit( editFrame );
2570
2571 auto installFeasibilityCallback =
2572 [&]()
2573 {
2574 grid.SetFeasibilityCallback( {} );
2575
2576 if( !m_constraintDragSession || dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
2577 return;
2578
2579 std::shared_ptr<BOARD_CONSTRAINT_DRAG_SESSION> session = m_constraintDragSession;
2580
2581 grid.SetFeasibilityCallback(
2582 [session]( const SNAP_SOURCE_CONTEXT& aContext,
2583 const std::vector<SNAP_CANDIDATE>& aCandidates )
2584 {
2585 return session->ResolveCandidates( aContext, aCandidates );
2586 } );
2587 };
2588
2589 // Main loop: keep receiving events
2590 while( TOOL_EVENT* evt = Wait() )
2591 {
2592 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
2593 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
2594 installFeasibilityCallback();
2595
2596 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
2598 else
2600
2601 if( !m_editPoints || evt->IsSelectionEvent() || evt->Matches( EVENTS::InhibitSelectionEditing ) )
2602 {
2603 break;
2604 }
2605
2606 EDIT_POINT* prevHover = m_hoveredPoint;
2607
2608 if( !inDrag )
2609 updateEditedPoint( *evt );
2610
2611 if( prevHover != m_hoveredPoint )
2612 {
2613 getView()->Update( m_editPoints.get() );
2614
2615 if( m_angleItem )
2616 getView()->Update( m_angleItem.get() );
2617 }
2618
2619 if( evt->IsDrag( BUT_LEFT ) && m_editedPoint )
2620 {
2621 if( !inDrag )
2622 {
2623 frame()->UndoRedoBlock( true );
2624
2625 if( item->Type() == PCB_GENERATOR_T )
2626 {
2627 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genStartEdit, &commit,
2628 static_cast<PCB_GENERATOR*>( item ) );
2629 }
2630
2632 m_original = *m_editedPoint; // Save the original position
2633 getViewControls()->SetAutoPan( true );
2634 inDrag = true;
2635
2636 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
2637 {
2638 if( std::optional<CONSTRAINT_MEMBER> member =
2640 {
2641 m_constraintDragSession = std::make_shared<BOARD_CONSTRAINT_DRAG_SESSION>();
2642
2643 if( !m_constraintDragSession->Build( board(), *member ) )
2645
2646 installFeasibilityCallback();
2647 }
2648 }
2649
2650 if( m_editedPoint->GetGridConstraint() != SNAP_BY_GRID )
2651 grid.SetAuxAxes( true, m_original.GetPosition() );
2652
2653 m_editedPoint->SetActive();
2654
2655 for( size_t ii = 0; ii < m_editPoints->PointsSize(); ++ii )
2656 {
2657 EDIT_POINT& point = m_editPoints->Point( ii );
2658
2659 if( &point != m_editedPoint )
2660 point.SetActive( false );
2661 }
2662
2663 // When we start dragging, create a clone of the item to use as the original
2664 // reference geometry (e.g. for intersections and extensions)
2665 BOARD_ITEM* clone = static_cast<BOARD_ITEM*>( item->Clone() );
2666 clone->SetParent( nullptr );
2667
2668 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2669 {
2670 shape->SetFlags( IS_MOVING );
2671 shape->UpdateHatching();
2672
2673 static_cast<PCB_SHAPE*>( clone )->SetFillMode( FILL_T::NO_FILL );
2674 }
2675
2676 clones.emplace_back( clone );
2677 grid.AddConstructionItems( { clone }, false, true );
2678
2679 updateSnapLineDirections();
2680 }
2681
2682 EDIT_LINE* line = dynamic_cast<EDIT_LINE*>( m_editedPoint );
2683 bool ctrlHeld = evt->Modifier( MD_CTRL );
2684
2685 bool need_constraint = ( Is45Limited() || Is90Limited() ) && !ctrlHeld;
2686
2687 if( isConstrained != need_constraint )
2688 {
2689 setAltConstraint( need_constraint );
2690 isConstrained = need_constraint;
2691 updateSnapLineDirections();
2692 }
2693
2694 if( need_constraint )
2695 {
2696 VECTOR2I origin = m_altConstraint ? m_altConstrainer.GetPosition()
2697 : m_original.GetPosition();
2698 grid.SetAngleRestriction( origin, Is45Limited() ? 45.0 : 90.0 );
2699 }
2700 else
2701 {
2702 grid.SetAngleRestriction( std::nullopt, 0.0 );
2703 }
2704
2705 // For polygon lines, Ctrl temporarily toggles between CONVERGING and FIXED_LENGTH modes
2706
2707 if( line )
2708 {
2709 bool isPoly = false;
2710
2711 switch( item->Type() )
2712 {
2713 case PCB_ZONE_T:
2714 isPoly = true;
2715 break;
2716
2717 case PCB_SHAPE_T:
2718 isPoly = static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY;
2719 break;
2720
2721 default:
2722 break;
2723 }
2724
2725 if( isPoly )
2726 {
2727 POLYGON_EDGE_DRAG_POLICY* policy = line->GetDragPolicy();
2728
2729 if( policy )
2730 {
2733
2734 if( policy->GetMode() != targetMode )
2735 policy->SetMode( targetMode );
2736 }
2737 }
2738 }
2739
2740 // Keep point inside of limits with some padding
2741 VECTOR2I pos = GetClampedCoords<double, int>( evt->Position(), COORDS_PADDING );
2742 LSET snapLayers;
2743
2744 switch( m_editedPoint->GetSnapConstraint() )
2745 {
2746 case IGNORE_SNAPS: break;
2747 case OBJECT_LAYERS: snapLayers = item->GetLayerSet(); break;
2748 case ALL_LAYERS: snapLayers = LSET::AllLayersMask(); break;
2749 }
2750
2751 if( m_editedPoint->GetGridConstraint() == SNAP_BY_GRID )
2752 {
2753 if( grid.GetUseGrid() )
2754 {
2755 POLYGON_EDGE_DRAG_POLICY* dragPolicy =
2756 line ? line->GetDragPolicy() : nullptr;
2757
2758 bool snappedAlongPerp = false;
2759
2760 if( dragPolicy )
2761 {
2762 // For a polygon edge, the line moves only perpendicular to itself.
2763 // Snapping pos.x and pos.y independently to the axis-aligned grid
2764 // produces inconsistent perpendicular displacements when the edge is
2765 // tilted (different magnitudes depending on which axis crossed the
2766 // half-grid threshold first), causing the rendered edge to flicker
2767 // between two positions. Quantize the perpendicular displacement
2768 // directly so each grid step produces one stable line position.
2769 const VECTOR2I& origCenter = dragPolicy->GetOriginalCenter();
2770 const VECTOR2I& perpVec = dragPolicy->GetPerpVector();
2771 double perpLen = VECTOR2D( perpVec ).EuclideanNorm();
2772
2773 if( perpLen > 0 )
2774 {
2775 VECTOR2D perpUnit = VECTOR2D( perpVec ) / perpLen;
2776 VECTOR2D gridSize = grid.GetGridSize( grid.GetItemGrid( item ) );
2777
2778 // Effective grid spacing along the perpendicular direction. For an
2779 // axis-aligned edge this reduces to the grid pitch on that axis.
2780 double step = std::hypot( gridSize.x * perpUnit.x,
2781 gridSize.y * perpUnit.y );
2782
2783 if( step > 0 )
2784 {
2785 double offset = VECTOR2D( pos - origCenter ).Dot( perpUnit );
2786 double snapped = std::round( offset / step ) * step;
2787 VECTOR2D snappedPt = VECTOR2D( origCenter ) + perpUnit * snapped;
2788 pos = VECTOR2I( KiROUND( snappedPt.x ), KiROUND( snappedPt.y ) );
2789 snappedAlongPerp = true;
2790 }
2791 }
2792 }
2793
2794 if( !snappedAlongPerp )
2795 {
2796 VECTOR2I gridPt =
2797 grid.ResolveSnap( pos, {}, grid.GetItemGrid( item ), { item } )
2798 .position;
2799
2800 VECTOR2I last = m_editedPoint->GetPosition();
2801 VECTOR2I delta = pos - last;
2802 VECTOR2I deltaGrid =
2803 gridPt
2804 - grid.ResolveSnap( last, {}, grid.GetItemGrid( item ), { item } )
2805 .position;
2806
2807 if( abs( delta.x ) > grid.GetGrid().x / 2 )
2808 pos.x = last.x + deltaGrid.x;
2809 else
2810 pos.x = last.x;
2811
2812 if( abs( delta.y ) > grid.GetGrid().y / 2 )
2813 pos.y = last.y + deltaGrid.y;
2814 else
2815 pos.y = last.y;
2816 }
2817 }
2818 }
2819
2820 if( m_angleSnapActive )
2821 {
2822 m_stickyDisplacement = evt->Position() - m_angleSnapPos;
2823 int stickyLimit = KiROUND( getView()->ToWorld( 5 ) );
2824
2825 if( m_stickyDisplacement.EuclideanNorm() > stickyLimit || evt->Modifier( MD_SHIFT ) )
2826 {
2827 m_angleSnapActive = false;
2828 }
2829 else
2830 {
2831 pos = m_angleSnapPos;
2832 }
2833 }
2834
2835 bool isFreePolygon =
2836 item->Type() == PCB_ZONE_T
2837 || ( item->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY );
2838
2839 if( isFreePolygon && !m_angleSnapActive && m_editPoints->PointsSize() > 2 && !evt->Modifier( MD_SHIFT ) )
2840 {
2841 int idx = getEditedPointIndex();
2842
2843 if( idx != wxNOT_FOUND )
2844 {
2845 int prevIdx = ( idx + m_editPoints->PointsSize() - 1 ) % m_editPoints->PointsSize();
2846 int nextIdx = ( idx + 1 ) % m_editPoints->PointsSize();
2847 VECTOR2I prev = m_editPoints->Point( prevIdx ).GetPosition();
2848 VECTOR2I next = m_editPoints->Point( nextIdx ).GetPosition();
2849 SEG segA( pos, prev );
2850 SEG segB( pos, next );
2851 double ang = segA.Angle( segB ).AsDegrees();
2852 double snapAng = 45.0 * std::round( ang / 45.0 );
2853
2854 if( std::abs( ang - snapAng ) < 2.0 )
2855 {
2856 VECTOR2I snapped = snapCorner( prev, next, pos, snapAng );
2857
2858 if( m_editedPoint->GetGridConstraint() == SNAP_TO_GRID && grid.GetSnap() )
2859 {
2860 VECTOR2I gridded =
2861 grid.ResolveSnap( snapped, {}, grid.GetItemGrid( item ), { item } )
2862 .position;
2863 double griddedAng = SEG( gridded, prev ).Angle( SEG( gridded, next ) ).AsDegrees();
2864
2865 snapped = std::abs( griddedAng - snapAng ) < 2.0 ? gridded : pos;
2866 }
2867
2868 if( snapped != pos )
2869 {
2870 m_angleSnapPos = snapped;
2871 m_angleSnapActive = true;
2872 m_stickyDisplacement = evt->Position() - m_angleSnapPos;
2873 pos = m_angleSnapPos;
2874 }
2875 }
2876 }
2877 }
2878
2879 bool constraintSnapped = false;
2880 std::vector<VECTOR2I> stationarySelfPoints;
2881 std::vector<SEG> stationarySelfSegments;
2882
2883 if( item->Type() == PCB_ZONE_T
2884 || ( item->Type() == PCB_SHAPE_T
2885 && static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY ) )
2886 {
2887 const EDIT_LINE* editedLine = dynamic_cast<const EDIT_LINE*>( m_editedPoint );
2888
2889 for( unsigned i = 0; i < m_editPoints->PointsSize(); ++i )
2890 {
2891 const EDIT_POINT& point = m_editPoints->Point( i );
2892
2893 if( &point != m_editedPoint
2894 && ( !editedLine
2895 || ( &point != &editedLine->GetOrigin()
2896 && &point != &editedLine->GetEnd() ) ) )
2897 {
2898 stationarySelfPoints.push_back( point.GetPosition() );
2899 }
2900 }
2901
2902 for( unsigned i = 0; i < m_editPoints->LinesSize(); ++i )
2903 {
2904 const EDIT_LINE& stationaryLine = m_editPoints->Line( i );
2905
2906 if( &stationaryLine != editedLine
2907 && &stationaryLine.GetOrigin() != m_editedPoint
2908 && &stationaryLine.GetEnd() != m_editedPoint
2909 && ( !editedLine
2910 || ( &stationaryLine.GetOrigin() != &editedLine->GetOrigin()
2911 && &stationaryLine.GetOrigin() != &editedLine->GetEnd()
2912 && &stationaryLine.GetEnd() != &editedLine->GetOrigin()
2913 && &stationaryLine.GetEnd() != &editedLine->GetEnd() ) ) )
2914 {
2915 stationarySelfSegments.emplace_back(
2916 stationaryLine.GetOrigin().GetPosition(),
2917 stationaryLine.GetEnd().GetPosition() );
2918 }
2919 }
2920 }
2921 else if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
2922 {
2923 int editedIndex = getEditedPointIndex();
2924
2925 if( shape->GetShape() == SHAPE_T::RECTANGLE && m_editPoints->PointsSize() >= RECT_MAX_POINTS
2926 && editedIndex >= RECT_TOP_LEFT && editedIndex <= RECT_BOT_LEFT )
2927 {
2928 int opposite = ( editedIndex + 2 ) % 4;
2929 stationarySelfPoints.push_back( m_editPoints->Point( opposite ).GetPosition() );
2930 }
2931 else if( shape->GetShape() == SHAPE_T::RECTANGLE && m_editPoints->LinesSize() >= 4 )
2932 {
2933 for( unsigned i = 0; i < m_editPoints->LinesSize() && i < 4; ++i )
2934 {
2935 if( m_editedPoint != &m_editPoints->Line( i ) )
2936 continue;
2937
2938 const EDIT_LINE& opposite = m_editPoints->Line( ( i + 2 ) % 4 );
2939 stationarySelfPoints.push_back( opposite.GetOrigin().GetPosition() );
2940 stationarySelfPoints.push_back( opposite.GetEnd().GetPosition() );
2941 stationarySelfSegments.emplace_back( opposite.GetOrigin().GetPosition(),
2942 opposite.GetEnd().GetPosition() );
2943 break;
2944 }
2945 }
2946 else if( shape->GetShape() == SHAPE_T::ARC )
2947 {
2948 constexpr int arcStart = 0;
2949 constexpr int arcMid = 1;
2950 constexpr int arcEnd = 2;
2951 constexpr int arcCenter = 3;
2952
2954 {
2955 if( editedIndex != arcStart )
2956 stationarySelfPoints.push_back( m_editPoints->Point( arcStart ).GetPosition() );
2957
2958 if( editedIndex != arcEnd )
2959 stationarySelfPoints.push_back( m_editPoints->Point( arcEnd ).GetPosition() );
2960 }
2961 else if( editedIndex == arcStart || editedIndex == arcMid || editedIndex == arcEnd )
2962 {
2963 stationarySelfPoints.push_back( m_editPoints->Point( arcCenter ).GetPosition() );
2964 }
2965 }
2966 }
2967
2968 grid.SetStationarySelfGeometry( std::move( stationarySelfPoints ),
2969 std::move( stationarySelfSegments ) );
2970
2971 // Apply 45 degree or other constraints
2973 {
2974 m_editedPoint->SetPosition(
2975 grid.ResolveSnap( pos, snapLayers, grid.GetItemGrid( item ), { item } )
2976 .position );
2977 constraintSnapped = true;
2978 }
2979 else if( !m_angleSnapActive && m_editedPoint->IsConstrained() )
2980 {
2981 m_editedPoint->SetPosition( pos );
2982 m_editedPoint->ApplyRelation( grid );
2983 constraintSnapped = true;
2984
2985 // For constrained lines (like zone edges), try to snap to nearby anchors
2986 // that lie on the constraint line. First get the constrained position, then
2987 // look for snap anchors and verify they're on the constraint line.
2988 if( grid.GetSnap() && !snapLayers.empty() )
2989 {
2990 VECTOR2I constrainedPos = m_editedPoint->GetPosition();
2991 VECTOR2I snapPos =
2992 grid.ResolveSnap( constrainedPos, snapLayers, grid.GetItemGrid( item ),
2993 { item } )
2994 .position;
2995
2996 // Require the relation to preserve the discrete anchor exactly.
2997 if( snapPos != constrainedPos )
2998 {
2999 m_editedPoint->SetPosition( snapPos );
3000 m_editedPoint->ApplyRelation( grid );
3001 VECTOR2I projectedPos = m_editedPoint->GetPosition();
3002
3003 if( projectedPos != snapPos )
3004 m_editedPoint->SetPosition( constrainedPos );
3005 }
3006 }
3007 }
3008 else if( !m_angleSnapActive && m_editedPoint->GetGridConstraint() == SNAP_TO_GRID )
3009 {
3010 m_editedPoint->SetPosition(
3011 grid.ResolveSnap( pos, snapLayers, grid.GetItemGrid( item ), { item } )
3012 .position );
3013 }
3014 else
3015 {
3016 m_editedPoint->SetPosition( pos );
3017 }
3018
3019 if( haveSnapLineDirections )
3020 {
3021 VECTOR2I snapOrigin = m_altConstraint ? m_altConstrainer.GetPosition() : m_original.GetPosition();
3022 grid.SetSnapLineOrigin( snapOrigin );
3023
3024 if( constraintSnapped )
3025 grid.SetSnapLineEnd( m_editedPoint->GetPosition() );
3026 else
3027 grid.SetSnapLineEnd( std::nullopt );
3028 }
3029
3030 updateItem( commit );
3031 getViewControls()->ForceCursorPosition( true, m_editedPoint->GetPosition() );
3032 updatePoints();
3033
3034 if( m_radiusHelper )
3035 {
3036 if( m_editPoints->PointsSize() > RECT_RADIUS
3037 && m_editedPoint == &m_editPoints->Point( RECT_RADIUS ) )
3038 {
3039 if( PCB_SHAPE* rect = dynamic_cast<PCB_SHAPE*>( item ) )
3040 {
3041 int radius = rect->GetCornerRadius();
3042 int offset = radius - M_SQRT1_2 * radius;
3043 VECTOR2I topLeft = rect->GetTopLeft();
3044 VECTOR2I botRight = rect->GetBotRight();
3045 VECTOR2I topRight( botRight.x, topLeft.y );
3046 VECTOR2I center( topRight.x - offset, topRight.y + offset );
3047 m_radiusHelper->Set( radius, center, VECTOR2I( 1, -1 ), editFrame->GetUserUnits() );
3048 }
3049 }
3050 else
3051 {
3052 m_radiusHelper->Hide();
3053 }
3054 }
3055
3056 getView()->Update( &m_preview );
3057 }
3058 else if( m_editedPoint && evt->Action() == TA_MOUSE_DOWN && evt->Buttons() == BUT_LEFT )
3059 {
3060 m_editedPoint->SetActive();
3061
3062 for( size_t ii = 0; ii < m_editPoints->PointsSize(); ++ii )
3063 {
3064 EDIT_POINT& point = m_editPoints->Point( ii );
3065
3066 if( &point != m_editedPoint )
3067 point.SetActive( false );
3068 }
3069
3070 getView()->Update( m_editPoints.get() );
3071
3072 if( m_angleItem )
3073 getView()->Update( m_angleItem.get() );
3074 }
3075 else if( inDrag && evt->IsMouseUp( BUT_LEFT ) )
3076 {
3077 if( m_editedPoint )
3078 {
3079 m_editedPoint->SetActive( false );
3080 getView()->Update( m_editPoints.get() );
3081
3082 if( m_angleItem )
3083 getView()->Update( m_angleItem.get() );
3084 }
3085
3086 if( m_radiusHelper )
3087 m_radiusHelper->Hide();
3088
3089 getView()->Update( &m_preview );
3090
3091 getViewControls()->SetAutoPan( false );
3092 setAltConstraint( false );
3093 updateSnapLineDirections();
3094
3095 if( m_editorBehavior )
3096 m_editorBehavior->FinalizeItem( *m_editPoints, commit );
3097
3098 if( item->Type() == PCB_GENERATOR_T )
3099 {
3100 PCB_GENERATOR* generator = static_cast<PCB_GENERATOR*>( item );
3101
3102 m_preview.FreeItems();
3103 m_radiusHelper = nullptr;
3104 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genFinishEdit, &commit, generator );
3105
3106 commit.Push( generator->GetCommitMessage() );
3107 }
3108 else if( item->Type() == PCB_TABLECELL_T )
3109 {
3110 commit.Push( _( "Resize Table Cells" ) );
3111 }
3112 else
3113 {
3114 commit.Push( _( "Move Point" ) );
3115 }
3116
3117 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
3118 {
3119 shape->ClearFlags( IS_MOVING );
3120 shape->UpdateHatching();
3121 }
3122
3123 inDrag = false;
3125 frame()->UndoRedoBlock( false );
3126 updateSnapLineDirections();
3127
3128 m_toolMgr->PostAction<EDA_ITEM*>( ACTIONS::reselectItem, item ); // FIXME: Needed for generators
3129 }
3130 else if( evt->IsCancelInteractive() || evt->IsActivate() )
3131 {
3132 if( inDrag ) // Restore the last change
3133 {
3134 if( item->Type() == PCB_GENERATOR_T )
3135 {
3136 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genCancelEdit, &commit,
3137 static_cast<PCB_GENERATOR*>( item ) );
3138 }
3139
3140 commit.Revert();
3141
3142 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
3143 {
3144 shape->ClearFlags( IS_MOVING );
3145 shape->UpdateHatching();
3146 }
3147
3148 inDrag = false;
3150 frame()->UndoRedoBlock( false );
3151 updateSnapLineDirections();
3152 }
3153
3154 // Only cancel point editor when activating a new tool
3155 // Otherwise, allow the points to persist when moving up the
3156 // tool stack
3157 if( evt->IsActivate() && !evt->IsMoveTool() )
3158 break;
3159 }
3160 else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
3161 {
3162 // Re-create the points for items which can have different behavior on different layers
3163 if( item->Type() == PCB_PAD_T && m_isFootprintEditor )
3164 {
3165 if( getView()->HasItem( m_editPoints.get() ) )
3166 getView()->Remove( m_editPoints.get() );
3167
3168 if( m_angleItem && getView()->HasItem( m_angleItem.get() ) )
3169 getView()->Remove( m_angleItem.get() );
3170
3171 m_editPoints = makePoints( item );
3172
3173 if( m_angleItem )
3174 {
3175 m_angleItem->SetEditPoints( m_editPoints );
3176 getView()->Add( m_angleItem.get() );
3177 }
3178
3179 getView()->Add( m_editPoints.get() );
3180 }
3181 }
3182 else if( evt->Action() == TA_UNDO_REDO_POST )
3183 {
3184 break;
3185 }
3186 else
3187 {
3188 evt->SetPassEvent();
3189 }
3190 }
3191
3192 // IS_MOVING is only still set if the loop broke mid-drag, and a null m_editPoints means
3193 // Reset() ran while we were suspended, so the board and item have already been destroyed
3194 if( inDrag && m_editPoints )
3195 {
3196 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( m_editPoints->GetParent() ) )
3197 {
3198 shape->ClearFlags( IS_MOVING );
3199 shape->UpdateHatching();
3200 }
3201 }
3202
3203 m_preview.FreeItems();
3204 m_radiusHelper = nullptr;
3206
3207 if( getView()->HasItem( &m_preview ) )
3208 getView()->Remove( &m_preview );
3209
3210 if( m_editPoints )
3211 {
3212 if( getView()->HasItem( m_editPoints.get() ) )
3213 getView()->Remove( m_editPoints.get() );
3214
3215 if( m_angleItem && getView()->HasItem( m_angleItem.get() ) )
3216 getView()->Remove( m_angleItem.get() );
3217
3218 m_editPoints.reset();
3219 m_angleItem.reset();
3220 }
3221
3222 m_editedPoint = nullptr;
3223 grid.SetSnapLineDirections( {} );
3224
3225 return 0;
3226}
3227
3228
3230{
3231 if( !m_editPoints || !m_editPoints->GetParent() || !HasPoint() )
3232 return 0;
3233
3235
3236 BOARD_COMMIT commit( editFrame );
3237 commit.Stage( m_editPoints->GetParent(), CHT_MODIFY );
3238
3239 VECTOR2I pt = m_editedPoint->GetPosition();
3240 wxString title;
3241 wxString msg;
3242
3243 if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
3244 {
3245 title = _( "Move Midpoint to Location" );
3246 msg = _( "Move Midpoint" );
3247 }
3248 else
3249 {
3250 title = _( "Move Corner to Location" );
3251 msg = _( "Move Corner" );
3252 }
3253
3254 WX_PT_ENTRY_DIALOG dlg( editFrame, title, _( "X:" ), _( "Y:" ), pt, false );
3255
3256 if( dlg.ShowModal() == wxID_OK )
3257 {
3258 m_editedPoint->SetPosition( dlg.GetValue() );
3259 updateItem( commit );
3260 commit.Push( msg );
3261 }
3262
3263 return 0;
3264}
3265
3266
3268{
3269 wxCHECK( m_editPoints, /* void */ );
3270 EDA_ITEM* item = m_editPoints->GetParent();
3271
3272 if( !item )
3273 return;
3274
3275 // item is always updated
3276 std::vector<EDA_ITEM*> updatedItems = { item };
3277 aCommit.Modify( item );
3278
3279 if( m_editorBehavior )
3280 {
3281 wxCHECK( m_editedPoint, /* void */ );
3282 m_editorBehavior->UpdateItem( *m_editedPoint, *m_editPoints, aCommit, updatedItems );
3283 }
3284
3285 // Re-derive any geometry constrained to the dragged segment endpoint (issue #2329). The
3286 // behavior above has already moved the dragged point to the cursor, so its current endpoint
3287 // position is the pin target for the solver.
3288 auto anyConstraints =
3289 [&]() -> bool
3290 {
3291 if( !board() )
3292 return false;
3293
3294 if( !board()->Constraints().empty() )
3295 return true;
3296
3297 // In the footprint editor the constraints live on the footprint, not the board.
3298 for( FOOTPRINT* fp : board()->Footprints() )
3299 {
3300 if( !fp->Constraints().empty() )
3301 return true;
3302 }
3303
3304 return false;
3305 };
3306
3307 if( auto* groupBehavior = dynamic_cast<SHAPE_GROUP_POINT_EDIT_BEHAVIOR*>( m_editorBehavior.get() );
3308 groupBehavior && anyConstraints() )
3309 {
3310 // A group edit writes its own shapes, so the neighbours still need re-solving
3311 std::vector<PCB_SHAPE*> modified;
3312
3313 ReSolveShapeClustersHoldingEdited( board(), groupBehavior->GetShapes(), &modified,
3314 [&]( BOARD_ITEM* aNeighbor )
3315 {
3316 aCommit.Modify( aNeighbor );
3317 } );
3318
3319 for( PCB_SHAPE* neighbor : modified )
3320 updatedItems.push_back( neighbor );
3321 }
3322 else if( item->Type() == PCB_SHAPE_T && m_editedPoint && anyConstraints() )
3323 {
3324 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3325 SHAPE_T type = shape->GetShape();
3326 VECTOR2I cursor = m_editedPoint->GetPosition();
3327
3328 std::optional<CONSTRAINT_MEMBER> member;
3329 std::optional<std::pair<CONSTRAINT_MEMBER, VECTOR2I>> coDragged;
3330
3331 // Match dragged point to a constraint anchor segment bezier and arc expose endpoints
3332 // arc and circle also expose a centre bezier has none so centre test stays arc only
3333 if( type == SHAPE_T::SEGMENT || type == SHAPE_T::ARC || type == SHAPE_T::BEZIER )
3334 {
3335 if( cursor == shape->GetStart() )
3337 else if( cursor == shape->GetEnd() )
3339 else if( type == SHAPE_T::ARC && cursor == shape->GetCenter() )
3341 }
3342 else if( type == SHAPE_T::CIRCLE )
3343 {
3344 if( cursor == shape->GetCenter() )
3346 }
3347 else if( type == SHAPE_T::RECTANGLE && m_editPoints->PointsSize() >= RECT_MAX_POINTS )
3348 {
3349 // Only corner handles map to vertex anchors centre radius and side handles reshape whole rect
3350 // corners kept in min max order so ordinal equals vertex index solve target reads post clamp position
3351 for( unsigned i = RECT_TOP_LEFT; i <= RECT_BOT_LEFT; ++i )
3352 {
3353 if( isModified( m_editPoints->Point( i ) ) )
3354 {
3355 if( std::optional<CONSTRAINT_ANCHOR_POINT> corner = ConstraintShapeVertex( shape, (int) i ) )
3356 {
3357 member = CONSTRAINT_MEMBER( shape->m_Uuid, corner->anchor, corner->index );
3358 cursor = corner->pos;
3359 }
3360
3361 break;
3362 }
3363 }
3364
3365 // Side handle drags one edge param aliased by both corners side i runs corner i to i plus 1 mod 4
3366 // pinning corner i covers dragged plus one perpendicular param opposite corner hold covers the rest
3367 if( !member.has_value() )
3368 {
3369 for( unsigned i = 0; i < m_editPoints->LinesSize() && i < 4; ++i )
3370 {
3371 if( isModified( m_editPoints->Line( i ) ) )
3372 {
3373 if( std::optional<CONSTRAINT_ANCHOR_POINT> corner = ConstraintShapeVertex( shape, (int) i ) )
3374 {
3375 member = CONSTRAINT_MEMBER( shape->m_Uuid, corner->anchor, corner->index );
3376 cursor = corner->pos;
3377 }
3378
3379 break;
3380 }
3381 }
3382 }
3383 }
3384 else if( type == SHAPE_T::POLY && ConstraintPolygonIsModelable( shape ) )
3385 {
3386 // Editor builds one edit point per outline vertex in order so dragged ordinal is vertex index
3387 // holds even when a drag lands a vertex on top of another where position match would be ambiguous
3388 for( unsigned i = 0; i < m_editPoints->PointsSize(); ++i )
3389 {
3390 if( isModified( m_editPoints->Point( i ) ) )
3391 {
3392 if( std::optional<CONSTRAINT_ANCHOR_POINT> vertex = ConstraintShapeVertex( shape, (int) i ) )
3393 {
3394 member = CONSTRAINT_MEMBER( shape->m_Uuid, vertex->anchor, vertex->index );
3395 cursor = vertex->pos;
3396 }
3397
3398 break;
3399 }
3400 }
3401
3402 // Edge handle moves two adjacent vertices one member cannot express both so second vertex
3403 // rides as a co dragged pin line i runs vertex i to i plus 1 mod count positions read back post move
3404 if( !member.has_value() )
3405 {
3406 for( unsigned i = 0; i < m_editPoints->LinesSize(); ++i )
3407 {
3408 if( isModified( m_editPoints->Line( i ) ) )
3409 {
3410 int next = ( (int) i + 1 ) % (int) m_editPoints->PointsSize();
3411
3412 std::optional<CONSTRAINT_ANCHOR_POINT> v0 = ConstraintShapeVertex( shape, (int) i );
3413 std::optional<CONSTRAINT_ANCHOR_POINT> v1 = ConstraintShapeVertex( shape, next );
3414
3415 if( v0 && v1 )
3416 {
3417 member = CONSTRAINT_MEMBER( shape->m_Uuid, v0->anchor, v0->index );
3418 cursor = v0->pos;
3419 coDragged = { CONSTRAINT_MEMBER( shape->m_Uuid, v1->anchor, v1->index ), v1->pos };
3420 }
3421
3422 break;
3423 }
3424 }
3425 }
3426 }
3427
3428 bool isCurve = type == SHAPE_T::CIRCLE || type == SHAPE_T::ARC || type == SHAPE_T::ELLIPSE
3429 || type == SHAPE_T::ELLIPSE_ARC;
3430
3431 std::vector<PCB_SHAPE*> modified;
3432
3433 // Failed or diverged solve leaves neighbors untouched below so nothing is half moved this frame
3434 // moved shapes report in modified moved dimensions do not so refresh view here or they freeze
3435 auto stageNeighbor = [&]( BOARD_ITEM* aItem )
3436 {
3437 aCommit.Modify( aItem );
3438
3439 if( aItem->Type() != PCB_SHAPE_T )
3440 updatedItems.push_back( aItem );
3441 };
3442
3443 if( member.has_value() )
3444 {
3445 if( !m_constraintDragSession || !m_constraintDragSession->Matches( *member ) )
3446 {
3447 m_constraintDragSession = std::make_shared<BOARD_CONSTRAINT_DRAG_SESSION>();
3448
3449 if( !m_constraintDragSession->Build( board(), *member ) )
3451 }
3452
3454 {
3455 m_constraintDragSession->Solve( cursor, &modified, stageNeighbor,
3456 /* aIncludeDragged */ false,
3457 /* aStabilize */ false, {}, coDragged );
3458 }
3459 else
3460 {
3461 SolveCluster( board(), member.value(), cursor, &modified, stageNeighbor,
3462 /* aIncludeDragged */ false, /* aStabilize */ false, {}, coDragged );
3463 }
3464 }
3465 else if( isCurve )
3466 {
3467 // Radius or axis handle not a constrained point resize hold keeps new radius but yields
3468 // to a real radius constraint full dof hold below would fight it so curves use this instead
3469 ReSolveAfterShapeResize( board(), shape, &modified, stageNeighbor );
3470 }
3471 else if( type == SHAPE_T::RECTANGLE || ( type == SHAPE_T::POLY && ConstraintPolygonIsModelable( shape ) ) )
3472 {
3473 // Only whole shape handles centre and corner radius grips land here side corner and edge
3474 // drags pinned members above already treated like a properties edit new geometry wins neighbors follow
3475 ReSolveShapeClustersHoldingEdited( board(), { shape }, &modified, stageNeighbor );
3476 }
3477
3478 for( PCB_SHAPE* neighbor : modified )
3479 updatedItems.push_back( neighbor );
3480 }
3481
3482 // Perform any post-edit actions that the item may require
3483
3484 switch( item->Type() )
3485 {
3486 case PCB_TEXTBOX_T:
3487 case PCB_SHAPE_T:
3488 {
3489 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3490
3491 if( shape->IsProxyItem() )
3492 {
3493 for( PAD* pad : shape->GetParentFootprint()->Pads() )
3494 {
3495 if( pad->IsEntered() )
3496 view()->Update( pad );
3497 }
3498 }
3499
3500 // Nuke outline font render caches
3501 if( PCB_TEXTBOX* textBox = dynamic_cast<PCB_TEXTBOX*>( item ) )
3502 textBox->ClearRenderCache();
3503
3504 break;
3505 }
3506 case PCB_GENERATOR_T:
3507 {
3508 GENERATOR_TOOL* generatorTool = m_toolMgr->GetTool<GENERATOR_TOOL>();
3509 PCB_GENERATOR* generatorItem = static_cast<PCB_GENERATOR*>( item );
3510
3511 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genUpdateEdit, &aCommit, generatorItem );
3512
3513 // Note: POINT_EDITOR::m_preview holds only the canvas-draw status "popup"; the meanders
3514 // themselves (ROUTER_PREVIEW_ITEMs) are owned by the router.
3515
3516 m_preview.FreeItems();
3517 m_radiusHelper = nullptr;
3518
3519 for( EDA_ITEM* previewItem : generatorItem->GetPreviewItems( generatorTool, frame(), STATUS_ITEMS_ONLY ) )
3520 m_preview.Add( previewItem );
3521
3522 getView()->Update( &m_preview );
3523 break;
3524 }
3525 default:
3526 break;
3527 }
3528
3529 // Update the item and any affected items
3530 for( EDA_ITEM* updatedItem : updatedItems )
3531 getView()->Update( updatedItem );
3532
3533 frame()->SetMsgPanel( item );
3534}
3535
3536
3538{
3539 if( !m_editPoints )
3540 return;
3541
3542 EDA_ITEM* item = m_editPoints->GetParent();
3543
3544 if( !item )
3545 return;
3546
3547 if( !m_editorBehavior )
3548 return;
3549
3550 int editedIndex = -1;
3551 bool editingLine = false;
3552
3553 if( m_editedPoint )
3554 {
3555 // Check if we're editing a point (vertex)
3556 for( unsigned ii = 0; ii < m_editPoints->PointsSize(); ++ii )
3557 {
3558 if( &m_editPoints->Point( ii ) == m_editedPoint )
3559 {
3560 editedIndex = ii;
3561 break;
3562 }
3563 }
3564
3565 // If not found in points, check if we're editing a line (midpoint)
3566 if( editedIndex == -1 )
3567 {
3568 for( unsigned ii = 0; ii < m_editPoints->LinesSize(); ++ii )
3569 {
3570 if( &m_editPoints->Line( ii ) == m_editedPoint )
3571 {
3572 editedIndex = ii;
3573 editingLine = true;
3574 break;
3575 }
3576 }
3577 }
3578 }
3579
3580 if( !m_editorBehavior->UpdatePoints( *m_editPoints ) )
3581 {
3582 if( getView()->HasItem( m_editPoints.get() ) )
3583 getView()->Remove( m_editPoints.get() );
3584
3585 m_editPoints = makePoints( item );
3586 getView()->Add( m_editPoints.get() );
3587 }
3588
3589 if( editedIndex >= 0 )
3590 {
3591 if( editingLine && editedIndex < (int) m_editPoints->LinesSize() )
3592 m_editedPoint = &m_editPoints->Line( editedIndex );
3593 else if( !editingLine && editedIndex < (int) m_editPoints->PointsSize() )
3594 m_editedPoint = &m_editPoints->Point( editedIndex );
3595 else
3596 m_editedPoint = nullptr;
3597 }
3598 else
3599 {
3600 m_editedPoint = nullptr;
3601 }
3602
3603 getView()->Update( m_editPoints.get() );
3604
3605 if( m_angleItem )
3606 getView()->Update( m_angleItem.get() );
3607}
3608
3609
3611{
3613
3614 if( aPoint )
3615 {
3616 frame()->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
3617 controls->ForceCursorPosition( true, aPoint->GetPosition() );
3618 controls->ShowCursor( true );
3619 }
3620 else
3621 {
3622 if( frame()->ToolStackIsEmpty() )
3623 controls->ShowCursor( false );
3624
3625 controls->ForceCursorPosition( false );
3626 }
3627
3628 m_editedPoint = aPoint;
3629}
3630
3631
3633{
3634 EDA_ITEM* parent = m_editPoints ? m_editPoints->GetParent() : nullptr;
3635 EDIT_LINE* line = dynamic_cast<EDIT_LINE*>( m_editedPoint );
3636 bool isPoly = false;
3637
3638 if( parent )
3639 {
3640 switch( parent->Type() )
3641 {
3642 case PCB_ZONE_T:
3643 isPoly = true;
3644 break;
3645
3646 case PCB_SHAPE_T:
3647 isPoly = static_cast<PCB_SHAPE*>( parent )->GetShape() == SHAPE_T::POLY;
3648 break;
3649
3650 default:
3651 break;
3652 }
3653 }
3654
3655 if( aEnabled )
3656 {
3657 if( line && isPoly )
3658 {
3659 // For polygon lines, toggle the mode on the existing constraint rather than
3660 // creating a new one. This preserves the original reference positions.
3661 POLYGON_EDGE_DRAG_POLICY* policy = line->GetDragPolicy();
3662
3663 if( policy )
3665
3666 // Don't set m_altConstraint - we're modifying the line's own constraint
3667 }
3668 else
3669 {
3670 // Find a proper constraining point for angle snapping mode
3672
3673 if( Is90Limited() )
3675 else
3677 }
3678 }
3679 else
3680 {
3681 if( line && isPoly )
3682 {
3683 // Restore the line's constraint to CONVERGING mode
3684 POLYGON_EDGE_DRAG_POLICY* policy = line->GetDragPolicy();
3685
3686 if( policy )
3688 }
3689
3690 m_altConstraint.reset();
3691 }
3692}
3693
3694
3696{
3697 // If there's a behaviour and it provides a constrainer, use that
3698 if( m_editorBehavior )
3699 {
3700 const OPT_VECTOR2I constrainer = m_editorBehavior->Get45DegreeConstrainer( *m_editedPoint, *m_editPoints );
3701
3702 if( constrainer )
3703 return EDIT_POINT( *constrainer );
3704 }
3705
3706 // In any other case we may align item to its original position
3707 return m_original;
3708}
3709
3710
3711// Finds a corresponding vertex in a polygon set
3712static std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX> findVertex( SHAPE_POLY_SET& aPolySet, const EDIT_POINT& aPoint )
3713{
3714 for( auto it = aPolySet.IterateWithHoles(); it; ++it )
3715 {
3716 auto vertexIdx = it.GetIndex();
3717
3718 if( aPolySet.CVertex( vertexIdx ) == aPoint.GetPosition() )
3719 return std::make_pair( true, vertexIdx );
3720 }
3721
3722 return std::make_pair( false, SHAPE_POLY_SET::VERTEX_INDEX() );
3723}
3724
3725
3727{
3728 if( !m_editPoints || !m_editedPoint )
3729 return false;
3730
3731 EDA_ITEM* item = m_editPoints->GetParent();
3732 SHAPE_POLY_SET* polyset = nullptr;
3733
3734 if( !item )
3735 return false;
3736
3737 switch( item->Type() )
3738 {
3739 case PCB_ZONE_T:
3740 polyset = static_cast<ZONE*>( item )->Outline();
3741 break;
3742
3743 case PCB_SHAPE_T:
3744 if( static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY )
3745 polyset = &static_cast<PCB_SHAPE*>( item )->GetPolyShape();
3746 else
3747 return false;
3748
3749 break;
3750
3751 default:
3752 return false;
3753 }
3754
3755 std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX> vertex = findVertex( *polyset, *m_editedPoint );
3756
3757 if( !vertex.first )
3758 return false;
3759
3760 const SHAPE_POLY_SET::VERTEX_INDEX& vertexIdx = vertex.second;
3761
3762 // Check if there are enough vertices so one can be removed without
3763 // degenerating the polygon.
3764 // The first condition allows one to remove all corners from holes (when
3765 // there are only 2 vertices left, a hole is removed).
3766 if( vertexIdx.m_contour == 0
3767 && polyset->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour].PointCount() <= 3 )
3768 {
3769 return false;
3770 }
3771
3772 // Remove corner does not work with lines
3773 if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
3774 return false;
3775
3776 return m_editedPoint != nullptr;
3777}
3778
3779
3781{
3782 if( !m_editPoints )
3783 return 0;
3784
3785 EDA_ITEM* item = m_editPoints->GetParent();
3787 const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
3788
3789 // called without an active edited polygon
3790 if( !item || !CanAddCorner( *item ) )
3791 return 0;
3792
3793 PCB_SHAPE* graphicItem = dynamic_cast<PCB_SHAPE*>( item );
3794 BOARD_COMMIT commit( frame );
3795
3796 if( item->Type() == PCB_ZONE_T || ( graphicItem && graphicItem->GetShape() == SHAPE_T::POLY ) )
3797 {
3798 unsigned int nearestIdx = 0;
3799 unsigned int nextNearestIdx = 0;
3800 unsigned int nearestDist = INT_MAX;
3801 unsigned int firstPointInContour = 0;
3802 SHAPE_POLY_SET* zoneOutline;
3803
3804 if( item->Type() == PCB_ZONE_T )
3805 {
3806 ZONE* zone = static_cast<ZONE*>( item );
3807 zoneOutline = zone->Outline();
3808 zone->SetNeedRefill( true );
3809 }
3810 else
3811 {
3812 zoneOutline = &( graphicItem->GetPolyShape() );
3813 }
3814
3815 commit.Modify( item );
3816
3817 // Search the best outline segment to add a new corner
3818 // and therefore break this segment into two segments
3819
3820 // Object to iterate through the corners of the outlines (main contour and its holes)
3821 SHAPE_POLY_SET::ITERATOR iterator = zoneOutline->Iterate( 0, zoneOutline->OutlineCount()-1,
3822 /* IterateHoles */ true );
3823 int curr_idx = 0;
3824
3825 // Iterate through all the corners of the outlines and search the best segment
3826 for( ; iterator; iterator++, curr_idx++ )
3827 {
3828 int jj = curr_idx+1;
3829
3830 if( iterator.IsEndContour() )
3831 { // We reach the last point of the current contour (main or hole)
3832 jj = firstPointInContour;
3833 firstPointInContour = curr_idx+1; // Prepare next contour analysis
3834 }
3835
3836 SEG curr_segment( zoneOutline->CVertex( curr_idx ), zoneOutline->CVertex( jj ) );
3837
3838 unsigned int distance = curr_segment.Distance( cursorPos );
3839
3840 if( distance < nearestDist )
3841 {
3842 nearestDist = distance;
3843 nearestIdx = curr_idx;
3844 nextNearestIdx = jj;
3845 }
3846 }
3847
3848 // Find the point on the closest segment
3849 const VECTOR2I& sideOrigin = zoneOutline->CVertex( nearestIdx );
3850 const VECTOR2I& sideEnd = zoneOutline->CVertex( nextNearestIdx );
3851 SEG nearestSide( sideOrigin, sideEnd );
3852 VECTOR2I nearestPoint = nearestSide.NearestPoint( cursorPos );
3853
3854 // Do not add points that have the same coordinates as ones that already belong to polygon
3855 // instead, add a point in the middle of the side
3856 if( nearestPoint == sideOrigin || nearestPoint == sideEnd )
3857 nearestPoint = ( sideOrigin + sideEnd ) / 2;
3858
3859 zoneOutline->InsertVertex( nextNearestIdx, nearestPoint );
3860
3861 // Zones cannot carry constraint members but shape polygons can insertion shifts ordinals at or
3862 // past new vertex issue 2329 members only exist on hole free polys index past outline count is a hole vertex
3863 if( graphicItem && nextNearestIdx < (unsigned) zoneOutline->COutline( 0 ).PointCount() )
3864 {
3865 RemapPolygonVertexMembers( frame->GetBoard(), graphicItem->m_Uuid, (int) nextNearestIdx, 1,
3866 [&]( BOARD_ITEM* aConstraint ) { commit.Modify( aConstraint ); },
3867 [&]( BOARD_ITEM* aConstraint ) { commit.Remove( aConstraint ); } );
3868 }
3869
3870 if( item->Type() == PCB_ZONE_T )
3871 static_cast<ZONE*>( item )->HatchBorder();
3872
3873 commit.Push( _( "Add Zone Corner" ) );
3874 }
3875 else if( graphicItem )
3876 {
3877 switch( graphicItem->GetShape() )
3878 {
3879 case SHAPE_T::SEGMENT:
3880 {
3881 commit.Modify( graphicItem );
3882
3883 SEG seg( graphicItem->GetStart(), graphicItem->GetEnd() );
3884 VECTOR2I nearestPoint = seg.NearestPoint( cursorPos );
3885
3886 // Move the end of the line to the break point..
3887 graphicItem->SetEnd( nearestPoint );
3888
3889 // and add another one starting from the break point
3890 PCB_SHAPE* newSegment = static_cast<PCB_SHAPE*>( graphicItem->Duplicate( true, &commit ) );
3891 newSegment->ClearSelected();
3892 newSegment->SetStart( nearestPoint );
3893 newSegment->SetEnd( VECTOR2I( seg.B.x, seg.B.y ) );
3894
3895 commit.Add( newSegment );
3896 commit.Push( _( "Split Segment" ) );
3897 break;
3898 }
3899 case SHAPE_T::ARC:
3900 {
3901 commit.Modify( graphicItem );
3902
3903 const SHAPE_ARC arc( graphicItem->GetStart(), graphicItem->GetArcMid(), graphicItem->GetEnd(), 0 );
3904 const VECTOR2I nearestPoint = arc.NearestPoint( cursorPos );
3905
3906 // Move the end of the arc to the break point..
3907 graphicItem->SetEnd( nearestPoint );
3908
3909 // and add another one starting from the break point
3910 PCB_SHAPE* newArc = static_cast<PCB_SHAPE*>( graphicItem->Duplicate( true, &commit ) );
3911
3912 newArc->ClearSelected();
3913 newArc->SetEnd( arc.GetP1() );
3914 newArc->SetStart( nearestPoint );
3915
3916 commit.Add( newArc );
3917 commit.Push( _( "Split Arc" ) );
3918 break;
3919 }
3920 default:
3921 // No split implemented for other shapes
3922 break;
3923 }
3924 }
3925
3926 updatePoints();
3927 return 0;
3928}
3929
3930
3932{
3933 if( !m_editPoints || !m_editedPoint )
3934 return 0;
3935
3936 EDA_ITEM* item = m_editPoints->GetParent();
3937
3938 if( !item )
3939 return 0;
3940
3941 SHAPE_POLY_SET* polygon = nullptr;
3942
3943 if( item->Type() == PCB_ZONE_T )
3944 {
3945 ZONE* zone = static_cast<ZONE*>( item );
3946 polygon = zone->Outline();
3947 zone->SetNeedRefill( true );
3948 }
3949 else if( item->Type() == PCB_SHAPE_T )
3950 {
3951 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3952
3953 if( shape->GetShape() == SHAPE_T::POLY )
3954 polygon = &shape->GetPolyShape();
3955 }
3956
3957 if( !polygon )
3958 return 0;
3959
3961 BOARD_COMMIT commit( frame );
3962 auto vertex = findVertex( *polygon, *m_editedPoint );
3963
3964 if( vertex.first )
3965 {
3966 const auto& vertexIdx = vertex.second;
3967 auto& outline = polygon->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour];
3968
3969 if( outline.PointCount() > 3 )
3970 {
3971 // the usual case: remove just the corner when there are >3 vertices
3972 commit.Modify( item );
3973 polygon->RemoveVertex( vertexIdx );
3974
3975 // Members only exist on hole free shape polygons where contour relative ordinal is member
3976 // index removed vertex member retires its constraint later ordinals shift down issue 2329
3977 if( item->Type() == PCB_SHAPE_T && vertexIdx.m_contour == 0 )
3978 {
3979 RemapPolygonVertexMembers( frame->GetBoard(), item->m_Uuid, vertexIdx.m_vertex, -1,
3980 [&]( BOARD_ITEM* aConstraint ) { commit.Modify( aConstraint ); },
3981 [&]( BOARD_ITEM* aConstraint ) { commit.Remove( aConstraint ); } );
3982 }
3983 }
3984 else
3985 {
3986 // either remove a hole or the polygon when there are <= 3 corners
3987 if( vertexIdx.m_contour > 0 )
3988 {
3989 // remove hole
3990 commit.Modify( item );
3991 polygon->RemoveContour( vertexIdx.m_contour );
3992 }
3993 else
3994 {
3995 m_toolMgr->RunAction( ACTIONS::selectionClear );
3996 commit.Remove( item );
3997 }
3998 }
3999
4000 setEditedPoint( nullptr );
4001
4002 if( item->Type() == PCB_ZONE_T )
4003 commit.Push( _( "Remove Zone Corner" ) );
4004 else
4005 commit.Push( _( "Remove Polygon Corner" ) );
4006
4007 if( item->Type() == PCB_ZONE_T )
4008 static_cast<ZONE*>( item )->HatchBorder();
4009
4010 updatePoints();
4011 }
4012
4013 return 0;
4014}
4015
4016
4018{
4019 if( !m_editPoints || !m_editedPoint )
4020 return 0;
4021
4022 EDA_ITEM* item = m_editPoints->GetParent();
4023
4024 if( !item )
4025 return 0;
4026
4027 SHAPE_POLY_SET* polygon = nullptr;
4028
4029 if( item->Type() == PCB_ZONE_T )
4030 {
4031 ZONE* zone = static_cast<ZONE*>( item );
4032 polygon = zone->Outline();
4033 zone->SetNeedRefill( true );
4034 }
4035 else if( item->Type() == PCB_SHAPE_T )
4036 {
4037 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
4038
4039 if( shape->GetShape() == SHAPE_T::POLY )
4040 polygon = &shape->GetPolyShape();
4041 }
4042
4043 if( !polygon )
4044 return 0;
4045
4046 // Search the best outline corner to break
4047
4049 BOARD_COMMIT commit( frame );
4050 const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
4051
4052 unsigned int nearestIdx = 0;
4053 unsigned int nearestDist = INT_MAX;
4054
4055 int curr_idx = 0;
4056 // Object to iterate through the corners of the outlines (main contour and its holes)
4057 SHAPE_POLY_SET::ITERATOR iterator = polygon->Iterate( 0, polygon->OutlineCount() - 1,
4058 /* IterateHoles */ true );
4059
4060 // Iterate through all the corners of the outlines and search the best segment
4061 for( ; iterator; iterator++, curr_idx++ )
4062 {
4063 unsigned int distance = polygon->CVertex( curr_idx ).Distance( cursorPos );
4064
4065 if( distance < nearestDist )
4066 {
4067 nearestDist = distance;
4068 nearestIdx = curr_idx;
4069 }
4070 }
4071
4072 int prevIdx, nextIdx;
4073 if( polygon->GetNeighbourIndexes( nearestIdx, &prevIdx, &nextIdx ) )
4074 {
4075 const SEG segA{ polygon->CVertex( prevIdx ), polygon->CVertex( nearestIdx ) };
4076 const SEG segB{ polygon->CVertex( nextIdx ), polygon->CVertex( nearestIdx ) };
4077
4078 // A plausible setback that won't consume a whole edge
4079 int setback = pcbIUScale.mmToIU( 5 );
4080 setback = std::min( setback, (int) ( segA.Length() * 0.25 ) );
4081 setback = std::min( setback, (int) ( segB.Length() * 0.25 ) );
4082
4083 CHAMFER_PARAMS chamferParams{ setback, setback };
4084
4085 std::optional<CHAMFER_RESULT> chamferResult = ComputeChamferPoints( segA, segB, chamferParams );
4086
4087 if( chamferResult && chamferResult->m_updated_seg_a && chamferResult->m_updated_seg_b )
4088 {
4089 commit.Modify( item );
4090 polygon->RemoveVertex( nearestIdx );
4091
4092 // The two end points of the chamfer are the new corners
4093 polygon->InsertVertex( nearestIdx, chamferResult->m_updated_seg_b->B );
4094 polygon->InsertVertex( nearestIdx, chamferResult->m_updated_seg_a->B );
4095
4096 // Chamfered corner constraints retire members past it net one higher insert pass must run
4097 // first threshold nearestIdx plus 1 deleting first nets negative one instead issue 2329
4098 if( item->Type() == PCB_SHAPE_T && nearestIdx < (unsigned) polygon->COutline( 0 ).PointCount() )
4099 {
4100 auto modify = [&]( BOARD_ITEM* aConstraint ) { commit.Modify( aConstraint ); };
4101 auto remove = [&]( BOARD_ITEM* aConstraint ) { commit.Remove( aConstraint ); };
4102
4103 RemapPolygonVertexMembers( frame->GetBoard(), item->m_Uuid, (int) nearestIdx + 1, 2, modify, remove );
4104 RemapPolygonVertexMembers( frame->GetBoard(), item->m_Uuid, (int) nearestIdx, -1, modify, remove );
4105 }
4106 }
4107 }
4108
4109 setEditedPoint( nullptr );
4110
4111 if( item->Type() == PCB_ZONE_T )
4112 commit.Push( _( "Break Zone Corner" ) );
4113 else
4114 commit.Push( _( "Break Polygon Corner" ) );
4115
4116 if( item->Type() == PCB_ZONE_T )
4117 static_cast<ZONE*>( item )->HatchBorder();
4118
4119 updatePoints();
4120
4121 return 0;
4122}
4123
4124
4126{
4127 updatePoints();
4128 return 0;
4129}
4130
4131
4133{
4135
4136 if( aEvent.Matches( ACTIONS::cycleArcEditMode.MakeEvent() ) )
4137 {
4138 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
4140 else
4142
4144 }
4145 else
4146 {
4148 }
4149
4150 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
4152 else
4154
4155 return 0;
4156}
4157
4158
4160{
4178}
BOX2I getBoundingBox(BOARD_ITEM *aItem)
ARC_EDIT_MODE
Settings for arc editing.
@ KEEP_ENDPOINTS_OR_START_DIRECTION
Whe editing endpoints, the other end remains in place.
@ KEEP_CENTER_ADJUST_ANGLE_RADIUS
When editing endpoints, the angle and radius are adjusted.
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
std::vector< VECTOR2I > ConstraintPinnedPositions(BOARD *aBoard, const std::set< KIID > &aItems)
The distinct board positions that fixed positions pin on aItems.
void ReSolveAfterShapeResize(BOARD *aBoard, PCB_SHAPE *aShape, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
Re-solve after a resize, e.g. a circle radius edit. Holds aShape fixed so its neighbors adjust.
CONSTRAINT_DIAGNOSIS SolveCluster(BOARD *aBoard, const CONSTRAINT_MEMBER &aDragged, const VECTOR2I &aCursor, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify, bool aIncludeDragged, bool aStabilize, const std::set< KIID > &aEdited, const std::optional< std::pair< CONSTRAINT_MEMBER, VECTOR2I > > &aCoDragged, const std::set< KIID > &aFixedShapes, bool aHoldDraggedRigid)
Gather the cluster of shapes transitively constrained with the dragged shape, solve with the dragged ...
bool ReSolveShapeClustersHoldingEdited(BOARD *aBoard, const std::vector< PCB_SHAPE * > &aEditedShapes, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
Re-solve clusters whose new geometry is authoritative holding every edited shape fully fixed so only ...
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
static TOOL_ACTION cycleArcEditMode
Definition actions.h:272
static TOOL_ACTION pointEditorArcKeepCenter
Definition actions.h:273
static TOOL_ACTION pointEditorArcKeepRadius
Definition actions.h:275
static TOOL_ACTION reselectItem
Definition actions.h:225
static TOOL_ACTION activatePointEditor
Definition actions.h:268
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION pointEditorArcKeepEndpoint
Definition actions.h:274
void updateOrthogonalDimension(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints)
Update orthogonal dimension points.
ALIGNED_DIMENSION_POINT_EDIT_BEHAVIOR(PCB_DIM_ALIGNED &aDimension)
void updateAlignedDimension(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints)
Update non-orthogonal dimension points.
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
OPT_VECTOR2I Get45DegreeConstrainer(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints) const override
Get the 45-degree constrainer for the item, when the given point is moved.
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
BARCODE_POINT_EDIT_BEHAVIOR(PCB_BARCODE &aBarcode)
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
COMMIT & Stage(EDA_ITEM *aItem, CHANGE_TYPE aChangeType, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE) override
Add a change of the item aItem of type aChangeType to the change list.
void Revert() override
Revert the commit by restoring the modified items state.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
virtual BOARD_ITEM * Duplicate(bool addToParentGroup, BOARD_COMMIT *aCommit=nullptr) const
Create a copy of this BOARD_ITEM.
bool IsLocked() const override
FOOTPRINT * GetParentFootprint() const
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition board_item.h:355
int GetMaxError() const
constexpr void SetMaximum()
Definition box2.h:77
constexpr const Vec GetEnd() const
Definition box2.h:209
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr Vec Centre() const
Definition box2.h:94
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr const Vec GetCenter() const
Definition box2.h:227
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:165
constexpr const Vec & GetOrigin() const
Definition box2.h:207
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition commit.h:68
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
int ShowModal() override
Class to help update the text position of a dimension when the crossbar changes.
DIM_ALIGNED_TEXT_UPDATER(PCB_DIM_ALIGNED &aDimension)
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
OPT_VECTOR2I Get45DegreeConstrainer(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints) const override
Get the 45-degree constrainer for the item, when the given point is moved.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
DIM_CENTER_POINT_EDIT_BEHAVIOR(PCB_DIM_CENTER &aDimension)
DIM_LEADER_POINT_EDIT_BEHAVIOR(PCB_DIM_LEADER &aDimension)
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
DIM_RADIAL_POINT_EDIT_BEHAVIOR(PCB_DIM_RADIAL &aDimension)
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
OPT_VECTOR2I Get45DegreeConstrainer(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints) const override
Get the 45-degree constrainer for the item, when the given point is moved.
double AsDegrees() const
Definition eda_angle.h:115
bool IsType(FRAME_T aType) const
bool GetOverrideLocks() const
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
const KIID m_Uuid
Definition eda_item.h:599
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
void ClearSelected()
Definition eda_item.h:153
virtual EDA_ITEM * Clone() const
Create a duplicate of this item with linked list members set to NULL.
Definition eda_item.cpp:278
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
EDA_ITEM(EDA_ITEM *parent, KICAD_T idType, bool isSCH_ITEM=false, bool isBOARD_ITEM=false)
Definition eda_item.cpp:84
virtual VECTOR2I GetTopLeft() const
Definition eda_shape.h:364
SHAPE_POLY_SET & GetPolyShape()
SHAPE_T GetShape() const
Definition eda_shape.h:177
virtual VECTOR2I GetBotRight() const
Definition eda_shape.h:365
virtual void SetBottom(int val)
Definition eda_shape.h:370
virtual void SetTop(int val)
Definition eda_shape.h:367
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:333
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:283
virtual void SetLeft(int val)
Definition eda_shape.h:368
virtual void SetRight(int val)
Definition eda_shape.h:369
int GetCornerRadius() const
VECTOR2I GetArcMid() const
Represent a line connecting two EDIT_POINTs.
POLYGON_EDGE_DRAG_POLICY * GetDragPolicy() const
void SetRelation(std::unique_ptr< EDIT_RELATION > aRelation)
Set a relation for an EDIT_LINE.
EDIT_POINT & GetEnd()
Return the end EDIT_POINT.
EDIT_POINT & GetOrigin()
Return the origin EDIT_POINT.
EDIT_POINTS is a VIEW_ITEM that manages EDIT_POINTs and EDIT_LINEs and draws them.
unsigned int PointsSize() const
Return number of stored EDIT_POINTs.
void AddPoint(const EDIT_POINT &aPoint)
Add an EDIT_POINT.
void SetSwapY(bool aSwap)
void Clear()
Clear all stored EDIT_POINTs and EDIT_LINEs.
EDIT_LINE & Line(unsigned int aIndex)
void AddIndicatorLine(EDIT_POINT &aOrigin, EDIT_POINT &aEnd)
Adds an EDIT_LINE that is shown as an indicator, rather than an editable line (no center point drag,...
bool SwapX() const
bool SwapY() const
void SetSwapX(bool aSwap)
unsigned int LinesSize() const
Return number of stored EDIT_LINEs.
EDIT_POINT & Point(unsigned int aIndex)
void AddLine(const EDIT_LINE &aLine)
Adds an EDIT_LINE.
Represent a single point that can be used for modifying items.
Definition edit_points.h:44
virtual void SetPosition(const VECTOR2I &aPosition)
Set new coordinates for an EDIT_POINT.
virtual VECTOR2I GetPosition() const
Return coordinates of an EDIT_POINT.
Definition edit_points.h:69
void SetSnapConstraint(SNAP_CONSTRAINT_TYPE aConstraint)
int GetX() const
Return X coordinate of an EDIT_POINT.
Definition edit_points.h:85
void SetRelation(std::unique_ptr< EDIT_RELATION > aRelation)
Set a relation for an EDIT_POINT.
void SetActive(bool aActive=true)
void SetDrawCircle(bool aDrawCircle=true)
static std::unique_ptr< EDIT_RELATION > Angle90(const EDIT_POINT &aReference)
static std::unique_ptr< EDIT_RELATION > Angle45(const EDIT_POINT &aReference)
static std::unique_ptr< EDIT_RELATION > PerpendicularTranslation(const EDIT_LINE &aLine)
const VECTOR2I & Direction() const
static std::unique_ptr< EDIT_RELATION > PointOnLine(const EDIT_POINT &aConstrained, const EDIT_POINT &aReference)
EDIT_RELATION_KIND Kind() const
static const TOOL_EVENT InhibitSelectionEditing
Used to inform tools that the selection should temporarily be non-editable.
Definition actions.h:359
static const TOOL_EVENT SelectedEvent
Definition actions.h:346
static const TOOL_EVENT SelectedItemsModified
Selected item had a property changed (except movement)
Definition actions.h:353
static const TOOL_EVENT UninhibitSelectionEditing
Definition actions.h:360
static const TOOL_EVENT PointSelectedEvent
Definition actions.h:345
static const TOOL_EVENT SelectedItemsMoved
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition actions.h:356
static const TOOL_EVENT UnselectedEvent
Definition actions.h:347
std::deque< PAD * > & Pads()
Definition footprint.h:355
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
GENERATOR_POINT_EDIT_BEHAVIOR(PCB_GENERATOR &aGenerator)
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
GENERATOR_POLY_POINT_EDIT_BEHAVIOR(PCB_GENERATOR_POLY &aGen)
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
Handle actions specific to filling copper zones.
VECTOR2I toLocal(const VECTOR2I &aWorld) const
GRID_POINT_EDIT_BEHAVIOR(PCB_GRID_ITEM &aGridItem)
VECTOR2I cornerLocal(int aX, int aY) const
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
VECTOR2I toWorld(const VECTOR2I &aLocal) const
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
virtual void Update(const VIEW_ITEM *aItem, int aUpdateFlags) const override
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
Definition pcb_view.cpp:87
An interface for classes handling user events controlling the view behavior such as zooming,...
virtual void ForceCursorPosition(bool aEnabled, const VECTOR2D &aPosition=VECTOR2D(0, 0))
Place the cursor immediately at a given point.
virtual void ShowCursor(bool aEnabled)
Enable or disables display of cursor.
VECTOR2D GetCursorPosition() const
Return the current cursor position in world coordinates.
virtual void SetAutoPan(bool aEnabled)
Turn on/off auto panning (this feature is used when there is a tool active (eg.
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition view.h:68
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1)
Add a VIEW_ITEM to the view.
Definition view.cpp:365
virtual void Remove(VIEW_ITEM *aItem)
Remove a VIEW_ITEM from the view.
Definition view.cpp:488
virtual void Update(const VIEW_ITEM *aItem, int aUpdateFlags) const
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
Definition view.cpp:2631
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllLayersMask()
Definition lset.cpp:637
static constexpr PCB_LAYER_ID TEMP_ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:176
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
PAD_POINT_EDIT_BEHAVIOR(PAD &aPad, PCB_LAYER_ID aLayer)
Definition pad.h:61
ARC_EDIT_MODE m_ArcEditMode
static TOOL_ACTION layerChanged
static TOOL_ACTION pointEditorMoveMidpoint
static TOOL_ACTION genFinishEdit
static TOOL_ACTION genStartEdit
static TOOL_ACTION pointEditorMoveCorner
static TOOL_ACTION genCancelEdit
static TOOL_ACTION genUpdateEdit
static TOOL_ACTION pointEditorChamferCorner
static TOOL_ACTION pointEditorRemoveCorner
static TOOL_ACTION pointEditorAddCorner
Common, abstract interface for edit frames.
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
PCBNEW_SETTINGS * GetPcbNewSettings() const
virtual MAGNETIC_SETTINGS * GetMagneticItemsSettings()
FOOTPRINT_EDITOR_SETTINGS * GetFootprintEditorSettings() const
For better understanding of the points that make a dimension:
Mark the center of a circle or arc with a cross shape.
A leader is a dimension-like object pointing to a specific point.
An orthogonal dimension is like an aligned dimension, but the extension lines are locked to the X or ...
void SetOrientation(DIR aOrientation)
Set the orientation of the dimension line (so, perpendicular to the feature lines).
DIR GetOrientation() const
A radial dimension indicates either the radius or diameter of an arc or circle.
virtual wxString GetCommitMessage() const =0
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
std::unordered_set< BOARD_ITEM * > GetBoardItems() const
int changeArcEditMode(const TOOL_EVENT &aEvent)
Change the edit method for arcs.
bool CanRemoveCorner(const SELECTION &aSelection)
Condition to display "Remove Corner" context menu entry.
void updateItem(BOARD_COMMIT &aCommit)
Update item's points with edit points.
VECTOR2I m_stickyDisplacement
int OnSelectionChange(const TOOL_EVENT &aEvent)
Change selection event handler.
void setAltConstraint(bool aEnabled)
Set up an alternative constraint (typically enabled upon a modifier key being pressed).
static const unsigned int COORDS_PADDING
EDIT_POINT * m_hoveredPoint
int addCorner(const TOOL_EVENT &aEvent)
bool HasPoint()
Indicate the cursor is over an edit point.
EDIT_POINT m_original
Original pos for the current drag point.
static bool CanChamferCorner(const EDA_ITEM &aItem)
Check if a corner of the given item can be chamfered (zones, polys only).
EDIT_POINT get45DegConstrainer() const
Return a point that should be used as a constrainer for 45 degrees mode.
int modifiedSelection(const TOOL_EVENT &aEvent)
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
std::unique_ptr< POINT_EDIT_BEHAVIOR > m_editorBehavior
int removeCorner(const TOOL_EVENT &aEvent)
bool Init() override
Init() is called once upon a registration of the tool.
std::shared_ptr< BOARD_CONSTRAINT_DRAG_SESSION > m_constraintDragSession
std::unique_ptr< EDIT_RELATION > m_altConstraint
RECT_RADIUS_TEXT_ITEM * m_radiusHelper
PCB_SELECTION m_preview
int movePoint(const TOOL_EVENT &aEvent)
TOOL_ACTION handlers.
static bool CanAddCorner(const EDA_ITEM &aItem)
Check if a corner can be added to the given item (zones, polys, segments, arcs).
void setEditedPoint(EDIT_POINT *aPoint)
Set the current point being edited. NULL means none.
EDIT_POINT m_altConstrainer
bool isModified(const EDIT_POINT &aPoint) const
Return true if aPoint is the currently modified point.
ARC_EDIT_MODE m_arcEditMode
std::unique_ptr< KIGFX::PREVIEW::ANGLE_ITEM > m_angleItem
EDIT_POINT * m_editedPoint
void updateEditedPoint(const TOOL_EVENT &aEvent)
Update which point is being edited.
int getEditedPointIndex() const
void updatePoints()
Update edit points with item's points.
int chamferCorner(const TOOL_EVENT &aEvent)
void setTransitions() override
Set up handlers for various events.
std::shared_ptr< EDIT_POINTS > m_editPoints
PCB_BASE_FRAME * m_frame
std::shared_ptr< EDIT_POINTS > makePoints(EDA_ITEM *aItem)
PCB_SELECTION_TOOL * m_selectionTool
Object to handle a bitmap image that can be inserted in a PCB.
REFERENCE_IMAGE & GetReferenceImage()
The selection tool: currently supports:
void SetCornerRadius(int aCornerRadius) override
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:80
void SetEnd(const VECTOR2I &aEnd) override
bool IsProxyItem() const override
Definition pcb_shape.h:155
void Move(const VECTOR2I &aMoveVector) override
Move this object.
void SetStart(const VECTOR2I &aStart) override
VECTOR2I unturned(const VECTOR2I &aPoint) const
VECTOR2I edgeMidpoint(TABLECELL_POINTS aPoint) const
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
PCB_TABLECELL_POINT_EDIT_BEHAVIOR(PCB_TABLECELL &aCell)
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
T * frame() const
KIGFX::PCB_VIEW * view() const
virtual bool Is45Limited() const
Should the tool use its 45° mode option?
KIGFX::VIEW_CONTROLS * controls() const
PCB_TOOL_BASE(TOOL_ID aId, const std::string &aName)
Constructor.
BOARD * board() const
virtual bool Is90Limited() const
Should the tool limit drawing to horizontal and vertical only?
const PCB_SELECTION & selection() const
A helper class interface to manage the edit points for a single item.
static bool isModified(const EDIT_POINT &aEditedPoint, const EDIT_POINT &aPoint)
Checks if two points are the same instance - which means the point is being edited.
void SetMode(POLYGON_LINE_MODE aMode)
POLYGON_LINE_MODE GetMode() const
const VECTOR2I & GetOriginalCenter() const
const VECTOR2I & GetPerpVector() const
POLYGON_POINT_EDIT_BEHAVIOR(SHAPE_POLY_SET &aPolygon)
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
static void UpdateItem(SCH_SHAPE &aRect, const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, const VECTOR2I &aMinSize={ 0, 0 })
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
static void UpdateItem(PCB_SHAPE &aRectangle, const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, const VECTOR2I &aMinSize={ 0, 0 })
static void UpdatePoints(SCH_SHAPE &aRect, EDIT_POINTS &aPoints)
static void PinEditedCorner(const EDIT_POINT &aEditedPoint, const EDIT_POINTS &aPoints, int minWidth, int minHeight, VECTOR2I &topLeft, VECTOR2I &topRight, VECTOR2I &botLeft, VECTOR2I &botRight)
Update the coordinates of 4 corners of a rectangle, according to constraints and the moved corner.
static void PinEditedCorner(const EDIT_POINT &aEditedPoint, const EDIT_POINTS &aEditPoints, VECTOR2I &aTopLeft, VECTOR2I &aTopRight, VECTOR2I &aBotLeft, VECTOR2I &aBotRight, const VECTOR2I &aHole={ 0, 0 }, const VECTOR2I &aHoleSize={ 0, 0 }, const VECTOR2I &aMinSize={ 0, 0 })
Update the coordinates of 4 corners of a rectangle, according to constraints and the moved corner.
static void MakePoints(SCH_SHAPE &aRect, EDIT_POINTS &aPoints)
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
static void MakePoints(const PCB_SHAPE &aRectangle, EDIT_POINTS &aPoints)
Standard rectangle points construction utility (other shapes may use this as well)
static void UpdatePoints(const PCB_SHAPE &aRectangle, EDIT_POINTS &aPoints)
RECTANGLE_POINT_EDIT_BEHAVIOR(PCB_SHAPE &aRectangle)
const EDA_IU_SCALE & m_iuScale
wxString GetClass() const override
Return the class name.
std::vector< int > ViewGetLayers() const override
Return the all the layers within the VIEW the object is painted on.
const BOX2I ViewBBox() const override
Return the bounding box of the item covering all its layers.
void Set(int aRadius, const VECTOR2I &aCorner, const VECTOR2I &aQuadrant, EDA_UNITS aUnits)
RECT_RADIUS_TEXT_ITEM(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits)
void ViewDraw(int aLayer, KIGFX::VIEW *aView) const override
Draw the parts of the object belonging to layer aLayer.
Definition seg.h:38
VECTOR2I B
Definition seg.h:46
const VECTOR2I NearestPoint(const VECTOR2I &aP) const
Compute a point on the segment (this) that is closest to point aP.
Definition seg.cpp:599
int Distance(const SEG &aSeg) const
Compute minimum Euclidean distance to segment aSeg.
Definition seg.cpp:668
EDA_ANGLE Angle(const SEG &aOther) const
Determine the smallest angle between two segments.
Definition seg.cpp:66
Class that groups generic conditions for selected items.
static SELECTION_CONDITION Count(int aNumber)
Create a functor that tests if the number of selected items is equal to the value given as parameter.
VECTOR2I NearestPoint(const VECTOR2I &aP) const
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
std::optional< VECTOR2I > m_pinned
const std::vector< PCB_SHAPE * > & GetShapes() const
SHAPE_GROUP_POINT_EDIT_BEHAVIOR(PCB_GROUP &aGroup)
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
std::unordered_map< PCB_SHAPE *, double > m_originalWidths
std::vector< PCB_SHAPE * > m_shapes
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
SHAPE_GROUP_POINT_EDIT_BEHAVIOR(std::vector< PCB_SHAPE * > aShapes, BOARD_ITEM *aParent, const std::optional< VECTOR2I > &aPinned)
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
int PointCount() const
Return the number of points (vertices) in this line chain.
Represent a set of closed polygons.
ITERATOR_TEMPLATE< VECTOR2I > ITERATOR
ITERATOR IterateWithHoles(int aOutline)
void InsertVertex(int aGlobalIndex, const VECTOR2I &aNewVertex)
Adds a vertex in the globally indexed position aGlobalIndex.
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
void RemoveVertex(int aGlobalIndex)
Delete the aGlobalIndex-th vertex.
void RemoveContour(int aContourIdx, int aPolygonIdx=-1)
Delete the aContourIdx-th contour of the aPolygonIdx-th polygon in the set.
bool GetNeighbourIndexes(int aGlobalIndex, int *aPrevious, int *aNext) const
Return the global indexes of the previous and the next corner of the aGlobalIndex-th corner of a cont...
ITERATOR Iterate(int aFirst, int aLast, bool aIterateHoles=false)
Return an object to iterate through the points of the polygons between aFirst and aLast.
const VECTOR2I & CVertex(int aIndex, int aOutline, int aHole) const
Return the index-th vertex in a given hole outline within a given outline.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
A textbox is edited as a rectnagle when it is orthogonally aligned.
TEXTBOX_POINT_EDIT_BEHAVIOR(PCB_TEXTBOX &aTextbox)
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
T * getEditFrame() const
Return the application window object, casted to requested user type.
Definition tool_base.h:182
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition tool_base.cpp:40
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
KIGFX::VIEW * getView() const
Return the instance of KIGFX::VIEW object used in the application.
Definition tool_base.cpp:34
RESET_REASON
Determine the reason of reset for a tool.
Definition tool_base.h:74
Generic, UI-independent tool event.
Definition tool_event.h:167
bool Matches(const TOOL_EVENT &aEvent) const
Test whether two events match in terms of category & action or command.
Definition tool_event.h:388
const VECTOR2D Position() const
Return mouse cursor position in world coordinates.
Definition tool_event.h:289
bool IsDrag(int aButtonMask=BUT_ANY) const
Definition tool_event.h:311
const VECTOR2D DragOrigin() const
Return the point where dragging has started.
Definition tool_event.h:295
T Parameter() const
Return a parameter assigned to the event.
Definition tool_event.h:484
bool IsMotion() const
Definition tool_event.h:326
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Define which state (aStateFunc) to go when a certain event arrives (aConditions).
TOOL_EVENT * Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
Suspend execution of the tool until an event specified in aEventList arrives.
void Activate()
Run the tool.
EDA_UNITS GetUserUnits() const
constexpr extended_type Cross(const VECTOR2< T > &aVector) const
Compute cross product of self with aVector.
Definition vector2d.h:559
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:574
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:312
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:567
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:406
VECTOR2I GetValue()
Return the value in internal units.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
Handle a list of polygons defining a copper zone.
Definition zone.h:70
void SetNeedRefill(bool aNeedRefill)
Definition zone.h:310
SHAPE_POLY_SET * Outline()
Definition zone.h:421
@ CHT_MODIFY
Definition commit.h:40
This file is part of the common library.
void RemapPolygonVertexMembers(BOARD *aBoard, const KIID &aPoly, int aChangedIndex, int aDelta, const std::function< void(BOARD_ITEM *)> &aBeforeModify, const std::function< void(BOARD_ITEM *)> &aBeforeRemove)
Repoint persisted VERTEX constraint members after an outline edit of polygon aPoly inserts or removes...
std::optional< CONSTRAINT_ANCHOR_POINT > ConstraintShapeVertex(const PCB_SHAPE *aShape, int aIndex)
VERTEX anchor at ordinal aIndex of a rectangle or eligible polygon or std::nullopt if the shape has n...
bool ConstraintPolygonIsModelable(const PCB_SHAPE *aShape)
True when polygon has one non empty hole free arc free outline making it solver eligible Shared by ad...
std::optional< CHAMFER_RESULT > ComputeChamferPoints(const SEG &aSegA, const SEG &aSegB, const CHAMFER_PARAMS &aChamferParams)
Compute the chamfer points for a given line pair and chamfer parameters.
@ ARROW
Definition cursors.h:43
static constexpr double MIN_SCALE
static bool empty(const wxTextEntryBase *aCtrl)
const int minSize
Push and Shove router track width and via size dialog.
#define _(s)
@ RADIANS_T
Definition eda_angle.h:31
@ NO_FILL
Definition eda_fill.h:30
@ RECURSE
Definition eda_item.h:51
#define IS_MOVING
Item being moved.
SHAPE_T
Definition eda_shape.h:54
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ ELLIPSE_ARC
Definition eda_shape.h:63
EDA_UNITS
Definition eda_units.h:44
@ ALL_LAYERS
@ OBJECT_LAYERS
@ IGNORE_SNAPS
POLYGON_LINE_MODE
@ SNAP_BY_GRID
@ SNAP_TO_GRID
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
a few functions useful in geometry calculations.
VECTOR2< ret_type > GetClampedCoords(const VECTOR2< in_type > &aCoords, pad_type aPadding=1u)
Clamp a vector to values that can be negated, respecting numeric limits of coordinates data type with...
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:844
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:741
@ LAYER_GP_OVERLAY
General purpose overlay.
Definition layer_ids.h:275
@ LAYER_SELECT_OVERLAY
Selected items overlay.
Definition layer_ids.h:276
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ B_Cu
Definition layer_ids.h:61
@ F_Cu
Definition layer_ids.h:60
constexpr int Mils2IU(const EDA_IU_SCALE &aIuScale, int mils)
Definition eda_units.h:172
bool PointProjectsOntoSegment(const VECTOR2I &aPoint, const SEG &aSeg)
Determine if a point projects onto a segment.
double GetLengthRatioFromStart(const VECTOR2I &aPoint, const SEG &aSeg)
Get the ratio of the vector to a point from the segment's start, compared to the segment's length.
const VECTOR2I & GetNearestEndpoint(const SEG &aSeg, const VECTOR2I &aPoint)
Get the nearest end of a segment to a point.
bool PointIsInDirection(const VECTOR2< T > &aPoint, const VECTOR2< T > &aDirection, const VECTOR2< T > &aFrom)
void DrawTextNextToCursor(KIGFX::VIEW *aView, const VECTOR2D &aCursorPos, const VECTOR2D &aTextQuadrant, const wxArrayString &aStrings, bool aDrawingDropShadows)
Draw strings next to the cursor.
wxString DimensionLabel(const wxString &prefix, double aVal, const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, bool aIncludeUnits=true)
Get a formatted string showing a dimension to a sane precision with an optional prefix and unit suffi...
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
@ CHAMFERED_RECT
Definition padstack.h:59
@ ROUNDRECT
Definition padstack.h:56
@ TRAPEZOID
Definition padstack.h:55
@ RECTANGLE
Definition padstack.h:53
BARCODE class definition.
@ VERTEX
An indexed rectangle corner or polygon outline vertex; pairs with CONSTRAINT_MEMBER::m_index.
@ START
First endpoint of a segment or arc.
@ END
Second endpoint of a segment or arc.
@ CENTER
Center of an arc or circle.
@ MANUAL
Text placement is manually set by the user.
#define STATUS_ITEMS_ONLY
Class to handle a set of BOARD_ITEMs.
static VECTOR2I snapCorner(const VECTOR2I &aPrev, const VECTOR2I &aNext, const VECTOR2I &aGuess, double aAngleDeg)
@ RECT_RIGHT
@ RECT_LEFT
static std::optional< CONSTRAINT_MEMBER > constraintMemberForEditPoint(PCB_SHAPE *aShape, EDIT_POINTS *aPoints, EDIT_POINT *aEditedPoint)
static std::pair< bool, SHAPE_POLY_SET::VERTEX_INDEX > findVertex(SHAPE_POLY_SET &aPolySet, const EDIT_POINT &aPoint)
static std::vector< VECTOR2I > getConstraintDirections(EDIT_RELATION *aRelation)
TEXTBOX_POINT_COUNT
Text boxes have different point counts depending on their orientation.
@ WHEN_POLYGON
@ WHEN_RECTANGLE
@ RECT_MAX_POINTS
@ RECT_BOT_LEFT
@ RECT_BOT_RIGHT
@ RECT_CENTER
@ RECT_TOP_RIGHT
@ RECT_RADIUS
@ RECT_TOP_LEFT
DIMENSION_POINTS
@ DIM_LEADER_MAX
@ DIM_RADIAL_MAX
@ DIM_CROSSBAREND
@ DIM_START
@ DIM_ALIGNED_MAX
@ DIM_CENTER_MAX
@ DIM_CROSSBARSTART
static void appendDirection(std::vector< VECTOR2I > &aDirections, const VECTOR2I &aDirection)
ARC_EDIT_MODE IncrementArcEditMode(ARC_EDIT_MODE aMode)
#define CHECK_POINT_COUNT(aPoints, aExpected)
#define CHECK_POINT_COUNT_GE(aPoints, aExpected)
CITER next(CITER it)
Definition ptree.cpp:120
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
SCH_CONDITIONS S_C
@ RECT_CENTER
@ RECT_RADIUS
@ RECT_RIGHT
@ RECT_LEFT
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:35
const int scale
std::vector< FAB_LAYER_COLOR > dummy
Parameters that define a simple chamfer operation.
One participant in a constraint: a referenced board item plus the feature of that item that participa...
Structure to hold the necessary information in order to index a vertex on a SHAPE_POLY_SET object: th...
KIBIS top(path, &reporter)
KIBIS_PIN * pin
VECTOR3I v1(5, 5, 5)
VECTOR2I center
int radius
VECTOR2I end
int delta
GR_TEXT_H_ALIGN_T
This is API surface mapped to common.types.HorizontalAlignment.
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
#define M_PI
@ TA_MOUSE_DOWN
Definition tool_event.h:66
@ TA_UNDO_REDO_POST
This event is sent after undo/redo command is performed.
Definition tool_event.h:105
@ MD_CTRL
Definition tool_event.h:140
@ MD_SHIFT
Definition tool_event.h:139
@ BUT_LEFT
Definition tool_event.h:128
VECTOR2I GetRotated(const VECTOR2I &aVector, const EDA_ANGLE &aAngle)
Return a new VECTOR2I that is the result of rotating aVector by aAngle.
Definition trigo.h:73
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:227
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:70
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:98
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:95
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:83
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:96
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:103
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:85
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:100
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:81
@ NOT_USED
the 3d code uses this value
Definition typeinfo.h:71
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:93
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:87
@ PCB_GRID_ITEM_T
a subgrid placed on a board
Definition typeinfo.h:238
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:94
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:97
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707
Supplemental functions for working with vectors and simple objects that interact with vectors.