@tscircuit/3d-viewer 0.0.626 → 0.0.628
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 +8 -11
- package/dist/index.js +455 -137
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ viewer. The host can switch tabs and focus the schematic using the source ID.
|
|
|
82
82
|
Omit the callback when schematic navigation is unavailable.
|
|
83
83
|
|
|
84
84
|
```tsx
|
|
85
|
-
|
|
85
|
+
;<CadViewer
|
|
86
86
|
circuitJson={circuitJson}
|
|
87
87
|
onViewSchematicComponent={({ source_component_id }) => {
|
|
88
88
|
// Find the schematic component with this source ID and focus it.
|
|
@@ -96,9 +96,9 @@ Omit the callback when schematic navigation is unavailable.
|
|
|
96
96
|
When using the SVG converter in Node.js environments, you'll need to provide JSDOM:
|
|
97
97
|
|
|
98
98
|
```typescript
|
|
99
|
-
import { JSDOM } from
|
|
100
|
-
import { convert3dCircuitToSvg } from
|
|
101
|
-
import { applyJsdomShim } from
|
|
99
|
+
import { JSDOM } from "jsdom"
|
|
100
|
+
import { convert3dCircuitToSvg } from "@tscircuit/3d-viewer/3d"
|
|
101
|
+
import { applyJsdomShim } from "@tscircuit/3d-viewer/utils"
|
|
102
102
|
|
|
103
103
|
// Setup JSDOM environment
|
|
104
104
|
const dom = new JSDOM()
|
|
@@ -113,8 +113,8 @@ const options = {
|
|
|
113
113
|
zoom: 50,
|
|
114
114
|
camera: {
|
|
115
115
|
position: { x: 0, y: 0, z: 100 },
|
|
116
|
-
lookAt: { x: 0, y: 0, z: 0 }
|
|
117
|
-
}
|
|
116
|
+
lookAt: { x: 0, y: 0, z: 0 },
|
|
117
|
+
},
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
const svgString = await convert3dCircuitToSvg(circuitJson, options)
|
|
@@ -137,7 +137,6 @@ The `convert3dCircuitToSvg` function accepts the following options:
|
|
|
137
137
|
Main component for rendering the 3D PCB viewer.
|
|
138
138
|
|
|
139
139
|
Props:
|
|
140
|
-
|
|
141
140
|
- `circuit-json`: (optional) An array of AnyCircuitElement objects representing the PCB layout.
|
|
142
141
|
- `children`: (optional) React children elements describing the PCB layout (alternative to using `circuit-json`).
|
|
143
142
|
- `resolveStaticAsset`: (optional) Function that receives each component model URL (`obj`, `wrl`, `stl`, `gltf`, `glb`, `step`) and returns the resolved URL to load.
|
|
@@ -147,14 +146,12 @@ Props:
|
|
|
147
146
|
Defines the PCB board dimensions.
|
|
148
147
|
|
|
149
148
|
Props:
|
|
150
|
-
|
|
151
149
|
- `width`: Width of the board (e.g., "20mm").
|
|
152
150
|
- `height`: Height of the board (e.g., "20mm").
|
|
153
151
|
|
|
154
152
|
### Component Elements
|
|
155
153
|
|
|
156
154
|
Various component elements can be used as children of the `<board>` element:
|
|
157
|
-
|
|
158
155
|
- `<resistor>`
|
|
159
156
|
- `<capacitor>`
|
|
160
157
|
- `<chip>`
|
|
@@ -269,7 +266,7 @@ input remains a distinct diagnostic, not the mounted pose's zero-angle control.
|
|
|
269
266
|
You can define custom 3D models for components using the `cadModel` prop:
|
|
270
267
|
|
|
271
268
|
```jsx
|
|
272
|
-
|
|
269
|
+
;<chip
|
|
273
270
|
name="U1"
|
|
274
271
|
footprint="soic8"
|
|
275
272
|
cadModel={{
|
|
@@ -284,7 +281,7 @@ You can define custom 3D models for components using the `cadModel` prop:
|
|
|
284
281
|
For more complex or programmatically defined models, you can use JSCAD:
|
|
285
282
|
|
|
286
283
|
```jsx
|
|
287
|
-
|
|
284
|
+
;<bug
|
|
288
285
|
footprint="soic8"
|
|
289
286
|
name="U1"
|
|
290
287
|
cadModel={{
|
package/dist/index.js
CHANGED
|
@@ -233,11 +233,11 @@ var require_constants = __commonJS({
|
|
|
233
233
|
"node_modules/@jscad/modeling/src/maths/constants.js"(exports, module) {
|
|
234
234
|
"use strict";
|
|
235
235
|
var spatialResolution = 1e5;
|
|
236
|
-
var
|
|
236
|
+
var EPS4 = 1e-5;
|
|
237
237
|
var NEPS = 1e-13;
|
|
238
238
|
var TAU = Math.PI * 2;
|
|
239
239
|
module.exports = {
|
|
240
|
-
EPS:
|
|
240
|
+
EPS: EPS4,
|
|
241
241
|
NEPS,
|
|
242
242
|
TAU,
|
|
243
243
|
spatialResolution
|
|
@@ -288,13 +288,13 @@ var require_identity = __commonJS({
|
|
|
288
288
|
var require_fromRotation = __commonJS({
|
|
289
289
|
"node_modules/@jscad/modeling/src/maths/mat4/fromRotation.js"(exports, module) {
|
|
290
290
|
"use strict";
|
|
291
|
-
var { EPS:
|
|
291
|
+
var { EPS: EPS4 } = require_constants();
|
|
292
292
|
var { sin, cos } = require_trigonometry();
|
|
293
293
|
var identity7 = require_identity();
|
|
294
294
|
var fromRotation6 = (out, rad, axis) => {
|
|
295
295
|
let [x, y, z] = axis;
|
|
296
296
|
const lengthSquared = x * x + y * y + z * z;
|
|
297
|
-
if (Math.abs(lengthSquared) <
|
|
297
|
+
if (Math.abs(lengthSquared) < EPS4) {
|
|
298
298
|
return identity7(out);
|
|
299
299
|
}
|
|
300
300
|
const len6 = 1 / Math.sqrt(lengthSquared);
|
|
@@ -1221,13 +1221,13 @@ var require_multiply2 = __commonJS({
|
|
|
1221
1221
|
var require_rotate = __commonJS({
|
|
1222
1222
|
"node_modules/@jscad/modeling/src/maths/mat4/rotate.js"(exports, module) {
|
|
1223
1223
|
"use strict";
|
|
1224
|
-
var { EPS:
|
|
1224
|
+
var { EPS: EPS4 } = require_constants();
|
|
1225
1225
|
var { sin, cos } = require_trigonometry();
|
|
1226
1226
|
var copy10 = require_copy();
|
|
1227
1227
|
var rotate7 = (out, matrix, radians, axis) => {
|
|
1228
1228
|
let [x, y, z] = axis;
|
|
1229
1229
|
const lengthSquared = x * x + y * y + z * z;
|
|
1230
|
-
if (Math.abs(lengthSquared) <
|
|
1230
|
+
if (Math.abs(lengthSquared) < EPS4) {
|
|
1231
1231
|
return copy10(out, matrix);
|
|
1232
1232
|
}
|
|
1233
1233
|
const len6 = 1 / Math.sqrt(lengthSquared);
|
|
@@ -3716,19 +3716,19 @@ var require_fromPoints3 = __commonJS({
|
|
|
3716
3716
|
var require_fromPointsRandom = __commonJS({
|
|
3717
3717
|
"node_modules/@jscad/modeling/src/maths/plane/fromPointsRandom.js"(exports, module) {
|
|
3718
3718
|
"use strict";
|
|
3719
|
-
var { EPS:
|
|
3719
|
+
var { EPS: EPS4 } = require_constants();
|
|
3720
3720
|
var vec3 = require_vec3();
|
|
3721
3721
|
var fromPointsRandom = (out, a, b, c) => {
|
|
3722
3722
|
let ba = vec3.subtract(vec3.create(), b, a);
|
|
3723
3723
|
let ca = vec3.subtract(vec3.create(), c, a);
|
|
3724
|
-
if (vec3.length(ba) <
|
|
3724
|
+
if (vec3.length(ba) < EPS4) {
|
|
3725
3725
|
ba = vec3.orthogonal(ba, ca);
|
|
3726
3726
|
}
|
|
3727
|
-
if (vec3.length(ca) <
|
|
3727
|
+
if (vec3.length(ca) < EPS4) {
|
|
3728
3728
|
ca = vec3.orthogonal(ca, ba);
|
|
3729
3729
|
}
|
|
3730
3730
|
let normal = vec3.cross(vec3.create(), ba, ca);
|
|
3731
|
-
if (vec3.length(normal) <
|
|
3731
|
+
if (vec3.length(normal) < EPS4) {
|
|
3732
3732
|
ca = vec3.orthogonal(ca, ba);
|
|
3733
3733
|
normal = vec3.cross(normal, ba, ca);
|
|
3734
3734
|
}
|
|
@@ -4411,7 +4411,7 @@ var require_isA3 = __commonJS({
|
|
|
4411
4411
|
var require_isConvex2 = __commonJS({
|
|
4412
4412
|
"node_modules/@jscad/modeling/src/geometries/geom3/isConvex.js"(exports, module) {
|
|
4413
4413
|
"use strict";
|
|
4414
|
-
var { EPS:
|
|
4414
|
+
var { EPS: EPS4 } = require_constants();
|
|
4415
4415
|
var vec3 = require_vec3();
|
|
4416
4416
|
var geom3 = require_isA3();
|
|
4417
4417
|
var toPolygons = require_toPolygons();
|
|
@@ -4442,7 +4442,7 @@ var require_isConvex2 = __commonJS({
|
|
|
4442
4442
|
for (let j = 0; j < vertices.length; j++) {
|
|
4443
4443
|
const v = vertices[j];
|
|
4444
4444
|
const distance5 = vec3.dot(plane, v) - plane[3];
|
|
4445
|
-
if (distance5 >
|
|
4445
|
+
if (distance5 > EPS4) {
|
|
4446
4446
|
return false;
|
|
4447
4447
|
}
|
|
4448
4448
|
}
|
|
@@ -4634,7 +4634,7 @@ var require_clone8 = __commonJS({
|
|
|
4634
4634
|
var require_close = __commonJS({
|
|
4635
4635
|
"node_modules/@jscad/modeling/src/geometries/path2/close.js"(exports, module) {
|
|
4636
4636
|
"use strict";
|
|
4637
|
-
var { EPS:
|
|
4637
|
+
var { EPS: EPS4 } = require_constants();
|
|
4638
4638
|
var vec2 = require_vec2();
|
|
4639
4639
|
var clone10 = require_clone8();
|
|
4640
4640
|
var close = (geometry) => {
|
|
@@ -4645,7 +4645,7 @@ var require_close = __commonJS({
|
|
|
4645
4645
|
const points = cloned.points;
|
|
4646
4646
|
const p0 = points[0];
|
|
4647
4647
|
let pn = points[points.length - 1];
|
|
4648
|
-
while (vec2.distance(p0, pn) <
|
|
4648
|
+
while (vec2.distance(p0, pn) < EPS4 * EPS4) {
|
|
4649
4649
|
points.pop();
|
|
4650
4650
|
if (points.length === 1) break;
|
|
4651
4651
|
pn = points[points.length - 1];
|
|
@@ -4680,7 +4680,7 @@ var require_create8 = __commonJS({
|
|
|
4680
4680
|
var require_fromPoints5 = __commonJS({
|
|
4681
4681
|
"node_modules/@jscad/modeling/src/geometries/path2/fromPoints.js"(exports, module) {
|
|
4682
4682
|
"use strict";
|
|
4683
|
-
var { EPS:
|
|
4683
|
+
var { EPS: EPS4 } = require_constants();
|
|
4684
4684
|
var vec2 = require_vec2();
|
|
4685
4685
|
var close = require_close();
|
|
4686
4686
|
var create10 = require_create8();
|
|
@@ -4692,7 +4692,7 @@ var require_fromPoints5 = __commonJS({
|
|
|
4692
4692
|
if (created.points.length > 1) {
|
|
4693
4693
|
const p0 = created.points[0];
|
|
4694
4694
|
const pn = created.points[created.points.length - 1];
|
|
4695
|
-
if (vec2.distance(p0, pn) <
|
|
4695
|
+
if (vec2.distance(p0, pn) < EPS4 * EPS4) {
|
|
4696
4696
|
closed = true;
|
|
4697
4697
|
}
|
|
4698
4698
|
}
|
|
@@ -6519,12 +6519,12 @@ var require_fromPlanes = __commonJS({
|
|
|
6519
6519
|
"use strict";
|
|
6520
6520
|
var vec3 = require_vec3();
|
|
6521
6521
|
var { solve2Linear } = require_utils();
|
|
6522
|
-
var { EPS:
|
|
6522
|
+
var { EPS: EPS4 } = require_constants();
|
|
6523
6523
|
var fromPointAndDirection = require_fromPointAndDirection();
|
|
6524
6524
|
var fromPlanes = (out, plane1, plane2) => {
|
|
6525
6525
|
let direction = vec3.cross(vec3.create(), plane1, plane2);
|
|
6526
6526
|
let length6 = vec3.length(direction);
|
|
6527
|
-
if (length6 <
|
|
6527
|
+
if (length6 < EPS4) {
|
|
6528
6528
|
throw new Error("parallel planes do not intersect");
|
|
6529
6529
|
}
|
|
6530
6530
|
length6 = 1 / length6;
|
|
@@ -6863,13 +6863,13 @@ var require_measureAggregateBoundingBox = __commonJS({
|
|
|
6863
6863
|
var require_calculateEpsilonFromBounds = __commonJS({
|
|
6864
6864
|
"node_modules/@jscad/modeling/src/measurements/calculateEpsilonFromBounds.js"(exports, module) {
|
|
6865
6865
|
"use strict";
|
|
6866
|
-
var { EPS:
|
|
6866
|
+
var { EPS: EPS4 } = require_constants();
|
|
6867
6867
|
var calculateEpsilonFromBounds = (bounds, dimensions) => {
|
|
6868
6868
|
let total = 0;
|
|
6869
6869
|
for (let i = 0; i < dimensions; i++) {
|
|
6870
6870
|
total += bounds[1][i] - bounds[0][i];
|
|
6871
6871
|
}
|
|
6872
|
-
return
|
|
6872
|
+
return EPS4 * total / dimensions;
|
|
6873
6873
|
};
|
|
6874
6874
|
module.exports = calculateEpsilonFromBounds;
|
|
6875
6875
|
}
|
|
@@ -7238,7 +7238,7 @@ var require_commonChecks = __commonJS({
|
|
|
7238
7238
|
var require_arc = __commonJS({
|
|
7239
7239
|
"node_modules/@jscad/modeling/src/primitives/arc.js"(exports, module) {
|
|
7240
7240
|
"use strict";
|
|
7241
|
-
var { EPS:
|
|
7241
|
+
var { EPS: EPS4, TAU } = require_constants();
|
|
7242
7242
|
var vec2 = require_vec2();
|
|
7243
7243
|
var path2 = require_path2();
|
|
7244
7244
|
var { isGT, isGTE, isNumberArray } = require_commonChecks();
|
|
@@ -7266,7 +7266,7 @@ var require_arc = __commonJS({
|
|
|
7266
7266
|
if (startAngle > endAngle) {
|
|
7267
7267
|
rotation = endAngle + (TAU - startAngle);
|
|
7268
7268
|
}
|
|
7269
|
-
const minangle = Math.acos((radius * radius + radius * radius -
|
|
7269
|
+
const minangle = Math.acos((radius * radius + radius * radius - EPS4 * EPS4) / (2 * radius * radius));
|
|
7270
7270
|
const centerv = vec2.clone(center);
|
|
7271
7271
|
let point;
|
|
7272
7272
|
const pointArray = [];
|
|
@@ -7304,7 +7304,7 @@ var require_arc = __commonJS({
|
|
|
7304
7304
|
var require_ellipse = __commonJS({
|
|
7305
7305
|
"node_modules/@jscad/modeling/src/primitives/ellipse.js"(exports, module) {
|
|
7306
7306
|
"use strict";
|
|
7307
|
-
var { EPS:
|
|
7307
|
+
var { EPS: EPS4, TAU } = require_constants();
|
|
7308
7308
|
var vec2 = require_vec2();
|
|
7309
7309
|
var geom2 = require_geom2();
|
|
7310
7310
|
var { sin, cos } = require_trigonometry();
|
|
@@ -7335,7 +7335,7 @@ var require_ellipse = __commonJS({
|
|
|
7335
7335
|
rotation = endAngle + (TAU - startAngle);
|
|
7336
7336
|
}
|
|
7337
7337
|
const minradius = Math.min(radius[0], radius[1]);
|
|
7338
|
-
const minangle = Math.acos((minradius * minradius + minradius * minradius -
|
|
7338
|
+
const minangle = Math.acos((minradius * minradius + minradius * minradius - EPS4 * EPS4) / (2 * minradius * minradius));
|
|
7339
7339
|
if (rotation < minangle) throw new Error("startAngle and endAngle do not define a significant rotation");
|
|
7340
7340
|
segments = Math.floor(segments * (rotation / TAU));
|
|
7341
7341
|
const centerv = vec2.clone(center);
|
|
@@ -7447,7 +7447,7 @@ var require_cube = __commonJS({
|
|
|
7447
7447
|
var require_cylinderElliptic = __commonJS({
|
|
7448
7448
|
"node_modules/@jscad/modeling/src/primitives/cylinderElliptic.js"(exports, module) {
|
|
7449
7449
|
"use strict";
|
|
7450
|
-
var { EPS:
|
|
7450
|
+
var { EPS: EPS4, TAU } = require_constants();
|
|
7451
7451
|
var vec3 = require_vec3();
|
|
7452
7452
|
var geom3 = require_geom3();
|
|
7453
7453
|
var poly3 = require_poly3();
|
|
@@ -7484,7 +7484,7 @@ var require_cylinderElliptic = __commonJS({
|
|
|
7484
7484
|
rotation = endAngle + (TAU - startAngle);
|
|
7485
7485
|
}
|
|
7486
7486
|
const minradius = Math.min(startRadius[0], startRadius[1], endRadius[0], endRadius[1]);
|
|
7487
|
-
const minangle = Math.acos((minradius * minradius + minradius * minradius -
|
|
7487
|
+
const minangle = Math.acos((minradius * minradius + minradius * minradius - EPS4 * EPS4) / (2 * minradius * minradius));
|
|
7488
7488
|
if (rotation < minangle) throw new Error("startAngle and endAngle do not define a significant rotation");
|
|
7489
7489
|
const slices = Math.floor(segments * (rotation / TAU));
|
|
7490
7490
|
const start = vec3.fromValues(0, 0, -(height / 2));
|
|
@@ -7933,7 +7933,7 @@ var require_rectangle = __commonJS({
|
|
|
7933
7933
|
var require_roundedCuboid = __commonJS({
|
|
7934
7934
|
"node_modules/@jscad/modeling/src/primitives/roundedCuboid.js"(exports, module) {
|
|
7935
7935
|
"use strict";
|
|
7936
|
-
var { EPS:
|
|
7936
|
+
var { EPS: EPS4, TAU } = require_constants();
|
|
7937
7937
|
var vec2 = require_vec2();
|
|
7938
7938
|
var vec3 = require_vec3();
|
|
7939
7939
|
var geom3 = require_geom3();
|
|
@@ -7949,7 +7949,7 @@ var require_roundedCuboid = __commonJS({
|
|
|
7949
7949
|
let layerradius = radius * cospitch;
|
|
7950
7950
|
let layeroffset = size4[2] - (radius - radius * sinpitch);
|
|
7951
7951
|
if (!positive) layeroffset = radius - radius * sinpitch - size4[2];
|
|
7952
|
-
layerradius = layerradius >
|
|
7952
|
+
layerradius = layerradius > EPS4 ? layerradius : 0;
|
|
7953
7953
|
const corner0 = vec3.add(vec3.create(), center, [size4[0] - radius, size4[1] - radius, layeroffset]);
|
|
7954
7954
|
const corner1 = vec3.add(vec3.create(), center, [radius - size4[0], size4[1] - radius, layeroffset]);
|
|
7955
7955
|
const corner2 = vec3.add(vec3.create(), center, [radius - size4[0], radius - size4[1], layeroffset]);
|
|
@@ -8044,7 +8044,7 @@ var require_roundedCuboid = __commonJS({
|
|
|
8044
8044
|
if (size4[0] === 0 || size4[1] === 0 || size4[2] === 0) return geom3.create();
|
|
8045
8045
|
if (roundRadius === 0) return cuboid5({ center, size: size4 });
|
|
8046
8046
|
size4 = size4.map((v) => v / 2);
|
|
8047
|
-
if (roundRadius > size4[0] -
|
|
8047
|
+
if (roundRadius > size4[0] - EPS4 || roundRadius > size4[1] - EPS4 || roundRadius > size4[2] - EPS4) throw new Error("roundRadius must be smaller than the radius of all dimensions");
|
|
8048
8048
|
segments = Math.floor(segments / 4);
|
|
8049
8049
|
let prevCornersPos = null;
|
|
8050
8050
|
let prevCornersNeg = null;
|
|
@@ -8086,7 +8086,7 @@ var require_roundedCuboid = __commonJS({
|
|
|
8086
8086
|
var require_roundedCylinder = __commonJS({
|
|
8087
8087
|
"node_modules/@jscad/modeling/src/primitives/roundedCylinder.js"(exports, module) {
|
|
8088
8088
|
"use strict";
|
|
8089
|
-
var { EPS:
|
|
8089
|
+
var { EPS: EPS4, TAU } = require_constants();
|
|
8090
8090
|
var vec3 = require_vec3();
|
|
8091
8091
|
var geom3 = require_geom3();
|
|
8092
8092
|
var poly3 = require_poly3();
|
|
@@ -8114,7 +8114,7 @@ var require_roundedCylinder = __commonJS({
|
|
|
8114
8114
|
const end = [0, 0, height / 2];
|
|
8115
8115
|
const direction = vec3.subtract(vec3.create(), end, start);
|
|
8116
8116
|
const length6 = vec3.length(direction);
|
|
8117
|
-
if (2 * roundRadius > length6 -
|
|
8117
|
+
if (2 * roundRadius > length6 - EPS4) throw new Error("height must be larger than twice roundRadius");
|
|
8118
8118
|
let defaultnormal;
|
|
8119
8119
|
if (Math.abs(direction[0]) > Math.abs(direction[1])) {
|
|
8120
8120
|
defaultnormal = vec3.fromValues(0, 1, 0);
|
|
@@ -8199,7 +8199,7 @@ var require_roundedCylinder = __commonJS({
|
|
|
8199
8199
|
var require_roundedRectangle = __commonJS({
|
|
8200
8200
|
"node_modules/@jscad/modeling/src/primitives/roundedRectangle.js"(exports, module) {
|
|
8201
8201
|
"use strict";
|
|
8202
|
-
var { EPS:
|
|
8202
|
+
var { EPS: EPS4, TAU } = require_constants();
|
|
8203
8203
|
var vec2 = require_vec2();
|
|
8204
8204
|
var geom2 = require_geom2();
|
|
8205
8205
|
var { isGTE, isNumberArray } = require_commonChecks();
|
|
@@ -8220,7 +8220,7 @@ var require_roundedRectangle = __commonJS({
|
|
|
8220
8220
|
if (size4[0] === 0 || size4[1] === 0) return geom2.create();
|
|
8221
8221
|
if (roundRadius === 0) return rectangle({ center, size: size4 });
|
|
8222
8222
|
size4 = size4.map((v) => v / 2);
|
|
8223
|
-
if (roundRadius > size4[0] -
|
|
8223
|
+
if (roundRadius > size4[0] - EPS4 || roundRadius > size4[1] - EPS4) throw new Error("roundRadius must be smaller than the radius of all dimensions");
|
|
8224
8224
|
const cornersegments = Math.floor(segments / 4);
|
|
8225
8225
|
const corner0 = vec2.add(vec2.create(), center, [size4[0] - roundRadius, size4[1] - roundRadius]);
|
|
8226
8226
|
const corner1 = vec2.add(vec2.create(), center, [roundRadius - size4[0], size4[1] - roundRadius]);
|
|
@@ -9284,7 +9284,7 @@ var require_repair = __commonJS({
|
|
|
9284
9284
|
var require_extrudeWalls = __commonJS({
|
|
9285
9285
|
"node_modules/@jscad/modeling/src/operations/extrusions/extrudeWalls.js"(exports, module) {
|
|
9286
9286
|
"use strict";
|
|
9287
|
-
var { EPS:
|
|
9287
|
+
var { EPS: EPS4 } = require_constants();
|
|
9288
9288
|
var vec3 = require_vec3();
|
|
9289
9289
|
var poly3 = require_poly3();
|
|
9290
9290
|
var slice = require_slice();
|
|
@@ -9324,7 +9324,7 @@ var require_extrudeWalls = __commonJS({
|
|
|
9324
9324
|
});
|
|
9325
9325
|
return newEdges;
|
|
9326
9326
|
};
|
|
9327
|
-
var EPSAREA =
|
|
9327
|
+
var EPSAREA = EPS4 * EPS4 / 2 * Math.sin(Math.PI / 3);
|
|
9328
9328
|
var extrudeWalls = (slice0, slice1) => {
|
|
9329
9329
|
let edges0 = slice.toEdges(slice0);
|
|
9330
9330
|
let edges1 = slice.toEdges(slice1);
|
|
@@ -10307,7 +10307,7 @@ var require_OrthoNormalBasis = __commonJS({
|
|
|
10307
10307
|
var require_reTesselateCoplanarPolygons = __commonJS({
|
|
10308
10308
|
"node_modules/@jscad/modeling/src/operations/modifiers/reTesselateCoplanarPolygons.js"(exports, module) {
|
|
10309
10309
|
"use strict";
|
|
10310
|
-
var { EPS:
|
|
10310
|
+
var { EPS: EPS4 } = require_constants();
|
|
10311
10311
|
var line2 = require_line2();
|
|
10312
10312
|
var vec2 = require_vec2();
|
|
10313
10313
|
var OrthoNormalBasis = require_OrthoNormalBasis();
|
|
@@ -10325,7 +10325,7 @@ var require_reTesselateCoplanarPolygons = __commonJS({
|
|
|
10325
10325
|
const topy2polygonindexes = /* @__PURE__ */ new Map();
|
|
10326
10326
|
const ycoordinatetopolygonindexes = /* @__PURE__ */ new Map();
|
|
10327
10327
|
const ycoordinatebins = /* @__PURE__ */ new Map();
|
|
10328
|
-
const ycoordinateBinningFactor = 10 /
|
|
10328
|
+
const ycoordinateBinningFactor = 10 / EPS4;
|
|
10329
10329
|
for (let polygonindex = 0; polygonindex < numpolygons; polygonindex++) {
|
|
10330
10330
|
const poly3d = sourcepolygons[polygonindex];
|
|
10331
10331
|
let vertices2d = [];
|
|
@@ -10505,7 +10505,7 @@ var require_reTesselateCoplanarPolygons = __commonJS({
|
|
|
10505
10505
|
const prevoutpolygon = newoutpolygonrow[newoutpolygonrow.length - 1];
|
|
10506
10506
|
const d1 = vec2.distance(outpolygon.topleft, prevoutpolygon.topright);
|
|
10507
10507
|
const d2 = vec2.distance(outpolygon.bottomleft, prevoutpolygon.bottomright);
|
|
10508
|
-
if (d1 <
|
|
10508
|
+
if (d1 < EPS4 && d2 < EPS4) {
|
|
10509
10509
|
outpolygon.topleft = prevoutpolygon.topleft;
|
|
10510
10510
|
outpolygon.leftline = prevoutpolygon.leftline;
|
|
10511
10511
|
outpolygon.bottomleft = prevoutpolygon.bottomleft;
|
|
@@ -10522,8 +10522,8 @@ var require_reTesselateCoplanarPolygons = __commonJS({
|
|
|
10522
10522
|
for (let ii = 0; ii < prevoutpolygonrow.length; ii++) {
|
|
10523
10523
|
if (!matchedindexes.has(ii)) {
|
|
10524
10524
|
const prevpolygon = prevoutpolygonrow[ii];
|
|
10525
|
-
if (vec2.distance(prevpolygon.bottomleft, thispolygon.topleft) <
|
|
10526
|
-
if (vec2.distance(prevpolygon.bottomright, thispolygon.topright) <
|
|
10525
|
+
if (vec2.distance(prevpolygon.bottomleft, thispolygon.topleft) < EPS4) {
|
|
10526
|
+
if (vec2.distance(prevpolygon.bottomright, thispolygon.topright) < EPS4) {
|
|
10527
10527
|
matchedindexes.add(ii);
|
|
10528
10528
|
const v1 = line2.direction(thispolygon.leftline);
|
|
10529
10529
|
const v2 = line2.direction(prevpolygon.leftline);
|
|
@@ -10531,8 +10531,8 @@ var require_reTesselateCoplanarPolygons = __commonJS({
|
|
|
10531
10531
|
const v3 = line2.direction(thispolygon.rightline);
|
|
10532
10532
|
const v4 = line2.direction(prevpolygon.rightline);
|
|
10533
10533
|
const d2 = v3[0] - v4[0];
|
|
10534
|
-
const leftlinecontinues = Math.abs(d1) <
|
|
10535
|
-
const rightlinecontinues = Math.abs(d2) <
|
|
10534
|
+
const leftlinecontinues = Math.abs(d1) < EPS4;
|
|
10535
|
+
const rightlinecontinues = Math.abs(d2) < EPS4;
|
|
10536
10536
|
const leftlineisconvex = leftlinecontinues || d1 >= 0;
|
|
10537
10537
|
const rightlineisconvex = rightlinecontinues || d2 >= 0;
|
|
10538
10538
|
if (leftlineisconvex && rightlineisconvex) {
|
|
@@ -10551,7 +10551,7 @@ var require_reTesselateCoplanarPolygons = __commonJS({
|
|
|
10551
10551
|
if (!prevcontinuedindexes.has(ii)) {
|
|
10552
10552
|
const prevpolygon = prevoutpolygonrow[ii];
|
|
10553
10553
|
prevpolygon.outpolygon.rightpoints.push(prevpolygon.bottomright);
|
|
10554
|
-
if (vec2.distance(prevpolygon.bottomright, prevpolygon.bottomleft) >
|
|
10554
|
+
if (vec2.distance(prevpolygon.bottomright, prevpolygon.bottomleft) > EPS4) {
|
|
10555
10555
|
prevpolygon.outpolygon.leftpoints.push(prevpolygon.bottomleft);
|
|
10556
10556
|
}
|
|
10557
10557
|
prevpolygon.outpolygon.leftpoints.reverse();
|
|
@@ -10570,7 +10570,7 @@ var require_reTesselateCoplanarPolygons = __commonJS({
|
|
|
10570
10570
|
rightpoints: []
|
|
10571
10571
|
};
|
|
10572
10572
|
thispolygon.outpolygon.leftpoints.push(thispolygon.topleft);
|
|
10573
|
-
if (vec2.distance(thispolygon.topleft, thispolygon.topright) >
|
|
10573
|
+
if (vec2.distance(thispolygon.topleft, thispolygon.topright) > EPS4) {
|
|
10574
10574
|
thispolygon.outpolygon.rightpoints.push(thispolygon.topright);
|
|
10575
10575
|
}
|
|
10576
10576
|
} else {
|
|
@@ -10671,7 +10671,7 @@ var require_retessellate = __commonJS({
|
|
|
10671
10671
|
var require_mayOverlap = __commonJS({
|
|
10672
10672
|
"node_modules/@jscad/modeling/src/operations/booleans/mayOverlap.js"(exports, module) {
|
|
10673
10673
|
"use strict";
|
|
10674
|
-
var { EPS:
|
|
10674
|
+
var { EPS: EPS4 } = require_constants();
|
|
10675
10675
|
var measureBoundingBox2 = require_measureBoundingBox2();
|
|
10676
10676
|
var mayOverlap = (geometry1, geometry2) => {
|
|
10677
10677
|
if (geometry1.polygons.length === 0 || geometry2.polygons.length === 0) {
|
|
@@ -10683,12 +10683,12 @@ var require_mayOverlap = __commonJS({
|
|
|
10683
10683
|
const bounds2 = measureBoundingBox2(geometry2);
|
|
10684
10684
|
const min22 = bounds2[0];
|
|
10685
10685
|
const max22 = bounds2[1];
|
|
10686
|
-
if (min22[0] - max1[0] >
|
|
10687
|
-
if (min1[0] - max22[0] >
|
|
10688
|
-
if (min22[1] - max1[1] >
|
|
10689
|
-
if (min1[1] - max22[1] >
|
|
10690
|
-
if (min22[2] - max1[2] >
|
|
10691
|
-
if (min1[2] - max22[2] >
|
|
10686
|
+
if (min22[0] - max1[0] > EPS4) return false;
|
|
10687
|
+
if (min1[0] - max22[0] > EPS4) return false;
|
|
10688
|
+
if (min22[1] - max1[1] > EPS4) return false;
|
|
10689
|
+
if (min1[1] - max22[1] > EPS4) return false;
|
|
10690
|
+
if (min22[2] - max1[2] > EPS4) return false;
|
|
10691
|
+
if (min1[2] - max22[2] > EPS4) return false;
|
|
10692
10692
|
return true;
|
|
10693
10693
|
};
|
|
10694
10694
|
module.exports = mayOverlap;
|
|
@@ -10838,12 +10838,12 @@ var require_splitLineSegmentByPlane = __commonJS({
|
|
|
10838
10838
|
var require_splitPolygonByPlane = __commonJS({
|
|
10839
10839
|
"node_modules/@jscad/modeling/src/operations/booleans/trees/splitPolygonByPlane.js"(exports, module) {
|
|
10840
10840
|
"use strict";
|
|
10841
|
-
var { EPS:
|
|
10841
|
+
var { EPS: EPS4 } = require_constants();
|
|
10842
10842
|
var plane = require_plane();
|
|
10843
10843
|
var vec3 = require_vec3();
|
|
10844
10844
|
var poly3 = require_poly3();
|
|
10845
10845
|
var splitLineSegmentByPlane = require_splitLineSegmentByPlane();
|
|
10846
|
-
var EPS_SQUARED =
|
|
10846
|
+
var EPS_SQUARED = EPS4 * EPS4;
|
|
10847
10847
|
var removeConsecutiveDuplicates = (vertices) => {
|
|
10848
10848
|
const result = [];
|
|
10849
10849
|
let prevvertex = vertices[vertices.length - 1];
|
|
@@ -10871,12 +10871,12 @@ var require_splitPolygonByPlane = __commonJS({
|
|
|
10871
10871
|
let hasfront = false;
|
|
10872
10872
|
let hasback = false;
|
|
10873
10873
|
const vertexIsBack = [];
|
|
10874
|
-
const MINEPS = -
|
|
10874
|
+
const MINEPS = -EPS4;
|
|
10875
10875
|
for (let i = 0; i < numvertices; i++) {
|
|
10876
10876
|
const t = vec3.dot(splane, vertices[i]) - splane[3];
|
|
10877
10877
|
const isback = t < MINEPS;
|
|
10878
10878
|
vertexIsBack.push(isback);
|
|
10879
|
-
if (t >
|
|
10879
|
+
if (t > EPS4) hasfront = true;
|
|
10880
10880
|
if (t < MINEPS) hasback = true;
|
|
10881
10881
|
}
|
|
10882
10882
|
if (!hasfront && !hasback) {
|
|
@@ -10941,7 +10941,7 @@ var require_splitPolygonByPlane = __commonJS({
|
|
|
10941
10941
|
var require_PolygonTreeNode = __commonJS({
|
|
10942
10942
|
"node_modules/@jscad/modeling/src/operations/booleans/trees/PolygonTreeNode.js"(exports, module) {
|
|
10943
10943
|
"use strict";
|
|
10944
|
-
var { EPS:
|
|
10944
|
+
var { EPS: EPS4 } = require_constants();
|
|
10945
10945
|
var vec3 = require_vec3();
|
|
10946
10946
|
var poly3 = require_poly3();
|
|
10947
10947
|
var splitPolygonByPlane = require_splitPolygonByPlane();
|
|
@@ -11044,7 +11044,7 @@ var require_PolygonTreeNode = __commonJS({
|
|
|
11044
11044
|
const polygon2 = this.polygon;
|
|
11045
11045
|
if (polygon2) {
|
|
11046
11046
|
const bound = poly3.measureBoundingSphere(polygon2);
|
|
11047
|
-
const sphereradius = bound[3] +
|
|
11047
|
+
const sphereradius = bound[3] + EPS4;
|
|
11048
11048
|
const spherecenter = bound;
|
|
11049
11049
|
const d = vec3.dot(splane, spherecenter) - splane[3];
|
|
11050
11050
|
if (d > sphereradius) {
|
|
@@ -11760,7 +11760,7 @@ var require_booleans = __commonJS({
|
|
|
11760
11760
|
var require_offsetFromPoints = __commonJS({
|
|
11761
11761
|
"node_modules/@jscad/modeling/src/operations/expansions/offsetFromPoints.js"(exports, module) {
|
|
11762
11762
|
"use strict";
|
|
11763
|
-
var { EPS:
|
|
11763
|
+
var { EPS: EPS4, TAU } = require_constants();
|
|
11764
11764
|
var intersect3 = require_intersect();
|
|
11765
11765
|
var line2 = require_line2();
|
|
11766
11766
|
var vec2 = require_vec2();
|
|
@@ -11773,7 +11773,7 @@ var require_offsetFromPoints = __commonJS({
|
|
|
11773
11773
|
segments: 16
|
|
11774
11774
|
};
|
|
11775
11775
|
let { delta, corners, closed, segments } = Object.assign({}, defaults, options);
|
|
11776
|
-
if (Math.abs(delta) <
|
|
11776
|
+
if (Math.abs(delta) < EPS4) return points;
|
|
11777
11777
|
let rotation = options.closed ? area2(points) : 1;
|
|
11778
11778
|
if (rotation === 0) rotation = 1;
|
|
11779
11779
|
const orientation2 = rotation > 0 && delta >= 0 || rotation < 0 && delta < 0;
|
|
@@ -11960,7 +11960,7 @@ var require_extrudePolygon = __commonJS({
|
|
|
11960
11960
|
var require_expandShell = __commonJS({
|
|
11961
11961
|
"node_modules/@jscad/modeling/src/operations/expansions/expandShell.js"(exports, module) {
|
|
11962
11962
|
"use strict";
|
|
11963
|
-
var { EPS:
|
|
11963
|
+
var { EPS: EPS4, TAU } = require_constants();
|
|
11964
11964
|
var mat4 = require_mat4();
|
|
11965
11965
|
var vec3 = require_vec3();
|
|
11966
11966
|
var fnNumberSort = require_fnNumberSort();
|
|
@@ -12065,7 +12065,7 @@ var require_expandShell = __commonJS({
|
|
|
12065
12065
|
const p2 = vec3.add(vec3.create(), endpoint, v1);
|
|
12066
12066
|
let skip = false;
|
|
12067
12067
|
if (i >= 0) {
|
|
12068
|
-
if (vec3.distance(p1, prevp1) <
|
|
12068
|
+
if (vec3.distance(p1, prevp1) < EPS4) {
|
|
12069
12069
|
skip = true;
|
|
12070
12070
|
}
|
|
12071
12071
|
}
|
|
@@ -20991,7 +20991,7 @@ import * as THREE27 from "three";
|
|
|
20991
20991
|
// package.json
|
|
20992
20992
|
var package_default = {
|
|
20993
20993
|
name: "@tscircuit/3d-viewer",
|
|
20994
|
-
version: "0.0.
|
|
20994
|
+
version: "0.0.627",
|
|
20995
20995
|
repository: {
|
|
20996
20996
|
type: "git",
|
|
20997
20997
|
url: "https://github.com/tscircuit/3d-viewer"
|
|
@@ -21046,7 +21046,7 @@ var package_default = {
|
|
|
21046
21046
|
"@tscircuit/circuit-json-util": "*"
|
|
21047
21047
|
},
|
|
21048
21048
|
devDependencies: {
|
|
21049
|
-
"@tscircuit/flex-utils": "https://jscdn.tscircuit.com/@tscircuit/flex-utils/0.0.
|
|
21049
|
+
"@tscircuit/flex-utils": "https://jscdn.tscircuit.com/@tscircuit/flex-utils/0.0.8.tgz",
|
|
21050
21050
|
"@biomejs/biome": "^2.1.4",
|
|
21051
21051
|
"@chromatic-com/storybook": "^1.9.0",
|
|
21052
21052
|
"@jscad/modeling": "^2.12.5",
|
|
@@ -21072,7 +21072,7 @@ var package_default = {
|
|
|
21072
21072
|
debug: "^4.4.0",
|
|
21073
21073
|
"jscad-planner": "^0.0.18",
|
|
21074
21074
|
jsdom: "^26.0.0",
|
|
21075
|
-
"manifold-3d": "
|
|
21075
|
+
"manifold-3d": "3.5.1",
|
|
21076
21076
|
"react-use-gesture": "^9.1.3",
|
|
21077
21077
|
semver: "^7.7.0",
|
|
21078
21078
|
"strip-ansi": "^7.1.0",
|
|
@@ -28130,30 +28130,265 @@ function signedArea(data, start, end, dim) {
|
|
|
28130
28130
|
}
|
|
28131
28131
|
|
|
28132
28132
|
// node_modules/@tscircuit/flex-utils/dist/index.js
|
|
28133
|
+
var PcbFoldError = class extends Error {
|
|
28134
|
+
constructor(issue) {
|
|
28135
|
+
super(issue.message);
|
|
28136
|
+
this.issue = issue;
|
|
28137
|
+
this.name = "PcbFoldError";
|
|
28138
|
+
}
|
|
28139
|
+
issue;
|
|
28140
|
+
};
|
|
28133
28141
|
var EPS = 1e-7;
|
|
28134
|
-
|
|
28142
|
+
var samePoint = (a, b) => Math.hypot(a.x - b.x, a.y - b.y) < EPS;
|
|
28143
|
+
function pointInPolygon(point, polygon2, includeBoundary = true) {
|
|
28144
|
+
let inside = false;
|
|
28145
|
+
for (let i = 0; i < polygon2.length; i++) {
|
|
28146
|
+
const a = polygon2[i], b = polygon2[(i + 1) % polygon2.length];
|
|
28147
|
+
const cross4 = (point.x - a.x) * (b.y - a.y) - (point.y - a.y) * (b.x - a.x);
|
|
28148
|
+
if (Math.abs(cross4) < EPS * Math.max(1, Math.hypot(b.x - a.x, b.y - a.y)) && point.x >= Math.min(a.x, b.x) - EPS && point.x <= Math.max(a.x, b.x) + EPS && point.y >= Math.min(a.y, b.y) - EPS && point.y <= Math.max(a.y, b.y) + EPS)
|
|
28149
|
+
return includeBoundary;
|
|
28150
|
+
if (a.y > point.y !== b.y > point.y && point.x < a.x + (b.x - a.x) * (point.y - a.y) / (b.y - a.y))
|
|
28151
|
+
inside = !inside;
|
|
28152
|
+
}
|
|
28153
|
+
return inside;
|
|
28154
|
+
}
|
|
28155
|
+
function lineCrossings(outline, nx, ny, d) {
|
|
28156
|
+
const crossings = [];
|
|
28157
|
+
for (let edge = 0; edge < outline.length; edge++) {
|
|
28158
|
+
const a = outline[edge], b = outline[(edge + 1) % outline.length];
|
|
28159
|
+
const da = a.x * nx + a.y * ny - d, db = b.x * nx + b.y * ny - d;
|
|
28160
|
+
if (Math.abs(da) < EPS && Math.abs(db) < EPS) continue;
|
|
28161
|
+
if (da > EPS && db > EPS || da < -EPS && db < -EPS) continue;
|
|
28162
|
+
if (Math.abs(da - db) < EPS) continue;
|
|
28163
|
+
const fraction = Math.max(0, Math.min(1, da / (da - db)));
|
|
28164
|
+
const point = {
|
|
28165
|
+
x: a.x + (b.x - a.x) * fraction,
|
|
28166
|
+
y: a.y + (b.y - a.y) * fraction
|
|
28167
|
+
};
|
|
28168
|
+
if (crossings.some((crossing) => samePoint(crossing.point, point))) continue;
|
|
28169
|
+
crossings.push(
|
|
28170
|
+
fraction > 1 - EPS ? { point: b, edge: (edge + 1) % outline.length, fraction: 0 } : { point, edge, fraction }
|
|
28171
|
+
);
|
|
28172
|
+
}
|
|
28173
|
+
return crossings.sort(
|
|
28174
|
+
(a, b) => -ny * a.point.x + nx * a.point.y - (-ny * b.point.x + nx * b.point.y)
|
|
28175
|
+
);
|
|
28176
|
+
}
|
|
28177
|
+
function movingPolygon(outline, a, b, nx, ny) {
|
|
28178
|
+
const polygon2 = [];
|
|
28179
|
+
for (let edge = 0; edge < outline.length; edge++) {
|
|
28180
|
+
polygon2.push(outline[edge]);
|
|
28181
|
+
for (const crossing of [a, b].filter((c) => c.edge === edge).sort((a2, b2) => a2.fraction - b2.fraction))
|
|
28182
|
+
if (crossing.fraction > EPS) polygon2.push(crossing.point);
|
|
28183
|
+
}
|
|
28184
|
+
const start = polygon2.findIndex((p) => samePoint(p, a.point));
|
|
28185
|
+
const end = polygon2.findIndex((p) => samePoint(p, b.point));
|
|
28186
|
+
if (start < 0 || end < 0)
|
|
28187
|
+
throw new Error("Cannot resolve PCB bend boundary crossings");
|
|
28188
|
+
const path = (from, to) => {
|
|
28189
|
+
const points = [polygon2[from]];
|
|
28190
|
+
while (from !== to) {
|
|
28191
|
+
from = (from + 1) % polygon2.length;
|
|
28192
|
+
points.push(polygon2[from]);
|
|
28193
|
+
}
|
|
28194
|
+
return points;
|
|
28195
|
+
};
|
|
28196
|
+
const area2 = outline.reduce((sum, p, i) => {
|
|
28197
|
+
const next = outline[(i + 1) % outline.length];
|
|
28198
|
+
return sum + p.x * next.y - next.x * p.y;
|
|
28199
|
+
}, 0);
|
|
28200
|
+
const side = Math.sign(area2) * ((b.point.y - a.point.y) * nx - (b.point.x - a.point.x) * ny);
|
|
28201
|
+
return side > 0 ? path(start, end) : path(end, start);
|
|
28202
|
+
}
|
|
28203
|
+
function getFiniteBendRegion({
|
|
28204
|
+
bendId,
|
|
28205
|
+
outline,
|
|
28206
|
+
nx,
|
|
28207
|
+
ny,
|
|
28208
|
+
center,
|
|
28209
|
+
proximal,
|
|
28210
|
+
axisMin,
|
|
28211
|
+
axisMax
|
|
28212
|
+
}) {
|
|
28213
|
+
const finiteCrossings = lineCrossings(outline, nx, ny, center).filter(
|
|
28214
|
+
({ point }) => {
|
|
28215
|
+
const along = -ny * point.x + nx * point.y;
|
|
28216
|
+
return along >= axisMin - EPS && along <= axisMax + EPS;
|
|
28217
|
+
}
|
|
28218
|
+
);
|
|
28219
|
+
if (finiteCrossings.length !== 2)
|
|
28220
|
+
throw new PcbFoldError({
|
|
28221
|
+
code: "invalid_finite_bend_region",
|
|
28222
|
+
message: "A finite PCB bend must cut one board cross-section from boundary to boundary",
|
|
28223
|
+
bendId
|
|
28224
|
+
});
|
|
28225
|
+
const [a, b] = finiteCrossings;
|
|
28226
|
+
const middle = {
|
|
28227
|
+
x: (a.point.x + b.point.x) / 2,
|
|
28228
|
+
y: (a.point.y + b.point.y) / 2
|
|
28229
|
+
};
|
|
28230
|
+
if (!pointInPolygon(middle, outline, false))
|
|
28231
|
+
throw new PcbFoldError({
|
|
28232
|
+
code: "invalid_finite_bend_region",
|
|
28233
|
+
message: "A finite PCB bend must pass through the board interior",
|
|
28234
|
+
bendId
|
|
28235
|
+
});
|
|
28236
|
+
const centerRegion = movingPolygon(outline, a, b, nx, ny);
|
|
28237
|
+
const alongMiddle = -ny * middle.x + nx * middle.y;
|
|
28238
|
+
const proximalCrossings = lineCrossings(outline, nx, ny, proximal);
|
|
28239
|
+
for (let i = 0; i + 1 < proximalCrossings.length; i++) {
|
|
28240
|
+
const left = proximalCrossings[i], right = proximalCrossings[i + 1];
|
|
28241
|
+
const lo = -ny * left.point.x + nx * left.point.y;
|
|
28242
|
+
const hi = -ny * right.point.x + nx * right.point.y;
|
|
28243
|
+
if (alongMiddle <= lo + EPS || alongMiddle >= hi - EPS) continue;
|
|
28244
|
+
const midpoint2 = {
|
|
28245
|
+
x: (left.point.x + right.point.x) / 2,
|
|
28246
|
+
y: (left.point.y + right.point.y) / 2
|
|
28247
|
+
};
|
|
28248
|
+
if (!pointInPolygon(midpoint2, outline, false)) continue;
|
|
28249
|
+
const region = movingPolygon(outline, left, right, nx, ny);
|
|
28250
|
+
if (region.some((p) => p.x * nx + p.y * ny < proximal - EPS) || centerRegion.some((p) => !pointInPolygon(p, region)) || region.some(
|
|
28251
|
+
(p) => p.x * nx + p.y * ny >= center - EPS && !pointInPolygon(p, centerRegion)
|
|
28252
|
+
))
|
|
28253
|
+
break;
|
|
28254
|
+
return region;
|
|
28255
|
+
}
|
|
28256
|
+
throw new PcbFoldError({
|
|
28257
|
+
code: "invalid_finite_bend_region",
|
|
28258
|
+
message: "The finite PCB bend zone must remain in one connected board cross-section",
|
|
28259
|
+
bendId
|
|
28260
|
+
});
|
|
28261
|
+
}
|
|
28262
|
+
function polygonsTouch(a, b) {
|
|
28263
|
+
if (a.some((p) => pointInPolygon(p, b)) || b.some((p) => pointInPolygon(p, a)))
|
|
28264
|
+
return true;
|
|
28265
|
+
const cross4 = (p, q, r) => (q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x);
|
|
28266
|
+
for (let i = 0; i < a.length; i++)
|
|
28267
|
+
for (let j = 0; j < b.length; j++) {
|
|
28268
|
+
const p = a[i], q = a[(i + 1) % a.length], r = b[j], s = b[(j + 1) % b.length];
|
|
28269
|
+
if (cross4(p, q, r) * cross4(p, q, s) < -EPS && cross4(r, s, p) * cross4(r, s, q) < -EPS)
|
|
28270
|
+
return true;
|
|
28271
|
+
}
|
|
28272
|
+
return false;
|
|
28273
|
+
}
|
|
28274
|
+
function polygonContainsPolygon(boundary, polygon2) {
|
|
28275
|
+
if (polygon2.some((p) => !pointInPolygon(p, boundary))) return false;
|
|
28276
|
+
for (let i = 0; i < polygon2.length; i++) {
|
|
28277
|
+
const a = polygon2[i], b = polygon2[(i + 1) % polygon2.length];
|
|
28278
|
+
const dx = b.x - a.x, dy = b.y - a.y;
|
|
28279
|
+
const lengthSquared = dx * dx + dy * dy;
|
|
28280
|
+
if (lengthSquared < EPS * EPS) continue;
|
|
28281
|
+
const crossings = [0, 1];
|
|
28282
|
+
for (let j = 0; j < boundary.length; j++) {
|
|
28283
|
+
const c = boundary[j], d = boundary[(j + 1) % boundary.length];
|
|
28284
|
+
const ex = d.x - c.x, ey = d.y - c.y;
|
|
28285
|
+
const denominator = dx * ey - dy * ex;
|
|
28286
|
+
const cx = c.x - a.x, cy = c.y - a.y;
|
|
28287
|
+
if (Math.abs(denominator) < EPS) {
|
|
28288
|
+
if (Math.abs(cx * dy - cy * dx) > EPS) continue;
|
|
28289
|
+
for (const p of [c, d]) {
|
|
28290
|
+
const t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / lengthSquared;
|
|
28291
|
+
if (t > 0 && t < 1) crossings.push(t);
|
|
28292
|
+
}
|
|
28293
|
+
} else {
|
|
28294
|
+
const t = (cx * ey - cy * ex) / denominator;
|
|
28295
|
+
const u = (cx * dy - cy * dx) / denominator;
|
|
28296
|
+
if (t > 0 && t < 1 && u >= -EPS && u <= 1 + EPS) crossings.push(t);
|
|
28297
|
+
}
|
|
28298
|
+
}
|
|
28299
|
+
crossings.sort((a2, b2) => a2 - b2);
|
|
28300
|
+
for (let j = 1; j < crossings.length; j++) {
|
|
28301
|
+
const t = (crossings[j - 1] + crossings[j]) / 2;
|
|
28302
|
+
if (!pointInPolygon({ x: a.x + t * dx, y: a.y + t * dy }, boundary))
|
|
28303
|
+
return false;
|
|
28304
|
+
}
|
|
28305
|
+
}
|
|
28306
|
+
return true;
|
|
28307
|
+
}
|
|
28308
|
+
var EPS2 = 1e-7;
|
|
28309
|
+
function orderPcbBends(bends) {
|
|
28310
|
+
const sorted = [...bends].sort(
|
|
28311
|
+
(a, b) => a.start - b.start || a.nx - b.nx || a.ny - b.ny || a.id.localeCompare(b.id)
|
|
28312
|
+
);
|
|
28313
|
+
const parents = /* @__PURE__ */ new Map();
|
|
28314
|
+
const overlap = (bend) => {
|
|
28315
|
+
throw new PcbFoldError({
|
|
28316
|
+
code: "overlapping_bend_zones",
|
|
28317
|
+
message: "Overlapping PCB bend zones or incompatible moving regions are not supported",
|
|
28318
|
+
bendId: bend.id
|
|
28319
|
+
});
|
|
28320
|
+
};
|
|
28321
|
+
for (let i = 0; i < sorted.length; i++)
|
|
28322
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
28323
|
+
const a = sorted[i], b = sorted[j];
|
|
28324
|
+
if (a.movingOutline && b.movingOutline) {
|
|
28325
|
+
if (!polygonsTouch(a.movingOutline, b.movingOutline)) continue;
|
|
28326
|
+
const aContainsB = polygonContainsPolygon(
|
|
28327
|
+
a.movingOutline,
|
|
28328
|
+
b.movingOutline
|
|
28329
|
+
);
|
|
28330
|
+
const bContainsA = polygonContainsPolygon(
|
|
28331
|
+
b.movingOutline,
|
|
28332
|
+
a.movingOutline
|
|
28333
|
+
);
|
|
28334
|
+
if (aContainsB === bContainsA) overlap(b);
|
|
28335
|
+
const parent = aContainsB ? a : b, child = aContainsB ? b : a;
|
|
28336
|
+
if (child.movingOutline.some(
|
|
28337
|
+
(p) => p.x * parent.nx + p.y * parent.ny < parent.end - EPS2
|
|
28338
|
+
))
|
|
28339
|
+
overlap(child);
|
|
28340
|
+
parents.set(child, [...parents.get(child) ?? [], parent]);
|
|
28341
|
+
} else {
|
|
28342
|
+
if (Math.abs(a.nx - b.nx) > EPS2 || Math.abs(a.ny - b.ny) > EPS2)
|
|
28343
|
+
throw new PcbFoldError({
|
|
28344
|
+
code: "nonparallel_bends",
|
|
28345
|
+
message: "Nonparallel PCB bends or bends with opposite moving directions require a board outline to resolve their moving regions"
|
|
28346
|
+
});
|
|
28347
|
+
if (b.start < a.end - EPS2) overlap(b);
|
|
28348
|
+
parents.set(b, [...parents.get(b) ?? [], a]);
|
|
28349
|
+
}
|
|
28350
|
+
}
|
|
28351
|
+
const ordered = [], visited = /* @__PURE__ */ new Set();
|
|
28352
|
+
const visit = (bend) => {
|
|
28353
|
+
if (visited.has(bend)) return;
|
|
28354
|
+
for (const parent of parents.get(bend) ?? []) visit(parent);
|
|
28355
|
+
visited.add(bend);
|
|
28356
|
+
ordered.push(bend);
|
|
28357
|
+
};
|
|
28358
|
+
for (const bend of sorted) visit(bend);
|
|
28359
|
+
return ordered;
|
|
28360
|
+
}
|
|
28361
|
+
function createPcbFold(records, thickness, { outline } = {}) {
|
|
28135
28362
|
if (!Number.isFinite(thickness) || thickness < 0)
|
|
28136
28363
|
throw new Error("Board thickness must be finite and nonnegative");
|
|
28364
|
+
if (outline && (outline.length < 3 || outline.some((p) => !Number.isFinite(p.x) || !Number.isFinite(p.y))))
|
|
28365
|
+
throw new Error(
|
|
28366
|
+
"A finite board outline with at least three points is required"
|
|
28367
|
+
);
|
|
28137
28368
|
const bends = records.map((b) => {
|
|
28138
28369
|
const { start, end, bend_angle: degrees, bend_radius: radius } = b;
|
|
28139
28370
|
const values = [start?.x, start?.y, end?.x, end?.y, degrees, radius];
|
|
28140
28371
|
if (values.some((v) => !Number.isFinite(v)) || radius <= thickness / 2 || !["left", "right"].includes(b.bend_side)) {
|
|
28141
|
-
throw new
|
|
28142
|
-
|
|
28143
|
-
|
|
28372
|
+
throw new PcbFoldError({
|
|
28373
|
+
code: "invalid_bend_geometry",
|
|
28374
|
+
message: `Invalid PCB bend ${b.pcb_bend_id}: finite geometry and radius greater than half the board thickness are required`,
|
|
28375
|
+
bendId: b.pcb_bend_id
|
|
28376
|
+
});
|
|
28144
28377
|
}
|
|
28145
28378
|
const length6 = Math.hypot(end.x - start.x, end.y - start.y);
|
|
28146
|
-
if (length6 <
|
|
28147
|
-
throw new
|
|
28148
|
-
|
|
28149
|
-
|
|
28379
|
+
if (length6 < EPS2 || Math.abs(degrees) > 180)
|
|
28380
|
+
throw new PcbFoldError({
|
|
28381
|
+
code: "unsupported_bend_geometry",
|
|
28382
|
+
message: `Unsupported PCB bend ${b.pcb_bend_id}: distinct endpoints and angles within \xB1180 degrees are required`,
|
|
28383
|
+
bendId: b.pcb_bend_id
|
|
28384
|
+
});
|
|
28150
28385
|
const sign = b.bend_side === "right" ? 1 : -1;
|
|
28151
28386
|
const nx = sign * (end.y - start.y) / length6;
|
|
28152
28387
|
const ny = -sign * (end.x - start.x) / length6;
|
|
28153
28388
|
const angle3 = degrees * Math.PI / 180;
|
|
28154
28389
|
const width = radius * Math.abs(angle3);
|
|
28155
28390
|
const center = start.x * nx + start.y * ny;
|
|
28156
|
-
|
|
28391
|
+
const geometry = {
|
|
28157
28392
|
id: b.pcb_bend_id,
|
|
28158
28393
|
nx,
|
|
28159
28394
|
ny,
|
|
@@ -28170,21 +28405,21 @@ function createPcbFold(records, thickness) {
|
|
|
28170
28405
|
-ny * end.x + nx * end.y
|
|
28171
28406
|
)
|
|
28172
28407
|
};
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
|
|
28176
|
-
|
|
28177
|
-
|
|
28178
|
-
|
|
28179
|
-
|
|
28180
|
-
|
|
28181
|
-
|
|
28182
|
-
|
|
28183
|
-
|
|
28184
|
-
|
|
28185
|
-
|
|
28186
|
-
|
|
28187
|
-
const distalFirst = [...
|
|
28408
|
+
if (outline && Math.abs(angle3) > EPS2)
|
|
28409
|
+
geometry.movingOutline = getFiniteBendRegion({
|
|
28410
|
+
bendId: b.pcb_bend_id,
|
|
28411
|
+
outline,
|
|
28412
|
+
nx,
|
|
28413
|
+
ny,
|
|
28414
|
+
center,
|
|
28415
|
+
proximal: geometry.start,
|
|
28416
|
+
axisMin: geometry.axisMin,
|
|
28417
|
+
axisMax: geometry.axisMax
|
|
28418
|
+
});
|
|
28419
|
+
return geometry;
|
|
28420
|
+
}).filter((b) => Math.abs(b.angle) > EPS2);
|
|
28421
|
+
const proximalFirst = orderPcbBends(bends);
|
|
28422
|
+
const distalFirst = [...proximalFirst].reverse();
|
|
28188
28423
|
const transform = ({
|
|
28189
28424
|
p,
|
|
28190
28425
|
anchor,
|
|
@@ -28192,6 +28427,7 @@ function createPcbFold(records, thickness) {
|
|
|
28192
28427
|
}) => {
|
|
28193
28428
|
let result = { ...p };
|
|
28194
28429
|
for (const b of distalFirst) {
|
|
28430
|
+
if (b.movingOutline && !pointInPolygon(anchor, b.movingOutline)) continue;
|
|
28195
28431
|
const width = b.end - b.start;
|
|
28196
28432
|
const s = anchor.x * b.nx + anchor.y * b.ny - b.start;
|
|
28197
28433
|
const q = Math.max(0, Math.min(width, s));
|
|
@@ -28232,21 +28468,28 @@ function createPcbFold(records, thickness) {
|
|
|
28232
28468
|
return { x: dot6(axes2[0]), y: dot6(axes2[1]), z: dot6(axes2[2]) };
|
|
28233
28469
|
};
|
|
28234
28470
|
return {
|
|
28235
|
-
bends,
|
|
28471
|
+
bends: proximalFirst,
|
|
28236
28472
|
inversePoint: (p, anchor) => inverse4({ p, anchor, direction: false }),
|
|
28237
28473
|
inverseDirection: (p, anchor) => inverse4({ p, anchor, direction: true }),
|
|
28238
28474
|
point: (p, anchor = p) => transform({ p, anchor, direction: false }),
|
|
28239
28475
|
direction: (p, anchor) => transform({ p, anchor, direction: true }),
|
|
28240
28476
|
assertRigid(points, label) {
|
|
28241
28477
|
for (const b of bends) {
|
|
28242
|
-
const
|
|
28243
|
-
|
|
28244
|
-
|
|
28478
|
+
const width = b.end - b.start;
|
|
28479
|
+
const progress = points.map(
|
|
28480
|
+
(p) => b.movingOutline && !pointInPolygon(p, b.movingOutline) ? 0 : Math.max(0, Math.min(width, p.x * b.nx + p.y * b.ny - b.start))
|
|
28481
|
+
);
|
|
28482
|
+
if (Math.max(...progress) > EPS2 && Math.min(...progress) < width - EPS2)
|
|
28483
|
+
throw new PcbFoldError({
|
|
28484
|
+
code: "rigid_bend_zone_intersection",
|
|
28485
|
+
message: `${label} intersects PCB bend zone ${b.id}`,
|
|
28486
|
+
bendId: b.id
|
|
28487
|
+
});
|
|
28245
28488
|
}
|
|
28246
28489
|
}
|
|
28247
28490
|
};
|
|
28248
28491
|
}
|
|
28249
|
-
var
|
|
28492
|
+
var EPS3 = 1e-7;
|
|
28250
28493
|
function boundsOfTriangles(triangles) {
|
|
28251
28494
|
const points = triangles.flatMap((t) => t.vertices);
|
|
28252
28495
|
if (!points.length)
|
|
@@ -28276,7 +28519,7 @@ function foldSurfaceMesh(mesh, fold) {
|
|
|
28276
28519
|
d
|
|
28277
28520
|
}) => {
|
|
28278
28521
|
const distances = polygon2.map(({ p }) => nx * p.x + ny * p.y - d);
|
|
28279
|
-
if (Math.min(...distances) >= -
|
|
28522
|
+
if (Math.min(...distances) >= -EPS3 || Math.max(...distances) <= EPS3)
|
|
28280
28523
|
return [polygon2];
|
|
28281
28524
|
const halves = [[], []];
|
|
28282
28525
|
for (let side = 0; side < 2; side++) {
|
|
@@ -28284,8 +28527,8 @@ function foldSurfaceMesh(mesh, fold) {
|
|
|
28284
28527
|
for (let i = 0; i < polygon2.length; i++) {
|
|
28285
28528
|
const a = polygon2[i], next = (i + 1) % polygon2.length, b = polygon2[next];
|
|
28286
28529
|
const da = distances[i] * sign, db = distances[next] * sign;
|
|
28287
|
-
if (da >= -
|
|
28288
|
-
if (da >
|
|
28530
|
+
if (da >= -EPS3) halves[side].push(a);
|
|
28531
|
+
if (da > EPS3 && db < -EPS3 || da < -EPS3 && db > EPS3) {
|
|
28289
28532
|
const t = da / (da - db);
|
|
28290
28533
|
halves[side].push({
|
|
28291
28534
|
p: {
|
|
@@ -28321,14 +28564,17 @@ function foldSurfaceMesh(mesh, fold) {
|
|
|
28321
28564
|
);
|
|
28322
28565
|
for (const polygon2 of polygons) {
|
|
28323
28566
|
for (const b of fold.bends) {
|
|
28567
|
+
if (b.movingOutline) continue;
|
|
28324
28568
|
const mid = polygon2.reduce((sum, { p }) => sum + p.x * b.nx + p.y * b.ny, 0) / polygon2.length;
|
|
28325
|
-
if (mid > b.start +
|
|
28569
|
+
if (mid > b.start + EPS3 && mid < b.end - EPS3 && polygon2.some(({ p }) => {
|
|
28326
28570
|
const along = -b.ny * p.x + b.nx * p.y;
|
|
28327
|
-
return along < b.axisMin -
|
|
28571
|
+
return along < b.axisMin - EPS3 || along > b.axisMax + EPS3;
|
|
28328
28572
|
}))
|
|
28329
|
-
throw new
|
|
28330
|
-
|
|
28331
|
-
|
|
28573
|
+
throw new PcbFoldError({
|
|
28574
|
+
code: "incomplete_bend_cross_section",
|
|
28575
|
+
message: `PCB bend ${b.id} must span the full board cross-section through its bend zone`,
|
|
28576
|
+
bendId: b.id
|
|
28577
|
+
});
|
|
28332
28578
|
}
|
|
28333
28579
|
for (let i = 1; i + 1 < polygon2.length; i++) {
|
|
28334
28580
|
const verts = [polygon2[0], polygon2[i], polygon2[i + 1]];
|
|
@@ -28454,7 +28700,12 @@ function getCadFoldContext(cadComponent, json) {
|
|
|
28454
28700
|
(e) => e.type === "pcb_bend" && e.pcb_board_id === board.pcb_board_id
|
|
28455
28701
|
);
|
|
28456
28702
|
return {
|
|
28457
|
-
fold: createPcbFold(bends, board.thickness ?? 1.6
|
|
28703
|
+
fold: createPcbFold(bends, board.thickness ?? 1.6, {
|
|
28704
|
+
outline: board.outline?.map((p) => ({
|
|
28705
|
+
x: p.x - board.center.x,
|
|
28706
|
+
y: p.y - board.center.y
|
|
28707
|
+
}))
|
|
28708
|
+
}),
|
|
28458
28709
|
boardCenter: board.center,
|
|
28459
28710
|
flatMount: pcb.center,
|
|
28460
28711
|
defaultRotation: {
|
|
@@ -28551,6 +28802,43 @@ function extrudePolygon({
|
|
|
28551
28802
|
}
|
|
28552
28803
|
return { triangles, boundingBox: boundsOfTriangles(triangles) };
|
|
28553
28804
|
}
|
|
28805
|
+
function foldRigidMesh(mesh, fold, { flatAnchor, label = "Rigid mesh", mount }) {
|
|
28806
|
+
if (mount) fold.assertRigid([mount], `${label} mount`);
|
|
28807
|
+
fold.assertRigid(
|
|
28808
|
+
mesh.triangles.flatMap((t) => t.vertices),
|
|
28809
|
+
label
|
|
28810
|
+
);
|
|
28811
|
+
const triangles = mesh.triangles.map((t) => ({
|
|
28812
|
+
...t,
|
|
28813
|
+
vertices: t.vertices.map(
|
|
28814
|
+
(p) => fold.point(p, flatAnchor)
|
|
28815
|
+
),
|
|
28816
|
+
normal: fold.direction(t.normal, flatAnchor)
|
|
28817
|
+
}));
|
|
28818
|
+
return { ...mesh, triangles, boundingBox: boundsOfTriangles(triangles) };
|
|
28819
|
+
}
|
|
28820
|
+
function getStiffenerOutline(stiffener) {
|
|
28821
|
+
if (stiffener.shape === "polygon") return stiffener.outline;
|
|
28822
|
+
if (!(stiffener.width > 0 && stiffener.height > 0))
|
|
28823
|
+
throw new Error("Invalid stiffener dimensions");
|
|
28824
|
+
const a = (stiffener.rotation ?? 0) * Math.PI / 180;
|
|
28825
|
+
return [
|
|
28826
|
+
[-1, -1],
|
|
28827
|
+
[1, -1],
|
|
28828
|
+
[1, 1],
|
|
28829
|
+
[-1, 1]
|
|
28830
|
+
].map(([x, y]) => ({
|
|
28831
|
+
x: stiffener.center.x + x * stiffener.width / 2 * Math.cos(a) - y * stiffener.height / 2 * Math.sin(a),
|
|
28832
|
+
y: stiffener.center.y + x * stiffener.width / 2 * Math.sin(a) + y * stiffener.height / 2 * Math.cos(a)
|
|
28833
|
+
}));
|
|
28834
|
+
}
|
|
28835
|
+
function getStiffenerAnchor(outline) {
|
|
28836
|
+
return {
|
|
28837
|
+
x: outline.reduce((s, p) => s + p.x, 0) / outline.length,
|
|
28838
|
+
y: outline.reduce((s, p) => s + p.y, 0) / outline.length,
|
|
28839
|
+
z: 0
|
|
28840
|
+
};
|
|
28841
|
+
}
|
|
28554
28842
|
function createStiffenerMesh({
|
|
28555
28843
|
stiffener,
|
|
28556
28844
|
boardThickness,
|
|
@@ -28559,20 +28847,7 @@ function createStiffenerMesh({
|
|
|
28559
28847
|
const adhesive = stiffener.adhesive_thickness ?? 0;
|
|
28560
28848
|
if (!Number.isFinite(adhesive) || adhesive < 0 || !Number.isFinite(stiffener.thickness) || stiffener.thickness <= 0 || !["top", "bottom"].includes(stiffener.layer))
|
|
28561
28849
|
throw new Error(`Invalid PCB stiffener ${stiffener.pcb_stiffener_id}`);
|
|
28562
|
-
const outline = stiffener
|
|
28563
|
-
if (!(stiffener.width > 0 && stiffener.height > 0))
|
|
28564
|
-
throw new Error("Invalid stiffener dimensions");
|
|
28565
|
-
const a = (stiffener.rotation ?? 0) * Math.PI / 180;
|
|
28566
|
-
return [
|
|
28567
|
-
[-1, -1],
|
|
28568
|
-
[1, -1],
|
|
28569
|
-
[1, 1],
|
|
28570
|
-
[-1, 1]
|
|
28571
|
-
].map(([x, y]) => ({
|
|
28572
|
-
x: stiffener.center.x + x * stiffener.width / 2 * Math.cos(a) - y * stiffener.height / 2 * Math.sin(a),
|
|
28573
|
-
y: stiffener.center.y + x * stiffener.width / 2 * Math.sin(a) + y * stiffener.height / 2 * Math.cos(a)
|
|
28574
|
-
}));
|
|
28575
|
-
})();
|
|
28850
|
+
const outline = getStiffenerOutline(stiffener);
|
|
28576
28851
|
const near = boardThickness / 2 + adhesive, far = near + stiffener.thickness;
|
|
28577
28852
|
const mesh = extrudePolygon({
|
|
28578
28853
|
outline,
|
|
@@ -28580,21 +28855,10 @@ function createStiffenerMesh({
|
|
|
28580
28855
|
top: stiffener.layer === "top" ? far : -near
|
|
28581
28856
|
});
|
|
28582
28857
|
if (!fold) return mesh;
|
|
28583
|
-
fold
|
|
28584
|
-
|
|
28585
|
-
stiffener.pcb_stiffener_id
|
|
28586
|
-
);
|
|
28587
|
-
const anchor = {
|
|
28588
|
-
x: outline.reduce((s, p) => s + p.x, 0) / outline.length,
|
|
28589
|
-
y: outline.reduce((s, p) => s + p.y, 0) / outline.length,
|
|
28590
|
-
z: 0
|
|
28591
|
-
};
|
|
28592
|
-
const triangles = mesh.triangles.map((t) => ({
|
|
28593
|
-
...t,
|
|
28594
|
-
vertices: t.vertices.map((p) => fold.point(p, anchor)),
|
|
28595
|
-
normal: fold.direction(t.normal, anchor)
|
|
28596
|
-
}));
|
|
28597
|
-
return { ...mesh, triangles, boundingBox: boundsOfTriangles(triangles) };
|
|
28858
|
+
return foldRigidMesh(mesh, fold, {
|
|
28859
|
+
flatAnchor: getStiffenerAnchor(outline),
|
|
28860
|
+
label: stiffener.pcb_stiffener_id
|
|
28861
|
+
});
|
|
28598
28862
|
}
|
|
28599
28863
|
|
|
28600
28864
|
// src/CadViewerManifold.tsx
|
|
@@ -29984,14 +30248,60 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson, visibility) => {
|
|
|
29984
30248
|
|
|
29985
30249
|
// src/utils/flex-meshes.ts
|
|
29986
30250
|
import * as THREE53 from "three";
|
|
29987
|
-
function
|
|
30251
|
+
function restoreOutlinePrecision(point, outline, tolerance) {
|
|
30252
|
+
let distanceSquared = tolerance * tolerance;
|
|
30253
|
+
let closest;
|
|
30254
|
+
for (let i = 0; i < outline.length; i++) {
|
|
30255
|
+
const a = outline[i];
|
|
30256
|
+
const b = outline[(i + 1) % outline.length];
|
|
30257
|
+
const dx = b.x - a.x;
|
|
30258
|
+
const dy = b.y - a.y;
|
|
30259
|
+
const lengthSquared = dx * dx + dy * dy;
|
|
30260
|
+
if (lengthSquared === 0) continue;
|
|
30261
|
+
const t = Math.max(
|
|
30262
|
+
0,
|
|
30263
|
+
Math.min(
|
|
30264
|
+
1,
|
|
30265
|
+
((point.x - a.x) * dx + (point.y - a.y) * dy) / lengthSquared
|
|
30266
|
+
)
|
|
30267
|
+
);
|
|
30268
|
+
const x = a.x + t * dx;
|
|
30269
|
+
const y = a.y + t * dy;
|
|
30270
|
+
const distance5 = (x - point.x) ** 2 + (y - point.y) ** 2;
|
|
30271
|
+
if (distance5 <= distanceSquared) {
|
|
30272
|
+
distanceSquared = distance5;
|
|
30273
|
+
closest = { x, y };
|
|
30274
|
+
}
|
|
30275
|
+
}
|
|
30276
|
+
if (closest) point.set(closest.x, closest.y, point.z);
|
|
30277
|
+
}
|
|
30278
|
+
function meshToSurface(mesh, center, outline) {
|
|
29988
30279
|
mesh.updateMatrix();
|
|
29989
30280
|
const geometry = mesh.geometry.index ? mesh.geometry.toNonIndexed() : mesh.geometry;
|
|
29990
30281
|
const position = geometry.getAttribute("position"), uv = geometry.getAttribute("uv");
|
|
29991
30282
|
const triangles = [];
|
|
29992
30283
|
for (let i = 0; i < position.count; i += 3) {
|
|
29993
30284
|
const vertices = [0, 1, 2].map((j) => {
|
|
29994
|
-
const v = new THREE53.Vector3().fromBufferAttribute(position, i + j)
|
|
30285
|
+
const v = new THREE53.Vector3().fromBufferAttribute(position, i + j);
|
|
30286
|
+
const localMagnitude = Math.max(
|
|
30287
|
+
1,
|
|
30288
|
+
Math.abs(v.x),
|
|
30289
|
+
Math.abs(v.y),
|
|
30290
|
+
Math.abs(v.z)
|
|
30291
|
+
);
|
|
30292
|
+
v.applyMatrix4(mesh.matrix);
|
|
30293
|
+
if (outline && position.array instanceof Float32Array) {
|
|
30294
|
+
const m = mesh.matrix.elements;
|
|
30295
|
+
const xyScale = Math.max(
|
|
30296
|
+
Math.abs(m[0]) + Math.abs(m[4]) + Math.abs(m[8]),
|
|
30297
|
+
Math.abs(m[1]) + Math.abs(m[5]) + Math.abs(m[9])
|
|
30298
|
+
);
|
|
30299
|
+
restoreOutlinePrecision(
|
|
30300
|
+
v,
|
|
30301
|
+
outline,
|
|
30302
|
+
localMagnitude * 2 ** -22 * xyScale + Math.max(1, Math.abs(v.x), Math.abs(v.y)) * Number.EPSILON * 4
|
|
30303
|
+
);
|
|
30304
|
+
}
|
|
29995
30305
|
return { x: v.x - center.x, y: v.y - center.y, z: v.z };
|
|
29996
30306
|
});
|
|
29997
30307
|
const [a, b, c] = vertices;
|
|
@@ -30048,13 +30358,21 @@ function createFlexMeshes(geometryMeshes, textureMeshes, json, foldPcbs) {
|
|
|
30048
30358
|
throw new Error(
|
|
30049
30359
|
"Flex rendering requires one board, matching board references, and no panel"
|
|
30050
30360
|
);
|
|
30051
|
-
const fold = foldPcbs ? createPcbFold(bends, board.thickness ?? 1.6
|
|
30361
|
+
const fold = foldPcbs ? createPcbFold(bends, board.thickness ?? 1.6, {
|
|
30362
|
+
outline: board.outline?.map((point) => ({
|
|
30363
|
+
x: point.x - board.center.x,
|
|
30364
|
+
y: point.y - board.center.y
|
|
30365
|
+
}))
|
|
30366
|
+
}) : void 0;
|
|
30052
30367
|
const body = geometryMeshes.find((m) => m.name === "board-geom");
|
|
30053
|
-
const flatBody = body ? meshToSurface(body, board.center) : void 0;
|
|
30368
|
+
const flatBody = fold && body ? meshToSurface(body, board.center, board.outline) : void 0;
|
|
30054
30369
|
const geometry = fold ? geometryMeshes.map(
|
|
30055
30370
|
(mesh) => replaceGeometry(
|
|
30056
30371
|
mesh,
|
|
30057
|
-
foldSurfaceMesh(
|
|
30372
|
+
foldSurfaceMesh(
|
|
30373
|
+
mesh === body && flatBody ? flatBody : meshToSurface(mesh, board.center, board.outline),
|
|
30374
|
+
fold
|
|
30375
|
+
),
|
|
30058
30376
|
board
|
|
30059
30377
|
)
|
|
30060
30378
|
) : [...geometryMeshes];
|
|
@@ -30226,7 +30544,7 @@ function loadManifold() {
|
|
|
30226
30544
|
const factory = existing ?? (await import(
|
|
30227
30545
|
/* @vite-ignore */
|
|
30228
30546
|
/* webpackIgnore: true */
|
|
30229
|
-
getRuntimeAssetUrl("manifold-3d", "3.
|
|
30547
|
+
getRuntimeAssetUrl("manifold-3d", "3.5.1", "manifold.js")
|
|
30230
30548
|
)).default;
|
|
30231
30549
|
const module = await factory();
|
|
30232
30550
|
module.setup();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/3d-viewer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.628",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/tscircuit/3d-viewer"
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@tscircuit/circuit-json-util": "*"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@tscircuit/flex-utils": "https://jscdn.tscircuit.com/@tscircuit/flex-utils/0.0.
|
|
58
|
+
"@tscircuit/flex-utils": "https://jscdn.tscircuit.com/@tscircuit/flex-utils/0.0.8.tgz",
|
|
59
59
|
"@biomejs/biome": "^2.1.4",
|
|
60
60
|
"@chromatic-com/storybook": "^1.9.0",
|
|
61
61
|
"@jscad/modeling": "^2.12.5",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"debug": "^4.4.0",
|
|
82
82
|
"jscad-planner": "^0.0.18",
|
|
83
83
|
"jsdom": "^26.0.0",
|
|
84
|
-
"manifold-3d": "
|
|
84
|
+
"manifold-3d": "3.5.1",
|
|
85
85
|
"react-use-gesture": "^9.1.3",
|
|
86
86
|
"semver": "^7.7.0",
|
|
87
87
|
"strip-ansi": "^7.1.0",
|