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