circuit-json-to-gltf 0.0.71 → 0.0.72

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/dist/index.d.ts CHANGED
@@ -196,6 +196,38 @@ declare const COORDINATE_TRANSFORMS: {
196
196
  readonly OBJ_Z_UP_TO_Y_UP: CoordinateTransformConfig;
197
197
  };
198
198
 
199
+ interface CameraFitOptions {
200
+ /**
201
+ * Target-to-camera direction vector used for solved camera position.
202
+ */
203
+ direction?: readonly [number, number, number];
204
+ /**
205
+ * Vertical field of view in degrees.
206
+ */
207
+ fov?: number;
208
+ /**
209
+ * Aspect ratio (width / height) used for horizontal fit calculations.
210
+ */
211
+ aspectRatio?: number;
212
+ /**
213
+ * Focal length in millimeters. If provided with sensorHeight,
214
+ * it is used instead of fov.
215
+ */
216
+ focalLength?: number;
217
+ /**
218
+ * Sensor height in millimeters for focalLength->fov conversion.
219
+ */
220
+ sensorHeight?: number;
221
+ }
222
+ /**
223
+ * Calculate optimal camera position for PCB viewing based on circuit dimensions
224
+ */
225
+ declare function getBestCameraPosition(circuitJson: CircuitJson): {
226
+ camPos: readonly [number, number, number];
227
+ lookAt: readonly [number, number, number];
228
+ fov: number;
229
+ };
230
+
199
231
  declare function convertCircuitJsonToGltf(circuitJson: CircuitJson, options?: ConversionOptions): Promise<ArrayBuffer | object>;
200
232
 
