Constraints: replace 'Set Inverse' operator with an eval-time update
authorSybren A. Stüvel <sybren@blender.org>
Thu, 27 Feb 2020 09:24:11 +0000 (10:24 +0100)
committerSybren A. Stüvel <sybren@blender.org>
Thu, 27 Feb 2020 09:37:59 +0000 (10:37 +0100)
This fixes {T70269}.

Before this commit there was complicated code to try and compute the
correct parent inverse matrix for the 'Child Of' and 'Object Solver'
constraints outside the constraint evaluation. This was done mostly
correctly, but did have some issues. The Set Inverse operator now defers
this computation to be performed during constraint evaluation by just
setting a flag. If the constraint is disabled, and thus tagging it for
update in the depsgraph is not enough to trigger immediate evaluation,
evaluation is forced by temporarily enabling it.

This fix changes the way how the inverse matrix works when some of the
channels of the constraint are disabled. Before this commit, the channel
flags were used to filter both the parent and the inverse matrix. This
meant that it was impossible to make an inverse matrix that would
actually fully neutralize the effect of the constraint. Now only the
parent matrix is filtered, while inverse is applied fully. As a result,
pressing the 'Set Inverse' matrix produces the same transformation as
disabling the constraint. This is also reflected in the changed values
in the 'Child Of' unit test.

This change is not backward compatible, but it should be OK because the
old way was effectively unusable, so it is unlikely anybody relied on
it.

The change in matrix for the Object Solver constraint is due to a
different method of computing it, which caused a slightly different
floating point error that was slightly bigger than allowed by the test,
so I updated the matrix values there as well.

This patch was original written by @angavrilov and subsequently updated
by me.

Differential Revision: https://developer.blender.org/D6091

source/blender/blenkernel/intern/constraint.c
source/blender/editors/object/object_constraint.c
source/blender/makesdna/DNA_constraint_types.h
source/blender/makesrna/intern/rna_constraint.c
tests/python/bl_constraints.py

index 8ba746e3493e9016b9868e9e037b1db5eb994c64..26dea11624ba90e884792f3cfda71b29761f7e5e 100644 (file)
@@ -860,94 +860,88 @@ static void childof_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar
   }
 
   float parmat[4][4];
