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 +26 -9
- package/dist/tasks/EnsureWaitForWorld.d.ts +32 -0
- package/dist/tasks/EnsureWaitForWorld.js +91 -0
- package/dist/tasks/EnsureWaitForWorld.js.map +1 -0
- package/dist/tasks/SpawnWorld.d.ts +37 -0
- package/dist/tasks/SpawnWorld.js +72 -0
- package/dist/tasks/SpawnWorld.js.map +1 -0
- package/package.json +1 -1
- package/ue-mcp.plugin.yml +23 -0
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
|
-
| `
|
|
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
|
-
##
|
|
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
|
-
|
|
53
|
+
# Attach the graph to a PCG component, then materialize:
|
|
40
54
|
pcg(action="execute", actorLabel="MyPCGActor")
|
|
41
55
|
```
|
|
42
56
|
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
+
`beforeNode` is whichever node you want to gate — almost 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
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: {}
|