Pull masklevel.py, modes.py, shapes.py out of brush/__init__.py

Move shapes out of shapewidget.py, too.
This commit is contained in:
David Vierra 2015-05-31 18:04:09 -10:00
parent b420e29613
commit b482e8d952
6 changed files with 326 additions and 282 deletions

View File

@ -4,22 +4,19 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
from PySide import QtGui, QtCore
import numpy
from PySide import QtGui
from mcedit2.editortools import EditorTool
from mcedit2.command import SimplePerformCommand
from mcedit2.editortools.brush.masklevel import FakeBrushSection
from mcedit2.editortools.brush.modes import BrushModes
from mcedit2.rendering import worldscene, scenegraph
from mcedit2.rendering.depths import DepthOffset
from mcedit2.util.load_ui import load_ui, registerCustomWidget
from mcedit2.util.settings import Settings
from mcedit2.util.showprogress import showProgress
from mcedit2.util.worldloader import WorldLoader
from mcedit2.widgets.blockpicker import BlockTypeButton
from mcedit2.widgets.layout import Row, Column
from mceditlib.anvil.biome_types import BiomeTypes
from mceditlib.geometry import Vector
from mceditlib.selection import ShapedSelection, BoundingBox
from mceditlib.selection import ShapedSelection
from mceditlib.util import exhaust
@ -47,9 +44,9 @@ class BrushCommand(SimplePerformCommand):
self.points = points
self.brushSize = options['brushSize']
self.brushStyle = options['brushStyle']
self.brushShape = options['brushShape']
self.brushMode = options['brushMode']
self.setText("%s %s Brush" % (self.brushMode.name, self.brushStyle.ID))
self.setText("%s %s Brush" % (self.brushMode.name, self.brushShape.ID))
if max(self.brushSize) > BrushTool.maxBrushSize:
self.brushSize = (BrushTool.maxBrushSize,) * 3
@ -74,7 +71,7 @@ class BrushCommand(SimplePerformCommand):
yield 0, len(self.points), "Applying {0} brush...".format(self.brushMode.name)
try:
#xxx combine selections
selections = [ShapedSelection(self.brushMode.brushBoxForPoint(point, self.options), self.brushStyle.shapeFunc) for point in self.points]
selections = [ShapedSelection(self.brushMode.brushBoxForPoint(point, self.options), self.brushShape.shapeFunc) for point in self.points]
self.brushMode.applyToSelections(self, selections)
except NotImplementedError:
for i, point in enumerate(self.points):
@ -88,245 +85,6 @@ class BrushCommand(SimplePerformCommand):
self.performed = True
class BrushMode(QtCore.QObject):
optionsWidget = None
def brushBoundingBox(self, center, options={}):
# Return a box of size options['brushSize'] centered around point.
# also used to position the preview cursor
size = options['brushSize']
x, y, z = size
origin = Vector(*center) - (Vector(x, y, z) / 2) + Vector((x % 2) * 0.5, (y % 2) * 0.5, (z % 2) * 0.5)
return BoundingBox(origin, size)
def applyToPoint(self, command, point):
"""
Called by BrushCommand for brush modes that can't be implemented using applyToChunk
:type point: Vector
:type command: BrushCommand
"""
raise NotImplementedError
def applyToSelections(self, command, selections):
"""
Called by BrushCommand to apply this brush mode to the given selection. Selection is generated
by calling
"""
raise NotImplementedError
def createOptionsWidget(self, brushTool):
return None
def createCursorLevel(self, brushTool):
return None
class Fill(BrushMode):
name = "fill"
def __init__(self):
super(Fill, self).__init__()
self.displayName = self.tr("Fill")
def createOptionsWidget(self, brushTool):
if self.optionsWidget:
return self.optionsWidget
self.optionsWidget = QtGui.QWidget()
label = QtGui.QLabel(self.tr("Fill Block:"))
self.blockTypeButton = BlockTypeButton()
self.blockTypeButton.editorSession = brushTool.editorSession
self.blockTypeButton.block = brushTool.editorSession.worldEditor.blocktypes['minecraft:stone']
self.blockTypeButton.blocksChanged.connect(brushTool.updateCursor)
self.optionsWidget.setLayout(Column(
Row(label, self.blockTypeButton, margin=0),
None, margin=0))
return self.optionsWidget
def getOptions(self):
return {'blockInfo': self.blockTypeButton.block}
def applyToSelections(self, command, selections):
"""
:type command: BrushCommand
"""
fill = command.editorSession.currentDimension.fillBlocksIter(selections[0], command.options['blockInfo'])
showProgress("Applying brush...", fill)
def brushBoxForPoint(self, point, options):
return self.brushBoundingBox(point, options)
def createCursorLevel(self, brushTool):
selection = ShapedSelection(self.brushBoxForPoint((0, 0, 0), brushTool.options), brushTool.brushStyle.shapeFunc)
cursorLevel = MaskLevel(selection,
self.blockTypeButton.block,
brushTool.editorSession.worldEditor.blocktypes)
return cursorLevel
class Biome(BrushMode):
name = "biome"
def __init__(self, *args, **kwargs):
super(Biome, self).__init__(*args, **kwargs)
self.displayName = self.tr("Biome")
def getOptions(self):
return {'biomeID': self.biomeTypeBox.itemData(self.biomeTypeBox.currentIndex())}
def createOptionsWidget(self, brushTool):
if self.optionsWidget:
return self.optionsWidget
self.optionsWidget = QtGui.QWidget()
label = QtGui.QLabel(self.tr("Fill Biome:"))
self.biomeTypeBox = QtGui.QComboBox()
self.biomeTypes = BiomeTypes()
for biome in self.biomeTypes.types.values():
self.biomeTypeBox.addItem(biome.name, biome.ID)
self.biomeTypeBox.activated.connect(brushTool.updateCursor)
self.optionsWidget.setLayout(Column(Row(label, self.biomeTypeBox, margin=0), None, margin=0))
return self.optionsWidget
def applyToSelections(self, command, selections):
"""
:type command: BrushCommand
"""
#task = command.editorSession.currentDimension.fillBlocksIter(selections[0], command.blockInfo)
#showProgress("Applying brush...", task)
selection = selections[0]
biomeID = command.options['biomeID']
for x, _, z in selection.positions:
command.editorSession.currentDimension.setBiomeID(x, z, biomeID)
def brushBoxForPoint(self, point, options):
x, y, z = options['brushSize']
options['brushSize'] = x, 1, z
return self.brushBoundingBox(point, options)
def createCursorLevel(self, brushTool):
box = self.brushBoxForPoint((0, 0, 0), brushTool.options)
selection = ShapedSelection(box, brushTool.brushStyle.shapeFunc)
cursorLevel = MaskLevel(selection,
brushTool.editorSession.worldEditor.blocktypes["minecraft:grass"],
brushTool.editorSession.worldEditor.blocktypes,
biomeID=self.getOptions()['biomeID'])
return cursorLevel
class BrushModes(object):
# load from plugins here
fill = Fill()
biome = Biome()
allModes = [fill, biome]
modesByName = {mode.name: mode for mode in allModes}
class Style(object):
ID = NotImplemented
icon = NotImplemented
shapeFunc = NotImplemented
NULL_ID = 255 # xxx
class MaskLevel(object):
def __init__(self, selection, fillBlock, blocktypes, biomeID=None):
"""
Level emulator to be used for rendering brushes and selections.
:type selection: mceditlib.selection.ShapedSelection
:param selection:
:param fillBlock:
:param blocktypes:
"""
self.bounds = self.selection = selection
self.blocktypes = blocktypes
self.sectionCache = {}
self.fillBlock = fillBlock
self.biomeID = biomeID
self.filename = "Temporary Level (%s %s %s)" % (selection, fillBlock, blocktypes)
def chunkPositions(self):
return self.bounds.chunkPositions()
def getChunk(self, cx, cz, create=False):
return FakeBrushChunk(self, cx, cz, self.biomeID)
def containsChunk(self, cx, cz):
return self.bounds.containsChunk(cx, cz)
class FakeBrushSection(object):
BlockLight = numpy.empty((16, 16, 16), dtype=numpy.uint8)
BlockLight[:] = 15
SkyLight = numpy.empty((16, 16, 16), dtype=numpy.uint8)
SkyLight[:] = 15
pass
class FakeBrushChunk(object):
Entities = ()
TileEntities = ()
def __init__(self, world, cx, cz, biomeID=None):
"""
:type world: MaskLevel
"""
self.dimension = world
self.cx = cx
self.cz = cz
self.Biomes = numpy.zeros((16, 16), numpy.uint8)
if biomeID:
self.Biomes[:] = biomeID
@property
def blocktypes(self):
return self.dimension.blocktypes
@property
def chunkPosition(self):
return self.cx, self.cz
def sectionPositions(self):
return self.dimension.selection.sectionPositions(self.cx, self.cz)
@property
def bounds(self):
return BoundingBox((self.cx << 4, self.dimension.bounds.miny, self.cz << 4),
(16, self.dimension.bounds.height, 16))
_sentinel = object()
def getSection(self, y, create=False):
selection = self.dimension.selection
sectionCache = self.dimension.sectionCache
fillBlock = self.dimension.fillBlock
cx, cz = self.chunkPosition
section = sectionCache.get((cx, y, cz), self._sentinel)
if section is self._sentinel:
mask = selection.section_mask(cx, y, cz)
if mask is None:
sectionCache[cx, y, cz] = None
return None
section = FakeBrushSection()
section.Y = y
if fillBlock.ID:
section.Blocks = numpy.array([0, fillBlock.ID], dtype=numpy.uint16)[mask.astype(numpy.uint8)]
section.Data = numpy.array([0, fillBlock.meta], dtype=numpy.uint8)[mask.astype(numpy.uint8)]
else:
section.Blocks = numpy.array([0, NULL_ID])[mask.astype(numpy.uint8)]
sectionCache[cx, y, cz] = section
return section
class BrushTool(EditorTool):
name = "Brush"
@ -336,6 +94,8 @@ class BrushTool(EditorTool):
def __init__(self, editorSession, *args, **kwargs):
super(BrushTool, self).__init__(editorSession, *args, **kwargs)
self.toolWidget = load_ui("editortools/brush.ui")
self.brushMode = None
self.brushLoader = None
BrushModeSetting.connectAndCall(self.modeSettingChanged)
@ -361,6 +121,7 @@ class BrushTool(EditorTool):
self.toolWidget.zSpinSlider.setValue(self.brushSize[2])
_brushSize = (0, 0, 0)
@property
def brushSize(self):
return self._brushSize
@ -406,7 +167,7 @@ class BrushTool(EditorTool):
@property
def options(self):
options = {'brushSize': self.brushSize,
'brushStyle': self.brushStyle,
'brushShape': self.brushShape,
'brushMode': self.brushMode}
options.update(self.brushMode.getOptions())
return options
@ -420,9 +181,8 @@ class BrushTool(EditorTool):
if widget:
stack.addWidget(widget)
@property
def brushStyle(self):
def brushShape(self):
return self.toolWidget.brushShapeInput.currentShape
def updateCursor(self):
@ -441,6 +201,7 @@ class BrushTool(EditorTool):
self.brushLoader.timer.start()
# xxx button palette?
@registerCustomWidget
class BrushModeWidget(QtGui.QComboBox):
def __init__(self, *args, **kwargs):

