Skip to content

OpenSCAD WASM for 3D Preview Rendering

  • Date: 2026-02-24
  • Author: Architecture Research
  • Status: Draft
  • Audience: Engineering team, product stakeholders

Table of Contents


1. Executive Summary

Our current 3D preview pipeline reimplements GridFlock geometry in JSCAD (@jscad/modeling), serializes to STL on the server, and renders via Three.js on the client. This approach suffers from geometry artifacts (internal faces from CSG operations), difficulty matching connector geometry, and ongoing maintenance burden as GridFlock evolves upstream.

This document evaluates replacing the JSCAD geometry layer with OpenSCAD compiled to WebAssembly (WASM), allowing us to use the original gridflock.scad source directly. Three architectural options are assessed: client-side WASM rendering, server-side OpenSCAD rendering, and embedding/linking to the existing GridFlock web editor.

Recommendation: A hybrid approach — use OpenSCAD server-side (CLI in Docker) for STL generation to avoid GPL contamination of our client bundle, while keeping Three.js for rendering. This provides pixel-perfect GridFlock output with minimal architecture change and no licensing risk.


2. Current Architecture

Data Flow

User Input (Shopify Theme)
  │
  ▼
POST /api/v1/gridflock/preview { widthMm, heightMm }
  │
  ▼
GridflockPreviewService
  ├── calculatePlateSet() → PlateSpec[]
  ├── For each plate:
  │     └── generateGridFlockPlate()
  │           ├── JSCAD: base-plate.ts (rounded rectangle)
  │           ├── JSCAD: grid-cells.ts (cell cutters)
  │           ├── JSCAD: intersection-puzzle.ts (connectors)
  │           ├── JSCAD: magnet-holes.ts (cavities)
  │           └── serializeToStl() → Buffer
  └── combineStlBuffers() → Combined STL
  │
  ▼
Binary STL → Client
  │
  ▼
configurator-3d.js
  ├── STLParser.parse() → THREE.BufferGeometry
  └── MeshStandardMaterial → Three.js Scene

Key Files

