Naming conventions
Naming conventions define how every asset in your project is named and where it lives, both in the engine and on disk. You author one document per project; Dousen compiles it and serves the result to every DCC addon and the Unreal plugin, so a name assembled in Blender matches what Unreal expects without anyone copying rules by hand.
How it works
- You author the document in the web portal — either as YAML (the default) or as a Lua script for computed rules (see below). Editing requires the admin role.
- The server compiles it: the taxonomy tree is resolved, every node gets its full effective validator set baked in, and errors (bad regex, malformed structure) are caught.
- Clients consume the compiled form from
GET /api/projects/:id/conventions/compiledand cache it for 5 minutes — after publishing a change, expect up to that long before every open DCC picks it up.
The current schema is version 3. Its core idea: names are assembled structurally from prefixes, suffixes, and dimensions — not from a single text template.
A complete example
YAMLversion: 3
dimensions:
variant:
required: false
values: [Base, Hero, Damaged]
lod:
required: false
values: [LOD0, LOD1, LOD2]
number:
required: false
values: [] # empty = free-form
validators: # global — apply to every asset
- id: naming.convention
severity: error
- id: naming.no_special_chars
severity: error
asset_types:
StaticMesh:
prefix: SM
suffix: ""
naming:
pattern: "^SM_[A-Za-z0-9_]+$" # guard rail the final name must satisfy
max_length: 64
source_path:
mirrors_engine: true
validators: # scoped to this asset type
- id: mesh.poly_count
severity: warning
params: { max_tris: 50000 }
categories:
Props:
folder: true
subcategories:
Weapons:
folder: true
prefix: Wep
validators: # scoped to this node + everything below
- id: mesh.poly_count
severity: error # tightens the asset-type rule
params: { max_tris: 20000 }
subcategories:
Melee:
folder: true
With that document, a Static Mesh named Sword at Props/Weapons/Melee with variant Hero resolves to the name SM_Wep_Sword_Hero and the engine folder Props/Weapons/Melee.
Keyword reference
Top level
| Key | Type | Required | Notes |
|---|---|---|---|
| version | int | yes | Must be 3. Missing or any other value is a compile error. |
| dimensions | map | no | Orthogonal naming axes appended to names. Default {}. |
| validators | list | no | Global rules applying to every asset. Default []. |
| asset_types | map | no | One entry per asset type. Default {}. |
dimensions.<name>
| Key | Type | Default | Notes |
|---|---|---|---|
| required | bool | false | Advisory — surfaced to client UIs; not enforced at name resolution. |
| values | list of strings | [] | The pick-list shown in DCC "Set Up Asset" UIs. Empty means free-form. Membership is not enforced at resolution. |
Dimension keys are open — you can declare any — but only variant, lod, and number participate in name assembly, in exactly that order. A custom dimension appears in client UIs and convention tags but is never appended to the name. variant is dropped from the name when its value is exactly Base.
Validator entries
The same entry shape is accepted at all three scopes — global, asset type, and taxonomy node. Placement is the scoping; there is no applies_to field. See Asset validation for rule semantics and the catalog.
| Key | Type | Default | Notes |
|---|---|---|---|
| id | string | required | Rule id, e.g. mesh.poly_count. Unknown ids are allowed (a DCC that doesn't have the rule skips it). |
| severity | string | — | error blocks export, warning is advisory. |
| enabled | bool | true | Set false to switch a rule off at this scope (and below). |
| params | map | {} | Rule-specific parameters. When a deeper scope redeclares a rule, its params are merged key-by-key over the shallower ones — so a node can tighten just max_tris without restating everything. |
| name / category | string | — | Optional display metadata; the portal fills these from the catalog. |
asset_types.<name>
| Key | Type | Default | Notes |
|---|---|---|---|
| prefix | string | "" | First token of every name of this type (e.g. SM). |
| suffix | string | "" | Last token of every name of this type. |
| naming.pattern | regex | — | A guard rail the final assembled name must match — not a template. An invalid regex is a compile error naming the asset type. |
| naming.max_length | int | — | Maximum length of the final name. |
| source_path.template_override | template | — | Where the working file lives when it doesn't mirror the engine path. See Source paths. |
| source_path.mirrors_engine | bool | true | Derived, not read: it is true exactly when no template_override is present. Writing it explicitly is harmless documentation. |
| validators | list | [] | Rules scoped to this asset type, merged over the globals. |
| categories | map | {} | The first level of the taxonomy tree. |
subcategories directly under an asset type is a compile error — the first level must be categories; subcategories only appears on nodes inside it.
Taxonomy nodes (categories and subcategories)
Every node under categories has the same shape and may nest subcategories recursively to any depth — Props/Weapons/Melee/Daggers/… is fine. An asset's place in the project is an ordered path of node names.
| Key | Type | Default | Notes |
|---|---|---|---|
| folder | bool | true | When true, the node's name becomes a folder segment in the engine path. Set false for nodes that should only shape the asset name. |
| prefix | string | "" | Name token contributed before the asset name. |
| suffix | string | "" | Name token contributed after the asset name. |
| required | bool | false | Advisory flag for client UIs. |
| validators | list | [] | Rules for this node and everything beneath it. |
| subcategories | map | {} | Child nodes — same shape, any depth. |
How a name is assembled
Dousen joins the following tokens with _, skipping empty ones:
- the asset type's
prefix - each taxonomy node's
prefix, walking the path root to leaf - the asset name
- each taxonomy node's
suffix, also root to leaf (same traversal as the prefixes) - the
variant(unless it isBase), thenlod, thennumbervalues - the asset type's
suffix
The engine folder path is derived separately: walk the same path and join the names of every node whose folder is true with /. Node prefixes and suffixes never affect the folder — only the node names do.
Finally, the assembled name is checked against the asset type's naming.pattern and naming.max_length — that's what the naming.convention validator enforces at export time.
Source paths
By default (mirrors_engine), the working file for an asset lives at the same relative path as its engine folder — a mesh headed for Props/Weapons keeps its .blend/.ma under Props/Weapons in the source root. To place sources elsewhere, give the asset type a template_override:
YAMLsource_path:
template_override: "Art/Source/{{ category }}/{{ subcategory }}"
Templates use Jinja-style double-brace syntax. Available variables:
| Variable | Value |
|---|---|
| category | First taxonomy segment (e.g. Props). |
| subcategory | Second segment (e.g. Weapons). |
| subcategory2, subcategory3, … | Deeper segments, numbered from the third. |
| variant, lod, number, … | The dimension values selected for the asset (any selection key the DCC sends). |
This is the only place templating exists in v3. Legacy single-brace { category } syntax from older documents is migrated to {{ category }} automatically on load. The asset's name is not a template variable — names come from the structural assembly above.
Authoring in Lua
When rules are computed rather than written out — dozens of asset types sharing patterns, prefixes derived from a table, per-category poly budgets from a formula — you can author the document as a Lua script instead of YAML. Switch the editor's mode selector to Lua Script (or save via the API with ?type=lua).
The contract: define a config() function that returns a table with exactly the same v3 structure as the YAML. The server runs it at save and compile time — clients always receive the same compiled JSON regardless of authoring format.
LUA-- Generate several asset types from one table.
local types = {
{ key = "static_mesh", prefix = "SM" },
{ key = "skeletal_mesh", prefix = "SK" },
{ key = "texture", prefix = "T" },
{ key = "animation", prefix = "A" },
}
function config()
local asset_types = {}
for _, t in ipairs(types) do
asset_types[t.key] = {
prefix = t.prefix,
naming = { pattern = "^" .. t.prefix .. "_[A-Za-z0-9_]+$" },
categories = { Characters = {}, Environment = {}, Props = {} },
}
end
return {
version = 3,
dimensions = { number = { required = true, values = {} } },
validators = {
{ id = "naming.convention", severity = "error" },
},
asset_types = asset_types,
}
end
The script runs in a sandbox: only the table, string, and math libraries are available — no os, io, require, load, or debug — with a 1 MB memory ceiling and an instruction limit, so a typo'd infinite loop errors out instead of hanging the server. Compile errors, a missing config(), or a return value that isn't a valid v3 table are all rejected at save, with the exact error shown in the editor.
In Lua mode the portal editor adds a Test Script panel: give it an asset name, an asset type, and a context (taxonomy path plus dimension values as JSON) and it dry-runs the script, returning the resolved name, engine path, and source path — without saving anything.
GET /api/projects/:id/conventions returns whatever you authored — the Lua source verbatim for a Lua document, YAML otherwise. The compiled JSON that clients use is always available from /conventions/compiled, whichever format you author in.
The portal editor
- Editor tab — a code editor with YAML and Lua modes, file upload, and presets to start from: UE5 Allar Style Guide (the default), Minimal, Nanite + Lumen — Static, Nanite + Lumen — Foliage, plus Lua versions of the Allar and Minimal presets.
- Quick Toggle tree — a master–detail view of the compiled taxonomy where you can enable/disable rules, change severities, and edit parameters per node without touching the YAML by hand. Each rule row shows whether it is added here, overridden, or inherited — and from where.
- Validate Names — paste a batch of asset names and check them against the current document before rolling it out.
What is checked when
| Moment | What can fail |
|---|---|
| Saving YAML | Invalid YAML; invalid template syntax in template_override. |
| Saving Lua | Lua compile errors; missing config(); a return value that doesn't compile as v3 (including bad regexes) — Lua saves are fully compile-checked. |
| Compiling (fetch) | Wrong/missing version; invalid naming.pattern regex; subcategories at asset-type level; malformed validator entries. |
| Never | Unknown validator ids (clients skip rules they don't implement); dimension values outside values (advisory). |
A bad regex in a YAML document isn't caught until compile — the plain YAML save only checks syntax and templates. Use the Quick Toggle tree or the Validate Names panel right after saving to confirm the document compiles, or author in Lua, where saves are compile-checked end to end.
API reference
| Endpoint | Purpose |
|---|---|
| GET /api/projects/:id/conventions | The authored document, verbatim — YAML or Lua source. |
| PUT /api/projects/:id/conventions | Replace the document. Admin only. ?type=template (YAML, the default) or ?type=lua; omitting type keeps the stored format. |
| GET /api/projects/:id/conventions/compiled | The compiled JSON clients consume — taxonomy resolved, effective_validators baked onto every asset type and node. |
| GET /api/projects/:id/conventions/json | Alias of /compiled. |
Clients cache the compiled document for 5 minutes. After publishing a change, expect a short delay before every DCC picks it up; the Unreal plugin refreshes on its own timer as well.