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