# blenderscripts/create_materials.py # Version: 1.2 # Description: Scans a library processed by the Asset Processor Tool, # reads metadata.json files, finds corresponding PBRSET node groups # in a specified Blender .blend file, and creates Blender materials # linking to those node groups. Skips assets if material already exists. # Sets material viewport properties and custom previews based on metadata. # Changes v1.2: # - Modified to link PBRSET node groups directly from a specified .blend file path # passed as a command-line argument, instead of relying on an Asset Catalog name. # - Removed PBRSET_ASSET_LIBRARY_NAME configuration and related checks. # Changes v1.1: # - Added logic to link PBRSET node groups from an external asset library. # - Added logic to skip processing if the target material already exists. # - Added configuration for the PBRSET asset library name. import bpy import os import json from pathlib import Path import time import base64 # Although not directly used here, keep for consistency if reusing more code later import sys # --- USER CONFIGURATION --- # Path to the root output directory of the Asset Processor Tool # Example: r"G:\Assets\Processed" # IMPORTANT: This should point to the base directory containing supplier folders (e.g., Poliigon) # This will be overridden by command-line arguments if provided. PROCESSED_ASSET_LIBRARY_ROOT = None # Set to None initially # Path to the .blend file containing the PBRSET node groups. # This will be overridden by command-line arguments if provided. NODEGROUP_BLEND_FILE_PATH = None # Set to None initially # Name of the required template material in the Blender file TEMPLATE_MATERIAL_NAME = "Template_PBRMaterial" # Label of the placeholder Group node within the template material's node tree # where the PBRSET node group will be linked PLACEHOLDER_NODE_LABEL = "PBRSET_PLACEHOLDER" # Prefix for the created materials MATERIAL_NAME_PREFIX = "Mat_" # Prefix used for the PBRSET node groups created by create_nodegroups.py PBRSET_GROUP_PREFIX = "PBRSET_" # Map type(s) to use for finding a reference image for the material preview # The script will look for these in order and use the first one found. REFERENCE_MAP_TYPES = ["COL", "COL-1", "COL-2"] # Preferred resolution order for reference image (lowest first is often faster) REFERENCE_RESOLUTION_ORDER = ["1K", "512", "2K", "4K"] # Adjust as needed # Assumed filename pattern for processed images. # [assetname], [maptype], [resolution], [ext] will be replaced. # This should match OUTPUT_FILENAME_PATTERN from app_settings.json. IMAGE_FILENAME_PATTERN = "[assetname]_[maptype]_[resolution].[ext]" # Fallback extensions to try if the primary format from metadata is not found # Order matters - first found will be used. FALLBACK_IMAGE_EXTENSIONS = ['png', 'jpg', 'exr', 'tif'] # Map types to check in metadata's 'image_stats_1k' for viewport diffuse color VIEWPORT_COLOR_MAP_TYPES = ["COL", "COL-1", "COL-2"] # Map types to check in metadata's 'image_stats_1k' for viewport roughness VIEWPORT_ROUGHNESS_MAP_TYPES = ["ROUGH"] # Map types to check in metadata's 'image_stats_1k' for viewport metallic VIEWPORT_METALLIC_MAP_TYPES = ["METAL"] # --- END USER CONFIGURATION --- # --- Helper Functions --- def find_nodes_by_label(node_tree, label, node_type=None): """Finds ALL nodes in a node tree matching the label and optionally type.""" if not node_tree: return [] matching_nodes = [] for node in node_tree.nodes: # Use node.label for labeled nodes, node.name for non-labeled (like Group Input/Output) node_identifier = node.label if node.label else node.name if node_identifier and node_identifier == label: if node_type is None or node.bl_idname == node_type or node.type == node_type: # Check bl_idname and type for flexibility matching_nodes.append(node) return matching_nodes def add_tag_if_new(asset_data, tag_name): """Adds a tag to the asset data if it's not None/empty and doesn't already exist.""" if not asset_data or not tag_name or not isinstance(tag_name, str): return False cleaned_tag_name = tag_name.strip() if not cleaned_tag_name: return False # Check if tag already exists (case-insensitive check might be better sometimes) if cleaned_tag_name not in [t.name for t in asset_data.tags]: try: asset_data.tags.new(cleaned_tag_name) print(f" + Added Asset Tag: '{cleaned_tag_name}'") return True except Exception as e: print(f" Error adding tag '{cleaned_tag_name}': {e}") return False return False # Tag already existed def reconstruct_image_path_with_fallback(asset_dir_path, asset_name, map_type, resolution, primary_format=None): """ Constructs the expected image file path. If primary_format is provided, tries that first. Then falls back to common extensions if the path doesn't exist or primary_format was None. Returns the found path as a string, or None if not found. """ if not all([asset_dir_path, asset_name, map_type, resolution]): print(f" !!! ERROR: Missing data for path reconstruction ({asset_name}/{map_type}/{resolution}).") return None found_path = None # 1. Try the primary format if provided if primary_format: try: filename = IMAGE_FILENAME_PATTERN.format( assetname=asset_name, # Token is 'assetname' maptype=map_type, # Token is 'maptype' resolution=resolution, # Token is 'resolution' ext=primary_format.lower() # Token is 'ext' ) primary_path = asset_dir_path / filename if primary_path.is_file(): return str(primary_path) except KeyError as e: print(f" !!! ERROR: Missing key '{e}' in IMAGE_FILENAME_PATTERN. Cannot reconstruct path.") return None # Cannot proceed without valid pattern except Exception as e: print(f" !!! ERROR reconstructing primary image path: {e}") # Continue to fallback # 2. Try fallback extensions for ext in FALLBACK_IMAGE_EXTENSIONS: # Skip if we already tried this extension as primary (and it failed) if primary_format and ext.lower() == primary_format.lower(): continue try: fallback_filename = IMAGE_FILENAME_PATTERN.format( assetname=asset_name, # Token is 'assetname' maptype=map_type, # Token is 'maptype' resolution=resolution, # Token is 'resolution' ext=ext.lower() # Token is 'ext' ) fallback_path = asset_dir_path / fallback_filename if fallback_path.is_file(): print(f" Found fallback path: {str(fallback_path)}") return str(fallback_path) # Found it! except KeyError: # Should not happen if primary format worked, but handle defensively print(f" !!! ERROR: Missing key in IMAGE_FILENAME_PATTERN during fallback. Cannot reconstruct path.") return None except Exception as e_fallback: print(f" !!! ERROR reconstructing fallback image path ({ext}): {e_fallback}") continue # Try next extension # If we get here, neither primary nor fallbacks worked if primary_format: print(f" !!! ERROR: Could not find image file for {map_type}/{resolution} using primary format '{primary_format}' or fallbacks {FALLBACK_IMAGE_EXTENSIONS}.") else: print(f" !!! ERROR: Could not find image file for {map_type}/{resolution} using fallbacks {FALLBACK_IMAGE_EXTENSIONS}.") return None # Not found after all checks def get_stat_value(stats_dict, map_type_list, stat_key): """ Safely retrieves a specific statistic (e.g., 'mean') for the first matching map type from the provided list within the image_stats_1k dictionary. Args: stats_dict (dict): The 'image_stats_1k' dictionary from metadata. map_type_list (list): List of map type strings to check (e.g., ["COL", "COL-1"]). stat_key (str): The statistic key to retrieve (e.g., "mean", "min", "max"). Returns: The found statistic value (can be float, list, etc.), or None if not found. """ if not stats_dict or not isinstance(stats_dict, dict): return None for map_type in map_type_list: if map_type in stats_dict: map_stats = stats_dict[map_type] if isinstance(map_stats, dict) and stat_key in map_stats: return map_stats[stat_key] # Return the value for the first match else: pass # Continue checking other map types in the list return None # Return None if no matching map type or stat key was found # --- Core Logic --- def process_library_for_materials(context, asset_library_root_override=None, nodegroup_blend_file_path_override=None): # Add nodegroup blend file path override global PROCESSED_ASSET_LIBRARY_ROOT # Allow modification of global global NODEGROUP_BLEND_FILE_PATH # Allow modification of global """ Scans the library, reads metadata, finds PBRSET node groups in the specified .blend file, and creates/updates materials linking to them. """ print("DEBUG: Script started.") start_time = time.time() print(f"\n--- Starting Material Creation from Node Groups ({time.strftime('%Y-%m-%d %H:%M:%S')}) ---") print(f" DEBUG: Received asset_library_root_override: {asset_library_root_override}") print(f" DEBUG: Received nodegroup_blend_file_path_override: {nodegroup_blend_file_path_override}") # --- Determine Asset Library Root --- if asset_library_root_override: PROCESSED_ASSET_LIBRARY_ROOT = asset_library_root_override print(f"Using asset library root from argument: '{PROCESSED_ASSET_LIBRARY_ROOT}'") elif not PROCESSED_ASSET_LIBRARY_ROOT: print("!!! ERROR: Processed asset library root not set in script and not provided via argument.") print("--- Script aborted. ---") return False print(f" DEBUG: Using final PROCESSED_ASSET_LIBRARY_ROOT: {PROCESSED_ASSET_LIBRARY_ROOT}") # --- Determine Nodegroup Blend File Path --- if nodegroup_blend_file_path_override: NODEGROUP_BLEND_FILE_PATH = nodegroup_blend_file_path_override print(f"Using nodegroup blend file path from argument: '{NODEGROUP_BLEND_FILE_PATH}'") elif not NODEGROUP_BLEND_FILE_PATH: print("!!! ERROR: Nodegroup blend file path not set in script and not provided via argument.") print("--- Script aborted. ---") return False print(f" DEBUG: Using final NODEGROUP_BLEND_FILE_PATH: {NODEGROUP_BLEND_FILE_PATH}") # --- Pre-run Checks --- print("Performing pre-run checks...") valid_setup = True # 1. Check Processed Asset Library Root Path root_path = Path(PROCESSED_ASSET_LIBRARY_ROOT) if not root_path.is_dir(): print(f"!!! ERROR: Processed asset library root directory not found or not a directory:") print(f"!!! '{PROCESSED_ASSET_LIBRARY_ROOT}'") valid_setup = False else: print(f" Processed Asset Library Root: '{root_path}'") # 2. Check Nodegroup Blend File Path pbrset_blend_file_path = Path(NODEGROUP_BLEND_FILE_PATH) if not pbrset_blend_file_path.is_file() or pbrset_blend_file_path.suffix.lower() != '.blend': print(f"!!! ERROR: Nodegroup blend file path is invalid or not a .blend file:") print(f"!!! '{NODEGROUP_BLEND_FILE_PATH}'") valid_setup = False else: print(f" Using PBRSET library file: '{pbrset_blend_file_path}'") # 3. Check Template Material and Placeholder Node template_mat = bpy.data.materials.get(TEMPLATE_MATERIAL_NAME) placeholder_node_found_in_template = False if not template_mat: print(f"!!! ERROR: Template material '{TEMPLATE_MATERIAL_NAME}' not found in this Blender file.") valid_setup = False elif not template_mat.use_nodes: print(f"!!! ERROR: Template material '{TEMPLATE_MATERIAL_NAME}' does not use nodes.") valid_setup = False else: placeholder_nodes = find_nodes_by_label(template_mat.node_tree, PLACEHOLDER_NODE_LABEL, 'ShaderNodeGroup') if not placeholder_nodes: print(f"!!! ERROR: Placeholder node '{PLACEHOLDER_NODE_LABEL}' not found in template material '{TEMPLATE_MATERIAL_NAME}'.") valid_setup = False else: placeholder_node_found_in_template = True print(f" Found Template Material: '{TEMPLATE_MATERIAL_NAME}' with placeholder '{PLACEHOLDER_NODE_LABEL}'") print(f" DEBUG: Template Material Found: {template_mat is not None}") print(f" DEBUG: Placeholder Node Found in Template: {placeholder_node_found_in_template}") if not valid_setup: print("\n--- Script aborted due to configuration errors. Please fix the issues above. ---") return False print("Pre-run checks passed.") # --- End Pre-run Checks --- # --- Initialize Counters --- metadata_files_found = 0 assets_processed = 0 assets_skipped = 0 materials_created = 0 node_groups_linked = 0 previews_set = 0 viewport_colors_set = 0 viewport_roughness_set = 0 viewport_metallic_set = 0 errors_encountered = 0 pbrset_groups_missing_in_library = 0 placeholder_nodes_missing = 0 library_link_errors = 0 # --- End Counters --- print(f"\nScanning for metadata files in '{root_path}'...") # --- Scan for metadata.json --- metadata_paths = [] for supplier_dir in root_path.iterdir(): if supplier_dir.is_dir(): for asset_dir in supplier_dir.iterdir(): if asset_dir.is_dir(): metadata_file = asset_dir / 'metadata.json' if metadata_file.is_file(): metadata_paths.append(metadata_file) metadata_files_found = len(metadata_paths) print(f"Found {metadata_files_found} metadata.json files.") print(f" DEBUG: Metadata paths found: {metadata_paths}") if metadata_files_found == 0: print("No metadata files found. Nothing to process.") print("--- Script Finished ---") return True # No work needed is considered success # --- Process Each Metadata File --- print(f" DEBUG: Starting metadata file loop. Found {len(metadata_paths)} files.") for metadata_path in metadata_paths: asset_dir_path = metadata_path.parent print(f"\n--- Processing Metadata: {metadata_path.relative_to(root_path)} ---") print(f" DEBUG: Processing file: {metadata_path}") try: with open(metadata_path, 'r', encoding='utf-8') as f: metadata = json.load(f) # --- Extract Key Info --- asset_name = metadata.get("asset_name") supplier_name = metadata.get("supplier_name") archetype = metadata.get("archetype") processed_resolutions = metadata.get("processed_map_resolutions", {}) merged_resolutions = metadata.get("merged_map_resolutions", {}) map_details = metadata.get("map_details", {}) image_stats_1k = metadata.get("image_stats_1k") all_map_resolutions = {**processed_resolutions, **merged_resolutions} if not asset_name: print(f" !!! ERROR: Metadata file is missing 'asset_name'. Skipping.") errors_encountered += 1 continue print(f" DEBUG: Valid metadata loaded for asset: {asset_name}") print(f" Asset Name: {asset_name}") # --- Determine Target Names --- target_material_name = f"{MATERIAL_NAME_PREFIX}{asset_name}" target_pbrset_group_name = f"{PBRSET_GROUP_PREFIX}{asset_name}" print(f" DEBUG: Target Material Name: {target_material_name}") print(f" DEBUG: Target PBRSET Group Name: {target_pbrset_group_name}") # --- Check if Material Already Exists (Skip Logic) --- if bpy.data.materials.get(target_material_name): print(f" Skipping asset '{asset_name}': Material '{target_material_name}' already exists.") assets_skipped += 1 continue # Move to the next metadata file print(f" DEBUG: Material '{target_material_name}' does not exist. Proceeding with creation.") # --- Create New Material --- print(f" Creating new material: '{target_material_name}'") print(f" DEBUG: Copying template material '{TEMPLATE_MATERIAL_NAME}'") material = template_mat.copy() if not material: print(f" !!! ERROR: Failed to copy template material '{TEMPLATE_MATERIAL_NAME}'. Skipping asset '{asset_name}'.") errors_encountered += 1 continue material.name = target_material_name materials_created += 1 print(f" DEBUG: Material '{material.name}' created.") # --- Find Placeholder Node --- if not material.use_nodes or not material.node_tree: print(f" !!! ERROR: Newly created material '{material.name}' does not use nodes or has no node tree. Skipping node linking.") placeholder_node = None # Ensure it's None else: placeholder_nodes = find_nodes_by_label(material.node_tree, PLACEHOLDER_NODE_LABEL, 'ShaderNodeGroup') if not placeholder_nodes: print(f" !!! WARNING: Placeholder node '{PLACEHOLDER_NODE_LABEL}' not found in material '{material.name}'. Cannot link PBRSET group.") placeholder_nodes_missing += 1 placeholder_node = None # Ensure it's None else: placeholder_node = placeholder_nodes[0] # Assume first is correct print(f" DEBUG: Found placeholder node '{placeholder_node.label}' in material '{material.name}'.") # --- Find and Link PBRSET Node Group from Library --- linked_pbrset_group = None if placeholder_node and pbrset_blend_file_path: # Only proceed if placeholder exists and library file is known print(f" DEBUG: Placeholder node exists and PBRSET library file path is known: {pbrset_blend_file_path}") # Check if the group is already linked in the current file existing_linked_group = bpy.data.node_groups.get(target_pbrset_group_name) # Check if the existing group's library filepath matches the target blend file path if existing_linked_group and existing_linked_group.library and bpy.path.abspath(existing_linked_group.library.filepath) == str(pbrset_blend_file_path.resolve()): linked_pbrset_group = existing_linked_group print(f" Found existing linked PBRSET group: '{linked_pbrset_group.name}'") else: # Link the node group from the external file print(f" Attempting to link PBRSET group '{target_pbrset_group_name}' from '{pbrset_blend_file_path.name}'...") try: with bpy.data.libraries.load(str(pbrset_blend_file_path.resolve()), link=True, relative=False) as (data_from, data_to): if target_pbrset_group_name in data_from.node_groups: data_to.node_groups = [target_pbrset_group_name] else: print(f" !!! ERROR: Node group '{target_pbrset_group_name}' not found in library file '{pbrset_blend_file_path.name}'.") pbrset_groups_missing_in_library += 1 # Verify linking was successful linked_pbrset_group = bpy.data.node_groups.get(target_pbrset_group_name) if not linked_pbrset_group or not linked_pbrset_group.library: print(f" !!! ERROR: Failed to link node group '{target_pbrset_group_name}'.") library_link_errors += 1 linked_pbrset_group = None # Ensure it's None on failure else: print(f" Successfully linked node group: '{linked_pbrset_group.name}'") except Exception as e_lib_load: print(f" !!! ERROR loading library or linking node group: {e_lib_load}") library_link_errors += 1 linked_pbrset_group = None # Ensure it's None on failure # --- Link Linked Node Group to Placeholder --- if placeholder_node and linked_pbrset_group: print(f" DEBUG: Attempting to link PBRSET group '{linked_pbrset_group.name}' to placeholder '{placeholder_node.label}'.") if placeholder_node.node_tree != linked_pbrset_group: try: placeholder_node.node_tree = linked_pbrset_group print(f" Linked PBRSET group '{linked_pbrset_group.name}' to placeholder '{placeholder_node.label}'.") node_groups_linked += 1 except TypeError as e_assign: print(f" !!! ERROR: Could not assign linked PBRSET group to placeholder '{placeholder_node.label}'. Is it a Group Node? Error: {e_assign}") errors_encountered += 1 except Exception as e_link: print(f" !!! UNEXPECTED ERROR linking PBRSET group to placeholder: {e_link}") errors_encountered += 1 elif placeholder_node and not linked_pbrset_group: print(f" Info: Cannot link node group as it was not found or failed to link.") # No 'else' needed if placeholder_node is None, error already logged # --- Mark Material as Asset --- if not material.asset_data: print(f" DEBUG: Marking material '{material.name}' as asset.") try: material.asset_mark() print(f" Marked material '{material.name}' as asset.") except Exception as e_mark: print(f" !!! ERROR: Failed to mark material '{material.name}' as asset: {e_mark}") # --- Copy Asset Tags --- if material.asset_data and linked_pbrset_group and linked_pbrset_group.asset_data: print(f" DEBUG: Copying asset tags from PBRSET group to material.") tags_copied_count = 0 if supplier_name: if add_tag_if_new(material.asset_data, supplier_name): tags_copied_count += 1 if archetype: if add_tag_if_new(material.asset_data, archetype): tags_copied_count += 1 # Copy other tags from PBRSET group for ng_tag in linked_pbrset_group.asset_data.tags: if add_tag_if_new(material.asset_data, ng_tag.name): tags_copied_count += 1 # --- Set Custom Preview --- ref_image_path = None for ref_map_type in REFERENCE_MAP_TYPES: if ref_map_type in all_map_resolutions: available_resolutions = all_map_resolutions[ref_map_type] lowest_res = None for res_pref in REFERENCE_RESOLUTION_ORDER: if res_pref in available_resolutions: lowest_res = res_pref break if lowest_res: ref_map_details = map_details.get(ref_map_type, {}) ref_format = ref_map_details.get("output_format") ref_image_path = reconstruct_image_path_with_fallback( asset_dir_path=asset_dir_path, asset_name=asset_name, map_type=ref_map_type, resolution=lowest_res, primary_format=ref_format ) if ref_image_path: break if ref_image_path and material.asset_data: print(f" Attempting to set preview from: {Path(ref_image_path).name}") try: with context.temp_override(id=material): bpy.ops.ed.lib_id_load_custom_preview(filepath=ref_image_path) print(f" Successfully set custom preview for material.") previews_set += 1 except RuntimeError as e_op: print(f" !!! ERROR running preview operator for material '{material.name}': {e_op}") errors_encountered += 1 except Exception as e_preview: print(f" !!! UNEXPECTED ERROR setting custom preview for material: {e_preview}") errors_encountered += 1 elif not material.asset_data: print(f" Info: Cannot set preview for '{material.name}' as it's not marked as an asset.") else: print(f" Info: Could not find suitable reference image ({REFERENCE_MAP_TYPES} at {REFERENCE_RESOLUTION_ORDER}) for preview.") # --- Set Viewport Properties from Stats --- if image_stats_1k and isinstance(image_stats_1k, dict): print(f" DEBUG: Applying viewport properties from stats.") # Viewport Color color_mean = get_stat_value(image_stats_1k, VIEWPORT_COLOR_MAP_TYPES, 'mean') if isinstance(color_mean, list) and len(color_mean) >= 3: color_rgba = (*color_mean[:3], 1.0) print(f" Debug: Raw color_mean from metadata: {color_mean[:3]}") if tuple(material.diffuse_color[:3]) != tuple(color_rgba[:3]): material.diffuse_color = color_rgba print(f" Set viewport color: {color_rgba[:3]}") viewport_colors_set += 1 # Viewport Roughness & Metallic Check roughness_mean = get_stat_value(image_stats_1k, VIEWPORT_ROUGHNESS_MAP_TYPES, 'mean') metallic_mean = get_stat_value(image_stats_1k, VIEWPORT_METALLIC_MAP_TYPES, 'mean') metal_map_found = metallic_mean is not None # Roughness if roughness_mean is not None: rough_val = roughness_mean[0] if isinstance(roughness_mean, list) else roughness_mean if isinstance(rough_val, (float, int)): final_roughness = float(rough_val) if not metal_map_found: final_roughness = 1.0 - final_roughness final_roughness = max(0.0, min(1.0, final_roughness)) if abs(material.roughness - final_roughness) > 0.001: material.roughness = final_roughness print(f" Set viewport roughness: {final_roughness:.3f}") viewport_roughness_set += 1 # Metallic if metal_map_found: metal_val = metallic_mean[0] if isinstance(metallic_mean, list) else metallic_mean if isinstance(metal_val, (float, int)): final_metallic = max(0.0, min(1.0, float(metal_val))) if abs(material.metallic - final_metallic) > 0.001: material.metallic = final_metallic print(f" Set viewport metallic: {final_metallic:.3f}") viewport_metallic_set += 1 else: if material.metallic != 0.0: material.metallic = 0.0 print(f" Set viewport metallic to default: 0.0 (No metal map found)") viewport_metallic_set += 1 assets_processed += 1 # Count assets where processing was attempted (even if errors occurred later) except FileNotFoundError: print(f" !!! ERROR: Metadata file not found (should not happen if scan worked): {metadata_path}") errors_encountered += 1 except json.JSONDecodeError: print(f" !!! ERROR: Invalid JSON in metadata file: {metadata_path}") errors_encountered += 1 except Exception as e_main_loop: print(f" !!! UNEXPECTED ERROR processing asset from {metadata_path}: {e_main_loop}") import traceback traceback.print_exc() errors_encountered += 1 # --- End Metadata File Loop --- # --- Final Summary --- end_time = time.time() duration = end_time - start_time print("\n--- Material Creation Script Finished ---") print(f"Duration: {duration:.2f} seconds") print(f"Metadata Files Found: {metadata_files_found}") print(f"Assets Processed/Attempted: {assets_processed}") print(f"Assets Skipped (Already Exist): {assets_skipped}") print(f"Materials Created: {materials_created}") print(f"PBRSET Node Groups Linked: {node_groups_linked}") print(f"Material Previews Set: {previews_set}") print(f"Viewport Colors Set: {viewport_colors_set}") print(f"Viewport Roughness Set: {viewport_roughness_set}") print(f"Viewport Metallic Set: {viewport_metallic_set}") if pbrset_groups_missing_in_library > 0: print(f"!!! PBRSET Node Groups Missing in Library File: {pbrset_groups_missing_in_library} !!!") if library_link_errors > 0: print(f"!!! Library Link Errors: {library_link_errors} !!!") if placeholder_nodes_missing > 0: print(f"!!! Placeholder Nodes Missing in Materials: {placeholder_nodes_missing} !!!") if errors_encountered > 0: print(f"!!! Other Errors Encountered: {errors_encountered} !!!") print("---------------------------------------") # --- Explicit Save --- print(f" DEBUG: Attempting explicit save for file: {bpy.data.filepath}") try: bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) print("\n--- Explicitly saved the .blend file. ---") except Exception as e_save: print(f"\n!!! ERROR explicitly saving .blend file: {e_save} !!!") # Note: We don't have an errors_encountered counter in this script currently. # If needed, we could add one or just rely on the printout. # For now, just printing the error is sufficient for debugging. return True # --- Execution Block --- if __name__ == "__main__": # Ensure we are running within Blender try: import bpy import sys except ImportError: print("!!! ERROR: This script must be run from within Blender. !!!") else: # --- Argument Parsing for Asset Library Root and Nodegroup Blend File --- asset_root_arg = None nodegroup_blend_file_arg = None try: # Blender arguments passed after '--' appear in sys.argv if "--" in sys.argv: args_after_dash = sys.argv[sys.argv.index("--") + 1:] if len(args_after_dash) >= 1: asset_root_arg = args_after_dash[0] print(f"Found asset library root argument: {asset_root_arg}") if len(args_after_dash) >= 2: # Check for second argument nodegroup_blend_file_arg = args_after_dash[1] print(f"Found nodegroup blend file path argument: {nodegroup_blend_file_arg}") else: print("Info: '--' found but not enough arguments after it for nodegroup blend file.") except Exception as e: print(f"Error parsing command line arguments: {e}") # --- End Argument Parsing --- process_library_for_materials(bpy.context, asset_library_root_override=asset_root_arg, nodegroup_blend_file_path_override=nodegroup_blend_file_arg)