Regression test for armature deformation on lattice
This is a basic armature deformation test for #141535 using Lattices instead of Mesh as the target object type. Lattice deformation was briefly broken, which is caught by this test. The test adds the general-purpose `unit_test_compare` function to lattice object data. It only compares lattice point counts and positions for now, more data can be added later if necessary. The `MeshTest` class did not support lattice object types yet, so needed some changes. The Curves case was already supported, but only by full conversion to mesh data, without actually using the `unit_test_compare` function specific for curves geometry. This is unchanged, because applying constructive modifiers on curves does not work. If it were not for this limitation the test could do actual curves comparisons now. For lattice support the `MeshTest` class comparison function has been generalized to all supported object data types. It runs the appropriate `unit_test_compare` api function and validation where supported (only meshes at this point). Pull Request: https://projects.blender.org/blender/blender/pulls/141546
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_mesh_types.hh"
|
||||
|
||||
#include "DNA_lattice_types.h"
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
@@ -46,4 +48,13 @@ std::optional<GeoMismatch> compare_curves(const CurvesGeometry &curves1,
|
||||
const CurvesGeometry &curves2,
|
||||
float threshold);
|
||||
|
||||
/**
|
||||
* \brief Checks if the two lattices are different, returning the type of mismatch if any.
|
||||
*
|
||||
* \returns The type of mismatch that was detected, if there is any.
|
||||
*/
|
||||
std::optional<GeoMismatch> compare_lattices(const Lattice &lattice1,
|
||||
const Lattice &lattice2,
|
||||
float threshold);
|
||||
|
||||
} // namespace blender::bke::compare_geometry
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
|
||||
#include "BKE_geometry_compare.hh"
|
||||
|
||||
#include "DNA_curve_types.h"
|
||||
#include "DNA_lattice_types.h"
|
||||
|
||||
namespace blender::bke::compare_geometry {
|
||||
|
||||
enum class GeoMismatch : int8_t {
|
||||
@@ -1022,4 +1025,35 @@ std::optional<GeoMismatch> compare_curves(const CurvesGeometry &curves1,
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<GeoMismatch> compare_lattices(const Lattice &lattice1,
|
||||
const Lattice &lattice2,
|
||||
float threshold)
|
||||
{
|
||||
if (lattice1.pntsu != lattice2.pntsu) {
|
||||
return GeoMismatch::NumPoints;
|
||||
}
|
||||
if (lattice1.pntsv != lattice2.pntsv) {
|
||||
return GeoMismatch::NumPoints;
|
||||
}
|
||||
if (lattice1.pntsw != lattice2.pntsw) {
|
||||
return GeoMismatch::NumPoints;
|
||||
}
|
||||
|
||||
const int num_points = lattice1.pntsu * lattice1.pntsv * lattice1.pntsw;
|
||||
const Span<BPoint> bpoints1 = {lattice1.def, num_points};
|
||||
const Span<BPoint> bpoints2 = {lattice2.def, num_points};
|
||||
for (const int i : IndexRange(num_points)) {
|
||||
const float3 co1 = bpoints1[i].vec;
|
||||
const float3 co2 = bpoints2[i].vec;
|
||||
for (const int component : IndexRange(3)) {
|
||||
if (values_different(co1, co2, threshold, component)) {
|
||||
return GeoMismatch::PointAttributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* No mismatches found. */
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace blender::bke::compare_geometry
|
||||
|
||||
@@ -13,6 +13,21 @@
|
||||
#include "rna_internal.hh" /* own include */
|
||||
|
||||
#ifdef RNA_RUNTIME
|
||||
|
||||
# include "BKE_geometry_compare.hh"
|
||||
|
||||
static const char *rna_Lattice_unit_test_compare(Lattice *lt, Lattice *lt2, float threshold)
|
||||
{
|
||||
using namespace blender::bke::compare_geometry;
|
||||
const std::optional<GeoMismatch> mismatch = compare_lattices(*lt, *lt2, threshold);
|
||||
|
||||
if (!mismatch) {
|
||||
return "Same";
|
||||
}
|
||||
|
||||
return mismatch_to_string(mismatch.value());
|
||||
}
|
||||
|
||||
static void rna_Lattice_transform(Lattice *lt, const float mat[16], bool shape_keys)
|
||||
{
|
||||
BKE_lattice_transform(lt, (const float(*)[4])mat, shape_keys);
|
||||
@@ -39,6 +54,22 @@ void RNA_api_lattice(StructRNA *srna)
|
||||
RNA_def_boolean(func, "shape_keys", false, "", "Transform Shape Keys");
|
||||
|
||||
RNA_def_function(srna, "update_gpu_tag", "rna_Lattice_update_gpu_tag");
|
||||
|
||||
func = RNA_def_function(srna, "unit_test_compare", "rna_Lattice_unit_test_compare");
|
||||
RNA_def_pointer(func, "lattice", "Lattice", "", "Lattice to compare to");
|
||||
RNA_def_float_factor(func,
|
||||
"threshold",
|
||||
FLT_EPSILON * 60,
|
||||
0.0f,
|
||||
FLT_MAX,
|
||||
"Threshold",
|
||||
"Comparison tolerance threshold",
|
||||
0.0f,
|
||||
FLT_MAX);
|
||||
/* return value */
|
||||
parm = RNA_def_string(
|
||||
func, "result", "nothing", 64, "Return value", "String description of result of comparison");
|
||||
RNA_def_function_return(func, parm);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
BIN
tests/files/modeling/modifiers.blend
(Stored with Git LFS)
BIN
tests/files/modeling/modifiers.blend
(Stored with Git LFS)
Binary file not shown.
@@ -377,6 +377,21 @@ def main():
|
||||
'use_vertex_groups': True,
|
||||
'vertex_group': "Mask",
|
||||
'use_multi_modifier': True})])]),
|
||||
SpecMeshTest("ArmatureLatticeEnvelope", "testLatticeArmatureEnvelope", "expectedLatticeArmatureEnvelope",
|
||||
[ModifierSpec('armature', 'ARMATURE',
|
||||
{'object': bpy.data.objects['testArmatureLatticeEnvelope'],
|
||||
'use_vertex_groups': False,
|
||||
'use_bone_envelopes': True})]),
|
||||
SpecMeshTest("ArmatureLatticeVGroup", "testLatticeArmatureVGroup", "expectedLatticeArmatureVGroup",
|
||||
[ModifierSpec('armature', 'ARMATURE',
|
||||
{'object': bpy.data.objects['testArmatureLatticeVGroup'],
|
||||
'use_vertex_groups': True,
|
||||
'use_bone_envelopes': False})]),
|
||||
SpecMeshTest("ArmatureLatticeNoVGroup", "testLatticeArmatureNoVGroup", "expectedLatticeArmatureNoVGroup",
|
||||
[ModifierSpec('armature', 'ARMATURE',
|
||||
{'object': bpy.data.objects['testArmatureLatticeNoVGroup'],
|
||||
'use_vertex_groups': True,
|
||||
'use_bone_envelopes': False})]),
|
||||
]
|
||||
|
||||
boolean_basename = "CubeBooleanDiffBMeshObject"
|
||||
|
||||
@@ -289,7 +289,7 @@ class MeshTest(ABC):
|
||||
print("Compare evaluated and expected object in Blender.\n")
|
||||
return False
|
||||
|
||||
result = self.compare_meshes(
|
||||
result = self.compare_object_data(
|
||||
self.evaluated_object,
|
||||
self.expected_object,
|
||||
self.threshold,
|
||||
@@ -412,9 +412,9 @@ class MeshTest(ABC):
|
||||
self.expected_object = self.evaluated_object
|
||||
|
||||
@staticmethod
|
||||
def compare_meshes(evaluated_object, expected_object, threshold, allow_index_change):
|
||||
def compare_object_data(evaluated_object, expected_object, threshold, allow_index_change):
|
||||
"""
|
||||
Compares evaluated object mesh with expected object mesh.
|
||||
Compares evaluated object data with expected object data.
|
||||
|
||||
:arg evaluated_object: first object for comparison.
|
||||
:arg expected_object: second object for comparison.
|
||||
@@ -422,32 +422,48 @@ class MeshTest(ABC):
|
||||
:return: dict: Contains results of different comparisons.
|
||||
"""
|
||||
objects = bpy.data.objects
|
||||
evaluated_test_mesh = objects[evaluated_object.name].data
|
||||
expected_mesh = expected_object.data
|
||||
evaluated_test_data = objects[evaluated_object.name].data
|
||||
expected_data = expected_object.data
|
||||
result_codes = {}
|
||||
|
||||
if threshold:
|
||||
result_mesh = expected_mesh.unit_test_compare(
|
||||
mesh=evaluated_test_mesh, threshold=threshold)
|
||||
if evaluated_object.type == 'CURVE':
|
||||
unit_test_compare_args = {"curves": evaluated_test_data}
|
||||
report_name = "Curves"
|
||||
validate_func = None
|
||||
elif evaluated_object.type == 'MESH':
|
||||
unit_test_compare_args = {"mesh": evaluated_test_data}
|
||||
report_name = "Mesh"
|
||||
def validate_func(): return evaluated_test_data.validate(verbose=True)
|
||||
elif evaluated_object.type == 'LATTICE':
|
||||
unit_test_compare_args = {"lattice": evaluated_test_data}
|
||||
report_name = "Lattice"
|
||||
validate_func = None
|
||||
else:
|
||||
result_mesh = expected_mesh.unit_test_compare(
|
||||
mesh=evaluated_test_mesh)
|
||||
raise Exception("This object type is not yet supported!")
|
||||
|
||||
if result_mesh == "Same":
|
||||
result_codes['Mesh Comparison'] = (True, result_mesh)
|
||||
elif allow_index_change and result_mesh == "The geometries are the same up to a change of indices":
|
||||
result_codes['Mesh Comparison'] = (True, result_mesh)
|
||||
if threshold:
|
||||
result_data = expected_data.unit_test_compare(
|
||||
threshold=threshold, **unit_test_compare_args)
|
||||
else:
|
||||
result_codes['Mesh Comparison'] = (False, result_mesh)
|
||||
result_data = expected_data.unit_test_compare(
|
||||
**unit_test_compare_args)
|
||||
|
||||
if result_data == "Same":
|
||||
result_codes[f'{report_name} Comparison'] = (True, result_data)
|
||||
elif allow_index_change and result_data == "The geometries are the same up to a change of indices":
|
||||
result_codes[f'{report_name} Comparison'] = (True, result_data)
|
||||
else:
|
||||
result_codes[f'{report_name} Comparison'] = (False, result_data)
|
||||
|
||||
# Validation check.
|
||||
result_validation = evaluated_test_mesh.validate(verbose=True)
|
||||
if result_validation:
|
||||
result_validation = "Invalid Mesh"
|
||||
result_codes['Mesh Validation'] = (False, result_validation)
|
||||
else:
|
||||
result_validation = "Valid"
|
||||
result_codes['Mesh Validation'] = (True, result_validation)
|
||||
if validate_func:
|
||||
result_validation = validate_func()
|
||||
if result_validation:
|
||||
result_validation = f"Invalid {report_name}"
|
||||
result_codes[f'{report_name} Validation'] = (False, result_validation)
|
||||
else:
|
||||
result_validation = "Valid"
|
||||
result_codes[f'{report_name} Validation'] = (True, result_validation)
|
||||
|
||||
return result_codes
|
||||
|
||||
@@ -614,16 +630,16 @@ class SpecMeshTest(MeshTest):
|
||||
scene.frame_set(modifier_spec.frame_end)
|
||||
|
||||
def _apply_modifier(self, test_object, modifier_name):
|
||||
# Modifier automatically gets applied when converting from Curve to Mesh.
|
||||
if test_object.type == 'CURVE':
|
||||
# Cannot apply constructive modifiers on curves, convert to mesh entirely.
|
||||
bpy.ops.object.convert(target='MESH')
|
||||
elif test_object.type == 'MESH':
|
||||
elif test_object.type in ['MESH', 'LATTICE']:
|
||||
bpy.ops.object.modifier_apply(modifier=modifier_name)
|
||||
else:
|
||||
raise Exception("This object type is not yet supported!")
|
||||
|
||||
def _apply_all_modifiers(self, test_object):
|
||||
if test_object.type in ['CURVE', 'MESH']:
|
||||
if test_object.type in ['CURVE', 'MESH', 'LATTICE']:
|
||||
bpy.ops.object.convert(target='MESH')
|
||||
else:
|
||||
raise Exception("This object type is not yet supported!")
|
||||
|
||||
Reference in New Issue
Block a user