Support Generation for SLA, DLP, LCD 3D Printing

A reference for implementing a desktop CAD/print-prep tool in Electron + React + TypeScript + Three.js with manifold-3d for CSG and three-mesh-bvh for geometry queries. Coordinates: Z-up, build plate at Z=0, gravity = −Ẑ, mm.

0. Executive summary

For an enterprise-grade SLA/mSLA support engine, the right architecture is a scalar-field pipeline: (1) a smooth, mesh-based “support-need” field derived from gravity-aligned overhang angle and unsupported-span geodesics, (2) variable-density blue-noise sampling driven by a density(p) → spacing function tied to the active resin profile, (3) collision-aware tree/pillar geometry CSG-unioned via manifold-3d, and (4) handoff as either a single watertight STL or a 3MF with a named “support” body so downstream slicers (Chitubox, Lychee, PreForm, PrusaSlicer SLA) do not regenerate supports. The dominant physical driver is peel/separation force, which scales nonlinearly with cross-section area, resin viscosity, and tank-film type (FEP < nFEP < ACF < LFS flexible film), so the orientation objective and density field must minimize per-layer cured area at peel time, not just total support volume.

The remainder of this document is the spec, with formulas, citations, pseudocode, parameter tables, a TypeScript ResinProfile schema with 10 Formlabs-derived starter profiles, slicer comparison tables, and an explicit “what NOT to do” list.

1. SLA Print Physics — Why Supports Exist

1.1 The cure / lift / peel cycle

Inverted (bottom-up) photopolymer printers — Formlabs Form 2/3/3+/4, Phrozen Sonic series, Anycubic Photon, Elegoo Mars/Saturn — execute the same five-step loop per layer:

  1. Settle: build plate descends to z = layer_thickness above the tank-bottom film.
  2. Cure: laser raster (Form 2) or LCD/LED mask (Form 4, Phrozen, Anycubic, Elegoo) exposes a 2-D image at the working wavelength (typically 385 nm or 405 nm) for the resin’s exposure time (1–8 s typical).
  3. Peel: the build plate retracts; the cured slice must release from the tank-bottom film (FEP, nFEP, ACF, or Formlabs’ flexible film).
  4. Reflow: liquid resin floods back beneath the part. Reflow time scales with viscosity and gap.
  5. Re-settle: plate descends one layer; the cycle repeats.

Failure modes that supports must mitigate in this loop:

  • Peel/suction force on cross-sections — the dominant cause of layer shifts, supports tearing free of the part, and parts ripping off the build plate. Magnitude: see §1.3.
  • Gravity sag of unsupported overhangs — surface normals with components anti-parallel to gravity (cos θ < 0) and unsupported in XY droop into the resin. Length-limited to roughly 1 mm for reliable resin printing without supports (MatterHackers SLA design guide, Mech Ninja “How to print overhangs in SLA”).
  • Layer shift from shear — peel applies in-plane shear; tall narrow features without bracing rotate or tilt.
  • Internal cup vacuum — sealed cavities with a downward opening trap resin; as the plate lifts, the cup forms a vacuum that can rip the part. Lychee, PreForm, and Chitubox all implement explicit suction-cup detection (Lychee Voxels Hollowing 3D / Suction Cup Detector, 2025).
  • Elephant foot / first-layer compression — bottom layers cure thicker than nominal because the plate’s downward stroke compresses uncured resin against the film. Investigations by Mrázek (2022, blog.honzamrazek.cz) show this effect persists 1–2 mm above the build plate; it is usually mitigated by raft + supports rather than by shrinking the first-layer image.
  • Warping / shrinkage during post-cure — most Formlabs engineering resins shrink ~1–3% in green→post-cure transition. Supports stabilize the part during print so this shrinkage occurs uniformly during cure, not asymmetrically in print.

1.2 LFS vs straight-pull mSLA vs tilt-peel

Process Mechanism Peel-force regime Supports needed
Form 2 SLA (legacy) Inverted laser, rigid PDMS-coated tank, straight Z-pull; some tanks tilt by ~7° High; dominated by suction over full cross-section Heavy
Form 3/3+/4 LFS / LFD Light Processing Unit traverses under a flexible tank film; film peels progressively rather than separating uniformly Formlabs reports up to 10× reduction vs Form 2 internal testing (formlabs.com/blog/benefits-of-lfs-3d-printing) Light, “tear-away”
Form 4 LFD (mSLA-class) Flexible Film Resin Tank + microtextured Release Texture that introduces airflow to break suction (formlabs.com guide to SLA, 2024) Lowest in inverted family Lightest; smaller touchpoints, lower density
Phrozen / Anycubic / Elegoo (mSLA, FEP) LCD mask above LED, rigid frame holding FEP under tension High; fully suction-limited Medium-heavy
Phrozen / Anycubic with nFEP, ACF Lower surface energy films, less suction Medium Medium
Tilt-vat (Asiga, some Anycubic) Vat tilts ~3–8° during peel; effective contact line moves across part Lower (peel ~ line force, not areal) Medium-light

The geometric upshot: peel is a line-fracture phenomenon for tilt-peel and LFS, but an areal suction phenomenon for straight-pull mSLA. This affects which loss function the orientation optimizer should use (§6).

1.3 Peel-force magnitudes

The constrained-surface separation force has been studied repeatedly. Liravi et al. (“Investigation of separation force for constrained-surface stereolithography process from mechanics perspective”, Rapid Prototyping Journal, 2017) measured pulling-up force histories with FlexiForce sensors and fit a cohesive-zone model; peak forces for 1–10 cm² test cross-sections fell in the 5–80 N range depending on resin, exposure, and film. Wu et al. (2018, “Effect of Constrained Surface Texturing on Separation Force in Projection Stereolithography”, J. Manuf. Sci. Eng.) modeled separation as combined adhesion + viscous Stefan flow:

For straight-pull from a flat film, the dominant viscous (Stefan) component for a cylindrical part of radius R, gap h(t), plate velocity v = dh/dt:

F_stefan = (3 · π · η · R⁴ · v) / (2 · h³)

with η the resin viscosity (Pa·s). For typical mSLA values (R = 10 mm, η = 0.5 Pa·s, v = 1 mm/s, h = 0.05 mm) this gives F ≈ 188 N — large enough to delaminate small features. The adhesion component for FEP peel depends linearly on perimeter once the meniscus breaks; this is why a part angled at 30°–60° has dramatically lower peel force than a part lying flat, even at equal volume: cross-sectional area per layer drops, and h⁻³ scaling makes that reduction enormous.

Practical magnitudes used internally by slicers and printer firmwares (community-extracted from CTB/PWMS files; Mrázek 2022): ~0.5–5 N for small islands, 5–30 N for full-cross-section organic miniatures, 50–200+ N for solid blocks at 200–2000 mm² cross-section. The first-layer/raft peel often exceeds 100 N which is why Formlabs uses thick rafts and aggressive base-supports.

Resin viscosity baselines (manufacturer SDS / TDS, typical values):

Resin Viscosity (cP @ 25 °C) Source
Formlabs Tough 2000 ~1600 Formlabs SDS 2001308
Formlabs Clear V4/V5 ~850–950 Formlabs MSDS
Formlabs Grey Pro ~1100 Formlabs MSDS
Formlabs Rigid 4000 / 10K ~1500–2400 (glass-filled) Formlabs MSDS
Formlabs Flexible 80A ~2000 Formlabs MSDS
Anycubic standard / ABS-like 200–600 Anycubic store data
Phrozen Aqua / standard 150–350 Phrozen TDS

Higher viscosity → more Stefan suction → heavier supports + slower lift speeds.

1.4 Why parts are angled 30°–60°

Industry consensus (Formlabs PreForm “Orient Selected” defaults; Xometry SLA design guide; MatterHackers “Designing for SLA”; Mech Ninja) is to angle parts 30°–60° from the build plate. Trade-offs:

  • ✓ Minimizes maximum cross-sectional area per layer → quadratic-to-cubic reduction in peel.
  • ✓ Distributes layer lines across surfaces (better visual finish on prominent faces).
  • ✓ Drains uncured resin from cups and pockets.
  • ✓ Reduces aliasing on near-vertical walls (mSLA voxel jaggies appear strongest at <10° from vertical).
  • ✗ Increases total height → more layers, longer print.
  • ✗ Increases support load on critical cosmetic surfaces unless oriented carefully.
  • ✗ Some flat features (e.g., dental occlusal surfaces) deliberately printed flat to avoid layer artifacts (Chitubox Dental guidance).

For LFS the optimum tilt is shallower (often 20°–35°) because the flexible film already eliminates most areal suction; for mSLA with FEP the optimum tilt is steeper (45°–60°).

2. Geometric Analysis of the Bottom Surface

The supported region is the union of (a) per-face downward-facing area below a critical angle threshold, (b) per-layer “islands” with no print material directly below them, and (c) silhouette / leading-edge curves that join the two.

2.1 Per-face overhang angle

Given mesh M = (V, F) and build direction d̂ = +Ẑ, gravity g = −Ẑ, for each triangle f with outward normal nᶠ:

θ_overhang(f) = acos(  -nᶠ · d̂ )            // angle between downward normal and gravity

                                              // 0° = horizontal facing down (worst)

                                              // 90° = vertical

                                              // 180° = facing up (no support needed)

A face is a candidate for support if θ_overhang(f) < θ_critical, i.e., the inward-facing-down dot product (-nᶠ · d̂) > cos(θ_critical). Note: literature often uses the complement (angle from build plate); be consistent.

