Skip to content

Art Pipeline

Purpose. How a custom 3D asset goes from a Blender source file to something an .item (or future Vehicle/DoorComponent/etc.) Model field can reference in-engine. Written because ADR-0016 and ADR-0017 between them resolve most of this project's placeholder-model debt with free assets, but confirm a real, permanent gap those sources cannot cover: all 14 Weapons/PoliceWeapons/IllegalWeapons items (the sbox.game/facepunch/sboxassets collection has no firearms or real melee weapons at all — see ADR-0016's second-pass finding) and, eventually, Vehicles (deferred at the feature level per ADR-0009, not just its art). This doc exists for that gap, not to replace free-asset sourcing as the default — free-and-suitable still wins whenever it exists, per ADR-0016's own "Alternatives considered."

Responsibilities. - Where source .blend files and their exports live, and why not in this repo. - Naming, scale, and collision conventions for a custom model. - The one step this machine can actually do (Blender export, headless or interactive). - The one step this machine cannot do (.vmdl compilation), and where that happens instead.

Not responsible for. Rigging/animation conventions for a fully animated first-person weapon — out of scope until Documentation/Standards/06_ART_PIPELINE.md#5-proof-of-concept's static-mesh proof of concept is done and a second, harder case is actually being built. Which items get custom art vs. a free-asset match — that's ADR-0016/ADR-0017's call, this doc only covers the mechanics once an item is confirmed to need one.

Dependencies. Blender (5.2.0 LTS, confirmed installed on this dev machine), the S&box Model Doc Editor (only available on the separate Windows machine that runs the real editor — see the project root CLAUDE.md).

Public API. N/A — this is a process document, not code.


1. Where source files live

Raw .blend files and their FBX exports are large, frequently-changing binaries. 04_GIT_WORKFLOW.md §6 already excludes "large binaries generally" from this repo's history without a separate discussion, and Architecture/01_PROJECT_LAYOUT.md §3 already states "no model, material or sound assets are checked in" — this doc doesn't change either rule, it follows them for the first time there's an actual asset to place.

Convention: source .blend files and exported .fbx live in a sibling directory outside this repo, ~/Projects/AppleJackRP-Art/<category>/<item_id>/ (e.g. ~/Projects/AppleJackRP-Art/weapons/weapon_crowbar/weapon_crowbar.blend), the same outside-version-control treatment legacy/content/ already gets. This is a convention, not a tool — nothing enforces it automatically. If the source-art volume grows enough to need its own history/backup story, that is a follow-up decision (a dedicated Git LFS-backed repo is the obvious next step), not decided here.

The compiled .vmdl/.vmdl_c output is the actual project asset once it exists, and does belong in the S&box project's own Assets/ tree (or wherever the real editor places compiled models) the same as any other in-engine asset — this exclusion is about the heavy, iterative source files, not the shipped result.

2. Naming and folder convention

Mirror the .item id: <item_id>.blend inside <item_id>/, one folder per item. A weapon that needs a separate first-person view model gets <item_id>_world.blend and <item_id>_view.blend rather than two objects in one file — keeps each export single-purpose and matches how the world/view model split already exists conceptually in the legacy w_*.mdl/v_*.mdl naming this project's own dead placeholders still carry.

3. Scale and orientation

Source 2 keeps Source 1's convention: 1 Hammer unit = 1 inch. Model the asset at real-world scale in Blender with the Unit System set to Imperial/Inches (or model in metres and scale by 0.0254 on export — either is fine as long as it's consistent), forward axis +X, up axis +Z, matching S&box's coordinate convention (confirmed by Rotation.FromYaw/Vector3 usage throughout this codebase's own Code/Movement/ — yaw rotates around Z).

4. Collision

A simple convex hull per static prop (weapon world models, packaging, etc.) is sufficient at this scale — no per-triangle collision, no LODs; these are small hand-held/world-decoration props, not large terrain meshes. Author the hull as a separate low-poly object in the same .blend file, named <item_id>_collision, so the export step (below) can split it into its own mesh on export rather than needing a second file.

