scad-gltf 0.1.3 → 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.
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import { execSync, spawn } from "node:child_process";
7
+ import { getGodotBin } from "../src/godot-utils.js";
8
+
9
+ function main() {
10
+ const packedScriptPath = process.argv[2];
11
+
12
+ if (!packedScriptPath) {
13
+ console.error(
14
+ "❌ Error: Path to the packed Node.js generator script is required.",
15
+ );
16
+ console.error(
17
+ "Usage: node bin/scad-play.js <path_to_generated_project.js>",
18
+ );
19
+ process.exit(1);
20
+ }
21
+
22
+ const absScriptPath = path.resolve(process.cwd(), packedScriptPath);
23
+ if (!fs.existsSync(absScriptPath)) {
24
+ console.error(`❌ Error: File not found: ${absScriptPath}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ // 1. Create a unique temporary directory
29
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "scad-play-"));
30
+ console.log(`📦 Unpacking script to temporary directory...\n ${tempDir}`);
31
+
32
+ let appProcess = null;
33
+
34
+ // Ensure cleanup happens on exit
35
+ const cleanup = () => {
36
+ if (fs.existsSync(tempDir)) {
37
+ console.log(`\n🧹 Cleaning up temporary directory...`);
38
+ try {
39
+ fs.rmSync(tempDir, { recursive: true, force: true });
40
+ } catch (e) {
41
+ console.error(`⚠️ Failed to fully clean up temp dir: ${e.message}`);
42
+ }
43
+ }
44
+ };
45
+
46
+ process.on("SIGINT", () => {
47
+ if (appProcess) appProcess.kill("SIGINT");
48
+ cleanup();
49
+ process.exit(0);
50
+ });
51
+
52
+ process.on("SIGTERM", () => {
53
+ if (appProcess) appProcess.kill("SIGTERM");
54
+ cleanup();
55
+ process.exit(0);
56
+ });
57
+
58
+ try {
59
+ // 2. Execute the packed Node.js script inside the temp directory
60
+ console.log(
61
+ `\n⚙️ Running generator script: ${path.basename(absScriptPath)}`,
62
+ );
63
+ execSync(`node "${absScriptPath}"`, { cwd: tempDir, stdio: "inherit" });
64
+
65
+ // 3. Find the resulting project folder and determine its type
66
+ let projectDir = null;
67
+ let projectType = null; // 'godot' | 'web'
68
+ let latestTime = 0;
69
+
70
+ const items = fs.readdirSync(tempDir);
71
+ for (const item of items) {
72
+ const itemPath = path.join(tempDir, item);
73
+ if (fs.statSync(itemPath).isDirectory()) {
74
+ const isGodot = fs.existsSync(path.join(itemPath, "project.godot"));
75
+ const isWeb = fs.existsSync(path.join(itemPath, "package.json"));
76
+ const isBevy = fs.existsSync(path.join(itemPath, "Cargo.toml"));
77
+
78
+ if (isGodot || isWeb || isBevy) {
79
+ const mtime = fs.statSync(itemPath).mtimeMs;
80
+ if (mtime > latestTime) {
81
+ latestTime = mtime;
82
+ projectDir = itemPath;
83
+ projectType = isGodot ? "godot" : isBevy ? "bevy" : "web";
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ if (!projectDir || !projectType) {
90
+ console.error("\n❌ Error: Could not find a valid generated project.");
91
+ console.error(
92
+ "The script did not produce a folder containing 'project.godot' or 'package.json'.",
93
+ );
94
+ cleanup();
95
+ process.exit(1);
96
+ }
97
+
98
+ console.log(
99
+ `\n✔️ Found generated ${projectType.toUpperCase()} project: ${path.basename(projectDir)}`,
100
+ );
101
+
102
+ // 4. Boot the corresponding environment
103
+ if (projectType === "godot") {
104
+ const godotBin = getGodotBin();
105
+
106
+ console.log(`\n⚙️ Importing Godot assets (this may take a moment)...`);
107
+ try {
108
+ // Force Godot to build the .godot/imported cache before attempting to run the game
109
+ execSync(`"${godotBin}" --headless --editor --quit`, {
110
+ cwd: projectDir,
111
+ stdio: "inherit",
112
+ });
113
+ } catch (e) {
114
+ console.log(
115
+ "⚠️ Asset import finished with non-zero exit code (continuing...)",
116
+ );
117
+ }
118
+
119
+ console.log(`\n🎮 Launching Godot Engine...`);
120
+ appProcess = spawn(godotBin, ["--path", projectDir], {
121
+ stdio: "inherit",
122
+ });
123
+ } else if (projectType === "web") {
124
+ console.log(
125
+ `\n🌐 Installing NPM dependencies (this may take a moment)...`,
126
+ );
127
+ try {
128
+ execSync("npm install", { cwd: projectDir, stdio: "inherit" });
129
+ } catch (e) {
130
+ console.log(
131
+ "⚠️ npm install failed or showed errors, attempting to continue anyway...",
132
+ );
133
+ }
134
+
135
+ console.log(`\n🌐 Starting Vite dev server...`);
136
+ const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
137
+
138
+ appProcess = spawn(npmCmd, ["run", "dev"], {
139
+ cwd: projectDir,
140
+ stdio: "inherit",
141
+ });
142
+ } else if (projectType === "bevy") {
143
+ console.log(
144
+ `\n🦀 Compiling and starting Rust Bevy game (this may take a moment)...`,
145
+ );
146
+ const cargoCmd = process.platform === "win32" ? "cargo.exe" : "cargo";
147
+ appProcess = spawn(cargoCmd, ["run"], {
148
+ cwd: projectDir,
149
+ stdio: "inherit",
150
+ });
151
+ }
152
+
153
+ // 5. Handle lifecycle events
154
+ appProcess.on("close", (code) => {
155
+ console.log(`\n🏁 Process exited with code ${code}.`);
156
+ cleanup();
157
+ process.exit(code || 0);
158
+ });
159
+
160
+ appProcess.on("error", (err) => {
161
+ console.error(`\n❌ Failed to start the application: ${err.message}`);
162
+ cleanup();
163
+ process.exit(1);
164
+ });
165
+ } catch (err) {
166
+ console.error(`\n❌ An error occurred during execution:\n`, err.message);
167
+ cleanup();
168
+ process.exit(1);
169
+ }
170
+ }
171
+
172
+ main();
package/bin/scad-web.js CHANGED
@@ -1,141 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import url from "node:url";
6
- import readline from "node:readline";
7
-
8
- // Safely resolve symlinks to find the actual package directory
9
- const __filename = fs.realpathSync(url.fileURLToPath(import.meta.url));
10
- const __dirname = path.dirname(__filename);
11
- const DIR = path.resolve(__dirname, "..");
12
-
13
- // Safely detect if actual data is being piped into the script via STDIN
14
- function hasStdinData() {
15
- try {
16
- const stat = fs.fstatSync(0); // 0 is the file descriptor for STDIN
17
- // isFIFO means piped (echo "foo" | script)
18
- // isFile means redirected (script < foo.txt)
19
- return stat.isFIFO() || stat.isFile();
20
- } catch (e) {
21
- return false;
22
- }
23
- }
24
-
25
- // Writes text to system clipboard
26
- async function writeToClipboard(text) {
27
- const clipboardy = (await import("clipboardy")).default;
28
- await clipboardy.write(text);
29
- }
30
-
31
- function waitForEnter(message) {
32
- return new Promise((resolve) => {
33
- // If standard input was piped/redirected, we need to bypass it and read from the actual terminal
34
- if (!process.stdin.isTTY) {
35
- try {
36
- const tty = process.platform === "win32" ? "CONIN$" : "/dev/tty";
37
- const fd = fs.openSync(tty, "rs");
38
- process.stdout.write(message);
39
- const buf = Buffer.alloc(1);
40
- fs.readSync(fd, buf, 0, 1, null);
41
- fs.closeSync(fd);
42
- console.log();
43
- resolve();
44
- return;
45
- } catch (e) {
46
- console.log(
47
- message +
48
- " (Auto-continuing due to non-interactive terminal environment)",
49
- );
50
- resolve();
51
- return;
52
- }
53
- }
54
-
55
- // For standard TTY terminals
56
- const rl = readline.createInterface({
57
- input: process.stdin,
58
- output: process.stdout,
59
- });
60
- rl.question(message, () => {
61
- rl.close();
62
- resolve();
63
- });
64
- });
65
- }
3
+ import { runCliApp } from "../src/cli-utils.js";
66
4
 
67
5
  async function main() {
68
- let task = "";
69
- let optionsStr = "{}";
70
-
71
- // 1. Read TASK and OPTIONS
72
- if (hasStdinData()) {
73
- try {
74
- task = fs.readFileSync(0, "utf-8").trim();
75
- } catch (e) {
76
- console.error("Error reading from STDIN:", e);
77
- }
78
- if (process.argv[2]) optionsStr = process.argv[2];
79
- } else {
80
- if (process.argv[2]) task = process.argv[2];
81
- if (process.argv[3]) optionsStr = process.argv[3];
82
- }
83
-
84
- if (!task) {
85
- console.error("Error: Task parameter is required.");
86
- console.error(
87
- 'Usage: scad-web "<description of the web app to generate>" [options_json]',
88
- );
89
- console.error(' or: echo "<description>" | scad-web [options_json]');
90
- console.error("");
91
- console.error("Example with JSON options:");
92
- console.error(
93
- ' scad-web "3D Car Configurator Web App" \'{"animation": false}\'',
94
- );
95
- process.exit(1);
96
- }
97
-
98
- // 2. Parse Options JSON
99
- let options = {};
100
- if (optionsStr) {
101
- try {
102
- options = JSON.parse(optionsStr);
103
- } catch (e) {
104
- console.error(`Invalid JSON options: ${optionsStr}`);
105
- process.exit(1);
106
- }
107
- }
108
-
109
- // Disable the modelName instructions specifically for this wrapper context
110
- options.modelName = false;
111
-
112
- // 3. Dynamically import and generate prompt rules from src/prompt.js
113
- let generatePrompt;
114
- try {
115
- const promptJsPath = path.join(DIR, "src", "prompt.js");
116
- const promptModuleUrl = url.pathToFileURL(promptJsPath).href;
117
- const m = await import(promptModuleUrl);
118
- generatePrompt = m.generatePrompt;
119
- } catch (e) {
120
- console.error(`Error loading ${path.join("src", "prompt.js")}:`, e);
121
- process.exit(1);
122
- }
123
-
124
- let promptRules = "";
125
- try {
126
- promptRules = generatePrompt("the 3D assets for the web app", options);
127
- } catch (e) {
128
- console.error("Error generating prompt rules from prompt.js:");
129
- console.error(e);
130
- process.exit(1);
131
- }
132
-
133
- // 4. Construct the Main Web Prompt System Text (System Instructions)
134
- const systemPrompt = `You are an expert Web 3D developer and procedural 3D technical artist.
6
+ await runCliApp({
7
+ projectType: "web",
8
+ buildSystemPrompt: (
9
+ promptRules,
10
+ ) => `You are an expert Web 3D developer and procedural 3D technical artist.
135
11
 
136
12
  What to generate:
137
13
  1. 3D Web Assets (.scad):
138
- - Generate procedural 3D models for the web app using OpenSCAD.
14
+ - Generate procedural 3D models for the game using OpenSCAD.
139
15
  - CRITICAL: The SCAD to glTF converter automatically converts OpenSCAD's Z-up coordinate system to the standard glTF Y-up coordinate system. Design your models naturally in OpenSCAD.
140
16
  - CRITICAL: You must use the custom OpenSCAD glTF extensions for PBR materials (e.g., \`roughness\`, \`metalness\`, \`emissive\`) and Hierarchical Node Animations (\`armature()\`, \`bone()\`). The rules and syntax for these features are provided below:
141
17
 
@@ -145,14 +21,14 @@ ${promptRules}
145
21
 
146
22
  2. Vite Web Project Files (npm based):
147
23
  - Create the necessary files for a modern web application (e.g., \`package.json\`, \`index.html\`, \`main.js\`).
148
- - You can use ANY web 3D library with glTF support (e.g., Three.js, Babylon.js, @google/model-viewer, PlayCanvas, A-Frame, etc.) that fits the app's requirements.
24
+ - You can use ANY web 3D library with glTF support (e.g., Three.js, Babylon.js, @google/model-viewer, PlayCanvas, A-Frame, etc.) that fits the game's requirements.
149
25
  - In \`package.json\`, you MUST include the scad to gltf converter tool as a dev dependency:
150
26
  \`"scad-gltf": "^0.1.0"\`
151
27
  - In \`package.json\`, add npm scripts to automatically compile the \`.scad\` files into \`.glb\` format inside the \`public/\` folder before Vite runs its dev or build steps.
152
28
  For example:
153
29
  \`"predev": "scad-convert ./scad ./public/models --cache"\`
154
30
  \`"prebuild": "scad-convert ./scad ./public/models --cache"\`
155
- - Write the core application logic to load and display the converted \`.glb\` files interactively.
31
+ - Write the core game logic to load and display the converted \`.glb\` files interactively.
156
32
 
157
33
  3. Delivery Format (Single Node.js Script):
158
34
  - Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
@@ -161,39 +37,11 @@ ${promptRules}
161
37
  - The script must embed and write:
162
38
  - Your generated \`.scad\` 3D assets.
163
39
  - Your generated Vite web project files.
164
- - Ensure all string file contents inside the Node.js script are properly escaped.`;
165
-
166
- // 5. Format the input request output
167
- const inputRequestOutput = `Design and implement a web-based 3D glTF app using Vite for the following concept: "${task}"`;
168
-
169
- // 6. Write to System Clipboard (Part 1: System Instructions)
170
- try {
171
- await writeToClipboard(systemPrompt);
172
- console.log("✔️ System instructions have been copied to the clipboard.");
173
- } catch (err) {
174
- console.error(
175
- "Error: Failed to copy system instructions to the clipboard.",
176
- );
177
- console.error(err.message);
178
- process.exit(1);
179
- }
180
-
181
- // 7. Await user confirmation
182
- await waitForEnter(
183
- "Please paste the system instructions into your LLM, then press ENTER to copy your input request...",
184
- );
185
-
186
- // 8. Write to System Clipboard (Part 2: Input Request)
187
- try {
188
- await writeToClipboard(inputRequestOutput);
189
- console.log(
190
- "✔️ Input request has been copied to the clipboard. You can now paste it into your LLM.",
191
- );
192
- } catch (err) {
193
- console.error("Error: Failed to copy input request to the clipboard.");
194
- console.error(err.message);
195
- process.exit(1);
196
- }
40
+ - Ensure all string file contents inside the Node.js script are properly escaped.`,
41
+ buildInputRequest: (task) =>
42
+ `Design and implement a web-based 3D glTF game using Vite for the following concept: "${task}"`,
43
+ allowedTools: ["render_scad_model"],
44
+ });
197
45
  }
198
46
 
199
47
  // Execute and handle unhandled runtime errors
@@ -1 +1 @@
1
- body{background-color:#333;width:100vw;height:100vh;margin:0;font-family:sans-serif;display:flex;overflow:hidden}#viewer-container{background:#1e1e1e;flex-grow:1;width:100%;height:100vh;position:relative;overflow:hidden}#viewer{width:100%;height:100%}#viewer-controls{z-index:10;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#0009;border-radius:6px;align-items:center;gap:8px;padding:6px;display:flex;position:absolute;top:15px;right:40px}#viewer-controls label{color:#fff;cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:4px;margin:0;font-size:13px;display:flex}#fullscreen-btn,#screenshot-btn{color:#fff;cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;height:auto;font-size:16px;display:flex}#fullscreen-btn{padding:0 4px}#screenshot-btn{padding:0 4px 6px}#fullscreen-btn:hover,#screenshot-btn:hover{color:#0af;background:0 0}#save-ui{z-index:10;background:#0009;border-radius:6px;gap:8px;padding:6px;display:flex;position:absolute;top:15px;left:15px}#filename-input{color:#fff;background:#333;border:1px solid #555;border-radius:4px;outline:none;width:160px;padding:4px 8px;font-family:monospace}#filename-input:focus{border-color:#0af}#save-btn{color:#fff;cursor:pointer;background:#0af;border:none;border-radius:4px;padding:4px 12px;font-weight:700}#save-btn:hover:not(:disabled){background:#08c}#save-btn:disabled{opacity:.7;cursor:not-allowed}#open-editor-btn{color:#fff;cursor:pointer;background:#6f42c1;border:none;border-radius:4px;padding:4px 12px;font-weight:700}#open-editor-btn:hover:not(:disabled){background:#5a32a3}#open-editor-btn:disabled{opacity:.7;cursor:not-allowed}
1
+ body{background-color:#333;width:100vw;height:100vh;margin:0;font-family:sans-serif;display:flex;overflow:hidden}#viewer-container{background:#1e1e1e;flex-grow:1;width:100%;height:100vh;position:relative;overflow:hidden}#viewer{width:100%;height:100%}#viewer-controls{z-index:10;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#0009;border-radius:6px;align-items:center;gap:8px;padding:6px;display:flex;position:absolute;top:15px;right:40px}#viewer-controls label{color:#fff;cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:4px;margin:0;font-size:13px;display:flex}#fullscreen-btn,#screenshot-btn{color:#fff;cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;height:auto;font-size:16px;display:flex}#fullscreen-btn{padding:0 4px}#screenshot-btn{padding:0 4px 6px}#fullscreen-btn:hover,#screenshot-btn:hover{color:#0af;background:0 0}#save-ui{z-index:10;background:#0009;border-radius:6px;gap:8px;padding:6px;display:flex;position:absolute;top:15px;left:15px}#filename-input{color:#fff;background:#333;border:1px solid #555;border-radius:4px;outline:none;width:160px;padding:4px 8px;font-family:monospace}#filename-input:focus{border-color:#0af}#save-btn{color:#fff;cursor:pointer;background:#0af;border:none;border-radius:4px;padding:4px 12px;font-weight:700}#save-btn:hover:not(:disabled){background:#08c}#save-btn:disabled{opacity:.7;cursor:not-allowed}#open-editor-btn{color:#fff;cursor:pointer;background:#6f42c1;border:none;border-radius:4px;padding:4px 12px;font-weight:700}#open-editor-btn:hover:not(:disabled){background:#5a32a3}#open-editor-btn:disabled{opacity:.7;cursor:not-allowed}#anim-controls{z-index:10;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#0009;border-radius:8px;align-items:center;gap:10px;width:80%;max-width:400px;padding:8px 16px;display:flex;position:absolute;bottom:20px;left:50%;transform:translate(-50%)}#anim-controls select{color:#fff;background:#333;border:1px solid #555;border-radius:4px;outline:none;padding:4px;font-size:13px}#anim-controls button{color:#fff;cursor:pointer;white-space:nowrap;background:#444;border:none;border-radius:4px;padding:4px 12px;font-size:13px}#anim-controls button:hover{background:#555}#anim-slider{cursor:pointer;-webkit-appearance:none;background:0 0;flex-grow:1;margin:0}#anim-slider:focus{outline:none}#anim-slider::-webkit-slider-runnable-track{cursor:pointer;background:#444;border-radius:3px;width:100%;height:6px}#anim-slider::-webkit-slider-thumb{cursor:pointer;-webkit-appearance:none;background:#0af;border-radius:50%;width:14px;height:14px;margin-top:-4px}
@@ -0,0 +1 @@
1
+ import{A as e,B as t,D as n,E as r,H as i,O as a,R as o,W as s,a as ee,d as c,et as te,f as ne,ht as re,i as ie,l,lt as u,mt as ae,n as oe,o as se,p as d,r as f,s as p,t as ce,u as le,ut as m,w as ue,y as de}from"./OutputPass-B6MrFnGE.js";var h=document.getElementById(`viewer-container`),fe=document.getElementById(`viewer`),g=document.getElementById(`filename-input`),_=document.getElementById(`save-btn`),v=document.getElementById(`open-editor-btn`),y=document.getElementById(`show-grid-cb`),b=document.getElementById(`wireframe-cb`),x=document.getElementById(`fullscreen-btn`),S=document.getElementById(`screenshot-btn`),C=document.getElementById(`anim-controls`),w=document.getElementById(`anim-play-btn`),T=document.getElementById(`anim-select`),E=document.getElementById(`anim-slider`),D=null,O=null,k=!0,A=!1,j=[],M=``,N=!1,P=null,F=null,I=!1,L=new te;L.background=new de(2236962);var R=new i(60,window.innerWidth/window.innerHeight,.1,2e3);R.position.set(50,50,-50);var z=new le({antialias:!0});z.setSize(window.innerWidth,window.innerHeight),z.setPixelRatio(Math.min(window.devicePixelRatio,2)),z.shadowMap.enabled=!0,z.shadowMap.type=2,z.toneMapping=4,z.toneMappingExposure=1,fe.appendChild(z.domElement);var B=new ie(z),pe=new f(L,R);B.addPass(pe);var me=new oe(new u(window.innerWidth,window.innerHeight),.1,.1,2);B.addPass(me);var V=new ce;B.addPass(V);var H=new p(R,z.domElement);H.enableDamping=!0,H.dampingFactor=.1,L.environment=new l(z).fromScene(new ee,.04).texture,L.environmentIntensity=.8;var U=new a;L.add(U);var he=new s(2e3,2e3),ge=new t({color:2236962,roughness:.8,metalness:.1}),W=new o(he,ge);W.rotation.x=-Math.PI/2,W.receiveShadow=!0,L.add(W);var G=new n(2e3,100,5592405,3355443);G.material.transparent=!0,G.material.opacity=.5,L.add(G);var K=new ne(100);L.add(K);var q=new ue(16777215,1.5);q.castShadow=!0,q.shadow.mapSize.width=2048,q.shadow.mapSize.height=2048,q.shadow.bias=-5e-4,U.add(q),U.add(q.target);var J=new e(16777215,4473924,.6);J.position.set(0,20,0),U.add(J),b&&b.addEventListener(`change`,()=>{let e=b.checked;D&&D.traverse(t=>{t.isMesh&&t.material&&(Array.isArray(t.material)?t.material.forEach(t=>{t.wireframe=e}):t.material.wireframe=e)})}),x&&h&&(x.addEventListener(`click`,()=>{document.fullscreenElement?document.exitFullscreen().catch(e=>{console.warn(`Error attempting to exit fullscreen: ${e.message}`)}):h.requestFullscreen().catch(e=>{console.warn(`Error attempting to enable fullscreen: ${e.message}`)})}),document.addEventListener(`fullscreenchange`,()=>{setTimeout(()=>window.dispatchEvent(new Event(`resize`)),100)})),S&&S.addEventListener(`click`,()=>{I=!0;let e=S.innerText;S.innerText=`✅`,setTimeout(()=>{S.innerText=e},1e3)});function Y(){O&&(k=!1,O.paused=!0,w&&(w.innerText=`▶ Play`))}function X(e){if(!F||!j[e])return;O&&O.stop();let t=j[e];O=F.clipAction(t),O.play(),O.time=0,F.update(0),k=!0,O.paused=!1,w&&(w.innerText=`⏸ Pause`),E&&(E.value=0)}T&&T.addEventListener(`change`,e=>{X(parseInt(e.target.value))}),w&&w.addEventListener(`click`,()=>{O&&(k=!k,O.paused=!k,w.innerText=k?`⏸ Pause`:`▶ Play`)}),E&&(E.addEventListener(`mousedown`,()=>{A=!0,Y()}),E.addEventListener(`mouseup`,()=>{A=!1}),E.addEventListener(`touchstart`,()=>{A=!0,Y()},{passive:!0}),E.addEventListener(`touchend`,()=>{A=!1},{passive:!0}),E.addEventListener(`input`,e=>{if(Y(),O){let t=O.getClip().duration;O.time=parseFloat(e.target.value)*t,F&&F.update(0)}}));var Z=performance.now();function Q(){requestAnimationFrame(Q);let e=performance.now(),t=(e-Z)/1e3;if(Z=e,F&&(F.update(t),O&&k&&!A)){let e=O.getClip().duration;if(e>0){let t=O.time%e;t<0&&(t+=e),E&&(E.value=t/e)}}H.update();let n=!y||y.checked;if(G.visible=n,K.visible=n,B.render(),I){I=!1;let e=z.domElement.toDataURL(`image/png`);window.parent.postMessage({type:`ADD_IMAGE_TO_CHAT`,dataUrl:e},`*`)}}Q(),window.addEventListener(`resize`,()=>{let e=h?h.clientWidth:window.innerWidth,t=h?h.clientHeight:window.innerHeight;R.aspect=e/t,R.updateProjectionMatrix(),z.setSize(e,t),B.setSize(e,t)});function _e(e,t){if(!e)return new d;e.updateMatrixWorld(!0);let n=new d().setFromObject(e);if(!t||t.length===0)return n;let r=new Map;e.traverse(e=>{r.set(e,{position:e.position.clone(),quaternion:e.quaternion.clone(),scale:e.scale.clone()})});let i=new c(e);for(let r of t){let t=i.clipAction(r);t.play();let a=new Set([0,r.duration]);if(r.tracks){for(let e of r.tracks)if(e.times)for(let t=0;t<e.times.length;t++)a.add(e.times[t])}if(r.duration>0)for(let e=0;e<=20;e++)a.add(e/20*r.duration);let o=Array.from(a).sort((e,t)=>e-t);if(o.length>60){let e=[],t=(o.length-1)/59;for(let n=0;n<60;n++)e.push(o[Math.round(n*t)]);o=e}for(let r of o)t.time=r,i.update(0),e.updateMatrixWorld(!0),n.expandByObject(e);t.stop()}return i.stopAllAction(),i.uncacheRoot(e),r.forEach((e,t)=>{t.position.copy(e.position),t.quaternion.copy(e.quaternion),t.scale.copy(e.scale)}),e.updateMatrixWorld(!0),n}function ve(e){return new Promise((t,n)=>{D&&=(F&&=(F.stopAllAction(),F.uncacheRoot(F.getRoot()),null),O=null,j=[],L.remove(D),D.traverse(e=>{e.isMesh&&(e.geometry.dispose(),Array.isArray(e.material)?e.material.forEach(e=>e.dispose()):e.material.dispose())}),null);let r=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);new se().parse(r,``,e=>{D=e.scene,j=e.animations||[];let n=b?b.checked:!1;j.length>0?(F=new c(D),C?(C.style.display=`flex`,T.innerHTML=``,j.forEach((e,t)=>{let n=document.createElement(`option`);n.value=t,n.innerText=e.name||`Animation ${t+1}`,T.appendChild(n)}),X(0)):(j.forEach(e=>{let t=F.clipAction(e);t.play(),t.time=0}),F.update(0))):(C&&(C.style.display=`none`),O=null),D.traverse(e=>{e.isMesh&&(e.castShadow=!0,e.receiveShadow=!0,e.frustumCulled=!1,e.material&&(Array.isArray(e.material)?e.material.forEach(e=>{e.wireframe=n}):e.material.wireframe=n))}),L.add(D),ye(),t()},e=>{console.error(e),n(e)})})}function ye(){if(!D)return;let e=_e(D,j);if(e.isEmpty())return;let t=e.getCenter(new m),n=e.getSize(new m),i=Math.max(n.x,n.y,n.z)||10;W.position.y=e.min.y-.01,G.position.y=W.position.y+.001,K.position.y=W.position.y+.002;let a=R.fov*(Math.PI/180),o=i/(2*Math.tan(a/2));R.aspect<1&&(o/=R.aspect),o*=1.5,R.near=Math.max(.01,i*.01),R.far=Math.max(2e3,o*10),R.updateProjectionMatrix(),H.maxDistance=R.far,R.position.set(t.x+o*.8,t.y+o*.8,t.z-o*.8),R.lookAt(t),H.target.copy(t),H.update(),q.position.set(t.x+i,t.y+i*1.5,t.z-i),q.target.position.copy(t),q.target.updateMatrixWorld();let s=i*1.5;q.shadow.camera.left=-s,q.shadow.camera.right=s,q.shadow.camera.top=s,q.shadow.camera.bottom=-s,q.shadow.camera.near=.1,q.shadow.camera.far=i*5,q.shadow.camera.updateProjectionMatrix(),L.fog=new r(2236962,o*1.5,o*5)}async function be(e){let t={},n=`http://localhost:3000`,r=[];try{let e=await fetch(`${n}/api/scads`);e.ok&&(r=(await e.json()).files||[])}catch{return t}let i=new Set;async function a(e){let o=/(?:include|use)\s*([<"])([^>"]+)([>"])/g,s;for(;(s=o.exec(e))!==null;){let e=s[2],o=e.split(/[/\\]/).pop();if(!i.has(e)&&r.includes(o)){i.add(e);try{let r=await fetch(`${n}/api/scads/${encodeURIComponent(o)}`);if(r.ok){let n=await r.json();t[e]=n.content,await a(n.content)}}catch{console.warn(`Failed to load dependency: ${e}`)}}}}return await a(e),t}async function $(e){if(N){P=e;return}N=!0;try{let t=await be(e);await ve(await ae(e,{wasmUrl:re,additionalFiles:t}))}catch(e){console.error(e)}finally{if(N=!1,P!==null){let e=P;P=null,$(e)}}}window.addEventListener(`message`,async e=>{if(e.data.type===`RENDER_SCAD`){M=e.data.code;let t=M.match(/\/\*\s*Model Name:\s*(.*?)\s*\*\//i);if(t&&t[1]){let e=t[1].trim().replace(/\s+/g,`_`).replace(/[^a-zA-Z0-9_-]/g,``);e&&(g.value=e)}await $(e.data.code)}}),_.addEventListener(`click`,async()=>{let e=g.value.trim();if(!e){alert(`Please enter a filename.`),g.focus();return}let t=`http://localhost:3000`;try{_.innerText=`Checking...`,_.disabled=!0;let n=await fetch(`${t}/api/scads`);if(!n.ok)throw Error(`Could not reach scad-serve.`);let r=await n.json(),i=e.toLowerCase().endsWith(`.scad`)?e:`${e}.scad`;if(r.files&&r.files.includes(i)&&!confirm(`File "${i}" already exists. Overwrite?`)){_.innerText=`Save`,_.disabled=!1;return}_.innerText=`Saving...`;let a=await fetch(`${t}/api/scads`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({filename:e,content:M||``})});if(!a.ok){let e=await a.json();throw Error(e.error||`Save failed.`)}_.innerText=`✅ Saved!`,setTimeout(()=>{_.innerText=`Save`,_.disabled=!1},2e3)}catch(e){console.error(e),alert(`Error saving file: `+e.message),_.innerText=`Save`,_.disabled=!1}});async function xe(e){try{if(typeof CompressionStream<`u`){let t=new Blob([e]).stream().pipeThrough(new CompressionStream(`deflate-raw`)),n=await new Response(t).arrayBuffer(),r=new Uint8Array(n),i=``;for(let e=0;e<r.length;e++)i+=String.fromCharCode(r[e]);return`c`+btoa(i).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}}catch(e){console.warn(`CompressionStream failed, falling back`,e)}return`u`+btoa(unescape(encodeURIComponent(e))).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}v.addEventListener(`click`,async()=>{if(!M){alert(`No model loaded to open.`);return}try{v.disabled=!0,v.innerText=`Preparing...`;let e=`https://iliagrigorevdev.github.io/scad-gltf/#${await xe(M)}`;window.open(e,`_blank`),window.parent.postMessage({type:`CLOSE_PREVIEW`},`*`),v.innerText=`✅ Opened!`,setTimeout(()=>{v.innerText=`Edit`,v.disabled=!1},2e3)}catch(e){console.error(`Error creating editor link`,e),alert(`Error creating editor link: `+e.message),v.innerText=`Edit`,v.disabled=!1}});
@@ -253,3 +253,51 @@
253
253
  .scad-primary-btn:hover {
254
254
  background: #7a1fd1;
255
255
  }
256
+
257
+ .scad-prompt-backend-ui {
258
+ display: block;
259
+ background: #2a2a2a;
260
+ padding: 10px;
261
+ border-radius: 6px;
262
+ border: 1px solid #444;
263
+ margin-bottom: 5px;
264
+ }
265
+
266
+ .scad-prompt-backend-ui-header {
267
+ display: flex;
268
+ justify-content: space-between;
269
+ align-items: center;
270
+ margin-bottom: 5px;
271
+ }
272
+
273
+ .scad-prompt-backend-ui-title {
274
+ font-size: 12px;
275
+ color: #ccc;
276
+ }
277
+
278
+ .scad-prompt-backend-connect-btn {
279
+ background: #444;
280
+ color: white;
281
+ border: none;
282
+ padding: 4px 10px;
283
+ border-radius: 4px;
284
+ cursor: pointer;
285
+ font-size: 12px;
286
+ transition: background 0.2s;
287
+ }
288
+
289
+ .scad-prompt-backend-connect-btn:hover {
290
+ background: #555;
291
+ }
292
+
293
+ #scad-prompt-model-select {
294
+ width: 100%;
295
+ background: #1e1e1e;
296
+ color: #fff;
297
+ border: 1px solid #444;
298
+ border-radius: 4px;
299
+ padding: 6px;
300
+ outline: none;
301
+ font-family: inherit;
302
+ display: none;
303
+ }
@@ -5,7 +5,7 @@ import{i as e,n as t,r as n,t as r}from"./assets/prompt-ui-B9QCNLfJ.js";console.
5
5
  <line x1="12" y1="22.08" x2="12" y2="12"></line>
6
6
  </svg>
7
7
  <span>Preview 3D</span>
8
- `,t.onpointerdown=e=>e.stopPropagation(),t.onmousedown=e=>e.stopPropagation(),t.onclick=t=>{t.preventDefault(),t.stopPropagation();let n=e.querySelector(`code`);n&&s(n.innerText)};let n=e.querySelector(`button[aria-label*="copy" i], button[aria-label*="download" i], button[aria-label*="collapse" i], button[mattooltip*="copy" i], button[mattooltip*="download" i], button[mattooltip*="collapse" i], button[title*="Copy" i], button[title*="Download" i]`),r=n?.parentElement||e.querySelector(`.actions`)||e.querySelector(`.header-actions`)||e.querySelector(`.action-buttons`)||e.querySelector(`.buttons`)||e.querySelector(`.header`)||e.querySelector(`.code-header`);if(r)n?r.insertBefore(t,n):r.prepend(t);else{let n=e.querySelector(`pre`);n?n.parentNode.insertBefore(t,n):e.appendChild(t)}})}).observe(document.body,{childList:!0,subtree:!0});function a(){if(document.getElementById(`scad-prompt-btn`))return;let e=document.createElement(`button`);e.id=`scad-prompt-btn`,e.innerText=`✨ SCAD`,e.className=`scad-prompt-btn`,e.onclick=e=>{e.preventDefault(),e.stopPropagation();let t=document.getElementById(`scad-prompt-modal`);if(t){let e=document.getElementById(`scad-prompt-desc`);e&&(e.value=``),t.style.display=`flex`,e&&e.focus()}},document.body.appendChild(e)}function o(){if(document.getElementById(`scad-prompt-modal`))return;let i=document.createElement(`div`);i.id=`scad-prompt-modal`,i.className=`scad-modal-overlay`,i.style.display=`none`,i.innerHTML=`
8
+ `,t.onpointerdown=e=>e.stopPropagation(),t.onmousedown=e=>e.stopPropagation(),t.onclick=t=>{t.preventDefault(),t.stopPropagation();let n=e.querySelector(`code`);n&&s(n.innerText)};let n=e.querySelector(`button[aria-label*="copy" i], button[aria-label*="download" i], button[aria-label*="collapse" i], button[mattooltip*="copy" i], button[mattooltip*="download" i], button[mattooltip*="collapse" i], button[title*="Copy" i], button[title*="Download" i]`),r=n?.parentElement||e.querySelector(`.actions`)||e.querySelector(`.header-actions`)||e.querySelector(`.action-buttons`)||e.querySelector(`.buttons`)||e.querySelector(`.header`)||e.querySelector(`.code-header`);if(r)n?r.insertBefore(t,n):r.prepend(t);else{let n=e.querySelector(`pre`);n?n.parentNode.insertBefore(t,n):e.appendChild(t)}})}).observe(document.body,{childList:!0,subtree:!0});function a(){if(document.getElementById(`scad-prompt-btn`))return;let e=document.createElement(`button`);e.id=`scad-prompt-btn`,e.innerText=`✨ SCAD`,e.className=`scad-prompt-btn`,e.onclick=async e=>{e.preventDefault(),e.stopPropagation();let t=document.getElementById(`scad-prompt-modal`);if(t){let e=document.getElementById(`scad-prompt-desc`);e&&(e.value=``),t.style.display=`flex`,e&&e.focus()}},document.body.appendChild(e)}function o(){if(document.getElementById(`scad-prompt-modal`))return;let i=document.createElement(`div`);i.id=`scad-prompt-modal`,i.className=`scad-modal-overlay`,i.style.display=`none`,i.innerHTML=`
9
9
  <div class="scad-modal-content">
10
10
  <div class="scad-modal-header">
11
11
  <h3>✨ SCAD Prompt Generator</h3>
@@ -13,6 +13,16 @@ import{i as e,n as t,r as n,t as r}from"./assets/prompt-ui-B9QCNLfJ.js";console.
13
13
  </div>
14
14
  <p class="scad-help-text">Describe the object you want to generate. The copied prompt will include advanced PBR and Animation rules based on your selection.</p>
15
15
 
16
+ <div id="scad-prompt-backend-ui" class="scad-prompt-backend-ui">
17
+ <div class="scad-prompt-backend-ui-header">
18
+ <div class="scad-prompt-backend-ui-title">Refine Local Model:</div>
19
+ <button id="scad-prompt-backend-connect" class="scad-prompt-backend-connect-btn">Connect</button>
20
+ </div>
21
+ <select id="scad-prompt-model-select">
22
+ <option value="">-- Select a local model --</option>
23
+ </select>
24
+ </div>
25
+
16
26
  <div id="scad-prompt-ui-container"></div>
17
27
 
18
28
  <textarea id="scad-prompt-desc" rows="3" placeholder="e.g. A shiny gold ring with an embedded red gem"></textarea>
@@ -21,4 +31,11 @@ import{i as e,n as t,r as n,t as r}from"./assets/prompt-ui-B9QCNLfJ.js";console.
21
31
  <button id="scad-prompt-submit" class="scad-primary-btn">📋 Generate & Paste</button>
22
32
  </div>
23
33
  </div>
24
- `,document.body.appendChild(i);let a=document.getElementById(`scad-prompt-ui-container`);a.innerHTML=r,n(a,`scad_prompt_settings`),document.getElementById(`scad-prompt-close`).onclick=()=>i.style.display=`none`,i.onclick=e=>{e.target===i&&(i.style.display=`none`)},document.getElementById(`scad-prompt-submit`).onclick=async()=>{let n=document.getElementById(`scad-prompt-desc`).value.trim();if(!n){alert(`Please enter a description!`);return}let r=t(a);try{let t=e(n,r),a=document.querySelector(`ms-prompt-box textarea[formcontrolname="promptText"]`)||document.querySelector(`ms-prompt-box textarea`)||document.querySelector(`textarea`);a&&(a.focus(),document.execCommand(`insertText`,!1,t)||(Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,`value`).set.call(a,t),a.dispatchEvent(new Event(`input`,{bubbles:!0})),a.dispatchEvent(new Event(`change`,{bubbles:!0})))),await navigator.clipboard.writeText(t),i.style.display=`none`;let o=document.getElementById(`scad-prompt-btn`),s=o.innerText;o.innerText=`✅ Prompt Pasted!`,setTimeout(()=>{o.innerText=s},2e3)}catch(e){console.error(`Error generating/copying prompt:`,e),alert(`Error: `+e.message)}}}function s(e){let t=document.getElementById(`scad-preview-iframe`);if(t){t.querySelector(`iframe`).contentWindow.postMessage({type:`RENDER_SCAD`,code:e},`*`);return}let n=document.createElement(`div`);n.id=`scad-preview-iframe`;let r=document.createElement(`button`);r.innerText=`X`,r.className=`scad-close-btn`,r.onclick=()=>n.remove();let i=document.createElement(`iframe`);i.src=chrome.runtime.getURL(`preview.html`),i.allow=`fullscreen`,i.allowFullscreen=!0,i.setAttribute(`allowfullscreen`,`true`),i.setAttribute(`allow`,`fullscreen`),n.appendChild(r),n.appendChild(i),document.body.appendChild(n),i.onload=()=>{i.contentWindow.postMessage({type:`RENDER_SCAD`,code:e},`*`)}}
34
+ `,document.body.appendChild(i);let a=document.getElementById(`scad-prompt-ui-container`);a.innerHTML=r,n(a,`scad_prompt_settings`),document.getElementById(`scad-prompt-close`).onclick=()=>i.style.display=`none`,i.onclick=e=>{e.target===i&&(i.style.display=`none`)};let o=document.getElementById(`scad-prompt-backend-connect`),s=document.getElementById(`scad-prompt-model-select`);o&&s&&(o.onclick=async()=>{try{o.innerText=`Connecting...`;let e=await fetch(`http://localhost:3000/api/scads`);if(e.ok){let t=await e.json();t.files&&t.files.length>0?(s.innerHTML=`<option value="">-- Select a local model --</option>`,t.files.forEach(e=>{let t=document.createElement(`option`);t.value=e,t.innerText=e,s.appendChild(t)}),s.style.display=`block`,o.style.display=`none`):(o.innerText=`No Models`,setTimeout(()=>{o.innerText=`Connect`},2e3))}else o.innerText=`Failed`,setTimeout(()=>{o.innerText=`Connect`},2e3)}catch{o.innerText=`Offline`,setTimeout(()=>{o.innerText=`Connect`},2e3)}}),document.getElementById(`scad-prompt-submit`).onclick=async()=>{let n=document.getElementById(`scad-prompt-desc`).value.trim(),r=s?s.value:``;if(!n&&!r){alert(`Please enter a description or select a model!`);return}let o=t(a),c=``;try{if(n&&(c=e(n,o)),r){let e=await fetch(`http://localhost:3000/api/scads/`+encodeURIComponent(r));if(e.ok){let t="```openscad\n"+(await e.json()).content+`
35
+ \`\`\`
36
+
37
+ `;c?c+=`
38
+
39
+ Reference code:
40
+ `+t:c=t}else throw Error(`Failed to load local model`)}let t=document.querySelector(`ms-prompt-box textarea[formcontrolname="promptText"]`)||document.querySelector(`ms-prompt-box textarea`)||document.querySelector(`textarea`);if(t&&(t.focus(),!document.execCommand(`insertText`,!1,c))){let e=t.value,n=e?e+`
41
+ `+c:c;Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,`value`).set.call(t,n),t.dispatchEvent(new Event(`input`,{bubbles:!0})),t.dispatchEvent(new Event(`change`,{bubbles:!0}))}await navigator.clipboard.writeText(c),i.style.display=`none`,s&&(s.value=``),t&&t.focus();let a=document.getElementById(`scad-prompt-btn`),l=a.innerText;a.innerText=`✅ Prompt Pasted!`,setTimeout(()=>{a.innerText=l},2e3)}catch(e){console.error(`Error generating/copying prompt:`,e),alert(`Error: `+e.message)}}}function s(e){let t=document.getElementById(`scad-preview-iframe`);if(t){t.querySelector(`iframe`).contentWindow.postMessage({type:`RENDER_SCAD`,code:e},`*`);return}let n=document.createElement(`div`);n.id=`scad-preview-iframe`;let r=document.createElement(`button`);r.innerText=`X`,r.className=`scad-close-btn`,r.onclick=()=>n.remove();let i=document.createElement(`iframe`);i.src=chrome.runtime.getURL(`preview.html`),i.allow=`fullscreen`,i.allowFullscreen=!0,i.setAttribute(`allowfullscreen`,`true`),i.setAttribute(`allow`,`fullscreen`),n.appendChild(r),n.appendChild(i),document.body.appendChild(n),i.onload=()=>{i.contentWindow.postMessage({type:`RENDER_SCAD`,code:e},`*`)}}
@@ -2,9 +2,9 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <script type="module" crossorigin src="./assets/preview-CA_S_fWF.js"></script>
5
+ <script type="module" crossorigin src="./assets/preview-CCwT_Mrn.js"></script>
6
6
  <link rel="modulepreload" crossorigin href="./assets/OutputPass-B6MrFnGE.js">
7
- <link rel="stylesheet" crossorigin href="./assets/preview-fQfL_FJ-.css">
7
+ <link rel="stylesheet" crossorigin href="./assets/preview-A6M04CFK.css">
8
8
  <link rel="manifest" href="./manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="./registerSW.js"></script></head>
9
9
  <body>
10
10
  <div id="viewer-container">
@@ -21,6 +21,18 @@
21
21
  <button id="save-btn">Save</button>
22
22
  <button id="open-editor-btn">Edit</button>
23
23
  </div>
24
+ <div id="anim-controls" style="display: none">
25
+ <button id="anim-play-btn">⏸ Pause</button>
26
+ <select id="anim-select"></select>
27
+ <input
28
+ type="range"
29
+ id="anim-slider"
30
+ min="0"
31
+ max="1"
32
+ step="0.001"
33
+ value="0"
34
+ />
35
+ </div>
24
36
  <div id="viewer"></div>
25
37
  </div>
26
38
  </body>
package/editor/dist/sw.js CHANGED
@@ -1 +1 @@
1
- if(!self.define){let e,s={};const i=(i,r)=>(i=new URL(i+".js",r).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(r,n)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let l={};const o=e=>i(e,t),c={module:{uri:t},exports:l,require:o};s[t]=Promise.all(r.map(e=>c[e]||o(e))).then(e=>(n(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"402b66900e731ca748771b6fc5e7a068"},{url:"preview.html",revision:"2a6772049eec874e201965d98e5cf922"},{url:"index.html",revision:"e09c648877bb53e1fc65cfebb5a020af"},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"content.js",revision:"17cbb3fbf8b208d733facfa254d7dd7c"},{url:"content.css",revision:"8a04232febb2faba7b10edc60e2974dd"},{url:"content-loader.js",revision:"d94f1755552d49c1fe3a6da123e8a346"},{url:"aristea_wreck_puresky_2k.hdr",revision:"e764c66f871ab0987f3fac422edc841d"},{url:"assets/prompt-ui-B9QCNLfJ.js",revision:null},{url:"assets/preview-fQfL_FJ-.css",revision:null},{url:"assets/preview-CA_S_fWF.js",revision:null},{url:"assets/openscad-CdBCY4mx.wasm",revision:null},{url:"assets/index-CgVih1C4.js",revision:null},{url:"assets/index-B1KAb97r.css",revision:null},{url:"assets/OutputPass-B6MrFnGE.js",revision:null},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"manifest.webmanifest",revision:"705049c691faa532cc20c736d56d54df"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
1
+ if(!self.define){let e,s={};const i=(i,r)=>(i=new URL(i+".js",r).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(r,n)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let l={};const o=e=>i(e,t),c={module:{uri:t},exports:l,require:o};s[t]=Promise.all(r.map(e=>c[e]||o(e))).then(e=>(n(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"402b66900e731ca748771b6fc5e7a068"},{url:"preview.html",revision:"8a9ab9ab63e023395f78103b72aa9c56"},{url:"index.html",revision:"e09c648877bb53e1fc65cfebb5a020af"},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"content.js",revision:"a0ee0962c4c041063d42d1a33eb0b294"},{url:"content.css",revision:"4ea93a2d03164b49c80a48138305e564"},{url:"content-loader.js",revision:"d94f1755552d49c1fe3a6da123e8a346"},{url:"aristea_wreck_puresky_2k.hdr",revision:"e764c66f871ab0987f3fac422edc841d"},{url:"assets/prompt-ui-B9QCNLfJ.js",revision:null},{url:"assets/preview-CCwT_Mrn.js",revision:null},{url:"assets/preview-A6M04CFK.css",revision:null},{url:"assets/openscad-CdBCY4mx.wasm",revision:null},{url:"assets/index-CgVih1C4.js",revision:null},{url:"assets/index-B1KAb97r.css",revision:null},{url:"assets/OutputPass-B6MrFnGE.js",revision:null},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"manifest.webmanifest",revision:"705049c691faa532cc20c736d56d54df"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});