201
233
  interface Point {
@@ -208,4 +240,4 @@ interface BRepShape {
208
240
  is_negative?: boolean;
209
241
  }
210
242
 
211
- export { type BRepShape, type BoardRenderOptions, type BoundingBox, type Box3D, COORDINATE_TRANSFORMS, type Camera3D, type CircuitTo3DOptions, type Color, type ConversionOptions, type CoordinateTransformConfig, type GLTFExportOptions, type LayerRef, type Light3D, type OBJMaterial, type OBJMesh, type Point, type Point3, type STLMesh, type Scene3D, type Size3, type Triangle, applyCoordinateTransform, clearGLBCache, clearOBJCache, clearSTLCache, convertCircuitJsonTo3D, convertCircuitJsonToGltf, convertSceneToGLTF, loadGLB, loadOBJ, loadSTL, renderBoardLayer, renderBoardTextures, transformTriangles };
243
+ export { type BRepShape, type BoardRenderOptions, type BoundingBox, type Box3D, COORDINATE_TRANSFORMS, type Camera3D, type CameraFitOptions, type CircuitTo3DOptions, type Color, type ConversionOptions, type CoordinateTransformConfig, type GLTFExportOptions, type LayerRef, type Light3D, type OBJMaterial, type OBJMesh, type Point, type Point3, type STLMesh, type Scene3D, type Size3, type Triangle, applyCoordinateTransform, clearGLBCache, clearOBJCache, clearSTLCache, convertCircuitJsonTo3D, convertCircuitJsonToGltf, convertSceneToGLTF, getBestCameraPosition, loadGLB, loadOBJ, loadSTL, renderBoardLayer, renderBoardTextures, transformTriangles };
package/dist/index.js CHANGED
@@ -446,8 +446,8 @@ var require_add2 = __commonJS({
446
446
  var require_dot = __commonJS({
447
447
  "node_modules/@jscad/modeling/src/maths/vec3/dot.js"(exports, module) {
448
448
  "use strict";
449
- var dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
450
- module.exports = dot;
449
+ var dot2 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
450
+ module.exports = dot2;
451
451
  }
452
452
  });
453
453
 
@@ -455,7 +455,7 @@ var require_dot = __commonJS({
455
455
  var require_angle = __commonJS({
456
456
  "node_modules/@jscad/modeling/src/maths/vec3/angle.js"(exports, module) {
457
457
  "use strict";
458
- var dot = require_dot();
458
+ var dot2 = require_dot();
459
459
  var angle = (a, b) => {
460
460
  const ax = a[0];
461
461
  const ay = a[1];
@@ -466,7 +466,7 @@ var require_angle = __commonJS({
466
466
  const mag1 = Math.sqrt(ax * ax + ay * ay + az * az);
467
467
  const mag2 = Math.sqrt(bx * bx + by * by + bz * bz);
468
468
  const mag = mag1 * mag2;
469
- const cosine = mag && dot(a, b) / mag;
469
+ const cosine = mag && dot2(a, b) / mag;
470
470
  return Math.acos(Math.min(Math.max(cosine, -1), 1));
471
471
  };
472
472
  module.exports = angle;
@@ -516,7 +516,7 @@ var require_copy2 = __commonJS({
516
516
  var require_cross = __commonJS({
517
517
  "node_modules/@jscad/modeling/src/maths/vec3/cross.js"(exports, module) {
518
518
  "use strict";
519
- var cross = (out, a, b) => {
519
+ var cross2 = (out, a, b) => {
520
520
  const ax = a[0];
521
521
  const ay = a[1];
522
522
  const az = a[2];
@@ -528,7 +528,7 @@ var require_cross = __commonJS({
528
528
  out[2] = ax * by - ay * bx;
529
529
  return out;
530
530
  };
531
- module.exports = cross;
531
+ module.exports = cross2;
532
532
  }
533
533
  });
534
534
 
@@ -724,13 +724,13 @@ var require_orthogonal = __commonJS({
724
724
  "use strict";
725
725
  var abs = require_abs();
726
726
  var create = require_create2();
727
- var cross = require_cross();
727
+ var cross2 = require_cross();
728
728
  var orthogonal = (out, vector) => {
729
729
  const bV = abs(create(), vector);
730
730
  const b0 = 0 + (bV[0] < bV[1] && bV[0] < bV[2]);
731
731
  const b1 = 0 + (bV[1] <= bV[0] && bV[1] < bV[2]);
732
732
  const b2 = 0 + (bV[2] <= bV[0] && bV[2] <= bV[1]);
733
- return cross(out, vector, [b0, b1, b2]);
733
+ return cross2(out, vector, [b0, b1, b2]);
734
734
  };
735
735
  module.exports = orthogonal;
736
736
  }
@@ -1650,13 +1650,13 @@ var require_copy3 = __commonJS({
1650
1650
  var require_cross2 = __commonJS({
1651
1651
  "node_modules/@jscad/modeling/src/maths/vec2/cross.js"(exports, module) {
1652
1652
  "use strict";
1653
- var cross = (out, a, b) => {
1653
+ var cross2 = (out, a, b) => {
1654
1654
  out[0] = 0;
1655
1655
  out[1] = 0;
1656
1656
  out[2] = a[0] * b[1] - a[1] * b[0];
1657
1657
  return out;
1658
1658
  };
1659
- module.exports = cross;
1659
+ module.exports = cross2;
1660
1660
  }
1661
1661
  });
1662
1662
 
@@ -1690,8 +1690,8 @@ var require_divide2 = __commonJS({
1690
1690
  var require_dot2 = __commonJS({
1691
1691
  "node_modules/@jscad/modeling/src/maths/vec2/dot.js"(exports, module) {
1692
1692
  "use strict";
1693
- var dot = (a, b) => a[0] * b[0] + a[1] * b[1];
1694
- module.exports = dot;
1693
+ var dot2 = (a, b) => a[0] * b[0] + a[1] * b[1];
1694
+ module.exports = dot2;
1695
1695
  }
1696
1696
  });
1697
1697
 
@@ -2401,7 +2401,7 @@ var require_create5 = __commonJS({
2401
2401
  var require_point_line_distance = __commonJS({
2402
2402
  "node_modules/@jscad/modeling/src/operations/hulls/quickhull/point-line-distance.js"(exports, module) {
2403
2403
  "use strict";
2404
- var cross = require_cross();
2404
+ var cross2 = require_cross();
2405
2405
  var subtract3 = require_subtract();
2406
2406
  var squaredLength = require_squaredLength();
2407
2407
  var distanceSquared = (p, a, b) => {
@@ -2410,7 +2410,7 @@ var require_point_line_distance = __commonJS({
2410
2410
  const cr = [];
2411
2411
  subtract3(ab, b, a);
2412
2412
  subtract3(ap, p, a);
2413
- const area = squaredLength(cross(cr, ap, ab));
2413
+ const area = squaredLength(cross2(cr, ap, ab));
2414
2414
  const s = squaredLength(ab);
2415
2415
  if (s === 0) {
2416
2416
  throw Error("a and b are the same point");
@@ -2426,14 +2426,14 @@ var require_point_line_distance = __commonJS({
2426
2426
  var require_get_plane_normal = __commonJS({
2427
2427
  "node_modules/@jscad/modeling/src/operations/hulls/quickhull/get-plane-normal.js"(exports, module) {
2428
2428
  "use strict";
2429
- var cross = require_cross();
2429
+ var cross2 = require_cross();
2430
2430
  var normalize = require_normalize();
2431
2431
  var subtract3 = require_subtract();
2432
2432
  var planeNormal = (out, point1, point2, point3) => {
2433
2433
  const tmp = [0, 0, 0];
2434
2434
  subtract3(out, point1, point2);
2435
2435
  subtract3(tmp, point2, point3);
2436
- cross(out, out, tmp);
2436
+ cross2(out, out, tmp);
2437
2437
  return normalize(out, out);
2438
2438
  };
2439
2439
  module.exports = planeNormal;
@@ -2641,8 +2641,8 @@ var require_Face = __commonJS({
2641
2641
  "use strict";
2642
2642
  var add = require_add2();
2643
2643
  var copy = require_copy2();
2644
- var cross = require_cross();
2645
- var dot = require_dot();
2644
+ var cross2 = require_cross();
2645
+ var dot2 = require_dot();
2646
2646
  var length = require_length();
2647
2647
  var normalize = require_normalize();
2648
2648
  var scale = require_scale();
@@ -2688,7 +2688,7 @@ var require_Face = __commonJS({
2688
2688
  while (e2 !== e0) {
2689
2689
  copy(v1, v2);
2690
2690
  subtract3(v2, e2.head().point, e0.head().point);
2691
- add(this.normal, this.normal, cross(t, v1, v2));
2691
+ add(this.normal, this.normal, cross2(t, v1, v2));
2692
2692
  e2 = e2.next;
2693
2693
  this.nVertices += 1;
2694
2694
  }
@@ -2714,7 +2714,7 @@ var require_Face = __commonJS({
2714
2714
  const maxVector = subtract3([], p2, p1);
2715
2715
  const maxLength = Math.sqrt(maxSquaredLength);
2716
2716
  scale(maxVector, maxVector, 1 / maxLength);
2717
- const maxProjection = dot(this.normal, maxVector);
2717
+ const maxProjection = dot2(this.normal, maxVector);
2718
2718
  scale(maxVector, maxVector, -maxProjection);
2719
2719
  add(this.normal, this.normal, maxVector);
2720
2720
  normalize(this.normal, this.normal);
@@ -2736,10 +2736,10 @@ var require_Face = __commonJS({
2736
2736
  this.computeNormal();
2737
2737
  }
2738
2738
  this.computeCentroid();
2739
- this.offset = dot(this.normal, this.centroid);
2739
+ this.offset = dot2(this.normal, this.centroid);
2740
2740
  }
2741
2741
  distanceToPlane(point) {
2742
- return dot(this.normal, point) - this.offset;
2742
+ return dot2(this.normal, point) - this.offset;
2743
2743
  }
2744
2744
  /**
2745
2745
  * @private
@@ -2848,7 +2848,7 @@ var require_Face = __commonJS({
2848
2848
  var require_QuickHull = __commonJS({
2849
2849
  "node_modules/@jscad/modeling/src/operations/hulls/quickhull/QuickHull.js"(exports, module) {
2850
2850
  "use strict";
2851
- var dot = require_dot();
2851
+ var dot2 = require_dot();
2852
2852
  var pointLineDistance = require_point_line_distance();
2853
2853
  var getPlaneNormal = require_get_plane_normal();
2854
2854
  var VertexList = require_VertexList();
@@ -3059,12 +3059,12 @@ var require_QuickHull = __commonJS({
3059
3059
  }
3060
3060
  }
3061
3061
  const normal = getPlaneNormal([], v0.point, v1.point, v2.point);
3062
- const distPO = dot(v0.point, normal);
3062
+ const distPO = dot2(v0.point, normal);
3063
3063
  maxDistance = -1;
3064
3064
  for (i = 0; i < this.vertices.length; i += 1) {
3065
3065
  const vertex = this.vertices[i];
3066
3066
  if (vertex !== v0 && vertex !== v1 && vertex !== v2) {
3067
- const distance = Math.abs(dot(normal, vertex.point) - distPO);
3067
+ const distance = Math.abs(dot2(normal, vertex.point) - distPO);
3068
3068
  if (distance > maxDistance) {
3069
3069
  maxDistance = distance;
3070
3070
  v3 = vertex;
@@ -3072,7 +3072,7 @@ var require_QuickHull = __commonJS({
3072
3072
  }
3073
3073
  }
3074
3074
  const faces = [];
3075
- if (dot(v3.point, normal) - distPO < 0) {
3075
+ if (dot2(v3.point, normal) - distPO < 0) {
3076
3076
  faces.push(
3077
3077
  Face.createTriangle(v0, v1, v2),
3078
3078
  Face.createTriangle(v3, v1, v0),
@@ -4004,8 +4004,8 @@ var require_measureBoundingBox = __commonJS({
4004
4004
  var require_dot3 = __commonJS({
4005
4005
  "node_modules/@jscad/modeling/src/maths/vec4/dot.js"(exports, module) {
4006
4006
  "use strict";
4007
- var dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
4008
- module.exports = dot;
4007
+ var dot2 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
4008
+ module.exports = dot2;
4009
4009
  }
4010
4010
  });
4011
4011
 
@@ -4112,10 +4112,10 @@ var require_measureSignedVolume = __commonJS({
4112
4112
  var measureSignedVolume = (polygon3) => {
4113
4113
  let signedVolume = 0;
4114
4114
  const vertices = polygon3.vertices;
4115
- const cross = vec3.create();
4115
+ const cross2 = vec3.create();
4116
4116
  for (let i = 0; i < vertices.length - 2; i++) {
4117
- vec3.cross(cross, vertices[i + 1], vertices[i + 2]);
4118
- signedVolume += vec3.dot(vertices[0], cross);
4117
+ vec3.cross(cross2, vertices[i + 1], vertices[i + 2]);
4118
+ signedVolume += vec3.dot(vertices[0], cross2);
4119
4119
  }
4120
4120
  signedVolume /= 6;
4121
4121
  return signedVolume;
@@ -11871,8 +11871,8 @@ var require_expandShell = __commonJS({
11871
11871
  let bestzaxisorthogonality = 0;
11872
11872
  for (let i = 1; i < planes.length; i++) {
11873
11873
  const normal = planes[i];
11874
- const cross = vec3.cross(v1, xaxis, normal);
11875
- const crosslength = vec3.length(cross);
11874
+ const cross2 = vec3.cross(v1, xaxis, normal);
11875
+ const crosslength = vec3.length(cross2);
11876
11876
  if (crosslength > 0.05) {
11877
11877
  if (crosslength > bestzaxisorthogonality) {
11878
11878
  bestzaxisorthogonality = crosslength;
@@ -15264,16 +15264,16 @@ var geom3ToTriangles = (geometry, polygons) => {
15264
15264
  next2[1] - base[1],
15265
15265
  next2[2] - base[2]
15266
15266
  ];
15267
- const cross = [
15267
+ const cross2 = [
15268
15268
  ab[1] * ac[2] - ab[2] * ac[1],
15269
15269
  ab[2] * ac[0] - ab[0] * ac[2],
15270
15270
  ab[0] * ac[1] - ab[1] * ac[0]
15271
15271
  ];
15272
- const length = Math.sqrt(cross[0] ** 2 + cross[1] ** 2 + cross[2] ** 2) || 1;
15272
+ const length = Math.sqrt(cross2[0] ** 2 + cross2[1] ** 2 + cross2[2] ** 2) || 1;
15273
15273
  const normal = {
15274
- x: cross[0] / length,
15275
- y: cross[1] / length,
15276
- z: cross[2] / length
15274
+ x: cross2[0] / length,
15275
+ y: cross2[1] / length,
15276
+ z: cross2[2] / length
15277
15277
  };
15278
15278
  for (let i = 1; i < poly.vertices.length - 1; i++) {
15279
15279
  const v1 = poly.vertices[i];
@@ -17190,6 +17190,130 @@ async function convertSceneToGLTF(scene, options = {}) {
17190
17190
  return result;
17191
17191
  }
17192
17192
 
17193
+ // lib/utils/camera-position.ts
17194
+ var DEFAULT_CAMERA_DIRECTION = [-0.7, 1.2, -0.8];
17195
+ function normalizeVector([x, y, z]) {
17196
+ const length = Math.hypot(x, y, z);
17197
+ if (length === 0) {
17198
+ return [0, 1, 0];
17199
+ }
17200
+ return [x / length, y / length, z / length];
17201
+ }
17202
+ function dot([ax, ay, az], [bx, by, bz]) {
17203
+ return ax * bx + ay * by + az * bz;
17204
+ }
17205
+ function cross([ax, ay, az], [bx, by, bz]) {
17206
+ return [ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx];
17207
+ }
17208
+ function getVerticalFovRadians(opts) {
17209
+ if (opts?.focalLength !== void 0 && opts.sensorHeight !== void 0 && opts.focalLength > 0 && opts.sensorHeight > 0) {
17210
+ return 2 * Math.atan(opts.sensorHeight / (2 * opts.focalLength));
17211
+ }
17212
+ const fovDegrees = opts?.fov ?? 50;
17213
+ const fovRadians = fovDegrees * Math.PI / 180;
17214
+ if (!Number.isFinite(fovRadians) || fovRadians <= 0) {
17215
+ return 50 * Math.PI / 180;
17216
+ }
17217
+ return Math.min(Math.max(fovRadians, 0.01), Math.PI - 0.01);
17218
+ }
17219
+ function getVerticalFovDegrees(opts) {
17220
+ return getVerticalFovRadians(opts) * 180 / Math.PI;
17221
+ }
17222
+ function getRequiredDistanceForFrustum(corners, cameraDirection, right, up, tanHalfHorizontal, tanHalfVertical) {
17223
+ let requiredDistance = 0;
17224
+ for (const corner of corners) {
17225
+ const u = [corner[0], corner[1], corner[2]];
17226
+ const un = dot(u, cameraDirection);
17227
+ const ur = Math.abs(dot(u, right));
17228
+ const uu = Math.abs(dot(u, up));
17229
+ const distanceForHorizontal = un + ur / tanHalfHorizontal;
17230
+ const distanceForVertical = un + uu / tanHalfVertical;
17231
+ requiredDistance = Math.max(
17232
+ requiredDistance,
17233
+ distanceForHorizontal,
17234
+ distanceForVertical
17235
+ );
17236
+ }
17237
+ return requiredDistance;
17238
+ }
17239
+ function getBestCameraPosition(circuitJson, opts) {
17240
+ const verticalFovDegrees = getVerticalFovDegrees(opts);
17241
+ const panel = circuitJson.find((item) => item.type === "pcb_panel");
17242
+ const board = circuitJson.find((item) => item.type === "pcb_board");
17243
+ const surface = panel || board;
17244
+ if (!surface) {
17245
+ return {
17246
+ camPos: [30, 30, 25],
17247
+ lookAt: [0, 0, 0],
17248
+ fov: verticalFovDegrees
17249
+ };
17250
+ }
17251
+ const { width, height, center } = surface;
17252
+ if (!width || !height || !center) {
17253
+ return {
17254
+ camPos: [30, 30, 25],
17255
+ lookAt: [0, 0, 0],
17256
+ fov: verticalFovDegrees
17257
+ };
17258
+ }
17259
+ const lookAtX = center.x;
17260
+ const lookAtZ = center.y;
17261
+ const cameraDirection = normalizeVector(
17262
+ opts?.direction ?? DEFAULT_CAMERA_DIRECTION
17263
+ );
17264
+ const forward = [
17265
+ -cameraDirection[0],
17266
+ -cameraDirection[1],
17267
+ -cameraDirection[2]
17268
+ ];
17269
+ const worldUp = [0, 1, 0];
17270
+ const right = normalizeVector(cross(forward, worldUp));
17271
+ const up = normalizeVector(cross(right, forward));
17272
+ const verticalFov = verticalFovDegrees * Math.PI / 180;
17273
+ const aspectRatio = opts?.aspectRatio !== void 0 && Number.isFinite(opts.aspectRatio) && opts.aspectRatio > 0 ? opts.aspectRatio : 4 / 3;
17274
+ const tanHalfVertical = Math.tan(verticalFov / 2);
17275
+ const tanHalfHorizontal = tanHalfVertical * aspectRatio;
17276
+ const halfWidth = width / 2;
17277
+ const halfHeight = height / 2;
17278
+ const boardCorners = [
17279
+ [halfWidth, 0, halfHeight],
17280
+ [halfWidth, 0, -halfHeight],
17281
+ [-halfWidth, 0, halfHeight],
17282
+ [-halfWidth, 0, -halfHeight]
17283
+ ];
17284
+ const requiredDistanceAssumingVerticalFov = getRequiredDistanceForFrustum(
17285
+ boardCorners,
17286
+ cameraDirection,
17287
+ right,
17288
+ up,
17289
+ tanHalfHorizontal,
17290
+ tanHalfVertical
17291
+ );
17292
+ const tanHalfHorizontalIfFovIsHorizontal = tanHalfVertical;
17293
+ const tanHalfVerticalIfFovIsHorizontal = tanHalfHorizontalIfFovIsHorizontal / aspectRatio;
17294
+ const requiredDistanceAssumingHorizontalFov = getRequiredDistanceForFrustum(
17295
+ boardCorners,
17296
+ cameraDirection,
17297
+ right,
17298
+ up,
17299
+ tanHalfHorizontalIfFovIsHorizontal,
17300
+ tanHalfVerticalIfFovIsHorizontal
17301
+ );
17302
+ const requiredDistance = Math.max(
17303
+ requiredDistanceAssumingVerticalFov,
17304
+ requiredDistanceAssumingHorizontalFov
17305
+ );
17306
+ const distance = Math.max(requiredDistance, 1);
17307
+ const camX = lookAtX + cameraDirection[0] * distance;
17308
+ const camY = cameraDirection[1] * distance;
17309
+ const camZ = lookAtZ + cameraDirection[2] * distance;
17310
+ return {
17311
+ camPos: [-camX, camY, camZ],
17312
+ lookAt: [-lookAtX, 0, lookAtZ],
17313
+ fov: verticalFovDegrees
17314
+ };
17315
+ }
17316
+
17193
17317
  // lib/index.ts
17194
17318
  async function convertCircuitJsonToGltf(circuitJson, options = {}) {
17195
17319
  const {
@@ -17227,6 +17351,7 @@ export {
17227
17351
  convertCircuitJsonTo3D,
17228
17352
  convertCircuitJsonToGltf,
17229
17353
  convertSceneToGLTF,
17354
+ getBestCameraPosition,
17230
17355
  loadGLB,
17231
17356
  loadOBJ,
17232
17357
  loadSTL,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "circuit-json-to-gltf",
3
3
  "main": "dist/index.js",
4
4
  "type": "module",
5
- "version": "0.0.71",
5
+ "version": "0.0.72",
6
6
  "scripts": {
7
7
  "test": "bun test tests/",
8
8
  "format": "biome format --write .",