From 77d8a5b3ddfd9a7c63f4884e69fc32d91056f36b Mon Sep 17 00:00:00 2001 From: Rusfort Date: Thu, 1 May 2025 19:43:04 +0200 Subject: [PATCH] Implemented 2-pass handling to account for DISP16 Varients --- gui/prediction_handler.py | 371 ++++++++++++++++++++++---------------- 1 file changed, 211 insertions(+), 160 deletions(-) diff --git a/gui/prediction_handler.py b/gui/prediction_handler.py index 177d1ba..8d6f42d 100644 --- a/gui/prediction_handler.py +++ b/gui/prediction_handler.py @@ -7,7 +7,7 @@ import re # Import regex import tempfile # Added for temporary extraction directory import zipfile # Added for zip file handling # 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 # --- PySide6 Imports --- @@ -50,8 +50,10 @@ if not log.hasHandlers(): # 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]]]: """ - Analyzes a list of files based on configuration rules to group them by asset - and determine initial file properties. + Analyzes a list of files based on configuration rules using a two-pass approach + 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: file_list: List of absolute file paths. @@ -62,16 +64,19 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis Example: { 'AssetName1': [ - {'file_path': '/path/to/AssetName1_Color.png', 'item_type': 'Color', 'asset_name': 'AssetName1'}, - {'file_path': '/path/to/AssetName1_Normal.png', 'item_type': 'Normal', '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_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 } Returns an empty dict if classification fails or no files are provided. """ temp_grouped_files = defaultdict(list) - extra_files_to_associate = [] # Store tuples: (file_path_str, filename) - primary_asset_names = set() # Store asset names derived from map files + extra_files_to_associate = [] # Store tuples: (file_path_str, filename) for Pass 2 association + 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 --- 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 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.") - # 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.") - # 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_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_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: file_path = Path(file_path_str) 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_map = False - # 1. Check for Extra Files FIRST + # 1. Check for Extra Files FIRST in Pass 2 for extra_pattern in compiled_extra_regex: 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)) is_extra = True - break # Stop checking extra patterns for this file + break 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 + # 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: - # --- DEBUG LOG: Inspect available rule info --- - log.debug(f" Match found! Rule Index: {rule_index}, Original Keyword: '{original_keyword}', Target Type: '{target_type}'") - # Access the full rule details directly from the config's map_type_mapping list using the index - matched_rule_details = None + # Access rule details + is_gloss_flag = False try: - # 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} 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 + 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. - 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({ '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, - # --- Store the flag retrieved from the rule --- - 'is_gloss_source': is_gloss_flag # Store the boolean value obtained above + 'is_gloss_source': is_gloss_flag }) - primary_asset_names.add(asset_name) # Mark this as a primary asset name is_map = True break # Stop checking patterns for this file if is_map: 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: - log.debug(f"File '{filename}' did not match any map/extra pattern. Grouping by stem as FILE_IGNORE.") - asset_name = file_path.stem + log.debug(f"PASS 2: File '{filename}' did not match any map/extra pattern. Grouping under asset '{asset_name}' as FILE_IGNORE.") temp_grouped_files[asset_name].append({ 'file_path': file_path_str, 'item_type': "FILE_IGNORE", - 'asset_name': asset_name + 'asset_name': asset_name, + 'is_gloss_source': False }) - # --- Determine Primary Asset Name --- - # Simple heuristic: if only one name derived from maps, use it. Otherwise, log warning. + log.debug("--- Finished Pass 2 ---") + + # --- Determine Primary Asset Name for Extra Association (using Pass 1 results) --- final_primary_asset_name = None - if len(primary_asset_names) == 1: - final_primary_asset_name = list(primary_asset_names)[0] - log.debug(f"Determined single primary asset name: '{final_primary_asset_name}'") - elif len(primary_asset_names) > 1: - # TODO: Implement a better heuristic for multiple assets (e.g., longest common prefix) - final_primary_asset_name = list(primary_asset_names)[0] # Fallback: use the first one found - 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.") - else: - # No maps found, but maybe extras exist? Associate with the first asset group found. - if temp_grouped_files and extra_files_to_associate: - final_primary_asset_name = list(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}'.") + if primary_asset_names: # Use names derived only from Pass 1 (prioritized variants) + # Find the most common name among those derived from primary maps identified in Pass 1 + primary_map_asset_names_pass1 = [ + f_info['asset_name'] + for asset_files in temp_grouped_files.values() + for f_info in asset_files + 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: - 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: 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: - temp_grouped_files[final_primary_asset_name].append({ - 'file_path': file_path_str, - 'item_type': "EXTRA", # Assign specific type - 'asset_name': final_primary_asset_name # Associate with primary asset - }) + # 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({ + '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: - 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.") - # Optionally, create a separate 'Extras' asset group? - # for file_path_str, filename in extra_files_to_associate: - # temp_grouped_files["_Extras_"].append(...) + # Logged warning above if final_primary_asset_name couldn't be determined + pass 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 # 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} predicted_asset_type = "Surface" # Default to "Surface" string - # Simple heuristic: if common material types exist, assume Surface - # Use strings directly from config.py's ALLOWED_FILE_TYPES - 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): + material_indicators = {"MAP_COL", "MAP_NRM", "MAP_ROUGH", "MAP_METAL", "MAP_AO", "MAP_DISP", "COL", "NRM", "ROUGH", "METAL", "AO", "DISP"} # Added base types too + if any(it in material_indicators for it in item_types_in_asset if it not in ["EXTRA", "FILE_IGNORE"]): # Exclude non-maps predicted_asset_type = "Surface" # Predict as "Surface" string # 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 from config. Falling back.") - # Fallback logic: use the default from config if allowed, else first allowed type - # Access DEFAULT_ASSET_CATEGORY using the property - default_type = config.default_asset_category # Use the property + default_type = config.default_asset_category if default_type in asset_type_definitions: predicted_asset_type = default_type 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: pass # Keep the original prediction if definitions are empty asset_rule = AssetRule( - asset_name=asset_name, # This is determined by classification - asset_type=predicted_asset_type, # Set overridable field (use the string) - # asset_type_override=None # This is for user edits, leave as None initially + asset_name=asset_name, + asset_type=predicted_asset_type, ) 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.") + base_item_type = file_info['item_type'] + target_asset_name_override = file_info['asset_name'] # 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"]: - # 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 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"]: - 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 + 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" + + + # 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 - # User override for item type starts as 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" 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" Determined Standard Map Type: {standard_map_type}") + is_gloss_source_value = file_info.get('is_gloss_source', 'MISSING') 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_path=file_info['file_path'], # This is static info based on input - item_type=final_item_type, # Set the new base item_type field - # --- Populate ONLY Overridable Fields --- - # Initialize override with the classified type for display + file_path=file_info['file_path'], + item_type=final_item_type, item_type_override=final_item_type, 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 --- + is_gloss_source=is_gloss_source_value if isinstance(is_gloss_source_value, bool) else False, + standard_map_type=standard_map_type, resolution_override=None, channel_merge_instructions={}, - # etc. ) file_rules.append(file_rule) asset_rule.files = file_rules asset_rules.append(asset_rule) - # Populate the SourceRule with its assets source_rule.assets = asset_rules 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: log.exception(f"Error building rule hierarchy for source '{input_source_identifier}': {e}") self.status_message.emit(f"Error building rules: {e}", 5000) - # Don't emit hierarchy, just finish self.prediction_finished.emit(input_source_identifier) self._is_running = False - # Removed erroneous temp_dir_obj cleanup return # --- Emit Results --- -# DEBUG Verify: Log the hierarchy being emitted log.info(f"VERIFY: Emitting rule_hierarchy_ready with {len(source_rules_list)} SourceRule(s).") for i, rule in enumerate(source_rules_list): 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}'.") - 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.") - # Removed prediction_results_ready signal emission - self.status_message.emit(f"Analysis complete for '{input_source_identifier}'.", 3000) self.prediction_finished.emit(input_source_identifier) 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.")