22 lines
28 KiB
JSON
22 lines
28 KiB
JSON
{
|
|
"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\")"
|
|
}
|
|
]
|
|
} |