Preferences, Config.py Migration, Bug Fixes, and Documention updates
This commit is contained in:
parent
5c041e3774
commit
1ac23eb252
@ -6,13 +6,18 @@ This document explains how to configure the Asset Processor Tool and use presets
|
|||||||
|
|
||||||
The tool's core settings are now stored in `config/app_settings.json`. This JSON file contains the base configuration for the application.
|
The tool's core settings are now stored in `config/app_settings.json`. This JSON file contains the base configuration for the application.
|
||||||
|
|
||||||
The `configuration.py` module is responsible for loading the settings from `app_settings.json` and merging them with the rules from the selected preset file.
|
The `configuration.py` module is responsible for loading the settings from `app_settings.json` (including loading and saving the JSON content), merging them with the rules from the selected preset file, and providing the base configuration via the `load_base_config()` function. Note that the old `config.py` file has been deleted.
|
||||||
|
|
||||||
|
The `app_settings.json` file is structured into several key sections, including:
|
||||||
|
* `FILE_TYPE_DEFINITIONS`: Defines known file types (like different texture maps, models, etc.) and their properties. Each definition now includes a `"standard_type"` key for aliasing to a common type and a `"bit_depth_rule"` key specifying how to handle bit depth for this file type. The separate `MAP_BIT_DEPTH_RULES` section has been removed.
|
||||||
|
* `ASSET_TYPE_DEFINITIONS`: Defines known asset types (like Surface, Model, Decal) and their properties.
|
||||||
|
* `MAP_MERGE_RULES`: Defines how multiple input maps can be merged into a single output map (e.g., combining Normal and Roughness into one).
|
||||||
|
|
||||||
## GUI Configuration Editor
|
## GUI Configuration Editor
|
||||||
|
|
||||||
You can modify the `app_settings.json` file using the built-in GUI editor. Access it via the **Edit** -> **Preferences...** menu.
|
You can modify the `app_settings.json` file using the built-in GUI editor. Access it via the **Edit** -> **Preferences...** menu.
|
||||||
|
|
||||||
This editor allows you to view and change the core application settings. Note that any changes made through the GUI editor require an application restart to take effect.
|
This editor provides a tabbed interface (e.g., "General", "Output & Naming") to view and change the core application settings defined in `app_settings.json`. Settings in the editor directly correspond to the structure and values within the JSON file. Note that any changes made through the GUI editor require an application restart to take effect.
|
||||||
|
|
||||||
*(Ideally, a screenshot of the GUI Configuration Editor would be included here.)*
|
*(Ideally, a screenshot of the GUI Configuration Editor would be included here.)*
|
||||||
|
|
||||||
|
|||||||
@ -21,8 +21,8 @@ python -m gui.main_window
|
|||||||
* **Preset Selector:** Choose the preset to use for *processing* the current queue.
|
* **Preset Selector:** Choose the preset to use for *processing* the current queue.
|
||||||
* **Output Directory:** Set the output path (defaults to `config/app_settings.json`, use "Browse...")
|
* **Output Directory:** Set the output path (defaults to `config/app_settings.json`, use "Browse...")
|
||||||
* **Drag and Drop Area:** Add asset `.zip`, `.rar`, `.7z` files, or folders by dragging and dropping them here.
|
* **Drag and Drop Area:** Add asset `.zip`, `.rar`, `.7z` files, or folders by dragging and dropping them here.
|
||||||
* **Preview Table:** Shows queued assets. Initially, this area displays a message prompting you to select a preset. Once a preset is selected from the Preset List, the detailed file preview will load here. The mode of the preview depends on the "View" menu:
|
* **Preview Table:** Shows queued assets in a hierarchical view (Source -> Asset -> File). Initially, this area displays a message prompting you to select a preset. Once a preset is selected from the Preset List, the detailed file preview will load here. The mode of the preview depends on the "View" menu:
|
||||||
* **Detailed Preview (Default):** Lists all files, predicted status (`Mapped`, `Model`, `Extra`, `Unrecognised`, `Ignored`, `Error`), output name, etc., based on the selected *processing* preset. Text colors are applied to cells based on the status of the individual file they represent. Rows use alternating background colors per asset group for visual separation.
|
* **Detailed Preview (Default):** Lists all files, predicted status (`Mapped`, `Model`, `Extra`, `Unrecognised`, `Ignored`, `Error`), output name, etc., based on the selected *processing* preset. The columns displayed are: Name, Target Asset, Supplier, Asset Type, Item Type. The "Target Asset" column stretches to fill available space, while others resize to content. The previous "Status" and "Output Path" columns have been removed. Text colors are applied to cells based on the status of the individual file they represent. Rows use alternating background colors per asset group for visual separation.
|
||||||
* **Simple View (Preview Disabled):** Lists only top-level input asset paths.
|
* **Simple View (Preview Disabled):** Lists only top-level input asset paths.
|
||||||
* **Progress Bar:** Shows overall processing progress.
|
* **Progress Bar:** Shows overall processing progress.
|
||||||
* **Blender Post-Processing:** Checkbox to enable Blender scripts. If enabled, shows fields and browse buttons for target `.blend` files (defaults from `config/app_settings.json`).
|
* **Blender Post-Processing:** Checkbox to enable Blender scripts. If enabled, shows fields and browse buttons for target `.blend` files (defaults from `config/app_settings.json`).
|
||||||
@ -36,7 +36,7 @@ python -m gui.main_window
|
|||||||
|
|
||||||
## GUI Configuration Editor
|
## GUI Configuration Editor
|
||||||
|
|
||||||
Access the GUI Configuration Editor via the **Edit** -> **Preferences...** menu. This dialog allows you to directly edit the `config/app_settings.json` file, which contains the core application settings.
|
Access the GUI Configuration Editor via the **Edit** -> **Preferences...** menu. This dialog allows you to directly edit the `config/app_settings.json` file, which contains the core application settings. The editor uses a tabbed layout (e.g., "General", "Output & Naming") to organize settings.
|
||||||
|
|
||||||
Any changes made in the GUI Configuration Editor require you to restart the application for them to take effect.
|
Any changes made in the GUI Configuration Editor require you to restart the application for them to take effect.
|
||||||
|
|
||||||
|
|||||||
@ -27,16 +27,16 @@ This hierarchy allows for fine-grained control over processing parameters. The G
|
|||||||
|
|
||||||
## Core Components
|
## Core Components
|
||||||
|
|
||||||
* `config.py`: Defines core, global settings, constants, and centralized definitions for allowed asset and file types (`ASSET_TYPE_DEFINITIONS`, `FILE_TYPE_DEFINITIONS`), including metadata like colors and descriptions.
|
* `config/app_settings.json`: Defines core, global settings, constants, and centralized definitions for allowed asset and file types (`ASSET_TYPE_DEFINITIONS`, `FILE_TYPE_DEFINITIONS`), including metadata like colors and descriptions. This replaces the old `config.py` file.
|
||||||
* `config/suppliers.json`: A persistent JSON file storing known supplier names for GUI auto-completion.
|
* `config/suppliers.json`: A persistent JSON file storing known supplier names for GUI auto-completion.
|
||||||
* `Presets/*.json`: Supplier-specific JSON files defining rules for file interpretation and initial prediction.
|
* `Presets/*.json`: Supplier-specific JSON files defining rules for file interpretation and initial prediction.
|
||||||
* `configuration.py` (`Configuration` class): Loads `config.py` settings and merges them with a selected preset, pre-compiling regex patterns for efficiency. This static configuration is used by the processing engine.
|
* `configuration.py` (`Configuration` class): Loads `config/app_settings.json` settings and merges them with a selected preset, pre-compiling regex patterns for efficiency. This static configuration is used by the processing engine.
|
||||||
* `rule_structure.py`: Defines the `SourceRule`, `AssetRule`, and `FileRule` dataclasses used to represent the hierarchical processing rules.
|
* `rule_structure.py`: Defines the `SourceRule`, `AssetRule`, and `FileRule` dataclasses used to represent the hierarchical processing rules.
|
||||||
* `gui/`: Directory containing modules for the Graphical User Interface (GUI), built with PySide6. The GUI is responsible for generating and managing the `SourceRule` hierarchy via the Unified View, accumulating prediction results, and interacting with background handlers (`ProcessingHandler`, `PredictionHandler`).
|
* `gui/`: Directory containing modules for the Graphical User Interface (GUI), built with PySide6. The GUI is responsible for generating and managing the `SourceRule` hierarchy via the Unified View, accumulating prediction results, and interacting with background handlers (`ProcessingHandler`, `PredictionHandler`).
|
||||||
* `unified_view_model.py`: Implements the `QAbstractItemModel` for the Unified Hierarchical View, holding the `SourceRule` data, handling inline editing (including direct model restructuring for `target_asset_name_override`), and managing row coloring based on config definitions.
|
* `unified_view_model.py`: Implements the `QAbstractItemModel` for the Unified Hierarchical View, holding the `SourceRule` data, handling inline editing (including direct model restructuring for `target_asset_name_override`), and managing row coloring based on config definitions.
|
||||||
* `delegates.py`: Contains custom `QStyledItemDelegate` implementations for inline editing in the Unified View, including the new `SupplierSearchDelegate` for supplier name auto-completion and management.
|
* `delegates.py`: Contains custom `QStyledItemDelegate` implementations for inline editing in the Unified View, including the new `SupplierSearchDelegate` for supplier name auto-completion and management.
|
||||||
* `prediction_handler.py`: Generates the initial `SourceRule` hierarchy with predicted values for a single input source based on its files and the selected preset.
|
* `prediction_handler.py`: Generates the initial `SourceRule` hierarchy with predicted values for a single input source based on its files and the selected preset. It uses the `"standard_type"` from the configuration's `FILE_TYPE_DEFINITIONS` to populate `FileRule.standard_map_type` and implements a two-pass classification logic to handle and prioritize bit-depth variants (e.g., `_DISP16_` vs `_DISP_`).
|
||||||
* `processing_engine.py` (`ProcessingEngine` class): The new core component that executes the processing pipeline for a single `SourceRule` object using the static `Configuration`. It contains no internal prediction or fallback logic.
|
* `processing_engine.py` (`ProcessingEngine` class): The new core component that executes the processing pipeline for a single `SourceRule` object using the static `Configuration`. A new instance is created per task for state isolation. It contains no internal prediction or fallback logic. Supplier overrides from the GUI are correctly preserved and used by the engine for output path generation and metadata.
|
||||||
* `asset_processor.py` (`AssetProcessor` class): The older processing engine, kept for reference but not used in the main processing flow.
|
* `asset_processor.py` (`AssetProcessor` class): The older processing engine, kept for reference but not used in the main processing flow.
|
||||||
* `main.py`: The entry point for the Command-Line Interface (CLI). It handles argument parsing, logging, parallel processing orchestration, and triggering Blender scripts. It now orchestrates processing by passing `SourceRule` objects to the `ProcessingEngine`.
|
* `main.py`: The entry point for the Command-Line Interface (CLI). It handles argument parsing, logging, parallel processing orchestration, and triggering Blender scripts. It now orchestrates processing by passing `SourceRule` objects to the `ProcessingEngine`.
|
||||||
* `monitor.py`: Implements the directory monitoring feature using `watchdog`.
|
* `monitor.py`: Implements the directory monitoring feature using `watchdog`.
|
||||||
|
|||||||
@ -6,10 +6,10 @@ This document provides technical details about the configuration system and the
|
|||||||
|
|
||||||
The tool utilizes a two-tiered configuration system managed by the `configuration.py` module:
|
The tool utilizes a two-tiered configuration system managed by the `configuration.py` module:
|
||||||
|
|
||||||
1. **Application Settings (`config/app_settings.json`):** This JSON file defines the core global default settings, constants, and rules that apply generally across different asset sources. Examples include default output paths, standard image resolutions, map merge rules, output format rules, Blender executable paths, and default map types. It also centrally defines metadata for allowed asset and file types.
|
1. **Application Settings (`config/app_settings.json`):** This JSON file defines the core global default settings, constants, and rules that apply generally across different asset sources. Examples include default output paths, standard image resolutions, map merge rules, output format rules, Blender executable paths, and default map types. It also centrally defines metadata for allowed asset and file types. Key sections include `FILE_TYPE_DEFINITIONS`, `ASSET_TYPE_DEFINITIONS`, and `MAP_MERGE_RULES`.
|
||||||
2. **Preset Files (`Presets/*.json`):** These JSON files define supplier-specific rules and overrides. They contain patterns (often regular expressions) to interpret filenames, classify map types, handle variants, define naming conventions, and specify other source-specific behaviors.
|
2. **Preset Files (`Presets/*.json`):** These JSON files define supplier-specific rules and overrides. They contain patterns (often regular expressions) to interpret filenames, classify map types, handle variants, define naming conventions, and specify other source-specific behaviors.
|
||||||
|
|
||||||
The `configuration.py` module is responsible for loading the base settings from `config/app_settings.json` and then merging them with the rules from the selected preset file. Preset values generally override core settings where applicable.
|
The `configuration.py` module is responsible for loading the base settings from `config/app_settings.json` (including loading and saving the JSON content), merging them with the rules from the selected preset file, and providing the base configuration via the `load_base_config()` function. Preset values generally override core settings where applicable. Note that the old `config.py` file has been deleted.
|
||||||
|
|
||||||
## Supplier Management (`config/suppliers.json`)
|
## Supplier Management (`config/suppliers.json`)
|
||||||
|
|
||||||
@ -37,7 +37,8 @@ An instance of `Configuration` is created within each worker process (`main.proc
|
|||||||
The GUI includes a dedicated editor for modifying the `config/app_settings.json` file. This is implemented in `gui/config_editor_dialog.py`.
|
The GUI includes a dedicated editor for modifying the `config/app_settings.json` file. This is implemented in `gui/config_editor_dialog.py`.
|
||||||
|
|
||||||
* **Purpose:** Provides a user-friendly interface for viewing and editing the core application settings defined in `app_settings.json`.
|
* **Purpose:** Provides a user-friendly interface for viewing and editing the core application settings defined in `app_settings.json`.
|
||||||
* **Implementation:** The dialog loads the JSON content of `app_settings.json`, presents it in an editable format (likely using standard GUI widgets mapped to the JSON structure), and saves the changes back to the file.
|
* **Implementation:** The dialog loads the JSON content of `app_settings.json`, presents it in a tabbed layout ("General", "Output & Naming", etc.) using standard GUI widgets mapped to the JSON structure, and saves the changes back to the file. It supports editing basic fields, tables for definitions (`FILE_TYPE_DEFINITIONS`, `ASSET_TYPE_DEFINITIONS`), and a list/detail view for merge rules (`MAP_MERGE_RULES`). The definitions tables include dynamic color editing features.
|
||||||
|
* **Limitations:** Currently, editing complex fields like `IMAGE_RESOLUTIONS` or the full details of `MAP_MERGE_RULES` via the UI is not fully supported.
|
||||||
* **Note:** Changes made through the GUI editor are written directly to `config/app_settings.json` but require an application restart to be loaded and applied by the `Configuration` class.
|
* **Note:** Changes made through the GUI editor are written directly to `config/app_settings.json` but require an application restart to be loaded and applied by the `Configuration` class.
|
||||||
|
|
||||||
## Preset File Structure (`Presets/*.json`)
|
## Preset File Structure (`Presets/*.json`)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Developer Guide: Processing Pipeline
|
# Developer Guide: Processing Pipeline
|
||||||
|
|
||||||
This document details the step-by-step technical process executed by the `ProcessingEngine` class (`processing_engine.py`) when processing a single asset.
|
This document details the step-by-step technical process executed by the `ProcessingEngine` class (`processing_engine.py`) when processing a single asset. A new instance of `ProcessingEngine` is created for each processing task to ensure state isolation.
|
||||||
|
|
||||||
The `ProcessingEngine.process()` method orchestrates the following pipeline based *solely* on the provided `SourceRule` object and the static `Configuration` object passed during engine initialization. It contains no internal prediction, classification, or fallback logic. All necessary overrides and static configuration values are accessed directly from these inputs.
|
The `ProcessingEngine.process()` method orchestrates the following pipeline based *solely* on the provided `SourceRule` object and the static `Configuration` object passed during engine initialization. It contains no internal prediction, classification, or fallback logic. All necessary overrides and static configuration values are accessed directly from these inputs.
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ The pipeline steps are:
|
|||||||
* Sorts potential map variants based on the order provided in the `SourceRule` or static configuration.
|
* Sorts potential map variants based on the order provided in the `SourceRule` or static configuration.
|
||||||
|
|
||||||
4. **Base Metadata Determination (`_determine_base_metadata`, `_determine_single_asset_metadata`)**:
|
4. **Base Metadata Determination (`_determine_base_metadata`, `_determine_single_asset_metadata`)**:
|
||||||
* Determines the base asset name, category, and archetype using the explicit values provided in the input `SourceRule` object and the static configuration from the `Configuration` object. Overrides (like `supplier_identifier`, `asset_type`, and `asset_name_override`) are taken directly from the `SourceRule`.
|
* Determines the base asset name, category, and archetype using the explicit values provided in the input `SourceRule` object and the static configuration from the `Configuration` object. Overrides (like `supplier_identifier`, `asset_type`, and `asset_name_override`), including supplier overrides from the GUI, are taken directly from the `SourceRule`.
|
||||||
|
|
||||||
5. **Skip Check**:
|
5. **Skip Check**:
|
||||||
* If the `overwrite` flag (passed during initialization) is `False`, the tool checks if the final output directory for the determined asset name already exists and contains a `metadata.json` file.
|
* If the `overwrite` flag (passed during initialization) is `False`, the tool checks if the final output directory for the determined asset name already exists and contains a `metadata.json` file.
|
||||||
@ -57,7 +57,7 @@ The pipeline steps are:
|
|||||||
* Writes this collected data into the `metadata.json` file within the temporary workspace using `json.dump`.
|
* Writes this collected data into the `metadata.json` file within the temporary workspace using `json.dump`.
|
||||||
|
|
||||||
9. **Output Organization (`_organize_output_files`)**:
|
9. **Output Organization (`_organize_output_files`)**:
|
||||||
* Creates the final structured output directory: `<output_base_dir>/<supplier_name>/<asset_name>/`.
|
* Creates the final structured output directory: `<output_base_dir>/<supplier_name>/<asset_name>/`. The `supplier_name` used here is derived from the `SourceRule`, ensuring that supplier overrides from the GUI are respected in the output path.
|
||||||
* Creates subdirectories `Extra/`, `Unrecognised/`, and `Ignored/` within the asset directory.
|
* Creates subdirectories `Extra/`, `Unrecognised/`, and `Ignored/` within the asset directory.
|
||||||
* Moves the processed maps, merged maps, model files, `metadata.json`, and files classified as Extra, Unrecognised, or Ignored from the temporary workspace into their respective locations in the final output directory structure.
|
* Moves the processed maps, merged maps, model files, `metadata.json`, and files classified as Extra, Unrecognised, or Ignored from the temporary workspace into their respective locations in the final output directory structure.
|
||||||
|
|
||||||
|
|||||||
@ -40,9 +40,12 @@ The GUI includes an integrated preset editor panel. This allows users to interac
|
|||||||
|
|
||||||
## Unified Hierarchical View (`gui/unified_view_model.py`, `gui/delegates.py`)
|
## Unified Hierarchical View (`gui/unified_view_model.py`, `gui/delegates.py`)
|
||||||
|
|
||||||
The core of the GUI's rule editing interface is the Unified Hierarchical View, implemented using a `QTreeView` with a custom model and delegates.
|
## Unified Hierarchical View (`gui/unified_view_model.py`, `gui/delegates.py`, `gui/main_window.py`)
|
||||||
|
|
||||||
|
The core of the GUI's rule editing interface is the Unified Hierarchical View, implemented using a `QTreeView` with a custom model and delegates. This view is managed within the `MainWindow`.
|
||||||
|
|
||||||
* **`Unified View Model` (`gui/unified_view_model.py`):** This class implements a `QAbstractItemModel` to expose the structure of a list of `SourceRule` objects (Source -> Asset -> File) to the `QTreeView`. It holds the `SourceRule` data that is the single source of truth for the GUI's processing rules. It provides data and flags for display in multiple columns and supports inline editing of specific rule attributes (e.g., asset type, item type override, target asset name override) by interacting with delegates.
|
* **`Unified View Model` (`gui/unified_view_model.py`):** This class implements a `QAbstractItemModel` to expose the structure of a list of `SourceRule` objects (Source -> Asset -> File) to the `QTreeView`. It holds the `SourceRule` data that is the single source of truth for the GUI's processing rules. It provides data and flags for display in multiple columns and supports inline editing of specific rule attributes (e.g., asset type, item type override, target asset name override) by interacting with delegates.
|
||||||
|
* **Column Order and Resizing:** The view currently displays the following columns in order: Name, Target Asset, Supplier, Asset Type, Item Type. The "Target Asset" column is set to stretch to fill available space, while other columns resize to their contents. The previous "Status" and "Output Path" columns have been removed.
|
||||||
* **Direct Model Restructuring:** The `setData` method now includes logic to directly restructure the underlying `SourceRule` hierarchy when the `target_asset_name_override` field of a `FileRule` is edited. This involves moving the `FileRule` to a different `AssetRule` (creating a new one if necessary) and removing the old `AssetRule` if it becomes empty. This replaces the previous mechanism of re-running prediction after an edit.
|
* **Direct Model Restructuring:** The `setData` method now includes logic to directly restructure the underlying `SourceRule` hierarchy when the `target_asset_name_override` field of a `FileRule` is edited. This involves moving the `FileRule` to a different `AssetRule` (creating a new one if necessary) and removing the old `AssetRule` if it becomes empty. This replaces the previous mechanism of re-running prediction after an edit.
|
||||||
* **Row Coloring:** Row background colors are dynamically determined based on the `asset_type` (for `AssetRule`s) and `item_type` or `item_type_override` (for `FileRule`s), using the color metadata defined in the `ASSET_TYPE_DEFINITIONS` and `FILE_TYPE_DEFINITIONS` dictionaries sourced from the configuration loaded by `configuration.py` (which includes data from `config/app_settings.json`). `SourceRule` rows have a fixed color.
|
* **Row Coloring:** Row background colors are dynamically determined based on the `asset_type` (for `AssetRule`s) and `item_type` or `item_type_override` (for `FileRule`s), using the color metadata defined in the `ASSET_TYPE_DEFINITIONS` and `FILE_TYPE_DEFINITIONS` dictionaries sourced from the configuration loaded by `configuration.py` (which includes data from `config/app_settings.json`). `SourceRule` rows have a fixed color.
|
||||||
* **`Delegates` (`gui/delegates.py`):** This module contains custom `QStyledItemDelegate` implementations used by the `QTreeView` to provide inline editors for specific data types or rule attributes.
|
* **`Delegates` (`gui/delegates.py`):** This module contains custom `QStyledItemDelegate` implementations used by the `QTreeView` to provide inline editors for specific data types or rule attributes.
|
||||||
@ -84,7 +87,8 @@ The GUI provides a "Cancel" button to stop ongoing processing. The `ProcessingHa
|
|||||||
|
|
||||||
A dedicated dialog, implemented in `gui/config_editor_dialog.py`, provides a graphical interface for editing the core application settings stored in `config/app_settings.json`.
|
A dedicated dialog, implemented in `gui/config_editor_dialog.py`, provides a graphical interface for editing the core application settings stored in `config/app_settings.json`.
|
||||||
|
|
||||||
* **Functionality:** This dialog loads the current content of `config/app_settings.json`, presents it in an editable format (likely using standard Qt widgets), and allows the user to save modifications back to the file.
|
* **Functionality:** This dialog loads the current content of `config/app_settings.json` and presents it in a tabbed layout (e.g., "General", "Output & Naming") using standard GUI widgets mapped to the JSON structure. It supports editing basic fields, tables for definitions (`FILE_TYPE_DEFINITIONS`, `ASSET_TYPE_DEFINITIONS`), and a list/detail view for merge rules (`MAP_MERGE_RULES`). The definitions tables include dynamic color editing features.
|
||||||
|
* **Limitations:** Currently, editing complex fields like `IMAGE_RESOLUTIONS` or the full details of `MAP_MERGE_RULES` via the UI is not fully supported.
|
||||||
* **Integration:** The `MainWindow` is responsible for creating and displaying an instance of this dialog when the user selects the "Edit" -> "Preferences..." menu option.
|
* **Integration:** The `MainWindow` is responsible for creating and displaying an instance of this dialog when the user selects the "Edit" -> "Preferences..." menu option.
|
||||||
* **Persistence:** Changes saved via this editor are written directly to the `config/app_settings.json` file, ensuring they persist across application sessions. However, the `Configuration` class loads settings at application startup, so a restart is required for changes made in the editor to take effect in the application's processing logic.
|
* **Persistence:** Changes saved via this editor are written directly to the `config/app_settings.json` file, ensuring they persist across application sessions. However, the `Configuration` class loads settings at application startup, so a restart is required for changes made in the editor to take effect in the application's processing logic.
|
||||||
|
|
||||||
|
|||||||
52
ProjectNotes/PreferencesLayout.md
Normal file
52
ProjectNotes/PreferencesLayout.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
3. Tab Breakdown and Widget Specifications:
|
||||||
|
|
||||||
|
Tab 1: General
|
||||||
|
|
||||||
|
OUTPUT_BASE_DIR: QLineEdit + QPushButton (opens QFileDialog.getExistingDirectory). Label: "Output Base Directory".
|
||||||
|
EXTRA_FILES_SUBDIR: QLineEdit. Label: "Subdirectory for Extra Files".
|
||||||
|
METADATA_FILENAME: QLineEdit. Label: "Metadata Filename".
|
||||||
|
Tab 2: Output & Naming
|
||||||
|
|
||||||
|
TARGET_FILENAME_PATTERN: QLineEdit. Label: "Output Filename Pattern". (Tooltip explaining placeholders recommended).
|
||||||
|
STANDARD_MAP_TYPES: QListWidget + "Add"/"Remove" QPushButtons. Label: "Standard Map Types".
|
||||||
|
RESPECT_VARIANT_MAP_TYPES: QLineEdit. Label: "Map Types Respecting Variants (comma-separated)".
|
||||||
|
ASPECT_RATIO_DECIMALS: QSpinBox (Min: 0, Max: ~6). Label: "Aspect Ratio Precision (Decimals)".
|
||||||
|
Tab 3: Image Processing
|
||||||
|
|
||||||
|
IMAGE_RESOLUTIONS: QTableWidget (Columns: "Name", "Resolution (px)") + "Add Row"/"Remove Row" QPushButtons. Label: "Defined Image Resolutions".
|
||||||
|
CALCULATE_STATS_RESOLUTION: QComboBox (populated from IMAGE_RESOLUTIONS keys). Label: "Resolution for Stats Calculation".
|
||||||
|
PNG_COMPRESSION_LEVEL: QSpinBox (Range: 0-9). Label: "PNG Compression Level".
|
||||||
|
JPG_QUALITY: QSpinBox (Range: 1-100). Label: "JPG Quality".
|
||||||
|
RESOLUTION_THRESHOLD_FOR_JPG: QComboBox (populated from IMAGE_RESOLUTIONS keys + "Never"/"Always"). Label: "Use JPG Above Resolution".
|
||||||
|
OUTPUT_FORMAT_8BIT: QComboBox (Options: "png", "jpg"). Label: "Output Format (8-bit)".
|
||||||
|
OUTPUT_FORMAT_16BIT_PRIMARY: QComboBox (Options: "png", "exr", "tif"). Label: "Primary Output Format (16-bit+)".
|
||||||
|
OUTPUT_FORMAT_16BIT_FALLBACK: QComboBox (Options: "png", "exr", "tif"). Label: "Fallback Output Format (16-bit+)".
|
||||||
|
Tab 4: Definitions (Overall QVBoxLayout)
|
||||||
|
|
||||||
|
Top Widget: DEFAULT_ASSET_CATEGORY: QComboBox (populated dynamically from Asset Types table below). Label: "Default Asset Category".
|
||||||
|
Bottom Widget: Inner QTabWidget:
|
||||||
|
Inner Tab 1: Asset Types
|
||||||
|
ASSET_TYPE_DEFINITIONS: QTableWidget (Columns: "Type Name", "Description", "Color", "Examples (comma-sep.)") + "Add Row"/"Remove Row" QPushButtons.
|
||||||
|
"Color" cell: QPushButton opening QColorDialog, button background shows color. Use QStyledItemDelegate.
|
||||||
|
"Examples" cell: Editable QLineEdit.
|
||||||
|
Inner Tab 2: File Types
|
||||||
|
FILE_TYPE_DEFINITIONS: QTableWidget (Columns: "Type ID", "Description", "Color", "Examples (comma-sep.)", "Standard Type", "Bit Depth Rule") + "Add Row"/"Remove Row" QPushButtons.
|
||||||
|
"Color" cell: QPushButton opening QColorDialog. Use QStyledItemDelegate.
|
||||||
|
"Examples" cell: Editable QLineEdit.
|
||||||
|
"Standard Type" cell: QComboBox (populated from STANDARD_MAP_TYPES + empty option). Use QStyledItemDelegate.
|
||||||
|
"Bit Depth Rule" cell: QComboBox (Options: "respect", "force_8bit"). Use QStyledItemDelegate.
|
||||||
|
Tab 5: Map Merging
|
||||||
|
|
||||||
|
Layout: QHBoxLayout.
|
||||||
|
Left Side: QListWidget displaying output_map_type for each rule. "Add Rule"/"Remove Rule" QPushButtons below. Label: "Merge Rules".
|
||||||
|
Right Side: QStackedWidget or dynamically populated QWidget showing details for the selected rule.
|
||||||
|
Rule Detail Form:
|
||||||
|
output_map_type: QLineEdit. Label: "Output Map Type Name".
|
||||||
|
inputs: QTableWidget (Fixed Rows: R, G, B, A. Columns: "Channel", "Input Map Type"). Label: "Channel Inputs". "Input Map Type" cell: QComboBox (populated from STANDARD_MAP_TYPES).
|
||||||
|
defaults: QTableWidget (Fixed Rows: R, G, B, A. Columns: "Channel", "Default Value"). Label: "Channel Defaults (if input missing)". "Default Value" cell: QDoubleSpinBox (Range: 0.0 - 1.0).
|
||||||
|
output_bit_depth: QComboBox (Options: "respect_inputs", "force_8bit", "force_16bit"). Label: "Output Bit Depth".
|
||||||
|
Tab 6: Postprocess Scripts
|
||||||
|
|
||||||
|
DEFAULT_NODEGROUP_BLEND_PATH: QLineEdit + QPushButton (opens QFileDialog.getOpenFileName, filter: "*.blend"). Label: "Default Node Group Library (.blend)".
|
||||||
|
DEFAULT_MATERIALS_BLEND_PATH: QLineEdit + QPushButton (opens QFileDialog.getOpenFileName, filter: "*.blend"). Label: "Default Materials Library (.blend)".
|
||||||
|
BLENDER_EXECUTABLE_PATH: QLineEdit + QPushButton (opens QFileDialog.getOpenFileName). Label: "Blender Executable Path".
|
||||||
@ -2,7 +2,7 @@
|
|||||||
"ASSET_TYPE_DEFINITIONS": {
|
"ASSET_TYPE_DEFINITIONS": {
|
||||||
"Surface": {
|
"Surface": {
|
||||||
"description": "Standard PBR material set for a surface.",
|
"description": "Standard PBR material set for a surface.",
|
||||||
"color": "#87CEEB",
|
"color": "#1f3e5d",
|
||||||
"examples": [
|
"examples": [
|
||||||
"WoodFloor01",
|
"WoodFloor01",
|
||||||
"MetalPlate05"
|
"MetalPlate05"
|
||||||
@ -44,7 +44,7 @@
|
|||||||
"FILE_TYPE_DEFINITIONS": {
|
"FILE_TYPE_DEFINITIONS": {
|
||||||
"MAP_COL": {
|
"MAP_COL": {
|
||||||
"description": "Color/Albedo Map",
|
"description": "Color/Albedo Map",
|
||||||
"color": "#FFFFE0",
|
"color": "#3d3021",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_col.",
|
"_col.",
|
||||||
"_basecolor."
|
"_basecolor."
|
||||||
@ -54,7 +54,7 @@
|
|||||||
},
|
},
|
||||||
"MAP_NRM": {
|
"MAP_NRM": {
|
||||||
"description": "Normal Map",
|
"description": "Normal Map",
|
||||||
"color": "#E6E6FA",
|
"color": "#23263d",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_nrm.",
|
"_nrm.",
|
||||||
"_normal."
|
"_normal."
|
||||||
@ -64,7 +64,7 @@
|
|||||||
},
|
},
|
||||||
"MAP_METAL": {
|
"MAP_METAL": {
|
||||||
"description": "Metalness Map",
|
"description": "Metalness Map",
|
||||||
"color": "#C0C0C0",
|
"color": "#1f1f1f",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_metal.",
|
"_metal.",
|
||||||
"_met."
|
"_met."
|
||||||
@ -74,7 +74,7 @@
|
|||||||
},
|
},
|
||||||
"MAP_ROUGH": {
|
"MAP_ROUGH": {
|
||||||
"description": "Roughness Map",
|
"description": "Roughness Map",
|
||||||
"color": "#A0522D",
|
"color": "#3d1f11",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_rough.",
|
"_rough.",
|
||||||
"_rgh."
|
"_rgh."
|
||||||
@ -84,7 +84,7 @@
|
|||||||
},
|
},
|
||||||
"MAP_AO": {
|
"MAP_AO": {
|
||||||
"description": "Ambient Occlusion Map",
|
"description": "Ambient Occlusion Map",
|
||||||
"color": "#A9A9A9",
|
"color": "#3d3d3d",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_ao.",
|
"_ao.",
|
||||||
"_ambientocclusion."
|
"_ambientocclusion."
|
||||||
@ -94,7 +94,7 @@
|
|||||||
},
|
},
|
||||||
"MAP_DISP": {
|
"MAP_DISP": {
|
||||||
"description": "Displacement/Height Map",
|
"description": "Displacement/Height Map",
|
||||||
"color": "#FFB6C1",
|
"color": "#35343d",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_disp.",
|
"_disp.",
|
||||||
"_height."
|
"_height."
|
||||||
@ -104,7 +104,7 @@
|
|||||||
},
|
},
|
||||||
"MAP_REFL": {
|
"MAP_REFL": {
|
||||||
"description": "Reflection/Specular Map",
|
"description": "Reflection/Specular Map",
|
||||||
"color": "#E0FFFF",
|
"color": "#363d3d",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_refl.",
|
"_refl.",
|
||||||
"_specular."
|
"_specular."
|
||||||
@ -114,7 +114,7 @@
|
|||||||
},
|
},
|
||||||
"MAP_SSS": {
|
"MAP_SSS": {
|
||||||
"description": "Subsurface Scattering Map",
|
"description": "Subsurface Scattering Map",
|
||||||
"color": "#FFDAB9",
|
"color": "#3d342c",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_sss.",
|
"_sss.",
|
||||||
"_subsurface."
|
"_subsurface."
|
||||||
@ -124,7 +124,7 @@
|
|||||||
},
|
},
|
||||||
"MAP_FUZZ": {
|
"MAP_FUZZ": {
|
||||||
"description": "Fuzz/Sheen Map",
|
"description": "Fuzz/Sheen Map",
|
||||||
"color": "#FFA07A",
|
"color": "#3d261d",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_fuzz.",
|
"_fuzz.",
|
||||||
"_sheen."
|
"_sheen."
|
||||||
@ -134,7 +134,7 @@
|
|||||||
},
|
},
|
||||||
"MAP_IDMAP": {
|
"MAP_IDMAP": {
|
||||||
"description": "ID Map (for masking)",
|
"description": "ID Map (for masking)",
|
||||||
"color": "#F08080",
|
"color": "#3d2121",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_id.",
|
"_id.",
|
||||||
"_matid."
|
"_matid."
|
||||||
@ -144,7 +144,7 @@
|
|||||||
},
|
},
|
||||||
"MAP_MASK": {
|
"MAP_MASK": {
|
||||||
"description": "Generic Mask Map",
|
"description": "Generic Mask Map",
|
||||||
"color": "#FFFFFF",
|
"color": "#3d3d3d",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_mask."
|
"_mask."
|
||||||
],
|
],
|
||||||
@ -153,7 +153,7 @@
|
|||||||
},
|
},
|
||||||
"MAP_IMPERFECTION": {
|
"MAP_IMPERFECTION": {
|
||||||
"description": "Imperfection Map (scratches, dust)",
|
"description": "Imperfection Map (scratches, dust)",
|
||||||
"color": "#F0E68C",
|
"color": "#3d3a24",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_imp.",
|
"_imp.",
|
||||||
"_imperfection."
|
"_imperfection."
|
||||||
@ -163,30 +163,33 @@
|
|||||||
},
|
},
|
||||||
"MODEL": {
|
"MODEL": {
|
||||||
"description": "3D Model File",
|
"description": "3D Model File",
|
||||||
"color": "#FFA500",
|
"color": "#3d2700",
|
||||||
"examples": [
|
"examples": [
|
||||||
".fbx",
|
".fbx",
|
||||||
".obj"
|
".obj"
|
||||||
],
|
],
|
||||||
"bit_depth_rule": "respect"
|
"standard_type": "",
|
||||||
|
"bit_depth_rule": ""
|
||||||
},
|
},
|
||||||
"EXTRA": {
|
"EXTRA": {
|
||||||
"description": "Non-standard/Unclassified File",
|
"description": "Non-standard/Unclassified File",
|
||||||
"color": "#778899",
|
"color": "#2f363d",
|
||||||
"examples": [
|
"examples": [
|
||||||
".txt",
|
".txt",
|
||||||
".zip"
|
".zip"
|
||||||
],
|
],
|
||||||
"bit_depth_rule": "respect"
|
"standard_type": "",
|
||||||
|
"bit_depth_rule": ""
|
||||||
},
|
},
|
||||||
"FILE_IGNORE": {
|
"FILE_IGNORE": {
|
||||||
"description": "File to be ignored",
|
"description": "File to be ignored",
|
||||||
"color": "#2F4F4F",
|
"color": "#243d3d",
|
||||||
"examples": [
|
"examples": [
|
||||||
"Thumbs.db",
|
"Thumbs.db",
|
||||||
".DS_Store"
|
".DS_Store"
|
||||||
],
|
],
|
||||||
"bit_depth_rule": "respect"
|
"standard_type": "",
|
||||||
|
"bit_depth_rule": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TARGET_FILENAME_PATTERN": "{base_name}_{map_type}_{resolution}.{ext}",
|
"TARGET_FILENAME_PATTERN": "{base_name}_{map_type}_{resolution}.{ext}",
|
||||||
@ -203,7 +206,9 @@
|
|||||||
"IDMAP",
|
"IDMAP",
|
||||||
"MASK"
|
"MASK"
|
||||||
],
|
],
|
||||||
"RESPECT_VARIANT_MAP_TYPES": "COL",
|
"RESPECT_VARIANT_MAP_TYPES": [
|
||||||
|
"COL"
|
||||||
|
],
|
||||||
"EXTRA_FILES_SUBDIR": "Extra",
|
"EXTRA_FILES_SUBDIR": "Extra",
|
||||||
"OUTPUT_BASE_DIR": "../Asset_Processor_Output",
|
"OUTPUT_BASE_DIR": "../Asset_Processor_Output",
|
||||||
"METADATA_FILENAME": "metadata.json",
|
"METADATA_FILENAME": "metadata.json",
|
||||||
@ -220,7 +225,7 @@
|
|||||||
"1K": 1024
|
"1K": 1024
|
||||||
},
|
},
|
||||||
"ASPECT_RATIO_DECIMALS": 2,
|
"ASPECT_RATIO_DECIMALS": 2,
|
||||||
"OUTPUT_FORMAT_16BIT_PRIMARY": "png",
|
"OUTPUT_FORMAT_16BIT_PRIMARY": "exr",
|
||||||
"OUTPUT_FORMAT_16BIT_FALLBACK": "png",
|
"OUTPUT_FORMAT_16BIT_FALLBACK": "png",
|
||||||
"OUTPUT_FORMAT_8BIT": "png",
|
"OUTPUT_FORMAT_8BIT": "png",
|
||||||
"MAP_MERGE_RULES": [
|
"MAP_MERGE_RULES": [
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -425,13 +425,19 @@ class MainWindow(QMainWindow):
|
|||||||
self.unified_view.setItemDelegateForColumn(UnifiedViewModel.COL_ITEM_TYPE, comboBoxDelegate)
|
self.unified_view.setItemDelegateForColumn(UnifiedViewModel.COL_ITEM_TYPE, comboBoxDelegate)
|
||||||
|
|
||||||
# Configure View Appearance (optional, customize as needed)
|
# Configure View Appearance (optional, customize as needed)
|
||||||
|
self.unified_view.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) # Expand horizontally and vertically
|
||||||
self.unified_view.setAlternatingRowColors(True)
|
self.unified_view.setAlternatingRowColors(True)
|
||||||
self.unified_view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
self.unified_view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||||
self.unified_view.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked | QAbstractItemView.EditTrigger.SelectedClicked | QAbstractItemView.EditTrigger.EditKeyPressed)
|
self.unified_view.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked | QAbstractItemView.EditTrigger.SelectedClicked | QAbstractItemView.EditTrigger.EditKeyPressed)
|
||||||
self.unified_view.header().setStretchLastSection(False) # Adjust as needed
|
|
||||||
# Set the "Name" column (index 0) to resize to contents
|
# Configure Header Resize Modes based on new column order
|
||||||
self.unified_view.header().setSectionResizeMode(UnifiedViewModel.COL_NAME, QHeaderView.ResizeMode.ResizeToContents)
|
header = self.unified_view.header()
|
||||||
# self.unified_view.header().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) # Example: Resize others to contents
|
header.setStretchLastSection(False) # Don't stretch the last section by default
|
||||||
|
header.setSectionResizeMode(UnifiedViewModel.COL_NAME, QHeaderView.ResizeMode.ResizeToContents)
|
||||||
|
header.setSectionResizeMode(UnifiedViewModel.COL_TARGET_ASSET, QHeaderView.ResizeMode.Stretch) # Stretch Target Asset
|
||||||
|
header.setSectionResizeMode(UnifiedViewModel.COL_SUPPLIER, QHeaderView.ResizeMode.ResizeToContents)
|
||||||
|
header.setSectionResizeMode(UnifiedViewModel.COL_ASSET_TYPE, QHeaderView.ResizeMode.ResizeToContents)
|
||||||
|
header.setSectionResizeMode(UnifiedViewModel.COL_ITEM_TYPE, QHeaderView.ResizeMode.ResizeToContents)
|
||||||
|
|
||||||
# Add the Unified View to the main layout
|
# Add the Unified View to the main layout
|
||||||
main_layout.addWidget(self.unified_view, 1) # Give it stretch factor 1
|
main_layout.addWidget(self.unified_view, 1) # Give it stretch factor 1
|
||||||
@ -1613,80 +1619,129 @@ class MainWindow(QMainWindow):
|
|||||||
@Slot(list)
|
@Slot(list)
|
||||||
def _on_rule_hierarchy_ready(self, source_rules_list: list):
|
def _on_rule_hierarchy_ready(self, source_rules_list: list):
|
||||||
"""Receives prediction results (a list containing one SourceRule) for a single input path,
|
"""Receives prediction results (a list containing one SourceRule) for a single input path,
|
||||||
accumulates them, and updates the model when all are ready."""
|
finds the corresponding existing rule in the model, updates it while preserving overrides,
|
||||||
|
and emits dataChanged/layoutChanged signals."""
|
||||||
|
|
||||||
# --- Extract input_path from the received rule ---
|
# --- Extract input_path and the new rule from the received list ---
|
||||||
input_path = None
|
input_path = None
|
||||||
source_rule = None
|
new_source_rule = None
|
||||||
if source_rules_list and isinstance(source_rules_list[0], SourceRule):
|
if source_rules_list and isinstance(source_rules_list[0], SourceRule):
|
||||||
source_rule = source_rules_list[0]
|
new_source_rule = source_rules_list[0]
|
||||||
input_path = source_rule.input_path
|
input_path = new_source_rule.input_path
|
||||||
log.debug(f"--> Entered _on_rule_hierarchy_ready for '{input_path}' with {len(source_rules_list)} SourceRule(s)")
|
log.debug(f"--> Entered _on_rule_hierarchy_ready for '{input_path}' with 1 SourceRule")
|
||||||
elif source_rules_list:
|
elif source_rules_list:
|
||||||
log.error(f"Received non-SourceRule object in list: {type(source_rules_list[0])}. Cannot process.")
|
log.error(f"Received non-SourceRule object in list: {type(source_rules_list[0])}. Cannot process.")
|
||||||
# Attempt to find which pending prediction this might correspond to? Difficult.
|
|
||||||
# For now, we can't reliably remove from pending without the path.
|
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
# This case might happen if prediction failed critically before creating a rule.
|
|
||||||
# The prediction_finished signal (which now includes input_path) should handle removing from pending.
|
|
||||||
log.warning("Received empty source_rules_list in _on_rule_hierarchy_ready. Prediction likely failed.")
|
log.warning("Received empty source_rules_list in _on_rule_hierarchy_ready. Prediction likely failed.")
|
||||||
return # Nothing to accumulate
|
# Try to deduce input_path if possible (e.g., if only one is pending)
|
||||||
|
if len(self._pending_predictions) == 1:
|
||||||
|
input_path = list(self._pending_predictions)[0]
|
||||||
|
log.warning(f"Assuming failed prediction corresponds to pending path: {input_path}")
|
||||||
|
else:
|
||||||
|
log.error("Cannot determine input_path for empty/failed prediction result when multiple predictions are pending.")
|
||||||
|
return
|
||||||
|
|
||||||
if input_path is None:
|
if input_path is None:
|
||||||
log.error("Could not determine input_path from received source_rules_list. Aborting accumulation.")
|
log.error("Could not determine input_path from received source_rules_list or pending state.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Log received rule details (even if it's None due to failure)
|
||||||
|
log.debug(f"_on_rule_hierarchy_ready: Processing result for '{input_path}'. Received Supplier ID='{getattr(new_source_rule, 'supplier_identifier', 'N/A')}', Received Override='{getattr(new_source_rule, 'supplier_override', 'N/A')}'")
|
||||||
|
|
||||||
if input_path not in self._pending_predictions:
|
if input_path not in self._pending_predictions:
|
||||||
log.warning(f"Received rule hierarchy for '{input_path}', but it was not in the pending set. Ignoring stale result? Pending: {self._pending_predictions}")
|
log.warning(f"Received rule hierarchy for '{input_path}', but it was not in the pending set. Ignoring stale result? Pending: {self._pending_predictions}")
|
||||||
return # Ignore if not expected
|
return # Ignore if not expected
|
||||||
|
|
||||||
# --- Accumulate Result ---
|
# --- Find existing rule in the model's internal list ---
|
||||||
if source_rule: # Check if we successfully got the rule object
|
# Access the model directly
|
||||||
self._accumulated_rules[input_path] = source_rule
|
existing_rule = None
|
||||||
log.debug(f"Accumulated rule for '{input_path}'. Total accumulated: {len(self._accumulated_rules)}")
|
existing_rule_index = -1
|
||||||
else:
|
model_rules = self.unified_model.get_all_source_rules() # Get current rules from model
|
||||||
# This path is already handled by the initial checks, but log just in case.
|
for i, rule in enumerate(model_rules):
|
||||||
log.warning(f"No valid SourceRule found for '{input_path}' to accumulate.")
|
if rule.input_path == input_path:
|
||||||
|
existing_rule = rule
|
||||||
|
existing_rule_index = i
|
||||||
|
break
|
||||||
|
|
||||||
# --- Mark as Completed ---
|
if existing_rule:
|
||||||
|
log.debug(f"Found existing rule for '{input_path}' in model at index {existing_rule_index}. Updating it.")
|
||||||
|
if new_source_rule: # Only update if prediction was successful
|
||||||
|
# Preserve existing user overrides from the rule currently in the model
|
||||||
|
preserved_supplier_override = existing_rule.supplier_override
|
||||||
|
# Preserve other potential user overrides if they exist
|
||||||
|
preserved_asset_overrides = {asset.asset_name: asset.asset_type_override for asset in existing_rule.assets}
|
||||||
|
preserved_file_overrides = {(file.file_path, 'target'): file.target_asset_name_override for asset in existing_rule.assets for file in asset.files}
|
||||||
|
preserved_file_overrides.update({(file.file_path, 'item'): file.item_type_override for asset in existing_rule.assets for file in asset.files})
|
||||||
|
|
||||||
|
# --- Update existing rule with new prediction data ---
|
||||||
|
existing_rule.supplier_identifier = new_source_rule.supplier_identifier
|
||||||
|
existing_rule.preset_name = new_source_rule.preset_name
|
||||||
|
existing_rule.assets = new_source_rule.assets # Replace assets list
|
||||||
|
|
||||||
|
# Re-apply preserved overrides
|
||||||
|
existing_rule.supplier_override = preserved_supplier_override
|
||||||
|
for asset in existing_rule.assets:
|
||||||
|
asset.asset_type_override = preserved_asset_overrides.get(asset.asset_name)
|
||||||
|
asset.parent_source = existing_rule # Set parent reference
|
||||||
|
for file in asset.files:
|
||||||
|
file.target_asset_name_override = preserved_file_overrides.get((file.file_path, 'target'))
|
||||||
|
file.item_type_override = preserved_file_overrides.get((file.file_path, 'item'))
|
||||||
|
file.parent_asset = asset # Set parent reference
|
||||||
|
# --- End Update ---
|
||||||
|
|
||||||
|
# Emit dataChanged and layoutChanged for the updated existing rule via the model
|
||||||
|
start_index = self.unified_model.createIndex(existing_rule_index, 0, existing_rule)
|
||||||
|
end_index = self.unified_model.createIndex(existing_rule_index, self.unified_model.columnCount() - 1, existing_rule)
|
||||||
|
log.debug(f"Emitting dataChanged and layoutChanged for updated existing rule at index {existing_rule_index}")
|
||||||
|
self.unified_model.dataChanged.emit(start_index, end_index)
|
||||||
|
self.unified_model.layoutChanged.emit() # Signal layout change
|
||||||
|
else:
|
||||||
|
log.warning(f"Prediction failed for '{input_path}'. Not updating existing rule data in model.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# If no existing rule was found (e.g., first time result arrives)
|
||||||
|
if new_source_rule: # Only add if prediction was successful
|
||||||
|
log.debug(f"No existing rule found for '{input_path}'. Adding new rule to model.")
|
||||||
|
# Ensure parent references are set
|
||||||
|
for asset_rule in new_source_rule.assets:
|
||||||
|
asset_rule.parent_source = new_source_rule
|
||||||
|
for file_rule in asset_rule.files:
|
||||||
|
file_rule.parent_asset = asset_rule
|
||||||
|
# Add to model's internal list and emit signal via model methods
|
||||||
|
self.unified_model.beginInsertRows(QModelIndex(), len(model_rules), len(model_rules))
|
||||||
|
model_rules.append(new_source_rule) # Append to the list obtained from the model
|
||||||
|
self.unified_model.endInsertRows()
|
||||||
|
else:
|
||||||
|
log.warning(f"Prediction failed for '{input_path}' and no existing rule found. Nothing to add to model.")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Remove from pending ---
|
||||||
self._pending_predictions.discard(input_path)
|
self._pending_predictions.discard(input_path)
|
||||||
log.debug(f"Removed '{input_path}' from pending predictions. Remaining: {self._pending_predictions}")
|
log.debug(f"Removed '{input_path}' from pending predictions. Remaining: {len(self._pending_predictions)} -> {self._pending_predictions}")
|
||||||
|
|
||||||
# --- Check for Completion ---
|
# --- Check for Completion ---
|
||||||
if not self._pending_predictions:
|
if not self._pending_predictions:
|
||||||
log.info("All pending predictions received. Finalizing model update.")
|
log.info("All pending predictions processed. Model should be up-to-date.")
|
||||||
self._finalize_model_update()
|
self.statusBar().showMessage(f"Preview complete.", 5000) # Update status
|
||||||
|
# Optional: Resize columns after all updates are done
|
||||||
|
for col in range(self.unified_model.columnCount()):
|
||||||
|
self.unified_view.resizeColumnToContents(col)
|
||||||
|
self.unified_view.expandToDepth(1) # Expand Source -> Asset level
|
||||||
else:
|
else:
|
||||||
# Update status bar with progress
|
# Update status bar with progress
|
||||||
completed_count = len(self._accumulated_rules)
|
completed_count = len(self.unified_model.get_all_source_rules()) # Count rules in model
|
||||||
pending_count = len(self._pending_predictions)
|
pending_count = len(self._pending_predictions)
|
||||||
# total_count = completed_count + pending_count # This might be slightly off if some failed without rules
|
total_requested = completed_count + pending_count # Estimate total
|
||||||
# We don't have the total count of *requested* predictions here easily,
|
status_msg = f"Preview updated for {Path(input_path).name}. Waiting for {pending_count} more ({completed_count}/{total_requested} processed)..."
|
||||||
# but we can use the initial number of items added.
|
|
||||||
total_requested = len(self.current_asset_paths) # Use the total number of items added
|
|
||||||
status_msg = f"Preview finished for {Path(input_path).name}. Waiting for {pending_count} more ({completed_count}/{total_requested} requested)..."
|
|
||||||
self.statusBar().showMessage(status_msg, 5000)
|
self.statusBar().showMessage(status_msg, 5000)
|
||||||
log.debug(status_msg)
|
log.debug(status_msg)
|
||||||
|
|
||||||
|
|
||||||
def _finalize_model_update(self):
|
# REMOVED _finalize_model_update method as it's no longer needed
|
||||||
"""Combines accumulated rules and updates the UI model and view."""
|
# def _finalize_model_update(self):
|
||||||
log.debug("Entering _finalize_model_update")
|
# """Combines accumulated rules and updates the UI model and view."""
|
||||||
final_rules = list(self._accumulated_rules.values())
|
# ... (old code removed) ...
|
||||||
log.info(f"Finalizing model with {len(final_rules)} accumulated SourceRule(s).")
|
|
||||||
|
|
||||||
# Load the FINAL LIST of data into the UnifiedViewModel
|
|
||||||
self.unified_model.load_data(final_rules)
|
|
||||||
log.debug("Unified view model updated with final list of SourceRules.")
|
|
||||||
|
|
||||||
# Resize columns to fit content after loading data
|
|
||||||
for col in range(self.unified_model.columnCount()):
|
|
||||||
self.unified_view.resizeColumnToContents(col)
|
|
||||||
log.debug("Unified view columns resized to contents.")
|
|
||||||
self.unified_view.expandToDepth(1) # Expand Source -> Asset level
|
|
||||||
|
|
||||||
self.statusBar().showMessage(f"Preview complete for {len(final_rules)} asset(s).", 5000)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Main Execution ---
|
# --- Main Execution ---
|
||||||
|
|||||||
@ -18,14 +18,14 @@ class UnifiedViewModel(QAbstractItemModel):
|
|||||||
of SourceRule -> AssetRule -> FileRule.
|
of SourceRule -> AssetRule -> FileRule.
|
||||||
"""
|
"""
|
||||||
Columns = [
|
Columns = [
|
||||||
"Name", "Supplier", "Asset Type",
|
"Name", "Target Asset", "Supplier",
|
||||||
"Target Asset", "Item Type"
|
"Asset Type", "Item Type"
|
||||||
]
|
]
|
||||||
|
|
||||||
COL_NAME = 0
|
COL_NAME = 0
|
||||||
COL_SUPPLIER = 1
|
COL_TARGET_ASSET = 1
|
||||||
COL_ASSET_TYPE = 2
|
COL_SUPPLIER = 2
|
||||||
COL_TARGET_ASSET = 3
|
COL_ASSET_TYPE = 3
|
||||||
COL_ITEM_TYPE = 4
|
COL_ITEM_TYPE = 4
|
||||||
# COL_STATUS = 5 # Removed
|
# COL_STATUS = 5 # Removed
|
||||||
# COL_OUTPUT_PATH = 6 # Removed
|
# COL_OUTPUT_PATH = 6 # Removed
|
||||||
@ -242,7 +242,7 @@ class UnifiedViewModel(QAbstractItemModel):
|
|||||||
if isinstance(item, SourceRule):
|
if isinstance(item, SourceRule):
|
||||||
if role == Qt.DisplayRole or role == Qt.EditRole: # Combine Display and Edit logic
|
if role == Qt.DisplayRole or role == Qt.EditRole: # Combine Display and Edit logic
|
||||||
if column == self.COL_NAME:
|
if column == self.COL_NAME:
|
||||||
return Path(item.input_path).name # Display only basename for SourceRule
|
return Path(item.input_path).name
|
||||||
elif column == self.COL_SUPPLIER:
|
elif column == self.COL_SUPPLIER:
|
||||||
# Return override if set, otherwise the original identifier, else empty string
|
# Return override if set, otherwise the original identifier, else empty string
|
||||||
display_value = item.supplier_override if item.supplier_override is not None else item.supplier_identifier
|
display_value = item.supplier_override if item.supplier_override is not None else item.supplier_identifier
|
||||||
@ -253,21 +253,21 @@ class UnifiedViewModel(QAbstractItemModel):
|
|||||||
elif isinstance(item, AssetRule):
|
elif isinstance(item, AssetRule):
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
if column == self.COL_NAME: return item.asset_name
|
if column == self.COL_NAME: return item.asset_name
|
||||||
if column == self.COL_ASSET_TYPE:
|
elif column == self.COL_ASSET_TYPE:
|
||||||
display_value = item.asset_type_override if item.asset_type_override is not None else item.asset_type
|
display_value = item.asset_type_override if item.asset_type_override is not None else item.asset_type
|
||||||
return display_value if display_value else ""
|
return display_value if display_value else ""
|
||||||
# Removed Status and Output Path columns
|
# Removed Status and Output Path columns
|
||||||
elif role == Qt.EditRole:
|
elif role == Qt.EditRole:
|
||||||
if column == self.COL_ASSET_TYPE:
|
if column == self.COL_ASSET_TYPE:
|
||||||
return item.asset_type_override # Return string or None
|
return item.asset_type_override
|
||||||
return None # Default for AssetRule
|
return None # Default for AssetRule
|
||||||
|
|
||||||
elif isinstance(item, FileRule):
|
elif isinstance(item, FileRule):
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
if column == self.COL_NAME: return Path(item.file_path).name # Display only filename
|
if column == self.COL_NAME: return Path(item.file_path).name # Display only filename
|
||||||
if column == self.COL_TARGET_ASSET:
|
elif column == self.COL_TARGET_ASSET:
|
||||||
return item.target_asset_name_override if item.target_asset_name_override is not None else ""
|
return item.target_asset_name_override if item.target_asset_name_override is not None else ""
|
||||||
if column == self.COL_ITEM_TYPE:
|
elif column == self.COL_ITEM_TYPE:
|
||||||
# Reverted Logic: Display override if set, otherwise base type. Shows prefixed keys.
|
# Reverted Logic: Display override if set, otherwise base type. Shows prefixed keys.
|
||||||
override = item.item_type_override
|
override = item.item_type_override
|
||||||
initial_type = item.item_type
|
initial_type = item.item_type
|
||||||
@ -278,8 +278,8 @@ class UnifiedViewModel(QAbstractItemModel):
|
|||||||
return initial_type if initial_type else ""
|
return initial_type if initial_type else ""
|
||||||
# Removed Status and Output Path columns
|
# Removed Status and Output Path columns
|
||||||
elif role == Qt.EditRole:
|
elif role == Qt.EditRole:
|
||||||
if column == self.COL_TARGET_ASSET: return item.target_asset_name_override if item.target_asset_name_override is not None else ""
|
if column == self.COL_TARGET_ASSET: return item.target_asset_name_override if item.target_asset_name_override is not None else "" # Return string or ""
|
||||||
if column == self.COL_ITEM_TYPE: return item.item_type_override # Return string or None
|
elif column == self.COL_ITEM_TYPE: return item.item_type_override # Return string or None
|
||||||
return None # Default for FileRule
|
return None # Default for FileRule
|
||||||
|
|
||||||
return None # Default return if role/item combination not handled
|
return None # Default return if role/item combination not handled
|
||||||
@ -299,6 +299,7 @@ class UnifiedViewModel(QAbstractItemModel):
|
|||||||
if isinstance(item, SourceRule): # If SourceRule is editable
|
if isinstance(item, SourceRule): # If SourceRule is editable
|
||||||
if column == self.COL_SUPPLIER:
|
if column == self.COL_SUPPLIER:
|
||||||
# Get the new value, strip whitespace, treat empty as None
|
# Get the new value, strip whitespace, treat empty as None
|
||||||
|
log.debug(f"setData COL_SUPPLIER: Index=({index.row()},{column}), Value='{value}', Type={type(value)}") # <-- ADDED LOGGING (Corrected Indentation)
|
||||||
new_value = str(value).strip() if value is not None and str(value).strip() else None
|
new_value = str(value).strip() if value is not None and str(value).strip() else None
|
||||||
|
|
||||||
# Get the original identifier (assuming it exists on SourceRule)
|
# Get the original identifier (assuming it exists on SourceRule)
|
||||||
@ -516,12 +517,12 @@ class UnifiedViewModel(QAbstractItemModel):
|
|||||||
can_edit = False
|
can_edit = False
|
||||||
# Determine editability based on item type and column
|
# Determine editability based on item type and column
|
||||||
if isinstance(item, SourceRule): # If SourceRule is displayed/editable
|
if isinstance(item, SourceRule): # If SourceRule is displayed/editable
|
||||||
if column == 1: can_edit = True
|
if column == self.COL_SUPPLIER: can_edit = True # Supplier is editable
|
||||||
elif isinstance(item, AssetRule):
|
elif isinstance(item, AssetRule):
|
||||||
if column == 2: can_edit = True
|
if column == self.COL_ASSET_TYPE: can_edit = True # Asset Type is editable
|
||||||
elif isinstance(item, FileRule):
|
elif isinstance(item, FileRule):
|
||||||
if column == 3: can_edit = True
|
if column == self.COL_TARGET_ASSET: can_edit = True # Target Asset is editable
|
||||||
if column == 4: can_edit = True
|
if column == self.COL_ITEM_TYPE: can_edit = True # Item Type is editable
|
||||||
|
|
||||||
if can_edit:
|
if can_edit:
|
||||||
return default_flags | Qt.ItemIsEditable
|
return default_flags | Qt.ItemIsEditable
|
||||||
@ -546,3 +547,4 @@ class UnifiedViewModel(QAbstractItemModel):
|
|||||||
if item: # Ensure internal pointer is not None
|
if item: # Ensure internal pointer is not None
|
||||||
return item
|
return item
|
||||||
return None # Return None for invalid index or None pointer
|
return None # Return None for invalid index or None pointer
|
||||||
|
|
||||||
42
main.py
42
main.py
@ -410,10 +410,48 @@ class App(QObject):
|
|||||||
log.debug("DEBUG: Entering task queuing loop.") # <-- Keep this log
|
log.debug("DEBUG: Entering task queuing loop.") # <-- Keep this log
|
||||||
for i, rule in enumerate(source_rules): # Added enumerate for index logging
|
for i, rule in enumerate(source_rules): # Added enumerate for index logging
|
||||||
if isinstance(rule, SourceRule):
|
if isinstance(rule, SourceRule):
|
||||||
|
log.info(f"DEBUG Task {i+1}: Rule Input='{rule.input_path}', Supplier ID='{getattr(rule, 'supplier_identifier', 'Not Set')}', Preset='{getattr(rule, 'preset_name', 'Not Set')}'") # <-- ADDED LOGGING (Corrected Indentation)
|
||||||
log.debug(f"DEBUG: Preparing to queue task {i+1}/{len(source_rules)} for rule: {rule.input_path}") # <-- Keep this log
|
log.debug(f"DEBUG: Preparing to queue task {i+1}/{len(source_rules)} for rule: {rule.input_path}") # <-- Keep this log
|
||||||
# Pass the required paths to the ProcessingTask constructor
|
|
||||||
|
# --- Create a new Configuration and Engine instance for this specific task ---
|
||||||
|
task_engine = None
|
||||||
|
try:
|
||||||
|
# Get preset name from the rule, fallback to app's default if missing
|
||||||
|
preset_name_for_task = getattr(rule, 'preset_name', None)
|
||||||
|
if not preset_name_for_task:
|
||||||
|
log.warning(f"Task {i+1} (Rule: {rule.input_path}): SourceRule missing preset_name. Falling back to default preset '{self.config_obj.preset_name}'.")
|
||||||
|
preset_name_for_task = self.config_obj.preset_name # Use the initially loaded default
|
||||||
|
|
||||||
|
# Load the specific configuration for this task's preset
|
||||||
|
task_config = Configuration(preset_name=preset_name_for_task)
|
||||||
|
task_engine = ProcessingEngine(task_config)
|
||||||
|
log.debug(f"Task {i+1}: Created new ProcessingEngine instance with preset '{preset_name_for_task}'.")
|
||||||
|
|
||||||
|
except ConfigurationError as config_err:
|
||||||
|
log.error(f"Task {i+1} (Rule: {rule.input_path}): Failed to load configuration for preset '{preset_name_for_task}': {config_err}. Skipping task.")
|
||||||
|
self._active_tasks_count -= 1 # Decrement count as this task won't run
|
||||||
|
self._task_results["failed"] += 1
|
||||||
|
# Optionally update GUI status for this specific rule
|
||||||
|
self.main_window.update_file_status(str(rule.input_path), "failed", f"Config Error: {config_err}")
|
||||||
|
continue # Skip to the next rule
|
||||||
|
except Exception as engine_err:
|
||||||
|
log.exception(f"Task {i+1} (Rule: {rule.input_path}): Failed to initialize ProcessingEngine for preset '{preset_name_for_task}': {engine_err}. Skipping task.")
|
||||||
|
self._active_tasks_count -= 1 # Decrement count
|
||||||
|
self._task_results["failed"] += 1
|
||||||
|
self.main_window.update_file_status(str(rule.input_path), "failed", f"Engine Init Error: {engine_err}")
|
||||||
|
continue # Skip to the next rule
|
||||||
|
|
||||||
|
if task_engine is None: # Should not happen if exceptions are caught, but safety check
|
||||||
|
log.error(f"Task {i+1} (Rule: {rule.input_path}): Engine is None after initialization attempt. Skipping task.")
|
||||||
|
self._active_tasks_count -= 1 # Decrement count
|
||||||
|
self._task_results["failed"] += 1
|
||||||
|
self.main_window.update_file_status(str(rule.input_path), "failed", "Engine initialization failed (unknown reason).")
|
||||||
|
continue # Skip to the next rule
|
||||||
|
# --- End Engine Instantiation ---
|
||||||
|
|
||||||
|
# Pass the required paths and the NEW engine instance to the ProcessingTask constructor
|
||||||
task = ProcessingTask(
|
task = ProcessingTask(
|
||||||
engine=self.processing_engine,
|
engine=task_engine, # Pass the newly created engine
|
||||||
rule=rule,
|
rule=rule,
|
||||||
workspace_path=workspace_path,
|
workspace_path=workspace_path,
|
||||||
output_base_path=output_base_path
|
output_base_path=output_base_path
|
||||||
|
|||||||
@ -319,21 +319,32 @@ class ProcessingEngine:
|
|||||||
temp_metadata_path_asset = None # Track metadata file for this asset
|
temp_metadata_path_asset = None # Track metadata file for this asset
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# --- Skip Check ---
|
# --- Determine Effective Supplier (Override > Identifier > Fallback) ---
|
||||||
# Use static config for supplier name and metadata filename
|
effective_supplier = source_rule.supplier_override # Prioritize override
|
||||||
supplier_sanitized = _sanitize_filename(self.config_obj.supplier_name)
|
if effective_supplier is None:
|
||||||
|
effective_supplier = source_rule.supplier_identifier # Fallback to original identifier
|
||||||
|
if not effective_supplier: # Check if still None or empty
|
||||||
|
log.warning(f"Asset '{asset_name}': Supplier identifier missing from rule and override. Using fallback 'UnknownSupplier'.")
|
||||||
|
effective_supplier = "UnknownSupplier" # Final fallback
|
||||||
|
|
||||||
|
log.debug(f"Asset '{asset_name}': Effective supplier determined as '{effective_supplier}' (Override: '{source_rule.supplier_override}', Original: '{source_rule.supplier_identifier}')")
|
||||||
|
|
||||||
|
# --- Skip Check (using effective supplier) ---
|
||||||
|
supplier_sanitized = _sanitize_filename(effective_supplier)
|
||||||
asset_name_sanitized = _sanitize_filename(asset_name)
|
asset_name_sanitized = _sanitize_filename(asset_name)
|
||||||
final_dir = output_base_path / supplier_sanitized / asset_name_sanitized
|
final_dir = output_base_path / supplier_sanitized / asset_name_sanitized
|
||||||
metadata_file_path = final_dir / self.config_obj.metadata_filename
|
metadata_file_path = final_dir / self.config_obj.metadata_filename # Metadata filename still comes from config
|
||||||
|
|
||||||
|
log.debug(f"Checking for existing output/overwrite at: {final_dir} (using effective supplier: '{effective_supplier}')")
|
||||||
|
|
||||||
if not overwrite and final_dir.exists():
|
if not overwrite and final_dir.exists():
|
||||||
log.info(f"Output directory and metadata found for asset '{asset_name_sanitized}' and overwrite is False. Skipping.")
|
log.info(f"Output directory found for asset '{asset_name_sanitized}' (Supplier: '{effective_supplier}') and overwrite is False. Skipping.")
|
||||||
overall_status["skipped"].append(asset_name)
|
overall_status["skipped"].append(asset_name)
|
||||||
asset_skipped = True
|
asset_skipped = True
|
||||||
continue # Skip to the next asset
|
continue # Skip to the next asset
|
||||||
|
|
||||||
elif overwrite and final_dir.exists():
|
elif overwrite and final_dir.exists():
|
||||||
log.warning(f"Output directory exists for '{asset_name_sanitized}' and overwrite is True. Removing existing directory: {final_dir}")
|
log.warning(f"Output directory exists for '{asset_name_sanitized}' (Supplier: '{effective_supplier}') and overwrite is True. Removing existing directory: {final_dir}")
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(final_dir)
|
shutil.rmtree(final_dir)
|
||||||
except Exception as rm_err:
|
except Exception as rm_err:
|
||||||
@ -343,6 +354,8 @@ class ProcessingEngine:
|
|||||||
# Start with common metadata from the rule, add asset name
|
# Start with common metadata from the rule, add asset name
|
||||||
current_asset_metadata = asset_rule.common_metadata.copy()
|
current_asset_metadata = asset_rule.common_metadata.copy()
|
||||||
current_asset_metadata["asset_name"] = asset_name
|
current_asset_metadata["asset_name"] = asset_name
|
||||||
|
# Use the EFFECTIVE supplier here
|
||||||
|
current_asset_metadata["supplier_name"] = effective_supplier
|
||||||
# Add other fields that will be populated
|
# Add other fields that will be populated
|
||||||
current_asset_metadata["maps_present"] = []
|
current_asset_metadata["maps_present"] = []
|
||||||
current_asset_metadata["merged_maps"] = []
|
current_asset_metadata["merged_maps"] = []
|
||||||
@ -371,8 +384,9 @@ class ProcessingEngine:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# --- Generate Metadata ---
|
# --- Generate Metadata ---
|
||||||
|
# Pass effective_supplier instead of the whole source_rule
|
||||||
temp_metadata_path_asset = self._generate_metadata_file(
|
temp_metadata_path_asset = self._generate_metadata_file(
|
||||||
source_rule=source_rule, # Pass the parent SourceRule
|
effective_supplier=effective_supplier, # Pass the determined supplier
|
||||||
asset_rule=asset_rule,
|
asset_rule=asset_rule,
|
||||||
current_asset_metadata=current_asset_metadata, # Pass the populated dict
|
current_asset_metadata=current_asset_metadata, # Pass the populated dict
|
||||||
processed_maps_details_asset=processed_maps_details_asset,
|
processed_maps_details_asset=processed_maps_details_asset,
|
||||||
@ -380,17 +394,18 @@ class ProcessingEngine:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# --- Organize Output ---
|
# --- Organize Output ---
|
||||||
|
# Pass effective_supplier instead of source_rule.supplier_identifier
|
||||||
self._organize_output_files(
|
self._organize_output_files(
|
||||||
asset_rule=asset_rule,
|
asset_rule=asset_rule,
|
||||||
workspace_path=workspace_path, # Pass the original workspace path
|
workspace_path=workspace_path, # Pass the original workspace path
|
||||||
supplier_identifier=source_rule.supplier_identifier, # Pass supplier from SourceRule
|
supplier_identifier=effective_supplier, # Pass the determined supplier
|
||||||
output_base_path=output_base_path, # Pass output path
|
output_base_path=output_base_path, # Pass output path
|
||||||
processed_maps_details_asset=processed_maps_details_asset,
|
processed_maps_details_asset=processed_maps_details_asset,
|
||||||
merged_maps_details_asset=merged_maps_details_asset,
|
merged_maps_details_asset=merged_maps_details_asset,
|
||||||
temp_metadata_path=temp_metadata_path_asset
|
temp_metadata_path=temp_metadata_path_asset
|
||||||
)
|
)
|
||||||
|
|
||||||
log.info(f"--- Asset '{asset_name}' processed successfully. ---")
|
log.info(f"--- Asset '{asset_name}' processed successfully (Supplier: {effective_supplier}). ---")
|
||||||
overall_status["processed"].append(asset_name)
|
overall_status["processed"].append(asset_name)
|
||||||
asset_processed = True
|
asset_processed = True
|
||||||
|
|
||||||
@ -1257,12 +1272,13 @@ class ProcessingEngine:
|
|||||||
return merged_maps_details_asset
|
return merged_maps_details_asset
|
||||||
|
|
||||||
|
|
||||||
def _generate_metadata_file(self, source_rule: SourceRule, asset_rule: AssetRule, current_asset_metadata: Dict, processed_maps_details_asset: Dict[str, Dict[str, Dict]], merged_maps_details_asset: Dict[str, Dict[str, Dict]]) -> Path:
|
def _generate_metadata_file(self, effective_supplier: str, asset_rule: AssetRule, current_asset_metadata: Dict, processed_maps_details_asset: Dict[str, Dict[str, Dict]], merged_maps_details_asset: Dict[str, Dict[str, Dict]]) -> Path:
|
||||||
"""
|
"""
|
||||||
Gathers metadata for a specific asset based on the AssetRule and processing results,
|
Gathers metadata for a specific asset based on the AssetRule and processing results,
|
||||||
and writes it to a temporary JSON file in the engine's temp_dir.
|
and writes it to a temporary JSON file in the engine's temp_dir.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
effective_supplier: The supplier name to use (override or original).
|
||||||
asset_rule: The AssetRule object for this asset.
|
asset_rule: The AssetRule object for this asset.
|
||||||
current_asset_metadata: Base metadata dictionary (already contains name, category, archetype, stats, aspect ratio, map_details).
|
current_asset_metadata: Base metadata dictionary (already contains name, category, archetype, stats, aspect ratio, map_details).
|
||||||
processed_maps_details_asset: Details of processed maps for this asset.
|
processed_maps_details_asset: Details of processed maps for this asset.
|
||||||
@ -1277,13 +1293,13 @@ class ProcessingEngine:
|
|||||||
log.warning("Asset name missing during metadata generation, file may be incomplete or incorrectly named.")
|
log.warning("Asset name missing during metadata generation, file may be incomplete or incorrectly named.")
|
||||||
asset_name = "UnknownAsset_Metadata" # Fallback for filename
|
asset_name = "UnknownAsset_Metadata" # Fallback for filename
|
||||||
|
|
||||||
log.info(f"Generating metadata file for asset '{asset_name}'...")
|
log.info(f"Generating metadata file for asset '{asset_name}' (Supplier: {effective_supplier})...")
|
||||||
|
|
||||||
# Start with the base metadata passed in (already contains name, category, archetype, stats, aspect, map_details)
|
# Start with the base metadata passed in (already contains name, category, archetype, stats, aspect, map_details)
|
||||||
final_metadata = current_asset_metadata.copy()
|
final_metadata = current_asset_metadata.copy()
|
||||||
|
|
||||||
# Use the supplier identifier determined by rules/selection (from SourceRule)
|
# Use the effective supplier passed as argument
|
||||||
final_metadata["supplier_name"] = source_rule.supplier_identifier or self.config_obj.supplier_name # Fallback to config if empty
|
final_metadata["supplier_name"] = effective_supplier # Already determined in process()
|
||||||
|
|
||||||
# Populate map resolution details from processing results
|
# Populate map resolution details from processing results
|
||||||
final_metadata["processed_map_resolutions"] = {}
|
final_metadata["processed_map_resolutions"] = {}
|
||||||
@ -1326,11 +1342,11 @@ class ProcessingEngine:
|
|||||||
source_files_in_extra_set.add(str(file_rule.file_path))
|
source_files_in_extra_set.add(str(file_rule.file_path))
|
||||||
final_metadata["source_files_in_extra"] = sorted(list(source_files_in_extra_set))
|
final_metadata["source_files_in_extra"] = sorted(list(source_files_in_extra_set))
|
||||||
|
|
||||||
# Add processing info (using static config for preset name)
|
# Add processing info
|
||||||
final_metadata["_processing_info"] = {
|
final_metadata["_processing_info"] = {
|
||||||
"preset_used": getattr(source_rule, 'preset_name', self.config_obj.preset_name), # Use preset name from SourceRule (fallback to static config)
|
"preset_used": self.config_obj.preset_name, # Preset name comes from the engine's config
|
||||||
"timestamp_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
"timestamp_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
"input_source": source_rule.supplier_identifier or "Unknown", # Use identifier from parent SourceRule
|
"input_source": effective_supplier, # Use the effective supplier
|
||||||
}
|
}
|
||||||
|
|
||||||
# Sort lists just before writing
|
# Sort lists just before writing
|
||||||
@ -1381,7 +1397,7 @@ class ProcessingEngine:
|
|||||||
log.warning(f"Asset '{asset_name}': Supplier identifier missing in SourceRule. Using fallback 'UnknownSupplier'.")
|
log.warning(f"Asset '{asset_name}': Supplier identifier missing in SourceRule. Using fallback 'UnknownSupplier'.")
|
||||||
supplier_identifier = "UnknownSupplier"
|
supplier_identifier = "UnknownSupplier"
|
||||||
|
|
||||||
supplier_sanitized = _sanitize_filename(supplier_identifier) # <<< FIX: Use passed identifier
|
supplier_sanitized = _sanitize_filename(supplier_identifier) # Use the effective supplier passed in
|
||||||
asset_name_sanitized = _sanitize_filename(asset_name)
|
asset_name_sanitized = _sanitize_filename(asset_name)
|
||||||
final_dir = output_base_path / supplier_sanitized / asset_name_sanitized
|
final_dir = output_base_path / supplier_sanitized / asset_name_sanitized
|
||||||
log.info(f"Organizing output files for asset '{asset_name_sanitized}' (Supplier: '{supplier_identifier}') into: {final_dir}")
|
log.info(f"Organizing output files for asset '{asset_name_sanitized}' (Supplier: '{supplier_identifier}') into: {final_dir}")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user