pcb-scene3d-viewer 1.3.1 → 1.3.2
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 +9 -0
- package/docs/api.md +16 -0
- package/docs/release-notes-v1.3.2.md +39 -0
- package/package.json +3 -2
- package/spec/library-scope.md +2 -0
- package/src/PcbScene3dCopperDetailFilter.mjs +5 -3
- package/src/PcbScene3dCopperFillAreaClipper.mjs +43 -3
- package/src/PcbScene3dRuntime.mjs +25 -26
- package/src/PcbScene3dViaFactory.mjs +71 -28
- package/src/PcbScene3dVisibilityGraph.mjs +102 -0
- package/src/scene3d.mjs +2 -0
package/README.md
CHANGED
|
@@ -34,6 +34,14 @@ annulus, mixed top/bottom states remain independent, and fully open Gerber vias
|
|
|
34
34
|
retain the exposed-copper path. See the
|
|
35
35
|
[1.3.1 release notes](docs/release-notes-v1.3.1.md).
|
|
36
36
|
|
|
37
|
+
Version 1.3.2 applies render-group and component visibility through a
|
|
38
|
+
self-adjusting dependency graph, so persistent runtimes repair only the stage
|
|
39
|
+
affected by a known toggle or structural revision. It also keeps tented copper
|
|
40
|
+
below mask surfaces, avoids duplicate pad barrels for filtered vias, and
|
|
41
|
+
short-circuits non-subdividing copper-fill clipping without changing its
|
|
42
|
+
observable geometry. See the
|
|
43
|
+
[1.3.2 release notes](docs/release-notes-v1.3.2.md).
|
|
44
|
+
|
|
37
45
|
## CircuitJSON 1.1 convergence
|
|
38
46
|
|
|
39
47
|
Version 1.2.2 accepts the common document and prepared-context shapes returned
|
|
@@ -142,6 +150,7 @@ const controller = new PcbScene3dController(viewportNode, document)
|
|
|
142
150
|
|
|
143
151
|
- [API](docs/api.md)
|
|
144
152
|
- [CircuitJSON usage](docs/circuitjson.md)
|
|
153
|
+
- [1.3.2 release notes](docs/release-notes-v1.3.2.md)
|
|
145
154
|
- [1.3.1 release notes](docs/release-notes-v1.3.1.md)
|
|
146
155
|
- [1.2.2 release notes](docs/release-notes-v1.2.2.md)
|
|
147
156
|
- [1.3.0 release notes](docs/release-notes-v1.3.0.md)
|
package/docs/api.md
CHANGED
|
@@ -165,10 +165,26 @@ traversal, absolute paths, and URL schemes are never attached implicitly.
|
|
|
165
165
|
|
|
166
166
|
## Runtime
|
|
167
167
|
|
|
168
|
+
### `PcbScene3dVisibilityGraph`
|
|
169
|
+
|
|
170
|
+
Applies render-group visibility and per-component visibility as two ordered
|
|
171
|
+
self-adjusting computations. `apply(state, changedPaths)` starts at readers of
|
|
172
|
+
the supplied toggle or revision roots, reuses unaffected effects, and returns
|
|
173
|
+
the stage result map with each stage's `recomputed` flag. A null change set is
|
|
174
|
+
conservative and repairs both stages. `clear()` reclaims stored traces.
|
|
175
|
+
|
|
176
|
+
The public root and `/scene3d` entrypoint also re-export the canonical
|
|
177
|
+
`SelfAdjustingComputation` identity from `circuitjson-toolkit`.
|
|
178
|
+
|
|
168
179
|
### `new PcbScene3dRuntime(viewportNode, sceneDescription, hooks?)`
|
|
169
180
|
|
|
170
181
|
Creates the Three.js scene in a browser viewport.
|
|
171
182
|
|
|
183
|
+
The persistent runtime routes toggle, selection, hidden-component, and model
|
|
184
|
+
topology changes through `PcbScene3dVisibilityGraph`. Changes within mutable
|
|
185
|
+
maps and sets use explicit structural revision roots so identity-stable
|
|
186
|
+
containers cannot cause stale visibility reuse.
|
|
187
|
+
|
|
172
188
|
Hooks:
|
|
173
189
|
|
|
174
190
|
- `setDiagnostics(messages)`: receives user-facing diagnostics.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# pcb-scene3d-viewer 1.3.2
|
|
2
|
+
|
|
3
|
+
Version 1.3.2 adds persistent self-adjusting visibility propagation and fixes
|
|
4
|
+
several general copper, via, and solder-mask interactions. The changes derive
|
|
5
|
+
from scene topology and authored surface state rather than source filenames or
|
|
6
|
+
project-specific rules.
|
|
7
|
+
|
|
8
|
+
## Incremental visibility
|
|
9
|
+
|
|
10
|
+
- `PcbScene3dVisibilityGraph` applies render-group visibility and component
|
|
11
|
+
visibility as two ordered computations backed by the canonical
|
|
12
|
+
`SelfAdjustingComputation` runtime from `circuitjson-toolkit` 1.4.1.
|
|
13
|
+
- Known toggle paths repair only render-group effects. Selection,
|
|
14
|
+
hidden-component, and identity-stable map/set mutations advance an explicit
|
|
15
|
+
component revision and repair only component effects.
|
|
16
|
+
- Unknown topology changes conservatively repair both stages, and runtime
|
|
17
|
+
disposal explicitly reclaims retained traces and reverse-reader edges.
|
|
18
|
+
- The package root and `/scene3d` entrypoint export both the canonical runtime
|
|
19
|
+
identity and the visibility graph.
|
|
20
|
+
|
|
21
|
+
## Copper and mask correctness
|
|
22
|
+
|
|
23
|
+
- Tented via faces no longer carry exposed copper geometry through the rendered
|
|
24
|
+
solder-mask surface. Fully open vias keep their full span, one-sided tenting
|
|
25
|
+
retains only the exposed half, and fully tented vias retain mask treatment
|
|
26
|
+
without a visible face-spanning copper annulus.
|
|
27
|
+
- Filtered tented vias still participate in plated-hole identity checks, so a
|
|
28
|
+
matching through-hole pad cannot create a duplicate copper barrel.
|
|
29
|
+
- Copper-fill clipping with partial subdivision disabled returns as soon as an
|
|
30
|
+
uncovered sample proves that a triangle must be kept, while fully sampled
|
|
31
|
+
triangles still use exact boundary crossing checks.
|
|
32
|
+
|
|
33
|
+
## Verification
|
|
34
|
+
|
|
35
|
+
- Tests cover stage-level recomputation, conservative invalidation,
|
|
36
|
+
from-scratch visibility consistency, one- and two-sided tenting, duplicate
|
|
37
|
+
barrel prevention, and non-subdividing fill overlap behavior.
|
|
38
|
+
- Release gates include the complete package suite, exact-geometry and context
|
|
39
|
+
asset benchmarks, formatting, and npm package dry run.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pcb-scene3d-viewer",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Reusable Three.js PCB 3D scene viewer for normalized ECAD and CircuitJSON scene descriptions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pcb",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"docs/release-notes-v1.2.2.md",
|
|
39
39
|
"docs/release-notes-v1.3.0.md",
|
|
40
40
|
"docs/release-notes-v1.3.1.md",
|
|
41
|
+
"docs/release-notes-v1.3.2.md",
|
|
41
42
|
"docs/model-format.md",
|
|
42
43
|
"docs/testing.md",
|
|
43
44
|
"spec",
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
},
|
|
60
61
|
"dependencies": {
|
|
61
62
|
"@sunbox/occt-import-js": "^0.0.28",
|
|
62
|
-
"circuitjson-toolkit": "^1.
|
|
63
|
+
"circuitjson-toolkit": "^1.4.1",
|
|
63
64
|
"earcut": "3.0.2",
|
|
64
65
|
"fflate": "^0.8.2",
|
|
65
66
|
"polygon-clipping": "^0.15.7",
|
package/spec/library-scope.md
CHANGED
|
@@ -12,6 +12,8 @@ scene descriptions.
|
|
|
12
12
|
from canonical bytes, session files, or explicitly enabled URLs.
|
|
13
13
|
- Camera presets, view compensation, selection styling, picking, and visibility
|
|
14
14
|
toggles.
|
|
15
|
+
- Ordered self-adjusting render-group and per-component visibility propagation
|
|
16
|
+
backed by the canonical `circuitjson-toolkit` runtime.
|
|
15
17
|
- Optional DOM shell/controller helpers for hosts that want ready-made scene
|
|
16
18
|
chrome.
|
|
17
19
|
- ZIP export of resolved component model assets.
|
|
@@ -140,7 +140,8 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
140
140
|
|
|
141
141
|
return PcbScene3dCopperDetailFilter.#appendPadBarrelSpecs(
|
|
142
142
|
vias,
|
|
143
|
-
detail.pads
|
|
143
|
+
detail.pads,
|
|
144
|
+
detail.vias
|
|
144
145
|
)
|
|
145
146
|
}
|
|
146
147
|
|
|
@@ -322,12 +323,13 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
322
323
|
* Appends copper barrels for through-hole pads with copper annuli.
|
|
323
324
|
* @param {any[]} vias Visible via list.
|
|
324
325
|
* @param {any[] | undefined} pads Pad list.
|
|
326
|
+
* @param {any[] | undefined} sourceVias All parsed vias, including tented vias.
|
|
325
327
|
* @returns {any[]}
|
|
326
328
|
*/
|
|
327
|
-
static #appendPadBarrelSpecs(vias, pads) {
|
|
329
|
+
static #appendPadBarrelSpecs(vias, pads, sourceVias) {
|
|
328
330
|
const output = [...(vias || [])]
|
|
329
331
|
const seen = new Set(
|
|
330
|
-
output.map((via) =>
|
|
332
|
+
[...(sourceVias || []), ...output].map((via) =>
|
|
331
333
|
PcbScene3dCopperDetailFilter.#platedHoleKey(via)
|
|
332
334
|
)
|
|
333
335
|
)
|
|
@@ -127,10 +127,17 @@ export class PcbScene3dCopperFillAreaClipper {
|
|
|
127
127
|
options
|
|
128
128
|
)
|
|
129
129
|
const coverage =
|
|
130
|
-
PcbScene3dCopperFillAreaClipper.#
|
|
131
|
-
|
|
132
|
-
areas
|
|
130
|
+
PcbScene3dCopperFillAreaClipper.#shouldSubdividePartialTriangles(
|
|
131
|
+
options
|
|
133
132
|
)
|
|
133
|
+
? PcbScene3dCopperFillAreaClipper.#resolveTriangleCoverage(
|
|
134
|
+
triangle,
|
|
135
|
+
areas
|
|
136
|
+
)
|
|
137
|
+
: PcbScene3dCopperFillAreaClipper.#resolveNonSubdividingCoverage(
|
|
138
|
+
triangle,
|
|
139
|
+
areas
|
|
140
|
+
)
|
|
134
141
|
|
|
135
142
|
if (coverage === 'none') {
|
|
136
143
|
PcbScene3dCopperFillAreaClipper.#appendTriangle(positions, triangle)
|
|
@@ -194,6 +201,39 @@ export class PcbScene3dCopperFillAreaClipper {
|
|
|
194
201
|
return options?.subdividePartialTriangles !== false
|
|
195
202
|
}
|
|
196
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Resolves coverage when partial triangles are kept without subdivision.
|
|
206
|
+
* Partial and uncovered triangles have the same observable keep result in
|
|
207
|
+
* this mode, so the first uncovered sample can return without querying area
|
|
208
|
+
* boundaries. Fully sampled triangles still require the exact boundary
|
|
209
|
+
* predicate to distinguish complete coverage from a crossing.
|
|
210
|
+
* @param {{ x: number, y: number }[]} triangle Triangle points.
|
|
211
|
+
* @param {object[]} areas Filled copper areas.
|
|
212
|
+
* @returns {'none' | 'full'}
|
|
213
|
+
*/
|
|
214
|
+
static #resolveNonSubdividingCoverage(triangle, areas) {
|
|
215
|
+
const samples = [
|
|
216
|
+
triangle[0],
|
|
217
|
+
triangle[1],
|
|
218
|
+
triangle[2],
|
|
219
|
+
PcbScene3dCopperFillAreaClipper.#centroid(triangle)
|
|
220
|
+
]
|
|
221
|
+
for (const point of samples) {
|
|
222
|
+
if (
|
|
223
|
+
!PcbScene3dCopperFillAreaClipper.#isPointInAnyArea(point, areas)
|
|
224
|
+
) {
|
|
225
|
+
return 'none'
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return PcbScene3dCopperFillAreaClipper.#triangleCrossesAnyBoundary(
|
|
230
|
+
triangle,
|
|
231
|
+
areas
|
|
232
|
+
)
|
|
233
|
+
? 'none'
|
|
234
|
+
: 'full'
|
|
235
|
+
}
|
|
236
|
+
|
|
197
237
|
/**
|
|
198
238
|
* Resolves whether a triangle is outside, inside, or crossing filled areas.
|
|
199
239
|
* @param {{ x: number, y: number }[]} triangle Triangle points.
|
|
@@ -18,7 +18,6 @@ import { PcbScene3dFallbackVisibility } from './PcbScene3dFallbackVisibility.mjs
|
|
|
18
18
|
import { PcbScene3dInteractionHints } from './PcbScene3dInteractionHints.mjs'
|
|
19
19
|
import { PcbScene3dModelSearchPlacement } from './PcbScene3dModelSearchPlacement.mjs'
|
|
20
20
|
import { PcbScene3dPresetState } from './PcbScene3dPresetState.mjs'
|
|
21
|
-
import { PcbScene3dRenderGroupVisibility } from './PcbScene3dRenderGroupVisibility.mjs'
|
|
22
21
|
import { PcbScene3dRenderScheduler } from './PcbScene3dRenderScheduler.mjs'
|
|
23
22
|
import { PcbScene3dRuntimeBoardMeshes } from './PcbScene3dRuntimeBoardMeshes.mjs'
|
|
24
23
|
import { PcbScene3dRuntimeHelpers } from './PcbScene3dRuntimeHelpers.mjs'
|
|
@@ -29,6 +28,7 @@ import { PcbScene3dSelectionStyler } from './PcbScene3dSelectionStyler.mjs'
|
|
|
29
28
|
import { PcbScene3dStaticBodyFactory } from './PcbScene3dStaticBodyFactory.mjs'
|
|
30
29
|
import { PcbScene3dViewportResize } from './PcbScene3dViewportResize.mjs'
|
|
31
30
|
import { PcbScene3dViewScale } from './PcbScene3dViewScale.mjs'
|
|
31
|
+
import { PcbScene3dVisibilityGraph } from './PcbScene3dVisibilityGraph.mjs'
|
|
32
32
|
const COPPER_HALF_MIL = PcbScene3dCopperFactory.visualHalfThicknessMil()
|
|
33
33
|
const Z_MIL = {
|
|
34
34
|
paste: COPPER_HALF_MIL + 0.28,
|
|
@@ -74,6 +74,7 @@ export class PcbScene3dRuntime {
|
|
|
74
74
|
#resolveReadyPromise
|
|
75
75
|
#hasSettledReady
|
|
76
76
|
#renderScheduler
|
|
77
|
+
#visibilityGraph
|
|
77
78
|
/**
|
|
78
79
|
* @param {HTMLElement} viewportNode
|
|
79
80
|
* @param {any} sceneDescription Scene description or CircuitJSON model.
|
|
@@ -127,6 +128,7 @@ export class PcbScene3dRuntime {
|
|
|
127
128
|
this.#render()
|
|
128
129
|
}
|
|
129
130
|
})
|
|
131
|
+
this.#visibilityGraph = new PcbScene3dVisibilityGraph()
|
|
130
132
|
this.#resolveReadyPromise = null
|
|
131
133
|
this.#readyPromise = new Promise((resolve) => {
|
|
132
134
|
this.#resolveReadyPromise = resolve
|
|
@@ -165,7 +167,7 @@ export class PcbScene3dRuntime {
|
|
|
165
167
|
return
|
|
166
168
|
}
|
|
167
169
|
this.#toggles[toggleName] = enabled
|
|
168
|
-
this.#applyToggleVisibility()
|
|
170
|
+
this.#applyToggleVisibility([['toggles', toggleName]])
|
|
169
171
|
this.#render()
|
|
170
172
|
}
|
|
171
173
|
/** @param {string} designator */
|
|
@@ -183,7 +185,7 @@ export class PcbScene3dRuntime {
|
|
|
183
185
|
) {
|
|
184
186
|
return
|
|
185
187
|
}
|
|
186
|
-
this.#applyToggleVisibility()
|
|
188
|
+
this.#applyToggleVisibility([['componentRevision']])
|
|
187
189
|
this.#updateSelectionMarker()
|
|
188
190
|
this.#render()
|
|
189
191
|
}
|
|
@@ -243,6 +245,7 @@ export class PcbScene3dRuntime {
|
|
|
243
245
|
this.#selectionRoots.clear()
|
|
244
246
|
this.#hiddenComponentDesignators.clear()
|
|
245
247
|
this.#componentAdjustmentRegistry.clear()
|
|
248
|
+
this.#visibilityGraph.clear()
|
|
246
249
|
this.#fallbackBodyRoots.clear()
|
|
247
250
|
this.#loadedExternalModelDesignators.clear()
|
|
248
251
|
this.#modelSearchExternalModelRoots.clear()
|
|
@@ -771,28 +774,24 @@ export class PcbScene3dRuntime {
|
|
|
771
774
|
this.#render()
|
|
772
775
|
}
|
|
773
776
|
|
|
774
|
-
/** @returns {void} */
|
|
775
|
-
#applyToggleVisibility() {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
modelSearchExternalModelRoots: this.#modelSearchExternalModelRoots,
|
|
793
|
-
toggles: this.#toggles,
|
|
794
|
-
hasLoadedBoardAssemblyModel: this.#hasLoadedBoardAssemblyModel
|
|
795
|
-
})
|
|
777
|
+
/** @param {PropertyKey[][] | null} [changedPaths] @returns {void} */
|
|
778
|
+
#applyToggleVisibility(changedPaths = null) {
|
|
779
|
+
this.#visibilityGraph.apply(
|
|
780
|
+
{
|
|
781
|
+
groups: this.#groups,
|
|
782
|
+
toggles: this.#toggles,
|
|
783
|
+
fallbackBodyRoots: this.#fallbackBodyRoots,
|
|
784
|
+
loadedExternalModelDesignators:
|
|
785
|
+
this.#loadedExternalModelDesignators,
|
|
786
|
+
modelSearchExternalModelRoots:
|
|
787
|
+
this.#modelSearchExternalModelRoots,
|
|
788
|
+
hasLoadedBoardAssemblyModel: this.#hasLoadedBoardAssemblyModel,
|
|
789
|
+
selectionRoots: this.#selectionRoots,
|
|
790
|
+
selectedDesignator: this.#selectedDesignator,
|
|
791
|
+
hiddenDesignators: this.#hiddenComponentDesignators
|
|
792
|
+
},
|
|
793
|
+
changedPaths
|
|
794
|
+
)
|
|
796
795
|
}
|
|
797
796
|
|
|
798
797
|
/**
|
|
@@ -949,7 +948,7 @@ export class PcbScene3dRuntime {
|
|
|
949
948
|
SELECTION_HIGHLIGHT_COLOR
|
|
950
949
|
)
|
|
951
950
|
this.#selectedDesignator = normalizedDesignator
|
|
952
|
-
this.#applyToggleVisibility()
|
|
951
|
+
this.#applyToggleVisibility([['componentRevision']])
|
|
953
952
|
this.#updateSelectionMarker()
|
|
954
953
|
this.#render()
|
|
955
954
|
}
|
|
@@ -39,27 +39,30 @@ export class PcbScene3dViaFactory {
|
|
|
39
39
|
const renderMode = PcbScene3dViaLayerSpan.renderMode(via)
|
|
40
40
|
if (!renderMode) return
|
|
41
41
|
|
|
42
|
-
const geometry = PcbScene3dViaFactory.#resolveGeometry(
|
|
43
|
-
THREE,
|
|
44
|
-
geometryCache,
|
|
45
|
-
via,
|
|
46
|
-
thicknessMil,
|
|
47
|
-
renderMode
|
|
48
|
-
)
|
|
49
|
-
const mesh = new THREE.Mesh(geometry, copperMaterial)
|
|
50
42
|
const point = normalizeBoardPoint(
|
|
51
43
|
Number(via?.x || 0),
|
|
52
44
|
Number(via?.y || 0)
|
|
53
45
|
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
const copperSpans = PcbScene3dViaFactory.#resolveCopperSpans(
|
|
47
|
+
via,
|
|
48
|
+
renderMode,
|
|
49
|
+
thicknessMil,
|
|
50
|
+
Boolean(options?.surfaceMaterial)
|
|
58
51
|
)
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
for (const copperSpan of copperSpans) {
|
|
53
|
+
const geometry = PcbScene3dViaFactory.#resolveGeometry(
|
|
54
|
+
THREE,
|
|
55
|
+
geometryCache,
|
|
56
|
+
via,
|
|
57
|
+
copperSpan.depth
|
|
58
|
+
)
|
|
59
|
+
const mesh = new THREE.Mesh(geometry, copperMaterial)
|
|
60
|
+
mesh.position.set(point.x, point.y, copperSpan.centerZ)
|
|
61
|
+
if (geometry.type === 'CylinderGeometry') {
|
|
62
|
+
mesh.rotation.x = Math.PI / 2
|
|
63
|
+
}
|
|
64
|
+
group.add(mesh)
|
|
61
65
|
}
|
|
62
|
-
group.add(mesh)
|
|
63
66
|
PcbScene3dViaFactory.#appendMaskSurfaceMeshes(
|
|
64
67
|
THREE,
|
|
65
68
|
group,
|
|
@@ -75,6 +78,57 @@ export class PcbScene3dViaFactory {
|
|
|
75
78
|
return group
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Resolves visible copper spans without carrying an open-side barrel
|
|
83
|
+
* through a solder-mask-covered board face.
|
|
84
|
+
* @param {object} via Via primitive.
|
|
85
|
+
* @param {'through' | 'top' | 'bottom'} renderMode Via geometry mode.
|
|
86
|
+
* @param {number} thicknessMil Board thickness in mil.
|
|
87
|
+
* @param {boolean} hasSurfaceMask Whether mask surface geometry is rendered.
|
|
88
|
+
* @returns {{ depth: number, centerZ: number }[]}
|
|
89
|
+
*/
|
|
90
|
+
static #resolveCopperSpans(via, renderMode, thicknessMil, hasSurfaceMask) {
|
|
91
|
+
const fullSpan = {
|
|
92
|
+
depth: PcbScene3dViaFactory.#geometryDepth(
|
|
93
|
+
renderMode,
|
|
94
|
+
thicknessMil
|
|
95
|
+
),
|
|
96
|
+
centerZ: PcbScene3dViaFactory.#centerZ(renderMode, thicknessMil)
|
|
97
|
+
}
|
|
98
|
+
if (!hasSurfaceMask) {
|
|
99
|
+
return [fullSpan]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (renderMode === 'top') {
|
|
103
|
+
return PcbScene3dViaFactory.#isSideTented(via, 'top')
|
|
104
|
+
? []
|
|
105
|
+
: [fullSpan]
|
|
106
|
+
}
|
|
107
|
+
if (renderMode === 'bottom') {
|
|
108
|
+
return PcbScene3dViaFactory.#isSideTented(via, 'bottom')
|
|
109
|
+
? []
|
|
110
|
+
: [fullSpan]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const isTopTented = PcbScene3dViaFactory.#isSideTented(via, 'top')
|
|
114
|
+
const isBottomTented = PcbScene3dViaFactory.#isSideTented(via, 'bottom')
|
|
115
|
+
if (isTopTented && isBottomTented) {
|
|
116
|
+
return []
|
|
117
|
+
}
|
|
118
|
+
if (!isTopTented && !isBottomTented) {
|
|
119
|
+
return [fullSpan]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const halfDepth = fullSpan.depth / 2
|
|
123
|
+
const centerDistance = halfDepth / 2
|
|
124
|
+
return [
|
|
125
|
+
{
|
|
126
|
+
depth: halfDepth,
|
|
127
|
+
centerZ: isTopTented ? -centerDistance : centerDistance
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
|
|
78
132
|
/**
|
|
79
133
|
* Resolves the via material.
|
|
80
134
|
* @param {any} THREE
|
|
@@ -222,23 +276,12 @@ export class PcbScene3dViaFactory {
|
|
|
222
276
|
* @param {any} THREE
|
|
223
277
|
* @param {Map<string, any>} geometryCache
|
|
224
278
|
* @param {{ diameter?: number, holeDiameter?: number, barrelOnly?: boolean }} via
|
|
225
|
-
* @param {number}
|
|
226
|
-
* @param {'through' | 'top' | 'bottom'} renderMode Via geometry mode.
|
|
279
|
+
* @param {number} depth Copper span depth in mil.
|
|
227
280
|
* @returns {any}
|
|
228
281
|
*/
|
|
229
|
-
static #resolveGeometry(
|
|
230
|
-
THREE,
|
|
231
|
-
geometryCache,
|
|
232
|
-
via,
|
|
233
|
-
thicknessMil,
|
|
234
|
-
renderMode
|
|
235
|
-
) {
|
|
282
|
+
static #resolveGeometry(THREE, geometryCache, via, depth) {
|
|
236
283
|
const outerRadius = Math.max(Number(via?.diameter || 0) / 2, 1.2)
|
|
237
284
|
const holeDiameter = Math.max(Number(via?.holeDiameter || 0), 0)
|
|
238
|
-
const depth = PcbScene3dViaFactory.#geometryDepth(
|
|
239
|
-
renderMode,
|
|
240
|
-
thicknessMil
|
|
241
|
-
)
|
|
242
285
|
const isBarrelOnly = Boolean(via?.barrelOnly)
|
|
243
286
|
const cacheKey = [
|
|
244
287
|
isBarrelOnly ? 'barrel' : 'annulus',
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { SelfAdjustingComputation } from 'circuitjson-toolkit'
|
|
2
|
+
import { PcbScene3dComponentVisibility } from './PcbScene3dComponentVisibility.mjs'
|
|
3
|
+
import { PcbScene3dRenderGroupVisibility } from './PcbScene3dRenderGroupVisibility.mjs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Repairs ordered PCB visibility effects from explicit mutable-state changes.
|
|
7
|
+
*/
|
|
8
|
+
export class PcbScene3dVisibilityGraph {
|
|
9
|
+
/** @type {SelfAdjustingComputation} */
|
|
10
|
+
#computation
|
|
11
|
+
|
|
12
|
+
/** @type {number} */
|
|
13
|
+
#groupRevision
|
|
14
|
+
|
|
15
|
+
/** @type {number} */
|
|
16
|
+
#componentRevision
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates an empty visibility trace graph.
|
|
20
|
+
*/
|
|
21
|
+
constructor() {
|
|
22
|
+
this.#computation = new SelfAdjustingComputation()
|
|
23
|
+
this.#groupRevision = 0
|
|
24
|
+
this.#componentRevision = 0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Applies affected visibility computations in observable effect order.
|
|
29
|
+
* Revision paths represent mutations inside otherwise atomic maps and sets.
|
|
30
|
+
* A null change set conservatively advances both structural revisions.
|
|
31
|
+
* @param {object} state Current visibility state.
|
|
32
|
+
* @param {PropertyKey[][] | null} changedPaths Changed roots or null when unknown.
|
|
33
|
+
* @returns {Map<string, { value: any, recomputed: boolean }>} Stage results.
|
|
34
|
+
*/
|
|
35
|
+
apply(state, changedPaths = null) {
|
|
36
|
+
const normalizedPaths = this.#advanceRevisions(changedPaths)
|
|
37
|
+
const input = {
|
|
38
|
+
...state,
|
|
39
|
+
groupRevision: this.#groupRevision,
|
|
40
|
+
componentRevision: this.#componentRevision
|
|
41
|
+
}
|
|
42
|
+
return this.#computation.propagate(input, normalizedPaths, [
|
|
43
|
+
{
|
|
44
|
+
name: 'render-groups',
|
|
45
|
+
computation: (current) => {
|
|
46
|
+
void current.groupRevision
|
|
47
|
+
PcbScene3dRenderGroupVisibility.apply(current)
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'components',
|
|
52
|
+
computation: (current) => {
|
|
53
|
+
void current.componentRevision
|
|
54
|
+
PcbScene3dComponentVisibility.apply(current)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Removes all stored visibility traces and reverse-reader edges.
|
|
62
|
+
* @returns {void}
|
|
63
|
+
*/
|
|
64
|
+
clear() {
|
|
65
|
+
this.#computation.clear()
|
|
66
|
+
this.#groupRevision = 0
|
|
67
|
+
this.#componentRevision = 0
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Advances revision roots named by a visibility change set.
|
|
72
|
+
* @param {PropertyKey[][] | null} changedPaths Changed roots or null.
|
|
73
|
+
* @returns {PropertyKey[][]} Conservative normalized change paths.
|
|
74
|
+
*/
|
|
75
|
+
#advanceRevisions(changedPaths) {
|
|
76
|
+
const normalizedPaths =
|
|
77
|
+
changedPaths === null
|
|
78
|
+
? [['groupRevision'], ['componentRevision']]
|
|
79
|
+
: changedPaths
|
|
80
|
+
if (!Array.isArray(normalizedPaths)) {
|
|
81
|
+
throw new TypeError(
|
|
82
|
+
'Visibility change sets must be arrays of property paths.'
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
const revisionRoots = new Set()
|
|
86
|
+
normalizedPaths.forEach((path) => {
|
|
87
|
+
if (!Array.isArray(path)) {
|
|
88
|
+
throw new TypeError(
|
|
89
|
+
'Visibility change-set entries must be property paths.'
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
revisionRoots.add(path[0])
|
|
93
|
+
})
|
|
94
|
+
if (revisionRoots.has('groupRevision')) {
|
|
95
|
+
this.#groupRevision += 1
|
|
96
|
+
}
|
|
97
|
+
if (revisionRoots.has('componentRevision')) {
|
|
98
|
+
this.#componentRevision += 1
|
|
99
|
+
}
|
|
100
|
+
return normalizedPaths
|
|
101
|
+
}
|
|
102
|
+
}
|
package/src/scene3d.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { SelfAdjustingComputation } from 'circuitjson-toolkit'
|
|
1
2
|
export { PcbModelArchiveExporter } from './PcbModelArchiveExporter.mjs'
|
|
2
3
|
export { PcbAssemblyBoardSubstrateBuilder } from './PcbAssemblyBoardSubstrateBuilder.mjs'
|
|
3
4
|
export { PcbAssemblyExportCoordinateFrame } from './PcbAssemblyExportCoordinateFrame.mjs'
|
|
@@ -57,4 +58,5 @@ export { PcbScene3dViaFactory } from './PcbScene3dViaFactory.mjs'
|
|
|
57
58
|
export { PcbScene3dViewCompensation } from './PcbScene3dViewCompensation.mjs'
|
|
58
59
|
export { PcbScene3dViewScale } from './PcbScene3dViewScale.mjs'
|
|
59
60
|
export { PcbScene3dViewportResize } from './PcbScene3dViewportResize.mjs'
|
|
61
|
+
export { PcbScene3dVisibilityGraph } from './PcbScene3dVisibilityGraph.mjs'
|
|
60
62
|
export { PcbScene3dWorkerClient } from './PcbScene3dWorkerClient.mjs'
|