Files
blender/release/scripts/ui/buttons_object_constraint.py
Joshua Leung ae3cf92491 New Track To Constraint: "Damped"
This is effectively a C-port of Nathan Vegdahl's "No Twist" TrackTo PyConstraint, and has been added as a separate type of constraint to be consistent with the existing constraints (Locked Track, and Track To). 

In general, this works considerably better than the existing "Track To" constraint, since it works by determining the smallest rotation necessary to get the current orientation of the owner to an orientation which would be tracking the target. It is also a much more straightforward approach than the weird old method the old Track To uses.

I've made a few tweaks to the code to deal with the (hopefully rare) cases where the target and the constrained are coincident. These don't appear to cause too much trouble in general.

TODO:
- Probably the naming of the constraints will change, to better convey their purposes. Naming suggestions welcome.
2009-10-30 06:33:40 +00:00

754 lines
20 KiB
Python

import bpy
class ConstraintButtonsPanel(bpy.types.Panel):
__space_type__ = 'PROPERTIES'
__region_type__ = 'WINDOW'
__context__ = "constraint"
def draw_constraint(self, context, con):
layout = self.layout
box = layout.template_constraint(con)
if box:
# match enum type to our functions, avoids a lookup table.
getattr(self, con.type)(context, box, con)
# show/key buttons here are most likely obsolete now, with
# keyframing functionality being part of every button
if con.type not in ('RIGID_BODY_JOINT', 'NULL'):
box.itemR(con, "influence")
def space_template(self, layout, con, target=True, owner=True):
if target or owner:
row = layout.row()
row.itemL(text="Convert:")
if target:
row.itemR(con, "target_space", text="")
if target and owner:
row.itemL(icon='ICON_ARROW_LEFTRIGHT')
if owner:
row.itemR(con, "owner_space", text="")
def target_template(self, layout, con, subtargets=True):
layout.itemR(con, "target") # XXX limiting settings for only 'curves' or some type of object
if con.target and subtargets:
if con.target.type == 'ARMATURE':
layout.item_pointerR(con, "subtarget", con.target.data, "bones", text="Bone")
if con.type == 'COPY_LOCATION':
row = layout.row()
row.itemL(text="Head/Tail:")
row.itemR(con, "head_tail", text="")
elif con.target.type in ('MESH', 'LATTICE'):
layout.item_pointerR(con, "subtarget", con.target, "vertex_groups", text="Vertex Group")
def ik_template(self, layout, con):
# only used for iTaSC
layout.itemR(con, "pole_target")
if con.pole_target and con.pole_target.type == 'ARMATURE':
layout.item_pointerR(con, "pole_subtarget", con.pole_target.data, "bones", text="Bone")
if con.pole_target:
row = layout.row()
row.itemL()
row.itemR(con, "pole_angle")
split = layout.split(percentage=0.33)
col = split.column()
col.itemR(con, "tail")
col.itemR(con, "stretch")
col = split.column()
col.itemR(con, "chain_length")
col.itemR(con, "targetless")
def CHILD_OF(self, context, layout, con):
self.target_template(layout, con)
split = layout.split()
col = split.column()
col.itemL(text="Location:")
col.itemR(con, "locationx", text="X")
col.itemR(con, "locationy", text="Y")
col.itemR(con, "locationz", text="Z")
col = split.column()
col.itemL(text="Rotation:")
col.itemR(con, "rotationx", text="X")
col.itemR(con, "rotationy", text="Y")
col.itemR(con, "rotationz", text="Z")
col = split.column()
col.itemL(text="Scale:")
col.itemR(con, "sizex", text="X")
col.itemR(con, "sizey", text="Y")
col.itemR(con, "sizez", text="Z")
row = layout.row()
row.itemO("constraint.childof_set_inverse")
row.itemO("constraint.childof_clear_inverse")
def TRACK_TO(self, context, layout, con):
self.target_template(layout, con)
row = layout.row()
row.itemL(text="To:")
row.itemR(con, "track", expand=True)
row = layout.row()
#row.itemR(con, "up", text="Up", expand=True) # XXX: up and expand don't play nice together
row.itemR(con, "up", text="Up")
row.itemR(con, "target_z")
self.space_template(layout, con)
def IK(self, context, layout, con):
if context.object.pose.ik_solver == "ITASC":
layout.itemR(con, "ik_type")
getattr(self, "IK_"+con.ik_type)(context, layout, con)
else:
# Legacy IK constraint
self.target_template(layout, con)
layout.itemR(con, "pole_target")
if con.pole_target and con.pole_target.type == 'ARMATURE':
layout.item_pointerR(con, "pole_subtarget", con.pole_target.data, "bones", text="Bone")
if con.pole_target:
row = layout.row()
row.itemL()
row.itemR(con, "pole_angle")
split = layout.split()
col = split.column()
col.itemR(con, "tail")
col.itemR(con, "stretch")
col = split.column()
col.itemR(con, "iterations")
col.itemR(con, "chain_length")
split = layout.split()
col = split.column()
col.itemL()
col.itemR(con, "targetless")
col.itemR(con, "rotation")
col = split.column()
col.itemL(text="Weight:")
col.itemR(con, "weight", text="Position", slider=True)
sub = col.column()
sub.active = con.rotation
sub.itemR(con, "orient_weight", text="Rotation", slider=True)
def IK_COPY_POSE(self, context, layout, con):
self.target_template(layout, con)
self.ik_template(layout, con)
row = layout.row()
row.itemL(text="Axis Ref:")
row.itemR(con, "axis_reference", expand=True)
split = layout.split(percentage=0.33)
split.row().itemR(con, "position")
row = split.row()
row.itemR(con, "weight", text="Weight", slider=True)
row.active = con.position
split = layout.split(percentage=0.33)
row = split.row()
row.itemL(text="Lock:")
row = split.row()
row.itemR(con, "pos_lock_x", text="X")
row.itemR(con, "pos_lock_y", text="Y")
row.itemR(con, "pos_lock_z", text="Z")
split.active = con.position
split = layout.split(percentage=0.33)
split.row().itemR(con, "rotation")
row = split.row()
row.itemR(con, "orient_weight", text="Weight", slider=True)
row.active = con.rotation
split = layout.split(percentage=0.33)
row = split.row()
row.itemL(text="Lock:")
row = split.row()
row.itemR(con, "rot_lock_x", text="X")
row.itemR(con, "rot_lock_y", text="Y")
row.itemR(con, "rot_lock_z", text="Z")
split.active = con.rotation
def IK_DISTANCE(self, context, layout, con):
self.target_template(layout, con)
self.ik_template(layout, con)
layout.itemR(con, "limit_mode")
row = layout.row()
row.itemR(con, "weight", text="Weight", slider=True)
row.itemR(con, "distance", text="Distance", slider=True)
def FOLLOW_PATH(self, context, layout, con):
self.target_template(layout, con)
split = layout.split()
col = split.column()
col.itemR(con, "use_curve_follow")
col.itemR(con, "use_curve_radius")
col = split.column()
col.itemR(con, "use_fixed_position")
if con.use_fixed_position:
col.itemR(con, "offset_factor", text="Offset")
else:
col.itemR(con, "offset")
row = layout.row()
row.itemL(text="Forward:")
row.itemR(con, "forward", expand=True)
row = layout.row()
row.itemR(con, "up", text="Up")
row.itemL()
def LIMIT_ROTATION(self, context, layout, con):
split = layout.split()
col = split.column()
col.itemR(con, "use_limit_x")
sub = col.column()
sub.active = con.use_limit_x
sub.itemR(con, "minimum_x", text="Min")
sub.itemR(con, "maximum_x", text="Max")
col = split.column()
col.itemR(con, "use_limit_y")
sub = col.column()
sub.active = con.use_limit_y
sub.itemR(con, "minimum_y", text="Min")
sub.itemR(con, "maximum_y", text="Max")
col = split.column()
col.itemR(con, "use_limit_z")
sub = col.column()
sub.active = con.use_limit_z
sub.itemR(con, "minimum_z", text="Min")
sub.itemR(con, "maximum_z", text="Max")
row = layout.row()
row.itemR(con, "limit_transform")
row.itemL()
row = layout.row()
row.itemL(text="Convert:")
row.itemR(con, "owner_space", text="")
def LIMIT_LOCATION(self, context, layout, con):
split = layout.split()
col = split.column()
col.itemR(con, "use_minimum_x")
sub = col.column()
sub.active = con.use_minimum_x
sub.itemR(con, "minimum_x", text="")
col.itemR(con, "use_maximum_x")
sub = col.column()
sub.active = con.use_maximum_x
sub.itemR(con, "maximum_x", text="")
col = split.column()
col.itemR(con, "use_minimum_y")
sub = col.column()
sub.active = con.use_minimum_y
sub.itemR(con, "minimum_y", text="")
col.itemR(con, "use_maximum_y")
sub = col.column()
sub.active = con.use_maximum_y
sub.itemR(con, "maximum_y", text="")
col = split.column()
col.itemR(con, "use_minimum_z")
sub = col.column()
sub.active = con.use_minimum_z
sub.itemR(con, "minimum_z", text="")
col.itemR(con, "use_maximum_z")
sub = col.column()
sub.active = con.use_maximum_z
sub.itemR(con, "maximum_z", text="")
row = layout.row()
row.itemR(con, "limit_transform")
row.itemL()
row = layout.row()
row.itemL(text="Convert:")
row.itemR(con, "owner_space", text="")
def LIMIT_SCALE(self, context, layout, con):
split = layout.split()
col = split.column()
col.itemR(con, "use_minimum_x")
sub = col.column()
sub.active = con.use_minimum_x
sub.itemR(con, "minimum_x", text="")
col.itemR(con, "use_maximum_x")
sub = col.column()
sub.active = con.use_maximum_x
sub.itemR(con, "maximum_x", text="")
col = split.column()
col.itemR(con, "use_minimum_y")
sub = col.column()
sub.active = con.use_minimum_y
sub.itemR(con, "minimum_y", text="")
col.itemR(con, "use_maximum_y")
sub = col.column()
sub.active = con.use_maximum_y
sub.itemR(con, "maximum_y", text="")
col = split.column()
col.itemR(con, "use_minimum_z")
sub = col.column()
sub.active = con.use_minimum_z
sub.itemR(con, "minimum_z", text="")
col.itemR(con, "use_maximum_z")
sub = col.column()
sub.active = con.use_maximum_z
sub.itemR(con, "maximum_z", text="")
row = layout.row()
row.itemR(con, "limit_transform")
row.itemL()
row = layout.row()
row.itemL(text="Convert:")
row.itemR(con, "owner_space", text="")
def COPY_ROTATION(self, context, layout, con):
self.target_template(layout, con)
split = layout.split()
col = split.column()
col.itemR(con, "rotate_like_x", text="X")
sub = col.column()
sub.active = con.rotate_like_x
sub.itemR(con, "invert_x", text="Invert")
col = split.column()
col.itemR(con, "rotate_like_y", text="Y")
sub = col.column()
sub.active = con.rotate_like_y
sub.itemR(con, "invert_y", text="Invert")
col = split.column()
col.itemR(con, "rotate_like_z", text="Z")
sub = col.column()
sub.active = con.rotate_like_z
sub.itemR(con, "invert_z", text="Invert")
layout.itemR(con, "offset")
self.space_template(layout, con)
def COPY_LOCATION(self, context, layout, con):
self.target_template(layout, con)
split = layout.split()
col = split.column()
col.itemR(con, "locate_like_x", text="X")
sub = col.column()
sub.active = con.locate_like_x
sub.itemR(con, "invert_x", text="Invert")
col = split.column()
col.itemR(con, "locate_like_y", text="Y")
sub = col.column()
sub.active = con.locate_like_y
sub.itemR(con, "invert_y", text="Invert")
col = split.column()
col.itemR(con, "locate_like_z", text="Z")
sub = col.column()
sub.active = con.locate_like_z
sub.itemR(con, "invert_z", text="Invert")
layout.itemR(con, "offset")
self.space_template(layout, con)
def COPY_SCALE(self, context, layout, con):
self.target_template(layout, con)
row = layout.row(align=True)
row.itemR(con, "size_like_x", text="X")
row.itemR(con, "size_like_y", text="Y")
row.itemR(con, "size_like_z", text="Z")
layout.itemR(con, "offset")
self.space_template(layout, con)
#def SCRIPT(self, context, layout, con):
def ACTION(self, context, layout, con):
self.target_template(layout, con)
layout.itemR(con, "action")
layout.itemR(con, "transform_channel")
split = layout.split()
col = split.column(align=True)
col.itemR(con, "start_frame", text="Start")
col.itemR(con, "end_frame", text="End")
col = split.column(align=True)
col.itemR(con, "minimum", text="Min")
col.itemR(con, "maximum", text="Max")
row = layout.row()
row.itemL(text="Convert:")
row.itemR(con, "owner_space", text="")
def LOCKED_TRACK(self, context, layout, con):
self.target_template(layout, con)
row = layout.row()
row.itemL(text="To:")
row.itemR(con, "track", expand=True)
row = layout.row()
row.itemL(text="Lock:")
row.itemR(con, "locked", expand=True)
def LIMIT_DISTANCE(self, context, layout, con):
self.target_template(layout, con)
col = layout.column(align=True);
col.itemR(con, "distance")
col.itemO("constraint.limitdistance_reset")
row = layout.row()
row.itemL(text="Clamp Region:")
row.itemR(con, "limit_mode", text="")
def STRETCH_TO(self, context, layout, con):
self.target_template(layout, con)
row = layout.row()
row.itemR(con, "original_length", text="Rest Length")
row.itemO("constraint.stretchto_reset", text="Reset")
col = layout.column()
col.itemR(con, "bulge", text="Volume Variation")
row = layout.row()
row.itemL(text="Volume:")
row.itemR(con, "volume", expand=True)
row.itemL(text="Plane:")
row.itemR(con, "keep_axis", expand=True)
def FLOOR(self, context, layout, con):
self.target_template(layout, con)
row = layout.row()
row.itemR(con, "sticky")
row.itemR(con, "use_rotation")
layout.itemR(con, "offset")
row = layout.row()
row.itemL(text="Min/Max:")
row.itemR(con, "floor_location", expand=True)
def RIGID_BODY_JOINT(self, context, layout, con):
self.target_template(layout, con)
layout.itemR(con, "pivot_type")
layout.itemR(con, "child")
row = layout.row()
row.itemR(con, "disable_linked_collision", text="No Collision")
row.itemR(con, "draw_pivot", text="Display Pivot")
split = layout.split()
col = split.column(align=True)
col.itemL(text="Pivot:")
col.itemR(con, "pivot_x", text="X")
col.itemR(con, "pivot_y", text="Y")
col.itemR(con, "pivot_z", text="Z")
col = split.column(align=True)
col.itemL(text="Axis:")
col.itemR(con, "axis_x", text="X")
col.itemR(con, "axis_y", text="Y")
col.itemR(con, "axis_z", text="Z")
#Missing: Limit arrays (not wrapped in RNA yet)
def CLAMP_TO(self, context, layout, con):
self.target_template(layout, con)
row = layout.row()
row.itemL(text="Main Axis:")
row.itemR(con, "main_axis", expand=True)
row = layout.row()
row.itemR(con, "cyclic")
def TRANSFORM(self, context, layout, con):
self.target_template(layout, con)
layout.itemR(con, "extrapolate_motion", text="Extrapolate")
split = layout.split()
col = split.column()
col.itemL(text="Source:")
col.row().itemR(con, "map_from", expand=True)
sub = col.row(align=True)
sub.itemL(text="X:")
sub.itemR(con, "from_min_x", text="")
sub.itemR(con, "from_max_x", text="")
sub = col.row(align=True)
sub.itemL(text="Y:")
sub.itemR(con, "from_min_y", text="")
sub.itemR(con, "from_max_y", text="")
sub = col.row(align=True)
sub.itemL(text="Z:")
sub.itemR(con, "from_min_z", text="")
sub.itemR(con, "from_max_z", text="")
split = layout.split()
col = split.column()
col.itemL(text="Destination:")
col.row().itemR(con, "map_to", expand=True)
sub = col.row(align=True)
sub.itemR(con, "map_to_x_from", text="")
sub.itemR(con, "to_min_x", text="")
sub.itemR(con, "to_max_x", text="")
sub = col.row(align=True)
sub.itemR(con, "map_to_y_from", text="")
sub.itemR(con, "to_min_y", text="")
sub.itemR(con, "to_max_y", text="")
sub = col.row(align=True)
sub.itemR(con, "map_to_z_from", text="")
sub.itemR(con, "to_min_z", text="")
sub.itemR(con, "to_max_z", text="")
self.space_template(layout, con)
def SHRINKWRAP (self, context, layout, con):
self.target_template(layout, con)
layout.itemR(con, "distance")
layout.itemR(con, "shrinkwrap_type")
if con.shrinkwrap_type == 'PROJECT':
row = layout.row(align=True)
row.itemR(con, "axis_x")
row.itemR(con, "axis_y")
row.itemR(con, "axis_z")
def DAMPED_TRACK(self, context, layout, con):
self.target_template(layout, con)
row = layout.row()
row.itemL(text="To:")
row.itemR(con, "track", expand=True)
class OBJECT_PT_constraints(ConstraintButtonsPanel):
__label__ = "Constraints"
__context__ = "constraint"
def poll(self, context):
return (context.object)
def draw(self, context):
layout = self.layout
ob = context.object
row = layout.row()
row.item_menu_enumO("object.constraint_add", "type")
row.itemL();
for con in ob.constraints:
self.draw_constraint(context, con)
class BONE_PT_inverse_kinematics(ConstraintButtonsPanel):
__label__ = "Inverse Kinematics"
__default_closed__ = True
__context__ = "bone_constraint"
def poll(self, context):
ob = context.object
bone = context.bone
if ob and bone:
pchan = ob.pose.pose_channels[bone.name]
return pchan.has_ik
return False
def draw(self, context):
layout = self.layout
ob = context.object
bone = context.bone
pchan = ob.pose.pose_channels[bone.name]
row = layout.row()
row.itemR(ob.pose, "ik_solver")
split = layout.split(percentage=0.25)
split.itemR(pchan, "ik_dof_x", text="X")
row = split.row()
row.itemR(pchan, "ik_stiffness_x", text="Stiffness", slider=True)
row.active = pchan.ik_dof_x
split = layout.split(percentage=0.25)
row = split.row()
row.itemR(pchan, "ik_limit_x", text="Limit")
row.active = pchan.ik_dof_x
row = split.row(align=True)
row.itemR(pchan, "ik_min_x", text="")
row.itemR(pchan, "ik_max_x", text="")
row.active = pchan.ik_dof_x and pchan.ik_limit_x
split = layout.split(percentage=0.25)
split.itemR(pchan, "ik_dof_y", text="Y")
row = split.row()
row.itemR(pchan, "ik_stiffness_y", text="Stiffness", slider=True)
row.active = pchan.ik_dof_y
split = layout.split(percentage=0.25)
row = split.row()
row.itemR(pchan, "ik_limit_y", text="Limit")
row.active = pchan.ik_dof_y
row = split.row(align=True)
row.itemR(pchan, "ik_min_y", text="")
row.itemR(pchan, "ik_max_y", text="")
row.active = pchan.ik_dof_y and pchan.ik_limit_y
split = layout.split(percentage=0.25)
split.itemR(pchan, "ik_dof_z", text="Z")
row = split.row()
row.itemR(pchan, "ik_stiffness_z", text="Stiffness", slider=True)
row.active = pchan.ik_dof_z
split = layout.split(percentage=0.25)
row = split.row()
row.itemR(pchan, "ik_limit_z", text="Limit")
row.active = pchan.ik_dof_z
row = split.row(align=True)
row.itemR(pchan, "ik_min_z", text="")
row.itemR(pchan, "ik_max_z", text="")
row.active = pchan.ik_dof_z and pchan.ik_limit_z
split = layout.split()
split.itemR(pchan, "ik_stretch", text="Stretch", slider=True)
split.itemL()
if ob.pose.ik_solver == "ITASC":
row = layout.row()
row.itemR(pchan, "ik_rot_control", text="Control Rotation")
row.itemR(pchan, "ik_rot_weight", text="Weight", slider=True)
# not supported yet
#row = layout.row()
#row.itemR(pchan, "ik_lin_control", text="Joint Size")
#row.itemR(pchan, "ik_lin_weight", text="Weight", slider=True)
class BONE_PT_iksolver_itasc(ConstraintButtonsPanel):
__label__ = "iTaSC parameters"
__default_closed__ = True
__context__ = "bone_constraint"
def poll(self, context):
ob = context.object
bone = context.bone
if ob and bone:
pchan = ob.pose.pose_channels[bone.name]
return pchan.has_ik and ob.pose.ik_solver == "ITASC" and ob.pose.ik_param
return False
def draw(self, context):
layout = self.layout
ob = context.object
itasc = ob.pose.ik_param
layout.itemR(itasc, "mode", expand=True)
simulation = itasc.mode == "SIMULATION"
if simulation:
layout.itemL(text="Reiteration:")
layout.itemR(itasc, "reiteration", expand=True)
flow = layout.column_flow()
flow.itemR(itasc, "precision", text="Prec")
flow.itemR(itasc, "num_iter", text="Iter")
flow.active = not simulation or itasc.reiteration != "NEVER"
if simulation:
layout.itemR(itasc, "auto_step")
row = layout.row()
if itasc.auto_step:
row.itemR(itasc, "min_step", text="Min")
row.itemR(itasc, "max_step", text="Max")
else:
row.itemR(itasc, "num_step")
layout.itemR(itasc, "solver")
if simulation:
layout.itemR(itasc, "feedback")
layout.itemR(itasc, "max_velocity")
if itasc.solver == "DLS":
row = layout.row()
row.itemR(itasc, "dampmax", text="Damp", slider=True)
row.itemR(itasc, "dampeps", text="Eps", slider=True)
class BONE_PT_constraints(ConstraintButtonsPanel):
__label__ = "Constraints"
__context__ = "bone_constraint"
def poll(self, context):
ob = context.object
return (ob and ob.type == 'ARMATURE' and context.bone)
def draw(self, context):
layout = self.layout
ob = context.object
pchan = ob.pose.pose_channels[context.bone.name]
row = layout.row()
row.item_menu_enumO("pose.constraint_add", "type")
row.itemL();
for con in pchan.constraints:
self.draw_constraint(context, con)
bpy.types.register(OBJECT_PT_constraints)
bpy.types.register(BONE_PT_iksolver_itasc)
bpy.types.register(BONE_PT_inverse_kinematics)
bpy.types.register(BONE_PT_constraints)