pcb-scene3d-viewer 1.1.49 → 1.2.0
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 +46 -21
- package/docs/api.md +109 -8
- package/docs/circuitjson.md +143 -29
- package/docs/model-format.md +50 -7
- package/docs/release-notes-v1.2.0.md +122 -0
- package/docs/testing.md +30 -1
- package/package.json +6 -3
- package/spec/library-scope.md +10 -2
- package/src/CircuitJsonCadModelAssetResolver.mjs +697 -83
- package/src/PcbAssemblyBoardSubstrateBuilder.mjs +43 -0
- package/src/PcbAssemblyGeometryBuilder.mjs +40 -18
- package/src/PcbAssemblyModelMeshLoader.mjs +101 -150
- package/src/PcbAssemblyPadMeshBuilder.mjs +22 -0
- package/src/PcbModelArchiveExporter.mjs +28 -112
- package/src/PcbModelArchiveSourceBundle.mjs +326 -0
- package/src/PcbScene3dAabbIndex.mjs +464 -0
- package/src/PcbScene3dBoardAssemblyPresentation.mjs +1 -1
- package/src/PcbScene3dBoardEdgeCutoutBuilder.mjs +9 -5
- package/src/PcbScene3dBoardMaterialPalette.mjs +29 -0
- package/src/PcbScene3dBoardShapeFactory.mjs +11 -118
- package/src/PcbScene3dBoardSolderMaskFactory.mjs +65 -39
- package/src/PcbScene3dCircuitJsonAdapter.mjs +151 -48
- package/src/PcbScene3dCircuitJsonDrillDetail.mjs +31 -0
- package/src/PcbScene3dCircuitJsonGeometry.mjs +184 -33
- package/src/PcbScene3dCircuitJsonInput.mjs +132 -0
- package/src/PcbScene3dCircuitJsonModelAsset.mjs +40 -0
- package/src/PcbScene3dController.mjs +40 -34
- package/src/PcbScene3dCopperFactory.mjs +36 -22
- package/src/PcbScene3dCopperFillAreaClipper.mjs +133 -235
- package/src/PcbScene3dCopperFillCoverageContext.mjs +204 -0
- package/src/PcbScene3dCopperFillLoopSetResolver.mjs +192 -0
- package/src/PcbScene3dCopperFillMeshBuilder.mjs +77 -295
- package/src/PcbScene3dCopperTextFactory.mjs +12 -4
- package/src/PcbScene3dCutoutCircleDetector.mjs +34 -17
- package/src/PcbScene3dCutoutGeometryFilter.mjs +104 -269
- package/src/PcbScene3dCutoutGridIndex.mjs +184 -0
- package/src/PcbScene3dDeferredModelFinalizer.mjs +52 -0
- package/src/PcbScene3dDescriptorSafeRecord.mjs +38 -0
- package/src/PcbScene3dDrillCutoutFilter.mjs +149 -143
- package/src/PcbScene3dDrillPathFactory.mjs +86 -16
- package/src/PcbScene3dDrillVoidFactory.mjs +35 -10
- package/src/PcbScene3dExternalModelGroupLoader.mjs +472 -31
- package/src/PcbScene3dExternalModels.mjs +23 -24
- package/src/PcbScene3dFacetedModelGroupBuilder.mjs +217 -0
- package/src/PcbScene3dGeometryZCompressor.mjs +4 -2
- package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +18 -4
- package/src/PcbScene3dMaskCoveredCopperSurfaceFilter.mjs +75 -10
- package/src/PcbScene3dModelContent.mjs +236 -0
- package/src/PcbScene3dModelFetchPolicy.mjs +304 -0
- package/src/PcbScene3dModelIdentity.mjs +106 -0
- package/src/PcbScene3dPlatedDrillSpecResolver.mjs +141 -0
- package/src/PcbScene3dPreparedPolygon.mjs +709 -0
- package/src/PcbScene3dPreparedPolygonSet.mjs +70 -0
- package/src/PcbScene3dRuntime.mjs +45 -45
- package/src/PcbScene3dRuntimeBoardMeshes.mjs +85 -1
- package/src/PcbScene3dShapeHoleGeometryCleaner.mjs +4 -2
- package/src/PcbScene3dShellRenderer.mjs +75 -6
- package/src/PcbScene3dSilkscreenCutoutContext.mjs +255 -0
- package/src/PcbScene3dSilkscreenFactory.mjs +87 -127
- package/src/PcbScene3dSilkscreenFillSeamBuilder.mjs +11 -5
- package/src/PcbScene3dStepLoader.mjs +11 -10
- package/src/PcbScene3dText.mjs +1 -1
- package/src/PcbScene3dTriangleVertexQueryBounds.mjs +302 -0
package/README.md
CHANGED
|
@@ -7,12 +7,13 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
7
7
|
# PCB Scene3D Viewer
|
|
8
8
|
|
|
9
9
|
Reusable browser-side 3D PCB viewer utilities for normalized ECAD scene
|
|
10
|
-
descriptions and
|
|
10
|
+
descriptions and canonical CircuitJSON documents.
|
|
11
11
|
|
|
12
12
|
This package renders scene descriptions produced by packages such as
|
|
13
13
|
`altium-toolkit/scene3d` and `kicad-toolkit/scene3d`. It does not parse ECAD
|
|
14
14
|
source files and does not build format-specific scene data. Hosts that already
|
|
15
|
-
have CircuitJSON can pass
|
|
15
|
+
have CircuitJSON can pass a common `DocumentResult`, a prepared
|
|
16
|
+
`CircuitJsonDocumentContext`, or an element array directly to the controller or
|
|
16
17
|
runtime.
|
|
17
18
|
|
|
18
19
|
The package was extracted from [ECAD Forge](https://ecadforge.app/), where it
|
|
@@ -21,6 +22,41 @@ factories, model loading, component picking, view presets, archive export,
|
|
|
21
22
|
GLTF/GLB assembly writing, and optional DOM shell can be reused by other
|
|
22
23
|
browser-based ECAD tools.
|
|
23
24
|
|
|
25
|
+
## CircuitJSON 1.1 convergence
|
|
26
|
+
|
|
27
|
+
Version 1.2.0 accepts the common document and prepared-context shapes returned
|
|
28
|
+
by CircuitJSON, Gerber, Altium, and KiCad Toolkit 1.1-compatible APIs. The
|
|
29
|
+
adapter requests the shared `elements` index once and reuses it across repeated
|
|
30
|
+
scene builds. `PcbScene3dCircuitJsonAdapter.prepare()` exposes that proof-aware
|
|
31
|
+
path to hosts. Existing bare arrays are normalized by the shared CircuitJSON
|
|
32
|
+
boundary, while legacy parser-compatible hybrid arrays retain their native
|
|
33
|
+
builder behavior. Canonical `model_asset` paths and matching document or session
|
|
34
|
+
assets are resolved directly by the adapter; hosts do not need an app-side
|
|
35
|
+
document transform or resolver wrapper. Document asset indexes are created only
|
|
36
|
+
for referenced models and cached through a prepared context. Exact
|
|
37
|
+
case-sensitive paths win; case-insensitive fallback is used only when unique.
|
|
38
|
+
Polygon-plated holes use the shared CircuitJSON hole primitive model, including
|
|
39
|
+
rotation-local `pad_outline` extents and pill-slot width, height, and rotation;
|
|
40
|
+
outer-pad and drill rotations remain independent and board-space drill angles
|
|
41
|
+
are applied exactly once. Gerber routed slots therefore retain horizontal,
|
|
42
|
+
diagonal, and vertical canonical geometry in the viewer. Multiple disjoint
|
|
43
|
+
`pcb_board` rows (or multiple `pcb_panel` rows) render as independent substrate,
|
|
44
|
+
outline, mask, and export contours instead of dropping every row after the
|
|
45
|
+
first.
|
|
46
|
+
Legal rectangular and square CircuitJSON drill apertures retain their exact
|
|
47
|
+
width, height, and board-space rotation through substrate, pad, and assembly
|
|
48
|
+
export meshes.
|
|
49
|
+
|
|
50
|
+
The live runtime loads STEP/STP, WRL/VRML, STL, OBJ, GLTF/GLB, and 3MF from
|
|
51
|
+
canonical text/bytes or browser files. Referenced GLTF buffers, OBJ material
|
|
52
|
+
libraries, and WRL textures are attached from matching document/session assets
|
|
53
|
+
using safe project-relative paths. URL loading is explicit through
|
|
54
|
+
`modelLoaderOptions.fetch` or `allowNetworkModelFetch: true`, with optional
|
|
55
|
+
headers, timeout, cache, and bounded-resource settings. Static `authHeaders`
|
|
56
|
+
stay on the main model origin; `authHeadersForUrl` is the explicit per-URL
|
|
57
|
+
override. The model ZIP exporter uses the same policy and writes each raw model
|
|
58
|
+
under its original source basename with safe GLTF, OBJ, and WRL companions.
|
|
59
|
+
|
|
24
60
|
## Install
|
|
25
61
|
|
|
26
62
|
```bash
|
|
@@ -61,37 +97,26 @@ const controller = new PcbScene3dController(
|
|
|
61
97
|
controller.setSelectedComponent('U1')
|
|
62
98
|
```
|
|
63
99
|
|
|
64
|
-
|
|
100
|
+
Canonical document input does not require a format-specific `buildScene`
|
|
65
101
|
callback:
|
|
66
102
|
|
|
67
103
|
```js
|
|
68
104
|
import { PcbScene3dController } from 'pcb-scene3d-viewer'
|
|
105
|
+
import { Parser } from 'gerber-toolkit'
|
|
69
106
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
height: 30,
|
|
75
|
-
thickness: 1.6,
|
|
76
|
-
center: { x: 25, y: 15 }
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
type: 'pcb_component',
|
|
80
|
-
source_component_id: 'source-r1',
|
|
81
|
-
layer: 1,
|
|
82
|
-
center: { x: 20, y: 15 },
|
|
83
|
-
width: 3,
|
|
84
|
-
height: 1.5
|
|
85
|
-
}
|
|
86
|
-
]
|
|
107
|
+
const document = await Parser.parseAsync({
|
|
108
|
+
fileName: file.name,
|
|
109
|
+
data: await file.arrayBuffer()
|
|
110
|
+
})
|
|
87
111
|
|
|
88
|
-
const controller = new PcbScene3dController(viewportNode,
|
|
112
|
+
const controller = new PcbScene3dController(viewportNode, document)
|
|
89
113
|
```
|
|
90
114
|
|
|
91
115
|
## Documentation
|
|
92
116
|
|
|
93
117
|
- [API](docs/api.md)
|
|
94
118
|
- [CircuitJSON usage](docs/circuitjson.md)
|
|
119
|
+
- [1.2.0 release notes](docs/release-notes-v1.2.0.md)
|
|
95
120
|
- [Model format](docs/model-format.md)
|
|
96
121
|
- [Testing](docs/testing.md)
|
|
97
122
|
- [Library scope](spec/library-scope.md)
|
package/docs/api.md
CHANGED
|
@@ -8,6 +8,10 @@ The package exports all public APIs from `pcb-scene3d-viewer` and
|
|
|
8
8
|
### `PcbScene3dShellRenderer.render(documentModel, translate?)`
|
|
9
9
|
|
|
10
10
|
Returns HTML markup for the optional interactive 3D scene shell.
|
|
11
|
+
`documentModel` accepts a legacy scene/parser model, a dense CircuitJSON array,
|
|
12
|
+
an `ecad-toolkit.document.v1` result, or a prepared
|
|
13
|
+
`CircuitJsonDocumentContext`. CircuitJSON shapes use the shared context index to
|
|
14
|
+
derive board, component, and BOM summary counts.
|
|
11
15
|
|
|
12
16
|
The markup includes:
|
|
13
17
|
|
|
@@ -36,10 +40,21 @@ Important options:
|
|
|
36
40
|
- `createModelRegistry`: optional function passed into `buildScene`.
|
|
37
41
|
- `createRuntime`: optional runtime factory for tests or custom hosts.
|
|
38
42
|
- `sessionAssets`: companion model assets passed to scene preparation.
|
|
43
|
+
- `modelLoaderOptions`: optional runtime model-loading policy. It accepts the
|
|
44
|
+
same `fetch`, `allowNetworkModelFetch`, `authHeaders`, `authHeadersForUrl`,
|
|
45
|
+
`fetchTimeoutMs`, `modelCache`, `maxModelBytes`, `maxModelResources`, and
|
|
46
|
+
`maxModelTotalBytes` fields as `PcbAssemblyModelMeshLoader`. The policy is
|
|
47
|
+
also forwarded unchanged to model ZIP export.
|
|
39
48
|
- `setLoadingVisible`: callback for shell loading state.
|
|
40
49
|
- `onComponentSelectionChange`: callback for 3D picks.
|
|
41
50
|
- `translate`: optional `(key) => string` translation function.
|
|
42
51
|
|
|
52
|
+
Preparation precedence is explicit: `sceneDescription`, then
|
|
53
|
+
`scenePrepClient`, then direct canonical CircuitJSON adaptation, and finally
|
|
54
|
+
the legacy `buildScene` callback. If asynchronous scene preparation fails, a
|
|
55
|
+
canonical document still falls back to direct adaptation with the supplied
|
|
56
|
+
adapter and asset options.
|
|
57
|
+
|
|
43
58
|
Methods:
|
|
44
59
|
|
|
45
60
|
- `getDocumentModel()`: returns the mounted document model.
|
|
@@ -55,15 +70,35 @@ controller only and are forwarded to the runtime through
|
|
|
55
70
|
|
|
56
71
|
### `PcbScene3dCircuitJsonAdapter`
|
|
57
72
|
|
|
58
|
-
Converts
|
|
59
|
-
|
|
73
|
+
Converts a common CircuitJSON `DocumentResult`, prepared
|
|
74
|
+
`CircuitJsonDocumentContext`, or serialized element array into the normalized
|
|
75
|
+
scene description consumed by the runtime.
|
|
60
76
|
|
|
61
77
|
Methods:
|
|
62
78
|
|
|
63
|
-
- `isCircuitJsonModel(value)`: returns true for
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- `
|
|
79
|
+
- `isCircuitJsonModel(value)`: returns true for accepted document, context, or
|
|
80
|
+
structurally valid array inputs without freezing or mutating an unprepared
|
|
81
|
+
caller value. Shared normalization and schema validation occur in `prepare`.
|
|
82
|
+
- `isDirectCircuitJsonModel(value)`: returns true when the canonical input
|
|
83
|
+
should bypass host `buildScene` callbacks.
|
|
84
|
+
- `prepare(circuitJson)`: returns a `CircuitJsonDocumentContext`, normalizes
|
|
85
|
+
supported legacy rows through the shared CircuitJSON boundary, reuses a
|
|
86
|
+
canonical document validation proof, and prepares the `elements` index once.
|
|
87
|
+
- `build(circuitJson, options?)`: returns a runtime-ready scene description and
|
|
88
|
+
reuses an existing context `elements` index when supplied. Canonical
|
|
89
|
+
`cad_component.model_asset` records are consumed directly and matched against
|
|
90
|
+
canonical document assets or `options.sessionAssets` through a prebuilt
|
|
91
|
+
descriptor-safe alias index. The index is not built for documents without
|
|
92
|
+
model references, is cached in a supplied context for repeated builds, and
|
|
93
|
+
materializes a canonical payload only when its first model reference is used.
|
|
94
|
+
Plated-hole copper and drill geometry comes from the shared
|
|
95
|
+
`CircuitJsonPcbHolePrimitiveModel`, so polygon pad outlines and pill slots use
|
|
96
|
+
the same canonical shape and dimensions as the producing toolkit. Drill
|
|
97
|
+
rotation is board-space and is never added to pad rotation a second time.
|
|
98
|
+
Rectangular and square apertures retain width, height, and rotation rather
|
|
99
|
+
than being approximated as circles or pill slots, including assembly export.
|
|
100
|
+
Every selected panel contour, or every board contour when no panel exists,
|
|
101
|
+
is retained in `board.contours` and rendered/exported independently.
|
|
67
102
|
`options.modelUrlResolver` can attach caller-owned URL resolution metadata to
|
|
68
103
|
`cad_component` external models without fetching them. `projectBaseUrl`
|
|
69
104
|
resolves relative model URLs and package-style `node_modules/...` model paths,
|
|
@@ -77,6 +112,31 @@ Methods:
|
|
|
77
112
|
when they receive direct CircuitJSON input. See
|
|
78
113
|
[CircuitJSON usage](circuitjson.md) for supported elements, units, and examples.
|
|
79
114
|
|
|
115
|
+
### `CircuitJsonCadModelAssetResolver`
|
|
116
|
+
|
|
117
|
+
`withModelAssetUrls(document)` accepts an element array, common
|
|
118
|
+
`DocumentResult`, or prepared context. It returns the same input shape when no
|
|
119
|
+
derived URL is needed; otherwise it returns a new array, canonical document
|
|
120
|
+
envelope, or prepared context with `model_asset` paths promoted to explicit
|
|
121
|
+
CircuitJSON model URL fields. Source metadata, extensions, assets, diagnostics,
|
|
122
|
+
and statistics are preserved. This utility remains available for consumers
|
|
123
|
+
that need explicit URL fields; the viewer adapter does not require it.
|
|
124
|
+
|
|
125
|
+
`withSessionAssetResolver(options, documentAssets?)` retains its public session
|
|
126
|
+
asset behavior and also accepts canonical document assets. Alias lookup is
|
|
127
|
+
indexed once and does not invoke asset accessors. Exact case-sensitive project
|
|
128
|
+
paths take precedence. Case-insensitive lookup is a compatibility fallback only
|
|
129
|
+
when one unique asset owns the folded path; ambiguous folded paths do not
|
|
130
|
+
resolve. Resolved metadata is copied through a descriptor-safe boundary.
|
|
131
|
+
|
|
132
|
+
`withContextAssetResolver(options, context)` uses the same behavior while
|
|
133
|
+
caching the canonical document-asset alias index in the supplied prepared
|
|
134
|
+
context. Session assets remain request-specific and take precedence over
|
|
135
|
+
document assets. Referenced GLTF `buffers[].uri`, OBJ `mtllib`, and WRL
|
|
136
|
+
`ImageTexture` companions are attached as `externalBuffers` or `resources`
|
|
137
|
+
when their safe project-relative path matches an indexed asset. Parent
|
|
138
|
+
traversal, absolute paths, and URL schemes are never attached implicitly.
|
|
139
|
+
|
|
80
140
|
## Runtime
|
|
81
141
|
|
|
82
142
|
### `new PcbScene3dRuntime(viewportNode, sceneDescription, hooks?)`
|
|
@@ -90,6 +150,38 @@ Hooks:
|
|
|
90
150
|
- `loadRuntimeModules()`: optional async loader returning `{ THREE,
|
|
91
151
|
OrbitControls }`.
|
|
92
152
|
- `translate`: optional translation function for interaction hints.
|
|
153
|
+
- `modelLoaderOptions`: opt-in model URL fetch policy and cache settings.
|
|
154
|
+
|
|
155
|
+
The live runtime accepts STEP/STP, WRL/VRML, STL, OBJ, GLTF/GLB, and 3MF
|
|
156
|
+
placements. Text-capable formats accept `payloadText`, `text`, and string
|
|
157
|
+
`data`; every format accepts binary `data`, `bytes`, `payloadBytes`, or browser
|
|
158
|
+
`File`/`Blob` content. URL fetching remains opt-in through an injected `fetch`
|
|
159
|
+
function or `allowNetworkModelFetch: true`. Relative GLTF sidecars resolve
|
|
160
|
+
beside absolute or project-relative main model paths. WRL texture references
|
|
161
|
+
are replaced with local or explicitly fetched data URIs before Three.js parses
|
|
162
|
+
the model, preventing implicit texture networking. STL, OBJ, GLTF, and GLB use
|
|
163
|
+
the shared faceted mesh pipeline so runtime and assembly export preserve the
|
|
164
|
+
same units, material color, opacity, and vertex-color behavior.
|
|
165
|
+
|
|
166
|
+
Static `authHeaders` are sent only to the main model origin. A host that
|
|
167
|
+
intentionally authorizes another origin can return headers from
|
|
168
|
+
`authHeadersForUrl(url, { mainUrl, sameOrigin, label })`. Each fetch scope
|
|
169
|
+
defaults to 128 MiB per resource, 256 resources, and 512 MiB aggregate; override
|
|
170
|
+
these with `maxModelBytes`, `maxModelResources`, and `maxModelTotalBytes`.
|
|
171
|
+
|
|
172
|
+
### `PcbModelArchiveExporter.buildArchive(options?)`
|
|
173
|
+
|
|
174
|
+
Exports resolved STEP/STP, WRL/VRML, 3MF, GLB/GLTF, STL, and OBJ sources. Raw
|
|
175
|
+
models accept the same canonical text, byte, file, and explicitly enabled URL
|
|
176
|
+
sources as the runtime. `modelLoaderOptions` is shared with stitched-component
|
|
177
|
+
mesh loading and raw URL export. Exact canonical project paths, source streams,
|
|
178
|
+
or IDs define deduplication; same-basename models in different paths remain
|
|
179
|
+
distinct. A controller derives the archive base name from `summary.title`,
|
|
180
|
+
canonical `source.fileName`, or the legacy `fileName`, in that order.
|
|
181
|
+
Each raw source is written to a unique pattern directory under its original
|
|
182
|
+
source basename. Safe relative GLTF buffers/images, OBJ resources, and WRL
|
|
183
|
+
textures are written beside it so internal references remain valid. Every raw
|
|
184
|
+
`exportedEntries` row includes `bundleDirectory` and `companionPaths`.
|
|
93
185
|
|
|
94
186
|
Methods:
|
|
95
187
|
|
|
@@ -175,9 +267,15 @@ Options:
|
|
|
175
267
|
- `fetch`: host-provided fetch function for resolved model URLs.
|
|
176
268
|
- `allowNetworkModelFetch`: use `globalThis.fetch` for `resolvedUrl` or
|
|
177
269
|
`sourceUrl` models when no local payload is present.
|
|
178
|
-
- `authHeaders`: headers forwarded to
|
|
270
|
+
- `authHeaders`: headers forwarded only to same-origin model requests.
|
|
271
|
+
- `authHeadersForUrl(url, context)`: explicit per-URL headers. Static
|
|
272
|
+
`authHeaders` are withheld when `context.sameOrigin` is false.
|
|
179
273
|
- `fetchTimeoutMs`: abort timeout for network model requests.
|
|
180
274
|
- `modelCache`: optional cache map keyed by resolved model URL.
|
|
275
|
+
- `maxModelBytes`: maximum bytes per fetched resource; defaults to 134217728.
|
|
276
|
+
- `maxModelResources`: maximum fetched main/sidecar resources per scope;
|
|
277
|
+
defaults to 256.
|
|
278
|
+
- `maxModelTotalBytes`: maximum aggregate fetched bytes per scope; defaults to 536870912.
|
|
181
279
|
|
|
182
280
|
## Model Archive Export
|
|
183
281
|
|
|
@@ -189,12 +287,15 @@ Options:
|
|
|
189
287
|
|
|
190
288
|
- `archiveBaseName`: base name for the downloaded archive.
|
|
191
289
|
- `sceneDescription`: scene description containing resolved external models.
|
|
290
|
+
- `modelLoaderOptions`: the same bounded explicit fetch policy used by runtime
|
|
291
|
+
model loading.
|
|
192
292
|
|
|
193
293
|
Returns:
|
|
194
294
|
|
|
195
295
|
- `archiveName`;
|
|
196
296
|
- `archiveBytes`;
|
|
197
|
-
- `exportedEntries
|
|
297
|
+
- `exportedEntries`, including `archivePath`, `bundleDirectory`, and
|
|
298
|
+
`companionPaths` for each raw source;
|
|
198
299
|
- `skippedEntries`.
|
|
199
300
|
|
|
200
301
|
## Geometry Factories
|
package/docs/circuitjson.md
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# CircuitJSON Usage
|
|
2
2
|
|
|
3
|
-
`pcb-scene3d-viewer` can render
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
`pcb-scene3d-viewer` can render common CircuitJSON document envelopes, prepared
|
|
4
|
+
document contexts, and serialized element arrays directly. Use this path when
|
|
5
|
+
the host application already has CircuitJSON and does not need an Altium,
|
|
6
|
+
KiCad, Gerber, or other format-specific scene builder.
|
|
7
|
+
|
|
8
|
+
## Accepted Input Shapes
|
|
9
|
+
|
|
10
|
+
- `ecad-toolkit.document.v1` results returned by converged toolkit parsers;
|
|
11
|
+
- `CircuitJsonDocumentContext` instances prepared by a host for reuse; or
|
|
12
|
+
- dense serialized CircuitJSON element arrays.
|
|
13
|
+
|
|
14
|
+
Prepared contexts are the fastest repeated-render path because validation and
|
|
15
|
+
the adapter's `elements` index are built at most once.
|
|
6
16
|
|
|
7
17
|
## Direct Controller Input
|
|
8
18
|
|
|
9
|
-
Pass
|
|
10
|
-
CircuitJSON input, converts it to the internal render model, and
|
|
11
|
-
runtime without a `buildScene` callback.
|
|
19
|
+
Pass any accepted CircuitJSON shape as the `documentModel`. The controller
|
|
20
|
+
detects direct CircuitJSON input, converts it to the internal render model, and
|
|
21
|
+
mounts the runtime without a `buildScene` callback.
|
|
12
22
|
|
|
13
23
|
```js
|
|
14
24
|
import {
|
|
@@ -19,6 +29,7 @@ import {
|
|
|
19
29
|
const circuitJson = [
|
|
20
30
|
{
|
|
21
31
|
type: 'pcb_board',
|
|
32
|
+
pcb_board_id: 'board-1',
|
|
22
33
|
width: 50,
|
|
23
34
|
height: 30,
|
|
24
35
|
thickness: 1.6,
|
|
@@ -26,14 +37,16 @@ const circuitJson = [
|
|
|
26
37
|
},
|
|
27
38
|
{
|
|
28
39
|
type: 'source_component',
|
|
29
|
-
|
|
40
|
+
source_component_id: 'source-r1',
|
|
30
41
|
name: 'R1',
|
|
31
|
-
ftype: '
|
|
42
|
+
ftype: 'simple_resistor',
|
|
43
|
+
resistance: '10k'
|
|
32
44
|
},
|
|
33
45
|
{
|
|
34
46
|
type: 'pcb_component',
|
|
47
|
+
pcb_component_id: 'pcb-r1',
|
|
35
48
|
source_component_id: 'source-r1',
|
|
36
|
-
layer:
|
|
49
|
+
layer: 'top',
|
|
37
50
|
center: { x: 20, y: 15 },
|
|
38
51
|
rotation: 90,
|
|
39
52
|
width: 1.6,
|
|
@@ -42,7 +55,10 @@ const circuitJson = [
|
|
|
42
55
|
},
|
|
43
56
|
{
|
|
44
57
|
type: 'pcb_smtpad',
|
|
45
|
-
|
|
58
|
+
pcb_smtpad_id: 'pad-r1-1',
|
|
59
|
+
pcb_component_id: 'pcb-r1',
|
|
60
|
+
layer: 'top',
|
|
61
|
+
shape: 'rect',
|
|
46
62
|
x: 19.2,
|
|
47
63
|
y: 15,
|
|
48
64
|
width: 0.7,
|
|
@@ -50,7 +66,10 @@ const circuitJson = [
|
|
|
50
66
|
},
|
|
51
67
|
{
|
|
52
68
|
type: 'pcb_smtpad',
|
|
53
|
-
|
|
69
|
+
pcb_smtpad_id: 'pad-r1-2',
|
|
70
|
+
pcb_component_id: 'pcb-r1',
|
|
71
|
+
layer: 'top',
|
|
72
|
+
shape: 'rect',
|
|
54
73
|
x: 20.8,
|
|
55
74
|
y: 15,
|
|
56
75
|
width: 0.7,
|
|
@@ -58,12 +77,23 @@ const circuitJson = [
|
|
|
58
77
|
},
|
|
59
78
|
{
|
|
60
79
|
type: 'pcb_trace',
|
|
61
|
-
|
|
80
|
+
pcb_trace_id: 'trace-r1-1',
|
|
62
81
|
route: [
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
{
|
|
83
|
+
route_type: 'wire',
|
|
84
|
+
x: 18,
|
|
85
|
+
y: 15,
|
|
86
|
+
width: 0.25,
|
|
87
|
+
layer: 'top'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
route_type: 'wire',
|
|
91
|
+
x: 19.2,
|
|
92
|
+
y: 15,
|
|
93
|
+
width: 0.25,
|
|
94
|
+
layer: 'top'
|
|
95
|
+
}
|
|
96
|
+
]
|
|
67
97
|
}
|
|
68
98
|
]
|
|
69
99
|
|
|
@@ -75,14 +105,14 @@ const controller = new PcbScene3dController(
|
|
|
75
105
|
)
|
|
76
106
|
```
|
|
77
107
|
|
|
78
|
-
The shell renderer
|
|
79
|
-
|
|
108
|
+
The shell renderer uses the shared CircuitJSON context/index to derive its
|
|
109
|
+
board, component, and BOM summary. The controller performs the full render-model
|
|
80
110
|
conversion.
|
|
81
111
|
|
|
82
112
|
## Direct Runtime Input
|
|
83
113
|
|
|
84
|
-
For custom UI shells, pass the same CircuitJSON array
|
|
85
|
-
`PcbScene3dRuntime`.
|
|
114
|
+
For custom UI shells, pass the same document, context, or CircuitJSON array
|
|
115
|
+
directly to `PcbScene3dRuntime`.
|
|
86
116
|
|
|
87
117
|
```js
|
|
88
118
|
import { PcbScene3dRuntime } from 'pcb-scene3d-viewer'
|
|
@@ -109,9 +139,49 @@ if (PcbScene3dCircuitJsonAdapter.isCircuitJsonModel(circuitJson)) {
|
|
|
109
139
|
}
|
|
110
140
|
```
|
|
111
141
|
|
|
142
|
+
For repeated builds, prepare and retain the shared context:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
import { CircuitJsonDocumentContext } from 'circuitjson-toolkit'
|
|
146
|
+
import { PcbScene3dCircuitJsonAdapter } from 'pcb-scene3d-viewer'
|
|
147
|
+
|
|
148
|
+
const context = CircuitJsonDocumentContext.prepare(document, {
|
|
149
|
+
indexes: ['elements']
|
|
150
|
+
})
|
|
151
|
+
const firstScene = PcbScene3dCircuitJsonAdapter.build(context)
|
|
152
|
+
const secondScene = PcbScene3dCircuitJsonAdapter.build(context)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Both builds reuse the same validated model and `elements` index. If the model
|
|
156
|
+
contains CAD model references, its canonical document-asset alias index is also
|
|
157
|
+
built at most once in the same context. Documents without model references do
|
|
158
|
+
not pay that indexing cost.
|
|
159
|
+
|
|
160
|
+
The viewer exposes the same operation as
|
|
161
|
+
`PcbScene3dCircuitJsonAdapter.prepare(document)`. Controller routing uses this
|
|
162
|
+
proof-aware path before mounting, so a validated toolkit document is not
|
|
163
|
+
validated again. Bare arrays first pass a non-mutating structural predicate and
|
|
164
|
+
are then normalized and validated by the shared CircuitJSON context; supported
|
|
165
|
+
legacy rows are not rejected by an earlier strict viewer precheck.
|
|
166
|
+
|
|
167
|
+
Canonical CAD rows may retain a `model_asset` path instead of an explicit
|
|
168
|
+
`model_step_url`, `model_glb_url`, or equivalent field. The adapter consumes
|
|
169
|
+
that field directly and resolves matching assets from the canonical document
|
|
170
|
+
or `sessionAssets`; no document pre-transform or resolver wrapper is required.
|
|
171
|
+
Canonical `ToolkitAsset` payloads may expose their immutable `data` through the
|
|
172
|
+
shared accessor-backed contract. The resolver recognizes and lazily
|
|
173
|
+
materializes those trusted assets before its descriptor-safe viewer copy;
|
|
174
|
+
arbitrary accessor-backed session rows remain unread and cannot execute.
|
|
175
|
+
For GLTF, OBJ, and WRL main assets, safe project-relative BIN, MTL, and texture
|
|
176
|
+
references are attached automatically from the same indexed asset sets.
|
|
177
|
+
Session companions take precedence over document companions. Absolute URLs,
|
|
178
|
+
parent traversal, and accessor-backed entries are excluded without executing
|
|
179
|
+
caller accessors.
|
|
180
|
+
`CircuitJsonCadModelAssetResolver.withModelAssetUrls()` remains available when
|
|
181
|
+
a separate consumer specifically needs explicit URL fields.
|
|
182
|
+
|
|
112
183
|
Hosts that need URL policy control can pass a synchronous `modelUrlResolver`.
|
|
113
|
-
The adapter records the returned metadata on each external model
|
|
114
|
-
fetch the referenced file:
|
|
184
|
+
The adapter records the returned metadata on each external model:
|
|
115
185
|
|
|
116
186
|
```js
|
|
117
187
|
const sceneDescription = PcbScene3dCircuitJsonAdapter.build(circuitJson, {
|
|
@@ -128,12 +198,20 @@ Hosts can also pass `projectBaseUrl` to resolve relative model URLs without a
|
|
|
128
198
|
custom resolver. Package-style `node_modules/...` paths resolve to the
|
|
129
199
|
project-origin `/package_files/download` endpoint with package and file-path
|
|
130
200
|
query parameters. Package download file paths are normalized under `dist/` when
|
|
131
|
-
the source path does not already include it. Model fetching remains
|
|
132
|
-
|
|
201
|
+
the source path does not already include it. Model fetching remains opt-in.
|
|
202
|
+
Pass `modelLoaderOptions` to the controller or runtime hooks with an injected
|
|
203
|
+
`fetch`, or set `allowNetworkModelFetch: true` to use `globalThis.fetch`.
|
|
204
|
+
Canonical document bytes and session `File`/`Blob` assets need no network
|
|
205
|
+
option. Static `authHeaders` remain on the main model origin. Use
|
|
206
|
+
`authHeadersForUrl` for an intentional cross-origin header and
|
|
207
|
+
`maxModelBytes`/`maxModelResources`/`maxModelTotalBytes` to adjust the bounded
|
|
208
|
+
fetch defaults.
|
|
133
209
|
`boardDrillQuality` accepts `low`, `medium`, or `high` and controls generated
|
|
134
210
|
circle sampling for direct CircuitJSON drill/cutout geometry. When a
|
|
135
211
|
CircuitJSON file has components but no board or panel, `drawFauxBoard: true`
|
|
136
212
|
generates a board from component bounds with a 2 mm margin per side.
|
|
213
|
+
The optional shell honors the same flag, so component-only documents show the
|
|
214
|
+
same faux-board scene controls instead of an empty-state message.
|
|
137
215
|
Set `showPcbNotes: true` to render `pcb_note_text`,
|
|
138
216
|
`pcb_fabrication_note_text`, note/fabrication path artwork, and courtyard
|
|
139
217
|
artwork as silkscreen detail. Notes are hidden by default so manufacturing
|
|
@@ -145,7 +223,9 @@ does not clutter normal board previews.
|
|
|
145
223
|
`isDirectCircuitJsonModel(value)` returns `false` for compatibility arrays that
|
|
146
224
|
also carry legacy parser fields such as `pcb`, `schematic`, or `bom`. Those
|
|
147
225
|
arrays continue through the host-provided `buildScene` callback so existing
|
|
148
|
-
parser integrations keep their source-specific conversion behavior.
|
|
226
|
+
parser integrations keep their source-specific conversion behavior. Canonical
|
|
227
|
+
document envelopes and prepared contexts always use the direct CircuitJSON path
|
|
228
|
+
regardless of their original source format.
|
|
149
229
|
|
|
150
230
|
## Units And Coordinates
|
|
151
231
|
|
|
@@ -159,6 +239,12 @@ board with a 1.6 mm thickness so incomplete test or preview models still
|
|
|
159
239
|
render. With `drawFauxBoard: true`, that fallback board is instead sized around
|
|
160
240
|
the PCB component bounds with a minimum 10 mm by 10 mm footprint.
|
|
161
241
|
|
|
242
|
+
When panels exist, every `pcb_panel` is an independent physical contour and
|
|
243
|
+
child `pcb_board` rows are not duplicated as substrate. Without panels, every
|
|
244
|
+
`pcb_board` is retained. `board.widthMil`, `heightMil`, and center describe the
|
|
245
|
+
aggregate bounds; `board.contours` carries each physical outline, thickness,
|
|
246
|
+
and targeted cutouts for runtime rendering and assembly export.
|
|
247
|
+
|
|
162
248
|
`cad_component` records with `show_as_bounding_box: true` are exported as
|
|
163
249
|
procedural component bodies. If `size` or `model_size` is present, those
|
|
164
250
|
dimensions drive the generated body; otherwise the paired `pcb_component`
|
|
@@ -270,6 +356,34 @@ Drills can use circular, pill, or rotated pill geometry. `hole_offset_x` and
|
|
|
270
356
|
}
|
|
271
357
|
```
|
|
272
358
|
|
|
359
|
+
Polygon-plated slots use the canonical `hole_with_polygon_pad` variant. The
|
|
360
|
+
shared CircuitJSON primitive model derives the copper dimensions in the pad's
|
|
361
|
+
rotation-local coordinate system and preserves the pill drill dimensions:
|
|
362
|
+
|
|
363
|
+
```js
|
|
364
|
+
{
|
|
365
|
+
type: 'pcb_plated_hole',
|
|
366
|
+
shape: 'hole_with_polygon_pad',
|
|
367
|
+
hole_shape: 'pill',
|
|
368
|
+
x: 10,
|
|
369
|
+
y: 8,
|
|
370
|
+
hole_width: 2.6,
|
|
371
|
+
hole_height: 0.6,
|
|
372
|
+
pad_outline: [
|
|
373
|
+
{ x: 8.7, y: 7.7 },
|
|
374
|
+
{ x: 11.3, y: 7.7 },
|
|
375
|
+
{ x: 11.3, y: 8.3 },
|
|
376
|
+
{ x: 8.7, y: 8.3 }
|
|
377
|
+
]
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
This is the same canonical shape emitted for a plated Gerber routed slot; no
|
|
382
|
+
format-specific scene adapter is required. `holeRotation` is board-space, so a
|
|
383
|
+
45-degree or 90-degree routed slot is applied once even when its outer pad uses
|
|
384
|
+
the same rotation. Rectangular outer pads may independently use
|
|
385
|
+
`rect_ccw_rotation`, while rotated pill drills use `hole_ccw_rotation`.
|
|
386
|
+
|
|
273
387
|
SMT pads can use circular, rectangular, rotated rectangular, or pill geometry.
|
|
274
388
|
Pill pads are normalized as rounded rectangles so their copper keeps the
|
|
275
389
|
expected capsule outline:
|
|
@@ -368,7 +482,7 @@ model conversion. Use `PcbScene3dCircuitJsonAdapter.isCircuitJsonModel(value)`
|
|
|
368
482
|
for a cheap guard when accepting untrusted JSON from users, and catch conversion
|
|
369
483
|
errors around `build(value)` when you need to show a custom diagnostic.
|
|
370
484
|
|
|
371
|
-
The viewer
|
|
372
|
-
Model URL matching
|
|
373
|
-
|
|
374
|
-
|
|
485
|
+
The viewer never fetches external assets unless the host explicitly enables a
|
|
486
|
+
model loader fetch policy. Model URL matching and same-origin/proxy decisions
|
|
487
|
+
remain host-owned; canonical document bytes and session files are consumed
|
|
488
|
+
directly without an app-side resolver wrapper.
|
package/docs/model-format.md
CHANGED
|
@@ -70,7 +70,8 @@ Components describe fallback package bodies and selection metadata:
|
|
|
70
70
|
|
|
71
71
|
## External Placements
|
|
72
72
|
|
|
73
|
-
External placements describe STEP, WRL, GLB, GLTF, STL, or OBJ model
|
|
73
|
+
External placements describe STEP, WRL, 3MF, GLB, GLTF, STL, or OBJ model
|
|
74
|
+
instances:
|
|
74
75
|
|
|
75
76
|
```js
|
|
76
77
|
{
|
|
@@ -112,12 +113,47 @@ GLTF/GLB import consumes triangle primitives and ignores unsupported primitive
|
|
|
112
113
|
modes such as points or lines. Remote `.gltf` files can reference external
|
|
113
114
|
`buffers[].uri` sidecars; when URL-backed model loading is enabled, the loader
|
|
114
115
|
resolves those sidecars relative to the `.gltf` URL and fetches them with the
|
|
115
|
-
same cache, timeout, and auth-header policy.
|
|
116
|
+
same cache, timeout, and auth-header policy. This also works when the main model
|
|
117
|
+
path is project-relative. Resident GLTF JSON uses already attached buffers and
|
|
118
|
+
fetches only missing sidecars when an explicit policy is configured.
|
|
119
|
+
|
|
120
|
+
Canonical document/session asset resolution attaches safe project-relative
|
|
121
|
+
GLTF BIN, OBJ MTL, and WRL texture companions directly. WRL textures are encoded
|
|
122
|
+
as data URIs before parsing. Unmatched texture URLs become inert unless the host
|
|
123
|
+
supplies an explicit fetch policy, so Three.js cannot perform an implicit
|
|
124
|
+
network request.
|
|
116
125
|
|
|
117
126
|
Resolved model URLs are not fetched by default. Hosts that want URL-backed model
|
|
118
|
-
loading must provide an explicit fetch policy through
|
|
119
|
-
|
|
120
|
-
|
|
127
|
+
loading must provide an explicit fetch policy through
|
|
128
|
+
`PcbScene3dController`/`PcbScene3dRuntime` `modelLoaderOptions`,
|
|
129
|
+
`PcbScene3dExternalModels.loadIntoScene({ modelLoaderOptions })`, or
|
|
130
|
+
`PcbAssemblyModelMeshLoader`. Use `authHeaders`, `fetchTimeoutMs`, and a
|
|
131
|
+
caller-owned `modelCache` when remote model requests are enabled. Successful
|
|
132
|
+
STEP/STP, WRL/VRML, and 3MF requests share that cache; rejected requests are
|
|
133
|
+
evicted so callers can retry. Embedded `payloadText`, `text`, `payloadBytes`,
|
|
134
|
+
`bytes`, canonical string/binary `data`, and browser `File`/`Blob` values are
|
|
135
|
+
loaded without enabling network access.
|
|
136
|
+
|
|
137
|
+
Static `authHeaders` are origin-bound to the main model URL and are not copied
|
|
138
|
+
to cross-origin GLTF buffers or WRL textures. Use
|
|
139
|
+
`authHeadersForUrl(url, { mainUrl, sameOrigin, label })` for an intentional
|
|
140
|
+
per-resource exception. Network scopes default to 128 MiB per resource, 256
|
|
141
|
+
resources, and 512 MiB aggregate across main models and sidecars. Configure
|
|
142
|
+
`maxModelBytes`, `maxModelResources`, and `maxModelTotalBytes` to adjust those
|
|
143
|
+
bounds.
|
|
144
|
+
|
|
145
|
+
Model caches and ZIP deduplication use exact canonical project paths, retained
|
|
146
|
+
source streams, or asset IDs before falling back to checksums and names. Two
|
|
147
|
+
different directories may therefore contain the same basename without a cache
|
|
148
|
+
collision, while repeated placements of one exact source reuse parsed geometry.
|
|
149
|
+
Canonical/session asset aliases resolve exact case-sensitive paths first. A
|
|
150
|
+
case-insensitive fallback is accepted only when unique, preventing collisions
|
|
151
|
+
between files that differ only by case.
|
|
152
|
+
|
|
153
|
+
Raw model archives use one unique pattern directory per source. The main file
|
|
154
|
+
keeps its original source basename, and safe relative GLTF buffers/images, OBJ
|
|
155
|
+
resources, and WRL textures are emitted beneath the same directory so their
|
|
156
|
+
references remain usable after extraction.
|
|
121
157
|
|
|
122
158
|
When `projectBaseUrl` is provided to the CircuitJSON adapter, package-style
|
|
123
159
|
model paths such as `node_modules/package-name/path/to/model.step` are resolved
|
|
@@ -167,9 +203,16 @@ model matching, the source toolkit should encode those decisions in the scene
|
|
|
167
203
|
description.
|
|
168
204
|
|
|
169
205
|
Through-hole pads can describe offset or slotted drills with `holeOffsetX`,
|
|
170
|
-
`holeOffsetY`, `holeSlotLength`, and `holeRotation
|
|
206
|
+
`holeOffsetY`, `holeSlotLength`, and board-space `holeRotation`; pad `rotation`
|
|
207
|
+
is not added to the drill angle. Board substrate export uses
|
|
171
208
|
`detail.drillQuality` values of `low`, `medium`, or `high` to choose circular
|
|
172
|
-
drill sampling density.
|
|
209
|
+
drill sampling density. Direct CircuitJSON polygon-plated holes are normalized
|
|
210
|
+
through the shared hole primitive model: polygon extents become the pad's local
|
|
211
|
+
copper size and pill width/height become `holeDiameter` and `holeSlotLength`.
|
|
212
|
+
|
|
213
|
+
`board.contours` contains every independently extruded board or panel outline.
|
|
214
|
+
The top-level board bounds center all detail, while runtime bodies, outlines,
|
|
215
|
+
solder-mask faces, and exported substrate meshes iterate the contour list.
|
|
173
216
|
|
|
174
217
|
Rounded SMT pads set `hasRoundedRect`, the side-specific
|
|
175
218
|
`roundedRectShapeTop`/`roundedRectShapeBottom`, and
|