Add scale/flip options to Clone and Move
This commit is contained in:
parent
204045a3ef
commit
ea32ca86c3
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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):
|
||||
|
126
src/mcedit2/ui/scale_widget.ui
Normal file
126
src/mcedit2/ui/scale_widget.ui
Normal file
@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>scaleWidget</class>
|
||||
<widget class="QWidget" name="scaleWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>455</width>
|
||||
<height>338</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Scale X:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ScaleSpinSlider" name="xScaleSpinSlider" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="xFlipButton">
|
||||
<property name="text">
|
||||
<string>Flip</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Scale Y:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ScaleSpinSlider" name="yScaleSpinSlider" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="yFlipButton">
|
||||
<property name="text">
|
||||
<string>Flip</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Scale Z:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ScaleSpinSlider" name="zScaleSpinSlider" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="zFlipButton">
|
||||
<property name="text">
|
||||
<string>Flip</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ScaleSpinSlider</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>mcedit2/widgets/spinslider.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
92
src/mcedit2/widgets/scale_widget.py
Normal file
92
src/mcedit2/widgets/scale_widget.py
Normal file
@ -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)
|
||||
|
@ -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.
|
@ -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)
|
||||
|
Reference in New Issue
Block a user