@womp/kakapo-sdk 0.1.0 → 0.2.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 +5 -0
- package/dist/api.d.ts +12 -0
- package/dist/api.js +35 -3
- package/dist/scene.js +1 -1
- package/dist/types.d.ts +19 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -51,6 +51,11 @@ kakapo.disconnect();
|
|
|
51
51
|
The transport uses the browser's native `WebSocket` when bundled for the web and loads `ws`
|
|
52
52
|
dynamically in Node.js.
|
|
53
53
|
|
|
54
|
+
Applications that already own a renderer connection can pass a `KakapoAPITransport` as
|
|
55
|
+
`new KakapoAPI({ transport })`. The adapter defines connection lifecycle, RPC, and token responses.
|
|
56
|
+
`captureScreenshot({ view: "current" })` requests the renderer's JPEG without changing the camera
|
|
57
|
+
or requiring a cached scene. Current-view captures cannot include `targetId` or `targetIds`.
|
|
58
|
+
|
|
54
59
|
All public inputs are checked at runtime as well as by TypeScript. Invalid calls throw structured
|
|
55
60
|
`KakapoValidationError` objects with a stable `code`, offending `path`, expected rule, and a
|
|
56
61
|
correction `hint` intended for automated agents.
|
package/dist/api.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ export interface SceneEdit {
|
|
|
13
13
|
getNodeParent(id: number): KakapoNode | null;
|
|
14
14
|
getNodeChildren(id: number): KakapoNode[];
|
|
15
15
|
applyScenePatch(operations: JsonPatchOperation[]): Scene;
|
|
16
|
+
getSceneExt(): Record<string, JsonValue>;
|
|
17
|
+
patchSceneExt(operations: JsonPatchOperation[]): Scene;
|
|
18
|
+
getSceneProperties(): Record<string, JsonValue>;
|
|
19
|
+
patchSceneProperties(operations: JsonPatchOperation[]): Scene;
|
|
16
20
|
createNode(input: CreateNodeInput): KakapoNode;
|
|
17
21
|
cloneNode(id: number, options?: {
|
|
18
22
|
parentId?: number;
|
|
@@ -74,6 +78,14 @@ export declare class KakapoAPI {
|
|
|
74
78
|
getScene(): Scene;
|
|
75
79
|
editScene<T>(callback: (scene: SceneEdit) => T | Promise<T>): Promise<T>;
|
|
76
80
|
private applyScenePatch;
|
|
81
|
+
/** The scene's consumer-owned extension bag. Kakapo does not interpret it. */
|
|
82
|
+
getSceneExt(): Record<string, JsonValue>;
|
|
83
|
+
/** Apply JSON Patch operations rooted at the scene's `ext` bag. */
|
|
84
|
+
patchSceneExt(operations: JsonPatchOperation[]): Scene;
|
|
85
|
+
/** The scene's renderer/environment properties (HDR, background, and similar). */
|
|
86
|
+
getSceneProperties(): Record<string, JsonValue>;
|
|
87
|
+
/** Apply JSON Patch operations rooted at the scene's `properties` bag. */
|
|
88
|
+
patchSceneProperties(operations: JsonPatchOperation[]): Scene;
|
|
77
89
|
listNodes(options?: ListNodeOptions): KakapoNode[];
|
|
78
90
|
findNodes(options: FindNodeOptions): KakapoNode[];
|
|
79
91
|
getNode(id: number): KakapoNode;
|
package/dist/api.js
CHANGED
|
@@ -25,7 +25,7 @@ export class KakapoAPI {
|
|
|
25
25
|
draftDirty = false;
|
|
26
26
|
rendererSyncRequired = false;
|
|
27
27
|
constructor(options = {}) {
|
|
28
|
-
this.transport = new KakapoTransport({
|
|
28
|
+
this.transport = options.transport ?? new KakapoTransport({
|
|
29
29
|
url: options.url ?? "ws://127.0.0.1:5502",
|
|
30
30
|
requestTimeoutMs: options.requestTimeoutMs ?? 10_000,
|
|
31
31
|
connectTimeoutMs: options.connectTimeoutMs ?? 30_000,
|
|
@@ -126,6 +126,24 @@ export class KakapoAPI {
|
|
|
126
126
|
this.draftDirty = true;
|
|
127
127
|
return this.getScene();
|
|
128
128
|
}
|
|
129
|
+
/** The scene's consumer-owned extension bag. Kakapo does not interpret it. */
|
|
130
|
+
getSceneExt() {
|
|
131
|
+
const scene = this.getScene();
|
|
132
|
+
return scene.ext ?? {};
|
|
133
|
+
}
|
|
134
|
+
/** Apply JSON Patch operations rooted at the scene's `ext` bag. */
|
|
135
|
+
patchSceneExt(operations) {
|
|
136
|
+
return this.applyScenePatch(operations.map((op) => ({ ...op, path: `/ext${op.path}` })));
|
|
137
|
+
}
|
|
138
|
+
/** The scene's renderer/environment properties (HDR, background, and similar). */
|
|
139
|
+
getSceneProperties() {
|
|
140
|
+
const scene = this.getScene();
|
|
141
|
+
return scene.properties ?? {};
|
|
142
|
+
}
|
|
143
|
+
/** Apply JSON Patch operations rooted at the scene's `properties` bag. */
|
|
144
|
+
patchSceneProperties(operations) {
|
|
145
|
+
return this.applyScenePatch(operations.map((op) => ({ ...op, path: `/properties${op.path}` })));
|
|
146
|
+
}
|
|
129
147
|
listNodes(options = {}) {
|
|
130
148
|
const scene = this.requireScene("listNodes");
|
|
131
149
|
return Object.keys(scene.nodes).map(Number).sort((a, b) => a - b).map((id) => wireToNode(scene, id)).filter((node) => (options.kind === undefined || node.kind === options.kind) && (options.parentId === undefined || node.parentId === options.parentId)).map(deepClone);
|
|
@@ -193,6 +211,17 @@ export class KakapoAPI {
|
|
|
193
211
|
async captureScreenshot(options) {
|
|
194
212
|
this.assertEngineReadAllowed("captureScreenshot");
|
|
195
213
|
const { targetId, timeoutMs } = options;
|
|
214
|
+
if (options.view === "current") {
|
|
215
|
+
if (targetId !== undefined || options.targetIds !== undefined) {
|
|
216
|
+
throw new KakapoValidationError("A current-view screenshot cannot specify framing targets.", {
|
|
217
|
+
code: "AMBIGUOUS_SCREENSHOT_TARGET",
|
|
218
|
+
operation: "captureScreenshot",
|
|
219
|
+
expected: "current view without target selectors",
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const frame = await this.captureRendererFrame(timeoutMs);
|
|
223
|
+
return { data: frame.payload, mediaType: "image/jpeg", width: frame.width, height: frame.height, view: "current", targetId: 0 };
|
|
224
|
+
}
|
|
196
225
|
if (targetId !== undefined && options.targetIds !== undefined) {
|
|
197
226
|
throw new KakapoValidationError("captureScreenshot accepts targetId or targetIds, not both.", {
|
|
198
227
|
code: "AMBIGUOUS_SCREENSHOT_TARGET",
|
|
@@ -264,8 +293,7 @@ export class KakapoAPI {
|
|
|
264
293
|
async captureRendererFrame(timeoutMs) {
|
|
265
294
|
const token = this.newToken();
|
|
266
295
|
const waiting = this.waitForToken(token, timeoutMs);
|
|
267
|
-
await this.call("get_color_buffer", [[token]]);
|
|
268
|
-
const message = await waiting;
|
|
296
|
+
const [, message] = await Promise.all([this.call("get_color_buffer", [[token]]), waiting]);
|
|
269
297
|
if (!isBinaryFrame(message)) {
|
|
270
298
|
const error = "error" in message ? String(message.error) : "Kakapo returned a non-binary renderer frame";
|
|
271
299
|
throw new KakapoRpcError("get_color_buffer", error);
|
|
@@ -569,6 +597,10 @@ export class KakapoAPI {
|
|
|
569
597
|
getNodeParent: (id) => this.getNodeParent(id),
|
|
570
598
|
getNodeChildren: (id) => this.getNodeChildren(id),
|
|
571
599
|
applyScenePatch: (operations) => this.applyScenePatch(operations),
|
|
600
|
+
getSceneExt: () => this.getSceneExt(),
|
|
601
|
+
patchSceneExt: (operations) => this.patchSceneExt(operations),
|
|
602
|
+
getSceneProperties: () => this.getSceneProperties(),
|
|
603
|
+
patchSceneProperties: (operations) => this.patchSceneProperties(operations),
|
|
572
604
|
createNode: (input) => this.createNode(input),
|
|
573
605
|
cloneNode: (id, options) => this.cloneNode(id, options),
|
|
574
606
|
updateNode: (id, changes) => this.updateNode(id, changes),
|
package/dist/scene.js
CHANGED
|
@@ -294,7 +294,7 @@ export function toPublicScene(raw) {
|
|
|
294
294
|
return id === undefined ? [] : [[key, wireToMaterial(id, value)]];
|
|
295
295
|
}));
|
|
296
296
|
const scripts = Object.fromEntries(Object.entries(raw.storage.openscad?.scripts ?? {}).map(([id, value]) => [id, { id: Number(id), name: str(value.name, "OpenSCAD Script"), source: str(value.source) }]));
|
|
297
|
-
return { version: raw.version ?? 0, name: raw.name ?? "", nodes, tree, materials, openScadScripts: scripts, properties: (raw.properties ?? {}), camera: (raw.camera ?? {}) };
|
|
297
|
+
return { version: raw.version ?? 0, name: raw.name ?? "", nodes, tree, materials, openScadScripts: scripts, properties: (raw.properties ?? {}), camera: (raw.camera ?? {}), ext: (raw.ext ?? {}) };
|
|
298
298
|
}
|
|
299
299
|
export function missingNode(id, operation) {
|
|
300
300
|
return new KakapoValidationError(`Node ${id} does not exist.`, { code: "NODE_NOT_FOUND", operation, path: "nodeId", received: id, expected: "an existing node ID", hint: "Call listNodes() or refreshScene() and use a current node ID." });
|
package/dist/types.d.ts
CHANGED
|
@@ -268,6 +268,12 @@ export interface Scene {
|
|
|
268
268
|
openScadScripts: Record<string, OpenScadScript>;
|
|
269
269
|
properties: Record<string, JsonValue>;
|
|
270
270
|
camera: Record<string, JsonValue>;
|
|
271
|
+
/**
|
|
272
|
+
* Consumer-owned extension bag carried in the scene file. Kakapo does not
|
|
273
|
+
* interpret its contents; Womp stores node and material metadata here
|
|
274
|
+
* (e.g. `ext[nodeId].locked`, `ext.materialsExt[materialId].tier`).
|
|
275
|
+
*/
|
|
276
|
+
ext: Record<string, JsonValue>;
|
|
271
277
|
}
|
|
272
278
|
export interface BoundingBox {
|
|
273
279
|
min: Vec3;
|
|
@@ -316,7 +322,8 @@ export interface CaptureScreenshotOptions {
|
|
|
316
322
|
/** Omit both selectors to frame all visible top-level scene nodes. */
|
|
317
323
|
targetId?: number;
|
|
318
324
|
targetIds?: number[];
|
|
319
|
-
|
|
325
|
+
/** "current" captures the renderer's existing camera without framing or reading scene state. */
|
|
326
|
+
view?: ScreenshotView | "current";
|
|
320
327
|
timeoutMs?: number;
|
|
321
328
|
}
|
|
322
329
|
export interface ScreenshotImage {
|
|
@@ -324,7 +331,7 @@ export interface ScreenshotImage {
|
|
|
324
331
|
mediaType: "image/jpeg";
|
|
325
332
|
width: number;
|
|
326
333
|
height: number;
|
|
327
|
-
view: ScreenshotView;
|
|
334
|
+
view: ScreenshotView | "current";
|
|
328
335
|
targetId: number;
|
|
329
336
|
}
|
|
330
337
|
export type JsonPatchOperation = {
|
|
@@ -351,7 +358,17 @@ export interface ListNodeOptions {
|
|
|
351
358
|
export interface DeleteNodeOptions {
|
|
352
359
|
recursive?: boolean;
|
|
353
360
|
}
|
|
361
|
+
/** An application can supply its existing connection; its adapter owns connection lifecycle. */
|
|
362
|
+
export interface KakapoAPITransport {
|
|
363
|
+
readonly connected: boolean;
|
|
364
|
+
connect(): Promise<void>;
|
|
365
|
+
disconnect(): void;
|
|
366
|
+
rpc<T>(method: string, params?: JsonValue[]): Promise<T>;
|
|
367
|
+
newToken(): string;
|
|
368
|
+
waitForToken(token: string, timeoutMs?: number): Promise<TokenMessage>;
|
|
369
|
+
}
|
|
354
370
|
export interface KakapoAPIOptions {
|
|
371
|
+
transport?: KakapoAPITransport;
|
|
355
372
|
url?: string;
|
|
356
373
|
requestTimeoutMs?: number;
|
|
357
374
|
connectTimeoutMs?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@womp/kakapo-sdk",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Typed Node.js and browser SDK for controlling Kakapo over WebSocket",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"sideEffects": false,
|
|
22
22
|
"engines": {
|
|
23
|
-
"node": ">=
|
|
23
|
+
"node": ">=22.0.0"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|