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