@vessel-dsp/stompbox 0.6.4

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.
Files changed (63) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +471 -0
  3. package/assets/cad/parts/box-125b/.tayda-a7244.step.glb +0 -0
  4. package/assets/cad/parts/box-125b/tayda-a7244.step +3588 -0
  5. package/assets/cad/parts/box-1590b/.tayda-a6619.step.glb +0 -0
  6. package/assets/cad/parts/box-1590b/tayda-a6619.step +3587 -0
  7. package/assets/cad/parts/box-1590bb/.tayda-a5880.step.glb +0 -0
  8. package/assets/cad/parts/box-1590bb/tayda-a5880.step +3589 -0
  9. package/assets/cad/parts/box-hammond-diecast-stompbox-series/.hammond-1590a.step.glb +0 -0
  10. package/assets/cad/parts/box-hammond-diecast-stompbox-series/.hammond-1590n1.step.glb +0 -0
  11. package/assets/cad/parts/box-hammond-diecast-stompbox-series/.hammond-1590xx.step.glb +0 -0
  12. package/assets/cad/parts/box-hammond-diecast-stompbox-series/hammond-1590a.step +3587 -0
  13. package/assets/cad/parts/box-hammond-diecast-stompbox-series/hammond-1590n1.step +3589 -0
  14. package/assets/cad/parts/box-hammond-diecast-stompbox-series/hammond-1590xx.step +3589 -0
  15. package/assets/cad/parts/dc-socket-dc099/.dc099.step.glb +0 -0
  16. package/assets/cad/parts/dc-socket-dc099/dc099.step +261 -0
  17. package/assets/cad/parts/jack-ts-pj629han/.pj-629han-05.step.glb +0 -0
  18. package/assets/cad/parts/jack-ts-pj629han/pj-629han-05.step +261 -0
  19. package/assets/cad/parts/knob-chickenhead-lms-30mm/.lovemyswitches-chicken-head-30mm.step.glb +0 -0
  20. package/assets/cad/parts/knob-chickenhead-lms-30mm/lovemyswitches-chicken-head-30mm.step +535 -0
  21. package/assets/cad/parts/knob-cm42-bb/.tayda-a6078-cm42-bb.step.glb +0 -0
  22. package/assets/cad/parts/knob-cm42-bb/tayda-a6078-cm42-bb.step +535 -0
  23. package/assets/cad/parts/knob-davies-instrument-series/.davies-1100.step.glb +0 -0
  24. package/assets/cad/parts/knob-davies-instrument-series/.davies-1105.step.glb +0 -0
  25. package/assets/cad/parts/knob-davies-instrument-series/.davies-1110.step.glb +0 -0
  26. package/assets/cad/parts/knob-davies-instrument-series/.davies-1120.step.glb +0 -0
  27. package/assets/cad/parts/knob-davies-instrument-series/.davies-1510bg.step.glb +0 -0
  28. package/assets/cad/parts/knob-davies-instrument-series/.davies-1550ag.step.glb +0 -0
  29. package/assets/cad/parts/knob-davies-instrument-series/.davies-1900.step.glb +0 -0
  30. package/assets/cad/parts/knob-davies-instrument-series/davies-1100.step +7781 -0
  31. package/assets/cad/parts/knob-davies-instrument-series/davies-1105.step +7975 -0
  32. package/assets/cad/parts/knob-davies-instrument-series/davies-1110.step +8754 -0
  33. package/assets/cad/parts/knob-davies-instrument-series/davies-1120.step +8570 -0
  34. package/assets/cad/parts/knob-davies-instrument-series/davies-1510bg.step +1686 -0
  35. package/assets/cad/parts/knob-davies-instrument-series/davies-1550ag.step +3137 -0
  36. package/assets/cad/parts/knob-davies-instrument-series/davies-1900.step +19769 -0
  37. package/assets/cad/parts/knob-mxr-style-fluted/.daier-mf-b01.step.glb +0 -0
  38. package/assets/cad/parts/knob-mxr-style-fluted/.daier-mf-b02.step.glb +0 -0
  39. package/assets/cad/parts/knob-mxr-style-fluted/.daier-mf-b03.step.glb +0 -0
  40. package/assets/cad/parts/knob-mxr-style-fluted/.tayda-a1829-tymf-b00.step.glb +0 -0
  41. package/assets/cad/parts/knob-mxr-style-fluted/daier-mf-b01.step +4277 -0
  42. package/assets/cad/parts/knob-mxr-style-fluted/daier-mf-b02.step +4318 -0
  43. package/assets/cad/parts/knob-mxr-style-fluted/daier-mf-b03.step +4577 -0
  44. package/assets/cad/parts/knob-mxr-style-fluted/tayda-a1829-tymf-b00.step +535 -0
  45. package/assets/cad/parts/led-5mm-red-kento-5408urc/.kento-5408urc.step.glb +0 -0
  46. package/assets/cad/parts/led-5mm-red-kento-5408urc/kento-5408urc.step +384 -0
  47. package/assets/cad/parts/led-bezel-lh5/.pedal-parts-and-kits-bzl-5mm-p.step.glb +0 -0
  48. package/assets/cad/parts/led-bezel-lh5/pedal-parts-and-kits-bzl-5mm-p.step +469 -0
  49. package/assets/cad/parts/switch-3pdt-pic-pbs24302/.pic-pbs24302.step.glb +0 -0
  50. package/assets/cad/parts/switch-3pdt-pic-pbs24302/pic-pbs24302.step +435 -0
  51. package/assets/cad/parts/switch-toggle-reference-series/.toggle-switch-dpdt-pcb.step.glb +0 -0
  52. package/assets/cad/parts/switch-toggle-reference-series/.toggle-switch-mounting-plate.step.glb +0 -0
  53. package/assets/cad/parts/switch-toggle-reference-series/.toggle-switch-spdt-pcb.step.glb +0 -0
  54. package/assets/cad/parts/switch-toggle-reference-series/.toggle-switch-spst-13x7.step.glb +0 -0
  55. package/assets/cad/parts/switch-toggle-reference-series/toggle-switch-dpdt-pcb.step +2860 -0
  56. package/assets/cad/parts/switch-toggle-reference-series/toggle-switch-mounting-plate.step +1090 -0
  57. package/assets/cad/parts/switch-toggle-reference-series/toggle-switch-spdt-pcb.step +1749 -0
  58. package/assets/cad/parts/switch-toggle-reference-series/toggle-switch-spst-13x7.step +1608 -0
  59. package/dist/index.d.ts +714 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +4462 -0
  62. package/dist/index.js.map +1 -0
  63. package/package.json +55 -0
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) VesselDSP contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,471 @@
1
+ # @vessel-dsp/stompbox
2
+
3
+ Headless stompbox drill-layout and preview manifest helpers for `.vdsp`
4
+ documents parsed by `@vessel-dsp/core`.
5
+
6
+ This package does not render UI. It emits headless artifacts that downstream
7
+ tools can display or save:
8
+
9
+ - drill-layout manifests;
10
+ - drill-template SVG strings in `preview` and A4 `print` modes;
11
+ - mesh-backed stompbox preview GLB bytes assembled from caller-provided CAD
12
+ part GLBs and STEP companions via `hardwareProfile` plus `basePath`;
13
+ - orthographic preview SVG views for `top`, `bottom`, `left`, `right`, and
14
+ `back`;
15
+ - optional text, SVG, or image decals for brand/model/custom sticker artwork;
16
+ - headless pedal state, interaction, and preview-patch helpers for live
17
+ stompbox previews.
18
+
19
+ Applications own production part profiles, enclosure profiles, and asset roots.
20
+ Keep named style presets in your application or docs layer and pass them through
21
+ `styleProfile` when you want preset-specific default parts or placement rules.
22
+
23
+ Drill-template holes are fabrication holes: their circle diameters come from
24
+ each part profile's panel drill clearance, such as the PJ-629HAN 9.5 mm drill
25
+ and the DC099 8 mm barrel opening. Preview views and collision checks may use
26
+ larger visible exterior geometry, such as jack rings, bushings, nuts, and
27
+ bezels.
28
+
29
+ When `.vdsp` physical placement is present, stompbox preserves it as
30
+ `vdsp-declared`. When placement or common enclosure hardware is absent, stompbox
31
+ generates deterministic `auto-generated` placements for knobs, status LED,
32
+ bypass footswitch, input/output jacks, and the 9V connector. Set
33
+ `includePowerJack: false` to omit the synthesized 9V connector.
34
+
35
+ Pass `decals` to preview or drill-template helpers to place custom text, SVG,
36
+ or image artwork on the enclosure. Preview SVG views render the decal content
37
+ on the box, preview GLB output includes decal plane nodes with sticker metadata,
38
+ drill template preview mode shows decal outlines, and A4 print mode places the
39
+ decals in a separate sticker sheet area.
40
+
41
+ Decals can target the `top`, `left`, `right`, `back`, or `bottom` plane. Use
42
+ `placement: { kind: "grid" }` when the sticker should snap to the center of a
43
+ face grid cell. The grid uses the requested `columns` and `rows`, capped so
44
+ each cell remains at least 12 mm wide or tall. For example, a 40 mm wide face
45
+ can address at most 3 columns.
46
+
47
+ ```ts
48
+ const decals = [
49
+ {
50
+ id: "brand-label",
51
+ kind: "text",
52
+ text: "FUZZ LAB",
53
+ face: "top",
54
+ color: "#2563eb",
55
+ fontFamily: '"Roboto", sans-serif',
56
+ placement: { kind: "grid", columns: 4, rows: 4, column: 4, row: 2 },
57
+ sizeMm: { widthMm: 28, heightMm: 7 },
58
+ },
59
+ {
60
+ id: "side-vector",
61
+ kind: "svg",
62
+ face: "left",
63
+ svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><path d="M1 5 H9" stroke="currentColor"/></svg>',
64
+ color: "#ef4444",
65
+ placement: { kind: "grid", columns: 2, rows: 4, column: 2, row: 3 },
66
+ sizeMm: { widthMm: 10, heightMm: 8 },
67
+ },
68
+ {
69
+ id: "back-image",
70
+ kind: "image",
71
+ face: "back",
72
+ href: "/artwork/sticker.png",
73
+ placement: { kind: "grid", columns: 4, rows: 1, column: 4, row: 1 },
74
+ sizeMm: { widthMm: 16, heightMm: 8 },
75
+ },
76
+ ] as const;
77
+ ```
78
+
79
+ ## Helper map
80
+
81
+ Use `FromVdsp` helpers when the caller has serialized `.vdsp` text. Use the
82
+ document helpers when the caller has already parsed or edited a
83
+ `CircuitDocument`.
84
+
85
+ | Need | `.vdsp` helper | `CircuitDocument` helper |
86
+ | --- | --- | --- |
87
+ | Drill hole coordinates and diagnostics | `createStompboxDrillLayoutFromVdsp` | `createStompboxDrillLayout` |
88
+ | Drill-template manifest | `createStompboxDrillTemplateFromVdsp` | `createStompboxDrillTemplate` |
89
+ | Drill-template SVG string | `createStompboxDrillTemplateSvgFromVdsp` | `createStompboxDrillTemplateSvg` |
90
+ | Preview manifest | `createStompboxPreviewFromVdsp` | `createStompboxPreview` |
91
+ | Orthographic 2D SVG views | `createStompboxPreviewSvgViewsFromVdsp` | `createStompboxPreviewSvgViews` |
92
+ | Mesh-backed 3D GLB bytes | `createStompboxPreviewGlbFromVdsp` | `createStompboxPreviewGlb` |
93
+ | Frontend recolor patch | `createStompboxAppearancePatch` | `createStompboxAppearancePatch` |
94
+ | Resolved appearance alias | `resolveStompboxAppearance` | `resolveStompboxAppearance` |
95
+ | Source/compiled control surface | `parseCircuitDocumentFile` then `createStompboxControlSurface` | `createStompboxControlSurface` |
96
+ | Default live pedal state | `createDefaultStompboxPedalStateFromVdsp` | `createDefaultStompboxPedalState` |
97
+ | Headless live state store | `createStompboxPedalStateStore` | `createStompboxPedalStateStore` |
98
+ | Preview state patch | `createStompboxPreviewStatePatch` | `createStompboxPreviewStatePatch` |
99
+
100
+ Generated docs examples are published at:
101
+
102
+ - 2D preview SVG: `/core/examples/stompbox-mxr-style-preview-top.svg`
103
+ - 3D preview GLB: `/core/examples/stompbox-mxr-style-preview.glb`
104
+ - Drill-template preview SVG: `/core/examples/stompbox-mxr-style-drill-template-preview.svg`
105
+ - Drill-layout JSON: `/core/examples/stompbox-mxr-style-drill-layout.json`
106
+
107
+ ## Providing CAD assets for 3D preview
108
+
109
+ `createStompboxPreviewGlbFromVdsp()` and `createStompboxPreviewGlb()` read GLB
110
+ files from disk. Put each part and enclosure GLB under an application-owned
111
+ asset root, reference those files from `hardwareProfile`, and pass that root as
112
+ `basePath`.
113
+
114
+ ```ts
115
+ import {
116
+ createStompboxPreviewGlbFromVdsp,
117
+ type StompboxHardwareProfile,
118
+ } from "@vessel-dsp/stompbox";
119
+
120
+ const hardwareProfile: StompboxHardwareProfile = {
121
+ id: "my-app-hardware",
122
+ label: "My app hardware",
123
+ defaultEnclosureId: "box-1590b",
124
+ defaultPartIds: {
125
+ knob: "my-knob",
126
+ largeKnob: "my-knob",
127
+ smallKnob: "my-knob",
128
+ led: "my-led",
129
+ footswitch: "my-footswitch",
130
+ audioJack: "my-audio-jack",
131
+ dcJack: "my-dc-jack",
132
+ },
133
+ enclosureProfiles: {
134
+ "box-1590b": {
135
+ variantId: "box-1590b",
136
+ label: "1590B enclosure",
137
+ dimensionsMm: { widthMm: 60.5, lengthMm: 111.5, depthMm: 31 },
138
+ topFace: { usableRectMm: { x: -29.25, y: -54.75, width: 58.5, height: 109.5 } },
139
+ assets: {
140
+ glbRelativePath: "enclosures/1590b.glb",
141
+ stepRelativePath: "enclosures/1590b.step",
142
+ },
143
+ },
144
+ },
145
+ partProfiles: {
146
+ "my-knob": {
147
+ id: "my-knob",
148
+ label: "My knob",
149
+ family: "knob",
150
+ level: "exterior",
151
+ status: "generated-stub",
152
+ panelHoleDrillMm: 7.14375,
153
+ drillHoleProfileId: "sixteen-mm-pot-9-32",
154
+ geometry: { kind: "knob", diameterMm: 20, depthMm: 11, shaftDiameterMm: 6.35 },
155
+ assets: {
156
+ glbRelativePath: "parts/my-knob.glb",
157
+ stepRelativePath: "parts/my-knob.step",
158
+ },
159
+ },
160
+ // Add my-led, my-footswitch, my-audio-jack, and my-dc-jack.
161
+ },
162
+ };
163
+
164
+ const glb = createStompboxPreviewGlbFromVdsp(vdspSource, {
165
+ hardwareProfile,
166
+ basePath: "/absolute/path/to/cad-assets",
167
+ });
168
+ ```
169
+
170
+ The example above reads `/absolute/path/to/cad-assets/parts/my-knob.glb` and
171
+ `/absolute/path/to/cad-assets/enclosures/1590b.glb`. Use `baseUrl` for served
172
+ preview references; use `basePath` when assembling a GLB because the files are
173
+ read from the filesystem.
174
+
175
+ ## Live pedal state
176
+
177
+ Stompbox can manage preview state for a pedal instance without owning the UI or
178
+ audio runtime. Use the state helpers to rotate knobs, light LEDs, depress
179
+ footswitches, and drive synthesized bypass hardware in the generated preview.
180
+
181
+ ```ts
182
+ import {
183
+ createDefaultStompboxPedalStateFromVdsp,
184
+ createStompboxControlSurface,
185
+ createStompboxPedalStateStore,
186
+ createStompboxPreviewFromVdsp,
187
+ } from "@vessel-dsp/stompbox";
188
+ import { parseCircuitDocumentFile } from "@vessel-dsp/core";
189
+
190
+ const document = parseCircuitDocumentFile(vdspSource, { filename: "pedal.vdsp" });
191
+ const surface = createStompboxControlSurface(document, {
192
+ pedalId: "stage-1",
193
+ compiledControls: runtimeControlsFromYourCompiler,
194
+ });
195
+ const preview = createStompboxPreviewFromVdsp(vdspSource, { hardwareProfile });
196
+ const state = createDefaultStompboxPedalStateFromVdsp(vdspSource, {
197
+ pedalId: "stage-1",
198
+ });
199
+ const store = createStompboxPedalStateStore(state, { preview });
200
+
201
+ store.subscribePreviewPatch((patch) => {
202
+ // Apply patch.parts["part-knob-GAIN"], patch.parts["part-led-status"], etc.
203
+ // to your SVG elements or loaded GLB node/material objects.
204
+ });
205
+
206
+ store.turnKnob("GAIN", 0.82);
207
+ store.pressFootswitch("switch-bypass", true);
208
+ ```
209
+
210
+ `compiledControls` is optional. When present, pass a downstream compiler output
211
+ shaped like `{ id, sourceComponentId, name, kind, value, min, max, step }` plus
212
+ optional `unit`, `sweep`, `options`, and `targets`. Stompbox uses it only to
213
+ merge audio-bound control metadata with `.vdsp` source-panel order and to
214
+ return headless runtime command descriptions. It does not compile `.vdsp` or
215
+ send worklet messages.
216
+
217
+ Live GLB previews require semantic state targets on user-provided LED and
218
+ footswitch part assets. Declare those targets in the part profile so renderers
219
+ can affect the LED lens and the moving footswitch actuator instead of guessing
220
+ against the whole part assembly:
221
+
222
+ ```ts
223
+ const ledPartProfile = {
224
+ // ...
225
+ stateTargets: {
226
+ led: {
227
+ lens: {
228
+ selector: {
229
+ nodeName: "o1.2",
230
+ meshNameIncludes: "5mm_led_lens",
231
+ },
232
+ },
233
+ },
234
+ },
235
+ };
236
+
237
+ const footswitchPartProfile = {
238
+ // ...
239
+ stateTargets: {
240
+ footswitch: {
241
+ actuator: {
242
+ selector: {
243
+ nodeName: "o1.3",
244
+ meshNameIncludes: "plunger",
245
+ },
246
+ },
247
+ travelAxis: "z",
248
+ travelMm: 1.2,
249
+ },
250
+ },
251
+ };
252
+ ```
253
+
254
+ Validate caller-provided GLBs before using them for live state:
255
+
256
+ ```ts
257
+ import {
258
+ validateStompboxGlbAssetFile,
259
+ validateStompboxHardwareProfileAssets,
260
+ } from "@vessel-dsp/stompbox";
261
+
262
+ const ledValidation = validateStompboxGlbAssetFile(
263
+ "/cad/parts/led-bezel-lh5/led.step.glb",
264
+ ledPartProfile,
265
+ );
266
+
267
+ const profileValidation = validateStompboxHardwareProfileAssets(hardwareProfile, {
268
+ basePath: "/cad/parts",
269
+ });
270
+ ```
271
+
272
+ Missing or ambiguous live targets produce diagnostics such as
273
+ `missing-state-target-contract`, `missing-state-target`, and
274
+ `ambiguous-state-target`. Static drill layouts and static SVG previews can still
275
+ be generated; live GLB preview code should treat those diagnostics as a signal
276
+ to avoid whole-part recolor or movement guesses.
277
+
278
+ For pointer interaction, convert viewer events into state commands, then apply
279
+ the resulting patch in your renderer:
280
+
281
+ ```ts
282
+ import {
283
+ applyStompboxPreviewInteraction,
284
+ createStompboxFootswitchPressCommand,
285
+ createStompboxKnobTurnCommand,
286
+ createStompboxPreviewStatePatch,
287
+ } from "@vessel-dsp/stompbox";
288
+
289
+ const knobCommand = createStompboxKnobTurnCommand(surface, {
290
+ controlId: "GAIN",
291
+ position: 0.65,
292
+ });
293
+ const nextState = applyStompboxPreviewInteraction(store.getSnapshot(), knobCommand);
294
+ const patch = createStompboxPreviewStatePatch(preview, nextState, store.getSnapshot());
295
+
296
+ store.dispatch(knobCommand);
297
+ applyPatchToYourViewer(patch);
298
+
299
+ const bypassCommand = createStompboxFootswitchPressCommand(surface, {
300
+ partId: "switch-bypass",
301
+ pressed: true,
302
+ });
303
+ store.dispatch(bypassCommand);
304
+ ```
305
+
306
+ Synthesized `switch-bypass` and `led-status` follow the pedal-level `enabled`
307
+ state. Source-declared knobs, switches, and LEDs follow their `ControlState`
308
+ entries. A 9V connector is still generated by default for pedal previews and can
309
+ be omitted with `includePowerJack: false`.
310
+
311
+ React context, hooks, pointer handlers, SVG mutation, Three.js node updates,
312
+ and audio/worklet routing remain downstream responsibilities. A React app can
313
+ wrap `createStompboxPedalStateStore()` with its own context and
314
+ `useSyncExternalStore`, but `@vessel-dsp/stompbox` stays framework-neutral.
315
+
316
+ ## 2D and 3D preview viewers
317
+
318
+ For static 2D previews, use `createStompboxPreviewSvgViewsFromVdsp()` or
319
+ `createStompboxPreviewSvgViews()` and display the returned `top`, `bottom`,
320
+ `left`, `right`, or `back` SVG string directly in the consuming app.
321
+
322
+ For model-backed 2D or interactive 3D previews, keep Three.js in the consuming
323
+ app. `@vessel-dsp/stompbox` should only create the preview GLB bytes and
324
+ metadata; the app can load that GLB, choose a camera, and add viewer-only
325
+ effects such as CAD-style linework.
326
+
327
+ ```ts
328
+ import * as THREE from "three";
329
+ import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
330
+ import { createStompboxPreviewGlbFromVdsp } from "@vessel-dsp/stompbox";
331
+
332
+ const assembly = createStompboxPreviewGlbFromVdsp(vdspSource, {
333
+ hardwareProfile,
334
+ basePath: "/absolute/path/to/cad-assets",
335
+ });
336
+
337
+ const previewUrl = URL.createObjectURL(
338
+ new Blob([assembly.bytes], { type: assembly.mimeType }),
339
+ );
340
+ const gltf = await new GLTFLoader().loadAsync(previewUrl);
341
+ scene.add(gltf.scene);
342
+ ```
343
+
344
+ Use a perspective camera and orbit controls for a 3D preview. For a 2D
345
+ model-backed preview, use an orthographic camera aimed at the desired face.
346
+ The assembled GLB uses millimeters and stable node names such as
347
+ `enclosure-box-1590b`, `part-knob-GAIN`, and `hole-backing-jack-IN`, so the
348
+ viewer can frame or select individual parts without parsing source documents.
349
+
350
+ ```ts
351
+ function setTopPreviewCamera(
352
+ camera: THREE.OrthographicCamera,
353
+ dimensionsMm: { widthMm: number; lengthMm: number },
354
+ ) {
355
+ const marginMm = 12;
356
+
357
+ camera.left = -dimensionsMm.widthMm / 2 - marginMm;
358
+ camera.right = dimensionsMm.widthMm / 2 + marginMm;
359
+ camera.top = dimensionsMm.lengthMm / 2 + marginMm;
360
+ camera.bottom = -dimensionsMm.lengthMm / 2 - marginMm;
361
+ camera.near = 0.1;
362
+ camera.far = 500;
363
+ camera.position.set(0, 0, 220);
364
+ camera.up.set(0, 1, 0);
365
+ camera.lookAt(0, 0, 0);
366
+ camera.updateProjectionMatrix();
367
+ }
368
+
369
+ setTopPreviewCamera(camera, assembly.preview.enclosure.dimensionsMm);
370
+ ```
371
+
372
+ To render CAD-style border linework, expose a viewer option such as `linework`
373
+ and `lineworkColor`, then add an `EdgesGeometry` overlay after loading the GLB.
374
+ This is a display-only effect and should not be written back into the stompbox
375
+ artifact.
376
+
377
+ ```ts
378
+ function addCadLinework(root: THREE.Object3D, lineworkColor = "#111827") {
379
+ const meshes: THREE.Mesh[] = [];
380
+ const material = new THREE.LineBasicMaterial({
381
+ color: new THREE.Color(lineworkColor),
382
+ transparent: true,
383
+ opacity: 0.85,
384
+ depthTest: true,
385
+ });
386
+
387
+ root.traverse((object) => {
388
+ if (object instanceof THREE.Mesh) {
389
+ meshes.push(object);
390
+ }
391
+ });
392
+
393
+ for (const object of meshes) {
394
+ if (!(object.geometry instanceof THREE.BufferGeometry)) continue;
395
+ const edges = new THREE.EdgesGeometry(object.geometry, 35);
396
+ const lines = new THREE.LineSegments(edges, material);
397
+
398
+ lines.name = `${object.name || "mesh"}-cad-linework`;
399
+ lines.renderOrder = 10;
400
+ object.add(lines);
401
+ }
402
+ }
403
+
404
+ const linework = true;
405
+ const lineworkColor = "#eb7223";
406
+
407
+ if (linework) {
408
+ addCadLinework(gltf.scene, lineworkColor);
409
+ }
410
+ ```
411
+
412
+ `LineBasicMaterial` is usually limited to one device pixel in WebGL. If the
413
+ viewer needs heavier strokes, use Three.js `LineSegments2` and `LineMaterial`
414
+ from `three/addons/lines` in the application layer.
415
+
416
+ ## Appearance customization
417
+
418
+ Pass `appearance` to preview, GLB, SVG-view, or drill-template helpers to style
419
+ the generated artifacts without changing `.vdsp` placement data. `state` remains
420
+ for live values such as knob position, LED on/off, and footswitch pressed state;
421
+ `appearance` is for enclosure, LED, label, template, and non-knob material
422
+ hints. Knob bodies keep the material colors from their imported CAD/GLB assets.
423
+
424
+ ```ts
425
+ import {
426
+ createStompboxAppearancePatch,
427
+ createStompboxPreviewFromVdsp,
428
+ createStompboxPreviewSvgViewsFromVdsp,
429
+ } from "@vessel-dsp/stompbox";
430
+
431
+ const appearance = {
432
+ enclosure: { color: "#f97316", strokeColor: "#7c2d12", roughnessFactor: 0.45 },
433
+ template: {
434
+ guideColor: "#0ea5e9",
435
+ foldColor: "#334155",
436
+ holeStrokeColor: "#7c3aed",
437
+ holeFillColor: "#faf5ff",
438
+ centerDotColor: "#581c87",
439
+ },
440
+ defaults: {
441
+ led: { color: "#ef4444", offColor: "#fee2e2" },
442
+ label: { color: "#111827" },
443
+ },
444
+ controls: {
445
+ GAIN: {
446
+ label: { text: "DRIVE", color: "#ffffff" },
447
+ },
448
+ LED1: {
449
+ led: { color: "#22c55e", offColor: "#064e3b" },
450
+ label: { text: "READY", color: "#16a34a" },
451
+ },
452
+ },
453
+ } as const;
454
+
455
+ const preview = createStompboxPreviewFromVdsp(vdspSource, {
456
+ hardwareProfile,
457
+ appearance,
458
+ });
459
+ const views = createStompboxPreviewSvgViewsFromVdsp(vdspSource, {
460
+ hardwareProfile,
461
+ appearance,
462
+ });
463
+ const patch = createStompboxAppearancePatch(preview);
464
+ ```
465
+
466
+ Preview SVG output includes stable hooks such as `data-control-id`,
467
+ `data-part-family`, `.knob-body`, `.knob-indicator`, `.led-lens`, `.label-text`,
468
+ `.top-panel`, `.hole`, `.drill-hole-center-dot`, `.fold-line`, and
469
+ `.guide-line`. Preview GLB output bakes available material colors into copied
470
+ GLB materials and writes the same frontend-friendly patch into
471
+ `asset.extras.appearance`. Knob GLB materials are left as imported.