Spaces:
Paused
Paused
Download scripts/clean_character_material.py from mantrakp/component-studio: direct link, hf CLI and curl.
- Browser
- Download file 4.29 kB
-
https://huggingface.co/spaces/mantrakp/component-studio/resolve/main/scripts/clean_character_material.py
- Command line
-
hf download hf://spaces/mantrakp/component-studio/scripts/clean_character_material.py
-
curl -L -o clean_character_material.py https://huggingface.co/spaces/mantrakp/component-studio/resolve/main/scripts/clean_character_material.py
4.29 kB
| """Bake a smooth ivory material on rear suit panels while retaining colored trim.""" | |
| import argparse | |
| from pathlib import Path | |
| import sys | |
| import bpy | |
| p = argparse.ArgumentParser() | |
| p.add_argument("--input", required=True) | |
| p.add_argument("--output", required=True) | |
| a = p.parse_args(sys.argv[sys.argv.index("--") + 1 :]) | |
| out = Path(a.output).resolve() | |
| out.mkdir(parents=True, exist_ok=True) | |
| bpy.ops.wm.open_mainfile(filepath=str(Path(a.input).resolve())) | |
| scene = bpy.context.scene | |
| scene.frame_set(1) | |
| objects = [o for o in scene.objects if o.type == "MESH"] | |
| rig = next(o for o in scene.objects if o.type == "ARMATURE") | |
| rig.data.pose_position = "REST" | |
| source = objects[0].data.materials[0] | |
| bsdf = next(n for n in source.node_tree.nodes if n.type == "BSDF_PRINCIPLED") | |
| original = bsdf.inputs["Base Color"].links[0].from_node.image | |
| mat = bpy.data.materials.new("Rear ivory cleanup bake") | |
| mat.use_nodes = True | |
| nodes = mat.node_tree.nodes | |
| links = mat.node_tree.links | |
| nodes.clear() | |
| def math(op, *values): | |
| n = nodes.new("ShaderNodeMath") | |
| n.operation = op | |
| for i, v in enumerate(values): | |
| if isinstance(v, (int, float)): | |
| n.inputs[i].default_value = v | |
| else: | |
| links.new(v, n.inputs[i]) | |
| return n.outputs[0] | |
| tex = nodes.new("ShaderNodeTexImage") | |
| tex.image = original | |
| hsv = nodes.new("ShaderNodeSeparateColor") | |
| hsv.mode = "HSV" | |
| links.new(tex.outputs["Color"], hsv.inputs[0]) | |
| geom = nodes.new("ShaderNodeNewGeometry") | |
| normal = nodes.new("ShaderNodeSeparateXYZ") | |
| links.new(geom.outputs["Normal"], normal.inputs[0]) | |
| pos = nodes.new("ShaderNodeSeparateXYZ") | |
| links.new(geom.outputs["Position"], pos.inputs[0]) | |
| rear = math("MINIMUM", math("MAXIMUM", math("MULTIPLY", math("ADD", normal.outputs["Y"], 0.05), 3), 0), 1) | |
| ivory = math("MULTIPLY", math("LESS_THAN", hsv.outputs[0], 0.22), math("LESS_THAN", hsv.outputs[1], 0.60)) | |
| ivory = math("MULTIPLY", ivory, math("GREATER_THAN", hsv.outputs[2], 0.10)) | |
| mask = math("MULTIPLY", math("MULTIPLY", rear, ivory), math("LESS_THAN", pos.outputs["Z"], 0.76)) | |
| mix = nodes.new("ShaderNodeMixRGB") | |
| links.new(mask, mix.inputs[0]) | |
| links.new(tex.outputs["Color"], mix.inputs[1]) | |
| mix.inputs[2].default_value = (0.72, 0.74, 0.53, 1) | |
| emit = nodes.new("ShaderNodeEmission") | |
| links.new(mix.outputs[0], emit.inputs[0]) | |
| output = nodes.new("ShaderNodeOutputMaterial") | |
| links.new(emit.outputs[0], output.inputs["Surface"]) | |
| baked = bpy.data.images.new("Reference and clean suit atlas", 4096, 4096, alpha=True) | |
| target = nodes.new("ShaderNodeTexImage") | |
| target.image = baked | |
| nodes.active = target | |
| bpy.ops.object.select_all(action="DESELECT") | |
| for obj in objects: | |
| obj.data.materials.clear() | |
| obj.data.materials.append(mat) | |
| obj.select_set(True) | |
| bpy.context.view_layer.objects.active = objects[0] | |
| bpy.ops.object.duplicate() | |
| copies = list(bpy.context.selected_objects) | |
| bpy.context.view_layer.objects.active = copies[0] | |
| bpy.ops.object.join() | |
| proxy = bpy.context.object | |
| scene.render.engine = "CYCLES" | |
| scene.cycles.samples = 1 | |
| scene.render.bake.margin = 16 | |
| bpy.ops.object.bake(type="EMIT") | |
| baked.filepath_raw = str(out / "reference-atlas.png") | |
| baked.file_format = "PNG" | |
| baked.save() | |
| bpy.data.objects.remove(proxy, do_unlink=True) | |
| final = bpy.data.materials.new("Reference matched painted suit") | |
| final.use_nodes = True | |
| bsdf = final.node_tree.nodes.get("Principled BSDF") | |
| bsdf.inputs["Roughness"].default_value = 0.75 | |
| bsdf.inputs["Metallic"].default_value = 0 | |
| tex = final.node_tree.nodes.new("ShaderNodeTexImage") | |
| tex.image = baked | |
| final.node_tree.links.new(tex.outputs["Color"], bsdf.inputs["Base Color"]) | |
| for obj in objects: | |
| obj.data.materials.clear() | |
| obj.data.materials.append(final) | |
| bpy.ops.object.select_all(action="DESELECT") | |
| for obj in objects: | |
| obj.select_set(True) | |
| bpy.ops.export_scene.gltf( | |
| filepath=str(out / "character-static.glb"), | |
| export_format="GLB", | |
| use_selection=True, | |
| export_animations=False, | |
| export_skins=False, | |
| ) | |
| rig.data.pose_position = "POSE" | |
| rig.select_set(True) | |
| bpy.context.view_layer.objects.active = rig | |
| bpy.ops.export_scene.gltf( | |
| filepath=str(out / "astronaut-wave.glb"), | |
| export_format="GLB", | |
| use_selection=True, | |
| export_animations=True, | |
| export_skins=True, | |
| export_frame_range=True, | |
| ) | |
| bpy.ops.wm.save_as_mainfile(filepath=str(out / "astronaut-wave.blend")) | |