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