Cam Shot Toolkit: Assemble Scene
Populate a floor with people who don't overlap
- rigs
- camera
- scene
- scene_json
Most synthetic-data nodes stop at "place things randomly." Assemble Scene is the one that refuses to let your pose references turn into garbage: it rolls a random-but-plausible group of actors onto a floor and only keeps placements that physically clear each other, land fully in frame, and don't hide each other past your tolerance. It's the dice between Load Pose Rigs (what's in your library) and Render Clay Grid (what you actually see).
Think of it as the anti-overlap bouncer for the whole pipeline. When you're generating pose-reference images to train a model, a frame where one person's head is cut off by the frame edge, or two bodies clip through each other, is a poisoned sample. This node exists so that failure mode is rare by construction rather than caught by eyeballing 10,000 renders.
How it works
The scene is built from a seed and a set of rules. First the actor-group count K is drawn from count_weights - a weighted distribution, default 1:0.6,2:0.3,3:0.1 means "60% single actor, 30% pairs, 10% trios," and 1:1 means one actor, always. Then each actor picks a rig from the loaded set and tries a random (x, z) inside ±region_x_ft by ±region_z_ft around the origin.
A placement is only accepted if all of these hold:
- its footprint circle clears every already-placed actor by
min_gap_ft, - it projects fully inside the frame - when a camera is connected,
- no actor's silhouette is occluded past
max_occlusion_frac(an id-render check atocclusion_check_scale, default 0.25, so it's cheap).
After max_tries failures an actor is dropped rather than squeezed in and mis-labeled. That's a design choice worth appreciating: the node prefers fewer, clean actors over more, overlapping ones.
The inputs that matter
rigs(POSE_RIGS) - from Load Pose Rigs.seed- bind to the sample seed; deterministic scenes.count_weights,max_bodies- how many people, and the hard cap (group rigs count all their actors).region_x_ft/region_z_ft- how big the stage is.min_gap_ft- personal-space enforcement.camera(optional,POSE_CAMERA) - connect it or the in-frame and occlusion checks are simply off.max_occlusion_fracdoes nothing without a camera; its default of 0 means "no overlap at all."mirror_mode(off/random/on) andyaw_jitter_deg- orientation variety, so the same gesture doesn't face the same way every frame.required_labels- labels that must appear, placed first; a dataset bucket can bind its own gesture here.placements_json- skip the dice entirely and pin exact positions:[{"label": "01_star", "xz_ft": [-2.5, 0], "yaw_deg": 0, "mirror": false}, ...].
Outputs: scene (POSE_SCENE) into Render Clay Grid, plus scene_json with the placed actors, their placements, labels, and placement stats (placed vs requested, rerolls).
Install
ComfyUI Manager (search sam3d-body-comfyUI-camshottoolkit) or:
cd ComfyUI/custom_nodes
git clone https://github.com/EnviralDesign/sam3d-body-comfyUI-camshottoolkit
cd sam3d-body-comfyUI-camshottoolkit
python -m pip install -r requirements.txt
Restart after installing.
Gotchas
The console log is your friend: it prints placed_k/requested_k actors and reroll count. If you're seeing far fewer placed than requested, your region is too small, max_bodies is too tight, or you've set allow_same_label off with too few distinct gestures in the library - the node is dropping actors because it can't fit them, not malfunctioning. If placements_json names a rig or label that isn't in the loaded set, it raises instead of guessing, so keep pin lists in sync with what Load actually returned. And remember the camera rule: wire a POSE_CAMERA before you trust any occlusion number, or the scene is assembled with framing and overlap checks disabled entirely.
Inputs (17)
| Name | Type | Default | Description |
|---|---|---|---|
| rigs | POSE_RIGS | Loaded rig set. | |
| seed | INT | 00–18446744073709550000 | Scene seed (bind to the sample seed). |
| count_weights | STRING | 1:0.6,2:0.3,3:0.1 | Distribution of actor groups per scene, 'k:weight,...'. '1:1' = single actor always. |
| max_bodies | INT | 81–64 | Hard cap on people in frame (group rigs count all their actors). |
| min_gap_ft | FLOAT | 1.00–20 | Minimum clear floor between footprints, feet. |
| region_x_ft | FLOAT | 4.00–100 | Placement half-width about the origin, feet. |
| region_z_ft | FLOAT | 3.00–100 | Placement half-depth about the origin, feet. |
| allow_same_label | BOOLEAN | true | Allow two groups doing the same gesture (still one label). |
| center_single | BOOLEAN | true | Single-actor scenes sit exactly at the origin (camera jitter provides the variety). |
| mirror_mode | COMBO | off | Mirror actors about their own vertical axis (guest orientation vs concept orientation). |
| yaw_jitter_deg | FLOAT | 0.00–180 | Uniform ±yaw per actor, degrees. |
| max_tries | INT | 201–500 | Rerolls per actor before it is dropped. |
| max_occlusion_frac | FLOAT | 0.000–1 | Reject placements hiding more than this fraction of any actor's silhouette (needs camera). 0 = no overlap at all. |
| cameraopt | POSE_CAMERA | Enables the in-frame and 2D occlusion checks. | |
| placements_jsonopt | STRING | Optional pinned placements: [{"label": "01_star", "xz_ft": [-2.5, 0], "yaw_deg": 0, "mirror": false}, ...]. Skips the dice. | |
| occlusion_check_scaleopt | FLOAT | 0.250.05–1 | Render scale for the occlusion check. |
| required_labelsopt | STRING | Comma-separated labels that must appear in every scene (placed first). A dataset bucket binds its own gesture here. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| scene | POSE_SCENE | — |
| scene_json | STRING | — |