Changeset View
Standalone View
tests/python/transform_operators.py
- This file was added.
| # ##### 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 ##### | |||||
| # <pep8 compliant> | |||||
| # To run all tests, use | |||||
| # blender path/to/transform_regression.blend --python | |||||
| # path/to/transform_operators.py --enable-event-simulate -- --run-all-tests | |||||
| # To run one test, use | |||||
| # blender path/to/transform_regression.blend --python | |||||
| # path/to/transform_operators.py --enable-event-simulate -- --run-test <name> | |||||
| # where <name> is the name or index of the test specified in the tests list. | |||||
| # | |||||
sybren: I've never seen this before, and I feel it's also a bit clunky. Most test frameworks use the… | |||||
Done Inline ActionsThis index is for internal use. The test name is "transform". This is the same as seen in modifier.py which has an API similar. mano-wii: This index is for internal use. The test name is `"transform"`.
This is the same as seen in… | |||||
Done Inline ActionsIf there is the need to manually add comments to a list to indicate the index of each item, that's an indication of a bad design. There is also a comment that explains how to use that index to run a single test, so it's not for internal use either. modifiers.py identifies tests by name, and not by index, so I don't really see your point. sybren: If there is the need to manually add comments to a list to indicate the index of each item… | |||||
Done Inline ActionsI may be seeing things, but I'm sure it used to be index. mano-wii: I may be seeing things, but I'm sure it used to be index.
I don't even know what the names of… | |||||
| # Possible indexes and names: | |||||
| # 0-"translate_objectmode_global" | |||||
| # 1-"translate_editmode_global" | |||||
| # 2-"translate_editmode_with_global_overridden" | |||||
| # 3-"translate_objectmode_local" | |||||
| # 4-"translate_editmode_local" | |||||
| # 5-"translate_editmode_with_local_overridden" | |||||
| # 6-"rotate_objectmode_local" | |||||
| # 7-"rotate_objectmode_with_local_overridden" | |||||
| # 8-"rotate_objectmode_view" | |||||
| # 9-"rotate_objectmode_with_view_and_orientmatrix_overridden," | |||||
| # 10-"rotate_weightpaintmode_local" | |||||
| # 11-"rotate_editmode_normal_and_individual_orig" | |||||
| # 12-"rotate_editmode_normal_and_individual_orig_with_orienttype_overridden" | |||||
| # 13-"rotate_editmode_normal_and_individual_orig_with_orienttype_and_orientmatrix_overridden" | |||||
| # 14-"translate_editmode_with_event_simulate_gg" (with --enable-event-simulate) | |||||
| # | |||||
| import bpy | |||||
| import os | |||||
Done Inline ActionsWhat are "edit mode a" and "b"? What is being tested here? The way the test code is written, it's hard to see the difference between the "a" and "b" functions. sybren: What are "edit mode a" and "b"? What is being tested here? The way the test code is written… | |||||
| import sys | |||||
| import math | |||||
| sys.path.append(os.path.dirname(os.path.realpath(__file__))) | |||||
| from modules.transform_test import TestContext | |||||
| def test_translate_objectmode_global(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject'] | |||||
| obj_expected = bpy.data.objects['MyObject_trans_global_object_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| bpy.ops.transform.translate(test_ctx.override_context, | |||||
| value=(0,0,1), | |||||
| constraint_axis=(True,True,True),) | |||||
| test_ctx.compare_matrix(obj_dupl, obj_expected, test_translate_objectmode_global.__name__) | |||||
| def test_translate_editmode_global(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject'] | |||||
| obj_expected = bpy.data.objects['MyObject_trans_global_edit_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| test_ctx.mode_set('EDIT_MESH') | |||||
| bpy.ops.mesh.select_all(test_ctx.override_context, action="SELECT") | |||||
| bpy.ops.transform.translate(test_ctx.override_context, | |||||
| value=(0,0,1), | |||||
| constraint_axis=(True,True,True),) | |||||
| test_ctx.compare_data(obj_dupl, obj_expected, test_translate_editmode_global.__name__) | |||||
| def test_translate_editmode_with_global_overridden(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject'] | |||||
| obj_expected = bpy.data.objects['MyObject_trans_global_edit_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
Done Inline ActionsI am sure you can make this more readable about what is the input, what is the expected expression. sergey: I am sure you can make this more readable about what is the input, what is the expected… | |||||
| test_ctx.mode_set('EDIT_MESH') | |||||
| bpy.ops.mesh.select_all(test_ctx.override_context, action="SELECT") | |||||
| bpy.ops.transform.translate(test_ctx.override_context, | |||||
| value=(0,0,1), | |||||
| constraint_axis=(True,True,True), | |||||
| orient_type='GLOBAL',) | |||||
| test_ctx.compare_data(obj_dupl, obj_expected, test_translate_editmode_with_global_overridden.__name__) | |||||
| def test_translate_objectmode_local(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject_transformed'] | |||||
| obj_expected = bpy.data.objects['MyObject_trans_local_object_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| bpy.context.scene.transform_orientation_slots[0].type = 'LOCAL' | |||||
| bpy.ops.transform.translate(test_ctx.override_context, | |||||
| value=(0,0,1), | |||||
| constraint_axis=(True,True,True),) | |||||
| test_ctx.compare_matrix(obj_dupl, obj_expected, test_translate_objectmode_local.__name__) | |||||
| def test_translate_editmode_local(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject_transformed'] | |||||
| obj_expected = bpy.data.objects['MyObject_trans_local_edit_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| test_ctx.mode_set('EDIT_MESH') | |||||
| bpy.context.scene.transform_orientation_slots[0].type = 'LOCAL' | |||||
| bpy.ops.mesh.select_all(test_ctx.override_context, action="SELECT") | |||||
| bpy.ops.transform.translate(test_ctx.override_context, | |||||
| value=(0,0,1), | |||||
| constraint_axis=(True,True,True),) | |||||
| test_ctx.compare_data(obj_dupl, obj_expected, test_translate_editmode_local.__name__) | |||||
| def test_translate_editmode_with_local_overridden(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject_transformed'] | |||||
| obj_expected = bpy.data.objects['MyObject_trans_local_edit_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| test_ctx.mode_set('EDIT_MESH') | |||||
| bpy.context.scene.transform_orientation_slots[0].type = 'LOCAL' | |||||
| bpy.ops.mesh.select_all(test_ctx.override_context, action="SELECT") | |||||
| bpy.ops.transform.translate(test_ctx.override_context, | |||||
| value=(0,0,1), | |||||
| constraint_axis=(True,True,True), | |||||
| orient_type='LOCAL',) | |||||
| test_ctx.compare_data(obj_dupl, obj_expected, test_translate_editmode_with_local_overridden.__name__) | |||||
| def test_rotate_objectmode_local(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject_transformed'] | |||||
| obj_expected = bpy.data.objects['MyObject_rot_local_object_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| bpy.context.scene.transform_orientation_slots[0].type = 'LOCAL' | |||||
| bpy.ops.transform.rotate(test_ctx.override_context, | |||||
| value=math.radians(90), | |||||
| constraint_axis=(False,False,True),) | |||||
| test_ctx.compare_matrix(obj_dupl, obj_expected, test_rotate_objectmode_local.__name__) | |||||
| def test_rotate_objectmode_with_local_overridden(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject_transformed'] | |||||
| obj_expected = bpy.data.objects['MyObject_rot_local_object_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| bpy.context.scene.transform_orientation_slots[0].type = 'LOCAL' | |||||
| bpy.ops.transform.rotate(test_ctx.override_context, | |||||
| value=math.radians(90), | |||||
| constraint_axis=(False,False,True), | |||||
| orient_type='LOCAL',) | |||||
| test_ctx.compare_matrix(obj_dupl, obj_expected, test_rotate_objectmode_with_local_overridden.__name__) | |||||
| def test_rotate_objectmode_view(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject_transformed'] | |||||
| obj_expected = bpy.data.objects['MyObject_rot_view_object_expected_0'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| bpy.context.scene.transform_orientation_slots[0].type = 'VIEW' | |||||
| bpy.ops.transform.rotate(test_ctx.override_context, | |||||
| value=math.radians(90),) | |||||
| test_ctx.compare_matrix(obj_dupl, obj_expected, test_rotate_objectmode_view.__name__) | |||||
| def test_rotate_objectmode_with_view_and_orientmatrix_overridden(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject'] | |||||
| obj_expected = bpy.data.objects['MyObject_rot_view_object_expected_1'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| bpy.context.scene.transform_orientation_slots[0].type = 'VIEW' | |||||
| bpy.ops.transform.rotate(test_ctx.override_context, | |||||
| value=math.radians(90), | |||||
| constraint_axis=(False,False,True), | |||||
| orient_type='VIEW', | |||||
| orient_matrix_type='VIEW', | |||||
| orient_matrix=((1.0,0.0,0.0), | |||||
| (0.0,0.0,1.0), | |||||
| (0.0,-1.0,0.0)), # -Y View | |||||
| ) | |||||
| test_ctx.compare_matrix(obj_dupl, obj_expected, test_rotate_objectmode_with_view_and_orientmatrix_overridden.__name__) | |||||
| def test_rotate_weightpaintmode_local(test_ctx): | |||||
| obj_arm = bpy.data.objects['Armature'] | |||||
| obj_test = bpy.data.objects['MyObject_rigged'] | |||||
| obj_expected = bpy.data.objects['MyObject_rot_local_weightpaint_expected'] | |||||
| test_ctx.select_objects((obj_arm, obj_test), active=obj_test) | |||||
| obj_dupl_act = test_ctx.duplicate_selected() | |||||
| test_ctx.mode_set('PAINT_WEIGHT') | |||||
| bpy.context.scene.transform_orientation_slots[0].type = 'LOCAL' | |||||
| bpy.ops.transform.rotate(test_ctx.override_context, | |||||
| value=math.radians(90), | |||||
| constraint_axis=(True,False,False),) | |||||
| test_ctx.compare_data(obj_dupl_act, obj_expected, test_rotate_weightpaintmode_local.__name__) | |||||
| def test_rotate_editmode_normal_and_individual_orig(test_ctx): | |||||
| scene = bpy.context.scene | |||||
| obj_test = bpy.data.objects['MyObject_transformed'] | |||||
| obj_expected = bpy.data.objects['MyObject_rot_normal_edit_individual_orig_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| test_ctx.mode_set('EDIT_MESH') | |||||
| scene.transform_orientation_slots[0].type = 'NORMAL' | |||||
| scene.tool_settings.transform_pivot_point = 'INDIVIDUAL_ORIGINS' | |||||
| bpy.ops.mesh.select_all(test_ctx.override_context, action="SELECT") | |||||
| bpy.ops.transform.rotate(test_ctx.override_context, | |||||
| value=math.radians(45), | |||||
| constraint_axis=(False,False,True),) | |||||
| test_ctx.compare_data(obj_dupl, obj_expected, test_rotate_editmode_normal_and_individual_orig.__name__) | |||||
| def test_rotate_editmode_normal_and_individual_orig_with_orienttype_overridden(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject_transformed'] | |||||
| obj_expected = bpy.data.objects['MyObject_rot_normal_edit_individual_orig_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| test_ctx.mode_set('EDIT_MESH') | |||||
| scene = bpy.context.scene | |||||
| scene.transform_orientation_slots[0].type = 'NORMAL' | |||||
| scene.tool_settings.transform_pivot_point = 'INDIVIDUAL_ORIGINS' | |||||
| bpy.ops.mesh.select_all(test_ctx.override_context, action="SELECT") | |||||
| bpy.ops.transform.rotate(test_ctx.override_context, | |||||
| value=math.radians(45), | |||||
| constraint_axis=(False,False,True), | |||||
| orient_type='NORMAL',) | |||||
| test_ctx.compare_data(obj_dupl, obj_expected, test_rotate_editmode_normal_and_individual_orig_with_orienttype_overridden.__name__) | |||||
| def test_rotate_editmode_normal_and_individual_orig_with_orienttype_and_orientmatrix_overridden(test_ctx): | |||||
| obj_test = bpy.data.objects['MyObject_transformed'] | |||||
| obj_expected = bpy.data.objects['MyObject_rot_normal_edit_individual_orig_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| test_ctx.mode_set('EDIT_MESH') | |||||
| scene = bpy.context.scene | |||||
| scene.transform_orientation_slots[0].type = 'NORMAL' | |||||
| scene.tool_settings.transform_pivot_point = 'INDIVIDUAL_ORIGINS' | |||||
| bpy.ops.mesh.select_all(test_ctx.override_context, action="SELECT") | |||||
| bpy.ops.transform.rotate(test_ctx.override_context, | |||||
| value=math.radians(45), | |||||
| constraint_axis=(False,False,True), | |||||
| orient_type='NORMAL', | |||||
| orient_matrix_type='NORMAL', | |||||
| orient_matrix=((1,0,0), | |||||
| (0,1,0), | |||||
| (0,0,1)),) | |||||
| test_ctx.compare_data(obj_dupl, obj_expected, test_rotate_editmode_normal_and_individual_orig_with_orienttype_and_orientmatrix_overridden.__name__) | |||||
| def test_translate_editmode_with_event_simulate_gg(test_ctx): | |||||
| import bmesh | |||||
| obj_test = bpy.data.objects['MyObject_transformed'] | |||||
| obj_expected = bpy.data.objects['MyObject_trans_event_gg_expected'] | |||||
| obj_dupl = test_ctx.duplicate_object(obj_test) | |||||
| test_ctx.select_objects((obj_dupl,), active=obj_dupl) | |||||
| test_ctx.mode_set('EDIT_MESH') | |||||
| bpy.ops.mesh.select_all(test_ctx.override_context, action="DESELECT") | |||||
| bm = bmesh.from_edit_mesh(obj_dupl.data) | |||||
| bm.faces.ensure_lookup_table() | |||||
| bm.faces[0].select_set(True) | |||||
| bpy.ops.transform.translate(test_ctx.override_context, 'INVOKE_DEFAULT',) | |||||
| window = test_ctx.override_context['window'] | |||||
| window.event_simulate('Z', 'PRESS') | |||||
| window.event_simulate('Z', 'RELEASE') | |||||
| window.event_simulate('Z', 'PRESS') | |||||
| window.event_simulate('Z', 'RELEASE') | |||||
| window.event_simulate('ONE', 'PRESS', unicode="1") | |||||
| window.event_simulate('ONE', 'RELEASE') | |||||
| window.event_simulate('RET', 'PRESS') | |||||
| window.event_simulate('RET', 'RELEASE') | |||||
| test_ctx.compare_data(obj_dupl, obj_expected, test_translate_editmode_with_event_simulate_gg.__name__) | |||||
Done Inline ActionsWhy isn't this using argparse instead? sybren: Why isn't this using `argparse` instead? | |||||
| def main(): | |||||
| import argparse | |||||
Done Inline ActionsWhat does this do? sybren: What does this do? | |||||
Done Inline ActionsRemoved because it was not meant to be set here. mano-wii: Removed because it was not meant to be set here.
This environment variable is used in some… | |||||
| parser = argparse.ArgumentParser() | |||||
| parser.add_argument("--keep-open", action="store_true") | |||||
| parser.add_argument("--run-all-tests", action="store_true") | |||||
| parser.add_argument("--run-test", default=0, nargs='?', type=str) | |||||
| parser.add_argument("--enable-event-simulate", action="store_true") | |||||
| args = parser.parse_args() | |||||
| funcs = [\ | |||||
| test_translate_objectmode_global, # 0 | |||||
| test_translate_editmode_global, # 1 | |||||
| test_translate_editmode_with_global_overridden, # 2 | |||||
| test_translate_objectmode_local, # 3 | |||||
| test_translate_editmode_local, # 4 | |||||
| test_translate_editmode_with_local_overridden, # 5 | |||||
| test_rotate_objectmode_local, # 6 | |||||
| test_rotate_objectmode_with_local_overridden, # 7 | |||||
| test_rotate_objectmode_view, # 8 | |||||
| test_rotate_objectmode_with_view_and_orientmatrix_overridden, # 9 | |||||
| test_rotate_weightpaintmode_local, #10 | |||||
| test_rotate_editmode_normal_and_individual_orig, # 11 | |||||
| test_rotate_editmode_normal_and_individual_orig_with_orienttype_overridden, # 12 | |||||
| test_rotate_editmode_normal_and_individual_orig_with_orienttype_and_orientmatrix_overridden, # 13 | |||||
| ] | |||||
| if args.enable_event_simulate: | |||||
| funcs.extend([\ | |||||
| test_translate_editmode_with_event_simulate_gg, # 14 | |||||
| ]) | |||||
| test_ctx = TestContext() | |||||
| if args.run_all_tests: | |||||
| test_ctx.funcs = funcs | |||||
| else: | |||||
| # Find the specified function. | |||||
| # Note: Errors are not being dealt with in this case. | |||||
| test_ctx.cleanup = False | |||||
| if args.run_test.isnumeric(): | |||||
| test_ctx.funcs = [functs[int(args.run_test)]] | |||||
| else: | |||||
| funct_name = "test_{}".format(args.run_test) | |||||
| test_ctx.funcs = [globals()[funct_name]] | |||||
| def on_error(): | |||||
| if not args.keep_open: | |||||
| import traceback | |||||
| traceback.print_exc() | |||||
| sys.exit(1) | |||||
| def on_exit(): | |||||
| if not args.keep_open: | |||||
| #bpy.ops.wm.quit_blender() | |||||
| sys.exit(0) | |||||
| test_ctx.on_error_fn = on_error | |||||
| test_ctx.on_exit_fn = on_exit | |||||
| test_ctx.run() | |||||
| if __name__ == "__main__": | |||||
| try: | |||||
| main() | |||||
| except: | |||||
| import traceback | |||||
| traceback.print_exc() | |||||
| sys.exit(1) | |||||
I've never seen this before, and I feel it's also a bit clunky. Most test frameworks use the name of the test, and not its index.