chore: snapshot workflow migration progress

This commit is contained in:
2026-04-12 11:49:04 +02:00
parent 0cd02513d5
commit 3e810c74a3
163 changed files with 31774 additions and 2753 deletions
@@ -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={