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
|
@@ -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 =
|
|
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 =
|
|
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(
|