Layer File Purpose
Server libs/gridflock-core/src/lib/generator.ts Orchestrates JSCAD geometry
Server libs/gridflock-core/src/lib/serializer.ts JSCAD → binary STL
Server libs/gridflock-core/src/lib/geometry/*.ts Individual geometry modules
Server libs/gridflock-core/src/lib/stl-combiner.ts Merges plate STLs
Server apps/gridflock-service/src/gridflock/ Preview REST endpoint
Client deployment/shopify-theme/assets/configurator-3d.js Three.js rendering
Client deployment/shopify-theme/sections/configurator.liquid Shopify Liquid template

Known Issues

  • Internal faces/artifacts: JSCAD CSG operations create internal geometry visible in Three.js when faces overlap. Simplification hacks (e.g., omitting lip details in base-plate.ts) reduce but don't eliminate these.
  • Connector fidelity: The intersection puzzle connectors in JSCAD are approximations of GridFlock's SVG-derived puzzle shapes. Getting exact geometry match is difficult.
  • Maintenance burden: Every upstream GridFlock feature (edge puzzle, click latch, ClickGroove, vertical screws, thumb screws, filler algorithms) must be manually reimplemented.
  • Rendering quality: The preview does not match the quality users see when they import the actual GridFlock STL into a slicer.

3. What is OpenSCAD WASM?

Overview

OpenSCAD is a programmable CAD modeler that creates 3D models from script files (.scad). It uses Constructive Solid Geometry (CSG) with an exact geometry kernel (historically CGAL, now with optional Manifold backend).

OpenSCAD WASM is a full port of the OpenSCAD engine to WebAssembly, enabling it to run in modern web browsers or any WASM runtime (Node.js, Deno, server-side).

Key Projects

Project Repository Description Stars
openscad-wasm openscad/openscad-wasm Official WASM port, npm package openscad-wasm ~360
OpenSCAD Playground openscad/openscad-playground Full web IDE (Monaco editor + 3D viewer) ~200
Web OpenSCAD Customizer vector76/Web_OpenSCAD_Customizer Lightweight parametric customizer UI ~44
DSchroer/openscad-wasm DSchroer/openscad-wasm Earlier independent WASM port

NPM Package

  • Package: openscad-wasm (version 0.0.4)
  • License: GPL-2.0
  • Distribution: Available via npm and jsDelivr CDN
  • Format: ES6 module with .wasm binary

API Surface

The WASM module exposes an Emscripten-based API:

import OpenSCAD from 'openscad-wasm';

const instance = await OpenSCAD();

// Write .scad source to the virtual filesystem
instance.FS.writeFile('/model.scad', scadSource);

// Write dependency files (libraries, SVGs, etc.)
instance.FS.writeFile('/puzzle.svg', svgContent);
instance.FS.mkdir('/gridfinity-rebuilt-openscad');
instance.FS.writeFile('/gridfinity-rebuilt-openscad/gridfinity-rebuilt-utility.scad', ...);

// Run OpenSCAD with command-line arguments
instance.callMain([
  '/model.scad',
  '-o', '/output.stl',
  '-D', 'plate_size=[200,200]',
  '-D', 'bed_size=[256,256]',
  '-D', 'connector_intersection_puzzle=true',
]);

// Read the generated STL
const stlData = instance.FS.readFile('/output.stl');

Maturity Assessment

Aspect Status
Core functionality Stable — full OpenSCAD feature set
Browser compatibility Chrome, Firefox, Safari (modern)
Manifold backend Supported — 10-100x faster than CGAL for unions
Font support Included (bundled fonts add to size)
Community adoption Growing — used by Perplexing Labs, Gridfinity tools
npm releases Infrequent (v0.0.4), but repo is active
Documentation Minimal — mostly examples and blog posts

4. How GridFlock's Web Editor Works

Architecture

The GridFlock web editor at gridflock.yawk.at is powered by the Perplexing Labs Gridfinity Generator platform, which is a general-purpose OpenSCAD WASM customizer. The flow:

  1. The editor.toml file in the GridFlock repo defines the parametric UI
  2. The platform reads editor.toml and generates HTML form controls
  3. User changes are encoded in the URL fragment (Base64 JSON) for shareability
  4. Parameter values are passed to OpenSCAD WASM via -D flags
  5. OpenSCAD WASM renders the .scad file to STL in a Web Worker
  6. The STL is displayed in a 3D viewer (model-viewer / Three.js)
  7. Users can download the generated STL

The editor.toml Format

The editor.toml file is a configuration schema that drives the parametric editor UI. Key sections:

[project]
name = "GridFlock"
uri = "https://github.com/yawkat/GridFlock"
export-filename-prefix = "GridFlock"
mode = "single"

[[model]]
file = "gridflock.scad"

# Parameter metadata controls UI rendering
[model.param-metadata.bed_size.presets]
text = "Printer Presets"
[model.param-metadata.bed_size.presets.values]
"Bambu Lab X1/P1/P2/A1" = [256, 256]
"Prusa Core One/+/MK4/S" = [250, 220]
# ...more presets

# Tab grouping with conditional visibility
[model.tab-metadata."Magnets"]
control-boolean = "magnets"
help-link = "https://github.com/yawkat/GridFlock/blob/main/README.md#magnets"

[model.param-metadata.magnet_style]
display-condition = {js = "magnets"}

Notable features of editor.toml:

  • Printer presets: Dropdown with bed sizes for ~20 printer models
  • Tab groups: Collapsible sections (Magnets, Connectors, Filler, etc.)
  • Conditional visibility: Parameters show/hide based on other parameter values
  • Help links: Each parameter links to README documentation
  • Analytics tracking: Umami event tracking on render/export actions

Dependencies

GridFlock's .scad file requires:

  • gridfinity-rebuilt-openscad — Git submodule (MIT license) for baseplate cutter geometry
  • puzzle.svg — SVG file containing the intersection puzzle connector paths
  • extract_paths.py — Python script to convert SVG paths to OpenSCAD polygon data

The web editor bundles all dependencies into the WASM virtual filesystem before rendering.


5. Architecture Options

Option A: Client-Side OpenSCAD WASM

Run the full OpenSCAD WASM engine in the user's browser.

User Input (Shopify Theme)
  │
  ▼
JavaScript: collect parameters
  │
  ▼
Web Worker: OpenSCAD WASM
  ├── Load gridflock.scad + dependencies into FS
  ├── callMain([...params]) → generates STL
  └── Return STL ArrayBuffer
  │
  ▼
Three.js: parse STL → render scene
Factor Assessment
Complexity High — must bundle WASM + .scad + dependencies in Shopify theme
Bundle size ~15-30 MB (WASM binary + fonts), ~1.7 MB with LZMA compression
Performance 5-60 seconds render depending on model complexity and device
UX Loading spinner during render; responsive UI via Web Worker
Server load None — all computation client-side
Licensing GPL-2.0 WASM bundle in our client code — significant concern
Shopify CSP Requires wasm-unsafe-eval directive; may conflict with theme policies
Offline capable Yes, after initial load

Option B: Server-Side OpenSCAD (CLI or WASM)

Run OpenSCAD on the server, keep Three.js on the client for rendering.

User Input (Shopify Theme)
  │
  ▼
POST /api/v1/gridflock/preview { params }
  │
  ▼
Server: OpenSCAD CLI (Docker container)
  ├── Write params to .scad variables
  ├── Execute: openscad gridflock.scad -o output.stl -D "..."
  └── Return STL binary
  │
  ▼
Three.js: parse STL → render scene

Sub-option B1: OpenSCAD CLI in Docker

  • Use the official openscad/openscad Docker image
  • Execute as a subprocess with timeout
  • Well-understood deployment pattern

Sub-option B2: OpenSCAD WASM in Node.js

  • Run the WASM module server-side in Node.js/Deno
  • No Docker overhead, same process
  • Less battle-tested in production
Factor Assessment
Complexity Medium — replaces JSCAD generator; keeps existing API/client
Bundle size No client impact — WASM stays on server
Performance 5-30s server-side (faster hardware than client); can cache results
UX Same as current — loading indicator while server generates
Server load Higher CPU per request; mitigated by caching
Licensing GPL stays server-side; output STL files are not GPL-encumbered
Shopify CSP No changes needed
Caching Deterministic output — same params = same STL; excellent cache hit rate

Option C: Embed GridFlock Web Editor

Use the existing GridFlock editor at gridflock.yawk.at via iframe or direct link.

User Input (Shopify Theme)
  │
  ▼
<iframe src="https://gridflock.yawk.at/?params=...">
  │
  ▼
GridFlock editor handles everything
  ├── OpenSCAD WASM rendering
  ├── 3D preview
  └── STL download
Factor Assessment
Complexity Low — just embed an iframe
Bundle size Zero — external resource
Performance Dependent on GridFlock's infrastructure
UX Poor — no Forma3D branding, separate UI, no integration with order flow
Server load None
Licensing No code integration — just linking
Customization Very limited — can only pass URL parameters
Business risk High — dependency on external service availability

6. Benefits

Pixel-Perfect Output

OpenSCAD uses the same geometry kernel as the original GridFlock project. The generated STL will be mathematically identical to what users produce with the GridFlock editor or CLI. No approximation artifacts, no internal faces, no geometry mismatch.

No Geometry Reimplementation

Our current JSCAD layer (libs/gridflock-core/src/lib/geometry/) reimplements every GridFlock feature from scratch. With OpenSCAD, we use gridflock.scad directly:

Feature Current (JSCAD) With OpenSCAD
Base plate base-plate.ts (custom) Built into gridflock.scad
Grid cells grid-cells.ts (custom) Gridfinity Rebuilt (exact)
Intersection puzzle intersection-puzzle.ts (approximation) puzzle.svg (exact)
Edge puzzle edge-puzzle.ts (custom) gridflock.scad (exact)
Magnet holes magnet-holes.ts (custom) gridflock.scad (exact)
Click latch Not implemented gridflock.scad (exact)
ClickGroove Not implemented gridflock.scad (exact)
Vertical screws Not implemented gridflock.scad (exact)
Thumb screws Not implemented gridflock.scad (exact)
Filler algorithms Not implemented gridflock.scad (exact)
Numbering numbering.ts (simplified) gridflock.scad (full fonts)

Automatic Upstream Updates

When GridFlock adds new features or fixes bugs, we can update by pulling the latest .scad files. No reverse-engineering or manual reimplementation required.

Community Alignment

GridFlock is actively developed (48 stars, regular commits) and used by the broader Gridfinity community. Using the same geometry engine ensures our output is compatible with community expectations and slicer profiles.

Reduced Maintenance Surface

Eliminating the JSCAD geometry layer removes ~1,500 lines of custom geometry code and associated tests. The remaining integration code (parameter mapping, STL serving) is simpler and less error-prone.


7. Risks and Challenges

Bundle Size (Client-Side Only)

The OpenSCAD WASM binary is substantial:

Component Raw Size Compressed (gzip) Compressed (LZMA)
openscad.wasm ~25 MB ~8 MB ~1.7 MB
Fonts ~5 MB ~3 MB ~1.5 MB
JS glue code ~500 KB ~150 KB ~100 KB
Total ~30 MB ~11 MB ~3.3 MB

This is prohibitive for a Shopify storefront where page load time directly impacts conversion. Mitigation: Use server-side rendering (Option B) to keep the client bundle unchanged.

Render Time

OpenSCAD rendering is computationally expensive. Estimated times for GridFlock baseplates:

Model Complexity Desktop (native) WASM (browser) WASM (server)
Simple 2×2 plate 2-5s 5-15s 3-8s
Medium 4×4 plate 5-15s 15-45s 8-20s
Complex 6×6 with magnets 15-45s 45-120s 20-50s

The Manifold backend (enabled by default in recent builds) provides 10-100x speedup over the legacy CGAL kernel for union operations, significantly reducing these times.

Mitigation strategies:

  • Aggressive caching (parameter hash → STL)
  • Background pre-generation for common configurations
  • Use Manifold backend for fastest rendering
  • Limit preview to single segments, not full plate sets

GridFlock Dependencies

Running gridflock.scad requires bundling:

  1. The gridflock.scad file itself
  2. The gridfinity-rebuilt-openscad library (Git submodule)
  3. The puzzle.svg file (connector paths)
  4. Pre-extracted polygon data from extract_paths.py

These must be loaded into the WASM virtual filesystem before rendering. The extraction step (just paths) converts SVG to OpenSCAD polygon data and must be run during the build process.

Customization Constraints

Adding Forma3D-specific features (custom branding, proprietary geometry) to gridflock.scad requires understanding OpenSCAD syntax. While OpenSCAD is programmable, it has a steeper learning curve than JSCAD/JavaScript for our team.

External Dependency Risk

Dependency Risk Mitigation
openscad-wasm npm package updates lag behind OpenSCAD releases Pin version; build from source if needed
GridFlock Upstream breaking changes Pin to specific commit/tag
gridfinity-rebuilt-openscad API changes in library Pinned via GridFlock's submodule
OpenSCAD itself Major version changes Use specific WASM build

Shopify Theme Constraints (Client-Side Only)

Constraint Impact
CSP: wasm-unsafe-eval required Must be explicitly allowed in theme headers
Asset size limits Shopify themes have 50MB total asset limits
CDN caching WASM files are not typical Shopify assets
Mobile performance WASM execution on mobile devices is significantly slower

8. Licensing Analysis

License Summary

Component License Implications
OpenSCAD engine GPL-2.0 Copyleft — derivative works must be GPL
openscad-wasm GPL-2.0 Same as above — compiled from GPL source
GridFlock (gridflock.scad) MIT Permissive — can use freely
Gridfinity Rebuilt MIT Permissive — can use freely
Generated STL output Not covered by GPL Output of GPL tools is not GPL-encumbered

Client-side WASM (Option A): Bundling openscad-wasm (GPL-2.0) into our client JavaScript creates a GPL-encumbered distribution. Our entire client-side codebase would arguably need to be released under GPL-2.0. This is the most restrictive interpretation and represents a significant legal risk for a commercial product.

The wasm-unsafe-eval CSP question adds technical complexity but does not resolve the licensing concern.

Server-side execution (Option B): Running OpenSCAD as a tool on the server (CLI in Docker or WASM in Node.js) to generate STL output is analogous to using GCC (GPL) to compile code — the output is not GPL-encumbered. The GPL applies to the tool itself, not its output. This is the safest approach and is the standard interpretation for GPL build tools.

Our server code calls OpenSCAD as an external process, does not link against it, and does not distribute the GPL binary to end users. This approach has strong precedent and is widely practiced.

Embedding/linking (Option C): Linking to gridflock.yawk.at via iframe carries no licensing obligations. We are not distributing GPL software — we are linking to a third-party service.

Recommendation

Use Option B (server-side) to avoid any GPL contamination of our commercial codebase. The .scad source files (MIT-licensed) and generated STL output are both freely usable.


9. Comparison Matrix

Criterion Current (JSCAD) Option A (Client WASM) Option B (Server OpenSCAD) Option C (Embed Editor)
Geometry fidelity Approximate Exact Exact Exact
Feature coverage Partial (~40%) Complete (100%) Complete (100%) Complete (100%)
Bundle size impact None +11-30 MB None None
Server CPU cost Medium None High (cacheable) None
Initial load time Fast Slow (WASM load) Fast Medium (iframe)
Render latency 1-5s 5-120s 3-50s 5-120s
Caching potential Low Browser-side Excellent (deterministic) N/A
GPL risk None High None None
Shopify compatibility Good Problematic Good Moderate
Customization Full (JS) Full (SCAD) Full (SCAD) Limited (URL params)
UX integration Seamless Seamless Seamless Poor (iframe)
Maintenance burden High Low Low Very low
Upstream sync effort High (reimpl.) Low (file update) Low (file update) Zero
Mobile performance Good Poor Good Poor
Offline capability No Yes (after load) No No

Primary Recommendation: Option B — Server-Side OpenSCAD CLI

Run OpenSCAD inside a Docker container on the server. Replace the JSCAD geometry pipeline with direct execution of gridflock.scad. Keep the existing Three.js client and API contract.

Why this option:

  • Zero client-side impact — no bundle size increase, no CSP changes, no mobile perf concern
  • Zero GPL risk — OpenSCAD runs as a server-side tool, output STLs are not encumbered
  • Pixel-perfect geometry — identical output to GridFlock editor
  • Excellent caching — deterministic params → deterministic STL; Redis/disk cache
  • Minimal API change — same endpoint, same binary STL response
  • Future-proof — as GridFlock adds features, we update .scad files

Caching Strategy

Given that OpenSCAD rendering is expensive (5-50s per plate), caching is critical:

Request: { widthMm: 450, heightMm: 320, magnets: false, connector: 'intersection-puzzle' }
  │
  ▼
Cache key: SHA256(canonical JSON of all OpenSCAD params)
  │
  ├── Cache HIT → return cached STL (< 50ms)
  │
  └── Cache MISS → run OpenSCAD → cache result → return STL
  • L1 cache: In-memory LRU (hot configs, ~100 entries)
  • L2 cache: Redis/disk (all generated STLs, TTL 7 days)
  • Pre-warm: Generate common configurations on deploy

Preview Optimization

To reduce perceived latency for uncached configurations:

  • Render a single representative segment instead of the full plate set
  • Use a simplified .scad variant with reduced $fn for faster preview
  • Show a low-poly placeholder immediately, upgrade when full render completes
  • Return rendering progress via Server-Sent Events or WebSocket

11. Implementation Plan

Phase 1: Proof of Concept (T-shirt: S — 1-2 weeks)

Goal: Validate that gridflock.scad can run in Docker and produce correct STL output.

  • Set up OpenSCAD Docker container with required dependencies
  • Bundle gridflock.scad, gridfinity-rebuilt-openscad, and puzzle.svg
  • Run extract_paths.py to generate polygon data in build step
  • Execute OpenSCAD with sample parameters and verify STL output
  • Compare output to GridFlock web editor output (binary diff or visual)
  • Measure rendering times for various plate configurations
  • Document parameter mapping between our API and OpenSCAD variables

Deliverable: Working Docker container that generates GridFlock STLs from parameters.

Phase 2: API Integration (T-shirt: M — 2-3 weeks)

Goal: Replace JSCAD geometry pipeline with OpenSCAD execution.

  • Create OpenScadGeneratorService in apps/gridflock-service
  • Implement parameter mapping: API DTO → OpenSCAD -D flags
  • Add subprocess execution with timeout and error handling
  • Implement STL caching layer (Redis or disk-based)
  • Update PreviewController to use new service
  • Add health check for OpenSCAD container availability
  • Write integration tests comparing output to known-good STLs
  • Feature flag to toggle between JSCAD and OpenSCAD backends

Deliverable: Preview endpoint generating STLs via OpenSCAD behind a feature flag.

Phase 3: Production Hardening (T-shirt: M — 2-3 weeks)

Goal: Make the OpenSCAD pipeline production-ready.

  • Add resource limits (CPU, memory, timeout) to OpenSCAD subprocess
  • Implement request queuing to prevent server overload
  • Add cache pre-warming for common printer presets
  • Add monitoring and alerting for render times and failures
  • Load test with concurrent requests
  • Update Docker Compose / Kubernetes manifests
  • Configure CI/CD pipeline for OpenSCAD container builds
  • Remove feature flag and deprecate JSCAD pipeline

Deliverable: Production-grade OpenSCAD preview pipeline.

Phase 4: Enhanced Features (T-shirt: L — 3-5 weeks)

Goal: Leverage full GridFlock feature set in our product.

  • Expose additional GridFlock parameters in the configurator UI
  • Connector type selection (intersection puzzle, edge puzzle)
  • Magnet options (press-fit, glued, none)
  • Filler algorithm selection
  • Click latch options
  • Add STL download for production printing (not just preview)
  • Implement progress feedback (SSE/WebSocket) for long renders
  • Evaluate client-side WASM for an "advanced editor" mode (separate page, not storefront)
  • Consider contributing Forma3D-specific features upstream to GridFlock

Deliverable: Full-featured configurator with all GridFlock capabilities.

Total Estimated Effort

Phase Size Duration Dependencies
Phase 1: PoC S 1-2 weeks Docker, OpenSCAD
Phase 2: Integration M 2-3 weeks Phase 1
Phase 3: Hardening M 2-3 weeks Phase 2
Phase 4: Features L 3-5 weeks Phase 3
Total 8-13 weeks

12. References

OpenSCAD WASM

GridFlock

Performance

Licensing

Shopify / CSP