pcb-scene3d-viewer 1.1.11 → 1.1.18

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pcb-scene3d-viewer",
3
- "version": "1.1.11",
3
+ "version": "1.1.18",
4
4
  "description": "Reusable Three.js PCB 3D scene viewer for normalized ECAD and CircuitJSON scene descriptions",
5
5
  "keywords": [
6
6
  "pcb",
@@ -29,7 +29,10 @@ export class PcbScene3dPadFactory {
29
29
  color: 0xd9a61d,
30
30
  roughness: 0.38,
31
31
  metalness: 0.55,
32
- side: THREE.DoubleSide
32
+ side: THREE.DoubleSide,
33
+ polygonOffset: true,
34
+ polygonOffsetFactor: -2,
35
+ polygonOffsetUnits: -2
33
36
  })
34
37
  const geometryCache = new Map()
35
38
  const side = PcbScene3dPadFactory.#normalizeSide(options?.side)
@@ -500,7 +503,7 @@ export class PcbScene3dPadFactory {
500
503
  * @returns {{ shape: any, shapeHoleCutouts: { x: number, y: number, diameter: number, slotLength?: number | null, rotationDeg?: number | null }[], clippingCutouts: { x: number, y: number }[][] }}
501
504
  */
502
505
  static #buildShapeCutoutPlan(THREE, spec, drillCutouts) {
503
- const baseShape = PcbScene3dPadFactory.#buildOuterShape(THREE, spec)
506
+ let baseShape = PcbScene3dPadFactory.#buildOuterShape(THREE, spec)
504
507
  if (!drillCutouts.length) {
505
508
  return {
506
509
  shape: baseShape,
@@ -509,7 +512,7 @@ export class PcbScene3dPadFactory {
509
512
  }
510
513
  }
511
514
 
512
- const contourPoints =
515
+ let contourPoints =
513
516
  PcbScene3dBoardEdgeCutoutBuilder.resolveShapePoints(baseShape)
514
517
  const shapeHoleCutouts = []
515
518
  const clippingCutouts = []
@@ -529,6 +532,30 @@ export class PcbScene3dPadFactory {
529
532
  continue
530
533
  }
531
534
 
535
+ const circularCutout =
536
+ PcbScene3dPadFactory.#resolveCircularDrillCutout(drillCutout)
537
+ if (circularCutout) {
538
+ const notchedContour =
539
+ PcbScene3dBoardEdgeCutoutBuilder.applyCircularEdgeCutouts(
540
+ contourPoints,
541
+ [circularCutout]
542
+ )
543
+ if (
544
+ !PcbScene3dPadFactory.#sameContourPoints(
545
+ contourPoints,
546
+ notchedContour
547
+ )
548
+ ) {
549
+ contourPoints = notchedContour
550
+ baseShape =
551
+ PcbScene3dBoardEdgeCutoutBuilder.buildShapeFromPoints(
552
+ THREE,
553
+ contourPoints
554
+ )
555
+ continue
556
+ }
557
+ }
558
+
532
559
  clippingCutouts.push(cutoutPoints)
533
560
  }
534
561
 
@@ -559,6 +586,30 @@ export class PcbScene3dPadFactory {
559
586
  }
560
587
  }
561
588
 
589
+ /**
590
+ * Checks whether two sampled contours have the same vertices.
591
+ * @param {{ x: number, y: number }[]} first
592
+ * @param {{ x: number, y: number }[]} second
593
+ * @returns {boolean}
594
+ */
595
+ static #sameContourPoints(first, second) {
596
+ if (!Array.isArray(first) || !Array.isArray(second)) {
597
+ return false
598
+ }
599
+
600
+ if (first.length !== second.length) {
601
+ return false
602
+ }
603
+
604
+ return first.every(
605
+ (point, index) =>
606
+ Math.hypot(
607
+ Number(point?.x || 0) - Number(second[index]?.x || 0),
608
+ Number(point?.y || 0) - Number(second[index]?.y || 0)
609
+ ) <= 0.001
610
+ )
611
+ }
612
+
562
613
  /**
563
614
  * Resolves polygon points for one local drill cutout.
564
615
  * @param {any} THREE
@@ -3,13 +3,15 @@
3
3
  */
