KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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 The 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#include "convert_tool.h"
25
26#include <ranges>
27
28#include <wx/statline.h>
29#include <wx/checkbox.h>
30#include <wx/button.h>
31#include <wx/radiobut.h>
32
33#include <bitmaps.h>
34#include <dialog_shim.h>
35#include <widgets/unit_binder.h>
36#include <board.h>
37#include <board_commit.h>
39#include <collectors.h>
40#include <confirm.h>
43#include <footprint.h>
45#include <geometry/roundrect.h>
47#include <pcb_edit_frame.h>
48#include <pcb_shape.h>
49#include <pcb_track.h>
50#include <pcb_barcode.h>
51#include <pad.h>
52#include <string_utils.h>
53#include <tool/tool_manager.h>
54#include <tools/edit_tool.h>
55#include <tools/pcb_actions.h>
58#include <trigo.h>
59#include <macros.h>
60#include <zone.h>
61
62
64{
65public:
67 bool aShowCopyLineWidthOption, bool aShowCenterlineOption,
68 bool aShowBoundingHullOption ) :
69 DIALOG_SHIM( aParent, wxID_ANY, _( "Conversion Settings" ), wxDefaultPosition,
70 wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
71 m_settings( aSettings )
72 {
73 // Allow each distinct version of the dialog to have a different size
74 m_hash_key = TO_UTF8( wxString::Format( wxS( "%s%c%c%c" ),
75 GetTitle(),
76 aShowCopyLineWidthOption,
77 aShowCenterlineOption,
78 aShowBoundingHullOption ) );
79
80 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
81 wxBoxSizer* topSizer = new wxBoxSizer( wxVERTICAL );
82 SetSizer( mainSizer );
83
84 m_rbMimicLineWidth = new wxRadioButton( this, wxID_ANY, _( "Copy line width of first object" ) );
85
86 if( aShowCopyLineWidthOption )
87 topSizer->Add( m_rbMimicLineWidth, 0, wxLEFT|wxRIGHT, 5 );
88 else
89 m_rbMimicLineWidth->Hide();
90
91 m_rbCenterline = new wxRadioButton( this, wxID_ANY, _( "Use centerlines" ) );
92
93 if( aShowCenterlineOption )
94 {
95 topSizer->AddSpacer( 6 );
96 topSizer->Add( m_rbCenterline, 0, wxLEFT|wxRIGHT, 5 );
97 }
98 else
99 {
100 m_rbCenterline->Hide();
101 }
102
103 m_rbBoundingHull = new wxRadioButton( this, wxID_ANY, _( "Create bounding hull" ) );
104
105 m_gapLabel = new wxStaticText( this, wxID_ANY, _( "Gap:" ) );
106 m_gapCtrl = new wxTextCtrl( this, wxID_ANY );
107 m_gapUnits = new wxStaticText( this, wxID_ANY, _( "mm" ) );
108 m_gap = new UNIT_BINDER( aParent, m_gapLabel, m_gapCtrl, m_gapUnits );
109
110 m_widthLabel = new wxStaticText( this, wxID_ANY, _( "Line width:" ) );
111 m_widthCtrl = new wxTextCtrl( this, wxID_ANY );
112 m_widthUnits = new wxStaticText( this, wxID_ANY, _( "mm" ) );
114
115 if( aShowBoundingHullOption )
116 {
117 topSizer->AddSpacer( 6 );
118 topSizer->Add( m_rbBoundingHull, 0, wxLEFT|wxRIGHT, 5 );
119
120 wxBoxSizer* hullParamsSizer = new wxBoxSizer( wxHORIZONTAL );
121 hullParamsSizer->Add( m_gapLabel, 0, wxALIGN_CENTRE_VERTICAL, 5 );
122 hullParamsSizer->Add( m_gapCtrl, 1, wxALIGN_CENTRE_VERTICAL|wxLEFT|wxRIGHT, 3 );
123 hullParamsSizer->Add( m_gapUnits, 0, wxALIGN_CENTRE_VERTICAL, 5 );
124 hullParamsSizer->AddSpacer( 18 );
125 hullParamsSizer->Add( m_widthLabel, 0, wxALIGN_CENTRE_VERTICAL, 5 );
126 hullParamsSizer->Add( m_widthCtrl, 1, wxALIGN_CENTRE_VERTICAL|wxLEFT|wxRIGHT, 3 );
127 hullParamsSizer->Add( m_widthUnits, 0, wxALIGN_CENTRE_VERTICAL, 5 );
128
129 topSizer->AddSpacer( 2 );
130 topSizer->Add( hullParamsSizer, 0, wxLEFT, 26 );
131
132 topSizer->AddSpacer( 15 );
133 }
134 else
135 {
136 m_rbBoundingHull->Hide();
137 m_gap->Show( false, true );
138 m_width->Show( false, true );
139 }
140
141 m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, _( "Delete source objects after conversion" ) );
142 topSizer->Add( m_cbDeleteOriginals, 0, wxALL, 5 );
143
144 mainSizer->Add( topSizer, 1, wxALL|wxEXPAND, 10 );
145
146 wxBoxSizer* buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
147 buttonsSizer->AddStretchSpacer();
148
149 wxStdDialogButtonSizer* sdbSizer = new wxStdDialogButtonSizer();
150 wxButton* sdbSizerOK = new wxButton( this, wxID_OK );
151 sdbSizer->AddButton( sdbSizerOK );
152 wxButton* sdbSizerCancel = new wxButton( this, wxID_CANCEL );
153 sdbSizer->AddButton( sdbSizerCancel );
154 sdbSizer->Realize();
155
156 buttonsSizer->Add( sdbSizer, 1, 0, 5 );
157 mainSizer->Add( buttonsSizer, 0, wxALL|wxEXPAND, 5 );
158
160
161 m_rbMimicLineWidth->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
162 wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
163 nullptr, this );
164 m_rbCenterline->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
165 wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
166 nullptr, this );
167 m_rbBoundingHull->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
168 wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
169 nullptr, this );
170
172 }
173
175 {
176 delete m_gap;
177 delete m_width;
178 };
179
180protected:
181 bool TransferDataToWindow() override
182 {
183 switch( m_settings->m_Strategy )
184 {
185 case COPY_LINEWIDTH: m_rbMimicLineWidth->SetValue( true ); break;
186 case CENTERLINE: m_rbCenterline->SetValue( true ); break;
187 case BOUNDING_HULL: m_rbBoundingHull->SetValue( true ); break;
188 }
189
190 m_gap->Enable( m_rbBoundingHull->GetValue() );
191 m_width->Enable( m_rbBoundingHull->GetValue() );
192 m_gap->SetValue( m_settings->m_Gap );
193 m_width->SetValue( m_settings->m_LineWidth );
194
195 m_cbDeleteOriginals->SetValue( m_settings->m_DeleteOriginals );
196 return true;
197 }
198
200 {
201 if( m_rbBoundingHull->GetValue() )
202 m_settings->m_Strategy = BOUNDING_HULL;
203 else if( m_rbCenterline->GetValue() )
204 m_settings->m_Strategy = CENTERLINE;
205 else
206 m_settings->m_Strategy = COPY_LINEWIDTH;
207
208 m_settings->m_Gap = m_gap->GetIntValue();
209 m_settings->m_LineWidth = m_width->GetIntValue();
210
211 m_settings->m_DeleteOriginals = m_cbDeleteOriginals->GetValue();
212 return true;
213 }
214
215 void onRadioButton( wxCommandEvent& aEvent )
216 {
217 m_gap->Enable( m_rbBoundingHull->GetValue() );
218 m_width->Enable( m_rbBoundingHull->GetValue() );
219 }
220
221private:
223
224 wxRadioButton* m_rbMimicLineWidth;
225 wxRadioButton* m_rbCenterline;
226 wxRadioButton* m_rbBoundingHull;
227 wxStaticText* m_gapLabel;
228 wxTextCtrl* m_gapCtrl;
229 wxStaticText* m_gapUnits;
231 wxStaticText* m_widthLabel;
232 wxTextCtrl* m_widthCtrl;
233 wxStaticText* m_widthUnits;
236};
237
238
240 PCB_TOOL_BASE( "pcbnew.Convert" ),
241 m_selectionTool( nullptr ),
242 m_menu( nullptr ),
243 m_frame( nullptr )
244{
246}
247
248
250{
251 delete m_menu;
252}
253
254
257
258
260{
263
264 // Create a context menu and make it available through selection tool
265 m_menu = new CONDITIONAL_MENU( this );
266 m_menu->SetIcon( BITMAPS::convert );
267 m_menu->SetUntranslatedTitle( _HKI( "Create from Selection" ) );
268
269 static const std::vector<KICAD_T> padTypes = { PCB_PAD_T };
270 static const std::vector<KICAD_T> toArcTypes = { PCB_ARC_T,
273 static const std::vector<KICAD_T> shapeTypes = { PCB_SHAPE_LOCATE_SEGMENT_T,
281 static const std::vector<KICAD_T> trackTypes = { PCB_TRACE_T,
282 PCB_ARC_T,
283 PCB_VIA_T };
284 static const std::vector<KICAD_T> toTrackTypes = { PCB_SHAPE_LOCATE_SEGMENT_T,
286 static const std::vector<KICAD_T> polyTypes = { PCB_ZONE_T,
289 static const std::vector<KICAD_T> outsetTypes = { PCB_PAD_T, PCB_SHAPE_T };
290
291 auto shapes = S_C::OnlyTypes( shapeTypes ) && P_S_C::SameLayer();
292 auto graphicToTrack = S_C::OnlyTypes( toTrackTypes );
293 auto anyTracks = S_C::MoreThan( 0 ) && S_C::OnlyTypes( trackTypes ) && P_S_C::SameLayer();
294 auto anyPolys = S_C::OnlyTypes( polyTypes );
295 auto anyPads = S_C::OnlyTypes( padTypes );
296
297 auto canCreateArcs = S_C::Count( 1 ) && S_C::OnlyTypes( toArcTypes );
298 auto canCreateArray = S_C::MoreThan( 0 );
299 auto canCreatePoly = shapes || anyPolys || anyTracks;
300
301 auto canCreateOutset = S_C::OnlyTypes( outsetTypes );
302
303 if( m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) )
304 canCreatePoly = shapes || anyPolys || anyTracks || anyPads;
305
306 auto canCreateLines = anyPolys;
307 auto canCreateTracks = anyPolys || graphicToTrack;
308 auto canCreate = canCreatePoly
309 || canCreateLines
310 || canCreateTracks
311 || canCreateArcs
312 || canCreateArray
313 || canCreateOutset;
314
315 m_menu->AddItem( PCB_ACTIONS::convertToPoly, canCreatePoly );
316
317 if( m_frame->IsType( FRAME_PCB_EDITOR ) )
318 m_menu->AddItem( PCB_ACTIONS::convertToZone, canCreatePoly );
319
320 m_menu->AddItem( PCB_ACTIONS::convertToKeepout, canCreatePoly );
321 m_menu->AddItem( PCB_ACTIONS::convertToLines, canCreateLines );
322 m_menu->AddItem( PCB_ACTIONS::outsetItems, canCreateOutset );
323 m_menu->AddSeparator();
324
325 // Currently the code exists, but tracks are not really existing in footprints
326 // only segments on copper layers
327 if( m_frame->IsType( FRAME_PCB_EDITOR ) )
328 m_menu->AddItem( PCB_ACTIONS::convertToTracks, canCreateTracks );
329
330 m_menu->AddItem( PCB_ACTIONS::convertToArc, canCreateArcs );
331
332 m_menu->AddSeparator();
333 m_menu->AddItem( PCB_ACTIONS::createArray, canCreateArray );
334
335 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
336 selToolMenu.AddMenu( m_menu, canCreate, 100 );
337
338 return true;
339}
340
341
343{
344 m_userSettings.m_Strategy = CENTERLINE;
345 m_userSettings.m_Gap = 0;
346 m_userSettings.m_LineWidth = 0;
347 m_userSettings.m_DeleteOriginals = true;
348}
349
350
352{
353 BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
354 std::vector<SHAPE_POLY_SET> polys;
355 PCB_LAYER_ID destLayer = m_frame->GetActiveLayer();
356 FOOTPRINT* parentFootprint = nullptr;
357
358 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
359 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
360 {
361 } );
362
363 if( selection.Empty() )
364 return 0;
365
366 auto getPolys =
367 [&]( CONVERT_SETTINGS cfg )
368 {
369 polys.clear();
370
371 for( EDA_ITEM* item : selection )
372 item->ClearTempFlags();
373
374 SHAPE_POLY_SET polySet;
375
376 polySet.Append( makePolysFromClosedGraphics( selection.GetItems(), cfg.m_Strategy ) );
377
378 if( cfg.m_Strategy == BOUNDING_HULL )
379 {
380 polySet.Append( makePolysFromOpenGraphics( selection.GetItems(), 0 ) );
381
382 polySet.ClearArcs();
383 polySet.Simplify();
384
385 // Now inflate the bounding hull by cfg.m_Gap
388 }
389 else
390 {
391 polySet.Append( makePolysFromChainedSegs( selection.GetItems(), cfg.m_Strategy ) );
392 }
393
394 if( polySet.IsEmpty() )
395 return false;
396
397 for( int ii = 0; ii < polySet.OutlineCount(); ++ii )
398 {
399 polys.emplace_back( SHAPE_POLY_SET( polySet.COutline( ii ) ) );
400
401 for( int jj = 0; jj < polySet.HoleCount( ii ); ++jj )
402 polys.back().AddHole( polySet.Hole( ii, jj ) );
403 }
404
405 return true;
406 };
407
408 // Pre-flight getPolys() to see if there's anything to convert.
409 CONVERT_SETTINGS preflightSettings = m_userSettings;
410 preflightSettings.m_Strategy = BOUNDING_HULL;
411
412 if( !getPolys( preflightSettings ) )
413 return 0;
414
415 if( selection.Front() && selection.Front()->IsBOARD_ITEM() )
416 parentFootprint = static_cast<BOARD_ITEM*>( selection.Front() )->GetParentFootprint();
417
418 PCB_LAYER_ID layer = m_frame->GetActiveLayer();
419 BOARD_COMMIT commit( m_frame );
420
421 if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) )
422 {
423 bool showCopyLineWidth = true;
424
425 // No copy-line-width option for pads
426 if( selection.Front()->Type() == PCB_PAD_T )
427 {
428 if( m_userSettings.m_Strategy == COPY_LINEWIDTH )
429 m_userSettings.m_Strategy = CENTERLINE;
430
431 showCopyLineWidth = false;
432 }
433
434 CONVERT_SETTINGS previousSettings = m_userSettings;
435
436 CONVERT_SETTINGS_DIALOG dlg( m_frame, &m_userSettings, showCopyLineWidth, true, true );
437
438 if( dlg.ShowModal() != wxID_OK )
439 return 0;
440
441 CONVERT_SETTINGS resolvedSettings = m_userSettings;
442
443 if( resolvedSettings.m_Strategy != CENTERLINE )
444 {
445 if( resolvedSettings.m_LineWidth == 0 )
446 resolvedSettings.m_LineWidth = bds.m_LineThickness[ bds.GetLayerClass( layer ) ];
447 }
448
449 if( resolvedSettings.m_Strategy == BOUNDING_HULL )
450 {
451 if( resolvedSettings.m_Gap > 0 )
452 resolvedSettings.m_Gap += KiROUND( (double) resolvedSettings.m_LineWidth / 2.0 );
453 }
454
455 if( !getPolys( resolvedSettings ) )
456 {
457 wxString msg;
458
459 if( resolvedSettings.m_Strategy == BOUNDING_HULL )
460 msg = _( "Resulting polygon would be empty" );
461 else
462 msg = _( "Objects must form a closed shape" );
463
464 DisplayErrorMessage( m_frame, _( "Could not convert selection" ), msg );
465
466 m_userSettings = previousSettings;
467 return 0;
468 }
469
470 for( const SHAPE_POLY_SET& poly : polys )
471 {
472 PCB_SHAPE* graphic = new PCB_SHAPE( parentFootprint );
473
474 if( resolvedSettings.m_Strategy == COPY_LINEWIDTH )
475 {
476 BOARD_ITEM* topLeftItem = nullptr;
477 VECTOR2I pos;
478
479 for( EDA_ITEM* item : selection )
480 {
481 if( !item->IsBOARD_ITEM() )
482 continue;
483
484 BOARD_ITEM* candidate = static_cast<BOARD_ITEM*>( item );
485
486 if( candidate->HasLineStroke() )
487 {
488 pos = candidate->GetPosition();
489
490 if( !topLeftItem
491 || ( pos.x < topLeftItem->GetPosition().x )
492 || ( topLeftItem->GetPosition().x == pos.x
493 && pos.y < topLeftItem->GetPosition().y ) )
494 {
495 topLeftItem = candidate;
496 resolvedSettings.m_LineWidth = topLeftItem->GetStroke().GetWidth();
497 }
498 }
499 }
500 }
501
502 graphic->SetShape( SHAPE_T::POLY );
503 graphic->SetStroke( STROKE_PARAMS( resolvedSettings.m_LineWidth, LINE_STYLE::SOLID,
505 graphic->SetFilled( resolvedSettings.m_Strategy == CENTERLINE );
506 graphic->SetLayer( destLayer );
507 graphic->SetPolyShape( poly );
508
509 commit.Add( graphic );
510 }
511 }
512 else
513 {
514 // Creating zone or keepout
516 BOARD_ITEM_CONTAINER* parent = frame->GetModel();
517 ZONE_SETTINGS zoneInfo = bds.GetDefaultZoneSettings();
518
519 bool nonCopper = IsNonCopperLayer( destLayer );
520 zoneInfo.m_Layers.reset().set( destLayer );
521 zoneInfo.m_Name.Empty();
522
523 int ret;
524
525 // No copy-line-width option for zones/keepouts
526 if( m_userSettings.m_Strategy == COPY_LINEWIDTH )
527 m_userSettings.m_Strategy = CENTERLINE;
528
530 {
531 zoneInfo.SetIsRuleArea( true );
532 ret = InvokeRuleAreaEditor( frame, &zoneInfo, board(), &m_userSettings );
533 }
534 else if( nonCopper )
535 {
536 zoneInfo.SetIsRuleArea( false );
537 ret = InvokeNonCopperZonesEditor( frame, &zoneInfo, &m_userSettings );
538 }
539 else
540 {
541 zoneInfo.SetIsRuleArea( false );
542 ret = InvokeCopperZonesEditor( frame, &zoneInfo, &m_userSettings );
543 }
544
545 if( ret == wxID_CANCEL )
546 return 0;
547
548 if( !getPolys( m_userSettings ) )
549 return 0;
550
551 for( const SHAPE_POLY_SET& poly : polys )
552 {
553 ZONE* zone = new ZONE( parent );
554
555 *zone->Outline() = poly;
556 zone->HatchBorder();
557
558 zoneInfo.ExportSetting( *zone );
559
560 commit.Add( zone );
561 }
562 }
563
564 if( m_userSettings.m_DeleteOriginals )
565 {
566 PCB_SELECTION selectionCopy = selection;
567 m_selectionTool->ClearSelection();
568
569 for( EDA_ITEM* item : selectionCopy )
570 {
571 if( item->GetFlags() & SKIP_STRUCT )
572 commit.Remove( item );
573 }
574 }
575
576 if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) )
577 {
578 if( m_userSettings.m_DeleteOriginals )
579 commit.Push( _( "Convert to Polygon" ) );
580 else
581 commit.Push( _( "Create Polygon" ) );
582 }
583 else
584 {
585 if( m_userSettings.m_DeleteOriginals )
586 commit.Push( _( "Convert to Zone" ) );
587 else
588 commit.Push( _( "Create Zone" ) );
589 }
590
591 return 0;
592}
593
594
595SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::deque<EDA_ITEM*>& aItems,
596 CONVERT_STRATEGY aStrategy )
597{
598 // TODO: This code has a somewhat-similar purpose to ConvertOutlineToPolygon but is slightly
599 // different, so this remains a separate algorithm. It might be nice to analyze the dfiferences
600 // in requirements and refactor this.
601
602 // Using a large epsilon here to allow for sloppy drawing can cause the algorithm to miss very
603 // short segments in a converted bezier. So use an epsilon only large enough to cover for
604 // rouding errors in the conversion.
605 int chainingEpsilon = 100; // max dist from one endPt to next startPt in IU
606
607 SHAPE_POLY_SET poly;
608
609 // Stores pairs of (anchor, item) where anchor == 0 -> SEG.A, anchor == 1 -> SEG.B
610 std::map<VECTOR2I, std::vector<std::pair<int, EDA_ITEM*>>> connections;
611 std::deque<EDA_ITEM*> toCheck;
612
613 auto closeEnough =
614 []( const VECTOR2I& aLeft, const VECTOR2I& aRight, int aLimit )
615 {
616 return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit );
617 };
618
619 auto findInsertionPoint =
620 [&]( const VECTOR2I& aPoint ) -> VECTOR2I
621 {
622 if( connections.count( aPoint ) )
623 return aPoint;
624
625 for( const auto& candidatePair : connections )
626 {
627 if( closeEnough( aPoint, candidatePair.first, chainingEpsilon ) )
628 return candidatePair.first;
629 }
630
631 return aPoint;
632 };
633
634 for( EDA_ITEM* item : aItems )
635 {
636 if( std::optional<SEG> seg = getStartEndPoints( item ) )
637 {
638 toCheck.push_back( item );
639 connections[findInsertionPoint( seg->A )].emplace_back( std::make_pair( 0, item ) );
640 connections[findInsertionPoint( seg->B )].emplace_back( std::make_pair( 1, item ) );
641 }
642 }
643
644 while( !toCheck.empty() )
645 {
646 std::vector<BOARD_ITEM*> insertedItems;
647
648 EDA_ITEM* candidate = toCheck.front();
649 toCheck.pop_front();
650
651 if( candidate->GetFlags() & SKIP_STRUCT )
652 continue;
653
654 SHAPE_LINE_CHAIN outline;
655
656 auto insert =
657 [&]( EDA_ITEM* aItem, const VECTOR2I& aAnchor, bool aDirection )
658 {
659 if( aItem->IsType( { PCB_ARC_T, PCB_SHAPE_LOCATE_ARC_T } ) )
660 {
661 SHAPE_ARC arc;
662
663 if( aItem->Type() == PCB_ARC_T )
664 {
665 PCB_ARC* pcb_arc = static_cast<PCB_ARC*>( aItem );
666 arc = *static_cast<SHAPE_ARC*>( pcb_arc->GetEffectiveShape().get() );
667 }
668 else
669 {
670 PCB_SHAPE* pcb_shape = static_cast<PCB_SHAPE*>( aItem );
671 arc = SHAPE_ARC( pcb_shape->GetStart(), pcb_shape->GetArcMid(),
672 pcb_shape->GetEnd(), pcb_shape->GetWidth() );
673 }
674
675 if( aDirection )
676 outline.Append( aAnchor == arc.GetP0() ? arc : arc.Reversed() );
677 else
678 outline.Insert( 0, aAnchor == arc.GetP0() ? arc : arc.Reversed() );
679
680 insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
681 }
682 else if( aItem->IsType( { PCB_SHAPE_LOCATE_BEZIER_T } ) )
683 {
684 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
685
686 if( aAnchor == graphic->GetStart() )
687 {
688 for( const VECTOR2I& pt : graphic->GetBezierPoints() )
689 {
690 if( aDirection )
691 outline.Append( pt );
692 else
693 outline.Insert( 0, pt );
694 }
695
696 }
697 else
698 {
699 for( const VECTOR2I& pt : std::ranges::reverse_view( graphic->GetBezierPoints() ) )
700 {
701 if( aDirection )
702 outline.Append( pt );
703 else
704 outline.Insert( 0, pt );
705 }
706 }
707
708 insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
709 }
710 else if( std::optional<SEG> nextSeg = getStartEndPoints( aItem ) )
711 {
712 VECTOR2I& point = ( aAnchor == nextSeg->A ) ? nextSeg->B : nextSeg->A;
713
714 if( aDirection )
715 outline.Append( point );
716 else
717 outline.Insert( 0, point );
718
719 insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
720 }
721 };
722
723 // aDirection == true for walking "right" and appending to the end of points
724 // false for walking "left" and prepending to the beginning
725 std::function<void( EDA_ITEM*, const VECTOR2I&, bool )> process =
726 [&]( EDA_ITEM* aItem, const VECTOR2I& aAnchor, bool aDirection )
727 {
728 if( aItem->GetFlags() & SKIP_STRUCT )
729 return;
730
731 aItem->SetFlags( SKIP_STRUCT );
732
733 insert( aItem, aAnchor, aDirection );
734
735 std::optional<SEG> anchors = getStartEndPoints( aItem );
736 wxASSERT( anchors );
737
738 VECTOR2I nextAnchor = ( aAnchor == anchors->A ) ? anchors->B : anchors->A;
739
740 for( std::pair<int, EDA_ITEM*> pair : connections[nextAnchor] )
741 {
742 if( pair.second == aItem )
743 continue;
744
745 process( pair.second, nextAnchor, aDirection );
746 }
747 };
748
749 std::optional<SEG> anchors = getStartEndPoints( candidate );
750 wxASSERT( anchors );
751
752 // Start with the first object and walk "right"
753 // Note if the first object is an arc, we don't need to insert its first point here, the
754 // whole arc will be inserted at anchor B inside process()
755 if( !candidate->IsType( { PCB_ARC_T, PCB_SHAPE_LOCATE_ARC_T } ) )
756 insert( candidate, anchors->A, true );
757
758 process( candidate, anchors->B, true );
759
760 // check for any candidates on the "left"
761 EDA_ITEM* left = nullptr;
762
763 for( std::pair<int, EDA_ITEM*> possibleLeft : connections[anchors->A] )
764 {
765 if( possibleLeft.second != candidate )
766 {
767 left = possibleLeft.second;
768 break;
769 }
770 }
771
772 if( left )
773 process( left, anchors->A, false );
774
775 if( outline.PointCount() < 3
776 || !closeEnough( outline.GetPoint( 0 ), outline.GetPoint( -1 ), chainingEpsilon ) )
777 {
778 for( EDA_ITEM* item : insertedItems )
779 item->ClearFlags( SKIP_STRUCT );
780
781 continue;
782 }
783
784 outline.SetClosed( true );
785
786 poly.AddOutline( outline );
787
788 if( aStrategy == BOUNDING_HULL )
789 {
790 for( BOARD_ITEM* item : insertedItems )
791 item->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, item->GetMaxError(), ERROR_INSIDE );
792 }
793
794 insertedItems.clear();
795 }
796
797 return poly;
798}
799
800
801SHAPE_POLY_SET CONVERT_TOOL::makePolysFromOpenGraphics( const std::deque<EDA_ITEM*>& aItems, int aGap )
802{
803 SHAPE_POLY_SET poly;
804
805 for( EDA_ITEM* item : aItems )
806 {
807 if( item->GetFlags() & SKIP_STRUCT )
808 continue;
809
810 switch( item->Type() )
811 {
812 case PCB_SHAPE_T:
813 {
814 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
815
816 if( shape->IsClosed() )
817 continue;
818
819 shape->TransformShapeToPolygon( poly, UNDEFINED_LAYER, aGap, shape->GetMaxError(), ERROR_INSIDE );
820 shape->SetFlags( SKIP_STRUCT );
821
822 break;
823 }
824
825 case PCB_TRACE_T:
826 case PCB_ARC_T:
827 case PCB_VIA_T:
828 {
829 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
830
831 track->TransformShapeToPolygon( poly, UNDEFINED_LAYER, aGap, track->GetMaxError(), ERROR_INSIDE );
832 track->SetFlags( SKIP_STRUCT );
833
834 break;
835 }
836
837 default:
838 continue;
839 }
840 }
841
842 return poly;
843}
844
845
847 CONVERT_STRATEGY aStrategy )
848{
849 SHAPE_POLY_SET poly;
850
851 for( EDA_ITEM* item : aItems )
852 {
853 if( item->GetFlags() & SKIP_STRUCT )
854 continue;
855
856 switch( item->Type() )
857 {
858 case PCB_SHAPE_T:
859 {
860 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
861 FILL_T wasFilled = shape->GetFillMode();
862
863 if( !shape->IsClosed() )
864 continue;
865
866 if( shape->GetShape() == SHAPE_T::CIRCLE && aStrategy != BOUNDING_HULL )
867 {
868 VECTOR2I c = shape->GetCenter();
869 int R = shape->GetRadius();
870 SHAPE_ARC arc( c - VECTOR2I( R, 0 ), c + VECTOR2I( R, 0 ), c - VECTOR2I( R, 0 ), 0 );
871
872 poly.NewOutline();
873 poly.Append( arc );
874 }
875 else
876 {
877 if( aStrategy != BOUNDING_HULL )
878 shape->SetFilled( true );
879
881 aStrategy != BOUNDING_HULL );
882
883 if( aStrategy != BOUNDING_HULL )
884 shape->SetFillMode( wasFilled );
885 }
886
887 shape->SetFlags( SKIP_STRUCT );
888 break;
889 }
890
891 case PCB_ZONE_T:
892 poly.Append( *static_cast<ZONE*>( item )->Outline() );
893 item->SetFlags( SKIP_STRUCT );
894 break;
895
896 case PCB_FIELD_T:
897 case PCB_TEXT_T:
898 {
899 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
900 text->TransformTextToPolySet( poly, 0, text->GetMaxError(), ERROR_INSIDE );
901 text->SetFlags( SKIP_STRUCT );
902 break;
903 }
904
905 case PCB_BARCODE_T:
906 {
907 PCB_BARCODE* barcode = static_cast<PCB_BARCODE*>( item );
908
909 if( aStrategy == BOUNDING_HULL )
910 barcode->GetBoundingHull( poly, UNDEFINED_LAYER, 0, barcode->GetMaxError(), ERROR_INSIDE );
911 else
912 barcode->TransformShapeToPolySet( poly, UNDEFINED_LAYER, 0, barcode->GetMaxError(), ERROR_INSIDE );
913
914 barcode->SetFlags( SKIP_STRUCT );
915 break;
916 }
917
918 case PCB_PAD_T:
919 {
920 PAD* pad = static_cast<PAD*>( item );
921
922 pad->Padstack().ForEachUniqueLayer(
923 [&]( PCB_LAYER_ID aLayer )
924 {
925 SHAPE_POLY_SET layerPoly;
926 pad->TransformShapeToPolygon( layerPoly, aLayer, 0, pad->GetMaxError(), ERROR_INSIDE );
927 poly.BooleanAdd( layerPoly );
928 } );
929
930 pad->SetFlags( SKIP_STRUCT );
931 break;
932 }
933
934
935 default:
936 continue;
937 }
938 }
939
940 return poly;
941}
942
943
945{
946 auto& selection = m_selectionTool->RequestSelection(
947 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
948 {
949 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
950 {
951 BOARD_ITEM* item = aCollector[i];
952
953 switch( item->Type() )
954 {
955 case PCB_SHAPE_T:
956 switch( static_cast<PCB_SHAPE*>( item )->GetShape() )
957 {
958 case SHAPE_T::SEGMENT:
959 case SHAPE_T::ARC:
960 case SHAPE_T::POLY:
962 break;
963
964 default:
965 aCollector.Remove( item );
966 }
967
968 break;
969
970 case PCB_ZONE_T:
971 break;
972
973 default:
974 aCollector.Remove( item );
975 }
976 }
977 } );
978
979 if( selection.Empty() )
980 return 0;
981
982 BOARD_COMMIT commit( m_frame );
984 FOOTPRINT_EDIT_FRAME* fpEditor = dynamic_cast<FOOTPRINT_EDIT_FRAME*>( m_frame );
985 FOOTPRINT* footprint = nullptr;
986 PCB_LAYER_ID targetLayer = m_frame->GetActiveLayer();
987 BOARD_ITEM_CONTAINER* parent = frame->GetModel();
988
989 if( fpEditor )
990 footprint = fpEditor->GetBoard()->GetFirstFootprint();
991
992 auto handleGraphicSeg =
993 [&]( EDA_ITEM* aItem )
994 {
995 if( aItem->Type() != PCB_SHAPE_T )
996 return false;
997
998 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
999
1000 if( graphic->GetShape() == SHAPE_T::SEGMENT )
1001 {
1002 PCB_TRACK* track = new PCB_TRACK( parent );
1003
1004 track->SetLayer( targetLayer );
1005 track->SetStart( graphic->GetStart() );
1006 track->SetEnd( graphic->GetEnd() );
1007 track->SetWidth( graphic->GetWidth() );
1008 commit.Add( track );
1009
1010 return true;
1011 }
1012 else if( graphic->GetShape() == SHAPE_T::ARC )
1013 {
1014 PCB_ARC* arc = new PCB_ARC( parent );
1015
1016 arc->SetLayer( targetLayer );
1017 arc->SetStart( graphic->GetStart() );
1018 arc->SetEnd( graphic->GetEnd() );
1019 arc->SetMid( graphic->GetArcMid() );
1020 arc->SetWidth( graphic->GetWidth() );
1021 commit.Add( arc );
1022
1023 return true;
1024 }
1025
1026 return false;
1027 };
1028
1029 auto addGraphicChain =
1030 [&]( const SHAPE_LINE_CHAIN& aChain, std::optional<int> aWidth )
1031 {
1032 for( size_t si = 0; si < aChain.GetSegmentCount(); ++si )
1033 {
1034 const SEG seg = aChain.GetSegment( si );
1035
1036 if( seg.Length() == 0 )
1037 continue;
1038
1039 if( aChain.IsArcSegment( si ) )
1040 continue;
1041
1042 PCB_SHAPE* graphic = new PCB_SHAPE( footprint, SHAPE_T::SEGMENT );
1043
1044 graphic->SetLayer( targetLayer );
1045 graphic->SetStart( seg.A );
1046 graphic->SetEnd( seg.B );
1047
1048 if( aWidth && *aWidth > 0 )
1049 graphic->SetWidth( *aWidth );
1050
1051 commit.Add( graphic );
1052 }
1053
1054 for( size_t ai = 0; ai < aChain.ArcCount(); ++ai )
1055 {
1056 const SHAPE_ARC& arc = aChain.Arc( ai );
1057
1058 if( arc.GetP0() == arc.GetP1() )
1059 continue;
1060
1061 PCB_SHAPE* graphic = new PCB_SHAPE( footprint, SHAPE_T::ARC );
1062
1063 graphic->SetLayer( targetLayer );
1064 graphic->SetFilled( false );
1065 graphic->SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() );
1066
1067 if( aWidth && *aWidth > 0 )
1068 graphic->SetWidth( *aWidth );
1069
1070 commit.Add( graphic );
1071 }
1072 };
1073
1074 auto addTrackChain =
1075 [&]( const SHAPE_LINE_CHAIN& aChain, std::optional<int> aWidth )
1076 {
1077 for( size_t si = 0; si < aChain.GetSegmentCount(); ++si )
1078 {
1079 const SEG seg = aChain.GetSegment( si );
1080
1081 if( seg.Length() == 0 )
1082 continue;
1083
1084 if( aChain.IsArcSegment( si ) )
1085 continue;
1086
1087 PCB_TRACK* track = new PCB_TRACK( parent );
1088
1089 track->SetLayer( targetLayer );
1090 track->SetStart( seg.A );
1091 track->SetEnd( seg.B );
1092
1093 if( aWidth && *aWidth > 0 )
1094 track->SetWidth( *aWidth );
1095
1096 commit.Add( track );
1097 }
1098
1099 for( size_t ai = 0; ai < aChain.ArcCount(); ++ai )
1100 {
1101 const SHAPE_ARC& arc = aChain.Arc( ai );
1102
1103 if( arc.GetP0() == arc.GetP1() )
1104 continue;
1105
1106 PCB_ARC* trackArc = new PCB_ARC( parent );
1107
1108 trackArc->SetLayer( targetLayer );
1109 trackArc->SetStart( arc.GetP0() );
1110 trackArc->SetEnd( arc.GetP1() );
1111 trackArc->SetMid( arc.GetArcMid() );
1112
1113 if( aWidth && *aWidth > 0 )
1114 trackArc->SetWidth( *aWidth );
1115
1116 commit.Add( trackArc );
1117 }
1118 };
1119
1120 auto processChain =
1121 [&]( const SHAPE_LINE_CHAIN& aChain, std::optional<int> aWidth )
1122 {
1123 if( aChain.GetSegmentCount() == 0 && aChain.ArcCount() == 0 )
1124 return;
1125
1126 if( aEvent.IsAction( &PCB_ACTIONS::convertToLines ) )
1127 {
1128 addGraphicChain( aChain, aWidth );
1129 }
1130 else if( fpEditor )
1131 {
1132 addGraphicChain( aChain, aWidth );
1133 }
1134 else
1135 {
1136 addTrackChain( aChain, aWidth );
1137 }
1138 };
1139
1140 auto processPolySet =
1141 [&]( const SHAPE_POLY_SET& aPoly, std::optional<int> aWidth )
1142 {
1143 for( int oi = 0; oi < aPoly.OutlineCount(); ++oi )
1144 {
1145 processChain( aPoly.COutline( oi ), aWidth );
1146
1147 for( int hi = 0; hi < aPoly.HoleCount( oi ); ++hi )
1148 processChain( aPoly.CHole( oi, hi ), aWidth );
1149 }
1150 };
1151
1152 if( aEvent.IsAction( &PCB_ACTIONS::convertToTracks ) )
1153 {
1154 if( !IsCopperLayer( targetLayer ) )
1155 {
1156 targetLayer = frame->SelectOneLayer( F_Cu, LSET::AllNonCuMask() );
1157
1158 if( targetLayer == UNDEFINED_LAYER ) // User canceled
1159 return true;
1160 }
1161 }
1162 else
1163 {
1164 CONVERT_SETTINGS_DIALOG dlg( m_frame, &m_userSettings, false, false, false );
1165
1166 if( dlg.ShowModal() != wxID_OK )
1167 return true;
1168 }
1169
1170 for( EDA_ITEM* item : selection )
1171 {
1172 if( !item->IsBOARD_ITEM() )
1173 continue;
1174
1175 if( handleGraphicSeg( item ) )
1176 continue;
1177
1178 BOARD_ITEM& boardItem = static_cast<BOARD_ITEM&>( *item );
1179 std::optional<int> itemWidth = GetBoardItemWidth( boardItem );
1180
1181 if( boardItem.Type() == PCB_SHAPE_T )
1182 {
1183 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( item );
1184
1185 switch( graphic->GetShape() )
1186 {
1187 case SHAPE_T::RECTANGLE:
1188 {
1189 SHAPE_RECT rect( graphic->GetStart(), graphic->GetEnd() );
1190 ROUNDRECT rrect( rect, graphic->GetCornerRadius(), true );
1191 SHAPE_POLY_SET poly;
1192
1193 rrect.TransformToPolygon( poly );
1194 processPolySet( poly, itemWidth );
1195 break;
1196 }
1197
1198 case SHAPE_T::POLY:
1199 processPolySet( graphic->GetPolyShape(), itemWidth );
1200 break;
1201
1202 default:
1203 wxFAIL_MSG( wxT( "Unhandled graphic shape type in PolyToLines" ) );
1204 break;
1205 }
1206 }
1207 else if( boardItem.Type() == PCB_ZONE_T )
1208 {
1209 ZONE* zone = static_cast<ZONE*>( item );
1210 processPolySet( *zone->Outline(), itemWidth );
1211 }
1212 else
1213 {
1214 wxFAIL_MSG( wxT( "Unhandled type in PolyToLines" ) );
1215 }
1216 }
1217
1218 if( m_userSettings.m_DeleteOriginals )
1219 {
1220 PCB_SELECTION selectionCopy = selection;
1221 m_selectionTool->ClearSelection();
1222
1223 for( EDA_ITEM* item : selectionCopy )
1224 commit.Remove( item );
1225 }
1226
1227 commit.Push( _( "Create Lines" ) );
1228
1229 return 0;
1230}
1231
1232
1234{
1235 auto& selection = m_selectionTool->RequestSelection(
1236 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1237 {
1238 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1239 {
1240 BOARD_ITEM* item = aCollector[i];
1241
1242 if( !item->IsType( { PCB_SHAPE_T, PCB_TRACE_T, PCB_ARC_T } ) )
1243 aCollector.Remove( item );
1244 }
1245 } );
1246
1247 if( selection.Empty() || !selection.Front()->IsBOARD_ITEM() )
1248 return -1;
1249
1250 BOARD_ITEM* source = static_cast<BOARD_ITEM*>( selection.Front() );
1251 VECTOR2I start, end, mid;
1252
1253 // Offset the midpoint along the normal a little bit so that it's more obviously an arc
1254 const double offsetRatio = 0.1;
1255
1256 if( std::optional<SEG> seg = getStartEndPoints( source ) )
1257 {
1258 start = seg->A;
1259 end = seg->B;
1260
1261 VECTOR2I normal = ( seg->B - seg->A ).Perpendicular().Resize( offsetRatio * seg->Length() );
1262 mid = seg->Center() + normal;
1263 }
1264 else
1265 {
1266 return -1;
1267 }
1268
1270 BOARD_ITEM_CONTAINER* parent = frame->GetModel();
1271 PCB_LAYER_ID layer = source->GetLayer();
1272 BOARD_COMMIT commit( m_frame );
1273
1274 if( source->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( source )->GetShape() == SHAPE_T::SEGMENT )
1275 {
1276 PCB_SHAPE* line = static_cast<PCB_SHAPE*>( source );
1277 PCB_SHAPE* arc = new PCB_SHAPE( parent, SHAPE_T::ARC );
1278
1279 VECTOR2I center = CalcArcCenter( start, mid, end );
1280
1281 arc->SetFilled( false );
1282 arc->SetLayer( layer );
1283 arc->SetStroke( line->GetStroke() );
1284
1285 arc->SetCenter( center );
1286 arc->SetStart( start );
1287 arc->SetEnd( end );
1288
1289 commit.Add( arc );
1290 }
1291 else if( source->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( source )->GetShape() == SHAPE_T::ARC )
1292 {
1293 PCB_SHAPE* source_arc = static_cast<PCB_SHAPE*>( source );
1294 PCB_ARC* arc = new PCB_ARC( parent );
1295
1296 arc->SetLayer( layer );
1297 arc->SetWidth( source_arc->GetWidth() );
1298 arc->SetStart( start );
1299 arc->SetMid( source_arc->GetArcMid() );
1300 arc->SetEnd( end );
1301
1302 commit.Add( arc );
1303 }
1304 else if( source->Type() == PCB_TRACE_T )
1305 {
1306 PCB_TRACK* line = static_cast<PCB_TRACK*>( source );
1307 PCB_ARC* arc = new PCB_ARC( parent );
1308
1309 arc->SetLayer( layer );
1310 arc->SetWidth( line->GetWidth() );
1311 arc->SetStart( start );
1312 arc->SetMid( mid );
1313 arc->SetEnd( end );
1314
1315 commit.Add( arc );
1316 }
1317 else if( source->Type() == PCB_ARC_T )
1318 {
1319 PCB_ARC* source_arc = static_cast<PCB_ARC*>( source );
1320 PCB_SHAPE* arc = new PCB_SHAPE( parent, SHAPE_T::ARC );
1321
1322 arc->SetFilled( false );
1323 arc->SetLayer( layer );
1324 arc->SetWidth( source_arc->GetWidth() );
1325
1326 arc->SetArcGeometry( source_arc->GetStart(), source_arc->GetMid(), source_arc->GetEnd() );
1327 commit.Add( arc );
1328 }
1329
1330 commit.Push( _( "Create Arc" ) );
1331
1332 return 0;
1333}
1334
1335
1336std::optional<SEG> CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem )
1337{
1338 switch( aItem->Type() )
1339 {
1340 case PCB_SHAPE_T:
1341 {
1342 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
1343
1344 switch( shape->GetShape() )
1345 {
1346 case SHAPE_T::SEGMENT:
1347 case SHAPE_T::ARC:
1348 case SHAPE_T::POLY:
1349 case SHAPE_T::BEZIER:
1350 if( shape->GetStart() == shape->GetEnd() )
1351 return std::nullopt;
1352
1353 return std::make_optional<SEG>( VECTOR2I( shape->GetStart() ), VECTOR2I( shape->GetEnd() ) );
1354
1355 default:
1356 return std::nullopt;
1357 }
1358 }
1359
1360 case PCB_TRACE_T:
1361 {
1362 PCB_TRACK* line = static_cast<PCB_TRACK*>( aItem );
1363 return std::make_optional<SEG>( VECTOR2I( line->GetStart() ), VECTOR2I( line->GetEnd() ) );
1364 }
1365
1366 case PCB_ARC_T:
1367 {
1368 PCB_ARC* arc = static_cast<PCB_ARC*>( aItem );
1369 return std::make_optional<SEG>( VECTOR2I( arc->GetStart() ), VECTOR2I( arc->GetEnd() ) );
1370 }
1371
1372 default:
1373 return std::nullopt;
1374 }
1375}
1376
1377
1379{
1381 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1382 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1383 {
1384 // Iterate from the back so we don't have to worry about removals.
1385 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1386 {
1387 BOARD_ITEM* item = aCollector[i];
1388
1389 // We've converted the polygon and rectangle to segments, so drop everything
1390 // that isn't a segment at this point
1391 if( !item->IsType( { PCB_PAD_T, PCB_SHAPE_T } ) )
1392 aCollector.Remove( item );
1393 }
1394
1395 sTool->FilterCollectorForLockedItems( aCollector );
1396 } );
1397
1398 BOARD_COMMIT commit( this );
1399
1400 for( EDA_ITEM* item : selection )
1401 item->ClearFlags( STRUCT_DELETED );
1402
1403 // List of thing to select at the end of the operation
1404 // (doing it as we go will invalidate the iterator)
1405 std::vector<BOARD_ITEM*> items_to_select_on_success;
1406
1407 // Handle modifications to existing items by the routine
1408 // How to deal with this depends on whether we're in the footprint editor or not
1409 // and whether the item was conjured up by decomposing a polygon or rectangle
1410 auto item_modification_handler =
1411 [&]( BOARD_ITEM& aItem )
1412 {
1413 };
1414
1415 bool any_items_created = false;
1416
1417 auto item_creation_handler =
1418 [&]( std::unique_ptr<BOARD_ITEM> aItem )
1419 {
1420 any_items_created = true;
1421 items_to_select_on_success.push_back( aItem.get() );
1422 commit.Add( aItem.release() );
1423 };
1424
1425 auto item_removal_handler =
1426 [&]( BOARD_ITEM& aItem )
1427 {
1428 // If you do an outset on a FP pad, do you really want to delete
1429 // the parent?
1430 if( !aItem.GetParentFootprint() )
1431 {
1432 commit.Remove( &aItem );
1433 }
1434 };
1435
1436 // Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
1437 ITEM_MODIFICATION_ROUTINE::CALLABLE_BASED_HANDLER change_handler( item_creation_handler,
1438 item_modification_handler,
1439 item_removal_handler );
1440
1441 // Persistent settings between dialog invocations
1442 // Init with some sensible defaults
1443 static OUTSET_ROUTINE::PARAMETERS outset_params_fp_edit{
1444 pcbIUScale.mmToIU( 0.25 ), // A common outset value
1445 false,
1446 false,
1447 true,
1448 F_CrtYd,
1449 frame.GetDesignSettings().GetLineThickness( F_CrtYd ),
1450 pcbIUScale.mmToIU( 0.01 ),
1451 false,
1452 };
1453
1454 static OUTSET_ROUTINE::PARAMETERS outset_params_pcb_edit{
1455 pcbIUScale.mmToIU( 1 ),
1456 true,
1457 true,
1458 true,
1459 Edge_Cuts, // Outsets often for slots?
1460 frame.GetDesignSettings().GetLineThickness( Edge_Cuts ),
1461 std::nullopt,
1462 false,
1463 };
1464
1465 OUTSET_ROUTINE::PARAMETERS& outset_params = IsFootprintEditor() ? outset_params_fp_edit
1466 : outset_params_pcb_edit;
1467
1468 {
1469 DIALOG_OUTSET_ITEMS dlg( frame, outset_params );
1470
1471 if( dlg.ShowModal() == wxID_CANCEL )
1472 return 0;
1473 }
1474
1475 OUTSET_ROUTINE outset_routine( frame.GetModel(), change_handler, outset_params );
1476
1477 for( EDA_ITEM* item : selection )
1478 {
1479 if( !item->IsBOARD_ITEM() )
1480 continue;
1481
1482 BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
1483 outset_routine.ProcessItem( *board_item );
1484 }
1485
1486 // Deselect all the original items
1487 m_selectionTool->ClearSelection();
1488
1489 // Select added and modified items
1490 for( BOARD_ITEM* item : items_to_select_on_success )
1491 m_selectionTool->AddItemToSel( item, true );
1492
1493 if( any_items_created )
1494 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1495
1496 // Notify other tools of the changes
1498
1499 commit.Push( outset_routine.GetCommitDescription() );
1500
1501 if( const std::optional<wxString> msg = outset_routine.GetStatusMessage() )
1502 frame.ShowInfoBarMsg( *msg );
1503
1504 return 0;
1505}
1506
1507
@ ERROR_OUTSIDE
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
BASE_SET & reset(size_t pos)
Definition base_set.h:143
BASE_SET & set(size_t pos)
Definition base_set.h:116
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
Container for design settings for a BOARD object.
int GetLayerClass(PCB_LAYER_ID aLayer) const
int m_LineThickness[LAYER_CLASS_COUNT]
ZONE_SETTINGS & GetDefaultZoneSettings()
Abstract interface for BOARD_ITEMs capable of storing other items inside.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:79
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:232
virtual void TransformShapeToPolySet(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc) const
Convert the item shape to a polyset.
Definition board_item.h:425
virtual STROKE_PARAMS GetStroke() const
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:280
FOOTPRINT * GetParentFootprint() const
virtual bool HasLineStroke() const
Check if this item has line stoke properties.
Definition board_item.h:222
int GetMaxError() const
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:505
int GetCount() const
Return the number of objects in the list.
Definition collector.h:83
void Remove(int aIndex)
Remove the item at aIndex (first position is 0).
Definition collector.h:111
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:398
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:90
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:78
void AddMenu(ACTION_MENU *aMenu, const SELECTION_CONDITION &aCondition=SELECTION_CONDITIONS::ShowAlways, int aOrder=ANY_ORDER)
Add a submenu to the menu.
CONVERT_SETTINGS * m_settings
bool TransferDataToWindow() override
wxRadioButton * m_rbBoundingHull
wxRadioButton * m_rbMimicLineWidth
wxRadioButton * m_rbCenterline
void onRadioButton(wxCommandEvent &aEvent)
bool TransferDataFromWindow() override
wxCheckBox * m_cbDeleteOriginals
CONVERT_SETTINGS_DIALOG(EDA_DRAW_FRAME *aParent, CONVERT_SETTINGS *aSettings, bool aShowCopyLineWidthOption, bool aShowCenterlineOption, bool aShowBoundingHullOption)
wxStaticText * m_widthLabel
wxStaticText * m_widthUnits
int CreateLines(const TOOL_EVENT &aEvent)
Convert selected polygon-like object to graphic lines, if possible.
bool Init() override
Init() is called once upon a registration of the tool.
int SegmentToArc(const TOOL_EVENT &aEvent)
Convert selected segment (graphic or track) to an arc of the same type.
void initUserSettings()
Initialize the user settings for the tool.
SHAPE_POLY_SET makePolysFromChainedSegs(const std::deque< EDA_ITEM * > &aItems, CONVERT_STRATEGY aStrategy)
Try to make polygons from chained segments in the selected items.
SHAPE_POLY_SET makePolysFromOpenGraphics(const std::deque< EDA_ITEM * > &aItems, int aGap)
Make polygons from graphic shapes and zones.
static std::optional< SEG > getStartEndPoints(EDA_ITEM *aItem)
Retrieve the start and end points for a generic item.
SHAPE_POLY_SET makePolysFromClosedGraphics(const std::deque< EDA_ITEM * > &aItems, CONVERT_STRATEGY aStrategy)
int CreatePolys(const TOOL_EVENT &aEvent)
Convert selected lines to a polygon, if possible.
PCB_SELECTION_TOOL * m_selectionTool
virtual ~CONVERT_TOOL()
CONVERT_SETTINGS m_userSettings
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
CONDITIONAL_MENU * m_menu
PCB_BASE_FRAME * m_frame
int OutsetItems(const TOOL_EVENT &aEvent)
Convert selected items to outset versions of themselves.
DIALOG_OUTSET_ITEMS, derived from DIALOG_OUTSET_ITEMS_BASE, created by wxFormBuilder.
void SetupStandardButtons(std::map< int, wxString > aLabels={})
std::string m_hash_key
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
DIALOG_SHIM(wxWindow *aParent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE|wxRESIZE_BORDER, const wxString &name=wxDialogNameStr)
int ShowModal() override
The base class for create windows for drawing purpose.
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
virtual VECTOR2I GetPosition() const
Definition eda_item.h:272
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:142
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
virtual bool IsType(const std::vector< KICAD_T > &aScanTypes) const
Check whether the item is one of the listed types.
Definition eda_item.h:192
EDA_ITEM_FLAGS GetFlags() const
Definition eda_item.h:145
void SetCenter(const VECTOR2I &aCenter)
FILL_T GetFillMode() const
Definition eda_shape.h:142
SHAPE_POLY_SET & GetPolyShape()
Definition eda_shape.h:337
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:168
void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition eda_shape.h:345
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:136
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:215
bool IsClosed() const
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:177
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:173
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:167
const std::vector< VECTOR2I > & GetBezierPoints() const
Definition eda_shape.h:320
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:219
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Set the three controlling points for an arc.
void SetWidth(int aWidth)
int GetCornerRadius() const
void SetFillMode(FILL_T aFill)
VECTOR2I GetArcMid() const
static const TOOL_EVENT SelectedEvent
Definition actions.h:346
static const TOOL_EVENT SelectedItemsModified
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition actions.h:353
Used when the right click button is pressed, or when the select tool is in effect.
Definition collectors.h:207
A handler that is based on a set of callbacks provided by the user of the ITEM_MODIFICATION_ROUTINE.
static LSET AllNonCuMask()
Return a mask holding all layer minus CU layers.
Definition lset.cpp:610
void ProcessItem(BOARD_ITEM &aItem)
wxString GetCommitDescription() const override
std::optional< wxString > GetStatusMessage() const
Definition pad.h:54
static TOOL_ACTION convertToKeepout
static TOOL_ACTION convertToTracks
static TOOL_ACTION convertToLines
static TOOL_ACTION convertToZone
static TOOL_ACTION convertToPoly
static TOOL_ACTION outsetItems
Create outset items from selection.
static TOOL_ACTION convertToArc
static TOOL_ACTION createArray
Tool for creating an array of objects.
void SetMid(const VECTOR2I &aMid)
Definition pcb_track.h:346
const VECTOR2I & GetMid() const
Definition pcb_track.h:347
std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT) const override
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
void GetBoundingHull(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Common, abstract interface for edit frames.
BOARD * GetBoard() const
static SELECTION_CONDITION SameLayer()
Creates a functor that tests if selection contains items that belong exclusively to the same layer.
The selection tool: currently supports:
void FilterCollectorForLockedItems(GENERAL_COLLECTOR &aCollector)
In the PCB editor strip out any locked items unless the OverrideLocks checkbox is set.
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:81
int GetWidth() const override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const override
Convert the shape to a closed polygon.
STROKE_PARAMS GetStroke() const override
Definition pcb_shape.h:91
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition pcb_shape.h:92
T * frame() const
bool IsFootprintEditor() const
PCB_TOOL_BASE(TOOL_ID aId, const std::string &aName)
Constructor.
BOARD * board() const
const PCB_SELECTION & selection() const
FOOTPRINT * footprint() const
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:150
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:153
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const override
Convert the track shape to a closed polygon.
const VECTOR2I & GetStart() const
Definition pcb_track.h:154
const VECTOR2I & GetEnd() const
Definition pcb_track.h:151
virtual void SetWidth(int aWidth)
Definition pcb_track.h:147
virtual int GetWidth() const
Definition pcb_track.h:148
A round rectangle shape, based on a rectangle and a radius.
Definition roundrect.h:36
void TransformToPolygon(SHAPE_POLY_SET &aBuffer) const
Get the polygonal representation of the roundrect.
Definition roundrect.cpp:81
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I B
Definition seg.h:50
int Length() const
Return the length (this).
Definition seg.h:343
static SEG::ecoord Square(int a)
Definition seg.h:123
Class that groups generic conditions for selected items.
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...
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.
static SELECTION_CONDITION OnlyTypes(std::vector< KICAD_T > aTypes)
Create a functor that tests if the selected items are only of given types.
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:118
SHAPE_ARC Reversed() const
const VECTOR2I & GetP1() const
Definition shape_arc.h:117
const VECTOR2I & GetP0() const
Definition shape_arc.h:116
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_ARC & Arc(size_t aArc) const
virtual const VECTOR2I GetPoint(int aIndex) const override
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
virtual const SEG GetSegment(int aIndex) const override
virtual size_t GetSegmentCount() const override
size_t ArcCount() const
bool IsArcSegment(size_t aSegment) const
void Insert(size_t aVertex, const VECTOR2I &aP)
Represent a set of closed polygons.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Return the reference to aHole-th hole in the aIndex-th outline.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
const SHAPE_LINE_CHAIN & CHole(int aOutline, int aHole) const
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
Simple container to manage line stroke parameters.
int GetWidth() const
T * getEditFrame() const
Return the application window object, casted to requested user type.
Definition tool_base.h:186
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
Generic, UI-independent tool event.
Definition tool_event.h:171
bool IsAction(const TOOL_ACTION *aAction) const
Test if the event contains an action issued upon activation of the given TOOL_ACTION.
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).
ZONE_SETTINGS handles zones parameters.
void SetIsRuleArea(bool aEnable)
void ExportSetting(ZONE &aTarget, bool aFullExport=true) const
Function ExportSetting copy settings to a given zone.
Handle a list of polygons defining a copper zone.
Definition zone.h:74
void HatchBorder()
Compute the hatch lines depending on the hatch parameters and stores it in the zone's attribute m_bor...
Definition zone.cpp:1244
SHAPE_POLY_SET * Outline()
Definition zone.h:335
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:194
This file is part of the common library.
PCB_SELECTION_CONDITIONS P_S_C
@ ROUND_ALL_CORNERS
All angles are rounded.
int InvokeCopperZonesEditor(PCB_BASE_FRAME *aCaller, ZONE_SETTINGS *aSettings, CONVERT_SETTINGS *aConvertSettings)
Function InvokeCopperZonesEditor invokes up a modal dialog window for copper zone editing.
int InvokeNonCopperZonesEditor(PCB_BASE_FRAME *aParent, ZONE_SETTINGS *aSettings, CONVERT_SETTINGS *aConvertSettings)
Function InvokeNonCopperZonesEditor invokes up a modal dialog window for non-copper zone editing.
int InvokeRuleAreaEditor(PCB_BASE_FRAME *aCaller, ZONE_SETTINGS *aZoneSettings, BOARD *aBoard, CONVERT_SETTINGS *aConvertSettings)
Function InvokeRuleAreaEditor invokes up a modal dialog window for copper zone editing.
#define _(s)
#define STRUCT_DELETED
flag indication structures to be erased
#define SKIP_STRUCT
flag indicating that the structure should be ignored
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
FILL_T
Definition eda_shape.h:56
static const std::vector< KICAD_T > padTypes
static const std::vector< KICAD_T > trackTypes
@ FRAME_PCB_EDITOR
Definition frame_type.h:42
@ FRAME_FOOTPRINT_EDITOR
Definition frame_type.h:43
bool IsNonCopperLayer(int aLayerId)
Test whether a layer is a non copper layer.
Definition layer_ids.h:709
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:676
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ F_CrtYd
Definition layer_ids.h:116
@ Edge_Cuts
Definition layer_ids.h:112
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ F_Cu
Definition layer_ids.h:64
This file contains miscellaneous commonly used macros and functions.
#define _HKI(x)
Definition page_info.cpp:44
BARCODE class definition.
std::optional< int > GetBoardItemWidth(const BOARD_ITEM &aItem)
Gets the width of a BOARD_ITEM, for items that have a meaningful width.
Utility functions that can be shared by PCB tools.
CONVERT_STRATEGY
@ COPY_LINEWIDTH
@ CENTERLINE
@ BOUNDING_HULL
static PGM_BASE * process
Definition pgm_base.cpp:910
SCH_CONDITIONS S_C
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
CONVERT_STRATEGY m_Strategy
VECTOR2I center
VECTOR2I end
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:521
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:88
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:108
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:92
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:90
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:101
@ PCB_SHAPE_LOCATE_CIRCLE_T
Definition typeinfo.h:139
@ PCB_SHAPE_LOCATE_SEGMENT_T
Definition typeinfo.h:137
@ PCB_SHAPE_LOCATE_RECT_T
Definition typeinfo.h:138
@ PCB_SHAPE_LOCATE_BEZIER_T
Definition typeinfo.h:142
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
@ PCB_SHAPE_LOCATE_POLY_T
Definition typeinfo.h:141
@ PCB_SHAPE_LOCATE_ARC_T
Definition typeinfo.h:140
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:96
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695