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/select.py

1123 lines
41 KiB
Python

"""Copyright (c) 2010-2012 David Rio Vierra
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
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."""
from collections import defaultdict
from pymclevel.box import Vector
from fill import FillTool, BlockFillOperation
import tempfile
from toolbasics import *
SelectSettings = config.Settings("Selection")
SelectSettings.showPreviousSelection = SelectSettings("Show Previous Selection", True)
SelectSettings.color = SelectSettings("Color", "teal")
ColorSettings = config.Settings("Selection Colors")
ColorSettings.defaultColors = {}
class ColorSetting(config.Setting):
def __init__(self, section, name, dtype, default):
super(ColorSetting, self).__init__(section, name, dtype, default)
ColorSettings.defaultColors[name] = self
def set(self, val):
values = str(tuple(val))[1:-1]
super(ColorSetting, self).set(values)
def get(self):
colorValues = super(ColorSetting, self).get()
return parseValues(colorValues)
ColorSettings.Setting = ColorSetting
def parseValues(colorValues):
if colorValues is None:
return 1., 1., 1.
try:
values = colorValues.split(",")
values = [(min(max(float(x), 0.0), 1.0)) for x in values]
except:
values = (1.0, 1.0, 1.0)
return tuple(values)
ColorSettings("white", (1.0, 1.0, 1.0))
ColorSettings("blue", (0.75, 0.75, 1.0))
ColorSettings("green", (0.75, 1.0, 0.75))
ColorSettings("red", (1.0, 0.75, 0.75))
ColorSettings("teal", (0.75, 1.0, 1.0))
ColorSettings("pink", (1.0, 0.75, 1.0))
ColorSettings("yellow", (1.0, 1.0, 0.75))
ColorSettings("grey", (0.6, 0.6, 0.6))
ColorSettings("black", (0.0, 0.0, 0.0))
def GetSelectionColor(colorWord=None):
if colorWord is None:
colorWord = SelectSettings.color.get()
colorValues = config.config.get("Selection Colors", colorWord)
return parseValues(colorValues)
class SelectionToolOptions(ToolOptions):
def updateColors(self):
names = [name.lower() for (name, value) in config.config.items("Selection Colors")]
self.colorPopupButton.choices = [name.capitalize() for name in names]
color = SelectSettings.color.get()
self.colorPopupButton.choiceIndex = names.index(color.lower())
def __init__(self, tool):
Panel.__init__(self)
self.tool = tool
self.colorPopupButton = ChoiceButton([], choose=self.colorChanged)
self.updateColors()
colorRow = Row((Label("Color: ", align="r"), self.colorPopupButton))
okButton = Button("OK", action=self.dismiss)
showPreviousRow = CheckBoxLabel("Show Previous Selection", ref=AttrRef(tool, 'showPreviousSelection'))
def set_colorvalue(ch):
i = "RGB".index(ch)
def _set(val):
choice = self.colorPopupButton.selectedChoice
values = GetSelectionColor(choice)
values = values[:i] + (val / 255.0,) + values[i + 1:]
setting = str(values)[1:-1]
config.config.set("Selection Colors", choice, setting)
self.colorChanged()
return _set
def get_colorvalue(ch):
i = "RGB".index(ch)
def _get():
return int(GetSelectionColor()[i] * 255)
return _get
colorValuesInputs = [IntInputRow(ch + ":", get_value=get_colorvalue(ch),
set_value=set_colorvalue(ch),
min=0, max=255)
for ch in "RGB"]
colorValuesRow = Row(colorValuesInputs)
col = Column((Label("Selection Options"), colorRow, colorValuesRow, showPreviousRow, okButton))
self.add(col)
self.shrink_wrap()
def colorChanged(self):
SelectSettings.color.set(self.colorPopupButton.selectedChoice)
self.tool.updateSelectionColor()
class SelectionToolPanel(Panel):
def __init__(self, tool):
Panel.__init__(self)
self.tool = tool
nudgeBlocksButton = NudgeButton()
nudgeBlocksButton.nudge = tool.nudgeBlocks
nudgeBlocksButton.bg_color = (0.3, 1.0, 0.3, 0.35)
self.nudgeBlocksButton = nudgeBlocksButton
nudgeSelectionButton = NudgeButton()
nudgeSelectionButton.nudge = tool.nudgeSelection
nudgeSelectionButton.bg_color = tool.selectionColor + (0.7,)
deleteBlocksButton = Button("Delete Blocks", action=self.tool.deleteBlocks)
deleteBlocksButton.tooltipText = "Fill the selection with Air. Shortcut: DELETE"
deleteEntitiesButton = Button("Delete Entities", action=self.tool.deleteEntities)
deleteEntitiesButton.tooltipText = "Remove all entities within the selection"
# deleteTileEntitiesButton = Button("Delete TileEntities", action=self.tool.deleteTileEntities)
analyzeButton = Button("Analyze", action=self.tool.analyzeSelection)
analyzeButton.tooltipText = "Count the different blocks and entities in the selection and display the totals."
cutButton = Button("Cut", action=self.tool.cutSelection)
cutButton.tooltipText = "Take a copy of all blocks and entities within the selection, then delete everything within the selection. Shortcut: {0}-X".format(mcplatform.cmd_name)
copyButton = Button("Copy", action=self.tool.copySelection)
copyButton.tooltipText = "Take a copy of all blocks and entities within the selection. Shortcut: {0}-C".format(mcplatform.cmd_name)
pasteButton = Button("Paste", action=self.tool.editor.pasteSelection)
pasteButton.tooltipText = "Import the last item taken by Cut or Copy. Shortcut: {0}-V".format(mcplatform.cmd_name)
exportButton = Button("Export", action=self.tool.exportSelection)
exportButton.tooltipText = "Export the selection to a .schematic file. Shortcut: {0}-E".format(mcplatform.cmd_name)
selectButton = Button("Select Chunks")
selectButton.tooltipText = "Expand the selection to the edges of the chunks within"
selectButton.action = tool.selectChunks
selectButton.highlight_color = (0, 255, 0)
deselectButton = Button("Deselect")
deselectButton.tooltipText = "Remove the selection. Shortcut: {0}-D".format(mcplatform.cmd_name)
deselectButton.action = tool.deselect
deselectButton.highlight_color = (0, 255, 0)
nudgeRow = Row((nudgeBlocksButton, nudgeSelectionButton))
buttonsColumn = (
nudgeRow,
deselectButton,
selectButton,
deleteBlocksButton,
deleteEntitiesButton,
analyzeButton,
cutButton,
copyButton,
pasteButton,
exportButton,
)
buttonsColumn = Column(buttonsColumn)
self.add(buttonsColumn)
self.shrink_wrap()
class NudgeBlocksOperation (Operation):
def __init__(self, editor, sourceBox, direction):
self.editor = editor
self.sourceBox = sourceBox
self.destBox = BoundingBox(sourceBox.origin + direction, sourceBox.size)
self.nudgeSelection = NudgeSelectionOperation(editor.selectionTool, direction)
def dirtyBox(self):
return self.sourceBox.union(self.destBox)
def perform(self, recordUndo=True):
level = self.editor.level
tempSchematic = level.extractSchematic(self.sourceBox)
if tempSchematic:
dirtyBox = self.dirtyBox()
if recordUndo:
self.undoSchematic = self.extractUndoSchematicFrom(level, dirtyBox)
level.fillBlocks(self.sourceBox, level.materials.Air)
level.removeTileEntitiesInBox(self.sourceBox)
level.removeTileEntitiesInBox(self.destBox)
level.removeEntitiesInBox(self.sourceBox)
level.removeEntitiesInBox(self.destBox)
level.copyBlocksFrom(tempSchematic, tempSchematic.bounds, self.destBox.origin)
self.editor.invalidateBox(dirtyBox)
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()
class SelectionTool(EditorTool):
# selectionColor = (1.0, .9, .9)
color = (0.7, 0., 0.7)
surfaceBuild = False
toolIconName = "selection"
tooltipText = "Select\nRight-click for options"
bottomLeftPoint = topRightPoint = None
bottomLeftColor = (0., 0., 1.)
bottomLeftSelectionColor = (0.75, 0.62, 1.0)
topRightColor = (0.89, 0.89, 0.35)
topRightSelectionColor = (1, 0.99, 0.65)
nudgePanel = None
def __init__(self, editor):
self.editor = editor
editor.selectionTool = self
self.selectionPoint = None
self.optionsPanel = SelectionToolOptions(self)
self.updateSelectionColor()
# --- Tooltips ---
def describeBlockAt(self, pos):
blockID = self.editor.level.blockAt(*pos)
blockdata = self.editor.level.blockDataAt(*pos)
text = "X: {pos[0]}\nY: {pos[1]}\nZ: {pos[2]}\n".format(pos=pos)
text += "L: {0} S: {1}\n".format(self.editor.level.blockLightAt(*pos), self.editor.level.skylightAt(*pos))
text += "{name} ({bid}:{bdata})\n".format(name=self.editor.level.materials.names[blockID][blockdata], bid=blockID, pos=pos, bdata=blockdata)
t = self.editor.level.tileEntityAt(*pos)
if t:
text += "TileEntity:\n"
try:
text += "{id}: {pos}\n".format(id=t["id"].value, pos=[t[a].value for a in "xyz"])
except Exception, e:
text += repr(e)
if "Items" not in t:
text += str(t)
return text
@property
def worldTooltipText(self):
pos, face = self.editor.blockFaceUnderCursor
if pos is None:
return
try:
size = None
box = self.selectionBoxInProgress()
if box:
size = "{s[0]} W x {s[2]} L x {s[1]} H".format(s=box.size)
text = size
if key.get_mods() & KMOD_ALT:
if size:
return size
elif self.dragResizeFace is not None:
return None
else:
return self.describeBlockAt(pos)
return text.strip()
else:
return self.worldTooltipForBlock(pos) or size
except Exception, e:
return repr(e)
def worldTooltipForBlock(self, pos):
x, y, z = pos
cx, cz = x / 16, z / 16
if isinstance(self.editor.level, MCInfdevOldLevel):
if y == 0:
try:
chunk = self.editor.level.getChunk(cx, cz)
except ChunkNotPresent:
return "Chunk not present."
if not any(chunk.HeightMap):
if self.editor.level.blockAt(x, y, z):
return "Chunk HeightMap is incorrect! Please relight this chunk as soon as possible!"
else:
return "Chunk is present and filled with air."
block = self.editor.level.blockAt(*pos)
if block in (alphaMaterials.Chest.ID,
alphaMaterials.Furnace.ID,
alphaMaterials.LitFurnace.ID,
alphaMaterials.Dispenser.ID):
t = self.editor.level.tileEntityAt(*pos)
if t:
containerID = t["id"].value
if "Items" in t:
items = t["Items"]
d = defaultdict(int)
for item in items:
if "id" in item and "Count" in item:
d[item["id"].value] += item["Count"].value
if len(d):
items = sorted((v, k) for (k, v) in d.iteritems())
try:
top = pymclevel.items.items.findItem(items[0][1]).name
except Exception, e:
top = repr(e)
return "{0} contains {len} items. (Mostly {top}) \n\nDouble-click to edit {0}.".format(containerID, len=len(d), top=top)
else:
return "Empty {0}. \n\nDouble-click to edit {0}.".format(containerID)
else:
return "Double-click to initialize the {0}.".format(alphaMaterials.names[block][0])
if block == alphaMaterials.MonsterSpawner.ID:
t = self.editor.level.tileEntityAt(*pos)
if t:
id = t["EntityId"].value
else:
id = "[Undefined]"
return "{id} spawner. \n\nDouble-click to edit spawner.".format(id=id)
if block in (alphaMaterials.Sign.ID,
alphaMaterials.WallSign.ID):
t = self.editor.level.tileEntityAt(*pos)
if t:
signtext = u"\n".join(t["Text" + str(x)].value for x in range(1, 5))
else:
signtext = "Undefined"
return "Sign text: \n" + signtext + "\n\n" + "Double-click to edit sign."
absentTexture = (self.editor.level.materials.blockTextures[block, 0, 0] == materials.NOTEX).all()
if absentTexture:
return self.describeBlockAt(pos)
@alertException
def selectChunks(self):
box = self.selectionBox()
newBox = BoundingBox((box.mincx << 4, 0, box.mincz << 4), (box.maxcx - box.mincx << 4, self.editor.level.Height, box.maxcz - box.mincz << 4))
self.editor.selectionTool.setSelection(newBox)
def updateSelectionColor(self):
self.selectionColor = GetSelectionColor()
from albow import theme
theme.root.sel_color = tuple(int(x * 112) for x in self.selectionColor)
# --- Nudge functions ---
@alertException
def nudgeBlocks(self, dir):
if key.get_mods() & KMOD_SHIFT:
dir = dir * (16, 16, 16)
op = NudgeBlocksOperation(self.editor, self.selectionBox(), dir)
self.performWithRetry(op)
self.editor.addOperation(op)
self.editor.addUnsavedEdit()
def nudgeSelection(self, dir):
if key.get_mods() & KMOD_SHIFT:
dir = dir * (16, 16, 16)
points = self.getSelectionPoints()
bounds = self.editor.level.bounds
if not all((p + dir) in bounds for p in points):
return
op = NudgeSelectionOperation(self, dir)
self.performWithRetry(op)
# self.editor.addOperation(op)
def nudgePoint(self, p, n):
if self.selectionBox() is None:
return
if key.get_mods() & KMOD_SHIFT:
n = n * (16, 16, 16)
self.setSelectionPoint(p, self.getSelectionPoint(p) + n)
def nudgeBottomLeft(self, n):
return self.nudgePoint(0, n)
def nudgeTopRight(self, n):
return self.nudgePoint(1, n)
# --- Panel functions ---
def sizeLabelText(self):
size = self.selectionSize()
if self.dragResizeFace is not None:
size = self.draggingSelectionBox().size
return "{0}W x {2}L x {1}H".format(*size)
def showPanel(self):
if self.selectionBox() is None:
return
if self.nudgePanel is None:
self.nudgePanel = Panel()
self.nudgePanel.bg_color = map(lambda x: x * 0.5, self.selectionColor) + [0.5, ]
self.bottomLeftNudge = bottomLeftNudge = NudgeButton()
bottomLeftNudge.nudge = self.nudgeBottomLeft
bottomLeftNudge.anchor = "brwh"
bottomLeftNudge.bg_color = self.bottomLeftColor + (0.33,)
self.topRightNudge = topRightNudge = NudgeButton()
topRightNudge.nudge = self.nudgeTopRight
topRightNudge.anchor = "blwh"
topRightNudge.bg_color = self.topRightColor + (0.33,)
self.nudgeRow = Row((bottomLeftNudge, topRightNudge))
self.nudgeRow.anchor = "blrh"
self.nudgePanel.add(self.nudgeRow)
self.editor.add(self.nudgePanel)
if hasattr(self, 'sizeLabel'):
self.nudgePanel.remove(self.sizeLabel)
self.sizeLabel = Label(self.sizeLabelText())
self.sizeLabel.anchor = "twh"
self.sizeLabel.tooltipText = "{0:n} blocks".format(self.selectionBox().volume)
# self.nudgePanelColumn = Column( (self.sizeLabel, self.nudgeRow) )
self.nudgePanel.top = self.nudgePanel.left = 0
self.nudgePanel.add(self.sizeLabel)
self.nudgeRow.top = self.sizeLabel.bottom
self.nudgePanel.shrink_wrap()
self.sizeLabel.centerx = self.nudgePanel.centerx
self.nudgeRow.centerx = self.nudgePanel.centerx
self.nudgePanel.bottom = self.editor.toolbar.top
self.nudgePanel.centerx = self.editor.centerx
self.nudgePanel.anchor = "bwh"
if self.panel is None and self.editor.currentTool in (self, None):
if self.bottomLeftPoint is not None and self.topRightPoint is not None:
self.panel = SelectionToolPanel(self)
self.panel.left = self.editor.left
self.panel.centery = self.editor.centery
self.editor.add(self.panel)
def hidePanel(self):
self.editor.remove(self.panel)
self.panel = None
def hideNudgePanel(self):
self.editor.remove(self.nudgePanel)
self.nudgePanel = None
selectionInProgress = False
dragStartPoint = None
# --- Event handlers ---
def toolReselected(self):
self.selectOtherCorner()
def toolSelected(self):
# if self.clearSelectionImmediately:
# self.setSelectionPoints(None)
self.showPanel()
def clampPos(self, pos):
x, y, z = pos
w, h, l = self.editor.level.Width, self.editor.level.Height, self.editor.level.Length
if w > 0:
if x >= w:
x = w - 1
if x < 0:
x = 0
if l > 0:
if z >= l:
z = l - 1
if z < 0:
z = 0
if y >= h:
y = h - 1
if y < 0:
y = 0
pos = [x, y, z]
return pos
@property
def currentCornerName(self):
return ("Blue", "Yellow")[self.currentCorner]
@property
def statusText(self):
# return "selectionInProgress {0} clickSelectionInProgress {1}".format(self.selectionInProgress, self.clickSelectionInProgress)
if self.selectionInProgress:
pd = self.editor.blockFaceUnderCursor
if pd:
p, d = pd
if self.dragStartPoint == p:
if self.clickSelectionInProgress:
return "Click the mouse button again to place the {0} selection corner. Press {1} to switch corners.".format(self.currentCornerName, self.hotkey)
else:
return "Release the mouse button here to place the {0} selection corner. Press {1} to switch corners.".format(self.currentCornerName, self.hotkey)
if self.clickSelectionInProgress:
return "Click the mouse button again to place the other selection corner."
return "Release the mouse button to finish the selection"
return "Click or drag to make a selection. Drag the selection walls to resize. Click near the edge to drag the opposite wall.".format(self.currentCornerName, self.hotkey)
clickSelectionInProgress = False
def endSelection(self):
self.selectionInProgress = False
self.clickSelectionInProgress = False
self.dragResizeFace = None
self.dragStartPoint = None
def cancel(self):
self.endSelection()
EditorTool.cancel(self)
dragResizeFace = None
dragResizeDimension = None
dragResizePosition = None
def mouseDown(self, evt, pos, direction):
# self.selectNone()
pos = self.clampPos(pos)
if self.selectionBox() and not self.selectionInProgress:
face, point = self.boxFaceUnderCursor(self.selectionBox())
if face is not None:
self.dragResizeFace = face
self.dragResizeDimension = self.findBestTrackingPlane(face)
# point = map(int, point)
self.dragResizePosition = point[self.dragResizeDimension]
return
if self.selectionInProgress is False:
self.dragStartPoint = pos
self.selectionInProgress = True
def mouseUp(self, evt, pos, direction):
pos = self.clampPos(pos)
if self.dragResizeFace is not None:
box = self.selectionBox()
if box is not None:
o, m = self.selectionPointsFromDragResize()
op = SelectionOperation(self, (o, m))
self.performWithRetry(op)
self.editor.addOperation(op)
self.dragResizeFace = None
return
if self.editor.viewMode == "Chunk":
self.clickSelectionInProgress = True
if self.dragStartPoint is None and not self.clickSelectionInProgress:
return
if self.dragStartPoint != pos or self.clickSelectionInProgress:
op = SelectionOperation(self, (self.dragStartPoint, pos))
self.performWithRetry(op)
self.editor.addOperation(op)
self.selectionInProgress = False
self.currentCorner = 1
self.clickSelectionInProgress = False
else:
points = self.getSelectionPoints()
if not all(points):
points = (pos, pos) # set both points on the first click
else:
points[self.currentCorner] = pos
if not self.clickSelectionInProgress:
self.clickSelectionInProgress = True
else:
op = SelectionOperation(self, points)
self.performWithRetry(op)
self.editor.addOperation(op)
self.selectOtherCorner()
self.selectionInProgress = False
self.clickSelectionInProgress = False
if self.chunkMode:
self.editor.selectionToChunks(remove=evt.alt, add=evt.shift)
self.editor.toolbar.selectTool(8)
@property
def chunkMode(self):
return self.editor.viewMode == "Chunk" or self.editor.currentTool is self.editor.toolbar.tools[8]
def selectionBoxInProgress(self):
if self.editor.blockFaceUnderCursor is None:
return
pos = self.editor.blockFaceUnderCursor[0]
if self.selectionInProgress or self.clickSelectionInProgress:
return self.selectionBoxForCorners(pos, self.dragStartPoint)
# requires a selection
def dragResizePoint(self):
# returns a point representing the intersection between the mouse ray
# and an imaginary plane perpendicular to the dragged face
pos = self.editor.mainViewport.cameraPosition
dim = self.dragResizeDimension
distance = self.dragResizePosition - pos[dim]
mouseVector = self.editor.mainViewport.mouseVector
scale = distance / (mouseVector[dim] or 0.0001)
point = map(lambda a, b: a * scale + b, mouseVector, pos)
return point
def draggingSelectionBox(self):
p1, p2 = self.selectionPointsFromDragResize()
box = self.selectionBoxForCorners(p1, p2)
return box
def selectionPointsFromDragResize(self):
point = self.dragResizePoint()
# glColor(1.0, 1.0, 0.0, 1.0)
# glPointSize(9.0)
# glBegin(GL_POINTS)
# glVertex3f(*point)
# glEnd()
#
# facebox = BoundingBox(box.origin, box.size)
# facebox.origin[dim] = self.dragResizePosition
# facebox.size[dim] = 0
# glEnable(GL_BLEND)
#
# drawFace(facebox, dim * 2)
#
# glDisable(GL_BLEND)
#
side = self.dragResizeFace & 1
dragdim = self.dragResizeFace >> 1
box = self.selectionBox()
o, m = list(box.origin), list(box.maximum)
(m, o)[side][dragdim] = int(floor(point[dragdim] + 0.5))
m = map(lambda a: a - 1, m)
return o, m
def option1(self):
self.selectOtherCorner()
_currentCorner = 0
@property
def currentCorner(self):
return self._currentCorner
@currentCorner.setter
def currentCorner(self, value):
self._currentCorner = value & 1
self.toolIconName = ("selection", "selection2")[self._currentCorner]
self.editor.toolbar.toolTextureChanged()
def selectOtherCorner(self):
self.currentCorner = 1 - self.currentCorner
showPreviousSelection = SelectSettings.showPreviousSelection.configProperty()
alpha = 0.25
def drawToolMarkers(self):
selectionBox = self.selectionBox()
if(selectionBox):
widg = self.editor.find_widget(mouse.get_pos())
# these corners stay even while using the chunk tool.
glPolygonOffset(DepthOffset.SelectionCorners, DepthOffset.SelectionCorners)
lineWidth = 3
for t, c, n in ((self.bottomLeftPoint, self.bottomLeftColor, self.bottomLeftNudge), (self.topRightPoint, self.topRightColor, self.topRightNudge)):
if t != None:
(sx, sy, sz) = t
if self.selectionInProgress:
if t == self.getSelectionPoint(self.currentCorner):
blockFace = self.editor.blockFaceUnderCursor
if blockFace:
p, d = blockFace
(sx, sy, sz) = p
else:
sx, sy, sz = self.dragStartPoint
# draw a blue or yellow wireframe box at the selection corner
r, g, b = c
alpha = 0.4
try:
bt = self.editor.level.blockAt(sx, sy, sz)
if(bt):
alpha = 0.2
except ChunkNotPresent:
pass
glLineWidth(lineWidth)
lineWidth += 1
# draw highlighted block faces when nudging
if (widg.parent == n or widg == n):
glEnable(GL_BLEND)
# drawCube(BoundingBox((sx, sy, sz), (1,1,1)))
nudgefaces = array([
selectionBox.minx, selectionBox.miny, selectionBox.minz,
selectionBox.minx, selectionBox.maxy, selectionBox.minz,
selectionBox.minx, selectionBox.maxy, selectionBox.maxz,
selectionBox.minx, selectionBox.miny, selectionBox.maxz,
selectionBox.minx, selectionBox.miny, selectionBox.minz,
selectionBox.maxx, selectionBox.miny, selectionBox.minz,
selectionBox.maxx, selectionBox.miny, selectionBox.maxz,
selectionBox.minx, selectionBox.miny, selectionBox.maxz,
selectionBox.minx, selectionBox.miny, selectionBox.minz,
selectionBox.minx, selectionBox.maxy, selectionBox.minz,
selectionBox.maxx, selectionBox.maxy, selectionBox.minz,
selectionBox.maxx, selectionBox.miny, selectionBox.minz,
], dtype=float32)
if sx != selectionBox.minx:
nudgefaces[0:12:3] = selectionBox.maxx
if sy != selectionBox.miny:
nudgefaces[13:24:3] = selectionBox.maxy
if sz != selectionBox.minz:
nudgefaces[26:36:3] = selectionBox.maxz
glColor(r, g, b, 0.3)
glVertexPointer(3, GL_FLOAT, 0, nudgefaces)
glEnable(GL_DEPTH_TEST)
glDrawArrays(GL_QUADS, 0, 12)
glDisable(GL_DEPTH_TEST)
glDisable(GL_BLEND)
glColor(r, g, b, alpha)
drawCube(BoundingBox((sx, sy, sz), (1, 1, 1)), GL_LINE_STRIP)
if not (not self.showPreviousSelection and self.selectionInProgress):
# draw the current selection as a white box. hangs around when you use other tools.
glPolygonOffset(DepthOffset.Selection, DepthOffset.Selection)
color = self.selectionColor + (self.alpha,)
if self.dragResizeFace is not None:
box = self.draggingSelectionBox()
else:
box = selectionBox
if self.panel and (widg is self.panel.nudgeBlocksButton or widg.parent is self.panel.nudgeBlocksButton):
color = (0.3, 1.0, 0.3, self.alpha)
self.editor.drawConstructionCube(box, color)
# highlight the face under the cursor, or the face being dragged
if self.dragResizeFace is None:
if self.selectionInProgress or self.clickSelectionInProgress:
pass
else:
face, point = self.boxFaceUnderCursor(box)
if face is not None:
glEnable(GL_BLEND)
glColor(*color)
# Shrink the highlighted face to show the click-through edges
offs = [s * self.edge_factor for s in box.size]
offs[face >> 1] = 0
origin = [o + off for o, off in zip(box.origin, offs)]
size = [s - off * 2 for s, off in zip(box.size, offs)]
cv = self.editor.mainViewport.cameraVector
for i in range(3):
if cv[i] > 0:
origin[i] -= offs[i]
size[i] += offs[i]
else:
size[i] += offs[i]
smallbox = FloatBox(origin, size)
drawFace(smallbox, face)
glColor(0.9, 0.6, 0.2, 0.8)
glLineWidth(2.0)
drawFace(box, face, type=GL.GL_LINE_STRIP)
glDisable(GL_BLEND)
else:
face = self.dragResizeFace
point = self.dragResizePoint()
dim = face >> 1
pos = point[dim]
side = face & 1
o, m = selectionBox.origin, selectionBox.maximum
otherFacePos = (m, o)[side ^ 1][dim] # ugly
direction = (-1, 1)[side]
# print "pos", pos, "otherFace", otherFacePos, "dir", direction
# print "m", (pos - otherFacePos) * direction
if (pos - otherFacePos) * direction > 0:
face ^= 1
glColor(0.9, 0.6, 0.2, 0.5)
drawFace(box, face, type=GL_LINE_STRIP)
glEnable(GL_BLEND)
glEnable(GL_DEPTH_TEST)
drawFace(box, face)
glDisable(GL_BLEND)
glDisable(GL_DEPTH_TEST)
pos, direction = self.editor.blockFaceUnderCursor
x, y, z = pos
selectionColor = map(lambda a: a * a * a * a, self.selectionColor)
# draw a colored box representing the possible selection
otherCorner = self.dragStartPoint
if self.dragResizeFace is not None:
self.showPanel() # xxx do this every frame while dragging because our UI kit is bad
if ((self.selectionInProgress or self.clickSelectionInProgress) and otherCorner != None):
glPolygonOffset(DepthOffset.PotentialSelection, DepthOffset.PotentialSelection)
box = self.selectionBoxForCorners(otherCorner, pos)
if self.chunkMode:
box = box.chunkBox(self.editor.level)
if key.get_mods() & KMOD_ALT:
selectionColor = [1., 0., 0.]
self.editor.drawConstructionCube(box, selectionColor + [self.alpha, ])
else:
# don't draw anything at the mouse cursor if we're resizing the box
if self.dragResizeFace is None:
box = self.selectionBox()
if box:
face, point = self.boxFaceUnderCursor(box)
if face is not None:
return
else:
return
def drawToolReticle(self):
glPolygonOffset(DepthOffset.SelectionReticle, DepthOffset.SelectionReticle)
pos, direction = self.editor.blockFaceUnderCursor
# draw a selection-colored box for the cursor reticle
selectionColor = map(lambda a: a * a * a * a, self.selectionColor)
r, g, b = selectionColor
alpha = 0.3
try:
bt = self.editor.level.blockAt(*pos)
if(bt):
## textureCoords = materials[bt][0]
alpha = 0.12
except ChunkNotPresent:
pass
# cube sides
glColor(r, g, b, alpha)
glDepthMask(False)
glEnable(GL_BLEND)
drawCube(BoundingBox(pos, (1, 1, 1)))
glDepthMask(True)
drawTerrainCuttingWire(BoundingBox(pos, (1, 1, 1)),
(r, g, b, 0.4),
(1., 1., 1., 1.0)
)
glDisable(GL_BLEND)
def setSelection(self, box):
if box is None:
self.selectNone()
else:
self.setSelectionPoints(self.selectionPointsFromBox(box))
def selectionPointsFromBox(self, box):
return (box.origin, map(lambda x: x - 1, box.maximum))
def selectNone(self):
self.setSelectionPoints(None)
def selectAll(self):
box = self.editor.level.bounds
op = SelectionOperation(self, self.selectionPointsFromBox(box))
self.performWithRetry(op)
self.editor.addOperation(op)
def deselect(self):
op = SelectionOperation(self, None)
self.performWithRetry(op)
self.editor.addOperation(op)
def setSelectionPoint(self, pointNumber, newPoint):
points = self.getSelectionPoints()
points[pointNumber] = newPoint
self.setSelectionPoints(points)
def setSelectionPoints(self, points):
if points:
self.bottomLeftPoint, self.topRightPoint = [Vector(*p) for p in points]
else:
self.bottomLeftPoint = self.topRightPoint = None
self._selectionChanged()
self.editor.selectionChanged()
def _selectionChanged(self):
if self.selectionBox():
self.showPanel()
else:
self.hidePanel()
self.hideNudgePanel()
def getSelectionPoint(self, pointNumber):
return (self.bottomLeftPoint, self.topRightPoint)[pointNumber] # lisp programmers think this doesn't evaluate 'self.topRightPoint' - lol!
def getSelectionPoints(self):
return [self.bottomLeftPoint, self.topRightPoint]
@alertException
def deleteBlocks(self):
box = self.selectionBox()
if None is box:
return
if box == box.chunkBox(self.editor.level):
resp = ask("You are deleting a chunk-shaped selection. Fill the selection with Air, or delete the chunks themselves?", responses=["Fill with Air", "Delete Chunks", "Cancel"])
if resp == "Delete Chunks":
self.editor.toolbar.tools[8].destroyChunks(box.chunkPositions)
elif resp == "Fill with Air":
self._deleteBlocks()
else:
self._deleteBlocks()
def _deleteBlocks(self, recordUndo=True):
box = self.selectionBox()
if None is box:
return
op = BlockFillOperation(self.editor, self.editor.level, box, self.editor.level.materials.Air, [])
with setWindowCaption("DELETING - "):
self.editor.freezeStatus("Deleting {0} blocks".format(box.volume))
self.performWithRetry(op, recordUndo)
self.editor.addOperation(op)
self.editor.invalidateBox(box)
self.editor.addUnsavedEdit()
@alertException
def deleteEntities(self, recordUndo=True):
box = self.selectionBox()
with setWindowCaption("WORKING - "):
self.editor.freezeStatus("Removing entities...")
level = self.editor.level
editor = self.editor
class DeleteEntitiesOperation(Operation):
def perform(self, recordUndo=True):
self.undoEntities = level.getEntitiesInBox(box)
level.removeEntitiesInBox(box)
editor.renderer.invalidateEntitiesInBox(box)
def undo(self):
level.removeEntitiesInBox(box)
level.addEntities(self.undoEntities)
editor.renderer.invalidateEntitiesInBox(box)
op = DeleteEntitiesOperation()
self.performWithRetry(op, recordUndo)
if recordUndo:
self.editor.addOperation(op)
self.editor.addUnsavedEdit()
@alertException
def analyzeSelection(self):
box = self.selectionBox()
self.editor.analyzeBox(self.editor.level, box)
@alertException
def cutSelection(self):
self.copySelection()
self.deleteBlocks()
self.deleteEntities(False)
@alertException
def copySelection(self):
schematic = self._copySelection()
if schematic:
self.editor.addCopiedSchematic(schematic)
def _copySelection(self):
box = self.selectionBox()
if not box:
return
shape = box.size
self.editor.mouseLookOff()
print "Clipping: ", shape
fileFormat = "schematic"
if box.volume > self.maxBlocks:
fileFormat = "schematic.zip"
if fileFormat == "schematic.zip":
missingChunks = filter(lambda x: not self.editor.level.containsChunk(*x), box.chunkPositions)
if len(missingChunks):
if not ((box.origin[0] & 0xf == 0) and (box.origin[2] & 0xf == 0)):
if ask("This is an uneven selection with missing chunks. Expand the selection to chunk edges, or copy air within the missing chunks?", ["Expand Selection", "Copy Air"]) == "Expand Selection":
self.selectChunks()
box = self.selectionBox()
with setWindowCaption("Copying - "):
filename = tempfile.mkdtemp(".zip", "mceditcopy")
os.rmdir(filename)
status = "Copying {0:n} blocks...".format(box.volume)
if fileFormat == "schematic":
schematic = showProgress(status,
self.editor.level.extractSchematicIter(box), cancel=True)
else:
schematic = showProgress(status,
self.editor.level.extractZipSchematicIter(box, filename), cancel=True)
if schematic == "Canceled":
return None
return schematic
@alertException
def exportSelection(self):
schematic = self._copySelection()
if schematic:
self.editor.exportSchematic(schematic)
class SelectionOperation(Operation):
changedLevel = False
def __init__(self, selectionTool, points):
self.selectionTool = selectionTool
self.points = points
def perform(self, recordUndo=True):
self.undoPoints = self.selectionTool.getSelectionPoints()
self.selectionTool.setSelectionPoints(self.points)
def undo(self):
points = self.points
self.points = self.undoPoints
self.perform()
self.points = points
class NudgeSelectionOperation (Operation):
changedLevel = False
def __init__(self, selectionTool, direction):
self.selectionTool = selectionTool
self.direction = direction
self.oldPoints = selectionTool.getSelectionPoints()
self.newPoints = [p + direction for p in self.oldPoints]
def perform(self, recordUndo=True):
self.selectionTool.setSelectionPoints(self.newPoints)
oldSelection = None
def undo(self):
self.selectionTool.setSelectionPoints(self.oldPoints)