# ##### 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 ##### if "bpy" in locals(): from importlib import reload utils = reload(utils) else: from blenderkit import utils import bpy import sys, threading, os import re from bpy.props import ( EnumProperty, ) bg_processes = [] class threadCom: # object passed to threads to read background process stdout info ''' Object to pass data between thread and ''' def __init__(self, eval_path_computing, eval_path_state, eval_path, process_type, proc, location=None, name=''): # self.obname=ob.name self.name = name self.eval_path_computing = eval_path_computing # property that gets written to. self.eval_path_state = eval_path_state # property that gets written to. self.eval_path = eval_path # property that gets written to. self.process_type = process_type self.outtext = '' self.proc = proc self.lasttext = '' self.message = '' # the message to be sent. self.progress = 0.0 self.location = location self.error = False self.log = '' def threadread(tcom): '''reads stdout of background process, done this way to have it non-blocking. this threads basically waits for a stdout line to come in, fills the data, dies.''' found = False while not found: inline = tcom.proc.stdout.readline() # print('readthread', time.time()) inline = str(inline) s = inline.find('progress{') if s > -1: e = inline.find('}') tcom.outtext = inline[s + 9:e] found = True if tcom.outtext.find('%') > -1: tcom.progress = float(re.findall('\d+\.\d+|\d+', tcom.outtext)[0]) return if s == -1: s = inline.find('Remaining') if s > -1: # e=inline.find('}') tcom.outtext = inline[s: s + 18] found = True return if len(inline) > 3: print(inline, len(inline)) # if inline.find('Error'): # tcom.error = True # tcom.outtext = inline[2:] def progress(text, n=None): '''function for reporting during the script, works for background operations in the header.''' # for i in range(n+1): # sys.stdout.flush() text = str(text) if n is None: n = '' else: n = ' ' + ' ' + str(int(n * 1000) / 1000) + '% ' spaces = ' ' * (len(text) + 55) sys.stdout.write('progress{%s%s}\n' % (text, n)) sys.stdout.flush() # @bpy.app.handlers.persistent def bg_update(): '''monitoring of background process''' text = '' s = bpy.context.scene global bg_processes if len(bg_processes) == 0: return 2 for p in bg_processes: # proc=p[1].proc readthread = p[0] tcom = p[1] if not readthread.is_alive(): readthread.join() # readthread. if tcom.error: estring = tcom.eval_path_computing + ' = False' exec(estring) tcom.lasttext = tcom.outtext if tcom.outtext != '': tcom.outtext = '' estring = tcom.eval_path_state + ' = tcom.lasttext' exec(estring) # print(tcom.lasttext) if 'finished successfully' in tcom.lasttext: bg_processes.remove(p) estring = tcom.eval_path_computing + ' = False' exec(estring) else: readthread = threading.Thread(target=threadread, args=([tcom]), daemon=True) readthread.start() p[0] = readthread # if len(bg_processes) == 0: # bpy.app.timers.unregister(bg_update) if len(bg_processes) > 0: return .3 return 1. process_types = ( ('UPLOAD', 'Upload', ''), ('THUMBNAILER', 'Thumbnailer', ''), ) process_sources = ( ('MODEL', 'Model', 'set of objects'), ('SCENE', 'Scene', 'set of scenes'), ('MATERIAL', 'Material', 'any .blend Material'), ('TEXTURE', 'Texture', 'a texture, or texture set'), ('BRUSH', 'Brush', 'brush, can be any type of blender brush'), ) class KillBgProcess(bpy.types.Operator): '''Remove processes in background''' bl_idname = "object.kill_bg_process" bl_label = "Kill Background Process" bl_options = {'REGISTER'} process_type: EnumProperty( name="Type", items=process_types, description="Type of process", default="UPLOAD", ) process_source: EnumProperty( name="Source", items=process_sources, description="Source of process", default="MODEL", ) def execute(self, context): s = bpy.context.scene cls = bpy.ops.object.convert.__class__ # first do the easy stuff...TODO all cases. props = utils.get_upload_props() if self.process_type == 'UPLOAD': props.uploading = False if self.process_type == 'THUMBNAILER': props.is_generating_thumbnail = False global blenderkit_bg_process # print('killing', self.process_source, self.process_type) # then go kill the process. this wasn't working for unsetting props and that was the reason for changing to the method above. processes = bg_processes for p in processes: tcom = p[1] # print(tcom.process_type, self.process_type) if tcom.process_type == self.process_type: source = eval(tcom.eval_path) print(source.bl_rna.name, self.process_source) print(source.name) kill = False if source.bl_rna.name == 'Object' and self.process_source == 'MODEL': if source.name == bpy.context.active_object.name: kill = True if source.bl_rna.name == 'Material' and self.process_source == 'MATERIAL': if source.name == bpy.context.active_object.active_material.name: kill = True if source.bl_rna.name == 'Brush' and self.process_source == 'BRUSH': brush = utils.get_active_brush() if brush is not None and source.name == brush.name: kill = True if kill: estring = tcom.eval_path_computing + ' = False' exec(estring) processes.remove(p) tcom.proc.kill() return {'FINISHED'} def add_bg_process(location=None, name=None, eval_path_computing='', eval_path_state='', eval_path='', process_type='', process=None): '''adds process for monitoring''' global bg_processes tcom = threadCom(eval_path_computing, eval_path_state, eval_path, process_type, process, location, name) readthread = threading.Thread(target=threadread, args=([tcom]), daemon=True) readthread.start() bg_processes.append([readthread, tcom]) # if not bpy.app.timers.is_registered(bg_update): # bpy.app.timers.register(bg_update, persistent=True) def register(): bpy.utils.register_class(KillBgProcess) bpy.app.timers.register(bg_update) def unregister(): bpy.utils.unregister_class(KillBgProcess) if bpy.app.timers.is_registered(bg_update): bpy.app.timers.unregister(bg_update)