From d62f4a322725a0c7fd3ea7adbde2f62c64f0718e Mon Sep 17 00:00:00 2001 From: Rodrigo Bertoldo de Sousa Martins Date: Tue, 8 Sep 2026 14:32:32 -0300 Subject: [PATCH] fix6 --- data-pipeline/scripts/extract-manifest.mjs | 87 ++++++++++- data-pipeline/scripts/skeletal-whitelist.mjs | 153 +++++++++++++++++++ 2 files changed, 236 insertions(+), 4 deletions(-) create mode 100644 data-pipeline/scripts/skeletal-whitelist.mjs diff --git a/data-pipeline/scripts/extract-manifest.mjs b/data-pipeline/scripts/extract-manifest.mjs index e07b281..0ac867c 100644 --- a/data-pipeline/scripts/extract-manifest.mjs +++ b/data-pipeline/scripts/extract-manifest.mjs @@ -14,6 +14,7 @@ import { fileURLToPath } from "node:url"; import { SOURCES, SOURCE_TARGETS, RESPIRATORY_KEYWORDS } from "./sources.mjs"; import { translateToPt } from "./translate-pt.mjs"; import { SYSTEMS } from "../seed/systems.mjs"; +import { SKELETAL_BONES } from "./skeletal-whitelist.mjs"; await MeshoptSimplifier.ready; @@ -40,6 +41,48 @@ function slugify(s) { .replace(/^-+|-+$/g, ""); } +// O arquivo esquelético real do Z-Anatomy mistura ossos de verdade com +// centenas de "acidentes ósseos" (pontos de referência NO osso, ex: +// "Deltoid tuberosity" — não são ossos) e marcadores de origem/inserção +// muscular (ex: "Pectoralis major muscle.ol" — pertencem ao músculo). +// Por isso usamos uma lista de INCLUSÃO com os 206 ossos reais em vez de +// aceitar tudo: só o que bate exatamente vira uma estrutura esquelética. +const SKELETAL_ALIASES = { atlas: "Atlas (C1)", axis: "Axis (C2)" }; +const skeletalLookup = new Map( + Object.entries(SKELETAL_BONES).map(([key, info]) => [ + key.toLowerCase(), + { canonicalKey: key, ...info }, + ]), +); + +function stripSideSuffix(rawName) { + const m = rawName.match(/^(.*)\.([lrij])$/i); + if (m) return { base: m[1].trim(), suffix: m[2].toLowerCase() }; + return { base: rawName.trim(), suffix: null }; +} + +// Retorna null se o nome bruto não corresponder a nenhum dos 206 ossos — +// nesse caso a malha é ignorada (não vira estrutura, mas continua no +// arquivo .glb; como não há "structure" apontando pra ela, o frontend já +// a mantém oculta automaticamente). +function resolveSkeletalBone(rawName) { + const { base, suffix } = stripSideSuffix(rawName); + const normBase = base.toLowerCase(); + const aliasTarget = SKELETAL_ALIASES[normBase]; + const lookupKey = (aliasTarget ?? base).toLowerCase(); + const entry = skeletalLookup.get(lookupKey); + if (!entry) return null; + + if (entry.paired) { + // Ignora variantes ".i"/".j"/sem sufixo para ossos pareados — o + // Z-Anatomy parece manter cópias duplicadas (visão isolada vs. + // esqueleto montado); ".l"/".r" é a marcação inequívoca de lado. + if (suffix !== "l" && suffix !== "r") return null; + return { canonicalKey: entry.canonicalKey, pt: entry.pt, side: suffix }; + } + return { canonicalKey: entry.canonicalKey, pt: entry.pt, side: "single" }; +} + function classifySystem(key, rawName) { const target = SOURCE_TARGETS[key]; if (target.system) return target.system; @@ -81,6 +124,7 @@ for (const { fbx, key } of SOURCES) { } const nameCounts = new Map(); // dedupe de nomes brutos duplicados no MESMO arquivo + const includedBones = new Set(); // dedupe de osso+lado no sistema esquelético let countThisFile = 0; function visit(node) { @@ -92,12 +136,32 @@ for (const { fbx, key } of SOURCES) { const uniqueMeshName = n === 1 ? rawName : `${rawName} (${n})`; node.setName(uniqueMeshName); - const systemSlug = classifySystem(key, rawName); + let systemSlug, namePt, nameEn; + + if (key === "skeletal") { + const resolved = resolveSkeletalBone(rawName); + const dedupeKey = resolved ? `${resolved.canonicalKey}::${resolved.side}` : null; + if (!resolved || includedBones.has(dedupeKey)) { + for (const child of node.listChildren()) visit(child); + return; + } + includedBones.add(dedupeKey); + systemSlug = "esqueletico"; + const sideLabel = resolved.side === "l" ? " Esquerdo" : resolved.side === "r" ? " Direito" : ""; + const sideWord = resolved.side === "l" ? "Left " : resolved.side === "r" ? "Right " : ""; + namePt = `${resolved.pt}${sideLabel}`; + nameEn = `${sideWord}${resolved.canonicalKey}`; + } else { + systemSlug = classifySystem(key, rawName); + namePt = translateToPt(rawName) + (n > 1 ? ` (${n})` : ""); + nameEn = rawName + (n > 1 ? ` (${n})` : ""); + } + const material = materialFor(systemSlug); for (const prim of mesh.listPrimitives()) prim.setMaterial(material); const sys = systemBySlug.get(systemSlug); - const slugPart = slugify(uniqueMeshName) || `sem-nome-${totalStructures}`; + const slugPart = slugify(key === "skeletal" ? namePt : uniqueMeshName) || `sem-nome-${totalStructures}`; const slug = `${systemSlug}-${slugPart}`; // Gravamos o slug como "extras" do nó (→ vira object.userData no @@ -109,8 +173,8 @@ for (const { fbx, key } of SOURCES) { manifest.push({ system: systemSlug, slug, - namePt: translateToPt(rawName) + (n > 1 ? ` (${n})` : ""), - nameEn: rawName + (n > 1 ? ` (${n})` : ""), + namePt, + nameEn, synonyms: [], description: "", regionPath: [sys?.namePt ?? systemSlug], @@ -130,6 +194,21 @@ for (const { fbx, key } of SOURCES) { console.warn(` ⚠ nenhuma malha nomeada encontrada em ${fbx} — confira o log do assimp/conversão.`); } + if (key === "skeletal") { + const missing = []; + for (const [boneKey, info] of Object.entries(SKELETAL_BONES)) { + const sides = info.paired ? ["l", "r"] : ["single"]; + for (const side of sides) { + if (!includedBones.has(`${boneKey}::${side}`)) missing.push(`${boneKey} (${side})`); + } + } + console.log(` → ${includedBones.size}/206 ossos da lista de referência encontrados.`); + if (missing.length > 0) { + console.warn(` ⚠ ${missing.length} ossos da lista NÃO encontrados no arquivo:`); + for (const m of missing) console.warn(` - ${m}`); + } + } + // Malhas do Z-Anatomy vêm pensadas para um app desktop (Unity), com // densidade de polígonos bem maior do que um visualizador web aguenta // com dezenas de estruturas na tela ao mesmo tempo. MAS: muitas das diff --git a/data-pipeline/scripts/skeletal-whitelist.mjs b/data-pipeline/scripts/skeletal-whitelist.mjs new file mode 100644 index 0000000..009364f --- /dev/null +++ b/data-pipeline/scripts/skeletal-whitelist.mjs @@ -0,0 +1,153 @@ +// Lista de referência dos 206 ossos do corpo humano adulto, usada como +// FILTRO DE INCLUSÃO para o sistema esquelético: o arquivo real do +// Z-Anatomy mistura ossos de verdade com centenas de "acidentes ósseos" +// (ex: "Deltoid tuberosity", "Greater trochanter" — pontos de referência +// NO osso, não ossos em si) e marcadores de origem/inserção muscular +// (ex: "Pectoralis major muscle.ol") — só o que bate exatamente com um +// nome desta lista vira uma "estrutura" clicável no sistema esquelético; +// o resto é ignorado (mas a malha continua no arquivo, só não aparece). +// +// Chave = nome bruto (em inglês) como aparece no Z-Anatomy, JÁ SEM o +// sufixo de lateralidade/variante (.l/.r/.i/.j) — ver stripSuffix() em +// classify-skeletal.mjs. `paired: true` = existe versão esquerda/direita. +export const SKELETAL_BONES = { + // ---- Crânio: neurocrânio (8) ---- + "Frontal bone": { pt: "Osso Frontal", paired: false }, + "Parietal bone": { pt: "Osso Parietal", paired: true }, + "Temporal bone": { pt: "Osso Temporal", paired: true }, + "Occipital bone": { pt: "Osso Occipital", paired: false }, + "Sphenoid bone": { pt: "Osso Esfenoide", paired: false }, + "Ethmoid bone": { pt: "Osso Etmoide", paired: false }, + + // ---- Crânio: esqueleto facial (14) ---- + "Nasal bone": { pt: "Osso Nasal", paired: true }, + "Maxilla": { pt: "Maxila", paired: true }, + "Zygomatic bone": { pt: "Osso Zigomático", paired: true }, + "Lacrimal bone": { pt: "Osso Lacrimal", paired: true }, + "Palatine bone": { pt: "Osso Palatino", paired: true }, + "Inferior nasal concha bone": { pt: "Concha Nasal Inferior", paired: true }, + "Vomer": { pt: "Vômer", paired: false }, + "Mandible": { pt: "Mandíbula", paired: false }, + + // ---- Ossículos da audição (6) ---- + "Malleus": { pt: "Martelo", paired: true }, + "Incus": { pt: "Bigorna", paired: true }, + "Stapes": { pt: "Estribo", paired: true }, + + // ---- Osso hioide (1) ---- + "Hyoid bone": { pt: "Osso Hioide", paired: false }, + + // ---- Coluna vertebral (26) ---- + "Atlas (C1)": { pt: "Atlas (C1)", paired: false }, + "Axis (C2)": { pt: "Áxis (C2)", paired: false }, + "Vertebra C3": { pt: "Vértebra C3", paired: false }, + "Vertebra C4": { pt: "Vértebra C4", paired: false }, + "Vertebra C5": { pt: "Vértebra C5", paired: false }, + "Vertebra C6": { pt: "Vértebra C6", paired: false }, + "Vertebra C7": { pt: "Vértebra C7", paired: false }, + "Vertebra T1": { pt: "Vértebra T1", paired: false }, + "Vertebra T2": { pt: "Vértebra T2", paired: false }, + "Vertebra T3": { pt: "Vértebra T3", paired: false }, + "Vertebra T4": { pt: "Vértebra T4", paired: false }, + "Vertebra T5": { pt: "Vértebra T5", paired: false }, + "Vertebra T6": { pt: "Vértebra T6", paired: false }, + "Vertebra T7": { pt: "Vértebra T7", paired: false }, + "Vertebra T8": { pt: "Vértebra T8", paired: false }, + "Vertebra T9": { pt: "Vértebra T9", paired: false }, + "Vertebra T10": { pt: "Vértebra T10", paired: false }, + "Vertebra T11": { pt: "Vértebra T11", paired: false }, + "Vertebra T12": { pt: "Vértebra T12", paired: false }, + "Vertebra L1": { pt: "Vértebra L1", paired: false }, + "Vertebra L2": { pt: "Vértebra L2", paired: false }, + "Vertebra L3": { pt: "Vértebra L3", paired: false }, + "Vertebra L4": { pt: "Vértebra L4", paired: false }, + "Vertebra L5": { pt: "Vértebra L5", paired: false }, + "Sacrum": { pt: "Sacro", paired: false }, + "Coccyx": { pt: "Cóccix", paired: false }, + + // ---- Caixa torácica (25) ---- + "Sternum": { pt: "Esterno", paired: false }, + "First rib": { pt: "1ª Costela", paired: true }, + "Second rib": { pt: "2ª Costela", paired: true }, + "Third rib": { pt: "3ª Costela", paired: true }, + "Fourth rib": { pt: "4ª Costela", paired: true }, + "Fifth rib": { pt: "5ª Costela", paired: true }, + "Sixth rib": { pt: "6ª Costela", paired: true }, + "Seventh rib": { pt: "7ª Costela", paired: true }, + "Eighth rib": { pt: "8ª Costela", paired: true }, + "Ninth rib": { pt: "9ª Costela", paired: true }, + "Tenth rib": { pt: "10ª Costela", paired: true }, + "Eleventh rib": { pt: "11ª Costela", paired: true }, + "Twelfth rib": { pt: "12ª Costela", paired: true }, + + // ---- Cíngulo do membro superior (4) ---- + "Clavicle": { pt: "Clavícula", paired: true }, + "Scapula": { pt: "Escápula", paired: true }, + + // ---- Membro superior livre (60) ---- + "Humerus": { pt: "Úmero", paired: true }, + "Radius": { pt: "Rádio", paired: true }, + "Ulna": { pt: "Ulna", paired: true }, + "Scaphoid bone": { pt: "Osso Escafoide", paired: true }, + "Lunate bone": { pt: "Osso Semilunar", paired: true }, + "Triquetrum bone": { pt: "Osso Piramidal", paired: true }, + "Pisiform bone": { pt: "Osso Pisiforme", paired: true }, + "Trapezium bone": { pt: "Osso Trapézio", paired: true }, + "Trapezoid bone": { pt: "Osso Trapezoide", paired: true }, + "Capitate bone": { pt: "Osso Capitato", paired: true }, + "Hamate bone": { pt: "Osso Hamato", paired: true }, + "First metacarpal bone": { pt: "1º Osso Metacarpal", paired: true }, + "Second metacarpal bone": { pt: "2º Osso Metacarpal", paired: true }, + "Third metacarpal bone": { pt: "3º Osso Metacarpal", paired: true }, + "Fourth metacarpal bone": { pt: "4º Osso Metacarpal", paired: true }, + "Fifth metacarpal bone": { pt: "5º Osso Metacarpal", paired: true }, + "Proximal phalanx of first finger of hand": { pt: "Falange Proximal do Polegar", paired: true }, + "Distal phalanx of first finger of hand": { pt: "Falange Distal do Polegar", paired: true }, + "Proximal phalanx of second finger of hand": { pt: "Falange Proximal do 2º Dedo da Mão", paired: true }, + "Middle phalanx of second finger of hand": { pt: "Falange Média do 2º Dedo da Mão", paired: true }, + "Distal phalanx of second finger of hand": { pt: "Falange Distal do 2º Dedo da Mão", paired: true }, + "Proximal phalanx of third finger of hand": { pt: "Falange Proximal do 3º Dedo da Mão", paired: true }, + "Middle phalanx of third finger of hand": { pt: "Falange Média do 3º Dedo da Mão", paired: true }, + "Distal phalanx of third finger of hand": { pt: "Falange Distal do 3º Dedo da Mão", paired: true }, + "Proximal phalanx of fourth finger of hand": { pt: "Falange Proximal do 4º Dedo da Mão", paired: true }, + "Middle phalanx of fourth finger of hand": { pt: "Falange Média do 4º Dedo da Mão", paired: true }, + "Distal phalanx of fourth finger of hand": { pt: "Falange Distal do 4º Dedo da Mão", paired: true }, + "Proximal phalanx of fifth finger of hand": { pt: "Falange Proximal do 5º Dedo da Mão", paired: true }, + "Middle phalanx of fifth finger of hand": { pt: "Falange Média do 5º Dedo da Mão", paired: true }, + "Distal phalanx of fifth finger of hand": { pt: "Falange Distal do 5º Dedo da Mão", paired: true }, + + // ---- Cíngulo do membro inferior (2) ---- + "Hip bone": { pt: "Osso do Quadril", paired: true }, + + // ---- Membro inferior livre (60) ---- + "Femur": { pt: "Fêmur", paired: true }, + "Patella": { pt: "Patela", paired: true }, + "Tibia": { pt: "Tíbia", paired: true }, + "Fibula": { pt: "Fíbula", paired: true }, + "Talus": { pt: "Tálus", paired: true }, + "Calcaneus": { pt: "Calcâneo", paired: true }, + "Navicular bone": { pt: "Osso Navicular", paired: true }, + "Medial cuneiform bone": { pt: "Cuneiforme Medial", paired: true }, + "Intermediate cuneiform bone": { pt: "Cuneiforme Intermédio", paired: true }, + "Lateral cuneiform bone": { pt: "Cuneiforme Lateral", paired: true }, + "Cuboid bone": { pt: "Osso Cuboide", paired: true }, + "First metatarsal bone": { pt: "1º Osso Metatarsal", paired: true }, + "Second metatarsal bone": { pt: "2º Osso Metatarsal", paired: true }, + "Third metatarsal bone": { pt: "3º Osso Metatarsal", paired: true }, + "Fourth metatarsal bone": { pt: "4º Osso Metatarsal", paired: true }, + "Fifth metatarsal bone": { pt: "5º Osso Metatarsal", paired: true }, + "Proximal phalanx of first finger of foot": { pt: "Falange Proximal do Hálux", paired: true }, + "Distal phalanx of first finger of foot": { pt: "Falange Distal do Hálux", paired: true }, + "Proximal phalanx of second finger of foot": { pt: "Falange Proximal do 2º Dedo do Pé", paired: true }, + "Middle phalanx of second finger of foot": { pt: "Falange Média do 2º Dedo do Pé", paired: true }, + "Distal phalanx of second finger of foot": { pt: "Falange Distal do 2º Dedo do Pé", paired: true }, + "Proximal phalanx of third finger of foot": { pt: "Falange Proximal do 3º Dedo do Pé", paired: true }, + "Middle phalanx of third finger of foot": { pt: "Falange Média do 3º Dedo do Pé", paired: true }, + "Distal phalanx of third finger of foot": { pt: "Falange Distal do 3º Dedo do Pé", paired: true }, + "Proximal phalanx of fourth finger of foot": { pt: "Falange Proximal do 4º Dedo do Pé", paired: true }, + "Middle phalanx of fourth finger of foot": { pt: "Falange Média do 4º Dedo do Pé", paired: true }, + "Distal phalanx of fourth finger of foot": { pt: "Falange Distal do 4º Dedo do Pé", paired: true }, + "Proximal phalanx of fifth finger of foot": { pt: "Falange Proximal do 5º Dedo do Pé", paired: true }, + "Middle phalanx of fifth finger of foot": { pt: "Falange Média do 5º Dedo do Pé", paired: true }, + "Distal phalanx of fifth finger of foot": { pt: "Falange Distal do 5º Dedo do Pé", paired: true }, +};