mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
Move CleanUp to SCHEMATIC
This commit is contained in:
parent
651fb1118f
commit
e95ae05dcf
@ -112,190 +112,6 @@ bool SCH_EDIT_FRAME::TrimWire( SCH_COMMIT* aCommit, const VECTOR2I& aStart, cons
|
||||
}
|
||||
|
||||
|
||||
void SCH_EDIT_FRAME::SchematicCleanUp( SCH_COMMIT* aCommit, SCH_SCREEN* aScreen )
|
||||
{
|
||||
SCH_SELECTION_TOOL* selectionTool = m_toolManager->GetTool<SCH_SELECTION_TOOL>();
|
||||
std::vector<SCH_LINE*> lines;
|
||||
std::vector<SCH_JUNCTION*> junctions;
|
||||
std::vector<SCH_NO_CONNECT*> ncs;
|
||||
std::vector<SCH_ITEM*> items_to_remove;
|
||||
bool changed = true;
|
||||
|
||||
if( aScreen == nullptr )
|
||||
aScreen = GetScreen();
|
||||
|
||||
auto remove_item = [&]( SCH_ITEM* aItem ) -> void
|
||||
{
|
||||
changed = true;
|
||||
|
||||
if( !( aItem->GetFlags() & STRUCT_DELETED ) )
|
||||
{
|
||||
aItem->SetFlags( STRUCT_DELETED );
|
||||
|
||||
if( aItem->IsSelected() )
|
||||
selectionTool->RemoveItemFromSel( aItem, true /*quiet mode*/ );
|
||||
|
||||
RemoveFromScreen( aItem, aScreen );
|
||||
aCommit->Removed( aItem, aScreen );
|
||||
}
|
||||
};
|
||||
|
||||
Schematic().BreakSegmentsOnJunctions( aCommit, aScreen );
|
||||
|
||||
for( SCH_ITEM* item : aScreen->Items().OfType( SCH_JUNCTION_T ) )
|
||||
{
|
||||
if( !aScreen->IsExplicitJunction( item->GetPosition() ) )
|
||||
items_to_remove.push_back( item );
|
||||
else
|
||||
junctions.push_back( static_cast<SCH_JUNCTION*>( item ) );
|
||||
}
|
||||
|
||||
for( SCH_ITEM* item : items_to_remove )
|
||||
remove_item( item );
|
||||
|
||||
for( SCH_ITEM* item : aScreen->Items().OfType( SCH_NO_CONNECT_T ) )
|
||||
ncs.push_back( static_cast<SCH_NO_CONNECT*>( item ) );
|
||||
|
||||
alg::for_all_pairs( junctions.begin(), junctions.end(),
|
||||
[&]( SCH_JUNCTION* aFirst, SCH_JUNCTION* aSecond )
|
||||
{
|
||||
if( ( aFirst->GetEditFlags() & STRUCT_DELETED )
|
||||
|| ( aSecond->GetEditFlags() & STRUCT_DELETED ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( aFirst->GetPosition() == aSecond->GetPosition() )
|
||||
remove_item( aSecond );
|
||||
} );
|
||||
|
||||
alg::for_all_pairs( ncs.begin(), ncs.end(),
|
||||
[&]( SCH_NO_CONNECT* aFirst, SCH_NO_CONNECT* aSecond )
|
||||
{
|
||||
if( ( aFirst->GetEditFlags() & STRUCT_DELETED )
|
||||
|| ( aSecond->GetEditFlags() & STRUCT_DELETED ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( aFirst->GetPosition() == aSecond->GetPosition() )
|
||||
remove_item( aSecond );
|
||||
} );
|
||||
|
||||
|
||||
auto minX = []( const SCH_LINE* l )
|
||||
{
|
||||
return std::min( l->GetStartPoint().x, l->GetEndPoint().x );
|
||||
};
|
||||
|
||||
auto maxX = []( const SCH_LINE* l )
|
||||
{
|
||||
return std::max( l->GetStartPoint().x, l->GetEndPoint().x );
|
||||
};
|
||||
|
||||
auto minY = []( const SCH_LINE* l )
|
||||
{
|
||||
return std::min( l->GetStartPoint().y, l->GetEndPoint().y );
|
||||
};
|
||||
|
||||
auto maxY = []( const SCH_LINE* l )
|
||||
{
|
||||
return std::max( l->GetStartPoint().y, l->GetEndPoint().y );
|
||||
};
|
||||
|
||||
// Would be nice to put lines in a canonical form here by swapping
|
||||
// start <-> end as needed but I don't know what swapping breaks.
|
||||
while( changed )
|
||||
{
|
||||
changed = false;
|
||||
lines.clear();
|
||||
|
||||
for( SCH_ITEM* item : aScreen->Items().OfType( SCH_LINE_T ) )
|
||||
{
|
||||
if( item->GetLayer() == LAYER_WIRE || item->GetLayer() == LAYER_BUS )
|
||||
lines.push_back( static_cast<SCH_LINE*>( item ) );
|
||||
}
|
||||
|
||||
// Sort by minimum X position
|
||||
std::sort( lines.begin(), lines.end(),
|
||||
[&]( const SCH_LINE* a, const SCH_LINE* b )
|
||||
{
|
||||
return minX( a ) < minX( b );
|
||||
} );
|
||||
|
||||
for( auto it1 = lines.begin(); it1 != lines.end(); ++it1 )
|
||||
{
|
||||
SCH_LINE* firstLine = *it1;
|
||||
|
||||
if( firstLine->GetEditFlags() & STRUCT_DELETED )
|
||||
continue;
|
||||
|
||||
if( firstLine->IsNull() )
|
||||
{
|
||||
remove_item( firstLine );
|
||||
continue;
|
||||
}
|
||||
|
||||
int firstRightXEdge = maxX( firstLine );
|
||||
auto it2 = it1;
|
||||
|
||||
for( ++it2; it2 != lines.end(); ++it2 )
|
||||
{
|
||||
SCH_LINE* secondLine = *it2;
|
||||
int secondLeftXEdge = minX( secondLine );
|
||||
|
||||
// impossible to overlap remaining lines
|
||||
if( secondLeftXEdge > firstRightXEdge )
|
||||
break;
|
||||
|
||||
// No Y axis overlap
|
||||
if( !( std::max( minY( firstLine ), minY( secondLine ) )
|
||||
<= std::min( maxY( firstLine ), maxY( secondLine ) ) ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if( secondLine->GetFlags() & STRUCT_DELETED )
|
||||
continue;
|
||||
|
||||
if( !secondLine->IsParallel( firstLine )
|
||||
|| !secondLine->IsStrokeEquivalent( firstLine )
|
||||
|| secondLine->GetLayer() != firstLine->GetLayer() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove identical lines
|
||||
if( firstLine->IsEndPoint( secondLine->GetStartPoint() )
|
||||
&& firstLine->IsEndPoint( secondLine->GetEndPoint() ) )
|
||||
{
|
||||
remove_item( secondLine );
|
||||
continue;
|
||||
}
|
||||
|
||||
// See if we can merge an overlap (or two colinear touching segments with
|
||||
// no junction where they meet).
|
||||
SCH_LINE* mergedLine = secondLine->MergeOverlap( aScreen, firstLine, true );
|
||||
|
||||
if( mergedLine != nullptr )
|
||||
{
|
||||
remove_item( firstLine );
|
||||
remove_item( secondLine );
|
||||
|
||||
AddToScreen( mergedLine, aScreen );
|
||||
aCommit->Added( mergedLine, aScreen );
|
||||
|
||||
if( firstLine->IsSelected() || secondLine->IsSelected() )
|
||||
selectionTool->AddItemToSel( mergedLine, true /*quiet mode*/ );
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SCH_EDIT_FRAME::DeleteJunction( SCH_COMMIT* aCommit, SCH_ITEM* aJunction )
|
||||
{
|
||||
SCH_SCREEN* screen = GetScreen();
|
||||
|
@ -216,7 +216,7 @@ public:
|
||||
* Remove an item from the screen (and view)
|
||||
* aScreen is the screen the item is located on, if not the current screen
|
||||
*/
|
||||
void RemoveFromScreen( EDA_ITEM* aItem, SCH_SCREEN* aScreen );
|
||||
void RemoveFromScreen( EDA_ITEM* aItem, SCH_SCREEN* aScreen ) override;
|
||||
|
||||
/**
|
||||
* Mark an item for refresh.
|
||||
|
@ -1780,12 +1780,12 @@ void SCH_EDIT_FRAME::RecalculateConnections( SCH_COMMIT* aCommit, SCH_CLEANUP_FL
|
||||
// Ensure schematic graph is accurate
|
||||
if( aCleanupFlags == LOCAL_CLEANUP )
|
||||
{
|
||||
SchematicCleanUp( aCommit, GetScreen() );
|
||||
Schematic().CleanUp( aCommit, GetScreen() );
|
||||
}
|
||||
else if( aCleanupFlags == GLOBAL_CLEANUP )
|
||||
{
|
||||
for( const SCH_SHEET_PATH& sheet : list )
|
||||
SchematicCleanUp( aCommit, sheet.LastScreen() );
|
||||
Schematic().CleanUp( aCommit, sheet.LastScreen() );
|
||||
}
|
||||
|
||||
timer.Stop();
|
||||
|
@ -481,15 +481,6 @@ public:
|
||||
|
||||
SCH_JUNCTION* AddJunction( SCH_COMMIT* aCommit, SCH_SCREEN* aScreen, const VECTOR2I& aPos );
|
||||
|
||||
/**
|
||||
* Perform routine schematic cleaning including breaking wire and buses and deleting
|
||||
* identical objects superimposed on top of each other.
|
||||
*
|
||||
* @param aCommit Transaction container used to record changes for undo/redo
|
||||
* @param aScreen is the screen to examine, or nullptr to examine the current screen
|
||||
*/
|
||||
void SchematicCleanUp( SCH_COMMIT* aCommit, SCH_SCREEN* aScreen = nullptr );
|
||||
|
||||
/**
|
||||
* If any single wire passes through _both points_, remove the portion between the two points,
|
||||
* potentially splitting the wire into two.
|
||||
|
@ -36,7 +36,9 @@
|
||||
#include <sch_label.h>
|
||||
#include <sch_line.h>
|
||||
#include <sch_marker.h>
|
||||
#include <sch_no_connect.h>
|
||||
#include <sch_screen.h>
|
||||
#include <sch_selection_tool.h>
|
||||
#include <sim/spice_settings.h>
|
||||
#include <sim/spice_value.h>
|
||||
|
||||
@ -1062,4 +1064,195 @@ bool SCHEMATIC::BreakSegmentsOnJunctions( SCH_COMMIT* aCommit, SCH_SCREEN* aScre
|
||||
}
|
||||
|
||||
return brokenSegments;
|
||||
}
|
||||
|
||||
|
||||
void SCHEMATIC::CleanUp( SCH_COMMIT* aCommit, SCH_SCREEN* aScreen )
|
||||
{
|
||||
SCH_SELECTION_TOOL* selectionTool = m_schematicHolder->GetSelectionTool();
|
||||
std::vector<SCH_LINE*> lines;
|
||||
std::vector<SCH_JUNCTION*> junctions;
|
||||
std::vector<SCH_NO_CONNECT*> ncs;
|
||||
std::vector<SCH_ITEM*> items_to_remove;
|
||||
bool changed = true;
|
||||
|
||||
if( aScreen == nullptr )
|
||||
aScreen = GetCurrentScreen();
|
||||
|
||||
auto remove_item = [&]( SCH_ITEM* aItem ) -> void
|
||||
{
|
||||
changed = true;
|
||||
|
||||
if( !( aItem->GetFlags() & STRUCT_DELETED ) )
|
||||
{
|
||||
aItem->SetFlags( STRUCT_DELETED );
|
||||
|
||||
if( aItem->IsSelected() && selectionTool )
|
||||
selectionTool->RemoveItemFromSel( aItem, true /*quiet mode*/ );
|
||||
|
||||
if( m_schematicHolder )
|
||||
{
|
||||
m_schematicHolder->RemoveFromScreen( aItem, aScreen );
|
||||
}
|
||||
aCommit->Removed( aItem, aScreen );
|
||||
}
|
||||
};
|
||||
|
||||
BreakSegmentsOnJunctions( aCommit, aScreen );
|
||||
|
||||
for( SCH_ITEM* item : aScreen->Items().OfType( SCH_JUNCTION_T ) )
|
||||
{
|
||||
if( !aScreen->IsExplicitJunction( item->GetPosition() ) )
|
||||
items_to_remove.push_back( item );
|
||||
else
|
||||
junctions.push_back( static_cast<SCH_JUNCTION*>( item ) );
|
||||
}
|
||||
|
||||
for( SCH_ITEM* item : items_to_remove )
|
||||
remove_item( item );
|
||||
|
||||
for( SCH_ITEM* item : aScreen->Items().OfType( SCH_NO_CONNECT_T ) )
|
||||
ncs.push_back( static_cast<SCH_NO_CONNECT*>( item ) );
|
||||
|
||||
alg::for_all_pairs( junctions.begin(), junctions.end(),
|
||||
[&]( SCH_JUNCTION* aFirst, SCH_JUNCTION* aSecond )
|
||||
{
|
||||
if( ( aFirst->GetEditFlags() & STRUCT_DELETED )
|
||||
|| ( aSecond->GetEditFlags() & STRUCT_DELETED ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( aFirst->GetPosition() == aSecond->GetPosition() )
|
||||
remove_item( aSecond );
|
||||
} );
|
||||
|
||||
alg::for_all_pairs( ncs.begin(), ncs.end(),
|
||||
[&]( SCH_NO_CONNECT* aFirst, SCH_NO_CONNECT* aSecond )
|
||||
{
|
||||
if( ( aFirst->GetEditFlags() & STRUCT_DELETED )
|
||||
|| ( aSecond->GetEditFlags() & STRUCT_DELETED ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( aFirst->GetPosition() == aSecond->GetPosition() )
|
||||
remove_item( aSecond );
|
||||
} );
|
||||
|
||||
|
||||
auto minX = []( const SCH_LINE* l )
|
||||
{
|
||||
return std::min( l->GetStartPoint().x, l->GetEndPoint().x );
|
||||
};
|
||||
|
||||
auto maxX = []( const SCH_LINE* l )
|
||||
{
|
||||
return std::max( l->GetStartPoint().x, l->GetEndPoint().x );
|
||||
};
|
||||
|
||||
auto minY = []( const SCH_LINE* l )
|
||||
{
|
||||
return std::min( l->GetStartPoint().y, l->GetEndPoint().y );
|
||||
};
|
||||
|
||||
auto maxY = []( const SCH_LINE* l )
|
||||
{
|
||||
return std::max( l->GetStartPoint().y, l->GetEndPoint().y );
|
||||
};
|
||||
|
||||
// Would be nice to put lines in a canonical form here by swapping
|
||||
// start <-> end as needed but I don't know what swapping breaks.
|
||||
while( changed )
|
||||
{
|
||||
changed = false;
|
||||
lines.clear();
|
||||
|
||||
for( SCH_ITEM* item : aScreen->Items().OfType( SCH_LINE_T ) )
|
||||
{
|
||||
if( item->GetLayer() == LAYER_WIRE || item->GetLayer() == LAYER_BUS )
|
||||
lines.push_back( static_cast<SCH_LINE*>( item ) );
|
||||
}
|
||||
|
||||
// Sort by minimum X position
|
||||
std::sort( lines.begin(), lines.end(),
|
||||
[&]( const SCH_LINE* a, const SCH_LINE* b )
|
||||
{
|
||||
return minX( a ) < minX( b );
|
||||
} );
|
||||
|
||||
for( auto it1 = lines.begin(); it1 != lines.end(); ++it1 )
|
||||
{
|
||||
SCH_LINE* firstLine = *it1;
|
||||
|
||||
if( firstLine->GetEditFlags() & STRUCT_DELETED )
|
||||
continue;
|
||||
|
||||
if( firstLine->IsNull() )
|
||||
{
|
||||
remove_item( firstLine );
|
||||
continue;
|
||||
}
|
||||
|
||||
int firstRightXEdge = maxX( firstLine );
|
||||
auto it2 = it1;
|
||||
|
||||
for( ++it2; it2 != lines.end(); ++it2 )
|
||||
{
|
||||
SCH_LINE* secondLine = *it2;
|
||||
int secondLeftXEdge = minX( secondLine );
|
||||
|
||||
// impossible to overlap remaining lines
|
||||
if( secondLeftXEdge > firstRightXEdge )
|
||||
break;
|
||||
|
||||
// No Y axis overlap
|
||||
if( !( std::max( minY( firstLine ), minY( secondLine ) )
|
||||
<= std::min( maxY( firstLine ), maxY( secondLine ) ) ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if( secondLine->GetFlags() & STRUCT_DELETED )
|
||||
continue;
|
||||
|
||||
if( !secondLine->IsParallel( firstLine )
|
||||
|| !secondLine->IsStrokeEquivalent( firstLine )
|
||||
|| secondLine->GetLayer() != firstLine->GetLayer() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove identical lines
|
||||
if( firstLine->IsEndPoint( secondLine->GetStartPoint() )
|
||||
&& firstLine->IsEndPoint( secondLine->GetEndPoint() ) )
|
||||
{
|
||||
remove_item( secondLine );
|
||||
continue;
|
||||
}
|
||||
|
||||
// See if we can merge an overlap (or two colinear touching segments with
|
||||
// no junction where they meet).
|
||||
SCH_LINE* mergedLine = secondLine->MergeOverlap( aScreen, firstLine, true );
|
||||
|
||||
if( mergedLine != nullptr )
|
||||
{
|
||||
remove_item( firstLine );
|
||||
remove_item( secondLine );
|
||||
|
||||
if( m_schematicHolder )
|
||||
{
|
||||
m_schematicHolder->AddToScreen( mergedLine, aScreen );
|
||||
}
|
||||
|
||||
aCommit->Added( mergedLine, aScreen );
|
||||
|
||||
if( firstLine->IsSelected() || secondLine->IsSelected() )
|
||||
selectionTool->AddItemToSel( mergedLine, true /*quiet mode*/ );
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -388,6 +388,15 @@ public:
|
||||
|
||||
void SetSchematicHolder( SCHEMATIC_HOLDER* aHolder ) { m_schematicHolder = aHolder; }
|
||||
|
||||
/**
|
||||
* Perform routine schematic cleaning including breaking wire and buses and deleting
|
||||
* identical objects superimposed on top of each other.
|
||||
*
|
||||
* @param aCommit Transaction container used to record changes for undo/redo
|
||||
* @param aScreen is the screen to examine, or nullptr to examine the current screen
|
||||
*/
|
||||
void CleanUp( SCH_COMMIT* aCommit, SCH_SCREEN* aScreen = nullptr );
|
||||
|
||||
/**
|
||||
* True if a SCHEMATIC exists, false if not
|
||||
*/
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
class EDA_ITEM;
|
||||
class SCH_SCREEN;
|
||||
class SCH_SELECTION_TOOL;
|
||||
|
||||
/**
|
||||
* This is a bridge class to help the schematic be able to affect SCH_EDIT_FRAME
|
||||
@ -37,4 +38,8 @@ public:
|
||||
* aScreen is the screen the item is located on, if not the current screen
|
||||
*/
|
||||
virtual void AddToScreen( EDA_ITEM* aItem, SCH_SCREEN* aScreen = nullptr ) = 0;
|
||||
|
||||
virtual SCH_SELECTION_TOOL* GetSelectionTool() { return nullptr; }
|
||||
|
||||
virtual void RemoveFromScreen( EDA_ITEM* aItem, SCH_SCREEN* aScreen ) = 0;
|
||||
};
|
@ -1466,7 +1466,7 @@ int SCH_DRAWING_TOOLS::SingleClickPlace( const TOOL_EVENT& aEvent )
|
||||
SCH_COMMIT commit( m_toolMgr );
|
||||
commit.Added( newItem, screen );
|
||||
|
||||
m_frame->SchematicCleanUp( &commit );
|
||||
m_frame->Schematic().CleanUp( &commit );
|
||||
|
||||
commit.Push( description );
|
||||
}
|
||||
|
@ -1046,7 +1046,7 @@ int SCH_EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent )
|
||||
lwbTool->TrimOverLappingWires( commit, &selectionCopy );
|
||||
lwbTool->AddJunctionsIfNeeded( commit, &selectionCopy );
|
||||
|
||||
m_frame->SchematicCleanUp( commit );
|
||||
m_frame->Schematic().CleanUp( commit );
|
||||
|
||||
if( !localCommit.Empty() )
|
||||
localCommit.Push( _( "Rotate" ) );
|
||||
@ -1243,7 +1243,7 @@ int SCH_EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent )
|
||||
lwbTool->TrimOverLappingWires( commit, &selectionCopy );
|
||||
lwbTool->AddJunctionsIfNeeded( commit, &selectionCopy );
|
||||
|
||||
m_frame->SchematicCleanUp( commit );
|
||||
m_frame->Schematic().CleanUp( commit );
|
||||
}
|
||||
|
||||
if( !localCommit.Empty() )
|
||||
@ -1592,7 +1592,7 @@ int SCH_EDIT_TOOL::RepeatDrawItem( const TOOL_EVENT& aEvent )
|
||||
lwbTool->TrimOverLappingWires( &commit, &newItems );
|
||||
lwbTool->AddJunctionsIfNeeded( &commit, &newItems );
|
||||
|
||||
m_frame->SchematicCleanUp( &commit );
|
||||
m_frame->Schematic().CleanUp( &commit );
|
||||
commit.Push( _( "Repeat Item" ) );
|
||||
}
|
||||
|
||||
|
@ -1238,7 +1238,7 @@ void SCH_LINE_WIRE_BUS_TOOL::finishSegments()
|
||||
getViewControls()->SetAutoPan( false );
|
||||
|
||||
// Correct and remove segments that need to be merged.
|
||||
m_frame->SchematicCleanUp( &commit );
|
||||
m_frame->Schematic().CleanUp( &commit );
|
||||
|
||||
std::vector<SCH_ITEM*> symbols;
|
||||
|
||||
|
@ -1024,7 +1024,7 @@ bool SCH_MOVE_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, SCH_COMMIT* aComm
|
||||
for( EDA_ITEM* item : selection )
|
||||
m_frame->AutoRotateItem( m_frame->GetScreen(), static_cast<SCH_ITEM*>( item ) );
|
||||
|
||||
m_frame->SchematicCleanUp( aCommit );
|
||||
m_frame->Schematic().CleanUp( aCommit );
|
||||
}
|
||||
|
||||
for( EDA_ITEM* item : m_frame->GetScreen()->Items() )
|
||||
@ -1052,7 +1052,7 @@ bool SCH_MOVE_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, SCH_COMMIT* aComm
|
||||
void SCH_MOVE_TOOL::trimDanglingLines( SCH_COMMIT* aCommit )
|
||||
{
|
||||
// Need a local cleanup first to ensure we remove unneeded junctions
|
||||
m_frame->SchematicCleanUp( aCommit, m_frame->GetScreen() );
|
||||
m_frame->Schematic().CleanUp( aCommit, m_frame->GetScreen() );
|
||||
|
||||
std::set<SCH_ITEM*> danglers;
|
||||
|
||||
@ -1840,7 +1840,7 @@ int SCH_MOVE_TOOL::AlignToGrid( const TOOL_EVENT& aEvent )
|
||||
|
||||
m_toolMgr->PostEvent( EVENTS::SelectedItemsMoved );
|
||||
|
||||
m_frame->SchematicCleanUp( &commit );
|
||||
m_frame->Schematic().CleanUp( &commit );
|
||||
commit.Push( _( "Align Items to Grid" ) );
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user