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