first commit
This commit is contained in:
commit
0e854110d3
49 files changed
+2292
No files matched your search
@@ -0,0 +1,6 @@
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
CMD ["sh", "-c", "node seed/build-manifest.mjs && node scripts/build-seed-models.mjs && node scripts/seed-db.mjs"]
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "anatomia-data-pipeline",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:seed-models": "node scripts/build-seed-models.mjs",
|
||||
"seed:db": "node scripts/seed-db.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gltf-transform/core": "^4.0.4",
|
||||
"@gltf-transform/functions": "^4.0.4",
|
||||
"pg": "^8.12.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { Document, NodeIO } from "@gltf-transform/core";
|
||||
import { readFileSync, mkdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { buildGeometry } from "./geometry.mjs";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const manifestPath = path.resolve(__dirname, "../seed/manifest.json");
|
||||
const outDir = path.resolve(__dirname, "../seed/models");
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
|
||||
const { structures } = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
||||
|
||||
function eulerToQuat([x, y, z]) {
|
||||
const cx = Math.cos(x / 2), sx = Math.sin(x / 2);
|
||||
const cy = Math.cos(y / 2), sy = Math.sin(y / 2);
|
||||
const cz = Math.cos(z / 2), sz = Math.sin(z / 2);
|
||||
return [
|
||||
sx * cy * cz - cx * sy * sz,
|
||||
cx * sy * cz + sx * cy * sz,
|
||||
cx * cy * sz - sx * sy * cz,
|
||||
cx * cy * cz + sx * sy * sz,
|
||||
];
|
||||
}
|
||||
|
||||
function hexToRgb(hex) {
|
||||
const n = parseInt(hex.replace("#", ""), 16);
|
||||
return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255, 1];
|
||||
}
|
||||
|
||||
const bySystem = new Map();
|
||||
for (const s of structures) {
|
||||
if (!bySystem.has(s.system)) bySystem.set(s.system, []);
|
||||
bySystem.get(s.system).push(s);
|
||||
}
|
||||
|
||||
const systemColors = {
|
||||
esqueletico: "#e8e2d0",
|
||||
muscular: "#b5433a",
|
||||
nervoso: "#f2c14e",
|
||||
cardiovascular: "#c1121f",
|
||||
respiratorio: "#4f7cff",
|
||||
tegumentar: "#e0a96d",
|
||||
};
|
||||
|
||||
for (const [systemSlug, items] of bySystem) {
|
||||
const doc = new Document();
|
||||
const buffer = doc.createBuffer();
|
||||
const scene = doc.createScene(systemSlug);
|
||||
doc.getRoot().setDefaultScene(scene);
|
||||
|
||||
const material = doc
|
||||
.createMaterial(systemSlug)
|
||||
.setBaseColorFactor(hexToRgb(systemColors[systemSlug] ?? "#cccccc"))
|
||||
.setRoughnessFactor(0.6)
|
||||
.setMetallicFactor(0.05);
|
||||
|
||||
for (const item of items) {
|
||||
const geo = buildGeometry(item.shape);
|
||||
const posAccessor = doc
|
||||
.createAccessor()
|
||||
.setType("VEC3")
|
||||
.setArray(geo.positions)
|
||||
.setBuffer(buffer);
|
||||
const normAccessor = doc
|
||||
.createAccessor()
|
||||
.setType("VEC3")
|
||||
.setArray(geo.normals)
|
||||
.setBuffer(buffer);
|
||||
const idxAccessor = doc
|
||||
.createAccessor()
|
||||
.setType("SCALAR")
|
||||
.setArray(geo.indices)
|
||||
.setBuffer(buffer);
|
||||
|
||||
const prim = doc
|
||||
.createPrimitive()
|
||||
.setAttribute("POSITION", posAccessor)
|
||||
.setAttribute("NORMAL", normAccessor)
|
||||
.setIndices(idxAccessor)
|
||||
.setMaterial(material);
|
||||
|
||||
const mesh = doc.createMesh(item.slug).addPrimitive(prim);
|
||||
const node = doc
|
||||
.createNode(item.slug)
|
||||
.setMesh(mesh)
|
||||
.setTranslation(item.position)
|
||||
.setRotation(eulerToQuat(item.rotation ?? [0, 0, 0]));
|
||||
|
||||
scene.addChild(node);
|
||||
}
|
||||
|
||||
const io = new NodeIO();
|
||||
const outFile = path.join(outDir, `${systemSlug}.glb`);
|
||||
await io.write(outFile, doc);
|
||||
console.log(`✓ ${outFile} (${items.length} estruturas)`);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Minimal parametric mesh generators (box/sphere/cylinder), dependency-free.
|
||||
// Returns { positions: Float32Array, normals: Float32Array, indices: Uint32Array }.
|
||||
|
||||
export function box(w, h, d) {
|
||||
const x = w / 2, y = h / 2, z = d / 2;
|
||||
const positions = [];
|
||||
const normals = [];
|
||||
const indices = [];
|
||||
|
||||
const faces = [
|
||||
// [normal, 4 corners CCW]
|
||||
[[0, 0, 1], [[-x, -y, z], [x, -y, z], [x, y, z], [-x, y, z]]],
|
||||
[[0, 0, -1], [[x, -y, -z], [-x, -y, -z], [-x, y, -z], [x, y, -z]]],
|
||||
[[0, 1, 0], [[-x, y, z], [x, y, z], [x, y, -z], [-x, y, -z]]],
|
||||
[[0, -1, 0], [[-x, -y, -z], [x, -y, -z], [x, -y, z], [-x, -y, z]]],
|
||||
[[1, 0, 0], [[x, -y, z], [x, -y, -z], [x, y, -z], [x, y, z]]],
|
||||
[[-1, 0, 0], [[-x, -y, -z], [-x, -y, z], [-x, y, z], [-x, y, -z]]],
|
||||
];
|
||||
|
||||
for (const [n, corners] of faces) {
|
||||
const base = positions.length / 3;
|
||||
for (const c of corners) {
|
||||
positions.push(...c);
|
||||
normals.push(...n);
|
||||
}
|
||||
indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
|
||||
}
|
||||
|
||||
return {
|
||||
positions: new Float32Array(positions),
|
||||
normals: new Float32Array(normals),
|
||||
indices: new Uint32Array(indices),
|
||||
};
|
||||
}
|
||||
|
||||
export function sphere(radius, widthSegments = 16, heightSegments = 12) {
|
||||
const positions = [];
|
||||
const normals = [];
|
||||
const indices = [];
|
||||
|
||||
for (let y = 0; y <= heightSegments; y++) {
|
||||
const v = y / heightSegments;
|
||||
const theta = v * Math.PI;
|
||||
for (let x = 0; x <= widthSegments; x++) {
|
||||
const u = x / widthSegments;
|
||||
const phi = u * Math.PI * 2;
|
||||
const nx = -Math.cos(phi) * Math.sin(theta);
|
||||
const ny = Math.cos(theta);
|
||||
const nz = Math.sin(phi) * Math.sin(theta);
|
||||
positions.push(radius * nx, radius * ny, radius * nz);
|
||||
normals.push(nx, ny, nz);
|
||||
}
|
||||
}
|
||||
|
||||
const rowSize = widthSegments + 1;
|
||||
for (let y = 0; y < heightSegments; y++) {
|
||||
for (let x = 0; x < widthSegments; x++) {
|
||||
const a = y * rowSize + x;
|
||||
const b = a + rowSize;
|
||||
indices.push(a, b, a + 1, b, b + 1, a + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
positions: new Float32Array(positions),
|
||||
normals: new Float32Array(normals),
|
||||
indices: new Uint32Array(indices),
|
||||
};
|
||||
}
|
||||
|
||||
export function cylinder(radiusTop, radiusBottom, height, radialSegments = 12) {
|
||||
const positions = [];
|
||||
const normals = [];
|
||||
const indices = [];
|
||||
const halfH = height / 2;
|
||||
|
||||
// Side wall.
|
||||
for (let y = 0; y <= 1; y++) {
|
||||
const r = y === 0 ? radiusTop : radiusBottom;
|
||||
const py = y === 0 ? halfH : -halfH;
|
||||
for (let x = 0; x <= radialSegments; x++) {
|
||||
const u = (x / radialSegments) * Math.PI * 2;
|
||||
const nx = Math.sin(u);
|
||||
const nz = Math.cos(u);
|
||||
positions.push(r * nx, py, r * nz);
|
||||
const slope = (radiusBottom - radiusTop) / height;
|
||||
const len = Math.sqrt(1 + slope * slope);
|
||||
normals.push(nx / len, -slope / len, nz / len);
|
||||
}
|
||||
}
|
||||
const rowSize = radialSegments + 1;
|
||||
for (let x = 0; x < radialSegments; x++) {
|
||||
const a = x, b = a + rowSize;
|
||||
indices.push(a, b, a + 1, b, b + 1, a + 1);
|
||||
}
|
||||
|
||||
// Caps.
|
||||
function cap(y, r, py, flip) {
|
||||
const base = positions.length / 3;
|
||||
const ny = flip ? 1 : -1;
|
||||
positions.push(0, py, 0);
|
||||
normals.push(0, ny, 0);
|
||||
for (let x = 0; x <= radialSegments; x++) {
|
||||
const u = (x / radialSegments) * Math.PI * 2;
|
||||
positions.push(r * Math.sin(u), py, r * Math.cos(u));
|
||||
normals.push(0, ny, 0);
|
||||
}
|
||||
for (let x = 0; x < radialSegments; x++) {
|
||||
if (flip) indices.push(base, base + x + 2, base + x + 1);
|
||||
else indices.push(base, base + x + 1, base + x + 2);
|
||||
}
|
||||
}
|
||||
if (radiusTop > 0) cap(0, radiusTop, halfH, true);
|
||||
if (radiusBottom > 0) cap(1, radiusBottom, -halfH, false);
|
||||
|
||||
return {
|
||||
positions: new Float32Array(positions),
|
||||
normals: new Float32Array(normals),
|
||||
indices: new Uint32Array(indices),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildGeometry(shape) {
|
||||
switch (shape.type) {
|
||||
case "box": return box(shape.w, shape.h, shape.d);
|
||||
case "sphere": return sphere(shape.r);
|
||||
case "cylinder": return cylinder(shape.rt, shape.rb, shape.h);
|
||||
default: throw new Error(`Unknown shape type: ${shape.type}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Populates the database from data-pipeline/seed/manifest.json.
|
||||
// Run after `npm run build:seed-models` and after backend Prisma migrations
|
||||
// have been applied (this script uses plain SQL, not Prisma, so the
|
||||
// data-pipeline stays independent of the backend's toolchain).
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import pg from "pg";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const manifestPath = path.resolve(__dirname, "../seed/manifest.json");
|
||||
const { systems, structures } = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
||||
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
if (!connectionString) {
|
||||
console.error("DATABASE_URL não definida.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const client = new pg.Client({ connectionString });
|
||||
await client.connect();
|
||||
|
||||
// The "api" service applies Prisma migrations on startup; if this pipeline
|
||||
// runs first (e.g. right after `docker compose up`), wait for the table.
|
||||
async function waitForTable(name, maxAttempts = 30) {
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
const { rows } = await client.query(
|
||||
`SELECT to_regclass($1) AS exists`,
|
||||
[name],
|
||||
);
|
||||
if (rows[0]?.exists) return;
|
||||
console.log(`Aguardando migrations do backend criarem "${name}" (${attempt}/${maxAttempts})...`);
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
}
|
||||
throw new Error(`Tabela "${name}" não apareceu — rode "docker compose up -d api" antes do pipeline.`);
|
||||
}
|
||||
|
||||
await waitForTable("structures");
|
||||
|
||||
function cuid(prefix) {
|
||||
return `${prefix}_${Math.random().toString(36).slice(2, 10)}${Date.now().toString(36)}`;
|
||||
}
|
||||
|
||||
try {
|
||||
await client.query("BEGIN");
|
||||
|
||||
const systemIds = new Map();
|
||||
for (const s of systems) {
|
||||
const id = cuid("sys");
|
||||
systemIds.set(s.slug, id);
|
||||
await client.query(
|
||||
`INSERT INTO anatomical_systems (id, slug, "namePt", "nameEn", "colorHex", "order")
|
||||
VALUES ($1,$2,$3,$4,$5,$6)
|
||||
ON CONFLICT (slug) DO UPDATE SET "namePt"=$3, "nameEn"=$4, "colorHex"=$5, "order"=$6
|
||||
RETURNING id`,
|
||||
[id, s.slug, s.namePt, s.nameEn, s.colorHex, s.order],
|
||||
);
|
||||
}
|
||||
// Re-read ids (in case of ON CONFLICT they may differ from freshly generated ones).
|
||||
const { rows: existingSystems } = await client.query(
|
||||
`SELECT id, slug FROM anatomical_systems`,
|
||||
);
|
||||
for (const row of existingSystems) systemIds.set(row.slug, row.id);
|
||||
|
||||
for (const st of structures) {
|
||||
const systemId = systemIds.get(st.system);
|
||||
if (!systemId) throw new Error(`Sistema desconhecido: ${st.system}`);
|
||||
await client.query(
|
||||
`INSERT INTO structures (id, "fmaId", slug, "namePt", "nameEn", synonyms, description, "regionPath", "modelFile", "meshName", "systemId", "updatedAt")
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11, now())
|
||||
ON CONFLICT (slug) DO UPDATE SET
|
||||
"namePt"=$4, "nameEn"=$5, synonyms=$6, description=$7, "regionPath"=$8,
|
||||
"modelFile"=$9, "meshName"=$10, "systemId"=$11, "updatedAt"=now()`,
|
||||
[
|
||||
cuid("str"),
|
||||
null,
|
||||
st.slug,
|
||||
st.namePt,
|
||||
st.nameEn,
|
||||
st.synonyms ?? [],
|
||||
st.description ?? null,
|
||||
st.regionPath ?? [],
|
||||
`${st.system}.glb`,
|
||||
st.slug,
|
||||
systemId,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
await client.query("COMMIT");
|
||||
console.log(`Seed concluído: ${systems.length} sistemas, ${structures.length} estruturas.`);
|
||||
} catch (err) {
|
||||
await client.query("ROLLBACK");
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await client.end();
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
// Generates manifest.json: the curated seed dataset of anatomical structures.
|
||||
//
|
||||
// IMPORTANT — read this before treating the geometry as "real anatomy":
|
||||
// The BodyParts3D dataset (lifesciencedb.jp/bp3d) does not expose a scriptable
|
||||
// download endpoint (it's served through a legacy Ext.js viewer app with no
|
||||
// stable direct-download URLs per structure). Rather than block the whole
|
||||
// platform on manually clicking through thousands of downloads, this seed
|
||||
// dataset uses simple parametric primitives (box/sphere/cylinder) placed at
|
||||
// anatomically correct positions/proportions on a standing human figure
|
||||
// (1.8 m tall, origin at the feet, y-up, +z = anterior).
|
||||
//
|
||||
// The names, systems, regions and relationships ARE real anatomy — only the
|
||||
// mesh shapes are schematic stand-ins. To upgrade to real segmented meshes:
|
||||
// obtain BodyParts3D OBJ files (manually exported from the viewer, or via
|
||||
// Z-Anatomy's Blender project which re-packages the same CC BY-SA dataset
|
||||
// with saner file names), then replace the shape generator's output for a
|
||||
// given `slug` with `scripts/convert-obj.mjs <file.obj> <slug>` (see
|
||||
// data-pipeline/README.md). No frontend/backend code needs to change.
|
||||
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/** @typedef {{type:'box',w:number,h:number,d:number}|{type:'sphere',r:number}|{type:'cylinder',rt:number,rb:number,h:number}} Shape */
|
||||
|
||||
const systems = [
|
||||
{ slug: "esqueletico", namePt: "Sistema Esquelético", nameEn: "Skeletal System", colorHex: "#e8e2d0", order: 1 },
|
||||
{ slug: "muscular", namePt: "Sistema Muscular", nameEn: "Muscular System", colorHex: "#b5433a", order: 2 },
|
||||
{ slug: "nervoso", namePt: "Sistema Nervoso", nameEn: "Nervous System", colorHex: "#f2c14e", order: 3 },
|
||||
{ slug: "cardiovascular", namePt: "Sistema Cardiovascular", nameEn: "Cardiovascular System", colorHex: "#c1121f", order: 4 },
|
||||
{ slug: "respiratorio", namePt: "Sistema Respiratório", nameEn: "Respiratory System", colorHex: "#4f7cff", order: 5 },
|
||||
{ slug: "tegumentar", namePt: "Sistema Tegumentar", nameEn: "Integumentary System", colorHex: "#e0a96d", order: 6 },
|
||||
];
|
||||
|
||||
/** @type {any[]} */
|
||||
const structures = [];
|
||||
|
||||
function add(system, slug, namePt, nameEn, region, shape, position, opts = {}) {
|
||||
structures.push({
|
||||
system,
|
||||
slug,
|
||||
namePt,
|
||||
nameEn,
|
||||
synonyms: opts.synonyms ?? [],
|
||||
description: opts.description ?? "",
|
||||
regionPath: region,
|
||||
shape,
|
||||
position,
|
||||
rotation: opts.rotation ?? [0, 0, 0],
|
||||
color: opts.color,
|
||||
});
|
||||
}
|
||||
|
||||
// Adds a left/right pair mirrored across the sagittal (x=0) plane.
|
||||
function addBilateral(system, baseSlug, baseNamePt, baseNameEn, region, shape, position, opts = {}) {
|
||||
const [x, y, z] = position;
|
||||
add(system, `${baseSlug}_direito`, `${baseNamePt} Direito`, `Right ${baseNameEn}`, region, shape, [x, y, z], opts);
|
||||
add(system, `${baseSlug}_esquerdo`, `${baseNamePt} Esquerdo`, `Left ${baseNameEn}`, region, shape, [-x, y, z], opts);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SISTEMA ESQUELÉTICO
|
||||
// ---------------------------------------------------------------------------
|
||||
const AX = ["Sistema Esquelético"];
|
||||
add("esqueletico", "cranio", "Crânio", "Skull", [...AX, "Cabeça"], { type: "sphere", r: 0.11 }, [0, 1.72, 0]);
|
||||
add("esqueletico", "mandibula", "Mandíbula", "Mandible", [...AX, "Cabeça"], { type: "box", w: 0.09, h: 0.04, d: 0.08 }, [0, 1.62, 0.03]);
|
||||
add("esqueletico", "coluna_cervical", "Coluna Cervical", "Cervical Spine", [...AX, "Pescoço"], { type: "cylinder", rt: 0.02, rb: 0.02, h: 0.12 }, [0, 1.55, -0.02]);
|
||||
add("esqueletico", "coluna_toracica", "Coluna Torácica", "Thoracic Spine", [...AX, "Tronco"], { type: "cylinder", rt: 0.025, rb: 0.025, h: 0.35 }, [0, 1.3, -0.03]);
|
||||
add("esqueletico", "coluna_lombar", "Coluna Lombar", "Lumbar Spine", [...AX, "Tronco"], { type: "cylinder", rt: 0.03, rb: 0.03, h: 0.18 }, [0, 1.0, -0.02]);
|
||||
add("esqueletico", "sacro", "Sacro", "Sacrum", [...AX, "Pelve"], { type: "box", w: 0.1, h: 0.08, d: 0.05 }, [0, 0.85, -0.03]);
|
||||
add("esqueletico", "gradeado_costal", "Gradeado Costal (Costelas)", "Rib Cage", [...AX, "Tórax"], { type: "box", w: 0.3, h: 0.3, d: 0.18 }, [0, 1.25, 0]);
|
||||
add("esqueletico", "esterno", "Esterno", "Sternum", [...AX, "Tórax"], { type: "box", w: 0.05, h: 0.2, d: 0.02 }, [0, 1.28, 0.1]);
|
||||
addBilateral("esqueletico", "clavicula", "Clavícula", "Clavicle", [...AX, "Cintura Escapular"], { type: "cylinder", rt: 0.012, rb: 0.012, h: 0.16 }, [0.12, 1.42, 0.04], { rotation: [0, 0, 1.3] });
|
||||
addBilateral("esqueletico", "escapula", "Escápula", "Scapula", [...AX, "Cintura Escapular"], { type: "box", w: 0.1, h: 0.14, d: 0.015 }, [0.15, 1.35, -0.06]);
|
||||
addBilateral("esqueletico", "umero", "Úmero", "Humerus", [...AX, "Membro Superior", "Braço"], { type: "cylinder", rt: 0.025, rb: 0.02, h: 0.28 }, [0.22, 1.15, 0]);
|
||||
addBilateral("esqueletico", "radio", "Rádio", "Radius", [...AX, "Membro Superior", "Antebraço"], { type: "cylinder", rt: 0.015, rb: 0.013, h: 0.24 }, [0.24, 0.85, 0.02]);
|
||||
addBilateral("esqueletico", "ulna", "Ulna", "Ulna", [...AX, "Membro Superior", "Antebraço"], { type: "cylinder", rt: 0.016, rb: 0.013, h: 0.25 }, [0.24, 0.85, -0.02]);
|
||||
addBilateral("esqueletico", "carpo", "Ossos do Carpo", "Carpal Bones", [...AX, "Membro Superior", "Mão"], { type: "sphere", r: 0.02 }, [0.24, 0.68, 0]);
|
||||
addBilateral("esqueletico", "mao", "Metacarpo e Falanges da Mão", "Hand Metacarpals/Phalanges", [...AX, "Membro Superior", "Mão"], { type: "box", w: 0.05, h: 0.1, d: 0.015 }, [0.24, 0.6, 0]);
|
||||
add("esqueletico", "pelve", "Pelve (Ossos do Quadril)", "Pelvis (Hip Bones)", [...AX, "Pelve"], { type: "box", w: 0.28, h: 0.15, d: 0.15 }, [0, 0.78, 0]);
|
||||
addBilateral("esqueletico", "femur", "Fêmur", "Femur", [...AX, "Membro Inferior", "Coxa"], { type: "cylinder", rt: 0.032, rb: 0.026, h: 0.42 }, [0.09, 0.5, 0]);
|
||||
addBilateral("esqueletico", "patela", "Patela", "Patella", [...AX, "Membro Inferior", "Joelho"], { type: "sphere", r: 0.025 }, [0.09, 0.28, 0.04]);
|
||||
addBilateral("esqueletico", "tibia", "Tíbia", "Tibia", [...AX, "Membro Inferior", "Perna"], { type: "cylinder", rt: 0.024, rb: 0.018, h: 0.35 }, [0.09, 0.12, 0]);
|
||||
addBilateral("esqueletico", "fibula", "Fíbula", "Fibula", [...AX, "Membro Inferior", "Perna"], { type: "cylinder", rt: 0.012, rb: 0.01, h: 0.34 }, [0.13, 0.12, -0.01]);
|
||||
addBilateral("esqueletico", "tarso", "Ossos do Tarso", "Tarsal Bones", [...AX, "Membro Inferior", "Pé"], { type: "sphere", r: 0.025 }, [0.09, 0.03, 0.02]);
|
||||
addBilateral("esqueletico", "pe", "Metatarso e Falanges do Pé", "Foot Metatarsals/Phalanges", [...AX, "Membro Inferior", "Pé"], { type: "box", w: 0.08, h: 0.04, d: 0.18 }, [0.09, 0.02, 0.1]);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SISTEMA MUSCULAR
|
||||
// ---------------------------------------------------------------------------
|
||||
const MX = ["Sistema Muscular"];
|
||||
add("muscular", "musculo_frontal", "Músculo Frontal", "Frontalis Muscle", [...MX, "Cabeça"], { type: "box", w: 0.1, h: 0.04, d: 0.01 }, [0, 1.78, 0.08]);
|
||||
add("muscular", "musculo_temporal", "Músculo Temporal", "Temporalis Muscle", [...MX, "Cabeça"], { type: "box", w: 0.03, h: 0.06, d: 0.06 }, [0.1, 1.74, 0]);
|
||||
add("muscular", "musculo_masseter", "Músculo Masseter", "Masseter Muscle", [...MX, "Cabeça"], { type: "box", w: 0.02, h: 0.04, d: 0.04 }, [0.09, 1.63, 0.02]);
|
||||
addBilateral("muscular", "esternocleidomastoideo", "Esternocleidomastóideo", "Sternocleidomastoid", [...MX, "Pescoço"], { type: "cylinder", rt: 0.015, rb: 0.02, h: 0.12 }, [0.04, 1.53, 0.03], { rotation: [0.3, 0, 0.15] });
|
||||
add("muscular", "trapezio", "Trapézio", "Trapezius", [...MX, "Dorso"], { type: "box", w: 0.32, h: 0.28, d: 0.03 }, [0, 1.42, -0.08]);
|
||||
addBilateral("muscular", "deltoide", "Deltoide", "Deltoid", [...MX, "Ombro"], { type: "sphere", r: 0.06 }, [0.22, 1.34, 0]);
|
||||
add("muscular", "peitoral_maior", "Peitoral Maior", "Pectoralis Major", [...MX, "Tórax"], { type: "box", w: 0.26, h: 0.18, d: 0.04 }, [0, 1.28, 0.1]);
|
||||
addBilateral("muscular", "biceps_braquial", "Bíceps Braquial", "Biceps Brachii", [...MX, "Membro Superior", "Braço"], { type: "cylinder", rt: 0.03, rb: 0.025, h: 0.24 }, [0.23, 1.15, 0.02]);
|
||||
addBilateral("muscular", "triceps_braquial", "Tríceps Braquial", "Triceps Brachii", [...MX, "Membro Superior", "Braço"], { type: "cylinder", rt: 0.028, rb: 0.024, h: 0.24 }, [0.23, 1.15, -0.02]);
|
||||
add("muscular", "reto_abdominal", "Reto Abdominal", "Rectus Abdominis", [...MX, "Abdome"], { type: "box", w: 0.14, h: 0.22, d: 0.03 }, [0, 1.02, 0.1]);
|
||||
add("muscular", "obliquo_externo", "Oblíquo Externo do Abdome", "External Oblique", [...MX, "Abdome"], { type: "box", w: 0.28, h: 0.2, d: 0.03 }, [0, 1.02, 0.06]);
|
||||
add("muscular", "latissimo_do_dorso", "Latíssimo do Dorso", "Latissimus Dorsi", [...MX, "Dorso"], { type: "box", w: 0.3, h: 0.24, d: 0.03 }, [0, 1.15, -0.09]);
|
||||
add("muscular", "gluteo_maximo", "Glúteo Máximo", "Gluteus Maximus", [...MX, "Pelve"], { type: "box", w: 0.28, h: 0.16, d: 0.08 }, [0, 0.75, -0.08]);
|
||||
addBilateral("muscular", "quadriceps_femoral", "Quadríceps Femoral", "Quadriceps Femoris", [...MX, "Membro Inferior", "Coxa"], { type: "cylinder", rt: 0.055, rb: 0.04, h: 0.38 }, [0.09, 0.5, 0.03]);
|
||||
addBilateral("muscular", "isquiotibiais", "Isquiotibiais", "Hamstrings", [...MX, "Membro Inferior", "Coxa"], { type: "cylinder", rt: 0.05, rb: 0.035, h: 0.38 }, [0.09, 0.5, -0.04]);
|
||||
addBilateral("muscular", "gastrocnemio", "Gastrocnêmio", "Gastrocnemius", [...MX, "Membro Inferior", "Perna"], { type: "cylinder", rt: 0.04, rb: 0.02, h: 0.22 }, [0.09, 0.18, -0.02]);
|
||||
addBilateral("muscular", "tibial_anterior", "Tibial Anterior", "Tibialis Anterior", [...MX, "Membro Inferior", "Perna"], { type: "cylinder", rt: 0.02, rb: 0.012, h: 0.22 }, [0.1, 0.18, 0.02]);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SISTEMA NERVOSO
|
||||
// ---------------------------------------------------------------------------
|
||||
const NX = ["Sistema Nervoso"];
|
||||
add("nervoso", "encefalo", "Encéfalo (Cérebro)", "Brain (Cerebrum)", [...NX, "Sistema Nervoso Central", "Cabeça"], { type: "sphere", r: 0.09 }, [0, 1.74, -0.01], { description: "Inclui os hemisférios cerebrais e o córtex." });
|
||||
add("nervoso", "cerebelo", "Cerebelo", "Cerebellum", [...NX, "Sistema Nervoso Central", "Cabeça"], { type: "sphere", r: 0.04 }, [0, 1.68, -0.06]);
|
||||
add("nervoso", "tronco_encefalico", "Tronco Encefálico", "Brainstem", [...NX, "Sistema Nervoso Central", "Cabeça"], { type: "cylinder", rt: 0.02, rb: 0.02, h: 0.06 }, [0, 1.62, -0.03]);
|
||||
add("nervoso", "medula_espinhal", "Medula Espinhal", "Spinal Cord", [...NX, "Sistema Nervoso Central", "Tronco"], { type: "cylinder", rt: 0.009, rb: 0.009, h: 0.6 }, [0, 1.2, -0.035]);
|
||||
addBilateral("nervoso", "plexo_braquial", "Plexo Braquial", "Brachial Plexus", [...NX, "Sistema Nervoso Periférico", "Ombro"], { type: "sphere", r: 0.02 }, [0.16, 1.38, 0]);
|
||||
addBilateral("nervoso", "nervo_frenico", "Nervo Frênico", "Phrenic Nerve", [...NX, "Sistema Nervoso Periférico", "Pescoço"], { type: "cylinder", rt: 0.004, rb: 0.004, h: 0.25 }, [0.05, 1.35, 0.01]);
|
||||
addBilateral("nervoso", "nervo_mediano", "Nervo Mediano", "Median Nerve", [...NX, "Sistema Nervoso Periférico", "Membro Superior"], { type: "cylinder", rt: 0.004, rb: 0.004, h: 0.4 }, [0.24, 0.95, 0.01]);
|
||||
addBilateral("nervoso", "nervo_radial", "Nervo Radial", "Radial Nerve", [...NX, "Sistema Nervoso Periférico", "Membro Superior"], { type: "cylinder", rt: 0.004, rb: 0.004, h: 0.4 }, [0.25, 0.95, -0.01]);
|
||||
addBilateral("nervoso", "nervo_ulnar", "Nervo Ulnar", "Ulnar Nerve", [...NX, "Sistema Nervoso Periférico", "Membro Superior"], { type: "cylinder", rt: 0.004, rb: 0.004, h: 0.4 }, [0.23, 0.95, -0.02]);
|
||||
addBilateral("nervoso", "nervo_isquiatico", "Nervo Isquiático (Ciático)", "Sciatic Nerve", [...NX, "Sistema Nervoso Periférico", "Membro Inferior"], { type: "cylinder", rt: 0.007, rb: 0.006, h: 0.4 }, [0.09, 0.45, -0.03], { synonyms: ["nervo ciático"] });
|
||||
addBilateral("nervoso", "nervo_femoral", "Nervo Femoral", "Femoral Nerve", [...NX, "Sistema Nervoso Periférico", "Membro Inferior"], { type: "cylinder", rt: 0.005, rb: 0.005, h: 0.3 }, [0.11, 0.55, 0.02]);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SISTEMA CARDIOVASCULAR
|
||||
// ---------------------------------------------------------------------------
|
||||
const CX = ["Sistema Cardiovascular"];
|
||||
add("cardiovascular", "coracao", "Coração", "Heart", [...CX, "Tórax"], { type: "sphere", r: 0.07 }, [0.03, 1.27, 0.04]);
|
||||
add("cardiovascular", "aorta", "Aorta", "Aorta", [...CX, "Tórax", "Abdome"], { type: "cylinder", rt: 0.014, rb: 0.01, h: 0.45 }, [0, 1.1, -0.02]);
|
||||
add("cardiovascular", "veia_cava_superior", "Veia Cava Superior", "Superior Vena Cava", [...CX, "Tórax"], { type: "cylinder", rt: 0.012, rb: 0.012, h: 0.1 }, [0.03, 1.36, 0]);
|
||||
add("cardiovascular", "veia_cava_inferior", "Veia Cava Inferior", "Inferior Vena Cava", [...CX, "Tórax", "Abdome"], { type: "cylinder", rt: 0.013, rb: 0.013, h: 0.35 }, [0.03, 1.1, -0.03]);
|
||||
addBilateral("cardiovascular", "arteria_carotida", "Artéria Carótida", "Carotid Artery", [...CX, "Pescoço"], { type: "cylinder", rt: 0.006, rb: 0.006, h: 0.14 }, [0.03, 1.53, 0.01]);
|
||||
addBilateral("cardiovascular", "arteria_subclavia", "Artéria Subclávia", "Subclavian Artery", [...CX, "Ombro"], { type: "cylinder", rt: 0.006, rb: 0.006, h: 0.12 }, [0.14, 1.4, 0]);
|
||||
addBilateral("cardiovascular", "veia_jugular", "Veia Jugular", "Jugular Vein", [...CX, "Pescoço"], { type: "cylinder", rt: 0.006, rb: 0.006, h: 0.14 }, [0.045, 1.53, -0.01]);
|
||||
addBilateral("cardiovascular", "arteria_braquial", "Artéria Braquial", "Brachial Artery", [...CX, "Membro Superior", "Braço"], { type: "cylinder", rt: 0.005, rb: 0.004, h: 0.24 }, [0.23, 1.15, 0.01]);
|
||||
addBilateral("cardiovascular", "arteria_femoral", "Artéria Femoral", "Femoral Artery", [...CX, "Membro Inferior", "Coxa"], { type: "cylinder", rt: 0.006, rb: 0.005, h: 0.4 }, [0.09, 0.5, 0.05]);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SISTEMA RESPIRATÓRIO
|
||||
// ---------------------------------------------------------------------------
|
||||
const RX = ["Sistema Respiratório"];
|
||||
add("respiratorio", "laringe", "Laringe", "Larynx", [...RX, "Pescoço"], { type: "cylinder", rt: 0.02, rb: 0.02, h: 0.05 }, [0, 1.48, 0.02]);
|
||||
add("respiratorio", "traqueia", "Traqueia", "Trachea", [...RX, "Pescoço", "Tórax"], { type: "cylinder", rt: 0.012, rb: 0.012, h: 0.13 }, [0, 1.4, 0.02]);
|
||||
addBilateral("respiratorio", "bronquio_principal", "Brônquio Principal", "Main Bronchus", [...RX, "Tórax"], { type: "cylinder", rt: 0.008, rb: 0.006, h: 0.06 }, [0.03, 1.32, 0], { rotation: [0, 0, -0.5] });
|
||||
addBilateral("respiratorio", "pulmao", "Pulmão", "Lung", [...RX, "Tórax"], { type: "box", w: 0.14, h: 0.28, d: 0.16 }, [0.13, 1.22, -0.02]);
|
||||
add("respiratorio", "diafragma", "Diafragma", "Diaphragm", [...RX, "Tórax", "Abdome"], { type: "box", w: 0.32, h: 0.02, d: 0.2 }, [0, 1.08, 0]);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SISTEMA TEGUMENTAR
|
||||
// ---------------------------------------------------------------------------
|
||||
const TX = ["Sistema Tegumentar"];
|
||||
add("tegumentar", "epiderme", "Epiderme", "Epidermis", [...TX, "Membro Superior", "Antebraço"], { type: "box", w: 0.09, h: 0.2, d: 0.005 }, [0.24, 0.85, 0.035], { description: "Camada mais externa da pele, avascular." });
|
||||
add("tegumentar", "derme", "Derme", "Dermis", [...TX, "Membro Superior", "Antebraço"], { type: "box", w: 0.09, h: 0.2, d: 0.01 }, [0.24, 0.85, 0.028], { description: "Camada intermediária, rica em vasos, nervos e anexos cutâneos." });
|
||||
add("tegumentar", "hipoderme", "Hipoderme (Tecido Subcutâneo)", "Hypodermis (Subcutaneous Tissue)", [...TX, "Membro Superior", "Antebraço"], { type: "box", w: 0.09, h: 0.2, d: 0.015 }, [0.24, 0.85, 0.018], { description: "Camada mais profunda, rica em tecido adiposo." });
|
||||
|
||||
writeFileSync(
|
||||
path.resolve(__dirname, "manifest.json"),
|
||||
JSON.stringify({ systems, structures }, null, 2),
|
||||
);
|
||||
|
||||
console.log(`manifest.json gerado com ${structures.length} estruturas em ${systems.length} sistemas.`);
|
||||
Reference in new issue
Block a user