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 grid.SetSnapLineDirections( directions );
2309 grid.SetSnapLineOrigin( m_original.GetPosition() );
2310 grid.SetSnapLineEnd( std::nullopt );
2311 haveSnapLineDirections = true;
2312 }
2313 };
2314
2315 BOARD_COMMIT commit( editFrame );
2316
2317 // Main loop: keep receiving events
2318 while( TOOL_EVENT* evt = Wait() )
2319 {
2320 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
2321 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
2322
2323 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
2325 else
2327
2328 if( !m_editPoints || evt->IsSelectionEvent() || evt->Matches( EVENTS::InhibitSelectionEditing ) )
2329 {
2330 break;
2331 }
2332
2333 EDIT_POINT* prevHover = m_hoveredPoint;
2334
2335 if( !inDrag )
2336 updateEditedPoint( *evt );
2337
2338 if( prevHover != m_hoveredPoint )
2339 {
2340 getView()->Update( m_editPoints.get() );
2341
2342 if( m_angleItem )
2343 getView()->Update( m_angleItem.get() );
2344 }
2345
2346 if( evt->IsDrag( BUT_LEFT ) && m_editedPoint )
2347 {
2348 if( !inDrag )
2349 {
2350 frame()->UndoRedoBlock( true );
2351
2352 if( item->Type() == PCB_GENERATOR_T )
2353 {
2354 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genStartEdit, &commit,
2355 static_cast<PCB_GENERATOR*>( item ) );
2356 }
2357
2359 m_original = *m_editedPoint; // Save the original position
2360 getViewControls()->SetAutoPan( true );
2361 inDrag = true;
2362
2363 if( m_editedPoint->GetGridConstraint() != SNAP_BY_GRID )
2364 grid.SetAuxAxes( true, m_original.GetPosition() );
2365
2366 m_editedPoint->SetActive();
2367
2368 for( size_t ii = 0; ii < m_editPoints->PointsSize(); ++ii )
2369 {
2370 EDIT_POINT& point = m_editPoints->Point( ii );
2371
2372 if( &point != m_editedPoint )
2373 point.SetActive( false );
2374 }
2375
2376 // When we start dragging, create a clone of the item to use as the original
2377 // reference geometry (e.g. for intersections and extensions)
2378 BOARD_ITEM* clone = static_cast<BOARD_ITEM*>( item->Clone() );
2379 clone->SetParent( nullptr );
2380
2381 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2382 {
2383 shape->SetFlags( IS_MOVING );
2384 shape->UpdateHatching();
2385
2386 static_cast<PCB_SHAPE*>( clone )->SetFillMode( FILL_T::NO_FILL );
2387 }
2388
2389 clones.emplace_back( clone );
2390 grid.AddConstructionItems( { clone }, false, true );
2391
2392 updateSnapLineDirections();
2393 }
2394
2395 bool need_constraint = Is45Limited() || Is90Limited();
2396
2397 if( isConstrained != need_constraint )
2398 {
2399 setAltConstraint( need_constraint );
2400 isConstrained = need_constraint;
2401 updateSnapLineDirections();
2402 }
2403
2404 // For polygon lines, Ctrl temporarily toggles between CONVERGING and FIXED_LENGTH modes
2405 EDIT_LINE* line = dynamic_cast<EDIT_LINE*>( m_editedPoint );
2406 bool ctrlHeld = evt->Modifier( MD_CTRL );
2407
2408 if( line )
2409 {
2410 bool isPoly = false;
2411
2412 switch( item->Type() )
2413 {
2414 case PCB_ZONE_T:
2415 isPoly = true;
2416 break;
2417
2418 case PCB_SHAPE_T:
2419 isPoly = static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY;
2420 break;
2421
2422 default:
2423 break;
2424 }
2425
2426 if( isPoly )
2427 {
2428 EC_CONVERGING* constraint =
2429 dynamic_cast<EC_CONVERGING*>( line->GetConstraint() );
2430
2431 if( constraint )
2432 {
2435
2436 if( constraint->GetMode() != targetMode )
2437 constraint->SetMode( targetMode );
2438 }
2439 }
2440 }
2441
2442 // Keep point inside of limits with some padding
2443 VECTOR2I pos = GetClampedCoords<double, int>( evt->Position(), COORDS_PADDING );
2444 LSET snapLayers;
2445
2446 switch( m_editedPoint->GetSnapConstraint() )
2447 {
2448 case IGNORE_SNAPS: break;
2449 case OBJECT_LAYERS: snapLayers = item->GetLayerSet(); break;
2450 case ALL_LAYERS: snapLayers = LSET::AllLayersMask(); break;
2451 }
2452
2453 if( m_editedPoint->GetGridConstraint() == SNAP_BY_GRID )
2454 {
2455 if( grid.GetUseGrid() )
2456 {
2457 EC_CONVERGING* convergingConstraint =
2458 line ? dynamic_cast<EC_CONVERGING*>( line->GetConstraint() ) : nullptr;
2459
2460 bool snappedAlongPerp = false;
2461
2462 if( convergingConstraint )
2463 {
2464 // For a polygon edge, the line moves only perpendicular to itself.
2465 // Snapping pos.x and pos.y independently to the axis-aligned grid
2466 // produces inconsistent perpendicular displacements when the edge is
2467 // tilted (different magnitudes depending on which axis crossed the
2468 // half-grid threshold first), causing the rendered edge to flicker
2469 // between two positions. Quantize the perpendicular displacement
2470 // directly so each grid step produces one stable line position.
2471 const VECTOR2I& origCenter = convergingConstraint->GetOriginalCenter();
2472 const VECTOR2I& perpVec = convergingConstraint->GetPerpVector();
2473 double perpLen = VECTOR2D( perpVec ).EuclideanNorm();
2474
2475 if( perpLen > 0 )
2476 {
2477 VECTOR2D perpUnit = VECTOR2D( perpVec ) / perpLen;
2478 VECTOR2D gridSize = grid.GetGridSize( grid.GetItemGrid( item ) );
2479
2480 // Effective grid spacing along the perpendicular direction. For an
2481 // axis-aligned edge this reduces to the grid pitch on that axis.
2482 double step = std::hypot( gridSize.x * perpUnit.x,
2483 gridSize.y * perpUnit.y );
2484
2485 if( step > 0 )
2486 {
2487 double offset = VECTOR2D( pos - origCenter ).Dot( perpUnit );
2488 double snapped = std::round( offset / step ) * step;
2489 VECTOR2D snappedPt = VECTOR2D( origCenter ) + perpUnit * snapped;
2490 pos = VECTOR2I( KiROUND( snappedPt.x ), KiROUND( snappedPt.y ) );
2491 snappedAlongPerp = true;
2492 }
2493 }
2494 }
2495
2496 if( !snappedAlongPerp )
2497 {
2498 VECTOR2I gridPt = grid.BestSnapAnchor( pos, {}, grid.GetItemGrid( item ),
2499 { item } );
2500
2501 VECTOR2I last = m_editedPoint->GetPosition();
2502 VECTOR2I delta = pos - last;
2503 VECTOR2I deltaGrid = gridPt - grid.BestSnapAnchor( last, {},
2504 grid.GetItemGrid( item ),
2505 { item } );
2506
2507 if( abs( delta.x ) > grid.GetGrid().x / 2 )
2508 pos.x = last.x + deltaGrid.x;
2509 else
2510 pos.x = last.x;
2511
2512 if( abs( delta.y ) > grid.GetGrid().y / 2 )
2513 pos.y = last.y + deltaGrid.y;
2514 else
2515 pos.y = last.y;
2516 }
2517 }
2518 }
2519
2520 if( m_angleSnapActive )
2521 {
2522 m_stickyDisplacement = evt->Position() - m_angleSnapPos;
2523 int stickyLimit = KiROUND( getView()->ToWorld( 5 ) );
2524
2525 if( m_stickyDisplacement.EuclideanNorm() > stickyLimit || evt->Modifier( MD_SHIFT ) )
2526 {
2527 m_angleSnapActive = false;
2528 }
2529 else
2530 {
2531 pos = m_angleSnapPos;
2532 }
2533 }
2534
2535 if( !m_angleSnapActive && m_editPoints->PointsSize() > 2 && !evt->Modifier( MD_SHIFT ) )
2536 {
2537 int idx = getEditedPointIndex();
2538
2539 if( idx != wxNOT_FOUND )
2540 {
2541 int prevIdx = ( idx + m_editPoints->PointsSize() - 1 ) % m_editPoints->PointsSize();
2542 int nextIdx = ( idx + 1 ) % m_editPoints->PointsSize();
2543 VECTOR2I prev = m_editPoints->Point( prevIdx ).GetPosition();
2544 VECTOR2I next = m_editPoints->Point( nextIdx ).GetPosition();
2545 SEG segA( pos, prev );
2546 SEG segB( pos, next );
2547 double ang = segA.Angle( segB ).AsDegrees();
2548 double snapAng = 45.0 * std::round( ang / 45.0 );
2549
2550 if( std::abs( ang - snapAng ) < 2.0 )
2551 {
2552 m_angleSnapPos = snapCorner( prev, next, pos, snapAng );
2553 m_angleSnapActive = true;
2554 m_stickyDisplacement = evt->Position() - m_angleSnapPos;
2555 pos = m_angleSnapPos;
2556 }
2557 }
2558 }
2559
2560 bool constraintSnapped = false;
2561
2562 // Apply 45 degree or other constraints
2564 {
2565 m_editedPoint->SetPosition( pos );
2566 m_altConstraint->Apply( grid );
2567 constraintSnapped = true;
2568
2569 // For constrained lines (like zone edges), try to snap to nearby anchors
2570 // that lie on the constraint line
2571 if( grid.GetSnap() && !snapLayers.empty() )
2572 {
2573 VECTOR2I constrainedPos = m_editedPoint->GetPosition();
2574 VECTOR2I snapPos = grid.BestSnapAnchor( constrainedPos, snapLayers,
2575 grid.GetItemGrid( item ), { item } );
2576
2577 if( snapPos != constrainedPos )
2578 {
2579 m_editedPoint->SetPosition( snapPos );
2580 m_altConstraint->Apply( grid );
2581 VECTOR2I projectedPos = m_editedPoint->GetPosition();
2582 const int snapTolerance = KiROUND( getView()->ToWorld( 5 ) );
2583
2584 if( ( projectedPos - snapPos ).EuclideanNorm() > snapTolerance )
2585 m_editedPoint->SetPosition( constrainedPos );
2586 }
2587 }
2588 }
2589 else if( !m_angleSnapActive && m_editedPoint->IsConstrained() )
2590 {
2591 m_editedPoint->SetPosition( pos );
2592 m_editedPoint->ApplyConstraint( grid );
2593 constraintSnapped = true;
2594
2595 // For constrained lines (like zone edges), try to snap to nearby anchors
2596 // that lie on the constraint line. First get the constrained position, then
2597 // look for snap anchors and verify they're on the constraint line.
2598 if( grid.GetSnap() && !snapLayers.empty() )
2599 {
2600 VECTOR2I constrainedPos = m_editedPoint->GetPosition();
2601 VECTOR2I snapPos = grid.BestSnapAnchor( constrainedPos, snapLayers,
2602 grid.GetItemGrid( item ), { item } );
2603
2604 // If we found a snap anchor different from the constrained position,
2605 // check if setting the point there and reapplying the constraint
2606 // results in a position close to the snap point
2607 if( snapPos != constrainedPos )
2608 {
2609 m_editedPoint->SetPosition( snapPos );
2610 m_editedPoint->ApplyConstraint( grid );
2611 VECTOR2I projectedPos = m_editedPoint->GetPosition();
2612
2613 // If the projection is close to the snap anchor, use it
2614 // Otherwise revert to the original constrained position
2615 const int snapTolerance = KiROUND( getView()->ToWorld( 5 ) );
2616
2617 if( ( projectedPos - snapPos ).EuclideanNorm() > snapTolerance )
2618 m_editedPoint->SetPosition( constrainedPos );
2619 }
2620 }
2621 }
2622 else if( !m_angleSnapActive && m_editedPoint->GetGridConstraint() == SNAP_TO_GRID )
2623 {
2624 m_editedPoint->SetPosition( grid.BestSnapAnchor( pos, snapLayers, grid.GetItemGrid( item ),
2625 { item } ) );
2626 }
2627 else
2628 {
2629 m_editedPoint->SetPosition( pos );
2630 }
2631
2632 if( haveSnapLineDirections )
2633 {
2634 if( constraintSnapped )
2635 grid.SetSnapLineEnd( m_editedPoint->GetPosition() );
2636 else
2637 grid.SetSnapLineEnd( std::nullopt );
2638 }
2639
2640 updateItem( commit );
2641 getViewControls()->ForceCursorPosition( true, m_editedPoint->GetPosition() );
2642 updatePoints();
2643
2644 if( m_radiusHelper )
2645 {
2646 if( m_editPoints->PointsSize() > RECT_RADIUS
2647 && m_editedPoint == &m_editPoints->Point( RECT_RADIUS ) )
2648 {
2649 if( PCB_SHAPE* rect = dynamic_cast<PCB_SHAPE*>( item ) )
2650 {
2651 int radius = rect->GetCornerRadius();
2652 int offset = radius - M_SQRT1_2 * radius;
2653 VECTOR2I topLeft = rect->GetTopLeft();
2654 VECTOR2I botRight = rect->GetBotRight();
2655 VECTOR2I topRight( botRight.x, topLeft.y );
2656 VECTOR2I center( topRight.x - offset, topRight.y + offset );
2657 m_radiusHelper->Set( radius, center, VECTOR2I( 1, -1 ), editFrame->GetUserUnits() );
2658 }
2659 }
2660 else
2661 {
2662 m_radiusHelper->Hide();
2663 }
2664 }
2665
2666 getView()->Update( &m_preview );
2667 }
2668 else if( m_editedPoint && evt->Action() == TA_MOUSE_DOWN && evt->Buttons() == BUT_LEFT )
2669 {
2670 m_editedPoint->SetActive();
2671
2672 for( size_t ii = 0; ii < m_editPoints->PointsSize(); ++ii )
2673 {
2674 EDIT_POINT& point = m_editPoints->Point( ii );
2675
2676 if( &point != m_editedPoint )
2677 point.SetActive( false );
2678 }
2679
2680 getView()->Update( m_editPoints.get() );
2681
2682 if( m_angleItem )
2683 getView()->Update( m_angleItem.get() );
2684 }
2685 else if( inDrag && evt->IsMouseUp( BUT_LEFT ) )
2686 {
2687 if( m_editedPoint )
2688 {
2689 m_editedPoint->SetActive( false );
2690 getView()->Update( m_editPoints.get() );
2691
2692 if( m_angleItem )
2693 getView()->Update( m_angleItem.get() );
2694 }
2695
2696 if( m_radiusHelper )
2697 m_radiusHelper->Hide();
2698
2699 getView()->Update( &m_preview );
2700
2701 getViewControls()->SetAutoPan( false );
2702 setAltConstraint( false );
2703 updateSnapLineDirections();
2704
2705 if( m_editorBehavior )
2706 m_editorBehavior->FinalizeItem( *m_editPoints, commit );
2707
2708 if( item->Type() == PCB_GENERATOR_T )
2709 {
2710 PCB_GENERATOR* generator = static_cast<PCB_GENERATOR*>( item );
2711
2712 m_preview.FreeItems();
2713 m_radiusHelper = nullptr;
2714 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genFinishEdit, &commit, generator );
2715
2716 commit.Push( generator->GetCommitMessage() );
2717 }
2718 else if( item->Type() == PCB_TABLECELL_T )
2719 {
2720 commit.Push( _( "Resize Table Cells" ) );
2721 }
2722 else
2723 {
2724 commit.Push( _( "Move Point" ) );
2725 }
2726
2727 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2728 {
2729 shape->ClearFlags( IS_MOVING );
2730 shape->UpdateHatching();
2731 }
2732
2733 inDrag = false;
2734 frame()->UndoRedoBlock( false );
2735 updateSnapLineDirections();
2736
2737 m_toolMgr->PostAction<EDA_ITEM*>( ACTIONS::reselectItem, item ); // FIXME: Needed for generators
2738 }
2739 else if( evt->IsCancelInteractive() || evt->IsActivate() )
2740 {
2741 if( inDrag ) // Restore the last change
2742 {
2743 if( item->Type() == PCB_GENERATOR_T )
2744 {
2745 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genCancelEdit, &commit,
2746 static_cast<PCB_GENERATOR*>( item ) );
2747 }
2748
2749 commit.Revert();
2750
2751 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2752 {
2753 shape->ClearFlags( IS_MOVING );
2754 shape->UpdateHatching();
2755 }
2756
2757 inDrag = false;
2758 frame()->UndoRedoBlock( false );
2759 updateSnapLineDirections();
2760 }
2761
2762 // Only cancel point editor when activating a new tool
2763 // Otherwise, allow the points to persist when moving up the
2764 // tool stack
2765 if( evt->IsActivate() && !evt->IsMoveTool() )
2766 break;
2767 }
2768 else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
2769 {
2770 // Re-create the points for items which can have different behavior on different layers
2771 if( item->Type() == PCB_PAD_T && m_isFootprintEditor )
2772 {
2773 if( getView()->HasItem( m_editPoints.get() ) )
2774 getView()->Remove( m_editPoints.get() );
2775
2776 if( m_angleItem && getView()->HasItem( m_angleItem.get() ) )
2777 getView()->Remove( m_angleItem.get() );
2778
2779 m_editPoints = makePoints( item );
2780
2781 if( m_angleItem )
2782 {
2783 m_angleItem->SetEditPoints( m_editPoints );
2784 getView()->Add( m_angleItem.get() );
2785 }
2786
2787 getView()->Add( m_editPoints.get() );
2788 }
2789 }
2790 else if( evt->Action() == TA_UNDO_REDO_POST )
2791 {
2792 break;
2793 }
2794 else
2795 {
2796 evt->SetPassEvent();
2797 }
2798 }
2799
2800 if( PCB_SHAPE* shape= dynamic_cast<PCB_SHAPE*>( item ) )
2801 {
2802 shape->ClearFlags( IS_MOVING );
2803 shape->UpdateHatching();
2804 }
2805
2806 m_preview.FreeItems();
2807 m_radiusHelper = nullptr;
2808
2809 if( getView()->HasItem( &m_preview ) )
2810 getView()->Remove( &m_preview );
2811
2812 if( m_editPoints )
2813 {
2814 if( getView()->HasItem( m_editPoints.get() ) )
2815 getView()->Remove( m_editPoints.get() );
2816
2817 if( m_angleItem && getView()->HasItem( m_angleItem.get() ) )
2818 getView()->Remove( m_angleItem.get() );
2819
2820 m_editPoints.reset();
2821 m_angleItem.reset();
2822 }
2823
2824 m_editedPoint = nullptr;
2825 grid.SetSnapLineDirections( {} );
2826
2827 return 0;
2828}
2829
2830
2832{
2833 if( !m_editPoints || !m_editPoints->GetParent() || !HasPoint() )
2834 return 0;
2835
2837
2838 BOARD_COMMIT commit( editFrame );
2839 commit.Stage( m_editPoints->GetParent(), CHT_MODIFY );
2840
2841 VECTOR2I pt = m_editedPoint->GetPosition();
2842 wxString title;
2843 wxString msg;
2844
2845 if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
2846 {
2847 title = _( "Move Midpoint to Location" );
2848 msg = _( "Move Midpoint" );
2849 }
2850 else
2851 {
2852 title = _( "Move Corner to Location" );
2853 msg = _( "Move Corner" );
2854 }
2855
2856 WX_PT_ENTRY_DIALOG dlg( editFrame, title, _( "X:" ), _( "Y:" ), pt, false );
2857
2858 if( dlg.ShowModal() == wxID_OK )
2859 {
2860 m_editedPoint->SetPosition( dlg.GetValue() );
2861 updateItem( commit );
2862 commit.Push( msg );
2863 }
2864
2865 return 0;
2866}
2867
2868
2870{
2871 wxCHECK( m_editPoints, /* void */ );
2872 EDA_ITEM* item = m_editPoints->GetParent();
2873
2874 if( !item )
2875 return;
2876
2877 // item is always updated
2878 std::vector<EDA_ITEM*> updatedItems = { item };
2879 aCommit.Modify( item );
2880
2881 if( m_editorBehavior )
2882 {
2883 wxCHECK( m_editedPoint, /* void */ );
2884 m_editorBehavior->UpdateItem( *m_editedPoint, *m_editPoints, aCommit, updatedItems );
2885 }
2886
2887 // Perform any post-edit actions that the item may require
2888
2889 switch( item->Type() )
2890 {
2891 case PCB_TEXTBOX_T:
2892 case PCB_SHAPE_T:
2893 {
2894 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
2895
2896 if( shape->IsProxyItem() )
2897 {
2898 for( PAD* pad : shape->GetParentFootprint()->Pads() )
2899 {
2900 if( pad->IsEntered() )
2901 view()->Update( pad );
2902 }
2903 }
2904
2905 // Nuke outline font render caches
2906 if( PCB_TEXTBOX* textBox = dynamic_cast<PCB_TEXTBOX*>( item ) )
2907 textBox->ClearRenderCache();
2908
2909 break;
2910 }
2911 case PCB_GENERATOR_T:
2912 {
2913 GENERATOR_TOOL* generatorTool = m_toolMgr->GetTool<GENERATOR_TOOL>();
2914 PCB_GENERATOR* generatorItem = static_cast<PCB_GENERATOR*>( item );
2915
2916 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genUpdateEdit, &aCommit, generatorItem );
2917
2918 // Note: POINT_EDITOR::m_preview holds only the canvas-draw status "popup"; the meanders
2919 // themselves (ROUTER_PREVIEW_ITEMs) are owned by the router.
2920
2921 m_preview.FreeItems();
2922 m_radiusHelper = nullptr;
2923
2924 for( EDA_ITEM* previewItem : generatorItem->GetPreviewItems( generatorTool, frame(), STATUS_ITEMS_ONLY ) )
2925 m_preview.Add( previewItem );
2926
2927 getView()->Update( &m_preview );
2928 break;
2929 }
2930 default:
2931 break;
2932 }
2933
2934 // Update the item and any affected items
2935 for( EDA_ITEM* updatedItem : updatedItems )
2936 getView()->Update( updatedItem );
2937
2938 frame()->SetMsgPanel( item );
2939}
2940
2941
2943{
2944 if( !m_editPoints )
2945 return;
2946
2947 EDA_ITEM* item = m_editPoints->GetParent();
2948
2949 if( !item )
2950 return;
2951
2952 if( !m_editorBehavior )
2953 return;
2954
2955 int editedIndex = -1;
2956 bool editingLine = false;
2957
2958 if( m_editedPoint )
2959 {
2960 // Check if we're editing a point (vertex)
2961 for( unsigned ii = 0; ii < m_editPoints->PointsSize(); ++ii )
2962 {
2963 if( &m_editPoints->Point( ii ) == m_editedPoint )
2964 {
2965 editedIndex = ii;
2966 break;
2967 }
2968 }
2969
2970 // If not found in points, check if we're editing a line (midpoint)
2971 if( editedIndex == -1 )
2972 {
2973 for( unsigned ii = 0; ii < m_editPoints->LinesSize(); ++ii )
2974 {
2975 if( &m_editPoints->Line( ii ) == m_editedPoint )
2976 {
2977 editedIndex = ii;
2978 editingLine = true;
2979 break;
2980 }
2981 }
2982 }
2983 }
2984
2985 if( !m_editorBehavior->UpdatePoints( *m_editPoints ) )
2986 {
2987 if( getView()->HasItem( m_editPoints.get() ) )
2988 getView()->Remove( m_editPoints.get() );
2989
2990 m_editPoints = makePoints( item );
2991 getView()->Add( m_editPoints.get() );
2992 }
2993
2994 if( editedIndex >= 0 )
2995 {
2996 if( editingLine && editedIndex < (int) m_editPoints->LinesSize() )
2997 m_editedPoint = &m_editPoints->Line( editedIndex );
2998 else if( !editingLine && editedIndex < (int) m_editPoints->PointsSize() )
2999 m_editedPoint = &m_editPoints->Point( editedIndex );
3000 else
3001 m_editedPoint = nullptr;
3002 }
3003 else
3004 {
3005 m_editedPoint = nullptr;
3006 }
3007
3008 getView()->Update( m_editPoints.get() );
3009
3010 if( m_angleItem )
3011 getView()->Update( m_angleItem.get() );
3012}
3013
3014
3016{
3018
3019 if( aPoint )
3020 {
3021 frame()->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
3022 controls->ForceCursorPosition( true, aPoint->GetPosition() );
3023 controls->ShowCursor( true );
3024 }
3025 else
3026 {
3027 if( frame()->ToolStackIsEmpty() )
3028 controls->ShowCursor( false );
3029
3030 controls->ForceCursorPosition( false );
3031 }
3032
3033 m_editedPoint = aPoint;
3034}
3035
3036
3038{
3039 EDA_ITEM* parent = m_editPoints ? m_editPoints->GetParent() : nullptr;
3040 EDIT_LINE* line = dynamic_cast<EDIT_LINE*>( m_editedPoint );
3041 bool isPoly = false;
3042
3043 if( parent )
3044 {
3045 switch( parent->Type() )
3046 {
3047 case PCB_ZONE_T:
3048 isPoly = true;
3049 break;
3050
3051 case PCB_SHAPE_T:
3052 isPoly = static_cast<PCB_SHAPE*>( parent )->GetShape() == SHAPE_T::POLY;
3053 break;
3054
3055 default:
3056 break;
3057 }
3058 }
3059
3060 if( aEnabled )
3061 {
3062 if( line && isPoly )
3063 {
3064 // For polygon lines, toggle the mode on the existing constraint rather than
3065 // creating a new one. This preserves the original reference positions.
3066 EC_CONVERGING* constraint = dynamic_cast<EC_CONVERGING*>( line->GetConstraint() );
3067
3068 if( constraint )
3070
3071 // Don't set m_altConstraint - we're modifying the line's own constraint
3072 }
3073 else
3074 {
3075 // Find a proper constraining point for angle snapping mode
3077
3078 if( Is90Limited() )
3080 else
3082 }
3083 }
3084 else
3085 {
3086 if( line && isPoly )
3087 {
3088 // Restore the line's constraint to CONVERGING mode
3089 EC_CONVERGING* constraint = dynamic_cast<EC_CONVERGING*>( line->GetConstraint() );
3090
3091 if( constraint )
3093 }
3094
3095 m_altConstraint.reset();
3096 }
3097}
3098
3099
3101{
3102 // If there's a behaviour and it provides a constrainer, use that
3103 if( m_editorBehavior )
3104 {
3105 const OPT_VECTOR2I constrainer = m_editorBehavior->Get45DegreeConstrainer( *m_editedPoint, *m_editPoints );
3106
3107 if( constrainer )
3108 return EDIT_POINT( *constrainer );
3109 }
3110
3111 // In any other case we may align item to its original position
3112 return m_original;
3113}
3114
3115
3116// Finds a corresponding vertex in a polygon set
3117static std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX> findVertex( SHAPE_POLY_SET& aPolySet, const EDIT_POINT& aPoint )
3118{
3119 for( auto it = aPolySet.IterateWithHoles(); it; ++it )
3120 {
3121 auto vertexIdx = it.GetIndex();
3122
3123 if( aPolySet.CVertex( vertexIdx ) == aPoint.GetPosition() )
3124 return std::make_pair( true, vertexIdx );
3125 }
3126
3127 return std::make_pair( false, SHAPE_POLY_SET::VERTEX_INDEX() );
3128}
3129
3130
3132{
3133 if( !m_editPoints || !m_editedPoint )
3134 return false;
3135
3136 EDA_ITEM* item = m_editPoints->GetParent();
3137 SHAPE_POLY_SET* polyset = nullptr;
3138
3139 if( !item )
3140 return false;
3141
3142 switch( item->Type() )
3143 {
3144 case PCB_ZONE_T:
3145 polyset = static_cast<ZONE*>( item )->Outline();
3146 break;
3147
3148 case PCB_SHAPE_T:
3149 if( static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY )
3150 polyset = &static_cast<PCB_SHAPE*>( item )->GetPolyShape();
3151 else
3152 return false;
3153
3154 break;
3155
3156 default:
3157 return false;
3158 }
3159
3160 std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX> vertex = findVertex( *polyset, *m_editedPoint );
3161
3162 if( !vertex.first )
3163 return false;
3164
3165 const SHAPE_POLY_SET::VERTEX_INDEX& vertexIdx = vertex.second;
3166
3167 // Check if there are enough vertices so one can be removed without
3168 // degenerating the polygon.
3169 // The first condition allows one to remove all corners from holes (when
3170 // there are only 2 vertices left, a hole is removed).
3171 if( vertexIdx.m_contour == 0
3172 && polyset->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour].PointCount() <= 3 )
3173 {
3174 return false;
3175 }
3176
3177 // Remove corner does not work with lines
3178 if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
3179 return false;
3180
3181 return m_editedPoint != nullptr;
3182}
3183
3184
3186{
3187 if( !m_editPoints )
3188 return 0;
3189
3190 EDA_ITEM* item = m_editPoints->GetParent();
3192 const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
3193
3194 // called without an active edited polygon
3195 if( !item || !CanAddCorner( *item ) )
3196 return 0;
3197
3198 PCB_SHAPE* graphicItem = dynamic_cast<PCB_SHAPE*>( item );
3199 BOARD_COMMIT commit( frame );
3200
3201 if( item->Type() == PCB_ZONE_T || ( graphicItem && graphicItem->GetShape() == SHAPE_T::POLY ) )
3202 {
3203 unsigned int nearestIdx = 0;
3204 unsigned int nextNearestIdx = 0;
3205 unsigned int nearestDist = INT_MAX;
3206 unsigned int firstPointInContour = 0;
3207 SHAPE_POLY_SET* zoneOutline;
3208
3209 if( item->Type() == PCB_ZONE_T )
3210 {
3211 ZONE* zone = static_cast<ZONE*>( item );
3212 zoneOutline = zone->Outline();
3213 zone->SetNeedRefill( true );
3214 }
3215 else
3216 {
3217 zoneOutline = &( graphicItem->GetPolyShape() );
3218 }
3219
3220 commit.Modify( item );
3221
3222 // Search the best outline segment to add a new corner
3223 // and therefore break this segment into two segments
3224
3225 // Object to iterate through the corners of the outlines (main contour and its holes)
3226 SHAPE_POLY_SET::ITERATOR iterator = zoneOutline->Iterate( 0, zoneOutline->OutlineCount()-1,
3227 /* IterateHoles */ true );
3228 int curr_idx = 0;
3229
3230 // Iterate through all the corners of the outlines and search the best segment
3231 for( ; iterator; iterator++, curr_idx++ )
3232 {
3233 int jj = curr_idx+1;
3234
3235 if( iterator.IsEndContour() )
3236 { // We reach the last point of the current contour (main or hole)
3237 jj = firstPointInContour;
3238 firstPointInContour = curr_idx+1; // Prepare next contour analysis
3239 }
3240
3241 SEG curr_segment( zoneOutline->CVertex( curr_idx ), zoneOutline->CVertex( jj ) );
3242
3243 unsigned int distance = curr_segment.Distance( cursorPos );
3244
3245 if( distance < nearestDist )
3246 {
3247 nearestDist = distance;
3248 nearestIdx = curr_idx;
3249 nextNearestIdx = jj;
3250 }
3251 }
3252
3253 // Find the point on the closest segment
3254 const VECTOR2I& sideOrigin = zoneOutline->CVertex( nearestIdx );
3255 const VECTOR2I& sideEnd = zoneOutline->CVertex( nextNearestIdx );
3256 SEG nearestSide( sideOrigin, sideEnd );
3257 VECTOR2I nearestPoint = nearestSide.NearestPoint( cursorPos );
3258
3259 // Do not add points that have the same coordinates as ones that already belong to polygon
3260 // instead, add a point in the middle of the side
3261 if( nearestPoint == sideOrigin || nearestPoint == sideEnd )
3262 nearestPoint = ( sideOrigin + sideEnd ) / 2;
3263
3264 zoneOutline->InsertVertex( nextNearestIdx, nearestPoint );
3265
3266 if( item->Type() == PCB_ZONE_T )
3267 static_cast<ZONE*>( item )->HatchBorder();
3268
3269 commit.Push( _( "Add Zone Corner" ) );
3270 }
3271 else if( graphicItem )
3272 {
3273 switch( graphicItem->GetShape() )
3274 {
3275 case SHAPE_T::SEGMENT:
3276 {
3277 commit.Modify( graphicItem );
3278
3279 SEG seg( graphicItem->GetStart(), graphicItem->GetEnd() );
3280 VECTOR2I nearestPoint = seg.NearestPoint( cursorPos );
3281
3282 // Move the end of the line to the break point..
3283 graphicItem->SetEnd( nearestPoint );
3284
3285 // and add another one starting from the break point
3286 PCB_SHAPE* newSegment = static_cast<PCB_SHAPE*>( graphicItem->Duplicate( true, &commit ) );
3287 newSegment->ClearSelected();
3288 newSegment->SetStart( nearestPoint );
3289 newSegment->SetEnd( VECTOR2I( seg.B.x, seg.B.y ) );
3290
3291 commit.Add( newSegment );
3292 commit.Push( _( "Split Segment" ) );
3293 break;
3294 }
3295 case SHAPE_T::ARC:
3296 {
3297 commit.Modify( graphicItem );
3298
3299 const SHAPE_ARC arc( graphicItem->GetStart(), graphicItem->GetArcMid(), graphicItem->GetEnd(), 0 );
3300 const VECTOR2I nearestPoint = arc.NearestPoint( cursorPos );
3301
3302 // Move the end of the arc to the break point..
3303 graphicItem->SetEnd( nearestPoint );
3304
3305 // and add another one starting from the break point
3306 PCB_SHAPE* newArc = static_cast<PCB_SHAPE*>( graphicItem->Duplicate( true, &commit ) );
3307
3308 newArc->ClearSelected();
3309 newArc->SetEnd( arc.GetP1() );
3310 newArc->SetStart( nearestPoint );
3311
3312 commit.Add( newArc );
3313 commit.Push( _( "Split Arc" ) );
3314 break;
3315 }
3316 default:
3317 // No split implemented for other shapes
3318 break;
3319 }
3320 }
3321
3322 updatePoints();
3323 return 0;
3324}
3325
3326
3328{
3329 if( !m_editPoints || !m_editedPoint )
3330 return 0;
3331
3332 EDA_ITEM* item = m_editPoints->GetParent();
3333
3334 if( !item )
3335 return 0;
3336
3337 SHAPE_POLY_SET* polygon = nullptr;
3338
3339 if( item->Type() == PCB_ZONE_T )
3340 {
3341 ZONE* zone = static_cast<ZONE*>( item );
3342 polygon = zone->Outline();
3343 zone->SetNeedRefill( true );
3344 }
3345 else if( item->Type() == PCB_SHAPE_T )
3346 {
3347 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3348
3349 if( shape->GetShape() == SHAPE_T::POLY )
3350 polygon = &shape->GetPolyShape();
3351 }
3352
3353 if( !polygon )
3354 return 0;
3355
3357 BOARD_COMMIT commit( frame );
3358 auto vertex = findVertex( *polygon, *m_editedPoint );
3359
3360 if( vertex.first )
3361 {
3362 const auto& vertexIdx = vertex.second;
3363 auto& outline = polygon->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour];
3364
3365 if( outline.PointCount() > 3 )
3366 {
3367 // the usual case: remove just the corner when there are >3 vertices
3368 commit.Modify( item );
3369 polygon->RemoveVertex( vertexIdx );
3370 }
3371 else
3372 {
3373 // either remove a hole or the polygon when there are <= 3 corners
3374 if( vertexIdx.m_contour > 0 )
3375 {
3376 // remove hole
3377 commit.Modify( item );
3378 polygon->RemoveContour( vertexIdx.m_contour );
3379 }
3380 else
3381 {
3382 m_toolMgr->RunAction( ACTIONS::selectionClear );
3383 commit.Remove( item );
3384 }
3385 }
3386
3387 setEditedPoint( nullptr );
3388
3389 if( item->Type() == PCB_ZONE_T )
3390 commit.Push( _( "Remove Zone Corner" ) );
3391 else
3392 commit.Push( _( "Remove Polygon Corner" ) );
3393
3394 if( item->Type() == PCB_ZONE_T )
3395 static_cast<ZONE*>( item )->HatchBorder();
3396
3397 updatePoints();
3398 }
3399
3400 return 0;
3401}
3402
3403
3405{
3406 if( !m_editPoints || !m_editedPoint )
3407 return 0;
3408
3409 EDA_ITEM* item = m_editPoints->GetParent();
3410
3411 if( !item )
3412 return 0;
3413
3414 SHAPE_POLY_SET* polygon = nullptr;
3415
3416 if( item->Type() == PCB_ZONE_T )
3417 {
3418 ZONE* zone = static_cast<ZONE*>( item );
3419 polygon = zone->Outline();
3420 zone->SetNeedRefill( true );
3421 }
3422 else if( item->Type() == PCB_SHAPE_T )
3423 {
3424 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3425
3426 if( shape->GetShape() == SHAPE_T::POLY )
3427 polygon = &shape->GetPolyShape();
3428 }
3429
3430 if( !polygon )
3431 return 0;
3432
3433 // Search the best outline corner to break
3434
3436 BOARD_COMMIT commit( frame );
3437 const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
3438
3439 unsigned int nearestIdx = 0;
3440 unsigned int nearestDist = INT_MAX;
3441
3442 int curr_idx = 0;
3443 // Object to iterate through the corners of the outlines (main contour and its holes)
3444 SHAPE_POLY_SET::ITERATOR iterator = polygon->Iterate( 0, polygon->OutlineCount() - 1,
3445 /* IterateHoles */ true );
3446
3447 // Iterate through all the corners of the outlines and search the best segment
3448 for( ; iterator; iterator++, curr_idx++ )
3449 {
3450 unsigned int distance = polygon->CVertex( curr_idx ).Distance( cursorPos );
3451
3452 if( distance < nearestDist )
3453 {
3454 nearestDist = distance;
3455 nearestIdx = curr_idx;
3456 }
3457 }
3458
3459 int prevIdx, nextIdx;
3460 if( polygon->GetNeighbourIndexes( nearestIdx, &prevIdx, &nextIdx ) )
3461 {
3462 const SEG segA{ polygon->CVertex( prevIdx ), polygon->CVertex( nearestIdx ) };
3463 const SEG segB{ polygon->CVertex( nextIdx ), polygon->CVertex( nearestIdx ) };
3464
3465 // A plausible setback that won't consume a whole edge
3466 int setback = pcbIUScale.mmToIU( 5 );
3467 setback = std::min( setback, (int) ( segA.Length() * 0.25 ) );
3468 setback = std::min( setback, (int) ( segB.Length() * 0.25 ) );
3469
3470 CHAMFER_PARAMS chamferParams{ setback, setback };
3471
3472 std::optional<CHAMFER_RESULT> chamferResult = ComputeChamferPoints( segA, segB, chamferParams );
3473
3474 if( chamferResult && chamferResult->m_updated_seg_a && chamferResult->m_updated_seg_b )
3475 {
3476 commit.Modify( item );
3477 polygon->RemoveVertex( nearestIdx );
3478
3479 // The two end points of the chamfer are the new corners
3480 polygon->InsertVertex( nearestIdx, chamferResult->m_updated_seg_b->B );
3481 polygon->InsertVertex( nearestIdx, chamferResult->m_updated_seg_a->B );
3482 }
3483 }
3484
3485 setEditedPoint( nullptr );
3486
3487 if( item->Type() == PCB_ZONE_T )
3488 commit.Push( _( "Break Zone Corner" ) );
3489 else
3490 commit.Push( _( "Break Polygon Corner" ) );
3491
3492 if( item->Type() == PCB_ZONE_T )
3493 static_cast<ZONE*>( item )->HatchBorder();
3494
3495 updatePoints();
3496
3497 return 0;
3498}
3499
3500
3502{
3503 updatePoints();
3504 return 0;
3505}
3506
3507
3509{
3511
3512 if( aEvent.Matches( ACTIONS::cycleArcEditMode.MakeEvent() ) )
3513 {
3514 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
3516 else
3518
3520 }
3521 else
3522 {
3524 }
3525
3526 if( editFrame->IsType( FRAME_PCB_EDITOR ) )
3528 else
3530
3531 return 0;
3532}
3533
3534
3536{
3554}
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:144
virtual EDA_ITEM * Clone() const
Create a duplicate of this item with linked list members set to NULL.
Definition eda_item.cpp:128
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:55
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:220
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.