pcb-scene3d-viewer 1.1.0 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pcb-scene3d-viewer",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Reusable Three.js PCB 3D scene viewer for normalized ECAD and CircuitJSON scene descriptions",
5
5
  "keywords": [
6
6
  "pcb",
@@ -6,7 +6,7 @@ import { PcbScene3dOutlineBuilder } from './PcbScene3dOutlineBuilder.mjs'
6
6
  * Builds the board solid profile, including drilled holes.
7
7
  */
8
8
  export class PcbScene3dBoardShapeFactory {
9
- static #CURVE_SEGMENTS = 12
9
+ static #CURVE_SEGMENTS = 8
10
10
  static #PAD_HOLE_SHAPE_SLOT = 2
11
11
  static #PLATED_WALL_MATERIAL_INDEX = 2
12
12
  static #EDGE_WALL_MATERIAL_INDEX = 1
@@ -7,7 +7,7 @@ import { PcbScene3dOutlineBuilder } from './PcbScene3dOutlineBuilder.mjs'
7
7
  * Builds separate solder-mask face sheets for board-assembly rendering.
8
8
  */
9
9
  export class PcbScene3dBoardSolderMaskFactory {
10
- static #CURVE_SEGMENTS = 20
10
+ static #CURVE_SEGMENTS = 8
11
11
  static #OUTER_SAMPLE_POINTS = 160
12
12
  static #DRILL_SAMPLE_POINTS = 72
13
13
  static #EDGE_CLEARANCE_MIL = 0
@@ -0,0 +1,167 @@
1
+ import { PcbScene3dCutoutCircleDetector } from './PcbScene3dCutoutCircleDetector.mjs'
2
+
3
+ /**
4
+ * Resolves analytic overlap checks for sampled circular drill cutouts.
5
+ */
6
+ export class PcbScene3dCircularCutoutOverlap {
7
+ /**
8
+ * Returns true when one triangle overlaps a circular cutout.
9
+ * @param {{ x: number, y: number }[]} triangle
10
+ * @param {{ points?: { x: number, y: number }[], centerX?: number, centerY?: number, radius?: number }} cutout
11
+ * @param {number} [epsilon]
12
+ * @returns {boolean}
13
+ */
14
+ static overlapsTriangle(triangle, cutout, epsilon = 0.001) {
15
+ const radius = Number(cutout?.radius || 0) + Number(epsilon || 0)
16
+ if (!Number.isFinite(radius) || radius <= 0) {
17
+ return false
18
+ }
19
+
20
+ const center = {
21
+ x: Number(cutout?.centerX || 0),
22
+ y: Number(cutout?.centerY || 0)
23
+ }
24
+ const radiusSquared = radius * radius
25
+
26
+ for (const point of triangle) {
27
+ if (
28
+ PcbScene3dCutoutCircleDetector.distanceSquared(point, cutout) <=
29
+ radiusSquared
30
+ ) {
31
+ return true
32
+ }
33
+ }
34
+
35
+ if (
36
+ PcbScene3dCircularCutoutOverlap.#isPointInsideOrOnTriangle(
37
+ center,
38
+ triangle,
39
+ epsilon
40
+ )
41
+ ) {
42
+ return true
43
+ }
44
+
45
+ for (let index = 0; index < triangle.length; index += 1) {
46
+ const current = triangle[index]
47
+ const next = triangle[(index + 1) % triangle.length]
48
+
49
+ if (
50
+ PcbScene3dCircularCutoutOverlap.#distanceToSegmentSquared(
51
+ center,
52
+ current,
53
+ next,
54
+ epsilon
55
+ ) <= radiusSquared
56
+ ) {
57
+ return true
58
+ }
59
+ }
60
+
61
+ return PcbScene3dCircularCutoutOverlap.#hasSampledBoundaryOverlap(
62
+ triangle,
63
+ cutout,
64
+ epsilon
65
+ )
66
+ }
67
+
68
+ /**
69
+ * Returns true when a sampled cutout boundary point lies in the triangle.
70
+ * @param {{ x: number, y: number }[]} triangle
71
+ * @param {{ points?: { x: number, y: number }[] }} cutout
72
+ * @param {number} epsilon
73
+ * @returns {boolean}
74
+ */
75
+ static #hasSampledBoundaryOverlap(triangle, cutout, epsilon) {
76
+ const boundaryPoints = Array.isArray(cutout?.points)
77
+ ? cutout.points
78
+ : []
79
+ const step = Math.max(1, Math.floor(boundaryPoints.length / 8))
80
+
81
+ for (let index = 0; index < boundaryPoints.length; index += step) {
82
+ if (
83
+ PcbScene3dCircularCutoutOverlap.#isPointInsideOrOnTriangle(
84
+ boundaryPoints[index],
85
+ triangle,
86
+ epsilon
87
+ )
88
+ ) {
89
+ return true
90
+ }
91
+ }
92
+
93
+ return false
94
+ }
95
+
96
+ /**
97
+ * Returns true when a point is inside or on one triangle.
98
+ * @param {{ x: number, y: number }} point
99
+ * @param {{ x: number, y: number }[]} triangle
100
+ * @param {number} epsilon
101
+ * @returns {boolean}
102
+ */
103
+ static #isPointInsideOrOnTriangle(point, triangle, epsilon) {
104
+ let hasNegative = false
105
+ let hasPositive = false
106
+
107
+ for (let index = 0; index < triangle.length; index += 1) {
108
+ const current = triangle[index]
109
+ const next = triangle[(index + 1) % triangle.length]
110
+ const sign = PcbScene3dCircularCutoutOverlap.#cross(
111
+ point,
112
+ current,
113
+ next
114
+ )
115
+
116
+ hasNegative = hasNegative || sign < -epsilon
117
+ hasPositive = hasPositive || sign > epsilon
118
+ }
119
+
120
+ return !(hasNegative && hasPositive)
121
+ }
122
+
123
+ /**
124
+ * Resolves the squared distance from one point to a finite segment.
125
+ * @param {{ x: number, y: number }} point
126
+ * @param {{ x: number, y: number }} start
127
+ * @param {{ x: number, y: number }} end
128
+ * @param {number} epsilon
129
+ * @returns {number}
130
+ */
131
+ static #distanceToSegmentSquared(point, start, end, epsilon) {
132
+ const dx = end.x - start.x
133
+ const dy = end.y - start.y
134
+ const lengthSquared = dx * dx + dy * dy
135
+
136
+ if (lengthSquared <= epsilon) {
137
+ return (point.x - start.x) ** 2 + (point.y - start.y) ** 2
138
+ }
139
+
140
+ const ratio = Math.max(
141
+ 0,
142
+ Math.min(
143
+ 1,
144
+ ((point.x - start.x) * dx + (point.y - start.y) * dy) /
145
+ lengthSquared
146
+ )
147
+ )
148
+ const projectedX = start.x + ratio * dx
149
+ const projectedY = start.y + ratio * dy
150
+
151
+ return (point.x - projectedX) ** 2 + (point.y - projectedY) ** 2
152
+ }
153
+
154
+ /**
155
+ * Resolves the signed area for three points.
156
+ * @param {{ x: number, y: number }} first
157
+ * @param {{ x: number, y: number }} second
158
+ * @param {{ x: number, y: number }} third
159
+ * @returns {number}
160
+ */
161
+ static #cross(first, second, third) {
162
+ return (
163
+ (second.x - first.x) * (third.y - first.y) -
164
+ (second.y - first.y) * (third.x - first.x)
165
+ )
166
+ }
167
+ }
@@ -1,3 +1,4 @@
1
+ import { PcbScene3dCircularCutoutOverlap } from './PcbScene3dCircularCutoutOverlap.mjs'
1
2
  import { PcbScene3dCutoutCircleDetector } from './PcbScene3dCutoutCircleDetector.mjs'
2
3
 
3
4
  /**
@@ -575,6 +576,14 @@ export class PcbScene3dCutoutGeometryFilter {
575
576
  return false
576
577
  }
577
578
 
579
+ if (cutout.isCircular) {
580
+ return PcbScene3dCircularCutoutOverlap.overlapsTriangle(
581
+ triangle,
582
+ cutout,
583
+ PcbScene3dCutoutGeometryFilter.#GEOMETRY_EPSILON
584
+ )
585
+ }
586
+
578
587
  for (const point of triangle) {
579
588
  if (
580
589
  PcbScene3dCutoutGeometryFilter.#isPointInsideOrOnCutout(