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_textbox.h>
56#include <pcb_tablecell.h>
57#include <pcb_table.h>
58#include <pad.h>
59#include <zone.h>
60#include <footprint.h>
61#include <board.h>
66#include <progress_reporter.h>
67#include <layer_ids.h>
69
70const unsigned int PCB_POINT_EDITOR::COORDS_PADDING = pcbIUScale.mmToIU( 20 );
71
72static void appendDirection( std::vector<VECTOR2I>& aDirections, const VECTOR2I& aDirection )
73{
74 if( aDirection.x != 0 || aDirection.y != 0 )
75 aDirections.push_back( aDirection );
76}
77
78static std::vector<VECTOR2I> getConstraintDirections( EDIT_RELATION* aRelation )
79{
80 std::vector<VECTOR2I> directions;
81
82 if( !aRelation )
83 return directions;
84
85 switch( aRelation->Kind() )
86 {
88 appendDirection( directions, VECTOR2I( 1, 0 ) );
89 appendDirection( directions, VECTOR2I( 0, 1 ) );
90 break;
91
93 appendDirection( directions, VECTOR2I( 1, 0 ) );
94 appendDirection( directions, VECTOR2I( 0, 1 ) );
95 appendDirection( directions, VECTOR2I( 1, 1 ) );
96 appendDirection( directions, VECTOR2I( 1, -1 ) );
97 break;
98
100 appendDirection( directions, VECTOR2I( 0, 1 ) );
101 break;
102
104 appendDirection( directions, VECTOR2I( 1, 0 ) );
105 break;
106
108 appendDirection( directions, aRelation->Direction() );
109 break;
110
111 default:
112 break;
113 }
114
115 return directions;
116}
117
118// Few constants to avoid using bare numbers for point indices
130
131
136
137
138static std::optional<CONSTRAINT_MEMBER> constraintMemberForEditPoint(
139 PCB_SHAPE* aShape, EDIT_POINTS* aPoints, EDIT_POINT* aEditedPoint )
140{
141 if( !aShape || !aPoints || !aEditedPoint )
142 return std::nullopt;
143
144 SHAPE_T type = aShape->GetShape();
145 VECTOR2I position = aEditedPoint->GetPosition();
146
147 if( type == SHAPE_T::SEGMENT || type == SHAPE_T::ARC || type == SHAPE_T::BEZIER )
148 {
149 if( position == aShape->GetStart() )
151
152 if( position == aShape->GetEnd() )
154
155 if( type == SHAPE_T::ARC && position == aShape->GetCenter() )
157 }
158 else if( type == SHAPE_T::CIRCLE && position == aShape->GetCenter() )
159 {
161 }
162 else if( type == SHAPE_T::RECTANGLE && aPoints->PointsSize() >= RECT_MAX_POINTS )
163 {
164 for( unsigned i = RECT_TOP_LEFT; i <= RECT_BOT_LEFT; ++i )
165 {
166 if( aEditedPoint == &aPoints->Point( i ) )
168 }
169
170 for( unsigned i = 0; i < aPoints->LinesSize() && i < 4; ++i )
171 {
172 if( aEditedPoint == &aPoints->Line( i ) )
174 }
175 }
176 else if( type == SHAPE_T::POLY && ConstraintPolygonIsModelable( aShape ) )
177 {
178 for( unsigned i = 0; i < aPoints->PointsSize(); ++i )
179 {
180 if( aEditedPoint == &aPoints->Point( i ) )
182 }
183
184 for( unsigned i = 0; i < aPoints->LinesSize(); ++i )
185 {
186 if( aEditedPoint == &aPoints->Line( i ) )
188 }
189 }
190
191 return std::nullopt;
192}
193
194
209
210
217
218
220{
221public:
222 RECT_RADIUS_TEXT_ITEM( const EDA_IU_SCALE& aIuScale, EDA_UNITS aUnits ) :
224 m_iuScale( aIuScale ),
225 m_units( aUnits ),
226 m_radius( 0 ),
227 m_corner(),
228 m_quadrant( -1, 1 ),
229 m_visible( false )
230 {
231 }
232
233 const BOX2I ViewBBox() const override
234 {
235 BOX2I tmp;
236 tmp.SetMaximum();
237 return tmp;
238 }
239
240 std::vector<int> ViewGetLayers() const override
241 {
243 }
244
245 void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override
246 {
247 if( !m_visible )
248 return;
249
250 wxArrayString strings;
251 strings.push_back( KIGFX::PREVIEW::DimensionLabel( "r", m_radius, m_iuScale, m_units ) );
253 aLayer == LAYER_SELECT_OVERLAY );
254 }
255
256 void Set( int aRadius, const VECTOR2I& aCorner, const VECTOR2I& aQuadrant, EDA_UNITS aUnits )
257 {
258 m_radius = aRadius;
259 m_corner = aCorner;
260 m_quadrant = aQuadrant;
261 m_units = aUnits;
262 m_visible = true;
263 }
264
265 void Hide()
266 {
267 m_visible = false;
268 }
269
270 wxString GetClass() const override
271 {
272 return wxT( "RECT_RADIUS_TEXT_ITEM" );
273 }
274
275private:
282};
283
284
286{
287public:
289 m_rectangle( aRectangle )
290 {
291 wxASSERT( m_rectangle.GetShape() == SHAPE_T::RECTANGLE );
292 }
293
298 static void MakePoints( const PCB_SHAPE& aRectangle, EDIT_POINTS& aPoints )
299 {
300 wxCHECK( aRectangle.GetShape() == SHAPE_T::RECTANGLE, /* void */ );
301
302 VECTOR2I topLeft = aRectangle.GetTopLeft();
303 VECTOR2I botRight = aRectangle.GetBotRight();
304
305 aPoints.SetSwapX( topLeft.x > botRight.x );
306 aPoints.SetSwapY( topLeft.y > botRight.y );
307
308 if( aPoints.SwapX() )
309 std::swap( topLeft.x, botRight.x );
310
311 if( aPoints.SwapY() )
312 std::swap( topLeft.y, botRight.y );
313
314 aPoints.AddPoint( topLeft );
315 aPoints.AddPoint( VECTOR2I( botRight.x, topLeft.y ) );
316 aPoints.AddPoint( botRight );
317 aPoints.AddPoint( VECTOR2I( topLeft.x, botRight.y ) );
318 aPoints.AddPoint( aRectangle.GetCenter() );
319 aPoints.AddPoint( VECTOR2I( botRight.x - aRectangle.GetCornerRadius(), topLeft.y ) );
320 aPoints.Point( RECT_RADIUS ).SetDrawCircle();
321
322 aPoints.AddLine( aPoints.Point( RECT_TOP_LEFT ), aPoints.Point( RECT_TOP_RIGHT ) );
323 aPoints.Line( RECT_TOP ).SetRelation(
325 aPoints.AddLine( aPoints.Point( RECT_TOP_RIGHT ), aPoints.Point( RECT_BOT_RIGHT ) );
326 aPoints.Line( RECT_RIGHT ).SetRelation(
328 aPoints.AddLine( aPoints.Point( RECT_BOT_RIGHT ), aPoints.Point( RECT_BOT_LEFT ) );
329 aPoints.Line( RECT_BOT ).SetRelation(
331 aPoints.AddLine( aPoints.Point( RECT_BOT_LEFT ), aPoints.Point( RECT_TOP_LEFT ) );
332 aPoints.Line( RECT_LEFT ).SetRelation(
334 }
335
336 static void UpdateItem( PCB_SHAPE& aRectangle, const EDIT_POINT& aEditedPoint,
337 EDIT_POINTS& aPoints, const VECTOR2I& aMinSize = { 0, 0 } )
338 {
339 // You can have more points if your item wants to have more points
340 // (this class assumes the rect points come first, but that can be changed)
342
343 auto setLeft =
344 [&]( int left )
345 {
346 aPoints.SwapX() ? aRectangle.SetRight( left ) : aRectangle.SetLeft( left );
347 };
348 auto setRight =
349 [&]( int right )
350 {
351 aPoints.SwapX() ? aRectangle.SetLeft( right ) : aRectangle.SetRight( right );
352 };
353 auto setTop =
354 [&]( int top )
355 {
356 aPoints.SwapY() ? aRectangle.SetBottom( top ) : aRectangle.SetTop( top );
357 };
358 auto setBottom =
359 [&]( int bottom )
360 {
361 aPoints.SwapY() ? aRectangle.SetTop( bottom ) : aRectangle.SetBottom( bottom );
362 };
363
364 VECTOR2I topLeft = aPoints.Point( RECT_TOP_LEFT ).GetPosition();
365 VECTOR2I topRight = aPoints.Point( RECT_TOP_RIGHT ).GetPosition();
366 VECTOR2I botLeft = aPoints.Point( RECT_BOT_LEFT ).GetPosition();
367 VECTOR2I botRight = aPoints.Point( RECT_BOT_RIGHT ).GetPosition();
368
369 PinEditedCorner( aEditedPoint, aPoints, topLeft, topRight, botLeft, botRight,
370 { 0, 0 }, { 0, 0 }, aMinSize );
371
372 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_LEFT ) )
373 || isModified( aEditedPoint, aPoints.Point( RECT_TOP_RIGHT ) )
374 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) )
375 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_LEFT ) ) )
376 {
377 setTop( topLeft.y );
378 setLeft( topLeft.x );
379 setRight( botRight.x );
380 setBottom( botRight.y );
381 }
382 else if( isModified( aEditedPoint, aPoints.Point( RECT_CENTER ) ) )
383 {
384 const VECTOR2I moveVector = aPoints.Point( RECT_CENTER ).GetPosition() - aRectangle.GetCenter();
385 aRectangle.Move( moveVector );
386 }
387 else if( isModified( aEditedPoint, aPoints.Point( RECT_RADIUS ) ) )
388 {
389 int width = std::abs( botRight.x - topLeft.x );
390 int height = std::abs( botRight.y - topLeft.y );
391 int maxRadius = std::min( width, height ) / 2;
392 int x = aPoints.Point( RECT_RADIUS ).GetX();
393 x = std::clamp( x, botRight.x - maxRadius, botRight.x );
394 aPoints.Point( RECT_RADIUS ).SetPosition( x, topLeft.y );
395 aRectangle.SetCornerRadius( botRight.x - x );
396 }
397 else if( isModified( aEditedPoint, aPoints.Line( RECT_TOP ) ) )
398 {
399 // Only top changes; keep others from previous full-local bbox
400 setTop( topLeft.y );
401 }
402 else if( isModified( aEditedPoint, aPoints.Line( RECT_LEFT ) ) )
403 {
404 // Only left changes; keep others from previous full-local bbox
405 setLeft( topLeft.x );
406 }
407 else if( isModified( aEditedPoint, aPoints.Line( RECT_BOT ) ) )
408 {
409 // Only bottom changes; keep others from previous full-local bbox
410 setBottom( botRight.y );
411 }
412 else if( isModified( aEditedPoint, aPoints.Line( RECT_RIGHT ) ) )
413 {
414 // Only right changes; keep others from previous full-local bbox
415 setRight( botRight.x );
416 }
417
418 for( unsigned i = 0; i < aPoints.LinesSize(); ++i )
419 {
420 if( !isModified( aEditedPoint, aPoints.Line( i ) ) )
421 aPoints.Line( i ).SetRelation(
423 }
424 }
425
426 static void UpdatePoints( const PCB_SHAPE& aRectangle, EDIT_POINTS& aPoints )
427 {
428 wxCHECK( aPoints.PointsSize() >= RECT_MAX_POINTS, /* void */ );
429
430 VECTOR2I topLeft = aRectangle.GetTopLeft();
431 VECTOR2I botRight = aRectangle.GetBotRight();
432
433 aPoints.SetSwapX( topLeft.x > botRight.x );
434 aPoints.SetSwapY( topLeft.y > botRight.y );
435
436 if( aPoints.SwapX() )
437 std::swap( topLeft.x, botRight.x );
438
439 if( aPoints.SwapY() )
440 std::swap( topLeft.y, botRight.y );
441
442 aPoints.Point( RECT_TOP_LEFT ).SetPosition( topLeft );
443 aPoints.Point( RECT_RADIUS ).SetPosition( botRight.x - aRectangle.GetCornerRadius(), topLeft.y );
444 aPoints.Point( RECT_TOP_RIGHT ).SetPosition( botRight.x, topLeft.y );
445 aPoints.Point( RECT_BOT_RIGHT ).SetPosition( botRight );
446 aPoints.Point( RECT_BOT_LEFT ).SetPosition( topLeft.x, botRight.y );
447 aPoints.Point( RECT_CENTER ).SetPosition( aRectangle.GetCenter() );
448 }
449
450 void MakePoints( EDIT_POINTS& aPoints ) override
451 {
452 // Just call the static helper
453 MakePoints( m_rectangle, aPoints );
454 }
455
456 bool UpdatePoints( EDIT_POINTS& aPoints ) override
457 {
458 // Careful; rectangle shape is mutable between cardinal and non-cardinal rotations...
459 if( m_rectangle.GetShape() != SHAPE_T::RECTANGLE || aPoints.PointsSize() == 0 )
460 return false;
461
462 UpdatePoints( m_rectangle, aPoints );
463 return true;
464 }
465
466 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
467 std::vector<EDA_ITEM*>& aUpdatedItems ) override
468 {
469 UpdateItem( m_rectangle, aEditedPoint, aPoints );
470 }
471
485 static void PinEditedCorner( const EDIT_POINT& aEditedPoint, const EDIT_POINTS& aEditPoints,
486 VECTOR2I& aTopLeft, VECTOR2I& aTopRight, VECTOR2I& aBotLeft, VECTOR2I& aBotRight,
487 const VECTOR2I& aHole = { 0, 0 }, const VECTOR2I& aHoleSize = { 0, 0 },
488 const VECTOR2I& aMinSize = { 0, 0 } )
489 {
490 int minWidth = std::max( EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 ), aMinSize.x );
491 int minHeight = std::max( EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 ), aMinSize.y );
492
493 if( isModified( aEditedPoint, aEditPoints.Point( RECT_TOP_LEFT ) ) )
494 {
495 if( aHoleSize.x )
496 {
497 // pin edited point to the top/left of the hole
498 aTopLeft.x = std::min( aTopLeft.x, aHole.x - aHoleSize.x / 2 - minWidth );
499 aTopLeft.y = std::min( aTopLeft.y, aHole.y - aHoleSize.y / 2 - minHeight );
500 }
501 else
502 {
503 // pin edited point within opposite corner
504 aTopLeft.x = std::min( aTopLeft.x, aBotRight.x - minWidth );
505 aTopLeft.y = std::min( aTopLeft.y, aBotRight.y - minHeight );
506 }
507
508 // push edited point edges to adjacent corners
509 aTopRight.y = aTopLeft.y;
510 aBotLeft.x = aTopLeft.x;
511 }
512 else if( isModified( aEditedPoint, aEditPoints.Point( RECT_TOP_RIGHT ) ) )
513 {
514 if( aHoleSize.x )
515 {
516 // pin edited point to the top/right of the hole
517 aTopRight.x = std::max( aTopRight.x, aHole.x + aHoleSize.x / 2 + minWidth );
518 aTopRight.y = std::min( aTopRight.y, aHole.y - aHoleSize.y / 2 - minHeight );
519 }
520 else
521 {
522 // pin edited point within opposite corner
523 aTopRight.x = std::max( aTopRight.x, aBotLeft.x + minWidth );
524 aTopRight.y = std::min( aTopRight.y, aBotLeft.y - minHeight );
525 }
526
527 // push edited point edges to adjacent corners
528 aTopLeft.y = aTopRight.y;
529 aBotRight.x = aTopRight.x;
530 }
531 else if( isModified( aEditedPoint, aEditPoints.Point( RECT_BOT_LEFT ) ) )
532 {
533 if( aHoleSize.x )
534 {
535 // pin edited point to the bottom/left of the hole
536 aBotLeft.x = std::min( aBotLeft.x, aHole.x - aHoleSize.x / 2 - minWidth );
537 aBotLeft.y = std::max( aBotLeft.y, aHole.y + aHoleSize.y / 2 + minHeight );
538 }
539 else
540 {
541 // pin edited point within opposite corner
542 aBotLeft.x = std::min( aBotLeft.x, aTopRight.x - minWidth );
543 aBotLeft.y = std::max( aBotLeft.y, aTopRight.y + minHeight );
544 }
545
546 // push edited point edges to adjacent corners
547 aBotRight.y = aBotLeft.y;
548 aTopLeft.x = aBotLeft.x;
549 }
550 else if( isModified( aEditedPoint, aEditPoints.Point( RECT_BOT_RIGHT ) ) )
551 {
552 if( aHoleSize.x )
553 {
554 // pin edited point to the bottom/right of the hole
555 aBotRight.x = std::max( aBotRight.x, aHole.x + aHoleSize.x / 2 + minWidth );
556 aBotRight.y = std::max( aBotRight.y, aHole.y + aHoleSize.y / 2 + minHeight );
557 }
558 else
559 {
560 // pin edited point within opposite corner
561 aBotRight.x = std::max( aBotRight.x, aTopLeft.x + minWidth );
562 aBotRight.y = std::max( aBotRight.y, aTopLeft.y + minHeight );
563 }
564
565 // push edited point edges to adjacent corners
566 aBotLeft.y = aBotRight.y;
567 aTopRight.x = aBotRight.x;
568 }
569 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_TOP ) ) )
570 {
571 aTopLeft.y = std::min( aTopLeft.y, aBotRight.y - minHeight );
572 }
573 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_LEFT ) ) )
574 {
575 aTopLeft.x = std::min( aTopLeft.x, aBotRight.x - minWidth );
576 }
577 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_BOT ) ) )
578 {
579 aBotRight.y = std::max( aBotRight.y, aTopLeft.y + minHeight );
580 }
581 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_RIGHT ) ) )
582 {
583 aBotRight.x = std::max( aBotRight.x, aTopLeft.x + minWidth );
584 }
585 }
586
587private:
589};
590
591
593{
594public:
596 POLYGON_POINT_EDIT_BEHAVIOR( *aZone.Outline() ),
597 m_zone( aZone )
598 {}
599
600 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
601 std::vector<EDA_ITEM*>& aUpdatedItems ) override
602 {
603 m_zone.UnFill();
604
605 // Defer to the base class to update the polygon
606 POLYGON_POINT_EDIT_BEHAVIOR::UpdateItem( aEditedPoint, aPoints, aCommit, aUpdatedItems );
607
608 m_zone.HatchBorder();
609 }
610
611private:
613};
614
615
617{
619 {
620 REFIMG_ORIGIN = RECT_CENTER, // Reuse the center point fo rthe transform origin
621
623 };
624
625public:
629
630 void MakePoints( EDIT_POINTS& aPoints ) override
631 {
632 REFERENCE_IMAGE& refImage = m_refImage.GetReferenceImage();
633
634 const VECTOR2I topLeft = refImage.GetPosition() - refImage.GetSize() / 2;
635 const VECTOR2I botRight = refImage.GetPosition() + refImage.GetSize() / 2;
636
637 aPoints.AddPoint( topLeft );
638 aPoints.AddPoint( VECTOR2I( botRight.x, topLeft.y ) );
639 aPoints.AddPoint( botRight );
640 aPoints.AddPoint( VECTOR2I( topLeft.x, botRight.y ) );
641
642 aPoints.AddPoint( refImage.GetPosition() + refImage.GetTransformOriginOffset() );
643 }
644
645 bool UpdatePoints( EDIT_POINTS& aPoints ) override
646 {
647 wxCHECK( aPoints.PointsSize() == REFIMG_MAX_POINTS, false );
648
649 REFERENCE_IMAGE& refImage = m_refImage.GetReferenceImage();
650
651 const VECTOR2I topLeft = refImage.GetPosition() - refImage.GetSize() / 2;
652 const VECTOR2I botRight = refImage.GetPosition() + refImage.GetSize() / 2;
653
654 aPoints.Point( RECT_TOP_LEFT ).SetPosition( topLeft );
655 aPoints.Point( RECT_TOP_RIGHT ).SetPosition( VECTOR2I( botRight.x, topLeft.y ) );
656 aPoints.Point( RECT_BOT_RIGHT ).SetPosition( botRight );
657 aPoints.Point( RECT_BOT_LEFT ).SetPosition( VECTOR2I( topLeft.x, botRight.y ) );
658 aPoints.Point( REFIMG_ORIGIN ).SetPosition( refImage.GetPosition() + refImage.GetTransformOriginOffset() );
659 return true;
660 }
661
662 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
663 std::vector<EDA_ITEM*>& aUpdatedItems ) override
664 {
666
667 REFERENCE_IMAGE& refImage = m_refImage.GetReferenceImage();
668
669 const VECTOR2I topLeft = aPoints.Point( RECT_TOP_LEFT ).GetPosition();
670 const VECTOR2I topRight = aPoints.Point( RECT_TOP_RIGHT ).GetPosition();
671 const VECTOR2I botRight = aPoints.Point( RECT_BOT_RIGHT ).GetPosition();
672 const VECTOR2I botLeft = aPoints.Point( RECT_BOT_LEFT ).GetPosition();
673 const VECTOR2I xfrmOrigin = aPoints.Point( REFIMG_ORIGIN ).GetPosition();
674
675 if( isModified( aEditedPoint, aPoints.Point( REFIMG_ORIGIN ) ) )
676 {
677 // Moving the transform origin
678 // As the other points didn't move, we can get the image extent from them
679 const VECTOR2I newOffset = xfrmOrigin - ( topLeft + botRight ) / 2;
680 refImage.SetTransformOriginOffset( newOffset );
681 }
682 else
683 {
684 const VECTOR2I oldOrigin = m_refImage.GetPosition() + refImage.GetTransformOriginOffset();
685 const VECTOR2I oldSize = refImage.GetSize();
686 const VECTOR2I pos = refImage.GetPosition();
687
688 OPT_VECTOR2I newCorner;
689 VECTOR2I oldCorner = pos;
690
691 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_LEFT ) ) )
692 {
693 newCorner = topLeft;
694 oldCorner -= oldSize / 2;
695 }
696 else if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_RIGHT ) ) )
697 {
698 newCorner = topRight;
699 oldCorner -= VECTOR2I( -oldSize.x, oldSize.y ) / 2;
700 }
701 else if( isModified( aEditedPoint, aPoints.Point( RECT_BOT_LEFT ) ) )
702 {
703 newCorner = botLeft;
704 oldCorner -= VECTOR2I( oldSize.x, -oldSize.y ) / 2;
705 }
706 else if( isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) ) )
707 {
708 newCorner = botRight;
709 oldCorner += oldSize / 2;
710 }
711
712 if( newCorner )
713 {
714 // Turn in the respective vectors from the origin
715 *newCorner -= xfrmOrigin;
716 oldCorner -= oldOrigin;
717
718 // If we tried to cross the origin, clamp it to stop it
719 if( sign( newCorner->x ) != sign( oldCorner.x ) || sign( newCorner->y ) != sign( oldCorner.y ) )
720 {
721 *newCorner = VECTOR2I( 0, 0 );
722 }
723
724 const double newLength = newCorner->EuclideanNorm();
725 const double oldLength = oldCorner.EuclideanNorm();
726
727 double ratio = oldLength > 0 ? ( newLength / oldLength ) : 1.0;
728
729 // Clamp the scaling to a minimum of 50 mils
730 VECTOR2I newSize = oldSize * ratio;
731 double newWidth = std::max( newSize.x, EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 50 ) );
732 double newHeight = std::max( newSize.y, EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 50 ) );
733 ratio = std::min( newWidth / oldSize.x, newHeight / oldSize.y );
734
735 // Also handles the origin offset
736 refImage.SetImageScale( refImage.GetImageScale() * ratio );
737 }
738 }
739 }
740
741private:
743};
744
745
747{
748public:
750 m_barcode( aBarcode )
751 {}
752
754 {
756 dummy.SetStart( m_barcode.GetCenter() - VECTOR2I( m_barcode.GetWidth() / 2, m_barcode.GetHeight() / 2 ) );
757 dummy.SetEnd( dummy.GetStart() + VECTOR2I( m_barcode.GetWidth(), m_barcode.GetHeight() ) );
758 dummy.Rotate( m_barcode.GetPosition(), m_barcode.GetAngle() );
759 return dummy;
760 }
761
762 void MakePoints( EDIT_POINTS& aPoints ) override
763 {
764 if( !m_barcode.GetAngle().IsCardinal() )
765 {
766 // Non-cardinal barcode point-editing isn't useful enough to support.
767 return;
768 }
769
770 auto set45Constraint =
771 [&]( int a, int b )
772 {
773 aPoints.Point( a ).SetRelation( EDIT_RELATION::Angle45( aPoints.Point( b ) ) );
774 };
775
777
778 if( m_barcode.KeepSquare() )
779 {
780 set45Constraint( RECT_TOP_LEFT, RECT_BOT_RIGHT );
781 set45Constraint( RECT_TOP_RIGHT, RECT_BOT_LEFT );
782 set45Constraint( RECT_BOT_RIGHT, RECT_TOP_LEFT );
783 set45Constraint( RECT_BOT_LEFT, RECT_TOP_RIGHT );
784 }
785 }
786
787 bool UpdatePoints( EDIT_POINTS& aPoints ) override
788 {
789 const unsigned target = m_barcode.GetAngle().IsCardinal() ? RECT_MAX_POINTS : 0;
790
791 if( aPoints.PointsSize() != target )
792 return false;
793
795 return true;
796 }
797
798 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
799 std::vector<EDA_ITEM*>& aUpdatedItems ) override
800 {
801 if( m_barcode.GetAngle().IsCardinal() )
802 {
804 RECTANGLE_POINT_EDIT_BEHAVIOR::UpdateItem( dummy, aEditedPoint, aPoints );
805 dummy.Rotate( dummy.GetCenter(), -m_barcode.GetAngle() );
806
807 m_barcode.SetPosition( dummy.GetCenter() );
808 m_barcode.SetWidth( dummy.GetRectangleWidth() );
809 m_barcode.SetHeight( dummy.GetRectangleHeight() );
810 m_barcode.AssembleBarcode();
811 }
812 }
813
814private:
816};
817
818
820{
821public:
826
827 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
828 std::vector<EDA_ITEM*>& aUpdatedItems ) override
829 {
831
832 PCB_TABLE& table = static_cast<PCB_TABLE&>( *m_cell.GetParent() );
833 aCommit.Modify( &table );
834 aUpdatedItems.push_back( &table );
835
836 if( !m_cell.GetTextAngle().IsHorizontal() )
837 {
838 if( isModified( aEditedPoint, aPoints.Point( ROW_HEIGHT ) ) )
839 {
840 m_cell.SetEnd( VECTOR2I( m_cell.GetEndX(), aPoints.Point( ROW_HEIGHT ).GetY() ) );
841
842 int colWidth = std::abs( m_cell.GetRectangleHeight() );
843
844 for( int ii = 0; ii < m_cell.GetColSpan() - 1; ++ii )
845 colWidth -= table.GetColWidth( m_cell.GetColumn() + ii );
846
847 table.SetColWidth( m_cell.GetColumn() + m_cell.GetColSpan() - 1, colWidth );
848 }
849 else if( isModified( aEditedPoint, aPoints.Point( COL_WIDTH ) ) )
850 {
851 m_cell.SetEnd( VECTOR2I( aPoints.Point( COL_WIDTH ).GetX(), m_cell.GetEndY() ) );
852
853 int rowHeight = m_cell.GetRectangleWidth();
854
855 for( int ii = 0; ii < m_cell.GetRowSpan() - 1; ++ii )
856 rowHeight -= table.GetRowHeight( m_cell.GetRow() + ii );
857
858 table.SetRowHeight( m_cell.GetRow() + m_cell.GetRowSpan() - 1, rowHeight );
859 }
860 }
861 else
862 {
863 if( isModified( aEditedPoint, aPoints.Point( COL_WIDTH ) ) )
864 {
865 m_cell.SetEnd( VECTOR2I( aPoints.Point( COL_WIDTH ).GetX(), m_cell.GetEndY() ) );
866
867 int colWidth = m_cell.GetRectangleWidth();
868
869 for( int ii = 0; ii < m_cell.GetColSpan() - 1; ++ii )
870 colWidth -= table.GetColWidth( m_cell.GetColumn() + ii );
871
872 table.SetColWidth( m_cell.GetColumn() + m_cell.GetColSpan() - 1, colWidth );
873 }
874 else if( isModified( aEditedPoint, aPoints.Point( ROW_HEIGHT ) ) )
875 {
876 m_cell.SetEnd( VECTOR2I( m_cell.GetEndX(), aPoints.Point( ROW_HEIGHT ).GetY() ) );
877
878 int rowHeight = m_cell.GetRectangleHeight();
879
880 for( int ii = 0; ii < m_cell.GetRowSpan() - 1; ++ii )
881 rowHeight -= table.GetRowHeight( m_cell.GetRow() + ii );
882
883 table.SetRowHeight( m_cell.GetRow() + m_cell.GetRowSpan() - 1, rowHeight );
884 }
885 }
886
887 table.Normalize();
888 }
889
890private:
892};
893
894
896{
897public:
899 m_pad( aPad ),
900 m_layer( aLayer )
901 {}
902
903 void MakePoints( EDIT_POINTS& aPoints ) override
904 {
905 VECTOR2I shapePos = m_pad.ShapePos( m_layer );
906 VECTOR2I halfSize( m_pad.GetSize( m_layer ).x / 2, m_pad.GetSize( m_layer ).y / 2 );
907
908 if( m_pad.IsLocked() )
909 return;
910
911 switch( m_pad.GetShape( m_layer ) )
912 {
914 aPoints.AddPoint( VECTOR2I( shapePos.x + halfSize.x, shapePos.y ) );
915 break;
916
917 case PAD_SHAPE::OVAL:
922 {
923 if( !m_pad.GetOrientation().IsCardinal() )
924 break;
925
926 if( m_pad.GetOrientation().IsVertical() )
927 std::swap( halfSize.x, halfSize.y );
928
929 // It's important to fill these according to the RECT indices
930 aPoints.AddPoint( shapePos - halfSize );
931 aPoints.AddPoint( VECTOR2I( shapePos.x + halfSize.x, shapePos.y - halfSize.y ) );
932 aPoints.AddPoint( shapePos + halfSize );
933 aPoints.AddPoint( VECTOR2I( shapePos.x - halfSize.x, shapePos.y + halfSize.y ) );
934 }
935 break;
936
937 default: // suppress warnings
938 break;
939 }
940 }
941
942 bool UpdatePoints( EDIT_POINTS& aPoints ) override
943 {
944 bool locked = m_pad.GetParent() && m_pad.IsLocked();
945 VECTOR2I shapePos = m_pad.ShapePos( m_layer );
946 VECTOR2I halfSize( m_pad.GetSize( m_layer ).x / 2, m_pad.GetSize( m_layer ).y / 2 );
947
948 switch( m_pad.GetShape( m_layer ) )
949 {
951 {
952 int target = locked ? 0 : 1;
953
954 // Careful; pad shape is mutable...
955 if( int( aPoints.PointsSize() ) != target )
956 {
957 aPoints.Clear();
958 MakePoints( aPoints );
959 }
960 else if( target == 1 )
961 {
962 shapePos.x += halfSize.x;
963 aPoints.Point( 0 ).SetPosition( shapePos );
964 }
965 }
966 break;
967
968 case PAD_SHAPE::OVAL:
973 {
974 // Careful; pad shape and orientation are mutable...
975 int target = locked || !m_pad.GetOrientation().IsCardinal() ? 0 : 4;
976
977 if( int( aPoints.PointsSize() ) != target )
978 {
979 aPoints.Clear();
980 MakePoints( aPoints );
981 }
982 else if( target == 4 )
983 {
984 if( m_pad.GetOrientation().IsVertical() )
985 std::swap( halfSize.x, halfSize.y );
986
987 aPoints.Point( RECT_TOP_LEFT ).SetPosition( shapePos - halfSize );
988 aPoints.Point( RECT_TOP_RIGHT ).SetPosition( VECTOR2I( shapePos.x + halfSize.x,
989 shapePos.y - halfSize.y ) );
990 aPoints.Point( RECT_BOT_RIGHT ).SetPosition( shapePos + halfSize );
991 aPoints.Point( RECT_BOT_LEFT ).SetPosition( VECTOR2I( shapePos.x - halfSize.x,
992 shapePos.y + halfSize.y ) );
993 }
994
995 break;
996 }
997
998 default: // suppress warnings
999 break;
1000 }
1001
1002 return true;
1003 }
1004
1005 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1006 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1007 {
1008 switch( m_pad.GetShape( m_layer ) )
1009 {
1010 case PAD_SHAPE::CIRCLE:
1011 {
1012 VECTOR2I end = aPoints.Point( 0 ).GetPosition();
1013 int diameter = 2 * ( end - m_pad.GetPosition() ).EuclideanNorm();
1014
1015 m_pad.SetSize( m_layer, VECTOR2I( diameter, diameter ) );
1016 break;
1017 }
1018
1019 case PAD_SHAPE::OVAL:
1024 {
1025 VECTOR2I topLeft = aPoints.Point( RECT_TOP_LEFT ).GetPosition();
1026 VECTOR2I topRight = aPoints.Point( RECT_TOP_RIGHT ).GetPosition();
1027 VECTOR2I botLeft = aPoints.Point( RECT_BOT_LEFT ).GetPosition();
1028 VECTOR2I botRight = aPoints.Point( RECT_BOT_RIGHT ).GetPosition();
1029 VECTOR2I holeCenter = m_pad.GetPosition();
1030 VECTOR2I holeSize = m_pad.GetDrillSize();
1031
1032 RECTANGLE_POINT_EDIT_BEHAVIOR::PinEditedCorner( aEditedPoint, aPoints, topLeft, topRight,
1033 botLeft, botRight, holeCenter, holeSize );
1034
1035 if( ( m_pad.GetOffset( m_layer ).x || m_pad.GetOffset( m_layer ).y )
1036 || ( m_pad.GetDrillSize().x && m_pad.GetDrillSize().y ) )
1037 {
1038 // Keep hole pinned at the current location; adjust the pad around the hole
1039
1040 VECTOR2I center = m_pad.GetPosition();
1041 int dist[4];
1042
1043 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_LEFT ) )
1044 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) ) )
1045 {
1046 dist[0] = center.x - topLeft.x;
1047 dist[1] = center.y - topLeft.y;
1048 dist[2] = botRight.x - center.x;
1049 dist[3] = botRight.y - center.y;
1050 }
1051 else
1052 {
1053 dist[0] = center.x - botLeft.x;
1054 dist[1] = center.y - topRight.y;
1055 dist[2] = topRight.x - center.x;
1056 dist[3] = botLeft.y - center.y;
1057 }
1058
1059 VECTOR2I padSize( dist[0] + dist[2], dist[1] + dist[3] );
1060 VECTOR2I deltaOffset( padSize.x / 2 - dist[2], padSize.y / 2 - dist[3] );
1061
1062 if( m_pad.GetOrientation().IsVertical() )
1063 std::swap( padSize.x, padSize.y );
1064
1065 RotatePoint( deltaOffset, -m_pad.GetOrientation() );
1066
1067 m_pad.SetSize( m_layer, padSize );
1068 m_pad.SetOffset( m_layer, -deltaOffset );
1069 }
1070 else
1071 {
1072 // Keep pad position at the center of the pad shape
1073
1074 int left, top, right, bottom;
1075
1076 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_LEFT ) )
1077 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) ) )
1078 {
1079 left = topLeft.x;
1080 top = topLeft.y;
1081 right = botRight.x;
1082 bottom = botRight.y;
1083 }
1084 else
1085 {
1086 left = botLeft.x;
1087 top = topRight.y;
1088 right = topRight.x;
1089 bottom = botLeft.y;
1090 }
1091
1092 VECTOR2I padSize( abs( right - left ), abs( bottom - top ) );
1093
1094 if( m_pad.GetOrientation().IsVertical() )
1095 std::swap( padSize.x, padSize.y );
1096
1097 m_pad.SetSize( m_layer, padSize );
1098 m_pad.SetPosition( VECTOR2I( ( left + right ) / 2, ( top + bottom ) / 2 ) );
1099 }
1100 break;
1101 }
1102 default: // suppress warnings
1103 break;
1104 }
1105 }
1106
1107private:
1110};
1111
1112
1119{
1120public:
1122 m_generator( aGenerator )
1123 {}
1124
1125 void MakePoints( EDIT_POINTS& aPoints ) override
1126 {
1127 m_generator.MakeEditPoints( aPoints );
1128 }
1129
1130 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1131 {
1132 m_generator.UpdateEditPoints( aPoints );
1133 return true;
1134 }
1135
1136 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1137 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1138 {
1139 m_generator.UpdateFromEditPoints( aPoints );
1140 }
1141
1142private:
1144};
1145
1146
1156{
1157public:
1159 m_dimension( aDimension ),
1160 m_originalTextPos( aDimension.GetTextPos() ),
1161 m_oldCrossBar( SEG{ aDimension.GetCrossbarStart(), aDimension.GetCrossbarEnd() } )
1162 {}
1163
1165 {
1166 const SEG newCrossBar{ m_dimension.GetCrossbarStart(), m_dimension.GetCrossbarEnd() };
1167
1168 if( newCrossBar == m_oldCrossBar )
1169 {
1170 // Crossbar didn't change, text doesn't need to change
1171 return;
1172 }
1173
1174 const VECTOR2I newTextPos = getDimensionNewTextPosition();
1175 m_dimension.SetTextPos( newTextPos );
1176
1177 const GR_TEXT_H_ALIGN_T oldJustify = m_dimension.GetHorizJustify();
1178
1179 // We may need to update the justification if we go past vertical.
1182 {
1183 const VECTOR2I oldProject = m_oldCrossBar.LineProject( m_originalTextPos );
1184 const VECTOR2I newProject = newCrossBar.LineProject( newTextPos );
1185
1186 const VECTOR2I oldProjectedOffset =
1187 oldProject - m_oldCrossBar.NearestPoint( oldProject );
1188 const VECTOR2I newProjectedOffset = newProject - newCrossBar.NearestPoint( newProject );
1189
1190 const bool textWasLeftOf = oldProjectedOffset.x < 0
1191 || ( oldProjectedOffset.x == 0 && oldProjectedOffset.y > 0 );
1192 const bool textIsLeftOf = newProjectedOffset.x < 0
1193 || ( newProjectedOffset.x == 0 && newProjectedOffset.y > 0 );
1194
1195 if( textWasLeftOf != textIsLeftOf )
1196 {
1197 // Flip whatever the user had set
1198 m_dimension.SetHorizJustify( ( oldJustify == GR_TEXT_H_ALIGN_T::GR_TEXT_H_ALIGN_LEFT )
1201 }
1202 }
1203
1204 // Update the dimension (again) to ensure the text knockouts are correct
1205 m_dimension.Update();
1206 }
1207
1208private:
1210 {
1211 const SEG newCrossBar{ m_dimension.GetCrossbarStart(), m_dimension.GetCrossbarEnd() };
1212
1213 const EDA_ANGLE oldAngle = EDA_ANGLE( m_oldCrossBar.B - m_oldCrossBar.A );
1214 const EDA_ANGLE newAngle = EDA_ANGLE( newCrossBar.B - newCrossBar.A );
1215 const EDA_ANGLE rotation = oldAngle - newAngle;
1216
1217 // There are two modes - when the text is between the crossbar points, and when it's not.
1219 {
1221 const VECTOR2I rotTextOffsetFromCbCenter = GetRotated( m_originalTextPos - m_oldCrossBar.Center(),
1222 rotation );
1223 const VECTOR2I rotTextOffsetFromCbEnd = GetRotated( m_originalTextPos - cbNearestEndToText, rotation );
1224
1225 // Which of the two crossbar points is now in the right direction? They could be swapped over now.
1226 // If zero-length, doesn't matter, they're the same thing
1227 const bool startIsInOffsetDirection = KIGEOM::PointIsInDirection( m_dimension.GetCrossbarStart(),
1228 rotTextOffsetFromCbCenter,
1229 newCrossBar.Center() );
1230
1231 const VECTOR2I& newCbRefPt = startIsInOffsetDirection ? m_dimension.GetCrossbarStart()
1232 : m_dimension.GetCrossbarEnd();
1233
1234 // Apply the new offset to the correct crossbar point
1235 return newCbRefPt + rotTextOffsetFromCbEnd;
1236 }
1237
1238 // If the text was between the crossbar points, it should stay there, but we need to find a
1239 // good place for it. Keep it the same distance from the crossbar line, but rotated as needed.
1240
1241 const VECTOR2I origTextPointProjected = m_oldCrossBar.NearestPoint( m_originalTextPos );
1242 const double oldRatio = KIGEOM::GetLengthRatioFromStart( origTextPointProjected, m_oldCrossBar );
1243
1244 // Perpendicular from the crossbar line to the text position
1245 // We need to keep this length constant
1246 const VECTOR2I rotCbNormalToText = GetRotated( m_originalTextPos - origTextPointProjected, rotation );
1247
1248 const VECTOR2I newProjected = newCrossBar.A + ( newCrossBar.B - newCrossBar.A ) * oldRatio;
1249 return newProjected + rotCbNormalToText;
1250 }
1251
1255};
1256
1257
1262{
1263public:
1265 m_dimension( aDimension )
1266 {}
1267
1268 void MakePoints( EDIT_POINTS& aPoints ) override
1269 {
1270 aPoints.AddPoint( m_dimension.GetStart() );
1271 aPoints.AddPoint( m_dimension.GetEnd() );
1272 aPoints.AddPoint( m_dimension.GetTextPos() );
1273 aPoints.AddPoint( m_dimension.GetCrossbarStart() );
1274 aPoints.AddPoint( m_dimension.GetCrossbarEnd() );
1275
1278
1279 if( m_dimension.Type() == PCB_DIM_ALIGNED_T )
1280 {
1281 // Dimension height setting - edit points should move only along the feature lines
1282 aPoints.Point( DIM_CROSSBARSTART )
1284 aPoints.Point( DIM_CROSSBARSTART ), aPoints.Point( DIM_START ) ) );
1285 aPoints.Point( DIM_CROSSBAREND )
1287 aPoints.Point( DIM_CROSSBAREND ), aPoints.Point( DIM_END ) ) );
1288 }
1289 }
1290
1291 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1292 {
1293 wxCHECK( aPoints.PointsSize() == DIM_ALIGNED_MAX, false );
1294
1295 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1296 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1297 aPoints.Point( DIM_TEXT ).SetPosition( m_dimension.GetTextPos() );
1298 aPoints.Point( DIM_CROSSBARSTART ).SetPosition( m_dimension.GetCrossbarStart() );
1299 aPoints.Point( DIM_CROSSBAREND ).SetPosition( m_dimension.GetCrossbarEnd() );
1300 return true;
1301 }
1302
1303 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1304 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1305 {
1307
1308 if( m_dimension.Type() == PCB_DIM_ALIGNED_T )
1309 updateAlignedDimension( aEditedPoint, aPoints );
1310 else
1311 updateOrthogonalDimension( aEditedPoint, aPoints );
1312 }
1313
1314 OPT_VECTOR2I Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const override
1315 {
1316 // Constraint for crossbar
1317 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1318 return aPoints.Point( DIM_END ).GetPosition();
1319
1320 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1321 return aPoints.Point( DIM_START ).GetPosition();
1322
1323 // No constraint
1324 return aEditedPoint.GetPosition();
1325 }
1326
1327private:
1331 void updateAlignedDimension( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints )
1332 {
1333 DIM_ALIGNED_TEXT_UPDATER textPositionUpdater( m_dimension );
1334
1335 // Check which point is currently modified and updated dimension's points respectively
1336 if( isModified( aEditedPoint, aPoints.Point( DIM_CROSSBARSTART ) ) )
1337 {
1338 VECTOR2D featureLine( aEditedPoint.GetPosition() - m_dimension.GetStart() );
1339 VECTOR2D crossBar( m_dimension.GetEnd() - m_dimension.GetStart() );
1340
1341 if( featureLine.Cross( crossBar ) > 0 )
1342 m_dimension.SetHeight( -featureLine.EuclideanNorm() );
1343 else
1344 m_dimension.SetHeight( featureLine.EuclideanNorm() );
1345
1346 m_dimension.Update();
1347 }
1348 else if( isModified( aEditedPoint, aPoints.Point( DIM_CROSSBAREND ) ) )
1349 {
1350 VECTOR2D featureLine( aEditedPoint.GetPosition() - m_dimension.GetEnd() );
1351 VECTOR2D crossBar( m_dimension.GetEnd() - m_dimension.GetStart() );
1352
1353 if( featureLine.Cross( crossBar ) > 0 )
1354 m_dimension.SetHeight( -featureLine.EuclideanNorm() );
1355 else
1356 m_dimension.SetHeight( featureLine.EuclideanNorm() );
1357
1358 m_dimension.Update();
1359 }
1360 else if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1361 {
1362 m_dimension.SetStart( aEditedPoint.GetPosition() );
1363 m_dimension.Update();
1364
1365 aPoints.Point( DIM_CROSSBARSTART )
1367 aPoints.Point( DIM_CROSSBARSTART ), aPoints.Point( DIM_START ) ) );
1368 aPoints.Point( DIM_CROSSBAREND )
1370 aPoints.Point( DIM_CROSSBAREND ), aPoints.Point( DIM_END ) ) );
1371 }
1372 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1373 {
1374 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1375 m_dimension.Update();
1376
1377 aPoints.Point( DIM_CROSSBARSTART )
1379 aPoints.Point( DIM_CROSSBARSTART ), aPoints.Point( DIM_START ) ) );
1380 aPoints.Point( DIM_CROSSBAREND )
1382 aPoints.Point( DIM_CROSSBAREND ), aPoints.Point( DIM_END ) ) );
1383 }
1384 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1385 {
1386 // Force manual mode if we weren't already in it
1387 m_dimension.SetTextPositionMode( DIM_TEXT_POSITION::MANUAL );
1388 m_dimension.SetTextPos( aEditedPoint.GetPosition() );
1389 m_dimension.Update();
1390 }
1391
1392 textPositionUpdater.UpdateTextAfterChange();
1393 }
1394
1398 void updateOrthogonalDimension( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints )
1399 {
1400 DIM_ALIGNED_TEXT_UPDATER textPositionUpdater( m_dimension );
1401 PCB_DIM_ORTHOGONAL& orthDimension = static_cast<PCB_DIM_ORTHOGONAL&>( m_dimension );
1402
1403 if( isModified( aEditedPoint, aPoints.Point( DIM_CROSSBARSTART ) )
1404 || isModified( aEditedPoint, aPoints.Point( DIM_CROSSBAREND ) ) )
1405 {
1406 BOX2I bounds( m_dimension.GetStart(), m_dimension.GetEnd() - m_dimension.GetStart() );
1407
1408 const VECTOR2I& cursorPos = aEditedPoint.GetPosition();
1409
1410 // Find vector from nearest dimension point to edit position
1411 VECTOR2I directionA( cursorPos - m_dimension.GetStart() );
1412 VECTOR2I directionB( cursorPos - m_dimension.GetEnd() );
1413 VECTOR2I direction = ( directionA < directionB ) ? directionA : directionB;
1414
1415 bool vert;
1416 VECTOR2D featureLine( cursorPos - m_dimension.GetStart() );
1417
1418 // Only change the orientation when we move outside the bounds
1419 if( !bounds.Contains( cursorPos ) )
1420 {
1421 // If the dimension is horizontal or vertical, set correct orientation
1422 // otherwise, test if we're left/right of the bounding box or above/below it
1423 if( bounds.GetWidth() == 0 )
1424 vert = true;
1425 else if( bounds.GetHeight() == 0 )
1426 vert = false;
1427 else if( cursorPos.x > bounds.GetLeft() && cursorPos.x < bounds.GetRight() )
1428 vert = false;
1429 else if( cursorPos.y > bounds.GetTop() && cursorPos.y < bounds.GetBottom() )
1430 vert = true;
1431 else
1432 vert = std::abs( direction.y ) < std::abs( direction.x );
1433
1436 }
1437 else
1438 {
1439 vert = orthDimension.GetOrientation() == PCB_DIM_ORTHOGONAL::DIR::VERTICAL;
1440 }
1441
1442 m_dimension.SetHeight( vert ? featureLine.x : featureLine.y );
1443 }
1444 else if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1445 {
1446 m_dimension.SetStart( aEditedPoint.GetPosition() );
1447 }
1448 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1449 {
1450 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1451 }
1452 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1453 {
1454 // Force manual mode if we weren't already in it
1455 m_dimension.SetTextPositionMode( DIM_TEXT_POSITION::MANUAL );
1456 m_dimension.SetTextPos( VECTOR2I( aEditedPoint.GetPosition() ) );
1457 }
1458
1459 m_dimension.Update();
1460
1461 // After recompute, find the new text position
1462 textPositionUpdater.UpdateTextAfterChange();
1463 }
1464
1466};
1467
1468
1470{
1471public:
1473 m_dimension( aDimension )
1474 {}
1475
1476 void MakePoints( EDIT_POINTS& aPoints ) override
1477 {
1478 aPoints.AddPoint( m_dimension.GetStart() );
1479 aPoints.AddPoint( m_dimension.GetEnd() );
1480
1482
1483 aPoints.Point( DIM_END ).SetRelation( EDIT_RELATION::Angle45( aPoints.Point( DIM_START ) ) );
1485 }
1486
1487 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1488 {
1489 wxCHECK( aPoints.PointsSize() == DIM_CENTER_MAX, false );
1490
1491 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1492 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1493 return true;
1494 }
1495
1496 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1497 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1498 {
1500
1501 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1502 m_dimension.SetStart( aEditedPoint.GetPosition() );
1503 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1504 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1505
1506 m_dimension.Update();
1507 }
1508
1509 OPT_VECTOR2I Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const override
1510 {
1511 if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1512 return aPoints.Point( DIM_START ).GetPosition();
1513
1514 return std::nullopt;
1515 }
1516
1517private:
1519};
1520
1521
1523{
1524public:
1526 m_dimension( aDimension )
1527 {}
1528
1529 void MakePoints( EDIT_POINTS& aPoints ) override
1530 {
1531 aPoints.AddPoint( m_dimension.GetStart() );
1532 aPoints.AddPoint( m_dimension.GetEnd() );
1533 aPoints.AddPoint( m_dimension.GetTextPos() );
1534 aPoints.AddPoint( m_dimension.GetKnee() );
1535
1538
1539 aPoints.Point( DIM_KNEE )
1541 aPoints.Point( DIM_END ) ) );
1543
1544 aPoints.Point( DIM_TEXT ).SetRelation( EDIT_RELATION::Angle45( aPoints.Point( DIM_KNEE ) ) );
1546 }
1547
1548 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1549 {
1550 wxCHECK( aPoints.PointsSize() == DIM_RADIAL_MAX, false );
1551
1552 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1553 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1554 aPoints.Point( DIM_TEXT ).SetPosition( m_dimension.GetTextPos() );
1555 aPoints.Point( DIM_KNEE ).SetPosition( m_dimension.GetKnee() );
1556 return true;
1557 }
1558
1559 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1560 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1561 {
1563
1564 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1565 {
1566 m_dimension.SetStart( aEditedPoint.GetPosition() );
1567 m_dimension.Update();
1568
1569 aPoints.Point( DIM_KNEE )
1571 aPoints.Point( DIM_END ) ) );
1572 }
1573 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1574 {
1575 VECTOR2I oldKnee = m_dimension.GetKnee();
1576
1577 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1578 m_dimension.Update();
1579
1580 VECTOR2I kneeDelta = m_dimension.GetKnee() - oldKnee;
1581 m_dimension.SetTextPos( m_dimension.GetTextPos() + kneeDelta );
1582 m_dimension.Update();
1583
1584 aPoints.Point( DIM_KNEE )
1586 aPoints.Point( DIM_END ) ) );
1587 }
1588 else if( isModified( aEditedPoint, aPoints.Point( DIM_KNEE ) ) )
1589 {
1590 VECTOR2I oldKnee = m_dimension.GetKnee();
1591 VECTOR2I arrowVec = aPoints.Point( DIM_KNEE ).GetPosition() - aPoints.Point( DIM_END ).GetPosition();
1592
1593 m_dimension.SetLeaderLength( arrowVec.EuclideanNorm() );
1594 m_dimension.Update();
1595
1596 VECTOR2I kneeDelta = m_dimension.GetKnee() - oldKnee;
1597 m_dimension.SetTextPos( m_dimension.GetTextPos() + kneeDelta );
1598 m_dimension.Update();
1599 }
1600 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1601 {
1602 m_dimension.SetTextPos( aEditedPoint.GetPosition() );
1603 m_dimension.Update();
1604 }
1605 }
1606
1607 OPT_VECTOR2I Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const override
1608 {
1609 if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1610 return aPoints.Point( DIM_KNEE ).GetPosition();
1611
1612 return std::nullopt;
1613 }
1614
1615private:
1617};
1618
1619
1621{
1622public:
1624 m_dimension( aDimension )
1625 {}
1626
1627 void MakePoints( EDIT_POINTS& aPoints ) override
1628 {
1629 aPoints.AddPoint( m_dimension.GetStart() );
1630 aPoints.AddPoint( m_dimension.GetEnd() );
1631 aPoints.AddPoint( m_dimension.GetTextPos() );
1632
1635
1636 aPoints.Point( DIM_TEXT ).SetRelation( EDIT_RELATION::Angle45( aPoints.Point( DIM_END ) ) );
1638 }
1639
1640 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1641 {
1642 wxCHECK( aPoints.PointsSize() == DIM_LEADER_MAX, false );
1643
1644 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1645 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1646 aPoints.Point( DIM_TEXT ).SetPosition( m_dimension.GetTextPos() );
1647 return true;
1648 }
1649
1650 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1651 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1652 {
1654
1655 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1656 {
1657 m_dimension.SetStart( aEditedPoint.GetPosition() );
1658 }
1659 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1660 {
1661 const VECTOR2I newPoint( aEditedPoint.GetPosition() );
1662 const VECTOR2I delta = newPoint - m_dimension.GetEnd();
1663
1664 m_dimension.SetEnd( newPoint );
1665 m_dimension.SetTextPos( m_dimension.GetTextPos() + delta );
1666 }
1667 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1668 {
1669 m_dimension.SetTextPos( aEditedPoint.GetPosition() );
1670 }
1671
1672 m_dimension.Update();
1673 }
1674
1675private:
1677};
1678
1679
1684{
1685public:
1687 m_textbox( aTextbox )
1688 {}
1689
1690 void MakePoints( EDIT_POINTS& aPoints ) override
1691 {
1692 if( m_textbox.GetShape() == SHAPE_T::RECTANGLE )
1694
1695 // Rotated textboxes are implemented as polygons and these aren't currently editable.
1696 }
1697
1698 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1699 {
1700 // Careful; textbox shape is mutable between cardinal and non-cardinal rotations...
1701 const unsigned target = m_textbox.GetShape() == SHAPE_T::RECTANGLE ? RECT_MAX_POINTS : 0;
1702
1703 if( aPoints.PointsSize() != target )
1704 return false;
1705
1707 return true;
1708 }
1709
1710 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1711 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1712 {
1713 if( m_textbox.GetShape() == SHAPE_T::RECTANGLE )
1714 {
1715 m_textbox.ClearBoundingBoxCache();
1716 VECTOR2I minSize = m_textbox.GetMinSize();
1718 }
1719 }
1720
1721private:
1723};
1724
1726{
1727public:
1729 m_group( &aGroup ),
1730 m_parent( &aGroup )
1731 {
1732 for( BOARD_ITEM* item : aGroup.GetBoardItems() )
1733 {
1734 if( item->Type() == PCB_SHAPE_T )
1735 {
1736 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
1737 m_shapes.push_back( shape );
1738 m_originalWidths[shape] = static_cast<double>( shape->GetWidth() );
1739 }
1740 }
1741 }
1742
1743 SHAPE_GROUP_POINT_EDIT_BEHAVIOR( std::vector<PCB_SHAPE*> aShapes, BOARD_ITEM* aParent ) :
1744 m_group( nullptr ),
1745 m_shapes( std::move( aShapes ) ),
1746 m_parent( aParent )
1747 {
1748 for( PCB_SHAPE* shape : m_shapes )
1749 m_originalWidths[shape] = static_cast<double>( shape->GetWidth() );
1750 }
1751
1752 void MakePoints( EDIT_POINTS& aPoints ) override
1753 {
1754 BOX2I bbox = getBoundingBox();
1755 VECTOR2I tl = bbox.GetOrigin();
1756 VECTOR2I br = bbox.GetEnd();
1757
1758 aPoints.AddPoint( tl );
1759 aPoints.AddPoint( VECTOR2I( br.x, tl.y ) );
1760 aPoints.AddPoint( br );
1761 aPoints.AddPoint( VECTOR2I( tl.x, br.y ) );
1762 aPoints.AddPoint( bbox.Centre() );
1763
1764 aPoints.AddIndicatorLine( aPoints.Point( RECT_TOP_LEFT ), aPoints.Point( RECT_TOP_RIGHT ) );
1765 aPoints.AddIndicatorLine( aPoints.Point( RECT_TOP_RIGHT ), aPoints.Point( RECT_BOT_RIGHT ) );
1766 aPoints.AddIndicatorLine( aPoints.Point( RECT_BOT_RIGHT ), aPoints.Point( RECT_BOT_LEFT ) );
1767 aPoints.AddIndicatorLine( aPoints.Point( RECT_BOT_LEFT ), aPoints.Point( RECT_TOP_LEFT ) );
1768 }
1769
1770 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1771 {
1772 BOX2I bbox = getBoundingBox();
1773 VECTOR2I tl = bbox.GetOrigin();
1774 VECTOR2I br = bbox.GetEnd();
1775
1776 aPoints.Point( RECT_TOP_LEFT ).SetPosition( tl );
1777 aPoints.Point( RECT_TOP_RIGHT ).SetPosition( br.x, tl.y );
1778 aPoints.Point( RECT_BOT_RIGHT ).SetPosition( br );
1779 aPoints.Point( RECT_BOT_LEFT ).SetPosition( tl.x, br.y );
1780 aPoints.Point( RECT_CENTER ).SetPosition( bbox.Centre() );
1781 return true;
1782 }
1783
1784 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1785 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1786 {
1787 BOX2I oldBox = getBoundingBox();
1788 VECTOR2I oldCenter = oldBox.Centre();
1789
1790 if( isModified( aEditedPoint, aPoints.Point( RECT_CENTER ) ) )
1791 {
1792 VECTOR2I delta = aPoints.Point( RECT_CENTER ).GetPosition() - oldCenter;
1793
1794 if( m_group )
1795 {
1796 aCommit.Modify( m_group, nullptr, RECURSE_MODE::RECURSE );
1797 m_group->Move( delta );
1798 }
1799 else
1800 {
1801 for( PCB_SHAPE* shape : m_shapes )
1802 {
1803 aCommit.Modify( shape );
1804 shape->Move( delta );
1805 }
1806 }
1807
1808 for( PCB_SHAPE* shape : m_shapes )
1809 aUpdatedItems.push_back( shape );
1810
1811 UpdatePoints( aPoints );
1812 return;
1813 }
1814
1815 VECTOR2I tl = aPoints.Point( RECT_TOP_LEFT ).GetPosition();
1816 VECTOR2I tr = aPoints.Point( RECT_TOP_RIGHT ).GetPosition();
1817 VECTOR2I bl = aPoints.Point( RECT_BOT_LEFT ).GetPosition();
1818 VECTOR2I br = aPoints.Point( RECT_BOT_RIGHT ).GetPosition();
1819
1820 RECTANGLE_POINT_EDIT_BEHAVIOR::PinEditedCorner( aEditedPoint, aPoints, tl, tr, bl, br );
1821
1822 double sx = static_cast<double>( br.x - tl.x ) / static_cast<double>( oldBox.GetWidth() );
1823 double sy = static_cast<double>( br.y - tl.y ) / static_cast<double>( oldBox.GetHeight() );
1824 double scale = ( sx + sy ) / 2.0;
1825
1826 // Prevent scaling below a minimum threshold to avoid precision loss when shapes
1827 // are scaled to near-zero size. Also prevent negative scaling which would flip
1828 // shapes when dragging past the center point.
1829 const double MIN_SCALE = 0.01;
1830
1831 if( scale < MIN_SCALE )
1832 scale = MIN_SCALE;
1833
1834 for( PCB_SHAPE* shape : m_shapes )
1835 {
1836 aCommit.Modify( shape );
1837 shape->Move( -oldCenter );
1838 shape->Scale( scale );
1839 shape->Move( oldCenter );
1840
1841 if( auto shapeIt = m_originalWidths.find( shape ); shapeIt != m_originalWidths.end() )
1842 {
1843 shapeIt->second = shapeIt->second * scale;
1844 shape->SetWidth( KiROUND( shapeIt->second ) );
1845 }
1846 else
1847 {
1848 shape->SetWidth( KiROUND( shape->GetWidth() * scale ) );
1849 }
1850
1851 aUpdatedItems.push_back( shape );
1852 }
1853
1854 UpdatePoints( aPoints );
1855 }
1856
1857 BOARD_ITEM* GetParent() const { return m_parent; }
1858
1859private:
1861 {
1862 BOX2I bbox;
1863
1864 for( const PCB_SHAPE* shape : m_shapes )
1865 bbox.Merge( shape->GetBoundingBox() );
1866
1867 return bbox;
1868 }
1869
1870private:
1872 std::vector<PCB_SHAPE*> m_shapes;
1874 std::unordered_map<PCB_SHAPE*, double> m_originalWidths;
1875};
1876
1877
1879 PCB_TOOL_BASE( "pcbnew.PointEditor" ),
1880 m_frame( nullptr ),
1881 m_selectionTool( nullptr ),
1882 m_editedPoint( nullptr ),
1883 m_hoveredPoint( nullptr ),
1884 m_original( VECTOR2I( 0, 0 ) ),
1886 m_radiusHelper( nullptr ),
1887 m_altConstrainer( VECTOR2I( 0, 0 ) ),
1888 m_inPointEditorTool( false ),
1889 m_angleSnapPos( VECTOR2I( 0, 0 ) ),
1890 m_stickyDisplacement( VECTOR2I( 0, 0 ) ),
1891 m_angleSnapActive( false )
1892{}
1893
1894
1896{
1898
1899 if( KIGFX::VIEW* view = getView() )
1900 {
1901 if( m_angleItem && view->HasItem( m_angleItem.get() ) )
1902 view->Remove( m_angleItem.get() );
1903
1904 if( m_editPoints && view->HasItem( m_editPoints.get() ) )
1905 view->Remove( m_editPoints.get() );
1906
1907 if( view->HasItem( &m_preview ) )
1908 view->Remove( &m_preview );
1909 }
1910
1911 m_angleItem.reset();
1912 m_editPoints.reset();
1913 m_altConstraint.reset();
1914 getViewControls()->SetAutoPan( false );
1915 m_angleSnapActive = false;
1917}
1918
1919
1921{
1922 const KICAD_T type = aItem.Type();
1923
1924 if( type == PCB_ZONE_T )
1925 return true;
1926
1927 if( type == PCB_SHAPE_T )
1928 {
1929 const PCB_SHAPE& shape = static_cast<const PCB_SHAPE&>( aItem );
1930 const SHAPE_T shapeType = shape.GetShape();
1931 return shapeType == SHAPE_T::SEGMENT || shapeType == SHAPE_T::POLY || shapeType == SHAPE_T::ARC;
1932 }
1933
1934 return false;
1935}
1936
1937
1939{
1940 const auto type = aItem.Type();
1941
1942 if( type == PCB_ZONE_T )
1943 return true;
1944
1945 if( type == PCB_SHAPE_T )
1946 {
1947 const PCB_SHAPE& shape = static_cast<const PCB_SHAPE&>( aItem );
1948 const SHAPE_T shapeType = shape.GetShape();
1949 return shapeType == SHAPE_T::POLY;
1950 }
1951
1952 return false;
1953}
1954
1955
1956static VECTOR2I snapCorner( const VECTOR2I& aPrev, const VECTOR2I& aNext, const VECTOR2I& aGuess,
1957 double aAngleDeg )
1958{
1959 double angleRad = aAngleDeg * M_PI / 180.0;
1960 VECTOR2D prev( aPrev );
1961 VECTOR2D next( aNext );
1962 double chord = ( next - prev ).EuclideanNorm();
1963 double sinA = sin( angleRad );
1964
1965 if( chord == 0.0 || fabs( sinA ) < 1e-9 )
1966 return aGuess;
1967
1968 double radius = chord / ( 2.0 * sinA );
1969 VECTOR2D mid = ( prev + next ) / 2.0;
1970 VECTOR2D dir = next - prev;
1971 VECTOR2D normal( -dir.y, dir.x );
1972 normal = normal.Resize( 1 );
1973 double h_sq = radius * radius - ( chord * chord ) / 4.0;
1974 double h = h_sq > 0.0 ? sqrt( h_sq ) : 0.0;
1975
1976 VECTOR2D center1 = mid + normal * h;
1977 VECTOR2D center2 = mid - normal * h;
1978
1979 auto project =
1980 [&]( const VECTOR2D& center )
1981 {
1982 VECTOR2D v = VECTOR2D( aGuess ) - center;
1983
1984 if( v.EuclideanNorm() == 0.0 )
1985 v = prev - center;
1986
1987 v = v.Resize( 1 );
1988 VECTOR2D p = center + v * radius;
1989 return KiROUND( p );
1990 };
1991
1992 VECTOR2I p1 = project( center1 );
1993 VECTOR2I p2 = project( center2 );
1994
1995 double d1 = ( VECTOR2D( aGuess ) - VECTOR2D( p1 ) ).EuclideanNorm();
1996 double d2 = ( VECTOR2D( aGuess ) - VECTOR2D( p2 ) ).EuclideanNorm();
1997
1998 return d1 < d2 ? p1 : p2;
1999}
2000
2001
2003{
2004 // Find the selection tool, so they can cooperate
2006
2007 wxASSERT_MSG( m_selectionTool, wxT( "pcbnew.InteractiveSelection tool is not available" ) );
2008
2009 const auto arcIsEdited =
2010 []( const SELECTION& aSelection ) -> bool
2011 {
2012 const EDA_ITEM* item = aSelection.Front();
2013 return ( item != nullptr ) && ( item->Type() == PCB_SHAPE_T )
2014 && static_cast<const PCB_SHAPE*>( item )->GetShape() == SHAPE_T::ARC;
2015 };
2016
2017 using S_C = SELECTION_CONDITIONS;
2018
2019 auto& menu = m_selectionTool->GetToolMenu().GetMenu();
2020
2021 menu.AddItem( PCB_ACTIONS::cycleArcEditMode, S_C::Count( 1 ) && arcIsEdited );
2022
2023 return true;
2024}
2025
2026
2027std::shared_ptr<EDIT_POINTS> PCB_POINT_EDITOR::makePoints( EDA_ITEM* aItem )
2028{
2029 std::shared_ptr<EDIT_POINTS> points = std::make_shared<EDIT_POINTS>( aItem );
2030
2031 if( !aItem )
2032 return points;
2033
2034 // Reset the behaviour and we'll make a new one
2035 m_editorBehavior = nullptr;
2036
2037 switch( aItem->Type() )
2038 {
2040 {
2041 PCB_REFERENCE_IMAGE& refImage = static_cast<PCB_REFERENCE_IMAGE&>( *aItem );
2042 m_editorBehavior = std::make_unique<REFERENCE_IMAGE_POINT_EDIT_BEHAVIOR>( refImage );
2043 break;
2044 }
2045 case PCB_BARCODE_T:
2046 {
2047 PCB_BARCODE& barcode = static_cast<PCB_BARCODE&>( *aItem );
2048 m_editorBehavior = std::make_unique<BARCODE_POINT_EDIT_BEHAVIOR>( barcode );
2049 break;
2050 }
2051 case PCB_TEXTBOX_T:
2052 {
2053 PCB_TEXTBOX& textbox = static_cast<PCB_TEXTBOX&>( *aItem );
2054 m_editorBehavior = std::make_unique<TEXTBOX_POINT_EDIT_BEHAVIOR>( textbox );
2055 break;
2056 }
2057 case PCB_SHAPE_T:
2058 {
2059 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
2060
2061 switch( shape->GetShape() )
2062 {
2063 case SHAPE_T::SEGMENT:
2064 m_editorBehavior = std::make_unique<EDA_SEGMENT_POINT_EDIT_BEHAVIOR>( *shape );
2065 break;
2066
2067 case SHAPE_T::RECTANGLE:
2068 m_editorBehavior = std::make_unique<RECTANGLE_POINT_EDIT_BEHAVIOR>( *shape );
2069 break;
2070
2071 case SHAPE_T::ARC:
2072 m_editorBehavior = std::make_unique<EDA_ARC_POINT_EDIT_BEHAVIOR>( *shape, m_arcEditMode,
2073 *getViewControls(),
2074 pcbIUScale );
2075 break;
2076
2077 case SHAPE_T::CIRCLE:
2078 m_editorBehavior = std::make_unique<EDA_CIRCLE_POINT_EDIT_BEHAVIOR>( *shape );
2079 break;
2080
2081 case SHAPE_T::POLY:
2082 m_editorBehavior = std::make_unique<EDA_POLYGON_POINT_EDIT_BEHAVIOR>( *shape );
2083 break;
2084
2085 case SHAPE_T::BEZIER:
2086 m_editorBehavior = std::make_unique<EDA_BEZIER_POINT_EDIT_BEHAVIOR>( *shape,
2087 shape->GetMaxError() );
2088 break;
2089
2090 case SHAPE_T::ELLIPSE:
2092 m_editorBehavior = std::make_unique<EDA_ELLIPSE_POINT_EDIT_BEHAVIOR>( *shape );
2093 break;
2094
2095 default: // suppress warnings
2096 break;
2097 }
2098
2099 break;
2100 }
2101
2102 case PCB_GROUP_T:
2103 {
2104 PCB_GROUP* group = static_cast<PCB_GROUP*>( aItem );
2105 bool shapesOnly = true;
2106
2107 for( BOARD_ITEM* child : group->GetBoardItems() )
2108 {
2109 if( child->Type() != PCB_SHAPE_T )
2110 {
2111 shapesOnly = false;
2112 break;
2113 }
2114 }
2115
2116 if( shapesOnly )
2117 m_editorBehavior = std::make_unique<SHAPE_GROUP_POINT_EDIT_BEHAVIOR>( *group );
2118 else
2119 points.reset();
2120
2121 break;
2122 }
2123
2124 case PCB_TABLECELL_T:
2125 {
2126 PCB_TABLECELL* cell = static_cast<PCB_TABLECELL*>( aItem );
2127
2128 // No support for point-editing of a rotated table
2129 if( cell->GetShape() == SHAPE_T::RECTANGLE )
2130 m_editorBehavior = std::make_unique<PCB_TABLECELL_POINT_EDIT_BEHAVIOR>( *cell );
2131
2132 break;
2133 }
2134
2135 case PCB_PAD_T:
2136 {
2137 // Pad edit only for the footprint editor
2139 {
2140 PAD& pad = static_cast<PAD&>( *aItem );
2141 PCB_LAYER_ID activeLayer = m_frame ? m_frame->GetActiveLayer() : PADSTACK::ALL_LAYERS;
2142
2143 // Point editor only handles copper shape changes
2144 if( !IsCopperLayer( activeLayer ) )
2145 activeLayer = IsFrontLayer( activeLayer ) ? F_Cu : B_Cu;
2146
2147 m_editorBehavior = std::make_unique<PAD_POINT_EDIT_BEHAVIOR>( pad, activeLayer );
2148 }
2149 break;
2150 }
2151
2152 case PCB_ZONE_T:
2153 {
2154 ZONE& zone = static_cast<ZONE&>( *aItem );
2155 m_editorBehavior = std::make_unique<ZONE_POINT_EDIT_BEHAVIOR>( zone );
2156 break;
2157 }
2158
2159 case PCB_GENERATOR_T:
2160 {
2161 PCB_GENERATOR* generator = static_cast<PCB_GENERATOR*>( aItem );
2162 m_editorBehavior = std::make_unique<GENERATOR_POINT_EDIT_BEHAVIOR>( *generator );
2163 break;
2164 }
2165
2166 case PCB_DIM_ALIGNED_T:
2168 {
2169 PCB_DIM_ALIGNED& dimension = static_cast<PCB_DIM_ALIGNED&>( *aItem );
2170 m_editorBehavior = std::make_unique<ALIGNED_DIMENSION_POINT_EDIT_BEHAVIOR>( dimension );
2171 break;
2172 }
2173
2174 case PCB_DIM_CENTER_T:
2175 {
2176 PCB_DIM_CENTER& dimension = static_cast<PCB_DIM_CENTER&>( *aItem );
2177 m_editorBehavior = std::make_unique<DIM_CENTER_POINT_EDIT_BEHAVIOR>( dimension );
2178 break;
2179 }
2180
2181 case PCB_DIM_RADIAL_T:
2182 {
2183 PCB_DIM_RADIAL& dimension = static_cast<PCB_DIM_RADIAL&>( *aItem );
2184 m_editorBehavior = std::make_unique<DIM_RADIAL_POINT_EDIT_BEHAVIOR>( dimension );
2185 break;
2186 }
2187
2188 case PCB_DIM_LEADER_T:
2189 {
2190 PCB_DIM_LEADER& dimension = static_cast<PCB_DIM_LEADER&>( *aItem );
2191 m_editorBehavior = std::make_unique<DIM_LEADER_POINT_EDIT_BEHAVIOR>( dimension );
2192 break;
2193 }
2194
2195 default:
2196 points.reset();
2197 break;
2198 }
2199
2200 if( m_editorBehavior )
2201 m_editorBehavior->MakePoints( *points );
2202
2203 return points;
2204}
2205
2206
2208{
2209 EDIT_POINT* point;
2210 EDIT_POINT* hovered = nullptr;
2211
2212 if( aEvent.IsMotion() )
2213 {
2214 point = m_editPoints->FindPoint( aEvent.Position(), getView() );
2215 hovered = point;
2216 }
2217 else if( aEvent.IsDrag( BUT_LEFT ) )
2218 {
2219 point = m_editPoints->FindPoint( aEvent.DragOrigin(), getView() );
2220 }
2221 else
2222 {
2223 point = m_editPoints->FindPoint( getViewControls()->GetCursorPosition(), getView() );
2224 }
2225
2226 if( hovered )
2227 {
2228 if( m_hoveredPoint != hovered )
2229 {
2230 if( m_hoveredPoint )
2231 m_hoveredPoint->SetHover( false );
2232
2233 m_hoveredPoint = hovered;
2234 m_hoveredPoint->SetHover();
2235 }
2236 }
2237 else if( m_hoveredPoint )
2238 {
2239 m_hoveredPoint->SetHover( false );
2240 m_hoveredPoint = nullptr;
2241 }
2242
2243 if( m_editedPoint != point )
2244 setEditedPoint( point );
2245}
2246
2247
2249{
2251 return 0;
2252
2254 return 0;
2255
2257
2259 const PCB_SELECTION& selection = m_selectionTool->GetSelection();
2260
2261 if( selection.Size() == 0 )
2262 return 0;
2263
2264 for( EDA_ITEM* selItem : selection )
2265 {
2266 if( selItem->GetEditFlags() || !selItem->IsBOARD_ITEM() )
2267 return 0;
2268 }
2269
2270 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
2271 bool overrideLocks = editFrame->GetOverrideLocks();
2272
2273 if( !item || ( item->IsLocked() && !overrideLocks ) )
2274 return 0;
2275
2276 Activate();
2277 // Must be done after Activate() so that it gets set into the correct context
2278 getViewControls()->ShowCursor( true );
2279
2281 grid.SetPointEditProfile( true );
2283
2284 // Use the original object as a construction item
2285 std::vector<std::unique_ptr<BOARD_ITEM>> clones;
2286
2287 m_editorBehavior.reset();
2288
2289 if( selection.Size() > 1 )
2290 {
2291 // Multi-selection: check if all items are shapes
2292 std::vector<PCB_SHAPE*> shapes;
2293 bool allShapes = true;
2294 bool anyLocked = false;
2295
2296 for( EDA_ITEM* selItem : selection )
2297 {
2298 if( selItem->Type() == PCB_SHAPE_T )
2299 {
2300 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( selItem );
2301 shapes.push_back( shape );
2302
2303 if( shape->IsLocked() )
2304 anyLocked = true;
2305 }
2306 else
2307 {
2308 allShapes = false;
2309 }
2310 }
2311
2312 if( allShapes && shapes.size() > 1 && ( !anyLocked || overrideLocks ) )
2313 {
2314 m_editorBehavior = std::make_unique<SHAPE_GROUP_POINT_EDIT_BEHAVIOR>(
2315 std::move( shapes ), item );
2316 m_editPoints = std::make_shared<EDIT_POINTS>( item );
2317 m_editorBehavior->MakePoints( *m_editPoints );
2318 }
2319 else
2320 {
2321 return 0;
2322 }
2323 }
2324 else
2325 {
2326 // Single selection: use existing makePoints logic
2327 m_editPoints = makePoints( item );
2328 }
2329
2330 if( !m_editPoints )
2331 return 0;
2332
2333 PCB_SHAPE* graphicItem = dynamic_cast<PCB_SHAPE*>( item );
2334
2335 // Only add the angle_item if we are editing a polygon or zone
2336 if( item->Type() == PCB_ZONE_T || ( graphicItem && graphicItem->GetShape() == SHAPE_T::POLY ) )
2337 {
2338 m_angleItem = std::make_unique<KIGFX::PREVIEW::ANGLE_ITEM>( m_editPoints );
2339 }
2340
2341 m_preview.FreeItems();
2342 m_radiusHelper = nullptr;
2343 getView()->Add( &m_preview );
2344
2347
2348 getView()->Add( m_editPoints.get() );
2349
2350 if( m_angleItem )
2351 getView()->Add( m_angleItem.get() );
2352
2353 setEditedPoint( nullptr );
2354 updateEditedPoint( aEvent );
2355 bool inDrag = false;
2356 bool isConstrained = false;
2357 bool haveSnapLineDirections = false;
2358
2359 auto updateSnapLineDirections =
2360 [&]()
2361 {
2362 std::vector<VECTOR2I> directions;
2363
2364 if( inDrag && m_editedPoint )
2365 {
2366 EDIT_RELATION* relation = nullptr;
2367
2368 if( m_altConstraint )
2369 relation = m_altConstraint.get();
2370 else if( m_editedPoint->IsConstrained() )
2371 relation = m_editedPoint->GetRelation();
2372
2373 directions = getConstraintDirections( relation );
2374 }
2375
2376 if( directions.empty() )
2377 {
2378 grid.SetSnapLineDirections( {} );
2379 grid.SetSnapLineEnd( std::nullopt );
2380 haveSnapLineDirections = false;
2381 }
2382 else
2383 {
2384 VECTOR2I origin = m_altConstraint ? m_altConstrainer.GetPosition() : m_original.GetPosition();
2385
2386 grid.SetSnapLineDirections( directions );
2387 grid.SetSnapLineOrigin( origin );
2388 grid.SetSnapLineEnd( std::nullopt );
2389 haveSnapLineDirections = true;
2390 }
2391 };
2392
2393 BOARD_COMMIT commit( editFrame );
2394
2395 auto installFeasibilityCallback =
2396 [&]()
2397 {
2398 grid.SetFeasibilityCallback( {} );
2399
2400 if( !m_constraintDragSession || dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
2401 return;
2402
2403 std::shared_ptr<BOARD_CONSTRAINT_DRAG_SESSION> session = m_constraintDragSession;
2404
2405 grid.SetFeasibilityCallback(
2406 [session]( const SNAP_SOURCE_CONTEXT& aContext,
2407 const std::vector<SNAP_CANDIDATE>& aCandidates )
2408 {
2409 return session->ResolveCandidates( aContext, aCandidates );
2410 } );
2411 };
2412
2413 // Main loop: keep receiving events
2414 while( TOOL_EVENT* evt = Wait() )
2415 {
2416 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
2417 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
2418 installFeasibilityCallback();
2419
2420 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
2422 else
2424
2425 if( !m_editPoints || evt->IsSelectionEvent() || evt->Matches( EVENTS::InhibitSelectionEditing ) )
2426 {
2427 break;
2428 }
2429
2430 EDIT_POINT* prevHover = m_hoveredPoint;
2431
2432 if( !inDrag )
2433 updateEditedPoint( *evt );
2434
2435 if( prevHover != m_hoveredPoint )
2436 {
2437 getView()->Update( m_editPoints.get() );
2438
2439 if( m_angleItem )
2440 getView()->Update( m_angleItem.get() );
2441 }
2442
2443 if( evt->IsDrag( BUT_LEFT ) && m_editedPoint )
2444 {
2445 if( !inDrag )
2446 {
2447 frame()->UndoRedoBlock( true );
2448
2449 if( item->Type() == PCB_GENERATOR_T )
2450 {
2451 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genStartEdit, &commit,
2452 static_cast<PCB_GENERATOR*>( item ) );
2453 }
2454
2456 m_original = *m_editedPoint; // Save the original position
2457 getViewControls()->SetAutoPan( true );
2458 inDrag = true;
2459
2460 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
2461 {
2462 if( std::optional<CONSTRAINT_MEMBER> member =
2464 {
2465 m_constraintDragSession = std::make_shared<BOARD_CONSTRAINT_DRAG_SESSION>();
2466
2467 if( !m_constraintDragSession->Build( board(), *member ) )
2469
2470 installFeasibilityCallback();
2471 }
2472 }
2473
2474 if( m_editedPoint->GetGridConstraint() != SNAP_BY_GRID )
2475 grid.SetAuxAxes( true, m_original.GetPosition() );
2476
2477 m_editedPoint->SetActive();
2478
2479 for( size_t ii = 0; ii < m_editPoints->PointsSize(); ++ii )
2480 {
2481 EDIT_POINT& point = m_editPoints->Point( ii );
2482
2483 if( &point != m_editedPoint )
2484 point.SetActive( false );
2485 }
2486
2487 // When we start dragging, create a clone of the item to use as the original
2488 // reference geometry (e.g. for intersections and extensions)
2489 BOARD_ITEM* clone = static_cast<BOARD_ITEM*>( item->Clone() );
2490 clone->SetParent( nullptr );
2491
2492 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2493 {
2494 shape->SetFlags( IS_MOVING );
2495 shape->UpdateHatching();
2496
2497 static_cast<PCB_SHAPE*>( clone )->SetFillMode( FILL_T::NO_FILL );
2498 }
2499
2500 clones.emplace_back( clone );
2501 grid.AddConstructionItems( { clone }, false, true );
2502
2503 updateSnapLineDirections();
2504 }
2505
2506 EDIT_LINE* line = dynamic_cast<EDIT_LINE*>( m_editedPoint );
2507 bool ctrlHeld = evt->Modifier( MD_CTRL );
2508
2509 bool need_constraint = ( Is45Limited() || Is90Limited() ) && !ctrlHeld;
2510
2511 if( isConstrained != need_constraint )
2512 {
2513 setAltConstraint( need_constraint );
2514 isConstrained = need_constraint;
2515 updateSnapLineDirections();
2516 }
2517
2518 if( need_constraint )
2519 {
2520 VECTOR2I origin = m_altConstraint ? m_altConstrainer.GetPosition()
2521 : m_original.GetPosition();
2522 grid.SetAngleRestriction( origin, Is45Limited() ? 45.0 : 90.0 );
2523 }
2524 else
2525 {
2526 grid.SetAngleRestriction( std::nullopt, 0.0 );
2527 }
2528
2529 // For polygon lines, Ctrl temporarily toggles between CONVERGING and FIXED_LENGTH modes
2530
2531 if( line )
2532 {
2533 bool isPoly = false;
2534
2535 switch( item->Type() )
2536 {
2537 case PCB_ZONE_T:
2538 isPoly = true;
2539 break;
2540
2541 case PCB_SHAPE_T:
2542 isPoly = static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY;
2543 break;
2544
2545 default:
2546 break;
2547 }
2548
2549 if( isPoly )
2550 {
2551 POLYGON_EDGE_DRAG_POLICY* policy = line->GetDragPolicy();
2552
2553 if( policy )
2554 {
2557
2558 if( policy->GetMode() != targetMode )
2559 policy->SetMode( targetMode );
2560 }
2561 }
2562 }
2563
2564 // Keep point inside of limits with some padding
2565 VECTOR2I pos = GetClampedCoords<double, int>( evt->Position(), COORDS_PADDING );
2566 LSET snapLayers;
2567
2568 switch( m_editedPoint->GetSnapConstraint() )
2569 {
2570 case IGNORE_SNAPS: break;
2571 case OBJECT_LAYERS: snapLayers = item->GetLayerSet(); break;
2572 case ALL_LAYERS: snapLayers = LSET::AllLayersMask(); break;
2573 }
2574
2575 if( m_editedPoint->GetGridConstraint() == SNAP_BY_GRID )
2576 {
2577 if( grid.GetUseGrid() )
2578 {
2579 POLYGON_EDGE_DRAG_POLICY* dragPolicy =
2580 line ? line->GetDragPolicy() : nullptr;
2581
2582 bool snappedAlongPerp = false;
2583
2584 if( dragPolicy )
2585 {
2586 // For a polygon edge, the line moves only perpendicular to itself.
2587 // Snapping pos.x and pos.y independently to the axis-aligned grid
2588 // produces inconsistent perpendicular displacements when the edge is
2589 // tilted (different magnitudes depending on which axis crossed the
2590 // half-grid threshold first), causing the rendered edge to flicker
2591 // between two positions. Quantize the perpendicular displacement
2592 // directly so each grid step produces one stable line position.
2593 const VECTOR2I& origCenter = dragPolicy->GetOriginalCenter();
2594 const VECTOR2I& perpVec = dragPolicy->GetPerpVector();
2595 double perpLen = VECTOR2D( perpVec ).EuclideanNorm();
2596
2597 if( perpLen > 0 )
2598 {
2599 VECTOR2D perpUnit = VECTOR2D( perpVec ) / perpLen;
2600 VECTOR2D gridSize = grid.GetGridSize( grid.GetItemGrid( item ) );
2601
2602 // Effective grid spacing along the perpendicular direction. For an
2603 // axis-aligned edge this reduces to the grid pitch on that axis.
2604 double step = std::hypot( gridSize.x * perpUnit.x,
2605 gridSize.y * perpUnit.y );
2606
2607 if( step > 0 )
2608 {
2609 double offset = VECTOR2D( pos - origCenter ).Dot( perpUnit );
2610 double snapped = std::round( offset / step ) * step;
2611 VECTOR2D snappedPt = VECTOR2D( origCenter ) + perpUnit * snapped;
2612 pos = VECTOR2I( KiROUND( snappedPt.x ), KiROUND( snappedPt.y ) );
2613 snappedAlongPerp = true;
2614 }
2615 }
2616 }
2617
2618 if( !snappedAlongPerp )
2619 {
2620 VECTOR2I gridPt =
2621 grid.ResolveSnap( pos, {}, grid.GetItemGrid( item ), { item } )
2622 .position;
2623
2624 VECTOR2I last = m_editedPoint->GetPosition();
2625 VECTOR2I delta = pos - last;
2626 VECTOR2I deltaGrid =
2627 gridPt
2628 - grid.ResolveSnap( last, {}, grid.GetItemGrid( item ), { item } )
2629 .position;
2630
2631 if( abs( delta.x ) > grid.GetGrid().x / 2 )
2632 pos.x = last.x + deltaGrid.x;
2633 else
2634 pos.x = last.x;
2635
2636 if( abs( delta.y ) > grid.GetGrid().y / 2 )
2637 pos.y = last.y + deltaGrid.y;
2638 else
2639 pos.y = last.y;
2640 }
2641 }
2642 }
2643
2644 if( m_angleSnapActive )
2645 {
2646 m_stickyDisplacement = evt->Position() - m_angleSnapPos;
2647 int stickyLimit = KiROUND( getView()->ToWorld( 5 ) );
2648
2649 if( m_stickyDisplacement.EuclideanNorm() > stickyLimit || evt->Modifier( MD_SHIFT ) )
2650 {
2651 m_angleSnapActive = false;
2652 }
2653 else
2654 {
2655 pos = m_angleSnapPos;
2656 }
2657 }
2658
2659 bool isFreePolygon =
2660 item->Type() == PCB_ZONE_T
2661 || ( item->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY );
2662
2663 if( isFreePolygon && !m_angleSnapActive && m_editPoints->PointsSize() > 2 && !evt->Modifier( MD_SHIFT ) )
2664 {
2665 int idx = getEditedPointIndex();
2666
2667 if( idx != wxNOT_FOUND )
2668 {
2669 int prevIdx = ( idx + m_editPoints->PointsSize() - 1 ) % m_editPoints->PointsSize();
2670 int nextIdx = ( idx + 1 ) % m_editPoints->PointsSize();
2671 VECTOR2I prev = m_editPoints->Point( prevIdx ).GetPosition();
2672 VECTOR2I next = m_editPoints->Point( nextIdx ).GetPosition();
2673 SEG segA( pos, prev );
2674 SEG segB( pos, next );
2675 double ang = segA.Angle( segB ).AsDegrees();
2676 double snapAng = 45.0 * std::round( ang / 45.0 );
2677
2678 if( std::abs( ang - snapAng ) < 2.0 )
2679 {
2680 VECTOR2I snapped = snapCorner( prev, next, pos, snapAng );
2681
2682 if( m_editedPoint->GetGridConstraint() == SNAP_TO_GRID && grid.GetSnap() )
2683 {
2684 VECTOR2I gridded =
2685 grid.ResolveSnap( snapped, {}, grid.GetItemGrid( item ), { item } )
2686 .position;
2687 double griddedAng = SEG( gridded, prev ).Angle( SEG( gridded, next ) ).AsDegrees();
2688
2689 snapped = std::abs( griddedAng - snapAng ) < 2.0 ? gridded : pos;
2690 }
2691
2692 if( snapped != pos )
2693 {
2694 m_angleSnapPos = snapped;
2695 m_angleSnapActive = true;
2696 m_stickyDisplacement = evt->Position() - m_angleSnapPos;
2697 pos = m_angleSnapPos;
2698 }
2699 }
2700 }
2701 }
2702
2703 bool constraintSnapped = false;
2704 std::vector<VECTOR2I> stationarySelfPoints;
2705 std::vector<SEG> stationarySelfSegments;
2706
2707 if( item->Type() == PCB_ZONE_T
2708 || ( item->Type() == PCB_SHAPE_T
2709 && static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY ) )
2710 {
2711 const EDIT_LINE* editedLine = dynamic_cast<const EDIT_LINE*>( m_editedPoint );
2712
2713 for( unsigned i = 0; i < m_editPoints->PointsSize(); ++i )
2714 {
2715 const EDIT_POINT& point = m_editPoints->Point( i );
2716
2717 if( &point != m_editedPoint
2718 && ( !editedLine
2719 || ( &point != &editedLine->GetOrigin()
2720 && &point != &editedLine->GetEnd() ) ) )
2721 {
2722 stationarySelfPoints.push_back( point.GetPosition() );
2723 }
2724 }
2725
2726 for( unsigned i = 0; i < m_editPoints->LinesSize(); ++i )
2727 {
2728 const EDIT_LINE& stationaryLine = m_editPoints->Line( i );
2729
2730 if( &stationaryLine != editedLine
2731 && &stationaryLine.GetOrigin() != m_editedPoint
2732 && &stationaryLine.GetEnd() != m_editedPoint
2733 && ( !editedLine
2734 || ( &stationaryLine.GetOrigin() != &editedLine->GetOrigin()
2735 && &stationaryLine.GetOrigin() != &editedLine->GetEnd()
2736 && &stationaryLine.GetEnd() != &editedLine->GetOrigin()
2737 && &stationaryLine.GetEnd() != &editedLine->GetEnd() ) ) )
2738 {
2739 stationarySelfSegments.emplace_back(
2740 stationaryLine.GetOrigin().GetPosition(),
2741 stationaryLine.GetEnd().GetPosition() );
2742 }
2743 }
2744 }
2745 else if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
2746 {
2747 int editedIndex = getEditedPointIndex();
2748
2749 if( shape->GetShape() == SHAPE_T::RECTANGLE && editedIndex >= RECT_TOP_LEFT
2750 && editedIndex <= RECT_BOT_LEFT )
2751 {
2752 int opposite = ( editedIndex + 2 ) % 4;
2753 stationarySelfPoints.push_back( m_editPoints->Point( opposite ).GetPosition() );
2754 }
2755 else if( shape->GetShape() == SHAPE_T::RECTANGLE )
2756 {
2757 for( unsigned i = 0; i < m_editPoints->LinesSize() && i < 4; ++i )
2758 {
2759 if( m_editedPoint != &m_editPoints->Line( i ) )
2760 continue;
2761
2762 const EDIT_LINE& opposite = m_editPoints->Line( ( i + 2 ) % 4 );
2763 stationarySelfPoints.push_back( opposite.GetOrigin().GetPosition() );
2764 stationarySelfPoints.push_back( opposite.GetEnd().GetPosition() );
2765 stationarySelfSegments.emplace_back( opposite.GetOrigin().GetPosition(),
2766 opposite.GetEnd().GetPosition() );
2767 break;
2768 }
2769 }
2770 else if( shape->GetShape() == SHAPE_T::ARC )
2771 {
2772 constexpr int arcStart = 0;
2773 constexpr int arcMid = 1;
2774 constexpr int arcEnd = 2;
2775 constexpr int arcCenter = 3;
2776
2778 {
2779 if( editedIndex != arcStart )
2780 stationarySelfPoints.push_back( m_editPoints->Point( arcStart ).GetPosition() );
2781
2782 if( editedIndex != arcEnd )
2783 stationarySelfPoints.push_back( m_editPoints->Point( arcEnd ).GetPosition() );
2784 }
2785 else if( editedIndex == arcStart || editedIndex == arcMid || editedIndex == arcEnd )
2786 {
2787 stationarySelfPoints.push_back( m_editPoints->Point( arcCenter ).GetPosition() );
2788 }
2789 }
2790 }
2791
2792 grid.SetStationarySelfGeometry( std::move( stationarySelfPoints ),
2793 std::move( stationarySelfSegments ) );
2794
2795 // Apply 45 degree or other constraints
2797 {
2798 m_editedPoint->SetPosition(
2799 grid.ResolveSnap( pos, snapLayers, grid.GetItemGrid( item ), { item } )
2800 .position );
2801 constraintSnapped = true;
2802 }
2803 else if( !m_angleSnapActive && m_editedPoint->IsConstrained() )
2804 {
2805 m_editedPoint->SetPosition( pos );
2806 m_editedPoint->ApplyRelation( grid );
2807 constraintSnapped = true;
2808
2809 // For constrained lines (like zone edges), try to snap to nearby anchors
2810 // that lie on the constraint line. First get the constrained position, then
2811 // look for snap anchors and verify they're on the constraint line.
2812 if( grid.GetSnap() && !snapLayers.empty() )
2813 {
2814 VECTOR2I constrainedPos = m_editedPoint->GetPosition();
2815 VECTOR2I snapPos =
2816 grid.ResolveSnap( constrainedPos, snapLayers, grid.GetItemGrid( item ),
2817 { item } )
2818 .position;
2819
2820 // Require the relation to preserve the discrete anchor exactly.
2821 if( snapPos != constrainedPos )
2822 {
2823 m_editedPoint->SetPosition( snapPos );
2824 m_editedPoint->ApplyRelation( grid );
2825 VECTOR2I projectedPos = m_editedPoint->GetPosition();
2826
2827 if( projectedPos != snapPos )
2828 m_editedPoint->SetPosition( constrainedPos );
2829 }
2830 }
2831 }
2832 else if( !m_angleSnapActive && m_editedPoint->GetGridConstraint() == SNAP_TO_GRID )
2833 {
2834 m_editedPoint->SetPosition(
2835 grid.ResolveSnap( pos, snapLayers, grid.GetItemGrid( item ), { item } )
2836 .position );
2837 }
2838 else
2839 {
2840 m_editedPoint->SetPosition( pos );
2841 }
2842
2843 if( haveSnapLineDirections )
2844 {
2845 VECTOR2I snapOrigin = m_altConstraint ? m_altConstrainer.GetPosition() : m_original.GetPosition();
2846 grid.SetSnapLineOrigin( snapOrigin );
2847
2848 if( constraintSnapped )
2849 grid.SetSnapLineEnd( m_editedPoint->GetPosition() );
2850 else
2851 grid.SetSnapLineEnd( std::nullopt );
2852 }
2853
2854 updateItem( commit );
2855 getViewControls()->ForceCursorPosition( true, m_editedPoint->GetPosition() );
2856 updatePoints();
2857
2858 if( m_radiusHelper )
2859 {
2860 if( m_editPoints->PointsSize() > RECT_RADIUS
2861 && m_editedPoint == &m_editPoints->Point( RECT_RADIUS ) )
2862 {
2863 if( PCB_SHAPE* rect = dynamic_cast<PCB_SHAPE*>( item ) )
2864 {
2865 int radius = rect->GetCornerRadius();
2866 int offset = radius - M_SQRT1_2 * radius;
2867 VECTOR2I topLeft = rect->GetTopLeft();
2868 VECTOR2I botRight = rect->GetBotRight();
2869 VECTOR2I topRight( botRight.x, topLeft.y );
2870 VECTOR2I center( topRight.x - offset, topRight.y + offset );
2871 m_radiusHelper->Set( radius, center, VECTOR2I( 1, -1 ), editFrame->GetUserUnits() );
2872 }
2873 }
2874 else
2875 {
2876 m_radiusHelper->Hide();
2877 }
2878 }
2879
2880 getView()->Update( &m_preview );
2881 }
2882 else if( m_editedPoint && evt->Action() == TA_MOUSE_DOWN && evt->Buttons() == BUT_LEFT )
2883 {
2884 m_editedPoint->SetActive();
2885
2886 for( size_t ii = 0; ii < m_editPoints->PointsSize(); ++ii )
2887 {
2888 EDIT_POINT& point = m_editPoints->Point( ii );
2889
2890 if( &point != m_editedPoint )
2891 point.SetActive( false );
2892 }
2893
2894 getView()->Update( m_editPoints.get() );
2895
2896 if( m_angleItem )
2897 getView()->Update( m_angleItem.get() );
2898 }
2899 else if( inDrag && evt->IsMouseUp( BUT_LEFT ) )
2900 {
2901 if( m_editedPoint )
2902 {
2903 m_editedPoint->SetActive( false );
2904 getView()->Update( m_editPoints.get() );
2905
2906 if( m_angleItem )
2907 getView()->Update( m_angleItem.get() );
2908 }
2909
2910 if( m_radiusHelper )
2911 m_radiusHelper->Hide();
2912
2913 getView()->Update( &m_preview );
2914
2915 getViewControls()->SetAutoPan( false );
2916 setAltConstraint( false );
2917 updateSnapLineDirections();
2918
2919 if( m_editorBehavior )
2920 m_editorBehavior->FinalizeItem( *m_editPoints, commit );
2921
2922 if( item->Type() == PCB_GENERATOR_T )
2923 {
2924 PCB_GENERATOR* generator = static_cast<PCB_GENERATOR*>( item );
2925
2926 m_preview.FreeItems();
2927 m_radiusHelper = nullptr;
2928 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genFinishEdit, &commit, generator );
2929
2930 commit.Push( generator->GetCommitMessage() );
2931 }
2932 else if( item->Type() == PCB_TABLECELL_T )
2933 {
2934 commit.Push( _( "Resize Table Cells" ) );
2935 }
2936 else
2937 {
2938 commit.Push( _( "Move Point" ) );
2939 }
2940
2941 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2942 {
2943 shape->ClearFlags( IS_MOVING );
2944 shape->UpdateHatching();
2945 }
2946
2947 inDrag = false;
2949 frame()->UndoRedoBlock( false );
2950 updateSnapLineDirections();
2951
2952 m_toolMgr->PostAction<EDA_ITEM*>( ACTIONS::reselectItem, item ); // FIXME: Needed for generators
2953 }
2954 else if( evt->IsCancelInteractive() || evt->IsActivate() )
2955 {
2956 if( inDrag ) // Restore the last change
2957 {
2958 if( item->Type() == PCB_GENERATOR_T )
2959 {
2960 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genCancelEdit, &commit,
2961 static_cast<PCB_GENERATOR*>( item ) );
2962 }
2963
2964 commit.Revert();
2965
2966 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2967 {
2968 shape->ClearFlags( IS_MOVING );
2969 shape->UpdateHatching();
2970 }
2971
2972 inDrag = false;
2974 frame()->UndoRedoBlock( false );
2975 updateSnapLineDirections();
2976 }
2977
2978 // Only cancel point editor when activating a new tool
2979 // Otherwise, allow the points to persist when moving up the
2980 // tool stack
2981 if( evt->IsActivate() && !evt->IsMoveTool() )
2982 break;
2983 }
2984 else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
2985 {
2986 // Re-create the points for items which can have different behavior on different layers
2987 if( item->Type() == PCB_PAD_T && m_isFootprintEditor )
2988 {
2989 if( getView()->HasItem( m_editPoints.get() ) )
2990 getView()->Remove( m_editPoints.get() );
2991
2992 if( m_angleItem && getView()->HasItem( m_angleItem.get() ) )
2993 getView()->Remove( m_angleItem.get() );
2994
2995 m_editPoints = makePoints( item );
2996
2997 if( m_angleItem )
2998 {
2999 m_angleItem->SetEditPoints( m_editPoints );
3000 getView()->Add( m_angleItem.get() );
3001 }
3002
3003 getView()->Add( m_editPoints.get() );
3004 }
3005 }
3006 else if( evt->Action() == TA_UNDO_REDO_POST )
3007 {
3008 break;
3009 }
3010 else
3011 {
3012 evt->SetPassEvent();
3013 }
3014 }
3015
3016 // IS_MOVING is only still set if the loop broke mid-drag, and a null m_editPoints means
3017 // Reset() ran while we were suspended, so the board and item have already been destroyed
3018 if( inDrag && m_editPoints )
3019 {
3020 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( m_editPoints->GetParent() ) )
3021 {
3022 shape->ClearFlags( IS_MOVING );
3023 shape->UpdateHatching();
3024 }
3025 }
3026
3027 m_preview.FreeItems();
3028 m_radiusHelper = nullptr;
3030
3031 if( getView()->HasItem( &m_preview ) )
3032 getView()->Remove( &m_preview );
3033
3034 if( m_editPoints )
3035 {
3036 if( getView()->HasItem( m_editPoints.get() ) )
3037 getView()->Remove( m_editPoints.get() );
3038
3039 if( m_angleItem && getView()->HasItem( m_angleItem.get() ) )
3040 getView()->Remove( m_angleItem.get() );
3041
3042 m_editPoints.reset();
3043 m_angleItem.reset();
3044 }
3045
3046 m_editedPoint = nullptr;
3047 grid.SetSnapLineDirections( {} );
3048
3049 return 0;
3050}
3051
3052
3054{
3055 if( !m_editPoints || !m_editPoints->GetParent() || !HasPoint() )
3056 return 0;
3057
3059
3060 BOARD_COMMIT commit( editFrame );
3061 commit.Stage( m_editPoints->GetParent(), CHT_MODIFY );
3062
3063 VECTOR2I pt = m_editedPoint->GetPosition();
3064 wxString title;
3065 wxString msg;
3066
3067 if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
3068 {
3069 title = _( "Move Midpoint to Location" );
3070 msg = _( "Move Midpoint" );
3071 }
3072 else
3073 {
3074 title = _( "Move Corner to Location" );
3075 msg = _( "Move Corner" );
3076 }
3077
3078 WX_PT_ENTRY_DIALOG dlg( editFrame, title, _( "X:" ), _( "Y:" ), pt, false );
3079
3080 if( dlg.ShowModal() == wxID_OK )
3081 {
3082 m_editedPoint->SetPosition( dlg.GetValue() );
3083 updateItem( commit );
3084 commit.Push( msg );
3085 }
3086
3087 return 0;
3088}
3089
3090
3092{
3093 wxCHECK( m_editPoints, /* void */ );
3094 EDA_ITEM* item = m_editPoints->GetParent();
3095
3096 if( !item )
3097 return;
3098
3099 // item is always updated
3100 std::vector<EDA_ITEM*> updatedItems = { item };
3101 aCommit.Modify( item );
3102
3103 if( m_editorBehavior )
3104 {
3105 wxCHECK( m_editedPoint, /* void */ );
3106 m_editorBehavior->UpdateItem( *m_editedPoint, *m_editPoints, aCommit, updatedItems );
3107 }
3108
3109 // Re-derive any geometry constrained to the dragged segment endpoint (issue #2329). The
3110 // behavior above has already moved the dragged point to the cursor, so its current endpoint
3111 // position is the pin target for the solver.
3112 auto anyConstraints =
3113 [&]() -> bool
3114 {
3115 if( !board() )
3116 return false;
3117
3118 if( !board()->Constraints().empty() )
3119 return true;
3120
3121 // In the footprint editor the constraints live on the footprint, not the board.
3122 for( FOOTPRINT* fp : board()->Footprints() )
3123 {
3124 if( !fp->Constraints().empty() )
3125 return true;
3126 }
3127
3128 return false;
3129 };
3130
3131 if( item->Type() == PCB_SHAPE_T && m_editedPoint && anyConstraints() )
3132 {
3133 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3134 SHAPE_T type = shape->GetShape();
3135 VECTOR2I cursor = m_editedPoint->GetPosition();
3136
3137 std::optional<CONSTRAINT_MEMBER> member;
3138 std::optional<std::pair<CONSTRAINT_MEMBER, VECTOR2I>> coDragged;
3139
3140 // Match dragged point to a constraint anchor segment bezier and arc expose endpoints
3141 // arc and circle also expose a centre bezier has none so centre test stays arc only
3142 if( type == SHAPE_T::SEGMENT || type == SHAPE_T::ARC || type == SHAPE_T::BEZIER )
3143 {
3144 if( cursor == shape->GetStart() )
3146 else if( cursor == shape->GetEnd() )
3148 else if( type == SHAPE_T::ARC && cursor == shape->GetCenter() )
3150 }
3151 else if( type == SHAPE_T::CIRCLE )
3152 {
3153 if( cursor == shape->GetCenter() )
3155 }
3156 else if( type == SHAPE_T::RECTANGLE && m_editPoints->PointsSize() >= RECT_MAX_POINTS )
3157 {
3158 // Only corner handles map to vertex anchors centre radius and side handles reshape whole rect
3159 // corners kept in min max order so ordinal equals vertex index solve target reads post clamp position
3160 for( unsigned i = RECT_TOP_LEFT; i <= RECT_BOT_LEFT; ++i )
3161 {
3162 if( isModified( m_editPoints->Point( i ) ) )
3163 {
3164 if( std::optional<CONSTRAINT_ANCHOR_POINT> corner = ConstraintShapeVertex( shape, (int) i ) )
3165 {
3166 member = CONSTRAINT_MEMBER( shape->m_Uuid, corner->anchor, corner->index );
3167 cursor = corner->pos;
3168 }
3169
3170 break;
3171 }
3172 }
3173
3174 // Side handle drags one edge param aliased by both corners side i runs corner i to i plus 1 mod 4
3175 // pinning corner i covers dragged plus one perpendicular param opposite corner hold covers the rest
3176 if( !member.has_value() )
3177 {
3178 for( unsigned i = 0; i < m_editPoints->LinesSize() && i < 4; ++i )
3179 {
3180 if( isModified( m_editPoints->Line( i ) ) )
3181 {
3182 if( std::optional<CONSTRAINT_ANCHOR_POINT> corner = ConstraintShapeVertex( shape, (int) i ) )
3183 {
3184 member = CONSTRAINT_MEMBER( shape->m_Uuid, corner->anchor, corner->index );
3185 cursor = corner->pos;
3186 }
3187
3188 break;
3189 }
3190 }
3191 }
3192 }
3193 else if( type == SHAPE_T::POLY && ConstraintPolygonIsModelable( shape ) )
3194 {
3195 // Editor builds one edit point per outline vertex in order so dragged ordinal is vertex index
3196 // holds even when a drag lands a vertex on top of another where position match would be ambiguous
3197 for( unsigned i = 0; i < m_editPoints->PointsSize(); ++i )
3198 {
3199 if( isModified( m_editPoints->Point( i ) ) )
3200 {
3201 if( std::optional<CONSTRAINT_ANCHOR_POINT> vertex = ConstraintShapeVertex( shape, (int) i ) )
3202 {
3203 member = CONSTRAINT_MEMBER( shape->m_Uuid, vertex->anchor, vertex->index );
3204 cursor = vertex->pos;
3205 }
3206
3207 break;
3208 }
3209 }
3210
3211 // Edge handle moves two adjacent vertices one member cannot express both so second vertex
3212 // rides as a co dragged pin line i runs vertex i to i plus 1 mod count positions read back post move
3213 if( !member.has_value() )
3214 {
3215 for( unsigned i = 0; i < m_editPoints->LinesSize(); ++i )
3216 {
3217 if( isModified( m_editPoints->Line( i ) ) )
3218 {
3219 int next = ( (int) i + 1 ) % (int) m_editPoints->PointsSize();
3220
3221 std::optional<CONSTRAINT_ANCHOR_POINT> v0 = ConstraintShapeVertex( shape, (int) i );
3222 std::optional<CONSTRAINT_ANCHOR_POINT> v1 = ConstraintShapeVertex( shape, next );
3223
3224 if( v0 && v1 )
3225 {
3226 member = CONSTRAINT_MEMBER( shape->m_Uuid, v0->anchor, v0->index );
3227 cursor = v0->pos;
3228 coDragged = { CONSTRAINT_MEMBER( shape->m_Uuid, v1->anchor, v1->index ), v1->pos };
3229 }
3230
3231 break;
3232 }
3233 }
3234 }
3235 }
3236
3237 bool isCurve = type == SHAPE_T::CIRCLE || type == SHAPE_T::ARC || type == SHAPE_T::ELLIPSE
3238 || type == SHAPE_T::ELLIPSE_ARC;
3239
3240 std::vector<PCB_SHAPE*> modified;
3241
3242 // Failed or diverged solve leaves neighbors untouched below so nothing is half moved this frame
3243 // moved shapes report in modified moved dimensions do not so refresh view here or they freeze
3244 auto stageNeighbor = [&]( BOARD_ITEM* aItem )
3245 {
3246 aCommit.Modify( aItem );
3247
3248 if( aItem->Type() != PCB_SHAPE_T )
3249 updatedItems.push_back( aItem );
3250 };
3251
3252 if( member.has_value() )
3253 {
3254 if( !m_constraintDragSession || !m_constraintDragSession->Matches( *member ) )
3255 {
3256 m_constraintDragSession = std::make_shared<BOARD_CONSTRAINT_DRAG_SESSION>();
3257
3258 if( !m_constraintDragSession->Build( board(), *member ) )
3260 }
3261
3263 {
3264 m_constraintDragSession->Solve( cursor, &modified, stageNeighbor,
3265 /* aIncludeDragged */ false,
3266 /* aStabilize */ false, {}, coDragged );
3267 }
3268 else
3269 {
3270 SolveCluster( board(), member.value(), cursor, &modified, stageNeighbor,
3271 /* aIncludeDragged */ false, /* aStabilize */ false, {}, coDragged );
3272 }
3273 }
3274 else if( isCurve )
3275 {
3276 // Radius or axis handle not a constrained point resize hold keeps new radius but yields
3277 // to a real radius constraint full dof hold below would fight it so curves use this instead
3278 ReSolveAfterShapeResize( board(), shape, &modified, stageNeighbor );
3279 }
3280 else if( type == SHAPE_T::RECTANGLE || ( type == SHAPE_T::POLY && ConstraintPolygonIsModelable( shape ) ) )
3281 {
3282 // Only whole shape handles centre and corner radius grips land here side corner and edge
3283 // drags pinned members above already treated like a properties edit new geometry wins neighbors follow
3284 ReSolveShapeClustersHoldingEdited( board(), { shape }, &modified, stageNeighbor );
3285 }
3286
3287 for( PCB_SHAPE* neighbor : modified )
3288 updatedItems.push_back( neighbor );
3289 }
3290
3291 // Perform any post-edit actions that the item may require
3292
3293 switch( item->Type() )
3294 {
3295 case PCB_TEXTBOX_T:
3296 case PCB_SHAPE_T:
3297 {
3298 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3299
3300 if( shape->IsProxyItem() )
3301 {
3302 for( PAD* pad : shape->GetParentFootprint()->Pads() )
3303 {
3304 if( pad->IsEntered() )
3305 view()->Update( pad );
3306 }
3307 }
3308
3309 // Nuke outline font render caches
3310 if( PCB_TEXTBOX* textBox = dynamic_cast<PCB_TEXTBOX*>( item ) )
3311 textBox->ClearRenderCache();
3312
3313 break;
3314 }
3315 case PCB_GENERATOR_T:
3316 {
3317 GENERATOR_TOOL* generatorTool = m_toolMgr->GetTool<GENERATOR_TOOL>();
3318 PCB_GENERATOR* generatorItem = static_cast<PCB_GENERATOR*>( item );
3319
3320 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genUpdateEdit, &aCommit, generatorItem );
3321
3322 // Note: POINT_EDITOR::m_preview holds only the canvas-draw status "popup"; the meanders
3323 // themselves (ROUTER_PREVIEW_ITEMs) are owned by the router.
3324
3325 m_preview.FreeItems();
3326 m_radiusHelper = nullptr;
3327
3328 for( EDA_ITEM* previewItem : generatorItem->GetPreviewItems( generatorTool, frame(), STATUS_ITEMS_ONLY ) )
3329 m_preview.Add( previewItem );
3330
3331 getView()->Update( &m_preview );
3332 break;
3333 }
3334 default:
3335 break;
3336 }
3337
3338 // Update the item and any affected items
3339 for( EDA_ITEM* updatedItem : updatedItems )
3340 getView()->Update( updatedItem );
3341
3342 frame()->SetMsgPanel( item );
3343}
3344
3345
3347{
3348 if( !m_editPoints )
3349 return;
3350
3351 EDA_ITEM* item = m_editPoints->GetParent();
3352
3353 if( !item )
3354 return;
3355
3356 if( !m_editorBehavior )
3357 return;
3358
3359 int editedIndex = -1;
3360 bool editingLine = false;
3361
3362 if( m_editedPoint )
3363 {
3364 // Check if we're editing a point (vertex)
3365 for( unsigned ii = 0; ii < m_editPoints->PointsSize(); ++ii )
3366 {
3367 if( &m_editPoints->Point( ii ) == m_editedPoint )
3368 {
3369 editedIndex = ii;
3370 break;
3371 }
3372 }
3373
3374 // If not found in points, check if we're editing a line (midpoint)
3375 if( editedIndex == -1 )
3376 {
3377 for( unsigned ii = 0; ii < m_editPoints->LinesSize(); ++ii )
3378 {
3379 if( &m_editPoints->Line( ii ) == m_editedPoint )
3380 {
3381 editedIndex = ii;
3382 editingLine = true;
3383 break;
3384 }
3385 }
3386 }
3387 }
3388
3389 if( !m_editorBehavior->UpdatePoints( *m_editPoints ) )
3390 {
3391 if( getView()->HasItem( m_editPoints.get() ) )
3392 getView()->Remove( m_editPoints.get() );
3393
3394 m_editPoints = makePoints( item );
3395 getView()->Add( m_editPoints.get() );
3396 }
3397
3398 if( editedIndex >= 0 )
3399 {
3400 if( editingLine && editedIndex < (int) m_editPoints->LinesSize() )
3401 m_editedPoint = &m_editPoints->Line( editedIndex );
3402 else if( !editingLine && editedIndex < (int) m_editPoints->PointsSize() )
3403 m_editedPoint = &m_editPoints->Point( editedIndex );
3404 else
3405 m_editedPoint = nullptr;
3406 }
3407 else
3408 {
3409 m_editedPoint = nullptr;
3410 }
3411
3412 getView()->Update( m_editPoints.get() );
3413
3414 if( m_angleItem )
3415 getView()->Update( m_angleItem.get() );
3416}
3417
3418
3420{
3422
3423 if( aPoint )
3424 {
3425 frame()->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
3426 controls->ForceCursorPosition( true, aPoint->GetPosition() );
3427 controls->ShowCursor( true );
3428 }
3429 else
3430 {
3431 if( frame()->ToolStackIsEmpty() )
3432 controls->ShowCursor( false );
3433
3434 controls->ForceCursorPosition( false );
3435 }
3436
3437 m_editedPoint = aPoint;
3438}
3439
3440
3442{
3443 EDA_ITEM* parent = m_editPoints ? m_editPoints->GetParent() : nullptr;
3444 EDIT_LINE* line = dynamic_cast<EDIT_LINE*>( m_editedPoint );
3445 bool isPoly = false;
3446
3447 if( parent )
3448 {
3449 switch( parent->Type() )
3450 {
3451 case PCB_ZONE_T:
3452 isPoly = true;
3453 break;
3454
3455 case PCB_SHAPE_T:
3456 isPoly = static_cast<PCB_SHAPE*>( parent )->GetShape() == SHAPE_T::POLY;
3457 break;
3458
3459 default:
3460 break;
3461 }
3462 }
3463
3464 if( aEnabled )
3465 {
3466 if( line && isPoly )
3467 {
3468 // For polygon lines, toggle the mode on the existing constraint rather than
3469 // creating a new one. This preserves the original reference positions.
3470 POLYGON_EDGE_DRAG_POLICY* policy = line->GetDragPolicy();
3471
3472 if( policy )
3474
3475 // Don't set m_altConstraint - we're modifying the line's own constraint
3476 }
3477 else
3478 {
3479 // Find a proper constraining point for angle snapping mode
3481
3482 if( Is90Limited() )
3484 else
3486 }
3487 }
3488 else
3489 {
3490 if( line && isPoly )
3491 {
3492 // Restore the line's constraint to CONVERGING mode
3493 POLYGON_EDGE_DRAG_POLICY* policy = line->GetDragPolicy();
3494
3495 if( policy )
3497 }
3498
3499 m_altConstraint.reset();
3500 }
3501}
3502
3503
3505{
3506 // If there's a behaviour and it provides a constrainer, use that
3507 if( m_editorBehavior )
3508 {
3509 const OPT_VECTOR2I constrainer = m_editorBehavior->Get45DegreeConstrainer( *m_editedPoint, *m_editPoints );
3510
3511 if( constrainer )
3512 return EDIT_POINT( *constrainer );
3513 }
3514
3515 // In any other case we may align item to its original position
3516 return m_original;
3517}
3518
3519
3520// Finds a corresponding vertex in a polygon set
3521static std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX> findVertex( SHAPE_POLY_SET& aPolySet, const EDIT_POINT& aPoint )
3522{
3523 for( auto it = aPolySet.IterateWithHoles(); it; ++it )
3524 {
3525 auto vertexIdx = it.GetIndex();
3526
3527 if( aPolySet.CVertex( vertexIdx ) == aPoint.GetPosition() )
3528 return std::make_pair( true, vertexIdx );
3529 }
3530
3531 return std::make_pair( false, SHAPE_POLY_SET::VERTEX_INDEX() );
3532}
3533
3534
3536{
3537 if( !m_editPoints || !m_editedPoint )
3538 return false;
3539
3540 EDA_ITEM* item = m_editPoints->GetParent();
3541 SHAPE_POLY_SET* polyset = nullptr;
3542
3543 if( !item )
3544 return false;
3545
3546 switch( item->Type() )
3547 {
3548 case PCB_ZONE_T:
3549 polyset = static_cast<ZONE*>( item )->Outline();
3550 break;
3551
3552 case PCB_SHAPE_T:
3553 if( static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY )
3554 polyset = &static_cast<PCB_SHAPE*>( item )->GetPolyShape();
3555 else
3556 return false;
3557
3558 break;
3559
3560 default:
3561 return false;
3562 }
3563
3564 std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX> vertex = findVertex( *polyset, *m_editedPoint );
3565
3566 if( !vertex.first )
3567 return false;
3568
3569 const SHAPE_POLY_SET::VERTEX_INDEX& vertexIdx = vertex.second;
3570
3571 // Check if there are enough vertices so one can be removed without
3572 // degenerating the polygon.
3573 // The first condition allows one to remove all corners from holes (when
3574 // there are only 2 vertices left, a hole is removed).
3575 if( vertexIdx.m_contour == 0
3576 && polyset->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour].PointCount() <= 3 )
3577 {
3578 return false;
3579 }
3580
3581 // Remove corner does not work with lines
3582 if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
3583 return false;
3584
3585 return m_editedPoint != nullptr;
3586}
3587
3588
3590{
3591 if( !m_editPoints )
3592 return 0;
3593
3594 EDA_ITEM* item = m_editPoints->GetParent();
3596 const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
3597
3598 // called without an active edited polygon
3599 if( !item || !CanAddCorner( *item ) )
3600 return 0;
3601
3602 PCB_SHAPE* graphicItem = dynamic_cast<PCB_SHAPE*>( item );
3603 BOARD_COMMIT commit( frame );
3604
3605 if( item->Type() == PCB_ZONE_T || ( graphicItem && graphicItem->GetShape() == SHAPE_T::POLY ) )
3606 {
3607 unsigned int nearestIdx = 0;
3608 unsigned int nextNearestIdx = 0;
3609 unsigned int nearestDist = INT_MAX;
3610 unsigned int firstPointInContour = 0;
3611 SHAPE_POLY_SET* zoneOutline;
3612
3613 if( item->Type() == PCB_ZONE_T )
3614 {
3615 ZONE* zone = static_cast<ZONE*>( item );
3616 zoneOutline = zone->Outline();
3617 zone->SetNeedRefill( true );
3618 }
3619 else
3620 {
3621 zoneOutline = &( graphicItem->GetPolyShape() );
3622 }
3623
3624 commit.Modify( item );
3625
3626 // Search the best outline segment to add a new corner
3627 // and therefore break this segment into two segments
3628
3629 // Object to iterate through the corners of the outlines (main contour and its holes)
3630 SHAPE_POLY_SET::ITERATOR iterator = zoneOutline->Iterate( 0, zoneOutline->OutlineCount()-1,
3631 /* IterateHoles */ true );
3632 int curr_idx = 0;
3633
3634 // Iterate through all the corners of the outlines and search the best segment
3635 for( ; iterator; iterator++, curr_idx++ )
3636 {
3637 int jj = curr_idx+1;
3638
3639 if( iterator.IsEndContour() )
3640 { // We reach the last point of the current contour (main or hole)
3641 jj = firstPointInContour;
3642 firstPointInContour = curr_idx+1; // Prepare next contour analysis
3643 }
3644
3645 SEG curr_segment( zoneOutline->CVertex( curr_idx ), zoneOutline->CVertex( jj ) );
3646
3647 unsigned int distance = curr_segment.Distance( cursorPos );
3648
3649 if( distance < nearestDist )
3650 {
3651 nearestDist = distance;
3652 nearestIdx = curr_idx;
3653 nextNearestIdx = jj;
3654 }
3655 }
3656
3657 // Find the point on the closest segment
3658 const VECTOR2I& sideOrigin = zoneOutline->CVertex( nearestIdx );
3659 const VECTOR2I& sideEnd = zoneOutline->CVertex( nextNearestIdx );
3660 SEG nearestSide( sideOrigin, sideEnd );
3661 VECTOR2I nearestPoint = nearestSide.NearestPoint( cursorPos );
3662
3663 // Do not add points that have the same coordinates as ones that already belong to polygon
3664 // instead, add a point in the middle of the side
3665 if( nearestPoint == sideOrigin || nearestPoint == sideEnd )
3666 nearestPoint = ( sideOrigin + sideEnd ) / 2;
3667
3668 zoneOutline->InsertVertex( nextNearestIdx, nearestPoint );
3669
3670 // Zones cannot carry constraint members but shape polygons can insertion shifts ordinals at or
3671 // past new vertex issue 2329 members only exist on hole free polys index past outline count is a hole vertex
3672 if( graphicItem && nextNearestIdx < (unsigned) zoneOutline->COutline( 0 ).PointCount() )
3673 {
3674 RemapPolygonVertexMembers( frame->GetBoard(), graphicItem->m_Uuid, (int) nextNearestIdx, 1,
3675 [&]( BOARD_ITEM* aConstraint ) { commit.Modify( aConstraint ); },
3676 [&]( BOARD_ITEM* aConstraint ) { commit.Remove( aConstraint ); } );
3677 }
3678
3679 if( item->Type() == PCB_ZONE_T )
3680 static_cast<ZONE*>( item )->HatchBorder();
3681
3682 commit.Push( _( "Add Zone Corner" ) );
3683 }
3684 else if( graphicItem )
3685 {
3686 switch( graphicItem->GetShape() )
3687 {
3688 case SHAPE_T::SEGMENT:
3689 {
3690 commit.Modify( graphicItem );
3691
3692 SEG seg( graphicItem->GetStart(), graphicItem->GetEnd() );
3693 VECTOR2I nearestPoint = seg.NearestPoint( cursorPos );
3694
3695 // Move the end of the line to the break point..
3696 graphicItem->SetEnd( nearestPoint );
3697
3698 // and add another one starting from the break point
3699 PCB_SHAPE* newSegment = static_cast<PCB_SHAPE*>( graphicItem->Duplicate( true, &commit ) );
3700 newSegment->ClearSelected();
3701 newSegment->SetStart( nearestPoint );
3702 newSegment->SetEnd( VECTOR2I( seg.B.x, seg.B.y ) );
3703
3704 commit.Add( newSegment );
3705 commit.Push( _( "Split Segment" ) );
3706 break;
3707 }
3708 case SHAPE_T::ARC:
3709 {
3710 commit.Modify( graphicItem );
3711
3712 const SHAPE_ARC arc( graphicItem->GetStart(), graphicItem->GetArcMid(), graphicItem->GetEnd(), 0 );
3713 const VECTOR2I nearestPoint = arc.NearestPoint( cursorPos );
3714
3715 // Move the end of the arc to the break point..
3716 graphicItem->SetEnd( nearestPoint );
3717
3718 // and add another one starting from the break point
3719 PCB_SHAPE* newArc = static_cast<PCB_SHAPE*>( graphicItem->Duplicate( true, &commit ) );
3720
3721 newArc->ClearSelected();
3722 newArc->SetEnd( arc.GetP1() );
3723 newArc->SetStart( nearestPoint );
3724
3725 commit.Add( newArc );
3726 commit.Push( _( "Split Arc" ) );
3727 break;
3728 }
3729 default:
3730 // No split implemented for other shapes
3731 break;
3732 }
3733 }
3734
3735 updatePoints();
3736 return 0;
3737}
3738
3739
3741{
3742 if( !m_editPoints || !m_editedPoint )
3743 return 0;
3744
3745 EDA_ITEM* item = m_editPoints->GetParent();
3746
3747 if( !item )
3748 return 0;
3749
3750 SHAPE_POLY_SET* polygon = nullptr;
3751
3752 if( item->Type() == PCB_ZONE_T )
3753 {
3754 ZONE* zone = static_cast<ZONE*>( item );
3755 polygon = zone->Outline();
3756 zone->SetNeedRefill( true );
3757 }
3758 else if( item->Type() == PCB_SHAPE_T )
3759 {
3760 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3761
3762 if( shape->GetShape() == SHAPE_T::POLY )
3763 polygon = &shape->GetPolyShape();
3764 }
3765
3766 if( !polygon )
3767 return 0;
3768
3770 BOARD_COMMIT commit( frame );
3771 auto vertex = findVertex( *polygon, *m_editedPoint );
3772
3773 if( vertex.first )
3774 {
3775 const auto& vertexIdx = vertex.second;
3776 auto& outline = polygon->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour];
3777
3778 if( outline.PointCount() > 3 )
3779 {
3780 // the usual case: remove just the corner when there are >3 vertices
3781 commit.Modify( item );
3782 polygon->RemoveVertex( vertexIdx );
3783
3784 // Members only exist on hole free shape polygons where contour relative ordinal is member
3785 // index removed vertex member retires its constraint later ordinals shift down issue 2329
3786 if( item->Type() == PCB_SHAPE_T && vertexIdx.m_contour == 0 )
3787 {
3788 RemapPolygonVertexMembers( frame->GetBoard(), item->m_Uuid, vertexIdx.m_vertex, -1,
3789 [&]( BOARD_ITEM* aConstraint ) { commit.Modify( aConstraint ); },
3790 [&]( BOARD_ITEM* aConstraint ) { commit.Remove( aConstraint ); } );
3791 }
3792 }
3793 else
3794 {
3795 // either remove a hole or the polygon when there are <= 3 corners
3796 if( vertexIdx.m_contour > 0 )
3797 {
3798 // remove hole
3799 commit.Modify( item );
3800 polygon->RemoveContour( vertexIdx.m_contour );
3801 }
3802 else
3803 {
3804 m_toolMgr->RunAction( ACTIONS::selectionClear );
3805 commit.Remove( item );
3806 }
3807 }
3808
3809 setEditedPoint( nullptr );
3810
3811 if( item->Type() == PCB_ZONE_T )
3812 commit.Push( _( "Remove Zone Corner" ) );
3813 else
3814 commit.Push( _( "Remove Polygon Corner" ) );
3815
3816 if( item->Type() == PCB_ZONE_T )
3817 static_cast<ZONE*>( item )->HatchBorder();
3818
3819 updatePoints();
3820 }
3821
3822 return 0;
3823}
3824
3825
3827{
3828 if( !m_editPoints || !m_editedPoint )
3829 return 0;
3830
3831 EDA_ITEM* item = m_editPoints->GetParent();
3832
3833 if( !item )
3834 return 0;
3835
3836 SHAPE_POLY_SET* polygon = nullptr;
3837
3838 if( item->Type() == PCB_ZONE_T )
3839 {
3840 ZONE* zone = static_cast<ZONE*>( item );
3841 polygon = zone->Outline();
3842 zone->SetNeedRefill( true );
3843 }
3844 else if( item->Type() == PCB_SHAPE_T )
3845 {
3846 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3847
3848 if( shape->GetShape() == SHAPE_T::POLY )
3849 polygon = &shape->GetPolyShape();
3850 }
3851
3852 if( !polygon )
3853 return 0;
3854
3855 // Search the best outline corner to break
3856
3858 BOARD_COMMIT commit( frame );
3859 const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
3860
3861 unsigned int nearestIdx = 0;
3862 unsigned int nearestDist = INT_MAX;
3863
3864 int curr_idx = 0;
3865 // Object to iterate through the corners of the outlines (main contour and its holes)
3866 SHAPE_POLY_SET::ITERATOR iterator = polygon->Iterate( 0, polygon->OutlineCount() - 1,
3867 /* IterateHoles */ true );
3868
3869 // Iterate through all the corners of the outlines and search the best segment
3870 for( ; iterator; iterator++, curr_idx++ )
3871 {
3872 unsigned int distance = polygon->CVertex( curr_idx ).Distance( cursorPos );
3873
3874 if( distance < nearestDist )
3875 {
3876 nearestDist = distance;
3877 nearestIdx = curr_idx;
3878 }
3879 }
3880
3881 int prevIdx, nextIdx;
3882 if( polygon->GetNeighbourIndexes( nearestIdx, &prevIdx, &nextIdx ) )
3883 {
3884 const SEG segA{ polygon->CVertex( prevIdx ), polygon->CVertex( nearestIdx ) };
3885 const SEG segB{ polygon->CVertex( nextIdx ), polygon->CVertex( nearestIdx ) };
3886
3887 // A plausible setback that won't consume a whole edge
3888 int setback = pcbIUScale.mmToIU( 5 );
3889 setback = std::min( setback, (int) ( segA.Length() * 0.25 ) );
3890 setback = std::min( setback, (int) ( segB.Length() * 0.25 ) );
3891
3892 CHAMFER_PARAMS chamferParams{ setback, setback };
3893
3894 std::optional<CHAMFER_RESULT> chamferResult = ComputeChamferPoints( segA, segB, chamferParams );
3895
3896 if( chamferResult && chamferResult->m_updated_seg_a && chamferResult->m_updated_seg_b )
3897 {
3898 commit.Modify( item );
3899 polygon->RemoveVertex( nearestIdx );
3900
3901 // The two end points of the chamfer are the new corners
3902 polygon->InsertVertex( nearestIdx, chamferResult->m_updated_seg_b->B );
3903 polygon->InsertVertex( nearestIdx, chamferResult->m_updated_seg_a->B );
3904
3905 // Chamfered corner constraints retire members past it net one higher insert pass must run
3906 // first threshold nearestIdx plus 1 deleting first nets negative one instead issue 2329
3907 if( item->Type() == PCB_SHAPE_T && nearestIdx < (unsigned) polygon->COutline( 0 ).PointCount() )
3908 {
3909 auto modify = [&]( BOARD_ITEM* aConstraint ) { commit.Modify( aConstraint ); };
3910 auto remove = [&]( BOARD_ITEM* aConstraint ) { commit.Remove( aConstraint ); };
3911
3912 RemapPolygonVertexMembers( frame->GetBoard(), item->m_Uuid, (int) nearestIdx + 1, 2, modify, remove );
3913 RemapPolygonVertexMembers( frame->GetBoard(), item->m_Uuid, (int) nearestIdx, -1, modify, remove );
3914 }
3915 }
3916 }
3917
3918 setEditedPoint( nullptr );
3919
3920 if( item->Type() == PCB_ZONE_T )
3921 commit.Push( _( "Break Zone Corner" ) );
3922 else
3923 commit.Push( _( "Break Polygon Corner" ) );
3924
3925 if( item->Type() == PCB_ZONE_T )
3926 static_cast<ZONE*>( item )->HatchBorder();
3927
3928 updatePoints();
3929
3930 return 0;
3931}
3932
3933
3935{
3936 updatePoints();
3937 return 0;
3938}
3939
3940
3942{
3944
3945 if( aEvent.Matches( ACTIONS::cycleArcEditMode.MakeEvent() ) )
3946 {
3947 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
3949 else
3951
3953 }
3954 else
3955 {
3957 }
3958
3959 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
3961 else
3963
3964 return 0;
3965}
3966
3967
3969{
3987}
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:121
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:918
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
static TOOL_ACTION cycleArcEditMode
Definition actions.h:270
static TOOL_ACTION pointEditorArcKeepCenter
Definition actions.h:271
static TOOL_ACTION pointEditorArcKeepRadius
Definition actions.h:273
static TOOL_ACTION reselectItem
Definition actions.h:225
static TOOL_ACTION activatePointEditor
Definition actions.h:267
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION pointEditorArcKeepEndpoint
Definition actions.h:272
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.
virtual 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.
virtual 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:83
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:315
int GetMaxError() const
constexpr void SetMaximum()
Definition box2.h:76
constexpr const Vec GetEnd() const
Definition box2.h:208
constexpr size_type GetWidth() const
Definition box2.h:210
constexpr Vec Centre() const
Definition box2.h:93
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:654
constexpr size_type GetHeight() const
Definition box2.h:211
constexpr coord_type GetLeft() const
Definition box2.h:224
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:164
constexpr const Vec & GetOrigin() const
Definition box2.h:206
constexpr coord_type GetRight() const
Definition box2.h:213
constexpr coord_type GetTop() const
Definition box2.h:225
constexpr coord_type GetBottom() const
Definition box2.h:218
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:116
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:96
const KIID m_Uuid
Definition eda_item.h:531
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
void ClearSelected()
Definition eda_item.h:147
virtual EDA_ITEM * Clone() const
Create a duplicate of this item with linked list members set to NULL.
Definition eda_item.cpp:143
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:89
EDA_ITEM(EDA_ITEM *parent, KICAD_T idType, bool isSCH_ITEM=false, bool isBOARD_ITEM=false)
Definition eda_item.cpp:37
virtual VECTOR2I GetTopLeft() const
Definition eda_shape.h:271
void SetCornerRadius(int aRadius)
SHAPE_POLY_SET & GetPolyShape()
SHAPE_T GetShape() const
Definition eda_shape.h:185
virtual VECTOR2I GetBotRight() const
Definition eda_shape.h:272
virtual void SetBottom(int val)
Definition eda_shape.h:277
virtual void SetTop(int val)
Definition eda_shape.h:274
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:240
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:190
virtual void SetLeft(int val)
Definition eda_shape.h:275
virtual void SetRight(int val)
Definition eda_shape.h:276
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
int GetY() const
Return Y coordinate of an EDIT_POINT.
Definition edit_points.h:92
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:68
void SetSnapConstraint(SNAP_CONSTRAINT_TYPE aConstraint)
int GetX() const
Return X coordinate of an EDIT_POINT.
Definition edit_points.h:84
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
Definition actions.h:356
static const TOOL_EVENT SelectedEvent
Definition actions.h:343
static const TOOL_EVENT SelectedItemsModified
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition actions.h:350
static const TOOL_EVENT UninhibitSelectionEditing
Used to inform tool that it should display the disambiguation menu.
Definition actions.h:357
static const TOOL_EVENT PointSelectedEvent
Definition actions.h:342
static const TOOL_EVENT SelectedItemsMoved
Used to inform tools that the selection should temporarily be non-editable.
Definition actions.h:353
static const TOOL_EVENT UnselectedEvent
Definition actions.h:344
std::deque< PAD * > & Pads()
Definition footprint.h:375
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.
Handle actions specific to filling copper zones.
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:63
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1)
Add a VIEW_ITEM to the view.
Definition view.cpp:300
virtual void Remove(VIEW_ITEM *aItem)
Remove a VIEW_ITEM from the view.
Definition view.cpp:404
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:1835
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 ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
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
Definition pcb_group.cpp:98
int changeArcEditMode(const TOOL_EVENT &aEvent)
bool CanRemoveCorner(const SELECTION &aSelection)
Condition to display "Remove Corner" context menu entry.
void updateItem(BOARD_COMMIT &aCommit)
Update edit points with item's points.
VECTOR2I m_stickyDisplacement
int OnSelectionChange(const TOOL_EVENT &aEvent)
Change selection event handler.
void setAltConstraint(bool aEnabled)
Return a point that should be used as a constrainer for 45 degrees mode.
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
int modifiedSelection(const TOOL_EVENT &aEvent)
Change the edit method for arcs.
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)
EDIT_POINT m_altConstrainer
bool isModified(const EDIT_POINT &aPoint) const
Set up an alternative constraint (typically enabled upon a modifier key being pressed).
ARC_EDIT_MODE m_arcEditMode
std::unique_ptr< KIGFX::PREVIEW::ANGLE_ITEM > m_angleItem
EDIT_POINT * m_editedPoint
void updateEditedPoint(const TOOL_EVENT &aEvent)
Set the current point being edited. NULL means none.
int getEditedPointIndex() const
Return true if aPoint is the currently modified point.
void updatePoints()
Update which point is being edited.
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)
Update item's points with edit points.
PCB_SELECTION_TOOL * m_selectionTool
Object to handle a bitmap image that can be inserted in a PCB.
The selection tool: currently supports:
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:78
void SetEnd(const VECTOR2I &aEnd) override
bool IsProxyItem() const override
Definition pcb_shape.h:146
void Move(const VECTOR2I &aMoveVector) override
Move this object.
void SetStart(const VECTOR2I &aStart) override
PCB_TABLECELL_POINT_EDIT_BEHAVIOR(PCB_TABLECELL &aCell)
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.
REFERENCE_IMAGE_POINT_EDIT_BEHAVIOR(PCB_REFERENCE_IMAGE &aRefImage)
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
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.
A REFERENCE_IMAGE is a wrapper around a BITMAP_IMAGE that is displayed in an editor as a reference fo...
void SetTransformOriginOffset(const VECTOR2I &aCenter)
VECTOR2I GetTransformOriginOffset() const
Get the center of scaling, etc, relative to the image center (GetPosition()).
VECTOR2I GetPosition() const
VECTOR2I GetSize() const
double GetImageScale() const
void SetImageScale(double aScale)
Set the image "zoom" value.
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:629
int Distance(const SEG &aSeg) const
Compute minimum Euclidean distance to segment aSeg.
Definition seg.cpp:698
EDA_ANGLE Angle(const SEG &aOther) const
Determine the smallest angle between two segments.
Definition seg.cpp:107
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
SHAPE_GROUP_POINT_EDIT_BEHAVIOR(PCB_GROUP &aGroup)
SHAPE_GROUP_POINT_EDIT_BEHAVIOR(std::vector< PCB_SHAPE * > aShapes, BOARD_ITEM *aParent)
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.
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
Returns the instance of #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:469
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:534
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:549
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:279
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:542
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:381
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:418
@ 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:42
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)
@ RECURSE
Definition eda_item.h:49
#define IS_MOVING
Item being moved.
SHAPE_T
Definition eda_shape.h:44
@ ELLIPSE
Definition eda_shape.h:52
@ SEGMENT
Definition eda_shape.h:46
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:47
@ ELLIPSE_ARC
Definition eda_shape.h:53
@ NO_FILL
Definition eda_shape.h:60
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)
Clamps a vector to values that can be negated, respecting numeric limits of coordinates data type wit...
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:786
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:683
@ 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:171
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:400
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ TRAPEZOID
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:54
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
@ 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)
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:225
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:71
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:81
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:99
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:96
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:84
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:97
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:104
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:86
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:101
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:82
@ NOT_USED
the 3d code uses this value
Definition typeinfo.h:72
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:94
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:88
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:95
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:98
constexpr int sign(T val)
Definition util.h:141
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682
Supplemental functions for working with vectors and simple objects that interact with vectors.