5. Export (this machine can do this step)

Blender runs headlessly here (blender --background, confirmed: blender -b -P script.py executes a Python script with no GUI). A minimal batch-export script:

# export_fbx.py -- run via: blender -b <item>.blend -P export_fbx.py -- <output.fbx>
import bpy, sys

argv = sys.argv[sys.argv.index("--") + 1:]
output_path = argv[0]

bpy.ops.export_scene.fbx(
    filepath=output_path,
    use_selection=False,
    apply_unit_scale=True,
    axis_forward='X',
    axis_up='Z',
)

This produces an editor-ready .fbx next to the .blend source, in the same ~/Projects/AppleJackRP-Art/<category>/<item_id>/ folder from §1.

6. Compile to .vmdl (this machine cannot do this step)

.vmdl compilation goes through the S&box Model Doc Editor — a GUI tool, not a headless CLI step, and part of the S&box Editor this project's root CLAUDE.md already documents as only running on the separate Windows machine (D:/Program Files (x86)/Steam/steamapps/common/sbox/). Once the FBX exists:

  1. Open the project in the real editor (directly on the Windows machine, or via tools/AppleJackTools/connect-remote-editor.sh's remote tunnel).
  2. Model Doc Editor → import the .fbx → assign the <item_id>_collision mesh as the physics/collision hull → compile.
  3. Note the resulting .vmdl path (it may not exactly match the models/<item_id>.vmdl guess used everywhere else in this project's placeholder JSON — if it differs, that is the same kind of mechanical find-and-replace correction ADR-0016/ADR-0017 already flag for their own guessed paths, not a design problem).
  4. Set that path as the .item file's Model field (no __references entry needed — that field is specifically for sbox.game cloud packages per ADR-0016, not project-local assets).

This step cannot be verified from this machine. Record exactly what happened (compiled clean / warnings / errors, final .vmdl path) in handoverdaily.md once it's actually run, the same "report which result happens" posture Assets/Prefabs/README.md already asks for.

7. Proof of concept: weapon_crowbar

Misc/weapon_crowbar.item is the deliberately-chosen first case: a real Equippable melee weapon, single static mesh, no animation or view-model rig required — the simplest possible exercise of §§1-6 end to end before attempting an animated firearm.

Done (this machine, 2026-07-31): ~/Projects/AppleJackRP-Art/weapons/weapon_crowbar/ holds weapon_crowbar.blend (a procedurally-built placeholder mesh — a bent shaft, tapered claw tip, and ribbed handle joined into one weapon_crowbar object, plus a coarse box weapon_crowbar_collision hull, per §4) and its export_fbx.py/weapon_crowbar.fbx output. The FBX was re-imported and verified clean: two objects survive the round-trip (weapon_crowbar, 48 verts; weapon_crowbar_collision, 8 verts) at real-world scale (~0.85 m — a plausible crowbar length). Misc/weapon_crowbar.item's Model field now reads models/weapon_crowbar.vmdl, the same kind of informed guess this project already makes for every cloud-sourced path, following the same models/<item_id>.vmdl convention.

Not done — cannot be done from this machine: §6's actual compile step. Nobody has opened this .fbx in the S&box Model Doc Editor yet, so models/weapon_crowbar.vmdl does not exist as a real in-engine asset and the item still won't render. This proof of concept validates §§1-5 (the mechanical, scriptable half of the pipeline) end to end; §6 is the first real item to exercise once a session has access to the actual editor. Record the outcome in handoverdaily.md when that happens, per §6's own instruction.

The mesh itself is a rough placeholder, not final art — good enough to prove the pipeline moves a .blend to an engine-ready path, not good enough to ship. Revisit the geometry once the compile step is confirmed working, rather than polishing art that might still need a mechanical path correction first.