mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
feat(generation): add Pixal3D generator to supported ecosystems
Wire Pixal3D as a new generation ecosystem, enabling it in the generation form and orchestration pipeline. Creates orchestrator handler, graph configuration, and ecosystem integration following the established patterns from Meshy and Tripio ecosystems. Updates @civitai/client to beta.95 to support the new model type.
This commit is contained in:
+1
-1
@@ -125,7 +125,7 @@
|
||||
"@civitai/app-sdk": "^0.14.0",
|
||||
"@civitai/auth": "workspace:*",
|
||||
"@civitai/buzz": "workspace:*",
|
||||
"@civitai/client": "0.2.0-beta.94",
|
||||
"@civitai/client": "0.2.0-beta.95",
|
||||
"@civitai/cybertipline-tools": "^0.1.0",
|
||||
"@civitai/db-queries": "workspace:*",
|
||||
"@civitai/db-schema": "workspace:*",
|
||||
|
||||
@@ -211,6 +211,8 @@ export const ECO = {
|
||||
PolyGen: 73,
|
||||
Tripo: 75,
|
||||
Hunyuan3D: 76,
|
||||
Pixal3D: 83,
|
||||
Trellis2: 84,
|
||||
|
||||
// Utility ecosystems
|
||||
Upscaler: 66,
|
||||
@@ -854,6 +856,18 @@ export const ecosystems: EcosystemRecord[] = [
|
||||
displayName: 'Hunyuan3D',
|
||||
sortOrder: 303,
|
||||
},
|
||||
{
|
||||
id: ECO.Pixal3D,
|
||||
key: 'Pixal3D',
|
||||
displayName: 'Pixal3D',
|
||||
sortOrder: 304,
|
||||
},
|
||||
{
|
||||
id: ECO.Trellis2,
|
||||
key: 'Trellis2',
|
||||
displayName: 'Trellis.2',
|
||||
sortOrder: 305,
|
||||
},
|
||||
];
|
||||
|
||||
export const ecosystemById = new Map(ecosystems.map((e) => [e.id, e]));
|
||||
@@ -866,7 +880,13 @@ export const ecosystemByKey = new Map(ecosystems.map((e) => [e.key, e]));
|
||||
* (their `BaseModelRecord`s register as `type: 'image'`). Single source of
|
||||
* truth for the 3D ecosystem picker filter in `getEcosystemDisplayItems`.
|
||||
*/
|
||||
export const MODEL3D_ECOSYSTEM_IDS = new Set<number>([ECO.PolyGen, ECO.Tripo, ECO.Hunyuan3D]);
|
||||
export const MODEL3D_ECOSYSTEM_IDS = new Set<number>([
|
||||
ECO.PolyGen,
|
||||
ECO.Tripo,
|
||||
ECO.Hunyuan3D,
|
||||
ECO.Pixal3D,
|
||||
ECO.Trellis2,
|
||||
]);
|
||||
|
||||
/** Ecosystem keys for the 3D-model ecosystems (`model3d` output). */
|
||||
export const MODEL3D_ECOSYSTEM_KEYS = new Set<string>(
|
||||
@@ -928,6 +948,9 @@ export const SELF_HOSTED_ECOSYSTEM_KEYS = [
|
||||
'Ace',
|
||||
// Hunyuan3dComfyPolyGenInput (3D; Meshy/Tripo are FAL and stay external)
|
||||
'Hunyuan3D',
|
||||
// Trellis2ImageTo3dComfyPolyGenInput (3D; Pixal3D + Trellis.2 are modelVersions of trellis2)
|
||||
'Pixal3D',
|
||||
'Trellis2',
|
||||
] as const;
|
||||
|
||||
const selfHostedEcosystemKeySet = new Set<string>(SELF_HOSTED_ECOSYSTEM_KEYS);
|
||||
@@ -1188,6 +1211,8 @@ export const ecosystemSupport: EcosystemSupport[] = [
|
||||
// route the img2model3d workflow to these ecosystems.
|
||||
{ ecosystemId: ECO.Tripo, supportType: 'generation', modelTypes: [] },
|
||||
{ ecosystemId: ECO.Hunyuan3D, supportType: 'generation', modelTypes: [] },
|
||||
{ ecosystemId: ECO.Pixal3D, supportType: 'generation', modelTypes: [] },
|
||||
{ ecosystemId: ECO.Trellis2, supportType: 'generation', modelTypes: [] },
|
||||
|
||||
// Upscaler - upscaler models only
|
||||
{ ecosystemId: ECO.Upscaler, supportType: 'generation', modelTypes: [ModelType.Upscaler] },
|
||||
@@ -2143,6 +2168,8 @@ export const BM = {
|
||||
Reve: 96,
|
||||
MageFlow: 97,
|
||||
Flux3Video: 98,
|
||||
Pixal3D: 102,
|
||||
Trellis2: 103,
|
||||
} as const;
|
||||
|
||||
// Guard against duplicate ids — `baseModelById` is keyed by id, so collisions
|
||||
@@ -3526,6 +3553,22 @@ export const baseModelRecords: BaseModelRecord[] = [
|
||||
ecosystemId: ECO.Hunyuan3D,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
id: BM.Pixal3D,
|
||||
name: 'Pixal3D',
|
||||
description: 'Pixal3D image-to-3D generation (via Comfy)',
|
||||
type: 'image',
|
||||
ecosystemId: ECO.Pixal3D,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
id: BM.Trellis2,
|
||||
name: 'Trellis.2',
|
||||
description: 'Trellis.2 image-to-3D generation (via Comfy)',
|
||||
type: 'image',
|
||||
ecosystemId: ECO.Trellis2,
|
||||
hidden: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const baseModelById = new Map(baseModelRecords.map((m) => [m.id, m]));
|
||||
|
||||
Generated
+5
-5
@@ -47,8 +47,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:packages/civitai-buzz
|
||||
'@civitai/client':
|
||||
specifier: 0.2.0-beta.94
|
||||
version: 0.2.0-beta.94
|
||||
specifier: 0.2.0-beta.95
|
||||
version: 0.2.0-beta.95
|
||||
'@civitai/cybertipline-tools':
|
||||
specifier: ^0.1.0
|
||||
version: 0.1.0
|
||||
@@ -2089,8 +2089,8 @@ packages:
|
||||
resolution: {integrity: sha512-dSXkI8hVoozzlPS9DymzoyncWz8eapH3wkSDzDVGWjjqamXn8Dbcq1hWu5xUZkZmqCaR76F7BS9BQNqvOJ++Sw==}
|
||||
engines: {git: '>=2.11.0', node: '>=18.0.0', npm: '>=7.19.0', yarn: '>=1.7.0'}
|
||||
|
||||
'@civitai/client@0.2.0-beta.94':
|
||||
resolution: {integrity: sha512-HEP+8BK+3ovJWmIeElHKniH1Oy32JGlA0RvMKSQ2hHIO6l5Hm6Nbnq1v8yYAg8ePP3FlZHkCHyxWYQjjimbfig==}
|
||||
'@civitai/client@0.2.0-beta.95':
|
||||
resolution: {integrity: sha512-wuKafMnIizfd08Wg6gjTjWHrOyLkXvr9gUPLdNFP1J7SUc56K5bG44gZKYzy1vTaVrmu8PFjrJabFh9Tz4IEgA==}
|
||||
engines: {git: '>=2.11.0', node: '>=18.0.0', npm: '>=7.19.0', yarn: '>=1.7.0'}
|
||||
|
||||
'@civitai/cybertipline-tools@0.1.0':
|
||||
@@ -13270,7 +13270,7 @@ snapshots:
|
||||
rfc6902: 5.2.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@civitai/client@0.2.0-beta.94':
|
||||
'@civitai/client@0.2.0-beta.95':
|
||||
dependencies:
|
||||
'@hey-api/client-fetch': 0.1.14
|
||||
rfc6902: 5.2.0
|
||||
|
||||
@@ -104,6 +104,8 @@ const POLYGEN_ECOSYSTEM_MODEL_LABELS: Record<string, string> = {
|
||||
PolyGen: 'Meshy',
|
||||
Tripo: 'Tripo',
|
||||
Hunyuan3D: 'Hunyuan3D',
|
||||
Pixal3D: 'Pixal3D',
|
||||
Trellis2: 'Trellis.2',
|
||||
};
|
||||
|
||||
export function QueueItem({
|
||||
|
||||
@@ -484,9 +484,9 @@ function InnerProvider({
|
||||
// The 3D ecosystem to land on — preserve the model (Meshy/Tripo/
|
||||
// Hunyuan3D) the source was generated with. Falls back to PolyGen
|
||||
// (Meshy) for legacy items whose metadata predates the model selector.
|
||||
const model3dEcosystem: 'PolyGen' | 'Tripo' | 'Hunyuan3D' =
|
||||
const model3dEcosystem: 'PolyGen' | 'Tripo' | 'Hunyuan3D' | 'Pixal3D' | 'Trellis2' =
|
||||
remixEcosystemKey && MODEL3D_ECOSYSTEM_KEYS.has(remixEcosystemKey)
|
||||
? (remixEcosystemKey as 'PolyGen' | 'Tripo' | 'Hunyuan3D')
|
||||
? (remixEcosystemKey as 'PolyGen' | 'Tripo' | 'Hunyuan3D' | 'Pixal3D' | 'Trellis2')
|
||||
: 'PolyGen';
|
||||
|
||||
let resolvedWorkflow: string;
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
display: grid;
|
||||
gap: var(--mantine-spacing-xs);
|
||||
grid-template-columns: repeat(2, minmax(0, 200px));
|
||||
justify-content: center;
|
||||
// Left-align with the field label. Centering pushed a lone image (every
|
||||
// image-to-3D input is max:1) off to the right of the form; multi-image
|
||||
// inputs simply pack from the left.
|
||||
justify-content: start;
|
||||
|
||||
@container (min-width: 530px) {
|
||||
grid-template-columns: repeat(3, minmax(0, 200px));
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Pixal3D Schema (Pixal3D via Comfy / Trellis2)
|
||||
*
|
||||
* Zod schema for the Pixal3D 3D-model generation form. Pixal3D is a
|
||||
* `modelVersion` of the Trellis2 comfy pipeline, exposed as its own ecosystem.
|
||||
* Like Tripo and Hunyuan3D it is image-to-3D only, so there is no text-to-3D
|
||||
* branch and no `process` discriminator.
|
||||
*
|
||||
* Mirrors `src/server/orchestrator/hunyuan3d/hunyuan3d.schema.ts` (the other
|
||||
* comfy 3D ecosystem):
|
||||
* - `sourceImageSchema` reused for the "URL or upload" pattern.
|
||||
* - `toPixal3dPolyGenInput` converts validated form data to the
|
||||
* `Trellis2ImageTo3dComfyPolyGenInput` shape (`engine: 'comfy',
|
||||
* model: 'trellis2', operation: 'imageTo3D', modelVersion: 'pixal3D'`)
|
||||
* consumed by the graph handler when building the `polyGen` step.
|
||||
*
|
||||
* Only a small, user-meaningful subset of the client's comfy knobs is surfaced
|
||||
* (texture / remesh / pbr toggles + seed); the orchestrator applies its own
|
||||
* defaults for the remaining sampler / step / resolution fields.
|
||||
*/
|
||||
|
||||
import type { Trellis2ImageTo3dComfyPolyGenInput } from '@civitai/client';
|
||||
import * as z from 'zod';
|
||||
import { sourceImageSchema } from '~/server/orchestrator/infrastructure/base.schema';
|
||||
import { removeEmpty } from '~/utils/object-helpers';
|
||||
|
||||
// =============================================================================
|
||||
// Schema
|
||||
// =============================================================================
|
||||
|
||||
export const pixal3dGenerationSchema = z.object({
|
||||
sourceImage: sourceImageSchema,
|
||||
shouldTexture: z.boolean().default(true),
|
||||
shouldRemesh: z.boolean().default(true),
|
||||
enablePbr: z.boolean().default(false),
|
||||
// Comfy accepts a 32-bit signed int seed; optional so the handler can
|
||||
// randomize when omitted.
|
||||
seed: z.number().int().min(-2147483648).max(2147483647).optional(),
|
||||
});
|
||||
export type Pixal3dGenerationSchema = z.infer<typeof pixal3dGenerationSchema>;
|
||||
|
||||
// =============================================================================
|
||||
// Helper — convert validated schema to Pixal3D/Comfy PolyGen input shape
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Convert form data to a Pixal3D/Comfy PolyGen input. Used by the graph handler
|
||||
* when building the `PolyGenStepTemplate` for `submitWorkflow`. `removeEmpty`
|
||||
* strips only null/undefined (not `false`/`0`), so the boolean toggles and a
|
||||
* `seed: 0` survive while an omitted `seed` is dropped.
|
||||
*/
|
||||
export function toPixal3dPolyGenInput(
|
||||
data: Pixal3dGenerationSchema
|
||||
): Trellis2ImageTo3dComfyPolyGenInput {
|
||||
return removeEmpty({
|
||||
engine: 'comfy' as const,
|
||||
model: 'trellis2' as const,
|
||||
operation: 'imageTo3D' as const,
|
||||
modelVersion: 'pixal3D' as const,
|
||||
imageUrl: data.sourceImage.url,
|
||||
shouldTexture: data.shouldTexture,
|
||||
shouldRemesh: data.shouldRemesh,
|
||||
enablePbr: data.enablePbr,
|
||||
seed: data.seed,
|
||||
}) as Trellis2ImageTo3dComfyPolyGenInput;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Trellis.2 Schema (Trellis.2 via Comfy)
|
||||
*
|
||||
* Zod schema for the Trellis.2 3D-model generation form. Trellis.2 is the
|
||||
* base `modelVersion` of the same comfy pipeline Pixal3D rides
|
||||
* (`Trellis2ImageTo3dComfyPolyGenInput`), differing only in
|
||||
* `modelVersion: 'trellis2'` vs Pixal3D's `'pixal3D'`. Like Pixal3D / Tripo /
|
||||
* Hunyuan3D it is image-to-3D only, so there is no text-to-3D branch and no
|
||||
* `process` discriminator.
|
||||
*
|
||||
* Mirrors `src/server/orchestrator/pixal3d/pixal3d.schema.ts`:
|
||||
* - `sourceImageSchema` reused for the "URL or upload" pattern.
|
||||
* - `toTrellis2PolyGenInput` converts validated form data to the
|
||||
* `Trellis2ImageTo3dComfyPolyGenInput` shape (`engine: 'comfy',
|
||||
* model: 'trellis2', operation: 'imageTo3D', modelVersion: 'trellis2'`)
|
||||
* consumed by the graph handler when building the `polyGen` step.
|
||||
*
|
||||
* Only a small, user-meaningful subset of the client's comfy knobs is surfaced
|
||||
* (texture / remesh / pbr toggles + seed); the orchestrator applies its own
|
||||
* defaults for the remaining sampler / step / resolution fields.
|
||||
*/
|
||||
|
||||
import type { Trellis2ImageTo3dComfyPolyGenInput } from '@civitai/client';
|
||||
import * as z from 'zod';
|
||||
import { sourceImageSchema } from '~/server/orchestrator/infrastructure/base.schema';
|
||||
import { removeEmpty } from '~/utils/object-helpers';
|
||||
|
||||
// =============================================================================
|
||||
// Schema
|
||||
// =============================================================================
|
||||
|
||||
export const trellis2GenerationSchema = z.object({
|
||||
sourceImage: sourceImageSchema,
|
||||
shouldTexture: z.boolean().default(true),
|
||||
shouldRemesh: z.boolean().default(true),
|
||||
enablePbr: z.boolean().default(false),
|
||||
// Comfy accepts a 32-bit signed int seed; optional so the handler can
|
||||
// randomize when omitted.
|
||||
seed: z.number().int().min(-2147483648).max(2147483647).optional(),
|
||||
});
|
||||
export type Trellis2GenerationSchema = z.infer<typeof trellis2GenerationSchema>;
|
||||
|
||||
// =============================================================================
|
||||
// Helper — convert validated schema to Trellis.2/Comfy PolyGen input shape
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Convert form data to a Trellis.2/Comfy PolyGen input. Used by the graph
|
||||
* handler when building the `PolyGenStepTemplate` for `submitWorkflow`.
|
||||
* `removeEmpty` strips only null/undefined (not `false`/`0`), so the boolean
|
||||
* toggles and a `seed: 0` survive while an omitted `seed` is dropped.
|
||||
*/
|
||||
export function toTrellis2PolyGenInput(
|
||||
data: Trellis2GenerationSchema
|
||||
): Trellis2ImageTo3dComfyPolyGenInput {
|
||||
return removeEmpty({
|
||||
engine: 'comfy' as const,
|
||||
model: 'trellis2' as const,
|
||||
operation: 'imageTo3D' as const,
|
||||
modelVersion: 'trellis2' as const,
|
||||
imageUrl: data.sourceImage.url,
|
||||
shouldTexture: data.shouldTexture,
|
||||
shouldRemesh: data.shouldRemesh,
|
||||
enablePbr: data.enablePbr,
|
||||
seed: data.seed,
|
||||
}) as Trellis2ImageTo3dComfyPolyGenInput;
|
||||
}
|
||||
@@ -479,6 +479,8 @@ const featureFlags = createFeatureFlags({
|
||||
// `polygenVersion: 'v7'` fail the node's schema (see polygen-graph.ts).
|
||||
tripoGenerator: { availability: ['mod'], fliptKey: 'tripo-generator' },
|
||||
hunyuan3dGenerator: { availability: ['public'], fliptKey: 'hunyuan3d-generator' },
|
||||
pixal3dGenerator: { availability: ['mod'], fliptKey: 'pixal3d-generator' },
|
||||
trellis2Generator: { availability: ['mod'], fliptKey: 'trellis2-generator' },
|
||||
meshyV7Generator: { availability: ['mod'], fliptKey: 'meshy-v7-generator' },
|
||||
// Grok Imagine Image 2.0 — gates ONLY the v2.0 entry in the Grok version
|
||||
// picker; v1.0 / v1.5 stay live regardless, so Grok image + video generation
|
||||
|
||||
+2
-1
@@ -102,7 +102,8 @@ vi.mock('@civitai/client', () => {
|
||||
SdCppSchedule SdxlAiToolkitTrainingInput SeedanceVideoGenInput SeedreamImageGenInput
|
||||
SeedreamVersion Sora2ImageToVideoInput Sora2TextToVideoInput SubmitWorkflowData
|
||||
TextToImageStep TextToImageStepTemplate TrainingOutput TrainingStep TrainingStepTemplate
|
||||
TransactionInfo TransactionType TripoFalPolyGenInput UpdateWorkflowRequest
|
||||
TransactionInfo TransactionType Trellis2ImageTo3dComfyPolyGenInput
|
||||
TripoFalPolyGenInput UpdateWorkflowRequest
|
||||
Veo3VideoGenInput VideoBlob VideoEnhancementInput VideoEnhancementStepTemplate
|
||||
VideoGenStepTemplate VideoInterpolationStepTemplate VideoUpscalerStepTemplate
|
||||
ViduQ3VideoGenInput ViduVideoGenInput Wan21CivitaiVideoGenInput
|
||||
|
||||
@@ -61,6 +61,8 @@ import { createAceAudioInput } from './ace-audio.handler';
|
||||
import { createPolyGenInput } from './polygen-graph.handler';
|
||||
import { createTripoInput } from './tripo-graph.handler';
|
||||
import { createHunyuan3dInput } from './hunyuan3d-graph.handler';
|
||||
import { createPixal3dInput } from './pixal3d-graph.handler';
|
||||
import { createTrellis2Input } from './trellis2-graph.handler';
|
||||
|
||||
// Video ecosystem handlers
|
||||
import { createWanSteps } from './wan.handler';
|
||||
@@ -261,6 +263,8 @@ export { createAceAudioInput } from './ace-audio.handler';
|
||||
export { createPolyGenInput } from './polygen-graph.handler';
|
||||
export { createTripoInput } from './tripo-graph.handler';
|
||||
export { createHunyuan3dInput } from './hunyuan3d-graph.handler';
|
||||
export { createPixal3dInput } from './pixal3d-graph.handler';
|
||||
export { createTrellis2Input } from './trellis2-graph.handler';
|
||||
|
||||
// Video ecosystems
|
||||
export { createWanSteps } from './wan.handler';
|
||||
@@ -544,6 +548,12 @@ async function createEcosystemStep(
|
||||
case 'Hunyuan3D':
|
||||
return createHunyuan3dInput(normalizedData, handlerCtx);
|
||||
|
||||
case 'Pixal3D':
|
||||
return createPixal3dInput(normalizedData, handlerCtx);
|
||||
|
||||
case 'Trellis2':
|
||||
return createTrellis2Input(normalizedData, handlerCtx);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown ecosystem: ${ecosystem}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Pixal3D Ecosystem Handler (V2 unified pipeline)
|
||||
*
|
||||
* Converts a validated pixal3d-graph snapshot into a `PolyGenStepTemplate` so
|
||||
* the unified generate/whatif pipeline can submit it like any other ecosystem
|
||||
* step. Mirrors `hunyuan3d-graph.handler.ts` (the other comfy 3D ecosystem);
|
||||
* differs in the model/modelVersion (`trellis2` / `pixal3D`) and the input
|
||||
* builder (`toPixal3dPolyGenInput`).
|
||||
*
|
||||
* The pixal3d graph carries the standard `images` array (polygen img2model3d
|
||||
* convention), which we map to the schema's `sourceImage` before handing off to
|
||||
* the converter. All other node names match the schema 1:1.
|
||||
*/
|
||||
|
||||
import type { PolyGenStepTemplate, Trellis2ImageTo3dComfyPolyGenInput } from '@civitai/client';
|
||||
import { defineHandler } from './handler-factory';
|
||||
import type { StepInput } from '.';
|
||||
import type { GenerationGraphTypes } from '~/shared/data-graph/generation/generation-graph';
|
||||
import {
|
||||
toPixal3dPolyGenInput,
|
||||
type Pixal3dGenerationSchema,
|
||||
} from '~/server/orchestrator/pixal3d/pixal3d.schema';
|
||||
import { buildModel3DPreviewStep } from './model3d-preview';
|
||||
|
||||
type EcosystemGraphOutput = Extract<GenerationGraphTypes['Ctx'], { ecosystem: string }>;
|
||||
type Pixal3dCtx = EcosystemGraphOutput & { ecosystem: 'Pixal3D' };
|
||||
|
||||
export const createPixal3dInput = defineHandler<Pixal3dCtx, StepInput[]>((data, ctx) => {
|
||||
const sourceImage = (data as { images?: Array<{ url: string; width: number; height: number }> })
|
||||
.images?.[0];
|
||||
const input = toPixal3dPolyGenInput({
|
||||
...data,
|
||||
...(sourceImage ? { sourceImage } : {}),
|
||||
} as unknown as Pixal3dGenerationSchema) as Trellis2ImageTo3dComfyPolyGenInput;
|
||||
|
||||
const polyGenStep: PolyGenStepTemplate = {
|
||||
$type: 'polyGen',
|
||||
input,
|
||||
};
|
||||
|
||||
const previewStep = buildModel3DPreviewStep(ctx.baseStepIndex);
|
||||
|
||||
// Cast to StepInput[] — the shared `StepInput` union lists neither
|
||||
// `PolyGenStepTemplate` nor the (client-untyped) `model3DPreview` step,
|
||||
// but the orchestrator queue accepts both natively.
|
||||
return [polyGenStep, previewStep] as unknown as StepInput[];
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Trellis.2 Ecosystem Handler (V2 unified pipeline)
|
||||
*
|
||||
* Converts a validated trellis2-graph snapshot into a `PolyGenStepTemplate` so
|
||||
* the unified generate/whatif pipeline can submit it like any other ecosystem
|
||||
* step. Mirrors `pixal3d-graph.handler.ts` (the sibling Trellis2 modelVersion);
|
||||
* differs only in `modelVersion` (`trellis2`) via `toTrellis2PolyGenInput`.
|
||||
*
|
||||
* The trellis2 graph carries the standard `images` array (polygen img2model3d
|
||||
* convention), which we map to the schema's `sourceImage` before handing off to
|
||||
* the converter. All other node names match the schema 1:1.
|
||||
*/
|
||||
|
||||
import type { PolyGenStepTemplate, Trellis2ImageTo3dComfyPolyGenInput } from '@civitai/client';
|
||||
import { defineHandler } from './handler-factory';
|
||||
import type { StepInput } from '.';
|
||||
import type { GenerationGraphTypes } from '~/shared/data-graph/generation/generation-graph';
|
||||
import {
|
||||
toTrellis2PolyGenInput,
|
||||
type Trellis2GenerationSchema,
|
||||
} from '~/server/orchestrator/trellis2/trellis2.schema';
|
||||
import { buildModel3DPreviewStep } from './model3d-preview';
|
||||
|
||||
type EcosystemGraphOutput = Extract<GenerationGraphTypes['Ctx'], { ecosystem: string }>;
|
||||
type Trellis2Ctx = EcosystemGraphOutput & { ecosystem: 'Trellis2' };
|
||||
|
||||
export const createTrellis2Input = defineHandler<Trellis2Ctx, StepInput[]>((data, ctx) => {
|
||||
const sourceImage = (data as { images?: Array<{ url: string; width: number; height: number }> })
|
||||
.images?.[0];
|
||||
const input = toTrellis2PolyGenInput({
|
||||
...data,
|
||||
...(sourceImage ? { sourceImage } : {}),
|
||||
} as unknown as Trellis2GenerationSchema) as Trellis2ImageTo3dComfyPolyGenInput;
|
||||
|
||||
const polyGenStep: PolyGenStepTemplate = {
|
||||
$type: 'polyGen',
|
||||
input,
|
||||
};
|
||||
|
||||
const previewStep = buildModel3DPreviewStep(ctx.baseStepIndex);
|
||||
|
||||
// Cast to StepInput[] — the shared `StepInput` union lists neither
|
||||
// `PolyGenStepTemplate` nor the (client-untyped) `model3DPreview` step,
|
||||
// but the orchestrator queue accepts both natively.
|
||||
return [polyGenStep, previewStep] as unknown as StepInput[];
|
||||
});
|
||||
@@ -423,13 +423,14 @@ export const workflowConfigs: WorkflowConfigs = {
|
||||
// Image segment, where txt2img/img2img both title as "Create Image").
|
||||
label: 'Create 3D Model',
|
||||
modeLabel: 'Image to 3D',
|
||||
description: 'Generate a 3D model from a source image (Meshy, Tripo, or Hunyuan3D)',
|
||||
description:
|
||||
'Generate a 3D model from a source image (Meshy, Tripo, Hunyuan3D, Pixal3D, or Trellis.2)',
|
||||
category: 'model3d',
|
||||
// Image-to-3D is supported by all three 3D ecosystems; the user picks one
|
||||
// Image-to-3D is supported by all the 3D ecosystems; the user picks one
|
||||
// via the `BaseModelInput` "Eco" picker. Text-to-3D (txt2model3d) stays
|
||||
// PolyGen-only — Tripo/Hunyuan3D have no text-to-3D operation, and neither
|
||||
// does Meshy v7 (a version inside PolyGen, see polygen-graph.ts).
|
||||
ecosystemIds: [ECO.PolyGen, ECO.Tripo, ECO.Hunyuan3D],
|
||||
// PolyGen-only — Tripo/Hunyuan3D/Pixal3D/Trellis.2 have no text-to-3D
|
||||
// operation, and neither does Meshy v7 (a version inside PolyGen).
|
||||
ecosystemIds: [ECO.PolyGen, ECO.Tripo, ECO.Hunyuan3D, ECO.Pixal3D, ECO.Trellis2],
|
||||
featureFlag: 'model3dGenerator',
|
||||
isNew: true,
|
||||
},
|
||||
|
||||
@@ -81,6 +81,8 @@ import { aceAudioGraph } from './ace-audio-graph';
|
||||
import { polyGenGraph } from './polygen-graph';
|
||||
import { tripoGraph } from './tripo-graph';
|
||||
import { hunyuan3dGraph } from './hunyuan3d-graph';
|
||||
import { pixal3dGraph } from './pixal3d-graph';
|
||||
import { trellis2Graph } from './trellis2-graph';
|
||||
|
||||
// =============================================================================
|
||||
// Helper Functions
|
||||
@@ -143,6 +145,8 @@ type EcosystemGateExt = Pick<
|
||||
const FEATURE_FLAG_GATED_ECOSYSTEMS: Array<{ key: string; flag: keyof FeatureAccess }> = [
|
||||
{ key: 'Tripo', flag: 'tripoGenerator' },
|
||||
{ key: 'Hunyuan3D', flag: 'hunyuan3dGenerator' },
|
||||
{ key: 'Pixal3D', flag: 'pixal3dGenerator' },
|
||||
{ key: 'Trellis2', flag: 'trellis2Generator' },
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -437,6 +441,8 @@ export const ecosystemGraph = new DataGraph<
|
||||
// the active ecosystem isn't the matching one (same pattern as PolyGen).
|
||||
{ values: ['Tripo'] as const, graph: tripoGraph },
|
||||
{ values: ['Hunyuan3D'] as const, graph: hunyuan3dGraph },
|
||||
{ values: ['Pixal3D'] as const, graph: pixal3dGraph },
|
||||
{ values: ['Trellis2'] as const, graph: trellis2Graph },
|
||||
])
|
||||
// Enhanced compatibility mode - txt2img only, supported ecosystems, hidden for Flux Ultra
|
||||
.node(
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Pixal3D Graph
|
||||
*
|
||||
* Controls for the Pixal3D (via Comfy / Trellis2) 3D-model generation
|
||||
* ecosystem. Pixal3D is image-to-3D only, so — like Tripo and Hunyuan3D and
|
||||
* unlike PolyGen (Meshy) — there is no `process` discriminator. It rides the
|
||||
* shared `img2model3d` workflow; the active ecosystem is chosen via the
|
||||
* `BaseModelInput` picker.
|
||||
*
|
||||
* The handler (`pixal3d-graph.handler.ts`) consumes the validated snapshot and
|
||||
* emits a `PolyGenStepTemplate` matching `pixal3d.schema.ts`. Every node name
|
||||
* matches a shared 3D Controller in `GenerationForm.tsx` (`images`,
|
||||
* `shouldTexture`, `shouldRemesh`, `enablePbr`, `seed`), so no Pixal3D-specific
|
||||
* form block is needed — those Controllers auto-show when the active ecosystem
|
||||
* is Pixal3D (same reuse pattern Hunyuan3D uses for its toggles).
|
||||
*/
|
||||
|
||||
import z from 'zod';
|
||||
import { DataGraph } from '~/libs/data-graph/data-graph';
|
||||
import type { GenerationCtx } from './context';
|
||||
import { imagesNode, seedNode } from './common';
|
||||
|
||||
// =============================================================================
|
||||
// Pixal3D Graph
|
||||
// =============================================================================
|
||||
|
||||
type Pixal3dCtx = { ecosystem: string; workflow: string };
|
||||
|
||||
export const pixal3dGraph = new DataGraph<Pixal3dCtx, GenerationCtx>()
|
||||
// Image-to-3D source — the standard `images` node (min/max 1), matching the
|
||||
// polygen graph's img2model3d convention. The handler reads `images[0]`.
|
||||
.node(
|
||||
'images',
|
||||
imagesNode({
|
||||
min: 1,
|
||||
max: 1,
|
||||
label: 'Starting image',
|
||||
description: 'The reference Pixal3D will use to build the 3D mesh',
|
||||
})
|
||||
)
|
||||
.node('shouldTexture', {
|
||||
input: z.boolean().optional(),
|
||||
output: z.boolean(),
|
||||
defaultValue: true,
|
||||
})
|
||||
.node('shouldRemesh', {
|
||||
input: z.boolean().optional(),
|
||||
output: z.boolean(),
|
||||
defaultValue: true,
|
||||
})
|
||||
.node('enablePbr', {
|
||||
input: z.boolean().optional(),
|
||||
output: z.boolean(),
|
||||
defaultValue: false,
|
||||
})
|
||||
.node('seed', seedNode());
|
||||
|
||||
export type Pixal3dGraphCtx = ReturnType<typeof pixal3dGraph.init>;
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Trellis.2 Graph
|
||||
*
|
||||
* Controls for the Trellis.2 (via Comfy) 3D-model generation ecosystem.
|
||||
* Trellis.2 is the base `modelVersion` of the same pipeline Pixal3D rides;
|
||||
* like Pixal3D / Tripo / Hunyuan3D it is image-to-3D only, so there is no
|
||||
* `process` discriminator. It rides the shared `img2model3d` workflow; the
|
||||
* active ecosystem is chosen via the `BaseModelInput` picker.
|
||||
*
|
||||
* The handler (`trellis2-graph.handler.ts`) consumes the validated snapshot and
|
||||
* emits a `PolyGenStepTemplate` matching `trellis2.schema.ts`. Every node name
|
||||
* matches a shared 3D Controller in `GenerationForm.tsx` (`images`,
|
||||
* `shouldTexture`, `shouldRemesh`, `enablePbr`, `seed`), so no Trellis.2-specific
|
||||
* form block is needed — those Controllers auto-show when the active ecosystem
|
||||
* is Trellis2 (same reuse pattern Pixal3D/Hunyuan3D use for their toggles).
|
||||
*/
|
||||
|
||||
import z from 'zod';
|
||||
import { DataGraph } from '~/libs/data-graph/data-graph';
|
||||
import type { GenerationCtx } from './context';
|
||||
import { imagesNode, seedNode } from './common';
|
||||
|
||||
// =============================================================================
|
||||
// Trellis.2 Graph
|
||||
// =============================================================================
|
||||
|
||||
type Trellis2Ctx = { ecosystem: string; workflow: string };
|
||||
|
||||
export const trellis2Graph = new DataGraph<Trellis2Ctx, GenerationCtx>()
|
||||
// Image-to-3D source — the standard `images` node (min/max 1), matching the
|
||||
// polygen graph's img2model3d convention. The handler reads `images[0]`.
|
||||
.node(
|
||||
'images',
|
||||
imagesNode({
|
||||
min: 1,
|
||||
max: 1,
|
||||
label: 'Starting image',
|
||||
description: 'The reference Trellis.2 will use to build the 3D mesh',
|
||||
})
|
||||
)
|
||||
.node('shouldTexture', {
|
||||
input: z.boolean().optional(),
|
||||
output: z.boolean(),
|
||||
defaultValue: true,
|
||||
})
|
||||
.node('shouldRemesh', {
|
||||
input: z.boolean().optional(),
|
||||
output: z.boolean(),
|
||||
defaultValue: true,
|
||||
})
|
||||
.node('enablePbr', {
|
||||
input: z.boolean().optional(),
|
||||
output: z.boolean(),
|
||||
defaultValue: false,
|
||||
})
|
||||
.node('seed', seedNode());
|
||||
|
||||
export type Trellis2GraphCtx = ReturnType<typeof trellis2Graph.init>;
|
||||
Reference in New Issue
Block a user