1# ##### BEGIN GPL LICENSE BLOCK ##### 2# 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. 7# 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. 12# 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. 16# 17# ##### END GPL LICENSE BLOCK ##### 18 19# <pep8 compliant> 20 21bl_info = { 22 "name": "Demo Mode", 23 "author": "Campbell Barton", 24 "blender": (2, 80, 0), 25 "location": "File > Demo Menu", 26 "description": "Demo mode lets you select multiple blend files and loop over them.", 27 "warning": "", 28 "doc_url": "{BLENDER_MANUAL_URL}/addons/system/demo_mode.html", 29 "support": 'OFFICIAL', 30 "category": "System", 31} 32 33# To support reload properly, try to access a package var, if it's there, reload everything 34if "bpy" in locals(): 35 import importlib 36 if "config" in locals(): 37 importlib.reload(config) 38 39 40import bpy 41from bpy.props import ( 42 StringProperty, 43 BoolProperty, 44 IntProperty, 45 FloatProperty, 46 EnumProperty, 47) 48 49 50class DemoModeSetup(bpy.types.Operator): 51 """Create a demo script and optionally execute it""" 52 bl_idname = "wm.demo_mode_setup" 53 bl_label = "Demo Mode (Setup)" 54 bl_options = {'PRESET'} 55 56 # List of operator properties, the attributes will be assigned 57 # to the class instance from the operator settings before calling. 58 59 # these are used to create the file list. 60 directory: StringProperty( 61 name="Search Path", 62 description="Directory used for importing the file", 63 maxlen=1024, 64 subtype='DIR_PATH', 65 ) 66 random_order: BoolProperty( 67 name="Random Order", 68 description="Select files randomly", 69 default=False, 70 ) 71 mode: EnumProperty( 72 name="Method", 73 items=( 74 ('AUTO', "Auto", ""), 75 ('PLAY', "Play", ""), 76 ('RENDER', "Render", ""), 77 ) 78 ) 79 80 run: BoolProperty( 81 name="Run Immediately!", 82 description="Run demo immediately", 83 default=True, 84 ) 85 exit: BoolProperty( 86 name="Exit", 87 description="Run once and exit", 88 default=False, 89 ) 90 91 # these are mapped directly to the config! 92 # 93 # anim 94 # ==== 95 anim_cycles: IntProperty( 96 name="Cycles", 97 description="Number of times to play the animation", 98 min=1, max=1000, 99 default=2, 100 ) 101 anim_time_min: FloatProperty( 102 name="Time Min", 103 description="Minimum number of seconds to show the animation for " 104 "(for small loops)", 105 min=0.0, max=1000.0, 106 soft_min=1.0, soft_max=1000.0, 107 default=4.0, 108 ) 109 anim_time_max: FloatProperty( 110 name="Time Max", 111 description="Maximum number of seconds to show the animation for " 112 "(in case the end frame is very high for no reason)", 113 min=0.0, max=100000000.0, 114 soft_min=1.0, soft_max=100000000.0, 115 default=8.0, 116 ) 117 anim_screen_switch: FloatProperty( 118 name="Screen Switch", 119 description="Time between switching screens (in seconds) " 120 "or 0 to disable", 121 min=0.0, max=100000000.0, 122 soft_min=1.0, soft_max=60.0, 123 default=0.0, 124 ) 125 # 126 # render 127 # ====== 128 display_render: FloatProperty( 129 name="Render Delay", 130 description="Time to display the rendered image before moving on " 131 "(in seconds)", 132 min=0.0, max=60.0, 133 default=4.0, 134 ) 135 anim_render: BoolProperty( 136 name="Render Anim", 137 description="Render entire animation (render mode only)", 138 default=False, 139 ) 140 141 def execute(self, context): 142 from . import config 143 144 keywords = self.as_keywords(ignore=("directory", "random_order", "run", "exit")) 145 cfg_str, _dirpath = config.as_string( 146 self.directory, 147 self.random_order, 148 self.exit, 149 **keywords, 150 ) 151 text = bpy.data.texts.get("demo.py") 152 if text: 153 text.name += ".back" 154 155 text = bpy.data.texts.new("demo.py") 156 text.from_string(cfg_str) 157 158 if self.run: 159 extern_demo_mode_run() 160 161 return {'FINISHED'} 162 163 def invoke(self, context, event): 164 context.window_manager.fileselect_add(self) 165 return {'RUNNING_MODAL'} 166 167 def check(self, context): 168 return True # lazy 169 170 def draw(self, context): 171 layout = self.layout 172 173 box = layout.box() 174 box.label(text="Search *.blend recursively") 175 box.label(text="Writes: demo.py config text") 176 177 layout.prop(self, "run") 178 layout.prop(self, "exit") 179 180 layout.label(text="Generate Settings:") 181 row = layout.row() 182 row.prop(self, "mode", expand=True) 183 layout.prop(self, "random_order") 184 185 mode = self.mode 186 187 layout.separator() 188 sub = layout.column() 189 sub.active = (mode in {'AUTO', 'PLAY'}) 190 sub.label(text="Animate Settings:") 191 sub.prop(self, "anim_cycles") 192 sub.prop(self, "anim_time_min") 193 sub.prop(self, "anim_time_max") 194 sub.prop(self, "anim_screen_switch") 195 196 layout.separator() 197 sub = layout.column() 198 sub.active = (mode in {'AUTO', 'RENDER'}) 199 sub.label(text="Render Settings:") 200 sub.prop(self, "display_render") 201 202 203class DemoModeRun(bpy.types.Operator): 204 bl_idname = "wm.demo_mode_run" 205 bl_label = "Demo Mode (Start)" 206 207 def execute(self, context): 208 if extern_demo_mode_run(): 209 return {'FINISHED'} 210 else: 211 self.report({'ERROR'}, "Cant load demo.py config, run: File -> Demo Mode (Setup)") 212 return {'CANCELLED'} 213 214 215# --- call demo_mode.py funcs 216def extern_demo_mode_run(): 217 # this accesses demo_mode.py which is kept standalone 218 # and can be run direct. 219 from . import demo_mode 220 if demo_mode.load_config(): 221 demo_mode.demo_mode_load_file() # kick starts the modal operator 222 return True 223 else: 224 return False 225 226 227def extern_demo_mode_register(): 228 # this accesses demo_mode.py which is kept standalone 229 # and can be run direct. 230 from . import demo_mode 231 demo_mode.register() 232 233 234def extern_demo_mode_unregister(): 235 # this accesses demo_mode.py which is kept standalone 236 # and can be run direct. 237 from . import demo_mode 238 demo_mode.unregister() 239 240# --- integration 241 242 243def menu_func(self, context): 244 layout = self.layout 245 layout.operator(DemoModeSetup.bl_idname, icon='PREFERENCES') 246 layout.operator(DemoModeRun.bl_idname, icon='PLAY') 247 layout.separator() 248 249 250classes = ( 251 DemoModeSetup, 252 DemoModeRun, 253) 254 255def register(): 256 from bpy.utils import register_class 257 for cls in classes: 258 register_class(cls) 259 260 bpy.types.TOPBAR_MT_file.prepend(menu_func) 261 262 extern_demo_mode_register() 263 264 265def unregister(): 266 from bpy.utils import unregister_class 267 for cls in classes: 268 unregister_class(cls) 269 270 bpy.types.TOPBAR_MT_file.remove(menu_func) 271 272 extern_demo_mode_unregister() 273 274if __name__ == "__main__": 275 register() 276