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
25#include <bitmaps.h>
26#include <dialog_shim.h>
27#include <wx/statline.h>
28#include <wx/checkbox.h>
29#include <wx/button.h>
30#include <wx/radiobut.h>
31#include <widgets/unit_binder.h>
32#include <board.h>
33#include <board_commit.h>
35#include <collectors.h>
36#include <confirm.h>
39#include <footprint.h>
42#include <pcb_edit_frame.h>
43#include <pcb_shape.h>
44#include <pcb_track.h>
45#include <pad.h>
46#include <tool/tool_manager.h>
47#include <tools/edit_tool.h>
48#include <tools/pcb_actions.h>
51#include <trigo.h>
52#include <macros.h>
53#include <zone.h>
54
55#include <ranges>
56
57#include "convert_tool.h"
58
59
61{
62public:
64 bool aShowCopyLineWidthOption, bool aShowCenterlineOption,
65 bool aShowBoundingHullOption ) :
66 DIALOG_SHIM( aParent, wxID_ANY, _( "Conversion Settings" ), wxDefaultPosition,
67 wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
68 m_settings( aSettings )
69 {
70 // Allow each distinct version of the dialog to have a different size
71 m_hash_key = TO_UTF8( wxString::Format( wxS( "%s%c%c%c" ),
72 GetTitle(),
73 aShowCopyLineWidthOption,
74 aShowCenterlineOption,
75 aShowBoundingHullOption ) );
76
77 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
78 wxBoxSizer* topSizer = new wxBoxSizer( wxVERTICAL );
79 SetSizer( mainSizer );
80
81 m_rbMimicLineWidth = new wxRadioButton( this, wxID_ANY, _( "Copy line width of first object" ) );
82
83 if( aShowCopyLineWidthOption )
84 topSizer->Add( m_rbMimicLineWidth, 0, wxLEFT|wxRIGHT, 5 );
85 else
86 m_rbMimicLineWidth->Hide();
87
88 m_rbCenterline = new wxRadioButton( this, wxID_ANY, _( "Use centerlines" ) );
89
90 if( aShowCenterlineOption )
91 {
92 topSizer->AddSpacer( 6 );
93 topSizer->Add( m_rbCenterline, 0, wxLEFT|wxRIGHT, 5 );
94 }
95 else
96 {
97 m_rbCenterline->Hide();
98 }
99
100 m_rbBoundingHull = new wxRadioButton( this, wxID_ANY, _( "Create bounding hull" ) );
101
102 m_gapLabel = new wxStaticText( this, wxID_ANY, _( "Gap:" ) );
103 m_gapCtrl = new wxTextCtrl( this, wxID_ANY );
104 m_gapUnits = new wxStaticText( this, wxID_ANY, _( "mm" ) );
105 m_gap = new UNIT_BINDER( aParent, m_gapLabel, m_gapCtrl, m_gapUnits );
106
107 m_widthLabel = new wxStaticText( this, wxID_ANY, _( "Line width:" ) );
108 m_widthCtrl = new wxTextCtrl( this, wxID_ANY );
109 m_widthUnits = new wxStaticText( this, wxID_ANY, _( "mm" ) );
111
112 if( aShowBoundingHullOption )
113 {
114 topSizer->AddSpacer( 6 );
115 topSizer->Add( m_rbBoundingHull, 0, wxLEFT|wxRIGHT, 5 );
116
117 wxBoxSizer* hullParamsSizer = new wxBoxSizer( wxHORIZONTAL );
118 hullParamsSizer->Add( m_gapLabel, 0, wxALIGN_CENTRE_VERTICAL, 5 );
119 hullParamsSizer->Add( m_gapCtrl, 1, wxALIGN_CENTRE_VERTICAL|wxLEFT|wxRIGHT, 3 );
120 hullParamsSizer->Add( m_gapUnits, 0, wxALIGN_CENTRE_VERTICAL, 5 );
121 hullParamsSizer->AddSpacer( 18 );
122 hullParamsSizer->Add( m_widthLabel, 0, wxALIGN_CENTRE_VERTICAL, 5 );
123 hullParamsSizer->Add( m_widthCtrl, 1, wxALIGN_CENTRE_VERTICAL|wxLEFT|wxRIGHT, 3 );
124 hullParamsSizer->Add( m_widthUnits, 0, wxALIGN_CENTRE_VERTICAL, 5 );
125
126 topSizer->AddSpacer( 2 );
127 topSizer->Add( hullParamsSizer, 0, wxLEFT, 26 );
128
129 topSizer->AddSpacer( 15 );
130 }
131 else
132 {
133 m_rbBoundingHull->Hide();
134 m_gap->Show( false, true );
135 m_width->Show( false, true );
136 }
137
138 m_cbDeleteOriginals = new wxCheckBox( this, wxID_ANY, _( "Delete source objects after conversion" ) );
139 topSizer->Add( m_cbDeleteOriginals, 0, wxALL, 5 );
140
141 mainSizer->Add( topSizer, 1, wxALL|wxEXPAND, 10 );
142
143 wxBoxSizer* buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
144 buttonsSizer->AddStretchSpacer();
145
146 wxStdDialogButtonSizer* sdbSizer = new wxStdDialogButtonSizer();
147 wxButton* sdbSizerOK = new wxButton( this, wxID_OK );
148 sdbSizer->AddButton( sdbSizerOK );
149 wxButton* sdbSizerCancel = new wxButton( this, wxID_CANCEL );
150 sdbSizer->AddButton( sdbSizerCancel );
151 sdbSizer->Realize();
152
153 buttonsSizer->Add( sdbSizer, 1, 0, 5 );
154 mainSizer->Add( buttonsSizer, 0, wxALL|wxEXPAND, 5 );
155
157
158 m_rbMimicLineWidth->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
159 wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
160 nullptr, this );
161 m_rbCenterline->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
162 wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
163 nullptr, this );
164 m_rbBoundingHull->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED,
165 wxCommandEventHandler( CONVERT_SETTINGS_DIALOG::onRadioButton ),
166 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() );
191
193 return true;
194 }
195
197 {
198 if( m_rbBoundingHull->GetValue() )
200 else if( m_rbCenterline->GetValue() )
202 else
204
207
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{
259 m_frame = getEditFrame<PCB_BASE_FRAME>();
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->SetTitle( _( "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,
276 PCB_TEXT_T };
277 static const std::vector<KICAD_T> trackTypes = { PCB_TRACE_T,
278 PCB_ARC_T,
279 PCB_VIA_T };
280 static const std::vector<KICAD_T> toTrackTypes = { PCB_SHAPE_LOCATE_SEGMENT_T,
282 static const std::vector<KICAD_T> polyTypes = { PCB_ZONE_T,
285 static const std::vector<KICAD_T> outsetTypes = { PCB_PAD_T, PCB_SHAPE_T };
286
287 auto shapes = S_C::OnlyTypes( shapeTypes ) && P_S_C::SameLayer();
288 auto graphicToTrack = S_C::OnlyTypes( toTrackTypes );
289 auto anyTracks = S_C::MoreThan( 0 ) && S_C::OnlyTypes( trackTypes ) && P_S_C::SameLayer();
290 auto anyPolys = S_C::OnlyTypes( polyTypes );
291 auto anyPads = S_C::OnlyTypes( padTypes );
292
293 auto canCreateArcs = S_C::Count( 1 ) && S_C::OnlyTypes( toArcTypes );
294 auto canCreateArray = S_C::MoreThan( 0 );
295 auto canCreatePoly = shapes || anyPolys || anyTracks;
296
297 auto canCreateOutset = S_C::OnlyTypes( outsetTypes );
298
300 canCreatePoly = shapes || anyPolys || anyTracks || anyPads;
301
302 auto canCreateLines = anyPolys;
303 auto canCreateTracks = anyPolys || graphicToTrack;
304 auto canCreate = canCreatePoly
305 || canCreateLines
306 || canCreateTracks
307 || canCreateArcs
308 || canCreateArray
309 || canCreateOutset;
310
311 m_menu->AddItem( PCB_ACTIONS::convertToPoly, canCreatePoly );
312
314 m_menu->AddItem( PCB_ACTIONS::convertToZone, canCreatePoly );
315
317 m_menu->AddItem( PCB_ACTIONS::convertToLines, canCreateLines );
318 m_menu->AddItem( PCB_ACTIONS::outsetItems, canCreateOutset );
320
321 // Currently the code exists, but tracks are not really existing in footprints
322 // only segments on copper layers
324 m_menu->AddItem( PCB_ACTIONS::convertToTracks, canCreateTracks );
325
326 m_menu->AddItem( PCB_ACTIONS::convertToArc, canCreateArcs );
327
329 m_menu->AddItem( PCB_ACTIONS::createArray, canCreateArray );
330
332 selToolMenu.AddMenu( m_menu, canCreate, 100 );
333
334 return true;
335}
336
337
339{
344}
345
346
348{
350 std::vector<SHAPE_POLY_SET> polys;
351 PCB_LAYER_ID destLayer = m_frame->GetActiveLayer();
352 FOOTPRINT* parentFootprint = nullptr;
353
355 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
356 {
357 } );
358
359 if( selection.Empty() )
360 return 0;
361
362 auto getPolys =
363 [&]( CONVERT_SETTINGS cfg )
364 {
365 polys.clear();
366
367 for( EDA_ITEM* item : selection )
368 item->ClearTempFlags();
369
370 SHAPE_POLY_SET polySet;
371
372 polySet.Append( makePolysFromClosedGraphics( selection.GetItems(), cfg.m_Strategy ) );
373
374 if( cfg.m_Strategy == BOUNDING_HULL )
375 {
377
378 polySet.ClearArcs();
379 polySet.Simplify();
380
381 // Now inflate the bounding hull by cfg.m_Gap
382 polySet.Inflate( cfg.m_Gap, CORNER_STRATEGY::ROUND_ALL_CORNERS, bds.m_MaxError,
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
412 parentFootprint = static_cast<BOARD_ITEM*>( selection.Front() )->GetParentFootprint();
413
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 {
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,
500 COLOR4D::UNSPECIFIED ) );
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
511 PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
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
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, &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
561 {
562 PCB_SELECTION selectionCopy = selection;
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 {
575 commit.Push( _( "Convert to Polygon" ) );
576 else
577 commit.Push( _( "Create Polygon" ) );
578 }
579 else
580 {
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_PAD_T:
902 {
903 PAD* pad = static_cast<PAD*>( item );
904
905 pad->Padstack().ForEachUniqueLayer(
906 [&]( PCB_LAYER_ID aLayer )
907 {
908 SHAPE_POLY_SET layerPoly;
909 pad->TransformShapeToPolygon( layerPoly, aLayer, 0, pad->GetMaxError(), ERROR_INSIDE );
910 poly.BooleanAdd( layerPoly );
911 } );
912
913 pad->SetFlags( SKIP_STRUCT );
914 break;
915 }
916
917
918 default:
919 continue;
920 }
921 }
922
923 return poly;
924}
925
926
928{
930 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
931 {
932 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
933 {
934 BOARD_ITEM* item = aCollector[i];
935
936 switch( item->Type() )
937 {
938 case PCB_SHAPE_T:
939 switch( static_cast<PCB_SHAPE*>( item )->GetShape() )
940 {
941 case SHAPE_T::SEGMENT:
942 case SHAPE_T::ARC:
943 case SHAPE_T::POLY:
944 case SHAPE_T::RECTANGLE:
945 break;
946
947 default:
948 aCollector.Remove( item );
949 }
950
951 break;
952
953 case PCB_ZONE_T:
954 break;
955
956 default:
957 aCollector.Remove( item );
958 }
959 }
960 } );
961
962 if( selection.Empty() )
963 return 0;
964
965 auto getPolySet =
966 []( EDA_ITEM* aItem )
967 {
968 SHAPE_POLY_SET set;
969
970 switch( aItem->Type() )
971 {
972 case PCB_ZONE_T:
973 set = *static_cast<ZONE*>( aItem )->Outline();
974 break;
975
976 case PCB_SHAPE_T:
977 {
978 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
979
980 if( graphic->GetShape() == SHAPE_T::POLY )
981 {
982 set = graphic->GetPolyShape();
983 }
984 else if( graphic->GetShape() == SHAPE_T::RECTANGLE )
985 {
986 SHAPE_LINE_CHAIN outline;
987 VECTOR2I start( graphic->GetStart() );
988 VECTOR2I end( graphic->GetEnd() );
989
990 outline.Append( start );
991 outline.Append( VECTOR2I( end.x, start.y ) );
992 outline.Append( end );
993 outline.Append( VECTOR2I( start.x, end.y ) );
994 outline.SetClosed( true );
995
996 set.AddOutline( outline );
997 }
998 else
999 {
1000 wxFAIL_MSG( wxT( "Unhandled graphic shape type in PolyToLines - getPolySet" ) );
1001 }
1002 break;
1003 }
1004
1005 default:
1006 wxFAIL_MSG( wxT( "Unhandled type in PolyToLines - getPolySet" ) );
1007 break;
1008 }
1009
1010 return set;
1011 };
1012
1013 auto getSegList =
1014 []( SHAPE_POLY_SET& aPoly )
1015 {
1016 std::vector<SEG> segs;
1017
1018 // Our input should be valid polys, so OK to assert here
1019 wxASSERT( aPoly.VertexCount() >= 2 );
1020
1021 for( int i = 1; i < aPoly.VertexCount(); i++ )
1022 segs.emplace_back( SEG( aPoly.CVertex( i - 1 ), aPoly.CVertex( i ) ) );
1023
1024 segs.emplace_back( SEG( aPoly.CVertex( aPoly.VertexCount() - 1 ), aPoly.CVertex( 0 ) ) );
1025
1026 return segs;
1027 };
1028
1029 BOARD_COMMIT commit( m_frame );
1030 PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
1031 FOOTPRINT_EDIT_FRAME* fpEditor = dynamic_cast<FOOTPRINT_EDIT_FRAME*>( m_frame );
1032 FOOTPRINT* footprint = nullptr;
1033 PCB_LAYER_ID targetLayer = m_frame->GetActiveLayer();
1034 BOARD_ITEM_CONTAINER* parent = frame->GetModel();
1035
1036 if( fpEditor )
1037 footprint = fpEditor->GetBoard()->GetFirstFootprint();
1038
1039 auto handleGraphicSeg =
1040 [&]( EDA_ITEM* aItem )
1041 {
1042 if( aItem->Type() != PCB_SHAPE_T )
1043 return false;
1044
1045 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
1046
1047 if( graphic->GetShape() == SHAPE_T::SEGMENT )
1048 {
1049 PCB_TRACK* track = new PCB_TRACK( parent );
1050
1051 track->SetLayer( targetLayer );
1052 track->SetStart( graphic->GetStart() );
1053 track->SetEnd( graphic->GetEnd() );
1054 track->SetWidth( graphic->GetWidth() );
1055 commit.Add( track );
1056
1057 return true;
1058 }
1059 else if( graphic->GetShape() == SHAPE_T::ARC )
1060 {
1061 PCB_ARC* arc = new PCB_ARC( parent );
1062
1063 arc->SetLayer( targetLayer );
1064 arc->SetStart( graphic->GetStart() );
1065 arc->SetEnd( graphic->GetEnd() );
1066 arc->SetMid( graphic->GetArcMid() );
1067 arc->SetWidth( graphic->GetWidth() );
1068 commit.Add( arc );
1069
1070 return true;
1071 }
1072
1073 return false;
1074 };
1075
1076 if( aEvent.IsAction( &PCB_ACTIONS::convertToTracks ) )
1077 {
1078 if( !IsCopperLayer( targetLayer ) )
1079 {
1080 targetLayer = frame->SelectOneLayer( F_Cu, LSET::AllNonCuMask() );
1081
1082 if( targetLayer == UNDEFINED_LAYER ) // User canceled
1083 return true;
1084 }
1085 }
1086 else
1087 {
1088 CONVERT_SETTINGS_DIALOG dlg( m_frame, &m_userSettings, false, false, false );
1089
1090 if( dlg.ShowModal() != wxID_OK )
1091 return true;
1092 }
1093
1094 for( EDA_ITEM* item : selection )
1095 {
1096 if( !item->IsBOARD_ITEM() )
1097 continue;
1098
1099 if( handleGraphicSeg( item ) )
1100 continue;
1101
1102 BOARD_ITEM& boardItem = static_cast<BOARD_ITEM&>( *item );
1103 SHAPE_POLY_SET polySet = getPolySet( item );
1104 std::vector<SEG> segs = getSegList( polySet );
1105
1106 std::optional<int> itemWidth = GetBoardItemWidth( boardItem );
1107
1108 if( aEvent.IsAction( &PCB_ACTIONS::convertToLines ) )
1109 {
1110 for( SEG& seg : segs )
1111 {
1112 PCB_SHAPE* graphic = new PCB_SHAPE( footprint, SHAPE_T::SEGMENT );
1113
1114 graphic->SetLayer( targetLayer );
1115 graphic->SetStart( VECTOR2I( seg.A ) );
1116 graphic->SetEnd( VECTOR2I( seg.B ) );
1117
1118 // The width can exist but be 0 for filled, unstroked shapes
1119 if( itemWidth && *itemWidth > 0 )
1120 graphic->SetWidth( *itemWidth );
1121
1122 commit.Add( graphic );
1123 }
1124 }
1125 else
1126 {
1127 // I am really unsure converting a polygon to "tracks" (i.e. segments on
1128 // copper layers) make sense for footprints, but anyway this code exists
1129 if( fpEditor )
1130 {
1131 // Creating segments on copper layer
1132 for( SEG& seg : segs )
1133 {
1134 PCB_SHAPE* graphic = new PCB_SHAPE( footprint, SHAPE_T::SEGMENT );
1135 graphic->SetLayer( targetLayer );
1136 graphic->SetStart( VECTOR2I( seg.A ) );
1137 graphic->SetEnd( VECTOR2I( seg.B ) );
1138
1139 if( itemWidth )
1140 graphic->SetWidth( *itemWidth );
1141
1142 commit.Add( graphic );
1143 }
1144 }
1145 else
1146 {
1147 // Creating tracks
1148 for( SEG& seg : segs )
1149 {
1150 PCB_TRACK* track = new PCB_TRACK( parent );
1151
1152 track->SetLayer( targetLayer );
1153 track->SetStart( VECTOR2I( seg.A ) );
1154 track->SetEnd( VECTOR2I( seg.B ) );
1155 commit.Add( track );
1156 }
1157 }
1158 }
1159 }
1160
1161 if( m_userSettings.m_DeleteOriginals )
1162 {
1163 PCB_SELECTION selectionCopy = selection;
1164 m_selectionTool->ClearSelection();
1165
1166 for( EDA_ITEM* item : selectionCopy )
1167 commit.Remove( item );
1168 }
1169
1170 commit.Push( _( "Create Lines" ) );
1171
1172 return 0;
1173}
1174
1175
1177{
1179 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1180 {
1181 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1182 {
1183 BOARD_ITEM* item = aCollector[i];
1184
1185 if( !item->IsType( { PCB_SHAPE_T, PCB_TRACE_T, PCB_ARC_T } ) )
1186 aCollector.Remove( item );
1187 }
1188 } );
1189
1190 if( selection.Empty() || !selection.Front()->IsBOARD_ITEM() )
1191 return -1;
1192
1193 BOARD_ITEM* source = static_cast<BOARD_ITEM*>( selection.Front() );
1194 VECTOR2I start, end, mid;
1195
1196 // Offset the midpoint along the normal a little bit so that it's more obviously an arc
1197 const double offsetRatio = 0.1;
1198
1199 if( std::optional<SEG> seg = getStartEndPoints( source ) )
1200 {
1201 start = seg->A;
1202 end = seg->B;
1203
1204 VECTOR2I normal = ( seg->B - seg->A ).Perpendicular().Resize( offsetRatio * seg->Length() );
1205 mid = seg->Center() + normal;
1206 }
1207 else
1208 {
1209 return -1;
1210 }
1211
1212 PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
1213 BOARD_ITEM_CONTAINER* parent = frame->GetModel();
1214 PCB_LAYER_ID layer = source->GetLayer();
1215 BOARD_COMMIT commit( m_frame );
1216
1217 if( source->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( source )->GetShape() == SHAPE_T::SEGMENT )
1218 {
1219 PCB_SHAPE* line = static_cast<PCB_SHAPE*>( source );
1220 PCB_SHAPE* arc = new PCB_SHAPE( parent, SHAPE_T::ARC );
1221
1222 VECTOR2I center = CalcArcCenter( start, mid, end );
1223
1224 arc->SetFilled( false );
1225 arc->SetLayer( layer );
1226 arc->SetStroke( line->GetStroke() );
1227
1228 arc->SetCenter( center );
1229 arc->SetStart( start );
1230 arc->SetEnd( end );
1231
1232 commit.Add( arc );
1233 }
1234 else if( source->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( source )->GetShape() == SHAPE_T::ARC )
1235 {
1236 PCB_SHAPE* source_arc = static_cast<PCB_SHAPE*>( source );
1237 PCB_ARC* arc = new PCB_ARC( parent );
1238
1239 arc->SetLayer( layer );
1240 arc->SetWidth( source_arc->GetWidth() );
1241 arc->SetStart( start );
1242 arc->SetMid( source_arc->GetArcMid() );
1243 arc->SetEnd( end );
1244
1245 commit.Add( arc );
1246 }
1247 else if( source->Type() == PCB_TRACE_T )
1248 {
1249 PCB_TRACK* line = static_cast<PCB_TRACK*>( source );
1250 PCB_ARC* arc = new PCB_ARC( parent );
1251
1252 arc->SetLayer( layer );
1253 arc->SetWidth( line->GetWidth() );
1254 arc->SetStart( start );
1255 arc->SetMid( mid );
1256 arc->SetEnd( end );
1257
1258 commit.Add( arc );
1259 }
1260 else if( source->Type() == PCB_ARC_T )
1261 {
1262 PCB_ARC* source_arc = static_cast<PCB_ARC*>( source );
1263 PCB_SHAPE* arc = new PCB_SHAPE( parent, SHAPE_T::ARC );
1264
1265 arc->SetFilled( false );
1266 arc->SetLayer( layer );
1267 arc->SetWidth( source_arc->GetWidth() );
1268
1269 arc->SetArcGeometry( source_arc->GetStart(), source_arc->GetMid(), source_arc->GetEnd() );
1270 commit.Add( arc );
1271 }
1272
1273 commit.Push( _( "Create Arc" ) );
1274
1275 return 0;
1276}
1277
1278
1279std::optional<SEG> CONVERT_TOOL::getStartEndPoints( EDA_ITEM* aItem )
1280{
1281 switch( aItem->Type() )
1282 {
1283 case PCB_SHAPE_T:
1284 {
1285 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
1286
1287 switch( shape->GetShape() )
1288 {
1289 case SHAPE_T::SEGMENT:
1290 case SHAPE_T::ARC:
1291 case SHAPE_T::POLY:
1292 case SHAPE_T::BEZIER:
1293 if( shape->GetStart() == shape->GetEnd() )
1294 return std::nullopt;
1295
1296 return std::make_optional<SEG>( VECTOR2I( shape->GetStart() ), VECTOR2I( shape->GetEnd() ) );
1297
1298 default:
1299 return std::nullopt;
1300 }
1301 }
1302
1303 case PCB_TRACE_T:
1304 {
1305 PCB_TRACK* line = static_cast<PCB_TRACK*>( aItem );
1306 return std::make_optional<SEG>( VECTOR2I( line->GetStart() ), VECTOR2I( line->GetEnd() ) );
1307 }
1308
1309 case PCB_ARC_T:
1310 {
1311 PCB_ARC* arc = static_cast<PCB_ARC*>( aItem );
1312 return std::make_optional<SEG>( VECTOR2I( arc->GetStart() ), VECTOR2I( arc->GetEnd() ) );
1313 }
1314
1315 default:
1316 return std::nullopt;
1317 }
1318}
1319
1320
1322{
1323 PCB_BASE_EDIT_FRAME& frame = *getEditFrame<PCB_BASE_EDIT_FRAME>();
1325 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1326 {
1327 // Iterate from the back so we don't have to worry about removals.
1328 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1329 {
1330 BOARD_ITEM* item = aCollector[i];
1331
1332 // We've converted the polygon and rectangle to segments, so drop everything
1333 // that isn't a segment at this point
1334 if( !item->IsType( { PCB_PAD_T, PCB_SHAPE_T } ) )
1335 aCollector.Remove( item );
1336 }
1337 },
1338 true /* prompt user regarding locked items */ );
1339
1340 BOARD_COMMIT commit( this );
1341
1342 for( EDA_ITEM* item : selection )
1343 item->ClearFlags( STRUCT_DELETED );
1344
1345 // List of thing to select at the end of the operation
1346 // (doing it as we go will invalidate the iterator)
1347 std::vector<BOARD_ITEM*> items_to_select_on_success;
1348
1349 // Handle modifications to existing items by the routine
1350 // How to deal with this depends on whether we're in the footprint editor or not
1351 // and whether the item was conjured up by decomposing a polygon or rectangle
1352 auto item_modification_handler =
1353 [&]( BOARD_ITEM& aItem )
1354 {
1355 };
1356
1357 bool any_items_created = false;
1358
1359 auto item_creation_handler =
1360 [&]( std::unique_ptr<BOARD_ITEM> aItem )
1361 {
1362 any_items_created = true;
1363 items_to_select_on_success.push_back( aItem.get() );
1364 commit.Add( aItem.release() );
1365 };
1366
1367 auto item_removal_handler =
1368 [&]( BOARD_ITEM& aItem )
1369 {
1370 // If you do an outset on a FP pad, do you really want to delete
1371 // the parent?
1372 if( !aItem.GetParentFootprint() )
1373 {
1374 commit.Remove( &aItem );
1375 }
1376 };
1377
1378 // Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
1379 ITEM_MODIFICATION_ROUTINE::CALLABLE_BASED_HANDLER change_handler( item_creation_handler,
1380 item_modification_handler,
1381 item_removal_handler );
1382
1383 // Persistent settings between dialog invocations
1384 // Init with some sensible defaults
1385 static OUTSET_ROUTINE::PARAMETERS outset_params_fp_edit{
1386 pcbIUScale.mmToIU( 0.25 ), // A common outset value
1387 false,
1388 false,
1389 true,
1390 F_CrtYd,
1392 pcbIUScale.mmToIU( 0.01 ),
1393 false,
1394 };
1395
1396 static OUTSET_ROUTINE::PARAMETERS outset_params_pcb_edit{
1397 pcbIUScale.mmToIU( 1 ),
1398 true,
1399 true,
1400 true,
1401 Edge_Cuts, // Outsets often for slots?
1403 std::nullopt,
1404 false,
1405 };
1406
1407 OUTSET_ROUTINE::PARAMETERS& outset_params = IsFootprintEditor() ? outset_params_fp_edit
1408 : outset_params_pcb_edit;
1409
1410 {
1411 DIALOG_OUTSET_ITEMS dlg( frame, outset_params );
1412
1413 if( dlg.ShowModal() == wxID_CANCEL )
1414 return 0;
1415 }
1416
1417 OUTSET_ROUTINE outset_routine( frame.GetModel(), change_handler, outset_params );
1418
1419 for( EDA_ITEM* item : selection )
1420 {
1421 if( !item->IsBOARD_ITEM() )
1422 continue;
1423
1424 BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
1425 outset_routine.ProcessItem( *board_item );
1426 }
1427
1428 // Deselect all the original items
1429 m_selectionTool->ClearSelection();
1430
1431 // Select added and modified items
1432 for( BOARD_ITEM* item : items_to_select_on_success )
1433 m_selectionTool->AddItemToSel( item, true );
1434
1435 if( any_items_created )
1436 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1437
1438 // Notify other tools of the changes
1439 m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1440
1441 commit.Push( outset_routine.GetCommitDescription() );
1442
1443 if( const std::optional<wxString> msg = outset_routine.GetStatusMessage() )
1444 frame.ShowInfoBarMsg( *msg );
1445
1446 return 0;
1447}
1448
1449
1451{
1452 // clang-format off
1460 // clang-format on
1461}
@ ERROR_OUTSIDE
Definition: approximation.h:33
@ ERROR_INSIDE
Definition: approximation.h:34
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:112
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition: box2.h:990
void SetTitle(const wxString &aTitle) override
Set title for the menu.
Definition: action_menu.cpp:92
void SetIcon(BITMAPS aIcon)
Assign an icon for the entry.
Definition: action_menu.cpp:78
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]
int GetLineThickness(PCB_LAYER_ID aLayer) const
Return the default graphic segment thickness from the layer class for the given layer.
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
Definition: board_item.cpp:118
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition: board_item.h:280
virtual bool HasLineStroke() const
Check if this item has line stoke properties.
Definition: board_item.h:222
int GetMaxError() const
Definition: board_item.cpp:138
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition: board.h:489
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:1024
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
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition: commit.h:91
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition: commit.h:79
void AddItem(const TOOL_ACTION &aAction, const SELECTION_CONDITION &aCondition, int aOrder=ANY_ORDER)
Add a menu entry to run a TOOL_ACTION on selected items.
void AddSeparator(int aOrder=ANY_ORDER)
Add a separator to the menu.
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
wxStaticText * m_gapLabel
wxRadioButton * m_rbMimicLineWidth
wxRadioButton * m_rbCenterline
void onRadioButton(wxCommandEvent &aEvent)
wxStaticText * m_gapUnits
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
Definition: convert_tool.h:110
virtual ~CONVERT_TOOL()
CONVERT_SETTINGS m_userSettings
Definition: convert_tool.h:113
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
CONDITIONAL_MENU * m_menu
Definition: convert_tool.h:111
PCB_BASE_FRAME * m_frame
Definition: convert_tool.h:112
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.
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition: dialog_shim.h:61
void SetupStandardButtons(std::map< int, wxString > aLabels={})
std::string m_hash_key
Definition: dialog_shim.h:236
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
int ShowModal() override
void ShowInfoBarMsg(const wxString &aMsg, bool aShowCloseButton=false)
Show the WX_INFOBAR displayed on the top of the canvas with a message and an info icon on the left of...
bool IsType(FRAME_T aType) const
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)
Definition: eda_shape.cpp:956
FILL_T GetFillMode() const
Definition: eda_shape.h:142
SHAPE_POLY_SET & GetPolyShape()
Definition: eda_shape.h:337
int GetRadius() const
Definition: eda_shape.cpp:1005
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
Definition: eda_shape.cpp:509
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.
Definition: eda_shape.cpp:1041
void SetWidth(int aWidth)
Definition: eda_shape.cpp:2375
void SetFillMode(FILL_T aFill)
Definition: eda_shape.cpp:540
VECTOR2I GetArcMid() const
Definition: eda_shape.cpp:975
static const TOOL_EVENT SelectedEvent
Definition: actions.h:342
static const TOOL_EVENT SelectedItemsModified
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition: actions.h:349
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.
bool IsBOARD_ITEM() const
Definition: view_item.h:102
static LSET AllNonCuMask()
Return a mask holding all layer minus CU layers.
Definition: lset.cpp:610
Definition: pad.h:54
static TOOL_ACTION convertToKeepout
Definition: pcb_actions.h:591
static TOOL_ACTION convertToTracks
Definition: pcb_actions.h:594
static TOOL_ACTION convertToLines
Definition: pcb_actions.h:592
static TOOL_ACTION convertToZone
Definition: pcb_actions.h:590
static TOOL_ACTION convertToPoly
Definition: pcb_actions.h:589
static TOOL_ACTION outsetItems
Create outset items from selection.
Definition: pcb_actions.h:158
static TOOL_ACTION convertToArc
Definition: pcb_actions.h:593
static TOOL_ACTION createArray
Tool for creating an array of objects.
Definition: pcb_actions.h:508
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.
Definition: pcb_track.cpp:2253
Common, abstract interface for edit frames.
virtual PCB_LAYER_ID GetActiveLayer() const
BOARD * GetBoard() const
virtual BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Return the BOARD_DESIGN_SETTINGS for the open project.
virtual BOARD_ITEM_CONTAINER * GetModel() const =0
PCB_LAYER_ID SelectOneLayer(PCB_LAYER_ID aDefaultLayer, const LSET &aNotAllowedLayersMask=LSET(), wxPoint aDlgPosition=wxDefaultPosition)
Show the dialog box for a layer selection.
Definition: sel_layer.cpp:301
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:
PCB_SELECTION & RequestSelection(CLIENT_SELECTION_FILTER aClientFilter, bool aConfirmLockedItems=false)
Return the current selection, filtered according to aClientFilter.
int ClearSelection(const TOOL_EVENT &aEvent)
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
Definition: pcb_shape.cpp:385
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition: pcb_shape.cpp:176
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.
Definition: pcb_shape.cpp:829
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
BOARD * board() const
const PCB_SELECTION & selection() 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.
Definition: pcb_track.cpp:2269
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.
const std::deque< EDA_ITEM * > GetItems() const
Definition: selection.h:126
virtual void Remove(EDA_ITEM *aItem)
Definition: selection.cpp:60
EDA_ITEM * Front() const
Definition: selection.h:177
bool Empty() const
Checks if there is anything selected.
Definition: selection.h:115
SHAPE_ARC Reversed() const
Definition: shape_arc.cpp:1019
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.
Definition: stroke_params.h:94
int GetWidth() const
TOOL_MANAGER * m_toolMgr
Definition: tool_base.h:220
Generic, UI-independent tool event.
Definition: tool_event.h:168
bool IsAction(const TOOL_ACTION *aAction) const
Test if the event contains an action issued upon activation of the given TOOL_ACTION.
Definition: tool_event.cpp:82
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).
TOOL_MENU & GetToolMenu()
CONDITIONAL_MENU & GetMenu()
Definition: tool_menu.cpp:44
int GetIntValue()
Definition: unit_binder.h:134
void Enable(bool aEnable)
Enable/disable the label, widget and units label.
virtual void SetValue(long long int aValue)
Set new value (in Internal Units) for the text field, taking care of units conversion.
void Show(bool aShow, bool aResize=false)
Show/hide the label, widget and units label.
ZONE_SETTINGS handles zones parameters.
Definition: zone_settings.h:88
void SetIsRuleArea(bool aEnable)
void ExportSetting(ZONE &aTarget, bool aFullExport=true) const
Function ExportSetting copy settings to a given zone.
wxString m_Name
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.
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
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
FILL_T
Definition: eda_shape.h:56
static const std::vector< KICAD_T > padTypes
Definition: edit_tool.cpp:79
static const std::vector< KICAD_T > trackTypes
Definition: edit_tool.cpp:85
@ 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:698
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition: layer_ids.h:665
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:899
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:429
CONVERT_STRATEGY m_Strategy
constexpr int mmToIU(double mm) const
Definition: base_units.h:92
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:136
@ PCB_SHAPE_LOCATE_SEGMENT_T
Definition: typeinfo.h:134
@ PCB_SHAPE_LOCATE_RECT_T
Definition: typeinfo.h:135
@ PCB_SHAPE_LOCATE_BEZIER_T
Definition: typeinfo.h:139
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition: typeinfo.h:87
@ PCB_SHAPE_LOCATE_POLY_T
Definition: typeinfo.h:138
@ PCB_SHAPE_LOCATE_ARC_T
Definition: typeinfo.h:137
@ 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