"""Door robot arm, red look (2026-10-19): does the bake fit the ship's copies? Run headless on the arm file (or the work copy). It signs every arm mesh the way the coordinator signed the user's open ship session (live_sig_.json: per object, "me" the mesh name, "sig" [vertex count, md5 of the rounded vertex coordinates plus the UVMap's uvs, first 10 hex]) and compares them by mesh name (a .001-style suffix allowed). It also measures the cable curves (which UV axis runs along them, and how long they are) for the braid. blender -b --python build/dra_look_sig.py -- """ import sys import os import re import json import hashlib import bpy import numpy as np HERE = os.path.dirname(os.path.abspath(__file__)) if HERE not in sys.path: sys.path.insert(0, HERE) def mesh_sig(me): """the coordinator's code, unchanged (python floats from a list, round 4, str, md5, 10 hex)""" h = hashlib.md5() co = [0.0] * (len(me.vertices) * 3) me.vertices.foreach_get("co", co) h.update(str([round(c, 4) for c in co]).encode()) uv = me.uv_layers.get("UVMap") if uv: u = [0.0] * (len(me.loops) * 2) uv.data.foreach_get("uv", u) h.update(str([round(c, 4) for c in u]).encode()) return [len(me.vertices), h.hexdigest()[:10]] def base(name): return re.sub(r"\.\d{3}$", "", name) def cables(): """the evaluated curve meshes: which UV axis is along the curve, and each curve's length""" dg = bpy.context.evaluated_depsgraph_get() out = {} for o in bpy.data.objects: if o.type != 'CURVE' or not o.name.startswith(("DoorArm_", "Showroom | arm")): continue oe = o.evaluated_get(dg) me = oe.to_mesh() if not me.uv_layers: out[o.name] = "no UV" oe.to_mesh_clear() continue n = len(me.loops) uv = np.empty(n * 2, np.float32) me.uv_layers[0].data.foreach_get("uv", uv) uv = uv.reshape(-1, 2) vi = np.empty(n, np.int64) me.loops.foreach_get("vertex_index", vi) co = np.empty(len(me.vertices) * 3, np.float32) me.vertices.foreach_get("co", co) p = co.reshape(-1, 3)[vi] res = {} for ax in (0, 1): keys = np.round(uv[:, ax], 4) ks = np.unique(keys) cents = np.array([p[keys == k].mean(0) for k in ks]) spread = float(np.mean([np.ptp(p[keys == k], 0).max() for k in ks])) res[ax] = (len(ks), spread, float(np.sum(np.linalg.norm(np.diff(cents, axis=0), axis=1)))) # the axis whose equal-value loops form small rings runs along the curve along = 0 if res[0][1] < res[1][1] else 1 out[o.name] = dict(along_axis="U" if along == 0 else "V", rings=res[along][0], ring_size=round(res[along][1], 4), length=round(res[along][2], 3), bevel=round(o.data.bevel_depth, 4)) oe.to_mesh_clear() return out def main(): argv = sys.argv[sys.argv.index("--") + 1:] live = json.load(open(argv[0])) import dra_rigdata as rd mine = {} for l in rd.LINKS: me = bpy.data.meshes.get(rd.mesh_name(l["name"])) if me is not None: mine[me.name] = mesh_sig(me) ship = {} for oname, d in live.items(): if d.get("t") == 'MESH' and d.get("me") and base(d["me"]) in mine: ship.setdefault(d["me"], dict(sig=d["sig"], objects=[]))["objects"].append(oname) rows, bad = [], [] for mname, s in ship.items(): ok = s["sig"] == mine[base(mname)] rows.append(dict(ship_mesh=mname, objects=s["objects"], ship=s["sig"], work=mine[base(mname)], match=ok)) if not ok: bad.append(mname) rep = dict(file=bpy.data.filepath, live=argv[0], arm_meshes=len(mine), in_ship=len(ship), not_in_ship=sorted(set(mine) - {base(m) for m in ship}), differ=bad, rows=rows, cables=cables()) json.dump(rep, open(argv[1], "w"), indent=1) print("[dra] sig: %d arm meshes, %d in the ship, %d differ %s" % (len(mine), len(ship), len(bad), bad), flush=True) main()