4
4
  export class PcbScene3dPadSurfaceStack {
5
5
  static #COPLANAR_PAD_LIFT_MIL = 0.04
6
+ static #SURFACE_Z_PRIORITY_DEFAULT = 0
7
+ static #SURFACE_Z_PRIORITY_DRILLED = 1
6
8
 
7
9
  /**
8
10
  * Resolves a small lift for a pad face that overlaps previous pad faces.
9
- * @param {{ bounds: { minX: number, maxX: number, minY: number, maxY: number }, zIndex: number }[]} stack
11
+ * @param {{ bounds: { minX: number, maxX: number, minY: number, maxY: number }, zIndex: number, priority?: number }[]} stack
10
12
  * @param {{ x: number, y: number }} point Normalized pad anchor.
11
- * @param {{ rotation?: number | null }} pad Source pad.
12
- * @param {{ width: number, height: number, kind: 'circle' | 'rect' | 'rounded-rect', radius: number, offsetX: number, offsetY: number }} spec Pad face spec.
13
+ * @param {{ rotation?: number | null, holeDiameter?: number | null }} pad Source pad.
14
+ * @param {{ width: number, height: number, kind: 'circle' | 'rect' | 'rounded-rect', radius: number, offsetX: number, offsetY: number, hasHole?: boolean, holeDiameter?: number | null }} spec Pad face spec.
13
15
  * @param {boolean} mirrorY Whether underside coordinates are mirrored.
14
16
  * @returns {number}
15
17
  */
@@ -20,23 +22,50 @@ export class PcbScene3dPadSurfaceStack {
20
22
  spec,
21
23
  mirrorY
22
24
  )
23
- let zIndex = 0
25
+ const priority = PcbScene3dPadSurfaceStack.#resolveSurfacePriority(
26
+ pad,
27
+ spec
28
+ )
29
+ let zIndex = priority
24
30
 
25
31
  for (const previous of stack) {
26
32
  if (
27
33
  PcbScene3dPadSurfaceStack.#boundsOverlap(
28
34
  surface.bounds,
29
35
  previous.bounds
30
- )
36
+ ) &&
37
+ priority >=
38
+ Number(
39
+ previous.priority ??
40
+ PcbScene3dPadSurfaceStack
41
+ .#SURFACE_Z_PRIORITY_DEFAULT
42
+ )
31
43
  ) {
32
44
  zIndex = Math.max(zIndex, previous.zIndex + 1)
33
45
  }
34
46
  }
35
47
 
36
- stack.push({ bounds: surface.bounds, zIndex })
48
+ stack.push({ bounds: surface.bounds, zIndex, priority })
37
49
  return zIndex * PcbScene3dPadSurfaceStack.#COPLANAR_PAD_LIFT_MIL
38
50
  }
39
51
 
52
+ /**
53
+ * Resolves stable visual priority for overlapping pad surfaces.
54
+ * @param {{ holeDiameter?: number | null }} pad Source pad.
55
+ * @param {{ hasHole?: boolean, holeDiameter?: number | null }} spec Pad face spec.
56
+ * @returns {number}
57
+ */
58
+ static #resolveSurfacePriority(pad, spec) {
59
+ if (
60
+ spec?.hasHole === true ||
61
+ Number(spec?.holeDiameter || pad?.holeDiameter || 0) > 0
62
+ ) {
63
+ return PcbScene3dPadSurfaceStack.#SURFACE_Z_PRIORITY_DRILLED
64
+ }
65
+
66
+ return PcbScene3dPadSurfaceStack.#SURFACE_Z_PRIORITY_DEFAULT
67
+ }
68
+
40
69
  /**
41
70
  * Resolves the projected board-space footprint for one pad face.
42
71
  * @param {{ x: number, y: number }} point Normalized pad anchor.
@@ -205,8 +205,8 @@ export class PcbScene3dShapeHoleMerger {
205
205
 
206
206
  for (const point of Array.isArray(points) ? points : []) {
207
207
  const normalizedPoint = {
208
- x: Number(point?.x),
209
- y: Number(point?.y)
208
+ x: PcbScene3dShapeHoleMerger.#snapCoordinate(point?.x),
209
+ y: PcbScene3dShapeHoleMerger.#snapCoordinate(point?.y)
210
210
  }
211
211
 
212
212
  if (
@@ -228,6 +228,17 @@ export class PcbScene3dShapeHoleMerger {
228
228
  return uniquePoints
229
229
  }
230
230
 
231
+ /**
232
+ * Snaps one coordinate to the duplicate-detection precision.
233
+ * @param {unknown} value Coordinate value.
234
+ * @returns {number}
235
+ */
236
+ static #snapCoordinate(value) {
237
+ const number = Number(value)
238
+
239
+ return Number.isFinite(number) ? Number(number.toFixed(6)) : number
240
+ }
241
+
231
242
  /**
232
243
  * Builds an axis-aligned polygon bounds box.
233
244
  * @param {{ x: number, y: number }[]} polygon Source polygon.