chore: snapshot workflow migration progress
This commit is contained in:
@@ -10,7 +10,17 @@ from app.core.process_steps import StepName
|
||||
StepCategory = Literal["input", "processing", "rendering", "output"]
|
||||
FieldType = Literal["number", "select", "boolean", "text"]
|
||||
ExecutionKind = Literal["native", "bridge"]
|
||||
WorkflowNodeFamily = Literal["cad_file", "order_line"]
|
||||
WorkflowNodeFamily = Literal["cad_file", "order_line", "shared"]
|
||||
TextFormat = Literal[
|
||||
"plain",
|
||||
"uuid",
|
||||
"absolute_path",
|
||||
"absolute_blend_path",
|
||||
"absolute_glb_path",
|
||||
"float_string",
|
||||
"hex_color",
|
||||
"safe_filename_suffix",
|
||||
]
|
||||
|
||||
|
||||
class WorkflowNodeFieldOption(BaseModel):
|
||||
@@ -30,6 +40,9 @@ class WorkflowNodeFieldDefinition(BaseModel):
|
||||
step: float | None = None
|
||||
unit: str | None = None
|
||||
options: list[WorkflowNodeFieldOption] = []
|
||||
allow_blank: bool = True
|
||||
max_length: int | None = None
|
||||
text_format: TextFormat = "plain"
|
||||
|
||||
|
||||
class WorkflowNodeDefinition(BaseModel):
|
||||
@@ -65,6 +78,9 @@ def _field(
|
||||
step: float | None = None,
|
||||
unit: str | None = None,
|
||||
options: list[tuple[str | int | float | bool, str]] | None = None,
|
||||
allow_blank: bool = True,
|
||||
max_length: int | None = None,
|
||||
text_format: TextFormat = "plain",
|
||||
) -> WorkflowNodeFieldDefinition:
|
||||
return WorkflowNodeFieldDefinition(
|
||||
key=key,
|
||||
@@ -81,6 +97,9 @@ def _field(
|
||||
WorkflowNodeFieldOption(value=value, label=option_label)
|
||||
for value, option_label in (options or [])
|
||||
],
|
||||
allow_blank=allow_blank,
|
||||
max_length=max_length,
|
||||
text_format=text_format,
|
||||
)
|
||||
|
||||
|
||||
@@ -169,7 +188,7 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
"cad_file",
|
||||
"cad.export_glb",
|
||||
"processing",
|
||||
"Convert STEP geometry into GLB for previews and downstream rendering.",
|
||||
"Convert STEP geometry into GLB for previews and downstream rendering. Uses the system tessellation profile; this node does not expose per-node overrides yet.",
|
||||
node_type="processNode",
|
||||
icon="refresh-cw",
|
||||
execution_kind="bridge",
|
||||
@@ -181,10 +200,10 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
_definition(
|
||||
StepName.GLB_BBOX,
|
||||
"Compute Bounding Box",
|
||||
"order_line",
|
||||
"shared",
|
||||
"geometry.compute_bbox",
|
||||
"processing",
|
||||
"Compute the model bounding box from the exported GLB for framing decisions.",
|
||||
"Compute the model bounding box from a prepared GLB artifact for framing decisions in either CAD-intake or order-line workflows.",
|
||||
node_type="processNode",
|
||||
icon="layers",
|
||||
execution_kind="bridge",
|
||||
@@ -196,10 +215,24 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
description="Optional absolute path to a specific GLB file. Leave empty to reuse the prepared preview/export artifact automatically.",
|
||||
section="Inputs",
|
||||
default="",
|
||||
text_format="absolute_glb_path",
|
||||
),
|
||||
_field(
|
||||
"source_preference",
|
||||
"Source Preference",
|
||||
"select",
|
||||
description="Prefer a prepared GLB, force STEP fallback, or fail when no GLB artifact is available.",
|
||||
section="Inputs",
|
||||
default="auto",
|
||||
options=[
|
||||
("auto", "Auto"),
|
||||
("step_only", "STEP Only"),
|
||||
("glb_only", "GLB Only"),
|
||||
],
|
||||
),
|
||||
],
|
||||
input_contract={"context": "order_line", "requires": ["glb_preview"]},
|
||||
output_contract={"context": "order_line", "provides": ["bbox"]},
|
||||
input_contract={"requires": ["glb_preview"]},
|
||||
output_contract={"provides": ["bbox"]},
|
||||
artifact_roles_consumed=["glb_preview"],
|
||||
artifact_roles_produced=["bbox"],
|
||||
),
|
||||
@@ -213,6 +246,25 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
node_type="processNode",
|
||||
icon="layers",
|
||||
execution_kind="bridge",
|
||||
defaults={"disable_materials": False, "material_override": ""},
|
||||
fields=[
|
||||
_field(
|
||||
"disable_materials",
|
||||
"Disable Materials",
|
||||
"boolean",
|
||||
description="Bypass template and alias-based material mapping for this node.",
|
||||
section="Materials",
|
||||
default=False,
|
||||
),
|
||||
_field(
|
||||
"material_override",
|
||||
"Material Override",
|
||||
"text",
|
||||
description="Optional material name forced onto every detected part before rendering.",
|
||||
section="Materials",
|
||||
default="",
|
||||
),
|
||||
],
|
||||
input_contract={"context": "order_line", "requires": ["order_line_context", "cad_materials"]},
|
||||
output_contract={"context": "order_line", "provides": ["material_assignments"]},
|
||||
artifact_roles_consumed=["order_line_context", "cad_materials"],
|
||||
@@ -228,6 +280,37 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
node_type="processNode",
|
||||
icon="layers",
|
||||
execution_kind="bridge",
|
||||
defaults={
|
||||
"persist_updates": True,
|
||||
"refresh_material_source": True,
|
||||
"include_populated_products": False,
|
||||
},
|
||||
fields=[
|
||||
_field(
|
||||
"persist_updates",
|
||||
"Persist Updates",
|
||||
"boolean",
|
||||
description="Write discovered part-material mappings back to product records in graph mode.",
|
||||
section="Behavior",
|
||||
default=True,
|
||||
),
|
||||
_field(
|
||||
"refresh_material_source",
|
||||
"Refresh Material Source",
|
||||
"boolean",
|
||||
description="Reload product material mappings into the workflow context after persistence.",
|
||||
section="Behavior",
|
||||
default=True,
|
||||
),
|
||||
_field(
|
||||
"include_populated_products",
|
||||
"Rewrite Populated Products",
|
||||
"boolean",
|
||||
description="Also rebuild material mappings for products that already have non-empty assignments.",
|
||||
section="Behavior",
|
||||
default=False,
|
||||
),
|
||||
],
|
||||
input_contract={"context": "order_line", "requires": ["cad_materials"]},
|
||||
output_contract={"context": "order_line", "provides": ["material_catalog_updates"]},
|
||||
artifact_roles_consumed=["cad_materials"],
|
||||
@@ -306,7 +389,7 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
"cad_file",
|
||||
"media.save_thumbnail",
|
||||
"output",
|
||||
"Persist the generated thumbnail back onto the CAD file record.",
|
||||
"Persist the generated thumbnail back onto the CAD file record. Rendering settings are supplied by the connected upstream thumbnail request node.",
|
||||
node_type="outputNode",
|
||||
icon="download",
|
||||
execution_kind="bridge",
|
||||
@@ -360,6 +443,113 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
node_type="processNode",
|
||||
icon="layers",
|
||||
execution_kind="bridge",
|
||||
defaults={
|
||||
"template_id_override": "",
|
||||
"material_library_path": "",
|
||||
"require_template": False,
|
||||
"disable_materials": False,
|
||||
"target_collection": "",
|
||||
"material_replace_mode": "inherit",
|
||||
"lighting_only_mode": "inherit",
|
||||
"shadow_catcher_mode": "inherit",
|
||||
"camera_orbit_mode": "inherit",
|
||||
},
|
||||
fields=[
|
||||
_field(
|
||||
"template_id_override",
|
||||
"Template ID Override",
|
||||
"text",
|
||||
description="Optional render-template UUID to force for this workflow node instead of category/output-type resolution.",
|
||||
section="Template",
|
||||
default="",
|
||||
text_format="uuid",
|
||||
),
|
||||
_field(
|
||||
"require_template",
|
||||
"Require Template",
|
||||
"boolean",
|
||||
description="Fail this node when no active render template can be resolved.",
|
||||
section="Template",
|
||||
default=False,
|
||||
),
|
||||
_field(
|
||||
"material_library_path",
|
||||
"Material Library Path",
|
||||
"text",
|
||||
description="Optional absolute .blend path used instead of the active asset library.",
|
||||
section="Materials",
|
||||
default="",
|
||||
text_format="absolute_blend_path",
|
||||
),
|
||||
_field(
|
||||
"disable_materials",
|
||||
"Disable Materials",
|
||||
"boolean",
|
||||
description="Resolve the template but skip material-map generation for downstream nodes.",
|
||||
section="Materials",
|
||||
default=False,
|
||||
),
|
||||
_field(
|
||||
"target_collection",
|
||||
"Target Collection Override",
|
||||
"text",
|
||||
description="Optional collection name override applied after template resolution. Leave blank to inherit from the template.",
|
||||
section="Template Overrides",
|
||||
default="",
|
||||
),
|
||||
_field(
|
||||
"material_replace_mode",
|
||||
"Material Replace",
|
||||
"select",
|
||||
description="Override whether template material replacement is active for downstream nodes.",
|
||||
section="Template Overrides",
|
||||
default="inherit",
|
||||
options=[
|
||||
("inherit", "Inherit Template"),
|
||||
("enabled", "Force Enabled"),
|
||||
("disabled", "Force Disabled"),
|
||||
],
|
||||
),
|
||||
_field(
|
||||
"lighting_only_mode",
|
||||
"Lighting Only",
|
||||
"select",
|
||||
description="Override the template lighting-only flag for downstream nodes.",
|
||||
section="Template Overrides",
|
||||
default="inherit",
|
||||
options=[
|
||||
("inherit", "Inherit Template"),
|
||||
("enabled", "Force Enabled"),
|
||||
("disabled", "Force Disabled"),
|
||||
],
|
||||
),
|
||||
_field(
|
||||
"shadow_catcher_mode",
|
||||
"Shadow Catcher",
|
||||
"select",
|
||||
description="Override the template shadow-catcher flag for downstream nodes.",
|
||||
section="Template Overrides",
|
||||
default="inherit",
|
||||
options=[
|
||||
("inherit", "Inherit Template"),
|
||||
("enabled", "Force Enabled"),
|
||||
("disabled", "Force Disabled"),
|
||||
],
|
||||
),
|
||||
_field(
|
||||
"camera_orbit_mode",
|
||||
"Camera Orbit",
|
||||
"select",
|
||||
description="Override whether turntable renders orbit the camera or rotate the object.",
|
||||
section="Template Overrides",
|
||||
default="inherit",
|
||||
options=[
|
||||
("inherit", "Inherit Template"),
|
||||
("enabled", "Force Camera Orbit"),
|
||||
("disabled", "Force Object Rotation"),
|
||||
],
|
||||
),
|
||||
],
|
||||
input_contract={"context": "order_line", "requires": ["order_line_context"]},
|
||||
output_contract={
|
||||
"context": "order_line",
|
||||
@@ -372,6 +562,8 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
"use_materials",
|
||||
"override_material",
|
||||
"category_key",
|
||||
"workflow_input_schema",
|
||||
"template_inputs",
|
||||
],
|
||||
},
|
||||
artifact_roles_consumed=["order_line_context"],
|
||||
@@ -384,6 +576,8 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
"use_materials",
|
||||
"override_material",
|
||||
"category_key",
|
||||
"workflow_input_schema",
|
||||
"template_inputs",
|
||||
],
|
||||
),
|
||||
_definition(
|
||||
@@ -420,7 +614,7 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
"select",
|
||||
description="Force CPU, GPU, or automatic device selection.",
|
||||
section="Render",
|
||||
default="auto",
|
||||
default="gpu",
|
||||
options=_CYCLES_DEVICE_OPTIONS,
|
||||
),
|
||||
_field(
|
||||
@@ -451,6 +645,7 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
description="Optional Cycles adaptive sampling threshold, for example 0.01.",
|
||||
section="Denoising",
|
||||
default="",
|
||||
text_format="float_string",
|
||||
),
|
||||
_field(
|
||||
"denoiser",
|
||||
@@ -606,7 +801,11 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
defaults={
|
||||
"use_custom_render_settings": False,
|
||||
"fps": 24,
|
||||
"frame_count": 120,
|
||||
"duration_s": 5,
|
||||
"turntable_degrees": 360,
|
||||
"turntable_axis": "world_z",
|
||||
"camera_orbit": True,
|
||||
"rotation_z": 0,
|
||||
},
|
||||
fields=[
|
||||
@@ -664,8 +863,20 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
description="Optional hex color used during FFmpeg compositing, for example #FFFFFF.",
|
||||
section="Output",
|
||||
default="",
|
||||
text_format="hex_color",
|
||||
),
|
||||
_field("fps", "FPS", "number", section="Animation", default=24, min=1, max=120, step=1),
|
||||
_field(
|
||||
"frame_count",
|
||||
"Frame Count",
|
||||
"number",
|
||||
description="Explicit total frame count for the rendered turntable clip.",
|
||||
section="Animation",
|
||||
default=120,
|
||||
min=1,
|
||||
max=7200,
|
||||
step=1,
|
||||
),
|
||||
_field(
|
||||
"duration_s",
|
||||
"Duration",
|
||||
@@ -818,6 +1029,32 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
node_type="outputNode",
|
||||
icon="download",
|
||||
execution_kind="bridge",
|
||||
defaults={"expected_artifact_role": "", "require_upstream_artifact": False},
|
||||
fields=[
|
||||
_field(
|
||||
"expected_artifact_role",
|
||||
"Expected Artifact Role",
|
||||
"select",
|
||||
description="Restrict this node to a specific upstream render artifact type.",
|
||||
section="Output",
|
||||
default="",
|
||||
options=[
|
||||
("", "Any Connected Artifact"),
|
||||
("render_output", "Still Output"),
|
||||
("turntable_output", "Turntable Output"),
|
||||
("blend_export", "Blend Export"),
|
||||
("thumbnail_output", "Thumbnail Output"),
|
||||
],
|
||||
),
|
||||
_field(
|
||||
"require_upstream_artifact",
|
||||
"Require Upstream Artifact",
|
||||
"boolean",
|
||||
description="Fail the node when no matching upstream artifact is connected.",
|
||||
section="Output",
|
||||
default=False,
|
||||
),
|
||||
],
|
||||
input_contract={
|
||||
"context": "order_line",
|
||||
"requires": ["order_line_context"],
|
||||
@@ -833,7 +1070,7 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
"order_line",
|
||||
"media.export_blend",
|
||||
"output",
|
||||
"Persist the generated .blend file as a downloadable media asset.",
|
||||
"Persist the generated .blend file as a downloadable media asset. Only the optional filename suffix is workflow-configurable today.",
|
||||
node_type="outputNode",
|
||||
icon="download",
|
||||
defaults={"output_name_suffix": ""},
|
||||
@@ -845,6 +1082,8 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
description="Optional suffix appended to the generated `.blend` filename.",
|
||||
section="Output",
|
||||
default="",
|
||||
text_format="safe_filename_suffix",
|
||||
max_length=64,
|
||||
),
|
||||
],
|
||||
execution_kind="bridge",
|
||||
@@ -859,7 +1098,7 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
"cad_file",
|
||||
"cad.generate_stl_cache",
|
||||
"processing",
|
||||
"Generate and cache STL derivatives next to the STEP source.",
|
||||
"Compatibility node for legacy CAD flows. HartOMat graph execution uses direct OCC/GLB export instead, so this node intentionally performs no per-node-configurable cache generation.",
|
||||
node_type="convertNode",
|
||||
icon="refresh-cw",
|
||||
execution_kind="bridge",
|
||||
@@ -877,7 +1116,7 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
"Emit a user-visible notification for workflow completion or failure.",
|
||||
node_type="outputNode",
|
||||
icon="bell",
|
||||
defaults={"channel": "audit_log"},
|
||||
defaults={"channel": "audit_log", "require_armed_render": False},
|
||||
fields=[
|
||||
_field(
|
||||
"channel",
|
||||
@@ -888,6 +1127,14 @@ _NODE_DEFINITIONS: list[WorkflowNodeDefinition] = [
|
||||
default="audit_log",
|
||||
options=[("audit_log", "Audit Log")],
|
||||
),
|
||||
_field(
|
||||
"require_armed_render",
|
||||
"Require Armed Render",
|
||||
"boolean",
|
||||
description="Fail this node when no upstream graph render task is configured to hand off notifications.",
|
||||
section="Notification",
|
||||
default=False,
|
||||
),
|
||||
],
|
||||
execution_kind="bridge",
|
||||
input_contract={
|
||||
|
||||
Reference in New Issue
Block a user