You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2453 lines
79 KiB

4 years ago
4 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  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 (C) 2018-2023 KiCad Developers, see AUTHORS.txt for contributors.
  6. * @author Maciej Suminski <maciej.suminski@cern.ch>
  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. #include <functional>
  26. #include <memory>
  27. using namespace std::placeholders;
  28. #include <advanced_config.h>
  29. #include <tool/tool_manager.h>
  30. #include <view/view_controls.h>
  31. #include <gal/graphics_abstraction_layer.h>
  32. #include <geometry/seg.h>
  33. #include <confirm.h>
  34. #include <tools/pcb_actions.h>
  35. #include <tools/pcb_selection_tool.h>
  36. #include <tools/pcb_point_editor.h>
  37. #include <tools/pcb_grid_helper.h>
  38. #include <dialogs/dialog_unit_entry.h>
  39. #include <board_commit.h>
  40. #include <pcb_edit_frame.h>
  41. #include <pcb_bitmap.h>
  42. #include <pcb_dimension.h>
  43. #include <pcb_textbox.h>
  44. #include <pad.h>
  45. #include <zone.h>
  46. #include <footprint.h>
  47. #include <footprint_editor_settings.h>
  48. #include <connectivity/connectivity_data.h>
  49. #include <progress_reporter.h>
  50. const unsigned int PCB_POINT_EDITOR::COORDS_PADDING = pcbIUScale.mmToIU( 20 );
  51. // Few constants to avoid using bare numbers for point indices
  52. enum SEG_POINTS
  53. {
  54. SEG_START, SEG_END
  55. };
  56. enum RECT_POINTS
  57. {
  58. RECT_TOP_LEFT, RECT_TOP_RIGHT, RECT_BOT_RIGHT, RECT_BOT_LEFT
  59. };
  60. enum RECT_LINES
  61. {
  62. RECT_TOP, RECT_RIGHT, RECT_BOT, RECT_LEFT
  63. };
  64. enum ARC_POINTS
  65. {
  66. ARC_CENTER, ARC_START, ARC_MID, ARC_END
  67. };
  68. enum CIRCLE_POINTS
  69. {
  70. CIRC_CENTER, CIRC_END
  71. };
  72. enum BEZIER_CURVE_POINTS
  73. {
  74. BEZIER_CURVE_START,
  75. BEZIER_CURVE_CONTROL_POINT1,
  76. BEZIER_CURVE_CONTROL_POINT2,
  77. BEZIER_CURVE_END
  78. };
  79. enum DIMENSION_POINTS
  80. {
  81. DIM_START,
  82. DIM_END,
  83. DIM_TEXT,
  84. DIM_CROSSBARSTART,
  85. DIM_CROSSBAREND,
  86. DIM_KNEE = DIM_CROSSBARSTART
  87. };
  88. PCB_POINT_EDITOR::PCB_POINT_EDITOR() :
  89. PCB_TOOL_BASE( "pcbnew.PointEditor" ),
  90. m_selectionTool( nullptr ),
  91. m_editedPoint( nullptr ),
  92. m_hoveredPoint( nullptr ),
  93. m_original( VECTOR2I( 0, 0 ) ),
  94. m_refill( false ),
  95. m_arcEditMode( ARC_EDIT_MODE::KEEP_CENTER_ADJUST_ANGLE_RADIUS ),
  96. m_altConstrainer( VECTOR2I( 0, 0 ) ),
  97. m_inPointEditorTool( false )
  98. {
  99. }
  100. void PCB_POINT_EDITOR::Reset( RESET_REASON aReason )
  101. {
  102. m_refill = false;
  103. m_editPoints.reset();
  104. m_altConstraint.reset();
  105. getViewControls()->SetAutoPan( false );
  106. }
  107. bool PCB_POINT_EDITOR::Init()
  108. {
  109. // Find the selection tool, so they can cooperate
  110. m_selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  111. wxASSERT_MSG( m_selectionTool, wxT( "pcbnew.InteractiveSelection tool is not available" ) );
  112. auto& menu = m_selectionTool->GetToolMenu().GetMenu();
  113. menu.AddItem( PCB_ACTIONS::pointEditorAddCorner, PCB_POINT_EDITOR::addCornerCondition );
  114. menu.AddItem( PCB_ACTIONS::pointEditorRemoveCorner,
  115. std::bind( &PCB_POINT_EDITOR::removeCornerCondition, this, _1 ) );
  116. return true;
  117. }
  118. void PCB_POINT_EDITOR::buildForPolyOutline( std::shared_ptr<EDIT_POINTS> points,
  119. const SHAPE_POLY_SET* aOutline )
  120. {
  121. int cornersCount = aOutline->TotalVertices();
  122. for( auto iterator = aOutline->CIterateWithHoles(); iterator; iterator++ )
  123. {
  124. points->AddPoint( *iterator );
  125. if( iterator.IsEndContour() )
  126. points->AddBreak();
  127. }
  128. // Lines have to be added after creating edit points, as they use EDIT_POINT references
  129. for( int i = 0; i < cornersCount - 1; ++i )
  130. {
  131. if( points->IsContourEnd( i ) )
  132. points->AddLine( points->Point( i ), points->Point( points->GetContourStartIdx( i ) ) );
  133. else
  134. points->AddLine( points->Point( i ), points->Point( i + 1 ) );
  135. points->Line( i ).SetConstraint( new EC_PERPLINE( points->Line( i ) ) );
  136. }
  137. // The last missing line, connecting the last and the first polygon point
  138. points->AddLine( points->Point( cornersCount - 1 ),
  139. points->Point( points->GetContourStartIdx( cornersCount - 1 ) ) );
  140. points->Line( points->LinesSize() - 1 )
  141. .SetConstraint( new EC_PERPLINE( points->Line( points->LinesSize() - 1 ) ) );
  142. }
  143. std::shared_ptr<EDIT_POINTS> PCB_POINT_EDITOR::makePoints( EDA_ITEM* aItem )
  144. {
  145. std::shared_ptr<EDIT_POINTS> points = std::make_shared<EDIT_POINTS>( aItem );
  146. if( !aItem )
  147. return points;
  148. if( aItem->Type() == PCB_TEXTBOX_T )
  149. {
  150. const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( aItem );
  151. // We can't currently handle TEXTBOXes that have been turned into SHAPE_T::POLYs due
  152. // to non-cardinal rotations
  153. if( shape->GetShape() != SHAPE_T::RECTANGLE )
  154. return points;
  155. }
  156. // Generate list of edit points basing on the item type
  157. switch( aItem->Type() )
  158. {
  159. case PCB_BITMAP_T:
  160. {
  161. PCB_BITMAP* bitmap = (PCB_BITMAP*) aItem;
  162. VECTOR2I topLeft = bitmap->GetPosition() - bitmap->GetSize() / 2;
  163. VECTOR2I botRight = bitmap->GetPosition() + bitmap->GetSize() / 2;
  164. points->AddPoint( topLeft );
  165. points->AddPoint( VECTOR2I( botRight.x, topLeft.y ) );
  166. points->AddPoint( botRight );
  167. points->AddPoint( VECTOR2I( topLeft.x, botRight.y ) );
  168. break;
  169. }
  170. case PCB_TEXTBOX_T:
  171. case PCB_SHAPE_T:
  172. {
  173. const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( aItem );
  174. switch( shape->GetShape() )
  175. {
  176. case SHAPE_T::SEGMENT:
  177. points->AddPoint( shape->GetStart() );
  178. points->AddPoint( shape->GetEnd() );
  179. break;
  180. case SHAPE_T::RECTANGLE:
  181. points->AddPoint( shape->GetTopLeft() );
  182. points->AddPoint( VECTOR2I( shape->GetBotRight().x, shape->GetTopLeft().y ) );
  183. points->AddPoint( shape->GetBotRight() );
  184. points->AddPoint( VECTOR2I( shape->GetTopLeft().x, shape->GetBotRight().y ) );
  185. points->AddLine( points->Point( RECT_TOP_LEFT ), points->Point( RECT_TOP_RIGHT ) );
  186. points->Line( RECT_TOP ).SetConstraint( new EC_PERPLINE( points->Line( RECT_TOP ) ) );
  187. points->AddLine( points->Point( RECT_TOP_RIGHT ), points->Point( RECT_BOT_RIGHT ) );
  188. points->Line( RECT_RIGHT ).SetConstraint( new EC_PERPLINE( points->Line( RECT_RIGHT ) ) );
  189. points->AddLine( points->Point( RECT_BOT_RIGHT ), points->Point( RECT_BOT_LEFT ) );
  190. points->Line( RECT_BOT ).SetConstraint( new EC_PERPLINE( points->Line( RECT_BOT ) ) );
  191. points->AddLine( points->Point( RECT_BOT_LEFT ), points->Point( RECT_TOP_LEFT ) );
  192. points->Line( RECT_LEFT ).SetConstraint( new EC_PERPLINE( points->Line( RECT_LEFT ) ) );
  193. break;
  194. case SHAPE_T::ARC:
  195. points->AddPoint( shape->GetCenter() );
  196. points->AddPoint( shape->GetStart() );
  197. points->AddPoint( shape->GetArcMid() );
  198. points->AddPoint( shape->GetEnd() );
  199. break;
  200. case SHAPE_T::CIRCLE:
  201. points->AddPoint( shape->GetCenter() );
  202. points->AddPoint( shape->GetEnd() );
  203. break;
  204. case SHAPE_T::POLY:
  205. buildForPolyOutline( points, &shape->GetPolyShape() );
  206. break;
  207. case SHAPE_T::BEZIER:
  208. points->AddPoint( shape->GetStart() );
  209. points->AddPoint( shape->GetBezierC1() );
  210. points->AddPoint( shape->GetBezierC2() );
  211. points->AddPoint( shape->GetEnd() );
  212. break;
  213. default: // suppress warnings
  214. break;
  215. }
  216. break;
  217. }
  218. case PCB_PAD_T:
  219. {
  220. const PAD* pad = static_cast<const PAD*>( aItem );
  221. VECTOR2I shapePos = pad->ShapePos();
  222. VECTOR2I halfSize( pad->GetSize().x / 2, pad->GetSize().y / 2 );
  223. if( !m_isFootprintEditor || pad->IsLocked() )
  224. break;
  225. switch( pad->GetShape() )
  226. {
  227. case PAD_SHAPE::CIRCLE:
  228. points->AddPoint( VECTOR2I( shapePos.x + halfSize.x, shapePos.y ) );
  229. break;
  230. case PAD_SHAPE::OVAL:
  231. case PAD_SHAPE::TRAPEZOID:
  232. case PAD_SHAPE::RECTANGLE:
  233. case PAD_SHAPE::ROUNDRECT:
  234. case PAD_SHAPE::CHAMFERED_RECT:
  235. {
  236. if( !pad->GetOrientation().IsCardinal() )
  237. break;
  238. if( pad->GetOrientation() == ANGLE_90 || pad->GetOrientation() == ANGLE_270 )
  239. std::swap( halfSize.x, halfSize.y );
  240. points->AddPoint( shapePos - halfSize );
  241. points->AddPoint( VECTOR2I( shapePos.x + halfSize.x, shapePos.y - halfSize.y ) );
  242. points->AddPoint( shapePos + halfSize );
  243. points->AddPoint( VECTOR2I( shapePos.x - halfSize.x, shapePos.y + halfSize.y ) );
  244. }
  245. break;
  246. default: // suppress warnings
  247. break;
  248. }
  249. break;
  250. }
  251. case PCB_ZONE_T:
  252. {
  253. const ZONE* zone = static_cast<const ZONE*>( aItem );
  254. buildForPolyOutline( points, zone->Outline() );
  255. break;
  256. }
  257. case PCB_DIM_ALIGNED_T:
  258. case PCB_DIM_ORTHOGONAL_T:
  259. {
  260. const PCB_DIM_ALIGNED* dimension = static_cast<const PCB_DIM_ALIGNED*>( aItem );
  261. points->AddPoint( dimension->GetStart() );
  262. points->AddPoint( dimension->GetEnd() );
  263. points->AddPoint( dimension->GetTextPos() );
  264. points->AddPoint( dimension->GetCrossbarStart() );
  265. points->AddPoint( dimension->GetCrossbarEnd() );
  266. points->Point( DIM_START ).SetSnapConstraint( ALL_LAYERS );
  267. points->Point( DIM_END ).SetSnapConstraint( ALL_LAYERS );
  268. if( aItem->Type() == PCB_DIM_ALIGNED_T )
  269. {
  270. // Dimension height setting - edit points should move only along the feature lines
  271. points->Point( DIM_CROSSBARSTART )
  272. .SetConstraint( new EC_LINE( points->Point( DIM_CROSSBARSTART ),
  273. points->Point( DIM_START ) ) );
  274. points->Point( DIM_CROSSBAREND )
  275. .SetConstraint( new EC_LINE( points->Point( DIM_CROSSBAREND ),
  276. points->Point( DIM_END ) ) );
  277. }
  278. break;
  279. }
  280. case PCB_DIM_CENTER_T:
  281. {
  282. const PCB_DIM_CENTER* dimension = static_cast<const PCB_DIM_CENTER*>( aItem );
  283. points->AddPoint( dimension->GetStart() );
  284. points->AddPoint( dimension->GetEnd() );
  285. points->Point( DIM_START ).SetSnapConstraint( ALL_LAYERS );
  286. points->Point( DIM_END ).SetConstraint( new EC_45DEGREE( points->Point( DIM_END ),
  287. points->Point( DIM_START ) ) );
  288. points->Point( DIM_END ).SetSnapConstraint( IGNORE_SNAPS );
  289. break;
  290. }
  291. case PCB_DIM_RADIAL_T:
  292. {
  293. const PCB_DIM_RADIAL* dimension = static_cast<const PCB_DIM_RADIAL*>( aItem );
  294. points->AddPoint( dimension->GetStart() );
  295. points->AddPoint( dimension->GetEnd() );
  296. points->AddPoint( dimension->GetTextPos() );
  297. points->AddPoint( dimension->GetKnee() );
  298. points->Point( DIM_START ).SetSnapConstraint( ALL_LAYERS );
  299. points->Point( DIM_END ).SetSnapConstraint( ALL_LAYERS );
  300. points->Point( DIM_KNEE ).SetConstraint( new EC_LINE( points->Point( DIM_START ),
  301. points->Point( DIM_END ) ) );
  302. points->Point( DIM_KNEE ).SetSnapConstraint( IGNORE_SNAPS );
  303. points->Point( DIM_TEXT ).SetConstraint( new EC_45DEGREE( points->Point( DIM_TEXT ),
  304. points->Point( DIM_KNEE ) ) );
  305. points->Point( DIM_TEXT ).SetSnapConstraint( IGNORE_SNAPS );
  306. break;
  307. }
  308. case PCB_DIM_LEADER_T:
  309. {
  310. const PCB_DIM_LEADER* dimension = static_cast<const PCB_DIM_LEADER*>( aItem );
  311. points->AddPoint( dimension->GetStart() );
  312. points->AddPoint( dimension->GetEnd() );
  313. points->AddPoint( dimension->GetTextPos() );
  314. points->Point( DIM_START ).SetSnapConstraint( ALL_LAYERS );
  315. points->Point( DIM_END ).SetSnapConstraint( ALL_LAYERS );
  316. points->Point( DIM_TEXT ).SetConstraint( new EC_45DEGREE( points->Point( DIM_TEXT ),
  317. points->Point( DIM_END ) ) );
  318. points->Point( DIM_TEXT ).SetSnapConstraint( IGNORE_SNAPS );
  319. break;
  320. }
  321. default:
  322. points.reset();
  323. break;
  324. }
  325. return points;
  326. }
  327. void PCB_POINT_EDITOR::updateEditedPoint( const TOOL_EVENT& aEvent )
  328. {
  329. EDIT_POINT* point;
  330. EDIT_POINT* hovered = nullptr;
  331. if( aEvent.IsMotion() )
  332. {
  333. point = m_editPoints->FindPoint( aEvent.Position(), getView() );
  334. hovered = point;
  335. }
  336. else if( aEvent.IsDrag( BUT_LEFT ) )
  337. {
  338. point = m_editPoints->FindPoint( aEvent.DragOrigin(), getView() );
  339. }
  340. else
  341. {
  342. point = m_editPoints->FindPoint( getViewControls()->GetCursorPosition(), getView() );
  343. }
  344. if( hovered )
  345. {
  346. if( m_hoveredPoint != hovered )
  347. {
  348. if( m_hoveredPoint )
  349. m_hoveredPoint->SetHover( false );
  350. m_hoveredPoint = hovered;
  351. m_hoveredPoint->SetHover();
  352. }
  353. }
  354. else if( m_hoveredPoint )
  355. {
  356. m_hoveredPoint->SetHover( false );
  357. m_hoveredPoint = nullptr;
  358. }
  359. if( m_editedPoint != point )
  360. setEditedPoint( point );
  361. }
  362. int PCB_POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent )
  363. {
  364. if( !m_selectionTool || aEvent.Matches( EVENTS::InhibitSelectionEditing ) )
  365. return 0;
  366. if( m_inPointEditorTool )
  367. return 0;
  368. REENTRANCY_GUARD guard( &m_inPointEditorTool );
  369. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  370. const PCB_SELECTION& selection = m_selectionTool->GetSelection();
  371. if( selection.Size() != 1 || selection.Front()->GetEditFlags() )
  372. return 0;
  373. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
  374. if( !item || item->IsLocked() )
  375. return 0;
  376. Activate();
  377. // Must be done after Activate() so that it gets set into the correct context
  378. getViewControls()->ShowCursor( true );
  379. PCB_GRID_HELPER grid( m_toolMgr, editFrame->GetMagneticItemsSettings() );
  380. m_editPoints = makePoints( item );
  381. if( !m_editPoints )
  382. return 0;
  383. getView()->Add( m_editPoints.get() );
  384. setEditedPoint( nullptr );
  385. updateEditedPoint( aEvent );
  386. m_refill = false;
  387. bool inDrag = false;
  388. bool useAltContraint = false;
  389. BOARD_COMMIT commit( editFrame );
  390. // Main loop: keep receiving events
  391. while( TOOL_EVENT* evt = Wait() )
  392. {
  393. grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
  394. grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
  395. if( editFrame->IsType( FRAME_PCB_EDITOR ) )
  396. {
  397. useAltContraint = editFrame->GetPcbNewSettings()->m_Use45DegreeLimit;
  398. m_arcEditMode = editFrame->GetPcbNewSettings()->m_ArcEditMode;
  399. }
  400. else
  401. {
  402. useAltContraint = editFrame->GetFootprintEditorSettings()->m_Use45Limit;
  403. m_arcEditMode = editFrame->GetFootprintEditorSettings()->m_ArcEditMode;
  404. }
  405. if( !m_editPoints || evt->IsSelectionEvent() ||
  406. evt->Matches( EVENTS::InhibitSelectionEditing ) )
  407. {
  408. break;
  409. }
  410. EDIT_POINT* prevHover = m_hoveredPoint;
  411. if( !inDrag )
  412. updateEditedPoint( *evt );
  413. if( prevHover != m_hoveredPoint )
  414. getView()->Update( m_editPoints.get() );
  415. if( evt->IsDrag( BUT_LEFT ) && m_editedPoint )
  416. {
  417. if( !inDrag )
  418. {
  419. frame()->UndoRedoBlock( true );
  420. commit.StageItems( selection, CHT_MODIFY );
  421. getViewControls()->ForceCursorPosition( false );
  422. m_original = *m_editedPoint; // Save the original position
  423. getViewControls()->SetAutoPan( true );
  424. inDrag = true;
  425. if( m_editedPoint->GetGridConstraint() != SNAP_BY_GRID )
  426. grid.SetAuxAxes( true, m_original.GetPosition() );
  427. setAltConstraint( true );
  428. m_editedPoint->SetActive();
  429. for( size_t ii = 0; ii < m_editPoints->PointsSize(); ++ii )
  430. {
  431. EDIT_POINT& point = m_editPoints->Point( ii );
  432. if( &point != m_editedPoint )
  433. point.SetActive( false );
  434. }
  435. }
  436. // Keep point inside of limits with some padding
  437. VECTOR2I pos = GetClampedCoords<double, int>( evt->Position(), COORDS_PADDING );
  438. LSET snapLayers;
  439. switch( m_editedPoint->GetSnapConstraint() )
  440. {
  441. case IGNORE_SNAPS: break;
  442. case OBJECT_LAYERS: snapLayers = item->GetLayerSet(); break;
  443. case ALL_LAYERS: snapLayers = LSET::AllLayersMask(); break;
  444. }
  445. if( m_editedPoint->GetGridConstraint() == SNAP_BY_GRID )
  446. {
  447. if( grid.GetUseGrid() )
  448. {
  449. VECTOR2I gridPt = grid.BestSnapAnchor( pos, {}, grid.GetItemGrid( item ), { item } );
  450. VECTOR2I last = m_editedPoint->GetPosition();
  451. VECTOR2I delta = pos - last;
  452. VECTOR2I deltaGrid = gridPt - grid.BestSnapAnchor( last, {}, grid.GetItemGrid( item ), { item } );
  453. if( abs( delta.x ) > grid.GetGrid().x / 2 )
  454. pos.x = last.x + deltaGrid.x;
  455. else
  456. pos.x = last.x;
  457. if( abs( delta.y ) > grid.GetGrid().y / 2 )
  458. pos.y = last.y + deltaGrid.y;
  459. else
  460. pos.y = last.y;
  461. }
  462. }
  463. m_editedPoint->SetPosition( pos );
  464. // The alternative constraint limits to 45 degrees
  465. if( useAltContraint )
  466. {
  467. m_altConstraint->Apply( grid );
  468. }
  469. else if( m_editedPoint->IsConstrained() )
  470. {
  471. m_editedPoint->ApplyConstraint( grid );
  472. }
  473. else if( m_editedPoint->GetGridConstraint() == SNAP_TO_GRID )
  474. {
  475. m_editedPoint->SetPosition(
  476. grid.BestSnapAnchor( m_editedPoint->GetPosition(), snapLayers,
  477. grid.GetItemGrid( item ), { item } ) );
  478. }
  479. updateItem();
  480. getViewControls()->ForceCursorPosition( true, m_editedPoint->GetPosition() );
  481. updatePoints();
  482. }
  483. else if( m_editedPoint && evt->Action() == TA_MOUSE_DOWN && evt->Buttons() == BUT_LEFT )
  484. {
  485. m_editedPoint->SetActive();
  486. for( size_t ii = 0; ii < m_editPoints->PointsSize(); ++ii )
  487. {
  488. EDIT_POINT& point = m_editPoints->Point( ii );
  489. if( &point != m_editedPoint )
  490. point.SetActive( false );
  491. }
  492. getView()->Update( m_editPoints.get() );
  493. }
  494. else if( inDrag && evt->IsMouseUp( BUT_LEFT ) )
  495. {
  496. if( m_editedPoint )
  497. {
  498. m_editedPoint->SetActive( false );
  499. getView()->Update( m_editPoints.get() );
  500. }
  501. getViewControls()->SetAutoPan( false );
  502. setAltConstraint( false );
  503. commit.Push( _( "Drag Corner" ) );
  504. inDrag = false;
  505. frame()->UndoRedoBlock( false );
  506. m_refill = true;
  507. }
  508. else if( evt->IsCancelInteractive() || evt->IsActivate() )
  509. {
  510. if( inDrag ) // Restore the last change
  511. {
  512. commit.Revert();
  513. inDrag = false;
  514. frame()->UndoRedoBlock( false );
  515. }
  516. // Only cancel point editor when activating a new tool
  517. // Otherwise, allow the points to persist when moving up the
  518. // tool stack
  519. if( evt->IsActivate() && !evt->IsMoveTool() )
  520. break;
  521. }
  522. else if( evt->Action() == TA_UNDO_REDO_POST )
  523. {
  524. break;
  525. }
  526. else
  527. {
  528. evt->SetPassEvent();
  529. }
  530. }
  531. if( m_editPoints )
  532. {
  533. getView()->Remove( m_editPoints.get() );
  534. m_editPoints.reset();
  535. }
  536. m_editedPoint = nullptr;
  537. return 0;
  538. }
  539. int PCB_POINT_EDITOR::movePoint( const TOOL_EVENT& aEvent )
  540. {
  541. if( !m_editPoints || !m_editPoints->GetParent() || !HasPoint() )
  542. return 0;
  543. BOARD_COMMIT commit( frame() );
  544. commit.Stage( m_editPoints->GetParent(), CHT_MODIFY );
  545. wxString title;
  546. wxString msg;
  547. if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
  548. {
  549. title = _( "Move Midpoint to Location" );
  550. msg = _( "Move Midpoint" );
  551. }
  552. else
  553. {
  554. title = _( "Move Corner to Location" );
  555. msg = _( "Move Corner" );
  556. }
  557. WX_PT_ENTRY_DIALOG dlg( frame(), title, _( "X:" ), _( "Y:" ), m_editedPoint->GetPosition() );
  558. if( dlg.ShowModal() == wxID_OK )
  559. {
  560. m_editedPoint->SetPosition( dlg.GetValue() );
  561. updateItem();
  562. commit.Push( msg );
  563. }
  564. return 0;
  565. }
  566. void PCB_POINT_EDITOR::editArcEndpointKeepTangent( PCB_SHAPE* aArc, const VECTOR2I& aCenter,
  567. const VECTOR2I& aStart, const VECTOR2I& aMid,
  568. const VECTOR2I& aEnd,
  569. const VECTOR2I& aCursor ) const
  570. {
  571. VECTOR2I center = aCenter;
  572. bool movingStart;
  573. bool arcValid = true;
  574. VECTOR2I p1, p2, p3;
  575. // p1 does not move, p2 does.
  576. if( aStart != aArc->GetStart() )
  577. {
  578. p1 = aEnd;
  579. p2 = aStart;
  580. p3 = aMid;
  581. movingStart = true;
  582. }
  583. else if( aEnd != aArc->GetEnd() )
  584. {
  585. p1 = aStart;
  586. p2 = aEnd;
  587. p3 = aMid;
  588. movingStart = false;
  589. }
  590. else
  591. {
  592. return;
  593. }
  594. VECTOR2D v1, v2, v3, v4;
  595. // Move the coordinate system
  596. v1 = p1 - aCenter;
  597. v2 = p2 - aCenter;
  598. v3 = p3 - aCenter;
  599. VECTOR2D u1, u2, u3;
  600. // A point cannot be both the center and on the arc.
  601. if( ( v1.EuclideanNorm() == 0 ) || ( v2.EuclideanNorm() == 0 ) )
  602. return;
  603. u1 = v1 / v1.EuclideanNorm();
  604. u2 = v3 - ( u1.x * v3.x + u1.y * v3.y ) * u1;
  605. u2 = u2 / u2.EuclideanNorm();
  606. // [ u1, u3 ] is a base centered on the circle with:
  607. // u1 : unit vector toward the point that does not move
  608. // u2 : unit vector toward the mid point.
  609. // Get vectors v1, and v2 in that coordinate system.
  610. double det = u1.x * u2.y - u2.x * u1.y;
  611. // u1 and u2 are unit vectors, and perpendicular.
  612. // det should not be 0. In case it is, do not change the arc.
  613. if( det == 0 )
  614. return;
  615. double tmpx = v1.x * u2.y - v1.y * u2.x;
  616. double tmpy = -v1.x * u1.y + v1.y * u1.x;
  617. v1.x = tmpx;
  618. v1.y = tmpy;
  619. v1 = v1 / det;
  620. tmpx = v2.x * u2.y - v2.y * u2.x;
  621. tmpy = -v2.x * u1.y + v2.y * u1.x;
  622. v2.x = tmpx;
  623. v2.y = tmpy;
  624. v2 = v2 / det;
  625. double R = v1.EuclideanNorm();
  626. bool transformCircle = false;
  627. /* p2
  628. * X***
  629. * ** <---- This is the arc
  630. * y ^ **
  631. * | R *
  632. * | <-----------> *
  633. * x------x------>--------x p1
  634. * C' <----> C x
  635. * delta
  636. *
  637. * p1 does not move, and the tangent at p1 remains the same.
  638. * => The new center, C', will be on the C-p1 axis.
  639. * p2 moves
  640. *
  641. * The radius of the new circle is delta + R
  642. *
  643. * || C' p2 || = || C' P1 ||
  644. * is the same as :
  645. * ( delta + p2.x ) ^ 2 + p2.y ^ 2 = ( R + delta ) ^ 2
  646. *
  647. * delta = ( R^2 - p2.x ^ 2 - p2.y ^2 ) / ( 2 * p2.x - 2 * R )
  648. *
  649. * We can use this equation for any point p2 with p2.x < R
  650. */
  651. if( v2.x == R )
  652. {
  653. // Straight line, do nothing
  654. }
  655. else
  656. {
  657. if( v2.x > R )
  658. {
  659. // If we need to invert the curvature.
  660. // We modify the input so we can use the same equation
  661. transformCircle = true;
  662. v2.x = 2 * R - v2.x;
  663. }
  664. // We can keep the tangent constraint.
  665. double delta = ( R * R - v2.x * v2.x - v2.y * v2.y ) / ( 2 * v2.x - 2 * R );
  666. // This is just to limit the radius, so nothing overflows later when drawing.
  667. if( abs( v2.y / ( R - v2.x ) ) > ADVANCED_CFG::GetCfg().m_DrawArcCenterMaxAngle )
  668. arcValid = false;
  669. // Never recorded a problem, but still checking.
  670. if( !std::isfinite( delta ) )
  671. arcValid = false;
  672. // v4 is the new center
  673. v4 = ( !transformCircle ) ? VECTOR2D( -delta, 0 ) : VECTOR2D( 2 * R + delta, 0 );
  674. tmpx = v4.x * u1.x + v4.y * u2.x;
  675. tmpy = v4.x * u1.y + v4.y * u2.y;
  676. v4.x = tmpx;
  677. v4.y = tmpy;
  678. center = v4 + aCenter;
  679. if( arcValid )
  680. {
  681. aArc->SetCenter( center );
  682. if( movingStart )
  683. aArc->SetStart( aStart );
  684. else
  685. aArc->SetEnd( aEnd );
  686. }
  687. }
  688. }
  689. void PCB_POINT_EDITOR::editArcCenterKeepEndpoints( PCB_SHAPE* aArc, const VECTOR2I& aCenter,
  690. const VECTOR2I& aStart, const VECTOR2I& aMid,
  691. const VECTOR2I& aEnd ) const
  692. {
  693. const int c_snapEpsilon_sq = 4;
  694. VECTOR2I m = ( aStart / 2 + aEnd / 2 );
  695. VECTOR2I perp = ( aEnd - aStart ).Perpendicular().Resize( INT_MAX / 2 );
  696. SEG legal( m - perp, m + perp );
  697. const SEG testSegments[] = { SEG( aCenter, aCenter + VECTOR2( 1, 0 ) ),
  698. SEG( aCenter, aCenter + VECTOR2( 0, 1 ) ) };
  699. std::vector<VECTOR2I> points = { legal.A, legal.B };
  700. for( const SEG& seg : testSegments )
  701. {
  702. OPT_VECTOR2I vec = legal.IntersectLines( seg );
  703. if( vec && legal.SquaredDistance( *vec ) <= c_snapEpsilon_sq )
  704. points.push_back( *vec );
  705. }
  706. OPT_VECTOR2I nearest;
  707. SEG::ecoord min_d_sq = VECTOR2I::ECOORD_MAX;
  708. // Snap by distance between cursor and intersections
  709. for( const VECTOR2I& pt : points )
  710. {
  711. SEG::ecoord d_sq = ( pt - aCenter ).SquaredEuclideanNorm();
  712. if( d_sq < min_d_sq - c_snapEpsilon_sq )
  713. {
  714. min_d_sq = d_sq;
  715. nearest = pt;
  716. }
  717. }
  718. if( nearest )
  719. aArc->SetCenter( *nearest );
  720. }
  721. /**
  722. * Update the coordinates of 4 corners of a rectangle, according to pad constraints and the
  723. * moved corner
  724. * @param aTopLeft [in/out] is the RECT_TOPLEFT to constraint
  725. * @param aTopRight [in/out] is the RECT_TOPRIGHT to constraint
  726. * @param aBotLeft [in/out] is the RECT_BOTLEFT to constraint
  727. * @param aBotRight [in/out] is the RECT_BOTRIGHT to constraint
  728. * @param aHole the location of the pad's hole
  729. * @param aHoleSize the pad's hole size (or {0,0} if it has no hole)
  730. */
  731. void PCB_POINT_EDITOR::pinEditedCorner( VECTOR2I& aTopLeft, VECTOR2I& aTopRight,
  732. VECTOR2I& aBotLeft, VECTOR2I& aBotRight,
  733. const VECTOR2I& aHole, const VECTOR2I& aHoleSize ) const
  734. {
  735. int minWidth = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 );
  736. int minHeight = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 );
  737. if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) ) )
  738. {
  739. if( aHoleSize.x )
  740. {
  741. // pin edited point to the top/left of the hole
  742. aTopLeft.x = std::min( aTopLeft.x, aHole.x - aHoleSize.x / 2 - minWidth );
  743. aTopLeft.y = std::min( aTopLeft.y, aHole.y - aHoleSize.y / 2 - minHeight );
  744. }
  745. else
  746. {
  747. // pin edited point within opposite corner
  748. aTopLeft.x = std::min( aTopLeft.x, aBotRight.x - minWidth );
  749. aTopLeft.y = std::min( aTopLeft.y, aBotRight.y - minHeight );
  750. }
  751. // push edited point edges to adjacent corners
  752. aTopRight.y = aTopLeft.y;
  753. aBotLeft.x = aTopLeft.x;
  754. }
  755. else if( isModified( m_editPoints->Point( RECT_TOP_RIGHT ) ) )
  756. {
  757. if( aHoleSize.x )
  758. {
  759. // pin edited point to the top/right of the hole
  760. aTopRight.x = std::max( aTopRight.x, aHole.x + aHoleSize.x / 2 + minWidth );
  761. aTopRight.y = std::min( aTopRight.y, aHole.y - aHoleSize.y / 2 - minHeight );
  762. }
  763. else
  764. {
  765. // pin edited point within opposite corner
  766. aTopRight.x = std::max( aTopRight.x, aBotLeft.x + minWidth );
  767. aTopRight.y = std::min( aTopRight.y, aBotLeft.y - minHeight );
  768. }
  769. // push edited point edges to adjacent corners
  770. aTopLeft.y = aTopRight.y;
  771. aBotRight.x = aTopRight.x;
  772. }
  773. else if( isModified( m_editPoints->Point( RECT_BOT_LEFT ) ) )
  774. {
  775. if( aHoleSize.x )
  776. {
  777. // pin edited point to the bottom/left of the hole
  778. aBotLeft.x = std::min( aBotLeft.x, aHole.x - aHoleSize.x / 2 - minWidth );
  779. aBotLeft.y = std::max( aBotLeft.y, aHole.y + aHoleSize.y / 2 + minHeight );
  780. }
  781. else
  782. {
  783. // pin edited point within opposite corner
  784. aBotLeft.x = std::min( aBotLeft.x, aTopRight.x - minWidth );
  785. aBotLeft.y = std::max( aBotLeft.y, aTopRight.y + minHeight );
  786. }
  787. // push edited point edges to adjacent corners
  788. aBotRight.y = aBotLeft.y;
  789. aTopLeft.x = aBotLeft.x;
  790. }
  791. else if( isModified( m_editPoints->Point( RECT_BOT_RIGHT ) ) )
  792. {
  793. if( aHoleSize.x )
  794. {
  795. // pin edited point to the bottom/right of the hole
  796. aBotRight.x = std::max( aBotRight.x, aHole.x + aHoleSize.x / 2 + minWidth );
  797. aBotRight.y = std::max( aBotRight.y, aHole.y + aHoleSize.y / 2 + minHeight );
  798. }
  799. else
  800. {
  801. // pin edited point within opposite corner
  802. aBotRight.x = std::max( aBotRight.x, aTopLeft.x + minWidth );
  803. aBotRight.y = std::max( aBotRight.y, aTopLeft.y + minHeight );
  804. }
  805. // push edited point edges to adjacent corners
  806. aBotLeft.y = aBotRight.y;
  807. aTopRight.x = aBotRight.x;
  808. }
  809. else if( isModified( m_editPoints->Line( RECT_TOP ) ) )
  810. {
  811. aTopLeft.y = std::min( aTopLeft.y, aBotRight.y - minHeight );
  812. }
  813. else if( isModified( m_editPoints->Line( RECT_LEFT ) ) )
  814. {
  815. aTopLeft.x = std::min( aTopLeft.x, aBotRight.x - minWidth );
  816. }
  817. else if( isModified( m_editPoints->Line( RECT_BOT ) ) )
  818. {
  819. aBotRight.y = std::max( aBotRight.y, aTopLeft.y + minHeight );
  820. }
  821. else if( isModified( m_editPoints->Line( RECT_RIGHT ) ) )
  822. {
  823. aBotRight.x = std::max( aBotRight.x, aTopLeft.x + minWidth );
  824. }
  825. }
  826. void PCB_POINT_EDITOR::editArcEndpointKeepCenter( PCB_SHAPE* aArc, const VECTOR2I& aCenter,
  827. const VECTOR2I& aStart, const VECTOR2I& aMid,
  828. const VECTOR2I& aEnd,
  829. const VECTOR2I& aCursor ) const
  830. {
  831. int minRadius = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 );
  832. bool movingStart;
  833. VECTOR2I p1, p2, prev_p1;
  834. // user is moving p1, we want to move p2 to the new radius.
  835. if( aStart != aArc->GetStart() )
  836. {
  837. prev_p1 = aArc->GetStart();
  838. p1 = aStart;
  839. p2 = aEnd;
  840. movingStart = true;
  841. }
  842. else
  843. {
  844. prev_p1 = aArc->GetEnd();
  845. p1 = aEnd;
  846. p2 = aStart;
  847. movingStart = false;
  848. }
  849. p1 = p1 - aCenter;
  850. p2 = p2 - aCenter;
  851. if( p1.x == 0 && p1.y == 0 )
  852. p1 = prev_p1 - aCenter;
  853. if( p2.x == 0 && p2.y == 0 )
  854. p2 = { 1, 0 };
  855. double radius = p1.EuclideanNorm();
  856. if( radius < minRadius )
  857. radius = minRadius;
  858. p1 = aCenter + p1.Resize( radius );
  859. p2 = aCenter + p2.Resize( radius );
  860. aArc->SetCenter( aCenter );
  861. if( movingStart )
  862. {
  863. aArc->SetStart( p1 );
  864. aArc->SetEnd( p2 );
  865. }
  866. else
  867. {
  868. aArc->SetStart( p2 );
  869. aArc->SetEnd( p1 );
  870. }
  871. }
  872. void PCB_POINT_EDITOR::editArcMidKeepCenter( PCB_SHAPE* aArc, const VECTOR2I& aCenter,
  873. const VECTOR2I& aStart, const VECTOR2I& aMid,
  874. const VECTOR2I& aEnd, const VECTOR2I& aCursor ) const
  875. {
  876. int minRadius = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 );
  877. SEG chord( aStart, aEnd );
  878. // Now, update the edit point position
  879. // Express the point in a circle-centered coordinate system.
  880. VECTOR2I start = aStart - aCenter;
  881. VECTOR2I end = aEnd - aCenter;
  882. double radius = ( aCursor - aCenter ).EuclideanNorm();
  883. if( radius < minRadius )
  884. radius = minRadius;
  885. start = start.Resize( radius );
  886. end = end.Resize( radius );
  887. start = start + aCenter;
  888. end = end + aCenter;
  889. aArc->SetStart( start );
  890. aArc->SetEnd( end );
  891. }
  892. void PCB_POINT_EDITOR::editArcMidKeepEndpoints( PCB_SHAPE* aArc, const VECTOR2I& aStart,
  893. const VECTOR2I& aEnd,
  894. const VECTOR2I& aCursor ) const
  895. {
  896. // Let 'm' be the middle point of the chord between the start and end points
  897. VECTOR2I m = ( aStart + aEnd ) / 2;
  898. // Legal midpoints lie on a vector starting just off the chord midpoint and extending out
  899. // past the existing midpoint. We do not allow arc inflection while point editing.
  900. const int JUST_OFF = ( aStart - aEnd ).EuclideanNorm() / 100;
  901. VECTOR2I v = (VECTOR2I) aArc->GetArcMid() - m;
  902. SEG legal( m + v.Resize( JUST_OFF ), m + v.Resize( INT_MAX / 2 ) );
  903. VECTOR2I mid = legal.NearestPoint( aCursor );
  904. aArc->SetArcGeometry( aStart, mid, aEnd );
  905. }
  906. void PCB_POINT_EDITOR::updateItem() const
  907. {
  908. EDA_ITEM* item = m_editPoints->GetParent();
  909. if( !item )
  910. return;
  911. switch( item->Type() )
  912. {
  913. case PCB_BITMAP_T:
  914. {
  915. PCB_BITMAP* bitmap = (PCB_BITMAP*) item;
  916. VECTOR2I topLeft = m_editPoints->Point( RECT_TOP_LEFT ).GetPosition();
  917. VECTOR2I topRight = m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition();
  918. VECTOR2I botLeft = m_editPoints->Point( RECT_BOT_LEFT ).GetPosition();
  919. VECTOR2I botRight = m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition();
  920. pinEditedCorner( topLeft, topRight, botLeft, botRight );
  921. double oldWidth = bitmap->GetSize().x;
  922. double newWidth = std::max( topRight.x - topLeft.x, EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 50 ) );
  923. double widthRatio = newWidth / oldWidth;
  924. double oldHeight = bitmap->GetSize().y;
  925. double newHeight =
  926. std::max( botLeft.y - topLeft.y, EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 50 ) );
  927. double heightRatio = newHeight / oldHeight;
  928. bitmap->SetImageScale( bitmap->GetImageScale() * std::min( widthRatio, heightRatio ) );
  929. break;
  930. }
  931. case PCB_TEXTBOX_T:
  932. case PCB_SHAPE_T:
  933. {
  934. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  935. switch( shape->GetShape() )
  936. {
  937. case SHAPE_T::SEGMENT:
  938. if( isModified( m_editPoints->Point( SEG_START ) ) )
  939. shape->SetStart( m_editPoints->Point( SEG_START ).GetPosition() );
  940. else if( isModified( m_editPoints->Point( SEG_END ) ) )
  941. shape->SetEnd( m_editPoints->Point( SEG_END ).GetPosition() );
  942. break;
  943. case SHAPE_T::RECTANGLE:
  944. {
  945. VECTOR2I topLeft = m_editPoints->Point( RECT_TOP_LEFT ).GetPosition();
  946. VECTOR2I topRight = m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition();
  947. VECTOR2I botLeft = m_editPoints->Point( RECT_BOT_LEFT ).GetPosition();
  948. VECTOR2I botRight = m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition();
  949. pinEditedCorner( topLeft, topRight, botLeft, botRight );
  950. if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) )
  951. || isModified( m_editPoints->Point( RECT_TOP_RIGHT ) )
  952. || isModified( m_editPoints->Point( RECT_BOT_RIGHT ) )
  953. || isModified( m_editPoints->Point( RECT_BOT_LEFT ) ) )
  954. {
  955. shape->SetLeft( topLeft.x );
  956. shape->SetTop( topLeft.y );
  957. shape->SetRight( botRight.x );
  958. shape->SetBottom( botRight.y );
  959. }
  960. else if( isModified( m_editPoints->Line( RECT_TOP ) ) )
  961. {
  962. shape->SetTop( topLeft.y );
  963. }
  964. else if( isModified( m_editPoints->Line( RECT_LEFT ) ) )
  965. {
  966. shape->SetLeft( topLeft.x );
  967. }
  968. else if( isModified( m_editPoints->Line( RECT_BOT ) ) )
  969. {
  970. shape->SetBottom( botRight.y );
  971. }
  972. else if( isModified( m_editPoints->Line( RECT_RIGHT ) ) )
  973. {
  974. shape->SetRight( botRight.x );
  975. }
  976. for( unsigned i = 0; i < m_editPoints->LinesSize(); ++i )
  977. {
  978. if( !isModified( m_editPoints->Line( i ) ) )
  979. {
  980. m_editPoints->Line( i ).SetConstraint(
  981. new EC_PERPLINE( m_editPoints->Line( i ) ) );
  982. }
  983. }
  984. break;
  985. }
  986. case SHAPE_T::ARC:
  987. {
  988. VECTOR2I center = m_editPoints->Point( ARC_CENTER ).GetPosition();
  989. VECTOR2I mid = m_editPoints->Point( ARC_MID ).GetPosition();
  990. VECTOR2I start = m_editPoints->Point( ARC_START ).GetPosition();
  991. VECTOR2I end = m_editPoints->Point( ARC_END ).GetPosition();
  992. if( isModified( m_editPoints->Point( ARC_CENTER ) ) )
  993. {
  994. if( m_arcEditMode == ARC_EDIT_MODE::KEEP_ENDPOINTS_OR_START_DIRECTION )
  995. {
  996. editArcCenterKeepEndpoints( shape, center, start, mid, end );
  997. }
  998. else
  999. {
  1000. VECTOR2I moveVector = VECTOR2I( center.x, center.y ) - shape->GetCenter();
  1001. shape->Move( moveVector );
  1002. }
  1003. }
  1004. else if( isModified( m_editPoints->Point( ARC_MID ) ) )
  1005. {
  1006. const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition( false );
  1007. if( m_arcEditMode == ARC_EDIT_MODE::KEEP_ENDPOINTS_OR_START_DIRECTION )
  1008. editArcMidKeepEndpoints( shape, start, end, cursorPos );
  1009. else
  1010. editArcMidKeepCenter( shape, center, start, mid, end, cursorPos );
  1011. }
  1012. else if( isModified( m_editPoints->Point( ARC_START ) )
  1013. || isModified( m_editPoints->Point( ARC_END ) ) )
  1014. {
  1015. const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
  1016. if( m_arcEditMode == ARC_EDIT_MODE::KEEP_ENDPOINTS_OR_START_DIRECTION )
  1017. editArcEndpointKeepTangent( shape, center, start, mid, end, cursorPos );
  1018. else
  1019. editArcEndpointKeepCenter( shape, center, start, mid, end, cursorPos );
  1020. }
  1021. break;
  1022. }
  1023. case SHAPE_T::CIRCLE:
  1024. {
  1025. const VECTOR2I& center = m_editPoints->Point( CIRC_CENTER ).GetPosition();
  1026. const VECTOR2I& end = m_editPoints->Point( CIRC_END ).GetPosition();
  1027. if( isModified( m_editPoints->Point( CIRC_CENTER ) ) )
  1028. {
  1029. VECTOR2I moveVector = VECTOR2I( center.x, center.y ) - shape->GetCenter();
  1030. shape->Move( moveVector );
  1031. }
  1032. else
  1033. {
  1034. shape->SetEnd( VECTOR2I( end.x, end.y ) );
  1035. }
  1036. break;
  1037. }
  1038. case SHAPE_T::POLY:
  1039. {
  1040. SHAPE_POLY_SET& outline = shape->GetPolyShape();
  1041. for( int i = 0; i < outline.TotalVertices(); ++i )
  1042. outline.SetVertex( i, m_editPoints->Point( i ).GetPosition() );
  1043. for( unsigned i = 0; i < m_editPoints->LinesSize(); ++i )
  1044. {
  1045. if( !isModified( m_editPoints->Line( i ) ) )
  1046. m_editPoints->Line( i ).SetConstraint(
  1047. new EC_PERPLINE( m_editPoints->Line( i ) ) );
  1048. }
  1049. validatePolygon( outline );
  1050. break;
  1051. }
  1052. case SHAPE_T::BEZIER:
  1053. if( isModified( m_editPoints->Point( BEZIER_CURVE_START ) ) )
  1054. shape->SetStart( m_editPoints->Point( BEZIER_CURVE_START ).GetPosition() );
  1055. else if( isModified( m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT1 ) ) )
  1056. shape->SetBezierC1( m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT1 ).GetPosition() );
  1057. else if( isModified( m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT2 ) ) )
  1058. shape->SetBezierC2( m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT2 ).GetPosition() );
  1059. else if( isModified( m_editPoints->Point( BEZIER_CURVE_END ) ) )
  1060. shape->SetEnd( m_editPoints->Point( BEZIER_CURVE_END ).GetPosition() );
  1061. shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() );
  1062. break;
  1063. default: // suppress warnings
  1064. break;
  1065. }
  1066. if( shape->IsProxyItem() )
  1067. {
  1068. for( PAD* pad : shape->GetParentFootprint()->Pads() )
  1069. {
  1070. if( pad->IsEntered() )
  1071. view()->Update( pad );
  1072. }
  1073. }
  1074. // Nuke outline font render caches
  1075. if( PCB_TEXTBOX* textBox = dynamic_cast<PCB_TEXTBOX*>( item ) )
  1076. textBox->ClearRenderCache();
  1077. break;
  1078. }
  1079. case PCB_PAD_T:
  1080. {
  1081. PAD* pad = static_cast<PAD*>( item );
  1082. switch( pad->GetShape() )
  1083. {
  1084. case PAD_SHAPE::CIRCLE:
  1085. {
  1086. VECTOR2I end = m_editPoints->Point( 0 ).GetPosition();
  1087. int diameter = (int) EuclideanNorm( end - pad->GetPosition() ) * 2;
  1088. pad->SetSize( VECTOR2I( diameter, diameter ) );
  1089. break;
  1090. }
  1091. case PAD_SHAPE::OVAL:
  1092. case PAD_SHAPE::TRAPEZOID:
  1093. case PAD_SHAPE::RECTANGLE:
  1094. case PAD_SHAPE::ROUNDRECT:
  1095. case PAD_SHAPE::CHAMFERED_RECT:
  1096. {
  1097. VECTOR2I topLeft = m_editPoints->Point( RECT_TOP_LEFT ).GetPosition();
  1098. VECTOR2I topRight = m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition();
  1099. VECTOR2I botLeft = m_editPoints->Point( RECT_BOT_LEFT ).GetPosition();
  1100. VECTOR2I botRight = m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition();
  1101. VECTOR2I holeCenter = pad->GetPosition();
  1102. VECTOR2I holeSize = pad->GetDrillSize();
  1103. pinEditedCorner( topLeft, topRight, botLeft, botRight, holeCenter, holeSize );
  1104. if( ( pad->GetOffset().x || pad->GetOffset().y )
  1105. || ( pad->GetDrillSize().x && pad->GetDrillSize().y ) )
  1106. {
  1107. // Keep hole pinned at the current location; adjust the pad around the hole
  1108. VECTOR2I center = pad->GetPosition();
  1109. int dist[4];
  1110. if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) )
  1111. || isModified( m_editPoints->Point( RECT_BOT_RIGHT ) ) )
  1112. {
  1113. dist[0] = center.x - topLeft.x;
  1114. dist[1] = center.y - topLeft.y;
  1115. dist[2] = botRight.x - center.x;
  1116. dist[3] = botRight.y - center.y;
  1117. }
  1118. else
  1119. {
  1120. dist[0] = center.x - botLeft.x;
  1121. dist[1] = center.y - topRight.y;
  1122. dist[2] = topRight.x - center.x;
  1123. dist[3] = botLeft.y - center.y;
  1124. }
  1125. VECTOR2I padSize( dist[0] + dist[2], dist[1] + dist[3] );
  1126. VECTOR2I deltaOffset( padSize.x / 2 - dist[2], padSize.y / 2 - dist[3] );
  1127. if( pad->GetOrientation() == ANGLE_90 || pad->GetOrientation() == ANGLE_270 )
  1128. std::swap( padSize.x, padSize.y );
  1129. RotatePoint( deltaOffset, -pad->GetOrientation() );
  1130. pad->SetSize( padSize );
  1131. pad->SetOffset( -deltaOffset );
  1132. }
  1133. else
  1134. {
  1135. // Keep pad position at the center of the pad shape
  1136. int left, top, right, bottom;
  1137. if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) )
  1138. || isModified( m_editPoints->Point( RECT_BOT_RIGHT ) ) )
  1139. {
  1140. left = topLeft.x;
  1141. top = topLeft.y;
  1142. right = botRight.x;
  1143. bottom = botRight.y;
  1144. }
  1145. else
  1146. {
  1147. left = botLeft.x;
  1148. top = topRight.y;
  1149. right = topRight.x;
  1150. bottom = botLeft.y;
  1151. }
  1152. VECTOR2I padSize( abs( right - left ), abs( bottom - top ) );
  1153. if( pad->GetOrientation() == ANGLE_90 || pad->GetOrientation() == ANGLE_270 )
  1154. std::swap( padSize.x, padSize.y );
  1155. pad->SetSize( padSize );
  1156. pad->SetPosition( VECTOR2I( ( left + right ) / 2, ( top + bottom ) / 2 ) );
  1157. }
  1158. break;
  1159. }
  1160. default: // suppress warnings
  1161. break;
  1162. }
  1163. break;
  1164. }
  1165. case PCB_ZONE_T:
  1166. {
  1167. ZONE* zone = static_cast<ZONE*>( item );
  1168. zone->UnFill();
  1169. SHAPE_POLY_SET& outline = *zone->Outline();
  1170. for( int i = 0; i < outline.TotalVertices(); ++i )
  1171. {
  1172. if( outline.CVertex( i ) != m_editPoints->Point( i ).GetPosition() )
  1173. zone->SetNeedRefill( true );
  1174. outline.SetVertex( i, m_editPoints->Point( i ).GetPosition() );
  1175. }
  1176. for( unsigned i = 0; i < m_editPoints->LinesSize(); ++i )
  1177. {
  1178. if( !isModified( m_editPoints->Line( i ) ) )
  1179. m_editPoints->Line( i ).SetConstraint( new EC_PERPLINE( m_editPoints->Line( i ) ) );
  1180. }
  1181. validatePolygon( outline );
  1182. zone->HatchBorder();
  1183. // TODO Refill zone when KiCad supports auto re-fill
  1184. break;
  1185. }
  1186. case PCB_DIM_ALIGNED_T:
  1187. {
  1188. PCB_DIM_ALIGNED* dimension = static_cast<PCB_DIM_ALIGNED*>( item );
  1189. // Check which point is currently modified and updated dimension's points respectively
  1190. if( isModified( m_editPoints->Point( DIM_CROSSBARSTART ) ) )
  1191. {
  1192. VECTOR2D featureLine( m_editedPoint->GetPosition() - dimension->GetStart() );
  1193. VECTOR2D crossBar( dimension->GetEnd() - dimension->GetStart() );
  1194. if( featureLine.Cross( crossBar ) > 0 )
  1195. dimension->SetHeight( -featureLine.EuclideanNorm() );
  1196. else
  1197. dimension->SetHeight( featureLine.EuclideanNorm() );
  1198. dimension->Update();
  1199. }
  1200. else if( isModified( m_editPoints->Point( DIM_CROSSBAREND ) ) )
  1201. {
  1202. VECTOR2D featureLine( m_editedPoint->GetPosition() - dimension->GetEnd() );
  1203. VECTOR2D crossBar( dimension->GetEnd() - dimension->GetStart() );
  1204. if( featureLine.Cross( crossBar ) > 0 )
  1205. dimension->SetHeight( -featureLine.EuclideanNorm() );
  1206. else
  1207. dimension->SetHeight( featureLine.EuclideanNorm() );
  1208. dimension->Update();
  1209. }
  1210. else if( isModified( m_editPoints->Point( DIM_START ) ) )
  1211. {
  1212. dimension->SetStart( m_editedPoint->GetPosition() );
  1213. dimension->Update();
  1214. m_editPoints->Point( DIM_CROSSBARSTART ).
  1215. SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBARSTART ),
  1216. m_editPoints->Point( DIM_START ) ) );
  1217. m_editPoints->Point( DIM_CROSSBAREND ).
  1218. SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBAREND ),
  1219. m_editPoints->Point( DIM_END ) ) );
  1220. }
  1221. else if( isModified( m_editPoints->Point( DIM_END ) ) )
  1222. {
  1223. dimension->SetEnd( m_editedPoint->GetPosition() );
  1224. dimension->Update();
  1225. m_editPoints->Point( DIM_CROSSBARSTART ).
  1226. SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBARSTART ),
  1227. m_editPoints->Point( DIM_START ) ) );
  1228. m_editPoints->Point( DIM_CROSSBAREND ).
  1229. SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBAREND ),
  1230. m_editPoints->Point( DIM_END ) ) );
  1231. }
  1232. else if( isModified( m_editPoints->Point(DIM_TEXT ) ) )
  1233. {
  1234. // Force manual mode if we weren't already in it
  1235. dimension->SetTextPositionMode( DIM_TEXT_POSITION::MANUAL );
  1236. dimension->SetTextPos( m_editedPoint->GetPosition() );
  1237. dimension->Update();
  1238. }
  1239. break;
  1240. }
  1241. case PCB_DIM_ORTHOGONAL_T:
  1242. {
  1243. PCB_DIM_ORTHOGONAL* dimension = static_cast<PCB_DIM_ORTHOGONAL*>( item );
  1244. if( isModified( m_editPoints->Point( DIM_CROSSBARSTART ) ) ||
  1245. isModified( m_editPoints->Point( DIM_CROSSBAREND ) ) )
  1246. {
  1247. BOX2I bounds( dimension->GetStart(), dimension->GetEnd() - dimension->GetStart() );
  1248. const VECTOR2I& cursorPos = m_editedPoint->GetPosition();
  1249. // Find vector from nearest dimension point to edit position
  1250. VECTOR2I directionA( cursorPos - dimension->GetStart() );
  1251. VECTOR2I directionB( cursorPos - dimension->GetEnd() );
  1252. VECTOR2I direction = ( directionA < directionB ) ? directionA : directionB;
  1253. bool vert;
  1254. VECTOR2D featureLine( cursorPos - dimension->GetStart() );
  1255. // Only change the orientation when we move outside the bounds
  1256. if( !bounds.Contains( cursorPos ) )
  1257. {
  1258. // If the dimension is horizontal or vertical, set correct orientation
  1259. // otherwise, test if we're left/right of the bounding box or above/below it
  1260. if( bounds.GetWidth() == 0 )
  1261. vert = true;
  1262. else if( bounds.GetHeight() == 0 )
  1263. vert = false;
  1264. else if( cursorPos.x > bounds.GetLeft() && cursorPos.x < bounds.GetRight() )
  1265. vert = false;
  1266. else if( cursorPos.y > bounds.GetTop() && cursorPos.y < bounds.GetBottom() )
  1267. vert = true;
  1268. else
  1269. vert = std::abs( direction.y ) < std::abs( direction.x );
  1270. dimension->SetOrientation( vert ? PCB_DIM_ORTHOGONAL::DIR::VERTICAL
  1271. : PCB_DIM_ORTHOGONAL::DIR::HORIZONTAL );
  1272. }
  1273. else
  1274. {
  1275. vert = dimension->GetOrientation() == PCB_DIM_ORTHOGONAL::DIR::VERTICAL;
  1276. }
  1277. dimension->SetHeight( vert ? featureLine.x : featureLine.y );
  1278. }
  1279. else if( isModified( m_editPoints->Point( DIM_START ) ) )
  1280. {
  1281. dimension->SetStart( m_editedPoint->GetPosition() );
  1282. }
  1283. else if( isModified( m_editPoints->Point( DIM_END ) ) )
  1284. {
  1285. dimension->SetEnd( m_editedPoint->GetPosition() );
  1286. }
  1287. else if( isModified( m_editPoints->Point(DIM_TEXT ) ) )
  1288. {
  1289. // Force manual mode if we weren't already in it
  1290. dimension->SetTextPositionMode( DIM_TEXT_POSITION::MANUAL );
  1291. dimension->SetTextPos( VECTOR2I( m_editedPoint->GetPosition() ) );
  1292. }
  1293. dimension->Update();
  1294. break;
  1295. }
  1296. case PCB_DIM_CENTER_T:
  1297. {
  1298. PCB_DIM_CENTER* dimension = static_cast<PCB_DIM_CENTER*>( item );
  1299. if( isModified( m_editPoints->Point( DIM_START ) ) )
  1300. dimension->SetStart( m_editedPoint->GetPosition() );
  1301. else if( isModified( m_editPoints->Point( DIM_END ) ) )
  1302. dimension->SetEnd( m_editedPoint->GetPosition() );
  1303. dimension->Update();
  1304. break;
  1305. }
  1306. case PCB_DIM_RADIAL_T:
  1307. {
  1308. PCB_DIM_RADIAL* dimension = static_cast<PCB_DIM_RADIAL*>( item );
  1309. if( isModified( m_editPoints->Point( DIM_START ) ) )
  1310. {
  1311. dimension->SetStart( m_editedPoint->GetPosition() );
  1312. dimension->Update();
  1313. m_editPoints->Point( DIM_KNEE ).SetConstraint( new EC_LINE( m_editPoints->Point( DIM_START ),
  1314. m_editPoints->Point( DIM_END ) ) );
  1315. }
  1316. else if( isModified( m_editPoints->Point( DIM_END ) ) )
  1317. {
  1318. VECTOR2I oldKnee = dimension->GetKnee();
  1319. dimension->SetEnd( m_editedPoint->GetPosition() );
  1320. dimension->Update();
  1321. VECTOR2I kneeDelta = dimension->GetKnee() - oldKnee;
  1322. dimension->SetTextPos( dimension->GetTextPos() + kneeDelta );
  1323. dimension->Update();
  1324. m_editPoints->Point( DIM_KNEE ).SetConstraint( new EC_LINE( m_editPoints->Point( DIM_START ),
  1325. m_editPoints->Point( DIM_END ) ) );
  1326. }
  1327. else if( isModified( m_editPoints->Point( DIM_KNEE ) ) )
  1328. {
  1329. VECTOR2I oldKnee = dimension->GetKnee();
  1330. VECTOR2I arrowVec = m_editPoints->Point( DIM_KNEE ).GetPosition()
  1331. - m_editPoints->Point( DIM_END ).GetPosition();
  1332. dimension->SetLeaderLength( arrowVec.EuclideanNorm() );
  1333. dimension->Update();
  1334. VECTOR2I kneeDelta = dimension->GetKnee() - oldKnee;
  1335. dimension->SetTextPos( dimension->GetTextPos() + kneeDelta );
  1336. dimension->Update();
  1337. }
  1338. else if( isModified( m_editPoints->Point( DIM_TEXT ) ) )
  1339. {
  1340. dimension->SetTextPos( m_editedPoint->GetPosition() );
  1341. dimension->Update();
  1342. }
  1343. break;
  1344. }
  1345. case PCB_DIM_LEADER_T:
  1346. {
  1347. PCB_DIM_LEADER* dimension = static_cast<PCB_DIM_LEADER*>( item );
  1348. if( isModified( m_editPoints->Point( DIM_START ) ) )
  1349. {
  1350. dimension->SetStart( (VECTOR2I) m_editedPoint->GetPosition() );
  1351. }
  1352. else if( isModified( m_editPoints->Point( DIM_END ) ) )
  1353. {
  1354. VECTOR2I newPoint( m_editedPoint->GetPosition() );
  1355. VECTOR2I delta = newPoint - dimension->GetEnd();
  1356. dimension->SetEnd( newPoint );
  1357. dimension->SetTextPos( dimension->GetTextPos() + delta );
  1358. }
  1359. else if( isModified( m_editPoints->Point( DIM_TEXT ) ) )
  1360. {
  1361. dimension->SetTextPos( (VECTOR2I) m_editedPoint->GetPosition() );
  1362. }
  1363. dimension->Update();
  1364. break;
  1365. }
  1366. default:
  1367. break;
  1368. }
  1369. getView()->Update( item );
  1370. frame()->SetMsgPanel( item );
  1371. }
  1372. bool PCB_POINT_EDITOR::validatePolygon( SHAPE_POLY_SET& aPoly ) const
  1373. {
  1374. return true;
  1375. }
  1376. void PCB_POINT_EDITOR::updatePoints()
  1377. {
  1378. if( !m_editPoints )
  1379. return;
  1380. EDA_ITEM* item = m_editPoints->GetParent();
  1381. if( !item )
  1382. return;
  1383. switch( item->Type() )
  1384. {
  1385. case PCB_BITMAP_T:
  1386. {
  1387. PCB_BITMAP* bitmap = (PCB_BITMAP*) item;
  1388. VECTOR2I topLeft = bitmap->GetPosition() - bitmap->GetSize() / 2;
  1389. VECTOR2I botRight = bitmap->GetPosition() + bitmap->GetSize() / 2;
  1390. m_editPoints->Point( RECT_TOP_LEFT ).SetPosition( topLeft );
  1391. m_editPoints->Point( RECT_TOP_RIGHT ).SetPosition( botRight.x, topLeft.y );
  1392. m_editPoints->Point( RECT_BOT_LEFT ).SetPosition( topLeft.x, botRight.y );
  1393. m_editPoints->Point( RECT_BOT_RIGHT ).SetPosition( botRight );
  1394. break;
  1395. }
  1396. case PCB_TEXTBOX_T:
  1397. {
  1398. const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( item );
  1399. int target = shape->GetShape() == SHAPE_T::RECTANGLE ? 4 : 0;
  1400. // Careful; textbox shape is mutable between cardinal and non-cardinal rotations...
  1401. if( int( m_editPoints->PointsSize() ) != target )
  1402. {
  1403. getView()->Remove( m_editPoints.get() );
  1404. m_editedPoint = nullptr;
  1405. m_editPoints = makePoints( item );
  1406. if( m_editPoints )
  1407. getView()->Add( m_editPoints.get() );
  1408. break;
  1409. }
  1410. if( shape->GetShape() == SHAPE_T::RECTANGLE )
  1411. {
  1412. m_editPoints->Point( RECT_TOP_LEFT ).SetPosition( shape->GetTopLeft() );
  1413. m_editPoints->Point( RECT_TOP_RIGHT ).SetPosition( shape->GetBotRight().x,
  1414. shape->GetTopLeft().y );
  1415. m_editPoints->Point( RECT_BOT_RIGHT ).SetPosition( shape->GetBotRight() );
  1416. m_editPoints->Point( RECT_BOT_LEFT ).SetPosition( shape->GetTopLeft().x,
  1417. shape->GetBotRight().y );
  1418. }
  1419. else if( shape->GetShape() == SHAPE_T::POLY )
  1420. {
  1421. // Not currently editable while rotated.
  1422. }
  1423. break;
  1424. }
  1425. case PCB_SHAPE_T:
  1426. {
  1427. const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( item );
  1428. switch( shape->GetShape() )
  1429. {
  1430. case SHAPE_T::SEGMENT:
  1431. m_editPoints->Point( SEG_START ).SetPosition( shape->GetStart() );
  1432. m_editPoints->Point( SEG_END ).SetPosition( shape->GetEnd() );
  1433. break;
  1434. case SHAPE_T::RECTANGLE:
  1435. m_editPoints->Point( RECT_TOP_LEFT ).SetPosition( shape->GetTopLeft() );
  1436. m_editPoints->Point( RECT_TOP_RIGHT ).SetPosition( shape->GetBotRight().x,
  1437. shape->GetTopLeft().y );
  1438. m_editPoints->Point( RECT_BOT_RIGHT ).SetPosition( shape->GetBotRight() );
  1439. m_editPoints->Point( RECT_BOT_LEFT ).SetPosition( shape->GetTopLeft().x,
  1440. shape->GetBotRight().y );
  1441. break;
  1442. case SHAPE_T::ARC:
  1443. m_editPoints->Point( ARC_CENTER ).SetPosition( shape->GetCenter() );
  1444. m_editPoints->Point( ARC_START ).SetPosition( shape->GetStart() );
  1445. m_editPoints->Point( ARC_MID ).SetPosition( shape->GetArcMid() );
  1446. m_editPoints->Point( ARC_END ).SetPosition( shape->GetEnd() );
  1447. break;
  1448. case SHAPE_T::CIRCLE:
  1449. m_editPoints->Point( CIRC_CENTER ).SetPosition( shape->GetCenter() );
  1450. m_editPoints->Point( CIRC_END ).SetPosition( shape->GetEnd() );
  1451. break;
  1452. case SHAPE_T::POLY:
  1453. {
  1454. std::vector<VECTOR2I> points;
  1455. shape->DupPolyPointsList( points );
  1456. if( m_editPoints->PointsSize() != (unsigned) points.size() )
  1457. {
  1458. getView()->Remove( m_editPoints.get() );
  1459. m_editedPoint = nullptr;
  1460. m_editPoints = makePoints( item );
  1461. if( m_editPoints )
  1462. getView()->Add( m_editPoints.get() );
  1463. }
  1464. else
  1465. {
  1466. for( unsigned i = 0; i < points.size(); i++ )
  1467. m_editPoints->Point( i ).SetPosition( points[i] );
  1468. }
  1469. break;
  1470. }
  1471. case SHAPE_T::BEZIER:
  1472. m_editPoints->Point( BEZIER_CURVE_START ).SetPosition( shape->GetStart() );
  1473. m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT1 ).SetPosition( shape->GetBezierC1() );
  1474. m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT2 ).SetPosition( shape->GetBezierC2() );
  1475. m_editPoints->Point( BEZIER_CURVE_END ).SetPosition( shape->GetEnd() );
  1476. break;
  1477. default: // suppress warnings
  1478. break;
  1479. }
  1480. break;
  1481. }
  1482. case PCB_PAD_T:
  1483. {
  1484. const PAD* pad = static_cast<const PAD*>( item );
  1485. bool locked = pad->GetParent() && pad->IsLocked();
  1486. VECTOR2I shapePos = pad->ShapePos();
  1487. VECTOR2I halfSize( pad->GetSize().x / 2, pad->GetSize().y / 2 );
  1488. switch( pad->GetShape() )
  1489. {
  1490. case PAD_SHAPE::CIRCLE:
  1491. {
  1492. int target = locked ? 0 : 1;
  1493. // Careful; pad shape is mutable...
  1494. if( int( m_editPoints->PointsSize() ) != target )
  1495. {
  1496. getView()->Remove( m_editPoints.get() );
  1497. m_editedPoint = nullptr;
  1498. m_editPoints = makePoints( item );
  1499. if( m_editPoints )
  1500. getView()->Add( m_editPoints.get() );
  1501. }
  1502. else if( target == 1 )
  1503. {
  1504. shapePos.x += halfSize.x;
  1505. m_editPoints->Point( 0 ).SetPosition( shapePos );
  1506. }
  1507. }
  1508. break;
  1509. case PAD_SHAPE::OVAL:
  1510. case PAD_SHAPE::TRAPEZOID:
  1511. case PAD_SHAPE::RECTANGLE:
  1512. case PAD_SHAPE::ROUNDRECT:
  1513. case PAD_SHAPE::CHAMFERED_RECT:
  1514. {
  1515. // Careful; pad shape and orientation are mutable...
  1516. int target = locked || !pad->GetOrientation().IsCardinal() ? 0 : 4;
  1517. if( int( m_editPoints->PointsSize() ) != target )
  1518. {
  1519. getView()->Remove( m_editPoints.get() );
  1520. m_editedPoint = nullptr;
  1521. m_editPoints = makePoints( item );
  1522. if( m_editPoints )
  1523. getView()->Add( m_editPoints.get() );
  1524. }
  1525. else if( target == 4 )
  1526. {
  1527. if( pad->GetOrientation() == ANGLE_90 || pad->GetOrientation() == ANGLE_270 )
  1528. std::swap( halfSize.x, halfSize.y );
  1529. m_editPoints->Point( RECT_TOP_LEFT ).SetPosition( shapePos - halfSize );
  1530. m_editPoints->Point( RECT_TOP_RIGHT )
  1531. .SetPosition(
  1532. VECTOR2I( shapePos.x + halfSize.x, shapePos.y - halfSize.y ) );
  1533. m_editPoints->Point( RECT_BOT_RIGHT ).SetPosition( shapePos + halfSize );
  1534. m_editPoints->Point( RECT_BOT_LEFT )
  1535. .SetPosition(
  1536. VECTOR2I( shapePos.x - halfSize.x, shapePos.y + halfSize.y ) );
  1537. }
  1538. break;
  1539. }
  1540. default: // suppress warnings
  1541. break;
  1542. }
  1543. }
  1544. break;
  1545. case PCB_ZONE_T:
  1546. {
  1547. ZONE* zone = static_cast<ZONE*>( item );
  1548. const SHAPE_POLY_SET* outline = zone->Outline();
  1549. if( m_editPoints->PointsSize() != (unsigned) outline->TotalVertices() )
  1550. {
  1551. getView()->Remove( m_editPoints.get() );
  1552. m_editedPoint = nullptr;
  1553. m_editPoints = makePoints( item );
  1554. if( m_editPoints )
  1555. getView()->Add( m_editPoints.get() );
  1556. }
  1557. else
  1558. {
  1559. for( int i = 0; i < outline->TotalVertices(); ++i )
  1560. m_editPoints->Point( i ).SetPosition( outline->CVertex( i ) );
  1561. }
  1562. break;
  1563. }
  1564. case PCB_DIM_ALIGNED_T:
  1565. case PCB_DIM_ORTHOGONAL_T:
  1566. {
  1567. const PCB_DIM_ALIGNED* dimension = static_cast<const PCB_DIM_ALIGNED*>( item );
  1568. m_editPoints->Point( DIM_START ).SetPosition( dimension->GetStart() );
  1569. m_editPoints->Point( DIM_END ).SetPosition( dimension->GetEnd() );
  1570. m_editPoints->Point( DIM_TEXT ).SetPosition( dimension->GetTextPos() );
  1571. m_editPoints->Point( DIM_CROSSBARSTART ).SetPosition( dimension->GetCrossbarStart() );
  1572. m_editPoints->Point( DIM_CROSSBAREND ).SetPosition( dimension->GetCrossbarEnd() );
  1573. break;
  1574. }
  1575. case PCB_DIM_CENTER_T:
  1576. {
  1577. const PCB_DIM_CENTER* dimension = static_cast<const PCB_DIM_CENTER*>( item );
  1578. m_editPoints->Point( DIM_START ).SetPosition( dimension->GetStart() );
  1579. m_editPoints->Point( DIM_END ).SetPosition( dimension->GetEnd() );
  1580. break;
  1581. }
  1582. case PCB_DIM_RADIAL_T:
  1583. {
  1584. const PCB_DIM_RADIAL* dimension = static_cast<const PCB_DIM_RADIAL*>( item );
  1585. m_editPoints->Point( DIM_START ).SetPosition( dimension->GetStart() );
  1586. m_editPoints->Point( DIM_END ).SetPosition( dimension->GetEnd() );
  1587. m_editPoints->Point( DIM_TEXT ).SetPosition( dimension->GetTextPos() );
  1588. m_editPoints->Point( DIM_KNEE ).SetPosition( dimension->GetKnee() );
  1589. break;
  1590. }
  1591. case PCB_DIM_LEADER_T:
  1592. {
  1593. const PCB_DIM_LEADER* dimension = static_cast<const PCB_DIM_LEADER*>( item );
  1594. m_editPoints->Point( DIM_START ).SetPosition( dimension->GetStart() );
  1595. m_editPoints->Point( DIM_END ).SetPosition( dimension->GetEnd() );
  1596. m_editPoints->Point( DIM_TEXT ).SetPosition( dimension->GetTextPos() );
  1597. break;
  1598. }
  1599. default:
  1600. break;
  1601. }
  1602. getView()->Update( m_editPoints.get() );
  1603. }
  1604. void PCB_POINT_EDITOR::setEditedPoint( EDIT_POINT* aPoint )
  1605. {
  1606. KIGFX::VIEW_CONTROLS* controls = getViewControls();
  1607. if( aPoint )
  1608. {
  1609. frame()->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
  1610. controls->ForceCursorPosition( true, aPoint->GetPosition() );
  1611. controls->ShowCursor( true );
  1612. }
  1613. else
  1614. {
  1615. if( frame()->ToolStackIsEmpty() )
  1616. controls->ShowCursor( false );
  1617. controls->ForceCursorPosition( false );
  1618. }
  1619. m_editedPoint = aPoint;
  1620. }
  1621. void PCB_POINT_EDITOR::setAltConstraint( bool aEnabled )
  1622. {
  1623. if( aEnabled )
  1624. {
  1625. EDA_ITEM* parent = m_editPoints->GetParent();
  1626. EDIT_LINE* line = dynamic_cast<EDIT_LINE*>( m_editedPoint );
  1627. bool isPoly;
  1628. switch( parent->Type() )
  1629. {
  1630. case PCB_ZONE_T:
  1631. isPoly = true;
  1632. break;
  1633. case PCB_SHAPE_T:
  1634. isPoly = static_cast<PCB_SHAPE*>( parent )->GetShape() == SHAPE_T::POLY;
  1635. break;
  1636. default:
  1637. isPoly = false;
  1638. break;
  1639. }
  1640. if( line && isPoly )
  1641. {
  1642. EC_CONVERGING* altConstraint = new EC_CONVERGING( *line, *m_editPoints );
  1643. m_altConstraint.reset( (EDIT_CONSTRAINT<EDIT_POINT>*) altConstraint );
  1644. }
  1645. else
  1646. {
  1647. // Find a proper constraining point for 45 degrees mode
  1648. m_altConstrainer = get45DegConstrainer();
  1649. m_altConstraint.reset( new EC_45DEGREE( *m_editedPoint, m_altConstrainer ) );
  1650. }
  1651. }
  1652. else
  1653. {
  1654. m_altConstraint.reset();
  1655. }
  1656. }
  1657. EDIT_POINT PCB_POINT_EDITOR::get45DegConstrainer() const
  1658. {
  1659. EDA_ITEM* item = m_editPoints->GetParent();
  1660. switch( item->Type() )
  1661. {
  1662. case PCB_SHAPE_T:
  1663. switch( static_cast<const PCB_SHAPE*>( item )->GetShape() )
  1664. {
  1665. case SHAPE_T::SEGMENT:
  1666. return *( m_editPoints->Next( *m_editedPoint ) ); // select the other end of line
  1667. case SHAPE_T::ARC:
  1668. case SHAPE_T::CIRCLE:
  1669. return m_editPoints->Point( CIRC_CENTER );
  1670. default: // suppress warnings
  1671. break;
  1672. }
  1673. break;
  1674. case PCB_DIM_ALIGNED_T:
  1675. {
  1676. // Constraint for crossbar
  1677. if( isModified( m_editPoints->Point( DIM_START ) ) )
  1678. return m_editPoints->Point( DIM_END );
  1679. else if( isModified( m_editPoints->Point( DIM_END ) ) )
  1680. return m_editPoints->Point( DIM_START );
  1681. else
  1682. return EDIT_POINT( m_editedPoint->GetPosition() ); // no constraint
  1683. break;
  1684. }
  1685. case PCB_DIM_CENTER_T:
  1686. {
  1687. if( isModified( m_editPoints->Point( DIM_END ) ) )
  1688. return m_editPoints->Point( DIM_START );
  1689. break;
  1690. }
  1691. case PCB_DIM_RADIAL_T:
  1692. {
  1693. if( isModified( m_editPoints->Point( DIM_TEXT ) ) )
  1694. return m_editPoints->Point( DIM_KNEE );
  1695. break;
  1696. }
  1697. default:
  1698. break;
  1699. }
  1700. // In any other case we may align item to its original position
  1701. return m_original;
  1702. }
  1703. bool PCB_POINT_EDITOR::canAddCorner( const EDA_ITEM& aItem )
  1704. {
  1705. const auto type = aItem.Type();
  1706. // Works only for zones and line segments
  1707. if( type == PCB_ZONE_T )
  1708. return true;
  1709. if( type == PCB_SHAPE_T )
  1710. {
  1711. const PCB_SHAPE& shape = static_cast<const PCB_SHAPE&>( aItem );
  1712. return shape.GetShape() == SHAPE_T::SEGMENT || shape.GetShape() == SHAPE_T::POLY;
  1713. }
  1714. return false;
  1715. }
  1716. bool PCB_POINT_EDITOR::addCornerCondition( const SELECTION& aSelection )
  1717. {
  1718. if( aSelection.Size() != 1 )
  1719. return false;
  1720. const EDA_ITEM* item = aSelection.Front();
  1721. return ( item != nullptr ) && canAddCorner( *item );
  1722. }
  1723. // Finds a corresponding vertex in a polygon set
  1724. static std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX>
  1725. findVertex( SHAPE_POLY_SET& aPolySet, const EDIT_POINT& aPoint )
  1726. {
  1727. for( auto it = aPolySet.IterateWithHoles(); it; ++it )
  1728. {
  1729. auto vertexIdx = it.GetIndex();
  1730. if( aPolySet.CVertex( vertexIdx ) == aPoint.GetPosition() )
  1731. return std::make_pair( true, vertexIdx );
  1732. }
  1733. return std::make_pair( false, SHAPE_POLY_SET::VERTEX_INDEX() );
  1734. }
  1735. bool PCB_POINT_EDITOR::removeCornerCondition( const SELECTION& )
  1736. {
  1737. if( !m_editPoints || !m_editedPoint )
  1738. return false;
  1739. EDA_ITEM* item = m_editPoints->GetParent();
  1740. SHAPE_POLY_SET* polyset = nullptr;
  1741. if( !item )
  1742. return false;
  1743. switch( item->Type() )
  1744. {
  1745. case PCB_ZONE_T:
  1746. polyset = static_cast<ZONE*>( item )->Outline();
  1747. break;
  1748. case PCB_SHAPE_T:
  1749. if( static_cast<PCB_SHAPE*>( item )->GetShape() == SHAPE_T::POLY )
  1750. polyset = &static_cast<PCB_SHAPE*>( item )->GetPolyShape();
  1751. else
  1752. return false;
  1753. break;
  1754. default:
  1755. return false;
  1756. }
  1757. std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX> vertex = findVertex( *polyset, *m_editedPoint );
  1758. if( !vertex.first )
  1759. return false;
  1760. const SHAPE_POLY_SET::VERTEX_INDEX& vertexIdx = vertex.second;
  1761. // Check if there are enough vertices so one can be removed without
  1762. // degenerating the polygon.
  1763. // The first condition allows one to remove all corners from holes (when
  1764. // there are only 2 vertices left, a hole is removed).
  1765. if( vertexIdx.m_contour == 0 &&
  1766. polyset->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour].PointCount() <= 3 )
  1767. {
  1768. return false;
  1769. }
  1770. // Remove corner does not work with lines
  1771. if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
  1772. return false;
  1773. return m_editedPoint != nullptr;
  1774. }
  1775. int PCB_POINT_EDITOR::addCorner( const TOOL_EVENT& aEvent )
  1776. {
  1777. if( !m_editPoints )
  1778. return 0;
  1779. EDA_ITEM* item = m_editPoints->GetParent();
  1780. PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  1781. const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
  1782. // called without an active edited polygon
  1783. if( !item || !canAddCorner( *item ) )
  1784. return 0;
  1785. PCB_SHAPE* graphicItem = dynamic_cast<PCB_SHAPE*>( item );
  1786. BOARD_COMMIT commit( frame );
  1787. if( item->Type() == PCB_ZONE_T || ( graphicItem && graphicItem->GetShape() == SHAPE_T::POLY ) )
  1788. {
  1789. unsigned int nearestIdx = 0;
  1790. unsigned int nextNearestIdx = 0;
  1791. unsigned int nearestDist = INT_MAX;
  1792. unsigned int firstPointInContour = 0;
  1793. SHAPE_POLY_SET* zoneOutline;
  1794. if( item->Type() == PCB_ZONE_T )
  1795. {
  1796. ZONE* zone = static_cast<ZONE*>( item );
  1797. zoneOutline = zone->Outline();
  1798. zone->SetNeedRefill( true );
  1799. }
  1800. else
  1801. {
  1802. zoneOutline = &( graphicItem->GetPolyShape() );
  1803. }
  1804. commit.Modify( item );
  1805. // Search the best outline segment to add a new corner
  1806. // and therefore break this segment into two segments
  1807. // Object to iterate through the corners of the outlines (main contour and its holes)
  1808. SHAPE_POLY_SET::ITERATOR iterator = zoneOutline->Iterate( 0, zoneOutline->OutlineCount()-1,
  1809. /* IterateHoles */ true );
  1810. int curr_idx = 0;
  1811. // Iterate through all the corners of the outlines and search the best segment
  1812. for( ; iterator; iterator++, curr_idx++ )
  1813. {
  1814. int jj = curr_idx+1;
  1815. if( iterator.IsEndContour() )
  1816. { // We reach the last point of the current contour (main or hole)
  1817. jj = firstPointInContour;
  1818. firstPointInContour = curr_idx+1; // Prepare next contour analysis
  1819. }
  1820. SEG curr_segment( zoneOutline->CVertex( curr_idx ), zoneOutline->CVertex( jj ) );
  1821. unsigned int distance = curr_segment.Distance( cursorPos );
  1822. if( distance < nearestDist )
  1823. {
  1824. nearestDist = distance;
  1825. nearestIdx = curr_idx;
  1826. nextNearestIdx = jj;
  1827. }
  1828. }
  1829. // Find the point on the closest segment
  1830. const VECTOR2I& sideOrigin = zoneOutline->CVertex( nearestIdx );
  1831. const VECTOR2I& sideEnd = zoneOutline->CVertex( nextNearestIdx );
  1832. SEG nearestSide( sideOrigin, sideEnd );
  1833. VECTOR2I nearestPoint = nearestSide.NearestPoint( cursorPos );
  1834. // Do not add points that have the same coordinates as ones that already belong to polygon
  1835. // instead, add a point in the middle of the side
  1836. if( nearestPoint == sideOrigin || nearestPoint == sideEnd )
  1837. nearestPoint = ( sideOrigin + sideEnd ) / 2;
  1838. zoneOutline->InsertVertex( nextNearestIdx, nearestPoint );
  1839. // We re-hatch the filled zones but not polygons
  1840. if( item->Type() == PCB_ZONE_T )
  1841. static_cast<ZONE*>( item )->HatchBorder();
  1842. commit.Push( _( "Add Zone Corner" ) );
  1843. }
  1844. else if( graphicItem && graphicItem->GetShape() == SHAPE_T::SEGMENT )
  1845. {
  1846. commit.Modify( graphicItem );
  1847. SEG seg( graphicItem->GetStart(), graphicItem->GetEnd() );
  1848. VECTOR2I nearestPoint = seg.NearestPoint( cursorPos );
  1849. // Move the end of the line to the break point..
  1850. graphicItem->SetEnd( VECTOR2I( nearestPoint.x, nearestPoint.y ) );
  1851. // and add another one starting from the break point
  1852. PCB_SHAPE* newSegment = new PCB_SHAPE( *graphicItem );
  1853. newSegment->ClearSelected();
  1854. newSegment->SetStart( VECTOR2I( nearestPoint.x, nearestPoint.y ) );
  1855. newSegment->SetEnd( VECTOR2I( seg.B.x, seg.B.y ) );
  1856. commit.Add( newSegment );
  1857. commit.Push( _( "Split Segment" ) );
  1858. }
  1859. updatePoints();
  1860. return 0;
  1861. }
  1862. int PCB_POINT_EDITOR::removeCorner( const TOOL_EVENT& aEvent )
  1863. {
  1864. if( !m_editPoints || !m_editedPoint )
  1865. return 0;
  1866. EDA_ITEM* item = m_editPoints->GetParent();
  1867. if( !item )
  1868. return 0;
  1869. SHAPE_POLY_SET* polygon = nullptr;
  1870. if( item->Type() == PCB_ZONE_T )
  1871. {
  1872. ZONE* zone = static_cast<ZONE*>( item );
  1873. polygon = zone->Outline();
  1874. zone->SetNeedRefill( true );
  1875. }
  1876. else if( item->Type() == PCB_SHAPE_T )
  1877. {
  1878. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  1879. if( shape->GetShape() == SHAPE_T::POLY )
  1880. polygon = &shape->GetPolyShape();
  1881. }
  1882. if( !polygon )
  1883. return 0;
  1884. PCB_BASE_FRAME* frame = getEditFrame<PCB_BASE_FRAME>();
  1885. BOARD_COMMIT commit( frame );
  1886. auto vertex = findVertex( *polygon, *m_editedPoint );
  1887. if( vertex.first )
  1888. {
  1889. const auto& vertexIdx = vertex.second;
  1890. auto& outline = polygon->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour];
  1891. if( outline.PointCount() > 3 )
  1892. {
  1893. // the usual case: remove just the corner when there are >3 vertices
  1894. commit.Modify( item );
  1895. polygon->RemoveVertex( vertexIdx );
  1896. validatePolygon( *polygon );
  1897. }
  1898. else
  1899. {
  1900. // either remove a hole or the polygon when there are <= 3 corners
  1901. if( vertexIdx.m_contour > 0 )
  1902. {
  1903. // remove hole
  1904. commit.Modify( item );
  1905. polygon->RemoveContour( vertexIdx.m_contour );
  1906. }
  1907. else
  1908. {
  1909. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  1910. commit.Remove( item );
  1911. }
  1912. }
  1913. setEditedPoint( nullptr );
  1914. if( item->Type() == PCB_ZONE_T )
  1915. commit.Push( _( "Remove Zone Corner" ) );
  1916. else
  1917. commit.Push( _( "Remove Polygon Corner" ) );
  1918. // Refresh zone hatching
  1919. if( item->Type() == PCB_ZONE_T )
  1920. static_cast<ZONE*>( item )->HatchBorder();
  1921. updatePoints();
  1922. }
  1923. return 0;
  1924. }
  1925. int PCB_POINT_EDITOR::modifiedSelection( const TOOL_EVENT& aEvent )
  1926. {
  1927. updatePoints();
  1928. return 0;
  1929. }
  1930. int PCB_POINT_EDITOR::changeArcEditMode( const TOOL_EVENT& aEvent )
  1931. {
  1932. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  1933. if( aEvent.Matches( ACTIONS::cycleArcEditMode.MakeEvent() ) )
  1934. {
  1935. if( editFrame->IsType( FRAME_PCB_EDITOR ) )
  1936. m_arcEditMode = editFrame->GetPcbNewSettings()->m_ArcEditMode;
  1937. else
  1938. m_arcEditMode = editFrame->GetFootprintEditorSettings()->m_ArcEditMode;
  1939. switch( m_arcEditMode )
  1940. {
  1941. case ARC_EDIT_MODE::KEEP_CENTER_ADJUST_ANGLE_RADIUS:
  1942. m_arcEditMode = ARC_EDIT_MODE::KEEP_ENDPOINTS_OR_START_DIRECTION;
  1943. break;
  1944. case ARC_EDIT_MODE::KEEP_ENDPOINTS_OR_START_DIRECTION:
  1945. m_arcEditMode = ARC_EDIT_MODE::KEEP_CENTER_ADJUST_ANGLE_RADIUS;
  1946. break;
  1947. }
  1948. }
  1949. else
  1950. {
  1951. m_arcEditMode = aEvent.Parameter<ARC_EDIT_MODE>();
  1952. }
  1953. if( editFrame->IsType( FRAME_PCB_EDITOR ) )
  1954. editFrame->GetPcbNewSettings()->m_ArcEditMode = m_arcEditMode;
  1955. else
  1956. editFrame->GetFootprintEditorSettings()->m_ArcEditMode = m_arcEditMode;
  1957. return 0;
  1958. }
  1959. void PCB_POINT_EDITOR::setTransitions()
  1960. {
  1961. Go( &PCB_POINT_EDITOR::OnSelectionChange, ACTIONS::activatePointEditor.MakeEvent() );
  1962. Go( &PCB_POINT_EDITOR::movePoint, PCB_ACTIONS::pointEditorMoveCorner.MakeEvent() );
  1963. Go( &PCB_POINT_EDITOR::movePoint, PCB_ACTIONS::pointEditorMoveMidpoint.MakeEvent() );
  1964. Go( &PCB_POINT_EDITOR::addCorner, PCB_ACTIONS::pointEditorAddCorner.MakeEvent() );
  1965. Go( &PCB_POINT_EDITOR::removeCorner, PCB_ACTIONS::pointEditorRemoveCorner.MakeEvent() );
  1966. Go( &PCB_POINT_EDITOR::changeArcEditMode, PCB_ACTIONS::pointEditorArcKeepCenter.MakeEvent() );
  1967. Go( &PCB_POINT_EDITOR::changeArcEditMode, PCB_ACTIONS::pointEditorArcKeepEndpoint.MakeEvent() );
  1968. Go( &PCB_POINT_EDITOR::changeArcEditMode, ACTIONS::cycleArcEditMode.MakeEvent() );
  1969. Go( &PCB_POINT_EDITOR::modifiedSelection, EVENTS::SelectedItemsModified );
  1970. Go( &PCB_POINT_EDITOR::modifiedSelection, EVENTS::SelectedItemsMoved );
  1971. Go( &PCB_POINT_EDITOR::OnSelectionChange, EVENTS::PointSelectedEvent );
  1972. Go( &PCB_POINT_EDITOR::OnSelectionChange, EVENTS::SelectedEvent );
  1973. Go( &PCB_POINT_EDITOR::OnSelectionChange, EVENTS::UnselectedEvent );
  1974. Go( &PCB_POINT_EDITOR::OnSelectionChange, EVENTS::InhibitSelectionEditing );
  1975. Go( &PCB_POINT_EDITOR::OnSelectionChange, EVENTS::UninhibitSelectionEditing );
  1976. }