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 source/tests/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 followuing 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':
201 mesh.show_all_edges = True
203 mesh.show_normal_vertex = True
207 for poly in mesh.polygons:
208 poly.use_smooth = True
210 for face in mesh.faces:
211 face.use_smooth = True
214 def defaults_modifier(mod):
215 mod.show_in_editmode = True
216 mod.show_on_cage = True
219 # -----------------------------------------------------------------------------
224 def mesh_bmesh_poly_elems(poly, elems):
225 vert_start = poly.loop_start
226 vert_total = poly.loop_total
227 return elems[vert_start:vert_start + vert_total]
229 def mesh_bmesh_poly_vertices(poly):
230 return [loop.vertex_index
231 for loop in mesh_bmesh_poly_elems(poly, poly.id_data.loops)]
234 def mesh_bounds(mesh):
235 xmin = ymin = zmin = +100000000.0
236 xmax = ymax = zmax = -100000000.0
238 for v in mesh.vertices:
248 return (xmin, ymin, zmin), (xmax, ymax, zmax)
251 def mesh_uv_add(obj):
258 uv_lay = obj.data.uv_textures.new()
261 # XXX, odd that we need to do this. until uvs and texface
262 # are separated we will need to keep it
263 uv_loops = obj.data.uv_layers[-1]
264 uv_list = uv_loops.data[:]
265 for poly in obj.data.polygons:
266 poly_uvs = mesh_bmesh_poly_elems(poly, uv_list)
267 for i, c in enumerate(poly_uvs):
270 for uv in uv_lay.data:
279 def mesh_vcol_add(obj, mode=0):
281 colors = ((0.0, 0.0, 0.0), # black
282 (1.0, 0.0, 0.0), # red
283 (0.0, 1.0, 0.0), # green
284 (0.0, 0.0, 1.0), # blue
285 (1.0, 1.0, 0.0), # yellow
286 (0.0, 1.0, 1.0), # cyan
287 (1.0, 0.0, 1.0), # magenta
288 (1.0, 1.0, 1.0), # white
292 return colors[i % len(colors)]
294 vcol_lay = obj.data.vertex_colors.new()
299 col_list = vcol_lay.data[:]
300 for poly in mesh.polygons:
301 face_verts = mesh_bmesh_poly_vertices(poly)
302 poly_cols = mesh_bmesh_poly_elems(poly, col_list)
303 for i, c in enumerate(poly_cols):
304 c.color = colors_get(face_verts[i])
306 for i, col in enumerate(vcol_lay.data):
307 face_verts = mesh.faces[i].vertices
308 col.color1 = colors_get(face_verts[0])
309 col.color2 = colors_get(face_verts[1])
310 col.color3 = colors_get(face_verts[2])
311 if len(face_verts) == 4:
312 col.color4 = colors_get(face_verts[3])
317 def mesh_vgroup_add(obj, name="Group", axis=0, invert=False, mode=0):
319 vgroup = obj.vertex_groups.new(name=name)
320 vgroup.add(list(range(len(mesh.vertices))), 1.0, 'REPLACE')
321 group_index = len(obj.vertex_groups) - 1
323 min_bb, max_bb = mesh_bounds(mesh)
325 range_axis = max_bb[axis] - min_bb[axis]
328 for v in mesh.vertices:
330 if vg.group == group_index:
331 f = (v.co[axis] - min_bb[axis]) / range_axis
332 vg.weight = 1.0 - f if invert else f
337 def mesh_shape_add(obj, mode=0):
341 def mesh_armature_add(obj, mode=0):
345 # -----------------------------------------------------------------------------
348 def modifier_subsurf_add(scene, obj, levels=2):
349 mod = obj.modifiers.new(name=whoami(), type='SUBSURF')
350 defaults_modifier(mod)
353 mod.render_levels = levels
357 def modifier_armature_add(scene, obj):
358 mod = obj.modifiers.new(name=whoami(), type='ARMATURE')
359 defaults_modifier(mod)
361 arm_data = bpy.data.armatures.new(whoami())
362 obj_arm = bpy.data.objects.new(whoami(), arm_data)
364 scene.objects.link(obj_arm)
366 obj_arm.select = True
367 scene.objects.active = obj_arm
369 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
370 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
372 # XXX, annoying, remove bone.
373 while arm_data.edit_bones:
374 obj_arm.edit_bones.remove(arm_data.edit_bones[-1])
376 bone_a = arm_data.edit_bones.new("Bone.A")
377 bone_b = arm_data.edit_bones.new("Bone.B")
378 bone_b.parent = bone_a
380 bone_a.head = -1, 0, 0
381 bone_a.tail = 0, 0, 0
382 bone_b.head = 0, 0, 0
383 bone_b.tail = 1, 0, 0
385 # Get armature animation data
386 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
389 obj_arm.pose.bones["Bone.B"].rotation_quaternion = 1, -0.5, 0, 0
391 # set back to the original
392 scene.objects.active = obj
395 obj_arm.show_x_ray = True
396 arm_data.draw_type = 'STICK'
401 mesh_vgroup_add(obj, name="Bone.A", axis=0, invert=True)
402 mesh_vgroup_add(obj, name="Bone.B", axis=0, invert=False)
407 def modifier_mirror_add(scene, obj):
408 mod = obj.modifiers.new(name=whoami(), type='MIRROR')
409 defaults_modifier(mod)
414 def modifier_solidify_add(scene, obj, thickness=0.25):
415 mod = obj.modifiers.new(name=whoami(), type='SOLIDIFY')
416 defaults_modifier(mod)
418 mod.thickness = thickness
423 def modifier_hook_add(scene, obj, use_vgroup=True):
424 scene.objects.active = obj
426 # no nice way to add hooks from py api yet
427 # assume object mode, hook first face!
431 for v in mesh.vertices:
434 for v in mesh.vertices:
438 face_verts = mesh_bmesh_poly_vertices(mesh.polygons[0])
440 face_verts = mesh.faces[0].vertices[:]
442 for i in mesh.faces[0].vertices:
443 mesh.vertices[i].select = True
445 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
446 bpy.ops.object.hook_add_newob()
447 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
449 # mod = obj.modifiers.new(name=whoami(), type='HOOK')
450 mod = obj.modifiers[-1]
451 defaults_modifier(mod)
453 obj_hook = mod.object
454 obj_hook.rotation_euler = 0, math.radians(45), 0
455 obj_hook.show_x_ray = True
458 mod.vertex_group = obj.vertex_groups[0].name
463 def modifier_decimate_add(scene, obj):
464 mod = obj.modifiers.new(name=whoami(), type='DECIMATE')
465 defaults_modifier(mod)
472 def modifier_build_add(scene, obj):
473 mod = obj.modifiers.new(name=whoami(), type='BUILD')
474 defaults_modifier(mod)
476 # ensure we display some faces
478 totface = len(obj.data.polygons)
480 totface = len(obj.data.faces)
482 mod.frame_start = totface // 2
483 mod.frame_duration = totface
488 def modifier_mask_add(scene, obj):
489 mod = obj.modifiers.new(name=whoami(), type='MASK')
490 defaults_modifier(mod)
492 mod.vertex_group = obj.vertex_groups[0].name
497 # -----------------------------------------------------------------------------
500 # useful since its solid boxy shape but simple enough to debug errors
501 cube_like_vertices = (
584 # useful since its a shell for solidify and it can be mirrored
585 cube_shell_vertices = (
614 def make_cube(scene):
615 bpy.ops.mesh.primitive_cube_add(view_align=False,
616 enter_editmode=False,
621 obj = scene.objects.active
627 def make_cube_extra(scene):
628 obj = make_cube(scene)
638 def make_cube_like(scene):
639 mesh = bpy.data.meshes.new(whoami())
641 mesh.from_pydata(cube_like_vertices, (), cube_like_faces)
642 mesh.update() # add edges
643 obj = bpy.data.objects.new(whoami(), mesh)
644 scene.objects.link(obj)
650 def make_cube_like_extra(scene):
651 obj = make_cube_like(scene)
661 def make_cube_shell(scene):
662 mesh = bpy.data.meshes.new(whoami())
664 mesh.from_pydata(cube_shell_vertices, (), cube_shell_face)
665 mesh.update() # add edges
666 obj = bpy.data.objects.new(whoami(), mesh)
667 scene.objects.link(obj)
673 def make_cube_shell_extra(scene):
674 obj = make_cube_shell(scene)
684 def make_monkey(scene):
685 bpy.ops.mesh.primitive_monkey_add(view_align=False,
686 enter_editmode=False,
690 obj = scene.objects.active
696 def make_monkey_extra(scene):
697 obj = make_monkey(scene)
707 # -----------------------------------------------------------------------------
712 global_tests.append(("none",
717 global_tests.append(("subsurf_single",
718 ((modifier_subsurf_add, dict(levels=2)), ),
722 global_tests.append(("armature_single",
723 ((modifier_armature_add, dict()), ),
727 global_tests.append(("mirror_single",
728 ((modifier_mirror_add, dict()), ),
731 global_tests.append(("hook_single",
732 ((modifier_hook_add, dict()), ),
735 global_tests.append(("decimate_single",
736 ((modifier_decimate_add, dict()), ),
739 global_tests.append(("build_single",
740 ((modifier_build_add, dict()), ),
743 global_tests.append(("mask_single",
744 ((modifier_mask_add, dict()), ),
749 global_tests.append(("mirror_subsurf",
750 ((modifier_mirror_add, dict()),
751 (modifier_subsurf_add, dict(levels=2))),
754 global_tests.append(("solidify_subsurf",
755 ((modifier_solidify_add, dict()),
756 (modifier_subsurf_add, dict(levels=2))),
760 def apply_test(test, scene, obj,
766 test_name, test_funcs = test
768 for cb, kwargs in test_funcs:
769 cb(scene, obj, **kwargs)
771 render_kwargs_copy = render_kwargs.copy()
773 # add test name in filepath
774 render_kwargs_copy["filepath"] += "_%s" % test_name
776 render_func(*render_args, **render_kwargs_copy)
779 # -----------------------------------------------------------------------------
781 # having the 'test_' prefix automatically means these functions are called
785 def test_cube(context, test):
786 scene = context.scene
787 obj = make_cube_extra(scene)
788 ctx_camera_setup(context, location=(3, 3, 3))
790 apply_test(test, scene, obj,
791 render_func=render_gl_all_modes,
792 render_args=(context, obj),
793 render_kwargs=dict(filepath=whoami()))
796 def test_cube_like(context, test):
797 scene = context.scene
798 obj = make_cube_like_extra(scene)
799 ctx_camera_setup(context, location=(5, 5, 5))
801 apply_test(test, scene, obj,
802 render_func=render_gl_all_modes,
803 render_args=(context, obj),
804 render_kwargs=dict(filepath=whoami()))
807 def test_cube_shell(context, test):
808 scene = context.scene
809 obj = make_cube_shell_extra(scene)
810 ctx_camera_setup(context, location=(4, 4, 4))
812 apply_test(test, scene, obj,
813 render_func=render_gl_all_modes,
814 render_args=(context, obj),
815 render_kwargs=dict(filepath=whoami()))
818 # -----------------------------------------------------------------------------
822 print("Calling main!")
823 #render_gl(bpy.context, "/testme")
826 context = bpy.context
831 for key, val in sorted(globals().items()):
832 if key.startswith("test_") and hasattr(val, "__call__"):
833 print("calling:", key)
834 for t in global_tests:
839 # -----------------------------------------------------------------------------
840 # annoying workaround for theme initialization
842 if __name__ == "__main__":
844 from bpy.app.handlers import persistent
847 def load_handler(dummy):
848 print("Load Handler:", bpy.data.filepath)
849 if load_handler.first == False:
850 bpy.app.handlers.scene_update_post.remove(load_handler)
857 traceback.print_exc()
860 # sys.exit(1) # comment to debug
863 load_handler.first = False
865 load_handler.first = True
866 bpy.app.handlers.scene_update_post.append(load_handler)