Skip to content

Instantly share code, notes, and snippets.

@renaudll
Created August 5, 2020 01:20
Show Gist options
  • Select an option

  • Save renaudll/aaa73ae71454fb4df99b8051a7cc9fe9 to your computer and use it in GitHub Desktop.

Select an option

Save renaudll/aaa73ae71454fb4df99b8051a7cc9fe9 to your computer and use it in GitHub Desktop.
"""
Align joints to view. Vendored from omtk.
This will align joints using the camera direction as the up-vector (Z axis).
This will preserve the start and end position and project everything to a 2D plane.
Usage:
1) Select multiple joints
2) Align the camera
3) Run the script
4) Profit!
"""
import math
import pymel.core as pymel
from pymel.core.datatypes import Matrix, Vector
from maya import OpenMaya
from maya import mel
def transfer_rotation_to_joint_orient(obj):
"""
Convert ajoint rotation values to orient.
In Maya it is not possible to do a "makeIdentity" command on a joint
that is bound to a skin_clusters. This method bypass this limitation.
:param obj: The joints to act on
:rtype obj: pymel.nodetypes.Joint
"""
try:
_check_joint_rotation_attributes(obj)
except ValueError as error:
pymel.warning(error)
return
mfn = obj.__apimfn__()
# Get current rotation
rotation = OpenMaya.MEulerRotation()
mfn.getRotation(rotation)
# Get current joint orient
joint_orient = OpenMaya.MEulerRotation()
mfn.getOrientation(joint_orient)
# Compute new rotation
result = rotation.reorder(OpenMaya.MEulerRotation.kXYZ) * joint_orient
obj.jointOrientX.set(math.degrees(result.x))
obj.jointOrientY.set(math.degrees(result.y))
obj.jointOrientZ.set(math.degrees(result.z))
obj.rotateX.set(0.0)
obj.rotateY.set(0.0)
obj.rotateZ.set(0.0)
def _check_joint_rotation_attributes(obj):
"""
Validate the rotation attributes of a joint are free to change
:param pymel.nodetypes.Joint obj: A joint
:raises ValueError: If a rotation attribute is not free to change
"""
for attr in (
obj.rotateX,
obj.rotateY,
obj.rotateZ,
obj.jointOrientX,
obj.jointOrientY,
obj.jointOrientZ,
):
if attr.isFreeToChange() != OpenMaya.MPlug.kFreeToChange:
raise ValueError("Can't transfer rotation to joint orient. %r is locked." % attr)
# todo: move to libPymel
def get_matrix_from_direction(
look_vec, upp_vec, look_axis=Vector.xAxis, upp_axis=Vector.zAxis,
):
# print look_axis, look_vec
# print upp_axis, upp_vec
# Ensure we deal with normalized vectors
look_vec.normalize()
upp_vec.normalize()
side_vec = Vector.cross(look_vec, upp_vec)
side_vec.normalize()
# recross in case up and front were not originally orthogonal:
upp_vec = Vector.cross(side_vec, look_vec)
#
# Build resulting matrix
#
tm = Matrix(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
# Add look component
axis = look_axis
vec = look_vec
tm += Matrix(
[axis.x * vec.x, axis.x * vec.y, axis.x * vec.z, 0],
[axis.y * vec.x, axis.y * vec.y, axis.y * vec.z, 0],
[axis.z * vec.x, axis.z * vec.y, axis.z * vec.z, 0],
[0, 0, 0, 0],
)
# Add upp component
axis = upp_axis
vec = upp_vec
tm += Matrix(
[axis.x * vec.x, axis.x * vec.y, axis.x * vec.z, 0],
[axis.y * vec.x, axis.y * vec.y, axis.y * vec.z, 0],
[axis.z * vec.x, axis.z * vec.y, axis.z * vec.z, 0],
[0, 0, 0, 0],
)
# Add side component
axis = look_axis.cross(upp_axis)
vec = side_vec
tm += Matrix(
[axis.x * vec.x, axis.x * vec.y, axis.x * vec.z, 0],
[axis.y * vec.x, axis.y * vec.y, axis.y * vec.z, 0],
[axis.z * vec.x, axis.z * vec.y, axis.z * vec.z, 0],
[0, 0, 0, 0],
)
return tm
def align_joints_to_view(joints, cam, affect_pos=True, look_axis=Vector.xAxis, upp_axis=Vector.zAxis):
"""
Align the up axis of selected joints to the look axis of a camera.
"""
pos_start = joints[0].getTranslation(space="world")
# Get camera direction
cam_pos = cam.getTranslation(space="world")
direction = cam_pos - pos_start
align_joints_to_direction(
joints, direction, affect_pos=affect_pos, look_axis=look_axis, upp_axis=upp_axis
)
def align_joints_to_direction(
joints, direction, affect_pos=True, look_axis=Vector.xAxis, upp_axis=Vector.zAxis
):
"""
Align the up axis of selected joints to a direction vector.
"""
pos_start = joints[0].getTranslation(space="world")
# Conform direction to Vector
direction = Vector(direction)
direction.normalize() # TODO: Do no modify by reference
# Store original positions
positions_orig = [joint.getTranslation(space="world") for joint in joints]
# Compute positions that respect the plane
positions = []
if affect_pos:
pos_inn = positions_orig[0]
pos_out = positions_orig[-1]
dir = pos_out - pos_inn
ref_tm = get_matrix_from_direction(dir, direction)
ref_tm.translate = pos_inn
ref_tm_inv = ref_tm.inverse()
for i in range(len(joints)):
joint_pos = positions_orig[i]
if i == 0:
positions.append(joint_pos)
else:
joint_local_pos = (joint_pos - pos_start) * ref_tm_inv
# Remove any translate out of the 2D plane
multiplier = look_axis + upp_axis
joint_local_pos.x *= multiplier.x
joint_local_pos.y *= multiplier.y
joint_local_pos.z *= multiplier.z
new_joint_pos = (joint_local_pos * ref_tm) + pos_start
positions.append(new_joint_pos)
else:
for joint in joints:
positions.append(joint.getTranslation(space="world"))
# Compute transforms
transforms = []
num_positions = len(positions)
for i in range(num_positions):
pos_inn = positions[i]
# Compute rotation-only matrix
if i < num_positions - 1:
pos_out = positions[i + 1]
# Compute look axis
x_axis = pos_out - pos_inn
x_axis.normalize()
# Compute side axis
z_axis = Vector(x_axis).cross(direction)
# Compute up axis (corrected)
y_axis = z_axis.cross(x_axis)
# Next ref_y_axis will use parent correct up axis to prevent flipping
direction = y_axis
tm = get_matrix_from_direction(x_axis, y_axis, look_axis=look_axis, upp_axis=upp_axis)
else:
tm = transforms[i - 1].copy() # Last joint share the same rotation as it's parent
# Add translation
if affect_pos:
tm.translate = pos_inn
else:
tm.translate = positions_orig[i]
transforms.append(tm)
# Apply transforms
for transform, node in zip(transforms, joints):
node.setMatrix(transform, worldSpace=True)
def get_active_camera():
"""
Return the active camera.
Thanks to Nohra Seif for the snippet!
"""
# seems that $gMainPane contain the name of the main window pane layout holding the panels.
main_pane = mel.eval("string $test = $gMainPane;")
if main_pane != "":
# get the layout's immediate children
main_pane_ctrls = pymel.paneLayout(main_pane, q=True, childArray=True)
for i in range(len(main_pane_ctrls)):
# panel containing the specified control
panel_name = pymel.getPanel(containing=main_pane_ctrls[i])
if "" != panel_name:
# Return the type of the specified panel.
if "modelPanel" == pymel.getPanel(typeOf=panel_name):
# Return whether the control can actually be seen by the user, isObscured for invisible
if not (pymel.control(main_pane_ctrls[i], q=True, isObscured=True)):
model_editor = pymel.modelPanel(panel_name, q=True, modelEditor=True)
if model_editor:
# If this view is already active, let's continue to use it.
if pymel.modelEditor(model_editor, q=True, activeView=True):
# get the camera in the current modelPanel
return pymel.PyNode(pymel.modelPanel(model_editor, q=True, camera=True))
def main():
sel = pymel.selected()
if not sel:
pymel.warning("Please select joints")
return
cam = get_active_camera()
align_joints_to_view(sel, cam)
for obj in sel:
transfer_rotation_to_joint_orient(obj)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment