diff --git a/packages/preview/maquette/0.1.1/LICENSE b/packages/preview/maquette/0.1.1/LICENSE new file mode 100644 index 0000000000..852a096870 --- /dev/null +++ b/packages/preview/maquette/0.1.1/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 bernsteining + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/preview/maquette/0.1.1/README.md b/packages/preview/maquette/0.1.1/README.md new file mode 100644 index 0000000000..46d87c6bac --- /dev/null +++ b/packages/preview/maquette/0.1.1/README.md @@ -0,0 +1,400 @@ +# Maquette + +[![Typst Universe](https://img.shields.io/badge/Typst%20Universe-maquette-239dad)](https://typst.app/universe/package/maquette) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) + +Maquette is a [Typst](https://typst.app) plugin that renders 3D models directly inside your documents — no screenshots, no external tools, no manual re-exports when you tweak the camera angle. + +Maquette takes STL, OBJ, and PLY files and turns them into SVG or PNG images at compile time, right inside the Typst pipeline. Point a camera, set up lighting, pick a shading model, and the result lands in your PDF. Change a parameter, recompile, done. This makes it practical to embed technical 3D illustrations in engineering reports, research papers, and documentation without maintaining a separate asset pipeline. + +Everything runs as a single WASM plugin (~485 KB), with focus on speed. + +Check the [documentation](https://github.com/bernsteining/maquette/blob/v0.1.1/examples/documentation.pdf) to see examples of all the features. + +## Example + +A back-lit Stanford bunny: a red point light placed *inside* the model glows through the thin ears (subsurface scattering), with the camera framed from behind. Lighting, camera and material all live in the source. + + + + + + +
+ +```typst +#import "@preview/maquette:0.1.1": render-obj +#let bunny = read("examples/data/bunny.obj") + +#render-obj(bunny, ( + up: (0, 1, 0), + azimuth: 180, + distance: 0.25, + background: "none", + lights: ( + (type: "positional", + vector: (-0.1, 0.14, -0.04), + color: "#ff0000", intensity: 3.0), + ), + sss: (intensity: 4, power: 3.5, distortion: 0.2), +), antialias: 4) +``` + + +Back-lit Stanford bunny with subsurface scattering +
+ +## Gallery + + + + + + + + + + +
+Multi-view grid
+Multi-view grid — front / right / top / isometric on one sheet +
+Per-group appearance
+Per-group appearance — per-part colour, stroke & opacity from OBJ groups +
+Scalar color map
+Scalar color map — a math expression f(x,y,z) over a custom palette +
+Clipping plane
+Clipping plane — a mathematical cut opens the model to its interior +
+ +Once again, the [documentation](https://github.com/bernsteining/maquette/blob/v0.1.1/examples/documentation.pdf) has many examples covering all the features offered by `maquette`. + +## Rendering Techniques + +| Category | Technique | Description | +|---|---|---| +| **Shading** | Blinn-Phong | Physically-motivated diffuse + specular highlights (default) | +| | Flat | Unlit, base color only — no lighting calculations | +| | Cel / Toon | Discrete color bands with configurable step count | +| | Gooch | Warm-to-cool non-photorealistic shading for technical illustration | +| | Normal | View-space normal visualization | +| | Smooth (Gouraud) | Per-vertex normal interpolation for curved surfaces | +| **Lighting** | Directional lights | Infinite light sources (sun-like) | +| | Point lights | Positional light sources with distance falloff | +| | Multi-light | Combine multiple lights with individual color and intensity | +| | Hemisphere ambient | Sky/ground gradient ambient lighting | +| | Fresnel rim | Edge glow effect based on view angle | +| | Specular highlights | Blinn-Phong half-vector reflections | +| | Subsurface scattering | Fake SSS for translucent materials (skin, wax, marble) | +| **Rendering** | Z-buffer rasterization | Pixel-accurate PNG output with depth testing | +| | Painter's algorithm | Depth-sorted SVG polygon output | +| | Wireframe | Full mesh topology edges, no fill | +| | Solid + Wireframe | Shaded surface with mesh overlay | +| | X-Ray | Transparent front faces, opaque back faces | +| **Post-processing** | SSAO | Screen-space ambient occlusion with bilateral blur | +| | FXAA | Fast approximate anti-aliasing | +| | SSAA | Supersampled anti-aliasing (2×, 4×) | +| | Bloom | Bright area bleed for HDR glow | +| | Glow | Colored edge glow effect | +| | Sharpen | Unsharp mask sharpening filter | +| | Tone mapping | Reinhard or ACES operators for HDR-to-LDR conversion | +| | Gamma correction | Linear-to-sRGB conversion | +| **Visualization** | Color maps | Height, overhang, curvature, or custom scalar function | +| | Silhouette outlines | Contour edge detection and rendering | +| | Ground shadows | Projected shadow onto ground plane | +| | Clipping planes | Mathematical plane cuts with interior visibility | +| | Exploded views | Auto-separate components outward from center | +| | Annotations | Leader lines with labels for OBJ group names | +| **Point Clouds** | k-NN triangulation | Automatic mesh reconstruction from unstructured point clouds | +| **Projection** | Perspective | Standard perspective with configurable FOV | +| | Orthographic | Parallel projection for technical drawings | +| | Isometric | Fixed isometric view | +| | Cabinet / Cavalier | Oblique parallel projections | +| | Fisheye | Wide-angle barrel distortion | +| | Stereographic | Conformal wide-angle projection | +| | Curvilinear | Panini-style rectilinear correction | +| | Cylindrical | Equidistant cylindrical mapping | +| | Pannini | Pannini projection for architectural scenes | +| | Tiny Planet | 360° inverse stereographic (little planet effect) | +| **Layout** | Multi-view grid | Named views (front, right, top, isometric, ...) in a grid | +| | Turntable | Evenly-spaced rotation views at fixed elevation | +| **Formats** | STL | Binary STL with optional per-face RGB565 colors | +| | OBJ | Wavefront OBJ with groups, materials | +| | PLY | Binary/ASCII PLY with per-vertex colors and point cloud support | + +## Documentation + +See [examples/documentation.pdf](https://github.com/bernsteining/maquette/blob/v0.1.1/examples/documentation.pdf) for a full walkthrough with examples, or compile it: + +```sh +make demo +``` + +## Functions + +### `render-stl` / `render-obj` / `render-ply` + +```typst +#render-stl(stl-data, config, width: auto, height: auto, format: "png") +#render-obj(obj-data, config, width: auto, height: auto, format: "png") +#render-ply(ply-data, config, width: auto, height: auto, format: "png") +``` + +Renders a 3D model. Data must be read with `encoding: none` (required for binary STL/PLY, optional for OBJ). Config is a JSON string via `json.encode((...))`. Set `format: "svg"` for vector output. + +### `get-stl-info` / `get-obj-info` / `get-ply-info` + +```typst +#let info = get-stl-info(stl-data, json.encode(())) +#let info = get-obj-info(obj-data, json.encode(())) +#let info = get-ply-info(ply-data, json.encode(())) +``` + +Returns JSON with model metadata (triangle count, vertex count, bounding box, groups). + +## Output Formats + +- **PNG** (default): Z-buffer rasterized. Best for high-poly models and smooth shading. Set `width`/`height` for resolution and `antialias` for supersampling. +- **SVG** (`format: "svg"`): Vector output via painter's algorithm. Best for low-to-medium poly models. Supports debug overlays and silhouette outlines. + +## Config Reference + +### Camera & Viewport + +| Key | Default | Description | +|---|---|---| +| `camera` | `null` | Camera position `(x, y, z)` in world space. Overrides `azimuth`/`elevation` | +| `azimuth` | `0` | Horizontal angle in degrees around the model | +| `elevation` | `0` | Vertical angle in degrees above the horizontal plane | +| `distance` | `null` | Camera distance from center (`null` = auto from bounding box) | +| `center` | `(0, 0, 0)` | Look-at target (overridden by `auto_center`) | +| `up` | `(0, 0, 1)` | Up direction. Z-up (CAD/STL) by default; use `(0, 1, 0)` for Y-up (Blender, game engines) | +| `fov` | `45` | Vertical field of view in degrees (perspective only) | +| `projection` | `"perspective"` | `"perspective"`, `"orthographic"`, `"isometric"`, `"cabinet"`, `"cavalier"`, `"fisheye"`, `"stereographic"`, `"curvilinear"`, `"cylindrical"`, `"pannini"`, `"tiny-planet"` | +| `auto_center` | `true` | Auto-center camera on model bounding box | +| `auto_fit` | `true` | Scale model to fill viewport | +| `width` | `500` | Output width in pixels | +| `height` | `500` | Output height in pixels | +| `background` | `"#f0f0f0"` | Background color (hex). `"none"` or empty string = transparent (PNG exports with an alpha channel; use `antialias` for smooth transparent edges) | + +### Appearance + +| Key | Default | Description | +|---|---|---| +| `color` | `"#4488cc"` | Model fill color (hex) | +| `stroke` | `"none"` | Triangle edge stroke: color string or `{ color, width }` | +| `mode` | `"solid"` | `"solid"`, `"wireframe"`, `"solid+wireframe"`, `"x-ray"` | +| `smooth` | `true` | Gouraud smooth shading (per-vertex normals) | +| `cull_backface` | `true` | Back-face culling | +| `opacity` | `1.0` | Global model opacity (0--1) | +| `xray_opacity` | `0.1` | Front-face opacity in x-ray mode (0--1) | +| `wireframe` | `""` | Wireframe overlay: color string or `{ color, width }` | +| `materials` | `{}` | OBJ material map: `{ "name": "#hex" }` for `usemtl` | +| `highlight` | `{}` | OBJ group overrides: `{ "name": "#hex" }` or `{ "name": { color, opacity, specular, ... } }` | + +### Shading & Lighting + +| Key | Default | Description | +|---|---|---| +| `shading` | `""` | Shading model: `""` (Blinn-Phong), `"flat"`, `"cel"`, `"gooch"`, `"normal"` | +| `light_dir` | `(1, 2, 3)` | Default directional light vector | +| `ambient` | `0.15` | Ambient light intensity (0--1), or `{ intensity, sky, ground }` for hemisphere | +| `specular` | `0.2` | Specular highlight intensity (0--1) | +| `shininess` | `32` | Specular exponent (higher = tighter highlights) | +| `fresnel` | `0.3` | Fresnel rim: intensity (0--1), or `{ intensity, power }` | +| `lights` | `[]` | Multi-light array: `{ type, vector, color, intensity }` per light | +| `gooch_warm` | `"#ffcc44"` | Gooch warm tone color | +| `gooch_cool` | `"#4466cc"` | Gooch cool tone color | +| `cel_bands` | `4` | Number of discrete bands for cel shading | +| `sss` | `false` | Subsurface scattering: `true` or `{ intensity, power, distortion }` | +| `gamma_correction` | `true` | Linear-to-sRGB gamma correction | +| `tone_mapping` | `""` | Tone mapping: `""` (off), `"reinhard"`, `"aces"`, or `{ method, exposure }` | + +### Color Mapping + +| Key | Default | Description | +|---|---|---| +| `color_map` | `""` | `"height"`, `"overhang"`, `"curvature"`, `"scalar"`, or `""` (off) | +| `color_map_palette` | `[]` | Custom hex color gradient | +| `scalar_function` | `""` | Math expression for scalar mode, e.g. `"sqrt(x*x+y*y)"` | +| `vertex_smoothing` | `4` | Smooth color values across vertices (0--4) | +| `overhang_angle` | `45` | Overhang threshold in degrees | + +### Outlines + +| Key | Default | Description | +|---|---|---| +| `outline` | `false` | Silhouette edge outlines: `true` or `{ color, width }` | + +### Effects + +| Key | Default | Description | +|---|---|---| +| `ground_shadow` | `false` | Ground shadow: `true` or `{ opacity, color }` | +| `clip_plane` | `null` | Clipping plane `(a, b, c, d)`: keeps `ax+by+cz+d >= 0` | +| `explode` | `0` | Exploded view factor (OBJ groups or auto-detected components) | +| `decimate` | `0` | Mesh simplification strength (0--1) Higher = fewer triangles. | +| `antialias` | `1` | Supersampling for PNG (`2` = 2×2 SSAA, `4` = 4×4) | +| `fxaa` | `true` | Fast approximate anti-aliasing (PNG only) | +| `ssao` | `false` | Screen-space AO: `true` or `{ samples, radius, bias, strength }` | +| `bloom` | `false` | Bloom: `true` or `{ threshold, intensity, radius }` | +| `glow` | `false` | Glow: `true` or `{ color, intensity, radius }` | +| `sharpen` | `false` | Sharpen: `true` or `{ strength }` | +| `point_size` | `0` | Point cloud neighbor search radius (`0` = auto from point density) | + +### Annotations + +| Key | Default | Description | +|---|---|---| +| `annotations` | `false` | Annotate OBJ groups: `true` or `{ groups, color, font_size, offset }` | + +### Multi-View + +| Key | Default | Description | +|---|---|---| +| `views` | `null` | Named view grid: `("front", "right", "top", "isometric")` | +| `turntable` | `0` | Turntable views: count, or `{ iterations, elevation }` | +| `grid_labels` | `true` | Show labels on multi-view / turntable grids | + +### Diagnostics + +| Key | Default | Description | +|---|---|---| +| `debug` | `false` | Overlay model metadata and light positions | +| `debug_color` | `"#cc2222"` | Debug text color | + +## Full Config + +```json +{ // ── Camera & Viewport ───────────────────────────────────────────── +"camera": [3, 3, 3], +// Camera position in world space (Cartesian) +"azimuth": null, +// Spherical camera: horizontal angle in degrees +"elevation": null, +// Spherical camera: vertical angle in degrees +"distance": null, +// Spherical camera: distance from center (auto) +"center": [0, 0, 0], +// Look-at target (overridden by auto_center) +"up": [0, 0, 1], +// Up direction vector +"fov": 45, +// Vertical FOV in degrees (perspective only) +"projection": "perspective", +// "perspective", "orthographic", "isometric", "cabinet", "cavalier", +// "fisheye", "stereographic", "curvilinear", "cylindrical", "pannini", +// "tiny-planet" +"auto_center": true, +// Auto-center on model bounding box +"auto_fit": true, +// Scale model to fill viewport +"width": 500, +// Output width in pixels +"height": 500, +// Output height in pixels +"background": "#f0f0f0", +// Background color (hex), "" = transparent +// ── Appearance ──────────────────────────────────────────────────── +"color": "#4488cc", +// Model fill color (hex) +"stroke": "none", +// Triangle edge stroke: color string or { color, width } +"light_dir": [1, 2, 3], +// Directional light vector +"ambient": 0.15, +// Ambient light intensity (0-1), or { intensity, sky, ground } +"mode": "solid", +// "solid", "wireframe", "solid+wireframe", "x-ray" +"opacity": 1.0, +// Global model opacity (0-1) +"xray_opacity": 0.1, +// Front-face opacity for x-ray mode (0-1) +"cull_backface": true, +// Back-face culling (auto-disabled for x-ray) +"wireframe": "", +// Wireframe overlay: color string or { color, width } +"smooth": true, +// Gouraud smooth shading (best with PNG) +"specular": 0.2, +// Specular highlight intensity (0-1) +"shininess": 32, +// Specular exponent (higher = tighter) +"gamma_correction": true, +// Compute lighting in linear sRGB space +"fresnel": 0.3, +// Fresnel rim lighting: intensity (0-1), or { intensity, power } +"lights": [], +// Array of light definitions (see Multi-Light) +"tone_mapping": "", +// "reinhard", "aces", or { method, exposure } +"shading": "", +// "blinn-phong" (default), "gooch", "cel", "flat", or "normal" +"gooch_warm": "#ffcc44", +// Gooch warm tone color +"gooch_cool": "#4466cc", +// Gooch cool tone color +"cel_bands": 4, +// Number of cel-shading bands +"sss": false, +// Subsurface scattering: true or { intensity, power, distortion } +"materials": {}, +// OBJ material map: { "name": "#hex" } +"highlight": {}, +// OBJ group highlight: "#hex" or { color, specular, ... } +// ── Color Mapping ───────────────────────────────────────────────── +"color_map": "", +// "height", "overhang", "curvature", "scalar", or "" (off) +"color_map_palette": [], +// Custom hex color gradient +"scalar_function": "", +// Math expression for scalar mode: "sqrt(x*x+y*y+z*z)" +"vertex_smoothing": 4, +// Smooth color values across vertices (0-4) +"overhang_angle": 45, +// Overhang threshold in degrees +// ── Outlines ────────────────────────────────────────────────────── +"outline": false, +// Silhouette edge outlines: true or { color, width } +// ── Effects ─────────────────────────────────────────────────────── +"ground_shadow": false, +// Ground shadow: true or { opacity, color } +"clip_plane": null, +// Clipping plane (a, b, c, d) +"explode": 0, +// Exploded view factor +"decimate": 0, +// Mesh simplification strength 0-1 (grid vertex clustering; higher = fewer triangles) +"point_size": 0, +// Point cloud splat radius (0 = auto) +"antialias": 1, +// Supersampling for PNG (2 = 2x2 SSAA, 4 = 4x4) +"fxaa": true, +// Fast approximate anti-aliasing (PNG only) +"ssao": false, +// Screen-space AO: true or { samples, radius, bias, strength } +"bloom": false, +// Bloom: true or { threshold, intensity, radius } +"glow": false, +// Glow: true or { color, intensity, radius } +"sharpen": false, +// Sharpen: true or { strength } +// ── Annotations ───────────────────────────────────────────────── +"annotations": false, +// Annotate OBJ groups: true or { groups, color, font_size, offset } +// ── Multi-View ──────────────────────────────────────────────────── +"views": null, +// Named views: ["front", "right", "top", ...] +"turntable": 0, +// Turntable views: count, or { iterations, elevation } +"grid_labels": true, +// Show labels on multi-view grids +// ── Diagnostics ─────────────────────────────────────────────────── +"debug": false, +// Overlay model metadata +"debug_color": "#cc2222" +// Debug text color +} +``` diff --git a/packages/preview/maquette/0.1.1/maquette.typ b/packages/preview/maquette/0.1.1/maquette.typ new file mode 100644 index 0000000000..a3d42a509d --- /dev/null +++ b/packages/preview/maquette/0.1.1/maquette.typ @@ -0,0 +1,78 @@ +// maquette — render 3D models (STL, OBJ, PLY) as SVG or PNG images in Typst + +#let maquette-plugin = plugin("maquette.wasm") + +// Auto-detect output format: PNG starts with 0x89, SVG starts with '<' (0x3C). +#let _detect-format(data) = { + if data.at(0) == 0x3C { "svg" } else { "png" } +} + +#let _parse-args(args) = { + // Extract display args (not part of render config) + let named = args.named() + let width = named.at("width", default: auto) + let height = named.at("height", default: auto) + let format = named.at("format", default: "png") + + // Build config: named params (minus display args) merged with positional dict if any + let config = (:) + if args.pos().len() > 0 { + let first = args.pos().at(0) + if type(first) == str { + // Legacy: pre-encoded JSON string + return (cfg: bytes(first), width: width, height: height, format: format) + } + if type(first) == dictionary { + config = first + } + } + for (k, v) in named { + if k not in ("width", "height", "format") { + config.insert(k, v) + } + } + ( + cfg: bytes(json.encode(config)), + width: width, + height: height, + format: format, + ) +} + +#let _render(data, png-fn, svg-fn, args) = { + let a = _parse-args(args) + if a.format == "png" { + let result = png-fn(data, a.cfg) + image(result, format: _detect-format(result), width: a.width, height: a.height) + } else { + image(svg-fn(data, a.cfg), format: "svg", width: a.width, height: a.height) + } +} + +#let render-stl(stl-data, ..args) = { + _render(stl-data, maquette-plugin.render_stl_png, maquette-plugin.render_stl, args) +} + +#let render-obj(obj-data, ..args) = { + let data = bytes(obj-data) + _render(data, maquette-plugin.render_obj_png, maquette-plugin.render_obj, args) +} + +#let render-ply(ply-data, ..args) = { + _render(ply-data, maquette-plugin.render_ply_png, maquette-plugin.render_ply, args) +} + +#let get-stl-info(stl-data, ..args) = { + let a = _parse-args(args) + json(maquette-plugin.get_stl_info(stl-data, a.cfg)) +} + +#let get-obj-info(obj-data, ..args) = { + let a = _parse-args(args) + json(maquette-plugin.get_obj_info(bytes(obj-data), a.cfg)) +} + +#let get-ply-info(ply-data, ..args) = { + let a = _parse-args(args) + json(maquette-plugin.get_ply_info(ply-data, a.cfg)) +} diff --git a/packages/preview/maquette/0.1.1/maquette.wasm b/packages/preview/maquette/0.1.1/maquette.wasm new file mode 100755 index 0000000000..945cac090c Binary files /dev/null and b/packages/preview/maquette/0.1.1/maquette.wasm differ diff --git a/packages/preview/maquette/0.1.1/typst.toml b/packages/preview/maquette/0.1.1/typst.toml new file mode 100644 index 0000000000..fce6905521 --- /dev/null +++ b/packages/preview/maquette/0.1.1/typst.toml @@ -0,0 +1,41 @@ +[package] +name = "maquette" +version = "0.1.1" +entrypoint = "maquette.typ" +authors = ["https://github.com/bernsteining"] +repository = "https://github.com/bernsteining/maquette" +license = "MIT" +description = "🫖 Render 3D models (PLY, STL, OBJ) as SVG or PNG images." +keywords = [ + "3D", + "STL", + "OBJ", + "PLY", + "scanner", + "model", + "blender", + "AR", + "LiDAR", + "scan", + "render", + "SVG", + "PNG", + "CAD", + "CAO", + "mesh", + "shading", + "wireframe", +] +categories = ["visualization"] +compiler = "0.14.0" +disciplines = [ + "architecture", + "chemistry", + "computer-science", + "design", + "drawing", + "engineering", + "mathematics", + "medicine", + "physics", +]