pcb-scene3d-viewer 1.2.1 → 1.3.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 +16 -1
- package/docs/api.md +26 -0
- package/docs/circuitjson.md +21 -8
- package/docs/release-notes-v1.2.2.md +33 -0
- package/docs/release-notes-v1.3.0.md +38 -0
- package/package.json +4 -2
- package/src/CircuitJsonCadModelAssetResolver.mjs +24 -10
- package/src/PcbAssemblyGltfModelMeshParser.mjs +3 -1
- package/src/PcbAssemblyModelMeshLoader.mjs +1 -1
- package/src/PcbAssemblyTextModelMeshParser.mjs +3 -1
- package/src/PcbScene3dBoardAssemblyPresentation.mjs +1 -11
- package/src/PcbScene3dBoardMaterialPalette.mjs +12 -0
- package/src/PcbScene3dCircuitJsonAdapter.mjs +34 -86
- package/src/PcbScene3dCircuitJsonCopperPourBuilder.mjs +6 -1
- package/src/PcbScene3dCircuitJsonCopperTextBuilder.mjs +385 -0
- package/src/PcbScene3dCircuitJsonDocumentationArtworkBuilder.mjs +123 -22
- package/src/PcbScene3dCircuitJsonGeometry.mjs +12 -0
- package/src/PcbScene3dCircuitJsonPadCorner.mjs +72 -0
- package/src/PcbScene3dCircuitJsonSilkscreenBuilder.mjs +140 -25
- package/src/PcbScene3dCircuitJsonSilkscreenDetailBuilder.mjs +21 -0
- package/src/PcbScene3dCircuitJsonSourceLayer.mjs +124 -0
- package/src/PcbScene3dCircuitJsonTraceRouteBuilder.mjs +56 -17
- package/src/PcbScene3dCopperDetailFilter.mjs +5 -1
- package/src/PcbScene3dCopperFactory.mjs +23 -4
- package/src/PcbScene3dCopperTextFactory.mjs +105 -20
- package/src/PcbScene3dExternalModels.mjs +26 -0
- package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +16 -1
- package/src/PcbScene3dModelContent.mjs +32 -1
- package/src/PcbScene3dRuntimeBoardMeshes.mjs +2 -4
- package/src/PcbScene3dSilkscreenCopperCutoutBuilder.mjs +588 -0
- package/src/PcbScene3dStepLoader.mjs +2 -1
- package/src/PcbScene3dStrokeCutoutBuilder.mjs +98 -0
- package/src/PcbScene3dViaFactory.mjs +51 -5
- package/src/PcbScene3dViaLayerSpan.mjs +126 -0
package/README.md
CHANGED
|
@@ -22,9 +22,15 @@ factories, model loading, component picking, view presets, archive export,
|
|
|
22
22
|
GLTF/GLB assembly writing, and optional DOM shell can be reused by other
|
|
23
23
|
browser-based ECAD tools.
|
|
24
24
|
|
|
25
|
+
Version 1.3.0 renders the CircuitJSON 1.2 fidelity contract directly: exact
|
|
26
|
+
oval and rounded pads, filled and mirrored silkscreen, copper text and cutouts,
|
|
27
|
+
surface-aware blind/buried vias, collision-safe model assets, and a consistent
|
|
28
|
+
light FR-4 substrate edge. See the
|
|
29
|
+
[1.3.0 release notes](docs/release-notes-v1.3.0.md).
|
|
30
|
+
|
|
25
31
|
## CircuitJSON 1.1 convergence
|
|
26
32
|
|
|
27
|
-
Version 1.2.
|
|
33
|
+
Version 1.2.2 accepts the common document and prepared-context shapes returned
|
|
28
34
|
by CircuitJSON, Gerber, Altium, and KiCad Toolkit 1.1-compatible APIs. The
|
|
29
35
|
adapter requests the shared `elements` index once and reuses it across repeated
|
|
30
36
|
scene builds. `PcbScene3dCircuitJsonAdapter.prepare()` exposes that proof-aware
|
|
@@ -47,6 +53,13 @@ Legal rectangular and square CircuitJSON drill apertures retain their exact
|
|
|
47
53
|
width, height, and board-space rotation through substrate, pad, and assembly
|
|
48
54
|
export meshes.
|
|
49
55
|
|
|
56
|
+
Canonical documents retain their exact `source.format` as the scene
|
|
57
|
+
`sourceFormat`; raw element arrays continue to use `circuitjson`. Routed traces
|
|
58
|
+
and copper pours with no authored solder-mask coverage value remain covered,
|
|
59
|
+
while `covered_with_solder_mask: false` keeps an explicit opening exposed.
|
|
60
|
+
Standard vias likewise default to tented and honor `is_tented: false` as an
|
|
61
|
+
explicit opening.
|
|
62
|
+
|
|
50
63
|
The live runtime loads STEP/STP, WRL/VRML, STL, OBJ, GLTF/GLB, and 3MF from
|
|
51
64
|
canonical text/bytes or browser files. Referenced GLTF buffers, OBJ material
|
|
52
65
|
libraries, and WRL textures are attached from matching document/session assets
|
|
@@ -123,6 +136,8 @@ const controller = new PcbScene3dController(viewportNode, document)
|
|
|
123
136
|
|
|
124
137
|
- [API](docs/api.md)
|
|
125
138
|
- [CircuitJSON usage](docs/circuitjson.md)
|
|
139
|
+
- [1.2.2 release notes](docs/release-notes-v1.2.2.md)
|
|
140
|
+
- [1.3.0 release notes](docs/release-notes-v1.3.0.md)
|
|
126
141
|
- [1.2.1 release notes](docs/release-notes-v1.2.1.md)
|
|
127
142
|
- [1.2.0 release notes](docs/release-notes-v1.2.0.md)
|
|
128
143
|
- [Model format](docs/model-format.md)
|
package/docs/api.md
CHANGED
|
@@ -68,6 +68,24 @@ controller only and are forwarded to the runtime through
|
|
|
68
68
|
|
|
69
69
|
## CircuitJSON Input
|
|
70
70
|
|
|
71
|
+
### `PcbScene3dBoardMaterialPalette`
|
|
72
|
+
|
|
73
|
+
The `pcb-scene3d-viewer/scene3d` subpath exports the shared board-material
|
|
74
|
+
resolver used by both runtime meshes and assembly export.
|
|
75
|
+
|
|
76
|
+
- `resolveSurfaceColor(board, options?)` returns the authored or fallback
|
|
77
|
+
solder-mask face color.
|
|
78
|
+
- `resolveBoardSurfaceColor(board, options?)` returns the display-darkened
|
|
79
|
+
solder-mask face color.
|
|
80
|
+
- `resolveEdgeColor(board)` returns an authored `edgeColor` or the light FR-4
|
|
81
|
+
substrate fallback `0xc9ca78`.
|
|
82
|
+
- `isGeneratedSurfaceVisible(options?)` and
|
|
83
|
+
`isGeneratedBodyVisible(options?)` expose the generated-board visibility
|
|
84
|
+
decision.
|
|
85
|
+
|
|
86
|
+
Runtime and exported board edges call the same resolver, so a missing edge
|
|
87
|
+
color cannot produce different substrate materials in the two paths.
|
|
88
|
+
|
|
71
89
|
### `PcbScene3dCircuitJsonAdapter`
|
|
72
90
|
|
|
73
91
|
Converts a common CircuitJSON `DocumentResult`, prepared
|
|
@@ -108,6 +126,14 @@ Methods:
|
|
|
108
126
|
and courtyard artwork as silkscreen detail. `showPcbPaste: true` renders
|
|
109
127
|
direct solder-paste artwork as a separate top/bottom overlay.
|
|
110
128
|
|
|
129
|
+
For canonical document and prepared-context inputs, the returned scene's
|
|
130
|
+
`sourceFormat` is the exact canonical `source.format`. Dense element arrays,
|
|
131
|
+
which do not carry source metadata, retain the `circuitjson` fallback. Routed
|
|
132
|
+
traces and copper pours default to covered when their coverage property is
|
|
133
|
+
omitted; an explicit `covered_with_solder_mask: false` remains an exposed
|
|
134
|
+
opening. Standard vias default to tented and honor `is_tented: false` as an
|
|
135
|
+
explicit opening.
|
|
136
|
+
|
|
111
137
|
`PcbScene3dController` and `PcbScene3dRuntime` call this adapter automatically
|
|
112
138
|
when they receive direct CircuitJSON input. See
|
|
113
139
|
[CircuitJSON usage](circuitjson.md) for supported elements, units, and examples.
|
package/docs/circuitjson.md
CHANGED
|
@@ -14,6 +14,10 @@ KiCad, Gerber, or other format-specific scene builder.
|
|
|
14
14
|
Prepared contexts are the fastest repeated-render path because validation and
|
|
15
15
|
the adapter's `elements` index are built at most once.
|
|
16
16
|
|
|
17
|
+
Canonical document envelopes and prepared contexts retain `source.format` as
|
|
18
|
+
the normalized scene's `sourceFormat`. Dense element arrays have no canonical
|
|
19
|
+
source metadata and therefore use `circuitjson`.
|
|
20
|
+
|
|
17
21
|
## Direct Controller Input
|
|
18
22
|
|
|
19
23
|
Pass any accepted CircuitJSON shape as the `documentModel`. The controller
|
|
@@ -403,13 +407,15 @@ expected capsule outline:
|
|
|
403
407
|
|
|
404
408
|
SMT pads, plated holes, and vias honor `is_covered_with_solder_mask` and
|
|
405
409
|
`covered_with_solder_mask` when present. `true` keeps the copper under solder
|
|
406
|
-
mask, while `false` exposes the copper on the applicable board side.
|
|
407
|
-
|
|
410
|
+
mask, while `false` exposes the copper on the applicable board side. Standard
|
|
411
|
+
vias also honor `is_tented`; omitted via tenting defaults to covered on both
|
|
412
|
+
sides, while `is_tented: false` retains an explicit opening.
|
|
408
413
|
|
|
409
414
|
Copper pours can use rectangular, polygon, or B-Rep geometry. Rectangular pours
|
|
410
415
|
support `rotation` or `ccw_rotation`; polygon and B-Rep point coordinates are
|
|
411
|
-
converted from millimeters to mils.
|
|
412
|
-
zone under solder mask, while
|
|
416
|
+
converted from millimeters to mils. Omitted or true
|
|
417
|
+
`covered_with_solder_mask` renders the zone under solder mask, while an
|
|
418
|
+
explicit false value exposes the copper:
|
|
413
419
|
|
|
414
420
|
```js
|
|
415
421
|
{
|
|
@@ -424,10 +430,12 @@ zone under solder mask, while false or omitted values expose the copper:
|
|
|
424
430
|
}
|
|
425
431
|
```
|
|
426
432
|
|
|
427
|
-
Silkscreen text
|
|
428
|
-
`y`.
|
|
429
|
-
`
|
|
430
|
-
|
|
433
|
+
Silkscreen and copper text use `anchor_position` when available, falling back
|
|
434
|
+
to `x` and `y`. Independent `font_width` and `font_height`, `stroke_width`,
|
|
435
|
+
`source_anchor_alignment`, source layer/type provenance, mirroring, and hidden
|
|
436
|
+
state are honored when present. Filled silkscreen shapes retain their fill and
|
|
437
|
+
drill/copper cutouts, and bottom-side artwork is mirrored around its authored
|
|
438
|
+
anchor instead of being repositioned as top-side text.
|
|
431
439
|
|
|
432
440
|
Component model metadata can be supplied on `cad_component` elements with
|
|
433
441
|
`model_3mf_url`, `model_step_url`, `model_wrl_url`, `model_glb_url`,
|
|
@@ -450,6 +458,11 @@ one track segment:
|
|
|
450
458
|
}
|
|
451
459
|
```
|
|
452
460
|
|
|
461
|
+
Trace-level or route-entry `covered_with_solder_mask` values control the
|
|
462
|
+
rendered opening. Omitted values default to covered; an explicit false value
|
|
463
|
+
remains exposed. A route-entry value takes precedence over its trace-level
|
|
464
|
+
value.
|
|
465
|
+
|
|
453
466
|
Route entries with `route_type: 'via'` produce via primitives when their
|
|
454
467
|
`from_layer`, `to_layer`, or `layer` touches the top or bottom surface. Adjacent
|
|
455
468
|
surface wire segments remain visible through those vias. Inner-only vias and
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 André Fiedler
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# PCB Scene3D Viewer 1.2.2
|
|
7
|
+
|
|
8
|
+
Version 1.2.2 preserves canonical source identity and aligns solder-mask
|
|
9
|
+
coverage with the converged CircuitJSON contract.
|
|
10
|
+
|
|
11
|
+
## API and behavior changes
|
|
12
|
+
|
|
13
|
+
- Canonical document and prepared-context inputs now retain their exact
|
|
14
|
+
`source.format` as the rendered scene's `sourceFormat`. Dense CircuitJSON
|
|
15
|
+
arrays continue to use the `circuitjson` fallback because they carry no
|
|
16
|
+
canonical source metadata.
|
|
17
|
+
- Routed traces and copper pours now default omitted
|
|
18
|
+
`covered_with_solder_mask` values to covered. Explicit false values remain
|
|
19
|
+
exposed through the solder mask.
|
|
20
|
+
- Standard vias now honor canonical `is_tented` metadata. Omitted values
|
|
21
|
+
default to tented, while `is_tented: false` remains exposed.
|
|
22
|
+
- Covered canonical copper follows the existing solder-mask material palette,
|
|
23
|
+
including tracks, pours, and via annuli.
|
|
24
|
+
|
|
25
|
+
Consumers that used the generic `circuitjson` marker for canonical documents
|
|
26
|
+
must now handle the retained source identity such as `gerber`, `altium`, or
|
|
27
|
+
`kicad`.
|
|
28
|
+
|
|
29
|
+
## Dependencies and validation
|
|
30
|
+
|
|
31
|
+
- Requires `circuitjson-toolkit ^1.1.2` and Node.js 20 or newer.
|
|
32
|
+
- The full test suite and both owned performance benchmarks cover the canonical
|
|
33
|
+
source, coverage, material, exact-geometry, and prepared-context paths.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# pcb-scene3d-viewer 1.3.0
|
|
2
|
+
|
|
3
|
+
This minor release consumes CircuitJSON Toolkit 1.2 directly and restores exact
|
|
4
|
+
PCB surface fidelity without source-format or host-app adapters.
|
|
5
|
+
|
|
6
|
+
## Rendering and API changes
|
|
7
|
+
|
|
8
|
+
- Oval, rounded-rectangle, pill, polygon, and independently rotated drilled
|
|
9
|
+
pads keep their authored copper and drill geometry.
|
|
10
|
+
- Silkscreen strokes, fills, text dimensions, anchors, mirroring, source-layer
|
|
11
|
+
mapping, and surface cutouts render from canonical CircuitJSON fields.
|
|
12
|
+
- Copper text participates in the same solder-mask and cutout pipeline as other
|
|
13
|
+
copper detail.
|
|
14
|
+
- Blind and buried vias render only on the board surfaces reached by their
|
|
15
|
+
explicit layer span.
|
|
16
|
+
- `PcbScene3dBoardMaterialPalette.resolveEdgeColor(board)` is now public on the
|
|
17
|
+
`scene3d` subpath. Runtime and exported board edges share its authored-color
|
|
18
|
+
lookup and light FR-4 fallback (`0xc9ca78`).
|
|
19
|
+
|
|
20
|
+
## Models and diagnostics
|
|
21
|
+
|
|
22
|
+
- Canonical and session CAD assets preserve exact case-sensitive project-path
|
|
23
|
+
aliases. Case-insensitive fallback is accepted only when unique, preventing
|
|
24
|
+
same-name and same-stem collisions across folders.
|
|
25
|
+
- Missing-model diagnostics are deferred until resolution has actually failed;
|
|
26
|
+
retryable or externally supplied assets are not reported as unavailable
|
|
27
|
+
prematurely.
|
|
28
|
+
- STEP, WRL, GLTF, and text/binary model payload handling avoids redundant
|
|
29
|
+
copies and retains the existing opt-in network policy.
|
|
30
|
+
|
|
31
|
+
## Compatibility and performance
|
|
32
|
+
|
|
33
|
+
- The runtime baseline is `circuitjson-toolkit@^1.2.0`; the existing
|
|
34
|
+
`@sunbox/occt-import-js@^0.0.28` WASM package remains compatible and does not
|
|
35
|
+
require a coordinated rebuild.
|
|
36
|
+
- Existing scene descriptions, document envelopes, controller/runtime
|
|
37
|
+
parameters, and return shapes remain accepted. The palette method and
|
|
38
|
+
CircuitJSON fields above are additive.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pcb-scene3d-viewer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Reusable Three.js PCB 3D scene viewer for normalized ECAD and CircuitJSON scene descriptions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pcb",
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
"docs/circuitjson.md",
|
|
36
36
|
"docs/release-notes-v1.2.0.md",
|
|
37
37
|
"docs/release-notes-v1.2.1.md",
|
|
38
|
+
"docs/release-notes-v1.2.2.md",
|
|
39
|
+
"docs/release-notes-v1.3.0.md",
|
|
38
40
|
"docs/model-format.md",
|
|
39
41
|
"docs/testing.md",
|
|
40
42
|
"spec",
|
|
@@ -56,7 +58,7 @@
|
|
|
56
58
|
},
|
|
57
59
|
"dependencies": {
|
|
58
60
|
"@sunbox/occt-import-js": "^0.0.28",
|
|
59
|
-
"circuitjson-toolkit": "^1.
|
|
61
|
+
"circuitjson-toolkit": "^1.2.0",
|
|
60
62
|
"earcut": "3.0.2",
|
|
61
63
|
"fflate": "^0.8.2",
|
|
62
64
|
"polygon-clipping": "^0.15.7",
|
|
@@ -352,7 +352,7 @@ export class CircuitJsonCadModelAssetResolver {
|
|
|
352
352
|
* Finds the earliest indexed asset matching one CAD model URL.
|
|
353
353
|
* @param {string} url Model URL.
|
|
354
354
|
* @param {object} context Resolver context.
|
|
355
|
-
* @param {Map<string, object>} assetIndex Asset alias index.
|
|
355
|
+
* @param {{ exact: Map<string, object | symbol>, folded: Map<string, object | symbol> }} assetIndex Asset alias index.
|
|
356
356
|
* @returns {object | null}
|
|
357
357
|
*/
|
|
358
358
|
static #matchingIndexedEntry(url, context, assetIndex) {
|
|
@@ -363,7 +363,11 @@ export class CircuitJsonCadModelAssetResolver {
|
|
|
363
363
|
let match = null
|
|
364
364
|
for (const candidate of candidates) {
|
|
365
365
|
const indexed = assetIndex.exact.get(candidate)
|
|
366
|
-
if (
|
|
366
|
+
if (
|
|
367
|
+
indexed &&
|
|
368
|
+
indexed !== AMBIGUOUS_ASSET &&
|
|
369
|
+
(!match || indexed.index < match.index)
|
|
370
|
+
) {
|
|
367
371
|
match = indexed
|
|
368
372
|
}
|
|
369
373
|
}
|
|
@@ -554,12 +558,13 @@ export class CircuitJsonCadModelAssetResolver {
|
|
|
554
558
|
|
|
555
559
|
/**
|
|
556
560
|
* Resolves an exact alias or one unambiguous case-insensitive fallback.
|
|
557
|
-
* @param {{ exact: Map<string, object>, folded: Map<string, object | symbol> }} index Asset index.
|
|
561
|
+
* @param {{ exact: Map<string, object | symbol>, folded: Map<string, object | symbol> }} index Asset index.
|
|
558
562
|
* @param {string} key Exact normalized key.
|
|
559
563
|
* @returns {object | null} Indexed asset entry.
|
|
560
564
|
*/
|
|
561
565
|
static #indexedAsset(index, key) {
|
|
562
566
|
const exact = index.exact.get(key)
|
|
567
|
+
if (exact === AMBIGUOUS_ASSET) return null
|
|
563
568
|
if (exact) return exact
|
|
564
569
|
const folded = index.folded.get(key.toLowerCase())
|
|
565
570
|
return folded && folded !== AMBIGUOUS_ASSET ? folded : null
|
|
@@ -606,10 +611,10 @@ export class CircuitJsonCadModelAssetResolver {
|
|
|
606
611
|
}
|
|
607
612
|
|
|
608
613
|
/**
|
|
609
|
-
* Builds one descriptor-safe
|
|
614
|
+
* Builds one descriptor-safe asset alias index with collision rejection.
|
|
610
615
|
* @param {object[]} assets Asset rows.
|
|
611
616
|
* @param {boolean} [canonical] Whether rows use the canonical toolkit asset contract.
|
|
612
|
-
* @returns {{ exact: Map<string, object>, folded: Map<string, object | symbol> }}
|
|
617
|
+
* @returns {{ exact: Map<string, object | symbol>, folded: Map<string, object | symbol> }}
|
|
613
618
|
*/
|
|
614
619
|
static #assetIndex(assets, canonical = false) {
|
|
615
620
|
const exact = new Map()
|
|
@@ -625,7 +630,12 @@ export class CircuitJsonCadModelAssetResolver {
|
|
|
625
630
|
for (const alias of CircuitJsonCadModelAssetResolver.#assetAliases(
|
|
626
631
|
asset
|
|
627
632
|
)) {
|
|
628
|
-
|
|
633
|
+
const exactPrevious = exact.get(alias)
|
|
634
|
+
if (!exactPrevious) {
|
|
635
|
+
exact.set(alias, entry)
|
|
636
|
+
} else if (exactPrevious !== entry) {
|
|
637
|
+
exact.set(alias, AMBIGUOUS_ASSET)
|
|
638
|
+
}
|
|
629
639
|
const foldedAlias = alias.toLowerCase()
|
|
630
640
|
const previous = folded.get(foldedAlias)
|
|
631
641
|
if (!previous) {
|
|
@@ -719,6 +729,9 @@ export class CircuitJsonCadModelAssetResolver {
|
|
|
719
729
|
asset,
|
|
720
730
|
'source'
|
|
721
731
|
)
|
|
732
|
+
const explicitAliases = CircuitJsonCadModelAssetResolver.#denseArray(
|
|
733
|
+
CircuitJsonCadModelAssetResolver.#ownData(asset, 'aliases')
|
|
734
|
+
).filter((value) => typeof value === 'string')
|
|
722
735
|
return new Set(
|
|
723
736
|
[
|
|
724
737
|
CircuitJsonCadModelAssetResolver.#ownData(
|
|
@@ -742,7 +755,8 @@ export class CircuitJsonCadModelAssetResolver {
|
|
|
742
755
|
'relativePath'
|
|
743
756
|
),
|
|
744
757
|
CircuitJsonCadModelAssetResolver.#ownData(source, 'url'),
|
|
745
|
-
CircuitJsonCadModelAssetResolver.#ownData(source, 'uri')
|
|
758
|
+
CircuitJsonCadModelAssetResolver.#ownData(source, 'uri'),
|
|
759
|
+
...explicitAliases
|
|
746
760
|
]
|
|
747
761
|
.map((value) => CircuitJsonCadModelAssetResolver.#key(value))
|
|
748
762
|
.filter(Boolean)
|
|
@@ -866,10 +880,10 @@ export class CircuitJsonCadModelAssetResolver {
|
|
|
866
880
|
*/
|
|
867
881
|
static #key(value) {
|
|
868
882
|
if (typeof value !== 'string' && typeof value !== 'number') return ''
|
|
869
|
-
|
|
883
|
+
const key = String(value || '')
|
|
870
884
|
.trim()
|
|
871
885
|
.split(/[?#]/u)[0]
|
|
872
|
-
.
|
|
873
|
-
|
|
886
|
+
.replaceAll('\\', '/')
|
|
887
|
+
return /^https?:\/\//iu.test(key) ? key : key.replace(/^\/+/u, '')
|
|
874
888
|
}
|
|
875
889
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PcbScene3dModelContent } from './PcbScene3dModelContent.mjs'
|
|
2
|
+
|
|
1
3
|
const MM_TO_MIL = 1000 / 25.4
|
|
2
4
|
const GLB_MAGIC = 0x46546c67
|
|
3
5
|
const GLB_VERSION = 2
|
|
@@ -887,7 +889,7 @@ export class PcbAssemblyGltfModelMeshParser {
|
|
|
887
889
|
return bytes
|
|
888
890
|
}
|
|
889
891
|
|
|
890
|
-
throw
|
|
892
|
+
throw PcbScene3dModelContent.unavailableError(label)
|
|
891
893
|
}
|
|
892
894
|
|
|
893
895
|
/**
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PcbScene3dModelContent } from './PcbScene3dModelContent.mjs'
|
|
2
|
+
|
|
1
3
|
const MM_TO_MIL = 1000 / 25.4
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -548,7 +550,7 @@ export class PcbAssemblyTextModelMeshParser {
|
|
|
548
550
|
return bytes
|
|
549
551
|
}
|
|
550
552
|
|
|
551
|
-
throw
|
|
553
|
+
throw PcbScene3dModelContent.unavailableError(label)
|
|
552
554
|
}
|
|
553
555
|
|
|
554
556
|
/**
|
|
@@ -35,8 +35,7 @@ export class PcbScene3dBoardAssemblyPresentation {
|
|
|
35
35
|
board,
|
|
36
36
|
options
|
|
37
37
|
)
|
|
38
|
-
const edgeColor =
|
|
39
|
-
PcbScene3dBoardAssemblyPresentation.#resolveEdgeColor(board)
|
|
38
|
+
const edgeColor = PcbScene3dBoardMaterialPalette.resolveEdgeColor(board)
|
|
40
39
|
const importedSurfaceColor =
|
|
41
40
|
PcbScene3dBoardAssemblyPresentation.#resolveImportedSurfaceColor(
|
|
42
41
|
meshRecords
|
|
@@ -426,15 +425,6 @@ export class PcbScene3dBoardAssemblyPresentation {
|
|
|
426
425
|
})
|
|
427
426
|
}
|
|
428
427
|
|
|
429
|
-
/**
|
|
430
|
-
* Resolves the app-level substrate edge color for board assembly meshes.
|
|
431
|
-
* @param {{ edgeColor?: number } | null | undefined} board Board dimensions.
|
|
432
|
-
* @returns {number}
|
|
433
|
-
*/
|
|
434
|
-
static #resolveEdgeColor(board) {
|
|
435
|
-
return Number.isInteger(board?.edgeColor) ? board.edgeColor : 0xc9ca78
|
|
436
|
-
}
|
|
437
|
-
|
|
438
428
|
/**
|
|
439
429
|
* Applies display treatment to board substrate material.
|
|
440
430
|
* @param {any | any[]} material Material or material list.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export class PcbScene3dBoardMaterialPalette {
|
|
5
5
|
static #DEFAULT_SURFACE_COLOR = 0x2a5f27
|
|
6
|
+
static #DEFAULT_EDGE_COLOR = 0xc9ca78
|
|
6
7
|
static #BOARD_SURFACE_DARKEN_RATIO = 0.88
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -39,6 +40,17 @@ export class PcbScene3dBoardMaterialPalette {
|
|
|
39
40
|
)
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Resolves the visible substrate-core color for board edge faces.
|
|
45
|
+
* @param {{ edgeColor?: number } | null | undefined} board Board metadata.
|
|
46
|
+
* @returns {number}
|
|
47
|
+
*/
|
|
48
|
+
static resolveEdgeColor(board) {
|
|
49
|
+
return Number.isInteger(board?.edgeColor)
|
|
50
|
+
? board.edgeColor
|
|
51
|
+
: PcbScene3dBoardMaterialPalette.#DEFAULT_EDGE_COLOR
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
/**
|
|
43
55
|
* Returns true when the source format supplies display-stable board colors.
|
|
44
56
|
* @param {{ sourceFormat?: string }} options Scene options.
|
|
@@ -7,7 +7,8 @@ import { PcbScene3dCircuitJsonGeometry } from './PcbScene3dCircuitJsonGeometry.m
|
|
|
7
7
|
import { PcbScene3dCircuitJsonDrillDetail } from './PcbScene3dCircuitJsonDrillDetail.mjs'
|
|
8
8
|
import { PcbScene3dCircuitJsonModelTransform } from './PcbScene3dCircuitJsonModelTransform.mjs'
|
|
9
9
|
import { PcbScene3dCircuitJsonCopperPourBuilder } from './PcbScene3dCircuitJsonCopperPourBuilder.mjs'
|
|
10
|
-
import {
|
|
10
|
+
import { PcbScene3dCircuitJsonCopperTextBuilder } from './PcbScene3dCircuitJsonCopperTextBuilder.mjs'
|
|
11
|
+
import { PcbScene3dCircuitJsonSilkscreenDetailBuilder } from './PcbScene3dCircuitJsonSilkscreenDetailBuilder.mjs'
|
|
11
12
|
import { PcbScene3dFootprintBodyBuilder } from './PcbScene3dFootprintBodyBuilder.mjs'
|
|
12
13
|
import { PcbScene3dCircuitJsonLayer } from './PcbScene3dCircuitJsonLayer.mjs'
|
|
13
14
|
import { PcbScene3dCircuitJsonInput } from './PcbScene3dCircuitJsonInput.mjs'
|
|
@@ -18,6 +19,7 @@ import { PcbScene3dCircuitJsonSolderPasteBuilder } from './PcbScene3dCircuitJson
|
|
|
18
19
|
import { CircuitJsonCadModelAssetResolver } from './CircuitJsonCadModelAssetResolver.mjs'
|
|
19
20
|
import { PcbScene3dDescriptorSafeRecord } from './PcbScene3dDescriptorSafeRecord.mjs'
|
|
20
21
|
import { PcbScene3dCircuitJsonModelAsset } from './PcbScene3dCircuitJsonModelAsset.mjs'
|
|
22
|
+
import { PcbScene3dCircuitJsonPadCorner } from './PcbScene3dCircuitJsonPadCorner.mjs'
|
|
21
23
|
|
|
22
24
|
const DEFAULT_COMPONENT_HEIGHT_MIL = 60
|
|
23
25
|
const RECTANGULAR_PAD_SHAPE = 2
|
|
@@ -131,7 +133,7 @@ export class PcbScene3dCircuitJsonAdapter {
|
|
|
131
133
|
)
|
|
132
134
|
|
|
133
135
|
return {
|
|
134
|
-
sourceFormat: 'circuitjson',
|
|
136
|
+
sourceFormat: context.source?.format || 'circuitjson',
|
|
135
137
|
coordinateSystem: 'circuitjson-mm',
|
|
136
138
|
board,
|
|
137
139
|
components,
|
|
@@ -682,27 +684,34 @@ export class PcbScene3dCircuitJsonAdapter {
|
|
|
682
684
|
index,
|
|
683
685
|
options
|
|
684
686
|
)
|
|
687
|
+
const pads = [
|
|
688
|
+
...PcbScene3dCircuitJsonAdapter.#buildSmtPads(index),
|
|
689
|
+
...PcbScene3dCircuitJsonAdapter.#buildPlatedHoles(index),
|
|
690
|
+
...PcbScene3dCircuitJsonAdapter.#buildNonPlatedHoles(index)
|
|
691
|
+
]
|
|
692
|
+
const vias = PcbScene3dCircuitJsonTraceRouteBuilder.buildVias(index)
|
|
693
|
+
const tracks = [
|
|
694
|
+
...PcbScene3dCircuitJsonTraceRouteBuilder.buildTracks(index),
|
|
695
|
+
...PcbScene3dCircuitJsonThermalSpokeBuilder.build(index)
|
|
696
|
+
]
|
|
697
|
+
const polygons = PcbScene3dCircuitJsonCopperPourBuilder.build(index)
|
|
698
|
+
const copperTexts = PcbScene3dCircuitJsonCopperTextBuilder.build(index)
|
|
699
|
+
const silkscreen = PcbScene3dCircuitJsonSilkscreenDetailBuilder.build(
|
|
700
|
+
index,
|
|
701
|
+
{ pads, vias, tracks, polygons, copperTexts },
|
|
702
|
+
options
|
|
703
|
+
)
|
|
685
704
|
|
|
686
705
|
return {
|
|
687
|
-
pads
|
|
688
|
-
|
|
689
|
-
...PcbScene3dCircuitJsonAdapter.#buildPlatedHoles(index),
|
|
690
|
-
...PcbScene3dCircuitJsonAdapter.#buildNonPlatedHoles(index)
|
|
691
|
-
],
|
|
692
|
-
tracks: [
|
|
693
|
-
...PcbScene3dCircuitJsonTraceRouteBuilder.buildTracks(index),
|
|
694
|
-
...PcbScene3dCircuitJsonThermalSpokeBuilder.build(index)
|
|
695
|
-
],
|
|
706
|
+
pads,
|
|
707
|
+
tracks,
|
|
696
708
|
arcs: [],
|
|
697
709
|
fills: [],
|
|
698
|
-
vias
|
|
699
|
-
polygons
|
|
700
|
-
copperTexts
|
|
710
|
+
vias,
|
|
711
|
+
polygons,
|
|
712
|
+
copperTexts,
|
|
701
713
|
embeddedFonts: [],
|
|
702
|
-
silkscreen
|
|
703
|
-
index,
|
|
704
|
-
options
|
|
705
|
-
),
|
|
714
|
+
silkscreen,
|
|
706
715
|
...(paste ? { paste } : {}),
|
|
707
716
|
drillQuality: PcbScene3dCircuitJsonGeometry.normalizeDrillQuality(
|
|
708
717
|
options?.boardDrillQuality
|
|
@@ -745,11 +754,7 @@ export class PcbScene3dCircuitJsonAdapter {
|
|
|
745
754
|
holeDiameter: 0,
|
|
746
755
|
hasTopSolderMaskOpening: !isBottom && exposesCopper,
|
|
747
756
|
hasBottomSolderMaskOpening: isBottom && exposesCopper,
|
|
748
|
-
...
|
|
749
|
-
pad,
|
|
750
|
-
size,
|
|
751
|
-
isBottom
|
|
752
|
-
)
|
|
757
|
+
...PcbScene3dCircuitJsonPadCorner.metadata(pad, size, isBottom)
|
|
753
758
|
}
|
|
754
759
|
return padDetail
|
|
755
760
|
})
|
|
@@ -768,7 +773,7 @@ export class PcbScene3dCircuitJsonAdapter {
|
|
|
768
773
|
y: Number(hole?.y || 0)
|
|
769
774
|
})
|
|
770
775
|
const size =
|
|
771
|
-
|
|
776
|
+
PcbScene3dCircuitJsonGeometry.platedHoleOuterSize(geometry)
|
|
772
777
|
const center = CircuitJsonUnits.pointMmToMil({
|
|
773
778
|
x: hole?.x,
|
|
774
779
|
y: hole?.y
|
|
@@ -796,6 +801,11 @@ export class PcbScene3dCircuitJsonAdapter {
|
|
|
796
801
|
sizeMidY: size.height,
|
|
797
802
|
sizeBottomX: size.width,
|
|
798
803
|
sizeBottomY: size.height,
|
|
804
|
+
...PcbScene3dCircuitJsonPadCorner.metadata(
|
|
805
|
+
geometry,
|
|
806
|
+
size,
|
|
807
|
+
null
|
|
808
|
+
),
|
|
799
809
|
...PcbScene3dCircuitJsonDrillDetail.fields(drill),
|
|
800
810
|
holeOffsetX: drill.center.x - center.x,
|
|
801
811
|
holeOffsetY: drill.center.y - center.y,
|
|
@@ -852,68 +862,6 @@ export class PcbScene3dCircuitJsonAdapter {
|
|
|
852
862
|
height: CircuitJsonUnits.mmToMil(pad?.height, 1)
|
|
853
863
|
}
|
|
854
864
|
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Builds rounded-rectangle metadata for pill-shaped SMT pads.
|
|
858
|
-
* @param {object} pad Pad element.
|
|
859
|
-
* @param {{ width: number, height: number }} size Pad copper size.
|
|
860
|
-
* @param {boolean} isBottom True when the pad is on the bottom side.
|
|
861
|
-
* @returns {object}
|
|
862
|
-
*/
|
|
863
|
-
static #roundedPadMetadata(pad, size, isBottom) {
|
|
864
|
-
if (!String(pad?.shape || '').endsWith('pill')) {
|
|
865
|
-
return {}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
const cornerRadius =
|
|
869
|
-
PcbScene3dCircuitJsonAdapter.#roundedPadCornerRadiusPercent(
|
|
870
|
-
pad,
|
|
871
|
-
size
|
|
872
|
-
)
|
|
873
|
-
return {
|
|
874
|
-
hasRoundedRect: true,
|
|
875
|
-
roundedRectShapeTop: isBottom ? null : RECTANGULAR_PAD_SHAPE,
|
|
876
|
-
roundedRectShapeBottom: isBottom ? RECTANGULAR_PAD_SHAPE : null,
|
|
877
|
-
cornerRadiusTop: isBottom ? null : cornerRadius,
|
|
878
|
-
cornerRadiusBottom: isBottom ? cornerRadius : null
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
/**
|
|
883
|
-
* Resolves a pill-pad corner radius as a percent of the shortest side.
|
|
884
|
-
* @param {object} pad Pad element.
|
|
885
|
-
* @param {{ width: number, height: number }} size Pad copper size.
|
|
886
|
-
* @returns {number}
|
|
887
|
-
*/
|
|
888
|
-
static #roundedPadCornerRadiusPercent(pad, size) {
|
|
889
|
-
const width = CircuitJsonUnits.optionalLength(pad?.width)
|
|
890
|
-
const height = CircuitJsonUnits.optionalLength(pad?.height)
|
|
891
|
-
const radius = CircuitJsonUnits.optionalLength(pad?.radius)
|
|
892
|
-
if (width > 0 && height > 0 && radius > 0) {
|
|
893
|
-
return Math.min((radius / Math.min(width, height)) * 100, 50)
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
const shortestSide = Math.min(Number(size.width), Number(size.height))
|
|
897
|
-
const radiusMil = CircuitJsonUnits.mmToMil(pad?.radius, 0)
|
|
898
|
-
if (shortestSide > 0 && radiusMil > 0) {
|
|
899
|
-
return Math.min((radiusMil / shortestSide) * 100, 50)
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
return 50
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
/**
|
|
906
|
-
* Resolves plated-hole copper size from CircuitJSON fields.
|
|
907
|
-
* @param {object} geometry Shared drilled-primitive geometry.
|
|
908
|
-
* @returns {{ width: number, height: number }}
|
|
909
|
-
*/
|
|
910
|
-
static #platedHoleOuterSize(geometry) {
|
|
911
|
-
return {
|
|
912
|
-
width: CircuitJsonUnits.mmToMil(geometry?.width, 1),
|
|
913
|
-
height: CircuitJsonUnits.mmToMil(geometry?.height, 1)
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
|
|
917
865
|
/**
|
|
918
866
|
* Resolves the viewer pad shape code.
|
|
919
867
|
* @param {object} pad CircuitJSON pad element.
|
|
@@ -257,7 +257,12 @@ export class PcbScene3dCircuitJsonCopperPourBuilder {
|
|
|
257
257
|
*/
|
|
258
258
|
static #isCoveredWithMask(pour) {
|
|
259
259
|
const value = pour?.covered_with_solder_mask
|
|
260
|
-
|
|
260
|
+
if (typeof value === 'boolean') return value
|
|
261
|
+
if (value === undefined || value === null || value === '') return true
|
|
262
|
+
|
|
263
|
+
const text = String(value).trim().toLowerCase()
|
|
264
|
+
if (text === 'false') return false
|
|
265
|
+
return true
|
|
261
266
|
}
|
|
262
267
|
|
|
263
268
|
/**
|