circuit-json-to-gltf 0.0.10 → 0.0.11

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
@@ -76,7 +76,7 @@ interface Box3D {
76
76
  };
77
77
  mesh?: STLMesh | OBJMesh;
78
78
  meshUrl?: string;
79
- meshType?: "stl" | "obj";
79
+ meshType?: "stl" | "obj" | "glb";
80
80
  label?: string;
81
81
  labelColor?: Color;
82
82
  }
@@ -135,6 +135,9 @@ declare function clearSTLCache(): void;
135
135
  declare function loadOBJ(url: string, transform?: CoordinateTransformConfig): Promise<OBJMesh>;
136
136
  declare function clearOBJCache(): void;
137
137
 
138
+ declare function loadGLB(url: string, transform?: CoordinateTransformConfig): Promise<STLMesh>;
139
+ declare function clearGLBCache(): void;
140
+
138
141
  declare function convertCircuitJsonTo3D(circuitJson: CircuitJson, options?: CircuitTo3DOptions): Promise<Scene3D>;
139
142
 
140
143
  declare function convertSceneToGLTF(scene: Scene3D, options?: GLTFExportOptions): Promise<ArrayBuffer | object>;
@@ -165,4 +168,4 @@ declare const COORDINATE_TRANSFORMS: {
165
168
 
166
169
  declare function convertCircuitJsonToGltf(circuitJson: CircuitJson, options?: ConversionOptions): Promise<ArrayBuffer | object>;
167
170
 
168
- export { type BoardRenderOptions, type BoundingBox, type Box3D, COORDINATE_TRANSFORMS, type Camera3D, type CircuitTo3DOptions, type Color, type ConversionOptions, type CoordinateTransformConfig, type GLTFExportOptions, type Light3D, type OBJMaterial, type OBJMesh, type Point3, type STLMesh, type Scene3D, type Size3, type Triangle, applyCoordinateTransform, clearOBJCache, clearSTLCache, convertCircuitJsonTo3D, convertCircuitJsonToGltf, convertSceneToGLTF, loadOBJ, loadSTL, renderBoardLayer, renderBoardTextures, transformTriangles };
171
+ export { type BoardRenderOptions, type BoundingBox, type Box3D, COORDINATE_TRANSFORMS, type Camera3D, type CircuitTo3DOptions, type Color, type ConversionOptions, type CoordinateTransformConfig, type GLTFExportOptions, type Light3D, type OBJMaterial, type OBJMesh, type Point3, type STLMesh, type Scene3D, type Size3, type Triangle, applyCoordinateTransform, clearGLBCache, clearOBJCache, clearSTLCache, convertCircuitJsonTo3D, convertCircuitJsonToGltf, convertSceneToGLTF, loadGLB, loadOBJ, loadSTL, renderBoardLayer, renderBoardTextures, transformTriangles };
package/dist/index.js CHANGED
@@ -480,6 +480,268 @@ function clearOBJCache() {
480
480
  objCache.clear();
481
481
  }
482
482
 
483
+ // lib/loaders/glb.ts
484
+ var glbCache = /* @__PURE__ */ new Map();
485
+ async function loadGLB(url, transform) {
486
+ const cacheKey = `${url}:${JSON.stringify(transform ?? {})}`;
487
+ if (glbCache.has(cacheKey)) {
488
+ return glbCache.get(cacheKey);
489
+ }
490
+ const response = await fetch(url);
491
+ const buffer = await response.arrayBuffer();
492
+ const mesh = parseGLB(buffer, transform);
493
+ glbCache.set(cacheKey, mesh);
494
+ return mesh;
495
+ }
496
+ function parseGLB(buffer, transform) {
497
+ const view = new DataView(buffer);
498
+ let offset = 0;
499
+ const magic = view.getUint32(offset, true);
500
+ offset += 4;
501
+ if (magic !== 1179937895) {
502
+ throw new Error("Invalid GLB file: incorrect magic number");
503
+ }
504
+ const version = view.getUint32(offset, true);
505
+ offset += 4;
506
+ if (version !== 2) {
507
+ throw new Error(`Unsupported GLB version: ${version}`);
508
+ }
509
+ const length = view.getUint32(offset, true);
510
+ offset += 4;
511
+ const jsonChunkLength = view.getUint32(offset, true);
512
+ offset += 4;
513
+ const jsonChunkType = view.getUint32(offset, true);
514
+ offset += 4;
515
+ if (jsonChunkType !== 1313821514) {
516
+ throw new Error("Expected JSON chunk");
517
+ }
518
+ const jsonBytes = new Uint8Array(buffer, offset, jsonChunkLength);
519
+ const jsonString = new TextDecoder().decode(jsonBytes);
520
+ const gltf = JSON.parse(jsonString);
521
+ offset += jsonChunkLength;
522
+ let binaryBuffer;
523
+ if (offset < length) {
524
+ const binaryChunkLength = view.getUint32(offset, true);
525
+ offset += 4;
526
+ const binaryChunkType = view.getUint32(offset, true);
527
+ offset += 4;
528
+ if (binaryChunkType === 5130562) {
529
+ binaryBuffer = buffer.slice(offset, offset + binaryChunkLength);
530
+ }
531
+ }
532
+ const triangles = extractTrianglesFromGLTF(gltf, binaryBuffer);
533
+ const finalConfig = transform ?? {
534
+ axisMapping: { x: "x", y: "z", z: "y" }
535
+ };
536
+ const transformedTriangles = transformTriangles(triangles, finalConfig);
537
+ return {
538
+ triangles: transformedTriangles,
539
+ boundingBox: calculateBoundingBox3(transformedTriangles)
540
+ };
541
+ }
542
+ function extractTrianglesFromGLTF(gltf, binaryBuffer) {
543
+ const triangles = [];
544
+ if (!gltf.meshes || !gltf.accessors || !gltf.bufferViews) {
545
+ return triangles;
546
+ }
547
+ for (const mesh of gltf.meshes) {
548
+ for (const primitive of mesh.primitives) {
549
+ const mode = primitive.mode ?? 4;
550
+ if (mode !== 4) {
551
+ continue;
552
+ }
553
+ const positionAccessorIndex = primitive.attributes.POSITION;
554
+ if (positionAccessorIndex === void 0) {
555
+ continue;
556
+ }
557
+ const positionAccessor = gltf.accessors[positionAccessorIndex];
558
+ const positions = getAccessorData(
559
+ positionAccessor,
560
+ gltf.bufferViews,
561
+ binaryBuffer
562
+ );
563
+ let normals;
564
+ const normalAccessorIndex = primitive.attributes.NORMAL;
565
+ if (normalAccessorIndex !== void 0) {
566
+ const normalAccessor = gltf.accessors[normalAccessorIndex];
567
+ normals = getAccessorData(
568
+ normalAccessor,
569
+ gltf.bufferViews,
570
+ binaryBuffer
571
+ );
572
+ }
573
+ let indices;
574
+ if (primitive.indices !== void 0) {
575
+ const indexAccessor = gltf.accessors[primitive.indices];
576
+ const indexData = getAccessorData(
577
+ indexAccessor,
578
+ gltf.bufferViews,
579
+ binaryBuffer
580
+ );
581
+ indices = indexAccessor.componentType === 5123 ? new Uint16Array(
582
+ indexData.buffer,
583
+ indexData.byteOffset,
584
+ indexData.length
585
+ ) : new Uint32Array(
586
+ indexData.buffer,
587
+ indexData.byteOffset,
588
+ indexData.length
589
+ );
590
+ }
591
+ const vertexCount = positions.length / 3;
592
+ if (indices) {
593
+ for (let i = 0; i < indices.length; i += 3) {
594
+ const i0 = indices[i];
595
+ const i1 = indices[i + 1];
596
+ const i2 = indices[i + 2];
597
+ const v0 = {
598
+ x: positions[i0 * 3],
599
+ y: positions[i0 * 3 + 1],
600
+ z: positions[i0 * 3 + 2]
601
+ };
602
+ const v1 = {
603
+ x: positions[i1 * 3],
604
+ y: positions[i1 * 3 + 1],
605
+ z: positions[i1 * 3 + 2]
606
+ };
607
+ const v2 = {
608
+ x: positions[i2 * 3],
609
+ y: positions[i2 * 3 + 1],
610
+ z: positions[i2 * 3 + 2]
611
+ };
612
+ let normal;
613
+ if (normals) {
614
+ normal = {
615
+ x: (normals[i0 * 3] + normals[i1 * 3] + normals[i2 * 3]) / 3,
616
+ y: (normals[i0 * 3 + 1] + normals[i1 * 3 + 1] + normals[i2 * 3 + 1]) / 3,
617
+ z: (normals[i0 * 3 + 2] + normals[i1 * 3 + 2] + normals[i2 * 3 + 2]) / 3
618
+ };
619
+ } else {
620
+ normal = computeNormal(v0, v1, v2);
621
+ }
622
+ triangles.push({ vertices: [v0, v1, v2], normal });
623
+ }
624
+ } else {
625
+ for (let i = 0; i < vertexCount; i += 3) {
626
+ const v0 = {
627
+ x: positions[i * 3],
628
+ y: positions[i * 3 + 1],
629
+ z: positions[i * 3 + 2]
630
+ };
631
+ const v1 = {
632
+ x: positions[(i + 1) * 3],
633
+ y: positions[(i + 1) * 3 + 1],
634
+ z: positions[(i + 1) * 3 + 2]
635
+ };
636
+ const v2 = {
637
+ x: positions[(i + 2) * 3],
638
+ y: positions[(i + 2) * 3 + 1],
639
+ z: positions[(i + 2) * 3 + 2]
640
+ };
641
+ let normal;
642
+ if (normals) {
643
+ normal = {
644
+ x: (normals[i * 3] + normals[(i + 1) * 3] + normals[(i + 2) * 3]) / 3,
645
+ y: (normals[i * 3 + 1] + normals[(i + 1) * 3 + 1] + normals[(i + 2) * 3 + 1]) / 3,
646
+ z: (normals[i * 3 + 2] + normals[(i + 1) * 3 + 2] + normals[(i + 2) * 3 + 2]) / 3
647
+ };
648
+ } else {
649
+ normal = computeNormal(v0, v1, v2);
650
+ }
651
+ triangles.push({ vertices: [v0, v1, v2], normal });
652
+ }
653
+ }
654
+ }
655
+ }
656
+ return triangles;
657
+ }
658
+ function getAccessorData(accessor, bufferViews, binaryBuffer) {
659
+ const bufferView = bufferViews[accessor.bufferView];
660
+ if (!bufferView || !binaryBuffer) {
661
+ throw new Error("Missing buffer data");
662
+ }
663
+ const byteOffset = (bufferView.byteOffset ?? 0) + (accessor.byteOffset ?? 0);
664
+ const componentType = accessor.componentType;
665
+ const count = accessor.count;
666
+ const type = accessor.type;
667
+ const componentsPerElement = type === "SCALAR" ? 1 : type === "VEC2" ? 2 : type === "VEC3" ? 3 : type === "VEC4" ? 4 : 1;
668
+ const totalComponents = count * componentsPerElement;
669
+ if (componentType === 5126) {
670
+ return new Float32Array(binaryBuffer, byteOffset, totalComponents);
671
+ } else if (componentType === 5123) {
672
+ const uint16Array = new Uint16Array(
673
+ binaryBuffer,
674
+ byteOffset,
675
+ totalComponents
676
+ );
677
+ return new Float32Array(uint16Array);
678
+ } else if (componentType === 5125) {
679
+ const uint32Array = new Uint32Array(
680
+ binaryBuffer,
681
+ byteOffset,
682
+ totalComponents
683
+ );
684
+ return new Float32Array(uint32Array);
685
+ } else if (componentType === 5122) {
686
+ const int16Array = new Int16Array(binaryBuffer, byteOffset, totalComponents);
687
+ return new Float32Array(int16Array);
688
+ } else if (componentType === 5124) {
689
+ const int32Array = new Int32Array(binaryBuffer, byteOffset, totalComponents);
690
+ return new Float32Array(int32Array);
691
+ } else if (componentType === 5121) {
692
+ const uint8Array = new Uint8Array(binaryBuffer, byteOffset, totalComponents);
693
+ return new Float32Array(uint8Array);
694
+ } else if (componentType === 5120) {
695
+ const int8Array = new Int8Array(binaryBuffer, byteOffset, totalComponents);
696
+ return new Float32Array(int8Array);
697
+ }
698
+ throw new Error(`Unsupported component type: ${componentType}`);
699
+ }
700
+ function computeNormal(v0, v1, v2) {
701
+ const edge1 = {
702
+ x: v1.x - v0.x,
703
+ y: v1.y - v0.y,
704
+ z: v1.z - v0.z
705
+ };
706
+ const edge2 = {
707
+ x: v2.x - v0.x,
708
+ y: v2.y - v0.y,
709
+ z: v2.z - v0.z
710
+ };
711
+ return {
712
+ x: edge1.y * edge2.z - edge1.z * edge2.y,
713
+ y: edge1.z * edge2.x - edge1.x * edge2.z,
714
+ z: edge1.x * edge2.y - edge1.y * edge2.x
715
+ };
716
+ }
717
+ function calculateBoundingBox3(triangles) {
718
+ if (triangles.length === 0) {
719
+ return {
720
+ min: { x: 0, y: 0, z: 0 },
721
+ max: { x: 0, y: 0, z: 0 }
722
+ };
723
+ }
724
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
725
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
726
+ for (const triangle of triangles) {
727
+ for (const vertex of triangle.vertices) {
728
+ minX = Math.min(minX, vertex.x);
729
+ minY = Math.min(minY, vertex.y);
730
+ minZ = Math.min(minZ, vertex.z);
731
+ maxX = Math.max(maxX, vertex.x);
732
+ maxY = Math.max(maxY, vertex.y);
733
+ maxZ = Math.max(maxZ, vertex.z);
734
+ }
735
+ }
736
+ return {
737
+ min: { x: minX, y: minY, z: minZ },
738
+ max: { x: maxX, y: maxY, z: maxZ }
739
+ };
740
+ }
741
+ function clearGLBCache() {
742
+ glbCache.clear();
743
+ }
744
+
483
745
  // lib/converters/board-renderer.ts
484
746
  import { convertCircuitJsonToPcbSvg } from "circuit-to-svg";
485
747
  async function renderBoardLayer(circuitJson, options) {
@@ -638,8 +900,8 @@ async function convertCircuitJsonTo3D(circuitJson, options = {}) {
638
900
  const cadComponents = db.cad_component?.list?.() ?? [];
639
901
  const pcbComponentIdsWith3D = /* @__PURE__ */ new Set();
640
902
  for (const cad of cadComponents) {
641
- const { model_stl_url, model_obj_url } = cad;
642
- if (!model_stl_url && !model_obj_url) continue;
903
+ const { model_stl_url, model_obj_url, model_glb_url } = cad;
904
+ if (!model_stl_url && !model_obj_url && !model_glb_url) continue;
643
905
  pcbComponentIdsWith3D.add(cad.pcb_component_id);
644
906
  const pcbComponent = db.pcb_component.get(cad.pcb_component_id);
645
907
  const size = cad.size ?? {
@@ -655,23 +917,27 @@ async function convertCircuitJsonTo3D(circuitJson, options = {}) {
655
917
  const box = {
656
918
  center,
657
919
  size,
658
- color: componentColor,
659
- meshUrl: model_stl_url || model_obj_url,
660
- meshType: model_stl_url ? "stl" : "obj"
920
+ meshUrl: model_stl_url || model_obj_url || model_glb_url,
921
+ meshType: model_stl_url ? "stl" : model_obj_url ? "obj" : "glb"
661
922
  };
662
923
  if (cad.rotation) {
663
924
  box.rotation = convertRotationFromCadRotation(cad.rotation);
664
925
  }
665
- const defaultTransform = coordinateTransform ?? COORDINATE_TRANSFORMS.Z_UP_TO_Y_UP_USB_FIX;
926
+ const defaultTransform = coordinateTransform ?? (model_glb_url ? void 0 : COORDINATE_TRANSFORMS.Z_UP_TO_Y_UP_USB_FIX);
666
927
  try {
667
928
  if (model_stl_url) {
668
929
  box.mesh = await loadSTL(model_stl_url, defaultTransform);
669
930
  } else if (model_obj_url) {
670
931
  box.mesh = await loadOBJ(model_obj_url, defaultTransform);
932
+ } else if (model_glb_url) {
933
+ box.mesh = await loadGLB(model_glb_url, defaultTransform);
671
934
  }
672
935
  } catch (error) {
673
936
  console.warn(`Failed to load 3D model: ${error}`);
674
937
  }
938
+ if (!box.mesh) {
939
+ box.color = componentColor;
940
+ }
675
941
  boxes.push(box);
676
942
  }
677
943
  for (const component of db.pcb_component.list()) {
@@ -1326,6 +1592,16 @@ var GLTFBuilder = class {
1326
1592
  let materialIndex = defaultMaterialIndex;
1327
1593
  if (box.color) {
1328
1594
  materialIndex = this.addMaterialFromColor(box.color, !box.mesh);
1595
+ } else if (box.mesh) {
1596
+ materialIndex = this.addMaterial({
1597
+ name: `MeshMaterial_${this.materials.length}`,
1598
+ pbrMetallicRoughness: {
1599
+ baseColorFactor: [0.7, 0.7, 0.7, 1],
1600
+ metallicFactor: 0.1,
1601
+ roughnessFactor: 0.9
1602
+ },
1603
+ alphaMode: "OPAQUE"
1604
+ });
1329
1605
  }
1330
1606
  const meshIndex = this.addMesh(meshData, materialIndex, box.label);
1331
1607
  const nodeIndex = this.nodes.length;
@@ -1852,11 +2128,13 @@ async function convertCircuitJsonToGltf(circuitJson, options = {}) {
1852
2128
  export {
1853
2129
  COORDINATE_TRANSFORMS,
1854
2130
  applyCoordinateTransform,
2131
+ clearGLBCache,
1855
2132
  clearOBJCache,
1856
2133
  clearSTLCache,
1857
2134
  convertCircuitJsonTo3D,
1858
2135
  convertCircuitJsonToGltf,
1859
2136
  convertSceneToGLTF,
2137
+ loadGLB,
1860
2138
  loadOBJ,
1861
2139
  loadSTL,
1862
2140
  renderBoardLayer,
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.10",
5
+ "version": "0.0.11",
6
6
  "scripts": {
7
7
  "test": "bun test tests/",
8
8
  "format": "biome format --write .",