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>
46#include <pcb_edit_frame.h>
47#include <pcb_shape.h>
48#include <pcb_track.h>
49#include <pad.h>
50#include <string_utils.h>
51#include <tool/tool_manager.h>
52#include <tools/edit_tool.h>
53#include <tools/pcb_actions.h>
56#include <trigo.h>
57#include <macros.h>
58#include <zone.h>
59
60
62{
63public:
65 bool aShowCopyLineWidthOption, bool aShowCenterlineOption,
66 bool aShowBoundingHullOption ) :
67 DIALOG_SHIM( aParent, wxID_ANY, _( "Conversion Settings" ), wxDefaultPosition,
68 wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
69 m_settings( aSettings )
70 {
71 // Allow each distinct version of the dialog to have a different size
72 m_hash_key = TO_UTF8( wxString::Format( wxS( "%s%c%c%c" ),
73 GetTitle(),
74 aShowCopyLineWidthOption,
75 aShowCenterlineOption,
76 aShowBoundingHullOption ) );
77
78 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
79 wxBoxSizer* topSizer = new wxBoxSizer( wxVERTICAL );
80 SetSizer( mainSizer );
81
82 m_rbMimicLineWidth = new wxRadioButton( this, wxID_ANY, _( "Copy line width of first object" ) );
83
84 if( aShowCopyLineWidthOption )
85 topSizer->Add( m_rbMimicLineWidth, 0, wxLEFT|wxRIGHT, 5 );
86 else
87 m_rbMimicLineWidth->Hide();
88
89 m_rbCenterline = new wxRadioButton( this, wxID_ANY, _( "Use centerlines" ) );
90
91 if( aShowCenterlineOption )
92 {
93 topSizer->AddSpacer( 6 );
94 topSizer->Add( m_rbCenterline, 0, wxLEFT|wxRIGHT, 5 );
95 }
96 else
97 {
98 m_rbCenterline->Hide();
99 }
100
101 m_rbBoundingHull = new wxRadioButton( this, wxID_ANY, _( "Create bounding hull" ) );
102
103 m_gapLabel = new wxStaticText( this, wxID_ANY, _( "Gap:" ) );
104 m_gapCtrl = new wxTextCtrl( this, wxID_ANY );
105 m_gapUnits = new wxStaticText( this, wxID_ANY, _( "mm" ) );
106 m_gap = new UNIT_BINDER( aParent, m_gapLabel, m_gapCtrl, m_gapUnits );
107
108 m_widthLabel = new wxStaticText( this, wxID_ANY, _( "Line width:" ) );
109 m_widthCtrl = new wxTextCtrl( this, wxID_ANY );
110 m_widthUnits = new wxStaticText( this, wxID_ANY, _( "mm" ) );
112
113 if( aShowBoundingHullOption )
114 {
115 topSizer->AddSpacer( 6 );
116 topSizer->Add( m_rbBoundingHull, 0, wxLEFT|wxRIGHT, 5 );
117
118 wxBoxSizer* hullParamsSizer = new wxBoxSizer( wxHORIZONTAL );
119 hullParamsSizer->Add( m_gapLabel, 0, wxALIGN_CENTRE_VERTICAL, 5 );
120 hullParamsSizer->Add( m_gapCtrl, 1, wxALIGN_CENTRE_VERTICAL|wxLEFT|wxRIGHT, 3 );
121 hullParamsSizer->Add( m_gapUnits, 0, wxALIGN_CENTRE_VERTICAL, 5 );
122 hullParamsSizer->AddSpacer( 18 );
123 hullParamsSizer->Add( m_widthLabel, 0, wxALIGN_CENTRE_VERTICAL, 5 );
124 hullParamsSizer->Add( m_widthCtrl, 1, wxALIGN_CENTRE_VERTICAL|wxLEFT|wxRIGHT, 3 );
125 hullParamsSizer->Add( m_widthUnits, 0, wxALIGN_CENTRE_VERTICAL, 5 );
126
127 topSizer->AddSpacer( 2 );
128 topSizer->Add( hullParamsSizer, 0, wxLEFT, 26 );
129
130 topSizer->AddSpacer( 15 );
131 }
132 else
133 {
134 m_rbBoundingHull->Hide();
135 m_gap->Show( false, true );
136 m_width->Show( false, true );
137 }
138
139 m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, _( "Delete source objects after conversion" ) );
140 topSizer->Add( m_cbDeleteOriginals, 0, wxALL, 5 );
141
142 mainSizer->Add( topSizer, 1, wxALL|wxEXPAND, 10 );
143
144 wxBoxSizer* buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
145 buttonsSizer->AddStretchSpacer();
146
147 wxStdDialogButtonSizer* sdbSizer = new wxStdDialogButtonSizer();
148 wxButton* sdbSizerOK = new wxButton( this, wxID_OK );
149 sdbSizer->AddButton( sdbSizerOK );
150 wxButton* sdbSizerCancel = new wxButton( this, wxID_CANCEL );
151 sdbSizer->AddButton( sdbSizerCancel );
152 sdbSizer->Realize();
153
154 buttonsSizer->Add( sdbSizer, 1, 0, 5 );
155 mainSizer->Add( buttonsSizer, 0, wxALL|wxEXPAND, 5 );
156
158
159 m_rbMimicLineWidth->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
160 wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
161 nullptr, this );
162 m_rbCenterline->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
163 wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
164 nullptr, this );
165 m_rbBoundingHull->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
166 wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
167 nullptr, this );
168
170 }
171
173 {
174 delete m_gap;
175 delete m_width;
176 };
177
178protected:
179 bool TransferDataToWindow() override
180 {
181 switch( m_settings->m_Strategy )
182 {
183 case COPY_LINEWIDTH: m_rbMimicLineWidth->SetValue( true ); break;
184 case CENTERLINE: m_rbCenterline->SetValue( true ); break;
185 case BOUNDING_HULL: m_rbBoundingHull->SetValue( true ); break;
186 }
187
188 m_gap->Enable( m_rbBoundingHull->GetValue() );
189 m_width->Enable( m_rbBoundingHull->GetValue() );
190 m_gap->SetValue( m_settings->m_Gap );
191 m_width->SetValue( m_settings->m_LineWidth );
192
193 m_cbDeleteOriginals->SetValue( m_settings->m_DeleteOriginals );
194 return true;
195 }
196
198 {
199 if( m_rbBoundingHull->GetValue() )
200 m_settings->m_Strategy = BOUNDING_HULL;
201 else if( m_rbCenterline->GetValue() )
202 m_settings->m_Strategy = CENTERLINE;
203 else
204 m_settings->m_Strategy = COPY_LINEWIDTH;
205
206 m_settings->m_Gap = m_gap->GetIntValue();
207 m_settings->m_LineWidth = m_width->GetIntValue();
208
209 m_settings->m_DeleteOriginals = m_cbDeleteOriginals->GetValue();
210 return true;
211 }
212
213 void onRadioButton( wxCommandEvent& aEvent )
214 {
215 m_gap->Enable( m_rbBoundingHull->GetValue() );
216 m_width->Enable( m_rbBoundingHull->GetValue() );
217 }
218
219private:
221
222 wxRadioButton* m_rbMimicLineWidth;
223 wxRadioButton* m_rbCenterline;
224 wxRadioButton* m_rbBoundingHull;
225 wxStaticText* m_gapLabel;
226 wxTextCtrl* m_gapCtrl;
227 wxStaticText* m_gapUnits;
229 wxStaticText* m_widthLabel;
230 wxTextCtrl* m_widthCtrl;
231 wxStaticText* m_widthUnits;
234};
235
236
238 PCB_TOOL_BASE( "pcbnew.Convert" ),
239 m_selectionTool( nullptr ),
240 m_menu( nullptr ),
241 m_frame( nullptr )
242{
244}
245
246
248{
249 delete m_menu;
250}
251
252
255
256
258{
261
262 // Create a context menu and make it available through selection tool
263 m_menu = new CONDITIONAL_MENU( this );
264 m_menu->SetIcon( BITMAPS::convert );
265 m_menu->SetTitle( _( "Create from Selection" ) );
266
267 static const std::vector<KICAD_T> padTypes = { PCB_PAD_T };
268 static const std::vector<KICAD_T> toArcTypes = { PCB_ARC_T,
271 static const std::vector<KICAD_T> shapeTypes = { PCB_SHAPE_LOCATE_SEGMENT_T,
277 PCB_TEXT_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
385 }
386 else
387 {
388 polySet.Append( makePolysFromChainedSegs( selection.GetItems(), cfg.m_Strategy ) );
389 }
390
391 if( polySet.IsEmpty() )
392 return false;
393
394 for( int ii = 0; ii < polySet.OutlineCount(); ++ii )
395 {
396 polys.emplace_back( SHAPE_POLY_SET( polySet.COutline( ii ) ) );
397
398 for( int jj = 0; jj < polySet.HoleCount( ii ); ++jj )
399 polys.back().AddHole( polySet.Hole( ii, jj ) );
400 }
401
402 return true;
403 };
404
405 // Pre-flight getPolys() to see if there's anything to convert.
406 CONVERT_SETTINGS preflightSettings = m_userSettings;
407 preflightSettings.m_Strategy = BOUNDING_HULL;
408
409 if( !getPolys( preflightSettings ) )
410 return 0;
411
412 if( selection.Front() && selection.Front()->IsBOARD_ITEM() )
413 parentFootprint = static_cast<BOARD_ITEM*>( selection.Front() )->GetParentFootprint();
414
415 PCB_LAYER_ID layer = m_frame->GetActiveLayer();
416 BOARD_COMMIT commit( m_frame );
417
418 if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) )
419 {
420 bool showCopyLineWidth = true;
421
422 // No copy-line-width option for pads
423 if( selection.Front()->Type() == PCB_PAD_T )
424 {
425 if( m_userSettings.m_Strategy == COPY_LINEWIDTH )
426 m_userSettings.m_Strategy = CENTERLINE;
427
428 showCopyLineWidth = false;
429 }
430
431 CONVERT_SETTINGS previousSettings = m_userSettings;
432
433 CONVERT_SETTINGS_DIALOG dlg( m_frame, &m_userSettings, showCopyLineWidth, true, true );
434
435 if( dlg.ShowModal() != wxID_OK )
436 return 0;
437
438 CONVERT_SETTINGS resolvedSettings = m_userSettings;
439
440 if( resolvedSettings.m_Strategy != CENTERLINE )
441 {
442 if( resolvedSettings.m_LineWidth == 0 )
443 resolvedSettings.m_LineWidth = bds.m_LineThickness[ bds.GetLayerClass( layer ) ];
444 }
445
446 if( resolvedSettings.m_Strategy == BOUNDING_HULL )
447 {
448 if( resolvedSettings.m_Gap > 0 )
449 resolvedSettings.m_Gap += KiROUND( (double) resolvedSettings.m_LineWidth / 2.0 );
450 }
451
452 if( !getPolys( resolvedSettings ) )
453 {
454 wxString msg;
455
456 if( resolvedSettings.m_Strategy == BOUNDING_HULL )
457 msg = _( "Resulting polygon would be empty" );
458 else
459 msg = _( "Objects must form a closed shape" );
460
461 DisplayErrorMessage( m_frame, _( "Could not convert selection" ), msg );
462
463 m_userSettings = previousSettings;
464 return 0;
465 }
466
467 for( const SHAPE_POLY_SET& poly : polys )
468 {
469 PCB_SHAPE* graphic = new PCB_SHAPE( parentFootprint );
470
471 if( resolvedSettings.m_Strategy == COPY_LINEWIDTH )
472 {
473 BOARD_ITEM* topLeftItem = nullptr;
474 VECTOR2I pos;
475
476 for( EDA_ITEM* item : selection )
477 {
478 if( !item->IsBOARD_ITEM() )
479 continue;
480
481 BOARD_ITEM* candidate = static_cast<BOARD_ITEM*>( item );
482
483 if( candidate->HasLineStroke() )
484 {
485 pos = candidate->GetPosition();
486
487 if( !topLeftItem
488 || ( pos.x < topLeftItem->GetPosition().x )
489 || ( topLeftItem->GetPosition().x == pos.x
490 && pos.y < topLeftItem->GetPosition().y ) )
491 {
492 topLeftItem = candidate;
493 resolvedSettings.m_LineWidth = topLeftItem->GetStroke().GetWidth();
494 }
495 }
496 }
497 }
498
499 graphic->SetShape( SHAPE_T::POLY );
500 graphic->SetStroke( STROKE_PARAMS( resolvedSettings.m_LineWidth, LINE_STYLE::SOLID,
502 graphic->SetFilled( resolvedSettings.m_Strategy == CENTERLINE );
503 graphic->SetLayer( destLayer );
504 graphic->SetPolyShape( poly );
505
506 commit.Add( graphic );
507 }
508 }
509 else
510 {
511 // Creating zone or keepout
513 BOARD_ITEM_CONTAINER* parent = frame->GetModel();
514 ZONE_SETTINGS zoneInfo = bds.GetDefaultZoneSettings();
515
516 bool nonCopper = IsNonCopperLayer( destLayer );
517 zoneInfo.m_Layers.reset().set( destLayer );
518 zoneInfo.m_Name.Empty();
519
520 int ret;
521
522 // No copy-line-width option for zones/keepouts
523 if( m_userSettings.m_Strategy == COPY_LINEWIDTH )
524 m_userSettings.m_Strategy = CENTERLINE;
525
527 {
528 zoneInfo.SetIsRuleArea( true );
529 ret = InvokeRuleAreaEditor( frame, &zoneInfo, board(), &m_userSettings );
530 }
531 else if( nonCopper )
532 {
533 zoneInfo.SetIsRuleArea( false );
534 ret = InvokeNonCopperZonesEditor( frame, &zoneInfo, &m_userSettings );
535 }
536 else
537 {
538 zoneInfo.SetIsRuleArea( false );
539 ret = InvokeCopperZonesEditor( frame, &zoneInfo, &m_userSettings );
540 }
541
542 if( ret == wxID_CANCEL )
543 return 0;
544
545 if( !getPolys( m_userSettings ) )
546 return 0;
547
548 for( const SHAPE_POLY_SET& poly : polys )
549 {
550 ZONE* zone = new ZONE( parent );
551
552 *zone->Outline() = poly;
553 zone->HatchBorder();
554
555 zoneInfo.ExportSetting( *zone );
556
557 commit.Add( zone );
558 }
559 }
560
561 if( m_userSettings.m_DeleteOriginals )
562 {
563 PCB_SELECTION selectionCopy = selection;
564 m_selectionTool->ClearSelection();
565
566 for( EDA_ITEM* item : selectionCopy )
567 {
568 if( item->GetFlags() & SKIP_STRUCT )
569 commit.Remove( item );
570 }
571 }
572
573 if( aEvent.IsAction( &PCB_ACTIONS::convertToPoly ) )
574 {
575 if( m_userSettings.m_DeleteOriginals )
576 commit.Push( _( "Convert to Polygon" ) );
577 else
578 commit.Push( _( "Create Polygon" ) );
579 }
580 else
581 {
582 if( m_userSettings.m_DeleteOriginals )
583 commit.Push( _( "Convert to Zone" ) );
584 else
585 commit.Push( _( "Create Zone" ) );
586 }
587
588 return 0;
589}
590
591
592SHAPE_POLY_SET CONVERT_TOOL::makePolysFromChainedSegs( const std::deque<EDA_ITEM*>& aItems,
593 CONVERT_STRATEGY aStrategy )
594{
595 // TODO: This code has a somewhat-similar purpose to ConvertOutlineToPolygon but is slightly
596 // different, so this remains a separate algorithm. It might be nice to analyze the dfiferences
597 // in requirements and refactor this.
598
599 // Using a large epsilon here to allow for sloppy drawing can cause the algorithm to miss very
600 // short segments in a converted bezier. So use an epsilon only large enough to cover for
601 // rouding errors in the conversion.
602 int chainingEpsilon = 100; // max dist from one endPt to next startPt in IU
603
604 SHAPE_POLY_SET poly;
605
606 // Stores pairs of (anchor, item) where anchor == 0 -> SEG.A, anchor == 1 -> SEG.B
607 std::map<VECTOR2I, std::vector<std::pair<int, EDA_ITEM*>>> connections;
608 std::deque<EDA_ITEM*> toCheck;
609
610 auto closeEnough =
611 []( const VECTOR2I& aLeft, const VECTOR2I& aRight, int aLimit )
612 {
613 return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit );
614 };
615
616 auto findInsertionPoint =
617 [&]( const VECTOR2I& aPoint ) -> VECTOR2I
618 {
619 if( connections.count( aPoint ) )
620 return aPoint;
621
622 for( const auto& candidatePair : connections )
623 {
624 if( closeEnough( aPoint, candidatePair.first, chainingEpsilon ) )
625 return candidatePair.first;
626 }
627
628 return aPoint;
629 };
630
631 for( EDA_ITEM* item : aItems )
632 {
633 if( std::optional<SEG> seg = getStartEndPoints( item ) )
634 {
635 toCheck.push_back( item );
636 connections[findInsertionPoint( seg->A )].emplace_back( std::make_pair( 0, item ) );
637 connections[findInsertionPoint( seg->B )].emplace_back( std::make_pair( 1, item ) );
638 }
639 }
640
641 while( !toCheck.empty() )
642 {
643 std::vector<BOARD_ITEM*> insertedItems;
644
645 EDA_ITEM* candidate = toCheck.front();
646 toCheck.pop_front();
647
648 if( candidate->GetFlags() & SKIP_STRUCT )
649 continue;
650
651 SHAPE_LINE_CHAIN outline;
652
653 auto insert =
654 [&]( EDA_ITEM* aItem, const VECTOR2I& aAnchor, bool aDirection )
655 {
656 if( aItem->IsType( { PCB_ARC_T, PCB_SHAPE_LOCATE_ARC_T } ) )
657 {
658 SHAPE_ARC arc;
659
660 if( aItem->Type() == PCB_ARC_T )
661 {
662 PCB_ARC* pcb_arc = static_cast<PCB_ARC*>( aItem );
663 arc = *static_cast<SHAPE_ARC*>( pcb_arc->GetEffectiveShape().get() );
664 }
665 else
666 {
667 PCB_SHAPE* pcb_shape = static_cast<PCB_SHAPE*>( aItem );
668 arc = SHAPE_ARC( pcb_shape->GetStart(), pcb_shape->GetArcMid(),
669 pcb_shape->GetEnd(), pcb_shape->GetWidth() );
670 }
671
672 if( aDirection )
673 outline.Append( aAnchor == arc.GetP0() ? arc : arc.Reversed() );
674 else
675 outline.Insert( 0, aAnchor == arc.GetP0() ? arc : arc.Reversed() );
676
677 insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
678 }
679 else if( aItem->IsType( { PCB_SHAPE_LOCATE_BEZIER_T } ) )
680 {
681 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
682
683 if( aAnchor == graphic->GetStart() )
684 {
685 for( const VECTOR2I& pt : graphic->GetBezierPoints() )
686 {
687 if( aDirection )
688 outline.Append( pt );
689 else
690 outline.Insert( 0, pt );
691 }
692
693 }
694 else
695 {
696 for( const VECTOR2I& pt : std::ranges::reverse_view( graphic->GetBezierPoints() ) )
697 {
698 if( aDirection )
699 outline.Append( pt );
700 else
701 outline.Insert( 0, pt );
702 }
703 }
704
705 insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
706 }
707 else if( std::optional<SEG> nextSeg = getStartEndPoints( aItem ) )
708 {
709 VECTOR2I& point = ( aAnchor == nextSeg->A ) ? nextSeg->B : nextSeg->A;
710
711 if( aDirection )
712 outline.Append( point );
713 else
714 outline.Insert( 0, point );
715
716 insertedItems.push_back( static_cast<BOARD_ITEM*>( aItem ) );
717 }
718 };
719
720 // aDirection == true for walking "right" and appending to the end of points
721 // false for walking "left" and prepending to the beginning
722 std::function<void( EDA_ITEM*, const VECTOR2I&, bool )> process =
723 [&]( EDA_ITEM* aItem, const VECTOR2I& aAnchor, bool aDirection )
724 {
725 if( aItem->GetFlags() & SKIP_STRUCT )
726 return;
727
728 aItem->SetFlags( SKIP_STRUCT );
729
730 insert( aItem, aAnchor, aDirection );
731
732 std::optional<SEG> anchors = getStartEndPoints( aItem );
733 wxASSERT( anchors );
734
735 VECTOR2I nextAnchor = ( aAnchor == anchors->A ) ? anchors->B : anchors->A;
736
737 for( std::pair<int, EDA_ITEM*> pair : connections[nextAnchor] )
738 {
739 if( pair.second == aItem )
740 continue;
741
742 process( pair.second, nextAnchor, aDirection );
743 }
744 };
745
746 std::optional<SEG> anchors = getStartEndPoints( candidate );
747 wxASSERT( anchors );
748
749 // Start with the first object and walk "right"
750 // Note if the first object is an arc, we don't need to insert its first point here, the
751 // whole arc will be inserted at anchor B inside process()
752 if( !candidate->IsType( { PCB_ARC_T, PCB_SHAPE_LOCATE_ARC_T } ) )
753 insert( candidate, anchors->A, true );
754
755 process( candidate, anchors->B, true );
756
757 // check for any candidates on the "left"
758 EDA_ITEM* left = nullptr;
759
760 for( std::pair<int, EDA_ITEM*> possibleLeft : connections[anchors->A] )
761 {
762 if( possibleLeft.second != candidate )
763 {
764 left = possibleLeft.second;
765 break;
766 }
767 }
768
769 if( left )
770 process( left, anchors->A, false );
771
772 if( outline.PointCount() < 3
773 || !closeEnough( outline.GetPoint( 0 ), outline.GetPoint( -1 ), chainingEpsilon ) )
774 {
775 for( EDA_ITEM* item : insertedItems )
776 item->ClearFlags( SKIP_STRUCT );
777
778 continue;
779 }
780
781 outline.SetClosed( true );
782
783 poly.AddOutline( outline );
784
785 if( aStrategy == BOUNDING_HULL )
786 {
787 for( BOARD_ITEM* item : insertedItems )
788 item->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, item->GetMaxError(), ERROR_INSIDE );
789 }
790
791 insertedItems.clear();
792 }
793
794 return poly;
795}
796
797
798SHAPE_POLY_SET CONVERT_TOOL::makePolysFromOpenGraphics( const std::deque<EDA_ITEM*>& aItems, int aGap )
799{
800 SHAPE_POLY_SET poly;
801
802 for( EDA_ITEM* item : aItems )
803 {
804 if( item->GetFlags() & SKIP_STRUCT )
805 continue;
806
807 switch( item->Type() )
808 {
809 case PCB_SHAPE_T:
810 {
811 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
812
813 if( shape->IsClosed() )
814 continue;
815
816 shape->TransformShapeToPolygon( poly, UNDEFINED_LAYER, aGap, shape->GetMaxError(), ERROR_INSIDE );
817 shape->SetFlags( SKIP_STRUCT );
818
819 break;
820 }
821
822 case PCB_TRACE_T:
823 case PCB_ARC_T:
824 case PCB_VIA_T:
825 {
826 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
827
828 track->TransformShapeToPolygon( poly, UNDEFINED_LAYER, aGap, track->GetMaxError(), ERROR_INSIDE );
829 track->SetFlags( SKIP_STRUCT );
830
831 break;
832 }
833
834 default:
835 continue;
836 }
837 }
838
839 return poly;
840}
841
842
844 CONVERT_STRATEGY aStrategy )
845{
846 SHAPE_POLY_SET poly;
847
848 for( EDA_ITEM* item : aItems )
849 {
850 if( item->GetFlags() & SKIP_STRUCT )
851 continue;
852
853 switch( item->Type() )
854 {
855 case PCB_SHAPE_T:
856 {
857 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
858 FILL_T wasFilled = shape->GetFillMode();
859
860 if( !shape->IsClosed() )
861 continue;
862
863 if( shape->GetShape() == SHAPE_T::CIRCLE && aStrategy != BOUNDING_HULL )
864 {
865 VECTOR2I c = shape->GetCenter();
866 int R = shape->GetRadius();
867 SHAPE_ARC arc( c - VECTOR2I( R, 0 ), c + VECTOR2I( R, 0 ), c - VECTOR2I( R, 0 ), 0 );
868
869 poly.NewOutline();
870 poly.Append( arc );
871 }
872 else
873 {
874 if( aStrategy != BOUNDING_HULL )
875 shape->SetFilled( true );
876
878 aStrategy != BOUNDING_HULL );
879
880 if( aStrategy != BOUNDING_HULL )
881 shape->SetFillMode( wasFilled );
882 }
883
884 shape->SetFlags( SKIP_STRUCT );
885 break;
886 }
887
888 case PCB_ZONE_T:
889 poly.Append( *static_cast<ZONE*>( item )->Outline() );
890 item->SetFlags( SKIP_STRUCT );
891 break;
892
893 case PCB_FIELD_T:
894 case PCB_TEXT_T:
895 {
896 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
897 text->TransformTextToPolySet( poly, 0, text->GetMaxError(), ERROR_INSIDE );
898 text->SetFlags( SKIP_STRUCT );
899 break;
900 }
901
902 case PCB_PAD_T:
903 {
904 PAD* pad = static_cast<PAD*>( item );
905
906 pad->Padstack().ForEachUniqueLayer(
907 [&]( PCB_LAYER_ID aLayer )
908 {
909 SHAPE_POLY_SET layerPoly;
910 pad->TransformShapeToPolygon( layerPoly, aLayer, 0, pad->GetMaxError(), ERROR_INSIDE );
911 poly.BooleanAdd( layerPoly );
912 } );
913
914 pad->SetFlags( SKIP_STRUCT );
915 break;
916 }
917
918
919 default:
920 continue;
921 }
922 }
923
924 return poly;
925}
926
927
929{
930 auto& selection = m_selectionTool->RequestSelection(
931 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
932 {
933 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
934 {
935 BOARD_ITEM* item = aCollector[i];
936
937 switch( item->Type() )
938 {
939 case PCB_SHAPE_T:
940 switch( static_cast<PCB_SHAPE*>( item )->GetShape() )
941 {
942 case SHAPE_T::SEGMENT:
943 case SHAPE_T::ARC:
944 case SHAPE_T::POLY:
946 break;
947
948 default:
949 aCollector.Remove( item );
950 }
951
952 break;
953
954 case PCB_ZONE_T:
955 break;
956
957 default:
958 aCollector.Remove( item );
959 }
960 }
961 } );
962
963 if( selection.Empty() )
964 return 0;
965
966 auto getPolySet =
967 []( EDA_ITEM* aItem )
968 {
969 SHAPE_POLY_SET set;
970
971 switch( aItem->Type() )
972 {
973 case PCB_ZONE_T:
974 set = *static_cast<ZONE*>( aItem )->Outline();
975 break;
976
977 case PCB_SHAPE_T:
978 {
979 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
980
981 if( graphic->GetShape() == SHAPE_T::RECTANGLE )
982 {
983 graphic->TransformShapeToPolygon( set, graphic->GetLayer(), 0, graphic->GetMaxError(), ERROR_INSIDE );
984 }
985 else if( graphic->GetShape() == SHAPE_T::POLY )
986 {
987 set = graphic->GetPolyShape();
988 }
989 else
990 {
991 wxFAIL_MSG( wxT( "Unhandled graphic shape type in PolyToLines - getPolySet" ) );
992 }
993 break;
994 }
995
996 default:
997 wxFAIL_MSG( wxT( "Unhandled type in PolyToLines - getPolySet" ) );
998 break;
999 }
1000
1001 return set;
1002 };
1003
1004 auto getSegList =
1005 []( SHAPE_POLY_SET& aPoly )
1006 {
1007 std::vector<SEG> segs;
1008
1009 // Our input should be valid polys, so OK to assert here
1010 wxASSERT( aPoly.VertexCount() >= 2 );
1011
1012 for( int i = 1; i < aPoly.VertexCount(); i++ )
1013 segs.emplace_back( SEG( aPoly.CVertex( i - 1 ), aPoly.CVertex( i ) ) );
1014
1015 segs.emplace_back( SEG( aPoly.CVertex( aPoly.VertexCount() - 1 ), aPoly.CVertex( 0 ) ) );
1016
1017 return segs;
1018 };
1019
1020 BOARD_COMMIT commit( m_frame );
1022 FOOTPRINT_EDIT_FRAME* fpEditor = dynamic_cast<FOOTPRINT_EDIT_FRAME*>( m_frame );
1023 FOOTPRINT* footprint = nullptr;
1024 PCB_LAYER_ID targetLayer = m_frame->GetActiveLayer();
1025 BOARD_ITEM_CONTAINER* parent = frame->GetModel();
1026
1027 if( fpEditor )
1028 footprint = fpEditor->GetBoard()->GetFirstFootprint();
1029
1030 auto handleGraphicSeg =
1031 [&]( EDA_ITEM* aItem )
1032 {
1033 if( aItem->Type() != PCB_SHAPE_T )
1034 return false;
1035
1036 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
1037
1038 if( graphic->GetShape() == SHAPE_T::SEGMENT )
1039 {
1040 PCB_TRACK* track = new PCB_TRACK( parent );
1041
1042 track->SetLayer( targetLayer );
1043 track->SetStart( graphic->GetStart() );
1044 track->SetEnd( graphic->GetEnd() );
1045 track->SetWidth( graphic->GetWidth() );
1046 commit.Add( track );
1047
1048 return true;
1049 }
1050 else if( graphic->GetShape() == SHAPE_T::ARC )
1051 {
1052 PCB_ARC* arc = new PCB_ARC( parent );
1053
1054 arc->SetLayer( targetLayer );
1055 arc->SetStart( graphic->GetStart() );
1056 arc->SetEnd( graphic->GetEnd() );
1057 arc->SetMid( graphic->GetArcMid() );
1058 arc->SetWidth( graphic->GetWidth() );
1059 commit.Add( arc );
1060
1061 return true;
1062 }
1063
1064 return false;
1065 };
1066
1067 if( aEvent.IsAction( &PCB_ACTIONS::convertToTracks ) )
1068 {
1069 if( !IsCopperLayer( targetLayer ) )
1070 {
1071 targetLayer = frame->SelectOneLayer( F_Cu, LSET::AllNonCuMask() );
1072
1073 if( targetLayer == UNDEFINED_LAYER ) // User canceled
1074 return true;
1075 }
1076 }
1077 else
1078 {
1079 CONVERT_SETTINGS_DIALOG dlg( m_frame, &m_userSettings, false, false, false );
1080
1081 if( dlg.ShowModal() != wxID_OK )
1082 return true;
1083 }
1084
1085 for( EDA_ITEM* item : selection )
1086 {
1087 if( !item->IsBOARD_ITEM() )
1088 continue;
1089
1090 if( handleGraphicSeg( item ) )
1091 continue;
1092
1093 BOARD_ITEM& boardItem = static_cast<BOARD_ITEM&>( *item );
1094 SHAPE_POLY_SET polySet = getPolySet( item );
1095 std::vector<SEG> segs = getSegList( polySet );
1096
1097 std::optional<int> itemWidth = GetBoardItemWidth( boardItem );
1098
1099 if( aEvent.IsAction( &PCB_ACTIONS::convertToLines ) )
1100 {
1101 for( SEG& seg : segs )
1102 {
1103 PCB_SHAPE* graphic = new PCB_SHAPE( footprint, SHAPE_T::SEGMENT );
1104
1105 graphic->SetLayer( targetLayer );
1106 graphic->SetStart( VECTOR2I( seg.A ) );
1107 graphic->SetEnd( VECTOR2I( seg.B ) );
1108
1109 // The width can exist but be 0 for filled, unstroked shapes
1110 if( itemWidth && *itemWidth > 0 )
1111 graphic->SetWidth( *itemWidth );
1112
1113 commit.Add( graphic );
1114 }
1115 }
1116 else
1117 {
1118 // I am really unsure converting a polygon to "tracks" (i.e. segments on
1119 // copper layers) make sense for footprints, but anyway this code exists
1120 if( fpEditor )
1121 {
1122 // Creating segments on copper layer
1123 for( SEG& seg : segs )
1124 {
1125 PCB_SHAPE* graphic = new PCB_SHAPE( footprint, SHAPE_T::SEGMENT );
1126 graphic->SetLayer( targetLayer );
1127 graphic->SetStart( VECTOR2I( seg.A ) );
1128 graphic->SetEnd( VECTOR2I( seg.B ) );
1129
1130 if( itemWidth )
1131 graphic->SetWidth( *itemWidth );
1132
1133 commit.Add( graphic );
1134 }
1135 }
1136 else
1137 {
1138 // Creating tracks
1139 for( SEG& seg : segs )
1140 {
1141 PCB_TRACK* track = new PCB_TRACK( parent );
1142
1143 track->SetLayer( targetLayer );
1144 track->SetStart( VECTOR2I( seg.A ) );
1145 track->SetEnd( VECTOR2I( seg.B ) );
1146 commit.Add( track );
1147 }
1148 }
1149 }
1150 }
1151
1152 if( m_userSettings.m_DeleteOriginals )
1153 {
1154 PCB_SELECTION selectionCopy = selection;
1155 m_selectionTool->ClearSelection();
1156
1157 for( EDA_ITEM* item : selectionCopy )
1158 commit.Remove( item );
1159 }
1160
1161 commit.Push( _( "Create Lines" ) );
1162
1163 return 0;
1164}
1165
1166
1168{
1169 auto& selection = m_selectionTool->RequestSelection(
1170 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1171 {
1172 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1173 {
1174 BOARD_ITEM* item = aCollector[i];
1175
1176 if( !item->IsType( { PCB_SHAPE_T, PCB_TRACE_T, PCB_ARC_T } ) )
1177 aCollector.Remove( item );
1178 }
1179 } );
1180
1181 if( selection.Empty() || !selection.Front()->IsBOARD_ITEM() )
1182 return -1;
1183
1184 BOARD_ITEM* source = static_cast<BOARD_ITEM*>( selection.Front() );
1185 VECTOR2I start, end, mid;
1186
1187 // Offset the midpoint along the normal a little bit so that it's more obviously an arc
1188 const double offsetRatio = 0.1;
1189
1190 if( std::optional<SEG> seg = getStartEndPoints( source ) )
1191 {
1192 start = seg->A;
1193 end = seg->B;
1194
1195 VECTOR2I normal = ( seg->B - seg->A ).Perpendicular().Resize( offsetRatio * seg->Length() );
1196 mid = seg->Center() + normal;
1197 }
1198 else
1199 {
1200 return -1;
1201 }
1202
1204 BOARD_ITEM_CONTAINER* parent = frame->GetModel();
1205 PCB_LAYER_ID layer = source->GetLayer();
1206 BOARD_COMMIT commit( m_frame );
1207
1208 if( source->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( source )->GetShape() == SHAPE_T::SEGMENT )
1209 {
1210 PCB_SHAPE* line = static_cast<PCB_SHAPE*>( source );
1211 PCB_SHAPE* arc = new PCB_SHAPE( parent, SHAPE_T::ARC );
1212
1213 VECTOR2I center = CalcArcCenter( start, mid, end );
1214
1215 arc->SetFilled( false );
1216 arc->SetLayer( layer );
1217 arc->SetStroke( line->GetStroke() );
1218
1219 arc->SetCenter( center );
1220 arc->SetStart( start );
1221 arc->SetEnd( end );
1222
1223 commit.Add( arc );
1224 }
1225 else if( source->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( source )->GetShape() == SHAPE_T::ARC )
1226 {
1227 PCB_SHAPE* source_arc = static_cast<PCB_SHAPE*>( source );
1228 PCB_ARC* arc = new PCB_ARC( parent );
1229
1230 arc->SetLayer( layer );
1231 arc->SetWidth( source_arc->GetWidth() );
1232 arc->SetStart( start );
1233 arc->SetMid( source_arc->GetArcMid() );
1234 arc->SetEnd( end );
1235
1236 commit.Add( arc );
1237 }
1238 else if( source->Type() == PCB_TRACE_T )
1239 {
1240 PCB_TRACK* line = static_cast<PCB_TRACK*>( source );
1241 PCB_ARC* arc = new PCB_ARC( parent );
1242
1243 arc->SetLayer( layer );
1244 arc->SetWidth( line->GetWidth() );
1245 arc->SetStart( start );
1246 arc->SetMid( mid );
1247 arc->SetEnd( end );
1248
1249 commit.Add( arc );
1250 }
1251 else if( source->Type() == PCB_ARC_T )
1252 {
1253 PCB_ARC* source_arc = static_cast<PCB_ARC*>( source );
1254 PCB_SHAPE* arc = new PCB_SHAPE( parent, SHAPE_T::ARC );
1255
1256 arc->SetFilled( false );
1257 arc->SetLayer( layer );
1258 arc->SetWidth( source_arc->GetWidth() );
1259
1260 arc->SetArcGeometry( source_arc->GetStart(), source_arc->GetMid(), source_arc->GetEnd() );
1261 commit.Add( arc );
1262 }
1263
1264 commit.Push( _( "Create Arc" ) );
1265
1266 return 0;
1267}
1268
1269
1270std::optional<SEG> CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem )
1271{
1272 switch( aItem->Type() )
1273 {
1274 case PCB_SHAPE_T:
1275 {
1276 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
1277
1278 switch( shape->GetShape() )
1279 {
1280 case SHAPE_T::SEGMENT:
1281 case SHAPE_T::ARC:
1282 case SHAPE_T::POLY:
1283 case SHAPE_T::BEZIER:
1284 if( shape->GetStart() == shape->GetEnd() )
1285 return std::nullopt;
1286
1287 return std::make_optional<SEG>( VECTOR2I( shape->GetStart() ), VECTOR2I( shape->GetEnd() ) );
1288
1289 default:
1290 return std::nullopt;
1291 }
1292 }
1293
1294 case PCB_TRACE_T:
1295 {
1296 PCB_TRACK* line = static_cast<PCB_TRACK*>( aItem );
1297 return std::make_optional<SEG>( VECTOR2I( line->GetStart() ), VECTOR2I( line->GetEnd() ) );
1298 }
1299
1300 case PCB_ARC_T:
1301 {
1302 PCB_ARC* arc = static_cast<PCB_ARC*>( aItem );
1303 return std::make_optional<SEG>( VECTOR2I( arc->GetStart() ), VECTOR2I( arc->GetEnd() ) );
1304 }
1305
1306 default:
1307 return std::nullopt;
1308 }
1309}
1310
1311
1313{
1315 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1316 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1317 {
1318 // Iterate from the back so we don't have to worry about removals.
1319 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1320 {
1321 BOARD_ITEM* item = aCollector[i];
1322
1323 // We've converted the polygon and rectangle to segments, so drop everything
1324 // that isn't a segment at this point
1325 if( !item->IsType( { PCB_PAD_T, PCB_SHAPE_T } ) )
1326 aCollector.Remove( item );
1327 }
1328
1329 sTool->FilterCollectorForLockedItems( aCollector );
1330 } );
1331
1332 BOARD_COMMIT commit( this );
1333
1334 for( EDA_ITEM* item : selection )
1335 item->ClearFlags( STRUCT_DELETED );
1336
1337 // List of thing to select at the end of the operation
1338 // (doing it as we go will invalidate the iterator)
1339 std::vector<BOARD_ITEM*> items_to_select_on_success;
1340
1341 // Handle modifications to existing items by the routine
1342 // How to deal with this depends on whether we're in the footprint editor or not
1343 // and whether the item was conjured up by decomposing a polygon or rectangle
1344 auto item_modification_handler =
1345 [&]( BOARD_ITEM& aItem )
1346 {
1347 };
1348
1349 bool any_items_created = false;
1350
1351 auto item_creation_handler =
1352 [&]( std::unique_ptr<BOARD_ITEM> aItem )
1353 {
1354 any_items_created = true;
1355 items_to_select_on_success.push_back( aItem.get() );
1356 commit.Add( aItem.release() );
1357 };
1358
1359 auto item_removal_handler =
1360 [&]( BOARD_ITEM& aItem )
1361 {
1362 // If you do an outset on a FP pad, do you really want to delete
1363 // the parent?
1364 if( !aItem.GetParentFootprint() )
1365 {
1366 commit.Remove( &aItem );
1367 }
1368 };
1369
1370 // Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
1371 ITEM_MODIFICATION_ROUTINE::CALLABLE_BASED_HANDLER change_handler( item_creation_handler,
1372 item_modification_handler,
1373 item_removal_handler );
1374
1375 // Persistent settings between dialog invocations
1376 // Init with some sensible defaults
1377 static OUTSET_ROUTINE::PARAMETERS outset_params_fp_edit{
1378 pcbIUScale.mmToIU( 0.25 ), // A common outset value
1379 false,
1380 false,
1381 true,
1382 F_CrtYd,
1383 frame.GetDesignSettings().GetLineThickness( F_CrtYd ),
1384 pcbIUScale.mmToIU( 0.01 ),
1385 false,
1386 };
1387
1388 static OUTSET_ROUTINE::PARAMETERS outset_params_pcb_edit{
1389 pcbIUScale.mmToIU( 1 ),
1390 true,
1391 true,
1392 true,
1393 Edge_Cuts, // Outsets often for slots?
1394 frame.GetDesignSettings().GetLineThickness( Edge_Cuts ),
1395 std::nullopt,
1396 false,
1397 };
1398
1399 OUTSET_ROUTINE::PARAMETERS& outset_params = IsFootprintEditor() ? outset_params_fp_edit
1400 : outset_params_pcb_edit;
1401
1402 {
1403 DIALOG_OUTSET_ITEMS dlg( frame, outset_params );
1404
1405 if( dlg.ShowModal() == wxID_CANCEL )
1406 return 0;
1407 }
1408
1409 OUTSET_ROUTINE outset_routine( frame.GetModel(), change_handler, outset_params );
1410
1411 for( EDA_ITEM* item : selection )
1412 {
1413 if( !item->IsBOARD_ITEM() )
1414 continue;
1415
1416 BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
1417 outset_routine.ProcessItem( *board_item );
1418 }
1419
1420 // Deselect all the original items
1421 m_selectionTool->ClearSelection();
1422
1423 // Select added and modified items
1424 for( BOARD_ITEM* item : items_to_select_on_success )
1425 m_selectionTool->AddItemToSel( item, true );
1426
1427 if( any_items_created )
1428 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1429
1430 // Notify other tools of the changes
1432
1433 commit.Push( outset_routine.GetCommitDescription() );
1434
1435 if( const std::optional<wxString> msg = outset_routine.GetStatusMessage() )
1436 frame.ShowInfoBarMsg( *msg );
1437
1438 return 0;
1439}
1440
1441
@ 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 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:488
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)
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:344
const VECTOR2I & GetMid() const
Definition pcb_track.h:345
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.
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
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition pcb_shape.h:71
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:148
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:151
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:152
const VECTOR2I & GetEnd() const
Definition pcb_track.h:149
virtual void SetWidth(int aWidth)
Definition pcb_track.h:145
virtual int GetWidth() const
Definition pcb_track.h:146
Definition seg.h:42
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.
SHAPE_ARC Reversed() const
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...
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.
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.
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
Definition edit_tool.cpp:80
static const std::vector< KICAD_T > trackTypes
Definition edit_tool.cpp:86
@ 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:707
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:674
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.
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:107
@ 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_SHAPE_LOCATE_CIRCLE_T
Definition typeinfo.h:137
@ PCB_SHAPE_LOCATE_SEGMENT_T
Definition typeinfo.h:135
@ PCB_SHAPE_LOCATE_RECT_T
Definition typeinfo.h:136
@ PCB_SHAPE_LOCATE_BEZIER_T
Definition typeinfo.h:140
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
@ PCB_SHAPE_LOCATE_POLY_T
Definition typeinfo.h:139
@ PCB_SHAPE_LOCATE_ARC_T
Definition typeinfo.h:138
@ 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