ADDED: Polygon boolean operations in PCB editor
This allows common operations like merging a pin courtyard into the body courtyard in the fooprint editor, taking a "bite" out of a polygon and so on, For now, this only supports polygons made of straight lines. There are some wierd cases when the operations result in nothing (e.g. wen a big polygon is substracted from a smaller one that it contains entirely). I have tried to do something senisble in these cases, but there may be more optimal ways to handle it. Relates-To: https://gitlab.com/kicad/code/kicad/-/issues/13025
|
@ -351,6 +351,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
|||
aBitmapInfoCache[BITMAPS::import].emplace_back( BITMAPS::import, wxT( "import_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::info].emplace_back( BITMAPS::info, wxT( "info_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::insert_module_board].emplace_back( BITMAPS::insert_module_board, wxT( "insert_module_board_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::intersect_polygons].emplace_back( BITMAPS::intersect_polygons, wxT( "intersect_polygons_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::language].emplace_back( BITMAPS::language, wxT( "language_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::layers_manager].emplace_back( BITMAPS::layers_manager, wxT( "layers_manager_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::leave_sheet].emplace_back( BITMAPS::leave_sheet, wxT( "leave_sheet_24.png" ), 24, wxT( "light" ) );
|
||||
|
@ -375,7 +376,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
|||
aBitmapInfoCache[BITMAPS::marker_next].emplace_back( BITMAPS::marker_next, wxT( "marker_next_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::marker_previous].emplace_back( BITMAPS::marker_previous, wxT( "marker_previous_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::measurement].emplace_back( BITMAPS::measurement, wxT( "measurement_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::pcb_target].emplace_back( BITMAPS::pcb_target, wxT( "pcb_target_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::merge_polygons].emplace_back( BITMAPS::merge_polygons, wxT( "merge_polygons_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::mirror_h].emplace_back( BITMAPS::mirror_h, wxT( "mirror_h_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::mirror_v].emplace_back( BITMAPS::mirror_v, wxT( "mirror_v_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::mode_module].emplace_back( BITMAPS::mode_module, wxT( "mode_module_24.png" ), 24, wxT( "light" ) );
|
||||
|
@ -429,6 +430,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
|||
aBitmapInfoCache[BITMAPS::part_properties].emplace_back( BITMAPS::part_properties, wxT( "part_properties_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::paste].emplace_back( BITMAPS::paste, wxT( "paste_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::paste_special].emplace_back( BITMAPS::paste_special, wxT( "paste_special_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::pcb_target].emplace_back( BITMAPS::pcb_target, wxT( "pcb_target_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::pin2pin].emplace_back( BITMAPS::pin2pin, wxT( "pin2pin_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::pin_size_to].emplace_back( BITMAPS::pin_size_to, wxT( "pin_size_to_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::pin_show_etype].emplace_back( BITMAPS::pin_show_etype, wxT( "pin_show_etype_24.png" ), 24, wxT( "light" ) );
|
||||
|
@ -520,6 +522,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
|||
aBitmapInfoCache[BITMAPS::show_back_assembly_layers].emplace_back( BITMAPS::show_back_assembly_layers, wxT( "show_back_assembly_layers_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::special_tools].emplace_back( BITMAPS::special_tools, wxT( "special_tools_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::spreadsheet].emplace_back( BITMAPS::spreadsheet, wxT( "spreadsheet_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::subtract_polygons].emplace_back( BITMAPS::subtract_polygons, wxT( "subtract_polygons_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::swap].emplace_back( BITMAPS::swap, wxT( "swap_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::swap_layer].emplace_back( BITMAPS::swap_layer, wxT( "swap_layer_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::switch_corner_rounding_shape].emplace_back( BITMAPS::switch_corner_rounding_shape, wxT( "switch_corner_rounding_shape_24.png" ), 24, wxT( "light" ) );
|
||||
|
@ -740,6 +743,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
|||
aBitmapInfoCache[BITMAPS::import].emplace_back( BITMAPS::import, wxT( "import_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::info].emplace_back( BITMAPS::info, wxT( "info_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::insert_module_board].emplace_back( BITMAPS::insert_module_board, wxT( "insert_module_board_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::intersect_polygons].emplace_back( BITMAPS::intersect_polygons, wxT( "intersect_polygons_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::language].emplace_back( BITMAPS::language, wxT( "language_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::layers_manager].emplace_back( BITMAPS::layers_manager, wxT( "layers_manager_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::leave_sheet].emplace_back( BITMAPS::leave_sheet, wxT( "leave_sheet_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
|
@ -764,7 +768,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
|||
aBitmapInfoCache[BITMAPS::marker_next].emplace_back( BITMAPS::marker_next, wxT( "marker_next_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::marker_previous].emplace_back( BITMAPS::marker_previous, wxT( "marker_previous_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::measurement].emplace_back( BITMAPS::measurement, wxT( "measurement_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::pcb_target].emplace_back( BITMAPS::pcb_target, wxT( "pcb_target_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::merge_polygons].emplace_back( BITMAPS::merge_polygons, wxT( "merge_polygons_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::mirror_h].emplace_back( BITMAPS::mirror_h, wxT( "mirror_h_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::mirror_v].emplace_back( BITMAPS::mirror_v, wxT( "mirror_v_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::mode_module].emplace_back( BITMAPS::mode_module, wxT( "mode_module_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
|
@ -818,6 +822,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
|||
aBitmapInfoCache[BITMAPS::part_properties].emplace_back( BITMAPS::part_properties, wxT( "part_properties_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::paste].emplace_back( BITMAPS::paste, wxT( "paste_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::paste_special].emplace_back( BITMAPS::paste_special, wxT( "paste_special_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::pcb_target].emplace_back( BITMAPS::pcb_target, wxT( "pcb_target_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::pin2pin].emplace_back( BITMAPS::pin2pin, wxT( "pin2pin_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::pin_size_to].emplace_back( BITMAPS::pin_size_to, wxT( "pin_size_to_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::pin_show_etype].emplace_back( BITMAPS::pin_show_etype, wxT( "pin_show_etype_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
|
@ -907,6 +912,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
|||
aBitmapInfoCache[BITMAPS::show_back_assembly_layers].emplace_back( BITMAPS::show_back_assembly_layers, wxT( "show_back_assembly_layers_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::special_tools].emplace_back( BITMAPS::special_tools, wxT( "special_tools_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::spreadsheet].emplace_back( BITMAPS::spreadsheet, wxT( "spreadsheet_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::subtract_polygons].emplace_back( BITMAPS::subtract_polygons, wxT( "subtract_polygons_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::swap].emplace_back( BITMAPS::swap, wxT( "swap_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::swap_layer].emplace_back( BITMAPS::swap_layer, wxT( "swap_layer_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::switch_corner_rounding_shape].emplace_back( BITMAPS::switch_corner_rounding_shape, wxT( "switch_corner_rounding_shape_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
|
|
|
@ -297,6 +297,7 @@ enum class BITMAPS : unsigned int
|
|||
import_vector,
|
||||
info,
|
||||
insert_module_board,
|
||||
intersect_polygons,
|
||||
invisible_text,
|
||||
kicad_icon_small,
|
||||
label_align_left,
|
||||
|
@ -328,6 +329,7 @@ enum class BITMAPS : unsigned int
|
|||
marker_next,
|
||||
marker_previous,
|
||||
measurement,
|
||||
merge_polygons,
|
||||
microstrip,
|
||||
microstrip_zodd_zeven,
|
||||
minus,
|
||||
|
@ -557,6 +559,7 @@ enum class BITMAPS : unsigned int
|
|||
stroke_dashdotdot,
|
||||
stroke_dot,
|
||||
stroke_solid,
|
||||
subtract_polygons,
|
||||
swap,
|
||||
swap_layer,
|
||||
switch_corner_rounding_shape,
|
||||
|
|
|
@ -116,11 +116,14 @@ static std::shared_ptr<CONDITIONAL_MENU> makeShapeModificationMenu( TOOL_INTERAC
|
|||
|
||||
menu->SetTitle( _( "Shape Modification" ) );
|
||||
|
||||
static std::vector<KICAD_T> filletChamferTypes = { PCB_SHAPE_LOCATE_POLY_T,
|
||||
PCB_SHAPE_LOCATE_RECT_T,
|
||||
PCB_SHAPE_LOCATE_SEGMENT_T };
|
||||
static const std::vector<KICAD_T> filletChamferTypes = { PCB_SHAPE_LOCATE_POLY_T,
|
||||
PCB_SHAPE_LOCATE_RECT_T,
|
||||
PCB_SHAPE_LOCATE_SEGMENT_T };
|
||||
|
||||
static std::vector<KICAD_T> lineExtendTypes = { PCB_SHAPE_LOCATE_SEGMENT_T };
|
||||
static const std::vector<KICAD_T> lineExtendTypes = { PCB_SHAPE_LOCATE_SEGMENT_T };
|
||||
|
||||
static const std::vector<KICAD_T> polygonBooleanTypes = { PCB_SHAPE_LOCATE_RECT_T,
|
||||
PCB_SHAPE_LOCATE_POLY_T };
|
||||
|
||||
auto hasCornerCondition =
|
||||
[aTool]( const SELECTION& aSelection )
|
||||
|
@ -145,6 +148,15 @@ static std::shared_ptr<CONDITIONAL_MENU> makeShapeModificationMenu( TOOL_INTERAC
|
|||
&& SELECTION_CONDITIONS::Count( 2 ) );
|
||||
menu->AddItem( PCB_ACTIONS::pointEditorMoveCorner, hasCornerCondition );
|
||||
menu->AddItem( PCB_ACTIONS::pointEditorMoveMidpoint, hasMidpointCondition );
|
||||
|
||||
menu->AddSeparator();
|
||||
|
||||
menu->AddItem( PCB_ACTIONS::mergePolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
|
||||
&& SELECTION_CONDITIONS::MoreThan( 1 ) );
|
||||
menu->AddItem( PCB_ACTIONS::subtractPolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
|
||||
&& SELECTION_CONDITIONS::MoreThan( 1 ) );
|
||||
menu->AddItem( PCB_ACTIONS::intersectPolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
|
||||
&& SELECTION_CONDITIONS::MoreThan( 1 ) );
|
||||
// clang-format on
|
||||
|
||||
return menu;
|
||||
|
@ -1394,6 +1406,117 @@ int EDIT_TOOL::ModifyLines( const TOOL_EVENT& aEvent )
|
|||
}
|
||||
|
||||
|
||||
int EDIT_TOOL::BooleanPolygons( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
PCB_SELECTION& selection = m_selectionTool->RequestSelection(
|
||||
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
|
||||
{
|
||||
// Iterate from the back so we don't have to worry about removals.
|
||||
for( int i = aCollector.GetCount() - 1; i >= 0; --i )
|
||||
{
|
||||
BOARD_ITEM* item = aCollector[i];
|
||||
|
||||
if( !item->IsType( {
|
||||
PCB_SHAPE_LOCATE_POLY_T,
|
||||
PCB_SHAPE_LOCATE_RECT_T,
|
||||
} ) )
|
||||
{
|
||||
aCollector.Remove( item );
|
||||
}
|
||||
}
|
||||
},
|
||||
true /* prompt user regarding locked items */ );
|
||||
|
||||
const EDA_ITEM* const last_item = selection.GetLastAddedItem();
|
||||
|
||||
// Gather or construct polygon source shapes to merge
|
||||
std::vector<PCB_SHAPE*> items_to_process;
|
||||
for( EDA_ITEM* item : selection )
|
||||
{
|
||||
items_to_process.push_back( static_cast<PCB_SHAPE*>( item ) );
|
||||
|
||||
// put the last one in the selection at the front of the vector
|
||||
// so it can be used as the property donor and as the basis for the
|
||||
// boolean operation
|
||||
if( item == last_item )
|
||||
{
|
||||
std::swap( items_to_process.back(), items_to_process.front() );
|
||||
}
|
||||
}
|
||||
|
||||
BOARD_COMMIT commit{ this };
|
||||
|
||||
// Handle modifications to existing items by the routine
|
||||
const auto item_modification_handler = [&]( PCB_SHAPE& aItem )
|
||||
{
|
||||
commit.Modify( &aItem );
|
||||
};
|
||||
|
||||
std::vector<PCB_SHAPE*> items_to_select_on_success;
|
||||
const auto item_creation_handler = [&]( std::unique_ptr<PCB_SHAPE> aItem )
|
||||
{
|
||||
items_to_select_on_success.push_back( aItem.get() );
|
||||
commit.Add( aItem.release() );
|
||||
};
|
||||
|
||||
const auto item_removal_handler = [&]( PCB_SHAPE& aItem )
|
||||
{
|
||||
commit.Remove( &aItem );
|
||||
};
|
||||
|
||||
// Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
|
||||
ITEM_MODIFICATION_ROUTINE::CALLABLE_BASED_HANDLER change_handler(
|
||||
item_creation_handler, item_modification_handler, item_removal_handler );
|
||||
|
||||
// Construct an appropriate routine
|
||||
std::unique_ptr<POLYGON_BOOLEAN_ROUTINE> boolean_routine;
|
||||
if( aEvent.IsAction( &PCB_ACTIONS::mergePolygons ) )
|
||||
{
|
||||
boolean_routine =
|
||||
std::make_unique<POLYGON_MERGE_ROUTINE>( frame()->GetModel(), change_handler );
|
||||
}
|
||||
else if( aEvent.IsAction( &PCB_ACTIONS::subtractPolygons ) )
|
||||
{
|
||||
boolean_routine =
|
||||
std::make_unique<POLYGON_SUBTRACT_ROUTINE>( frame()->GetModel(), change_handler );
|
||||
}
|
||||
else if( aEvent.IsAction( &PCB_ACTIONS::intersectPolygons ) )
|
||||
{
|
||||
boolean_routine =
|
||||
std::make_unique<POLYGON_INTERSECT_ROUTINE>( frame()->GetModel(), change_handler );
|
||||
}
|
||||
else
|
||||
{
|
||||
wxASSERT_MSG( false, "Could not find a polygon routine for this action" );
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Perform the operation on each polygon
|
||||
for( PCB_SHAPE* shape : items_to_process )
|
||||
{
|
||||
boolean_routine->ProcessShape( *shape );
|
||||
}
|
||||
|
||||
// Select new items
|
||||
for( PCB_SHAPE* item : items_to_select_on_success )
|
||||
{
|
||||
m_selectionTool->AddItemToSel( item, true );
|
||||
}
|
||||
|
||||
// Notify other tools of the changes
|
||||
m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
|
||||
|
||||
commit.Push( boolean_routine->GetCommitDescription() );
|
||||
|
||||
if( const std::optional<wxString> msg = boolean_routine->GetStatusMessage() )
|
||||
{
|
||||
frame()->ShowInfoBarMsg( *msg );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int EDIT_TOOL::Properties( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
|
||||
|
@ -2642,6 +2765,9 @@ void EDIT_TOOL::setTransitions()
|
|||
Go( &EDIT_TOOL::ModifyLines, PCB_ACTIONS::filletLines.MakeEvent() );
|
||||
Go( &EDIT_TOOL::ModifyLines, PCB_ACTIONS::chamferLines.MakeEvent() );
|
||||
Go( &EDIT_TOOL::ModifyLines, PCB_ACTIONS::extendLines.MakeEvent() );
|
||||
Go( &EDIT_TOOL::BooleanPolygons, PCB_ACTIONS::mergePolygons.MakeEvent() );
|
||||
Go( &EDIT_TOOL::BooleanPolygons, PCB_ACTIONS::subtractPolygons.MakeEvent() );
|
||||
Go( &EDIT_TOOL::BooleanPolygons, PCB_ACTIONS::intersectPolygons.MakeEvent() );
|
||||
|
||||
Go( &EDIT_TOOL::copyToClipboard, ACTIONS::copy.MakeEvent() );
|
||||
Go( &EDIT_TOOL::copyToClipboard, PCB_ACTIONS::copyWithReference.MakeEvent() );
|
||||
|
|
|
@ -126,6 +126,12 @@ public:
|
|||
*/
|
||||
int ModifyLines( const TOOL_EVENT& aEvent );
|
||||
|
||||
/**
|
||||
* Modify selected polygons into a single polygon using boolean operations
|
||||
* such as merge (union) or subtract (difference)
|
||||
*/
|
||||
int BooleanPolygons( const TOOL_EVENT& aEvent );
|
||||
|
||||
/**
|
||||
* Delete currently selected items.
|
||||
*/
|
||||
|
|
|
@ -301,3 +301,194 @@ void LINE_EXTENSION_ROUTINE::ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLin
|
|||
|
||||
AddSuccess();
|
||||
}
|
||||
|
||||
|
||||
void POLYGON_BOOLEAN_ROUTINE::ProcessShape( PCB_SHAPE& aPcbShape )
|
||||
{
|
||||
std::unique_ptr<SHAPE_POLY_SET> poly;
|
||||
|
||||
switch( aPcbShape.GetShape() )
|
||||
{
|
||||
case SHAPE_T::POLY:
|
||||
{
|
||||
poly = std::make_unique<SHAPE_POLY_SET>( aPcbShape.GetPolyShape() );
|
||||
break;
|
||||
}
|
||||
case SHAPE_T::RECTANGLE:
|
||||
{
|
||||
SHAPE_POLY_SET rect_poly;
|
||||
|
||||
const std::vector<VECTOR2I> rect_pts = aPcbShape.GetRectCorners();
|
||||
|
||||
rect_poly.NewOutline();
|
||||
|
||||
for( const VECTOR2I& pt : rect_pts )
|
||||
{
|
||||
rect_poly.Append( pt );
|
||||
}
|
||||
|
||||
poly = std::make_unique<SHAPE_POLY_SET>( std::move( rect_poly ) );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( !poly )
|
||||
{
|
||||
// Not a polygon or rectangle, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if( !m_workingPolygon )
|
||||
{
|
||||
auto initial = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::POLY );
|
||||
initial->SetPolyShape( *poly );
|
||||
|
||||
// Copy properties
|
||||
initial->SetLayer( aPcbShape.GetLayer() );
|
||||
initial->SetWidth( aPcbShape.GetWidth() );
|
||||
|
||||
// Keep the pointer
|
||||
m_workingPolygon = initial.get();
|
||||
// Hand over ownership
|
||||
GetHandler().AddNewItem( std::move( initial ) );
|
||||
|
||||
// And remove the shape
|
||||
GetHandler().DeleteItem( aPcbShape );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( ProcessSubsequentPolygon( *poly ) )
|
||||
{
|
||||
// If we could process the polygon, delete the source
|
||||
GetHandler().DeleteItem( aPcbShape );
|
||||
AddSuccess();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddFailure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
wxString POLYGON_MERGE_ROUTINE::GetCommitDescription() const
|
||||
{
|
||||
return _( "Merge polygons." );
|
||||
}
|
||||
|
||||
|
||||
std::optional<wxString> POLYGON_MERGE_ROUTINE::GetStatusMessage() const
|
||||
{
|
||||
if( GetSuccesses() == 0 )
|
||||
{
|
||||
return _( "Unable to merge the selected polygons." );
|
||||
}
|
||||
else if( GetFailures() > 0 )
|
||||
{
|
||||
return _( "Some of the polygons could not be merged." );
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
bool POLYGON_MERGE_ROUTINE::ProcessSubsequentPolygon( const SHAPE_POLY_SET& aPolygon )
|
||||
{
|
||||
const SHAPE_POLY_SET::POLYGON_MODE poly_mode = SHAPE_POLY_SET::POLYGON_MODE::PM_FAST;
|
||||
|
||||
SHAPE_POLY_SET working_copy = GetWorkingPolygon()->GetPolyShape();
|
||||
working_copy.BooleanAdd( aPolygon, poly_mode );
|
||||
|
||||
// Check it's not disjoint - this doesn't work well in the UI
|
||||
if( working_copy.OutlineCount() != 1 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GetWorkingPolygon()->SetPolyShape( working_copy );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
wxString POLYGON_SUBTRACT_ROUTINE::GetCommitDescription() const
|
||||
{
|
||||
return _( "Subtract polygons." );
|
||||
}
|
||||
|
||||
|
||||
std::optional<wxString> POLYGON_SUBTRACT_ROUTINE::GetStatusMessage() const
|
||||
{
|
||||
if( GetSuccesses() == 0 )
|
||||
{
|
||||
return _( "Unable to subtract the selected polygons." );
|
||||
}
|
||||
else if( GetFailures() > 0 )
|
||||
{
|
||||
return _( "Some of the polygons could not be subtracted." );
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
bool POLYGON_SUBTRACT_ROUTINE::ProcessSubsequentPolygon( const SHAPE_POLY_SET& aPolygon )
|
||||
{
|
||||
const SHAPE_POLY_SET::POLYGON_MODE poly_mode = SHAPE_POLY_SET::POLYGON_MODE::PM_FAST;
|
||||
|
||||
SHAPE_POLY_SET working_copy = GetWorkingPolygon()->GetPolyShape();
|
||||
working_copy.BooleanSubtract( aPolygon, poly_mode );
|
||||
|
||||
// Subtraction can create holes or delete the polygon
|
||||
// In theory we can allow holes as the EDA_SHAPE will fracture for us, but that's
|
||||
// probably not what the user has in mind (?)
|
||||
if( working_copy.OutlineCount() != 1 || working_copy.HoleCount( 0 ) > 0
|
||||
|| working_copy.VertexCount( 0 ) == 0 )
|
||||
{
|
||||
// If that happens, just skip the operation
|
||||
return false;
|
||||
}
|
||||
|
||||
GetWorkingPolygon()->SetPolyShape( working_copy );
|
||||
return true;
|
||||
}
|
||||
|
||||
wxString POLYGON_INTERSECT_ROUTINE::GetCommitDescription() const
|
||||
{
|
||||
return _( "Intersect polygons." );
|
||||
}
|
||||
|
||||
|
||||
std::optional<wxString> POLYGON_INTERSECT_ROUTINE::GetStatusMessage() const
|
||||
{
|
||||
if( GetSuccesses() == 0 )
|
||||
{
|
||||
return _( "Unable to intersect the selected polygons." );
|
||||
}
|
||||
else if( GetFailures() > 0 )
|
||||
{
|
||||
return _( "Some of the polygons could not be intersected." );
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
bool POLYGON_INTERSECT_ROUTINE::ProcessSubsequentPolygon( const SHAPE_POLY_SET& aPolygon )
|
||||
{
|
||||
const SHAPE_POLY_SET::POLYGON_MODE poly_mode = SHAPE_POLY_SET::POLYGON_MODE::PM_FAST;
|
||||
|
||||
SHAPE_POLY_SET working_copy = GetWorkingPolygon()->GetPolyShape();
|
||||
working_copy.BooleanIntersection( aPolygon, poly_mode );
|
||||
|
||||
// Is there anything left?
|
||||
if( working_copy.OutlineCount() == 0 )
|
||||
{
|
||||
// There was no intersection. Rather than deleting the working polygon, we'll skip
|
||||
// and report a failure.
|
||||
return false;
|
||||
}
|
||||
|
||||
GetWorkingPolygon()->SetPolyShape( working_copy );
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -297,4 +297,74 @@ public:
|
|||
void ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLineB ) override;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A routine that modifies polygons using boolean operations
|
||||
*/
|
||||
class POLYGON_BOOLEAN_ROUTINE : public ITEM_MODIFICATION_ROUTINE
|
||||
{
|
||||
public:
|
||||
POLYGON_BOOLEAN_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler ) :
|
||||
ITEM_MODIFICATION_ROUTINE( aBoard, aHandler ), m_workingPolygon( nullptr )
|
||||
{
|
||||
}
|
||||
|
||||
void ProcessShape( PCB_SHAPE& aPcbShape );
|
||||
|
||||
protected:
|
||||
PCB_SHAPE* GetWorkingPolygon() const { return m_workingPolygon; }
|
||||
|
||||
virtual bool ProcessSubsequentPolygon( const SHAPE_POLY_SET& aPolygon ) = 0;
|
||||
|
||||
private:
|
||||
PCB_SHAPE* m_workingPolygon;
|
||||
};
|
||||
|
||||
class POLYGON_MERGE_ROUTINE : public POLYGON_BOOLEAN_ROUTINE
|
||||
{
|
||||
public:
|
||||
POLYGON_MERGE_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler ) :
|
||||
POLYGON_BOOLEAN_ROUTINE( aBoard, aHandler )
|
||||
{
|
||||
}
|
||||
|
||||
wxString GetCommitDescription() const override;
|
||||
std::optional<wxString> GetStatusMessage() const override;
|
||||
|
||||
private:
|
||||
bool ProcessSubsequentPolygon( const SHAPE_POLY_SET& aPolygon ) override;
|
||||
};
|
||||
|
||||
|
||||
class POLYGON_SUBTRACT_ROUTINE : public POLYGON_BOOLEAN_ROUTINE
|
||||
{
|
||||
public:
|
||||
POLYGON_SUBTRACT_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler ) :
|
||||
POLYGON_BOOLEAN_ROUTINE( aBoard, aHandler )
|
||||
{
|
||||
}
|
||||
|
||||
wxString GetCommitDescription() const override;
|
||||
std::optional<wxString> GetStatusMessage() const override;
|
||||
|
||||
private:
|
||||
bool ProcessSubsequentPolygon( const SHAPE_POLY_SET& aPolygon ) override;
|
||||
};
|
||||
|
||||
|
||||
class POLYGON_INTERSECT_ROUTINE : public POLYGON_BOOLEAN_ROUTINE
|
||||
{
|
||||
public:
|
||||
POLYGON_INTERSECT_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler ) :
|
||||
POLYGON_BOOLEAN_ROUTINE( aBoard, aHandler )
|
||||
{
|
||||
}
|
||||
|
||||
wxString GetCommitDescription() const override;
|
||||
std::optional<wxString> GetStatusMessage() const override;
|
||||
|
||||
private:
|
||||
bool ProcessSubsequentPolygon( const SHAPE_POLY_SET& aPolygon ) override;
|
||||
};
|
||||
|
||||
#endif /* ITEM_MODIFICATION_ROUTINE_H_ */
|
||||
|
|
|
@ -585,6 +585,27 @@ TOOL_ACTION PCB_ACTIONS::extendLines( TOOL_ACTION_ARGS()
|
|||
.MenuText( _( "Extend Lines to Meet" ) )
|
||||
.Tooltip( _( "Extend lines to meet each other" ) ) );
|
||||
|
||||
TOOL_ACTION PCB_ACTIONS::mergePolygons( TOOL_ACTION_ARGS()
|
||||
.Name( "pcbnew.InteractiveEdit.mergePolygons" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.MenuText( _( "Merge Polygons" ) )
|
||||
.Tooltip( _( "Merge selected polygons into a single polygon" ) )
|
||||
.Icon( BITMAPS::merge_polygons ) );
|
||||
|
||||
TOOL_ACTION PCB_ACTIONS::subtractPolygons( TOOL_ACTION_ARGS()
|
||||
.Name( "pcbnew.InteractiveEdit.subtractPolygons" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.MenuText( _( "Subtract Polygons" ) )
|
||||
.Tooltip( _( "Subtract selected polygons from the last one selected" ) )
|
||||
.Icon( BITMAPS::subtract_polygons ) );
|
||||
|
||||
TOOL_ACTION PCB_ACTIONS::intersectPolygons( TOOL_ACTION_ARGS()
|
||||
.Name( "pcbnew.InteractiveEdit.intersectPolygons" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.MenuText( _( "Intersect Polygons" ) )
|
||||
.Tooltip( _( "Create the intersection of the selected polygons" ) )
|
||||
.Icon( BITMAPS::intersect_polygons ) );
|
||||
|
||||
TOOL_ACTION PCB_ACTIONS::deleteFull( TOOL_ACTION_ARGS()
|
||||
.Name( "pcbnew.InteractiveEdit.deleteFull" )
|
||||
.Scope( AS_GLOBAL )
|
||||
|
|
|
@ -160,6 +160,13 @@ public:
|
|||
/// Extend selected lines to meet at a point
|
||||
static TOOL_ACTION extendLines;
|
||||
|
||||
/// Merge multiple polygons into a single polygon
|
||||
static TOOL_ACTION mergePolygons;
|
||||
/// Subtract polygons from other polygons
|
||||
static TOOL_ACTION subtractPolygons;
|
||||
/// Intersection of multiple polygons
|
||||
static TOOL_ACTION intersectPolygons;
|
||||
|
||||
/// Activation of the edit tool
|
||||
static TOOL_ACTION properties;
|
||||
|
||||
|
|
|
@ -327,6 +327,7 @@ set( BMAPS_MID
|
|||
import
|
||||
info
|
||||
insert_module_board
|
||||
intersect_polygons
|
||||
language
|
||||
layers_manager
|
||||
leave_sheet
|
||||
|
@ -351,7 +352,7 @@ set( BMAPS_MID
|
|||
marker_next
|
||||
marker_previous
|
||||
measurement
|
||||
pcb_target
|
||||
merge_polygons
|
||||
mirror_h
|
||||
mirror_v
|
||||
mode_module
|
||||
|
@ -405,6 +406,7 @@ set( BMAPS_MID
|
|||
part_properties
|
||||
paste
|
||||
paste_special
|
||||
pcb_target
|
||||
pin2pin
|
||||
pin_size_to
|
||||
pin_show_etype
|
||||
|
@ -496,6 +498,7 @@ set( BMAPS_MID
|
|||
show_back_assembly_layers
|
||||
special_tools
|
||||
spreadsheet
|
||||
subtract_polygons
|
||||
swap
|
||||
swap_layer
|
||||
switch_corner_rounding_shape
|
||||
|
|
After Width: | Height: | Size: 142 B |
After Width: | Height: | Size: 156 B |
After Width: | Height: | Size: 142 B |
After Width: | Height: | Size: 156 B |
After Width: | Height: | Size: 144 B |
After Width: | Height: | Size: 162 B |
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
sodipodi:docname="intersect_polygons.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata20">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs18" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2554"
|
||||
inkscape:window-height="1400"
|
||||
id="namedview16"
|
||||
showgrid="true"
|
||||
inkscape:zoom="11.313708"
|
||||
inkscape:cx="41.100582"
|
||||
inkscape:cy="9.3249707"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="18"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2997"
|
||||
empspacing="2"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="0.5"
|
||||
spacingy="0.5" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
id="rect4835"
|
||||
style="fill:none;fill-opacity:0.336982;stroke:#e0e0e0;stroke-width:2;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="M 3,15 V 3 h 12 v 6 h 6 V 21 H 9 v -6 z"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#ded3dd;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 15,15 V 9 H 9 v 6 z"
|
||||
id="path6559"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
sodipodi:docname="merge_polygons.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata20">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs18" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2554"
|
||||
inkscape:window-height="1400"
|
||||
id="namedview16"
|
||||
showgrid="true"
|
||||
inkscape:zoom="11.313708"
|
||||
inkscape:cx="48.260038"
|
||||
inkscape:cy="9.148194"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="18"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2997"
|
||||
empspacing="2"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="0.5"
|
||||
spacingy="0.5" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 15,15 V 9 H 9 v 6 z"
|
||||
id="path6559"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
id="rect4835"
|
||||
style="fill:none;fill-opacity:0.336982;stroke:#ded3dd;stroke-width:2;stroke-linejoin:round"
|
||||
d="M 3,15 V 3 h 12 v 6 h 6 V 21 H 9 v -6 z"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
sodipodi:docname="subtract_polygons.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata20">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs18" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2554"
|
||||
inkscape:window-height="1400"
|
||||
id="namedview16"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="8"
|
||||
inkscape:cy="3.5"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="18"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2997"
|
||||
empspacing="2"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="0.5"
|
||||
spacingy="0.5" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 21,21 V 9 H 9 v 12 z"
|
||||
id="path6559-8"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
id="rect4835-5"
|
||||
style="fill:none;fill-opacity:0.336982;stroke:#ded3dd;stroke-width:2;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="M 3,15 V 3 H 15 V 9 H 9 v 6 z"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,100 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
sodipodi:docname="merge_polygons.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata20">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs18" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2554"
|
||||
inkscape:window-height="1400"
|
||||
id="namedview16"
|
||||
showgrid="true"
|
||||
inkscape:zoom="22.124583"
|
||||
inkscape:cx="10.983258"
|
||||
inkscape:cy="9.9662896"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="18"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showguides="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2997"
|
||||
empspacing="2"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="0.5"
|
||||
spacingy="0.5" />
|
||||
<sodipodi:guide
|
||||
position="9,34"
|
||||
orientation="0,-1"
|
||||
id="guide38552" />
|
||||
<sodipodi:guide
|
||||
position="9,34"
|
||||
orientation="0,-1"
|
||||
id="guide38554" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#545454;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 15,15 V 9 H 9 v 6 z"
|
||||
id="path6559"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
id="rect4835"
|
||||
style="fill:none;fill-opacity:0.336982;stroke:#b9b9b9;stroke-width:2;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="M 3,15 V 3 h 12 v 6 h 6 V 21 H 9 v -6 z"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
sodipodi:docname="merge_polygons.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata20">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs18" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2554"
|
||||
inkscape:window-height="1400"
|
||||
id="namedview16"
|
||||
showgrid="true"
|
||||
inkscape:zoom="7.8222213"
|
||||
inkscape:cx="-5.113637"
|
||||
inkscape:cy="18.217332"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="18"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2997"
|
||||
empspacing="2"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="0.5"
|
||||
spacingy="0.5" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#b9b9b9;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 15,15 V 9 H 9 v 6 z"
|
||||
id="path6559"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
id="rect4835"
|
||||
style="fill:none;fill-opacity:0.336982;stroke:#545454;stroke-width:2;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="M 3,15 V 3 h 12 v 6 h 6 V 21 H 9 v -6 z"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
sodipodi:docname="subtract_polygons.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata20">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs18" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2554"
|
||||
inkscape:window-height="1400"
|
||||
id="namedview16"
|
||||
showgrid="true"
|
||||
inkscape:zoom="11.062291"
|
||||
inkscape:cx="14.101961"
|
||||
inkscape:cy="23.819658"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="18"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2997"
|
||||
empspacing="2"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="0.5"
|
||||
spacingy="0.5" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#b9b9b9;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 21,21 V 9 H 9 v 12 z"
|
||||
id="path6559"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
id="rect4835"
|
||||
style="fill:none;fill-opacity:0.336982;stroke:#545454;stroke-width:2;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="M 3,15 V 3 H 15 V 9 H 9 v 6 z"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |