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.

622 lines
19 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors.
  5. * @author Jon Evans <jon@craftyjon.com>
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <bitmaps.h>
  25. #include <board_commit.h>
  26. #include <class_board.h>
  27. #include <class_drawsegment.h>
  28. #include <class_track.h>
  29. #include <class_zone.h>
  30. #include <collectors.h>
  31. #include <confirm.h>
  32. #include <menus_helpers.h>
  33. #include <pcb_edit_frame.h>
  34. #include <trigo.h>
  35. #include <tool/tool_manager.h>
  36. #include <tools/edit_tool.h>
  37. #include <tools/pcb_actions.h>
  38. #include <tools/selection_tool.h>
  39. #include "convert_tool.h"
  40. CONVERT_TOOL::CONVERT_TOOL() :
  41. TOOL_INTERACTIVE( "pcbnew.Convert" ), m_selectionTool( NULL ),
  42. m_menu( NULL ), m_frame( NULL )
  43. {
  44. }
  45. CONVERT_TOOL::~CONVERT_TOOL()
  46. {
  47. delete m_menu;
  48. }
  49. using S_C = SELECTION_CONDITIONS;
  50. using P_S_C = PCB_SELECTION_CONDITIONS;
  51. bool CONVERT_TOOL::Init()
  52. {
  53. m_selectionTool = m_toolMgr->GetTool<SELECTION_TOOL>();
  54. m_frame = getEditFrame<PCB_BASE_FRAME>();
  55. // Create a context menu and make it available through selection tool
  56. m_menu = new CONDITIONAL_MENU( this );
  57. m_menu->SetIcon( refresh_xpm );
  58. m_menu->SetTitle( _( "Convert..." ) );
  59. static KICAD_T convertableTracks[] = { PCB_TRACE_T, PCB_ARC_T, EOT };
  60. auto graphicLines = P_S_C::OnlyGraphicShapeTypes( { S_SEGMENT, S_RECT } ) && P_S_C::SameLayer();
  61. auto trackLines = S_C::MoreThan( 1 ) &&
  62. S_C::OnlyTypes( convertableTracks ) && P_S_C::SameLayer();
  63. auto anyLines = graphicLines || trackLines;
  64. auto anyPolys = ( S_C::OnlyType( PCB_ZONE_AREA_T ) ||
  65. P_S_C::OnlyGraphicShapeTypes( { S_POLYGON, S_RECT } ) );
  66. auto lineToArc = P_S_C::OnlyGraphicShapeTypes( { S_SEGMENT } ) ||
  67. S_C::OnlyType( PCB_TRACE_T );
  68. auto showConvert = anyPolys || anyLines || lineToArc;
  69. m_menu->AddItem( PCB_ACTIONS::convertToPoly, anyLines );
  70. m_menu->AddItem( PCB_ACTIONS::convertToZone, anyLines );
  71. m_menu->AddItem( PCB_ACTIONS::convertToKeepout, anyLines );
  72. m_menu->AddItem( PCB_ACTIONS::convertToLines, anyPolys );
  73. m_menu->AddItem( PCB_ACTIONS::convertToTracks, anyPolys );
  74. m_menu->AddItem( PCB_ACTIONS::convertToArc, lineToArc );
  75. for( std::shared_ptr<ACTION_MENU>& subMenu : m_selectionTool->GetToolMenu().GetSubMenus() )
  76. {
  77. if( dynamic_cast<SPECIAL_TOOLS_CONTEXT_MENU*>( subMenu.get() ) )
  78. static_cast<CONDITIONAL_MENU*>( subMenu.get() )->AddMenu( m_menu, showConvert );
  79. }
  80. return true;
  81. }
  82. int CONVERT_TOOL::LinesToPoly( const TOOL_EVENT& aEvent )
  83. {
  84. auto& selection = m_selectionTool->RequestSelection(
  85. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
  86. {
  87. EditToolSelectionFilter( aCollector,
  88. EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS, sTool );
  89. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  90. {
  91. BOARD_ITEM* item = aCollector[i];
  92. switch( item->Type() )
  93. {
  94. case PCB_LINE_T:
  95. switch( static_cast<DRAWSEGMENT*>( item )->GetShape() )
  96. {
  97. case S_SEGMENT:
  98. case S_RECT:
  99. //case S_ARC: // Not yet
  100. break;
  101. default:
  102. aCollector.Remove( item );
  103. }
  104. break;
  105. case PCB_TRACE_T:
  106. // case PCB_ARC_T: // Not yet
  107. break;
  108. default:
  109. aCollector.Remove( item );
  110. }
  111. }
  112. } );
  113. if( selection.Empty() )
  114. return 0;
  115. // TODO(JE) From a context menu, the selection condition enforces that the items are on
  116. // a single layer. But, you can still trigger this with items on multiple layer selected.
  117. // Technically we should make this work if each contiguous poly shares a layer
  118. PCB_LAYER_ID destLayer = static_cast<BOARD_ITEM*>( selection.Front() )->GetLayer();
  119. SHAPE_POLY_SET polySet = makePolysFromSegs( selection.GetItems() );
  120. polySet.Append( makePolysFromRects( selection.GetItems() ) );
  121. if( polySet.IsEmpty() )
  122. return 0;
  123. BOARD_COMMIT commit( m_frame );
  124. // For now, we convert each outline in the returned shape to its own polygon
  125. std::vector<SHAPE_POLY_SET> polys;
  126. for( int i = 0; i < polySet.OutlineCount(); i++ )
  127. polys.emplace_back( SHAPE_POLY_SET( polySet.COutline( i ) ) );
  128. if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) )
  129. {
  130. for( const SHAPE_POLY_SET& poly : polys )
  131. {
  132. DRAWSEGMENT* graphic = new DRAWSEGMENT;
  133. graphic->SetShape( S_POLYGON );
  134. graphic->SetLayer( destLayer );
  135. graphic->SetPolyShape( poly );
  136. commit.Add( graphic );
  137. }
  138. commit.Push( _( "Convert shapes to polygon" ) );
  139. }
  140. else
  141. {
  142. // Creating zone or keepout
  143. PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  144. BOARD_ITEM_CONTAINER* parent = frame->GetModel();
  145. ZONE_SETTINGS zoneInfo = frame->GetZoneSettings();
  146. int ret;
  147. if( aEvent.IsAction( &PCB_ACTIONS::convertToKeepout ) )
  148. ret = InvokeRuleAreaEditor( frame, &zoneInfo );
  149. else
  150. ret = InvokeCopperZonesEditor( frame, &zoneInfo );
  151. if( ret == wxID_CANCEL )
  152. return 0;
  153. for( const SHAPE_POLY_SET& poly : polys )
  154. {
  155. ZONE_CONTAINER* zone = new ZONE_CONTAINER( parent );
  156. *zone->Outline() = poly;
  157. zone->HatchBorder();
  158. zoneInfo.ExportSetting( *zone );
  159. commit.Add( zone );
  160. }
  161. commit.Push( _( "Convert shapes to zone" ) );
  162. }
  163. return 0;
  164. }
  165. SHAPE_POLY_SET CONVERT_TOOL::makePolysFromSegs( const std::deque<EDA_ITEM*>& aItems )
  166. {
  167. SHAPE_POLY_SET poly;
  168. std::map<VECTOR2I, std::vector<EDA_ITEM*>> connections;
  169. std::set<EDA_ITEM*> used;
  170. std::deque<EDA_ITEM*> toCheck;
  171. for( EDA_ITEM* item : aItems )
  172. {
  173. if( OPT<SEG> seg = getStartEndPoints( item ) )
  174. {
  175. toCheck.push_back( item );
  176. connections[seg->A].emplace_back( item );
  177. connections[seg->B].emplace_back( item );
  178. }
  179. }
  180. while( !toCheck.empty() )
  181. {
  182. EDA_ITEM* candidate = toCheck.front();
  183. toCheck.pop_front();
  184. if( used.count( candidate ) )
  185. continue;
  186. OPT<SEG> seg = getStartEndPoints( candidate );
  187. wxASSERT( seg );
  188. SHAPE_LINE_CHAIN outline;
  189. std::deque<VECTOR2I> points;
  190. // aDirection == true for walking "right" and appending to the end of points
  191. // false for walking "left" and prepending to the beginning
  192. std::function<void( EDA_ITEM*, bool )> process =
  193. [&]( EDA_ITEM* aItem, bool aDirection )
  194. {
  195. if( used.count( aItem ) )
  196. return;
  197. used.insert( aItem );
  198. OPT<SEG> nextSeg = getStartEndPoints( aItem );
  199. wxASSERT( nextSeg );
  200. // The reference point, i.e. last added point in the direction we're headed
  201. VECTOR2I& ref = aDirection ? points.back() : points.front();
  202. // The next point, i.e. the other point on this segment
  203. VECTOR2I& next = ( ref == nextSeg->A ) ? nextSeg->B : nextSeg->A;
  204. if( aDirection )
  205. points.push_back( next );
  206. else
  207. points.push_front( next );
  208. for( EDA_ITEM* neighbor : connections[next] )
  209. process( neighbor, aDirection );
  210. };
  211. // Start with just one point and walk one direction
  212. points.push_back( seg->A );
  213. process( candidate, true );
  214. // check for any candidates on the "left"
  215. EDA_ITEM* left = nullptr;
  216. for( EDA_ITEM* possibleLeft : connections[seg->A] )
  217. {
  218. if( possibleLeft != candidate )
  219. {
  220. left = possibleLeft;
  221. break;
  222. }
  223. }
  224. if( left )
  225. process( left, false );
  226. if( points.size() < 3 )
  227. continue;
  228. for( const VECTOR2I& point : points )
  229. outline.Append( point );
  230. outline.SetClosed( true );
  231. poly.AddOutline( outline );
  232. }
  233. return poly;
  234. }
  235. SHAPE_POLY_SET CONVERT_TOOL::makePolysFromRects( const std::deque<EDA_ITEM*>& aItems )
  236. {
  237. SHAPE_POLY_SET poly;
  238. for( EDA_ITEM* item : aItems )
  239. {
  240. if( item->Type() != PCB_LINE_T )
  241. continue;
  242. DRAWSEGMENT* graphic = static_cast<DRAWSEGMENT*>( item );
  243. if( graphic->GetShape() != S_RECT )
  244. continue;
  245. SHAPE_LINE_CHAIN outline;
  246. VECTOR2I start( graphic->GetStart() );
  247. VECTOR2I end( graphic->GetEnd() );
  248. outline.Append( start );
  249. outline.Append( VECTOR2I( end.x, start.y ) );
  250. outline.Append( end );
  251. outline.Append( VECTOR2I( start.x, end.y ) );
  252. outline.SetClosed( true );
  253. poly.AddOutline( outline );
  254. }
  255. return poly;
  256. }
  257. int CONVERT_TOOL::PolyToLines( const TOOL_EVENT& aEvent )
  258. {
  259. auto& selection = m_selectionTool->RequestSelection(
  260. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
  261. {
  262. EditToolSelectionFilter( aCollector,
  263. EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS, sTool );
  264. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  265. {
  266. BOARD_ITEM* item = aCollector[i];
  267. switch( item->Type() )
  268. {
  269. case PCB_LINE_T:
  270. switch( static_cast<DRAWSEGMENT*>( item )->GetShape() )
  271. {
  272. case S_POLYGON:
  273. break;
  274. case S_RECT:
  275. break;
  276. default:
  277. aCollector.Remove( item );
  278. }
  279. break;
  280. case PCB_ZONE_AREA_T:
  281. break;
  282. default:
  283. aCollector.Remove( item );
  284. }
  285. }
  286. } );
  287. if( selection.Empty() )
  288. return 0;
  289. auto getPolySet =
  290. []( EDA_ITEM* aItem )
  291. {
  292. SHAPE_POLY_SET set;
  293. switch( aItem->Type() )
  294. {
  295. case PCB_ZONE_AREA_T:
  296. set = *static_cast<ZONE_CONTAINER*>( aItem )->Outline();
  297. break;
  298. case PCB_LINE_T:
  299. {
  300. DRAWSEGMENT* graphic = static_cast<DRAWSEGMENT*>( aItem );
  301. if( graphic->GetShape() == S_POLYGON )
  302. {
  303. set = graphic->GetPolyShape();
  304. }
  305. else if( graphic->GetShape() == S_RECT )
  306. {
  307. SHAPE_LINE_CHAIN outline;
  308. VECTOR2I start( graphic->GetStart() );
  309. VECTOR2I end( graphic->GetEnd() );
  310. outline.Append( start );
  311. outline.Append( VECTOR2I( end.x, start.y ) );
  312. outline.Append( end );
  313. outline.Append( VECTOR2I( start.x, end.y ) );
  314. outline.SetClosed( true );
  315. set.AddOutline( outline );
  316. }
  317. else
  318. {
  319. wxFAIL_MSG( "Unhandled graphic shape type in PolyToLines - getPolySet" );
  320. }
  321. break;
  322. }
  323. default:
  324. wxFAIL_MSG( "Unhandled type in PolyToLines - getPolySet" );
  325. break;
  326. }
  327. return set;
  328. };
  329. auto getSegList =
  330. []( SHAPE_POLY_SET& aPoly )
  331. {
  332. std::vector<SEG> segs;
  333. // Our input should be valid polys, so OK to assert here
  334. wxASSERT( aPoly.VertexCount() >= 2 );
  335. for( int i = 1; i < aPoly.VertexCount(); i++ )
  336. segs.emplace_back( SEG( aPoly.CVertex( i - 1 ), aPoly.CVertex( i ) ) );
  337. segs.emplace_back( SEG( aPoly.CVertex( aPoly.VertexCount() - 1 ),
  338. aPoly.CVertex( 0 ) ) );
  339. return segs;
  340. };
  341. BOARD_COMMIT commit( m_frame );
  342. for( EDA_ITEM* item : selection )
  343. {
  344. PCB_LAYER_ID layer = static_cast<BOARD_ITEM*>( item )->GetLayer();
  345. SHAPE_POLY_SET polySet = getPolySet( item );
  346. std::vector<SEG> segs = getSegList( polySet );
  347. if( aEvent.IsAction( &PCB_ACTIONS::convertToLines ) )
  348. {
  349. for( SEG& seg : segs )
  350. {
  351. DRAWSEGMENT* graphic = new DRAWSEGMENT;
  352. graphic->SetShape( S_SEGMENT );
  353. graphic->SetLayer( layer );
  354. graphic->SetStart( wxPoint( seg.A ) );
  355. graphic->SetEnd( wxPoint( seg.B ) );
  356. commit.Add( graphic );
  357. }
  358. }
  359. else
  360. {
  361. PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  362. BOARD_ITEM_CONTAINER* parent = frame->GetModel();
  363. if( !IsCopperLayer( layer ) )
  364. layer = frame->SelectLayer( F_Cu, LSET::AllNonCuMask() );
  365. // Creating tracks
  366. for( SEG& seg : segs )
  367. {
  368. TRACK* track = new TRACK( parent );
  369. track->SetLayer( layer );
  370. track->SetStart( wxPoint( seg.A ) );
  371. track->SetEnd( wxPoint( seg.B ) );
  372. commit.Add( track );
  373. }
  374. }
  375. }
  376. commit.Push( _( "Convert polygons to lines" ) );
  377. return 0;
  378. }
  379. int CONVERT_TOOL::SegmentToArc( const TOOL_EVENT& aEvent )
  380. {
  381. auto& selection = m_selectionTool->RequestSelection(
  382. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
  383. {
  384. EditToolSelectionFilter( aCollector,
  385. EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS, sTool );
  386. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  387. {
  388. BOARD_ITEM* item = aCollector[i];
  389. if( !( item->Type() == PCB_LINE_T || item->Type() == PCB_TRACE_T ) )
  390. aCollector.Remove( item );
  391. }
  392. } );
  393. EDA_ITEM* source = selection.Front();
  394. VECTOR2I start, end, mid;
  395. // Offset the midpoint along the normal a little bit so that it's more obviously an arc
  396. const double offsetRatio = 0.1;
  397. if( OPT<SEG> seg = getStartEndPoints( source ) )
  398. {
  399. start = seg->A;
  400. end = seg->B;
  401. VECTOR2I normal = ( seg->B - seg->A ).Perpendicular().Resize( offsetRatio * seg->Length() );
  402. mid = seg->Center() + normal;
  403. }
  404. else
  405. return -1;
  406. PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  407. BOARD_ITEM_CONTAINER* parent = frame->GetModel();
  408. BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( source );
  409. // Don't continue processing if we don't actually have a board item
  410. if( !boardItem )
  411. return 0;
  412. PCB_LAYER_ID layer = boardItem->GetLayer();
  413. BOARD_COMMIT commit( m_frame );
  414. if( source->Type() == PCB_LINE_T )
  415. {
  416. DRAWSEGMENT* line = static_cast<DRAWSEGMENT*>( source );
  417. DRAWSEGMENT* arc = new DRAWSEGMENT( parent );
  418. VECTOR2I center = GetArcCenter( start, mid, end );
  419. arc->SetShape( S_ARC );
  420. arc->SetLayer( layer );
  421. arc->SetWidth( line->GetWidth() );
  422. arc->SetCenter( wxPoint( center ) );
  423. arc->SetArcStart( wxPoint( start ) );
  424. arc->SetAngle( GetArcAngle( start, mid, end ) );
  425. arc->SetArcEnd( wxPoint( end ) );
  426. commit.Add( arc );
  427. }
  428. else
  429. {
  430. wxASSERT( source->Type() == PCB_TRACE_T );
  431. TRACK* line = static_cast<TRACK*>( source );
  432. ARC* arc = new ARC( parent );
  433. arc->SetLayer( layer );
  434. arc->SetWidth( line->GetWidth() );
  435. arc->SetStart( wxPoint( start ) );
  436. arc->SetMid( wxPoint( mid ) );
  437. arc->SetEnd( wxPoint( end ) );
  438. commit.Add( arc );
  439. }
  440. commit.Push( _( "Create arc from line segment" ) );
  441. return 0;
  442. }
  443. OPT<SEG> CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem )
  444. {
  445. switch( aItem->Type() )
  446. {
  447. case PCB_LINE_T:
  448. {
  449. DRAWSEGMENT* line = static_cast<DRAWSEGMENT*>( aItem );
  450. return boost::make_optional<SEG>( { VECTOR2I( line->GetStart() ),
  451. VECTOR2I( line->GetEnd() ) } );
  452. }
  453. case PCB_TRACE_T:
  454. {
  455. TRACK* line = static_cast<TRACK*>( aItem );
  456. return boost::make_optional<SEG>( { VECTOR2I( line->GetStart() ),
  457. VECTOR2I( line->GetEnd() ) } );
  458. }
  459. case PCB_ARC_T:
  460. {
  461. ARC* arc = static_cast<ARC*>( aItem );
  462. return boost::make_optional<SEG>( { VECTOR2I( arc->GetStart() ),
  463. VECTOR2I( arc->GetEnd() ) } );
  464. }
  465. default:
  466. return NULLOPT;
  467. }
  468. }
  469. void CONVERT_TOOL::setTransitions()
  470. {
  471. Go( &CONVERT_TOOL::LinesToPoly, PCB_ACTIONS::convertToPoly.MakeEvent() );
  472. Go( &CONVERT_TOOL::LinesToPoly, PCB_ACTIONS::convertToZone.MakeEvent() );
  473. Go( &CONVERT_TOOL::LinesToPoly, PCB_ACTIONS::convertToKeepout.MakeEvent() );
  474. Go( &CONVERT_TOOL::PolyToLines, PCB_ACTIONS::convertToLines.MakeEvent() );
  475. Go( &CONVERT_TOOL::PolyToLines, PCB_ACTIONS::convertToTracks.MakeEvent() );
  476. Go( &CONVERT_TOOL::SegmentToArc, PCB_ACTIONS::convertToArc.MakeEvent() );
  477. }