Skip to content

STL Preview Cache Reuse for Order Pipeline — Research

Date: 2026-02-28 Status: Research (references to the legacy preview cache are outdated — the full-preview-per-dimension cache has been replaced by the plate-level cache, see ADR-061)


1. Problem Statement

When a Shopify order arrives for a custom grid, the pipeline currently:

  1. Calculates the plate set for the requested dimensions
  2. Generates individual plate STL buffers from scratch (CPU-intensive, JSCAD)
  3. Slices each plate STL → gcode
  4. Uploads gcode to SimplyPrint
  5. Creates the product mapping

Step 2 is the most CPU-intensive part of the pipeline. Meanwhile, the preview cache already contains pre-generated STL data for ~16,000 dimension combinations, generated using the exact same calculatePlateSet() and generatePlateStl() functions from @forma3d/gridflock-core.

Question: Can the cached preview STLs be reused to skip step 2 for orders where matching cache data exists?


2. Current Architecture Comparison

Preview Cache (Shopify storefront)

Aspect Detail
Cache path /data/gridflock/preview-cache/
File format Binary STL
Naming preview-{w}x{h}.stl (dimensions normalized, larger first)
Content Single combined STL buffer — all plates merged with offsets
Parameters Fixed: intersection-puzzle connector, magnets disabled
Printer profile Fixed: bambu-a1
Generation generatePreviewStl()generatePlatesParallel()combineStlBuffers()
Coverage 100–1000mm in 5mm increments = 16,471 combinations

Order Pipeline (gcode generation)

Aspect Detail
Input Per-order: dimensions, connector type, magnets boolean
Content Individual plate STL buffers — one per plate
Parameters Variable: intersection-puzzle or edge-puzzle, magnets on/off
Printer profile From tenant settings (defaults to bambu-a1)
Generation calculatePlateSet()generatePlateStl() per plate (sequential)
Output Each plate STL → slicer → gcode → SimplyPrint upload

3. Compatibility Analysis

3.1 Structural blocker: combined vs. individual STLs

The preview cache stores a single combined STL containing all plates merged together with spatial offsets. The order pipeline needs individual plate STL buffers to slice each one separately.

Impact: The combined STL cannot be fed directly to the slicer. It would need to be either: - (a) Split back into individual plates (complex, lossy), or - (b) The cache format changed to store individual plates alongside (or instead of) the combined file.

Verdict: Direct reuse of the current cache format is not possible without cache format changes.

3.2 Parameter mismatch: connector type and magnets

The preview cache uses fixed parameters: - Connector type: intersection-puzzle only - Magnets: always disabled

Orders can specify: - Connector type: intersection-puzzle or edge-puzzle - Magnets: on or off

These parameters fundamentally change the STL geometry: - Different connector types produce different interlocking features on plate edges - Magnets add cylindrical cavities on the plate bottom

Verdict: Cache reuse is only valid for orders that exactly match the preview parameters (intersection-puzzle + no magnets). For all other parameter combinations, the STL must be regenerated.

3.3 Printer profile mismatch

The preview always uses bambu-a1 (256×256mm bed). The pipeline selects a printer profile from tenant settings.

Since calculatePlateSet() uses the printer profile to determine how many plates to create and how to divide the grid, a different printer profile produces a different plate layout.

Verdict: Cache reuse is only valid when the tenant uses the bambu-a1 printer profile (which is also the default, and currently the only printer in production use).

3.4 Generation function determinism

Both the preview and the pipeline use the same core functions from @forma3d/gridflock-core: - calculatePlateSet() — deterministic, same inputs → same plate layout - generatePlateStl() — deterministic, same plate spec + options → identical STL buffer

If parameters match, the individual plate STLs produced by the pipeline are byte-identical to those produced during preview generation (before they get combined).

Verdict: When parameters match, the STL buffers are guaranteed identical. This is a strong foundation for reuse.


4. Feasible Approaches

