pcb-scene3d-viewer 1.1.31 → 1.1.44
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/AGENTS.md +4 -3
- package/docs/api.md +35 -3
- package/docs/circuitjson.md +164 -25
- package/docs/model-format.md +54 -3
- package/package.json +2 -2
- package/src/CircuitJsonCadModelAssetResolver.mjs +261 -0
- package/src/PcbAssemblyBoardSubstrateBuilder.mjs +30 -9
- package/src/PcbAssemblyComponentMeshBuilder.mjs +110 -30
- package/src/PcbAssemblyGeometryBuildProgress.mjs +3 -0
- package/src/PcbAssemblyGeometryBuilder.mjs +54 -1
- package/src/PcbAssemblyGltfModelMeshParser.mjs +58 -1
- package/src/PcbAssemblyGltfWriter.mjs +184 -7
- package/src/PcbAssemblyModelMeshLoader.mjs +312 -2
- package/src/PcbAssemblyPadMeshBuilder.mjs +9 -7
- package/src/PcbAssemblyTextModelMeshParser.mjs +142 -19
- package/src/PcbScene3dCircuitJsonAdapter.mjs +316 -379
- package/src/PcbScene3dCircuitJsonCopperPourBuilder.mjs +298 -0
- package/src/PcbScene3dCircuitJsonDocumentationArtworkBuilder.mjs +606 -0
- package/src/PcbScene3dCircuitJsonGeometry.mjs +542 -0
- package/src/PcbScene3dCircuitJsonLayer.mjs +110 -0
- package/src/PcbScene3dCircuitJsonModelUrlResolver.mjs +142 -0
- package/src/PcbScene3dCircuitJsonSilkscreenBuilder.mjs +729 -0
- package/src/PcbScene3dCircuitJsonSolderPasteBuilder.mjs +359 -0
- package/src/PcbScene3dCircuitJsonThermalSpokeBuilder.mjs +167 -0
- package/src/PcbScene3dCircuitJsonTraceRouteBuilder.mjs +335 -0
- package/src/PcbScene3dComponentVisibility.mjs +5 -1
- package/src/PcbScene3dFallbackBodyFactory.mjs +45 -4
- package/src/PcbScene3dFallbackVisibility.mjs +17 -32
- package/src/PcbScene3dFootprintBodyBuilder.mjs +894 -0
- package/src/PcbScene3dModelSeatingPolicy.mjs +58 -0
- package/src/PcbScene3dRenderGroupVisibility.mjs +10 -13
- package/src/PcbScene3dRuntime.mjs +58 -53
- package/src/PcbScene3dRuntimeSurfaceArtworkLoader.mjs +91 -0
- package/src/PcbScene3dSelectionMarkerFactory.mjs +39 -4
- package/src/PcbScene3dStepLoader.mjs +42 -6
- package/src/scene3d.mjs +1 -0
package/AGENTS.md
CHANGED
|
@@ -35,9 +35,10 @@
|
|
|
35
35
|
## Library Scope
|
|
36
36
|
|
|
37
37
|
- Include browser-side Three.js scene runtime, geometry factories, STEP/WRL
|
|
38
|
-
model loading, component picking, view presets, archive export,
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
model loading, component picking, view presets, archive export, CircuitJSON
|
|
39
|
+
scene adapters and CAD model asset resolution, the optional DOM controller,
|
|
40
|
+
and the optional 3D shell renderer/styles.
|
|
41
|
+
- Do not include ECAD parser logic or source-format-specific scene-description
|
|
41
42
|
builders. Altium and KiCad scene descriptions are produced by their own
|
|
42
43
|
toolkits and passed into this package.
|
|
43
44
|
- Keep renderer fixes universal. Never special-case a specific file name,
|
package/docs/api.md
CHANGED
|
@@ -65,7 +65,13 @@ Methods:
|
|
|
65
65
|
host `buildScene` callbacks.
|
|
66
66
|
- `build(circuitJson, options?)`: returns a runtime-ready scene description.
|
|
67
67
|
`options.modelUrlResolver` can attach caller-owned URL resolution metadata to
|
|
68
|
-
`cad_component` external models without fetching them.
|
|
68
|
+
`cad_component` external models without fetching them. `projectBaseUrl`
|
|
69
|
+
resolves relative model URLs and package-style `node_modules/...` model paths,
|
|
70
|
+
`drawFauxBoard: true` generates a board around component bounds when no board
|
|
71
|
+
or panel exists, `boardDrillQuality` controls generated circular
|
|
72
|
+
drill/cutout sampling, and `showPcbNotes: true` renders note, fabrication,
|
|
73
|
+
and courtyard artwork as silkscreen detail. `showPcbPaste: true` renders
|
|
74
|
+
direct solder-paste artwork as a separate top/bottom overlay.
|
|
69
75
|
|
|
70
76
|
`PcbScene3dController` and `PcbScene3dRuntime` call this adapter automatically
|
|
71
77
|
when they receive direct CircuitJSON input. See
|
|
@@ -130,7 +136,10 @@ Workers respond with `scene3d:success` and `sceneDescription`, or
|
|
|
130
136
|
Builds export meshes from a prepared scene description. `options.modelMeshLoader`
|
|
131
137
|
can provide external model meshes, `options.includeModels: false` skips external
|
|
132
138
|
model loading, and `options.renderFallbackBodies: false` disables procedural
|
|
133
|
-
component bodies for unresolved models.
|
|
139
|
+
component bodies for unresolved models. Placements with `renderAsBoundingBox:
|
|
140
|
+
true` are exported as procedural component bodies instead of loading the
|
|
141
|
+
referenced model. Optional solder-paste detail exports as distinct paste meshes
|
|
142
|
+
when present in `sceneDescription.detail.paste`.
|
|
134
143
|
|
|
135
144
|
### `PcbAssemblyGltfWriter.write(options?)`
|
|
136
145
|
|
|
@@ -143,9 +152,32 @@ Options:
|
|
|
143
152
|
`color`, optional `opacity`, and optional board `texture` data URIs.
|
|
144
153
|
- `format`: `gltf` or `glb`.
|
|
145
154
|
- `binary`: optional boolean equivalent to `format: 'glb'`.
|
|
155
|
+
- `includeSceneMetadata`: when `true`, emits a default camera and punctual light
|
|
156
|
+
for third-party GLTF viewers.
|
|
146
157
|
|
|
147
158
|
Returns a GLTF JSON object for `gltf` and a `Uint8Array` for `glb`. RGBA colors
|
|
148
|
-
or opacity values below `1` are emitted as blended GLTF materials.
|
|
159
|
+
or opacity values below `1` are emitted as blended GLTF materials. Mesh
|
|
160
|
+
`vertexColors` are exported as `COLOR_0`, and mesh `material` metadata is
|
|
161
|
+
preserved in material extras.
|
|
162
|
+
|
|
163
|
+
### `new PcbAssemblyModelMeshLoader(options?)`
|
|
164
|
+
|
|
165
|
+
Loads STEP, WRL, STL, OBJ, GLTF, and GLB external models into assembly meshes.
|
|
166
|
+
By default the loader only reads embedded payloads, session files, and provided
|
|
167
|
+
byte buffers. Network loading is opt-in through either an injected `fetch`
|
|
168
|
+
function or `allowNetworkModelFetch: true`. Remote `.gltf` buffer sidecars are
|
|
169
|
+
resolved relative to the `.gltf` URL and use the same fetch, auth-header,
|
|
170
|
+
timeout, and cache policy as the top-level model.
|
|
171
|
+
|
|
172
|
+
Options:
|
|
173
|
+
|
|
174
|
+
- `stepLoader`: custom STEP loader.
|
|
175
|
+
- `fetch`: host-provided fetch function for resolved model URLs.
|
|
176
|
+
- `allowNetworkModelFetch`: use `globalThis.fetch` for `resolvedUrl` or
|
|
177
|
+
`sourceUrl` models when no local payload is present.
|
|
178
|
+
- `authHeaders`: headers forwarded to network model requests.
|
|
179
|
+
- `fetchTimeoutMs`: abort timeout for network model requests.
|
|
180
|
+
- `modelCache`: optional cache map keyed by resolved model URL.
|
|
149
181
|
|
|
150
182
|
## Model Archive Export
|
|
151
183
|
|
package/docs/circuitjson.md
CHANGED
|
@@ -124,6 +124,24 @@ const sceneDescription = PcbScene3dCircuitJsonAdapter.build(circuitJson, {
|
|
|
124
124
|
})
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
Hosts can also pass `projectBaseUrl` to resolve relative model URLs without a
|
|
128
|
+
custom resolver. Package-style `node_modules/...` paths resolve to the
|
|
129
|
+
project-origin `/package_files/download` endpoint with package and file-path
|
|
130
|
+
query parameters. Package download file paths are normalized under `dist/` when
|
|
131
|
+
the source path does not already include it. Model fetching remains a separate
|
|
132
|
+
host/export decision.
|
|
133
|
+
`boardDrillQuality` accepts `low`, `medium`, or `high` and controls generated
|
|
134
|
+
circle sampling for direct CircuitJSON drill/cutout geometry. When a
|
|
135
|
+
CircuitJSON file has components but no board or panel, `drawFauxBoard: true`
|
|
136
|
+
generates a board from component bounds with a 2 mm margin per side.
|
|
137
|
+
Set `showPcbNotes: true` to render `pcb_note_text`,
|
|
138
|
+
`pcb_fabrication_note_text`, note/fabrication path artwork, and courtyard
|
|
139
|
+
artwork as silkscreen detail. Notes are hidden by default so manufacturing
|
|
140
|
+
annotations do not unexpectedly appear in board previews.
|
|
141
|
+
Set `showPcbPaste: true` to render `pcb_solder_paste` as a grey top/bottom
|
|
142
|
+
surface overlay. Paste is hidden by default so stencil-only manufacturing data
|
|
143
|
+
does not clutter normal board previews.
|
|
144
|
+
|
|
127
145
|
`isDirectCircuitJsonModel(value)` returns `false` for compatibility arrays that
|
|
128
146
|
also carry legacy parser fields such as `pcb`, `schematic`, or `bom`. Those
|
|
129
147
|
arrays continue through the host-provided `buildScene` callback so existing
|
|
@@ -138,12 +156,27 @@ the Three.js runtime.
|
|
|
138
156
|
The board center defaults to `{ x: 0, y: 0 }` when omitted. If no `pcb_panel`
|
|
139
157
|
or `pcb_board` element is present, the adapter creates a 25.4 mm by 25.4 mm
|
|
140
158
|
board with a 1.6 mm thickness so incomplete test or preview models still
|
|
141
|
-
render.
|
|
159
|
+
render. With `drawFauxBoard: true`, that fallback board is instead sized around
|
|
160
|
+
the PCB component bounds with a minimum 10 mm by 10 mm footprint.
|
|
161
|
+
|
|
162
|
+
`cad_component` records with `show_as_bounding_box: true` are exported as
|
|
163
|
+
procedural component bodies. If `size` or `model_size` is present, those
|
|
164
|
+
dimensions drive the generated body; otherwise the paired `pcb_component`
|
|
165
|
+
footprint dimensions are used.
|
|
166
|
+
|
|
167
|
+
When a `cad_component` has no explicit model URL, package footprint metadata
|
|
168
|
+
from `footprinter_string`, `footprint_string`, `footprint`, or `package` can
|
|
169
|
+
generate a richer fallback body. Common dual-row, quad-row, SOT-style,
|
|
170
|
+
through-hole pin-header, passive-chip, pushbutton, testpoint, and TO-style
|
|
171
|
+
package strings add simple lead, pin, or terminal geometry while keeping the
|
|
172
|
+
plain box fallback for unrecognized package text.
|
|
142
173
|
|
|
143
174
|
Layer values resolve as follows:
|
|
144
175
|
|
|
145
|
-
- `1`, `top`, `front`,
|
|
146
|
-
|
|
176
|
+
- `1`, `top`, `front`, `f.cu`, and top-side artwork layer names map to the
|
|
177
|
+
top side.
|
|
178
|
+
- `32`, `bottom`, `back`, `b.cu`, and bottom-side artwork layer names map to
|
|
179
|
+
the bottom side.
|
|
147
180
|
- Unknown layer values default to the top side.
|
|
148
181
|
|
|
149
182
|
## Supported Elements
|
|
@@ -151,21 +184,37 @@ Layer values resolve as follows:
|
|
|
151
184
|
The adapter focuses on renderer-ready PCB geometry and ignores unsupported
|
|
152
185
|
CircuitJSON elements instead of failing the whole scene.
|
|
153
186
|
|
|
154
|
-
| Element type
|
|
155
|
-
|
|
|
156
|
-
| `pcb_panel`
|
|
157
|
-
| `pcb_board`
|
|
158
|
-
| `pcb_cutout`
|
|
159
|
-
| `source_component`
|
|
160
|
-
| `pcb_component`
|
|
161
|
-
| `cad_component`
|
|
162
|
-
| `pcb_smtpad`
|
|
163
|
-
| `pcb_plated_hole`
|
|
164
|
-
| `pcb_hole`
|
|
165
|
-
| `pcb_via`
|
|
166
|
-
| `pcb_trace`
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
187
|
+
| Element type | Rendered as |
|
|
188
|
+
| --------------------------- | ------------------------------------------------------------------- |
|
|
189
|
+
| `pcb_panel` | Preferred board/panel size, thickness, center, and optional outline |
|
|
190
|
+
| `pcb_board` | Board size, thickness, center, and optional outline |
|
|
191
|
+
| `pcb_cutout` | Through-board cutout loop |
|
|
192
|
+
| `source_component` | Component designator and package metadata |
|
|
193
|
+
| `pcb_component` | Fallback component body and selection target |
|
|
194
|
+
| `cad_component` | External model URL metadata and placement data |
|
|
195
|
+
| `pcb_smtpad` | Top or bottom SMT pad copper, including pill pads |
|
|
196
|
+
| `pcb_plated_hole` | Through-hole pad copper and drill |
|
|
197
|
+
| `pcb_hole` | Non-plated drill opening |
|
|
198
|
+
| `pcb_via` | Via copper and drill |
|
|
199
|
+
| `pcb_trace` | Routed copper track segments and route-derived surface vias |
|
|
200
|
+
| `pcb_copper_pour` | Top or bottom copper zone polygon |
|
|
201
|
+
| `pcb_solder_paste` | Optional top or bottom paste fill when `showPcbPaste` is enabled |
|
|
202
|
+
| `pcb_silkscreen_line` | Top or bottom silkscreen stroke |
|
|
203
|
+
| `pcb_silkscreen_path` | Top or bottom routed silkscreen strokes |
|
|
204
|
+
| `pcb_silkscreen_circle` | Top or bottom full-circle silkscreen stroke |
|
|
205
|
+
| `pcb_silkscreen_rect` | Top or bottom rectangular silkscreen outline |
|
|
206
|
+
| `pcb_silkscreen_oval` | Top or bottom oval silkscreen outline |
|
|
207
|
+
| `pcb_silkscreen_pill` | Top or bottom pill silkscreen outline |
|
|
208
|
+
| `pcb_silkscreen_text` | Top or bottom silkscreen text placeholder |
|
|
209
|
+
| `pcb_note_text` | Optional top or bottom note text when `showPcbNotes` is enabled |
|
|
210
|
+
| `pcb_note_line` | Optional top or bottom note stroke when `showPcbNotes` is enabled |
|
|
211
|
+
| `pcb_note_path` | Optional top or bottom note path when `showPcbNotes` is enabled |
|
|
212
|
+
| `pcb_note_rect` | Optional top or bottom note outline when `showPcbNotes` is enabled |
|
|
213
|
+
| `pcb_fabrication_note_text` | Optional fabrication text when `showPcbNotes` is enabled |
|
|
214
|
+
| `pcb_fabrication_note_path` | Optional fabrication path when `showPcbNotes` is enabled |
|
|
215
|
+
| `pcb_courtyard_rect` | Optional courtyard rectangle when `showPcbNotes` is enabled |
|
|
216
|
+
| `pcb_courtyard_circle` | Optional courtyard circle when `showPcbNotes` is enabled |
|
|
217
|
+
| `pcb_courtyard_outline` | Optional courtyard outline when `showPcbNotes` is enabled |
|
|
169
218
|
|
|
170
219
|
`cad_component` entries can provide external model URLs and model-local
|
|
171
220
|
placement hints. The adapter maps `model_unit_to_mm_scale_factor`,
|
|
@@ -189,7 +238,9 @@ Board outlines can be supplied as `pcb_board.outline`, using an array of points:
|
|
|
189
238
|
```
|
|
190
239
|
|
|
191
240
|
Through-board cutouts can be supplied as polygon, rectangle, or circle
|
|
192
|
-
`pcb_cutout` elements
|
|
241
|
+
`pcb_cutout` elements. A `pcb_cutout` with `pcb_board_id` only applies when that
|
|
242
|
+
board is selected, while cutouts without a board target are global. Rectangular
|
|
243
|
+
cutouts support `ccw_rotation`:
|
|
193
244
|
|
|
194
245
|
```js
|
|
195
246
|
{
|
|
@@ -197,17 +248,80 @@ Through-board cutouts can be supplied as polygon, rectangle, or circle
|
|
|
197
248
|
shape: 'rect',
|
|
198
249
|
center: { x: 25, y: 15 },
|
|
199
250
|
width: 4,
|
|
200
|
-
height: 2
|
|
251
|
+
height: 2,
|
|
252
|
+
ccw_rotation: 45
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Drills can use circular, pill, or rotated pill geometry. `hole_offset_x` and
|
|
257
|
+
`hole_offset_y` move the drill center independently of the pad/copper center:
|
|
258
|
+
|
|
259
|
+
```js
|
|
260
|
+
{
|
|
261
|
+
type: 'pcb_plated_hole',
|
|
262
|
+
x: 10,
|
|
263
|
+
y: 8,
|
|
264
|
+
hole_shape: 'rotated_pill',
|
|
265
|
+
hole_width: 0.5,
|
|
266
|
+
hole_height: 1.5,
|
|
267
|
+
hole_offset_x: 0.1,
|
|
268
|
+
hole_offset_y: -0.1,
|
|
269
|
+
outer_diameter: 2
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
SMT pads can use circular, rectangular, rotated rectangular, or pill geometry.
|
|
274
|
+
Pill pads are normalized as rounded rectangles so their copper keeps the
|
|
275
|
+
expected capsule outline:
|
|
276
|
+
|
|
277
|
+
```js
|
|
278
|
+
{
|
|
279
|
+
type: 'pcb_smtpad',
|
|
280
|
+
layer: 'top',
|
|
281
|
+
shape: 'rotated_pill',
|
|
282
|
+
x: 12,
|
|
283
|
+
y: 8,
|
|
284
|
+
width: 2,
|
|
285
|
+
height: 1,
|
|
286
|
+
ccw_rotation: 30
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
SMT pads, plated holes, and vias honor `is_covered_with_solder_mask` and
|
|
291
|
+
`covered_with_solder_mask` when present. `true` keeps the copper under solder
|
|
292
|
+
mask, while `false` exposes the copper on the applicable board side. For vias,
|
|
293
|
+
covered values map to tenting metadata on both sides.
|
|
294
|
+
|
|
295
|
+
Copper pours can use rectangular, polygon, or B-Rep geometry. Rectangular pours
|
|
296
|
+
support `rotation` or `ccw_rotation`; polygon and B-Rep point coordinates are
|
|
297
|
+
converted from millimeters to mils. `covered_with_solder_mask: true` renders the
|
|
298
|
+
zone under solder mask, while false or omitted values expose the copper:
|
|
299
|
+
|
|
300
|
+
```js
|
|
301
|
+
{
|
|
302
|
+
type: 'pcb_copper_pour',
|
|
303
|
+
layer: 'top',
|
|
304
|
+
shape: 'rect',
|
|
305
|
+
center: { x: 25, y: 15 },
|
|
306
|
+
width: 8,
|
|
307
|
+
height: 5,
|
|
308
|
+
rotation: 45,
|
|
309
|
+
covered_with_solder_mask: false
|
|
201
310
|
}
|
|
202
311
|
```
|
|
203
312
|
|
|
313
|
+
Silkscreen text uses `anchor_position` when available, falling back to `x` and
|
|
314
|
+
`y`. Common `anchor_alignment` values such as `center`, `bottom_left`, or
|
|
315
|
+
`top_right` are normalized into horizontal and vertical alignment metadata for
|
|
316
|
+
stroke text rendering.
|
|
317
|
+
|
|
204
318
|
Component model metadata can be supplied on `cad_component` elements with
|
|
205
|
-
`model_step_url`, `model_wrl_url`, `model_glb_url`,
|
|
206
|
-
`model_stl_url`, or `model_obj_url`. The adapter emits
|
|
207
|
-
metadata for the host runtime or export pipeline to resolve.
|
|
319
|
+
`model_3mf_url`, `model_step_url`, `model_wrl_url`, `model_glb_url`,
|
|
320
|
+
`model_gltf_url`, `model_stl_url`, or `model_obj_url`. The adapter emits
|
|
321
|
+
external placement metadata for the host runtime or export pipeline to resolve.
|
|
208
322
|
|
|
209
|
-
Traces can be supplied as a `route` array. Each adjacent point pair becomes
|
|
210
|
-
track segment:
|
|
323
|
+
Traces can be supplied as a `route` array. Each adjacent wire point pair becomes
|
|
324
|
+
one track segment:
|
|
211
325
|
|
|
212
326
|
```js
|
|
213
327
|
{
|
|
@@ -222,6 +336,31 @@ track segment:
|
|
|
222
336
|
}
|
|
223
337
|
```
|
|
224
338
|
|
|
339
|
+
Route entries with `route_type: 'via'` produce via primitives when their
|
|
340
|
+
`from_layer`, `to_layer`, or `layer` touches the top or bottom surface. Adjacent
|
|
341
|
+
surface wire segments remain visible through those vias. Inner-only vias and
|
|
342
|
+
inner-only wire segments are ignored by the surface-copper view. If a route via
|
|
343
|
+
omits `hole_diameter`, `via_hole_diameter`, or `drill_diameter`, the drill
|
|
344
|
+
diameter defaults to half of `via_diameter`.
|
|
345
|
+
|
|
346
|
+
```js
|
|
347
|
+
{
|
|
348
|
+
type: 'pcb_trace',
|
|
349
|
+
route: [
|
|
350
|
+
{ route_type: 'wire', x: 5, y: 5, width: 0.2, layer: 'top' },
|
|
351
|
+
{
|
|
352
|
+
route_type: 'via',
|
|
353
|
+
x: 8,
|
|
354
|
+
y: 5,
|
|
355
|
+
from_layer: 'top',
|
|
356
|
+
to_layer: 'bottom',
|
|
357
|
+
via_diameter: 0.4
|
|
358
|
+
},
|
|
359
|
+
{ route_type: 'wire', x: 8, y: 10, width: 0.2, layer: '32' }
|
|
360
|
+
]
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
225
364
|
## Diagnostics
|
|
226
365
|
|
|
227
366
|
Malformed CircuitJSON input is rejected by `circuitjson-toolkit` before render
|
package/docs/model-format.md
CHANGED
|
@@ -35,6 +35,10 @@ toolkits are responsible for creating this data.
|
|
|
35
35
|
silkscreen: {
|
|
36
36
|
top: {},
|
|
37
37
|
bottom: {}
|
|
38
|
+
},
|
|
39
|
+
paste: {
|
|
40
|
+
top: {},
|
|
41
|
+
bottom: {}
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
}
|
|
@@ -92,14 +96,48 @@ External placements describe STEP, WRL, GLB, GLTF, STL, or OBJ model instances:
|
|
|
92
96
|
name: 'soic-8.step',
|
|
93
97
|
relativePath: 'packages/soic-8.step',
|
|
94
98
|
format: 'step',
|
|
99
|
+
sourceUrl: '/models/soic-8.step',
|
|
100
|
+
resolvedUrl: 'https://assets.example.invalid/models/soic-8.step',
|
|
95
101
|
file: File
|
|
96
102
|
}
|
|
97
103
|
}
|
|
98
104
|
```
|
|
99
105
|
|
|
100
106
|
OBJ models can carry sidecar material libraries in `resources`, `relatedFiles`,
|
|
101
|
-
`assets`, or similar metadata collections.
|
|
102
|
-
|
|
107
|
+
`assets`, or similar metadata collections. OBJ diffuse, ambient, specular,
|
|
108
|
+
shininess, alpha, and vertex colors are preserved in the parsed mesh metadata.
|
|
109
|
+
GLTF/GLB material alpha, mesh opacity, and `COLOR_0` vertex colors are
|
|
110
|
+
preserved in imported and exported GLTF/GLB materials and primitives.
|
|
111
|
+
GLTF/GLB import consumes triangle primitives and ignores unsupported primitive
|
|
112
|
+
modes such as points or lines. Remote `.gltf` files can reference external
|
|
113
|
+
`buffers[].uri` sidecars; when URL-backed model loading is enabled, the loader
|
|
114
|
+
resolves those sidecars relative to the `.gltf` URL and fetches them with the
|
|
115
|
+
same cache, timeout, and auth-header policy.
|
|
116
|
+
|
|
117
|
+
Resolved model URLs are not fetched by default. Hosts that want URL-backed model
|
|
118
|
+
loading must provide an explicit fetch policy through `PcbAssemblyModelMeshLoader`
|
|
119
|
+
or a higher-level export service. Use `authHeaders`, `fetchTimeoutMs`, and a
|
|
120
|
+
caller-owned `modelCache` when remote model requests are enabled.
|
|
121
|
+
|
|
122
|
+
When `projectBaseUrl` is provided to the CircuitJSON adapter, package-style
|
|
123
|
+
model paths such as `node_modules/package-name/path/to/model.step` are resolved
|
|
124
|
+
to `/package_files/download` on the project origin with
|
|
125
|
+
`package_name_with_version` and `file_path` query parameters. Package download
|
|
126
|
+
file paths are normalized under `dist/` unless they already start there. This
|
|
127
|
+
only records a resolved URL; fetching still requires an explicit loader/export
|
|
128
|
+
fetch policy.
|
|
129
|
+
|
|
130
|
+
CAD placements can request procedural bounding-box output with
|
|
131
|
+
`show_as_bounding_box: true`. The adapter marks those placements with
|
|
132
|
+
`renderAsBoundingBox: true`, copies `size`/`model_size` into fallback body
|
|
133
|
+
dimensions when present, and the assembly geometry builder skips external model
|
|
134
|
+
loading for that placement.
|
|
135
|
+
|
|
136
|
+
When package footprint text is available, fallback bodies can derive simple
|
|
137
|
+
package-specific geometry for passive chips, dual-row and quad-row ICs,
|
|
138
|
+
SOT-style packages, pin headers, pushbuttons, testpoints, and TO-style
|
|
139
|
+
through-hole packages. Testpoints and radial capacitors use round fallback
|
|
140
|
+
bodies in both runtime rendering and assembly export.
|
|
103
141
|
|
|
104
142
|
Embedded STEP models can use:
|
|
105
143
|
|
|
@@ -118,11 +156,24 @@ The runtime expects pre-normalized primitive lists for:
|
|
|
118
156
|
|
|
119
157
|
- pads and vias with drill and copper dimensions;
|
|
120
158
|
- tracks and arcs with layer metadata;
|
|
121
|
-
- fills and polygons with point loops;
|
|
159
|
+
- fills and polygons with point loops or B-Rep ring metadata;
|
|
122
160
|
- silkscreen tracks, arcs, fills, and texts;
|
|
161
|
+
- optional solder-paste fills in `detail.paste`;
|
|
123
162
|
- copper text primitives.
|
|
124
163
|
|
|
125
164
|
The viewer does not infer source-file semantics. When a scene needs
|
|
126
165
|
format-specific layer mapping, solder-mask interpretation, text layout, or
|
|
127
166
|
model matching, the source toolkit should encode those decisions in the scene
|
|
128
167
|
description.
|
|
168
|
+
|
|
169
|
+
Through-hole pads can describe offset or slotted drills with `holeOffsetX`,
|
|
170
|
+
`holeOffsetY`, `holeSlotLength`, and `holeRotation`. Board substrate export uses
|
|
171
|
+
`detail.drillQuality` values of `low`, `medium`, or `high` to choose circular
|
|
172
|
+
drill sampling density.
|
|
173
|
+
|
|
174
|
+
Rounded SMT pads set `hasRoundedRect`, the side-specific
|
|
175
|
+
`roundedRectShapeTop`/`roundedRectShapeBottom`, and
|
|
176
|
+
`cornerRadiusTop`/`cornerRadiusBottom` as a percent of the shortest copper side.
|
|
177
|
+
Route-derived vias use the same `detail.vias` shape as standalone vias, and
|
|
178
|
+
surface route segments use `layerId: 1` for top copper or `layerId: 32` for
|
|
179
|
+
bottom copper.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pcb-scene3d-viewer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.44",
|
|
4
4
|
"description": "Reusable Three.js PCB 3D scene viewer for normalized ECAD and CircuitJSON scene descriptions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pcb",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@sunbox/occt-import-js": "^0.0.25",
|
|
55
|
-
"circuitjson-toolkit": "^1.0.
|
|
55
|
+
"circuitjson-toolkit": "^1.0.10",
|
|
56
56
|
"earcut": "^3.0.2",
|
|
57
57
|
"fflate": "^0.8.2",
|
|
58
58
|
"three": "^0.183.2"
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridges CAD component model asset metadata to scene model resolution.
|
|
3
|
+
*/
|
|
4
|
+
export class CircuitJsonCadModelAssetResolver {
|
|
5
|
+
/**
|
|
6
|
+
* Adds URL fields derived from CAD `model_asset` records.
|
|
7
|
+
* @param {object | object[]} documentModel Document model.
|
|
8
|
+
* @returns {object | object[]}
|
|
9
|
+
*/
|
|
10
|
+
static withModelAssetUrls(documentModel) {
|
|
11
|
+
const elements =
|
|
12
|
+
CircuitJsonCadModelAssetResolver.#elements(documentModel)
|
|
13
|
+
if (
|
|
14
|
+
!elements.some((element) =>
|
|
15
|
+
CircuitJsonCadModelAssetResolver.#modelAssetUrlField(element)
|
|
16
|
+
)
|
|
17
|
+
) {
|
|
18
|
+
return documentModel
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const nextElements = elements.map((element) =>
|
|
22
|
+
CircuitJsonCadModelAssetResolver.#withModelAssetUrl(element)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if (Array.isArray(documentModel)) {
|
|
26
|
+
Object.assign(nextElements, {
|
|
27
|
+
sourceFormat: documentModel.sourceFormat,
|
|
28
|
+
kind: documentModel.kind,
|
|
29
|
+
fileName: documentModel.fileName,
|
|
30
|
+
bom: documentModel.bom
|
|
31
|
+
})
|
|
32
|
+
return nextElements
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (Array.isArray(documentModel?.elements)) {
|
|
36
|
+
return { ...documentModel, elements: nextElements }
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(documentModel?.circuitJson)) {
|
|
39
|
+
return { ...documentModel, circuitJson: nextElements }
|
|
40
|
+
}
|
|
41
|
+
return documentModel
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Adds a session-asset-aware model URL resolver.
|
|
46
|
+
* @param {object} options Scene preparation options.
|
|
47
|
+
* @returns {object}
|
|
48
|
+
*/
|
|
49
|
+
static withSessionAssetResolver(options = {}) {
|
|
50
|
+
const sessionAssets = Array.isArray(options.sessionAssets)
|
|
51
|
+
? options.sessionAssets
|
|
52
|
+
: []
|
|
53
|
+
const callerResolver =
|
|
54
|
+
typeof options.modelUrlResolver === 'function'
|
|
55
|
+
? options.modelUrlResolver
|
|
56
|
+
: null
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...options,
|
|
60
|
+
modelUrlResolver: (url, context) =>
|
|
61
|
+
CircuitJsonCadModelAssetResolver.#resolveModelUrl(
|
|
62
|
+
url,
|
|
63
|
+
context,
|
|
64
|
+
sessionAssets,
|
|
65
|
+
callerResolver
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Adds a model URL field to one CAD component when possible.
|
|
72
|
+
* @param {object} element Element row.
|
|
73
|
+
* @returns {object}
|
|
74
|
+
*/
|
|
75
|
+
static #withModelAssetUrl(element) {
|
|
76
|
+
const field =
|
|
77
|
+
CircuitJsonCadModelAssetResolver.#modelAssetUrlField(element)
|
|
78
|
+
if (!field) return element
|
|
79
|
+
const asset = element.model_asset || {}
|
|
80
|
+
const sourceUrl = String(
|
|
81
|
+
asset.project_relative_path || asset.url || ''
|
|
82
|
+
).trim()
|
|
83
|
+
if (!sourceUrl) return element
|
|
84
|
+
return { ...element, [field]: sourceUrl }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Resolves a model URL field for one CAD component asset.
|
|
89
|
+
* @param {object} element Element row.
|
|
90
|
+
* @returns {string}
|
|
91
|
+
*/
|
|
92
|
+
static #modelAssetUrlField(element) {
|
|
93
|
+
if (element?.type !== 'cad_component') return ''
|
|
94
|
+
const asset = element.model_asset
|
|
95
|
+
if (!asset || typeof asset !== 'object') return ''
|
|
96
|
+
if (CircuitJsonCadModelAssetResolver.#hasModelUrl(element)) return ''
|
|
97
|
+
const format = CircuitJsonCadModelAssetResolver.#format(asset)
|
|
98
|
+
return (
|
|
99
|
+
{
|
|
100
|
+
'3mf': 'model_3mf_url',
|
|
101
|
+
glb: 'model_glb_url',
|
|
102
|
+
gltf: 'model_gltf_url',
|
|
103
|
+
obj: 'model_obj_url',
|
|
104
|
+
step: 'model_step_url',
|
|
105
|
+
stl: 'model_stl_url',
|
|
106
|
+
wrl: 'model_wrl_url'
|
|
107
|
+
}[format] || ''
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resolves a URL to a session asset or caller-provided value.
|
|
113
|
+
* @param {string} url Model URL.
|
|
114
|
+
* @param {object} context Resolver context.
|
|
115
|
+
* @param {object[]} sessionAssets Session asset rows.
|
|
116
|
+
* @param {((url: string, context: object) => any) | null} callerResolver Caller resolver.
|
|
117
|
+
* @returns {object | string | null}
|
|
118
|
+
*/
|
|
119
|
+
static #resolveModelUrl(url, context, sessionAssets, callerResolver) {
|
|
120
|
+
const matchedAsset =
|
|
121
|
+
CircuitJsonCadModelAssetResolver.#matchingSessionAsset(
|
|
122
|
+
url,
|
|
123
|
+
context,
|
|
124
|
+
sessionAssets
|
|
125
|
+
)
|
|
126
|
+
if (matchedAsset) {
|
|
127
|
+
return matchedAsset
|
|
128
|
+
}
|
|
129
|
+
return callerResolver ? callerResolver(url, context) : null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Finds a session asset matching one CAD model URL.
|
|
134
|
+
* @param {string} url Model URL.
|
|
135
|
+
* @param {object} context Resolver context.
|
|
136
|
+
* @param {object[]} sessionAssets Session assets.
|
|
137
|
+
* @returns {object | null}
|
|
138
|
+
*/
|
|
139
|
+
static #matchingSessionAsset(url, context, sessionAssets) {
|
|
140
|
+
const candidates = CircuitJsonCadModelAssetResolver.#urlCandidates(
|
|
141
|
+
url,
|
|
142
|
+
context
|
|
143
|
+
)
|
|
144
|
+
return (
|
|
145
|
+
sessionAssets.find((asset) =>
|
|
146
|
+
CircuitJsonCadModelAssetResolver.#assetMatches(
|
|
147
|
+
asset,
|
|
148
|
+
candidates
|
|
149
|
+
)
|
|
150
|
+
) || null
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Builds normalized URL/path candidates for matching.
|
|
156
|
+
* @param {string} url Model URL.
|
|
157
|
+
* @param {object} context Resolver context.
|
|
158
|
+
* @returns {Set<string>}
|
|
159
|
+
*/
|
|
160
|
+
static #urlCandidates(url, context) {
|
|
161
|
+
return new Set(
|
|
162
|
+
[
|
|
163
|
+
url,
|
|
164
|
+
context?.cadComponent?.model_asset?.project_relative_path,
|
|
165
|
+
context?.cadComponent?.model_asset?.url
|
|
166
|
+
]
|
|
167
|
+
.map((value) => CircuitJsonCadModelAssetResolver.#key(value))
|
|
168
|
+
.filter(Boolean)
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Returns true when a session asset matches a model URL.
|
|
174
|
+
* @param {object} asset Session asset.
|
|
175
|
+
* @param {Set<string>} candidates URL candidates.
|
|
176
|
+
* @returns {boolean}
|
|
177
|
+
*/
|
|
178
|
+
static #assetMatches(asset, candidates) {
|
|
179
|
+
return [
|
|
180
|
+
asset?.relativePath,
|
|
181
|
+
asset?.sourceUrl,
|
|
182
|
+
asset?.url,
|
|
183
|
+
asset?.name
|
|
184
|
+
].some((value) =>
|
|
185
|
+
candidates.has(CircuitJsonCadModelAssetResolver.#key(value))
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Resolves a supported model format from asset metadata.
|
|
191
|
+
* @param {object} asset Model asset metadata.
|
|
192
|
+
* @returns {string}
|
|
193
|
+
*/
|
|
194
|
+
static #format(asset) {
|
|
195
|
+
const text = String(
|
|
196
|
+
asset.format ||
|
|
197
|
+
asset.mimetype ||
|
|
198
|
+
asset.project_relative_path ||
|
|
199
|
+
asset.url ||
|
|
200
|
+
''
|
|
201
|
+
).toLowerCase()
|
|
202
|
+
if (/\bglb\b|\.glb(?:[?#]|$)/u.test(text)) return 'glb'
|
|
203
|
+
if (/\bgltf\b|\.gltf(?:[?#]|$)/u.test(text)) return 'gltf'
|
|
204
|
+
if (/\b3mf\b|\.3mf(?:[?#]|$)/u.test(text)) return '3mf'
|
|
205
|
+
if (/\bobj\b|\.obj(?:[?#]|$)/u.test(text)) return 'obj'
|
|
206
|
+
if (/\bstl\b|\.stl(?:[?#]|$)/u.test(text)) return 'stl'
|
|
207
|
+
if (/\bwrl\b|vrml|\.wrl(?:[?#]|$)/u.test(text)) return 'wrl'
|
|
208
|
+
if (/step|stp|\.step(?:[?#]|$)|\.stp(?:[?#]|$)/u.test(text)) {
|
|
209
|
+
return 'step'
|
|
210
|
+
}
|
|
211
|
+
return ''
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Returns true when a CAD component already has a model URL.
|
|
216
|
+
* @param {object} element Element row.
|
|
217
|
+
* @returns {boolean}
|
|
218
|
+
*/
|
|
219
|
+
static #hasModelUrl(element) {
|
|
220
|
+
return [
|
|
221
|
+
'model_step_url',
|
|
222
|
+
'model_stp_url',
|
|
223
|
+
'model_3mf_url',
|
|
224
|
+
'model_wrl_url',
|
|
225
|
+
'model_vrml_url',
|
|
226
|
+
'model_glb_url',
|
|
227
|
+
'model_gltf_url',
|
|
228
|
+
'model_stl_url',
|
|
229
|
+
'model_obj_url'
|
|
230
|
+
].some((field) => String(element?.[field] || '').trim())
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Reads element rows from document wrappers.
|
|
235
|
+
* @param {object | object[]} documentModel Document model.
|
|
236
|
+
* @returns {object[]}
|
|
237
|
+
*/
|
|
238
|
+
static #elements(documentModel) {
|
|
239
|
+
if (Array.isArray(documentModel)) return documentModel
|
|
240
|
+
if (Array.isArray(documentModel?.elements))
|
|
241
|
+
return documentModel.elements
|
|
242
|
+
if (Array.isArray(documentModel?.circuitJson)) {
|
|
243
|
+
return documentModel.circuitJson
|
|
244
|
+
}
|
|
245
|
+
return []
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Normalizes a path or URL key.
|
|
250
|
+
* @param {unknown} value Raw path.
|
|
251
|
+
* @returns {string}
|
|
252
|
+
*/
|
|
253
|
+
static #key(value) {
|
|
254
|
+
return String(value || '')
|
|
255
|
+
.trim()
|
|
256
|
+
.split(/[?#]/u)[0]
|
|
257
|
+
.replace(/^https?:\/\/[^/]+\//iu, '')
|
|
258
|
+
.replace(/^\/+/u, '')
|
|
259
|
+
.toLowerCase()
|
|
260
|
+
}
|
|
261
|
+
}
|