circuit-json-to-gltf 0.0.16 → 0.0.18

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.
Files changed (2) hide show
  1. package/dist/index.js +458 -50
  2. package/package.json +7 -3
package/dist/index.js CHANGED
@@ -1078,6 +1078,180 @@ async function renderBoardTextures(circuitJson, resolution = 1024) {
1078
1078
  return { top, bottom };
1079
1079
  }
1080
1080
 
1081
+ // lib/utils/pcb-board-geometry.ts
1082
+ import { extrudeLinear } from "@jscad/modeling/src/operations/extrusions";
1083
+ import {
1084
+ polygon,
1085
+ rectangle,
1086
+ roundedRectangle,
1087
+ cylinder
1088
+ } from "@jscad/modeling/src/primitives";
1089
+ import {
1090
+ translate,
1091
+ rotateZ,
1092
+ rotateX
1093
+ } from "@jscad/modeling/src/operations/transforms";
1094
+ import { subtract } from "@jscad/modeling/src/operations/booleans";
1095
+ import * as geom3 from "@jscad/modeling/src/geometries/geom3";
1096
+ import measureBoundingBox from "@jscad/modeling/src/measurements/measureBoundingBox";
1097
+ var DEFAULT_SEGMENTS = 64;
1098
+ var RADIUS_EPSILON = 1e-4;
1099
+ var toVec2 = (point, center) => [
1100
+ point.x - center.x,
1101
+ point.y - center.y
1102
+ ];
1103
+ var arePointsClockwise = (points) => {
1104
+ let area = 0;
1105
+ for (let i = 0; i < points.length; i++) {
1106
+ const j = (i + 1) % points.length;
1107
+ area += points[i][0] * points[j][1];
1108
+ area -= points[j][0] * points[i][1];
1109
+ }
1110
+ const signedArea = area / 2;
1111
+ return signedArea <= 0;
1112
+ };
1113
+ var getNumberProperty = (obj, key) => {
1114
+ const value = obj[key];
1115
+ return typeof value === "number" ? value : void 0;
1116
+ };
1117
+ var createBoardOutlineGeom = (board, center, thickness) => {
1118
+ if (board.outline && board.outline.length >= 3) {
1119
+ let outlinePoints = board.outline.map((pt) => toVec2(pt, center));
1120
+ if (arePointsClockwise(outlinePoints)) {
1121
+ outlinePoints = outlinePoints.slice().reverse();
1122
+ }
1123
+ const shape2d = polygon({ points: outlinePoints });
1124
+ let geom2 = extrudeLinear({ height: thickness }, shape2d);
1125
+ geom2 = translate([0, 0, -thickness / 2], geom2);
1126
+ return geom2;
1127
+ }
1128
+ const baseRect = rectangle({ size: [board.width, board.height] });
1129
+ let geom = extrudeLinear({ height: thickness }, baseRect);
1130
+ geom = translate([0, 0, -thickness / 2], geom);
1131
+ return geom;
1132
+ };
1133
+ var createCircularHole = (x, y, radius, thickness) => cylinder({
1134
+ center: [x, y, 0],
1135
+ height: thickness + 1,
1136
+ radius,
1137
+ segments: DEFAULT_SEGMENTS
1138
+ });
1139
+ var createPillHole = (x, y, width, height, thickness, rotate) => {
1140
+ const minDimension = Math.min(width, height);
1141
+ const maxAllowedRadius = Math.max(0, minDimension / 2 - RADIUS_EPSILON);
1142
+ const roundRadius = maxAllowedRadius <= 0 ? 0 : Math.min(height / 2, maxAllowedRadius);
1143
+ const hole2d = roundedRectangle({
1144
+ size: [width, height],
1145
+ roundRadius,
1146
+ segments: DEFAULT_SEGMENTS
1147
+ });
1148
+ let hole3d = extrudeLinear({ height: thickness + 1 }, hole2d);
1149
+ hole3d = translate([0, 0, -(thickness + 1) / 2], hole3d);
1150
+ if (rotate) {
1151
+ hole3d = rotateZ(Math.PI / 2, hole3d);
1152
+ }
1153
+ return translate([x, y, 0], hole3d);
1154
+ };
1155
+ var createHoleGeoms = (boardCenter, thickness, holes = [], platedHoles = []) => {
1156
+ const holeGeoms = [];
1157
+ for (const hole of holes) {
1158
+ const holeRecord = hole;
1159
+ const diameter = getNumberProperty(holeRecord, "hole_diameter") ?? getNumberProperty(holeRecord, "diameter");
1160
+ if (!diameter) continue;
1161
+ const radius = diameter / 2;
1162
+ const relX = hole.x - boardCenter.x;
1163
+ const relY = -(hole.y - boardCenter.y);
1164
+ holeGeoms.push(createCircularHole(relX, relY, radius, thickness));
1165
+ }
1166
+ for (const plated of platedHoles) {
1167
+ const relX = plated.x - boardCenter.x;
1168
+ const relY = -(plated.y - boardCenter.y);
1169
+ const platedRecord = plated;
1170
+ if (plated.shape === "pill" || plated.shape === "pill_hole_with_rect_pad") {
1171
+ const holeWidth = getNumberProperty(platedRecord, "hole_width") ?? getNumberProperty(platedRecord, "outer_diameter") ?? 0;
1172
+ const holeHeight = getNumberProperty(platedRecord, "hole_height") ?? getNumberProperty(platedRecord, "hole_diameter") ?? 0;
1173
+ if (!holeWidth || !holeHeight) continue;
1174
+ const rotate = holeHeight > holeWidth;
1175
+ const width = rotate ? holeHeight : holeWidth;
1176
+ const height = rotate ? holeWidth : holeHeight;
1177
+ holeGeoms.push(
1178
+ createPillHole(relX, relY, width, height, thickness, rotate)
1179
+ );
1180
+ continue;
1181
+ }
1182
+ const diameter = getNumberProperty(platedRecord, "hole_diameter") ?? getNumberProperty(platedRecord, "outer_diameter");
1183
+ if (!diameter) continue;
1184
+ holeGeoms.push(createCircularHole(relX, relY, diameter / 2, thickness));
1185
+ }
1186
+ return holeGeoms;
1187
+ };
1188
+ var geom3ToTriangles = (geometry, polygons) => {
1189
+ const sourcePolygons = polygons ?? geom3.toPolygons(geometry);
1190
+ const triangles = [];
1191
+ for (const poly of sourcePolygons) {
1192
+ if (!poly || poly.vertices.length < 3) continue;
1193
+ const base = poly.vertices[0];
1194
+ const next = poly.vertices[1];
1195
+ const next2 = poly.vertices[2];
1196
+ const ab = [next[0] - base[0], next[1] - base[1], next[2] - base[2]];
1197
+ const ac = [
1198
+ next2[0] - base[0],
1199
+ next2[1] - base[1],
1200
+ next2[2] - base[2]
1201
+ ];
1202
+ const cross = [
1203
+ ab[1] * ac[2] - ab[2] * ac[1],
1204
+ ab[2] * ac[0] - ab[0] * ac[2],
1205
+ ab[0] * ac[1] - ab[1] * ac[0]
1206
+ ];
1207
+ const length = Math.sqrt(cross[0] ** 2 + cross[1] ** 2 + cross[2] ** 2) || 1;
1208
+ const normal = {
1209
+ x: cross[0] / length,
1210
+ y: cross[1] / length,
1211
+ z: cross[2] / length
1212
+ };
1213
+ for (let i = 1; i < poly.vertices.length - 1; i++) {
1214
+ const v1 = poly.vertices[i];
1215
+ const v2 = poly.vertices[i + 1];
1216
+ const triangle = {
1217
+ vertices: [
1218
+ { x: base[0], y: base[1], z: base[2] },
1219
+ { x: v1[0], y: v1[1], z: v1[2] },
1220
+ { x: v2[0], y: v2[1], z: v2[2] }
1221
+ ],
1222
+ normal
1223
+ };
1224
+ triangles.push(triangle);
1225
+ }
1226
+ }
1227
+ return triangles;
1228
+ };
1229
+ var createBoundingBox = (bbox) => {
1230
+ const [min, max] = bbox;
1231
+ return {
1232
+ min: { x: min[0], y: min[1], z: min[2] },
1233
+ max: { x: max[0], y: max[1], z: max[2] }
1234
+ };
1235
+ };
1236
+ var createBoardMesh = (board, options) => {
1237
+ const { thickness, holes = [], platedHoles = [] } = options;
1238
+ const center = board.center ?? { x: 0, y: 0 };
1239
+ let boardGeom = createBoardOutlineGeom(board, center, thickness);
1240
+ const holeGeoms = createHoleGeoms(center, thickness, holes, platedHoles);
1241
+ if (holeGeoms.length > 0) {
1242
+ boardGeom = subtract(boardGeom, ...holeGeoms);
1243
+ }
1244
+ boardGeom = rotateX(-Math.PI / 2, boardGeom);
1245
+ const polygons = geom3.toPolygons(boardGeom);
1246
+ const triangles = geom3ToTriangles(boardGeom, polygons);
1247
+ const bboxValues = measureBoundingBox(boardGeom);
1248
+ const boundingBox = createBoundingBox(bboxValues);
1249
+ return {
1250
+ triangles,
1251
+ boundingBox
1252
+ };
1253
+ };
1254
+
1081
1255
  // lib/converters/circuit-to-3d.ts
