From ad43aaf881652a10a47a850277bfe3d376a82433 Mon Sep 17 00:00:00 2001 From: Joshua Leung Date: Sat, 12 Sep 2009 12:30:23 +0000 Subject: [PATCH] 2.5 - Rotation Locking for Bones * Added Transform Locks panel. The layout for rotation I'm not satisfied with yet, though it is the best alternative so far. * Rotations can now be locked per-component for quats/axis-angle instead of going through eulers. This is currently enabled by the checkbox for the 'label' of the Lock Rotation column. - The naming of the property in RNA + the way this is presented in the UI can get some work done. - The setting for the 'w' component for quats/axis-angle is currently a separate flag in RNA, since I can't figure out how to lump this in under the 'lock_rotation' property instead (i.e. getting that to be either 3 or 4 components, depending on whether per-component locking is enabled). - Editing values directly should not be possible when these locks are set... * Fixed some tools which made use of this --- release/ui/buttons_data_bone.py | 30 +++++ .../blender/editors/armature/editarmature.c | 80 +++++++----- source/blender/editors/armature/poseobject.c | 1 - source/blender/editors/transform/transform.c | 123 ++++++++++++++---- source/blender/makesdna/DNA_object_types.h | 2 + source/blender/makesrna/intern/rna_pose.c | 17 ++- 6 files changed, 186 insertions(+), 67 deletions(-) diff --git a/release/ui/buttons_data_bone.py b/release/ui/buttons_data_bone.py index 5aac44bbc94..a0121821e55 100644 --- a/release/ui/buttons_data_bone.py +++ b/release/ui/buttons_data_bone.py @@ -71,6 +71,35 @@ class BONE_PT_transform(BoneButtonsPanel): col = layout.column(align=True) col.itemL(text="Euler:") col.row().itemR(pchan, "euler_rotation", text="") + +class BONE_PT_transform_locks(BoneButtonsPanel): + __label__ = "Transform Locks" + + def poll(self, context): + return context.bone + + def draw(self, context): + layout = self.layout + + ob = context.object + bone = context.bone + pchan = ob.pose.pose_channels[context.bone.name] + + row = layout.row() + col = row.column() + col.itemR(pchan, "lock_location") + col.active = not (bone.parent and bone.connected) + + col = row.column() + if pchan.rotation_mode in ('QUATERNION', 'AXIS_ANGLE'): + col.itemR(pchan, "lock_rotations_4d", text="Lock Rotation") + if pchan.lock_rotations_4d: + col.itemR(pchan, "lock_rotation_w", text="W") + col.itemR(pchan, "lock_rotation", text="") + else: + col.itemR(pchan, "lock_rotation", text="Rotation") + + row.column().itemR(pchan, "lock_scale") class BONE_PT_bone(BoneButtonsPanel): __label__ = "Bone" @@ -243,6 +272,7 @@ class BONE_PT_deform(BoneButtonsPanel): bpy.types.register(BONE_PT_context_bone) bpy.types.register(BONE_PT_transform) +bpy.types.register(BONE_PT_transform_locks) bpy.types.register(BONE_PT_bone) bpy.types.register(BONE_PT_deform) bpy.types.register(BONE_PT_inverse_kinematics) diff --git a/source/blender/editors/armature/editarmature.c b/source/blender/editors/armature/editarmature.c index e2293b65878..7d196d23c98 100644 --- a/source/blender/editors/armature/editarmature.c +++ b/source/blender/editors/armature/editarmature.c @@ -4849,42 +4849,56 @@ static int pose_clear_rot_exec(bContext *C, wmOperator *op) /* only clear those channels that are not locked */ CTX_DATA_BEGIN(C, bPoseChannel*, pchan, selected_pchans) { - if (pchan->protectflag & (OB_LOCK_ROTX|OB_LOCK_ROTY|OB_LOCK_ROTZ)) { - float eul[3], oldeul[3], quat1[4]; - - if (pchan->rotmode == PCHAN_ROT_QUAT) { - QUATCOPY(quat1, pchan->quat); - QuatToEul(pchan->quat, oldeul); - } - else if (pchan->rotmode == PCHAN_ROT_AXISANGLE) { - continue; // XXX + if (pchan->protectflag & (OB_LOCK_ROTX|OB_LOCK_ROTY|OB_LOCK_ROTZ|OB_LOCK_ROTW)) { + /* check if convert to eulers for locking... */ + if (pchan->protectflag & OB_LOCK_ROT4D) { + /* perform clamping on a component by component basis */ + if ((pchan->protectflag & OB_LOCK_ROTW) == 0) + pchan->quat[0]= (pchan->rotmode == PCHAN_ROT_AXISANGLE) ? 0.0f : 1.0f; + if ((pchan->protectflag & OB_LOCK_ROTX) == 0) + pchan->quat[1]= 0.0f; + if ((pchan->protectflag & OB_LOCK_ROTY) == 0) + pchan->quat[2]= 0.0f; + if ((pchan->protectflag & OB_LOCK_ROTZ) == 0) + pchan->quat[3]= 0.0f; } else { - VECCOPY(oldeul, pchan->eul); - } - - eul[0]= eul[1]= eul[2]= 0.0f; - - // TODO: for 4 channel rotations, we need special flags for those too... - if (pchan->protectflag & OB_LOCK_ROTX) - eul[0]= oldeul[0]; - if (pchan->protectflag & OB_LOCK_ROTY) - eul[1]= oldeul[1]; - if (pchan->protectflag & OB_LOCK_ROTZ) - eul[2]= oldeul[2]; - - if (pchan->rotmode == PCHAN_ROT_QUAT) { - EulToQuat(eul, pchan->quat); - /* quaternions flip w sign to accumulate rotations correctly */ - if ((quat1[0]<0.0f && pchan->quat[0]>0.0f) || (quat1[0]>0.0f && pchan->quat[0]<0.0f)) { - QuatMulf(pchan->quat, -1.0f); + /* perform clamping using euler form (3-components) */ + float eul[3], oldeul[3], quat1[4]; + + if (pchan->rotmode == PCHAN_ROT_QUAT) { + QUATCOPY(quat1, pchan->quat); + QuatToEul(pchan->quat, oldeul); + } + else if (pchan->rotmode == PCHAN_ROT_AXISANGLE) { + AxisAngleToEulO(&pchan->quat[1], pchan->quat[0], oldeul, EULER_ORDER_DEFAULT); + } + else { + VECCOPY(oldeul, pchan->eul); + } + + eul[0]= eul[1]= eul[2]= 0.0f; + + if (pchan->protectflag & OB_LOCK_ROTX) + eul[0]= oldeul[0]; + if (pchan->protectflag & OB_LOCK_ROTY) + eul[1]= oldeul[1]; + if (pchan->protectflag & OB_LOCK_ROTZ) + eul[2]= oldeul[2]; + + if (pchan->rotmode == PCHAN_ROT_QUAT) { + EulToQuat(eul, pchan->quat); + /* quaternions flip w sign to accumulate rotations correctly */ + if ((quat1[0]<0.0f && pchan->quat[0]>0.0f) || (quat1[0]>0.0f && pchan->quat[0]<0.0f)) { + QuatMulf(pchan->quat, -1.0f); + } + } + else if (pchan->rotmode == PCHAN_ROT_AXISANGLE) { + AxisAngleToEulO(&pchan->quat[1], pchan->quat[0], oldeul, EULER_ORDER_DEFAULT); + } + else { + VECCOPY(pchan->eul, eul); } - } - else if (pchan->rotmode == PCHAN_ROT_AXISANGLE) { - // TODO... - } - else { - VECCOPY(pchan->eul, eul); } } else { diff --git a/source/blender/editors/armature/poseobject.c b/source/blender/editors/armature/poseobject.c index b473b9ee226..7d7f54309a8 100644 --- a/source/blender/editors/armature/poseobject.c +++ b/source/blender/editors/armature/poseobject.c @@ -998,7 +998,6 @@ static int pose_paste_exec (bContext *C, wmOperator *op) pchan->flag= chan->flag; /* check if rotation modes are compatible (i.e. do they need any conversions) */ - // FIXME: add axis-angle here too... if (pchan->rotmode == chan->rotmode) { /* copy the type of rotation in use */ if (pchan->rotmode > 0) { diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index 467f5c59e3d..e877f1fecae 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -1610,29 +1610,88 @@ static void protectedRotateBits(short protectflag, float *eul, float *oldeul) eul[2]= oldeul[2]; } + +/* this function only does the delta rotation */ +/* axis-angle is usually internally stored as quats... */ +static void protectedAxisAngleBits(short protectflag, float *quat, float *oldquat) +{ + /* check that protection flags are set */ + if ((protectflag & (OB_LOCK_ROTX|OB_LOCK_ROTY|OB_LOCK_ROTZ|OB_LOCK_ROTW)) == 0) + return; + + if (protectflag & OB_LOCK_ROT4D) { + /* axis-angle getting limited as 4D entities that they are... */ + if (protectflag & OB_LOCK_ROTW) + quat[0]= oldquat[0]; + if (protectflag & OB_LOCK_ROTX) + quat[1]= oldquat[1]; + if (protectflag & OB_LOCK_ROTY) + quat[2]= oldquat[2]; + if (protectflag & OB_LOCK_ROTZ) + quat[3]= oldquat[3]; + } + else { + /* axis-angle get limited with euler... */ + float eul[3], oldeul[3], quat1[4]; + + QUATCOPY(quat1, quat); + AxisAngleToEulO(quat+1, quat[0], eul, EULER_ORDER_DEFAULT); + AxisAngleToEulO(oldquat+1, oldquat[0], oldeul, EULER_ORDER_DEFAULT); + + if (protectflag & OB_LOCK_ROTX) + eul[0]= oldeul[0]; + if (protectflag & OB_LOCK_ROTY) + eul[1]= oldeul[1]; + if (protectflag & OB_LOCK_ROTZ) + eul[2]= oldeul[2]; + + EulOToAxisAngle(eul, EULER_ORDER_DEFAULT, quat+1, quat); + + /* when converting to axis-angle, we need a special exception for the case when there is no axis */ + if (IS_EQ(quat[1], quat[2]) && IS_EQ(quat[2], quat[3])) { + /* for now, rotate around y-axis then (so that it simply becomes the roll) */ + quat[2]= 1.0f; + } + } +} + +/* this function only does the delta rotation */ static void protectedQuaternionBits(short protectflag, float *quat, float *oldquat) { - /* quaternions get limited with euler... */ - /* this function only does the delta rotation */ + /* check that protection flags are set */ + if ((protectflag & (OB_LOCK_ROTX|OB_LOCK_ROTY|OB_LOCK_ROTZ|OB_LOCK_ROTW)) == 0) + return; - // FIXME: need special checks for quality here... - if(protectflag) { + if (protectflag & OB_LOCK_ROT4D) { + /* quaternions getting limited as 4D entities that they are... */ + if (protectflag & OB_LOCK_ROTW) + quat[0]= oldquat[0]; + if (protectflag & OB_LOCK_ROTX) + quat[1]= oldquat[1]; + if (protectflag & OB_LOCK_ROTY) + quat[2]= oldquat[2]; + if (protectflag & OB_LOCK_ROTZ) + quat[3]= oldquat[3]; + } + else { + /* quaternions get limited with euler... (compatability mode) */ float eul[3], oldeul[3], quat1[4]; - + QUATCOPY(quat1, quat); QuatToEul(quat, eul); QuatToEul(oldquat, oldeul); - - if(protectflag & OB_LOCK_ROTX) + + if (protectflag & OB_LOCK_ROTX) eul[0]= oldeul[0]; - if(protectflag & OB_LOCK_ROTY) + if (protectflag & OB_LOCK_ROTY) eul[1]= oldeul[1]; - if(protectflag & OB_LOCK_ROTZ) + if (protectflag & OB_LOCK_ROTZ) eul[2]= oldeul[2]; - + EulToQuat(eul, quat); + /* quaternions flip w sign to accumulate rotations correctly */ - if( (quat1[0]<0.0f && quat[0]>0.0f) || (quat1[0]>0.0f && quat[0]<0.0f) ) { + if ( (quat1[0]<0.0f && quat[0]>0.0f) || (quat1[0]>0.0f && quat[0]<0.0f) ) { QuatMulf(quat, -1.0f); } } @@ -1733,17 +1792,24 @@ static void constraintRotLim(TransInfo *t, TransData *td) else return; } - else if (td->tdi) { + else if (td->tdi) { // XXX depreceated /* ipo-keys eulers */ TransDataIpokey *tdi= td->tdi; float eul[3]; - + eul[0]= tdi->rotx[0]; eul[1]= tdi->roty[0]; eul[2]= tdi->rotz[0]; - + EulOToMat4(eul, td->rotOrder, cob.matrix); } + else if (td->rotOrder == PCHAN_ROT_AXISANGLE) { + /* axis angle */ + if (td->ext) + AxisAngleToMat4(&td->ext->quat[1], td->ext->quat[0], cob.matrix); + else + return; + } else { /* eulers */ if (td->ext) @@ -1751,22 +1817,22 @@ static void constraintRotLim(TransInfo *t, TransData *td) else return; } - + /* Evaluate valid constraints */ for (con= td->con; con; con= con->next) { /* only consider constraint if enabled */ if (con->flag & CONSTRAINT_DISABLE) continue; if (con->enforce == 0.0f) continue; - + /* we're only interested in Limit-Rotation constraints */ if (con->type == CONSTRAINT_TYPE_ROTLIMIT) { bRotLimitConstraint *data= con->data; float tmat[4][4]; - + /* only use it if it's tagged for this purpose */ if ((data->flag2 & LIMIT_TRANSFORM)==0) continue; - + /* do space conversions */ if (con->ownspace == CONSTRAINT_SPACE_WORLD) { /* just multiply by td->mtx (this should be ok) */ @@ -1777,10 +1843,10 @@ static void constraintRotLim(TransInfo *t, TransData *td) /* skip... incompatable spacetype */ continue; } - + /* do constraint */ cti->evaluate_constraint(con, &cob, NULL); - + /* convert spaces again */ if (con->ownspace == CONSTRAINT_SPACE_WORLD) { /* just multiply by td->mtx (this should be ok) */ @@ -1789,7 +1855,7 @@ static void constraintRotLim(TransInfo *t, TransData *td) } } } - + /* copy results from cob->matrix */ if (td->flag & TD_USEQUAT) { /* quats */ @@ -1799,13 +1865,17 @@ static void constraintRotLim(TransInfo *t, TransData *td) /* ipo-keys eulers */ TransDataIpokey *tdi= td->tdi; float eul[3]; - + Mat4ToEulO(cob.matrix, eul, td->rotOrder); - + tdi->rotx[0]= eul[0]; tdi->roty[0]= eul[1]; tdi->rotz[0]= eul[2]; } + else if (td->rotOrder == PCHAN_ROT_AXISANGLE) { + /* axis angle */ + Mat4ToAxisAngle(cob.matrix, &td->ext->quat[1], &td->ext->quat[0]); + } else { /* eulers */ Mat4ToEulO(cob.matrix, td->ext->rot, td->rotOrder); @@ -2688,12 +2758,11 @@ static void ElementRotation(TransInfo *t, TransData *td, float mat[3][3], short QuatMul(td->ext->quat, quat, iquat); /* make temp copy (since stored in same place) */ - QuatCopy(quat, td->ext->quat); // this is just a 4d vector copying function + QUATCOPY(quat, td->ext->quat); // this is just a 4d vector copying macro QuatToAxisAngle(quat, &td->ext->quat[1], &td->ext->quat[0]); /* this function works on end result */ - // TODO: we really need a specialised version of this for axis-angle that doesn't try to do quats... - protectedQuaternionBits(td->protectflag, td->ext->quat, td->ext->iquat); + protectedAxisAngleBits(td->protectflag, td->ext->quat, td->ext->iquat); } else { float eulmat[3][3]; @@ -2714,7 +2783,7 @@ static void ElementRotation(TransInfo *t, TransData *td, float mat[3][3], short protectedRotateBits(td->protectflag, eul, td->ext->irot); VECCOPY(td->ext->rot, eul); } - + constraintRotLim(t, td); } } diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index d1e70c16408..a89f8e1fb2e 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -515,6 +515,8 @@ extern Object workob; #define OB_LOCK_SCALEY 128 #define OB_LOCK_SCALEZ 256 #define OB_LOCK_SCALE 448 +#define OB_LOCK_ROTW 512 +#define OB_LOCK_ROT4D 1024 /* ob->mode */ typedef enum ObjectMode { diff --git a/source/blender/makesrna/intern/rna_pose.c b/source/blender/makesrna/intern/rna_pose.c index d143c94ebb1..3a03e7a624d 100644 --- a/source/blender/makesrna/intern/rna_pose.c +++ b/source/blender/makesrna/intern/rna_pose.c @@ -698,14 +698,19 @@ static void rna_def_pose_channel(BlenderRNA *brna) prop= RNA_def_property(srna, "lock_rotation", PROP_BOOLEAN, PROP_XYZ); RNA_def_property_boolean_sdna(prop, NULL, "protectflag", OB_LOCK_ROTX); RNA_def_property_array(prop, 3); - RNA_def_property_ui_text(prop, "Lock Rotation", "Lock editing of rotation (with three components) in the interface."); + RNA_def_property_ui_text(prop, "Lock Rotation", "Lock editing of rotation in the interface."); RNA_def_property_update(prop, NC_OBJECT|ND_POSE, "rna_Pose_update"); - //prop= RNA_def_property(srna, "lock_rotation_4d", PROP_BOOLEAN, PROP_XYZ); - //RNA_def_property_boolean_sdna(prop, NULL, "protectflag", OB_LOCK_ROTW); - //RNA_def_property_array(prop, 4); - //RNA_def_property_ui_text(prop, "Lock Rotation (4D)", "Lock editing of rotations (with four components) in the interface."); - //RNA_def_property_update(prop, NC_OBJECT|ND_POSE, "rna_Pose_update"); + // XXX this is sub-optimal - it really should be included above, but due to technical reasons we can't do this! + prop= RNA_def_property(srna, "lock_rotation_w", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "protectflag", OB_LOCK_ROTW); + RNA_def_property_ui_text(prop, "Lock Rotation (4D Angle)", "Lock editing of 'angle' component of four-component rotations in the interface."); + RNA_def_property_update(prop, NC_OBJECT|ND_POSE, "rna_Pose_update"); + // XXX this needs a better name + prop= RNA_def_property(srna, "lock_rotations_4d", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "protectflag", OB_LOCK_ROT4D); + RNA_def_property_ui_text(prop, "Lock Rotations (4D)", "Lock editing of four component rotations by components (instead of as Eulers)."); + RNA_def_property_update(prop, NC_OBJECT|ND_POSE, "rna_Pose_update"); prop= RNA_def_property(srna, "lock_scale", PROP_BOOLEAN, PROP_XYZ); RNA_def_property_boolean_sdna(prop, NULL, "protectflag", OB_LOCK_SCALEX);