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.

2428 lines
79 KiB

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