-
-  /* simple matrix parenting */
-  if (data->flag == CHILDOF_ALL) {
-
-    /* multiply target (parent matrix) by offset (parent inverse) to get
-     * the effect of the parent that will be exerted on the owner
-     */
-    mul_m4_m4m4(parmat, ct->matrix, data->invmat);
-
-    /* now multiply the parent matrix by the owner matrix to get the
-     * the effect of this constraint (i.e. owner is 'parented' to parent)
-     */
-    mul_m4_m4m4(cob->matrix, parmat, cob->matrix);
+  /* Simple matrix parenting. */
+  if ((data->flag & CHILDOF_ALL) == CHILDOF_ALL) {
+    copy_m4_m4(parmat, ct->matrix);
   }
+  /* Filter the parent matrix by channel. */
   else {
-    float invmat[4][4], tempmat[4][4];
     float loc[3], eul[3], size[3];
-    float loco[3], eulo[3], sizo[3];
-
-    /* get offset (parent-inverse) matrix */
-    copy_m4_m4(invmat, data->invmat);
 
     /* extract components of both matrices */
     copy_v3_v3(loc, ct->matrix[3]);
     mat4_to_eulO(eul, ct->rotOrder, ct->matrix);
     mat4_to_size(size, ct->matrix);
 
-    copy_v3_v3(loco, invmat[3]);
-    mat4_to_eulO(eulo, cob->rotOrder, invmat);
-    mat4_to_size(sizo, invmat);
-
     /* disable channels not enabled */
     if (!(data->flag & CHILDOF_LOCX)) {
-      loc[0] = loco[0] = 0.0f;
+      loc[0] = 0.0f;
     }
     if (!(data->flag & CHILDOF_LOCY)) {
-      loc[1] = loco[1] = 0.0f;
+      loc[1] = 0.0f;
     }
     if (!(data->flag & CHILDOF_LOCZ)) {
-      loc[2] = loco[2] = 0.0f;
+      loc[2] = 0.0f;
     }
     if (!(data->flag & CHILDOF_ROTX)) {
-      eul[0] = eulo[0] = 0.0f;
+      eul[0] = 0.0f;
     }
     if (!(data->flag & CHILDOF_ROTY)) {
-      eul[1] = eulo[1] = 0.0f;
+      eul[1] = 0.0f;
     }
     if (!(data->flag & CHILDOF_ROTZ)) {
-      eul[2] = eulo[2] = 0.0f;
+      eul[2] = 0.0f;
     }
     if (!(data->flag & CHILDOF_SIZEX)) {
-      size[0] = sizo[0] = 1.0f;
+      size[0] = 1.0f;
     }
     if (!(data->flag & CHILDOF_SIZEY)) {
-      size[1] = sizo[1] = 1.0f;
+      size[1] = 1.0f;
     }
     if (!(data->flag & CHILDOF_SIZEZ)) {
-      size[2] = sizo[2] = 1.0f;
+      size[2] = 1.0f;
     }
 
     /* make new target mat and offset mat */
-    loc_eulO_size_to_mat4(ct->matrix, loc, eul, size, ct->rotOrder);
-    loc_eulO_size_to_mat4(invmat, loco, eulo, sizo, cob->rotOrder);
+    loc_eulO_size_to_mat4(parmat, loc, eul, size, ct->rotOrder);
+  }
 
-    /* multiply target (parent matrix) by offset (parent inverse) to get
-     * the effect of the parent that will be exerted on the owner
-     */
-    mul_m4_m4m4(parmat, ct->matrix, invmat);
+  /* Compute the inverse matrix if requested. */
+  if (data->flag & CHILDOF_SET_INVERSE) {
+    invert_m4_m4(data->invmat, parmat);
 
-    /* now multiply the parent matrix by the owner matrix to get the
-     * the effect of this constraint (i.e.  owner is 'parented' to parent)
-     */
-    copy_m4_m4(tempmat, cob->matrix);
-    mul_m4_m4m4(cob->matrix, parmat, tempmat);
+    data->flag &= ~CHILDOF_SET_INVERSE;
 
-    /* without this, changes to scale and rotation can change location
-     * of a parentless bone or a disconnected bone. Even though its set
-     * to zero above. */
-    if (!(data->flag & CHILDOF_LOCX)) {
-      cob->matrix[3][0] = tempmat[3][0];
-    }
-    if (!(data->flag & CHILDOF_LOCY)) {
-      cob->matrix[3][1] = tempmat[3][1];
-    }
-    if (!(data->flag & CHILDOF_LOCZ)) {
-      cob->matrix[3][2] = tempmat[3][2];
+    /* Write the computed matrix back to the master copy if in COW evaluation. */
+    bConstraint *orig_con = constraint_find_original_for_update(cob, con);
+
+    if (orig_con != NULL) {
+      bChildOfConstraint *orig_data = orig_con->data;
+
+      copy_m4_m4(orig_data->invmat, data->invmat);
+      orig_data->flag &= ~CHILDOF_SET_INVERSE;
     }
   }
+
+  /* Multiply together the target (parent) matrix, parent inverse,
+   * and the owner transform matrixto get the effect of this constraint
+   * (i.e.  owner is 'parented' to parent). */
+  float orig_cob_matrix[4][4];
+  copy_m4_m4(orig_cob_matrix, cob->matrix);
+  mul_m4_series(cob->matrix, parmat, data->invmat, orig_cob_matrix);
+
+  /* Without this, changes to scale and rotation can change location
+   * of a parentless bone or a disconnected bone. Even though its set
+   * to zero above. */
+  if (!(data->flag & CHILDOF_LOCX)) {
+    cob->matrix[3][0] = orig_cob_matrix[3][0];
+  }
+  if (!(data->flag & CHILDOF_LOCY)) {
+    cob->matrix[3][1] = orig_cob_matrix[3][1];
+  }
+  if (!(data->flag & CHILDOF_LOCZ)) {
+    cob->matrix[3][2] = orig_cob_matrix[3][2];
+  }
 }
 
 /* XXX note, con->flag should be CONSTRAINT_SPACEONCE for bone-childof, patched in readfile.c */
@@ -4891,23 +4885,35 @@ static void objectsolver_evaluate(bConstraint *con, bConstraintOb *cob, ListBase
     return;
   }
 
-  float mat[4][4], obmat[4][4], imat[4][4], cammat[4][4], camimat[4][4], parmat[4][4];
+  float mat[4][4], obmat[4][4], imat[4][4], parmat[4][4];
   float ctime = DEG_get_ctime(depsgraph);
   float framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, ctime);
 
