Unreal Engine

Updated July 2026

DousenUE is an editor-only plugin for Unreal Engine 5. It keeps Unreal in step with the rest of your pipeline without anyone touching the Content Browser by hand: an asset exported from a DCC lands in Unreal (or offers to), asset names are re-checked against your project's conventions on the way in, and assets can be handed off between Unreal and any other app on the bridge in either direction.

The plugin talks to exactly two things, both over HTTP/JSON — the Dousen server's REST API (DousenCore, default port 8080) and the DousenDesktop bridge relay (default port 8766). There is no gRPC, no separate socket protocol to install, and no feature here works without a valid Dousen license behind the DousenCore instance it talks to.

This page covers the plugin as it exists in Source/ today. A couple of design docs bundled in the plugin folder (README.md, kickstart_ue.md) describe an earlier plan for an in-editor task list with Jira status buttons — that never shipped in the actual C++ and isn't part of the current plugin. Task management lives entirely in DousenDesktop; see Jira tasks.

Installing and configuring

  1. Copy the plugin into your project.

    Copy the DousenUE folder into your project's Plugins/ directory. (The authoritative source lives in the dousen-io/dousenUE.git submodule, mounted at DousenDesktop/plugins/DousenUE in the Dousen monorepo — copy or symlink from there.)

  2. Regenerate your project files.
  3. Open the project in Unreal Engine 5. The plugin is editor-only and loads automatically with the editor.
  4. Edit Config/BaseDousenUE.ini under the section [/Script/DousenUE.DousenProjectSettings] before relying on conventions or the material catalog — both stay off until ProjectId is set.
KeyTypeDefaultMeaning
ServerSchemestringhttpsScheme for the DousenCore host. Defaults to https so an unconfigured remote host never silently falls back to cleartext — see the loopback rule below.
ServerHoststringlocalhost:8080Host and port of the DousenCore REST API, e.g. your-host:8080.
BridgeRelayPortint8766Port of the DousenDesktop HTTP bridge relay (BridgeHttpRelay). Matches DousenDesktop's own default.
ProjectIdstring (UUID)emptyYour Dousen project's UUID. Conventions fetching, material-catalog reporting, and their refresh timers are all skipped entirely while this is empty — not just disabled, never even attempted.
bAutoReimportOnBridgeEventbooltrueAutomatically reimport (or offer to import) a mesh or texture when a mesh.export/texture.export bridge event arrives while the editor is open. Gates only the reimport step — DCC registration and Open in DCC keep working regardless.
MaterialCatalogsoft object pathunsetPath to a UDousenMaterialCatalog Data Asset living in your project's /Game/ content. Empty skips material-catalog reporting entirely (fail-open to legacy import behavior). See Material catalog and the Nanite gate.

The plugin ships with a starter Config/BaseDousenUE.ini:

INI[/Script/DousenUE.DousenProjectSettings]
; Use https for any non-loopback DousenCore host. http is only accepted for
; localhost / 127.0.0.0/8 / ::1; cleartext to a remote host is refused (DOU-192).
ServerScheme=http
ServerHost=localhost:8080
BridgeRelayPort=8766
; DousenCore project UUID — set this to your project's ID to enable conventions
ProjectId=
; Soft path to the TA-authored material catalog Data Asset (must live in /Game
; project content), e.g.
; MaterialCatalog=/Game/Pipeline/DA_DousenMaterialCatalog.DA_DousenMaterialCatalog
;MaterialCatalog=

Cleartext HTTP only works to loopback (DOU-192). ServerScheme=http is accepted only when ServerHost resolves to localhost, 127.0.0.0/8, or ::1 — exactly why the shipped default (localhost:8080) is safe as-is. Point ServerHost at any other machine and the plugin refuses the request outright rather than send credentials or JWTs unencrypted, logging "Refusing to contact DousenCore over cleartext HTTP on a non-loopback host. Set [DousenUE] ServerScheme=https…". Switch to ServerScheme=https for anything beyond your own machine.

Panels

The plugin registers two dockable Nomad tabs under the editor's Window menu, titled exactly Dousen and Dousen Bridge:

Neither panel is a task or Jira UI, and the Bridge panel's event table isn't the same list that drives Open in DCC — that submenu is populated separately from GET /bridge/dccs (below). Task assignment, Jira status, and checkout/submit all live in DousenDesktop, not in Unreal.

Automatic asset sync

Unreal already ships an Auto Reimport that watches source files on disk and refreshes an asset when its .fbx or .tga changes. Dousen's asset sync is deliberately not that. A file-watcher only ever knows "a file changed" — it has no idea where the asset belongs in your project, what its textures are, or how it should be set up. FAssetSyncManager acts on the bridge export event instead, which carries the pipeline metadata a disk watcher never sees, and uses it to land the export correctly, not just freshly:

