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.

715 lines
23 KiB

2 years ago
5 years ago
5 years ago
5 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 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) 2016 CERN
  5. * Copyright (C) 2020-2023 KiCad Developers, see AUTHORS.txt for contributors.
  6. * @author Tomasz Wlostowski <tomasz.wlostowski@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 <macros.h>
  26. #include <board.h>
  27. #include <footprint.h>
  28. #include <lset.h>
  29. #include <pcb_group.h>
  30. #include <pcb_track.h>
  31. #include <tool/tool_manager.h>
  32. #include <tools/pcb_selection_tool.h>
  33. #include <tools/zone_filler_tool.h>
  34. #include <view/view.h>
  35. #include <board_commit.h>
  36. #include <tools/pcb_tool_base.h>
  37. #include <tools/pcb_actions.h>
  38. #include <connectivity/connectivity_data.h>
  39. #include <teardrop/teardrop.h>
  40. #include <functional>
  41. using namespace std::placeholders;
  42. BOARD_COMMIT::BOARD_COMMIT( TOOL_BASE* aTool ) :
  43. m_toolMgr( aTool->GetManager() ),
  44. m_isBoardEditor( false ),
  45. m_isFootprintEditor( false )
  46. {
  47. if( PCB_TOOL_BASE* pcb_tool = dynamic_cast<PCB_TOOL_BASE*>( aTool ) )
  48. {
  49. m_isBoardEditor = pcb_tool->IsBoardEditor();
  50. m_isFootprintEditor = pcb_tool->IsFootprintEditor();
  51. }
  52. }
  53. BOARD_COMMIT::BOARD_COMMIT( EDA_DRAW_FRAME* aFrame ) :
  54. m_toolMgr( aFrame->GetToolManager() ),
  55. m_isBoardEditor( aFrame->IsType( FRAME_PCB_EDITOR ) ),
  56. m_isFootprintEditor( aFrame->IsType( FRAME_FOOTPRINT_EDITOR ) )
  57. {
  58. }
  59. BOARD_COMMIT::BOARD_COMMIT( TOOL_MANAGER* aMgr ) :
  60. m_toolMgr( aMgr ),
  61. m_isBoardEditor( false ),
  62. m_isFootprintEditor( false )
  63. {
  64. EDA_DRAW_FRAME* frame = dynamic_cast<EDA_DRAW_FRAME*>( aMgr->GetToolHolder() );
  65. if( frame && frame->IsType( FRAME_PCB_EDITOR ) )
  66. m_isBoardEditor = true;
  67. else if( frame && frame->IsType( FRAME_FOOTPRINT_EDITOR ) )
  68. m_isFootprintEditor = true;
  69. }
  70. BOARD* BOARD_COMMIT::GetBoard() const
  71. {
  72. return static_cast<BOARD*>( m_toolMgr->GetModel() );
  73. }
  74. COMMIT& BOARD_COMMIT::Stage( EDA_ITEM* aItem, CHANGE_TYPE aChangeType, BASE_SCREEN* aScreen )
  75. {
  76. wxCHECK( aItem, *this );
  77. // Many operations (move, rotate, etc.) are applied directly to a group's children, so they
  78. // must be staged as well.
  79. if( aChangeType == CHT_MODIFY )
  80. {
  81. if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( aItem ) )
  82. {
  83. group->RunOnChildren(
  84. [&]( BOARD_ITEM* child )
  85. {
  86. Stage( child, aChangeType );
  87. } );
  88. }
  89. }
  90. return COMMIT::Stage( aItem, aChangeType );
  91. }
  92. COMMIT& BOARD_COMMIT::Stage( std::vector<EDA_ITEM*>& container, CHANGE_TYPE aChangeType,
  93. BASE_SCREEN* aScreen )
  94. {
  95. return COMMIT::Stage( container, aChangeType, aScreen );
  96. }
  97. COMMIT& BOARD_COMMIT::Stage( const PICKED_ITEMS_LIST& aItems, UNDO_REDO aModFlag,
  98. BASE_SCREEN* aScreen )
  99. {
  100. return COMMIT::Stage( aItems, aModFlag, aScreen );
  101. }
  102. void BOARD_COMMIT::dirtyIntersectingZones( BOARD_ITEM* item, int aChangeType )
  103. {
  104. wxCHECK( item, /* void */ );
  105. ZONE_FILLER_TOOL* zoneFillerTool = m_toolMgr->GetTool<ZONE_FILLER_TOOL>();
  106. if( item->Type() == PCB_ZONE_T )
  107. zoneFillerTool->DirtyZone( static_cast<ZONE*>( item ) );
  108. item->RunOnChildren( std::bind( &BOARD_COMMIT::dirtyIntersectingZones, this, _1, aChangeType ) );
  109. BOARD* board = static_cast<BOARD*>( m_toolMgr->GetModel() );
  110. BOX2I bbox = item->GetBoundingBox();
  111. LSET layers = item->GetLayerSet();
  112. if( layers.test( Edge_Cuts ) || layers.test( Margin ) )
  113. layers = LSET::PhysicalLayersMask();
  114. else
  115. layers &= LSET::AllCuMask();
  116. if( layers.any() )
  117. {
  118. for( ZONE* zone : board->Zones() )
  119. {
  120. if( zone->GetIsRuleArea() )
  121. continue;
  122. if( ( zone->GetLayerSet() & layers ).any()
  123. && zone->GetBoundingBox().Intersects( bbox ) )
  124. {
  125. zoneFillerTool->DirtyZone( zone );
  126. }
  127. }
  128. }
  129. }
  130. void BOARD_COMMIT::Push( const wxString& aMessage, int aCommitFlags )
  131. {
  132. KIGFX::VIEW* view = m_toolMgr->GetView();
  133. BOARD* board = static_cast<BOARD*>( m_toolMgr->GetModel() );
  134. PCB_BASE_FRAME* frame = dynamic_cast<PCB_BASE_FRAME*>( m_toolMgr->GetToolHolder() );
  135. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  136. // Notification info
  137. PICKED_ITEMS_LIST undoList;
  138. bool itemsDeselected = false;
  139. bool selectedModified = false;
  140. // Dirty flags and lists
  141. bool solderMaskDirty = false;
  142. bool autofillZones = false;
  143. std::vector<BOARD_ITEM*> staleTeardropPadsAndVias;
  144. std::set<PCB_TRACK*> staleTeardropTracks;
  145. PCB_GROUP* addedGroup = nullptr;
  146. if( Empty() )
  147. return;
  148. undoList.SetDescription( aMessage );
  149. TEARDROP_MANAGER teardropMgr( board, m_toolMgr );
  150. std::shared_ptr<CONNECTIVITY_DATA> connectivity = board->GetConnectivity();
  151. // Note: frame == nullptr happens in QA tests
  152. std::vector<BOARD_ITEM*> bulkAddedItems;
  153. std::vector<BOARD_ITEM*> bulkRemovedItems;
  154. std::vector<BOARD_ITEM*> itemsChanged;
  155. if( m_isBoardEditor
  156. && !( aCommitFlags & ZONE_FILL_OP )
  157. && ( frame && frame->GetPcbNewSettings()->m_AutoRefillZones ) )
  158. {
  159. autofillZones = true;
  160. for( ZONE* zone : board->Zones() )
  161. zone->CacheBoundingBox();
  162. }
  163. for( COMMIT_LINE& ent : m_changes )
  164. {
  165. BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( ent.m_item );
  166. if( m_isBoardEditor && boardItem )
  167. {
  168. if( boardItem->Type() == PCB_VIA_T || boardItem->Type() == PCB_FOOTPRINT_T
  169. || boardItem->IsOnLayer( F_Mask ) || boardItem->IsOnLayer( B_Mask ) )
  170. {
  171. solderMaskDirty = true;
  172. }
  173. if( !( aCommitFlags & SKIP_TEARDROPS ) )
  174. {
  175. if( boardItem->Type() == PCB_FOOTPRINT_T )
  176. {
  177. for( PAD* pad : static_cast<FOOTPRINT*>( boardItem )->Pads() )
  178. staleTeardropPadsAndVias.push_back( pad );
  179. }
  180. else if( boardItem->Type() == PCB_PAD_T || boardItem->Type() == PCB_VIA_T )
  181. {
  182. staleTeardropPadsAndVias.push_back( boardItem );
  183. }
  184. else if( boardItem->Type() == PCB_TRACE_T || boardItem->Type() == PCB_ARC_T )
  185. {
  186. PCB_TRACK* track = static_cast<PCB_TRACK*>( boardItem );
  187. staleTeardropTracks.insert( track );
  188. std::vector<PAD*> connectedPads;
  189. std::vector<PCB_VIA*> connectedVias;
  190. connectivity->GetConnectedPadsAndVias( track, &connectedPads, &connectedVias );
  191. for( PAD* pad : connectedPads )
  192. staleTeardropPadsAndVias.push_back( pad );
  193. for( PCB_VIA* via : connectedVias )
  194. staleTeardropPadsAndVias.push_back( via );
  195. }
  196. }
  197. }
  198. if( boardItem && boardItem->IsSelected() )
  199. selectedModified = true;
  200. }
  201. // Old teardrops must be removed before connectivity is rebuilt
  202. if( !staleTeardropPadsAndVias.empty() || !staleTeardropTracks.empty() )
  203. teardropMgr.RemoveTeardrops( *this, &staleTeardropPadsAndVias, &staleTeardropTracks );
  204. for( COMMIT_LINE& ent : m_changes )
  205. {
  206. int changeType = ent.m_type & CHT_TYPE;
  207. int changeFlags = ent.m_type & CHT_FLAGS;
  208. BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( ent.m_item );
  209. wxASSERT( ent.m_item );
  210. wxCHECK2( boardItem, continue );
  211. switch( changeType )
  212. {
  213. case CHT_ADD:
  214. if( selTool && selTool->GetEnteredGroup() && !boardItem->GetParentGroup()
  215. && PCB_GROUP::IsGroupableType( boardItem->Type() ) )
  216. {
  217. selTool->GetEnteredGroup()->AddItem( boardItem );
  218. }
  219. if( !( aCommitFlags & SKIP_UNDO ) )
  220. undoList.PushItem( ITEM_PICKER( nullptr, boardItem, UNDO_REDO::NEWITEM ) );
  221. if( !( changeFlags & CHT_DONE ) )
  222. {
  223. if( m_isFootprintEditor )
  224. {
  225. FOOTPRINT* parentFP = board->GetFirstFootprint();
  226. wxCHECK2_MSG( parentFP, continue, "Commit thinks this is footprint editor, but "
  227. "there is no first footprint!" );
  228. parentFP->Add( boardItem );
  229. }
  230. else if( FOOTPRINT* parentFP = boardItem->GetParentFootprint() )
  231. {
  232. parentFP->Add( boardItem );
  233. }
  234. else
  235. {
  236. board->Add( boardItem, ADD_MODE::BULK_INSERT ); // handles connectivity
  237. bulkAddedItems.push_back( boardItem );
  238. }
  239. }
  240. if( boardItem->Type() == PCB_GROUP_T || boardItem->Type() == PCB_GENERATOR_T )
  241. addedGroup = static_cast<PCB_GROUP*>( boardItem );
  242. if( m_isBoardEditor && autofillZones && boardItem->Type() != PCB_MARKER_T )
  243. dirtyIntersectingZones( boardItem, changeType );
  244. if( view && boardItem->Type() != PCB_NETINFO_T )
  245. view->Add( boardItem );
  246. break;
  247. case CHT_REMOVE:
  248. {
  249. FOOTPRINT* parentFP = boardItem->GetParentFootprint();
  250. PCB_GROUP* parentGroup = boardItem->GetParentGroup();
  251. if( !( aCommitFlags & SKIP_UNDO ) )
  252. undoList.PushItem( ITEM_PICKER( nullptr, boardItem, UNDO_REDO::DELETED ) );
  253. if( boardItem->IsSelected() )
  254. {
  255. if( selTool )
  256. selTool->RemoveItemFromSel( boardItem, true /* quiet mode */ );
  257. itemsDeselected = true;
  258. }
  259. if( parentGroup && !( parentGroup->GetFlags() & STRUCT_DELETED ) )
  260. parentGroup->RemoveItem( boardItem );
  261. if( parentFP && !( parentFP->GetFlags() & STRUCT_DELETED ) )
  262. ent.m_parent = parentFP->m_Uuid;
  263. if( m_isBoardEditor && autofillZones && boardItem->Type() != PCB_MARKER_T )
  264. dirtyIntersectingZones( boardItem, changeType );
  265. switch( boardItem->Type() )
  266. {
  267. case PCB_FIELD_T:
  268. static_cast<PCB_FIELD*>( boardItem )->SetVisible( false );
  269. break;
  270. case PCB_TEXT_T:
  271. case PCB_PAD_T:
  272. case PCB_SHAPE_T: // a shape (normally not on copper layers)
  273. case PCB_REFERENCE_IMAGE_T: // a bitmap on an associated layer
  274. case PCB_GENERATOR_T: // a generator on a layer
  275. case PCB_TEXTBOX_T: // a line-wrapped (and optionally bordered) text item
  276. case PCB_TABLE_T: // rows and columns of tablecells
  277. case PCB_TRACE_T: // a track segment (segment on a copper layer)
  278. case PCB_ARC_T: // an arced track segment (segment on a copper layer)
  279. case PCB_VIA_T: // a via (like track segment on a copper layer)
  280. case PCB_DIM_ALIGNED_T: // a dimension (graphic item)
  281. case PCB_DIM_CENTER_T:
  282. case PCB_DIM_RADIAL_T:
  283. case PCB_DIM_ORTHOGONAL_T:
  284. case PCB_DIM_LEADER_T: // a leader dimension
  285. case PCB_TARGET_T: // a target (graphic item)
  286. case PCB_MARKER_T: // a marker used to show something
  287. case PCB_ZONE_T:
  288. case PCB_FOOTPRINT_T:
  289. case PCB_GROUP_T:
  290. if( view )
  291. view->Remove( boardItem );
  292. if( !( changeFlags & CHT_DONE ) )
  293. {
  294. if( parentFP )
  295. {
  296. parentFP->Remove( boardItem );
  297. }
  298. else
  299. {
  300. board->Remove( boardItem, REMOVE_MODE::BULK );
  301. bulkRemovedItems.push_back( boardItem );
  302. }
  303. }
  304. break;
  305. // Metadata items
  306. case PCB_NETINFO_T:
  307. board->Remove( boardItem, REMOVE_MODE::BULK );
  308. bulkRemovedItems.push_back( boardItem );
  309. break;
  310. default: // other types do not need to (or should not) be handled
  311. wxASSERT( false );
  312. break;
  313. }
  314. // The item has been removed from the board; it is now owned by undo/redo.
  315. boardItem->SetFlags( UR_TRANSIENT );
  316. break;
  317. }
  318. case CHT_UNGROUP:
  319. if( PCB_GROUP* group = boardItem->GetParentGroup() )
  320. {
  321. if( !( aCommitFlags & SKIP_UNDO ) )
  322. {
  323. ITEM_PICKER itemWrapper( nullptr, boardItem, UNDO_REDO::UNGROUP );
  324. itemWrapper.SetGroupId( group->m_Uuid );
  325. undoList.PushItem( itemWrapper );
  326. }
  327. group->RemoveItem( boardItem );
  328. }
  329. break;
  330. case CHT_GROUP:
  331. if( addedGroup )
  332. {
  333. addedGroup->AddItem( boardItem );
  334. if( !( aCommitFlags & SKIP_UNDO ) )
  335. undoList.PushItem( ITEM_PICKER( nullptr, boardItem, UNDO_REDO::REGROUP ) );
  336. }
  337. break;
  338. case CHT_MODIFY:
  339. {
  340. BOARD_ITEM* boardItemCopy = dynamic_cast<BOARD_ITEM*>( ent.m_copy );
  341. if( !( aCommitFlags & SKIP_UNDO ) )
  342. {
  343. ITEM_PICKER itemWrapper( nullptr, boardItem, UNDO_REDO::CHANGED );
  344. wxASSERT( boardItemCopy );
  345. itemWrapper.SetLink( boardItemCopy );
  346. undoList.PushItem( itemWrapper );
  347. }
  348. if( !( aCommitFlags & SKIP_CONNECTIVITY ) )
  349. {
  350. if( boardItemCopy )
  351. connectivity->MarkItemNetAsDirty( boardItemCopy );
  352. connectivity->Update( boardItem );
  353. }
  354. if( m_isBoardEditor && autofillZones && boardItem->Type() != PCB_MARKER_T )
  355. {
  356. dirtyIntersectingZones( boardItemCopy, changeType ); // before
  357. dirtyIntersectingZones( boardItem, changeType ); // after
  358. }
  359. if( view )
  360. view->Update( boardItem );
  361. itemsChanged.push_back( boardItem );
  362. // if no undo entry is needed, the copy would create a memory leak
  363. if( aCommitFlags & SKIP_UNDO )
  364. delete ent.m_copy;
  365. break;
  366. }
  367. default:
  368. UNIMPLEMENTED_FOR( boardItem->GetClass() );
  369. break;
  370. }
  371. boardItem->ClearEditFlags();
  372. boardItem->RunOnDescendants(
  373. [&]( BOARD_ITEM* item )
  374. {
  375. item->ClearEditFlags();
  376. } );
  377. }
  378. if( m_isBoardEditor )
  379. {
  380. size_t num_changes = m_changes.size();
  381. if( aCommitFlags & SKIP_CONNECTIVITY )
  382. {
  383. connectivity->ClearRatsnest();
  384. connectivity->ClearLocalRatsnest();
  385. }
  386. else
  387. {
  388. connectivity->RecalculateRatsnest( this );
  389. board->UpdateRatsnestExclusions();
  390. connectivity->ClearLocalRatsnest();
  391. if( frame )
  392. frame->GetCanvas()->RedrawRatsnest();
  393. board->OnRatsnestChanged();
  394. }
  395. if( solderMaskDirty )
  396. {
  397. if( frame )
  398. frame->HideSolderMask();
  399. }
  400. if( !staleTeardropPadsAndVias.empty() || !staleTeardropTracks.empty() )
  401. teardropMgr.UpdateTeardrops( *this, &staleTeardropPadsAndVias, &staleTeardropTracks );
  402. // Log undo items for any connectivity or teardrop changes
  403. for( size_t i = num_changes; i < m_changes.size(); ++i )
  404. {
  405. COMMIT_LINE& ent = m_changes[i];
  406. BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( ent.m_item );
  407. BOARD_ITEM* boardItemCopy = dynamic_cast<BOARD_ITEM*>( ent.m_copy );
  408. wxCHECK2( boardItem, continue );
  409. if( !( aCommitFlags & SKIP_UNDO ) )
  410. {
  411. ITEM_PICKER itemWrapper( nullptr, boardItem, convert( ent.m_type & CHT_TYPE ) );
  412. itemWrapper.SetLink( boardItemCopy );
  413. undoList.PushItem( itemWrapper );
  414. }
  415. else
  416. {
  417. delete ent.m_copy;
  418. }
  419. if( view )
  420. {
  421. if( ( ent.m_type & CHT_TYPE ) == CHT_ADD )
  422. view->Add( boardItem );
  423. else if( ( ent.m_type & CHT_TYPE ) == CHT_REMOVE )
  424. view->Remove( boardItem );
  425. else
  426. view->Update( boardItem );
  427. }
  428. }
  429. }
  430. if( bulkAddedItems.size() > 0 || bulkRemovedItems.size() > 0 || itemsChanged.size() > 0 )
  431. board->OnItemsCompositeUpdate( bulkAddedItems, bulkRemovedItems, itemsChanged );
  432. if( frame )
  433. {
  434. if( !( aCommitFlags & SKIP_UNDO ) )
  435. {
  436. if( aCommitFlags & APPEND_UNDO )
  437. frame->AppendCopyToUndoList( undoList, UNDO_REDO::UNSPECIFIED );
  438. else
  439. frame->SaveCopyInUndoList( undoList, UNDO_REDO::UNSPECIFIED );
  440. }
  441. }
  442. m_toolMgr->PostEvent( { TC_MESSAGE, TA_MODEL_CHANGE, AS_GLOBAL } );
  443. if( itemsDeselected )
  444. m_toolMgr->PostEvent( EVENTS::UnselectedEvent );
  445. if( autofillZones )
  446. m_toolMgr->PostAction( PCB_ACTIONS::zoneFillDirty );
  447. if( selectedModified )
  448. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  449. if( frame )
  450. {
  451. if( !( aCommitFlags & SKIP_SET_DIRTY ) )
  452. frame->OnModify();
  453. else
  454. frame->Update3DView( true, frame->GetPcbNewSettings()->m_Display.m_Live3DRefresh );
  455. }
  456. clear();
  457. }
  458. EDA_ITEM* BOARD_COMMIT::parentObject( EDA_ITEM* aItem ) const
  459. {
  460. return aItem;
  461. }
  462. EDA_ITEM* BOARD_COMMIT::makeImage( EDA_ITEM* aItem ) const
  463. {
  464. return MakeImage( aItem );
  465. }
  466. EDA_ITEM* BOARD_COMMIT::MakeImage( EDA_ITEM* aItem )
  467. {
  468. EDA_ITEM* clone = aItem->Clone();
  469. if( BOARD_ITEM* board_item = dynamic_cast<BOARD_ITEM*>( clone ) )
  470. board_item->SetParentGroup( nullptr );
  471. clone->SetFlags( UR_TRANSIENT );
  472. return clone;
  473. }
  474. void BOARD_COMMIT::Revert()
  475. {
  476. PICKED_ITEMS_LIST undoList;
  477. KIGFX::VIEW* view = m_toolMgr->GetView();
  478. BOARD* board = (BOARD*) m_toolMgr->GetModel();
  479. std::shared_ptr<CONNECTIVITY_DATA> connectivity = board->GetConnectivity();
  480. board->IncrementTimeStamp(); // clear caches
  481. std::vector<BOARD_ITEM*> bulkAddedItems;
  482. std::vector<BOARD_ITEM*> bulkRemovedItems;
  483. std::vector<BOARD_ITEM*> itemsChanged;
  484. for( auto it = m_changes.rbegin(); it != m_changes.rend(); ++it )
  485. {
  486. COMMIT_LINE& ent = *it;
  487. BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( ent.m_item );
  488. int changeType = ent.m_type & CHT_TYPE;
  489. int changeFlags = ent.m_type & CHT_FLAGS;
  490. wxCHECK2( boardItem, continue );
  491. switch( changeType )
  492. {
  493. case CHT_ADD:
  494. // Items are auto-added to the parent group by BOARD_ITEM::Duplicate(), not when
  495. // the commit is pushed.
  496. if( PCB_GROUP* parentGroup = boardItem->GetParentGroup() )
  497. {
  498. if( GetStatus( parentGroup ) == 0 )
  499. parentGroup->RemoveItem( boardItem );
  500. }
  501. if( !( changeFlags & CHT_DONE ) )
  502. break;
  503. view->Remove( boardItem );
  504. connectivity->Remove( boardItem );
  505. if( FOOTPRINT* parentFP = boardItem->GetParentFootprint() )
  506. {
  507. parentFP->Remove( boardItem );
  508. }
  509. else
  510. {
  511. board->Remove( boardItem, REMOVE_MODE::BULK );
  512. bulkRemovedItems.push_back( boardItem );
  513. }
  514. break;
  515. case CHT_REMOVE:
  516. if( !( changeFlags & CHT_DONE ) )
  517. break;
  518. view->Add( boardItem );
  519. connectivity->Add( boardItem );
  520. if( FOOTPRINT* parentFP = dynamic_cast<FOOTPRINT*>( board->GetItem( ent.m_parent ) ) )
  521. {
  522. parentFP->Add( boardItem, ADD_MODE::INSERT );
  523. }
  524. else
  525. {
  526. board->Add( boardItem, ADD_MODE::INSERT );
  527. bulkAddedItems.push_back( boardItem );
  528. }
  529. break;
  530. case CHT_MODIFY:
  531. {
  532. view->Remove( boardItem );
  533. connectivity->Remove( boardItem );
  534. BOARD_ITEM* boardItemCopy = dynamic_cast<BOARD_ITEM*>( ent.m_copy );
  535. wxASSERT( boardItemCopy );
  536. boardItem->SwapItemData( boardItemCopy );
  537. if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( boardItem ) )
  538. {
  539. group->RunOnChildren(
  540. [&]( BOARD_ITEM* child )
  541. {
  542. child->SetParentGroup( group );
  543. } );
  544. }
  545. view->Add( boardItem );
  546. connectivity->Add( boardItem );
  547. itemsChanged.push_back( boardItem );
  548. delete ent.m_copy;
  549. break;
  550. }
  551. default:
  552. UNIMPLEMENTED_FOR( boardItem->GetClass() );
  553. break;
  554. }
  555. boardItem->ClearEditFlags();
  556. }
  557. if( bulkAddedItems.size() > 0 || bulkRemovedItems.size() > 0 || itemsChanged.size() > 0 )
  558. board->OnItemsCompositeUpdate( bulkAddedItems, bulkRemovedItems, itemsChanged );
  559. if( m_isBoardEditor )
  560. {
  561. connectivity->RecalculateRatsnest();
  562. board->UpdateRatsnestExclusions();
  563. board->OnRatsnestChanged();
  564. }
  565. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  566. selTool->RebuildSelection();
  567. // Property panel needs to know about the reselect
  568. m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
  569. clear();
  570. }