Release Healer Man 0.1.24

This commit is contained in:
phenom committed 2026-09-09 16:43:37 -04:00
1 parent c4346751a9
commit bac0da9300
118 files changed
+857 -289

No files matched your search

@@ -6,6 +6,7 @@ Expected invocation:
"""
from __future__ import annotations
from array import array
import json
import math
import os
@@ -111,6 +112,69 @@ def material_image_key(material: bpy.types.Material) -> str:
return f"material:{material.name.casefold()}"
def material_image(material: bpy.types.Material) -> bpy.types.Image | None:
if not material.use_nodes or material.node_tree is None:
return None
for node in material.node_tree.nodes:
if node.type == "TEX_IMAGE" and node.image is not None:
return node.image
return None
def image_alpha_role(image: bpy.types.Image | None) -> str:
"""Classify source alpha without turning fully opaque RGBA textures translucent."""
if image is None or image.channels < 4:
return "opaque"
pixels = array("f", [0.0]) * len(image.pixels)
image.pixels.foreach_get(pixels)
alpha_values = pixels[3::image.channels]
if min(alpha_values, default=1.0) >= 0.999:
return "opaque"
if any(0.001 < alpha < 0.999 for alpha in alpha_values):
return "blend"
return "cutout"
def preserve_material_alpha(material: bpy.types.Material) -> str:
image = material_image(material)
role = image_alpha_role(image)
if role == "opaque" or material.node_tree is None:
return role
image_node = next(
node
for node in material.node_tree.nodes
if node.type == "TEX_IMAGE" and node.image == image
)
principled = next(
(node for node in material.node_tree.nodes if node.type == "BSDF_PRINCIPLED"),
None,
)
if principled is None:
return role
alpha_output = image_node.outputs.get("Alpha")
alpha_input = principled.inputs.get("Alpha")
if alpha_output is None or alpha_input is None:
return role
if role == "blend":
if not alpha_input.is_linked:
material.node_tree.links.new(alpha_output, alpha_input)
material.blend_method = "BLEND"
if hasattr(material, "surface_render_method"):
material.surface_render_method = "BLENDED"
else:
alpha_clip = material.node_tree.nodes.new("ShaderNodeMath")
alpha_clip.name = "RuneWaker Alpha Clip"
alpha_clip.operation = "GREATER_THAN"
alpha_clip.inputs[1].default_value = 0.5
material.node_tree.links.new(alpha_output, alpha_clip.inputs[0])
material.node_tree.links.new(alpha_clip.outputs[0], alpha_input)
material.blend_method = "CLIP"
material.alpha_threshold = 0.5
return role
def deduplicate_materials(objects: list[bpy.types.Object]) -> dict[str, int]:
canonical: dict[str, bpy.types.Material] = {}
replacements = 0
@@ -126,7 +190,7 @@ def deduplicate_materials(objects: list[bpy.types.Object]) -> dict[str, int]:
replacements += 1
else:
canonical[key] = material
material["semanticRole"] = "opaque"
material["semanticRole"] = preserve_material_alpha(material)
material["sourceFormat"] = "RuneWaker DDS"
return {"unique": len(canonical), "replacements": replacements}