Recommended critical overhang angle as a function of resin family and layer height (synthesized from MatterHackers, Mech Ninja, Chitubox docs, Formlabs PreForm defaults; Anycubic store; Phrozen XP-Finder methodology):

Resin family Layer thickness Critical angle from horizontal* Notes
Standard / Grey / Clear 50 µm 30°–35° PreForm “Default” preset
Standard / Grey / Clear 100 µm 35°–40° Heavier preset
Tough 2000 / Tough 1500 / Durable 50–100 µm 30°–35° Higher viscosity → more suction
Rigid 4000 / Rigid 10K (glass-filled) 50 µm 25°–30° Lower elongation, brittle in green state, needs tighter density
Castable Wax / Castable Wax 40 25–50 µm 20°–25° Soft green, sags easily
Flexible 80A / Elastic 50A 100 µm 20°–25° Sags severely; densest support recommended
BioMed Clear / Dental LT 50–100 µm 30°–35°

*”Critical” = below this, supports required. Formlabs PreForm internally exposes this as “Slope Multiplier” / “steepest slope at which supports start.” (Formlabs PreForm docs).

2.2 Pseudocode: per-face angle field

function overhangAngleField(geom: BufferGeometry): Float32Array {

  const pos = geom.attributes.position.array;

  const idx = geom.index!.array;

  const out = new Float32Array(idx.length / 3);

  const a = new Vector3(), b = new Vector3(), c = new Vector3(), n = new Vector3();

  for (let t = 0; t < idx.length; t += 3) {

    a.fromArray(pos, 3*idx[t]);

    b.fromArray(pos, 3*idx[t+1]);

    c.fromArray(pos, 3*idx[t+2]);

    n.subVectors(b, a).cross(c.clone().sub(a)).normalize();

    // angle of the downward-facing direction from gravity (-Z):

    out[t/3] = Math.acos(Math.max(-1, Math.min(1, -n.z)));

  }

  return out;  // radians; smaller = more horizontal-facing-down = needier

}

2.3 Island detection per slice

A 2-D approach is faster, more conservative, and matches what every production slicer actually does at slice time. PrusaSlicer 2.9.1 changelog explicitly describes this strategy: “Small islands are supported by a single point placed at their center of mass. Medium islands use the Voronoi diagram to optimally place two or more supports. Large islands are divided into thin and thick sections, where supports are placed either along the central axis or around the perimeter for greater stability” (help.prusa3d.com/article/tree-supports). Chitubox documents “islands = pixels with nothing in the layer right above” (XPIDEMIX, Chitubox island detection). Lychee Slicer’s island detector exposes “Real” mode that operates on actual sliced raster layers.

// Pseudocode: island detection on rasterized slice stack

type Slice = Uint8Array;          // 1 = cured pixel, 0 = empty, indexed [y*w + x]

type Layer = { z: number; raster: Slice; w: number; h: number; };

 

function detectIslands(layers: Layer[]): IslandSet[] {

  const result: IslandSet[] = [];

  for (let i = 0; i < layers.length; i++) {

    const cur = layers[i];

    const below = i === 0 ? null : layers[i-1].raster;

    // Pixel is “island-pixel” if cur=1 AND (no pixel below OR no pixel below within 8-conn neighborhood).

    const island = new Uint8Array(cur.raster.length);

    for (let p = 0; p < cur.raster.length; p++) {

      if (!cur.raster[p]) continue;

      if (!below || !any8connected(below, p, cur.w)) island[p] = 1;

    }

    // Connected-component label island pixels (two-pass classic CCL or union-find).

    const components = labelCC(island, cur.w, cur.h);

    result.push({ z: cur.z, components });

  }

  return result;

}

For each component, classify by area A and perimeter P:

  • A < A_small (e.g., < 0.2 mm²): single touchpoint at centroid.
  • A_small ≤ A < A_medium (e.g., 0.2–10 mm²): two-or-three points by Voronoi or principal axes.
  • A ≥ A_medium: medial-axis sampling along skeleton + perimeter sampling at half-spacing.

2.4 Continuous angularity field on the mesh (the smooth approach)

Per-triangle thresholding gives jagged support patterns and visible “boundary” lines on the part. Better is a smooth scalar field s: V → [0, 1] (“support need”) computed on the mesh, then resampled as a density. Two ingredients:

(i) angular need: α(v) = clamp((θ_critical − θ_overhang(v)) / θ_critical, 0, 1) averaged from incident triangles.

(ii) unsupported-span need: how far below v does the next “supported” surface lie? Cast a ray from v along +Ẑ (recall: gravity = −Ẑ, so to find what is above) and along −Ẑ (what is below); use BVH (three-mesh-bvh) closest-hit. Then convert the unsupported drop distance into a 0–1 risk via a sigmoid centered on layer_thickness × 20.

Smoothing. Use the Heat Method of Crane, Weischedel & Wardetzky (ACM TOG 2013, 2017; cs.cmu.edu/~kmcrane/Projects/HeatMethod) for fast geodesic distance from the union of “definitely needs support” vertices. The heat method is two sparse linear systems:

  1. (M − tLc) u = δ_S       // diffuse heat from sources S for time t
  2. ∇u → -∇u/|∇u| = X       // unit vector field along geodesics
  3. Lc φ = ∇·X              // Poisson recovery of geodesic distance

M = lumped mass matrix, Lc = cotangent Laplacian. Implementations: geometry-central (C++), geogram, CGAL Heat_method_3 (BSD/GPL — study only). For our toolchain, write a TypeScript Laplace solver with a sparse Cholesky (@penrose/eigen or wgpu-matrix for GPU). Or take the simpler Laplacian-smoothing fallback: iterate s ← (1−λ) s + λ · neighbor_avg(s) 5–20 times.

Why smooth field beats per-triangle thresholding: (a) supports cluster naturally where geodesically-distant unsupported regions persist, (b) edge & tip cases are softened, (c) varying density “fades” support count near the threshold rather than producing sharp on/off rings.

2.5 Bridge detection

A bridge is a downfacing region whose perimeter touches two or more “already supported” regions. Algorithm:

For each layer slice:

  1. Compute connected components of overhang pixels.
  2. For each component, dilate by epsilon and intersect with cured pixels in same layer

     that are themselves directly above cured pixels in layer-1.

  1. If ≥2 disjoint contact regions exist, classify as bridge.
  2. Bridge max unsupported span is shorter for resin (<5 mm typical) than FDM.

     If span > L_max(resin), force support.

Bridge handling is more important in FDM than SLA but still matters for thin-walled resin parts where layer cohesion is marginal.

2.6 Recommended density-as-function-of-resin

density(s, profile) = lerp(profile.spacing_max, profile.spacing_min, s)

// where s ∈ [0,1] from the smoothed support-need field

// Light resins (Standard Clear, Grey V5):  spacing_min=2.5, spacing_max=6.0 mm

// Tough/Durable:                            spacing_min=2.0, spacing_max=5.0 mm

// Rigid 4000/10K:                           spacing_min=1.8, spacing_max=4.0 mm

// Castable Wax / Flexible 80A:              spacing_min=1.5, spacing_max=3.5 mm

These ranges align with PreForm’s three presets (“Default”, “Reduced Density”, “Smaller Touchpoints”) and with Lychee’s Light/Medium/Heavy presets.

3. Touch-Point Selection — Where Supports Land

3.1 Algorithm survey

Surveyed approaches:

  1. Density-driven blue-noise (Poisson-disc) sampling on a scalar-field-controlled surface. Used by PreForm V2 (since 2022; “touchpoints up to 50% smaller, placed along edges and corners”), Lychee Slicer (Auto Support 7.5+ “analyzes models in 3D, categorizes different support needs, and applies intelligent strategies for each type”). Theoretical basis: Bowers et al., ACM TOG 2010 (“Parallel Poisson disk sampling with spectrum analysis on surfaces”) and Wei (2008) progressive sample projection; npm poisson-disk-sampling (kchapelier, MIT) handles n-D variable-density.

  2. Centroidal Voronoi tessellation (CVT) on the down-surface. Convergent, more uniform than Poisson-disc, but slower; used in research papers and MeshMixer-derived tools.

  3. Medial-axis-driven placement for islands and elongated overhangs. Zhang et al. 2015 (CAGD, “Medial axis tree—an internal supporting structure for 3D printing”) proposed medial-axis tree internals; PrusaSlicer uses 2D medial axis on large island slices (“Large islands are divided into thin and thick sections, where supports are placed either along the central axis or around the perimeter”) (help.prusa3d.com).

  4. Tree (branching) supports. Vanek, Galicia & Benes 2014 (“Clever Support: Efficient Support Structure Generation for Digital Fabrication”, Comp. Graph. Forum) — top-down greedy minimal-length branching; foundational. Schmidt & Umetani; Hornus, Lefebvre et al. (“Procedural Bridges-and-pillars Support Generation”, Eurographics 2022, CC-BY) — rule-based bridges-and-pillars, MIT-friendly to study. Cura’s tree supports (LGPL — study only) and PrusaSlicer’s “Organic Supports” (AGPL — study only) both descend conceptually from Thomas Rahm’s reimplementation of Vanek-style trees (per help.prusa3d.com Organic supports doc).

  5. Cellular / lattice supports: less common for SLA because removal damages cosmetic surfaces; more common for FDM and metal LPBF.

