{
    "sourceFile": "blender_addon/material_merger/operator.py",
    "activeCommit": 0,
    "commits": [
        {
            "activePatchIndex": 1,
            "patches": [
                {
                    "date": 1745659330832,
                    "content": "Index: \n===================================================================\n--- \n+++ \n"
                },
                {
                    "date": 1745659574514,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -9,8 +9,144 @@\n MATERIAL_MERGE_NODEGROUP_NAME = \"MaterialMerge\"\r\n HANDLER_NODEGROUP_NAME = \"PBR_Handler\" # Assumption from plan\r\n BSDF_NODEGROUP_NAME = \"PBR_BSDF\" # Assumption from plan\r\n \r\n+# Helper function to copy nodes and identify outputs\r\n+def copy_material_nodes(source_mat, target_tree, location_offset=(0, 0)):\r\n+    \"\"\"\r\n+    Copies nodes from source_mat's node tree to target_tree, applying an offset.\r\n+    Identifies and returns the copied nodes corresponding to the final BSDF and Displacement outputs.\r\n+\r\n+    Returns:\r\n+        tuple: (copied_node_map, copied_final_bsdf_node, copied_final_disp_node)\r\n+               Returns (None, None, None) on failure.\r\n+    \"\"\"\r\n+    if not source_mat or not source_mat.node_tree:\r\n+        print(f\"Error: Source material '{source_mat.name if source_mat else 'None'}' has no node tree.\")\r\n+        return None, None, None\r\n+\r\n+    source_tree = source_mat.node_tree\r\n+    copied_node_map = {} # Map original node to copied node\r\n+    copied_final_bsdf_node = None\r\n+    copied_final_disp_node = None\r\n+\r\n+    # --- Identify Final Output Nodes in Source Tree ---\r\n+    # This logic needs to handle both base materials and already-merged materials\r\n+    source_final_bsdf_node = None\r\n+    source_final_disp_node = None\r\n+\r\n+    # Try finding a top-level MaterialMerge node first (for recursive merging)\r\n+    top_merge_node = None\r\n+    for node in source_tree.nodes:\r\n+        if node.type == 'GROUP' and node.node_tree and node.node_tree.name == MATERIAL_MERGE_NODEGROUP_NAME:\r\n+            # Check if it's connected to the Material Output (likely the top one)\r\n+            for link in source_tree.links:\r\n+                if link.from_node == node and link.to_node.type == 'OUTPUT_MATERIAL':\r\n+                    top_merge_node = node\r\n+                    break\r\n+            if top_merge_node:\r\n+                break\r\n+\r\n+    if top_merge_node:\r\n+        print(f\"  Identified top-level '{MATERIAL_MERGE_NODEGROUP_NAME}' in '{source_mat.name}'. Using its outputs.\")\r\n+        source_final_bsdf_node = top_merge_node\r\n+        source_final_disp_node = top_merge_node # Both outputs come from the merge node\r\n+        # Ensure the sockets exist before proceeding\r\n+        if 'BSDF' not in source_final_bsdf_node.outputs or 'Displacement' not in source_final_disp_node.outputs:\r\n+             print(f\"  Error: Identified merge node in '{source_mat.name}' lacks required BSDF/Displacement outputs.\")\r\n+             return None, None, None\r\n+    else:\r\n+        # If no top-level merge node, assume it's a base material\r\n+        print(f\"  No top-level merge node found in '{source_mat.name}'. Assuming base material structure.\")\r\n+        source_final_bsdf_node = source_tree.nodes.get(BSDF_NODEGROUP_NAME)\r\n+        source_final_disp_node = source_tree.nodes.get(HANDLER_NODEGROUP_NAME) # Displacement from Handler\r\n+        if not source_final_bsdf_node:\r\n+            print(f\"  Error: Could not find base BSDF node '{BSDF_NODEGROUP_NAME}' in '{source_mat.name}'.\")\r\n+            return None, None, None\r\n+        if not source_final_disp_node:\r\n+            print(f\"  Error: Could not find base Handler node '{HANDLER_NODEGROUP_NAME}' in '{source_mat.name}'.\")\r\n+            return None, None, None\r\n+        # Ensure sockets exist\r\n+        if 'BSDF' not in source_final_bsdf_node.outputs:\r\n+             print(f\"  Error: Identified BSDF node '{BSDF_NODEGROUP_NAME}' lacks BSDF output.\")\r\n+             return None, None, None\r\n+        if 'Displacement' not in source_final_disp_node.outputs:\r\n+             print(f\"  Error: Identified Handler node '{HANDLER_NODEGROUP_NAME}' lacks Displacement output.\")\r\n+             return None, None, None\r\n+\r\n+\r\n+    # --- Copy Nodes ---\r\n+    print(f\"  Copying nodes from '{source_mat.name}'...\")\r\n+    for original_node in source_tree.nodes:\r\n+        if original_node.type == 'OUTPUT_MATERIAL':\r\n+            continue # Skip the material output node\r\n+\r\n+        new_node = target_tree.nodes.new(type=original_node.bl_idname)\r\n+        # Copy properties (basic example, might need more specific handling)\r\n+        for prop in original_node.bl_rna.properties:\r\n+            if not prop.is_readonly and prop.identifier != \"rna_type\":\r\n+                try:\r\n+                    setattr(new_node, prop.identifier, getattr(original_node, prop.identifier))\r\n+                except AttributeError:\r\n+                    pass # Some properties might not be directly settable\r\n+\r\n+        # Copy specific node group if it's a group node\r\n+        if original_node.type == 'GROUP' and original_node.node_tree:\r\n+            new_node.node_tree = original_node.node_tree # Link the same node group\r\n+\r\n+        new_node.location = (original_node.location.x + location_offset[0],\r\n+                             original_node.location.y + location_offset[1])\r\n+        new_node.width = original_node.width\r\n+        new_node.label = original_node.label\r\n+        new_node.name = original_node.name # Keep original name if possible (Blender might rename on conflict)\r\n+\r\n+        copied_node_map[original_node] = new_node\r\n+\r\n+        # Store the *copied* versions of the identified final output nodes\r\n+        if original_node == source_final_bsdf_node:\r\n+            copied_final_bsdf_node = new_node\r\n+        if original_node == source_final_disp_node:\r\n+             # If source was merge node, both point to the same copied node\r\n+             # If source was base material, this points to the copied handler\r\n+            copied_final_disp_node = new_node\r\n+\r\n+    # --- Copy Links ---\r\n+    print(f\"  Copying links for '{source_mat.name}'...\")\r\n+    for original_link in source_tree.links:\r\n+        original_from_node = original_link.from_node\r\n+        original_to_node = original_link.to_node\r\n+\r\n+        # Check if both ends of the link were copied (i.e., not connected to Material Output)\r\n+        if original_from_node in copied_node_map and original_to_node in copied_node_map:\r\n+            new_from_node = copied_node_map[original_from_node]\r\n+            new_to_node = copied_node_map[original_to_node]\r\n+\r\n+            # Find matching sockets by name (more robust than index)\r\n+            try:\r\n+                from_socket_name = original_link.from_socket.name\r\n+                to_socket_name = original_link.to_socket.name\r\n+                new_from_socket = new_from_node.outputs.get(from_socket_name)\r\n+                new_to_socket = new_to_node.inputs.get(to_socket_name)\r\n+\r\n+                if new_from_socket and new_to_socket:\r\n+                    target_tree.links.new(new_from_socket, new_to_socket)\r\n+                else:\r\n+                     print(f\"    Warning: Could not find matching sockets for link between '{original_from_node.name}' and '{original_to_node.name}' (Sockets: '{from_socket_name}', '{to_socket_name}')\")\r\n+            except Exception as e:\r\n+                 print(f\"    Error creating link between copied nodes '{new_from_node.name}' and '{new_to_node.name}': {e}\")\r\n+\r\n+\r\n+    if not copied_final_bsdf_node:\r\n+         print(f\"  Error: Failed to find the copied version of the final BSDF node for '{source_mat.name}'.\")\r\n+         return None, None, None\r\n+    if not copied_final_disp_node:\r\n+         print(f\"  Error: Failed to find the copied version of the final Displacement node for '{source_mat.name}'.\")\r\n+         return None, None, None\r\n+\r\n+    print(f\"  Finished copying '{source_mat.name}'.\")\r\n+    return copied_node_map, copied_final_bsdf_node, copied_final_disp_node\r\n+\r\n+\r\n class MATERIAL_OT_merge_materials(Operator):\r\n     \"\"\"Merge two selected Asset Processor materials\"\"\"\r\n     bl_idname = \"material.merge_materials\"\r\n     bl_label = \"Merge Selected Materials\"\r\n@@ -61,64 +197,30 @@\n         output_node = new_node_tree.nodes.new(type='ShaderNodeOutputMaterial')\r\n         output_node.location = (400, 0) # Basic positioning\r\n \r\n         # 2. Copy nodes from source materials\r\n-        # This is a simplified placeholder. Actual implementation needs\r\n-        # to iterate nodes, copy them, handle links within the copied group,\r\n-        # and identify the final BSDF/Displacement outputs.\r\n-        # Need to handle relative positioning during copy.\r\n+        print(\"Copying nodes for Material A...\")\r\n+        copied_map_a, copied_bsdf_a, copied_disp_a = copy_material_nodes(mat_a, new_node_tree, location_offset=(0, 0))\r\n+        if not copied_bsdf_a or not copied_disp_a:\r\n+            self.report({'ERROR'}, f\"Failed to copy nodes or identify outputs for material '{mat_a.name}'. Check console for details.\")\r\n+            bpy.data.materials.remove(new_mat) # Clean up\r\n+            return {'CANCELLED'}\r\n \r\n-        # Placeholder for copied nodes and identified outputs\r\n-        copied_nodes_a = []\r\n-        final_bsdf_node_a = None\r\n-        final_disp_node_a = None\r\n+        print(\"Copying nodes for Material B...\")\r\n+        # Calculate offset for Material B based on Material A's nodes (simple approach)\r\n+        offset_x = 0\r\n+        if copied_map_a:\r\n+             max_x = max((n.location.x + n.width for n in copied_map_a.values()), default=0)\r\n+             min_x = min((n.location.x for n in copied_map_a.values()), default=0)\r\n+             offset_x = max_x - min_x + 100 # Add some spacing\r\n \r\n-        copied_nodes_b = []\r\n-        final_bsdf_node_b = None\r\n-        final_disp_node_b = None\r\n+        copied_map_b, copied_bsdf_b, copied_disp_b = copy_material_nodes(mat_b, new_node_tree, location_offset=(offset_x, 0))\r\n+        if not copied_bsdf_b or not copied_disp_b:\r\n+            self.report({'ERROR'}, f\"Failed to copy nodes or identify outputs for material '{mat_b.name}'. Check console for details.\")\r\n+            bpy.data.materials.remove(new_mat) # Clean up\r\n+            return {'CANCELLED'}\r\n \r\n-        # --- Copy and Identify for Material A ---\r\n-        # TODO: Implement node copying and identification of final outputs\r\n-        # For prototype, let's assume we can find the PBR_BSDF and PBR_Handler directly\r\n-        # This will need refinement for recursive merging later.\r\n-        if mat_a.node_tree:\r\n-             # Simple placeholder: Find the assumed final nodes in the original tree\r\n-             # This is NOT the final implementation for copying, just for testing identification\r\n-             final_bsdf_node_a = mat_a.node_tree.nodes.get(BSDF_NODEGROUP_NAME)\r\n-             final_disp_node_a = mat_a.node_tree.nodes.get(HANDLER_NODEGROUP_NAME) # Displacement comes from Handler\r\n \r\n-             # TODO: Implement actual node copying from mat_a.node_tree to new_node_tree\r\n-             # Need to map old nodes to new nodes and recreate internal links.\r\n-             # Need to identify the *copied* final_bsdf_node_a and final_disp_node_a in the new tree.\r\n-             # For now, we'll use the original nodes for connection logic (will fail if not linked/appended)\r\n-             # This needs to be fixed with proper node copying and mapping.\r\n-\r\n-\r\n-        # --- Copy and Identify for Material B ---\r\n-        # TODO: Implement node copying and identification of final outputs\r\n-        if mat_b.node_tree:\r\n-             # Simple placeholder: Find the assumed final nodes in the original tree\r\n-             final_bsdf_node_b = mat_b.node_tree.nodes.get(BSDF_NODEGROUP_NAME)\r\n-             final_disp_node_b = mat_b.node_tree.nodes.get(HANDLER_NODEGROUP_NAME) # Displacement comes from Handler\r\n-\r\n-             # TODO: Implement actual node copying from mat_b.node_tree to new_node_tree\r\n-             # Need to map old nodes to new nodes and recreate internal links.\r\n-             # Need to identify the *copied* final_bsdf_node_b and final_disp_node_b in the new tree.\r\n-             # For now, we'll use the original nodes for connection logic (will fail if not linked/appended)\r\n-             # This needs to be fixed with proper node copying and mapping.\r\n-\r\n-\r\n-        if not final_bsdf_node_a or not final_disp_node_a:\r\n-             self.report({'ERROR'}, f\"Could not find required nodes ({BSDF_NODEGROUP_NAME} or {HANDLER_NODEGROUP_NAME}) in material '{mat_a.name}'.\")\r\n-             # TODO: Clean up newly created material if there's an error\r\n-             return {'CANCELLED'}\r\n-\r\n-        if not final_bsdf_node_b or not final_disp_node_b:\r\n-             self.report({'ERROR'}, f\"Could not find required nodes ({BSDF_NODEGROUP_NAME} or {HANDLER_NODEGROUP_NAME}) in material '{mat_b.name}'.\")\r\n-             # TODO: Clean up newly created material if there's an error\r\n-             return {'CANCELLED'}\r\n-\r\n-\r\n         # 3. Link/Append MaterialMerge node group\r\n         merge_node = None\r\n         if not UTILITY_NODEGROUPS_FILE.is_file():\r\n              self.report({'ERROR'}, f\"Utility nodegroups file not found: {UTILITY_NODEGROUPS_FILE}\")\r\n@@ -161,21 +263,56 @@\n         links = new_node_tree.links\r\n \r\n         # Connect BSDFs to Merge node\r\n         # NOTE: Using original nodes here as placeholder. Needs to use *copied* nodes.\r\n-        link_bsdf_a = links.new(final_bsdf_node_a.outputs['BSDF'], merge_node.inputs['Shader A'])\r\n-        link_bsdf_b = links.new(final_bsdf_node_b.outputs['BSDF'], merge_node.inputs['Shader B'])\r\n+        # NOTE: Using *copied* nodes now.\r\n+        # Ensure the sockets exist before linking\r\n+        bsdf_output_socket_a = copied_bsdf_a.outputs.get('BSDF')\r\n+        shader_input_socket_a = merge_node.inputs.get('Shader A')\r\n+        bsdf_output_socket_b = copied_bsdf_b.outputs.get('BSDF')\r\n+        shader_input_socket_b = merge_node.inputs.get('Shader B')\r\n \r\n+        if not all([bsdf_output_socket_a, shader_input_socket_a, bsdf_output_socket_b, shader_input_socket_b]):\r\n+             self.report({'ERROR'}, \"Could not find required BSDF/Shader sockets for linking.\")\r\n+             bpy.data.materials.remove(new_mat) # Clean up\r\n+             return {'CANCELLED'}\r\n+\r\n+        link_bsdf_a = links.new(bsdf_output_socket_a, shader_input_socket_a)\r\n+        link_bsdf_b = links.new(bsdf_output_socket_b, shader_input_socket_b)\r\n+\r\n         # Connect Displacements to Merge node\r\n         # NOTE: Using original nodes here as placeholder. Needs to use *copied* nodes.\r\n-        link_disp_a = links.new(final_disp_node_a.outputs['Displacement'], merge_node.inputs['Displacement A'])\r\n-        link_disp_b = links.new(final_disp_node_b.outputs['Displacement'], merge_node.inputs['Displacement B'])\r\n+        # NOTE: Using *copied* nodes now.\r\n+        # Ensure the sockets exist before linking\r\n+        disp_output_socket_a = copied_disp_a.outputs.get('Displacement')\r\n+        disp_input_socket_a = merge_node.inputs.get('Displacement A')\r\n+        disp_output_socket_b = copied_disp_b.outputs.get('Displacement')\r\n+        disp_input_socket_b = merge_node.inputs.get('Displacement B')\r\n \r\n+        if not all([disp_output_socket_a, disp_input_socket_a, disp_output_socket_b, disp_input_socket_b]):\r\n+             self.report({'ERROR'}, \"Could not find required Displacement sockets for linking.\")\r\n+             bpy.data.materials.remove(new_mat) # Clean up\r\n+             return {'CANCELLED'}\r\n+\r\n+        link_disp_a = links.new(disp_output_socket_a, disp_input_socket_a)\r\n+        link_disp_b = links.new(disp_output_socket_b, disp_input_socket_b)\r\n+\r\n         # Connect Merge node outputs to Material Output\r\n-        link_merge_bsdf = links.new(merge_node.outputs['BSDF'], output_node.inputs['Surface'])\r\n-        link_merge_disp = links.new(merge_node.outputs['Displacement'], output_node.inputs['Displacement'])\r\n+        # Ensure the sockets exist before linking\r\n+        merge_bsdf_output = merge_node.outputs.get('BSDF')\r\n+        output_surface_input = output_node.inputs.get('Surface')\r\n+        merge_disp_output = merge_node.outputs.get('Displacement')\r\n+        output_disp_input = output_node.inputs.get('Displacement')\r\n \r\n+        if not all([merge_bsdf_output, output_surface_input, merge_disp_output, output_disp_input]):\r\n+             self.report({'ERROR'}, \"Could not find required Merge/Output sockets for linking.\")\r\n+             bpy.data.materials.remove(new_mat) # Clean up\r\n+             return {'CANCELLED'}\r\n \r\n+        link_merge_bsdf = links.new(merge_bsdf_output, output_surface_input)\r\n+        link_merge_disp = links.new(merge_disp_output, output_disp_input)\r\n+\r\n+\r\n         # 5. Layout (Optional)\r\n         # TODO: Implement better node layout\r\n \r\n         # Update node tree to apply changes\r\n"
                }
            ],
            "date": 1745659330832,
            "name": "Commit-0",
            "content": "import bpy\r\nfrom bpy.types import Operator\r\nfrom bpy.props import StringProperty\r\nfrom pathlib import Path\r\n\r\n# Assuming the utility nodegroups file is located relative to the addon\r\n# This path might need adjustment depending on final addon distribution\r\nUTILITY_NODEGROUPS_FILE = Path(__file__).parent / \"blender_files\" / \"utility_nodegroups.blend\"\r\nMATERIAL_MERGE_NODEGROUP_NAME = \"MaterialMerge\"\r\nHANDLER_NODEGROUP_NAME = \"PBR_Handler\" # Assumption from plan\r\nBSDF_NODEGROUP_NAME = \"PBR_BSDF\" # Assumption from plan\r\n\r\nclass MATERIAL_OT_merge_materials(Operator):\r\n    \"\"\"Merge two selected Asset Processor materials\"\"\"\r\n    bl_idname = \"material.merge_materials\"\r\n    bl_label = \"Merge Selected Materials\"\r\n    bl_options = {'REGISTER', 'UNDO'}\r\n\r\n    # Properties to hold the names of the selected materials\r\n    # These will be set by the UI panel\r\n    material_a_name: StringProperty(\r\n        name=\"Material A\",\r\n        description=\"First material to merge\"\r\n    )\r\n    material_b_name: StringProperty(\r\n        name=\"Material B\",\r\n        description=\"Second material to merge\"\r\n    )\r\n\r\n    def execute(self, context):\r\n        mat_a = bpy.data.materials.get(self.material_a_name)\r\n        mat_b = bpy.data.materials.get(self.material_b_name)\r\n\r\n        if not mat_a or not mat_b:\r\n            self.report({'ERROR'}, \"Please select two valid materials to merge.\")\r\n            return {'CANCELLED'}\r\n\r\n        if mat_a == mat_b:\r\n             self.report({'ERROR'}, \"Cannot merge a material with itself.\")\r\n             return {'CANCELLED'}\r\n\r\n        # --- Core Merging Logic (Based on Plan) ---\r\n\r\n        # 1. Create new material\r\n        new_mat_name = f\"MAT_Merged_{mat_a.name}_{mat_b.name}\"\r\n        if new_mat_name in bpy.data.materials:\r\n             # Handle potential naming conflicts, maybe append a number\r\n             new_mat_name = f\"{new_mat_name}.001\" # Simple increment for now\r\n             # A more robust approach would check for existing names and find the next available number\r\n             # For prototype, this simple approach is acceptable.\r\n\r\n        new_mat = bpy.data.materials.new(name=new_mat_name)\r\n        new_mat.use_nodes = True\r\n        new_node_tree = new_mat.node_tree\r\n\r\n        # Clear default nodes (Principled BSDF and Material Output)\r\n        for node in new_node_tree.nodes:\r\n            new_node_tree.nodes.remove(node)\r\n\r\n        # Add Material Output node\r\n        output_node = new_node_tree.nodes.new(type='ShaderNodeOutputMaterial')\r\n        output_node.location = (400, 0) # Basic positioning\r\n\r\n        # 2. Copy nodes from source materials\r\n        # This is a simplified placeholder. Actual implementation needs\r\n        # to iterate nodes, copy them, handle links within the copied group,\r\n        # and identify the final BSDF/Displacement outputs.\r\n        # Need to handle relative positioning during copy.\r\n\r\n        # Placeholder for copied nodes and identified outputs\r\n        copied_nodes_a = []\r\n        final_bsdf_node_a = None\r\n        final_disp_node_a = None\r\n\r\n        copied_nodes_b = []\r\n        final_bsdf_node_b = None\r\n        final_disp_node_b = None\r\n\r\n        # --- Copy and Identify for Material A ---\r\n        # TODO: Implement node copying and identification of final outputs\r\n        # For prototype, let's assume we can find the PBR_BSDF and PBR_Handler directly\r\n        # This will need refinement for recursive merging later.\r\n        if mat_a.node_tree:\r\n             # Simple placeholder: Find the assumed final nodes in the original tree\r\n             # This is NOT the final implementation for copying, just for testing identification\r\n             final_bsdf_node_a = mat_a.node_tree.nodes.get(BSDF_NODEGROUP_NAME)\r\n             final_disp_node_a = mat_a.node_tree.nodes.get(HANDLER_NODEGROUP_NAME) # Displacement comes from Handler\r\n\r\n             # TODO: Implement actual node copying from mat_a.node_tree to new_node_tree\r\n             # Need to map old nodes to new nodes and recreate internal links.\r\n             # Need to identify the *copied* final_bsdf_node_a and final_disp_node_a in the new tree.\r\n             # For now, we'll use the original nodes for connection logic (will fail if not linked/appended)\r\n             # This needs to be fixed with proper node copying and mapping.\r\n\r\n\r\n        # --- Copy and Identify for Material B ---\r\n        # TODO: Implement node copying and identification of final outputs\r\n        if mat_b.node_tree:\r\n             # Simple placeholder: Find the assumed final nodes in the original tree\r\n             final_bsdf_node_b = mat_b.node_tree.nodes.get(BSDF_NODEGROUP_NAME)\r\n             final_disp_node_b = mat_b.node_tree.nodes.get(HANDLER_NODEGROUP_NAME) # Displacement comes from Handler\r\n\r\n             # TODO: Implement actual node copying from mat_b.node_tree to new_node_tree\r\n             # Need to map old nodes to new nodes and recreate internal links.\r\n             # Need to identify the *copied* final_bsdf_node_b and final_disp_node_b in the new tree.\r\n             # For now, we'll use the original nodes for connection logic (will fail if not linked/appended)\r\n             # This needs to be fixed with proper node copying and mapping.\r\n\r\n\r\n        if not final_bsdf_node_a or not final_disp_node_a:\r\n             self.report({'ERROR'}, f\"Could not find required nodes ({BSDF_NODEGROUP_NAME} or {HANDLER_NODEGROUP_NAME}) in material '{mat_a.name}'.\")\r\n             # TODO: Clean up newly created material if there's an error\r\n             return {'CANCELLED'}\r\n\r\n        if not final_bsdf_node_b or not final_disp_node_b:\r\n             self.report({'ERROR'}, f\"Could not find required nodes ({BSDF_NODEGROUP_NAME} or {HANDLER_NODEGROUP_NAME}) in material '{mat_b.name}'.\")\r\n             # TODO: Clean up newly created material if there's an error\r\n             return {'CANCELLED'}\r\n\r\n\r\n        # 3. Link/Append MaterialMerge node group\r\n        merge_node = None\r\n        if not UTILITY_NODEGROUPS_FILE.is_file():\r\n             self.report({'ERROR'}, f\"Utility nodegroups file not found: {UTILITY_NODEGROUPS_FILE}\")\r\n             # TODO: Clean up newly created material if there's an error\r\n             return {'CANCELLED'}\r\n\r\n        # Check if the group is already in the current file\r\n        merge_group = bpy.data.node_groups.get(MATERIAL_MERGE_NODEGROUP_NAME)\r\n\r\n        if not merge_group:\r\n            # Attempt to link the node group\r\n            try:\r\n                with bpy.data.libraries.load(str(UTILITY_NODEGROUPS_FILE), link=True) as (data_from, data_to):\r\n                    if MATERIAL_MERGE_NODEGROUP_NAME in data_from.node_groups:\r\n                        data_to.node_groups = [MATERIAL_MERGE_NODEGROUP_NAME]\r\n                    else:\r\n                        self.report({'ERROR'}, f\"Node group '{MATERIAL_MERGE_NODEGROUP_NAME}' not found in '{UTILITY_NODEGROUPS_FILE.name}'.\")\r\n                        # TODO: Clean up newly created material if there's an error\r\n                        return {'CANCELLED'}\r\n\r\n                merge_group = bpy.data.node_groups.get(MATERIAL_MERGE_NODEGROUP_NAME)\r\n                if not merge_group:\r\n                     self.report({'ERROR'}, f\"Failed to link node group '{MATERIAL_MERGE_NODEGROUP_NAME}'.\")\r\n                     # TODO: Clean up newly created material if there's an error\r\n                     return {'CANCELLED'}\r\n\r\n            except Exception as e:\r\n                self.report({'ERROR'}, f\"Error linking '{MATERIAL_MERGE_NODEGROUP_NAME}' from '{UTILITY_NODEGROUPS_FILE.name}': {e}\")\r\n                # TODO: Clean up newly created material if there's an error\r\n                return {'CANCELLED'}\r\n\r\n        # Add the linked/appended group to the new material's node tree\r\n        merge_node = new_node_tree.nodes.new(type='ShaderNodeGroup')\r\n        merge_node.node_tree = merge_group\r\n        merge_node.label = MATERIAL_MERGE_NODEGROUP_NAME # Set label for clarity\r\n        merge_node.location = (200, 0) # Basic positioning\r\n\r\n\r\n        # 4. Make Connections\r\n        links = new_node_tree.links\r\n\r\n        # Connect BSDFs to Merge node\r\n        # NOTE: Using original nodes here as placeholder. Needs to use *copied* nodes.\r\n        link_bsdf_a = links.new(final_bsdf_node_a.outputs['BSDF'], merge_node.inputs['Shader A'])\r\n        link_bsdf_b = links.new(final_bsdf_node_b.outputs['BSDF'], merge_node.inputs['Shader B'])\r\n\r\n        # Connect Displacements to Merge node\r\n        # NOTE: Using original nodes here as placeholder. Needs to use *copied* nodes.\r\n        link_disp_a = links.new(final_disp_node_a.outputs['Displacement'], merge_node.inputs['Displacement A'])\r\n        link_disp_b = links.new(final_disp_node_b.outputs['Displacement'], merge_node.inputs['Displacement B'])\r\n\r\n        # Connect Merge node outputs to Material Output\r\n        link_merge_bsdf = links.new(merge_node.outputs['BSDF'], output_node.inputs['Surface'])\r\n        link_merge_disp = links.new(merge_node.outputs['Displacement'], output_node.inputs['Displacement'])\r\n\r\n\r\n        # 5. Layout (Optional)\r\n        # TODO: Implement better node layout\r\n\r\n        # Update node tree to apply changes\r\n        new_node_tree.nodes.update()\r\n\r\n        self.report({'INFO'}, f\"Successfully merged '{mat_a.name}' and '{mat_b.name}' into '{new_mat.name}'\")\r\n\r\n        return {'FINISHED'}\r\n\r\n    # Optional: Add invoke method if needed for more complex setup before execute\r\n    # def invoke(self, context, event):\r\n    #     # Example: Open a dialog to select materials if not already selected\r\n    #     return context.window_manager.invoke_props_dialog(self)\r\n\r\n\r\ndef register():\r\n    bpy.utils.register_class(MATERIAL_OT_merge_materials)\r\n    print(\"MATERIAL_OT_merge_materials registered\")\r\n\r\ndef unregister():\r\n    bpy.utils.unregister_class(MATERIAL_OT_merge_materials)\r\n    print(\"MATERIAL_OT_merge_materials unregistered\")\r\n\r\nif __name__ == \"__main__\":\r\n    # This block is for running the script directly in Blender's text editor\r\n    # It's useful for testing the operator logic without installing the addon\r\n    register()\r\n\r\n    # Example usage (replace with actual material names in your file)\r\n    # bpy.ops.material.merge_materials(material_a_name=\"MAT_Wood01\", material_b_name=\"MAT_SandBeach01\")"
        }
    ]
}