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.

412 lines
13 KiB

  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 <macros.h>
  27. #include <board_commit.h>
  28. #include <cleanup_item.h>
  29. #include <pcb_shape.h>
  30. #include <pad.h>
  31. #include <footprint.h>
  32. #include <graphics_cleaner.h>
  33. #include <fix_board_shape.h>
  34. #include <board_design_settings.h>
  35. #include <tool/tool_manager.h>
  36. #include <tools/pad_tool.h>
  37. GRAPHICS_CLEANER::GRAPHICS_CLEANER( DRAWINGS& aDrawings, FOOTPRINT* aParentFootprint,
  38. BOARD_COMMIT& aCommit, TOOL_MANAGER* aToolMgr ) :
  39. m_drawings( aDrawings ),
  40. m_parentFootprint( aParentFootprint ),
  41. m_commit( aCommit ),
  42. m_toolMgr( aToolMgr ),
  43. m_dryRun( true ),
  44. m_epsilon( 0 ),
  45. m_outlinesTolerance( 0 ),
  46. m_itemsList( nullptr )
  47. {
  48. }
  49. void GRAPHICS_CLEANER::CleanupBoard( bool aDryRun,
  50. std::vector<std::shared_ptr<CLEANUP_ITEM>>* aItemsList,
  51. bool aMergeRects, bool aDeleteRedundant, bool aMergePads,
  52. bool aFixBoardOutlines, int aTolerance )
  53. {
  54. m_dryRun = aDryRun;
  55. m_itemsList = aItemsList;
  56. m_outlinesTolerance = aTolerance;
  57. m_epsilon = m_commit.GetBoard()->GetDesignSettings().m_MaxError;
  58. // Clear the flag used to mark some shapes as deleted, in dry run:
  59. for( BOARD_ITEM* drawing : m_drawings )
  60. drawing->ClearFlags( IS_DELETED );
  61. if( aDeleteRedundant )
  62. cleanupShapes();
  63. if( aFixBoardOutlines )
  64. fixBoardOutlines();
  65. if( aMergeRects )
  66. mergeRects();
  67. if( aMergePads )
  68. mergePads();
  69. // Clear the flag used to mark some shapes:
  70. for( BOARD_ITEM* drawing : m_drawings )
  71. drawing->ClearFlags( IS_DELETED );
  72. }
  73. bool equivalent( const VECTOR2I& a, const VECTOR2I& b, int epsilon )
  74. {
  75. return abs( a.x - b.x ) < epsilon && abs( a.y - b.y ) < epsilon;
  76. };
  77. bool GRAPHICS_CLEANER::isNullShape( PCB_SHAPE* aShape )
  78. {
  79. switch( aShape->GetShape() )
  80. {
  81. case SHAPE_T::SEGMENT:
  82. case SHAPE_T::RECTANGLE:
  83. case SHAPE_T::ARC:
  84. return equivalent( aShape->GetStart(), aShape->GetEnd(), m_epsilon );
  85. case SHAPE_T::CIRCLE:
  86. return aShape->GetRadius() == 0;
  87. case SHAPE_T::POLY:
  88. return aShape->GetPointCount() == 0;
  89. case SHAPE_T::BEZIER:
  90. aShape->RebuildBezierToSegmentsPointsList( aShape->GetWidth() );
  91. // If the Bezier points list contains 2 points, it is equivalent to a segment
  92. if( aShape->GetBezierPoints().size() == 2 )
  93. return equivalent( aShape->GetStart(), aShape->GetEnd(), m_epsilon );
  94. // If the Bezier points list contains 1 points, it is equivalent to a point
  95. return aShape->GetBezierPoints().size() < 2;
  96. default:
  97. UNIMPLEMENTED_FOR( aShape->SHAPE_T_asString() );
  98. return false;
  99. }
  100. }
  101. bool GRAPHICS_CLEANER::areEquivalent( PCB_SHAPE* aShape1, PCB_SHAPE* aShape2 )
  102. {
  103. if( aShape1->GetShape() != aShape2->GetShape()
  104. || aShape1->GetLayer() != aShape2->GetLayer()
  105. || aShape1->GetWidth() != aShape2->GetWidth() )
  106. {
  107. return false;
  108. }
  109. switch( aShape1->GetShape() )
  110. {
  111. case SHAPE_T::SEGMENT:
  112. case SHAPE_T::RECTANGLE:
  113. case SHAPE_T::CIRCLE:
  114. return equivalent( aShape1->GetStart(), aShape2->GetStart(), m_epsilon )
  115. && equivalent( aShape1->GetEnd(), aShape2->GetEnd(), m_epsilon );
  116. case SHAPE_T::ARC:
  117. return equivalent( aShape1->GetCenter(), aShape2->GetCenter(), m_epsilon )
  118. && equivalent( aShape1->GetStart(), aShape2->GetStart(), m_epsilon )
  119. && equivalent( aShape1->GetEnd(), aShape2->GetEnd(), m_epsilon );
  120. case SHAPE_T::POLY:
  121. // TODO
  122. return false;
  123. case SHAPE_T::BEZIER:
  124. return equivalent( aShape1->GetStart(), aShape2->GetStart(), m_epsilon )
  125. && equivalent( aShape1->GetEnd(), aShape2->GetEnd(), m_epsilon )
  126. && equivalent( aShape1->GetBezierC1(), aShape2->GetBezierC1(), m_epsilon )
  127. && equivalent( aShape1->GetBezierC2(), aShape2->GetBezierC2(), m_epsilon );
  128. default:
  129. wxFAIL_MSG( wxT( "GRAPHICS_CLEANER::areEquivalent unimplemented for " )
  130. + aShape1->SHAPE_T_asString() );
  131. return false;
  132. }
  133. }
  134. void GRAPHICS_CLEANER::cleanupShapes()
  135. {
  136. // Remove duplicate shapes (2 superimposed identical shapes):
  137. for( auto it = m_drawings.begin(); it != m_drawings.end(); it++ )
  138. {
  139. PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( *it );
  140. if( !shape || shape->HasFlag( IS_DELETED ) )
  141. continue;
  142. if( isNullShape( shape ) )
  143. {
  144. std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_NULL_GRAPHIC );
  145. item->SetItems( shape );
  146. m_itemsList->push_back( item );
  147. if( !m_dryRun )
  148. m_commit.Remove( shape );
  149. continue;
  150. }
  151. for( auto it2 = it + 1; it2 != m_drawings.end(); it2++ )
  152. {
  153. PCB_SHAPE* shape2 = dynamic_cast<PCB_SHAPE*>( *it2 );
  154. if( !shape2 || shape2->HasFlag( IS_DELETED ) )
  155. continue;
  156. if( areEquivalent( shape, shape2 ) )
  157. {
  158. std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_DUPLICATE_GRAPHIC );
  159. item->SetItems( shape2 );
  160. m_itemsList->push_back( item );
  161. shape2->SetFlags(IS_DELETED );
  162. if( !m_dryRun )
  163. m_commit.Remove( shape2 );
  164. }
  165. }
  166. }
  167. }
  168. void GRAPHICS_CLEANER::fixBoardOutlines()
  169. {
  170. if( m_dryRun )
  171. return;
  172. std::vector<PCB_SHAPE*> shapeList;
  173. std::vector<std::unique_ptr<PCB_SHAPE>> newShapes;
  174. for( BOARD_ITEM* item : m_drawings )
  175. {
  176. PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
  177. if( !shape || !shape->IsOnLayer( Edge_Cuts ) )
  178. continue;
  179. shapeList.push_back( shape );
  180. if( !m_dryRun )
  181. m_commit.Modify( shape );
  182. }
  183. ConnectBoardShapes( shapeList, newShapes, m_outlinesTolerance );
  184. std::vector<PCB_SHAPE*> items_to_select;
  185. for( std::unique_ptr<PCB_SHAPE>& ptr : newShapes )
  186. m_commit.Add( ptr.release() );
  187. }
  188. void GRAPHICS_CLEANER::mergeRects()
  189. {
  190. struct SIDE_CANDIDATE
  191. {
  192. SIDE_CANDIDATE( PCB_SHAPE* aShape ) :
  193. start( aShape->GetStart() ),
  194. end( aShape->GetEnd() ),
  195. shape( aShape )
  196. {
  197. if( start.x > end.x || start.y > end.y )
  198. std::swap( start, end );
  199. }
  200. VECTOR2I start;
  201. VECTOR2I end;
  202. PCB_SHAPE* shape;
  203. };
  204. std::vector<SIDE_CANDIDATE*> sides;
  205. std::map<VECTOR2I, std::vector<SIDE_CANDIDATE*>> ptMap;
  206. // First load all the candidates into the side vector and layer maps
  207. for( BOARD_ITEM* item : m_drawings )
  208. {
  209. PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
  210. if( !shape || isNullShape( shape ) || shape->GetShape() != SHAPE_T::SEGMENT )
  211. continue;
  212. if( shape->GetStart().x == shape->GetEnd().x || shape->GetStart().y == shape->GetEnd().y )
  213. {
  214. sides.emplace_back( new SIDE_CANDIDATE( shape ) );
  215. ptMap[ sides.back()->start ].push_back( sides.back() );
  216. }
  217. }
  218. // Now go through the sides and try and match lines into rectangles
  219. for( SIDE_CANDIDATE* side : sides )
  220. {
  221. if( side->shape->HasFlag( IS_DELETED ) )
  222. continue;
  223. SIDE_CANDIDATE* left = nullptr;
  224. SIDE_CANDIDATE* top = nullptr;
  225. SIDE_CANDIDATE* right = nullptr;
  226. SIDE_CANDIDATE* bottom = nullptr;
  227. auto viable = [&]( SIDE_CANDIDATE* aCandidate ) -> bool
  228. {
  229. return aCandidate->shape->GetLayer() == side->shape->GetLayer()
  230. && aCandidate->shape->GetWidth() == side->shape->GetWidth()
  231. && !aCandidate->shape->HasFlag( IS_DELETED );
  232. };
  233. if( side->start.x == side->end.x )
  234. {
  235. // We've found a possible left; see if we have a top
  236. //
  237. left = side;
  238. for( SIDE_CANDIDATE* candidate : ptMap[ left->start ] )
  239. {
  240. if( candidate != left && viable( candidate ) )
  241. {
  242. top = candidate;
  243. break;
  244. }
  245. }
  246. }
  247. else if( side->start.y == side->end.y )
  248. {
  249. // We've found a possible top; see if we have a left
  250. //
  251. top = side;
  252. for( SIDE_CANDIDATE* candidate : ptMap[ top->start ] )
  253. {
  254. if( candidate != top && viable( candidate ) )
  255. {
  256. left = candidate;
  257. break;
  258. }
  259. }
  260. }
  261. if( top && left )
  262. {
  263. // See if we can fill in the other two sides
  264. //
  265. for( SIDE_CANDIDATE* candidate : ptMap[ top->end ] )
  266. {
  267. if( candidate != top && candidate != left && viable( candidate ) )
  268. {
  269. right = candidate;
  270. break;
  271. }
  272. }
  273. for( SIDE_CANDIDATE* candidate : ptMap[ left->end ] )
  274. {
  275. if( candidate != top && candidate != left && viable( candidate ) )
  276. {
  277. bottom = candidate;
  278. break;
  279. }
  280. }
  281. if( right && bottom && right->end == bottom->end )
  282. {
  283. left->shape->SetFlags( IS_DELETED );
  284. top->shape->SetFlags( IS_DELETED );
  285. right->shape->SetFlags( IS_DELETED );
  286. bottom->shape->SetFlags( IS_DELETED );
  287. std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_LINES_TO_RECT );
  288. item->SetItems( left->shape, top->shape, right->shape, bottom->shape );
  289. m_itemsList->push_back( item );
  290. if( !m_dryRun )
  291. {
  292. PCB_SHAPE* rect = new PCB_SHAPE( m_parentFootprint );
  293. rect->SetShape( SHAPE_T::RECTANGLE );
  294. rect->SetFilled( false );
  295. rect->SetStart( top->start );
  296. rect->SetEnd( bottom->end );
  297. rect->SetLayer( top->shape->GetLayer() );
  298. rect->SetStroke( top->shape->GetStroke() );
  299. m_commit.Add( rect );
  300. m_commit.Remove( left->shape );
  301. m_commit.Remove( top->shape );
  302. m_commit.Remove( right->shape );
  303. m_commit.Remove( bottom->shape );
  304. }
  305. }
  306. }
  307. }
  308. for( SIDE_CANDIDATE* side : sides )
  309. delete side;
  310. }
  311. void GRAPHICS_CLEANER::mergePads()
  312. {
  313. wxCHECK_MSG( m_parentFootprint, /*void*/, wxT( "mergePads() is FootprintEditor only" ) );
  314. PAD_TOOL* padTool = m_toolMgr->GetTool<PAD_TOOL>();
  315. std::map<wxString, int> padToNetTieGroupMap = m_parentFootprint->MapPadNumbersToNetTieGroups();
  316. for( PAD* pad : m_parentFootprint->Pads() )
  317. {
  318. // Don't merge a pad that's in a net-tie pad group. (We don't care which group.)
  319. if( padToNetTieGroupMap[ pad->GetNumber() ] >= 0 )
  320. continue;
  321. if( m_commit.GetStatus( m_parentFootprint ) == 0 )
  322. m_commit.Modify( m_parentFootprint );
  323. std::vector<PCB_SHAPE*> shapes = padTool->RecombinePad( pad, m_dryRun );
  324. if( !shapes.empty() )
  325. {
  326. std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_MERGE_PAD );
  327. for( PCB_SHAPE* shape : shapes )
  328. item->AddItem( shape );
  329. item->AddItem( pad );
  330. m_itemsList->push_back( item );
  331. }
  332. }
  333. }