This repository has been archived on 2024-06-13. You can view files and clone it, but cannot push or open issues or pull requests.
mcedit/editortools/operation.py
David Vierra 963d2a728c Added: "Record Undo" option.
"Record Undo" is reset when loading or saving a level.
Long undo operations can be canceled.
2013-03-13 23:13:54 -10:00

118 lines
4.0 KiB
Python

import atexit
import os
import shutil
import tempfile
import albow
from pymclevel import BoundingBox
import numpy
from albow.root import Cancel
import pymclevel
from mceutils import showProgress
from pymclevel.mclevelbase import exhaust
undo_folder = os.path.join(tempfile.gettempdir(), "mcedit_undo", str(os.getpid()))
def mkundotemp():
if not os.path.exists(undo_folder):
os.makedirs(undo_folder)
return tempfile.mkdtemp("mceditundo", dir=undo_folder)
atexit.register(shutil.rmtree, undo_folder, True)
class Operation(object):
changedLevel = True
undoLevel = None
def __init__(self, editor, level):
self.editor = editor
self.level = level
def extractUndo(self, level, box):
if isinstance(level, pymclevel.MCInfdevOldLevel):
return self.extractUndoChunks(level, box.chunkPositions, box.chunkCount)
else:
return self.extractUndoSchematic(level, box)
def extractUndoChunks(self, level, chunks, chunkCount = None):
if not isinstance(level, pymclevel.MCInfdevOldLevel):
chunks = numpy.array(list(chunks))
mincx, mincz = numpy.min(chunks, 0)
maxcx, maxcz = numpy.max(chunks, 0)
box = BoundingBox((mincx << 4, 0, mincz << 4), (maxcx << 4, level.Height, maxcz << 4))
return self.extractUndoSchematic(level, box)
undoLevel = pymclevel.MCInfdevOldLevel(mkundotemp(), create=True)
if not chunkCount:
try:
chunkCount = len(chunks)
except TypeError:
chunkCount = -1
def _extractUndo():
yield 0, 0, "Recording undo..."
for i, (cx, cz) in enumerate(chunks):
undoLevel.copyChunkFrom(level, cx, cz)
yield i, chunkCount, "Copying chunk %s..." % ((cx, cz),)
undoLevel.saveInPlace()
if chunkCount > 25 or chunkCount < 1:
if "Canceled" == showProgress("Recording undo...", _extractUndo(), cancel=True):
if albow.ask("Continue with undo disabled?", ["Continue", "Cancel"]) == "Cancel":
raise Cancel
else:
return None
else:
exhaust(_extractUndo())
return undoLevel
def extractUndoSchematic(self, level, box):
if box.volume > 131072:
sch = showProgress("Recording undo...", level.extractZipSchematicIter(box), cancel=True)
else:
sch = level.extractZipSchematic(box)
if sch == "Cancel":
raise Cancel
if sch:
sch.sourcePoint = box.origin
return sch
# represents a single undoable operation
def perform(self, recordUndo=True):
" Perform the operation. Record undo information if recordUndo"
def undo(self):
""" Undo the operation. Ought to leave the Operation in a state where it can be performed again.
Default implementation copies all chunks in undoLevel back into level. Non-chunk-based operations
should override this."""
if self.undoLevel:
def _undo():
yield 0, 0, "Undoing..."
if hasattr(self.level, 'copyChunkFrom'):
for i, (cx, cz) in enumerate(self.undoLevel.allChunks):
self.level.copyChunkFrom(self.undoLevel, cx, cz)
yield i, self.undoLevel.chunkCount, "Copying chunk %s..." % ((cx, cz),)
else:
for i in self.level.copyBlocksFromIter(self.undoLevel, self.undoLevel.bounds, self.undoLevel.sourcePoint, biomes=True):
yield i, self.undoLevel.chunkCount, "Copying..."
if self.undoLevel.chunkCount > 25:
showProgress("Undoing...", _undo())
else:
exhaust(_undo())
self.editor.invalidateChunks(self.undoLevel.allChunks)
def dirtyBox(self):
""" The region modified by the operation.
Return None to indicate no blocks were changed.
"""
return None