None of that is derivable from a file path alone, which is exactly why Unreal's stock watcher can't do it — it all rides on the bridge event's metadata. The reimport of an already-existing asset is the one part that overlaps with stock Auto Reimport; everything above is the part that doesn't.

How the event reaches the editor

Getting the event into the editor is deliberately unremarkable — the value is in the handling above, not the transport. Two paths feed FAssetSyncManager:

What happens when an event lands depends on whether the computed target package already exists:

Naming-convention violations are checked on the same event and surface as their own toast, "[Dousen] Naming: <message>" (8 seconds) — a violation doesn't block the import itself, it's advisory.

When an event also carries a material assignment (see the material catalog), the plugin classifies each incoming texture by filename to a channel — BaseColor, Normal, ARM (packed occlusion/roughness/metallic), Roughness, Metallic, AO, Emissive, Opacity, Height, or Mask — plus its sRGB and compression settings. That classifier (ClassifyTexture/SlotForTexture) is deliberately kept identical to DousenDesktop's python/dousen/texture_classify.py (both carry an explicit "mirrors … — change together" comment), so a texture gets the same channel guess whether that guess happens DCC-side at export or here at import.

Naming conventions on import

FConventionsConfig parses the compiled conventions JSON fetched from DousenCore's GET /api/projects/:id/conventions/compiled. The fetch runs once at editor startup and then on a 300-second (5-minute) timer — but only while ProjectId is set; with it empty, neither the initial fetch nor the timer is ever scheduled.

Three resolver methods do the actual work, all driven off the tag map attached to a bridge event, plus a fourth used purely for the naming-violation toast above:

MethodWhat it does
ResolveNameFills a template like SM_{asset_name}_{variant} — prefix, taxonomy prefixes, asset name, taxonomy suffixes, variant, LOD, number, suffix, joined with _ — from the tags on the event.
ResolveEnginePathWalks the project's taxonomy tree, joining the path segments of whichever nodes are flagged as folders, to produce a /Game/... destination.
ResolveSourcePathMirrors the engine path by default. If an asset type's convention instead sets a source-path override with its own template, renders that template's {{ key }} placeholders against the tags — for source files that don't live in the same shape as the imported asset.
ValidateNameChecks a name against the asset type's convention and returns the list of violations, if any — this is what produces the "[Dousen] Naming: …" toast.

This is what lets asset sync (above) compute a destination path deterministically instead of searching the content tree, and it's the plugin's second line of defense on naming — the first is the naming.convention validator that already ran inside the DCC before export. A name that somehow slipped past that check (or was renamed after export, before publishing) still gets caught here on the way into Unreal.

Open in DCC

FDousenContentBrowserExtension adds a context-menu extender for exactly three classes — StaticMesh, SkeletalMesh, and Texture2D — no others. Right-clicking one of these in the Content Browser adds a Dousen section to the Common Asset Actions group with two entries:

The DCC list comes from GET /bridge/dccs, fetched once at plugin startup and refreshed on a 60-second timer (which also re-sends Unreal's own registration, in case DousenDesktop restarts) — so a DCC that connects to the bridge after the editor is already open shows up in the submenu within a minute.

Unreal as a bridge participant

The plugin registers itself with the relay as DCC id unreal / display name Unreal Engine, including the local command-server port from above, and re-registers (a heartbeat, effectively) every 30 seconds — comfortably inside the relay's 90-second registration TTL (see the Bridge API reference) — and unregisters cleanly on editor shutdown.

Saving a Static Mesh, Skeletal Mesh, or Texture2D asset publishes mesh.export or texture.export; saving a Material or Material Instance publishes material.export; saving a Landscape actor publishes asset.export — each carrying the asset's path, class, and package filename, so other tools and DousenCore's asset tracking see the change immediately. Incoming dcc.open events targeted at unreal (or with no target at all) resolve the requested asset path through the Asset Registry and navigate the Content Browser straight to it.

Material catalog and the Nanite gate

Material catalog integration is implemented end-to-end — Unreal, Substance Painter, Substance Designer, Blender, Maya, 3ds Max, and Houdini all read it — but it's still pending a full workstation-based end-to-end verification pass before it's considered production-hardened. Expect edge-case behavior (unresolved materials, mixed assignments) to be refined.

A Dousen Material Catalog is a UDousenMaterialCatalog Data Asset that a TA authors once per project (add one from the Content Browser: Add → Miscellaneous → Data Asset → Dousen Material Catalog). Because the plugin itself ships with no project content, the catalog instance has to live under your project's own /Game/ tree — point MaterialCatalog at it. Each entry describes one master or parent material:

FieldMeaning
IdStable, TA-edited slug (lowercase, underscores). DCC payloads reference materials by this id. Auto-filled by Auto Populate From Materials below.
DisplayNameHuman-readable name shown in DCC-side material pickers.
MaterialSoft reference to the actual master/parent UMaterialInterface.
bAllowNaniteIf false, meshes assigned this material must not enable Nanite. Drives both the import-time Nanite gate below and the DCC-side Nanite/NonNanite validator gate.
TextureSlotsPer-slot declarations: ParamName, a Content semantic (Albedo, Normal, Roughness, Metallic, ARM, ORM, AO, Emissive, Opacity, Height, Mask, or Custom), bSRGB, a Compression preset (Default, NormalMap, Masks, Grayscale, HDR), and MatchKeywords for auto-matching filenames.
ScalarParamsPer-param declarations: Name, Default, Min, Max, Label.
VectorParamsPer-param declarations: Name, Default color, Label.

The Auto Populate From Materials editor button (AutoPopulateFromMaterials) speeds up authoring: for every entry with a Material assigned, it fills in Id/DisplayName when empty and enumerates that material's actual texture/scalar/vector parameters, guessing each texture slot's content/sRGB/compression/keywords from its parameter name using the same keyword table ClassifyTexture uses. It never clobbers a field you've already filled in and never removes a declaration you added by hand — so the TA workflow is: assign the material, click the button, then review the guesses and set bAllowNanite by hand (it's never guessed).

