pcb-scene3d-viewer 1.2.2 → 1.3.0
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 +7 -0
- package/docs/api.md +18 -0
- package/docs/circuitjson.md +6 -4
- package/docs/release-notes-v1.3.0.md +38 -0
- package/package.json +3 -2
- package/src/CircuitJsonCadModelAssetResolver.mjs +24 -10
- package/src/PcbAssemblyGltfModelMeshParser.mjs +3 -1
- package/src/PcbAssemblyModelMeshLoader.mjs +1 -1
- package/src/PcbAssemblyTextModelMeshParser.mjs +3 -1
- package/src/PcbScene3dBoardAssemblyPresentation.mjs +1 -11
- package/src/PcbScene3dBoardMaterialPalette.mjs +12 -0
- package/src/PcbScene3dCircuitJsonAdapter.mjs +33 -85
- package/src/PcbScene3dCircuitJsonCopperTextBuilder.mjs +385 -0
- package/src/PcbScene3dCircuitJsonDocumentationArtworkBuilder.mjs +123 -22
- package/src/PcbScene3dCircuitJsonGeometry.mjs +12 -0
- package/src/PcbScene3dCircuitJsonPadCorner.mjs +72 -0
- package/src/PcbScene3dCircuitJsonSilkscreenBuilder.mjs +140 -25
- package/src/PcbScene3dCircuitJsonSilkscreenDetailBuilder.mjs +21 -0
- package/src/PcbScene3dCircuitJsonSourceLayer.mjs +124 -0
- package/src/PcbScene3dCircuitJsonTraceRouteBuilder.mjs +6 -7
- package/src/PcbScene3dCopperDetailFilter.mjs +5 -1
- package/src/PcbScene3dCopperFactory.mjs +23 -4
- package/src/PcbScene3dCopperTextFactory.mjs +105 -20
- package/src/PcbScene3dExternalModels.mjs +26 -0
- package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +16 -1
- package/src/PcbScene3dModelContent.mjs +32 -1
- package/src/PcbScene3dRuntimeBoardMeshes.mjs +2 -4
- package/src/PcbScene3dSilkscreenCopperCutoutBuilder.mjs +588 -0
- package/src/PcbScene3dStepLoader.mjs +2 -1
- package/src/PcbScene3dStrokeCutoutBuilder.mjs +98 -0
- package/src/PcbScene3dViaFactory.mjs +51 -5
- package/src/PcbScene3dViaLayerSpan.mjs +126 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PcbScene3dStrokeFont } from './PcbScene3dStrokeFont.mjs'
|
|
2
2
|
import { PcbScene3dCutoutGeometryFilter } from './PcbScene3dCutoutGeometryFilter.mjs'
|
|
3
3
|
import { PcbScene3dStrokeGeometryBuilder } from './PcbScene3dStrokeGeometryBuilder.mjs'
|
|
4
|
+
import { PcbScene3dStrokeCutoutBuilder } from './PcbScene3dStrokeCutoutBuilder.mjs'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Builds KiCad copper text as widened stroke meshes for the 3D PCB scene.
|
|
@@ -11,6 +12,33 @@ export class PcbScene3dCopperTextFactory {
|
|
|
11
12
|
static #FIRST_LINE_HEIGHT_RATIO = 1.17
|
|
12
13
|
static #STROKE_BASELINE_FUDGE_RATIO = 0.052
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Builds exact round-capped stroke polygons for one copper text primitive.
|
|
17
|
+
* @param {object} text Copper text primitive.
|
|
18
|
+
* @param {{ glyphYUp?: boolean, alignmentStrokeWidth?: number }} [options] Text geometry options. `alignmentStrokeWidth` keeps paired text with different stroke widths on one source-authored anchor.
|
|
19
|
+
* @returns {{ x: number, y: number }[][]}
|
|
20
|
+
*/
|
|
21
|
+
static strokeCutouts(text, options = {}) {
|
|
22
|
+
const width = PcbScene3dCopperTextFactory.#textStrokeWidth(text)
|
|
23
|
+
return PcbScene3dCopperTextFactory.#textStrokes(
|
|
24
|
+
text,
|
|
25
|
+
Boolean(options?.glyphYUp),
|
|
26
|
+
options?.alignmentStrokeWidth
|
|
27
|
+
).flatMap((stroke) => {
|
|
28
|
+
const cutouts = []
|
|
29
|
+
for (let index = 1; index < stroke.length; index += 1) {
|
|
30
|
+
const cutout = PcbScene3dStrokeCutoutBuilder.build(
|
|
31
|
+
stroke[index - 1],
|
|
32
|
+
stroke[index],
|
|
33
|
+
width,
|
|
34
|
+
{ minWidth: 1 }
|
|
35
|
+
)
|
|
36
|
+
if (cutout.length >= 3) cutouts.push(cutout)
|
|
37
|
+
}
|
|
38
|
+
return cutouts
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
14
42
|
/**
|
|
15
43
|
* Builds one side-specific copper text group.
|
|
16
44
|
* @param {any} THREE
|
|
@@ -171,9 +199,10 @@ export class PcbScene3dCopperTextFactory {
|
|
|
171
199
|
* Builds all KiCad stroke-font point lists for one text primitive.
|
|
172
200
|
* @param {object} text
|
|
173
201
|
* @param {boolean} glyphYUp
|
|
202
|
+
* @param {number | undefined} alignmentStrokeWidth Stroke width used only for anchor adjustment.
|
|
174
203
|
* @returns {{ x: number, y: number }[][]}
|
|
175
204
|
*/
|
|
176
|
-
static #textStrokes(text, glyphYUp) {
|
|
205
|
+
static #textStrokes(text, glyphYUp, alignmentStrokeWidth) {
|
|
177
206
|
const lines = String(text?.value ?? text?.text ?? '').split('\n')
|
|
178
207
|
const lineSpacing = PcbScene3dCopperTextFactory.#textLineSpacing(text)
|
|
179
208
|
|
|
@@ -184,7 +213,8 @@ export class PcbScene3dCopperTextFactory {
|
|
|
184
213
|
index,
|
|
185
214
|
lines.length,
|
|
186
215
|
lineSpacing,
|
|
187
|
-
glyphYUp
|
|
216
|
+
glyphYUp,
|
|
217
|
+
alignmentStrokeWidth
|
|
188
218
|
)
|
|
189
219
|
)
|
|
190
220
|
}
|
|
@@ -197,6 +227,7 @@ export class PcbScene3dCopperTextFactory {
|
|
|
197
227
|
* @param {number} lineCount
|
|
198
228
|
* @param {number} lineSpacing
|
|
199
229
|
* @param {boolean} glyphYUp
|
|
230
|
+
* @param {number | undefined} alignmentStrokeWidth Stroke width used only for anchor adjustment.
|
|
200
231
|
* @returns {{ x: number, y: number }[][]}
|
|
201
232
|
*/
|
|
202
233
|
static #textLineStrokes(
|
|
@@ -205,7 +236,8 @@ export class PcbScene3dCopperTextFactory {
|
|
|
205
236
|
index,
|
|
206
237
|
lineCount,
|
|
207
238
|
lineSpacing,
|
|
208
|
-
glyphYUp
|
|
239
|
+
glyphYUp,
|
|
240
|
+
alignmentStrokeWidth
|
|
209
241
|
) {
|
|
210
242
|
const sizeX = PcbScene3dCopperTextFactory.#textWidth(text)
|
|
211
243
|
const sizeY = PcbScene3dCopperTextFactory.#textHeight(text)
|
|
@@ -215,12 +247,17 @@ export class PcbScene3dCopperTextFactory {
|
|
|
215
247
|
sizeX,
|
|
216
248
|
sizeY
|
|
217
249
|
})
|
|
218
|
-
const x = PcbScene3dCopperTextFactory.#textLineX(
|
|
250
|
+
const x = PcbScene3dCopperTextFactory.#textLineX(
|
|
251
|
+
text,
|
|
252
|
+
layout.width,
|
|
253
|
+
alignmentStrokeWidth
|
|
254
|
+
)
|
|
219
255
|
const y = PcbScene3dCopperTextFactory.#textLineY(
|
|
220
256
|
text,
|
|
221
257
|
index,
|
|
222
258
|
lineCount,
|
|
223
|
-
lineSpacing
|
|
259
|
+
lineSpacing,
|
|
260
|
+
alignmentStrokeWidth
|
|
224
261
|
)
|
|
225
262
|
|
|
226
263
|
return layout.strokes.map((stroke) =>
|
|
@@ -293,11 +330,14 @@ export class PcbScene3dCopperTextFactory {
|
|
|
293
330
|
* Resolves line origin from KiCad horizontal justification.
|
|
294
331
|
* @param {object} text
|
|
295
332
|
* @param {number} lineWidth
|
|
333
|
+
* @param {number | undefined} alignmentStrokeWidth Stroke width used only for anchor adjustment.
|
|
296
334
|
* @returns {number}
|
|
297
335
|
*/
|
|
298
|
-
static #textLineX(text, lineWidth) {
|
|
299
|
-
const fudge =
|
|
300
|
-
|
|
336
|
+
static #textLineX(text, lineWidth, alignmentStrokeWidth) {
|
|
337
|
+
const fudge = PcbScene3dCopperTextFactory.#textStrokeHorizontalFudge(
|
|
338
|
+
text,
|
|
339
|
+
alignmentStrokeWidth
|
|
340
|
+
)
|
|
301
341
|
|
|
302
342
|
if (text?.hAlign === 'left') {
|
|
303
343
|
return Number(text?.x || 0) + fudge
|
|
@@ -316,9 +356,16 @@ export class PcbScene3dCopperTextFactory {
|
|
|
316
356
|
* @param {number} index
|
|
317
357
|
* @param {number} lineCount
|
|
318
358
|
* @param {number} lineSpacing
|
|
359
|
+
* @param {number | undefined} alignmentStrokeWidth Stroke width used only for anchor adjustment.
|
|
319
360
|
* @returns {number}
|
|
320
361
|
*/
|
|
321
|
-
static #textLineY(
|
|
362
|
+
static #textLineY(
|
|
363
|
+
text,
|
|
364
|
+
index,
|
|
365
|
+
lineCount,
|
|
366
|
+
lineSpacing,
|
|
367
|
+
alignmentStrokeWidth
|
|
368
|
+
) {
|
|
322
369
|
const height = PcbScene3dCopperTextFactory.#textHeight(text)
|
|
323
370
|
const blockHeight =
|
|
324
371
|
height * PcbScene3dCopperTextFactory.#FIRST_LINE_HEIGHT_RATIO +
|
|
@@ -326,7 +373,10 @@ export class PcbScene3dCopperTextFactory {
|
|
|
326
373
|
let baseline =
|
|
327
374
|
Number(text?.y || 0) +
|
|
328
375
|
height -
|
|
329
|
-
PcbScene3dCopperTextFactory.#textStrokeBaselineFudge(
|
|
376
|
+
PcbScene3dCopperTextFactory.#textStrokeBaselineFudge(
|
|
377
|
+
text,
|
|
378
|
+
alignmentStrokeWidth
|
|
379
|
+
)
|
|
330
380
|
|
|
331
381
|
if (text?.vAlign === 'bottom') {
|
|
332
382
|
baseline -= blockHeight
|
|
@@ -354,24 +404,46 @@ export class PcbScene3dCopperTextFactory {
|
|
|
354
404
|
/**
|
|
355
405
|
* Resolves KiCad's small horizontal text adjustment.
|
|
356
406
|
* @param {object} text
|
|
407
|
+
* @param {number | undefined} alignmentStrokeWidth Stroke width used only for anchor adjustment.
|
|
357
408
|
* @returns {number}
|
|
358
409
|
*/
|
|
359
|
-
static #textStrokeHorizontalFudge(text) {
|
|
360
|
-
return
|
|
410
|
+
static #textStrokeHorizontalFudge(text, alignmentStrokeWidth) {
|
|
411
|
+
return (
|
|
412
|
+
PcbScene3dCopperTextFactory.#alignmentStrokeWidth(
|
|
413
|
+
text,
|
|
414
|
+
alignmentStrokeWidth
|
|
415
|
+
) / 1.52
|
|
416
|
+
)
|
|
361
417
|
}
|
|
362
418
|
|
|
363
419
|
/**
|
|
364
420
|
* Resolves KiCad's small baseline text adjustment.
|
|
365
421
|
* @param {object} text
|
|
422
|
+
* @param {number | undefined} alignmentStrokeWidth Stroke width used only for anchor adjustment.
|
|
366
423
|
* @returns {number}
|
|
367
424
|
*/
|
|
368
|
-
static #textStrokeBaselineFudge(text) {
|
|
425
|
+
static #textStrokeBaselineFudge(text, alignmentStrokeWidth) {
|
|
369
426
|
return (
|
|
370
|
-
PcbScene3dCopperTextFactory.#
|
|
371
|
-
|
|
427
|
+
PcbScene3dCopperTextFactory.#alignmentStrokeWidth(
|
|
428
|
+
text,
|
|
429
|
+
alignmentStrokeWidth
|
|
430
|
+
) * PcbScene3dCopperTextFactory.#STROKE_BASELINE_FUDGE_RATIO
|
|
372
431
|
)
|
|
373
432
|
}
|
|
374
433
|
|
|
434
|
+
/**
|
|
435
|
+
* Resolves the stroke width used for source-anchor alignment adjustments.
|
|
436
|
+
* @param {object} text Copper text primitive.
|
|
437
|
+
* @param {number | undefined} alignmentStrokeWidth Optional paired-text reference width.
|
|
438
|
+
* @returns {number}
|
|
439
|
+
*/
|
|
440
|
+
static #alignmentStrokeWidth(text, alignmentStrokeWidth) {
|
|
441
|
+
const candidate = Number(alignmentStrokeWidth)
|
|
442
|
+
return Number.isFinite(candidate) && candidate > 0
|
|
443
|
+
? candidate
|
|
444
|
+
: PcbScene3dCopperTextFactory.#textStrokeWidth(text)
|
|
445
|
+
}
|
|
446
|
+
|
|
375
447
|
/**
|
|
376
448
|
* Applies KiCad text rotation and mirrored text transforms.
|
|
377
449
|
* @param {object} text
|
|
@@ -389,15 +461,15 @@ export class PcbScene3dCopperTextFactory {
|
|
|
389
461
|
: point
|
|
390
462
|
|
|
391
463
|
if (text?.mirrored) {
|
|
392
|
-
const
|
|
464
|
+
const mirroredPoint = PcbScene3dCopperTextFactory.#mirrorPointX(
|
|
393
465
|
sourcePoint,
|
|
466
|
+
origin
|
|
467
|
+
)
|
|
468
|
+
return PcbScene3dCopperTextFactory.#rotatePoint(
|
|
469
|
+
mirroredPoint,
|
|
394
470
|
origin,
|
|
395
471
|
Number(text?.rotation || 0)
|
|
396
472
|
)
|
|
397
|
-
return {
|
|
398
|
-
x: origin.x - (rotated.x - origin.x),
|
|
399
|
-
y: rotated.y
|
|
400
|
-
}
|
|
401
473
|
}
|
|
402
474
|
|
|
403
475
|
return PcbScene3dCopperTextFactory.#rotatePoint(
|
|
@@ -407,6 +479,19 @@ export class PcbScene3dCopperTextFactory {
|
|
|
407
479
|
)
|
|
408
480
|
}
|
|
409
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Mirrors one stroke-font point across the text anchor's local Y axis.
|
|
484
|
+
* @param {{ x: number, y: number }} point
|
|
485
|
+
* @param {{ x: number, y: number }} origin
|
|
486
|
+
* @returns {{ x: number, y: number }}
|
|
487
|
+
*/
|
|
488
|
+
static #mirrorPointX(point, origin) {
|
|
489
|
+
return {
|
|
490
|
+
x: origin.x - (Number(point?.x || 0) - origin.x),
|
|
491
|
+
y: Number(point?.y || 0)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
410
495
|
/**
|
|
411
496
|
* Mirrors one stroke-font point across the text anchor's local X axis.
|
|
412
497
|
* @param {{ x: number, y: number }} point
|
|
@@ -84,6 +84,15 @@ export class PcbScene3dExternalModels {
|
|
|
84
84
|
await PcbScene3dExternalModels.#yieldToMainThread()
|
|
85
85
|
}
|
|
86
86
|
} catch (error) {
|
|
87
|
+
if (
|
|
88
|
+
PcbScene3dExternalModels.#isDeferredContentError(
|
|
89
|
+
placement,
|
|
90
|
+
error,
|
|
91
|
+
modelLoaderOptions
|
|
92
|
+
)
|
|
93
|
+
) {
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
87
96
|
diagnostics.push(
|
|
88
97
|
'Could not load external model for ' +
|
|
89
98
|
String(placement?.designator || 'component') +
|
|
@@ -101,6 +110,23 @@ export class PcbScene3dExternalModels {
|
|
|
101
110
|
return diagnostics
|
|
102
111
|
}
|
|
103
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Returns whether a failure represents an intentionally deferred model.
|
|
115
|
+
* @param {object} placement External-model placement.
|
|
116
|
+
* @param {unknown} error Model loading failure.
|
|
117
|
+
* @param {object} modelLoaderOptions Scoped model loading options.
|
|
118
|
+
* @returns {boolean}
|
|
119
|
+
*/
|
|
120
|
+
static #isDeferredContentError(placement, error, modelLoaderOptions) {
|
|
121
|
+
const model = placement?.externalModel
|
|
122
|
+
return Boolean(
|
|
123
|
+
model &&
|
|
124
|
+
PcbScene3dModelContent.isUnavailableError(error) &&
|
|
125
|
+
!PcbScene3dModelContent.hasLocal(model) &&
|
|
126
|
+
!PcbScene3dModelContent.canFetch(modelLoaderOptions)
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
104
130
|
/**
|
|
105
131
|
* Applies the active view mirror compensation to loaded model geometry.
|
|
106
132
|
* @param {any} externalModelsGroup Root group containing placed models.
|
|
@@ -16,7 +16,7 @@ export class PcbScene3dMaskCoveredCopperSideGroupBuilder {
|
|
|
16
16
|
/**
|
|
17
17
|
* Builds one side group from prepared mask-covered copper meshes.
|
|
18
18
|
* @param {any} THREE Three.js namespace.
|
|
19
|
-
* @param {{ trackMesh?: any | null, arcMesh?: any | null, fillMesh?: any | null, z?: number, mirrorY?: boolean }} options
|
|
19
|
+
* @param {{ trackMesh?: any | null, arcMesh?: any | null, fillMesh?: any | null, textMesh?: any | null, z?: number, mirrorY?: boolean }} options
|
|
20
20
|
* @returns {any}
|
|
21
21
|
*/
|
|
22
22
|
static build(
|
|
@@ -25,6 +25,7 @@ export class PcbScene3dMaskCoveredCopperSideGroupBuilder {
|
|
|
25
25
|
trackMesh = null,
|
|
26
26
|
arcMesh = null,
|
|
27
27
|
fillMesh = null,
|
|
28
|
+
textMesh = null,
|
|
28
29
|
z = 0,
|
|
29
30
|
mirrorY = false
|
|
30
31
|
} = {}
|
|
@@ -87,6 +88,20 @@ export class PcbScene3dMaskCoveredCopperSideGroupBuilder {
|
|
|
87
88
|
.#FILL_RENDER_ORDER
|
|
88
89
|
}
|
|
89
90
|
)
|
|
91
|
+
PcbScene3dMaskCoveredCopperSideGroupBuilder.#addCompressedMesh(
|
|
92
|
+
group,
|
|
93
|
+
textMesh,
|
|
94
|
+
'mask-covered-copper-text',
|
|
95
|
+
z,
|
|
96
|
+
{
|
|
97
|
+
copperBlend:
|
|
98
|
+
PcbScene3dMaskCoveredCopperSideGroupBuilder
|
|
99
|
+
.#TRACK_COPPER_BLEND,
|
|
100
|
+
renderOrder:
|
|
101
|
+
PcbScene3dMaskCoveredCopperSideGroupBuilder
|
|
102
|
+
.#TRACK_RENDER_ORDER
|
|
103
|
+
}
|
|
104
|
+
)
|
|
90
105
|
|
|
91
106
|
if (mirrorY && group.children.length) {
|
|
92
107
|
group.rotation.x = Math.PI
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { PcbScene3dModelIdentity } from './PcbScene3dModelIdentity.mjs'
|
|
2
2
|
import { PcbScene3dModelFetchPolicy } from './PcbScene3dModelFetchPolicy.mjs'
|
|
3
3
|
|
|
4
|
+
const CONTENT_UNAVAILABLE_ERROR = 'ERR_MODEL_CONTENT_UNAVAILABLE'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Reads external-model payloads through one explicit local/network policy.
|
|
6
8
|
*/
|
|
@@ -23,6 +25,35 @@ export class PcbScene3dModelContent {
|
|
|
23
25
|
return PcbScene3dModelFetchPolicy.canFetch(options)
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Creates a typed missing-content error for deferred model references.
|
|
30
|
+
* @param {string} [label] Human-readable format label.
|
|
31
|
+
* @returns {Error}
|
|
32
|
+
*/
|
|
33
|
+
static unavailableError(label = 'Model') {
|
|
34
|
+
const error = new Error(label + ' model content is not available.')
|
|
35
|
+
Object.defineProperty(error, 'code', {
|
|
36
|
+
configurable: false,
|
|
37
|
+
enumerable: true,
|
|
38
|
+
value: CONTENT_UNAVAILABLE_ERROR,
|
|
39
|
+
writable: false
|
|
40
|
+
})
|
|
41
|
+
return error
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns whether a failure is the typed missing-content condition.
|
|
46
|
+
* @param {unknown} error Candidate failure.
|
|
47
|
+
* @returns {boolean}
|
|
48
|
+
*/
|
|
49
|
+
static isUnavailableError(error) {
|
|
50
|
+
try {
|
|
51
|
+
return error?.code === CONTENT_UNAVAILABLE_ERROR
|
|
52
|
+
} catch {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
26
57
|
/**
|
|
27
58
|
* Reads one model as bytes from text, binary data, files, or opted-in URLs.
|
|
28
59
|
* @param {unknown} model External model metadata.
|
|
@@ -155,7 +186,7 @@ export class PcbScene3dModelContent {
|
|
|
155
186
|
''
|
|
156
187
|
).trim()
|
|
157
188
|
if (!url || !PcbScene3dModelFetchPolicy.canFetch(options)) {
|
|
158
|
-
throw
|
|
189
|
+
throw PcbScene3dModelContent.unavailableError(label)
|
|
159
190
|
}
|
|
160
191
|
|
|
161
192
|
const cache =
|
|
@@ -124,10 +124,8 @@ export class PcbScene3dRuntimeBoardMeshes {
|
|
|
124
124
|
metalness: 0.08,
|
|
125
125
|
visible: generatedBodyVisible
|
|
126
126
|
}
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
? edgeColor
|
|
130
|
-
: 0xc9ca78
|
|
127
|
+
const resolvedEdgeColor =
|
|
128
|
+
PcbScene3dBoardMaterialPalette.resolveEdgeColor(board)
|
|
131
129
|
const surfaceColor = hasBoardAssemblyModel
|
|
132
130
|
? resolvedEdgeColor
|
|
133
131
|
: PcbScene3dBoardMaterialPalette.resolveBoardSurfaceColor(board, {
|