Research: Dexter Quiet reference cage (operator's hand cage, evaluated limit mesh, fidelity to the scan)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-12 17:39:46 +02:00
commit d426f865ad
5 changed files with 6405 additions and 0 deletions

View file

@ -0,0 +1,21 @@
# Dexter Quiet: the operator's reference cage (2026-10-12)
The reference brush-tree target r2 is measured against (ruling brush-tree:ruling:pursue-sdf-composition-remeshing:
the operator rebuilds Dexter Quiet by hand as the reference cage).
Source: `Asset Sources/Ships.blend` in Aetheria, object `Dexter Quiet Cage` (collection Quiet), saved 2026-10-12 17:31.
Exported from a copy by `export.py` (Blender, background mode). Files:
- `cage_raw.obj`: the cage as authored, half a hull: 29 vertices, 25 faces, 9 edges creased at 1.0 (`cage_info.txt`).
- `cage_eval.obj`: the evaluated stack: Mirror (merge on) -> Subsurf level 3 (render 2, limit surface on) ->
Bevel (0.001, 1 segment, weight-limited) -> Weighted Normal. 3,154 vertices, 3,152 faces.
- The scanned original `Dexter Quiet` (4,832 vertices, 5,600 faces) is not copied here; it stays in Ships.blend.
Fidelity of the hand cage to the scan (vertex-to-nearest-vertex, an upper bound on surface distance; hull diagonal 1.492):
cage->scan mean 0.88% p95 2.04% max 3.11% of the diagonal; scan->cage mean 1.29% p95 4.12% max 6.03%.
That spread is the artistic liberty a human cage takes, and is the fidelity band an automated remesh is measured against.
Operator, 2026-10-12: "I'm afraid I had to take a bit of artistic liberty to get the cage to behave, mostly because
of how creases work in Blender (generates horrible geometry if the crease path isn't closed)". Body fact for the
remesher: crease chains in the cage are closed loops; an open crease path ending inside a face region produces
bad subdivision geometry in Blender's Catmull-Clark (OpenSubdiv) evaluation, so a generated cage must close its creases.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
[('cage_raw', (29, 25)), ('cage_limit_eval', (3154, 3152)), ('dexter_ref', (4832, 5600))]
creases=[(1, 3, 1.0), (5, 1, 1.0), (17, 11, 1.0), (3, 17, 1.0), (13, 16, 1.0), (24, 22, 1.0), (25, 16, 1.0), (13, 11, 1.0), (24, 25, 1.0)]
mods=[('MIRROR', {'use_clip': False, 'use_mirror_merge': True}), ('SUBSURF', {'levels': 3, 'render_levels': 2, 'use_limit_surface': True}), ('BEVEL', {'width': 0.0010000001639127731, 'segments': 1, 'limit_method': 'WEIGHT'}), ('WEIGHTED_NORMAL', {})]
cage_dims=(0.9702155590057373, 1.0190552473068237, 0.3775596618652344) ref_dims=(0.9811705946922302, 1.0477101802825928, 0.407174676656723)

View file

@ -0,0 +1,54 @@
v -0.050183 -0.515158 0.120381
v -0.050183 -0.502594 0.165733
v -0.066373 -0.290093 0.034410
v -0.066373 -0.279099 0.154038
v -0.000000 -0.521785 0.077995
v -0.000000 -0.495967 0.196439
v 0.000000 -0.257172 0.011836
v 0.000000 -0.079878 0.194280
v -0.085456 0.296420 0.036890
v 0.000000 0.264006 -0.120438
v -0.000000 0.324338 0.095861
v -0.532850 -0.332750 0.201392
v -0.000000 0.083429 0.170193
v -0.445757 -0.036017 0.098265
v -0.069995 0.072662 0.125021
v -0.305911 -0.163824 0.119325
v -0.219219 0.256351 -0.032856
v -0.271429 -0.083411 0.118972
v -0.071629 0.154301 -0.071808
v 0.000000 0.096312 -0.125795
v -0.230102 0.060001 0.075233
v -0.231539 0.092497 -0.020192
v 0.002669 0.513952 -0.201831
v 0.002669 0.519207 -0.172940
v -0.010721 0.515629 -0.180240
v -0.106624 0.345361 -0.096021
v -0.041564 0.406965 -0.070340
v 0.001707 0.450794 -0.082077
v 0.001374 0.391670 -0.173090
f 1 2 4 3
f 15 13 11 9
f 5 6 2 1
f 3 7 5 1
f 8 4 2 6
f 22 16 12 14
f 20 19 10
f 10 19 22 17
f 16 18 12
f 21 17 14
f 15 9 17 21
f 3 4 18 16
f 19 3 16 22
f 4 15 21 18
f 18 21 14 12
f 7 3 19 20
f 17 22 14
f 4 8 13 15
f 24 23 25
f 26 27 25
f 9 11 28 27
f 29 26 25 23
f 10 17 26 29
f 17 9 27 26
f 27 28 24 25

View file

@ -0,0 +1,20 @@
import bpy, sys, bmesh
d=sys.argv[-1]
dg=bpy.context.evaluated_depsgraph_get()
cage=bpy.data.objects['Dexter Quiet Cage']; ref=bpy.data.objects['Dexter Quiet']
def dump(o, ev, path):
src=o.evaluated_get(dg) if ev else o
me=src.to_mesh()
with open(path,'w') as f:
for v in me.vertices:
p=o.matrix_world @ v.co; f.write(f"v {p.x:.6f} {p.y:.6f} {p.z:.6f}\n")
for poly in me.polygons: f.write("f "+" ".join(str(i+1) for i in poly.vertices)+"\n")
n=(len(me.vertices),len(me.polygons)); src.to_mesh_clear(); return n
info=[]
info.append(("cage_raw",dump(cage,False,d+"/cage_raw.obj")))
info.append(("cage_limit_eval",dump(cage,True,d+"/cage_eval.obj")))
info.append(("dexter_ref",dump(ref,False,d+"/dexter_ref.obj")))
me=cage.data; a=me.attributes.get('crease_edge')
cr=[(e.vertices[0],e.vertices[1],round(a.data[e.index].value,3)) for e in me.edges if a and a.data[e.index].value>0]
mods=[(m.type,{k:getattr(m,k) for k in ('levels','render_levels','use_limit_surface','width','segments','limit_method','use_clip','use_mirror_merge') if hasattr(m,k)}) for m in cage.modifiers]
open(d+"/cage_info.txt","w").write(f"{info}\ncreases={cr}\nmods={mods}\ncage_dims={tuple(cage.dimensions)} ref_dims={tuple(ref.dimensions)}\n")