Source code for blenderproc.python.types.ArmatureUtility

from typing import Union, List, Optional
import numpy as np
from mathutils import Euler, Vector

import bpy

from blenderproc.python.utility.Utility import Utility
from blenderproc.python.types.EntityUtility import Entity


[docs]class Armature(Entity): def __init__(self, bpy_object: bpy.types.Object): super().__init__(bpy_object=bpy_object)
[docs] def set_rotation_euler(self, rotation_euler: Union[float, list, Euler, np.ndarray], mode: str = "absolute", frame: int = None): """ Rotates the armature based on euler angles. Validates values with given constraints. :param rotation_euler: The amount of rotation (in radians). Either three floats for x, y and z axes, or a single float. In the latter case, the axis of rotation is derived based on the rotation constraint. If these are not properly set (i.e., two axes must have equal min/max values) an exception will be thrown. :param mode: One of ["absolute", "relative"]. For absolute rotations we clip the rotation value based on the constraints. For relative we don't - this will result in inverse motion after the constraint's limits have been reached. :param frame: Keyframe where to insert the respective rotations. """ assert mode in ["absolute", "relative"] bpy.ops.object.select_all(action='DESELECT') bone = self.blender_obj.pose.bones.get('Bone') bone.bone.select = True bone.rotation_mode = 'XYZ' # in absolute mode we overwrite the rotation values of the armature if mode == "absolute": if isinstance(rotation_euler, float): axis = self._determine_rotation_axis() rotation_euler = self._clip_value_from_constraint(value=rotation_euler, constraint_name="Limit Rotation", axis=axis) current_rotation_euler = bone.rotation_euler current_rotation_euler[["X", "Y", "Z"].index(axis)] = rotation_euler bone.rotation_euler = current_rotation_euler print(f"Set rotation_euler of armature {self.get_name()} to {rotation_euler}") else: bone.rotation_euler = Vector([self._clip_value_from_constraint(value=rot_euler, constraint_name="Limit Rotation", axis=axis) for rot_euler, axis in zip(rotation_euler, ["X", "Y", "Z"])]) print(f"Set rotation_euler of armature {self.get_name()} to {rotation_euler}") # in relative mode we add the rotation to the current value elif mode == "relative": if isinstance(rotation_euler, float): axis = self._determine_rotation_axis() bone.rotation_euler.rotate_axis(axis, rotation_euler) print(f"Relatively rotated armature {self.get_name()} around axis {axis} for {rotation_euler} radians") else: for axis, rotation in zip(["X", "Y", "Z"], rotation_euler): bone.rotation_euler.rotate_axis(axis, rotation) print(f"Relatively rotated armature {self.get_name()} for {rotation_euler} radians") Utility.insert_keyframe(bone, "rotation_euler", frame) if frame is not None and frame > bpy.context.scene.frame_end: bpy.context.scene.frame_end += 1
[docs] def _determine_rotation_axis(self): """ Determines the single rotation axis and checks if the constraints are set well to have only one axis of freedom. :return: The single rotation axis ('X', 'Y' or 'Z'). """ c = self.get_constraint(constraint_name="Limit Rotation") assert c is not None, f"Tried to determine the single rotation axis but no rotation constraints are set!" axes = ['X', 'Y', 'Z'] if c.use_limit_x and c.min_x == c.max_x: axes.pop(axes.index('X')) if c.use_limit_y and c.min_y == c.max_y: axes.pop(axes.index('Y')) if c.use_limit_z and c.min_z == c.max_z: axes.pop(axes.index('Z')) assert len(axes) == 1, f"Constraints are set wrong for a rotation around a single axis. Only one axis should " \ f"be allowed to move, but found freedom in {len(axes)} axes of armature " \ f"{self.get_name()} (constraint: {c}, uses limits (xyz): " \ f"{c.use_limit_x, c.use_limit_y, c.use_limit_z}, " \ f"values: {c.min_x, c.max_x, c.min_y, c.max_y, c.min_z, c.max_z})." return axes[0]
[docs] def _clip_value_from_constraint(self, value: float, constraint_name: str, axis: str) -> float: """ Checks if an axis is constraint, and clips the value to the min/max of this constraint. If the constraint does not exist, nothing is done. :param value: Value to be clipped. :param constraint_name: Name of the constraint. :param axis: Axis to check. :return: Clipped value if a constraint is set, else the initial value. """ c = self.get_constraint(constraint_name=constraint_name) if c is not None: min_value = eval(f"c.min_{axis.lower()}") max_value = eval(f"c.max_{axis.lower()}") print(f"Clipping {value} to be in range {min_value}, {max_value}") if value < min_value: return min_value elif value > max_value: return max_value return value
[docs] def add_constraint_if_not_existing(self, constraint_name: str) -> bpy.types.Constraint: """ Adds a new constraint if it doesn't exist, and returns the specified constraint. :param constraint_name: Name of the desired constraint. """ if constraint_name not in self.blender_obj.pose.bones["Bone"].constraints.keys(): self.blender_obj.pose.bones["Bone"].constraints.new(constraint_name.upper().replace(' ', '_')) return self.blender_obj.pose.bones["Bone"].constraints[constraint_name]
[docs] def set_rotation_constraint(self, x_limits: Optional[List[float]] = None, y_limits: Optional[List[float]] = None, z_limits: Optional[List[float]] = None): """ Sets rotation constraints on the armature's bone. :param x_limits: A list of two float values specifying min/max radiant values along the x-axis or None if no constraint should be applied. :param y_limits: A list of two float values specifying min/max radiant values along the y-axis or None if no constraint should be applied. :param z_limits: A list of two float values specifying min/max radiant values along the z-axis or None if no constraint should be applied. """ if x_limits is None and y_limits is None and z_limits is None: return # add new constraint if it doesn't exist constraint = self.add_constraint_if_not_existing(constraint_name="Limit Rotation") if x_limits is not None: constraint.use_limit_x = True constraint.min_x, constraint.max_x = x_limits if y_limits is not None: constraint.use_limit_y = True constraint.min_y, constraint.max_y = y_limits if z_limits is not None: constraint.use_limit_z = True constraint.min_z, constraint.max_z = z_limits constraint.owner_space = "LOCAL"
[docs] def set_location_constraint(self, x_limits: Optional[List[float]] = None, y_limits: Optional[List[float]] = None, z_limits: Optional[List[float]] = None): """ Sets location constraints on the armature's bone. :param x_limits: A list of two float values specifying min/max values along the x-axis or None if no constraint should be applied. :param y_limits: A list of two float values specifying min/max values along the y-axis or None if no constraint should be applied. :param z_limits: A list of two float values specifying min/max values along the z-axis or None if no constraint should be applied. """ if x_limits is None and y_limits is None and z_limits is None: return # add new constraint if it doesn't exist constraint = self.add_constraint_if_not_existing(constraint_name="Limit Location") if x_limits is not None: constraint.use_min_x = True constraint.use_max_x = True constraint.min_x, constraint.max_x = x_limits if y_limits is not None: constraint.use_min_y = True constraint.use_max_y = True constraint.min_y, constraint.max_y = y_limits if z_limits is not None: constraint.use_min_z = True constraint.use_max_z = True constraint.min_z, constraint.max_z = z_limits constraint.owner_space = "LOCAL"
[docs] def get_constraint(self, constraint_name: str) -> Optional[bpy.types.Constraint]: """ Returns the desired constraint if existing; otherwise None. :param constraint_name: Name of the constraint. :return: Constraint if it exists; else None. """ if constraint_name in self.blender_obj.pose.bones["Bone"].constraints.keys(): return self.blender_obj.pose.bones["Bone"].constraints[constraint_name] return None
[docs] def get_location_constraint(self) -> Optional[bpy.types.Constraint]: """ Returns the location constraint if existing; otherwise None. :return: Location constraint if it exists; else None. """ return self.get_constraint(constraint_name="Limit Location")
[docs] def get_rotation_constraint(self) -> Optional[bpy.types.Constraint]: """ Returns the rotation constraint if existing; otherwise None. :return: Rotation constraint if it exists; else None. """ return self.get_constraint(constraint_name="Limit Rotation")
[docs] def remove_constraint(self, constraint_key: str): """ Removes a specified constraint. :param constraint_key: Key to be removed. """ bone = self.blender_obj.pose.bones["Bone"] bone.constraints.remove(bone.constraints[constraint_key])
[docs] def remove_constraints(self): """ Removes all constraints of the armature. """ bone = self.blender_obj.pose.bones["Bone"] for constraint_key in bone.constraints.keys(): self.remove_constraint(constraint_key=constraint_key)
[docs] def hide(self, hide_object: bool = True): """ Sets the visibility of the object. :param hide_object: Determines whether the object should be hidden in rendering. """ self.blender_obj.hide_render = hide_object