How Modly Handles the "Add to Scene" Workflow Node: A Technical Deep Dive
The "Add to Scene" node is Modly's terminal output node that validates mesh inputs at design-time and pushes generated geometry to the 3D viewer at runtime.
The Add to Scene workflow node serves as the sink for all mesh generation pipelines in the Modly visual editor. Understanding its three-phase lifecycle—label rendering, pre-flight validation, and scene pushing—is essential for building custom workflow nodes that integrate with the 3D viewport.
Node Type Declaration and Behavior Registry
In src/areas/workflows/nodeBehaviors.ts, the BEHAVIORS lookup table defines how the workflow engine treats each node type.
// src/areas/workflows/nodeBehaviors.ts#L25-L28
const BEHAVIORS: Record<string, NodeBehavior> = {
outputNode: { sceneOutput: true, branchConsumer: true },
// ...
};
The outputNode entry carries two critical flags:
sceneOutput: true— Signals the runner that this node terminates in a scene push, not a data returnbranchConsumer: true— Restricts the node to single-branch inputs (Wait-branch pattern), preventing parallel mesh merges
This behavior-driven architecture means any future node can become a scene sink by adding { sceneOutput: true } to BEHAVIORS without modifying core engine code.
UI Label: "Add to Scene" Rendering
The user-facing label is resolved at runtime from src/areas/workflows/preflight.ts:
// src/areas/workflows/preflight.ts#L13-L18
export function nodeLabel(type: string): string {
if (type === 'outputNode') return 'Add to Scene';
// additional label mappings...
}
Modly uses this function rather than static labels to support localization and dynamic node naming based on configuration state.
Pre-Flight Validation of Mesh Inputs
Before execution, validateWorkflowPreflight ensures the Add to Scene node receives compatible upstream data:
// src/areas/workflows/preflight.ts#L42-L46
function getNodeOutputType(node: WorkflowNode): string {
if (node.type === 'outputNode') return 'mesh';
// type resolution for other nodes...
}
The validator performs two checks:
- Type compatibility — Confirms the incoming edge carries a
meshtype (frommeshNode,forEachNode, or similar) - Single connection enforcement —
branchConsumer: trueblocks multiple upstream branches
Failure raises a blocking issue: "Add to Scene needs an incoming mesh connection".
Runtime Execution and Scene Push
At execution, the workflow runner queries isSceneOutput(node.type) against the behavior table. For outputNode, this triggers the scene-push path:
- Mesh data is serialized from the upstream node's output
- The Electron main process receives the payload via IPC
src/electron/main/artifact-registry-service.tshandles the actual insertion into the 3D viewer
This separation between validation logic (renderer) and scene mutation (main process) maintains Modly's security model.
Practical Workflow Definition
Below is a complete minimal workflow connecting a mesh file to the scene output.
// Create the mesh source node
const meshNode = {
id: 'mesh-001',
type: 'meshNode',
data: {
source: 'file',
path: '/assets/props/crate.obj'
},
position: { x: 100, y: 100 }
};
// Create the Add to Scene output node
const addToSceneNode = {
id: 'output-001',
type: 'outputNode', // Triggers sceneOutput behavior
data: {}, // No parameters required
position: { x: 400, y: 100 }
};
// Define the connecting edge
const meshToSceneEdge = {
id: 'edge-001',
source: 'mesh-001',
target: 'output-001'
};
// Full workflow assembly
const workflow = {
nodes: [meshNode, addToSceneNode],
edges: [meshToSceneEdge]
};
Validation will confirm that meshNode outputs mesh and that outputNode accepts it. Runtime execution pushes crate.obj into the viewer.
Key Files and Their Roles
| File | Purpose |
|---|---|
src/areas/workflows/nodeBehaviors.ts |
Declares outputNode as sceneOutput: true, branchConsumer: true |
src/areas/workflows/preflight.ts |
Provides "Add to Scene" label and getNodeOutputType for mesh validation |
src/shared/stores/workflowsStore.ts |
Lists outputNode in NODE_TYPES_WITHOUT_SOURCE for UI constraint handling |
src/electron/main/artifact-registry-service.ts |
Performs actual mesh insertion into the 3D viewer at runtime |
Summary
- Behavior-driven design — The
BEHAVIORStable innodeBehaviors.tsmakesoutputNodea scene sink viasceneOutput: true - Label resolution —
nodeLabel()inpreflight.tsreturns"Add to Scene"for UI display - Strict validation — Pre-flight guarantees a single mesh input via
getNodeOutputTypeandbranchConsumer: true - Secure execution — Scene mutation is delegated to Electron's main process through the artifact registry service
Frequently Asked Questions
What happens if the Add to Scene node has no connected upstream node?
The pre-flight validator raises a blocking issue: "Add to Scene needs an incoming mesh connection". The workflow cannot execute until a meshNode, forEachNode, or other mesh-producing node is wired to it.
Can I have multiple Add to Scene nodes in one workflow?
Yes. Each outputNode operates independently. The workflow runner processes all nodes where isSceneOutput() returns true, pushing each to the scene in execution order. However, branchConsumer: true means each outputNode can only receive from one branch.
How do I create a custom node that pushes to the 3D scene?
Add an entry to BEHAVIORS in nodeBehaviors.ts with sceneOutput: true, implement getNodeOutputType to return 'mesh', and ensure your node's runtime handler produces valid geometry data. The runner will automatically route to the scene-push path.
Why is the actual scene insertion handled in the main Electron process?
Renderer-process isolation prevents untrusted workflow code from directly manipulating the 3D viewport. The artifact-registry-service in the main process acts as a gated bridge, validating and sanitizing mesh data before insertion.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →