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 ), nullptr, this );
163 m_rbCenterline->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
164 wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ), nullptr, this );
165 m_rbBoundingHull->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
166 wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ), nullptr, this );
167
169 }
170
172 {
173 delete m_gap;
174 delete m_width;
175 };
176
177protected:
178 bool TransferDataToWindow() override
179 {
180 switch( m_settings->m_Strategy )
181 {
182 case COPY_LINEWIDTH: m_rbMimicLineWidth->SetValue( true ); break;
183 case CENTERLINE: m_rbCenterline->SetValue( true ); break;
184 case BOUNDING_HULL: m_rbBoundingHull->SetValue( true ); break;
185 }
186
187 m_gap->Enable( m_rbBoundingHull->GetValue() );
188 m_width->Enable( m_rbBoundingHull->GetValue() );
189 m_gap->SetValue( m_settings->m_Gap );
190 m_width->SetValue( m_settings->m_LineWidth );
191
192 m_cbDeleteOriginals->SetValue( m_settings->m_DeleteOriginals );
193 return true;
194 }
195
197 {
198 if( m_rbBoundingHull->GetValue() )
199 m_settings->m_Strategy = BOUNDING_HULL;
200 else if( m_rbCenterline->GetValue() )
201 m_settings->m_Strategy = CENTERLINE;
202 else
203 m_settings->m_Strategy = COPY_LINEWIDTH;
204
205 m_settings->m_Gap = m_gap->GetIntValue();
206 m_settings->m_LineWidth = m_width->GetIntValue();
207
208 m_settings->m_DeleteOriginals = m_cbDeleteOriginals->GetValue();
209 return true;
210 }
211
212 void onRadioButton( wxCommandEvent& aEvent )
213 {
214 m_gap->Enable( m_rbBoundingHull->GetValue() );
215 m_width->Enable( m_rbBoundingHull->GetValue() );
216 }
217
218private:
220
221 wxRadioButton* m_rbMimicLineWidth;
222 wxRadioButton* m_rbCenterline;
223 wxRadioButton* m_rbBoundingHull;
224 wxStaticText* m_gapLabel;
225 wxTextCtrl* m_gapCtrl;
226 wxStaticText* m_gapUnits;
228 wxStaticText* m_widthLabel;
229 wxTextCtrl* m_widthCtrl;
230 wxStaticText* m_widthUnits;
233};
234
235
237 PCB_TOOL_BASE( "pcbnew.Convert" ),
238 m_selectionTool( nullptr ),
239 m_menu( nullptr ),
240 m_frame( nullptr )
241{
243}
244
245
247{
248 delete m_menu;
249}
250
251
254
255
257{
260
261 // Create a context menu and make it available through selection tool
262 m_menu = new CONDITIONAL_MENU( this );
263 m_menu->SetIcon( BITMAPS::convert );
264 m_menu->SetUntranslatedTitle( _HKI( "Create from Selection" ) );
265
266 static const std::vector<KICAD_T> padTypes = { PCB_PAD_T };
267 static const std::vector<KICAD_T> toArcTypes = { PCB_ARC_T,
270 static const std::vector<KICAD_T> shapeTypes = { PCB_SHAPE_LOCATE_SEGMENT_T,
278 static const std::vector<KICAD_T> trackTypes = { PCB_TRACE_T,
279 PCB_ARC_T,
280 PCB_VIA_T };
281 static const std::vector<KICAD_T> toTrackTypes = { PCB_SHAPE_LOCATE_SEGMENT_T,
283 static const std::vector<KICAD_T> polyTypes = { PCB_ZONE_T,
286 static const std::vector<KICAD_T> outsetTypes = { PCB_PAD_T, PCB_SHAPE_T };
287
288 auto shapes = S_C::OnlyTypes( shapeTypes ) && P_S_C::SameLayer();
289 auto graphicToTrack = S_C::OnlyTypes( toTrackTypes );
290 auto anyTracks = S_C::MoreThan( 0 ) && S_C::OnlyTypes( trackTypes ) && P_S_C::SameLayer();
291 auto anyPolys = S_C::OnlyTypes( polyTypes );
292 auto anyPads = S_C::OnlyTypes( padTypes );
293
294 auto canCreateArcs = S_C::Count( 1 ) && S_C::OnlyTypes( toArcTypes );
295 auto canCreateArray = S_C::MoreThan( 0 );
296 auto canCreatePoly = shapes || anyPolys || anyTracks;
297
298 auto canCreateOutset = S_C::OnlyTypes( outsetTypes );
299
300 if( m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) )
301 canCreatePoly = shapes || anyPolys || anyTracks || anyPads;
302
303 auto canCreateLines = anyPolys;
304 auto canCreateTracks = anyPolys || graphicToTrack;
305 auto canCreate = canCreatePoly
306 || canCreateLines
307 || canCreateTracks
308 || canCreateArcs
309 || canCreateArray
310 || canCreateOutset;
311
312 m_menu->AddItem( PCB_ACTIONS::convertToPoly, canCreatePoly );
313
314 if( m_frame->IsType( FRAME_PCB_EDITOR ) )
315 m_menu->AddItem( PCB_ACTIONS::convertToZone, canCreatePoly );
316
317 m_menu->AddItem( PCB_ACTIONS::convertToKeepout, canCreatePoly );
318 m_menu->AddItem( PCB_ACTIONS::convertToLines, canCreateLines );
319 m_menu->AddItem( PCB_ACTIONS::outsetItems, canCreateOutset );
320 m_menu->AddSeparator();
321
322 // Currently the code exists, but tracks are not really existing in footprints
323 // only segments on copper layers
324 if( m_frame->IsType( FRAME_PCB_EDITOR ) )
325 m_menu->AddItem( PCB_ACTIONS::convertToTracks, canCreateTracks );
326
327 m_menu->AddItem( PCB_ACTIONS::convertToArc, canCreateArcs );
328
329 m_menu->AddSeparator();
330 m_menu->AddItem( PCB_ACTIONS::createArray, canCreateArray );
331
332 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
333 selToolMenu.AddMenu( m_menu, canCreate, 100 );
334
335 return true;
336}
337
338
340{
341 m_userSettings.m_Strategy = CENTERLINE;
342 m_userSettings.m_Gap = 0;
343 m_userSettings.m_LineWidth = 0;
344 m_userSettings.m_DeleteOriginals = true;
345}
346
347
349{
350 BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
351 std::vector<SHAPE_POLY_SET> polys;
352 PCB_LAYER_ID destLayer = m_frame->GetActiveLayer();
353 FOOTPRINT* parentFootprint = nullptr;
354
355 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
356 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
357 {
358 } );
359
360 if( selection.Empty() )
361 return 0;
362
363 auto getPolys =
364 [&]( CONVERT_SETTINGS cfg )
365 {
366 polys.clear();
367
368 for( EDA_ITEM* item : selection )
369 item->ClearTempFlags();
370
371 SHAPE_POLY_SET polySet;
372
373 polySet.Append( makePolysFromClosedGraphics( selection.GetItems(), cfg.m_Strategy ) );
374
375 if( cfg.m_Strategy == BOUNDING_HULL )
376 {
377 polySet.Append( makePolysFromOpenGraphics( selection.GetItems(), 0 ) );
378
379 polySet.ClearArcs();
380 polySet.Simplify();
381
382 // Now inflate the bounding hull by cfg.m_Gap
384 }
385 else
386 {
387 polySet.Append( makePolysFromChainedSegs( selection.GetItems(), cfg.m_Strategy ) );
388 }
389
390 if( polySet.IsEmpty() )
391 return false;
392
393 for( int ii = 0; ii < polySet.OutlineCount(); ++ii )
394 {
395 polys.emplace_back( SHAPE_POLY_SET( polySet.COutline( ii ) ) );
396
397 for( int jj = 0; jj < polySet.HoleCount( ii ); ++jj )
398 polys.back().AddHole( polySet.Hole( ii, jj ) );
399 }
400
401 return true;
402 };
403
404 // Pre-flight getPolys() to see if there's anything to convert.
405 CONVERT_SETTINGS preflightSettings = m_userSettings;
406 preflightSettings.m_Strategy = BOUNDING_HULL;
407
408 if( !getPolys( preflightSettings ) )
409 return 0;
410
411 if( selection.Front() && selection.Front()->IsBOARD_ITEM() )
412 parentFootprint = static_cast<BOARD_ITEM*>( selection.Front() )->GetParentFootprint();
413
414 PCB_LAYER_ID layer = m_frame->GetActiveLayer();
415 BOARD_COMMIT commit( m_frame );
416
417 if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) )
418 {
419 bool showCopyLineWidth = true;
420
421 // No copy-line-width option for pads
422 if( selection.Front()->Type() == PCB_PAD_T )
423 {
424 if( m_userSettings.m_Strategy == COPY_LINEWIDTH )
425 m_userSettings.m_Strategy = CENTERLINE;
426
427 showCopyLineWidth = false;
428 }
429
430 CONVERT_SETTINGS previousSettings = m_userSettings;
431
432 CONVERT_SETTINGS_DIALOG dlg( m_frame, &m_userSettings, showCopyLineWidth, true, true );
433
434 if( dlg.ShowModal() != wxID_OK )
435 return 0;
436
437 CONVERT_SETTINGS resolvedSettings = m_userSettings;
438
439 if( resolvedSettings.m_Strategy != CENTERLINE )
440 {
441 if( resolvedSettings.m_LineWidth == 0 )
442 resolvedSettings.m_LineWidth = bds.m_LineThickness[ bds.GetLayerClass( layer ) ];
443 }
444
445 if( resolvedSettings.m_Strategy == BOUNDING_HULL )
446 {
447 if( resolvedSettings.m_Gap > 0 )
448 resolvedSettings.m_Gap += KiROUND( (double) resolvedSettings.m_LineWidth / 2.0 );
449 }
450
451 if( !getPolys( resolvedSettings ) )
452 {
453 wxString msg;
454
455 if( resolvedSettings.m_Strategy == BOUNDING_HULL )
456 msg = _( "Resulting polygon would be empty" );
457 else
458 msg = _( "Objects must form a closed shape" );
459
460 DisplayErrorMessage( m_frame, _( "Could not convert selection" ), msg );
461
462 m_userSettings = previousSettings;
463 return 0;
464 }
465
466 for( const SHAPE_POLY_SET& poly : polys )
467 {
468 PCB_SHAPE* graphic = new PCB_SHAPE( parentFootprint );
469
470 if( resolvedSettings.m_Strategy == COPY_LINEWIDTH )
471 {
472 BOARD_ITEM* topLeftItem = nullptr;
473 VECTOR2I pos;
474
475 for( EDA_ITEM* item : selection )
476 {
477 if( !item->IsBOARD_ITEM() )
478 continue;
479
480 BOARD_ITEM* candidate = static_cast<BOARD_ITEM*>( item );
481
482 if( candidate->HasLineStroke() )
483 {
484 pos = candidate->GetPosition();
485
486 if( !topLeftItem
487 || ( pos.x < topLeftItem->GetPosition().x )
488 || ( topLeftItem->GetPosition().x == pos.x
489 && pos.y < topLeftItem->GetPosition().y ) )
490 {
491 topLeftItem = candidate;
492 resolvedSettings.m_LineWidth = topLeftItem->GetStroke().GetWidth();
493 }
494 }
495 }
496 }
497
498 graphic->SetShape( SHAPE_T::POLY );
499 graphic->SetStroke( STROKE_PARAMS( resolvedSettings.m_LineWidth, LINE_STYLE::SOLID,
501 graphic->SetFilled( resolvedSettings.m_Strategy == CENTERLINE );
502 graphic->SetLayer( destLayer );
503 graphic->SetPolyShape( poly );
504
505 commit.Add( graphic );
506 }
507 }
508 else
509 {
510 // Creating zone or keepout
512 BOARD_ITEM_CONTAINER* parent = frame->GetModel();
513 ZONE_SETTINGS zoneInfo = bds.GetDefaultZoneSettings();
514
515 bool nonCopper = IsNonCopperLayer( destLayer );
516 zoneInfo.m_Layers.reset().set( destLayer );
517 zoneInfo.m_Name.Empty();
518
519 int ret;
520
521 // No copy-line-width option for zones/keepouts
522 if( m_userSettings.m_Strategy == COPY_LINEWIDTH )
523 m_userSettings.m_Strategy = CENTERLINE;
524
526 {
527 zoneInfo.SetIsRuleArea( true );
528 ret = InvokeRuleAreaEditor( frame, &zoneInfo, board(), &m_userSettings );
529 }
530 else if( nonCopper )
531 {
532 zoneInfo.SetIsRuleArea( false );
533 ret = InvokeNonCopperZonesEditor( frame, &zoneInfo, &m_userSettings );
534 }
535 else
536 {
537 zoneInfo.SetIsRuleArea( false );
538 ret = InvokeCopperZonesEditor( frame, nullptr, &zoneInfo, &m_userSettings );
539 }
540
541 if( ret == wxID_CANCEL )
542 return 0;
543
544 if( !getPolys( m_userSettings ) )
545 return 0;
546
547 for( const SHAPE_POLY_SET& poly : polys )
548 {
549 ZONE* zone = new ZONE( parent );
550
551 *zone->Outline() = poly;
552 zone->HatchBorder();
553
554 zoneInfo.ExportSetting( *zone );
555
556 commit.Add( zone );
557 }
558 }
559
560 if( m_userSettings.m_DeleteOriginals )
561 {
562 PCB_SELECTION selectionCopy = selection;
563 m_selectionTool->ClearSelection();
564
565 for( EDA_ITEM* item : selectionCopy )
566 {
567 if( item->GetFlags() & SKIP_STRUCT )
568 commit.Remove( item );
569 }
570 }
571
572 if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) )
573 {
574 if( m_userSettings.m_DeleteOriginals )
575 commit.Push( _( "Convert to Polygon" ) );
576 else
577 commit.Push( _( "Create Polygon" ) );
578 }
579 else
580 {
581 if( m_userSettings.m_DeleteOriginals )
582 commit.Push( _( "Convert to Zone" ) );
583 else
584 commit.Push( _( "Create Zone" ) );
585 }
586
587 return 0;
588}
589
590
591SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::deque<EDA_ITEM*>& aItems,
592 CONVERT_STRATEGY aStrategy )
593{
594 // TODO: This code has a somewhat-similar purpose to ConvertOutlineToPolygon but is slightly
595 // different, so this remains a separate algorithm. It might be nice to analyze the dfiferences
596 // in requirements and refactor this.
597
598 // Using a large epsilon here to allow for sloppy drawing can cause the algorithm to miss very
599 // short segments in a converted bezier. So use an epsilon only large enough to cover for
600 // rouding errors in the conversion.
601 int chainingEpsilon = 100; // max dist from one endPt to next startPt in IU
602
603 SHAPE_POLY_SET poly;
604
605 // Stores pairs of (anchor, item) where anchor == 0 -> SEG.A, anchor == 1 -> SEG.B
606 std::map<VECTOR2I, std::vector<std::pair<int, EDA_ITEM*>>> connections;
607 std::deque<EDA_ITEM*> toCheck;
608
609 auto closeEnough =
610 []( const VECTOR2I& aLeft, const VECTOR2I& aRight, int aLimit )
611 {
612 return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit );
613 };
614
615 auto findInsertionPoint =
616 [&]( const VECTOR2I& aPoint ) -> VECTOR2I
617 {
618 if( connections.count( aPoint ) )
619 return aPoint;
620
621 for( const auto& candidatePair : connections )
622 {
623 if( closeEnough( aPoint, candidatePair.first, chainingEpsilon ) )
624 return candidatePair.first;
625 }
626
627 return aPoint;
628 };
629
630 for( EDA_ITEM* item : aItems )
631 {
632 if( std::optional<SEG> seg = getStartEndPoints( item ) )
633 {
634 toCheck.push_back( item );
635 connections[findInsertionPoint( seg->A )].emplace_back( std::make_pair( 0, item ) );
636 connections[findInsertionPoint( seg->B )].emplace_back( std::make_pair( 1, item ) );
637 }
638 }
639
640 while( !toCheck.empty() )
641 {
642 std::vector<BOARD_ITEM*> insertedItems;
643
644 EDA_ITEM* candidate = toCheck.front();
645 toCheck.pop_front();
646
647 if( candidate->GetFlags() & SKIP_STRUCT )
648 continue;
649
650 SHAPE_LINE_CHAIN outline;
651
652 auto insert =
653 [&]( EDA_ITEM* aItem, const VECTOR2I& aAnchor, bool aDirection )
654 {
655 if( aItem->IsType( { PCB_ARC_T, PCB_SHAPE_LOCATE_ARC_T } ) )
656 {
657 SHAPE_ARC arc;
658
659 if( aItem->Type() == PCB_ARC_T )
660 {
661 PCB_ARC* pcb_arc = static_cast<PCB_ARC*>( aItem );
662 arc = *static_cast<SHAPE_ARC*>( pcb_arc->GetEffectiveShape().get() );
663 }
664 else
665 {
666 PCB_SHAPE* pcb_shape = static_cast<PCB_SHAPE*>( aItem );
667 arc = SHAPE_ARC( pcb_shape->GetStart(), pcb_shape->GetArcMid(),
668 pcb_shape->GetEnd(), pcb_shape->GetWidth() );
669 }
670
671 if( aDirection )
672 outline.Append( aAnchor == arc.GetP0() ? arc : arc.Reversed() );
673 else
674 outline.Insert( 0, aAnchor == arc.GetP0() ? arc : arc.Reversed() );
675
676 insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
677 }
678 else if( aItem->IsType( { PCB_SHAPE_LOCATE_BEZIER_T } ) )
679 {
680 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
681
682 if( aAnchor == graphic->GetStart() )
683 {
684 for( const VECTOR2I& pt : graphic->GetBezierPoints() )
685 {
686 if( aDirection )
687 outline.Append( pt );
688 else
689 outline.Insert( 0, pt );
690 }
691
692 }
693 else
694 {
695 for( const VECTOR2I& pt : std::ranges::reverse_view( graphic->GetBezierPoints() ) )
696 {
697 if( aDirection )
698 outline.Append( pt );
699 else
700 outline.Insert( 0, pt );
701 }
702 }
703
704 insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
705 }
706 else if( std::optional<SEG> nextSeg = getStartEndPoints( aItem ) )
707 {
708 VECTOR2I& point = ( aAnchor == nextSeg->A ) ? nextSeg->B : nextSeg->A;
709
710 if( aDirection )
711 outline.Append( point );
712 else
713 outline.Insert( 0, point );
714
715 insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
716 }
717 };
718
719 // aDirection == true for walking "right" and appending to the end of points
720 // false for walking "left" and prepending to the beginning
721 std::function<void( EDA_ITEM*, const VECTOR2I&, bool )> process =
722 [&]( EDA_ITEM* aItem, const VECTOR2I& aAnchor, bool aDirection )
723 {
724 if( aItem->GetFlags() & SKIP_STRUCT )
725 return;
726
727 aItem->SetFlags( SKIP_STRUCT );
728
729 insert( aItem, aAnchor, aDirection );
730
731 std::optional<SEG> anchors = getStartEndPoints( aItem );
732 wxASSERT( anchors );
733
734 VECTOR2I nextAnchor = ( aAnchor == anchors->A ) ? anchors->B : anchors->A;
735
736 for( std::pair<int, EDA_ITEM*> pair : connections[nextAnchor] )
737 {
738 if( pair.second == aItem )
739 continue;
740
741 process( pair.second, nextAnchor, aDirection );
742 }
743 };
744
745 std::optional<SEG> anchors = getStartEndPoints( candidate );
746 wxASSERT( anchors );
747
748 // Start with the first object and walk "right"
749 // Note if the first object is an arc, we don't need to insert its first point here, the
750 // whole arc will be inserted at anchor B inside process()
751 if( !candidate->IsType( { PCB_ARC_T, PCB_SHAPE_LOCATE_ARC_T } ) )
752 insert( candidate, anchors->A, true );
753
754 process( candidate, anchors->B, true );
755
756 // check for any candidates on the "left"
757 EDA_ITEM* left = nullptr;
758
759 for( std::pair<int, EDA_ITEM*> possibleLeft : connections[anchors->A] )
760 {
761 if( possibleLeft.second != candidate )
762 {
763 left = possibleLeft.second;
764 break;
765 }
766 }
767
768 if( left )
769 process( left, anchors->A, false );
770
771 if( outline.PointCount() < 3
772 || !closeEnough( outline.GetPoint( 0 ), outline.GetPoint( -1 ), chainingEpsilon ) )
773 {
774 for( EDA_ITEM* item : insertedItems )
775 item->ClearFlags( SKIP_STRUCT );
776
777 continue;
778 }
779
780 outline.SetClosed( true );
781
782 poly.AddOutline( outline );
783
784 if( aStrategy == BOUNDING_HULL )
785 {
786 for( BOARD_ITEM* item : insertedItems )
787 item->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, item->GetMaxError(), ERROR_INSIDE );
788 }
789
790 insertedItems.clear();
791 }
792
793 return poly;
794}
795
796
797SHAPE_POLY_SET CONVERT_TOOL::makePolysFromOpenGraphics( const std::deque<EDA_ITEM*>& aItems, int aGap )
798{
799 SHAPE_POLY_SET poly;
800
801 for( EDA_ITEM* item : aItems )
802 {
803 if( item->GetFlags() & SKIP_STRUCT )
804 continue;
805
806 switch( item->Type() )
807 {
808 case PCB_SHAPE_T:
809 {
810 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
811
812 if( shape->IsClosed() )
813 continue;
814
815 shape->TransformShapeToPolygon( poly, UNDEFINED_LAYER, aGap, shape->GetMaxError(), ERROR_INSIDE );
816 shape->SetFlags( SKIP_STRUCT );
817
818 break;
819 }
820
821 case PCB_TRACE_T:
822 case PCB_ARC_T:
823 case PCB_VIA_T:
824 {
825 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
826
827 track->TransformShapeToPolygon( poly, UNDEFINED_LAYER, aGap, track->GetMaxError(), ERROR_INSIDE );
828 track->SetFlags( SKIP_STRUCT );
829
830 break;
831 }
832
833 default:
834 continue;
835 }
836 }
837
838 return poly;
839}
840
841
843 CONVERT_STRATEGY aStrategy )
844{
845 SHAPE_POLY_SET poly;
846
847 for( EDA_ITEM* item : aItems )
848 {
849 if( item->GetFlags() & SKIP_STRUCT )
850 continue;
851
852 switch( item->Type() )
853 {
854 case PCB_SHAPE_T:
855 {
856 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
857 FILL_T wasFilled = shape->GetFillMode();
858
859 if( !shape->IsClosed() )
860 continue;
861
862 if( shape->GetShape() == SHAPE_T::CIRCLE && aStrategy != BOUNDING_HULL )
863 {
864 VECTOR2I c = shape->GetCenter();
865 int R = shape->GetRadius();
866 SHAPE_ARC arc( c - VECTOR2I( R, 0 ), c + VECTOR2I( R, 0 ), c - VECTOR2I( R, 0 ), 0 );
867
868 poly.NewOutline();
869 poly.Append( arc );
870 }
871 else
872 {
873 if( aStrategy != BOUNDING_HULL )
874 shape->SetFilled( true );
875
877 aStrategy != BOUNDING_HULL );
878
879 if( aStrategy != BOUNDING_HULL )
880 shape->SetFillMode( wasFilled );
881 }
882
883 shape->SetFlags( SKIP_STRUCT );
884 break;
885 }
886
887 case PCB_ZONE_T:
888 poly.Append( *static_cast<ZONE*>( item )->Outline() );
889 item->SetFlags( SKIP_STRUCT );
890 break;
891
892 case PCB_FIELD_T:
893 case PCB_TEXT_T:
894 {
895 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
896 text->TransformTextToPolySet( poly, 0, text->GetMaxError(), ERROR_INSIDE );
897 text->SetFlags( SKIP_STRUCT );
898 break;
899 }
900
901 case PCB_BARCODE_T:
902 {
903 PCB_BARCODE* barcode = static_cast<PCB_BARCODE*>( item );
904
905 if( aStrategy == BOUNDING_HULL )
906 barcode->GetBoundingHull( poly, UNDEFINED_LAYER, 0, barcode->GetMaxError(), ERROR_INSIDE );
907 else
908 barcode->TransformShapeToPolySet( poly, UNDEFINED_LAYER, 0, barcode->GetMaxError(), ERROR_INSIDE );
909
910 barcode->SetFlags( SKIP_STRUCT );
911 break;
912 }
913
914 case PCB_PAD_T:
915 {
916 PAD* pad = static_cast<PAD*>( item );
917
918 pad->Padstack().ForEachUniqueLayer(
919 [&]( PCB_LAYER_ID aLayer )
920 {
921 SHAPE_POLY_SET layerPoly;
922 pad->TransformShapeToPolygon( layerPoly, aLayer, 0, pad->GetMaxError(), ERROR_INSIDE );
923 poly.BooleanAdd( layerPoly );
924 } );
925
926 pad->SetFlags( SKIP_STRUCT );
927 break;
928 }
929
930
931 default:
932 continue;
933 }
934 }
935
936 return poly;
937}
938
939
941{
942 auto& selection = m_selectionTool->RequestSelection(
943 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
944 {
945 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
946 {
947 BOARD_ITEM* item = aCollector[i];
948
949 switch( item->Type() )
950 {
951 case PCB_SHAPE_T:
952 switch( static_cast<PCB_SHAPE*>( item )->GetShape() )
953 {
954 case SHAPE_T::SEGMENT:
955 case SHAPE_T::ARC:
956 case SHAPE_T::POLY:
958 break;
959
960 default:
961 aCollector.Remove( item );
962 }
963
964 break;
965
966 case PCB_ZONE_T:
967 break;
968
969 default:
970 aCollector.Remove( item );
971 }
972 }
973 } );
974
975 if( selection.Empty() )
976 return 0;
977
978 BOARD_COMMIT commit( m_frame );
980 FOOTPRINT_EDIT_FRAME* fpEditor = dynamic_cast<FOOTPRINT_EDIT_FRAME*>( m_frame );
981 FOOTPRINT* footprint = nullptr;
982 PCB_LAYER_ID targetLayer = m_frame->GetActiveLayer();
983 BOARD_ITEM_CONTAINER* parent = frame->GetModel();
984
985 if( fpEditor )
986 footprint = fpEditor->GetBoard()->GetFirstFootprint();
987
988 auto handleGraphicSeg =
989 [&]( EDA_ITEM* aItem )
990 {
991 if( aItem->Type() != PCB_SHAPE_T )
992 return false;
993
994 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
995
996 if( graphic->GetShape() == SHAPE_T::SEGMENT )
997 {
998 PCB_TRACK* track = new PCB_TRACK( parent );
999
1000 track->SetLayer( targetLayer );
1001 track->SetStart( graphic->GetStart() );
1002 track->SetEnd( graphic->GetEnd() );
1003 track->SetWidth( graphic->GetWidth() );
1004 commit.Add( track );
1005
1006 return true;
1007 }
1008 else if( graphic->GetShape() == SHAPE_T::ARC )
1009 {
1010 PCB_ARC* arc = new PCB_ARC( parent );
1011
1012 arc->SetLayer( targetLayer );
1013 arc->SetStart( graphic->GetStart() );
1014 arc->SetEnd( graphic->GetEnd() );
1015 arc->SetMid( graphic->GetArcMid() );
1016 arc->SetWidth( graphic->GetWidth() );
1017 commit.Add( arc );
1018
1019 return true;
1020 }
1021
1022 return false;
1023 };
1024
1025 auto addGraphicChain =
1026 [&]( const SHAPE_LINE_CHAIN& aChain, std::optional<int> aWidth )
1027 {
1028 for( size_t si = 0; si < aChain.GetSegmentCount(); ++si )
1029 {
1030 const SEG seg = aChain.GetSegment( si );
1031
1032 if( seg.Length() == 0 )
1033 continue;
1034
1035 if( aChain.IsArcSegment( si ) )
1036 continue;
1037
1038 PCB_SHAPE* graphic = new PCB_SHAPE( footprint, SHAPE_T::SEGMENT );
1039
1040 graphic->SetLayer( targetLayer );
1041 graphic->SetStart( seg.A );
1042 graphic->SetEnd( seg.B );
1043
1044 if( aWidth && *aWidth > 0 )
1045 graphic->SetWidth( *aWidth );
1046
1047 commit.Add( graphic );
1048 }
1049
1050 for( size_t ai = 0; ai < aChain.ArcCount(); ++ai )
1051 {
1052 const SHAPE_ARC& arc = aChain.Arc( ai );
1053
1054 if( arc.GetP0() == arc.GetP1() )
1055 continue;
1056
1057 PCB_SHAPE* graphic = new PCB_SHAPE( footprint, SHAPE_T::ARC );
1058
1059 graphic->SetLayer( targetLayer );
1060 graphic->SetFilled( false );
1061 graphic->SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() );
1062
1063 if( aWidth && *aWidth > 0 )
1064 graphic->SetWidth( *aWidth );
1065
1066 commit.Add( graphic );
1067 }
1068 };
1069
1070 auto addTrackChain =
1071 [&]( const SHAPE_LINE_CHAIN& aChain, std::optional<int> aWidth )
1072 {
1073 for( size_t si = 0; si < aChain.GetSegmentCount(); ++si )
1074 {
1075 const SEG seg = aChain.GetSegment( si );
1076
1077 if( seg.Length() == 0 )
1078 continue;
1079
1080 if( aChain.IsArcSegment( si ) )
1081 continue;
1082
1083 PCB_TRACK* track = new PCB_TRACK( parent );
1084
1085 track->SetLayer( targetLayer );
1086 track->SetStart( seg.A );
1087 track->SetEnd( seg.B );
1088
1089 if( aWidth && *aWidth > 0 )
1090 track->SetWidth( *aWidth );
1091
1092 commit.Add( track );
1093 }
1094
1095 for( size_t ai = 0; ai < aChain.ArcCount(); ++ai )
1096 {
1097 const SHAPE_ARC& arc = aChain.Arc( ai );
1098
1099 if( arc.GetP0() == arc.GetP1() )
1100 continue;
1101
1102 PCB_ARC* trackArc = new PCB_ARC( parent );
1103
1104 trackArc->SetLayer( targetLayer );
1105 trackArc->SetStart( arc.GetP0() );
1106 trackArc->SetEnd( arc.GetP1() );
1107 trackArc->SetMid( arc.GetArcMid() );
1108
1109 if( aWidth && *aWidth > 0 )
1110 trackArc->SetWidth( *aWidth );
1111
1112 commit.Add( trackArc );
1113 }
1114 };
1115
1116 auto processChain =
1117 [&]( const SHAPE_LINE_CHAIN& aChain, std::optional<int> aWidth )
1118 {
1119 if( aChain.GetSegmentCount() == 0 && aChain.ArcCount() == 0 )
1120 return;
1121
1122 if( aEvent.IsAction( &PCB_ACTIONS::convertToLines ) )
1123 {
1124 addGraphicChain( aChain, aWidth );
1125 }
1126 else if( fpEditor )
1127 {
1128 addGraphicChain( aChain, aWidth );
1129 }
1130 else
1131 {
1132 addTrackChain( aChain, aWidth );
1133 }
1134 };
1135
1136 auto processPolySet =
1137 [&]( const SHAPE_POLY_SET& aPoly, std::optional<int> aWidth )
1138 {
1139 for( int oi = 0; oi < aPoly.OutlineCount(); ++oi )
1140 {
1141 processChain( aPoly.COutline( oi ), aWidth );
1142
1143 for( int hi = 0; hi < aPoly.HoleCount( oi ); ++hi )
1144 processChain( aPoly.CHole( oi, hi ), aWidth );
1145 }
1146 };
1147
1148 if( aEvent.IsAction( &PCB_ACTIONS::convertToTracks ) )
1149 {
1150 if( !IsCopperLayer( targetLayer ) )
1151 {
1152 targetLayer = frame->SelectOneLayer( F_Cu, LSET::AllNonCuMask() );
1153
1154 if( targetLayer == UNDEFINED_LAYER ) // User canceled
1155 return true;
1156 }
1157 }
1158 else
1159 {
1160 CONVERT_SETTINGS_DIALOG dlg( m_frame, &m_userSettings, false, false, false );
1161
1162 if( dlg.ShowModal() != wxID_OK )
1163 return true;
1164 }
1165
1166 for( EDA_ITEM* item : selection )
1167 {
1168 if( !item->IsBOARD_ITEM() )
1169 continue;
1170
1171 if( handleGraphicSeg( item ) )
1172 continue;
1173
1174 BOARD_ITEM& boardItem = static_cast<BOARD_ITEM&>( *item );
1175 std::optional<int> itemWidth = GetBoardItemWidth( boardItem );
1176
1177 if( boardItem.Type() == PCB_SHAPE_T )
1178 {
1179 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( item );
1180
1181 switch( graphic->GetShape() )
1182 {
1183 case SHAPE_T::RECTANGLE:
1184 {
1185 SHAPE_RECT rect( graphic->GetStart(), graphic->GetEnd() );
1186 ROUNDRECT rrect( rect, graphic->GetCornerRadius(), true );
1187 SHAPE_POLY_SET poly;
1188
1189 rrect.TransformToPolygon( poly, graphic->GetMaxError() );
1190 processPolySet( poly, itemWidth );
1191 break;
1192 }
1193
1194 case SHAPE_T::POLY:
1195 processPolySet( graphic->GetPolyShape(), itemWidth );
1196 break;
1197
1198 default:
1199 wxFAIL_MSG( wxT( "Unhandled graphic shape type in PolyToLines" ) );
1200 break;
1201 }
1202 }
1203 else if( boardItem.Type() == PCB_ZONE_T )
1204 {
1205 ZONE* zone = static_cast<ZONE*>( item );
1206 processPolySet( *zone->Outline(), itemWidth );
1207 }
1208 else
1209 {
1210 wxFAIL_MSG( wxT( "Unhandled type in PolyToLines" ) );
1211 }
1212 }
1213
1214 if( m_userSettings.m_DeleteOriginals )
1215 {
1216 PCB_SELECTION selectionCopy = selection;
1217 m_selectionTool->ClearSelection();
1218
1219 for( EDA_ITEM* item : selectionCopy )
1220 commit.Remove( item );
1221 }
1222
1223 commit.Push( _( "Create Lines" ) );
1224
1225 return 0;
1226}
1227
1228
1230{
1231 auto& selection = m_selectionTool->RequestSelection(
1232 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1233 {
1234 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1235 {
1236 BOARD_ITEM* item = aCollector[i];
1237
1238 if( !item->IsType( { PCB_SHAPE_T, PCB_TRACE_T, PCB_ARC_T } ) )
1239 aCollector.Remove( item );
1240 }
1241 } );
1242
1243 if( selection.Empty() || !selection.Front()->IsBOARD_ITEM() )
1244 return -1;
1245
1246 BOARD_ITEM* source = static_cast<BOARD_ITEM*>( selection.Front() );
1247 VECTOR2I start, end, mid;
1248
1249 // Offset the midpoint along the normal a little bit so that it's more obviously an arc
1250 const double offsetRatio = 0.1;
1251
1252 if( std::optional<SEG> seg = getStartEndPoints( source ) )
1253 {
1254 start = seg->A;
1255 end = seg->B;
1256
1257 VECTOR2I normal = ( seg->B - seg->A ).Perpendicular().Resize( offsetRatio * seg->Length() );
1258 mid = seg->Center() + normal;
1259 }
1260 else
1261 {
1262 return -1;
1263 }
1264
1266 BOARD_ITEM_CONTAINER* parent = frame->GetModel();
1267 PCB_LAYER_ID layer = source->GetLayer();
1268 BOARD_COMMIT commit( m_frame );
1269
1270 if( source->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( source )->GetShape() == SHAPE_T::SEGMENT )
1271 {
1272 PCB_SHAPE* line = static_cast<PCB_SHAPE*>( source );
1273 PCB_SHAPE* arc = new PCB_SHAPE( parent, SHAPE_T::ARC );
1274
1275 VECTOR2I center = CalcArcCenter( start, mid, end );
1276
1277 arc->SetFilled( false );
1278 arc->SetLayer( layer );
1279 arc->SetStroke( line->GetStroke() );
1280
1281 arc->SetCenter( center );
1282 arc->SetStart( start );
1283 arc->SetEnd( end );
1284
1285 commit.Add( arc );
1286 }
1287 else if( source->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( source )->GetShape() == SHAPE_T::ARC )
1288 {
1289 PCB_SHAPE* source_arc = static_cast<PCB_SHAPE*>( source );
1290 PCB_ARC* arc = new PCB_ARC( parent );
1291
1292 arc->SetLayer( layer );
1293 arc->SetWidth( source_arc->GetWidth() );
1294 arc->SetStart( start );
1295 arc->SetMid( source_arc->GetArcMid() );
1296 arc->SetEnd( end );
1297
1298 commit.Add( arc );
1299 }
1300 else if( source->Type() == PCB_TRACE_T )
1301 {
1302 PCB_TRACK* line = static_cast<PCB_TRACK*>( source );
1303 PCB_ARC* arc = new PCB_ARC( parent );
1304
1305 arc->SetLayer( layer );
1306 arc->SetWidth( line->GetWidth() );
1307 arc->SetStart( start );
1308 arc->SetMid( mid );
1309 arc->SetEnd( end );
1310
1311 commit.Add( arc );
1312 }
1313 else if( source->Type() == PCB_ARC_T )
1314 {
1315 PCB_ARC* source_arc = static_cast<PCB_ARC*>( source );
1316 PCB_SHAPE* arc = new PCB_SHAPE( parent, SHAPE_T::ARC );
1317
1318 arc->SetFilled( false );
1319 arc->SetLayer( layer );
1320 arc->SetWidth( source_arc->GetWidth() );
1321
1322 arc->SetArcGeometry( source_arc->GetStart(), source_arc->GetMid(), source_arc->GetEnd() );
1323 commit.Add( arc );
1324 }
1325
1326 commit.Push( _( "Create Arc" ) );
1327
1328 return 0;
1329}
1330
1331
1332std::optional<SEG> CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem )
1333{
1334 switch( aItem->Type() )
1335 {
1336 case PCB_SHAPE_T:
1337 {
1338 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
1339
1340 switch( shape->GetShape() )
1341 {
1342 case SHAPE_T::SEGMENT:
1343 case SHAPE_T::ARC:
1344 case SHAPE_T::POLY:
1345 case SHAPE_T::BEZIER:
1346 if( shape->GetStart() == shape->GetEnd() )
1347 return std::nullopt;
1348
1349 return std::make_optional<SEG>( VECTOR2I( shape->GetStart() ), VECTOR2I( shape->GetEnd() ) );
1350
1351 default:
1352 return std::nullopt;
1353 }
1354 }
1355
1356 case PCB_TRACE_T:
1357 {
1358 PCB_TRACK* line = static_cast<PCB_TRACK*>( aItem );
1359 return std::make_optional<SEG>( VECTOR2I( line->GetStart() ), VECTOR2I( line->GetEnd() ) );
1360 }
1361
1362 case PCB_ARC_T:
1363 {
1364 PCB_ARC* arc = static_cast<PCB_ARC*>( aItem );
1365 return std::make_optional<SEG>( VECTOR2I( arc->GetStart() ), VECTOR2I( arc->GetEnd() ) );
1366 }
1367
1368 default:
1369 return std::nullopt;
1370 }
1371}
1372
1373
1375{
1377 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1378 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1379 {
1380 // Iterate from the back so we don't have to worry about removals.
1381 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1382 {
1383 BOARD_ITEM* item = aCollector[i];
1384
1385 // We've converted the polygon and rectangle to segments, so drop everything
1386 // that isn't a segment at this point
1387 if( !item->IsType( { PCB_PAD_T, PCB_SHAPE_T } ) )
1388 aCollector.Remove( item );
1389 }
1390
1391 sTool->FilterCollectorForLockedItems( aCollector );
1392 } );
1393
1394 BOARD_COMMIT commit( this );
1395
1396 for( EDA_ITEM* item : selection )
1397 item->ClearFlags( STRUCT_DELETED );
1398
1399 // List of thing to select at the end of the operation
1400 // (doing it as we go will invalidate the iterator)
1401 std::vector<BOARD_ITEM*> items_to_select_on_success;
1402
1403 // Handle modifications to existing items by the routine
1404 // How to deal with this depends on whether we're in the footprint editor or not
1405 // and whether the item was conjured up by decomposing a polygon or rectangle
1406 auto item_modification_handler =
1407 [&]( BOARD_ITEM& aItem )
1408 {
1409 };
1410
1411 bool any_items_created = false;
1412
1413 auto item_creation_handler =
1414 [&]( std::unique_ptr<BOARD_ITEM> aItem )
1415 {
1416 any_items_created = true;
1417 items_to_select_on_success.push_back( aItem.get() );
1418 commit.Add( aItem.release() );
1419 };
1420
1421 auto item_removal_handler =
1422 [&]( BOARD_ITEM& aItem )
1423 {
1424 // If you do an outset on a FP pad, do you really want to delete
1425 // the parent?
1426 if( !aItem.GetParentFootprint() )
1427 {
1428 commit.Remove( &aItem );
1429 }
1430 };
1431
1432 // Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
1433 ITEM_MODIFICATION_ROUTINE::CALLABLE_BASED_HANDLER change_handler( item_creation_handler,
1434 item_modification_handler,
1435 item_removal_handler );
1436
1437 // Persistent settings between dialog invocations
1438 // Init with some sensible defaults
1439 static OUTSET_ROUTINE::PARAMETERS outset_params_fp_edit
1440 {
1441 pcbIUScale.mmToIU( 0.25 ), // A common outset value
1442 false,
1443 false,
1444 true,
1445 F_CrtYd,
1446 frame.GetDesignSettings().GetLineThickness( F_CrtYd ),
1447 pcbIUScale.mmToIU( 0.01 ),
1448 false,
1449 };
1450
1451 static OUTSET_ROUTINE::PARAMETERS outset_params_pcb_edit
1452 {
1453 pcbIUScale.mmToIU( 1 ),
1454 true,
1455 true,
1456 true,
1457 Edge_Cuts, // Outsets often for slots?
1458 frame.GetDesignSettings().GetLineThickness( Edge_Cuts ),
1459 std::nullopt,
1460 false,
1461 };
1462
1463 OUTSET_ROUTINE::PARAMETERS& outset_params = IsFootprintEditor() ? outset_params_fp_edit
1464 : outset_params_pcb_edit;
1465
1466 {
1467 DIALOG_OUTSET_ITEMS dlg( frame, outset_params );
1468
1469 if( dlg.ShowModal() == wxID_CANCEL )
1470 return 0;
1471 }
1472
1473 OUTSET_ROUTINE outset_routine( frame.GetModel(), change_handler, outset_params );
1474
1475 for( EDA_ITEM* item : selection )
1476 {
1477 if( !item->IsBOARD_ITEM() )
1478 continue;
1479
1480 BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
1481 outset_routine.ProcessItem( *board_item );
1482 }
1483
1484 // Deselect all the original items
1485 m_selectionTool->ClearSelection();
1486
1487 // Select added and modified items
1488 for( BOARD_ITEM* item : items_to_select_on_success )
1489 m_selectionTool->AddItemToSel( item, true );
1490
1491 if( any_items_created )
1492 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1493
1494 // Notify other tools of the changes
1496
1497 commit.Push( outset_routine.GetCommitDescription() );
1498
1499 if( const std::optional<wxString> msg = outset_routine.GetStatusMessage() )
1500 frame.ShowInfoBarMsg( *msg );
1501
1502 return 0;
1503}
1504
1505
@ 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.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
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:84
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:237
virtual STROKE_PARAMS GetStroke() const
FOOTPRINT * GetParentFootprint() const
virtual void TransformShapeToPolySet(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, KIGFX::RENDER_SETTINGS *aRenderSettings=nullptr) const
Convert the item shape to a polyset.
Definition board_item.h:430
virtual bool HasLineStroke() const
Check if this item has line stoke properties.
Definition board_item.h:227
int GetMaxError() const
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:530
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:402
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:99
virtual VECTOR2I GetPosition() const
Definition eda_item.h:278
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:148
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:111
virtual bool IsType(const std::vector< KICAD_T > &aScanTypes) const
Check whether the item is one of the listed types.
Definition eda_item.h:198
EDA_ITEM_FLAGS GetFlags() const
Definition eda_item.h:151
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:169
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:216
bool IsClosed() const
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:178
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:174
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:168
const std::vector< VECTOR2I > & GetBezierPoints() const
Definition eda_shape.h:321
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:220
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:345
static const TOOL_EVENT SelectedItemsModified
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition actions.h:352
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:627
void ProcessItem(BOARD_ITEM &aItem)
wxString GetCommitDescription() const override
std::optional< wxString > GetStatusMessage() const
Definition pad.h:55
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:289
const VECTOR2I & GetMid() const
Definition pcb_track.h:290
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:93
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:96
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:97
const VECTOR2I & GetEnd() const
Definition pcb_track.h:94
virtual void SetWidth(int aWidth)
Definition pcb_track.h:90
virtual int GetWidth() const
Definition pcb_track.h:91
A round rectangle shape, based on a rectangle and a radius.
Definition roundrect.h:36
void TransformToPolygon(SHAPE_POLY_SET &aBuffer, int aMaxError) const
Get the polygonal representation of the roundrect.
Definition roundrect.cpp:83
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:120
SHAPE_ARC Reversed() const
const VECTOR2I & GetP1() const
Definition shape_arc.h:119
const VECTOR2I & GetP0() const
Definition shape_arc.h:118
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:73
void HatchBorder()
Compute the hatch lines depending on the hatch parameters and stores it in the zone's attribute m_bor...
Definition zone.cpp:1293
SHAPE_POLY_SET * Outline()
Definition zone.h:340
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:202
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 *aZone, 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:710
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:677
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
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