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