263 lines
8.5 KiB
Python
263 lines
8.5 KiB
Python
import bpy
|
|
import os
|
|
import re
|
|
import time
|
|
|
|
from queue import Queue
|
|
from threading import Thread, Event
|
|
from subprocess import Popen, PIPE, STDOUT
|
|
|
|
from bpy.types import Panel, Operator, PropertyGroup
|
|
from bpy.props import (StringProperty,
|
|
BoolProperty,
|
|
IntProperty,
|
|
FloatProperty,
|
|
EnumProperty,
|
|
PointerProperty,
|
|
CollectionProperty
|
|
)
|
|
|
|
|
|
class BlendFileProperty(PropertyGroup):
|
|
path = StringProperty(subtype="FILE_PATH")
|
|
filename = StringProperty(subtype="FILE_NAME")
|
|
blending_enabled = BoolProperty(
|
|
name="Enable or Disable",
|
|
description="Enable blend file for blending",
|
|
default=False)
|
|
status = StringProperty(
|
|
name="Status",
|
|
description="Status of current file",
|
|
default=""
|
|
)
|
|
|
|
|
|
class PushBlendPullProperties(PropertyGroup):
|
|
project_folder = StringProperty(
|
|
name="Project folder",
|
|
description="Path to project folder",
|
|
default="//",
|
|
subtype="DIR_PATH"
|
|
)
|
|
remote_host = StringProperty(
|
|
name="Remote host",
|
|
description="Address or host of remote",
|
|
defaul =""
|
|
)
|
|
remote_folder = StringProperty(
|
|
name="Remote project folder",
|
|
description="Path to remote project folder",
|
|
default=""
|
|
)
|
|
blend_file_list = CollectionProperty(type=BlendFileProperty)
|
|
upload_status = StringProperty(
|
|
name="Upload status",
|
|
description="Status of current upload",
|
|
default=""
|
|
)
|
|
|
|
|
|
class ProjectManagerRefreshBlenFileOperator(Operator):
|
|
bl_idname = "wm.pm_refresh_blend"
|
|
bl_label = "Refresh Blender Files"
|
|
|
|
def execute(self, context):
|
|
blendFileList = context.window_manager.project_manager.blend_file_list
|
|
enabled = []
|
|
for blendFile in blendFileList:
|
|
if blendFile.blending_enabled:
|
|
enabled.append(blendFile.path)
|
|
|
|
blendFileList.clear()
|
|
path = bpy.path.abspath(context.window_manager.project_manager.project_folder)
|
|
for root, dirs, files in os.walk(path):
|
|
for file in files:
|
|
if file.endswith(".blend"):
|
|
blendFile = blendFileList.add()
|
|
blendFile.filename = os.path.splitext(file)[0]
|
|
blendFile.path = os.path.join(root, file)
|
|
blendFile.name = bpy.path.relpath(blendFile.path)
|
|
blendFile.blending_enabled = blendFile.path in enabled
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class ProjectManagerStartBlend(Operator):
|
|
bl_idname = "wm.project_manager"
|
|
bl_label = "Start blend"
|
|
|
|
_timer = None
|
|
_queue = None
|
|
_worker = None
|
|
|
|
def execute(self, context):
|
|
wm = context.window_manager
|
|
|
|
self._queue = Queue()
|
|
self._worker = RemoteThreadHandler(
|
|
self._queue,
|
|
wm.project_manager.blend_file_list,
|
|
bpy.path.abspath(wm.project_manager.project_folder),
|
|
wm.project_manager.remote_host,
|
|
wm.project_manager.remote_folder
|
|
)
|
|
self._worker.start()
|
|
|
|
self._timer = wm.event_timer_add(1, context.window)
|
|
wm.modal_handler_add(self)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
def modal(self, context, event):
|
|
if event.type in {'ESC'}:
|
|
self.cancel(context)
|
|
print("canceled")
|
|
return {'CANCELLED'}
|
|
|
|
if event.type == 'TIMER':
|
|
while not self._queue.empty():
|
|
self.handle_message(self._queue.get(), context.window_manager.project_manager)
|
|
self._queue.task_done()
|
|
if not self._worker.is_alive():
|
|
self.cancel(context)
|
|
print("finished")
|
|
return {'FINISHED'}
|
|
|
|
return {'PASS_THROUGH'}
|
|
|
|
def cancel(self, context):
|
|
wm = context.window_manager
|
|
wm.event_timer_remove(self._timer)
|
|
self._worker.stop()
|
|
|
|
def handle_message(self, msg, pm):
|
|
if msg[0] == "push":
|
|
print(msg)
|
|
pm.upload_status = "push "+msg[1]
|
|
elif msg[0] == "pull":
|
|
msg[2].status = "pull "+msg[1]
|
|
elif msg[0] == "blend":
|
|
msg[2].status = "blend "+msg[1]
|
|
|
|
|
|
class RemoteThreadHandler(Thread):
|
|
def __init__(self, q, blendFileList, localFolder, remoteHost, remoteFolder):
|
|
super(RemoteThreadHandler, self).__init__()
|
|
|
|
self._queue = q
|
|
self.blendFileList = blendFileList
|
|
self.remotePath = "{}:{}".format(remoteHost,remoteFolder)
|
|
self.localFolder = localFolder
|
|
self.remoteHost = remoteHost
|
|
self.remoteFolder = remoteFolder
|
|
|
|
self._stop_event = Event()
|
|
|
|
def stop(self):
|
|
self._stop_event.set()
|
|
|
|
def stopped(self):
|
|
return self._stop_event.is_set()
|
|
|
|
ARGS_PUSH = "rsync -azP --info=progress2 --exclude '*.blend1' --exclude 'README.md' {} {}"
|
|
ARGS_PULL = "rsync -azP --info=progress2 --exclude '*.blend' {} {}"
|
|
ARGS_BLEND = "ssh -t hawat 'blender -b {} -a'"
|
|
|
|
REGEX_PUSHPULL_STATUS = "\d+%"
|
|
|
|
def handleLinePush(self, line, current_file=None):
|
|
if line == 'end':
|
|
self._queue.put(("push", "end",))
|
|
return
|
|
t = re.search(self.REGEX_PUSHPULL_STATUS, line)
|
|
if t is not None:
|
|
self._queue.put(("push", t[0],))
|
|
|
|
def handleLinePull(self, line, current_file):
|
|
if line == 'end':
|
|
self._queue.put(("pull", "end", current_file,))
|
|
return
|
|
t = re.search(self.REGEX_PUSHPULL_STATUS, line)
|
|
if t is not None:
|
|
self._queue.put(("pull", t[0], current_file,))
|
|
|
|
def run(self):
|
|
self.start_process_handle_queue(self.ARGS_PUSH.format(self.localFolder, self.remotePath), self.handleLinePush)
|
|
|
|
for blendFile in self.blendFileList:
|
|
if blendFile.blending_enabled:
|
|
remoteBlendFilePath = blendFile.path.replace(self.localFolder, self.remoteFolder)
|
|
self.start_process_handle_queue(self.ARGS_BLEND.format(remoteBlendFilePath), self.handleLineBlend, blendFile)
|
|
|
|
pullWorker = Thread(
|
|
target=self.start_process_handle_queue,
|
|
args=(self.ARGS_PULL.format(self.remotePath, self.localFolder) , self.handleLinePull, blendFile)
|
|
)
|
|
pullWorker.start()
|
|
|
|
def start_process_handle_queue(self, args, lineHandler, current_file = None):
|
|
if self.stopped():
|
|
return
|
|
print(args)
|
|
p = Popen(args, shell=True, stdout=PIPE, stderr=STDOUT, universal_newlines=True, bufsize=1)
|
|
last_line = ''
|
|
while not self.stopped():
|
|
line = p.stdout.readline()
|
|
if line == '':
|
|
if p.poll() is not None:
|
|
break
|
|
elif line != last_line:
|
|
lineHandler(line, current_file)
|
|
last_line = line
|
|
|
|
if self.stopped():
|
|
p.kill()
|
|
|
|
lineHandler('end', current_file)
|
|
|
|
|
|
class ProjectManagerPanel(Panel):
|
|
bl_idname = "OBJECT_PT_project_manager"
|
|
bl_label = "Project Management"
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = ""
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
layout.operator(ProjectManagerStartBlend.bl_idname)
|
|
|
|
configuration = context.window_manager.project_manager
|
|
layout.label(text="Configuration")
|
|
layout = self.layout
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(configuration, "project_folder")
|
|
row = layout.row(align=True)
|
|
row.prop(configuration, "remote_host")
|
|
row = layout.row(align=True)
|
|
row.prop(configuration, "remote_folder")
|
|
row = layout.row(align=True)
|
|
row.prop(configuration, "upload_status")
|
|
|
|
layout.operator(ProjectManagerRefreshBlenFileOperator.bl_idname)
|
|
fileList = configuration.blend_file_list
|
|
for blendFile in sorted(fileList.values(), key=lambda file: file.path):
|
|
row = layout.row()
|
|
row.prop(blendFile, "blending_enabled", text=bpy.path.relpath(blendFile.path))
|
|
row.prop(blendFile, "status", text="")
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_module(__name__)
|
|
bpy.types.WindowManager.project_manager = PointerProperty(type=ProjectManagerProperties)
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_module(__name__)
|
|
del bpy.types.WindowManager.project_manage
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register()
|