Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'system_demo_mode/demo_mode.py')
-rw-r--r--system_demo_mode/demo_mode.py510
1 files changed, 510 insertions, 0 deletions
diff --git a/system_demo_mode/demo_mode.py b/system_demo_mode/demo_mode.py
new file mode 100644
index 00000000..296abd86
--- /dev/null
+++ b/system_demo_mode/demo_mode.py
@@ -0,0 +1,510 @@
+# ##### 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>
+
+'''
+Even though this is in a package this can run as a stand alone scripts.
+
+# --- example usage
+blender --python release/scripts/addons/system_demo_mode/demo_mode.py
+
+looks for demo.py textblock or file in the same path as the blend:
+# --- example
+config = [
+ dict(anim_cycles=1, anim_render=False, anim_screen_switch=0.0, anim_time_max=10.0, anim_time_min=4.0, mode='AUTO', display_render=4.0, file='/l/19534_simplest_mesh_2.blend'),
+ dict(anim_cycles=1, anim_render=False, anim_screen_switch=0.0, anim_time_max=10.0, anim_time_min=4.0, mode='AUTO', display_render=4.0, file='/l/252_pivotConstraint_01.blend'),
+ ]
+# ---
+/data/src/blender/lib/tests/rendering/
+'''
+
+import bpy
+import time
+import tempfile
+import os
+
+DEMO_CFG = "demo.py"
+
+# populate from script
+global_config_files = []
+
+
+global_config = dict(anim_cycles=1,
+ anim_render=False,
+ anim_screen_switch=0.0,
+ anim_time_max=60.0,
+ anim_time_min=4.0,
+ mode='AUTO',
+ display_render=4.0)
+
+# switch to the next file in 2 sec.
+global_config_fallback = dict(anim_cycles=1,
+ anim_render=False,
+ anim_screen_switch=0.0,
+ anim_time_max=60.0,
+ anim_time_min=4.0,
+ mode='AUTO',
+ display_render=4.0)
+
+
+global_state = {
+ "init_time": 0.0,
+ "last_switch": 0.0,
+ "reset_anim": False,
+ "anim_cycles": 0, # count how many times we played the anim
+ "last_frame": -1,
+ "render_out": "",
+ "render_time": "", # time render was finished.
+ "timer": None,
+ "basedir": "", # demo.py is stored here
+ "demo_index": 0,
+}
+
+
+def demo_mode_auto_select():
+
+ play_area = 0
+ render_area = 0
+
+ totimg = 0
+
+ for area in bpy.context.window.screen.areas:
+ size = area.width * area.height
+ if area.type in {'VIEW_3D', 'GRAPH_EDITOR', 'DOPESHEET_EDITOR', 'NLA_EDITOR', 'TIMELINE'}:
+ play_area += size
+ elif area.type in {'IMAGE_EDITOR', 'SEQUENCE_EDITOR', 'NODE_EDITOR'}:
+ render_area += size
+
+ if area.type == 'IMAGE_EDITOR':
+ totimg += 1
+
+ # since our test files have this as defacto standard
+ scene = bpy.context.scene
+ if totimg >= 2 and (scene.camera or scene.render.use_sequencer):
+ mode = 'RENDER'
+ else:
+ if play_area >= render_area:
+ mode = 'PLAY'
+ else:
+ mode = 'RENDER'
+
+ if 0:
+ return 'PLAY'
+
+ return mode
+
+
+def demo_mode_next_file(step=1):
+ print(global_state["demo_index"])
+ global_state["demo_index"] = (global_state["demo_index"] + step) % len(global_config_files)
+ print(global_state["demo_index"], "....")
+ print("func:demo_mode_next_file", global_state["demo_index"])
+ filepath = global_config_files[global_state["demo_index"]]["file"]
+ bpy.ops.wm.open_mainfile(filepath=filepath)
+
+
+def demo_mode_timer_add():
+ global_state["timer"] = bpy.context.window_manager.event_timer_add(0.8, bpy.context.window)
+
+
+def demo_mode_timer_remove():
+ if global_state["timer"]:
+ bpy.context.window_manager.event_timer_remove(global_state["timer"])
+ global_state["timer"] = None
+
+
+def demo_mode_load_file():
+ """ Take care, this can only do limited functions since its running
+ before the file is fully loaded.
+ Some operators will crash like playing an animation.
+ """
+ print("func:demo_mode_load_file")
+ DemoMode.first_run = True
+ bpy.ops.wm.demo_mode('EXEC_DEFAULT')
+
+
+def demo_mode_init():
+ print("func:demo_mode_init")
+ DemoKeepAlive.ensure()
+
+ if 1:
+ global_config.clear()
+ global_config.update(global_config_files[global_state["demo_index"]])
+
+ print(global_config)
+
+ demo_mode_timer_add()
+
+ if global_config["mode"] == 'AUTO':
+ global_config["mode"] = demo_mode_auto_select()
+
+ if global_config["mode"] == 'PLAY':
+ global_state["last_frame"] = -1
+ global_state["anim_cycles"] = 0
+ bpy.ops.screen.animation_play()
+
+ elif global_config["mode"] == 'RENDER':
+ print(" render")
+
+ # setup tempfile
+ global_state["render_out"] = tempfile.mkstemp()[1]
+ if os.path.exists(global_state["render_out"]):
+ print(" render!!!")
+ os.remove(global_state["render_out"])
+
+ # setup scene.
+ scene = bpy.context.scene
+ scene.render.filepath = global_state["render_out"]
+ scene.render.file_format = 'AVI_JPEG' if global_config["anim_render"] else 'PNG'
+ scene.render.use_file_extension = False
+ scene.render.use_placeholder = False
+ try:
+ if global_config["anim_render"]:
+ bpy.ops.render.render('INVOKE_DEFAULT', animation=True)
+ else:
+ bpy.ops.render.render('INVOKE_DEFAULT', write_still=True)
+ except RuntimeError: # no camera for eg:
+ import traceback
+ traceback.print_exc()
+
+ open(global_state["render_out"], 'w').close() # touch so we move on.
+
+ else:
+ raise Exception("Unsupported mode %r" % global_config["mode"])
+
+ global_state["init_time"] = global_state["last_switch"] = time.time()
+ global_state["render_time"] = -1.0
+
+
+def demo_mode_update():
+ time_current = time.time()
+ time_delta = time_current - global_state["last_switch"]
+ time_total = time_current - global_state["init_time"]
+
+ # --------------------------------------------------------------------------
+ # ANIMATE MODE
+ if global_config["mode"] == 'PLAY':
+ frame = bpy.context.scene.frame_current
+ # check for exit
+ if time_total > global_config["anim_time_max"]:
+ demo_mode_next_file()
+ return
+ # above cycles and minimum display time
+ if (time_total > global_config["anim_time_min"]) and \
+ (global_state["anim_cycles"] > global_config["anim_cycles"]):
+
+ # looped enough now.
+ demo_mode_next_file()
+ return
+
+ # run update funcs
+ if global_state["reset_anim"]:
+ global_state["reset_anim"] = False
+ bpy.ops.screen.animation_cancel(restore_frame=False)
+ bpy.ops.screen.animation_play()
+
+ # warning, switching the screen can switch the scene
+ # and mess with our last-frame/cycles counting.
+ if global_config["anim_screen_switch"]:
+ # print(time_delta, 1)
+ if time_delta > global_config["anim_screen_switch"]:
+
+ screen = bpy.context.window.screen
+ index = bpy.data.screens.keys().index(screen.name)
+ screen_new = bpy.data.screens[(index if index > 0 else len(bpy.data.screens)) - 1]
+ bpy.context.window.screen = screen_new
+
+ global_state["last_switch"] = time_current
+
+ # if we also switch scenes then reset last frame
+ # otherwise it could mess up cycle calc.
+ if screen.scene != screen_new.scene:
+ global_state["last_frame"] = -1
+
+ #if global_config["mode"] == 'PLAY':
+ if 1:
+ global_state["reset_anim"] = True
+
+ # did we loop?
+ if global_state["last_frame"] > frame:
+ print("Cycle!")
+ global_state["anim_cycles"] += 1
+
+ global_state["last_frame"] = frame
+
+ # --------------------------------------------------------------------------
+ # RENDER MODE
+ elif global_config["mode"] == 'RENDER':
+ if os.path.exists(global_state["render_out"]):
+ # wait until the time has passed
+ # XXX, todo, if rendering an anim we need some way to check its done.
+ if global_state["render_time"] == -1.0:
+ global_state["render_time"] = time.time()
+ else:
+ if time.time() - global_state["render_time"] > global_config["display_render"]:
+ os.remove(global_state["render_out"])
+ demo_mode_next_file()
+ return
+ else:
+ raise Exception("Unsupported mode %r" % global_config["mode"])
+
+# -----------------------------------------------------------------------------
+# modal operator
+
+
+class DemoKeepAlive:
+ secret_attr = "_keepalive"
+
+ @staticmethod
+ def ensure():
+ if DemoKeepAlive.secret_attr not in bpy.app.driver_namespace:
+ bpy.app.driver_namespace[DemoKeepAlive.secret_attr] = DemoKeepAlive()
+
+ @staticmethod
+ def remove():
+ if DemoKeepAlive.secret_attr in bpy.app.driver_namespace:
+ del bpy.app.driver_namespace[DemoKeepAlive.secret_attr]
+
+ def __del__(self):
+ """ Hack, when the file is loaded the drivers namespace is cleared.
+ """
+ if DemoMode.enabled:
+ demo_mode_load_file()
+
+
+class DemoMode(bpy.types.Operator):
+ bl_idname = "wm.demo_mode"
+ bl_label = "Demo"
+
+ enabled = False
+ first_run = True
+
+ def cleanup(self, disable=False):
+ demo_mode_timer_remove()
+ DemoMode.first_run = True
+
+ if disable:
+ DemoMode.enabled = False
+ DemoKeepAlive.remove()
+
+ def modal(self, context, event):
+ # print("DemoMode.modal", global_state["anim_cycles"])
+ if not DemoMode.enabled:
+ self.cleanup(disable=True)
+ return {'CANCELLED'}
+
+ if event.type == 'ESC':
+ self.cleanup(disable=True)
+ # disable here and not in cleanup because this is a user level disable.
+ # which should stay disabled until explicitly enabled again.
+ return {'CANCELLED'}
+
+ # print(event.type)
+ if DemoMode.first_run:
+ DemoMode.first_run = False
+
+ demo_mode_init()
+ else:
+ demo_mode_update()
+
+ return {'PASS_THROUGH'}
+
+ def execute(self, context):
+ print("func:DemoMode.execute:", len(global_config_files), "files")
+
+ # load config if not loaded
+ if not global_config_files:
+ load_config()
+ if not global_config_files:
+ self.report({'INFO'}, "No configuration found with text or file: %s. Run File -> Demo Mode Setup" % DEMO_CFG)
+ return {'CANCELLED'}
+
+ # toggle
+ if DemoMode.enabled and DemoMode.first_run == False:
+ # this actually cancells the previous running instance
+ # should never happen now, DemoModeControl is for this.
+ return {'CANCELLED'}
+ else:
+ DemoMode.enabled = True
+ context.window_manager.modal_handler_add(self)
+
+ return {'RUNNING_MODAL'}
+
+ def cancel(self, context):
+ print("func:DemoMode.cancel")
+ # disable here means no running on file-load.
+ self.cleanup()
+ return {'CANCELLED'}
+
+ # call from DemoModeControl
+ @classmethod
+ def disable(cls):
+ if cls.enabled and cls.first_run == False:
+ # this actually cancells the previous running instance
+ # should never happen now, DemoModeControl is for this.
+ cls.enabled = False
+
+
+class DemoModeControl(bpy.types.Operator):
+ bl_idname = "wm.demo_mode_control"
+ bl_label = "Control"
+
+ mode = bpy.props.EnumProperty(items=(
+ ('PREV', "Prev", ""),
+ ('PAUSE', "Pause", ""),
+ ('NEXT', "Next", ""),
+ ),
+ name="Mode")
+
+ def execute(self, context):
+ mode = self.mode
+ if mode == 'PREV':
+ demo_mode_next_file(-1)
+ elif mode == 'NEXT':
+ demo_mode_next_file(1)
+ else: # pause
+ DemoMode.disable()
+ return {'FINISHED'}
+
+
+def menu_func(self, context):
+ # print("func:menu_func - DemoMode.enabled:", DemoMode.enabled, "bpy.app.driver_namespace:", DemoKeepAlive.secret_attr not in bpy.app.driver_namespace, 'global_state["timer"]:', global_state["timer"])
+ layout = self.layout
+ layout.operator_context = 'EXEC_DEFAULT'
+ box = layout.row() # BOX messes layout
+ row = box.row(align=True)
+ row.label("Demo Mode:")
+ if not DemoMode.enabled:
+ row.operator("wm.demo_mode", icon='PLAY', text="")
+ else:
+ row.operator("wm.demo_mode_control", icon='REW', text="").mode = 'PREV'
+ row.operator("wm.demo_mode_control", icon='PAUSE', text="").mode = 'PAUSE'
+ row.operator("wm.demo_mode_control", icon='FF', text="").mode = 'NEXT'
+
+
+def register():
+ bpy.utils.register_class(DemoMode)
+ bpy.utils.register_class(DemoModeControl)
+ bpy.types.INFO_HT_header.append(menu_func)
+
+
+def unregister():
+ bpy.utils.unregister_class(DemoMode)
+ bpy.utils.unregister_class(DemoModeControl)
+ bpy.types.INFO_HT_header.remove(menu_func)
+
+
+# -----------------------------------------------------------------------------
+# parse args
+
+def load_config(cfg_name=DEMO_CFG):
+ namespace = {}
+ global_config_files[:] = []
+ basedir = os.path.dirname(bpy.data.filepath)
+
+ text = bpy.data.texts.get(cfg_name)
+ if text is None:
+ demo_path = os.path.join(basedir, cfg_name)
+ if os.path.exists(demo_path):
+ print("Using config file: %r" % demo_path)
+ demo_file = open(demo_path, "r")
+ demo_data = demo_file.read()
+ demo_file.close()
+ else:
+ demo_data = ""
+ else:
+ print("Using config textblock: %r" % cfg_name)
+ demo_data = text.as_string()
+ demo_path = os.path.join(bpy.data.filepath, cfg_name) # fake
+
+ if not demo_data:
+ print("Could not find %r textblock or %r file." % (DEMO_CFG, demo_path))
+ return False
+
+ namespace["__file__"] = demo_path
+
+ exec(demo_data, namespace, namespace)
+
+ demo_config = namespace["config"]
+ demo_search_path = namespace.get("search_path")
+
+ if demo_search_path is None:
+ print("reading: %r, no search_path found, missing files wont be searched." % demo_path)
+ if demo_search_path.startswith("//"):
+ demo_search_path = os.path.relpath(demo_search_path)
+ if not os.path.exists(demo_search_path):
+ print("reading: %r, search_path %r does not exist." % (demo_path, demo_search_path))
+ demo_search_path = None
+
+ blend_lookup = {}
+ # initialize once, case insensitive dict
+
+ def lookup_file(filepath):
+ filename = os.path.basename(filepath).lower()
+
+ if not blend_lookup:
+ # ensure only ever run once.
+ blend_lookup[None] = None
+
+ def blend_dict_items(path):
+ for dirpath, dirnames, filenames in os.walk(path):
+ # skip '.svn'
+ if dirpath.startswith("."):
+ continue
+ for filename in filenames:
+ if filename.lower().endswith(".blend"):
+ filepath = os.path.join(dirpath, filename)
+ yield (filename.lower(), filepath)
+
+ blend_lookup.update(dict(blend_dict_items(demo_search_path)))
+
+ # fallback to orginal file
+ return blend_lookup.get(filename, filepath)
+ # done with search lookup
+
+ for filecfg in demo_config:
+ filepath_test = filecfg["file"]
+ if not os.path.exists(filepath_test):
+ filepath_test = os.path.join(basedir, filecfg["file"])
+ if not os.path.exists(filepath_test):
+ filepath_test = lookup_file(filepath_test) # attempt to get from searchpath
+ if not os.path.exists(filepath_test):
+ print("Cant find %r or %r, skipping!")
+ continue
+
+ filecfg["file"] = os.path.normpath(filepath_test)
+
+ # sanitize
+ filecfg["file"] = os.path.abspath(filecfg["file"])
+ filecfg["file"] = os.path.normpath(filecfg["file"])
+ print(" Adding: %r" % filecfg["file"])
+ global_config_files.append(filecfg)
+
+ print("found %d files" % len(global_config_files))
+
+ global_state["basedir"] = basedir
+
+ return bool(global_config_files)
+
+
+# support direct execution
+if __name__ == "__main__":
+ register()
+
+ demo_mode_load_file() # kick starts the modal operator