ue-mcp-plugin-voxel-plugin 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,8 +8,10 @@ Each action is cited to the C++ header it wraps. Full API reference under [`docs
8
8
 
9
9
  | Category | Action | Wraps |
10
10
  |----------|---------------------------------|-------|
11
- | `pcg` | `voxel_build_scatter_graph` | `UPCGVoxelSamplerSettings` (`VoxelPCG/Public/PCGVoxelSampler.h`) feeding `UPCGStaticMeshSpawnerSettings` |
11
+ | `level` | `voxel_spawn_voxel_world` | `level.place_actor` for `AVoxelWorld` (`Voxel/Public/VoxelWorld.h`) + property defaults so the world renders |
12
12
  | `level` | `voxel_get_voxel_world_status` | 5 zero-arg lifecycle UFUNCTIONs on `AVoxelWorld` (`Voxel/Public/VoxelWorld.h`) |
13
+ | `pcg` | `voxel_build_scatter_graph` | `UPCGVoxelSamplerSettings` (`VoxelPCG/Public/PCGVoxelSampler.h`) feeding `UPCGStaticMeshSpawnerSettings` |
14
+ | `pcg` | `voxel_ensure_wait_for_world` | Splices `UPCGWaitForVoxelWorldSettings` (`VoxelPCG/Public/PCGWaitForVoxelWorld.h`) into an existing graph |
13
15
 
14
16
  The v0.1.0 release shipped three actions that called ue-mcp tasks with wrong parameter names and passed PCG node-type strings that did not exist; v0.1.1 removed them.
15
17
 
@@ -21,9 +23,24 @@ ue-mcp plugin install ue-mcp-plugin-voxel-plugin
21
23
 
22
24
  The CLI adds an entry under `plugins:` in your `ue-mcp.yml`. Restart ue-mcp; the injected action shows up under `pcg`.
23
25
 
24
- ## Usage
26
+ ## 0-to-1 workflow
27
+
28
+ ```text
29
+ # 1. drop a voxel world into the level
30
+ level(action="voxel_spawn_voxel_world", label="MyVoxelWorld")
31
+
32
+ # 2. poll until the runtime finishes its first generation pass
33
+ level(action="voxel_get_voxel_world_status", actorLabel="MyVoxelWorld")
34
+ # => { isRuntimeCreated, isVoxelWorldReady, isProcessingNewState, progress, numPendingTasks }
35
+ # wait for isVoxelWorldReady && !isProcessingNewState before doing anything else.
36
+ ```
37
+
38
+ That's the hello-world. `spawn_voxel_world` defaults `LayerStack` to the plugin-bundled `/Voxel/Default/DefaultStack.DefaultStack` so the actor renders without further setup.
39
+
40
+ ## PCG actions (once a world is live)
25
41
 
26
42
  ```text
43
+ # Build a scatter graph that drops weighted meshes on the voxel surface.
27
44
  pcg(action="voxel_build_scatter_graph",
28
45
  assetPath="/Game/PCG/RockScatter",
29
46
  meshes=[
@@ -32,21 +49,21 @@ pcg(action="voxel_build_scatter_graph",
32
49
  ],
33
50
  pointsPerSquaredMeter=0.05,
34
51
  seed=42)
35
- ```
36
-
37
- The call creates a `UPCGGraph` at `assetPath`, adds a Voxel Sampler → Static Mesh Spawner pipeline, and populates the weighted mesh table. Attach the resulting graph to a PCG component on or near your `AVoxelWorld`, then:
38
52
 
39
- ```text
53
+ # Attach the graph to a PCG component, then materialize:
40
54
  pcg(action="execute", actorLabel="MyPCGActor")
41
55
  ```
42
56
 
43
- Check the voxel world is actually live before scattering / stamping into it:
57
+ If a PCG graph scatters before the voxel runtime finishes generating, you get empty / stale output. The gate is a `WaitForVoxelWorld` node — splice one into any graph idempotently:
44
58
 
45
59
  ```text
46
- level(action="voxel_get_voxel_world_status", actorLabel="MyVoxelWorld")
60
+ pcg(action="voxel_ensure_wait_for_world",
61
+ assetPath="/Game/PCG/RockScatter",
62
+ beforeNode="PCGStaticMeshSpawner")
63
+ # => { waitNode, inserted: true, rewiredEdges: N } # or inserted:false if already gated
47
64
  ```
48
65
 
49
- Returns `{ isRuntimeCreated, isVoxelWorldReady, isProcessingNewState, progress, numPendingTasks }`. The runtime can be mid-regeneration even after `IsRuntimeCreated` flips true wait for `isVoxelWorldReady && !isProcessingNewState` before pipelines that depend on stable terrain.
66
+ `beforeNode` is whichever node you want to gatealmost always your spawner.
50
67
 
51
68
  ## Requirements
52
69
 
@@ -0,0 +1,32 @@
1
+ import { BaseTask, type TaskResult } from "@db-lyon/flowkit";
2
+ interface Options {
3
+ assetPath: string;
4
+ /**
5
+ * The PCG node you want to gate on the voxel world being ready —
6
+ * almost always the spawner that materializes the final result
7
+ * (PCGStaticMeshSpawner, PCGVoxelStampSpawner, etc.). Whichever
8
+ * nodes currently feed `beforeNode` get rerouted through a new
9
+ * `WaitForVoxelWorld` node.
10
+ */
11
+ beforeNode: string;
12
+ }
13
+ /**
14
+ * Insert a `WaitForVoxelWorld` node immediately upstream of `beforeNode`
15
+ * in an existing PCG graph. Idempotent: if any node already feeding
16
+ * `beforeNode` is a Wait node, returns `inserted: false` and changes
17
+ * nothing.
18
+ *
19
+ * Header: `VoxelPCG/Public/PCGWaitForVoxelWorld.h`
20
+ * (`UPCGWaitForVoxelWorldSettings`, control-flow node, no settings).
21
+ *
22
+ * Fixes the most common PCG-on-voxel footgun documented in
23
+ * `docs/VoxelPCG.md`: pipelines that scatter / stamp before the voxel
24
+ * runtime finishes generating produce empty or stale output. The Wait
25
+ * node blocks downstream execution until the voxel world is ready.
26
+ */
27
+ export default class EnsureWaitForWorld extends BaseTask<Options> {
28
+ get taskName(): string;
29
+ protected validate(): void;
30
+ execute(): Promise<TaskResult>;
31
+ }
32
+ export {};
@@ -0,0 +1,91 @@
1
+ import { BaseTask } from "@db-lyon/flowkit";
2
+ const WAIT_NODE_TITLE = "Wait For Voxel World";
3
+ const WAIT_NODE_TYPE = "/Script/VoxelPCG.PCGWaitForVoxelWorldSettings";
4
+ /**
5
+ * Insert a `WaitForVoxelWorld` node immediately upstream of `beforeNode`
6
+ * in an existing PCG graph. Idempotent: if any node already feeding
7
+ * `beforeNode` is a Wait node, returns `inserted: false` and changes
8
+ * nothing.
9
+ *
10
+ * Header: `VoxelPCG/Public/PCGWaitForVoxelWorld.h`
11
+ * (`UPCGWaitForVoxelWorldSettings`, control-flow node, no settings).
12
+ *
13
+ * Fixes the most common PCG-on-voxel footgun documented in
14
+ * `docs/VoxelPCG.md`: pipelines that scatter / stamp before the voxel
15
+ * runtime finishes generating produce empty or stale output. The Wait
16
+ * node blocks downstream execution until the voxel world is ready.
17
+ */
18
+ export default class EnsureWaitForWorld extends BaseTask {
19
+ get taskName() { return "voxel.ensure_wait_for_world"; }
20
+ validate() {
21
+ if (!this.options.assetPath)
22
+ throw new Error("assetPath is required");
23
+ if (!this.options.beforeNode)
24
+ throw new Error("beforeNode is required");
25
+ }
26
+ async execute() {
27
+ const { assetPath, beforeNode } = this.options;
28
+ const graphR = await this.call("pcg.read_graph", { assetPath });
29
+ if (!graphR.success)
30
+ return graphR;
31
+ const graph = (graphR.data ?? {});
32
+ const nodes = graph.nodes ?? [];
33
+ const edges = graph.edges ?? [];
34
+ if (!nodes.some(n => n.name === beforeNode)) {
35
+ return { success: false, error: new Error(`node '${beforeNode}' not found in ${assetPath}`) };
36
+ }
37
+ const inbound = edges.filter(e => e.to === beforeNode);
38
+ if (inbound.length === 0) {
39
+ return { success: false, error: new Error(`no edges flow into '${beforeNode}' in ${assetPath} — nothing to gate`) };
40
+ }
41
+ const nodesByName = new Map(nodes.map(n => [n.name, n]));
42
+ const alreadyGated = inbound
43
+ .map(e => nodesByName.get(e.from))
44
+ .find(n => n?.title === WAIT_NODE_TITLE);
45
+ if (alreadyGated) {
46
+ return {
47
+ success: true,
48
+ data: { assetPath, beforeNode, waitNode: alreadyGated.name, inserted: false, rewiredEdges: 0 },
49
+ };
50
+ }
51
+ const waitR = await this.call("pcg.add_node", { assetPath, nodeType: WAIT_NODE_TYPE });
52
+ if (!waitR.success)
53
+ return waitR;
54
+ const waitName = waitR.data?.nodeName;
55
+ if (!waitName) {
56
+ return { success: false, error: new Error("pcg.add_node WaitForVoxelWorld returned no nodeName") };
57
+ }
58
+ for (const e of inbound) {
59
+ const dis = await this.call("pcg.disconnect_nodes", {
60
+ assetPath,
61
+ sourceNode: e.from,
62
+ sourcePin: e.fromPin,
63
+ targetNode: e.to,
64
+ targetPin: e.toPin,
65
+ });
66
+ if (!dis.success)
67
+ return dis;
68
+ const c1 = await this.call("pcg.connect_nodes", {
69
+ assetPath,
70
+ sourceNode: e.from,
71
+ sourcePin: e.fromPin,
72
+ targetNode: waitName,
73
+ });
74
+ if (!c1.success)
75
+ return c1;
76
+ const c2 = await this.call("pcg.connect_nodes", {
77
+ assetPath,
78
+ sourceNode: waitName,
79
+ targetNode: beforeNode,
80
+ targetPin: e.toPin,
81
+ });
82
+ if (!c2.success)
83
+ return c2;
84
+ }
85
+ return {
86
+ success: true,
87
+ data: { assetPath, beforeNode, waitNode: waitName, inserted: true, rewiredEdges: inbound.length },
88
+ };
89
+ }
90
+ }
91
+ //# sourceMappingURL=EnsureWaitForWorld.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EnsureWaitForWorld.js","sourceRoot":"","sources":["../../src/tasks/EnsureWaitForWorld.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { BaseTask, type TaskResult } from "@db-lyon/flowkit";
2
+ interface Vec3 {
3
+ x: number;
4
+ y: number;
5
+ z: number;
6
+ }
7
+ interface Options {
8
+ label?: string;
9
+ location?: Vec3;
10
+ voxelSize?: number;
11
+ /**
12
+ * Asset path of a UVoxelLayerStack. Defaults to the plugin-bundled
13
+ * `/Voxel/Default/DefaultStack.DefaultStack`. Pass an empty string to
14
+ * leave the property unset (the world will load but render nothing
15
+ * until you assign a stack yourself).
16
+ */
17
+ layerStack?: string;
18
+ /** Optional UVoxelMegaMaterial asset path. */
19
+ megaMaterial?: string;
20
+ }
21
+ /**
22
+ * Drop an `AVoxelWorld` actor into the active level — the first thing
23
+ * any Voxel-Plugin workflow needs. Wraps `level.place_actor` with the
24
+ * Voxel-Plugin class path, then applies `voxelSize` / `LayerStack` /
25
+ * `MegaMaterial` via `level.set_actor_property` so the resulting actor
26
+ * is renderable out of the box.
27
+ *
28
+ * Header: `Voxel/Public/VoxelWorld.h` (`AVoxelWorld`).
29
+ *
30
+ * Pair with `level.voxel_get_voxel_world_status` to poll the runtime
31
+ * until `isVoxelWorldReady` flips true before scattering / stamping.
32
+ */
33
+ export default class SpawnWorld extends BaseTask<Options> {
34
+ get taskName(): string;
35
+ execute(): Promise<TaskResult>;
36
+ }
37
+ export {};
@@ -0,0 +1,72 @@
1
+ import { BaseTask } from "@db-lyon/flowkit";
2
+ const DEFAULT_LAYER_STACK = "/Voxel/Default/DefaultStack.DefaultStack";
3
+ /**
4
+ * Drop an `AVoxelWorld` actor into the active level — the first thing
5
+ * any Voxel-Plugin workflow needs. Wraps `level.place_actor` with the
6
+ * Voxel-Plugin class path, then applies `voxelSize` / `LayerStack` /
7
+ * `MegaMaterial` via `level.set_actor_property` so the resulting actor
8
+ * is renderable out of the box.
9
+ *
10
+ * Header: `Voxel/Public/VoxelWorld.h` (`AVoxelWorld`).
11
+ *
12
+ * Pair with `level.voxel_get_voxel_world_status` to poll the runtime
13
+ * until `isVoxelWorldReady` flips true before scattering / stamping.
14
+ */
15
+ export default class SpawnWorld extends BaseTask {
16
+ get taskName() { return "voxel.spawn_world"; }
17
+ async execute() {
18
+ const { label, location, voxelSize, layerStack, megaMaterial } = this.options;
19
+ const placeParams = {
20
+ actorClass: "/Script/Voxel.VoxelWorld",
21
+ };
22
+ if (label)
23
+ placeParams.label = label;
24
+ if (location)
25
+ placeParams.location = location;
26
+ const placed = await this.call("level.place_actor", placeParams);
27
+ if (!placed.success)
28
+ return placed;
29
+ const actorLabel = placed.data?.actorLabel
30
+ ?? placed.data?.label
31
+ ?? label;
32
+ if (!actorLabel) {
33
+ return { success: false, error: new Error("level.place_actor did not return an actorLabel") };
34
+ }
35
+ const applied = {};
36
+ // Default the LayerStack so a bare AVoxelWorld actually renders.
37
+ // Explicit empty string opts out.
38
+ const resolvedLayerStack = layerStack === "" ? undefined
39
+ : (layerStack ?? DEFAULT_LAYER_STACK);
40
+ const setProp = async (propertyName, value) => {
41
+ const r = await this.call("level.set_actor_property", { actorLabel, propertyName, value });
42
+ if (!r.success)
43
+ return r;
44
+ applied[propertyName] = value;
45
+ return r;
46
+ };
47
+ if (typeof voxelSize === "number") {
48
+ const r = await setProp("VoxelSize", voxelSize);
49
+ if (!r.success)
50
+ return r;
51
+ }
52
+ if (resolvedLayerStack) {
53
+ const r = await setProp("LayerStack", resolvedLayerStack);
54
+ if (!r.success)
55
+ return r;
56
+ }
57
+ if (megaMaterial) {
58
+ const r = await setProp("MegaMaterial", megaMaterial);
59
+ if (!r.success)
60
+ return r;
61
+ }
62
+ return {
63
+ success: true,
64
+ data: {
65
+ actorLabel,
66
+ actorClass: "/Script/Voxel.VoxelWorld",
67
+ applied,
68
+ },
69
+ };
70
+ }
71
+ }
72
+ //# sourceMappingURL=SpawnWorld.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpawnWorld.js","sourceRoot":"","sources":["../../src/tasks/SpawnWorld.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAmB,MAAM,kBAAkB,CAAC;AAmB7D,MAAM,mBAAmB,GAAG,0CAA0C,CAAC;AAEvE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,QAAiB;IACvD,IAAI,QAAQ,KAAa,OAAO,mBAAmB,CAAC,CAAC,CAAC;IAEtD,KAAK,CAAC,OAAO;QACX,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QAE9E,MAAM,WAAW,GAA4B;YAC3C,UAAU,EAAE,0BAA0B;SACvC,CAAC;QACF,IAAI,KAAK;YAAE,WAAW,CAAC,KAAK,GAAG,KAAK,CAAC;QACrC,IAAI,QAAQ;YAAE,WAAW,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAE9C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAC;QACjE,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,MAAM,CAAC;QAEnC,MAAM,UAAU,GAAI,MAAM,CAAC,IAA4D,EAAE,UAAU;eAC/E,MAAM,CAAC,IAAuC,EAAE,KAAK;eACtD,KAAK,CAAC;QACzB,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,gDAAgD,CAAC,EAAE,CAAC;QAChG,CAAC;QAED,MAAM,OAAO,GAA4B,EAAE,CAAC;QAE5C,iEAAiE;QACjE,kCAAkC;QAClC,MAAM,kBAAkB,GAAG,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS;YAC/B,CAAC,CAAC,CAAC,UAAU,IAAI,mBAAmB,CAAC,CAAC;QAE/D,MAAM,OAAO,GAAG,KAAK,EAAE,YAAoB,EAAE,KAAc,EAAE,EAAE;YAC7D,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;YAC3F,IAAI,CAAC,CAAC,CAAC,OAAO;gBAAE,OAAO,CAAC,CAAC;YACzB,OAAO,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC;YAC9B,OAAO,CAAC,CAAC;QACX,CAAC,CAAC;QAEF,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YAClC,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;YAChD,IAAI,CAAC,CAAC,CAAC,OAAO;gBAAE,OAAO,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,kBAAkB,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC;YAC1D,IAAI,CAAC,CAAC,CAAC,OAAO;gBAAE,OAAO,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;YACtD,IAAI,CAAC,CAAC,CAAC,OAAO;gBAAE,OAAO,CAAC,CAAC;QAC3B,CAAC;QAED,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE;gBACJ,UAAU;gBACV,UAAU,EAAE,0BAA0B;gBACtC,OAAO;aACR;SACF,CAAC;IACJ,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ue-mcp-plugin-voxel-plugin",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Voxel Plugin actions for ue-mcp. Cited to the C++ headers each action wraps; full reference under docs/.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/ue-mcp.plugin.yml CHANGED
@@ -13,6 +13,13 @@ inject:
13
13
  pointsPerSquaredMeter: { type: number }
14
14
  seed: { type: number }
15
15
 
16
+ ensure_wait_for_world:
17
+ task: voxel.ensure_wait_for_world
18
+ description: "Insert a WaitForVoxelWorld node immediately upstream of beforeNode in an existing PCG graph. Idempotent — if a Wait node is already gating beforeNode, returns inserted:false. Fixes the standard PCG-on-voxel footgun: pipelines that run before the voxel runtime finishes generating produce empty/stale output. Wraps UPCGWaitForVoxelWorldSettings (VoxelPCG/Public/PCGWaitForVoxelWorld.h). Params: assetPath, beforeNode (the node to gate — typically your spawner)."
19
+ schema:
20
+ assetPath: { type: string, required: true }
21
+ beforeNode: { type: string, required: true }
22
+
16
23
  level:
17
24
  get_voxel_world_status:
18
25
  task: voxel.get_world_status
@@ -21,14 +28,30 @@ inject:
21
28
  actorLabel: { type: string, required: true }
22
29
  world: { type: string }
23
30
 
31
+ spawn_voxel_world:
32
+ task: voxel.spawn_world
33
+ description: "Drop an AVoxelWorld actor into the active level - the prerequisite for every other Voxel-Plugin operation. Wraps level.place_actor with actorClass=/Script/Voxel.VoxelWorld, then applies VoxelSize / LayerStack / MegaMaterial via level.set_actor_property. LayerStack defaults to the plugin-bundled /Voxel/Default/DefaultStack.DefaultStack so the world renders out of the box; pass empty string to opt out. Header: Voxel/Public/VoxelWorld.h. Params: label?, location? ({x,y,z}), voxelSize? (cm, default 100), layerStack? (asset path or '' to skip), megaMaterial? (asset path)."
34
+ schema:
35
+ label: { type: string }
36
+ location: { type: object }
37
+ voxelSize: { type: number }
38
+ layerStack: { type: string }
39
+ megaMaterial: { type: string }
40
+
24
41
  knowledge: {}
25
42
 
26
43
  tasks:
27
44
  voxel.build_scatter_graph:
28
45
  class_path: tasks/BuildScatterGraph
29
46
  description: "Build a Voxel-Sampler-driven PCG mesh scatter graph"
47
+ voxel.ensure_wait_for_world:
48
+ class_path: tasks/EnsureWaitForWorld
49
+ description: "Splice WaitForVoxelWorld into an existing PCG graph"
30
50
  voxel.get_world_status:
31
51
  class_path: tasks/GetWorldStatus
32
52
  description: "Snapshot AVoxelWorld lifecycle state in one call"
53
+ voxel.spawn_world:
54
+ class_path: tasks/SpawnWorld
55
+ description: "Spawn an AVoxelWorld actor with sane defaults"
33
56
 
34
57
  flows: {}