Added: Use copyChunkFrom to record undo information.

Operation now has `editor` and `level` as required arguments to init. Operation has a default `undo` implementation that copies all chunks from `undoLevel`. `undoSchematic` renamed to `undoLevel`
This commit is contained in:
David Vierra 2012-11-13 11:06:40 -10:00
parent 24e0c8a9b2
commit 4985791551
7 changed files with 84 additions and 110 deletions

View File

@ -118,8 +118,7 @@ class Modes:
if (cx, cz) in dirtyChunks: if (cx, cz) in dirtyChunks:
return return
dirtyChunks.add((cx, cz)) dirtyChunks.add((cx, cz))
b = BoundingBox((cx * 16, 0, cz * 16), (16, 128, 16)) undoLevel.copyChunkFrom(op.level, cx, cz)
undoLevel.copyBlocksFrom(op.level, b, b.origin, create=True)
doomedBlock = op.level.blockAt(*point) doomedBlock = op.level.blockAt(*point)
doomedBlockData = op.level.blockDataAt(*point) doomedBlockData = op.level.blockDataAt(*point)
@ -176,7 +175,7 @@ class Modes:
showProgress("Flood fill...", spread([point]), cancel=True) showProgress("Flood fill...", spread([point]), cancel=True)
op.editor.invalidateChunks(dirtyChunks) op.editor.invalidateChunks(dirtyChunks)
op.undoSchematic = undoLevel op.undoLevel = undoLevel
class Replace(Fill): class Replace(Fill):
name = "Replace" name = "Replace"
@ -371,12 +370,13 @@ class Modes:
class BrushOperation(Operation): class BrushOperation(Operation):
def __init__(self, editor, points, options): def __init__(self, editor, level, points, options):
super(BrushOperation, self).__init__(editor, level)
# if options is None: options = {} # if options is None: options = {}
self.options = options self.options = options
self.editor = editor self.editor = editor
self.level = editor.level
if isinstance(points[0], (int, float)): if isinstance(points[0], (int, float)):
points = [points] points = [points]
@ -395,7 +395,6 @@ class BrushOperation(Operation):
boxes = [self.brushMode.brushBoxForPointAndOptions(p, options) for p in points] boxes = [self.brushMode.brushBoxForPointAndOptions(p, options) for p in points]
self._dirtyBox = reduce(lambda a, b: a.union(b), boxes) self._dirtyBox = reduce(lambda a, b: a.union(b), boxes)
undoSchematic = None
brushStyles = ["Round", "Square", "Diamond"] brushStyles = ["Round", "Square", "Diamond"]
# brushModeNames = ["Fill", "Flood Fill", "Replace", "Erode", "Topsoil", "Paste"] # "Smooth", "Flatten", "Raise", "Lower", "Build", "Erode", "Evert"] # brushModeNames = ["Fill", "Flood Fill", "Replace", "Erode", "Topsoil", "Paste"] # "Smooth", "Flatten", "Raise", "Lower", "Build", "Erode", "Evert"]
brushModeClasses = [ brushModeClasses = [
@ -420,29 +419,9 @@ class BrushOperation(Operation):
def dirtyBox(self): def dirtyBox(self):
return self._dirtyBox return self._dirtyBox
def undo(self):
if self.undoSchematic:
self.level.removeEntitiesInBox(self._dirtyBox)
self.level.removeTileEntitiesInBox(self._dirtyBox)
if self.brushMode.name == "Flood Fill":
i = self.level.copyBlocksFromIter(self.undoSchematic,
self.undoSchematic.bounds,
self.undoSchematic.bounds.origin
)
else:
i = self.level.copyBlocksFromIter(self.undoSchematic,
BoundingBox((0, 0, 0), self._dirtyBox.size),
self._dirtyBox.origin
)
showProgress("Undoing brush...", i)
self.editor.invalidateChunks(self.undoSchematic.allChunks)
def perform(self, recordUndo=True): def perform(self, recordUndo=True):
if recordUndo: if recordUndo:
self.undoSchematic = self.extractUndoSchematicFrom(self.level, self._dirtyBox) self.undoLevel = self.extractUndo(self.level, self._dirtyBox)
def _perform(): def _perform():
yield 0, len(self.points), "Applying {0} brush...".format(self.brushMode.name) yield 0, len(self.points), "Applying {0} brush...".format(self.brushMode.name)
@ -849,6 +828,7 @@ class BrushTool(CloneTool):
self.draggedPositions = self.draggedPositions[-1:] self.draggedPositions = self.draggedPositions[-1:]
op = BrushOperation(self.editor, op = BrushOperation(self.editor,
self.editor.level,
self.draggedPositions, self.draggedPositions,
self.getBrushOptions()) self.getBrushOptions())
self.performWithRetry(op) self.performWithRetry(op)

View File

@ -79,14 +79,14 @@ class CoordsInput(Widget):
class BlockCopyOperation(Operation): class BlockCopyOperation(Operation):
def __init__(self, editor, sourceLevel, sourceBox, destLevel, destPoint, copyAir, copyWater): def __init__(self, editor, sourceLevel, sourceBox, destLevel, destPoint, copyAir, copyWater):
self.editor = editor super(BlockCopyOperation, self).__init__(editor, destLevel)
self.sourceLevel = sourceLevel self.sourceLevel = sourceLevel
self.sourceBox = sourceBox self.sourceBox = sourceBox
self.destLevel = destLevel
self.destPoint = Vector(*destPoint) self.destPoint = Vector(*destPoint)
self.copyAir = copyAir self.copyAir = copyAir
self.copyWater = copyWater self.copyWater = copyWater
self.sourceBox, self.destPoint = block_copy.adjustCopyParameters(self.destLevel, self.sourceLevel, self.sourceBox, self.destPoint) self.sourceBox, self.destPoint = block_copy.adjustCopyParameters(self.level, self.sourceLevel, self.sourceBox,
self.destPoint)
def dirtyBox(self): def dirtyBox(self):
return BoundingBox(self.destPoint, self.sourceBox.size) return BoundingBox(self.destPoint, self.sourceBox.size)
@ -94,16 +94,12 @@ class BlockCopyOperation(Operation):
def name(self): def name(self):
return "Copy {0} blocks".format(self.sourceBox.volume) return "Copy {0} blocks".format(self.sourceBox.volume)
def recordUndo(self):
self.undoSchematic = self.extractUndoSchematicFrom(self.destLevel, BoundingBox(self.destPoint, self.sourceBox.size))
def perform(self, recordUndo=True): def perform(self, recordUndo=True):
dirtyBox = self.dirtyBox()
sourceBox = self.sourceBox sourceBox = self.sourceBox
destBox = BoundingBox(self.destPoint, sourceBox.size)
if recordUndo: if recordUndo:
self.recordUndo() self.undoLevel = self.extractUndo(self.level, BoundingBox(self.destPoint, self.sourceBox.size))
blocksToCopy = None blocksToCopy = None
if not (self.copyAir and self.copyWater): if not (self.copyAir and self.copyWater):
@ -116,16 +112,7 @@ class BlockCopyOperation(Operation):
blocksToCopy.remove(9) blocksToCopy.remove(9)
with setWindowCaption("Copying - "): with setWindowCaption("Copying - "):
i = self.destLevel.copyBlocksFromIter(self.sourceLevel, self.sourceBox, self.destPoint, blocksToCopy, create=True) i = self.level.copyBlocksFromIter(self.sourceLevel, self.sourceBox, self.destPoint, blocksToCopy, create=True)
showProgress("Copying {0:n} blocks...".format(self.sourceBox.volume), i)
def undo(self):
if self.undoSchematic:
self.destLevel.removeEntitiesInBox(BoundingBox(self.destPoint, self.sourceBox.size))
self.destLevel.removeTileEntitiesInBox(BoundingBox(self.destPoint, self.sourceBox.size))
with setWindowCaption("Undoing - "):
i = self.destLevel.copyBlocksFromIter(self.undoSchematic, BoundingBox((0, 0, 0), self.sourceBox.size), self.destPoint, create=True)
showProgress("Copying {0:n} blocks...".format(self.sourceBox.volume), i) showProgress("Copying {0:n} blocks...".format(self.sourceBox.volume), i)
def bufferSize(self): def bufferSize(self):
@ -134,6 +121,8 @@ class BlockCopyOperation(Operation):
class CloneOperation(Operation): class CloneOperation(Operation):
def __init__(self, editor, sourceLevel, sourceBox, originSourceBox, destLevel, destPoint, copyAir, copyWater, repeatCount): def __init__(self, editor, sourceLevel, sourceBox, originSourceBox, destLevel, destPoint, copyAir, copyWater, repeatCount):
super(CloneOperation, self).__init__(editor, destLevel)
self.blockCopyOps = [] self.blockCopyOps = []
dirtyBoxes = [] dirtyBoxes = []
if repeatCount > 1: # clone tool only if repeatCount > 1: # clone tool only

View File

@ -20,8 +20,7 @@ FillSettings.chooseBlockImmediately = FillSettings("Choose Block Immediately", T
class BlockFillOperation(Operation): class BlockFillOperation(Operation):
def __init__(self, editor, destLevel, destBox, blockInfo, blocksToReplace): def __init__(self, editor, destLevel, destBox, blockInfo, blocksToReplace):
self.editor = editor super(BlockFillOperation, self).__init__(editor, destLevel)
self.destLevel = destLevel
self.destBox = destBox self.destBox = destBox
self.blockInfo = blockInfo self.blockInfo = blockInfo
self.blocksToReplace = blocksToReplace self.blocksToReplace = blocksToReplace
@ -31,23 +30,15 @@ class BlockFillOperation(Operation):
def perform(self, recordUndo=True): def perform(self, recordUndo=True):
if recordUndo: if recordUndo:
self.undoSchematic = self.extractUndoSchematicFrom(self.destLevel, self.destBox) self.undoLevel = self.extractUndo(self.level, self.destBox)
destBox = self.destBox destBox = self.destBox
if self.destLevel.bounds == self.destBox: if self.level.bounds == self.destBox:
destBox = None destBox = None
fill = self.destLevel.fillBlocksIter(destBox, self.blockInfo, blocksToReplace=self.blocksToReplace) fill = self.level.fillBlocksIter(destBox, self.blockInfo, blocksToReplace=self.blocksToReplace)
showProgress("Replacing blocks...", fill, cancel=True) showProgress("Replacing blocks...", fill, cancel=True)
def undo(self):
if self.undoSchematic:
self.destLevel.removeEntitiesInBox(self.destBox)
self.destLevel.removeTileEntitiesInBox(self.destBox)
with setWindowCaption("Undoing - "):
i = self.destLevel.copyBlocksFromIter(self.undoSchematic, BoundingBox((0, 0, 0), self.destBox.size), self.destBox.origin)
showProgress("Copying {0:n} blocks...".format(self.destBox.volume), i)
def bufferSize(self): def bufferSize(self):
return self.destBox.volume * 2 return self.destBox.volume * 2

View File

@ -278,29 +278,20 @@ class FilterToolPanel(Panel):
class FilterOperation(Operation): class FilterOperation(Operation):
def __init__(self, level, box, filter, options): def __init__(self, editor, level, box, filter, options):
super(FilterOperation, self).__init__(editor, level)
self.box = box self.box = box
self.level = level
self.filter = filter self.filter = filter
self.options = options self.options = options
def perform(self, recordUndo=True): def perform(self, recordUndo=True):
if recordUndo: if recordUndo:
self.recordUndo() self.undoLevel = self.extractUndo(self.level, self.box)
self.filter.perform(self.level, BoundingBox(self.box), self.options) self.filter.perform(self.level, BoundingBox(self.box), self.options)
pass pass
def recordUndo(self):
self.undoSchematic = self.extractUndoSchematicFrom(self.level, self.box)
def undo(self):
if self.undoSchematic:
self.level.removeEntitiesInBox(self.box)
self.level.removeTileEntitiesInBox(self.box)
self.level.copyBlocksFrom(self.undoSchematic, BoundingBox((0, 0, 0), self.box.size), self.box.origin)
def dirtyBox(self): def dirtyBox(self):
return self.box return self.box
@ -370,7 +361,7 @@ class FilterTool(EditorTool):
with setWindowCaption("APPLYING FILTER - "): with setWindowCaption("APPLYING FILTER - "):
filterModule = self.filterModules[self.panel.filterSelect.selectedChoice] filterModule = self.filterModules[self.panel.filterSelect.selectedChoice]
op = FilterOperation(self.editor.level, self.selectionBox(), filterModule, self.panel.filterOptionsPanel.options) op = FilterOperation(self.editor, self.editor.level, self.selectionBox(), filterModule, self.panel.filterOptionsPanel.options)
self.editor.level.showProgress = showProgress self.editor.level.showProgress = showProgress
self.performWithRetry(op) self.performWithRetry(op)

View File

@ -24,9 +24,11 @@ class PlayerMoveOperation(Operation):
undoPos = None undoPos = None
def __init__(self, tool, pos, player="Player", yp=(None, None)): def __init__(self, tool, pos, player="Player", yp=(None, None)):
self.tool, self.pos = tool, pos super(PlayerMoveOperation, self).__init__(tool.editor, tool.editor.level)
self.yp = yp self.tool = tool
self.pos = pos
self.player = player self.player = player
self.yp = yp
def perform(self, recordUndo=True): def perform(self, recordUndo=True):
try: try:

View File

@ -192,8 +192,9 @@ class SelectionToolPanel(Panel):
class NudgeBlocksOperation(Operation): class NudgeBlocksOperation(Operation):
def __init__(self, editor, sourceBox, direction): def __init__(self, editor, level, sourceBox, direction):
self.editor = editor super(NudgeBlocksOperation, self).__init__(editor, level)
self.sourceBox = sourceBox self.sourceBox = sourceBox
self.destBox = BoundingBox(sourceBox.origin + direction, sourceBox.size) self.destBox = BoundingBox(sourceBox.origin + direction, sourceBox.size)
self.nudgeSelection = NudgeSelectionOperation(editor.selectionTool, direction) self.nudgeSelection = NudgeSelectionOperation(editor.selectionTool, direction)
@ -207,7 +208,7 @@ class NudgeBlocksOperation (Operation):
if tempSchematic: if tempSchematic:
dirtyBox = self.dirtyBox() dirtyBox = self.dirtyBox()
if recordUndo: if recordUndo:
self.undoSchematic = self.extractUndoSchematicFrom(level, dirtyBox) self.undoLevel = self.extractUndo(level, dirtyBox)
level.fillBlocks(self.sourceBox, level.materials.Air) level.fillBlocks(self.sourceBox, level.materials.Air)
level.removeTileEntitiesInBox(self.sourceBox) level.removeTileEntitiesInBox(self.sourceBox)
@ -221,11 +222,7 @@ class NudgeBlocksOperation (Operation):
self.nudgeSelection.perform(recordUndo) self.nudgeSelection.perform(recordUndo)
def undo(self): def undo(self):
if self.undoSchematic: super(NudgeBlocksOperation, self).undo()
self.editor.level.removeTileEntitiesInBox(self.destBox)
self.editor.level.removeEntitiesInBox(self.destBox)
self.editor.level.copyBlocksFrom(self.undoSchematic, self.undoSchematic.bounds, self.dirtyBox().origin)
self.editor.invalidateBox(self.dirtyBox())
self.nudgeSelection.undo() self.nudgeSelection.undo()
@ -388,7 +385,7 @@ class SelectionTool(EditorTool):
def nudgeBlocks(self, dir): def nudgeBlocks(self, dir):
if key.get_mods() & KMOD_SHIFT: if key.get_mods() & KMOD_SHIFT:
dir = dir * (16, 16, 16) dir = dir * (16, 16, 16)
op = NudgeBlocksOperation(self.editor, self.selectionBox(), dir) op = NudgeBlocksOperation(self.editor, self.editor.level, self.selectionBox(), dir)
self.performWithRetry(op) self.performWithRetry(op)
self.editor.addOperation(op) self.editor.addOperation(op)
@ -1018,7 +1015,7 @@ class SelectionTool(EditorTool):
level.addEntities(self.undoEntities) level.addEntities(self.undoEntities)
editor.renderer.invalidateEntitiesInBox(box) editor.renderer.invalidateEntitiesInBox(box)
op = DeleteEntitiesOperation() op = DeleteEntitiesOperation(self.editor, self.editor.level)
self.performWithRetry(op, recordUndo) self.performWithRetry(op, recordUndo)
if recordUndo: if recordUndo:
self.editor.addOperation(op) self.editor.addOperation(op)
@ -1091,6 +1088,7 @@ class SelectionOperation(Operation):
changedLevel = False changedLevel = False
def __init__(self, selectionTool, points): def __init__(self, selectionTool, points):
super(SelectionOperation, self).__init__(selectionTool.editor, selectionTool.editor.level)
self.selectionTool = selectionTool self.selectionTool = selectionTool
self.points = points self.points = points
@ -1109,10 +1107,10 @@ class NudgeSelectionOperation (Operation):
changedLevel = False changedLevel = False
def __init__(self, selectionTool, direction): def __init__(self, selectionTool, direction):
super(NudgeSelectionOperation, self).__init__(selectionTool.editor, selectionTool.editor.level)
self.selectionTool = selectionTool self.selectionTool = selectionTool
self.direction = direction self.direction = direction
self.oldPoints = selectionTool.getSelectionPoints() self.oldPoints = selectionTool.getSelectionPoints()
self.newPoints = [p + direction for p in self.oldPoints] self.newPoints = [p + direction for p in self.oldPoints]
def perform(self, recordUndo=True): def perform(self, recordUndo=True):

View File

@ -11,6 +11,9 @@ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.""" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE."""
import atexit
import shutil
import tempfile
from OpenGL.GL import * from OpenGL.GL import *
from pymclevel import * from pymclevel import *
@ -96,19 +99,27 @@ class NudgeButton(GLBackground):
class Operation(object): class Operation(object):
changedLevel = True changedLevel = True
undoLevel = None
def extractUndoSchematicFrom(self, level, box): def __init__(self, editor, level):
if box.volume > 131072: self.editor = editor
sch = showProgress("Recording undo...", level.extractAnySchematicIter(box), cancel=True) self.level = level
else:
sch = level.extractAnySchematic(box)
if sch == "Canceled": def extractUndo(self, level, box):
raise Cancel undoPath = tempfile.mkdtemp("mceditundo")
if sch is None: undoLevel = MCInfdevOldLevel(undoPath, create=True)
sch = MCSchematic((0, 0, 0)) atexit.register(shutil.rmtree, undoPath, True)
return sch def _extractUndo():
yield 0, 0, "Recording undo..."
for i, (cx, cz) in enumerate(box.chunkPositions):
undoLevel.copyChunkFrom(level, cx, cz)
yield i, box.chunkCount, "Copying chunk %s..." % ((cx, cz),)
undoLevel.saveInPlace()
showProgress("Recording undo...", _extractUndo())
return undoLevel
# represents a single undoable operation # represents a single undoable operation
def perform(self, recordUndo=True): def perform(self, recordUndo=True):
@ -116,9 +127,21 @@ class Operation(object):
def undo(self): def undo(self):
""" Undo the operation. Ought to leave the Operation in a state where it can be performed again. """ Undo the operation. Ought to leave the Operation in a state where it can be performed again.
Returns a BoundingBox containing all of the modified areas of the level. """ Default implementation copies all chunks in undoLevel back into level. Non-chunk-based operations
pass should override this."""
undoSchematic = None
if self.undoLevel:
def _undo():
yield 0, 0, "Undoing..."
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),)
showProgress("Undoing...", _undo())
self.editor.invalidateChunks(self.undoLevel.allChunks)
def dirtyBox(self): def dirtyBox(self):
""" The region modified by the operation. """ The region modified by the operation.