{
    "sourceFile": "blenderscripts/create_materials.py",
    "activeCommit": 0,
    "commits": [
        {
            "activePatchIndex": 7,
            "patches": [
                {
                    "date": 1745258817112,
                    "content": "Index: \n===================================================================\n--- \n+++ \n"
                },
                {
                    "date": 1745259821809,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,10 +1,15 @@\n # blenderscripts/create_materials.py\r\n-# Version: 1.0\r\n+# Version: 1.1\r\n # Description: Scans a library processed by the Asset Processor Tool,\r\n-#              reads metadata.json files, finds corresponding PBRSET node groups,\r\n-#              and creates/updates Blender materials linking to those node groups.\r\n+#              reads metadata.json files, finds corresponding PBRSET node groups\r\n+#              in a specified Blender Asset Library, and creates Blender materials\r\n+#              linking to those node groups. Skips assets if material already exists.\r\n #              Sets material viewport properties and custom previews based on metadata.\r\n+# Changes v1.1:\r\n+#   - Added logic to link PBRSET node groups from an external asset library.\r\n+#   - Added logic to skip processing if the target material already exists.\r\n+#   - Added configuration for the PBRSET asset library name.\r\n \r\n import bpy\r\n import os\r\n import json\r\n@@ -18,8 +23,12 @@\n # Example: r\"G:\\Assets\\Processed\"\r\n # IMPORTANT: This should point to the base directory containing supplier folders (e.g., Poliigon)\r\n PROCESSED_ASSET_LIBRARY_ROOT = r\"G:\\02 Content\\10-19 Content\\13 Textures Power of Two\\Asset_Processor_Output\" # <<< CHANGE THIS PATH!\r\n \r\n+# Name of the Blender Asset Library (configured in Blender Preferences)\r\n+# that contains the PBRSET node groups created by create_nodegroups.py\r\n+PBRSET_ASSET_LIBRARY_NAME = \"Nodes-Linked\" # <<< CHANGE THIS NAME!\r\n+\r\n # Name of the required template material in the Blender file\r\n TEMPLATE_MATERIAL_NAME = \"Template_PBRMaterial\"\r\n \r\n # Label of the placeholder Group node within the template material's node tree\r\n@@ -194,27 +203,46 @@\n # --- Core Logic ---\r\n \r\n def process_library_for_materials(context):\r\n     \"\"\"\r\n-    Scans the library, reads metadata, finds PBRSET node groups,\r\n-    and creates/updates materials linking to them.\r\n+    Scans the library, reads metadata, finds PBRSET node groups in the specified\r\n+    asset library, and creates/updates materials linking to them.\r\n     \"\"\"\r\n     start_time = time.time()\r\n     print(f\"\\n--- Starting Material Creation from Node Groups ({time.strftime('%Y-%m-%d %H:%M:%S')}) ---\")\r\n \r\n     # --- Pre-run Checks ---\r\n     print(\"Performing pre-run checks...\")\r\n     valid_setup = True\r\n-    # 1. Check Library Root Path\r\n+    # 1. Check Processed Asset Library Root Path\r\n     root_path = Path(PROCESSED_ASSET_LIBRARY_ROOT)\r\n     if not root_path.is_dir():\r\n         print(f\"!!! ERROR: Processed asset library root directory not found or not a directory:\")\r\n         print(f\"!!!        '{PROCESSED_ASSET_LIBRARY_ROOT}'\")\r\n         valid_setup = False\r\n     else:\r\n-        print(f\"   Asset Library Root: '{root_path}'\")\r\n+        print(f\"   Processed Asset Library Root: '{root_path}'\")\r\n \r\n-    # 2. Check Template Material and Placeholder Node\r\n+    # 2. Check PBRSET Asset Library Name and Path\r\n+    pbrset_library = context.preferences.filepaths.asset_libraries.get(PBRSET_ASSET_LIBRARY_NAME)\r\n+    pbrset_library_path = None\r\n+    if not pbrset_library:\r\n+        print(f\"!!! ERROR: PBRSET Asset Library '{PBRSET_ASSET_LIBRARY_NAME}' not found in Blender Preferences.\")\r\n+        valid_setup = False\r\n+    else:\r\n+        pbrset_library_path_str = bpy.path.abspath(pbrset_library.path)\r\n+        pbrset_library_path = Path(pbrset_library_path_str)\r\n+        # Check if the library path points to a single .blend file or a directory\r\n+        if not pbrset_library_path.exists() or (not pbrset_library_path.is_file() and not pbrset_library_path.is_dir()):\r\n+             print(f\"!!! ERROR: Path for PBRSET Asset Library '{PBRSET_ASSET_LIBRARY_NAME}' is invalid or does not exist:\")\r\n+             print(f\"!!!        '{pbrset_library_path_str}'\")\r\n+             valid_setup = False\r\n+             pbrset_library_path = None # Ensure it's None if invalid\r\n+        else:\r\n+             print(f\"   PBRSET Asset Library: '{PBRSET_ASSET_LIBRARY_NAME}' -> '{pbrset_library_path_str}'\")\r\n+\r\n+\r\n+    # 3. Check Template Material and Placeholder Node\r\n     template_mat = bpy.data.materials.get(TEMPLATE_MATERIAL_NAME)\r\n     placeholder_node_found_in_template = False\r\n     if not template_mat:\r\n         print(f\"!!! ERROR: Template material '{TEMPLATE_MATERIAL_NAME}' not found in this Blender file.\")\r\n@@ -239,18 +267,20 @@\n \r\n     # --- Initialize Counters ---\r\n     metadata_files_found = 0\r\n     assets_processed = 0\r\n+    assets_skipped = 0\r\n     materials_created = 0\r\n-    materials_updated = 0\r\n+    # materials_updated = 0 # Not updating existing materials anymore\r\n     node_groups_linked = 0\r\n     previews_set = 0\r\n     viewport_colors_set = 0\r\n     viewport_roughness_set = 0\r\n     viewport_metallic_set = 0\r\n     errors_encountered = 0\r\n-    pbrset_groups_missing = 0\r\n+    pbrset_groups_missing_in_library = 0\r\n     placeholder_nodes_missing = 0\r\n+    library_link_errors = 0\r\n     # --- End Counters ---\r\n \r\n     print(f\"\\nScanning for metadata files in '{root_path}'...\")\r\n \r\n@@ -271,8 +301,40 @@\n         print(\"No metadata files found. Nothing to process.\")\r\n         print(\"--- Script Finished ---\")\r\n         return True # No work needed is considered success\r\n \r\n+    # --- Determine the .blend file path for the PBRSET library ---\r\n+    # If the library path is a directory, we assume the node groups are in a file named like the library\r\n+    # This might need adjustment based on how the user organizes their library file(s)\r\n+    pbrset_blend_file_path = None\r\n+    if pbrset_library_path:\r\n+        if pbrset_library_path.is_file() and pbrset_library_path.suffix.lower() == '.blend':\r\n+            pbrset_blend_file_path = str(pbrset_library_path)\r\n+        elif pbrset_library_path.is_dir():\r\n+            # Attempt to find a .blend file named like the library or a common name\r\n+            potential_blend_name = f\"{PBRSET_ASSET_LIBRARY_NAME}.blend\"\r\n+            potential_path = pbrset_library_path / potential_blend_name\r\n+            if potential_path.is_file():\r\n+                 pbrset_blend_file_path = str(potential_path)\r\n+            else:\r\n+                 # Fallback: look for any .blend file (take the first one found) - less reliable\r\n+                 found_blend = next(pbrset_library_path.glob('*.blend'), None)\r\n+                 if found_blend:\r\n+                      pbrset_blend_file_path = str(found_blend)\r\n+                      print(f\"    Warning: PBRSET library path is a directory. Using first found .blend file: {found_blend.name}\")\r\n+                 else:\r\n+                      print(f\"!!! ERROR: PBRSET library path is a directory, but no .blend file found inside.\")\r\n+                      errors_encountered += 1 # Count as error if no blend file found\r\n+\r\n+        if pbrset_blend_file_path:\r\n+             print(f\"   Using PBRSET library file: '{pbrset_blend_file_path}'\")\r\n+        else:\r\n+             print(f\"!!! ERROR: Could not determine the .blend file for the PBRSET library '{PBRSET_ASSET_LIBRARY_NAME}'.\")\r\n+             # No point continuing if we can't find the source blend file\r\n+             print(\"\\n--- Script aborted: Cannot find PBRSET library .blend file. ---\")\r\n+             return False\r\n+\r\n+\r\n     # --- Process Each Metadata File ---\r\n     for metadata_path in metadata_paths:\r\n         asset_dir_path = metadata_path.parent\r\n         print(f\"\\n--- Processing Metadata: {metadata_path.relative_to(root_path)} ---\")\r\n@@ -283,18 +345,15 @@\n             # --- Extract Key Info ---\r\n             asset_name = metadata.get(\"asset_name\")\r\n             supplier_name = metadata.get(\"supplier_name\")\r\n             archetype = metadata.get(\"archetype\")\r\n-            # Get map info needed for preview path reconstruction\r\n             processed_resolutions = metadata.get(\"processed_map_resolutions\", {})\r\n             merged_resolutions = metadata.get(\"merged_map_resolutions\", {})\r\n             map_details = metadata.get(\"map_details\", {})\r\n-            image_stats_1k = metadata.get(\"image_stats_1k\") # Dict: {map_type: {stats}}\r\n+            image_stats_1k = metadata.get(\"image_stats_1k\")\r\n \r\n-            # Combine processed and merged maps to find reference map\r\n             all_map_resolutions = {**processed_resolutions, **merged_resolutions}\r\n \r\n-            # Validate essential data\r\n             if not asset_name:\r\n                 print(f\"    !!! ERROR: Metadata file is missing 'asset_name'. Skipping.\")\r\n                 errors_encountered += 1\r\n                 continue\r\n@@ -304,83 +363,112 @@\n             # --- Determine Target Names ---\r\n             target_material_name = f\"{MATERIAL_NAME_PREFIX}{asset_name}\"\r\n             target_pbrset_group_name = f\"{PBRSET_GROUP_PREFIX}{asset_name}\"\r\n \r\n-            # --- Find PBRSET Node Group ---\r\n-            pbrset_group = bpy.data.node_groups.get(target_pbrset_group_name)\r\n-            if not pbrset_group:\r\n-                print(f\"    !!! WARNING: PBRSET Node Group '{target_pbrset_group_name}' not found. Cannot create/update material. Run create_nodegroups.py first?\")\r\n-                pbrset_groups_missing += 1\r\n-                continue # Skip this asset if the required node group doesn't exist\r\n+            # --- Check if Material Already Exists (Skip Logic) ---\r\n+            if bpy.data.materials.get(target_material_name):\r\n+                print(f\"  Skipping asset '{asset_name}': Material '{target_material_name}' already exists.\")\r\n+                assets_skipped += 1\r\n+                continue # Move to the next metadata file\r\n \r\n-            # --- Find or Create Material ---\r\n-            material = bpy.data.materials.get(target_material_name)\r\n-            is_new_material = False\r\n+            # --- Create New Material ---\r\n+            print(f\"  Creating new material: '{target_material_name}'\")\r\n+            material = template_mat.copy()\r\n+            if not material:\r\n+                print(f\"    !!! ERROR: Failed to copy template material '{TEMPLATE_MATERIAL_NAME}'. Skipping asset '{asset_name}'.\")\r\n+                errors_encountered += 1\r\n+                continue\r\n+            material.name = target_material_name\r\n+            materials_created += 1\r\n \r\n-            if material is None:\r\n-                print(f\"  Creating new material: '{target_material_name}'\")\r\n-                material = template_mat.copy()\r\n-                if not material:\r\n-                    print(f\"    !!! ERROR: Failed to copy template material '{TEMPLATE_MATERIAL_NAME}'. Skipping asset '{asset_name}'.\")\r\n-                    errors_encountered += 1\r\n-                    continue\r\n-                material.name = target_material_name\r\n-                materials_created += 1\r\n-                is_new_material = True\r\n-            else:\r\n-                print(f\"  Updating existing material: '{target_material_name}'\")\r\n-                materials_updated += 1\r\n-\r\n-            # Ensure material uses nodes\r\n+            # --- Find Placeholder Node ---\r\n             if not material.use_nodes or not material.node_tree:\r\n-                 print(f\"    !!! ERROR: Material '{material.name}' does not use nodes or has no node tree. Skipping node linking.\")\r\n-                 # Continue to set other properties if possible\r\n+                 print(f\"    !!! ERROR: Newly created material '{material.name}' does not use nodes or has no node tree. Skipping node linking.\")\r\n+                 placeholder_node = None # Ensure it's None\r\n             else:\r\n-                # --- Find Placeholder Node ---\r\n                 placeholder_nodes = find_nodes_by_label(material.node_tree, PLACEHOLDER_NODE_LABEL, 'ShaderNodeGroup')\r\n                 if not placeholder_nodes:\r\n                     print(f\"    !!! WARNING: Placeholder node '{PLACEHOLDER_NODE_LABEL}' not found in material '{material.name}'. Cannot link PBRSET group.\")\r\n                     placeholder_nodes_missing += 1\r\n+                    placeholder_node = None # Ensure it's None\r\n                 else:\r\n                     placeholder_node = placeholder_nodes[0] # Assume first is correct\r\n-                    # --- Link Node Group to Placeholder ---\r\n-                    if placeholder_node.node_tree != pbrset_group:\r\n-                        try:\r\n-                            placeholder_node.node_tree = pbrset_group\r\n-                            print(f\"    Linked PBRSET group '{pbrset_group.name}' to placeholder '{placeholder_node.label}'.\")\r\n-                            node_groups_linked += 1\r\n-                        except TypeError as e_assign:\r\n-                            print(f\"    !!! ERROR: Could not assign PBRSET group to placeholder '{placeholder_node.label}'. Is it a Group Node? Error: {e_assign}\")\r\n-                            errors_encountered += 1\r\n-                        except Exception as e_link:\r\n-                             print(f\"    !!! UNEXPECTED ERROR linking PBRSET group: {e_link}\")\r\n-                             errors_encountered += 1\r\n \r\n+            # --- Find and Link PBRSET Node Group from Library ---\r\n+            linked_pbrset_group = None\r\n+            if placeholder_node and pbrset_blend_file_path: # Only proceed if placeholder exists and library file is known\r\n+                # Check if the group is already linked in the current file\r\n+                existing_linked_group = bpy.data.node_groups.get(target_pbrset_group_name)\r\n+                if existing_linked_group and existing_linked_group.library and bpy.path.abspath(existing_linked_group.library.filepath) == pbrset_blend_file_path:\r\n+                     linked_pbrset_group = existing_linked_group\r\n+                     print(f\"    Found existing linked PBRSET group: '{linked_pbrset_group.name}'\")\r\n+                else:\r\n+                    # Link the node group from the external file\r\n+                    print(f\"    Attempting to link PBRSET group '{target_pbrset_group_name}' from '{Path(pbrset_blend_file_path).name}'...\")\r\n+                    try:\r\n+                        with bpy.data.libraries.load(pbrset_blend_file_path, link=True, relative=False) as (data_from, data_to):\r\n+                            if target_pbrset_group_name in data_from.node_groups:\r\n+                                data_to.node_groups = [target_pbrset_group_name]\r\n+                            else:\r\n+                                print(f\"      !!! ERROR: Node group '{target_pbrset_group_name}' not found in library file.\")\r\n+                                pbrset_groups_missing_in_library += 1\r\n+\r\n+                        # Verify linking was successful\r\n+                        linked_pbrset_group = bpy.data.node_groups.get(target_pbrset_group_name)\r\n+                        if not linked_pbrset_group or not linked_pbrset_group.library:\r\n+                            print(f\"      !!! ERROR: Failed to link node group '{target_pbrset_group_name}'.\")\r\n+                            library_link_errors += 1\r\n+                            linked_pbrset_group = None # Ensure it's None on failure\r\n+                        else:\r\n+                             print(f\"      Successfully linked node group: '{linked_pbrset_group.name}'\")\r\n+\r\n+                    except Exception as e_lib_load:\r\n+                        print(f\"      !!! ERROR loading library or linking node group: {e_lib_load}\")\r\n+                        library_link_errors += 1\r\n+                        linked_pbrset_group = None # Ensure it's None on failure\r\n+\r\n+            # --- Link Linked Node Group to Placeholder ---\r\n+            if placeholder_node and linked_pbrset_group:\r\n+                if placeholder_node.node_tree != linked_pbrset_group:\r\n+                    try:\r\n+                        placeholder_node.node_tree = linked_pbrset_group\r\n+                        print(f\"    Linked PBRSET group '{linked_pbrset_group.name}' to placeholder '{placeholder_node.label}'.\")\r\n+                        node_groups_linked += 1\r\n+                    except TypeError as e_assign:\r\n+                        print(f\"    !!! ERROR: Could not assign linked PBRSET group to placeholder '{placeholder_node.label}'. Is it a Group Node? Error: {e_assign}\")\r\n+                        errors_encountered += 1\r\n+                    except Exception as e_link:\r\n+                         print(f\"    !!! UNEXPECTED ERROR linking PBRSET group to placeholder: {e_link}\")\r\n+                         errors_encountered += 1\r\n+            elif placeholder_node and not linked_pbrset_group:\r\n+                 print(f\"    Info: Cannot link node group as it was not found or failed to link.\")\r\n+            # No 'else' needed if placeholder_node is None, error already logged\r\n+\r\n+\r\n             # --- Mark Material as Asset ---\r\n             if not material.asset_data:\r\n                 try:\r\n                     material.asset_mark()\r\n                     print(f\"    Marked material '{material.name}' as asset.\")\r\n                 except Exception as e_mark:\r\n                     print(f\"    !!! ERROR: Failed to mark material '{material.name}' as asset: {e_mark}\")\r\n-                    # Continue if possible\r\n \r\n             # --- Copy Asset Tags ---\r\n-            if material.asset_data and pbrset_group.asset_data:\r\n+            if material.asset_data and linked_pbrset_group and linked_pbrset_group.asset_data:\r\n                 tags_copied_count = 0\r\n                 if supplier_name:\r\n                     if add_tag_if_new(material.asset_data, supplier_name): tags_copied_count += 1\r\n                 if archetype:\r\n                     if add_tag_if_new(material.asset_data, archetype): tags_copied_count += 1\r\n-                # Copy other tags from PBRSET group if needed\r\n-                for ng_tag in pbrset_group.asset_data.tags:\r\n+                # Copy other tags from PBRSET group\r\n+                for ng_tag in linked_pbrset_group.asset_data.tags:\r\n                      if add_tag_if_new(material.asset_data, ng_tag.name): tags_copied_count += 1\r\n                 # if tags_copied_count > 0: print(f\"      Copied {tags_copied_count} asset tags to material.\") # Optional info\r\n-            # else: print(f\"    Warn: Cannot copy tags. Material asset_data: {material.asset_data is not None}, Group asset_data: {pbrset_group.asset_data is not None}\") # Debug\r\n+            # else: print(f\"    Warn: Cannot copy tags. Material asset_data: {material.asset_data is not None}, Linked Group: {linked_pbrset_group}, Group asset_data: {linked_pbrset_group.asset_data if linked_pbrset_group else None}\") # Debug\r\n \r\n+\r\n             # --- Set Custom Preview ---\r\n             ref_image_path = None\r\n-            # Find reference image path (similar logic to create_nodegroups.py)\r\n             for ref_map_type in REFERENCE_MAP_TYPES:\r\n                 if ref_map_type in all_map_resolutions:\r\n                     available_resolutions = all_map_resolutions[ref_map_type]\r\n                     lowest_res = None\r\n@@ -398,14 +486,13 @@\n                             resolution=lowest_res,\r\n                             primary_format=ref_format\r\n                         )\r\n                         if ref_image_path:\r\n-                            break # Found a suitable reference image path\r\n+                            break\r\n \r\n             if ref_image_path and material.asset_data:\r\n                 print(f\"    Attempting to set preview from: {Path(ref_image_path).name}\")\r\n                 try:\r\n-                    # Ensure the ID (material) is the active one for the operator context\r\n                     with context.temp_override(id=material):\r\n                          bpy.ops.ed.lib_id_load_custom_preview(filepath=ref_image_path)\r\n                     print(f\"      Successfully set custom preview for material.\")\r\n                     previews_set += 1\r\n@@ -425,63 +512,49 @@\n             if image_stats_1k and isinstance(image_stats_1k, dict):\r\n                 # Viewport Color\r\n                 color_mean = get_stat_value(image_stats_1k, VIEWPORT_COLOR_MAP_TYPES, 'mean')\r\n                 if isinstance(color_mean, list) and len(color_mean) >= 3:\r\n-                    # Add alpha if missing (default to 1.0)\r\n                     color_rgba = (*color_mean[:3], 1.0)\r\n-                    # Check if update is needed (compare first 3 elements)\r\n                     if tuple(material.diffuse_color[:3]) != tuple(color_rgba[:3]):\r\n                          material.diffuse_color = color_rgba\r\n                          print(f\"    Set viewport color: {color_rgba[:3]}\")\r\n                          viewport_colors_set += 1\r\n-                # else: print(f\"    Info: Could not find valid 'mean' stat for viewport color in {VIEWPORT_COLOR_MAP_TYPES}.\") # Optional\r\n \r\n                 # Viewport Roughness & Metallic Check\r\n                 roughness_mean = get_stat_value(image_stats_1k, VIEWPORT_ROUGHNESS_MAP_TYPES, 'mean')\r\n                 metallic_mean = get_stat_value(image_stats_1k, VIEWPORT_METALLIC_MAP_TYPES, 'mean')\r\n                 metal_map_found = metallic_mean is not None\r\n \r\n                 # Roughness\r\n                 if roughness_mean is not None:\r\n-                    # Extract float value if it's a list (e.g., [0.5])\r\n                     rough_val = roughness_mean[0] if isinstance(roughness_mean, list) else roughness_mean\r\n                     if isinstance(rough_val, (float, int)):\r\n                         final_roughness = float(rough_val)\r\n-                        # Invert if metal map is NOT found (Gloss workflow)\r\n                         if not metal_map_found:\r\n                             final_roughness = 1.0 - final_roughness\r\n-                            # print(f\"      Inverting roughness (no metal map): {rough_val:.3f} -> {final_roughness:.3f}\") # Debug\r\n-                        final_roughness = max(0.0, min(1.0, final_roughness)) # Clamp\r\n-                        # Check if update is needed\r\n+                        final_roughness = max(0.0, min(1.0, final_roughness))\r\n                         if abs(material.roughness - final_roughness) > 0.001:\r\n                              material.roughness = final_roughness\r\n                              print(f\"    Set viewport roughness: {final_roughness:.3f}\")\r\n                              viewport_roughness_set += 1\r\n-                    # else: print(f\"    Info: Roughness 'mean' stat is not a valid number: {roughness_mean}\") # Optional\r\n-                # else: print(f\"    Info: Could not find 'mean' stat for viewport roughness in {VIEWPORT_ROUGHNESS_MAP_TYPES}.\") # Optional\r\n \r\n                 # Metallic\r\n                 if metal_map_found:\r\n                     metal_val = metallic_mean[0] if isinstance(metallic_mean, list) else metallic_mean\r\n                     if isinstance(metal_val, (float, int)):\r\n-                        final_metallic = max(0.0, min(1.0, float(metal_val))) # Clamp\r\n-                        # Check if update is needed\r\n+                        final_metallic = max(0.0, min(1.0, float(metal_val)))\r\n                         if abs(material.metallic - final_metallic) > 0.001:\r\n                              material.metallic = final_metallic\r\n                              print(f\"    Set viewport metallic: {final_metallic:.3f}\")\r\n                              viewport_metallic_set += 1\r\n-                    # else: print(f\"    Info: Metallic 'mean' stat is not a valid number: {metallic_mean}\") # Optional\r\n                 else:\r\n-                    # If metal map wasn't found, ensure metallic is 0.0\r\n                     if material.metallic != 0.0:\r\n                         material.metallic = 0.0\r\n                         print(f\"    Set viewport metallic to default: 0.0 (No metal map found)\")\r\n-                        viewport_metallic_set += 1 # Count setting default as a set action\r\n+                        viewport_metallic_set += 1\r\n \r\n-            # else: print(f\"    Warn: 'image_stats_1k' missing or invalid in metadata. Cannot set viewport properties.\") # Optional\r\n+            assets_processed += 1 # Count assets where processing was attempted (even if errors occurred later)\r\n \r\n-            assets_processed += 1\r\n-\r\n         except FileNotFoundError:\r\n             print(f\"    !!! ERROR: Metadata file not found (should not happen if scan worked): {metadata_path}\")\r\n             errors_encountered += 1\r\n         except json.JSONDecodeError:\r\n@@ -489,11 +562,10 @@\n             errors_encountered += 1\r\n         except Exception as e_main_loop:\r\n             print(f\"    !!! UNEXPECTED ERROR processing asset from {metadata_path}: {e_main_loop}\")\r\n             import traceback\r\n-            traceback.print_exc() # Print detailed traceback for debugging\r\n+            traceback.print_exc()\r\n             errors_encountered += 1\r\n-            # Continue to the next asset\r\n \r\n     # --- End Metadata File Loop ---\r\n \r\n     # --- Final Summary ---\r\n@@ -502,17 +574,20 @@\n     print(\"\\n--- Material Creation Script Finished ---\")\r\n     print(f\"Duration: {duration:.2f} seconds\")\r\n     print(f\"Metadata Files Found: {metadata_files_found}\")\r\n     print(f\"Assets Processed/Attempted: {assets_processed}\")\r\n+    print(f\"Assets Skipped (Already Exist): {assets_skipped}\")\r\n     print(f\"Materials Created: {materials_created}\")\r\n-    print(f\"Materials Updated: {materials_updated}\")\r\n+    # print(f\"Materials Updated: {materials_updated}\") # Removed as we skip existing\r\n     print(f\"PBRSET Node Groups Linked: {node_groups_linked}\")\r\n     print(f\"Material Previews Set: {previews_set}\")\r\n     print(f\"Viewport Colors Set: {viewport_colors_set}\")\r\n     print(f\"Viewport Roughness Set: {viewport_roughness_set}\")\r\n     print(f\"Viewport Metallic Set: {viewport_metallic_set}\")\r\n-    if pbrset_groups_missing > 0:\r\n-        print(f\"!!! PBRSET Node Groups Missing: {pbrset_groups_missing} !!!\")\r\n+    if pbrset_groups_missing_in_library > 0:\r\n+        print(f\"!!! PBRSET Node Groups Missing in Library '{PBRSET_ASSET_LIBRARY_NAME}': {pbrset_groups_missing_in_library} !!!\")\r\n+    if library_link_errors > 0:\r\n+         print(f\"!!! Library Link Errors: {library_link_errors} !!!\")\r\n     if placeholder_nodes_missing > 0:\r\n          print(f\"!!! Placeholder Nodes Missing in Materials: {placeholder_nodes_missing} !!!\")\r\n     if errors_encountered > 0:\r\n         print(f\"!!! Other Errors Encountered: {errors_encountered} !!!\")\r\n@@ -526,10 +601,8 @@\n if __name__ == \"__main__\":\r\n     # Ensure we are running within Blender\r\n     try:\r\n         import bpy\r\n-        # import base64 # Already imported above\r\n     except ImportError:\r\n         print(\"!!! ERROR: This script must be run from within Blender. !!!\")\r\n     else:\r\n-        # Pass Blender's current context to the processing function\r\n         process_library_for_materials(bpy.context)\n\\ No newline at end of file\n"
                },
                {
                    "date": 1745261978731,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -201,16 +201,26 @@\n \r\n \r\n # --- Core Logic ---\r\n \r\n-def process_library_for_materials(context):\r\n+def process_library_for_materials(context, asset_library_root_override=None): # Add override parameter\r\n+    global PROCESSED_ASSET_LIBRARY_ROOT # Allow modification of global\r\n     \"\"\"\r\n     Scans the library, reads metadata, finds PBRSET node groups in the specified\r\n     asset library, and creates/updates materials linking to them.\r\n     \"\"\"\r\n     start_time = time.time()\r\n     print(f\"\\n--- Starting Material Creation from Node Groups ({time.strftime('%Y-%m-%d %H:%M:%S')}) ---\")\r\n \r\n+    # --- Determine Asset Library Root ---\r\n+    if asset_library_root_override:\r\n+        PROCESSED_ASSET_LIBRARY_ROOT = asset_library_root_override\r\n+        print(f\"Using asset library root from argument: '{PROCESSED_ASSET_LIBRARY_ROOT}'\")\r\n+    elif not PROCESSED_ASSET_LIBRARY_ROOT:\r\n+        print(\"!!! ERROR: Processed asset library root not set in script and not provided via argument.\")\r\n+        print(\"--- Script aborted. ---\")\r\n+        return False\r\n+\r\n     # --- Pre-run Checks ---\r\n     print(\"Performing pre-run checks...\")\r\n     valid_setup = True\r\n     # 1. Check Processed Asset Library Root Path\r\n@@ -513,8 +523,9 @@\n                 # Viewport Color\r\n                 color_mean = get_stat_value(image_stats_1k, VIEWPORT_COLOR_MAP_TYPES, 'mean')\r\n                 if isinstance(color_mean, list) and len(color_mean) >= 3:\r\n                     color_rgba = (*color_mean[:3], 1.0)\r\n+                    print(f\"    Debug: Raw color_mean from metadata: {color_mean[:3]}\") # Added logging\r\n                     if tuple(material.diffuse_color[:3]) != tuple(color_rgba[:3]):\r\n                          material.diffuse_color = color_rgba\r\n                          print(f\"    Set viewport color: {color_rgba[:3]}\")\r\n                          viewport_colors_set += 1\r\n@@ -601,8 +612,25 @@\n if __name__ == \"__main__\":\r\n     # Ensure we are running within Blender\r\n     try:\r\n         import bpy\r\n+        import sys\r\n     except ImportError:\r\n         print(\"!!! ERROR: This script must be run from within Blender. !!!\")\r\n     else:\r\n\\ No newline at end of file\n-        process_library_for_materials(bpy.context)\n+        # --- Argument Parsing for Asset Library Root ---\r\n+        asset_root_arg = None\r\n+        try:\r\n+            # Blender arguments passed after '--' appear in sys.argv\r\n+            if \"--\" in sys.argv:\r\n+                args_after_dash = sys.argv[sys.argv.index(\"--\") + 1:]\r\n+                if len(args_after_dash) >= 1:\r\n+                    asset_root_arg = args_after_dash[0]\r\n+                    print(f\"Found asset library root argument: {asset_root_arg}\")\r\n+                else:\r\n+                    print(\"Info: '--' found but no arguments after it.\")\r\n+            # else: print(\"Info: No '--' found in arguments.\") # Optional debug\r\n+        except Exception as e:\r\n+            print(f\"Error parsing command line arguments: {e}\")\r\n+        # --- End Argument Parsing ---\r\n+\r\n+        process_library_for_materials(bpy.context, asset_library_root_override=asset_root_arg)\n\\ No newline at end of file\n"
                },
                {
                    "date": 1745261995905,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -15,15 +15,17 @@\n import json\r\n from pathlib import Path\r\n import time\r\n import base64 # Although not directly used here, keep for consistency if reusing more code later\r\n+import sys # <<< ADDED IMPORT\r\n \r\n # --- USER CONFIGURATION ---\r\n \r\n # Path to the root output directory of the Asset Processor Tool\r\n # Example: r\"G:\\Assets\\Processed\"\r\n # IMPORTANT: This should point to the base directory containing supplier folders (e.g., Poliigon)\r\n-PROCESSED_ASSET_LIBRARY_ROOT = r\"G:\\02 Content\\10-19 Content\\13 Textures Power of Two\\Asset_Processor_Output\" # <<< CHANGE THIS PATH!\r\n+# This will be overridden by command-line arguments if provided.\r\n+PROCESSED_ASSET_LIBRARY_ROOT = None # Set to None initially\r\n \r\n # Name of the Blender Asset Library (configured in Blender Preferences)\r\n # that contains the PBRSET node groups created by create_nodegroups.py\r\n PBRSET_ASSET_LIBRARY_NAME = \"Nodes-Linked\" # <<< CHANGE THIS NAME!\r\n"
                },
                {
                    "date": 1745263542717,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -607,9 +607,19 @@\n     print(\"---------------------------------------\")\r\n \r\n     return True\r\n \r\n+    # --- Explicit Save ---\r\n+    try:\r\n+        bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)\r\n+        print(\"\\n--- Explicitly saved the .blend file. ---\")\r\n+    except Exception as e_save:\r\n+        print(f\"\\n!!! ERROR explicitly saving .blend file: {e_save} !!!\")\r\n+        # Note: We don't have an errors_encountered counter in this script currently.\r\n+        # If needed, we could add one or just rely on the printout.\r\n+        # For now, just printing the error is sufficient for debugging.\r\n \r\n+\r\n # --- Execution Block ---\r\n \r\n if __name__ == \"__main__\":\r\n     # Ensure we are running within Blender\r\n"
                },
                {
                    "date": 1745264210616,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -0,0 +1,678 @@\n+# blenderscripts/create_materials.py\r\n+# Version: 1.1\r\n+# Description: Scans a library processed by the Asset Processor Tool,\r\n+#              reads metadata.json files, finds corresponding PBRSET node groups\r\n+#              in a specified Blender Asset Library, and creates Blender materials\r\n+#              linking to those node groups. Skips assets if material already exists.\r\n+#              Sets material viewport properties and custom previews based on metadata.\r\n+# Changes v1.1:\r\n+#   - Added logic to link PBRSET node groups from an external asset library.\r\n+#   - Added logic to skip processing if the target material already exists.\r\n+#   - Added configuration for the PBRSET asset library name.\r\n+\r\n+import bpy\r\n+import os\r\n+import json\r\n+from pathlib import Path\r\n+import time\r\n+import base64 # Although not directly used here, keep for consistency if reusing more code later\r\n+import sys # <<< ADDED IMPORT\r\n+\r\n+# --- USER CONFIGURATION ---\r\n+\r\n+# Path to the root output directory of the Asset Processor Tool\r\n+# Example: r\"G:\\Assets\\Processed\"\r\n+# IMPORTANT: This should point to the base directory containing supplier folders (e.g., Poliigon)\r\n+# This will be overridden by command-line arguments if provided.\r\n+PROCESSED_ASSET_LIBRARY_ROOT = None # Set to None initially\r\n+\r\n+# Name of the Blender Asset Library (configured in Blender Preferences)\r\n+# that contains the PBRSET node groups created by create_nodegroups.py\r\n+PBRSET_ASSET_LIBRARY_NAME = \"Nodes-Linked\" # <<< CHANGE THIS NAME!\r\n+\r\n+# Name of the required template material in the Blender file\r\n+TEMPLATE_MATERIAL_NAME = \"Template_PBRMaterial\"\r\n+\r\n+# Label of the placeholder Group node within the template material's node tree\r\n+# where the PBRSET node group will be linked\r\n+PLACEHOLDER_NODE_LABEL = \"PBRSET_PLACEHOLDER\"\r\n+\r\n+# Prefix for the created materials\r\n+MATERIAL_NAME_PREFIX = \"Mat_\"\r\n+\r\n+# Prefix used for the PBRSET node groups created by create_nodegroups.py\r\n+PBRSET_GROUP_PREFIX = \"PBRSET_\"\r\n+\r\n+# Map type(s) to use for finding a reference image for the material preview\r\n+# The script will look for these in order and use the first one found.\r\n+REFERENCE_MAP_TYPES = [\"COL\", \"COL-1\", \"COL-2\"]\r\n+\r\n+# Preferred resolution order for reference image (lowest first is often faster)\r\n+REFERENCE_RESOLUTION_ORDER = [\"1K\", \"512\", \"2K\", \"4K\"] # Adjust as needed\r\n+\r\n+# Assumed filename pattern for processed images.\r\n+# {asset_name}, {map_type}, {resolution}, {format} will be replaced.\r\n+# Check Asset Processor Tool's config.py (TARGET_FILENAME_PATTERN) if this is wrong.\r\n+IMAGE_FILENAME_PATTERN = \"{asset_name}_{map_type}_{resolution}.{format}\"\r\n+\r\n+# Fallback extensions to try if the primary format from metadata is not found\r\n+# Order matters - first found will be used.\r\n+FALLBACK_IMAGE_EXTENSIONS = ['png', 'jpg', 'exr', 'tif']\r\n+\r\n+# Map types to check in metadata's 'image_stats_1k' for viewport diffuse color\r\n+VIEWPORT_COLOR_MAP_TYPES = [\"COL\", \"COL-1\", \"COL-2\"]\r\n+\r\n+# Map types to check in metadata's 'image_stats_1k' for viewport roughness\r\n+VIEWPORT_ROUGHNESS_MAP_TYPES = [\"ROUGH\"]\r\n+\r\n+# Map types to check in metadata's 'image_stats_1k' for viewport metallic\r\n+VIEWPORT_METALLIC_MAP_TYPES = [\"METAL\"]\r\n+\r\n+# --- END USER CONFIGURATION ---\r\n+\r\n+\r\n+# --- Helper Functions ---\r\n+\r\n+def find_nodes_by_label(node_tree, label, node_type=None):\r\n+    \"\"\"Finds ALL nodes in a node tree matching the label and optionally type.\"\"\"\r\n+    if not node_tree:\r\n+        return []\r\n+    matching_nodes = []\r\n+    for node in node_tree.nodes:\r\n+        # Use node.label for labeled nodes, node.name for non-labeled (like Group Input/Output)\r\n+        node_identifier = node.label if node.label else node.name\r\n+        if node_identifier and node_identifier == label:\r\n+            if node_type is None or node.bl_idname == node_type or node.type == node_type: # Check bl_idname and type for flexibility\r\n+                matching_nodes.append(node)\r\n+    return matching_nodes\r\n+\r\n+def add_tag_if_new(asset_data, tag_name):\r\n+    \"\"\"Adds a tag to the asset data if it's not None/empty and doesn't already exist.\"\"\"\r\n+    if not asset_data or not tag_name or not isinstance(tag_name, str):\r\n+        return False\r\n+    cleaned_tag_name = tag_name.strip()\r\n+    if not cleaned_tag_name:\r\n+        return False\r\n+\r\n+    # Check if tag already exists (case-insensitive check might be better sometimes)\r\n+    if cleaned_tag_name not in [t.name for t in asset_data.tags]:\r\n+        try:\r\n+            asset_data.tags.new(cleaned_tag_name)\r\n+            print(f\"        + Added Asset Tag: '{cleaned_tag_name}'\")\r\n+            return True\r\n+        except Exception as e:\r\n+            print(f\"        Error adding tag '{cleaned_tag_name}': {e}\")\r\n+            return False\r\n+    return False # Tag already existed\r\n+\r\n+def reconstruct_image_path_with_fallback(asset_dir_path, asset_name, map_type, resolution, primary_format=None):\r\n+    \"\"\"\r\n+    Constructs the expected image file path.\r\n+    If primary_format is provided, tries that first.\r\n+    Then falls back to common extensions if the path doesn't exist or primary_format was None.\r\n+    Returns the found path as a string, or None if not found.\r\n+    \"\"\"\r\n+    if not all([asset_dir_path, asset_name, map_type, resolution]):\r\n+        print(f\"    !!! ERROR: Missing data for path reconstruction ({asset_name}/{map_type}/{resolution}).\")\r\n+        return None\r\n+\r\n+    found_path = None\r\n+\r\n+    # 1. Try the primary format if provided\r\n+    if primary_format:\r\n+        try:\r\n+            filename = IMAGE_FILENAME_PATTERN.format(\r\n+                asset_name=asset_name,\r\n+                map_type=map_type,\r\n+                resolution=resolution,\r\n+                format=primary_format.lower() # Ensure format is lowercase\r\n+            )\r\n+            primary_path = asset_dir_path / filename\r\n+            if primary_path.is_file():\r\n+                # print(f\"          Found primary path: {str(primary_path)}\") # Verbose\r\n+                return str(primary_path)\r\n+            # else: print(f\"          Primary path not found: {str(primary_path)}\") # Verbose\r\n+        except KeyError as e:\r\n+            print(f\"    !!! ERROR: Missing key '{e}' in IMAGE_FILENAME_PATTERN. Cannot reconstruct path.\")\r\n+            return None # Cannot proceed without valid pattern\r\n+        except Exception as e:\r\n+            print(f\"    !!! ERROR reconstructing primary image path: {e}\")\r\n+            # Continue to fallback\r\n+\r\n+    # 2. Try fallback extensions\r\n+    # print(f\"          Trying fallback extensions for {map_type}/{resolution}...\") # Verbose\r\n+    for ext in FALLBACK_IMAGE_EXTENSIONS:\r\n+        # Skip if we already tried this extension as primary (and it failed)\r\n+        if primary_format and ext.lower() == primary_format.lower():\r\n+            continue\r\n+        try:\r\n+            fallback_filename = IMAGE_FILENAME_PATTERN.format(\r\n+                asset_name=asset_name,\r\n+                map_type=map_type,\r\n+                resolution=resolution,\r\n+                format=ext.lower()\r\n+            )\r\n+            fallback_path = asset_dir_path / fallback_filename\r\n+            if fallback_path.is_file():\r\n+                print(f\"          Found fallback path: {str(fallback_path)}\")\r\n+                return str(fallback_path) # Found it!\r\n+        except KeyError:\r\n+             # Should not happen if primary format worked, but handle defensively\r\n+             print(f\"    !!! ERROR: Missing key in IMAGE_FILENAME_PATTERN during fallback. Cannot reconstruct path.\")\r\n+             return None\r\n+        except Exception as e_fallback:\r\n+            print(f\"    !!! ERROR reconstructing fallback image path ({ext}): {e_fallback}\")\r\n+            continue # Try next extension\r\n+\r\n+    # If we get here, neither primary nor fallbacks worked\r\n+    if primary_format:\r\n+        print(f\"    !!! ERROR: Could not find image file for {map_type}/{resolution} using primary format '{primary_format}' or fallbacks {FALLBACK_IMAGE_EXTENSIONS}.\")\r\n+    else:\r\n+        print(f\"    !!! ERROR: Could not find image file for {map_type}/{resolution} using fallbacks {FALLBACK_IMAGE_EXTENSIONS}.\")\r\n+    return None # Not found after all checks\r\n+\r\n+\r\n+def get_stat_value(stats_dict, map_type_list, stat_key):\r\n+    \"\"\"\r\n+    Safely retrieves a specific statistic (e.g., 'mean') for the first matching\r\n+    map type from the provided list within the image_stats_1k dictionary.\r\n+\r\n+    Args:\r\n+        stats_dict (dict): The 'image_stats_1k' dictionary from metadata.\r\n+        map_type_list (list): List of map type strings to check (e.g., [\"COL\", \"COL-1\"]).\r\n+        stat_key (str): The statistic key to retrieve (e.g., \"mean\", \"min\", \"max\").\r\n+\r\n+    Returns:\r\n+        The found statistic value (can be float, list, etc.), or None if not found.\r\n+    \"\"\"\r\n+    if not stats_dict or not isinstance(stats_dict, dict):\r\n+        return None\r\n+\r\n+    for map_type in map_type_list:\r\n+        if map_type in stats_dict:\r\n+            map_stats = stats_dict[map_type]\r\n+            if isinstance(map_stats, dict) and stat_key in map_stats:\r\n+                return map_stats[stat_key] # Return the value for the first match\r\n+            else:\r\n+                # print(f\"      Debug: Stats for '{map_type}' found but key '{stat_key}' or format is invalid.\") # Optional debug\r\n+                pass # Continue checking other map types in the list\r\n+        # else: print(f\"      Debug: Map type '{map_type}' not found in stats_dict.\") # Optional debug\r\n+\r\n+    return None # Return None if no matching map type or stat key was found\r\n+\r\n+\r\n+# --- Core Logic ---\r\n+\r\n+def process_library_for_materials(context, asset_library_root_override=None): # Add override parameter\r\n+    global PROCESSED_ASSET_LIBRARY_ROOT # Allow modification of global\r\n+    \"\"\"\r\n+    Scans the library, reads metadata, finds PBRSET node groups in the specified\r\n+    asset library, and creates/updates materials linking to them.\r\n+    \"\"\"\r\n+    start_time = time.time()\r\n+    print(f\"\\n--- Starting Material Creation from Node Groups ({time.strftime('%Y-%m-%d %H:%M:%S')}) ---\")\r\n+    print(f\"  DEBUG: Received asset_library_root_override: {asset_library_root_override}\") # DEBUG LOG (Indented)\r\n+\r\n+    # --- Determine Asset Library Root ---\r\n+    if asset_library_root_override:\r\n+        PROCESSED_ASSET_LIBRARY_ROOT = asset_library_root_override\r\n+        print(f\"Using asset library root from argument: '{PROCESSED_ASSET_LIBRARY_ROOT}'\")\r\n+    elif not PROCESSED_ASSET_LIBRARY_ROOT:\r\n+        print(\"!!! ERROR: Processed asset library root not set in script and not provided via argument.\")\r\n+        print(\"--- Script aborted. ---\")\r\n+        return False\r\n+    print(f\"  DEBUG: Using final PROCESSED_ASSET_LIBRARY_ROOT: {PROCESSED_ASSET_LIBRARY_ROOT}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+    # --- Pre-run Checks ---\r\n+    print(\"Performing pre-run checks...\")\r\n+    valid_setup = True\r\n+    # 1. Check Processed Asset Library Root Path\r\n+    root_path = Path(PROCESSED_ASSET_LIBRARY_ROOT)\r\n+    if not root_path.is_dir():\r\n+        print(f\"!!! ERROR: Processed asset library root directory not found or not a directory:\")\r\n+        print(f\"!!!        '{PROCESSED_ASSET_LIBRARY_ROOT}'\")\r\n+        valid_setup = False\r\n+    else:\r\n+        print(f\"   Processed Asset Library Root: '{root_path}'\")\r\n+    print(f\"  DEBUG: Checking PBRSET Asset Library '{PBRSET_ASSET_LIBRARY_NAME}'\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+    # 2. Check PBRSET Asset Library Name and Path\r\n+    pbrset_library = context.preferences.filepaths.asset_libraries.get(PBRSET_ASSET_LIBRARY_NAME)\r\n+    pbrset_library_path = None\r\n+    if not pbrset_library:\r\n+        print(f\"!!! ERROR: PBRSET Asset Library '{PBRSET_ASSET_LIBRARY_NAME}' not found in Blender Preferences.\")\r\n+        valid_setup = False\r\n+    else:\r\n+        pbrset_library_path_str = bpy.path.abspath(pbrset_library.path)\r\n+        pbrset_library_path = Path(pbrset_library_path_str)\r\n+        # Check if the library path points to a single .blend file or a directory\r\n+        if not pbrset_library_path.exists() or (not pbrset_library_path.is_file() and not pbrset_library_path.is_dir()):\r\n+             print(f\"!!! ERROR: Path for PBRSET Asset Library '{PBRSET_ASSET_LIBRARY_NAME}' is invalid or does not exist:\")\r\n+             print(f\"!!!        '{pbrset_library_path_str}'\")\r\n+             valid_setup = False\r\n+             pbrset_library_path = None # Ensure it's None if invalid\r\n+        else:\r\n+             print(f\"   PBRSET Asset Library: '{PBRSET_ASSET_LIBRARY_NAME}' -> '{pbrset_library_path_str}'\")\r\n+    print(f\"  DEBUG: PBRSET Library Path: {pbrset_library_path}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+    # 3. Check Template Material and Placeholder Node\r\n+    template_mat = bpy.data.materials.get(TEMPLATE_MATERIAL_NAME)\r\n+    placeholder_node_found_in_template = False\r\n+    if not template_mat:\r\n+        print(f\"!!! ERROR: Template material '{TEMPLATE_MATERIAL_NAME}' not found in this Blender file.\")\r\n+        valid_setup = False\r\n+    elif not template_mat.use_nodes:\r\n+        print(f\"!!! ERROR: Template material '{TEMPLATE_MATERIAL_NAME}' does not use nodes.\")\r\n+        valid_setup = False\r\n+    else:\r\n+        placeholder_nodes = find_nodes_by_label(template_mat.node_tree, PLACEHOLDER_NODE_LABEL, 'ShaderNodeGroup')\r\n+        if not placeholder_nodes:\r\n+            print(f\"!!! ERROR: Placeholder node '{PLACEHOLDER_NODE_LABEL}' not found in template material '{TEMPLATE_MATERIAL_NAME}'.\")\r\n+            valid_setup = False\r\n+        else:\r\n+            placeholder_node_found_in_template = True\r\n+            print(f\"   Found Template Material: '{TEMPLATE_MATERIAL_NAME}' with placeholder '{PLACEHOLDER_NODE_LABEL}'\")\r\n+    print(f\"  DEBUG: Template Material Found: {template_mat is not None}\") # DEBUG LOG (Indented)\r\n+    print(f\"  DEBUG: Placeholder Node Found in Template: {placeholder_node_found_in_template}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+    if not valid_setup:\r\n+        print(\"\\n--- Script aborted due to configuration errors. Please fix the issues above. ---\")\r\n+        return False\r\n+    print(\"Pre-run checks passed.\")\r\n+    # --- End Pre-run Checks ---\r\n+\r\n+    # --- Initialize Counters ---\r\n+    metadata_files_found = 0\r\n+    assets_processed = 0\r\n+    assets_skipped = 0\r\n+    materials_created = 0\r\n+    # materials_updated = 0 # Not updating existing materials anymore\r\n+    node_groups_linked = 0\r\n+    previews_set = 0\r\n+    viewport_colors_set = 0\r\n+    viewport_roughness_set = 0\r\n+    viewport_metallic_set = 0\r\n+    errors_encountered = 0\r\n+    pbrset_groups_missing_in_library = 0\r\n+    placeholder_nodes_missing = 0\r\n+    library_link_errors = 0\r\n+    # --- End Counters ---\r\n+\r\n+    print(f\"\\nScanning for metadata files in '{root_path}'...\")\r\n+\r\n+    # --- Scan for metadata.json ---\r\n+    metadata_paths = []\r\n+    for supplier_dir in root_path.iterdir():\r\n+        if supplier_dir.is_dir():\r\n+            for asset_dir in supplier_dir.iterdir():\r\n+                if asset_dir.is_dir():\r\n+                    metadata_file = asset_dir / 'metadata.json'\r\n+                    if metadata_file.is_file():\r\n+                        metadata_paths.append(metadata_file)\r\n+\r\n+    metadata_files_found = len(metadata_paths)\r\n+    print(f\"Found {metadata_files_found} metadata.json files.\")\r\n+    print(f\"  DEBUG: Metadata paths found: {metadata_paths}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+    if metadata_files_found == 0:\r\n+        print(\"No metadata files found. Nothing to process.\")\r\n+        print(\"--- Script Finished ---\")\r\n+        return True # No work needed is considered success\r\n+\r\n+    # --- Determine the .blend file path for the PBRSET library ---\r\n+    # If the library path is a directory, we assume the node groups are in a file named like the library\r\n+    # This might need adjustment based on how the user organizes their library file(s)\r\n+    pbrset_blend_file_path = None\r\n+    if pbrset_library_path:\r\n+        if pbrset_library_path.is_file() and pbrset_library_path.suffix.lower() == '.blend':\r\n+            pbrset_blend_file_path = str(pbrset_library_path)\r\n+        elif pbrset_library_path.is_dir():\r\n+            # Attempt to find a .blend file named like the library or a common name\r\n+            potential_blend_name = f\"{PBRSET_ASSET_LIBRARY_NAME}.blend\"\r\n+            potential_path = pbrset_library_path / potential_blend_name\r\n+            if potential_path.is_file():\r\n+                 pbrset_blend_file_path = str(potential_path)\r\n+            else:\r\n+                 # Fallback: look for any .blend file (take the first one found) - less reliable\r\n+                 found_blend = next(pbrset_library_path.glob('*.blend'), None)\r\n+                 if found_blend:\r\n+                      pbrset_blend_file_path = str(found_blend)\r\n+                      print(f\"    Warning: PBRSET library path is a directory. Using first found .blend file: {found_blend.name}\")\r\n+                 else:\r\n+                      print(f\"!!! ERROR: PBRSET library path is a directory, but no .blend file found inside.\")\r\n+                      errors_encountered += 1 # Count as error if no blend file found\r\n+\r\n+        if pbrset_blend_file_path:\r\n+             print(f\"   Using PBRSET library file: '{pbrset_blend_file_path}'\")\r\n+        else:\r\n+             print(f\"!!! ERROR: Could not determine the .blend file for the PBRSET library '{PBRSET_ASSET_LIBRARY_NAME}'.\")\r\n+             # No point continuing if we can't find the source blend file\r\n+             print(\"\\n--- Script aborted: Cannot find PBRSET library .blend file. ---\")\r\n+             return False\r\n+\r\n+\r\n+    # --- Process Each Metadata File ---\r\n+    print(f\"  DEBUG: Starting metadata file loop. Found {len(metadata_paths)} files.\") # DEBUG LOG (Indented)\r\n+    for metadata_path in metadata_paths:\r\n+        asset_dir_path = metadata_path.parent\r\n+        print(f\"\\n--- Processing Metadata: {metadata_path.relative_to(root_path)} ---\")\r\n+        print(f\"  DEBUG: Processing file: {metadata_path}\") # DEBUG LOG (Indented)\r\n+        try:\r\n+            with open(metadata_path, 'r', encoding='utf-8') as f:\r\n+                metadata = json.load(f)\r\n+\r\n+            # --- Extract Key Info ---\r\n+            asset_name = metadata.get(\"asset_name\")\r\n+            supplier_name = metadata.get(\"supplier_name\")\r\n+            archetype = metadata.get(\"archetype\")\r\n+            processed_resolutions = metadata.get(\"processed_map_resolutions\", {})\r\n+            merged_resolutions = metadata.get(\"merged_map_resolutions\", {})\r\n+            map_details = metadata.get(\"map_details\", {})\r\n+            image_stats_1k = metadata.get(\"image_stats_1k\")\r\n+\r\n+            all_map_resolutions = {**processed_resolutions, **merged_resolutions}\r\n+\r\n+            if not asset_name:\r\n+                print(f\"    !!! ERROR: Metadata file is missing 'asset_name'. Skipping.\")\r\n+                errors_encountered += 1\r\n+                continue\r\n+            print(f\"    DEBUG: Valid metadata loaded for asset: {asset_name}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+            print(f\"  Asset Name: {asset_name}\")\r\n+\r\n+            # --- Determine Target Names ---\r\n+            target_material_name = f\"{MATERIAL_NAME_PREFIX}{asset_name}\"\r\n+            target_pbrset_group_name = f\"{PBRSET_GROUP_PREFIX}{asset_name}\"\r\n+            print(f\"    DEBUG: Target Material Name: {target_material_name}\") # DEBUG LOG (Indented)\r\n+            print(f\"    DEBUG: Target PBRSET Group Name: {target_pbrset_group_name}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+            # --- Check if Material Already Exists (Skip Logic) ---\r\n+            if bpy.data.materials.get(target_material_name):\r\n+                print(f\"  Skipping asset '{asset_name}': Material '{target_material_name}' already exists.\")\r\n+                assets_skipped += 1\r\n+                continue # Move to the next metadata file\r\n+            print(f\"    DEBUG: Material '{target_material_name}' does not exist. Proceeding with creation.\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+            # --- Create New Material ---\r\n+            print(f\"  Creating new material: '{target_material_name}'\")\r\n+            print(f\"    DEBUG: Copying template material '{TEMPLATE_MATERIAL_NAME}'\") # DEBUG LOG (Indented)\r\n+            material = template_mat.copy()\r\n+            if not material:\r\n+                print(f\"    !!! ERROR: Failed to copy template material '{TEMPLATE_MATERIAL_NAME}'. Skipping asset '{asset_name}'.\")\r\n+                errors_encountered += 1\r\n+                continue\r\n+            material.name = target_material_name\r\n+            materials_created += 1\r\n+            print(f\"    DEBUG: Material '{material.name}' created.\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+            # --- Find Placeholder Node ---\r\n+            if not material.use_nodes or not material.node_tree:\r\n+                 print(f\"    !!! ERROR: Newly created material '{material.name}' does not use nodes or has no node tree. Skipping node linking.\")\r\n+                 placeholder_node = None # Ensure it's None\r\n+            else:\r\n+                placeholder_nodes = find_nodes_by_label(material.node_tree, PLACEHOLDER_NODE_LABEL, 'ShaderNodeGroup')\r\n+                if not placeholder_nodes:\r\n+                    print(f\"    !!! WARNING: Placeholder node '{PLACEHOLDER_NODE_LABEL}' not found in material '{material.name}'. Cannot link PBRSET group.\")\r\n+                    placeholder_nodes_missing += 1\r\n+                    placeholder_node = None # Ensure it's None\r\n+                else:\r\n+                    placeholder_node = placeholder_nodes[0] # Assume first is correct\r\n+                    print(f\"    DEBUG: Found placeholder node '{placeholder_node.label}' in material '{material.name}'.\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+            # --- Find and Link PBRSET Node Group from Library ---\r\n+            linked_pbrset_group = None\r\n+            if placeholder_node and pbrset_blend_file_path: # Only proceed if placeholder exists and library file is known\r\n+                print(f\"    DEBUG: Placeholder node exists and PBRSET library file path is known: {pbrset_blend_file_path}\") # DEBUG LOG (Indented)\r\n+                # Check if the group is already linked in the current file\r\n+                existing_linked_group = bpy.data.node_groups.get(target_pbrset_group_name)\r\n+                if existing_linked_group and existing_linked_group.library and bpy.path.abspath(existing_linked_group.library.filepath) == pbrset_blend_file_path:\r\n+                     linked_pbrset_group = existing_linked_group\r\n+                     print(f\"    Found existing linked PBRSET group: '{linked_pbrset_group.name}'\")\r\n+                else:\r\n+                    # Link the node group from the external file\r\n+                    print(f\"    Attempting to link PBRSET group '{target_pbrset_group_name}' from '{Path(pbrset_blend_file_path).name}'...\")\r\n+                    try:\r\n+                        with bpy.data.libraries.load(pbrset_blend_file_path, link=True, relative=False) as (data_from, data_to):\r\n+                            if target_pbrset_group_name in data_from.node_groups:\r\n+                                data_to.node_groups = [target_pbrset_group_name]\r\n+                            else:\r\n+                                print(f\"      !!! ERROR: Node group '{target_pbrset_group_name}' not found in library file.\")\r\n+                                pbrset_groups_missing_in_library += 1\r\n+\r\n+                        # Verify linking was successful\r\n+                        linked_pbrset_group = bpy.data.node_groups.get(target_pbrset_group_name)\r\n+                        if not linked_pbrset_group or not linked_pbrset_group.library:\r\n+                            print(f\"      !!! ERROR: Failed to link node group '{target_pbrset_group_name}'.\")\r\n+                            library_link_errors += 1\r\n+                            linked_pbrset_group = None # Ensure it's None on failure\r\n+                        else:\r\n+                             print(f\"      Successfully linked node group: '{linked_pbrset_group.name}'\")\r\n+\r\n+                    except Exception as e_lib_load:\r\n+                        print(f\"      !!! ERROR loading library or linking node group: {e_lib_load}\")\r\n+                        library_link_errors += 1\r\n+                        linked_pbrset_group = None # Ensure it's None on failure\r\n+\r\n+            # --- Link Linked Node Group to Placeholder ---\r\n+            if placeholder_node and linked_pbrset_group:\r\n+                print(f\"    DEBUG: Attempting to link PBRSET group '{linked_pbrset_group.name}' to placeholder '{placeholder_node.label}'.\") # DEBUG LOG (Indented)\r\n+                if placeholder_node.node_tree != linked_pbrset_group:\r\n+                    try:\r\n+                        placeholder_node.node_tree = linked_pbrset_group\r\n+                        print(f\"    Linked PBRSET group '{linked_pbrset_group.name}' to placeholder '{placeholder_node.label}'.\")\r\n+                        node_groups_linked += 1\r\n+                    except TypeError as e_assign:\r\n+                        print(f\"    !!! ERROR: Could not assign linked PBRSET group to placeholder '{placeholder_node.label}'. Is it a Group Node? Error: {e_assign}\")\r\n+                        errors_encountered += 1\r\n+                    except Exception as e_link:\r\n+                         print(f\"    !!! UNEXPECTED ERROR linking PBRSET group to placeholder: {e_link}\")\r\n+                         errors_encountered += 1\r\n+            elif placeholder_node and not linked_pbrset_group:\r\n+                 print(f\"    Info: Cannot link node group as it was not found or failed to link.\")\r\n+            # No 'else' needed if placeholder_node is None, error already logged\r\n+\r\n+\r\n+            # --- Mark Material as Asset ---\r\n+            if not material.asset_data:\r\n+                print(f\"    DEBUG: Marking material '{material.name}' as asset.\") # DEBUG LOG (Indented)\r\n+                try:\r\n+                    material.asset_mark()\r\n+                    print(f\"    Marked material '{material.name}' as asset.\")\r\n+                except Exception as e_mark:\r\n+                    print(f\"    !!! ERROR: Failed to mark material '{material.name}' as asset: {e_mark}\")\r\n+\r\n+            # --- Copy Asset Tags ---\r\n+            if material.asset_data and linked_pbrset_group and linked_pbrset_group.asset_data:\r\n+                print(f\"    DEBUG: Copying asset tags from PBRSET group to material.\") # DEBUG LOG (Indented)\r\n+                tags_copied_count = 0\r\n+                if supplier_name:\r\n+                    if add_tag_if_new(material.asset_data, supplier_name): tags_copied_count += 1\r\n+                if archetype:\r\n+                    if add_tag_if_new(material.asset_data, archetype): tags_copied_count += 1\r\n+                # Copy other tags from PBRSET group\r\n+                for ng_tag in linked_pbrset_group.asset_data.tags:\r\n+                     if add_tag_if_new(material.asset_data, ng_tag.name): tags_copied_count += 1\r\n+                # if tags_copied_count > 0: print(f\"      Copied {tags_copied_count} asset tags to material.\") # Optional info\r\n+            # else: print(f\"    Warn: Cannot copy tags. Material asset_data: {material.asset_data is not None}, Linked Group: {linked_pbrset_group}, Group asset_data: {linked_pbrset_group.asset_data if linked_pbrset_group else None}\") # Debug\r\n+\r\n+\r\n+            # --- Set Custom Preview ---\r\n+            ref_image_path = None\r\n+            for ref_map_type in REFERENCE_MAP_TYPES:\r\n+                if ref_map_type in all_map_resolutions:\r\n+                    available_resolutions = all_map_resolutions[ref_map_type]\r\n+                    lowest_res = None\r\n+                    for res_pref in REFERENCE_RESOLUTION_ORDER:\r\n+                        if res_pref in available_resolutions:\r\n+                            lowest_res = res_pref\r\n+                            break\r\n+                    if lowest_res:\r\n+                        ref_map_details = map_details.get(ref_map_type, {})\r\n+                        ref_format = ref_map_details.get(\"output_format\")\r\n+                        ref_image_path = reconstruct_image_path_with_fallback(\r\n+                            asset_dir_path=asset_dir_path,\r\n+                            asset_name=asset_name,\r\n+                            map_type=ref_map_type,\r\n+                            resolution=lowest_res,\r\n+                            primary_format=ref_format\r\n+                        )\r\n+                        if ref_image_path:\r\n+                            break\r\n+\r\n+            if ref_image_path and material.asset_data:\r\n+                print(f\"    Attempting to set preview from: {Path(ref_image_path).name}\")\r\n+                try:\r\n+                    with context.temp_override(id=material):\r\n+                         bpy.ops.ed.lib_id_load_custom_preview(filepath=ref_image_path)\r\n+                    print(f\"      Successfully set custom preview for material.\")\r\n+                    previews_set += 1\r\n+                except RuntimeError as e_op:\r\n+                    print(f\"      !!! ERROR running preview operator for material '{material.name}': {e_op}\")\r\n+                    errors_encountered += 1\r\n+                except Exception as e_preview:\r\n+                    print(f\"      !!! UNEXPECTED ERROR setting custom preview for material: {e_preview}\")\r\n+                    errors_encountered += 1\r\n+            elif not material.asset_data:\r\n+                 print(f\"    Info: Cannot set preview for '{material.name}' as it's not marked as an asset.\")\r\n+            else:\r\n+                 print(f\"    Info: Could not find suitable reference image ({REFERENCE_MAP_TYPES} at {REFERENCE_RESOLUTION_ORDER}) for preview.\")\r\n+\r\n+\r\n+            # --- Set Viewport Properties from Stats ---\r\n+            if image_stats_1k and isinstance(image_stats_1k, dict):\r\n+                print(f\"    DEBUG: Applying viewport properties from stats.\") # DEBUG LOG (Indented)\r\n+                # Viewport Color\r\n+                color_mean = get_stat_value(image_stats_1k, VIEWPORT_COLOR_MAP_TYPES, 'mean')\r\n+                if isinstance(color_mean, list) and len(color_mean) >= 3:\r\n+                    color_rgba = (*color_mean[:3], 1.0)\r\n+                    print(f\"    Debug: Raw color_mean from metadata: {color_mean[:3]}\") # Added logging\r\n+                    if tuple(material.diffuse_color[:3]) != tuple(color_rgba[:3]):\r\n+                         material.diffuse_color = color_rgba\r\n+                         print(f\"    Set viewport color: {color_rgba[:3]}\")\r\n+                         viewport_colors_set += 1\r\n+\r\n+                # Viewport Roughness & Metallic Check\r\n+                roughness_mean = get_stat_value(image_stats_1k, VIEWPORT_ROUGHNESS_MAP_TYPES, 'mean')\r\n+                metallic_mean = get_stat_value(image_stats_1k, VIEWPORT_METALLIC_MAP_TYPES, 'mean')\r\n+                metal_map_found = metallic_mean is not None\r\n+\r\n+                # Roughness\r\n+                if roughness_mean is not None:\r\n+                    rough_val = roughness_mean[0] if isinstance(roughness_mean, list) else roughness_mean\r\n+                    if isinstance(rough_val, (float, int)):\r\n+                        final_roughness = float(rough_val)\r\n+                        if not metal_map_found:\r\n+                            final_roughness = 1.0 - final_roughness\r\n+                        final_roughness = max(0.0, min(1.0, final_roughness))\r\n+                        if abs(material.roughness - final_roughness) > 0.001:\r\n+                             material.roughness = final_roughness\r\n+                             print(f\"    Set viewport roughness: {final_roughness:.3f}\")\r\n+                             viewport_roughness_set += 1\r\n+\r\n+                # Metallic\r\n+                if metal_map_found:\r\n+                    metal_val = metallic_mean[0] if isinstance(metallic_mean, list) else metallic_mean\r\n+                    if isinstance(metal_val, (float, int)):\r\n+                        final_metallic = max(0.0, min(1.0, float(metal_val)))\r\n+                        if abs(material.metallic - final_metallic) > 0.001:\r\n+                             material.metallic = final_metallic\r\n+                             print(f\"    Set viewport metallic: {final_metallic:.3f}\")\r\n+                             viewport_metallic_set += 1\r\n+                else:\r\n+                    if material.metallic != 0.0:\r\n+                        material.metallic = 0.0\r\n+                        print(f\"    Set viewport metallic to default: 0.0 (No metal map found)\")\r\n+                        viewport_metallic_set += 1\r\n+\r\n+            assets_processed += 1 # Count assets where processing was attempted (even if errors occurred later)\r\n+\r\n+        except FileNotFoundError:\r\n+            print(f\"    !!! ERROR: Metadata file not found (should not happen if scan worked): {metadata_path}\")\r\n+            errors_encountered += 1\r\n+        except json.JSONDecodeError:\r\n+            print(f\"    !!! ERROR: Invalid JSON in metadata file: {metadata_path}\")\r\n+            errors_encountered += 1\r\n+        except Exception as e_main_loop:\r\n+            print(f\"    !!! UNEXPECTED ERROR processing asset from {metadata_path}: {e_main_loop}\")\r\n+            import traceback\r\n+            traceback.print_exc()\r\n+            errors_encountered += 1\r\n+\r\n+    # --- End Metadata File Loop ---\r\n+\r\n+    # --- Final Summary ---\r\n+    end_time = time.time()\r\n+    duration = end_time - start_time\r\n+    print(\"\\n--- Material Creation Script Finished ---\")\r\n+    print(f\"Duration: {duration:.2f} seconds\")\r\n+    print(f\"Metadata Files Found: {metadata_files_found}\")\r\n+    print(f\"Assets Processed/Attempted: {assets_processed}\")\r\n+    print(f\"Assets Skipped (Already Exist): {assets_skipped}\")\r\n+    print(f\"Materials Created: {materials_created}\")\r\n+    # print(f\"Materials Updated: {materials_updated}\") # Removed as we skip existing\r\n+    print(f\"PBRSET Node Groups Linked: {node_groups_linked}\")\r\n+    print(f\"Material Previews Set: {previews_set}\")\r\n+    print(f\"Viewport Colors Set: {viewport_colors_set}\")\r\n+    print(f\"Viewport Roughness Set: {viewport_roughness_set}\")\r\n+    print(f\"Viewport Metallic Set: {viewport_metallic_set}\")\r\n+    if pbrset_groups_missing_in_library > 0:\r\n+        print(f\"!!! PBRSET Node Groups Missing in Library '{PBRSET_ASSET_LIBRARY_NAME}': {pbrset_groups_missing_in_library} !!!\")\r\n+    if library_link_errors > 0:\r\n+         print(f\"!!! Library Link Errors: {library_link_errors} !!!\")\r\n+    if placeholder_nodes_missing > 0:\r\n+         print(f\"!!! Placeholder Nodes Missing in Materials: {placeholder_nodes_missing} !!!\")\r\n+    if errors_encountered > 0:\r\n+        print(f\"!!! Other Errors Encountered: {errors_encountered} !!!\")\r\n+    print(\"---------------------------------------\")\r\n+\r\n+    # --- Explicit Save ---\r\n+    print(f\"  DEBUG: Attempting explicit save for file: {bpy.data.filepath}\") # DEBUG LOG (Indented)\r\n+    try:\r\n+        bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)\r\n+        print(\"\\n--- Explicitly saved the .blend file. ---\")\r\n+    except Exception as e_save:\r\n+        print(f\"\\n!!! ERROR explicitly saving .blend file: {e_save} !!!\")\r\n+        # Note: We don't have an errors_encountered counter in this script currently.\r\n+        # If needed, we could add one or just rely on the printout.\r\n+        # For now, just printing the error is sufficient for debugging.\r\n+\r\n+    return True\r\n+\r\n+\r\n+# --- Execution Block ---\r\n+\r\n+if __name__ == \"__main__\":\r\n+    # Ensure we are running within Blender\r\n+    try:\r\n+        import bpy\r\n+        import sys\r\n+    except ImportError:\r\n+        print(\"!!! ERROR: This script must be run from within Blender. !!!\")\r\n+    else:\r\n+        # --- Argument Parsing for Asset Library Root ---\r\n+        asset_root_arg = None\r\n+        try:\r\n+            # Blender arguments passed after '--' appear in sys.argv\r\n+            if \"--\" in sys.argv:\r\n+                args_after_dash = sys.argv[sys.argv.index(\"--\") + 1:]\r\n+                if len(args_after_dash) >= 1:\r\n+                    asset_root_arg = args_after_dash[0]\r\n+                    print(f\"Found asset library root argument: {asset_root_arg}\")\r\n+                else:\r\n+                    print(\"Info: '--' found but no arguments after it.\")\r\n+            # else: print(\"Info: No '--' found in arguments.\") # Optional debug\r\n+        except Exception as e:\r\n+            print(f\"Error parsing command line arguments: {e}\")\r\n+        # --- End Argument Parsing ---\r\n+\r\n+        process_library_for_materials(bpy.context, asset_library_root_override=asset_root_arg)\n\\ No newline at end of file\n"
                },
                {
                    "date": 1745264936593,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -675,5 +675,5 @@\n         except Exception as e:\r\n             print(f\"Error parsing command line arguments: {e}\")\r\n         # --- End Argument Parsing ---\r\n \r\n-        process_library_for_materials(bpy.context, asset_library_root_override=asset_root_ar\n\\ No newline at end of file\n+        process_library_for_materials(bpy.context, asset_library_root_override=asset_root_arg)\n\\ No newline at end of file\n"
                },
                {
                    "date": 1745266523931,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -0,0 +1,680 @@\n+# blenderscripts/create_materials.py\r\n+# Version: 1.1\r\n+# Description: Scans a library processed by the Asset Processor Tool,\r\n+#              reads metadata.json files, finds corresponding PBRSET node groups\r\n+#              in a specified Blender Asset Library, and creates Blender materials\r\n+#              linking to those node groups. Skips assets if material already exists.\r\n+#              Sets material viewport properties and custom previews based on metadata.\r\n+# Changes v1.1:\r\n+#   - Added logic to link PBRSET node groups from an external asset library.\r\n+#   - Added logic to skip processing if the target material already exists.\r\n+#   - Added configuration for the PBRSET asset library name.\r\n+\r\n+import bpy\r\n+import os\r\n+import json\r\n+from pathlib import Path\r\n+import time\r\n+import base64 # Although not directly used here, keep for consistency if reusing more code later\r\n+import sys # <<< ADDED IMPORT\r\n+\r\n+# --- USER CONFIGURATION ---\r\n+\r\n+# Path to the root output directory of the Asset Processor Tool\r\n+# Example: r\"G:\\Assets\\Processed\"\r\n+# IMPORTANT: This should point to the base directory containing supplier folders (e.g., Poliigon)\r\n+# This will be overridden by command-line arguments if provided.\r\n+PROCESSED_ASSET_LIBRARY_ROOT = None # Set to None initially\r\n+\r\n+# Name of the Blender Asset Library (configured in Blender Preferences)\r\n+# that contains the PBRSET node groups created by create_nodegroups.py\r\n+PBRSET_ASSET_LIBRARY_NAME = \"Nodes-Linked\" # <<< CHANGE THIS NAME!\r\n+\r\n+# Name of the required template material in the Blender file\r\n+TEMPLATE_MATERIAL_NAME = \"Template_PBRMaterial\"\r\n+\r\n+# Label of the placeholder Group node within the template material's node tree\r\n+# where the PBRSET node group will be linked\r\n+PLACEHOLDER_NODE_LABEL = \"PBRSET_PLACEHOLDER\"\r\n+\r\n+# Prefix for the created materials\r\n+MATERIAL_NAME_PREFIX = \"Mat_\"\r\n+\r\n+# Prefix used for the PBRSET node groups created by create_nodegroups.py\r\n+PBRSET_GROUP_PREFIX = \"PBRSET_\"\r\n+\r\n+# Map type(s) to use for finding a reference image for the material preview\r\n+# The script will look for these in order and use the first one found.\r\n+REFERENCE_MAP_TYPES = [\"COL\", \"COL-1\", \"COL-2\"]\r\n+\r\n+# Preferred resolution order for reference image (lowest first is often faster)\r\n+REFERENCE_RESOLUTION_ORDER = [\"1K\", \"512\", \"2K\", \"4K\"] # Adjust as needed\r\n+\r\n+# Assumed filename pattern for processed images.\r\n+# {asset_name}, {map_type}, {resolution}, {format} will be replaced.\r\n+# Check Asset Processor Tool's config.py (TARGET_FILENAME_PATTERN) if this is wrong.\r\n+IMAGE_FILENAME_PATTERN = \"{asset_name}_{map_type}_{resolution}.{format}\"\r\n+\r\n+# Fallback extensions to try if the primary format from metadata is not found\r\n+# Order matters - first found will be used.\r\n+FALLBACK_IMAGE_EXTENSIONS = ['png', 'jpg', 'exr', 'tif']\r\n+\r\n+# Map types to check in metadata's 'image_stats_1k' for viewport diffuse color\r\n+VIEWPORT_COLOR_MAP_TYPES = [\"COL\", \"COL-1\", \"COL-2\"]\r\n+\r\n+# Map types to check in metadata's 'image_stats_1k' for viewport roughness\r\n+VIEWPORT_ROUGHNESS_MAP_TYPES = [\"ROUGH\"]\r\n+\r\n+# Map types to check in metadata's 'image_stats_1k' for viewport metallic\r\n+VIEWPORT_METALLIC_MAP_TYPES = [\"METAL\"]\r\n+\r\n+# --- END USER CONFIGURATION ---\r\n+\r\n+\r\n+# --- Helper Functions ---\r\n+\r\n+def find_nodes_by_label(node_tree, label, node_type=None):\r\n+    \"\"\"Finds ALL nodes in a node tree matching the label and optionally type.\"\"\"\r\n+    if not node_tree:\r\n+        return []\r\n+    matching_nodes = []\r\n+    for node in node_tree.nodes:\r\n+        # Use node.label for labeled nodes, node.name for non-labeled (like Group Input/Output)\r\n+        node_identifier = node.label if node.label else node.name\r\n+        if node_identifier and node_identifier == label:\r\n+            if node_type is None or node.bl_idname == node_type or node.type == node_type: # Check bl_idname and type for flexibility\r\n+                matching_nodes.append(node)\r\n+    return matching_nodes\r\n+\r\n+def add_tag_if_new(asset_data, tag_name):\r\n+    \"\"\"Adds a tag to the asset data if it's not None/empty and doesn't already exist.\"\"\"\r\n+    if not asset_data or not tag_name or not isinstance(tag_name, str):\r\n+        return False\r\n+    cleaned_tag_name = tag_name.strip()\r\n+    if not cleaned_tag_name:\r\n+        return False\r\n+\r\n+    # Check if tag already exists (case-insensitive check might be better sometimes)\r\n+    if cleaned_tag_name not in [t.name for t in asset_data.tags]:\r\n+        try:\r\n+            asset_data.tags.new(cleaned_tag_name)\r\n+            print(f\"        + Added Asset Tag: '{cleaned_tag_name}'\")\r\n+            return True\r\n+        except Exception as e:\r\n+            print(f\"        Error adding tag '{cleaned_tag_name}': {e}\")\r\n+            return False\r\n+    return False # Tag already existed\r\n+\r\n+def reconstruct_image_path_with_fallback(asset_dir_path, asset_name, map_type, resolution, primary_format=None):\r\n+    \"\"\"\r\n+    Constructs the expected image file path.\r\n+    If primary_format is provided, tries that first.\r\n+    Then falls back to common extensions if the path doesn't exist or primary_format was None.\r\n+    Returns the found path as a string, or None if not found.\r\n+    \"\"\"\r\n+    if not all([asset_dir_path, asset_name, map_type, resolution]):\r\n+        print(f\"    !!! ERROR: Missing data for path reconstruction ({asset_name}/{map_type}/{resolution}).\")\r\n+        return None\r\n+\r\n+    found_path = None\r\n+\r\n+    # 1. Try the primary format if provided\r\n+    if primary_format:\r\n+        try:\r\n+            filename = IMAGE_FILENAME_PATTERN.format(\r\n+                asset_name=asset_name,\r\n+                map_type=map_type,\r\n+                resolution=resolution,\r\n+                format=primary_format.lower() # Ensure format is lowercase\r\n+            )\r\n+            primary_path = asset_dir_path / filename\r\n+            if primary_path.is_file():\r\n+                # print(f\"          Found primary path: {str(primary_path)}\") # Verbose\r\n+                return str(primary_path)\r\n+            # else: print(f\"          Primary path not found: {str(primary_path)}\") # Verbose\r\n+        except KeyError as e:\r\n+            print(f\"    !!! ERROR: Missing key '{e}' in IMAGE_FILENAME_PATTERN. Cannot reconstruct path.\")\r\n+            return None # Cannot proceed without valid pattern\r\n+        except Exception as e:\r\n+            print(f\"    !!! ERROR reconstructing primary image path: {e}\")\r\n+            # Continue to fallback\r\n+\r\n+    # 2. Try fallback extensions\r\n+    # print(f\"          Trying fallback extensions for {map_type}/{resolution}...\") # Verbose\r\n+    for ext in FALLBACK_IMAGE_EXTENSIONS:\r\n+        # Skip if we already tried this extension as primary (and it failed)\r\n+        if primary_format and ext.lower() == primary_format.lower():\r\n+            continue\r\n+        try:\r\n+            fallback_filename = IMAGE_FILENAME_PATTERN.format(\r\n+                asset_name=asset_name,\r\n+                map_type=map_type,\r\n+                resolution=resolution,\r\n+                format=ext.lower()\r\n+            )\r\n+            fallback_path = asset_dir_path / fallback_filename\r\n+            if fallback_path.is_file():\r\n+                print(f\"          Found fallback path: {str(fallback_path)}\")\r\n+                return str(fallback_path) # Found it!\r\n+        except KeyError:\r\n+             # Should not happen if primary format worked, but handle defensively\r\n+             print(f\"    !!! ERROR: Missing key in IMAGE_FILENAME_PATTERN during fallback. Cannot reconstruct path.\")\r\n+             return None\r\n+        except Exception as e_fallback:\r\n+            print(f\"    !!! ERROR reconstructing fallback image path ({ext}): {e_fallback}\")\r\n+            continue # Try next extension\r\n+\r\n+    # If we get here, neither primary nor fallbacks worked\r\n+    if primary_format:\r\n+        print(f\"    !!! ERROR: Could not find image file for {map_type}/{resolution} using primary format '{primary_format}' or fallbacks {FALLBACK_IMAGE_EXTENSIONS}.\")\r\n+    else:\r\n+        print(f\"    !!! ERROR: Could not find image file for {map_type}/{resolution} using fallbacks {FALLBACK_IMAGE_EXTENSIONS}.\")\r\n+    return None # Not found after all checks\r\n+\r\n+\r\n+def get_stat_value(stats_dict, map_type_list, stat_key):\r\n+    \"\"\"\r\n+    Safely retrieves a specific statistic (e.g., 'mean') for the first matching\r\n+    map type from the provided list within the image_stats_1k dictionary.\r\n+\r\n+    Args:\r\n+        stats_dict (dict): The 'image_stats_1k' dictionary from metadata.\r\n+        map_type_list (list): List of map type strings to check (e.g., [\"COL\", \"COL-1\"]).\r\n+        stat_key (str): The statistic key to retrieve (e.g., \"mean\", \"min\", \"max\").\r\n+\r\n+    Returns:\r\n+        The found statistic value (can be float, list, etc.), or None if not found.\r\n+    \"\"\"\r\n+    if not stats_dict or not isinstance(stats_dict, dict):\r\n+        return None\r\n+\r\n+    for map_type in map_type_list:\r\n+        if map_type in stats_dict:\r\n+            map_stats = stats_dict[map_type]\r\n+            if isinstance(map_stats, dict) and stat_key in map_stats:\r\n+                return map_stats[stat_key] # Return the value for the first match\r\n+            else:\r\n+                # print(f\"      Debug: Stats for '{map_type}' found but key '{stat_key}' or format is invalid.\") # Optional debug\r\n+                pass # Continue checking other map types in the list\r\n+        # else: print(f\"      Debug: Map type '{map_type}' not found in stats_dict.\") # Optional debug\r\n+\r\n+    return None # Return None if no matching map type or stat key was found\r\n+\r\n+\r\n+# --- Core Logic ---\r\n+\r\n+def process_library_for_materials(context, asset_library_root_override=None): # Add override parameter\r\n+    global PROCESSED_ASSET_LIBRARY_ROOT # Allow modification of global\r\n+    \"\"\"\r\n+    Scans the library, reads metadata, finds PBRSET node groups in the specified\r\n+    asset library, and creates/updates materials linking to them.\r\n+    \"\"\"\r\n+    print(\"DEBUG: Script started.\") # DEBUG LOG\r\n+    start_time = time.time()\r\n+    print(f\"\\n--- Starting Material Creation from Node Groups ({time.strftime('%Y-%m-%d %H:%M:%S')}) ---\")\r\n+    print(f\"  DEBUG: Received asset_library_root_override: {asset_library_root_override}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+    # --- Determine Asset Library Root ---\r\n+    if asset_library_root_override:\r\n+        PROCESSED_ASSET_LIBRARY_ROOT = asset_library_root_override\r\n+        print(f\"Using asset library root from argument: '{PROCESSED_ASSET_LIBRARY_ROOT}'\")\r\n+    elif not PROCESSED_ASSET_LIBRARY_ROOT:\r\n+        print(\"!!! ERROR: Processed asset library root not set in script and not provided via argument.\")\r\n+        print(\"--- Script aborted. ---\")\r\n+        return False\r\n+    print(f\"  DEBUG: Using final PROCESSED_ASSET_LIBRARY_ROOT: {PROCESSED_ASSET_LIBRARY_ROOT}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+    # --- Pre-run Checks ---\r\n+    print(\"Performing pre-run checks...\")\r\n+    valid_setup = True\r\n+    # 1. Check Processed Asset Library Root Path\r\n+    root_path = Path(PROCESSED_ASSET_LIBRARY_ROOT)\r\n+    if not root_path.is_dir():\r\n+        print(f\"!!! ERROR: Processed asset library root directory not found or not a directory:\")\r\n+        print(f\"!!!        '{PROCESSED_ASSET_LIBRARY_ROOT}'\")\r\n+        valid_setup = False\r\n+    else:\r\n+        print(f\"   Processed Asset Library Root: '{root_path}'\")\r\n+    print(f\"  DEBUG: Checking PBRSET Asset Library '{PBRSET_ASSET_LIBRARY_NAME}'\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+    # 2. Check PBRSET Asset Library Name and Path\r\n+    pbrset_library = context.preferences.filepaths.asset_libraries.get(PBRSET_ASSET_LIBRARY_NAME)\r\n+    pbrset_library_path = None\r\n+    if not pbrset_library:\r\n+        print(f\"!!! ERROR: PBRSET Asset Library '{PBRSET_ASSET_LIBRARY_NAME}' not found in Blender Preferences.\")\r\n+        valid_setup = False\r\n+    else:\r\n+        pbrset_library_path_str = bpy.path.abspath(pbrset_library.path)\r\n+        pbrset_library_path = Path(pbrset_library_path_str)\r\n+        # Check if the library path points to a single .blend file or a directory\r\n+        if not pbrset_library_path.exists() or (not pbrset_library_path.is_file() and not pbrset_library_path.is_dir()):\r\n+             print(f\"!!! ERROR: Path for PBRSET Asset Library '{PBRSET_ASSET_LIBRARY_NAME}' is invalid or does not exist:\")\r\n+             print(f\"!!!        '{pbrset_library_path_str}'\")\r\n+             valid_setup = False\r\n+             pbrset_library_path = None # Ensure it's None if invalid\r\n+        else:\r\n+             print(f\"   PBRSET Asset Library: '{PBRSET_ASSET_LIBRARY_NAME}' -> '{pbrset_library_path_str}'\")\r\n+    print(f\"  DEBUG: PBRSET Library Path: {pbrset_library_path}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+    # 3. Check Template Material and Placeholder Node\r\n+    template_mat = bpy.data.materials.get(TEMPLATE_MATERIAL_NAME)\r\n+    placeholder_node_found_in_template = False\r\n+    if not template_mat:\r\n+        print(f\"!!! ERROR: Template material '{TEMPLATE_MATERIAL_NAME}' not found in this Blender file.\")\r\n+        valid_setup = False\r\n+    elif not template_mat.use_nodes:\r\n+        print(f\"!!! ERROR: Template material '{TEMPLATE_MATERIAL_NAME}' does not use nodes.\")\r\n+        valid_setup = False\r\n+    else:\r\n+        placeholder_nodes = find_nodes_by_label(template_mat.node_tree, PLACEHOLDER_NODE_LABEL, 'ShaderNodeGroup')\r\n+        if not placeholder_nodes:\r\n+            print(f\"!!! ERROR: Placeholder node '{PLACEHOLDER_NODE_LABEL}' not found in template material '{TEMPLATE_MATERIAL_NAME}'.\")\r\n+            valid_setup = False\r\n+        else:\r\n+            placeholder_node_found_in_template = True\r\n+            print(f\"   Found Template Material: '{TEMPLATE_MATERIAL_NAME}' with placeholder '{PLACEHOLDER_NODE_LABEL}'\")\r\n+    print(f\"  DEBUG: Template Material Found: {template_mat is not None}\") # DEBUG LOG (Indented)\r\n+    print(f\"  DEBUG: Placeholder Node Found in Template: {placeholder_node_found_in_template}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+    if not valid_setup:\r\n+        print(\"\\n--- Script aborted due to configuration errors. Please fix the issues above. ---\")\r\n+        return False\r\n+    print(\"Pre-run checks passed.\")\r\n+    # --- End Pre-run Checks ---\r\n+\r\n+    # --- Initialize Counters ---\r\n+    metadata_files_found = 0\r\n+    assets_processed = 0\r\n+    assets_skipped = 0\r\n+    materials_created = 0\r\n+    # materials_updated = 0 # Not updating existing materials anymore\r\n+    node_groups_linked = 0\r\n+    previews_set = 0\r\n+    viewport_colors_set = 0\r\n+    viewport_roughness_set = 0\r\n+    viewport_metallic_set = 0\r\n+    errors_encountered = 0\r\n+    pbrset_groups_missing_in_library = 0\r\n+    placeholder_nodes_missing = 0\r\n+    library_link_errors = 0\r\n+    # --- End Counters ---\r\n+\r\n+    print(f\"\\nScanning for metadata files in '{root_path}'...\")\r\n+\r\n+    # --- Scan for metadata.json ---\r\n+    metadata_paths = []\r\n+    for supplier_dir in root_path.iterdir():\r\n+        if supplier_dir.is_dir():\r\n+            for asset_dir in supplier_dir.iterdir():\r\n+                if asset_dir.is_dir():\r\n+                    metadata_file = asset_dir / 'metadata.json'\r\n+                    if metadata_file.is_file():\r\n+                        metadata_paths.append(metadata_file)\r\n+\r\n+    metadata_files_found = len(metadata_paths)\r\n+    print(f\"Found {metadata_files_found} metadata.json files.\")\r\n+    print(f\"  DEBUG: Metadata paths found: {metadata_paths}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+    if metadata_files_found == 0:\r\n+        print(\"No metadata files found. Nothing to process.\")\r\n+        print(\"--- Script Finished ---\")\r\n+        return True # No work needed is considered success\r\n+\r\n+    # --- Determine the .blend file path for the PBRSET library ---\r\n+    # If the library path is a directory, we assume the node groups are in a file named like the library\r\n+    # This might need adjustment based on how the user organizes their library file(s)\r\n+    pbrset_blend_file_path = None\r\n+    if pbrset_library_path:\r\n+        if pbrset_library_path.is_file() and pbrset_library_path.suffix.lower() == '.blend':\r\n+            pbrset_blend_file_path = str(pbrset_library_path)\r\n+        elif pbrset_library_path.is_dir():\r\n+            # Attempt to find a .blend file named like the library or a common name\r\n+            potential_blend_name = f\"{PBRSET_ASSET_LIBRARY_NAME}.blend\"\r\n+            potential_path = pbrset_library_path / potential_blend_name\r\n+            if potential_path.is_file():\r\n+                 pbrset_blend_file_path = str(potential_path)\r\n+            else:\r\n+                 # Fallback: look for any .blend file (take the first one found) - less reliable\r\n+                 found_blend = next(pbrset_library_path.glob('*.blend'), None)\r\n+                 if found_blend:\r\n+                      pbrset_blend_file_path = str(found_blend)\r\n+                      print(f\"    Warning: PBRSET library path is a directory. Using first found .blend file: {found_blend.name}\")\r\n+                 else:\r\n+                      print(f\"!!! ERROR: PBRSET library path is a directory, but no .blend file found inside.\")\r\n+                      errors_encountered += 1 # Count as error if no blend file found\r\n+\r\n+        if pbrset_blend_file_path:\r\n+             print(f\"   Using PBRSET library file: '{pbrset_blend_file_path}'\")\r\n+        else:\r\n+             print(f\"!!! ERROR: Could not determine the .blend file for the PBRSET library '{PBRSET_ASSET_LIBRARY_NAME}'.\")\r\n+             # No point continuing if we can't find the source blend file\r\n+             print(\"\\n--- Script aborted: Cannot find PBRSET library .blend file. ---\")\r\n+             return False\r\n+\r\n+\r\n+    # --- Process Each Metadata File ---\r\n+    print(f\"  DEBUG: Starting metadata file loop. Found {len(metadata_paths)} files.\") # DEBUG LOG (Indented)\r\n+    for metadata_path in metadata_paths:\r\n+        asset_dir_path = metadata_path.parent\r\n+        print(f\"\\n--- Processing Metadata: {metadata_path.relative_to(root_path)} ---\")\r\n+        print(f\"  DEBUG: Processing file: {metadata_path}\") # DEBUG LOG (Indented)\r\n+        try:\r\n+            with open(metadata_path, 'r', encoding='utf-8') as f:\r\n+                metadata = json.load(f)\r\n+\r\n+            # --- Extract Key Info ---\r\n+            asset_name = metadata.get(\"asset_name\")\r\n+            supplier_name = metadata.get(\"supplier_name\")\r\n+            archetype = metadata.get(\"archetype\")\r\n+            processed_resolutions = metadata.get(\"processed_map_resolutions\", {})\r\n+            merged_resolutions = metadata.get(\"merged_map_resolutions\", {})\r\n+            map_details = metadata.get(\"map_details\", {})\r\n+            image_stats_1k = metadata.get(\"image_stats_1k\")\r\n+\r\n+            all_map_resolutions = {**processed_resolutions, **merged_resolutions}\r\n+\r\n+            if not asset_name:\r\n+                print(f\"    !!! ERROR: Metadata file is missing 'asset_name'. Skipping.\")\r\n+                errors_encountered += 1\r\n+                continue\r\n+            print(f\"    DEBUG: Valid metadata loaded for asset: {asset_name}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+            print(f\"  Asset Name: {asset_name}\")\r\n+\r\n+            # --- Determine Target Names ---\r\n+            target_material_name = f\"{MATERIAL_NAME_PREFIX}{asset_name}\"\r\n+            target_pbrset_group_name = f\"{PBRSET_GROUP_PREFIX}{asset_name}\"\r\n+            print(f\"    DEBUG: Target Material Name: {target_material_name}\") # DEBUG LOG (Indented)\r\n+            print(f\"    DEBUG: Target PBRSET Group Name: {target_pbrset_group_name}\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+            # --- Check if Material Already Exists (Skip Logic) ---\r\n+            if bpy.data.materials.get(target_material_name):\r\n+                print(f\"  Skipping asset '{asset_name}': Material '{target_material_name}' already exists.\")\r\n+                assets_skipped += 1\r\n+                continue # Move to the next metadata file\r\n+            print(f\"    DEBUG: Material '{target_material_name}' does not exist. Proceeding with creation.\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+            # --- Create New Material ---\r\n+            print(f\"  Creating new material: '{target_material_name}'\")\r\n+            print(f\"    DEBUG: Copying template material '{TEMPLATE_MATERIAL_NAME}'\") # DEBUG LOG (Indented)\r\n+            material = template_mat.copy()\r\n+            if not material:\r\n+                print(f\"    !!! ERROR: Failed to copy template material '{TEMPLATE_MATERIAL_NAME}'. Skipping asset '{asset_name}'.\")\r\n+                errors_encountered += 1\r\n+                continue\r\n+            material.name = target_material_name\r\n+            materials_created += 1\r\n+            print(f\"    DEBUG: Material '{material.name}' created.\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+            # --- Find Placeholder Node ---\r\n+            if not material.use_nodes or not material.node_tree:\r\n+                 print(f\"    !!! ERROR: Newly created material '{material.name}' does not use nodes or has no node tree. Skipping node linking.\")\r\n+                 placeholder_node = None # Ensure it's None\r\n+            else:\r\n+                placeholder_nodes = find_nodes_by_label(material.node_tree, PLACEHOLDER_NODE_LABEL, 'ShaderNodeGroup')\r\n+                if not placeholder_nodes:\r\n+                    print(f\"    !!! WARNING: Placeholder node '{PLACEHOLDER_NODE_LABEL}' not found in material '{material.name}'. Cannot link PBRSET group.\")\r\n+                    placeholder_nodes_missing += 1\r\n+                    placeholder_node = None # Ensure it's None\r\n+                else:\r\n+                    placeholder_node = placeholder_nodes[0] # Assume first is correct\r\n+                    print(f\"    DEBUG: Found placeholder node '{placeholder_node.label}' in material '{material.name}'.\") # DEBUG LOG (Indented)\r\n+\r\n+\r\n+            # --- Find and Link PBRSET Node Group from Library ---\r\n+            linked_pbrset_group = None\r\n+            if placeholder_node and pbrset_blend_file_path: # Only proceed if placeholder exists and library file is known\r\n+                print(f\"    DEBUG: Placeholder node exists and PBRSET library file path is known: {pbrset_blend_file_path}\") # DEBUG LOG (Indented)\r\n+                # Check if the group is already linked in the current file\r\n+                existing_linked_group = bpy.data.node_groups.get(target_pbrset_group_name)\r\n+                if existing_linked_group and existing_linked_group.library and bpy.path.abspath(existing_linked_group.library.filepath) == pbrset_blend_file_path:\r\n+                     linked_pbrset_group = existing_linked_group\r\n+                     print(f\"    Found existing linked PBRSET group: '{linked_pbrset_group.name}'\")\r\n+                else:\r\n+                    # Link the node group from the external file\r\n+                    print(f\"    Attempting to link PBRSET group '{target_pbrset_group_name}' from '{Path(pbrset_blend_file_path).name}'...\")\r\n+                    try:\r\n+                        with bpy.data.libraries.load(pbrset_blend_file_path, link=True, relative=False) as (data_from, data_to):\r\n+                            if target_pbrset_group_name in data_from.node_groups:\r\n+                                data_to.node_groups = [target_pbrset_group_name]\r\n+                            else:\r\n+                                print(f\"      !!! ERROR: Node group '{target_pbrset_group_name}' not found in library file.\")\r\n+                                pbrset_groups_missing_in_library += 1\r\n+\r\n+                        # Verify linking was successful\r\n+                        linked_pbrset_group = bpy.data.node_groups.get(target_pbrset_group_name)\r\n+                        if not linked_pbrset_group or not linked_pbrset_group.library:\r\n+                            print(f\"      !!! ERROR: Failed to link node group '{target_pbrset_group_name}'.\")\r\n+                            library_link_errors += 1\r\n+                            linked_pbrset_group = None # Ensure it's None on failure\r\n+                        else:\r\n+                             print(f\"      Successfully linked node group: '{linked_pbrset_group.name}'\")\r\n+\r\n+                    except Exception as e_lib_load:\r\n+                        print(f\"      !!! ERROR loading library or linking node group: {e_lib_load}\")\r\n+                        library_link_errors += 1\r\n+                        linked_pbrset_group = None # Ensure it's None on failure\r\n+\r\n+            # --- Link Linked Node Group to Placeholder ---\r\n+            if placeholder_node and linked_pbrset_group:\r\n+                print(f\"    DEBUG: Attempting to link PBRSET group '{linked_pbrset_group.name}' to placeholder '{placeholder_node.label}'.\") # DEBUG LOG (Indented)\r\n+                if placeholder_node.node_tree != linked_pbrset_group:\r\n+                    try:\r\n+                        placeholder_node.node_tree = linked_pbrset_group\r\n+                        print(f\"    Linked PBRSET group '{linked_pbrset_group.name}' to placeholder '{placeholder_node.label}'.\")\r\n+                        node_groups_linked += 1\r\n+                    except TypeError as e_assign:\r\n+                        print(f\"    !!! ERROR: Could not assign linked PBRSET group to placeholder '{placeholder_node.label}'. Is it a Group Node? Error: {e_assign}\")\r\n+                        errors_encountered += 1\r\n+                    except Exception as e_link:\r\n+                         print(f\"    !!! UNEXPECTED ERROR linking PBRSET group to placeholder: {e_link}\")\r\n+                         errors_encountered += 1\r\n+            elif placeholder_node and not linked_pbrset_group:\r\n+                 print(f\"    Info: Cannot link node group as it was not found or failed to link.\")\r\n+            # No 'else' needed if placeholder_node is None, error already logged\r\n+\r\n+\r\n+            # --- Mark Material as Asset ---\r\n+            if not material.asset_data:\r\n+                print(f\"    DEBUG: Marking material '{material.name}' as asset.\") # DEBUG LOG (Indented)\r\n+                try:\r\n+                    material.asset_mark()\r\n+                    print(f\"    Marked material '{material.name}' as asset.\")\r\n+                except Exception as e_mark:\r\n+                    print(f\"    !!! ERROR: Failed to mark material '{material.name}' as asset: {e_mark}\")\r\n+\r\n+            # --- Copy Asset Tags ---\r\n+            if material.asset_data and linked_pbrset_group and linked_pbrset_group.asset_data:\r\n+                print(f\"    DEBUG: Copying asset tags from PBRSET group to material.\") # DEBUG LOG (Indented)\r\n+                tags_copied_count = 0\r\n+                if supplier_name:\r\n+                    if add_tag_if_new(material.asset_data, supplier_name): tags_copied_count += 1\r\n+                if archetype:\r\n+                    if add_tag_if_new(material.asset_data, archetype): tags_copied_count += 1\r\n+                # Copy other tags from PBRSET group\r\n+                for ng_tag in linked_pbrset_group.asset_data.tags:\r\n+                     if add_tag_if_new(material.asset_data, ng_tag.name): tags_copied_count += 1\r\n+                # if tags_copied_count > 0: print(f\"      Copied {tags_copied_count} asset tags to material.\") # Optional info\r\n+            # else: print(f\"    Warn: Cannot copy tags. Material asset_data: {material.asset_data is not None}, Linked Group: {linked_pbrset_group}, Group asset_data: {linked_pbrset_group.asset_data if linked_pbrset_group else None}\") # Debug\r\n+\r\n+\r\n+            # --- Set Custom Preview ---\r\n+            ref_image_path = None\r\n+            for ref_map_type in REFERENCE_MAP_TYPES:\r\n+                if ref_map_type in all_map_resolutions:\r\n+                    available_resolutions = all_map_resolutions[ref_map_type]\r\n+                    lowest_res = None\r\n+                    for res_pref in REFERENCE_RESOLUTION_ORDER:\r\n+                        if res_pref in available_resolutions:\r\n+                            lowest_res = res_pref\r\n+                            break\r\n+                    if lowest_res:\r\n+                        ref_map_details = map_details.get(ref_map_type, {})\r\n+                        ref_format = ref_map_details.get(\"output_format\")\r\n+                        ref_image_path = reconstruct_image_path_with_fallback(\r\n+                            asset_dir_path=asset_dir_path,\r\n+                            asset_name=asset_name,\r\n+                            map_type=ref_map_type,\r\n+                            resolution=lowest_res,\r\n+                            primary_format=ref_format\r\n+                        )\r\n+                        if ref_image_path:\r\n+                            break\r\n+\r\n+            if ref_image_path and material.asset_data:\r\n+                print(f\"    Attempting to set preview from: {Path(ref_image_path).name}\")\r\n+                try:\r\n+                    with context.temp_override(id=material):\r\n+                         bpy.ops.ed.lib_id_load_custom_preview(filepath=ref_image_path)\r\n+                    print(f\"      Successfully set custom preview for material.\")\r\n+                    previews_set += 1\r\n+                except RuntimeError as e_op:\r\n+                    print(f\"      !!! ERROR running preview operator for material '{material.name}': {e_op}\")\r\n+                    errors_encountered += 1\r\n+                except Exception as e_preview:\r\n+                    print(f\"      !!! UNEXPECTED ERROR setting custom preview for material: {e_preview}\")\r\n+                    errors_encountered += 1\r\n+            elif not material.asset_data:\r\n+                 print(f\"    Info: Cannot set preview for '{material.name}' as it's not marked as an asset.\")\r\n+            else:\r\n+                 print(f\"    Info: Could not find suitable reference image ({REFERENCE_MAP_TYPES} at {REFERENCE_RESOLUTION_ORDER}) for preview.\")\r\n+\r\n+\r\n+            # --- Set Viewport Properties from Stats ---\r\n+            if image_stats_1k and isinstance(image_stats_1k, dict):\r\n+                print(f\"    DEBUG: Applying viewport properties from stats.\") # DEBUG LOG (Indented)\r\n+                # Viewport Color\r\n+                color_mean = get_stat_value(image_stats_1k, VIEWPORT_COLOR_MAP_TYPES, 'mean')\r\n+                if isinstance(color_mean, list) and len(color_mean) >= 3:\r\n+                    color_rgba = (*color_mean[:3], 1.0)\r\n+                    print(f\"    Debug: Raw color_mean from metadata: {color_mean[:3]}\") # Added logging\r\n+                    if tuple(material.diffuse_color[:3]) != tuple(color_rgba[:3]):\r\n+                         material.diffuse_color = color_rgba\r\n+                         print(f\"    Set viewport color: {color_rgba[:3]}\")\r\n+                         viewport_colors_set += 1\r\n+\r\n+                # Viewport Roughness & Metallic Check\r\n+                roughness_mean = get_stat_value(image_stats_1k, VIEWPORT_ROUGHNESS_MAP_TYPES, 'mean')\r\n+                metallic_mean = get_stat_value(image_stats_1k, VIEWPORT_METALLIC_MAP_TYPES, 'mean')\r\n+                metal_map_found = metallic_mean is not None\r\n+\r\n+                # Roughness\r\n+                if roughness_mean is not None:\r\n+                    rough_val = roughness_mean[0] if isinstance(roughness_mean, list) else roughness_mean\r\n+                    if isinstance(rough_val, (float, int)):\r\n+                        final_roughness = float(rough_val)\r\n+                        if not metal_map_found:\r\n+                            final_roughness = 1.0 - final_roughness\r\n+                        final_roughness = max(0.0, min(1.0, final_roughness))\r\n+                        if abs(material.roughness - final_roughness) > 0.001:\r\n+                             material.roughness = final_roughness\r\n+                             print(f\"    Set viewport roughness: {final_roughness:.3f}\")\r\n+                             viewport_roughness_set += 1\r\n+\r\n+                # Metallic\r\n+                if metal_map_found:\r\n+                    metal_val = metallic_mean[0] if isinstance(metallic_mean, list) else metallic_mean\r\n+                    if isinstance(metal_val, (float, int)):\r\n+                        final_metallic = max(0.0, min(1.0, float(metal_val)))\r\n+                        if abs(material.metallic - final_metallic) > 0.001:\r\n+                             material.metallic = final_metallic\r\n+                             print(f\"    Set viewport metallic: {final_metallic:.3f}\")\r\n+                             viewport_metallic_set += 1\r\n+                else:\r\n+                    if material.metallic != 0.0:\r\n+                        material.metallic = 0.0\r\n+                        print(f\"    Set viewport metallic to default: 0.0 (No metal map found)\")\r\n+                        viewport_metallic_set += 1\r\n+\r\n+            assets_processed += 1 # Count assets where processing was attempted (even if errors occurred later)\r\n+\r\n+        except FileNotFoundError:\r\n+            print(f\"    !!! ERROR: Metadata file not found (should not happen if scan worked): {metadata_path}\")\r\n+            errors_encountered += 1\r\n+        except json.JSONDecodeError:\r\n+            print(f\"    !!! ERROR: Invalid JSON in metadata file: {metadata_path}\")\r\n+            errors_encountered += 1\r\n+        except Exception as e_main_loop:\r\n+            print(f\"    !!! UNEXPECTED ERROR processing asset from {metadata_path}: {e_main_loop}\")\r\n+            import traceback\r\n+            traceback.print_exc()\r\n+            errors_encountered += 1\r\n+\r\n+    # --- End Metadata File Loop ---\r\n+\r\n+    # --- Final Summary ---\r\n+    end_time = time.time()\r\n+    duration = end_time - start_time\r\n+    print(\"\\n--- Material Creation Script Finished ---\")\r\n+    print(f\"Duration: {duration:.2f} seconds\")\r\n+    print(f\"Metadata Files Found: {metadata_files_found}\")\r\n+    print(f\"Assets Processed/Attempted: {assets_processed}\")\r\n+    print(f\"Assets Skipped (Already Exist): {assets_skipped}\")\r\n+    print(f\"Materials Created: {materials_created}\")\r\n+    # print(f\"Materials Updated: {materials_updated}\") # Removed as we skip existing\r\n+    print(f\"PBRSET Node Groups Linked: {node_groups_linked}\")\r\n+    print(f\"Material Previews Set: {previews_set}\")\r\n+    print(f\"Viewport Colors Set: {viewport_colors_set}\")\r\n+    print(f\"Viewport Roughness Set: {viewport_roughness_set}\")\r\n+    print(f\"Viewport Metallic Set: {viewport_metallic_set}\")\r\n+    if pbrset_groups_missing_in_library > 0:\r\n+        print(f\"!!! PBRSET Node Groups Missing in Library '{PBRSET_ASSET_LIBRARY_NAME}': {pbrset_groups_missing_in_library} !!!\")\r\n+    if library_link_errors > 0:\r\n+         print(f\"!!! Library Link Errors: {library_link_errors} !!!\")\r\n+    if placeholder_nodes_missing > 0:\r\n+         print(f\"!!! Placeholder Nodes Missing in Materials: {placeholder_nodes_missing} !!!\")\r\n+    if errors_encountered > 0:\r\n+        print(f\"!!! Other Errors Encountered: {errors_encountered} !!!\")\r\n+    print(\"---------------------------------------\")\r\n+\r\n+    # --- Explicit Save ---\r\n+    print(f\"  DEBUG: Attempting explicit save for file: {bpy.data.filepath}\") # DEBUG LOG (Indented)\r\n+    try:\r\n+        bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)\r\n+        print(\"\\n--- Explicitly saved the .blend file. ---\")\r\n+    except Exception as e_save:\r\n+        print(f\"\\n!!! ERROR explicitly saving .blend file: {e_save} !!!\")\r\n+        # Note: We don't have an errors_encountered counter in this script currently.\r\n+        # If needed, we could add one or just rely on the printout.\r\n+        # For now, just printing the error is sufficient for debugging.\r\n+\r\n+    return True\r\n+\r\n+\r\n+# --- Execution Block ---\r\n+\r\n+if __name__ == \"__main__\":\r\n+    # Ensure we are running within Blender\r\n+    try:\r\n+        import bpy\r\n+        import sys\r\n+    except ImportError:\r\n+        print(\"!!! ERROR: This script must be run from within Blender. !!!\")\r\n+    else:\r\n+        # --- Argument Parsing for Asset Library Root ---\r\n+        asset_root_arg = None\r\n+        try:\r\n+            # Blender arguments passed after '--' appear in sys.argv\r\n+            if \"--\" in sys.argv:\r\n+                args_after_dash = sys.argv[sys.argv.index(\"--\") + 1:]\r\n+                if len(args_after_dash) >= 1:\r\n+                    asset_root_arg = args_after_dash[0]\r\n+                    print(f\"Found asset library root argument: {asset_root_arg}\")\r\n+                else:\r\n+                    print(\"Info: '--' found but no arguments after it.\")\r\n+            # else: print(\"Info: No '--' found in arguments.\") # Optional debug\r\n+        except Exception as e:\r\n+            print(f\"Error parsing command line arguments: {e}\")\r\n+        # --- End Argument Parsing ---\r\n+\r\n+        process_library_for_materials(bpy.context, asset_library_root_override=asset_root_arg)\n\\ No newline at end of file\n"
                }
            ],
            "date": 1745258817112,
            "name": "Commit-0",
            "content": "# blenderscripts/create_materials.py\r\n# Version: 1.0\r\n# Description: Scans a library processed by the Asset Processor Tool,\r\n#              reads metadata.json files, finds corresponding PBRSET node groups,\r\n#              and creates/updates Blender materials linking to those node groups.\r\n#              Sets material viewport properties and custom previews based on metadata.\r\n\r\nimport bpy\r\nimport os\r\nimport json\r\nfrom pathlib import Path\r\nimport time\r\nimport base64 # Although not directly used here, keep for consistency if reusing more code later\r\n\r\n# --- USER CONFIGURATION ---\r\n\r\n# Path to the root output directory of the Asset Processor Tool\r\n# Example: r\"G:\\Assets\\Processed\"\r\n# IMPORTANT: This should point to the base directory containing supplier folders (e.g., Poliigon)\r\nPROCESSED_ASSET_LIBRARY_ROOT = r\"G:\\02 Content\\10-19 Content\\13 Textures Power of Two\\Asset_Processor_Output\" # <<< CHANGE THIS PATH!\r\n\r\n# Name of the required template material in the Blender file\r\nTEMPLATE_MATERIAL_NAME = \"Template_PBRMaterial\"\r\n\r\n# Label of the placeholder Group node within the template material's node tree\r\n# where the PBRSET node group will be linked\r\nPLACEHOLDER_NODE_LABEL = \"PBRSET_PLACEHOLDER\"\r\n\r\n# Prefix for the created materials\r\nMATERIAL_NAME_PREFIX = \"Mat_\"\r\n\r\n# Prefix used for the PBRSET node groups created by create_nodegroups.py\r\nPBRSET_GROUP_PREFIX = \"PBRSET_\"\r\n\r\n# Map type(s) to use for finding a reference image for the material preview\r\n# The script will look for these in order and use the first one found.\r\nREFERENCE_MAP_TYPES = [\"COL\", \"COL-1\", \"COL-2\"]\r\n\r\n# Preferred resolution order for reference image (lowest first is often faster)\r\nREFERENCE_RESOLUTION_ORDER = [\"1K\", \"512\", \"2K\", \"4K\"] # Adjust as needed\r\n\r\n# Assumed filename pattern for processed images.\r\n# {asset_name}, {map_type}, {resolution}, {format} will be replaced.\r\n# Check Asset Processor Tool's config.py (TARGET_FILENAME_PATTERN) if this is wrong.\r\nIMAGE_FILENAME_PATTERN = \"{asset_name}_{map_type}_{resolution}.{format}\"\r\n\r\n# Fallback extensions to try if the primary format from metadata is not found\r\n# Order matters - first found will be used.\r\nFALLBACK_IMAGE_EXTENSIONS = ['png', 'jpg', 'exr', 'tif']\r\n\r\n# Map types to check in metadata's 'image_stats_1k' for viewport diffuse color\r\nVIEWPORT_COLOR_MAP_TYPES = [\"COL\", \"COL-1\", \"COL-2\"]\r\n\r\n# Map types to check in metadata's 'image_stats_1k' for viewport roughness\r\nVIEWPORT_ROUGHNESS_MAP_TYPES = [\"ROUGH\"]\r\n\r\n# Map types to check in metadata's 'image_stats_1k' for viewport metallic\r\nVIEWPORT_METALLIC_MAP_TYPES = [\"METAL\"]\r\n\r\n# --- END USER CONFIGURATION ---\r\n\r\n\r\n# --- Helper Functions ---\r\n\r\ndef find_nodes_by_label(node_tree, label, node_type=None):\r\n    \"\"\"Finds ALL nodes in a node tree matching the label and optionally type.\"\"\"\r\n    if not node_tree:\r\n        return []\r\n    matching_nodes = []\r\n    for node in node_tree.nodes:\r\n        # Use node.label for labeled nodes, node.name for non-labeled (like Group Input/Output)\r\n        node_identifier = node.label if node.label else node.name\r\n        if node_identifier and node_identifier == label:\r\n            if node_type is None or node.bl_idname == node_type or node.type == node_type: # Check bl_idname and type for flexibility\r\n                matching_nodes.append(node)\r\n    return matching_nodes\r\n\r\ndef add_tag_if_new(asset_data, tag_name):\r\n    \"\"\"Adds a tag to the asset data if it's not None/empty and doesn't already exist.\"\"\"\r\n    if not asset_data or not tag_name or not isinstance(tag_name, str):\r\n        return False\r\n    cleaned_tag_name = tag_name.strip()\r\n    if not cleaned_tag_name:\r\n        return False\r\n\r\n    # Check if tag already exists (case-insensitive check might be better sometimes)\r\n    if cleaned_tag_name not in [t.name for t in asset_data.tags]:\r\n        try:\r\n            asset_data.tags.new(cleaned_tag_name)\r\n            print(f\"        + Added Asset Tag: '{cleaned_tag_name}'\")\r\n            return True\r\n        except Exception as e:\r\n            print(f\"        Error adding tag '{cleaned_tag_name}': {e}\")\r\n            return False\r\n    return False # Tag already existed\r\n\r\ndef reconstruct_image_path_with_fallback(asset_dir_path, asset_name, map_type, resolution, primary_format=None):\r\n    \"\"\"\r\n    Constructs the expected image file path.\r\n    If primary_format is provided, tries that first.\r\n    Then falls back to common extensions if the path doesn't exist or primary_format was None.\r\n    Returns the found path as a string, or None if not found.\r\n    \"\"\"\r\n    if not all([asset_dir_path, asset_name, map_type, resolution]):\r\n        print(f\"    !!! ERROR: Missing data for path reconstruction ({asset_name}/{map_type}/{resolution}).\")\r\n        return None\r\n\r\n    found_path = None\r\n\r\n    # 1. Try the primary format if provided\r\n    if primary_format:\r\n        try:\r\n            filename = IMAGE_FILENAME_PATTERN.format(\r\n                asset_name=asset_name,\r\n                map_type=map_type,\r\n                resolution=resolution,\r\n                format=primary_format.lower() # Ensure format is lowercase\r\n            )\r\n            primary_path = asset_dir_path / filename\r\n            if primary_path.is_file():\r\n                # print(f\"          Found primary path: {str(primary_path)}\") # Verbose\r\n                return str(primary_path)\r\n            # else: print(f\"          Primary path not found: {str(primary_path)}\") # Verbose\r\n        except KeyError as e:\r\n            print(f\"    !!! ERROR: Missing key '{e}' in IMAGE_FILENAME_PATTERN. Cannot reconstruct path.\")\r\n            return None # Cannot proceed without valid pattern\r\n        except Exception as e:\r\n            print(f\"    !!! ERROR reconstructing primary image path: {e}\")\r\n            # Continue to fallback\r\n\r\n    # 2. Try fallback extensions\r\n    # print(f\"          Trying fallback extensions for {map_type}/{resolution}...\") # Verbose\r\n    for ext in FALLBACK_IMAGE_EXTENSIONS:\r\n        # Skip if we already tried this extension as primary (and it failed)\r\n        if primary_format and ext.lower() == primary_format.lower():\r\n            continue\r\n        try:\r\n            fallback_filename = IMAGE_FILENAME_PATTERN.format(\r\n                asset_name=asset_name,\r\n                map_type=map_type,\r\n                resolution=resolution,\r\n                format=ext.lower()\r\n            )\r\n            fallback_path = asset_dir_path / fallback_filename\r\n            if fallback_path.is_file():\r\n                print(f\"          Found fallback path: {str(fallback_path)}\")\r\n                return str(fallback_path) # Found it!\r\n        except KeyError:\r\n             # Should not happen if primary format worked, but handle defensively\r\n             print(f\"    !!! ERROR: Missing key in IMAGE_FILENAME_PATTERN during fallback. Cannot reconstruct path.\")\r\n             return None\r\n        except Exception as e_fallback:\r\n            print(f\"    !!! ERROR reconstructing fallback image path ({ext}): {e_fallback}\")\r\n            continue # Try next extension\r\n\r\n    # If we get here, neither primary nor fallbacks worked\r\n    if primary_format:\r\n        print(f\"    !!! ERROR: Could not find image file for {map_type}/{resolution} using primary format '{primary_format}' or fallbacks {FALLBACK_IMAGE_EXTENSIONS}.\")\r\n    else:\r\n        print(f\"    !!! ERROR: Could not find image file for {map_type}/{resolution} using fallbacks {FALLBACK_IMAGE_EXTENSIONS}.\")\r\n    return None # Not found after all checks\r\n\r\n\r\ndef get_stat_value(stats_dict, map_type_list, stat_key):\r\n    \"\"\"\r\n    Safely retrieves a specific statistic (e.g., 'mean') for the first matching\r\n    map type from the provided list within the image_stats_1k dictionary.\r\n\r\n    Args:\r\n        stats_dict (dict): The 'image_stats_1k' dictionary from metadata.\r\n        map_type_list (list): List of map type strings to check (e.g., [\"COL\", \"COL-1\"]).\r\n        stat_key (str): The statistic key to retrieve (e.g., \"mean\", \"min\", \"max\").\r\n\r\n    Returns:\r\n        The found statistic value (can be float, list, etc.), or None if not found.\r\n    \"\"\"\r\n    if not stats_dict or not isinstance(stats_dict, dict):\r\n        return None\r\n\r\n    for map_type in map_type_list:\r\n        if map_type in stats_dict:\r\n            map_stats = stats_dict[map_type]\r\n            if isinstance(map_stats, dict) and stat_key in map_stats:\r\n                return map_stats[stat_key] # Return the value for the first match\r\n            else:\r\n                # print(f\"      Debug: Stats for '{map_type}' found but key '{stat_key}' or format is invalid.\") # Optional debug\r\n                pass # Continue checking other map types in the list\r\n        # else: print(f\"      Debug: Map type '{map_type}' not found in stats_dict.\") # Optional debug\r\n\r\n    # print(f\"      Debug: Stat key '{stat_key}' not found for any map types in {map_type_list}.\") # Optional debug\r\n    return None # Return None if no matching map type or stat key was found\r\n\r\n\r\n# --- Core Logic ---\r\n\r\ndef process_library_for_materials(context):\r\n    \"\"\"\r\n    Scans the library, reads metadata, finds PBRSET node groups,\r\n    and creates/updates materials linking to them.\r\n    \"\"\"\r\n    start_time = time.time()\r\n    print(f\"\\n--- Starting Material Creation from Node Groups ({time.strftime('%Y-%m-%d %H:%M:%S')}) ---\")\r\n\r\n    # --- Pre-run Checks ---\r\n    print(\"Performing pre-run checks...\")\r\n    valid_setup = True\r\n    # 1. Check Library Root Path\r\n    root_path = Path(PROCESSED_ASSET_LIBRARY_ROOT)\r\n    if not root_path.is_dir():\r\n        print(f\"!!! ERROR: Processed asset library root directory not found or not a directory:\")\r\n        print(f\"!!!        '{PROCESSED_ASSET_LIBRARY_ROOT}'\")\r\n        valid_setup = False\r\n    else:\r\n        print(f\"   Asset Library Root: '{root_path}'\")\r\n\r\n    # 2. Check Template Material and Placeholder Node\r\n    template_mat = bpy.data.materials.get(TEMPLATE_MATERIAL_NAME)\r\n    placeholder_node_found_in_template = False\r\n    if not template_mat:\r\n        print(f\"!!! ERROR: Template material '{TEMPLATE_MATERIAL_NAME}' not found in this Blender file.\")\r\n        valid_setup = False\r\n    elif not template_mat.use_nodes:\r\n        print(f\"!!! ERROR: Template material '{TEMPLATE_MATERIAL_NAME}' does not use nodes.\")\r\n        valid_setup = False\r\n    else:\r\n        placeholder_nodes = find_nodes_by_label(template_mat.node_tree, PLACEHOLDER_NODE_LABEL, 'ShaderNodeGroup')\r\n        if not placeholder_nodes:\r\n            print(f\"!!! ERROR: Placeholder node '{PLACEHOLDER_NODE_LABEL}' not found in template material '{TEMPLATE_MATERIAL_NAME}'.\")\r\n            valid_setup = False\r\n        else:\r\n            placeholder_node_found_in_template = True\r\n            print(f\"   Found Template Material: '{TEMPLATE_MATERIAL_NAME}' with placeholder '{PLACEHOLDER_NODE_LABEL}'\")\r\n\r\n    if not valid_setup:\r\n        print(\"\\n--- Script aborted due to configuration errors. Please fix the issues above. ---\")\r\n        return False\r\n    print(\"Pre-run checks passed.\")\r\n    # --- End Pre-run Checks ---\r\n\r\n    # --- Initialize Counters ---\r\n    metadata_files_found = 0\r\n    assets_processed = 0\r\n    materials_created = 0\r\n    materials_updated = 0\r\n    node_groups_linked = 0\r\n    previews_set = 0\r\n    viewport_colors_set = 0\r\n    viewport_roughness_set = 0\r\n    viewport_metallic_set = 0\r\n    errors_encountered = 0\r\n    pbrset_groups_missing = 0\r\n    placeholder_nodes_missing = 0\r\n    # --- End Counters ---\r\n\r\n    print(f\"\\nScanning for metadata files in '{root_path}'...\")\r\n\r\n    # --- Scan for metadata.json ---\r\n    metadata_paths = []\r\n    for supplier_dir in root_path.iterdir():\r\n        if supplier_dir.is_dir():\r\n            for asset_dir in supplier_dir.iterdir():\r\n                if asset_dir.is_dir():\r\n                    metadata_file = asset_dir / 'metadata.json'\r\n                    if metadata_file.is_file():\r\n                        metadata_paths.append(metadata_file)\r\n\r\n    metadata_files_found = len(metadata_paths)\r\n    print(f\"Found {metadata_files_found} metadata.json files.\")\r\n\r\n    if metadata_files_found == 0:\r\n        print(\"No metadata files found. Nothing to process.\")\r\n        print(\"--- Script Finished ---\")\r\n        return True # No work needed is considered success\r\n\r\n    # --- Process Each Metadata File ---\r\n    for metadata_path in metadata_paths:\r\n        asset_dir_path = metadata_path.parent\r\n        print(f\"\\n--- Processing Metadata: {metadata_path.relative_to(root_path)} ---\")\r\n        try:\r\n            with open(metadata_path, 'r', encoding='utf-8') as f:\r\n                metadata = json.load(f)\r\n\r\n            # --- Extract Key Info ---\r\n            asset_name = metadata.get(\"asset_name\")\r\n            supplier_name = metadata.get(\"supplier_name\")\r\n            archetype = metadata.get(\"archetype\")\r\n            # Get map info needed for preview path reconstruction\r\n            processed_resolutions = metadata.get(\"processed_map_resolutions\", {})\r\n            merged_resolutions = metadata.get(\"merged_map_resolutions\", {})\r\n            map_details = metadata.get(\"map_details\", {})\r\n            image_stats_1k = metadata.get(\"image_stats_1k\") # Dict: {map_type: {stats}}\r\n\r\n            # Combine processed and merged maps to find reference map\r\n            all_map_resolutions = {**processed_resolutions, **merged_resolutions}\r\n\r\n            # Validate essential data\r\n            if not asset_name:\r\n                print(f\"    !!! ERROR: Metadata file is missing 'asset_name'. Skipping.\")\r\n                errors_encountered += 1\r\n                continue\r\n\r\n            print(f\"  Asset Name: {asset_name}\")\r\n\r\n            # --- Determine Target Names ---\r\n            target_material_name = f\"{MATERIAL_NAME_PREFIX}{asset_name}\"\r\n            target_pbrset_group_name = f\"{PBRSET_GROUP_PREFIX}{asset_name}\"\r\n\r\n            # --- Find PBRSET Node Group ---\r\n            pbrset_group = bpy.data.node_groups.get(target_pbrset_group_name)\r\n            if not pbrset_group:\r\n                print(f\"    !!! WARNING: PBRSET Node Group '{target_pbrset_group_name}' not found. Cannot create/update material. Run create_nodegroups.py first?\")\r\n                pbrset_groups_missing += 1\r\n                continue # Skip this asset if the required node group doesn't exist\r\n\r\n            # --- Find or Create Material ---\r\n            material = bpy.data.materials.get(target_material_name)\r\n            is_new_material = False\r\n\r\n            if material is None:\r\n                print(f\"  Creating new material: '{target_material_name}'\")\r\n                material = template_mat.copy()\r\n                if not material:\r\n                    print(f\"    !!! ERROR: Failed to copy template material '{TEMPLATE_MATERIAL_NAME}'. Skipping asset '{asset_name}'.\")\r\n                    errors_encountered += 1\r\n                    continue\r\n                material.name = target_material_name\r\n                materials_created += 1\r\n                is_new_material = True\r\n            else:\r\n                print(f\"  Updating existing material: '{target_material_name}'\")\r\n                materials_updated += 1\r\n\r\n            # Ensure material uses nodes\r\n            if not material.use_nodes or not material.node_tree:\r\n                 print(f\"    !!! ERROR: Material '{material.name}' does not use nodes or has no node tree. Skipping node linking.\")\r\n                 # Continue to set other properties if possible\r\n            else:\r\n                # --- Find Placeholder Node ---\r\n                placeholder_nodes = find_nodes_by_label(material.node_tree, PLACEHOLDER_NODE_LABEL, 'ShaderNodeGroup')\r\n                if not placeholder_nodes:\r\n                    print(f\"    !!! WARNING: Placeholder node '{PLACEHOLDER_NODE_LABEL}' not found in material '{material.name}'. Cannot link PBRSET group.\")\r\n                    placeholder_nodes_missing += 1\r\n                else:\r\n                    placeholder_node = placeholder_nodes[0] # Assume first is correct\r\n                    # --- Link Node Group to Placeholder ---\r\n                    if placeholder_node.node_tree != pbrset_group:\r\n                        try:\r\n                            placeholder_node.node_tree = pbrset_group\r\n                            print(f\"    Linked PBRSET group '{pbrset_group.name}' to placeholder '{placeholder_node.label}'.\")\r\n                            node_groups_linked += 1\r\n                        except TypeError as e_assign:\r\n                            print(f\"    !!! ERROR: Could not assign PBRSET group to placeholder '{placeholder_node.label}'. Is it a Group Node? Error: {e_assign}\")\r\n                            errors_encountered += 1\r\n                        except Exception as e_link:\r\n                             print(f\"    !!! UNEXPECTED ERROR linking PBRSET group: {e_link}\")\r\n                             errors_encountered += 1\r\n\r\n            # --- Mark Material as Asset ---\r\n            if not material.asset_data:\r\n                try:\r\n                    material.asset_mark()\r\n                    print(f\"    Marked material '{material.name}' as asset.\")\r\n                except Exception as e_mark:\r\n                    print(f\"    !!! ERROR: Failed to mark material '{material.name}' as asset: {e_mark}\")\r\n                    # Continue if possible\r\n\r\n            # --- Copy Asset Tags ---\r\n            if material.asset_data and pbrset_group.asset_data:\r\n                tags_copied_count = 0\r\n                if supplier_name:\r\n                    if add_tag_if_new(material.asset_data, supplier_name): tags_copied_count += 1\r\n                if archetype:\r\n                    if add_tag_if_new(material.asset_data, archetype): tags_copied_count += 1\r\n                # Copy other tags from PBRSET group if needed\r\n                for ng_tag in pbrset_group.asset_data.tags:\r\n                     if add_tag_if_new(material.asset_data, ng_tag.name): tags_copied_count += 1\r\n                # if tags_copied_count > 0: print(f\"      Copied {tags_copied_count} asset tags to material.\") # Optional info\r\n            # else: print(f\"    Warn: Cannot copy tags. Material asset_data: {material.asset_data is not None}, Group asset_data: {pbrset_group.asset_data is not None}\") # Debug\r\n\r\n            # --- Set Custom Preview ---\r\n            ref_image_path = None\r\n            # Find reference image path (similar logic to create_nodegroups.py)\r\n            for ref_map_type in REFERENCE_MAP_TYPES:\r\n                if ref_map_type in all_map_resolutions:\r\n                    available_resolutions = all_map_resolutions[ref_map_type]\r\n                    lowest_res = None\r\n                    for res_pref in REFERENCE_RESOLUTION_ORDER:\r\n                        if res_pref in available_resolutions:\r\n                            lowest_res = res_pref\r\n                            break\r\n                    if lowest_res:\r\n                        ref_map_details = map_details.get(ref_map_type, {})\r\n                        ref_format = ref_map_details.get(\"output_format\")\r\n                        ref_image_path = reconstruct_image_path_with_fallback(\r\n                            asset_dir_path=asset_dir_path,\r\n                            asset_name=asset_name,\r\n                            map_type=ref_map_type,\r\n                            resolution=lowest_res,\r\n                            primary_format=ref_format\r\n                        )\r\n                        if ref_image_path:\r\n                            break # Found a suitable reference image path\r\n\r\n            if ref_image_path and material.asset_data:\r\n                print(f\"    Attempting to set preview from: {Path(ref_image_path).name}\")\r\n                try:\r\n                    # Ensure the ID (material) is the active one for the operator context\r\n                    with context.temp_override(id=material):\r\n                         bpy.ops.ed.lib_id_load_custom_preview(filepath=ref_image_path)\r\n                    print(f\"      Successfully set custom preview for material.\")\r\n                    previews_set += 1\r\n                except RuntimeError as e_op:\r\n                    print(f\"      !!! ERROR running preview operator for material '{material.name}': {e_op}\")\r\n                    errors_encountered += 1\r\n                except Exception as e_preview:\r\n                    print(f\"      !!! UNEXPECTED ERROR setting custom preview for material: {e_preview}\")\r\n                    errors_encountered += 1\r\n            elif not material.asset_data:\r\n                 print(f\"    Info: Cannot set preview for '{material.name}' as it's not marked as an asset.\")\r\n            else:\r\n                 print(f\"    Info: Could not find suitable reference image ({REFERENCE_MAP_TYPES} at {REFERENCE_RESOLUTION_ORDER}) for preview.\")\r\n\r\n\r\n            # --- Set Viewport Properties from Stats ---\r\n            if image_stats_1k and isinstance(image_stats_1k, dict):\r\n                # Viewport Color\r\n                color_mean = get_stat_value(image_stats_1k, VIEWPORT_COLOR_MAP_TYPES, 'mean')\r\n                if isinstance(color_mean, list) and len(color_mean) >= 3:\r\n                    # Add alpha if missing (default to 1.0)\r\n                    color_rgba = (*color_mean[:3], 1.0)\r\n                    # Check if update is needed (compare first 3 elements)\r\n                    if tuple(material.diffuse_color[:3]) != tuple(color_rgba[:3]):\r\n                         material.diffuse_color = color_rgba\r\n                         print(f\"    Set viewport color: {color_rgba[:3]}\")\r\n                         viewport_colors_set += 1\r\n                # else: print(f\"    Info: Could not find valid 'mean' stat for viewport color in {VIEWPORT_COLOR_MAP_TYPES}.\") # Optional\r\n\r\n                # Viewport Roughness & Metallic Check\r\n                roughness_mean = get_stat_value(image_stats_1k, VIEWPORT_ROUGHNESS_MAP_TYPES, 'mean')\r\n                metallic_mean = get_stat_value(image_stats_1k, VIEWPORT_METALLIC_MAP_TYPES, 'mean')\r\n                metal_map_found = metallic_mean is not None\r\n\r\n                # Roughness\r\n                if roughness_mean is not None:\r\n                    # Extract float value if it's a list (e.g., [0.5])\r\n                    rough_val = roughness_mean[0] if isinstance(roughness_mean, list) else roughness_mean\r\n                    if isinstance(rough_val, (float, int)):\r\n                        final_roughness = float(rough_val)\r\n                        # Invert if metal map is NOT found (Gloss workflow)\r\n                        if not metal_map_found:\r\n                            final_roughness = 1.0 - final_roughness\r\n                            # print(f\"      Inverting roughness (no metal map): {rough_val:.3f} -> {final_roughness:.3f}\") # Debug\r\n                        final_roughness = max(0.0, min(1.0, final_roughness)) # Clamp\r\n                        # Check if update is needed\r\n                        if abs(material.roughness - final_roughness) > 0.001:\r\n                             material.roughness = final_roughness\r\n                             print(f\"    Set viewport roughness: {final_roughness:.3f}\")\r\n                             viewport_roughness_set += 1\r\n                    # else: print(f\"    Info: Roughness 'mean' stat is not a valid number: {roughness_mean}\") # Optional\r\n                # else: print(f\"    Info: Could not find 'mean' stat for viewport roughness in {VIEWPORT_ROUGHNESS_MAP_TYPES}.\") # Optional\r\n\r\n                # Metallic\r\n                if metal_map_found:\r\n                    metal_val = metallic_mean[0] if isinstance(metallic_mean, list) else metallic_mean\r\n                    if isinstance(metal_val, (float, int)):\r\n                        final_metallic = max(0.0, min(1.0, float(metal_val))) # Clamp\r\n                        # Check if update is needed\r\n                        if abs(material.metallic - final_metallic) > 0.001:\r\n                             material.metallic = final_metallic\r\n                             print(f\"    Set viewport metallic: {final_metallic:.3f}\")\r\n                             viewport_metallic_set += 1\r\n                    # else: print(f\"    Info: Metallic 'mean' stat is not a valid number: {metallic_mean}\") # Optional\r\n                else:\r\n                    # If metal map wasn't found, ensure metallic is 0.0\r\n                    if material.metallic != 0.0:\r\n                        material.metallic = 0.0\r\n                        print(f\"    Set viewport metallic to default: 0.0 (No metal map found)\")\r\n                        viewport_metallic_set += 1 # Count setting default as a set action\r\n\r\n            # else: print(f\"    Warn: 'image_stats_1k' missing or invalid in metadata. Cannot set viewport properties.\") # Optional\r\n\r\n            assets_processed += 1\r\n\r\n        except FileNotFoundError:\r\n            print(f\"    !!! ERROR: Metadata file not found (should not happen if scan worked): {metadata_path}\")\r\n            errors_encountered += 1\r\n        except json.JSONDecodeError:\r\n            print(f\"    !!! ERROR: Invalid JSON in metadata file: {metadata_path}\")\r\n            errors_encountered += 1\r\n        except Exception as e_main_loop:\r\n            print(f\"    !!! UNEXPECTED ERROR processing asset from {metadata_path}: {e_main_loop}\")\r\n            import traceback\r\n            traceback.print_exc() # Print detailed traceback for debugging\r\n            errors_encountered += 1\r\n            # Continue to the next asset\r\n\r\n    # --- End Metadata File Loop ---\r\n\r\n    # --- Final Summary ---\r\n    end_time = time.time()\r\n    duration = end_time - start_time\r\n    print(\"\\n--- Material Creation Script Finished ---\")\r\n    print(f\"Duration: {duration:.2f} seconds\")\r\n    print(f\"Metadata Files Found: {metadata_files_found}\")\r\n    print(f\"Assets Processed/Attempted: {assets_processed}\")\r\n    print(f\"Materials Created: {materials_created}\")\r\n    print(f\"Materials Updated: {materials_updated}\")\r\n    print(f\"PBRSET Node Groups Linked: {node_groups_linked}\")\r\n    print(f\"Material Previews Set: {previews_set}\")\r\n    print(f\"Viewport Colors Set: {viewport_colors_set}\")\r\n    print(f\"Viewport Roughness Set: {viewport_roughness_set}\")\r\n    print(f\"Viewport Metallic Set: {viewport_metallic_set}\")\r\n    if pbrset_groups_missing > 0:\r\n        print(f\"!!! PBRSET Node Groups Missing: {pbrset_groups_missing} !!!\")\r\n    if placeholder_nodes_missing > 0:\r\n         print(f\"!!! Placeholder Nodes Missing in Materials: {placeholder_nodes_missing} !!!\")\r\n    if errors_encountered > 0:\r\n        print(f\"!!! Other Errors Encountered: {errors_encountered} !!!\")\r\n    print(\"---------------------------------------\")\r\n\r\n    return True\r\n\r\n\r\n# --- Execution Block ---\r\n\r\nif __name__ == \"__main__\":\r\n    # Ensure we are running within Blender\r\n    try:\r\n        import bpy\r\n        # import base64 # Already imported above\r\n    except ImportError:\r\n        print(\"!!! ERROR: This script must be run from within Blender. !!!\")\r\n    else:\r\n        # Pass Blender's current context to the processing function\r\n        process_library_for_materials(bpy.context)"
        }
    ]
}