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.

329 lines
10 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-2020 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 <pcb_shape.h>
  29. #include <fp_shape.h>
  30. #include <graphics_cleaner.h>
  31. GRAPHICS_CLEANER::GRAPHICS_CLEANER( DRAWINGS& aDrawings, FOOTPRINT* aParentFootprint,
  32. BOARD_COMMIT& aCommit ) :
  33. m_drawings( aDrawings ),
  34. m_parentFootprint( aParentFootprint ),
  35. m_commit( aCommit ),
  36. m_dryRun( true ),
  37. m_itemsList( nullptr )
  38. {
  39. }
  40. void GRAPHICS_CLEANER::CleanupBoard( bool aDryRun,
  41. std::vector<std::shared_ptr<CLEANUP_ITEM>>* aItemsList,
  42. bool aMergeRects, bool aDeleteRedundant )
  43. {
  44. m_dryRun = aDryRun;
  45. m_itemsList = aItemsList;
  46. // Clear the flag used to mark some segments as deleted, in dry run:
  47. for( BOARD_ITEM* drawing : m_drawings )
  48. drawing->ClearFlags( IS_DELETED );
  49. if( aDeleteRedundant )
  50. cleanupSegments();
  51. if( aMergeRects )
  52. mergeRects();
  53. // Clear the flag used to mark some segments:
  54. for( BOARD_ITEM* drawing : m_drawings )
  55. drawing->ClearFlags( IS_DELETED );
  56. }
  57. bool GRAPHICS_CLEANER::isNullSegment( PCB_SHAPE* aSegment )
  58. {
  59. switch( aSegment->GetShape() )
  60. {
  61. case S_SEGMENT:
  62. case S_RECT:
  63. return aSegment->GetStart() == aSegment->GetEnd();
  64. case S_CIRCLE:
  65. return aSegment->GetRadius() == 0;
  66. case S_ARC:
  67. return aSegment->GetCenter().x == aSegment->GetArcStart().x
  68. && aSegment->GetCenter().y == aSegment->GetArcStart().y;
  69. case S_POLYGON:
  70. return aSegment->GetPointCount() == 0;
  71. case S_CURVE:
  72. aSegment->RebuildBezierToSegmentsPointsList( aSegment->GetWidth() );
  73. return aSegment->GetBezierPoints().empty();
  74. default:
  75. wxFAIL_MSG( "GRAPHICS_CLEANER::isNullSegment unsupported PCB_SHAPE shape: "
  76. + PCB_SHAPE_TYPE_T_asString( aSegment->GetShape() ) );
  77. return false;
  78. }
  79. }
  80. bool GRAPHICS_CLEANER::areEquivalent( PCB_SHAPE* aShape1, PCB_SHAPE* aShape2 )
  81. {
  82. if( aShape1->GetShape() != aShape2->GetShape()
  83. || aShape1->GetLayer() != aShape2->GetLayer()
  84. || aShape1->GetWidth() != aShape2->GetWidth() )
  85. {
  86. return false;
  87. }
  88. switch( aShape1->GetShape() )
  89. {
  90. case S_SEGMENT:
  91. case S_RECT:
  92. case S_CIRCLE:
  93. return aShape1->GetStart() == aShape2->GetStart()
  94. && aShape1->GetEnd() == aShape2->GetEnd();
  95. case S_ARC:
  96. return aShape1->GetCenter() == aShape2->GetCenter()
  97. && aShape1->GetArcStart() == aShape2->GetArcStart()
  98. && aShape1->GetAngle() == aShape2->GetAngle();
  99. case S_POLYGON:
  100. // TODO
  101. return false;
  102. case S_CURVE:
  103. return aShape1->GetBezControl1() == aShape2->GetBezControl1()
  104. && aShape1->GetBezControl2() == aShape2->GetBezControl2()
  105. && aShape1->GetBezierPoints() == aShape2->GetBezierPoints();
  106. default:
  107. wxFAIL_MSG( "GRAPHICS_CLEANER::areEquivalent unsupported PCB_SHAPE shape: "
  108. + PCB_SHAPE_TYPE_T_asString( aShape1->GetShape() ) );
  109. return false;
  110. }
  111. }
  112. void GRAPHICS_CLEANER::cleanupSegments()
  113. {
  114. // Remove duplicate segments (2 superimposed identical segments):
  115. for( auto it = m_drawings.begin(); it != m_drawings.end(); it++ )
  116. {
  117. PCB_SHAPE* segment = dynamic_cast<PCB_SHAPE*>( *it );
  118. if( !segment || segment->GetShape() != S_SEGMENT || segment->HasFlag( IS_DELETED ) )
  119. continue;
  120. if( isNullSegment( segment ) )
  121. {
  122. std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_NULL_GRAPHIC );
  123. item->SetItems( segment );
  124. m_itemsList->push_back( item );
  125. if( !m_dryRun )
  126. m_commit.Removed( segment );
  127. continue;
  128. }
  129. for( auto it2 = it + 1; it2 != m_drawings.end(); it2++ )
  130. {
  131. PCB_SHAPE* segment2 = dynamic_cast<PCB_SHAPE*>( *it2 );
  132. if( !segment2 || segment2->HasFlag( IS_DELETED ) )
  133. continue;
  134. if( areEquivalent( segment, segment2 ) )
  135. {
  136. std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_DUPLICATE_GRAPHIC );
  137. item->SetItems( segment2 );
  138. m_itemsList->push_back( item );
  139. segment2->SetFlags( IS_DELETED );
  140. if( !m_dryRun )
  141. m_commit.Removed( segment2 );
  142. }
  143. }
  144. }
  145. }
  146. void GRAPHICS_CLEANER::mergeRects()
  147. {
  148. struct SIDE_CANDIDATE
  149. {
  150. SIDE_CANDIDATE( PCB_SHAPE* aShape ) :
  151. start( aShape->GetStart() ),
  152. end( aShape->GetEnd() ),
  153. shape( aShape )
  154. {
  155. if( start.x > end.x || start.y > end.y )
  156. std::swap( start, end );
  157. }
  158. wxPoint start;
  159. wxPoint end;
  160. PCB_SHAPE* shape;
  161. };
  162. std::vector<SIDE_CANDIDATE*> sides;
  163. std::map<wxPoint, std::vector<SIDE_CANDIDATE*>> ptMap;
  164. // First load all the candidates into the side vector and layer maps
  165. for( BOARD_ITEM* item : m_drawings )
  166. {
  167. PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
  168. if( !shape || shape->GetShape() != S_SEGMENT )
  169. continue;
  170. if( shape->GetStart().x == shape->GetEnd().x || shape->GetStart().y == shape->GetEnd().y )
  171. {
  172. sides.emplace_back( new SIDE_CANDIDATE( shape ) );
  173. ptMap[ sides.back()->start ].push_back( sides.back() );
  174. }
  175. }
  176. // Now go through the sides and try and match lines into rectangles
  177. for( SIDE_CANDIDATE* side : sides )
  178. {
  179. if( side->shape->HasFlag( IS_DELETED ) )
  180. continue;
  181. SIDE_CANDIDATE* left = nullptr;
  182. SIDE_CANDIDATE* top = nullptr;
  183. SIDE_CANDIDATE* right = nullptr;
  184. SIDE_CANDIDATE* bottom = nullptr;
  185. auto viable = [&]( SIDE_CANDIDATE* aCandidate ) -> bool
  186. {
  187. return aCandidate->shape->GetLayer() == side->shape->GetLayer()
  188. && aCandidate->shape->GetWidth() == side->shape->GetWidth()
  189. && !aCandidate->shape->HasFlag( IS_DELETED );
  190. };
  191. if( side->start.x == side->end.x )
  192. {
  193. // We've found a possible left; see if we have a top
  194. //
  195. left = side;
  196. for( SIDE_CANDIDATE* candidate : ptMap[ left->start ] )
  197. {
  198. if( candidate != left && viable( candidate ) )
  199. {
  200. top = candidate;
  201. break;
  202. }
  203. }
  204. }
  205. else if( side->start.y == side->end.y )
  206. {
  207. // We've found a possible top; see if we have a left
  208. //
  209. top = side;
  210. for( SIDE_CANDIDATE* candidate : ptMap[ top->start ] )
  211. {
  212. if( candidate != top && viable( candidate ) )
  213. {
  214. left = candidate;
  215. break;
  216. }
  217. }
  218. }
  219. if( top && left )
  220. {
  221. // See if we can fill in the other two sides
  222. //
  223. for( SIDE_CANDIDATE* candidate : ptMap[ top->end ] )
  224. {
  225. if( candidate != top && viable( candidate ) )
  226. {
  227. right = candidate;
  228. break;
  229. }
  230. }
  231. for( SIDE_CANDIDATE* candidate : ptMap[ left->end ] )
  232. {
  233. if( candidate != left && viable( candidate ) )
  234. {
  235. bottom = candidate;
  236. break;
  237. }
  238. }
  239. if( right && bottom && right->end == bottom->end )
  240. {
  241. left->shape->SetFlags( IS_DELETED );
  242. top->shape->SetFlags( IS_DELETED );
  243. right->shape->SetFlags( IS_DELETED );
  244. bottom->shape->SetFlags( IS_DELETED );
  245. std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_LINES_TO_RECT );
  246. item->SetItems( left->shape, top->shape, right->shape, bottom->shape );
  247. m_itemsList->push_back( item );
  248. if( !m_dryRun )
  249. {
  250. PCB_SHAPE* rect;
  251. if( m_parentFootprint )
  252. rect = new FP_SHAPE( m_parentFootprint );
  253. else
  254. rect = new PCB_SHAPE();
  255. rect->SetShape( S_RECT );
  256. rect->SetFilled( false );
  257. rect->SetStart( top->start );
  258. rect->SetEnd( bottom->end );
  259. rect->SetLayer( top->shape->GetLayer() );
  260. rect->SetWidth( top->shape->GetWidth() );
  261. m_commit.Add( rect );
  262. m_commit.Remove( left->shape );
  263. m_commit.Remove( top->shape );
  264. m_commit.Remove( right->shape );
  265. m_commit.Remove( bottom->shape );
  266. }
  267. }
  268. }
  269. }
  270. for( SIDE_CANDIDATE* side : sides )
  271. delete side;
  272. }