The sliders are used only for relative rotations. See more details here: Is the rotation of the 3D Slicer's Transform module Euler or Quaternion? - #2 by lassoan
We should reset them to 0 more often to make sure users don’t mistake it for Euler angles or something similar.
Euler angles (and similar representations that rely on successive rotations along multiple axes) are not well suited for representing arbitrary orientations, as it suffers from gimbal lock and there are multiple parametrizations for the same orientation. Having multiple parametrizations for the same orientation means that conversion between Euler<->matrix representations is not invertible, which means that in general it is not possible to create 3 independent sliders for specifying orientation using Euler angles.
Just to illustrate the problem, you can copy-paste the code snippet below to get orientation sliders that operate in Euler angles (YXZ order). Note how the angles are modified after you release the slider if any of the angles >=90 deg (numerical instabilities may start to appear a few degrees before 90deg).
transformNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLTransformNode')
# transformNode = getNode('Transform')
###
def updateTransformFromWidget(value):
transform = vtk.vtkTransform()
transform.RotateZ(axisSliderWidgets[2].value)
transform.RotateX(axisSliderWidgets[0].value)
transform.RotateY(axisSliderWidgets[1].value)
transformNode.SetMatrixTransformToParent(transform.GetMatrix())
def updateWidgetFromTransform(caller, event):
transformMatrix = transformNode.GetMatrixTransformToParent()
transform = vtk.vtkTransform()
transform.SetMatrix(transformMatrix)
orientation = transform.GetOrientation()
for i in range(3):
axisSliderWidget = axisSliderWidgets[i]
wasBlocked = axisSliderWidget.blockSignals(True)
axisSliderWidget.value = orientation[i]
axisSliderWidget.blockSignals(wasBlocked)
def resetTransform():
transformNode.SetMatrixTransformToParent(vtk.vtkMatrix4x4())
# Create widget
widget = qt.QFrame()
layout = qt.QFormLayout()
widget.setLayout(layout)
axisSliderWidgets = []
for i in range(3):
axisSliderWidget = ctk.ctkSliderWidget()
axisSliderWidget.singleStep = 1.0
axisSliderWidget.minimum = -180
axisSliderWidget.maximum = 180
axisSliderWidget.value = 0
axisSliderWidget.tracking = False
layout.addRow(f"Axis {i+1}: ", axisSliderWidget)
axisSliderWidgets.append(axisSliderWidget)
axisSliderWidget.connect("valueChanged(double)", updateTransformFromWidget)
resetButton = qt.QPushButton("Reset")
layout.addWidget(resetButton)
resetButton.connect("clicked()", resetTransform)
widget.show()
transformObserver = transformNode.AddObserver(slicer.vtkMRMLTransformableNode.TransformModifiedEvent, updateWidgetFromTransform)
# transformNode.RemoveObserver(transformObserver)