-  BKE_object_where_is_calc_mat4(camob, cammat);
-
   BKE_tracking_camera_get_reconstructed_interpolate(tracking, object, framenr, mat);
 
-  invert_m4_m4(camimat, cammat);
-  mul_m4_m4m4(parmat, cammat, data->invmat);
+  invert_m4_m4(imat, mat);
+  mul_m4_m4m4(parmat, camob->obmat, imat);
 
-  copy_m4_m4(cammat, camob->obmat);
   copy_m4_m4(obmat, cob->matrix);
 
-  invert_m4_m4(imat, mat);
+  /* Recalculate the inverse matrix if requested. */
+  if (data->flag & OBJECTSOLVER_SET_INVERSE) {
+    invert_m4_m4(data->invmat, parmat);
+
+    data->flag &= ~OBJECTSOLVER_SET_INVERSE;
+
+    /* Write the computed matrix back to the master copy if in COW evaluation. */
+    bConstraint *orig_con = constraint_find_original_for_update(cob, con);
+
+    if (orig_con != NULL) {
+      bObjectSolverConstraint *orig_data = orig_con->data;
+
+      copy_m4_m4(orig_data->invmat, data->invmat);
+      orig_data->flag &= ~OBJECTSOLVER_SET_INVERSE;
+    }
+  }
 
