pcb-scene3d-viewer 1.1.20 → 1.1.22
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/package.json +1 -1
- package/src/PcbAssemblyBoardSubstrateBuilder.mjs +7 -5
- package/src/PcbAssemblyGeometryBuilder.mjs +7 -134
- package/src/PcbAssemblyMeshUtils.mjs +117 -0
- package/src/PcbAssemblyPadMeshBuilder.mjs +394 -0
- package/src/PcbAssemblyStepWriter.mjs +24 -2
- package/src/PcbModelArchiveExporter.mjs +504 -5
- package/src/PcbScene3dCameraRig.mjs +11 -5
- package/src/PcbScene3dCompanionBasePlacementAdjuster.mjs +242 -0
- package/src/PcbScene3dComponentVisibility.mjs +257 -0
- package/src/PcbScene3dController.mjs +29 -4
- package/src/PcbScene3dExternalCompanionFallback.mjs +202 -0
- package/src/PcbScene3dExternalModelPlacementRepair.mjs +1 -1
- package/src/PcbScene3dExternalModels.mjs +6 -0
- package/src/PcbScene3dFallbackBodyFactory.mjs +105 -0
- package/src/PcbScene3dFallbackVisibility.mjs +58 -1
- package/src/PcbScene3dInteractionHints.mjs +14 -6
- package/src/PcbScene3dRenderGroupVisibility.mjs +8 -1
- package/src/PcbScene3dRenderScheduler.mjs +55 -0
- package/src/PcbScene3dRuntime.mjs +110 -67
- package/src/PcbScene3dSelectionInspectorRenderer.mjs +64 -3
- package/src/PcbScene3dSelectionVisibilityBinder.mjs +119 -0
- package/src/PcbScene3dStaticBodyFactory.mjs +241 -0
- package/src/PcbScene3dText.mjs +4 -2
- package/src/scene3d.mjs +3 -0
- package/src/styles/scene3d.css +48 -0
package/package.json
CHANGED
|
@@ -145,11 +145,13 @@ export class PcbAssemblyBoardSubstrateBuilder {
|
|
|
145
145
|
* @returns {number[][][]}
|
|
146
146
|
*/
|
|
147
147
|
static #boardHoleLoops(sceneDescription, outer) {
|
|
148
|
-
const
|
|
149
|
-
PcbAssemblyBoardSubstrateBuilder.#
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
const primitiveLoops =
|
|
149
|
+
PcbAssemblyBoardSubstrateBuilder.#primitiveHoleLoops(
|
|
150
|
+
sceneDescription
|
|
151
|
+
)
|
|
152
|
+
const rawLoops = primitiveLoops.length
|
|
153
|
+
? primitiveLoops
|
|
154
|
+
: PcbAssemblyBoardSubstrateBuilder.#drillCutoutLoops(
|
|
153
155
|
sceneDescription
|
|
154
156
|
)
|
|
155
157
|
const seen = new Set()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PcbAssemblyBoardSubstrateBuilder } from './PcbAssemblyBoardSubstrateBuilder.mjs'
|
|
2
2
|
import { PcbAssemblyMeshUtils } from './PcbAssemblyMeshUtils.mjs'
|
|
3
|
+
import { PcbAssemblyPadMeshBuilder } from './PcbAssemblyPadMeshBuilder.mjs'
|
|
3
4
|
|
|
4
5
|
const COPPER_THICKNESS_MIL = 2.2
|
|
5
6
|
const SILKSCREEN_THICKNESS_MIL = 0.8
|
|
@@ -166,19 +167,21 @@ export class PcbAssemblyGeometryBuilder {
|
|
|
166
167
|
const pads = PcbAssemblyGeometryBuilder.#array(detail.pads)
|
|
167
168
|
for (let index = 0; index < pads.length; index += 1) {
|
|
168
169
|
const pad = pads[index]
|
|
169
|
-
const topMesh =
|
|
170
|
+
const topMesh = PcbAssemblyPadMeshBuilder.build(
|
|
170
171
|
'pad-top-' + (index + 1),
|
|
171
172
|
pad,
|
|
172
173
|
'top',
|
|
173
174
|
topZ,
|
|
174
|
-
COPPER_THICKNESS_MIL
|
|
175
|
+
COPPER_THICKNESS_MIL,
|
|
176
|
+
COPPER_COLOR
|
|
175
177
|
)
|
|
176
|
-
const bottomMesh =
|
|
178
|
+
const bottomMesh = PcbAssemblyPadMeshBuilder.build(
|
|
177
179
|
'pad-bottom-' + (index + 1),
|
|
178
180
|
pad,
|
|
179
181
|
'bottom',
|
|
180
182
|
bottomZ,
|
|
181
|
-
COPPER_THICKNESS_MIL
|
|
183
|
+
COPPER_THICKNESS_MIL,
|
|
184
|
+
COPPER_COLOR
|
|
182
185
|
)
|
|
183
186
|
if (topMesh) meshes.push(topMesh)
|
|
184
187
|
await progress?.advance?.(
|
|
@@ -583,136 +586,6 @@ export class PcbAssemblyGeometryBuilder {
|
|
|
583
586
|
)
|
|
584
587
|
}
|
|
585
588
|
|
|
586
|
-
/**
|
|
587
|
-
* Builds one pad mesh.
|
|
588
|
-
* @param {string} name Mesh name.
|
|
589
|
-
* @param {object} pad Pad primitive.
|
|
590
|
-
* @param {'top' | 'bottom'} side Pad side.
|
|
591
|
-
* @param {number} z Center Z.
|
|
592
|
-
* @param {number} thickness Extrusion thickness.
|
|
593
|
-
* @returns {object | null}
|
|
594
|
-
*/
|
|
595
|
-
static #padMesh(name, pad, side, z, thickness) {
|
|
596
|
-
const size = PcbAssemblyGeometryBuilder.#padSize(pad, side)
|
|
597
|
-
if (!size) {
|
|
598
|
-
return null
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
const offset = PcbAssemblyGeometryBuilder.#padOffset(pad, side)
|
|
602
|
-
const x = Number(pad?.x || 0) + offset.x
|
|
603
|
-
const y = Number(pad?.y || 0) + offset.y
|
|
604
|
-
const shape = PcbAssemblyGeometryBuilder.#padShape(pad, side)
|
|
605
|
-
const isCircle =
|
|
606
|
-
Math.abs(size.width - size.height) < 0.001 && shape !== 2
|
|
607
|
-
const mesh = isCircle
|
|
608
|
-
? PcbAssemblyMeshUtils.cylinder(name, {
|
|
609
|
-
x,
|
|
610
|
-
y,
|
|
611
|
-
z,
|
|
612
|
-
radius: size.width / 2,
|
|
613
|
-
height: thickness,
|
|
614
|
-
color: COPPER_COLOR
|
|
615
|
-
})
|
|
616
|
-
: PcbAssemblyMeshUtils.box(name, {
|
|
617
|
-
x,
|
|
618
|
-
y,
|
|
619
|
-
z,
|
|
620
|
-
width: size.width,
|
|
621
|
-
depth: size.height,
|
|
622
|
-
height: thickness,
|
|
623
|
-
color: COPPER_COLOR
|
|
624
|
-
})
|
|
625
|
-
|
|
626
|
-
return PcbAssemblyGeometryBuilder.#rotateMeshAroundZ(
|
|
627
|
-
mesh,
|
|
628
|
-
Number(pad?.rotation || 0),
|
|
629
|
-
x,
|
|
630
|
-
y
|
|
631
|
-
)
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Resolves side-specific pad dimensions.
|
|
636
|
-
* @param {object} pad Pad primitive.
|
|
637
|
-
* @param {'top' | 'bottom'} side Pad side.
|
|
638
|
-
* @returns {{ width: number, height: number } | null}
|
|
639
|
-
*/
|
|
640
|
-
static #padSize(pad, side) {
|
|
641
|
-
const prefix = side === 'bottom' ? 'Bottom' : 'Top'
|
|
642
|
-
let width = PcbAssemblyGeometryBuilder.#firstPositive([
|
|
643
|
-
pad?.['size' + prefix + 'X']
|
|
644
|
-
])
|
|
645
|
-
let height = PcbAssemblyGeometryBuilder.#firstPositive([
|
|
646
|
-
pad?.['size' + prefix + 'Y']
|
|
647
|
-
])
|
|
648
|
-
|
|
649
|
-
if (side === 'top' && !width) {
|
|
650
|
-
width = PcbAssemblyGeometryBuilder.#firstPositive([
|
|
651
|
-
pad?.width,
|
|
652
|
-
pad?.sizeX,
|
|
653
|
-
pad?.diameter,
|
|
654
|
-
pad?.sizeMidX,
|
|
655
|
-
pad?.sizeBottomX
|
|
656
|
-
])
|
|
657
|
-
}
|
|
658
|
-
if (side === 'top' && !height) {
|
|
659
|
-
height = PcbAssemblyGeometryBuilder.#firstPositive([
|
|
660
|
-
pad?.height,
|
|
661
|
-
pad?.sizeY,
|
|
662
|
-
pad?.diameter,
|
|
663
|
-
pad?.sizeMidY,
|
|
664
|
-
pad?.sizeBottomY,
|
|
665
|
-
width
|
|
666
|
-
])
|
|
667
|
-
}
|
|
668
|
-
if (width && !height) {
|
|
669
|
-
height = width
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
return width && height
|
|
673
|
-
? {
|
|
674
|
-
width: Math.max(width, 1),
|
|
675
|
-
height: Math.max(height, 1)
|
|
676
|
-
}
|
|
677
|
-
: null
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* Resolves side-specific pad center offset.
|
|
682
|
-
* @param {object} pad Pad primitive.
|
|
683
|
-
* @param {'top' | 'bottom'} side Pad side.
|
|
684
|
-
* @returns {{ x: number, y: number }}
|
|
685
|
-
*/
|
|
686
|
-
static #padOffset(pad, side) {
|
|
687
|
-
const prefix = side === 'bottom' ? 'Bottom' : 'Top'
|
|
688
|
-
|
|
689
|
-
return {
|
|
690
|
-
x: Number(pad?.['offset' + prefix + 'X'] || 0),
|
|
691
|
-
y: Number(pad?.['offset' + prefix + 'Y'] || 0)
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* Resolves the renderer pad shape code for one side.
|
|
697
|
-
* @param {object} pad Pad primitive.
|
|
698
|
-
* @param {'top' | 'bottom'} side Pad side.
|
|
699
|
-
* @returns {number}
|
|
700
|
-
*/
|
|
701
|
-
static #padShape(pad, side) {
|
|
702
|
-
const prefix = side === 'bottom' ? 'Bottom' : 'Top'
|
|
703
|
-
const rounded = pad?.['roundedRectShape' + prefix]
|
|
704
|
-
if (Number.isInteger(Number(rounded))) {
|
|
705
|
-
return Number(rounded)
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
return Number(
|
|
709
|
-
pad?.['shape' + prefix] ||
|
|
710
|
-
pad?.shapeMid ||
|
|
711
|
-
(side === 'top' ? pad?.shapeBottom : pad?.shapeTop) ||
|
|
712
|
-
0
|
|
713
|
-
)
|
|
714
|
-
}
|
|
715
|
-
|
|
716
589
|
/**
|
|
717
590
|
* Builds one via mesh.
|
|
718
591
|
* @param {string} name Mesh name.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import earcut from 'earcut'
|
|
2
|
+
|
|
1
3
|
const FULL_CIRCLE_DEGREES = 360
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -88,6 +90,100 @@ export class PcbAssemblyMeshUtils {
|
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Builds an extruded polygon mesh with inner cutout loops.
|
|
95
|
+
* @param {string} name Mesh name.
|
|
96
|
+
* @param {number[][]} outerPoints Outer polygon points in mils.
|
|
97
|
+
* @param {number[][][]} holeLoops Inner cutout loops in mils.
|
|
98
|
+
* @param {number} z Center Z in mils.
|
|
99
|
+
* @param {number} thickness Extrusion thickness in mils.
|
|
100
|
+
* @param {number[] | undefined} color Optional RGB color.
|
|
101
|
+
* @returns {{ name: string, vertices: number[][], faces: number[][], color?: number[] } | null}
|
|
102
|
+
*/
|
|
103
|
+
static prismWithHoles(
|
|
104
|
+
name,
|
|
105
|
+
outerPoints,
|
|
106
|
+
holeLoops,
|
|
107
|
+
z,
|
|
108
|
+
thickness,
|
|
109
|
+
color = undefined
|
|
110
|
+
) {
|
|
111
|
+
const outer = PcbAssemblyMeshUtils.cleanLoop(outerPoints)
|
|
112
|
+
const holes = (Array.isArray(holeLoops) ? holeLoops : [])
|
|
113
|
+
.map((loop) => PcbAssemblyMeshUtils.cleanLoop(loop))
|
|
114
|
+
.filter((loop) => loop.length >= 3)
|
|
115
|
+
if (outer.length < 3) {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
if (!holes.length) {
|
|
119
|
+
return PcbAssemblyMeshUtils.prism(name, outer, z, thickness, color)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const loops = [outer, ...holes]
|
|
123
|
+
const holeIndexes = []
|
|
124
|
+
const points = []
|
|
125
|
+
const flat = []
|
|
126
|
+
for (const loop of loops) {
|
|
127
|
+
if (points.length) {
|
|
128
|
+
holeIndexes.push(points.length)
|
|
129
|
+
}
|
|
130
|
+
for (const point of loop) {
|
|
131
|
+
points.push(point)
|
|
132
|
+
flat.push(point[0], point[1])
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const triangles = earcut(flat, holeIndexes, 2)
|
|
137
|
+
if (!triangles.length) {
|
|
138
|
+
return PcbAssemblyMeshUtils.prism(name, outer, z, thickness, color)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const halfThickness = Math.max(Number(thickness || 0), 0.001) / 2
|
|
142
|
+
const bottomZ = Number(z || 0) - halfThickness
|
|
143
|
+
const topZ = Number(z || 0) + halfThickness
|
|
144
|
+
const topOffset = points.length
|
|
145
|
+
const vertices = [
|
|
146
|
+
...points.map((point) => [point[0], point[1], bottomZ]),
|
|
147
|
+
...points.map((point) => [point[0], point[1], topZ])
|
|
148
|
+
]
|
|
149
|
+
const faces = []
|
|
150
|
+
|
|
151
|
+
for (let index = 0; index + 2 < triangles.length; index += 3) {
|
|
152
|
+
const a = triangles[index]
|
|
153
|
+
const b = triangles[index + 1]
|
|
154
|
+
const c = triangles[index + 2]
|
|
155
|
+
faces.push([c, b, a])
|
|
156
|
+
faces.push([a + topOffset, b + topOffset, c + topOffset])
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let loopStart = 0
|
|
160
|
+
PcbAssemblyMeshUtils.#appendLoopWalls(
|
|
161
|
+
faces,
|
|
162
|
+
loopStart,
|
|
163
|
+
outer.length,
|
|
164
|
+
topOffset,
|
|
165
|
+
false
|
|
166
|
+
)
|
|
167
|
+
loopStart += outer.length
|
|
168
|
+
for (const hole of holes) {
|
|
169
|
+
PcbAssemblyMeshUtils.#appendLoopWalls(
|
|
170
|
+
faces,
|
|
171
|
+
loopStart,
|
|
172
|
+
hole.length,
|
|
173
|
+
topOffset,
|
|
174
|
+
true
|
|
175
|
+
)
|
|
176
|
+
loopStart += hole.length
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
name,
|
|
181
|
+
vertices,
|
|
182
|
+
faces,
|
|
183
|
+
...(Array.isArray(color) ? { color } : {})
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
91
187
|
/**
|
|
92
188
|
* Builds a cylinder mesh from polygonal rings.
|
|
93
189
|
* @param {string} name Mesh name.
|
|
@@ -538,4 +634,25 @@ export class PcbAssemblyMeshUtils {
|
|
|
538
634
|
.replace(/^-+|-+$/gu, '')
|
|
539
635
|
.slice(0, 80)
|
|
540
636
|
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Appends side-wall faces for one loop.
|
|
640
|
+
* @param {number[][]} faces Mutable face list.
|
|
641
|
+
* @param {number} start Loop start index.
|
|
642
|
+
* @param {number} length Loop length.
|
|
643
|
+
* @param {number} topOffset Top vertex offset.
|
|
644
|
+
* @param {boolean} reverse Whether to reverse wall orientation.
|
|
645
|
+
* @returns {void}
|
|
646
|
+
*/
|
|
647
|
+
static #appendLoopWalls(faces, start, length, topOffset, reverse) {
|
|
648
|
+
for (let index = 0; index < length; index += 1) {
|
|
649
|
+
const current = start + index
|
|
650
|
+
const next = start + ((index + 1) % length)
|
|
651
|
+
faces.push(
|
|
652
|
+
reverse
|
|
653
|
+
? [next, current, current + topOffset, next + topOffset]
|
|
654
|
+
: [current, next, next + topOffset, current + topOffset]
|
|
655
|
+
)
|
|
656
|
+
}
|
|
657
|
+
}
|
|
541
658
|
}
|