GUI FIXES
This commit is contained in:
623
gui/config_editor_dialog.py
Normal file
623
gui/config_editor_dialog.py
Normal file
@@ -0,0 +1,623 @@
|
||||
# gui/config_editor_dialog.py
|
||||
|
||||
import json
|
||||
from PySide6.QtWidgets import ( # Changed from PyQt5
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QTabWidget, QWidget,
|
||||
QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox, QCheckBox,
|
||||
QPushButton, QFileDialog, QLabel, QTableWidget, # Removed QColorDialog
|
||||
QTableWidgetItem, QDialogButtonBox, QMessageBox, QListWidget,
|
||||
QListWidgetItem, QFormLayout, QGroupBox
|
||||
)
|
||||
from PySide6.QtGui import QColor # Changed from PyQt5
|
||||
from PySide6.QtCore import Qt # Changed from PyQt5
|
||||
from PySide6.QtWidgets import QColorDialog # Import QColorDialog separately for PySide6
|
||||
|
||||
# Assuming configuration.py is in the parent directory or accessible
|
||||
# Adjust import path if necessary
|
||||
try:
|
||||
from configuration import load_base_config, save_base_config
|
||||
except ImportError:
|
||||
# Fallback import for testing or different project structure
|
||||
from ..configuration import load_base_config, save_base_config
|
||||
|
||||
|
||||
class ConfigEditorDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Configuration Editor")
|
||||
self.setGeometry(100, 100, 800, 600)
|
||||
|
||||
self.settings = {}
|
||||
self.widgets = {} # Dictionary to hold references to created widgets
|
||||
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.tab_widget = QTabWidget()
|
||||
self.main_layout.addWidget(self.tab_widget)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.save_settings)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
self.main_layout.addWidget(self.button_box)
|
||||
|
||||
self.load_settings()
|
||||
self.create_tabs()
|
||||
self.populate_widgets()
|
||||
|
||||
def load_settings(self):
|
||||
"""Loads settings from the configuration file."""
|
||||
try:
|
||||
self.settings = load_base_config()
|
||||
print("Configuration loaded successfully.") # Debug print
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Loading Error", f"Failed to load configuration: {e}")
|
||||
self.settings = {} # Use empty settings on failure
|
||||
|
||||
def create_tabs(self):
|
||||
"""Creates tabs based on logical groupings of settings."""
|
||||
if not self.settings:
|
||||
return
|
||||
|
||||
# --- Create Tabs ---
|
||||
self.tabs = {
|
||||
"definitions": QWidget(),
|
||||
"paths_output": QWidget(),
|
||||
"image_settings": QWidget(),
|
||||
"blender": QWidget(),
|
||||
"misc": QWidget() # For settings that don't fit elsewhere
|
||||
}
|
||||
self.tab_widget.addTab(self.tabs["definitions"], "Definitions")
|
||||
self.tab_widget.addTab(self.tabs["paths_output"], "Paths & Output")
|
||||
self.tab_widget.addTab(self.tabs["image_settings"], "Image Settings")
|
||||
self.tab_widget.addTab(self.tabs["blender"], "Blender")
|
||||
self.tab_widget.addTab(self.tabs["misc"], "Miscellaneous")
|
||||
|
||||
# --- Setup Layouts for Tabs ---
|
||||
self.tab_layouts = {name: QVBoxLayout(tab) for name, tab in self.tabs.items()}
|
||||
|
||||
# --- Populate Tabs ---
|
||||
self.populate_definitions_tab(self.tab_layouts["definitions"])
|
||||
self.populate_paths_output_tab(self.tab_layouts["paths_output"])
|
||||
self.populate_image_settings_tab(self.tab_layouts["image_settings"])
|
||||
self.populate_blender_tab(self.tab_layouts["blender"])
|
||||
self.populate_misc_tab(self.tab_layouts["misc"])
|
||||
|
||||
def create_widget_for_setting(self, parent_layout, key, value, setting_key_prefix=""):
|
||||
"""Creates an appropriate widget for a single setting key-value pair."""
|
||||
full_key = f"{setting_key_prefix}{key}" if setting_key_prefix else key
|
||||
label_text = key.replace('_', ' ').title()
|
||||
label = QLabel(label_text + ":")
|
||||
widget = None
|
||||
layout_to_add = None # Use this for widgets needing extra controls (like browse button)
|
||||
|
||||
if isinstance(value, str):
|
||||
if 'PATH' in key.upper() or 'DIR' in key.upper() or key == "BLENDER_EXECUTABLE_PATH":
|
||||
widget = QLineEdit(value)
|
||||
button = QPushButton("Browse...")
|
||||
# Determine if it's a file or directory browse
|
||||
is_dir = 'DIR' in key.upper()
|
||||
button.clicked.connect(lambda checked, w=widget, k=full_key, is_dir=is_dir: self.browse_path(w, k, is_dir))
|
||||
h_layout = QHBoxLayout()
|
||||
h_layout.addWidget(widget)
|
||||
h_layout.addWidget(button)
|
||||
layout_to_add = h_layout
|
||||
elif 'COLOR' in key.upper() or 'COLOUR' in key.upper():
|
||||
widget = QLineEdit(value)
|
||||
button = QPushButton("Pick Color...")
|
||||
button.clicked.connect(lambda checked, w=widget: self.pick_color(w))
|
||||
h_layout = QHBoxLayout()
|
||||
h_layout.addWidget(widget)
|
||||
h_layout.addWidget(button)
|
||||
layout_to_add = h_layout
|
||||
else:
|
||||
widget = QLineEdit(value)
|
||||
elif isinstance(value, int):
|
||||
widget = QSpinBox()
|
||||
widget.setRange(-2147483648, 2147483647)
|
||||
widget.setValue(value)
|
||||
elif isinstance(value, float):
|
||||
widget = QDoubleSpinBox()
|
||||
widget.setRange(-1.7976931348623157e+308, 1.7976931348623157e+308)
|
||||
widget.setValue(value)
|
||||
elif isinstance(value, bool):
|
||||
widget = QCheckBox()
|
||||
widget.setChecked(value)
|
||||
elif isinstance(value, list) and key != "MAP_MERGE_RULES": # Handle simple lists (excluding complex ones)
|
||||
# Assuming list of strings or simple types
|
||||
widget = QLineEdit(", ".join(map(str, value)))
|
||||
# Complex dicts/lists like ASSET_TYPE_DEFINITIONS, MAP_MERGE_RULES etc. are handled in dedicated methods
|
||||
|
||||
if widget or layout_to_add:
|
||||
if layout_to_add:
|
||||
parent_layout.addRow(label, layout_to_add)
|
||||
else:
|
||||
parent_layout.addRow(label, widget)
|
||||
|
||||
# Store reference using the full key only if a widget was created
|
||||
if widget:
|
||||
self.widgets[full_key] = widget
|
||||
else:
|
||||
# Optionally handle unsupported types or log a warning
|
||||
# print(f"Skipping widget creation for key '{full_key}' with unsupported type: {type(value)}")
|
||||
pass
|
||||
|
||||
def populate_definitions_tab(self, layout):
|
||||
"""Populates the Definitions tab."""
|
||||
if "ASSET_TYPE_DEFINITIONS" in self.settings:
|
||||
group = QGroupBox("Asset Type Definitions")
|
||||
group_layout = QVBoxLayout(group)
|
||||
self.create_asset_definitions_widget(group_layout, self.settings["ASSET_TYPE_DEFINITIONS"])
|
||||
layout.addWidget(group)
|
||||
|
||||
if "FILE_TYPE_DEFINITIONS" in self.settings:
|
||||
group = QGroupBox("File Type Definitions")
|
||||
group_layout = QVBoxLayout(group)
|
||||
self.create_file_type_definitions_widget(group_layout, self.settings["FILE_TYPE_DEFINITIONS"])
|
||||
layout.addWidget(group)
|
||||
|
||||
# Add STANDARD_MAP_TYPES and RESPECT_VARIANT_MAP_TYPES here
|
||||
form_layout = QFormLayout()
|
||||
if "STANDARD_MAP_TYPES" in self.settings:
|
||||
self.create_widget_for_setting(form_layout, "STANDARD_MAP_TYPES", self.settings["STANDARD_MAP_TYPES"])
|
||||
if "RESPECT_VARIANT_MAP_TYPES" in self.settings:
|
||||
self.create_widget_for_setting(form_layout, "RESPECT_VARIANT_MAP_TYPES", self.settings["RESPECT_VARIANT_MAP_TYPES"])
|
||||
if "DEFAULT_ASSET_CATEGORY" in self.settings:
|
||||
self.create_widget_for_setting(form_layout, "DEFAULT_ASSET_CATEGORY", self.settings["DEFAULT_ASSET_CATEGORY"])
|
||||
|
||||
layout.addLayout(form_layout)
|
||||
layout.addStretch()
|
||||
|
||||
def populate_paths_output_tab(self, layout):
|
||||
"""Populates the Paths & Output tab."""
|
||||
form_layout = QFormLayout()
|
||||
keys_to_include = [
|
||||
"OUTPUT_BASE_DIR", "EXTRA_FILES_SUBDIR", "METADATA_FILENAME",
|
||||
"TARGET_FILENAME_PATTERN", "TEMP_DIR_PREFIX"
|
||||
]
|
||||
for key in keys_to_include:
|
||||
if key in self.settings:
|
||||
self.create_widget_for_setting(form_layout, key, self.settings[key])
|
||||
layout.addLayout(form_layout)
|
||||
layout.addStretch()
|
||||
|
||||
def populate_image_settings_tab(self, layout):
|
||||
"""Populates the Image Settings tab."""
|
||||
form_layout = QFormLayout()
|
||||
simple_keys = [
|
||||
"PNG_COMPRESSION_LEVEL", "JPG_QUALITY", "RESOLUTION_THRESHOLD_FOR_JPG",
|
||||
"ASPECT_RATIO_DECIMALS", "CALCULATE_STATS_RESOLUTION",
|
||||
"OUTPUT_FORMAT_16BIT_PRIMARY", "OUTPUT_FORMAT_16BIT_FALLBACK",
|
||||
"OUTPUT_FORMAT_8BIT"
|
||||
]
|
||||
for key in simple_keys:
|
||||
if key in self.settings:
|
||||
self.create_widget_for_setting(form_layout, key, self.settings[key])
|
||||
layout.addLayout(form_layout)
|
||||
|
||||
# Add complex widgets
|
||||
if "IMAGE_RESOLUTIONS" in self.settings:
|
||||
group = QGroupBox("Image Resolutions")
|
||||
group_layout = QVBoxLayout(group)
|
||||
self.create_image_resolutions_widget(group_layout, self.settings["IMAGE_RESOLUTIONS"])
|
||||
layout.addWidget(group)
|
||||
|
||||
if "MAP_BIT_DEPTH_RULES" in self.settings:
|
||||
group = QGroupBox("Map Bit Depth Rules")
|
||||
group_layout = QVBoxLayout(group)
|
||||
self.create_map_bit_depth_rules_widget(group_layout, self.settings["MAP_BIT_DEPTH_RULES"])
|
||||
layout.addWidget(group)
|
||||
|
||||
if "MAP_MERGE_RULES" in self.settings:
|
||||
group = QGroupBox("Map Merge Rules")
|
||||
group_layout = QVBoxLayout(group)
|
||||
self.create_map_merge_rules_widget(group_layout, self.settings["MAP_MERGE_RULES"])
|
||||
layout.addWidget(group)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
def populate_blender_tab(self, layout):
|
||||
"""Populates the Blender tab."""
|
||||
form_layout = QFormLayout()
|
||||
keys_to_include = [
|
||||
"DEFAULT_NODEGROUP_BLEND_PATH", "DEFAULT_MATERIALS_BLEND_PATH",
|
||||
"BLENDER_EXECUTABLE_PATH"
|
||||
]
|
||||
for key in keys_to_include:
|
||||
if key in self.settings:
|
||||
self.create_widget_for_setting(form_layout, key, self.settings[key])
|
||||
layout.addLayout(form_layout)
|
||||
layout.addStretch()
|
||||
|
||||
def populate_misc_tab(self, layout):
|
||||
"""Populates the Miscellaneous tab with any remaining settings."""
|
||||
form_layout = QFormLayout()
|
||||
handled_keys = set()
|
||||
# Collect keys handled by other tabs
|
||||
handled_keys.update([
|
||||
"ASSET_TYPE_DEFINITIONS", "FILE_TYPE_DEFINITIONS", "STANDARD_MAP_TYPES",
|
||||
"RESPECT_VARIANT_MAP_TYPES", "DEFAULT_ASSET_CATEGORY", "OUTPUT_BASE_DIR",
|
||||
"EXTRA_FILES_SUBDIR", "METADATA_FILENAME", "TARGET_FILENAME_PATTERN",
|
||||
"TEMP_DIR_PREFIX", "PNG_COMPRESSION_LEVEL", "JPG_QUALITY",
|
||||
"RESOLUTION_THRESHOLD_FOR_JPG", "ASPECT_RATIO_DECIMALS",
|
||||
"CALCULATE_STATS_RESOLUTION", "OUTPUT_FORMAT_16BIT_PRIMARY",
|
||||
"OUTPUT_FORMAT_16BIT_FALLBACK", "OUTPUT_FORMAT_8BIT",
|
||||
"IMAGE_RESOLUTIONS", "MAP_BIT_DEPTH_RULES", "MAP_MERGE_RULES",
|
||||
"DEFAULT_NODEGROUP_BLEND_PATH", "DEFAULT_MATERIALS_BLEND_PATH",
|
||||
"BLENDER_EXECUTABLE_PATH"
|
||||
])
|
||||
|
||||
for key, value in self.settings.items():
|
||||
if key not in handled_keys:
|
||||
# Only create widgets for simple types here
|
||||
if isinstance(value, (str, int, float, bool, list)):
|
||||
# Check if list is simple
|
||||
is_simple_list = isinstance(value, list) and (not value or not isinstance(value[0], (dict, list)))
|
||||
if not isinstance(value, list) or is_simple_list:
|
||||
self.create_widget_for_setting(form_layout, key, value)
|
||||
handled_keys.add(key) # Mark as handled
|
||||
|
||||
if form_layout.rowCount() == 0:
|
||||
layout.addWidget(QLabel("No miscellaneous settings found."))
|
||||
|
||||
layout.addLayout(form_layout)
|
||||
layout.addStretch()
|
||||
|
||||
|
||||
# Remove the old create_widgets_for_section method as it's replaced
|
||||
# def create_widgets_for_section(self, layout, section_data, section_key):
|
||||
# ... (old implementation removed) ...
|
||||
|
||||
|
||||
def create_asset_definitions_widget(self, layout, definitions_data):
|
||||
"""Creates a widget for editing asset type definitions."""
|
||||
table = QTableWidget()
|
||||
table.setColumnCount(3) # Asset Type, Description, Color
|
||||
table.setHorizontalHeaderLabels(["Asset Type", "Description", "Color"])
|
||||
table.setRowCount(len(definitions_data))
|
||||
|
||||
row = 0
|
||||
for asset_type, details in definitions_data.items():
|
||||
table.setItem(row, 0, QTableWidgetItem(asset_type))
|
||||
table.setItem(row, 1, QTableWidgetItem(details.get("description", "")))
|
||||
|
||||
color_widget = QLineEdit(details.get("color", ""))
|
||||
color_button = QPushButton("Pick Color...")
|
||||
color_button.clicked.connect(lambda checked, w=color_widget: self.pick_color(w))
|
||||
h_layout = QHBoxLayout()
|
||||
h_layout.addWidget(color_widget)
|
||||
h_layout.addWidget(color_button)
|
||||
|
||||
cell_widget = QWidget()
|
||||
cell_widget.setLayout(h_layout)
|
||||
table.setCellWidget(row, 2, cell_widget)
|
||||
|
||||
row += 1
|
||||
|
||||
table.horizontalHeader().setStretchLastSection(True)
|
||||
layout.addWidget(table)
|
||||
self.widgets["DEFINITION_SETTINGS.ASSET_TYPE_DEFINITIONS"] = table # Store table reference
|
||||
|
||||
def create_file_type_definitions_widget(self, layout, definitions_data):
|
||||
"""Creates a widget for editing file type definitions."""
|
||||
table = QTableWidget()
|
||||
table.setColumnCount(3) # File Type, Description, Color
|
||||
table.setHorizontalHeaderLabels(["File Type", "Description", "Color"])
|
||||
table.setRowCount(len(definitions_data))
|
||||
|
||||
row = 0
|
||||
for file_type, details in definitions_data.items():
|
||||
table.setItem(row, 0, QTableWidgetItem(file_type))
|
||||
table.setItem(row, 1, QTableWidgetItem(details.get("description", "")))
|
||||
|
||||
color_widget = QLineEdit(details.get("color", ""))
|
||||
color_button = QPushButton("Pick Color...")
|
||||
color_button.clicked.connect(lambda checked, w=color_widget: self.pick_color(w))
|
||||
h_layout = QHBoxLayout()
|
||||
h_layout.addWidget(color_widget)
|
||||
h_layout.addWidget(color_button)
|
||||
|
||||
cell_widget = QWidget()
|
||||
cell_widget.setLayout(h_layout)
|
||||
table.setCellWidget(row, 2, cell_widget)
|
||||
|
||||
row += 1
|
||||
|
||||
table.horizontalHeader().setStretchLastSection(True)
|
||||
layout.addWidget(table)
|
||||
self.widgets["DEFINITION_SETTINGS.FILE_TYPE_DEFINITIONS"] = table # Store table reference
|
||||
|
||||
def create_image_resolutions_widget(self, layout, resolutions_data):
|
||||
"""Creates a widget for editing image resolutions."""
|
||||
table = QTableWidget()
|
||||
table.setColumnCount(2) # Width, Height
|
||||
table.setHorizontalHeaderLabels(["Width", "Height"])
|
||||
table.setRowCount(len(resolutions_data))
|
||||
|
||||
for row, resolution in enumerate(resolutions_data):
|
||||
table.setItem(row, 0, QTableWidgetItem(str(resolution[0])))
|
||||
table.setItem(row, 1, QTableWidgetItem(str(resolution[1])))
|
||||
|
||||
table.horizontalHeader().setStretchLastSection(True)
|
||||
layout.addWidget(table)
|
||||
self.widgets["IMAGE_PROCESSING_SETTINGS.IMAGE_RESOLUTIONS"] = table # Store table reference
|
||||
|
||||
def create_map_bit_depth_rules_widget(self, layout, rules_data: dict):
|
||||
"""Creates a widget for editing map bit depth rules (Map Type -> Rule)."""
|
||||
table = QTableWidget()
|
||||
table.setColumnCount(2) # Map Type, Rule
|
||||
table.setHorizontalHeaderLabels(["Map Type", "Rule (respect/force_8bit)"])
|
||||
table.setRowCount(len(rules_data))
|
||||
|
||||
# Iterate through dictionary items (key-value pairs)
|
||||
for row, (map_type, rule_string) in enumerate(rules_data.items()):
|
||||
table.setItem(row, 0, QTableWidgetItem(map_type))
|
||||
# Optionally use a ComboBox for the rule selection later
|
||||
table.setItem(row, 1, QTableWidgetItem(str(rule_string)))
|
||||
|
||||
table.horizontalHeader().setStretchLastSection(True)
|
||||
layout.addWidget(table)
|
||||
# Store reference using a more specific key if needed, or handle in save_settings
|
||||
self.widgets["MAP_BIT_DEPTH_RULES_TABLE"] = table # Use a distinct key for the table widget
|
||||
|
||||
def create_map_merge_rules_widget(self, layout, rules_data):
|
||||
"""Creates a widget for editing map merge rules."""
|
||||
# This is a more complex structure (list of dicts)
|
||||
# Using a ListWidget to select rules and a separate form to edit details
|
||||
h_layout = QHBoxLayout()
|
||||
layout.addLayout(h_layout)
|
||||
|
||||
self.merge_rules_list = QListWidget()
|
||||
self.merge_rules_list.currentItemChanged.connect(self.display_merge_rule_details)
|
||||
h_layout.addWidget(self.merge_rules_list, 1) # Give list more space
|
||||
|
||||
self.merge_rule_details_group = QGroupBox("Rule Details")
|
||||
self.merge_rule_details_layout = QFormLayout(self.merge_rule_details_group)
|
||||
h_layout.addWidget(self.merge_rule_details_group, 2) # Give details form more space
|
||||
|
||||
self.merge_rule_widgets = {} # Widgets for the currently displayed rule
|
||||
|
||||
self.populate_merge_rules_list(rules_data)
|
||||
self.widgets["IMAGE_PROCESSING_SETTINGS.MAP_MERGE_RULES"] = rules_data # Store original data reference
|
||||
|
||||
def populate_merge_rules_list(self, rules_data):
|
||||
"""Populates the list widget with map merge rules."""
|
||||
self.merge_rules_list.clear()
|
||||
for rule in rules_data:
|
||||
item = QListWidgetItem(rule.get("output_name", "Unnamed Rule"))
|
||||
item.setData(Qt.UserRole, rule) # Store the rule dictionary in the item
|
||||
self.merge_rules_list.addItem(item)
|
||||
|
||||
def display_merge_rule_details(self, current, previous):
|
||||
"""Displays details of the selected merge rule."""
|
||||
# Clear previous widgets
|
||||
for i in reversed(range(self.merge_rule_details_layout.count())):
|
||||
widget_item = self.merge_rule_details_layout.itemAt(i)
|
||||
if widget_item:
|
||||
widget = widget_item.widget()
|
||||
if widget:
|
||||
widget.deleteLater()
|
||||
layout = widget_item.layout()
|
||||
if layout:
|
||||
# Recursively delete widgets in layout
|
||||
while layout.count():
|
||||
item = layout.takeAt(0)
|
||||
widget = item.widget()
|
||||
if widget:
|
||||
widget.deleteLater()
|
||||
elif item.layout():
|
||||
# Handle nested layouts if necessary
|
||||
pass # For simplicity, assuming no deeply nested layouts here
|
||||
|
||||
self.merge_rule_widgets.clear()
|
||||
|
||||
if current:
|
||||
rule_data = current.data(Qt.UserRole)
|
||||
if rule_data:
|
||||
for key, value in rule_data.items():
|
||||
label = QLabel(key.replace('_', ' ').title() + ":")
|
||||
if isinstance(value, str):
|
||||
widget = QLineEdit(value)
|
||||
elif isinstance(value, (int, float)):
|
||||
if isinstance(value, int):
|
||||
widget = QSpinBox()
|
||||
widget.setRange(-2147483648, 2147483647)
|
||||
widget.setValue(value)
|
||||
else:
|
||||
widget = QDoubleSpinBox()
|
||||
widget.setRange(-1.7976931348623157e+308, 1.7976931348623157e+308)
|
||||
widget.setValue(value)
|
||||
elif isinstance(value, bool):
|
||||
widget = QCheckBox()
|
||||
widget.setChecked(value)
|
||||
elif isinstance(value, list):
|
||||
# Assuming list of strings or simple types for now
|
||||
widget = QLineEdit(", ".join(map(str, value)))
|
||||
elif isinstance(value, dict):
|
||||
# Assuming simple key-value dicts for now
|
||||
widget = QLineEdit(json.dumps(value)) # Display as JSON string
|
||||
else:
|
||||
widget = QLabel(f"Unsupported type: {type(value)}")
|
||||
|
||||
self.merge_rule_details_layout.addRow(label, widget)
|
||||
self.merge_rule_widgets[key] = widget # Store widget reference
|
||||
|
||||
def populate_widgets(self):
|
||||
"""Populates the created widgets with loaded settings (for simple types)."""
|
||||
# This method is less critical with the recursive create_widgets_for_section
|
||||
# but could be used for specific post-creation population if needed.
|
||||
pass
|
||||
|
||||
def browse_path(self, widget, key):
|
||||
"""Opens a file or directory dialog based on the setting key."""
|
||||
if 'DIR' in key.upper():
|
||||
path = QFileDialog.getExistingDirectory(self, "Select Directory", widget.text())
|
||||
else:
|
||||
path, _ = QFileDialog.getOpenFileName(self, "Select File", widget.text())
|
||||
|
||||
if path:
|
||||
widget.setText(path)
|
||||
|
||||
def pick_color(self, widget):
|
||||
"""Opens a color dialog and sets the selected color in the widget."""
|
||||
color = QColorDialog.getColor(QColor(widget.text()))
|
||||
if color.isValid():
|
||||
widget.setText(color.name()) # Get color as hex string
|
||||
|
||||
def save_settings(self):
|
||||
"""Reads values from widgets and saves them to the configuration file."""
|
||||
new_settings = {}
|
||||
|
||||
# Reconstruct the settings dictionary from widgets
|
||||
# This requires iterating through the widgets and mapping them back
|
||||
# to the original structure. This is a simplified approach and might
|
||||
# need refinement for complex nested structures or dynamic lists/tables.
|
||||
|
||||
# Start with a deep copy of the original settings structure to preserve
|
||||
# sections/keys that might not have dedicated widgets (though ideally all should)
|
||||
import copy
|
||||
new_settings = copy.deepcopy(self.settings)
|
||||
|
||||
# Iterate through the stored widgets and update the new_settings dictionary
|
||||
for key, widget in self.widgets.items():
|
||||
# Handle simple widgets
|
||||
if isinstance(widget, (QLineEdit, QSpinBox, QDoubleSpinBox, QCheckBox)):
|
||||
# Split the key to navigate the dictionary structure
|
||||
keys = key.split('.')
|
||||
current_dict = new_settings
|
||||
for i, k in enumerate(keys):
|
||||
if i == len(keys) - 1:
|
||||
# This is the final key, update the value
|
||||
if isinstance(widget, QLineEdit):
|
||||
current_dict[k] = widget.text()
|
||||
elif isinstance(widget, QSpinBox):
|
||||
current_dict[k] = widget.value()
|
||||
elif isinstance(widget, QDoubleSpinBox):
|
||||
current_dict[k] = widget.value()
|
||||
elif isinstance(widget, QCheckBox):
|
||||
current_dict[k] = widget.isChecked()
|
||||
else:
|
||||
# Navigate to the next level
|
||||
if k not in current_dict or not isinstance(current_dict[k], dict):
|
||||
# This should not happen if create_tabs is correct, but handle defensively
|
||||
print(f"Warning: Structure mismatch for key part '{k}' in '{key}'")
|
||||
break # Stop processing this key
|
||||
current_dict = current_dict[k]
|
||||
# Handle TableWidgets (for definitions, resolutions, bit depth rules)
|
||||
elif isinstance(widget, QTableWidget):
|
||||
keys = key.split('.')
|
||||
if len(keys) >= 2:
|
||||
section_key = keys[0]
|
||||
list_key = keys[1]
|
||||
if section_key in new_settings and list_key in new_settings[section_key]:
|
||||
if list_key == "ASSET_TYPE_DEFINITIONS":
|
||||
new_definitions = {}
|
||||
for row in range(widget.rowCount()):
|
||||
asset_type_item = widget.item(row, 0)
|
||||
description_item = widget.item(row, 1)
|
||||
color_widget_container = widget.cellWidget(row, 2)
|
||||
if asset_type_item and color_widget_container:
|
||||
asset_type = asset_type_item.text()
|
||||
description = description_item.text() if description_item else ""
|
||||
color_widget = color_widget_container.findChild(QLineEdit)
|
||||
if color_widget:
|
||||
color = color_widget.text()
|
||||
new_definitions[asset_type] = {"description": description, "color": color}
|
||||
new_settings[section_key][list_key] = new_definitions
|
||||
elif list_key == "FILE_TYPE_DEFINITIONS":
|
||||
new_definitions = {}
|
||||
for row in range(widget.rowCount()):
|
||||
file_type_item = widget.item(row, 0)
|
||||
description_item = widget.item(row, 1)
|
||||
color_widget_container = widget.cellWidget(row, 2)
|
||||
if file_type_item and color_widget_container:
|
||||
file_type = file_type_item.text()
|
||||
description = description_item.text() if description_item else ""
|
||||
color_widget = color_widget_container.findChild(QLineEdit)
|
||||
if color_widget:
|
||||
color = color_widget.text()
|
||||
new_definitions[file_type] = {"description": description, "color": color}
|
||||
new_settings[section_key][list_key] = new_definitions
|
||||
elif list_key == "IMAGE_RESOLUTIONS":
|
||||
new_resolutions = []
|
||||
for row in range(widget.rowCount()):
|
||||
width_item = widget.item(row, 0)
|
||||
height_item = widget.item(row, 1)
|
||||
if width_item and height_item:
|
||||
try:
|
||||
width = int(width_item.text())
|
||||
height = int(height_item.text())
|
||||
new_resolutions.append([width, height])
|
||||
except ValueError:
|
||||
print(f"Warning: Invalid resolution value at row {row}")
|
||||
new_settings[section_key][list_key] = new_resolutions
|
||||
elif list_key == "MAP_BIT_DEPTH_RULES":
|
||||
new_rules = []
|
||||
for row in range(widget.rowCount()):
|
||||
pattern_item = widget.item(row, 0)
|
||||
bit_depth_item = widget.item(row, 1)
|
||||
if pattern_item and bit_depth_item:
|
||||
try:
|
||||
bit_depth = int(bit_depth_item.text())
|
||||
new_rules.append({"pattern": pattern_item.text(), "bit_depth": bit_depth})
|
||||
except ValueError:
|
||||
print(f"Warning: Invalid bit depth value at row {row}")
|
||||
new_settings[section_key][list_key] = new_rules
|
||||
|
||||
# Handle Map Merge Rules (more complex)
|
||||
# This requires reading from the details form for the currently selected item
|
||||
# and updating the corresponding dictionary in the original list stored in self.widgets
|
||||
elif key == "IMAGE_PROCESSING_SETTINGS.MAP_MERGE_RULES":
|
||||
# The original list is stored in self.widgets["IMAGE_PROCESSING_SETTINGS.MAP_MERGE_RULES"]
|
||||
# We need to iterate through the list widget items and update the corresponding
|
||||
# dictionary in the list based on the details form if that item was selected and edited.
|
||||
# A simpler approach for now is to just read the currently displayed rule details
|
||||
# and update the corresponding item in the list widget's data, then reconstruct the list.
|
||||
|
||||
# Reconstruct the list from the list widget items' data
|
||||
new_merge_rules = []
|
||||
for i in range(self.merge_rules_list.count()):
|
||||
item = self.merge_rules_list.item(i)
|
||||
rule_data = item.data(Qt.UserRole)
|
||||
if rule_data:
|
||||
# If this item is the currently selected one, update its data from the details widgets
|
||||
if item == self.merge_rules_list.currentItem():
|
||||
updated_rule_data = {}
|
||||
for detail_key, detail_widget in self.merge_rule_widgets.items():
|
||||
if isinstance(detail_widget, QLineEdit):
|
||||
updated_rule_data[detail_key] = detail_widget.text()
|
||||
elif isinstance(detail_widget, QSpinBox):
|
||||
updated_rule_data[detail_key] = detail_widget.value()
|
||||
elif isinstance(detail_widget, QDoubleSpinBox):
|
||||
updated_rule_data[detail_key] = detail_widget.value()
|
||||
elif isinstance(detail_widget, QCheckBox):
|
||||
updated_rule_data[detail_key] = detail_widget.isChecked()
|
||||
# Add handling for other widget types in details form if needed
|
||||
# Merge updated data with original data (in case some fields weren't in details form)
|
||||
rule_data.update(updated_rule_data)
|
||||
new_merge_rules.append(rule_data)
|
||||
|
||||
# Update the new_settings dictionary with the reconstructed list
|
||||
keys = key.split('.')
|
||||
if len(keys) == 2:
|
||||
section_key = keys[0]
|
||||
list_key = keys[1]
|
||||
if section_key in new_settings and list_key in new_settings[section_key]:
|
||||
new_settings[section_key][list_key] = new_merge_rules
|
||||
|
||||
|
||||
# Save the new settings
|
||||
try:
|
||||
save_base_config(new_settings)
|
||||
QMessageBox.information(self, "Settings Saved", "Configuration saved successfully.\nRestart the application to apply changes.")
|
||||
self.accept() # Close the dialog
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Saving Error", f"Failed to save configuration: {e}")
|
||||
|
||||
# Example usage (for testing the dialog independently)
|
||||
if __name__ == '__main__':
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
import sys
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
dialog = ConfigEditorDialog()
|
||||
dialog.exec_()
|
||||
sys.exit(app.exec_())
|
||||
@@ -2,7 +2,16 @@
|
||||
from PySide6.QtWidgets import QStyledItemDelegate, QLineEdit, QComboBox
|
||||
from PySide6.QtCore import Qt, QModelIndex
|
||||
# Import the new config dictionaries
|
||||
from config import ASSET_TYPE_DEFINITIONS, FILE_TYPE_DEFINITIONS
|
||||
from configuration import load_base_config # Import load_base_config
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os # Added for path manipulation if needed, though json.dump handles creation
|
||||
from PySide6.QtWidgets import QCompleter # Added QCompleter
|
||||
|
||||
# Configure logging
|
||||
log = logging.getLogger(__name__)
|
||||
SUPPLIERS_CONFIG_PATH = "config/suppliers.json"
|
||||
|
||||
class LineEditDelegate(QStyledItemDelegate):
|
||||
"""Delegate for editing string values using a QLineEdit."""
|
||||
@@ -44,10 +53,16 @@ class ComboBoxDelegate(QStyledItemDelegate):
|
||||
|
||||
# Populate based on column using keys from config dictionaries
|
||||
items_keys = None
|
||||
if column == 2: # Asset-Type Override (AssetRule)
|
||||
items_keys = list(ASSET_TYPE_DEFINITIONS.keys())
|
||||
elif column == 4: # Item-Type Override (FileRule)
|
||||
items_keys = list(FILE_TYPE_DEFINITIONS.keys())
|
||||
try:
|
||||
base_config = load_base_config() # Load base config
|
||||
if column == 2: # Asset-Type Override (AssetRule)
|
||||
items_keys = list(base_config.get('ASSET_TYPE_DEFINITIONS', {}).keys()) # Access from base_config
|
||||
elif column == 4: # Item-Type Override (FileRule)
|
||||
items_keys = list(base_config.get('FILE_TYPE_DEFINITIONS', {}).keys()) # Access from base_config
|
||||
except Exception as e:
|
||||
log.error(f"Error loading base config for ComboBoxDelegate: {e}")
|
||||
items_keys = [] # Fallback to empty list on error
|
||||
|
||||
|
||||
if items_keys:
|
||||
for item_key in sorted(items_keys): # Sort keys alphabetically for consistency
|
||||
@@ -88,16 +103,6 @@ class ComboBoxDelegate(QStyledItemDelegate):
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
# Ensures the editor widget is placed correctly within the cell.
|
||||
editor.setGeometry(option.rect)
|
||||
# gui/delegates.py - New content to insert
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os # Added for path manipulation if needed, though json.dump handles creation
|
||||
from PySide6.QtWidgets import QCompleter # Added QCompleter
|
||||
|
||||
# Configure logging
|
||||
log = logging.getLogger(__name__)
|
||||
SUPPLIERS_CONFIG_PATH = "config/suppliers.json"
|
||||
|
||||
class SupplierSearchDelegate(QStyledItemDelegate):
|
||||
"""
|
||||
|
||||
@@ -39,22 +39,24 @@ if str(project_root) not in sys.path:
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
try:
|
||||
from configuration import Configuration, ConfigurationError
|
||||
from configuration import Configuration, ConfigurationError, load_base_config # Import Configuration and load_base_config
|
||||
from asset_processor import AssetProcessor, AssetProcessingError
|
||||
# from gui.processing_handler import ProcessingHandler # REMOVED Obsolete Handler
|
||||
from gui.prediction_handler import PredictionHandler
|
||||
import config as core_config # Import the config module
|
||||
# Removed: import config as core_config # Import the config module
|
||||
# PresetEditorDialog is no longer needed
|
||||
except ImportError as e:
|
||||
print(f"ERROR: Failed to import backend modules: {e}")
|
||||
print(f"Ensure GUI is run from project root or backend modules are in PYTHONPATH.")
|
||||
Configuration = None
|
||||
load_base_config = None # Set to None if import fails
|
||||
ConfigurationError = Exception
|
||||
AssetProcessor = None
|
||||
# ProcessingHandler = None # REMOVED Obsolete Handler
|
||||
PredictionHandler = None
|
||||
ConfigurationError = Exception
|
||||
AssetProcessingError = Exception
|
||||
|
||||
|
||||
# --- Constants ---
|
||||
PRESETS_DIR = project_root / "presets"
|
||||
TEMPLATE_PATH = PRESETS_DIR / "_template.json"
|
||||
@@ -75,7 +77,7 @@ class QtLogHandler(logging.Handler, QObject):
|
||||
log_record_received = Signal(str) # Signal emitting the formatted log string
|
||||
|
||||
def __init__(self, parent=None):
|
||||
logging.Handler.__init__(self)
|
||||
logging.Handler.__init__(self) # Call parent Handler init (level is optional)
|
||||
QObject.__init__(self, parent) # Initialize QObject part
|
||||
|
||||
def emit(self, record):
|
||||
@@ -373,16 +375,23 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# --- Set Initial Output Path ---
|
||||
try:
|
||||
output_base_dir_config = getattr(core_config, 'OUTPUT_BASE_DIR', '../Asset_Processor_Output') # Default if not found
|
||||
# Use load_base_config to get the default output directory
|
||||
base_config = load_base_config()
|
||||
output_base_dir_config = base_config.get('OUTPUT_BASE_DIR', '../Asset_Processor_Output') # Default if not found
|
||||
# Resolve the path relative to the project root
|
||||
default_output_dir = (project_root / output_base_dir_config).resolve()
|
||||
self.output_path_edit.setText(str(default_output_dir))
|
||||
log.info(f"Default output directory set to: {default_output_dir}")
|
||||
except ConfigurationError as e:
|
||||
log.error(f"Error reading base configuration for default output directory: {e}")
|
||||
self.output_path_edit.setText("") # Clear on error
|
||||
self.statusBar().showMessage(f"Error setting default output path: {e}", 5000)
|
||||
except Exception as e:
|
||||
log.error(f"Error setting default output directory: {e}")
|
||||
log.exception(f"Error setting default output directory: {e}")
|
||||
self.output_path_edit.setText("") # Clear on error
|
||||
self.statusBar().showMessage(f"Error setting default output path: {e}", 5000)
|
||||
|
||||
|
||||
# --- Drag and Drop Area ---
|
||||
self.drag_drop_area = QFrame()
|
||||
self.drag_drop_area.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
@@ -466,13 +475,18 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# Initialize paths from config
|
||||
try:
|
||||
default_ng_path = getattr(core_config, 'DEFAULT_NODEGROUP_BLEND_PATH', '')
|
||||
default_mat_path = getattr(core_config, 'DEFAULT_MATERIALS_BLEND_PATH', '')
|
||||
# Use load_base_config to get default Blender paths
|
||||
base_config = load_base_config()
|
||||
default_ng_path = base_config.get('DEFAULT_NODEGROUP_BLEND_PATH', '')
|
||||
default_mat_path = base_config.get('DEFAULT_MATERIALS_BLEND_PATH', '')
|
||||
self.nodegroup_blend_path_input.setText(default_ng_path if default_ng_path else "")
|
||||
self.materials_blend_path_input.setText(default_mat_path if default_mat_path else "")
|
||||
except ConfigurationError as e:
|
||||
log.error(f"Error reading base configuration for default Blender paths: {e}")
|
||||
except Exception as e:
|
||||
log.error(f"Error reading default Blender paths from config: {e}")
|
||||
|
||||
|
||||
# Disable Blender controls initially if checkbox is unchecked
|
||||
self.nodegroup_blend_path_input.setEnabled(False)
|
||||
self.browse_nodegroup_blend_button.setEnabled(False)
|
||||
@@ -1001,14 +1015,22 @@ class MainWindow(QMainWindow):
|
||||
self._finalize_model_update()
|
||||
else:
|
||||
# Update status about remaining items
|
||||
remaining_count = len(self._pending_predictions)
|
||||
self.statusBar().showMessage(f"Prediction failed/finished for {Path(input_path).name}. Waiting for {remaining_count} more...", 5000)
|
||||
completed_count = len(self._accumulated_rules)
|
||||
pending_count = len(self._pending_predictions)
|
||||
# total_count = completed_count + pending_count # This might be slightly off if some failed without rules
|
||||
# We don't have the total count of *requested* predictions here easily,
|
||||
# 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"Prediction finished for {Path(input_path).name}. Waiting for {pending_count} more ({completed_count}/{total_requested} requested)..."
|
||||
self.statusBar().showMessage(status_msg, 5000)
|
||||
log.debug(status_msg)
|
||||
else:
|
||||
log.debug(f"Prediction finished for '{input_path}', which was already processed.")
|
||||
|
||||
# Original status message might be misleading now, handled by accumulation logic.
|
||||
# self.statusBar().showMessage("Preview updated.", 3000) # Removed
|
||||
|
||||
|
||||
@Slot(str, str, str)
|
||||
def update_file_status(self, input_path_str, status, message):
|
||||
# TODO: Update status bar or potentially find rows in table later
|
||||
@@ -1220,17 +1242,18 @@ class MainWindow(QMainWindow):
|
||||
self.editor_list_model_patterns.addItems(category_rules.get("model_patterns", []))
|
||||
self.editor_list_decal_keywords.clear()
|
||||
self.editor_list_decal_keywords.addItems(category_rules.get("decal_keywords", []))
|
||||
self.editor_table_archetype_rules.setRowCount(0)
|
||||
arch_rules = preset_data.get("archetype_rules", [])
|
||||
for i, rule in enumerate(arch_rules):
|
||||
if isinstance(rule, (list, tuple)) and len(rule) == 2:
|
||||
arch_name, conditions = rule
|
||||
match_any = ", ".join(conditions.get("match_any", []))
|
||||
match_all = ", ".join(conditions.get("match_all", []))
|
||||
self.editor_table_archetype_rules.insertRow(i)
|
||||
self.editor_table_archetype_rules.setItem(i, 0, QTableWidgetItem(arch_name))
|
||||
self.editor_table_archetype_rules.setItem(i, 1, QTableWidgetItem(match_any))
|
||||
self.editor_table_archetype_rules.setItem(i, 2, QTableWidgetItem(match_all))
|
||||
preset_data["asset_category_rules"] = category_rules
|
||||
arch_rules = []
|
||||
for r in range(self.editor_table_archetype_rules.rowCount()):
|
||||
name_item = self.editor_table_archetype_rules.item(r, 0)
|
||||
any_item = self.editor_table_archetype_rules.item(r, 1)
|
||||
all_item = self.editor_table_archetype_rules.item(r, 2)
|
||||
if name_item and any_item and all_item:
|
||||
match_any = [k.strip() for k in any_item.text().split(',') if k.strip()]
|
||||
match_all = [k.strip() for k in all_item.text().split(',') if k.strip()]
|
||||
arch_rules.append([name_item.text().strip(), {"match_any": match_any, "match_all": match_all}])
|
||||
preset_data["archetype_rules"] = arch_rules
|
||||
return preset_data
|
||||
finally:
|
||||
self._is_loading_editor = False
|
||||
|
||||
@@ -1424,15 +1447,17 @@ class MainWindow(QMainWindow):
|
||||
QMessageBox.critical(self, "Error", f"Could not load template preset file:\n{TEMPLATE_PATH}\n\nError: {e}")
|
||||
self._clear_editor()
|
||||
self.setWindowTitle("Asset Processor Tool - New Preset*")
|
||||
self.editor_supplier_name.setText("MySupplier") # Set a default supplier name
|
||||
else:
|
||||
log.warning("Presets/_template.json not found. Creating empty preset.")
|
||||
self.setWindowTitle("Asset Processor Tool - New Preset*")
|
||||
self.editor_preset_name.setText("NewPreset")
|
||||
self.editor_supplier_name.setText("MySupplier")
|
||||
self.editor_supplier_name.setText("MySupplier") # Set a default supplier name
|
||||
self._set_editor_enabled(True)
|
||||
self.editor_unsaved_changes = True
|
||||
self.editor_save_button.setEnabled(True)
|
||||
|
||||
|
||||
def _delete_selected_preset(self):
|
||||
"""Deletes the currently selected preset file from the editor list after confirmation."""
|
||||
current_item = self.editor_preset_list.currentItem()
|
||||
@@ -1455,8 +1480,22 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# --- Menu Bar Setup ---
|
||||
def setup_menu_bar(self):
|
||||
"""Creates the main menu bar and View menu."""
|
||||
"""Creates the main menu bar and adds menus/actions."""
|
||||
self.menu_bar = self.menuBar()
|
||||
|
||||
# --- File Menu (Optional, add if needed later) ---
|
||||
# file_menu = self.menu_bar.addMenu("&File")
|
||||
# Add actions like New, Open, Save, Exit
|
||||
|
||||
# --- Edit Menu ---
|
||||
edit_menu = self.menu_bar.addMenu("&Edit")
|
||||
|
||||
# Preferences/Settings Action
|
||||
self.preferences_action = QAction("&Preferences...", self)
|
||||
self.preferences_action.triggered.connect(self._open_config_editor)
|
||||
edit_menu.addAction(self.preferences_action)
|
||||
|
||||
# --- View Menu ---
|
||||
view_menu = self.menu_bar.addMenu("&View")
|
||||
|
||||
# Log Console Action
|
||||
@@ -1495,6 +1534,23 @@ class MainWindow(QMainWindow):
|
||||
log.info("UI Log Handler Initialized.") # Log that the handler is ready
|
||||
|
||||
# --- Slots for Menu Actions and Logging ---
|
||||
@Slot()
|
||||
def _open_config_editor(self):
|
||||
"""Opens the configuration editor dialog."""
|
||||
log.debug("Opening configuration editor dialog.")
|
||||
try:
|
||||
from .config_editor_dialog import ConfigEditorDialog # Import locally to avoid circular dependency if needed
|
||||
dialog = ConfigEditorDialog(self)
|
||||
dialog.exec_() # Use exec_() to run as a modal dialog
|
||||
log.debug("Configuration editor dialog closed.")
|
||||
except ImportError:
|
||||
log.error("Failed to import ConfigEditorDialog. Ensure gui/config_editor_dialog.py exists and is accessible.")
|
||||
QMessageBox.critical(self, "Error", "Could not open configuration editor.\nRequired file not found or has errors.")
|
||||
except Exception as e:
|
||||
log.exception(f"Error opening configuration editor dialog: {e}")
|
||||
QMessageBox.critical(self, "Error", f"An error occurred while opening the configuration editor:\n{e}")
|
||||
|
||||
|
||||
@Slot(bool)
|
||||
def _toggle_log_console_visibility(self, checked):
|
||||
"""Shows or hides the log console widget based on menu action."""
|
||||
@@ -1604,8 +1660,11 @@ class MainWindow(QMainWindow):
|
||||
# Update status bar with progress
|
||||
completed_count = len(self._accumulated_rules)
|
||||
pending_count = len(self._pending_predictions)
|
||||
total_count = completed_count + pending_count # This might be slightly off if some failed without rules
|
||||
status_msg = f"Preview updated for {Path(input_path).name}. Waiting for {pending_count} more ({completed_count}/{total_count} requested)..."
|
||||
# total_count = completed_count + pending_count # This might be slightly off if some failed without rules
|
||||
# We don't have the total count of *requested* predictions here easily,
|
||||
# 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)
|
||||
log.debug(status_msg)
|
||||
|
||||
|
||||
@@ -21,23 +21,24 @@ if str(project_root) not in sys.path:
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
try:
|
||||
from configuration import Configuration, ConfigurationError
|
||||
from configuration import Configuration, ConfigurationError, load_base_config # Import Configuration, ConfigurationError, and load_base_config
|
||||
# AssetProcessor might not be needed directly anymore if logic is moved here
|
||||
# from asset_processor import AssetProcessor, AssetProcessingError
|
||||
from rule_structure import SourceRule, AssetRule, FileRule # Removed AssetType, ItemType
|
||||
import config as app_config # Import project's config module
|
||||
# Import the new dictionaries directly for easier access
|
||||
from config import ASSET_TYPE_DEFINITIONS, FILE_TYPE_DEFINITIONS
|
||||
# Removed: import config as app_config # Import project's config module
|
||||
# Removed: Import the new dictionaries directly for easier access
|
||||
# Removed: from config import ASSET_TYPE_DEFINITIONS, FILE_TYPE_DEFINITIONS
|
||||
BACKEND_AVAILABLE = True
|
||||
except ImportError as e:
|
||||
print(f"ERROR (PredictionHandler): Failed to import backend/config modules: {e}")
|
||||
# Define placeholders if imports fail
|
||||
Configuration = None
|
||||
# AssetProcessor = None
|
||||
load_base_config = None # Placeholder
|
||||
ConfigurationError = Exception
|
||||
# AssetProcessingError = Exception
|
||||
SourceRule, AssetRule, FileRule, AssetType, ItemType = (None,)*5 # Placeholder for rule structures
|
||||
app_config = None # Placeholder for config
|
||||
SourceRule, AssetRule, FileRule = (None,)*3 # Placeholder for rule structures
|
||||
# Removed: AssetType, ItemType = (None,)*2 # Placeholder for types
|
||||
# Removed: app_config = None # Placeholder for config
|
||||
BACKEND_AVAILABLE = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -76,8 +77,9 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis
|
||||
if not file_list or not config:
|
||||
log.warning("Classification skipped: Missing file list or config.")
|
||||
return {}
|
||||
# Access compiled regex directly from the config object
|
||||
if not hasattr(config, 'compiled_map_keyword_regex') or not config.compiled_map_keyword_regex:
|
||||
log.warning("Classification skipped: Missing compiled map keyword regex.")
|
||||
log.warning("Classification skipped: Missing compiled map keyword regex in config.")
|
||||
# Don't return yet, might still find extras
|
||||
if not hasattr(config, 'compiled_extra_regex'):
|
||||
log.warning("Configuration object missing 'compiled_extra_regex'. Cannot classify extra files.")
|
||||
@@ -119,12 +121,14 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis
|
||||
# Access the full rule details directly from the config's map_type_mapping list using the index
|
||||
matched_rule_details = None
|
||||
try:
|
||||
matched_rule_details = config.map_type_mapping[rule_index] # Access rule by index
|
||||
# Access map_type_mapping using the property
|
||||
map_type_mapping_list = config.map_type_mapping # Use the property
|
||||
matched_rule_details = map_type_mapping_list[rule_index] # Access rule by index
|
||||
is_gloss_flag = matched_rule_details.get('is_gloss_source', False) # Get flag or default False
|
||||
log.debug(f" Associated rule details: {matched_rule_details}")
|
||||
log.debug(f" 'is_gloss_source' flag from rule: {is_gloss_flag}")
|
||||
except IndexError:
|
||||
log.warning(f" Could not access map_type_mapping rule at index {rule_index}. Cannot determine 'is_gloss_source' flag.")
|
||||
log.warning(f" Could not access map_type_mapping rule at index {rule_index} in config.settings. Cannot determine 'is_gloss_source' flag.")
|
||||
is_gloss_flag = False # Default if rule cannot be accessed
|
||||
# --- End DEBUG LOG ---
|
||||
matched_item_type = target_type # The standard type (e.g., MAP_COL)
|
||||
@@ -269,19 +273,19 @@ class PredictionHandler(QObject):
|
||||
self.status_message.emit(f"Analyzing '{source_path.name}'...", 0)
|
||||
|
||||
config: Configuration | None = None
|
||||
asset_type_definitions: Dict[str, Dict] = {}
|
||||
file_type_definitions: Dict[str, Dict] = {} # These are ItemType names
|
||||
# Removed: asset_type_definitions: Dict[str, Dict] = {}
|
||||
# Removed: file_type_definitions: Dict[str, Dict] = {} # These are ItemType names
|
||||
|
||||
try:
|
||||
config = Configuration(preset_name)
|
||||
# Load allowed types from the project's config module (now dictionaries)
|
||||
if app_config:
|
||||
asset_type_definitions = getattr(app_config, 'ASSET_TYPE_DEFINITIONS', {})
|
||||
file_type_definitions = getattr(app_config, 'FILE_TYPE_DEFINITIONS', {})
|
||||
log.debug(f"Loaded AssetType Definitions: {list(asset_type_definitions.keys())}")
|
||||
log.debug(f"Loaded FileType Definitions (ItemTypes): {list(file_type_definitions.keys())}")
|
||||
else:
|
||||
log.warning("Project config module not loaded. Cannot get type definitions.")
|
||||
# Removed: Load allowed types from the project's config module (now dictionaries)
|
||||
# Removed: if app_config:
|
||||
# Removed: asset_type_definitions = getattr(app_config, 'ASSET_TYPE_DEFINITIONS', {})
|
||||
# Removed: file_type_definitions = getattr(app_config, 'FILE_TYPE_DEFINITIONS', {})
|
||||
# Removed: log.debug(f"Loaded AssetType Definitions: {list(asset_type_definitions.keys())}")
|
||||
# Removed: log.debug(f"Loaded FileType Definitions (ItemTypes): {list(file_type_definitions.keys())}")
|
||||
# Removed: else:
|
||||
# Removed: log.warning("Project config module not loaded. Cannot get type definitions.")
|
||||
|
||||
except ConfigurationError as e:
|
||||
log.error(f"Failed to load configuration for preset '{preset_name}': {e}")
|
||||
@@ -331,6 +335,10 @@ class PredictionHandler(QObject):
|
||||
log.debug(f"Created SourceRule for identifier: {input_source_identifier} with supplier: {supplier_identifier}")
|
||||
|
||||
asset_rules = []
|
||||
# Get allowed asset types from config's internal core settings
|
||||
asset_type_definitions = config._core_settings.get('ASSET_TYPE_DEFINITIONS', {})
|
||||
log.debug(f"Loaded AssetType Definitions from config: {list(asset_type_definitions.keys())}")
|
||||
|
||||
for asset_name, files_info in classified_assets.items():
|
||||
if not files_info: continue # Skip empty asset groups
|
||||
|
||||
@@ -349,9 +357,10 @@ class PredictionHandler(QObject):
|
||||
# Ensure the predicted type is allowed, fallback if necessary
|
||||
# Now predicted_asset_type is already a string
|
||||
if asset_type_definitions and predicted_asset_type not in asset_type_definitions:
|
||||
log.warning(f"Predicted AssetType '{predicted_asset_type}' for asset '{asset_name}' is not in ASSET_TYPE_DEFINITIONS. Falling back.")
|
||||
log.warning(f"Predicted AssetType '{predicted_asset_type}' for asset '{asset_name}' is not in ASSET_TYPE_DEFINITIONS from config. Falling back.")
|
||||
# Fallback logic: use the default from config if allowed, else first allowed type
|
||||
default_type = getattr(app_config, 'DEFAULT_ASSET_CATEGORY', 'Surface')
|
||||
# Access DEFAULT_ASSET_CATEGORY using the property
|
||||
default_type = config.default_asset_category # Use the property
|
||||
if default_type in asset_type_definitions:
|
||||
predicted_asset_type = default_type
|
||||
elif asset_type_definitions:
|
||||
@@ -368,20 +377,38 @@ class PredictionHandler(QObject):
|
||||
log.debug(f"Created AssetRule for asset: {asset_name} with type: {predicted_asset_type}")
|
||||
|
||||
file_rules = []
|
||||
# Get allowed file types from config's internal core settings
|
||||
file_type_definitions = config._core_settings.get('FILE_TYPE_DEFINITIONS', {})
|
||||
log.debug(f"Loaded FileType Definitions (ItemTypes) from config: {list(file_type_definitions.keys())}")
|
||||
|
||||
for file_info in files_info:
|
||||
# Determine FileRule level overrides/defaults
|
||||
base_item_type = file_info['item_type'] # Type from classification (e.g., COL, NRM, EXTRA)
|
||||
target_asset_name_override = file_info['asset_name'] # From classification
|
||||
|
||||
# Retrieve the standard_type from the config if available
|
||||
standard_map_type = None
|
||||
file_type_details = file_type_definitions.get(base_item_type)
|
||||
if file_type_details:
|
||||
standard_map_type = file_type_details.get('standard_type') # Try to get explicit standard_type
|
||||
|
||||
# If standard_type wasn't found in the definition, use the base_item_type itself
|
||||
# (which is the alias in presets like Poliigon.json)
|
||||
if standard_map_type is None and base_item_type in file_type_definitions: # Check base_item_type is a valid key
|
||||
log.debug(f" No explicit 'standard_type' found for item type '{base_item_type}'. Using base_item_type itself as standard_map_type.")
|
||||
standard_map_type = base_item_type # Fallback to using the base type (alias)
|
||||
elif standard_map_type is None:
|
||||
log.debug(f" No 'standard_type' found and base_item_type '{base_item_type}' not in definitions. Setting standard_map_type to None.")
|
||||
|
||||
# Determine the final item_type string (prefix maps, check if allowed)
|
||||
final_item_type = base_item_type # Start with the base type
|
||||
if not base_item_type.startswith("MAP_") and base_item_type not in ["FILE_IGNORE", "EXTRA", "MODEL"]:
|
||||
# Prefix map types that don't already have it
|
||||
final_item_type = f"MAP_{base_item_type}"
|
||||
|
||||
# Check if the final type is allowed (exists as a key in config)
|
||||
# Check if the final type is allowed (exists as a key in config settings)
|
||||
if file_type_definitions and final_item_type not in file_type_definitions and base_item_type not in ["FILE_IGNORE", "EXTRA"]:
|
||||
log.warning(f"Predicted ItemType '{base_item_type}' (checked as '{final_item_type}') for file '{file_info['file_path']}' is not in FILE_TYPE_DEFINITIONS. Setting base type to FILE_IGNORE.")
|
||||
log.warning(f"Predicted ItemType '{base_item_type}' (checked as '{final_item_type}') for file '{file_info['file_path']}' is not in FILE_TYPE_DEFINITIONS from config. Setting base type to FILE_IGNORE.")
|
||||
final_item_type = "FILE_IGNORE" # Fallback base type to FILE_IGNORE string
|
||||
|
||||
# Output format is determined by the engine, not predicted here. Leave as None.
|
||||
@@ -394,12 +421,37 @@ class PredictionHandler(QObject):
|
||||
log.debug(f" Base Item Type (from classification): {base_item_type}")
|
||||
log.debug(f" Final Item Type (for model): {final_item_type}")
|
||||
log.debug(f" Target Asset Name Override: {target_asset_name_override}")
|
||||
# --- DETAILED DEBUG LOG: Inspect standard_map_type assignment ---
|
||||
log.debug(f" DEBUG: Processing file: {file_info['file_path']}")
|
||||
log.debug(f" DEBUG: base_item_type = {base_item_type}")
|
||||
log.debug(f" DEBUG: file_type_definitions keys = {list(file_type_definitions.keys())}")
|
||||
# --- Fix: Use final_item_type (prefixed) for lookup, fallback to base_item_type (alias) ---
|
||||
standard_map_type = None
|
||||
# Use final_item_type (e.g., "MAP_AO") for the lookup
|
||||
file_type_details = file_type_definitions.get(final_item_type)
|
||||
log.debug(f" DEBUG: file_type_definitions.get({final_item_type}) = {file_type_details}") # Log lookup result
|
||||
if file_type_details:
|
||||
# Try to get explicit standard_type (might still be missing in some presets)
|
||||
standard_map_type = file_type_details.get('standard_type')
|
||||
log.debug(f" DEBUG: Explicit standard_type from details = {standard_map_type}")
|
||||
|
||||
# If standard_type wasn't found in the definition, use the base_item_type (alias)
|
||||
# This handles presets like Poliigon.json where the alias is the target_type
|
||||
if standard_map_type is None and final_item_type in file_type_definitions: # Check if the prefixed type was valid
|
||||
log.debug(f" No explicit 'standard_type' found for item type '{final_item_type}'. Using base_item_type ('{base_item_type}') as standard_map_type.")
|
||||
standard_map_type = base_item_type # Fallback to using the base type (alias)
|
||||
elif standard_map_type is None:
|
||||
log.debug(f" Could not determine standard_map_type for base '{base_item_type}' / final '{final_item_type}'. Setting to None.")
|
||||
# --- End Fix ---
|
||||
log.debug(f" DEBUG: Final standard_map_type variable value = {standard_map_type}") # Log final value
|
||||
# --- END DETAILED DEBUG LOG ---
|
||||
# Explicitly check and log the flag value from file_info
|
||||
is_gloss_source_value = file_info.get('is_gloss_source', 'MISSING') # Get value or 'MISSING'
|
||||
log.debug(f" Value for 'is_gloss_source' from file_info: {is_gloss_source_value}")
|
||||
# --- End DEBUG LOG ---
|
||||
|
||||
# Pass the retrieved flag value to the constructor
|
||||
|
||||
# Pass the retrieved flag value and standard_map_type to the constructor
|
||||
file_rule = FileRule(
|
||||
file_path=file_info['file_path'], # This is static info based on input
|
||||
item_type=final_item_type, # Set the new base item_type field
|
||||
@@ -409,6 +461,7 @@ class PredictionHandler(QObject):
|
||||
target_asset_name_override=target_asset_name_override,
|
||||
output_format_override=output_format_override,
|
||||
is_gloss_source=is_gloss_source_value if isinstance(is_gloss_source_value, bool) else False, # Pass the flag, ensure boolean
|
||||
standard_map_type=standard_map_type, # Assign the determined standard_map_type
|
||||
# --- Leave Static Fields as Default/None ---
|
||||
resolution_override=None,
|
||||
channel_merge_instructions={},
|
||||
|
||||
@@ -26,15 +26,16 @@ try:
|
||||
# Import the worker function from main.py
|
||||
from main import process_single_asset_wrapper
|
||||
# Import exceptions if needed for type hinting or specific handling
|
||||
from configuration import ConfigurationError
|
||||
from configuration import ConfigurationError, load_base_config # Import ConfigurationError and load_base_config
|
||||
from asset_processor import AssetProcessingError
|
||||
import config as core_config # <<< ADDED IMPORT
|
||||
# Removed: import config as core_config # <<< ADDED IMPORT
|
||||
BACKEND_AVAILABLE = True
|
||||
except ImportError as e:
|
||||
print(f"ERROR (ProcessingHandler): Failed to import backend modules/worker: {e}")
|
||||
# Define placeholders if imports fail, so the GUI doesn't crash immediately
|
||||
process_single_asset_wrapper = None
|
||||
ConfigurationError = Exception
|
||||
load_base_config = None # Placeholder
|
||||
AssetProcessingError = Exception
|
||||
BACKEND_AVAILABLE = False
|
||||
|
||||
@@ -70,7 +71,11 @@ class ProcessingHandler(QObject):
|
||||
def is_running(self):
|
||||
return self._is_running
|
||||
|
||||
def run_processing(self, input_paths: list[str], preset_name: str, output_dir_str: str, overwrite: bool, num_workers: int,
|
||||
# Removed _predict_single_asset method
|
||||
|
||||
@Slot(str, list, str, str, bool, int,
|
||||
bool, str, str, bool, SourceRule) # Explicitly define types for the slot
|
||||
def run_processing(self, input_source_identifier: str, original_input_paths: list[str], preset_name: str, output_dir_str: str, overwrite: bool, num_workers: int,
|
||||
run_blender: bool, nodegroup_blend_path: str, materials_blend_path: str, verbose: bool, rules: SourceRule): # <<< ADDED verbose PARAM
|
||||
"""
|
||||
Starts the asset processing task and optionally runs Blender scripts afterwards.
|
||||
@@ -84,13 +89,13 @@ class ProcessingHandler(QObject):
|
||||
if not BACKEND_AVAILABLE or not process_single_asset_wrapper:
|
||||
log.error("Backend modules or worker function not available. Cannot start processing.")
|
||||
self.status_message.emit("Error: Backend components missing. Cannot process.", 5000)
|
||||
self.processing_finished.emit(0, 0, len(input_paths)) # Emit finished with all failed
|
||||
self.processing_finished.emit(0, 0, len(original_input_paths)) # Emit finished with all failed
|
||||
return
|
||||
|
||||
self._is_running = True
|
||||
self._cancel_requested = False
|
||||
self._futures = {} # Reset futures
|
||||
total_files = len(input_paths)
|
||||
total_files = len(original_input_paths) # Use original_input_paths for total count
|
||||
processed_count = 0
|
||||
skipped_count = 0
|
||||
failed_count = 0
|
||||
@@ -105,9 +110,20 @@ class ProcessingHandler(QObject):
|
||||
self._executor = executor # Store for potential cancellation
|
||||
|
||||
# Submit tasks
|
||||
for input_path in input_paths:
|
||||
for input_path in original_input_paths: # Iterate through the list of input paths
|
||||
if self._cancel_requested: break # Check before submitting more
|
||||
log.debug(f"Submitting task for: {input_path}")
|
||||
# Pass the single SourceRule object to the worker
|
||||
# --- DEBUG LOG: Inspect FileRule overrides before sending to worker ---
|
||||
log.debug(f"ProcessingHandler: Inspecting rules for input '{input_path}' before submitting to worker:")
|
||||
if rules: # Check if rules object exists
|
||||
for asset_rule in rules.assets:
|
||||
log.debug(f" Asset: {asset_rule.asset_name}")
|
||||
for file_rule in asset_rule.files:
|
||||
log.debug(f" File: {Path(file_rule.file_path).name}, ItemType: {file_rule.item_type}, Override: {file_rule.item_type_override}, StandardMap: {getattr(file_rule, 'standard_map_type', 'N/A')}")
|
||||
else:
|
||||
log.debug(" Rules object is None.")
|
||||
# --- END DEBUG LOG ---
|
||||
future = executor.submit(process_single_asset_wrapper, input_path, preset_name, output_dir_str, overwrite, verbose=verbose, rules=rules) # Pass verbose flag from GUI and rules
|
||||
self._futures[future] = input_path # Map future back to input path
|
||||
# Optionally emit "processing" status here
|
||||
@@ -288,7 +304,14 @@ class ProcessingHandler(QObject):
|
||||
def _find_blender_executable(self) -> Optional[str]:
|
||||
"""Finds the Blender executable path from config or system PATH."""
|
||||
try:
|
||||
blender_exe_config = getattr(core_config, 'BLENDER_EXECUTABLE_PATH', None)
|
||||
# Use load_base_config to get the Blender executable path
|
||||
if load_base_config:
|
||||
base_config = load_base_config()
|
||||
blender_exe_config = base_config.get('BLENDER_EXECUTABLE_PATH', None)
|
||||
else:
|
||||
blender_exe_config = None
|
||||
log.warning("load_base_config not available. Cannot read BLENDER_EXECUTABLE_PATH from config.")
|
||||
|
||||
if blender_exe_config:
|
||||
p = Path(blender_exe_config)
|
||||
if p.is_file():
|
||||
@@ -306,6 +329,9 @@ class ProcessingHandler(QObject):
|
||||
else:
|
||||
log.warning("Could not find 'blender' in system PATH.")
|
||||
return None
|
||||
except ConfigurationError as e:
|
||||
log.error(f"Error reading base configuration for Blender executable path: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
log.error(f"Error checking Blender executable path: {e}")
|
||||
return None
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# gui/unified_view_model.py
|
||||
import logging # Added for debugging
|
||||
log = logging.getLogger(__name__) # Added for debugging
|
||||
from PySide6.QtCore import QAbstractItemModel, QModelIndex, Qt, Signal # Added Signal
|
||||
from PySide6.QtGui import QColor # Added for background role
|
||||
from pathlib import Path # Added for file_name extraction
|
||||
from rule_structure import SourceRule, AssetRule, FileRule # Removed AssetType, ItemType import
|
||||
from config import ASSET_TYPE_DEFINITIONS, FILE_TYPE_DEFINITIONS # Added for coloring
|
||||
from configuration import load_base_config # Import load_base_config
|
||||
|
||||
class UnifiedViewModel(QAbstractItemModel):
|
||||
# --- Color Constants for Row Backgrounds ---
|
||||
@@ -133,10 +135,10 @@ class UnifiedViewModel(QAbstractItemModel):
|
||||
if not parent.isValid():
|
||||
# Parent is invisible root. Children are SourceRules.
|
||||
if row < len(self._source_rules):
|
||||
child_item = self._source_rules[row]
|
||||
return self.createIndex(row, column, child_item)
|
||||
child_item = self._source_rules[row]
|
||||
return self.createIndex(row, column, child_item)
|
||||
else:
|
||||
return QModelIndex() # Row out of bounds for top-level items
|
||||
return QModelIndex() # Row out of bounds for top-level items
|
||||
else:
|
||||
# Parent is a valid index, get its item
|
||||
parent_item = parent.internalPointer()
|
||||
@@ -181,47 +183,57 @@ class UnifiedViewModel(QAbstractItemModel):
|
||||
# Determine effective asset type
|
||||
asset_type = item.asset_type_override if item.asset_type_override else item.asset_type
|
||||
if asset_type:
|
||||
type_info = ASSET_TYPE_DEFINITIONS.get(asset_type)
|
||||
if type_info:
|
||||
hex_color = type_info.get("color")
|
||||
if hex_color:
|
||||
try:
|
||||
return QColor(hex_color)
|
||||
except ValueError:
|
||||
# Optional: Add logging for invalid hex color
|
||||
# print(f"Warning: Invalid hex color '{hex_color}' for asset type '{asset_type}' in config.")
|
||||
return None # Fallback for invalid hex
|
||||
try:
|
||||
base_config = load_base_config() # Load base config
|
||||
asset_type_definitions = base_config.get('ASSET_TYPE_DEFINITIONS', {}) # Get definitions
|
||||
type_info = asset_type_definitions.get(asset_type)
|
||||
if type_info:
|
||||
hex_color = type_info.get("color")
|
||||
if hex_color:
|
||||
try:
|
||||
return QColor(hex_color)
|
||||
except ValueError:
|
||||
# Optional: Add logging for invalid hex color
|
||||
# print(f"Warning: Invalid hex color '{hex_color}' for asset type '{asset_type}' in config.")
|
||||
return None # Fallback for invalid hex
|
||||
else:
|
||||
# Optional: Add logging for missing color key
|
||||
# print(f"Warning: No color defined for asset type '{asset_type}' in config.")
|
||||
return None # Fallback if color key missing
|
||||
else:
|
||||
# Optional: Add logging for missing color key
|
||||
# print(f"Warning: No color defined for asset type '{asset_type}' in config.")
|
||||
return None # Fallback if color key missing
|
||||
else:
|
||||
# Optional: Add logging for missing asset type definition
|
||||
# print(f"Warning: Asset type '{asset_type}' not found in ASSET_TYPE_DEFINITIONS.")
|
||||
return None # Fallback if type not in config
|
||||
# Optional: Add logging for missing asset type definition
|
||||
# print(f"Warning: Asset type '{asset_type}' not found in ASSET_TYPE_DEFINITIONS.")
|
||||
return None # Fallback if type not in config
|
||||
except Exception: # Catch errors during config loading
|
||||
return None # Fallback on error
|
||||
else:
|
||||
return None # Fallback if no asset_type determined
|
||||
elif isinstance(item, FileRule):
|
||||
# Determine effective item type: Prioritize override, then use base type
|
||||
effective_item_type = item.item_type_override if item.item_type_override is not None else item.item_type
|
||||
if effective_item_type:
|
||||
type_info = FILE_TYPE_DEFINITIONS.get(effective_item_type)
|
||||
if type_info:
|
||||
hex_color = type_info.get("color")
|
||||
if hex_color:
|
||||
try:
|
||||
return QColor(hex_color)
|
||||
except ValueError:
|
||||
# Optional: Add logging for invalid hex color
|
||||
# print(f"Warning: Invalid hex color '{hex_color}' for file type '{item_type}' in config.")
|
||||
return None # Fallback for invalid hex
|
||||
try:
|
||||
base_config = load_base_config() # Load base config
|
||||
file_type_definitions = base_config.get('FILE_TYPE_DEFINITIONS', {}) # Get definitions
|
||||
type_info = file_type_definitions.get(effective_item_type)
|
||||
if type_info:
|
||||
hex_color = type_info.get("color")
|
||||
if hex_color:
|
||||
try:
|
||||
return QColor(hex_color)
|
||||
except ValueError:
|
||||
# Optional: Add logging for invalid hex color
|
||||
# print(f"Warning: Invalid hex color '{hex_color}' for file type '{item_type}' in config.")
|
||||
return None # Fallback for invalid hex
|
||||
else:
|
||||
# Optional: Add logging for missing color key
|
||||
# print(f"Warning: No color defined for file type '{item_type}' in config.")
|
||||
return None # Fallback if color key missing
|
||||
else:
|
||||
# Optional: Add logging for missing color key
|
||||
# print(f"Warning: No color defined for file type '{item_type}' in config.")
|
||||
return None # Fallback if color key missing
|
||||
else:
|
||||
# File types often don't have specific colors, so no warning needed unless debugging
|
||||
return None # Fallback if type not in config
|
||||
# File types often don't have specific colors, so no warning needed unless debugging
|
||||
return None # Fallback if type not in config
|
||||
except Exception: # Catch errors during config loading
|
||||
return None # Fallback on error
|
||||
else:
|
||||
return None # Fallback if no item_type determined
|
||||
else: # Other item types or if item is None
|
||||
@@ -258,7 +270,14 @@ class UnifiedViewModel(QAbstractItemModel):
|
||||
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_ITEM_TYPE:
|
||||
return item.item_type_override if item.item_type_override else ""
|
||||
# Reverted Logic: Display override if set, otherwise base type. Shows prefixed keys.
|
||||
override = item.item_type_override
|
||||
initial_type = item.item_type
|
||||
|
||||
if override is not None:
|
||||
return override
|
||||
else:
|
||||
return initial_type if initial_type else ""
|
||||
if column == self.COL_STATUS: return "" # Status (Not handled yet)
|
||||
if column == self.COL_OUTPUT_PATH: return "" # Output Path (Not handled yet)
|
||||
elif role == Qt.EditRole:
|
||||
@@ -436,8 +455,47 @@ class UnifiedViewModel(QAbstractItemModel):
|
||||
if new_value == "": new_value = None # Treat empty string as None
|
||||
# Update item_type_override
|
||||
if item.item_type_override != new_value:
|
||||
item.item_type_override = new_value
|
||||
changed = True
|
||||
log.debug(f"setData COL_ITEM_TYPE: File='{Path(item.file_path).name}', Original Override='{item.item_type_override}', Original Standard='{getattr(item, 'standard_map_type', 'N/A')}', New Value='{new_value}'") # DEBUG LOG - Added getattr for safety
|
||||
old_override = item.item_type_override # Store old value for logging
|
||||
item.item_type_override = new_value
|
||||
changed = True
|
||||
|
||||
# --- BEGIN FIX: Update standard_map_type ---
|
||||
try:
|
||||
base_config = load_base_config()
|
||||
file_type_definitions = base_config.get('FILE_TYPE_DEFINITIONS', {})
|
||||
|
||||
# Determine the type to look up (override first, then original)
|
||||
type_to_lookup = new_value if new_value is not None else item.item_type
|
||||
|
||||
new_standard_type = None
|
||||
if type_to_lookup:
|
||||
type_info = file_type_definitions.get(type_to_lookup)
|
||||
if type_info:
|
||||
new_standard_type = type_info.get("standard_type")
|
||||
# If standard_type itself is missing in the definition, treat as None or keep old? Let's default to None.
|
||||
if new_standard_type is None:
|
||||
log.warning(f"setData: No 'standard_type' defined for item type '{type_to_lookup}' in FILE_TYPE_DEFINITIONS.")
|
||||
else:
|
||||
log.warning(f"setData: Item type '{type_to_lookup}' not found in FILE_TYPE_DEFINITIONS.")
|
||||
# Fallback: Keep the existing standard_map_type if lookup fails completely
|
||||
new_standard_type = getattr(item, 'standard_map_type', None)
|
||||
else:
|
||||
# If both override and original type are None, standard type should be None
|
||||
new_standard_type = None
|
||||
|
||||
# Update the standard_map_type if it changed or needs setting
|
||||
current_standard_type = getattr(item, 'standard_map_type', None)
|
||||
if current_standard_type != new_standard_type:
|
||||
item.standard_map_type = new_standard_type
|
||||
log.debug(f"setData: Updated standard_map_type from '{current_standard_type}' to '{new_standard_type}' for file '{Path(item.file_path).name}' based on type '{type_to_lookup}'")
|
||||
# No need to set 'changed = True' again, already set above
|
||||
|
||||
except Exception as e:
|
||||
log.exception(f"setData: Error updating standard_map_type for file '{Path(item.file_path).name}': {e}")
|
||||
# --- END FIX ---
|
||||
|
||||
log.debug(f"setData COL_ITEM_TYPE: File='{Path(item.file_path).name}', Final Override='{item.item_type_override}', Final Standard='{getattr(item, 'standard_map_type', 'N/A')}'") # DEBUG LOG - Updated
|
||||
|
||||
|
||||
if changed:
|
||||
|
||||
Reference in New Issue
Block a user