View File

@ -0,0 +1,108 @@
"""
masklevel
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import numpy
from mceditlib.selection import BoundingBox
import logging
log = logging.getLogger(__name__)
NULL_ID = 255 # xxx WHAT IS THIS FOR?
class MaskLevel(object):
def __init__(self, selection, fillBlock, blocktypes, biomeID=None):
"""
Level emulator to be used for rendering brushes and selections.
:type selection: mceditlib.selection.ShapedSelection
:param selection:
:param fillBlock:
:param blocktypes:
"""
self.bounds = self.selection = selection
self.blocktypes = blocktypes
self.sectionCache = {}
self.fillBlock = fillBlock
self.biomeID = biomeID
self.filename = "Temporary Level (%s %s %s)" % (selection, fillBlock, blocktypes)
def chunkPositions(self):
return self.bounds.chunkPositions()
def getChunk(self, cx, cz, create=False):
return FakeBrushChunk(self, cx, cz, self.biomeID)
def containsChunk(self, cx, cz):
return self.bounds.containsChunk(cx, cz)
class FakeBrushSection(object):
BlockLight = numpy.empty((16, 16, 16), dtype=numpy.uint8)
BlockLight[:] = 15
SkyLight = numpy.empty((16, 16, 16), dtype=numpy.uint8)
SkyLight[:] = 15
pass
class FakeBrushChunk(object):
Entities = ()
TileEntities = ()
def __init__(self, world, cx, cz, biomeID=None):
"""
:type world: mcedit2.editortools.brush.masklevel.MaskLevel
"""
self.dimension = world
self.cx = cx
self.cz = cz
self.Biomes = numpy.zeros((16, 16), numpy.uint8)
if biomeID:
self.Biomes[:] = biomeID
@property
def blocktypes(self):
return self.dimension.blocktypes
@property
def chunkPosition(self):
return self.cx, self.cz
def sectionPositions(self):
return self.dimension.selection.sectionPositions(self.cx, self.cz)
@property
def bounds(self):
return BoundingBox((self.cx << 4, self.dimension.bounds.miny, self.cz << 4),
(16, self.dimension.bounds.height, 16))
_sentinel = object()
def getSection(self, y, create=False):
selection = self.dimension.selection
sectionCache = self.dimension.sectionCache
fillBlock = self.dimension.fillBlock
cx, cz = self.chunkPosition
section = sectionCache.get((cx, y, cz), self._sentinel)
if section is self._sentinel:
mask = selection.section_mask(cx, y, cz)
if mask is None:
sectionCache[cx, y, cz] = None
return None
section = FakeBrushSection()
section.Y = y
if fillBlock.ID:
section.Blocks = numpy.array([0, fillBlock.ID], dtype=numpy.uint16)[mask.astype(numpy.uint8)]
section.Data = numpy.array([0, fillBlock.meta], dtype=numpy.uint8)[mask.astype(numpy.uint8)]
else:
section.Blocks = numpy.array([0, NULL_ID])[mask.astype(numpy.uint8)]
sectionCache[cx, y, cz] = section
return section

View File

@ -0,0 +1,156 @@
"""
modes
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
from PySide import QtGui, QtCore
from mcedit2.editortools.brush.masklevel import MaskLevel
from mcedit2.util.showprogress import showProgress
from mcedit2.widgets.blockpicker import BlockTypeButton
from mcedit2.widgets.layout import Column, Row
from mceditlib.anvil.biome_types import BiomeTypes
from mceditlib.geometry import Vector
from mceditlib.selection import ShapedSelection, BoundingBox
log = logging.getLogger(__name__)
class BrushMode(QtCore.QObject):
optionsWidget = None
def brushBoundingBox(self, center, options={}):
# Return a box of size options['brushSize'] centered around point.
# also used to position the preview cursor
size = options['brushSize']
x, y, z = size
origin = Vector(*center) - (Vector(x, y, z) / 2) + Vector((x % 2) * 0.5, (y % 2) * 0.5, (z % 2) * 0.5)
return BoundingBox(origin, size)
def applyToPoint(self, command, point):
"""
Called by BrushCommand for brush modes that can't be implemented using applyToChunk
:type point: Vector
:type command: BrushCommand
"""
raise NotImplementedError
def applyToSelections(self, command, selections):
"""
Called by BrushCommand to apply this brush mode to the given selection. Selection is generated
by calling
"""
raise NotImplementedError
def createOptionsWidget(self, brushTool):
return None
def createCursorLevel(self, brushTool):
return None
class Fill(BrushMode):
name = "fill"
def __init__(self):
super(Fill, self).__init__()
self.displayName = self.tr("Fill")
def createOptionsWidget(self, brushTool):
if self.optionsWidget:
return self.optionsWidget
self.optionsWidget = QtGui.QWidget()
label = QtGui.QLabel(self.tr("Fill Block:"))
self.blockTypeButton = BlockTypeButton()
self.blockTypeButton.editorSession = brushTool.editorSession
self.blockTypeButton.block = brushTool.editorSession.worldEditor.blocktypes['minecraft:stone']
self.blockTypeButton.blocksChanged.connect(brushTool.updateCursor)
self.optionsWidget.setLayout(Column(
Row(label, self.blockTypeButton, margin=0),
None, margin=0))
return self.optionsWidget
def getOptions(self):
return {'blockInfo': self.blockTypeButton.block}
def applyToSelections(self, command, selections):
"""
:type command: BrushCommand
"""
fill = command.editorSession.currentDimension.fillBlocksIter(selections[0], command.options['blockInfo'])
showProgress("Applying brush...", fill)
def brushBoxForPoint(self, point, options):
return self.brushBoundingBox(point, options)
def createCursorLevel(self, brushTool):
selection = ShapedSelection(self.brushBoxForPoint((0, 0, 0), brushTool.options), brushTool.brushShape.shapeFunc)
cursorLevel = MaskLevel(selection,
self.blockTypeButton.block,
brushTool.editorSession.worldEditor.blocktypes)
return cursorLevel
class Biome(BrushMode):
name = "biome"
def __init__(self, *args, **kwargs):
super(Biome, self).__init__(*args, **kwargs)
self.displayName = self.tr("Biome")
def getOptions(self):
return {'biomeID': self.biomeTypeBox.itemData(self.biomeTypeBox.currentIndex())}
def createOptionsWidget(self, brushTool):
if self.optionsWidget:
return self.optionsWidget
self.optionsWidget = QtGui.QWidget()
label = QtGui.QLabel(self.tr("Fill Biome:"))
self.biomeTypeBox = QtGui.QComboBox()
self.biomeTypes = BiomeTypes()
for biome in self.biomeTypes.types.values():
self.biomeTypeBox.addItem(biome.name, biome.ID)
self.biomeTypeBox.activated.connect(brushTool.updateCursor)
self.optionsWidget.setLayout(Column(Row(label, self.biomeTypeBox, margin=0), None, margin=0))
return self.optionsWidget
def applyToSelections(self, command, selections):
"""
:type command: BrushCommand
"""
#task = command.editorSession.currentDimension.fillBlocksIter(selections[0], command.blockInfo)
#showProgress("Applying brush...", task)
selection = selections[0]
biomeID = command.options['biomeID']
for x, _, z in selection.positions:
command.editorSession.currentDimension.setBiomeID(x, z, biomeID)
def brushBoxForPoint(self, point, options):
x, y, z = options['brushSize']
options['brushSize'] = x, 1, z
return self.brushBoundingBox(point, options)
def createCursorLevel(self, brushTool):
box = self.brushBoxForPoint((0, 0, 0), brushTool.options)
selection = ShapedSelection(box, brushTool.brushShape.shapeFunc)
cursorLevel = MaskLevel(selection,
brushTool.editorSession.worldEditor.blocktypes["minecraft:grass"],
brushTool.editorSession.worldEditor.blocktypes,
biomeID=self.getOptions()['biomeID'])
return cursorLevel
class BrushModes(object):
# load from plugins here
fill = Fill()
biome = Biome()
allModes = [fill, biome]
modesByName = {mode.name: mode for mode in allModes}

