1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
21 # Currently this script only generates images from different modifier
22 # combinations and does not validate they work correctly,
23 # this is because we don't get 1:1 match with bmesh.
25 # Later, we may have a way to check the results are valid.
28 # ./blender.bin --factory-startup --python tests/python/bl_mesh_modifiers.py
33 USE_QUICK_RENDER = False
34 IS_BMESH = hasattr(__import__("bpy").types, "LoopColors")
36 # -----------------------------------------------------------------------------
40 def render_gl(context, filepath, shade):
42 def ctx_viewport_shade(context, shade):
43 for area in context.window.screen.areas:
44 if area.type == 'VIEW_3D':
45 space = area.spaces.active
46 # rv3d = space.region_3d
47 space.viewport_shade = shade
52 render.filepath = filepath
53 render.image_settings.file_format = 'PNG'
54 render.image_settings.color_mode = 'RGB'
55 render.use_file_extension = True
56 render.use_antialiasing = False
59 render.resolution_percentage = 100
60 render.resolution_x = 512
61 render.resolution_y = 512
63 ctx_viewport_shade(context, shade)
66 #~ if filepath == "test_cube_shell_solidify_subsurf_wp_wire":
71 bpy.ops.render.opengl(write_still=True,
75 def render_gl_all_modes(context, obj, filepath=""):
77 assert(obj is not None)
78 assert(filepath != "")
82 # avoid drawing outline/center dot
83 bpy.ops.object.select_all(action='DESELECT')
84 scene.objects.active = None
87 scene.tool_settings.mesh_select_mode = False, True, False
90 render_gl(context, filepath + "_ob_solid", shade='SOLID')
95 render_gl(context, filepath + "_ob_wire", shade='WIREFRAME')
96 render_gl(context, filepath + "_ob_textured", shade='TEXTURED')
98 # -------------------------------------------------------------------------
99 # not just draw modes, but object modes!
100 scene.objects.active = obj
102 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
103 bpy.ops.mesh.select_all(action='DESELECT')
104 render_gl(context, filepath + "_edit_wire", shade='WIREFRAME')
105 render_gl(context, filepath + "_edit_solid", shade='SOLID')
106 render_gl(context, filepath + "_edit_textured", shade='TEXTURED')
107 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
109 bpy.ops.object.mode_set(mode='WEIGHT_PAINT', toggle=False)
111 render_gl(context, filepath + "_wp_wire", shade='WIREFRAME')
115 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
117 scene.objects.active = None
120 def ctx_clear_scene(): # copied from batch_import.py
123 for scene in bpy.data.scenes:
124 for obj in scene.objects[:]:
125 scene.objects.unlink(obj)
128 # remove obdata, for now only worry about the startup scene
129 for bpy_data_iter in (bpy.data.objects,
135 for id_data in bpy_data_iter:
136 bpy_data_iter.remove(id_data)
139 def ctx_viewport_camera(context):
140 # because gl render without view_context has no shading option.
141 for area in context.window.screen.areas:
142 if area.type == 'VIEW_3D':
143 space = area.spaces.active
144 space.region_3d.view_perspective = 'CAMERA'
147 def ctx_camera_setup(context,
148 location=(0.0, 0.0, 0.0),
149 lookat=(0.0, 0.0, 0.0),
150 # most likely the following vars can be left as defaults
156 camera = bpy.data.cameras.new(whoami())
157 obj = bpy.data.objects.new(whoami(), camera)
159 scene = context.scene
160 scene.objects.link(obj)
163 from mathutils import Vector, Matrix
166 view_vec = Vector(lookat) - Vector(location)
167 rot_mat = view_vec.to_track_quat(lookat_axis, up_axis).to_matrix().to_4x4()
168 tra_mat = Matrix.Translation(location)
170 obj.matrix_world = tra_mat * rot_mat
172 ctx_viewport_camera(context)
177 # -----------------------------------------------------------------------------
186 return inspect.stack()[1][3]
190 return inspect.stack()[2][3]
193 # -----------------------------------------------------------------------------
196 def defaults_object(obj):
199 if obj.type == 'MESH':
200 obj.show_all_edges = True
204 mesh.show_normal_vertex = True
208 for poly in mesh.polygons:
209 poly.use_smooth = True
211 for face in mesh.faces:
212 face.use_smooth = True
215 def defaults_modifier(mod):
216 mod.show_in_editmode = True
217 mod.show_on_cage = True
220 # -----------------------------------------------------------------------------
225 def mesh_bmesh_poly_elems(poly, elems):
226 vert_start = poly.loop_start
227 vert_total = poly.loop_total
228 return elems[vert_start:vert_start + vert_total]
230 def mesh_bmesh_poly_vertices(poly):
231 return [loop.vertex_index
232 for loop in mesh_bmesh_poly_elems(poly, poly.id_data.loops)]
235 def mesh_bounds(mesh):
236 xmin = ymin = zmin = +100000000.0
237 xmax = ymax = zmax = -100000000.0
239 for v in mesh.vertices:
249 return (xmin, ymin, zmin), (xmax, ymax, zmax)
252 def mesh_uv_add(obj):
259 uv_lay = obj.data.uv_textures.new()
262 # XXX, odd that we need to do this. until UV's and texface
263 # are separated we will need to keep it
264 uv_loops = obj.data.uv_layers[-1]
265 uv_list = uv_loops.data[:]
266 for poly in obj.data.polygons:
267 poly_uvs = mesh_bmesh_poly_elems(poly, uv_list)
268 for i, c in enumerate(poly_uvs):
271 for uv in uv_lay.data:
280 def mesh_vcol_add(obj, mode=0):
282 colors = ((0.0, 0.0, 0.0), # black
283 (1.0, 0.0, 0.0), # red
284 (0.0, 1.0, 0.0), # green
285 (0.0, 0.0, 1.0), # blue
286 (1.0, 1.0, 0.0), # yellow
287 (0.0, 1.0, 1.0), # cyan
288 (1.0, 0.0, 1.0), # magenta
289 (1.0, 1.0, 1.0), # white
293 return colors[i % len(colors)]
295 vcol_lay = obj.data.vertex_colors.new()
300 col_list = vcol_lay.data[:]
301 for poly in mesh.polygons:
302 face_verts = mesh_bmesh_poly_vertices(poly)
303 poly_cols = mesh_bmesh_poly_elems(poly, col_list)
304 for i, c in enumerate(poly_cols):
305 c.color = colors_get(face_verts[i])
307 for i, col in enumerate(vcol_lay.data):
308 face_verts = mesh.faces[i].vertices
309 col.color1 = colors_get(face_verts[0])
310 col.color2 = colors_get(face_verts[1])
311 col.color3 = colors_get(face_verts[2])
312 if len(face_verts) == 4:
313 col.color4 = colors_get(face_verts[3])
318 def mesh_vgroup_add(obj, name="Group", axis=0, invert=False, mode=0):
320 vgroup = obj.vertex_groups.new(name=name)
321 vgroup.add(list(range(len(mesh.vertices))), 1.0, 'REPLACE')
322 group_index = len(obj.vertex_groups) - 1
324 min_bb, max_bb = mesh_bounds(mesh)
326 range_axis = max_bb[axis] - min_bb[axis]
329 for v in mesh.vertices:
331 if vg.group == group_index:
332 f = (v.co[axis] - min_bb[axis]) / range_axis
333 vg.weight = 1.0 - f if invert else f
338 def mesh_shape_add(obj, mode=0):
342 def mesh_armature_add(obj, mode=0):
346 # -----------------------------------------------------------------------------
349 def modifier_subsurf_add(scene, obj, levels=2):
350 mod = obj.modifiers.new(name=whoami(), type='SUBSURF')
351 defaults_modifier(mod)
354 mod.render_levels = levels
358 def modifier_armature_add(scene, obj):
359 mod = obj.modifiers.new(name=whoami(), type='ARMATURE')
360 defaults_modifier(mod)
362 arm_data = bpy.data.armatures.new(whoami())
363 obj_arm = bpy.data.objects.new(whoami(), arm_data)
365 scene.objects.link(obj_arm)
367 obj_arm.select = True
368 scene.objects.active = obj_arm
370 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
371 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
373 # XXX, annoying, remove bone.
374 while arm_data.edit_bones:
375 obj_arm.edit_bones.remove(arm_data.edit_bones[-1])
377 bone_a = arm_data.edit_bones.new("Bone.A")
378 bone_b = arm_data.edit_bones.new("Bone.B")
379 bone_b.parent = bone_a
381 bone_a.head = -1, 0, 0
382 bone_a.tail = 0, 0, 0
383 bone_b.head = 0, 0, 0
384 bone_b.tail = 1, 0, 0
386 # Get armature animation data
387 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
390 obj_arm.pose.bones["Bone.B"].rotation_quaternion = 1, -0.5, 0, 0
392 # set back to the original
393 scene.objects.active = obj
396 obj_arm.show_x_ray = True
397 arm_data.draw_type = 'STICK'
402 mesh_vgroup_add(obj, name="Bone.A", axis=0, invert=True)
403 mesh_vgroup_add(obj, name="Bone.B", axis=0, invert=False)
408 def modifier_mirror_add(scene, obj):
409 mod = obj.modifiers.new(name=whoami(), type='MIRROR')
410 defaults_modifier(mod)
415 def modifier_solidify_add(scene, obj, thickness=0.25):
416 mod = obj.modifiers.new(name=whoami(), type='SOLIDIFY')
417 defaults_modifier(mod)
419 mod.thickness = thickness
424 def modifier_hook_add(scene, obj, use_vgroup=True):
425 scene.objects.active = obj
427 # no nice way to add hooks from py api yet
428 # assume object mode, hook first face!
432 for v in mesh.vertices:
435 for v in mesh.vertices:
438 for i in mesh.faces[0].vertices:
439 mesh.vertices[i].select = True
441 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
442 bpy.ops.object.hook_add_newob()
443 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
445 # mod = obj.modifiers.new(name=whoami(), type='HOOK')
446 mod = obj.modifiers[-1]
447 defaults_modifier(mod)
449 obj_hook = mod.object
450 obj_hook.rotation_euler = 0, math.radians(45), 0
451 obj_hook.show_x_ray = True
454 mod.vertex_group = obj.vertex_groups[0].name
459 def modifier_decimate_add(scene, obj):
460 mod = obj.modifiers.new(name=whoami(), type='DECIMATE')
461 defaults_modifier(mod)
468 def modifier_build_add(scene, obj):
469 mod = obj.modifiers.new(name=whoami(), type='BUILD')
470 defaults_modifier(mod)
472 # ensure we display some faces
474 totface = len(obj.data.polygons)
476 totface = len(obj.data.faces)
478 mod.frame_start = totface // 2
479 mod.frame_duration = totface
484 def modifier_mask_add(scene, obj):
485 mod = obj.modifiers.new(name=whoami(), type='MASK')
486 defaults_modifier(mod)
488 mod.vertex_group = obj.vertex_groups[0].name
493 # -----------------------------------------------------------------------------
496 # useful since its solid boxy shape but simple enough to debug errors
497 cube_like_vertices = (
580 # useful since its a shell for solidify and it can be mirrored
581 cube_shell_vertices = (
610 def make_cube(scene):
611 bpy.ops.mesh.primitive_cube_add(view_align=False,
612 enter_editmode=False,
617 obj = scene.objects.active
623 def make_cube_extra(scene):
624 obj = make_cube(scene)
634 def make_cube_like(scene):
635 mesh = bpy.data.meshes.new(whoami())
637 mesh.from_pydata(cube_like_vertices, (), cube_like_faces)
638 mesh.update() # add edges
639 obj = bpy.data.objects.new(whoami(), mesh)
640 scene.objects.link(obj)
646 def make_cube_like_extra(scene):
647 obj = make_cube_like(scene)
657 def make_cube_shell(scene):
658 mesh = bpy.data.meshes.new(whoami())
660 mesh.from_pydata(cube_shell_vertices, (), cube_shell_face)
661 mesh.update() # add edges
662 obj = bpy.data.objects.new(whoami(), mesh)
663 scene.objects.link(obj)
669 def make_cube_shell_extra(scene):
670 obj = make_cube_shell(scene)
680 def make_monkey(scene):
681 bpy.ops.mesh.primitive_monkey_add(view_align=False,
682 enter_editmode=False,
686 obj = scene.objects.active
692 def make_monkey_extra(scene):
693 obj = make_monkey(scene)
703 # -----------------------------------------------------------------------------
708 global_tests.append(("none",
713 global_tests.append(("subsurf_single",
714 ((modifier_subsurf_add, dict(levels=2)), ),
718 global_tests.append(("armature_single",
719 ((modifier_armature_add, dict()), ),
723 global_tests.append(("mirror_single",
724 ((modifier_mirror_add, dict()), ),
727 global_tests.append(("hook_single",
728 ((modifier_hook_add, dict()), ),
731 global_tests.append(("decimate_single",
732 ((modifier_decimate_add, dict()), ),
735 global_tests.append(("build_single",
736 ((modifier_build_add, dict()), ),
739 global_tests.append(("mask_single",
740 ((modifier_mask_add, dict()), ),
745 global_tests.append(("mirror_subsurf",
746 ((modifier_mirror_add, dict()),
747 (modifier_subsurf_add, dict(levels=2))),
750 global_tests.append(("solidify_subsurf",
751 ((modifier_solidify_add, dict()),
752 (modifier_subsurf_add, dict(levels=2))),
756 def apply_test(test, scene, obj,
762 test_name, test_funcs = test
764 for cb, kwargs in test_funcs:
765 cb(scene, obj, **kwargs)
767 render_kwargs_copy = render_kwargs.copy()
769 # add test name in filepath
770 render_kwargs_copy["filepath"] += "_%s" % test_name
772 render_func(*render_args, **render_kwargs_copy)
775 # -----------------------------------------------------------------------------
777 # having the 'test_' prefix automatically means these functions are called
781 def test_cube(context, test):
782 scene = context.scene
783 obj = make_cube_extra(scene)
784 ctx_camera_setup(context, location=(3, 3, 3))
786 apply_test(test, scene, obj,
787 render_func=render_gl_all_modes,
788 render_args=(context, obj),
789 render_kwargs=dict(filepath=whoami()))
792 def test_cube_like(context, test):
793 scene = context.scene
794 obj = make_cube_like_extra(scene)
795 ctx_camera_setup(context, location=(5, 5, 5))
797 apply_test(test, scene, obj,
798 render_func=render_gl_all_modes,
799 render_args=(context, obj),
800 render_kwargs=dict(filepath=whoami()))
803 def test_cube_shell(context, test):
804 scene = context.scene
805 obj = make_cube_shell_extra(scene)
806 ctx_camera_setup(context, location=(4, 4, 4))
808 apply_test(test, scene, obj,
809 render_func=render_gl_all_modes,
810 render_args=(context, obj),
811 render_kwargs=dict(filepath=whoami()))
814 # -----------------------------------------------------------------------------
818 print("Calling main!")
819 # render_gl(bpy.context, "/testme")
822 context = bpy.context
827 for key, val in sorted(globals().items()):
828 if key.startswith("test_") and hasattr(val, "__call__"):
829 print("calling:", key)
830 for t in global_tests:
835 # -----------------------------------------------------------------------------
836 # annoying workaround for theme initialization
838 if __name__ == "__main__":
840 from bpy.app.handlers import persistent
843 def load_handler(dummy):
844 print("Load Handler:", bpy.data.filepath)
845 if load_handler.first is False:
846 bpy.app.handlers.scene_update_post.remove(load_handler)
853 traceback.print_exc()
856 # sys.exit(1) # comment to debug
859 load_handler.first = False
861 load_handler.first = True
862 bpy.app.handlers.scene_update_post.append(load_handler)