{"_id":"openscad-playground","_rev":"3-13625f3e1f9be6d6e7ad5038095b8e57","name":"openscad-playground","dist-tags":{"latest":"2.4.0"},"versions":{"2.0.1":{"name":"openscad-playground","version":"2.0.1","keywords":["openscad","cad","3d","modeling","wasm","react","component"],"author":"","license":"GPL-2.0-or-later","_id":"openscad-playground@2.0.1","maintainers":[{"name":"reisscashmore","email":"reiss.cashmore@outlook.com"}],"dist":{"shasum":"d9b6dbb758c3a5ef0c84bd13e5c2e1a1e1b99efd","tarball":"https://registry.npmjs.org/openscad-playground/-/openscad-playground-2.0.1.tgz","fileCount":135,"integrity":"sha512-o9brKqh0f03RKCWLGQ1sIzBu0aOBLrEeEjLt9cftFd+rMFX35mVpueGneynstNy+pQZbw3bziAy5UJadE+XTBg==","signatures":[{"sig":"MEYCIQDJgkRO41rG73QR20aG7NgFQpC4dk6jAt95X1sQTQE62wIhAIVg3xth2h58D1Oj/wXiB0Sy3wehAEzjm5avD228prJJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":52798364},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./styles":"./dist/openscad-playground.css","./worker":{"types":"./dist/openscad-worker.d.ts","import":"./dist/openscad-worker.js"}},"gitHead":"7cce432f0037487019cb3d0bdd8b0a2b9f3fc22c","scripts":{"dev":"vite","test":"vitest","build":"vite build && node scripts/post-build.js","preview":"vite preview","test:ui":"vitest --ui","build:libs":"tsx scripts/build-libs.ts","type-check":"tsc --noEmit","build:watch":"vite build --watch","prepublishOnly":"npm run build"},"_npmUser":{"name":"reisscashmore","email":"reiss.cashmore@outlook.com"},"repository":{"url":"","type":"git"},"_npmVersion":"10.9.2","description":"OpenSCAD in the browser - React component for 3D CAD modeling","directories":{},"_nodeVersion":"22.13.1","dependencies":{"uuid":"^11.0.3","uzip":"^0.20201231.0","debug":"^4.4.0","jszip":"^3.10.1","blurhash":"^2.0.5","chroma-js":"^3.1.2","primeflex":"^3.3.1","thumbhash":"^0.1.1","primeicons":"^7.0.0","primereact":"^10.8.5","monaco-editor":"^0.52.2","@gltf-transform/core":"^4.1.1","@monaco-editor/react":"^4.6.0","@monaco-editor/loader":"^1.4.0","@gltf-transform/extensions":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.2","vite":"^6.0.0","jsdom":"^27.0.1","vitest":"^2.0.0","@vitest/ui":"^2.0.0","typescript":"^5.9.2","@types/node":"^22.10.2","@types/uzip":"^0.20201231.2","@types/debug":"^4.1.12","@types/react":"^18.3.18","vite-plugin-dts":"^4.3.0","@types/chroma-js":"^2.4.5","@types/react-dom":"^18.3.5","@vitejs/plugin-react":"^4.3.4"},"peerDependencies":{"react":"^18.0.0","react-dom":"^18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/openscad-playground_2.0.1_1760778452664_0.01418627259319516","host":"s3://npm-registry-packages-npm-production"}},"2.3.0":{"name":"openscad-playground","version":"2.3.0","keywords":["openscad","cad","3d","modeling","wasm","react","component"],"author":"","license":"GPL-2.0-or-later","_id":"openscad-playground@2.3.0","maintainers":[{"name":"reisscashmore","email":"reiss.cashmore@outlook.com"}],"dist":{"shasum":"ffa53e803dd6cdaf158a2078d3a320b75d68dddd","tarball":"https://registry.npmjs.org/openscad-playground/-/openscad-playground-2.3.0.tgz","fileCount":142,"integrity":"sha512-mhS5pxHITV/TpPDVsJj5Cy1+3KLBOD5ZPnlBIkqKwOHdUCWZxY2KHFWqjt6e/9oItAiyIZZMoON4kcYJUzdyTg==","signatures":[{"sig":"MEYCIQDxEmF1uEFuzmfS22KPCBNMPTl6W2MdSso0xRggTXp1KQIhAKUbX0OP0JLQj5zPywUs2QTbbUgjRPU6mD2L4hJrHhZm","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":50994497},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./styles":"./dist/openscad-playground.css","./worker":{"types":"./dist/openscad-worker.d.ts","import":"./dist/openscad-worker.js"}},"gitHead":"36add867b35bf896b31643ce998cf329de1fcf84","scripts":{"dev":"vite","test":"vitest","build":"vite build && node scripts/post-build.js","preview":"vite preview","test:ui":"vitest --ui","build:demo":"vite build --mode demo","build:libs":"tsx scripts/build-libs.ts","type-check":"tsc --noEmit","build:watch":"vite build --watch","prepublishOnly":"npm run build"},"_npmUser":{"name":"reisscashmore","email":"reiss.cashmore@outlook.com"},"repository":{"url":"","type":"git"},"_npmVersion":"10.9.4","description":"OpenSCAD in the browser - React component for 3D CAD modeling","directories":{},"_nodeVersion":"20.19.5","dependencies":{"uuid":"^13.0.0","uzip":"^0.20201231.0","debug":"^4.4.1","jszip":"^3.10.1","blurhash":"^2.0.5","chroma-js":"^3.2.0","thumbhash":"^0.1.1","@mui/material":"^7.1.0","monaco-editor":"^0.52.2","@emotion/react":"^11.14.0","@emotion/styled":"^11.14.0","react-router-dom":"^7.6.1","@mui/icons-material":"^7.1.0","@gltf-transform/core":"^4.1.1","@monaco-editor/react":"^4.7.0","@monaco-editor/loader":"^1.7.0","@gltf-transform/extensions":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.21.0","vite":"^7.2.6","jsdom":"^27.2.0","react":"^18.3.1","vitest":"^4.0.14","react-dom":"^18.3.1","@vitest/ui":"^4.0.14","typescript":"^5.8.3","@types/node":"^24.10.1","@types/uzip":"^0.20201231.2","@types/debug":"^4.1.12","@types/react":"^18.3.18","vite-plugin-dts":"^4.5.4","@types/chroma-js":"^3.1.2","@types/react-dom":"^18.3.5","semantic-release":"^24.2.9","@vitejs/plugin-react":"^5.1.1","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^12.0.2","@semantic-release/github":"^11.0.6","@semantic-release/changelog":"^6.0.3","conventional-changelog-conventionalcommits":"^9.1.0"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/openscad-playground_2.3.0_1764685198103_0.29049237401439787","host":"s3://npm-registry-packages-npm-production"}},"2.4.0":{"name":"openscad-playground","version":"2.4.0","type":"module","description":"OpenSCAD in the browser - React component for 3D CAD modeling","keywords":["openscad","cad","3d","modeling","wasm","react","component"],"author":"","license":"GPL-2.0-or-later","repository":{"type":"git","url":""},"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./worker":{"types":"./dist/openscad-worker.d.ts","import":"./dist/openscad-worker.js"},"./styles":"./dist/openscad-playground.css"},"scripts":{"dev":"vite","build":"vite build && node scripts/post-build.js","build:demo":"vite build --mode demo","build:watch":"vite build --watch","build:libs":"tsx scripts/build-libs.ts","preview":"vite preview","test":"vitest","test:ui":"vitest --ui","type-check":"tsc --noEmit","prepublishOnly":"npm run build"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0"},"dependencies":{"@emotion/react":"^11.14.0","@emotion/styled":"^11.14.0","@gltf-transform/core":"^4.1.1","@gltf-transform/extensions":"^4.1.1","@monaco-editor/loader":"^1.7.0","@monaco-editor/react":"^4.7.0","@mui/icons-material":"^7.1.0","@mui/material":"^7.1.0","blurhash":"^2.0.5","chroma-js":"^3.2.0","debug":"^4.4.1","jszip":"^3.10.1","monaco-editor":"^0.52.2","react-router-dom":"^7.6.1","thumbhash":"^0.1.1","uuid":"^13.0.0","uzip":"^0.20201231.0"},"devDependencies":{"react":"^18.3.1","react-dom":"^18.3.1","@semantic-release/changelog":"^6.0.3","@semantic-release/git":"^10.0.1","@semantic-release/github":"^11.0.6","@semantic-release/npm":"^12.0.2","@types/chroma-js":"^3.1.2","@types/debug":"^4.1.12","@types/node":"^24.10.1","@types/react":"^18.3.18","@types/react-dom":"^18.3.5","@types/uzip":"^0.20201231.2","@vitejs/plugin-react":"^5.1.1","@vitest/ui":"^4.0.14","conventional-changelog-conventionalcommits":"^9.1.0","jsdom":"^27.2.0","semantic-release":"^24.2.9","tsx":"^4.21.0","typescript":"^5.8.3","vite":"^7.2.6","vite-plugin-dts":"^4.5.4","vitest":"^4.0.14"},"_id":"openscad-playground@2.4.0","gitHead":"3fba9bfc09b9fcc55386bad61b156eaaf8d64627","_nodeVersion":"20.19.6","_npmVersion":"10.9.4","dist":{"integrity":"sha512-y9xoInvFz7WhkA/3izy3C6MwCeQ7xPvxQNg42bhlYsz8GdUdGschpJ7DhAn0nwZuJeILECujS/z8kDT/IbI0kw==","shasum":"a9889fc27e2545b5baea4ed715413e38cd5981cc","tarball":"https://registry.npmjs.org/openscad-playground/-/openscad-playground-2.4.0.tgz","fileCount":142,"unpackedSize":50994497,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDfHse7HiYCL4LLgqs846c/4rYNeg4ZqGsHuhhwHld5MwIgLNQFzB/bIfIPXG/Ztj8smsfT+O9Z1AoAj92FBKZp2e4="}]},"_npmUser":{"name":"reisscashmore","email":"reiss.cashmore@outlook.com"},"directories":{},"maintainers":[{"name":"reisscashmore","email":"reiss.cashmore@outlook.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/openscad-playground_2.4.0_1765198662099_0.4057158424672096"},"_hasShrinkwrap":false}},"time":{"created":"2025-10-18T09:07:32.663Z","modified":"2025-12-08T12:57:42.943Z","2.0.1":"2025-10-18T09:07:33.100Z","2.3.0":"2025-12-02T14:19:58.491Z","2.4.0":"2025-12-08T12:57:42.712Z"},"license":"GPL-2.0-or-later","keywords":["openscad","cad","3d","modeling","wasm","react","component"],"repository":{"type":"git","url":""},"description":"OpenSCAD in the browser - React component for 3D CAD modeling","maintainers":[{"name":"reisscashmore","email":"reiss.cashmore@outlook.com"}],"readme":"# OpenSCAD Playground\n\nOpenSCAD in the browser - A React component for 3D CAD modeling with WebAssembly.\n\n## Features\n\n- **Full OpenSCAD in the browser** - No installation required\n- **Monaco Editor** - VSCode-like editing experience with syntax highlighting\n- **3D Viewer** - Real-time rendering with Google's model-viewer\n- **20+ Libraries** - BOSL2, MCAD, NopSCADlib, and more pre-bundled\n- **Multiple Export Formats** - STL, 3MF, GLB, SVG, DXF, and more\n- **Customizer** - Interactive parameter controls\n- **React Component** - Easy integration into your React apps\n\n## Installation\n\n```bash\nnpm install openscad-playground\n```\n\n## Quick Start\n\n```tsx\nimport { OpenSCADPlayground } from 'openscad-playground';\nimport 'openscad-playground/styles';\n\nfunction App() {\n  return (\n    <OpenSCADPlayground\n      initialFiles={{\n        'main.scad': 'cube([10, 10, 10]);'\n      }}\n      layout=\"multi\"\n    />\n  );\n}\n```\n\n## Documentation\n\n### Basic Usage\n\n```tsx\nimport { OpenSCADPlayground } from 'openscad-playground';\nimport 'openscad-playground/styles';\n\nfunction MyCADApp() {\n  return (\n    <div style={{ height: '100vh' }}>\n      <OpenSCADPlayground\n        initialFiles={{\n          'main.scad': `\n            // Your OpenSCAD code\n            cube([20, 20, 20], center=true);\n            sphere(r=10);\n          `\n        }}\n        layout=\"multi\"\n        onRender={(output) => {\n          console.log('Rendered:', output);\n        }}\n      />\n    </div>\n  );\n}\n```\n\n### Props\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `initialFiles` | `Record<string, string>` | - | Initial file contents |\n| `initialState` | `Partial<State>` | - | Full initial state |\n| `layout` | `'single' \\| 'multi'` | `'multi'` | Layout mode |\n| `defaultFocus` | `'editor' \\| 'viewer' \\| 'customizer'` | `'editor'` | Default focused panel |\n| `libraries` | `LibraryConfig[]` | - | Custom library configurations |\n| `onStateChange` | `(state: State) => void` | - | State change callback |\n| `onRender` | `(output: any) => void` | - | Render complete callback |\n| `onError` | `(error: Error) => void` | - | Error callback |\n| `statePersister` | `StatePersister` | - | Custom state persistence |\n| `className` | `string` | - | CSS class for container |\n| `style` | `React.CSSProperties` | - | Inline styles for container |\n| `customizerValues` | `CustomizerValuesInput` | - | Override customizer parameter values |\n| `onCustomizerValuesChange` | `(values: CustomizerValues) => void` | - | Callback when values change |\n| `onParametersChange` | `(parameters: Parameter[]) => void` | - | Callback when parameter schema changes |\n\n### Required Assets\n\nYou need to host these files publicly (available in the `public/` folder):\n\n1. **OpenSCAD WASM** (~10MB)\n   - `public/wasm/openscad.js`\n   - `public/wasm/openscad.wasm`\n\n2. **External Libraries**\n   - `public/browserfs.min.js` - Virtual filesystem\n   - `public/model-viewer.min.js` - 3D viewer\n\n3. **OpenSCAD Libraries** (optional)\n   - `public/libraries/*.zip` - 20+ OpenSCAD libraries\n\n4. **Other Assets**\n   - `public/fonts/` - Font files\n   - `public/axes.glb` - 3D axes model\n   - `public/skybox-lights.jpg` - Environment lighting\n\n## Development\n\n### Project Structure\n\n```\nopenscad-component/\n├── src/              # Source code\n├── demo/             # Demo app (npm run dev)\n├── test-app/         # Test consumer app\n├── public/           # Static assets\n└── dist/             # Built package\n```\n\n### Commands\n\n```bash\n# Development (runs demo app)\nnpm run dev\n\n# Build the component\nnpm run build\n\n# Build with watch mode\nnpm run build:watch\n```\n\n### Testing the Built Package\n\n```bash\n# Build and setup test app\nbash scripts/build-and-test.sh\n\n# Run test app\ncd test-app\nnpm run dev  # Opens on port 3001\n```\n\n## Advanced Usage\n\n### Custom Customizer Panel\n\nBuild your own customizer UI by using the `customizerValues`, `onCustomizerValuesChange`, and `onParametersChange` props. This allows you to create a completely custom parameter interface while the playground handles rendering.\n\n#### Types\n\n```typescript\n// Simple input format for setting values\ntype CustomizerValuesInput = Record<string, number | string | boolean | number[]>;\n\n// Rich output format with full metadata\ntype CustomizerValues = Record<string, {\n  value: number | string | boolean | number[];\n  type: 'number' | 'string' | 'boolean';\n  initial: number | string | boolean | number[];\n  group: string;      // From OpenSCAD /* [Group Name] */ syntax\n  caption?: string;\n  min?: number;\n  max?: number;\n  step?: number;\n  options?: { name: string; value: number | string }[];\n}>;\n\n// Parameter schema from OpenSCAD file\ntype Parameter = {\n  name: string;\n  type: 'number' | 'string' | 'boolean';\n  initial: number | string | boolean | number[];\n  group: string;\n  caption?: string;\n  min?: number;\n  max?: number;\n  step?: number;\n  options?: { name: string; value: number | string }[];\n};\n```\n\n#### Example `onParametersChange` Payload\n\n```json\n[\n  { \"name\": \"width\", \"type\": \"number\", \"initial\": 20, \"group\": \"Dimensions\", \"min\": 1, \"max\": 100 },\n  { \"name\": \"height\", \"type\": \"number\", \"initial\": 10, \"group\": \"Dimensions\" },\n  { \"name\": \"rounded\", \"type\": \"boolean\", \"initial\": true, \"group\": \"Options\" },\n  { \"name\": \"style\", \"type\": \"string\", \"initial\": \"modern\", \"group\": \"Options\",\n    \"options\": [{ \"name\": \"Modern\", \"value\": \"modern\" }, { \"name\": \"Classic\", \"value\": \"classic\" }] }\n]\n```\n\n#### Example\n\n```tsx\nimport { OpenSCADPlayground } from 'openscad-playground';\nimport type { CustomizerValues, CustomizerValuesInput, Parameter } from 'openscad-playground';\nimport { useState, useCallback } from 'react';\n\nfunction CustomCustomizerApp() {\n  // Parameter schema from the OpenSCAD file\n  const [parameters, setParameters] = useState<Parameter[]>([]);\n  // Current values with full metadata\n  const [values, setValues] = useState<CustomizerValues>({});\n  // Your overrides to send back to the playground\n  const [overrides, setOverrides] = useState<CustomizerValuesInput>({});\n\n  const handleChange = useCallback((name: string, value: number | string | boolean) => {\n    setOverrides(prev => ({ ...prev, [name]: value }));\n  }, []);\n\n  // Group parameters by their /* [Group] */ annotations\n  const grouped = parameters.reduce((acc, param) => {\n    (acc[param.group] ??= []).push(param);\n    return acc;\n  }, {} as Record<string, Parameter[]>);\n\n  return (\n    <div style={{ display: 'flex', height: '100vh' }}>\n      {/* Your custom UI */}\n      <aside style={{ width: 300, padding: 16, overflow: 'auto' }}>\n        {Object.entries(grouped).map(([group, params]) => (\n          <section key={group}>\n            <h3>{group}</h3>\n            {params.map(param => (\n              <div key={param.name}>\n                <label>{param.caption || param.name}</label>\n                {param.type === 'boolean' ? (\n                  <input\n                    type=\"checkbox\"\n                    checked={Boolean(overrides[param.name] ?? values[param.name]?.value ?? param.initial)}\n                    onChange={e => handleChange(param.name, e.target.checked)}\n                  />\n                ) : param.type === 'number' ? (\n                  <input\n                    type=\"number\"\n                    value={Number(overrides[param.name] ?? values[param.name]?.value ?? param.initial)}\n                    min={param.min}\n                    max={param.max}\n                    step={param.step}\n                    onChange={e => handleChange(param.name, Number(e.target.value))}\n                  />\n                ) : (\n                  <input\n                    type=\"text\"\n                    value={String(overrides[param.name] ?? values[param.name]?.value ?? param.initial)}\n                    onChange={e => handleChange(param.name, e.target.value)}\n                  />\n                )}\n              </div>\n            ))}\n          </section>\n        ))}\n      </aside>\n\n      {/* Playground with bindings */}\n      <div style={{ flex: 1 }}>\n        <OpenSCADPlayground\n          initialFiles={{\n            'main.scad': `\n              /* [Dimensions] */\n              width = 20;\n              height = 10;\n              \n              /* [Options] */\n              rounded = true;\n              \n              cube([width, width, height], center=true);\n            `\n          }}\n          customizerValues={overrides}\n          onCustomizerValuesChange={setValues}\n          onParametersChange={setParameters}\n          initialState={{\n            view: {\n              layout: { mode: 'multi', editor: true, viewer: true, customizer: false }\n            }\n          }}\n        />\n      </div>\n    </div>\n  );\n}\n```\n\n#### How It Works\n\n1. **`onParametersChange`** fires when the OpenSCAD file is parsed, providing the parameter schema with types, groups, and constraints\n2. **`onCustomizerValuesChange`** fires whenever values change, providing the current state with full metadata\n3. **`customizerValues`** accepts simple key-value pairs to override parameter values - changes trigger instant re-renders\n\nThe OpenSCAD `/* [Group Name] */` syntax is used to organize parameters into groups in your custom UI.\n\n### Custom State Management\n\n```tsx\nimport { OpenSCADPlayground, State } from 'openscad-playground';\nimport { useState } from 'react';\n\nfunction App() {\n  const [state, setState] = useState<Partial<State>>({\n    params: {\n      activePath: 'main.scad',\n      sources: [\n        { path: 'main.scad', content: 'cube([10,10,10]);' }\n      ]\n    }\n  });\n\n  return (\n    <OpenSCADPlayground\n      initialState={state}\n      onStateChange={(newState) => {\n        setState(newState);\n        // Save to localStorage, database, etc.\n      }}\n    />\n  );\n}\n```\n\n### Using Hooks\n\n```tsx\nimport { useOpenSCAD } from 'openscad-playground';\n\nfunction MyComponent() {\n  const { model, fs, isReady, error } = useOpenSCAD({\n    initialState: getInitialState()\n  });\n\n  if (!isReady) return <div>Loading...</div>;\n  if (error) return <div>Error: {error.message}</div>;\n\n  return <div>OpenSCAD is ready!</div>;\n}\n```\n\n\n## Credits\n\n- Original OpenSCAD Playground by [Olivier Chafik](https://github.com/ochafik)\n- OpenSCAD project: [openscad.org](https://openscad.org)\n- BOSL2 and other bundled libraries by their respective authors\n","readmeFilename":"README.md"}