A standalone Unreal Engine Blueprint visualizer for the web, with some modifications to suit its use on my portfolio site: www.kris-j.com.
These modifications include: (This is still a work-in-progress. The following may change, shrink, or grow)
- Editor-facing Node Names
- Ability to open referenced Material Functions
- Material graph inspection and Unreal-style preview-target selection (press W)
- Node visuals that closer match the UE5 editor
- Draggable nodes with a reset, and host-facing chrome colours
- Copying a selection back out as Unreal-pasteable clipboard text
Klee accepts Unreal Material graph clipboard text, including the nested
MaterialExpression object blocks produced by the editor. Material nodes use
editor-facing captions when those values are serialized. This includes
parameter names, custom descriptions, common expression names, and the exact
asset name of a Material Function call. Unknown expression types receive a
readable title derived from their Unreal class name.
The Material Function call stores both the asset name and canonical Unreal object path. The referenced function graph is not embedded in the parent clipboard data; a host can use the activation event below to resolve and load a separately supplied function graph.
display() automatically classifies clipboard text as blueprint, material,
material-function, material-fragment, mixed, or unknown, and returns the
same inspection object exposed by viewer.inspection:
const viewer = Klee.init(document.querySelector("canvas.klee"));
const inspection = viewer.display(source, {
graph: {
material: {
domain: "MD_UI",
blendMode: "BLEND_Translucent",
shadingModel: "MSM_Unlit",
useMaterialAttributes: false,
unrealVersion: "5.7",
// Optional exact editor-facing names; overrides built-in rules.
rootInputs: ["Final Color", "Opacity"],
},
},
});Klee prefers Material settings serialized in the pasted root/Material block,
records whether each value was serialized, authored, or unknown, and warns
when authored fallback metadata conflicts. Without an explicit rootInputs
list, Klee only filters root pins for a deliberately small, versioned set of
common UE5 UI-domain cases. Unsupported or incomplete metadata leaves the
serialized pins unchanged; Klee does not claim complete Unreal-version parity.
For a Material-family graph, select one Material node and press W, or use the
keyboard-operable Preview toolbar button, to mark it as the preview target.
Pressing W again returns to the Material root. With no selection, W clears an
active node preview; multiple selections do not change the active target.
viewer.togglePreviewSelected();
viewer.clearPreview();
const state = viewer.getPreviewState();
canvas.addEventListener("klee:previewchange", event => {
console.log(event.detail.nodeName, event.detail.reason);
});The blue outline identifies the effective output target. This build does not
compile Unreal expressions or render Material pixels: inspection and preview
events explicitly report pixelRenderingAvailable: false, allowing a host to
provide an honest unavailable state or attach a compatible renderer.
Selecting nodes and pressing Ctrl+C, or Cmd+C on macOS, copies them as
Unreal clipboard text, so
they paste straight into a graph in the editor. Each node contributes its
original object block; links to nodes that were not copied are dropped on paste,
exactly as they are when copying a partial selection inside Unreal.
viewer.selectionText; // the same text, or "" when nothing is selectedWith nothing selected the clipboard is left untouched rather than overwritten with an empty string.
Dragging a node moves it and every other selected node, snapping to the 16px minor grid. The position the pasted graph gave each node is kept separately, so a layout can always be undone:
viewer.hasMovedNodes; // true once anything has been dragged
viewer.resetNodePositions(); // returns true if it moved anything backThe toolbar shows a Reset layout button only while there is something to reset, and reloading a graph re-reads the authored positions.
Klee.init starts an animation frame loop and installs listeners on window,
document and the canvas. A host that mounts and unmounts the canvas repeatedly
— a single-page application, most obviously — must release each viewer, or every
mount leaves a frame loop and its listeners running:
const viewer = Klee.init(canvas);
// ...
viewer.destroy();destroy() cancels the frame loop, detaches every listener, unloads the parsed
scene, returns the canvas to the parent it had before the overlay wrapped it,
and frees the instance registry slot. It is idempotent, Klee.get() stops
returning a destroyed viewer, and the same canvas can be passed to Klee.init
again afterwards.
Double-clicking a rendered node dispatches a bubbling, cancelable
klee:nodeactivate CustomEvent from the canvas. Its detail is a plain,
serializable object:
const canvas = document.querySelector("canvas.klee");
canvas.addEventListener("klee:nodeactivate", (event) => {
const {
nodeName,
nodeClass,
title,
expressionClass,
assetName,
objectPath,
reference,
references,
} = event.detail;
// reference is the first generic graph reference; references contains all.
// Legacy assetName and objectPath remain for Material Function calls.
});Every node activation includes nodeName, nodeClass, and title.
expressionClass is included for Material expression nodes, while assetName
and objectPath are included only when the node references a Material Function.
The exported KLEE_NODE_ACTIVATE_EVENT constant contains the event name.
Canvas nodes are not keyboard focus targets. Hosts that use node activation
must provide an equivalent keyboard-operable control, such as a list of linked
referenced graphs beside the canvas.
The generic reference/references contract covers Material Functions,
Blueprint function calls, Blueprint macros, and collapsed graphs. References
include the available asset path, graph/member name, GUID, self-context, and
whether they are built in or navigable. Native /Script calls are marked as
built-in and non-navigable; hosts should only open graph source that they have
explicitly mapped.
To build a minified JavaScript file of klee you have to install the development dependencies:
npm installAs soon as the dependencies are installed you can run the following command to build a minified JavaScript file.
npm run buildYou can find the output at dist/klee.min.js relative to the root of the project directory.
To build the complete static website for deployment, run:
npm ci
npm run build:siteThe deployable website is written to site-dist/. This build copies the site
from docs/ and replaces site-dist/js/klee.min.js with the freshly compiled
library, so the deployed website cannot use a stale library bundle.
The Deploy Hostinger site GitHub Actions workflow runs after every push to
main. It builds the complete website and publishes only the contents of
site-dist/ to the deploy branch.
In Hostinger, connect the repository using Advanced → Git, select the
deploy branch, set the root directory to public_html, and enable automatic
deployment. Hostinger then receives a clean static site with index.html at
the branch root; source files and development dependencies are not published.
npm install
npm run devTo preview the documentation site against the current source, run:
npm run docsThis compiles src/ straight to docs/js/klee.min.js, the path
docs/index.html loads. That file is generated and git-ignored on purpose: a
committed copy silently goes stale against src/, so the demo would show a
library that no longer matches the code. npm run build:site writes the
production bundle to the same path inside site-dist/.