From ea32ca86c3fb88e079066b17d258cab1eb41de7e Mon Sep 17 00:00:00 2001 From: David Vierra Date: Thu, 30 Mar 2017 17:39:25 -1000 Subject: [PATCH] Add scale/flip options to Clone and Move --- src/mcedit2/editortools/clone.py | 58 +++++++++- src/mcedit2/editortools/move.py | 34 ++++++ src/mcedit2/imports.py | 47 +++++++- src/mcedit2/rendering/scenegraph/matrix.py | 13 ++- src/mcedit2/ui/scale_widget.ui | 126 +++++++++++++++++++++ src/mcedit2/widgets/scale_widget.py | 92 +++++++++++++++ src/mcedit2/widgets/spinslider.py | 37 +++++- src/mceditlib/transform.py | 69 +++++++---- 8 files changed, 436 insertions(+), 40 deletions(-) create mode 100644 src/mcedit2/ui/scale_widget.ui create mode 100644 src/mcedit2/widgets/scale_widget.py diff --git a/src/mcedit2/editortools/clone.py b/src/mcedit2/editortools/clone.py index a094441..469b55d 100644 --- a/src/mcedit2/editortools/clone.py +++ b/src/mcedit2/editortools/clone.py @@ -15,6 +15,7 @@ from mcedit2.util.showprogress import showProgress from mcedit2.widgets.coord_widget import CoordinateWidget from mcedit2.widgets.layout import Column, Row from mcedit2.widgets.rotation_widget import RotationWidget +from mcedit2.widgets.scale_widget import ScaleWidget from mceditlib import transform log = logging.getLogger(__name__) @@ -69,6 +70,21 @@ class CloneRotateCommand(QtGui.QUndoCommand): self.cloneTool.setRotation(self.newRotation) +class CloneScaleCommand(QtGui.QUndoCommand): + def __init__(self, oldScale, newScale, cloneTool): + super(CloneScaleCommand, self).__init__() + self.cloneTool = cloneTool + self.setText(QtGui.qApp.tr("Scale Cloned Objects")) + self.newScale = newScale + self.oldScale = oldScale + + def undo(self): + self.cloneTool.setScale(self.oldScale) + + def redo(self): + self.cloneTool.setScale(self.newScale) + + class CloneFinishCommand(SimpleRevisionCommand): def __init__(self, cloneTool, pendingImport, originPoint, *args, **kwargs): super(CloneFinishCommand, self).__init__(cloneTool.editorSession, cloneTool.tr("Finish Clone"), *args, **kwargs) @@ -130,6 +146,9 @@ class CloneTool(EditorTool): self.rotationInput = RotationWidget() self.rotationInput.rotationChanged.connect(self.rotationChanged) + self.scaleInput = ScaleWidget() + self.scaleInput.scaleChanged.connect(self.scaleChanged) + confirmButton = QtGui.QPushButton(self.tr("Confirm")) # xxxx should be in worldview confirmButton.clicked.connect(self.confirmClone) @@ -147,6 +166,7 @@ class CloneTool(EditorTool): self.rotationInput, Row(self.rotateRepeatsCheckbox, self.rotateOffsetCheckbox), + self.scaleInput, Row(QtGui.QLabel(self.tr("Repeat count: ")), self.repeatCountInput), confirmButton, None)) @@ -159,9 +179,11 @@ class CloneTool(EditorTool): self.updateTiling() def rotationChanged(self, rots, live): + scale = self.scaleInput.scale if live: - for node, (nodePos, nodeRots) in zip(self.pendingCloneNodes, self.getTilingPositions(rotations=rots)): + for node, (nodePos, nodeRots, nodeScale) in zip(self.pendingCloneNodes, self.getTilingPositions(None, rots, scale)): node.setPreviewRotation(nodeRots) + node.setPreviewScale(nodeScale) node.setPreviewBasePosition(nodePos + node.pendingImport.transformOffset) self.editorSession.updateView() else: @@ -170,6 +192,20 @@ class CloneTool(EditorTool): self.editorSession.pushCommand(command) self.updateTiling() + def scaleChanged(self, scale, live): + rots = self.rotationInput.rotation + if live: + for node, (nodePos, nodeRots, nodeScale) in zip(self.pendingCloneNodes, self.getTilingPositions(None, rots, scale)): + node.setPreviewRotation(nodeRots) + node.setPreviewScale(nodeScale) + node.setPreviewBasePosition(nodePos + node.pendingImport.transformOffset) + self.editorSession.updateView() + else: + if self.mainPendingClone and self.mainPendingClone.scale != scale: + command = CloneScaleCommand(self.mainPendingClone.scale, scale, self) + self.editorSession.pushCommand(command) + self.updateTiling() + def setRepeatCount(self, value): self.repeatCount = value self.updateTiling() @@ -181,6 +217,13 @@ class CloneTool(EditorTool): self.mainPendingClone.rotation = rots self.updateTiling() + def setScale(self, scale): + if self.mainPendingClone is None: + return + else: + self.mainPendingClone.scale = scale + self.updateTiling() + def updateTiling(self): if self.mainPendingClone is None: repeatCount = 0 @@ -221,21 +264,24 @@ class CloneTool(EditorTool): def updateTilingPositions(self, offsetPoint=None): if self.originPoint is not None: - for clone, (pos, rots) in zip(self.pendingClones, self.getTilingPositions(offsetPoint)): + for clone, (pos, rots, scale) in zip(self.pendingClones, self.getTilingPositions(offsetPoint)): clone.basePosition = pos clone.rotation = rots + clone.scale = scale self.editorSession.updateView() - def getTilingPositions(self, offsetPoint=None, rotations=None): + def getTilingPositions(self, offsetPoint=None, rotations=None, scale=None): rotateRepeats = self.rotateRepeatsCheckbox.isChecked() rotateOffsets = self.rotateOffsetCheckbox.isChecked() baseRotations = rotations or self.mainPendingClone.rotation rotations = baseRotations + scale = scale or self.mainPendingClone.scale - matrix = transform.rotationMatrix((0, 0, 0), *rotations) + matrix = transform.transformationMatrix((0, 0, 0), rotations, scale) matrix = numpy.linalg.inv(matrix)[:3, :3] - + + # TODO: Use scales here if offsetPoint is None: offsetPoint = self.mainPendingClone.basePosition if None not in (offsetPoint, self.originPoint): @@ -243,7 +289,7 @@ class CloneTool(EditorTool): offset = offsetPoint - self.originPoint for i in range(self.repeatCount): pos = pos + offset - yield pos.intfloor(), rotations + yield pos.intfloor(), rotations, scale if rotateRepeats: rotations = [a+b for a,b in zip(rotations, baseRotations)] if rotateOffsets: diff --git a/src/mcedit2/editortools/move.py b/src/mcedit2/editortools/move.py index 4937e6f..e55772c 100644 --- a/src/mcedit2/editortools/move.py +++ b/src/mcedit2/editortools/move.py @@ -15,6 +15,7 @@ from mcedit2.util.showprogress import showProgress from mcedit2.widgets.coord_widget import CoordinateWidget from mcedit2.widgets.layout import Column from mcedit2.widgets.rotation_widget import RotationWidget +from mcedit2.widgets.scale_widget import ScaleWidget from mceditlib.export import extractSchematicFromIter from mceditlib.selection import BoundingBox @@ -72,6 +73,21 @@ class MoveRotateCommand(QtGui.QUndoCommand): self.pendingImport.rotation = self.newRotation +class MoveScaleCommand(QtGui.QUndoCommand): + def __init__(self, oldScale, newScale, pendingImport): + super(MoveScaleCommand, self).__init__() + self.pendingImport = pendingImport + self.setText(QtGui.qApp.tr("Scale Object")) + self.newScale = newScale + self.oldScale = oldScale + + def undo(self): + self.pendingImport.scale = self.oldScale + + def redo(self): + self.pendingImport.scale = self.newScale + + class MoveFinishCommand(SimpleRevisionCommand): def __init__(self, moveTool, pendingImport, *args, **kwargs): super(MoveFinishCommand, self).__init__(moveTool.editorSession, moveTool.tr("Finish Move"), *args, **kwargs) @@ -114,6 +130,9 @@ class MoveTool(EditorTool): self.rotationInput = RotationWidget() self.rotationInput.rotationChanged.connect(self.rotationChanged) + self.scaleInput = ScaleWidget() + self.scaleInput.scaleChanged.connect(self.scaleChanged) + self.copyOptionsWidget = QtGui.QGroupBox(self.tr("Options")) self.copyAirCheckbox = QtGui.QCheckBox(self.tr("Copy Air")) @@ -123,6 +142,7 @@ class MoveTool(EditorTool): confirmButton.clicked.connect(self.confirmImport) self.toolWidget.setLayout(Column(self.pointInput, self.rotationInput, + self.scaleInput, self.copyOptionsWidget, confirmButton, None)) @@ -137,6 +157,16 @@ class MoveTool(EditorTool): self.editorSession.updateView() + def scaleChanged(self, scale, live): + if self.currentImport: + if live: + self.currentImportNode.setPreviewScale(scale) + elif scale != self.currentImport.scale: + command = MoveScaleCommand(self.currentImport.scale, scale, self.currentImport) + self.editorSession.pushCommand(command) + + self.editorSession.updateView() + def pointInputChanged(self, value): if value is not None: self.importDidMove(value, self.currentImport.basePosition) @@ -161,6 +191,7 @@ class MoveTool(EditorTool): self._currentImport = pendingImport self.pointInput.setEnabled(pendingImport is not None) if pendingImport is not None: + pendingImport.scaleChanged.connect(self.setScaleInput) pendingImport.rotationChanged.connect(self.setRotationInput) pendingImport.positionChanged.connect(self.setPositionInput) @@ -184,6 +215,9 @@ class MoveTool(EditorTool): def currentImportNode(self): return self._currentImportNode + def setScaleInput(self, scale): + self.scaleInput.scale = scale + def setRotationInput(self, rots): self.rotationInput.rotation = rots diff --git a/src/mcedit2/imports.py b/src/mcedit2/imports.py index a5ad3cb..9cebdef 100644 --- a/src/mcedit2/imports.py +++ b/src/mcedit2/imports.py @@ -7,7 +7,7 @@ from PySide import QtCore, QtGui from PySide.QtCore import Qt from mcedit2.handles.boxhandle import BoxHandle from mcedit2.rendering.depths import DepthOffsets -from mcedit2.rendering.scenegraph.matrix import Translate, Rotate +from mcedit2.rendering.scenegraph.matrix import Translate, Rotate, Scale from mcedit2.rendering.scenegraph.scenenode import Node from mcedit2.rendering.selection import SelectionBoxNode from mcedit2.rendering.worldscene import WorldScene @@ -123,6 +123,10 @@ class PendingImportNode(Node, QtCore.QObject): self.rotateNode.setAnchor(self.pendingImport.selection.size * 0.5) self.rotateNode.addChild(self.worldScene) + self.scaleNode = Scale3DNode() + self.scaleNode.setAnchor(self.pendingImport.selection.size * 0.5) + self.scaleNode.addChild(self.rotateNode) + # plainSceneNode contains the non-transformed preview of the imported # object, including its world scene. This preview will be rotated model-wise # while the user is dragging the rotate controls. @@ -130,7 +134,7 @@ class PendingImportNode(Node, QtCore.QObject): self.plainSceneNode = Node("plainScene") self.positionTranslate = Translate() self.plainSceneNode.addState(self.positionTranslate) - self.plainSceneNode.addChild(self.rotateNode) + self.plainSceneNode.addChild(self.scaleNode) self.addChild(self.plainSceneNode) @@ -178,6 +182,7 @@ class PendingImportNode(Node, QtCore.QObject): self.pendingImport.positionChanged.connect(self.setPosition) self.pendingImport.rotationChanged.connect(self.setRotation) + self.pendingImport.scaleChanged.connect(self.setScale) # Emitted when the user finishes dragging the box handle and releases the mouse # button. Arguments are (newPosition, oldPosition). @@ -193,6 +198,7 @@ class PendingImportNode(Node, QtCore.QObject): self.importMoved.emit(point, oldPoint) def handleBoundsChanged(self, bounds): + log.info("handleBoundsChanged: %s", bounds) self.setPreviewBasePosition(bounds.origin) def setPreviewBasePosition(self, origin): @@ -214,6 +220,16 @@ class PendingImportNode(Node, QtCore.QObject): self.updateBoxHandle() self.rotateNode.setRotation(rots) + def setPreviewScale(self, scale): + self.plainSceneNode.visible = True + self.transformedSceneNode.visible = False + self.scaleNode.setScale(scale) + + def setScale(self, scale): + self.updateTransformedScene() + self.updateBoxHandle() + self.scaleNode.setScale(scale) + def updateTransformedScene(self): if self.pendingImport.transformedDim is not None: log.info("Showing transformed scene") @@ -458,23 +474,23 @@ class PendingImport(QtCore.QObject): @property def scale(self): - return self._rotation + return self._scale @scale.setter def scale(self, value): if self._scale == value: return self._scale = Vector(*value) - self.scaleChanged.emit(self._scale) self.updateTransform() + self.scaleChanged.emit(self._scale) def updateTransform(self): - if self.rotation == (0, 0, 0) and self.scale == (0, 0, 0): + if self.rotation == (0, 0, 0) and self.scale == (1, 1, 1): self.transformedDim = None self.transformOffset = Vector(0, 0, 0) else: selectionDim = SelectionTransform(self.sourceDim, self.selection) - self.transformedDim = DimensionTransform(selectionDim, self.rotateAnchor, *self.rotation) + self.transformedDim = DimensionTransform(selectionDim, self.rotateAnchor, self.rotation, self.scale) self.transformOffset = self.transformedDim.bounds.origin - self.selection.origin self.updateImportPos() @@ -509,6 +525,25 @@ class Rotate3DNode(Node): self.rotY.degrees = ry self.rotZ.degrees = rz + def setAnchor(self, point): + self.anchor.translateOffset = point + self.recenter.translateOffset = -point + + +class Scale3DNode(Node): + def __init__(self): + super(Scale3DNode, self).__init__() + self.anchor = Translate() + self.scale = Scale() + self.recenter = Translate() + + self.addState(self.anchor) + self.addState(self.scale) + self.addState(self.recenter) + + def setScale(self, scale): + self.scale.scale = scale + def setAnchor(self, point): self.anchor.translateOffset = point self.recenter.translateOffset = -point \ No newline at end of file diff --git a/src/mcedit2/rendering/scenegraph/matrix.py b/src/mcedit2/rendering/scenegraph/matrix.py index b2c7d9a..8d38ba9 100644 --- a/src/mcedit2/rendering/scenegraph/matrix.py +++ b/src/mcedit2/rendering/scenegraph/matrix.py @@ -90,9 +90,20 @@ class Scale(states.SceneNodeState): GL.glMatrixMode(GL.GL_MODELVIEW) GL.glPopMatrix() - def __init__(self, scale): + def __init__(self, scale=(1.0, 1.0, 1.0)): super(Scale, self).__init__() self.scale = scale + + _scale = (1.0, 1.0, 1.0) + + @property + def scale(self): + return self._scale + + @scale.setter + def scale(self, value): + self._scale = value + self.dirty = True class MatrixState(states.SceneNodeState): diff --git a/src/mcedit2/ui/scale_widget.ui b/src/mcedit2/ui/scale_widget.ui new file mode 100644 index 0000000..a0543bc --- /dev/null +++ b/src/mcedit2/ui/scale_widget.ui @@ -0,0 +1,126 @@ + + + scaleWidget + + + + 0 + 0 + 455 + 338 + + + + Form + + + + + + + + + + Scale X: + + + + + + + + 0 + 0 + + + + + + + + Flip + + + false + + + + + + + + + + + Scale Y: + + + + + + + + 0 + 0 + + + + + + + + Flip + + + false + + + + + + + + + + + Scale Z: + + + + + + + + 0 + 0 + + + + + + + + Flip + + + false + + + + + + + + + + + + ScaleSpinSlider + QWidget +
mcedit2/widgets/spinslider.h
+ 1 +
+
+ + +
diff --git a/src/mcedit2/widgets/scale_widget.py b/src/mcedit2/widgets/scale_widget.py new file mode 100644 index 0000000..c794fae --- /dev/null +++ b/src/mcedit2/widgets/scale_widget.py @@ -0,0 +1,92 @@ +""" + rotation_widget +""" +from __future__ import absolute_import, division, print_function, unicode_literals +import logging + +from PySide import QtGui, QtCore + +from mcedit2.ui.scale_widget import Ui_scaleWidget +from mcedit2.util.resources import resourcePath + +log = logging.getLogger(__name__) + + +class ScaleWidget(QtGui.QWidget, Ui_scaleWidget): + def __init__(self): + super(ScaleWidget, self).__init__() + self.setupUi(self) + + self.xScaleSpinSlider.valueChanged.connect(self.setXScale) + self.yScaleSpinSlider.valueChanged.connect(self.setYScale) + self.zScaleSpinSlider.valueChanged.connect(self.setZScale) + + icon = QtGui.QIcon(resourcePath("mcedit2/assets/mcedit2/icons/mirror.png")) + self.xFlipButton.setIcon(icon) + self.yFlipButton.setIcon(icon) + self.zFlipButton.setIcon(icon) + + self.xFlipButton.clicked.connect(self.xFlipClicked) + self.yFlipButton.clicked.connect(self.yFlipClicked) + self.zFlipButton.clicked.connect(self.zFlipClicked) + + self.xScale = self.yScale = self.zScale = 1.0 + + def xFlipClicked(self): + x, y, z = self.scale + self.scale = -x, y, z + + def yFlipClicked(self): + x, y, z = self.scale + self.scale = x, -y, z + + def zFlipClicked(self): + x, y, z = self.scale + self.scale = x, y, -z + + scaleChanged = QtCore.Signal(object, bool) + + @property + def scale(self): + return self.xScale, self.yScale, self.zScale + + @scale.setter + def scale(self, value): + if value == self.scale: + return + + xScale, yScale, zScale = value + self.xScale, self.yScale, self.zScale = value + + self.xScaleSpinSlider.setValue(xScale) + self.yScaleSpinSlider.setValue(yScale) + self.zScaleSpinSlider.setValue(zScale) + + self.emitScaleChanged(False) + + def emitScaleChanged(self, live): + log.info("emitScaleChanged %s %s", self.scale, live) + self.scaleChanged.emit(self.scale, live) + + def setXScale(self, value, live): + log.info("setXScale %s %s", value, live) + if self.xScale == value and live: + return + + self.xScale = value + self.emitScaleChanged(live) + + def setYScale(self, value, live): + if self.yScale == value and live: + return + + self.yScale = value + self.emitScaleChanged(live) + + def setZScale(self, value, live): + if self.zScale == value and live: + return + + self.zScale = value + self.emitScaleChanged(live) + \ No newline at end of file diff --git a/src/mcedit2/widgets/spinslider.py b/src/mcedit2/widgets/spinslider.py index 0d09905..20eddbc 100644 --- a/src/mcedit2/widgets/spinslider.py +++ b/src/mcedit2/widgets/spinslider.py @@ -116,16 +116,49 @@ class SpinSlider(QtGui.QWidget): def setMinimum(self, value): self._minimum = value - self.slider.setMinimum(value * self.sliderFactor) self.spinBox.setMinimum(value) + if value is not None: + value = self.toSlider(value) + self.slider.setMinimum(value) def maximum(self): return self._maximum def setMaximum(self, value): self._maximum = value - self.slider.setMaximum(value * self.sliderFactor) self.spinBox.setMaximum(value) + if value is not None: + value = self.toSlider(value) + self.slider.setMaximum(value) valueChanged = QtCore.Signal(float, bool) +@registerCustomWidget +class DoubleSpinSlider(SpinSlider): + def __init__(self, *a, **kw): + kw['double'] = True + super(DoubleSpinSlider, self).__init__(*a, **kw) + +@registerCustomWidget +class ScaleSpinSlider(DoubleSpinSlider): + def __init__(self, *a, **kw): + kw['minimum'] = -20 + kw['maximum'] = 20 + kw['value'] = 1 + + super(ScaleSpinSlider, self).__init__(*a, **kw) + + def toSlider(self, value): + if value < -1.0: + return (value * 50) - 1000 + if value > 1.0: + return (value * 50) + 1000 + + return value * 1000 + + def fromSlider(self, value): + if value < -1000: + return ((value + 1000) * 2) / 100. + if value > 1000: + return ((value - 1000) * 2) / 100. + return value / 1000. \ No newline at end of file diff --git a/src/mceditlib/transform.py b/src/mceditlib/transform.py index 9c18139..145f6e3 100644 --- a/src/mceditlib/transform.py +++ b/src/mceditlib/transform.py @@ -30,34 +30,42 @@ def transformBounds(bounds, matrix): corners = np.array(boundsCorners(bounds)) corners = np.hstack([corners, ([1],)*8]) corners = corners * matrix - - minx = min(corners[:, 0]) - miny = min(corners[:, 1]) - minz = min(corners[:, 2]) - maxx = max(corners[:, 0]) - maxy = max(corners[:, 1]) - maxz = max(corners[:, 2]) - - if maxx % 1: - maxx += 1 - if maxy % 1: - maxy += 1 - if maxz % 1: - maxz += 1 + + minx = math.floor(min(corners[:, 0])) + miny = math.floor(min(corners[:, 1])) + minz = math.floor(min(corners[:, 2])) + maxx = math.ceil(max(corners[:, 0])) + maxy = math.ceil(max(corners[:, 1])) + maxz = math.ceil(max(corners[:, 2])) + + # Why? Weird hacks for rotation? + + # if maxx % 1: + # maxx += 1 + # if maxy % 1: + # maxy += 1 + # if maxz % 1: + # maxz += 1 newbox = BoundingBox(origin=Vector(minx, miny, minz).intfloor(), maximum=Vector(maxx, maxy, maxz).intfloor()) return newbox -def rotationMatrix(anchor, rotX, rotY, rotZ): +def transformationMatrix(anchor, rotation, scale): + rotX, rotY, rotZ = rotation + + scaleInv = tuple([1.0/c if c != 0 else 1.0 for c in scale]) + translate = np.matrix(np.identity(4)) translate[3, 0] = anchor[0] translate[3, 1] = anchor[1] translate[3, 2] = anchor[2] - - # Rotate around center of cells. - anchor = Vector(*anchor) - (0.5, 0.5, 0.5) + + scaleMatrix = np.matrix(np.identity(4)) + scaleMatrix[0, 0] = scaleInv[0] + scaleMatrix[1, 1] = scaleInv[1] + scaleMatrix[2, 2] = scaleInv[2] reverse_translate = np.matrix(np.identity(4)) reverse_translate[3, 0] = -anchor[0] @@ -73,6 +81,8 @@ def rotationMatrix(anchor, rotX, rotY, rotZ): if rotZ: matrix = npRotate('z', rotZ) * matrix + matrix = scaleMatrix * matrix + matrix = reverse_translate * matrix return matrix @@ -250,8 +260,9 @@ class SelectionTransform(DimensionTransformBase): section.Blocks[sectionMask] = baseSection.Blocks[sectionMask] section.Data[sectionMask] = baseSection.Data[sectionMask] + class DimensionTransform(DimensionTransformBase): - def __init__(self, dimension, anchor, rotX=0, rotY=0, rotZ=0): + def __init__(self, dimension, anchor, rotation=(0, 0, 0), scale=(1, 1, 1)): """ A wrapper around a WorldEditorDimension that applies a three-dimensional rotation around a given anchor point. The wrapped dimension's bounds will be different from the @@ -263,14 +274,16 @@ class DimensionTransform(DimensionTransformBase): dimension: mceditlib.worldeditor.WorldEditorDimension The dimension to wrap and apply rotations to - anchor: Vector - The point to rotate the dimension around + anchor: mceditlib.geometry.Vector + The point to rotate and scale the dimension around - rotX: float - rotY: float - rotZ: float + rotation: float[3] The angles to rotate the dimension around, along each axis respectively. The angles are given in radians. + + scale: float[3] + The scales to resize the dimension along each axis respectively. 1.0 is + normal size. Returns ------- @@ -279,12 +292,14 @@ class DimensionTransform(DimensionTransformBase): A dimension that acts as a rotated version of the given dimension. """ super(DimensionTransform, self).__init__(dimension) + rotX, rotY, rotZ = rotation self.rotX = rotX self.rotY = rotY self.rotZ = rotZ self.anchor = anchor + self.scale = scale - self.matrix = rotationMatrix(anchor, rotX, rotY, rotZ) + self.matrix = transformationMatrix(anchor, rotation, scale) blockRotation = BlockRotations(dimension.blocktypes) rotationTable = blankRotationTable() @@ -308,6 +323,10 @@ class DimensionTransform(DimensionTransformBase): self.rotationTable = rotationTable self._transformedBounds = transformBounds(dimension.bounds, self.matrix) + + print("Bounds: ", dimension.bounds) + print("Transformed: ", self._transformedBounds) + print("Anchor: ", anchor) def initSection(self, section): shape = (16, 16, 16)