skin-tokens

This commit is contained in:
Aggtaa
2026-09-30 07:38:21 +03:00
parent 8db94e5c31
commit 9cad7452bb
16 changed files with 1314 additions and 78 deletions
+47 -1
View File
@@ -70,6 +70,52 @@ function meshPreview(mesh: Link, prefix: string, id: number): Graph {
};
}
/**
* Очищенная картинка → GLB через ноды ComfyUI-Hunyuan3DWrapper (kijai) на той же модели Hunyuan3D-2 mini:
* Hy3DModelLoader → Hy3DGenerateMesh (объект на белом, 518×518) → Hy3DVAEDecode → Hy3DPostprocessMesh (до o.faces) →
* [Hy3DIMRemesh → Img2GlbRepairTrimesh → упрощение, если o.remeshVertices > 0] → Hy3DExportMesh. Файл сохраняет Hy3DExportMesh, в историю задачи его
* записывает Img2GlbOutputFile; превью — встроенными нодами по тому же файлу.
*/
export function hy3dWrapperGraph(o: {
image: string; prefix: string; seed: number; steps: number; octree: number; faces: number; remeshVertices: number;
}): Graph {
const g: Graph = {
'1': { class_type: 'LoadImage', inputs: { image: o.image } },
'2': { class_type: 'LoadBackgroundRemovalModel', inputs: { bg_removal_name: 'BiRefNet-general.safetensors' } },
'3': { class_type: 'RemoveBackground', inputs: { bg_removal_model: ['2', 0], image: ['1', 0] } },
// объект по маске — в квадрат 518 (вход DINO) с полями на белом фоне, как в обучении Hunyuan3D; маска в
// Hy3DGenerateMesh не подаётся — она лишь умножается на картинку, и фон становится чёрным
'15': { class_type: 'ImageCropToMask', inputs: {
images: ['1', 0], masks: ['3', 0], width: 518, height: 518, pad_factor: 1.2, grow_mask: 0, background: '#FFFFFF' } },
'4': { class_type: 'Hy3DModelLoader', inputs: { model: 'hunyuan3d-dit-v2-mini_fp16.safetensors', attention_mode: 'sdpa', cublas_ops: false } },
'5': { class_type: 'Hy3DGenerateMesh', inputs: { pipeline: ['4', 0], image: ['15', 0], guidance_scale: 5.5, steps: o.steps,
seed: o.seed, scheduler: 'FlowMatchEulerDiscreteScheduler', force_offload: true } },
'6': { class_type: 'Hy3DVAEDecode', inputs: { vae: ['4', 1], latents: ['5', 0], box_v: 1.01, octree_resolution: o.octree, num_chunks: 8000,
mc_level: 0, mc_algo: 'mc', enable_flash_vdm: true, force_offload: true } },
'7': { class_type: 'Hy3DPostprocessMesh', inputs: { trimesh: ['6', 0], remove_floaters: true, remove_degenerate_faces: true,
reduce_faces: true, max_facenum: o.faces, smooth_normals: false } },
};
let mesh: Link = ['7', 0];
if (o.remeshVertices > 0) {
// Instant Meshes оставляет дыры в случайных местах и неманифолдные рёбра: перестраиваем без упрощения,
// чиним (Img2GlbRepairTrimesh) и только потом упрощаем до o.faces
g['7'].inputs.max_facenum = Math.max(o.faces, 20000);
g['8'] = { class_type: 'Hy3DIMRemesh', inputs: { trimesh: mesh, merge_vertices: true, vertex_count: o.remeshVertices, smooth_iter: 8,
align_to_boundaries: true, triangulate_result: true, max_facenum: 10_000_000 } };
g['16'] = { class_type: 'Img2GlbRepairTrimesh', inputs: { trimesh: ['8', 0] } };
g['17'] = { class_type: 'Hy3DPostprocessMesh', inputs: { trimesh: ['16', 0], remove_floaters: false, remove_degenerate_faces: true,
reduce_faces: true, max_facenum: o.faces, smooth_normals: false } };
mesh = ['17', 0];
}
Object.assign(g, {
'9': { class_type: 'Hy3DExportMesh', inputs: { trimesh: mesh, filename_prefix: o.prefix, file_format: 'glb', save_file: true } },
'10': { class_type: 'Img2GlbOutputFile', inputs: { path: ['9', 0] } },
'11': { class_type: 'Img2GlbLoadOutputMesh', inputs: { path: ['9', 0] } },
...meshPreview(['11', 0], o.prefix, 12),
});
return g;
}
/** Ракурсы рендеров текстурированной модели TRELLIS.2, градусы поворота вокруг вертикали. */
export const TEXTURE_VIEWS = [0, 90, 180, 270];
@@ -192,7 +238,7 @@ export function paintGraph(o: { image: string; mesh: string; prefix: string; see
export function kindOf(graph: unknown): JobKind {
const types = Object.values((graph ?? {}) as Graph).map(n => n?.class_type);
if (types.includes('Img2GlbHunyuanPaint')) return 'текстура';
if (types.includes('SaveGLB')) return '3D-модель';
if (types.includes('SaveGLB') || types.includes('Hy3DExportMesh')) return '3D-модель';
if (types.includes('RemoveBackground')) return 'очистка фона';
if (types.includes('KSampler') && types.includes('CheckpointLoaderSimple')) return 'генерация';
return 'другое';