3.2 Density function

// Returns target Euclidean spacing in mm at point p on the mesh.

// Lower spacing → denser supports.

function density(p: Vector3, ctx: Context): number {

  const a   = ctx.angleNeed[p];          // [0,1] from §2.4 (i)

  const u   = ctx.unsupSpan[p];          // [0,1] from §2.4 (ii)

  const flat = ctx.flatnessAtP(p);       // 1 if locally flat downface, 0 if curved

  const peelLeading = ctx.peelDirIndicator(p);  // for tilt-peel printers, leading edge

  const verticalish = clamp(1 – a, 0, 1);

 

  // Combine; weights tunable per resin profile.

  const need = clamp(0.55*a + 0.30*u + 0.10*flat + 0.05*peelLeading – 0.20*verticalish, 0, 1);

 

  // Convert to spacing using profile-defined extremes.

  return lerp(ctx.profile.spacing_max, ctx.profile.spacing_min, need);

}

3.3 Variable-density surface Poisson-disc

// Pseudocode based on Bridson 2007 + Wei 2008 (progressive sample projection).

function sampleSupportPoints(downSurfacePatches: Patch[], ctx: Context): TouchPoint[] {

  const accepted: TouchPoint[] = [];

  const grid = new SpatialGrid(ctx.bbox, ctx.profile.spacing_min);

  // Seeds: one per island centroid + silhouette curve points (§3.4).

  const queue: Vector3[] = […islandSeeds(ctx), …edgeSeeds(ctx)];

  while (queue.length) {

    const p = queue.shift()!;

    if (!isInsidePatches(p, downSurfacePatches)) continue;

    const r = density(p, ctx);

    if (grid.hasNeighborWithinRadius(p, r)) continue;

    accepted.push({ pos: p, radius: r, normal: normalAt(p) });

    grid.insert(p, r);

    // Generate up to k=30 candidates in annulus [r, 2r] on tangent plane.

    for (let i = 0; i < 30; i++) {

      const cand = sampleAnnulus(p, r, 2*r, normalAt(p));

      // Project onto mesh via BVH closest-point.

      const proj = ctx.bvh.closestPointToPoint(cand);

      if (proj.distance < 0.5*r) queue.push(proj.point);

    }

  }

  return accepted;

}

The three-mesh-bvh closestPointToPoint and bvhcast functions cover the projection step. The SpatialGrid is a uniform-spacing voxel grid keyed by the minimum allowed spacing.

3.4 Silhouette and edge seeding

Surface sampling alone misses thin edges and tips (“ear tips on miniatures”). PreForm’s V2 release notes explicitly call out edge-and-corner placement. To extract silhouette curves:

Compute per-edge dihedral angle between adjacent triangles f1, f2.

An edge is a “silhouette candidate” if:

  (i)  -n_f1 · ẑ > 0  XOR  -n_f2 · ẑ > 0   (one face down, one not), OR

  (ii) dihedral > θ_crease  (e.g., 30°)  AND both faces downward-facing.

For each silhouette polyline, sample at spacing min(density(p), edge_seed_spacing).

Edge seeds are added to the Poisson queue before surface sampling so they are guaranteed to be retained.

3.5 Tip seeding

A “tip” is a vertex v with all incident face normals pointing roughly downward (e.g., minimum angle to −ẑ < 30°) and lying lower than all 1-ring neighbors in z. Every tip gets at least one touchpoint.

3.6 Optimization to minimize per-layer peel

A naïve sampler can place many supports terminating at the same z, yielding a peel-force spike. Post-process:

Bin accepted touchpoints by z (bin width = layer_thickness * N, N=4..16).

For bins with cumulative cross-section area > peel_budget:

  – Stagger: nudge subset of points up or down by ≤ 2 layer_thickness so they cure at different layers.

  – Or merge to a smaller number of larger touchpoints (raises shaft diameter, lowers count).

3.7 Slicer reverse-engineering snapshot

Combined from official docs and observed behavior:

Slicer Touch-point algorithm (best public knowledge) Default density preset Default tip diameter
Formlabs PreForm V2 Smooth-field Poisson sampling + edge/corner seeding; “Bundled Supports” cluster pillars at base for easy removal (formlabs.com/blog/preform-support-menu-updates) Default / Reduced / Smaller Touchpoints (1.0× / 0.6× / 1.5×) 0.40 mm (Default), 0.25 mm (“Smaller / Light Touch”)
Chitubox Basic / Pro Down-face triangle thresholding + grid sampling tuned by “Touch Tip Distance” + auto-orient option (docs.chitubox.com) Light / Medium / Heavy 0.25–0.45 mm
Lychee Slicer 7.5+ New 3D analyzer that classifies overhangs into 4 types and applies different strategies; auto-bracing and parented branches; island detector with 4 accuracy modes including layer-resolved “Real” (lychee.co/whats-new/lychee-slicer-7-5-0) Light / Medium / Heavy + custom 0.30–0.45 mm
PrusaSlicer SLA (Default & Branching) 2D-slice islands → CoM/Voronoi/medial-axis split; analytical (not meshed) support slicing since 3.0.0 (github.com/prusa3d/PrusaSlicer releases) “Support points density” 0–1, default 0.7 head_front 0.4 mm; pillar 1.0 mm
Photon Workshop Conservative rectilinear-tip sampling; less sophisticated than the above (Anycubic; community knowledge) None / Light / Heavy ~0.4 mm

4. Support Geometry — Tips, Bodies, Bases, Connections

4.1 Per-segment geometry

A typical SLA support is composed of:

  1. Tip (contact patch): cone or sphere, diameter d_tip (the surface-marking parameter the user cares about most).
  2. Conic transition: from d_tip up to shaft diameter over a length L_trans (1–3 mm).
  3. Shaft (pillar): cylinder of d_shaft, possibly tapered toward the base.
  4. Base flare: cone widening to d_base where it meets the raft or build plate.

PrusaSlicer’s parameter set (from PrintConfig.cpp, libslic3r/SLA/SupportTreeStrategies):

  • support_head_front_diameter (default 0.4 mm)
  • support_head_penetration (default 0.4 mm — how far the tip sinks into the part)
  • support_head_width (default 1.0 mm — length of conic transition)
  • support_pillar_diameter (default 1.0 mm)
  • support_small_pillar_diameter_percent (default 60%)
  • support_base_diameter, support_base_height (default 4.0 / 1.0 mm)

Recommended ranges (synthesized from PreForm defaults, Chitubox / Lychee presets, and PrusaSlicer):

Parameter Range Default for Standard Default for Tough/Rigid Notes
Tip diameter d_tip 0.20 – 0.80 mm 0.40 mm 0.50 mm PreForm “Smaller Touchpoints” 0.25; “Default” 0.40 (cimquest-inc.com)
Tip contact length / penetration 0.20 – 0.50 mm 0.40 mm 0.40 mm
Cone-transition length 1.0 – 3.0 mm 1.5 mm 1.5 mm
Shaft diameter d_shaft 0.6 – 1.4 mm 1.0 mm 1.2 mm Scale by load: d_shaft = max(0.6, 0.05 √F_load)
Base flare diameter 3.0 – 6.0 mm 4.0 mm 4.5 mm
Min vertical clearance from part 1.0 – 3.0 mm 2.0 mm 2.5 mm
Raft thickness 1.0 – 2.5 mm 1.5 mm 2.0 mm PreForm “Height above the base” min 2 mm, default 5 mm
Cross-brace spacing 8 – 20 mm 12 mm 10 mm tall thin shafts only

4.2 Tip styles

Tip Surface mark Removal ease Strength Recommended for
Cone (sharp) Smallest Easiest (snaps off) Lowest Standard, Clear, Grey, light overhangs
Ball (sphere) Slightly more Easy Medium General miniatures
Needle (very thin cone, d ≤ 0.2 mm) Minimal Hardest (may break in part) Lowest Cosmetic surfaces only
Custom truncated-pyramid Moderate Moderate Highest High-load Rigid / Castable parts

PreForm “Light Touch” (≈0.25 mm cone) is the de facto reference for “no visible mark” supports; use it as the lower bound and warn the user if they go thinner.

4.3 Shaft load sizing

Approximate combined buckling + shear failure check. For an unbraced shaft of length L, diameter d, modulus E (green-state, ~0.7 × post-cured E), expected vertical load F_v (peel + part weight share):

F_buckle = π² · E · I / (K · L)²        // I = π d⁴ / 64; K=2 for cantilever-like fixed-free

Required d  ≥  ( 64 · K² · L² · F_v / (π³ · E) )^(1/4) · safety_factor

safety_factor ≈ 3 in green state. At F_v = 5 N, L = 20 mm, E_green = 1 GPa → d ≥ 0.7 mm. Add cross-bracing if L > 25 mm or unbraced segment fails the check.

4.4 Tree / branching supports for SLA

Vanek 2014 method, adapted for SLA:

INPUT: set of touch points T = {(p_i, r_i, n_i)}, build plate plane B

OUTPUT: tree mesh

 

  1. Sort T by descending z.
  2. Initialize node set N = T (each as a leaf).
  3. While |N| > 1:

     pick two nodes a, b ∈ N with smallest “merge cost”:

        cost(a,b) = α · dist(a,b) + β · max_z(a,b) – β · z_meet(a,b) + γ · collisionPenalty

     compute meeting point m at z_meet by sliding both branches downward at angle θ_branch

        from vertical (θ_branch ≤ θ_max ≈ 40°).

     if collision-free (BVH ray check vs part), replace a,b in N with m.

     else: keep them separate; lower one until it can route around.

  1. Continue until all nodes meet build plate or a “trunk” budget is reached.
  2. Sweep cylinders along edges; CSG-union via manifold-3d.

