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 return
  • branchConsumer: 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:

  1. Type compatibility — Confirms the incoming edge carries a mesh type (from meshNode, forEachNode, or similar)
  2. Single connection enforcement — branchConsumer: true blocks 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:

  1. Mesh data is serialized from the upstream node's output
  2. The Electron main process receives the payload via IPC
  3. src/electron/main/artifact-registry-service.ts handles 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 BEHAVIORS table in nodeBehaviors.ts makes outputNode a scene sink via sceneOutput: true
  • Label resolution — nodeLabel() in preflight.ts returns "Add to Scene" for UI display
  • Strict validation — Pre-flight guarantees a single mesh input via getNodeOutputType and branchConsumer: 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →