Nodes/PAL Layout Node/PAL Layout (Lenscowboy)
ComfyUI Node

PAL Layout (Lenscowboy)

Stop guessing depth — PAL Layout gives you real depth maps from an actual 3D scene

By Lenscowboy·Created 5 months ago·Updated 14 days ago· 0
PAL Layout (Lenscowboy)
  • model_3d
  • beauty pass
  • alpha pass
  • depth pass
  • normal pass
  • id matte pass
  • scene json
  • camera json
  • frame start
  • frame end
  • sequence json
lc_api_key
lc_project_id
lc_shot_id
glb_path
GLB
OBJ
prompt
camera_preseteye_level
frame_start1
frame_end24
render_width512
render_height512
scene_json_in
use_local_rendererfalse
_pal_scene_state{}

Every ControlNet depth workflow you've seen starts by estimating depth from a flat image with Marigold or MiDaS. The estimate is good - and it's still an estimate. PAL Layout (PAL is Lenscowboy's 3D previsualization tool) takes the other route: you actually build the shot. Place the camera, drop in proxy geometry, set the lighting, and the node hands you a depth map rendered from real geometry. Same for normals. No preprocessor, no hallucinated geometry - just the exact camera you chose, as pixels.

That's the whole reason to reach for it. Depth-conditioned ControlNet already gives you strong spatial control; what it inherits is the quality of your condition. A rendered pass beats a monocular estimate every time, and if you're doing shot design - character placement, camera moves, consistent framing across frames - it beats alt-tabbing into Blender too.

Honest framing up front: this is a young, small pack. v1.1.x, launched spring 2026, almost no community footprint yet. You're an early adopter, and the README is candid that some things (animated sequence renders, camera data import) were still landing after v1.0. The author is active and responsive on Discord, which helps when you hit the rough edges.

How it works

It's a hybrid of a browser app and a Python node. The frontend embeds Lenscowboy's full Three.js viewport as an iframe (served from app.lenscowboy.com by default). You get orbit/pan/zoom, camera presets (eye level, low angle, overhead, rear, OTS, POV), WASD fly mode, proxy geometry for humans, vehicles, buildings and terrain, plus a shot list and timeline. When you render, passes flow back over a postMessage bridge as base64, get stashed in a hidden _pal_scene_state widget, and are decoded into IMAGE tensors at execute() time.

There's a second render path: flip use_local_renderer on and rendering happens in-process with pygfx/wgpu - no iframe round-trip, no base64 cap. Everything else is dependency-light: the pack's requirements.txt declares nothing beyond ComfyUI's own torch/numpy/PIL, because the Three.js viewport runs in your browser, not the server.

The inputs that matter

Everything is optional - the node works fully offline with no API key.

  • model_3d, GLB, OBJ, glb_path - your 3D assets. model_3d takes a FILE_3D socket matching ComfyUI's native Load3D output exactly, so an image-to-3D result (Hunyuan3D, TRELLIS) wires straight in. GLB/OBJ also accept FILE_3D/MESH/STRING, or type a file path into glb_path. The viewport supports drag-and-drop GLB import too.
  • camera_preset - a string, default eye_level; pick a preset or type your own.
  • frame_start / frame_end - timeline range (defaults 1–24). v1.1 added sequence rendering, so you can hand an IMAGE batch of frames to KSampler or Video Combine.
  • render_width / render_height - 64–2048, step 64, default 512. The free tier caps at 512 regardless.
  • lc_api_key, lc_project_id, lc_shot_id - the optional Lenscowboy platform connection for project/shot data.
  • use_local_renderer - the pygfx path.

Outputs - and the catch

Ten outputs: beauty, alpha, depth, normal and id-matte IMAGE passes, scene/camera/sequence JSON strings, and the frame range. The beauty pass covers preview, reference and img2img. Depth and normals feed the corresponding ControlNet models with the same wiring as any preprocessed map.

Here's the trap nobody spells out: depth, normal, alpha and id-matte are gated behind a paid Lenscowboy plan. The source literally sets has_multipass = plan != "free" and fills those outputs with blank frames on the free tier. Free users get the 512px beauty pass and nothing else. So the feature most people want this node for - real depth/normals into ControlNet - needs an API key and a subscription. Generate a key at app.lenscowboy.com/settings (they expire after 365 days) and check your plan before you go chasing a black depth map. Keys unlock features per-plan, and revoking is instant.

Install

ComfyUI Manager, search "PAL Layout". Or:

cd ComfyUI/custom_nodes
git clone https://github.com/Lenscowboy/comfyui-lenscowboy-pal.git

Restart ComfyUI. No model downloads, no heavy dependencies. Want the local renderer? Its extras install separately so the node still loads without them:

cd ComfyUI/custom_nodes/comfyui-lenscowboy-pal/pal_renderer
pip install -r requirements.txt

That graceful fallback to iframe mode is deliberate - the base install is genuinely zero-friction.

Troubleshooting

  • Queue does nothing. The node blocks Queue Prompt until passes are rendered (via beforeQueuePrompt). Open the viewport, arrange, render, save, then queue.
  • Blank depth/normals. Almost always the free-tier gate above, not a bug.
  • FBX cameras don't import. At launch the node only recognized geometry, not cameras baked into FBX exports; the author was mid-fix in April 2026. If your pipeline is C4D/Blender cameras, test with a simple FBX first.
  • It always re-runs. The node returns NaN from IS_CHANGED, so ComfyUI executes it on every queue. Fine for an interactive tool, mildly annoying in batches.
CategoryLenscowboy/Layout

Inputs (16)

NameTypeDefaultDescription
lc_api_keyoptSTRING
lc_project_idoptSTRING
lc_shot_idoptSTRING
glb_pathoptSTRING
GLBoptCOMBO4 options: FILE_3D_GLB, FILE_3D, MESH, STRING
OBJoptCOMBO4 options: FILE_3D_OBJ, FILE_3D, MESH, STRING
model_3doptFILE_3D
promptoptSTRING
camera_presetoptSTRINGeye_level
frame_startoptINT10–9999
frame_endoptINT241–9999
render_widthoptINT51264–2048
render_heightoptINT51264–2048
scene_json_inoptSTRING
use_local_rendereroptBOOLEANfalse
_pal_scene_stateoptSTRING{}

Outputs (10)

NameTypeDescription
beauty passIMAGE
alpha passIMAGE
depth passIMAGE
normal passIMAGE
id matte passIMAGE
scene jsonSTRING
camera jsonSTRING
frame startINT
frame endINT
sequence jsonSTRING