Once configured, the plugin reports the catalog to DousenCore with POST /api/projects/:id/material-catalog (admin-gated on DousenCore's side, like the rest of the pipeline-config endpoints): once at startup, again on the same 5-minute conventions-refresh timer, and again immediately whenever the catalog asset itself is saved. Each report is skipped if a CRC32 hash of the serialized catalog JSON matches the last successful report — so an unchanged catalog isn't re-sent every five minutes, and a failed POST retries on the very next cycle rather than being silently dropped. DCC-side material pickers (Substance Painter/Designer, Blender, Maya, 3ds Max, Houdini) read the same catalog back via GET on that endpoint, so artists choose from a materials list scoped to what's actually approved for the project instead of a raw asset browse — see DCC addons.

How the import-side Nanite gate works

This is the import half of the single Nanite gate described from the export side in Asset validation. When the plugin assigns catalog materials to a reimported mesh, it ANDs bAllowNanite across every material slot the mesh resolves to:

This gating only ever runs when the import event actually carries a material assignment — the legacy flat texture.export path (no catalog, textures matched by filename only) never touches Nanite at all.

Material Instances created from catalog entries go through the same naming-convention resolver as every other asset, under asset type material_instance, falling back to a legacy MI_ prefix if the project's conventions don't define that asset type yet. Their texture, scalar, and vector parameters are copied straight from the catalog entry's declared slots onto a new Material Instance parented to that entry's master material.

CV tag search

On import or reimport, ApplyCvMetadata stamps whatever CV tags a bridge event or sidecar carries onto the asset's package metadata as dousen_cv_<key> asset-registry tags — sourced upstream from DousenDesktop's local, always-on CV auto-tagger and its review dialog before export. The Dousen panel's Tag Search area pre-populates its key dropdown with the five known keys:

Add a row per tag you want to filter on, fill in a value, and Search builds an AND query (an FARFilter over every key/value pair, recursive under /Game) and syncs the Content Browser's selection to every match, reporting "N assets matched". Clear resets the query. Pair it with Show Dousen Tags (above) when you want to see what's actually stamped on one specific asset first, rather than searching blind.

Headless import

You don't have to keep Unreal open to receive assets. DousenDesktop's importer treats the editor as "active" if the plugin has polled the bridge within the last 6 seconds:

Both the toggle and the executable path are configured on the DousenDesktop side, under Settings → Unreal.

Connecting to the bridge

The plugin authenticates to two different servers with two different bearer tokens: a DousenCore JWT (cached from your desktop login, or a DOUSEN_TOKEN environment override) for the REST API on port 8080, and the bridge relay's own per-session token (rotated every time DousenDesktop restarts, read from a file or a DOUSEN_BRIDGE_TOKEN environment override) for everything on port 8766 — see the Bridge API reference for the token file locations and rotation behavior. Plain HTTP to DousenCore is refused outside loopback per the cleartext rule above; the bridge relay itself is loopback-only by construction.

If the relay goes quiet, the poller backs off automatically as described in asset sync and recovers on its own once DousenDesktop is reachable again — no restart of the editor required.

Diagnostics

Everything the plugin does is logged to the editor's Output Log under the LogDousenUE category (and mirrored to the project's Saved/Logs/). Filter on it first when an import, a naming check, or a material-catalog report doesn't happen as expected — see Troubleshooting for the common cases.