@womp/spark-agent 7.0.0 → 9.0.0
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/CHANGELOG.md +67 -0
- package/README.md +3 -2
- package/dist/capabilities/geometry.d.ts +17 -5
- package/dist/capabilities/geometry.js +11 -12
- package/dist/generated/geometry-declarations.d.ts +1 -1
- package/dist/generated/geometry-declarations.js +1 -1
- package/dist/geometry/backendRegistry.d.ts +1 -0
- package/dist/geometry/common.d.ts +5 -2
- package/dist/geometry/common.js +18 -3
- package/dist/geometry/compiler.js +2 -2
- package/dist/geometry/diagnostics.d.ts +2 -7
- package/dist/geometry/diagnostics.js +3 -51
- package/dist/geometry/graph.d.ts +1 -1
- package/dist/geometry/graph.js +1 -0
- package/dist/geometry/index.d.ts +0 -1
- package/dist/geometry/index.js +0 -3
- package/dist/geometry/lift.js +105 -113
- package/dist/geometry/lower.js +29 -49
- package/dist/geometry/schemas/flowir-dialect.json +117 -117
- package/dist/geometry/writeParamData.js +4 -4
- package/dist/geometry.d.ts +2 -5
- package/dist/geometry.js +7 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +0 -1
- package/dist/intent-bench.js +1 -1
- package/dist/kakapo.d.ts +1 -1
- package/dist/scene-operations.d.ts +2 -17
- package/dist/scene-operations.js +4 -40
- package/dist/sdk-geometry-host.d.ts +1 -1
- package/dist/sdk-geometry-host.js +252 -251
- package/dist/sdk-openscad-host.js +1 -3
- package/dist/sdk-scene-host.d.ts +3 -5
- package/dist/sdk-scene-host.js +5 -15
- package/dist/server/sqlite-store.d.ts +1 -5
- package/dist/server/sqlite-store.js +3 -40
- package/dist/server/standalone.js +0 -1
- package/dist/womp-scene-api.d.ts +9 -1
- package/dist/womp-scene-api.js +41 -32
- package/package.json +2 -2
|
@@ -1,293 +1,294 @@
|
|
|
1
|
+
import { KakapoError, } from "@womp/kakapo-sdk";
|
|
1
2
|
import { SceneOperationError } from "./sdk-scene-host.js";
|
|
2
|
-
import { compileGeometry, migrateGraphExt, readEditableGeometry
|
|
3
|
-
import { graphExtSchema } from "./geometry/graph.js";
|
|
3
|
+
import { compileGeometry, migrateGraphExt, readEditableGeometry } from "./geometry/compiler.js";
|
|
4
4
|
import { boundedDiagnostics } from "./geometry/diagnostics.js";
|
|
5
|
-
import {
|
|
5
|
+
import { graphExtSchema } from "./geometry/graph.js";
|
|
6
|
+
import { migrateParameterOverrides, readParameterOverrides } from "./geometry/parameters.js";
|
|
7
|
+
import { convertAABB, sceneNodePosition, sceneNodeRotation, sceneNodeSize } from "./womp-scene-api.js";
|
|
8
|
+
import { sceneUnitMultiplier } from "./coordinates.js";
|
|
6
9
|
import { unverifiedSceneHealth } from "./scene-health.js";
|
|
7
|
-
import { createGeometryRepairTracker
|
|
10
|
+
import { createGeometryRepairTracker } from "./repair-state.js";
|
|
11
|
+
const MEMORY_LIMIT = "Scene memory limits prevent applying this graph.";
|
|
8
12
|
function errorMessage(error) {
|
|
9
13
|
return error instanceof Error ? error.message : String(error);
|
|
10
14
|
}
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17
|
+
}
|
|
11
18
|
function unusedId(entries) {
|
|
12
19
|
let id = 0;
|
|
13
20
|
while (id in entries)
|
|
14
21
|
id++;
|
|
15
22
|
return id;
|
|
16
23
|
}
|
|
17
|
-
function
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
return { graphs: {}, instances: {} };
|
|
21
|
-
const value = scripts;
|
|
24
|
+
function readScripts(scene) {
|
|
25
|
+
const stored = scene.getSceneStorage().scripts;
|
|
26
|
+
const scripts = isRecord(stored) ? stored : {};
|
|
22
27
|
return {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
instances: value.instances && typeof value.instances === "object"
|
|
27
|
-
? value.instances
|
|
28
|
-
: {},
|
|
28
|
+
...scripts,
|
|
29
|
+
graphs: isRecord(scripts.graphs) ? scripts.graphs : {},
|
|
30
|
+
instances: isRecord(scripts.instances) ? scripts.instances : {},
|
|
29
31
|
};
|
|
30
32
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
function readGraphExts(scene) {
|
|
34
|
+
const graphExts = scene.getSceneExt(["graphsExt"]).graphsExt;
|
|
35
|
+
return isRecord(graphExts) ? graphExts : {};
|
|
36
|
+
}
|
|
37
|
+
function graphExtOf(graphExts, graphId) {
|
|
38
|
+
return graphExtSchema.parse(graphExts[graphId] ?? {});
|
|
36
39
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
40
|
+
function graphInstances(scripts, graphId) {
|
|
41
|
+
return Object.entries(scripts.instances).filter(([, instance]) => instance.graph_id === graphId);
|
|
42
|
+
}
|
|
43
|
+
function objectIdsByInstance(scene) {
|
|
44
|
+
const objectIds = new Map();
|
|
45
|
+
for (const node of scene.listNodes({ kind: "union" })) {
|
|
46
|
+
if (node.kind !== "union" || node.scriptInstanceId === undefined)
|
|
47
|
+
continue;
|
|
48
|
+
const ids = objectIds.get(node.scriptInstanceId);
|
|
49
|
+
if (ids)
|
|
50
|
+
ids.push(node.id);
|
|
51
|
+
else
|
|
52
|
+
objectIds.set(node.scriptInstanceId, [node.id]);
|
|
53
|
+
}
|
|
54
|
+
return objectIds;
|
|
55
|
+
}
|
|
56
|
+
function resolveTarget(scene, objectId) {
|
|
57
|
+
const node = scene.getNode(objectId);
|
|
39
58
|
if (objectId !== 0 && node.kind !== "union" && node.kind !== "group")
|
|
40
59
|
throw new Error("Geometry Nodes requires an Area, Group, or scene root target.");
|
|
60
|
+
const scripts = readScripts(scene);
|
|
41
61
|
const instanceId = node.kind === "union" ? node.scriptInstanceId : undefined;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
if (instanceId === undefined)
|
|
63
|
+
return { node, scripts };
|
|
64
|
+
const instance = scripts.instances[instanceId];
|
|
65
|
+
if (!instance)
|
|
45
66
|
throw new Error("The target's Geometry Nodes instance is missing. Inspect it before editing.");
|
|
46
|
-
const graphId = instance
|
|
47
|
-
const graph =
|
|
48
|
-
if (
|
|
67
|
+
const graphId = instance.graph_id;
|
|
68
|
+
const graph = scripts.graphs[graphId];
|
|
69
|
+
if (graph === undefined || !Number.isInteger(graphId))
|
|
49
70
|
throw new Error("The target's Geometry Nodes graph is missing. Inspect it before editing.");
|
|
50
|
-
return { node,
|
|
71
|
+
return { node, scripts, graphId, instanceId, graph };
|
|
51
72
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
objectId,
|
|
56
|
-
title: target.node.name,
|
|
57
|
-
graphId: target.graphId,
|
|
58
|
-
instanceId: target.instanceId,
|
|
59
|
-
source: null,
|
|
60
|
-
instances: [],
|
|
61
|
-
diagnostics: [],
|
|
62
|
-
};
|
|
63
|
-
if (target.graphId === undefined || target.graph === undefined)
|
|
64
|
-
return result;
|
|
73
|
+
function describeGraph(api, target) {
|
|
74
|
+
if (target.graph === undefined)
|
|
75
|
+
return { source: null, instances: [], diagnostics: [] };
|
|
65
76
|
try {
|
|
66
|
-
const ext =
|
|
77
|
+
const ext = graphExtOf(readGraphExts(api), target.graphId);
|
|
67
78
|
const editable = readEditableGeometry(target.graph, ext);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const { params } = readParameterOverrides(editable.graph, ext, instance.data ?? {});
|
|
73
|
-
result.instances.push({
|
|
79
|
+
const objectIds = objectIdsByInstance(api);
|
|
80
|
+
return {
|
|
81
|
+
source: editable.source,
|
|
82
|
+
instances: graphInstances(target.scripts, target.graphId).map(([id, instance]) => ({
|
|
74
83
|
instanceId: Number(id),
|
|
75
|
-
objectIds:
|
|
76
|
-
params,
|
|
77
|
-
})
|
|
78
|
-
|
|
84
|
+
objectIds: objectIds.get(Number(id)) ?? [],
|
|
85
|
+
params: readParameterOverrides(editable.graph, ext, instance.data ?? {}).params,
|
|
86
|
+
})),
|
|
87
|
+
diagnostics: [],
|
|
88
|
+
};
|
|
79
89
|
}
|
|
80
90
|
catch (error) {
|
|
81
|
-
|
|
82
|
-
// Bounded: a compiler diagnostic is unbounded text (./geometry/diagnostics.ts).
|
|
83
|
-
result.diagnostics.push(...boundedDiagnostics([errorMessage(error)]));
|
|
91
|
+
return { source: null, instances: [], diagnostics: boundedDiagnostics([errorMessage(error)]) };
|
|
84
92
|
}
|
|
85
|
-
return structuredClone(result);
|
|
86
93
|
}
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
async function readPlacement(api, objectId, target) {
|
|
95
|
+
if (objectId === 0)
|
|
96
|
+
return { transform: null, bounds: null, diagnostics: [] };
|
|
97
|
+
const reader = { requireNode: (id) => api.getNode(id), getNode: (id) => api.getNode(id) };
|
|
98
|
+
const unitMultiplier = sceneUnitMultiplier(api.getSceneProperties().unit_multiplier);
|
|
99
|
+
const transform = {
|
|
100
|
+
position: sceneNodePosition(reader, 0, unitMultiplier, objectId),
|
|
101
|
+
rotation: sceneNodeRotation(reader, objectId),
|
|
102
|
+
size: sceneNodeSize(reader, 0, unitMultiplier, objectId),
|
|
103
|
+
};
|
|
104
|
+
if (target.graph === undefined || target.node.parentId !== 0)
|
|
105
|
+
return { transform, bounds: null, diagnostics: [] };
|
|
106
|
+
try {
|
|
107
|
+
return { transform, bounds: convertAABB(await api.getNodeBoundingBox(objectId), unitMultiplier), diagnostics: [] };
|
|
97
108
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
additions.push({
|
|
103
|
-
op: "add",
|
|
104
|
-
path: "/scripts/graphs",
|
|
105
|
-
value: {},
|
|
106
|
-
});
|
|
107
|
-
if (!record.instances)
|
|
108
|
-
additions.push({
|
|
109
|
-
op: "add",
|
|
110
|
-
path: "/scripts/instances",
|
|
111
|
-
value: {},
|
|
112
|
-
});
|
|
113
|
-
if (additions.length)
|
|
114
|
-
draft.patchSceneStorage(additions, { returnScene: false });
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (error instanceof KakapoError && error.code === "EMPTY_BOUNDING_BOX")
|
|
111
|
+
return { transform, bounds: null, diagnostics: [] };
|
|
112
|
+
return { transform, bounds: null, diagnostics: [`Bounds are unavailable: ${errorMessage(error)}`] };
|
|
115
113
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
114
|
+
}
|
|
115
|
+
async function readTarget(api, objectId) {
|
|
116
|
+
const target = resolveTarget(api, objectId);
|
|
117
|
+
const graph = describeGraph(api, target);
|
|
118
|
+
const placement = await readPlacement(api, objectId, target);
|
|
119
|
+
return {
|
|
120
|
+
kind: target.graph === undefined ? "container" : "graph",
|
|
121
|
+
objectId,
|
|
122
|
+
title: target.node.name,
|
|
123
|
+
graphId: target.graphId,
|
|
124
|
+
instanceId: target.instanceId,
|
|
125
|
+
source: graph.source,
|
|
126
|
+
transform: placement.transform,
|
|
127
|
+
bounds: placement.bounds,
|
|
128
|
+
instances: graph.instances,
|
|
129
|
+
diagnostics: boundedDiagnostics([...graph.diagnostics, ...placement.diagnostics]),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function planUpdate(target, graphExts, compiled) {
|
|
133
|
+
const ext = graphExtOf(graphExts, target.graphId);
|
|
134
|
+
const previous = readEditableGeometry(target.graph, ext);
|
|
135
|
+
const nextExt = migrateGraphExt(ext, previous.nodeIds, compiled);
|
|
136
|
+
const instances = {};
|
|
137
|
+
const diagnostics = [];
|
|
138
|
+
for (const [id, instance] of graphInstances(target.scripts, target.graphId)) {
|
|
139
|
+
const migrated = migrateParameterOverrides(previous.graph, ext, compiled.graph, nextExt, instance.data ?? {});
|
|
140
|
+
instances[id] = { graph_id: target.graphId, data: migrated.data };
|
|
141
|
+
if (migrated.removed.length)
|
|
142
|
+
diagnostics.push(`Instance ${id}: removed or type-changed overrides ${migrated.removed.join(", ")} now use graph defaults where present.`);
|
|
143
|
+
}
|
|
144
|
+
return { graphId: target.graphId, instanceId: target.instanceId, graph: compiled.graph, ext: nextExt, instances, diagnostics };
|
|
145
|
+
}
|
|
146
|
+
function planCreation(target, compiled) {
|
|
147
|
+
const graphId = unusedId(target.scripts.graphs);
|
|
148
|
+
const instanceId = unusedId(target.scripts.instances);
|
|
149
|
+
return {
|
|
150
|
+
graphId,
|
|
151
|
+
instanceId,
|
|
152
|
+
graph: compiled.graph,
|
|
153
|
+
ext: migrateGraphExt({ nodes: {} }, {}, compiled),
|
|
154
|
+
instances: { [instanceId]: { graph_id: graphId, data: {} } },
|
|
155
|
+
diagnostics: [],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function attachArea(draft, objectId, node, instanceId) {
|
|
159
|
+
const areaId = objectId !== 0 && node.kind === "union"
|
|
160
|
+
? objectId
|
|
161
|
+
: draft.createNode({ kind: "union", parentId: objectId, name: "Area" }).id;
|
|
162
|
+
draft.updateNode(areaId, { scriptInstanceId: instanceId });
|
|
163
|
+
return areaId;
|
|
164
|
+
}
|
|
165
|
+
function writeGraph(draft, scripts, graphExts, change) {
|
|
166
|
+
const nextScripts = {
|
|
167
|
+
...scripts,
|
|
168
|
+
graphs: { ...scripts.graphs, [change.graphId]: change.graph },
|
|
169
|
+
instances: { ...scripts.instances, ...change.instances },
|
|
170
|
+
};
|
|
171
|
+
draft.patchSceneStorage([{ op: "add", path: "/scripts", value: nextScripts }], { returnScene: false });
|
|
172
|
+
draft.patchSceneExt([{ op: "add", path: "/graphsExt", value: { ...graphExts, [change.graphId]: change.ext } }], { returnScene: false });
|
|
128
173
|
}
|
|
129
174
|
export function createSdkGeometryHost(options) {
|
|
130
|
-
const editor = options
|
|
175
|
+
const { editor } = options;
|
|
131
176
|
const repairs = createGeometryRepairTracker(options.repairs, "geometry");
|
|
132
177
|
const pendingTargets = new Set();
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
178
|
+
const write = async (input, invocation) => {
|
|
179
|
+
const target = await editor.read(invocation, (api) => resolveTarget(api, input.objectId));
|
|
180
|
+
const rejected = (diagnostics) => ({
|
|
181
|
+
status: "rejected",
|
|
182
|
+
objectId: input.objectId,
|
|
183
|
+
graphId: target.graphId,
|
|
184
|
+
instanceId: target.instanceId,
|
|
185
|
+
affectedObjectIds: [],
|
|
186
|
+
diagnostics: boundedDiagnostics(diagnostics),
|
|
187
|
+
});
|
|
188
|
+
const pendingKey = target.graphId === undefined ? `object:${input.objectId}` : `graph:${target.graphId}`;
|
|
189
|
+
if (pendingTargets.has(pendingKey))
|
|
190
|
+
throw new Error("This Geometry Nodes target is still being updated. Await its current update first.");
|
|
191
|
+
pendingTargets.add(pendingKey);
|
|
192
|
+
try {
|
|
193
|
+
const repairKey = target.graphId === undefined ? undefined : String(target.graphId);
|
|
194
|
+
const repairing = repairKey !== undefined && (await repairs.isPending(repairKey));
|
|
195
|
+
if (repairKey !== undefined)
|
|
196
|
+
await repairs.assertCanWrite(repairKey);
|
|
197
|
+
let compiled;
|
|
144
198
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
objectId: input.objectId,
|
|
170
|
-
};
|
|
171
|
-
let operationId;
|
|
172
|
-
let applied;
|
|
173
|
-
try {
|
|
174
|
-
applied = await editor.access({
|
|
175
|
-
capability: "geometry",
|
|
176
|
-
operation: "write",
|
|
177
|
-
target: () => receiptTarget,
|
|
178
|
-
}, async (_api, draft, id) => {
|
|
179
|
-
operationId = id;
|
|
180
|
-
const current = resolveTarget(draft, input.objectId);
|
|
181
|
-
if (repairingGeometry === false && options.canGrow?.() === false)
|
|
182
|
-
throw new Error("Scene memory limits prevent applying this graph.");
|
|
183
|
-
if (current.graphId !== target.graphId)
|
|
184
|
-
throw new Error("The Geometry Nodes target changed. Read it again.");
|
|
185
|
-
let nextExt;
|
|
186
|
-
const updates = new Map();
|
|
187
|
-
try {
|
|
188
|
-
if (target.graphId === undefined) {
|
|
189
|
-
nextExt = migrateGraphExt({ nodes: {} }, {}, compiled);
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
if (current.graph === undefined)
|
|
193
|
-
throw new Error("The Geometry Nodes graph is missing.");
|
|
194
|
-
const graphExt = draft.getSceneExt(["graphsExt"]).graphsExt;
|
|
195
|
-
const ext = graphExtSchema.parse(graphExt?.[target.graphId] ?? {});
|
|
196
|
-
const previous = readEditableGeometry(current.graph, ext);
|
|
197
|
-
nextExt = migrateGraphExt(ext, previous.nodeIds, compiled);
|
|
198
|
-
for (const [id, instance] of Object.entries(current.scripts.instances)) {
|
|
199
|
-
if (instance.graph_id !== target.graphId)
|
|
200
|
-
continue;
|
|
201
|
-
const old = readParameterOverrides(previous.graph, ext, instance.data ?? {});
|
|
202
|
-
const migrated = migrateParameterOverrides(previous.graph, ext, compiled.graph, nextExt, old.data);
|
|
203
|
-
updates.set(id, {
|
|
204
|
-
graph_id: target.graphId,
|
|
205
|
-
data: migrated.data,
|
|
206
|
-
});
|
|
207
|
-
if (migrated.removed.length)
|
|
208
|
-
diagnostics.push(`Instance ${id}: removed or type-changed overrides ${migrated.removed.join(", ")} now use graph defaults where present.`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
return { rejected: [errorMessage(error)] };
|
|
214
|
-
}
|
|
215
|
-
if (current.graphId !== undefined)
|
|
216
|
-
await repairs.reserve(String(current.graphId), id);
|
|
217
|
-
let objectId = input.objectId;
|
|
218
|
-
let graphId = current.graphId;
|
|
219
|
-
let instanceId = current.instanceId;
|
|
220
|
-
const nextInstances = new Map(updates);
|
|
221
|
-
if (graphId === undefined) {
|
|
222
|
-
graphId = unusedId(current.scripts.graphs);
|
|
223
|
-
instanceId = unusedId(current.scripts.instances);
|
|
224
|
-
if (objectId === 0 || current.node.kind !== "union") {
|
|
225
|
-
objectId = draft.createNode({
|
|
226
|
-
kind: "union",
|
|
227
|
-
parentId: objectId,
|
|
228
|
-
name: "Area",
|
|
229
|
-
}).id;
|
|
230
|
-
}
|
|
231
|
-
draft.updateNode(objectId, { scriptInstanceId: instanceId });
|
|
232
|
-
nextInstances.set(String(instanceId), {
|
|
233
|
-
graph_id: graphId,
|
|
234
|
-
data: {},
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
writeGraphStorage(draft, graphId, compiled.graph, nextInstances);
|
|
238
|
-
const ext = draft.getSceneExt();
|
|
239
|
-
if (!ext.graphsExt)
|
|
240
|
-
draft.patchSceneExt([
|
|
241
|
-
{ op: "add", path: "/graphsExt", value: {} },
|
|
242
|
-
], { returnScene: false });
|
|
243
|
-
draft.patchSceneExt([
|
|
244
|
-
{
|
|
245
|
-
op: "add",
|
|
246
|
-
path: `/graphsExt/${graphId}`,
|
|
247
|
-
value: nextExt,
|
|
248
|
-
},
|
|
249
|
-
], { returnScene: false });
|
|
250
|
-
const affectedObjectIds = Object.entries(scriptStorage(draft).instances)
|
|
251
|
-
.filter(([, instance]) => instance.graph_id === graphId)
|
|
252
|
-
.flatMap(([id]) => objectIdsFor(draft, Number(id)));
|
|
253
|
-
Object.assign(receiptTarget, { objectId, graphId, instanceId });
|
|
254
|
-
return { objectId, graphId, instanceId, affectedObjectIds };
|
|
255
|
-
}, invocation, async (operation) => {
|
|
256
|
-
const graphId = receiptTarget.graphId;
|
|
257
|
-
if (graphId === undefined)
|
|
258
|
-
return;
|
|
259
|
-
const recorded = await repairs.record(String(graphId), operation.id, unverifiedSceneHealth([], "Health was not requested."));
|
|
260
|
-
return {
|
|
261
|
-
geometryRepairAttempts: recorded.repairAttempts,
|
|
262
|
-
geometryRepairLimit: recorded.repairLimit,
|
|
263
|
-
};
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
catch (error) {
|
|
267
|
-
if (operationId !== undefined && target.graphId !== undefined) {
|
|
268
|
-
const operation = error instanceof SceneOperationError
|
|
269
|
-
? error.operation : await editor.getOperation(operationId);
|
|
270
|
-
if (operation?.status === "rolled_back")
|
|
271
|
-
await repairs.refund(String(target.graphId), operationId);
|
|
199
|
+
compiled = compileGeometry(input.source);
|
|
200
|
+
readEditableGeometry(compiled.graph, compiled.graphExt);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
return rejected([errorMessage(error)]);
|
|
204
|
+
}
|
|
205
|
+
const memoryBlocked = () => repairing === false && options.canGrow?.() === false;
|
|
206
|
+
if (memoryBlocked())
|
|
207
|
+
return rejected([MEMORY_LIMIT]);
|
|
208
|
+
const receipt = { objectId: input.objectId };
|
|
209
|
+
let operationId;
|
|
210
|
+
let applied;
|
|
211
|
+
try {
|
|
212
|
+
applied = await editor.access({ capability: "geometry", operation: "write", target: () => receipt }, async (_api, draft, id) => {
|
|
213
|
+
operationId = id;
|
|
214
|
+
const current = resolveTarget(draft, input.objectId);
|
|
215
|
+
if (memoryBlocked())
|
|
216
|
+
throw new Error(MEMORY_LIMIT);
|
|
217
|
+
if (current.graphId !== target.graphId)
|
|
218
|
+
throw new Error("The Geometry Nodes target changed. Read it again.");
|
|
219
|
+
const graphExts = readGraphExts(draft);
|
|
220
|
+
let change;
|
|
221
|
+
try {
|
|
222
|
+
change = current.graph === undefined ? planCreation(current, compiled) : planUpdate(current, graphExts, compiled);
|
|
272
223
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
224
|
+
catch (error) {
|
|
225
|
+
return { rejected: [errorMessage(error)] };
|
|
226
|
+
}
|
|
227
|
+
if (current.graph !== undefined)
|
|
228
|
+
await repairs.reserve(String(current.graphId), id);
|
|
229
|
+
const objectId = current.graph === undefined ? attachArea(draft, input.objectId, current.node, change.instanceId) : input.objectId;
|
|
230
|
+
writeGraph(draft, current.scripts, graphExts, change);
|
|
231
|
+
const objectIds = objectIdsByInstance(draft);
|
|
232
|
+
Object.assign(receipt, { objectId, graphId: change.graphId, instanceId: change.instanceId });
|
|
233
|
+
return {
|
|
234
|
+
objectId,
|
|
235
|
+
graphId: change.graphId,
|
|
236
|
+
instanceId: change.instanceId,
|
|
237
|
+
affectedObjectIds: Object.keys(change.instances).flatMap((instanceId) => objectIds.get(Number(instanceId)) ?? []),
|
|
238
|
+
diagnostics: change.diagnostics,
|
|
239
|
+
};
|
|
240
|
+
}, invocation, async (operation) => {
|
|
241
|
+
if (receipt.graphId === undefined)
|
|
242
|
+
return;
|
|
243
|
+
const recorded = await repairs.record(String(receipt.graphId), operation.id, unverifiedSceneHealth([], "Health was not requested."));
|
|
244
|
+
return { geometryRepairAttempts: recorded.repairAttempts, geometryRepairLimit: recorded.repairLimit };
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (operationId !== undefined && repairKey !== undefined) {
|
|
249
|
+
if (error instanceof SceneOperationError && error.operation.status === "rolled_back")
|
|
250
|
+
await repairs.refund(repairKey, operationId);
|
|
280
251
|
}
|
|
281
|
-
|
|
282
|
-
return {
|
|
283
|
-
status: "applied",
|
|
284
|
-
...applied.value,
|
|
285
|
-
diagnostics: boundedDiagnostics(diagnostics),
|
|
286
|
-
};
|
|
252
|
+
throw error;
|
|
287
253
|
}
|
|
288
|
-
|
|
289
|
-
|
|
254
|
+
const result = applied.value;
|
|
255
|
+
if (result.rejected)
|
|
256
|
+
return rejected(result.rejected);
|
|
257
|
+
invocation.signal.throwIfAborted();
|
|
258
|
+
try {
|
|
259
|
+
await options.onSelect?.(result.objectId);
|
|
260
|
+
}
|
|
261
|
+
catch { }
|
|
262
|
+
let committed;
|
|
263
|
+
try {
|
|
264
|
+
committed = await editor.read(invocation, (api) => readTarget(api, result.objectId));
|
|
290
265
|
}
|
|
291
|
-
|
|
266
|
+
catch (error) {
|
|
267
|
+
committed = {
|
|
268
|
+
source: null,
|
|
269
|
+
transform: null,
|
|
270
|
+
bounds: null,
|
|
271
|
+
diagnostics: [`The write was applied, but reading it back failed: ${errorMessage(error)} Read the target before editing it again.`],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
status: "applied",
|
|
276
|
+
objectId: result.objectId,
|
|
277
|
+
graphId: result.graphId,
|
|
278
|
+
instanceId: result.instanceId,
|
|
279
|
+
affectedObjectIds: result.affectedObjectIds,
|
|
280
|
+
source: committed.source ?? undefined,
|
|
281
|
+
transform: committed.transform,
|
|
282
|
+
bounds: committed.bounds,
|
|
283
|
+
diagnostics: boundedDiagnostics([...result.diagnostics, ...committed.diagnostics]),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
pendingTargets.delete(pendingKey);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
return {
|
|
291
|
+
read: (objectId, invocation) => editor.read(invocation, (api) => readTarget(api, objectId)),
|
|
292
|
+
write,
|
|
292
293
|
};
|
|
293
294
|
}
|
|
@@ -238,9 +238,7 @@ export function createSdkOpenScadHost(options) {
|
|
|
238
238
|
}
|
|
239
239
|
catch (error) {
|
|
240
240
|
if (creating === false && operationId !== "") {
|
|
241
|
-
|
|
242
|
-
? error.operation : await editor.getOperation(operationId);
|
|
243
|
-
if (operation?.status === "rolled_back") {
|
|
241
|
+
if (error instanceof SceneOperationError && error.operation.status === "rolled_back") {
|
|
244
242
|
await geometryRepairs.refund(resolved.repairKey, operationId);
|
|
245
243
|
await printabilityRepairs.refund(resolved.repairKey, operationId);
|
|
246
244
|
}
|
package/dist/sdk-scene-host.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FontFamily, KakapoAPI, SceneEdit, MaterialResource, NativeSceneSnapshot } from "@womp/kakapo-sdk";
|
|
2
2
|
import type { ArtifactRef, SparkInvocationContext } from "./capabilities.js";
|
|
3
3
|
import type { SceneHost, SceneHealthHost, SceneScreenshotOptions } from "./capabilities/scene.js";
|
|
4
|
-
import { type SceneOperation
|
|
4
|
+
import { type SceneOperation } from "./scene-operations.js";
|
|
5
5
|
import type { SceneHdr } from "./scene-environment.js";
|
|
6
6
|
export type SdkSceneServices = {
|
|
7
7
|
fonts: FontFamily[];
|
|
@@ -19,7 +19,6 @@ export type SdkSceneServices = {
|
|
|
19
19
|
export type SdkSceneEditorOptions = {
|
|
20
20
|
targetKey: string;
|
|
21
21
|
projectId?: number;
|
|
22
|
-
operations?: SceneOperationStore;
|
|
23
22
|
saveToRenderer?: boolean;
|
|
24
23
|
withConnection<T>(context: SparkInvocationContext, action: (api: KakapoAPI, beginSettlement: () => void,
|
|
25
24
|
/** Supply this when the host adds a stricter connection lifetime. */
|
|
@@ -27,7 +26,7 @@ export type SdkSceneEditorOptions = {
|
|
|
27
26
|
assertEditable(context: SparkInvocationContext): void | Promise<void>;
|
|
28
27
|
/** Suspend for the complete access, including reads and failed preparation. */
|
|
29
28
|
acquireAccess?(context: SparkInvocationContext): Promise<(synchronized: boolean) => void | Promise<void>>;
|
|
30
|
-
/** Must adopt each operation ID at most once
|
|
29
|
+
/** Must adopt each operation ID at most once. */
|
|
31
30
|
onCommitted?(operation: SceneOperation, api: KakapoAPI, context: SparkInvocationContext, identity: SdkSceneEditIdentity, scene: NativeSceneSnapshot): Promise<Record<string, unknown> | void>;
|
|
32
31
|
};
|
|
33
32
|
export type SdkSceneEditIdentity = {
|
|
@@ -35,13 +34,12 @@ export type SdkSceneEditIdentity = {
|
|
|
35
34
|
operation: string;
|
|
36
35
|
target?: Record<string, unknown> | (() => Record<string, unknown>);
|
|
37
36
|
};
|
|
38
|
-
/**
|
|
37
|
+
/** A failed edit's outcome, for callers that settle per-edit state such as repair budgets. */
|
|
39
38
|
export declare class SceneOperationError extends Error {
|
|
40
39
|
readonly operation: SceneOperation;
|
|
41
40
|
constructor(operation: SceneOperation, cause: unknown);
|
|
42
41
|
}
|
|
43
42
|
export type SdkSceneEditor = {
|
|
44
|
-
getOperation(operationId: string): Promise<SceneOperation | undefined>;
|
|
45
43
|
withConnection<T>(context: SparkInvocationContext, action: (api: KakapoAPI, beginSettlement: () => void) => T | Promise<T>): Promise<T>;
|
|
46
44
|
read<T>(context: SparkInvocationContext, action: (api: KakapoAPI, beginSettlement: () => void) => T | Promise<T>): Promise<T>;
|
|
47
45
|
access<T>(identity: SdkSceneEditIdentity, callback: (api: KakapoAPI, draft: SceneEdit, operationId: string) => T | Promise<T>, context: SparkInvocationContext, afterCommitted?: (operation: SceneOperation, api: KakapoAPI) => Promise<Record<string, unknown> | void>): Promise<{
|