pcb-scene3d-viewer 1.3.0 → 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 +16 -0
- package/docs/api.md +16 -0
- package/docs/model-format.md +6 -0
- package/docs/release-notes-v1.3.1.md +23 -0
- package/docs/release-notes-v1.3.2.md +39 -0
- package/package.json +4 -2
- package/spec/library-scope.md +2 -0
- package/src/PcbScene3dCopperDetailFilter.mjs +44 -7
- package/src/PcbScene3dCopperDetailGroupBuilder.mjs +1 -1
- package/src/PcbScene3dCopperFillAreaClipper.mjs +43 -3
- package/src/PcbScene3dRuntime.mjs +25 -26
- package/src/PcbScene3dViaFactory.mjs +211 -29
- package/src/PcbScene3dVisibilityGraph.mjs +102 -0
- package/src/scene3d.mjs +2 -0
package/README.md
CHANGED
|
@@ -28,6 +28,20 @@ surface-aware blind/buried vias, collision-safe model assets, and a consistent
|
|
|
28
28
|
light FR-4 substrate edge. See the
|
|
29
29
|
[1.3.0 release notes](docs/release-notes-v1.3.0.md).
|
|
30
30
|
|
|
31
|
+
Version 1.3.1 renders side-specific tented Gerber vias without recoloring the
|
|
32
|
+
plated drill wall. Covered surfaces receive a solder-mask ring above the copper
|
|
33
|
+
annulus, mixed top/bottom states remain independent, and fully open Gerber vias
|
|
34
|
+
retain the exposed-copper path. See the
|
|
35
|
+
[1.3.1 release notes](docs/release-notes-v1.3.1.md).
|
|
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
|
+
|
|
31
45
|
## CircuitJSON 1.1 convergence
|
|
32
46
|
|
|
33
47
|
Version 1.2.2 accepts the common document and prepared-context shapes returned
|
|
@@ -136,6 +150,8 @@ const controller = new PcbScene3dController(viewportNode, document)
|
|
|
136
150
|
|
|
137
151
|
- [API](docs/api.md)
|
|
138
152
|
- [CircuitJSON usage](docs/circuitjson.md)
|
|
153
|
+
- [1.3.2 release notes](docs/release-notes-v1.3.2.md)
|
|
154
|
+
- [1.3.1 release notes](docs/release-notes-v1.3.1.md)
|
|
139
155
|
- [1.2.2 release notes](docs/release-notes-v1.2.2.md)
|
|
140
156
|
- [1.3.0 release notes](docs/release-notes-v1.3.0.md)
|
|
141
157
|
- [1.2.1 release notes](docs/release-notes-v1.2.1.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.
|
package/docs/model-format.md
CHANGED
|
@@ -220,3 +220,9 @@ Rounded SMT pads set `hasRoundedRect`, the side-specific
|
|
|
220
220
|
Route-derived vias use the same `detail.vias` shape as standalone vias, and
|
|
221
221
|
surface route segments use `layerId: 1` for top copper or `layerId: 32` for
|
|
222
222
|
bottom copper.
|
|
223
|
+
|
|
224
|
+
Via surfaces can set `isTentingTop` and `isTentingBottom` independently. A
|
|
225
|
+
truthy field adds a solder-mask ring on that board surface while the plated
|
|
226
|
+
through-hole barrel remains copper. A mixed via therefore renders one covered
|
|
227
|
+
annulus and one exposed annulus; a via with both fields explicitly false stays
|
|
228
|
+
on the exposed-copper path. The source toolkit owns this classification.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# pcb-scene3d-viewer 1.3.1
|
|
2
|
+
|
|
3
|
+
Version 1.3.1 renders source-classified via solder mask as a surface treatment
|
|
4
|
+
without replacing the plated copper barrel material.
|
|
5
|
+
|
|
6
|
+
## Via rendering
|
|
7
|
+
|
|
8
|
+
- Gerber vias with either `isTentingTop` or `isTentingBottom` use the covered
|
|
9
|
+
via rendering path; both fields explicitly false retain the exposed path.
|
|
10
|
+
- Each tented board surface receives its own solder-mask ring above the copper
|
|
11
|
+
annulus. Mixed top/bottom tenting is preserved.
|
|
12
|
+
- The mask ring keeps the authored drill opening clear and leaves the plated
|
|
13
|
+
through-hole wall copper-colored.
|
|
14
|
+
- Blind and buried surface reachability remains authoritative, so mask geometry
|
|
15
|
+
is added only where a via actually reaches the corresponding board surface.
|
|
16
|
+
|
|
17
|
+
## Compatibility and verification
|
|
18
|
+
|
|
19
|
+
- Existing CircuitJSON default-tenting and explicit-opening behavior is
|
|
20
|
+
unchanged.
|
|
21
|
+
- Scene, controller, runtime, export, and package entrypoints are unchanged.
|
|
22
|
+
- Tests cover fully tented, mixed, and fully open Gerber classification plus
|
|
23
|
+
copper-barrel and one-sided mask-ring material routing.
|
|
@@ -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",
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
"docs/release-notes-v1.2.1.md",
|
|
38
38
|
"docs/release-notes-v1.2.2.md",
|
|
39
39
|
"docs/release-notes-v1.3.0.md",
|
|
40
|
+
"docs/release-notes-v1.3.1.md",
|
|
41
|
+
"docs/release-notes-v1.3.2.md",
|
|
40
42
|
"docs/model-format.md",
|
|
41
43
|
"docs/testing.md",
|
|
42
44
|
"spec",
|
|
@@ -58,7 +60,7 @@
|
|
|
58
60
|
},
|
|
59
61
|
"dependencies": {
|
|
60
62
|
"@sunbox/occt-import-js": "^0.0.28",
|
|
61
|
-
"circuitjson-toolkit": "^1.
|
|
63
|
+
"circuitjson-toolkit": "^1.4.1",
|
|
62
64
|
"earcut": "3.0.2",
|
|
63
65
|
"fflate": "^0.8.2",
|
|
64
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
|
|
|
@@ -266,24 +267,37 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
266
267
|
* @returns {any[]}
|
|
267
268
|
*/
|
|
268
269
|
static #filterExposedVias(vias, sceneDescription) {
|
|
270
|
+
if (PcbScene3dCopperDetailFilter.#isGerberScene(sceneDescription)) {
|
|
271
|
+
return (vias || []).filter(
|
|
272
|
+
(via) =>
|
|
273
|
+
via?.isTentingTop === false &&
|
|
274
|
+
via?.isTentingBottom === false
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
269
278
|
return (vias || []).filter((via) =>
|
|
270
279
|
PcbScene3dCopperDetailFilter.#isViaExplicitlyOpen(via)
|
|
271
280
|
)
|
|
272
281
|
}
|
|
273
282
|
|
|
274
283
|
/**
|
|
275
|
-
* Keeps KiCad via annuli that are covered by solder mask.
|
|
284
|
+
* Keeps KiCad and Gerber via annuli that are covered by solder mask.
|
|
276
285
|
* @param {any[] | undefined} vias Via list.
|
|
277
286
|
* @param {object} sceneDescription 3D scene description.
|
|
278
287
|
* @returns {any[]}
|
|
279
288
|
*/
|
|
280
289
|
static #filterMaskCoveredVias(vias, sceneDescription) {
|
|
281
|
-
if (
|
|
290
|
+
if (
|
|
291
|
+
!PcbScene3dCopperDetailFilter.#isKiCadScene(sceneDescription) &&
|
|
292
|
+
!PcbScene3dCopperDetailFilter.#isGerberScene(sceneDescription)
|
|
293
|
+
) {
|
|
282
294
|
return []
|
|
283
295
|
}
|
|
284
296
|
|
|
285
|
-
return (vias || []).filter(
|
|
286
|
-
|
|
297
|
+
return (vias || []).filter((via) =>
|
|
298
|
+
PcbScene3dCopperDetailFilter.#isGerberScene(sceneDescription)
|
|
299
|
+
? PcbScene3dCopperDetailFilter.#isViaExplicitlyTented(via)
|
|
300
|
+
: !PcbScene3dCopperDetailFilter.#isViaExplicitlyOpen(via)
|
|
287
301
|
)
|
|
288
302
|
}
|
|
289
303
|
|
|
@@ -296,16 +310,26 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
296
310
|
return via?.isTentingTop === false || via?.isTentingBottom === false
|
|
297
311
|
}
|
|
298
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Checks whether one via explicitly carries mask on either board side.
|
|
315
|
+
* @param {object} via Via primitive.
|
|
316
|
+
* @returns {boolean}
|
|
317
|
+
*/
|
|
318
|
+
static #isViaExplicitlyTented(via) {
|
|
319
|
+
return via?.isTentingTop === true || via?.isTentingBottom === true
|
|
320
|
+
}
|
|
321
|
+
|
|
299
322
|
/**
|
|
300
323
|
* Appends copper barrels for through-hole pads with copper annuli.
|
|
301
324
|
* @param {any[]} vias Visible via list.
|
|
302
325
|
* @param {any[] | undefined} pads Pad list.
|
|
326
|
+
* @param {any[] | undefined} sourceVias All parsed vias, including tented vias.
|
|
303
327
|
* @returns {any[]}
|
|
304
328
|
*/
|
|
305
|
-
static #appendPadBarrelSpecs(vias, pads) {
|
|
329
|
+
static #appendPadBarrelSpecs(vias, pads, sourceVias) {
|
|
306
330
|
const output = [...(vias || [])]
|
|
307
331
|
const seen = new Set(
|
|
308
|
-
output.map((via) =>
|
|
332
|
+
[...(sourceVias || []), ...output].map((via) =>
|
|
309
333
|
PcbScene3dCopperDetailFilter.#platedHoleKey(via)
|
|
310
334
|
)
|
|
311
335
|
)
|
|
@@ -487,6 +511,19 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
487
511
|
)
|
|
488
512
|
}
|
|
489
513
|
|
|
514
|
+
/**
|
|
515
|
+
* Checks whether one scene was built from Gerber fabrication artwork.
|
|
516
|
+
* @param {object} sceneDescription Scene description.
|
|
517
|
+
* @returns {boolean}
|
|
518
|
+
*/
|
|
519
|
+
static #isGerberScene(sceneDescription) {
|
|
520
|
+
return (
|
|
521
|
+
String(sceneDescription?.sourceFormat || '')
|
|
522
|
+
.trim()
|
|
523
|
+
.toLowerCase() === 'gerber'
|
|
524
|
+
)
|
|
525
|
+
}
|
|
526
|
+
|
|
490
527
|
/**
|
|
491
528
|
* Checks whether one text primitive belongs to a solder-mask layer.
|
|
492
529
|
* @param {object} text Text primitive.
|
|
@@ -130,7 +130,7 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
130
130
|
sceneDescription?.board?.thicknessMil,
|
|
131
131
|
normalizePoint,
|
|
132
132
|
{
|
|
133
|
-
|
|
133
|
+
surfaceMaterial: PcbScene3dMaskCoveredCopperMaterial.build(
|
|
134
134
|
THREE,
|
|
135
135
|
PcbScene3dCopperDetailGroupBuilder.#coveredCopperMaterialOptions(
|
|
136
136
|
sceneDescription
|
|
@@ -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
|
}
|
|
@@ -9,6 +9,7 @@ export class PcbScene3dViaFactory {
|
|
|
9
9
|
static #PAD_BARREL_MIN_WALL_MIL = 1.2
|
|
10
10
|
static #PAD_BARREL_WALL_FRACTION = 0.09
|
|
11
11
|
static #SURFACE_COPPER_DEPTH_MIL = 2
|
|
12
|
+
static #SURFACE_MASK_Z_OFFSET_MIL = 1.3
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Builds the via mesh group for one scene.
|
|
@@ -16,7 +17,7 @@ export class PcbScene3dViaFactory {
|
|
|
16
17
|
* @param {{ diameter?: number, holeDiameter?: number, x?: number, y?: number, barrelOnly?: boolean, layers?: unknown[], fromLayer?: unknown, toLayer?: unknown, from_layer?: unknown, to_layer?: unknown }[]} vias
|
|
17
18
|
* @param {number} thicknessMil
|
|
18
19
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
19
|
-
* @param {{ material?: any }} [options]
|
|
20
|
+
* @param {{ material?: any, surfaceMaterial?: any }} [options]
|
|
20
21
|
* @returns {any}
|
|
21
22
|
*/
|
|
22
23
|
static buildGroup(
|
|
@@ -27,39 +28,107 @@ export class PcbScene3dViaFactory {
|
|
|
27
28
|
options = {}
|
|
28
29
|
) {
|
|
29
30
|
const group = new THREE.Group()
|
|
30
|
-
const
|
|
31
|
+
const copperMaterial = PcbScene3dViaFactory.#resolveMaterial(
|
|
32
|
+
THREE,
|
|
33
|
+
options
|
|
34
|
+
)
|
|
31
35
|
const geometryCache = new Map()
|
|
36
|
+
const surfaceGeometryCache = new Map()
|
|
32
37
|
|
|
33
38
|
;(vias || []).forEach((via) => {
|
|
34
39
|
const renderMode = PcbScene3dViaLayerSpan.renderMode(via)
|
|
35
40
|
if (!renderMode) return
|
|
36
41
|
|
|
37
|
-
const geometry = PcbScene3dViaFactory.#resolveGeometry(
|
|
38
|
-
THREE,
|
|
39
|
-
geometryCache,
|
|
40
|
-
via,
|
|
41
|
-
thicknessMil,
|
|
42
|
-
renderMode
|
|
43
|
-
)
|
|
44
|
-
const mesh = new THREE.Mesh(geometry, material)
|
|
45
42
|
const point = normalizeBoardPoint(
|
|
46
43
|
Number(via?.x || 0),
|
|
47
44
|
Number(via?.y || 0)
|
|
48
45
|
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
const copperSpans = PcbScene3dViaFactory.#resolveCopperSpans(
|
|
47
|
+
via,
|
|
48
|
+
renderMode,
|
|
49
|
+
thicknessMil,
|
|
50
|
+
Boolean(options?.surfaceMaterial)
|
|
53
51
|
)
|
|
54
|
-
|
|
55
|
-
|
|
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)
|
|
56
65
|
}
|
|
57
|
-
|
|
66
|
+
PcbScene3dViaFactory.#appendMaskSurfaceMeshes(
|
|
67
|
+
THREE,
|
|
68
|
+
group,
|
|
69
|
+
surfaceGeometryCache,
|
|
70
|
+
via,
|
|
71
|
+
renderMode,
|
|
72
|
+
thicknessMil,
|
|
73
|
+
point,
|
|
74
|
+
options?.surfaceMaterial
|
|
75
|
+
)
|
|
58
76
|
})
|
|
59
77
|
|
|
60
78
|
return group
|
|
61
79
|
}
|
|
62
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
|
+
|
|
63
132
|
/**
|
|
64
133
|
* Resolves the via material.
|
|
65
134
|
* @param {any} THREE
|
|
@@ -79,27 +148,140 @@ export class PcbScene3dViaFactory {
|
|
|
79
148
|
}
|
|
80
149
|
|
|
81
150
|
/**
|
|
82
|
-
*
|
|
83
|
-
* @param {any} THREE
|
|
84
|
-
* @param {
|
|
85
|
-
* @param {
|
|
86
|
-
* @param {
|
|
151
|
+
* Adds side-specific solder-mask rings above the copper via surface.
|
|
152
|
+
* @param {any} THREE Three.js namespace.
|
|
153
|
+
* @param {any} group Output group.
|
|
154
|
+
* @param {Map<string, any>} geometryCache Surface geometry cache.
|
|
155
|
+
* @param {object} via Via primitive.
|
|
87
156
|
* @param {'through' | 'top' | 'bottom'} renderMode Via geometry mode.
|
|
88
|
-
* @
|
|
157
|
+
* @param {number} thicknessMil Board thickness in mil.
|
|
158
|
+
* @param {{ x: number, y: number }} point Normalized board point.
|
|
159
|
+
* @param {any | undefined} material Solder-mask material.
|
|
160
|
+
* @returns {void}
|
|
89
161
|
*/
|
|
90
|
-
static #
|
|
162
|
+
static #appendMaskSurfaceMeshes(
|
|
91
163
|
THREE,
|
|
164
|
+
group,
|
|
92
165
|
geometryCache,
|
|
93
166
|
via,
|
|
167
|
+
renderMode,
|
|
94
168
|
thicknessMil,
|
|
95
|
-
|
|
169
|
+
point,
|
|
170
|
+
material
|
|
96
171
|
) {
|
|
172
|
+
if (!material) {
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const geometry = PcbScene3dViaFactory.#resolveSurfaceGeometry(
|
|
177
|
+
THREE,
|
|
178
|
+
geometryCache,
|
|
179
|
+
via
|
|
180
|
+
)
|
|
181
|
+
if (!geometry) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const side of ['top', 'bottom']) {
|
|
186
|
+
if (
|
|
187
|
+
!PcbScene3dViaFactory.#renderModeTouchesSide(
|
|
188
|
+
renderMode,
|
|
189
|
+
side
|
|
190
|
+
) ||
|
|
191
|
+
!PcbScene3dViaFactory.#isSideTented(via, side)
|
|
192
|
+
) {
|
|
193
|
+
continue
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const mesh = new THREE.Mesh(geometry, material)
|
|
197
|
+
mesh.position.set(
|
|
198
|
+
point.x,
|
|
199
|
+
point.y,
|
|
200
|
+
PcbScene3dViaFactory.#surfaceZ(side, thicknessMil)
|
|
201
|
+
)
|
|
202
|
+
group.add(mesh)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Resolves one reusable annular surface geometry.
|
|
208
|
+
* @param {any} THREE Three.js namespace.
|
|
209
|
+
* @param {Map<string, any>} geometryCache Surface geometry cache.
|
|
210
|
+
* @param {object} via Via primitive.
|
|
211
|
+
* @returns {any | null}
|
|
212
|
+
*/
|
|
213
|
+
static #resolveSurfaceGeometry(THREE, geometryCache, via) {
|
|
214
|
+
const diameter = Number(via?.diameter || 0)
|
|
215
|
+
const holeDiameter = Number(via?.holeDiameter || 0)
|
|
216
|
+
if (diameter <= 0 || holeDiameter < 0 || diameter <= holeDiameter) {
|
|
217
|
+
return null
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const key = `${diameter.toFixed(4)}:${holeDiameter.toFixed(4)}`
|
|
221
|
+
const cached = geometryCache.get(key)
|
|
222
|
+
if (cached) {
|
|
223
|
+
return cached
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const shape = PcbScene3dViaFactory.#buildCircleShape(
|
|
227
|
+
THREE,
|
|
228
|
+
diameter / 2
|
|
229
|
+
)
|
|
230
|
+
if (holeDiameter > 0) {
|
|
231
|
+
shape.holes.push(
|
|
232
|
+
PcbScene3dViaFactory.#buildCirclePath(THREE, holeDiameter / 2)
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
const geometry = new THREE.ShapeGeometry(shape, 24)
|
|
236
|
+
geometryCache.set(key, geometry)
|
|
237
|
+
return geometry
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Checks whether one rendered via span reaches a board side.
|
|
242
|
+
* @param {'through' | 'top' | 'bottom'} renderMode Via geometry mode.
|
|
243
|
+
* @param {'top' | 'bottom'} side Board side.
|
|
244
|
+
* @returns {boolean}
|
|
245
|
+
*/
|
|
246
|
+
static #renderModeTouchesSide(renderMode, side) {
|
|
247
|
+
return renderMode === 'through' || renderMode === side
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Checks whether one via surface is tented on a board side.
|
|
252
|
+
* @param {object} via Via primitive.
|
|
253
|
+
* @param {'top' | 'bottom'} side Board side.
|
|
254
|
+
* @returns {boolean}
|
|
255
|
+
*/
|
|
256
|
+
static #isSideTented(via, side) {
|
|
257
|
+
const fieldName = side === 'bottom' ? 'isTentingBottom' : 'isTentingTop'
|
|
258
|
+
return via?.[fieldName] !== false
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Resolves the solder-mask surface Z above the exposed copper stack.
|
|
263
|
+
* @param {'top' | 'bottom'} side Board side.
|
|
264
|
+
* @param {number} thicknessMil Board thickness in mil.
|
|
265
|
+
* @returns {number}
|
|
266
|
+
*/
|
|
267
|
+
static #surfaceZ(side, thicknessMil) {
|
|
268
|
+
const distance =
|
|
269
|
+
Math.max(Number(thicknessMil) || 0, 0) / 2 +
|
|
270
|
+
PcbScene3dViaFactory.#SURFACE_MASK_Z_OFFSET_MIL
|
|
271
|
+
return side === 'bottom' ? -distance : distance
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Resolves one reusable via geometry from the via drill spec.
|
|
276
|
+
* @param {any} THREE
|
|
277
|
+
* @param {Map<string, any>} geometryCache
|
|
278
|
+
* @param {{ diameter?: number, holeDiameter?: number, barrelOnly?: boolean }} via
|
|
279
|
+
* @param {number} depth Copper span depth in mil.
|
|
280
|
+
* @returns {any}
|
|
281
|
+
*/
|
|
282
|
+
static #resolveGeometry(THREE, geometryCache, via, depth) {
|
|
97
283
|
const outerRadius = Math.max(Number(via?.diameter || 0) / 2, 1.2)
|
|
98
284
|
const holeDiameter = Math.max(Number(via?.holeDiameter || 0), 0)
|
|
99
|
-
const depth = PcbScene3dViaFactory.#geometryDepth(
|
|
100
|
-
renderMode,
|
|
101
|
-
thicknessMil
|
|
102
|
-
)
|
|
103
285
|
const isBarrelOnly = Boolean(via?.barrelOnly)
|
|
104
286
|
const cacheKey = [
|
|
105
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'
|