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
asset_name = get_asset_name(file_path, config)
processed = False
for target_type, variant_regex in compiled_bit_depth_regex_map.items():
match = variant_regex.search(filename)
if match:
log.debug(f"PASS 1: File '{filename}' matched PRIORITIZED bit depth variant for type '{target_type}'.")
matched_item_type = target_type
is_gloss_flag = False # Bit depth variants are typically not gloss
# Check if primary already assigned (safety for overlapping patterns)
if (asset_name, matched_item_type) in primary_assignments:
log.warning(f"PASS 1: Primary assignment ({asset_name}, {matched_item_type}) already exists. File '{filename}' will be handled in Pass 2.")
# Don't process here, let Pass 2 handle it as a general map or extra
else:
primary_assignments.add((asset_name, matched_item_type))
log.debug(f" PASS 1: Added primary assignment: ({asset_name}, {matched_item_type})")
primary_asset_names.add(asset_name)
temp_grouped_files[asset_name].append({
'file_path': file_path_str,
'item_type': matched_item_type,
'asset_name': asset_name,
'is_gloss_source': is_gloss_flag
})
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_extra = False
is_map = False is_map = False
# 1. Check for Extra Files FIRST # 1. Check for Extra Files FIRST in Pass 2
for extra_pattern in compiled_extra_regex: for extra_pattern in compiled_extra_regex:
if extra_pattern.search(filename): if extra_pattern.search(filename):
log.debug(f"File '{filename}' matched EXTRA pattern: {extra_pattern.pattern}") 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)) extra_files_to_associate.append((file_path_str, filename))
is_extra = True is_extra = True
break # Stop checking extra patterns for this file break
if is_extra: if is_extra:
continue # Move to the next file if it's an extra continue # Move to the next file if it's an extra
# 2. Check for Map Files # 2. Check for General Map Files in Pass 2
# TODO: Consider rule priority if multiple patterns match the same file
for target_type, patterns_list in compiled_map_regex.items(): for target_type, patterns_list in compiled_map_regex.items():
for compiled_regex, original_keyword, rule_index in patterns_list: for compiled_regex, original_keyword, rule_index in patterns_list:
match = compiled_regex.search(filename) match = compiled_regex.search(filename)
if match: if match:
# --- DEBUG LOG: Inspect available rule info --- # Access rule details
log.debug(f" Match found! Rule Index: {rule_index}, Original Keyword: '{original_keyword}', Target Type: '{target_type}'") is_gloss_flag = False
# Access the full rule details directly from the config's map_type_mapping list using the index
matched_rule_details = None
try: try:
# Access map_type_mapping using the property map_type_mapping_list = config.map_type_mapping
map_type_mapping_list = config.map_type_mapping # Use the property matched_rule_details = map_type_mapping_list[rule_index]
matched_rule_details = map_type_mapping_list[rule_index] # Access rule by index is_gloss_flag = matched_rule_details.get('is_gloss_source', False)
is_gloss_flag = matched_rule_details.get('is_gloss_source', False) # Get flag or default False log.debug(f" PASS 2: Match found! Rule Index: {rule_index}, Keyword: '{original_keyword}', Target: '{target_type}', Gloss: {is_gloss_flag}")
log.debug(f" Associated rule details: {matched_rule_details}") except Exception as e:
log.debug(f" 'is_gloss_source' flag from rule: {is_gloss_flag}") log.exception(f" PASS 2: Error accessing rule details for index {rule_index}: {e}")
except IndexError:
log.warning(f" Could not access map_type_mapping rule at index {rule_index} in config.settings. Cannot determine 'is_gloss_source' flag.") # *** Crucial Check: Has a prioritized variant claimed this type? ***
is_gloss_flag = False # Default if rule cannot be accessed if (asset_name, target_type) in primary_assignments:
# --- End DEBUG LOG --- 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 = target_type # The standard type (e.g., MAP_COL) matched_item_type = "EXTRA"
asset_name = None is_gloss_flag = False # Extras are not gloss sources
# --- Asset Name Extraction Logic (Simplified Heuristic) --- else:
match_start_index = match.start(1) # No prioritized variant exists, assign the general map type
if match_start_index > 0: log.debug(f"PASS 2: File '{filename}' matched '{original_keyword}' for item_type '{target_type}'.")
potential_name = filename[:match_start_index].rstrip('_- .') matched_item_type = target_type
asset_name = potential_name if potential_name else file_path.stem # Do NOT add to primary_assignments here - only Pass 1 does that.
else: # Do NOT add to primary_asset_names here either.
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, # Could be target_type or EXTRA
'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
}) })
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
else: ]
# No maps found, but maybe extras exist? Associate with the first asset group found. if primary_map_asset_names_pass1:
if temp_grouped_files and extra_files_to_associate: name_counts = Counter(primary_map_asset_names_pass1)
final_primary_asset_name = list(temp_grouped_files.keys())[0] most_common_names = name_counts.most_common()
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 = 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:
log.debug("No primary asset name determined (no maps 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:
fallback_name = sorted(temp_grouped_files.keys())[0]
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:
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:
temp_grouped_files[final_primary_asset_name].append({ # Check if file already exists in the group (e.g., if somehow classified twice)
'file_path': file_path_str, if not any(f['file_path'] == file_path_str for f in temp_grouped_files[final_primary_asset_name]):
'item_type': "EXTRA", # Assign specific type temp_grouped_files[final_primary_asset_name].append({
'asset_name': final_primary_asset_name # Associate with primary asset 'file_path': file_path_str,
}) 'item_type': "EXTRA",
'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.")