Created
August 5, 2020 01:20
-
-
Save renaudll/aaa73ae71454fb4df99b8051a7cc9fe9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| 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