circuit-json-to-gltf 0.0.12 → 0.0.14
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 +1 -1
- package/dist/index.js +271 -24
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -135,7 +135,7 @@ 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>;
|
|
138
|
+
declare function loadGLB(url: string, transform?: CoordinateTransformConfig): Promise<STLMesh | OBJMesh>;
|
|
139
139
|
declare function clearGLBCache(): void;
|
|
140
140
|
|
|
141
141
|
declare function convertCircuitJsonTo3D(circuitJson: CircuitJson, options?: CircuitTo3DOptions): Promise<Scene3D>;
|
package/dist/index.js
CHANGED
|
@@ -534,11 +534,59 @@ function parseGLB(buffer, transform) {
|
|
|
534
534
|
axisMapping: { x: "x", y: "z", z: "y" }
|
|
535
535
|
};
|
|
536
536
|
const transformedTriangles = transformTriangles(triangles, finalConfig);
|
|
537
|
+
const hasColors = transformedTriangles.some((t) => t.color !== void 0);
|
|
538
|
+
if (hasColors) {
|
|
539
|
+
return convertToOBJMesh(transformedTriangles);
|
|
540
|
+
}
|
|
537
541
|
return {
|
|
538
542
|
triangles: transformedTriangles,
|
|
539
543
|
boundingBox: calculateBoundingBox3(transformedTriangles)
|
|
540
544
|
};
|
|
541
545
|
}
|
|
546
|
+
function convertToOBJMesh(triangles) {
|
|
547
|
+
const colorGroups = /* @__PURE__ */ new Map();
|
|
548
|
+
for (const triangle of triangles) {
|
|
549
|
+
const colorKey = triangle.color ? JSON.stringify(triangle.color) : "default";
|
|
550
|
+
if (!colorGroups.has(colorKey)) {
|
|
551
|
+
colorGroups.set(colorKey, []);
|
|
552
|
+
}
|
|
553
|
+
colorGroups.get(colorKey).push(triangle);
|
|
554
|
+
}
|
|
555
|
+
const materials = /* @__PURE__ */ new Map();
|
|
556
|
+
const materialIndexMap = /* @__PURE__ */ new Map();
|
|
557
|
+
let materialIndex = 0;
|
|
558
|
+
const trianglesWithMaterialIndex = [];
|
|
559
|
+
for (const [colorKey, groupTriangles] of colorGroups) {
|
|
560
|
+
const materialName = `Material_${materialIndex}`;
|
|
561
|
+
materialIndexMap.set(materialName, materialIndex);
|
|
562
|
+
if (colorKey === "default") {
|
|
563
|
+
materials.set(materialName, {
|
|
564
|
+
name: materialName,
|
|
565
|
+
color: [179, 179, 179, 1]
|
|
566
|
+
// 0.7 * 255 = 179
|
|
567
|
+
});
|
|
568
|
+
} else {
|
|
569
|
+
const color = JSON.parse(colorKey);
|
|
570
|
+
materials.set(materialName, {
|
|
571
|
+
name: materialName,
|
|
572
|
+
color
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
for (const triangle of groupTriangles) {
|
|
576
|
+
trianglesWithMaterialIndex.push({
|
|
577
|
+
...triangle,
|
|
578
|
+
materialIndex
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
materialIndex++;
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
triangles: trianglesWithMaterialIndex,
|
|
585
|
+
boundingBox: calculateBoundingBox3(trianglesWithMaterialIndex),
|
|
586
|
+
materials,
|
|
587
|
+
materialIndexMap
|
|
588
|
+
};
|
|
589
|
+
}
|
|
542
590
|
function extractTrianglesFromGLTF(gltf, binaryBuffer) {
|
|
543
591
|
const triangles = [];
|
|
544
592
|
if (!gltf.meshes || !gltf.accessors || !gltf.bufferViews) {
|
|
@@ -570,6 +618,29 @@ function extractTrianglesFromGLTF(gltf, binaryBuffer) {
|
|
|
570
618
|
binaryBuffer
|
|
571
619
|
);
|
|
572
620
|
}
|
|
621
|
+
let vertexColors;
|
|
622
|
+
const colorAccessorIndex = primitive.attributes.COLOR_0;
|
|
623
|
+
if (colorAccessorIndex !== void 0) {
|
|
624
|
+
const colorAccessor = gltf.accessors[colorAccessorIndex];
|
|
625
|
+
vertexColors = getAccessorData(
|
|
626
|
+
colorAccessor,
|
|
627
|
+
gltf.bufferViews,
|
|
628
|
+
binaryBuffer
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
let materialColor;
|
|
632
|
+
if (!vertexColors && primitive.material !== void 0 && gltf.materials) {
|
|
633
|
+
const material = gltf.materials[primitive.material];
|
|
634
|
+
if (material?.pbrMetallicRoughness?.baseColorFactor) {
|
|
635
|
+
const factor = material.pbrMetallicRoughness.baseColorFactor;
|
|
636
|
+
materialColor = [
|
|
637
|
+
Math.round(factor[0] * 255),
|
|
638
|
+
Math.round(factor[1] * 255),
|
|
639
|
+
Math.round(factor[2] * 255),
|
|
640
|
+
factor[3]
|
|
641
|
+
];
|
|
642
|
+
}
|
|
643
|
+
}
|
|
573
644
|
let indices;
|
|
574
645
|
if (primitive.indices !== void 0) {
|
|
575
646
|
const indexAccessor = gltf.accessors[primitive.indices];
|
|
@@ -619,7 +690,44 @@ function extractTrianglesFromGLTF(gltf, binaryBuffer) {
|
|
|
619
690
|
} else {
|
|
620
691
|
normal = computeNormal(v0, v1, v2);
|
|
621
692
|
}
|
|
622
|
-
|
|
693
|
+
let triangleColor;
|
|
694
|
+
if (vertexColors) {
|
|
695
|
+
const componentsPerColor = vertexColors.length / vertexCount;
|
|
696
|
+
if (componentsPerColor === 3) {
|
|
697
|
+
triangleColor = [
|
|
698
|
+
Math.round(
|
|
699
|
+
(vertexColors[i0 * 3] + vertexColors[i1 * 3] + vertexColors[i2 * 3]) / 3 * 255
|
|
700
|
+
),
|
|
701
|
+
Math.round(
|
|
702
|
+
(vertexColors[i0 * 3 + 1] + vertexColors[i1 * 3 + 1] + vertexColors[i2 * 3 + 1]) / 3 * 255
|
|
703
|
+
),
|
|
704
|
+
Math.round(
|
|
705
|
+
(vertexColors[i0 * 3 + 2] + vertexColors[i1 * 3 + 2] + vertexColors[i2 * 3 + 2]) / 3 * 255
|
|
706
|
+
),
|
|
707
|
+
1
|
|
708
|
+
];
|
|
709
|
+
} else if (componentsPerColor === 4) {
|
|
710
|
+
triangleColor = [
|
|
711
|
+
Math.round(
|
|
712
|
+
(vertexColors[i0 * 4] + vertexColors[i1 * 4] + vertexColors[i2 * 4]) / 3 * 255
|
|
713
|
+
),
|
|
714
|
+
Math.round(
|
|
715
|
+
(vertexColors[i0 * 4 + 1] + vertexColors[i1 * 4 + 1] + vertexColors[i2 * 4 + 1]) / 3 * 255
|
|
716
|
+
),
|
|
717
|
+
Math.round(
|
|
718
|
+
(vertexColors[i0 * 4 + 2] + vertexColors[i1 * 4 + 2] + vertexColors[i2 * 4 + 2]) / 3 * 255
|
|
719
|
+
),
|
|
720
|
+
(vertexColors[i0 * 4 + 3] + vertexColors[i1 * 4 + 3] + vertexColors[i2 * 4 + 3]) / 3
|
|
721
|
+
];
|
|
722
|
+
}
|
|
723
|
+
} else {
|
|
724
|
+
triangleColor = materialColor;
|
|
725
|
+
}
|
|
726
|
+
triangles.push({
|
|
727
|
+
vertices: [v0, v1, v2],
|
|
728
|
+
normal,
|
|
729
|
+
color: triangleColor
|
|
730
|
+
});
|
|
623
731
|
}
|
|
624
732
|
} else {
|
|
625
733
|
for (let i = 0; i < vertexCount; i += 3) {
|
|
@@ -648,7 +756,44 @@ function extractTrianglesFromGLTF(gltf, binaryBuffer) {
|
|
|
648
756
|
} else {
|
|
649
757
|
normal = computeNormal(v0, v1, v2);
|
|
650
758
|
}
|
|
651
|
-
|
|
759
|
+
let triangleColor;
|
|
760
|
+
if (vertexColors) {
|
|
761
|
+
const componentsPerColor = vertexColors.length / vertexCount;
|
|
762
|
+
if (componentsPerColor === 3) {
|
|
763
|
+
triangleColor = [
|
|
764
|
+
Math.round(
|
|
765
|
+
(vertexColors[i * 3] + vertexColors[(i + 1) * 3] + vertexColors[(i + 2) * 3]) / 3 * 255
|
|
766
|
+
),
|
|
767
|
+
Math.round(
|
|
768
|
+
(vertexColors[i * 3 + 1] + vertexColors[(i + 1) * 3 + 1] + vertexColors[(i + 2) * 3 + 1]) / 3 * 255
|
|
769
|
+
),
|
|
770
|
+
Math.round(
|
|
771
|
+
(vertexColors[i * 3 + 2] + vertexColors[(i + 1) * 3 + 2] + vertexColors[(i + 2) * 3 + 2]) / 3 * 255
|
|
772
|
+
),
|
|
773
|
+
1
|
|
774
|
+
];
|
|
775
|
+
} else if (componentsPerColor === 4) {
|
|
776
|
+
triangleColor = [
|
|
777
|
+
Math.round(
|
|
778
|
+
(vertexColors[i * 4] + vertexColors[(i + 1) * 4] + vertexColors[(i + 2) * 4]) / 3 * 255
|
|
779
|
+
),
|
|
780
|
+
Math.round(
|
|
781
|
+
(vertexColors[i * 4 + 1] + vertexColors[(i + 1) * 4 + 1] + vertexColors[(i + 2) * 4 + 1]) / 3 * 255
|
|
782
|
+
),
|
|
783
|
+
Math.round(
|
|
784
|
+
(vertexColors[i * 4 + 2] + vertexColors[(i + 1) * 4 + 2] + vertexColors[(i + 2) * 4 + 2]) / 3 * 255
|
|
785
|
+
),
|
|
786
|
+
(vertexColors[i * 4 + 3] + vertexColors[(i + 1) * 4 + 3] + vertexColors[(i + 2) * 4 + 3]) / 3
|
|
787
|
+
];
|
|
788
|
+
}
|
|
789
|
+
} else {
|
|
790
|
+
triangleColor = materialColor;
|
|
791
|
+
}
|
|
792
|
+
triangles.push({
|
|
793
|
+
vertices: [v0, v1, v2],
|
|
794
|
+
normal,
|
|
795
|
+
color: triangleColor
|
|
796
|
+
});
|
|
652
797
|
}
|
|
653
798
|
}
|
|
654
799
|
}
|
|
@@ -742,6 +887,95 @@ function clearGLBCache() {
|
|
|
742
887
|
glbCache.clear();
|
|
743
888
|
}
|
|
744
889
|
|
|
890
|
+
// lib/loaders/gltf.ts
|
|
891
|
+
async function fetchAsArrayBuffer(url) {
|
|
892
|
+
const response = await fetch(url);
|
|
893
|
+
if (!response.ok) {
|
|
894
|
+
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
|
|
895
|
+
}
|
|
896
|
+
return response.arrayBuffer();
|
|
897
|
+
}
|
|
898
|
+
function dataUriToArrayBuffer(uri) {
|
|
899
|
+
const a = uri.split(",");
|
|
900
|
+
const byteString = atob(a[1]);
|
|
901
|
+
const ab = new ArrayBuffer(byteString.length);
|
|
902
|
+
const ia = new Uint8Array(ab);
|
|
903
|
+
for (let i = 0; i < byteString.length; i++) {
|
|
904
|
+
ia[i] = byteString.charCodeAt(i);
|
|
905
|
+
}
|
|
906
|
+
return ab;
|
|
907
|
+
}
|
|
908
|
+
async function fetchGltfAndConvertToGlb(url) {
|
|
909
|
+
const gltfResponse = await fetch(url);
|
|
910
|
+
if (!gltfResponse.ok) {
|
|
911
|
+
throw new Error(`Failed to fetch glTF file: ${gltfResponse.statusText}`);
|
|
912
|
+
}
|
|
913
|
+
const gltf = await gltfResponse.json();
|
|
914
|
+
const bufferPromises = [];
|
|
915
|
+
if (gltf.buffers) {
|
|
916
|
+
for (const buffer of gltf.buffers) {
|
|
917
|
+
if (buffer.uri) {
|
|
918
|
+
if (buffer.uri.startsWith("data:")) {
|
|
919
|
+
bufferPromises.push(Promise.resolve(dataUriToArrayBuffer(buffer.uri)));
|
|
920
|
+
} else {
|
|
921
|
+
const bufferUrl = new URL(buffer.uri, url).toString();
|
|
922
|
+
bufferPromises.push(fetchAsArrayBuffer(bufferUrl));
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
const buffers = await Promise.all(bufferPromises);
|
|
928
|
+
let binaryBuffer = new ArrayBuffer(0);
|
|
929
|
+
if (buffers.length > 0 && buffers[0]) {
|
|
930
|
+
binaryBuffer = buffers[0];
|
|
931
|
+
}
|
|
932
|
+
if (gltf.buffers && gltf.buffers.length > 0) {
|
|
933
|
+
delete gltf.buffers[0].uri;
|
|
934
|
+
gltf.buffers[0].byteLength = binaryBuffer.byteLength;
|
|
935
|
+
}
|
|
936
|
+
const jsonString = JSON.stringify(gltf);
|
|
937
|
+
const jsonBuffer = new TextEncoder().encode(jsonString);
|
|
938
|
+
const jsonPadding = (4 - jsonBuffer.length % 4) % 4;
|
|
939
|
+
const binaryPadding = (4 - binaryBuffer.byteLength % 4) % 4;
|
|
940
|
+
const totalLength = 12 + // header
|
|
941
|
+
(8 + jsonBuffer.length + jsonPadding) + // json chunk
|
|
942
|
+
(binaryBuffer.byteLength > 0 ? 8 + binaryBuffer.byteLength + binaryPadding : 0);
|
|
943
|
+
const glbBuffer = new ArrayBuffer(totalLength);
|
|
944
|
+
const dataView = new DataView(glbBuffer);
|
|
945
|
+
let offset = 0;
|
|
946
|
+
dataView.setUint32(offset, 1179937895, true);
|
|
947
|
+
offset += 4;
|
|
948
|
+
dataView.setUint32(offset, 2, true);
|
|
949
|
+
offset += 4;
|
|
950
|
+
dataView.setUint32(offset, totalLength, true);
|
|
951
|
+
offset += 4;
|
|
952
|
+
dataView.setUint32(offset, jsonBuffer.length + jsonPadding, true);
|
|
953
|
+
offset += 4;
|
|
954
|
+
dataView.setUint32(offset, 1313821514, true);
|
|
955
|
+
offset += 4;
|
|
956
|
+
new Uint8Array(glbBuffer, offset).set(jsonBuffer);
|
|
957
|
+
offset += jsonBuffer.length;
|
|
958
|
+
for (let i = 0; i < jsonPadding; i++) {
|
|
959
|
+
dataView.setUint8(offset++, 32);
|
|
960
|
+
}
|
|
961
|
+
if (binaryBuffer.byteLength > 0) {
|
|
962
|
+
dataView.setUint32(offset, binaryBuffer.byteLength + binaryPadding, true);
|
|
963
|
+
offset += 4;
|
|
964
|
+
dataView.setUint32(offset, 5130562, true);
|
|
965
|
+
offset += 4;
|
|
966
|
+
new Uint8Array(glbBuffer, offset).set(new Uint8Array(binaryBuffer));
|
|
967
|
+
offset += binaryBuffer.byteLength;
|
|
968
|
+
for (let i = 0; i < binaryPadding; i++) {
|
|
969
|
+
dataView.setUint8(offset++, 0);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return glbBuffer;
|
|
973
|
+
}
|
|
974
|
+
async function loadGLTF(url, transform) {
|
|
975
|
+
const glb_buffer = await fetchGltfAndConvertToGlb(url);
|
|
976
|
+
return parseGLB(glb_buffer, transform);
|
|
977
|
+
}
|
|
978
|
+
|
|
745
979
|
// lib/converters/board-renderer.ts
|
|
746
980
|
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg";
|
|
747
981
|
async function renderBoardLayer(circuitJson, options) {
|
|
@@ -900,12 +1134,15 @@ async function convertCircuitJsonTo3D(circuitJson, options = {}) {
|
|
|
900
1134
|
const cadComponents = db.cad_component?.list?.() ?? [];
|
|
901
1135
|
const pcbComponentIdsWith3D = /* @__PURE__ */ new Set();
|
|
902
1136
|
for (const cad of cadComponents) {
|
|
903
|
-
let { model_stl_url, model_obj_url, model_glb_url } = cad;
|
|
904
|
-
const hasModelUrl = Boolean(
|
|
1137
|
+
let { model_stl_url, model_obj_url, model_glb_url, model_gltf_url } = cad;
|
|
1138
|
+
const hasModelUrl = Boolean(
|
|
1139
|
+
model_stl_url || model_obj_url || model_glb_url || model_gltf_url
|
|
1140
|
+
);
|
|
905
1141
|
if (!hasModelUrl && cad.footprinter_string) {
|
|
906
1142
|
model_glb_url = `https://modelcdn.tscircuit.com/jscad_models/${cad.footprinter_string}.glb`;
|
|
907
1143
|
}
|
|
908
|
-
if (!model_stl_url && !model_obj_url && !model_glb_url)
|
|
1144
|
+
if (!model_stl_url && !model_obj_url && !model_glb_url && !model_gltf_url)
|
|
1145
|
+
continue;
|
|
909
1146
|
pcbComponentIdsWith3D.add(cad.pcb_component_id);
|
|
910
1147
|
const pcbComponent = db.pcb_component.get(cad.pcb_component_id);
|
|
911
1148
|
const size = cad.size ?? {
|
|
@@ -918,26 +1155,35 @@ async function convertCircuitJsonTo3D(circuitJson, options = {}) {
|
|
|
918
1155
|
y: boardThickness / 2 + size.y / 2,
|
|
919
1156
|
z: pcbComponent?.center.y ?? 0
|
|
920
1157
|
};
|
|
1158
|
+
const meshType = model_stl_url ? "stl" : model_obj_url ? "obj" : model_gltf_url ? "gltf" : "glb";
|
|
921
1159
|
const box = {
|
|
922
1160
|
center,
|
|
923
1161
|
size,
|
|
924
|
-
meshUrl: model_stl_url || model_obj_url || model_glb_url,
|
|
925
|
-
meshType
|
|
1162
|
+
meshUrl: model_stl_url || model_obj_url || model_glb_url || model_gltf_url,
|
|
1163
|
+
meshType
|
|
926
1164
|
};
|
|
927
1165
|
if (cad.rotation) {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
} else
|
|
937
|
-
box.
|
|
1166
|
+
if (model_glb_url || model_gltf_url) {
|
|
1167
|
+
box.rotation = convertRotationFromCadRotation({
|
|
1168
|
+
x: cad.rotation.x,
|
|
1169
|
+
y: cad.rotation.z,
|
|
1170
|
+
// Circuit Z rotation becomes model Y rotation
|
|
1171
|
+
z: cad.rotation.y
|
|
1172
|
+
// Circuit Y rotation becomes model Z rotation
|
|
1173
|
+
});
|
|
1174
|
+
} else {
|
|
1175
|
+
box.rotation = convertRotationFromCadRotation(cad.rotation);
|
|
938
1176
|
}
|
|
939
|
-
}
|
|
940
|
-
|
|
1177
|
+
}
|
|
1178
|
+
const defaultTransform = coordinateTransform ?? (model_glb_url || model_gltf_url ? void 0 : COORDINATE_TRANSFORMS.Z_UP_TO_Y_UP_USB_FIX);
|
|
1179
|
+
if (model_stl_url) {
|
|
1180
|
+
box.mesh = await loadSTL(model_stl_url, defaultTransform);
|
|
1181
|
+
} else if (model_obj_url) {
|
|
1182
|
+
box.mesh = await loadOBJ(model_obj_url, defaultTransform);
|
|
1183
|
+
} else if (model_glb_url) {
|
|
1184
|
+
box.mesh = await loadGLB(model_glb_url, defaultTransform);
|
|
1185
|
+
} else if (model_gltf_url) {
|
|
1186
|
+
box.mesh = await loadGLTF(model_gltf_url, defaultTransform);
|
|
941
1187
|
}
|
|
942
1188
|
if (!box.mesh) {
|
|
943
1189
|
box.color = componentColor;
|
|
@@ -1619,18 +1865,19 @@ var GLTFBuilder = class {
|
|
|
1619
1865
|
const meshDataArray = createMeshFromOBJ(objMesh);
|
|
1620
1866
|
const objMaterialIndices = /* @__PURE__ */ new Map();
|
|
1621
1867
|
for (const [name, objMaterial] of objMesh.materials) {
|
|
1622
|
-
|
|
1623
|
-
const alpha = 1 - dissolve;
|
|
1624
|
-
let baseColor = [0.3, 0.3, 0.3, alpha];
|
|
1868
|
+
let baseColor = [0.3, 0.3, 0.3, 1];
|
|
1625
1869
|
if (objMaterial.color) {
|
|
1626
1870
|
const color = typeof objMaterial.color === "string" ? this.parseColorString(objMaterial.color) : [
|
|
1627
1871
|
objMaterial.color[0] / 255,
|
|
1628
1872
|
objMaterial.color[1] / 255,
|
|
1629
1873
|
objMaterial.color[2] / 255,
|
|
1630
|
-
|
|
1874
|
+
objMaterial.color[3]
|
|
1875
|
+
// Use the alpha from the color
|
|
1631
1876
|
];
|
|
1632
|
-
baseColor = [color[0], color[1], color[2],
|
|
1877
|
+
baseColor = [color[0], color[1], color[2], color[3]];
|
|
1633
1878
|
}
|
|
1879
|
+
const alpha = objMaterial.dissolve !== void 0 ? 1 - objMaterial.dissolve : baseColor[3];
|
|
1880
|
+
baseColor[3] = alpha;
|
|
1634
1881
|
const gltfMaterialIndex = this.addMaterial({
|
|
1635
1882
|
name: `OBJ_${name}`,
|
|
1636
1883
|
pbrMetallicRoughness: {
|