KiCad PCB EDA Suite
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  * @author Maciej Suminski <maciej.suminski@cern.ch>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #include <functional>
26 #include <memory>
27 
28 using namespace std::placeholders;
29 #include <advanced_config.h>
30 #include <tool/tool_manager.h>
31 #include <view/view_controls.h>
32 #include <geometry/seg.h>
33 #include <confirm.h>
34 #include "pcb_actions.h"
35 #include "pcb_selection_tool.h"
36 #include "pcb_point_editor.h"
37 #include "pcb_grid_helper.h"
38 #include <board_commit.h>
39 #include <bitmaps.h>
40 #include <status_popup.h>
41 #include <pcb_edit_frame.h>
42 #include <fp_shape.h>
43 #include <pcb_dimension.h>
44 #include <pad.h>
45 #include <zone.h>
48 
49 // Few constants to avoid using bare numbers for point indices
51 {
53 };
54 
56 {
58 };
59 
61 {
63 };
64 
66 {
68 };
69 
71 {
73 };
74 
76 {
81 };
82 
84 {
90 };
91 
93  PCB_TOOL_BASE( "pcbnew.PointEditor" ),
94  m_selectionTool( nullptr ),
95  m_editedPoint( nullptr ),
96  m_hoveredPoint( nullptr ),
97  m_original( VECTOR2I( 0, 0 ) ),
98  m_refill( false ),
99  m_altEditMethod( false ),
100  m_altConstrainer( VECTOR2I( 0, 0 ) )
101 {
102 }
103 
104 
106 {
107  m_refill = false;
108  m_editPoints.reset();
109  m_altConstraint.reset();
110  getViewControls()->SetAutoPan( false );
111 
112  m_statusPopup = std::make_unique<STATUS_TEXT_POPUP>( getEditFrame<PCB_BASE_EDIT_FRAME>() );
113  m_statusPopup->SetTextColor( wxColour( 255, 0, 0 ) );
114  m_statusPopup->SetText( _( "Self-intersecting polygons are not allowed." ) );
115 }
116 
117 
119 {
120  // Find the selection tool, so they can cooperate
122 
123  wxASSERT_MSG( m_selectionTool, "pcbnew.InteractiveSelection tool is not available" );
124 
125  auto& menu = m_selectionTool->GetToolMenu().GetMenu();
128  std::bind( &PCB_POINT_EDITOR::removeCornerCondition, this, _1 ) );
129 
130  return true;
131 }
132 
133 
134 void PCB_POINT_EDITOR::buildForPolyOutline( std::shared_ptr<EDIT_POINTS> points,
135  const SHAPE_POLY_SET* aOutline )
136 {
137  int cornersCount = aOutline->TotalVertices();
138 
139  for( auto iterator = aOutline->CIterateWithHoles(); iterator; iterator++ )
140  {
141  points->AddPoint( *iterator );
142 
143  if( iterator.IsEndContour() )
144  points->AddBreak();
145  }
146 
147  // Lines have to be added after creating edit points,
148  // as they use EDIT_POINT references
149  for( int i = 0; i < cornersCount - 1; ++i )
150  {
151  if( points->IsContourEnd( i ) )
152  points->AddLine( points->Point( i ), points->Point( points->GetContourStartIdx( i ) ) );
153  else
154  points->AddLine( points->Point( i ), points->Point( i + 1 ) );
155 
156  points->Line( i ).SetConstraint( new EC_PERPLINE( points->Line( i ) ) );
157  }
158 
159  // The last missing line, connecting the last and the first polygon point
160  points->AddLine( points->Point( cornersCount - 1 ),
161  points->Point( points->GetContourStartIdx( cornersCount - 1 ) ) );
162 
163  points->Line( points->LinesSize() - 1 )
164  .SetConstraint( new EC_PERPLINE( points->Line( points->LinesSize() - 1 ) ) );
165 }
166 
167 
168 std::shared_ptr<EDIT_POINTS> PCB_POINT_EDITOR::makePoints( EDA_ITEM* aItem )
169 {
170  std::shared_ptr<EDIT_POINTS> points = std::make_shared<EDIT_POINTS>( aItem );
171 
172  if( !aItem )
173  return points;
174 
175  // Generate list of edit points basing on the item type
176  switch( aItem->Type() )
177  {
178  case PCB_SHAPE_T:
179  case PCB_FP_SHAPE_T:
180  {
181  const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( aItem );
182 
183  switch( shape->GetShape() )
184  {
186  points->AddPoint( shape->GetStart() );
187  points->AddPoint( shape->GetEnd() );
188  break;
189 
191  points->AddPoint( shape->GetStart() );
192  points->AddPoint( wxPoint( shape->GetEnd().x, shape->GetStart().y ) );
193  points->AddPoint( shape->GetEnd() );
194  points->AddPoint( wxPoint( shape->GetStart().x, shape->GetEnd().y ) );
195 
196  points->AddLine( points->Point( RECT_TOP_LEFT ), points->Point( RECT_TOP_RIGHT ) );
197  points->Line( RECT_TOP ).SetConstraint( new EC_PERPLINE( points->Line( RECT_TOP ) ) );
198  points->AddLine( points->Point( RECT_TOP_RIGHT ), points->Point( RECT_BOT_RIGHT ) );
199  points->Line( RECT_RIGHT ).SetConstraint( new EC_PERPLINE( points->Line( RECT_RIGHT ) ) );
200  points->AddLine( points->Point( RECT_BOT_RIGHT ), points->Point( RECT_BOT_LEFT ) );
201  points->Line( RECT_BOT ).SetConstraint( new EC_PERPLINE( points->Line( RECT_BOT ) ) );
202  points->AddLine( points->Point( RECT_BOT_LEFT ), points->Point( RECT_TOP_LEFT ) );
203  points->Line( RECT_LEFT ).SetConstraint( new EC_PERPLINE( points->Line( RECT_LEFT ) ) );
204 
205  break;
206 
207  case PCB_SHAPE_TYPE::ARC:
208  points->AddPoint( shape->GetCenter() );
209  points->AddPoint( shape->GetArcStart() );
210  points->AddPoint( shape->GetArcMid() );
211  points->AddPoint( shape->GetArcEnd() );
212 
213  points->Point( ARC_MID ).SetGridConstraint( IGNORE_GRID );
214  points->Point( ARC_START ).SetGridConstraint( SNAP_TO_GRID );
215  points->Point( ARC_CENTER ).SetGridConstraint( SNAP_BY_GRID );
216  points->Point( ARC_END ).SetGridConstraint( SNAP_TO_GRID );
217  break;
218 
220  points->AddPoint( shape->GetCenter() );
221  points->AddPoint( shape->GetEnd() );
222  break;
223 
225  buildForPolyOutline( points, &shape->GetPolyShape() );
226  break;
227 
229  points->AddPoint( shape->GetStart() );
230  points->AddPoint( shape->GetBezControl1() );
231  points->AddPoint( shape->GetBezControl2() );
232  points->AddPoint( shape->GetEnd() );
233  break;
234 
235  default: // suppress warnings
236  break;
237  }
238 
239  break;
240  }
241 
242  case PCB_PAD_T:
243  {
244  const PAD* pad = static_cast<const PAD*>( aItem );
245  wxPoint shapePos = pad->ShapePos();
246  wxPoint halfSize( pad->GetSize().x / 2, pad->GetSize().y / 2 );
247 
248  if( !m_isFootprintEditor || pad->IsLocked() )
249  break;
250 
251  switch( pad->GetShape() )
252  {
253  case PAD_SHAPE::CIRCLE:
254  points->AddPoint( shapePos );
255  points->AddPoint( wxPoint( shapePos.x + halfSize.x, shapePos.y ) );
256  break;
257 
258  case PAD_SHAPE::OVAL:
260  case PAD_SHAPE::RECT:
263  {
264  if( (int) pad->GetOrientation() % 900 != 0 )
265  break;
266 
267  if( pad->GetOrientation() == 900 || pad->GetOrientation() == 2700 )
268  std::swap( halfSize.x, halfSize.y );
269 
270  points->AddPoint( shapePos - halfSize );
271  points->AddPoint( wxPoint( shapePos.x + halfSize.x, shapePos.y - halfSize.y ) );
272  points->AddPoint( shapePos + halfSize );
273  points->AddPoint( wxPoint( shapePos.x - halfSize.x, shapePos.y + halfSize.y ) );
274  }
275  break;
276 
277  default: // suppress warnings
278  break;
279  }
280  }
281  break;
282 
283  case PCB_FP_ZONE_T:
284  case PCB_ZONE_T:
285  {
286  const ZONE* zone = static_cast<const ZONE*>( aItem );
287  buildForPolyOutline( points, zone->Outline() );
288  }
289  break;
290 
291  case PCB_DIM_ALIGNED_T:
293  {
294  const PCB_DIM_ALIGNED* dimension = static_cast<const PCB_DIM_ALIGNED*>( aItem );
295 
296  points->AddPoint( dimension->GetStart() );
297  points->AddPoint( dimension->GetEnd() );
298  points->AddPoint( dimension->Text().GetPosition() );
299  points->AddPoint( dimension->GetCrossbarStart() );
300  points->AddPoint( dimension->GetCrossbarEnd() );
301 
302  if( aItem->Type() == PCB_DIM_ALIGNED_T )
303  {
304  // Dimension height setting - edit points should move only along the feature lines
305  points->Point( DIM_CROSSBARSTART )
306  .SetConstraint( new EC_LINE( points->Point( DIM_CROSSBARSTART ),
307  points->Point( DIM_START ) ) );
308  points->Point( DIM_CROSSBAREND )
309  .SetConstraint( new EC_LINE( points->Point( DIM_CROSSBAREND ),
310  points->Point( DIM_END ) ) );
311  }
312 
313  break;
314  }
315 
316  case PCB_DIM_CENTER_T:
317  {
318  const PCB_DIM_CENTER* dimension = static_cast<const PCB_DIM_CENTER*>( aItem );
319 
320  points->AddPoint( dimension->GetStart() );
321  points->AddPoint( dimension->GetEnd() );
322 
323  points->Point( DIM_END ).SetConstraint( new EC_45DEGREE( points->Point( DIM_END ),
324  points->Point( DIM_START ) ) );
325 
326  break;
327  }
328 
329  case PCB_DIM_LEADER_T:
330  {
331  const PCB_DIM_LEADER* dimension = static_cast<const PCB_DIM_LEADER*>( aItem );
332 
333  points->AddPoint( dimension->GetStart() );
334  points->AddPoint( dimension->GetEnd() );
335  points->AddPoint( dimension->Text().GetPosition() );
336 
337  break;
338  }
339 
340  default:
341  points.reset();
342  break;
343  }
344 
345  return points;
346 }
347 
348 
350 {
351  EDIT_POINT* point;
352  EDIT_POINT* hovered = nullptr;
353 
354  if( aEvent.IsMotion() )
355  {
356  point = m_editPoints->FindPoint( aEvent.Position(), getView() );
357  hovered = point;
358  }
359  else if( aEvent.IsDrag( BUT_LEFT ) )
360  {
361  point = m_editPoints->FindPoint( aEvent.DragOrigin(), getView() );
362  }
363  else
364  {
365  point = m_editPoints->FindPoint( getViewControls()->GetCursorPosition(), getView() );
366  }
367 
368  if( hovered )
369  {
370  if( m_hoveredPoint != hovered )
371  {
372  if( m_hoveredPoint )
373  m_hoveredPoint->SetHover( false );
374 
375  m_hoveredPoint = hovered;
377  }
378  }
379  else if( m_hoveredPoint )
380  {
381  m_hoveredPoint->SetHover( false );
382  m_hoveredPoint = nullptr;
383  }
384 
385  if( m_editedPoint != point )
386  setEditedPoint( point );
387 }
388 
389 
391 {
393  return 0;
394 
395  PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
397 
398  if( selection.Size() != 1 || selection.Front()->GetEditFlags() )
399  return 0;
400 
401  BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
402 
403  if( !item || item->IsLocked() )
404  return 0;
405 
406  Activate();
407  getViewControls()->ShowCursor( true );
408 
410  m_editPoints = makePoints( item );
411 
412  if( !m_editPoints )
413  return 0;
414 
415  getView()->Add( m_editPoints.get() );
416  setEditedPoint( nullptr );
417  updateEditedPoint( aEvent );
418  m_refill = false;
419  bool inDrag = false;
420 
421  BOARD_COMMIT commit( editFrame );
422  LSET snapLayers = item->GetLayerSet();
423 
424  if( BaseType( item->Type() ) == PCB_DIMENSION_T )
425  snapLayers = LSET::AllLayersMask();
426 
427  // Main loop: keep receiving events
428  while( TOOL_EVENT* evt = Wait() )
429  {
430  grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
431  grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
432 
433  if( !m_editPoints || evt->IsSelectionEvent() ||
434  evt->Matches( EVENTS::InhibitSelectionEditing ) )
435  {
436  break;
437  }
438 
439  EDIT_POINT* prevHover = m_hoveredPoint;
440 
441  if( !inDrag )
442  updateEditedPoint( *evt );
443 
444  if( prevHover != m_hoveredPoint )
445  getView()->Update( m_editPoints.get() );
446 
447  if( evt->IsDrag( BUT_LEFT ) && m_editedPoint )
448  {
449  if( !inDrag )
450  {
451  frame()->UndoRedoBlock( true );
452 
453  commit.StageItems( selection, CHT_MODIFY );
454 
456  m_original = *m_editedPoint; // Save the original position
457  getViewControls()->SetAutoPan( true );
458  inDrag = true;
459 
461  grid.SetAuxAxes( true, m_original.GetPosition() );
462 
463  setAltConstraint( true );
465  }
466 
467  VECTOR2I pos = evt->Position();
468 
469  //TODO: unify the constraints to solve simultaneously instead of sequentially
470  switch( m_editedPoint->GetGridConstraint() )
471  {
472  case IGNORE_GRID:
473  m_editedPoint->SetPosition( pos );
474  break;
475 
476  case SNAP_TO_GRID:
477  m_editedPoint->SetPosition( grid.BestSnapAnchor( pos, snapLayers, { item } ) );
478  break;
479 
480  case SNAP_BY_GRID:
481  {
482  if( grid.GetUseGrid() )
483  {
484  VECTOR2I gridPt = grid.BestSnapAnchor( pos, {}, { item } );
485 
487  VECTOR2I delta = pos - last;
488  VECTOR2I deltaGrid = gridPt - grid.BestSnapAnchor( last, {}, { item } );
489 
490  if( abs( delta.x ) > grid.GetGrid().x / 2 )
491  pos.x = last.x + deltaGrid.x;
492  else
493  pos.x = last.x;
494 
495  if( abs( delta.y ) > grid.GetGrid().y / 2 )
496  pos.y = last.y + deltaGrid.y;
497  else
498  pos.y = last.y;
499  }
500 
501  m_editedPoint->SetPosition( pos );
502  }
503  break;
504  }
505 
506  // The alternative constraint limits to 45 degrees
507  bool enableAltConstraint = !!evt->Modifier( MD_SHIFT );
508 
509  if( enableAltConstraint )
510  m_altConstraint->Apply();
511  else
513 
515  m_editedPoint->SetPosition( grid.BestSnapAnchor( pos, snapLayers, { item } ) );
516 
517  updateItem();
519  updatePoints();
520  }
521  else if( m_editedPoint && evt->Action() == TA_MOUSE_DOWN && evt->Buttons() == BUT_LEFT )
522  {
524  getView()->Update( m_editPoints.get() );
525  }
526  else if( inDrag && evt->IsMouseUp( BUT_LEFT ) )
527  {
528  if( m_editedPoint )
529  {
530  m_editedPoint->SetActive( false );
531  getView()->Update( m_editPoints.get() );
532  }
533 
534  getViewControls()->SetAutoPan( false );
535  setAltConstraint( false );
536 
537  commit.Push( _( "Drag a corner" ) );
538  inDrag = false;
539  frame()->UndoRedoBlock( false );
540 
541  m_refill = true;
542  }
543 
544  else if( evt->IsCancelInteractive() || evt->IsActivate() )
545  {
546  if( inDrag ) // Restore the last change
547  {
548  commit.Revert();
549  inDrag = false;
550  frame()->UndoRedoBlock( false );
551  }
552 
553  // Only cancel point editor when activating a new tool
554  // Otherwise, allow the points to persist when moving up the
555  // tool stack
556  if( evt->IsActivate() && !evt->IsMoveTool() )
557  break;
558  }
559 
560  else if( evt->Action() == TA_UNDO_REDO_POST )
561  {
562  break;
563  }
564 
565  else
566  {
567  evt->SetPassEvent();
568  }
569  }
570 
571  if( m_editPoints )
572  {
573  getView()->Remove( m_editPoints.get() );
574 
575  finishItem();
576  m_editPoints.reset();
577  }
578 
579  frame()->UpdateMsgPanel();
580 
581  return 0;
582 }
583 
585  VECTOR2I aStart, VECTOR2I aMid, VECTOR2I aEnd,
586  const VECTOR2I aCursor ) const
587 {
588  VECTOR2D startLine = aStart - aCenter;
589  VECTOR2D endLine = aEnd - aCenter;
590  double newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() );
591 
592  bool clockwise;
593  bool movingStart;
594  bool arcValid = true;
595 
596  VECTOR2I *p1, *p2, *p3;
597  // p1 does not move, p2 does.
598 
599  if( aStart != aArc->GetArcStart() )
600  {
601  aStart = aCursor;
602  p1 = &aEnd;
603  p2 = &aStart;
604  p3 = &aMid;
605  movingStart = true;
606  }
607  else if( aEnd != aArc->GetArcEnd() )
608  {
609  aEnd = aCursor;
610  p1 = &aStart;
611  p2 = &aEnd;
612  p3 = &aMid;
613  movingStart = false;
614  }
615  else
616  {
617  return;
618  }
619 
620  VECTOR2D v1, v2, v3, v4;
621 
622  // Move the coordinate system
623  v1 = *p1 - aCenter;
624  v2 = *p2 - aCenter;
625  v3 = *p3 - aCenter;
626 
627  VECTOR2D u1, u2, u3;
628 
629  // A point cannot be both the center and on the arc.
630  if( ( v1.EuclideanNorm() == 0 ) || ( v2.EuclideanNorm() == 0 ) )
631  return;
632 
633  u1 = v1 / v1.EuclideanNorm();
634  u2 = v3 - ( u1.x * v3.x + u1.y * v3.y ) * u1;
635  u2 = u2 / u2.EuclideanNorm();
636 
637  // [ u1, u3 ] is a base centered on the circle with:
638  // u1 : unit vector toward the point that does not move
639  // u2 : unit vector toward the mid point.
640 
641  // Get vectors v1, and v2 in that coordinate system.
642 
643  double det = u1.x * u2.y - u2.x * u1.y;
644 
645  // u1 and u2 are unit vectors, and perpendicular.
646  // det should not be 0. In case it is, do not change the arc.
647  if( det == 0 )
648  return;
649 
650  double tmpx = v1.x * u2.y - v1.y * u2.x;
651  double tmpy = -v1.x * u1.y + v1.y * u1.x;
652  v1.x = tmpx;
653  v1.y = tmpy;
654  v1 = v1 / det;
655 
656  tmpx = v2.x * u2.y - v2.y * u2.x;
657  tmpy = -v2.x * u1.y + v2.y * u1.x;
658  v2.x = tmpx;
659  v2.y = tmpy;
660  v2 = v2 / det;
661 
662  double R = v1.EuclideanNorm();
663  bool transformCircle = false;
664 
665  /* p2
666  * X***
667  * ** <---- This is the arc
668  * y ^ **
669  * | R *
670  * | <-----------> *
671  * x------x------>--------x p1
672  * C' <----> C x
673  * delta
674  *
675  * p1 does not move, and the tangent at p1 remains the same.
676  * => The new center, C', will be on the C-p1 axis.
677  * p2 moves
678  *
679  * The radius of the new circle is delta + R
680  *
681  * || C' p2 || = || C' P1 ||
682  * is the same as :
683  * ( delta + p2.x ) ^ 2 + p2.y ^ 2 = ( R + delta ) ^ 2
684  *
685  * delta = ( R^2 - p2.x ^ 2 - p2.y ^2 ) / ( 2 * p2.x - 2 * R )
686  *
687  * We can use this equation for any point p2 with p2.x < R
688  */
689 
690  if( v2.x == R )
691  {
692  // Straight line, do nothing
693  }
694  else
695  {
696  if( v2.x > R )
697  {
698  // If we need to invert the curvature.
699  // We modify the input so we can use the same equation
700  transformCircle = true;
701  v2.x = 2 * R - v2.x;
702  }
703  // We can keep the tangent constraint.
704  double delta = ( R * R - v2.x * v2.x - v2.y * v2.y ) / ( 2 * v2.x - 2 * R );
705 
706  // This is just to limit the radius, so nothing overflows later when drawing.
707  if( abs( v2.y / ( R - v2.x ) ) > ADVANCED_CFG::GetCfg().m_DrawArcCenterMaxAngle )
708  {
709  arcValid = false;
710  }
711  // Never recorded a problem, but still checking.
712  if( !std::isfinite( delta ) )
713  {
714  arcValid = false;
715  }
716  // v4 is the new center
717  v4 = ( !transformCircle ) ? VECTOR2D( -delta, 0 ) : VECTOR2D( 2 * R + delta, 0 );
718 
719  clockwise = aArc->GetAngle() > 0;
720 
721  if( transformCircle )
722  clockwise = !clockwise;
723 
724  tmpx = v4.x * u1.x + v4.y * u2.x;
725  tmpy = v4.x * u1.y + v4.y * u2.y;
726  v4.x = tmpx;
727  v4.y = tmpy;
728 
729  aCenter = v4 + aCenter;
730 
731  startLine = aStart - aCenter;
732  endLine = aEnd - aCenter;
733  newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() );
734 
735  if( clockwise && newAngle < 0.0 )
736  newAngle += 3600.0;
737  else if( !clockwise && newAngle > 0.0 )
738  newAngle -= 3600.0;
739 
740  if( arcValid )
741  {
742  aArc->SetAngle( newAngle, false );
743  aArc->SetCenter( wxPoint( aCenter.x, aCenter.y ) );
744 
745  if( movingStart )
746  aArc->SetArcStart( wxPoint( aStart.x, aStart.y ) );
747  else
748  aArc->SetArcEnd( wxPoint( aEnd.x, aEnd.y ) );
749  }
750  }
751 }
752 
753 
767 static void pinEditedCorner( int aEditedPointIndex, int aMinWidth, int aMinHeight,
768  VECTOR2I& aTopLeft, VECTOR2I& aTopRight, VECTOR2I& aBotLeft,
769  VECTOR2I& aBotRight, VECTOR2I aHole, VECTOR2I aHoleSize )
770 {
771  switch( aEditedPointIndex )
772  {
773  case RECT_TOP_LEFT:
774  if( aHoleSize.x )
775  {
776  // pin edited point to the top/left of the hole
777  aTopLeft.x = std::min( aTopLeft.x, aHole.x - aHoleSize.x / 2 - aMinWidth );
778  aTopLeft.y = std::min( aTopLeft.y, aHole.y - aHoleSize.y / 2 - aMinHeight );
779  }
780  else
781  {
782  // pin edited point within opposite corner
783  aTopLeft.x = std::min( aTopLeft.x, aBotRight.x - aMinWidth );
784  aTopLeft.y = std::min( aTopLeft.y, aBotRight.y - aMinHeight );
785  }
786 
787  // push edited point edges to adjacent corners
788  aTopRight.y = aTopLeft.y;
789  aBotLeft.x = aTopLeft.x;
790 
791  break;
792 
793  case RECT_TOP_RIGHT:
794  if( aHoleSize.x )
795  {
796  // pin edited point to the top/right of the hole
797  aTopRight.x = std::max( aTopRight.x, aHole.x + aHoleSize.x / 2 + aMinWidth );
798  aTopRight.y = std::min( aTopRight.y, aHole.y - aHoleSize.y / 2 - aMinHeight );
799  }
800  else
801  {
802  // pin edited point within opposite corner
803  aTopRight.x = std::max( aTopRight.x, aBotLeft.x + aMinWidth );
804  aTopRight.y = std::min( aTopRight.y, aBotLeft.y - aMinHeight );
805  }
806 
807  // push edited point edges to adjacent corners
808  aTopLeft.y = aTopRight.y;
809  aBotRight.x = aTopRight.x;
810 
811  break;
812 
813  case RECT_BOT_LEFT:
814  if( aHoleSize.x )
815  {
816  // pin edited point to the bottom/left of the hole
817  aBotLeft.x = std::min( aBotLeft.x, aHole.x - aHoleSize.x / 2 - aMinWidth );
818  aBotLeft.y = std::max( aBotLeft.y, aHole.y + aHoleSize.y / 2 + aMinHeight );
819  }
820  else
821  {
822  // pin edited point within opposite corner
823  aBotLeft.x = std::min( aBotLeft.x, aTopRight.x - aMinWidth );
824  aBotLeft.y = std::max( aBotLeft.y, aTopRight.y + aMinHeight );
825  }
826 
827  // push edited point edges to adjacent corners
828  aBotRight.y = aBotLeft.y;
829  aTopLeft.x = aBotLeft.x;
830 
831  break;
832 
833  case RECT_BOT_RIGHT:
834  if( aHoleSize.x )
835  {
836  // pin edited point to the bottom/right of the hole
837  aBotRight.x = std::max( aBotRight.x, aHole.x + aHoleSize.x / 2 + aMinWidth );
838  aBotRight.y = std::max( aBotRight.y, aHole.y + aHoleSize.y / 2 + aMinHeight );
839  }
840  else
841  {
842  // pin edited point within opposite corner
843  aBotRight.x = std::max( aBotRight.x, aTopLeft.x + aMinWidth );
844  aBotRight.y = std::max( aBotRight.y, aTopLeft.y + aMinHeight );
845  }
846 
847  // push edited point edges to adjacent corners
848  aBotLeft.y = aBotRight.y;
849  aTopRight.x = aBotRight.x;
850 
851  break;
852  }
853 }
854 
855 
857  VECTOR2I aStart, VECTOR2I aMid, VECTOR2I aEnd,
858  const VECTOR2I aCursor ) const
859 {
860  bool clockwise;
861  bool movingStart;
862 
863  VECTOR2I *p1, *p2;
864  VECTOR2I target;
865 
866  // p1 does not move, p2 does.
867 
868  if( aStart != aArc->GetArcStart() )
869  {
870  p1 = &aEnd;
871  p2 = &aStart;
872  movingStart = true;
873  }
874  else
875  {
876  p1 = &aStart;
877  p2 = &aEnd;
878  movingStart = false;
879  }
880 
881  target = *p2 - aCenter;
882 
883  double sqRadius = ( *p1 - aCenter ).SquaredEuclideanNorm();
884 
885  *p1 = *p1 - aCenter;
886  *p2 = *p2 - aCenter;
887 
888  // Circle : x^2 + y^2 = R ^ 2
889  // In this coordinate system, the angular position of the cursor is (r, theta)
890  // The line coming from the center of the circle is y = start.y / start.x * x
891  // The intersection fulfills : x^2 = R^2 / ( 1 + ( start.y / start.x ) ^ 2 )
892 
893  if( target.x == 0 )
894  {
895  p2->x = 0;
896  p2->y = ( target.y > 0 ) ? sqrt( sqRadius ) : -sqrt( sqRadius );
897  }
898  else
899  {
900  double tan = target.y / static_cast<double>( target.x );
901  // The divider is always greater than 1 ( cannot be 0 )
902  double tmp = sqrt( sqRadius / ( 1.0 + tan * tan ) );
903  // Move to the correct quadrant
904  tmp = target.x > 0 ? tmp : -tmp;
905  p2->y = target.y / static_cast<double>( target.x ) * tmp;
906  p2->x = tmp;
907  }
908 
909  *p1 = *p1 + aCenter;
910  *p2 = *p2 + aCenter;
911 
912  clockwise = aArc->GetAngle() > 0;
913 
914  VECTOR2D startLine = aStart - aCenter;
915  VECTOR2D endLine = aEnd - aCenter;
916  double newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() );
917 
918  if( clockwise && newAngle < 0.0 )
919  newAngle += 3600.0;
920  else if( !clockwise && newAngle > 0.0 )
921  newAngle -= 3600.0;
922 
923  aArc->SetAngle( newAngle, false );
924  aArc->SetCenter( (wxPoint) aCenter );
925 
926  if( movingStart )
927  aArc->SetArcStart( (wxPoint) aStart );
928  else
929  aArc->SetArcEnd( (wxPoint) aEnd );
930 }
931 
932 
934  VECTOR2I aMid, VECTOR2I aEnd,
935  const VECTOR2I aCursor ) const
936 {
937  // Now, update the edit point position
938  // Express the point in a cercle-centered coordinate system.
939  aStart = aStart - aCenter;
940  aEnd = aEnd - aCenter;
941 
942  double sqRadius = ( aCursor - aCenter ).SquaredEuclideanNorm();
943 
944  // Special case, because the tangent would lead to +/- infinity
945  if( aStart.x == 0 )
946  {
947  aStart.y = aCursor.y > 0 ? sqrt( sqRadius ) : -sqrt( sqRadius );
948  }
949  else
950  {
951  // Circle : x^2 + y^2 = R ^ 2
952  // In this coordinate system, the angular position of the cursor is (r, theta)
953  // The line coming from the center of the circle is y = start.y / start.x * x
954  // The intersection fulfills : x^2 = R^2 / ( 1 + ( start.y / start.x ) ^ 2 )
955 
956  double tan = aStart.y / static_cast<double>( aStart.x );
957  double tmp = sqrt( sqRadius / ( 1.0 + tan * tan ) );
958  // Move to the correct quadrant
959  tmp = aStart.x > 0 ? tmp : -tmp;
960  aStart.y = aStart.y / static_cast<double>( aStart.x ) * tmp;
961  aStart.x = tmp;
962  }
963 
964  // Special case, because the tangent would lead to +/- infinity
965  if( aEnd.x == 0 )
966  {
967  aEnd.y = aMid.y > 0 ? sqrt( sqRadius ) : -sqrt( sqRadius );
968  }
969  else
970  {
971  // Circle : x^2 + y^2 = R ^ 2
972  // In this coordinate system, the angular position of the cursor is (r, theta)
973  // The line coming from the center of the circle is y = start.y / start.x * x
974  // The intersection fulfills : x^2 = R^2 / ( 1 + ( start.y / start.x ) ^ 2 )
975 
976  double tan = aEnd.y / static_cast<double>( aEnd.x );
977  double tmp = sqrt( sqRadius / ( 1.0 + tan * tan ) );
978  // Move to the correct quadrant
979  tmp = aEnd.x > 0 ? tmp : -tmp;
980  aEnd.y = aEnd.y / static_cast<double>( aEnd.x ) * tmp;
981  aEnd.x = tmp;
982  }
983 
984  aStart = aStart + aCenter;
985  aEnd = aEnd + aCenter;
986 
987  aArc->SetArcStart( (wxPoint) aStart );
988  aArc->SetArcEnd( (wxPoint) aEnd );
989 }
990 
991 
993  const VECTOR2I aCursor ) const
994 {
995  // Let 'm' be the middle point of the chord between the start and end points
996  VECTOR2I m = ( aStart + aEnd ) / 2;
997 
998  // Legal midpoints lie on a vector starting just off the chord midpoint and extending out
999  // past the existing midpoint. We do not allow arc inflection while point editing.
1000  const int JUST_OFF = ( aStart - aEnd ).EuclideanNorm() / 100;
1001  VECTOR2I v = (VECTOR2I) aArc->GetArcMid() - m;
1002  SEG legal( m + v.Resize( JUST_OFF ), m + v.Resize( INT_MAX / 2 ) );
1003  VECTOR2I mid = legal.NearestPoint( aCursor );
1004 
1005  aArc->SetArcGeometry( (wxPoint) aStart, (wxPoint) mid, (wxPoint) aEnd );
1006 }
1007 
1008 
1010 {
1011  EDA_ITEM* item = m_editPoints->GetParent();
1012 
1013  if( !item )
1014  return;
1015 
1016  switch( item->Type() )
1017  {
1018  case PCB_SHAPE_T:
1019  case PCB_FP_SHAPE_T:
1020  {
1021  PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
1022 
1023  switch( shape->GetShape() )
1024  {
1026  if( isModified( m_editPoints->Point( SEG_START ) ) )
1027  {
1028  shape->SetStart( wxPoint( m_editPoints->Point( SEG_START ).GetPosition().x,
1029  m_editPoints->Point( SEG_START ).GetPosition().y ) );
1030  }
1031  else if( isModified( m_editPoints->Point( SEG_END ) ) )
1032  {
1033  shape->SetEnd( wxPoint( m_editPoints->Point( SEG_END ).GetPosition().x,
1034  m_editPoints->Point( SEG_END ).GetPosition().y ) );
1035  }
1036 
1037  break;
1038 
1039  case PCB_SHAPE_TYPE::RECT:
1040  {
1041  if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) ) )
1042  {
1043  shape->SetStart((wxPoint) m_editPoints->Point( RECT_TOP_LEFT ).GetPosition() );
1044  }
1045  else if( isModified( m_editPoints->Point( RECT_TOP_RIGHT ) ) )
1046  {
1047  shape->SetStartY( m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition().y );
1048  shape->SetEndX( m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition().x );
1049  }
1050  else if( isModified( m_editPoints->Point( RECT_BOT_RIGHT ) ) )
1051  {
1052  shape->SetEnd((wxPoint) m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition() );
1053  }
1054  else if( isModified( m_editPoints->Point( RECT_BOT_LEFT ) ) )
1055  {
1056  shape->SetStartX( m_editPoints->Point( RECT_BOT_LEFT ).GetPosition().x );
1057  shape->SetEndY( m_editPoints->Point( RECT_BOT_LEFT ).GetPosition().y );
1058  }
1059  else if( isModified( m_editPoints->Line( RECT_TOP ) ) )
1060  {
1061  shape->SetStartY( m_editPoints->Point( RECT_TOP_LEFT ).GetPosition().y );
1062  }
1063  else if( isModified( m_editPoints->Line( RECT_LEFT ) ) )
1064  {
1065  shape->SetStartX( m_editPoints->Point( RECT_BOT_LEFT ).GetPosition().x );
1066  }
1067  else if( isModified( m_editPoints->Line( RECT_BOT ) ) )
1068  {
1069  shape->SetEndY( m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition().y );
1070  }
1071  else if( isModified( m_editPoints->Line( RECT_RIGHT ) ) )
1072  {
1073  shape->SetEndX( m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition().x );
1074  }
1075 
1076  for( unsigned i = 0; i < m_editPoints->LinesSize(); ++i )
1077  {
1078  if( !isModified( m_editPoints->Line( i ) ) )
1079  m_editPoints->Line( i ).SetConstraint( new EC_PERPLINE( m_editPoints->Line( i ) ) );
1080  }
1081  }
1082  break;
1083 
1084  case PCB_SHAPE_TYPE::ARC:
1085  {
1086  VECTOR2I center = m_editPoints->Point( ARC_CENTER ).GetPosition();
1087  VECTOR2I mid = m_editPoints->Point( ARC_MID ).GetPosition();
1088  VECTOR2I start = m_editPoints->Point( ARC_START ).GetPosition();
1089  VECTOR2I end = m_editPoints->Point( ARC_END ).GetPosition();
1090 
1091  if( isModified( m_editPoints->Point( ARC_CENTER ) ) )
1092  {
1093  wxPoint moveVector = wxPoint( center.x, center.y ) - shape->GetCenter();
1094  shape->Move( moveVector );
1095  }
1096  else if( isModified( m_editPoints->Point( ARC_MID ) ) )
1097  {
1098  const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition( false );
1099 
1100  if( m_altEditMethod )
1101  editArcMidKeepCenter( shape, center, start, mid, end, cursorPos );
1102  else
1103  editArcMidKeepEndpoints( shape, start, end, cursorPos );
1104  }
1105  else if( isModified( m_editPoints->Point( ARC_START ) )
1106  || isModified( m_editPoints->Point( ARC_END ) ) )
1107  {
1108  const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
1109 
1110  if( m_altEditMethod )
1111  editArcEndpointKeepCenter( shape, center, start, mid, end, cursorPos );
1112  else
1113  editArcEndpointKeepTangent( shape, center, start, mid, end, cursorPos );
1114  }
1115  }
1116  break;
1117 
1119  {
1120  const VECTOR2I& center = m_editPoints->Point( CIRC_CENTER ).GetPosition();
1121  const VECTOR2I& end = m_editPoints->Point( CIRC_END ).GetPosition();
1122 
1123  if( isModified( m_editPoints->Point( CIRC_CENTER ) ) )
1124  {
1125  wxPoint moveVector = wxPoint( center.x, center.y ) - shape->GetCenter();
1126  shape->Move( moveVector );
1127  }
1128  else
1129  {
1130  shape->SetEnd( wxPoint( end.x, end.y ) );
1131  }
1132  }
1133  break;
1134 
1136  {
1137  SHAPE_POLY_SET& outline = shape->GetPolyShape();
1138 
1139  for( int i = 0; i < outline.TotalVertices(); ++i )
1140  outline.SetVertex( i, m_editPoints->Point( i ).GetPosition() );
1141 
1142  for( unsigned i = 0; i < m_editPoints->LinesSize(); ++i )
1143  {
1144  if( !isModified( m_editPoints->Line( i ) ) )
1145  m_editPoints->Line( i ).SetConstraint( new EC_PERPLINE( m_editPoints->Line( i ) ) );
1146  }
1147 
1148  validatePolygon( outline );
1149  }
1150  break;
1151 
1152  case PCB_SHAPE_TYPE::CURVE:
1153  if( isModified( m_editPoints->Point( BEZIER_CURVE_START ) ) )
1154  shape->SetStart( (wxPoint) m_editPoints->Point( BEZIER_CURVE_START ).GetPosition() );
1155  else if( isModified( m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT1 ) ) )
1156  shape->SetBezControl1( (wxPoint) m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT1 ).GetPosition() );
1157  else if( isModified( m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT2 ) ) )
1158  shape->SetBezControl2( (wxPoint) m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT2 ).GetPosition() );
1159  else if( isModified( m_editPoints->Point( BEZIER_CURVE_END ) ) )
1160  shape->SetEnd( (wxPoint) m_editPoints->Point( BEZIER_CURVE_END ).GetPosition() );
1161 
1162  shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() );
1163  break;
1164 
1165  default: // suppress warnings
1166  break;
1167  }
1168 
1169  // Update relative coordinates for footprint shapes
1170  if( FP_SHAPE* fpShape = dyn_cast<FP_SHAPE*>( item ) )
1171  fpShape->SetLocalCoord();
1172 
1173  break;
1174  }
1175 
1176  case PCB_PAD_T:
1177  {
1178  PAD* pad = static_cast<PAD*>( item );
1179 
1180  switch( pad->GetShape() )
1181  {
1182  case PAD_SHAPE::CIRCLE:
1183  {
1184  wxPoint center = (wxPoint) m_editPoints->Point( CIRC_CENTER ).GetPosition();
1185  wxPoint end = (wxPoint) m_editPoints->Point( CIRC_END ).GetPosition();
1186 
1187  if( isModified( m_editPoints->Point( CIRC_CENTER ) ) )
1188  {
1189  wxPoint moveVector = center - pad->ShapePos();
1190  pad->SetOffset( pad->GetOffset() + moveVector );
1191  }
1192  else
1193  {
1194  int diameter = (int) EuclideanNorm( end - center ) * 2;
1195  pad->SetSize( wxSize( diameter, diameter ) );
1196  }
1197  }
1198  break;
1199 
1200  case PAD_SHAPE::OVAL:
1201  case PAD_SHAPE::TRAPEZOID:
1202  case PAD_SHAPE::RECT:
1203  case PAD_SHAPE::ROUNDRECT:
1205  {
1206  VECTOR2I topLeft = m_editPoints->Point( RECT_TOP_LEFT ).GetPosition();
1207  VECTOR2I topRight = m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition();
1208  VECTOR2I botLeft = m_editPoints->Point( RECT_BOT_LEFT ).GetPosition();
1209  VECTOR2I botRight = m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition();
1210 
1211  pinEditedCorner( getEditedPointIndex(), Mils2iu( 1 ), Mils2iu( 1 ), topLeft, topRight,
1212  botLeft, botRight,pad->GetPosition(), pad->GetDrillSize() );
1213 
1214  if( ( pad->GetOffset().x || pad->GetOffset().y )
1215  || ( pad->GetDrillSize().x && pad->GetDrillSize().y ) )
1216  {
1217  // Keep hole pinned at the current location; adjust the pad around the hole
1218 
1219  wxPoint center = pad->GetPosition();
1220  int dist[4];
1221 
1222  if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) )
1223  || isModified( m_editPoints->Point( RECT_BOT_RIGHT ) ) )
1224  {
1225  dist[0] = center.x - topLeft.x;
1226  dist[1] = center.y - topLeft.y;
1227  dist[2] = botRight.x - center.x;
1228  dist[3] = botRight.y - center.y;
1229  }
1230  else
1231  {
1232  dist[0] = center.x - botLeft.x;
1233  dist[1] = center.y - topRight.y;
1234  dist[2] = topRight.x - center.x;
1235  dist[3] = botLeft.y - center.y;
1236  }
1237 
1238  wxSize padSize( dist[0] + dist[2], dist[1] + dist[3] );
1239  wxPoint deltaOffset( padSize.x / 2 - dist[2], padSize.y / 2 - dist[3] );
1240 
1241  if( pad->GetOrientation() == 900 || pad->GetOrientation() == 2700 )
1242  std::swap( padSize.x, padSize.y );
1243 
1244  RotatePoint( &deltaOffset, -pad->GetOrientation() );
1245 
1246  pad->SetSize( padSize );
1247  pad->SetOffset( -deltaOffset );
1248  }
1249  else
1250  {
1251  // Keep pad position at the center of the pad shape
1252 
1253  int left, top, right, bottom;
1254 
1255  if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) )
1256  || isModified( m_editPoints->Point( RECT_BOT_RIGHT ) ) )
1257  {
1258  left = topLeft.x;
1259  top = topLeft.y;
1260  right = botRight.x;
1261  bottom = botRight.y;
1262  }
1263  else
1264  {
1265  left = botLeft.x;
1266  top = topRight.y;
1267  right = topRight.x;
1268  bottom = botLeft.y;
1269  }
1270 
1271  wxSize padSize( abs( right - left ), abs( bottom - top ) );
1272 
1273  if( pad->GetOrientation() == 900 || pad->GetOrientation() == 2700 )
1274  std::swap( padSize.x, padSize.y );
1275 
1276  pad->SetSize( padSize );
1277  pad->SetPosition( wxPoint( ( left + right ) / 2, ( top + bottom ) / 2 ) );
1278  }
1279  }
1280  break;
1281 
1282  default: // suppress warnings
1283  break;
1284  }
1285  }
1286  break;
1287 
1288  case PCB_FP_ZONE_T:
1289  case PCB_ZONE_T:
1290  {
1291  ZONE* zone = static_cast<ZONE*>( item );
1292  zone->UnFill();
1293  SHAPE_POLY_SET& outline = *zone->Outline();
1294 
1295  for( int i = 0; i < outline.TotalVertices(); ++i )
1296  {
1297  if( outline.CVertex( i ) != m_editPoints->Point( i ).GetPosition() )
1298  zone->SetNeedRefill( true );
1299 
1300  outline.SetVertex( i, m_editPoints->Point( i ).GetPosition() );
1301  }
1302 
1303  for( unsigned i = 0; i < m_editPoints->LinesSize(); ++i )
1304  {
1305  if( !isModified( m_editPoints->Line( i ) ) )
1306  m_editPoints->Line( i ).SetConstraint( new EC_PERPLINE( m_editPoints->Line( i ) ) );
1307  }
1308 
1309  validatePolygon( outline );
1310  zone->HatchBorder();
1311  // TODO Refill zone when KiCad supports auto re-fill
1312  break;
1313  }
1314 
1315  case PCB_DIM_ALIGNED_T:
1316  {
1317  PCB_DIM_ALIGNED* dimension = static_cast<PCB_DIM_ALIGNED*>( item );
1318 
1319  // Check which point is currently modified and updated dimension's points respectively
1320  if( isModified( m_editPoints->Point( DIM_CROSSBARSTART ) ) )
1321  {
1322  VECTOR2D featureLine( m_editedPoint->GetPosition() - dimension->GetStart() );
1323  VECTOR2D crossBar( dimension->GetEnd() - dimension->GetStart() );
1324 
1325  if( featureLine.Cross( crossBar ) > 0 )
1326  dimension->SetHeight( -featureLine.EuclideanNorm() );
1327  else
1328  dimension->SetHeight( featureLine.EuclideanNorm() );
1329 
1330  dimension->Update();
1331  }
1332  else if( isModified( m_editPoints->Point( DIM_CROSSBAREND ) ) )
1333  {
1334  VECTOR2D featureLine( m_editedPoint->GetPosition() - dimension->GetEnd() );
1335  VECTOR2D crossBar( dimension->GetEnd() - dimension->GetStart() );
1336 
1337  if( featureLine.Cross( crossBar ) > 0 )
1338  dimension->SetHeight( -featureLine.EuclideanNorm() );
1339  else
1340  dimension->SetHeight( featureLine.EuclideanNorm() );
1341 
1342  dimension->Update();
1343  }
1344  else if( isModified( m_editPoints->Point( DIM_START ) ) )
1345  {
1346  dimension->SetStart( wxPoint( m_editedPoint->GetPosition().x,
1347  m_editedPoint->GetPosition().y ) );
1348  dimension->Update();
1349 
1350  m_editPoints->Point( DIM_CROSSBARSTART ).SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBARSTART ),
1351  m_editPoints->Point( DIM_START ) ) );
1352  m_editPoints->Point( DIM_CROSSBAREND ).SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBAREND ),
1353  m_editPoints->Point( DIM_END ) ) );
1354  }
1355  else if( isModified( m_editPoints->Point( DIM_END ) ) )
1356  {
1357  dimension->SetEnd( wxPoint( m_editedPoint->GetPosition().x,
1358  m_editedPoint->GetPosition().y ) );
1359  dimension->Update();
1360 
1361  m_editPoints->Point( DIM_CROSSBARSTART ).SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBARSTART ),
1362  m_editPoints->Point( DIM_START ) ) );
1363  m_editPoints->Point( DIM_CROSSBAREND ).SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBAREND ),
1364  m_editPoints->Point( DIM_END ) ) );
1365  }
1366  else if( isModified( m_editPoints->Point(DIM_TEXT ) ) )
1367  {
1368  // Force manual mode if we weren't already in it
1370  dimension->Text().SetPosition( wxPoint( m_editedPoint->GetPosition() ) );
1371  dimension->Update();
1372  }
1373 
1374  break;
1375  }
1376 
1377  case PCB_DIM_ORTHOGONAL_T:
1378  {
1379  PCB_DIM_ORTHOGONAL* dimension = static_cast<PCB_DIM_ORTHOGONAL*>( item );
1380 
1381  if( isModified( m_editPoints->Point( DIM_CROSSBARSTART ) ) ||
1382  isModified( m_editPoints->Point( DIM_CROSSBAREND ) ) )
1383  {
1384  BOX2I bounds( dimension->GetStart(), dimension->GetEnd() - dimension->GetStart() );
1385 
1386  const VECTOR2I& cursorPos = m_editedPoint->GetPosition();
1387 
1388  // Find vector from nearest dimension point to edit position
1389  VECTOR2I directionA( cursorPos - dimension->GetStart() );
1390  VECTOR2I directionB( cursorPos - dimension->GetEnd() );
1391  VECTOR2I direction = ( directionA < directionB ) ? directionA : directionB;
1392 
1393  bool vert;
1394  VECTOR2D featureLine( cursorPos - dimension->GetStart() );
1395 
1396  // Only change the orientation when we move outside the bounds
1397  if( !bounds.Contains( cursorPos ) )
1398  {
1399  // If the dimension is horizontal or vertical, set correct orientation
1400  // otherwise, test if we're left/right of the bounding box or above/below it
1401  if( bounds.GetWidth() == 0 )
1402  {
1403  vert = true;
1404  }
1405  else if( bounds.GetHeight() == 0 )
1406  {
1407  vert = false;
1408  }
1409  else if( cursorPos.x > bounds.GetLeft() && cursorPos.x < bounds.GetRight() )
1410  {
1411  vert = false;
1412  }
1413  else if( cursorPos.y > bounds.GetTop() && cursorPos.y < bounds.GetBottom() )
1414  {
1415  vert = true;
1416  }
1417  else
1418  {
1419  vert = std::abs( direction.y ) < std::abs( direction.x );
1420  }
1423  }
1424  else
1425  {
1426  vert = dimension->GetOrientation() == PCB_DIM_ORTHOGONAL::DIR::VERTICAL;
1427  }
1428 
1429  dimension->SetHeight( vert ? featureLine.x : featureLine.y );
1430  }
1431  else if( isModified( m_editPoints->Point( DIM_START ) ) )
1432  {
1433  dimension->SetStart( wxPoint( m_editedPoint->GetPosition().x,
1434  m_editedPoint->GetPosition().y ) );
1435  }
1436  else if( isModified( m_editPoints->Point( DIM_END ) ) )
1437  {
1438  dimension->SetEnd( wxPoint( m_editedPoint->GetPosition().x,
1439  m_editedPoint->GetPosition().y ) );
1440  }
1441  else if( isModified( m_editPoints->Point(DIM_TEXT ) ) )
1442  {
1443  // Force manual mode if we weren't already in it
1445  dimension->Text().SetPosition( wxPoint( m_editedPoint->GetPosition() ) );
1446  }
1447 
1448  dimension->Update();
1449 
1450  break;
1451  }
1452 
1453  case PCB_DIM_CENTER_T:
1454  {
1455  PCB_DIM_CENTER* dimension = static_cast<PCB_DIM_CENTER*>( item );
1456 
1457  if( isModified( m_editPoints->Point( DIM_START ) ) )
1458  {
1459  dimension->SetStart( wxPoint( m_editedPoint->GetPosition().x,
1460  m_editedPoint->GetPosition().y ) );
1461  }
1462  else if( isModified( m_editPoints->Point( DIM_END ) ) )
1463  {
1464  dimension->SetEnd( wxPoint( m_editedPoint->GetPosition().x,
1465  m_editedPoint->GetPosition().y ) );
1466  }
1467 
1468  dimension->Update();
1469 
1470  break;
1471  }
1472 
1473  case PCB_DIM_LEADER_T:
1474  {
1475  PCB_DIM_LEADER* dimension = static_cast<PCB_DIM_LEADER*>( item );
1476 
1477  if( isModified( m_editPoints->Point( DIM_START ) ) )
1478  {
1479  dimension->SetStart( wxPoint( m_editedPoint->GetPosition().x,
1480  m_editedPoint->GetPosition().y ) );
1481  }
1482  else if( isModified( m_editPoints->Point( DIM_END ) ) )
1483  {
1484  wxPoint newPoint( m_editedPoint->GetPosition().x, m_editedPoint->GetPosition().y );
1485  wxPoint delta = newPoint - dimension->GetEnd();
1486 
1487  dimension->SetEnd( newPoint );
1488  dimension->Text().SetPosition( dimension->Text().GetPosition() + delta );
1489  }
1490  else if( isModified( m_editPoints->Point( DIM_TEXT ) ) )
1491  {
1492  dimension->Text().SetPosition( wxPoint( m_editedPoint->GetPosition() ) );
1493  }
1494 
1495  dimension->Update();
1496 
1497  break;
1498  }
1499 
1500  default:
1501  break;
1502  }
1503 
1504  getView()->Update( item );
1505 }
1506 
1507 
1509 {
1510  auto item = m_editPoints->GetParent();
1511 
1512  if( !item )
1513  return;
1514 
1515  // TODO Refill edited zones when KiCad supports auto re-fill
1516 }
1517 
1518 
1520 {
1521  bool valid = !aPoly.IsSelfIntersecting();
1522 
1523  if( m_statusPopup )
1524  {
1525  if( valid )
1526  {
1527  m_statusPopup->Hide();
1528  }
1529  else
1530  {
1531  wxPoint p = wxGetMousePosition() + wxPoint( 20, 20 );
1532  m_statusPopup->Move( p );
1533  m_statusPopup->PopupFor( 1500 );
1534  }
1535  }
1536 
1537  return valid;
1538 }
1539 
1540 
1542 {
1543  if( !m_editPoints )
1544  return;
1545 
1546  EDA_ITEM* item = m_editPoints->GetParent();
1547 
1548  if( !item )
1549  return;
1550 
1551  switch( item->Type() )
1552  {
1553  case PCB_SHAPE_T:
1554  case PCB_FP_SHAPE_T:
1555  {
1556  const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( item );
1557 
1558  switch( shape->GetShape() )
1559  {
1561  m_editPoints->Point( SEG_START ).SetPosition( shape->GetStart() );
1562  m_editPoints->Point( SEG_END ).SetPosition( shape->GetEnd() );
1563  break;
1564 
1565  case PCB_SHAPE_TYPE::RECT:
1566  m_editPoints->Point( RECT_TOP_LEFT ).SetPosition( shape->GetStart() );
1567  m_editPoints->Point( RECT_TOP_RIGHT ).SetPosition( shape->GetEnd().x,
1568  shape->GetStart().y );
1569  m_editPoints->Point( RECT_BOT_RIGHT ).SetPosition( shape->GetEnd() );
1570  m_editPoints->Point( RECT_BOT_LEFT ).SetPosition( shape->GetStart().x,
1571  shape->GetEnd().y );
1572  break;
1573 
1574  case PCB_SHAPE_TYPE::ARC:
1575  m_editPoints->Point( ARC_CENTER ).SetPosition( shape->GetCenter() );
1576  m_editPoints->Point( ARC_START ).SetPosition( shape->GetArcStart() );
1577  m_editPoints->Point( ARC_MID ).SetPosition( shape->GetArcMid() );
1578  m_editPoints->Point( ARC_END ).SetPosition( shape->GetArcEnd() );
1579  break;
1580 
1582  m_editPoints->Point( CIRC_CENTER ).SetPosition( shape->GetCenter() );
1583  m_editPoints->Point( CIRC_END ).SetPosition( shape->GetEnd() );
1584  break;
1585 
1587  {
1588  const auto& points = shape->BuildPolyPointsList();
1589 
1590  if( m_editPoints->PointsSize() != (unsigned) points.size() )
1591  {
1592  getView()->Remove( m_editPoints.get() );
1593  m_editedPoint = nullptr;
1594 
1595  m_editPoints = makePoints( item );
1596 
1597  if( m_editPoints )
1598  getView()->Add( m_editPoints.get() );
1599  }
1600  else
1601  {
1602  for( unsigned i = 0; i < points.size(); i++ )
1603  m_editPoints->Point( i ).SetPosition( points[i] );
1604  }
1605  break;
1606  }
1607 
1608  case PCB_SHAPE_TYPE::CURVE:
1609  m_editPoints->Point( BEZIER_CURVE_START ).SetPosition( shape->GetStart() );
1610  m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT1 ).SetPosition( shape->GetBezControl1() );
1611  m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT2 ).SetPosition( shape->GetBezControl2() );
1612  m_editPoints->Point( BEZIER_CURVE_END ).SetPosition( shape->GetEnd() );
1613  break;
1614 
1615  default: // suppress warnings
1616  break;
1617  }
1618 
1619  break;
1620  }
1621 
1622  case PCB_PAD_T:
1623  {
1624  const PAD* pad = static_cast<const PAD*>( item );
1625  bool locked = pad->GetParent() && pad->IsLocked();
1626  wxPoint shapePos = pad->ShapePos();
1627  wxPoint halfSize( pad->GetSize().x / 2, pad->GetSize().y / 2 );
1628 
1629  switch( pad->GetShape() )
1630  {
1631  case PAD_SHAPE::CIRCLE:
1632  {
1633  int target = locked ? 0 : 2;
1634 
1635  // Careful; pad shape is mutable...
1636  if( int( m_editPoints->PointsSize() ) != target )
1637  {
1638  getView()->Remove( m_editPoints.get() );
1639  m_editedPoint = nullptr;
1640 
1641  m_editPoints = makePoints( item );
1642 
1643  if( m_editPoints )
1644  getView()->Add( m_editPoints.get() );
1645  }
1646  else if( target == 2 )
1647  {
1648  VECTOR2I vec = m_editPoints->Point( CIRC_END ).GetPosition()
1649  - m_editPoints->Point( CIRC_CENTER ).GetPosition();
1650  vec.Resize( halfSize.x );
1651 
1652  m_editPoints->Point( CIRC_CENTER ).SetPosition( shapePos );
1653  m_editPoints->Point( CIRC_END ).SetPosition( vec + shapePos );
1654  }
1655  }
1656  break;
1657 
1658  case PAD_SHAPE::OVAL:
1659  case PAD_SHAPE::TRAPEZOID:
1660  case PAD_SHAPE::RECT:
1661  case PAD_SHAPE::ROUNDRECT:
1663  {
1664  // Careful; pad shape and orientation are mutable...
1665  int target = locked || (int) pad->GetOrientation() % 900 > 0 ? 0 : 4;
1666 
1667  if( int( m_editPoints->PointsSize() ) != target )
1668  {
1669  getView()->Remove( m_editPoints.get() );
1670  m_editedPoint = nullptr;
1671 
1672  m_editPoints = makePoints( item );
1673 
1674  if( m_editPoints )
1675  getView()->Add( m_editPoints.get() );
1676  }
1677  else if( target == 4 )
1678  {
1679  if( pad->GetOrientation() == 900 || pad->GetOrientation() == 2700 )
1680  std::swap( halfSize.x, halfSize.y );
1681 
1682  m_editPoints->Point( RECT_TOP_LEFT ).SetPosition( shapePos - halfSize );
1683  m_editPoints->Point( RECT_TOP_RIGHT ).SetPosition( wxPoint( shapePos.x + halfSize.x,
1684  shapePos.y - halfSize.y ) );
1685  m_editPoints->Point( RECT_BOT_RIGHT ).SetPosition( shapePos + halfSize );
1686  m_editPoints->Point( RECT_BOT_LEFT ).SetPosition( wxPoint( shapePos.x - halfSize.x,
1687  shapePos.y + halfSize.y ) );
1688  }
1689  }
1690  break;
1691 
1692  default: // suppress warnings
1693  break;
1694  }
1695  }
1696  break;
1697 
1698  case PCB_FP_ZONE_T:
1699  case PCB_ZONE_T:
1700  {
1701  ZONE* zone = static_cast<ZONE*>( item );
1702  const SHAPE_POLY_SET* outline = zone->Outline();
1703 
1704  if( m_editPoints->PointsSize() != (unsigned) outline->TotalVertices() )
1705  {
1706  getView()->Remove( m_editPoints.get() );
1707  m_editedPoint = nullptr;
1708 
1709  m_editPoints = makePoints( item );
1710 
1711  if( m_editPoints )
1712  getView()->Add( m_editPoints.get() );
1713  }
1714  else
1715  {
1716  for( int i = 0; i < outline->TotalVertices(); ++i )
1717  m_editPoints->Point( i ).SetPosition( outline->CVertex( i ) );
1718  }
1719 
1720  break;
1721  }
1722 
1723  case PCB_DIM_ALIGNED_T:
1724  case PCB_DIM_ORTHOGONAL_T:
1725  {
1726  const PCB_DIM_ALIGNED* dimension = static_cast<const PCB_DIM_ALIGNED*>( item );
1727 
1728  m_editPoints->Point( DIM_START ).SetPosition( dimension->GetStart() );
1729  m_editPoints->Point( DIM_END ).SetPosition( dimension->GetEnd() );
1730  m_editPoints->Point( DIM_TEXT ).SetPosition( dimension->Text().GetPosition() );
1731  m_editPoints->Point( DIM_CROSSBARSTART ).SetPosition( dimension->GetCrossbarStart() );
1732  m_editPoints->Point( DIM_CROSSBAREND ).SetPosition( dimension->GetCrossbarEnd() );
1733  break;
1734  }
1735 
1736  case PCB_DIM_CENTER_T:
1737  {
1738  const PCB_DIM_CENTER* dimension = static_cast<const PCB_DIM_CENTER*>( item );
1739 
1740  m_editPoints->Point( DIM_START ).SetPosition( dimension->GetStart() );
1741  m_editPoints->Point( DIM_END ).SetPosition( dimension->GetEnd() );
1742  break;
1743  }
1744 
1745  case PCB_DIM_LEADER_T:
1746  {
1747  const PCB_DIM_LEADER* dimension = static_cast<const PCB_DIM_LEADER*>( item );
1748 
1749  m_editPoints->Point( DIM_START ).SetPosition( dimension->GetStart() );
1750  m_editPoints->Point( DIM_END ).SetPosition( dimension->GetEnd() );
1751  m_editPoints->Point( DIM_TEXT ).SetPosition( dimension->Text().GetPosition() );
1752  break;
1753  }
1754 
1755  default:
1756  break;
1757  }
1758 
1759  getView()->Update( m_editPoints.get() );
1760 }
1761 
1762 
1764 {
1766 
1767  if( aPoint )
1768  {
1770  controls->ForceCursorPosition( true, aPoint->GetPosition() );
1771  controls->ShowCursor( true );
1772  }
1773  else
1774  {
1775  if( frame()->ToolStackIsEmpty() )
1776  controls->ShowCursor( false );
1777 
1778  controls->ForceCursorPosition( false );
1779  }
1780 
1781  m_editedPoint = aPoint;
1782 }
1783 
1784 
1786 {
1787  if( aEnabled )
1788  {
1789  EDIT_LINE* line = dynamic_cast<EDIT_LINE*>( m_editedPoint );
1790  bool isPoly;
1791 
1792  switch( m_editPoints->GetParent()->Type() )
1793  {
1794  case PCB_ZONE_T:
1795  case PCB_FP_ZONE_T:
1796  isPoly = true;
1797  break;
1798 
1799  case PCB_SHAPE_T:
1800  case PCB_FP_SHAPE_T:
1801  isPoly = static_cast<PCB_SHAPE*>( m_editPoints->GetParent() )->GetShape()
1803  break;
1804 
1805  default:
1806  isPoly = false;
1807  break;
1808  }
1809 
1810  if( line && isPoly )
1811  {
1812  EC_CONVERGING* altConstraint = new EC_CONVERGING( *line, *m_editPoints );
1813  m_altConstraint.reset( (EDIT_CONSTRAINT<EDIT_POINT>*) altConstraint );
1814  }
1815  else
1816  {
1817  // Find a proper constraining point for 45 degrees mode
1820  }
1821  }
1822  else
1823  {
1824  m_altConstraint.reset();
1825  }
1826 }
1827 
1828 
1830 {
1831  EDA_ITEM* item = m_editPoints->GetParent();
1832 
1833  switch( item->Type() )
1834  {
1835  case PCB_SHAPE_T:
1836  case PCB_FP_SHAPE_T:
1837  switch( static_cast<const PCB_SHAPE*>( item )->GetShape() )
1838  {
1840  return *( m_editPoints->Next( *m_editedPoint ) ); // select the other end of line
1841 
1842  case PCB_SHAPE_TYPE::ARC:
1844  return m_editPoints->Point( CIRC_CENTER );
1845 
1846  default: // suppress warnings
1847  break;
1848  }
1849 
1850  break;
1851 
1852  case PCB_DIM_ALIGNED_T:
1853  {
1854  // Constraint for crossbar
1855  if( isModified( m_editPoints->Point( DIM_START ) ) )
1856  return m_editPoints->Point( DIM_END );
1857 
1858  else if( isModified( m_editPoints->Point( DIM_END ) ) )
1859  return m_editPoints->Point( DIM_START );
1860 
1861  else
1862  return EDIT_POINT( m_editedPoint->GetPosition() ); // no constraint
1863 
1864  break;
1865  }
1866 
1867  case PCB_DIM_CENTER_T:
1868  {
1869  if( isModified( m_editPoints->Point( DIM_END ) ) )
1870  return m_editPoints->Point( DIM_START );
1871 
1872  break;
1873  }
1874 
1875  default:
1876  break;
1877  }
1878 
1879  // In any other case we may align item to its original position
1880  return m_original;
1881 }
1882 
1883 
1885 {
1886  const auto type = aItem.Type();
1887 
1888  // Works only for zones and line segments
1889  if( type == PCB_ZONE_T || type == PCB_FP_ZONE_T )
1890  return true;
1891 
1892  if( type == PCB_SHAPE_T || type == PCB_FP_SHAPE_T )
1893  {
1894  const PCB_SHAPE& shape = static_cast<const PCB_SHAPE&>( aItem );
1895  return shape.GetShape() == PCB_SHAPE_TYPE::SEGMENT
1896  || shape.GetShape() == PCB_SHAPE_TYPE::POLYGON;
1897  }
1898 
1899  return false;
1900 }
1901 
1902 
1904 {
1905  if( aSelection.Size() != 1 )
1906  return false;
1907 
1908  const EDA_ITEM* item = aSelection.Front();
1909 
1910  return ( item != nullptr ) && canAddCorner( *item );
1911 }
1912 
1913 
1914 // Finds a corresponding vertex in a polygon set
1915 static std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX>
1916 findVertex( SHAPE_POLY_SET& aPolySet, const EDIT_POINT& aPoint )
1917 {
1918  for( auto it = aPolySet.IterateWithHoles(); it; ++it )
1919  {
1920  auto vertexIdx = it.GetIndex();
1921 
1922  if( aPolySet.CVertex( vertexIdx ) == aPoint.GetPosition() )
1923  return std::make_pair( true, vertexIdx );
1924  }
1925 
1926  return std::make_pair( false, SHAPE_POLY_SET::VERTEX_INDEX() );
1927 }
1928 
1929 
1931 {
1932  if( !m_editPoints || !m_editedPoint )
1933  return false;
1934 
1935  EDA_ITEM* item = m_editPoints->GetParent();
1936  SHAPE_POLY_SET* polyset = nullptr;
1937 
1938  if( !item )
1939  return false;
1940 
1941  switch( item->Type() )
1942  {
1943  case PCB_ZONE_T:
1944  case PCB_FP_ZONE_T:
1945  polyset = static_cast<ZONE*>( item )->Outline();
1946  break;
1947 
1948  case PCB_SHAPE_T:
1949  case PCB_FP_SHAPE_T:
1950  if( static_cast<PCB_SHAPE*>( item )->GetShape() == PCB_SHAPE_TYPE::POLYGON )
1951  polyset = &static_cast<PCB_SHAPE*>( item )->GetPolyShape();
1952  else
1953  return false;
1954 
1955  break;
1956 
1957  default:
1958  return false;
1959  }
1960 
1961  auto vertex = findVertex( *polyset, *m_editedPoint );
1962 
1963  if( !vertex.first )
1964  return false;
1965 
1966  const auto& vertexIdx = vertex.second;
1967 
1968  // Check if there are enough vertices so one can be removed without
1969  // degenerating the polygon.
1970  // The first condition allows one to remove all corners from holes (when
1971  // there are only 2 vertices left, a hole is removed).
1972  if( vertexIdx.m_contour == 0 && polyset->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour].PointCount() <= 3 )
1973  return false;
1974 
1975  // Remove corner does not work with lines
1976  if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
1977  return false;
1978 
1979  return m_editedPoint != NULL;
1980 }
1981 
1982 
1984 {
1985  if( !m_editPoints )
1986  return 0;
1987 
1988  EDA_ITEM* item = m_editPoints->GetParent();
1989  PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
1990  const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
1991 
1992  // called without an active edited polygon
1993  if( !item || !canAddCorner( *item ) )
1994  return 0;
1995 
1996  PCB_SHAPE* graphicItem = dynamic_cast<PCB_SHAPE*>( item );
1997  BOARD_COMMIT commit( frame );
1998 
1999  if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T
2000  || ( graphicItem && graphicItem->GetShape() == PCB_SHAPE_TYPE::POLYGON ) )
2001  {
2002  unsigned int nearestIdx = 0;
2003  unsigned int nextNearestIdx = 0;
2004  unsigned int nearestDist = INT_MAX;
2005  unsigned int firstPointInContour = 0;
2006  SHAPE_POLY_SET* zoneOutline;
2007 
2008  if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
2009  {
2010  ZONE* zone = static_cast<ZONE*>( item );
2011  zoneOutline = zone->Outline();
2012  zone->SetNeedRefill( true );
2013  }
2014  else
2015  zoneOutline = &( graphicItem->GetPolyShape() );
2016 
2017  commit.Modify( item );
2018 
2019  // Search the best outline segment to add a new corner
2020  // and therefore break this segment into two segments
2021 
2022  // Object to iterate through the corners of the outlines (main contour and its holes)
2023  SHAPE_POLY_SET::ITERATOR iterator = zoneOutline->Iterate( 0, zoneOutline->OutlineCount()-1,
2024  /* IterateHoles */ true );
2025  int curr_idx = 0;
2026 
2027  // Iterate through all the corners of the outlines and search the best segment
2028  for( ; iterator; iterator++, curr_idx++ )
2029  {
2030  int jj = curr_idx+1;
2031 
2032  if( iterator.IsEndContour() )
2033  { // We reach the last point of the current contour (main or hole)
2034  jj = firstPointInContour;
2035  firstPointInContour = curr_idx+1; // Prepare next contour analysis
2036  }
2037 
2038  SEG curr_segment( zoneOutline->CVertex( curr_idx ), zoneOutline->CVertex( jj ) );
2039 
2040  unsigned int distance = curr_segment.Distance( cursorPos );
2041 
2042  if( distance < nearestDist )
2043  {
2044  nearestDist = distance;
2045  nearestIdx = curr_idx;
2046  nextNearestIdx = jj;
2047  }
2048  }
2049 
2050  // Find the point on the closest segment
2051  auto& sideOrigin = zoneOutline->CVertex( nearestIdx );
2052  auto& sideEnd = zoneOutline->CVertex( nextNearestIdx );
2053  SEG nearestSide( sideOrigin, sideEnd );
2054  VECTOR2I nearestPoint = nearestSide.NearestPoint( cursorPos );
2055 
2056  // Do not add points that have the same coordinates as ones that already belong to polygon
2057  // instead, add a point in the middle of the side
2058  if( nearestPoint == sideOrigin || nearestPoint == sideEnd )
2059  nearestPoint = ( sideOrigin + sideEnd ) / 2;
2060 
2061  zoneOutline->InsertVertex( nextNearestIdx, nearestPoint );
2062 
2063  // We re-hatch the filled zones but not polygons
2064  if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
2065  static_cast<ZONE*>( item )->HatchBorder();
2066 
2067 
2068  commit.Push( _( "Add a zone corner" ) );
2069  }
2070 
2071  else if( graphicItem && graphicItem->GetShape() == PCB_SHAPE_TYPE::SEGMENT )
2072  {
2073  commit.Modify( graphicItem );
2074 
2075  SEG seg( graphicItem->GetStart(), graphicItem->GetEnd() );
2076  VECTOR2I nearestPoint = seg.NearestPoint( cursorPos );
2077 
2078  // Move the end of the line to the break point..
2079  graphicItem->SetEnd( wxPoint( nearestPoint.x, nearestPoint.y ) );
2080 
2081  if( graphicItem->Type() == PCB_FP_SHAPE_T )
2082  static_cast<FP_SHAPE*>( graphicItem )->SetLocalCoord();
2083 
2084  // and add another one starting from the break point
2085  PCB_SHAPE* newSegment;
2086 
2087  if( item->Type() == PCB_FP_SHAPE_T )
2088  {
2089  FP_SHAPE* edge = static_cast<FP_SHAPE*>( graphicItem );
2090  assert( edge->GetParent()->Type() == PCB_FOOTPRINT_T );
2091  newSegment = new FP_SHAPE( *edge );
2092  }
2093  else
2094  {
2095  newSegment = new PCB_SHAPE( *graphicItem );
2096  }
2097 
2098  newSegment->ClearSelected();
2099  newSegment->SetStart( wxPoint( nearestPoint.x, nearestPoint.y ) );
2100  newSegment->SetEnd( wxPoint( seg.B.x, seg.B.y ) );
2101 
2102  if( newSegment->Type() == PCB_FP_SHAPE_T )
2103  static_cast<FP_SHAPE*>( newSegment )->SetLocalCoord();
2104 
2105  commit.Add( newSegment );
2106  commit.Push( _( "Split segment" ) );
2107  }
2108 
2109  updatePoints();
2110  return 0;
2111 }
2112 
2113 
2115 {
2116  if( !m_editPoints || !m_editedPoint )
2117  return 0;
2118 
2119  EDA_ITEM* item = m_editPoints->GetParent();
2120 
2121  if( !item )
2122  return 0;
2123 
2124  SHAPE_POLY_SET* polygon = nullptr;
2125 
2126  if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
2127  {
2128  ZONE* zone = static_cast<ZONE*>( item );
2129  polygon = zone->Outline();
2130  zone->SetNeedRefill( true );
2131  }
2132  else if( item->Type() == PCB_FP_SHAPE_T || item->Type() == PCB_SHAPE_T )
2133  {
2134  PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
2135 
2136  if( shape->GetShape() == PCB_SHAPE_TYPE::POLYGON )
2137  polygon = &shape->GetPolyShape();
2138  }
2139 
2140  if( !polygon )
2141  return 0;
2142 
2143  PCB_BASE_FRAME* frame = getEditFrame<PCB_BASE_FRAME>();
2144  BOARD_COMMIT commit( frame );
2145  auto vertex = findVertex( *polygon, *m_editedPoint );
2146 
2147  if( vertex.first )
2148  {
2149  const auto& vertexIdx = vertex.second;
2150  auto& outline = polygon->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour];
2151 
2152  if( outline.PointCount() > 3 )
2153  {
2154  // the usual case: remove just the corner when there are >3 vertices
2155  commit.Modify( item );
2156  polygon->RemoveVertex( vertexIdx );
2157  validatePolygon( *polygon );
2158  }
2159  else
2160  {
2161  // either remove a hole or the polygon when there are <= 3 corners
2162  if( vertexIdx.m_contour > 0 )
2163  {
2164  // remove hole
2165  commit.Modify( item );
2166  polygon->RemoveContour( vertexIdx.m_contour );
2167  }
2168  else
2169  {
2171  commit.Remove( item );
2172  }
2173  }
2174 
2175  setEditedPoint( nullptr );
2176 
2177  commit.Push( _( "Remove a zone/polygon corner" ) );
2178 
2179  // Refresh zone hatching
2180  if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
2181  static_cast<ZONE*>( item )->HatchBorder();
2182 
2183  updatePoints();
2184  }
2185 
2186  return 0;
2187 }
2188 
2189 
2191 {
2192  updatePoints();
2193  return 0;
2194 }
2195 
2197 {
2199  return 0;
2200 }
2201 
2203 {
2213 }
double EuclideanNorm(const wxPoint &vector)
Euclidean norm of a 2D vector.
Definition: trigo.h:148
static TOOL_ACTION selectionClear
Clear the current selection.
Definition: pcb_actions.h:59
int removeCorner(const TOOL_EVENT &aEvent)
int TotalVertices() const
Delete aIdx-th polygon from the set.
void SetStartX(int x)
Definition: pcb_shape.h:129
virtual void ShowCursor(bool aEnabled)
Enable or disables display of cursor.
EDIT_CONSTRAINT for 3 segments: dragged and two adjacent ones, enforcing to keep their slopes and all...
wxPoint GetArcEnd() const
Definition: pcb_shape.cpp:376
COMMIT & Modify(EDA_ITEM *aItem)
Create an undo entry for an item that has been already modified.
Definition: commit.h:103
static const TOOL_EVENT SelectedEvent
Definition: actions.h:199
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition: typeinfo.h:100
VECTOR2I v2(1, 0)
Test suite for KiCad math code.
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition: typeinfo.h:101
int OutlineCount() const
Return the number of vertices in a given outline/hole.
void setTransitions() override
< Set up handlers for various events.
void SetCurrentCursor(KICURSOR aCursor)
Set the current cursor shape for this panel.
const wxPoint & GetCrossbarStart() const
void SetHeight(int aHeight)
Set the distance from the feature points to the crossbar line.
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.
EDIT_POINT get45DegConstrainer() const
Condition to display "Create corner" context menu entry.
static TOOL_ACTION activatePointEditor
Definition: actions.h:168
void updatePoints()
Update which point is being edited.
SHAPE_POLY_SET & GetPolyShape()
Definition: pcb_shape.h:239
virtual void SetPosition(const wxPoint &aPos) override
Definition: pcb_text.h:77
static const TOOL_EVENT UnselectedEvent
Definition: actions.h:200
const wxPoint & GetEnd() const
Return the ending point of the graphic.
Definition: pcb_shape.h:134
void updateEditedPoint(const TOOL_EVENT &aEvent)
Set the current point being edited. NULL means none.
void SetBezControl2(const wxPoint &aPoint)
Definition: pcb_shape.h:115
int addCorner(const TOOL_EVENT &aEvent)
TOOL_ACTION handlers.
This file is part of the common library.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:80
void ClearSelected()
Definition: eda_item.h:132
bool removeCornerCondition(const SELECTION &aSelection)
COMMIT & Add(EDA_ITEM *aItem)
Notify observers that aItem has been added.
Definition: commit.h:78
PCB_TEXT & Text()
void buildForPolyOutline(std::shared_ptr< EDIT_POINTS > points, const SHAPE_POLY_SET *aOutline)
void setEditedPoint(EDIT_POINT *aPoint)
PCB_SHAPE_TYPE GetShape() const
Definition: pcb_shape.h:110
constexpr KICAD_T BaseType(const KICAD_T aType)
Return the underlying type of the given type.
Definition: typeinfo.h:235
const VECTOR2I & CVertex(int aIndex, int aOutline, int aHole) const
Return the aGlobalIndex-th vertex in the poly set.
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition: typeinfo.h:102
SHAPE_POLY_SET * Outline()
Definition: zone.h:317
static TOOL_ACTION changeEditMethod
Definition: actions.h:169
virtual void Revert() override
PCB_DRAW_PANEL_GAL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
CONDITIONAL_MENU & GetMenu()
Definition: tool_menu.cpp:46
TOOL_MANAGER * m_toolMgr
Definition: tool_base.h:214
std::unique_ptr< STATUS_TEXT_POPUP > m_statusPopup
Represent a line connecting two EDIT_POINTs.
Definition: edit_points.h:215
int GetWidth() const
Definition: pcb_shape.h:97
static void pinEditedCorner(int aEditedPointIndex, int aMinWidth, int aMinHeight, VECTOR2I &aTopLeft, VECTOR2I &aTopRight, VECTOR2I &aBotLeft, VECTOR2I &aBotRight, VECTOR2I aHole, VECTOR2I aHoleSize)
Update the coordinates of 4 corners of a rectangle, according to pad constraints and the moved corner...
virtual void Remove(VIEW_ITEM *aItem)
Remove a VIEW_ITEM from the view.
Definition: view.cpp:352
EDIT_CONSTRAINT for a EDIT_LINE, that constrains the line to move perpendicular to the line itself.
const wxPoint & GetCrossbarEnd() const
CONST_ITERATOR CIterateWithHoles(int aOutline) const
TOOL_MENU & GetToolMenu()
bool IsMotion() const
Definition: tool_event.h:295
bool RunAction(const std::string &aActionName, bool aNow=false, T aParam=NULL)
Run the specified action.
Definition: tool_manager.h:143
double RAD2DECIDEG(double rad)
Definition: trigo.h:236
Structure to hold the necessary information in order to index a vertex on a SHAPE_POLY_SET object: th...
double m_DrawArcCenterMaxAngle
When drawing an arc, the angle ( center - start ) - ( start - end ) can be limited to avoid extremely...
class FP_SHAPE, a footprint edge
Definition: typeinfo.h:93
class PAD, a pad in a footprint
Definition: typeinfo.h:89
static std::pair< bool, SHAPE_POLY_SET::VERTEX_INDEX > findVertex(SHAPE_POLY_SET &aPolySet, const EDIT_POINT &aPoint)
void UndoRedoBlock(bool aBlock=true)
Enable/disable undo and redo operations.
virtual MAGNETIC_SETTINGS * GetMagneticItemsSettings()
ITERATOR Iterate(int aFirst, int aLast, bool aIterateHoles=false)
Return an object to iterate through the points of the polygons between aFirst and aLast.
void RotatePoint(int *pX, int *pY, double angle)
Definition: trigo.cpp:228
VECTOR2< int > VECTOR2I
Definition: vector2d.h:623
std::shared_ptr< EDIT_POINTS > m_editPoints
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).
An orthogonal dimension is like an aligned dimension, but the extension lines are locked to the X or ...
void editArcEndpointKeepCenter(PCB_SHAPE *aArc, VECTOR2I aCenter, VECTOR2I aStart, VECTOR2I aMid, VECTOR2I aEnd, const VECTOR2I aCursor) const
Move an end point of the arc around the circumference.
GRID_CONSTRAINT_TYPE GetGridConstraint() const
Definition: edit_points.h:177
EDIT_CONSTRAINT that imposes a constraint that a point has to lie on a line (determined by 2 points).
EDIT_CONSTRAINT that imposes a constraint that two points have to be located at angle of 45 degree mu...
static const TOOL_EVENT SelectedItemsModified
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition: actions.h:204
int modifiedSelection(const TOOL_EVENT &aEvent)
wxPoint GetArcStart() const
Definition: pcb_shape.h:157
EDIT_POINT * m_editedPoint
For better understanding of the points that make a dimension:
EDIT_POINT m_altConstrainer
std::shared_ptr< EDIT_POINTS > makePoints(EDA_ITEM *aItem)
Update item's points with edit points.
polygon (not yet used for tracks, but could be in microwave apps)
PCB_BASE_EDIT_FRAME * frame() const
int getEditedPointIndex() const
Return true if aPoint is the currently modified point.
void SetVertex(const VERTEX_INDEX &aIndex, const VECTOR2I &aPos)
Accessor function to set the position of a specific point.
static bool canAddCorner(const EDA_ITEM &aItem)
Condition to display "Remove corner" context menu entry.
void SetArcGeometry(const wxPoint &aStart, const wxPoint &aMid, const wxPoint &aEnd)
Set the three controlling points for an arc.
Definition: pcb_shape.cpp:454
PCB_SELECTION & GetSelection()
Return the set of currently selected items.
virtual void Move(const wxPoint &aMoveVector) override
Move this object.
Definition: pcb_shape.cpp:115
const std::vector< wxPoint > BuildPolyPointsList() const
Build and return the list of corners in a std::vector<wxPoint>
Definition: pcb_shape.cpp:1225
virtual const wxPoint & GetStart() const
The dimension's origin is the first feature point for the dimension.
void SetActive(bool aActive=true)
Definition: edit_points.h:172
bool IsDrag(int aButtonMask=BUT_ANY) const
Definition: tool_event.h:285
LSET is a set of PCB_LAYER_IDs.
DIR GetOrientation() const
const PCB_SELECTION & selection() const
void SetEndY(int y)
Definition: pcb_shape.h:138
virtual VECTOR2I GetPosition() const
Return coordinates of an EDIT_POINT.
Definition: edit_points.h:70
#define NULL
VECTOR2< double > VECTOR2D
Definition: vector2d.h:622
int OnSelectionChange(const TOOL_EVENT &aEvent)
Change selection event handler.
virtual wxPoint GetPosition() const override
Definition: pcb_text.h:72
DIMENSION_POINTS
PCB_SELECTION_TOOL * m_selectionTool
void HatchBorder()
Compute the hatch lines depending on the hatch parameters and stores it in the zone's attribute m_bor...
Definition: zone.cpp:929
ITERATOR IterateWithHoles(int aOutline)
Represent a set of closed polygons.
SHAPE_LINE_CHAIN & Outline(int aIndex)
SEG_POINTS
virtual void SetEnd(const wxPoint &aPoint)
static TOOL_ACTION pointEditorAddCorner
Break outline (insert additional points to an edge)
Definition: pcb_actions.h:212
static TOOL_ACTION pointEditorRemoveCorner
Removes a corner.
Definition: pcb_actions.h:215
void setAltConstraint(bool aEnabled)
Return a point that should be used as a constrainer for 45 degrees mode.
segment with non rounded ends
Generic, UI-independent tool event.
Definition: tool_event.h:152
wxPoint GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition: pcb_shape.cpp:344
int changeEditMethod(const TOOL_EVENT &aEvent)
const wxPoint & GetStart() const
Return the starting point of the graphic.
Definition: pcb_shape.h:124
Mark the center of a circle or arc with a cross shape.
COMMIT & StageItems(const Range &aRange, CHANGE_TYPE aChangeType)
Add a change of the item aItem of type aChangeType to the change list.
Definition: commit.h:117
static const TOOL_EVENT UninhibitSelectionEditing
Definition: actions.h:211
const VECTOR2I NearestPoint(const VECTOR2I &aP) const
Compute a point on the segment (this) that is closest to point aP.
Definition: seg.cpp:227
An interface for classes handling user events controlling the view behavior such as zooming,...
#define _(s)
const VECTOR2D DragOrigin() const
Returns information about mouse buttons state.
Definition: tool_event.h:269
virtual void ForceCursorPosition(bool aEnabled, const VECTOR2D &aPosition=VECTOR2D(0, 0))
Place the cursor immediately at a given point.
static LSET AllLayersMask()
Definition: lset.cpp:787
Handle a list of polygons defining a copper zone.
Definition: zone.h:57
class ZONE, a copper pour area
Definition: typeinfo.h:105
COMMIT & Remove(EDA_ITEM *aItem)
Notify observers that aItem has been removed.
Definition: commit.h:90
std::shared_ptr< EDIT_CONSTRAINT< EDIT_POINT > > m_altConstraint
bool Matches(const TOOL_EVENT &aEvent) const
Test whether two events match in terms of category & action or command.
Definition: tool_event.h:357
void SetCenter(const wxPoint &aCenterPoint)
Definition: pcb_shape.h:199
EDA_ITEM_FLAGS GetEditFlags() const
Definition: eda_item.h:158
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
double Angle() const
Compute the angle of the vector.
Definition: vector2d.h:307
void SetBezControl1(const wxPoint &aPoint)
Definition: pcb_shape.h:112
void SetOrientation(DIR aOrientation)
Set the orientation of the dimension line (so, perpendicular to the feature lines).
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition: typeinfo.h:99
bool m_isFootprintEditor
void SetEndX(int x)
Definition: pcb_shape.h:139
KIGFX::VIEW * getView() const
Returns the instance of #VIEW object used in the application.
Definition: tool_base.cpp:36
virtual void SetAutoPan(bool aEnabled)
Turn on/off auto panning (this feature is used when there is a tool active (eg.
class FOOTPRINT, a footprint
Definition: typeinfo.h:88
Definition: seg.h:40
EDIT_POINT m_original
Original position for the current drag point.
void editArcMidKeepEndpoints(PCB_SHAPE *aArc, VECTOR2I aStart, VECTOR2I aEnd, const VECTOR2I aCursor) const
Move the mid point of the arc, while keeping the two endpoints.
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition: vector2d.h:404
void editArcMidKeepCenter(PCB_SHAPE *aArc, VECTOR2I aCenter, VECTOR2I aStart, VECTOR2I aMid, VECTOR2I aEnd, const VECTOR2I aCursor) const
Move the mid point of the arc, while keeping the angle.
virtual bool IsLocked() const
Definition: board_item.h:247
ARC_POINTS
bool Init() override
Init() is called once upon a registration of the tool.
KIGFX::VIEW_CONTROLS * controls() const
Common, abstract interface for edit frames.
RECT_POINTS
void SetStart(const wxPoint &aStart)
Definition: pcb_shape.h:127
const wxPoint & GetBezControl2() const
Definition: pcb_shape.h:116
void RemoveVertex(int aGlobalIndex)
Delete the aGlobalIndex-th vertex.
CIRCLE_POINTS
Base class for iterating over all vertices in a given SHAPE_POLY_SET.
void editArcEndpointKeepTangent(PCB_SHAPE *aArc, VECTOR2I aCenter, VECTOR2I aStart, VECTOR2I aMid, VECTOR2I aEnd, const VECTOR2I aCursor) const
Move an end point of the arc, while keeping the tangent at the other endpoint.
class ZONE, managed by a footprint
Definition: typeinfo.h:94
void SetHover(bool aHover=true)
Definition: edit_points.h:175
double GetAngle() const
Definition: pcb_shape.h:107
virtual void SetStart(const wxPoint &aPoint)
usual segment : line with rounded ends
int Size() const
Returns the number of selected parts.
Definition: selection.h:103
The selection tool: currently supports:
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:100
static const TOOL_EVENT InhibitSelectionEditing
Definition: actions.h:210
virtual void Push(const wxString &aMessage=wxT("A commit"), bool aCreateUndoEntry=true, bool aSetDirtyBit=true) override
Revert the commit by restoring the modified items state.
RESET_REASON
Determine the reason of reset for a tool.
Definition: tool_base.h:77
void RebuildBezierToSegmentsPointsList(int aMinSegLen)
Rebuild the m_BezierPoints vertex list that approximate the Bezier curve by a list of segments.
Definition: pcb_shape.cpp:318
void SetArcEnd(const wxPoint &aArcEndPoint)
Initialize the end arc point.
Definition: pcb_shape.h:194
bool isModified(const EDIT_POINT &aPoint) const
Set up an alternative constraint (typically enabled upon a modifier key being pressed).
void Activate()
Run the tool.
void InsertVertex(int aGlobalIndex, VECTOR2I aNewVertex)
Adds a vertex in the globally indexed position aGlobalIndex.
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
static bool addCornerCondition(const SELECTION &aSelection)
Determine if the tool can currently add a corner to the given item.
A leader is a dimension-like object pointing to a specific point.
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
void SetStartY(int y)
Definition: pcb_shape.h:128
EDIT_POINT * m_hoveredPoint
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition: typeinfo.h:103
wxPoint GetArcMid() const
Definition: pcb_shape.cpp:394
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition: vector2d.h:293
virtual const wxPoint & GetEnd() const
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1)
Add a VIEW_ITEM to the view.
Definition: view.cpp:322
void SetNeedRefill(bool aNeedRefill)
Definition: zone.h:235
Represent a single point that can be used for modifying items.
Definition: edit_points.h:47
BEZIER_CURVE_POINTS
POLYGON & Polygon(int aIndex)
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition: tool_base.cpp:42
RECT_LINES
Definition: pad.h:57
Text placement is manually set by the user.
bool UnFill()
Removes the zone filling.
Definition: zone.cpp:188
bool validatePolygon(SHAPE_POLY_SET &aModified) const
Validate a polygon and displays a popup warning if invalid.
Arcs (with rounded ends)
BOARD_ITEM_CONTAINER * GetParent() const
Definition: board_item.h:166
virtual void SetAngle(double aAngle, bool aUpdateEnd=true)
Set the angle for arcs, and normalizes it within the range 0 - 360 degrees.
Definition: pcb_shape.cpp:477
void updateItem() const
Apply the last changes to the edited item.
class PCB_SHAPE, a segment not on copper layers
Definition: typeinfo.h:90
void AddItem(const TOOL_ACTION &aAction, const SELECTION_CONDITION &aCondition, int aOrder=ANY_ORDER)
Add a menu entry to run a TOOL_ACTION on selected items.
const wxPoint & GetBezControl1() const
Definition: pcb_shape.h:113
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
const VECTOR2D Position() const
Returns the point where dragging has started.
Definition: tool_event.h:263
virtual void ApplyConstraint()
Correct coordinates of an EDIT_POINT by applying previously set constraint.
Definition: edit_points.h:165
void SetEnd(const wxPoint &aEnd)
Definition: pcb_shape.h:137
VECTOR2D GetCursorPosition() const
Return the current cursor position in world coordinates.
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition: board_item.h:176
void SetTextPositionMode(DIM_TEXT_POSITION aMode)
EDA_ITEM * Front() const
Definition: selection.h:144
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:1504
void SetArcStart(const wxPoint &aArcStartPoint)
Initialize the start arc point.
Definition: pcb_shape.h:184
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:113
virtual void UpdateMsgPanel()
Redraw the message panel.
bool IsSelfIntersecting() const
Check whether any of the polygons in the set is self intersecting.
void RemoveContour(int aContourIdx, int aPolygonIdx=-1)
Delete the aContourIdx-th contour of the aPolygonIdx-th polygon in the set.
virtual void SetPosition(const VECTOR2I &aPosition)
Set new coordinates for an EDIT_POINT.
Definition: edit_points.h:106
void Update()
Update the dimension's cached text and geometry.