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