pcb-scene3d-viewer 1.1.2 → 1.1.10
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/PcbScene3dArcUtils.mjs +18 -0
- package/src/PcbScene3dBoardAssemblyPresentation.mjs +3 -0
- package/src/PcbScene3dBoardSolderMaskFactory.mjs +2 -2
- package/src/PcbScene3dCameraRig.mjs +16 -1
- package/src/PcbScene3dCopperDetailFilter.mjs +73 -1
- package/src/PcbScene3dCopperDetailGroupBuilder.mjs +120 -0
- package/src/PcbScene3dCopperFactory.mjs +465 -33
- package/src/PcbScene3dCopperTextFactory.mjs +6 -3
- package/src/PcbScene3dGeometryZCompressor.mjs +48 -0
- package/src/PcbScene3dMaskCoveredCopperMaterial.mjs +75 -0
- package/src/PcbScene3dMaterialFinish.mjs +49 -0
- package/src/PcbScene3dRuntime.mjs +11 -64
- package/src/PcbScene3dRuntimeBoardMeshes.mjs +8 -3
- package/src/PcbScene3dSilkscreenFactory.mjs +25 -13
- package/src/PcbScene3dStrokeGeometryBuilder.mjs +7 -6
- package/src/PcbScene3dTrueTypeTextFactory.mjs +8 -3
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
import { PcbScene3dArcUtils } from './PcbScene3dArcUtils.mjs'
|
|
2
2
|
import { PcbScene3dPadFactory } from './PcbScene3dPadFactory.mjs'
|
|
3
3
|
import { PcbScene3dCopperTextFactory } from './PcbScene3dCopperTextFactory.mjs'
|
|
4
|
+
import { PcbScene3dMaskCoveredCopperMaterial } from './PcbScene3dMaskCoveredCopperMaterial.mjs'
|
|
5
|
+
import { PcbScene3dGeometryZCompressor } from './PcbScene3dGeometryZCompressor.mjs'
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Builds copper-detail meshes for the interactive 3D PCB scene.
|
|
7
9
|
*/
|
|
8
10
|
export class PcbScene3dCopperFactory {
|
|
11
|
+
static #ARC_SEGMENT_DEGREES = 3
|
|
9
12
|
static #TOP_COPPER_LAYER_ID = 1
|
|
10
13
|
static #BOTTOM_COPPER_LAYER_ID = 32
|
|
11
14
|
static #FULL_CIRCLE_EPSILON = 0.001
|
|
12
15
|
static #ROUND_CAP_SEGMENTS = 16
|
|
16
|
+
static #COPPER_THICKNESS_MIL = 2.2
|
|
17
|
+
static #COPPER_COLOR = 0xd9a61d
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns half the visual copper extrusion thickness.
|
|
21
|
+
* @returns {number}
|
|
22
|
+
*/
|
|
23
|
+
static visualHalfThicknessMil() {
|
|
24
|
+
return PcbScene3dCopperFactory.#COPPER_THICKNESS_MIL / 2
|
|
25
|
+
}
|
|
13
26
|
|
|
14
27
|
/**
|
|
15
28
|
* Builds the combined top and bottom copper group.
|
|
@@ -78,6 +91,71 @@ export class PcbScene3dCopperFactory {
|
|
|
78
91
|
return group
|
|
79
92
|
}
|
|
80
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Builds top and bottom traces that are covered by solder mask.
|
|
96
|
+
* @param {any} THREE
|
|
97
|
+
* @param {{ tracks?: any[], arcs?: any[] }} detail Mask-covered detail.
|
|
98
|
+
* @param {number} topZ
|
|
99
|
+
* @param {number} bottomZ
|
|
100
|
+
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
101
|
+
* @param {{ solderMaskColor?: number }} [options]
|
|
102
|
+
* @returns {any}
|
|
103
|
+
*/
|
|
104
|
+
static buildMaskCoveredGroup(
|
|
105
|
+
THREE,
|
|
106
|
+
detail,
|
|
107
|
+
topZ,
|
|
108
|
+
bottomZ,
|
|
109
|
+
normalizeBoardPoint,
|
|
110
|
+
options = {}
|
|
111
|
+
) {
|
|
112
|
+
const group = new THREE.Group()
|
|
113
|
+
const material = PcbScene3dMaskCoveredCopperMaterial.build(
|
|
114
|
+
THREE,
|
|
115
|
+
options
|
|
116
|
+
)
|
|
117
|
+
const topGroup = PcbScene3dCopperFactory.#buildMaskCoveredSideGroup(
|
|
118
|
+
THREE,
|
|
119
|
+
{
|
|
120
|
+
tracks: PcbScene3dCopperFactory.#filterTracks(
|
|
121
|
+
detail?.tracks,
|
|
122
|
+
'top'
|
|
123
|
+
),
|
|
124
|
+
arcs: PcbScene3dCopperFactory.#filterArcs(detail?.arcs, 'top')
|
|
125
|
+
},
|
|
126
|
+
Math.abs(Number(topZ || 0)),
|
|
127
|
+
normalizeBoardPoint,
|
|
128
|
+
false,
|
|
129
|
+
material
|
|
130
|
+
)
|
|
131
|
+
const bottomGroup = PcbScene3dCopperFactory.#buildMaskCoveredSideGroup(
|
|
132
|
+
THREE,
|
|
133
|
+
{
|
|
134
|
+
tracks: PcbScene3dCopperFactory.#filterTracks(
|
|
135
|
+
detail?.tracks,
|
|
136
|
+
'bottom'
|
|
137
|
+
),
|
|
138
|
+
arcs: PcbScene3dCopperFactory.#filterArcs(
|
|
139
|
+
detail?.arcs,
|
|
140
|
+
'bottom'
|
|
141
|
+
)
|
|
142
|
+
},
|
|
143
|
+
Math.abs(Number(bottomZ || 0)),
|
|
144
|
+
normalizeBoardPoint,
|
|
145
|
+
true,
|
|
146
|
+
material
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if (topGroup.children.length) {
|
|
150
|
+
group.add(topGroup)
|
|
151
|
+
}
|
|
152
|
+
if (bottomGroup.children.length) {
|
|
153
|
+
group.add(bottomGroup)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return group
|
|
157
|
+
}
|
|
158
|
+
|
|
81
159
|
/**
|
|
82
160
|
* Builds one side-specific copper group.
|
|
83
161
|
* @param {any} THREE
|
|
@@ -152,6 +230,65 @@ export class PcbScene3dCopperFactory {
|
|
|
152
230
|
return group
|
|
153
231
|
}
|
|
154
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Builds one side of the mask-covered trace relief.
|
|
235
|
+
* @param {any} THREE
|
|
236
|
+
* @param {{ tracks?: any[], arcs?: any[] }} detail Mask-covered detail.
|
|
237
|
+
* @param {number} z
|
|
238
|
+
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
239
|
+
* @param {boolean} mirrorY
|
|
240
|
+
* @param {any} material Shared covered-trace material.
|
|
241
|
+
* @returns {any}
|
|
242
|
+
*/
|
|
243
|
+
static #buildMaskCoveredSideGroup(
|
|
244
|
+
THREE,
|
|
245
|
+
detail,
|
|
246
|
+
z,
|
|
247
|
+
normalizeBoardPoint,
|
|
248
|
+
mirrorY,
|
|
249
|
+
material
|
|
250
|
+
) {
|
|
251
|
+
const group = new THREE.Group()
|
|
252
|
+
const trackMesh = PcbScene3dCopperFactory.#buildTrackMesh(
|
|
253
|
+
THREE,
|
|
254
|
+
detail?.tracks || [],
|
|
255
|
+
z,
|
|
256
|
+
normalizeBoardPoint,
|
|
257
|
+
mirrorY,
|
|
258
|
+
material
|
|
259
|
+
)
|
|
260
|
+
const arcMesh = PcbScene3dCopperFactory.#buildArcMesh(
|
|
261
|
+
THREE,
|
|
262
|
+
detail?.arcs || [],
|
|
263
|
+
z,
|
|
264
|
+
normalizeBoardPoint,
|
|
265
|
+
mirrorY,
|
|
266
|
+
material
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if (trackMesh) {
|
|
270
|
+
PcbScene3dGeometryZCompressor.compressMaskCoveredCopperMesh(
|
|
271
|
+
trackMesh,
|
|
272
|
+
z
|
|
273
|
+
)
|
|
274
|
+
trackMesh.name = 'mask-covered-copper-tracks'
|
|
275
|
+
group.add(trackMesh)
|
|
276
|
+
}
|
|
277
|
+
if (arcMesh) {
|
|
278
|
+
PcbScene3dGeometryZCompressor.compressMaskCoveredCopperMesh(
|
|
279
|
+
arcMesh,
|
|
280
|
+
z
|
|
281
|
+
)
|
|
282
|
+
arcMesh.name = 'mask-covered-copper-arcs'
|
|
283
|
+
group.add(arcMesh)
|
|
284
|
+
}
|
|
285
|
+
if (mirrorY && group.children.length) {
|
|
286
|
+
group.rotation.x = Math.PI
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return group
|
|
290
|
+
}
|
|
291
|
+
|
|
155
292
|
/**
|
|
156
293
|
* Checks whether copper text glyph strokes are already in y-up scene space.
|
|
157
294
|
* @param {{ coordinateSystem?: string } | undefined} options
|
|
@@ -164,13 +301,21 @@ export class PcbScene3dCopperFactory {
|
|
|
164
301
|
/**
|
|
165
302
|
* Builds one widened copper-track mesh for one face.
|
|
166
303
|
* @param {any} THREE
|
|
167
|
-
* @param {{ x1?: number, y1?: number, x2?: number, y2?: number, width?: number }[]} tracks
|
|
304
|
+
* @param {{ x1?: number, y1?: number, x2?: number, y2?: number, width?: number, capStartRound?: boolean, capEndRound?: boolean, capStartSideWall?: boolean, capEndSideWall?: boolean }[]} tracks
|
|
168
305
|
* @param {number} z
|
|
169
306
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
170
307
|
* @param {boolean} mirrorY
|
|
308
|
+
* @param {any | null} [material] Optional material override.
|
|
171
309
|
* @returns {any | null}
|
|
172
310
|
*/
|
|
173
|
-
static #buildTrackMesh(
|
|
311
|
+
static #buildTrackMesh(
|
|
312
|
+
THREE,
|
|
313
|
+
tracks,
|
|
314
|
+
z,
|
|
315
|
+
normalizeBoardPoint,
|
|
316
|
+
mirrorY,
|
|
317
|
+
material = null
|
|
318
|
+
) {
|
|
174
319
|
const positions = []
|
|
175
320
|
|
|
176
321
|
for (const track of tracks) {
|
|
@@ -191,11 +336,16 @@ export class PcbScene3dCopperFactory {
|
|
|
191
336
|
start,
|
|
192
337
|
end,
|
|
193
338
|
Number(track?.width || 0),
|
|
194
|
-
z
|
|
339
|
+
z,
|
|
340
|
+
track
|
|
195
341
|
)
|
|
196
342
|
}
|
|
197
343
|
|
|
198
|
-
return PcbScene3dCopperFactory.#buildStrokeMesh(
|
|
344
|
+
return PcbScene3dCopperFactory.#buildStrokeMesh(
|
|
345
|
+
THREE,
|
|
346
|
+
positions,
|
|
347
|
+
material
|
|
348
|
+
)
|
|
199
349
|
}
|
|
200
350
|
|
|
201
351
|
/**
|
|
@@ -205,9 +355,17 @@ export class PcbScene3dCopperFactory {
|
|
|
205
355
|
* @param {number} z
|
|
206
356
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
207
357
|
* @param {boolean} mirrorY
|
|
358
|
+
* @param {any | null} [material] Optional material override.
|
|
208
359
|
* @returns {any | null}
|
|
209
360
|
*/
|
|
210
|
-
static #buildArcMesh(
|
|
361
|
+
static #buildArcMesh(
|
|
362
|
+
THREE,
|
|
363
|
+
arcs,
|
|
364
|
+
z,
|
|
365
|
+
normalizeBoardPoint,
|
|
366
|
+
mirrorY,
|
|
367
|
+
material = null
|
|
368
|
+
) {
|
|
211
369
|
const positions = []
|
|
212
370
|
|
|
213
371
|
for (const arc of arcs) {
|
|
@@ -226,16 +384,21 @@ export class PcbScene3dCopperFactory {
|
|
|
226
384
|
)
|
|
227
385
|
}
|
|
228
386
|
|
|
229
|
-
return PcbScene3dCopperFactory.#buildStrokeMesh(
|
|
387
|
+
return PcbScene3dCopperFactory.#buildStrokeMesh(
|
|
388
|
+
THREE,
|
|
389
|
+
positions,
|
|
390
|
+
material
|
|
391
|
+
)
|
|
230
392
|
}
|
|
231
393
|
|
|
232
394
|
/**
|
|
233
395
|
* Builds one copper stroke mesh from triangle positions.
|
|
234
396
|
* @param {any} THREE
|
|
235
397
|
* @param {number[]} positions
|
|
398
|
+
* @param {any | null} [material] Optional material override.
|
|
236
399
|
* @returns {any | null}
|
|
237
400
|
*/
|
|
238
|
-
static #buildStrokeMesh(THREE, positions) {
|
|
401
|
+
static #buildStrokeMesh(THREE, positions, material = null) {
|
|
239
402
|
if (!positions.length) {
|
|
240
403
|
return null
|
|
241
404
|
}
|
|
@@ -249,7 +412,7 @@ export class PcbScene3dCopperFactory {
|
|
|
249
412
|
|
|
250
413
|
return new THREE.Mesh(
|
|
251
414
|
geometry,
|
|
252
|
-
PcbScene3dCopperFactory.#buildMaterial(THREE)
|
|
415
|
+
material || PcbScene3dCopperFactory.#buildMaterial(THREE)
|
|
253
416
|
)
|
|
254
417
|
}
|
|
255
418
|
|
|
@@ -260,7 +423,7 @@ export class PcbScene3dCopperFactory {
|
|
|
260
423
|
*/
|
|
261
424
|
static #buildMaterial(THREE) {
|
|
262
425
|
return new THREE.MeshStandardMaterial({
|
|
263
|
-
color:
|
|
426
|
+
color: PcbScene3dCopperFactory.#COPPER_COLOR,
|
|
264
427
|
roughness: 0.38,
|
|
265
428
|
metalness: 0.55,
|
|
266
429
|
side: THREE.DoubleSide,
|
|
@@ -318,9 +481,17 @@ export class PcbScene3dCopperFactory {
|
|
|
318
481
|
* @param {{ x: number, y: number }} end
|
|
319
482
|
* @param {number} width
|
|
320
483
|
* @param {number} z
|
|
484
|
+
* @param {{ capStartRound?: boolean, capEndRound?: boolean, capStartSideWall?: boolean, capEndSideWall?: boolean }} [options]
|
|
321
485
|
* @returns {void}
|
|
322
486
|
*/
|
|
323
|
-
static #appendTrackTriangles(
|
|
487
|
+
static #appendTrackTriangles(
|
|
488
|
+
positions,
|
|
489
|
+
start,
|
|
490
|
+
end,
|
|
491
|
+
width,
|
|
492
|
+
z,
|
|
493
|
+
options = {}
|
|
494
|
+
) {
|
|
324
495
|
const dx = end.x - start.x
|
|
325
496
|
const dy = end.y - start.y
|
|
326
497
|
const length = Math.hypot(dx, dy)
|
|
@@ -352,25 +523,47 @@ export class PcbScene3dCopperFactory {
|
|
|
352
523
|
{ x: start.x - normalX, y: start.y - normalY },
|
|
353
524
|
z
|
|
354
525
|
)
|
|
355
|
-
PcbScene3dCopperFactory.#
|
|
526
|
+
PcbScene3dCopperFactory.#appendBoundarySideTriangles(
|
|
356
527
|
positions,
|
|
357
|
-
start,
|
|
358
|
-
|
|
528
|
+
{ x: start.x + normalX, y: start.y + normalY },
|
|
529
|
+
{ x: end.x + normalX, y: end.y + normalY },
|
|
359
530
|
z
|
|
360
531
|
)
|
|
361
|
-
PcbScene3dCopperFactory.#
|
|
532
|
+
PcbScene3dCopperFactory.#appendBoundarySideTriangles(
|
|
362
533
|
positions,
|
|
363
|
-
end,
|
|
364
|
-
|
|
534
|
+
{ x: end.x - normalX, y: end.y - normalY },
|
|
535
|
+
{ x: start.x - normalX, y: start.y - normalY },
|
|
365
536
|
z
|
|
366
537
|
)
|
|
538
|
+
if (options?.capStartRound !== false) {
|
|
539
|
+
PcbScene3dCopperFactory.#appendRoundCapTriangles(
|
|
540
|
+
positions,
|
|
541
|
+
start,
|
|
542
|
+
halfWidth,
|
|
543
|
+
z,
|
|
544
|
+
-dx / length,
|
|
545
|
+
-dy / length,
|
|
546
|
+
options?.capStartSideWall !== false
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
if (options?.capEndRound !== false) {
|
|
550
|
+
PcbScene3dCopperFactory.#appendRoundCapTriangles(
|
|
551
|
+
positions,
|
|
552
|
+
end,
|
|
553
|
+
halfWidth,
|
|
554
|
+
z,
|
|
555
|
+
dx / length,
|
|
556
|
+
dy / length,
|
|
557
|
+
options?.capEndSideWall !== false
|
|
558
|
+
)
|
|
559
|
+
}
|
|
367
560
|
}
|
|
368
561
|
|
|
369
562
|
/**
|
|
370
563
|
* Appends one widened arc band as triangles.
|
|
371
564
|
* @param {number[]} positions
|
|
372
565
|
* @param {{ x: number, y: number }} center
|
|
373
|
-
* @param {{ radius?: number, width?: number, startAngle?: number, endAngle?: number }} arc
|
|
566
|
+
* @param {{ radius?: number, width?: number, startAngle?: number, endAngle?: number, sweepAngle?: number }} arc
|
|
374
567
|
* @param {number} z
|
|
375
568
|
* @param {boolean} mirrorY
|
|
376
569
|
* @returns {void}
|
|
@@ -381,10 +574,7 @@ export class PcbScene3dCopperFactory {
|
|
|
381
574
|
const outerRadius = radius + strokeWidth / 2
|
|
382
575
|
const innerRadius = Math.max(radius - strokeWidth / 2, 0)
|
|
383
576
|
const startAngleRad = (Number(arc?.startAngle || 0) * Math.PI) / 180
|
|
384
|
-
const deltaAngleDeg = PcbScene3dArcUtils.
|
|
385
|
-
Number(arc?.startAngle || 0),
|
|
386
|
-
Number(arc?.endAngle || 0)
|
|
387
|
-
)
|
|
577
|
+
const deltaAngleDeg = PcbScene3dArcUtils.resolveArcSweepDelta(arc)
|
|
388
578
|
const isFullCircle =
|
|
389
579
|
Math.abs(deltaAngleDeg) <=
|
|
390
580
|
PcbScene3dCopperFactory.#FULL_CIRCLE_EPSILON ||
|
|
@@ -395,7 +585,10 @@ export class PcbScene3dCopperFactory {
|
|
|
395
585
|
: (deltaAngleDeg * Math.PI) / 180
|
|
396
586
|
const segments = Math.max(
|
|
397
587
|
isFullCircle ? 20 : 8,
|
|
398
|
-
Math.ceil(
|
|
588
|
+
Math.ceil(
|
|
589
|
+
Math.abs(deltaAngleDeg) /
|
|
590
|
+
PcbScene3dCopperFactory.#ARC_SEGMENT_DEGREES
|
|
591
|
+
)
|
|
399
592
|
)
|
|
400
593
|
const yDirection = mirrorY ? -1 : 1
|
|
401
594
|
|
|
@@ -421,6 +614,12 @@ export class PcbScene3dCopperFactory {
|
|
|
421
614
|
outerEnd,
|
|
422
615
|
z
|
|
423
616
|
)
|
|
617
|
+
PcbScene3dCopperFactory.#appendBoundarySideTriangles(
|
|
618
|
+
positions,
|
|
619
|
+
outerStart,
|
|
620
|
+
outerEnd,
|
|
621
|
+
z
|
|
622
|
+
)
|
|
424
623
|
continue
|
|
425
624
|
}
|
|
426
625
|
|
|
@@ -441,19 +640,45 @@ export class PcbScene3dCopperFactory {
|
|
|
441
640
|
innerStart,
|
|
442
641
|
z
|
|
443
642
|
)
|
|
643
|
+
PcbScene3dCopperFactory.#appendBoundarySideTriangles(
|
|
644
|
+
positions,
|
|
645
|
+
outerStart,
|
|
646
|
+
outerEnd,
|
|
647
|
+
z
|
|
648
|
+
)
|
|
649
|
+
PcbScene3dCopperFactory.#appendBoundarySideTriangles(
|
|
650
|
+
positions,
|
|
651
|
+
innerEnd,
|
|
652
|
+
innerStart,
|
|
653
|
+
z
|
|
654
|
+
)
|
|
444
655
|
}
|
|
445
656
|
|
|
446
657
|
if (!isFullCircle) {
|
|
447
|
-
|
|
658
|
+
const sweepDirection = deltaAngleRad < 0 ? -1 : 1
|
|
659
|
+
const startTangent = PcbScene3dCopperFactory.#resolveArcTangent(
|
|
660
|
+
startAngleRad,
|
|
661
|
+
yDirection,
|
|
662
|
+
sweepDirection
|
|
663
|
+
)
|
|
664
|
+
const endTangent = PcbScene3dCopperFactory.#resolveArcTangent(
|
|
665
|
+
startAngleRad + deltaAngleRad,
|
|
666
|
+
yDirection,
|
|
667
|
+
sweepDirection
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
PcbScene3dCopperFactory.#appendRoundCapTriangles(
|
|
448
671
|
positions,
|
|
449
672
|
{
|
|
450
673
|
x: center.x + Math.cos(startAngleRad) * radius,
|
|
451
674
|
y: center.y + Math.sin(startAngleRad) * radius * yDirection
|
|
452
675
|
},
|
|
453
676
|
strokeWidth / 2,
|
|
454
|
-
z
|
|
677
|
+
z,
|
|
678
|
+
-startTangent.x,
|
|
679
|
+
-startTangent.y
|
|
455
680
|
)
|
|
456
|
-
PcbScene3dCopperFactory.#
|
|
681
|
+
PcbScene3dCopperFactory.#appendRoundCapTriangles(
|
|
457
682
|
positions,
|
|
458
683
|
{
|
|
459
684
|
x:
|
|
@@ -466,7 +691,9 @@ export class PcbScene3dCopperFactory {
|
|
|
466
691
|
yDirection
|
|
467
692
|
},
|
|
468
693
|
strokeWidth / 2,
|
|
469
|
-
z
|
|
694
|
+
z,
|
|
695
|
+
endTangent.x,
|
|
696
|
+
endTangent.y
|
|
470
697
|
)
|
|
471
698
|
}
|
|
472
699
|
}
|
|
@@ -525,6 +752,153 @@ export class PcbScene3dCopperFactory {
|
|
|
525
752
|
},
|
|
526
753
|
z
|
|
527
754
|
)
|
|
755
|
+
PcbScene3dCopperFactory.#appendBoundarySideTriangles(
|
|
756
|
+
positions,
|
|
757
|
+
{
|
|
758
|
+
x: center.x + Math.cos(startAngle) * safeRadius,
|
|
759
|
+
y: center.y + Math.sin(startAngle) * safeRadius
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
x: center.x + Math.cos(endAngle) * safeRadius,
|
|
763
|
+
y: center.y + Math.sin(endAngle) * safeRadius
|
|
764
|
+
},
|
|
765
|
+
z
|
|
766
|
+
)
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Appends only the exposed half of one rounded stroke endpoint.
|
|
772
|
+
* @param {number[]} positions Position buffer.
|
|
773
|
+
* @param {{ x: number, y: number }} center Cap center.
|
|
774
|
+
* @param {number} radius Cap radius.
|
|
775
|
+
* @param {number} z Center Z position.
|
|
776
|
+
* @param {number} outwardX Unit X direction pointing out of the stroke.
|
|
777
|
+
* @param {number} outwardY Unit Y direction pointing out of the stroke.
|
|
778
|
+
* @param {boolean} [includeSideWall] Whether to emit the cap perimeter wall.
|
|
779
|
+
* @returns {void}
|
|
780
|
+
*/
|
|
781
|
+
static #appendRoundCapTriangles(
|
|
782
|
+
positions,
|
|
783
|
+
center,
|
|
784
|
+
radius,
|
|
785
|
+
z,
|
|
786
|
+
outwardX,
|
|
787
|
+
outwardY,
|
|
788
|
+
includeSideWall = true
|
|
789
|
+
) {
|
|
790
|
+
const safeRadius = Math.max(Number(radius || 0), 0)
|
|
791
|
+
const outwardLength = Math.hypot(outwardX, outwardY)
|
|
792
|
+
if (safeRadius <= 0) {
|
|
793
|
+
return
|
|
794
|
+
}
|
|
795
|
+
if (outwardLength <= 0.001) {
|
|
796
|
+
PcbScene3dCopperFactory.#appendDiscTriangles(
|
|
797
|
+
positions,
|
|
798
|
+
center,
|
|
799
|
+
safeRadius,
|
|
800
|
+
z
|
|
801
|
+
)
|
|
802
|
+
return
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const unitX = outwardX / outwardLength
|
|
806
|
+
const unitY = outwardY / outwardLength
|
|
807
|
+
const normalX = -unitY
|
|
808
|
+
const normalY = unitX
|
|
809
|
+
|
|
810
|
+
for (
|
|
811
|
+
let index = 0;
|
|
812
|
+
index < PcbScene3dCopperFactory.#ROUND_CAP_SEGMENTS;
|
|
813
|
+
index += 1
|
|
814
|
+
) {
|
|
815
|
+
const startAngle =
|
|
816
|
+
-Math.PI / 2 +
|
|
817
|
+
(Math.PI * index) / PcbScene3dCopperFactory.#ROUND_CAP_SEGMENTS
|
|
818
|
+
const endAngle =
|
|
819
|
+
-Math.PI / 2 +
|
|
820
|
+
(Math.PI * (index + 1)) /
|
|
821
|
+
PcbScene3dCopperFactory.#ROUND_CAP_SEGMENTS
|
|
822
|
+
const start = PcbScene3dCopperFactory.#resolveCapPoint(
|
|
823
|
+
center,
|
|
824
|
+
safeRadius,
|
|
825
|
+
unitX,
|
|
826
|
+
unitY,
|
|
827
|
+
normalX,
|
|
828
|
+
normalY,
|
|
829
|
+
startAngle
|
|
830
|
+
)
|
|
831
|
+
const end = PcbScene3dCopperFactory.#resolveCapPoint(
|
|
832
|
+
center,
|
|
833
|
+
safeRadius,
|
|
834
|
+
unitX,
|
|
835
|
+
unitY,
|
|
836
|
+
normalX,
|
|
837
|
+
normalY,
|
|
838
|
+
endAngle
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
PcbScene3dCopperFactory.#appendTriangle(
|
|
842
|
+
positions,
|
|
843
|
+
center,
|
|
844
|
+
start,
|
|
845
|
+
end,
|
|
846
|
+
z
|
|
847
|
+
)
|
|
848
|
+
if (includeSideWall) {
|
|
849
|
+
PcbScene3dCopperFactory.#appendBoundarySideTriangles(
|
|
850
|
+
positions,
|
|
851
|
+
start,
|
|
852
|
+
end,
|
|
853
|
+
z
|
|
854
|
+
)
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Resolves one point on an oriented round cap boundary.
|
|
861
|
+
* @param {{ x: number, y: number }} center Cap center.
|
|
862
|
+
* @param {number} radius Cap radius.
|
|
863
|
+
* @param {number} unitX Outward X direction.
|
|
864
|
+
* @param {number} unitY Outward Y direction.
|
|
865
|
+
* @param {number} normalX Cap normal X direction.
|
|
866
|
+
* @param {number} normalY Cap normal Y direction.
|
|
867
|
+
* @param {number} angle Local cap angle.
|
|
868
|
+
* @returns {{ x: number, y: number }}
|
|
869
|
+
*/
|
|
870
|
+
static #resolveCapPoint(
|
|
871
|
+
center,
|
|
872
|
+
radius,
|
|
873
|
+
unitX,
|
|
874
|
+
unitY,
|
|
875
|
+
normalX,
|
|
876
|
+
normalY,
|
|
877
|
+
angle
|
|
878
|
+
) {
|
|
879
|
+
return {
|
|
880
|
+
x:
|
|
881
|
+
center.x +
|
|
882
|
+
unitX * Math.cos(angle) * radius +
|
|
883
|
+
normalX * Math.sin(angle) * radius,
|
|
884
|
+
y:
|
|
885
|
+
center.y +
|
|
886
|
+
unitY * Math.cos(angle) * radius +
|
|
887
|
+
normalY * Math.sin(angle) * radius
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Resolves one normalized centerline tangent for an arc endpoint.
|
|
893
|
+
* @param {number} angle Endpoint angle in radians.
|
|
894
|
+
* @param {number} yDirection Mirrored Y direction.
|
|
895
|
+
* @param {number} sweepDirection Arc sweep direction.
|
|
896
|
+
* @returns {{ x: number, y: number }}
|
|
897
|
+
*/
|
|
898
|
+
static #resolveArcTangent(angle, yDirection, sweepDirection) {
|
|
899
|
+
return {
|
|
900
|
+
x: -Math.sin(angle) * sweepDirection,
|
|
901
|
+
y: Math.cos(angle) * yDirection * sweepDirection
|
|
528
902
|
}
|
|
529
903
|
}
|
|
530
904
|
|
|
@@ -548,15 +922,73 @@ export class PcbScene3dCopperFactory {
|
|
|
548
922
|
}
|
|
549
923
|
|
|
550
924
|
/**
|
|
551
|
-
* Appends one
|
|
552
|
-
* @param {number[]} positions
|
|
553
|
-
* @param {{ x: number, y: number }} a
|
|
554
|
-
* @param {{ x: number, y: number }} b
|
|
555
|
-
* @param {{ x: number, y: number }} c
|
|
556
|
-
* @param {number} z
|
|
925
|
+
* Appends one shallow triangular prism into the position buffer.
|
|
926
|
+
* @param {number[]} positions Position buffer.
|
|
927
|
+
* @param {{ x: number, y: number }} a First point.
|
|
928
|
+
* @param {{ x: number, y: number }} b Second point.
|
|
929
|
+
* @param {{ x: number, y: number }} c Third point.
|
|
930
|
+
* @param {number} z Center Z position.
|
|
557
931
|
* @returns {void}
|
|
558
932
|
*/
|
|
559
933
|
static #appendTriangle(positions, a, b, c, z) {
|
|
560
|
-
|
|
934
|
+
const halfThickness = PcbScene3dCopperFactory.#COPPER_THICKNESS_MIL / 2
|
|
935
|
+
const topZ = z + halfThickness
|
|
936
|
+
const bottomZ = z - halfThickness
|
|
937
|
+
|
|
938
|
+
positions.push(
|
|
939
|
+
a.x,
|
|
940
|
+
a.y,
|
|
941
|
+
topZ,
|
|
942
|
+
b.x,
|
|
943
|
+
b.y,
|
|
944
|
+
topZ,
|
|
945
|
+
c.x,
|
|
946
|
+
c.y,
|
|
947
|
+
topZ,
|
|
948
|
+
c.x,
|
|
949
|
+
c.y,
|
|
950
|
+
bottomZ,
|
|
951
|
+
b.x,
|
|
952
|
+
b.y,
|
|
953
|
+
bottomZ,
|
|
954
|
+
a.x,
|
|
955
|
+
a.y,
|
|
956
|
+
bottomZ
|
|
957
|
+
)
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Appends a side wall for one actual copper boundary edge.
|
|
962
|
+
* @param {number[]} positions Position buffer.
|
|
963
|
+
* @param {{ x: number, y: number }} start Wall start point.
|
|
964
|
+
* @param {{ x: number, y: number }} end Wall end point.
|
|
965
|
+
* @param {number} z Center Z position.
|
|
966
|
+
* @returns {void}
|
|
967
|
+
*/
|
|
968
|
+
static #appendBoundarySideTriangles(positions, start, end, z) {
|
|
969
|
+
const halfThickness = PcbScene3dCopperFactory.#COPPER_THICKNESS_MIL / 2
|
|
970
|
+
const topZ = z + halfThickness
|
|
971
|
+
const bottomZ = z - halfThickness
|
|
972
|
+
|
|
973
|
+
positions.push(
|
|
974
|
+
start.x,
|
|
975
|
+
start.y,
|
|
976
|
+
topZ,
|
|
977
|
+
end.x,
|
|
978
|
+
end.y,
|
|
979
|
+
topZ,
|
|
980
|
+
end.x,
|
|
981
|
+
end.y,
|
|
982
|
+
bottomZ,
|
|
983
|
+
start.x,
|
|
984
|
+
start.y,
|
|
985
|
+
topZ,
|
|
986
|
+
end.x,
|
|
987
|
+
end.y,
|
|
988
|
+
bottomZ,
|
|
989
|
+
start.x,
|
|
990
|
+
start.y,
|
|
991
|
+
bottomZ
|
|
992
|
+
)
|
|
561
993
|
}
|
|
562
994
|
}
|
|
@@ -17,7 +17,7 @@ export class PcbScene3dCopperTextFactory {
|
|
|
17
17
|
* @param {any[]} texts
|
|
18
18
|
* @param {number} z
|
|
19
19
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
20
|
-
* @param {{ side?: 'top' | 'bottom', mirrorY?: boolean, materialColor?: number, filterSide?: boolean, glyphYUp?: boolean, drillCutouts?: { x: number, y: number }[][] }} [options]
|
|
20
|
+
* @param {{ side?: 'top' | 'bottom', mirrorY?: boolean, materialColor?: number, materialProperties?: { roughness?: number, metalness?: number }, filterSide?: boolean, glyphYUp?: boolean, drillCutouts?: { x: number, y: number }[][] }} [options]
|
|
21
21
|
* @returns {any}
|
|
22
22
|
*/
|
|
23
23
|
static buildGroup(THREE, texts, z, normalizeBoardPoint, options = {}) {
|
|
@@ -74,7 +74,8 @@ export class PcbScene3dCopperTextFactory {
|
|
|
74
74
|
THREE,
|
|
75
75
|
PcbScene3dCopperTextFactory.#resolveMaterialColor(
|
|
76
76
|
options?.materialColor
|
|
77
|
-
)
|
|
77
|
+
),
|
|
78
|
+
options?.materialProperties
|
|
78
79
|
)
|
|
79
80
|
)
|
|
80
81
|
mesh.name = 'copper-text'
|
|
@@ -471,13 +472,15 @@ export class PcbScene3dCopperTextFactory {
|
|
|
471
472
|
* Builds the shared copper text material.
|
|
472
473
|
* @param {any} THREE
|
|
473
474
|
* @param {number} color
|
|
475
|
+
* @param {{ roughness?: number, metalness?: number }} [materialProperties]
|
|
474
476
|
* @returns {any}
|
|
475
477
|
*/
|
|
476
|
-
static #buildMaterial(THREE, color) {
|
|
478
|
+
static #buildMaterial(THREE, color, materialProperties = {}) {
|
|
477
479
|
return new THREE.MeshStandardMaterial({
|
|
478
480
|
color,
|
|
479
481
|
roughness: 0.38,
|
|
480
482
|
metalness: 0.55,
|
|
483
|
+
...materialProperties,
|
|
481
484
|
side: THREE.DoubleSide
|
|
482
485
|
})
|
|
483
486
|
}
|