@tscircuit/3d-viewer 0.0.281 → 0.0.283

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.js CHANGED
@@ -9914,7 +9914,7 @@ var require_vectorText = __commonJS({
9914
9914
  }
9915
9915
  return line3;
9916
9916
  };
9917
- var vectorText3 = (options, text) => {
9917
+ var vectorText2 = (options, text) => {
9918
9918
  const {
9919
9919
  xOffset,
9920
9920
  yOffset,
@@ -9971,7 +9971,7 @@ var require_vectorText = __commonJS({
9971
9971
  }
9972
9972
  return output;
9973
9973
  };
9974
- module.exports = vectorText3;
9974
+ module.exports = vectorText2;
9975
9975
  }
9976
9976
  });
9977
9977
 
@@ -13980,7 +13980,7 @@ var require_browser = __commonJS({
13980
13980
  });
13981
13981
 
13982
13982
  // src/CadViewer.tsx
13983
- import { useState as useState11, useRef as useRef8, useEffect as useEffect8 } from "react";
13983
+ import { useState as useState10, useRef as useRef8, useEffect as useEffect7 } from "react";
13984
13984
 
13985
13985
  // src/CadViewerJscad.tsx
13986
13986
  import { su as su4 } from "@tscircuit/soup-util";
@@ -17282,7 +17282,7 @@ import {
17282
17282
  // package.json
17283
17283
  var package_default = {
17284
17284
  name: "@tscircuit/3d-viewer",
17285
- version: "0.0.280",
17285
+ version: "0.0.282",
17286
17286
  main: "./dist/index.js",
17287
17287
  module: "./dist/index.js",
17288
17288
  type: "module",
@@ -17314,7 +17314,7 @@ var package_default = {
17314
17314
  "@react-three/drei": "^9.121.4",
17315
17315
  "@react-three/fiber": "^8.17.14",
17316
17316
  "@tscircuit/core": "^0.0.454",
17317
- "@tscircuit/props": "^0.0.238",
17317
+ "@tscircuit/props": "^0.0.240",
17318
17318
  "@tscircuit/soup-util": "^0.0.41",
17319
17319
  "jscad-electronics": "^0.0.29",
17320
17320
  "jscad-fiber": "^0.0.79",
@@ -17677,10 +17677,6 @@ var colors = {
17677
17677
  fr1Copper: [0.8, 0.4, 0.2],
17678
17678
  fr1CopperSolderWithMask: [0.9, 0.6, 0.2]
17679
17679
  };
17680
- var MANIFOLD_Z_OFFSET = 1e-3;
17681
- var SMOOTH_CIRCLE_SEGMENTS = 32;
17682
- var DEFAULT_SMT_PAD_THICKNESS = 0.035;
17683
- var TRACE_TEXTURE_RESOLUTION = 50;
17684
17680
  var boardMaterialColors = {
17685
17681
  fr1: colors.fr1Copper,
17686
17682
  fr4: colors.fr4Green
@@ -19056,14 +19052,13 @@ var CadViewerJscad = forwardRef2(
19056
19052
  );
19057
19053
 
19058
19054
  // src/CadViewerManifold.tsx
19059
- import { su as su13 } from "@tscircuit/soup-util";
19060
- import ManifoldModule from "manifold-3d";
19061
- import { useEffect as useEffect6, useMemo as useMemo8, useState as useState9 } from "react";
19055
+ import { su as su6 } from "@tscircuit/soup-util";
19056
+ import { useMemo as useMemo8 } from "react";
19062
19057
 
19063
19058
  // src/hooks/useManifoldBoardBuilder.ts
19064
19059
  import { useState as useState8, useEffect as useEffect5, useMemo as useMemo7, useRef as useRef6 } from "react";
19065
- import { su as su12 } from "@tscircuit/soup-util";
19066
- import * as THREE10 from "three";
19060
+ import { su as su5 } from "@tscircuit/soup-util";
19061
+ import * as THREE5 from "three";
19067
19062
 
19068
19063
  // src/utils/manifold-mesh-to-three-geometry.ts
19069
19064
  import * as THREE4 from "three";
@@ -19086,914 +19081,150 @@ function manifoldMeshToThreeGeometry(manifoldMesh) {
19086
19081
  return geometry;
19087
19082
  }
19088
19083
 
19089
- // src/utils/trace-texture.ts
19090
- import * as THREE5 from "three";
19091
- import { su as su5 } from "@tscircuit/soup-util";
19092
- function isWireRoutePoint(point) {
19093
- return point && point.route_type === "wire" && typeof point.layer === "string" && typeof point.width === "number";
19094
- }
19095
- function createTraceTextureForLayer({
19096
- layer,
19097
- circuitJson,
19098
- boardData,
19099
- traceColor,
19100
- traceTextureResolution
19101
- }) {
19102
- const pcbTraces = su5(circuitJson).pcb_trace.list();
19103
- const allPcbVias = su5(circuitJson).pcb_via.list();
19104
- const allPcbPlatedHoles = su5(
19105
- circuitJson
19106
- ).pcb_plated_hole.list();
19107
- const tracesOnLayer = pcbTraces.filter(
19108
- (t) => t.route.some((p) => isWireRoutePoint(p) && p.layer === layer)
19109
- );
19110
- if (tracesOnLayer.length === 0) return null;
19111
- const canvas = document.createElement("canvas");
19112
- const canvasWidth = Math.floor(boardData.width * traceTextureResolution);
19113
- const canvasHeight = Math.floor(boardData.height * traceTextureResolution);
19114
- canvas.width = canvasWidth;
19115
- canvas.height = canvasHeight;
19116
- const ctx = canvas.getContext("2d");
19117
- if (!ctx) return null;
19118
- if (layer === "bottom") {
19119
- ctx.translate(0, canvasHeight);
19120
- ctx.scale(1, -1);
19121
- }
19122
- tracesOnLayer.forEach((trace) => {
19123
- let firstPoint = true;
19124
- ctx.beginPath();
19125
- ctx.strokeStyle = traceColor;
19126
- ctx.lineCap = "round";
19127
- ctx.lineJoin = "round";
19128
- let currentLineWidth = 0;
19129
- for (const point of trace.route) {
19130
- if (!isWireRoutePoint(point) || point.layer !== layer) {
19131
- if (!firstPoint) ctx.stroke();
19132
- firstPoint = true;
19133
- continue;
19134
- }
19135
- const pcbX = point.x;
19136
- const pcbY = point.y;
19137
- currentLineWidth = point.width * traceTextureResolution;
19138
- ctx.lineWidth = currentLineWidth;
19139
- const canvasX = (pcbX - boardData.center.x + boardData.width / 2) * traceTextureResolution;
19140
- const canvasY = (-(pcbY - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
19141
- if (firstPoint) {
19142
- ctx.moveTo(canvasX, canvasY);
19143
- firstPoint = false;
19144
- } else {
19145
- ctx.lineTo(canvasX, canvasY);
19146
- }
19147
- }
19148
- if (!firstPoint) {
19149
- ctx.stroke();
19150
- }
19151
- });
19152
- ctx.globalCompositeOperation = "destination-out";
19153
- ctx.fillStyle = "black";
19154
- allPcbVias.forEach((via) => {
19155
- const canvasX = (via.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
19156
- const canvasY = (-(via.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
19157
- const canvasRadius = via.outer_diameter / 2 * traceTextureResolution;
19158
- ctx.beginPath();
19159
- ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
19160
- ctx.fill();
19161
- });
19162
- allPcbPlatedHoles.forEach((ph) => {
19163
- if (ph.layers.includes(layer) && ph.shape === "circle") {
19164
- const canvasX = (ph.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
19165
- const canvasY = (-(ph.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
19166
- const canvasRadius = ph.outer_diameter / 2 * traceTextureResolution;
19167
- ctx.beginPath();
19168
- ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
19169
- ctx.fill();
19170
- }
19171
- });
19172
- ctx.globalCompositeOperation = "source-over";
19173
- const texture = new THREE5.CanvasTexture(canvas);
19174
- texture.generateMipmaps = true;
19175
- texture.minFilter = THREE5.LinearMipmapLinearFilter;
19176
- texture.magFilter = THREE5.LinearFilter;
19177
- texture.anisotropy = 16;
19178
- texture.needsUpdate = true;
19179
- return texture;
19180
- }
19181
-
19182
- // src/utils/silkscreen-texture.ts
19183
- var import_text2 = __toESM(require_text(), 1);
19184
- import * as THREE6 from "three";
19185
- import { su as su6 } from "@tscircuit/soup-util";
19186
- function createSilkscreenTextureForLayer({
19187
- layer,
19188
- circuitJson,
19189
- boardData,
19190
- silkscreenColor = "rgb(255,255,255)",
19191
- traceTextureResolution
19192
- }) {
19193
- const pcbSilkscreenTexts = su6(circuitJson).pcb_silkscreen_text.list();
19194
- const pcbSilkscreenPaths = su6(circuitJson).pcb_silkscreen_path.list();
19195
- const textsOnLayer = pcbSilkscreenTexts.filter((t) => t.layer === layer);
19196
- const pathsOnLayer = pcbSilkscreenPaths.filter((p) => p.layer === layer);
19197
- if (textsOnLayer.length === 0 && pathsOnLayer.length === 0) return null;
19198
- const canvas = document.createElement("canvas");
19199
- const canvasWidth = Math.floor(boardData.width * traceTextureResolution);
19200
- const canvasHeight = Math.floor(boardData.height * traceTextureResolution);
19201
- canvas.width = canvasWidth;
19202
- canvas.height = canvasHeight;
19203
- const ctx = canvas.getContext("2d");
19204
- if (!ctx) return null;
19205
- if (layer === "bottom") {
19206
- ctx.translate(0, canvasHeight);
19207
- ctx.scale(1, -1);
19208
- }
19209
- ctx.strokeStyle = silkscreenColor;
19210
- ctx.fillStyle = silkscreenColor;
19211
- pathsOnLayer.forEach((path) => {
19212
- if (path.route.length < 2) return;
19213
- ctx.beginPath();
19214
- ctx.lineWidth = (path.stroke_width || 0.1) * traceTextureResolution;
19215
- ctx.lineCap = "round";
19216
- ctx.lineJoin = "round";
19217
- path.route.forEach((point, index) => {
19218
- const canvasX = (point.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
19219
- const canvasY = (-(point.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
19220
- if (index === 0) ctx.moveTo(canvasX, canvasY);
19221
- else ctx.lineTo(canvasX, canvasY);
19222
- });
19223
- ctx.stroke();
19224
- });
19225
- textsOnLayer.forEach((textS) => {
19226
- const fontSize = textS.font_size || 0.25;
19227
- const textStrokeWidth = Math.min(Math.max(0.01, fontSize * 0.1), fontSize * 0.05) * traceTextureResolution;
19228
- ctx.lineWidth = textStrokeWidth;
19229
- ctx.lineCap = "butt";
19230
- ctx.lineJoin = "miter";
19231
- const rawTextOutlines = (0, import_text2.vectorText)({
19232
- height: fontSize * 0.45,
19233
- input: textS.text
19234
- });
19235
- const processedTextOutlines = [];
19236
- rawTextOutlines.forEach((outline) => {
19237
- if (outline.length === 29) {
19238
- processedTextOutlines.push(
19239
- outline.slice(0, 15)
19240
- );
19241
- processedTextOutlines.push(
19242
- outline.slice(14, 29)
19243
- );
19244
- } else if (outline.length === 17) {
19245
- processedTextOutlines.push(
19246
- outline.slice(0, 10)
19247
- );
19248
- processedTextOutlines.push(
19249
- outline.slice(9, 17)
19250
- );
19251
- } else {
19252
- processedTextOutlines.push(outline);
19253
- }
19254
- });
19255
- const points = processedTextOutlines.flat();
19256
- const textBounds = {
19257
- minX: points.length > 0 ? Math.min(...points.map((p) => p[0])) : 0,
19258
- maxX: points.length > 0 ? Math.max(...points.map((p) => p[0])) : 0,
19259
- minY: points.length > 0 ? Math.min(...points.map((p) => p[1])) : 0,
19260
- maxY: points.length > 0 ? Math.max(...points.map((p) => p[1])) : 0
19261
- };
19262
- const textCenterX = (textBounds.minX + textBounds.maxX) / 2;
19263
- const textCenterY = (textBounds.minY + textBounds.maxY) / 2;
19264
- let xOff = -textCenterX;
19265
- let yOff = -textCenterY;
19266
- const alignment = textS.anchor_alignment || "center";
19267
- if (alignment.includes("left")) {
19268
- xOff = -textBounds.minX;
19269
- } else if (alignment.includes("right")) {
19270
- xOff = -textBounds.maxX;
19271
- }
19272
- if (alignment.includes("top")) {
19273
- yOff = -textBounds.maxY;
19274
- } else if (alignment.includes("bottom")) {
19275
- yOff = -textBounds.minY;
19276
- }
19277
- const transformMatrices = [];
19278
- let rotationDeg = textS.ccw_rotation ?? 0;
19279
- if (textS.layer === "bottom") {
19280
- transformMatrices.push(
19281
- translate2(textCenterX, textCenterY),
19282
- { a: -1, b: 0, c: 0, d: 1, e: 0, f: 0 },
19283
- translate2(-textCenterX, -textCenterY)
19284
- );
19285
- rotationDeg = -rotationDeg;
19286
- }
19287
- if (rotationDeg) {
19288
- const rad = rotationDeg * Math.PI / 180;
19289
- transformMatrices.push(
19290
- translate2(textCenterX, textCenterY),
19291
- rotate(rad),
19292
- translate2(-textCenterX, -textCenterY)
19293
- );
19294
- }
19295
- const finalTransformMatrix = transformMatrices.length > 0 ? compose(...transformMatrices) : void 0;
19296
- processedTextOutlines.forEach((segment) => {
19297
- ctx.beginPath();
19298
- segment.forEach((p, index) => {
19299
- let transformedP = { x: p[0], y: p[1] };
19300
- if (finalTransformMatrix) {
19301
- transformedP = applyToPoint(finalTransformMatrix, transformedP);
19302
- }
19303
- const pcbX = transformedP.x + xOff + textS.anchor_position.x;
19304
- const pcbY = transformedP.y + yOff + textS.anchor_position.y;
19305
- const canvasX = (pcbX - boardData.center.x + boardData.width / 2) * traceTextureResolution;
19306
- const canvasY = (-(pcbY - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
19307
- if (index === 0) ctx.moveTo(canvasX, canvasY);
19308
- else ctx.lineTo(canvasX, canvasY);
19309
- });
19310
- ctx.stroke();
19311
- });
19312
- });
19313
- const texture = new THREE6.CanvasTexture(canvas);
19314
- texture.generateMipmaps = true;
19315
- texture.minFilter = THREE6.LinearMipmapLinearFilter;
19316
- texture.magFilter = THREE6.LinearFilter;
19317
- texture.anisotropy = 16;
19318
- texture.needsUpdate = true;
19319
- return texture;
19320
- }
19321
-
19322
- // src/utils/manifold/process-non-plated-holes.ts
19323
- import { su as su7 } from "@tscircuit/soup-util";
19324
-
19325
- // src/utils/hole-geoms.ts
19326
- function createCircleHoleDrill({
19327
- Manifold,
19328
- x,
19329
- y,
19330
- diameter,
19331
- thickness,
19332
- segments = 32
19333
- }) {
19334
- const drill = Manifold.cylinder(
19335
- thickness * 1.2,
19336
- diameter / 2,
19337
- diameter / 2,
19338
- segments,
19339
- true
19340
- );
19341
- return drill.translate([x, y, 0]);
19342
- }
19343
- function createPlatedHoleDrill({
19344
- Manifold,
19345
- x,
19346
- y,
19347
- outerDiameter,
19348
- thickness,
19349
- zOffset = 1e-3,
19350
- segments = 32
19351
- }) {
19352
- const boardHoleRadius = outerDiameter / 2 + zOffset;
19353
- const drill = Manifold.cylinder(
19354
- thickness * 1.2,
19355
- boardHoleRadius,
19356
- boardHoleRadius,
19357
- segments,
19358
- true
19359
- );
19360
- return drill.translate([x, y, 0]);
19361
- }
19362
-
19363
- // src/utils/manifold/process-non-plated-holes.ts
19364
- function isCircleHole(hole) {
19365
- return (hole.shape === "circle" || hole.hole_shape === "circle") && typeof hole.hole_diameter === "number";
19366
- }
19367
- function processNonPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
19368
- const nonPlatedHoleBoardDrills = [];
19369
- const pcbHoles = su7(circuitJson).pcb_hole.list();
19370
- pcbHoles.forEach((hole) => {
19371
- if (isCircleHole(hole)) {
19372
- const translatedDrill = createCircleHoleDrill({
19373
- Manifold,
19374
- x: hole.x,
19375
- y: hole.y,
19376
- diameter: hole.hole_diameter,
19377
- thickness: pcbThickness,
19378
- segments: SMOOTH_CIRCLE_SEGMENTS
19379
- });
19380
- manifoldInstancesForCleanup.push(translatedDrill);
19381
- nonPlatedHoleBoardDrills.push(translatedDrill);
19382
- }
19383
- });
19384
- return { nonPlatedHoleBoardDrills };
19385
- }
19386
-
19387
- // src/utils/manifold/process-plated-holes.ts
19388
- import { su as su8 } from "@tscircuit/soup-util";
19389
- import * as THREE7 from "three";
19390
- var COPPER_COLOR = new THREE7.Color(...colors.copper);
19391
- function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
19392
- const platedHoleBoardDrills = [];
19393
- const pcbPlatedHoles = su8(circuitJson).pcb_plated_hole.list();
19394
- const platedHoleCopperGeoms = [];
19395
- pcbPlatedHoles.forEach((ph, index) => {
19396
- if (ph.shape === "circle") {
19397
- const translatedDrill = createPlatedHoleDrill({
19398
- Manifold,
19399
- x: ph.x,
19400
- y: ph.y,
19401
- outerDiameter: ph.outer_diameter,
19402
- // Drill for the board
19403
- thickness: pcbThickness,
19404
- zOffset: MANIFOLD_Z_OFFSET,
19405
- segments: SMOOTH_CIRCLE_SEGMENTS
19406
- });
19407
- manifoldInstancesForCleanup.push(translatedDrill);
19408
- platedHoleBoardDrills.push(translatedDrill);
19409
- const copperPartThickness = pcbThickness + 2 * MANIFOLD_Z_OFFSET;
19410
- let platedPart = Manifold.cylinder(
19411
- copperPartThickness,
19412
- ph.outer_diameter / 2,
19413
- ph.outer_diameter / 2,
19414
- SMOOTH_CIRCLE_SEGMENTS,
19415
- true
19416
- );
19417
- manifoldInstancesForCleanup.push(platedPart);
19418
- const drillForCopper = Manifold.cylinder(
19419
- copperPartThickness * 1.05,
19420
- // ensure it cuts through
19421
- ph.hole_diameter / 2,
19422
- ph.hole_diameter / 2,
19423
- SMOOTH_CIRCLE_SEGMENTS,
19424
- true
19425
- );
19426
- manifoldInstancesForCleanup.push(drillForCopper);
19427
- const finalPlatedPartOp = platedPart.subtract(drillForCopper);
19428
- manifoldInstancesForCleanup.push(finalPlatedPartOp);
19429
- const translatedPlatedPart = finalPlatedPartOp.translate([ph.x, ph.y, 0]);
19430
- manifoldInstancesForCleanup.push(translatedPlatedPart);
19431
- const threeGeom = manifoldMeshToThreeGeometry(
19432
- translatedPlatedPart.getMesh()
19433
- );
19434
- platedHoleCopperGeoms.push({
19435
- key: `ph-${ph.pcb_plated_hole_id || index}`,
19436
- geometry: threeGeom,
19437
- color: COPPER_COLOR
19438
- });
19439
- } else if (ph.shape === "pill") {
19440
- const holeWidthRaw = ph.hole_width;
19441
- const holeHeightRaw = ph.hole_height;
19442
- const shouldRotate = holeHeightRaw > holeWidthRaw;
19443
- const holeW = shouldRotate ? holeHeightRaw : holeWidthRaw;
19444
- const holeH = shouldRotate ? holeWidthRaw : holeHeightRaw;
19445
- const defaultPadExtension = 0.4;
19446
- const outerW = shouldRotate ? ph.outer_height ?? holeH + defaultPadExtension / 2 : ph.outer_width ?? holeW + defaultPadExtension / 2;
19447
- const outerH = shouldRotate ? ph.outer_width ?? holeW + defaultPadExtension / 2 : ph.outer_height ?? holeH + defaultPadExtension / 2;
19448
- const createPill = (width, height, depth) => {
19449
- const radius = height / 2;
19450
- const rectLength = width - height;
19451
- let pillOp;
19452
- if (rectLength < 1e-9) {
19453
- pillOp = Manifold.cylinder(
19454
- depth,
19455
- radius,
19456
- radius,
19457
- SMOOTH_CIRCLE_SEGMENTS,
19458
- true
19459
- );
19460
- } else {
19461
- const rect = Manifold.cube(
19462
- [Math.max(0, rectLength), height, depth],
19463
- true
19464
- );
19465
- const cap1 = Manifold.cylinder(
19466
- depth,
19467
- radius,
19468
- radius,
19469
- SMOOTH_CIRCLE_SEGMENTS,
19470
- true
19471
- ).translate([-rectLength / 2, 0, 0]);
19472
- const cap2 = Manifold.cylinder(
19473
- depth,
19474
- radius,
19475
- radius,
19476
- SMOOTH_CIRCLE_SEGMENTS,
19477
- true
19478
- ).translate([rectLength / 2, 0, 0]);
19479
- pillOp = Manifold.union([rect, cap1, cap2]);
19480
- manifoldInstancesForCleanup.push(rect, cap1, cap2);
19481
- }
19482
- manifoldInstancesForCleanup.push(pillOp);
19483
- return pillOp;
19484
- };
19485
- const drillW = holeW + 2 * MANIFOLD_Z_OFFSET;
19486
- const drillH = holeH + 2 * MANIFOLD_Z_OFFSET;
19487
- const drillDepth = pcbThickness * 1.2;
19488
- let boardPillDrillOp = createPill(drillW, drillH, drillDepth);
19489
- if (shouldRotate) {
19490
- const rotatedOp = boardPillDrillOp.rotate([0, 0, 90]);
19491
- manifoldInstancesForCleanup.push(rotatedOp);
19492
- boardPillDrillOp = rotatedOp;
19493
- }
19494
- const translatedBoardPillDrill = boardPillDrillOp.translate([
19495
- ph.x,
19496
- ph.y,
19497
- 0
19498
- ]);
19499
- manifoldInstancesForCleanup.push(translatedBoardPillDrill);
19500
- platedHoleBoardDrills.push(translatedBoardPillDrill);
19501
- const copperPartThickness = pcbThickness + 2 * MANIFOLD_Z_OFFSET;
19502
- const outerCopperOpUnrotated = createPill(
19503
- outerW,
19504
- outerH,
19505
- copperPartThickness
19506
- );
19507
- const innerDrillOpUnrotated = createPill(
19508
- holeW,
19509
- holeH,
19510
- copperPartThickness * 1.05
19511
- // Make drill slightly thicker to ensure cut
19512
- );
19513
- let finalPlatedPartOp = outerCopperOpUnrotated.subtract(
19514
- innerDrillOpUnrotated
19515
- );
19516
- manifoldInstancesForCleanup.push(finalPlatedPartOp);
19517
- if (shouldRotate) {
19518
- const rotatedOp = finalPlatedPartOp.rotate([0, 0, 90]);
19519
- manifoldInstancesForCleanup.push(rotatedOp);
19520
- finalPlatedPartOp = rotatedOp;
19521
- }
19522
- const translatedPlatedPart = finalPlatedPartOp.translate([ph.x, ph.y, 0]);
19523
- manifoldInstancesForCleanup.push(translatedPlatedPart);
19524
- const threeGeom = manifoldMeshToThreeGeometry(
19525
- translatedPlatedPart.getMesh()
19526
- );
19527
- platedHoleCopperGeoms.push({
19528
- key: `ph-${ph.pcb_plated_hole_id || index}`,
19529
- geometry: threeGeom,
19530
- color: COPPER_COLOR
19531
- });
19532
- }
19533
- });
19534
- return { platedHoleBoardDrills, platedHoleCopperGeoms };
19535
- }
19536
-
19537
- // src/utils/manifold/process-vias.ts
19538
- import { su as su9 } from "@tscircuit/soup-util";
19539
- import * as THREE8 from "three";
19540
-
19541
- // src/utils/via-geoms.ts
19542
- function createViaCopper({
19543
- Manifold,
19544
- x,
19545
- y,
19546
- outerDiameter,
19547
- holeDiameter,
19548
- thickness,
19549
- zOffset = 1e-3,
19550
- segments = 32
19551
- }) {
19552
- const copperPartThickness = thickness + 2 * zOffset;
19553
- let viaCopper = Manifold.cylinder(
19554
- copperPartThickness,
19555
- outerDiameter / 2,
19556
- -1,
19557
- segments,
19558
- true
19559
- );
19560
- const drill = Manifold.cylinder(
19561
- copperPartThickness * 1.05,
19562
- holeDiameter / 2,
19563
- -1,
19564
- segments,
19565
- true
19566
- );
19567
- const finalViaCopperOp = viaCopper.subtract(drill);
19568
- return finalViaCopperOp.translate([x, y, 0]);
19569
- }
19570
-
19571
- // src/utils/manifold/process-vias.ts
19572
- var COPPER_COLOR2 = new THREE8.Color(...colors.copper);
19573
- function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
19574
- const viaBoardDrills = [];
19575
- const pcbVias = su9(circuitJson).pcb_via.list();
19576
- const viaCopperGeoms = [];
19577
- pcbVias.forEach((via, index) => {
19578
- if (typeof via.outer_diameter === "number") {
19579
- const translatedDrill = createPlatedHoleDrill({
19580
- Manifold,
19581
- x: via.x,
19582
- y: via.y,
19583
- outerDiameter: via.outer_diameter,
19584
- thickness: pcbThickness,
19585
- zOffset: MANIFOLD_Z_OFFSET,
19586
- segments: SMOOTH_CIRCLE_SEGMENTS
19587
- });
19588
- manifoldInstancesForCleanup.push(translatedDrill);
19589
- viaBoardDrills.push(translatedDrill);
19590
- }
19591
- if (typeof via.outer_diameter === "number" && typeof via.hole_diameter === "number") {
19592
- const translatedViaCopper = createViaCopper({
19593
- Manifold,
19594
- x: via.x,
19595
- y: via.y,
19596
- outerDiameter: via.outer_diameter,
19597
- holeDiameter: via.hole_diameter,
19598
- thickness: pcbThickness,
19599
- zOffset: MANIFOLD_Z_OFFSET,
19600
- segments: SMOOTH_CIRCLE_SEGMENTS
19601
- });
19602
- manifoldInstancesForCleanup.push(translatedViaCopper);
19603
- const threeGeom = manifoldMeshToThreeGeometry(
19604
- translatedViaCopper.getMesh()
19605
- );
19606
- viaCopperGeoms.push({
19607
- key: `via-${via.pcb_via_id || index}`,
19608
- geometry: threeGeom,
19609
- color: COPPER_COLOR2
19610
- });
19611
- }
19612
- });
19613
- return { viaBoardDrills, viaCopperGeoms };
19614
- }
19615
-
19616
- // src/utils/manifold/process-smt-pads.ts
19617
- import { su as su10 } from "@tscircuit/soup-util";
19618
- import * as THREE9 from "three";
19619
-
19620
- // src/utils/pad-geoms.ts
19621
- function createPadManifoldOp({
19622
- Manifold,
19623
- pad,
19624
- padBaseThickness
19625
- }) {
19626
- if (pad.shape === "rect") {
19627
- return Manifold.cube([pad.width, pad.height, padBaseThickness], true);
19628
- } else if (pad.shape === "circle" && pad.radius) {
19629
- return Manifold.cylinder(padBaseThickness, pad.radius, -1, 32, true);
19630
- }
19631
- return null;
19632
- }
19633
-
19634
- // src/utils/manifold/process-smt-pads.ts
19635
- var COPPER_COLOR3 = new THREE9.Color(...colors.copper);
19636
- function processSmtPadsForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
19637
- const smtPadGeoms = [];
19638
- const smtPads = su10(circuitJson).pcb_smtpad.list();
19639
- smtPads.forEach((pad, index) => {
19640
- const padBaseThickness = DEFAULT_SMT_PAD_THICKNESS;
19641
- const zPos = pad.layer === "bottom" ? -pcbThickness / 2 - padBaseThickness / 2 - MANIFOLD_Z_OFFSET : pcbThickness / 2 + padBaseThickness / 2 + MANIFOLD_Z_OFFSET;
19642
- let padManifoldOp = createPadManifoldOp({
19643
- Manifold,
19644
- pad,
19645
- padBaseThickness
19646
- });
19647
- if (padManifoldOp) {
19648
- manifoldInstancesForCleanup.push(padManifoldOp);
19649
- const translatedPad = padManifoldOp.translate([pad.x, pad.y, zPos]);
19650
- manifoldInstancesForCleanup.push(translatedPad);
19651
- const threeGeom = manifoldMeshToThreeGeometry(translatedPad.getMesh());
19652
- smtPadGeoms.push({
19653
- key: `pad-${pad.pcb_smtpad_id || index}`,
19654
- geometry: threeGeom,
19655
- color: COPPER_COLOR3
19656
- });
19657
- }
19658
- });
19659
- return { smtPadGeoms };
19660
- }
19661
-
19662
- // src/utils/manifold/create-manifold-board.ts
19663
- var arePointsClockwise2 = (points) => {
19664
- let area = 0;
19665
- for (let i = 0; i < points.length; i++) {
19666
- const j = (i + 1) % points.length;
19667
- if (points[i] && points[j]) {
19668
- area += points[i][0] * points[j][1];
19669
- area -= points[j][0] * points[i][1];
19670
- }
19671
- }
19672
- const signedArea = area / 2;
19673
- return signedArea <= 0;
19674
- };
19675
- function createManifoldBoard(Manifold, CrossSection, boardData, pcbThickness, manifoldInstancesForCleanup) {
19676
- let boardOp;
19677
- if (boardData.outline && boardData.outline.length >= 3) {
19678
- let outlineVec2 = boardData.outline.map((p) => [
19679
- p.x,
19680
- p.y
19681
- ]);
19682
- if (arePointsClockwise2(outlineVec2)) {
19683
- outlineVec2 = outlineVec2.reverse();
19684
- }
19685
- const crossSection = CrossSection.ofPolygons([outlineVec2]);
19686
- manifoldInstancesForCleanup.push(crossSection);
19687
- boardOp = Manifold.extrude(
19688
- crossSection,
19689
- pcbThickness,
19690
- void 0,
19691
- // nDivisions
19692
- void 0,
19693
- // twistDegrees
19694
- void 0,
19695
- // scaleTop
19696
- true
19697
- // center (for Z-axis)
19698
- );
19699
- } else {
19700
- if (boardData.outline && boardData.outline.length > 0) {
19701
- console.warn(
19702
- "Board outline has fewer than 3 points, falling back to rectangular board."
19703
- );
19704
- }
19705
- boardOp = Manifold.cube(
19706
- [boardData.width, boardData.height, pcbThickness],
19707
- true
19708
- // center (for all axes)
19709
- );
19710
- }
19711
- manifoldInstancesForCleanup.push(boardOp);
19712
- boardOp = boardOp.translate([boardData.center.x, boardData.center.y, 0]);
19713
- manifoldInstancesForCleanup.push(boardOp);
19714
- return boardOp;
19715
- }
19716
-
19717
- // src/utils/manifold/process-cutouts.ts
19718
- import { su as su11 } from "@tscircuit/soup-util";
19719
- var arePointsClockwise3 = (points) => {
19720
- let area = 0;
19721
- for (let i = 0; i < points.length; i++) {
19722
- const j = (i + 1) % points.length;
19723
- if (points[i] && points[j]) {
19724
- area += points[i][0] * points[j][1];
19725
- area -= points[j][0] * points[i][1];
19726
- }
19727
- }
19728
- const signedArea = area / 2;
19729
- return signedArea <= 0;
19730
- };
19731
- function processCutoutsForManifold(Manifold, CrossSection, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
19732
- const cutoutOps = [];
19733
- const pcbCutouts = su11(circuitJson).pcb_cutout.list();
19734
- for (const cutout of pcbCutouts) {
19735
- let cutoutOp;
19736
- const cutoutHeight = pcbThickness * 1.5;
19737
- switch (cutout.shape) {
19738
- case "rect":
19739
- cutoutOp = Manifold.cube(
19740
- [cutout.width, cutout.height, cutoutHeight],
19741
- true
19742
- // centered
19743
- );
19744
- manifoldInstancesForCleanup.push(cutoutOp);
19745
- if (cutout.rotation) {
19746
- const rotationRadians = cutout.rotation * Math.PI / 180;
19747
- const rotatedOp = cutoutOp.rotate([0, 0, cutout.rotation]);
19748
- manifoldInstancesForCleanup.push(rotatedOp);
19749
- cutoutOp = rotatedOp;
19750
- }
19751
- cutoutOp = cutoutOp.translate([
19752
- cutout.center.x,
19753
- cutout.center.y,
19754
- 0
19755
- // Centered vertically by Manifold.cube, so Z is 0 for board plane
19756
- ]);
19757
- manifoldInstancesForCleanup.push(cutoutOp);
19758
- break;
19759
- case "circle":
19760
- cutoutOp = Manifold.cylinder(
19761
- cutoutHeight,
19762
- cutout.radius,
19763
- -1,
19764
- // default for radiusHigh
19765
- SMOOTH_CIRCLE_SEGMENTS,
19766
- true
19767
- // centered
19768
- );
19769
- manifoldInstancesForCleanup.push(cutoutOp);
19770
- cutoutOp = cutoutOp.translate([cutout.center.x, cutout.center.y, 0]);
19771
- manifoldInstancesForCleanup.push(cutoutOp);
19772
- break;
19773
- case "polygon":
19774
- if (cutout.points.length < 3) {
19775
- console.warn(
19776
- `PCB Cutout [${cutout.pcb_cutout_id}] polygon has fewer than 3 points, skipping.`
19777
- );
19778
- continue;
19779
- }
19780
- let pointsVec2 = cutout.points.map((p) => [
19781
- p.x,
19782
- p.y
19783
- ]);
19784
- if (arePointsClockwise3(pointsVec2)) {
19785
- pointsVec2 = pointsVec2.reverse();
19786
- }
19787
- const crossSection = CrossSection.ofPolygons([pointsVec2]);
19788
- manifoldInstancesForCleanup.push(crossSection);
19789
- cutoutOp = Manifold.extrude(
19790
- crossSection,
19791
- cutoutHeight,
19792
- 0,
19793
- // nDivisions
19794
- 0,
19795
- // twistDegrees
19796
- [1, 1],
19797
- // scaleTop
19798
- true
19799
- // center extrusion
19800
- );
19801
- manifoldInstancesForCleanup.push(cutoutOp);
19802
- break;
19803
- default:
19804
- console.warn(
19805
- `Unsupported cutout shape: ${cutout.shape} for cutout ${cutout.pcb_cutout_id}`
19806
- );
19807
- continue;
19808
- }
19809
- if (cutoutOp) {
19810
- cutoutOps.push(cutoutOp);
19811
- }
19812
- }
19813
- return { cutoutOps };
19814
- }
19815
-
19816
19084
  // src/hooks/useManifoldBoardBuilder.ts
19817
- var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
19085
+ var MANIFOLD_CDN_BASE_URL = "https://cdn.jsdelivr.net/npm/manifold-3d@3.1.1";
19086
+ var useManifoldBoardBuilder = (circuitJson) => {
19818
19087
  const [geoms, setGeoms] = useState8(null);
19819
19088
  const [textures, setTextures] = useState8(null);
19820
19089
  const [pcbThickness, setPcbThickness] = useState8(null);
19821
19090
  const [error, setError] = useState8(null);
19822
19091
  const [isLoading, setIsLoading] = useState8(true);
19823
- const manifoldInstancesForCleanup = useRef6([]);
19824
- const boardData = useMemo7(() => {
19825
- if (!circuitJson) return null;
19826
- const boards = su12(circuitJson).pcb_board.list();
19827
- if (boards.length === 0) {
19828
- return null;
19829
- }
19830
- return boards[0];
19831
- }, [circuitJson]);
19092
+ const [boardData, setBoardData] = useState8(null);
19093
+ const workerRef = useRef6(null);
19832
19094
  useEffect5(() => {
19833
- if (!manifoldJSModule || !circuitJson || !boardData) {
19095
+ if (!circuitJson) {
19834
19096
  setGeoms(null);
19835
19097
  setTextures(null);
19836
19098
  setPcbThickness(null);
19837
- if (circuitJson && su12(circuitJson).pcb_board.list().length === 0) {
19838
- setError("No pcb_board found in circuitJson.");
19839
- }
19099
+ setError(null);
19100
+ setIsLoading(false);
19101
+ setBoardData(null);
19102
+ return;
19103
+ }
19104
+ const boards = su5(circuitJson).pcb_board.list();
19105
+ if (boards.length === 0) {
19106
+ setError("No pcb_board found in circuitJson.");
19840
19107
  setIsLoading(false);
19841
19108
  return;
19842
19109
  }
19843
19110
  setIsLoading(true);
19844
19111
  setError(null);
19845
- const Manifold = manifoldJSModule.Manifold;
19846
- const CrossSection = manifoldJSModule.CrossSection;
19847
- manifoldInstancesForCleanup.current.forEach((inst) => inst.delete());
19848
- manifoldInstancesForCleanup.current = [];
19849
- let boardManifold = null;
19850
- const currentGeoms = {};
19851
- const currentTextures = {};
19852
- try {
19853
- const currentPcbThickness = boardData.thickness || 1.6;
19854
- setPcbThickness(currentPcbThickness);
19855
- let currentBoardOp = createManifoldBoard(
19856
- Manifold,
19857
- CrossSection,
19858
- boardData,
19859
- currentPcbThickness,
19860
- manifoldInstancesForCleanup.current
19861
- );
19862
- const allBoardDrills = [];
19863
- const { nonPlatedHoleBoardDrills } = processNonPlatedHolesForManifold(
19864
- Manifold,
19865
- circuitJson,
19866
- currentPcbThickness,
19867
- manifoldInstancesForCleanup.current
19868
- );
19869
- allBoardDrills.push(...nonPlatedHoleBoardDrills);
19870
- const { platedHoleBoardDrills, platedHoleCopperGeoms } = processPlatedHolesForManifold(
19871
- Manifold,
19872
- circuitJson,
19873
- currentPcbThickness,
19874
- manifoldInstancesForCleanup.current
19875
- );
19876
- allBoardDrills.push(...platedHoleBoardDrills);
19877
- currentGeoms.platedHoles = platedHoleCopperGeoms;
19878
- const { viaBoardDrills, viaCopperGeoms } = processViasForManifold(
19879
- Manifold,
19880
- circuitJson,
19881
- currentPcbThickness,
19882
- manifoldInstancesForCleanup.current
19883
- );
19884
- allBoardDrills.push(...viaBoardDrills);
19885
- currentGeoms.vias = viaCopperGeoms;
19886
- if (allBoardDrills.length > 0) {
19887
- const unionedDrills = Manifold.union(allBoardDrills);
19888
- manifoldInstancesForCleanup.current.push(unionedDrills);
19889
- const nextBoardAfterDrills = currentBoardOp.subtract(unionedDrills);
19890
- manifoldInstancesForCleanup.current.push(nextBoardAfterDrills);
19891
- currentBoardOp = nextBoardAfterDrills;
19892
- }
19893
- const { cutoutOps } = processCutoutsForManifold(
19894
- Manifold,
19895
- CrossSection,
19896
- circuitJson,
19897
- currentPcbThickness,
19898
- manifoldInstancesForCleanup.current
19899
- );
19900
- if (cutoutOps.length > 0) {
19901
- const unionedCutouts = Manifold.union(cutoutOps);
19902
- manifoldInstancesForCleanup.current.push(unionedCutouts);
19903
- const nextBoardAfterCutouts = currentBoardOp.subtract(unionedCutouts);
19904
- manifoldInstancesForCleanup.current.push(nextBoardAfterCutouts);
19905
- currentBoardOp = nextBoardAfterCutouts;
19906
- }
19907
- boardManifold = currentBoardOp;
19908
- if (boardManifold) {
19909
- const boardThreeMesh = boardManifold.getMesh();
19910
- const finalBoardGeom = manifoldMeshToThreeGeometry(boardThreeMesh);
19911
- const matColorArray = boardMaterialColors[boardData.material] ?? colors.fr4Green;
19912
- currentGeoms.board = {
19913
- geometry: finalBoardGeom,
19914
- color: new THREE10.Color(
19915
- matColorArray[0],
19916
- matColorArray[1],
19917
- matColorArray[2]
19918
- )
19919
- };
19112
+ setGeoms(null);
19113
+ setTextures(null);
19114
+ if (workerRef.current) {
19115
+ workerRef.current.terminate();
19116
+ }
19117
+ const worker = new Worker(
19118
+ new URL("../workers/manifold-builder.worker.ts", import.meta.url),
19119
+ { type: "module" }
19120
+ );
19121
+ workerRef.current = worker;
19122
+ worker.postMessage({
19123
+ circuitJson,
19124
+ manifoldPath: `${MANIFOLD_CDN_BASE_URL}/manifold.wasm`
19125
+ });
19126
+ worker.onmessage = (event) => {
19127
+ const { type, payload, error: workerError, message } = event.data;
19128
+ switch (type) {
19129
+ case "pcb_thickness":
19130
+ setPcbThickness(payload);
19131
+ break;
19132
+ case "board_data":
19133
+ setBoardData(payload);
19134
+ break;
19135
+ case "geom_update":
19136
+ setGeoms((prevGeoms) => {
19137
+ const newGeoms = { ...prevGeoms };
19138
+ if (payload.board) {
19139
+ newGeoms.board = {
19140
+ geometry: manifoldMeshToThreeGeometry(payload.board.mesh),
19141
+ color: new THREE5.Color(...payload.board.color)
19142
+ };
19143
+ }
19144
+ const processGeomArray = (key) => {
19145
+ if (payload[key]) {
19146
+ const geomArray = payload[key].map((item) => ({
19147
+ key: item.key,
19148
+ geometry: manifoldMeshToThreeGeometry(item.mesh),
19149
+ color: new THREE5.Color(...item.color)
19150
+ }));
19151
+ newGeoms[key] = [...newGeoms[key] ?? [], ...geomArray];
19152
+ }
19153
+ };
19154
+ processGeomArray("platedHoles");
19155
+ processGeomArray("smtPads");
19156
+ processGeomArray("vias");
19157
+ return newGeoms;
19158
+ });
19159
+ break;
19160
+ case "texture_update":
19161
+ setTextures((prevTextures) => {
19162
+ const newTextures = { ...prevTextures };
19163
+ for (const key in payload) {
19164
+ const imageBitmap = payload[key];
19165
+ const texture = new THREE5.CanvasTexture(imageBitmap);
19166
+ texture.generateMipmaps = true;
19167
+ texture.minFilter = THREE5.LinearMipmapLinearFilter;
19168
+ texture.magFilter = THREE5.LinearFilter;
19169
+ texture.anisotropy = 16;
19170
+ texture.needsUpdate = true;
19171
+ newTextures[key] = texture;
19172
+ }
19173
+ return newTextures;
19174
+ });
19175
+ break;
19176
+ case "done":
19177
+ setIsLoading(false);
19178
+ break;
19179
+ case "error":
19180
+ setError(workerError);
19181
+ setIsLoading(false);
19182
+ break;
19183
+ case "log":
19184
+ console.time(message);
19185
+ break;
19186
+ case "log_end":
19187
+ console.timeEnd(message);
19188
+ break;
19920
19189
  }
19921
- const { smtPadGeoms } = processSmtPadsForManifold(
19922
- Manifold,
19923
- circuitJson,
19924
- currentPcbThickness,
19925
- manifoldInstancesForCleanup.current
19926
- );
19927
- currentGeoms.smtPads = smtPadGeoms;
19928
- setGeoms(currentGeoms);
19929
- const traceColorArr = tracesMaterialColors[boardData.material] ?? colors.fr4GreenSolderWithMask;
19930
- const traceColor = `rgb(${Math.round(traceColorArr[0] * 255)}, ${Math.round(traceColorArr[1] * 255)}, ${Math.round(traceColorArr[2] * 255)})`;
19931
- currentTextures.topTrace = createTraceTextureForLayer({
19932
- layer: "top",
19933
- circuitJson,
19934
- boardData,
19935
- traceColor,
19936
- traceTextureResolution: TRACE_TEXTURE_RESOLUTION
19937
- });
19938
- currentTextures.bottomTrace = createTraceTextureForLayer({
19939
- layer: "bottom",
19940
- circuitJson,
19941
- boardData,
19942
- traceColor,
19943
- traceTextureResolution: TRACE_TEXTURE_RESOLUTION
19944
- });
19945
- const silkscreenColor = "rgb(255,255,255)";
19946
- currentTextures.topSilkscreen = createSilkscreenTextureForLayer({
19947
- layer: "top",
19948
- circuitJson,
19949
- boardData,
19950
- silkscreenColor,
19951
- traceTextureResolution: TRACE_TEXTURE_RESOLUTION
19952
- });
19953
- currentTextures.bottomSilkscreen = createSilkscreenTextureForLayer({
19954
- layer: "bottom",
19955
- circuitJson,
19956
- boardData,
19957
- silkscreenColor,
19958
- traceTextureResolution: TRACE_TEXTURE_RESOLUTION
19959
- });
19960
- setTextures(currentTextures);
19961
- } catch (e) {
19962
- console.error("Error processing geometry with Manifold in hook:", e);
19963
- setError(
19964
- e.message || "An unknown error occurred while processing geometry in hook."
19965
- );
19966
- setGeoms(null);
19967
- setTextures(null);
19968
- } finally {
19190
+ };
19191
+ worker.onerror = (err) => {
19192
+ setError(`Worker error: ${err.message}`);
19969
19193
  setIsLoading(false);
19970
- }
19194
+ };
19971
19195
  return () => {
19972
- manifoldInstancesForCleanup.current.forEach((inst) => inst.delete());
19973
- manifoldInstancesForCleanup.current = [];
19196
+ if (workerRef.current) {
19197
+ workerRef.current.terminate();
19198
+ workerRef.current = null;
19199
+ }
19974
19200
  };
19975
- }, [manifoldJSModule, circuitJson, boardData]);
19201
+ }, [circuitJson]);
19202
+ const derivedBoardData = useMemo7(() => {
19203
+ if (!circuitJson) return null;
19204
+ const boards = su5(circuitJson).pcb_board.list();
19205
+ return boards[0] ?? null;
19206
+ }, [circuitJson]);
19976
19207
  return {
19977
19208
  geoms,
19978
19209
  textures,
19979
19210
  pcbThickness,
19980
19211
  error,
19981
19212
  isLoading,
19982
- boardData
19213
+ boardData: boardData ?? derivedBoardData
19983
19214
  };
19984
19215
  };
19985
19216
 
19986
19217
  // src/utils/manifold/create-three-geometry-meshes.ts
19987
- import * as THREE11 from "three";
19218
+ import * as THREE6 from "three";
19988
19219
  function createGeometryMeshes(geoms) {
19989
19220
  const meshes = [];
19990
19221
  if (!geoms) return meshes;
19991
19222
  if (geoms.board && geoms.board.geometry) {
19992
- const mesh = new THREE11.Mesh(
19223
+ const mesh = new THREE6.Mesh(
19993
19224
  geoms.board.geometry,
19994
- new THREE11.MeshStandardMaterial({
19225
+ new THREE6.MeshStandardMaterial({
19995
19226
  color: geoms.board.color,
19996
- side: THREE11.DoubleSide,
19227
+ side: THREE6.DoubleSide,
19997
19228
  flatShading: true
19998
19229
  })
19999
19230
  );
@@ -20003,11 +19234,11 @@ function createGeometryMeshes(geoms) {
20003
19234
  const createMeshesFromArray = (geomArray) => {
20004
19235
  if (geomArray) {
20005
19236
  geomArray.forEach((comp) => {
20006
- const mesh = new THREE11.Mesh(
19237
+ const mesh = new THREE6.Mesh(
20007
19238
  comp.geometry,
20008
- new THREE11.MeshStandardMaterial({
19239
+ new THREE6.MeshStandardMaterial({
20009
19240
  color: comp.color,
20010
- side: THREE11.DoubleSide,
19241
+ side: THREE6.DoubleSide,
20011
19242
  flatShading: true
20012
19243
  // Consistent with board
20013
19244
  })
@@ -20024,21 +19255,21 @@ function createGeometryMeshes(geoms) {
20024
19255
  }
20025
19256
 
20026
19257
  // src/utils/manifold/create-three-texture-meshes.ts
20027
- import * as THREE12 from "three";
19258
+ import * as THREE7 from "three";
20028
19259
  function createTextureMeshes(textures, boardData, pcbThickness) {
20029
19260
  const meshes = [];
20030
19261
  if (!textures || !boardData || pcbThickness === null) return meshes;
20031
19262
  const createTexturePlane = (texture, yOffset, isBottomLayer, keySuffix) => {
20032
19263
  if (!texture) return null;
20033
- const planeGeom = new THREE12.PlaneGeometry(boardData.width, boardData.height);
20034
- const material = new THREE12.MeshBasicMaterial({
19264
+ const planeGeom = new THREE7.PlaneGeometry(boardData.width, boardData.height);
19265
+ const material = new THREE7.MeshBasicMaterial({
20035
19266
  map: texture,
20036
19267
  transparent: true,
20037
- side: THREE12.DoubleSide,
19268
+ side: THREE7.DoubleSide,
20038
19269
  depthWrite: false
20039
19270
  // Important for layers to avoid z-fighting issues with board itself
20040
19271
  });
20041
- const mesh = new THREE12.Mesh(planeGeom, material);
19272
+ const mesh = new THREE7.Mesh(planeGeom, material);
20042
19273
  mesh.position.set(boardData.center.x, boardData.center.y, yOffset);
20043
19274
  if (isBottomLayer) {
20044
19275
  mesh.rotation.set(Math.PI, 0, 0);
@@ -20081,40 +19312,11 @@ function createTextureMeshes(textures, boardData, pcbThickness) {
20081
19312
 
20082
19313
  // src/CadViewerManifold.tsx
20083
19314
  import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
20084
- var MANIFOLD_CDN_BASE_URL = "https://cdn.jsdelivr.net/npm/manifold-3d@3.1.1";
20085
19315
  var CadViewerManifold = ({
20086
19316
  circuitJson,
20087
19317
  autoRotateDisabled,
20088
19318
  clickToInteractEnabled
20089
19319
  }) => {
20090
- const [manifoldJSModule, setManifoldJSModule] = useState9(null);
20091
- const [manifoldLoadingError, setManifoldLoadingError] = useState9(null);
20092
- useEffect6(() => {
20093
- const loadManifoldWasmFromCDN = async () => {
20094
- try {
20095
- const wasmUrl = `${MANIFOLD_CDN_BASE_URL}/manifold.wasm`;
20096
- const manifoldConfig = {
20097
- locateFile: (path, scriptDirectory) => {
20098
- if (path === "manifold.wasm") {
20099
- return wasmUrl;
20100
- }
20101
- return scriptDirectory + path;
20102
- }
20103
- };
20104
- const loadedModule = await ManifoldModule(
20105
- manifoldConfig
20106
- );
20107
- loadedModule.setup();
20108
- setManifoldJSModule(loadedModule);
20109
- } catch (error) {
20110
- console.error("Failed to load Manifold from CDN:", error);
20111
- setManifoldLoadingError(
20112
- `Failed to load Manifold module: ${error instanceof Error ? error.message : "Unknown error"}`
20113
- );
20114
- }
20115
- };
20116
- loadManifoldWasmFromCDN();
20117
- }, []);
20118
19320
  const {
20119
19321
  geoms,
20120
19322
  textures,
@@ -20122,14 +19324,14 @@ var CadViewerManifold = ({
20122
19324
  error: builderError,
20123
19325
  isLoading: builderIsLoading,
20124
19326
  boardData
20125
- } = useManifoldBoardBuilder(manifoldJSModule, circuitJson);
19327
+ } = useManifoldBoardBuilder(circuitJson);
20126
19328
  const geometryMeshes = useMemo8(() => createGeometryMeshes(geoms), [geoms]);
20127
19329
  const textureMeshes = useMemo8(
20128
19330
  () => createTextureMeshes(textures, boardData, pcbThickness),
20129
19331
  [textures, boardData, pcbThickness]
20130
19332
  );
20131
19333
  const cadComponents = useMemo8(
20132
- () => circuitJson ? su13(circuitJson).cad_component.list() : [],
19334
+ () => circuitJson ? su6(circuitJson).cad_component.list() : [],
20133
19335
  [circuitJson]
20134
19336
  );
20135
19337
  const boardDimensions = useMemo8(() => {
@@ -20145,26 +19347,6 @@ var CadViewerManifold = ({
20145
19347
  const largestDim = Math.max(safeWidth, safeHeight, 5);
20146
19348
  return [largestDim * 0.75, largestDim * 0.75, largestDim * 0.75];
20147
19349
  }, [boardData]);
20148
- if (manifoldLoadingError) {
20149
- return /* @__PURE__ */ jsxs8(
20150
- "div",
20151
- {
20152
- style: {
20153
- color: "red",
20154
- padding: "1em",
20155
- border: "1px solid red",
20156
- margin: "1em"
20157
- },
20158
- children: [
20159
- "Error: ",
20160
- manifoldLoadingError
20161
- ]
20162
- }
20163
- );
20164
- }
20165
- if (!manifoldJSModule) {
20166
- return /* @__PURE__ */ jsx11("div", { style: { padding: "1em" }, children: "Loading Manifold module..." });
20167
- }
20168
19350
  if (builderError) {
20169
19351
  return /* @__PURE__ */ jsxs8(
20170
19352
  "div",
@@ -20182,7 +19364,7 @@ var CadViewerManifold = ({
20182
19364
  }
20183
19365
  );
20184
19366
  }
20185
- if (builderIsLoading || !boardData || !geoms || !textures) {
19367
+ if (builderIsLoading && !geoms) {
20186
19368
  return /* @__PURE__ */ jsx11("div", { style: { padding: "1em" }, children: "Processing board geometry..." });
20187
19369
  }
20188
19370
  return /* @__PURE__ */ jsxs8(
@@ -20216,10 +19398,10 @@ var CadViewerManifold = ({
20216
19398
  var CadViewerManifold_default = CadViewerManifold;
20217
19399
 
20218
19400
  // src/hooks/useContextMenu.ts
20219
- import { useState as useState10, useCallback as useCallback5, useRef as useRef7, useEffect as useEffect7 } from "react";
19401
+ import { useState as useState9, useCallback as useCallback5, useRef as useRef7, useEffect as useEffect6 } from "react";
20220
19402
  var useContextMenu = ({ containerRef }) => {
20221
- const [menuVisible, setMenuVisible] = useState10(false);
20222
- const [menuPos, setMenuPos] = useState10({
19403
+ const [menuVisible, setMenuVisible] = useState9(false);
19404
+ const [menuPos, setMenuPos] = useState9({
20223
19405
  x: 0,
20224
19406
  y: 0
20225
19407
  });
@@ -20288,7 +19470,7 @@ var useContextMenu = ({ containerRef }) => {
20288
19470
  setMenuVisible(false);
20289
19471
  }
20290
19472
  }, []);
20291
- useEffect7(() => {
19473
+ useEffect6(() => {
20292
19474
  if (menuVisible) {
20293
19475
  document.addEventListener("mousedown", handleClickAway);
20294
19476
  return () => document.removeEventListener("mousedown", handleClickAway);
@@ -20320,7 +19502,7 @@ var useContextMenu = ({ containerRef }) => {
20320
19502
  // src/CadViewer.tsx
20321
19503
  import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
20322
19504
  var CadViewer = (props) => {
20323
- const [engine, setEngine] = useState11("jscad");
19505
+ const [engine, setEngine] = useState10("jscad");
20324
19506
  const containerRef = useRef8(null);
20325
19507
  const {
20326
19508
  menuVisible,
@@ -20333,13 +19515,13 @@ var CadViewer = (props) => {
20333
19515
  setEngine(newEngine);
20334
19516
  setMenuVisible(false);
20335
19517
  };
20336
- useEffect8(() => {
19518
+ useEffect7(() => {
20337
19519
  const stored = window.localStorage.getItem("cadViewerEngine");
20338
19520
  if (stored === "jscad" || stored === "manifold") {
20339
19521
  setEngine(stored);
20340
19522
  }
20341
19523
  }, []);
20342
- useEffect8(() => {
19524
+ useEffect7(() => {
20343
19525
  window.localStorage.setItem("cadViewerEngine", engine);
20344
19526
  }, [engine]);
20345
19527
  const viewerKey = props.circuitJson ? JSON.stringify(props.circuitJson) : void 0;
@@ -20438,12 +19620,12 @@ var CadViewer = (props) => {
20438
19620
 
20439
19621
  // src/convert-circuit-json-to-3d-svg.ts
20440
19622
  var import_debug = __toESM(require_browser(), 1);
20441
- import { su as su14 } from "@tscircuit/soup-util";
20442
- import * as THREE16 from "three";
19623
+ import { su as su7 } from "@tscircuit/soup-util";
19624
+ import * as THREE11 from "three";
20443
19625
  import { SVGRenderer } from "three/examples/jsm/renderers/SVGRenderer.js";
20444
19626
 
20445
19627
  // src/utils/create-geometry-from-polygons.ts
20446
- import * as THREE13 from "three";
19628
+ import * as THREE8 from "three";
20447
19629
  import { BufferGeometry as BufferGeometry4, Float32BufferAttribute as Float32BufferAttribute4 } from "three";
20448
19630
  function createGeometryFromPolygons(polygons) {
20449
19631
  const geometry = new BufferGeometry4();
@@ -20457,12 +19639,12 @@ function createGeometryFromPolygons(polygons) {
20457
19639
  ...polygon2.vertices[i + 1]
20458
19640
  // Third vertex
20459
19641
  );
20460
- const v1 = new THREE13.Vector3(...polygon2.vertices[0]);
20461
- const v2 = new THREE13.Vector3(...polygon2.vertices[i]);
20462
- const v3 = new THREE13.Vector3(...polygon2.vertices[i + 1]);
20463
- const normal = new THREE13.Vector3().crossVectors(
20464
- new THREE13.Vector3().subVectors(v2, v1),
20465
- new THREE13.Vector3().subVectors(v3, v1)
19642
+ const v1 = new THREE8.Vector3(...polygon2.vertices[0]);
19643
+ const v2 = new THREE8.Vector3(...polygon2.vertices[i]);
19644
+ const v3 = new THREE8.Vector3(...polygon2.vertices[i + 1]);
19645
+ const normal = new THREE8.Vector3().crossVectors(
19646
+ new THREE8.Vector3().subVectors(v2, v1),
19647
+ new THREE8.Vector3().subVectors(v3, v1)
20466
19648
  ).normalize();
20467
19649
  normals.push(
20468
19650
  normal.x,
@@ -20488,22 +19670,22 @@ import { Footprinter3d as Footprinter3d2 } from "jscad-electronics";
20488
19670
  import { convertCSGToThreeGeom as convertCSGToThreeGeom2 } from "jscad-fiber/three";
20489
19671
  import { createJSCADRenderer as createJSCADRenderer2 } from "jscad-fiber";
20490
19672
  import { executeJscadOperations as executeJscadOperations2, jscadPlanner as jscadPlanner2 } from "jscad-planner";
20491
- import * as THREE15 from "three";
19673
+ import * as THREE10 from "three";
20492
19674
 
20493
19675
  // src/utils/load-model.ts
20494
- import * as THREE14 from "three";
19676
+ import * as THREE9 from "three";
20495
19677
  import { OBJLoader as OBJLoader3 } from "three/examples/jsm/loaders/OBJLoader.js";
20496
19678
  import { STLLoader as STLLoader2 } from "three/examples/jsm/loaders/STLLoader.js";
20497
19679
  async function load3DModel(url) {
20498
19680
  if (url.endsWith(".stl")) {
20499
19681
  const loader = new STLLoader2();
20500
19682
  const geometry = await loader.loadAsync(url);
20501
- const material = new THREE14.MeshStandardMaterial({
19683
+ const material = new THREE9.MeshStandardMaterial({
20502
19684
  color: 8947848,
20503
19685
  metalness: 0.5,
20504
19686
  roughness: 0.5
20505
19687
  });
20506
- return new THREE14.Mesh(geometry, material);
19688
+ return new THREE9.Mesh(geometry, material);
20507
19689
  }
20508
19690
  if (url.endsWith(".obj")) {
20509
19691
  const loader = new OBJLoader3();
@@ -20530,9 +19712,9 @@ async function renderComponent(component, scene) {
20530
19712
  }
20531
19713
  if (component.rotation) {
20532
19714
  model.rotation.set(
20533
- THREE15.MathUtils.degToRad(component.rotation.x ?? 0),
20534
- THREE15.MathUtils.degToRad(component.rotation.y ?? 0),
20535
- THREE15.MathUtils.degToRad(component.rotation.z ?? 0)
19715
+ THREE10.MathUtils.degToRad(component.rotation.x ?? 0),
19716
+ THREE10.MathUtils.degToRad(component.rotation.y ?? 0),
19717
+ THREE10.MathUtils.degToRad(component.rotation.z ?? 0)
20536
19718
  );
20537
19719
  }
20538
19720
  scene.add(model);
@@ -20545,13 +19727,13 @@ async function renderComponent(component, scene) {
20545
19727
  component.model_jscad
20546
19728
  );
20547
19729
  const threeGeom = convertCSGToThreeGeom2(jscadObject);
20548
- const material2 = new THREE15.MeshStandardMaterial({
19730
+ const material2 = new THREE10.MeshStandardMaterial({
20549
19731
  color: 8947848,
20550
19732
  metalness: 0.5,
20551
19733
  roughness: 0.5,
20552
- side: THREE15.DoubleSide
19734
+ side: THREE10.DoubleSide
20553
19735
  });
20554
- const mesh2 = new THREE15.Mesh(threeGeom, material2);
19736
+ const mesh2 = new THREE10.Mesh(threeGeom, material2);
20555
19737
  if (component.position) {
20556
19738
  mesh2.position.set(
20557
19739
  component.position.x ?? 0,
@@ -20561,9 +19743,9 @@ async function renderComponent(component, scene) {
20561
19743
  }
20562
19744
  if (component.rotation) {
20563
19745
  mesh2.rotation.set(
20564
- THREE15.MathUtils.degToRad(component.rotation.x ?? 0),
20565
- THREE15.MathUtils.degToRad(component.rotation.y ?? 0),
20566
- THREE15.MathUtils.degToRad(component.rotation.z ?? 0)
19746
+ THREE10.MathUtils.degToRad(component.rotation.x ?? 0),
19747
+ THREE10.MathUtils.degToRad(component.rotation.y ?? 0),
19748
+ THREE10.MathUtils.degToRad(component.rotation.z ?? 0)
20567
19749
  );
20568
19750
  }
20569
19751
  scene.add(mesh2);
@@ -20576,13 +19758,13 @@ async function renderComponent(component, scene) {
20576
19758
  for (const operation of jscadOperations) {
20577
19759
  const jscadObject = executeJscadOperations2(import_modeling2.default, operation);
20578
19760
  const threeGeom = convertCSGToThreeGeom2(jscadObject);
20579
- const material2 = new THREE15.MeshStandardMaterial({
19761
+ const material2 = new THREE10.MeshStandardMaterial({
20580
19762
  color: 4473924,
20581
19763
  metalness: 0.2,
20582
19764
  roughness: 0.8,
20583
- side: THREE15.DoubleSide
19765
+ side: THREE10.DoubleSide
20584
19766
  });
20585
- const mesh2 = new THREE15.Mesh(threeGeom, material2);
19767
+ const mesh2 = new THREE10.Mesh(threeGeom, material2);
20586
19768
  if (component.position) {
20587
19769
  mesh2.position.set(
20588
19770
  component.position.x ?? 0,
@@ -20592,22 +19774,22 @@ async function renderComponent(component, scene) {
20592
19774
  }
20593
19775
  if (component.rotation) {
20594
19776
  mesh2.rotation.set(
20595
- THREE15.MathUtils.degToRad(component.rotation.x ?? 0),
20596
- THREE15.MathUtils.degToRad(component.rotation.y ?? 0),
20597
- THREE15.MathUtils.degToRad(component.rotation.z ?? 0)
19777
+ THREE10.MathUtils.degToRad(component.rotation.x ?? 0),
19778
+ THREE10.MathUtils.degToRad(component.rotation.y ?? 0),
19779
+ THREE10.MathUtils.degToRad(component.rotation.z ?? 0)
20598
19780
  );
20599
19781
  }
20600
19782
  scene.add(mesh2);
20601
19783
  }
20602
19784
  return;
20603
19785
  }
20604
- const geometry = new THREE15.BoxGeometry(0.5, 0.5, 0.5);
20605
- const material = new THREE15.MeshStandardMaterial({
19786
+ const geometry = new THREE10.BoxGeometry(0.5, 0.5, 0.5);
19787
+ const material = new THREE10.MeshStandardMaterial({
20606
19788
  color: 16711680,
20607
19789
  transparent: true,
20608
19790
  opacity: 0.25
20609
19791
  });
20610
- const mesh = new THREE15.Mesh(geometry, material);
19792
+ const mesh = new THREE10.Mesh(geometry, material);
20611
19793
  if (component.position) {
20612
19794
  mesh.position.set(
20613
19795
  component.position.x ?? 0,
@@ -20628,11 +19810,11 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
20628
19810
  padding = 20,
20629
19811
  zoom = 1.5
20630
19812
  } = options;
20631
- const scene = new THREE16.Scene();
19813
+ const scene = new THREE11.Scene();
20632
19814
  const renderer = new SVGRenderer();
20633
19815
  renderer.setSize(width, height);
20634
- renderer.setClearColor(new THREE16.Color(backgroundColor), 1);
20635
- const camera = new THREE16.OrthographicCamera();
19816
+ renderer.setClearColor(new THREE11.Color(backgroundColor), 1);
19817
+ const camera = new THREE11.OrthographicCamera();
20636
19818
  const aspect = width / height;
20637
19819
  const frustumSize = 100;
20638
19820
  const halfFrustumSize = frustumSize / 2 / zoom;
@@ -20646,14 +19828,14 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
20646
19828
  camera.position.set(position.x, position.y, position.z);
20647
19829
  camera.up.set(0, 1, 0);
20648
19830
  const lookAt = options.camera?.lookAt ?? { x: 0, y: 0, z: 0 };
20649
- camera.lookAt(new THREE16.Vector3(lookAt.x, lookAt.y, lookAt.z));
19831
+ camera.lookAt(new THREE11.Vector3(lookAt.x, lookAt.y, lookAt.z));
20650
19832
  camera.updateProjectionMatrix();
20651
- const ambientLight = new THREE16.AmbientLight(16777215, Math.PI / 2);
19833
+ const ambientLight = new THREE11.AmbientLight(16777215, Math.PI / 2);
20652
19834
  scene.add(ambientLight);
20653
- const pointLight = new THREE16.PointLight(16777215, Math.PI / 4);
19835
+ const pointLight = new THREE11.PointLight(16777215, Math.PI / 4);
20654
19836
  pointLight.position.set(-10, -10, 10);
20655
19837
  scene.add(pointLight);
20656
- const components = su14(circuitJson).cad_component.list();
19838
+ const components = su7(circuitJson).cad_component.list();
20657
19839
  for (const component of components) {
20658
19840
  await renderComponent(component, scene);
20659
19841
  }
@@ -20661,8 +19843,8 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
20661
19843
  if (boardGeom) {
20662
19844
  for (const geom of boardGeom) {
20663
19845
  const geometry = createGeometryFromPolygons(geom.polygons);
20664
- const material = new THREE16.MeshStandardMaterial({
20665
- color: new THREE16.Color(
19846
+ const material = new THREE11.MeshStandardMaterial({
19847
+ color: new THREE11.Color(
20666
19848
  geom.color?.[0] ?? 0,
20667
19849
  geom.color?.[1] ?? 0,
20668
19850
  geom.color?.[2] ?? 0
@@ -20671,18 +19853,18 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
20671
19853
  roughness: 0.8,
20672
19854
  opacity: 0.9,
20673
19855
  transparent: true,
20674
- side: THREE16.DoubleSide
19856
+ side: THREE11.DoubleSide
20675
19857
  });
20676
- const mesh = new THREE16.Mesh(geometry, material);
19858
+ const mesh = new THREE11.Mesh(geometry, material);
20677
19859
  scene.add(mesh);
20678
19860
  }
20679
19861
  }
20680
- const gridHelper = new THREE16.GridHelper(100, 100);
19862
+ const gridHelper = new THREE11.GridHelper(100, 100);
20681
19863
  gridHelper.rotation.x = Math.PI / 2;
20682
19864
  scene.add(gridHelper);
20683
- const box = new THREE16.Box3().setFromObject(scene);
20684
- const center = box.getCenter(new THREE16.Vector3());
20685
- const size = box.getSize(new THREE16.Vector3());
19865
+ const box = new THREE11.Box3().setFromObject(scene);
19866
+ const center = box.getCenter(new THREE11.Vector3());
19867
+ const size = box.getSize(new THREE11.Vector3());
20686
19868
  scene.position.sub(center);
20687
19869
  const maxDim = Math.max(size.x, size.y, size.z);
20688
19870
  if (maxDim > 0) {
@@ -20699,7 +19881,7 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
20699
19881
  }
20700
19882
 
20701
19883
  // src/hooks/exporter/gltf.ts
20702
- import { useEffect as useEffect9, useState as useState12, useMemo as useMemo9, useCallback as useCallback7 } from "react";
19884
+ import { useEffect as useEffect8, useState as useState11, useMemo as useMemo9, useCallback as useCallback7 } from "react";
20703
19885
  function useSaveGltfAs(options = {}) {
20704
19886
  const parse = useParser(options);
20705
19887
  const link = useMemo9(() => document.createElement("a"), []);
@@ -20712,7 +19894,7 @@ function useSaveGltfAs(options = {}) {
20712
19894
  link.dispatchEvent(new MouseEvent("click"));
20713
19895
  URL.revokeObjectURL(url);
20714
19896
  };
20715
- useEffect9(
19897
+ useEffect8(
20716
19898
  () => () => {
20717
19899
  link.remove();
20718
19900
  instance = null;
@@ -20727,13 +19909,13 @@ function useSaveGltfAs(options = {}) {
20727
19909
  }
20728
19910
  function useExportGltfUrl(options = {}) {
20729
19911
  const parse = useParser(options);
20730
- const [url, setUrl] = useState12();
20731
- const [error, setError] = useState12();
19912
+ const [url, setUrl] = useState11();
19913
+ const [error, setError] = useState11();
20732
19914
  const ref = useCallback7(
20733
19915
  (instance) => parse(instance).then(setUrl).catch(setError),
20734
19916
  []
20735
19917
  );
20736
- useEffect9(() => () => URL.revokeObjectURL(url), [url]);
19918
+ useEffect8(() => () => URL.revokeObjectURL(url), [url]);
20737
19919
  return [ref, url, error];
20738
19920
  }
20739
19921
  function useParser(options = {}) {