# Stage 32 (read-only): why did 4,502 triangles (3.66% of her surface) fail to rasterise into the # new atlas? They render as grey tears, so they are not the harmless sub-texel slivers I assumed. # # blender --background --python 32_skipprobe.py -- import bpy, sys import numpy as np BLEND = sys.argv[sys.argv.index("--") + 1] bpy.ops.wm.open_mainfile(filepath=BLEND) ob = max([o for o in bpy.data.objects if o.type == 'MESH'], key=lambda o: len(o.data.vertices)) me = ob.data n_v, n_l, n_f = len(me.vertices), len(me.loops), len(me.polygons) loops_v = np.empty(n_l, dtype=np.int32); me.loops.foreach_get("vertex_index", loops_v) co = np.empty(n_v * 3); me.vertices.foreach_get("co", co); co = co.reshape(-1, 3) ls = np.empty(n_f, dtype=np.int32); me.polygons.foreach_get("loop_start", ls) lt = np.empty(n_f, dtype=np.int32); me.polygons.foreach_get("loop_total", lt) li = ls[lt == 3] IDX = np.stack([li, li + 1, li + 2], axis=1) def uv_of(name): a = np.empty(n_l * 2) me.uv_layers[name].data.foreach_get("uv", a) return a.reshape(-1, 2) UN = uv_of("UVMap_atlas") UO = uv_of("UVMap_tripo") print(f"UVMap_atlas RAW range: u {UN[:,0].min():.4f}..{UN[:,0].max():.4f} " f"v {UN[:,1].min():.4f}..{UN[:,1].max():.4f}") print(f"UVMap_tripo RAW range: u {UO[:,0].min():.4f}..{UO[:,0].max():.4f} " f"v {UO[:,1].min():.4f}..{UO[:,1].max():.4f}") out01 = ((UN[:, 0] < -1e-6) | (UN[:, 0] > 1 + 1e-6) | (UN[:, 1] < -1e-6) | (UN[:, 1] > 1 + 1e-6)) print(f"loops with UVMap_atlas OUTSIDE [0,1]: {int(out01.sum())} of {n_l} " f"({100.0*out01.mean():.2f}%)") W = 4096 Pn_raw = UN[IDX] Pn_clip = np.clip(Pn_raw, 0, 1) def uvarea(P): return 0.5 * np.abs((P[:, 1, 0] - P[:, 0, 0]) * (P[:, 2, 1] - P[:, 0, 1]) - (P[:, 2, 0] - P[:, 0, 0]) * (P[:, 1, 1] - P[:, 0, 1])) ar_raw = uvarea(Pn_raw) * W * W # in texels ar_clip = uvarea(Pn_clip) * W * W P3 = co[loops_v[IDX]] a3 = 0.5 * np.linalg.norm(np.cross(P3[:, 1] - P3[:, 0], P3[:, 2] - P3[:, 0]), axis=1) UNITM = 1.777 / (co[:, 2].max() - co[:, 2].min()) cm2 = a3 * UNITM * UNITM * 1e4 tiny_raw = ar_raw < 0.5 killed_by_clip = (ar_raw >= 0.5) & (ar_clip < 0.5) print(f"\ntriangles with <0.5 texel of NEW uv area (before clipping): {int(tiny_raw.sum())} " f"holding {cm2[tiny_raw].sum():.1f} cm2") print(f"triangles that had area but LOSE it to [0,1] clipping: " f"{int(killed_by_clip.sum())} holding {cm2[killed_by_clip].sum():.1f} cm2") bad = tiny_raw | killed_by_clip print(f"\ntotal suspect: {int(bad.sum())} triangles, {cm2[bad].sum():.1f} cm2 " f"({100.0*cm2[bad].sum()/cm2.sum():.2f}% of surface)") if bad.any(): cen = P3[bad].mean(axis=1) z = (cen[:, 2] - co[:, 2].min()) / (co[:, 2].max() - co[:, 2].min()) print(" height distribution of the suspect faces (u = fraction of body height):") hist, ed = np.histogram(z, bins=10, range=(0, 1)) for c, l_, h_ in zip(hist, ed[:-1], ed[1:]): if c: print(f" u {l_:.1f}-{h_:.1f}: {c:5d}") print(f" their source-atlas uv area: median {np.median(uvarea(UO[IDX][bad])*W*W):.2f} texels " f"(all triangles: {np.median(uvarea(UO[IDX])*W*W):.2f})") # Are they degenerate in 3D as well, or real surface? print(f"\n3D area of suspect faces: median {np.median(cm2[bad]):.4f} cm2 vs " f"{np.median(cm2):.4f} cm2 for all faces") # At the reported uniform 2.2 px/mm a 0.11 cm2 face should get ~53 texels, not <0.5. So the # density is NOT uniform across all islands — only across the big ones that got reported. # Measure it per island, over every island. Q = 1 << 20 k = (loops_v.astype(np.int64) * Q * Q + np.round(np.clip(UN[:, 0], 0, 1) * (Q - 1)).astype(np.int64) * Q + np.round(np.clip(UN[:, 1], 0, 1) * (Q - 1)).astype(np.int64)) _, uvv = np.unique(k, return_inverse=True) n_uvv = uvv.max() + 1 T = np.stack([uvv[li], uvv[li + 1], uvv[li + 2]], axis=1) par = np.arange(n_uvv, dtype=np.int64) def find(x): r = x while par[r] != r: r = par[r] while par[x] != r: par[x], x = r, par[x] return r for a_, b_, c_ in T: ra, rb, rc = find(a_), find(b_), find(c_) if ra != rb: par[rb] = ra if ra != rc: par[rc] = ra _, isl = np.unique(np.array([find(i) for i in range(n_uvv)]), return_inverse=True) fisl = isl[T[:, 0]] n_isl = isl.max() + 1 iuv = np.bincount(fisl, weights=uvarea(Pn_clip), minlength=n_isl) i3d = np.bincount(fisl, weights=a3, minlength=n_isl) inf_ = np.bincount(fisl, minlength=n_isl) UN_M = UNITM dens = np.where(i3d > 0, np.sqrt(np.maximum(iuv, 0) / np.maximum(i3d, 1e-20)) * W / (UN_M * 1000.0), np.nan) print(f"\n=== PER-ISLAND TEXEL DENSITY, all {n_isl} islands (px/mm) ===") fd = dens[np.isfinite(dens)] for p in (1, 5, 25, 50, 75, 95, 99): print(f" p{p:<2d} {np.percentile(fd, p):.4f}") low = np.isfinite(dens) & (dens < 0.5) print(f" islands under 0.5 px/mm: {int(low.sum())} of {n_isl}, holding " f"{(i3d[low]*UN_M*UN_M*1e4).sum():.1f} cm2 " f"({100.0*i3d[low].sum()/i3d.sum():.2f}% of surface) in {int(inf_[low].sum())} faces") big = np.isfinite(dens) & (dens > 1.5) print(f" islands over 1.5 px/mm: {int(big.sum())} holding " f"{100.0*i3d[big].sum()/i3d.sum():.2f}% of surface") print("SKIPPROBE_DONE")