VirtualUndo Script - Anyone want to Help?

I am teaching a 3D class and my students are still in that phase where they just can’t live without an UNDO. So, I figured, I would give it a shot.
Since edit mode has a basic kind-of undo, I thought I would just tackle object position, rotation, and scale for starters. Actually, I have begun just by working on Z location while I get the concept worked out and figure out python. So far I have the script writing the Selected Object’s LocZ to a text file and retrieving it with buttons. I figure I can get Blender to basically write a history of the objects attributes to a file and then go get the attributes when I want to go back in time. This could even remain an object specific undo History. That’s about it. Anyone care to help me out?

# # # # # # # # # # # # # # # # # # # # # # # # # # # # 
#"VirtualUndo" version A1.0
# by Michael Thoenes and
# "your name here"
#
# Thanks to: Bradley Berthold *nikolatesla20*
# for his Blender 2.14 GUI WIZARD script
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# This is an experiment to create a type of UNDO
# for Blender. The script writes the Z Location
# of the selected object to a text file "Undo.txt" with a
# button press.(File currently is written in folder where
# Blender was Launched).The stored Z-Location can be retrieved
# with another button press.
#
# By the way , I have no I dea what I am doing! Please Help!
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Things to do.
#
# 1. Make the script loop so the stored value can be updated
#    in the text file
# 2. Make this work for othe selected objects
# 3. Include the Loc/Rot/Size data in the List
# 4. Make the recording automatic, triggered by action on the
#    Selected object perhaps
# 5. Record Multiple instances of previous object attributes
#    to allow for multiple undos
# 6. Expand this beyound objects
# 7. Add file path selection for Undo.txt
# 8. Make a small unobtrusive GUI 
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #


###########################################################
# You need to have Object selected before you start the script
#
# Alt-P to run script, X to Exit
###########################################################

import Blender
from Blender.BGL import *
from Blender.Draw import *

# Get selected object
obj = Blender.Object.getSelected() [0]

# Converts obj.Loc to string for use in write(Zlocation)
Zlocation = repr(obj.LocZ)

# These are you button object global variables! 
 
#Here is the main Draw() routine. All your drawing 
#Should be done in this function.
def Draw(): 
	# You must use the global keyword here to allow you to modify
	# a variable outside the scope of the function.

	global Button1
	global Button2

	# Change this color according to what you like ;) 
	glClearColor(0.4,0.5,0.6 ,0.0)
	glClear(GL_COLOR_BUFFER_BIT) 

	Blender.Draw.Button("Store LocZ - writes LocZ to text file",1,40.0,50.0,300,30)
	Blender.Draw.Button("Reset LocZ - Click in 3D window to update ",2,40.0,10.0,300,30)

	glColor3d(0,0,0)
	glRasterPos2i(40, 120)
	Text("VirtualUndo Ver. A1.0 ( LocZ Undo Experiment )")
	glColor3d(0,0,0)
	glRasterPos2i(40, 100)
	Text("Press XKEY to Exit")
 
 
# Here is the main Window Event function. 
# Right now it only has a exit key defined.
def Events(eventnum,eventmod): 
	if eventnum==Blender.Draw.XKEY: 
		Blender.Draw.Exit() 


 
# These are the button Events handlers.
def ButEvent(butnum): 
		
 	if butnum==1: 
		# EVENT HANDLER FOR Button1
		# Put your code in here to respond to the event
		print "Store LocZ=", obj.LocZ 
		out_file = open("Undo.txt", "w")
		out_file.write(Zlocation)
		out_file.close

 
	if butnum==2: 
		# EVENT HANDLER FOR Button2
		# Put your code in here to respond to the event
  
		in_file = open("Undo.txt", "r")
		text = in_file.read()
		in_file.close()
		print "Reset LocZ= ", text
# Convert the variable text type string to type float
		obj.LocZ = float(text)



 
# The Main Blender.Draw.Register() Call...
Blender.Draw.Register(Draw,Events,ButEvent)

Here’s the blend

http://home.att.net/~cs221/VirtualUndoA10.blend

# # # # # # # # # # # # # # # # # # # # # # # # # # # #
#"VirtualUndo" version A1.01
# by Michael Thoenes and
# Jean-Michel Soler
#
# Thanks to: Bradley Berthold *nikolatesla20*
# for his Blender 2.14 GUI WIZARD script
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # #

###########################################################
# You need to have Object selected before you start the script
#
# Alt-P to run script, X to Exit
###########################################################

import Blender
from Blender.BGL import *
from Blender.Draw import *

# Get selected object
obj = Blender.Object.getSelected() [0]
location = repr(obj.loc)
back=0

# These are you button object global variables!

#Here is the main Draw() routine. All your drawing
#Should be done in this function.
def Draw():
   # You must use the global keyword here to allow you to modify
   # a variable outside the scope of the function.

   global Button1
   global Button2

   # Change this color according to what you like ;)
   glClearColor(0.4,0.5,0.6 ,0.0)
   glClear(GL_COLOR_BUFFER_BIT)

   Blender.Draw.Button("Store LocZ - writes LocZ to text file",1,40.0,50.0,300,30)
   Blender.Draw.Button("Reset Loc",2,40.0,10.0,300,30)

   glColor3d(0,0,0)
   glRasterPos2i(40, 120)
   Text("VirtualUndo Ver. A1.0 ( LocZ Undo Experiment )")
   glColor3d(0,0,0)
   glRasterPos2i(40, 100)
   Text("Press XKEY to Exit")


# Here is the main Window Event function.
# Right now it only has a exit key defined.
def Events(eventnum,eventmod):
   if eventnum==Blender.Draw.XKEY:
      Blender.Draw.Exit()