1082
1256
  var DEFAULT_BOARD_THICKNESS = 1.6;
1083
1257
  var DEFAULT_COMPONENT_HEIGHT = 2;
@@ -1100,37 +1274,51 @@ async function convertCircuitJsonTo3D(circuitJson, options = {}) {
1100
1274
  } = options;
1101
1275
  const db = cju(circuitJson);
1102
1276
  const boxes = [];
1103
- const pcbBoard = db.pcb_board.list()[0];
1104
- if (!pcbBoard) {
1105
- throw new Error("No pcb_board found in circuit JSON");
1106
- }
1107
- const boardBox = {
1108
- center: {
1109
- x: pcbBoard.center.x,
1110
- y: 0,
1111
- z: pcbBoard.center.y
1112
- },
1113
- size: {
1114
- x: pcbBoard.width,
1115
- y: boardThickness,
1116
- z: pcbBoard.height
1117
- }
1118
- };
1119
- if (shouldRenderTextures && textureResolution > 0) {
1120
- try {
1121
- const textures = await renderBoardTextures(circuitJson, textureResolution);
1122
- boardBox.texture = {
1123
- top: textures.top,
1124
- bottom: textures.bottom
1125
- };
1126
- } catch (error) {
1127
- console.warn("Failed to render board textures:", error);
1277
+ const pcbBoard = db.pcb_board?.list?.()[0];
1278
+ const effectiveBoardThickness = pcbBoard?.thickness ?? boardThickness;
1279
+ if (pcbBoard) {
1280
+ const pcbHoles = db.pcb_hole?.list?.() ?? [];
1281
+ const pcbPlatedHoles = db.pcb_plated_hole?.list?.() ?? [];
1282
+ const boardMesh = createBoardMesh(pcbBoard, {
1283
+ thickness: effectiveBoardThickness,
1284
+ holes: pcbHoles,
1285
+ platedHoles: pcbPlatedHoles
1286
+ });
1287
+ const meshWidth = boardMesh.boundingBox.max.x - boardMesh.boundingBox.min.x;
1288
+ const meshHeight = boardMesh.boundingBox.max.z - boardMesh.boundingBox.min.z;
1289
+ const boardBox = {
1290
+ center: {
1291
+ x: pcbBoard.center.x,
1292
+ y: 0,
1293
+ z: pcbBoard.center.y
1294
+ },
1295
+ size: {
1296
+ x: Number.isFinite(meshWidth) ? meshWidth : pcbBoard.width,
1297
+ y: effectiveBoardThickness,
1298
+ z: Number.isFinite(meshHeight) ? meshHeight : pcbBoard.height
1299
+ },
1300
+ mesh: boardMesh,
1301
+ color: pcbColor
1302
+ };
1303
+ if (shouldRenderTextures && textureResolution > 0) {
1304
+ try {
1305
+ const textures = await renderBoardTextures(
1306
+ circuitJson,
1307
+ textureResolution
1308
+ );
1309
+ boardBox.texture = {
1310
+ top: textures.top,
1311
+ bottom: textures.bottom
1312
+ };
1313
+ } catch (error) {
1314
+ console.warn("Failed to render board textures:", error);
1315
+ boardBox.color = pcbColor;
1316
+ }
1317
+ } else {
1128
1318
  boardBox.color = pcbColor;
1129
1319
  }
1130
- } else {
1131
- boardBox.color = pcbColor;
1320
+ boxes.push(boardBox);
1132
1321
  }
1133
- boxes.push(boardBox);
1134
1322
  const cadComponents = db.cad_component?.list?.() ?? [];
1135
1323
  const pcbComponentIdsWith3D = /* @__PURE__ */ new Set();
1136
1324
  for (const cad of cadComponents) {
@@ -1157,7 +1345,7 @@ async function convertCircuitJsonTo3D(circuitJson, options = {}) {
1157
1345
  z: cad.position.y
1158
1346
  } : {
1159
1347
  x: pcbComponent?.center.x ?? 0,
1160
- y: isBottomLayer ? -(boardThickness / 2 + size.y / 2) : boardThickness / 2 + size.y / 2,
1348
+ y: isBottomLayer ? -(effectiveBoardThickness / 2 + size.y / 2) : effectiveBoardThickness / 2 + size.y / 2,
1161
1349
  z: pcbComponent?.center.y ?? 0
1162
1350
  };
1163
1351
  const meshType = model_stl_url ? "stl" : model_obj_url ? "obj" : model_gltf_url ? "gltf" : "glb";
@@ -1226,7 +1414,7 @@ async function convertCircuitJsonTo3D(circuitJson, options = {}) {
1226
1414
  boxes.push({
1227
1415
  center: {
1228
1416
  x: component.center.x,
1229
- y: isBottomLayer ? -(boardThickness / 2 + compHeight / 2) : boardThickness / 2 + compHeight / 2,
1417
+ y: isBottomLayer ? -(effectiveBoardThickness / 2 + compHeight / 2) : effectiveBoardThickness / 2 + compHeight / 2,
1230
1418
  z: component.center.y
1231
1419
  },
1232
1420
  size: {
@@ -1239,26 +1427,72 @@ async function convertCircuitJsonTo3D(circuitJson, options = {}) {
1239
1427
  labelColor: "white"
1240
1428
  });
1241
1429
  }
1242
- const boardDiagonal = Math.sqrt(
1243
- pcbBoard.width * pcbBoard.width + pcbBoard.height * pcbBoard.height
1244
- );
1245
- const cameraDistance = boardDiagonal * 1.5;
1246
- const camera = {
1247
- position: {
1248
- x: pcbBoard.center.x + cameraDistance * 0.5,
1249
- y: cameraDistance * 0.7,
1250
- z: pcbBoard.center.y + cameraDistance * 0.5
1251
- },
1252
- target: {
1253
- x: pcbBoard.center.x,
1254
- y: 0,
1255
- z: pcbBoard.center.y
1256
- },
1257
- up: { x: 0, y: 1, z: 0 },
1258
- fov: 50,
1259
- near: 0.1,
1260
- far: cameraDistance * 4
1261
- };
1430
+ let camera;
1431
+ if (pcbBoard) {
1432
+ const boardDiagonal = Math.sqrt(
1433
+ pcbBoard.width * pcbBoard.width + pcbBoard.height * pcbBoard.height
1434
+ );
1435
+ const cameraDistance = boardDiagonal * 1.5;
1436
+ camera = {
1437
+ position: {
1438
+ x: pcbBoard.center.x + cameraDistance * 0.5,
1439
+ y: cameraDistance * 0.7,
1440
+ z: pcbBoard.center.y + cameraDistance * 0.5
1441
+ },
1442
+ target: {
1443
+ x: pcbBoard.center.x,
1444
+ y: 0,
1445
+ z: pcbBoard.center.y
1446
+ },
1447
+ up: { x: 0, y: 1, z: 0 },
1448
+ fov: 50,
1449
+ near: 0.1,
1450
+ far: cameraDistance * 4
1451
+ };
1452
+ } else {
1453
+ const hasBoxes = boxes.length > 0;
1454
+ if (hasBoxes) {
1455
+ let minX = Infinity;
1456
+ let minZ = Infinity;
1457
+ let maxX = -Infinity;
1458
+ let maxZ = -Infinity;
1459
+ for (const box of boxes) {
1460
+ const halfX = (box.size?.x ?? 0) / 2;
1461
+ const halfZ = (box.size?.z ?? 0) / 2;
1462
+ minX = Math.min(minX, box.center.x - halfX);
1463
+ maxX = Math.max(maxX, box.center.x + halfX);
1464
+ minZ = Math.min(minZ, box.center.z - halfZ);
1465
+ maxZ = Math.max(maxZ, box.center.z + halfZ);
1466
+ }
1467
+ const width = Math.max(maxX - minX, 1);
1468
+ const height = Math.max(maxZ - minZ, 1);
1469
+ const diagonal = Math.sqrt(width * width + height * height);
1470
+ const distance = diagonal * 1.5;
1471
+ const centerX = (minX + maxX) / 2;
1472
+ const centerZ = (minZ + maxZ) / 2;
1473
+ camera = {
1474
+ position: {
1475
+ x: centerX + distance * 0.5,
1476
+ y: distance * 0.7,
1477
+ z: centerZ + distance * 0.5
1478
+ },
1479
+ target: { x: centerX, y: 0, z: centerZ },
1480
+ up: { x: 0, y: 1, z: 0 },
1481
+ fov: 50,
1482
+ near: 0.1,
1483
+ far: distance * 4
1484
+ };
1485
+ } else {
1486
+ camera = {
1487
+ position: { x: 30, y: 30, z: 25 },
1488
+ target: { x: 0, y: 0, z: 0 },
1489
+ up: { x: 0, y: 1, z: 0 },
1490
+ fov: 50,
1491
+ near: 0.1,
1492
+ far: 120
1493
+ };
1494
+ }
1495
+ }
1262
1496
  const lights = [
1263
1497
  {
1264
1498
  type: "ambient",
@@ -1969,7 +2203,181 @@ var GLTFBuilder = class {
1969
2203
  });
1970
2204
  this.gltf.scenes[0].nodes.push(nodeIndex);
1971
2205
  }
2206
+ async addMeshWithFaceTextures(box, defaultMaterialIndex) {
2207
+ const topTriangles = [];
2208
+ const bottomTriangles = [];
2209
+ const sideTriangles = [];
2210
+ const yThreshold = 0.8;
2211
+ for (const triangle of box.mesh.triangles) {
2212
+ const ny = Math.abs(triangle.normal.y);
2213
+ if (ny > yThreshold) {
2214
+ if (triangle.normal.y > 0) {
2215
+ topTriangles.push(triangle);
2216
+ } else {
2217
+ bottomTriangles.push(triangle);
2218
+ }
2219
+ } else {
2220
+ sideTriangles.push(triangle);
2221
+ }
2222
+ }
2223
+ const materials = [];
2224
+ if (topTriangles.length > 0 && box.texture?.top) {
2225
+ const topMaterialIndex = this.addMaterial({
2226
+ name: `TopMaterial_${this.materials.length}`,
2227
+ pbrMetallicRoughness: {
2228
+ baseColorFactor: [1, 1, 1, 1],
2229
+ metallicFactor: 0,
2230
+ roughnessFactor: 0.8
2231
+ },
2232
+ alphaMode: "OPAQUE",
2233
+ doubleSided: true
2234
+ });
2235
+ const textureIndex = await this.addTextureFromDataUrl(box.texture.top);
2236
+ if (textureIndex !== -1) {
2237
+ const material = this.materials[topMaterialIndex];
2238
+ if (material.pbrMetallicRoughness) {
2239
+ material.pbrMetallicRoughness.baseColorTexture = {
2240
+ index: textureIndex
2241
+ };
2242
+ }
2243
+ }
2244
+ materials.push({
2245
+ triangles: topTriangles,
2246
+ materialIndex: topMaterialIndex
2247
+ });
2248
+ }
2249
+ if (bottomTriangles.length > 0 && box.texture?.bottom) {
2250
+ const bottomMaterialIndex = this.addMaterial({
2251
+ name: `BottomMaterial_${this.materials.length}`,
2252
+ pbrMetallicRoughness: {
2253
+ baseColorFactor: [1, 1, 1, 1],
2254
+ metallicFactor: 0,
2255
+ roughnessFactor: 0.8
2256
+ },
2257
+ alphaMode: "OPAQUE",
2258
+ doubleSided: true
2259
+ });
2260
+ const textureIndex = await this.addTextureFromDataUrl(box.texture.bottom);
2261
+ if (textureIndex !== -1) {
2262
+ const material = this.materials[bottomMaterialIndex];
2263
+ if (material.pbrMetallicRoughness) {
2264
+ material.pbrMetallicRoughness.baseColorTexture = {
2265
+ index: textureIndex
2266
+ };
2267
+ }
2268
+ }
2269
+ materials.push({
2270
+ triangles: bottomTriangles,
2271
+ materialIndex: bottomMaterialIndex
2272
+ });
2273
+ }
2274
+ if (sideTriangles.length > 0) {
2275
+ const sideMaterialIndex = this.addMaterial({
2276
+ name: `GreenSideMaterial_${this.materials.length}`,
2277
+ pbrMetallicRoughness: {
2278
+ baseColorFactor: [0, 0.55, 0, 1],
2279
+ metallicFactor: 0,
2280
+ roughnessFactor: 0.8
2281
+ },
2282
+ alphaMode: "OPAQUE",
2283
+ doubleSided: true
2284
+ });
2285
+ materials.push({
2286
+ triangles: sideTriangles,
2287
+ materialIndex: sideMaterialIndex
2288
+ });
2289
+ }
2290
+ const primitives = [];
2291
+ const bounds = getBounds([]);
2292
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
2293
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
2294
+ for (const triangle of box.mesh.triangles) {
2295
+ for (const v of triangle.vertices) {
2296
+ minX = Math.min(minX, v.x);
2297
+ minY = Math.min(minY, v.y);
2298
+ minZ = Math.min(minZ, v.z);
2299
+ maxX = Math.max(maxX, v.x);
2300
+ maxY = Math.max(maxY, v.y);
2301
+ maxZ = Math.max(maxZ, v.z);
2302
+ }
2303
+ }
2304
+ const sizeX = maxX - minX;
2305
+ const sizeZ = maxZ - minZ;
2306
+ for (const { triangles, materialIndex } of materials) {
2307
+ const positions = [];
2308
+ const normals = [];
2309
+ const texcoords = [];
2310
+ const indices = [];
2311
+ let vertexIndex = 0;
2312
+ for (const triangle of triangles) {
2313
+ for (const v of triangle.vertices) {
2314
+ positions.push(v.x, v.y, v.z);
2315
+ normals.push(triangle.normal.x, triangle.normal.y, triangle.normal.z);
2316
+ const u = sizeX > 0 ? (v.x - minX) / sizeX : 0.5;
2317
+ const v_coord = sizeZ > 0 ? (v.z - minZ) / sizeZ : 0.5;
2318
+ texcoords.push(u, 1 - v_coord);
2319
+ }
2320
+ indices.push(vertexIndex, vertexIndex + 1, vertexIndex + 2);
2321
+ vertexIndex += 3;
2322
+ }
2323
+ const meshData = { positions, normals, texcoords, indices };
2324
+ const transformedMeshData = transformMesh(
2325
+ meshData,
2326
+ box.center,
2327
+ box.rotation
2328
+ );
2329
+ const positionAccessorIndex = this.addAccessor(
2330
+ transformedMeshData.positions,
2331
+ "VEC3",
2332
+ COMPONENT_TYPE.FLOAT,
2333
+ TARGET.ARRAY_BUFFER
2334
+ );
2335
+ const normalAccessorIndex = this.addAccessor(
2336
+ transformedMeshData.normals,
2337
+ "VEC3",
2338
+ COMPONENT_TYPE.FLOAT,
2339
+ TARGET.ARRAY_BUFFER
2340
+ );
2341
+ const texcoordAccessorIndex = this.addAccessor(
2342
+ transformedMeshData.texcoords,
2343
+ "VEC2",
2344
+ COMPONENT_TYPE.FLOAT,
2345
+ TARGET.ARRAY_BUFFER
2346
+ );
2347
+ const indicesAccessorIndex = this.addAccessor(
2348
+ transformedMeshData.indices,
2349
+ "SCALAR",
2350
+ COMPONENT_TYPE.UNSIGNED_SHORT,
2351
+ TARGET.ELEMENT_ARRAY_BUFFER
2352
+ );
2353
+ primitives.push({
2354
+ attributes: {
2355
+ POSITION: positionAccessorIndex,
2356
+ NORMAL: normalAccessorIndex,
2357
+ TEXCOORD_0: texcoordAccessorIndex
2358
+ },
2359
+ indices: indicesAccessorIndex,
2360
+ material: materialIndex,
2361
+ mode: PRIMITIVE_MODE.TRIANGLES
2362
+ });
2363
+ }
2364
+ const meshIndex = this.meshes.length;
2365
+ this.meshes.push({
2366
+ name: box.label || `MeshWithTextures${meshIndex}`,
2367
+ primitives
2368
+ });
2369
+ const nodeIndex = this.nodes.length;
2370
+ this.nodes.push({
2371
+ name: box.label || `Box${nodeIndex}`,
2372
+ mesh: meshIndex
2373
+ });
2374
+ this.gltf.scenes[0].nodes.push(nodeIndex);
2375
+ }
1972
2376
  async addBoxWithFaceMaterials(box, defaultMaterialIndex) {
2377
+ if (box.mesh) {
2378
+ await this.addMeshWithFaceTextures(box, defaultMaterialIndex);
2379
+ return;
2380
+ }
1973
2381
  const faceMeshes = createBoxMeshByFaces(box.size);
1974
2382
  const faceMaterials = {};
1975
2383
  if (box.texture?.top) {
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.16",
5
+ "version": "0.0.18",
6
6
  "scripts": {
7
7
  "test": "bun test tests/",
8
8
  "format": "biome format --write .",
@@ -27,15 +27,16 @@
27
27
  "bun-match-svg": "^0.0.12",
28
28
  "circuit-json": "^0.0.267",
29
29
  "circuit-to-svg": "^0.0.175",
30
+ "graphics-debug": "^0.0.65",
30
31
  "looks-same": "^9.0.1",
31
32
  "poppygl": "^0.0.9",
32
33
  "react": "^19.1.1",
33
34
  "react-cosmos": "^7.0.0",
34
35
  "react-cosmos-plugin-vite": "^7.0.0",
35
36
  "react-dom": "^19.1.1",
37
+ "tscircuit": "^0.0.701",
36
38
  "tsup": "^8.5.0",
37
- "vite": "^7.1.1",
38
- "tscircuit": "^0.0.701"
39
+ "vite": "^7.1.1"
39
40
  },
40
41
  "peerDependencies": {
41
42
  "typescript": "^5",
@@ -52,5 +53,8 @@
52
53
  "@resvg/resvg-js": {
53
54
  "optional": true
54
55
  }
56
+ },
57
+ "dependencies": {
58
+ "@jscad/modeling": "^2.12.6"
55
59
  }
56
60
  }