chore: snapshot workflow migration progress
This commit is contained in:
+610
-18
@@ -1,6 +1,7 @@
|
||||
import api from './client'
|
||||
|
||||
export type OutputTypeWorkflowFamily = 'cad_file' | 'order_line'
|
||||
export type OutputTypeWorkflowRolloutMode = 'legacy_only' | 'shadow' | 'graph'
|
||||
export type OutputTypeArtifactKind =
|
||||
| 'still_image'
|
||||
| 'turntable_video'
|
||||
@@ -10,6 +11,17 @@ export type OutputTypeArtifactKind =
|
||||
| 'package'
|
||||
| 'custom'
|
||||
|
||||
export type OutputTypeInvocationOverrideKey = typeof OUTPUT_TYPE_INVOCATION_OVERRIDE_KEYS[number]
|
||||
export type OutputTypeInvocationOverrides = Partial<Record<OutputTypeInvocationOverrideKey, string | number | boolean>>
|
||||
export type OutputTypeContractIssueSeverity = 'error' | 'warning'
|
||||
export type OutputTypeContractCatalogMap<K extends string, V> = Record<K, V>
|
||||
|
||||
export interface OutputTypeParameterOwnershipCatalog {
|
||||
output_type_profile_keys: string[]
|
||||
template_runtime_keys: string[]
|
||||
workflow_node_keys_by_step: Record<string, string[]>
|
||||
}
|
||||
|
||||
export const OUTPUT_TYPE_INVOCATION_OVERRIDE_KEYS = [
|
||||
'width',
|
||||
'height',
|
||||
@@ -27,8 +39,171 @@ export const OUTPUT_TYPE_INVOCATION_OVERRIDE_KEYS = [
|
||||
'denoising_use_gpu',
|
||||
] as const
|
||||
|
||||
export const IMAGE_OUTPUT_FORMATS = ['png', 'jpg', 'jpeg', 'webp'] as const
|
||||
export const VIDEO_OUTPUT_FORMATS = ['mp4', 'webm', 'mov'] as const
|
||||
export const MODEL_OUTPUT_FORMATS = ['gltf', 'glb', 'stl', 'obj', 'usd', 'usdz'] as const
|
||||
export const BLEND_OUTPUT_FORMATS = ['blend'] as const
|
||||
|
||||
const CAD_FILE_ARTIFACT_KINDS: OutputTypeArtifactKind[] = ['thumbnail_image', 'model_export', 'package', 'custom']
|
||||
const ORDER_LINE_ARTIFACT_KINDS: OutputTypeArtifactKind[] = ['still_image', 'turntable_video', 'blend_asset', 'package', 'custom']
|
||||
const STATIC_RENDER_OVERRIDE_KEYS: OutputTypeInvocationOverrideKey[] = [
|
||||
'width',
|
||||
'height',
|
||||
'engine',
|
||||
'samples',
|
||||
'bg_color',
|
||||
'noise_threshold',
|
||||
'denoiser',
|
||||
'denoising_input_passes',
|
||||
'denoising_prefilter',
|
||||
'denoising_quality',
|
||||
'denoising_use_gpu',
|
||||
]
|
||||
const ANIMATION_OVERRIDE_KEYS: OutputTypeInvocationOverrideKey[] = ['frame_count', 'fps', 'turntable_axis']
|
||||
|
||||
export interface OutputTypeContractCatalog {
|
||||
workflow_families: OutputTypeWorkflowFamily[]
|
||||
workflow_rollout_modes: OutputTypeWorkflowRolloutMode[]
|
||||
artifact_kinds: OutputTypeArtifactKind[]
|
||||
allowed_artifact_kinds_by_family: OutputTypeContractCatalogMap<OutputTypeWorkflowFamily, OutputTypeArtifactKind[]>
|
||||
allowed_output_formats_by_family: OutputTypeContractCatalogMap<OutputTypeWorkflowFamily, string[]>
|
||||
allowed_invocation_override_keys_by_artifact_kind: OutputTypeContractCatalogMap<
|
||||
OutputTypeArtifactKind,
|
||||
OutputTypeInvocationOverrideKey[]
|
||||
>
|
||||
default_output_format_by_artifact_kind: OutputTypeContractCatalogMap<OutputTypeArtifactKind, string>
|
||||
parameter_ownership: OutputTypeParameterOwnershipCatalog
|
||||
}
|
||||
|
||||
const FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG: OutputTypeContractCatalog = {
|
||||
workflow_families: ['order_line', 'cad_file'],
|
||||
workflow_rollout_modes: ['legacy_only', 'shadow', 'graph'],
|
||||
artifact_kinds: ['still_image', 'turntable_video', 'model_export', 'thumbnail_image', 'blend_asset', 'package', 'custom'],
|
||||
allowed_artifact_kinds_by_family: {
|
||||
cad_file: [...CAD_FILE_ARTIFACT_KINDS],
|
||||
order_line: [...ORDER_LINE_ARTIFACT_KINDS],
|
||||
},
|
||||
allowed_output_formats_by_family: {
|
||||
cad_file: [...IMAGE_OUTPUT_FORMATS, ...MODEL_OUTPUT_FORMATS],
|
||||
order_line: [...IMAGE_OUTPUT_FORMATS, ...VIDEO_OUTPUT_FORMATS, ...BLEND_OUTPUT_FORMATS],
|
||||
},
|
||||
allowed_invocation_override_keys_by_artifact_kind: {
|
||||
still_image: [...STATIC_RENDER_OVERRIDE_KEYS],
|
||||
thumbnail_image: [...STATIC_RENDER_OVERRIDE_KEYS],
|
||||
turntable_video: [...STATIC_RENDER_OVERRIDE_KEYS, ...ANIMATION_OVERRIDE_KEYS],
|
||||
model_export: [],
|
||||
blend_asset: [],
|
||||
package: [...OUTPUT_TYPE_INVOCATION_OVERRIDE_KEYS],
|
||||
custom: [...OUTPUT_TYPE_INVOCATION_OVERRIDE_KEYS],
|
||||
},
|
||||
default_output_format_by_artifact_kind: {
|
||||
still_image: 'png',
|
||||
thumbnail_image: 'png',
|
||||
turntable_video: 'mp4',
|
||||
model_export: 'gltf',
|
||||
blend_asset: 'blend',
|
||||
package: 'png',
|
||||
custom: 'png',
|
||||
},
|
||||
parameter_ownership: {
|
||||
output_type_profile_keys: ['transparent_bg', 'cycles_device', 'material_override'],
|
||||
template_runtime_keys: ['target_collection', 'lighting_only', 'shadow_catcher', 'camera_orbit', 'template_inputs'],
|
||||
workflow_node_keys_by_step: {
|
||||
resolve_template: [
|
||||
'template_id_override',
|
||||
'require_template',
|
||||
'material_library_path',
|
||||
'disable_materials',
|
||||
'target_collection',
|
||||
'material_replace_mode',
|
||||
'lighting_only_mode',
|
||||
'shadow_catcher_mode',
|
||||
'camera_orbit_mode',
|
||||
],
|
||||
blender_still: [
|
||||
'use_custom_render_settings',
|
||||
'render_engine',
|
||||
'cycles_device',
|
||||
'samples',
|
||||
'width',
|
||||
'height',
|
||||
'transparent_bg',
|
||||
'noise_threshold',
|
||||
'denoiser',
|
||||
'denoising_input_passes',
|
||||
'denoising_prefilter',
|
||||
'denoising_quality',
|
||||
'denoising_use_gpu',
|
||||
'target_collection',
|
||||
'lighting_only',
|
||||
'shadow_catcher',
|
||||
'rotation_x',
|
||||
'rotation_y',
|
||||
'rotation_z',
|
||||
'focal_length_mm',
|
||||
'sensor_width_mm',
|
||||
'material_override',
|
||||
],
|
||||
blender_turntable: [
|
||||
'use_custom_render_settings',
|
||||
'render_engine',
|
||||
'cycles_device',
|
||||
'samples',
|
||||
'width',
|
||||
'height',
|
||||
'transparent_bg',
|
||||
'bg_color',
|
||||
'fps',
|
||||
'frame_count',
|
||||
'duration_s',
|
||||
'turntable_degrees',
|
||||
'turntable_axis',
|
||||
'camera_orbit',
|
||||
'target_collection',
|
||||
'lighting_only',
|
||||
'shadow_catcher',
|
||||
'rotation_x',
|
||||
'rotation_y',
|
||||
'rotation_z',
|
||||
'focal_length_mm',
|
||||
'sensor_width_mm',
|
||||
'material_override',
|
||||
],
|
||||
export_blend: ['output_name_suffix'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
let cachedOutputTypeContractCatalog: OutputTypeContractCatalog = FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG
|
||||
|
||||
export interface OutputTypeWorkflowContractWorkflowLike {
|
||||
id: string
|
||||
name: string
|
||||
family: OutputTypeWorkflowFamily | 'mixed' | null
|
||||
supported_artifact_kinds?: OutputTypeArtifactKind[]
|
||||
}
|
||||
|
||||
export interface OutputTypeWorkflowContractIssue {
|
||||
code: string
|
||||
severity: OutputTypeContractIssueSeverity
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface OutputTypeInvocationProfile {
|
||||
renderer: string
|
||||
render_backend: string
|
||||
workflow_family: OutputTypeWorkflowFamily
|
||||
artifact_kind: OutputTypeArtifactKind
|
||||
output_format: string
|
||||
is_animation: boolean
|
||||
workflow_definition_id: string | null
|
||||
workflow_rollout_mode: OutputTypeWorkflowRolloutMode
|
||||
transparent_bg: boolean
|
||||
cycles_device: string | null
|
||||
material_override: string | null
|
||||
allowed_override_keys: OutputTypeInvocationOverrideKey[]
|
||||
invocation_overrides: OutputTypeInvocationOverrides
|
||||
}
|
||||
|
||||
export interface OutputType {
|
||||
id: string
|
||||
@@ -36,7 +211,7 @@ export interface OutputType {
|
||||
description: string | null
|
||||
renderer: string
|
||||
render_settings: Record<string, unknown>
|
||||
invocation_overrides: Record<string, unknown>
|
||||
invocation_overrides: OutputTypeInvocationOverrides
|
||||
output_format: string
|
||||
sort_order: number
|
||||
compatible_categories: string[]
|
||||
@@ -50,13 +225,141 @@ export interface OutputType {
|
||||
pricing_tier_name: string | null
|
||||
price_per_item: number | null
|
||||
workflow_definition_id: string | null
|
||||
workflow_rollout_mode: OutputTypeWorkflowRolloutMode
|
||||
workflow_name?: string | null
|
||||
material_override: string | null
|
||||
invocation_profile: OutputTypeInvocationProfile | null
|
||||
is_active: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
function isWorkflowFamily(value: unknown): value is OutputTypeWorkflowFamily {
|
||||
return value === 'cad_file' || value === 'order_line'
|
||||
}
|
||||
|
||||
function isWorkflowRolloutMode(value: unknown): value is OutputTypeWorkflowRolloutMode {
|
||||
return value === 'legacy_only' || value === 'shadow' || value === 'graph'
|
||||
}
|
||||
|
||||
function isArtifactKind(value: unknown): value is OutputTypeArtifactKind {
|
||||
return FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.artifact_kinds.includes(value as OutputTypeArtifactKind)
|
||||
}
|
||||
|
||||
function isInvocationOverrideKey(value: unknown): value is OutputTypeInvocationOverrideKey {
|
||||
return OUTPUT_TYPE_INVOCATION_OVERRIDE_KEYS.includes(value as OutputTypeInvocationOverrideKey)
|
||||
}
|
||||
|
||||
function normalizeOrderedStrings<T extends string>(
|
||||
values: unknown,
|
||||
fallback: readonly T[],
|
||||
predicate: (value: unknown) => value is T,
|
||||
): T[] {
|
||||
const provided = Array.isArray(values) ? values.filter(predicate) : []
|
||||
const usable = provided.length > 0 ? provided : [...fallback]
|
||||
const usableSet = new Set(usable)
|
||||
return fallback.filter(value => usableSet.has(value))
|
||||
}
|
||||
|
||||
function normalizeStringList(values: unknown): string[] {
|
||||
return Array.isArray(values) ? values.filter((value): value is string => typeof value === 'string' && value.trim().length > 0) : []
|
||||
}
|
||||
|
||||
function normalizeRecordOfStringLists(values: unknown): Record<string, string[]> {
|
||||
if (!values || typeof values !== 'object' || Array.isArray(values)) return {}
|
||||
return Object.fromEntries(
|
||||
Object.entries(values).map(([key, value]) => [key, normalizeStringList(value)]),
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeOutputTypeContractCatalog(
|
||||
catalog: Partial<OutputTypeContractCatalog> | undefined | null,
|
||||
): OutputTypeContractCatalog {
|
||||
const workflowFamilies = normalizeOrderedStrings(
|
||||
catalog?.workflow_families,
|
||||
FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.workflow_families,
|
||||
isWorkflowFamily,
|
||||
)
|
||||
const workflowRolloutModes = normalizeOrderedStrings(
|
||||
catalog?.workflow_rollout_modes,
|
||||
FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.workflow_rollout_modes,
|
||||
isWorkflowRolloutMode,
|
||||
)
|
||||
const artifactKinds = normalizeOrderedStrings(
|
||||
catalog?.artifact_kinds,
|
||||
FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.artifact_kinds,
|
||||
isArtifactKind,
|
||||
)
|
||||
|
||||
const allowedArtifactKindsByFamily = Object.fromEntries(
|
||||
workflowFamilies.map(family => [
|
||||
family,
|
||||
normalizeOrderedStrings(
|
||||
catalog?.allowed_artifact_kinds_by_family?.[family],
|
||||
FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.allowed_artifact_kinds_by_family[family],
|
||||
isArtifactKind,
|
||||
),
|
||||
]),
|
||||
) as OutputTypeContractCatalog['allowed_artifact_kinds_by_family']
|
||||
|
||||
const allowedOutputFormatsByFamily = Object.fromEntries(
|
||||
workflowFamilies.map(family => [
|
||||
family,
|
||||
normalizeStringList(catalog?.allowed_output_formats_by_family?.[family]).length > 0
|
||||
? normalizeStringList(catalog?.allowed_output_formats_by_family?.[family])
|
||||
: [...FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.allowed_output_formats_by_family[family]],
|
||||
]),
|
||||
) as OutputTypeContractCatalog['allowed_output_formats_by_family']
|
||||
|
||||
const allowedInvocationOverrideKeysByArtifactKind = Object.fromEntries(
|
||||
artifactKinds.map(artifactKind => [
|
||||
artifactKind,
|
||||
normalizeOrderedStrings(
|
||||
catalog?.allowed_invocation_override_keys_by_artifact_kind?.[artifactKind],
|
||||
FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.allowed_invocation_override_keys_by_artifact_kind[artifactKind],
|
||||
isInvocationOverrideKey,
|
||||
),
|
||||
]),
|
||||
) as OutputTypeContractCatalog['allowed_invocation_override_keys_by_artifact_kind']
|
||||
|
||||
const defaultOutputFormatByArtifactKind = Object.fromEntries(
|
||||
artifactKinds.map(artifactKind => [
|
||||
artifactKind,
|
||||
typeof catalog?.default_output_format_by_artifact_kind?.[artifactKind] === 'string' &&
|
||||
catalog.default_output_format_by_artifact_kind[artifactKind].trim().length > 0
|
||||
? catalog.default_output_format_by_artifact_kind[artifactKind]
|
||||
: FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.default_output_format_by_artifact_kind[artifactKind],
|
||||
]),
|
||||
) as OutputTypeContractCatalog['default_output_format_by_artifact_kind']
|
||||
|
||||
const normalizedNodeOwnership = normalizeRecordOfStringLists(catalog?.parameter_ownership?.workflow_node_keys_by_step)
|
||||
const parameterOwnership: OutputTypeParameterOwnershipCatalog = {
|
||||
output_type_profile_keys:
|
||||
normalizeStringList(catalog?.parameter_ownership?.output_type_profile_keys).length > 0
|
||||
? normalizeStringList(catalog?.parameter_ownership?.output_type_profile_keys)
|
||||
: [...FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.parameter_ownership.output_type_profile_keys],
|
||||
template_runtime_keys:
|
||||
normalizeStringList(catalog?.parameter_ownership?.template_runtime_keys).length > 0
|
||||
? normalizeStringList(catalog?.parameter_ownership?.template_runtime_keys)
|
||||
: [...FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.parameter_ownership.template_runtime_keys],
|
||||
workflow_node_keys_by_step:
|
||||
Object.keys(normalizedNodeOwnership).length > 0
|
||||
? normalizedNodeOwnership
|
||||
: { ...FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.parameter_ownership.workflow_node_keys_by_step },
|
||||
}
|
||||
|
||||
return {
|
||||
workflow_families: workflowFamilies,
|
||||
workflow_rollout_modes: workflowRolloutModes,
|
||||
artifact_kinds: artifactKinds,
|
||||
allowed_artifact_kinds_by_family: allowedArtifactKindsByFamily,
|
||||
allowed_output_formats_by_family: allowedOutputFormatsByFamily,
|
||||
allowed_invocation_override_keys_by_artifact_kind: allowedInvocationOverrideKeysByArtifactKind,
|
||||
default_output_format_by_artifact_kind: defaultOutputFormatByArtifactKind,
|
||||
parameter_ownership: parameterOwnership,
|
||||
}
|
||||
}
|
||||
|
||||
export async function listOutputTypes(
|
||||
includeInactive = false,
|
||||
category?: string,
|
||||
@@ -64,25 +367,151 @@ export async function listOutputTypes(
|
||||
const params: Record<string, unknown> = { include_inactive: includeInactive }
|
||||
if (category) params.category = category
|
||||
const res = await api.get<OutputType[]>('/output-types', { params })
|
||||
return res.data
|
||||
return res.data.map(normalizeOutputType)
|
||||
}
|
||||
|
||||
export async function createOutputType(data: Partial<OutputType>): Promise<OutputType> {
|
||||
const res = await api.post<OutputType>('/output-types', data)
|
||||
return res.data
|
||||
return normalizeOutputType(res.data)
|
||||
}
|
||||
|
||||
export async function updateOutputType(id: string, data: Partial<OutputType>): Promise<OutputType> {
|
||||
const res = await api.patch<OutputType>(`/output-types/${id}`, data)
|
||||
return res.data
|
||||
return normalizeOutputType(res.data)
|
||||
}
|
||||
|
||||
export async function deleteOutputType(id: string): Promise<void> {
|
||||
await api.delete(`/output-types/${id}`)
|
||||
}
|
||||
|
||||
export function listAllowedArtifactKindsForFamily(family: OutputTypeWorkflowFamily): OutputTypeArtifactKind[] {
|
||||
return family === 'cad_file' ? [...CAD_FILE_ARTIFACT_KINDS] : [...ORDER_LINE_ARTIFACT_KINDS]
|
||||
export async function getOutputTypeContractCatalog(): Promise<OutputTypeContractCatalog> {
|
||||
const res = await api.get<OutputTypeContractCatalog>('/output-types/contract-catalog')
|
||||
cachedOutputTypeContractCatalog = normalizeOutputTypeContractCatalog(res.data)
|
||||
return cachedOutputTypeContractCatalog
|
||||
}
|
||||
|
||||
export function getCachedOutputTypeContractCatalog(): OutputTypeContractCatalog {
|
||||
return cachedOutputTypeContractCatalog
|
||||
}
|
||||
|
||||
export function listAllowedArtifactKindsForFamily(
|
||||
family: OutputTypeWorkflowFamily,
|
||||
contractCatalog: OutputTypeContractCatalog = cachedOutputTypeContractCatalog,
|
||||
): OutputTypeArtifactKind[] {
|
||||
return [...(contractCatalog.allowed_artifact_kinds_by_family[family] ?? FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.allowed_artifact_kinds_by_family[family])]
|
||||
}
|
||||
|
||||
export function listAllowedOutputFormatsForFamily(
|
||||
family: OutputTypeWorkflowFamily,
|
||||
contractCatalog: OutputTypeContractCatalog = cachedOutputTypeContractCatalog,
|
||||
): string[] {
|
||||
return [...(contractCatalog.allowed_output_formats_by_family[family] ?? FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.allowed_output_formats_by_family[family])]
|
||||
}
|
||||
|
||||
export function getDefaultOutputFormatForArtifactKind(
|
||||
artifactKind: OutputTypeArtifactKind,
|
||||
contractCatalog: OutputTypeContractCatalog = cachedOutputTypeContractCatalog,
|
||||
): string {
|
||||
return contractCatalog.default_output_format_by_artifact_kind[artifactKind]
|
||||
?? FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.default_output_format_by_artifact_kind[artifactKind]
|
||||
}
|
||||
|
||||
export function workflowSupportsArtifactKindForOutputTypeContract(
|
||||
workflow: OutputTypeWorkflowContractWorkflowLike,
|
||||
artifactKind: OutputTypeArtifactKind,
|
||||
): boolean {
|
||||
return Array.isArray(workflow.supported_artifact_kinds) && workflow.supported_artifact_kinds.includes(artifactKind)
|
||||
}
|
||||
|
||||
export function getCompatibleWorkflowsForOutputTypeContract(
|
||||
workflows: OutputTypeWorkflowContractWorkflowLike[],
|
||||
workflowFamily: OutputTypeWorkflowFamily,
|
||||
artifactKind: OutputTypeArtifactKind,
|
||||
): OutputTypeWorkflowContractWorkflowLike[] {
|
||||
return workflows.filter(workflow =>
|
||||
workflow.family === workflowFamily &&
|
||||
workflowSupportsArtifactKindForOutputTypeContract(workflow, artifactKind),
|
||||
)
|
||||
}
|
||||
|
||||
export function listAllowedInvocationOverrideKeysForArtifactKind(
|
||||
artifactKind: OutputTypeArtifactKind,
|
||||
contractCatalog: OutputTypeContractCatalog = cachedOutputTypeContractCatalog,
|
||||
): OutputTypeInvocationOverrideKey[] {
|
||||
return [
|
||||
...(contractCatalog.allowed_invocation_override_keys_by_artifact_kind[artifactKind]
|
||||
?? FALLBACK_OUTPUT_TYPE_CONTRACT_CATALOG.allowed_invocation_override_keys_by_artifact_kind[artifactKind]),
|
||||
]
|
||||
}
|
||||
|
||||
function isInvocationOverrideValue(value: unknown): value is string | number | boolean {
|
||||
return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
|
||||
}
|
||||
|
||||
function sanitizeInvocationOverrides(
|
||||
artifactKind: OutputTypeArtifactKind,
|
||||
overrides: Record<string, unknown> | undefined | null,
|
||||
contractCatalog: OutputTypeContractCatalog = cachedOutputTypeContractCatalog,
|
||||
): OutputTypeInvocationOverrides {
|
||||
const normalized: OutputTypeInvocationOverrides = {}
|
||||
const allowed = new Set(listAllowedInvocationOverrideKeysForArtifactKind(artifactKind, contractCatalog))
|
||||
for (const key of OUTPUT_TYPE_INVOCATION_OVERRIDE_KEYS) {
|
||||
if (!allowed.has(key)) continue
|
||||
const value = overrides?.[key]
|
||||
if (value !== undefined && value !== null && value !== '' && isInvocationOverrideValue(value)) {
|
||||
normalized[key] = value
|
||||
}
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
function buildFallbackInvocationProfile(outputType: OutputType): OutputTypeInvocationProfile {
|
||||
const artifactKind = outputType.artifact_kind ?? inferArtifactKind(
|
||||
outputType.workflow_family,
|
||||
outputType.output_format,
|
||||
outputType.is_animation,
|
||||
)
|
||||
return {
|
||||
renderer: outputType.renderer,
|
||||
render_backend: outputType.render_backend,
|
||||
workflow_family: outputType.workflow_family,
|
||||
artifact_kind: artifactKind,
|
||||
output_format: outputType.output_format,
|
||||
is_animation: outputType.is_animation,
|
||||
workflow_definition_id: outputType.workflow_definition_id,
|
||||
workflow_rollout_mode: outputType.workflow_rollout_mode ?? 'legacy_only',
|
||||
transparent_bg: outputType.transparent_bg,
|
||||
cycles_device: outputType.cycles_device,
|
||||
material_override: outputType.material_override,
|
||||
allowed_override_keys: listAllowedInvocationOverrideKeysForArtifactKind(artifactKind),
|
||||
invocation_overrides: sanitizeInvocationOverrides(artifactKind, {
|
||||
...outputType.render_settings,
|
||||
...outputType.invocation_overrides,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeOutputType(outputType: OutputType): OutputType {
|
||||
const invocationProfile = outputType.invocation_profile
|
||||
? {
|
||||
...outputType.invocation_profile,
|
||||
workflow_rollout_mode: outputType.invocation_profile.workflow_rollout_mode ?? outputType.workflow_rollout_mode ?? 'legacy_only',
|
||||
allowed_override_keys: outputType.invocation_profile.allowed_override_keys
|
||||
?.filter((key): key is OutputTypeInvocationOverrideKey => OUTPUT_TYPE_INVOCATION_OVERRIDE_KEYS.includes(key as OutputTypeInvocationOverrideKey))
|
||||
?? listAllowedInvocationOverrideKeysForArtifactKind(outputType.artifact_kind),
|
||||
invocation_overrides: sanitizeInvocationOverrides(
|
||||
outputType.invocation_profile.artifact_kind ?? outputType.artifact_kind,
|
||||
outputType.invocation_profile.invocation_overrides,
|
||||
),
|
||||
}
|
||||
: buildFallbackInvocationProfile(outputType)
|
||||
|
||||
return {
|
||||
...outputType,
|
||||
workflow_rollout_mode: outputType.workflow_rollout_mode ?? 'legacy_only',
|
||||
invocation_overrides: invocationProfile.invocation_overrides,
|
||||
invocation_profile: invocationProfile,
|
||||
}
|
||||
}
|
||||
|
||||
export function inferArtifactKind(
|
||||
@@ -92,10 +521,13 @@ export function inferArtifactKind(
|
||||
): OutputTypeArtifactKind {
|
||||
const normalizedFormat = outputFormat.trim().toLowerCase()
|
||||
|
||||
if (isAnimation || ['mp4', 'webm', 'mov'].includes(normalizedFormat)) {
|
||||
if (BLEND_OUTPUT_FORMATS.includes(normalizedFormat as (typeof BLEND_OUTPUT_FORMATS)[number])) {
|
||||
return 'blend_asset'
|
||||
}
|
||||
if (isAnimation || VIDEO_OUTPUT_FORMATS.includes(normalizedFormat as (typeof VIDEO_OUTPUT_FORMATS)[number])) {
|
||||
return 'turntable_video'
|
||||
}
|
||||
if (['gltf', 'glb', 'stl', 'obj', 'usd', 'usdz'].includes(normalizedFormat)) {
|
||||
if (MODEL_OUTPUT_FORMATS.includes(normalizedFormat as (typeof MODEL_OUTPUT_FORMATS)[number])) {
|
||||
return 'model_export'
|
||||
}
|
||||
if (workflowFamily === 'cad_file') {
|
||||
@@ -107,19 +539,179 @@ export function inferArtifactKind(
|
||||
export function isArtifactKindAllowedForFamily(
|
||||
workflowFamily: OutputTypeWorkflowFamily,
|
||||
artifactKind: OutputTypeArtifactKind,
|
||||
contractCatalog: OutputTypeContractCatalog = cachedOutputTypeContractCatalog,
|
||||
): boolean {
|
||||
return listAllowedArtifactKindsForFamily(workflowFamily).includes(artifactKind)
|
||||
return listAllowedArtifactKindsForFamily(workflowFamily, contractCatalog).includes(artifactKind)
|
||||
}
|
||||
|
||||
export function getOutputTypeWorkflowContractIssues(args: {
|
||||
workflowFamily: OutputTypeWorkflowFamily
|
||||
artifactKind: OutputTypeArtifactKind
|
||||
outputFormat: string
|
||||
isAnimation: boolean
|
||||
workflowDefinitionId?: string | null
|
||||
workflowRolloutMode: OutputTypeWorkflowRolloutMode
|
||||
workflows?: OutputTypeWorkflowContractWorkflowLike[]
|
||||
contractCatalog?: OutputTypeContractCatalog
|
||||
}): OutputTypeWorkflowContractIssue[] {
|
||||
const {
|
||||
workflowFamily,
|
||||
artifactKind,
|
||||
outputFormat,
|
||||
isAnimation,
|
||||
workflowDefinitionId,
|
||||
workflowRolloutMode,
|
||||
workflows = [],
|
||||
contractCatalog = cachedOutputTypeContractCatalog,
|
||||
} = args
|
||||
|
||||
const issues: OutputTypeWorkflowContractIssue[] = []
|
||||
const normalizedFormat = outputFormat.trim().toLowerCase()
|
||||
const selectedWorkflowId = workflowDefinitionId?.trim() ?? ''
|
||||
const selectedWorkflow = selectedWorkflowId
|
||||
? workflows.find(workflow => workflow.id === selectedWorkflowId) ?? null
|
||||
: null
|
||||
|
||||
if (!isArtifactKindAllowedForFamily(workflowFamily, artifactKind, contractCatalog)) {
|
||||
issues.push({
|
||||
code: 'artifact_family_mismatch',
|
||||
severity: 'error',
|
||||
message: `${artifactKind} is not allowed for the ${workflowFamily} workflow family.`,
|
||||
})
|
||||
}
|
||||
|
||||
if (normalizedFormat && !listAllowedOutputFormatsForFamily(workflowFamily, contractCatalog).includes(normalizedFormat)) {
|
||||
issues.push({
|
||||
code: 'format_family_mismatch',
|
||||
severity: 'error',
|
||||
message: `${normalizedFormat} is not allowed for the ${workflowFamily} workflow family.`,
|
||||
})
|
||||
}
|
||||
|
||||
if (workflowFamily === 'cad_file' && isAnimation) {
|
||||
issues.push({
|
||||
code: 'cad_animation_unsupported',
|
||||
severity: 'error',
|
||||
message: 'CAD intake workflows do not support animated output types.',
|
||||
})
|
||||
}
|
||||
|
||||
if (artifactKind === 'turntable_video') {
|
||||
if (!isAnimation) {
|
||||
issues.push({
|
||||
code: 'turntable_requires_animation',
|
||||
severity: 'error',
|
||||
message: 'Turntable Video requires animation to be enabled.',
|
||||
})
|
||||
}
|
||||
if (normalizedFormat && !VIDEO_OUTPUT_FORMATS.includes(normalizedFormat as (typeof VIDEO_OUTPUT_FORMATS)[number])) {
|
||||
issues.push({
|
||||
code: 'turntable_requires_video_format',
|
||||
severity: 'error',
|
||||
message: 'Turntable Video requires a video output format.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(artifactKind === 'still_image' || artifactKind === 'thumbnail_image') &&
|
||||
VIDEO_OUTPUT_FORMATS.includes(normalizedFormat as (typeof VIDEO_OUTPUT_FORMATS)[number])
|
||||
) {
|
||||
issues.push({
|
||||
code: 'image_artifact_with_video_format',
|
||||
severity: 'error',
|
||||
message: `${artifactKind} cannot use a video output format.`,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
artifactKind === 'model_export' &&
|
||||
normalizedFormat &&
|
||||
!MODEL_OUTPUT_FORMATS.includes(normalizedFormat as (typeof MODEL_OUTPUT_FORMATS)[number])
|
||||
) {
|
||||
issues.push({
|
||||
code: 'model_export_requires_model_format',
|
||||
severity: 'error',
|
||||
message: 'Model Export requires a 3D export format such as gltf, glb, stl, obj, usd, or usdz.',
|
||||
})
|
||||
}
|
||||
|
||||
if (artifactKind === 'blend_asset') {
|
||||
if (isAnimation) {
|
||||
issues.push({
|
||||
code: 'blend_asset_animation_unsupported',
|
||||
severity: 'error',
|
||||
message: 'Blend Asset does not support animation output.',
|
||||
})
|
||||
}
|
||||
if (normalizedFormat && !BLEND_OUTPUT_FORMATS.includes(normalizedFormat as (typeof BLEND_OUTPUT_FORMATS)[number])) {
|
||||
issues.push({
|
||||
code: 'blend_asset_requires_blend_format',
|
||||
severity: 'error',
|
||||
message: 'Blend Asset requires the blend output format.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
BLEND_OUTPUT_FORMATS.includes(normalizedFormat as (typeof BLEND_OUTPUT_FORMATS)[number]) &&
|
||||
artifactKind !== 'blend_asset'
|
||||
) {
|
||||
issues.push({
|
||||
code: 'blend_format_requires_blend_asset',
|
||||
severity: 'error',
|
||||
message: 'The blend output format requires the Blend Asset artifact kind.',
|
||||
})
|
||||
}
|
||||
|
||||
if (!selectedWorkflowId) {
|
||||
if (workflowRolloutMode !== 'legacy_only') {
|
||||
issues.push({
|
||||
code: 'rollout_requires_workflow',
|
||||
severity: 'error',
|
||||
message: 'Shadow or graph rollout requires a linked workflow definition.',
|
||||
})
|
||||
}
|
||||
return issues
|
||||
}
|
||||
|
||||
if (selectedWorkflow == null) {
|
||||
issues.push({
|
||||
code: 'workflow_missing',
|
||||
severity: 'error',
|
||||
message: 'The selected workflow definition could not be resolved.',
|
||||
})
|
||||
return issues
|
||||
}
|
||||
|
||||
if (selectedWorkflow.family === 'mixed') {
|
||||
issues.push({
|
||||
code: 'workflow_family_mixed',
|
||||
severity: 'error',
|
||||
message: `Workflow "${selectedWorkflow.name}" mixes CAD and order-line nodes and cannot be linked to an output type.`,
|
||||
})
|
||||
} else if (selectedWorkflow.family !== workflowFamily) {
|
||||
issues.push({
|
||||
code: 'workflow_family_mismatch',
|
||||
severity: 'error',
|
||||
message: `Workflow "${selectedWorkflow.name}" belongs to ${selectedWorkflow.family ?? 'an unknown'} family and does not match ${workflowFamily}.`,
|
||||
})
|
||||
}
|
||||
|
||||
if (!workflowSupportsArtifactKindForOutputTypeContract(selectedWorkflow, artifactKind)) {
|
||||
issues.push({
|
||||
code: 'workflow_artifact_mismatch',
|
||||
severity: 'error',
|
||||
message: `Workflow "${selectedWorkflow.name}" does not produce the ${artifactKind} artifact contract.`,
|
||||
})
|
||||
}
|
||||
|
||||
return issues
|
||||
}
|
||||
|
||||
export function getOutputTypeInvocationOverrides(outputType: OutputType): Record<string, unknown> {
|
||||
const normalized: Record<string, unknown> = {}
|
||||
for (const key of OUTPUT_TYPE_INVOCATION_OVERRIDE_KEYS) {
|
||||
const explicitValue = outputType.invocation_overrides?.[key]
|
||||
const legacyValue = outputType.render_settings?.[key]
|
||||
const value = explicitValue ?? legacyValue
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
normalized[key] = value
|
||||
}
|
||||
if (outputType.invocation_profile?.invocation_overrides) {
|
||||
return outputType.invocation_profile.invocation_overrides
|
||||
}
|
||||
return normalized
|
||||
return buildFallbackInvocationProfile(outputType).invocation_overrides
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import api from './client';
|
||||
import type { WorkflowNodeFieldDefinition } from './workflows'
|
||||
|
||||
export interface RenderTemplate {
|
||||
id: string;
|
||||
@@ -15,6 +16,7 @@ export interface RenderTemplate {
|
||||
lighting_only: boolean;
|
||||
shadow_catcher_enabled: boolean;
|
||||
camera_orbit: boolean;
|
||||
workflow_input_schema: WorkflowNodeFieldDefinition[];
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -41,7 +43,7 @@ export async function createRenderTemplate(formData: FormData): Promise<RenderTe
|
||||
|
||||
export async function duplicateRenderTemplate(
|
||||
sourceId: string,
|
||||
overrides: Partial<Pick<RenderTemplate, 'name' | 'category_key' | 'target_collection' | 'material_replace_enabled' | 'lighting_only' | 'shadow_catcher_enabled' | 'camera_orbit'>> & { output_type_ids?: string[] },
|
||||
overrides: Partial<Pick<RenderTemplate, 'name' | 'category_key' | 'target_collection' | 'material_replace_enabled' | 'lighting_only' | 'shadow_catcher_enabled' | 'camera_orbit' | 'workflow_input_schema'>> & { output_type_ids?: string[] },
|
||||
): Promise<RenderTemplate> {
|
||||
const fd = new FormData();
|
||||
fd.append('name', overrides.name || 'Untitled (copy)');
|
||||
@@ -53,6 +55,7 @@ export async function duplicateRenderTemplate(
|
||||
fd.append('lighting_only', String(overrides.lighting_only ?? false));
|
||||
fd.append('shadow_catcher_enabled', String(overrides.shadow_catcher_enabled ?? false));
|
||||
fd.append('camera_orbit', String(overrides.camera_orbit ?? true));
|
||||
fd.append('workflow_input_schema', JSON.stringify(overrides.workflow_input_schema ?? []));
|
||||
const { data } = await api.post<RenderTemplate>('/render-templates', fd, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
@@ -61,7 +64,7 @@ export async function duplicateRenderTemplate(
|
||||
|
||||
export async function updateRenderTemplate(
|
||||
id: string,
|
||||
updates: Partial<Pick<RenderTemplate, 'name' | 'category_key' | 'output_type_ids' | 'target_collection' | 'material_replace_enabled' | 'lighting_only' | 'shadow_catcher_enabled' | 'camera_orbit' | 'is_active'>>,
|
||||
updates: Partial<Pick<RenderTemplate, 'name' | 'category_key' | 'output_type_ids' | 'target_collection' | 'material_replace_enabled' | 'lighting_only' | 'shadow_catcher_enabled' | 'camera_orbit' | 'workflow_input_schema' | 'is_active'>>,
|
||||
): Promise<RenderTemplate> {
|
||||
const { data } = await api.patch<RenderTemplate>(`/render-templates/${id}`, updates);
|
||||
return data;
|
||||
|
||||
+455
-107
@@ -1,8 +1,43 @@
|
||||
import api from './client'
|
||||
import type { OutputTypeArtifactKind, OutputTypeWorkflowRolloutMode } from './outputTypes'
|
||||
|
||||
export type WorkflowPresetType = 'still' | 'still_graph' | 'turntable' | 'multi_angle' | 'still_with_exports' | 'custom'
|
||||
export type WorkflowExecutionMode = 'legacy' | 'graph' | 'shadow'
|
||||
export type WorkflowStarterFamily = 'cad_file' | 'order_line'
|
||||
export type WorkflowBlueprintType = 'cad_intake' | 'order_rendering' | 'still_graph_reference'
|
||||
export type WorkflowCanonicalBlueprintType = WorkflowBlueprintType | 'starter_cad_intake' | 'starter_order_rendering'
|
||||
|
||||
export interface WorkflowRolloutLatestRun {
|
||||
workflow_run_id: string
|
||||
execution_mode: WorkflowExecutionMode
|
||||
status: string
|
||||
created_at: string
|
||||
completed_at: string | null
|
||||
}
|
||||
|
||||
export interface WorkflowRolloutLinkedOutputType {
|
||||
id: string
|
||||
name: string
|
||||
is_active: boolean
|
||||
artifact_kind: OutputTypeArtifactKind
|
||||
workflow_rollout_mode: OutputTypeWorkflowRolloutMode
|
||||
}
|
||||
|
||||
export interface WorkflowRolloutSummary {
|
||||
linked_output_type_count: number
|
||||
active_output_type_count: number
|
||||
linked_output_type_names: string[]
|
||||
linked_output_types: WorkflowRolloutLinkedOutputType[]
|
||||
rollout_modes: ('legacy_only' | 'shadow' | 'graph' | string)[]
|
||||
has_blocking_contracts: boolean
|
||||
blocking_reasons: string[]
|
||||
latest_run: WorkflowRolloutLatestRun | null
|
||||
latest_shadow_run: WorkflowRolloutLatestRun | null
|
||||
latest_rollout_gate_verdict: 'pass' | 'warn' | 'fail' | null
|
||||
latest_rollout_ready: boolean | null
|
||||
latest_rollout_status: 'ready_for_rollout' | 'hold_legacy_authoritative' | string | null
|
||||
latest_rollout_reasons: string[]
|
||||
}
|
||||
|
||||
export interface WorkflowDefinition {
|
||||
id: string
|
||||
@@ -10,6 +45,8 @@ export interface WorkflowDefinition {
|
||||
output_type_id: string | null
|
||||
config: WorkflowConfig
|
||||
family: WorkflowNodeFamily | 'mixed' | null
|
||||
supported_artifact_kinds?: OutputTypeArtifactKind[]
|
||||
rollout_summary: WorkflowRolloutSummary
|
||||
is_active: boolean
|
||||
created_at: string
|
||||
}
|
||||
@@ -132,6 +169,18 @@ export interface WorkflowPreflightResponse {
|
||||
nodes: WorkflowPreflightNode[]
|
||||
}
|
||||
|
||||
export interface WorkflowOrderLineContextOption {
|
||||
value: string
|
||||
label: string
|
||||
meta: string
|
||||
}
|
||||
|
||||
export interface WorkflowOrderLineContextGroup {
|
||||
order_id: string
|
||||
order_label: string
|
||||
options: WorkflowOrderLineContextOption[]
|
||||
}
|
||||
|
||||
export interface WorkflowDraftPreflightRequest {
|
||||
workflow_id?: string | null
|
||||
context_id: string
|
||||
@@ -162,6 +211,11 @@ export interface WorkflowRunComparison {
|
||||
execution_mode: WorkflowExecutionMode
|
||||
status: string
|
||||
summary: string
|
||||
rollout_gate_verdict: 'pass' | 'warn' | 'fail'
|
||||
workflow_rollout_ready: boolean
|
||||
workflow_rollout_status: 'ready_for_rollout' | 'hold_legacy_authoritative'
|
||||
rollout_reasons: string[]
|
||||
rollout_thresholds: Record<string, number>
|
||||
authoritative_output: WorkflowComparisonArtifact
|
||||
observer_output: WorkflowComparisonArtifact
|
||||
exact_match: boolean | null
|
||||
@@ -209,6 +263,9 @@ export const preflightWorkflowDraft = (
|
||||
): Promise<WorkflowPreflightResponse> =>
|
||||
api.post('/workflows/preflight', data).then(r => r.data)
|
||||
|
||||
export const getWorkflowOrderLineContexts = (limit = 50): Promise<WorkflowOrderLineContextGroup[]> =>
|
||||
api.get('/workflows/contexts/order-lines', { params: { limit } }).then(r => r.data)
|
||||
|
||||
export const getWorkflowRunComparison = (runId: string): Promise<WorkflowRunComparison> =>
|
||||
api.get(`/workflows/runs/${runId}/comparison`).then(r => r.data)
|
||||
|
||||
@@ -235,9 +292,12 @@ export interface WorkflowNodeFieldDefinition {
|
||||
step: number | null
|
||||
unit: string | null
|
||||
options: WorkflowNodeFieldOption[]
|
||||
allow_blank?: boolean
|
||||
max_length?: number | null
|
||||
text_format?: string
|
||||
}
|
||||
|
||||
export type WorkflowNodeFamily = 'cad_file' | 'order_line'
|
||||
export type WorkflowNodeFamily = 'cad_file' | 'order_line' | 'shared'
|
||||
|
||||
export interface WorkflowNodeDefinition {
|
||||
step: string
|
||||
@@ -280,32 +340,75 @@ export const getNodeDefinitions = (): Promise<WorkflowNodeDefinitionsResponse> =
|
||||
export const getPipelineSteps = (): Promise<PipelineStepsResponse> =>
|
||||
api.get('/workflows/pipeline-steps').then(r => r.data)
|
||||
|
||||
function buildStillGraphNodes(renderParams: WorkflowParams): { nodes: WorkflowNode[]; edges: WorkflowEdge[] } {
|
||||
function normalizeRenderParams(params: WorkflowParams = {}): WorkflowParams {
|
||||
const normalized = { ...params }
|
||||
const resolution = Array.isArray(normalized.resolution) ? normalized.resolution : undefined
|
||||
if (resolution && resolution.length === 2) {
|
||||
normalized.width = Number(resolution[0])
|
||||
normalized.height = Number(resolution[1])
|
||||
delete normalized.resolution
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
function buildWorkflowNode(
|
||||
id: string,
|
||||
step: string,
|
||||
x: number,
|
||||
y: number,
|
||||
options: {
|
||||
label: string
|
||||
type?: string
|
||||
params?: WorkflowParams
|
||||
},
|
||||
): WorkflowNode {
|
||||
return {
|
||||
id,
|
||||
step,
|
||||
params: { ...(options.params ?? {}) },
|
||||
ui: {
|
||||
type: options.type,
|
||||
label: options.label,
|
||||
position: { x, y },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function extractRenderParamsFromNodes(nodes: WorkflowNode[], step: string): WorkflowParams {
|
||||
const match = nodes.find(node => node.step === step)
|
||||
return normalizeRenderParams(match?.params ?? {})
|
||||
}
|
||||
|
||||
function buildOrderLineStillGraphNodes(renderParams: WorkflowParams): { nodes: WorkflowNode[]; edges: WorkflowEdge[] } {
|
||||
return {
|
||||
nodes: [
|
||||
{ id: 'setup', step: 'order_line_setup', params: {}, ui: { label: 'Order Line Setup', position: { x: 0, y: 160 } } },
|
||||
{ id: 'template', step: 'resolve_template', params: {}, ui: { label: 'Resolve Template', position: { x: 220, y: 160 } } },
|
||||
{
|
||||
id: 'populate_materials',
|
||||
step: 'auto_populate_materials',
|
||||
params: {},
|
||||
ui: { type: 'processNode', label: 'Auto Populate Materials', position: { x: 220, y: 320 } },
|
||||
},
|
||||
{ id: 'bbox', step: 'glb_bbox', params: {}, ui: { type: 'processNode', label: 'Compute Bounding Box', position: { x: 220, y: 40 } } },
|
||||
{
|
||||
id: 'resolve_materials',
|
||||
step: 'material_map_resolve',
|
||||
params: {},
|
||||
ui: { type: 'processNode', label: 'Resolve Material Map', position: { x: 440, y: 200 } },
|
||||
},
|
||||
{
|
||||
id: 'render',
|
||||
step: 'blender_still',
|
||||
params: { use_custom_render_settings: true, ...renderParams },
|
||||
ui: { type: 'renderNode', label: 'Still Render', position: { x: 680, y: 160 } },
|
||||
},
|
||||
{ id: 'output', step: 'output_save', params: {}, ui: { type: 'outputNode', label: 'Save Output', position: { x: 920, y: 120 } } },
|
||||
{ id: 'notify', step: 'notify', params: {}, ui: { type: 'outputNode', label: 'Notify Result', position: { x: 920, y: 220 } } },
|
||||
buildWorkflowNode('setup', 'order_line_setup', 0, 160, { label: 'Order Line Setup' }),
|
||||
buildWorkflowNode('template', 'resolve_template', 220, 160, { label: 'Resolve Template' }),
|
||||
buildWorkflowNode('populate_materials', 'auto_populate_materials', 220, 320, {
|
||||
label: 'Auto Populate Materials',
|
||||
type: 'processNode',
|
||||
}),
|
||||
buildWorkflowNode('bbox', 'glb_bbox', 220, 40, {
|
||||
label: 'Compute Bounding Box',
|
||||
type: 'processNode',
|
||||
}),
|
||||
buildWorkflowNode('resolve_materials', 'material_map_resolve', 440, 200, {
|
||||
label: 'Resolve Material Map',
|
||||
type: 'processNode',
|
||||
}),
|
||||
buildWorkflowNode('render', 'blender_still', 680, 160, {
|
||||
label: 'Still Render',
|
||||
type: 'renderNode',
|
||||
params: { use_custom_render_settings: false, ...renderParams },
|
||||
}),
|
||||
buildWorkflowNode('output', 'output_save', 920, 120, {
|
||||
label: 'Save Output',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
buildWorkflowNode('notify', 'notify', 920, 220, {
|
||||
label: 'Notify Result',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
],
|
||||
edges: [
|
||||
{ from: 'setup', to: 'template' },
|
||||
@@ -322,24 +425,25 @@ function buildStillGraphNodes(renderParams: WorkflowParams): { nodes: WorkflowNo
|
||||
}
|
||||
}
|
||||
|
||||
function migratePresetConfig(type: WorkflowPresetType, params: WorkflowParams = {}): WorkflowConfig {
|
||||
const renderParams = { ...params }
|
||||
const resolution = Array.isArray(renderParams.resolution) ? renderParams.resolution : undefined
|
||||
if (resolution && resolution.length === 2) {
|
||||
renderParams.width = Number(resolution[0])
|
||||
renderParams.height = Number(resolution[1])
|
||||
delete renderParams.resolution
|
||||
}
|
||||
function buildPresetWorkflowConfigInternal(type: WorkflowPresetType, params: WorkflowParams = {}): WorkflowConfig {
|
||||
const renderParams = normalizeRenderParams(params)
|
||||
|
||||
if (type === 'still') {
|
||||
return {
|
||||
version: 1,
|
||||
ui: { preset: type, execution_mode: 'legacy', family: 'order_line' },
|
||||
nodes: [
|
||||
{ id: 'setup', step: 'order_line_setup', params: {}, ui: { label: 'Order Line Setup', position: { x: 0, y: 100 } } },
|
||||
{ id: 'template', step: 'resolve_template', params: {}, ui: { label: 'Resolve Template', position: { x: 220, y: 100 } } },
|
||||
{ id: 'render', step: 'blender_still', params: renderParams, ui: { type: 'renderNode', label: 'Still Render', position: { x: 440, y: 100 } } },
|
||||
{ id: 'output', step: 'output_save', params: {}, ui: { type: 'outputNode', label: 'Save Output', position: { x: 660, y: 100 } } },
|
||||
buildWorkflowNode('setup', 'order_line_setup', 0, 100, { label: 'Order Line Setup' }),
|
||||
buildWorkflowNode('template', 'resolve_template', 220, 100, { label: 'Resolve Template' }),
|
||||
buildWorkflowNode('render', 'blender_still', 440, 100, {
|
||||
label: 'Still Render',
|
||||
type: 'renderNode',
|
||||
params: renderParams,
|
||||
}),
|
||||
buildWorkflowNode('output', 'output_save', 660, 100, {
|
||||
label: 'Save Output',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
],
|
||||
edges: [
|
||||
{ from: 'setup', to: 'template' },
|
||||
@@ -350,7 +454,7 @@ function migratePresetConfig(type: WorkflowPresetType, params: WorkflowParams =
|
||||
}
|
||||
|
||||
if (type === 'still_graph') {
|
||||
const { nodes, edges } = buildStillGraphNodes(renderParams)
|
||||
const { nodes, edges } = buildOrderLineStillGraphNodes(renderParams)
|
||||
return {
|
||||
version: 1,
|
||||
ui: { preset: type, execution_mode: 'graph', family: 'order_line' },
|
||||
@@ -364,10 +468,17 @@ function migratePresetConfig(type: WorkflowPresetType, params: WorkflowParams =
|
||||
version: 1,
|
||||
ui: { preset: type, execution_mode: 'legacy', family: 'order_line' },
|
||||
nodes: [
|
||||
{ id: 'setup', step: 'order_line_setup', params: {}, ui: { label: 'Order Line Setup', position: { x: 0, y: 100 } } },
|
||||
{ id: 'template', step: 'resolve_template', params: {}, ui: { label: 'Resolve Template', position: { x: 220, y: 100 } } },
|
||||
{ id: 'turntable', step: 'blender_turntable', params: renderParams, ui: { type: 'renderFramesNode', label: 'Turntable Render', position: { x: 440, y: 100 } } },
|
||||
{ id: 'output', step: 'output_save', params: {}, ui: { type: 'outputNode', label: 'Save Output', position: { x: 660, y: 100 } } },
|
||||
buildWorkflowNode('setup', 'order_line_setup', 0, 100, { label: 'Order Line Setup' }),
|
||||
buildWorkflowNode('template', 'resolve_template', 220, 100, { label: 'Resolve Template' }),
|
||||
buildWorkflowNode('turntable', 'blender_turntable', 440, 100, {
|
||||
label: 'Turntable Render',
|
||||
type: 'renderFramesNode',
|
||||
params: renderParams,
|
||||
}),
|
||||
buildWorkflowNode('output', 'output_save', 660, 100, {
|
||||
label: 'Save Output',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
],
|
||||
edges: [
|
||||
{ from: 'setup', to: 'template' },
|
||||
@@ -385,15 +496,19 @@ function migratePresetConfig(type: WorkflowPresetType, params: WorkflowParams =
|
||||
version: 1,
|
||||
ui: { preset: type, execution_mode: 'legacy', family: 'order_line' },
|
||||
nodes: [
|
||||
{ id: 'setup', step: 'order_line_setup', params: {}, ui: { label: 'Order Line Setup', position: { x: 0, y: 195 } } },
|
||||
{ id: 'template', step: 'resolve_template', params: {}, ui: { label: 'Resolve Template', position: { x: 220, y: 195 } } },
|
||||
...angles.map((angle, index) => ({
|
||||
id: `render_${index}`,
|
||||
step: 'blender_still',
|
||||
params: { ...sharedParams, rotation_z: angle },
|
||||
ui: { type: 'renderNode', label: `Render ${angle}°`, position: { x: 440, y: index * 130 } },
|
||||
})),
|
||||
{ id: 'output', step: 'output_save', params: {}, ui: { type: 'outputNode', label: 'Save Output', position: { x: 700, y: 195 } } },
|
||||
buildWorkflowNode('setup', 'order_line_setup', 0, 195, { label: 'Order Line Setup' }),
|
||||
buildWorkflowNode('template', 'resolve_template', 220, 195, { label: 'Resolve Template' }),
|
||||
...angles.map((angle, index) =>
|
||||
buildWorkflowNode(`render_${index}`, 'blender_still', 440, index * 130, {
|
||||
label: `Render ${angle}°`,
|
||||
type: 'renderNode',
|
||||
params: { ...sharedParams, rotation_z: angle },
|
||||
}),
|
||||
),
|
||||
buildWorkflowNode('output', 'output_save', 700, 195, {
|
||||
label: 'Save Output',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
],
|
||||
edges: [
|
||||
{ from: 'setup', to: 'template' },
|
||||
@@ -408,11 +523,21 @@ function migratePresetConfig(type: WorkflowPresetType, params: WorkflowParams =
|
||||
version: 1,
|
||||
ui: { preset: type, execution_mode: 'legacy', family: 'order_line' },
|
||||
nodes: [
|
||||
{ id: 'setup', step: 'order_line_setup', params: {}, ui: { label: 'Order Line Setup', position: { x: 0, y: 100 } } },
|
||||
{ id: 'template', step: 'resolve_template', params: {}, ui: { label: 'Resolve Template', position: { x: 220, y: 100 } } },
|
||||
{ id: 'render', step: 'blender_still', params: renderParams, ui: { type: 'renderNode', label: 'Still Render', position: { x: 440, y: 100 } } },
|
||||
{ id: 'output', step: 'output_save', params: {}, ui: { type: 'outputNode', label: 'Save Output', position: { x: 660, y: 70 } } },
|
||||
{ id: 'blend', step: 'export_blend', params: {}, ui: { type: 'outputNode', label: 'Export Blend', position: { x: 660, y: 160 } } },
|
||||
buildWorkflowNode('setup', 'order_line_setup', 0, 100, { label: 'Order Line Setup' }),
|
||||
buildWorkflowNode('template', 'resolve_template', 220, 100, { label: 'Resolve Template' }),
|
||||
buildWorkflowNode('render', 'blender_still', 440, 100, {
|
||||
label: 'Still Render',
|
||||
type: 'renderNode',
|
||||
params: renderParams,
|
||||
}),
|
||||
buildWorkflowNode('output', 'output_save', 660, 70, {
|
||||
label: 'Save Output',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
buildWorkflowNode('blend', 'export_blend', 660, 160, {
|
||||
label: 'Export Blend',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
],
|
||||
edges: [
|
||||
{ from: 'setup', to: 'template' },
|
||||
@@ -427,22 +552,245 @@ function migratePresetConfig(type: WorkflowPresetType, params: WorkflowParams =
|
||||
version: 1,
|
||||
ui: { preset: 'custom', execution_mode: 'legacy', family: 'order_line' },
|
||||
nodes: [
|
||||
{
|
||||
id: 'setup',
|
||||
step: 'order_line_setup',
|
||||
params: {},
|
||||
ui: { label: 'Order Line Setup', position: { x: 120, y: 140 } },
|
||||
},
|
||||
buildWorkflowNode('setup', 'order_line_setup', 120, 140, {
|
||||
label: 'Order Line Setup',
|
||||
type: 'processNode',
|
||||
}),
|
||||
],
|
||||
edges: [],
|
||||
}
|
||||
}
|
||||
|
||||
export function buildWorkflowBlueprintConfig(blueprint: WorkflowBlueprintType): WorkflowConfig {
|
||||
if (blueprint === 'cad_intake') {
|
||||
return {
|
||||
version: 1,
|
||||
ui: { preset: 'custom', execution_mode: 'legacy', family: 'cad_file', blueprint },
|
||||
nodes: [
|
||||
buildWorkflowNode('resolve_step', 'resolve_step_path', 0, 180, { label: 'Resolve STEP Path' }),
|
||||
buildWorkflowNode('extract_objects', 'occ_object_extract', 220, 180, {
|
||||
label: 'Extract STEP Objects',
|
||||
}),
|
||||
buildWorkflowNode('export_glb', 'occ_glb_export', 440, 180, { label: 'Export GLB' }),
|
||||
buildWorkflowNode('bbox', 'glb_bbox', 660, 120, {
|
||||
label: 'Compute Bounding Box',
|
||||
type: 'processNode',
|
||||
}),
|
||||
buildWorkflowNode('stl_cache', 'stl_cache_generate', 660, 300, { label: 'Generate STL Cache' }),
|
||||
buildWorkflowNode('blender_thumb', 'blender_render', 880, 120, {
|
||||
label: 'Render Thumbnail (Blender)',
|
||||
type: 'renderNode',
|
||||
params: { render_engine: 'cycles', samples: 64, width: 512, height: 512 },
|
||||
}),
|
||||
buildWorkflowNode('threejs_thumb', 'threejs_render', 880, 320, {
|
||||
label: 'Render Thumbnail (Three.js)',
|
||||
type: 'renderNode',
|
||||
params: { width: 512, height: 512, transparent_bg: true },
|
||||
}),
|
||||
buildWorkflowNode('save_blender_thumb', 'thumbnail_save', 1100, 120, {
|
||||
label: 'Save Blender Thumbnail',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
buildWorkflowNode('save_threejs_thumb', 'thumbnail_save', 1100, 320, {
|
||||
label: 'Save Three.js Thumbnail',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
],
|
||||
edges: [
|
||||
{ from: 'resolve_step', to: 'extract_objects' },
|
||||
{ from: 'extract_objects', to: 'export_glb' },
|
||||
{ from: 'export_glb', to: 'bbox' },
|
||||
{ from: 'export_glb', to: 'stl_cache' },
|
||||
{ from: 'export_glb', to: 'blender_thumb' },
|
||||
{ from: 'export_glb', to: 'threejs_thumb' },
|
||||
{ from: 'bbox', to: 'threejs_thumb' },
|
||||
{ from: 'blender_thumb', to: 'save_blender_thumb' },
|
||||
{ from: 'threejs_thumb', to: 'save_threejs_thumb' },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
if (blueprint === 'order_rendering') {
|
||||
return {
|
||||
version: 1,
|
||||
ui: { preset: 'custom', execution_mode: 'legacy', family: 'order_line', blueprint },
|
||||
nodes: [
|
||||
buildWorkflowNode('setup', 'order_line_setup', 0, 220, { label: 'Order Line Setup' }),
|
||||
buildWorkflowNode('template', 'resolve_template', 220, 220, { label: 'Resolve Template' }),
|
||||
buildWorkflowNode('populate_materials', 'auto_populate_materials', 220, 360, {
|
||||
label: 'Auto Populate Materials',
|
||||
}),
|
||||
buildWorkflowNode('bbox', 'glb_bbox', 220, 80, { label: 'Compute Bounding Box' }),
|
||||
buildWorkflowNode('resolve_materials', 'material_map_resolve', 440, 220, {
|
||||
label: 'Resolve Material Map',
|
||||
}),
|
||||
buildWorkflowNode('still_render', 'blender_still', 680, 80, {
|
||||
label: 'Render Still',
|
||||
type: 'renderNode',
|
||||
params: { rotation_z: 0 },
|
||||
}),
|
||||
buildWorkflowNode('turntable_render', 'blender_turntable', 680, 220, {
|
||||
label: 'Render Turntable',
|
||||
type: 'renderFramesNode',
|
||||
params: { fps: 24, duration_s: 5 },
|
||||
}),
|
||||
buildWorkflowNode('blend_export', 'export_blend', 680, 360, {
|
||||
label: 'Export Blend',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
buildWorkflowNode('save_still', 'output_save', 920, 80, {
|
||||
label: 'Save Still Output',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
buildWorkflowNode('save_turntable', 'output_save', 920, 220, {
|
||||
label: 'Save Turntable Output',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
buildWorkflowNode('notify_still', 'notify', 920, 140, {
|
||||
label: 'Notify Still Result',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
buildWorkflowNode('notify_turntable', 'notify', 920, 280, {
|
||||
label: 'Notify Turntable Result',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
buildWorkflowNode('notify_export', 'notify', 920, 360, {
|
||||
label: 'Notify Blend Export',
|
||||
type: 'outputNode',
|
||||
}),
|
||||
],
|
||||
edges: [
|
||||
{ from: 'setup', to: 'template' },
|
||||
{ from: 'setup', to: 'populate_materials' },
|
||||
{ from: 'setup', to: 'bbox' },
|
||||
{ from: 'template', to: 'resolve_materials' },
|
||||
{ from: 'populate_materials', to: 'resolve_materials' },
|
||||
{ from: 'resolve_materials', to: 'still_render' },
|
||||
{ from: 'resolve_materials', to: 'turntable_render' },
|
||||
{ from: 'bbox', to: 'still_render' },
|
||||
{ from: 'bbox', to: 'turntable_render' },
|
||||
{ from: 'template', to: 'still_render' },
|
||||
{ from: 'template', to: 'turntable_render' },
|
||||
{ from: 'template', to: 'blend_export' },
|
||||
{ from: 'still_render', to: 'save_still' },
|
||||
{ from: 'still_render', to: 'notify_still' },
|
||||
{ from: 'turntable_render', to: 'save_turntable' },
|
||||
{ from: 'turntable_render', to: 'notify_turntable' },
|
||||
{ from: 'blend_export', to: 'notify_export' },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const { nodes, edges } = buildOrderLineStillGraphNodes({
|
||||
render_engine: 'cycles',
|
||||
samples: 256,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
})
|
||||
|
||||
return {
|
||||
version: 1,
|
||||
ui: { preset: 'custom', execution_mode: 'graph', family: 'order_line', blueprint },
|
||||
nodes,
|
||||
edges,
|
||||
}
|
||||
}
|
||||
|
||||
function buildStarterWorkflowConfigInternal(family: WorkflowStarterFamily = 'order_line'): WorkflowConfig {
|
||||
if (family === 'cad_file') {
|
||||
return {
|
||||
version: 1,
|
||||
ui: {
|
||||
preset: 'custom',
|
||||
execution_mode: 'legacy',
|
||||
family: 'cad_file',
|
||||
blueprint: 'starter_cad_intake',
|
||||
},
|
||||
nodes: [
|
||||
buildWorkflowNode('resolve_step', 'resolve_step_path', 120, 140, {
|
||||
label: 'Resolve STEP Path',
|
||||
type: 'inputNode',
|
||||
}),
|
||||
],
|
||||
edges: [],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
version: 1,
|
||||
ui: {
|
||||
preset: 'custom',
|
||||
execution_mode: 'legacy',
|
||||
family: 'order_line',
|
||||
blueprint: 'starter_order_rendering',
|
||||
},
|
||||
nodes: [
|
||||
buildWorkflowNode('setup', 'order_line_setup', 120, 140, {
|
||||
label: 'Order Line Setup',
|
||||
type: 'processNode',
|
||||
}),
|
||||
],
|
||||
edges: [],
|
||||
}
|
||||
}
|
||||
|
||||
export function buildStillGraphNodes(renderParams: WorkflowParams): { nodes: WorkflowNode[]; edges: WorkflowEdge[] } {
|
||||
return buildOrderLineStillGraphNodes(normalizeRenderParams(renderParams))
|
||||
}
|
||||
|
||||
function migratePresetConfig(type: WorkflowPresetType, params: WorkflowParams = {}): WorkflowConfig {
|
||||
return buildPresetWorkflowConfigInternal(type, params)
|
||||
}
|
||||
|
||||
function normalizeWorkflowDefinition(raw: WorkflowDefinition): WorkflowDefinition {
|
||||
const config = normalizeWorkflowConfig(raw.config as unknown as Record<string, unknown>)
|
||||
return {
|
||||
...raw,
|
||||
family: raw.family ?? inferWorkflowFamily(config),
|
||||
supported_artifact_kinds: Array.isArray(raw.supported_artifact_kinds)
|
||||
? raw.supported_artifact_kinds
|
||||
: [],
|
||||
rollout_summary: {
|
||||
linked_output_type_count: Number(raw.rollout_summary?.linked_output_type_count ?? 0),
|
||||
active_output_type_count: Number(raw.rollout_summary?.active_output_type_count ?? 0),
|
||||
linked_output_type_names: Array.isArray(raw.rollout_summary?.linked_output_type_names)
|
||||
? raw.rollout_summary.linked_output_type_names
|
||||
: [],
|
||||
linked_output_types: Array.isArray(raw.rollout_summary?.linked_output_types)
|
||||
? raw.rollout_summary.linked_output_types
|
||||
.filter((outputType): outputType is WorkflowRolloutLinkedOutputType => (
|
||||
outputType != null
|
||||
&& typeof outputType === 'object'
|
||||
&& typeof outputType.id === 'string'
|
||||
&& typeof outputType.name === 'string'
|
||||
))
|
||||
.map(outputType => ({
|
||||
id: outputType.id,
|
||||
name: outputType.name,
|
||||
is_active: Boolean(outputType.is_active),
|
||||
artifact_kind: outputType.artifact_kind,
|
||||
workflow_rollout_mode: outputType.workflow_rollout_mode ?? 'legacy_only',
|
||||
}))
|
||||
: [],
|
||||
rollout_modes: Array.isArray(raw.rollout_summary?.rollout_modes)
|
||||
? raw.rollout_summary.rollout_modes
|
||||
: [],
|
||||
has_blocking_contracts: Boolean(raw.rollout_summary?.has_blocking_contracts),
|
||||
blocking_reasons: Array.isArray(raw.rollout_summary?.blocking_reasons)
|
||||
? raw.rollout_summary.blocking_reasons
|
||||
: [],
|
||||
latest_run: raw.rollout_summary?.latest_run ?? null,
|
||||
latest_shadow_run: raw.rollout_summary?.latest_shadow_run ?? null,
|
||||
latest_rollout_gate_verdict: raw.rollout_summary?.latest_rollout_gate_verdict ?? null,
|
||||
latest_rollout_ready:
|
||||
typeof raw.rollout_summary?.latest_rollout_ready === 'boolean'
|
||||
? raw.rollout_summary.latest_rollout_ready
|
||||
: null,
|
||||
latest_rollout_status: raw.rollout_summary?.latest_rollout_status ?? null,
|
||||
latest_rollout_reasons: Array.isArray(raw.rollout_summary?.latest_rollout_reasons)
|
||||
? raw.rollout_summary.latest_rollout_reasons
|
||||
: [],
|
||||
},
|
||||
config,
|
||||
}
|
||||
}
|
||||
@@ -455,14 +803,51 @@ export function normalizeWorkflowConfig(raw: Record<string, unknown>): WorkflowC
|
||||
params: { ...(node.params ?? {}) },
|
||||
}))
|
||||
const edges = Array.isArray(raw.edges) ? (raw.edges as WorkflowEdge[]) : []
|
||||
const mergedUi = {
|
||||
...rawUi,
|
||||
execution_mode: rawUi.execution_mode ?? 'legacy',
|
||||
}
|
||||
|
||||
if (rawUi.preset === 'still_graph') {
|
||||
const canonical = buildPresetWorkflowConfigInternal('still_graph', extractRenderParamsFromNodes(nodes, 'blender_still'))
|
||||
return {
|
||||
...canonical,
|
||||
ui: {
|
||||
...canonical.ui,
|
||||
...mergedUi,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (rawUi.blueprint === 'cad_intake' || rawUi.blueprint === 'order_rendering' || rawUi.blueprint === 'still_graph_reference') {
|
||||
const canonical = buildWorkflowBlueprintConfig(rawUi.blueprint)
|
||||
return {
|
||||
...canonical,
|
||||
ui: {
|
||||
...canonical.ui,
|
||||
...mergedUi,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (rawUi.blueprint === 'starter_cad_intake' || rawUi.blueprint === 'starter_order_rendering') {
|
||||
const canonical = buildStarterWorkflowConfigInternal(rawUi.blueprint === 'starter_cad_intake' ? 'cad_file' : 'order_line')
|
||||
return {
|
||||
...canonical,
|
||||
ui: {
|
||||
...canonical.ui,
|
||||
...mergedUi,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
version: Number(raw.version ?? 1),
|
||||
nodes,
|
||||
edges,
|
||||
ui: {
|
||||
...rawUi,
|
||||
execution_mode: rawUi.execution_mode ?? 'legacy',
|
||||
family: rawUi.family ?? inferWorkflowFamily({ version: Number(raw.version ?? 1), nodes, edges }),
|
||||
...mergedUi,
|
||||
family: rawUi.family ?? inferWorkflowFamily({ version: Number(raw.version ?? 1), nodes, edges }) ?? undefined,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -480,49 +865,11 @@ export function normalizeWorkflowConfig(raw: Record<string, unknown>): WorkflowC
|
||||
}
|
||||
|
||||
export function createPresetWorkflowConfig(type: WorkflowPresetType, params: WorkflowParams = {}): WorkflowConfig {
|
||||
return migratePresetConfig(type, params)
|
||||
return buildPresetWorkflowConfigInternal(type, params)
|
||||
}
|
||||
|
||||
export function createStarterWorkflowConfig(family: WorkflowStarterFamily = 'order_line'): WorkflowConfig {
|
||||
if (family === 'cad_file') {
|
||||
return {
|
||||
version: 1,
|
||||
ui: {
|
||||
preset: 'custom',
|
||||
execution_mode: 'legacy',
|
||||
family: 'cad_file',
|
||||
blueprint: 'starter_cad_intake',
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 'resolve_step',
|
||||
step: 'resolve_step_path',
|
||||
params: {},
|
||||
ui: { type: 'inputNode', label: 'Resolve STEP Path', position: { x: 120, y: 140 } },
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
version: 1,
|
||||
ui: {
|
||||
preset: 'custom',
|
||||
execution_mode: 'legacy',
|
||||
family: 'order_line',
|
||||
blueprint: 'starter_order_rendering',
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 'setup',
|
||||
step: 'order_line_setup',
|
||||
params: {},
|
||||
ui: { type: 'processNode', label: 'Order Line Setup', position: { x: 120, y: 140 } },
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
}
|
||||
return buildStarterWorkflowConfigInternal(family)
|
||||
}
|
||||
|
||||
export function getWorkflowPresetType(config: WorkflowConfig): WorkflowPresetType {
|
||||
@@ -542,11 +889,12 @@ export function inferWorkflowFamily(config: WorkflowConfig): WorkflowNodeFamily
|
||||
case 'threejs_render':
|
||||
case 'thumbnail_save':
|
||||
return 'cad_file'
|
||||
case 'glb_bbox':
|
||||
return null
|
||||
case 'order_line_setup':
|
||||
case 'resolve_template':
|
||||
case 'material_map_resolve':
|
||||
case 'auto_populate_materials':
|
||||
case 'glb_bbox':
|
||||
case 'blender_still':
|
||||
case 'blender_turntable':
|
||||
case 'output_save':
|
||||
@@ -557,7 +905,7 @@ export function inferWorkflowFamily(config: WorkflowConfig): WorkflowNodeFamily
|
||||
return null
|
||||
}
|
||||
})
|
||||
.filter((family): family is WorkflowNodeFamily => family !== null),
|
||||
.filter((family): family is Exclude<WorkflowNodeFamily, 'shared'> => family !== null),
|
||||
)
|
||||
if (families.size === 0) return null
|
||||
if (families.size > 1) return 'mixed'
|
||||
|
||||
Reference in New Issue
Block a user