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:
parent
24e0c8a9b2
commit
4985791551
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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.
|
||||
|
Reference in New Issue
Block a user