When branching beats straight columns: many nearby touchpoints (within ~6 mm), tall prints (>30 mm), and parts with cosmetic surfaces below — branches consolidate visible scarring at fewer base attachments.

When straight columns win: very dense touchpoints on flat downfaces (branching offers little length saving), or when downstream slicer must reslice supports — straight cylinders are easier to interpret.

4.5 Cross-bracing rules

For two adjacent shafts i, j with dist(i,j) < 3 · d_shaft + 4 mm, both L > 25 mm, add horizontal struts at z values where neither shaft is yet at its tip — typical strut diameter 0.6 × d_shaft, spacing 8–20 mm in z. Lychee exposes this as “Auto Bracing” with explicit max-distance and max-strut-diameter parameters (docs.mango3d.io/doc/resin-documentation/resin-preferences/configuration-algorithms).

4.6 Raft

Raft variants:

  • None (PreForm “no base”): fastest, but base supports often fail at peel.
  • Thin pad raft (PreForm default, Chitubox “Plate”): 1.5–2.0 mm thick, perimeter offset 2 mm beyond outermost support footprint.
  • Full raft (PrusaSlicer SL1): thicker (2.0–2.5 mm), with internal infill for stability.

Recommend default raft 1.5 mm, perimeter offset 2 mm, with corner chamfer for elephant-foot tolerance. If Castable Wax or Flexible 80A, bump to 2.0 mm.

5. Resin Profiles — Formlabs-Focused Starter Database

5.1 Schema (TypeScript)

// SupportEngine/src/profiles/ResinProfile.ts

export type ResinFamily =

  | ‘standard’ | ‘tough’ | ‘durable’ | ‘rigid’

  | ‘flexible’ | ‘elastic’ | ‘castable’ | ‘biomed’

  | ‘dental’ | ‘ceramic’ | ‘engineering’ | ‘high-temp’;

 

export interface ResinProfile {

  /** Stable identifier; e.g. “formlabs/clear-v5” */

  id: string;

  name: string;

  vendor: string;

  family: ResinFamily;

 

  /** Mechanical (post-cured, manufacturer TDS) */

  youngs_modulus_MPa: number;        // tensile modulus

  ultimate_tensile_MPa: number;

  elongation_break_pct: number;

  flexural_modulus_MPa?: number;

  notched_izod_J_per_m?: number;

  hdt_045MPa_C?: number;             // heat deflection 0.45 MPa

  green_modulus_ratio?: number;      // green / post-cured E; default 0.6

 

  /** Process */

  viscosity_cP_25C: number;

  shrinkage_pct_linear: number;      // typical 1–3%

  color: ‘clear’ | ‘translucent’ | ‘grey’ | ‘black’ | ‘white’ | ‘amber’ | ‘tan’ | string;

  layer_height_min_mm: number;

  layer_height_max_mm: number;

  layer_height_default_mm: number;

 

  /** Support recommendations driven by the engine */

  support: {

    critical_overhang_deg: number;   // from horizontal; below = supported

    /** Spacing for variable-density Poisson sampling (mm). */

    spacing_min_mm: number;          // densest cluster

    spacing_max_mm: number;          // sparsest cluster

    tip_diameter_mm: number;         // contact tip

    tip_diameter_min_mm: number;

    tip_diameter_max_mm: number;

    shaft_diameter_mm: number;

    base_diameter_mm: number;

    raft_thickness_mm: number;

    /** Heavy/Medium/Light multiplier on density for one-click presets. */

    preset_multiplier: { light: number; medium: number; heavy: number };

    bracing_min_unbraced_mm: number;

  };

 

  /** Printer/film compatibility */

  recommended_printers?: string[];   // ‘form-3’, ‘form-4’, ‘phrozen-sonic-mighty-8k’, …

  film_compatibility?: (‘FEP’|’nFEP’|’ACF’|’LFS-flex’)[];

 

  /** Provenance */

  source: { tds_url?: string; sds_url?: string; retrieved: string };

}

5.2 Starter profiles (10)

Values below come from the cited TDS/SDS PDFs. Where Formlabs publishes only post-cured numbers (the typical case), green_modulus_ratio is set to 0.6 as a conservative default. shrinkage_pct_linear follows Formlabs’ ~1–2.5% green→cured guidance for engineering resins. Spacings/tips reflect PreForm V2 conventions plus Cimquest’s published reference settings (cimquest-inc.com/preform-orienting-supports).

