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 <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 
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>
31 #include <convert_basic_shapes_to_polygon.h>
32 #include <footprint_edit_frame.h>
33 #include <fp_shape.h>
34 #include <geometry/shape_compound.h>
35 #include <menus_helpers.h>
36 #include <pcb_edit_frame.h>
37 #include <pcb_shape.h>
38 #include <pcb_track.h>
39 #include <tool/tool_manager.h>
40 #include <tools/edit_tool.h>
41 #include <tools/pcb_actions.h>
42 #include <tools/pcb_selection_tool.h>
43 #include <trigo.h>
44 #include <zone.h>
45 
46 #include "convert_tool.h"
47 
48 
CONVERT_TOOL()49 CONVERT_TOOL::CONVERT_TOOL() :
50     TOOL_INTERACTIVE( "pcbnew.Convert" ),
51     m_selectionTool( nullptr ),
52     m_menu( nullptr ),
53     m_frame( nullptr )
54 {
55 }
56 
57 
~CONVERT_TOOL()58 CONVERT_TOOL::~CONVERT_TOOL()
59 {
60     delete m_menu;
61 }
62 
63 
64 using S_C   = SELECTION_CONDITIONS;
65 using P_S_C = PCB_SELECTION_CONDITIONS;
66 
67 
Init()68 bool CONVERT_TOOL::Init()
69 {
70     m_selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
71     m_frame         = getEditFrame<PCB_BASE_FRAME>();
72 
73     // Create a context menu and make it available through selection tool
74     m_menu = new CONDITIONAL_MENU( this );
75     m_menu->SetIcon( BITMAPS::convert );
76     m_menu->SetTitle( _( "Create from Selection" ) );
77 
78     static KICAD_T convertibleTracks[] = { PCB_TRACE_T, PCB_ARC_T, EOT };
79     static KICAD_T zones[]  = { PCB_ZONE_T, PCB_FP_ZONE_T, EOT };
80 
81     auto graphicLines = P_S_C::OnlyGraphicShapeTypes( { SHAPE_T::SEGMENT,
82                                                         SHAPE_T::RECT,
83                                                         SHAPE_T::CIRCLE,
84                                                         SHAPE_T::ARC } )
85                                 && P_S_C::SameLayer();
86 
87     auto graphicToTrack = P_S_C::OnlyGraphicShapeTypes( { SHAPE_T::SEGMENT, SHAPE_T::ARC } );
88 
89     auto trackLines   = S_C::MoreThan( 1 ) && S_C::OnlyTypes( convertibleTracks )
90                                 && P_S_C::SameLayer();
91 
92     auto anyLines     = graphicLines || trackLines;
93     auto anyPolys     = S_C::OnlyTypes( zones )
94                             || P_S_C::OnlyGraphicShapeTypes( { SHAPE_T::POLY, SHAPE_T::RECT } );
95 
96     auto lineToArc = S_C::Count( 1 )
97                          && ( P_S_C::OnlyGraphicShapeTypes( { SHAPE_T::SEGMENT } )
98                                 || S_C::OnlyType( PCB_TRACE_T ) );
99 
100     auto showConvert       = anyPolys || anyLines || lineToArc;
101     auto canCreatePolyType = anyLines || anyPolys;
102     auto canCreateTracks   = anyPolys || graphicToTrack;
103 
104     m_menu->AddItem( PCB_ACTIONS::convertToPoly, canCreatePolyType );
105     m_menu->AddItem( PCB_ACTIONS::convertToZone, canCreatePolyType );
106     m_menu->AddItem( PCB_ACTIONS::convertToKeepout, canCreatePolyType );
107     m_menu->AddItem( PCB_ACTIONS::convertToLines, anyPolys );
108 
109     // Currently the code exists, but tracks are not really existing in footprints
110     // only segments on copper layers
111     if( m_frame->IsType( FRAME_PCB_EDITOR ) )
112         m_menu->AddItem( PCB_ACTIONS::convertToTracks, canCreateTracks );
113 
114     m_menu->AddItem( PCB_ACTIONS::convertToArc, lineToArc );
115 
116     CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
117     selToolMenu.AddMenu( m_menu, showConvert, 100 );
118 
119     return true;
120 }
121 
122 
CreatePolys(const TOOL_EVENT & aEvent)123 int CONVERT_TOOL::CreatePolys( const TOOL_EVENT& aEvent )
124 {
125     FOOTPRINT* parentFootprint = nullptr;
126 
127     auto& selection = m_selectionTool->RequestSelection(
128             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
129             {
130                 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
131                 {
132                     BOARD_ITEM* item = aCollector[i];
133 
134                     switch( item->Type() )
135                     {
136                     case PCB_SHAPE_T:
137                     case PCB_FP_SHAPE_T:
138                         switch( static_cast<PCB_SHAPE*>( item )->GetShape() )
139                         {
140                         case SHAPE_T::SEGMENT:
141                         case SHAPE_T::RECT:
142                         case SHAPE_T::CIRCLE:
143                         case SHAPE_T::ARC:
144                         case SHAPE_T::POLY:
145                             break;
146 
147                         default:
148                             aCollector.Remove( item );
149                         }
150 
151                         break;
152 
153                     case PCB_TRACE_T:
154                     case PCB_ARC_T:
155                         break;
156 
157                     case PCB_ZONE_T:
158                     case PCB_FP_ZONE_T:
159                         break;
160 
161                     default:
162                         aCollector.Remove( item );
163                     }
164                 }
165             } );
166 
167     if( selection.Empty() )
168         return 0;
169 
170     PCB_LAYER_ID   destLayer = m_frame->GetActiveLayer();
171     SHAPE_POLY_SET polySet   = makePolysFromSegs( selection.GetItems() );
172 
173     polySet.Append( makePolysFromRects( selection.GetItems() ) );
174 
175     polySet.Append( makePolysFromCircles( selection.GetItems() ) );
176 
177     polySet.Append( extractPolygons( selection.GetItems() ) );
178 
179     if( polySet.IsEmpty() )
180         return 0;
181 
182     bool isFootprint = m_frame->IsType( FRAME_FOOTPRINT_EDITOR );
183 
184     if( FP_SHAPE* graphic = dynamic_cast<FP_SHAPE*>( selection.Front() ) )
185         parentFootprint = graphic->GetParentFootprint();
186 
187     BOARD_COMMIT commit( m_frame );
188 
189     // For now, we convert each outline in the returned shape to its own polygon
190     std::vector<SHAPE_POLY_SET> polys;
191 
192     for( int i = 0; i < polySet.OutlineCount(); i++ )
193         polys.emplace_back( SHAPE_POLY_SET( polySet.COutline( i ) ) );
194 
195     if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) )
196     {
197         for( const SHAPE_POLY_SET& poly : polys )
198         {
199             PCB_SHAPE* graphic = isFootprint ? new FP_SHAPE( parentFootprint ) : new PCB_SHAPE;
200 
201             graphic->SetShape( SHAPE_T::POLY );
202             graphic->SetFilled( false );
203             graphic->SetWidth( poly.Outline( 0 ).Width() );
204             graphic->SetLayer( destLayer );
205             graphic->SetPolyShape( poly );
206 
207             commit.Add( graphic );
208         }
209 
210         commit.Push( _( "Convert shapes to polygon" ) );
211     }
212     else
213     {
214         // Creating zone or keepout
215         PCB_BASE_EDIT_FRAME*  frame    = getEditFrame<PCB_BASE_EDIT_FRAME>();
216         BOARD_ITEM_CONTAINER* parent   = frame->GetModel();
217         ZONE_SETTINGS         zoneInfo = frame->GetZoneSettings();
218 
219         bool nonCopper = IsNonCopperLayer( destLayer );
220         zoneInfo.m_Layers.reset().set( destLayer );
221         zoneInfo.m_Name.Empty();
222 
223         int ret;
224 
225         if( aEvent.IsAction( &PCB_ACTIONS::convertToKeepout ) )
226         {
227             zoneInfo.SetIsRuleArea( true );
228             ret = InvokeRuleAreaEditor( frame, &zoneInfo );
229         }
230         else if( nonCopper )
231         {
232             zoneInfo.SetIsRuleArea( false );
233             ret = InvokeNonCopperZonesEditor( frame, &zoneInfo );
234         }
235         else
236         {
237             zoneInfo.SetIsRuleArea( false );
238             ret = InvokeCopperZonesEditor( frame, &zoneInfo );
239         }
240 
241         if( ret == wxID_CANCEL )
242             return 0;
243 
244         for( const SHAPE_POLY_SET& poly : polys )
245         {
246             ZONE* zone = isFootprint ? new FP_ZONE( parent ) : new ZONE( parent );
247 
248             *zone->Outline() = poly;
249             zone->HatchBorder();
250 
251             zoneInfo.ExportSetting( *zone );
252 
253             commit.Add( zone );
254         }
255 
256         commit.Push( _( "Convert shapes to zone" ) );
257     }
258 
259     return 0;
260 }
261 
262 
makePolysFromSegs(const std::deque<EDA_ITEM * > & aItems)263 SHAPE_POLY_SET CONVERT_TOOL::makePolysFromSegs( const std::deque<EDA_ITEM*>& aItems )
264 {
265     // TODO: This code has a somewhat-similar purpose to ConvertOutlineToPolygon but is slightly
266     // different, so this remains a separate algorithm.  It might be nice to analyze the dfiferences
267     // in requirements and refactor this.
268 
269     // Very tight epsilon used here to account for rounding errors in import, not sloppy drawing
270     const int chainingEpsilonSquared = SEG::Square( 100 );
271 
272     SHAPE_POLY_SET poly;
273 
274     // Stores pairs of (anchor, item) where anchor == 0 -> SEG.A, anchor == 1 -> SEG.B
275     std::map<VECTOR2I, std::vector<std::pair<int, EDA_ITEM*>>> connections;
276     std::set<EDA_ITEM*> used;
277     std::deque<EDA_ITEM*> toCheck;
278 
279     auto closeEnough =
280             []( VECTOR2I aLeft, VECTOR2I aRight, unsigned aLimit )
281             {
282                 return ( aLeft - aRight ).SquaredEuclideanNorm() <= aLimit;
283             };
284 
285     auto findInsertionPoint =
286             [&]( VECTOR2I aPoint ) -> VECTOR2I
287             {
288                 if( connections.count( aPoint ) )
289                     return aPoint;
290 
291                 for( const auto& candidatePair : connections )
292                 {
293                     if( closeEnough( aPoint, candidatePair.first, chainingEpsilonSquared ) )
294                         return candidatePair.first;
295                 }
296 
297                 return aPoint;
298             };
299 
300     for( EDA_ITEM* item : aItems )
301     {
302         if( OPT<SEG> seg = getStartEndPoints( item, nullptr ) )
303         {
304             toCheck.push_back( item );
305             connections[findInsertionPoint( seg->A )].emplace_back( std::make_pair( 0, item ) );
306             connections[findInsertionPoint( seg->B )].emplace_back( std::make_pair( 1, item ) );
307         }
308     }
309 
310     while( !toCheck.empty() )
311     {
312         EDA_ITEM* candidate = toCheck.front();
313         toCheck.pop_front();
314 
315         if( used.count( candidate ) )
316             continue;
317 
318         int width = -1;
319         SHAPE_LINE_CHAIN outline;
320 
321         auto insert =
322                 [&]( EDA_ITEM* aItem, VECTOR2I aAnchor, bool aDirection )
323                 {
324                     if( aItem->Type() == PCB_ARC_T
325                         || ( ( aItem->Type() == PCB_SHAPE_T || aItem->Type() == PCB_FP_SHAPE_T )
326                              && static_cast<PCB_SHAPE*>( aItem )->GetShape() == SHAPE_T::ARC ) )
327                     {
328                         SHAPE_ARC arc;
329 
330                         if( aItem->Type() == PCB_ARC_T )
331                         {
332                             std::shared_ptr<SHAPE> es =
333                                     static_cast<PCB_ARC*>( aItem )->GetEffectiveShape();
334                             arc = *static_cast<SHAPE_ARC*>( es.get() );
335                         }
336                         else
337                         {
338                             PCB_SHAPE* ps = static_cast<PCB_SHAPE*>( aItem );
339                             arc = SHAPE_ARC( ps->GetStart(), ps->GetArcMid(), ps->GetEnd(),
340                                              ps->GetWidth() );
341                         }
342 
343                         if( aDirection )
344                             outline.Append( aAnchor == arc.GetP0() ? arc : arc.Reversed() );
345                         else
346                             outline.Insert( 0, aAnchor == arc.GetP0() ? arc : arc.Reversed() );
347                     }
348                     else
349                     {
350                         OPT<SEG> nextSeg = getStartEndPoints( aItem, &width );
351                         wxASSERT( nextSeg );
352 
353                         VECTOR2I& point = ( aAnchor == nextSeg->A ) ? nextSeg->B : nextSeg->A;
354 
355                         if( aDirection )
356                             outline.Append( point );
357                         else
358                             outline.Insert( 0, point );
359                     }
360                 };
361 
362         // aDirection == true for walking "right" and appending to the end of points
363         // false for walking "left" and prepending to the beginning
364         std::function<void( EDA_ITEM*, VECTOR2I, bool )> process =
365                 [&]( EDA_ITEM* aItem, VECTOR2I aAnchor, bool aDirection )
366                 {
367                     if( used.count( aItem ) )
368                         return;
369 
370                     used.insert( aItem );
371 
372                     insert( aItem, aAnchor, aDirection );
373 
374                     OPT<SEG> anchors = getStartEndPoints( aItem, &width );
375                     wxASSERT( anchors );
376 
377                     VECTOR2I nextAnchor = ( aAnchor == anchors->A ) ? anchors->B : anchors->A;
378 
379                     for( std::pair<int, EDA_ITEM*> pair : connections[nextAnchor] )
380                     {
381                         if( pair.second == aItem )
382                             continue;
383 
384                         process( pair.second, nextAnchor, aDirection );
385                     }
386                 };
387 
388         OPT<SEG> anchors = getStartEndPoints( candidate, &width );
389         wxASSERT( anchors );
390 
391         // Start with the first object and walk "right"
392         // Note if the first object is an arc, we don't need to insert its first point here, the
393         // whole arc will be inserted at anchor B inside process()
394         if( !( candidate->Type() == PCB_ARC_T
395                || ( ( candidate->Type() == PCB_SHAPE_T || candidate->Type() == PCB_FP_SHAPE_T )
396                     && static_cast<PCB_SHAPE*>( candidate )->GetShape() == SHAPE_T::ARC ) ) )
397         {
398             insert( candidate, anchors->A, true );
399         }
400 
401         process( candidate, anchors->B, true );
402 
403         // check for any candidates on the "left"
404         EDA_ITEM* left = nullptr;
405 
406         for( std::pair<int, EDA_ITEM*> possibleLeft : connections[anchors->A] )
407         {
408             if( possibleLeft.second != candidate )
409             {
410                 left = possibleLeft.second;
411                 break;
412             }
413         }
414 
415         if( left )
416             process( left, anchors->A, false );
417 
418         if( outline.PointCount() < 3 )
419             continue;
420 
421         outline.SetClosed( true );
422         outline.Simplify();
423 
424         if( width >= 0 )
425             outline.SetWidth( width );
426 
427         poly.AddOutline( outline );
428     }
429 
430     return poly;
431 }
432 
433 
makePolysFromRects(const std::deque<EDA_ITEM * > & aItems)434 SHAPE_POLY_SET CONVERT_TOOL::makePolysFromRects( const std::deque<EDA_ITEM*>& aItems )
435 {
436     SHAPE_POLY_SET poly;
437 
438     for( EDA_ITEM* item : aItems )
439     {
440         if( item->Type() != PCB_SHAPE_T && item->Type() != PCB_FP_SHAPE_T )
441             continue;
442 
443         PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( item );
444 
445         if( graphic->GetShape() != SHAPE_T::RECT )
446             continue;
447 
448         SHAPE_LINE_CHAIN outline;
449         VECTOR2I start( graphic->GetStart() );
450         VECTOR2I end( graphic->GetEnd() );
451 
452         outline.Append( start );
453         outline.Append( VECTOR2I( end.x, start.y ) );
454         outline.Append( end );
455         outline.Append( VECTOR2I( start.x, end.y ) );
456         outline.SetClosed( true );
457 
458         outline.SetWidth( graphic->GetWidth() );
459 
460         poly.AddOutline( outline );
461     }
462 
463     return poly;
464 }
465 
466 
makePolysFromCircles(const std::deque<EDA_ITEM * > & aItems)467 SHAPE_POLY_SET CONVERT_TOOL::makePolysFromCircles( const std::deque<EDA_ITEM*>& aItems )
468 {
469     SHAPE_POLY_SET poly;
470 
471     for( EDA_ITEM* item : aItems )
472     {
473         if( item->Type() != PCB_SHAPE_T && item->Type() != PCB_FP_SHAPE_T )
474             continue;
475 
476         PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( item );
477 
478         if( graphic->GetShape() != SHAPE_T::CIRCLE )
479             continue;
480 
481         BOARD_DESIGN_SETTINGS& bds = graphic->GetBoard()->GetDesignSettings();
482         SHAPE_LINE_CHAIN outline;
483 
484         TransformCircleToPolygon( outline, graphic->GetPosition(), graphic->GetRadius(),
485                                   bds.m_MaxError, ERROR_OUTSIDE );
486 
487         poly.AddOutline( outline );
488     }
489 
490     return poly;
491 }
492 
493 
extractPolygons(const std::deque<EDA_ITEM * > & aItems)494 SHAPE_POLY_SET CONVERT_TOOL::extractPolygons( const std::deque<EDA_ITEM*>& aItems )
495 {
496     SHAPE_POLY_SET poly;
497 
498     for( EDA_ITEM* item : aItems )
499     {
500         switch( item->Type() )
501         {
502         case PCB_SHAPE_T:
503             switch( static_cast<PCB_SHAPE*>( item )->GetShape() )
504             {
505             case SHAPE_T::POLY:
506                 poly.Append( static_cast<PCB_SHAPE*>( item )->GetPolyShape() );
507                 break;
508 
509             default:
510                 continue;
511             }
512 
513             break;
514 
515         case PCB_ZONE_T:
516         case PCB_FP_ZONE_T:
517             poly.Append( *static_cast<ZONE*>( item )->Outline() );
518             break;
519 
520         default:
521             continue;
522         }
523     }
524 
525     return poly;
526 }
527 
528 
CreateLines(const TOOL_EVENT & aEvent)529 int CONVERT_TOOL::CreateLines( const TOOL_EVENT& aEvent )
530 {
531     auto& selection = m_selectionTool->RequestSelection(
532             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
533             {
534                 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
535                 {
536                     BOARD_ITEM* item = aCollector[i];
537 
538                     switch( item->Type() )
539                     {
540                     case PCB_SHAPE_T:
541                     case PCB_FP_SHAPE_T:
542                         switch( static_cast<PCB_SHAPE*>( item )->GetShape() )
543                         {
544                         case SHAPE_T::SEGMENT:
545                         case SHAPE_T::ARC:
546                         case SHAPE_T::POLY:
547                         case SHAPE_T::RECT:
548                             break;
549 
550                         default:
551                             aCollector.Remove( item );
552                         }
553 
554                         break;
555 
556                     case PCB_ZONE_T:
557                     case PCB_FP_ZONE_T:
558                         break;
559 
560                     default:
561                         aCollector.Remove( item );
562                     }
563                 }
564             } );
565 
566     if( selection.Empty() )
567         return 0;
568 
569     auto getPolySet =
570             []( EDA_ITEM* aItem )
571             {
572                 SHAPE_POLY_SET set;
573 
574                 switch( aItem->Type() )
575                 {
576                 case PCB_ZONE_T:
577                 case PCB_FP_ZONE_T:
578                     set = *static_cast<ZONE*>( aItem )->Outline();
579                     break;
580 
581                 case PCB_SHAPE_T:
582                 case PCB_FP_SHAPE_T:
583                 {
584                     PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
585 
586                     if( graphic->GetShape() == SHAPE_T::POLY )
587                     {
588                         set = graphic->GetPolyShape();
589                     }
590                     else if( graphic->GetShape() == SHAPE_T::RECT )
591                     {
592                         SHAPE_LINE_CHAIN outline;
593                         VECTOR2I start( graphic->GetStart() );
594                         VECTOR2I end( graphic->GetEnd() );
595 
596                         outline.Append( start );
597                         outline.Append( VECTOR2I( end.x, start.y ) );
598                         outline.Append( end );
599                         outline.Append( VECTOR2I( start.x, end.y ) );
600                         outline.SetClosed( true );
601 
602                         set.AddOutline( outline );
603                     }
604                     else
605                     {
606                         wxFAIL_MSG( "Unhandled graphic shape type in PolyToLines - getPolySet" );
607                     }
608                     break;
609                 }
610 
611                 default:
612                     wxFAIL_MSG( "Unhandled type in PolyToLines - getPolySet" );
613                     break;
614                 }
615 
616                 return set;
617             };
618 
619     auto getSegList =
620             []( SHAPE_POLY_SET& aPoly )
621             {
622                 std::vector<SEG> segs;
623 
624                 // Our input should be valid polys, so OK to assert here
625                 wxASSERT( aPoly.VertexCount() >= 2 );
626 
627                 for( int i = 1; i < aPoly.VertexCount(); i++ )
628                     segs.emplace_back( SEG( aPoly.CVertex( i - 1 ), aPoly.CVertex( i ) ) );
629 
630                 segs.emplace_back( SEG( aPoly.CVertex( aPoly.VertexCount() - 1 ),
631                                         aPoly.CVertex( 0 ) ) );
632 
633                 return segs;
634             };
635 
636     BOARD_COMMIT          commit( m_frame );
637     PCB_BASE_EDIT_FRAME*  frame       = getEditFrame<PCB_BASE_EDIT_FRAME>();
638     FOOTPRINT_EDIT_FRAME* fpEditor    = dynamic_cast<FOOTPRINT_EDIT_FRAME*>( m_frame );
639     FOOTPRINT*            footprint   = nullptr;
640     PCB_LAYER_ID          targetLayer = m_frame->GetActiveLayer();
641     PCB_LAYER_ID          copperLayer = UNSELECTED_LAYER;
642     BOARD_ITEM_CONTAINER* parent      = frame->GetModel();
643 
644     if( fpEditor )
645         footprint = fpEditor->GetBoard()->GetFirstFootprint();
646 
647     auto handleGraphicSeg =
648             [&]( EDA_ITEM* aItem )
649             {
650                 if( aItem->Type() != PCB_SHAPE_T && aItem->Type() != PCB_FP_SHAPE_T )
651                     return false;
652 
653                 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
654 
655                 if( graphic->GetShape() == SHAPE_T::SEGMENT )
656                 {
657                     PCB_TRACK* track = new PCB_TRACK( parent );
658 
659                     track->SetLayer( targetLayer );
660                     track->SetStart( graphic->GetStart() );
661                     track->SetEnd( graphic->GetEnd() );
662                     commit.Add( track );
663 
664                     return true;
665                 }
666                 else if( graphic->GetShape() == SHAPE_T::ARC )
667                 {
668                     PCB_ARC* arc = new PCB_ARC( parent );
669 
670                     arc->SetLayer( targetLayer );
671                     arc->SetStart( graphic->GetStart() );
672                     arc->SetEnd( graphic->GetEnd() );
673                     arc->SetMid( graphic->GetArcMid() );
674                     commit.Add( arc );
675 
676                     return true;
677                 }
678 
679                 return false;
680             };
681 
682     for( EDA_ITEM* item : selection )
683     {
684         if( handleGraphicSeg( item ) )
685             continue;
686 
687         SHAPE_POLY_SET   polySet = getPolySet( item );
688         std::vector<SEG> segs    = getSegList( polySet );
689 
690         if( aEvent.IsAction( &PCB_ACTIONS::convertToLines ) )
691         {
692             for( SEG& seg : segs )
693             {
694                 if( fpEditor )
695                 {
696                     FP_SHAPE* graphic = new FP_SHAPE( footprint, SHAPE_T::SEGMENT );
697 
698                     graphic->SetLayer( targetLayer );
699                     graphic->SetStart( wxPoint( seg.A ) );
700                     graphic->SetStart0( wxPoint( seg.A ) );
701                     graphic->SetEnd( wxPoint( seg.B ) );
702                     graphic->SetEnd0( wxPoint( seg.B ) );
703                     commit.Add( graphic );
704                 }
705                 else
706                 {
707                     PCB_SHAPE* graphic = new PCB_SHAPE( nullptr, SHAPE_T::SEGMENT );
708 
709                     graphic->SetLayer( targetLayer );
710                     graphic->SetStart( wxPoint( seg.A ) );
711                     graphic->SetEnd( wxPoint( seg.B ) );
712                     commit.Add( graphic );
713                 }
714             }
715         }
716         else
717         {
718 
719 
720             if( !IsCopperLayer( targetLayer ) )
721             {
722                 if( copperLayer == UNSELECTED_LAYER )
723                     copperLayer = frame->SelectOneLayer( F_Cu, LSET::AllNonCuMask() );
724 
725                 if( copperLayer == UNDEFINED_LAYER )    // User canceled
726                     continue;
727 
728                 targetLayer = copperLayer;
729             }
730 
731             // I am really unsure converting a polygon to "tracks" (i.e. segments on
732             // copper layers) make sense for footprints, but anyway this code exists
733             if( fpEditor )
734             {
735                 // Creating segments on copper layer
736                 for( SEG& seg : segs )
737                 {
738                     FP_SHAPE* graphic = new FP_SHAPE( footprint, SHAPE_T::SEGMENT );
739                     graphic->SetLayer( targetLayer );
740                     graphic->SetStart( wxPoint( seg.A ) );
741                     graphic->SetStart0( wxPoint( seg.A ) );
742                     graphic->SetEnd( wxPoint( seg.B ) );
743                     graphic->SetEnd0( wxPoint( seg.B ) );
744                     commit.Add( graphic );
745                 }
746             }
747             else
748             {
749                 // Creating tracks
750                 for( SEG& seg : segs )
751                 {
752                     PCB_TRACK* track = new PCB_TRACK( parent );
753 
754                     track->SetLayer( targetLayer );
755                     track->SetStart( wxPoint( seg.A ) );
756                     track->SetEnd( wxPoint( seg.B ) );
757                     commit.Add( track );
758                 }
759             }
760         }
761     }
762 
763     commit.Push( _( "Convert polygons to lines" ) );
764 
765     return 0;
766 }
767 
768 
SegmentToArc(const TOOL_EVENT & aEvent)769 int CONVERT_TOOL::SegmentToArc( const TOOL_EVENT& aEvent )
770 {
771     auto& selection = m_selectionTool->RequestSelection(
772             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
773             {
774                 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
775                 {
776                     BOARD_ITEM* item = aCollector[i];
777 
778                     if( !( item->Type() == PCB_SHAPE_T ||
779                            item->Type() == PCB_TRACE_T ||
780                            item->Type() == PCB_FP_SHAPE_T ) )
781                     {
782                         aCollector.Remove( item );
783                     }
784                 }
785             } );
786 
787     EDA_ITEM* source = selection.Front();
788     VECTOR2I start, end, mid;
789 
790     // Offset the midpoint along the normal a little bit so that it's more obviously an arc
791     const double offsetRatio = 0.1;
792 
793     if( OPT<SEG> seg = getStartEndPoints( source, nullptr ) )
794     {
795         start = seg->A;
796         end   = seg->B;
797 
798         VECTOR2I normal = ( seg->B - seg->A ).Perpendicular().Resize( offsetRatio * seg->Length() );
799         mid = seg->Center() + normal;
800     }
801     else
802     {
803         return -1;
804     }
805 
806     PCB_BASE_EDIT_FRAME*  frame  = getEditFrame<PCB_BASE_EDIT_FRAME>();
807     BOARD_ITEM_CONTAINER* parent = frame->GetModel();
808 
809     BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( source );
810 
811     // Don't continue processing if we don't actually have a board item
812     if( !boardItem )
813         return 0;
814 
815     PCB_LAYER_ID layer = boardItem->GetLayer();
816 
817     BOARD_COMMIT commit( m_frame );
818 
819     if( source->Type() == PCB_SHAPE_T || source->Type() == PCB_FP_SHAPE_T )
820     {
821         PCB_SHAPE* line = static_cast<PCB_SHAPE*>( source );
822         PCB_SHAPE* arc  = new PCB_SHAPE( parent, SHAPE_T::ARC );
823 
824         VECTOR2I center = CalcArcCenter( start, mid, end );
825 
826         arc->SetFilled( false );
827         arc->SetLayer( layer );
828         arc->SetWidth( line->GetWidth() );
829 
830         arc->SetCenter( wxPoint( center ) );
831         arc->SetStart( wxPoint( start ) );
832         arc->SetEnd( wxPoint( end ) );
833 
834         commit.Add( arc );
835     }
836     else
837     {
838         wxASSERT( source->Type() == PCB_TRACE_T );
839         PCB_TRACK* line = static_cast<PCB_TRACK*>( source );
840         PCB_ARC*   arc  = new PCB_ARC( parent );
841 
842         arc->SetLayer( layer );
843         arc->SetWidth( line->GetWidth() );
844         arc->SetStart( wxPoint( start ) );
845         arc->SetMid( wxPoint( mid ) );
846         arc->SetEnd( wxPoint( end ) );
847 
848         commit.Add( arc );
849     }
850 
851     commit.Push( _( "Create arc from line segment" ) );
852 
853     return 0;
854 }
855 
856 
getStartEndPoints(EDA_ITEM * aItem,int * aWidth)857 OPT<SEG> CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem, int* aWidth )
858 {
859     switch( aItem->Type() )
860     {
861     case PCB_SHAPE_T:
862     case PCB_FP_SHAPE_T:
863     {
864         PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
865 
866         if( aWidth )
867             *aWidth = shape->GetWidth();
868 
869         return boost::make_optional<SEG>( { VECTOR2I( shape->GetStart() ),
870                                             VECTOR2I( shape->GetEnd() ) } );
871     }
872 
873     case PCB_TRACE_T:
874     {
875         PCB_TRACK* line = static_cast<PCB_TRACK*>( aItem );
876 
877         if( aWidth )
878             *aWidth = line->GetWidth();
879 
880         return boost::make_optional<SEG>( { VECTOR2I( line->GetStart() ),
881                                             VECTOR2I( line->GetEnd() ) } );
882     }
883 
884     case PCB_ARC_T:
885     {
886         PCB_ARC* arc = static_cast<PCB_ARC*>( aItem );
887 
888         if( aWidth )
889             *aWidth = arc->GetWidth();
890 
891         return boost::make_optional<SEG>( { VECTOR2I( arc->GetStart() ),
892                                             VECTOR2I( arc->GetEnd() ) } );
893     }
894 
895     default:
896         return NULLOPT;
897     }
898 }
899 
900 
setTransitions()901 void CONVERT_TOOL::setTransitions()
902 {
903     Go( &CONVERT_TOOL::CreatePolys,    PCB_ACTIONS::convertToPoly.MakeEvent() );
904     Go( &CONVERT_TOOL::CreatePolys,    PCB_ACTIONS::convertToZone.MakeEvent() );
905     Go( &CONVERT_TOOL::CreatePolys,    PCB_ACTIONS::convertToKeepout.MakeEvent() );
906     Go( &CONVERT_TOOL::CreateLines,    PCB_ACTIONS::convertToLines.MakeEvent() );
907     Go( &CONVERT_TOOL::CreateLines,    PCB_ACTIONS::convertToTracks.MakeEvent() );
908     Go( &CONVERT_TOOL::SegmentToArc,   PCB_ACTIONS::convertToArc.MakeEvent() );
909 }
910