poppygl 0.0.35 → 0.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -136,7 +136,7 @@ const png = await renderGLTFToPNGFromGLB(glb, {
136
136
  - `showEdges`: draw visible boundary and crease edges on all triangle meshes (default `false`).
137
137
  - `showHiddenEdges`: draw visible edges plus faint dashed occluded edges on all meshes. Omit it or use `null` to preserve each model's flag; `false` disables those flags.
138
138
  - `edgeWidth`: width of both visible and hidden mesh edges in output pixels (default `1`).
139
- - `lineWidth`: width of glTF `LINES` and finite grid lines in output pixels (default `1`).
139
+ - `lineWidth`: width of glTF `LINES`, finite grid lines, and explosion guides in output pixels (default `1`).
140
140
  - `hiddenEdgeOpacity`: opacity of dashed occluded mesh edges, from `0` to `1` (default `0.35`).
141
141
  - `fetchImpl`: optional override for resource loading (must match the `fetch` signature).
142
142
 
@@ -167,8 +167,9 @@ finite fractional widths are supported; invalid widths fall back to one pixel.
167
167
  Wider strokes use antialiased screen-space coverage with round caps in the
168
168
  software renderer. They are clipped before the perspective divide and remain
169
169
  depth tested across their full width. No GPU or shader runtime is required.
170
- `lineWidth` also applies to ordinary glTF line primitives and finite grid lines;
171
- the procedural infinite grid keeps its existing independent styling.
170
+ `lineWidth` also applies to ordinary glTF line primitives, finite grid lines,
171
+ and explosion guides, including arrowhead strokes. The procedural infinite grid
172
+ keeps its existing independent styling.
172
173
 
173
174
  ### Hidden edges for selected sub-models
174
175
 
@@ -238,6 +239,53 @@ const png = await encodePNG(bitmap)
238
239
 
239
240
  The only contract is that `buffers` is an array of `Uint8Array` instances and `images` is an array of `BitmapLike` textures (PNG and JPEG are supported out of the box via `decodeImageFromBuffer`).
240
241
 
242
+ ## Explosion guide lines
243
+
244
+ Pass `explosionLines` to any render entrypoint to draw polylines between assembled
245
+ and displaced part positions. The caller moves the geometry and supplies the
246
+ points in the same glTF scene frame and units (normally right-handed, +Y up).
247
+
248
+ This lamp is adapted from [core's reference-surface lamp](https://github.com/tscircuit/core/blob/81bef4337570e3ac4d7dd6d30ab63481304e8147/tests/assembly/fixtures/reference-surface-lamp.tsx).
249
+ Its base, hollow blue stem, and bulb stay in place while the hollow shade,
250
+ mounting collar, and three spokes move together by **+60 mm on scene Y**. The
251
+ shade mount moves from Y=92 to Y=152. Two dashed guides join corresponding points
252
+ on its lower rim, from Y=68 to Y=128. Arrowheads at the displaced endpoints show
253
+ the upward movement:
254
+
255
+ ![Assembled lamp and shade lifted 60 mm with dashed upward arrow guides](tests/render/__snapshots__/explosion-lamp.snap.png)
256
+
257
+ ```ts
258
+ // explodedLampGlb already contains the shade translated +60 mm on scene Y.
259
+ const rimPoints = [[-36 / Math.SQRT2, 36 / Math.SQRT2], [36, 0]] as const
260
+ const png = await renderGLTFToPNGFromGLB(explodedLampGlb, {
261
+ explosionLines: rimPoints.map(([x, z]) => ({
262
+ points: [[x, 68, z], [x, 128, z]],
263
+ color: [0.015, 0.04, 0.08], // Linear RGB, 0..1; optional.
264
+ dashed: true, // Default; false gives a solid guide.
265
+ arrow: true, // Optional; false by default.
266
+ })),
267
+ })
268
+ ```
269
+
270
+ The deterministic [lamp fixture](tests/fixtures/reference-lamp.ts) and
271
+ [visual test](tests/render/explosion-lamp.test.ts) generate the comparison without
272
+ network assets. Open the `explosion-lamp` Cosmos example to toggle shade movement,
273
+ guides, and arrows interactively. The separate synthetic explosion-lines snapshot
274
+ exercises elbows, geometry occlusion, and camera clipping.
275
+
276
+ Guides respect scene depth, do not write to the depth buffer, and render after
277
+ geometry denoising. Their points contribute to automatic camera framing. Lines
278
+ are clipped to the camera frustum before rasterization; dash length scales with
279
+ supersampling. Omitting the option or passing an empty list preserves the existing
280
+ rendering behavior. Each guide needs at least two finite XYZ points.
281
+
282
+ Set `arrow: true` to draw a solid open arrowhead at the final authored point,
283
+ following the final nonzero-length segment's direction. Arrowheads are at most
284
+ 10 output pixels long, capped for short segments, and scale with supersampling.
285
+ They respect scene depth and remain solid when the shaft is dashed. `lineWidth`
286
+ controls both the shaft and arrowhead stroke width. Omit `arrow` or set it to
287
+ `false` for a guide without an arrow.
288
+
241
289
  ## Additional utilities
242
290
 
243
291
  - `loadGLTFWithResourcesFromURL` returns `{ gltf, resources }` if you prefer to inspect or cache the parsed data before rendering.
@@ -247,3 +295,57 @@ The only contract is that `buffers` is an array of `Uint8Array` instances and `i
247
295
  - `encodePNG` packs any `BitmapLike` into a `Uint8Array` PNG that can be written to disk or served over the network.
248
296
 
249
297
  Happy rendering!
298
+
299
+ ### Outline rendering
300
+
301
+ ```ts
302
+ const png = await renderGLTFToPNGFromURL("https://example.com/housing.glb", {
303
+ renderStyle: "outline",
304
+ width: 1200,
305
+ height: 900,
306
+ supersampling: 2,
307
+ camPos: [80, 60, 100],
308
+ lookAt: [0, 0, 0],
309
+ showHiddenEdges: true,
310
+ edgeWidth: 1.5,
311
+ })
312
+ ```
313
+
314
+ `renderStyle: "outline"` uses white opaque surfaces, black boundary/crease
315
+ edges and view-dependent silhouettes, and orthographic projection by default.
316
+ Hidden edges are omitted by default, including model-level hidden-edge flags;
317
+ `showHiddenEdges: true` adds dashed occluded strokes with `hiddenEdgeOpacity`.
318
+ Materials, textures, vertex colors, transparency and lighting are ignored in this
319
+ style. Input meshes are not modified. Existing shaded rendering defaults stay
320
+ unchanged. The same options work with `renderDrawCalls`, GLB bytes, and glTF/GLB URLs.
321
+
322
+ `projection: "orthographic"` also works independently of outline style.
323
+ `orthographicHeight` sets the vertical field of view in model scene units; omit it
324
+ to fit the transformed scene bounds with a margin. Orthographic projection preserves
325
+ parallel edges and apparent size with distance. `camPos`, `lookAt`, `up` and
326
+ `cameraRotation` keep their existing meanings. For an axis-aligned view choose a
327
+ camera on that axis and an `up` axis that is not parallel to it.
328
+
329
+ Section views are available in outline style:
330
+
331
+ ```ts
332
+ const png = await renderGLTFToPNGFromGLB(glbBytes, {
333
+ renderStyle: "outline",
334
+ up: "y+",
335
+ section: { axis: "x", offset: 5, keepSide: "less", hatchSpacing: 1 },
336
+ })
337
+ ```
338
+
339
+ The plane is specified in **world glTF coordinates**, normally +Y up, using model
340
+ scene units. `less` retains axis coordinates <= offset; `greater` retains >= offset.
341
+ Closed cut surfaces receive diagonal hatching in the section plane (spacing defaults
342
+ to one scene unit). Nested loops preserve cavities. Open cut chains remain uncapped; non-manifold inputs are unsupported. `createOutlineSection` exposes the prepared draw calls and
343
+ `openChains` count for callers that need to report incomplete caps. Primitives are
344
+ capped independently; surfaces split across separate primitives cannot form a cap.
345
+ Sections outside or tangent to a mesh do not create artificial caps. Section cuts
346
+ currently require outline style; shaded sections are not supported.
347
+
348
+ These are raster outline illustrations, not CAD/B-rep exports: silhouettes
349
+ follow the input tessellation, crease extraction uses a 30-degree threshold, and
350
+ hidden lines are resolved against raster depth. Dimension annotations, paper sheet
351
+ layout and TSX/core integration are separate downstream work.
package/dist/index.d.ts CHANGED
@@ -13,6 +13,8 @@ declare const createUint8Bitmap: ImageFactory;
13
13
  interface Material {
14
14
  baseColorFactor: [number, number, number, number];
15
15
  baseColorTexture: BitmapLike | null;
16
+ /** Repeat rather than clamp the base-color texture (used by section hatching). */
17
+ baseColorTextureWrap?: "repeat" | "clamp";
16
18
  metallicFactor?: number;
17
19
  roughnessFactor?: number;
18
20
  emissiveFactor?: [number, number, number];
@@ -22,6 +24,8 @@ interface Material {
22
24
  alphaCutoff?: number;
23
25
  }
24
26
  interface DrawCall {
27
+ /** Generated section cap; preserve its hatch material in outline style. */
28
+ outlineSectionCap?: boolean;
25
29
  positions: Float32Array;
26
30
  normals: Float32Array | null;
27
31
  uvs: Float32Array | null;
@@ -60,6 +64,23 @@ interface GLTFScene {
60
64
  gltf: any;
61
65
  }
62
66
 
67
+ type SectionAxis = "x" | "y" | "z";
68
+ /** Axis-aligned plane in world/glTF coordinates, in model scene units. */
69
+ interface OutlineSection {
70
+ axis: SectionAxis;
71
+ offset: number;
72
+ keepSide: "less" | "greater";
73
+ /** Distance between diagonal hatch strokes in scene units; defaults to 1. */
74
+ hatchSpacing?: number;
75
+ }
76
+ /** Prepare opaque monochrome section geometry without modifying the input.
77
+ * Separate primitives are capped independently; open surfaces remain uncapped.
78
+ */
79
+ declare function createOutlineSection(drawCalls: DrawCall[], spec: OutlineSection): {
80
+ drawCalls: DrawCall[];
81
+ openChains: number;
82
+ };
83
+
63
84
  declare const DEFAULT_LIGHT_DIR: readonly [-0.4, -0.9, -0.2];
64
85
  interface DebugPoint {
65
86
  label: string;
@@ -69,6 +90,21 @@ interface DebugPoint {
69
90
  z: number;
70
91
  };
71
92
  }
93
+ /** Guide polyline in right-handed glTF scene coordinates (normally +Y up),
94
+ * using the model's scene units. Each vertex is a point, not a displacement.
95
+ */
96
+ interface ExplosionLine {
97
+ points: readonly (readonly [number, number, number])[];
98
+ /** Linear RGB, 0..1; defaults to a muted blue. */
99
+ color?: readonly [number, number, number];
100
+ /** Defaults to true. Visible segments respect scene depth. */
101
+ dashed?: boolean;
102
+ /** Add a solid arrowhead at the last point, following the final segment.
103
+ * Defaults to false. The arrowhead is 10 output pixels long (or shorter for
104
+ * short segments), independent of camera distance and supersampling.
105
+ */
106
+ arrow?: boolean;
107
+ }
72
108
  type CameraUp = "y+" | "y-" | "x+" | "x-" | "z+" | "z-";
73
109
  interface CameraRotation {
74
110
  x: number;
@@ -82,6 +118,14 @@ interface CameraRotation {
82
118
  */
83
119
  declare function hexToRgb(hex: string): [number, number, number] | null;
84
120
  interface RenderOptions {
121
+ /** White opaque surfaces with black boundary, crease and silhouette edges. */
122
+ renderStyle: "shaded" | "outline";
123
+ /** Section cuts are supported in outline style only. */
124
+ section: OutlineSection | null;
125
+ /** Outline defaults to orthographic; existing renders stay perspective. */
126
+ projection: "perspective" | "orthographic";
127
+ /** Vertical span in scene units. Null automatically fits the world bounds. */
128
+ orthographicHeight: number | null;
85
129
  /** Opt into metallic/roughness studio lighting and geometry-based soft shadows. */
86
130
  realistic: boolean;
87
131
  width: number;
@@ -102,13 +146,14 @@ interface RenderOptions {
102
146
  debugFontSize?: number | null;
103
147
  debugPointColor?: readonly [number, number, number] | null;
104
148
  debugLabelColor?: readonly [number, number, number] | null;
149
+ explosionLines?: readonly ExplosionLine[] | null;
105
150
  /** Draw visible boundary and crease edges on every triangle mesh. */
106
151
  showEdges: boolean;
107
152
  /** Override model showHiddenEdges flags; null preserves each model's flag. */
108
153
  showHiddenEdges: boolean | null;
109
154
  /** Width of visible and hidden mesh edges in output pixels. */
110
155
  edgeWidth: number;
111
- /** Width of glTF LINES and finite grid lines in output pixels. */
156
+ /** Width of glTF LINES, finite grid lines, and explosion guides in output pixels. */
112
157
  lineWidth: number;
113
158
  /** Opacity of dashed, occluded mesh edges, from 0 to 1. */
114
159
  hiddenEdgeOpacity: number;
@@ -121,7 +166,7 @@ interface Camera {
121
166
  view: mat4;
122
167
  proj: mat4;
123
168
  }
124
- declare function buildCamera(drawCalls: DrawCall[], width: number, height: number, fovDeg: number, camPos: readonly [number, number, number] | null | undefined, lookAt: readonly [number, number, number] | null | undefined, up?: CameraUp | null | undefined, cameraRotation?: CameraRotation | null | undefined): Camera;
169
+ declare function buildCamera(drawCalls: DrawCall[], width: number, height: number, fovDeg: number, camPos: readonly [number, number, number] | null | undefined, lookAt: readonly [number, number, number] | null | undefined, up?: CameraUp | null | undefined, cameraRotation?: CameraRotation | null | undefined, projection?: "perspective" | "orthographic", orthographicHeight?: number | null): Camera;
125
170
 
126
171
  interface RenderResult {
127
172
  bitmap: BitmapLike;
@@ -139,7 +184,12 @@ declare function createGrid(options?: GridOptions): DrawCall;
139
184
  * mesh. Vertices are welded by position so glTF meshes that duplicate vertices
140
185
  * for normals or UV seams do not produce coplanar triangulation lines.
141
186
  */
142
- declare function createEdgeDrawCall(mesh: DrawCall, creaseAngleDegrees?: number): DrawCall | null;
187
+ declare function createEdgeDrawCall(mesh: DrawCall, creaseAngleDegrees?: number,
188
+ /** Include view-dependent silhouettes, with crease angles measured after transforms. */
189
+ camera?: {
190
+ view: mat4;
191
+ projection: "perspective" | "orthographic";
192
+ }): DrawCall | null;
143
193
 
144
194
  /**
145
195
  * Creates an infinite grid that matches the appearance of the 3d-viewer reference.
@@ -263,4 +313,4 @@ declare function renderGLTFToPNGFromGLB(glb: ArrayBuffer | Uint8Array, options?:
263
313
 
264
314
  declare function renderSceneFromGLTF(scene: GLTFScene, options?: RenderOptionsInput, imageFactory?: ImageFactory): RenderResult;
265
315
 
266
- export { type BitmapLike, type Camera, type CameraRotation, type CameraUp, DEFAULT_LIGHT_DIR, DEFAULT_RENDER_OPTIONS, type DebugPoint, type DrawCall, type FetchLike, type GLTFResources, type GLTFScene, type GridOptions, type ImageFactory, type LightSettings, type LineRenderOptions, type LoadGLTFWithResourcesFromURLOptions, type Material, type MutableRGBA, type RGBA, type RenderGLTFToPNGFromGLBOptions, type RenderGLTFToPNGFromURLOptions, type RenderOptions, type RenderOptionsInput, type RenderResult, SoftwareRenderer, bufferFromDataURI, buildCamera, computeSmoothNormals, computeWorldAABB, createEdgeDrawCall, createGrid, createInfiniteGrid, createSceneFromGLTF, createUint8Bitmap, decodeImageFromBuffer, drawInfiniteGrid, encodePNG, getDefaultRenderOptions, hexToRgb, isJPG, isPNG, loadGLTFWithResourcesFromURL, renderDrawCalls, renderGLTFToPNGFromGLB, renderGLTFToPNGFromURL, renderSceneFromGLTF, resolveRenderOptions };
316
+ export { type BitmapLike, type Camera, type CameraRotation, type CameraUp, DEFAULT_LIGHT_DIR, DEFAULT_RENDER_OPTIONS, type DebugPoint, type DrawCall, type ExplosionLine, type FetchLike, type GLTFResources, type GLTFScene, type GridOptions, type ImageFactory, type LightSettings, type LineRenderOptions, type LoadGLTFWithResourcesFromURLOptions, type Material, type MutableRGBA, type OutlineSection, type RGBA, type RenderGLTFToPNGFromGLBOptions, type RenderGLTFToPNGFromURLOptions, type RenderOptions, type RenderOptionsInput, type RenderResult, SoftwareRenderer, bufferFromDataURI, buildCamera, computeSmoothNormals, computeWorldAABB, createEdgeDrawCall, createGrid, createInfiniteGrid, createOutlineSection, createSceneFromGLTF, createUint8Bitmap, decodeImageFromBuffer, drawInfiniteGrid, encodePNG, getDefaultRenderOptions, hexToRgb, isJPG, isPNG, loadGLTFWithResourcesFromURL, renderDrawCalls, renderGLTFToPNGFromGLB, renderGLTFToPNGFromURL, renderSceneFromGLTF, resolveRenderOptions };