================================ Asset Processor Tool - Developer Documentation ================================ This document provides a concise overview of the Asset Processor Tool's codebase for developers joining the project. It focuses on the architecture, key components, and development workflow. **NOTE:** This documentation strictly excludes details on environment setup, dependency installation, building the project, or deployment procedures. It assumes familiarity with Python and the relevant libraries (OpenCV, NumPy, PySide6). -------------------------------- 1. Project Overview -------------------------------- * **Purpose:** To process 3D asset source files (texture sets, models, etc., typically from ZIP archives or folders) into a standardized library format. * **Core Functionality:** Uses configurable JSON presets to interpret different asset sources, automating tasks like file classification, image resizing, channel merging, and metadata generation. * **High-Level Architecture:** Consists of a core processing engine (`AssetProcessor`), a configuration system handling presets (`Configuration`), multiple interfaces (GUI, CLI, Directory Monitor), and optional integration with Blender for automated material/nodegroup creation. -------------------------------- 2. Codebase Structure -------------------------------- Key files and directories: * `asset_processor.py`: Contains the `AssetProcessor` class, the core logic for processing a single asset through the pipeline. Includes methods for classification, map processing, merging, metadata generation, and output organization. Also provides methods for predicting output structure used by the GUI. * `configuration.py`: Defines the `Configuration` class. Responsible for loading core settings from `config.py` and merging them with a specified preset JSON file (`Presets/*.json`). Pre-compiles regex patterns from presets for efficiency. * `config.py`: Stores global default settings, constants, and core rules (e.g., standard map types, default resolutions, merge rules, output format rules, Blender paths). * `main.py`: Entry point for the Command-Line Interface (CLI). Handles argument parsing, logging setup, parallel processing orchestration (using `concurrent.futures.ProcessPoolExecutor`), calls `AssetProcessor` via a wrapper function, and optionally triggers Blender scripts. * `monitor.py`: Implements the automated directory monitoring feature using the `watchdog` library. Contains the `ZipHandler` class to detect new ZIP files and trigger processing via `main.run_processing`. * `gui/`: Directory containing all code related to the Graphical User Interface (GUI), built with PySide6. * `main_window.py`: Defines the `MainWindow` class, the main application window structure, UI layout (preset editor, processing panel, drag-and-drop, preview table, controls), event handling (button clicks, drag/drop), and menu setup. Manages GUI-specific logging (`QtLogHandler`). * `processing_handler.py`: Defines the `ProcessingHandler` class (runs on a `QThread`). Manages the execution of the main asset processing pipeline (using `ProcessPoolExecutor`) and Blender script execution in the background to keep the GUI responsive. Communicates progress and results back to the `MainWindow` via signals. * `prediction_handler.py`: Defines the `PredictionHandler` class (runs on a `QThread`). Manages background file analysis/preview generation by calling `AssetProcessor.get_detailed_file_predictions()`. Sends results back to the `MainWindow` via signals to update the preview table. * `preview_table_model.py`: Defines `PreviewTableModel` (inherits `QAbstractTableModel`) and `PreviewSortFilterProxyModel` for managing and displaying data in the GUI's preview table, including custom sorting logic. * `blenderscripts/`: Contains Python scripts (`create_nodegroups.py`, `create_materials.py`) designed to be executed *within* Blender, typically triggered by the main tool after processing to automate PBR nodegroup and material setup in `.blend` files. * `Presets/`: Contains supplier-specific configuration files in JSON format (e.g., `Poliigon.json`). These define rules for interpreting asset filenames, classifying maps, handling variants, etc. `_template.json` serves as a base for new presets. * `Testfiles/`: Contains example input assets for testing purposes. * `Tickets/`: Directory for issue and feature tracking using Markdown files. -------------------------------- 3. Key Components/Modules -------------------------------- * **`AssetProcessor` (`asset_processor.py`):** The heart of the tool. Orchestrates the entire processing pipeline for a single input asset (ZIP or folder). Responsibilities include workspace management, file classification, metadata extraction, map processing (resizing, format conversion), channel merging, `metadata.json` generation, and organizing final output files. * **`Configuration` (`configuration.py`):** Manages the loading and merging of configuration settings. Takes a preset name, loads defaults from `config.py`, loads the specified `Presets/*.json`, merges them, validates settings, and pre-compiles regex patterns defined in the preset for efficient use by `AssetProcessor`. * **`MainWindow` (`gui/main_window.py`):** The main class for the GUI application. Sets up the UI layout, connects user actions (button clicks, drag/drop) to slots, manages the preset editor, interacts with background handlers (`ProcessingHandler`, `PredictionHandler`) via signals/slots, and displays feedback (logs, progress, status). * **`ProcessingHandler` (`gui/processing_handler.py`):** Handles the execution of the core asset processing logic and Blender scripts in a background thread for the GUI. Manages the `ProcessPoolExecutor` for parallel asset processing and communicates progress/results back to the `MainWindow`. * **`PredictionHandler` (`gui/prediction_handler.py`):** Handles the generation of file classification previews in a background thread for the GUI. Calls `AssetProcessor`'s prediction methods and sends results back to the `MainWindow` to populate the preview table without blocking the UI. * **`ZipHandler` (`monitor.py`):** A `watchdog` event handler used by `monitor.py`. Detects newly created ZIP files in the monitored input directory, validates the filename format (for preset extraction), and triggers the main processing logic via `main.run_processing`. -------------------------------- 4. Core Concepts & Data Flow -------------------------------- * **Preset-Driven Configuration:** * Global defaults are set in `config.py`. * Supplier-specific rules (filename patterns, map keywords, variant handling, etc.) are defined using regex in `Presets/*.json` files. * The `Configuration` class loads `config.py` and merges it with the selected preset JSON, providing a unified configuration object to the `AssetProcessor`. Regex patterns are pre-compiled during `Configuration` initialization for performance. * **Asset Processing Pipeline (Simplified Flow):** 1. **Workspace Setup:** Create a temporary directory. 2. **Extract/Copy:** Extract ZIP or copy folder contents to the workspace. 3. **Classify Files:** Scan workspace, use compiled regex from `Configuration` to classify files (Map, Model, Extra, Ignored, Unrecognized). Handle 16-bit variants and assign suffixes based on rules. 4. **Determine Metadata:** Extract asset name, category, archetype based on preset rules. 5. **Skip Check:** If overwrite is false, check if output already exists; if so, skip this asset. 6. **Process Maps:** Load images, resize (no upscale), convert format/bit depth based on complex rules (`config.py` and preset), handle Gloss->Roughness inversion, calculate stats, determine aspect ratio change. Save processed maps. 7. **Merge Maps:** Combine channels from different processed maps based on `MAP_MERGE_RULES` in `config.py`. Save merged maps. 8. **Generate `metadata.json`:** Collect all relevant information (map details, stats, aspect ratio, category, etc.) and write to `metadata.json` in the workspace. 9. **Organize Output:** Create the final output directory structure (`///`) and move processed maps, merged maps, models, `metadata.json`, Extra files, and Ignored files into it. 10. **Cleanup Workspace:** Delete the temporary directory. 11. **(Optional) Blender Scripts:** If triggered via CLI/GUI, execute `blenderscripts/*.py` using the configured Blender executable via a subprocess. * **Parallel Processing:** * Multiple input assets are processed concurrently using `concurrent.futures.ProcessPoolExecutor`. * This pool is managed by `main.py` (CLI) or `gui/processing_handler.py` (GUI). * Each asset runs in an isolated worker process, ensuring separate `Configuration` and `AssetProcessor` instances. * **GUI Interaction & Threading:** * The GUI (`PySide6`) uses `QThread` to run `ProcessingHandler` (asset processing) and `PredictionHandler` (file preview generation) in the background, preventing the UI from freezing. * Communication between the main UI thread (`MainWindow`) and background threads relies on Qt's signals and slots mechanism for thread safety (e.g., updating progress, status messages, preview table data). * `PredictionHandler` calls `AssetProcessor` methods to get file classification details, which are then sent back to `MainWindow` to populate the `PreviewTableModel`. * **Output (`metadata.json`):** * A key output file generated for each processed asset. * Contains structured data about the asset: map filenames, resolutions, formats, bit depths, merged map details, calculated image statistics, aspect ratio change info, asset category/archetype, source preset used, list of ignored source files, etc. This file is intended for use by downstream tools or scripts (like the Blender integration scripts). -------------------------------- 5. Development Workflow -------------------------------- * **Modifying Core Processing Logic:** Changes to how assets are classified, maps are processed/resized/converted, channels are merged, or metadata is generated typically involve editing the `AssetProcessor` class in `asset_processor.py`. * **Changing Global Settings/Rules:** Adjustments to default output paths, standard resolutions, default format rules, map merge definitions, or Blender paths should be made in `config.py`. * **Adding/Modifying Supplier Rules:** To add support for a new asset source or change how an existing one is interpreted, create or edit the corresponding JSON file in the `Presets/` directory. Refer to `_template.json` and existing presets for structure. Focus on defining accurate regex patterns in `map_type_mapping`, `bit_depth_variants`, `model_patterns`, `source_naming_convention`, etc. * **Adjusting CLI Behavior:** Changes to command-line arguments, argument parsing, or the overall CLI workflow are handled in `main.py`. * **Modifying the GUI:** UI layout changes, adding new controls, altering event handling, or modifying background task management for the GUI involves working within the `gui/` directory, primarily `main_window.py`, `processing_handler.py`, and `prediction_handler.py`. UI elements are built using PySide6 widgets. * **Enhancing Blender Integration:** Improvements or changes to how nodegroups or materials are created in Blender require editing the Python scripts within the `blenderscripts/` directory. Consider how these scripts are invoked and what data they expect (primarily from `metadata.json` and command-line arguments passed via subprocess calls in `main.py` or `gui/processing_handler.py`). -------------------------------- 6. Coding Conventions -------------------------------- * **Object-Oriented:** The codebase heavily utilizes classes (e.g., `AssetProcessor`, `Configuration`, `MainWindow`, various Handlers). * **Type Hinting:** Python type hints are used throughout the code for clarity and static analysis. * **Logging:** Standard Python `logging` module is used for logging messages at different levels (DEBUG, INFO, WARNING, ERROR). The GUI uses a custom `QtLogHandler` to display logs in the UI console. * **Error Handling:** Uses standard `try...except` blocks and defines some custom exceptions (e.g., `ConfigurationError`, `AssetProcessingError`). * **Parallelism:** Uses `concurrent.futures.ProcessPoolExecutor` for CPU-bound tasks (asset processing). * **GUI:** Uses `PySide6` (Qt for Python) with signals and slots for communication between UI elements and background threads (`QThread`). * **Configuration:** Relies on Python modules (`config.py`) for core settings and JSON files (`Presets/`) for specific rule sets. * **File Paths:** Uses `pathlib.Path` for handling file system paths. ================================ Internal Details for Debugging ================================ This section provides deeper technical details about the internal workings, intended to aid in debugging unexpected behavior. -------------------------------- 7. Internal Logic & Algorithms -------------------------------- * **Configuration Preparation (`Configuration` class in `configuration.py`):** * Instantiated per preset (`__init__`). * Loads core settings from `config.py` using `importlib.util`. * Loads specified preset from `presets/{preset_name}.json`. * Validates basic structure of loaded settings (`_validate_configs`), checking for required keys and basic types (e.g., `map_type_mapping` is a list of dicts). * Compiles regex patterns (`_compile_regex_patterns`) from preset rules (extra, model, bit depth, map keywords) using `re.compile` (mostly case-insensitive) and stores them on the instance (e.g., `self.compiled_map_keyword_regex`). Uses `_fnmatch_to_regex` helper for basic wildcard conversion. * **CLI Argument Parsing (`main.py:setup_arg_parser`):** * Uses `argparse` to define and parse command-line arguments. * Key arguments influencing flow: `--preset` (required), `--output-dir` (optional override), `--workers` (concurrency), `--overwrite` (force reprocessing), `--verbose` (logging level), `--nodegroup-blend`, `--materials-blend`. * Calculates a default worker count based on `os.cpu_count()`. * **Output Directory Resolution (`main.py:main`):** * Determines the base output directory by checking `--output-dir` argument first, then falling back to `OUTPUT_BASE_DIR` from `config.py`. * Resolves the path to an absolute path and ensures the directory exists (`Path.resolve()`, `Path.mkdir(parents=True, exist_ok=True)`). * **Asset Processing (`AssetProcessor` class in `asset_processor.py`):** * **Classification (`_inventory_and_classify_files`):** * Multi-pass approach: Explicit Extra (regex) -> Models (regex) -> Potential Maps (keyword regex) -> Standalone 16-bit check (regex) -> Prioritize 16-bit variants -> Final Maps -> Remaining as Unrecognised (Extra). * Uses compiled regex patterns provided by the `Configuration` object passed during initialization. * Sorts potential map variants based on: 1. Preset rule index, 2. Keyword index within rule, 3. Alphabetical path. Suffixes (`-1`, `-2`) are assigned later per-asset based on this sort order and `RESPECT_VARIANT_MAP_TYPES`. * **Map Processing (`_process_maps`):** * Loads images using `cv2.imread` (flags: `IMREAD_UNCHANGED` or `IMREAD_GRAYSCALE`). Converts loaded 3-channel images from BGR to RGB for internal consistency (stats, merging). * **Saving Channel Order:** Before saving with `cv2.imwrite`, 3-channel images are conditionally converted back from RGB to BGR *only* if the target output format is *not* EXR (e.g., for PNG, JPG, TIF). This ensures correct channel order for standard formats while preserving RGB for EXR. (Fix for ISSUE-010). * Handles Gloss->Roughness inversion: Loads gloss, inverts using float math (`1.0 - img/norm`), stores as float32 with original dtype. Prioritizes gloss source if both gloss and native rough exist. * Resizes using `cv2.resize` (interpolation: `INTER_LANCZOS4` for downscale, `INTER_CUBIC` for potential same-size/upscale - though upscaling is generally avoided by checks). * Determines output format based on hierarchy: `FORCE_LOSSLESS_MAP_TYPES` > `RESOLUTION_THRESHOLD_FOR_JPG` > Input format priority (TIF/EXR often lead to lossless) > Configured defaults (`OUTPUT_FORMAT_16BIT_PRIMARY`, `OUTPUT_FORMAT_8BIT`). * Determines output bit depth based on `MAP_BIT_DEPTH_RULES` ('respect' vs 'force_8bit'). * Converts dtype before saving (e.g., float to uint8/uint16 using scaling factors 255.0/65535.0). * Calculates stats (`_calculate_image_stats`) on normalized float64 data (in RGB space) for a specific resolution (`CALCULATE_STATS_RESOLUTION`). * Calculates aspect ratio string (`_normalize_aspect_ratio_change`) based on relative dimension changes. * Handles save fallback: If primary 16-bit format (e.g., EXR) fails, attempts fallback (e.g., PNG). * **Merging (`_merge_maps_from_source`):** * Identifies the required *source* files for merge inputs based on classified files. * Determines common resolutions based on available processed maps (as a proxy for size compatibility). * Loads required source maps for each common resolution using the `_load_and_transform_source` helper (utilizing the cache). * Converts loaded inputs to float32 (normalized 0-1). * Injects default values (from rule `defaults`) for missing channels. * Merges channels using `cv2.merge`. * Determines output bit depth based on rule (`force_16bit`, `respect_inputs`). * Determines output format based on complex rules (`config.py` and preset), considering the highest format among *source* inputs if not forced lossless or over JPG threshold. Handles JPG 16-bit conflict by forcing 8-bit. * Saves the merged image using the `_save_image` helper, including final data type/color space conversions and fallback logic (e.g., EXR->PNG). * **Metadata (`_determine_base_metadata`, `_determine_single_asset_metadata`, `_generate_metadata_file`):** * Base name determined using `source_naming` separator/index from `Configuration`, with fallback to common prefix or input name. Handles multiple assets within one input. * Category determined by model presence or `decal_keywords` from `Configuration`. * Archetype determined by matching keywords in `archetype_rules` (from `Configuration`) against file stems/base name. * Final `metadata.json` populated by accumulating results (map details, stats, features, etc.) during the per-asset processing loop. * **Blender Integration (`main.py:run_blender_script`, `gui/processing_handler.py:_run_blender_script_subprocess`):** * Uses `subprocess.run` to execute Blender. * Command includes `-b` (background), the target `.blend` file, `--python` followed by the script path (`blenderscripts/*.py`), and `--` separator. * Arguments after `--` (currently just the `asset_root_dir`, and optionally the nodegroup blend path for the materials script) are passed to the Python script via `sys.argv`. * Uses `--factory-startup` in GUI handler. Checks return code and logs stdout/stderr. -------------------------------- 8. State Management -------------------------------- * **`Configuration` Object:** Holds the loaded and merged configuration state (core + preset) and compiled regex patterns. Designed to be immutable after initialization. Instantiated once per worker process. * **`AssetProcessor` Instance:** Primarily stateless between calls to `process()`. State *within* a `process()` call is managed through local variables scoped to the overall call or the per-asset loop (e.g., `current_asset_metadata`, `processed_maps_details_asset`). `self.classified_files` is populated once by `_inventory_and_classify_files` early in `process()` and then used read-only (filtered copies) within the per-asset loop. * **`main.py` (CLI):** Tracks overall run progress (processed, skipped, failed counts) based on results returned from worker processes. * **`gui/processing_handler.py`:** Manages the state of a GUI processing run using internal flags (`_is_running`, `_cancel_requested`) and stores `Future` objects in `self._futures` dictionary while the pool is active. -------------------------------- 9. Error Handling & Propagation -------------------------------- * **Custom Exceptions:** `ConfigurationError` (raised by `Configuration` on load/validation failure), `AssetProcessingError` (raised by `AssetProcessor` for various processing failures). * **Configuration:** `ConfigurationError` halts initialization. Regex compilation errors are logged as warnings but do not stop initialization. * **AssetProcessor:** Uses `try...except Exception` within key pipeline steps (`_process_maps`, `_merge_maps`, etc.) and within the per-asset loop in `process()`. Errors specific to one asset are logged (`log.error(exc_info=True)`), the asset is marked "failed" in the returned status dictionary, and the loop continues to the next asset. Critical setup errors (e.g., workspace creation) raise `AssetProcessingError`, halting the entire `process()` call. Includes specific save fallback logic (EXR->PNG) on `cv2.imwrite` failure for 16-bit formats. * **Worker Wrapper (`main.py:process_single_asset_wrapper`):** Catches `ConfigurationError`, `AssetProcessingError`, and general `Exception` during worker execution. Logs the error and returns a ("failed", error_message) status tuple to the main process. * **Process Pool (`main.py`, `gui/processing_handler.py`):** The `with ProcessPoolExecutor(...)` block handles pool setup/teardown. A `try...except` around `as_completed` or `future.result()` catches critical worker failures (e.g., process crash). * **GUI Communication (`ProcessingHandler`):** Catches exceptions during `future.result()` retrieval. Emits `file_status_updated` signal with "failed" status and error message. Emits `processing_finished` with final counts. * **Blender Scripts:** Checks `subprocess.run` return code. Logs stderr as ERROR if return code is non-zero, otherwise as WARNING. Catches `FileNotFoundError` if the Blender executable path is invalid. -------------------------------- 10. Key Data Structures -------------------------------- * **`Configuration` Instance Attributes:** * `compiled_map_keyword_regex`: `dict[str, list[tuple[re.Pattern, str, int]]]` (Base type -> list of compiled regex tuples) * `compiled_extra_regex`, `compiled_model_regex`: `list[re.Pattern]` * `compiled_bit_depth_regex_map`: `dict[str, re.Pattern]` (Base type -> compiled regex) * **`AssetProcessor` Internal Structures (within `process()`):** * `self.classified_files`: `dict[str, list[dict]]` (Category -> list of file info dicts like `{'source_path': Path, 'map_type': str, ...}`) * `processed_maps_details_asset`, `merged_maps_details_asset`: `dict[str, dict[str, dict]]` (Map Type -> Resolution Key -> Details Dict `{'path': Path, 'width': int, ...}`) * `file_to_base_name_map`: `dict[Path, Optional[str]]` (Source relative path -> Determined asset base name or None) * `current_asset_metadata`: `dict` (Accumulates name, category, archetype, stats, map details per asset) * **Return Values:** * `AssetProcessor.process()`: `Dict[str, List[str]]` (e.g., `{"processed": [...], "skipped": [...], "failed": [...]}`) * `main.process_single_asset_wrapper()`: `Tuple[str, str, Optional[str]]` (input_path, status_string, error_message) * **`ProcessingHandler._futures`:** `dict[Future, str]` (Maps `concurrent.futures.Future` object to the input path string) * **Image Data:** `numpy.ndarray` (Handled by OpenCV). -------------------------------- 11. Concurrency Models (CLI & GUI) -------------------------------- * **Common Core:** Both CLI and GUI utilize `concurrent.futures.ProcessPoolExecutor` for parallel processing. The target function executed by workers is `main.process_single_asset_wrapper`. * **Isolation:** Crucially, `Configuration` and `AssetProcessor` objects are instantiated *within* the `process_single_asset_wrapper` function, meaning each worker process gets its own independent configuration and processor instance based on the arguments passed. This prevents state conflicts between concurrent asset processing tasks. Data is passed between the main process and workers via pickling of arguments and return values. * **CLI Orchestration (`main.py:run_processing`):** * Creates the `ProcessPoolExecutor`. * Submits all `process_single_asset_wrapper` tasks. * Uses `concurrent.futures.as_completed` to iterate over finished futures as they complete, blocking until the next one is done. * Gathers results synchronously within the main script's execution flow. * **GUI Orchestration (`gui/processing_handler.py`):** * The `ProcessingHandler` object (a `QObject`) contains the `run_processing` method. * This method is intended to be run in a separate `QThread` (managed by `MainWindow`) to avoid blocking the main UI thread. * Inside `run_processing`, it creates and manages the `ProcessPoolExecutor`. * It uses `as_completed` similarly to the CLI to iterate over finished futures. * **Communication:** Instead of blocking the thread gathering results, it emits Qt signals (`progress_updated`, `file_status_updated`, `processing_finished`) from within the `as_completed` loop. These signals are connected to slots in `MainWindow` (running on the main UI thread), allowing for thread-safe updates to the GUI (progress bar, table status, status bar messages). * **Cancellation (GUI - `gui/processing_handler.py:request_cancel`):** * Sets an internal `_cancel_requested` flag. * Attempts `executor.shutdown(wait=False)` which prevents new tasks from starting and may cancel pending ones (depending on Python version). * Manually iterates through stored `_futures` and calls `future.cancel()` on those not yet running or done. * **Limitation:** This does *not* forcefully terminate worker processes that are already executing the `process_single_asset_wrapper` function. Cancellation primarily affects pending tasks and the processing of results from already running tasks (they will be marked as failed/cancelled when their future completes). -------------------------------- 12. Resource Management -------------------------------- * **Configuration:** Preset JSON files are opened and closed using `with open(...)`. * **AssetProcessor:** * Temporary workspace directory created using `tempfile.mkdtemp()`. * Cleanup (`_cleanup_workspace`) uses `shutil.rmtree()` and is called within a `finally` block in the main `process()` method, ensuring cleanup attempt even if errors occur. * Metadata JSON file written using `with open(...)`. * Image data is loaded into memory using OpenCV/NumPy; memory usage depends on image size and number of concurrent workers. * **Process Pool:** The `ProcessPoolExecutor` manages the lifecycle of worker processes. Using it within a `with` statement (as done in `main.py` and `gui/processing_handler.py`) ensures proper shutdown and resource release for the pool itself. -------------------------------- 13. Known Limitations & Edge Cases -------------------------------- * **Configuration:** * Validation (`_validate_configs`) is primarily structural (key presence, basic types), not deeply logical (e.g., doesn't check if regex patterns are *sensible*). * Regex compilation errors in `_compile_regex_patterns` are logged as warnings but don't prevent `Configuration` initialization, potentially leading to unexpected classification later. * `_fnmatch_to_regex` helper only handles basic `*` and `?` wildcards. Complex fnmatch patterns might not translate correctly. * **AssetProcessor:** * Heavily reliant on correct filename patterns and rules defined in presets. Ambiguous or incorrect patterns lead to misclassification. * Potential for high memory usage when processing very large images, especially with many workers. * Error handling within `process()` is per-asset; a failure during map processing for one asset marks the whole asset as failed, without attempting other maps for that asset. No partial recovery within an asset. * Gloss->Roughness inversion assumes gloss map is single channel or convertible to grayscale. * `predict_output_structure` and `get_detailed_file_predictions` use simplified logic (e.g., assuming PNG output, highest resolution only) and may not perfectly match final output names/formats in all cases. * Filename sanitization (`_sanitize_filename`) is basic and might not cover all edge cases for all filesystems. * **CLI (`main.py`):** * Preset existence check (`{preset}.json`) happens only in the main process before workers start. * Blender executable finding logic relies on `config.py` path being valid or `blender` being in the system PATH. * **GUI Concurrency (`gui/processing_handler.py`):** * Cancellation (`request_cancel`) is not immediate for tasks already running in worker processes. It prevents new tasks and stops processing results from completed futures once the flag is checked. * **General:** * Limited input format support (ZIP archives, folders). Internal file formats limited by OpenCV (`cv2.imread`, `cv2.imwrite`). Optional `OpenEXR` package recommended for full EXR support. * Error messages propagated from workers might lack full context in some edge cases.