pcb-scene3d-viewer 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +67 -0
- package/COMMERCIAL-LICENSE.md +15 -0
- package/CONTRIBUTING.md +14 -0
- package/LICENSE +19 -0
- package/LICENSES/AGPL-3.0-or-later.txt +235 -0
- package/LICENSES/CC-BY-SA-4.0.txt +170 -0
- package/LICENSES/LGPL-2.1-or-later.txt +176 -0
- package/LICENSES/LicenseRef-PolyForm-Noncommercial-1.0.0.txt +131 -0
- package/NOTICE.md +36 -0
- package/README.md +128 -0
- package/REUSE.toml +16 -0
- package/docs/api.md +148 -0
- package/docs/circuitjson.md +190 -0
- package/docs/model-format.md +117 -0
- package/docs/testing.md +23 -0
- package/package.json +65 -0
- package/spec/library-scope.md +36 -0
- package/src/PcbModelArchiveExporter.mjs +320 -0
- package/src/PcbScene3dArcUtils.mjs +27 -0
- package/src/PcbScene3dBoardAssemblyPlacement.mjs +36 -0
- package/src/PcbScene3dBoardAssemblyPresentation.mjs +859 -0
- package/src/PcbScene3dBoardEdgeCutoutBuilder.mjs +537 -0
- package/src/PcbScene3dBoardMaterialPalette.mjs +40 -0
- package/src/PcbScene3dBoardShapeFactory.mjs +895 -0
- package/src/PcbScene3dBoardSolderMaskFactory.mjs +613 -0
- package/src/PcbScene3dCameraRig.mjs +168 -0
- package/src/PcbScene3dCircuitJsonAdapter.mjs +545 -0
- package/src/PcbScene3dController.mjs +956 -0
- package/src/PcbScene3dCopperDetailFilter.mjs +490 -0
- package/src/PcbScene3dCopperFactory.mjs +559 -0
- package/src/PcbScene3dCopperTextFactory.mjs +534 -0
- package/src/PcbScene3dCutoutGeometryFilter.mjs +873 -0
- package/src/PcbScene3dDetailCoordinateNormalizer.mjs +65 -0
- package/src/PcbScene3dDrillCutoutFilter.mjs +224 -0
- package/src/PcbScene3dDrillPathFactory.mjs +362 -0
- package/src/PcbScene3dDrillVoidFactory.mjs +268 -0
- package/src/PcbScene3dExternalModelLoadOrder.mjs +54 -0
- package/src/PcbScene3dExternalModels.mjs +968 -0
- package/src/PcbScene3dFallbackVisibility.mjs +82 -0
- package/src/PcbScene3dInteractionHints.mjs +56 -0
- package/src/PcbScene3dMountRig.mjs +53 -0
- package/src/PcbScene3dOutlineBuilder.mjs +210 -0
- package/src/PcbScene3dPadFactory.mjs +553 -0
- package/src/PcbScene3dPresetState.mjs +48 -0
- package/src/PcbScene3dRenderGroupVisibility.mjs +134 -0
- package/src/PcbScene3dRuntime.mjs +996 -0
- package/src/PcbScene3dRuntimeBoardMeshes.mjs +99 -0
- package/src/PcbScene3dSelectionStyler.mjs +252 -0
- package/src/PcbScene3dShapePathFactory.mjs +220 -0
- package/src/PcbScene3dShellRenderer.mjs +131 -0
- package/src/PcbScene3dSilkscreenFactory.mjs +854 -0
- package/src/PcbScene3dSilkscreenStrokeWidthResolver.mjs +81 -0
- package/src/PcbScene3dStepLoader.mjs +611 -0
- package/src/PcbScene3dStrokeFont.mjs +671 -0
- package/src/PcbScene3dStrokeGeometryBuilder.mjs +322 -0
- package/src/PcbScene3dText.mjs +99 -0
- package/src/PcbScene3dTrueTypeTextFactory.mjs +885 -0
- package/src/PcbScene3dViaFactory.mjs +176 -0
- package/src/PcbScene3dViewCompensation.mjs +109 -0
- package/src/PcbScene3dViewScale.mjs +24 -0
- package/src/PcbScene3dViewportResize.mjs +35 -0
- package/src/PcbScene3dWorkerClient.mjs +123 -0
- package/src/index.mjs +1 -0
- package/src/scene3d.mjs +44 -0
- package/src/styles/scene3d.css +295 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { PcbScene3dDrillPathFactory } from './PcbScene3dDrillPathFactory.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds annular via barrels for the interactive 3D PCB scene.
|
|
5
|
+
*/
|
|
6
|
+
export class PcbScene3dViaFactory {
|
|
7
|
+
static #PAD_BARREL_OUTER_RADIUS_SCALE = 0.98
|
|
8
|
+
static #PAD_BARREL_MIN_WALL_MIL = 1.2
|
|
9
|
+
static #PAD_BARREL_WALL_FRACTION = 0.09
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds the via mesh group for one scene.
|
|
13
|
+
* @param {any} THREE
|
|
14
|
+
* @param {{ diameter?: number, holeDiameter?: number, x?: number, y?: number, barrelOnly?: boolean }[]} vias
|
|
15
|
+
* @param {number} thicknessMil
|
|
16
|
+
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
17
|
+
* @returns {any}
|
|
18
|
+
*/
|
|
19
|
+
static buildGroup(THREE, vias, thicknessMil, normalizeBoardPoint) {
|
|
20
|
+
const group = new THREE.Group()
|
|
21
|
+
const material = new THREE.MeshStandardMaterial({
|
|
22
|
+
color: 0xcaa24e,
|
|
23
|
+
roughness: 0.48,
|
|
24
|
+
metalness: 0.42,
|
|
25
|
+
side: THREE.DoubleSide
|
|
26
|
+
})
|
|
27
|
+
const geometryCache = new Map()
|
|
28
|
+
|
|
29
|
+
;(vias || []).forEach((via) => {
|
|
30
|
+
const geometry = PcbScene3dViaFactory.#resolveGeometry(
|
|
31
|
+
THREE,
|
|
32
|
+
geometryCache,
|
|
33
|
+
via,
|
|
34
|
+
thicknessMil
|
|
35
|
+
)
|
|
36
|
+
const mesh = new THREE.Mesh(geometry, material)
|
|
37
|
+
const point = normalizeBoardPoint(
|
|
38
|
+
Number(via?.x || 0),
|
|
39
|
+
Number(via?.y || 0)
|
|
40
|
+
)
|
|
41
|
+
mesh.position.set(point.x, point.y, 0)
|
|
42
|
+
if (geometry.type === 'CylinderGeometry') {
|
|
43
|
+
mesh.rotation.x = Math.PI / 2
|
|
44
|
+
}
|
|
45
|
+
group.add(mesh)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return group
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolves one reusable via geometry from the via drill spec.
|
|
53
|
+
* @param {any} THREE
|
|
54
|
+
* @param {Map<string, any>} geometryCache
|
|
55
|
+
* @param {{ diameter?: number, holeDiameter?: number, barrelOnly?: boolean }} via
|
|
56
|
+
* @param {number} thicknessMil
|
|
57
|
+
* @returns {any}
|
|
58
|
+
*/
|
|
59
|
+
static #resolveGeometry(THREE, geometryCache, via, thicknessMil) {
|
|
60
|
+
const outerRadius = Math.max(Number(via?.diameter || 0) / 2, 1.2)
|
|
61
|
+
const holeDiameter = Math.max(Number(via?.holeDiameter || 0), 0)
|
|
62
|
+
const depth = thicknessMil + 2
|
|
63
|
+
const isBarrelOnly = Boolean(via?.barrelOnly)
|
|
64
|
+
const cacheKey = [
|
|
65
|
+
isBarrelOnly ? 'barrel' : 'annulus',
|
|
66
|
+
outerRadius.toFixed(4),
|
|
67
|
+
holeDiameter.toFixed(4),
|
|
68
|
+
depth.toFixed(4)
|
|
69
|
+
].join(':')
|
|
70
|
+
const cached = geometryCache.get(cacheKey)
|
|
71
|
+
if (cached) {
|
|
72
|
+
return cached
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let geometry
|
|
76
|
+
if (isBarrelOnly && holeDiameter > 0) {
|
|
77
|
+
geometry = PcbScene3dViaFactory.#buildPadBarrelGeometry(
|
|
78
|
+
THREE,
|
|
79
|
+
holeDiameter,
|
|
80
|
+
depth
|
|
81
|
+
)
|
|
82
|
+
} else if (holeDiameter > 0 && holeDiameter < outerRadius * 2 - 0.001) {
|
|
83
|
+
const shape = new THREE.Shape()
|
|
84
|
+
shape.moveTo(outerRadius, 0)
|
|
85
|
+
shape.absarc(0, 0, outerRadius, 0, Math.PI, false)
|
|
86
|
+
shape.absarc(0, 0, outerRadius, Math.PI, Math.PI * 2, false)
|
|
87
|
+
const drillHole = PcbScene3dDrillPathFactory.buildViaHolePath(
|
|
88
|
+
THREE,
|
|
89
|
+
via
|
|
90
|
+
)
|
|
91
|
+
if (drillHole) {
|
|
92
|
+
shape.holes.push(drillHole)
|
|
93
|
+
}
|
|
94
|
+
geometry = new THREE.ExtrudeGeometry(shape, {
|
|
95
|
+
depth,
|
|
96
|
+
bevelEnabled: false,
|
|
97
|
+
curveSegments: 24,
|
|
98
|
+
steps: 1
|
|
99
|
+
})
|
|
100
|
+
geometry.translate?.(0, 0, -depth / 2)
|
|
101
|
+
} else {
|
|
102
|
+
geometry = new THREE.CylinderGeometry(
|
|
103
|
+
outerRadius,
|
|
104
|
+
outerRadius,
|
|
105
|
+
depth,
|
|
106
|
+
18
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
geometryCache.set(cacheKey, geometry)
|
|
111
|
+
return geometry
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Builds a visible copper sleeve for through-hole pads.
|
|
116
|
+
* @param {any} THREE
|
|
117
|
+
* @param {number} holeDiameter
|
|
118
|
+
* @param {number} depth
|
|
119
|
+
* @returns {any}
|
|
120
|
+
*/
|
|
121
|
+
static #buildPadBarrelGeometry(THREE, holeDiameter, depth) {
|
|
122
|
+
const holeRadius = Math.max(Number(holeDiameter || 0) / 2, 0.8)
|
|
123
|
+
const outerRadius = Math.max(
|
|
124
|
+
holeRadius * PcbScene3dViaFactory.#PAD_BARREL_OUTER_RADIUS_SCALE,
|
|
125
|
+
0.8
|
|
126
|
+
)
|
|
127
|
+
const wallThickness = Math.max(
|
|
128
|
+
Number(holeDiameter || 0) *
|
|
129
|
+
PcbScene3dViaFactory.#PAD_BARREL_WALL_FRACTION,
|
|
130
|
+
PcbScene3dViaFactory.#PAD_BARREL_MIN_WALL_MIL
|
|
131
|
+
)
|
|
132
|
+
const innerRadius = Math.max(outerRadius - wallThickness, 0.6)
|
|
133
|
+
const shape = PcbScene3dViaFactory.#buildCircleShape(THREE, outerRadius)
|
|
134
|
+
shape.holes.push(
|
|
135
|
+
PcbScene3dViaFactory.#buildCirclePath(THREE, innerRadius)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
const geometry = new THREE.ExtrudeGeometry(shape, {
|
|
139
|
+
depth,
|
|
140
|
+
bevelEnabled: false,
|
|
141
|
+
curveSegments: 24,
|
|
142
|
+
steps: 1
|
|
143
|
+
})
|
|
144
|
+
geometry.translate?.(0, 0, -depth / 2)
|
|
145
|
+
return geometry
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Builds one filled circular shape centered on the origin.
|
|
150
|
+
* @param {any} THREE
|
|
151
|
+
* @param {number} radius
|
|
152
|
+
* @returns {any}
|
|
153
|
+
*/
|
|
154
|
+
static #buildCircleShape(THREE, radius) {
|
|
155
|
+
const shape = new THREE.Shape()
|
|
156
|
+
shape.moveTo(radius, 0)
|
|
157
|
+
shape.absarc(0, 0, radius, 0, Math.PI, false)
|
|
158
|
+
shape.absarc(0, 0, radius, Math.PI, Math.PI * 2, false)
|
|
159
|
+
return shape
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Builds one circular hole path centered on the origin.
|
|
164
|
+
* @param {any} THREE
|
|
165
|
+
* @param {number} radius
|
|
166
|
+
* @returns {any}
|
|
167
|
+
*/
|
|
168
|
+
static #buildCirclePath(THREE, radius) {
|
|
169
|
+
const path = new THREE.Path()
|
|
170
|
+
path.moveTo(radius, 0)
|
|
171
|
+
path.absarc(0, 0, radius, 0, Math.PI, false)
|
|
172
|
+
path.absarc(0, 0, radius, Math.PI, Math.PI * 2, false)
|
|
173
|
+
path.closePath()
|
|
174
|
+
return path
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies scene-view mirror compensation to selected 3D detail nodes.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dViewCompensation {
|
|
5
|
+
/**
|
|
6
|
+
* Applies the active view mirror compensation throughout one scene branch.
|
|
7
|
+
* @param {any} root Root group or mesh.
|
|
8
|
+
* @param {{ x?: number, y?: number, z?: number } | null | undefined} viewScale Active scene view scale.
|
|
9
|
+
* @returns {void}
|
|
10
|
+
*/
|
|
11
|
+
static apply(root, viewScale) {
|
|
12
|
+
const pendingGroups = [root]
|
|
13
|
+
|
|
14
|
+
while (pendingGroups.length) {
|
|
15
|
+
const group = pendingGroups.pop()
|
|
16
|
+
if (!group) {
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (group.userData?.scene3dViewCompensation) {
|
|
21
|
+
PcbScene3dViewCompensation.applyToGroup(group, viewScale)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (Array.isArray(group.children)) {
|
|
25
|
+
group.children.forEach((child) => pendingGroups.push(child))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Applies the active view mirror compensation to one marked object.
|
|
32
|
+
* @param {any} group View compensation target.
|
|
33
|
+
* @param {{ x?: number, y?: number, z?: number } | null | undefined} viewScale Active scene view scale.
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
36
|
+
static applyToGroup(group, viewScale) {
|
|
37
|
+
const axes = group?.userData?.scene3dViewCompensationAxes || {}
|
|
38
|
+
const axisSources =
|
|
39
|
+
group?.userData?.scene3dViewCompensationAxisSources || {}
|
|
40
|
+
const sourceScale = group?.userData?.scene3dSourceFrameScale || {}
|
|
41
|
+
|
|
42
|
+
group?.scale?.set?.(
|
|
43
|
+
PcbScene3dViewCompensation.#resolveScaleMultiplier(sourceScale?.x) *
|
|
44
|
+
(axes.x === false
|
|
45
|
+
? 1
|
|
46
|
+
: PcbScene3dViewCompensation.#resolveCompensationSign(
|
|
47
|
+
viewScale,
|
|
48
|
+
'x',
|
|
49
|
+
axisSources?.x
|
|
50
|
+
)),
|
|
51
|
+
PcbScene3dViewCompensation.#resolveScaleMultiplier(sourceScale?.y) *
|
|
52
|
+
(axes.y === false
|
|
53
|
+
? 1
|
|
54
|
+
: PcbScene3dViewCompensation.#resolveCompensationSign(
|
|
55
|
+
viewScale,
|
|
56
|
+
'y',
|
|
57
|
+
axisSources?.y
|
|
58
|
+
)),
|
|
59
|
+
PcbScene3dViewCompensation.#resolveScaleMultiplier(sourceScale?.z) *
|
|
60
|
+
(axes.z === false
|
|
61
|
+
? 1
|
|
62
|
+
: PcbScene3dViewCompensation.#resolveCompensationSign(
|
|
63
|
+
viewScale,
|
|
64
|
+
'z',
|
|
65
|
+
axisSources?.z
|
|
66
|
+
))
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolves a source-frame scale multiplier.
|
|
72
|
+
* @param {number | string | undefined} value Source scale value.
|
|
73
|
+
* @returns {number}
|
|
74
|
+
*/
|
|
75
|
+
static #resolveScaleMultiplier(value) {
|
|
76
|
+
const scale = Number(value)
|
|
77
|
+
|
|
78
|
+
return Number.isFinite(scale) && scale !== 0 ? scale : 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolves the compensation sign for one view axis.
|
|
83
|
+
* @param {{ x?: number, y?: number, z?: number } | null | undefined} viewScale Active scene view scale.
|
|
84
|
+
* @param {'x' | 'y' | 'z'} axis Target scale axis.
|
|
85
|
+
* @param {string | undefined} source Axis source mode.
|
|
86
|
+
* @returns {number}
|
|
87
|
+
*/
|
|
88
|
+
static #resolveCompensationSign(viewScale, axis, source) {
|
|
89
|
+
if (source === 'board-mirror') {
|
|
90
|
+
return (
|
|
91
|
+
PcbScene3dViewCompensation.#resolveViewScaleSign(viewScale?.x) *
|
|
92
|
+
PcbScene3dViewCompensation.#resolveViewScaleSign(viewScale?.y)
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return PcbScene3dViewCompensation.#resolveViewScaleSign(
|
|
97
|
+
viewScale?.[axis]
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Converts one view scale axis into the matching mirror compensation sign.
|
|
103
|
+
* @param {number | string | undefined} value View scale axis.
|
|
104
|
+
* @returns {1 | -1}
|
|
105
|
+
*/
|
|
106
|
+
static #resolveViewScaleSign(value) {
|
|
107
|
+
return Number(value) < 0 ? -1 : 1
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves per-view scene scale transforms for PCB 3D presets.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dViewScale {
|
|
5
|
+
/**
|
|
6
|
+
* Resolves the scene-scale transform for one named preset.
|
|
7
|
+
* @param {string} preset Preset name.
|
|
8
|
+
* @param {{ coordinateSystem?: string } | null} [sceneDescription] Scene coordinate metadata.
|
|
9
|
+
* @returns {{ x: number, y: number, z: number }}
|
|
10
|
+
*/
|
|
11
|
+
static resolve(preset, sceneDescription = null) {
|
|
12
|
+
const normalizedPreset = String(preset || 'isometric').toLowerCase()
|
|
13
|
+
|
|
14
|
+
if (sceneDescription?.coordinateSystem === 'kicad-3d-y-up') {
|
|
15
|
+
return { x: 1, y: 1, z: 1 }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return normalizedPreset === 'top'
|
|
19
|
+
? { x: 1, y: -1, z: 1 }
|
|
20
|
+
: normalizedPreset === 'bottom'
|
|
21
|
+
? { x: -1, y: 1, z: 1 }
|
|
22
|
+
: { x: 1, y: -1, z: 1 }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Viewport sizing helpers for the 3D runtime.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dViewportResize {
|
|
5
|
+
/**
|
|
6
|
+
* Observes element-level viewport size changes.
|
|
7
|
+
* @param {typeof globalThis} globalRef Runtime global object.
|
|
8
|
+
* @param {HTMLElement | null} viewportNode Viewport mount node.
|
|
9
|
+
* @param {() => void} onResize Resize callback.
|
|
10
|
+
* @returns {{ disconnect?: () => void } | null}
|
|
11
|
+
*/
|
|
12
|
+
static observe(globalRef, viewportNode, onResize) {
|
|
13
|
+
const ResizeObserverCtor = globalRef?.ResizeObserver
|
|
14
|
+
if (typeof ResizeObserverCtor !== 'function' || !viewportNode) {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const observer = new ResizeObserverCtor(() => onResize())
|
|
19
|
+
observer.observe(viewportNode)
|
|
20
|
+
return observer
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolves the viewport size while guarding against zero-sized mounts.
|
|
25
|
+
* @param {HTMLElement | null} viewportNode Viewport mount node.
|
|
26
|
+
* @returns {{ width: number, height: number }}
|
|
27
|
+
*/
|
|
28
|
+
static resolveSize(viewportNode) {
|
|
29
|
+
const rect = viewportNode?.getBoundingClientRect?.()
|
|
30
|
+
return {
|
|
31
|
+
width: Math.max(Math.round(Number(rect?.width || 960)), 320),
|
|
32
|
+
height: Math.max(Math.round(Number(rect?.height || 560)), 320)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side client wrapper for the dedicated 3D scene prep worker.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dWorkerClient {
|
|
5
|
+
/** @type {(() => Worker) | null} */
|
|
6
|
+
#createWorker
|
|
7
|
+
|
|
8
|
+
/** @type {Worker | null} */
|
|
9
|
+
#worker
|
|
10
|
+
|
|
11
|
+
/** @type {number} */
|
|
12
|
+
#requestSequence
|
|
13
|
+
|
|
14
|
+
/** @type {Map<string, { resolve: (sceneDescription: any) => void, reject: (error: Error) => void }>} */
|
|
15
|
+
#pendingRequests
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {(() => Worker) | null} workerFactory
|
|
19
|
+
*/
|
|
20
|
+
constructor(workerFactory) {
|
|
21
|
+
this.#createWorker = workerFactory || null
|
|
22
|
+
this.#worker = null
|
|
23
|
+
this.#requestSequence = 1
|
|
24
|
+
this.#pendingRequests = new Map()
|
|
25
|
+
this.#ensureWorker()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Sends one 3D scene prep request through the worker.
|
|
30
|
+
* @param {any} documentModel
|
|
31
|
+
* @param {any[]} [sessionAssets]
|
|
32
|
+
* @returns {Promise<any>}
|
|
33
|
+
*/
|
|
34
|
+
prepareScene(documentModel, sessionAssets = []) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
if (!this.#worker) {
|
|
37
|
+
reject(new Error('3D scene worker is unavailable.'))
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const requestId = 'scene3d-request-' + this.#requestSequence++
|
|
42
|
+
this.#pendingRequests.set(requestId, { resolve, reject })
|
|
43
|
+
this.#worker.postMessage({
|
|
44
|
+
type: 'scene3d:prepare',
|
|
45
|
+
requestId,
|
|
46
|
+
documentModel,
|
|
47
|
+
sessionAssets
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Releases the worker and rejects any pending requests.
|
|
54
|
+
* @returns {void}
|
|
55
|
+
*/
|
|
56
|
+
dispose() {
|
|
57
|
+
this.#pendingRequests.forEach(({ reject }) => {
|
|
58
|
+
reject(new Error('3D scene worker terminated.'))
|
|
59
|
+
})
|
|
60
|
+
this.#pendingRequests.clear()
|
|
61
|
+
this.#worker?.terminate()
|
|
62
|
+
this.#worker = null
|
|
63
|
+
this.#createWorker = null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates the backing worker and event listeners once.
|
|
68
|
+
* @returns {void}
|
|
69
|
+
*/
|
|
70
|
+
#ensureWorker() {
|
|
71
|
+
if (!this.#createWorker || this.#worker) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.#worker = this.#createWorker()
|
|
76
|
+
this.#worker.addEventListener('message', (event) => {
|
|
77
|
+
this.#handleWorkerMessage(event?.data || {})
|
|
78
|
+
})
|
|
79
|
+
this.#worker.addEventListener('error', (event) => {
|
|
80
|
+
this.#rejectAllPending(
|
|
81
|
+
new Error(
|
|
82
|
+
'3D scene worker failed: ' +
|
|
83
|
+
String(event?.message || 'Unknown error.')
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Routes one worker payload to the matching pending request.
|
|
91
|
+
* @param {{ type?: string, requestId?: string, sceneDescription?: any, message?: string }} payload
|
|
92
|
+
* @returns {void}
|
|
93
|
+
*/
|
|
94
|
+
#handleWorkerMessage(payload) {
|
|
95
|
+
const requestId = String(payload?.requestId || '')
|
|
96
|
+
const pendingRequest = this.#pendingRequests.get(requestId) || null
|
|
97
|
+
if (!pendingRequest) {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.#pendingRequests.delete(requestId)
|
|
102
|
+
if (payload?.type === 'scene3d:success') {
|
|
103
|
+
pendingRequest.resolve(payload.sceneDescription || {})
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pendingRequest.reject(
|
|
108
|
+
new Error(payload?.message || '3D scene worker failed.')
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Rejects every unresolved worker request with one shared error.
|
|
114
|
+
* @param {Error} error
|
|
115
|
+
* @returns {void}
|
|
116
|
+
*/
|
|
117
|
+
#rejectAllPending(error) {
|
|
118
|
+
this.#pendingRequests.forEach(({ reject }) => {
|
|
119
|
+
reject(error)
|
|
120
|
+
})
|
|
121
|
+
this.#pendingRequests.clear()
|
|
122
|
+
}
|
|
123
|
+
}
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './scene3d.mjs'
|
package/src/scene3d.mjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export { PcbModelArchiveExporter } from './PcbModelArchiveExporter.mjs'
|
|
2
|
+
export { PcbScene3dBoardAssemblyPlacement } from './PcbScene3dBoardAssemblyPlacement.mjs'
|
|
3
|
+
export { PcbScene3dBoardAssemblyPresentation } from './PcbScene3dBoardAssemblyPresentation.mjs'
|
|
4
|
+
export { PcbScene3dBoardEdgeCutoutBuilder } from './PcbScene3dBoardEdgeCutoutBuilder.mjs'
|
|
5
|
+
export { PcbScene3dBoardMaterialPalette } from './PcbScene3dBoardMaterialPalette.mjs'
|
|
6
|
+
export { PcbScene3dBoardShapeFactory } from './PcbScene3dBoardShapeFactory.mjs'
|
|
7
|
+
export { PcbScene3dBoardSolderMaskFactory } from './PcbScene3dBoardSolderMaskFactory.mjs'
|
|
8
|
+
export { PcbScene3dCameraRig } from './PcbScene3dCameraRig.mjs'
|
|
9
|
+
export { PcbScene3dController } from './PcbScene3dController.mjs'
|
|
10
|
+
export { PcbScene3dCircuitJsonAdapter } from './PcbScene3dCircuitJsonAdapter.mjs'
|
|
11
|
+
export { PcbScene3dCopperDetailFilter } from './PcbScene3dCopperDetailFilter.mjs'
|
|
12
|
+
export { PcbScene3dCopperFactory } from './PcbScene3dCopperFactory.mjs'
|
|
13
|
+
export { PcbScene3dCopperTextFactory } from './PcbScene3dCopperTextFactory.mjs'
|
|
14
|
+
export { PcbScene3dCutoutGeometryFilter } from './PcbScene3dCutoutGeometryFilter.mjs'
|
|
15
|
+
export { PcbScene3dDetailCoordinateNormalizer } from './PcbScene3dDetailCoordinateNormalizer.mjs'
|
|
16
|
+
export { PcbScene3dDrillCutoutFilter } from './PcbScene3dDrillCutoutFilter.mjs'
|
|
17
|
+
export { PcbScene3dDrillPathFactory } from './PcbScene3dDrillPathFactory.mjs'
|
|
18
|
+
export { PcbScene3dDrillVoidFactory } from './PcbScene3dDrillVoidFactory.mjs'
|
|
19
|
+
export { PcbScene3dExternalModelLoadOrder } from './PcbScene3dExternalModelLoadOrder.mjs'
|
|
20
|
+
export { PcbScene3dExternalModels } from './PcbScene3dExternalModels.mjs'
|
|
21
|
+
export { PcbScene3dFallbackVisibility } from './PcbScene3dFallbackVisibility.mjs'
|
|
22
|
+
export { PcbScene3dInteractionHints } from './PcbScene3dInteractionHints.mjs'
|
|
23
|
+
export { PcbScene3dMountRig } from './PcbScene3dMountRig.mjs'
|
|
24
|
+
export { PcbScene3dOutlineBuilder } from './PcbScene3dOutlineBuilder.mjs'
|
|
25
|
+
export { PcbScene3dPadFactory } from './PcbScene3dPadFactory.mjs'
|
|
26
|
+
export { PcbScene3dPresetState } from './PcbScene3dPresetState.mjs'
|
|
27
|
+
export { PcbScene3dRenderGroupVisibility } from './PcbScene3dRenderGroupVisibility.mjs'
|
|
28
|
+
export { PcbScene3dRuntime } from './PcbScene3dRuntime.mjs'
|
|
29
|
+
export { PcbScene3dRuntimeBoardMeshes } from './PcbScene3dRuntimeBoardMeshes.mjs'
|
|
30
|
+
export { PcbScene3dSelectionStyler } from './PcbScene3dSelectionStyler.mjs'
|
|
31
|
+
export { PcbScene3dShapePathFactory } from './PcbScene3dShapePathFactory.mjs'
|
|
32
|
+
export { PcbScene3dShellRenderer } from './PcbScene3dShellRenderer.mjs'
|
|
33
|
+
export { PcbScene3dSilkscreenFactory } from './PcbScene3dSilkscreenFactory.mjs'
|
|
34
|
+
export { PcbScene3dSilkscreenStrokeWidthResolver } from './PcbScene3dSilkscreenStrokeWidthResolver.mjs'
|
|
35
|
+
export { PcbScene3dStepLoader } from './PcbScene3dStepLoader.mjs'
|
|
36
|
+
export { PcbScene3dStrokeFont } from './PcbScene3dStrokeFont.mjs'
|
|
37
|
+
export { PcbScene3dStrokeGeometryBuilder } from './PcbScene3dStrokeGeometryBuilder.mjs'
|
|
38
|
+
export { PcbScene3dText } from './PcbScene3dText.mjs'
|
|
39
|
+
export { PcbScene3dTrueTypeTextFactory } from './PcbScene3dTrueTypeTextFactory.mjs'
|
|
40
|
+
export { PcbScene3dViaFactory } from './PcbScene3dViaFactory.mjs'
|
|
41
|
+
export { PcbScene3dViewCompensation } from './PcbScene3dViewCompensation.mjs'
|
|
42
|
+
export { PcbScene3dViewScale } from './PcbScene3dViewScale.mjs'
|
|
43
|
+
export { PcbScene3dViewportResize } from './PcbScene3dViewportResize.mjs'
|
|
44
|
+
export { PcbScene3dWorkerClient } from './PcbScene3dWorkerClient.mjs'
|