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, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26#include <functional>
27#include <memory>
28#include <algorithm>
29#include <limits>
30
31using namespace std::placeholders;
32#include <advanced_config.h>
33#include <kiplatform/ui.h>
34#include <view/view_controls.h>
38#include <geometry/seg.h>
40#include <math/util.h>
41#include <confirm.h>
42#include <tool/tool_manager.h>
46#include <tools/pcb_actions.h>
52#include <board_commit.h>
53#include <pcb_edit_frame.h>
54#include <pcb_reference_image.h>
55#include <pcb_generator.h>
56#include <pcb_group.h>
57#include <pcb_dimension.h>
58#include <pcb_barcode.h>
59#include <pcb_textbox.h>
60#include <pcb_tablecell.h>
61#include <pcb_table.h>
62#include <pad.h>
63#include <zone.h>
64#include <footprint.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_CONSTRAINT<EDIT_POINT>* aConstraint )
80{
81 std::vector<VECTOR2I> directions;
82
83 if( !aConstraint )
84 return directions;
85
86 if( dynamic_cast<EC_90DEGREE*>( aConstraint ) )
87 {
88 appendDirection( directions, VECTOR2I( 1, 0 ) );
89 appendDirection( directions, VECTOR2I( 0, 1 ) );
90 }
91 else if( dynamic_cast<EC_45DEGREE*>( aConstraint ) )
92 {
93 appendDirection( directions, VECTOR2I( 1, 0 ) );
94 appendDirection( directions, VECTOR2I( 0, 1 ) );
95 appendDirection( directions, VECTOR2I( 1, 1 ) );
96 appendDirection( directions, VECTOR2I( 1, -1 ) );
97 }
98 else if( dynamic_cast<EC_VERTICAL*>( aConstraint ) )
99 {
100 appendDirection( directions, VECTOR2I( 0, 1 ) );
101 }
102 else if( dynamic_cast<EC_HORIZONTAL*>( aConstraint ) )
103 {
104 appendDirection( directions, VECTOR2I( 1, 0 ) );
105 }
106 else if( EC_LINE* lineConstraint = dynamic_cast<EC_LINE*>( aConstraint ) )
107 {
108 appendDirection( directions, lineConstraint->GetLineVector() );
109 }
110
111 return directions;
112}
113
114// Few constants to avoid using bare numbers for point indices
126
127
132
133
148
149
156
157
159{
160public:
161 RECT_RADIUS_TEXT_ITEM( const EDA_IU_SCALE& aIuScale, EDA_UNITS aUnits ) :
163 m_iuScale( aIuScale ),
164 m_units( aUnits ),
165 m_radius( 0 ),
166 m_corner(),
167 m_quadrant( -1, 1 ),
168 m_visible( false )
169 {
170 }
171
172 const BOX2I ViewBBox() const override
173 {
174 BOX2I tmp;
175 tmp.SetMaximum();
176 return tmp;
177 }
178
179 std::vector<int> ViewGetLayers() const override
180 {
182 }
183
184 void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override
185 {
186 if( !m_visible )
187 return;
188
189 wxArrayString strings;
190 strings.push_back( KIGFX::PREVIEW::DimensionLabel( "r", m_radius, m_iuScale, m_units ) );
192 aLayer == LAYER_SELECT_OVERLAY );
193 }
194
195 void Set( int aRadius, const VECTOR2I& aCorner, const VECTOR2I& aQuadrant, EDA_UNITS aUnits )
196 {
197 m_radius = aRadius;
198 m_corner = aCorner;
199 m_quadrant = aQuadrant;
200 m_units = aUnits;
201 m_visible = true;
202 }
203
204 void Hide()
205 {
206 m_visible = false;
207 }
208
209 wxString GetClass() const override
210 {
211 return wxT( "RECT_RADIUS_TEXT_ITEM" );
212 }
213
214private:
221};
222
223
225{
226public:
228 m_rectangle( aRectangle )
229 {
230 wxASSERT( m_rectangle.GetShape() == SHAPE_T::RECTANGLE );
231 }
232
237 static void MakePoints( const PCB_SHAPE& aRectangle, EDIT_POINTS& aPoints )
238 {
239 wxCHECK( aRectangle.GetShape() == SHAPE_T::RECTANGLE, /* void */ );
240
241 VECTOR2I topLeft = aRectangle.GetTopLeft();
242 VECTOR2I botRight = aRectangle.GetBotRight();
243
244 aPoints.SetSwapX( topLeft.x > botRight.x );
245 aPoints.SetSwapY( topLeft.y > botRight.y );
246
247 if( aPoints.SwapX() )
248 std::swap( topLeft.x, botRight.x );
249
250 if( aPoints.SwapY() )
251 std::swap( topLeft.y, botRight.y );
252
253 aPoints.AddPoint( topLeft );
254 aPoints.AddPoint( VECTOR2I( botRight.x, topLeft.y ) );
255 aPoints.AddPoint( botRight );
256 aPoints.AddPoint( VECTOR2I( topLeft.x, botRight.y ) );
257 aPoints.AddPoint( aRectangle.GetCenter() );
258 aPoints.AddPoint( VECTOR2I( botRight.x - aRectangle.GetCornerRadius(), topLeft.y ) );
259 aPoints.Point( RECT_RADIUS ).SetDrawCircle();
260
261 aPoints.AddLine( aPoints.Point( RECT_TOP_LEFT ), aPoints.Point( RECT_TOP_RIGHT ) );
262 aPoints.Line( RECT_TOP ).SetConstraint( new EC_PERPLINE( aPoints.Line( RECT_TOP ) ) );
263 aPoints.AddLine( aPoints.Point( RECT_TOP_RIGHT ), aPoints.Point( RECT_BOT_RIGHT ) );
264 aPoints.Line( RECT_RIGHT ).SetConstraint( new EC_PERPLINE( aPoints.Line( RECT_RIGHT ) ) );
265 aPoints.AddLine( aPoints.Point( RECT_BOT_RIGHT ), aPoints.Point( RECT_BOT_LEFT ) );
266 aPoints.Line( RECT_BOT ).SetConstraint( new EC_PERPLINE( aPoints.Line( RECT_BOT ) ) );
267 aPoints.AddLine( aPoints.Point( RECT_BOT_LEFT ), aPoints.Point( RECT_TOP_LEFT ) );
268 aPoints.Line( RECT_LEFT ).SetConstraint( new EC_PERPLINE( aPoints.Line( RECT_LEFT ) ) );
269 }
270
271 static void UpdateItem( PCB_SHAPE& aRectangle, const EDIT_POINT& aEditedPoint,
272 EDIT_POINTS& aPoints, const VECTOR2I& aMinSize = { 0, 0 } )
273 {
274 // You can have more points if your item wants to have more points
275 // (this class assumes the rect points come first, but that can be changed)
277
278 auto setLeft =
279 [&]( int left )
280 {
281 aPoints.SwapX() ? aRectangle.SetRight( left ) : aRectangle.SetLeft( left );
282 };
283 auto setRight =
284 [&]( int right )
285 {
286 aPoints.SwapX() ? aRectangle.SetLeft( right ) : aRectangle.SetRight( right );
287 };
288 auto setTop =
289 [&]( int top )
290 {
291 aPoints.SwapY() ? aRectangle.SetBottom( top ) : aRectangle.SetTop( top );
292 };
293 auto setBottom =
294 [&]( int bottom )
295 {
296 aPoints.SwapY() ? aRectangle.SetTop( bottom ) : aRectangle.SetBottom( bottom );
297 };
298
299 VECTOR2I topLeft = aPoints.Point( RECT_TOP_LEFT ).GetPosition();
300 VECTOR2I topRight = aPoints.Point( RECT_TOP_RIGHT ).GetPosition();
301 VECTOR2I botLeft = aPoints.Point( RECT_BOT_LEFT ).GetPosition();
302 VECTOR2I botRight = aPoints.Point( RECT_BOT_RIGHT ).GetPosition();
303
304 PinEditedCorner( aEditedPoint, aPoints, topLeft, topRight, botLeft, botRight,
305 { 0, 0 }, { 0, 0 }, aMinSize );
306
307 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_LEFT ) )
308 || isModified( aEditedPoint, aPoints.Point( RECT_TOP_RIGHT ) )
309 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) )
310 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_LEFT ) ) )
311 {
312 setTop( topLeft.y );
313 setLeft( topLeft.x );
314 setRight( botRight.x );
315 setBottom( botRight.y );
316 }
317 else if( isModified( aEditedPoint, aPoints.Point( RECT_CENTER ) ) )
318 {
319 const VECTOR2I moveVector = aPoints.Point( RECT_CENTER ).GetPosition() - aRectangle.GetCenter();
320 aRectangle.Move( moveVector );
321 }
322 else if( isModified( aEditedPoint, aPoints.Point( RECT_RADIUS ) ) )
323 {
324 int width = std::abs( botRight.x - topLeft.x );
325 int height = std::abs( botRight.y - topLeft.y );
326 int maxRadius = std::min( width, height ) / 2;
327 int x = aPoints.Point( RECT_RADIUS ).GetX();
328 x = std::clamp( x, botRight.x - maxRadius, botRight.x );
329 aPoints.Point( RECT_RADIUS ).SetPosition( x, topLeft.y );
330 aRectangle.SetCornerRadius( botRight.x - x );
331 }
332 else if( isModified( aEditedPoint, aPoints.Line( RECT_TOP ) ) )
333 {
334 // Only top changes; keep others from previous full-local bbox
335 setTop( topLeft.y );
336 }
337 else if( isModified( aEditedPoint, aPoints.Line( RECT_LEFT ) ) )
338 {
339 // Only left changes; keep others from previous full-local bbox
340 setLeft( topLeft.x );
341 }
342 else if( isModified( aEditedPoint, aPoints.Line( RECT_BOT ) ) )
343 {
344 // Only bottom changes; keep others from previous full-local bbox
345 setBottom( botRight.y );
346 }
347 else if( isModified( aEditedPoint, aPoints.Line( RECT_RIGHT ) ) )
348 {
349 // Only right changes; keep others from previous full-local bbox
350 setRight( botRight.x );
351 }
352
353 for( unsigned i = 0; i < aPoints.LinesSize(); ++i )
354 {
355 if( !isModified( aEditedPoint, aPoints.Line( i ) ) )
356 aPoints.Line( i ).SetConstraint( new EC_PERPLINE( aPoints.Line( i ) ) );
357 }
358 }
359
360 static void UpdatePoints( const PCB_SHAPE& aRectangle, EDIT_POINTS& aPoints )
361 {
362 wxCHECK( aPoints.PointsSize() >= RECT_MAX_POINTS, /* void */ );
363
364 VECTOR2I topLeft = aRectangle.GetTopLeft();
365 VECTOR2I botRight = aRectangle.GetBotRight();
366
367 aPoints.SetSwapX( topLeft.x > botRight.x );
368 aPoints.SetSwapY( topLeft.y > botRight.y );
369
370 if( aPoints.SwapX() )
371 std::swap( topLeft.x, botRight.x );
372
373 if( aPoints.SwapY() )
374 std::swap( topLeft.y, botRight.y );
375
376 aPoints.Point( RECT_TOP_LEFT ).SetPosition( topLeft );
377 aPoints.Point( RECT_RADIUS ).SetPosition( botRight.x - aRectangle.GetCornerRadius(), topLeft.y );
378 aPoints.Point( RECT_TOP_RIGHT ).SetPosition( botRight.x, topLeft.y );
379 aPoints.Point( RECT_BOT_RIGHT ).SetPosition( botRight );
380 aPoints.Point( RECT_BOT_LEFT ).SetPosition( topLeft.x, botRight.y );
381 aPoints.Point( RECT_CENTER ).SetPosition( aRectangle.GetCenter() );
382 }
383
384 void MakePoints( EDIT_POINTS& aPoints ) override
385 {
386 // Just call the static helper
387 MakePoints( m_rectangle, aPoints );
388 }
389
390 bool UpdatePoints( EDIT_POINTS& aPoints ) override
391 {
392 // Careful; rectangle shape is mutable between cardinal and non-cardinal rotations...
393 if( m_rectangle.GetShape() != SHAPE_T::RECTANGLE || aPoints.PointsSize() == 0 )
394 return false;
395
396 UpdatePoints( m_rectangle, aPoints );
397 return true;
398 }
399
400 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
401 std::vector<EDA_ITEM*>& aUpdatedItems ) override
402 {
403 UpdateItem( m_rectangle, aEditedPoint, aPoints );
404 }
405
419 static void PinEditedCorner( const EDIT_POINT& aEditedPoint, const EDIT_POINTS& aEditPoints,
420 VECTOR2I& aTopLeft, VECTOR2I& aTopRight, VECTOR2I& aBotLeft, VECTOR2I& aBotRight,
421 const VECTOR2I& aHole = { 0, 0 }, const VECTOR2I& aHoleSize = { 0, 0 },
422 const VECTOR2I& aMinSize = { 0, 0 } )
423 {
424 int minWidth = std::max( EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 ), aMinSize.x );
425 int minHeight = std::max( EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 ), aMinSize.y );
426
427 if( isModified( aEditedPoint, aEditPoints.Point( RECT_TOP_LEFT ) ) )
428 {
429 if( aHoleSize.x )
430 {
431 // pin edited point to the top/left of the hole
432 aTopLeft.x = std::min( aTopLeft.x, aHole.x - aHoleSize.x / 2 - minWidth );
433 aTopLeft.y = std::min( aTopLeft.y, aHole.y - aHoleSize.y / 2 - minHeight );
434 }
435 else
436 {
437 // pin edited point within opposite corner
438 aTopLeft.x = std::min( aTopLeft.x, aBotRight.x - minWidth );
439 aTopLeft.y = std::min( aTopLeft.y, aBotRight.y - minHeight );
440 }
441
442 // push edited point edges to adjacent corners
443 aTopRight.y = aTopLeft.y;
444 aBotLeft.x = aTopLeft.x;
445 }
446 else if( isModified( aEditedPoint, aEditPoints.Point( RECT_TOP_RIGHT ) ) )
447 {
448 if( aHoleSize.x )
449 {
450 // pin edited point to the top/right of the hole
451 aTopRight.x = std::max( aTopRight.x, aHole.x + aHoleSize.x / 2 + minWidth );
452 aTopRight.y = std::min( aTopRight.y, aHole.y - aHoleSize.y / 2 - minHeight );
453 }
454 else
455 {
456 // pin edited point within opposite corner
457 aTopRight.x = std::max( aTopRight.x, aBotLeft.x + minWidth );
458 aTopRight.y = std::min( aTopRight.y, aBotLeft.y - minHeight );
459 }
460
461 // push edited point edges to adjacent corners
462 aTopLeft.y = aTopRight.y;
463 aBotRight.x = aTopRight.x;
464 }
465 else if( isModified( aEditedPoint, aEditPoints.Point( RECT_BOT_LEFT ) ) )
466 {
467 if( aHoleSize.x )
468 {
469 // pin edited point to the bottom/left of the hole
470 aBotLeft.x = std::min( aBotLeft.x, aHole.x - aHoleSize.x / 2 - minWidth );
471 aBotLeft.y = std::max( aBotLeft.y, aHole.y + aHoleSize.y / 2 + minHeight );
472 }
473 else
474 {
475 // pin edited point within opposite corner
476 aBotLeft.x = std::min( aBotLeft.x, aTopRight.x - minWidth );
477 aBotLeft.y = std::max( aBotLeft.y, aTopRight.y + minHeight );
478 }
479
480 // push edited point edges to adjacent corners
481 aBotRight.y = aBotLeft.y;
482 aTopLeft.x = aBotLeft.x;
483 }
484 else if( isModified( aEditedPoint, aEditPoints.Point( RECT_BOT_RIGHT ) ) )
485 {
486 if( aHoleSize.x )
487 {
488 // pin edited point to the bottom/right of the hole
489 aBotRight.x = std::max( aBotRight.x, aHole.x + aHoleSize.x / 2 + minWidth );
490 aBotRight.y = std::max( aBotRight.y, aHole.y + aHoleSize.y / 2 + minHeight );
491 }
492 else
493 {
494 // pin edited point within opposite corner
495 aBotRight.x = std::max( aBotRight.x, aTopLeft.x + minWidth );
496 aBotRight.y = std::max( aBotRight.y, aTopLeft.y + minHeight );
497 }
498
499 // push edited point edges to adjacent corners
500 aBotLeft.y = aBotRight.y;
501 aTopRight.x = aBotRight.x;
502 }
503 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_TOP ) ) )
504 {
505 aTopLeft.y = std::min( aTopLeft.y, aBotRight.y - minHeight );
506 }
507 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_LEFT ) ) )
508 {
509 aTopLeft.x = std::min( aTopLeft.x, aBotRight.x - minWidth );
510 }
511 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_BOT ) ) )
512 {
513 aBotRight.y = std::max( aBotRight.y, aTopLeft.y + minHeight );
514 }
515 else if( isModified( aEditedPoint, aEditPoints.Line( RECT_RIGHT ) ) )
516 {
517 aBotRight.x = std::max( aBotRight.x, aTopLeft.x + minWidth );
518 }
519 }
520
521private:
523};
524
525
527{
528public:
530 POLYGON_POINT_EDIT_BEHAVIOR( *aZone.Outline() ),
531 m_zone( aZone )
532 {}
533
534 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
535 std::vector<EDA_ITEM*>& aUpdatedItems ) override
536 {
537 m_zone.UnFill();
538
539 // Defer to the base class to update the polygon
540 POLYGON_POINT_EDIT_BEHAVIOR::UpdateItem( aEditedPoint, aPoints, aCommit, aUpdatedItems );
541
542 m_zone.HatchBorder();
543 }
544
545private:
547};
548
549
551{
553 {
554 REFIMG_ORIGIN = RECT_CENTER, // Reuse the center point fo rthe transform origin
555
557 };
558
559public:
563
564 void MakePoints( EDIT_POINTS& aPoints ) override
565 {
566 REFERENCE_IMAGE& refImage = m_refImage.GetReferenceImage();
567
568 const VECTOR2I topLeft = refImage.GetPosition() - refImage.GetSize() / 2;
569 const VECTOR2I botRight = refImage.GetPosition() + refImage.GetSize() / 2;
570
571 aPoints.AddPoint( topLeft );
572 aPoints.AddPoint( VECTOR2I( botRight.x, topLeft.y ) );
573 aPoints.AddPoint( botRight );
574 aPoints.AddPoint( VECTOR2I( topLeft.x, botRight.y ) );
575
576 aPoints.AddPoint( refImage.GetPosition() + refImage.GetTransformOriginOffset() );
577 }
578
579 bool UpdatePoints( EDIT_POINTS& aPoints ) override
580 {
581 wxCHECK( aPoints.PointsSize() == REFIMG_MAX_POINTS, false );
582
583 REFERENCE_IMAGE& refImage = m_refImage.GetReferenceImage();
584
585 const VECTOR2I topLeft = refImage.GetPosition() - refImage.GetSize() / 2;
586 const VECTOR2I botRight = refImage.GetPosition() + refImage.GetSize() / 2;
587
588 aPoints.Point( RECT_TOP_LEFT ).SetPosition( topLeft );
589 aPoints.Point( RECT_TOP_RIGHT ).SetPosition( VECTOR2I( botRight.x, topLeft.y ) );
590 aPoints.Point( RECT_BOT_RIGHT ).SetPosition( botRight );
591 aPoints.Point( RECT_BOT_LEFT ).SetPosition( VECTOR2I( topLeft.x, botRight.y ) );
592 aPoints.Point( REFIMG_ORIGIN ).SetPosition( refImage.GetPosition() + refImage.GetTransformOriginOffset() );
593 return true;
594 }
595
596 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
597 std::vector<EDA_ITEM*>& aUpdatedItems ) override
598 {
600
601 REFERENCE_IMAGE& refImage = m_refImage.GetReferenceImage();
602
603 const VECTOR2I topLeft = aPoints.Point( RECT_TOP_LEFT ).GetPosition();
604 const VECTOR2I topRight = aPoints.Point( RECT_TOP_RIGHT ).GetPosition();
605 const VECTOR2I botRight = aPoints.Point( RECT_BOT_RIGHT ).GetPosition();
606 const VECTOR2I botLeft = aPoints.Point( RECT_BOT_LEFT ).GetPosition();
607 const VECTOR2I xfrmOrigin = aPoints.Point( REFIMG_ORIGIN ).GetPosition();
608
609 if( isModified( aEditedPoint, aPoints.Point( REFIMG_ORIGIN ) ) )
610 {
611 // Moving the transform origin
612 // As the other points didn't move, we can get the image extent from them
613 const VECTOR2I newOffset = xfrmOrigin - ( topLeft + botRight ) / 2;
614 refImage.SetTransformOriginOffset( newOffset );
615 }
616 else
617 {
618 const VECTOR2I oldOrigin = m_refImage.GetPosition() + refImage.GetTransformOriginOffset();
619 const VECTOR2I oldSize = refImage.GetSize();
620 const VECTOR2I pos = refImage.GetPosition();
621
622 OPT_VECTOR2I newCorner;
623 VECTOR2I oldCorner = pos;
624
625 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_LEFT ) ) )
626 {
627 newCorner = topLeft;
628 oldCorner -= oldSize / 2;
629 }
630 else if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_RIGHT ) ) )
631 {
632 newCorner = topRight;
633 oldCorner -= VECTOR2I( -oldSize.x, oldSize.y ) / 2;
634 }
635 else if( isModified( aEditedPoint, aPoints.Point( RECT_BOT_LEFT ) ) )
636 {
637 newCorner = botLeft;
638 oldCorner -= VECTOR2I( oldSize.x, -oldSize.y ) / 2;
639 }
640 else if( isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) ) )
641 {
642 newCorner = botRight;
643 oldCorner += oldSize / 2;
644 }
645
646 if( newCorner )
647 {
648 // Turn in the respective vectors from the origin
649 *newCorner -= xfrmOrigin;
650 oldCorner -= oldOrigin;
651
652 // If we tried to cross the origin, clamp it to stop it
653 if( sign( newCorner->x ) != sign( oldCorner.x ) || sign( newCorner->y ) != sign( oldCorner.y ) )
654 {
655 *newCorner = VECTOR2I( 0, 0 );
656 }
657
658 const double newLength = newCorner->EuclideanNorm();
659 const double oldLength = oldCorner.EuclideanNorm();
660
661 double ratio = oldLength > 0 ? ( newLength / oldLength ) : 1.0;
662
663 // Clamp the scaling to a minimum of 50 mils
664 VECTOR2I newSize = oldSize * ratio;
665 double newWidth = std::max( newSize.x, EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 50 ) );
666 double newHeight = std::max( newSize.y, EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 50 ) );
667 ratio = std::min( newWidth / oldSize.x, newHeight / oldSize.y );
668
669 // Also handles the origin offset
670 refImage.SetImageScale( refImage.GetImageScale() * ratio );
671 }
672 }
673 }
674
675private:
677};
678
679
681{
682public:
684 m_barcode( aBarcode )
685 {}
686
688 {
690 dummy.SetStart( m_barcode.GetCenter() - VECTOR2I( m_barcode.GetWidth() / 2, m_barcode.GetHeight() / 2 ) );
691 dummy.SetEnd( dummy.GetStart() + VECTOR2I( m_barcode.GetWidth(), m_barcode.GetHeight() ) );
692 dummy.Rotate( m_barcode.GetPosition(), m_barcode.GetAngle() );
693 return dummy;
694 }
695
696 void MakePoints( EDIT_POINTS& aPoints ) override
697 {
698 if( !m_barcode.GetAngle().IsCardinal() )
699 {
700 // Non-cardinal barcode point-editing isn't useful enough to support.
701 return;
702 }
703
704 auto set45Constraint =
705 [&]( int a, int b )
706 {
707 aPoints.Point( a ).SetConstraint( new EC_45DEGREE( aPoints.Point( a ), aPoints.Point( b ) ) );
708 };
709
711
712 if( m_barcode.KeepSquare() )
713 {
714 set45Constraint( RECT_TOP_LEFT, RECT_BOT_RIGHT );
715 set45Constraint( RECT_TOP_RIGHT, RECT_BOT_LEFT );
716 set45Constraint( RECT_BOT_RIGHT, RECT_TOP_LEFT );
717 set45Constraint( RECT_BOT_LEFT, RECT_TOP_RIGHT );
718 }
719 }
720
721 bool UpdatePoints( EDIT_POINTS& aPoints ) override
722 {
723 const unsigned target = m_barcode.GetAngle().IsCardinal() ? RECT_MAX_POINTS : 0;
724
725 if( aPoints.PointsSize() != target )
726 return false;
727
729 return true;
730 }
731
732 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
733 std::vector<EDA_ITEM*>& aUpdatedItems ) override
734 {
735 if( m_barcode.GetAngle().IsCardinal() )
736 {
738 RECTANGLE_POINT_EDIT_BEHAVIOR::UpdateItem( dummy, aEditedPoint, aPoints );
739 dummy.Rotate( dummy.GetCenter(), -m_barcode.GetAngle() );
740
741 m_barcode.SetPosition( dummy.GetCenter() );
742 m_barcode.SetWidth( dummy.GetRectangleWidth() );
743 m_barcode.SetHeight( dummy.GetRectangleHeight() );
744 m_barcode.AssembleBarcode();
745 }
746 }
747
748private:
750};
751
752
754{
755public:
760
761 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
762 std::vector<EDA_ITEM*>& aUpdatedItems ) override
763 {
765
766 PCB_TABLE& table = static_cast<PCB_TABLE&>( *m_cell.GetParent() );
767 aCommit.Modify( &table );
768 aUpdatedItems.push_back( &table );
769
770 if( !m_cell.GetTextAngle().IsHorizontal() )
771 {
772 if( isModified( aEditedPoint, aPoints.Point( ROW_HEIGHT ) ) )
773 {
774 m_cell.SetEnd( VECTOR2I( m_cell.GetEndX(), aPoints.Point( ROW_HEIGHT ).GetY() ) );
775
776 int colWidth = std::abs( m_cell.GetRectangleHeight() );
777
778 for( int ii = 0; ii < m_cell.GetColSpan() - 1; ++ii )
779 colWidth -= table.GetColWidth( m_cell.GetColumn() + ii );
780
781 table.SetColWidth( m_cell.GetColumn() + m_cell.GetColSpan() - 1, colWidth );
782 }
783 else if( isModified( aEditedPoint, aPoints.Point( COL_WIDTH ) ) )
784 {
785 m_cell.SetEnd( VECTOR2I( aPoints.Point( COL_WIDTH ).GetX(), m_cell.GetEndY() ) );
786
787 int rowHeight = m_cell.GetRectangleWidth();
788
789 for( int ii = 0; ii < m_cell.GetRowSpan() - 1; ++ii )
790 rowHeight -= table.GetRowHeight( m_cell.GetRow() + ii );
791
792 table.SetRowHeight( m_cell.GetRow() + m_cell.GetRowSpan() - 1, rowHeight );
793 }
794 }
795 else
796 {
797 if( isModified( aEditedPoint, aPoints.Point( COL_WIDTH ) ) )
798 {
799 m_cell.SetEnd( VECTOR2I( aPoints.Point( COL_WIDTH ).GetX(), m_cell.GetEndY() ) );
800
801 int colWidth = m_cell.GetRectangleWidth();
802
803 for( int ii = 0; ii < m_cell.GetColSpan() - 1; ++ii )
804 colWidth -= table.GetColWidth( m_cell.GetColumn() + ii );
805
806 table.SetColWidth( m_cell.GetColumn() + m_cell.GetColSpan() - 1, colWidth );
807 }
808 else if( isModified( aEditedPoint, aPoints.Point( ROW_HEIGHT ) ) )
809 {
810 m_cell.SetEnd( VECTOR2I( m_cell.GetEndX(), aPoints.Point( ROW_HEIGHT ).GetY() ) );
811
812 int rowHeight = m_cell.GetRectangleHeight();
813
814 for( int ii = 0; ii < m_cell.GetRowSpan() - 1; ++ii )
815 rowHeight -= table.GetRowHeight( m_cell.GetRow() + ii );
816
817 table.SetRowHeight( m_cell.GetRow() + m_cell.GetRowSpan() - 1, rowHeight );
818 }
819 }
820
821 table.Normalize();
822 }
823
824private:
826};
827
828
830{
831public:
833 m_pad( aPad ),
834 m_layer( aLayer )
835 {}
836
837 void MakePoints( EDIT_POINTS& aPoints ) override
838 {
839 VECTOR2I shapePos = m_pad.ShapePos( m_layer );
840 VECTOR2I halfSize( m_pad.GetSize( m_layer ).x / 2, m_pad.GetSize( m_layer ).y / 2 );
841
842 if( m_pad.IsLocked() )
843 return;
844
845 switch( m_pad.GetShape( m_layer ) )
846 {
848 aPoints.AddPoint( VECTOR2I( shapePos.x + halfSize.x, shapePos.y ) );
849 break;
850
851 case PAD_SHAPE::OVAL:
856 {
857 if( !m_pad.GetOrientation().IsCardinal() )
858 break;
859
860 if( m_pad.GetOrientation().IsVertical() )
861 std::swap( halfSize.x, halfSize.y );
862
863 // It's important to fill these according to the RECT indices
864 aPoints.AddPoint( shapePos - halfSize );
865 aPoints.AddPoint( VECTOR2I( shapePos.x + halfSize.x, shapePos.y - halfSize.y ) );
866 aPoints.AddPoint( shapePos + halfSize );
867 aPoints.AddPoint( VECTOR2I( shapePos.x - halfSize.x, shapePos.y + halfSize.y ) );
868 }
869 break;
870
871 default: // suppress warnings
872 break;
873 }
874 }
875
876 bool UpdatePoints( EDIT_POINTS& aPoints ) override
877 {
878 bool locked = m_pad.GetParent() && m_pad.IsLocked();
879 VECTOR2I shapePos = m_pad.ShapePos( m_layer );
880 VECTOR2I halfSize( m_pad.GetSize( m_layer ).x / 2, m_pad.GetSize( m_layer ).y / 2 );
881
882 switch( m_pad.GetShape( m_layer ) )
883 {
885 {
886 int target = locked ? 0 : 1;
887
888 // Careful; pad shape is mutable...
889 if( int( aPoints.PointsSize() ) != target )
890 {
891 aPoints.Clear();
892 MakePoints( aPoints );
893 }
894 else if( target == 1 )
895 {
896 shapePos.x += halfSize.x;
897 aPoints.Point( 0 ).SetPosition( shapePos );
898 }
899 }
900 break;
901
902 case PAD_SHAPE::OVAL:
907 {
908 // Careful; pad shape and orientation are mutable...
909 int target = locked || !m_pad.GetOrientation().IsCardinal() ? 0 : 4;
910
911 if( int( aPoints.PointsSize() ) != target )
912 {
913 aPoints.Clear();
914 MakePoints( aPoints );
915 }
916 else if( target == 4 )
917 {
918 if( m_pad.GetOrientation().IsVertical() )
919 std::swap( halfSize.x, halfSize.y );
920
921 aPoints.Point( RECT_TOP_LEFT ).SetPosition( shapePos - halfSize );
922 aPoints.Point( RECT_TOP_RIGHT ).SetPosition( VECTOR2I( shapePos.x + halfSize.x,
923 shapePos.y - halfSize.y ) );
924 aPoints.Point( RECT_BOT_RIGHT ).SetPosition( shapePos + halfSize );
925 aPoints.Point( RECT_BOT_LEFT ).SetPosition( VECTOR2I( shapePos.x - halfSize.x,
926 shapePos.y + halfSize.y ) );
927 }
928
929 break;
930 }
931
932 default: // suppress warnings
933 break;
934 }
935
936 return true;
937 }
938
939 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
940 std::vector<EDA_ITEM*>& aUpdatedItems ) override
941 {
942 switch( m_pad.GetShape( m_layer ) )
943 {
945 {
946 VECTOR2I end = aPoints.Point( 0 ).GetPosition();
947 int diameter = 2 * ( end - m_pad.GetPosition() ).EuclideanNorm();
948
949 m_pad.SetSize( m_layer, VECTOR2I( diameter, diameter ) );
950 break;
951 }
952
953 case PAD_SHAPE::OVAL:
958 {
959 VECTOR2I topLeft = aPoints.Point( RECT_TOP_LEFT ).GetPosition();
960 VECTOR2I topRight = aPoints.Point( RECT_TOP_RIGHT ).GetPosition();
961 VECTOR2I botLeft = aPoints.Point( RECT_BOT_LEFT ).GetPosition();
962 VECTOR2I botRight = aPoints.Point( RECT_BOT_RIGHT ).GetPosition();
963 VECTOR2I holeCenter = m_pad.GetPosition();
964 VECTOR2I holeSize = m_pad.GetDrillSize();
965
966 RECTANGLE_POINT_EDIT_BEHAVIOR::PinEditedCorner( aEditedPoint, aPoints, topLeft, topRight,
967 botLeft, botRight, holeCenter, holeSize );
968
969 if( ( m_pad.GetOffset( m_layer ).x || m_pad.GetOffset( m_layer ).y )
970 || ( m_pad.GetDrillSize().x && m_pad.GetDrillSize().y ) )
971 {
972 // Keep hole pinned at the current location; adjust the pad around the hole
973
974 VECTOR2I center = m_pad.GetPosition();
975 int dist[4];
976
977 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_LEFT ) )
978 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) ) )
979 {
980 dist[0] = center.x - topLeft.x;
981 dist[1] = center.y - topLeft.y;
982 dist[2] = botRight.x - center.x;
983 dist[3] = botRight.y - center.y;
984 }
985 else
986 {
987 dist[0] = center.x - botLeft.x;
988 dist[1] = center.y - topRight.y;
989 dist[2] = topRight.x - center.x;
990 dist[3] = botLeft.y - center.y;
991 }
992
993 VECTOR2I padSize( dist[0] + dist[2], dist[1] + dist[3] );
994 VECTOR2I deltaOffset( padSize.x / 2 - dist[2], padSize.y / 2 - dist[3] );
995
996 if( m_pad.GetOrientation().IsVertical() )
997 std::swap( padSize.x, padSize.y );
998
999 RotatePoint( deltaOffset, -m_pad.GetOrientation() );
1000
1001 m_pad.SetSize( m_layer, padSize );
1002 m_pad.SetOffset( m_layer, -deltaOffset );
1003 }
1004 else
1005 {
1006 // Keep pad position at the center of the pad shape
1007
1008 int left, top, right, bottom;
1009
1010 if( isModified( aEditedPoint, aPoints.Point( RECT_TOP_LEFT ) )
1011 || isModified( aEditedPoint, aPoints.Point( RECT_BOT_RIGHT ) ) )
1012 {
1013 left = topLeft.x;
1014 top = topLeft.y;
1015 right = botRight.x;
1016 bottom = botRight.y;
1017 }
1018 else
1019 {
1020 left = botLeft.x;
1021 top = topRight.y;
1022 right = topRight.x;
1023 bottom = botLeft.y;
1024 }
1025
1026 VECTOR2I padSize( abs( right - left ), abs( bottom - top ) );
1027
1028 if( m_pad.GetOrientation().IsVertical() )
1029 std::swap( padSize.x, padSize.y );
1030
1031 m_pad.SetSize( m_layer, padSize );
1032 m_pad.SetPosition( VECTOR2I( ( left + right ) / 2, ( top + bottom ) / 2 ) );
1033 }
1034 break;
1035 }
1036 default: // suppress warnings
1037 break;
1038 }
1039 }
1040
1041private:
1044};
1045
1046
1053{
1054public:
1056 m_generator( aGenerator )
1057 {}
1058
1059 void MakePoints( EDIT_POINTS& aPoints ) override
1060 {
1061 m_generator.MakeEditPoints( aPoints );
1062 }
1063
1064 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1065 {
1066 m_generator.UpdateEditPoints( aPoints );
1067 return true;
1068 }
1069
1070 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1071 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1072 {
1073 m_generator.UpdateFromEditPoints( aPoints );
1074 }
1075
1076private:
1078};
1079
1080
1090{
1091public:
1093 m_dimension( aDimension ),
1094 m_originalTextPos( aDimension.GetTextPos() ),
1095 m_oldCrossBar( SEG{ aDimension.GetCrossbarStart(), aDimension.GetCrossbarEnd() } )
1096 {}
1097
1099 {
1100 const SEG newCrossBar{ m_dimension.GetCrossbarStart(), m_dimension.GetCrossbarEnd() };
1101
1102 if( newCrossBar == m_oldCrossBar )
1103 {
1104 // Crossbar didn't change, text doesn't need to change
1105 return;
1106 }
1107
1108 const VECTOR2I newTextPos = getDimensionNewTextPosition();
1109 m_dimension.SetTextPos( newTextPos );
1110
1111 const GR_TEXT_H_ALIGN_T oldJustify = m_dimension.GetHorizJustify();
1112
1113 // We may need to update the justification if we go past vertical.
1116 {
1117 const VECTOR2I oldProject = m_oldCrossBar.LineProject( m_originalTextPos );
1118 const VECTOR2I newProject = newCrossBar.LineProject( newTextPos );
1119
1120 const VECTOR2I oldProjectedOffset =
1121 oldProject - m_oldCrossBar.NearestPoint( oldProject );
1122 const VECTOR2I newProjectedOffset = newProject - newCrossBar.NearestPoint( newProject );
1123
1124 const bool textWasLeftOf = oldProjectedOffset.x < 0
1125 || ( oldProjectedOffset.x == 0 && oldProjectedOffset.y > 0 );
1126 const bool textIsLeftOf = newProjectedOffset.x < 0
1127 || ( newProjectedOffset.x == 0 && newProjectedOffset.y > 0 );
1128
1129 if( textWasLeftOf != textIsLeftOf )
1130 {
1131 // Flip whatever the user had set
1132 m_dimension.SetHorizJustify( ( oldJustify == GR_TEXT_H_ALIGN_T::GR_TEXT_H_ALIGN_LEFT )
1135 }
1136 }
1137
1138 // Update the dimension (again) to ensure the text knockouts are correct
1139 m_dimension.Update();
1140 }
1141
1142private:
1144 {
1145 const SEG newCrossBar{ m_dimension.GetCrossbarStart(), m_dimension.GetCrossbarEnd() };
1146
1147 const EDA_ANGLE oldAngle = EDA_ANGLE( m_oldCrossBar.B - m_oldCrossBar.A );
1148 const EDA_ANGLE newAngle = EDA_ANGLE( newCrossBar.B - newCrossBar.A );
1149 const EDA_ANGLE rotation = oldAngle - newAngle;
1150
1151 // There are two modes - when the text is between the crossbar points, and when it's not.
1153 {
1155 const VECTOR2I rotTextOffsetFromCbCenter = GetRotated( m_originalTextPos - m_oldCrossBar.Center(),
1156 rotation );
1157 const VECTOR2I rotTextOffsetFromCbEnd = GetRotated( m_originalTextPos - cbNearestEndToText, rotation );
1158
1159 // Which of the two crossbar points is now in the right direction? They could be swapped over now.
1160 // If zero-length, doesn't matter, they're the same thing
1161 const bool startIsInOffsetDirection = KIGEOM::PointIsInDirection( m_dimension.GetCrossbarStart(),
1162 rotTextOffsetFromCbCenter,
1163 newCrossBar.Center() );
1164
1165 const VECTOR2I& newCbRefPt = startIsInOffsetDirection ? m_dimension.GetCrossbarStart()
1166 : m_dimension.GetCrossbarEnd();
1167
1168 // Apply the new offset to the correct crossbar point
1169 return newCbRefPt + rotTextOffsetFromCbEnd;
1170 }
1171
1172 // If the text was between the crossbar points, it should stay there, but we need to find a
1173 // good place for it. Keep it the same distance from the crossbar line, but rotated as needed.
1174
1175 const VECTOR2I origTextPointProjected = m_oldCrossBar.NearestPoint( m_originalTextPos );
1176 const double oldRatio = KIGEOM::GetLengthRatioFromStart( origTextPointProjected, m_oldCrossBar );
1177
1178 // Perpendicular from the crossbar line to the text position
1179 // We need to keep this length constant
1180 const VECTOR2I rotCbNormalToText = GetRotated( m_originalTextPos - origTextPointProjected, rotation );
1181
1182 const VECTOR2I newProjected = newCrossBar.A + ( newCrossBar.B - newCrossBar.A ) * oldRatio;
1183 return newProjected + rotCbNormalToText;
1184 }
1185
1189};
1190
1191
1196{
1197public:
1199 m_dimension( aDimension )
1200 {}
1201
1202 void MakePoints( EDIT_POINTS& aPoints ) override
1203 {
1204 aPoints.AddPoint( m_dimension.GetStart() );
1205 aPoints.AddPoint( m_dimension.GetEnd() );
1206 aPoints.AddPoint( m_dimension.GetTextPos() );
1207 aPoints.AddPoint( m_dimension.GetCrossbarStart() );
1208 aPoints.AddPoint( m_dimension.GetCrossbarEnd() );
1209
1212
1213 if( m_dimension.Type() == PCB_DIM_ALIGNED_T )
1214 {
1215 // Dimension height setting - edit points should move only along the feature lines
1217 aPoints.Point( DIM_START ) ) );
1218 aPoints.Point( DIM_CROSSBAREND ).SetConstraint( new EC_LINE( aPoints.Point( DIM_CROSSBAREND ),
1219 aPoints.Point( DIM_END ) ) );
1220 }
1221 }
1222
1223 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1224 {
1225 wxCHECK( aPoints.PointsSize() == DIM_ALIGNED_MAX, false );
1226
1227 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1228 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1229 aPoints.Point( DIM_TEXT ).SetPosition( m_dimension.GetTextPos() );
1230 aPoints.Point( DIM_CROSSBARSTART ).SetPosition( m_dimension.GetCrossbarStart() );
1231 aPoints.Point( DIM_CROSSBAREND ).SetPosition( m_dimension.GetCrossbarEnd() );
1232 return true;
1233 }
1234
1235 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1236 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1237 {
1239
1240 if( m_dimension.Type() == PCB_DIM_ALIGNED_T )
1241 updateAlignedDimension( aEditedPoint, aPoints );
1242 else
1243 updateOrthogonalDimension( aEditedPoint, aPoints );
1244 }
1245
1246 OPT_VECTOR2I Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const override
1247 {
1248 // Constraint for crossbar
1249 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1250 return aPoints.Point( DIM_END ).GetPosition();
1251
1252 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1253 return aPoints.Point( DIM_START ).GetPosition();
1254
1255 // No constraint
1256 return aEditedPoint.GetPosition();
1257 }
1258
1259private:
1263 void updateAlignedDimension( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints )
1264 {
1265 DIM_ALIGNED_TEXT_UPDATER textPositionUpdater( m_dimension );
1266
1267 // Check which point is currently modified and updated dimension's points respectively
1268 if( isModified( aEditedPoint, aPoints.Point( DIM_CROSSBARSTART ) ) )
1269 {
1270 VECTOR2D featureLine( aEditedPoint.GetPosition() - m_dimension.GetStart() );
1271 VECTOR2D crossBar( m_dimension.GetEnd() - m_dimension.GetStart() );
1272
1273 if( featureLine.Cross( crossBar ) > 0 )
1274 m_dimension.SetHeight( -featureLine.EuclideanNorm() );
1275 else
1276 m_dimension.SetHeight( featureLine.EuclideanNorm() );
1277
1278 m_dimension.Update();
1279 }
1280 else if( isModified( aEditedPoint, aPoints.Point( DIM_CROSSBAREND ) ) )
1281 {
1282 VECTOR2D featureLine( aEditedPoint.GetPosition() - m_dimension.GetEnd() );
1283 VECTOR2D crossBar( m_dimension.GetEnd() - m_dimension.GetStart() );
1284
1285 if( featureLine.Cross( crossBar ) > 0 )
1286 m_dimension.SetHeight( -featureLine.EuclideanNorm() );
1287 else
1288 m_dimension.SetHeight( featureLine.EuclideanNorm() );
1289
1290 m_dimension.Update();
1291 }
1292 else if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1293 {
1294 m_dimension.SetStart( aEditedPoint.GetPosition() );
1295 m_dimension.Update();
1296
1298 aPoints.Point( DIM_START ) ) );
1299 aPoints.Point( DIM_CROSSBAREND ).SetConstraint( new EC_LINE( aPoints.Point( DIM_CROSSBAREND ),
1300 aPoints.Point( DIM_END ) ) );
1301 }
1302 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1303 {
1304 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1305 m_dimension.Update();
1306
1308 aPoints.Point( DIM_START ) ) );
1309 aPoints.Point( DIM_CROSSBAREND ).SetConstraint( new EC_LINE( aPoints.Point( DIM_CROSSBAREND ),
1310 aPoints.Point( DIM_END ) ) );
1311 }
1312 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1313 {
1314 // Force manual mode if we weren't already in it
1315 m_dimension.SetTextPositionMode( DIM_TEXT_POSITION::MANUAL );
1316 m_dimension.SetTextPos( aEditedPoint.GetPosition() );
1317 m_dimension.Update();
1318 }
1319
1320 textPositionUpdater.UpdateTextAfterChange();
1321 }
1322
1326 void updateOrthogonalDimension( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints )
1327 {
1328 DIM_ALIGNED_TEXT_UPDATER textPositionUpdater( m_dimension );
1329 PCB_DIM_ORTHOGONAL& orthDimension = static_cast<PCB_DIM_ORTHOGONAL&>( m_dimension );
1330
1331 if( isModified( aEditedPoint, aPoints.Point( DIM_CROSSBARSTART ) )
1332 || isModified( aEditedPoint, aPoints.Point( DIM_CROSSBAREND ) ) )
1333 {
1334 BOX2I bounds( m_dimension.GetStart(), m_dimension.GetEnd() - m_dimension.GetStart() );
1335
1336 const VECTOR2I& cursorPos = aEditedPoint.GetPosition();
1337
1338 // Find vector from nearest dimension point to edit position
1339 VECTOR2I directionA( cursorPos - m_dimension.GetStart() );
1340 VECTOR2I directionB( cursorPos - m_dimension.GetEnd() );
1341 VECTOR2I direction = ( directionA < directionB ) ? directionA : directionB;
1342
1343 bool vert;
1344 VECTOR2D featureLine( cursorPos - m_dimension.GetStart() );
1345
1346 // Only change the orientation when we move outside the bounds
1347 if( !bounds.Contains( cursorPos ) )
1348 {
1349 // If the dimension is horizontal or vertical, set correct orientation
1350 // otherwise, test if we're left/right of the bounding box or above/below it
1351 if( bounds.GetWidth() == 0 )
1352 vert = true;
1353 else if( bounds.GetHeight() == 0 )
1354 vert = false;
1355 else if( cursorPos.x > bounds.GetLeft() && cursorPos.x < bounds.GetRight() )
1356 vert = false;
1357 else if( cursorPos.y > bounds.GetTop() && cursorPos.y < bounds.GetBottom() )
1358 vert = true;
1359 else
1360 vert = std::abs( direction.y ) < std::abs( direction.x );
1361
1364 }
1365 else
1366 {
1367 vert = orthDimension.GetOrientation() == PCB_DIM_ORTHOGONAL::DIR::VERTICAL;
1368 }
1369
1370 m_dimension.SetHeight( vert ? featureLine.x : featureLine.y );
1371 }
1372 else if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1373 {
1374 m_dimension.SetStart( aEditedPoint.GetPosition() );
1375 }
1376 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1377 {
1378 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1379 }
1380 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1381 {
1382 // Force manual mode if we weren't already in it
1383 m_dimension.SetTextPositionMode( DIM_TEXT_POSITION::MANUAL );
1384 m_dimension.SetTextPos( VECTOR2I( aEditedPoint.GetPosition() ) );
1385 }
1386
1387 m_dimension.Update();
1388
1389 // After recompute, find the new text position
1390 textPositionUpdater.UpdateTextAfterChange();
1391 }
1392
1394};
1395
1396
1398{
1399public:
1401 m_dimension( aDimension )
1402 {}
1403
1404 void MakePoints( EDIT_POINTS& aPoints ) override
1405 {
1406 aPoints.AddPoint( m_dimension.GetStart() );
1407 aPoints.AddPoint( m_dimension.GetEnd() );
1408
1410
1411 aPoints.Point( DIM_END ).SetConstraint(new EC_45DEGREE( aPoints.Point( DIM_END ),
1412 aPoints.Point( DIM_START ) ) );
1414 }
1415
1416 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1417 {
1418 wxCHECK( aPoints.PointsSize() == DIM_CENTER_MAX, false );
1419
1420 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1421 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1422 return true;
1423 }
1424
1425 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1426 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1427 {
1429
1430 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1431 m_dimension.SetStart( aEditedPoint.GetPosition() );
1432 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1433 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1434
1435 m_dimension.Update();
1436 }
1437
1438 OPT_VECTOR2I Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const override
1439 {
1440 if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1441 return aPoints.Point( DIM_START ).GetPosition();
1442
1443 return std::nullopt;
1444 }
1445
1446private:
1448};
1449
1450
1452{
1453public:
1455 m_dimension( aDimension )
1456 {}
1457
1458 void MakePoints( EDIT_POINTS& aPoints ) override
1459 {
1460 aPoints.AddPoint( m_dimension.GetStart() );
1461 aPoints.AddPoint( m_dimension.GetEnd() );
1462 aPoints.AddPoint( m_dimension.GetTextPos() );
1463 aPoints.AddPoint( m_dimension.GetKnee() );
1464
1467
1468 aPoints.Point( DIM_KNEE ).SetConstraint( new EC_LINE( aPoints.Point( DIM_START ),
1469 aPoints.Point( DIM_END ) ) );
1471
1472 aPoints.Point( DIM_TEXT ).SetConstraint( new EC_45DEGREE( aPoints.Point( DIM_TEXT ),
1473 aPoints.Point( DIM_KNEE ) ) );
1475 }
1476
1477 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1478 {
1479 wxCHECK( aPoints.PointsSize() == DIM_RADIAL_MAX, false );
1480
1481 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1482 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1483 aPoints.Point( DIM_TEXT ).SetPosition( m_dimension.GetTextPos() );
1484 aPoints.Point( DIM_KNEE ).SetPosition( m_dimension.GetKnee() );
1485 return true;
1486 }
1487
1488 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1489 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1490 {
1492
1493 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1494 {
1495 m_dimension.SetStart( aEditedPoint.GetPosition() );
1496 m_dimension.Update();
1497
1498 aPoints.Point( DIM_KNEE ).SetConstraint( new EC_LINE( aPoints.Point( DIM_START ),
1499 aPoints.Point( DIM_END ) ) );
1500 }
1501 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1502 {
1503 VECTOR2I oldKnee = m_dimension.GetKnee();
1504
1505 m_dimension.SetEnd( aEditedPoint.GetPosition() );
1506 m_dimension.Update();
1507
1508 VECTOR2I kneeDelta = m_dimension.GetKnee() - oldKnee;
1509 m_dimension.SetTextPos( m_dimension.GetTextPos() + kneeDelta );
1510 m_dimension.Update();
1511
1512 aPoints.Point( DIM_KNEE ).SetConstraint( new EC_LINE( aPoints.Point( DIM_START ),
1513 aPoints.Point( DIM_END ) ) );
1514 }
1515 else if( isModified( aEditedPoint, aPoints.Point( DIM_KNEE ) ) )
1516 {
1517 VECTOR2I oldKnee = m_dimension.GetKnee();
1518 VECTOR2I arrowVec = aPoints.Point( DIM_KNEE ).GetPosition() - aPoints.Point( DIM_END ).GetPosition();
1519
1520 m_dimension.SetLeaderLength( arrowVec.EuclideanNorm() );
1521 m_dimension.Update();
1522
1523 VECTOR2I kneeDelta = m_dimension.GetKnee() - oldKnee;
1524 m_dimension.SetTextPos( m_dimension.GetTextPos() + kneeDelta );
1525 m_dimension.Update();
1526 }
1527 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1528 {
1529 m_dimension.SetTextPos( aEditedPoint.GetPosition() );
1530 m_dimension.Update();
1531 }
1532 }
1533
1534 OPT_VECTOR2I Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const override
1535 {
1536 if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1537 return aPoints.Point( DIM_KNEE ).GetPosition();
1538
1539 return std::nullopt;
1540 }
1541
1542private:
1544};
1545
1546
1548{
1549public:
1551 m_dimension( aDimension )
1552 {}
1553
1554 void MakePoints( EDIT_POINTS& aPoints ) override
1555 {
1556 aPoints.AddPoint( m_dimension.GetStart() );
1557 aPoints.AddPoint( m_dimension.GetEnd() );
1558 aPoints.AddPoint( m_dimension.GetTextPos() );
1559
1562
1563 aPoints.Point( DIM_TEXT ).SetConstraint( new EC_45DEGREE( aPoints.Point( DIM_TEXT ),
1564 aPoints.Point( DIM_END ) ) );
1566 }
1567
1568 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1569 {
1570 wxCHECK( aPoints.PointsSize() == DIM_LEADER_MAX, false );
1571
1572 aPoints.Point( DIM_START ).SetPosition( m_dimension.GetStart() );
1573 aPoints.Point( DIM_END ).SetPosition( m_dimension.GetEnd() );
1574 aPoints.Point( DIM_TEXT ).SetPosition( m_dimension.GetTextPos() );
1575 return true;
1576 }
1577
1578 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1579 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1580 {
1582
1583 if( isModified( aEditedPoint, aPoints.Point( DIM_START ) ) )
1584 {
1585 m_dimension.SetStart( aEditedPoint.GetPosition() );
1586 }
1587 else if( isModified( aEditedPoint, aPoints.Point( DIM_END ) ) )
1588 {
1589 const VECTOR2I newPoint( aEditedPoint.GetPosition() );
1590 const VECTOR2I delta = newPoint - m_dimension.GetEnd();
1591
1592 m_dimension.SetEnd( newPoint );
1593 m_dimension.SetTextPos( m_dimension.GetTextPos() + delta );
1594 }
1595 else if( isModified( aEditedPoint, aPoints.Point( DIM_TEXT ) ) )
1596 {
1597 m_dimension.SetTextPos( aEditedPoint.GetPosition() );
1598 }
1599
1600 m_dimension.Update();
1601 }
1602
1603private:
1605};
1606
1607
1612{
1613public:
1615 m_textbox( aTextbox )
1616 {}
1617
1618 void MakePoints( EDIT_POINTS& aPoints ) override
1619 {
1620 if( m_textbox.GetShape() == SHAPE_T::RECTANGLE )
1622
1623 // Rotated textboxes are implemented as polygons and these aren't currently editable.
1624 }
1625
1626 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1627 {
1628 // Careful; textbox shape is mutable between cardinal and non-cardinal rotations...
1629 const unsigned target = m_textbox.GetShape() == SHAPE_T::RECTANGLE ? RECT_MAX_POINTS : 0;
1630
1631 if( aPoints.PointsSize() != target )
1632 return false;
1633
1635 return true;
1636 }
1637
1638 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1639 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1640 {
1641 if( m_textbox.GetShape() == SHAPE_T::RECTANGLE )
1642 {
1643 m_textbox.ClearBoundingBoxCache();
1644 VECTOR2I minSize = m_textbox.GetMinSize();
1646 }
1647 }
1648
1649private:
1651};
1652
1654{
1655public:
1657 m_group( &aGroup ),
1658 m_parent( &aGroup )
1659 {
1660 for( BOARD_ITEM* item : aGroup.GetBoardItems() )
1661 {
1662 if( item->Type() == PCB_SHAPE_T )
1663 {
1664 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
1665 m_shapes.push_back( shape );
1666 m_originalWidths[shape] = static_cast<double>( shape->GetWidth() );
1667 }
1668 }
1669 }
1670
1671 SHAPE_GROUP_POINT_EDIT_BEHAVIOR( std::vector<PCB_SHAPE*> aShapes, BOARD_ITEM* aParent ) :
1672 m_group( nullptr ),
1673 m_shapes( std::move( aShapes ) ),
1674 m_parent( aParent )
1675 {
1676 for( PCB_SHAPE* shape : m_shapes )
1677 m_originalWidths[shape] = static_cast<double>( shape->GetWidth() );
1678 }
1679
1680 void MakePoints( EDIT_POINTS& aPoints ) override
1681 {
1682 BOX2I bbox = getBoundingBox();
1683 VECTOR2I tl = bbox.GetOrigin();
1684 VECTOR2I br = bbox.GetEnd();
1685
1686 aPoints.AddPoint( tl );
1687 aPoints.AddPoint( VECTOR2I( br.x, tl.y ) );
1688 aPoints.AddPoint( br );
1689 aPoints.AddPoint( VECTOR2I( tl.x, br.y ) );
1690 aPoints.AddPoint( bbox.Centre() );
1691
1692 aPoints.AddIndicatorLine( aPoints.Point( RECT_TOP_LEFT ), aPoints.Point( RECT_TOP_RIGHT ) );
1693 aPoints.AddIndicatorLine( aPoints.Point( RECT_TOP_RIGHT ), aPoints.Point( RECT_BOT_RIGHT ) );
1694 aPoints.AddIndicatorLine( aPoints.Point( RECT_BOT_RIGHT ), aPoints.Point( RECT_BOT_LEFT ) );
1695 aPoints.AddIndicatorLine( aPoints.Point( RECT_BOT_LEFT ), aPoints.Point( RECT_TOP_LEFT ) );
1696 }
1697
1698 bool UpdatePoints( EDIT_POINTS& aPoints ) override
1699 {
1700 BOX2I bbox = getBoundingBox();
1701 VECTOR2I tl = bbox.GetOrigin();
1702 VECTOR2I br = bbox.GetEnd();
1703
1704 aPoints.Point( RECT_TOP_LEFT ).SetPosition( tl );
1705 aPoints.Point( RECT_TOP_RIGHT ).SetPosition( br.x, tl.y );
1706 aPoints.Point( RECT_BOT_RIGHT ).SetPosition( br );
1707 aPoints.Point( RECT_BOT_LEFT ).SetPosition( tl.x, br.y );
1708 aPoints.Point( RECT_CENTER ).SetPosition( bbox.Centre() );
1709 return true;
1710 }
1711
1712 void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit,
1713 std::vector<EDA_ITEM*>& aUpdatedItems ) override
1714 {
1715 BOX2I oldBox = getBoundingBox();
1716 VECTOR2I oldCenter = oldBox.Centre();
1717
1718 if( isModified( aEditedPoint, aPoints.Point( RECT_CENTER ) ) )
1719 {
1720 VECTOR2I delta = aPoints.Point( RECT_CENTER ).GetPosition() - oldCenter;
1721
1722 if( m_group )
1723 {
1724 aCommit.Modify( m_group, nullptr, RECURSE_MODE::RECURSE );
1725 m_group->Move( delta );
1726 }
1727 else
1728 {
1729 for( PCB_SHAPE* shape : m_shapes )
1730 {
1731 aCommit.Modify( shape );
1732 shape->Move( delta );
1733 }
1734 }
1735
1736 for( PCB_SHAPE* shape : m_shapes )
1737 aUpdatedItems.push_back( shape );
1738
1739 UpdatePoints( aPoints );
1740 return;
1741 }
1742
1743 VECTOR2I tl = aPoints.Point( RECT_TOP_LEFT ).GetPosition();
1744 VECTOR2I tr = aPoints.Point( RECT_TOP_RIGHT ).GetPosition();
1745 VECTOR2I bl = aPoints.Point( RECT_BOT_LEFT ).GetPosition();
1746 VECTOR2I br = aPoints.Point( RECT_BOT_RIGHT ).GetPosition();
1747
1748 RECTANGLE_POINT_EDIT_BEHAVIOR::PinEditedCorner( aEditedPoint, aPoints, tl, tr, bl, br );
1749
1750 double sx = static_cast<double>( br.x - tl.x ) / static_cast<double>( oldBox.GetWidth() );
1751 double sy = static_cast<double>( br.y - tl.y ) / static_cast<double>( oldBox.GetHeight() );
1752 double scale = ( sx + sy ) / 2.0;
1753
1754 // Prevent scaling below a minimum threshold to avoid precision loss when shapes
1755 // are scaled to near-zero size. Also prevent negative scaling which would flip
1756 // shapes when dragging past the center point.
1757 const double MIN_SCALE = 0.01;
1758
1759 if( scale < MIN_SCALE )
1760 scale = MIN_SCALE;
1761
1762 for( PCB_SHAPE* shape : m_shapes )
1763 {
1764 aCommit.Modify( shape );
1765 shape->Move( -oldCenter );
1766 shape->Scale( scale );
1767 shape->Move( oldCenter );
1768
1769 if( auto shapeIt = m_originalWidths.find( shape ); shapeIt != m_originalWidths.end() )
1770 {
1771 shapeIt->second = shapeIt->second * scale;
1772 shape->SetWidth( KiROUND( shapeIt->second ) );
1773 }
1774 else
1775 {
1776 shape->SetWidth( KiROUND( shape->GetWidth() * scale ) );
1777 }
1778
1779 aUpdatedItems.push_back( shape );
1780 }
1781
1782 UpdatePoints( aPoints );
1783 }
1784
1785 BOARD_ITEM* GetParent() const { return m_parent; }
1786
1787private:
1789 {
1790 BOX2I bbox;
1791
1792 for( const PCB_SHAPE* shape : m_shapes )
1793 bbox.Merge( shape->GetBoundingBox() );
1794
1795 return bbox;
1796 }
1797
1798private:
1800 std::vector<PCB_SHAPE*> m_shapes;
1802 std::unordered_map<PCB_SHAPE*, double> m_originalWidths;
1803};
1804
1805
1807 PCB_TOOL_BASE( "pcbnew.PointEditor" ),
1808 m_frame( nullptr ),
1809 m_selectionTool( nullptr ),
1810 m_editedPoint( nullptr ),
1811 m_hoveredPoint( nullptr ),
1812 m_original( VECTOR2I( 0, 0 ) ),
1814 m_radiusHelper( nullptr ),
1815 m_altConstrainer( VECTOR2I( 0, 0 ) ),
1816 m_inPointEditorTool( false ),
1817 m_angleSnapPos( VECTOR2I( 0, 0 ) ),
1818 m_stickyDisplacement( VECTOR2I( 0, 0 ) ),
1819 m_angleSnapActive( false )
1820{}
1821
1822
1824{
1826
1827 if( KIGFX::VIEW* view = getView() )
1828 {
1829 if( m_angleItem && view->HasItem( m_angleItem.get() ) )
1830 view->Remove( m_angleItem.get() );
1831
1832 if( m_editPoints && view->HasItem( m_editPoints.get() ) )
1833 view->Remove( m_editPoints.get() );
1834
1835 if( view->HasItem( &m_preview ) )
1836 view->Remove( &m_preview );
1837 }
1838
1839 m_angleItem.reset();
1840 m_editPoints.reset();
1841 m_altConstraint.reset();
1842 getViewControls()->SetAutoPan( false );
1843 m_angleSnapActive = false;
1845}
1846
1847
1849{
1850 const KICAD_T type = aItem.Type();
1851
1852 if( type == PCB_ZONE_T )
1853 return true;
1854
1855 if( type == PCB_SHAPE_T )
1856 {
1857 const PCB_SHAPE& shape = static_cast<const PCB_SHAPE&>( aItem );
1858 const SHAPE_T shapeType = shape.GetShape();
1859 return shapeType == SHAPE_T::SEGMENT || shapeType == SHAPE_T::POLY || shapeType == SHAPE_T::ARC;
1860 }
1861
1862 return false;
1863}
1864
1865
1867{
1868 const auto type = aItem.Type();
1869
1870 if( type == PCB_ZONE_T )
1871 return true;
1872
1873 if( type == PCB_SHAPE_T )
1874 {
1875 const PCB_SHAPE& shape = static_cast<const PCB_SHAPE&>( aItem );
1876 const SHAPE_T shapeType = shape.GetShape();
1877 return shapeType == SHAPE_T::POLY;
1878 }
1879
1880 return false;
1881}
1882
1883
1884static VECTOR2I snapCorner( const VECTOR2I& aPrev, const VECTOR2I& aNext, const VECTOR2I& aGuess,
1885 double aAngleDeg )
1886{
1887 double angleRad = aAngleDeg * M_PI / 180.0;
1888 VECTOR2D prev( aPrev );
1889 VECTOR2D next( aNext );
1890 double chord = ( next - prev ).EuclideanNorm();
1891 double sinA = sin( angleRad );
1892
1893 if( chord == 0.0 || fabs( sinA ) < 1e-9 )
1894 return aGuess;
1895
1896 double radius = chord / ( 2.0 * sinA );
1897 VECTOR2D mid = ( prev + next ) / 2.0;
1898 VECTOR2D dir = next - prev;
1899 VECTOR2D normal( -dir.y, dir.x );
1900 normal = normal.Resize( 1 );
1901 double h_sq = radius * radius - ( chord * chord ) / 4.0;
1902 double h = h_sq > 0.0 ? sqrt( h_sq ) : 0.0;
1903
1904 VECTOR2D center1 = mid + normal * h;
1905 VECTOR2D center2 = mid - normal * h;
1906
1907 auto project =
1908 [&]( const VECTOR2D& center )
1909 {
1910 VECTOR2D v = VECTOR2D( aGuess ) - center;
1911
1912 if( v.EuclideanNorm() == 0.0 )
1913 v = prev - center;
1914
1915 v = v.Resize( 1 );
1916 VECTOR2D p = center + v * radius;
1917 return KiROUND( p );
1918 };
1919
1920 VECTOR2I p1 = project( center1 );
1921 VECTOR2I p2 = project( center2 );
1922
1923 double d1 = ( VECTOR2D( aGuess ) - VECTOR2D( p1 ) ).EuclideanNorm();
1924 double d2 = ( VECTOR2D( aGuess ) - VECTOR2D( p2 ) ).EuclideanNorm();
1925
1926 return d1 < d2 ? p1 : p2;
1927}
1928
1929
1931{
1932 // Find the selection tool, so they can cooperate
1934
1935 wxASSERT_MSG( m_selectionTool, wxT( "pcbnew.InteractiveSelection tool is not available" ) );
1936
1937 const auto arcIsEdited =
1938 []( const SELECTION& aSelection ) -> bool
1939 {
1940 const EDA_ITEM* item = aSelection.Front();
1941 return ( item != nullptr ) && ( item->Type() == PCB_SHAPE_T )
1942 && static_cast<const PCB_SHAPE*>( item )->GetShape() == SHAPE_T::ARC;
1943 };
1944
1945 using S_C = SELECTION_CONDITIONS;
1946
1947 auto& menu = m_selectionTool->GetToolMenu().GetMenu();
1948
1949 menu.AddItem( PCB_ACTIONS::cycleArcEditMode, S_C::Count( 1 ) && arcIsEdited );
1950
1951 return true;
1952}
1953
1954
1955std::shared_ptr<EDIT_POINTS> PCB_POINT_EDITOR::makePoints( EDA_ITEM* aItem )
1956{
1957 std::shared_ptr<EDIT_POINTS> points = std::make_shared<EDIT_POINTS>( aItem );
1958
1959 if( !aItem )
1960 return points;
1961
1962 // Reset the behaviour and we'll make a new one
1963 m_editorBehavior = nullptr;
1964
1965 switch( aItem->Type() )
1966 {
1968 {
1969 PCB_REFERENCE_IMAGE& refImage = static_cast<PCB_REFERENCE_IMAGE&>( *aItem );
1970 m_editorBehavior = std::make_unique<REFERENCE_IMAGE_POINT_EDIT_BEHAVIOR>( refImage );
1971 break;
1972 }
1973 case PCB_BARCODE_T:
1974 {
1975 PCB_BARCODE& barcode = static_cast<PCB_BARCODE&>( *aItem );
1976 m_editorBehavior = std::make_unique<BARCODE_POINT_EDIT_BEHAVIOR>( barcode );
1977 break;
1978 }
1979 case PCB_TEXTBOX_T:
1980 {
1981 PCB_TEXTBOX& textbox = static_cast<PCB_TEXTBOX&>( *aItem );
1982 m_editorBehavior = std::make_unique<TEXTBOX_POINT_EDIT_BEHAVIOR>( textbox );
1983 break;
1984 }
1985 case PCB_SHAPE_T:
1986 {
1987 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
1988
1989 switch( shape->GetShape() )
1990 {
1991 case SHAPE_T::SEGMENT:
1992 m_editorBehavior = std::make_unique<EDA_SEGMENT_POINT_EDIT_BEHAVIOR>( *shape );
1993 break;
1994
1995 case SHAPE_T::RECTANGLE:
1996 m_editorBehavior = std::make_unique<RECTANGLE_POINT_EDIT_BEHAVIOR>( *shape );
1997 break;
1998
1999 case SHAPE_T::ARC:
2000 m_editorBehavior = std::make_unique<EDA_ARC_POINT_EDIT_BEHAVIOR>( *shape, m_arcEditMode,
2001 *getViewControls() );
2002 break;
2003
2004 case SHAPE_T::CIRCLE:
2005 m_editorBehavior = std::make_unique<EDA_CIRCLE_POINT_EDIT_BEHAVIOR>( *shape );
2006 break;
2007
2008 case SHAPE_T::POLY:
2009 m_editorBehavior = std::make_unique<EDA_POLYGON_POINT_EDIT_BEHAVIOR>( *shape );
2010 break;
2011
2012 case SHAPE_T::BEZIER:
2013 m_editorBehavior = std::make_unique<EDA_BEZIER_POINT_EDIT_BEHAVIOR>( *shape,
2014 shape->GetMaxError() );
2015 break;
2016
2017 case SHAPE_T::ELLIPSE:
2019 m_editorBehavior = std::make_unique<EDA_ELLIPSE_POINT_EDIT_BEHAVIOR>( *shape );
2020 break;
2021
2022 default: // suppress warnings
2023 break;
2024 }
2025
2026 break;
2027 }
2028
2029 case PCB_GROUP_T:
2030 {
2031 PCB_GROUP* group = static_cast<PCB_GROUP*>( aItem );
2032 bool shapesOnly = true;
2033
2034 for( BOARD_ITEM* child : group->GetBoardItems() )
2035 {
2036 if( child->Type() != PCB_SHAPE_T )
2037 {
2038 shapesOnly = false;
2039 break;
2040 }
2041 }
2042
2043 if( shapesOnly )
2044 m_editorBehavior = std::make_unique<SHAPE_GROUP_POINT_EDIT_BEHAVIOR>( *group );
2045 else
2046 points.reset();
2047
2048 break;
2049 }
2050
2051 case PCB_TABLECELL_T:
2052 {
2053 PCB_TABLECELL* cell = static_cast<PCB_TABLECELL*>( aItem );
2054
2055 // No support for point-editing of a rotated table
2056 if( cell->GetShape() == SHAPE_T::RECTANGLE )
2057 m_editorBehavior = std::make_unique<PCB_TABLECELL_POINT_EDIT_BEHAVIOR>( *cell );
2058
2059 break;
2060 }
2061
2062 case PCB_PAD_T:
2063 {
2064 // Pad edit only for the footprint editor
2066 {
2067 PAD& pad = static_cast<PAD&>( *aItem );
2068 PCB_LAYER_ID activeLayer = m_frame ? m_frame->GetActiveLayer() : PADSTACK::ALL_LAYERS;
2069
2070 // Point editor only handles copper shape changes
2071 if( !IsCopperLayer( activeLayer ) )
2072 activeLayer = IsFrontLayer( activeLayer ) ? F_Cu : B_Cu;
2073
2074 m_editorBehavior = std::make_unique<PAD_POINT_EDIT_BEHAVIOR>( pad, activeLayer );
2075 }
2076 break;
2077 }
2078
2079 case PCB_ZONE_T:
2080 {
2081 ZONE& zone = static_cast<ZONE&>( *aItem );
2082 m_editorBehavior = std::make_unique<ZONE_POINT_EDIT_BEHAVIOR>( zone );
2083 break;
2084 }
2085
2086 case PCB_GENERATOR_T:
2087 {
2088 PCB_GENERATOR* generator = static_cast<PCB_GENERATOR*>( aItem );
2089 m_editorBehavior = std::make_unique<GENERATOR_POINT_EDIT_BEHAVIOR>( *generator );
2090 break;
2091 }
2092
2093 case PCB_DIM_ALIGNED_T:
2095 {
2096 PCB_DIM_ALIGNED& dimension = static_cast<PCB_DIM_ALIGNED&>( *aItem );
2097 m_editorBehavior = std::make_unique<ALIGNED_DIMENSION_POINT_EDIT_BEHAVIOR>( dimension );
2098 break;
2099 }
2100
2101 case PCB_DIM_CENTER_T:
2102 {
2103 PCB_DIM_CENTER& dimension = static_cast<PCB_DIM_CENTER&>( *aItem );
2104 m_editorBehavior = std::make_unique<DIM_CENTER_POINT_EDIT_BEHAVIOR>( dimension );
2105 break;
2106 }
2107
2108 case PCB_DIM_RADIAL_T:
2109 {
2110 PCB_DIM_RADIAL& dimension = static_cast<PCB_DIM_RADIAL&>( *aItem );
2111 m_editorBehavior = std::make_unique<DIM_RADIAL_POINT_EDIT_BEHAVIOR>( dimension );
2112 break;
2113 }
2114
2115 case PCB_DIM_LEADER_T:
2116 {
2117 PCB_DIM_LEADER& dimension = static_cast<PCB_DIM_LEADER&>( *aItem );
2118 m_editorBehavior = std::make_unique<DIM_LEADER_POINT_EDIT_BEHAVIOR>( dimension );
2119 break;
2120 }
2121
2122 default:
2123 points.reset();
2124 break;
2125 }
2126
2127 if( m_editorBehavior )
2128 m_editorBehavior->MakePoints( *points );
2129
2130 return points;
2131}
2132
2133
2135{
2136 EDIT_POINT* point;
2137 EDIT_POINT* hovered = nullptr;
2138
2139 if( aEvent.IsMotion() )
2140 {
2141 point = m_editPoints->FindPoint( aEvent.Position(), getView() );
2142 hovered = point;
2143 }
2144 else if( aEvent.IsDrag( BUT_LEFT ) )
2145 {
2146 point = m_editPoints->FindPoint( aEvent.DragOrigin(), getView() );
2147 }
2148 else
2149 {
2150 point = m_editPoints->FindPoint( getViewControls()->GetCursorPosition(), getView() );
2151 }
2152
2153 if( hovered )
2154 {
2155 if( m_hoveredPoint != hovered )
2156 {
2157 if( m_hoveredPoint )
2158 m_hoveredPoint->SetHover( false );
2159
2160 m_hoveredPoint = hovered;
2161 m_hoveredPoint->SetHover();
2162 }
2163 }
2164 else if( m_hoveredPoint )
2165 {
2166 m_hoveredPoint->SetHover( false );
2167 m_hoveredPoint = nullptr;
2168 }
2169
2170 if( m_editedPoint != point )
2171 setEditedPoint( point );
2172}
2173
2174
2176{
2178 return 0;
2179
2181 return 0;
2182
2184
2186 const PCB_SELECTION& selection = m_selectionTool->GetSelection();
2187
2188 if( selection.Size() == 0 )
2189 return 0;
2190
2191 for( EDA_ITEM* selItem : selection )
2192 {
2193 if( selItem->GetEditFlags() || !selItem->IsBOARD_ITEM() )
2194 return 0;
2195 }
2196
2197 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
2198
2199 if( !item || item->IsLocked() )
2200 return 0;
2201
2202 Activate();
2203 // Must be done after Activate() so that it gets set into the correct context
2204 getViewControls()->ShowCursor( true );
2205
2207
2208 // Use the original object as a construction item
2209 std::vector<std::unique_ptr<BOARD_ITEM>> clones;
2210
2211 m_editorBehavior.reset();
2212
2213 if( selection.Size() > 1 )
2214 {
2215 // Multi-selection: check if all items are shapes
2216 std::vector<PCB_SHAPE*> shapes;
2217 bool allShapes = true;
2218 bool anyLocked = false;
2219
2220 for( EDA_ITEM* selItem : selection )
2221 {
2222 if( selItem->Type() == PCB_SHAPE_T )
2223 {
2224 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( selItem );
2225 shapes.push_back( shape );
2226
2227 if( shape->IsLocked() )
2228 anyLocked = true;
2229 }
2230 else
2231 {
2232 allShapes = false;
2233 }
2234 }
2235
2236 if( allShapes && shapes.size() > 1 && !anyLocked )
2237 {
2238 m_editorBehavior = std::make_unique<SHAPE_GROUP_POINT_EDIT_BEHAVIOR>(
2239 std::move( shapes ), item );
2240 m_editPoints = std::make_shared<EDIT_POINTS>( item );
2241 m_editorBehavior->MakePoints( *m_editPoints );
2242 }
2243 else
2244 {
2245 return 0;
2246 }
2247 }
2248 else
2249 {
2250 // Single selection: use existing makePoints logic
2251 m_editPoints = makePoints( item );
2252 }
2253
2254 if( !m_editPoints )
2255 return 0;
2256
2257 PCB_SHAPE* graphicItem = dynamic_cast<PCB_SHAPE*>( item );
2258
2259 // Only add the angle_item if we are editing a polygon or zone
2260 if( item->Type() == PCB_ZONE_T || ( graphicItem && graphicItem->GetShape() == SHAPE_T::POLY ) )
2261 {
2262 m_angleItem = std::make_unique<KIGFX::PREVIEW::ANGLE_ITEM>( m_editPoints );
2263 }
2264
2265 m_preview.FreeItems();
2266 m_radiusHelper = nullptr;
2267 getView()->Add( &m_preview );
2268
2271
2272 getView()->Add( m_editPoints.get() );
2273
2274 if( m_angleItem )
2275 getView()->Add( m_angleItem.get() );
2276
2277 setEditedPoint( nullptr );
2278 updateEditedPoint( aEvent );
2279 bool inDrag = false;
2280 bool isConstrained = false;
2281 bool haveSnapLineDirections = false;
2282
2283 auto updateSnapLineDirections =
2284 [&]()
2285 {
2286 std::vector<VECTOR2I> directions;
2287
2288 if( inDrag && m_editedPoint )
2289 {
2290 EDIT_CONSTRAINT<EDIT_POINT>* constraint = nullptr;
2291
2292 if( m_altConstraint )
2293 constraint = m_altConstraint.get();
2294 else if( m_editedPoint->IsConstrained() )
2295 constraint = m_editedPoint->GetConstraint();
2296
2297 directions = getConstraintDirections( constraint );
2298 }
2299
2300 if( directions.empty() )
2301 {
2302 grid.SetSnapLineDirections( {} );
2303 grid.SetSnapLineEnd( std::nullopt );
2304 haveSnapLineDirections = false;
2305 }
2306 else
2307 {
2308 VECTOR2I origin = m_altConstraint ? m_altConstrainer.GetPosition() : m_original.GetPosition();
2309
2310 grid.SetSnapLineDirections( directions );
2311 grid.SetSnapLineOrigin( origin );
2312 grid.SetSnapLineEnd( std::nullopt );
2313 haveSnapLineDirections = true;
2314 }
2315 };
2316
2317 BOARD_COMMIT commit( editFrame );
2318
2319 // Main loop: keep receiving events
2320 while( TOOL_EVENT* evt = Wait() )
2321 {
2322 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
2323 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
2324
2325 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
2327 else
2329
2330 if( !m_editPoints || evt->IsSelectionEvent() || evt->Matches( EVENTS::InhibitSelectionEditing ) )
2331 {
2332 break;
2333 }
2334
2335 EDIT_POINT* prevHover = m_hoveredPoint;
2336
2337 if( !inDrag )
2338 updateEditedPoint( *evt );
2339
2340 if( prevHover != m_hoveredPoint )
2341 {
2342 getView()->Update( m_editPoints.get() );
2343
2344 if( m_angleItem )
2345 getView()->Update( m_angleItem.get() );
2346 }
2347
2348 if( evt->IsDrag( BUT_LEFT ) && m_editedPoint )
2349 {
2350 if( !inDrag )
2351 {
2352 frame()->UndoRedoBlock( true );
2353
2354 if( item->Type() == PCB_GENERATOR_T )
2355 {
2356 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genStartEdit, &commit,
2357 static_cast<PCB_GENERATOR*>( item ) );
2358 }
2359
2361 m_original = *m_editedPoint; // Save the original position
2362 getViewControls()->SetAutoPan( true );
2363 inDrag = true;
2364
2365 if( m_editedPoint->GetGridConstraint() != SNAP_BY_GRID )
2366 grid.SetAuxAxes( true, m_original.GetPosition() );
2367
2368 m_editedPoint->SetActive();
2369
2370 for( size_t ii = 0; ii < m_editPoints->PointsSize(); ++ii )
2371 {
2372 EDIT_POINT& point = m_editPoints->Point( ii );
2373
2374 if( &point != m_editedPoint )
2375 point.SetActive( false );
2376 }
2377
2378 // When we start dragging, create a clone of the item to use as the original
2379 // reference geometry (e.g. for intersections and extensions)
2380 BOARD_ITEM* clone = static_cast<BOARD_ITEM*>( item->Clone() );
2381 clone->SetParent( nullptr );
2382
2383 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2384 {
2385 shape->SetFlags( IS_MOVING );
2386 shape->UpdateHatching();
2387
2388 static_cast<PCB_SHAPE*>( clone )->SetFillMode( FILL_T::NO_FILL );
2389 }
2390
2391 clones.emplace_back( clone );
2392 grid.AddConstructionItems( { clone }, false, true );
2393
2394 updateSnapLineDirections();
2395 }
2396
2397 EDIT_LINE* line = dynamic_cast<EDIT_LINE*>( m_editedPoint );
2398 bool ctrlHeld = evt->Modifier( MD_CTRL );
2399
2400 bool need_constraint = ( Is45Limited() || Is90Limited() ) && !ctrlHeld;
2401
2402 if( isConstrained != need_constraint )
2403 {
2404 setAltConstraint( need_constraint );
2405 isConstrained = need_constraint;
2406 updateSnapLineDirections();
2407 }
2408
2409 // For polygon lines, Ctrl temporarily toggles between CONVERGING and FIXED_LENGTH modes
2410
2411 if( line )
2412 {
2413 bool isPoly = false;
2414
2415 switch( item->Type() )
2416 {
2417 case PCB_ZONE_T:
2418 isPoly = true;
2419 break;
2420
2421 case PCB_SHAPE_T:
2422 isPoly = static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY;
2423 break;
2424
2425 default:
2426 break;
2427 }
2428
2429 if( isPoly )
2430 {
2431 EC_CONVERGING* constraint =
2432 dynamic_cast<EC_CONVERGING*>( line->GetConstraint() );
2433
2434 if( constraint )
2435 {
2438
2439 if( constraint->GetMode() != targetMode )
2440 constraint->SetMode( targetMode );
2441 }
2442 }
2443 }
2444
2445 // Keep point inside of limits with some padding
2446 VECTOR2I pos = GetClampedCoords<double, int>( evt->Position(), COORDS_PADDING );
2447 LSET snapLayers;
2448
2449 switch( m_editedPoint->GetSnapConstraint() )
2450 {
2451 case IGNORE_SNAPS: break;
2452 case OBJECT_LAYERS: snapLayers = item->GetLayerSet(); break;
2453 case ALL_LAYERS: snapLayers = LSET::AllLayersMask(); break;
2454 }
2455
2456 if( m_editedPoint->GetGridConstraint() == SNAP_BY_GRID )
2457 {
2458 if( grid.GetUseGrid() )
2459 {
2460 EC_CONVERGING* convergingConstraint =
2461 line ? dynamic_cast<EC_CONVERGING*>( line->GetConstraint() ) : nullptr;
2462
2463 bool snappedAlongPerp = false;
2464
2465 if( convergingConstraint )
2466 {
2467 // For a polygon edge, the line moves only perpendicular to itself.
2468 // Snapping pos.x and pos.y independently to the axis-aligned grid
2469 // produces inconsistent perpendicular displacements when the edge is
2470 // tilted (different magnitudes depending on which axis crossed the
2471 // half-grid threshold first), causing the rendered edge to flicker
2472 // between two positions. Quantize the perpendicular displacement
2473 // directly so each grid step produces one stable line position.
2474 const VECTOR2I& origCenter = convergingConstraint->GetOriginalCenter();
2475 const VECTOR2I& perpVec = convergingConstraint->GetPerpVector();
2476 double perpLen = VECTOR2D( perpVec ).EuclideanNorm();
2477
2478 if( perpLen > 0 )
2479 {
2480 VECTOR2D perpUnit = VECTOR2D( perpVec ) / perpLen;
2481 VECTOR2D gridSize = grid.GetGridSize( grid.GetItemGrid( item ) );
2482
2483 // Effective grid spacing along the perpendicular direction. For an
2484 // axis-aligned edge this reduces to the grid pitch on that axis.
2485 double step = std::hypot( gridSize.x * perpUnit.x,
2486 gridSize.y * perpUnit.y );
2487
2488 if( step > 0 )
2489 {
2490 double offset = VECTOR2D( pos - origCenter ).Dot( perpUnit );
2491 double snapped = std::round( offset / step ) * step;
2492 VECTOR2D snappedPt = VECTOR2D( origCenter ) + perpUnit * snapped;
2493 pos = VECTOR2I( KiROUND( snappedPt.x ), KiROUND( snappedPt.y ) );
2494 snappedAlongPerp = true;
2495 }
2496 }
2497 }
2498
2499 if( !snappedAlongPerp )
2500 {
2501 VECTOR2I gridPt = grid.BestSnapAnchor( pos, {}, grid.GetItemGrid( item ),
2502 { item } );
2503
2504 VECTOR2I last = m_editedPoint->GetPosition();
2505 VECTOR2I delta = pos - last;
2506 VECTOR2I deltaGrid = gridPt - grid.BestSnapAnchor( last, {},
2507 grid.GetItemGrid( item ),
2508 { item } );
2509
2510 if( abs( delta.x ) > grid.GetGrid().x / 2 )
2511 pos.x = last.x + deltaGrid.x;
2512 else
2513 pos.x = last.x;
2514
2515 if( abs( delta.y ) > grid.GetGrid().y / 2 )
2516 pos.y = last.y + deltaGrid.y;
2517 else
2518 pos.y = last.y;
2519 }
2520 }
2521 }
2522
2523 if( m_angleSnapActive )
2524 {
2525 m_stickyDisplacement = evt->Position() - m_angleSnapPos;
2526 int stickyLimit = KiROUND( getView()->ToWorld( 5 ) );
2527
2528 if( m_stickyDisplacement.EuclideanNorm() > stickyLimit || evt->Modifier( MD_SHIFT ) )
2529 {
2530 m_angleSnapActive = false;
2531 }
2532 else
2533 {
2534 pos = m_angleSnapPos;
2535 }
2536 }
2537
2538 bool isFreePolygon =
2539 item->Type() == PCB_ZONE_T
2540 || ( item->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY );
2541
2542 if( isFreePolygon && !m_angleSnapActive && m_editPoints->PointsSize() > 2 && !evt->Modifier( MD_SHIFT ) )
2543 {
2544 int idx = getEditedPointIndex();
2545
2546 if( idx != wxNOT_FOUND )
2547 {
2548 int prevIdx = ( idx + m_editPoints->PointsSize() - 1 ) % m_editPoints->PointsSize();
2549 int nextIdx = ( idx + 1 ) % m_editPoints->PointsSize();
2550 VECTOR2I prev = m_editPoints->Point( prevIdx ).GetPosition();
2551 VECTOR2I next = m_editPoints->Point( nextIdx ).GetPosition();
2552 SEG segA( pos, prev );
2553 SEG segB( pos, next );
2554 double ang = segA.Angle( segB ).AsDegrees();
2555 double snapAng = 45.0 * std::round( ang / 45.0 );
2556
2557 if( std::abs( ang - snapAng ) < 2.0 )
2558 {
2559 VECTOR2I snapped = snapCorner( prev, next, pos, snapAng );
2560
2561 if( m_editedPoint->GetGridConstraint() == SNAP_TO_GRID && grid.GetSnap() )
2562 {
2563 VECTOR2I gridded = grid.BestSnapAnchor( snapped, {}, grid.GetItemGrid( item ), { item } );
2564 double griddedAng = SEG( gridded, prev ).Angle( SEG( gridded, next ) ).AsDegrees();
2565
2566 snapped = std::abs( griddedAng - snapAng ) < 2.0 ? gridded : pos;
2567 }
2568
2569 if( snapped != pos )
2570 {
2571 m_angleSnapPos = snapped;
2572 m_angleSnapActive = true;
2573 m_stickyDisplacement = evt->Position() - m_angleSnapPos;
2574 pos = m_angleSnapPos;
2575 }
2576 }
2577 }
2578 }
2579
2580 bool constraintSnapped = false;
2581
2582 // Apply 45 degree or other constraints
2584 {
2585 m_editedPoint->SetPosition( pos );
2586 m_altConstraint->Apply( grid );
2587 constraintSnapped = true;
2588
2589 // For constrained lines (like zone edges), try to snap to nearby anchors
2590 // that lie on the constraint line
2591 if( grid.GetSnap() && !snapLayers.empty() )
2592 {
2593 VECTOR2I constrainedPos = m_editedPoint->GetPosition();
2594 VECTOR2I snapPos = grid.BestSnapAnchor( constrainedPos, snapLayers,
2595 grid.GetItemGrid( item ), { item } );
2596
2597 if( snapPos != constrainedPos )
2598 {
2599 m_editedPoint->SetPosition( snapPos );
2600 m_altConstraint->Apply( grid );
2601 VECTOR2I projectedPos = m_editedPoint->GetPosition();
2602 const int snapTolerance = KiROUND( getView()->ToWorld( 5 ) );
2603
2604 if( ( projectedPos - snapPos ).EuclideanNorm() > snapTolerance )
2605 m_editedPoint->SetPosition( constrainedPos );
2606 }
2607 }
2608 }
2609 else if( !m_angleSnapActive && m_editedPoint->IsConstrained() )
2610 {
2611 m_editedPoint->SetPosition( pos );
2612 m_editedPoint->ApplyConstraint( grid );
2613 constraintSnapped = true;
2614
2615 // For constrained lines (like zone edges), try to snap to nearby anchors
2616 // that lie on the constraint line. First get the constrained position, then
2617 // look for snap anchors and verify they're on the constraint line.
2618 if( grid.GetSnap() && !snapLayers.empty() )
2619 {
2620 VECTOR2I constrainedPos = m_editedPoint->GetPosition();
2621 VECTOR2I snapPos = grid.BestSnapAnchor( constrainedPos, snapLayers,
2622 grid.GetItemGrid( item ), { item } );
2623
2624 // If we found a snap anchor different from the constrained position,
2625 // check if setting the point there and reapplying the constraint
2626 // results in a position close to the snap point
2627 if( snapPos != constrainedPos )
2628 {
2629 m_editedPoint->SetPosition( snapPos );
2630 m_editedPoint->ApplyConstraint( grid );
2631 VECTOR2I projectedPos = m_editedPoint->GetPosition();
2632
2633 // If the projection is close to the snap anchor, use it
2634 // Otherwise revert to the original constrained position
2635 const int snapTolerance = KiROUND( getView()->ToWorld( 5 ) );
2636
2637 if( ( projectedPos - snapPos ).EuclideanNorm() > snapTolerance )
2638 m_editedPoint->SetPosition( constrainedPos );
2639 }
2640 }
2641 }
2642 else if( !m_angleSnapActive && m_editedPoint->GetGridConstraint() == SNAP_TO_GRID )
2643 {
2644 m_editedPoint->SetPosition( grid.BestSnapAnchor( pos, snapLayers, grid.GetItemGrid( item ),
2645 { item } ) );
2646 }
2647 else
2648 {
2649 m_editedPoint->SetPosition( pos );
2650 }
2651
2652 if( haveSnapLineDirections )
2653 {
2654 VECTOR2I snapOrigin = m_altConstraint ? m_altConstrainer.GetPosition() : m_original.GetPosition();
2655 grid.SetSnapLineOrigin( snapOrigin );
2656
2657 if( constraintSnapped )
2658 grid.SetSnapLineEnd( m_editedPoint->GetPosition() );
2659 else
2660 grid.SetSnapLineEnd( std::nullopt );
2661 }
2662
2663 updateItem( commit );
2664 getViewControls()->ForceCursorPosition( true, m_editedPoint->GetPosition() );
2665 updatePoints();
2666
2667 if( m_radiusHelper )
2668 {
2669 if( m_editPoints->PointsSize() > RECT_RADIUS
2670 && m_editedPoint == &m_editPoints->Point( RECT_RADIUS ) )
2671 {
2672 if( PCB_SHAPE* rect = dynamic_cast<PCB_SHAPE*>( item ) )
2673 {
2674 int radius = rect->GetCornerRadius();
2675 int offset = radius - M_SQRT1_2 * radius;
2676 VECTOR2I topLeft = rect->GetTopLeft();
2677 VECTOR2I botRight = rect->GetBotRight();
2678 VECTOR2I topRight( botRight.x, topLeft.y );
2679 VECTOR2I center( topRight.x - offset, topRight.y + offset );
2680 m_radiusHelper->Set( radius, center, VECTOR2I( 1, -1 ), editFrame->GetUserUnits() );
2681 }
2682 }
2683 else
2684 {
2685 m_radiusHelper->Hide();
2686 }
2687 }
2688
2689 getView()->Update( &m_preview );
2690 }
2691 else if( m_editedPoint && evt->Action() == TA_MOUSE_DOWN && evt->Buttons() == BUT_LEFT )
2692 {
2693 m_editedPoint->SetActive();
2694
2695 for( size_t ii = 0; ii < m_editPoints->PointsSize(); ++ii )
2696 {
2697 EDIT_POINT& point = m_editPoints->Point( ii );
2698
2699 if( &point != m_editedPoint )
2700 point.SetActive( false );
2701 }
2702
2703 getView()->Update( m_editPoints.get() );
2704
2705 if( m_angleItem )
2706 getView()->Update( m_angleItem.get() );
2707 }
2708 else if( inDrag && evt->IsMouseUp( BUT_LEFT ) )
2709 {
2710 if( m_editedPoint )
2711 {
2712 m_editedPoint->SetActive( false );
2713 getView()->Update( m_editPoints.get() );
2714
2715 if( m_angleItem )
2716 getView()->Update( m_angleItem.get() );
2717 }
2718
2719 if( m_radiusHelper )
2720 m_radiusHelper->Hide();
2721
2722 getView()->Update( &m_preview );
2723
2724 getViewControls()->SetAutoPan( false );
2725 setAltConstraint( false );
2726 updateSnapLineDirections();
2727
2728 if( m_editorBehavior )
2729 m_editorBehavior->FinalizeItem( *m_editPoints, commit );
2730
2731 if( item->Type() == PCB_GENERATOR_T )
2732 {
2733 PCB_GENERATOR* generator = static_cast<PCB_GENERATOR*>( item );
2734
2735 m_preview.FreeItems();
2736 m_radiusHelper = nullptr;
2737 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genFinishEdit, &commit, generator );
2738
2739 commit.Push( generator->GetCommitMessage() );
2740 }
2741 else if( item->Type() == PCB_TABLECELL_T )
2742 {
2743 commit.Push( _( "Resize Table Cells" ) );
2744 }
2745 else
2746 {
2747 commit.Push( _( "Move Point" ) );
2748 }
2749
2750 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2751 {
2752 shape->ClearFlags( IS_MOVING );
2753 shape->UpdateHatching();
2754 }
2755
2756 inDrag = false;
2757 frame()->UndoRedoBlock( false );
2758 updateSnapLineDirections();
2759
2760 m_toolMgr->PostAction<EDA_ITEM*>( ACTIONS::reselectItem, item ); // FIXME: Needed for generators
2761 }
2762 else if( evt->IsCancelInteractive() || evt->IsActivate() )
2763 {
2764 if( inDrag ) // Restore the last change
2765 {
2766 if( item->Type() == PCB_GENERATOR_T )
2767 {
2768 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genCancelEdit, &commit,
2769 static_cast<PCB_GENERATOR*>( item ) );
2770 }
2771
2772 commit.Revert();
2773
2774 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2775 {
2776 shape->ClearFlags( IS_MOVING );
2777 shape->UpdateHatching();
2778 }
2779
2780 inDrag = false;
2781 frame()->UndoRedoBlock( false );
2782 updateSnapLineDirections();
2783 }
2784
2785 // Only cancel point editor when activating a new tool
2786 // Otherwise, allow the points to persist when moving up the
2787 // tool stack
2788 if( evt->IsActivate() && !evt->IsMoveTool() )
2789 break;
2790 }
2791 else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
2792 {
2793 // Re-create the points for items which can have different behavior on different layers
2794 if( item->Type() == PCB_PAD_T && m_isFootprintEditor )
2795 {
2796 if( getView()->HasItem( m_editPoints.get() ) )
2797 getView()->Remove( m_editPoints.get() );
2798
2799 if( m_angleItem && getView()->HasItem( m_angleItem.get() ) )
2800 getView()->Remove( m_angleItem.get() );
2801
2802 m_editPoints = makePoints( item );
2803
2804 if( m_angleItem )
2805 {
2806 m_angleItem->SetEditPoints( m_editPoints );
2807 getView()->Add( m_angleItem.get() );
2808 }
2809
2810 getView()->Add( m_editPoints.get() );
2811 }
2812 }
2813 else if( evt->Action() == TA_UNDO_REDO_POST )
2814 {
2815 break;
2816 }
2817 else
2818 {
2819 evt->SetPassEvent();
2820 }
2821 }
2822
2823 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2824 {
2825 shape->ClearFlags( IS_MOVING );
2826 shape->UpdateHatching();
2827 }
2828
2829 m_preview.FreeItems();
2830 m_radiusHelper = nullptr;
2831
2832 if( getView()->HasItem( &m_preview ) )
2833 getView()->Remove( &m_preview );
2834
2835 if( m_editPoints )
2836 {
2837 if( getView()->HasItem( m_editPoints.get() ) )
2838 getView()->Remove( m_editPoints.get() );
2839
2840 if( m_angleItem && getView()->HasItem( m_angleItem.get() ) )
2841 getView()->Remove( m_angleItem.get() );
2842
2843 m_editPoints.reset();
2844 m_angleItem.reset();
2845 }
2846
2847 m_editedPoint = nullptr;
2848 grid.SetSnapLineDirections( {} );
2849
2850 return 0;
2851}
2852
2853
2855{
2856 if( !m_editPoints || !m_editPoints->GetParent() || !HasPoint() )
2857 return 0;
2858
2860
2861 BOARD_COMMIT commit( editFrame );
2862 commit.Stage( m_editPoints->GetParent(), CHT_MODIFY );
2863
2864 VECTOR2I pt = m_editedPoint->GetPosition();
2865 wxString title;
2866 wxString msg;
2867
2868 if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
2869 {
2870 title = _( "Move Midpoint to Location" );
2871 msg = _( "Move Midpoint" );
2872 }
2873 else
2874 {
2875 title = _( "Move Corner to Location" );
2876 msg = _( "Move Corner" );
2877 }
2878
2879 WX_PT_ENTRY_DIALOG dlg( editFrame, title, _( "X:" ), _( "Y:" ), pt, false );
2880
2881 if( dlg.ShowModal() == wxID_OK )
2882 {
2883 m_editedPoint->SetPosition( dlg.GetValue() );
2884 updateItem( commit );
2885 commit.Push( msg );
2886 }
2887
2888 return 0;
2889}
2890
2891
2893{
2894 wxCHECK( m_editPoints, /* void */ );
2895 EDA_ITEM* item = m_editPoints->GetParent();
2896
2897 if( !item )
2898 return;
2899
2900 // item is always updated
2901 std::vector<EDA_ITEM*> updatedItems = { item };
2902 aCommit.Modify( item );
2903
2904 if( m_editorBehavior )
2905 {
2906 wxCHECK( m_editedPoint, /* void */ );
2907 m_editorBehavior->UpdateItem( *m_editedPoint, *m_editPoints, aCommit, updatedItems );
2908 }
2909
2910 // Perform any post-edit actions that the item may require
2911
2912 switch( item->Type() )
2913 {
2914 case PCB_TEXTBOX_T:
2915 case PCB_SHAPE_T:
2916 {
2917 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
2918
2919 if( shape->IsProxyItem() )
2920 {
2921 for( PAD* pad : shape->GetParentFootprint()->Pads() )
2922 {
2923 if( pad->IsEntered() )
2924 view()->Update( pad );
2925 }
2926 }
2927
2928 // Nuke outline font render caches
2929 if( PCB_TEXTBOX* textBox = dynamic_cast<PCB_TEXTBOX*>( item ) )
2930 textBox->ClearRenderCache();
2931
2932 break;
2933 }
2934 case PCB_GENERATOR_T:
2935 {
2936 GENERATOR_TOOL* generatorTool = m_toolMgr->GetTool<GENERATOR_TOOL>();
2937 PCB_GENERATOR* generatorItem = static_cast<PCB_GENERATOR*>( item );
2938
2939 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genUpdateEdit, &aCommit, generatorItem );
2940
2941 // Note: POINT_EDITOR::m_preview holds only the canvas-draw status "popup"; the meanders
2942 // themselves (ROUTER_PREVIEW_ITEMs) are owned by the router.
2943
2944 m_preview.FreeItems();
2945 m_radiusHelper = nullptr;
2946
2947 for( EDA_ITEM* previewItem : generatorItem->GetPreviewItems( generatorTool, frame(), STATUS_ITEMS_ONLY ) )
2948 m_preview.Add( previewItem );
2949
2950 getView()->Update( &m_preview );
2951 break;
2952 }
2953 default:
2954 break;
2955 }
2956
2957 // Update the item and any affected items
2958 for( EDA_ITEM* updatedItem : updatedItems )
2959 getView()->Update( updatedItem );
2960
2961 frame()->SetMsgPanel( item );
2962}
2963
2964
2966{
2967 if( !m_editPoints )
2968 return;
2969
2970 EDA_ITEM* item = m_editPoints->GetParent();
2971
2972 if( !item )
2973 return;
2974
2975 if( !m_editorBehavior )
2976 return;
2977
2978 int editedIndex = -1;
2979 bool editingLine = false;
2980
2981 if( m_editedPoint )
2982 {
2983 // Check if we're editing a point (vertex)
2984 for( unsigned ii = 0; ii < m_editPoints->PointsSize(); ++ii )
2985 {
2986 if( &m_editPoints->Point( ii ) == m_editedPoint )
2987 {
2988 editedIndex = ii;
2989 break;
2990 }
2991 }
2992
2993 // If not found in points, check if we're editing a line (midpoint)
2994 if( editedIndex == -1 )
2995 {
2996 for( unsigned ii = 0; ii < m_editPoints->LinesSize(); ++ii )
2997 {
2998 if( &m_editPoints->Line( ii ) == m_editedPoint )
2999 {
3000 editedIndex = ii;
3001 editingLine = true;
3002 break;
3003 }
3004 }
3005 }
3006 }
3007
3008 if( !m_editorBehavior->UpdatePoints( *m_editPoints ) )
3009 {
3010 if( getView()->HasItem( m_editPoints.get() ) )
3011 getView()->Remove( m_editPoints.get() );
3012
3013 m_editPoints = makePoints( item );
3014 getView()->Add( m_editPoints.get() );
3015 }
3016
3017 if( editedIndex >= 0 )
3018 {
3019 if( editingLine && editedIndex < (int) m_editPoints->LinesSize() )
3020 m_editedPoint = &m_editPoints->Line( editedIndex );
3021 else if( !editingLine && editedIndex < (int) m_editPoints->PointsSize() )
3022 m_editedPoint = &m_editPoints->Point( editedIndex );
3023 else
3024 m_editedPoint = nullptr;
3025 }
3026 else
3027 {
3028 m_editedPoint = nullptr;
3029 }
3030
3031 getView()->Update( m_editPoints.get() );
3032
3033 if( m_angleItem )
3034 getView()->Update( m_angleItem.get() );
3035}
3036
3037
3039{
3041
3042 if( aPoint )
3043 {
3044 frame()->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
3045 controls->ForceCursorPosition( true, aPoint->GetPosition() );
3046 controls->ShowCursor( true );
3047 }
3048 else
3049 {
3050 if( frame()->ToolStackIsEmpty() )
3051 controls->ShowCursor( false );
3052
3053 controls->ForceCursorPosition( false );
3054 }
3055
3056 m_editedPoint = aPoint;
3057}
3058
3059
3061{
3062 EDA_ITEM* parent = m_editPoints ? m_editPoints->GetParent() : nullptr;
3063 EDIT_LINE* line = dynamic_cast<EDIT_LINE*>( m_editedPoint );
3064 bool isPoly = false;
3065
3066 if( parent )
3067 {
3068 switch( parent->Type() )
3069 {
3070 case PCB_ZONE_T:
3071 isPoly = true;
3072 break;
3073
3074 case PCB_SHAPE_T:
3075 isPoly = static_cast<PCB_SHAPE*>( parent )->GetShape() == SHAPE_T::POLY;
3076 break;
3077
3078 default:
3079 break;
3080 }
3081 }
3082
3083 if( aEnabled )
3084 {
3085 if( line && isPoly )
3086 {
3087 // For polygon lines, toggle the mode on the existing constraint rather than
3088 // creating a new one. This preserves the original reference positions.
3089 EC_CONVERGING* constraint = dynamic_cast<EC_CONVERGING*>( line->GetConstraint() );
3090
3091 if( constraint )
3093
3094 // Don't set m_altConstraint - we're modifying the line's own constraint
3095 }
3096 else
3097 {
3098 // Find a proper constraining point for angle snapping mode
3100
3101 if( Is90Limited() )
3103 else
3105 }
3106 }
3107 else
3108 {
3109 if( line && isPoly )
3110 {
3111 // Restore the line's constraint to CONVERGING mode
3112 EC_CONVERGING* constraint = dynamic_cast<EC_CONVERGING*>( line->GetConstraint() );
3113
3114 if( constraint )
3116 }
3117
3118 m_altConstraint.reset();
3119 }
3120}
3121
3122
3124{
3125 // If there's a behaviour and it provides a constrainer, use that
3126 if( m_editorBehavior )
3127 {
3128 const OPT_VECTOR2I constrainer = m_editorBehavior->Get45DegreeConstrainer( *m_editedPoint, *m_editPoints );
3129
3130 if( constrainer )
3131 return EDIT_POINT( *constrainer );
3132 }
3133
3134 // In any other case we may align item to its original position
3135 return m_original;
3136}
3137
3138
3139// Finds a corresponding vertex in a polygon set
3140static std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX> findVertex( SHAPE_POLY_SET& aPolySet, const EDIT_POINT& aPoint )
3141{
3142 for( auto it = aPolySet.IterateWithHoles(); it; ++it )
3143 {
3144 auto vertexIdx = it.GetIndex();
3145
3146 if( aPolySet.CVertex( vertexIdx ) == aPoint.GetPosition() )
3147 return std::make_pair( true, vertexIdx );
3148 }
3149
3150 return std::make_pair( false, SHAPE_POLY_SET::VERTEX_INDEX() );
3151}
3152
3153
3155{
3156 if( !m_editPoints || !m_editedPoint )
3157 return false;
3158
3159 EDA_ITEM* item = m_editPoints->GetParent();
3160 SHAPE_POLY_SET* polyset = nullptr;
3161
3162 if( !item )
3163 return false;
3164
3165 switch( item->Type() )
3166 {
3167 case PCB_ZONE_T:
3168 polyset = static_cast<ZONE*>( item )->Outline();
3169 break;
3170
3171 case PCB_SHAPE_T:
3172 if( static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY )
3173 polyset = &static_cast<PCB_SHAPE*>( item )->GetPolyShape();
3174 else
3175 return false;
3176
3177 break;
3178
3179 default:
3180 return false;
3181 }
3182
3183 std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX> vertex = findVertex( *polyset, *m_editedPoint );
3184
3185 if( !vertex.first )
3186 return false;
3187
3188 const SHAPE_POLY_SET::VERTEX_INDEX& vertexIdx = vertex.second;
3189
3190 // Check if there are enough vertices so one can be removed without
3191 // degenerating the polygon.
3192 // The first condition allows one to remove all corners from holes (when
3193 // there are only 2 vertices left, a hole is removed).
3194 if( vertexIdx.m_contour == 0
3195 && polyset->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour].PointCount() <= 3 )
3196 {
3197 return false;
3198 }
3199
3200 // Remove corner does not work with lines
3201 if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
3202 return false;
3203
3204 return m_editedPoint != nullptr;
3205}
3206
3207
3209{
3210 if( !m_editPoints )
3211 return 0;
3212
3213 EDA_ITEM* item = m_editPoints->GetParent();
3215 const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
3216
3217 // called without an active edited polygon
3218 if( !item || !CanAddCorner( *item ) )
3219 return 0;
3220
3221 PCB_SHAPE* graphicItem = dynamic_cast<PCB_SHAPE*>( item );
3222 BOARD_COMMIT commit( frame );
3223
3224 if( item->Type() == PCB_ZONE_T || ( graphicItem && graphicItem->GetShape() == SHAPE_T::POLY ) )
3225 {
3226 unsigned int nearestIdx = 0;
3227 unsigned int nextNearestIdx = 0;
3228 unsigned int nearestDist = INT_MAX;
3229 unsigned int firstPointInContour = 0;
3230 SHAPE_POLY_SET* zoneOutline;
3231
3232 if( item->Type() == PCB_ZONE_T )
3233 {
3234 ZONE* zone = static_cast<ZONE*>( item );
3235 zoneOutline = zone->Outline();
3236 zone->SetNeedRefill( true );
3237 }
3238 else
3239 {
3240 zoneOutline = &( graphicItem->GetPolyShape() );
3241 }
3242
3243 commit.Modify( item );
3244
3245 // Search the best outline segment to add a new corner
3246 // and therefore break this segment into two segments
3247
3248 // Object to iterate through the corners of the outlines (main contour and its holes)
3249 SHAPE_POLY_SET::ITERATOR iterator = zoneOutline->Iterate( 0, zoneOutline->OutlineCount()-1,
3250 /* IterateHoles */ true );
3251 int curr_idx = 0;
3252
3253 // Iterate through all the corners of the outlines and search the best segment
3254 for( ; iterator; iterator++, curr_idx++ )
3255 {
3256 int jj = curr_idx+1;
3257
3258 if( iterator.IsEndContour() )
3259 { // We reach the last point of the current contour (main or hole)
3260 jj = firstPointInContour;
3261 firstPointInContour = curr_idx+1; // Prepare next contour analysis
3262 }
3263
3264 SEG curr_segment( zoneOutline->CVertex( curr_idx ), zoneOutline->CVertex( jj ) );
3265
3266 unsigned int distance = curr_segment.Distance( cursorPos );
3267
3268 if( distance < nearestDist )
3269 {
3270 nearestDist = distance;
3271 nearestIdx = curr_idx;
3272 nextNearestIdx = jj;
3273 }
3274 }
3275
3276 // Find the point on the closest segment
3277 const VECTOR2I& sideOrigin = zoneOutline->CVertex( nearestIdx );
3278 const VECTOR2I& sideEnd = zoneOutline->CVertex( nextNearestIdx );
3279 SEG nearestSide( sideOrigin, sideEnd );
3280 VECTOR2I nearestPoint = nearestSide.NearestPoint( cursorPos );
3281
3282 // Do not add points that have the same coordinates as ones that already belong to polygon
3283 // instead, add a point in the middle of the side
3284 if( nearestPoint == sideOrigin || nearestPoint == sideEnd )
3285 nearestPoint = ( sideOrigin + sideEnd ) / 2;
3286
3287 zoneOutline->InsertVertex( nextNearestIdx, nearestPoint );
3288
3289 if( item->Type() == PCB_ZONE_T )
3290 static_cast<ZONE*>( item )->HatchBorder();
3291
3292 commit.Push( _( "Add Zone Corner" ) );
3293 }
3294 else if( graphicItem )
3295 {
3296 switch( graphicItem->GetShape() )
3297 {
3298 case SHAPE_T::SEGMENT:
3299 {
3300 commit.Modify( graphicItem );
3301
3302 SEG seg( graphicItem->GetStart(), graphicItem->GetEnd() );
3303 VECTOR2I nearestPoint = seg.NearestPoint( cursorPos );
3304
3305 // Move the end of the line to the break point..
3306 graphicItem->SetEnd( nearestPoint );
3307
3308 // and add another one starting from the break point
3309 PCB_SHAPE* newSegment = static_cast<PCB_SHAPE*>( graphicItem->Duplicate( true, &commit ) );
3310 newSegment->ClearSelected();
3311 newSegment->SetStart( nearestPoint );
3312 newSegment->SetEnd( VECTOR2I( seg.B.x, seg.B.y ) );
3313
3314 commit.Add( newSegment );
3315 commit.Push( _( "Split Segment" ) );
3316 break;
3317 }
3318 case SHAPE_T::ARC:
3319 {
3320 commit.Modify( graphicItem );
3321
3322 const SHAPE_ARC arc( graphicItem->GetStart(), graphicItem->GetArcMid(), graphicItem->GetEnd(), 0 );
3323 const VECTOR2I nearestPoint = arc.NearestPoint( cursorPos );
3324
3325 // Move the end of the arc to the break point..
3326 graphicItem->SetEnd( nearestPoint );
3327
3328 // and add another one starting from the break point
3329 PCB_SHAPE* newArc = static_cast<PCB_SHAPE*>( graphicItem->Duplicate( true, &commit ) );
3330
3331 newArc->ClearSelected();
3332 newArc->SetEnd( arc.GetP1() );
3333 newArc->SetStart( nearestPoint );
3334
3335 commit.Add( newArc );
3336 commit.Push( _( "Split Arc" ) );
3337 break;
3338 }
3339 default:
3340 // No split implemented for other shapes
3341 break;
3342 }
3343 }
3344
3345 updatePoints();
3346 return 0;
3347}
3348
3349
3351{
3352 if( !m_editPoints || !m_editedPoint )
3353 return 0;
3354
3355 EDA_ITEM* item = m_editPoints->GetParent();
3356
3357 if( !item )
3358 return 0;
3359
3360 SHAPE_POLY_SET* polygon = nullptr;
3361
3362 if( item->Type() == PCB_ZONE_T )
3363 {
3364 ZONE* zone = static_cast<ZONE*>( item );
3365 polygon = zone->Outline();
3366 zone->SetNeedRefill( true );
3367 }
3368 else if( item->Type() == PCB_SHAPE_T )
3369 {
3370 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3371
3372 if( shape->GetShape() == SHAPE_T::POLY )
3373 polygon = &shape->GetPolyShape();
3374 }
3375
3376 if( !polygon )
3377 return 0;
3378
3380 BOARD_COMMIT commit( frame );
3381 auto vertex = findVertex( *polygon, *m_editedPoint );
3382
3383 if( vertex.first )
3384 {
3385 const auto& vertexIdx = vertex.second;
3386 auto& outline = polygon->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour];
3387
3388 if( outline.PointCount() > 3 )
3389 {
3390 // the usual case: remove just the corner when there are >3 vertices
3391 commit.Modify( item );
3392 polygon->RemoveVertex( vertexIdx );
3393 }
3394 else
3395 {
3396 // either remove a hole or the polygon when there are <= 3 corners
3397 if( vertexIdx.m_contour > 0 )
3398 {
3399 // remove hole
3400 commit.Modify( item );
3401 polygon->RemoveContour( vertexIdx.m_contour );
3402 }
3403 else
3404 {
3405 m_toolMgr->RunAction( ACTIONS::selectionClear );
3406 commit.Remove( item );
3407 }
3408 }
3409
3410 setEditedPoint( nullptr );
3411
3412 if( item->Type() == PCB_ZONE_T )
3413 commit.Push( _( "Remove Zone Corner" ) );
3414 else
3415 commit.Push( _( "Remove Polygon Corner" ) );
3416
3417 if( item->Type() == PCB_ZONE_T )
3418 static_cast<ZONE*>( item )->HatchBorder();
3419
3420 updatePoints();
3421 }
3422
3423 return 0;
3424}
3425
3426
3428{
3429 if( !m_editPoints || !m_editedPoint )
3430 return 0;
3431
3432 EDA_ITEM* item = m_editPoints->GetParent();
3433
3434 if( !item )
3435 return 0;
3436
3437 SHAPE_POLY_SET* polygon = nullptr;
3438
3439 if( item->Type() == PCB_ZONE_T )
3440 {
3441 ZONE* zone = static_cast<ZONE*>( item );
3442 polygon = zone->Outline();
3443 zone->SetNeedRefill( true );
3444 }
3445 else if( item->Type() == PCB_SHAPE_T )
3446 {
3447 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3448
3449 if( shape->GetShape() == SHAPE_T::POLY )
3450 polygon = &shape->GetPolyShape();
3451 }
3452
3453 if( !polygon )
3454 return 0;
3455
3456 // Search the best outline corner to break
3457
3459 BOARD_COMMIT commit( frame );
3460 const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
3461
3462 unsigned int nearestIdx = 0;
3463 unsigned int nearestDist = INT_MAX;
3464
3465 int curr_idx = 0;
3466 // Object to iterate through the corners of the outlines (main contour and its holes)
3467 SHAPE_POLY_SET::ITERATOR iterator = polygon->Iterate( 0, polygon->OutlineCount() - 1,
3468 /* IterateHoles */ true );
3469
3470 // Iterate through all the corners of the outlines and search the best segment
3471 for( ; iterator; iterator++, curr_idx++ )
3472 {
3473 unsigned int distance = polygon->CVertex( curr_idx ).Distance( cursorPos );
3474
3475 if( distance < nearestDist )
3476 {
3477 nearestDist = distance;
3478 nearestIdx = curr_idx;
3479 }
3480 }
3481
3482 int prevIdx, nextIdx;
3483 if( polygon->GetNeighbourIndexes( nearestIdx, &prevIdx, &nextIdx ) )
3484 {
3485 const SEG segA{ polygon->CVertex( prevIdx ), polygon->CVertex( nearestIdx ) };
3486 const SEG segB{ polygon->CVertex( nextIdx ), polygon->CVertex( nearestIdx ) };
3487
3488 // A plausible setback that won't consume a whole edge
3489 int setback = pcbIUScale.mmToIU( 5 );
3490 setback = std::min( setback, (int) ( segA.Length() * 0.25 ) );
3491 setback = std::min( setback, (int) ( segB.Length() * 0.25 ) );
3492
3493 CHAMFER_PARAMS chamferParams{ setback, setback };
3494
3495 std::optional<CHAMFER_RESULT> chamferResult = ComputeChamferPoints( segA, segB, chamferParams );
3496
3497 if( chamferResult && chamferResult->m_updated_seg_a && chamferResult->m_updated_seg_b )
3498 {
3499 commit.Modify( item );
3500 polygon->RemoveVertex( nearestIdx );
3501
3502 // The two end points of the chamfer are the new corners
3503 polygon->InsertVertex( nearestIdx, chamferResult->m_updated_seg_b->B );
3504 polygon->InsertVertex( nearestIdx, chamferResult->m_updated_seg_a->B );
3505 }
3506 }
3507
3508 setEditedPoint( nullptr );
3509
3510 if( item->Type() == PCB_ZONE_T )
3511 commit.Push( _( "Break Zone Corner" ) );
3512 else
3513 commit.Push( _( "Break Polygon Corner" ) );
3514
3515 if( item->Type() == PCB_ZONE_T )
3516 static_cast<ZONE*>( item )->HatchBorder();
3517
3518 updatePoints();
3519
3520 return 0;
3521}
3522
3523
3525{
3526 updatePoints();
3527 return 0;
3528}
3529
3530
3532{
3534
3535 if( aEvent.Matches( ACTIONS::cycleArcEditMode.MakeEvent() ) )
3536 {
3537 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
3539 else
3541
3543 }
3544 else
3545 {
3547 }
3548
3549 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
3551 else
3553
3554 return 0;
3555}
3556
3557
3559{
3577}
BOX2I getBoundingBox(BOARD_ITEM *aItem)
ARC_EDIT_MODE
Settings for arc editing.
@ KEEP_CENTER_ADJUST_ANGLE_RADIUS
When editing endpoints, the angle and radius are adjusted.
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
static TOOL_ACTION cycleArcEditMode
Definition actions.h:272
static TOOL_ACTION pointEditorArcKeepCenter
Definition actions.h:273
static TOOL_ACTION pointEditorArcKeepRadius
Definition actions.h:275
static TOOL_ACTION reselectItem
Definition actions.h:229
static TOOL_ACTION activatePointEditor
Definition actions.h:271
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:224
static TOOL_ACTION pointEditorArcKeepEndpoint
Definition actions.h:274
void updateOrthogonalDimension(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints)
Update orthogonal dimension points.
ALIGNED_DIMENSION_POINT_EDIT_BEHAVIOR(PCB_DIM_ALIGNED &aDimension)
void updateAlignedDimension(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints)
Update non-orthogonal dimension points.
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
OPT_VECTOR2I Get45DegreeConstrainer(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints) const override
Get the 45-degree constrainer for the item, when the given point is moved.
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
BARCODE_POINT_EDIT_BEHAVIOR(PCB_BARCODE &aBarcode)
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
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:288
int GetMaxError() const
constexpr void SetMaximum()
Definition box2.h:80
constexpr const Vec GetEnd() const
Definition box2.h:212
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr Vec Centre() const
Definition box2.h:97
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr size_type GetHeight() const
Definition box2.h:215
constexpr coord_type GetLeft() const
Definition box2.h:228
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:168
constexpr const Vec & GetOrigin() const
Definition box2.h:210
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr coord_type GetTop() const
Definition box2.h:229
constexpr coord_type GetBottom() const
Definition box2.h:222
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition commit.h:72
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:90
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:106
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:78
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.
EDIT_CONSTRAINT that imposes a constraint that two points have to be located at angle of 45 degree mu...
EDIT_CONSTRAINT that imposes a constraint that a point has to be located at angle of 90 degree multip...
EDIT_CONSTRAINT for polygon line dragging.
const VECTOR2I & GetPerpVector() const
Perpendicular direction of motion for the dragged line, captured at drag start.
POLYGON_LINE_MODE GetMode() const
Get the current constraint mode.
const VECTOR2I & GetOriginalCenter() const
Original center of the dragged line, captured at drag start.
void SetMode(POLYGON_LINE_MODE aMode)
Set the constraint mode (allows switching between converging and fixed-length)
EDIT_CONSTRAINT that imposes a constraint that two points have to have the same Y coordinate.
EDIT_CONSTRAINT that imposes a constraint that a point has to lie on a line (determined by 2 points).
EDIT_CONSTRAINT for a EDIT_LINE, that constrains the line to move perpendicular to the line itself.
EDIT_CONSTRAINT that imposes a constraint that two points have to have the same X coordinate.
double AsDegrees() const
Definition eda_angle.h:116
bool IsType(FRAME_T aType) const
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:100
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:112
void ClearSelected()
Definition eda_item.h:151
virtual EDA_ITEM * Clone() const
Create a duplicate of this item with linked list members set to NULL.
Definition eda_item.cpp:147
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:93
EDA_ITEM(EDA_ITEM *parent, KICAD_T idType, bool isSCH_ITEM=false, bool isBOARD_ITEM=false)
Definition eda_item.cpp:41
virtual VECTOR2I GetTopLeft() const
Definition eda_shape.h:267
void SetCornerRadius(int aRadius)
SHAPE_POLY_SET & GetPolyShape()
SHAPE_T GetShape() const
Definition eda_shape.h:189
virtual VECTOR2I GetBotRight() const
Definition eda_shape.h:268
virtual void SetBottom(int val)
Definition eda_shape.h:273
virtual void SetTop(int val)
Definition eda_shape.h:270
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:236
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:198
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:194
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:240
virtual void SetLeft(int val)
Definition eda_shape.h:271
virtual void SetRight(int val)
Definition eda_shape.h:272
int GetCornerRadius() const
VECTOR2I GetArcMid() const
Describe constraints between two edit handles.
Represent a line connecting two EDIT_POINTs.
void SetConstraint(EDIT_CONSTRAINT< EDIT_LINE > *aConstraint)
Set a constraint for and EDIT_POINT.
EDIT_CONSTRAINT< EDIT_LINE > * GetConstraint() const
Return the constraint imposed on an 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:48
int GetY() const
Return Y coordinate of an EDIT_POINT.
Definition edit_points.h:96
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:72
void SetSnapConstraint(SNAP_CONSTRAINT_TYPE aConstraint)
void SetConstraint(EDIT_CONSTRAINT< EDIT_POINT > *aConstraint)
Set a constraint for an EDIT_POINT.
int GetX() const
Return X coordinate of an EDIT_POINT.
Definition edit_points.h:88
void SetActive(bool aActive=true)
void SetDrawCircle(bool aDrawCircle=true)
static const TOOL_EVENT InhibitSelectionEditing
Definition actions.h:358
static const TOOL_EVENT SelectedEvent
Definition actions.h:345
static const TOOL_EVENT SelectedItemsModified
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition actions.h:352
static const TOOL_EVENT UninhibitSelectionEditing
Used to inform tool that it should display the disambiguation menu.
Definition actions.h:359
static const TOOL_EVENT PointSelectedEvent
Definition actions.h:344
static const TOOL_EVENT SelectedItemsMoved
Used to inform tools that the selection should temporarily be non-editable.
Definition actions.h:355
static const TOOL_EVENT UnselectedEvent
Definition actions.h:346
std::deque< PAD * > & Pads()
Definition footprint.h:377
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
GENERATOR_POINT_EDIT_BEHAVIOR(PCB_GENERATOR &aGenerator)
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
Handle actions specific to filling copper zones.
virtual void Update(const VIEW_ITEM *aItem, int aUpdateFlags) const override
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
Definition pcb_view.cpp:91
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:67
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1)
Add a VIEW_ITEM to the view.
Definition view.cpp:304
virtual void Remove(VIEW_ITEM *aItem)
Remove a VIEW_ITEM from the view.
Definition view.cpp:408
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:1828
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllLayersMask()
Definition lset.cpp:641
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
PAD_POINT_EDIT_BEHAVIOR(PAD &aPad, PCB_LAYER_ID aLayer)
Definition pad.h:65
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:53
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.
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
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.
std::shared_ptr< EDIT_CONSTRAINT< EDIT_POINT > > m_altConstraint
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:81
bool IsProxyItem() const override
Definition pcb_shape.h:122
void Move(const VECTOR2I &aMoveVector) override
Move this object.
PCB_TABLECELL_POINT_EDIT_BEHAVIOR(PCB_TABLECELL &aCell)
void UpdateItem(const EDIT_POINT &aEditedPoint, EDIT_POINTS &aPoints, COMMIT &aCommit, std::vector< EDA_ITEM * > &aUpdatedItems) override
Update the item with the new positions of the edit points.
T * frame() const
KIGFX::PCB_VIEW * view() const
virtual bool Is45Limited() const
Should the tool use its 45° mode option?
KIGFX::VIEW_CONTROLS * controls() const
PCB_TOOL_BASE(TOOL_ID aId, const std::string &aName)
Constructor.
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.
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:42
VECTOR2I B
Definition seg.h:50
const VECTOR2I NearestPoint(const VECTOR2I &aP) const
Compute a point on the segment (this) that is closest to point aP.
Definition seg.cpp:633
int Distance(const SEG &aSeg) const
Compute minimum Euclidean distance to segment aSeg.
Definition seg.cpp:702
EDA_ANGLE Angle(const SEG &aOther) const
Determine the smallest angle between two segments.
Definition seg.cpp:111
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:119
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.
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.
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:186
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition tool_base.cpp:44
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:224
KIGFX::VIEW * getView() const
Returns the instance of #VIEW object used in the application.
Definition tool_base.cpp:38
RESET_REASON
Determine the reason of reset for a tool.
Definition tool_base.h:78
Generic, UI-independent tool event.
Definition tool_event.h:171
bool Matches(const TOOL_EVENT &aEvent) const
Test whether two events match in terms of category & action or command.
Definition tool_event.h:392
const VECTOR2D Position() const
Return mouse cursor position in world coordinates.
Definition tool_event.h:293
bool IsDrag(int aButtonMask=BUT_ANY) const
Definition tool_event.h:315
const VECTOR2D DragOrigin() const
Return the point where dragging has started.
Definition tool_event.h:299
T Parameter() const
Return a parameter assigned to the event.
Definition tool_event.h:473
bool IsMotion() const
Definition tool_event.h:330
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:538
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:553
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:283
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:546
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:385
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:74
void SetNeedRefill(bool aNeedRefill)
Definition zone.h:314
SHAPE_POLY_SET * Outline()
Definition zone.h:422
@ CHT_MODIFY
Definition commit.h:44
This file is part of the common library.
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:46
static constexpr double MIN_SCALE
const int minSize
Push and Shove router track width and via size dialog.
#define _(s)
@ RECURSE
Definition eda_item.h:53
#define IS_MOVING
Item being moved.
SHAPE_T
Definition eda_shape.h:48
@ ELLIPSE
Definition eda_shape.h:56
@ SEGMENT
Definition eda_shape.h:50
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:51
@ ELLIPSE_ARC
Definition eda_shape.h:57
@ NO_FILL
Definition eda_shape.h:64
EDA_UNITS
Definition eda_units.h:48
@ ALL_LAYERS
@ OBJECT_LAYERS
@ IGNORE_SNAPS
POLYGON_LINE_MODE
Mode for polygon line edge constraints.
@ CONVERGING
Adjacent lines converge/diverge, dragged line length changes.
@ FIXED_LENGTH
Dragged line maintains its length, adjacent lines adjust angles.
@ SNAP_BY_GRID
@ SNAP_TO_GRID
@ FRAME_PCB_EDITOR
Definition frame_type.h:42
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:782
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:679
@ LAYER_GP_OVERLAY
General purpose overlay.
Definition layer_ids.h:279
@ LAYER_SELECT_OVERLAY
Selected items overlay.
Definition layer_ids.h:280
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ B_Cu
Definition layer_ids.h:65
@ F_Cu
Definition layer_ids.h:64
constexpr int Mils2IU(const EDA_IU_SCALE &aIuScale, int mils)
Definition eda_units.h:175
bool PointProjectsOntoSegment(const VECTOR2I &aPoint, const SEG &aSeg)
Determine if a point projects onto a segment.
double GetLengthRatioFromStart(const VECTOR2I &aPoint, const SEG &aSeg)
Get the ratio of the vector to a point from the segment's start, compared to the segment's length.
const VECTOR2I & GetNearestEndpoint(const SEG &aSeg, const VECTOR2I &aPoint)
Get the nearest end of a segment to a point.
bool PointIsInDirection(const VECTOR2< T > &aPoint, const VECTOR2< T > &aDirection, const VECTOR2< T > &aFrom)
void DrawTextNextToCursor(KIGFX::VIEW *aView, const VECTOR2D &aCursorPos, const VECTOR2D &aTextQuadrant, const wxArrayString &aStrings, bool aDrawingDropShadows)
Draw strings next to the cursor.
wxString DimensionLabel(const wxString &prefix, double aVal, const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, bool aIncludeUnits=true)
Get a formatted string showing a dimension to a sane precision with an optional prefix and unit suffi...
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ TRAPEZOID
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:54
BARCODE class definition.
@ 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::pair< bool, SHAPE_POLY_SET::VERTEX_INDEX > findVertex(SHAPE_POLY_SET &aPolySet, const EDIT_POINT &aPoint)
static std::vector< VECTOR2I > getConstraintDirections(EDIT_CONSTRAINT< EDIT_POINT > *aConstraint)
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:124
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
SCH_CONDITIONS S_C
@ RECT_CENTER
@ RECT_RADIUS
@ RECT_RIGHT
@ RECT_LEFT
@ RECT_BOT
@ RECT_TOP
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:39
const int scale
std::vector< FAB_LAYER_COLOR > dummy
Parameters that define a simple chamfer operation.
Structure to hold the necessary information in order to index a vertex on a SHAPE_POLY_SET object: th...
KIBIS top(path, &reporter)
std::vector< std::vector< std::string > > table
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:70
@ TA_UNDO_REDO_POST
This event is sent after undo/redo command is performed.
Definition tool_event.h:109
@ MD_CTRL
Definition tool_event.h:144
@ MD_SHIFT
Definition tool_event.h:143
@ BUT_LEFT
Definition tool_event.h:132
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:77
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:229
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:75
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:85
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:103
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:100
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:88
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:101
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:108
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:90
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:105
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:86
@ NOT_USED
the 3d code uses this value
Definition typeinfo.h:76
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:98
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:92
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:99
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:84
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:102
constexpr int sign(T val)
Definition util.h:145
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
VECTOR2< double > VECTOR2D
Definition vector2d.h:686
Supplemental functions for working with vectors and simple objects that interact with vectors.