KiCad PCB EDA Suite
convert_tool.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
5  * @author Jon Evans <[email protected]>
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 
25 #include <bitmaps.h>
26 #include <board.h>
27 #include <board_commit.h>
28 #include <board_design_settings.h>
29 #include <collectors.h>
30 #include <confirm.h>
32 #include <footprint.h>
33 #include <footprint_edit_frame.h>
34 #include <fp_shape.h>
36 #include <menus_helpers.h>
37 #include <pcb_edit_frame.h>
38 #include <pcb_shape.h>
39 #include <pcb_track.h>
40 #include <tool/tool_manager.h>
41 #include <tools/edit_tool.h>
42 #include <tools/pcb_actions.h>
44 #include <trigo.h>
45 #include <zone.h>
46 
47 #include "convert_tool.h"
48 
49 
51  TOOL_INTERACTIVE( "pcbnew.Convert" ),
52  m_selectionTool( nullptr ),
53  m_menu( nullptr ),
54  m_frame( nullptr )
55 {
56 }
57 
58 
60 {
61  delete m_menu;
62 }
63 
64 
67 
68 
70 {
72  m_frame = getEditFrame<PCB_BASE_FRAME>();
73 
74  // Create a context menu and make it available through selection tool
75  m_menu = new CONDITIONAL_MENU( this );
77  m_menu->SetTitle( _( "Create from Selection" ) );
78 
79  static KICAD_T convertibleTracks[] = { PCB_TRACE_T, PCB_ARC_T, EOT };
80  static KICAD_T zones[] = { PCB_ZONE_T, PCB_FP_ZONE_T, EOT };
81 
82  auto graphicLines = P_S_C::OnlyGraphicShapeTypes( { SHAPE_T::SEGMENT,
85  SHAPE_T::ARC } )
86  && P_S_C::SameLayer();
87 
88  auto graphicToTrack = P_S_C::OnlyGraphicShapeTypes( { SHAPE_T::SEGMENT, SHAPE_T::ARC } );
89 
90  auto trackLines = S_C::MoreThan( 1 ) && S_C::OnlyTypes( convertibleTracks )
91  && P_S_C::SameLayer();
92 
93  auto anyLines = graphicLines || trackLines;
94  auto anyPolys = S_C::OnlyTypes( zones )
96 
97  auto lineToArc = S_C::Count( 1 )
99  || S_C::OnlyType( PCB_TRACE_T ) );
100 
101  auto showConvert = anyPolys || anyLines || lineToArc;
102  auto canCreatePolyType = anyLines || anyPolys;
103  auto canCreateTracks = anyPolys || graphicToTrack;
104 
105  m_menu->AddItem( PCB_ACTIONS::convertToPoly, canCreatePolyType );
106  m_menu->AddItem( PCB_ACTIONS::convertToZone, canCreatePolyType );
107  m_menu->AddItem( PCB_ACTIONS::convertToKeepout, canCreatePolyType );
109 
110  // Currently the code exists, but tracks are not really existing in footprints
111  // only segments on copper layers
113  m_menu->AddItem( PCB_ACTIONS::convertToTracks, canCreateTracks );
114 
116 
118  selToolMenu.AddMenu( m_menu, showConvert, 100 );
119 
120  return true;
121 }
122 
123 
125 {
126  FOOTPRINT* parentFootprint = nullptr;
127 
128  auto& selection = m_selectionTool->RequestSelection(
129  []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
130  {
131  for( int i = aCollector.GetCount() - 1; i >= 0; --i )
132  {
133  BOARD_ITEM* item = aCollector[i];
134 
135  switch( item->Type() )
136  {
137  case PCB_SHAPE_T:
138  case PCB_FP_SHAPE_T:
139  switch( static_cast<PCB_SHAPE*>( item )->GetShape() )
140  {
141  case SHAPE_T::SEGMENT:
142  case SHAPE_T::RECT:
143  case SHAPE_T::CIRCLE:
144  case SHAPE_T::ARC:
145  case SHAPE_T::POLY:
146  break;
147 
148  default:
149  aCollector.Remove( item );
150  }
151 
152  break;
153 
154  case PCB_TRACE_T:
155  case PCB_ARC_T:
156  break;
157 
158  case PCB_ZONE_T:
159  case PCB_FP_ZONE_T:
160  break;
161 
162  default:
163  aCollector.Remove( item );
164  }
165  }
166  } );
167 
168  if( selection.Empty() )
169  return 0;
170 
171  PCB_LAYER_ID destLayer = m_frame->GetActiveLayer();
172  SHAPE_POLY_SET polySet = makePolysFromSegs( selection.GetItems() );
173 
174  polySet.Append( makePolysFromRects( selection.GetItems() ) );
175 
176  polySet.Append( makePolysFromCircles( selection.GetItems() ) );
177 
178  polySet.Append( extractPolygons( selection.GetItems() ) );
179 
180  if( polySet.IsEmpty() )
181  return 0;
182 
183  bool isFootprint = m_frame->IsType( FRAME_FOOTPRINT_EDITOR );
184 
185  if( FP_SHAPE* graphic = dynamic_cast<FP_SHAPE*>( selection.Front() ) )
186  parentFootprint = graphic->GetParentFootprint();
187  else if( FP_ZONE* zone = dynamic_cast<FP_ZONE*>( selection.Front() ) )
188  parentFootprint = static_cast<FOOTPRINT*>( zone->GetParent() );
189  else
190  wxFAIL_MSG( "Unimplemented footprint parent in CONVERT_TOOL::CreatePolys" );
191 
192  BOARD_COMMIT commit( m_frame );
193 
194  // For now, we convert each outline in the returned shape to its own polygon
195  std::vector<SHAPE_POLY_SET> polys;
196 
197  for( int i = 0; i < polySet.OutlineCount(); i++ )
198  polys.emplace_back( SHAPE_POLY_SET( polySet.COutline( i ) ) );
199 
200  if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) )
201  {
202  for( const SHAPE_POLY_SET& poly : polys )
203  {
204  PCB_SHAPE* graphic = isFootprint ? new FP_SHAPE( parentFootprint ) : new PCB_SHAPE;
205 
206  graphic->SetShape( SHAPE_T::POLY );
207  graphic->SetFilled( false );
208  graphic->SetWidth( poly.Outline( 0 ).Width() );
209  graphic->SetLayer( destLayer );
210  graphic->SetPolyShape( poly );
211 
212  commit.Add( graphic );
213  }
214 
215  commit.Push( _( "Convert shapes to polygon" ) );
216  }
217  else
218  {
219  // Creating zone or keepout
220  PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
221  BOARD_ITEM_CONTAINER* parent = frame->GetModel();
222  ZONE_SETTINGS zoneInfo = frame->GetZoneSettings();
223 
224  bool nonCopper = IsNonCopperLayer( destLayer );
225  zoneInfo.m_Layers.reset().set( destLayer );
226  zoneInfo.m_Name.Empty();
227 
228  int ret;
229 
230  if( aEvent.IsAction( &PCB_ACTIONS::convertToKeepout ) )
231  {
232  zoneInfo.SetIsRuleArea( true );
233  ret = InvokeRuleAreaEditor( frame, &zoneInfo );
234  }
235  else if( nonCopper )
236  {
237  zoneInfo.SetIsRuleArea( false );
238  ret = InvokeNonCopperZonesEditor( frame, &zoneInfo );
239  }
240  else
241  {
242  zoneInfo.SetIsRuleArea( false );
243  ret = InvokeCopperZonesEditor( frame, &zoneInfo );
244  }
245 
246  if( ret == wxID_CANCEL )
247  return 0;
248 
249  for( const SHAPE_POLY_SET& poly : polys )
250  {
251  ZONE* zone = isFootprint ? new FP_ZONE( parent ) : new ZONE( parent );
252 
253  *zone->Outline() = poly;
254  zone->HatchBorder();
255 
256  zoneInfo.ExportSetting( *zone );
257 
258  commit.Add( zone );
259  }
260 
261  commit.Push( _( "Convert shapes to zone" ) );
262  }
263 
264  return 0;
265 }
266 
267 
268 SHAPE_POLY_SET CONVERT_TOOL::makePolysFromSegs( const std::deque<EDA_ITEM*>& aItems )
269 {
270  // TODO: This code has a somewhat-similar purpose to ConvertOutlineToPolygon but is slightly
271  // different, so this remains a separate algorithm. It might be nice to analyze the dfiferences
272  // in requirements and refactor this.
273 
274  // Very tight epsilon used here to account for rounding errors in import, not sloppy drawing
275  const int chainingEpsilonSquared = SEG::Square( 100 );
276 
277  SHAPE_POLY_SET poly;
278 
279  // Stores pairs of (anchor, item) where anchor == 0 -> SEG.A, anchor == 1 -> SEG.B
280  std::map<VECTOR2I, std::vector<std::pair<int, EDA_ITEM*>>> connections;
281  std::set<EDA_ITEM*> used;
282  std::deque<EDA_ITEM*> toCheck;
283 
284  auto closeEnough =
285  []( VECTOR2I aLeft, VECTOR2I aRight, unsigned aLimit )
286  {
287  return ( aLeft - aRight ).SquaredEuclideanNorm() <= aLimit;
288  };
289 
290  auto findInsertionPoint =
291  [&]( VECTOR2I aPoint ) -> VECTOR2I
292  {
293  if( connections.count( aPoint ) )
294  return aPoint;
295 
296  for( const auto& candidatePair : connections )
297  {
298  if( closeEnough( aPoint, candidatePair.first, chainingEpsilonSquared ) )
299  return candidatePair.first;
300  }
301 
302  return aPoint;
303  };
304 
305  for( EDA_ITEM* item : aItems )
306  {
307  if( OPT<SEG> seg = getStartEndPoints( item, nullptr ) )
308  {
309  toCheck.push_back( item );
310  connections[findInsertionPoint( seg->A )].emplace_back( std::make_pair( 0, item ) );
311  connections[findInsertionPoint( seg->B )].emplace_back( std::make_pair( 1, item ) );
312  }
313  }
314 
315  while( !toCheck.empty() )
316  {
317  EDA_ITEM* candidate = toCheck.front();
318  toCheck.pop_front();
319 
320  if( used.count( candidate ) )
321  continue;
322 
323  int width = -1;
324  SHAPE_LINE_CHAIN outline;
325 
326  auto insert =
327  [&]( EDA_ITEM* aItem, VECTOR2I aAnchor, bool aDirection )
328  {
329  if( aItem->Type() == PCB_ARC_T
330  || ( ( aItem->Type() == PCB_SHAPE_T || aItem->Type() == PCB_FP_SHAPE_T )
331  && static_cast<PCB_SHAPE*>( aItem )->GetShape() == SHAPE_T::ARC ) )
332  {
333  SHAPE_ARC arc;
334 
335  if( aItem->Type() == PCB_ARC_T )
336  {
337  std::shared_ptr<SHAPE> es =
338  static_cast<PCB_ARC*>( aItem )->GetEffectiveShape();
339  arc = *static_cast<SHAPE_ARC*>( es.get() );
340  }
341  else
342  {
343  PCB_SHAPE* ps = static_cast<PCB_SHAPE*>( aItem );
344  arc = SHAPE_ARC( ps->GetStart(), ps->GetArcMid(), ps->GetEnd(),
345  ps->GetWidth() );
346  }
347 
348  if( aDirection )
349  outline.Append( aAnchor == arc.GetP0() ? arc : arc.Reversed() );
350  else
351  outline.Insert( 0, aAnchor == arc.GetP0() ? arc : arc.Reversed() );
352  }
353  else
354  {
355  OPT<SEG> nextSeg = getStartEndPoints( aItem, &width );
356  wxASSERT( nextSeg );
357 
358  VECTOR2I& point = ( aAnchor == nextSeg->A ) ? nextSeg->B : nextSeg->A;
359 
360  if( aDirection )
361  outline.Append( point );
362  else
363  outline.Insert( 0, point );
364  }
365  };
366 
367  // aDirection == true for walking "right" and appending to the end of points
368  // false for walking "left" and prepending to the beginning
369  std::function<void( EDA_ITEM*, VECTOR2I, bool )> process =
370  [&]( EDA_ITEM* aItem, VECTOR2I aAnchor, bool aDirection )
371  {
372  if( used.count( aItem ) )
373  return;
374 
375  used.insert( aItem );
376 
377  insert( aItem, aAnchor, aDirection );
378 
379  OPT<SEG> anchors = getStartEndPoints( aItem, &width );
380  wxASSERT( anchors );
381 
382  VECTOR2I nextAnchor = ( aAnchor == anchors->A ) ? anchors->B : anchors->A;
383 
384  for( std::pair<int, EDA_ITEM*> pair : connections[nextAnchor] )
385  {
386  if( pair.second == aItem )
387  continue;
388 
389  process( pair.second, nextAnchor, aDirection );
390  }
391  };
392 
393  OPT<SEG> anchors = getStartEndPoints( candidate, &width );
394  wxASSERT( anchors );
395 
396  // Start with the first object and walk "right"
397  // Note if the first object is an arc, we don't need to insert its first point here, the
398  // whole arc will be inserted at anchor B inside process()
399  if( !( candidate->Type() == PCB_ARC_T
400  || ( ( candidate->Type() == PCB_SHAPE_T || candidate->Type() == PCB_FP_SHAPE_T )
401  && static_cast<PCB_SHAPE*>( candidate )->GetShape() == SHAPE_T::ARC ) ) )
402  {
403  insert( candidate, anchors->A, true );
404  }
405 
406  process( candidate, anchors->B, true );
407 
408  // check for any candidates on the "left"
409  EDA_ITEM* left = nullptr;
410 
411  for( std::pair<int, EDA_ITEM*> possibleLeft : connections[anchors->A] )
412  {
413  if( possibleLeft.second != candidate )
414  {
415  left = possibleLeft.second;
416  break;
417  }
418  }
419 
420  if( left )
421  process( left, anchors->A, false );
422 
423  if( outline.PointCount() < 3 )
424  continue;
425 
426  outline.SetClosed( true );
427  outline.Simplify();
428 
429  if( width >= 0 )
430  outline.SetWidth( width );
431 
432  poly.AddOutline( outline );
433  }
434 
435  return poly;
436 }
437 
438 
439 SHAPE_POLY_SET CONVERT_TOOL::makePolysFromRects( const std::deque<EDA_ITEM*>& aItems )
440 {
441  SHAPE_POLY_SET poly;
442 
443  for( EDA_ITEM* item : aItems )
444  {
445  if( item->Type() != PCB_SHAPE_T && item->Type() != PCB_FP_SHAPE_T )
446  continue;
447 
448  PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( item );
449 
450  if( graphic->GetShape() != SHAPE_T::RECT )
451  continue;
452 
453  SHAPE_LINE_CHAIN outline;
454  VECTOR2I start( graphic->GetStart() );
455  VECTOR2I end( graphic->GetEnd() );
456 
457  outline.Append( start );
458  outline.Append( VECTOR2I( end.x, start.y ) );
459  outline.Append( end );
460  outline.Append( VECTOR2I( start.x, end.y ) );
461  outline.SetClosed( true );
462 
463  outline.SetWidth( graphic->GetWidth() );
464 
465  poly.AddOutline( outline );
466  }
467 
468  return poly;
469 }
470 
471 
472 SHAPE_POLY_SET CONVERT_TOOL::makePolysFromCircles( const std::deque<EDA_ITEM*>& aItems )
473 {
474  SHAPE_POLY_SET poly;
475 
476  for( EDA_ITEM* item : aItems )
477  {
478  if( item->Type() != PCB_SHAPE_T && item->Type() != PCB_FP_SHAPE_T )
479  continue;
480 
481  PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( item );
482 
483  if( graphic->GetShape() != SHAPE_T::CIRCLE )
484  continue;
485 
486  BOARD_DESIGN_SETTINGS& bds = graphic->GetBoard()->GetDesignSettings();
487  SHAPE_LINE_CHAIN outline;
488 
489  TransformCircleToPolygon( outline, graphic->GetPosition(), graphic->GetRadius(),
490  bds.m_MaxError, ERROR_OUTSIDE );
491 
492  poly.AddOutline( outline );
493  }
494 
495  return poly;
496 }
497 
498 
499 SHAPE_POLY_SET CONVERT_TOOL::extractPolygons( const std::deque<EDA_ITEM*>& aItems )
500 {
501  SHAPE_POLY_SET poly;
502 
503  for( EDA_ITEM* item : aItems )
504  {
505  switch( item->Type() )
506  {
507  case PCB_SHAPE_T:
508  case PCB_FP_SHAPE_T:
509  switch( static_cast<PCB_SHAPE*>( item )->GetShape() )
510  {
511  case SHAPE_T::POLY:
512  poly.Append( static_cast<PCB_SHAPE*>( item )->GetPolyShape() );
513  break;
514 
515  default:
516  continue;
517  }
518 
519  break;
520 
521  case PCB_ZONE_T:
522  case PCB_FP_ZONE_T:
523  poly.Append( *static_cast<ZONE*>( item )->Outline() );
524  break;
525 
526  default:
527  continue;
528  }
529  }
530 
531  return poly;
532 }
533 
534 
536 {
537  auto& selection = m_selectionTool->RequestSelection(
538  []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
539  {
540  for( int i = aCollector.GetCount() - 1; i >= 0; --i )
541  {
542  BOARD_ITEM* item = aCollector[i];
543 
544  switch( item->Type() )
545  {
546  case PCB_SHAPE_T:
547  case PCB_FP_SHAPE_T:
548  switch( static_cast<PCB_SHAPE*>( item )->GetShape() )
549  {
550  case SHAPE_T::SEGMENT:
551  case SHAPE_T::ARC:
552  case SHAPE_T::POLY:
553  case SHAPE_T::RECT:
554  break;
555 
556  default:
557  aCollector.Remove( item );
558  }
559 
560  break;
561 
562  case PCB_ZONE_T:
563  case PCB_FP_ZONE_T:
564  break;
565 
566  default:
567  aCollector.Remove( item );
568  }
569  }
570  } );
571 
572  if( selection.Empty() )
573  return 0;
574 
575  auto getPolySet =
576  []( EDA_ITEM* aItem )
577  {
578  SHAPE_POLY_SET set;
579 
580  switch( aItem->Type() )
581  {
582  case PCB_ZONE_T:
583  case PCB_FP_ZONE_T:
584  set = *static_cast<ZONE*>( aItem )->Outline();
585  break;
586 
587  case PCB_SHAPE_T:
588  case PCB_FP_SHAPE_T:
589  {
590  PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
591 
592  if( graphic->GetShape() == SHAPE_T::POLY )
593  {
594  set = graphic->GetPolyShape();
595  }
596  else if( graphic->GetShape() == SHAPE_T::RECT )
597  {
598  SHAPE_LINE_CHAIN outline;
599  VECTOR2I start( graphic->GetStart() );
600  VECTOR2I end( graphic->GetEnd() );
601 
602  outline.Append( start );
603  outline.Append( VECTOR2I( end.x, start.y ) );
604  outline.Append( end );
605  outline.Append( VECTOR2I( start.x, end.y ) );
606  outline.SetClosed( true );
607 
608  set.AddOutline( outline );
609  }
610  else
611  {
612  wxFAIL_MSG( "Unhandled graphic shape type in PolyToLines - getPolySet" );
613  }
614  break;
615  }
616 
617  default:
618  wxFAIL_MSG( "Unhandled type in PolyToLines - getPolySet" );
619  break;
620  }
621 
622  return set;
623  };
624 
625  auto getSegList =
626  []( SHAPE_POLY_SET& aPoly )
627  {
628  std::vector<SEG> segs;
629 
630  // Our input should be valid polys, so OK to assert here
631  wxASSERT( aPoly.VertexCount() >= 2 );
632 
633  for( int i = 1; i < aPoly.VertexCount(); i++ )
634  segs.emplace_back( SEG( aPoly.CVertex( i - 1 ), aPoly.CVertex( i ) ) );
635 
636  segs.emplace_back( SEG( aPoly.CVertex( aPoly.VertexCount() - 1 ),
637  aPoly.CVertex( 0 ) ) );
638 
639  return segs;
640  };
641 
642  BOARD_COMMIT commit( m_frame );
643  PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
644  FOOTPRINT_EDIT_FRAME* fpEditor = dynamic_cast<FOOTPRINT_EDIT_FRAME*>( m_frame );
645  FOOTPRINT* footprint = nullptr;
646  PCB_LAYER_ID targetLayer = m_frame->GetActiveLayer();
647  PCB_LAYER_ID copperLayer = UNSELECTED_LAYER;
648  BOARD_ITEM_CONTAINER* parent = frame->GetModel();
649 
650  if( fpEditor )
651  footprint = fpEditor->GetBoard()->GetFirstFootprint();
652 
653  auto handleGraphicSeg =
654  [&]( EDA_ITEM* aItem )
655  {
656  if( aItem->Type() != PCB_SHAPE_T && aItem->Type() != PCB_FP_SHAPE_T )
657  return false;
658 
659  PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
660 
661  if( graphic->GetShape() == SHAPE_T::SEGMENT )
662  {
663  PCB_TRACK* track = new PCB_TRACK( parent );
664 
665  track->SetLayer( targetLayer );
666  track->SetStart( graphic->GetStart() );
667  track->SetEnd( graphic->GetEnd() );
668  commit.Add( track );
669 
670  return true;
671  }
672  else if( graphic->GetShape() == SHAPE_T::ARC )
673  {
674  PCB_ARC* arc = new PCB_ARC( parent );
675 
676  arc->SetLayer( targetLayer );
677  arc->SetStart( graphic->GetStart() );
678  arc->SetEnd( graphic->GetEnd() );
679  arc->SetMid( graphic->GetArcMid() );
680  commit.Add( arc );
681 
682  return true;
683  }
684 
685  return false;
686  };
687 
688  for( EDA_ITEM* item : selection )
689  {
690  if( handleGraphicSeg( item ) )
691  continue;
692 
693  SHAPE_POLY_SET polySet = getPolySet( item );
694  std::vector<SEG> segs = getSegList( polySet );
695 
696  if( aEvent.IsAction( &PCB_ACTIONS::convertToLines ) )
697  {
698  for( SEG& seg : segs )
699  {
700  if( fpEditor )
701  {
702  FP_SHAPE* graphic = new FP_SHAPE( footprint, SHAPE_T::SEGMENT );
703 
704  graphic->SetLayer( targetLayer );
705  graphic->SetStart( wxPoint( seg.A ) );
706  graphic->SetStart0( wxPoint( seg.A ) );
707  graphic->SetEnd( wxPoint( seg.B ) );
708  graphic->SetEnd0( wxPoint( seg.B ) );
709  commit.Add( graphic );
710  }
711  else
712  {
713  PCB_SHAPE* graphic = new PCB_SHAPE( nullptr, SHAPE_T::SEGMENT );
714 
715  graphic->SetLayer( targetLayer );
716  graphic->SetStart( wxPoint( seg.A ) );
717  graphic->SetEnd( wxPoint( seg.B ) );
718  commit.Add( graphic );
719  }
720  }
721  }
722  else
723  {
724 
725 
726  if( !IsCopperLayer( targetLayer ) )
727  {
728  if( copperLayer == UNSELECTED_LAYER )
729  copperLayer = frame->SelectOneLayer( F_Cu, LSET::AllNonCuMask() );
730 
731  if( copperLayer == UNDEFINED_LAYER ) // User canceled
732  continue;
733 
734  targetLayer = copperLayer;
735  }
736 
737  // I am really unsure converting a polygon to "tracks" (i.e. segments on
738  // copper layers) make sense for footprints, but anyway this code exists
739  if( fpEditor )
740  {
741  // Creating segments on copper layer
742  for( SEG& seg : segs )
743  {
744  FP_SHAPE* graphic = new FP_SHAPE( footprint, SHAPE_T::SEGMENT );
745  graphic->SetLayer( targetLayer );
746  graphic->SetStart( wxPoint( seg.A ) );
747  graphic->SetStart0( wxPoint( seg.A ) );
748  graphic->SetEnd( wxPoint( seg.B ) );
749  graphic->SetEnd0( wxPoint( seg.B ) );
750  commit.Add( graphic );
751  }
752  }
753  else
754  {
755  // Creating tracks
756  for( SEG& seg : segs )
757  {
758  PCB_TRACK* track = new PCB_TRACK( parent );
759 
760  track->SetLayer( targetLayer );
761  track->SetStart( wxPoint( seg.A ) );
762  track->SetEnd( wxPoint( seg.B ) );
763  commit.Add( track );
764  }
765  }
766  }
767  }
768 
769  commit.Push( _( "Convert polygons to lines" ) );
770 
771  return 0;
772 }
773 
774 
776 {
777  auto& selection = m_selectionTool->RequestSelection(
778  []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
779  {
780  for( int i = aCollector.GetCount() - 1; i >= 0; --i )
781  {
782  BOARD_ITEM* item = aCollector[i];
783 
784  if( !( item->Type() == PCB_SHAPE_T ||
785  item->Type() == PCB_TRACE_T ||
786  item->Type() == PCB_FP_SHAPE_T ) )
787  {
788  aCollector.Remove( item );
789  }
790  }
791  } );
792 
793  EDA_ITEM* source = selection.Front();
794  VECTOR2I start, end, mid;
795 
796  // Offset the midpoint along the normal a little bit so that it's more obviously an arc
797  const double offsetRatio = 0.1;
798 
799  if( OPT<SEG> seg = getStartEndPoints( source, nullptr ) )
800  {
801  start = seg->A;
802  end = seg->B;
803 
804  VECTOR2I normal = ( seg->B - seg->A ).Perpendicular().Resize( offsetRatio * seg->Length() );
805  mid = seg->Center() + normal;
806  }
807  else
808  {
809  return -1;
810  }
811 
812  PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
813  BOARD_ITEM_CONTAINER* parent = frame->GetModel();
814 
815  BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( source );
816 
817  // Don't continue processing if we don't actually have a board item
818  if( !boardItem )
819  return 0;
820 
821  PCB_LAYER_ID layer = boardItem->GetLayer();
822 
823  BOARD_COMMIT commit( m_frame );
824 
825  if( source->Type() == PCB_SHAPE_T || source->Type() == PCB_FP_SHAPE_T )
826  {
827  PCB_SHAPE* line = static_cast<PCB_SHAPE*>( source );
828  PCB_SHAPE* arc = new PCB_SHAPE( parent, SHAPE_T::ARC );
829 
830  VECTOR2I center = CalcArcCenter( start, mid, end );
831 
832  arc->SetFilled( false );
833  arc->SetLayer( layer );
834  arc->SetWidth( line->GetWidth() );
835 
836  arc->SetCenter( wxPoint( center ) );
837  arc->SetStart( wxPoint( start ) );
838  arc->SetEnd( wxPoint( end ) );
839 
840  commit.Add( arc );
841  }
842  else
843  {
844  wxASSERT( source->Type() == PCB_TRACE_T );
845  PCB_TRACK* line = static_cast<PCB_TRACK*>( source );
846  PCB_ARC* arc = new PCB_ARC( parent );
847 
848  arc->SetLayer( layer );
849  arc->SetWidth( line->GetWidth() );
850  arc->SetStart( wxPoint( start ) );
851  arc->SetMid( wxPoint( mid ) );
852  arc->SetEnd( wxPoint( end ) );
853 
854  commit.Add( arc );
855  }
856 
857  commit.Push( _( "Create arc from line segment" ) );
858 
859  return 0;
860 }
861 
862 
864 {
865  switch( aItem->Type() )
866  {
867  case PCB_SHAPE_T:
868  case PCB_FP_SHAPE_T:
869  {
870  PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
871 
872  if( aWidth )
873  *aWidth = shape->GetWidth();
874 
875  return boost::make_optional<SEG>( { VECTOR2I( shape->GetStart() ),
876  VECTOR2I( shape->GetEnd() ) } );
877  }
878 
879  case PCB_TRACE_T:
880  {
881  PCB_TRACK* line = static_cast<PCB_TRACK*>( aItem );
882 
883  if( aWidth )
884  *aWidth = line->GetWidth();
885 
886  return boost::make_optional<SEG>( { VECTOR2I( line->GetStart() ),
887  VECTOR2I( line->GetEnd() ) } );
888  }
889 
890  case PCB_ARC_T:
891  {
892  PCB_ARC* arc = static_cast<PCB_ARC*>( aItem );
893 
894  if( aWidth )
895  *aWidth = arc->GetWidth();
896 
897  return boost::make_optional<SEG>( { VECTOR2I( arc->GetStart() ),
898  VECTOR2I( arc->GetEnd() ) } );
899  }
900 
901  default:
902  return NULLOPT;
903  }
904 }
905 
906 
908 {
915 }
PCB_LAYER_ID SelectOneLayer(PCB_LAYER_ID aDefaultLayer, LSET aNotAllowedLayersMask=LSET(), wxPoint aDlgPosition=wxDefaultPosition)
Show the dialog box for a layer selection.
Definition: sel_layer.cpp:274
void AddMenu(ACTION_MENU *aMenu, const SELECTION_CONDITION &aCondition=SELECTION_CONDITIONS::ShowAlways, int aOrder=ANY_ORDER)
Add a submenu to the menu.
int GetWidth() const
Definition: eda_shape.h:89
int OutlineCount() const
Return the number of vertices in a given outline/hole.
void SetEnd0(const wxPoint &aPoint)
Definition: fp_shape.h:114
static OPT< SEG > getStartEndPoints(EDA_ITEM *aItem, int *aWidth)
Retrieve the start and end points for a generic item.
static TOOL_ACTION convertToTracks
Definition: pcb_actions.h:479
void SetFilled(bool aFlag)
Definition: eda_shape.h:83
void SetEnd(const wxPoint &aEnd)
Definition: eda_shape.h:126
const wxPoint & GetEnd() const
Definition: pcb_track.h:105
static TOOL_ACTION convertToLines
Definition: pcb_actions.h:477
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition: board_item.h:163
This file is part of the common library.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:49
const wxPoint & GetStart() const
Return the starting point of the graphic.
Definition: eda_shape.h:97
void SetIcon(BITMAPS aIcon)
Assign an icon for the entry.
Definition: action_menu.cpp:73
static SELECTION_CONDITION MoreThan(int aNumber)
Create a functor that tests if the number of selected items is greater than the value given as parame...
void SetEnd(const wxPoint &aEnd)
Definition: pcb_track.h:104
COMMIT & Add(EDA_ITEM *aItem)
Notify observers that aItem has been added.
Definition: commit.h:78
SHAPE_LINE_CHAIN & Simplify(bool aRemoveColinear=true)
Simplify the line chain by removing colinear adjacent segments and duplicate vertices.
void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition: eda_shape.h:215
static SELECTION_CONDITION OnlyTypes(const KICAD_T aTypes[])
Create a functor that tests if the selected items are only of given types.
int InvokeNonCopperZonesEditor(PCB_BASE_FRAME *aParent, ZONE_SETTINGS *aSettings)
Function InvokeNonCopperZonesEditor invokes up a modal dialog window for non-copper zone editing.
SHAPE_POLY_SET * Outline()
Definition: zone.h:320
CONDITIONAL_MENU & GetMenu()
Definition: tool_menu.cpp:46
TOOL_MANAGER * m_toolMgr
Definition: tool_base.h:214
bool IsEmpty() const
static LSET AllNonCuMask()
Return a mask holding all layer minus CU layers.
Definition: lset.cpp:782
static SHAPE_POLY_SET makePolysFromCircles(const std::deque< EDA_ITEM * > &aItems)
Try to make polygons from circles.
TOOL_MENU & GetToolMenu()
class PCB_ARC, an arc track segment on a copper layer
Definition: typeinfo.h:97
Class that groups generic conditions for selected items.
wxString m_Name
Definition: zone_settings.h:95
class FP_SHAPE, a footprint edge
Definition: typeinfo.h:93
int GetWidth() const
Definition: pcb_track.h:102
static SELECTION_CONDITION Count(int aNumber)
Create a functor that tests if the number of selected items is equal to the value given as parameter.
PCB_SELECTION_TOOL * m_selectionTool
Definition: convert_tool.h:109
VECTOR2< int > VECTOR2I
Definition: vector2d.h:622
int PointCount() const
Return the number of points (vertices) in this line chain.
static SEG::ecoord Square(int a)
Definition: seg.h:122
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:590
static SELECTION_CONDITION SameLayer()
Creates a functor that tests if selection contains items that belong exclusively to the same layer.
static SELECTION_CONDITION OnlyGraphicShapeTypes(const std::set< SHAPE_T > aTypes)
Create a functor that tests if the selection contains PCB_SHAPE* items of certain shapes.
static SHAPE_POLY_SET makePolysFromRects(const std::deque< EDA_ITEM * > &aItems)
Try to make polygons from rectangles.
int CreatePolys(const TOOL_EVENT &aEvent)
Convert selected lines to a polygon, if possible.
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Define which state (aStateFunc) to go when a certain event arrives (aConditions).
void Remove(int aIndex)
Remove the item at aIndex (first position is 0).
Definition: collector.h:110
search types array terminator (End Of Types)
Definition: typeinfo.h:81
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition: typeinfo.h:77
class PCB_TRACK, a track segment (segment on a copper layer)
Definition: typeinfo.h:95
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
bool IsAction(const TOOL_ACTION *aAction) const
Test if the event contains an action issued upon activation of the given TOOL_ACTION.
Definition: tool_event.cpp:88
virtual ~CONVERT_TOOL()
void Insert(size_t aVertex, const VECTOR2I &aP)
void SetStart(const wxPoint &aStart)
Definition: eda_shape.h:101
virtual PCB_LAYER_ID GetActiveLayer() const
int GetCount() const
Return the number of objects in the list.
Definition: collector.h:82
void SetWidth(int aWidth)
Set the width of all segments in the chain.
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
const wxPoint & GetEnd() const
Return the ending point of the graphic.
Definition: eda_shape.h:122
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition: board.h:318
const auto NULLOPT
Definition: optional.h:9
void ExportSetting(ZONE &aTarget, bool aFullExport=true) const
Function ExportSetting copy settings to a given zone.
void HatchBorder()
Compute the hatch lines depending on the hatch parameters and stores it in the zone's attribute m_bor...
Definition: zone.cpp:941
Represent a set of closed polygons.
SHAPE_LINE_CHAIN & Outline(int aIndex)
void SetMid(const wxPoint &aMid)
Definition: pcb_track.h:270
const VECTOR2I & GetP0() const
Definition: shape_arc.h:111
const ZONE_SETTINGS & GetZoneSettings() const
Generic, UI-independent tool event.
Definition: tool_event.h:152
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aCornerBuffer, const wxPoint &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
bool IsNonCopperLayer(LAYER_NUM aLayerId)
Test whether a layer is a non copper layer.
Definition: layer_ids.h:808
static TOOL_ACTION convertToZone
Definition: pcb_actions.h:475
void SetStart(const wxPoint &aStart)
Definition: pcb_track.h:107
static TOOL_ACTION convertToKeepout
Definition: pcb_actions.h:476
#define _(s)
void SetWidth(int aWidth)
Definition: eda_shape.h:88
Handle a list of polygons defining a copper zone.
Definition: zone.h:56
class ZONE, a copper pour area
Definition: typeinfo.h:105
void SetCenter(const wxPoint &aCenter)
Definition: eda_shape.cpp:419
void SetIsRuleArea(bool aEnable)
void SetStart0(const wxPoint &aPoint)
Definition: fp_shape.h:111
SHAPE_POLY_SET & GetPolyShape()
Definition: eda_shape.h:207
int InvokeRuleAreaEditor(PCB_BASE_FRAME *aCaller, ZONE_SETTINGS *aSettings)
Function InvokeRuleAreaEditor invokes up a modal dialog window for copper zone editing.
bool IsCopperLayer(LAYER_NUM aLayerId)
Tests whether a layer is a copper layer.
Definition: layer_ids.h:797
ZONE_SETTINGS handles zones parameters.
Definition: zone_settings.h:67
virtual BOARD_ITEM_CONTAINER * GetModel() const =0
Definition: seg.h:40
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new hole to the given outline (default: last) and returns its index.
wxPoint GetPosition() const override
Definition: pcb_shape.h:77
PCB_BASE_FRAME * m_frame
Definition: convert_tool.h:111
void SetWidth(int aWidth)
Definition: pcb_track.h:101
static TOOL_ACTION convertToArc
Definition: pcb_actions.h:478
CONDITIONAL_MENU * m_menu
Definition: convert_tool.h:110
bool Init() override
Init() is called once upon a registration of the tool.
void SetTitle(const wxString &aTitle) override
Set title for the menu.
Definition: action_menu.cpp:87
PCB_SELECTION & RequestSelection(CLIENT_SELECTION_FILTER aClientFilter, bool aConfirmLockedItems=false)
Return the current selection set, filtered according to aFlags and aClientFilter.
bool IsType(FRAME_T aType) const
Common, abstract interface for edit frames.
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:65
Used when the right click button is pressed, or when the select tool is in effect.
Definition: collectors.h:240
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
Definition: layer_ids.h:71
static SELECTION_CONDITION OnlyType(KICAD_T aType)
Create a functor that tests if the selected items are only of given type.
class ZONE, managed by a footprint
Definition: typeinfo.h:94
wxPoint GetArcMid() const
Definition: eda_shape.cpp:437
void process(const BOARD_CONNECTED_ITEM *item, int net)
void SetShape(SHAPE_T aShape)
Definition: eda_shape.h:91
The selection tool: currently supports:
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:99
virtual void Push(const wxString &aMessage=wxT("A commit"), bool aCreateUndoEntry=true, bool aSetDirtyBit=true) override
Revert the commit by restoring the modified items state.
boost::optional< T > OPT
Definition: optional.h:7
int InvokeCopperZonesEditor(PCB_BASE_FRAME *aCaller, ZONE_SETTINGS *aSettings)
Function InvokeCopperZonesEditor invokes up a modal dialog window for copper zone editing.
static TOOL_ACTION convertToPoly
Definition: pcb_actions.h:474
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
Definition: board_item.cpp:36
int CreateLines(const TOOL_EVENT &aEvent)
Convert selected polygon-like object to graphic lines, if possible.
SHAPE_ARC Reversed() const
Definition: shape_arc.cpp:584
static SHAPE_POLY_SET makePolysFromSegs(const std::deque< EDA_ITEM * > &aItems)
Try to make polygons from segments in the selected items.
SHAPE_T GetShape() const
Definition: eda_shape.h:92
Abstract interface for BOARD_ITEMs capable of storing other items inside.
BOARD * GetBoard() const
int GetRadius() const
Definition: eda_shape.cpp:466
const VECTOR2I CalcArcCenter(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Determine the center of an arc or circle given three points on its circumference.
Definition: trigo.cpp:454
class PCB_SHAPE, a segment not on copper layers
Definition: typeinfo.h:90
void AddItem(const TOOL_ACTION &aAction, const SELECTION_CONDITION &aCondition, int aOrder=ANY_ORDER)
Add a menu entry to run a TOOL_ACTION on selected items.
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition: board_item.h:142
static SHAPE_POLY_SET extractPolygons(const std::deque< EDA_ITEM * > &aItems)
For any polygon shapes (zones, keepouts, graphic polys) in aItems, extracts the polygon outlines into...
A specialization of ZONE for use in footprints.
Definition: zone.h:945
const wxPoint & GetStart() const
Definition: pcb_track.h:108
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:112
BOARD_ITEM_CONTAINER * GetParentFootprint() const
Definition: board_item.cpp:179
int SegmentToArc(const TOOL_EVENT &aEvent)
Convert selected segment (graphic or track) to an arc of the same type.
Container for design settings for a BOARD object.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Add a new vertex to the contour indexed by aOutline and aHole (defaults to the outline of the last po...