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:
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()

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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):

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
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.