Accepting patch D5357: Modifiers and operators automated testing.

Patch from Habib Gahbiche (zazizizou) moves the "run operator and
compare mesh to a golden" paradigm used in bevel and boolean tests
into a general framework that separates the test specs from the
blend files. Then adds some other operator and modifier tests using
the new framework. Diff D5357.id20724.diff was applied.
New .blend files, modifiers.blend and operators.blend are needed
in the tests/modeling svn directory; those were separately committed.
This commit is contained in:
Howard Trickey 2020-01-13 07:11:45 -05:00
parent a60606e467
commit 3fdc04d3ee
6 changed files with 1100 additions and 2 deletions

View File

@ -22,6 +22,7 @@
set(USE_EXPERIMENTAL_TESTS FALSE)
set(TEST_SRC_DIR ${CMAKE_SOURCE_DIR}/../lib/tests)
set(TEST_PYTHON_DIR ${CMAKE_SOURCE_DIR}/tests/python)
set(TEST_OUT_DIR ${CMAKE_BINARY_DIR}/tests)
# ugh, any better way to do this on testing only?
@ -126,13 +127,17 @@ add_blender_test(
add_blender_test(
bmesh_bevel
${TEST_SRC_DIR}/modeling/bevel_regression.blend
--python-text run_tests
--python ${TEST_PYTHON_DIR}/bevel_operator.py
--
--run-all-tests
)
add_blender_test(
bmesh_boolean
${TEST_SRC_DIR}/modeling/bool_regression.blend
--python-text run_tests
--python ${TEST_PYTHON_DIR}/boolean_operator.py
--
--run-all-tests
)
add_blender_test(
@ -149,6 +154,24 @@ add_blender_test(
--python-text run_tests.py
)
add_blender_test(
modifiers
${TEST_SRC_DIR}/modeling/modifiers.blend
--python ${TEST_PYTHON_DIR}/modifiers.py
--
--run-all-tests
)
# ------------------------------------------------------------------------------
# OPERATORS TESTS
add_blender_test(
operators
${TEST_SRC_DIR}/modeling/operators.blend
--python ${TEST_PYTHON_DIR}/operators.py
--
--run-all-tests
)
# ------------------------------------------------------------------------------
# IO TESTS

View File

@ -0,0 +1,184 @@
# ##### 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 #####
# To run all tests, use
# BLENDER_VERBOSE=1 blender path/to/bevel_regression.blend --python path/to/bevel_operator.py -- --run_all_tests
# To run one test, use
# BLENDER_VERBOSE=1 blender path/to/bevel_regression.blend --python path/to/bevel_operator.py -- --run_test <index>
# where <index> is the index of the test specified in the list tests.
import bpy
import os
import sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from modules.mesh_test import OperatorTest
def main():
tests = [
# 0
['EDGE', {10}, 'Cube_test', 'Cube_result_1', 'bevel', {'offset': 0.2}],
['EDGE', {10, 7}, 'Cube_test', 'Cube_result_2', 'bevel', {'offset': 0.2, 'offset_type': 'WIDTH'}],
['EDGE', {8, 10, 7}, 'Cube_test', 'Cube_result_3', 'bevel', {'offset': 0.2, 'offset_type': 'DEPTH'}],
['EDGE', {10}, 'Cube_test', 'Cube_result_4', 'bevel', {'offset': 0.4, 'segments': 2}],
['EDGE', {10, 7}, 'Cube_test', 'Cube_result_5', 'bevel', {'offset': 0.4, 'segments': 3}],
# 5
['EDGE', {8, 10, 7}, 'Cube_test', 'Cube_result_6', 'bevel', {'offset': 0.4, 'segments': 4}],
['EDGE', {0, 10, 4, 7}, 'Cube_test', 'Cube_result_7', 'bevel', {'offset': 0.4, 'segments': 5, 'profile': 0.2}],
['EDGE', {8, 10, 7}, 'Cube_test', 'Cube_result_8', 'bevel', {'offset': 0.4, 'segments': 5, 'profile': 0.25}],
['EDGE', {8, 10, 7}, 'Cube_test', 'Cube_result_9', 'bevel', {'offset': 0.4, 'segments': 6, 'profile': 0.9}],
['EDGE', {10, 7}, 'Cube_test', 'Cube_result_10', 'bevel', {'offset': 0.4, 'segments': 4, 'profile': 1.0}],
# 10
['EDGE', {8, 10, 7}, 'Cube_test', 'Cube_result_11', 'bevel', {'offset': 0.4, 'segments': 5, 'profile': 1.0}],
['EDGE', {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, 'Cube_test', 'Cube_result_12', 'bevel',
{'offset': 0.4, 'segments': 8}],
['EDGE', {5}, 'Pyr4_test', 'Pyr4_result_1', 'bevel', {'offset': 0.2}],
['EDGE', {2, 5}, 'Pyr4_test', 'Pyr4_result_2', 'bevel', {'offset': 0.2}],
['EDGE', {2, 3, 5}, 'Pyr4_test', 'Pyr4_result_3', 'bevel', {'offset': 0.2}],
# 15
['EDGE', {1, 2, 3, 5}, 'Pyr4_test', 'Pyr4_result_4', 'bevel', {'offset': 0.2}],
['EDGE', {1, 2, 3, 5}, 'Pyr4_test', 'Pyr4_result_5', 'bevel', {'offset': 0.2, 'segments': 3}],
['EDGE', {2, 3}, 'Pyr4_test', 'Pyr4_result_6', 'bevel', {'offset': 0.2, 'segments': 2}],
['EDGE', {1, 2, 3, 5}, 'Pyr4_test', 'Pyr4_result_7', 'bevel', {'offset': 0.2, 'segments': 4, 'profile': 0.15}],
['VERT', {1}, 'Pyr4_test', 'Pyr4_result_8', 'bevel', {'offset': 0.75, 'segments': 4, 'vertex_only': True}],
# 20
['VERT', {1}, 'Pyr4_test', 'Pyr4_result_9', 'bevel',
{'offset': 0.75, 'segments': 3, 'vertex_only': True, 'profile': 0.25}],
['EDGE', {2, 3}, 'Pyr6_test', 'Pyr6_result_1', 'bevel', {'offset': 0.2}],
['EDGE', {8, 2, 3}, 'Pyr6_test', 'Pyr6_result_2', 'bevel', {'offset': 0.2, 'segments': 2}],
['EDGE', {0, 2, 3, 4, 6, 7, 9, 10, 11}, 'Pyr6_test', 'Pyr6_result_3', 'bevel',
{'offset': 0.2, 'segments': 4, 'profile': 0.8}],
['EDGE', {8, 9, 3, 11}, 'Sept_test', 'Sept_result_1', 'bevel', {'offset': 0.1}],
# 25
['EDGE', {8, 9, 11}, 'Sept_test', 'Sept_result_2', 'bevel', {'offset': 0.1, 'offset_type': 'WIDTH'}],
['EDGE', {2, 8, 9, 12, 13, 14}, 'Saddle_test', 'Saddle_result_1', 'bevel', {'offset': 0.3, 'segments': 5}],
['VERT', {4}, 'Saddle_test', 'Saddle_result_2', 'bevel', {'offset': 0.6, 'segments': 6, 'vertex_only': True}],
['EDGE', {2, 5, 8, 11, 14, 18, 21, 24, 27, 30, 34, 37, 40, 43, 46, 50, 53, 56, 59, 62, 112, 113, 114, 115},
'Bent_test', 'Bent_result_1', 'bevel', {'offset': 0.2, 'segments': 3}],
['EDGE', {1, 8, 9, 10, 11}, 'Bentlines_test', 'Bentlines_result_1', 'bevel', {'offset': 0.2, 'segments': 3}],
# 30
['EDGE', {26, 12, 20}, 'Flaretop_test', 'Flaretop_result_1', 'bevel', {'offset': 0.4, 'segments': 2}],
['EDGE', {26, 12, 20}, 'Flaretop_test', 'Flaretop_result_2', 'bevel',
{'offset': 0.4, 'segments': 2, 'profile': 1.0}],
['FACE', {1, 6, 7, 8, 9, 10, 11, 12}, 'Flaretop_test', 'Flaretop_result_3', 'bevel',
{'offset': 0.4, 'segments': 4}],
['EDGE', {4, 8, 10, 18, 24}, 'BentL_test', 'BentL_result_1', 'bevel', {'offset': 0.2}],
['EDGE', {0, 1, 2, 10}, 'Wires_test', 'Wires_test_result_1', 'bevel', {'offset': 0.3}],
# 35
['VERT', {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 'Wires_test', 'Wires_test_result_2', 'bevel',
{'offset': 0.3, 'vertex_only': True}],
['EDGE', {3, 4, 5}, 'tri', 'tri_result_1', 'bevel', {'offset': 0.2}],
['EDGE', {3, 4, 5}, 'tri', 'tri_result_2', 'bevel', {'offset': 0.2, 'segments': 2}],
['EDGE', {3, 4, 5}, 'tri', 'tri_result_3', 'bevel', {'offset': 0.2, 'segments': 3}],
['EDGE', {3, 4}, 'tri', 'tri_result_4', 'bevel', {'offset': 0.2}],
# 40
['EDGE', {3, 4}, 'tri', 'tri_result_5', 'bevel', {'offset': 0.2, 'segments': 2}],
['VERT', {3}, 'tri', 'tri_result_6', 'bevel', {'offset': 0.2, 'vertex_only': True}],
['VERT', {3}, 'tri', 'tri_result_7', 'bevel', {'offset': 0.2, 'segments': 2, 'vertex_only': True}],
['VERT', {3}, 'tri', 'tri_result_8', 'bevel', {'offset': 0.2, 'segments': 3, 'vertex_only': True}],
['VERT', {1}, 'tri', 'tri_result_9', 'bevel', {'offset': 0.2, 'vertex_only': True}],
# 45
['EDGE', {3, 4, 5}, 'tri1gap', 'tri1gap_result_1', 'bevel', {'offset': 0.2}],
['EDGE', {3, 4, 5}, 'tri1gap', 'tri1gap_result_2', 'bevel', {'offset': 0.2, 'segments': 2}],
['EDGE', {3, 4, 5}, 'tri1gap', 'tri1gap_result_3', 'bevel', {'offset': 0.2, 'segments': 3}],
['EDGE', {3, 4}, 'tri1gap', 'tri1gap_result_4', 'bevel', {'offset': 0.2}],
['EDGE', {3, 4}, 'tri1gap', 'tri1gap_result_5', 'bevel', {'offset': 0.2, 'segments': 2}],
# 50
['EDGE', {3, 4}, 'tri1gap', 'tri1gap_result_6', 'bevel', {'offset': 0.2, 'segments': 3}],
['EDGE', {3, 5}, 'tri1gap', 'tri1gap_result_7', 'bevel', {'offset': 0.2}],
['EDGE', {3, 5}, 'tri1gap', 'tri1gap_result_8', 'bevel', {'offset': 0.2, 'segments': 2}],
['EDGE', {3, 5}, 'tri1gap', 'tri1gap_result_9', 'bevel', {'offset': 0.2, 'segments': 3}],
['VERT', {3}, 'tri1gap', 'tri1gap_result_10', 'bevel', {'offset': 0.2, 'vertex_only': True}],
# 55
['EDGE', {3, 4, 5}, 'tri2gaps', 'tri2gaps_result_1', 'bevel', {'offset': 0.2}],
['EDGE', {3, 4, 5}, 'tri2gaps', 'tri2gaps_result_2', 'bevel', {'offset': 0.2, 'segments': 2}],
['EDGE', {3, 4, 5}, 'tri2gaps', 'tri2gaps_result_3', 'bevel', {'offset': 0.2, 'segments': 3}],
['EDGE', {3, 4}, 'tri2gaps', 'tri2gaps_result_4', 'bevel', {'offset': 0.2}],
['EDGE', {3, 4}, 'tri2gaps', 'tri2gaps_result_5', 'bevel', {'offset': 0.2, 'segments': 2}],
# 60
['EDGE', {3, 4}, 'tri2gaps', 'tri2gaps_result_6', 'bevel', {'offset': 0.2, 'segments': 3}],
['EDGE', {3, 4, 5}, 'tri3gaps', 'tri3gaps_result_1', 'bevel', {'offset': 0.2}],
['EDGE', {3, 4, 5}, 'tri3gaps', 'tri3gaps_result_2', 'bevel', {'offset': 0.2, 'segments': 2}],
['EDGE', {3, 4, 5}, 'tri3gaps', 'tri3gaps_result_3', 'bevel', {'offset': 0.2, 'segments': 3}],
['EDGE', {32, 33, 34, 35, 24, 25, 26, 27, 28, 29, 30, 31}, 'cube3', 'cube3_result_1', 'bevel', {'offset': 0.2}],
# 65
['EDGE', {32, 33, 34, 35, 24, 25, 26, 27, 28, 29, 30, 31}, 'cube3', 'cube3_result_2', 'bevel',
{'offset': 0.2, 'segments': 2}],
['EDGE', {32, 35}, 'cube3', 'cube3_result_3', 'bevel', {'offset': 0.2}],
['EDGE', {24, 35}, 'cube3', 'cube3_result_4', 'bevel', {'offset': 0.2}],
['EDGE', {24, 32, 35}, 'cube3', 'cube3_result_5', 'bevel', {'offset': 0.2, 'segments': 2}],
['EDGE', {24, 32, 35}, 'cube3', 'cube3_result_6', 'bevel', {'offset': 0.2, 'segments': 3}],
# 70
['EDGE', {0, 1, 6, 7, 12, 14, 16, 17}, 'Tray', 'Tray_result_1', 'bevel', {'offset': 0.01, 'segments': 2}],
['EDGE', {33, 4, 38, 8, 41, 10, 42, 12, 14, 17, 24, 31}, 'Bumptop', 'Bumptop_result_1', 'bevel',
{'offset': 0.1, 'segments': 4}],
['EDGE', {16, 14, 15}, 'Multisegment_test', 'Multisegment_result_1', 'bevel', {'offset': 0.2}],
['EDGE', {16, 14, 15}, 'Multisegment_test', 'Multisegment_result_1', 'bevel', {'offset': 0.2}],
['EDGE', {19, 20, 23, 15}, 'Window_test', 'Window_result_1', 'bevel', {'offset': 0.05, 'segments': 2}],
# 75
['EDGE', {8}, 'Cube_hn_test', 'Cube_hn_result_1', 'bevel', {'offset': 0.2, 'harden_normals': True}],
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps_test', 'Blocksteps_result_1', 'bevel',
{'offset': 0.2, 'miter_outer': 'PATCH'}],
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps_test', 'Blocksteps_result_2', 'bevel',
{'offset': 0.2, 'segments': 2, 'miter_outer': 'PATCH'}],
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps_test', 'Blocksteps_result_3', 'bevel',
{'offset': 0.2, 'segments': 3, 'miter_outer': 'PATCH'}],
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps_test', 'Blocksteps_result_4', 'bevel',
{'offset': 0.2, 'miter_outer': 'ARC'}],
# 80
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps_test', 'Blocksteps_result_5', 'bevel',
{'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}],
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps_test', 'Blocksteps_result_6', 'bevel',
{'offset': 0.2, 'segments': 3, 'miter_outer': 'ARC'}],
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps_test', 'Blocksteps_result_7', 'bevel',
{'offset': 0.2, 'miter_outer': 'PATCH', 'miter_inner': 'ARC'}],
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps_test', 'Blocksteps_result_8', 'bevel',
{'offset': 0.2, 'segments': 2, 'miter_outer': 'PATCH', 'miter_inner': 'ARC'}],
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps2_test', 'Blocksteps2_result_9', 'bevel',
{'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}],
# 85
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps3_test', 'Blocksteps3_result_10', 'bevel',
{'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}],
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps4_test', 'Blocksteps4_result_11', 'bevel',
{'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}],
['EDGE', {4, 7, 39, 27, 30, 31}, 'Blocksteps4_test', 'Blocksteps4_result_12', 'bevel',
{'offset': 0.2, 'segments': 3, 'miter_outer': 'ARC'}],
['EDGE', {1, 7}, 'Spike_test', 'Spike_result_1', 'bevel', {'offset': 0.2, 'segments': 3}]
]
operator_test = OperatorTest(tests)
command = list(sys.argv)
for i, cmd in enumerate(command):
if cmd == "--run-all-tests":
operator_test.run_all_tests()
break
elif cmd == "--run-test":
index = int(command[i + 1])
operator_test.run_test(index)
break
if __name__ == "__main__":
try:
main()
except:
import traceback
traceback.print_exc()
sys.exit(1)

View File

@ -0,0 +1,68 @@
# ##### 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_VERBOSE=1 blender path/to/bool_regression.blend --python path/to/boolean_operator.py -- --run_all_tests
# To run one test, use
# BLENDER_VERBOSE=1 blender path/to/bool_regression.blend --python path/to/boolean_operator.py -- --run_test <index>
# where <index> is the index of the test specified in the list tests.
import bpy
import os
import sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from modules.mesh_test import OperatorTest
def main():
tests = [
['FACE', {0, 1, 2, 3, 4, 5}, 'Cubecube', 'Cubecube_result_1', 'intersect_boolean', {'operation': 'UNION'}],
['FACE', {0, 1, 2, 3, 4, 5}, 'Cubecube', 'Cubecube_result_2', 'intersect_boolean', {'operation': 'INTERSECT'}],
['FACE', {0, 1, 2, 3, 4, 5}, 'Cubecube', 'Cubecube_result_3', 'intersect_boolean', {'operation': 'DIFFERENCE'}],
['FACE', {0, 1, 2, 3, 4, 5}, 'Cubecube', 'Cubecube_result_4', 'intersect', {'separate_mode': 'CUT'}],
['FACE', {0, 1, 2, 3, 4, 5}, 'Cubecube', 'Cubecube_result_5', 'intersect', {'separate_mode': 'ALL'}],
['FACE', {0, 1, 2, 3, 4, 5}, 'Cubecube', 'Cubecube_result_6', 'intersect', {'separate_mode': 'NONE'}],
['FACE', {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, 'Cubecube', 'Cubecube_result_7', 'intersect',
{'mode': 'SELECT', 'separate_mode': 'NONE'}],
['FACE', {6, 7, 8, 9, 10}, 'Cubecone', 'Cubecone_result_1', 'intersect_boolean', {'operation': 'UNION'}],
['FACE', {0, 1, 2, 3, 4, 5}, 'Cubecones', 'Cubecones_result_1', 'intersect_boolean', {'operation': 'UNION'}],
]
operator_test = OperatorTest(tests)
command = list(sys.argv)
for i, cmd in enumerate(command):
if cmd == "--run-all-tests":
operator_test.run_all_tests()
break
elif cmd == "--run-test":
index = int(command[i + 1])
operator_test.run_test(index)
break
if __name__ == "__main__":
try:
main()
except:
import traceback
traceback.print_exc()
sys.exit(1)

156
tests/python/modifiers.py Normal file
View File

@ -0,0 +1,156 @@
# ##### 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>
import bpy
import os
import sys
from random import shuffle, seed
seed(0)
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from modules.mesh_test import ModifierTest, ModifierSpec
def get_generate_modifiers_list(test_object_name, randomize=False):
"""
Construct a list of 'Generate' modifiers with default parameters.
:param test_object_name: str - name of test object. Some modifiers like boolean need an extra parameter beside
the default one. E.g. boolean needs object, mask needs vertex group etc...
The extra parameter name will be <test_object_name>_<modifier_type>
:param randomize: bool - if True shuffle the list of modifiers.
:return: list of 'Generate' modifiers with default parameters.
"""
boolean_test_object = bpy.data.objects[test_object_name + "_boolean"]
generate_modifiers = [
ModifierSpec('array', 'ARRAY', {}),
ModifierSpec('bevel', 'BEVEL', {'width': 0.1}),
ModifierSpec('boolean', 'BOOLEAN', {'object': boolean_test_object}),
ModifierSpec('build', 'BUILD', {'frame_start': 0, 'frame_duration': 1}),
ModifierSpec('decimate', 'DECIMATE', {}),
ModifierSpec('edge split', 'EDGE_SPLIT', {}),
# mask can effectively delete the mesh since the vertex group need to be updated after each
# applied modifier. Needs to be tested separately.
# ModifierSpec('mask', 'MASK', {'vertex_group': mask_vertex_group}, False),
ModifierSpec('mirror', 'MIRROR', {}),
ModifierSpec('multires', 'MULTIRES', {}),
# remesh can also generate an empty mesh. Skip.
# ModifierSpec('remesh', 'REMESH', {}),
# ModifierSpec('screw', 'SCREW', {}), # screw can make the test very slow. Skipping for now.
# ModifierSpec('skin', 'SKIN', {}), # skin is not reproducible .
ModifierSpec('solidify', 'SOLIDIFY', {}),
ModifierSpec('subsurf', 'SUBSURF', {}),
ModifierSpec('triangulate', 'TRIANGULATE', {}),
ModifierSpec('wireframe', 'WIREFRAME', {})
]
if randomize:
shuffle(generate_modifiers)
return generate_modifiers
def main():
mask_first_list = get_generate_modifiers_list("testCubeMaskFirst", randomize=True)
mask_vertex_group = "testCubeMaskFirst" + "_mask"
mask_first_list.insert(0, ModifierSpec('mask', 'MASK', {'vertex_group': mask_vertex_group}))
tests = [
###############################
# List of 'Generate' modifiers on a cube
###############################
# 0
# ["testCube", "expectedCube", get_generate_modifiers_list("testCube")],
["testCubeRandom", "expectedCubeRandom", get_generate_modifiers_list("testCubeRandom", randomize=True)],
["testCubeMaskFirst", "expectedCubeMaskFirst", mask_first_list],
############################################
# One 'Generate' modifier on primitive meshes
#############################################
# 4
["testCubeArray", "expectedCubeArray", [ModifierSpec('array', 'ARRAY', {})]],
["testCylinderBuild", "expectedCylinderBuild", [ModifierSpec('build', 'BUILD', {'frame_start': 0, 'frame_duration': 1})]],
# 6
["testConeDecimate", "expectedConeDecimate", [ModifierSpec('decimate', 'DECIMATE', {'ratio': 0.5})]],
["testCubeEdgeSplit", "expectedCubeEdgeSplit", [ModifierSpec('edge split', 'EDGE_SPLIT', {})]],
["testSphereMirror", "expectedSphereMirror", [ModifierSpec('mirror', 'MIRROR', {})]],
["testCylinderMask", "expectedCylinderMask", [ModifierSpec('mask', 'MASK', {'vertex_group': "mask_vertex_group"})]],
["testConeMultiRes", "expectedConeMultiRes", [ModifierSpec('multires', 'MULTIRES', {})]],
# 11
["testCubeScrew", "expectedCubeScrew", [ModifierSpec('screw', 'SCREW', {})]],
["testCubeSolidify", "expectedCubeSolidify", [ModifierSpec('solidify', 'SOLIDIFY', {})]],
["testMonkeySubsurf", "expectedMonkeySubsurf", [ModifierSpec('subsurf', 'SUBSURF', {})]],
["testSphereTriangulate", "expectedSphereTriangulate", [ModifierSpec('triangulate', 'TRIANGULATE', {})]],
["testMonkeyWireframe", "expectedMonkeyWireframe", [ModifierSpec('wireframe', 'WIREFRAME', {})]],
#ModifierSpec('skin', 'SKIN', {}), # skin is not reproducible .
#############################################
# One 'Deform' modifier on primitive meshes
#############################################
# 16
["testMonkeyArmature", "expectedMonkeyArmature",
[ModifierSpec('armature', 'ARMATURE', {'object': bpy.data.objects['testArmature'], 'use_vertex_groups': True})]],
["testTorusCast", "expectedTorusCast", [ModifierSpec('cast', 'CAST', {'factor': 2.64})]],
["testCubeCurve", "expectedCubeCurve",
[ModifierSpec('curve', 'CURVE', {'object': bpy.data.objects['testBezierCurve']})]],
["testMonkeyDisplace", "expectedMonkeyDisplace", [ModifierSpec('displace', "DISPLACE", {})]],
# Hook modifier requires moving the hook object to get a mesh change, so can't test it with the current framework
# ["testMonkeyHook", "expectedMonkeyHook",
# [ModifierSpec('hook', 'HOOK', {'object': bpy.data.objects["EmptyHook"], 'vertex_group': "HookVertexGroup"})]],
# 20
#ModifierSpec('laplacian_deform', 'LAPLACIANDEFORM', {}) Laplacian requires a more complex mesh
["testCubeLattice", "expectedCubeLattice",
[ModifierSpec('lattice', 'LATTICE', {'object': bpy.data.objects["testLattice"]})]],
]
modifiers_test = ModifierTest(tests)
command = list(sys.argv)
for i, cmd in enumerate(command):
if cmd == "--run-all-tests":
modifiers_test.apply_modifiers = True
modifiers_test.run_all_tests()
break
elif cmd == "--run-test":
modifiers_test.apply_modifiers = False
index = int(command[i + 1])
modifiers_test.run_test(index)
break
if __name__ == "__main__":
try:
main()
except:
import traceback
traceback.print_exc()
sys.exit(1)

View File

@ -0,0 +1,495 @@
# ##### 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>
# A framework to run regression tests on mesh modifiers and operators based on howardt's mesh_ops_test.py
#
# General idea:
# A test is:
# Object mode
# Select <test_object>
# Duplicate the object
# Select the object
# Apply operation for each operation in <operations_stack> with given parameters
# (an operation is either a modifier or an operator)
# test_mesh = <test_object>.data
# run test_mesh.unit_test_compare(<expected object>.data)
# delete the duplicate object
#
# The words in angle brackets are parameters of the test, and are specified in
# the main class MeshTest.
#
# If the environment variable BLENDER_TEST_UPDATE is set to 1, the <expected_object>
# is updated with the new test result.
# Tests are verbose when the environment variable BLENDER_VERBOSE is set.
import bpy
import os
import inspect
class ModifierSpec:
"""
Holds one modifier and its parameters.
"""
def __init__(self, modifier_name: str, modifier_type: str, modifier_parameters: dict):
"""
Constructs a modifier spec.
:param modifier_name: str - name of object modifier, e.g. "myFirstSubsurfModif"
:param modifier_type: str - type of object modifier, e.g. "SUBSURF"
:param modifier_parameters: dict - {name : val} dictionary giving modifier parameters, e.g. {"quality" : 4}
"""
self.modifier_name = modifier_name
self.modifier_type = modifier_type
self.modifier_parameters = modifier_parameters
def __str__(self):
return "Modifier: " + self.modifier_name + " of type " + self.modifier_type + \
" with parameters: " + str(self.modifier_parameters)
class OperatorSpec:
"""
Holds one operator and its parameters.
"""
def __init__(self, operator_name: str, operator_parameters: dict, select_mode: str, selection: set):
"""
Constructs an operatorSpec. Raises ValueError if selec_mode is invalid.
:param operator_name: str - name of mesh operator from bpy.ops.mesh, e.g. "bevel" or "fill"
:param operator_parameters: dict - {name : val} dictionary containing operator parameters.
:param select_mode: str - mesh selection mode, must be either 'VERT', 'EDGE' or 'FACE'
:param selection: set - set of vertices/edges/faces indices to select, e.g. [0, 9, 10].
"""
self.operator_name = operator_name
self.operator_parameters = operator_parameters
if select_mode not in ['VERT', 'EDGE', 'FACE']:
raise ValueError("select_mode must be either {}, {} or {}".format('VERT', 'EDGE', 'FACE'))
self.select_mode = select_mode
self.selection = selection
def __str__(self):
return "Operator: " + self.operator_name + " with parameters: " + str(self.operator_parameters) + \
" in selection mode: " + self.select_mode + ", selecting " + str(self.selection)
class MeshTest:
"""
A mesh testing class targeted at testing modifiers and operators on a single object.
It holds a stack of mesh operations, i.e. modifiers or operators. The test is executed using
the public method run_test().
"""
def __init__(self, test_object_name: str, expected_object_name: str, operations_stack=None, apply_modifiers=False):
"""
Constructs a MeshTest object. Raises a KeyError if objects with names expected_object_name
or test_object_name don't exist.
:param test_object: str - Name of object of mesh type to run the operations on.
:param expected_object: str - Name of object of mesh type that has the expected
geometry after running the operations.
:param operations_stack: list - stack holding operations to perform on the test_object.
:param apply_modifier: bool - True if we want to apply the modifiers right after adding them to the object.
This affects operations of type ModifierSpec only.
"""
if operations_stack is None:
operations_stack = []
for operation in operations_stack:
if not (isinstance(operation, ModifierSpec) or isinstance(operation, OperatorSpec)):
raise ValueError("Expected operation of type {} or {}. Got {}".
format(type(ModifierSpec), type(OperatorSpec),
type(operation)))
self.operations_stack = operations_stack
self.apply_modifier = apply_modifiers
self.verbose = os.environ.get("BLENDER_VERBOSE") is not None
self.update = os.getenv('BLENDER_TEST_UPDATE') is not None
# Initialize test objects.
objects = bpy.data.objects
self.test_object = objects[test_object_name]
self.expected_object = objects[expected_object_name]
if self.verbose:
print("Found test object {}".format(test_object_name))
print("Found test object {}".format(expected_object_name))
# Private flag to indicate whether the blend file was updated after the test.
self._test_updated = False
def set_test_object(self, test_object_name):
"""
Set test object for the test. Raises a KeyError if object with given name does not exist.
:param test_object_name: name of test object to run operations on.
"""
objects = bpy.data.objects
self.test_object = objects[test_object_name]
def set_expected_object(self, expected_object_name):
"""
Set expected object for the test. Raises a KeyError if object with given name does not exist
:param expected_object_name: Name of expected object.
"""
objects = bpy.data.objects
self.expected_object = objects[expected_object_name]
def add_modifier(self, modifier_spec: ModifierSpec):
"""
Add a modifier to the operations stack.
:param modifier_spec: modifier to add to the operations stack
"""
self.operations_stack.append(modifier_spec)
if self.verbose:
print("Added modififier {}".format(modifier_spec))
def add_operator(self, operator_spec: OperatorSpec):
"""
Adds an operator to the operations stack.
:param operator_spec: OperatorSpec - operator to add to the operations stack.
"""
self.operations_stack.append(operator_spec)
def _on_failed_test(self, compare, evaluated_test_object):
if self.update:
if self.verbose:
print("Test failed expectantly. Updating expected mesh...")
# Replace expected object with object we ran operations on, i.e. evaluated_test_object.
evaluated_test_object.location = self.expected_object.location
expected_object_name = self.expected_object.name
bpy.data.objects.remove(self.expected_object, do_unlink=True)
evaluated_test_object.name = expected_object_name
# Save file
blend_file = bpy.data.filepath
bpy.ops.wm.save_as_mainfile(filepath=blend_file)
self._test_updated = True
# Set new expected object.
self.expected_object = evaluated_test_object
return True
else:
blender_file = bpy.data.filepath
print("Test failed with error: {}. Resulting object mesh '{}' did not match expected object '{}' "
"from file blender file {}".
format(compare, evaluated_test_object.name, self.expected_object.name, blender_file))
return False
def is_test_updated(self):
"""
Check whether running the test with BLENDER_TEST_UPDATE actually modified the .blend test file.
:return: Bool - True if blend file has been updated. False otherwise.
"""
return self._test_updated
def _apply_modifier(self, test_object, modifier_spec: ModifierSpec):
"""
Add modifier to object and apply (if modifier_spec.apply_modifier is True)
:param test_object: bpy.types.Object - Blender object to apply modifier on.
:param modifier_spec: ModifierSpec - ModifierSpec object with parameters
"""
modifier = test_object.modifiers.new(modifier_spec.modifier_name,
modifier_spec.modifier_type)
if self.verbose:
print("Created modifier '{}' of type '{}'.".
format(modifier_spec.modifier_name, modifier_spec.modifier_type))
for param_name in modifier_spec.modifier_parameters:
try:
setattr(modifier, param_name, modifier_spec.modifier_parameters[param_name])
if self.verbose:
print("\t set parameter '{}' with value '{}'".
format(param_name, modifier_spec.modifier_parameters[param_name]))
except AttributeError:
# Clean up first
bpy.ops.object.delete()
raise AttributeError("Modifier '{}' has no parameter named '{}'".
format(modifier_spec.modifier_type, param_name))
if self.apply_modifier:
bpy.ops.object.modifier_apply(modifier=modifier_spec.modifier_name)
def _apply_operator(self, test_object, operator: OperatorSpec):
"""
Apply operator on test object.
:param test_object: bpy.types.Object - Blender object to apply operator on.
:param operator: OperatorSpec - OperatorSpec object with parameters.
"""
mesh = test_object.data
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
# Do selection.
bpy.context.tool_settings.mesh_select_mode = (operator.select_mode == 'VERT',
operator.select_mode == 'EDGE',
operator.select_mode == 'FACE')
for index in operator.selection:
if operator.select_mode == 'VERT':
mesh.vertices[index].select = True
elif operator.select_mode == 'EDGE':
mesh.edges[index].select = True
elif operator.select_mode == 'FACE':
mesh.polygons[index].select = True
else:
raise ValueError("Invalid selection mode")
# Apply operator in edit mode.
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type=operator.select_mode)
mesh_operator = getattr(bpy.ops.mesh, operator.operator_name)
if not mesh_operator:
raise AttributeError("No mesh operator {}".format(operator.operator_name))
retval = mesh_operator(**operator.operator_parameters)
if retval != {'FINISHED'}:
raise RuntimeError("Unexpected operator return value: {}".format(retval))
if self.verbose:
print("Applied operator {}".format(operator))
bpy.ops.object.mode_set(mode='OBJECT')
def run_test(self):
"""
Apply operations in self.operations_stack on self.test_object and compare the
resulting mesh with self.expected_object.data
:return: bool - True if the test passed, False otherwise.
"""
self._test_updated = False
bpy.context.view_layer.objects.active = self.test_object
# Duplicate test object.
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
bpy.context.view_layer.objects.active = self.test_object
self.test_object.select_set(True)
bpy.ops.object.duplicate()
evaluated_test_object = bpy.context.active_object
evaluated_test_object.name = "evaluated_object"
if self.verbose:
print(evaluated_test_object.name, "is set to active")
# Add modifiers and operators.
for operation in self.operations_stack:
if isinstance(operation, ModifierSpec):
self._apply_modifier(evaluated_test_object, operation)
elif isinstance(operation, OperatorSpec):
self._apply_operator(evaluated_test_object, operation)
else:
raise ValueError("Expected operation of type {} or {}. Got {}".
format(type(ModifierSpec), type(OperatorSpec),
type(operation)))
# Compare resulting mesh with expected one.
if self.verbose:
print("Comparing expected mesh with resulting mesh...")
evaluated_test_mesh = evaluated_test_object.data
expected_mesh = self.expected_object.data
compare = evaluated_test_mesh.unit_test_compare(mesh=expected_mesh)
success = (compare == 'Same')
if success:
if self.verbose:
print("Success!")
# Clean up.
if self.verbose:
print("Cleaning up...")
# Delete evaluated_test_object.
bpy.ops.object.delete()
return True
else:
return self._on_failed_test(compare, evaluated_test_object)
class OperatorTest:
"""
Helper class that stores and executes operator tests.
Example usage:
>>> tests = [
>>> ['FACE', {0, 1, 2, 3, 4, 5}, 'Cubecube', 'Cubecube_result_1', 'intersect_boolean', {'operation': 'UNION'}],
>>> ['FACE', {0, 1, 2, 3, 4, 5}, 'Cubecube', 'Cubecube_result_2', 'intersect_boolean', {'operation': 'INTERSECT'}],
>>> ]
>>> operator_test = OperatorTest(tests)
>>> operator_test.run_all_tests()
"""
def __init__(self, operator_tests):
"""
Constructs an operator test.
:param operator_tests: list - list of operator test cases. Each element in the list must contain the following
in the correct order:
1) select_mode: str - mesh selection mode, must be either 'VERT', 'EDGE' or 'FACE'
2) selection: set - set of vertices/edges/faces indices to select, e.g. [0, 9, 10].
3) test_object_name: bpy.Types.Object - test object
4) expected_object_name: bpy.Types.Object - expected object
5) operator_name: str - name of mesh operator from bpy.ops.mesh, e.g. "bevel" or "fill"
6) operator_parameters: dict - {name : val} dictionary containing operator parameters.
"""
self.operator_tests = operator_tests
self.verbose = os.environ.get("BLENDER_VERBOSE") is not None
self._failed_tests_list = []
def run_test(self, index: int):
"""
Run a single test from operator_tests list
:param index: int - index of test
:return: bool - True if test is successful. False otherwise.
"""
case = self.operator_tests[index]
if len(case) != 6:
raise ValueError("Expected exactly 6 parameters for each test case, got {}".format(len(case)))
select_mode = case[0]
selection = case[1]
test_object_name = case[2]
expected_object_name = case[3]
operator_name = case[4]
operator_parameters = case[5]
operator_spec = OperatorSpec(operator_name, operator_parameters, select_mode, selection)
test = MeshTest(test_object_name, expected_object_name)
test.add_operator(operator_spec)
success = test.run_test()
if test.is_test_updated():
# Run the test again if the blend file has been updated.
success = test.run_test()
return success
def run_all_tests(self):
for index, _ in enumerate(self.operator_tests):
if self.verbose:
print()
print("Running test {}...".format(index))
success = self.run_test(index)
if not success:
self._failed_tests_list.append(index)
if len(self._failed_tests_list) != 0:
print("Following tests failed: {}".format(self._failed_tests_list))
blender_path = bpy.app.binary_path
blend_path = bpy.data.filepath
frame = inspect.stack()[1]
module = inspect.getmodule(frame[0])
python_path = module.__file__
print("Run following command to open Blender and run the failing test:")
print("{} {} --python {} -- {} {}"
.format(blender_path, blend_path, python_path, "--run-test", "<test_index>"))
raise Exception("Tests {} failed".format(self._failed_tests_list))
class ModifierTest:
"""
Helper class that stores and executes modifier tests.
Example usage:
>>> modifier_list = [
>>> ModifierSpec("firstSUBSURF", "SUBSURF", {"quality": 5}),
>>> ModifierSpec("firstSOLIDIFY", "SOLIDIFY", {"thickness_clamp": 0.9, "thickness": 1})
>>> ]
>>> tests = [
>>> ["testCube", "expectedCube", modifier_list],
>>> ["testCube_2", "expectedCube_2", modifier_list]
>>> ]
>>> modifiers_test = ModifierTest(tests)
>>> modifiers_test.run_all_tests()
"""
def __init__(self, modifier_tests: list, apply_modifiers=False):
"""
Construct a modifier test.
:param modifier_tests: list - list of modifier test cases. Each element in the list must contain the following
in the correct order:
1) test_object_name: bpy.Types.Object - test object
2) expected_object_name: bpy.Types.Object - expected object
3) modifiers: list - list of mesh_test.ModifierSpec objects.
"""
self.modifier_tests = modifier_tests
self.apply_modifiers = apply_modifiers
self.verbose = os.environ.get("BLENDER_VERBOSE") is not None
self._failed_tests_list = []
def run_test(self, index: int):
"""
Run a single test from self.modifier_tests list
:param index: int - index of test
:return: bool - True if test passed, False otherwise.
"""
case = self.modifier_tests[index]
if len(case) != 3:
raise ValueError("Expected exactly 3 parameters for each test case, got {}".format(len(case)))
test_object_name = case[0]
expected_object_name = case[1]
spec_list = case[2]
test = MeshTest(test_object_name, expected_object_name)
if self.apply_modifiers:
test.apply_modifier = True
for modifier_spec in spec_list:
test.add_modifier(modifier_spec)
success = test.run_test()
if test.is_test_updated():
# Run the test again if the blend file has been updated.
success = test.run_test()
return success
def run_all_tests(self):
"""
Run all tests in self.modifiers_tests list. Raises an exception if one the tests fails.
"""
for index, _ in enumerate(self.modifier_tests):
if self.verbose:
print()
print("Running test {}...\n".format(index))
success = self.run_test(index)
if not success:
self._failed_tests_list.append(index)
if len(self._failed_tests_list) != 0:
print("Following tests failed: {}".format(self._failed_tests_list))
blender_path = bpy.app.binary_path
blend_path = bpy.data.filepath
frame = inspect.stack()[1]
module = inspect.getmodule(frame[0])
python_path = module.__file__
print("Run following command to open Blender and run the failing test:")
print("{} {} --python {} -- {} {}"
.format(blender_path, blend_path, python_path, "--run-test", "<test_index>"))
raise Exception("Tests {} failed".format(self._failed_tests_list))

172
tests/python/operators.py Normal file
View File

@ -0,0 +1,172 @@
# ##### 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>
import bpy
import os
import sys
from random import shuffle, seed
seed(0)
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from modules.mesh_test import OperatorTest, OperatorSpec
# Central vertical loop of Suzanne
MONKEY_LOOP_VERT = {68, 69, 71, 73, 74, 75, 76, 77, 90, 129, 136, 175, 188, 189, 198, 207,
216, 223, 230, 301, 302, 303, 304, 305, 306, 307, 308}
MONKEY_LOOP_EDGE = {131, 278, 299, 305, 307, 334, 337, 359, 384, 396, 399, 412, 415, 560,
567, 572, 577, 615, 622, 627, 632, 643, 648, 655, 660, 707}
def main():
tests = [
#### 0
# bisect
['FACE', {0, 1, 2, 3, 4, 5}, "testCubeBisect", "expectedCubeBisect", "bisect",
{"plane_co": (0, 0, 0), "plane_no": (0, 1, 1), "clear_inner": True, "use_fill": True}],
# blend from shape
['FACE', {0, 1, 2, 3, 4, 5}, "testCubeBlendFromShape", "expectedCubeBlendFromShape", "blend_from_shape",
{"shape": "Key 1"}],
# bridge edge loops
["FACE", {0, 1}, "testCubeBrigeEdgeLoop", "expectedCubeBridgeEdgeLoop", "bridge_edge_loops", {}],
# decimate
["FACE", {i for i in range(500)}, "testMonkeyDecimate", "expectedMonkeyDecimate", "decimate", {"ratio": 0.1}],
### 4
# delete
["VERT", {3}, "testCubeDeleteVertices", "expectedCubeDeleteVertices", "delete", {}],
["FACE", {0}, "testCubeDeleteFaces", "expectedCubeDeleteFaces", "delete", {}],
["EDGE", {0, 1, 2, 3}, "testCubeDeleteEdges", "expectedCubeDeleteEdges", "delete", {}],
# delete edge loop
["VERT", MONKEY_LOOP_VERT, "testMokneyDeleteEdgeLoopVertices", "expectedMonkeyDeleteEdgeLoopVertices",
"delete_edgeloop", {}],
["EDGE", MONKEY_LOOP_EDGE, "testMokneyDeleteEdgeLoopEdges", "expectedMonkeyDeleteEdgeLoopEdges",
"delete_edgeloop", {}],
### 9
# delete loose
["VERT", {i for i in range(12)}, "testCubeDeleteLooseVertices", "expectedCubeDeleteLooseVertices",
"delete_loose", {"use_verts": True, "use_edges": False, "use_faces": False}],
["EDGE", {i for i in range(14)}, "testCubeDeleteLooseEdges", "expectedCubeDeleteLooseEdges",
"delete_loose", {"use_verts": False, "use_edges": True, "use_faces": False}],
["FACE", {i for i in range(7)}, "testCubeDeleteLooseFaces", "expectedCubeDeleteLooseFaces",
"delete_loose", {"use_verts": False, "use_edges": False, "use_faces": True}],
# dissolve degenerate
["VERT", {i for i in range(8)}, "testCubeDissolveDegenerate", "expectedCubeDissolveDegenerate",
"dissolve_degenerate", {}],
### 13
# dissolve edges
["EDGE", {0, 5, 6, 9}, "testCylinderDissolveEdges", "expectedCylinderDissolveEdges",
"dissolve_edges", {}],
# dissolve faces
["VERT", {5, 34, 47, 49, 83, 91, 95}, "testCubeDissolveFaces", "expectedCubeDissolveFaces", "dissolve_faces",
{}],
### 15
# dissolve verts
["VERT", {16, 20, 22, 23, 25}, "testCubeDissolveVerts", "expectedCubeDissolveVerts", "dissolve_verts", {}],
# duplicate
["VERT", {i for i in range(33)} - {23}, "testConeDuplicateVertices", "expectedConeDuplicateVertices",
"duplicate", {}],
["VERT", {23}, "testConeDuplicateOneVertex", "expectedConeDuplicateOneVertex", "duplicate", {}],
["FACE", {6, 9}, "testConeDuplicateFaces", "expectedConeDuplicateFaces", "duplicate", {}],
["EDGE", {i for i in range(64)}, "testConeDuplicateEdges", "expectedConeDuplicateEdges", "duplicate", {}],
### 20
# edge collapse
["EDGE", {1, 9, 4}, "testCylinderEdgeCollapse", "expectedCylinderEdgeCollapse", "edge_collapse", {}],
# edge face add
["VERT", {1, 3, 4, 5, 7}, "testCubeEdgeFaceAddFace", "expectedCubeEdgeFaceAddFace", "edge_face_add", {}],
["VERT", {4, 5}, "testCubeEdgeFaceAddEdge", "expectedCubeEdgeFaceAddEdge", "edge_face_add", {}],
# edge rotate
["EDGE", {1}, "testCubeEdgeRotate", "expectedCubeEdgeRotate", "edge_rotate", {}],
# edge split
["EDGE", {2, 5, 8, 11, 14, 17, 20, 23}, "testCubeEdgeSplit", "expectedCubeEdgeSplit", "edge_split", {}],
### 25
# face make planar
["FACE", {i for i in range(500)}, "testMonkeyFaceMakePlanar", "expectedMonkeyFaceMakePlanar",
"face_make_planar", {}],
# face split by edges
["VERT", {i for i in range(6)}, "testPlaneFaceSplitByEdges", "expectedPlaneFaceSplitByEdges",
"face_split_by_edges", {}],
# fill
["EDGE", {20, 21, 22, 23, 24, 45, 46, 47, 48, 49}, "testIcosphereFill", "expectedIcosphereFill",
"fill", {}],
["EDGE", {20, 21, 22, 23, 24, 45, 46, 47, 48, 49}, "testIcosphereFillUseBeautyFalse",
"expectedIcosphereFillUseBeautyFalse", "fill", {"use_beauty": False}],
# fill grid
["EDGE", {1, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13, 15}, "testPlaneFillGrid", "expectedPlaneFillGrid",
"fill_grid", {}],
["EDGE", {1, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13, 15}, "testPlaneFillGridSimpleBlending",
"expectedPlaneFillGridSimpleBlending", "fill_grid", {"use_interp_simple": True}],
### 31
# fill holes
["VERT", {i for i in range(481)}, "testSphereFillHoles", "expectedSphereFillHoles", "fill_holes", {"sides": 9}],
# inset faces
["VERT", {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, 52, 59, 61, 62, 65, 83, 91, 95}, "testCubeInset",
"expectedCubeInset", "inset", {"thickness": 0.2}],
["VERT", {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, 52, 59, 61, 62, 65, 83, 91, 95},
"testCubeInsetEvenOffsetFalse", "expectedCubeInsetEvenOffsetFalse",
"inset", {"thickness": 0.2, "use_even_offset": False}],
["VERT", {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, 52, 59, 61, 62, 65, 83, 91, 95}, "testCubeInsetDepth",
"expectedCubeInsetDepth", "inset", {"thickness": 0.2, "depth": 0.2}],
["FACE", {35, 36, 37, 45, 46, 47, 55, 56, 57}, "testGridInsetRelativeOffset", "expectedGridInsetRelativeOffset",
"inset", {"thickness": 0.4, "use_relative_offset": True}],
]
operators_test = OperatorTest(tests)
command = list(sys.argv)
for i, cmd in enumerate(command):
if cmd == "--run-all-tests":
operators_test.run_all_tests()
break
elif cmd == "--run-test":
operators_test.apply_modifiers = False
index = int(command[i + 1])
operators_test.run_test(index)
break
if __name__ == "__main__":
try:
main()
except:
import traceback
traceback.print_exc()
sys.exit(1)