Asset-Frameworker/gui/unified_view_model.py
Rusfort 6971b8189f Data Flow Overhaul
Known regressions in current commit:
- No "extra" files
- GLOSS map does not look corrected
- "override" flag is not respected
2025-05-01 09:13:20 +02:00

319 lines
15 KiB
Python

# gui/unified_view_model.py
from PySide6.QtCore import QAbstractItemModel, QModelIndex, Qt
from pathlib import Path # Added for file_name extraction
from rule_structure import SourceRule, AssetRule, FileRule # Removed AssetType, ItemType import
class UnifiedViewModel(QAbstractItemModel):
"""
A QAbstractItemModel for displaying and editing the hierarchical structure
of SourceRule -> AssetRule -> FileRule.
"""
Columns = [
"Name", "Supplier Override", "Asset-Type Override",
"Target Asset Name Override", "Item-Type Override",
"Status", "Output Path"
]
COL_NAME = 0
COL_SUPPLIER = 1
COL_ASSET_TYPE = 2
COL_TARGET_ASSET = 3
COL_ITEM_TYPE = 4
COL_STATUS = 5
COL_OUTPUT_PATH = 6
def __init__(self, parent=None):
super().__init__(parent)
self._source_rules = [] # Now stores a list of SourceRule objects
def load_data(self, source_rules_list: list): # Accepts a list
"""Loads or reloads the model with a list of SourceRule objects."""
self.beginResetModel()
self._source_rules = source_rules_list if source_rules_list else [] # Assign the new list
# Ensure back-references for parent lookup are set on the NEW items
for source_rule in self._source_rules:
for asset_rule in source_rule.assets:
asset_rule.parent_source = source_rule # Set parent SourceRule
for file_rule in asset_rule.files:
file_rule.parent_asset = asset_rule # Set parent AssetRule
self.endResetModel()
def clear_data(self):
"""Clears the model data."""
self.beginResetModel()
self._source_rules = [] # Clear the list
self.endResetModel()
def get_all_source_rules(self) -> list:
"""Returns the internal list of SourceRule objects."""
return self._source_rules
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
"""Returns the number of rows under the given parent."""
if not parent.isValid():
# Parent is the invisible root. Children are the SourceRules.
return len(self._source_rules)
parent_item = parent.internalPointer()
if isinstance(parent_item, SourceRule):
# Parent is a SourceRule. Children are AssetRules.
return len(parent_item.assets)
elif isinstance(parent_item, AssetRule):
# Parent is an AssetRule. Children are FileRules.
return len(parent_item.files)
elif isinstance(parent_item, FileRule):
return 0 # FileRules have no children
return 0 # Should not happen for valid items
def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
"""Returns the number of columns."""
return len(self.Columns)
def parent(self, index: QModelIndex) -> QModelIndex:
"""Returns the parent of the model item with the given index."""
if not index.isValid():
return QModelIndex()
child_item = index.internalPointer()
if child_item is None:
return QModelIndex()
# Determine the parent based on the item type
if isinstance(child_item, SourceRule):
# Parent is the invisible root
return QModelIndex()
elif isinstance(child_item, AssetRule):
# Parent is a SourceRule. Find its row in the _source_rules list.
parent_item = getattr(child_item, 'parent_source', None)
if parent_item and parent_item in self._source_rules:
try:
parent_row = self._source_rules.index(parent_item)
return self.createIndex(parent_row, 0, parent_item)
except ValueError:
return QModelIndex() # Should not happen if parent_source is correct
else:
return QModelIndex() # Parent SourceRule not found or reference missing
elif isinstance(child_item, FileRule):
# Parent is an AssetRule. Find its row within its parent SourceRule.
parent_item = getattr(child_item, 'parent_asset', None) # Get parent AssetRule
if parent_item:
grandparent_item = getattr(parent_item, 'parent_source', None) # Get the SourceRule
if grandparent_item:
try:
parent_row = grandparent_item.assets.index(parent_item)
# We need the index of the grandparent (SourceRule) to create the parent index
grandparent_row = self._source_rules.index(grandparent_item)
return self.createIndex(parent_row, 0, parent_item) # Create index for the AssetRule parent
except ValueError:
return QModelIndex() # Parent AssetRule or Grandparent SourceRule not found in respective lists
else:
return QModelIndex() # Grandparent (SourceRule) reference missing
else:
return QModelIndex() # Parent AssetRule reference missing
return QModelIndex() # Should not be reached
def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex:
"""Returns the index of the item in the model specified by the given row, column and parent index."""
if not self.hasIndex(row, column, parent):
return QModelIndex()
parent_item = None
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)
else:
return QModelIndex() # Row out of bounds for top-level items
else:
# Parent is a valid index, get its item
parent_item = parent.internalPointer()
child_item = None
if isinstance(parent_item, SourceRule):
# Parent is SourceRule. Children are AssetRules.
if row < len(parent_item.assets):
child_item = parent_item.assets[row]
# Ensure parent reference is set
if not hasattr(child_item, 'parent_source'):
child_item.parent_source = parent_item
elif isinstance(parent_item, AssetRule):
# Parent is AssetRule. Children are FileRules.
if row < len(parent_item.files):
child_item = parent_item.files[row]
# Ensure parent reference is set
if not hasattr(child_item, 'parent_asset'):
child_item.parent_asset = parent_item
if child_item:
# Create index for the child item under the parent
return self.createIndex(row, column, child_item)
else:
# Invalid row or parent type has no children (FileRule)
return QModelIndex()
def data(self, index: QModelIndex, role: int = Qt.DisplayRole):
"""Returns the data stored under the given role for the item referred to by the index."""
if not index.isValid(): # Check only index validity, data list might be empty but valid
return None
item = index.internalPointer()
column = index.column()
# --- Handle different item types ---
if isinstance(item, SourceRule): # This might only be relevant if SourceRule is displayed
if role == Qt.DisplayRole:
if column == 0: return item.input_path
# Use supplier_override if set, otherwise empty string
if column == self.COL_SUPPLIER: return item.supplier_override if item.supplier_override is not None else ""
# Other columns return None or "" for SourceRule
elif role == Qt.EditRole:
# Return supplier_override for editing
if column == self.COL_SUPPLIER: return item.supplier_override if item.supplier_override is not None else ""
return None # Default for SourceRule for other roles/columns
elif isinstance(item, AssetRule):
if role == Qt.DisplayRole:
if column == self.COL_NAME: return item.asset_name
# Use asset_type_override if set, otherwise fall back to predicted asset_type
if column == self.COL_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 ""
# Placeholder columns
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:
# Return asset_type_override for editing (delegate expects string or None)
if column == self.COL_ASSET_TYPE:
return item.asset_type_override # Return string or None
return None # Default for AssetRule
elif isinstance(item, FileRule):
if role == Qt.DisplayRole:
if column == self.COL_NAME: return Path(item.file_path).name # Display only filename
# Use target_asset_name_override if set, otherwise empty string
if column == self.COL_TARGET_ASSET:
return item.target_asset_name_override if item.target_asset_name_override is not None else ""
# Use item_type_override if set, otherwise empty string (assuming predicted isn't stored directly)
if column == self.COL_ITEM_TYPE:
# Assuming item_type_override stores the string name of the ItemType enum
return item.item_type_override if item.item_type_override 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:
# Return target_asset_name_override for editing
if column == self.COL_TARGET_ASSET: return item.target_asset_name_override if item.target_asset_name_override is not None else ""
# Return item_type_override for editing (delegate expects string or None)
if column == self.COL_ITEM_TYPE: return item.item_type_override # Return string or None
return None # Default for FileRule
return None # Should not be reached if item is one of the known types
def setData(self, index: QModelIndex, value, role: int = Qt.EditRole) -> bool:
"""Sets the role data for the item at index to value."""
if not index.isValid() or role != Qt.EditRole: # Check only index and role
return False
item = index.internalPointer()
if item is None: # Extra check for safety
return False
column = index.column()
changed = False
# --- Handle different item types ---
if isinstance(item, SourceRule): # If SourceRule is editable
if column == self.COL_SUPPLIER:
# Ensure value is string or None
new_value = str(value).strip() if value is not None else None
if new_value == "": new_value = None # Treat empty string as None
# Update supplier_override
if item.supplier_override != new_value:
item.supplier_override = new_value
changed = True
elif isinstance(item, AssetRule):
if column == self.COL_ASSET_TYPE:
# Delegate provides string value (e.g., "Surface", "Model") or None
new_value = str(value) if value is not None else None
if new_value == "": new_value = None # Treat empty string as None
# Update asset_type_override
if item.asset_type_override != new_value:
item.asset_type_override = new_value
changed = True
elif isinstance(item, FileRule):
if column == self.COL_TARGET_ASSET: # Target Asset Name Override
# Ensure value is string or None
new_value = str(value).strip() if value is not None else None
if new_value == "": new_value = None # Treat empty string as None
# Update target_asset_name_override
if item.target_asset_name_override != new_value:
item.target_asset_name_override = new_value
changed = True
elif column == self.COL_ITEM_TYPE: # Item-Type Override
# Delegate provides string value (e.g., "MAP_COL") or None
new_value = str(value) if value is not None else None
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
if changed:
# Emit dataChanged for the specific index and affected roles
self.dataChanged.emit(index, index, [Qt.DisplayRole, Qt.EditRole])
return True
return False
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
"""Returns the item flags for the given index."""
if not index.isValid():
return Qt.NoItemFlags # No flags for invalid index
# Start with default flags for a valid item
default_flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
item = index.internalPointer()
column = index.column()
can_edit = False
# Determine editability based on item type and column
if isinstance(item, SourceRule): # If SourceRule is displayed/editable
if column == 1: can_edit = True
elif isinstance(item, AssetRule):
if column == 2: can_edit = True
elif isinstance(item, FileRule):
if column == 3: can_edit = True
if column == 4: can_edit = True
if can_edit:
return default_flags | Qt.ItemIsEditable
else:
return default_flags
def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole):
"""Returns the data for the given role and section in the header."""
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
if 0 <= section < len(self.Columns):
return self.Columns[section]
# Optionally handle Vertical header (row numbers)
# if orientation == Qt.Vertical and role == Qt.DisplayRole:
# return str(section + 1)
return None
# Helper to get item from index
def getItem(self, index: QModelIndex):
"""Safely returns the item associated with the index."""
if index.isValid():
item = index.internalPointer()
if item: # Ensure internal pointer is not None
return item
return None # Return None for invalid index or None pointer