111 lines
3.8 KiB
Python
111 lines
3.8 KiB
Python
import json
|
|
import pathlib
|
|
import sys
|
|
|
|
import bpy
|
|
|
|
|
|
def group_weight(vertex, group_index):
|
|
return next((item.weight for item in vertex.groups if item.group == group_index), 0.0)
|
|
|
|
|
|
def set_weight(obj, vertex_index, group_index, value):
|
|
group = obj.vertex_groups[group_index]
|
|
if value == 0.0:
|
|
try:
|
|
group.remove([vertex_index])
|
|
except RuntimeError:
|
|
pass
|
|
else:
|
|
group.add([vertex_index], value, "REPLACE")
|
|
|
|
|
|
def normalize_vertex(obj, vertex_index):
|
|
vertex = obj.data.vertices[vertex_index]
|
|
total = sum(item.weight for item in vertex.groups)
|
|
if total > 0.0:
|
|
for item in list(vertex.groups):
|
|
set_weight(obj, vertex_index, item.group, item.weight / total)
|
|
|
|
|
|
def limit_vertex(obj, vertex_index, limit):
|
|
vertex = obj.data.vertices[vertex_index]
|
|
while len(vertex.groups) > limit:
|
|
remove = min(vertex.groups, key=lambda item: (item.weight, -item.group))
|
|
obj.vertex_groups[remove.group].remove([vertex_index])
|
|
|
|
|
|
def mirror_map(obj, axis, tolerance):
|
|
result = {}
|
|
for source in obj.data.vertices:
|
|
reflected = source.co.copy()
|
|
reflected[axis] = -reflected[axis]
|
|
candidate = min(obj.data.vertices, key=lambda item: ((item.co - reflected).length, item.index))
|
|
if (candidate.co - reflected).length > tolerance:
|
|
raise RuntimeError("mesh is not mirror symmetric")
|
|
result[source.index] = candidate.index
|
|
if any(result[result[index]] != index for index in result):
|
|
raise RuntimeError("mesh mirror map is not reciprocal")
|
|
return result
|
|
|
|
|
|
def snapshot(obj):
|
|
groups = [group.name for group in obj.vertex_groups]
|
|
return {
|
|
"groups": groups,
|
|
"vertices": [
|
|
{"index": vertex.index, "weights": {
|
|
groups[item.group]: round(item.weight, 9) for item in vertex.groups
|
|
}}
|
|
for vertex in obj.data.vertices
|
|
],
|
|
}
|
|
|
|
|
|
def main(blend_path, output_path):
|
|
bpy.ops.wm.open_mainfile(filepath=str(pathlib.Path(blend_path).resolve()), load_ui=False)
|
|
obj = bpy.data.objects.get("RiggedShapeObject")
|
|
if obj is None or obj.type != "MESH":
|
|
raise RuntimeError("weight paint fixture object is missing")
|
|
group = obj.vertex_groups.get("WebPaintGroup") or obj.vertex_groups.new(name="WebPaintGroup")
|
|
|
|
set_weight(obj, 0, group.index, 0.75)
|
|
set_weight(obj, 1, group.index, 0.25)
|
|
steps = [{"name": "initial", **snapshot(obj)}]
|
|
|
|
set_weight(obj, 2, group.index, 0.5)
|
|
normalize_vertex(obj, 2)
|
|
steps.append({"name": "normalize", **snapshot(obj)})
|
|
|
|
set_weight(obj, 3, group.index, 0.4)
|
|
limit_vertex(obj, 3, 2)
|
|
normalize_vertex(obj, 3)
|
|
steps.append({"name": "limit-normalize", **snapshot(obj)})
|
|
|
|
mapping = mirror_map(obj, 0, 1e-4)
|
|
for source in (0, mapping[0]):
|
|
set_weight(obj, source, group.index, 0.9)
|
|
steps.append({"name": "mirror", **snapshot(obj)})
|
|
|
|
result = {
|
|
"schemaVersion": 1,
|
|
"fixture": pathlib.Path(blend_path).name,
|
|
"blenderVersion": bpy.app.version_string,
|
|
"object": obj.name,
|
|
"mesh": obj.data.name,
|
|
"operations": [
|
|
{"name": "normalize", "vertex": 2},
|
|
{"name": "limit-normalize", "vertex": 3, "limit": 2},
|
|
{"name": "mirror", "vertices": [0, 1], "axis": 0, "tolerance": 1e-4},
|
|
],
|
|
"steps": steps,
|
|
"tolerance": 1e-6,
|
|
}
|
|
pathlib.Path(output_path).write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if "--" not in sys.argv or len(sys.argv) <= sys.argv.index("--") + 2:
|
|
raise SystemExit("usage: blender -b --python generate-weight-paint-golden.py -- input.blend output.json")
|
|
main(*sys.argv[sys.argv.index("--") + 1:sys.argv.index("--") + 3])
|