SOURCE / PINNED RELEASE
Made of little things.
Body Journal
- Release
- e8049f8da4e2…
- Author-recorded commit
- 61c04bf0e8aa…
- License
- LICENSE
- Author’s source reference
- nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-6c4554844c7
Archive hash verified: 4369d778da046a8b…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
"""Package the existing adult mouth assets without changing any older topology.
Run: python3 scripts/prepare-mouth.py /path/to/isa_BP3D_4.0_obj_99.zip
Uses only Python's standard library. No scene, network, or Blender dependency.
"""
from pathlib import Path
import hashlib, json, re, struct, sys, zipfile
ROOT = Path(__file__).resolve().parents[1]
archive = Path(sys.argv[1])
raw = (ROOT/'assets/skin/body-skin.bin').read_bytes()
length = struct.unpack_from('<I', raw, 8)[0]
skin = json.loads(raw[12:12+length])
heads = struct.unpack_from('<159f', raw, 12+length+skin['arrays']['heads4']['offset'])
head = heads[44*3:45*3]
parts, catalog, sources, blob = [], [], {}, bytearray()
def pack(info, positions, indices):
assert len(positions) % 3 == len(indices) % 3 == 0
assert max(indices) < len(positions)//3 < 65536
while len(blob) % 4: blob.append(0)
info['vertices'], info['triangles'], info['positions'] = len(positions)//3, len(indices)//3, len(blob)
blob.extend(struct.pack(f'<{len(positions)}f', *positions))
info['indices'] = len(blob)
blob.extend(struct.pack(f'<{len(indices)}H', *indices))
parts.append(info)
catalog.append({k:v for k,v in info.items() if k not in ['positions','indices','vertices']})
with zipfile.ZipFile(archive) as z:
for n in [*range(1254,1282),2761]:
path = f'isa_BP3D_4.0_obj_99/FJ{n}.obj'
data = z.read(path); source = data.decode()
sources[Path(path).name] = hashlib.sha256(data).hexdigest()
name = re.search(r'# English name : (.+)',source)[1].strip()
fma = re.search(r'# Concept ID : (.+)',source)[1].strip()
positions, indices = [], []
for line in source.splitlines():
if line.startswith('v '):
x,y,height = map(float,line.split()[1:4])
positions.extend([x/1000-head[0],height/1000+.08-head[1],-y/1000-.075-head[2]])
elif line.startswith('f '):
face = [int(v.split('/')[0])-1 for v in line.split()[1:]]
for j in range(1,len(face)-1): indices.extend([face[0],face[j],face[j+1]])
info = {'id':fma, 'part':'head', 'jaw':'lower', 'kind':'tongue', 'label':'Tongue (illustrated surface)'}
if n != 2761:
lower, left = 'lower' in name, 'Left' in name
quadrant = (3 if left else 4) if lower else (2 if left else 1)
position = next(i for word,i in [('central',1),('lateral',2),('canine',3),('first secondary premolar',4),('second secondary premolar',5),('first secondary molar',6),('second secondary molar',7)] if word in name)
fdi = quadrant*10+position
plain = name.replace(' secondary','').replace(' tooth','').lower()
info.update(jaw='lower' if lower else 'upper',kind='tooth',fdi=fdi,label=f'{plain.capitalize()} · FDI {fdi}')
pack(info,positions,indices)
# Reuse the already fitted/decimated jaws. This leaves the skeleton model intact.
raw = (ROOT/'assets/skeleton/skeleton.bin').read_bytes()
length = struct.unpack_from('<I',raw,8)[0]; skeleton = json.loads(raw[12:12+length]); start = 12+length
for old in skeleton['parts']:
if old['id'] not in ['FMA52748','FMA53649','FMA53650']: continue
assert old['bone'] == 44 and old['tail'] == -1
positions = struct.unpack_from(f"<{old['vertices']*3}f",raw,start+old['positions'])
indices = struct.unpack_from(f"<{old['triangles']*3}H",raw,start+old['indices'])
if old['id'] == 'FMA52748':
jaw_points = list(zip(*[iter(positions)]*3))
pack({'id':old['id'],'part':'head','kind':'jaw','jaw':'lower' if old['id']=='FMA52748' else 'upper','label':old['label']+' (jaw reference)'},positions,indices)
# Midpoint of the posterior condylar region in this source atlas. A simple hinge
# exposes the teeth; it does not simulate TMJ translation or soft-tissue motion.
condyles = [p for p in jaw_points if abs(p[0]+head[0]) > .04 and p[1]+head[1] > 1.57 and p[2]+head[2] < .03]
centers = [[sum(p[i] for p in condyles if p[0]*sign > 0)/sum(p[0]*sign > 0 for p in condyles) for i in range(3)] for sign in [-1,1]]
hinge = [(a+b)/2 for a,b in zip(*centers)]
meta = json.dumps({'id':'bp3d-mouth-1','head':head,'hinge':hinge,'parts':parts},separators=(',',':')).encode()
meta += b' '*((-len(meta))%4)
result = b'BJMOUTH1'+struct.pack('<I',len(meta))+meta+blob
out = ROOT/'assets/mouth';out.mkdir(exist_ok=True)
(out/'mouth.bin').write_bytes(result)
(ROOT/'src/mouth-catalog.ts').write_text('// Generated by scripts/prepare-mouth.py. Stable FMA IDs; FDI permanent dentition.\nexport const mouthCatalog = '+json.dumps(catalog,indent=2)+' as const;\n')
provenance = {'id':'bp3d-mouth-1','source':'BodyParts3D 4.0 ISA tree, 99% archive; jaw geometry from bundled skeleton',
'url':'https://dbarchive.biosciencedbc.jp/data/bodyparts3d/LATEST/isa_BP3D_4.0_obj_99.zip',
'attribution':'BodyParts3D, © The Database Center for Life Science licensed under CC Attribution 4.0 International',
'license':'CC-BY-4.0','licenseSource':'https://dbarchive.biosciencedbc.jp/en/bodyparts3d/lic.html','licenseUpdated':'2025-02-27',
'numberingSource':'https://ndhcb.uplifterinc.com/pages/ndhce/exam-prep/',
'modifications':'Coordinate conversion, head-relative attachment, FDI labels, binary packaging. Jaw reference meshes already reduced. Illustrative hinge motion added at runtime.',
'limitations':'28 permanent teeth; no wisdom teeth, gums, palate or child dentition. Tongue follows the lower jaw rigidly; not a physiological tongue or TMJ simulation.',
'archiveSha256':hashlib.sha256(archive.read_bytes()).hexdigest(),'skeletonSha256':hashlib.sha256(raw).hexdigest(),
'sourceFiles':sources,'structures':len(parts),'triangles':sum(p['triangles'] for p in parts),'bytes':len(result),'sha256':hashlib.sha256(result).hexdigest()}
(out/'provenance.json').write_text(json.dumps(provenance,indent=2)+'\n')
print(json.dumps({k:provenance[k] for k in ['structures','triangles','bytes','sha256']}))
