Implemented 2-pass handling to account for DISP16 Varients

This commit is contained in:
Rusfort 2025-05-01 19:43:04 +02:00
parent d5b7fb2d47
commit 77d8a5b3dd

View File

@ -7,7 +7,7 @@ import re # Import regex
import tempfile # Added for temporary extraction directory import tempfile # Added for temporary extraction directory
import zipfile # Added for zip file handling import zipfile # Added for zip file handling
# import patoolib # Potential import for rar/7z - Add later if zip works # import patoolib # Potential import for rar/7z - Add later if zip works
from collections import defaultdict from collections import defaultdict, Counter # Added Counter
from typing import List, Dict, Any # For type hinting from typing import List, Dict, Any # For type hinting
# --- PySide6 Imports --- # --- PySide6 Imports ---
@ -50,8 +50,10 @@ if not log.hasHandlers():
# Helper function for classification (can be moved outside class if preferred) # Helper function for classification (can be moved outside class if preferred)
def classify_files(file_list: List[str], config: Configuration) -> Dict[str, List[Dict[str, Any]]]: def classify_files(file_list: List[str], config: Configuration) -> Dict[str, List[Dict[str, Any]]]:
""" """
Analyzes a list of files based on configuration rules to group them by asset Analyzes a list of files based on configuration rules using a two-pass approach
and determine initial file properties. to group them by asset and determine initial file properties.
Pass 1: Identifies and classifies prioritized bit depth variants.
Pass 2: Classifies extras, general maps (downgrading if primary exists), and ignores.
Args: Args:
file_list: List of absolute file paths. file_list: List of absolute file paths.
@ -62,16 +64,19 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis
Example: Example:
{ {
'AssetName1': [ 'AssetName1': [
{'file_path': '/path/to/AssetName1_Color.png', 'item_type': 'Color', 'asset_name': 'AssetName1'}, {'file_path': '/path/to/AssetName1_DISP16.png', 'item_type': 'DISP', 'asset_name': 'AssetName1', 'is_gloss_source': False},
{'file_path': '/path/to/AssetName1_Normal.png', 'item_type': 'Normal', 'asset_name': 'AssetName1'} {'file_path': '/path/to/AssetName1_DISP.png', 'item_type': 'EXTRA', 'asset_name': 'AssetName1', 'is_gloss_source': False},
{'file_path': '/path/to/AssetName1_Color.png', 'item_type': 'COL', 'asset_name': 'AssetName1', 'is_gloss_source': False}
], ],
# ... other assets # ... other assets
} }
Returns an empty dict if classification fails or no files are provided. Returns an empty dict if classification fails or no files are provided.
""" """
temp_grouped_files = defaultdict(list) temp_grouped_files = defaultdict(list)
extra_files_to_associate = [] # Store tuples: (file_path_str, filename) extra_files_to_associate = [] # Store tuples: (file_path_str, filename) for Pass 2 association
primary_asset_names = set() # Store asset names derived from map files primary_asset_names = set() # Store asset names derived *only* from primary map files (populated in Pass 1)
primary_assignments = set() # Stores tuples: (asset_name, target_type) (populated *only* in Pass 1)
processed_in_pass1 = set() # Keep track of files handled in Pass 1
# --- Validation --- # --- Validation ---
if not file_list or not config: if not file_list or not config:
@ -80,125 +85,218 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis
# Access compiled regex directly from the config object # Access compiled regex directly from the config object
if not hasattr(config, 'compiled_map_keyword_regex') or not config.compiled_map_keyword_regex: if not hasattr(config, 'compiled_map_keyword_regex') or not config.compiled_map_keyword_regex:
log.warning("Classification skipped: Missing compiled map keyword regex in config.") 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'): if not hasattr(config, 'compiled_extra_regex'):
log.warning("Configuration object missing 'compiled_extra_regex'. Cannot classify extra files.") log.warning("Configuration object missing 'compiled_extra_regex'. Cannot classify extra files.")
# Continue, but extras won't be found if not hasattr(config, 'compiled_bit_depth_regex_map'):
log.warning("Configuration object missing 'compiled_bit_depth_regex_map'. Cannot prioritize bit depth variants.")
compiled_map_regex = getattr(config, 'compiled_map_keyword_regex', {}) compiled_map_regex = getattr(config, 'compiled_map_keyword_regex', {})
compiled_extra_regex = getattr(config, 'compiled_extra_regex', []) compiled_extra_regex = getattr(config, 'compiled_extra_regex', [])
compiled_bit_depth_regex_map = getattr(config, 'compiled_bit_depth_regex_map', {})
num_map_rules = sum(len(patterns) for patterns in compiled_map_regex.values()) num_map_rules = sum(len(patterns) for patterns in compiled_map_regex.values())
num_extra_rules = len(compiled_extra_regex) num_extra_rules = len(compiled_extra_regex)
num_bit_depth_rules = len(compiled_bit_depth_regex_map)
log.debug(f"Starting classification for {len(file_list)} files using {num_map_rules} map keyword patterns and {num_extra_rules} extra patterns.") log.debug(f"Starting classification for {len(file_list)} files using {num_map_rules} map keyword patterns, {num_bit_depth_rules} bit depth patterns, and {num_extra_rules} extra patterns.")
# --- Initial Pass: Classify Maps and Identify Extras --- # --- Asset Name Extraction Helper ---
def get_asset_name(f_path: Path, cfg: Configuration) -> str:
filename = f_path.name
asset_name = None
try:
separator = cfg.source_naming_separator
indices = cfg.source_naming_indices
base_name_index = indices.get('base_name')
if separator is not None and base_name_index is not None:
stem = f_path.stem
parts = stem.split(separator)
if 0 <= base_name_index < len(parts):
asset_name = parts[base_name_index]
else:
log.warning(f"Preset base_name index {base_name_index} out of bounds for '{stem}' split by '{separator}'. Falling back.")
else:
log.debug(f"Preset rules for asset name extraction incomplete (separator: {separator}, index: {base_name_index}). Falling back for '{filename}'.")
if not asset_name:
asset_name = f_path.stem.split('_')[0] if '_' in f_path.stem else f_path.stem
log.debug(f"Used fallback asset name extraction: '{asset_name}' for '{filename}'.")
except Exception as e:
log.exception(f"Error extracting asset name for '{filename}': {e}. Falling back to stem.")
asset_name = f_path.stem
if not asset_name:
asset_name = f_path.stem
log.warning(f"Asset name extraction resulted in empty string for '{filename}'. Using stem: '{asset_name}'.")
return asset_name
# --- Pass 1: Prioritized Bit Depth Variants ---
log.debug("--- Starting Classification Pass 1: Prioritized Variants ---")
for file_path_str in file_list: for file_path_str in file_list:
file_path = Path(file_path_str) file_path = Path(file_path_str)
filename = file_path.name filename = file_path.name
is_extra = False asset_name = get_asset_name(file_path, config)
is_map = False processed = False
# 1. Check for Extra Files FIRST for target_type, variant_regex in compiled_bit_depth_regex_map.items():
for extra_pattern in compiled_extra_regex: match = variant_regex.search(filename)
if extra_pattern.search(filename):
log.debug(f"File '{filename}' matched EXTRA pattern: {extra_pattern.pattern}")
extra_files_to_associate.append((file_path_str, filename))
is_extra = True
break # Stop checking extra patterns for this file
if is_extra:
continue # Move to the next file if it's an extra
# 2. Check for Map Files
# TODO: Consider rule priority if multiple patterns match the same file
for target_type, patterns_list in compiled_map_regex.items():
for compiled_regex, original_keyword, rule_index in patterns_list:
match = compiled_regex.search(filename)
if match: if match:
# --- DEBUG LOG: Inspect available rule info --- log.debug(f"PASS 1: File '{filename}' matched PRIORITIZED bit depth variant for type '{target_type}'.")
log.debug(f" Match found! Rule Index: {rule_index}, Original Keyword: '{original_keyword}', Target Type: '{target_type}'") matched_item_type = target_type
# Access the full rule details directly from the config's map_type_mapping list using the index is_gloss_flag = False # Bit depth variants are typically not gloss
matched_rule_details = None
try: # Check if primary already assigned (safety for overlapping patterns)
# Access map_type_mapping using the property if (asset_name, matched_item_type) in primary_assignments:
map_type_mapping_list = config.map_type_mapping # Use the property log.warning(f"PASS 1: Primary assignment ({asset_name}, {matched_item_type}) already exists. File '{filename}' will be handled in Pass 2.")
matched_rule_details = map_type_mapping_list[rule_index] # Access rule by index # Don't process here, let Pass 2 handle it as a general map or extra
is_gloss_flag = matched_rule_details.get('is_gloss_source', False) # Get flag or default False else:
log.debug(f" Associated rule details: {matched_rule_details}") primary_assignments.add((asset_name, matched_item_type))
log.debug(f" 'is_gloss_source' flag from rule: {is_gloss_flag}") log.debug(f" PASS 1: Added primary assignment: ({asset_name}, {matched_item_type})")
except IndexError: primary_asset_names.add(asset_name)
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)
asset_name = None
# --- Asset Name Extraction Logic (Simplified Heuristic) ---
match_start_index = match.start(1)
if match_start_index > 0:
potential_name = filename[:match_start_index].rstrip('_- .')
asset_name = potential_name if potential_name else file_path.stem
else:
asset_name = file_path.stem
if not asset_name: asset_name = file_path.stem
log.debug(f"File '{filename}' matched keyword '{original_keyword}' (rule {rule_index}) for item_type '{matched_item_type}'. Assigned asset name: '{asset_name}'")
temp_grouped_files[asset_name].append({ temp_grouped_files[asset_name].append({
'file_path': file_path_str, 'file_path': file_path_str,
'item_type': matched_item_type, 'item_type': matched_item_type,
'asset_name': asset_name, 'asset_name': asset_name,
# --- Store the flag retrieved from the rule --- 'is_gloss_source': is_gloss_flag
'is_gloss_source': is_gloss_flag # Store the boolean value obtained above })
processed_in_pass1.add(file_path_str)
processed = True
break # Stop checking other variant patterns for this file
# Log if not processed in this pass
# if not processed:
# log.debug(f"PASS 1: File '{filename}' did not match any prioritized variant.")
log.debug(f"--- Finished Pass 1. Primary assignments made: {primary_assignments} ---")
# --- Pass 2: Extras, General Maps, Ignores ---
log.debug("--- Starting Classification Pass 2: Extras, General Maps, Ignores ---")
for file_path_str in file_list:
if file_path_str in processed_in_pass1:
log.debug(f"PASS 2: Skipping '{Path(file_path_str).name}' (processed in Pass 1).")
continue # Skip files already classified as prioritized variants
file_path = Path(file_path_str)
filename = file_path.name
asset_name = get_asset_name(file_path, config)
is_extra = False
is_map = False
# 1. Check for Extra Files FIRST in Pass 2
for extra_pattern in compiled_extra_regex:
if extra_pattern.search(filename):
log.debug(f"PASS 2: File '{filename}' matched EXTRA pattern: {extra_pattern.pattern}")
# Don't group yet, just collect for later association
extra_files_to_associate.append((file_path_str, filename))
is_extra = True
break
if is_extra:
continue # Move to the next file if it's an extra
# 2. Check for General Map Files in Pass 2
for target_type, patterns_list in compiled_map_regex.items():
for compiled_regex, original_keyword, rule_index in patterns_list:
match = compiled_regex.search(filename)
if match:
# Access rule details
is_gloss_flag = False
try:
map_type_mapping_list = config.map_type_mapping
matched_rule_details = map_type_mapping_list[rule_index]
is_gloss_flag = matched_rule_details.get('is_gloss_source', False)
log.debug(f" PASS 2: Match found! Rule Index: {rule_index}, Keyword: '{original_keyword}', Target: '{target_type}', Gloss: {is_gloss_flag}")
except Exception as e:
log.exception(f" PASS 2: Error accessing rule details for index {rule_index}: {e}")
# *** Crucial Check: Has a prioritized variant claimed this type? ***
if (asset_name, target_type) in primary_assignments:
log.debug(f"PASS 2: File '{filename}' matched '{original_keyword}' for type '{target_type}', but primary already assigned via Pass 1. Classifying as EXTRA.")
matched_item_type = "EXTRA"
is_gloss_flag = False # Extras are not gloss sources
else:
# No prioritized variant exists, assign the general map type
log.debug(f"PASS 2: File '{filename}' matched '{original_keyword}' for item_type '{target_type}'.")
matched_item_type = target_type
# Do NOT add to primary_assignments here - only Pass 1 does that.
# Do NOT add to primary_asset_names here either.
temp_grouped_files[asset_name].append({
'file_path': file_path_str,
'item_type': matched_item_type, # Could be target_type or EXTRA
'asset_name': asset_name,
'is_gloss_source': is_gloss_flag
}) })
primary_asset_names.add(asset_name) # Mark this as a primary asset name
is_map = True is_map = True
break # Stop checking patterns for this file break # Stop checking patterns for this file
if is_map: if is_map:
break # Stop checking target types for this file break # Stop checking target types for this file
# 3. Handle Unmatched Files (Not Extra, Not Map) # 3. Handle Unmatched Files in Pass 2 (Not Extra, Not Map)
if not is_extra and not is_map: if not is_extra and not is_map:
log.debug(f"File '{filename}' did not match any map/extra pattern. Grouping by stem as FILE_IGNORE.") log.debug(f"PASS 2: File '{filename}' did not match any map/extra pattern. Grouping under asset '{asset_name}' as FILE_IGNORE.")
asset_name = file_path.stem
temp_grouped_files[asset_name].append({ temp_grouped_files[asset_name].append({
'file_path': file_path_str, 'file_path': file_path_str,
'item_type': "FILE_IGNORE", 'item_type': "FILE_IGNORE",
'asset_name': asset_name 'asset_name': asset_name,
'is_gloss_source': False
}) })
# --- Determine Primary Asset Name --- log.debug("--- Finished Pass 2 ---")
# Simple heuristic: if only one name derived from maps, use it. Otherwise, log warning.
# --- Determine Primary Asset Name for Extra Association (using Pass 1 results) ---
final_primary_asset_name = None final_primary_asset_name = None
if len(primary_asset_names) == 1: if primary_asset_names: # Use names derived only from Pass 1 (prioritized variants)
final_primary_asset_name = list(primary_asset_names)[0] # Find the most common name among those derived from primary maps identified in Pass 1
log.debug(f"Determined single primary asset name: '{final_primary_asset_name}'") primary_map_asset_names_pass1 = [
elif len(primary_asset_names) > 1: f_info['asset_name']
# TODO: Implement a better heuristic for multiple assets (e.g., longest common prefix) for asset_files in temp_grouped_files.values()
final_primary_asset_name = list(primary_asset_names)[0] # Fallback: use the first one found for f_info in asset_files
log.warning(f"Multiple potential primary asset names found: {primary_asset_names}. Using '{final_primary_asset_name}' for associating extra files. Consider refining asset name extraction.") if f_info['asset_name'] in primary_asset_names and (f_info['asset_name'], f_info['item_type']) in primary_assignments # Ensure it was a Pass 1 assignment
]
if primary_map_asset_names_pass1:
name_counts = Counter(primary_map_asset_names_pass1)
most_common_names = name_counts.most_common()
final_primary_asset_name = most_common_names[0][0]
if len(most_common_names) > 1 and most_common_names[0][1] == most_common_names[1][1]:
tied_names = sorted([name for name, count in most_common_names if count == most_common_names[0][1]])
final_primary_asset_name = tied_names[0]
log.warning(f"Multiple primary asset names tied for most common based on Pass 1: {tied_names}. Using '{final_primary_asset_name}' for associating extra files.")
log.debug(f"Determined primary asset name for extras based on Pass 1 primary maps: '{final_primary_asset_name}'")
else: else:
# No maps found, but maybe extras exist? Associate with the first asset group found. log.warning("Primary asset names set (from Pass 1) was populated, but no corresponding groups found. Falling back.")
if not final_primary_asset_name:
# Fallback: No primary maps found in Pass 1. Use the first asset group found overall.
if temp_grouped_files and extra_files_to_associate: if temp_grouped_files and extra_files_to_associate:
final_primary_asset_name = list(temp_grouped_files.keys())[0] fallback_name = sorted(temp_grouped_files.keys())[0]
log.warning(f"No map files found to determine primary asset name. Associating extras with first group found: '{final_primary_asset_name}'.") final_primary_asset_name = fallback_name
log.warning(f"No primary map files found in Pass 1. Associating extras with first group found alphabetically: '{final_primary_asset_name}'.")
elif extra_files_to_associate:
log.warning(f"Could not determine any asset name to associate {len(extra_files_to_associate)} extra file(s) with. They will be ignored.")
else: else:
log.debug("No primary asset name determined (no maps found).") log.debug("No primary asset name determined (no maps or extras found).")
# --- Associate Extra Files --- # --- Associate Extra Files (collected in Pass 2) ---
if final_primary_asset_name and extra_files_to_associate: if final_primary_asset_name and extra_files_to_associate:
log.debug(f"Associating {len(extra_files_to_associate)} extra file(s) with primary asset '{final_primary_asset_name}'") log.debug(f"Associating {len(extra_files_to_associate)} extra file(s) with primary asset '{final_primary_asset_name}'")
for file_path_str, filename in extra_files_to_associate: for file_path_str, filename in extra_files_to_associate:
# Check if file already exists in the group (e.g., if somehow classified twice)
if not any(f['file_path'] == file_path_str for f in temp_grouped_files[final_primary_asset_name]):
temp_grouped_files[final_primary_asset_name].append({ temp_grouped_files[final_primary_asset_name].append({
'file_path': file_path_str, 'file_path': file_path_str,
'item_type': "EXTRA", # Assign specific type 'item_type': "EXTRA",
'asset_name': final_primary_asset_name # Associate with primary asset 'asset_name': final_primary_asset_name,
'is_gloss_source': False
}) })
else:
log.debug(f"Skipping duplicate association of extra file: {filename}")
elif extra_files_to_associate: elif extra_files_to_associate:
log.warning(f"Could not determine a primary asset name to associate {len(extra_files_to_associate)} extra file(s) with. They will be ignored.") # Logged warning above if final_primary_asset_name couldn't be determined
# Optionally, create a separate 'Extras' asset group? pass
# for file_path_str, filename in extra_files_to_associate:
# temp_grouped_files["_Extras_"].append(...)
log.debug(f"Classification complete. Found {len(temp_grouped_files)} potential assets.") log.debug(f"Classification complete. Found {len(temp_grouped_files)} potential assets.")
@ -343,163 +441,116 @@ class PredictionHandler(QObject):
if not files_info: continue # Skip empty asset groups if not files_info: continue # Skip empty asset groups
# Determine AssetRule level overrides/defaults # Determine AssetRule level overrides/defaults
# TODO: Implement logic to determine asset_type based on file types present?
# For now, default to MATERIAL if common material maps are present, else GENERIC.
# This requires checking item_types in files_info.
item_types_in_asset = {f_info['item_type'] for f_info in files_info} item_types_in_asset = {f_info['item_type'] for f_info in files_info}
predicted_asset_type = "Surface" # Default to "Surface" string predicted_asset_type = "Surface" # Default to "Surface" string
# Simple heuristic: if common material types exist, assume Surface material_indicators = {"MAP_COL", "MAP_NRM", "MAP_ROUGH", "MAP_METAL", "MAP_AO", "MAP_DISP", "COL", "NRM", "ROUGH", "METAL", "AO", "DISP"} # Added base types too
# Use strings directly from config.py's ALLOWED_FILE_TYPES if any(it in material_indicators for it in item_types_in_asset if it not in ["EXTRA", "FILE_IGNORE"]): # Exclude non-maps
material_indicators = {"MAP_COL", "MAP_NRM", "MAP_ROUGH", "MAP_METAL", "MAP_AO", "MAP_DISP"}
if any(it in material_indicators for it in item_types_in_asset):
predicted_asset_type = "Surface" # Predict as "Surface" string predicted_asset_type = "Surface" # Predict as "Surface" string
# Ensure the predicted type is allowed, fallback if necessary # 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: 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 from config. 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 = config.default_asset_category
# Access DEFAULT_ASSET_CATEGORY using the property
default_type = config.default_asset_category # Use the property
if default_type in asset_type_definitions: if default_type in asset_type_definitions:
predicted_asset_type = default_type predicted_asset_type = default_type
elif asset_type_definitions: elif asset_type_definitions:
predicted_asset_type = list(asset_type_definitions.keys())[0] # Use first key predicted_asset_type = list(asset_type_definitions.keys())[0]
else: else:
pass # Keep the original prediction if definitions are empty pass # Keep the original prediction if definitions are empty
asset_rule = AssetRule( asset_rule = AssetRule(
asset_name=asset_name, # This is determined by classification asset_name=asset_name,
asset_type=predicted_asset_type, # Set overridable field (use the string) asset_type=predicted_asset_type,
# asset_type_override=None # This is for user edits, leave as None initially
) )
log.debug(f"Created AssetRule for asset: {asset_name} with type: {predicted_asset_type}") log.debug(f"Created AssetRule for asset: {asset_name} with type: {predicted_asset_type}")
file_rules = [] file_rules = []
# Get allowed file types from config's internal core settings
file_type_definitions = config._core_settings.get('FILE_TYPE_DEFINITIONS', {}) file_type_definitions = config._core_settings.get('FILE_TYPE_DEFINITIONS', {})
log.debug(f"Loaded FileType Definitions (ItemTypes) from config: {list(file_type_definitions.keys())}") log.debug(f"Loaded FileType Definitions (ItemTypes) from config: {list(file_type_definitions.keys())}")
for file_info in files_info: for file_info in files_info:
# Determine FileRule level overrides/defaults base_item_type = file_info['item_type']
base_item_type = file_info['item_type'] # Type from classification (e.g., COL, NRM, EXTRA) target_asset_name_override = file_info['asset_name']
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) # Determine the final item_type string (prefix maps, check if allowed)
final_item_type = base_item_type # Start with the base type final_item_type = base_item_type
if not base_item_type.startswith("MAP_") and base_item_type not in ["FILE_IGNORE", "EXTRA", "MODEL"]: 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}" final_item_type = f"MAP_{base_item_type}"
# Check if the final type is allowed (exists as a key in config settings) # Check if the final type is allowed
if file_type_definitions and final_item_type not in file_type_definitions and base_item_type not in ["FILE_IGNORE", "EXTRA"]: 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 from config. 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. Setting to FILE_IGNORE.")
final_item_type = "FILE_IGNORE" # Fallback base type to FILE_IGNORE string final_item_type = "FILE_IGNORE"
# Retrieve the standard_type
standard_map_type = None
file_type_details = file_type_definitions.get(final_item_type)
if file_type_details:
standard_map_type = file_type_details.get('standard_type')
log.debug(f" Found standard_type '{standard_map_type}' for final_item_type '{final_item_type}'")
else:
file_type_details_alias = file_type_definitions.get(base_item_type)
if file_type_details_alias:
standard_map_type = file_type_details_alias.get('standard_type')
log.debug(f" Found standard_type '{standard_map_type}' via alias lookup for base_item_type '{base_item_type}'")
elif base_item_type in file_type_definitions:
standard_map_type = base_item_type
log.debug(f" Using base_item_type '{base_item_type}' itself as standard_map_type.")
else:
log.debug(f" Could not determine standard_map_type for base '{base_item_type}' / final '{final_item_type}'. Setting to None.")
# Output format is determined by the engine, not predicted here. Leave as None.
output_format_override = None output_format_override = None
# User override for item type starts as None
item_type_override = None item_type_override = None
# --- DEBUG LOG: Inspect data before FileRule creation ---
log.debug(f" Creating FileRule for: {file_info['file_path']}") log.debug(f" Creating FileRule for: {file_info['file_path']}")
log.debug(f" Base Item Type (from classification): {base_item_type}") 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" Final Item Type (for model): {final_item_type}")
log.debug(f" Target Asset Name Override: {target_asset_name_override}") log.debug(f" Target Asset Name Override: {target_asset_name_override}")
# --- DETAILED DEBUG LOG: Inspect standard_map_type assignment --- log.debug(f" Determined Standard Map Type: {standard_map_type}")
log.debug(f" DEBUG: Processing file: {file_info['file_path']}") is_gloss_source_value = file_info.get('is_gloss_source', 'MISSING')
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}") log.debug(f" Value for 'is_gloss_source' from file_info: {is_gloss_source_value}")
# --- End DEBUG LOG ---
# Pass the retrieved flag value and standard_map_type to the constructor
file_rule = FileRule( file_rule = FileRule(
file_path=file_info['file_path'], # This is static info based on input file_path=file_info['file_path'],
item_type=final_item_type, # Set the new base item_type field item_type=final_item_type,
# --- Populate ONLY Overridable Fields ---
# Initialize override with the classified type for display
item_type_override=final_item_type, item_type_override=final_item_type,
target_asset_name_override=target_asset_name_override, target_asset_name_override=target_asset_name_override,
output_format_override=output_format_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 is_gloss_source=is_gloss_source_value if isinstance(is_gloss_source_value, bool) else False,
standard_map_type=standard_map_type, # Assign the determined standard_map_type standard_map_type=standard_map_type,
# --- Leave Static Fields as Default/None ---
resolution_override=None, resolution_override=None,
channel_merge_instructions={}, channel_merge_instructions={},
# etc.
) )
file_rules.append(file_rule) file_rules.append(file_rule)
asset_rule.files = file_rules asset_rule.files = file_rules
asset_rules.append(asset_rule) asset_rules.append(asset_rule)
# Populate the SourceRule with its assets
source_rule.assets = asset_rules source_rule.assets = asset_rules
log.debug(f"Built SourceRule '{source_rule.input_path}' with {len(asset_rules)} AssetRule(s).") log.debug(f"Built SourceRule '{source_rule.input_path}' with {len(asset_rules)} AssetRule(s).")
source_rules_list.append(source_rule) # Add the single completed SourceRule source_rules_list.append(source_rule)
except Exception as e: except Exception as e:
log.exception(f"Error building rule hierarchy for source '{input_source_identifier}': {e}") log.exception(f"Error building rule hierarchy for source '{input_source_identifier}': {e}")
self.status_message.emit(f"Error building rules: {e}", 5000) self.status_message.emit(f"Error building rules: {e}", 5000)
# Don't emit hierarchy, just finish
self.prediction_finished.emit(input_source_identifier) self.prediction_finished.emit(input_source_identifier)
self._is_running = False self._is_running = False
# Removed erroneous temp_dir_obj cleanup
return return
# --- Emit Results --- # --- Emit Results ---
# DEBUG Verify: Log the hierarchy being emitted
log.info(f"VERIFY: Emitting rule_hierarchy_ready with {len(source_rules_list)} SourceRule(s).") log.info(f"VERIFY: Emitting rule_hierarchy_ready with {len(source_rules_list)} SourceRule(s).")
for i, rule in enumerate(source_rules_list): for i, rule in enumerate(source_rules_list):
log.debug(f" VERIFY Rule {i}: Input='{rule.input_path}', Assets={len(rule.assets)}") log.debug(f" VERIFY Rule {i}: Input='{rule.input_path}', Assets={len(rule.assets)}")
log.info(f"[{time.time():.4f}][T:{thread_id}] Prediction run finished. Emitting hierarchy for '{input_source_identifier}'.") log.info(f"[{time.time():.4f}][T:{thread_id}] Prediction run finished. Emitting hierarchy for '{input_source_identifier}'.")
self.rule_hierarchy_ready.emit(source_rules_list) # Emit list containing the one SourceRule self.rule_hierarchy_ready.emit(source_rules_list)
log.info(f"[{time.time():.4f}][T:{thread_id}] Emitted rule_hierarchy_ready signal.") log.info(f"[{time.time():.4f}][T:{thread_id}] Emitted rule_hierarchy_ready signal.")
# Removed prediction_results_ready signal emission
self.status_message.emit(f"Analysis complete for '{input_source_identifier}'.", 3000) self.status_message.emit(f"Analysis complete for '{input_source_identifier}'.", 3000)
self.prediction_finished.emit(input_source_identifier) self.prediction_finished.emit(input_source_identifier)
self._is_running = False self._is_running = False
# Removed temp_dir_obj cleanup - not relevant here
log.info(f"[{time.time():.4f}][T:{thread_id}] <-- Exiting PredictionHandler.run_prediction.") log.info(f"[{time.time():.4f}][T:{thread_id}] <-- Exiting PredictionHandler.run_prediction.")