export const FORMLABS_PROFILES: ResinProfile[] = [

  {

    id: ‘formlabs/clear-v5’,

    name: ‘Clear V5’,

    vendor: ‘Formlabs’, family: ‘standard’,

    youngs_modulus_MPa: 2750, ultimate_tensile_MPa: 60, elongation_break_pct: 8,

    flexural_modulus_MPa: 2700, notched_izod_J_per_m: 29, hdt_045MPa_C: 74,

    viscosity_cP_25C: 900, shrinkage_pct_linear: 1.8, color: ‘clear’,

    layer_height_min_mm: 0.025, layer_height_max_mm: 0.10, layer_height_default_mm: 0.05,

    support: {

      critical_overhang_deg: 35, spacing_min_mm: 2.5, spacing_max_mm: 6.0,

      tip_diameter_mm: 0.40, tip_diameter_min_mm: 0.25, tip_diameter_max_mm: 0.60,

      shaft_diameter_mm: 1.0, base_diameter_mm: 4.0, raft_thickness_mm: 1.5,

      preset_multiplier: { light: 0.6, medium: 1.0, heavy: 1.5 },

      bracing_min_unbraced_mm: 25,

    },

    recommended_printers: [‘form-4′,’form-4l’], film_compatibility: [‘LFS-flex’],

    source: { tds_url: ‘formlabs.com/tds/clear-resin-v5-tds/’, retrieved: ‘2026-05-03’ },

  },

  {

    id: ‘formlabs/grey-pro’,

    name: ‘Grey Pro’,

    vendor: ‘Formlabs’, family: ‘standard’,

    youngs_modulus_MPa: 2600, ultimate_tensile_MPa: 61, elongation_break_pct: 13,

    flexural_modulus_MPa: 2200, notched_izod_J_per_m: 19, hdt_045MPa_C: 78,

    viscosity_cP_25C: 1100, shrinkage_pct_linear: 2.0, color: ‘grey’,

    layer_height_min_mm: 0.05, layer_height_max_mm: 0.10, layer_height_default_mm: 0.05,

    support: {

      critical_overhang_deg: 33, spacing_min_mm: 2.3, spacing_max_mm: 5.5,

      tip_diameter_mm: 0.45, tip_diameter_min_mm: 0.30, tip_diameter_max_mm: 0.60,

      shaft_diameter_mm: 1.0, base_diameter_mm: 4.0, raft_thickness_mm: 1.5,

      preset_multiplier: { light: 0.6, medium: 1.0, heavy: 1.5 },

      bracing_min_unbraced_mm: 22,

    },

    recommended_printers: [‘form-2′,’form-3′,’form-3+’],

    source: { tds_url: ‘formlabs-media.formlabs.com/datasheets/1801086-TDS-ENUS-0P.pdf’, retrieved: ‘2026-05-03’ },

  },

  {

    id: ‘formlabs/tough-2000’,

    name: ‘Tough 2000 V2’,

    vendor: ‘Formlabs’, family: ‘tough’,

    youngs_modulus_MPa: 2000, ultimate_tensile_MPa: 46, elongation_break_pct: 79,

    flexural_modulus_MPa: 1900, hdt_045MPa_C: 70, notched_izod_J_per_m: 40,

    viscosity_cP_25C: 1600, shrinkage_pct_linear: 1.8, color: ‘grey’,

    layer_height_min_mm: 0.05, layer_height_max_mm: 0.10, layer_height_default_mm: 0.10,

    support: {

      critical_overhang_deg: 32, spacing_min_mm: 2.0, spacing_max_mm: 5.0,

      tip_diameter_mm: 0.50, tip_diameter_min_mm: 0.35, tip_diameter_max_mm: 0.70,

      shaft_diameter_mm: 1.1, base_diameter_mm: 4.5, raft_thickness_mm: 2.0,

      preset_multiplier: { light: 0.7, medium: 1.0, heavy: 1.4 },

      bracing_min_unbraced_mm: 22,

    },

    recommended_printers: [‘form-3′,’form-3+’,’form-4′,’form-4l’],

    source: { tds_url: ‘formlabs-media.formlabs.com/datasheets/251013-MS-TDS-Tough_2000_V2.pdf’, retrieved: ‘2026-05-03’ },

  },

  {

    id: ‘formlabs/tough-1500’,

    name: ‘Tough 1500’,

    vendor: ‘Formlabs’, family: ‘tough’,

    youngs_modulus_MPa: 1500, ultimate_tensile_MPa: 33, elongation_break_pct: 51,

    viscosity_cP_25C: 1400, shrinkage_pct_linear: 1.8, color: ‘grey’,

    layer_height_min_mm: 0.05, layer_height_max_mm: 0.10, layer_height_default_mm: 0.10,

    support: {

      critical_overhang_deg: 32, spacing_min_mm: 2.1, spacing_max_mm: 5.2,

      tip_diameter_mm: 0.50, tip_diameter_min_mm: 0.35, tip_diameter_max_mm: 0.70,

      shaft_diameter_mm: 1.1, base_diameter_mm: 4.5, raft_thickness_mm: 2.0,

      preset_multiplier: { light: 0.7, medium: 1.0, heavy: 1.4 },

      bracing_min_unbraced_mm: 22,

    },

    source: { retrieved: ‘2026-05-03’ },

  },

  {

    id: ‘formlabs/durable’,

    name: ‘Durable V2’,

    vendor: ‘Formlabs’, family: ‘durable’,

    youngs_modulus_MPa: 1260, ultimate_tensile_MPa: 31.8, elongation_break_pct: 49,

    notched_izod_J_per_m: 109, viscosity_cP_25C: 1100, shrinkage_pct_linear: 1.7,

    color: ‘translucent’,

    layer_height_min_mm: 0.05, layer_height_max_mm: 0.10, layer_height_default_mm: 0.10,

    support: {

      critical_overhang_deg: 30, spacing_min_mm: 1.9, spacing_max_mm: 4.8,

      tip_diameter_mm: 0.55, tip_diameter_min_mm: 0.40, tip_diameter_max_mm: 0.70,

      shaft_diameter_mm: 1.1, base_diameter_mm: 4.5, raft_thickness_mm: 2.0,

      preset_multiplier: { light: 0.7, medium: 1.0, heavy: 1.4 },

      bracing_min_unbraced_mm: 22,

    },

    source: { retrieved: ‘2026-05-03’ },

  },

  {

    id: ‘formlabs/rigid-4000’,

    name: ‘Rigid 4000’,

    vendor: ‘Formlabs’, family: ‘rigid’,

    youngs_modulus_MPa: 4100, ultimate_tensile_MPa: 69, elongation_break_pct: 5.3,

    flexural_modulus_MPa: 3400, notched_izod_J_per_m: 23, hdt_045MPa_C: 77,

    viscosity_cP_25C: 1800, shrinkage_pct_linear: 1.5, color: ‘white’,

    layer_height_min_mm: 0.05, layer_height_max_mm: 0.10, layer_height_default_mm: 0.10,

    support: {

      critical_overhang_deg: 28, spacing_min_mm: 1.8, spacing_max_mm: 4.5,

      tip_diameter_mm: 0.55, tip_diameter_min_mm: 0.40, tip_diameter_max_mm: 0.70,

      shaft_diameter_mm: 1.2, base_diameter_mm: 5.0, raft_thickness_mm: 2.0,

      preset_multiplier: { light: 0.75, medium: 1.0, heavy: 1.35 },

      bracing_min_unbraced_mm: 18,

    },

    recommended_printers: [‘form-3′,’form-4’],

    source: { tds_url: ‘formlabs-media.formlabs.com/datasheets/1801088-TDS-ENUS-0.pdf’, retrieved: ‘2026-05-03’ },

  },

  {

    id: ‘formlabs/rigid-10k’,

    name: ‘Rigid 10K’,

    vendor: ‘Formlabs’, family: ‘rigid’,

    youngs_modulus_MPa: 10000, ultimate_tensile_MPa: 65, elongation_break_pct: 1.8,

    flexural_modulus_MPa: 9500, hdt_045MPa_C: 218,

    viscosity_cP_25C: 2400, shrinkage_pct_linear: 1.0, color: ‘white’,

    layer_height_min_mm: 0.05, layer_height_max_mm: 0.10, layer_height_default_mm: 0.10,

    support: {

      critical_overhang_deg: 25, spacing_min_mm: 1.6, spacing_max_mm: 4.0,

      tip_diameter_mm: 0.55, tip_diameter_min_mm: 0.40, tip_diameter_max_mm: 0.65,

      shaft_diameter_mm: 1.3, base_diameter_mm: 5.0, raft_thickness_mm: 2.2,

      preset_multiplier: { light: 0.8, medium: 1.0, heavy: 1.3 },

      bracing_min_unbraced_mm: 15,

    },

    recommended_printers: [‘form-3′,’form-4’],

    source: { tds_url: ‘formlabs-media.formlabs.com/datasheets/2001479-TDS-ENUS-0.pdf’, retrieved: ‘2026-05-03’ },

  },

  {

    id: ‘formlabs/flexible-80a’,

    name: ‘Flexible 80A’,

    vendor: ‘Formlabs’, family: ‘flexible’,

    youngs_modulus_MPa: 6.0,        // shore 80A; effective E ~6 MPa

    ultimate_tensile_MPa: 8.9, elongation_break_pct: 120,

    viscosity_cP_25C: 2000, shrinkage_pct_linear: 2.5, color: ‘translucent’,

    layer_height_min_mm: 0.10, layer_height_max_mm: 0.10, layer_height_default_mm: 0.10,

    support: {

      critical_overhang_deg: 22, spacing_min_mm: 1.5, spacing_max_mm: 3.5,

      tip_diameter_mm: 0.60, tip_diameter_min_mm: 0.45, tip_diameter_max_mm: 0.80,

      shaft_diameter_mm: 1.2, base_diameter_mm: 5.0, raft_thickness_mm: 2.0,

      preset_multiplier: { light: 0.9, medium: 1.0, heavy: 1.25 },

      bracing_min_unbraced_mm: 12,

    },

    source: { tds_url: ‘formlabs-media.formlabs.com/datasheets/2001418-TDS-ENUS-0.pdf’, retrieved: ‘2026-05-03’ },

  },

  {

    id: ‘formlabs/castable-wax’,

    name: ‘Castable Wax’,

    vendor: ‘Formlabs’, family: ‘castable’,

    youngs_modulus_MPa: 290, ultimate_tensile_MPa: 7.6, elongation_break_pct: 7,

    viscosity_cP_25C: 950, shrinkage_pct_linear: 2.2, color: ‘amber’,

    layer_height_min_mm: 0.025, layer_height_max_mm: 0.05, layer_height_default_mm: 0.025,

    support: {

      critical_overhang_deg: 22, spacing_min_mm: 1.5, spacing_max_mm: 3.5,

      tip_diameter_mm: 0.30, tip_diameter_min_mm: 0.20, tip_diameter_max_mm: 0.45,

      shaft_diameter_mm: 0.9, base_diameter_mm: 4.0, raft_thickness_mm: 1.5,

      preset_multiplier: { light: 0.85, medium: 1.0, heavy: 1.3 },

      bracing_min_unbraced_mm: 14,

    },

    source: { tds_url: ‘formlabs-media.formlabs.com/datasheets/Castable_Wax_Technical_Data_Sheet.pdf’, retrieved: ‘2026-05-03’ },

  },

  {

    id: ‘formlabs/biomed-clear’,

    name: ‘BioMed Clear’,

    vendor: ‘Formlabs’, family: ‘biomed’,

    youngs_modulus_MPa: 2080, ultimate_tensile_MPa: 52, elongation_break_pct: 12,

    hdt_045MPa_C: 75, viscosity_cP_25C: 1300, shrinkage_pct_linear: 1.8,

    color: ‘clear’,

    layer_height_min_mm: 0.10, layer_height_max_mm: 0.10, layer_height_default_mm: 0.10,

    support: {

      critical_overhang_deg: 32, spacing_min_mm: 2.2, spacing_max_mm: 5.0,

      tip_diameter_mm: 0.45, tip_diameter_min_mm: 0.35, tip_diameter_max_mm: 0.60,

      shaft_diameter_mm: 1.0, base_diameter_mm: 4.5, raft_thickness_mm: 1.5,

      preset_multiplier: { light: 0.7, medium: 1.0, heavy: 1.4 },

      bracing_min_unbraced_mm: 22,

    },

    recommended_printers: [‘form-3b’,’form-3b+’,’form-4b’],

    source: { tds_url: ‘formlabs-media.formlabs.com/datasheets/2001432-TDS-ENUS-0.pdf’, retrieved: ‘2026-05-03’ },

  },

];

The Tough 2000 V2 numbers shown reflect the V2 TDS released for Form 4 (formlabs.com/store/materials/tough-2000-resin); legacy V1 had E=2,000 MPa, σ_UTS≈46 MPa. Two values for Tough 2000 exist in market literature; the V2 numbers are most current as of late 2025.

5.3 Effects of temperature, build plate, and film type

  • Temperature. Anycubic and Phrozen TDSs both call out >25 °C as ideal, recommending +30% exposure and −30% lift speed below 20 °C (store.anycubic.com/blogs/news/resin-settings-for-anycubic-3d-printers). Lower temperature → higher viscosity → higher peel; the engine should let users set a “cold environment” toggle that scales spacing_min down by 10% and tip_diameter up by 10%.

  • Build plate. Formlabs Build Platform Flex (flexible plate, since Form 4) reduces base-removal force, but supports must adhere strongly enough not to pop during print. Default raft thickness is fine; aggressive Light presets should be disabled for flexible plates on the first print.

  • Film type. FEP (most desktop mSLA) is the highest-friction; nFEP (newer Phrozen, Anycubic) lowers stick by ~30%; ACF (Anycubic ACF, Phrozen HDF) lowers further; LFS flexible film is in a different regime entirely. The engine’s density(p) should accept a film_factor ∈ [0.7, 1.3] that scales spacings.

6. Auto-Orientation

6.1 Objective function

