textures getting added but flat
This commit is contained in:
parent
2cd82ad93d
commit
4113f62b0a
518
addon.py
518
addon.py
@ -168,6 +168,7 @@ class BlenderMCPServer:
|
|||||||
"get_polyhaven_categories": self.get_polyhaven_categories,
|
"get_polyhaven_categories": self.get_polyhaven_categories,
|
||||||
"search_polyhaven_assets": self.search_polyhaven_assets,
|
"search_polyhaven_assets": self.search_polyhaven_assets,
|
||||||
"download_polyhaven_asset": self.download_polyhaven_asset,
|
"download_polyhaven_asset": self.download_polyhaven_asset,
|
||||||
|
"set_texture": self.set_texture,
|
||||||
}
|
}
|
||||||
|
|
||||||
handler = handlers.get(cmd_type)
|
handler = handlers.get(cmd_type)
|
||||||
@ -508,8 +509,6 @@ class BlenderMCPServer:
|
|||||||
return {"error": str(e)}
|
return {"error": str(e)}
|
||||||
|
|
||||||
def download_polyhaven_asset(self, asset_id, asset_type, resolution="1k", file_format=None):
|
def download_polyhaven_asset(self, asset_id, asset_type, resolution="1k", file_format=None):
|
||||||
"""Download an asset from Polyhaven and import it into Blender"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# First get the files information
|
# First get the files information
|
||||||
files_response = requests.get(f"https://api.polyhaven.com/files/{asset_id}")
|
files_response = requests.get(f"https://api.polyhaven.com/files/{asset_id}")
|
||||||
@ -613,103 +612,138 @@ class BlenderMCPServer:
|
|||||||
return {"error": f"Requested resolution or format not available for this HDRI"}
|
return {"error": f"Requested resolution or format not available for this HDRI"}
|
||||||
|
|
||||||
elif asset_type == "textures":
|
elif asset_type == "textures":
|
||||||
# For textures, download available maps
|
|
||||||
if not file_format:
|
if not file_format:
|
||||||
file_format = "jpg" # Default format for textures
|
file_format = "jpg" # Default format for textures
|
||||||
|
|
||||||
# Find available maps (diffuse, normal, etc.)
|
|
||||||
downloaded_maps = {}
|
downloaded_maps = {}
|
||||||
for map_type in files_data:
|
|
||||||
if map_type not in ["blend", "gltf"]: # Skip non-texture files
|
try:
|
||||||
if resolution in files_data[map_type] and file_format in files_data[map_type][resolution]:
|
for map_type in files_data:
|
||||||
file_info = files_data[map_type][resolution][file_format]
|
if map_type not in ["blend", "gltf"]: # Skip non-texture files
|
||||||
file_url = file_info["url"]
|
if resolution in files_data[map_type] and file_format in files_data[map_type][resolution]:
|
||||||
|
file_info = files_data[map_type][resolution][file_format]
|
||||||
# Download the file directly into Blender's memory
|
file_url = file_info["url"]
|
||||||
response = requests.get(file_url)
|
|
||||||
if response.status_code == 200:
|
|
||||||
# Create a new image in Blender's memory
|
|
||||||
image_name = f"{asset_id}_{map_type}.{file_format}"
|
|
||||||
image = bpy.data.images.new(name=image_name, width=1, height=1)
|
|
||||||
|
|
||||||
# Save the downloaded data
|
# Use NamedTemporaryFile like we do for HDRIs
|
||||||
image.file_format = file_format.upper()
|
with tempfile.NamedTemporaryFile(suffix=f".{file_format}", delete=False) as tmp_file:
|
||||||
image.filepath_raw = f"/tmp/{image_name}" # This is just for reference
|
# Download the file
|
||||||
image.pack(data=response.content)
|
response = requests.get(file_url)
|
||||||
|
if response.status_code == 200:
|
||||||
downloaded_maps[map_type] = image
|
tmp_file.write(response.content)
|
||||||
|
tmp_path = tmp_file.name
|
||||||
|
|
||||||
|
# Load image from temporary file
|
||||||
|
image = bpy.data.images.load(tmp_path)
|
||||||
|
image.name = f"{asset_id}_{map_type}.{file_format}"
|
||||||
|
|
||||||
|
# Pack the image into .blend file
|
||||||
|
image.pack()
|
||||||
|
|
||||||
|
# Set color space based on map type
|
||||||
|
if map_type in ['color', 'diffuse', 'albedo']:
|
||||||
|
try:
|
||||||
|
image.colorspace_settings.name = 'sRGB'
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
image.colorspace_settings.name = 'Non-Color'
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
downloaded_maps[map_type] = image
|
||||||
|
|
||||||
|
# Clean up temporary file
|
||||||
|
try:
|
||||||
|
os.unlink(tmp_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if not downloaded_maps:
|
if not downloaded_maps:
|
||||||
return {"error": f"No texture maps found for the requested resolution and format"}
|
return {"error": f"No texture maps found for the requested resolution and format"}
|
||||||
|
|
||||||
# Create a new material with the downloaded textures
|
|
||||||
mat = bpy.data.materials.new(name=asset_id)
|
|
||||||
mat.use_nodes = True
|
|
||||||
nodes = mat.node_tree.nodes
|
|
||||||
links = mat.node_tree.links
|
|
||||||
|
|
||||||
# Clear default nodes
|
|
||||||
for node in nodes:
|
|
||||||
nodes.remove(node)
|
|
||||||
|
|
||||||
# Create output node
|
|
||||||
output = nodes.new(type='ShaderNodeOutputMaterial')
|
|
||||||
output.location = (300, 0)
|
|
||||||
|
|
||||||
# Create principled BSDF node
|
|
||||||
principled = nodes.new(type='ShaderNodeBsdfPrincipled')
|
|
||||||
principled.location = (0, 0)
|
|
||||||
links.new(principled.outputs[0], output.inputs[0])
|
|
||||||
|
|
||||||
# Add texture nodes based on available maps
|
|
||||||
tex_coord = nodes.new(type='ShaderNodeTexCoord')
|
|
||||||
tex_coord.location = (-800, 0)
|
|
||||||
|
|
||||||
mapping = nodes.new(type='ShaderNodeMapping')
|
|
||||||
mapping.location = (-600, 0)
|
|
||||||
links.new(tex_coord.outputs['UV'], mapping.inputs['Vector'])
|
|
||||||
|
|
||||||
# Position offset for texture nodes
|
|
||||||
x_pos = -400
|
|
||||||
y_pos = 300
|
|
||||||
|
|
||||||
# Connect different texture maps
|
|
||||||
for map_type, image in downloaded_maps.items():
|
|
||||||
tex_node = nodes.new(type='ShaderNodeTexImage')
|
|
||||||
tex_node.location = (x_pos, y_pos)
|
|
||||||
tex_node.image = image
|
|
||||||
tex_node.image.colorspace_settings.name = 'sRGB' if map_type in ['color', 'diffuse', 'albedo'] else 'Non-Color'
|
|
||||||
|
|
||||||
links.new(mapping.outputs['Vector'], tex_node.inputs['Vector'])
|
# Create a new material with the downloaded textures
|
||||||
|
mat = bpy.data.materials.new(name=asset_id)
|
||||||
|
mat.use_nodes = True
|
||||||
|
nodes = mat.node_tree.nodes
|
||||||
|
links = mat.node_tree.links
|
||||||
|
|
||||||
# Connect to appropriate input on Principled BSDF
|
# Clear default nodes
|
||||||
if map_type in ['color', 'diffuse', 'albedo']:
|
for node in nodes:
|
||||||
links.new(tex_node.outputs['Color'], principled.inputs['Base Color'])
|
nodes.remove(node)
|
||||||
elif map_type in ['roughness', 'rough']:
|
|
||||||
links.new(tex_node.outputs['Color'], principled.inputs['Roughness'])
|
|
||||||
elif map_type in ['metallic', 'metalness', 'metal']:
|
|
||||||
links.new(tex_node.outputs['Color'], principled.inputs['Metallic'])
|
|
||||||
elif map_type in ['normal', 'nor']:
|
|
||||||
# Add normal map node
|
|
||||||
normal_map = nodes.new(type='ShaderNodeNormalMap')
|
|
||||||
normal_map.location = (x_pos + 200, y_pos)
|
|
||||||
links.new(tex_node.outputs['Color'], normal_map.inputs['Color'])
|
|
||||||
links.new(normal_map.outputs['Normal'], principled.inputs['Normal'])
|
|
||||||
elif map_type in ['displacement', 'disp', 'height']:
|
|
||||||
# Add displacement node
|
|
||||||
disp_node = nodes.new(type='ShaderNodeDisplacement')
|
|
||||||
disp_node.location = (x_pos + 200, y_pos - 200)
|
|
||||||
links.new(tex_node.outputs['Color'], disp_node.inputs['Height'])
|
|
||||||
links.new(disp_node.outputs['Displacement'], output.inputs['Displacement'])
|
|
||||||
|
|
||||||
y_pos -= 250
|
# Create output node
|
||||||
|
output = nodes.new(type='ShaderNodeOutputMaterial')
|
||||||
|
output.location = (300, 0)
|
||||||
|
|
||||||
|
# Create principled BSDF node
|
||||||
|
principled = nodes.new(type='ShaderNodeBsdfPrincipled')
|
||||||
|
principled.location = (0, 0)
|
||||||
|
links.new(principled.outputs[0], output.inputs[0])
|
||||||
|
|
||||||
|
# Add texture nodes based on available maps
|
||||||
|
tex_coord = nodes.new(type='ShaderNodeTexCoord')
|
||||||
|
tex_coord.location = (-800, 0)
|
||||||
|
|
||||||
|
mapping = nodes.new(type='ShaderNodeMapping')
|
||||||
|
mapping.location = (-600, 0)
|
||||||
|
links.new(tex_coord.outputs['UV'], mapping.inputs['Vector'])
|
||||||
|
|
||||||
|
# Position offset for texture nodes
|
||||||
|
x_pos = -400
|
||||||
|
y_pos = 300
|
||||||
|
|
||||||
|
# Connect different texture maps
|
||||||
|
for map_type, image in downloaded_maps.items():
|
||||||
|
tex_node = nodes.new(type='ShaderNodeTexImage')
|
||||||
|
tex_node.location = (x_pos, y_pos)
|
||||||
|
tex_node.image = image
|
||||||
|
|
||||||
|
# Set color space based on map type
|
||||||
|
if map_type.lower() in ['color', 'diffuse', 'albedo']:
|
||||||
|
try:
|
||||||
|
tex_node.image.colorspace_settings.name = 'sRGB'
|
||||||
|
except:
|
||||||
|
pass # Use default if sRGB not available
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
tex_node.image.colorspace_settings.name = 'Non-Color'
|
||||||
|
except:
|
||||||
|
pass # Use default if Non-Color not available
|
||||||
|
|
||||||
|
links.new(mapping.outputs['Vector'], tex_node.inputs['Vector'])
|
||||||
|
|
||||||
|
# Connect to appropriate input on Principled BSDF
|
||||||
|
if map_type.lower() in ['color', 'diffuse', 'albedo']:
|
||||||
|
links.new(tex_node.outputs['Color'], principled.inputs['Base Color'])
|
||||||
|
elif map_type.lower() in ['roughness', 'rough']:
|
||||||
|
links.new(tex_node.outputs['Color'], principled.inputs['Roughness'])
|
||||||
|
elif map_type.lower() in ['metallic', 'metalness', 'metal']:
|
||||||
|
links.new(tex_node.outputs['Color'], principled.inputs['Metallic'])
|
||||||
|
elif map_type.lower() in ['normal', 'nor']:
|
||||||
|
# Add normal map node
|
||||||
|
normal_map = nodes.new(type='ShaderNodeNormalMap')
|
||||||
|
normal_map.location = (x_pos + 200, y_pos)
|
||||||
|
links.new(tex_node.outputs['Color'], normal_map.inputs['Color'])
|
||||||
|
links.new(normal_map.outputs['Normal'], principled.inputs['Normal'])
|
||||||
|
elif map_type in ['displacement', 'disp', 'height']:
|
||||||
|
# Add displacement node
|
||||||
|
disp_node = nodes.new(type='ShaderNodeDisplacement')
|
||||||
|
disp_node.location = (x_pos + 200, y_pos - 200)
|
||||||
|
links.new(tex_node.outputs['Color'], disp_node.inputs['Height'])
|
||||||
|
links.new(disp_node.outputs['Displacement'], output.inputs['Displacement'])
|
||||||
|
|
||||||
|
y_pos -= 250
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Texture {asset_id} imported as material",
|
||||||
|
"material": mat.name,
|
||||||
|
"maps": list(downloaded_maps.keys())
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
except Exception as e:
|
||||||
"success": True,
|
return {"error": f"Failed to process textures: {str(e)}"}
|
||||||
"message": f"Texture {asset_id} imported as material",
|
|
||||||
"material": mat.name,
|
|
||||||
"maps": list(downloaded_maps.keys())
|
|
||||||
}
|
|
||||||
|
|
||||||
elif asset_type == "models":
|
elif asset_type == "models":
|
||||||
# For models, prefer glTF format if available
|
# For models, prefer glTF format if available
|
||||||
@ -798,6 +832,312 @@ class BlenderMCPServer:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": f"Failed to download asset: {str(e)}"}
|
return {"error": f"Failed to download asset: {str(e)}"}
|
||||||
|
|
||||||
|
def set_texture(self, object_name, texture_id):
|
||||||
|
"""Apply a previously downloaded Polyhaven texture to an object by creating a new material"""
|
||||||
|
try:
|
||||||
|
# Get the object
|
||||||
|
obj = bpy.data.objects.get(object_name)
|
||||||
|
if not obj:
|
||||||
|
return {"error": f"Object not found: {object_name}"}
|
||||||
|
|
||||||
|
# Make sure object can accept materials
|
||||||
|
if not hasattr(obj, 'data') or not hasattr(obj.data, 'materials'):
|
||||||
|
return {"error": f"Object {object_name} cannot accept materials"}
|
||||||
|
|
||||||
|
# Find all images related to this texture and ensure they're properly loaded
|
||||||
|
texture_images = {}
|
||||||
|
for img in bpy.data.images:
|
||||||
|
if img.name.startswith(texture_id + "_"):
|
||||||
|
# Extract the map type from the image name
|
||||||
|
map_type = img.name.split('_')[-1].split('.')[0]
|
||||||
|
|
||||||
|
# Force a reload of the image
|
||||||
|
img.reload()
|
||||||
|
|
||||||
|
# Ensure proper color space
|
||||||
|
if map_type.lower() in ['color', 'diffuse', 'albedo']:
|
||||||
|
try:
|
||||||
|
img.colorspace_settings.name = 'sRGB'
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
img.colorspace_settings.name = 'Non-Color'
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Ensure the image is packed
|
||||||
|
if not img.packed_file:
|
||||||
|
img.pack()
|
||||||
|
|
||||||
|
texture_images[map_type] = img
|
||||||
|
print(f"Loaded texture map: {map_type} - {img.name}")
|
||||||
|
|
||||||
|
# Debug info
|
||||||
|
print(f"Image size: {img.size[0]}x{img.size[1]}")
|
||||||
|
print(f"Color space: {img.colorspace_settings.name}")
|
||||||
|
print(f"File format: {img.file_format}")
|
||||||
|
print(f"Is packed: {bool(img.packed_file)}")
|
||||||
|
|
||||||
|
if not texture_images:
|
||||||
|
return {"error": f"No texture images found for: {texture_id}. Please download the texture first."}
|
||||||
|
|
||||||
|
# Create a new material
|
||||||
|
new_mat_name = f"{texture_id}_material_{object_name}"
|
||||||
|
|
||||||
|
# Remove any existing material with this name to avoid conflicts
|
||||||
|
existing_mat = bpy.data.materials.get(new_mat_name)
|
||||||
|
if existing_mat:
|
||||||
|
bpy.data.materials.remove(existing_mat)
|
||||||
|
|
||||||
|
new_mat = bpy.data.materials.new(name=new_mat_name)
|
||||||
|
new_mat.use_nodes = True
|
||||||
|
|
||||||
|
# Set up the material nodes
|
||||||
|
nodes = new_mat.node_tree.nodes
|
||||||
|
links = new_mat.node_tree.links
|
||||||
|
|
||||||
|
# Clear default nodes
|
||||||
|
nodes.clear()
|
||||||
|
|
||||||
|
# Create output node
|
||||||
|
output = nodes.new(type='ShaderNodeOutputMaterial')
|
||||||
|
output.location = (600, 0)
|
||||||
|
|
||||||
|
# Create principled BSDF node
|
||||||
|
principled = nodes.new(type='ShaderNodeBsdfPrincipled')
|
||||||
|
principled.location = (300, 0)
|
||||||
|
links.new(principled.outputs[0], output.inputs[0])
|
||||||
|
|
||||||
|
# Add texture nodes based on available maps
|
||||||
|
tex_coord = nodes.new(type='ShaderNodeTexCoord')
|
||||||
|
tex_coord.location = (-800, 0)
|
||||||
|
|
||||||
|
mapping = nodes.new(type='ShaderNodeMapping')
|
||||||
|
mapping.location = (-600, 0)
|
||||||
|
# Set a smaller scale to make the texture more visible
|
||||||
|
mapping.inputs['Scale'].default_value = (0.5, 0.5, 0.5)
|
||||||
|
links.new(tex_coord.outputs['UV'], mapping.inputs['Vector'])
|
||||||
|
|
||||||
|
# Position offset for texture nodes
|
||||||
|
x_pos = -400
|
||||||
|
y_pos = 300
|
||||||
|
|
||||||
|
# Connect different texture maps
|
||||||
|
for map_type, image in texture_images.items():
|
||||||
|
tex_node = nodes.new(type='ShaderNodeTexImage')
|
||||||
|
tex_node.location = (x_pos, y_pos)
|
||||||
|
tex_node.image = image
|
||||||
|
|
||||||
|
# Set color space based on map type
|
||||||
|
if map_type.lower() in ['color', 'diffuse', 'albedo']:
|
||||||
|
try:
|
||||||
|
tex_node.image.colorspace_settings.name = 'sRGB'
|
||||||
|
except:
|
||||||
|
pass # Use default if sRGB not available
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
tex_node.image.colorspace_settings.name = 'Non-Color'
|
||||||
|
except:
|
||||||
|
pass # Use default if Non-Color not available
|
||||||
|
|
||||||
|
links.new(mapping.outputs['Vector'], tex_node.inputs['Vector'])
|
||||||
|
|
||||||
|
# Connect to appropriate input on Principled BSDF
|
||||||
|
if map_type.lower() in ['color', 'diffuse', 'albedo']:
|
||||||
|
links.new(tex_node.outputs['Color'], principled.inputs['Base Color'])
|
||||||
|
elif map_type.lower() in ['roughness', 'rough']:
|
||||||
|
links.new(tex_node.outputs['Color'], principled.inputs['Roughness'])
|
||||||
|
elif map_type.lower() in ['metallic', 'metalness', 'metal']:
|
||||||
|
links.new(tex_node.outputs['Color'], principled.inputs['Metallic'])
|
||||||
|
elif map_type.lower() in ['normal', 'nor', 'dx', 'gl']:
|
||||||
|
# Add normal map node
|
||||||
|
normal_map = nodes.new(type='ShaderNodeNormalMap')
|
||||||
|
normal_map.location = (x_pos + 200, y_pos)
|
||||||
|
links.new(tex_node.outputs['Color'], normal_map.inputs['Color'])
|
||||||
|
links.new(normal_map.outputs['Normal'], principled.inputs['Normal'])
|
||||||
|
elif map_type.lower() in ['displacement', 'disp', 'height']:
|
||||||
|
# Add displacement node
|
||||||
|
disp_node = nodes.new(type='ShaderNodeDisplacement')
|
||||||
|
disp_node.location = (x_pos + 200, y_pos - 200)
|
||||||
|
disp_node.inputs['Scale'].default_value = 0.1 # Reduce displacement strength
|
||||||
|
links.new(tex_node.outputs['Color'], disp_node.inputs['Height'])
|
||||||
|
links.new(disp_node.outputs['Displacement'], output.inputs['Displacement'])
|
||||||
|
|
||||||
|
y_pos -= 250
|
||||||
|
|
||||||
|
# Second pass: Connect nodes with proper handling for special cases
|
||||||
|
texture_nodes = {}
|
||||||
|
|
||||||
|
# First find all texture nodes and store them by map type
|
||||||
|
for node in nodes:
|
||||||
|
if node.type == 'TEX_IMAGE' and node.image:
|
||||||
|
for map_type, image in texture_images.items():
|
||||||
|
if node.image == image:
|
||||||
|
texture_nodes[map_type] = node
|
||||||
|
break
|
||||||
|
|
||||||
|
# Now connect everything using the nodes instead of images
|
||||||
|
# Handle base color (diffuse)
|
||||||
|
for map_name in ['color', 'diffuse', 'albedo']:
|
||||||
|
if map_name in texture_nodes:
|
||||||
|
links.new(texture_nodes[map_name].outputs['Color'], principled.inputs['Base Color'])
|
||||||
|
print(f"Connected {map_name} to Base Color")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Handle roughness
|
||||||
|
for map_name in ['roughness', 'rough']:
|
||||||
|
if map_name in texture_nodes:
|
||||||
|
links.new(texture_nodes[map_name].outputs['Color'], principled.inputs['Roughness'])
|
||||||
|
print(f"Connected {map_name} to Roughness")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Handle metallic
|
||||||
|
for map_name in ['metallic', 'metalness', 'metal']:
|
||||||
|
if map_name in texture_nodes:
|
||||||
|
links.new(texture_nodes[map_name].outputs['Color'], principled.inputs['Metallic'])
|
||||||
|
print(f"Connected {map_name} to Metallic")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Handle normal maps
|
||||||
|
for map_name in ['gl', 'dx', 'nor']:
|
||||||
|
if map_name in texture_nodes:
|
||||||
|
normal_map_node = nodes.new(type='ShaderNodeNormalMap')
|
||||||
|
normal_map_node.location = (100, 100)
|
||||||
|
links.new(texture_nodes[map_name].outputs['Color'], normal_map_node.inputs['Color'])
|
||||||
|
links.new(normal_map_node.outputs['Normal'], principled.inputs['Normal'])
|
||||||
|
print(f"Connected {map_name} to Normal")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Handle displacement
|
||||||
|
for map_name in ['displacement', 'disp', 'height']:
|
||||||
|
if map_name in texture_nodes:
|
||||||
|
disp_node = nodes.new(type='ShaderNodeDisplacement')
|
||||||
|
disp_node.location = (300, -200)
|
||||||
|
disp_node.inputs['Scale'].default_value = 0.1 # Reduce displacement strength
|
||||||
|
links.new(texture_nodes[map_name].outputs['Color'], disp_node.inputs['Height'])
|
||||||
|
links.new(disp_node.outputs['Displacement'], output.inputs['Displacement'])
|
||||||
|
print(f"Connected {map_name} to Displacement")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Handle ARM texture (Ambient Occlusion, Roughness, Metallic)
|
||||||
|
if 'arm' in texture_nodes:
|
||||||
|
separate_rgb = nodes.new(type='ShaderNodeSeparateRGB')
|
||||||
|
separate_rgb.location = (-200, -100)
|
||||||
|
links.new(texture_nodes['arm'].outputs['Color'], separate_rgb.inputs['Image'])
|
||||||
|
|
||||||
|
# Connect Roughness (G) if no dedicated roughness map
|
||||||
|
if not any(map_name in texture_nodes for map_name in ['roughness', 'rough']):
|
||||||
|
links.new(separate_rgb.outputs['G'], principled.inputs['Roughness'])
|
||||||
|
print("Connected ARM.G to Roughness")
|
||||||
|
|
||||||
|
# Connect Metallic (B) if no dedicated metallic map
|
||||||
|
if not any(map_name in texture_nodes for map_name in ['metallic', 'metalness', 'metal']):
|
||||||
|
links.new(separate_rgb.outputs['B'], principled.inputs['Metallic'])
|
||||||
|
print("Connected ARM.B to Metallic")
|
||||||
|
|
||||||
|
# For AO (R channel), multiply with base color if we have one
|
||||||
|
base_color_node = None
|
||||||
|
for map_name in ['color', 'diffuse', 'albedo']:
|
||||||
|
if map_name in texture_nodes:
|
||||||
|
base_color_node = texture_nodes[map_name]
|
||||||
|
break
|
||||||
|
|
||||||
|
if base_color_node:
|
||||||
|
mix_node = nodes.new(type='ShaderNodeMixRGB')
|
||||||
|
mix_node.location = (100, 200)
|
||||||
|
mix_node.blend_type = 'MULTIPLY'
|
||||||
|
mix_node.inputs['Fac'].default_value = 0.8 # 80% influence
|
||||||
|
|
||||||
|
# Disconnect direct connection to base color
|
||||||
|
for link in base_color_node.outputs['Color'].links:
|
||||||
|
if link.to_socket == principled.inputs['Base Color']:
|
||||||
|
links.remove(link)
|
||||||
|
|
||||||
|
# Connect through the mix node
|
||||||
|
links.new(base_color_node.outputs['Color'], mix_node.inputs[1])
|
||||||
|
links.new(separate_rgb.outputs['R'], mix_node.inputs[2])
|
||||||
|
links.new(mix_node.outputs['Color'], principled.inputs['Base Color'])
|
||||||
|
print("Connected ARM.R to AO mix with Base Color")
|
||||||
|
|
||||||
|
# Handle AO (Ambient Occlusion) if separate
|
||||||
|
if 'ao' in texture_nodes:
|
||||||
|
base_color_node = None
|
||||||
|
for map_name in ['color', 'diffuse', 'albedo']:
|
||||||
|
if map_name in texture_nodes:
|
||||||
|
base_color_node = texture_nodes[map_name]
|
||||||
|
break
|
||||||
|
|
||||||
|
if base_color_node:
|
||||||
|
mix_node = nodes.new(type='ShaderNodeMixRGB')
|
||||||
|
mix_node.location = (100, 200)
|
||||||
|
mix_node.blend_type = 'MULTIPLY'
|
||||||
|
mix_node.inputs['Fac'].default_value = 0.8 # 80% influence
|
||||||
|
|
||||||
|
# Disconnect direct connection to base color
|
||||||
|
for link in base_color_node.outputs['Color'].links:
|
||||||
|
if link.to_socket == principled.inputs['Base Color']:
|
||||||
|
links.remove(link)
|
||||||
|
|
||||||
|
# Connect through the mix node
|
||||||
|
links.new(base_color_node.outputs['Color'], mix_node.inputs[1])
|
||||||
|
links.new(texture_nodes['ao'].outputs['Color'], mix_node.inputs[2])
|
||||||
|
links.new(mix_node.outputs['Color'], principled.inputs['Base Color'])
|
||||||
|
print("Connected AO to mix with Base Color")
|
||||||
|
|
||||||
|
# CRITICAL: Make sure to clear all existing materials from the object
|
||||||
|
while len(obj.data.materials) > 0:
|
||||||
|
obj.data.materials.pop(index=0)
|
||||||
|
|
||||||
|
# Assign the new material to the object
|
||||||
|
obj.data.materials.append(new_mat)
|
||||||
|
|
||||||
|
# CRITICAL: Make the object active and select it
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
obj.select_set(True)
|
||||||
|
|
||||||
|
# CRITICAL: Force Blender to update the material
|
||||||
|
bpy.context.view_layer.update()
|
||||||
|
|
||||||
|
# Get the list of texture maps
|
||||||
|
texture_maps = list(texture_images.keys())
|
||||||
|
|
||||||
|
# Get info about texture nodes for debugging
|
||||||
|
material_info = {
|
||||||
|
"name": new_mat.name,
|
||||||
|
"has_nodes": new_mat.use_nodes,
|
||||||
|
"node_count": len(new_mat.node_tree.nodes),
|
||||||
|
"texture_nodes": []
|
||||||
|
}
|
||||||
|
|
||||||
|
for node in new_mat.node_tree.nodes:
|
||||||
|
if node.type == 'TEX_IMAGE' and node.image:
|
||||||
|
connections = []
|
||||||
|
for output in node.outputs:
|
||||||
|
for link in output.links:
|
||||||
|
connections.append(f"{output.name} → {link.to_node.name}.{link.to_socket.name}")
|
||||||
|
|
||||||
|
material_info["texture_nodes"].append({
|
||||||
|
"name": node.name,
|
||||||
|
"image": node.image.name,
|
||||||
|
"colorspace": node.image.colorspace_settings.name,
|
||||||
|
"connections": connections
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Created new material and applied texture {texture_id} to {object_name}",
|
||||||
|
"material": new_mat.name,
|
||||||
|
"maps": texture_maps,
|
||||||
|
"material_info": material_info
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in set_texture: {str(e)}")
|
||||||
|
traceback.print_exc()
|
||||||
|
return {"error": f"Failed to apply texture: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
# Blender UI Panel
|
# Blender UI Panel
|
||||||
class BLENDERMCP_PT_Panel(bpy.types.Panel):
|
class BLENDERMCP_PT_Panel(bpy.types.Panel):
|
||||||
bl_label = "Blender MCP"
|
bl_label = "Blender MCP"
|
||||||
|
|||||||
@ -572,6 +572,67 @@ def download_polyhaven_asset(
|
|||||||
logger.error(f"Error downloading Polyhaven asset: {str(e)}")
|
logger.error(f"Error downloading Polyhaven asset: {str(e)}")
|
||||||
return f"Error downloading Polyhaven asset: {str(e)}"
|
return f"Error downloading Polyhaven asset: {str(e)}"
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def set_texture(
|
||||||
|
ctx: Context,
|
||||||
|
object_name: str,
|
||||||
|
texture_id: str
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Apply a previously downloaded Polyhaven texture to an object.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- object_name: Name of the object to apply the texture to
|
||||||
|
- texture_id: ID of the Polyhaven texture to apply (must be downloaded first)
|
||||||
|
|
||||||
|
Returns a message indicating success or failure.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get the global connection
|
||||||
|
blender = get_blender_connection()
|
||||||
|
|
||||||
|
result = blender.send_command("set_texture", {
|
||||||
|
"object_name": object_name,
|
||||||
|
"texture_id": texture_id
|
||||||
|
})
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
return f"Error: {result['error']}"
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
material_name = result.get("material", "")
|
||||||
|
maps = ", ".join(result.get("maps", []))
|
||||||
|
|
||||||
|
# Add detailed material info
|
||||||
|
material_info = result.get("material_info", {})
|
||||||
|
node_count = material_info.get("node_count", 0)
|
||||||
|
has_nodes = material_info.get("has_nodes", False)
|
||||||
|
texture_nodes = material_info.get("texture_nodes", [])
|
||||||
|
|
||||||
|
output = f"Successfully applied texture '{texture_id}' to {object_name}.\n"
|
||||||
|
output += f"Using material '{material_name}' with maps: {maps}.\n\n"
|
||||||
|
output += f"Material has nodes: {has_nodes}\n"
|
||||||
|
output += f"Total node count: {node_count}\n\n"
|
||||||
|
|
||||||
|
if texture_nodes:
|
||||||
|
output += "Texture nodes:\n"
|
||||||
|
for node in texture_nodes:
|
||||||
|
output += f"- {node['name']} using image: {node['image']}\n"
|
||||||
|
if node['connections']:
|
||||||
|
output += " Connections:\n"
|
||||||
|
for conn in node['connections']:
|
||||||
|
output += f" {conn}\n"
|
||||||
|
else:
|
||||||
|
output += "No texture nodes found in the material.\n"
|
||||||
|
|
||||||
|
return output
|
||||||
|
else:
|
||||||
|
return f"Failed to apply texture: {result.get('message', 'Unknown error')}"
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error applying texture: {str(e)}")
|
||||||
|
return f"Error applying texture: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
# Main execution
|
# Main execution
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user