Files
Zordon/src/engines/blender/blender_engine.py
Brett dcc0504d3c Engine and downloader refactoring (#50)
* Make downloaders subclass of base_downloader.py

* Link engines and downloaders together for all engines

* Replace / merge worker_factory.py with engine_manager.py
2023-10-29 20:57:26 -05:00

156 lines
6.8 KiB
Python

import json
import re
from src.engines.core.base_engine import *
from src.utilities.misc_helper import system_safe_path
logger = logging.getLogger()
class Blender(BaseRenderEngine):
install_paths = ['/Applications/Blender.app/Contents/MacOS/Blender']
supported_extensions = ['.blend']
binary_names = {'linux': 'blender', 'windows': 'blender.exe', 'macos': 'Blender'}
@staticmethod
def downloader():
from src.engines.blender.blender_downloader import BlenderDownloader
return BlenderDownloader
@staticmethod
def worker_class():
from src.engines.blender.blender_worker import BlenderRenderWorker
return BlenderRenderWorker
def version(self):
version = None
try:
render_path = self.renderer_path()
if render_path:
ver_out = subprocess.check_output([render_path, '-v'], timeout=SUBPROCESS_TIMEOUT)
version = ver_out.decode('utf-8').splitlines()[0].replace('Blender', '').strip()
except Exception as e:
logger.error(f'Failed to get Blender version: {e}')
return version
def get_output_formats(self):
format_string = self.get_help().split('Format Options')[-1].split('Animation Playback Options')[0]
formats = re.findall(r"'([A-Z_0-9]+)'", format_string)
return formats
def run_python_expression(self, project_path, python_expression, timeout=None):
if os.path.exists(project_path):
try:
return subprocess.run([self.renderer_path(), '-b', project_path, '--python-expr', python_expression],
capture_output=True, timeout=timeout)
except Exception as e:
logger.error(f"Error running python expression in blender: {e}")
else:
raise FileNotFoundError(f'Project file not found: {project_path}')
def run_python_script(self, project_path, script_path, timeout=None):
if os.path.exists(project_path) and os.path.exists(script_path):
try:
return subprocess.run([self.renderer_path(), '-b', project_path, '--python', script_path],
capture_output=True, timeout=timeout)
except Exception as e:
logger.warning(f"Error running python script in blender: {e}")
pass
elif not os.path.exists(project_path):
raise FileNotFoundError(f'Project file not found: {project_path}')
elif not os.path.exists(script_path):
raise FileNotFoundError(f'Python script not found: {script_path}')
raise Exception("Uncaught exception")
def get_scene_info(self, project_path, timeout=10):
scene_info = {}
try:
script_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'scripts', 'get_file_info.py')
results = self.run_python_script(project_path, system_safe_path(script_path), timeout=timeout)
result_text = results.stdout.decode()
for line in result_text.splitlines():
if line.startswith('SCENE_DATA:'):
raw_data = line.split('SCENE_DATA:')[-1]
scene_info = json.loads(raw_data)
break
elif line.startswith('Error'):
logger.error(f"get_scene_info error: {line.strip()}")
except Exception as e:
logger.error(f'Error getting file details for .blend file: {e}')
return scene_info
def pack_project_file(self, project_path, timeout=30):
# Credit to L0Lock for pack script - https://blender.stackexchange.com/a/243935
try:
logger.info(f"Starting to pack Blender file: {project_path}")
script_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'scripts', 'pack_project.py')
results = self.run_python_script(project_path, system_safe_path(script_path), timeout=timeout)
result_text = results.stdout.decode()
dir_name = os.path.dirname(project_path)
# report any missing textures
not_found = re.findall("(Unable to pack file, source path .*)\n", result_text)
for err in not_found:
logger.error(err)
p = re.compile('Saved to: (.*)\n')
match = p.search(result_text)
if match:
new_path = os.path.join(dir_name, match.group(1).strip())
logger.info(f'Blender file packed successfully to {new_path}')
return new_path
except Exception as e:
logger.error(f'Error packing .blend file: {e}')
return None
def get_arguments(self):
help_text = subprocess.check_output([self.renderer_path(), '-h']).decode('utf-8')
lines = help_text.splitlines()
options = {}
current_category = None
current_option = None
for line in lines:
line = line.strip()
# Check if line starts with - or --, indicating a new option
if line.endswith('Options:'):
current_category = line.split('Options')[0].strip()
options[current_category] = {}
elif line.startswith("-") or line.startswith("/"):
parts = line.split(' or ')
flag = parts[-1] # Choose the verbose option
has_argument = '<' in flag and '>' in flag
flag = flag.split(' <')[0] # Remove argument placeholder
current_option = flag.strip("--") or flag
options[current_category][current_option] = {
'flag': flag, 'description': '', 'takes_argument': has_argument
}
elif line == "" and current_option is not None:
current_option = None
elif current_option is not None:
d = options[current_category][current_option]['description']
d = d + (' ' if d else '') + line
options[current_category][current_option]['description'] = d
return options
def get_detected_gpus(self):
engine_output = subprocess.run([self.renderer_path(), '-E', 'help'], timeout=SUBPROCESS_TIMEOUT,
capture_output=True).stdout.decode('utf-8')
gpu_names = re.findall(r"DETECTED GPU: (.+)", engine_output)
return gpu_names
def supported_render_engines(self):
engine_output = subprocess.run([self.renderer_path(), '-E', 'help'], timeout=SUBPROCESS_TIMEOUT,
capture_output=True).stdout.decode('utf-8').strip()
render_engines = [x.strip() for x in engine_output.split('Blender Engine Listing:')[-1].strip().splitlines()]
return render_engines
if __name__ == "__main__":
x = Blender.get_detected_gpus()
print(x)