The orientation problem is: find rotation R ∈ SO(3) (typically a unit quaternion q) that minimizes a weighted multi-objective:

J(q) =  w₁ · V_support(q)

      + w₂ · A_cosmetic_supported(q)        // surface area of “designated cosmetic” patches that get touched

      + w₃ · ∫ A_cross(z; q) dz · η_film    // proxy for total peel force

      + w₄ · max_z(q)                        // total height

      + w₅ · N_cups(q)                       // number of trapped resin cups

      + w₆ · A_first_layer(q)                // first-layer cross-section (peel/elephant-foot)

with η_film a per-printer factor: 1.0 for FEP straight-pull, 0.5 for LFS, 0.7 for tilt-peel.

PreForm “One-Click Print” uses essentially this objective with weights tuned by Formlabs internally; Lychee’s Magic Mode and Chitubox auto-orient are simpler height + downface-area minimizers.

Theoretical references:

  • Ezair, Massarwi & Elber (2015), “Orientation analysis of 3D objects toward minimal support volume in 3D-printing”, Computers & Graphics — proves V_support(q) is continuous but non-smooth in q; provides a GPU-friendly evaluator using depth-peeling.
  • Guacheta-Alba et al. (2022), “3D printing part orientation optimization: discrete approximation of support volume”, IJECE — compares 5 mono-objective optimizers (random walk, hill climbing, simulated annealing, GA, evolution strategy) on support-volume metric.

6.2 Search strategies

Strategy Quality Runtime (100K-tri mesh, integrated GPU) Notes
Brute-force quaternion sampling (Fibonacci sphere of build directions × angle steps) Good with N ≥ 256 directions 5–20 s in JS; ~1 s with WebGL depth-peel Easy to parallelize; what we recommend by default
Simulated annealing on q Better local minima than gradient descent 3–10 s Use after coarse Fibonacci seed
Gradient descent Fast but stuck in local minima 1–3 s Need smoothed objective
Evolutionary / CMA-ES Best quality 30–120 s Overkill for desktop tool

Our recommendation: Fibonacci-256 coarse sweep + 50-iteration simulated-annealing refinement runs in 1–3 s using BVH-accelerated overhang-angle queries, fits the sub-2-second budget for 100K triangles, and is deterministic enough for “always-the-same-answer” enterprise UX.

6.3 Pseudocode

function autoOrient(mesh: BufferGeometry, profile: ResinProfile, weights: Weights): Quaternion {

  const dirs = fibonacciDirections(256);                 // unit ẑ candidates

  const evals = dirs.map(d => evalOrientation(mesh, d, profile, weights));

  const best = evals.reduce((a,b) => a.J < b.J ? a : b);

  // Refine

  let q = best.q, J = best.J;

  for (let i = 0; i < 50; i++) {

    const T = 1.0 * Math.pow(0.95, i);                   // cooling

    const qPert = perturbQuat(q, deg(5));

    const Jp = evalOrientationByQ(mesh, qPert, profile, weights).J;

    if (Jp < J || Math.random() < Math.exp((J-Jp)/T)) { q = qPert; J = Jp; }

  }

  return q;

}

 

function evalOrientation(mesh, dir, profile, w): { q: Quat; J: number } {

  const q = quatFromUpDir(dir);

  // Cheap proxies, not actual support generation:

  const angles = overhangAngleField(rotated(mesh, q));

  const downAreaByAngle = histogramDownArea(mesh, angles);

  const V_supp_proxy = ∫ downAreaByAngle · weight(angle – θ_crit) · max(0, drop_dist);

  const h = boundsHeight(mesh, q);

  const A_first = areaOfLowestSlice(mesh, q);

  const cups = detectCups(mesh, q);                       // §6.4

  const A_cross_int = integrateCrossSection(mesh, q, layer_thickness);

  const A_cosmetic_supp = cosmeticSupportedArea(mesh, q); // requires user-tagged cosmetic faces

  return { q,

    J: w.support * V_supp_proxy +

       w.cosmetic * A_cosmetic_supp +

       w.peel * A_cross_int * profile.film_factor +

       w.height * h +

       w.cups * cups.length +

       w.firstLayer * A_first };

}

6.4 Trapped-resin cup detection

A “cup” is a connected interior void open downward (toward −Ẑ) and closed upward. Detect by:

  1. Voxelize mesh interior (or use a slice stack).
  2. From each interior voxel, flood-fill in +Ẑ direction; if it never escapes the mesh

   (i.e., reaches the +Ẑ bounding-box face without exiting through a hole), mark as “trapped”.

  1. Connected components of trapped voxels = cups.
  2. For each cup, optionally suggest drainage hole at the lowest point of its boundary.

Lychee’s Suction Cup Detector and Voxels Hollowing 3D (lychee.co/whats-new/new-feature-voxels-hollowing-3-d) implement this voxel-cavity approach; PreForm flags “internal hollows or enclosed volumes” with a warning rather than autofixing.

For our toolchain: a 256³ voxel grid through manifold-3d‘s level-set or a custom GPU rasterizer takes 100–300 ms for a 100K-tri mesh.

6.5 Single user slider

Expose one “Quality ↔ Speed” slider that linearly mixes weight sets. Default slider position = 0.5 = “Balanced”. Power users can unlock per-objective weights.

Quality (slider=0):   {support: 1.5, cosmetic: 3.0, peel: 1.0, height: 0.2, cups: 5.0, firstLayer: 0.5}

Balanced (0.5):       {support: 1.0, cosmetic: 1.5, peel: 1.0, height: 0.5, cups: 3.0, firstLayer: 0.7}

Speed (1.0):          {support: 0.7, cosmetic: 0.5, peel: 1.5, height: 1.5, cups: 2.0, firstLayer: 1.0}

7. Slicer Interoperability

7.1 Format matrix

Format Carries supports Carries materials Carries print settings Where it’s accepted
STL (binary or ASCII) As geometry only (single mesh) No No Universal; required by most resin slicers
OBJ As geometry only; can carry separate groups No No Most slicers
3MF Yes (multi-component); custom extensions vary Yes (per-object) Yes (slicer-specific) PrusaSlicer, Cura, Bambu, Lychee, Chitubox limited
CTB / GOO / PWMS / PWMA / etc. Implicitly (already sliced raster) No Yes (printer-specific) Specific printers; UVTools converts between many

7.2 Single mesh vs separate bodies

Approach Pros Cons
Part + supports unioned into one watertight STL Universally accepted; downstream slicer cannot regenerate or edit; deterministic Loses semantic distinction; downstream hollowing tools may miss internal cavities; user can’t edit supports without re-running our tool
Part STL + Supports STL (two files) Some slicers (Chitubox, Lychee) accept “support” tagging on second body; can adjust exposure separately Two-file workflow; slicer may still try to add its own supports; the user has to import both
3MF with named bodies Best of both; per-body metadata; supported by Bambu/Prusa/Cura ecosystems and partially by Chitubox Cross-slicer 3MF compatibility is uneven (Bambu inserts custom extensions Cura may reject)

