trellis.2. unified 3d workflows

This commit is contained in:
Aggtaa
2026-09-24 13:34:06 +03:00
parent a47d23603c
commit 8db94e5c31
11 changed files with 690 additions and 177 deletions
+50 -6
View File
@@ -1,10 +1,14 @@
// Фоновое отслеживание незавершённых задач: результаты из ComfyUI записываются в базу,
// даже если ни одна страница не открыта.
import type { FileRef, Job, LiveStatus } from '../shared/api.ts';
import type {
FileRef, Job, LiveStatus, ModelArtifacts, ModelGenerator, TextureArtifacts, TextureSource,
} from '../shared/api.ts';
import { comfyJson } from './comfy.ts';
import { glbTriangles } from './glb.ts';
import { TEXTURE_VIEWS } from './workflows.ts';
import {
batchByJob, deleteJob, finishBatch, finishJob, finishProjectClean, finishProjectModel, pendingJobs, projectByCleanJob,
projectByModelJob, transaction,
batchByJob, deleteJob, finishBatch, finishJob, finishProjectClean, finishProjectModel, finishProjectTexture, pendingJobs,
projectByCleanJob, projectByModelJob, projectByTextureJob, transaction,
} from './db.ts';
/** Сколько ждать задачу, которой нет ни в очереди, ни в истории ComfyUI (ComfyUI перезапускался). */
@@ -40,6 +44,36 @@ function outputFiles(h: HistoryEntry) {
return { images, models };
}
type Files = { images?: FileRef[]; models?: FileRef[] };
/** Файл по метке префикса (см. workflows.ts): <prefix><tag>_NNNNN_.png. */
const tagged = (files: FileRef[] | undefined, tag: string) => files?.find(i => i.filename.includes(tag + '_')) ?? null;
/**
* Модель без текстур: у Hunyuan3D-2 mini — единственный GLB, у TRELLIS.2 — <prefix>_mesh_…glb; плюс превью.
*/
function modelArtifacts(generator: ModelGenerator, r: Files): ModelArtifacts | null {
const glb = generator === 'trellis' ? tagged(r.models, '_mesh') : r.models?.[0];
if (!glb) return null;
return { generator, glb, preview: tagged(r.images, '_preview'), faces: glbTriangles(glb), updated: Date.now() };
}
/**
* Текстурная группа: текстурированный GLB (не _mesh), base color, metallic, roughness, развёртка и виды —
* рендеры _view<угол> у TRELLIS.2 или 6 нарисованных видов _mv у Paint.
*/
function textureArtifacts(source: TextureSource, r: Files): TextureArtifacts | null {
const glb = r.models?.find(m => !m.filename.includes('_mesh_')) ?? null;
if (!glb) return null;
const images = r.images ?? [];
const views = source === 'trellis'
? TEXTURE_VIEWS.map(a => tagged(images, '_view' + a)).filter((i): i is FileRef => i != null)
: images.filter(i => i.filename.includes('_mv_')).sort((x, y) => x.filename.localeCompare(y.filename));
return {
source, glb, faces: glbTriangles(glb), baseColor: tagged(images, '_basecolor'), metallic: tagged(images, '_metallic'),
roughness: tagged(images, '_roughness'), uv: tagged(images, '_uv'), views, updated: Date.now(),
};
}
/**
* Завершение задачи. Картинки text2img прикрепляются к её батчу (Images.batchId);
* результат очистки и модель проекта заменяют прежние в проекте, а сама задача удаляется — история не хранится.
@@ -52,11 +86,21 @@ function finish(job: Job, state: 'done' | 'error', result: { error?: string; ima
deleteJob(job.id);
return;
}
// модель проекта: GLB и превью — в проект, задача удаляется (история не хранится)
// модель проекта (у TRELLIS.2 — сразу с текстурами) — в проект, задача удаляется (история не хранится)
const modelProject = job.type === 'model' ? job.params.projectId ?? projectByModelJob(job.id) : null;
if (modelProject != null) {
if (job.type === 'model' && modelProject != null) {
const generator = job.params.generator ?? 'hunyuan';
const done = state === 'done';
finishProjectModel(modelProject, done ? result.models?.[0] ?? null : null, done ? result.images?.[0] ?? null : null, result.error);
const model = done ? modelArtifacts(generator, result) : null;
const texture = done && generator === 'trellis' ? textureArtifacts('trellis', result) : null;
finishProjectModel(modelProject, model, texture, result.error ?? (done && !model ? 'нет GLB в результате' : undefined));
deleteJob(job.id);
return;
}
// текстура Hunyuan3D-2 Paint — в проект, задача удаляется
const textureProject = job.type === 'texture' ? job.params.projectId ?? projectByTextureJob(job.id) : null;
if (textureProject != null) {
finishProjectTexture(textureProject, state === 'done' ? textureArtifacts('paint', result) : null, result.error);
deleteJob(job.id);
return;
}