Turn Shot Designer (Hollywood Camera Work .hcw) scene files into Unreal
Engine 5.8 scenes (compatible with 5.6+) — cameras (with moves), actors,
props, walls, and lights — via a Python script you run inside the editor.
Shot Designer .hcw ──parse──▶ IR (JSON) ──emit──▶ Unreal 5.8 Python script
(plain XML) neutral scene run in the editor
No third-party dependencies for the core pipeline (stdlib only).
pip install -e . # provides the `vsm` command
# or run without installing:
PYTHONPATH=src python -m virtualsetmaker.cli --help# Parse a Shot Designer scene and emit an Unreal script
# (default output: Sceneforclaude_unreal.py beside the input; -o to override):
vsm build samples/Sceneforclaude.hcw
# Tunables via flags (defaults are 35mm, 1.5 m camera height, 2.5 m walls):
vsm build scene.hcw --focal-length 50 --camera-height 1.7 --wall-height 300 \
--frame-rate 30 --content-path /Game/Blocking --units-per-meter 100
# Inspect the intermediate representation as JSON:
vsm parse samples/Sceneforclaude.hcw -o scene.json
# Emit straight from a hand-authored IR (no .hcw needed) — good for demos:
vsm emit examples/example_scene.json
# Just confirm a file is a Shot Designer scene:
vsm probe samples/Sceneforclaude.hcw
# List the prop blockout library:
vsm props
# Open the graphical exporter:
vsm gui # (or run `vsm-gui`)Every tunable can also be set once in ~/.virtualsetmaker.json (created by the
GUI; hand-editable). Precedence: CLI flag > config defaults > built-in.
Both vsm and the GUI honor the defaults section:
{
"input_dir": "C:/scenes",
"output_dir": "C:/exports",
"units_per_meter": 100,
"defaults": {
"focal_length_mm": 35,
"camera_height_m": 1.5,
"wall_height_cm": 250,
"wall_thickness_cm": 10,
"frame_rate": 24,
"ue_content_path": "/Game/VSM",
"manny_paths": ["/Game/MyChars/Hero.Hero"],
"quinn_paths": ["/Game/MyChars/Heroine.Heroine"]
}
}| key | meaning | built-in |
|---|---|---|
focal_length_mm |
lens for every camera (Shot Designer stores none) | 35 |
camera_height_m |
camera height (Shot Designer is 2D top-down) | 1.5 |
wall_height_cm / wall_thickness_cm |
wall blockout dimensions | 250 / 10 |
frame_rate |
level-sequence display rate | 24 |
ue_content_path |
where the sequence asset is created | /Game/VSM |
manny_paths / quinn_paths |
skeletal-mesh asset paths tried for actors | UE mannequins |
units_per_meter |
Shot Designer units per meter | 100 |
input_dir/output_dir/top-level units_per_meter are the GUI's remembered
state; unknown keys and bad values are ignored, never fatal.
vsm gui (or the vsm-gui command / vsm-gui.exe) opens a small exporter:
add one or more .hcw files, choose an export folder, hit Export — each
scene becomes <name>_unreal.py in that folder, with a log of object counts
and any unmatched-prop notes. Last-used folders are remembered in
~/.virtualsetmaker.json.
Standalone exe (no Python needed to run it):
- Get this repo onto the PC (clone or download ZIP).
- Double-click
packaging\build_windows.bat— one-time build; needs Python 3 from python.org with the default tcl/tk option. It installs PyInstaller and producesdist\vsm-gui.exe. - Double-click
dist\vsm-gui.exeto use the exporter from then on.
Or via pip: install Python 3.9+, then pip install . in the repo — this
gives vsm (terminal) and vsm-gui (windowed, no console) commands.
- Enable Edit → Plugins → "Python Editor Script Plugin" (restart once).
- Open the Output Log, switch the dropdown from
CmdtoPython. - Run
py "C:/path/to/scene.py"(or paste the file path).
It spawns the actors/props/walls/lights into the current level and creates a
Level Sequence (/Game/VSM/SEQ_<name>) with the CineCamera(s), any camera
move keyframes, focal-length animation, and a camera-cut track. The sequence
is opened in Sequencer as part of the build (required by the UE 5.6+
LevelSequenceEditorSubsystem spawnable API) and left open for you.
- 1 Shot Designer unit = 1 cm (override with
--units-per-meter). - Shot Designer positions are screen-space (
+ydown) with angles in radians. Both that frame and Unreal's are left-handed, so coordinates and yaw carry over unchanged (chirality preserved — scenes and shots are never mirrored); the whole conversion is pinned down in one place,coords.py. - Actors render as the UE Mannequin —
SKM_Manny, orSKM_Quinnfor Shot Designer Type B (female) characters — facing where the character faces, with a capsule fallback (plus instructions in the Output Log for adding the Third Person content pack) if the meshes aren't in the project.
Props aren't anonymous cubes: each Shot Designer objectKey maps to a
parametric blockout recipe in
emit/blockouts.py — a chair is a seat
- backrest + four legs, a table is a top + legs, an open door is a frame + a
swung panel, all at real-world dimensions. Run
vsm propsto see the recipes.
The library covers the entire Shot Designer object palette (62 recipes, mapped from the app's palette screenshots): all furniture and tables, desk/hand props (laptop, monitor, phone, plate, cup, paper, guns), greenery (bush, tree), every vehicle (car, mini bus, semi truck, trailer, motorcycle, tank, small plane, fighter jet, commercial jet), production gear (camera crane, boom mic, microphone, monitor village, equipment), all door/window types (open, closed, double open, double closed, medium opening, prison bars, window), and floor arrows.
Lights spawn as placeholder rig geometry + a real Unreal light attached at the fixture's emit point, grouped so they move together: fresnels/ellipsoidal/ PAR/scoop/cyc/open-face get a stand + head with a pitched SpotLight at the lens; panels/FLO/softbox/silk/bounce get their slab/frame with a RectLight on the face; china ball/balloon/practical/light-on-a-stick get hanging or lamp geo with a PointLight inside; Sun spawns a DirectionalLight only (position is meaningless for it, so no rig); the Virtual Speed Rail spawns rigging geo with no light.
Matching is exact key first, then ordered substring aliases (COUCHMODERN →
SOFA; BOOMMICROPHONE → boom rig, not phone), so unseen key-name variants land
on the right shape. Unmatched kinds fall back to a labeled 1 m cube and
vsm build prints a note — send a .hcw using that prop and the recipe can be
added in a few lines. Parts spawn grouped (attached to the first part) in the
VSM/Props outliner folder, so each prop moves as one unit.
Recipes live in emit/blockouts.py.
Each is a list of primitive parts built with _p(shape, offset, size, rot):
- Frame (before the prop's own rotation):
+X= width (right),+Y= toward the prop's front,+Z= up. offset— the part's center in cm, relative to the prop origin on the floor.size—[x, y, z]extent in cm (basic-shape meshes are 100 cm, so actor scale = size/100).rot— optional[pitch, yaw, roll]degrees (pitched cylinders make car wheels, a rolled one makes a tank barrel).- Add the recipe under its Shot Designer
objectKey, plus anALIASESrow for name variants — order matters: more specific substrings must come first (ARMCHAIRbeforeCHAIR,CARPETbeforeCAR).
Shot Designer's objectScaleX/Y is relative to each icon's native art size
— not to our real-world recipes. The SD_NATIVE table in blockouts.py now
carries values measured from the app's own vector art (the 1.80.8
installer's FXG assets; the app places art at 1 unit = 1 cm, confirmed in its
decompiled source), so the emitted footprint is objectScale x native —
exactly what the Shot Designer canvas shows. Some icons differ from their
names: the "Square Table" icon is really a 120x220 cm rectangular table, and a
fresh door is placed at objectScale 0.7 by default. Doors/windows/openings are
wall-snapped (their art includes wall stubs) and keep the verified
opening-based sizing without a native entry. For any future icon, the same
measurement can be redone from the installer's FXG/ assets.
vsm build/parse (and the GUI log) can print:
warning: skipped N <Tag> object(s)— the.hcwcontains a Shot Designer object type the parser doesn't handle yet; nothing was generated for it.warning: the document has N snapshot(s) beyond the current one— only the current snapshot converts; objects existing only in other snapshots won't appear.warning: file version '…' is not the tested ['1']— a newer Shot Designer file format; parsing is attempted but new features may be missing.note: no blockout recipe for prop kind '…'— that prop spawned as a generic 1 m cube (see Adding a prop recipe).
Inside Unreal, the generated script logs VSM: lines for every spawn failure
plus a final spawned-vs-expected summary per category.
- Static scenes (placement of camera, actors, props, walls, lights): done,
verified against a real
.hcw. - Camera moves: done. Shot Designer stores a move as numbered camera stop
marks (
<stopMarks>) linked by a<Track>dolly path; the parser merges each chain into one camera keyed at the start and end stops (position and look direction from the stops themselves) plus a key per intermediate track point so curved paths keep their bow (Unreal's AUTO tangents smooth through them). Timing comes from path length at the scene's camera-speed index (speed 3 ≈ 0.75 m/s dolly;_DOLLY_MPS_PER_SPEEDinparse/shotdesigner.py). Verified against straight-line and curved-move samples insamples/.
- Prop size calibration (measured) — new
SD_NATIVEtable maps Shot Designer icons' native art sizes so scaled props emit at their true on-canvas span (objectScale × native). Values are measured from the app's own vector art (installer FXG assets, 30 freestanding icons), anchored against real-scene measurements. Fixes from the same decompiled source: the Cup icon's real keyBOTTLE(was a generic cube),DOUBLEDOOROPENmapping to the closed-door recipe,HOLLYWOODLIGHT(Light On A Stick) getting the stand fixture instead of its stick + point light, the exact 8-color character palette, and confirmation that<cameraStyle>carries no lens data (display-only). - Config file + CLI flags — every hardcoded default is now tunable: focal
length, camera height, wall height/thickness, frame rate, UE content path,
and mannequin asset paths, via
vsm buildflags or adefaultssection in~/.virtualsetmaker.jsonshared by the CLI and GUI (flag > config > built-in). - Camera filmback — the sensor size carried in the IR (36×24 default) is now applied to the CineCamera filmback, so focal lengths frame correctly.
- Actor colors — Shot Designer character colors (the packed
<color>value) now tint the capsule-fallback actors; mannequin spawns keep their own materials. - CLI/GUI fixes —
vsm build/parse/emitprint friendlyerror:lines instead of tracebacks;vsm builddefault output is now<name>_unreal.pybeside the input (same convention as the GUI); the GUI no longer freezes during a batch export (worker thread) and failures show their traceback in the log pane (the windowed exe has no console to hide them in); an untested.hcwfile version now produces a warning instead of silence. - Light table unified — each light fixture now carries its Unreal light
class in one table;
SOFT/FRAME/LANTERN/BULBkinds get matching rig geometry instead of the generic stand.
- Actors spawn as UE Mannequins —
SKM_Mannyfor Type A characters,SKM_Quinnfor Type B (the.hcw<female>flag), with the correct facing (mannequin meshes are authored facing +Y, so spawns apply a −90° yaw offset). If the project lacks the meshes, the Output Log explains how to add the Third Person content pack instead of silently dropping capsules. - Fixed mirrored scenes — the screen→Unreal conversion previously negated Y, which reflected the entire scene: props appeared on the wrong side of the room and every through-camera shot was flipped left/right. Coordinates and yaw now carry over unchanged, so Unreal matches the Shot Designer plan exactly.
- Calibrated prop orientation — freestanding props store the direction of
their back in the
.hcw(a sofa placed against a wall now sits flush, facing the room); doors/windows snapped into a wall follow the wall's direction, with door panels swinging into the room. - Export diagnostics — unsupported Shot Designer object types and multi-snapshot documents are reported at export instead of dropped silently; the generated script survives individual spawn failures, names them in the Output Log, and reports spawned-vs-expected counts per category.
- Known issue — prop footprints are real-world recipe dimensions ×
objectScale, but some Shot Designer icons (notably tables) have native sizes larger than real-world furniture, so those props come out small. (Fixed 2026-07-20 via theSD_NATIVEcalibration table.)
- Camera moves — stop marks + dolly
<Track>chains become one keyframed CineCamera per move, with curve bow preserved and timing from path length at the scene's camera-speed index. - Unreal 5.8 target (compatible 5.6+) — spawnables go through
LevelSequenceEditorSubsystem. - Fixes — generated-script rotations (the UE Python
Rotatorconstructor is(roll, pitch, yaw), not the C++ order), scene data embedded as JSON (not pasted as a Python literal), and the Windows standalone-exe build. - Initial release —
.hcw→ IR → Unreal editor Python script; full prop palette blockout library; lights with real Unreal light types; tkinter GUI and Windows packaging.
src/virtualsetmaker/
ir.py neutral scene dataclasses (+ JSON, validation)
coords.py the single Shot Designer→Unreal coordinate transform
parse/shotdesigner.py .hcw XML → IR
parse/probe.py format guard
emit/unreal_python.py IR → Unreal 5.8 Python script
emit/blockouts.py prop + light-fixture blockout recipes
build.py shared build core (used by CLI and GUI)
settings.py shared config (~/.virtualsetmaker.json) + Defaults
gui.py tkinter exporter (vsm gui / vsm-gui)
cli.py the `vsm` command
packaging/ PyInstaller spec + Windows one-click build script
examples/example_scene.json hand-authored IR with an animated push-in
samples/Sceneforclaude.hcw real Shot Designer sample
tests/ pytest suite (IR, coords, parser, emitter)
pip install -e ".[dev]"
pytest