Our recommendation. Default export = 3MF with two named bodies (part, supports), each as separate <object> in the 3dmodel.model with production:UUID for traceability. Provide three export buttons:

  • “STL (one piece)”manifold-3d union of part and all support cylinders, fall-through compatibility with everything.
  • “STL (two files)” — for users targeting Chitubox/Lychee who want to keep our supports but still slice in their tool.
  • “3MF (recommended)” — multi-body, with a custom <metadata name=”ourtool:body-type”>supports</metadata> and color tag (#FF8800 for supports). PrusaSlicer and Bambu read these; Lychee and Chitubox treat them as separate selectable bodies.

7.3 Disabling slicer auto-supports

If you ship single-STL, the downstream slicer normally auto-adds supports if it sees overhangs. Approaches that work in practice:

  • Chitubox: instruct the user to set “Add platform” off and “Generate auto support” off after import. Our README/onboarding should document this; better, pre-fill a .chitubox project file when possible.
  • Lychee: import → support tab → ensure “Auto Supports” not pressed; supports we provide are recognized as part of the geometry.
  • PreForm: PreForm only respects pre-existing geometry; it will mark as “undersupported” anything our tool didn’t cover, but it won’t add. The user has to right-click → “Don’t add supports”.
  • PrusaSlicer SLA: set support_enable=false in the loaded profile. 3MF can carry this directly.

7.4 Exposure tagging for support tips

Best-in-class print quality reduces tip exposure (~70% of body exposure) so tips cure thinner and snap easier. Mechanisms:

  • 3MF custom metadata — namespaced extension <metadata name=”ourtool:exposure-multiplier”>0.7</metadata> per body. Honored by tools that round-trip our format; ignored otherwise (no harm).
  • Color tags — Chitubox can map specific colors to per-region exposure if “Multi-Resin” workflows are configured; rare in practice.
  • Separate body convention — works with UVTools post-processing where a user can intentionally apply per-pixel exposure adjustment based on which mesh painted that pixel. Document this for advanced users.

Most users will never need this; default export uses uniform exposure.

8. Manual Editing UX

8.1 What established tools expose

  • PreForm. Click on red “undersupported” gradient to add touchpoint; click on existing touchpoint to delete; per-touchpoint “Edit” panel. No drag-on-surface tool. Generate-all + Edit-all workflow (support.formlabs.com).
  • Chitubox. Click-to-add, click-to-delete, mid-click tip drag, edge selection for “add support along edge” (docs.chitubox.com).
  • Lychee. All of the above + Projection Supports (paint-region → grow tips from projection light direction → auto-route bases) + Inline Supports (bracing between two existing supports) + Island Detector with 4 levels. Every support is a selectable object with override parameters (docs.mango3d.io).
  • Photon Workshop. Click-to-add, basic delete; less polished than the above.

8.2 Visualizing coverage gaps

Three overlays to provide:

  1. Red-gradient heatmap of unsupported overhangs (PreForm convention). Map s ∈ [0,1] from §2.4 to red intensity; threshold at 0.4.
  2. Green checkmarks at supported regions; small white dots at touchpoint origins.
  3. Peel-force-per-layer chart as a Z-axis line chart, showing predicted force magnitude per layer in N. Spikes ≥ 30 N flagged.

// React component sketch

<HeatmapOverlay scalarField={ctx.supportNeed} mesh={part}

  colorRamp={[‘transparent’,’#ff8800′,’#ff0000′]} threshold={0.4}/>

<TouchpointMarkers points={ctx.touchpoints} selectable onSelect={editTip}/>

<PeelForceChart layers={ctx.predictedPeel} threshold={30}/>

8.3 Iteration loop

auto-generate → user reviews red gaps → user clicks to add or remove tips

            → engine recomputes only affected supports (incremental)

            → re-render in <300 ms

            → export when satisfied

Incremental update means: a single click adds one touchpoint, runs collision-check, regenerates the local branch (or single column), and CSG-unions only that subtree. No full regen.

9. Existing Open-Source / Reference Implementations

9.1 Project survey

Project License Language Algorithmic approach Borrowable directly?
PrusaSlicer SLA (src/libslic3r/SLA/) AGPL-3.0+ C++ Per-slice 2D islands → CoM/Voronoi/medial-axis; analytical conic-section support slicing (since 3.0) STUDY ONLY — algorithms are well-documented in code; do not copy/paste
Cura tree supports (CuraEngine) LGPL-3.0 (engine) / AGPL (UI) C++ Tree algorithm by Thomas Rahm; FDM-focused but applicable conceptually STUDY ONLY
PrusaSlicer “Organic” supports AGPL-3.0+ C++ Improved tree algorithm based on Rahm’s STUDY ONLY
UVTools (sn4k3) AGPL-3.0 C# Post-slice analysis: island detection, suction-cup detection, file-format conversion (CTB/GOO/PWMS/many others). 100% acceptable as a reference for what to detect; archive of file-format quirks (github.com/sn4k3/UVtools) STUDY ONLY for support algorithms; usable directly as documentation of binary slicer formats (most format docs in repo are MIT/CC)
NanoSupport / NanoDLP Commercial (free for non-commercial use) JS / Go Web-based slicer with “advanced” auto-supports; closed source Reference only
Mango3D / Lychee Commercial closed-source C++/QML Industry-leading SLA support generator Behavioral reference only
CGAL Heat_method_3 GPL/commercial C++ Crane heat method for geodesics GPL — study only for the math; reimplement in TS
manifold-3d Apache-2.0 C++→WASM TS Robust mesh boolean Use directly
three-mesh-bvh MIT TypeScript BVH raycast/closest-point Use directly
poisson-disk-sampling (kchapelier) MIT JS n-D variable-density Poisson Use directly
@bitbybit-dev/manifold-webworker MIT TS Manifold-3d in a Web Worker Use directly

9.2 Foundational papers (last ~12 years)

  • Vanek, Galicia & Benes, 2014“Clever Support: Efficient Support Structure Generation for Digital Fabrication”, Comp. Graph. Forum. The original tree-support paper; CSI: top-down greedy minimum-length descent. Reduces material 13.9–49.5%.
  • Schmidt & Umetani, 2014 — design-time support feedback in Meshmixer.
  • Hornus, Lefebvre, Dumas, Claux et al., 2018, 2020, 2022 — series on bridges-and-pillars (Eurographics 2022, “Procedural Bridges-and-pillars Support Generation”, CC-BY 4.0; freely usable reference).
  • Zhang et al., 2015, CAGD“Medial axis tree—an internal supporting structure for 3D printing”, internal supports based on medial axis.
  • Crane, Weischedel & Wardetzky, 2013/2017, ACM TOG“Geodesics in heat: a new approach to computing distance based on heat flow”.
  • Bowers, Wang, Wei, Maletz, 2010, ACM TOG“Parallel Poisson disk sampling with spectrum analysis on surfaces”.
  • Liravi et al., 2017, Rapid Prototyping J.“Investigation of separation force for constrained-surface stereolithography process from mechanics perspective”; the cohesive-zone separation-force model.
  • Wu, Liravi et al., 2018, ASME JMSE“Effect of Constrained Surface Texturing on Separation Force in Projection Stereolithography”; analytical Stefan-flow + adhesion model.
  • Ezair, Massarwi, Elber, 2015, Computers & Graphics“Orientation analysis of 3D objects toward minimal support volume”.
  • Mrázek 2022/2023, blog.honzamrazek.cz — community-grade engineering blog on continuous-print films, elephant-foot causes; not peer-reviewed but excellent first-principles measurements.

10. Implementation Notes for Three.js / TypeScript

10.1 Library stack

electron + react + typescript + three (r155+)

├── three-mesh-bvh (MIT) …………… overhang queries, closest-point, ray-mesh

├── manifold-3d (Apache-2.0, WASM) ….. CSG union of N support cylinders, hollowing

├── @bitbybit-dev/manifold-webworker …. wraps manifold-3d in a Worker (or roll our own)

├── poisson-disk-sampling (MIT) …….. variable-density n-D Poisson

├── kdbush or rbush ……………….. 2-D spatial index for slice islands

├── @stomp/sparse-cholesky / wgpu-matrix scientific linalg for heat method

├── three-csg-ts (MIT) …………….. fallback CSG for small ops where manifold round-trip is overkill

└── lib3mf-js or custom 3mf writer ….. 3MF export

10.2 Performance budget for 100K-tri mesh

Target: < 2 s end-to-end auto-support on integrated-GPU laptop (Intel Iris / Apple M-series).

Stage Budget Notes
BVH build (computeBoundsTree({ strategy: SAH })) 150–250 ms once per part import; cache
Per-face overhang field 30 ms flat loop over triangles
Smoothed scalar field (Laplace 8 iters or heat-method) 250–400 ms heat-method preferred for quality, but Laplace iters are sufficient for v1
Slice rasterization (for islands; via MeshBVH.intersectsBox per slice plane) 300 ms (50 µm × 50 mm = 1000 layers) parallelize in Web Worker pool
Variable-density Poisson sampling 150 ms use spatial grid; bridson
Tree routing + collision checking 200–300 ms BVH ray-cast per branch segment; budget 4 BVH calls per segment
CSG union via manifold-3d 300–500 ms for 200–800 cylinders. Manifold’s SDF level-set is faster for >2000 ops
Mesh upload to GPU 50 ms
Total ~1.6 s

10.3 Web Workers

Critical paths to push off the main thread:

  • Slicing — plane-mesh intersection per layer is embarrassingly parallel. Pool of navigator.hardwareConcurrency – 1 workers.
  • CSG via manifold — manifold-3d’s WASM is single-threaded by default; run it in a single dedicated worker so the UI never freezes (community pattern, discourse.threejs.org/t/three-bvh-csg-with-web-worker/75610).
  • Auto-orientation — Fibonacci candidates split across workers.

SharedArrayBuffer is supported in Electron with crossOriginIsolated; use it for the BVH (three-mesh-bvh 0.5+ explicitly supports SAB-backed BVHs).

10.4 Object3D structure

// scene graph

group “PartGroup”  (transform applied here for orientation)

 ├── mesh “Part”   (user model; static)

 ├── group “SupportsGroup”

 │    ├── mesh “Tree-0”

 │    ├── mesh “Tree-1”

 │    └── …

 ├── mesh “Raft”

 └── group “Overlays”

      ├── mesh “HeatmapOverlay”

      └── points “TouchpointMarkers”

Why this structure: rotating PartGroup rotates everything together (so the user sees what they’ll print), but the SupportsGroup is regenerable independently — clear it on every regenerate.

class SupportEngine {

  private partGroup: THREE.Group;

  private supportsGroup: THREE.Group;

 

  async regenerate(profile: ResinProfile, opts: SupportOpts) {

    this.supportsGroup.clear();

    const angleField = await this.worker.computeAngleField(this.part.geometry);

    const smoothField = await this.worker.smoothLaplacian(angleField, 8);

    const islands = await this.worker.detectIslands(this.part.geometry, opts.layerHeight);

    const points = await this.worker.poissonSample(smoothField, profile, islands);

    const tree = await this.worker.routeTree(points, this.part.bvh);

    const supportMeshes = await this.csgWorker.unionCylinders(tree, profile);

    this.supportsGroup.add(supportMeshes);

  }

}

10.5 Memory caveats

  • Manifold-3d wants manifold input; user-uploaded STLs frequently aren’t. Run a MeshFixer.weldVertices(geom, eps=1e-5) + removeDegenerateTriangles() pipeline before CSG.
  • Don’t pass huge BufferGeometry objects across postMessage repeatedly. Use Transferable ArrayBuffers and SAB.

11. Recommended End-to-End Workflow

┌──────────────┐

│ User imports │

│ STL/OBJ/3MF  │

└──────┬───────┘

       ▼

┌──────────────────────────────────────────┐

│ 1) MESH HYGIENE                          │

│    weld vertices, fix winding,           │  data: BufferGeometry (manifold)

│    compute boundsTree (SAH BVH)          │  + MeshBVH

│    compute boundingBox, volume           │

└──────┬───────────────────────────────────┘

       ▼

┌──────────────────────────────────────────┐

│ 2) AUTO-ORIENT                           │  in: BufferGeometry, ResinProfile, weights

│    Fibonacci-256 + 50-iter SA            │  out: Quaternion (applied to PartGroup)

│    cup detection                         │

│    cosmetic-face hint (user can mark)    │

└──────┬───────────────────────────────────┘

       ▼ rotate scene

┌──────────────────────────────────────────┐

│ 3) ANGLE MAP                             │  out: Float32Array (per-vertex angle)

