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
24 KiB

5 years ago
5 years ago
5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2004-2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2011 Wayne Stambaugh <stambaughw@verizon.net>
  6. * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors.
  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 <reporter.h>
  26. #include <board_commit.h>
  27. #include <cleanup_item.h>
  28. #include <connectivity/connectivity_algo.h>
  29. #include <connectivity/connectivity_data.h>
  30. #include <tool/tool_manager.h>
  31. #include <tools/pcb_actions.h>
  32. #include <tools/global_edit_tool.h>
  33. #include <drc/drc_rtree.h>
  34. #include <tracks_cleaner.h>
  35. TRACKS_CLEANER::TRACKS_CLEANER( BOARD* aPcb, BOARD_COMMIT& aCommit ) :
  36. m_brd( aPcb ),
  37. m_commit( aCommit ),
  38. m_dryRun( true ),
  39. m_itemsList( nullptr ),
  40. m_reporter( nullptr )
  41. {
  42. }
  43. /* Main cleaning function.
  44. * Delete
  45. * - Redundant points on tracks (merge aligned segments)
  46. * - vias on pad
  47. * - null length segments
  48. */
  49. void TRACKS_CLEANER::CleanupBoard( bool aDryRun,
  50. std::vector<std::shared_ptr<CLEANUP_ITEM> >* aItemsList,
  51. bool aRemoveMisConnected, bool aCleanVias, bool aMergeSegments,
  52. bool aDeleteUnconnected, bool aDeleteTracksinPad,
  53. bool aDeleteDanglingVias, REPORTER* aReporter )
  54. {
  55. m_reporter = aReporter;
  56. bool has_deleted = false;
  57. m_dryRun = aDryRun;
  58. m_itemsList = aItemsList;
  59. if( m_reporter )
  60. {
  61. if( aDryRun )
  62. m_reporter->Report( _( "Checking null tracks and vias..." ) );
  63. else
  64. m_reporter->Report( _( "Removing null tracks and vias..." ) );
  65. wxSafeYield(); // Timeslice to update UI
  66. }
  67. bool removeNullSegments = aMergeSegments || aRemoveMisConnected;
  68. cleanup( aCleanVias, removeNullSegments, aMergeSegments /* dup segments*/, aMergeSegments );
  69. if( m_reporter )
  70. {
  71. if( aDryRun )
  72. m_reporter->Report( _( "Checking redundant tracks..." ) );
  73. else
  74. m_reporter->Report( _( "Removing redundant tracks..." ) );
  75. wxSafeYield(); // Timeslice to update UI
  76. }
  77. // If we didn't remove duplicates above, do it now
  78. if( !aMergeSegments )
  79. cleanup( false, false, true, false );
  80. if( aRemoveMisConnected )
  81. {
  82. if( m_reporter )
  83. {
  84. if( aDryRun )
  85. m_reporter->Report( _( "Checking shorting tracks..." ) );
  86. else
  87. m_reporter->Report( _( "Removing shorting tracks..." ) );
  88. wxSafeYield(); // Timeslice to update UI
  89. }
  90. removeShortingTrackSegments();
  91. }
  92. if( aDeleteTracksinPad )
  93. {
  94. if( m_reporter )
  95. {
  96. if( aDryRun )
  97. m_reporter->Report( _( "Checking tracks in pads..." ) );
  98. else
  99. m_reporter->Report( _( "Removing tracks in pads..." ) );
  100. wxSafeYield(); // Timeslice to update UI
  101. }
  102. deleteTracksInPads();
  103. }
  104. if( aDeleteUnconnected || aDeleteDanglingVias )
  105. {
  106. if( m_reporter )
  107. {
  108. if( aDryRun )
  109. {
  110. m_reporter->Report( _( "Checking dangling tracks and vias..." ) );
  111. }
  112. else
  113. {
  114. if( aDeleteUnconnected )
  115. m_reporter->Report( _( "Removing dangling tracks..." ) );
  116. if( aDeleteDanglingVias )
  117. m_reporter->Report( _( "Removing dangling vias..." ) );
  118. }
  119. wxSafeYield(); // Timeslice to update UI
  120. }
  121. has_deleted = deleteDanglingTracks( aDeleteUnconnected, aDeleteDanglingVias );
  122. }
  123. if( has_deleted && aMergeSegments )
  124. {
  125. if( m_reporter )
  126. {
  127. if( aDryRun )
  128. m_reporter->Report( _( "Checking collinear tracks..." ) );
  129. else
  130. m_reporter->Report( _( "Merging collinear tracks..." ) );
  131. wxSafeYield(); // Timeslice to update UI
  132. }
  133. cleanup( false, false, false, true );
  134. }
  135. }
  136. void TRACKS_CLEANER::removeShortingTrackSegments()
  137. {
  138. std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_brd->GetConnectivity();
  139. std::set<BOARD_ITEM *> toRemove;
  140. for( PCB_TRACK* segment : m_brd->Tracks() )
  141. {
  142. // Assume that the user knows what they are doing
  143. if( segment->IsLocked() )
  144. continue;
  145. for( PAD* testedPad : connectivity->GetConnectedPads( segment ) )
  146. {
  147. if( segment->GetNetCode() != testedPad->GetNetCode() )
  148. {
  149. std::shared_ptr<CLEANUP_ITEM> item;
  150. if( segment->Type() == PCB_VIA_T )
  151. item = std::make_shared<CLEANUP_ITEM>( CLEANUP_SHORTING_VIA );
  152. else
  153. item = std::make_shared<CLEANUP_ITEM>( CLEANUP_SHORTING_TRACK );
  154. item->SetItems( segment );
  155. m_itemsList->push_back( item );
  156. toRemove.insert( segment );
  157. }
  158. }
  159. for( PCB_TRACK* testedTrack : connectivity->GetConnectedTracks( segment ) )
  160. {
  161. if( segment->GetNetCode() != testedTrack->GetNetCode() )
  162. {
  163. std::shared_ptr<CLEANUP_ITEM> item;
  164. if( segment->Type() == PCB_VIA_T )
  165. item = std::make_shared<CLEANUP_ITEM>( CLEANUP_SHORTING_VIA );
  166. else
  167. item = std::make_shared<CLEANUP_ITEM>( CLEANUP_SHORTING_TRACK );
  168. item->SetItems( segment );
  169. m_itemsList->push_back( item );
  170. toRemove.insert( segment );
  171. }
  172. }
  173. }
  174. if( !m_dryRun )
  175. removeItems( toRemove );
  176. }
  177. bool TRACKS_CLEANER::testTrackEndpointIsNode( PCB_TRACK* aTrack, bool aTstStart )
  178. {
  179. // A node is a point where more than 2 items are connected.
  180. const std::list<CN_ITEM*>& items =
  181. m_brd->GetConnectivity()->GetConnectivityAlgo()->ItemEntry( aTrack ).GetItems();
  182. if( items.empty() )
  183. return false;
  184. CN_ITEM* citem = items.front();
  185. if( !citem->Valid() )
  186. return false;
  187. const std::vector<std::shared_ptr<CN_ANCHOR>>& anchors = citem->Anchors();
  188. VECTOR2I refpoint = aTstStart ? aTrack->GetStart() : aTrack->GetEnd();
  189. for( const std::shared_ptr<CN_ANCHOR>& anchor : anchors )
  190. {
  191. if( anchor->Pos() != refpoint )
  192. continue;
  193. // The right anchor point is found: if more than one other item
  194. // (pad, via, track...) is connected, it is a node:
  195. return anchor->ConnectedItemsCount() > 1;
  196. }
  197. return false;
  198. }
  199. bool TRACKS_CLEANER::deleteDanglingTracks( bool aTrack, bool aVia )
  200. {
  201. bool item_erased = false;
  202. bool modified = false;
  203. if( !aTrack && !aVia )
  204. return false;
  205. do // Iterate when at least one track is deleted
  206. {
  207. item_erased = false;
  208. // Ensure the connectivity is up to date, especially after removing a dangling segment
  209. m_brd->BuildConnectivity();
  210. // Keep a duplicate deque to all deleting in the primary
  211. std::deque<PCB_TRACK*> temp_tracks( m_brd->Tracks() );
  212. for( PCB_TRACK* track : temp_tracks )
  213. {
  214. if( track->IsLocked() || ( track->GetFlags() & IS_DELETED ) > 0 )
  215. continue;
  216. if( !aVia && track->Type() == PCB_VIA_T )
  217. continue;
  218. if( !aTrack && ( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T ) )
  219. continue;
  220. // Test if a track (or a via) endpoint is not connected to another track or zone.
  221. if( m_brd->GetConnectivity()->TestTrackEndpointDangling( track, false ) )
  222. {
  223. std::shared_ptr<CLEANUP_ITEM> item;
  224. if( track->Type() == PCB_VIA_T )
  225. item = std::make_shared<CLEANUP_ITEM>( CLEANUP_DANGLING_VIA );
  226. else
  227. item = std::make_shared<CLEANUP_ITEM>( CLEANUP_DANGLING_TRACK );
  228. item->SetItems( track );
  229. m_itemsList->push_back( item );
  230. track->SetFlags( IS_DELETED );
  231. // keep iterating, because a track connected to the deleted track
  232. // now perhaps is not connected and should be deleted
  233. item_erased = true;
  234. if( !m_dryRun )
  235. {
  236. m_brd->Remove( track );
  237. m_commit.Removed( track );
  238. modified = true;
  239. }
  240. }
  241. }
  242. } while( item_erased ); // A segment was erased: test for some new dangling segments
  243. return modified;
  244. }
  245. void TRACKS_CLEANER::deleteTracksInPads()
  246. {
  247. std::set<BOARD_ITEM*> toRemove;
  248. // Delete tracks that start and end on the same pad
  249. std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_brd->GetConnectivity();
  250. for( PCB_TRACK* track : m_brd->Tracks() )
  251. {
  252. if( track->IsLocked() )
  253. continue;
  254. if( track->Type() == PCB_VIA_T )
  255. continue;
  256. // Mark track if connected to pads
  257. for( PAD* pad : connectivity->GetConnectedPads( track ) )
  258. {
  259. if( pad->HitTest( track->GetStart() ) && pad->HitTest( track->GetEnd() ) )
  260. {
  261. SHAPE_POLY_SET poly;
  262. track->TransformShapeToPolygon( poly, track->GetLayer(), 0, ARC_HIGH_DEF,
  263. ERROR_INSIDE );
  264. poly.BooleanSubtract( *pad->GetEffectivePolygon( ERROR_INSIDE ),
  265. SHAPE_POLY_SET::PM_FAST );
  266. if( poly.IsEmpty() )
  267. {
  268. auto item = std::make_shared<CLEANUP_ITEM>( CLEANUP_TRACK_IN_PAD );
  269. item->SetItems( track );
  270. m_itemsList->push_back( item );
  271. toRemove.insert( track );
  272. track->SetFlags( IS_DELETED );
  273. }
  274. }
  275. }
  276. }
  277. if( !m_dryRun )
  278. removeItems( toRemove );
  279. }
  280. /**
  281. * Geometry-based cleanup: duplicate items, null items, colinear items.
  282. */
  283. void TRACKS_CLEANER::cleanup( bool aDeleteDuplicateVias, bool aDeleteNullSegments,
  284. bool aDeleteDuplicateSegments, bool aMergeSegments )
  285. {
  286. DRC_RTREE rtree;
  287. for( PCB_TRACK* track : m_brd->Tracks() )
  288. {
  289. track->ClearFlags( IS_DELETED | SKIP_STRUCT );
  290. rtree.Insert( track, track->GetLayer() );
  291. }
  292. std::set<BOARD_ITEM*> toRemove;
  293. for( PCB_TRACK* track : m_brd->Tracks() )
  294. {
  295. if( track->HasFlag( IS_DELETED ) || track->IsLocked() )
  296. continue;
  297. if( aDeleteDuplicateVias && track->Type() == PCB_VIA_T )
  298. {
  299. PCB_VIA* via = static_cast<PCB_VIA*>( track );
  300. if( via->GetStart() != via->GetEnd() )
  301. via->SetEnd( via->GetStart() );
  302. rtree.QueryColliding( via, via->GetLayer(), via->GetLayer(),
  303. // Filter:
  304. [&]( BOARD_ITEM* aItem ) -> bool
  305. {
  306. return aItem->Type() == PCB_VIA_T
  307. && !aItem->HasFlag( SKIP_STRUCT )
  308. && !aItem->HasFlag( IS_DELETED );
  309. },
  310. // Visitor:
  311. [&]( BOARD_ITEM* aItem ) -> bool
  312. {
  313. PCB_VIA* other = static_cast<PCB_VIA*>( aItem );
  314. if( via->GetPosition() == other->GetPosition()
  315. && via->GetViaType() == other->GetViaType()
  316. && via->GetLayerSet() == other->GetLayerSet() )
  317. {
  318. auto item = std::make_shared<CLEANUP_ITEM>( CLEANUP_REDUNDANT_VIA );
  319. item->SetItems( via );
  320. m_itemsList->push_back( item );
  321. via->SetFlags( IS_DELETED );
  322. toRemove.insert( via );
  323. }
  324. return true;
  325. } );
  326. // To delete through Via on THT pads at same location
  327. // Examine the list of connected pads: if a through pad is found, the via is redundant
  328. for( PAD* pad : m_brd->GetConnectivity()->GetConnectedPads( via ) )
  329. {
  330. const LSET all_cu = LSET::AllCuMask();
  331. if( ( pad->GetLayerSet() & all_cu ) == all_cu )
  332. {
  333. auto item = std::make_shared<CLEANUP_ITEM>( CLEANUP_REDUNDANT_VIA );
  334. item->SetItems( via, pad );
  335. m_itemsList->push_back( item );
  336. via->SetFlags( IS_DELETED );
  337. toRemove.insert( via );
  338. break;
  339. }
  340. }
  341. via->SetFlags( SKIP_STRUCT );
  342. }
  343. if( aDeleteNullSegments && track->Type() != PCB_VIA_T )
  344. {
  345. if( track->IsNull() )
  346. {
  347. auto item = std::make_shared<CLEANUP_ITEM>( CLEANUP_ZERO_LENGTH_TRACK );
  348. item->SetItems( track );
  349. m_itemsList->push_back( item );
  350. track->SetFlags( IS_DELETED );
  351. toRemove.insert( track );
  352. }
  353. }
  354. if( aDeleteDuplicateSegments && track->Type() == PCB_TRACE_T && !track->IsNull() )
  355. {
  356. rtree.QueryColliding( track, track->GetLayer(), track->GetLayer(),
  357. // Filter:
  358. [&]( BOARD_ITEM* aItem ) -> bool
  359. {
  360. return aItem->Type() == PCB_TRACE_T
  361. && !aItem->HasFlag( SKIP_STRUCT )
  362. && !aItem->HasFlag( IS_DELETED )
  363. && !static_cast<PCB_TRACK*>( aItem )->IsNull();
  364. },
  365. // Visitor:
  366. [&]( BOARD_ITEM* aItem ) -> bool
  367. {
  368. PCB_TRACK* other = static_cast<PCB_TRACK*>( aItem );
  369. if( track->IsPointOnEnds( other->GetStart() )
  370. && track->IsPointOnEnds( other->GetEnd() )
  371. && track->GetWidth() == other->GetWidth()
  372. && track->GetLayer() == other->GetLayer() )
  373. {
  374. auto item = std::make_shared<CLEANUP_ITEM>( CLEANUP_DUPLICATE_TRACK );
  375. item->SetItems( track );
  376. m_itemsList->push_back( item );
  377. track->SetFlags( IS_DELETED );
  378. toRemove.insert( track );
  379. }
  380. return true;
  381. } );
  382. track->SetFlags( SKIP_STRUCT );
  383. }
  384. }
  385. if( !m_dryRun )
  386. removeItems( toRemove );
  387. auto mergeSegments =
  388. [&]( std::shared_ptr<CN_CONNECTIVITY_ALGO> connectivity ) -> bool
  389. {
  390. for( PCB_TRACK* segment : m_brd->Tracks() )
  391. {
  392. // one can merge only collinear segments, not vias or arcs.
  393. if( segment->Type() != PCB_TRACE_T )
  394. continue;
  395. if( segment->HasFlag( IS_DELETED ) ) // already taken into account
  396. continue;
  397. // for each end of the segment:
  398. for( CN_ITEM* citem : connectivity->ItemEntry( segment ).GetItems() )
  399. {
  400. // Do not merge an end which has different width tracks attached -- it's a
  401. // common use-case for necking-down a track between pads.
  402. std::vector<PCB_TRACK*> sameWidthCandidates;
  403. std::vector<PCB_TRACK*> differentWidthCandidates;
  404. for( CN_ITEM* connected : citem->ConnectedItems() )
  405. {
  406. if( !connected->Valid() )
  407. continue;
  408. BOARD_CONNECTED_ITEM* candidate = connected->Parent();
  409. if( candidate->Type() == PCB_TRACE_T
  410. && !candidate->HasFlag( IS_DELETED ) )
  411. {
  412. PCB_TRACK* candidateSegment = static_cast<PCB_TRACK*>( candidate );
  413. if( candidateSegment->GetWidth() == segment->GetWidth() )
  414. {
  415. sameWidthCandidates.push_back( candidateSegment );
  416. }
  417. else
  418. {
  419. differentWidthCandidates.push_back( candidateSegment );
  420. break;
  421. }
  422. }
  423. }
  424. if( !differentWidthCandidates.empty() )
  425. continue;
  426. for( PCB_TRACK* candidate : sameWidthCandidates )
  427. {
  428. if( segment->ApproxCollinear( *candidate )
  429. && mergeCollinearSegments( segment, candidate ) )
  430. {
  431. return true;
  432. }
  433. }
  434. }
  435. }
  436. return false;
  437. };
  438. if( aMergeSegments )
  439. {
  440. do
  441. {
  442. while( !m_brd->BuildConnectivity() )
  443. wxSafeYield();
  444. m_connectedItemsCache.clear();
  445. } while( mergeSegments( m_brd->GetConnectivity()->GetConnectivityAlgo() ) );
  446. }
  447. for( PCB_TRACK* track : m_brd->Tracks() )
  448. track->ClearFlags( IS_DELETED | SKIP_STRUCT );
  449. }
  450. const std::vector<BOARD_CONNECTED_ITEM*>& TRACKS_CLEANER::getConnectedItems( PCB_TRACK* aTrack )
  451. {
  452. const std::shared_ptr<CONNECTIVITY_DATA>& connectivity = m_brd->GetConnectivity();
  453. if( m_connectedItemsCache.count( aTrack ) == 0 )
  454. {
  455. m_connectedItemsCache[ aTrack ] =
  456. connectivity->GetConnectedItems( aTrack, { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T,
  457. PCB_PAD_T, PCB_ZONE_T } );
  458. }
  459. return m_connectedItemsCache[ aTrack ];
  460. }
  461. bool TRACKS_CLEANER::mergeCollinearSegments( PCB_TRACK* aSeg1, PCB_TRACK* aSeg2 )
  462. {
  463. if( aSeg1->IsLocked() || aSeg2->IsLocked() )
  464. return false;
  465. // Collect the unique points where the two tracks are connected to other items
  466. std::set<VECTOR2I> pts;
  467. auto collectPts =
  468. [&]( BOARD_CONNECTED_ITEM* citem )
  469. {
  470. if( citem->Type() == PCB_TRACE_T || citem->Type() == PCB_ARC_T
  471. || citem->Type() == PCB_VIA_T )
  472. {
  473. PCB_TRACK* track = static_cast<PCB_TRACK*>( citem );
  474. if( track->IsPointOnEnds( aSeg1->GetStart() ) )
  475. pts.emplace( aSeg1->GetStart() );
  476. if( track->IsPointOnEnds( aSeg1->GetEnd() ) )
  477. pts.emplace( aSeg1->GetEnd() );
  478. if( track->IsPointOnEnds( aSeg2->GetStart() ) )
  479. pts.emplace( aSeg2->GetStart() );
  480. if( track->IsPointOnEnds( aSeg2->GetEnd() ) )
  481. pts.emplace( aSeg2->GetEnd() );
  482. }
  483. else
  484. {
  485. if( citem->HitTest( aSeg1->GetStart(), ( aSeg1->GetWidth() + 1 ) / 2 ) )
  486. pts.emplace( aSeg1->GetStart() );
  487. if( citem->HitTest( aSeg1->GetEnd(), ( aSeg1->GetWidth() + 1 ) / 2 ) )
  488. pts.emplace( aSeg1->GetEnd() );
  489. if( citem->HitTest( aSeg2->GetStart(), ( aSeg2->GetWidth() + 1 ) / 2 ) )
  490. pts.emplace( aSeg2->GetStart() );
  491. if( citem->HitTest( aSeg2->GetEnd(), ( aSeg2->GetWidth() + 1 ) / 2 ) )
  492. pts.emplace( aSeg2->GetEnd() );
  493. }
  494. };
  495. for( BOARD_CONNECTED_ITEM* item : getConnectedItems( aSeg1 ) )
  496. {
  497. if( item != aSeg1 && item != aSeg2 )
  498. collectPts( item );
  499. }
  500. for( BOARD_CONNECTED_ITEM* item : getConnectedItems( aSeg2 ) )
  501. {
  502. if( item != aSeg1 && item != aSeg2 )
  503. collectPts( item );
  504. }
  505. // This means there is a node in the center
  506. if( pts.size() > 2 )
  507. return false;
  508. // Verify the removed point after merging is not a node.
  509. // If it is a node (i.e. if more than one other item is connected, the segments cannot be merged
  510. PCB_TRACK dummy_seg( *aSeg1 );
  511. // Calculate the new ends of the segment to merge, and store them to dummy_seg:
  512. int min_x = std::min( aSeg1->GetStart().x,
  513. std::min( aSeg1->GetEnd().x, std::min( aSeg2->GetStart().x, aSeg2->GetEnd().x ) ) );
  514. int min_y = std::min( aSeg1->GetStart().y,
  515. std::min( aSeg1->GetEnd().y, std::min( aSeg2->GetStart().y, aSeg2->GetEnd().y ) ) );
  516. int max_x = std::max( aSeg1->GetStart().x,
  517. std::max( aSeg1->GetEnd().x, std::max( aSeg2->GetStart().x, aSeg2->GetEnd().x ) ) );
  518. int max_y = std::max( aSeg1->GetStart().y,
  519. std::max( aSeg1->GetEnd().y, std::max( aSeg2->GetStart().y, aSeg2->GetEnd().y ) ) );
  520. if( ( aSeg1->GetStart().x > aSeg1->GetEnd().x )
  521. == ( aSeg1->GetStart().y > aSeg1->GetEnd().y ) )
  522. {
  523. dummy_seg.SetStart( VECTOR2I( min_x, min_y ) );
  524. dummy_seg.SetEnd( VECTOR2I( max_x, max_y ) );
  525. }
  526. else
  527. {
  528. dummy_seg.SetStart( VECTOR2I( min_x, max_y ) );
  529. dummy_seg.SetEnd( VECTOR2I( max_x, min_y ) );
  530. }
  531. // The new ends of the segment must be connected to all of the same points as the original
  532. // segments. If not, the segments cannot be merged.
  533. for( auto& pt : pts )
  534. {
  535. if( !dummy_seg.IsPointOnEnds( pt ) )
  536. return false;
  537. }
  538. // Now find the removed end(s) and stop merging if it is a node:
  539. if( aSeg1->GetStart() != dummy_seg.GetStart() && aSeg1->GetStart() != dummy_seg.GetEnd() )
  540. {
  541. if( testTrackEndpointIsNode( aSeg1, true ) )
  542. return false;
  543. }
  544. if( aSeg1->GetEnd() != dummy_seg.GetStart() && aSeg1->GetEnd() != dummy_seg.GetEnd() )
  545. {
  546. if( testTrackEndpointIsNode( aSeg1, false ) )
  547. return false;
  548. }
  549. std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_MERGE_TRACKS );
  550. item->SetItems( aSeg1, aSeg2 );
  551. m_itemsList->push_back( item );
  552. aSeg2->SetFlags( IS_DELETED );
  553. if( !m_dryRun )
  554. {
  555. m_commit.Modify( aSeg1 );
  556. *aSeg1 = dummy_seg;
  557. m_brd->GetConnectivity()->Update( aSeg1 );
  558. // Merge successful, seg2 has to go away
  559. m_brd->Remove( aSeg2 );
  560. m_commit.Removed( aSeg2 );
  561. }
  562. if( dummy_seg.GetParentGroup() )
  563. dummy_seg.SetParentGroup( nullptr );
  564. return true;
  565. }
  566. void TRACKS_CLEANER::removeItems( std::set<BOARD_ITEM*>& aItems )
  567. {
  568. for( BOARD_ITEM* item : aItems )
  569. {
  570. m_brd->Remove( item );
  571. m_commit.Removed( item );
  572. }
  573. }