diff --git a/source/blender/blenkernel/BKE_geometry_compare.hh b/source/blender/blenkernel/BKE_geometry_compare.hh index 5af51f6cd68..c487d924c86 100644 --- a/source/blender/blenkernel/BKE_geometry_compare.hh +++ b/source/blender/blenkernel/BKE_geometry_compare.hh @@ -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 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 compare_lattices(const Lattice &lattice1, + const Lattice &lattice2, + float threshold); + } // namespace blender::bke::compare_geometry diff --git a/source/blender/blenkernel/intern/geometry_compare.cc b/source/blender/blenkernel/intern/geometry_compare.cc index 24088e57614..98fa6ab8ffa 100644 --- a/source/blender/blenkernel/intern/geometry_compare.cc +++ b/source/blender/blenkernel/intern/geometry_compare.cc @@ -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 compare_curves(const CurvesGeometry &curves1, return std::nullopt; } +std::optional 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 bpoints1 = {lattice1.def, num_points}; + const Span 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 diff --git a/source/blender/makesrna/intern/rna_lattice_api.cc b/source/blender/makesrna/intern/rna_lattice_api.cc index 6711d0a1ebc..d603b60ca38 100644 --- a/source/blender/makesrna/intern/rna_lattice_api.cc +++ b/source/blender/makesrna/intern/rna_lattice_api.cc @@ -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 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 diff --git a/tests/files/modeling/modifiers.blend b/tests/files/modeling/modifiers.blend index 25e75fe97f0..a8c3b5b7358 100644 --- a/tests/files/modeling/modifiers.blend +++ b/tests/files/modeling/modifiers.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5315deaf56d915d9608ac946e98caad79c1f9613124e24e634f6d67a8f9c553 -size 11563916 +oid sha256:f6efef2004ac186610b7fd615e9fb4c56afc144a774d10beeafa9d051ace280d +size 11622132 diff --git a/tests/python/modifiers.py b/tests/python/modifiers.py index bd7334c4eba..579cc55e753 100644 --- a/tests/python/modifiers.py +++ b/tests/python/modifiers.py @@ -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" diff --git a/tests/python/modules/mesh_test.py b/tests/python/modules/mesh_test.py index 7a35e809e8c..cda2464b111 100644 --- a/tests/python/modules/mesh_test.py +++ b/tests/python/modules/mesh_test.py @@ -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!")