│    per-face overhang θ from -ẑ           │

└──────┬───────────────────────────────────┘

       ▼

┌──────────────────────────────────────────┐

│ 4) DENSITY FIELD                         │  out: Float32Array s∈[0,1] per vertex

│    Laplacian smoothing (or heat method)  │

│    + unsupported-span via BVH ray        │

└──────┬───────────────────────────────────┘

       ▼

┌──────────────────────────────────────────┐

│ 5) ISLAND DETECTION                      │  out: Island[] per slice

│    raster slice stack at 0.05 mm         │

│    connected-components label            │

└──────┬───────────────────────────────────┘

       ▼

┌──────────────────────────────────────────┐

│ 6) TOUCH-POINT SAMPLING                  │  out: TouchPoint[] = {pos, normal, radius}

│    edge & tip seeding                    │

│    surface variable-density Poisson      │

│    per-layer peel staggering             │

└──────┬───────────────────────────────────┘

       ▼

┌──────────────────────────────────────────┐

│ 7) GEOMETRY GENERATION                   │  out: SupportTree mesh + Raft mesh

│    tip cone + transition + shaft + base  │

│    (optional) tree routing & merging     │

│    cross-bracing                         │

│    manifold-3d union                     │

└──────┬───────────────────────────────────┘

       ▼

┌──────────────────────────────────────────┐

│ 8) MANUAL EDIT LOOP                      │  user can add/delete tips

│    incremental regen (only affected      │

│    branches)                             │

│    overlays: heatmap, peel-force chart   │

└──────┬───────────────────────────────────┘

       ▼

┌──────────────────────────────────────────┐

│ 9) EXPORT                                │

│    STL (union)  /  STL+STL  /  3MF       │

│    apply current transform               │

└──────────────────────────────────────────┘

Data structures passed between stages:

interface PartContext {

  geom: BufferGeometry;

  bvh: MeshBVH;

  bbox: Box3;

  volume_mm3: number;

}

interface OrientCtx { q: Quaternion; rotated: BufferGeometry; cups: Cup[]; }

interface SupportField {

  angleNeed: Float32Array;       // per vertex

  unsupSpan: Float32Array;       // per vertex

  smooth:    Float32Array;       // per vertex; combined

}

interface Island { z: number; centroid: Vec2; area_mm2: number; perimeter: Vec2[]; }

interface TouchPoint { pos: Vector3; normal: Vector3; tipRadius: number; }

interface SupportTree { trunks: TrunkSegment[]; bracings: Brace[]; raft: Polygon; }

12. What NOT To Do — Common Pitfalls in DIY SLA Support Generators

  1. Don’t threshold per-triangle without smoothing. Boundaries of supported regions become visible “rings” on the part. Smooth the field (heat method or Laplace iters) first.
  2. Don’t rely on volume-of-supports as the only orientation objective. It optimizes material cost but ignores peel; large flat cross-sections survive minimization but blow up peel forces. Always include the per-layer cross-section integral with film-factor weight.
  3. Don’t trust STL booleans naïvely. THREE-CSGMesh and three-csg-ts use BSP and break on near-coplanar triangles. Use manifold-3d; its guarantee of manifold output saves hours of debugging.
  4. Don’t union cylinders one at a time in a loop on the main thread. Manifold’s pipeline is fastest with batch unions or with the level-set/SDF path. For >300 supports, build a single BatchedMesh-style merged geometry first, then one Manifold operation.
  5. Don’t skip island detection at slice time. Per-face overhang misses overhangs that arise from slicing artifacts (e.g., slightly-sloping faces that produce isolated pixels). Both Chitubox and PrusaSlicer specifically run a 2D-raster island pass.
  6. Don’t set tip diameter < 0.20 mm by default. Below this, tips break off in the part during removal and embed into surfaces. PreForm “Smaller Touchpoints” preset stops at 0.25 mm for a reason.
  7. Don’t ignore trapped-resin cups. They cause the most spectacular failures. Detect them before printing; surface a “Drainage Hole Suggested” warning with click-to-place-hole.
  8. Don’t put many supports terminating at the same z. Stagger across ±2 layers; this halves peak peel force.
  9. Don’t silently regenerate the user’s manual edits. Every “regenerate” must respect locked / pinned / user-added supports unless explicitly cleared.
  10. Don’t rotate the part each time the user hits “regenerate”. Keep orientation as a separate user gesture; “regenerate supports” should preserve orientation. Separating these two operations is a major UX clarity win.
  11. Don’t ship raft = flat brick. Add a 0.3–0.5 mm chamfer at base to mitigate elephant-foot blooming (Mrázek 2022).
  12. Don’t borrow code from PrusaSlicer or Cura. AGPL/LGPL contamination of an Electron app is a legal risk. The algorithms are all describable in the literature; reimplement.
  13. Don’t assume a “one-mesh export” is universally accepted. Some old printers / firmwares choke on >8M triangles. Provide a “decimate supports” option for >1M-triangle support meshes.
  14. Don’t ignore green-state modulus. Supports must hold during print (green state, ~0.6× post-cure E), not after post-cure. Buckling sizing should use the green E.
  15. Don’t auto-orient flat objects edge-up “because it minimizes contact area”. For dental and other workflows, flat-on-build-plate is the right answer (Chitubox Dental documentation); detect “lay-flat hint” in 3MF metadata or user-marked faces.

13. References (inline above; consolidated bibliography for the engineering team)

  • Crane, K., Weischedel, C., Wardetzky, M. (2013/2017). Geodesics in heat. ACM Trans. Graph. cs.cmu.edu/~kmcrane/Projects/HeatMethod/
  • Vanek, J., Galicia, J.A., Benes, B. (2014). Clever Support: Efficient Support Structure Generation for Digital Fabrication. Comp. Graph. Forum.
  • Zhang, X. et al. (2015). Medial axis tree. CAGD 35-36, 149–162.
  • Ezair, Massarwi, Elber (2015). Orientation analysis of 3D objects toward minimal support volume. Computers & Graphics.
  • Liravi, F. et al. (2017). Investigation of separation force for constrained-surface stereolithography. Rapid Prototyping J.
  • Wu, X. et al. (2018). Effect of Constrained Surface Texturing on Separation Force in Projection Stereolithography. ASME J. Manuf. Sci. Eng. 140(9).
  • Bowers, J., Wang, R., Wei, L.-Y., Maletz, D. (2010). Parallel Poisson disk sampling with spectrum analysis on surfaces. ACM TOG.
  • Freire, M., Hornus, S., Perchy, S., Lefebvre, S. (2022). Procedural Bridges-and-pillars Support Generation. Eurographics Short Papers (CC-BY 4.0).
  • Formlabs material data sheets: Tough 2000 V2 (251013-MS-TDS), Rigid 4000 (1801088), Rigid 10K (2001479), Grey Pro (1801086), Castable Wax, Flexible 80A (2001418), BioMed Clear (2001432), Clear Resin V5 TDS, all at formlabs.com/materials/data-sheets/
  • Formlabs (2019). Introducing the Form 3 and Form 3L, Powered by Low Force Stereolithography. formlabs.com/blog
  • Formlabs (2022). New Support Features for SLA 3D Printing With PreForm 3.51. formlabs.com/blog/preform-support-menu-updates
  • PrusaSlicer source: github.com/prusa3d/PrusaSlicer (AGPL — study only), specifically src/libslic3r/SLA/
  • Help.prusa3d.com (2025). Tree supports; Organic supports.
  • Chitubox docs: docs.chitubox.com/en-US/chitubox-basic/latest/ui-and-features/configure-support-parameters
  • Lychee/Mango3D docs: docs.mango3d.io and lychee.co/whats-new
  • UVTools: github.com/sn4k3/UVtools (AGPL)
  • gkjohnson, three-mesh-bvh: github.com/gkjohnson/three-mesh-bvh (MIT)
  • elalish, manifold: github.com/elalish/manifold (Apache-2.0); npm manifold-3d
  • kchapelier, poisson-disk-sampling: npmjs.com/package/poisson-disk-sampling (MIT)
  • Mrázek, J. (2022/2023). Personal blog. blog.honzamrazek.cz

 

About the Author
RapidMade | Support Generation for SLA, DLP, LCD 3D Printing

Micah Chaban
Founder & Vice President
RapidMade, Inc.

For 15 years I have worn every hat in our factory. I have advised engineers, fixed 3D printers, and toiled in the shop before we had a single employee. I write technical content for people who make parts that need to work in the real world.

talk out to us!

Call

(503) 943-2781 ext 1

Email

info@rapidmade.com

Chat

Bottom right page corner

Contact
VP of Sales and Marketing

Contact
3D Print Sales Specialist