From b9f422c4be9c27bb47e5305bad2c91901719100e Mon Sep 17 00:00:00 2001 From: Himanshi Kalra Date: Mon, 27 Apr 2020 16:47:07 +0200 Subject: Tests: add physics tests cloth and softybody This uses the same framework as automated modifier tests. It adds a physics modifier, bakes and compares vertex coordinates on the end frame. Differential Revision: https://developer.blender.org/D7017 --- tests/python/CMakeLists.txt | 16 ++++++++ tests/python/modules/mesh_test.py | 85 ++++++++++++++++++++++++++++++++++++--- tests/python/physics_cloth.py | 56 ++++++++++++++++++++++++++ tests/python/physics_softbody.py | 56 ++++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 tests/python/physics_cloth.py create mode 100644 tests/python/physics_softbody.py (limited to 'tests') diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index db5d5dcf73b..2d6dc4af40e 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -188,6 +188,22 @@ add_blender_test( --run-all-tests ) +add_blender_test( + physics_cloth + ${TEST_SRC_DIR}/physics/cloth_test.blend + --python ${TEST_PYTHON_DIR}/physics_cloth.py + -- + --run-all-tests +) + +add_blender_test( + physics_softbody + ${TEST_SRC_DIR}/physics/softbody_test.blend + --python ${TEST_PYTHON_DIR}/physics_softbody.py + -- + --run-all-tests +) + add_blender_test( constraints --python ${CMAKE_CURRENT_LIST_DIR}/bl_constraints.py diff --git a/tests/python/modules/mesh_test.py b/tests/python/modules/mesh_test.py index f188d998884..af0e78257d5 100644 --- a/tests/python/modules/mesh_test.py +++ b/tests/python/modules/mesh_test.py @@ -73,6 +73,28 @@ class ModifierSpec: " with parameters: " + str(self.modifier_parameters) +class PhysicsSpec: + """ + Holds one Physics modifier and its parameters. + """ + + def __init__(self, modifier_name: str, modifier_type: str, modifier_parameters: dict, frame_end: int): + """ + Constructs a physics spec. + :param modifier_name: str - name of object modifier, e.g. "Cloth" + :param modifier_type: str - type of object modifier, e.g. "CLOTH" + :param modifier_parameters: dict - {name : val} dictionary giving modifier parameters, e.g. {"quality" : 4} + :param frame_end:int - the last frame of the simulation at which it is baked + """ + self.modifier_name = modifier_name + self.modifier_type = modifier_type + self.modifier_parameters = modifier_parameters + self.frame_end = frame_end + + def __str__(self): + return "Physics Modifier: " + self.modifier_name + " of type " + self.modifier_type + \ + " with parameters: " + str(self.modifier_parameters) + " with frame end: " + str(self.frame_end) + class OperatorSpec: """ Holds one operator and its parameters. @@ -105,7 +127,7 @@ class MeshTest: the public method run_test(). """ - def __init__(self, test_object_name: str, expected_object_name: str, operations_stack=None, apply_modifiers=False): + def __init__(self, test_object_name: str, expected_object_name: str, operations_stack=None, apply_modifiers=False, threshold=None): """ Constructs a MeshTest object. Raises a KeyError if objects with names expected_object_name or test_object_name don't exist. @@ -125,6 +147,7 @@ class MeshTest: type(operation))) self.operations_stack = operations_stack self.apply_modifier = apply_modifiers + self.threshold = threshold self.verbose = os.environ.get("BLENDER_VERBOSE") is not None self.update = os.getenv('BLENDER_TEST_UPDATE') is not None @@ -235,6 +258,49 @@ class MeshTest: if self.apply_modifier: bpy.ops.object.modifier_apply(modifier=modifier_spec.modifier_name) + + def _bake_current_simulation(self, obj, test_mod_type, test_mod_name, frame_end): + for scene in bpy.data.scenes: + for modifier in obj.modifiers: + if modifier.type == test_mod_type: + obj.modifiers[test_mod_name].point_cache.frame_end = frame_end + override = {'scene': scene, 'active_object': obj, 'point_cache': modifier.point_cache} + bpy.ops.ptcache.bake(override, bake=True) + break + + def _apply_physics_settings(self, test_object, physics_spec: PhysicsSpec): + """ + Apply Physics settings to test objects. + """ + scene = bpy.context.scene + scene.frame_set(1) + modifier = test_object.modifiers.new(physics_spec.modifier_name, + physics_spec.modifier_type) + physics_setting = modifier.settings + if self.verbose: + print("Created modifier '{}' of type '{}'.". + format(physics_spec.modifier_name, physics_spec.modifier_type)) + + + for param_name in physics_spec.modifier_parameters: + try: + setattr(physics_setting, param_name, physics_spec.modifier_parameters[param_name]) + if self.verbose: + print("\t set parameter '{}' with value '{}'". + format(param_name, physics_spec.modifier_parameters[param_name])) + except AttributeError: + # Clean up first + bpy.ops.object.delete() + raise AttributeError("Modifier '{}' has no parameter named '{}'". + format(physics_spec.modifier_type, param_name)) + + scene.frame_set(physics_spec.frame_end + 1) + + self._bake_current_simulation(test_object, physics_spec.modifier_type, physics_spec.modifier_name, physics_spec.frame_end) + if self.apply_modifier: + bpy.ops.object.modifier_apply(modifier=physics_spec.modifier_name) + + def _apply_operator(self, test_object, operator: OperatorSpec): """ Apply operator on test object. @@ -302,9 +368,12 @@ class MeshTest: elif isinstance(operation, OperatorSpec): self._apply_operator(evaluated_test_object, operation) + + elif isinstance(operation, PhysicsSpec): + self._apply_physics_settings(evaluated_test_object, operation) else: - raise ValueError("Expected operation of type {} or {}. Got {}". - format(type(ModifierSpec), type(OperatorSpec), + raise ValueError("Expected operation of type {} or {} or {}. Got {}". + format(type(ModifierSpec), type(OperatorSpec), type(PhysicsSpec), type(operation))) # Compare resulting mesh with expected one. @@ -312,7 +381,10 @@ class MeshTest: print("Comparing expected mesh with resulting mesh...") evaluated_test_mesh = evaluated_test_object.data expected_mesh = self.expected_object.data - compare_result = evaluated_test_mesh.unit_test_compare(mesh=expected_mesh) + if self.threshold: + compare_result = evaluated_test_mesh.unit_test_compare(mesh=expected_mesh, threshold=self.threshold) + else: + compare_result = evaluated_test_mesh.unit_test_compare(mesh=expected_mesh) compare_success = (compare_result == 'Same') # Also check if invalid geometry (which is never expected) had to be corrected... @@ -434,7 +506,7 @@ class ModifierTest: >>> modifiers_test.run_all_tests() """ - def __init__(self, modifier_tests: list, apply_modifiers=False): + def __init__(self, modifier_tests: list, apply_modifiers=False, threshold=None): """ Construct a modifier test. :param modifier_tests: list - list of modifier test cases. Each element in the list must contain the following @@ -445,6 +517,7 @@ class ModifierTest: """ self.modifier_tests = modifier_tests self.apply_modifiers = apply_modifiers + self.threshold = threshold self.verbose = os.environ.get("BLENDER_VERBOSE") is not None self._failed_tests_list = [] @@ -461,7 +534,7 @@ class ModifierTest: expected_object_name = case[1] spec_list = case[2] - test = MeshTest(test_object_name, expected_object_name) + test = MeshTest(test_object_name, expected_object_name, threshold=self.threshold) if self.apply_modifiers: test.apply_modifier = True diff --git a/tests/python/physics_cloth.py b/tests/python/physics_cloth.py new file mode 100644 index 00000000000..835d0aaed60 --- /dev/null +++ b/tests/python/physics_cloth.py @@ -0,0 +1,56 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# + +import os +import sys + +import bpy + +sys.path.append(os.path.dirname(os.path.realpath(__file__))) +from modules.mesh_test import ModifierTest, PhysicsSpec + + +def main(): + test = [ + ["testCloth", "expectedCloth", + [PhysicsSpec('Cloth', 'CLOTH', {'quality': 5}, 35)]], + ] + cloth_test = ModifierTest(test, threshold=1e-3) + + command = list(sys.argv) + for i, cmd in enumerate(command): + if cmd == "--run-all-tests": + cloth_test.apply_modifiers = True + cloth_test.run_all_tests() + break + elif cmd == "--run-test": + cloth_test.apply_modifiers = False + index = int(command[i + 1]) + cloth_test.run_test(index) + break + + +if __name__ == "__main__": + try: + main() + except: + import traceback + traceback.print_exc() + sys.exit(1) diff --git a/tests/python/physics_softbody.py b/tests/python/physics_softbody.py new file mode 100644 index 00000000000..22e35a6cc76 --- /dev/null +++ b/tests/python/physics_softbody.py @@ -0,0 +1,56 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# + +import os +import sys + +import bpy + +sys.path.append(os.path.dirname(os.path.realpath(__file__))) +from modules.mesh_test import ModifierTest, PhysicsSpec + + +def main(): + test = [ + ["testSoftBody", "expectedSoftBody", + [PhysicsSpec('Softbody', 'SOFT_BODY', {'use_goal': False, 'bend': 8, 'pull': 0.8, 'push': 0.8}, 45)]], + ] + softBody_test = ModifierTest(test) + + command = list(sys.argv) + for i, cmd in enumerate(command): + if cmd == "--run-all-tests": + softBody_test.apply_modifiers = True + softBody_test.run_all_tests() + break + elif cmd == "--run-test": + softBody_test.apply_modifiers = False + index = int(command[i + 1]) + softBody_test.run_test(index) + break + + +if __name__ == "__main__": + try: + main() + except: + import traceback + traceback.print_exc() + sys.exit(1) -- cgit v1.2.3