View File

@ -0,0 +1,39 @@
"""
shapes
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
from mceditlib import selection
log = logging.getLogger(__name__)
class BrushShape(object):
ID = NotImplemented
icon = NotImplemented
shapeFunc = NotImplemented
class Round(BrushShape):
ID = "Round"
icon = "shapes/round.png"
shapeFunc = staticmethod(selection.SphereShape)
class Square(BrushShape):
ID = "Square"
icon = "shapes/square.png"
shapeFunc = staticmethod(selection.BoxShape)
class Diamond(BrushShape):
ID = "Diamond"
icon = "shapes/diamond.png"
shapeFunc = staticmethod(selection.DiamondShape)
# load from plugins here, rename to selection shapes?
allShapes = (Square(), Round(), Diamond())
def getShapes():
return allShapes

View File

@ -8,6 +8,7 @@ from OpenGL import GL
from PySide import QtGui, QtCore
from mcedit2.editortools import EditorTool
from mcedit2.editortools.brush import shapes
from mcedit2.handles.boxhandle import BoxHandle
from mcedit2.rendering import cubes
from mcedit2.rendering.selection import SelectionScene, SelectionFaceNode
@ -15,7 +16,6 @@ from mcedit2.util.load_ui import load_ui
from mcedit2.util.glutils import gl
from mcedit2.rendering.depths import DepthOffset
from mcedit2.rendering import scenegraph, rendergraph
from mcedit2.widgets import shapewidget
from mcedit2.widgets.layout import Column
from mcedit2.widgets.shapewidget import ShapeWidget
from mceditlib import faces
@ -282,8 +282,8 @@ class SelectionTool(EditorTool):
showPreviousSelection = True
def createShapedSelection(self, box):
if self.shapeInput.currentShape is shapewidget.Square:
return box
if self.shapeInput.currentShape is shapes.Square:
return box # ugly hack
else:
return selection.ShapedSelection(box, self.shapeInput.currentShape.shapeFunc)

View File

@ -5,11 +5,11 @@ from __future__ import absolute_import, division, print_function
import logging
import os
from PySide import QtGui, QtCore
from mcedit2.editortools.brush import BrushShapeSetting, Style
from mcedit2.editortools.brush import BrushShapeSetting
from mcedit2.editortools.brush.shapes import getShapes
from mcedit2.util.load_ui import registerCustomWidget
from mcedit2.util.resources import resourcePath
from mcedit2.widgets import flowlayout
from mceditlib import selection
log = logging.getLogger(__name__)
@ -24,12 +24,13 @@ class ShapeWidget(QtGui.QWidget):
actionGroup.setExclusive(True)
iconBase = resourcePath("mcedit2/assets/mcedit2/icons")
actions = {}
for shape in Shapes.allShapes:
for shape in getShapes():
filename = os.path.join(iconBase, shape.icon)
assert os.path.exists(filename), "%r does not exist" % filename
icon = QtGui.QIcon(filename)
if icon is None:
raise ValueError("Failed to read shape icon file %s" % filename)
def _handler(shape):
def handler():
self.currentShape = shape
@ -47,33 +48,12 @@ class ShapeWidget(QtGui.QWidget):
actions[shape.ID] = action
self.setLayout(layout)
currentID = BrushShapeSetting.value(Shapes.allShapes[0].ID)
shapesByID = {shape.ID:shape for shape in Shapes.allShapes}
currentID = BrushShapeSetting.value(getShapes()[0].ID)
shapesByID = {shape.ID:shape for shape in getShapes()}
actions[currentID].setChecked(True)
self.currentShape = shapesByID.get(currentID, Shapes.allShapes[0])
self.currentShape = shapesByID.get(currentID, getShapes()[0])
shapeChanged = QtCore.Signal()
class Round(Style):
ID = "Round"
icon = "shapes/round.png"
shapeFunc = staticmethod(selection.SphereShape)
class Square(Style):
ID = "Square"
icon = "shapes/square.png"
shapeFunc = staticmethod(selection.BoxShape)
class Diamond(Style):
ID = "Diamond"
icon = "shapes/diamond.png"
shapeFunc = staticmethod(selection.DiamondShape)
class Shapes(object):
# load from plugins here, rename to selection shapes?
allShapes = (Square(), Round(), Diamond())