@tscircuit/3d-viewer 0.0.633 → 0.0.635

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 +115 -60
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -20161,7 +20161,7 @@ var JscadModel = ({
20161
20161
  );
20162
20162
  };
20163
20163
 
20164
- // src/hooks/use-global-obj-loader.ts
20164
+ // src/hooks/use-global-model-loader.ts
20165
20165
  import { useEffect as useEffect10, useState as useState8 } from "react";
20166
20166
 
20167
20167
  // src/utils/vrml.ts
@@ -20183,21 +20183,24 @@ async function loadVrml(url) {
20183
20183
  return loader.parse(sanitizeVrmlIdentifiers(text), url);
20184
20184
  }
20185
20185
 
20186
- // src/hooks/use-global-obj-loader.ts
20187
- import { MTLLoader, OBJLoader } from "three-stdlib";
20188
- if (typeof window !== "undefined" && !window.TSCIRCUIT_OBJ_LOADER_CACHE) {
20189
- window.TSCIRCUIT_OBJ_LOADER_CACHE = /* @__PURE__ */ new Map();
20186
+ // src/hooks/use-global-model-loader.ts
20187
+ import { Mesh as Mesh8, MeshStandardMaterial as MeshStandardMaterial4 } from "three";
20188
+ import { MTLLoader, OBJLoader, STLLoader } from "three-stdlib";
20189
+ if (typeof window !== "undefined" && !window.TSCIRCUIT_MODEL_LOADER_CACHE) {
20190
+ window.TSCIRCUIT_MODEL_LOADER_CACHE = /* @__PURE__ */ new Map();
20190
20191
  }
20191
- function useGlobalObjLoader(url) {
20192
+ function useGlobalModelLoader(url, modelType) {
20192
20193
  const [obj, setObj] = useState8(null);
20193
20194
  useEffect10(() => {
20194
20195
  if (!url) return;
20195
20196
  const cleanUrl = url.replace(/&cachebust_origin=$/, "");
20196
- const cache = window.TSCIRCUIT_OBJ_LOADER_CACHE;
20197
+ const cachesByModelType = window.TSCIRCUIT_MODEL_LOADER_CACHE;
20198
+ const cache = cachesByModelType.get(modelType) ?? /* @__PURE__ */ new Map();
20199
+ cachesByModelType.set(modelType, cache);
20197
20200
  let hasUrlChanged = false;
20198
- async function loadAndParseObj() {
20201
+ async function loadAndParseModel() {
20199
20202
  try {
20200
- if (cleanUrl.endsWith(".wrl")) {
20203
+ if (modelType === "wrl") {
20201
20204
  return await loadVrml(cleanUrl);
20202
20205
  }
20203
20206
  const response = await fetch(cleanUrl);
@@ -20206,6 +20209,11 @@ function useGlobalObjLoader(url) {
20206
20209
  `Failed to fetch "${cleanUrl}": ${response.status} ${response.statusText}`
20207
20210
  );
20208
20211
  }
20212
+ if (modelType === "stl") {
20213
+ const geometry = new STLLoader().parse(await response.arrayBuffer());
20214
+ const material = new MeshStandardMaterial4({ color: 8947848 });
20215
+ return new Mesh8(geometry, material);
20216
+ }
20209
20217
  const text = await response.text();
20210
20218
  const mtlContentArr = text.match(/newmtl[\s\S]*?endmtl/g);
20211
20219
  const objLoader = new OBJLoader();
@@ -20236,7 +20244,7 @@ function useGlobalObjLoader(url) {
20236
20244
  return result.clone();
20237
20245
  });
20238
20246
  }
20239
- const promise = loadAndParseObj().then((result) => {
20247
+ const promise = loadAndParseModel().then((result) => {
20240
20248
  if (result instanceof Error) {
20241
20249
  return result;
20242
20250
  }
@@ -20255,7 +20263,7 @@ function useGlobalObjLoader(url) {
20255
20263
  return () => {
20256
20264
  hasUrlChanged = true;
20257
20265
  };
20258
- }, [url]);
20266
+ }, [url, modelType]);
20259
20267
  return obj;
20260
20268
  }
20261
20269
 
@@ -20265,6 +20273,7 @@ import * as THREE13 from "three";
20265
20273
  import { jsx as jsx10 } from "react/jsx-runtime";
20266
20274
  function MixedStlModel({
20267
20275
  url,
20276
+ modelType,
20268
20277
  position,
20269
20278
  rotation,
20270
20279
  modelOffset = [0, 0, 0],
@@ -20278,7 +20287,7 @@ function MixedStlModel({
20278
20287
  scale: scale11,
20279
20288
  isTranslucent = false
20280
20289
  }) {
20281
- const obj = useGlobalObjLoader(url);
20290
+ const obj = useGlobalModelLoader(url, modelType);
20282
20291
  const model = useMemo8(() => {
20283
20292
  if (obj && !(obj instanceof Error)) {
20284
20293
  obj.traverse((child) => {
@@ -20352,11 +20361,11 @@ import {
20352
20361
  Color as Color3,
20353
20362
  Float32BufferAttribute,
20354
20363
  Group as Group5,
20355
- Mesh as Mesh9,
20356
- MeshStandardMaterial as MeshStandardMaterial5
20364
+ Mesh as Mesh10,
20365
+ MeshStandardMaterial as MeshStandardMaterial6
20357
20366
  } from "three";
20358
20367
  var DEFAULT_COLOR = new Color3(0.82, 0.82, 0.82);
20359
- var createMaterial = (color) => new MeshStandardMaterial5({
20368
+ var createMaterial = (color) => new MeshStandardMaterial6({
20360
20369
  color: color ? new Color3(color[0], color[1], color[2]) : DEFAULT_COLOR
20361
20370
  });
20362
20371
  function applyFaceColorGroups(geometry, mesh) {
@@ -20426,7 +20435,7 @@ function occtMeshesToGroup(meshes) {
20426
20435
  geometry.setIndex(indices);
20427
20436
  geometry.clearGroups();
20428
20437
  const materials2 = applyFaceColorGroups(geometry, mesh);
20429
- const threeMesh = new Mesh9(
20438
+ const threeMesh = new Mesh10(
20430
20439
  geometry,
20431
20440
  materials2.length > 1 ? materials2 : materials2[0]
20432
20441
  );
@@ -21096,6 +21105,7 @@ var AnyCadComponent = ({
21096
21105
  MixedStlModel,
21097
21106
  {
21098
21107
  url,
21108
+ modelType: meshModelType,
21099
21109
  position: adjustedPosition,
21100
21110
  rotation: rotationOffset,
21101
21111
  modelOffset: modelTransform.modelPosition,
@@ -21111,7 +21121,7 @@ var AnyCadComponent = ({
21111
21121
  isHovered,
21112
21122
  isTranslucent
21113
21123
  },
21114
- `${cad_component.cad_component_id}-mixed-${url}`
21124
+ `${cad_component.cad_component_id}-mixed-${meshModelType}-${url}`
21115
21125
  )
21116
21126
  );
21117
21127
  }
@@ -22051,7 +22061,7 @@ import * as THREE30 from "three";
22051
22061
  // package.json
22052
22062
  var package_default = {
22053
22063
  name: "@tscircuit/3d-viewer",
22054
- version: "0.0.632",
22064
+ version: "0.0.634",
22055
22065
  repository: {
22056
22066
  type: "git",
22057
22067
  url: "https://github.com/tscircuit/3d-viewer"
@@ -22119,8 +22129,8 @@ var package_default = {
22119
22129
  "@tscircuit/alphabet": "^0.0.25",
22120
22130
  "@tscircuit/circuit-json-util": "^0.0.119",
22121
22131
  "@tscircuit/copper-pour-solver": "^0.0.29",
22122
- "@tscircuit/core": "^0.0.2188",
22123
- "@tscircuit/props": "^0.0.705",
22132
+ "@tscircuit/core": "^0.0.2195",
22133
+ "@tscircuit/props": "^0.0.707",
22124
22134
  "@tscircuit/solver-utils": "^0.0.4",
22125
22135
  "@types/debug": "^4.1.13",
22126
22136
  "@types/jsdom": "^21.1.7",
@@ -24039,46 +24049,45 @@ var createPillPrism = (width, height, depth) => {
24039
24049
  });
24040
24050
  return (0, import_booleans.union)(rect, firstCap, secondCap);
24041
24051
  };
24042
- var createHoleWithPolygonPadHoleGeom = (hole, height, options = {}) => {
24052
+ var createHoleWithPolygonPadHoleGeom = (hole, depth, options = {}) => {
24043
24053
  const holeShape = hole.hole_shape || "circle";
24044
24054
  const sizeDelta = options.sizeDelta ?? 0;
24045
24055
  const offsetX = hole.hole_offset_x || 0;
24046
24056
  const offsetY = hole.hole_offset_y || 0;
24047
- const center = [
24048
- hole.x + offsetX,
24049
- hole.y + offsetY,
24050
- 0
24051
- ];
24057
+ let drill;
24052
24058
  if (holeShape === "circle") {
24053
24059
  const diameter = Math.max((hole.hole_diameter ?? 0) + sizeDelta, M);
24054
- const radius = Math.max(diameter / 2, M / 2);
24055
- return (0, import_primitives3.cylinder)({ center, radius, height });
24056
- }
24057
- const baseWidth = hole.hole_width ?? hole.hole_diameter;
24058
- const baseHeight = hole.hole_height ?? hole.hole_diameter;
24059
- if (!baseWidth || !baseHeight) {
24060
- return null;
24061
- }
24062
- const width = Math.max(baseWidth + sizeDelta, M);
24063
- const heightVal = Math.max(baseHeight + sizeDelta, M);
24064
- if (holeShape === "oval") {
24065
- const ellipsePrism = createEllipsePrism(width, heightVal, height);
24066
- return (0, import_transforms2.translate)([center[0], center[1], 0], ellipsePrism);
24067
- }
24068
- if (holeShape === "pill" || holeShape === "rotated_pill") {
24069
- let pill = createPillPrism(width, heightVal, height);
24070
- if (!pill) return null;
24071
- const rotation = hole.ccw_rotation || 0;
24072
- if (rotation) {
24073
- pill = (0, import_transforms2.rotateZ)(rotation * Math.PI / 180, pill);
24060
+ const radius = diameter / 2;
24061
+ drill = (0, import_primitives3.cylinder)({ center: [0, 0, 0], radius, height: depth });
24062
+ } else {
24063
+ const baseWidth = hole.hole_width ?? hole.hole_diameter;
24064
+ const baseHeight = hole.hole_height ?? hole.hole_diameter;
24065
+ if (!baseWidth || !baseHeight) {
24066
+ return null;
24067
+ }
24068
+ const shapeWidth = Math.max(baseWidth + sizeDelta, M);
24069
+ const shapeHeight = Math.max(baseHeight + sizeDelta, M);
24070
+ if (holeShape === "oval") {
24071
+ drill = createEllipsePrism(shapeWidth, shapeHeight, depth);
24072
+ } else if (holeShape === "pill" || holeShape === "rotated_pill") {
24073
+ drill = createPillPrism(shapeWidth, shapeHeight, depth);
24074
+ } else {
24075
+ const fallbackDiameter = Math.max(
24076
+ (hole.hole_diameter ?? baseWidth) + sizeDelta,
24077
+ M
24078
+ );
24079
+ drill = (0, import_primitives3.cylinder)({
24080
+ center: [0, 0, 0],
24081
+ radius: fallbackDiameter / 2,
24082
+ height: depth
24083
+ });
24074
24084
  }
24075
- return (0, import_transforms2.translate)(center, pill);
24076
24085
  }
24077
- const fallbackDiameter = Math.max(
24078
- (hole.hole_diameter ?? baseWidth ?? baseHeight ?? M) + sizeDelta,
24079
- M
24080
- );
24081
- return (0, import_primitives3.cylinder)({ center, radius: fallbackDiameter / 2, height });
24086
+ drill = (0, import_transforms2.translate)([offsetX, offsetY, 0], drill);
24087
+ if (hole.ccw_rotation) {
24088
+ drill = (0, import_transforms2.rotateZ)(hole.ccw_rotation * Math.PI / 180, drill);
24089
+ }
24090
+ return (0, import_transforms2.translate)([hole.x, hole.y, 0], drill);
24082
24091
  };
24083
24092
 
24084
24093
  // src/geoms/plated-hole.ts
@@ -24567,7 +24576,13 @@ var platedHole = (plated_hole, ctx, options = {}) => {
24567
24576
  const centerZ = 0;
24568
24577
  const createPolygonPad = (thickness, zCenter) => {
24569
24578
  const safeThickness = Math.max(thickness, M);
24570
- const extruded = (0, import_extrusions3.extrudeLinear)({ height: safeThickness }, polygon2d);
24579
+ let extruded = (0, import_extrusions3.extrudeLinear)({ height: safeThickness }, polygon2d);
24580
+ if (plated_hole.ccw_rotation) {
24581
+ extruded = (0, import_transforms4.rotate)(
24582
+ [0, 0, plated_hole.ccw_rotation * Math.PI / 180],
24583
+ extruded
24584
+ );
24585
+ }
24571
24586
  return (0, import_transforms4.translate)(
24572
24587
  [plated_hole.x, plated_hole.y, zCenter - safeThickness / 2],
24573
24588
  extruded
@@ -25655,6 +25670,7 @@ function clearViaHolesFromSurface({
25655
25670
  }
25656
25671
 
25657
25672
  // src/textures/create-combined-board-textures.ts
25673
+ import { CircuitToCanvasDrawer as CircuitToCanvasDrawer12 } from "circuit-to-canvas";
25658
25674
  import * as THREE46 from "three";
25659
25675
 
25660
25676
  // src/utils/pad-texture.ts
@@ -27163,7 +27179,8 @@ var createCombinedTexture = ({
27163
27179
  boardData,
27164
27180
  traceTextureResolution,
27165
27181
  layer,
27166
- openViaHoles
27182
+ openViaHoles,
27183
+ drillElements
27167
27184
  }) => {
27168
27185
  const hasImage = textures.some((texture) => texture?.image);
27169
27186
  if (!hasImage) return null;
@@ -27185,6 +27202,30 @@ var createCombinedTexture = ({
27185
27202
  const image = texture.image;
27186
27203
  ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight);
27187
27204
  }
27205
+ if (drillElements.length > 0) {
27206
+ const apertureMask = document.createElement("canvas");
27207
+ apertureMask.width = canvasWidth;
27208
+ apertureMask.height = canvasHeight;
27209
+ const maskCtx = apertureMask.getContext("2d");
27210
+ if (maskCtx) {
27211
+ maskCtx.fillStyle = "black";
27212
+ maskCtx.fillRect(0, 0, canvasWidth, canvasHeight);
27213
+ if (layer === "bottom") {
27214
+ maskCtx.translate(0, canvasHeight);
27215
+ maskCtx.scale(1, -1);
27216
+ }
27217
+ const drawer = new CircuitToCanvasDrawer12(maskCtx);
27218
+ drawer.setCameraBounds(boardOutlineBounds);
27219
+ drawer.drawElements(drillElements, {
27220
+ layers: [layer === "top" ? "top_copper" : "bottom_copper"],
27221
+ clearDrillHoles: true
27222
+ });
27223
+ ctx.save();
27224
+ ctx.globalCompositeOperation = "destination-in";
27225
+ ctx.drawImage(apertureMask, 0, 0, canvasWidth, canvasHeight);
27226
+ ctx.restore();
27227
+ }
27228
+ }
27188
27229
  clearViaHolesFromSurface({
27189
27230
  ctx,
27190
27231
  vias: openViaHoles,
@@ -27254,6 +27295,9 @@ function createCombinedBoardTextures({
27254
27295
  const silkscreenColor = "rgb(255,255,255)";
27255
27296
  const copperColor = toRgb3(colors.copper);
27256
27297
  const showBoardBody = visibility?.boardBody ?? true;
27298
+ const drillElements = circuitJson.filter(
27299
+ (element) => element.type === "pcb_hole" || element.type === "pcb_plated_hole" || element.type === "pcb_cutout"
27300
+ );
27257
27301
  const openViaHoles = getViaHolesAtExposedPads(
27258
27302
  circuitJson,
27259
27303
  boardData,
@@ -27348,6 +27392,7 @@ function createCombinedBoardTextures({
27348
27392
  }) : null;
27349
27393
  const boardTexture = createCombinedTexture({
27350
27394
  openViaHoles,
27395
+ drillElements,
27351
27396
  textures: [
27352
27397
  copperPourTexture,
27353
27398
  traceTexture,
@@ -27976,7 +28021,7 @@ function JscadBoardTextures({
27976
28021
  // src/three-components/STLModel.tsx
27977
28022
  import { useState as useState18, useEffect as useEffect29, useMemo as useMemo22 } from "react";
27978
28023
  import * as THREE50 from "three";
27979
- import { STLLoader } from "three-stdlib";
28024
+ import { STLLoader as STLLoader2 } from "three-stdlib";
27980
28025
  function STLModel({
27981
28026
  stlUrl,
27982
28027
  stlData,
@@ -27988,7 +28033,7 @@ function STLModel({
27988
28033
  const { rootObject } = useThree();
27989
28034
  const [geom, setGeom] = useState18(null);
27990
28035
  useEffect29(() => {
27991
- const loader = new STLLoader();
28036
+ const loader = new STLLoader2();
27992
28037
  if (stlData) {
27993
28038
  try {
27994
28039
  const geometry = loader.parse(stlData);
@@ -30463,12 +30508,17 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
30463
30508
  if (!Array.isArray(padOutline) || padOutline.length < 3) {
30464
30509
  return;
30465
30510
  }
30466
- const boardHoleOp = createHoleOpForPolygonPad({
30511
+ let boardHoleOp = createHoleOpForPolygonPad({
30467
30512
  ph,
30468
30513
  depth: pcbThickness * 1.2,
30469
30514
  sizeDelta: 2 * M
30470
30515
  });
30471
30516
  if (!boardHoleOp) return;
30517
+ if (ph.ccw_rotation) {
30518
+ const rotatedOp = boardHoleOp.rotate([0, 0, ph.ccw_rotation]);
30519
+ manifoldInstancesForCleanup.push(rotatedOp);
30520
+ boardHoleOp = rotatedOp;
30521
+ }
30472
30522
  const translatedBoardHole = boardHoleOp.translate([ph.x, ph.y, 0]);
30473
30523
  manifoldInstancesForCleanup.push(translatedBoardHole);
30474
30524
  platedHoleBoardDrills.push(translatedBoardHole);
@@ -30494,8 +30544,13 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
30494
30544
  }) || barrelOp;
30495
30545
  const copperUnion = Manifold.union([mainFill, barrelOp]);
30496
30546
  manifoldInstancesForCleanup.push(copperUnion);
30497
- const finalCopper = copperUnion.subtract(holeCutOp);
30547
+ let finalCopper = copperUnion.subtract(holeCutOp);
30498
30548
  manifoldInstancesForCleanup.push(finalCopper);
30549
+ if (ph.ccw_rotation) {
30550
+ const rotatedOp = finalCopper.rotate([0, 0, ph.ccw_rotation]);
30551
+ manifoldInstancesForCleanup.push(rotatedOp);
30552
+ finalCopper = rotatedOp;
30553
+ }
30499
30554
  const translatedCopper = finalCopper.translate([ph.x, ph.y, 0]);
30500
30555
  manifoldInstancesForCleanup.push(translatedCopper);
30501
30556
  let finalCopperOp = translatedCopper;
@@ -40411,10 +40466,10 @@ import * as THREE65 from "three";
40411
40466
  import * as THREE64 from "three";
40412
40467
  import { GLTFLoader as GLTFLoader3 } from "three/examples/jsm/loaders/GLTFLoader.js";
40413
40468
  import { OBJLoader as OBJLoader2 } from "three/examples/jsm/loaders/OBJLoader.js";
40414
- import { STLLoader as STLLoader2 } from "three/examples/jsm/loaders/STLLoader.js";
40469
+ import { STLLoader as STLLoader3 } from "three/examples/jsm/loaders/STLLoader.js";
40415
40470
  async function load3DModel(url) {
40416
40471
  if (url.endsWith(".stl")) {
40417
- const loader = new STLLoader2();
40472
+ const loader = new STLLoader3();
40418
40473
  const geometry = await loader.loadAsync(url);
40419
40474
  const material = new THREE64.MeshStandardMaterial({
40420
40475
  color: 8947848,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/3d-viewer",
3
- "version": "0.0.633",
3
+ "version": "0.0.635",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/tscircuit/3d-viewer"
@@ -68,8 +68,8 @@
68
68
  "@tscircuit/alphabet": "^0.0.25",
69
69
  "@tscircuit/circuit-json-util": "^0.0.119",
70
70
  "@tscircuit/copper-pour-solver": "^0.0.29",
71
- "@tscircuit/core": "^0.0.2188",
72
- "@tscircuit/props": "^0.0.705",
71
+ "@tscircuit/core": "^0.0.2195",
72
+ "@tscircuit/props": "^0.0.707",
73
73
  "@tscircuit/solver-utils": "^0.0.4",
74
74
  "@types/debug": "^4.1.13",
75
75
  "@types/jsdom": "^21.1.7",