Approach A: Per-plate STL cache (alongside combined preview)

Concept: During preview generation (or cache pre-population), also cache each individual plate STL buffer separately.

Cache structure:

/data/gridflock/preview-cache/
  preview-450x320.stl              ← existing combined (for storefront)
  plates/
    GF-450x320-IP-NOMAG/
      plate_1.stl                  ← individual plate STL
      plate_2.stl
      plate_3.stl

Cache key: {sku}/plate_{index}.stl — includes connector type and magnets in the SKU, matching the product mapping SKU format.

Pipeline change: Before generating STL, check if plates/{sku}/plate_{index}.stl exists in the cache directory. If all plates exist, skip generation and send each directly to the slicer.

Pros: - Clean separation — preview cache and plate cache coexist - SKU-based keys naturally accommodate different connector/magnet combos - Pre-population script can be extended to generate all four parameter combos per dimension - Completely backward-compatible with existing preview cache

Cons: - Storage increase: ~4× if all parameter combos are cached (IP/EP × MAG/NOMAG) - Pre-population time increase for generating additional combos - Cache directory grows from ~16K files to ~16K × 4 combos × avg 2 plates ≈ 130K files - Requires shared volume access between preview service and pipeline service

Approach B: In-memory plate cache in pipeline service

Concept: When the pipeline generates plates for an order, cache the individual STL buffers in memory (or Redis) keyed by plate spec hash. Subsequent orders with the same grid dimensions and parameters can reuse the cached buffers.

Pros: - No file system changes needed - Works regardless of preview cache existence - Automatically handles all parameter combinations

Cons: - Memory pressure (STL buffers are 50KB–7MB each) - Cold start on every service restart - Doesn't leverage the existing pre-populated preview cache at all - Solves a different problem (repeat orders) rather than first-order optimization

Approach C: Modify preview generation to store individual plates

Concept: Change generatePreviewStl() to persist individual plate buffers as a side effect, then combine them into the preview STL as before.

Implementation sketch: 1. generatePreviewStl() generates plates in parallel → gets individual stlBuffer per plate 2. New: Write each plate buffer to plates/{sku}/plate_{index}.stl 3. Combine all plate buffers into the preview STL (existing logic) 4. Write combined preview to preview-{w}x{h}.stl (existing logic)

Pipeline change: Before generating a plate STL, check if the per-plate cache file exists. If yes, read from disk and skip generation.

Pros: - Leverages existing preview generation — individual plates are already computed (line 60-65 of preview-generator.ts shows results[i].stlBuffer is available per plate) - No extra CPU work — just persisting data that's already in memory during preview generation - Existing pre-population script would automatically populate the plate cache

Cons: - Preview and pipeline share a file system dependency - Only covers the intersection-puzzle + no magnets combo (the preview defaults) - Modifies the preview generator's contract (side effects)

Approach D: Dedicated per-plate STL cache service

Concept: Create a small caching layer that sits between the pipeline and STL generation:

  1. Pipeline computes the plate set (cheap — just math)
  2. For each plate, derive a deterministic cache key from plate spec + generation options
  3. Check cache (file or Redis)
  4. On hit: return cached STL buffer → send to slicer
  5. On miss: generate STL → cache it → send to slicer

Cache key formula:

plate-{gridW}x{gridH}-{connEdges}-{connType}-{mag}-{border}.stl
Example: plate-6x6-TFFT-IP-NOMAG-0-0-0-0.stl

Pros: - Completely generic — works for any parameter combination - Plates with identical specs (same grid size, same edges, same options) share one cache entry even across different grid surface sizes - Maximum deduplication: a 6×6 interior plate is the same whether it comes from a 450×320 grid or a 900×600 grid - Can be warmed from either preview generation or pipeline execution

Cons: - More complex implementation - Requires careful cache key design to guarantee correctness - Storage estimates needed for full cache size


5. Storage and Performance Estimates

