kicad-developers team mailing list archive
-
kicad-developers team
-
Mailing list archive
-
Message #27469
[PATCH] ActionPlugin undo and redo feature
Dear All,
Find attached a patch to add undo/redo feature for external python
plugin called from the "External Plugins" menu.
Attached:
- The patch himself
- A plugin to test is (named testundoredo.py):
- Implement 3 plugins for undo/redo test purpose only
- Remove all modules
- Generate random content
- Move elements randomly
To test it:
- Apply the patch and compile at least with option
KICAD_SCRIPTING_ACTION_MENU=ON
- Copy the script testundoredo.py into your plugin directory
(~/.kicad_plugins under Linux).
- Open pcbnew (from kicad or directly from command line)
- Run plugins in the order you wish to test it.
Regards,
From 5ec8199885de451d62765068cc889c52c0685ae7 Mon Sep 17 00:00:00 2001
From: Jean-Samuel Reynaud <js.reynaud@xxxxxxxxx>
Date: Thu, 2 Feb 2017 10:40:34 +0100
Subject: [PATCH] Adding Undo/Redo support for ActionPlugin calls from menu
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="------------2.7.4"
This is a multi-part message in MIME format.
--------------2.7.4
Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
pcbnew/swig/pcbnew_action_plugins.cpp | 196 ++++++++++++++++++++++++++++++++--
1 file changed, 187 insertions(+), 9 deletions(-)
--------------2.7.4
Content-Type: text/x-patch; name="0001-Adding-Undo-Redo-support-for-ActionPlugin-calls-from.patch"
Content-Transfer-Encoding: 8bit
Content-Disposition: attachment; filename="0001-Adding-Undo-Redo-support-for-ActionPlugin-calls-from.patch"
diff --git a/pcbnew/swig/pcbnew_action_plugins.cpp b/pcbnew/swig/pcbnew_action_plugins.cpp
index 7118f7f..2ecfbfb 100644
--- a/pcbnew/swig/pcbnew_action_plugins.cpp
+++ b/pcbnew/swig/pcbnew_action_plugins.cpp
@@ -36,6 +36,8 @@
#include <class_board.h>
#include <class_module.h>
#include <class_track.h>
+#include <class_drawsegment.h>
+#include <class_zone.h>
#include <board_commit.h>
#include <kicad_device_context.h>
@@ -176,14 +178,190 @@ void PCB_EDIT_FRAME::OnActionPlugin( wxCommandEvent& aEvent )
if( actionPlugin )
{
- // TODO: Adding recovery point for jobs
- // BOARD_COMMIT commit( this );
- // commit.Push( _( "External plugin" ) );
+ PICKED_ITEMS_LIST itemsList;
+ BOARD* currentPcb = GetBoard();
+ bool fromEmpty = false;
- actionPlugin->Run();
+ itemsList.m_Status = UR_CHANGED;
OnModify();
+ // Append tracks:
+ for( BOARD_ITEM* item = currentPcb->m_Track; item != NULL; item = item->Next() )
+ {
+ ITEM_PICKER picker( item, UR_CHANGED );
+ itemsList.PushItem( picker );
+ }
+
+ // Append modules:
+ for( BOARD_ITEM* item = currentPcb->m_Modules; item != NULL; item = item->Next() )
+ {
+ ITEM_PICKER picker( item, UR_CHANGED );
+ itemsList.PushItem( picker );
+ }
+
+ // Append drawings
+ for( BOARD_ITEM* item = currentPcb->m_Drawings; item != NULL; item = item->Next() )
+ {
+ ITEM_PICKER picker( item, UR_CHANGED );
+ itemsList.PushItem( picker );
+ }
+
+ // Append zones outlines
+ for( int ii = 0; ii < currentPcb->GetAreaCount(); ii++ )
+ {
+ ITEM_PICKER picker( (EDA_ITEM*) currentPcb->GetArea(
+ ii ), UR_CHANGED );
+ itemsList.PushItem( picker );
+ }
+
+ // Append zones segm:
+ for( BOARD_ITEM* item = currentPcb->m_Zone; item != NULL; item = item->Next() )
+ {
+ ITEM_PICKER picker( item, UR_CHANGED );
+ itemsList.PushItem( picker );
+ }
+
+ if( itemsList.GetCount() > 0 )
+ SaveCopyInUndoList( itemsList, UR_CHANGED, wxPoint( 0.0, 0.0 ) );
+ else
+ fromEmpty = true;
+
+ itemsList.ClearItemsList();
+
+ // Execute plugin himself...
+ actionPlugin->Run();
+
+ currentPcb->m_Status_Pcb = 0;
+
+ // Get back the undo buffer to fix some modifications
+ PICKED_ITEMS_LIST* oldBuffer = NULL;
+
+ if( fromEmpty )
+ {
+ oldBuffer = new PICKED_ITEMS_LIST();
+ oldBuffer->m_Status = UR_NEW;
+ }
+ else
+ {
+ oldBuffer = GetScreen()->PopCommandFromUndoList();
+ wxASSERT( oldBuffer );
+ }
+
+ // Try do discover what was modified
+
+ PICKED_ITEMS_LIST deletedItemsList;
+
+ // Found deleted modules
+ for( unsigned int i = 0; i < oldBuffer->GetCount(); i++ )
+ {
+ BOARD_ITEM* item = (BOARD_ITEM*) oldBuffer->GetPickedItem( i );
+ ITEM_PICKER picker( item, UR_DELETED );
+
+ wxASSERT( item );
+
+ switch( item->Type() )
+ {
+ case PCB_NETINFO_T:
+ case PCB_MARKER_T:
+ case PCB_MODULE_T:
+ case PCB_TRACE_T:
+ case PCB_VIA_T:
+ case PCB_LINE_T:
+ case PCB_TEXT_T:
+ case PCB_DIMENSION_T:
+ case PCB_TARGET_T:
+ case PCB_ZONE_T:
+
+ // If item has a list it's mean that the element is on the board
+ if( item->GetList() == NULL )
+ {
+ deletedItemsList.PushItem( picker );
+ }
+
+ break;
+
+ case PCB_ZONE_AREA_T:
+ {
+ bool zoneFound = false;
+
+ for( int ii = 0; ii < currentPcb->GetAreaCount(); ii++ )
+ zoneFound |= currentPcb->GetArea( ii ) == item;
+
+ if( !zoneFound )
+ {
+ deletedItemsList.PushItem( picker );
+ }
+
+ break;
+ }
+
+ default:
+ wxString msg;
+ msg.Printf( wxT( "(PCB_EDIT_FRAME::OnActionPlugin) needs work: "
+ "BOARD_ITEM type (%d) not handled" ),
+ item->Type() );
+ wxFAIL_MSG( msg );
+ break;
+ }
+ }
+
+ // Mark deleted elements in undolist
+ for( unsigned int i = 0; i < deletedItemsList.GetCount(); i++ )
+ {
+ oldBuffer->PushItem( deletedItemsList.GetItemWrapper( i ) );
+ }
+
+ // Find new modules
+ for( BOARD_ITEM* item = currentPcb->m_Modules; item != NULL; item = item->Next() )
+ {
+ if( !oldBuffer->ContainsItem( item ) )
+ {
+ ITEM_PICKER picker( item, UR_NEW );
+ oldBuffer->PushItem( picker );
+ }
+ }
+
+ for( BOARD_ITEM* item = currentPcb->m_Track; item != NULL; item = item->Next() )
+ {
+ if( !oldBuffer->ContainsItem( item ) )
+ {
+ ITEM_PICKER picker( item, UR_NEW );
+ oldBuffer->PushItem( picker );
+ }
+ }
+
+ for( BOARD_ITEM* item = currentPcb->m_Drawings; item != NULL; item = item->Next() )
+ {
+ if( !oldBuffer->ContainsItem( item ) )
+ {
+ ITEM_PICKER picker( item, UR_NEW );
+ oldBuffer->PushItem( picker );
+ }
+ }
+
+ for( BOARD_ITEM* item = currentPcb->m_Zone; item != NULL; item = item->Next() )
+ {
+ if( !oldBuffer->ContainsItem( item ) )
+ {
+ ITEM_PICKER picker( item, UR_NEW );
+ oldBuffer->PushItem( picker );
+ }
+ }
+
+ for( int ii = 0; ii < currentPcb->GetAreaCount(); ii++ )
+ {
+ if( !oldBuffer->ContainsItem( (EDA_ITEM*) currentPcb->GetArea( ii ) ) )
+ {
+ ITEM_PICKER picker( (EDA_ITEM*) currentPcb->GetArea(
+ ii ), UR_NEW );
+ oldBuffer->PushItem( picker );
+ }
+ }
+
+
+ GetScreen()->PushCommandToUndoList( oldBuffer );
+
if( IsGalCanvasActive() )
{
UseGalCanvas( GetGalCanvas() );
@@ -201,7 +379,7 @@ void PCB_EDIT_FRAME::RebuildActionPluginMenus()
{
wxMenu* actionMenu = GetMenuBar()->FindItem( ID_TOOLBARH_PCB_ACTION_PLUGIN )->GetSubMenu();
- if( !actionMenu ) // Should not occur.
+ if( !actionMenu ) // Should not occur.
return;
// First, remove existing submenus, if they are too many
@@ -235,7 +413,7 @@ void PCB_EDIT_FRAME::RebuildActionPluginMenus()
{
wxMenuItem* item;
- if( ii < (int)available_menus.size() )
+ if( ii < (int) available_menus.size() )
{
item = available_menus[ii];
item->SetItemLabel( ACTION_PLUGINS::GetAction( ii )->GetName() );
@@ -248,9 +426,9 @@ void PCB_EDIT_FRAME::RebuildActionPluginMenus()
ACTION_PLUGINS::GetAction( ii )->GetDescription(),
KiBitmap( hammer_xpm ) );
- Connect( item->GetId(), wxEVT_COMMAND_MENU_SELECTED,
- (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &
- PCB_EDIT_FRAME::OnActionPlugin );
+ Connect( item->GetId(), wxEVT_COMMAND_MENU_SELECTED,
+ (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &
+ PCB_EDIT_FRAME::OnActionPlugin );
}
ACTION_PLUGINS::SetActionMenu( ii, item->GetId() );
--------------2.7.4--
from pcbnew import *
import random
import pprint
class testundoredo1(ActionPlugin):
def defaults(self):
self.name = "Test Undo/Redo: Remove all modules"
self.category = "Test Undo/Redo"
self.description = ""
def Run(self):
pcb = GetBoard()
while pcb.GetAreaCount() > 0:
area = pcb.GetArea(0)
pcb.RemoveNative(area)
for module in pcb.GetModules():
pcb.RemoveNative(module)
for track in pcb.GetTracks():
pcb.RemoveNative(track)
for draw in pcb.m_Drawings:
pcb.RemoveNative(draw)
class testundoredo2(ActionPlugin):
def defaults(self):
self.name = "Test Undo/Redo: Generate random content"
self.category = "Test Undo/Redo"
self.description = ""
def createFPCXModule(self,pads):
size_025_160mm = wxSizeMM(0.25,1.6)
size_150_200mm = wxSizeMM(1.50,2.0)
# create a new module, it's parent is our previously created pcb
module = MODULE(self.pcb)
module.SetReference("FPC"+str(pads)) # give it a reference name
module.Reference().SetPosition(wxPointMM(-1,-1))
self.pcb.Add(module) # add it to our pcb
m_pos = wxPointMM(0,0)#random.randint(10,200),random.randint(10,200))
module.SetPosition(m_pos)
# create a pad array and add it to the module
def smdRectPad(module,size,pos,name):
pad = D_PAD(module)
pad.SetSize(size)
pad.SetShape(PAD_SHAPE_RECT)
pad.SetAttribute(PAD_ATTRIB_SMD)
pad.SetLayerSet(pad.SMDMask())
pad.SetPosition(pos)
pad.SetPadName(name)
return pad
for n in range (0,pads):
pad = smdRectPad(module,size_025_160mm,wxPointMM(0.5*n,0),str(n+1))
module.Add(pad)
pad_s0 = smdRectPad(module,size_150_200mm,wxPointMM(-1.6,1.3),"0")
pad_s1 = smdRectPad(module,size_150_200mm,wxPointMM((pads-1)*0.5+1.6,1.3),"0")
module.Add(pad_s0)
module.Add(pad_s1)
e = EDGE_MODULE(module)
e.SetStart0(wxPointMM(-1,0))
e.SetEnd0(wxPointMM(0,0))
e.SetWidth(FromMM(0.2))
e.SetLayer(F_SilkS)
e.SetShape(S_SEGMENT)
module.Add(e)
module.SetPosition(wxPointMM(random.randint(20,200),random.randint(20,150)))
return module
def Run(self):
self.pcb = GetBoard()
random.seed()
for i in range(10):
seg = DRAWSEGMENT()
seg.SetLayer( random.choice([Edge_Cuts,Cmts_User,Eco1_User,Eco2_User]) )
seg.SetStart( wxPointMM( random.randint(10,100),
random.randint(10,100) ) )
seg.SetEnd( wxPointMM( random.randint(10,100),
random.randint(10,100) ) )
self.pcb.Add( seg )
if i%2 == 0:
t = TRACK(None)
else:
t = VIA(None)
#t.SetLayerPair(segments['layerPair'][0],segments['layerPair'][1])
t.SetViaType(VIA_THROUGH)
t.SetDrill(FromMM(random.randint(1,20)/10.0))
t.SetStart(wxPointMM(random.randint(100,150),random.randint(100,150)))
t.SetEnd(wxPointMM(random.randint(100,150),random.randint(100,150)))
t.SetWidth(FromMM(random.randint(1,15)/10.0))
t.SetLayer(random.choice([F_Cu,B_Cu]))
self.pcb.Add(t)
self.createFPCXModule(random.randint(2,40))
class testundoredo3(ActionPlugin):
def defaults(self):
self.name = "Test Undo/Redo: Move elements randomly"
self.category = "Test Undo/Redo"
self.description = ""
def Run(self):
pcb = GetBoard()
for i in range(0,pcb.GetAreaCount()):
area = pcb.GetArea(i)
area.Move(wxPointMM(random.randint(-20,20),random.randint(-20,20)))
for module in pcb.GetModules():
module.Move(wxPointMM(random.randint(-20,20),random.randint(-20,20)))
if random.randint(0,10) > 5:
module.Flip(module.GetPosition())
for track in pcb.GetTracks():
track.Move(wxPointMM(random.randint(-20,20),random.randint(-20,20)))
for draw in pcb.m_Drawings:
draw.Move(wxPointMM(random.randint(-20,20),random.randint(-20,20)))
testundoredo1().register()
testundoredo2().register()
testundoredo3().register()
Follow ups