@womp/spark-agent 4.4.1 → 5.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/.env.example +6 -0
- package/README.md +21 -2
- package/dist/capabilities/geometry.d.ts +0 -3
- package/dist/capabilities/geometry.js +7 -8
- package/dist/capabilities/openscad-instructions.d.ts +1 -1
- package/dist/capabilities/openscad-instructions.js +80 -81
- package/dist/capabilities/openscad.d.ts +0 -3
- package/dist/capabilities/openscad.js +1 -2
- package/dist/capabilities/scene.d.ts +4 -11
- package/dist/capabilities/scene.js +38 -55
- package/dist/generated/geometry-declarations.d.ts +1 -1
- package/dist/generated/geometry-declarations.js +1 -1
- package/dist/generated/openscad-declarations.d.ts +1 -1
- package/dist/generated/openscad-declarations.js +1 -1
- package/dist/generated/scene-declarations.d.ts +1 -1
- package/dist/generated/scene-declarations.js +1 -1
- package/dist/native-executor.js +2 -2
- package/dist/openscad.d.ts +1 -2
- package/dist/openscad.js +1 -19
- package/dist/quickjs-executor.js +1 -1
- package/dist/repair-state.d.ts +2 -0
- package/dist/repair-state.js +15 -0
- package/dist/runtime.d.ts +1 -5
- package/dist/runtime.js +8 -14
- package/dist/scene-environment.js +1 -1
- package/dist/scene-health.d.ts +1 -2
- package/dist/scene-health.js +0 -17
- package/dist/scene-operations.d.ts +5 -5
- package/dist/scene-operations.js +24 -43
- package/dist/sdk-geometry-host.d.ts +2 -2
- package/dist/sdk-geometry-host.js +44 -61
- package/dist/sdk-openscad-host.js +63 -102
- package/dist/sdk-scene-health-host.d.ts +2 -1
- package/dist/sdk-scene-health-host.js +54 -4
- package/dist/sdk-scene-host.d.ts +20 -5
- package/dist/sdk-scene-host.js +34 -31
- package/dist/sdk-scene-screenshot.d.ts +2 -2
- package/dist/sdk-scene-screenshot.js +3 -16
- package/dist/server/scene-access.d.ts +4 -0
- package/dist/server/scene-access.js +40 -0
- package/dist/server/standalone.js +24 -19
- package/dist/womp-scene-api.d.ts +15 -0
- package/dist/womp-scene-api.js +18 -1
- package/package.json +139 -139
- package/dist/server/scene-initialization.d.ts +0 -3
- package/dist/server/scene-initialization.js +0 -44
package/.env.example
ADDED
package/README.md
CHANGED
|
@@ -6,7 +6,23 @@ Spark Agent uses AI SDK 7 `UIMessage` and `UIMessageChunk` as its public chat pr
|
|
|
6
6
|
JavaScript uses the host's executor and capabilities. Native browser execution and isolated QuickJS
|
|
7
7
|
execution use the same model loop. The host owns conversations, artifacts, authorization, and connections.
|
|
8
8
|
|
|
9
|
-
## Requirements
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
### Migrating to 5.0
|
|
12
|
+
|
|
13
|
+
- Replace scene capability `read`/`edit` with `access(callback)`. Inspect the current draft
|
|
14
|
+
through `scene.nodes({ ids, name, type, parentId, offset, limit })`, not a `nodes` array.
|
|
15
|
+
Results default to 100, with a maximum page size of 1,000.
|
|
16
|
+
- Update custom scene hosts to `SceneHost.access` and SDK adapters to `acquireAccess`.
|
|
17
|
+
`onCommitted` receives the acknowledged scene; keep host access suspended until synchronization succeeds.
|
|
18
|
+
- Remove scene revision arguments from Geometry/OpenSCAD writes. Current parameters and overrides
|
|
19
|
+
are prepared inside protected access. Hosts must still enforce connection/project ownership.
|
|
20
|
+
- Screenshot calls capture the current native viewport; remove target, view and camera options.
|
|
21
|
+
- Scene health is explicitly requested through `scene.health()`, not automatically after each edit.
|
|
22
|
+
- This release requires Kakapo SDK 0.7.0. Native and QuickJS executors now allow 100,000 host calls
|
|
23
|
+
per execution. Receipts, cancellation, timeout limits and uncertain-outcome handling remain enforced.
|
|
24
|
+
|
|
25
|
+
### Runtime
|
|
10
26
|
|
|
11
27
|
- Node.js 22.13 or newer
|
|
12
28
|
- AI SDK 7
|
|
@@ -339,7 +355,10 @@ The standalone host uses a fresh QuickJS runtime for each JavaScript execution.
|
|
|
339
355
|
or browser globals and receives only the composed capability APIs. Imports, filesystem access,
|
|
340
356
|
network APIs, and host environment variables are not exposed. QuickJS limits memory, stack size,
|
|
341
357
|
execution time, source size, host calls, and data crossing the host bridge. Browser hosts can instead
|
|
342
|
-
choose native page execution, with the page-global access and interruption limitations described above.
|
|
358
|
+
choose native page execution, with the page-global access and interruption limitations described above.
|
|
359
|
+
Both executors allow 100,000 host calls per execution by default; the counter resets for each
|
|
360
|
+
execution. QuickJS supports overriding this through `maxHostCalls`. Cancellation, timeouts,
|
|
361
|
+
and the other execution limits still apply.
|
|
343
362
|
|
|
344
363
|
QuickJS isolates generated code from the JavaScript host API; it is not an operating-system security
|
|
345
364
|
boundary. Multi-tenant deployments must additionally isolate workers, authorize every Kakapo
|
|
@@ -17,13 +17,11 @@ export type GeometryTarget = {
|
|
|
17
17
|
instanceId?: number;
|
|
18
18
|
source: string | null;
|
|
19
19
|
instances: GeometryInstance[];
|
|
20
|
-
revision: string;
|
|
21
20
|
diagnostics: string[];
|
|
22
21
|
};
|
|
23
22
|
export type GeometryWrite = {
|
|
24
23
|
objectId: number;
|
|
25
24
|
source: string;
|
|
26
|
-
expectedRevision: string;
|
|
27
25
|
};
|
|
28
26
|
export type GeometryWriteResult = {
|
|
29
27
|
status: "applied" | "rejected";
|
|
@@ -31,7 +29,6 @@ export type GeometryWriteResult = {
|
|
|
31
29
|
graphId?: number;
|
|
32
30
|
instanceId?: number;
|
|
33
31
|
affectedObjectIds: number[];
|
|
34
|
-
revision: string;
|
|
35
32
|
diagnostics: string[];
|
|
36
33
|
};
|
|
37
34
|
export type WompGeometryApi = {
|
|
@@ -5,7 +5,6 @@ const write = z
|
|
|
5
5
|
.object({
|
|
6
6
|
objectId,
|
|
7
7
|
source: z.string().trim().min(1),
|
|
8
|
-
expectedRevision: z.string().min(1),
|
|
9
8
|
})
|
|
10
9
|
.strict();
|
|
11
10
|
export function createGeometryCapability(host, reference) {
|
|
@@ -15,26 +14,26 @@ export function createGeometryCapability(host, reference) {
|
|
|
15
14
|
name: "geometry-nodes",
|
|
16
15
|
version: "5",
|
|
17
16
|
description: "Create and edit visual Geometry Nodes graphs with adjustable parameters and animation.",
|
|
18
|
-
instructions: `Use womp.geometry for requested Geometry Nodes, parametric organic/procedural geometry, or animation.
|
|
17
|
+
instructions: `Use womp.geometry for requested Geometry Nodes, parametric organic/procedural geometry, or animation.
|
|
19
18
|
Keep follow-up edits to an existing Geometry Nodes object in its graph, preserving its adjustable parameters and instances.
|
|
20
19
|
Read the target after any scene edit this build needs. read(0) reads the scene root as a creation container.
|
|
21
20
|
write updates an existing shared graph, attaches a new graph to an Area without one, or creates a new Area beneath the root/Group target. Use the returned objectId for follow-up edits.
|
|
22
|
-
Read geometry-nodes-reference before writing source. Supply the complete FlowIR text
|
|
23
|
-
|
|
21
|
+
Read geometry-nodes-reference before writing source. Supply the complete FlowIR text after inspecting the target. JavaScript calls execute JavaScript; source is a FlowIR string, not JavaScript or JSON.
|
|
22
|
+
Inspect the current target before replacing its graph; preserve unrelated geometry and parameter overrides.
|
|
24
23
|
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.
|
|
25
24
|
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.
|
|
26
25
|
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.
|
|
27
26
|
SetMaterial assigns one scene material to every primitive it receives. Its Material input is a scene material ID, not a color; 0 selects the scene default material.
|
|
28
|
-
Obtain that ID from the scene-editing skill inside one scene.
|
|
29
|
-
|
|
27
|
+
Obtain that ID from the scene-editing skill inside one scene.access, where those lookups live: materialList with materialGetName and materialGetColor to reuse an existing material, materialCreate with materialSetColor and materialSetName for a new one, or listSuperMaterials with materialImport for a library material. Prefer reusing a material that already matches the request over creating another, and do not recolor a shared material unless every object using it should change.
|
|
28
|
+
Create or import the material inside scene.access before writing the geometry target, all within one JavaScript call. Each call has a fresh scope and cannot read an earlier call's variables.
|
|
30
29
|
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.
|
|
31
30
|
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.
|
|
32
31
|
After applying, read the returned object and use the scene screenshot API with emitImage to inspect the applied result.`,
|
|
33
32
|
declarations: WOMP_GEOMETRY_DECLARATIONS,
|
|
34
33
|
examples: [
|
|
35
34
|
"const target = await womp.geometry.read(objectId); print(target);",
|
|
36
|
-
"const target = await womp.geometry.read(objectId); print(await womp.geometry.write({ objectId: target.objectId, source: editedFlowIR
|
|
37
|
-
"const materialId = await womp.scene.
|
|
35
|
+
"const target = await womp.geometry.read(objectId); print(await womp.geometry.write({ objectId: target.objectId, source: editedFlowIR }));",
|
|
36
|
+
"const materialId = await womp.scene.access(scene => { const id = scene.materialCreate(); scene.materialSetName(id, 'Red'); scene.materialSetColor(id, { r: 255, g: 0, b: 0 }); return id; }); const target = await womp.geometry.read(0); const source = ['param Float size = 20.0 min=1 max=100 step=1', 'node box Primitive Type=Cube Position=Vec3(0.0,0.0,0.0) Rotation=Vec3(0.0,0.0,0.0) Size=size Roundness=0.0', `node painted SetMaterial Material=${materialId} Geometry=box.Geometry`, 'out Geometry=painted.Geometry'].join('\\n'); print(await womp.geometry.write({ objectId: target.objectId, source }));",
|
|
38
37
|
],
|
|
39
38
|
},
|
|
40
39
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const OPENSCAD_INSTRUCTIONS = "Use womp.openscad to create or update one adjustable CAD part. Read an existing part before editing it.\nKeep follow-up edits to an existing CAD part in that part unless the user asks to change representation.\nread(0) returns the root container
|
|
1
|
+
export declare const OPENSCAD_INSTRUCTIONS = "Use womp.openscad to create or update one adjustable CAD part. Read an existing part before editing it.\nKeep follow-up edits to an existing CAD part in that part unless the user asks to change representation.\nread(0) returns the root container 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.\nPreserve intervening user changes. Ask the user when the intended edits conflict or remain ambiguous.\nExisting 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 scene.unitMultiplier inside the access callback, 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. 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.";
|
|
@@ -1,82 +1,81 @@
|
|
|
1
|
-
export const OPENSCAD_INSTRUCTIONS = `Use womp.openscad to create or update one adjustable CAD part. Read an existing part before editing it.
|
|
2
|
-
Keep follow-up edits to an existing CAD part in that part unless the user asks to change representation.
|
|
3
|
-
read(0) returns the root container
|
|
4
|
-
part or container. Use the scene-editing skill when available to discover other scene objects.
|
|
5
|
-
create requires a parentId (0 means the scene root); update requires the existing objectId.
|
|
6
|
-
Both methods compile the effective source and parameters, apply one undoable change, and check wall thickness.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
An
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
p
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
- difference() cutters become subtractive primitives, so use clean cutters that fully pass through the target solid.
|
|
1
|
+
export const OPENSCAD_INSTRUCTIONS = `Use womp.openscad to create or update one adjustable CAD part. Read an existing part before editing it.
|
|
2
|
+
Keep follow-up edits to an existing CAD part in that part unless the user asks to change representation.
|
|
3
|
+
read(0) returns the root container for creation. read(id) returns an existing
|
|
4
|
+
part or container. Use the scene-editing skill when available to discover other scene objects.
|
|
5
|
+
create requires a parentId (0 means the scene root); update requires the existing objectId.
|
|
6
|
+
Both methods compile the effective source and parameters, apply one undoable change, and check wall thickness.
|
|
7
|
+
Preserve intervening user changes. Ask the user when the intended edits conflict or remain ambiguous.
|
|
8
|
+
Existing overrides are preserved for parameters still present;
|
|
9
|
+
params supplies overrides to merge into the current values. Keep top-level parameter names stable.
|
|
10
|
+
Return complete source on every update and preserve unrelated geometry. Never create a replacement to repair a part.
|
|
11
|
+
Compile errors return status rejected without applying. Creation requires validated placement bounds.
|
|
12
|
+
An update may apply with validation unavailable; that is a diagnostic, not proof that it compiles.
|
|
13
|
+
An applied part remains in the scene even if thickness fails or is unavailable. Use the returned objectId for repairs.
|
|
14
|
+
When thickness fails, increase thin walls and fragile connections while preserving requested dimensions and openings.
|
|
15
|
+
At most four applied printability repairs are allowed per logical build/edit in this run. Respect the returned limit;
|
|
16
|
+
do not bypass it by recreating the part. Unavailable thickness is not a pass. Report unresolved issues honestly.
|
|
17
|
+
Build connected geometry and avoid features below 1.4 mm. OpenSCAD source dimensions use millimetres,
|
|
18
|
+
independently of the scene display unit. New parts are centered and grounded from compiled bounds.
|
|
19
|
+
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,
|
|
20
|
+
a source point [x,y,z] mm maps to scene {x:y, y:z, z:-x} times the scene.unitMultiplier inside the access callback, plus nodeGetPos(partId).
|
|
21
|
+
Account for the part and parent transforms when they are not identity; source coordinates are not scene coordinates.
|
|
22
|
+
After a major change, use the scene screenshot API and emitImage to inspect the applied result.
|
|
23
|
+
A committed effect can survive later failure. Inspect receipts and the existing part before retrying.
|
|
24
|
+
Do not print source in the final answer unless requested; describe the result and any unresolved diagnostics.
|
|
25
|
+
|
|
26
|
+
RUNTIME
|
|
27
|
+
- Produce a single OpenSCAD file.
|
|
28
|
+
- Do not use imports, includes, external libraries, text, hull, minkowski, polyhedron, or extrudes.
|
|
29
|
+
- Prefer cube, sphere, cylinder, translate, rotate, color, union, difference, and intersection.
|
|
30
|
+
- Do not use $fn, $fa, or $fs; tessellation controls are ignored.
|
|
31
|
+
- Build faceted geometry by combining supported primitives with CSG operations and transforms.
|
|
32
|
+
- Do not use scale(); size primitives directly instead.
|
|
33
|
+
- Use color("#rrggbb") around distinct visible parts.
|
|
34
|
+
- Keep the model centered near the origin and use millimeter-like units consistently.
|
|
35
|
+
- 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
|
+
- Put core primitives first. Avoid wrapping goop children in union() when they should blend individually.
|
|
37
|
+
|
|
38
|
+
SCREWS AND THREADS
|
|
39
|
+
- Copy helical_ridge below for rounded screw threads, necks, caps, and coils. It joins spheres with cylinders; never use stacked rings or disconnected spheres.
|
|
40
|
+
- Dimensions are mm: radius = ridge centerline radius; pitch = axial travel/turn; tube_radius = half ridge thickness; handedness = 1 (right) or -1 (left).
|
|
41
|
+
- For a hand control use thread_hand = 1; // [-1:Left, 1:Right] and pass it directly. Ternary expressions (?:) are unsupported.
|
|
42
|
+
- 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.
|
|
43
|
+
- 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.
|
|
44
|
+
- Build heads/grips separately using cylinders, tapered cylinders, and radial ribs/cutters, not thread samples.
|
|
45
|
+
|
|
46
|
+
module helical_ridge(radius, pitch, turns, tube_radius, handedness) {
|
|
47
|
+
if (radius > 0 && pitch > 0 && turns > 0 && tube_radius > 0) {
|
|
48
|
+
// Limit chord error to 0.03 mm or 3% of the ridge radius.
|
|
49
|
+
chord_error = min(0.03, tube_radius * 0.03);
|
|
50
|
+
steps_per_turn = max(48, ceil(180 / acos(1 - min(1, chord_error / radius))));
|
|
51
|
+
steps = ceil(turns * steps_per_turn);
|
|
52
|
+
for (i = [0:steps]) {
|
|
53
|
+
angle = handedness * 360 * turns * i / steps;
|
|
54
|
+
p = [radius * cos(angle), radius * sin(angle), pitch * turns * i / steps];
|
|
55
|
+
translate(p) sphere(r = tube_radius);
|
|
56
|
+
if (i < steps) {
|
|
57
|
+
next_angle = handedness * 360 * turns * (i + 1) / steps;
|
|
58
|
+
dx = radius * cos(next_angle) - p[0];
|
|
59
|
+
dy = radius * sin(next_angle) - p[1];
|
|
60
|
+
dz = pitch * turns / steps;
|
|
61
|
+
segment_length = sqrt(dx * dx + dy * dy + dz * dz);
|
|
62
|
+
translate(p)
|
|
63
|
+
rotate([0, acos(dz / segment_length), atan2(dy, dx)])
|
|
64
|
+
cylinder(h = segment_length, r = tube_radius);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
PARAMETERS
|
|
71
|
+
- Make important top-level constants user-adjustable with OpenSCAD Customizer comments:
|
|
72
|
+
- Numbers with ranges: body_radius = 12; // [4:0.5:40]
|
|
73
|
+
- Counts: leg_count = 4; // [2:8]
|
|
74
|
+
- Booleans: show_base = true;
|
|
75
|
+
- Options: style = "round"; // [round, sharp, chunky]
|
|
76
|
+
- Prefer stable size/count/ratio/style controls; keep fragile dependent dimensions as local expressions.
|
|
77
|
+
|
|
78
|
+
SUPPORTED SUBSET
|
|
79
|
+
- Export is best-effort from a supported CSG subset: cube, sphere, cylinder, translate, rotate, color, union, difference, intersection, and goop.
|
|
80
|
+
- difference() cutters become subtractive primitives, so use clean cutters that fully pass through the target solid.
|
|
82
81
|
- Unsupported OpenSCAD may validate but can warn or skip during export; prefer the supported subset above.`;
|
|
@@ -6,13 +6,11 @@ export type OpenScadTarget = {
|
|
|
6
6
|
title: string;
|
|
7
7
|
source: string | null;
|
|
8
8
|
params: Record<string, unknown>;
|
|
9
|
-
revision: string;
|
|
10
9
|
};
|
|
11
10
|
export type OpenScadSource = {
|
|
12
11
|
source: string;
|
|
13
12
|
title: string;
|
|
14
13
|
params?: Record<string, unknown>;
|
|
15
|
-
expectedRevision: string;
|
|
16
14
|
};
|
|
17
15
|
export type OpenScadCreate = OpenScadSource & {
|
|
18
16
|
parentId: number;
|
|
@@ -35,7 +33,6 @@ export type OpenScadPrintability = {
|
|
|
35
33
|
export type OpenScadWriteResult = {
|
|
36
34
|
status: "applied" | "rejected";
|
|
37
35
|
objectId?: number;
|
|
38
|
-
revision: string;
|
|
39
36
|
validation: OpenScadValidation;
|
|
40
37
|
printability?: OpenScadPrintability;
|
|
41
38
|
repairAttempts: number;
|
|
@@ -6,7 +6,6 @@ const source = z.object({
|
|
|
6
6
|
source: z.string().trim().min(1),
|
|
7
7
|
title: z.string().trim().min(1),
|
|
8
8
|
params: z.record(z.string(), z.json()).optional(),
|
|
9
|
-
expectedRevision: z.string().min(1),
|
|
10
9
|
});
|
|
11
10
|
const create = source.extend({ parentId: nodeId }).strict();
|
|
12
11
|
const update = source.extend({ objectId: nodeId.positive() }).strict();
|
|
@@ -21,7 +20,7 @@ export function createOpenScadCapability(host) {
|
|
|
21
20
|
declarations: WOMP_OPENSCAD_DECLARATIONS,
|
|
22
21
|
examples: [
|
|
23
22
|
"const part = await womp.openscad.read(objectId); print(part);",
|
|
24
|
-
"const part = await womp.openscad.read(objectId); print(await womp.openscad.update({ objectId: part.objectId, source: part.source, title: part.title, params: { width: 30 }
|
|
23
|
+
"const part = await womp.openscad.read(objectId); print(await womp.openscad.update({ objectId: part.objectId, source: part.source, title: part.title, params: { width: 30 } }));",
|
|
25
24
|
],
|
|
26
25
|
},
|
|
27
26
|
],
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import type { CameraConfiguration } from "@womp/kakapo-sdk";
|
|
2
1
|
import type { ArtifactRef, SparkCapability, SparkInvocationContext } from "../capabilities.js";
|
|
3
2
|
import type { WompSceneDraft } from "./scene-draft.js";
|
|
4
3
|
import { type SceneHealth } from "../scene-health.js";
|
|
5
4
|
export type SceneSnapshot = {
|
|
6
|
-
health: SceneHealth;
|
|
7
|
-
revision: string;
|
|
8
5
|
projectId?: number;
|
|
9
6
|
unitMultiplier: number;
|
|
10
7
|
nodes: {
|
|
@@ -14,23 +11,19 @@ export type SceneSnapshot = {
|
|
|
14
11
|
parentId: number;
|
|
15
12
|
}[];
|
|
16
13
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
camera?: CameraConfiguration;
|
|
20
|
-
};
|
|
14
|
+
/** Current viewport only; no framing or camera overrides. */
|
|
15
|
+
export type SceneScreenshotOptions = Record<string, never>;
|
|
21
16
|
export type WompSceneApi = {
|
|
22
|
-
read(): Promise<SceneSnapshot>;
|
|
23
17
|
health(objectIds?: number[]): Promise<SceneHealth>;
|
|
24
|
-
|
|
18
|
+
access<T>(callback: (draft: WompSceneDraft) => T | Promise<T>): Promise<T>;
|
|
25
19
|
screenshot(options?: SceneScreenshotOptions): Promise<ArtifactRef>;
|
|
26
20
|
};
|
|
27
21
|
export interface SceneHealthHost {
|
|
28
22
|
read(objectIds: number[], context: SparkInvocationContext): Promise<SceneHealth>;
|
|
29
23
|
}
|
|
30
24
|
export interface SceneHost {
|
|
31
|
-
read(context: SparkInvocationContext): Promise<Omit<SceneSnapshot, "health">>;
|
|
32
25
|
health?: SceneHealthHost["read"];
|
|
33
|
-
|
|
26
|
+
access<T>(callback: (draft: WompSceneDraft) => T | Promise<T>, context: SparkInvocationContext): Promise<T>;
|
|
34
27
|
screenshot(options: SceneScreenshotOptions, context: SparkInvocationContext): Promise<ArtifactRef>;
|
|
35
28
|
}
|
|
36
29
|
export declare function createSceneCapability(host: SceneHost): SparkCapability<WompSceneApi>;
|