From 4985791551fbb37269dc3161592a7cd8ea3b2891 Mon Sep 17 00:00:00 2001 From: David Vierra Date: Tue, 13 Nov 2012 11:06:40 -1000 Subject: [PATCH] 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` --- editortools/brush.py | 38 +++++++----------------------- editortools/clone.py | 29 +++++++---------------- editortools/fill.py | 17 ++++---------- editortools/filter.py | 29 ++++++++--------------- editortools/player.py | 6 +++-- editortools/select.py | 26 ++++++++++----------- editortools/toolbasics.py | 49 ++++++++++++++++++++++++++++----------- 7 files changed, 84 insertions(+), 110 deletions(-) diff --git a/editortools/brush.py b/editortools/brush.py index 00229bd..b15b4ac 100644 --- a/editortools/brush.py +++ b/editortools/brush.py @@ -118,8 +118,7 @@ class Modes: if (cx, cz) in dirtyChunks: return dirtyChunks.add((cx, cz)) - b = BoundingBox((cx * 16, 0, cz * 16), (16, 128, 16)) - undoLevel.copyBlocksFrom(op.level, b, b.origin, create=True) + undoLevel.copyChunkFrom(op.level, cx, cz) doomedBlock = op.level.blockAt(*point) doomedBlockData = op.level.blockDataAt(*point) @@ -176,7 +175,7 @@ class Modes: showProgress("Flood fill...", spread([point]), cancel=True) op.editor.invalidateChunks(dirtyChunks) - op.undoSchematic = undoLevel + op.undoLevel = undoLevel class Replace(Fill): name = "Replace" @@ -371,12 +370,13 @@ class Modes: 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 = {} self.options = options self.editor = editor - self.level = editor.level if isinstance(points[0], (int, float)): points = [points] @@ -395,7 +395,6 @@ class BrushOperation(Operation): boxes = [self.brushMode.brushBoxForPointAndOptions(p, options) for p in points] self._dirtyBox = reduce(lambda a, b: a.union(b), boxes) - undoSchematic = None brushStyles = ["Round", "Square", "Diamond"] # brushModeNames = ["Fill", "Flood Fill", "Replace", "Erode", "Topsoil", "Paste"] # "Smooth", "Flatten", "Raise", "Lower", "Build", "Erode", "Evert"] brushModeClasses = [ @@ -420,29 +419,9 @@ class BrushOperation(Operation): def dirtyBox(self): 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): if recordUndo: - self.undoSchematic = self.extractUndoSchematicFrom(self.level, self._dirtyBox) + self.undoLevel = self.extractUndo(self.level, self._dirtyBox) def _perform(): yield 0, len(self.points), "Applying {0} brush...".format(self.brushMode.name) @@ -849,8 +828,9 @@ class BrushTool(CloneTool): self.draggedPositions = self.draggedPositions[-1:] op = BrushOperation(self.editor, - self.draggedPositions, - self.getBrushOptions()) + self.editor.level, + self.draggedPositions, + self.getBrushOptions()) self.performWithRetry(op) box = op.dirtyBox() diff --git a/editortools/clone.py b/editortools/clone.py index d2d0b68..73a6022 100644 --- a/editortools/clone.py +++ b/editortools/clone.py @@ -79,14 +79,14 @@ class CoordsInput(Widget): class BlockCopyOperation(Operation): def __init__(self, editor, sourceLevel, sourceBox, destLevel, destPoint, copyAir, copyWater): - self.editor = editor + super(BlockCopyOperation, self).__init__(editor, destLevel) self.sourceLevel = sourceLevel self.sourceBox = sourceBox - self.destLevel = destLevel self.destPoint = Vector(*destPoint) self.copyAir = copyAir 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): return BoundingBox(self.destPoint, self.sourceBox.size) @@ -94,16 +94,12 @@ class BlockCopyOperation(Operation): def name(self): 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): - dirtyBox = self.dirtyBox() sourceBox = self.sourceBox - destBox = BoundingBox(self.destPoint, sourceBox.size) if recordUndo: - self.recordUndo() + self.undoLevel = self.extractUndo(self.level, BoundingBox(self.destPoint, self.sourceBox.size)) + blocksToCopy = None if not (self.copyAir and self.copyWater): @@ -116,24 +112,17 @@ class BlockCopyOperation(Operation): blocksToCopy.remove(9) 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) - def bufferSize(self): return 123456 -class CloneOperation (Operation): +class CloneOperation(Operation): def __init__(self, editor, sourceLevel, sourceBox, originSourceBox, destLevel, destPoint, copyAir, copyWater, repeatCount): + super(CloneOperation, self).__init__(editor, destLevel) + self.blockCopyOps = [] dirtyBoxes = [] if repeatCount > 1: # clone tool only diff --git a/editortools/fill.py b/editortools/fill.py index 2c30538..030c671 100644 --- a/editortools/fill.py +++ b/editortools/fill.py @@ -20,8 +20,7 @@ FillSettings.chooseBlockImmediately = FillSettings("Choose Block Immediately", T class BlockFillOperation(Operation): def __init__(self, editor, destLevel, destBox, blockInfo, blocksToReplace): - self.editor = editor - self.destLevel = destLevel + super(BlockFillOperation, self).__init__(editor, destLevel) self.destBox = destBox self.blockInfo = blockInfo self.blocksToReplace = blocksToReplace @@ -31,23 +30,15 @@ class BlockFillOperation(Operation): def perform(self, recordUndo=True): if recordUndo: - self.undoSchematic = self.extractUndoSchematicFrom(self.destLevel, self.destBox) + self.undoLevel = self.extractUndo(self.level, self.destBox) destBox = self.destBox - if self.destLevel.bounds == self.destBox: + if self.level.bounds == self.destBox: 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) - 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): return self.destBox.volume * 2 diff --git a/editortools/filter.py b/editortools/filter.py index 9e36563..d44b600 100644 --- a/editortools/filter.py +++ b/editortools/filter.py @@ -53,14 +53,14 @@ class FilterModuleOptions(Widget): self.pages = pages self.optionDict = {} pageTabContents = [] - + print "Creating options for ", module if hasattr(module, "inputs"): if isinstance(module.inputs, list): for tabData in module.inputs: title, page, pageRect = self.makeTabPage(self.tool, tabData) pages.add_page(title, page) - pages.set_rect(pageRect.union(pages._rect)) + pages.set_rect(pageRect.union(pages._rect)) elif isinstance(module.inputs, tuple): title, page, pageRect = self.makeTabPage(self.tool, module.inputs) pages.add_page(title, page) @@ -75,7 +75,7 @@ class FilterModuleOptions(Widget): if(pages.current_page != None): pages.show_page(pages.current_page) else: - pages.show_page(pages.pages[0]) + pages.show_page(pages.pages[0]) for eachPage in pages.pages: self.optionDict = dict(self.optionDict.items() + eachPage.optionDict.items()) @@ -109,7 +109,7 @@ class FilterModuleOptions(Widget): if a == "strValSize": field = TextField(value=b, width=c) page.optionDict[optionName] = AttrRef(field, 'value') - + row = Row((Label(optionName), field)) rows.append(row) else: @@ -145,7 +145,7 @@ class FilterModuleOptions(Widget): row = Row((Label(optionName), cbox)) rows.append(row) - + elif isinstance(optionType, (int, float)): rows.append(addNumField(self, optionName, optionType)) @@ -164,7 +164,7 @@ class FilterModuleOptions(Widget): elif optionType == "string": field = TextField(value="Input String Here", width=200) page.optionDict[optionName] = AttrRef(field, 'value') - + row = Row((Label(optionName), field)) rows.append(row) @@ -278,29 +278,20 @@ class FilterToolPanel(Panel): 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.level = level self.filter = filter self.options = options def perform(self, recordUndo=True): if recordUndo: - self.recordUndo() + self.undoLevel = self.extractUndo(self.level, self.box) self.filter.perform(self.level, BoundingBox(self.box), self.options) 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): return self.box @@ -370,7 +361,7 @@ class FilterTool(EditorTool): with setWindowCaption("APPLYING FILTER - "): 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.performWithRetry(op) diff --git a/editortools/player.py b/editortools/player.py index 4e5e580..b935d76 100644 --- a/editortools/player.py +++ b/editortools/player.py @@ -24,9 +24,11 @@ class PlayerMoveOperation(Operation): undoPos = None def __init__(self, tool, pos, player="Player", yp=(None, None)): - self.tool, self.pos = tool, pos - self.yp = yp + super(PlayerMoveOperation, self).__init__(tool.editor, tool.editor.level) + self.tool = tool + self.pos = pos self.player = player + self.yp = yp def perform(self, recordUndo=True): try: diff --git a/editortools/select.py b/editortools/select.py index 069a219..f70285b 100644 --- a/editortools/select.py +++ b/editortools/select.py @@ -191,9 +191,10 @@ class SelectionToolPanel(Panel): self.shrink_wrap() -class NudgeBlocksOperation (Operation): - def __init__(self, editor, sourceBox, direction): - self.editor = editor +class NudgeBlocksOperation(Operation): + def __init__(self, editor, level, sourceBox, direction): + super(NudgeBlocksOperation, self).__init__(editor, level) + self.sourceBox = sourceBox self.destBox = BoundingBox(sourceBox.origin + direction, sourceBox.size) self.nudgeSelection = NudgeSelectionOperation(editor.selectionTool, direction) @@ -207,7 +208,7 @@ class NudgeBlocksOperation (Operation): if tempSchematic: dirtyBox = self.dirtyBox() if recordUndo: - self.undoSchematic = self.extractUndoSchematicFrom(level, dirtyBox) + self.undoLevel = self.extractUndo(level, dirtyBox) level.fillBlocks(self.sourceBox, level.materials.Air) level.removeTileEntitiesInBox(self.sourceBox) @@ -221,12 +222,8 @@ class NudgeBlocksOperation (Operation): self.nudgeSelection.perform(recordUndo) def undo(self): - if self.undoSchematic: - 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() + super(NudgeBlocksOperation, self).undo() + self.nudgeSelection.undo() class SelectionTool(EditorTool): @@ -388,7 +385,7 @@ class SelectionTool(EditorTool): def nudgeBlocks(self, dir): if key.get_mods() & KMOD_SHIFT: 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.editor.addOperation(op) @@ -1018,7 +1015,7 @@ class SelectionTool(EditorTool): level.addEntities(self.undoEntities) editor.renderer.invalidateEntitiesInBox(box) - op = DeleteEntitiesOperation() + op = DeleteEntitiesOperation(self.editor, self.editor.level) self.performWithRetry(op, recordUndo) if recordUndo: self.editor.addOperation(op) @@ -1091,6 +1088,7 @@ class SelectionOperation(Operation): changedLevel = False def __init__(self, selectionTool, points): + super(SelectionOperation, self).__init__(selectionTool.editor, selectionTool.editor.level) self.selectionTool = selectionTool self.points = points @@ -1105,14 +1103,14 @@ class SelectionOperation(Operation): self.points = points -class NudgeSelectionOperation (Operation): +class NudgeSelectionOperation(Operation): changedLevel = False def __init__(self, selectionTool, direction): + super(NudgeSelectionOperation, self).__init__(selectionTool.editor, selectionTool.editor.level) self.selectionTool = selectionTool self.direction = direction self.oldPoints = selectionTool.getSelectionPoints() - self.newPoints = [p + direction for p in self.oldPoints] def perform(self, recordUndo=True): diff --git a/editortools/toolbasics.py b/editortools/toolbasics.py index 50c058d..9f22c3b 100644 --- a/editortools/toolbasics.py +++ b/editortools/toolbasics.py @@ -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 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.""" +import atexit +import shutil +import tempfile from OpenGL.GL import * from pymclevel import * @@ -96,19 +99,27 @@ class NudgeButton(GLBackground): class Operation(object): changedLevel = True + undoLevel = None - def extractUndoSchematicFrom(self, level, box): - if box.volume > 131072: - sch = showProgress("Recording undo...", level.extractAnySchematicIter(box), cancel=True) - else: - sch = level.extractAnySchematic(box) + def __init__(self, editor, level): + self.editor = editor + self.level = level - if sch == "Canceled": - raise Cancel - if sch is None: - sch = MCSchematic((0, 0, 0)) + def extractUndo(self, level, box): + undoPath = tempfile.mkdtemp("mceditundo") + undoLevel = MCInfdevOldLevel(undoPath, create=True) + 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 def perform(self, recordUndo=True): @@ -116,9 +127,21 @@ class Operation(object): def undo(self): """ 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. """ - pass - undoSchematic = None + 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..." + 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): """ The region modified by the operation.