# These are the button Events handlers.
def ButEvent(butnum):
   global back
   if butnum==1:
      # EVENT HANDLER FOR Button1
      # Put your code in here to respond to the event
      # Converts obj.Loc to string for use in write(Zlocation)
      location = repr(obj.loc)

      print "Store Loc=", obj.loc
      try:
         out_file = open(obj.name+"Undo.txt", "a")
         out_file.write(location+'
')
         out_file.close
      except:
         out_file = open(obj.name+"Undo.txt", "w")
         out_file.write(location+'
')
         out_file.close


   if butnum==2:
      # EVENT HANDLER FOR Button2
      # Put your code in here to respond to the event
      in_file = open(obj.name+"Undo.txt", "r")
      text = in_file.readlines()
      in_file.close()
      n=0
      n=back+1
      if n<=len(text):
          print text[len(text)-n],'  ',n
          exec 'obj.loc = %s'%text[len(text)-n]
          back+=1
      Blender.Redraw()


# The Main Blender.Draw.Register() Call...
Blender.Draw.Register(Draw,Events,ButEvent)

A long time ago, I was working on a script like that too, but since you cannot make global script links to the Redraw option (at least, it doesn’t work correctly AFAIK), you’re stuck with having to write undo levels only when the users presses a button or a key over the script window.

If you want to do per-object multiple levels of undo, I suggest you use the pickle (or cPickle) module to store directly a dictionary variable in a file.

you can probably make the dictionary so that each keys is an object name and the corresponding objects are lists working on the first in last out basis (append(x) to add an item, pop() to remove).

Martin

Did you use that method with blender’s objects before?

Thanks for the Help guys. Anyone is welcome to jump in and add your name to the list of contributors. I was afraid about the global link to redraw issue. I had not found away to make it work and it sounds like it won’t.

jms,
You are way ahead of me. Great additions. I like the object specific flie it writes, the history of locations. Excellent. I probably can get back to this tonight. Got to mow the grass! - Big Yard. Anyhow.

Things to do for those interested…

  1. Add Rot and size.
  2. Look into the teeth’s suggestion "If you want to do per-object multiple levels of undo, I suggest you use the pickle (or cPickle) module to store directly a dictionary variable in a file… "
  3. You name it…

Thanks again for your help…My students will love it already.

It seems that pickle ( or cPickle) does not work with the blender’s objects.

Latest improvements:
New Gui Layout with Back and Forward Buttons
and an EXIT Button, QKEY, XKEY and ESCKEY all Exit now
I copied jms script for Back Button and modified - does not work quite as I had hoped, (I don’t quite understand it completely). Anyway it works until you get an 'out of range error- that crashes the script. Maybe you can fix it jms.

Is it possible to have “if eventnum==Blender.Draw.GKEY:” or any Key while in the 3D window cause the write to file? That would allow us to get away from pressing the button all the time.

We could at least set it up where a mouse click anywhere in the active script window would add to the file. That would be much faster than hitting the “store” button. Perhaps it is possible to cause an event off the mouse movement into the window and no click would be required, just a quick mouse movement.

What do you think?

# # # # # # # # # # # # # # # # # # # # # # # # # # # # 
#"VirtualUndo" version A1.02 
# by Michael Thoenes and 
# Jean-Michel Soler 
# 
# Thanks to: Bradley Berthold *nikolatesla20* 
# for his Blender 2.14 GUI WIZARD script 
# 
# # # # # # # # # # # # # # # # # # # # # # # # # # # # 

########################################################### 
# You need to have Object selected before you start the script 
# 
# Alt-P to run script, X to Exit 
########################################################### 

import Blender 
from Blender.BGL import * 
from Blender.Draw import * 

# Get selected object 
obj = Blender.Object.getSelected() [0] 
location = repr(obj.loc) 
back=0 

# These are you button object global variables! 

#Here is the main Draw() routine. All your drawing 
#Should be done in this function. 
def Draw(): 
   # You must use the global keyword here to allow you to modify 
   # a variable outside the scope of the function. 

   global Button1 
   global Button2
   global Button3
   global Button4 

   # Change this color according to what you like ;) 
   glClearColor(0.4,0.5,0.6 ,0.0) 
   glClear(GL_COLOR_BUFFER_BIT) 

   Blender.Draw.Button("Store",1,5,62,100,60, "Writes data to 

text file") 
   Blender.Draw.Button("<Back",2,5,30,47,25, "Back")
   Blender.Draw.Button("Fwd>",3,58,30,47,25, "Forward")
   Blender.Draw.Button("Exit",4,5,5,100,20, "Exit") 

   glColor3d(0,0,0) 
   glRasterPos2i(5, 155) 
   Text("VirtualUndo A1.02") 
   glColor3d(0,0,0) 
   glRasterPos2i(5, 135) 
   Text("Obj Location Undo") 


# Here is the main Window Event function. 
# Right now it only has a exit key defined. 
def Events(eventnum,eventmod): 
   if eventnum==Blender.Draw.XKEY: 
      Blender.Draw.Exit() 
   if eventnum==Blender.Draw.QKEY: 
      Blender.Draw.Exit()
   if eventnum==Blender.Draw.ESCKEY: 
      Blender.Draw.Exit()


# These are the button Events handlers. 
def ButEvent(butnum): 
   global back 
   if butnum==1: 
      # EVENT HANDLER FOR Button1 
      # Put your code in here to respond to the event 
      # Converts obj.Loc to string for use in write(Zlocation) 
      location = repr(obj.loc) 

      print "Store Loc=", obj.loc 
      try: 
         out_file = open(obj.name+"Undo.txt", "a") 
         out_file.write(location+'
') 
         out_file.close 
      except: 
         out_file = open(obj.name+"Undo.txt", "w") 
         out_file.write(location+'
') 
         out_file.close 


   if butnum==2: 
      # EVENT HANDLER FOR Button2 
      # Put your code in here to respond to the event 
      in_file = open(obj.name+"Undo.txt", "r") 
      text = in_file.readlines() 
      in_file.close() 
      n=0 
      n=back+1 
      if n<=len(text): 
          print text[len(text)-n],'  ',n 
          exec 'obj.loc = %s'%text[len(text)-n] 
          back+=1
      Blender.Redraw()

   if butnum==3: 
      # EVENT HANDLER FOR Button3 
      # Put your code in here to respond to the event
      in_file = open(obj.name+"Undo.txt", "r") 
      text = in_file.readlines() 
      in_file.close() 
      n=0 
      n=back+1 
      if n<=len(text): 
          print text[len(text)-n],'  ',n 
          exec 'obj.loc = %s'%text[len(text)-n] 
          back+=-1 
      Blender.Redraw()
   if butnum==4: 
      # EVENT HANDLER FOR Button4 
      # Put your code in here to respond to the event   
      Exit()

# The Main Blender.Draw.Register() Call... 
Blender.Draw.Register(Draw,Events,ButEvent)

[quote=“jms”]

It seems that pickle ( or cPickle) does not work with the blender’s objects.[/quote]
that I know, why I suggested to use the object’s name only, the list corresponding to that object could be a tupple containing loc, rot size. For example


import Blender, cPickle

file = open("test.dat", "w")

lib = {}

objs = Blender.Object.getSelected()

for obj in objs:
        if lib.has_key(ob.name):
                lib[ob.name].append((ob.LocX, ob.LocY, ob.LocZ, ob.RotX, ob.RotY, ob.RotZ, ob.SizeX, ob.SizeY, ob.SizeZ))
        else:
                lib[ob.name] = [(ob.LocX, ob.LocY, ob.LocZ, ob.RotX, ob.RotY, ob.RotZ, ob.SizeX, ob.SizeY, ob.SizeZ)]

cPickle.dump(lib, file, 1)
file.close()

that would be the code to read all the data of the selected objects and dump it to a file.

to retrieve data from a file


import Blender, cPickle

file = open("test.dat", "r")

lib = cPickle.load(file)
file.close()

I’ll try to work on something for the rest, it all sounds more or less easy to me…

Martin

A simple version for little less than two hours of work (started from scratch)

feature list:
works with multiple objects at a time
multiple levels of undo (1 to 100, upper limit can be changed in the code)
save to a file so it reuse the saved history if you re-open the blend file
store data on mouse click on button press
use pickling
use a dictionnary to store the data
data file’s name is on a per blend file basis (based on last blend saved)


# Martin Poirier 2003 #
#  [email protected]   #

import Blender
import cPickle
import os

bNum = Blender.Draw.Create(10)
lib = {}
fileblend = Blender.Get("filename")
filename = fileblend[:fileblend.rfind(".")] + ".dat"

def draw():
	global bNum
	# clearing screen
	Blender.BGL.glClearColor(0.5, 0.5, 0.5, 1)
	Blender.BGL.glColor3f(1.,1.,1.)
	Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)

	#Title
	Blender.BGL.glColor3f(1, 1, 1)
	Blender.BGL.glRasterPos2d(8, 183)
	Blender.Draw.Text("Blender Virtual Undo")
	Blender.BGL.glRasterPos2d(8, 163)
	Blender.Draw.Text("""(C) March 2003 Martin Poirier (aka "theeth")""")
  
	# Instructions
	Blender.BGL.glRasterPos2d(8, 113)
	Blender.Draw.Text("Press Store or left click in the script window")
	Blender.BGL.glRasterPos2d(8, 98)
	Blender.Draw.Text("to store the data of the selected object(s).")

	Blender.BGL.glRasterPos2d(8, 68)
	Blender.Draw.Text("Press Undo to all the selected object(s)")
	Blender.BGL.glRasterPos2d(8, 53)
	Blender.Draw.Text("to their previous state.")

	# Buttons
	Blender.Draw.Button("Store", 3, 10, 10, 50, 25)
	Blender.Draw.Button("Undo", 4, 60, 10, 50, 25)
	#Blender.Draw.Button("Redo", 5, 110, 10, 50, 25)
	Blender.Draw.Button("Exit", 1, 200, 177, 40, 18)
	bNum = Blender.Draw.Number("Level", 10, 10, 130, 90, 18, bNum.val, 1, 100, "The maximum level of Undo stored in the file.")


def event(evt, val):
	if evt == Blender.Draw.ESCKEY and not val: Blender.Draw.Exit()
	if evt == Blender.Draw.LEFTMOUSE and val:
		print "click"
		store()

def bevent(evt):
	if evt == 1: Blender.Draw.Exit()
	if evt == 3: store()
	if evt == 4: undo()

def store():
	global lib, filename, bNum
	objs = Blender.Object.getSelected()
	file = open(filename, "w")

	for obj in objs:
		data = (obj.LocX, obj.LocY, obj.LocZ, obj.RotX, obj.RotY, obj.RotZ, obj.SizeX, obj.SizeY, obj.SizeZ)
		if lib.has_key(obj.name):
			if lib[obj.name]:
				if data != lib[obj.name][-1]:
					if len(lib[obj.name]) >= bNum.val:
						while len(lib[obj.name]) >= bNum.val:
							lib[obj.name].pop(0)
					lib[obj.name].append(data)
			else:
				lib[obj.name] = [data]
		else:
			lib[obj.name] = [data]

	cPickle.dump(lib,file,1)
	file.close()

def read():
	global lib, filename
	file = 0
	try:
		file = open(filename, "r")
	except IOError:
		print "no previous data found, starting from scratch"
	
	if file:
		print "previous data found"
		lib = cPickle.load(file)

def undo():
	global lib, filename, bNum
	objs = Blender.Object.getSelected()
	file = open(filename, "w")

	for obj in objs:
		if lib.has_key(obj.name):
			if lib[obj.name]:
				print lib[obj.name]
				data = lib[obj.name].pop()
				print lib[obj.name]
				obj.LocX = data[0]
				obj.LocY = data[1]
				obj.LocZ = data[2]
				obj.RotX = data[3]
				obj.RotY = data[4]
				obj.RotZ = data[5]
				obj.SizeX = data[6]
				obj.SizeY = data[7]
				obj.SizeZ = data[8]
				Blender.Redraw()
			else:
				print "Maximum level of undo reached"
		else:
			print "no data corresponding to that object"
		
		


	cPickle.dump(lib,file,1)
	file.close()


read()

Blender.Draw.Register(draw,event,bevent)

redo is a little harder than undo, but it shouldn’t be really hard.

Martin

Added: size and rotate.


# # # # # # # # # # # # # # # # # # # # # # # # # # # #
#"VirtualUndo" version A1.02
# by Michael Thoenes and
# Jean-Michel Soler
#
# Thanks to: Bradley Berthold *nikolatesla20*
# for his Blender 2.14 GUI WIZARD script
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # #

###########################################################
# You need to have Object selected before you start the script
#
# Alt-P to run script, X to Exit
###########################################################

import Blender
from Blender.BGL import *
from Blender.Draw import *

# Get selected object
obj = Blender.Object.getSelected() [0]
location = repr(obj.loc)
back=0

# These are you button object global variables!

#Here is the main Draw() routine. All your drawing
#Should be done in this function.
def Draw():
   # You must use the global keyword here to allow you to modify
   # a variable outside the scope of the function.

   global Button1
   global Button2
   global Button3
   global Button4

   # Change this color according to what you like ;)
   glClearColor(0.4,0.5,0.6 ,0.0)
   glClear(GL_COLOR_BUFFER_BIT)

   Blender.Draw.Button("Store",1,5,62,100,60, "Writes data to text file")
   Blender.Draw.Button("<Back",2,5,30,47,25, "Back")
   Blender.Draw.Button("Fwd>",3,58,30,47,25, "Forward")
   Blender.Draw.Button("Exit",4,5,5,100,20, "Exit")

   glColor3d(0,0,0)
   glRasterPos2i(5, 155)
   Text("VirtualUndo A1.02")
   glColor3d(0,0,0)
   glRasterPos2i(5, 135)
   Text("Obj Location Undo")


# Here is the main Window Event function.
# Right now it only has a exit key defined.
def Events(eventnum,eventmod):
   if eventnum==Blender.Draw.XKEY:
      Blender.Draw.Exit()
   if eventnum==Blender.Draw.QKEY:
      Blender.Draw.Exit()
   if eventnum==Blender.Draw.ESCKEY:
      Blender.Draw.Exit()


# These are the button Events handlers.
def ButEvent(butnum):
   global back
   if butnum==1:
      # EVENT HANDLER FOR Button1
      # Put your code in here to respond to the event
      # Converts obj.Loc to string for use in write(Zlocation)
      location = repr(obj.loc)+";"+repr(obj.rot)+";"+repr(obj.size)

      print "Store Loc=", obj.loc

      try:
         out_file = open(obj.name+"Undo.txt", "a")
         out_file.write(location+'
')
         out_file.close
      except:
         out_file = open(obj.name+"Undo.txt", "w")
         out_file.write(location+'
')
         out_file.close


   if butnum==2:
      # EVENT HANDLER FOR Button2
      # Put your code in here to respond to the event
      in_file = open(obj.name+"Undo.txt", "r")
      text = in_file.readlines()
      in_file.close()
      if back<len(text):back+=1
      n=0
      n=back+1
      if n<=len(text):

          print back
          t=text[len(text)-n].split(';')
          #print t 
          exec 'obj.loc,obj.rot,obj.size = %s,%s,%s'%(t[0],t[1],t[2])

          

      Blender.Redraw()

   if butnum==3:
      # EVENT HANDLER FOR Button3
      # Put your code in here to respond to the event
      in_file = open(obj.name+"Undo.txt", "r")
      text = in_file.readlines()
      in_file.close()
      if back>0: back-=1
      n=0
      n=back+1;print 'back',back
      if n<=len(text) and n>=0:
          t=text[len(text)-n].split(';')
          exec 'obj.loc,obj.rot,obj.size = %s,%s,%s'%(t[0],t[1],t[2])
      Blender.Redraw()
   if butnum==4:
      # EVENT HANDLER FOR Button4
      # Put your code in here to respond to the event   
      Exit()

# The Main Blender.Draw.Register() Call...
Blender.Draw.Register(Draw,Events,ButEvent)
   

jms,

Good work on the fixes and the loc rot size…
Now that theeth has sort of blown this little script out of the water, (Great work theeth!) what should we do. I am going to study theeths script and learn a few things, ask a couple of questions etc. My students will be pleased with these options.

theeth,

  1. Do you think you could expand this to include the “redo” capability?
  2. I get an error on line 6 import os, It works fine if I comment that out.
    That is with Blender 2.26 on Win 2000
  1. sure, I’ll see what I can do tonight.
  2. strange, if it imports cPickle, it should have any problem importing os…
    anyway, I was only planing on using that module, but I didn’t.

Martin

theeth wrote:

  1. sure, I’ll see what I can do tonight.

Thats great theeth.

We need to tell the user that they should go ahead and save their file first so the .dat will get the same name as the file.

Another request: Can you have a Right Click in the Script Window do a Blender file save. I Know Ctrl-W is not all that hard, but it would be cool to be part of the script. that combined with setting the number of versons actually gives blender a lot of undo capabilities.

I think you we should spread the word about this. with some basic instructions for newbies. I will see if I can work something up inluding the undo suggestions by Joeri - http://www.blender.org/modules.php?op=modload&name=phpBB2&file=viewtopic&t=974&highlight=undo

Blender has some “undo’s”, they are just not automated.

  1. Material undo:
  • copy current material into buffer (the arrow into yellow icon)
  • funble with the material, not happy? copy buffer back into material.
  1. Mesh edit undo:
  • Tab into edit mode.
  • funble with the mesh, not happy? Alt-U back the old mesh.
  1. Ipo curve undo:
  • same buffer system as material.
  1. Text editors undo:
  • famous alt-Z (alt-X and alt-C as well)
  1. Objects undo:
  • weird harddisk buffer system
    {ctrl-w} {PlusSign} {Enter} (yes saved the file with an added number)
  • fumble around with the objects, not happy? {ctrl-o} {enter}

I would also mention setting up the Auto Temp Save… That has saved me several times on some complex mistakes. The vversion save is good too.

I said last saved filename, but it’s truely last saved or openned file, so unless you start from scratch, there shouldn’t be a problem with loading the wrong dat file.

don’t know, I’ll have to dig in the API to see if that can be done. I think the blender210 api has something like this, but it would limit that feature of the script to 2.23.

Martin

oh, by the way, new update with redo


# Martin Poirier 2003 #
#  [email protected]   #

import Blender
import cPickle

bNum = Blender.Draw.Create(10)
libU = {}
libR = {}
fileblend = Blender.Get("filename")
filename = fileblend[:fileblend.rfind(".")] + ".dat"

def draw():
	global bNum
	# clearing screen
	Blender.BGL.glClearColor(0.5, 0.5, 0.5, 1)
	Blender.BGL.glColor3f(1.,1.,1.)
	Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)

	#Title
	Blender.BGL.glColor3f(1, 1, 1)
	Blender.BGL.glRasterPos2d(8, 183)
	Blender.Draw.Text("Blender Virtual Undo")
	Blender.BGL.glRasterPos2d(8, 163)
	Blender.Draw.Text("""(C) March 2003 Martin Poirier (aka "theeth")""")
  
	# Instructions
	Blender.BGL.glRasterPos2d(8, 113)
	Blender.Draw.Text("Press Store or left click in the script window")
	Blender.BGL.glRasterPos2d(8, 98)
	Blender.Draw.Text("to store the data of the selected object(s).")

	Blender.BGL.glRasterPos2d(8, 68)
	Blender.Draw.Text("Press Undo to all the selected object(s)")
	Blender.BGL.glRasterPos2d(8, 53)
	Blender.Draw.Text("to their previous state.")

	# Buttons
	Blender.Draw.Button("Store", 3, 10, 10, 50, 25)
	Blender.Draw.Button("Undo", 4, 60, 10, 50, 25)
	Blender.Draw.Button("Redo", 5, 110, 10, 50, 25)
	Blender.Draw.Button("Exit", 1, 200, 177, 40, 18)
	bNum = Blender.Draw.Number("Level", 10, 10, 130, 90, 18, bNum.val, 1, 100, "The maximum level of Undo stored in the file.")


def event(evt, val):
	if evt == Blender.Draw.ESCKEY and not val: Blender.Draw.Exit()
	if evt == Blender.Draw.LEFTMOUSE and val:
		store()

def bevent(evt):
	if evt == 1: Blender.Draw.Exit()
	if evt == 3: store()
	if evt == 4: undo()
	if evt == 5: redo()

def store():
	global libU, libR, filename, bNum
	objs = Blender.Object.getSelected()
	file = open(filename, "w")

	for obj in objs:
		data = (obj.LocX, obj.LocY, obj.LocZ, obj.RotX, obj.RotY, obj.RotZ, obj.SizeX, obj.SizeY, obj.SizeZ)
		if libU.has_key(obj.name):
			if libU[obj.name]:
				if data != libU[obj.name][-1]:
					if len(libU[obj.name]) >= bNum.val:
						while len(libU[obj.name]) >= bNum.val:
							libU[obj.name].pop(0)
					libU[obj.name].append(data)
			else:
				libU[obj.name] = [data]
		else:
			libU[obj.name] = [data]

		if libR.has_key(obj.name):
			libR[obj.name] = []

	cPickle.dump(libU,file,1)
	cPickle.dump(libR,file,1)
	file.close()

def read():
	global libU, libR, filename
	file = 0
	try:
		file = open(filename, "r")
	except IOError:
		print "no previous data found, starting from scratch"
	
	if file:
		print "previous data found"
		libU = cPickle.load(file)
		libR = cPickle.load(file)

def undo():
	global libU, libR, filename, bNum
	objs = Blender.Object.getSelected()
	file = open(filename, "w")

	for obj in objs:
		if libU.has_key(obj.name):
			if libU[obj.name]:
				dataR = (obj.LocX, obj.LocY, obj.LocZ, obj.RotX, obj.RotY, obj.RotZ, obj.SizeX, obj.SizeY, obj.SizeZ)
				dataU = libU[obj.name].pop()
				obj.LocX = dataU[0]
				obj.LocY = dataU[1]
				obj.LocZ = dataU[2]
				obj.RotX = dataU[3]
				obj.RotY = dataU[4]
				obj.RotZ = dataU[5]
				obj.SizeX = dataU[6]
				obj.SizeY = dataU[7]
				obj.SizeZ = dataU[8]

				if libR.has_key(obj.name):
					libR[obj.name].append(dataR)
				else:
					libR[obj.name] = [dataR]

				Blender.Redraw()
			else:
				print "Maximum level of undo reached"
		else:
			print "no data corresponding to that object"
		
		
	cPickle.dump(libU,file,1)
	cPickle.dump(libR,file,1)
	file.close()

def redo():
	global libU, libR, filename, bNum
	objs = Blender.Object.getSelected()
	file = open(filename, "w")

	for obj in objs:
		if libR.has_key(obj.name):
			if libR[obj.name]:
				dataR = libR[obj.name].pop()
				dataU = (obj.LocX, obj.LocY, obj.LocZ, obj.RotX, obj.RotY, obj.RotZ, obj.SizeX, obj.SizeY, obj.SizeZ)
				obj.LocX = dataR[0]
				obj.LocY = dataR[1]
				obj.LocZ = dataR[2]
				obj.RotX = dataR[3]
				obj.RotY = dataR[4]
				obj.RotZ = dataR[5]
				obj.SizeX = dataR[6]
				obj.SizeY = dataR[7]
				obj.SizeZ = dataR[8]

				if libU.has_key(obj.name):
					libU[obj.name].append(dataU)
				else:
					libU[obj.name] = [dataU]

				Blender.Redraw()
			else:
				print "Maximum level of redo reached"
		else:
			print "no data corresponding to that object"
		
		
	cPickle.dump(libU,file,1)
	cPickle.dump(libR,file,1)
	file.close()

read()

Blender.Draw.Register(draw,event,bevent)

now, I’ll work on making a compressed version of the GUI, so you can use it without cluttering your work space too much.

Martin

just pay attention to the fact that you are teaching and the data file writing by our script can be edited in a words processor by your students.

it’s in binary format, so not really editable by hand.
it could still be deleted though.

Martin

theeth,
I tried your update, it works great. I was looking at the previous version last night and thought I would try to add the update messages to the GUI.
I borrowed the idea from S68’s knife script. This version variation displays 'Stored, Undo, Maximum undo reached… etc in the script window.
I find it very useful to know what is going on without having to jump to the command window.

# Martin Poirier 2003 # 
#  [email protected]   # 

import Blender 
import cPickle 

bNum = Blender.Draw.Create(10) 
libU = {} 
libR = {} 
fileblend = Blender.Get("filename") 
filename = fileblend[:fileblend.rfind(".")] + ".dat" 

def draw(): 
   global bNum 
   # clearing screen 
   Blender.BGL.glClearColor(0.5, 0.5, 0.5, 1) 
   Blender.BGL.glColor3f(1.,1.,1.) 
   Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT) 

   #Title 
   Blender.BGL.glColor3f(1, 1, 1) 
   Blender.BGL.glRasterPos2d(8, 183) 
   Blender.Draw.Text("Blender Virtual Undo") 
   Blender.BGL.glRasterPos2d(8, 163) 
   Blender.Draw.Text("""(C) March 2003 Martin Poirier (aka "theeth")""") 
  
   # Instructions 
   Blender.BGL.glRasterPos2d(8, 113) 
   Blender.Draw.Text("Press Store or left click in the script window") 
   Blender.BGL.glRasterPos2d(8, 98) 
   Blender.Draw.Text("to store the data of the selected object(s).") 

   Blender.BGL.glRasterPos2d(8, 68) 
   Blender.Draw.Text("Press Undo to all the selected object(s)") 
   Blender.BGL.glRasterPos2d(8, 53) 
   Blender.Draw.Text("to their previous state.") 

   # Buttons 
   Blender.Draw.Button("Store", 3, 10, 10, 50, 25) 
   Blender.Draw.Button("Undo", 4, 60, 10, 50, 25) 
   Blender.Draw.Button("Redo", 5, 110, 10, 50, 25) 
   Blender.Draw.Button("Exit", 1, 200, 177, 40, 18) 
   bNum = Blender.Draw.Number("Level", 10, 10, 130, 90, 18, bNum.val, 1, 100, "The maximum level of Undo stored in the file.") 


def event(evt, val): 
   if evt == Blender.Draw.ESCKEY and not val: Blender.Draw.Exit() 
   if evt == Blender.Draw.LEFTMOUSE and val: 
      store() 

def bevent(evt): 
   if evt == 1: Blender.Draw.Exit() 
   if evt == 3: store() 
   if evt == 4: undo() 
   if evt == 5: redo() 

def store(): 
   global libU, libR, filename, bNum 
   objs = Blender.Object.getSelected() 
   file = open(filename, "w") 

   for obj in objs: 
      data = (obj.LocX, obj.LocY, obj.LocZ, obj.RotX, obj.RotY, obj.RotZ, obj.SizeX, obj.SizeY, obj.SizeZ) 
      if libU.has_key(obj.name): 
         if libU[obj.name]: 
            if data != libU[obj.name][-1]: 
               if len(libU[obj.name]) >= bNum.val: 
                  while len(libU[obj.name]) >= bNum.val: 
                     libU[obj.name].pop(0) 
               libU[obj.name].append(data) 
         else: 
            libU[obj.name] = [data] 
      else: 
         libU[obj.name] = [data] 

      if libR.has_key(obj.name): 
         libR[obj.name] = [] 

   cPickle.dump(libU,file,1) 
   cPickle.dump(libR,file,1) 
   file.close() 

def read(): 
   global libU, libR, filename 
   file = 0 
   try: 
      file = open(filename, "r") 
   except IOError: 
      print "no previous data found, starting from scratch" 
    
   if file: 
      print "previous data found" 
      libU = cPickle.load(file) 
      libR = cPickle.load(file) 

def undo(): 
   global libU, libR, filename, bNum 
   objs = Blender.Object.getSelected() 
   file = open(filename, "w") 

   for obj in objs: 
      if libU.has_key(obj.name): 
         if libU[obj.name]: 
            dataR = (obj.LocX, obj.LocY, obj.LocZ, obj.RotX, obj.RotY, obj.RotZ, obj.SizeX, obj.SizeY, obj.SizeZ) 
            dataU = libU[obj.name].pop() 
            obj.LocX = dataU[0] 
            obj.LocY = dataU[1] 
            obj.LocZ = dataU[2] 
            obj.RotX = dataU[3] 
            obj.RotY = dataU[4] 
            obj.RotZ = dataU[5] 
            obj.SizeX = dataU[6] 
            obj.SizeY = dataU[7] 
            obj.SizeZ = dataU[8] 

            if libR.has_key(obj.name): 
               libR[obj.name].append(dataR) 
            else: 
               libR[obj.name] = [dataR] 

            Blender.Redraw() 
         else: 
            print "Maximum level of undo reached" 
      else: 
         print "no data corresponding to that object" 
       
       
   cPickle.dump(libU,file,1) 
   cPickle.dump(libR,file,1) 
   file.close() 

def redo(): 
   global libU, libR, filename, bNum 
   objs = Blender.Object.getSelected() 
   file = open(filename, "w") 

   for obj in objs: 
      if libR.has_key(obj.name): 
         if libR[obj.name]: 
            dataR = libR[obj.name].pop() 
            dataU = (obj.LocX, obj.LocY, obj.LocZ, obj.RotX, obj.RotY, obj.RotZ, obj.SizeX, obj.SizeY, obj.SizeZ) 
            obj.LocX = dataR[0] 
            obj.LocY = dataR[1] 
            obj.LocZ = dataR[2] 
            obj.RotX = dataR[3] 
            obj.RotY = dataR[4] 
            obj.RotZ = dataR[5] 
            obj.SizeX = dataR[6] 
            obj.SizeY = dataR[7] 
            obj.SizeZ = dataR[8] 

            if libU.has_key(obj.name): 
               libU[obj.name].append(dataU) 
            else: 
               libU[obj.name] = [dataU] 

            Blender.Redraw() 
         else: 
            print "Maximum level of redo reached" 
      else: 
         print "no data corresponding to that object" 
       
       
   cPickle.dump(libU,file,1) 
   cPickle.dump(libR,file,1) 
   file.close() 

read() 

Blender.Draw.Register(draw,event,bevent) 

The update for the click in window is not working right all the time. but the other messages seem to update fine. I would like to have the message actually tell what level you are at in the undo tree. A bit more complicated, but I am enjoying learning this stuff. If it is easy for you, I think it would make a good addition.

jms wrote:

just pay attention to the fact that you are teaching and the data file writing by our script can be edited in a words processor by your students.

Good point jms… I don’t think our script is without merit. I would like to continue to play with it. I am on a real learning curve right now. I encourage you to do whatever you like with it. I am now going to look into building script manager that will call and execute different scripts from a single Gui interface. I would find this very useful. I would like to include an undo script, knife, rustyknife, etc…and many more all called from one GUI, I would like to group them as Object tools, mesh-vertex tools, animation tools, etc…similar to the way Blender breaks things apart.
It’s just an idea at the moment. I will start a new thread for this when I figure out what I am doing.

new version with two different GUIs (compressed and standard), number of undo and redo level (only for the Active object though since all the selected objects may not have the same number of levels stored).

I’ll post a download link directly to the .py file tomorrow.

# Martin Poirier 2003 #
#  [email protected]   #

import Blender
import cPickle

nbGUI = 0

bNum = Blender.Draw.Create(10)
nbLvl4Obj = (0,0)
libU = {}
libR = {}
fileblend = Blender.Get("filename")
filename = fileblend[:fileblend.rfind(".")] + ".dat"

def draw():
	global bNum, nbGUI, nbLvl4Obj

	calcLvl4Obj()

	# clearing screen
	Blender.BGL.glClearColor(0.5, 0.5, 0.5, 1)
	Blender.BGL.glColor3f(1.,1.,1.)
	Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)

	if nbGUI:
		#Title
		Blender.BGL.glColor3f(1, 1, 1)
		Blender.BGL.glRasterPos2d(8, 183)
		Blender.Draw.Text("Blender Virtual Undo")
		Blender.BGL.glRasterPos2d(8, 163)
		Blender.Draw.Text("""(C) March 2003 Martin Poirier (aka "theeth")""")
	  
		# Instructions
		Blender.BGL.glRasterPos2d(8, 113)
		Blender.Draw.Text("Press Store or left click in the script window")
		Blender.BGL.glRasterPos2d(8, 98)
		Blender.Draw.Text("to store the data of the selected object(s).")
	
		Blender.BGL.glRasterPos2d(8, 68)
		Blender.Draw.Text("Press Undo to all the selected object(s)")
		Blender.BGL.glRasterPos2d(8, 53)
		Blender.Draw.Text("to their previous state.")
	
		# Buttons
		Blender.Draw.Button("Store", 3, 10, 10, 50, 25)
		Blender.Draw.Button("Undo(" + nbLvl4Obj[0] + ")", 4, 60, 10, 60, 25)
		Blender.Draw.Button("Redo(" + nbLvl4Obj[1] + ")", 5, 120, 10, 60, 25)
		Blender.Draw.Button("Exit", 1, 200, 177, 40, 18)
		Blender.Draw.Button("Reduce", 9, 140, 177, 60, 18)
		bNum = Blender.Draw.Number("Level", 10, 10, 130, 90, 18, bNum.val, 1, 100, "The maximum level of Undo stored in the file.")
	else:
		# Buttons
		Blender.Draw.Button("Store", 3, 10, 10, 60, 15)
		Blender.Draw.Button("<-", 4, 10, 40, 30, 15, "Undo")
		Blender.Draw.Button("->", 5, 40, 40, 30, 15, "Redo")
		Blender.Draw.Button("X", 1, 10, 70, 30, 15, "Exit")
		Blender.Draw.Button("^", 9, 40, 70, 30, 15, "Expend")
		bNum = Blender.Draw.Number("Lvl", 10, 10, 55, 60, 15, bNum.val, 1, 100, "The maximum level of Undo stored in the file.")
		Blender.BGL.glColor3i(0,0,0)
		Blender.BGL.glRasterPos2d(15, 30)
		Blender.Draw.Text("(" + nbLvl4Obj[0] + ")")
		Blender.BGL.glRasterPos2d(45, 30)
		Blender.Draw.Text("(" + nbLvl4Obj[1] + ")")
		

def event(evt, val):
	if evt == Blender.Draw.ESCKEY and not val: Blender.Draw.Exit()
	if evt == Blender.Draw.LEFTMOUSE and val:
		store()
		Blender.Draw.Redraw()
	if evt == Blender.Draw.LEFTARROWKEY and val:
		undo()
		Blender.Draw.Redraw()
	if evt == Blender.Draw.RIGHTARROWKEY and val:
		redo()
		Blender.Draw.Redraw()

def bevent(evt):
	global nbGUI
	if evt == 1: Blender.Draw.Exit()
	if evt == 3:
		store()
		Blender.Draw.Redraw()
	if evt == 4:
		undo()
		Blender.Draw.Redraw()
	if evt == 5:
		redo()
		Blender.Draw.Redraw()
	if evt == 9:
		nbGUI = -1 * (nbGUI - 1)
		Blender.Draw.Redraw()

def store():
	global libU, libR, filename, bNum
	objs = Blender.Object.getSelected()
	file = open(filename, "w")

	for obj in objs:
		data = (obj.LocX, obj.LocY, obj.LocZ, obj.RotX, obj.RotY, obj.RotZ, obj.SizeX, obj.SizeY, obj.SizeZ)
		if libU.has_key(obj.name):
			if libU[obj.name]:
				if data != libU[obj.name][-1]:
					if len(libU[obj.name]) >= bNum.val:
						while len(libU[obj.name]) >= bNum.val:
							libU[obj.name].pop(0)
					libU[obj.name].append(data)
			else:
				libU[obj.name] = [data]
		else:
			libU[obj.name] = [data]

		if libR.has_key(obj.name):
			libR[obj.name] = []

	cPickle.dump(libU,file,1)
	cPickle.dump(libR,file,1)
	file.close()

def read():
	global libU, libR, filename
	file = 0
	try:
		file = open(filename, "r")
	except IOError:
		print "no previous data found, starting from scratch"
	
	if file:
		print "previous data found"
		libU = cPickle.load(file)
		libR = cPickle.load(file)

def calcLvl4Obj():
	global nbLvl4Obj, libU, libR
	objName = Blender.Object.getSelected()[0].name

	if libU.has_key(objName):
		lvlU = len(libU[objName])
	else:
		lvlU = 0
	
	if libR.has_key(objName):
		lvlR = len(libR[objName])
	else:
		lvlR = 0

	nbLvl4Obj = (str(lvlU), str(lvlR))

def undo():
	global libU, libR, filename, bNum
	objs = Blender.Object.getSelected()
	file = open(filename, "w")

	for obj in objs:
		if libU.has_key(obj.name):
			if libU[obj.name]:
				dataR = (obj.LocX, obj.LocY, obj.LocZ, obj.RotX, obj.RotY, obj.RotZ, obj.SizeX, obj.SizeY, obj.SizeZ)
				dataU = libU[obj.name].pop()
				obj.LocX = dataU[0]
				obj.LocY = dataU[1]
				obj.LocZ = dataU[2]
				obj.RotX = dataU[3]
				obj.RotY = dataU[4]
				obj.RotZ = dataU[5]
				obj.SizeX = dataU[6]
				obj.SizeY = dataU[7]
				obj.SizeZ = dataU[8]

				if libR.has_key(obj.name):
					libR[obj.name].append(dataR)
				else:
					libR[obj.name] = [dataR]

				Blender.Redraw()
			else:
				print "Maximum level of undo reached"
		else:
			print "no data corresponding to that object"
		
		
	cPickle.dump(libU,file,1)
	cPickle.dump(libR,file,1)
	file.close()

def redo():
	global libU, libR, filename, bNum
	objs = Blender.Object.getSelected()
	file = open(filename, "w")

	for obj in objs:
		if libR.has_key(obj.name):
			if libR[obj.name]:
				dataR = libR[obj.name].pop()
				dataU = (obj.LocX, obj.LocY, obj.LocZ, obj.RotX, obj.RotY, obj.RotZ, obj.SizeX, obj.SizeY, obj.SizeZ)
				obj.LocX = dataR[0]
				obj.LocY = dataR[1]
				obj.LocZ = dataR[2]
				obj.RotX = dataR[3]
				obj.RotY = dataR[4]
				obj.RotZ = dataR[5]
				obj.SizeX = dataR[6]
				obj.SizeY = dataR[7]
				obj.SizeZ = dataR[8]

				if libU.has_key(obj.name):
					libU[obj.name].append(dataU)
				else:
					libU[obj.name] = [dataU]

				Blender.Redraw()
			else:
				print "Maximum level of redo reached"
		else:
			print "no data corresponding to that object"
		
		
	cPickle.dump(libU,file,1)
	cPickle.dump(libR,file,1)
	file.close()

read()

Blender.Draw.Register(draw,event,bevent)

Martin