-  mul_m4_series(cob->matrix, cammat, imat, camimat, parmat, obmat);
+  mul_m4_series(cob->matrix, parmat, data->invmat, obmat);
 }
 
 static bConstraintTypeInfo CTI_OBJECTSOLVER = {
index 906a9e44870384ed0ea9eee4b4713e66985a368e..bfff36bb83a5397d47f2e8dceb4912834284cf3b 100644 (file)
@@ -871,118 +871,31 @@ void CONSTRAINT_OT_limitdistance_reset(wmOperatorType *ot)
 
 /* ------------- Child-Of Constraint ------------------ */
 
-static void child_get_inverse_matrix_owner_bone(
-    Depsgraph *depsgraph, wmOperator *op, Scene *scene, Object *ob, float invmat[4][4])
+/* Force evaluation so that the 'set inverse' flag is handled.
+ * No-op when the constraint is enabled, as in such cases the evaluation will happen anyway.
+ */
+static void force_evaluation_if_constraint_disabled(bContext *C, Object *ob, bConstraint *con)
 {
-  /* For bone owner we want to do this in evaluated domain.
-   * BKE_pose_where_is / BKE_pose_where_is_bone relies on (re)evaluating parts of the scene
-   * and copying new evaluated stuff back to original.
-   */
-  Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
-  bConstraint *con_eval = edit_constraint_property_get(op, ob_eval, CONSTRAINT_TYPE_CHILDOF);
-
-  /* nullify inverse matrix first */
-  unit_m4(invmat);
-
-  bPoseChannel *pchan_eval = BKE_pose_channel_active(ob_eval);
-
-  /* try to find a pose channel - assume that this is the constraint owner */
-  /* TODO: get from context instead? */
-  if (ob_eval && ob_eval->pose && pchan_eval) {
-    bConstraint *con_last;
-
-    /* calculate/set inverse matrix:
-     * We just calculate all transform-stack eval up to but not including this constraint.
-     * This is because inverse should just inverse correct for just the constraint's influence
-     * when it gets applied; that is, at the time of application, we don't know anything about
-     * what follows.
-     */
-    float imat[4][4], tmat[4][4];
-    float pmat[4][4];
-
-    /* make sure we passed the correct constraint */
-    BLI_assert(BLI_findindex(&pchan_eval->constraints, con_eval) != -1);
-
-    /* 1. calculate posemat where inverse doesn't exist yet (inverse was cleared above),
-     * to use as baseline ("pmat") to derive delta from. This extra calc saves users
-     * from having pressing "Clear Inverse" first
-     */
-    BKE_pose_where_is(depsgraph, scene, ob_eval);
-    copy_m4_m4(pmat, pchan_eval->pose_mat);
-
-    /* 2. knock out constraints starting from this one */
-    con_last = pchan_eval->constraints.last;
-    pchan_eval->constraints.last = con_eval->prev;
-
-    if (con_eval->prev) {
-      /* new end must not point to this one, else this chain cutting is useless */
-      con_eval->prev->next = NULL;
-    }
-    else {
-      /* constraint was first */
-      pchan_eval->constraints.first = NULL;
-    }
-
-    /* 3. solve pose without disabled constraints */
-    BKE_pose_where_is(depsgraph, scene, ob_eval);
-
-    /* 4. determine effect of constraint by removing the newly calculated
-     * pchan->pose_mat from the original pchan->pose_mat, thus determining
-     * the effect of the constraint
-     */
-    invert_m4_m4(imat, pchan_eval->pose_mat);
-    mul_m4_m4m4(tmat, pmat, imat);
-    invert_m4_m4(invmat, tmat);
-
-    /* 5. restore constraints */
-    pchan_eval->constraints.last = con_last;
-
-    if (con_eval->prev) {
-      /* hook up prev to this one again */
-      con_eval->prev->next = con_eval;
-    }
-    else {
-      /* set as first again */
-      pchan_eval->constraints.first = con_eval;
-    }
-
-    /* 6. recalculate pose with new inv-mat applied */
-    /* this one is unnecessary? (DEG seems to update correctly without)
-     + if we leave this in, we have to click "Set Inverse" twice to see updates...
-    BKE_pose_where_is(depsgraph, scene, ob_eval); */
+  if ((con->flag & (CONSTRAINT_DISABLE | CONSTRAINT_OFF)) == 0) {
+    return;
   }
-}
-
-static void child_get_inverse_matrix_owner_object(
-    Depsgraph *depsgraph, Scene *scene, Object *ob, bConstraint *con, float invmat[4][4])
-{
 
-  /* nullify inverse matrix first */
-  unit_m4(invmat);
-
-  if (ob) {
-    Object workob;
-
-    /* make sure we passed the correct constraint */
-    BLI_assert(BLI_findindex(&ob->constraints, con) != -1);
-    UNUSED_VARS_NDEBUG(con);
+  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+  Scene *scene = DEG_get_evaluated_scene(depsgraph);
 
-    /* use BKE_object_workob_calc_parent to find inverse - just like for normal parenting */
-    BKE_object_workob_calc_parent(depsgraph, scene, ob, &workob);
-    invert_m4_m4(invmat, workob.obmat);
-  }
+  short flag_backup = con->flag;
+  con->flag &= ~(CONSTRAINT_DISABLE | CONSTRAINT_OFF);
+  BKE_object_eval_constraints(depsgraph, scene, ob);
+  con->flag = flag_backup;
 }
 
 /* ChildOf Constraint - set inverse callback */
 static int childof_set_inverse_exec(bContext *C, wmOperator *op)
 {
   Main *bmain = CTX_data_main(C);
-  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
-  Scene *scene = CTX_data_scene(C);
   Object *ob = ED_object_active_context(C);
   bConstraint *con = edit_constraint_property_get(op, ob, CONSTRAINT_TYPE_CHILDOF);
   bChildOfConstraint *data = (con) ? (bChildOfConstraint *)con->data : NULL;
-  const int owner = RNA_enum_get(op->ptr, "owner");
 
   /* despite 3 layers of checks, we may still not be able to find a constraint */
   if (data == NULL) {
@@ -991,12 +904,11 @@ static int childof_set_inverse_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
-  if (owner == EDIT_CONSTRAINT_OWNER_OBJECT) {
-    child_get_inverse_matrix_owner_object(depsgraph, scene, ob, con, data->invmat);
-  }
-  else if (owner == EDIT_CONSTRAINT_OWNER_BONE) {
-    child_get_inverse_matrix_owner_bone(depsgraph, op, scene, ob, data->invmat);
-  }
+  /* Set a flag to request recalculation on next update. */
+  data->flag |= CHILDOF_SET_INVERSE;
+
+  /* Force constraint to run, it will perform the recalculation. */
+  force_evaluation_if_constraint_disabled(C, ob, con);
 
   ED_object_constraint_update(bmain, ob);
   WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, ob);
@@ -1231,12 +1143,9 @@ void CONSTRAINT_OT_followpath_path_animate(wmOperatorType *ot)
 static int objectsolver_set_inverse_exec(bContext *C, wmOperator *op)
 {
   Main *bmain = CTX_data_main(C);
-  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
-  Scene *scene = CTX_data_scene(C);
   Object *ob = ED_object_active_context(C);
   bConstraint *con = edit_constraint_property_get(op, ob, CONSTRAINT_TYPE_OBJECTSOLVER);
   bObjectSolverConstraint *data = (con) ? (bObjectSolverConstraint *)con->data : NULL;
-  const int owner = RNA_enum_get(op->ptr, "owner");
 
   /* despite 3 layers of checks, we may still not be able to find a constraint */
   if (data == NULL) {
@@ -1246,12 +1155,11 @@ static int objectsolver_set_inverse_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
-  if (owner == EDIT_CONSTRAINT_OWNER_OBJECT) {
-    child_get_inverse_matrix_owner_object(depsgraph, scene, ob, con, data->invmat);
-  }
-  else if (owner == EDIT_CONSTRAINT_OWNER_BONE) {
-    child_get_inverse_matrix_owner_bone(depsgraph, op, scene, ob, data->invmat);
-  }
+  /* Set a flag to request recalculation on next update. */
+  data->flag |= OBJECTSOLVER_SET_INVERSE;
+
+  /* Force constraint to run, it will perform the recalculation. */
+  force_evaluation_if_constraint_disabled(C, ob, con);
 
   ED_object_constraint_update(bmain, ob);
   WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, ob);
index 6caa0ac5a7498b05fb8b0786daf240a5e2040f00..670a6c46187ce68a11166a5d3dd4a0bd63e342dd 100644 (file)
@@ -1096,6 +1096,8 @@ typedef enum eChildOf_Flags {
   CHILDOF_SIZEY = (1 << 7),
   CHILDOF_SIZEZ = (1 << 8),
   CHILDOF_ALL = 511,
+  /* Temporary flag used by the Set Inverse operator. */
+  CHILDOF_SET_INVERSE = (1 << 9),
 } eChildOf_Flags;
 
 /* Pivot Constraint */
@@ -1147,6 +1149,8 @@ typedef enum eCameraSolver_Flags {
 /* ObjectSolver Constraint -> flag */
 typedef enum eObjectSolver_Flags {
   OBJECTSOLVER_ACTIVECLIP = (1 << 0),
+  /* Temporary flag used by the Set Inverse operator. */
+  OBJECTSOLVER_SET_INVERSE = (1 << 1),
 } eObjectSolver_Flags;
 
 /* ObjectSolver Constraint -> flag */
index 8e57de9baebcd5dc31e64407d76df4e122a9d6aa..64e6fc9059c63a5568a0a625cbe97b5938d2d155 100644 (file)
@@ -973,11 +973,18 @@ static void rna_def_constraint_childof(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Scale Z", "Use Z Scale of Parent");
   RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
 
+  prop = RNA_def_property(srna, "set_inverse_pending", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", CHILDOF_SET_INVERSE);
+  RNA_def_property_ui_text(
+      prop, "Set Inverse Pending", "Set to true to request recalculation of the inverse matrix");
+  RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
+
   prop = RNA_def_property(srna, "inverse_matrix", PROP_FLOAT, PROP_MATRIX);
   RNA_def_property_float_sdna(prop, NULL, "invmat");
   RNA_def_property_multi_array(prop, 2, rna_matrix_dimsize_4x4);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Inverse Matrix", "Transformation matrix to apply before");
+  RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
 }
 
 static void rna_def_constraint_python(BlenderRNA *brna)
@@ -3152,6 +3159,12 @@ static void rna_def_constraint_object_solver(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Active Clip", "Use active clip defined in scene");
   RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
 
+  prop = RNA_def_property(srna, "set_inverse_pending", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", OBJECTSOLVER_SET_INVERSE);
+  RNA_def_property_ui_text(
+      prop, "Set Inverse Pending", "Set to true to request recalculation of the inverse matrix");
+  RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
+
   /* object */
   prop = RNA_def_property(srna, "object", PROP_STRING, PROP_NONE);
   RNA_def_property_string_sdna(prop, NULL, "object");
index 42f81bddce663b6983b04320b9ce5709a897786a..13a431541bc79a67f468bdc354fe9076aff3a4a4 100644 (file)
@@ -156,9 +156,9 @@ class ChildOfTest(AbstractConstraintTests):
         context = self.constraint_context('Child Of', owner_name='Child Of.object.owner')
         bpy.ops.constraint.childof_set_inverse(context, constraint='Child Of')
         self.matrix_test('Child Of.object.owner', Matrix((
-            (0.9228900671005249, 0.23250490427017212, -0.035540513694286346, 0.10000000149011612),
-            (-0.011224273592233658, 0.9838480949401855, 0.24731633067131042, 0.21246682107448578),
-            (0.0383986234664917, -0.3163823187351227, 0.9553266167640686, 0.27248233556747437),
+            (0.9992386102676392, 0.019843991845846176, -0.03359176218509674, 0.10000000149011612),
+            (-0.017441775649785995, 0.997369647026062, 0.0703534483909607, 0.2000001221895218),
+            (0.034899499267339706, -0.06971398741006851, 0.996956467628479, 0.3000001311302185),
             (0.0, 0.0, 0.0, 1.0),
         )))
 
@@ -188,6 +188,29 @@ class ChildOfTest(AbstractConstraintTests):
         bpy.ops.constraint.childof_clear_inverse(context, constraint='Child Of')
         self.matrix_test('Child Of.armature.owner', initial_matrix)
 
+    def test_vertexgroup_simple_parent(self):
+        """Child Of: simple evaluation of vertex group parent."""
+        initial_matrix = Matrix((
+            (-0.8076590895652771, 0.397272527217865, 0.4357309341430664, 1.188504934310913),
+            (-0.4534659683704376, -0.8908230066299438, -0.028334975242614746, 1.7851561307907104),
+            (0.3769024908542633, -0.22047416865825653, 0.8996308445930481, 3.4457669258117676),
+            (0.0, 0.0, 0.0, 1.0),
+        ))
+        self.matrix_test('Child Of.vertexgroup.owner', initial_matrix)
+
+        context = self.constraint_context('Child Of', owner_name='Child Of.vertexgroup.owner')
+        bpy.ops.constraint.childof_set_inverse(context, constraint='Child Of')
+
+        self.matrix_test('Child Of.vertexgroup.owner', Matrix((
+            (0.9992386102676392, 0.019843988120555878, -0.03359176218509674, 0.10000000149011612),
+            (-0.017441775649785995, 0.997369647026062, 0.0703534483909607, 0.20000000298023224),
+            (0.03489949554204941, -0.06971397995948792, 0.9969563484191895, 0.30000001192092896),
+            (0.0, 0.0, 0.0, 1.0),
+        )))
+
+        bpy.ops.constraint.childof_clear_inverse(context, constraint='Child Of')
+        self.matrix_test('Child Of.vertexgroup.owner', initial_matrix)
+
 
 class ObjectSolverTest(AbstractConstraintTests):
     layer_collection = 'Object Solver'
@@ -205,9 +228,9 @@ class ObjectSolverTest(AbstractConstraintTests):
         context = self.constraint_context('Object Solver')
         bpy.ops.constraint.objectsolver_set_inverse(context, constraint='Object Solver')
         self.matrix_test('Object Solver.owner', Matrix((
-            (0.9992386102676392, 0.019843988120555878, -0.03359176218509674, 0.10000000149011612),
-            (-0.017441775649785995, 0.997369647026062, 0.0703534483909607, 0.20000000298023224),
-            (0.03489949554204941, -0.06971397995948792, 0.9969563484191895, 0.30000001192092896),
+            (0.9992387294769287, 0.019843989983201027, -0.03359176591038704, 0.10000025480985641),
+            (-0.017441747710108757, 0.9973697662353516, 0.07035345584154129, 0.1999993920326233),
+            (0.034899502992630005, -0.06971398741006851, 0.996956467628479, 0.29999980330467224),
             (0.0, 0.0, 0.0, 1.0),
         )))