@womp/spark-agent 3.0.2 → 4.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/README.md +59 -33
- package/dist/agent-run-cli.d.ts +2 -0
- package/dist/agent-run-cli.js +30 -0
- package/dist/agent-run-input.d.ts +11 -0
- package/dist/agent-run-input.js +59 -0
- package/dist/agent-run.d.ts +81 -0
- package/dist/agent-run.js +234 -0
- package/dist/agent-tools.js +3 -2
- package/dist/agent.d.ts +0 -2
- package/dist/agent.js +4 -36
- package/dist/capabilities/geometry.js +1 -1
- package/dist/capabilities/openscad-instructions.d.ts +1 -1
- package/dist/capabilities/openscad-instructions.js +4 -1
- package/dist/capabilities/scene.d.ts +11 -3
- package/dist/capabilities/scene.js +88 -27
- package/dist/capabilities.d.ts +1 -2
- package/dist/capabilities.js +0 -1
- package/dist/client.d.ts +5 -0
- package/dist/client.js +22 -0
- package/dist/coordinates.d.ts +0 -7
- package/dist/coordinates.js +8 -47
- package/dist/generated/scene-declarations.d.ts +1 -1
- package/dist/generated/scene-declarations.js +1 -1
- package/dist/geometry/schemas/flowir-dialect.json +2 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/models.d.ts +7 -0
- package/dist/models.js +10 -0
- package/dist/request-limits.d.ts +1 -0
- package/dist/request-limits.js +1 -0
- package/dist/run-metrics.d.ts +15 -0
- package/dist/run-metrics.js +30 -0
- package/dist/runtime.d.ts +3 -5
- package/dist/scene-metadata.js +1 -1
- package/dist/sdk-scene-host.d.ts +11 -13
- package/dist/sdk-scene-host.js +1 -0
- package/dist/sdk-scene-screenshot.d.ts +4 -6
- package/dist/sdk-scene-screenshot.js +14 -15
- package/dist/server/artifacts.js +1 -5
- package/dist/server/executor.d.ts +0 -1
- package/dist/server/executor.js +0 -1
- package/dist/server/express.js +17 -5
- package/dist/server/scene-actions.d.ts +2 -0
- package/dist/server/scene-actions.js +20 -0
- package/dist/server/service.d.ts +2 -0
- package/dist/server/service.js +10 -0
- package/dist/server/sqlite-store.d.ts +3 -1
- package/dist/server/sqlite-store.js +26 -3
- package/dist/server/standalone.d.ts +2 -0
- package/dist/server/standalone.js +54 -3
- package/dist/server/store.d.ts +3 -1
- package/dist/server/store.js +21 -1
- package/dist/server/types.d.ts +5 -0
- package/dist/simple-bench-cli.d.ts +2 -0
- package/dist/simple-bench-cli.js +64 -0
- package/dist/simple-bench-prompts.d.ts +28 -0
- package/dist/simple-bench-prompts.js +11 -0
- package/dist/simple-bench.d.ts +15 -0
- package/dist/simple-bench.js +175 -0
- package/dist/types.d.ts +4 -13
- package/dist/ui.d.ts +3 -8
- package/dist/womp-scene-api.d.ts +14 -3
- package/dist/womp-scene-api.js +91 -72
- package/package.json +7 -3
- package/dist/capabilities/health.d.ts +0 -9
- package/dist/capabilities/health.js +0 -27
- package/dist/context.d.ts +0 -15
- package/dist/context.js +0 -266
- package/dist/generated/health-declarations.d.ts +0 -1
- package/dist/generated/health-declarations.js +0 -3
package/dist/agent.js
CHANGED
|
@@ -2,7 +2,6 @@ import { createUIMessageStream, stepCountIs, streamText, } from "ai";
|
|
|
2
2
|
import { createSparkTools } from "./agent-tools.js";
|
|
3
3
|
import { SPARK_AGENT_PROMPT } from "./prompt.js";
|
|
4
4
|
import { calculateStepCost } from "./pricing.js";
|
|
5
|
-
import { prepareSparkContext, SparkContextLimitError } from "./context.js";
|
|
6
5
|
function errorMessage(error) {
|
|
7
6
|
return error instanceof Error ? error.message : String(error);
|
|
8
7
|
}
|
|
@@ -91,7 +90,6 @@ export function runSparkAgent(options) {
|
|
|
91
90
|
execute: async ({ writer }) => {
|
|
92
91
|
let generationError;
|
|
93
92
|
let stepCount = 0;
|
|
94
|
-
let readUsage;
|
|
95
93
|
try {
|
|
96
94
|
controller.signal.throwIfAborted();
|
|
97
95
|
const tools = createSparkTools({
|
|
@@ -123,12 +121,9 @@ export function runSparkAgent(options) {
|
|
|
123
121
|
? messages
|
|
124
122
|
: await options.prepareMessages(messages);
|
|
125
123
|
controller.signal.throwIfAborted();
|
|
126
|
-
const system = readSystem();
|
|
127
124
|
return {
|
|
128
|
-
system,
|
|
129
|
-
messages:
|
|
130
|
-
maxInputTokens: options.maxInputTokens ?? 128_000,
|
|
131
|
-
}),
|
|
125
|
+
system: readSystem(),
|
|
126
|
+
messages: prepared,
|
|
132
127
|
};
|
|
133
128
|
},
|
|
134
129
|
onError: ({ error }) => {
|
|
@@ -158,12 +153,9 @@ export function runSparkAgent(options) {
|
|
|
158
153
|
}
|
|
159
154
|
},
|
|
160
155
|
});
|
|
161
|
-
|
|
162
|
-
writer.merge(generation
|
|
163
|
-
.toUIMessageStream({
|
|
156
|
+
writer.merge(generation.toUIMessageStream({
|
|
164
157
|
generateMessageId: () => options.messageId,
|
|
165
158
|
onError: (error) => {
|
|
166
|
-
// Record the error before its UI chunk reaches the filter below;
|
|
167
159
|
// streamText's onError callback may run later on another stream branch.
|
|
168
160
|
generationError = error;
|
|
169
161
|
return errorMessage(error);
|
|
@@ -178,16 +170,7 @@ export function runSparkAgent(options) {
|
|
|
178
170
|
}
|
|
179
171
|
return undefined;
|
|
180
172
|
},
|
|
181
|
-
})
|
|
182
|
-
.pipeThrough(new TransformStream({
|
|
183
|
-
transform(chunk, streamController) {
|
|
184
|
-
// A context limit is a controlled terminal outcome. An SDK error
|
|
185
|
-
// chunk would make chat consumers stop before its final checkpoint.
|
|
186
|
-
if (chunk.type === "error" && generationError instanceof SparkContextLimitError)
|
|
187
|
-
return;
|
|
188
|
-
streamController.enqueue(chunk);
|
|
189
|
-
},
|
|
190
|
-
})));
|
|
173
|
+
}));
|
|
191
174
|
const [text, usage, finishReason] = await Promise.all([
|
|
192
175
|
generation.text,
|
|
193
176
|
generation.totalUsage,
|
|
@@ -201,21 +184,6 @@ export function runSparkAgent(options) {
|
|
|
201
184
|
}
|
|
202
185
|
catch (error) {
|
|
203
186
|
const failure = generationError ?? error;
|
|
204
|
-
if (failure instanceof SparkContextLimitError && !options.signal?.aborted) {
|
|
205
|
-
const usage = await Promise.resolve(readUsage?.()).catch(() => undefined);
|
|
206
|
-
writer.write({
|
|
207
|
-
type: "message-metadata",
|
|
208
|
-
messageMetadata: { ...metadata("incomplete", usage), error: failure.message },
|
|
209
|
-
});
|
|
210
|
-
completedResult = {
|
|
211
|
-
text: failure.message,
|
|
212
|
-
steps: stepCount,
|
|
213
|
-
usage,
|
|
214
|
-
status: "incomplete",
|
|
215
|
-
finishReason: "context-limit",
|
|
216
|
-
};
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
187
|
const status = options.signal?.aborted ? "cancelled" : "failed";
|
|
220
188
|
writer.write({
|
|
221
189
|
type: "message-metadata",
|
|
@@ -25,7 +25,7 @@ After a stale revision rejection, read the latest graph and adapt the edit while
|
|
|
25
25
|
If source is null and diagnostics are present, the existing graph cannot be edited without loss. Explain the limitation; never treat it as empty or create a replacement to bypass the refusal.
|
|
26
26
|
Preserve unrelated statements, parameter names, and FlowIR node IDs from the read. Shared graph edits affect all listed instances; report the affected objects. Valid overrides migrate by parameter name and type. Removed or type-changed parameters return to graph defaults and are reported.
|
|
27
27
|
Use min/max/step for naturally bounded parameters and omit ranges for free-form positions/rotations. Meaningful parameter names become editor labels. Do not invent label fields.
|
|
28
|
-
|
|
28
|
+
Positions and sizes are millimetres; Boolean Blend uses the conversion in geometry-nodes-reference. Coordinates are right-handed, +X right, +Y up, +Z forward. Rotations are degrees, intrinsic X-Z-Y.
|
|
29
29
|
On compiler errors, repair the source in this same conversation and retry with a fresh read. A rejected write made no change. Committed/unknown effects remain in execution receipts even if a later call fails; inspect before retrying.
|
|
30
30
|
After applying, read the returned object and inspect a screenshot through scene-verification when available.`,
|
|
31
31
|
declarations: WOMP_GEOMETRY_DECLARATIONS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const OPENSCAD_INSTRUCTIONS = "Use womp.openscad to create or update one adjustable part. Read an existing part before editing it.\nChoose CAD for mechanical, measured, and hard-surface parametric parts even without explicit dimensions.\nFor an explicit visual node graph, parametric organic or smoothly blended forms, or parametric animation,\nuse geometry-nodes when available. Do not force organic sculpture into CAD or silently change the requested editing surface.\nread(0) returns the root container and a scene revision for creation. read(id) returns an existing\npart or container. Use the scene-editing skill when available to discover other scene objects.\ncreate requires a parentId (0 means the scene root); update requires the existing objectId.\nBoth methods compile the effective source and parameters, apply one undoable change, and check wall thickness.\nA stale revision requires reading again and adapting the edit while preserving intervening user changes.\nAsk the user only when the intended edits conflict or remain ambiguous; never reuse the old replacement\nwith a refreshed revision token. Existing overrides are preserved for parameters still present;\nparams supplies overrides to merge into the current values. Keep top-level parameter names stable.\nReturn complete source on every update and preserve unrelated geometry. Never create a replacement to repair a part.\nCompile errors return status rejected without applying. Creation requires validated placement bounds.\nAn update may apply with validation unavailable; that is a diagnostic, not proof that it compiles.\nAn applied part remains in the scene even if thickness fails or is unavailable. Use the returned objectId for repairs.\nWhen thickness fails, increase thin walls and fragile connections while preserving requested dimensions and openings.\nAt most four applied printability repairs are allowed per logical build/edit in this run. Respect the returned limit;\ndo not bypass it by recreating the part. Unavailable thickness is not a pass. Report unresolved issues honestly.\nBuild connected geometry and avoid features below 1.4 mm. OpenSCAD source dimensions use millimetres,\nindependently of the scene display unit. New parts are centered and grounded from compiled bounds.\nAfter a major change, use the scene screenshot API and emitImage to inspect the applied result.\nA committed effect can survive later failure. Inspect receipts and the existing part before retrying.\nDo not print source in the final answer unless requested; describe the result and any unresolved diagnostics.\n\nRUNTIME\n- Produce a single OpenSCAD file.\n- Do not use imports, includes, external libraries, text, hull, minkowski, polyhedron, or extrudes.\n- Prefer cube, sphere, cylinder, translate, rotate, color, union, difference, and intersection.\n- Do not use $fn, $fa, or $fs; tessellation controls are ignored.\n- Build faceted geometry by combining supported primitives with CSG operations and transforms.\n- Do not use scale(); size primitives directly instead.\n- Use color(\"#rrggbb\") around distinct visible parts.\n- Keep the model centered near the origin and use millimeter-like units consistently.\n- For organic blending, use goop(
|
|
1
|
+
export declare const OPENSCAD_INSTRUCTIONS = "Use womp.openscad to create or update one adjustable part. Read an existing part before editing it.\nChoose CAD for mechanical, measured, and hard-surface parametric parts even without explicit dimensions.\nFor an explicit visual node graph, parametric organic or smoothly blended forms, or parametric animation,\nuse geometry-nodes when available. Do not force organic sculpture into CAD or silently change the requested editing surface.\nread(0) returns the root container and a scene revision for creation. read(id) returns an existing\npart or container. Use the scene-editing skill when available to discover other scene objects.\ncreate requires a parentId (0 means the scene root); update requires the existing objectId.\nBoth methods compile the effective source and parameters, apply one undoable change, and check wall thickness.\nA stale revision requires reading again and adapting the edit while preserving intervening user changes.\nAsk the user only when the intended edits conflict or remain ambiguous; never reuse the old replacement\nwith a refreshed revision token. Existing overrides are preserved for parameters still present;\nparams supplies overrides to merge into the current values. Keep top-level parameter names stable.\nReturn complete source on every update and preserve unrelated geometry. Never create a replacement to repair a part.\nCompile errors return status rejected without applying. Creation requires validated placement bounds.\nAn update may apply with validation unavailable; that is a diagnostic, not proof that it compiles.\nAn applied part remains in the scene even if thickness fails or is unavailable. Use the returned objectId for repairs.\nWhen thickness fails, increase thin walls and fragile connections while preserving requested dimensions and openings.\nAt most four applied printability repairs are allowed per logical build/edit in this run. Respect the returned limit;\ndo not bypass it by recreating the part. Unavailable thickness is not a pass. Report unresolved issues honestly.\nBuild connected geometry and avoid features below 1.4 mm. OpenSCAD source dimensions use millimetres,\nindependently of the scene display unit. New parts are centered and grounded from compiled bounds.\nOpenSCAD is Z-up; the scene-editing API is Y-up. For an unrotated part at its creation-time scale and an identity parent transform,\na source point [x,y,z] mm maps to scene {x:y, y:z, z:-x} times the unitMultiplier returned by scene.read(), plus nodeGetPos(partId).\nAccount for the part and parent transforms when they are not identity; source coordinates are not scene coordinates.\nAfter a major change, use the scene screenshot API and emitImage to inspect the applied result.\nA committed effect can survive later failure. Inspect receipts and the existing part before retrying.\nDo not print source in the final answer unless requested; describe the result and any unresolved diagnostics.\n\nRUNTIME\n- Produce a single OpenSCAD file.\n- Do not use imports, includes, external libraries, text, hull, minkowski, polyhedron, or extrudes.\n- Prefer cube, sphere, cylinder, translate, rotate, color, union, difference, and intersection.\n- Do not use $fn, $fa, or $fs; tessellation controls are ignored.\n- Build faceted geometry by combining supported primitives with CSG operations and transforms.\n- Do not use scale(); size primitives directly instead.\n- Use color(\"#rrggbb\") around distinct visible parts.\n- Keep the model centered near the origin and use millimeter-like units consistently.\n- For organic blending, use goop(radius_mm) { ... }. The radius is in millimetres before parent scaling, matching scene-editing; do not multiply by project unitMultiplier. It is ordered: the first child seeds with blend 0, and each later child uses radius_mm.\n- Put core primitives first. Avoid wrapping goop children in union() when they should blend individually.\n\nSCREWS AND THREADS\n- Copy helical_ridge below for rounded screw threads, necks, caps, and coils. Like geometry-node curves, it joins spheres with cylinders; never use stacked rings or disconnected spheres.\n- Dimensions are mm: radius = ridge centerline radius; pitch = axial travel/turn; tube_radius = half ridge thickness; handedness = 1 (right) or -1 (left).\n- For a hand control use thread_hand = 1; // [-1:Left, 1:Right] and pass it directly. Ternary expressions (?:) are unsupported.\n- Embed the ridge in the shaft/neck (e.g. radius = shaft_radius). Hollow fittings: subtract the bore from body + thread. Internal threads: subtract the helix from the socket wall with mating clearance. Mating parts need matching pitch and handedness.\n- Expose diameter, pitch, thread length, and thickness in Customizer; derive sampling. Keep 2 * tube_radius < pitch and allow tube_radius beyond both path endpoints for rounded ends. This is a rounded profile, not ISO/metric V-thread; never claim standards compliance.\n- Build heads/grips separately using cylinders, tapered cylinders, and radial ribs/cutters, not thread samples.\n\nmodule helical_ridge(radius, pitch, turns, tube_radius, handedness) {\n if (radius > 0 && pitch > 0 && turns > 0 && tube_radius > 0) {\n // Limit chord error to 0.03 mm or 3% of the ridge radius.\n chord_error = min(0.03, tube_radius * 0.03);\n steps_per_turn = max(48, ceil(180 / acos(1 - min(1, chord_error / radius))));\n steps = ceil(turns * steps_per_turn);\n for (i = [0:steps]) {\n angle = handedness * 360 * turns * i / steps;\n p = [radius * cos(angle), radius * sin(angle), pitch * turns * i / steps];\n translate(p) sphere(r = tube_radius);\n if (i < steps) {\n next_angle = handedness * 360 * turns * (i + 1) / steps;\n dx = radius * cos(next_angle) - p[0];\n dy = radius * sin(next_angle) - p[1];\n dz = pitch * turns / steps;\n segment_length = sqrt(dx * dx + dy * dy + dz * dz);\n translate(p)\n rotate([0, acos(dz / segment_length), atan2(dy, dx)])\n cylinder(h = segment_length, r = tube_radius);\n }\n }\n }\n}\n\nPARAMETERS\n- Make important top-level constants user-adjustable with OpenSCAD Customizer comments:\n - Numbers with ranges: body_radius = 12; // [4:0.5:40]\n - Counts: leg_count = 4; // [2:8]\n - Booleans: show_base = true;\n - Options: style = \"round\"; // [round, sharp, chunky]\n- Prefer stable size/count/ratio/style controls; keep fragile dependent dimensions as local expressions.\n\nSUPPORTED SUBSET\n- Export is best-effort from a supported CSG subset: cube, sphere, cylinder, translate, rotate, color, union, difference, intersection, and goop.\n- difference() cutters become subtractive primitives, so use clean cutters that fully pass through the target solid.\n- Unsupported OpenSCAD may validate but can warn or skip during export; prefer the supported subset above.";
|
|
@@ -19,6 +19,9 @@ At most four applied printability repairs are allowed per logical build/edit in
|
|
|
19
19
|
do not bypass it by recreating the part. Unavailable thickness is not a pass. Report unresolved issues honestly.
|
|
20
20
|
Build connected geometry and avoid features below 1.4 mm. OpenSCAD source dimensions use millimetres,
|
|
21
21
|
independently of the scene display unit. New parts are centered and grounded from compiled bounds.
|
|
22
|
+
OpenSCAD is Z-up; the scene-editing API is Y-up. For an unrotated part at its creation-time scale and an identity parent transform,
|
|
23
|
+
a source point [x,y,z] mm maps to scene {x:y, y:z, z:-x} times the unitMultiplier returned by scene.read(), plus nodeGetPos(partId).
|
|
24
|
+
Account for the part and parent transforms when they are not identity; source coordinates are not scene coordinates.
|
|
22
25
|
After a major change, use the scene screenshot API and emitImage to inspect the applied result.
|
|
23
26
|
A committed effect can survive later failure. Inspect receipts and the existing part before retrying.
|
|
24
27
|
Do not print source in the final answer unless requested; describe the result and any unresolved diagnostics.
|
|
@@ -32,7 +35,7 @@ RUNTIME
|
|
|
32
35
|
- Do not use scale(); size primitives directly instead.
|
|
33
36
|
- Use color("#rrggbb") around distinct visible parts.
|
|
34
37
|
- Keep the model centered near the origin and use millimeter-like units consistently.
|
|
35
|
-
- For organic blending, use goop(
|
|
38
|
+
- For organic blending, use goop(radius_mm) { ... }. The radius is in millimetres before parent scaling, matching scene-editing; do not multiply by project unitMultiplier. It is ordered: the first child seeds with blend 0, and each later child uses radius_mm.
|
|
36
39
|
- Put core primitives first. Avoid wrapping goop children in union() when they should blend individually.
|
|
37
40
|
|
|
38
41
|
SCREWS AND THREADS
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { CameraConfiguration } from "@womp/kakapo-sdk";
|
|
1
2
|
import type { ArtifactRef, SparkCapability, SparkInvocationContext } from "../capabilities.js";
|
|
2
3
|
import type { WompSceneDraft } from "./scene-draft.js";
|
|
4
|
+
import { type SceneHealth } from "../scene-health.js";
|
|
3
5
|
export type SceneSnapshot = {
|
|
6
|
+
health: SceneHealth;
|
|
4
7
|
revision: string;
|
|
5
8
|
projectId?: number;
|
|
6
9
|
unitMultiplier: number;
|
|
@@ -12,16 +15,21 @@ export type SceneSnapshot = {
|
|
|
12
15
|
}[];
|
|
13
16
|
};
|
|
14
17
|
export type SceneScreenshotOptions = {
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
/** Position/target and orthographic width use Womp scene coordinates and current project units. */
|
|
19
|
+
camera?: CameraConfiguration;
|
|
17
20
|
};
|
|
18
21
|
export type WompSceneApi = {
|
|
19
22
|
read(): Promise<SceneSnapshot>;
|
|
23
|
+
health(objectIds?: number[]): Promise<SceneHealth>;
|
|
20
24
|
edit<T>(callback: (draft: WompSceneDraft) => T | Promise<T>, expectedRevision: string): Promise<T>;
|
|
21
25
|
screenshot(options?: SceneScreenshotOptions): Promise<ArtifactRef>;
|
|
22
26
|
};
|
|
27
|
+
export interface SceneHealthHost {
|
|
28
|
+
read(objectIds: number[], context: SparkInvocationContext): Promise<SceneHealth>;
|
|
29
|
+
}
|
|
23
30
|
export interface SceneHost {
|
|
24
|
-
read(context: SparkInvocationContext): Promise<SceneSnapshot
|
|
31
|
+
read(context: SparkInvocationContext): Promise<Omit<SceneSnapshot, "health">>;
|
|
32
|
+
health?: SceneHealthHost["read"];
|
|
25
33
|
edit<T>(callback: (draft: WompSceneDraft) => T | Promise<T>, expectedRevision: string, context: SparkInvocationContext): Promise<T>;
|
|
26
34
|
screenshot(options: SceneScreenshotOptions, context: SparkInvocationContext): Promise<ArtifactRef>;
|
|
27
35
|
}
|
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { WOMP_SCENE_DECLARATIONS } from "../generated/scene-declarations.js";
|
|
3
|
+
import { unverifiedSceneHealth } from "../scene-health.js";
|
|
4
|
+
const cameraVector = z.object({ x: z.number().finite(), y: z.number().finite(), z: z.number().finite() }).strict();
|
|
3
5
|
const screenshotOptions = z.object({
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
camera: z.object({
|
|
7
|
+
position: cameraVector,
|
|
8
|
+
target: cameraVector,
|
|
9
|
+
projection: z.enum(["perspective", "orthographic"]).optional(),
|
|
10
|
+
horizontalFovDegrees: z.number().finite().optional(),
|
|
11
|
+
orthographicWidth: z.number().finite().positive().optional(),
|
|
12
|
+
}).strict().optional(),
|
|
13
|
+
}).strict();
|
|
7
14
|
export function createSceneCapability(host) {
|
|
8
15
|
return {
|
|
9
16
|
skills: [
|
|
10
17
|
{
|
|
11
18
|
name: "scene-editing",
|
|
12
|
-
version: "
|
|
19
|
+
version: "10",
|
|
13
20
|
description: "Inspect the open scene and edit its objects, materials, curves, and environment.",
|
|
14
|
-
instructions: `Read the scene first and keep its revision and object IDs.
|
|
21
|
+
instructions: `Read the scene first and keep its revision and object IDs. read() includes health.
|
|
22
|
+
Use womp.scene.health() to refresh whole-scene health or womp.scene.health(objectIds) to check specific objects (mapped to their owning Areas).
|
|
23
|
+
Health is a separate snapshot, not proof of consistency with read().revision or that the latest edit finished evaluating. Inspect post-edit health in receipts before claiming success; refresh only when needed.
|
|
24
|
+
Unavailable, stale, or unverified feedback must not trigger blind regeneration or a claim that the model passed. Hosts without health services return unverified health with a reason.
|
|
25
|
+
For confirmed primitive or depth overflow, repair the same target while preserving design, dimensions, active parameter overrides, and unrelated objects. At most two geometry repairs are allowed per target in one logical run.
|
|
26
|
+
Empty Areas may be pending, intentionally empty, mesh-only, or failed. Overflow flags take precedence over possibly stale counts. max_total_depth is a scene hierarchy limit, not measured Boolean depth.
|
|
27
|
+
Call scene.edit(callback, expectedRevision)
|
|
15
28
|
for one coherent undoable change. The callback receives a draft; all node/material/curve functions and enums
|
|
16
29
|
are properties of that draft. The draft expires when the callback ends. Await asynchronous methods.
|
|
17
30
|
Do not start unrelated operations from inside an edit. A stale revision requires reading again.
|
|
@@ -19,42 +32,90 @@ Preserve the existing scene unless asked to remove or replace content. Use seman
|
|
|
19
32
|
known IDs when editing rather than creating duplicates. Distances follow the current project's units
|
|
20
33
|
(read().unitMultiplier is 1 for millimetres, 0.1 for centimetres, approximately 0.0393701 for inches).
|
|
21
34
|
Convert a dimension in millimetres to project units by multiplying it by read().unitMultiplier.
|
|
22
|
-
Area size is a dimensionless scale; Group size stays fixed at one.
|
|
23
|
-
|
|
35
|
+
Area size is a dimensionless scale; Group size stays fixed at one. Lights must be created at root (parentId 0); their size has x/y dimensions.
|
|
36
|
+
Light power must be a finite number in [0, 100].
|
|
37
|
+
nodeCreate(Curve, parentId) creates an empty curve with zero blend. curvePointAdd(curveId, point, index?) returns the new index; omit index to append or supply an integer from 0 through point count to insert before that position.
|
|
38
|
+
Point defaults: position/rotation/handles zero, profile size {x:1,y:1,z:1} in project units, the curve's material, fixed false, round 0. Pass {} for defaults.
|
|
39
|
+
Removing all points is supported. One point renders a single profile primitive, not a path.
|
|
40
|
+
nodeSetUnionResolution(unionId, resolution) sets Area/Union resolution, not child-part resolution, and clamps to 0.01–0.65. Material emission ranges from 0 to 1.
|
|
41
|
+
Blend and mirror goop are radii in millimetres before parent scaling, regardless of project units. nodeSetBlend clamps to 0–500 mm.
|
|
42
|
+
V1/V2 curve transforms are read-only;
|
|
24
43
|
edit their points instead. Handles and closure are available only on V3 Bezier curves.
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
Typed rotations are right-handed intrinsic XYZ Euler degrees, relative to the parent (R = Rx Ry Rz), including curve points and mirrors.
|
|
45
|
+
Points and curve mirrors use the owning curve frame; primitive mirrors use the primitive's parent frame.
|
|
46
|
+
lookAt(origin, target) aligns local +Z; both points must be in the same parent-local space. For a segment along a different local axis,
|
|
47
|
+
use alignAxis(direction, 'x'|'y'|'z'); cylinders and cones extend along local Y. These return rotations, not world-space transforms.
|
|
48
|
+
Alignment chooses a deterministic roll using Y-up (Z-up fallback when parallel); coincident/non-finite directions are errors.
|
|
49
|
+
The SDK handles native XZY conversion; do not swap Euler components or convert again. Geometry Nodes source remains native XZY.
|
|
50
|
+
Colors use RGB values from 0 to 255. Use the exposed enums instead of guessing values.
|
|
51
|
+
For visible geometry color, create or clone a material, call materialSetColor, and assign it with nodeSetMaterialId only to
|
|
52
|
+
Primitive, Curve, Text, SVG, Mesh, Field, Decal or OpenSCAD nodes. Assigning a curve material recolors its existing points; use curvePointSetMaterialId for per-point overrides afterward.
|
|
53
|
+
Areas/Unions, Groups, Lights and Sockets do not accept direct materials. Color ordinary containers through their supported children.
|
|
54
|
+
Geometry Nodes materials belong in the graph: the current FlowIR API does not expose native SetMaterial/SetMaterialIndex.
|
|
55
|
+
Report that limitation without replacing a parametric graph with ordinary primitives. Ordinary Stain/CSG children do not modify graph output.
|
|
27
56
|
nodeSetColor writes the node color field (used by lights); it does not replace a geometry object's material color.
|
|
28
57
|
Do not recolor a shared material unless every object using it should change. Create and assign the material in the same edit as the geometry.
|
|
29
58
|
Create primitives, curves, and text inside Areas/Unions. Respect tree order for subtraction and intersection.
|
|
30
|
-
|
|
31
|
-
|
|
59
|
+
Groups are organizational only; move and rotate their child Areas individually, not the Group. Use unions for blended masses.
|
|
60
|
+
Sphere uses size.x as uniform diameter (y/z do not stretch it). Cylinder/Cone/Pyramid use x as base diameter/width and y as height;
|
|
61
|
+
Torus uses x as outer diameter and y as tube thickness. For these four primitives, nodeSetSize accepts {x, y}; z follows x even when supplied. Cube uses all three dimensions; round controls rounding, not size.
|
|
62
|
+
Build and verify coarse geometry and unfamiliar operations before replicating details. Check one representative result, then repeat it.
|
|
63
|
+
nodeClone is shallow (no children). await cloneSubtree(id, { parentId?, name?, index? }) copies the complete hierarchy with fresh IDs,
|
|
64
|
+
preserving local transforms, order and metadata, remapping internal references and sharing external materials/assets.
|
|
65
|
+
Cloning requires a host transport with atomic scene patches; an unsupported host rejects it without changing the draft.
|
|
66
|
+
For primitives, nodeSetBlend joins against all accumulated preceding geometry in the Area, not only the nearest part. A Union's blend affects its outer join, not joins between its children; top-level Area blend does not smooth its contents.
|
|
67
|
+
For organic joins, overlap primitives and tune blend on the joining children while preserving intended gaps; keep crisp accessories separate.
|
|
32
68
|
Check font, HDR, and material catalogs before selecting names. A host can explicitly report a catalog as
|
|
33
69
|
unavailable. Selecting an HDR resets its intensity; apply a custom intensity after selecting it.
|
|
34
|
-
For library materials, use listSuperMaterials(), then await materialImport(name) inside scene.edit and assign the returned numeric ID with nodeSetMaterialId or curvePointSetMaterialId.
|
|
35
|
-
Filter large read results with JavaScript and print only useful details.
|
|
36
|
-
After a major edit, capture a screenshot outside the transaction and emitImage its reference.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
70
|
+
For library materials, use listSuperMaterials(), then await materialImport(name) inside scene.edit and assign the returned numeric ID with nodeSetMaterialId or curvePointSetMaterialId. A failed import rolls back the draft.
|
|
71
|
+
Filter large read results with JavaScript and print only useful details.
|
|
72
|
+
After a major edit, capture a screenshot outside the transaction and emitImage its reference.
|
|
73
|
+
screenshot({ camera: { position: {x,y,z}, target: {x,y,z}, projection: 'perspective', horizontalFovDegrees: 60 } })
|
|
74
|
+
sets a world-space camera in the same Womp coordinates and project units as scene positions (Y-up).
|
|
75
|
+
For orthographic framing use projection: 'orthographic' and orthographicWidth in project units instead of FOV.
|
|
76
|
+
Direction is derived from position/target; do not use coincident points or an exactly vertical viewing direction.
|
|
77
|
+
Omitted projection/lens settings are preserved. The camera changes immediately and remains active; there is no auto-fit.
|
|
78
|
+
screenshot() keeps the active camera. Do not change a locked camera without the user's instruction. Verify silhouette,
|
|
79
|
+
connections, openings, proportions, and materials before claiming completion. Use the owning top-level Area for scene-space bounds;
|
|
80
|
+
OpenSCAD part bounds are local to the part; individual primitive and curve bounds are unsupported. Renderer bounds and resize calls
|
|
40
81
|
must finish before subsequent writes. Inside scene.edit, complete renderer-backed bounds reads before the first
|
|
41
82
|
draft mutation; the SDK rejects them once the draft is dirty. Inspect committed results when renderer state has not caught up.
|
|
42
83
|
A failed script can retain earlier committed effects. Use receipts and inspect unknown outcomes before retrying.`,
|
|
43
84
|
declarations: WOMP_SCENE_DECLARATIONS,
|
|
44
85
|
examples: [
|
|
86
|
+
"const snapshot = await womp.scene.read(); print(snapshot.health);",
|
|
87
|
+
"print(await womp.scene.health([objectId]));",
|
|
45
88
|
"const snapshot = await womp.scene.read(); print(snapshot.nodes.filter(node => /body/i.test(node.name)));",
|
|
46
|
-
"const snapshot = await womp.scene.read(); const diameter = 20 * snapshot.unitMultiplier; const result = await womp.scene.edit(scene => { const areaId = scene.nodeCreate(scene.NodeType.Union, 0); const bodyId = scene.nodeCreate(scene.NodeType.Primitive, areaId); scene.nodeSetName(bodyId, '20 mm orange sphere'); scene.nodeSetPrimType(bodyId, scene.PrimitiveType.Sphere); scene.nodeSetSize(bodyId, { x: diameter, y: diameter, z: diameter }); scene.nodeSetPos(bodyId, { x: 0, y: diameter / 2, z: 0 }); const materialId = scene.materialCreate(); scene.materialSetColor(materialId, { r: 255, g: 128, b: 0 }); scene.nodeSetMaterialId(bodyId, materialId); return { areaId, bodyId }; }, snapshot.revision); print(result); await emitImage(await womp.scene.screenshot({
|
|
89
|
+
"const snapshot = await womp.scene.read(); const diameter = 20 * snapshot.unitMultiplier; const result = await womp.scene.edit(scene => { const areaId = scene.nodeCreate(scene.NodeType.Union, 0); const bodyId = scene.nodeCreate(scene.NodeType.Primitive, areaId); scene.nodeSetName(bodyId, '20 mm orange sphere'); scene.nodeSetPrimType(bodyId, scene.PrimitiveType.Sphere); scene.nodeSetSize(bodyId, { x: diameter, y: diameter, z: diameter }); scene.nodeSetPos(bodyId, { x: 0, y: diameter / 2, z: 0 }); const materialId = scene.materialCreate(); scene.materialSetColor(materialId, { r: 255, g: 128, b: 0 }); scene.nodeSetMaterialId(bodyId, materialId); return { areaId, bodyId }; }, snapshot.revision); print(result); await emitImage(await womp.scene.screenshot({ camera: { position: { x: diameter * 2, y: diameter * 1.5, z: diameter * 2 }, target: { x: 0, y: diameter / 2, z: 0 }, projection: 'perspective', horizontalFovDegrees: 60 } }));",
|
|
47
90
|
],
|
|
48
91
|
},
|
|
49
92
|
],
|
|
50
|
-
createApi: (context) =>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
93
|
+
createApi: (context) => {
|
|
94
|
+
const health = async (objectIds) => {
|
|
95
|
+
const ids = z.array(z.number().int().positive()).default([]).parse(objectIds);
|
|
96
|
+
context.signal.throwIfAborted();
|
|
97
|
+
if (!host.health)
|
|
98
|
+
return unverifiedSceneHealth([], "This host does not provide scene health services.");
|
|
99
|
+
try {
|
|
100
|
+
const result = await host.health(ids, context);
|
|
101
|
+
context.signal.throwIfAborted();
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
context.signal.throwIfAborted();
|
|
106
|
+
return unverifiedSceneHealth([], `Scene health unavailable: ${String(error)}`);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
read: async () => ({ ...await host.read(context), health: await health() }),
|
|
111
|
+
health,
|
|
112
|
+
edit: (callback, expectedRevision) => {
|
|
113
|
+
if (typeof callback !== "function")
|
|
114
|
+
throw new Error("scene.edit requires a callback.");
|
|
115
|
+
return host.edit(callback, z.string().min(1).parse(expectedRevision), context);
|
|
116
|
+
},
|
|
117
|
+
screenshot: (options) => host.screenshot(screenshotOptions.parse(options ?? {}), context),
|
|
118
|
+
};
|
|
119
|
+
},
|
|
59
120
|
};
|
|
60
121
|
}
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -14,8 +14,7 @@ export type { AccountHost, AccountRead, AccountSection, AccountScope, AccountRes
|
|
|
14
14
|
export type { KnowledgeAnswer, KnowledgeHost, WompKnowledgeApi, } from "./capabilities/knowledge.js";
|
|
15
15
|
export { createOpenScadCapability } from "./capabilities/openscad.js";
|
|
16
16
|
export { createGeometryCapability } from "./capabilities/geometry.js";
|
|
17
|
-
export {
|
|
18
|
-
export type { SceneHealthHost, WompHealthApi } from "./capabilities/health.js";
|
|
17
|
+
export type { SceneHealthHost } from "./capabilities/scene.js";
|
|
19
18
|
export { createFlowCapability } from "./capabilities/flow.js";
|
|
20
19
|
export type { FlowHost, FlowSnapshot, FlowWrite, FlowWriteResult, WompFlowApi, } from "./capabilities/flow.js";
|
|
21
20
|
export type { GeometryHost, GeometryTarget, GeometryInstance, GeometryParamValue, GeometryWrite, GeometryWriteResult, WompGeometryApi, } from "./capabilities/geometry.js";
|
package/dist/capabilities.js
CHANGED
|
@@ -5,7 +5,6 @@ export { createOperationsCapability } from "./capabilities/operations.js";
|
|
|
5
5
|
export { createAssetsCapability } from "./capabilities/assets.js";
|
|
6
6
|
export { createOpenScadCapability } from "./capabilities/openscad.js";
|
|
7
7
|
export { createGeometryCapability } from "./capabilities/geometry.js";
|
|
8
|
-
export { createSceneHealthCapability } from "./capabilities/health.js";
|
|
9
8
|
export { createFlowCapability } from "./capabilities/flow.js";
|
|
10
9
|
/** The enabled bindings are also the only source of discoverable skills. */
|
|
11
10
|
export function composeCapabilities(capabilities) {
|
package/dist/client.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type SparkChatTransportOptions = {
|
|
|
10
10
|
headers?: HeadersResolver;
|
|
11
11
|
fetch?: typeof globalThis.fetch;
|
|
12
12
|
getSettings?: () => SparkRunSettings;
|
|
13
|
+
onRun?: (run: SparkRun) => void;
|
|
13
14
|
};
|
|
14
15
|
export declare class SparkChatTransport implements ChatTransport<SparkUIMessage> {
|
|
15
16
|
private readonly api;
|
|
@@ -17,6 +18,7 @@ export declare class SparkChatTransport implements ChatTransport<SparkUIMessage>
|
|
|
17
18
|
private readonly headers?;
|
|
18
19
|
private readonly fetchImplementation;
|
|
19
20
|
private readonly getSettings?;
|
|
21
|
+
private readonly onRun?;
|
|
20
22
|
private readonly activeRuns;
|
|
21
23
|
constructor(options: SparkChatTransportOptions);
|
|
22
24
|
sendMessages(options: {
|
|
@@ -36,6 +38,9 @@ export declare class SparkChatTransport implements ChatTransport<SparkUIMessage>
|
|
|
36
38
|
title?: string;
|
|
37
39
|
}): Promise<SparkConversation>;
|
|
38
40
|
getConversation(id: string): Promise<SparkConversation>;
|
|
41
|
+
getRun(id: string): Promise<SparkRun>;
|
|
42
|
+
saveConversationSettings(id: string, settings: SparkRunSettings): Promise<void>;
|
|
43
|
+
deleteConversation(id: string): Promise<void>;
|
|
39
44
|
listConversations(): Promise<SparkConversationSummary[]>;
|
|
40
45
|
private settingsFrom;
|
|
41
46
|
private openRunStream;
|
package/dist/client.js
CHANGED
|
@@ -27,6 +27,7 @@ export class SparkChatTransport {
|
|
|
27
27
|
headers;
|
|
28
28
|
fetchImplementation;
|
|
29
29
|
getSettings;
|
|
30
|
+
onRun;
|
|
30
31
|
activeRuns = new Map();
|
|
31
32
|
constructor(options) {
|
|
32
33
|
this.api = options.api.replace(/\/$/u, "");
|
|
@@ -34,6 +35,7 @@ export class SparkChatTransport {
|
|
|
34
35
|
this.headers = options.headers;
|
|
35
36
|
this.fetchImplementation = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
36
37
|
this.getSettings = options.getSettings;
|
|
38
|
+
this.onRun = options.onRun;
|
|
37
39
|
}
|
|
38
40
|
async sendMessages(options) {
|
|
39
41
|
const message = options.messageId === undefined
|
|
@@ -56,6 +58,7 @@ export class SparkChatTransport {
|
|
|
56
58
|
throw await responseError(response);
|
|
57
59
|
const body = await response.json();
|
|
58
60
|
this.activeRuns.set(options.chatId, body.run.id);
|
|
61
|
+
this.onRun?.(body.run);
|
|
59
62
|
return this.openRunStream(body.run.id, options.abortSignal, options.headers);
|
|
60
63
|
}
|
|
61
64
|
async reconnectToStream(options) {
|
|
@@ -107,6 +110,25 @@ export class SparkChatTransport {
|
|
|
107
110
|
const body = await response.json();
|
|
108
111
|
return body.conversation;
|
|
109
112
|
}
|
|
113
|
+
async getRun(id) {
|
|
114
|
+
const response = await this.request(`/runs/${encodeURIComponent(id)}`, { method: "GET" });
|
|
115
|
+
if (!response.ok)
|
|
116
|
+
throw await responseError(response);
|
|
117
|
+
return (await response.json()).run;
|
|
118
|
+
}
|
|
119
|
+
async saveConversationSettings(id, settings) {
|
|
120
|
+
const response = await this.request(`/conversations/${encodeURIComponent(id)}/settings`, {
|
|
121
|
+
method: "PATCH", body: JSON.stringify(settings),
|
|
122
|
+
});
|
|
123
|
+
if (response.ok === false)
|
|
124
|
+
throw await responseError(response);
|
|
125
|
+
}
|
|
126
|
+
async deleteConversation(id) {
|
|
127
|
+
const response = await this.request(`/conversations/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
128
|
+
if (response.ok === false)
|
|
129
|
+
throw await responseError(response);
|
|
130
|
+
this.activeRuns.delete(id);
|
|
131
|
+
}
|
|
110
132
|
async listConversations() {
|
|
111
133
|
const response = await this.request("/conversations", { method: "GET" });
|
|
112
134
|
if (response.ok === false)
|
package/dist/coordinates.d.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { Transform, Vec3 } from "@womp/kakapo-sdk";
|
|
2
2
|
export declare const INTERNAL_UNITS_PER_MM: number;
|
|
3
|
-
export declare const WOMP_BLEND_MULTIPLIER = 55;
|
|
4
|
-
export declare const MAX_ENGINE_BLEND = 500;
|
|
5
|
-
export declare const MAX_GOOP_MM: number;
|
|
6
|
-
export declare const MAX_MIRROR_GOOP_MM = 1000;
|
|
7
3
|
export declare function sceneUnitMultiplier(value: unknown): number;
|
|
8
4
|
export declare function toEnginePosition(value: Vec3, unitMultiplier?: number): Vec3;
|
|
9
5
|
export declare function fromEnginePosition(value: Vec3, unitMultiplier?: number): Vec3;
|
|
@@ -15,9 +11,6 @@ export declare function toEngineTransform(input: {
|
|
|
15
11
|
rotation?: Vec3;
|
|
16
12
|
dimensions?: Vec3;
|
|
17
13
|
}): Partial<Transform>;
|
|
18
|
-
export declare function toEngineBlend(goopMm: number): number;
|
|
19
|
-
export declare function fromEngineBlend(blend: number): number;
|
|
20
|
-
export declare function toEngineMirrorGoop(goopMm: number): number;
|
|
21
14
|
export type MirrorAxis = "x" | "y" | "z";
|
|
22
15
|
export declare function toEngineMirrorRotation(axis: MirrorAxis): Vec3;
|
|
23
16
|
export declare function fromEngineMirrorRotation(rotation: Vec3): MirrorAxis | undefined;
|
package/dist/coordinates.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
+
import { xyzRotationMatrix, xyzRotationFromMatrix } from "@womp/kakapo-sdk";
|
|
1
2
|
export const INTERNAL_UNITS_PER_MM = 1 / 25;
|
|
2
|
-
export const WOMP_BLEND_MULTIPLIER = 55;
|
|
3
|
-
export const MAX_ENGINE_BLEND = 500;
|
|
4
|
-
export const MAX_GOOP_MM = MAX_ENGINE_BLEND / WOMP_BLEND_MULTIPLIER;
|
|
5
|
-
export const MAX_MIRROR_GOOP_MM = 1000;
|
|
6
3
|
const BASIS = [
|
|
7
4
|
[0, 0, -1],
|
|
8
5
|
[0, 1, 0],
|
|
@@ -50,21 +47,8 @@ export function fromEngineDimensions(value, unitMultiplier = 1) {
|
|
|
50
47
|
};
|
|
51
48
|
}
|
|
52
49
|
export function convertRotationBasis(value) {
|
|
53
|
-
//
|
|
54
|
-
const
|
|
55
|
-
const y = value.y * Math.PI / 180;
|
|
56
|
-
const z = value.z * Math.PI / 180;
|
|
57
|
-
const a = Math.cos(x);
|
|
58
|
-
const b = Math.sin(x);
|
|
59
|
-
const c = Math.cos(y);
|
|
60
|
-
const d = Math.sin(y);
|
|
61
|
-
const e = Math.cos(z);
|
|
62
|
-
const f = Math.sin(z);
|
|
63
|
-
const source = [
|
|
64
|
-
[c * e, -f, d * e],
|
|
65
|
-
[a * c * f + b * d, a * e, a * d * f - b * c],
|
|
66
|
-
[b * c * f - a * d, b * e, b * d * f + a * c],
|
|
67
|
-
];
|
|
50
|
+
// Both typed APIs use XYZ. Only change basis here; the SDK owns native XZY conversion.
|
|
51
|
+
const source = xyzRotationMatrix(value);
|
|
68
52
|
const multiply = (left, right) => {
|
|
69
53
|
const cell = (row, column) => left[row][0] * right[0][column] +
|
|
70
54
|
left[row][1] * right[1][column] +
|
|
@@ -76,19 +60,7 @@ export function convertRotationBasis(value) {
|
|
|
76
60
|
];
|
|
77
61
|
};
|
|
78
62
|
const converted = multiply(multiply(BASIS, source), BASIS);
|
|
79
|
-
|
|
80
|
-
const nearGimbalLock = Math.abs(converted[0][1]) >= 0.9999999;
|
|
81
|
-
const convertedX = nearGimbalLock
|
|
82
|
-
? Math.atan2(-converted[1][2], converted[2][2])
|
|
83
|
-
: Math.atan2(converted[2][1], converted[1][1]);
|
|
84
|
-
const convertedY = nearGimbalLock
|
|
85
|
-
? 0
|
|
86
|
-
: Math.atan2(converted[0][2], converted[0][0]);
|
|
87
|
-
return {
|
|
88
|
-
x: clean(convertedX * 180 / Math.PI),
|
|
89
|
-
y: clean(convertedY * 180 / Math.PI),
|
|
90
|
-
z: clean(convertedZ * 180 / Math.PI),
|
|
91
|
-
};
|
|
63
|
+
return xyzRotationFromMatrix(converted);
|
|
92
64
|
}
|
|
93
65
|
export function toEngineTransform(input) {
|
|
94
66
|
return {
|
|
@@ -97,15 +69,6 @@ export function toEngineTransform(input) {
|
|
|
97
69
|
...(input.dimensions ? { scale: toEngineDimensions(input.dimensions) } : {}),
|
|
98
70
|
};
|
|
99
71
|
}
|
|
100
|
-
export function toEngineBlend(goopMm) {
|
|
101
|
-
return Math.min(MAX_ENGINE_BLEND, Math.max(0, goopMm * WOMP_BLEND_MULTIPLIER));
|
|
102
|
-
}
|
|
103
|
-
export function fromEngineBlend(blend) {
|
|
104
|
-
return blend / WOMP_BLEND_MULTIPLIER;
|
|
105
|
-
}
|
|
106
|
-
export function toEngineMirrorGoop(goopMm) {
|
|
107
|
-
return Math.min(MAX_MIRROR_GOOP_MM, Math.max(0, goopMm)) * WOMP_BLEND_MULTIPLIER;
|
|
108
|
-
}
|
|
109
72
|
export function toEngineMirrorRotation(axis) {
|
|
110
73
|
if (axis === "x")
|
|
111
74
|
return { x: 0, y: 90, z: 0 };
|
|
@@ -114,13 +77,11 @@ export function toEngineMirrorRotation(axis) {
|
|
|
114
77
|
return { x: 0, y: 180, z: 0 };
|
|
115
78
|
}
|
|
116
79
|
export function fromEngineMirrorRotation(rotation) {
|
|
117
|
-
const
|
|
118
|
-
const y = rotation.y * Math.PI / 180;
|
|
119
|
-
const z = rotation.z * Math.PI / 180;
|
|
80
|
+
const matrix = xyzRotationMatrix(rotation);
|
|
120
81
|
const engineNormal = {
|
|
121
|
-
x:
|
|
122
|
-
y:
|
|
123
|
-
z:
|
|
82
|
+
x: matrix[0][0],
|
|
83
|
+
y: matrix[1][0],
|
|
84
|
+
z: matrix[2][0],
|
|
124
85
|
};
|
|
125
86
|
const publicNormal = {
|
|
126
87
|
x: -engineNormal.z,
|