try: from .base_engine import * except ImportError: from base_engine import * import json import re import logging logger = logging.getLogger() class Blender(BaseRenderEngine): install_paths = ['/Applications/Blender.app/Contents/MacOS/Blender'] supported_extensions = ['.blend'] @classmethod def version(cls): version = None try: render_path = cls.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 @classmethod def get_output_formats(cls): format_string = cls.get_help().split('Format Options')[-1].split('Animation Playback Options')[0] formats = re.findall(r"'([A-Z_0-9]+)'", format_string) return formats @classmethod def run_python_expression(cls, project_path, python_expression, timeout=None): if os.path.exists(project_path): try: return subprocess.run([cls.renderer_path(), '-b', project_path, '--python-expr', python_expression], capture_output=True, timeout=timeout) except Exception as e: logger.warning(f"Error running python expression in blender: {e}") pass else: raise FileNotFoundError(f'Project file not found: {project_path}') @classmethod def run_python_script(cls, project_path, script_path, timeout=None): if os.path.exists(project_path) and os.path.exists(script_path): try: return subprocess.run([cls.renderer_path(), '-b', project_path, '--python', script_path], capture_output=True, timeout=timeout) except Exception as e: logger.warning(f"Error running python expression 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") @classmethod def get_scene_info(cls, project_path, timeout=10): scene_info = {} try: results = cls.run_python_script(project_path, os.path.join(os.path.dirname(os.path.realpath(__file__)), 'scripts', 'blender', 'get_file_info.py'), 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 @classmethod def pack_project_file(cls, 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}") results = cls.run_python_script(project_path, os.path.join(os.path.dirname(os.path.realpath(__file__)), 'scripts', 'blender', 'pack_project.py'), 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