13
$\begingroup$

I want to get my entire session's undo history back when I load a Blender file. This would allow me to undo as far as would have been possible, had I not closed Blender.

Can any in-built feature or plugin support this?

$\endgroup$
3
  • 1
    $\begingroup$ I wanted to ask the same for Blender 2.8* and I found this. $\endgroup$ – mugnozzo Aug 26 '20 at 13:52
  • 4
    $\begingroup$ Related info: Blender's unusual handling of undo steps Devtalk: Undo Performance Must Be Addressed. Blender basically reloads the entire scene when you Undo a step. The backup scenes are kept in RAM, never actually written to disk. $\endgroup$ – Jachym Michal Aug 26 '20 at 16:52
  • 1
    $\begingroup$ And an interesting thread on ux.stackexchange: Undo History - Why limit it?. $\endgroup$ – Jachym Michal Aug 28 '20 at 9:04
8
+25
$\begingroup$

as you mentioned there is a limitation. this test shows you how many steps you can undo. In my pc I can undo only 32 steps:

import bpy

for i in range(0,100):
    bpy.ops.mesh.primitive_cube_add(enter_editmode=False, align='WORLD', location=(i, 0, 0))
    bpy.ops.ed.undo_push(message="Add Cube")


for i in range(0,100):
    bpy.ops.ed.undo()

I decompiled undo script

void ED_undo_push(bContext *C, const char *str)
{
  CLOG_INFO(&LOG, 1, "name='%s'", str);
  WM_file_tag_modified();

  wmWindowManager *wm = CTX_wm_manager(C);
  int steps = U.undosteps;

  /* Ensure steps that have been initialized are always pushed,
   * even when undo steps are zero.
   *
   * Note that some modes (paint, sculpt) initialize an undo step before an action runs,
   * then accumulate changes there, or restore data from it in the case of 2D painting.
   *
   * For this reason we need to handle the undo step even when undo steps is set to zero.
   */
  if ((steps <= 0) && wm->undo_stack->step_init != NULL) {
    steps = 1;
  }
  if (steps <= 0) {
    return;
  }

  /* Only apply limit if this is the last undo step. */
  if (wm->undo_stack->step_active && (wm->undo_stack->step_active->next == NULL)) {
    BKE_undosys_stack_limit_steps_and_memory(wm->undo_stack, steps - 1, 0);
  }

  BKE_undosys_step_push(wm->undo_stack, C, str);

  if (U.undomemory != 0) {
    const size_t memory_limit = (size_t)U.undomemory * 1024 * 1024;
    BKE_undosys_stack_limit_steps_and_memory(wm->undo_stack, -1, memory_limit);
  }

  if (CLOG_CHECK(&LOG, 1)) {
    BKE_undosys_print(wm->undo_stack);
  }
}

you can see how limitation calculated:

const size_t memory_limit = (size_t)U.undomemory * 1024 * 1024;

as @Jachym Michal said in comments increasing this limitation is not recommended

I suggest you use Git.you can easily undo (checkout) by git commands

Git is a distributed version control system for tracking changes in source code during software development.

but if you like the automatic way you can commit after each change:

import subprocess
from subprocess import Popen, PIPE, STDOUT

import re

# undo step
step = 0
# repository path
repoDir = 'C:/Users/Kamali/Desktop/Test'

def gitCommit(message):
    global step
    step += 1
    cmd = 'git add . && git commit -am '+message
    p = subprocess.Popen(cmd, cwd=repoDir)
    p.wait()

def undo():
  global step
  step -= 1
  checkoutTo(step)


# check out to specific step
def checkoutTo(Index):
    cmd = 'git checkout ' + getStepRevision()
    p = subprocess.Popen(cmd, cwd=repoDir)
    p.wait()


# get specific revision from git log history 546bece2c66acb454878500689e3017b92c43d28
def getStepRevision():
    cmd = 'powershell.exe git show HEAD~'+str(step)+' --pretty=oneline --no-patch'
    p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True, cwd=repoDir)
    output = p.stdout.read()
    p.wait()
    
    revision = re.search('(.*?)\s',output.decode('utf-8')).group(1)
    print(revision)

Update

if you don't know git commands you can use git extension

after you undo (check out) you have to close and reopen blender to refresh but these lines of code help you to reload it easy

import bpy

bpy.ops.wm.open_mainfile(filepath=bpy.data.filepath)

Tutorial

run this code and see what happened!!!

import subprocess
from subprocess import Popen, PIPE, STDOUT

import bpy

repoDir = 'C:/Users/Kamali/Desktop/New folder'

for i in range(0,100):
    bpy.ops.mesh.primitive_cube_add(enter_editmode=False, align='WORLD', location=(i, 0, 0))
    bpy.ops.ed.undo_push(message="Add Cube")


def gitCommit(message):
    bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)
    add = 'git add .'
    p = subprocess.Popen(add, cwd=repoDir)
    p.wait()
    commit = 'git commit -m ' + message
    p = subprocess.Popen(commit, cwd=repoDir)
    p.wait()

I could commit 100 steps with code

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.