# ##### 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 ##### # # Currently this script only generates images from different modifier # combinations and does not validate they work correctly, # this is because we don't get 1:1 match with bmesh. # # Later, we may have a way to check the results are valid. # ./blender.bin --factory-startup --python tests/python/bl_mesh_modifiers.py # import math USE_QUICK_RENDER = False # ----------------------------------------------------------------------------- # utility functions def render_gl(context, filepath, shade): def ctx_shading_type(context, shade): for area in context.window.screen.areas: if area.type == 'VIEW_3D': space = area.spaces.active # rv3d = space.region_3d space.shading.type = shade import bpy scene = context.scene render = scene.render render.filepath = filepath render.image_settings.file_format = 'PNG' render.image_settings.color_mode = 'RGB' render.use_file_extension = True render.use_antialiasing = False # render size render.resolution_percentage = 100 render.resolution_x = 512 render.resolution_y = 512 ctx_shading_type(context, shade) # stop to inspect! # if filepath == "test_cube_shell_solidify_subsurf_wp_wire": # assert(0) # else: # return bpy.ops.render.opengl(write_still=True, view_context=True) def render_gl_all_modes(context, obj, filepath=""): assert(obj is not None) assert(filepath != "") scene = context.scene # avoid drawing outline/center dot bpy.ops.object.select_all(action='DESELECT') scene.objects.active = None # editmode scene.tool_settings.mesh_select_mode = False, True, False # render render_gl(context, filepath + "_ob_solid", shade='SOLID') if USE_QUICK_RENDER: return render_gl(context, filepath + "_ob_wire", shade='WIREFRAME') render_gl(context, filepath + "_ob_textured", shade='TEXTURED') # ------------------------------------------------------------------------- # not just draw modes, but object modes! scene.objects.active = obj bpy.ops.object.mode_set(mode='EDIT', toggle=False) bpy.ops.mesh.select_all(action='DESELECT') render_gl(context, filepath + "_edit_wire", shade='WIREFRAME') render_gl(context, filepath + "_edit_solid", shade='SOLID') render_gl(context, filepath + "_edit_textured", shade='TEXTURED') bpy.ops.object.mode_set(mode='OBJECT', toggle=False) bpy.ops.object.mode_set(mode='WEIGHT_PAINT', toggle=False) render_gl(context, filepath + "_wp_wire", shade='WIREFRAME') assert(1) bpy.ops.object.mode_set(mode='OBJECT', toggle=False) scene.objects.active = None def ctx_clear_scene(): # copied from batch_import.py import bpy unique_obs = set() for scene in bpy.data.scenes: for obj in scene.objects[:]: scene.objects.unlink(obj) unique_obs.add(obj) # remove obdata, for now only worry about the startup scene for bpy_data_iter in (bpy.data.objects, bpy.data.meshes, bpy.data.lights, bpy.data.cameras, ): for id_data in bpy_data_iter: bpy_data_iter.remove(id_data) def ctx_viewport_camera(context): # because gl render without view_context has no shading option. for area in context.window.screen.areas: if area.type == 'VIEW_3D': space = area.spaces.active space.region_3d.view_perspective = 'CAMERA' def ctx_camera_setup(context, location=(0.0, 0.0, 0.0), lookat=(0.0, 0.0, 0.0), # most likely the following vars can be left as defaults up=(0.0, 0.0, 1.0), lookat_axis='-Z', up_axis='Y', ): camera = bpy.data.cameras.new(whoami()) obj = bpy.data.objects.new(whoami(), camera) scene = context.scene scene.objects.link(obj) scene.camera = obj from mathutils import Vector, Matrix # setup transform view_vec = Vector(lookat) - Vector(location) rot_mat = view_vec.to_track_quat(lookat_axis, up_axis).to_matrix().to_4x4() tra_mat = Matrix.Translation(location) obj.matrix_world = tra_mat * rot_mat ctx_viewport_camera(context) return obj # ----------------------------------------------------------------------------- # inspect functions import inspect # functions def whoami(): return inspect.stack()[1][3] def whosdaddy(): return inspect.stack()[2][3] # ----------------------------------------------------------------------------- # models (defaults) def defaults_object(obj): obj.show_wire = True if obj.type == 'MESH': obj.show_all_edges = True mesh = obj.data mesh.show_normal_vertex = True for poly in mesh.polygons: poly.use_smooth = True def defaults_modifier(mod): mod.show_in_editmode = True mod.show_on_cage = True # ----------------------------------------------------------------------------- # models (utils) def mesh_bmesh_poly_elems(poly, elems): vert_start = poly.loop_start vert_total = poly.loop_total return elems[vert_start:vert_start + vert_total] def mesh_bmesh_poly_vertices(poly): return [loop.vertex_index for loop in mesh_bmesh_poly_elems(poly, poly.id_data.loops)] def mesh_bounds(mesh): xmin = ymin = zmin = +100000000.0 xmax = ymax = zmax = -100000000.0 for v in mesh.vertices: x, y, z = v.co xmax = max(x, xmax) ymax = max(y, ymax) zmax = max(z, zmax) xmin = min(x, xmin) ymin = min(y, ymin) zmin = min(z, zmin) return (xmin, ymin, zmin), (xmax, ymax, zmax) def mesh_uv_add(obj): uvs = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)) uv_lay = obj.data.uv_layers.new() # XXX, odd that we need to do this. until UV's and texface # are separated we will need to keep it uv_loops = obj.data.uv_layers[-1] uv_list = uv_loops.data[:] for poly in obj.data.polygons: poly_uvs = mesh_bmesh_poly_elems(poly, uv_list) for i, c in enumerate(poly_uvs): c.uv = uvs[i % 4] return uv_lay def mesh_vcol_add(obj, mode=0): colors = ((0.0, 0.0, 0.0), # black (1.0, 0.0, 0.0), # red (0.0, 1.0, 0.0), # green (0.0, 0.0, 1.0), # blue (1.0, 1.0, 0.0), # yellow (0.0, 1.0, 1.0), # cyan (1.0, 0.0, 1.0), # magenta (1.0, 1.0, 1.0), # white ) def colors_get(i): return colors[i % len(colors)] vcol_lay = obj.data.vertex_colors.new() mesh = obj.data col_list = vcol_lay.data[:] for poly in mesh.polygons: face_verts = mesh_bmesh_poly_vertices(poly) poly_cols = mesh_bmesh_poly_elems(poly, col_list) for i, c in enumerate(poly_cols): c.color = colors_get(face_verts[i]) return vcol_lay def mesh_vgroup_add(obj, name="Group", axis=0, invert=False, mode=0): mesh = obj.data vgroup = obj.vertex_groups.new(name=name) vgroup.add(list(range(len(mesh.vertices))), 1.0, 'REPLACE') group_index = len(obj.vertex_groups) - 1 min_bb, max_bb = mesh_bounds(mesh) range_axis = max_bb[axis] - min_bb[axis] # gradient for v in mesh.vertices: for vg in v.groups: if vg.group == group_index: f = (v.co[axis] - min_bb[axis]) / range_axis vg.weight = 1.0 - f if invert else f return vgroup def mesh_shape_add(obj, mode=0): pass def mesh_armature_add(obj, mode=0): pass # ----------------------------------------------------------------------------- # modifiers def modifier_subsurf_add(scene, obj, levels=2): mod = obj.modifiers.new(name=whoami(), type='SUBSURF') defaults_modifier(mod) mod.levels = levels mod.render_levels = levels return mod def modifier_armature_add(scene, obj): mod = obj.modifiers.new(name=whoami(), type='ARMATURE') defaults_modifier(mod) arm_data = bpy.data.armatures.new(whoami()) obj_arm = bpy.data.objects.new(whoami(), arm_data) scene.objects.link(obj_arm) obj_arm.select = True scene.objects.active = obj_arm bpy.ops.object.mode_set(mode='OBJECT', toggle=False) bpy.ops.object.mode_set(mode='EDIT', toggle=False) # XXX, annoying, remove bone. while arm_data.edit_bones: obj_arm.edit_bones.remove(arm_data.edit_bones[-1]) bone_a = arm_data.edit_bones.new("Bone.A") bone_b = arm_data.edit_bones.new("Bone.B") bone_b.parent = bone_a bone_a.head = -1, 0, 0 bone_a.tail = 0, 0, 0 bone_b.head = 0, 0, 0 bone_b.tail = 1, 0, 0 # Get armature animation data bpy.ops.object.mode_set(mode='OBJECT', toggle=False) # 45d armature obj_arm.pose.bones["Bone.B"].rotation_quaternion = 1, -0.5, 0, 0 # set back to the original scene.objects.active = obj # display options obj_arm.show_in_front = True arm_data.draw_type = 'STICK' # apply to modifier mod.object = obj_arm mesh_vgroup_add(obj, name="Bone.A", axis=0, invert=True) mesh_vgroup_add(obj, name="Bone.B", axis=0, invert=False) return mod def modifier_mirror_add(scene, obj): mod = obj.modifiers.new(name=whoami(), type='MIRROR') defaults_modifier(mod) return mod def modifier_solidify_add(scene, obj, thickness=0.25): mod = obj.modifiers.new(name=whoami(), type='SOLIDIFY') defaults_modifier(mod) mod.thickness = thickness return mod def modifier_hook_add(scene, obj, use_vgroup=True): scene.objects.active = obj # no nice way to add hooks from py api yet # assume object mode, hook first face! mesh = obj.data if use_vgroup: for v in mesh.vertices: v.select = True else: for v in mesh.vertices: v.select = False for i in mesh.faces[0].vertices: mesh.vertices[i].select = True bpy.ops.object.mode_set(mode='EDIT', toggle=False) bpy.ops.object.hook_add_newob() bpy.ops.object.mode_set(mode='OBJECT', toggle=False) # mod = obj.modifiers.new(name=whoami(), type='HOOK') mod = obj.modifiers[-1] defaults_modifier(mod) obj_hook = mod.object obj_hook.rotation_euler = 0, math.radians(45), 0 obj_hook.show_in_front = True if use_vgroup: mod.vertex_group = obj.vertex_groups[0].name return mod def modifier_decimate_add(scene, obj): mod = obj.modifiers.new(name=whoami(), type='DECIMATE') defaults_modifier(mod) mod.ratio = 1 / 3 return mod def modifier_build_add(scene, obj): mod = obj.modifiers.new(name=whoami(), type='BUILD') defaults_modifier(mod) # ensure we display some faces totface = len(obj.data.polygons) mod.frame_start = totface // 2 mod.frame_duration = totface return mod def modifier_mask_add(scene, obj): mod = obj.modifiers.new(name=whoami(), type='MASK') defaults_modifier(mod) mod.vertex_group = obj.vertex_groups[0].name return mod # ----------------------------------------------------------------------------- # models # useful since its solid boxy shape but simple enough to debug errors cube_like_vertices = ( (1, 1, -1), (1, -1, -1), (-1, -1, -1), (-1, 1, -1), (1, 1, 1), (1, -1, 1), (-1, -1, 1), (-1, 1, 1), (0, -1, -1), (1, 0, -1), (0, 1, -1), (-1, 0, -1), (1, 0, 1), (0, -1, 1), (-1, 0, 1), (0, 1, 1), (1, -1, 0), (1, 1, 0), (-1, -1, 0), (-1, 1, 0), (0, 0, -1), (0, 0, 1), (1, 0, 0), (0, -1, 0), (-1, 0, 0), (2, 0, 0), (2, 0, -1), (2, 1, 0), (2, 1, -1), (0, 1, 2), (0, 0, 2), (-1, 0, 2), (-1, 1, 2), (-1, 0, 3), (-1, 1, 3), (0, 1, 3), (0, 0, 3), ) cube_like_faces = ( (0, 9, 20, 10), (0, 10, 17), (0, 17, 27, 28), (1, 16, 23, 8), (2, 18, 24, 11), (3, 19, 10), (4, 15, 21, 12), (4, 17, 15), (7, 14, 31, 32), (7, 15, 19), (8, 23, 18, 2), (9, 0, 28, 26), (9, 1, 8, 20), (9, 22, 16, 1), (10, 20, 11, 3), (11, 24, 19, 3), (12, 21, 13, 5), (13, 6, 18), (14, 21, 30, 31), (15, 7, 32, 29), (15, 17, 10, 19), (16, 5, 13, 23), (17, 4, 12, 22), (17, 22, 25, 27), (18, 6, 14, 24), (20, 8, 2, 11), (21, 14, 6, 13), (21, 15, 29, 30), (22, 9, 26, 25), (22, 12, 5, 16), (23, 13, 18), (24, 14, 7, 19), (28, 27, 25, 26), (29, 32, 34, 35), (30, 29, 35, 36), (31, 30, 36, 33), (32, 31, 33, 34), (35, 34, 33, 36), ) # useful since its a shell for solidify and it can be mirrored cube_shell_vertices = ( (0, 0, 1), (0, 1, 1), (-1, 1, 1), (-1, 0, 1), (0, 0, 0), (0, 1, 0), (-1, 1, 0), (-1, 0, 0), (-1, -1, 0), (0, -1, 0), (0, 0, -1), (0, 1, -1), ) cube_shell_face = ( (0, 1, 2, 3), (0, 3, 8, 9), (1, 5, 6, 2), (2, 6, 7, 3), (3, 7, 8), (4, 7, 10), (6, 5, 11), (7, 4, 9, 8), (10, 7, 6, 11), ) def make_cube(scene): bpy.ops.mesh.primitive_cube_add(align='WORLD', enter_editmode=False, location=(0, 0, 0), rotation=(0, 0, 0), ) obj = scene.objects.active defaults_object(obj) return obj def make_cube_extra(scene): obj = make_cube(scene) # extra data layers mesh_uv_add(obj) mesh_vcol_add(obj) mesh_vgroup_add(obj) return obj def make_cube_like(scene): mesh = bpy.data.meshes.new(whoami()) mesh.from_pydata(cube_like_vertices, (), cube_like_faces) mesh.update() # add edges obj = bpy.data.objects.new(whoami(), mesh) scene.objects.link(obj) defaults_object(obj) return obj def make_cube_like_extra(scene): obj = make_cube_like(scene) # extra data layers mesh_uv_add(obj) mesh_vcol_add(obj) mesh_vgroup_add(obj) return obj def make_cube_shell(scene): mesh = bpy.data.meshes.new(whoami()) mesh.from_pydata(cube_shell_vertices, (), cube_shell_face) mesh.update() # add edges obj = bpy.data.objects.new(whoami(), mesh) scene.objects.link(obj) defaults_object(obj) return obj def make_cube_shell_extra(scene): obj = make_cube_shell(scene) # extra data layers mesh_uv_add(obj) mesh_vcol_add(obj) mesh_vgroup_add(obj) return obj def make_monkey(scene): bpy.ops.mesh.primitive_monkey_add(align='WORLD', enter_editmode=False, location=(0, 0, 0), rotation=(0, 0, 0), ) obj = scene.objects.active defaults_object(obj) return obj def make_monkey_extra(scene): obj = make_monkey(scene) # extra data layers mesh_uv_add(obj) mesh_vcol_add(obj) mesh_vgroup_add(obj) return obj # ----------------------------------------------------------------------------- # tests (utils) global_tests = [] global_tests.append( ("none", (), ) ) # single global_tests.append( ("subsurf_single", ((modifier_subsurf_add, dict(levels=2)), ), ) ) global_tests.append( ("armature_single", ((modifier_armature_add, dict()), ), ) ) global_tests.append( ("mirror_single", ((modifier_mirror_add, dict()), ), ) ) global_tests.append( ("hook_single", ((modifier_hook_add, dict()), ), ) ) global_tests.append( ("decimate_single", ((modifier_decimate_add, dict()), ), ) ) global_tests.append( ("build_single", ((modifier_build_add, dict()), ), ) ) global_tests.append( ("mask_single", ((modifier_mask_add, dict()), ), ) ) # combinations global_tests.append( ("mirror_subsurf", ((modifier_mirror_add, dict()), (modifier_subsurf_add, dict(levels=2))), ) ) global_tests.append( ("solidify_subsurf", ((modifier_solidify_add, dict()), (modifier_subsurf_add, dict(levels=2))), ) ) def apply_test( test, scene, obj, render_func=None, render_args=None, render_kwargs=None, ): test_name, test_funcs = test for cb, kwargs in test_funcs: cb(scene, obj, **kwargs) render_kwargs_copy = render_kwargs.copy() # add test name in filepath render_kwargs_copy["filepath"] += "_%s" % test_name render_func(*render_args, **render_kwargs_copy) # ----------------------------------------------------------------------------- # tests themselves! # having the 'test_' prefix automatically means these functions are called # for testing def test_cube(context, test): scene = context.scene obj = make_cube_extra(scene) ctx_camera_setup(context, location=(3, 3, 3)) apply_test( test, scene, obj, render_func=render_gl_all_modes, render_args=(context, obj), render_kwargs=dict(filepath=whoami()) ) def test_cube_like(context, test): scene = context.scene obj = make_cube_like_extra(scene) ctx_camera_setup(context, location=(5, 5, 5)) apply_test( test, scene, obj, render_func=render_gl_all_modes, render_args=(context, obj), render_kwargs=dict(filepath=whoami()) ) def test_cube_shell(context, test): scene = context.scene obj = make_cube_shell_extra(scene) ctx_camera_setup(context, location=(4, 4, 4)) apply_test( test, scene, obj, render_func=render_gl_all_modes, render_args=(context, obj), render_kwargs=dict(filepath=whoami()) ) # ----------------------------------------------------------------------------- # call all tests def main(): print("Calling main!") # render_gl(bpy.context, "/testme") # ctx_clear_scene() context = bpy.context ctx_clear_scene() # run all tests for key, val in sorted(globals().items()): if key.startswith("test_") and hasattr(val, "__call__"): print("calling:", key) for t in global_tests: val(context, test=t) ctx_clear_scene() # ----------------------------------------------------------------------------- # annoying workaround for theme initialization if __name__ == "__main__": import bpy from bpy.app.handlers import persistent @persistent def load_handler(dummy): print("Load Handler:", bpy.data.filepath) if load_handler.first is False: bpy.app.handlers.scene_update_post.remove(load_handler) main() else: load_handler.first = False load_handler.first = True bpy.app.handlers.scene_update_post.append(load_handler)