Per-plate file sizes

Grid size Approx STL size
1×1 (42mm) ~3 KB
3×3 (126mm) ~25 KB
6×6 (252mm) ~52 KB
6×3 (edge plate) ~30 KB

Unique plate specs (Approach D analysis)

For the bambu-a1 printer with default parameters: - Grid sizes range from 1×1 to 6×6 - Each plate can have 16 connector edge combinations (4 boolean edges) - Border values vary by position in the assembly

Rough estimate: ~200–500 unique plate specs across all 16,471 dimension combinations. This is dramatically fewer files than caching per-dimension.

Total storage for Approach D: ~500 plates × 50 KB avg ≈ 25 MB (negligible)

CPU time savings

STL generation for a single plate: 200–800ms depending on grid size and features. Typical order (2–6 plates): 1–4 seconds of CPU time saved per order.

For orders matching the preview parameters (majority of orders in practice), 100% of STL generation can be skipped.


6. Recommendation

Approach D (Dedicated per-plate STL cache) is the strongest option

Reasons: 1. Maximum cache hit rate: Plates with identical specs share cache entries regardless of the overall grid surface size. A 6×6 full plate appears in thousands of different grid configurations — generate it once, reuse everywhere. 2. Parameter-agnostic: Works for all connector type and magnet combinations, not just the preview defaults. 3. Minimal storage: ~25 MB for full coverage vs. ~32 GB for the preview cache. 4. Dual warm-up path: Cache is populated automatically by both preview generation and first-time pipeline runs. 5. No preview generator changes: The cache layer sits in the pipeline; preview generation continues unchanged.

Approach C as a pragmatic short-term win

If most orders use the default parameters (intersection-puzzle, no magnets, bambu-a1), Approach C gives the best effort-to-value ratio: - The individual plate buffers are already computed during preview generation (line 60-65 of preview-generator.ts) - Just persist them to disk as a side effect - Pipeline checks for them before regenerating - Falls back to generation for non-default parameter combos

Implementation priority

Given the deduplication fix (from the getMappingStatus implementation) already prevents duplicate uploads for repeat orders of the same grid size, the cache reuse optimization primarily benefits first-time orders for a given grid size + parameter combination.

The value depends on order volume: - Low volume (current): The product mapping deduplication fix is sufficient; STL generation only happens once per unique grid config. - High volume (growth scenario): Per-plate caching (Approach D) eliminates redundant CPU work across different grid sizes that share identical plate specs.


7. Key Code References

Component Path
Preview STL generator libs/gridflock-core/src/lib/preview-generator.ts
Individual plates available at preview-generator.ts:60-65results[i].stlBuffer per plate
Pipeline STL generation apps/gridflock-service/src/gridflock/gridflock-pipeline.service.ts:164-209
Plate generation function libs/gridflock-core/src/lib/generator.ts:183-196
Parallel plate generation libs/gridflock-core/src/lib/parallel-generator.ts
Preview cache service apps/gridflock-service/src/gridflock/gridflock-preview.service.ts
Cache pre-population script scripts/populate-preview-cache.ts
Plate set calculator libs/gridflock-core/src/lib/plate-set-calculator.ts
Default parameters libs/gridflock-core/src/lib/defaults.ts

8. Open Questions

  1. What percentage of orders use intersection-puzzle + no magnets? This determines how much value Approach C alone provides vs. needing the full Approach D.
  2. Is there a shared volume between the preview cache and the pipeline container? Both currently run in the gridflock-service, so disk access should be shared. If they are ever separated into distinct services, a shared volume or object store would be needed.
  3. Should the slicer also cache gcode? The slicer produces deterministic output for a given STL + profile combination. Caching gcode would skip both STL generation and slicing for repeat plate specs.
  4. Cache invalidation: If gridflock-core geometry generation changes (bugfix, new features), cached STLs would be stale. A cache version key or full cache invalidation strategy is needed.