Skip to content

Instantly share code, notes, and snippets.

@aconz2
Last active April 20, 2020 00:08
Show Gist options
  • Save aconz2/d208c98c8eca2ee039523416887bb4ac to your computer and use it in GitHub Desktop.
Save aconz2/d208c98c8eca2ee039523416887bb4ac to your computer and use it in GitHub Desktop.
Blender plugin to rerun a Python script on change. Still buggy properly shutting down
from threading import Event, Thread
import bpy
from inotify_simple import INotify, flags
import traceback
# this is still buggy on properly cleaning things up on exit
def run(exit: Event, changed: Event, filename):
inotify = INotify()
inotify.add_watch(filename, flags.MODIFY)
# wish we could do epoll over the event and the inotify fd
while not exit.is_set():
if inotify.read(500, 10):
print(f'Changes to {filename}')
changed.set()
# based on http://merwanachibet.net/blog/blender-long-running-python-scripts/
class FileWatcher(bpy.types.Operator):
bl_description = 'Rerun a Python script on external change'
bl_idname = 'aconz2.live_reload'
bl_label = 'Live Reload a Python Script on Change'
thread = None
changed = Event()
shouldexit = Event()
timer = None
filepath: bpy.props.StringProperty(subtype='FILE_PATH') # gets magically set by fileselect_add
def modal(self, context, event):
if event.type == 'ESC':
self.shouldexit.set()
self.thread.join()
context.window_manager.event_timer_remove(self.timer)
return {'CANCELLED'}
if event.type == 'TIMER':
if self.changed.is_set():
with open(self.filepath) as fh:
try:
print(f'Running {self.filepath}')
exec(compile(fh.read(), self.filepath, 'exec'), {})
except:
traceback.print_exc()
self.changed.clear()
return {'PASS_THROUGH'}
def execute(self, context):
print('filepath is ', self.filepath)
self.changed.set()
self.thread = Thread(target=run, args=(self.shouldexit, self.changed, self.filepath))
self.thread.start()
self.timer = context.window_manager.event_timer_add(0.1, window=context.window)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def cancel(self, context):
self.shouldexit.set()
self.thread.join()
context.window_manager.event_timer_remove(self.timer)
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
bl_info = {
'name': 'Live Reload External Script',
'blender': (2, 80, 0),
'category': 'Development',
}
def register():
bpy.utils.register_class(FileWatcher)
def unregister():
bpy.utils.unregister_class(FileWatcher)
if __name__ == '__main__':
register()
bpy.ops.aconz2.live_reload('INVOKE_DEFAULT')
@aconz2
Copy link
Author

aconz2 commented Apr 20, 2020

works for a bit and then fails, still buggy; WIP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment