Files
animation/tools/handpose_skin_to_obj.py
T

99 lines
3.6 KiB
Python
Raw Normal View History

"""Skin a baked-pose GLB's hand region with Godot-exact LBS and write it as a plain OBJ.
usage: godot_skin_hand_obj.py posed.glb side(l|r) out.obj"""
import json, struct, sys
def read_glb(path):
d = open(path, "rb").read()
length = struct.unpack_from("<I", d, 8)[0]
off = 12; g = None; b = None
while off < length:
clen, ct = struct.unpack_from("<II", d, off); off += 8
if ct == 0x4E4F534A: g = json.loads(d[off:off+clen])
else: b = d[off:off+clen]
off += clen
return g, b
def acc(g, b, i):
a = g["accessors"][i]; bv = g["bufferViews"][a["bufferView"]]
nc = {"SCALAR":1,"VEC2":2,"VEC3":3,"VEC4":4,"MAT4":16}[a["type"]]
fmt = {5121:"B",5123:"H",5125:"I",5126:"f"}[a["componentType"]]
size = struct.calcsize(fmt)*nc; stride = bv.get("byteStride") or size
off = bv.get("byteOffset",0)+a.get("byteOffset",0)
return [struct.unpack_from("<%d%s"%(nc,fmt), b, off+k*stride) for k in range(a["count"])], a["componentType"]
def quat_mat(q):
x,y,z,w = q
return [[1-2*(y*y+z*z),2*(x*y-z*w),2*(x*z+y*w)],
[2*(x*y+z*w),1-2*(x*x+z*z),2*(y*z-x*w)],
[2*(x*z-y*w),2*(y*z+x*w),1-2*(x*x+y*y)]]
def node_local(nd):
t = nd.get("translation",[0,0,0]); r = nd.get("rotation",[0,0,0,1]); s = nd.get("scale",[1,1,1])
R = quat_mat(r)
M = [[R[i][j]*s[j] for j in range(3)]+[t[i]] for i in range(3)]
return M+[[0,0,0,1]]
def matmul(A,B):
return [[sum(A[i][k]*B[k][j] for k in range(4)) for j in range(4)] for i in range(4)]
glb, side, out = sys.argv[1:4]
g, b = read_glb(glb)
names = [nd.get("name","") for nd in g["nodes"]]
loc = [node_local(nd) for nd in g["nodes"]]
parent = {}
for i,nd in enumerate(g["nodes"]):
for c in nd.get("children",[]): parent[c] = i
memo = {}
def gm(i):
if i in memo: return memo[i]
m = loc[i] if i not in parent else matmul(gm(parent[i]), loc[i])
memo[i] = m; return m
G = [gm(i) for i in range(len(g["nodes"]))]
skin = g["skins"][0]; joints = skin["joints"]
ibm,_ = acc(g,b,skin["inverseBindMatrices"])
def m16(row):
return [[row[c*4+r] for c in range(4)] for r in range(4)]
JM = [matmul(G[joints[j]], m16(ibm[j])) for j in range(len(joints))]
REGION = ("hand_", "thumb_", "index_", "middle_", "ring_", "pinky_", "lowerarm_")
region_j = {j for j in range(len(joints))
if any(names[joints[j]].startswith(p) for p in REGION)
and names[joints[j]].endswith("_"+side)}
prim = g["meshes"][0]["primitives"][0]
P,_ = acc(g,b,prim["attributes"]["POSITION"])
J,_ = acc(g,b,prim["attributes"]["JOINTS_0"])
W,wt = acc(g,b,prim["attributes"]["WEIGHTS_0"])
I,_ = acc(g,b,prim["indices"])
wsc = 1.0 if wt==5126 else (1/255 if wt==5121 else 1/65535)
keep = {}
for vi,(p,jr,wr) in enumerate(zip(P,J,W)):
if not any(j in region_j and w>0 for j,w in zip(jr,wr)): continue
x=y=z=0.0
for j,w in zip(jr,wr):
w*=wsc
if w<=0: continue
M=JM[j]
x+=w*(M[0][0]*p[0]+M[0][1]*p[1]+M[0][2]*p[2]+M[0][3])
y+=w*(M[1][0]*p[0]+M[1][1]*p[1]+M[1][2]*p[2]+M[1][3])
z+=w*(M[2][0]*p[0]+M[2][1]*p[1]+M[2][2]*p[2]+M[2][3])
keep[vi]=(x,y,z)
remap = {vi:k+1 for k,vi in enumerate(keep)}
tris = []
flat = [ix[0] for ix in I]
for t in range(0, len(flat), 3):
a1,a2,a3 = flat[t], flat[t+1], flat[t+2]
if a1 in remap and a2 in remap and a3 in remap:
tris.append((remap[a1], remap[a2], remap[a3]))
with open(out, "w") as f:
for vi in keep:
x,y,z = keep[vi]
f.write(f"v {x:.6f} {y:.6f} {z:.6f}\n")
for t in tris:
f.write(f"f {t[0]} {t[1]} {t[2]}\n")
print(f"[skinobj] {out}: {len(keep)} verts, {len(tris)} tris")