@tscircuit/core 0.0.2115 → 0.0.2116

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 (3) hide show
  1. package/dist/index.d.ts +187 -53
  2. package/dist/index.js +286 -76
  3. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ __export(components_exports, {
27
27
  AssemblyMotor: () => AssemblyMotor,
28
28
  AssemblyPart: () => AssemblyPart,
29
29
  AssemblyPrintedPart: () => AssemblyPrintedPart,
30
+ AssemblyReferenceSurface: () => AssemblyReferenceSurface,
30
31
  AssemblyScreen: () => AssemblyScreen,
31
32
  AssemblySubassembly: () => AssemblySubassembly,
32
33
  AutoroutingPhase: () => AutoroutingPhase,
@@ -14772,6 +14773,30 @@ var matchesAssemblyIdentity = (component, path) => {
14772
14773
  return true;
14773
14774
  };
14774
14775
 
14776
+ // lib/components/primitive-components/get-part-reference-surfaces.ts
14777
+ import {
14778
+ resolveReferencePlanes
14779
+ } from "jscad-planner";
14780
+ var getPartReferenceSurfaces = (part) => {
14781
+ const model = part._parsedProps.cadModel;
14782
+ const authored = part.componentName === "AssemblyPrintedPart" ? part.printedPartPlan?.referencePlanes ?? (model && typeof model === "object" && "jscad" in model ? resolveReferencePlanes(model.jscad).referencePlanes : []) : [];
14783
+ const surfaces = [
14784
+ ...authored,
14785
+ ...part.children.filter(
14786
+ (child) => child.componentName === "AssemblyReferenceSurface"
14787
+ ).map((child) => child.referenceFrame)
14788
+ ];
14789
+ const names = /* @__PURE__ */ new Set();
14790
+ for (const surface of surfaces) {
14791
+ if (names.has(surface.name))
14792
+ throw new Error(
14793
+ `Part "${part.name}" has duplicate reference surface "${surface.name}"`
14794
+ );
14795
+ names.add(surface.name);
14796
+ }
14797
+ return surfaces;
14798
+ };
14799
+
14775
14800
  // lib/components/primitive-components/get-assembly-scope-components.ts
14776
14801
  var getAssemblyScope = (component) => {
14777
14802
  let ancestor = component.parent;
@@ -14789,9 +14814,19 @@ var getComponentsInAssemblyScope = (component) => {
14789
14814
  };
14790
14815
 
14791
14816
  // lib/components/primitive-components/resolve-printed-part-mounts.ts
14792
- var isMountablePart = (part) => part.componentName === "AssemblyMotor" || part.componentName === "AssemblyPrintedPart";
14817
+ var isMountablePart = (part) => part.componentName === "AssemblyMotor" || part.componentName === "AssemblyPart" || part.componentName === "AssemblyPrintedPart";
14793
14818
  var isPrintedPart = (part) => part.componentName === "AssemblyPrintedPart";
14819
+ var isMotor = (part) => part.componentName === "AssemblyMotor";
14794
14820
  var identity7 = () => [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
14821
+ var getMountTransformPlacement = (world) => {
14822
+ const bottom = world[10] < 0;
14823
+ const sign = bottom ? -1 : 1;
14824
+ return {
14825
+ position: { x: world[12], y: world[13], z: world[14] },
14826
+ pcbRotation: Math.atan2(sign * world[1], sign * world[0]) * 180 / Math.PI,
14827
+ layer: bottom ? "bottom" : "top"
14828
+ };
14829
+ };
14795
14830
  var faceMatrix = ({
14796
14831
  origin,
14797
14832
  normal,
@@ -14806,14 +14841,12 @@ var faceMatrix = ({
14806
14841
  );
14807
14842
  };
14808
14843
  var getFace = (part, faceName) => {
14809
- if (isPrintedPart(part)) {
14810
- const face = part.printedPartPlan?.referencePlanes.find(
14844
+ if (!isMotor(part)) {
14845
+ const face = getPartReferenceSurfaces(part).find(
14811
14846
  (face2) => face2.name === faceName
14812
14847
  );
14813
14848
  if (!face)
14814
- throw new Error(
14815
- `Printed part "${part.name}" has no reference face "${faceName}"`
14816
- );
14849
+ throw new Error(`Part "${part.name}" has no reference face "${faceName}"`);
14817
14850
  return face;
14818
14851
  }
14819
14852
  if (faceName !== "backface" && faceName !== "frontface")
@@ -14845,9 +14878,21 @@ var findTarget = (owner, selector, parts) => {
14845
14878
  const part = matches[0];
14846
14879
  return { part, face: getFace(part, faceName) };
14847
14880
  };
14848
- var rootOrientation = (part) => {
14881
+ var rootOrientation = (part, placement) => {
14849
14882
  const result = identity7();
14850
14883
  if (isPrintedPart(part)) return result;
14884
+ if (!isMotor(part)) {
14885
+ if (!placement)
14886
+ throw new Error(`Part "${part.name}" has no inherited placement`);
14887
+ mat4.translate(result, result, [
14888
+ placement.position.x,
14889
+ placement.position.y,
14890
+ placement.position.z
14891
+ ]);
14892
+ mat4.rotateZ(result, result, placement.pcbRotation * Math.PI / 180);
14893
+ if (placement.layer === "bottom") mat4.rotateY(result, result, Math.PI);
14894
+ return result;
14895
+ }
14851
14896
  switch (part._parsedProps.shaftFacingDirection) {
14852
14897
  case "x+":
14853
14898
  return mat4.rotateY(result, result, Math.PI / 2);
@@ -14877,9 +14922,25 @@ var resolvePrintedPartMounts = (component) => {
14877
14922
  );
14878
14923
  const existing = transforms.get(part);
14879
14924
  if (existing) return existing;
14880
- let world = rootOrientation(part);
14925
+ let world;
14881
14926
  let root = part;
14882
- if (part._parsedProps.mountedTo) {
14927
+ const parent = part.componentName === "AssemblyPart" ? findParentAssembly(part) : void 0;
14928
+ if (parent && isMountablePart(parent)) {
14929
+ world = [...resolvePart(parent, [...path, part])];
14930
+ root = roots.get(parent);
14931
+ } else {
14932
+ const placement = part.componentName === "AssemblyPart" ? resolveInheritedAssemblyPlacement(
14933
+ part,
14934
+ [],
14935
+ (target) => {
14936
+ const targetWorld = resolvePart(target, [...path, part]);
14937
+ root = roots.get(target);
14938
+ return getMountTransformPlacement(targetWorld);
14939
+ }
14940
+ ) : void 0;
14941
+ world = rootOrientation(part, placement);
14942
+ }
14943
+ if ((isMotor(part) || isPrintedPart(part)) && part._parsedProps.mountedTo) {
14883
14944
  const { part: target, face } = findTarget(
14884
14945
  part,
14885
14946
  part._parsedProps.mountedTo,
@@ -14904,7 +14965,7 @@ var resolvePrintedPartMounts = (component) => {
14904
14965
  };
14905
14966
  for (const part of parts) resolvePart(part);
14906
14967
  const motorMountRoots = new Set(
14907
- parts.filter((part) => !isPrintedPart(part) && part._parsedProps.mountedTo).map((part) => roots.get(part))
14968
+ parts.filter((part) => isMotor(part) && part._parsedProps.mountedTo).map((part) => roots.get(part))
14908
14969
  );
14909
14970
  const anchoredRoots = /* @__PURE__ */ new Set();
14910
14971
  for (const board of scoped.filter(
@@ -14922,7 +14983,7 @@ var resolvePrintedPartMounts = (component) => {
14922
14983
  `Assembly "${root.name}" has multiple mounted boards; only one board may determine its placement`
14923
14984
  );
14924
14985
  anchoredRoots.add(root);
14925
- if (!isPrintedPart(root) && !["z+", "z-"].includes(root._parsedProps.shaftFacingDirection ?? "z+"))
14986
+ if (isMotor(root) && !["z+", "z-"].includes(root._parsedProps.shaftFacingDirection ?? "z+"))
14926
14987
  throw new Error(
14927
14988
  `Board "${board.name}" cannot mount to motor "${root.name}" facing ${root._parsedProps.shaftFacingDirection}: rotated PCB boards are not yet supported`
14928
14989
  );
@@ -14995,7 +15056,7 @@ var boardMountsToPrintedPart = (board) => {
14995
15056
  if (!selector) return false;
14996
15057
  const partName = selector.slice(0, selector.lastIndexOf("."));
14997
15058
  return getComponentsInAssemblyScope(board).some(
14998
- (part) => part.componentName === "AssemblyPrintedPart" && matchesAssemblyIdentity(part, partName)
15059
+ (part) => (part.componentName === "AssemblyPrintedPart" || part.componentName === "AssemblyPart") && matchesAssemblyIdentity(part, partName)
14999
15060
  );
15000
15061
  };
15001
15062
 
@@ -15050,17 +15111,32 @@ var findParentAssembly = (component) => {
15050
15111
  return void 0;
15051
15112
  };
15052
15113
  var resolveAssemblyPlacement = (component, path = []) => {
15053
- if (component.componentName === "AssemblyPrintedPart") {
15114
+ if (path.includes(component)) {
15115
+ throw new Error(
15116
+ `Assembly attachment cycle: ${[...path, component].map((item) => item.name).join(" -> ")}`
15117
+ );
15118
+ }
15119
+ if (component.componentName === "AssemblyPrintedPart" || component.componentName === "AssemblyPart") {
15054
15120
  const part = component;
15055
15121
  const { transforms, subcircuitId } = resolvePrintedPartMounts(part);
15056
15122
  const transform = transforms.get(part);
15123
+ if (part.componentName === "AssemblyPart")
15124
+ return {
15125
+ ...getMountTransformPlacement(transform),
15126
+ worldTransform: Math.abs(Math.abs(transform[10]) - 1) > 1e-6 ? transform : void 0,
15127
+ subcircuit_id: subcircuitId
15128
+ };
15057
15129
  return {
15130
+ worldTransform: transform,
15058
15131
  position: { x: transform[12], y: transform[13], z: transform[14] },
15059
15132
  pcbRotation: 0,
15060
15133
  layer: "top",
15061
15134
  subcircuit_id: subcircuitId
15062
15135
  };
15063
15136
  }
15137
+ return resolveInheritedAssemblyPlacement(component, path);
15138
+ };
15139
+ var resolveInheritedAssemblyPlacement = (component, path = [], resolvePartPlacement) => {
15064
15140
  if (path.includes(component)) {
15065
15141
  throw new Error(
15066
15142
  `Assembly attachment cycle: ${[...path, component].map((item) => item.name).join(" -> ")}`
@@ -15073,6 +15149,14 @@ var resolveAssemblyPlacement = (component, path = []) => {
15073
15149
  ) : findParentAssembly(component);
15074
15150
  if (!target)
15075
15151
  return { position: { x: 0, y: 0, z: 0 }, pcbRotation: 0, layer: "top" };
15152
+ if (resolvePartPlacement && (target.componentName === "AssemblyPart" || target.componentName === "AssemblyPrintedPart"))
15153
+ return resolvePartPlacement(target);
15154
+ if (resolvePartPlacement && isPositionedAssembly(target))
15155
+ return resolveInheritedAssemblyPlacement(
15156
+ target,
15157
+ nextPath,
15158
+ resolvePartPlacement
15159
+ );
15076
15160
  if (isPositionedAssembly(target))
15077
15161
  return resolveAssemblyPlacement(target, nextPath);
15078
15162
  const db2 = component.root.db;
@@ -15201,6 +15285,20 @@ var resolveAssemblyModelUrl = (modelUrl) => {
15201
15285
  import { normalizeDegrees as normalizeDegrees4 } from "@tscircuit/math-utils";
15202
15286
  import { cadModelBase, point3 } from "@tscircuit/props";
15203
15287
  import { rotation } from "circuit-json";
15288
+ import { mat4 as mat42, vec3 as vec32 } from "gl-matrix";
15289
+
15290
+ // lib/components/primitive-components/get-assembly-euler-angles.ts
15291
+ var getAssemblyEulerAngles = (transform) => {
15292
+ const y2 = Math.asin(Math.max(-1, Math.min(1, -transform[2])));
15293
+ const angles = Math.abs(Math.cos(y2)) > 1e-8 ? [
15294
+ Math.atan2(transform[6], transform[10]),
15295
+ y2,
15296
+ Math.atan2(transform[1], transform[0])
15297
+ ] : [0, y2, Math.atan2(-transform[4], transform[5])];
15298
+ return angles.map((angle) => angle === 0 ? 0 : angle);
15299
+ };
15300
+
15301
+ // lib/components/primitive-components/render-assembly-cad-model.ts
15204
15302
  import {
15205
15303
  applyToPoint as applyToPoint29,
15206
15304
  compose as compose8,
@@ -15226,6 +15324,28 @@ var renderAssemblyCadModel = (owner, model, placement) => {
15226
15324
  z: rotation.parse(authoredRotation.z)
15227
15325
  };
15228
15326
  const assetUrl = (url) => constructAssetUrl(url, owner.root?.platform?.projectBaseUrl);
15327
+ let mountedPosition;
15328
+ let mountedRotation;
15329
+ if (placement.worldTransform) {
15330
+ const point6 = vec32.transformMat4(
15331
+ [0, 0, 0],
15332
+ [offset.x, offset.y, offset.z + (base.zOffsetFromSurface ?? 0)],
15333
+ placement.worldTransform
15334
+ );
15335
+ mountedPosition = { x: point6[0], y: point6[1], z: point6[2] };
15336
+ const frame = [...placement.worldTransform];
15337
+ mat42.rotateZ(frame, frame, rotationOffset.z * Math.PI / 180);
15338
+ mat42.rotateY(frame, frame, rotationOffset.y * Math.PI / 180);
15339
+ mat42.rotateX(frame, frame, rotationOffset.x * Math.PI / 180);
15340
+ const angles = getAssemblyEulerAngles(frame).map(
15341
+ (angle) => angle * 180 / Math.PI
15342
+ );
15343
+ mountedRotation = {
15344
+ x: angles[0],
15345
+ y: angles[1],
15346
+ z: normalizeDegrees4(angles[2])
15347
+ };
15348
+ }
15229
15349
  const urls = typeof model === "string" ? { footprinter_string: model } : {
15230
15350
  model_stl_url: "stlUrl" in model ? assetUrl(model.stlUrl) : void 0,
15231
15351
  model_obj_url: "objUrl" in model ? assetUrl(model.objUrl) : void 0,
@@ -15238,13 +15358,13 @@ var renderAssemblyCadModel = (owner, model, placement) => {
15238
15358
  };
15239
15359
  const cad = owner.root.db.cad_component.insert({
15240
15360
  ...urls,
15241
- position: {
15361
+ position: mountedPosition ?? {
15242
15362
  ...xy2,
15243
15363
  z: placement.position.z + (bottom ? -1 : 1) * (offset.z + (base.zOffsetFromSurface ?? 0))
15244
15364
  },
15245
15365
  // Same CAD Euler convention as NormalComponent.doInitialCadModelRender:
15246
15366
  // bottom flips Y and negates the board-plane Z angle.
15247
- rotation: {
15367
+ rotation: mountedRotation ?? {
15248
15368
  x: rotationOffset.x,
15249
15369
  y: (bottom ? 180 : 0) + rotationOffset.y,
15250
15370
  z: normalizeDegrees4(
@@ -15253,6 +15373,7 @@ var renderAssemblyCadModel = (owner, model, placement) => {
15253
15373
  },
15254
15374
  layer: placement.layer,
15255
15375
  source_component_id: owner.source_component_id,
15376
+ color: owner.componentName === "AssemblyPrintedPart" ? owner._parsedProps.color : void 0,
15256
15377
  subcircuit_id: placement.subcircuit_id,
15257
15378
  model_origin_position: base.modelOriginPosition ?? { x: 0, y: 0, z: 0 },
15258
15379
  model_unit_to_mm_scale_factor: base.modelUnitToMmScale ?? 1,
@@ -118103,7 +118224,7 @@ function Group_getRoutingPhasePlans(group) {
118103
118224
  var package_default = {
118104
118225
  name: "@tscircuit/core",
118105
118226
  type: "module",
118106
- version: "0.0.2114",
118227
+ version: "0.0.2115",
118107
118228
  types: "dist/index.d.ts",
118108
118229
  main: "dist/index.js",
118109
118230
  module: "dist/index.js",
@@ -118162,7 +118283,7 @@ var package_default = {
118162
118283
  "@tscircuit/miniflex": "^0.0.4",
118163
118284
  "@tscircuit/modelprinter": "^0.0.18",
118164
118285
  "@tscircuit/ngspice-spice-engine": "^0.0.20",
118165
- "@tscircuit/props": "^0.0.696",
118286
+ "@tscircuit/props": "^0.0.697",
118166
118287
  "@tscircuit/schematic-match-adapt": "^0.0.18",
118167
118288
  "@tscircuit/schematic-trace-solver": "https://jscdn.tscircuit.com/@tscircuit/schematic-trace-solver/0.0.228.tgz",
118168
118289
  "@tscircuit/solver-utils": "^0.0.16",
@@ -118178,10 +118299,10 @@ var package_default = {
118178
118299
  "bun-match-svg": "0.0.12",
118179
118300
  "calculate-elbow": "^0.0.12",
118180
118301
  "chokidar-cli": "^3.0.0",
118181
- "circuit-json": "^0.0.521",
118302
+ "circuit-json": "^0.0.525",
118182
118303
  "circuit-json-to-bpc": "^0.0.13",
118183
118304
  "circuit-json-to-connectivity-map": "^0.0.33",
118184
- "circuit-json-to-gltf": "^0.0.148",
118305
+ "circuit-json-to-gltf": "^0.0.151",
118185
118306
  "circuit-json-to-spice": "^0.0.45",
118186
118307
  "circuit-to-svg": "^0.0.445",
118187
118308
  concurrently: "^9.1.2",
@@ -118246,8 +118367,8 @@ var package_default = {
118246
118367
  },
118247
118368
  overrides: {
118248
118369
  "@tscircuit/circuit-json-util": "^0.0.119",
118249
- "@tscircuit/props": "^0.0.696",
118250
- "circuit-json": "^0.0.521",
118370
+ "@tscircuit/props": "^0.0.697",
118371
+ "circuit-json": "^0.0.525",
118251
118372
  "@tscircuit/cableprinter": "^0.0.6"
118252
118373
  }
118253
118374
  };
@@ -130360,7 +130481,7 @@ import { assemblyMotorProps } from "@tscircuit/props";
130360
130481
 
130361
130482
  // lib/components/primitive-components/resolve-assembly-motor-rotation.ts
130362
130483
  import { normalizeDegrees as normalizeDegrees10 } from "@tscircuit/math-utils";
130363
- import { mat4 as mat42, vec3 as vec32 } from "gl-matrix";
130484
+ import { mat4 as mat43, vec3 as vec33 } from "gl-matrix";
130364
130485
 
130365
130486
  // lib/components/primitive-components/resolve-board-mount-rotation-anchor.ts
130366
130487
  var resolveBoardMountRotationAnchor = (board) => {
@@ -130421,14 +130542,14 @@ var resolveAssemblyMotorRotation = (motor, placement) => {
130421
130542
  if (!board || !input) return alignment;
130422
130543
  const bottom = placement.layer === "bottom";
130423
130544
  const sign = bottom ? -1 : 1;
130424
- const orientation2 = mat42.create();
130425
- mat42.rotateX(orientation2, orientation2, radians(alignment.x));
130426
- mat42.rotateY(
130545
+ const orientation2 = mat43.create();
130546
+ mat43.rotateX(orientation2, orientation2, radians(alignment.x));
130547
+ mat43.rotateY(
130427
130548
  orientation2,
130428
130549
  orientation2,
130429
130550
  radians(alignment.y + (bottom ? 180 : 0))
130430
130551
  );
130431
- mat42.rotateZ(orientation2, orientation2, radians(sign * placement.pcbRotation));
130552
+ mat43.rotateZ(orientation2, orientation2, radians(sign * placement.pcbRotation));
130432
130553
  const expression = input.startsWith("calc(") ? input.match(
130433
130554
  /^calc\(\s*(.*?)\s*(?:([+-])\s*((?:\d+(?:\.\d*)?|\.\d+))\s*(degcw|degccw))?\s*\)$/
130434
130555
  ) : [input, input];
@@ -130454,23 +130575,23 @@ var resolveAssemblyMotorRotation = (motor, placement) => {
130454
130575
  const clockwiseAngle = radians(
130455
130576
  (expression[2] === "-" ? -1 : 1) * Number(expression[3] ?? 0) * (expression[4] === "degccw" ? -1 : 1)
130456
130577
  );
130457
- const anchor = vec32.normalize(
130458
- vec32.create(),
130578
+ const anchor = vec33.normalize(
130579
+ vec33.create(),
130459
130580
  resolveBoardMountRotationAnchor(board)
130460
130581
  );
130461
- const shaft = vec32.transformMat4(vec32.create(), [0, 0, 1], orientation2);
130462
- const turn = mat42.fromRotation(mat42.create(), -clockwiseAngle, shaft);
130463
- const target = vec32.transformMat4(vec32.create(), anchor, turn);
130464
- const motorDirection = vec32.transformMat4(
130465
- vec32.create(),
130582
+ const shaft = vec33.transformMat4(vec33.create(), [0, 0, 1], orientation2);
130583
+ const turn = mat43.fromRotation(mat43.create(), -clockwiseAngle, shaft);
130584
+ const target = vec33.transformMat4(vec33.create(), anchor, turn);
130585
+ const motorDirection = vec33.transformMat4(
130586
+ vec33.create(),
130466
130587
  [direction.direction.x, direction.direction.y, direction.direction.z],
130467
130588
  orientation2
130468
130589
  );
130469
130590
  const spin = Math.atan2(
130470
- vec32.dot(shaft, vec32.cross(vec32.create(), motorDirection, target)),
130471
- vec32.dot(motorDirection, target)
130591
+ vec33.dot(shaft, vec33.cross(vec33.create(), motorDirection, target)),
130592
+ vec33.dot(motorDirection, target)
130472
130593
  );
130473
- mat42.rotateZ(orientation2, orientation2, spin);
130594
+ mat43.rotateZ(orientation2, orientation2, spin);
130474
130595
  const pitch = Math.asin(Math.max(-1, Math.min(1, orientation2[8])));
130475
130596
  const nonsingular = Math.abs(orientation2[8]) < 0.999999;
130476
130597
  const roll = nonsingular ? Math.atan2(-orientation2[9], orientation2[10]) : Math.atan2(orientation2[6], orientation2[5]);
@@ -130684,6 +130805,49 @@ var AssemblySubassembly = class extends PrimitiveComponent2 {
130684
130805
  // lib/components/primitive-components/AssemblyPart.ts
130685
130806
  import { assemblyPartProps } from "@tscircuit/props";
130686
130807
  import { isValidElement as isValidElement5 } from "react";
130808
+
130809
+ // lib/components/primitive-components/render-part-reference-surfaces.ts
130810
+ import { mat3, vec3 as vec34 } from "gl-matrix";
130811
+ var renderPartReferenceSurfaces = (part) => {
130812
+ const surfaces = getPartReferenceSurfaces(part);
130813
+ if (!surfaces.length) return;
130814
+ const { transforms, subcircuitId } = resolvePrintedPartMounts(part);
130815
+ const world = transforms.get(part);
130816
+ const rotation5 = mat3.fromMat4(mat3.create(), world);
130817
+ const toPoint = (vector3) => ({
130818
+ x: vector3[0],
130819
+ y: vector3[1],
130820
+ z: vector3[2]
130821
+ });
130822
+ for (const surface of surfaces) {
130823
+ const child = part.children.find(
130824
+ (child2) => child2.componentName === "AssemblyReferenceSurface" && child2.name === surface.name
130825
+ );
130826
+ part.root.db.cad_reference_surface.insert({
130827
+ source_component_id: part.source_component_id,
130828
+ name: surface.name,
130829
+ shape: "rect",
130830
+ center: toPoint(vec34.transformMat4(vec34.create(), surface.origin, world)),
130831
+ normal: toPoint(
130832
+ vec34.normalize(
130833
+ vec34.create(),
130834
+ vec34.transformMat3(vec34.create(), surface.normal, rotation5)
130835
+ )
130836
+ ),
130837
+ x_axis: toPoint(
130838
+ vec34.normalize(
130839
+ vec34.create(),
130840
+ vec34.transformMat3(vec34.create(), surface.xAxis, rotation5)
130841
+ )
130842
+ ),
130843
+ width: child?._parsedProps.width,
130844
+ height: child?._parsedProps.height,
130845
+ subcircuit_id: subcircuitId
130846
+ });
130847
+ }
130848
+ };
130849
+
130850
+ // lib/components/primitive-components/AssemblyPart.ts
130687
130851
  var AssemblyPart = class extends PrimitiveComponent2 {
130688
130852
  isAssemblyDeviceContainer = true;
130689
130853
  get config() {
@@ -130697,6 +130861,7 @@ var AssemblyPart = class extends PrimitiveComponent2 {
130697
130861
  this.add(createInstanceFromReactElement(this.props.cadModel));
130698
130862
  }
130699
130863
  doInitialSourceRender() {
130864
+ getPartReferenceSurfaces(this);
130700
130865
  this.source_component_id = this.root.db.source_component.insert({
130701
130866
  // Circuit JSON currently represents generic assembly geometry as subassembly.
130702
130867
  ftype: "subassembly",
@@ -130706,6 +130871,7 @@ var AssemblyPart = class extends PrimitiveComponent2 {
130706
130871
  }
130707
130872
  doInitialCadModelRender() {
130708
130873
  if (!this.root || this.root.pcbDisabled || !this.source_component_id) return;
130874
+ renderPartReferenceSurfaces(this);
130709
130875
  const model = resolveAssemblyModel(this._parsedProps) ?? this._parsedProps.cadModel;
130710
130876
  if (model && !(typeof model === "object" && "type" in model)) {
130711
130877
  this.cad_component_id = renderAssemblyCadModel(
@@ -130717,6 +130883,46 @@ var AssemblyPart = class extends PrimitiveComponent2 {
130717
130883
  }
130718
130884
  };
130719
130885
 
130886
+ // lib/components/primitive-components/AssemblyReferenceSurface.ts
130887
+ import { assemblyReferenceSurfaceProps } from "@tscircuit/props";
130888
+ var AssemblyReferenceSurface = class extends PrimitiveComponent2 {
130889
+ get config() {
130890
+ return {
130891
+ componentName: "AssemblyReferenceSurface",
130892
+ zodProps: assemblyReferenceSurfaceProps
130893
+ };
130894
+ }
130895
+ doInitialSourceRender() {
130896
+ if (this.parent?.componentName !== "AssemblyPart" && this.parent?.componentName !== "AssemblyPrintedPart")
130897
+ throw new Error(
130898
+ `Reference surface "${this.name}" must be a child of assembly.part or assembly.printedpart`
130899
+ );
130900
+ }
130901
+ /** Part-local frame, right-handed XYZ in mm: origin is a point, normal and
130902
+ * xAxis are unit directions. XY faces +Z, XZ faces +Y, YZ faces +X.
130903
+ * XZ's second tangent is -Z so its tangent basis remains right-handed.
130904
+ */
130905
+ get referenceFrame() {
130906
+ const {
130907
+ name,
130908
+ plane,
130909
+ normalDirection,
130910
+ centerXOffset,
130911
+ centerYOffset,
130912
+ centerZOffset
130913
+ } = this._parsedProps;
130914
+ const sign = normalDirection.endsWith("-") ? -1 : 1;
130915
+ const normal = plane === "xy" ? [0, 0, sign] : plane === "xz" ? [0, sign, 0] : [sign, 0, 0];
130916
+ const xAxis = plane === "yz" ? [0, 1, 0] : [1, 0, 0];
130917
+ return {
130918
+ name,
130919
+ origin: [centerXOffset, centerYOffset, centerZOffset],
130920
+ normal,
130921
+ xAxis
130922
+ };
130923
+ }
130924
+ };
130925
+
130720
130926
  // lib/components/primitive-components/EnclosureCutoutAperture/EnclosureCutoutAperture.ts
130721
130927
  import { enclosureCutoutApertureProps } from "@tscircuit/props";
130722
130928
 
@@ -133266,7 +133472,7 @@ import { isValidElement as isValidElement6 } from "react";
133266
133472
  import { assemblyPrintedPartProps } from "@tscircuit/props";
133267
133473
  import { renderToJscadPlan } from "jscad-fiber/headless";
133268
133474
  import {
133269
- resolveReferencePlanes
133475
+ resolveReferencePlanes as resolveReferencePlanes2
133270
133476
  } from "jscad-planner";
133271
133477
  var AssemblyPrintedPart = class extends PrimitiveComponent2 {
133272
133478
  isAssemblyDeviceContainer = true;
@@ -133284,7 +133490,7 @@ var AssemblyPrintedPart = class extends PrimitiveComponent2 {
133284
133490
  get printedPartPlan() {
133285
133491
  if (!this._parsedProps.jscad) return void 0;
133286
133492
  if (!this.compiledJscad || this.compiledJscadInput !== this._parsedProps.jscad) {
133287
- const { geometry, referencePlanes } = resolveReferencePlanes(
133493
+ const { geometry, referencePlanes } = resolveReferencePlanes2(
133288
133494
  renderToJscadPlan(this._parsedProps.jscad)
133289
133495
  );
133290
133496
  if (!geometry)
@@ -133302,20 +133508,24 @@ var AssemblyPrintedPart = class extends PrimitiveComponent2 {
133302
133508
  }
133303
133509
  doInitialSourceRender() {
133304
133510
  this.printedPartPlan;
133511
+ getPartReferenceSurfaces(this);
133305
133512
  this.source_component_id = this.root.db.source_component.insert({
133306
133513
  ftype: "printedpart",
133307
- name: this.name
133514
+ name: this.name,
133515
+ color: this._parsedProps.color,
133516
+ material: this._parsedProps.material
133308
133517
  }).source_component_id;
133309
133518
  }
133310
133519
  doInitialCadModelRender() {
133311
133520
  if (!this.root || this.root.pcbDisabled || !this.source_component_id) return;
133521
+ renderPartReferenceSurfaces(this);
133312
133522
  const plan = this.printedPartPlan;
133313
133523
  if (!plan) {
133314
133524
  let model = resolveAssemblyModel(this._parsedProps) ?? this._parsedProps.cadModel;
133315
133525
  const placement = resolveAssemblyPlacement(this);
133316
133526
  if (!model || typeof model === "object" && "type" in model) return;
133317
133527
  if (typeof model === "object" && "jscad" in model) {
133318
- const { geometry } = resolveReferencePlanes(
133528
+ const { geometry } = resolveReferencePlanes2(
133319
133529
  model.jscad
133320
133530
  );
133321
133531
  if (!geometry)
@@ -133329,12 +133539,7 @@ var AssemblyPrintedPart = class extends PrimitiveComponent2 {
133329
133539
  }
133330
133540
  const { transforms, subcircuitId } = resolvePrintedPartMounts(this);
133331
133541
  const transform = transforms.get(this);
133332
- const y2 = Math.asin(Math.max(-1, Math.min(1, -transform[2])));
133333
- const angles = Math.abs(Math.cos(y2)) > 1e-8 ? [
133334
- Math.atan2(transform[6], transform[10]),
133335
- y2,
133336
- Math.atan2(transform[1], transform[0])
133337
- ] : [0, y2, Math.atan2(-transform[4], transform[5])];
133542
+ const angles = getAssemblyEulerAngles(transform);
133338
133543
  this.cad_component_id = renderAssemblyCadModel(
133339
133544
  this,
133340
133545
  {
@@ -133843,15 +134048,15 @@ function parseCableString(cableString) {
133843
134048
  }
133844
134049
 
133845
134050
  // lib/components/primitive-components/resolve-assembly-cable.ts
133846
- import { vec3 as vec36 } from "gl-matrix";
134051
+ import { vec3 as vec38 } from "gl-matrix";
133847
134052
 
133848
134053
  // lib/components/primitive-components/resolve-assembly-cable-endpoint.ts
133849
134054
  import { getJstMotorConnector } from "@tscircuit/modelprinter";
133850
- import { mat4 as mat43, vec3 as vec34 } from "gl-matrix";
134055
+ import { mat4 as mat44, vec3 as vec36 } from "gl-matrix";
133851
134056
  import { selectAll as selectAll3 } from "css-select";
133852
134057
 
133853
134058
  // lib/components/primitive-components/resolve-assembly-cable-pin1-position.ts
133854
- import { vec3 as vec33 } from "gl-matrix";
134059
+ import { vec3 as vec35 } from "gl-matrix";
133855
134060
  function resolveAssemblyCablePin1Position(cable, connector, matingCenter, outward) {
133856
134061
  const db2 = cable.root.db;
133857
134062
  const pins = db2.source_port.list().flatMap((sourcePort) => {
@@ -133876,10 +134081,10 @@ function resolveAssemblyCablePin1Position(cable, connector, matingCenter, outwar
133876
134081
  }),
133877
134082
  { x: 0, y: 0 }
133878
134083
  );
133879
- const offset = vec33.fromValues(pin1.x - center.x, pin1.y - center.y, 0);
133880
- const axis = vec33.normalize(vec33.create(), [outward.x, outward.y, outward.z]);
133881
- vec33.scaleAndAdd(offset, offset, axis, -vec33.dot(offset, axis));
133882
- if (vec33.length(offset) < 1e-6) return void 0;
134084
+ const offset = vec35.fromValues(pin1.x - center.x, pin1.y - center.y, 0);
134085
+ const axis = vec35.normalize(vec35.create(), [outward.x, outward.y, outward.z]);
134086
+ vec35.scaleAndAdd(offset, offset, axis, -vec35.dot(offset, axis));
134087
+ if (vec35.length(offset) < 1e-6) return void 0;
133883
134088
  return {
133884
134089
  x: matingCenter.x + offset[0],
133885
134090
  y: matingCenter.y + offset[1],
@@ -133908,33 +134113,33 @@ function resolveAssemblyCableEndpoint(cable, selector, cableConnector) {
133908
134113
  const faceMount = resolveMotorFaceMount(motor);
133909
134114
  const placement = faceMount ? { position: faceMount.position, pcbRotation: 0, layer: "top" } : resolveAssemblyMotorPlacement(motor);
133910
134115
  const offset = faceMount?.rotation ?? resolveAssemblyMotorRotation(motor, placement);
133911
- const orientation2 = mat43.create();
134116
+ const orientation2 = mat44.create();
133912
134117
  const bottom = placement.layer === "bottom";
133913
- mat43.rotateX(orientation2, orientation2, offset.x * Math.PI / 180);
133914
- mat43.rotateY(
134118
+ mat44.rotateX(orientation2, orientation2, offset.x * Math.PI / 180);
134119
+ mat44.rotateY(
133915
134120
  orientation2,
133916
134121
  orientation2,
133917
134122
  (offset.y + (bottom ? 180 : 0)) * Math.PI / 180
133918
134123
  );
133919
- mat43.rotateZ(
134124
+ mat44.rotateZ(
133920
134125
  orientation2,
133921
134126
  orientation2,
133922
134127
  (bottom ? -1 : 1) * (placement.pcbRotation + offset.z) * Math.PI / 180
133923
134128
  );
133924
134129
  const reference = motor.motorReferencePoints.wireside;
133925
- const direction2 = vec34.transformMat4(
133926
- vec34.create(),
134130
+ const direction2 = vec36.transformMat4(
134131
+ vec36.create(),
133927
134132
  [reference.direction.x, reference.direction.y, reference.direction.z],
133928
134133
  orientation2
133929
134134
  );
133930
- const point6 = vec34.transformMat4(
133931
- vec34.create(),
134135
+ const point6 = vec36.transformMat4(
134136
+ vec36.create(),
133932
134137
  [reference.position.x, reference.position.y, reference.position.z],
133933
134138
  orientation2
133934
134139
  );
133935
134140
  const halfPinSpan = (motorConnector.pinCount - 1) * motorConnector.pitch / 2;
133936
- const pin1Offset = vec34.transformMat4(
133937
- vec34.create(),
134141
+ const pin1Offset = vec36.transformMat4(
134142
+ vec36.create(),
133938
134143
  [
133939
134144
  reference.direction.y * halfPinSpan,
133940
134145
  -reference.direction.x * halfPinSpan,
@@ -133983,12 +134188,12 @@ function resolveAssemblyCableEndpoint(cable, selector, cableConnector) {
133983
134188
  const board = connector._getBoard();
133984
134189
  const layer = pcb.layer === "bottom" ? "bottom" : "top";
133985
134190
  const inferredCenter = pcb.cable_insertion_center ?? pcb.center;
133986
- const inferredDirection = vec34.normalize(vec34.create(), [
134191
+ const inferredDirection = vec36.normalize(vec36.create(), [
133987
134192
  inferredCenter.x - pcb.center.x,
133988
134193
  inferredCenter.y - pcb.center.y,
133989
134194
  0
133990
134195
  ]);
133991
- const direction = connector._getPcbComponentInsertionAxisDirection(layer, pcb.rotation) ?? (props.standard === "usb_c" && vec34.length(inferredDirection) > 0 ? { x: inferredDirection[0], y: inferredDirection[1], z: 0 } : { x: 0, y: 0, z: layer === "bottom" ? -1 : 1 });
134196
+ const direction = connector._getPcbComponentInsertionAxisDirection(layer, pcb.rotation) ?? (props.standard === "usb_c" && vec36.length(inferredDirection) > 0 ? { x: inferredDirection[0], y: inferredDirection[1], z: 0 } : { x: 0, y: 0, z: layer === "bottom" ? -1 : 1 });
133992
134197
  const center = Math.abs(direction.z) > 0.5 ? pcb.center : inferredCenter;
133993
134198
  const sign = layer === "bottom" ? -1 : 1;
133994
134199
  const height = !props.standard && cableConnector ? Math.abs(direction.z) > 0.5 ? cableConnector.bodyDepth : cableConnector.bodyHeight / 2 : Math.abs(direction.z) > 0.5 ? props.standard === "jst_ph" ? 6 : props.standard === "jst_sh" ? 4.25 : 1.5 : props.standard === "usb_c" ? 1.5 : props.standard === "jst_sh" ? 1.4 : 2.25;
@@ -134012,7 +134217,7 @@ function resolveAssemblyCableEndpoint(cable, selector, cableConnector) {
134012
134217
  }
134013
134218
 
134014
134219
  // lib/components/primitive-components/infer-assembly-cable-path.ts
134015
- import { vec3 as vec35 } from "gl-matrix";
134220
+ import { vec3 as vec37 } from "gl-matrix";
134016
134221
  function inferAssemblyCablePath({
134017
134222
  from,
134018
134223
  to,
@@ -134022,26 +134227,26 @@ function inferAssemblyCablePath({
134022
134227
  const distance27 = Math.hypot(to.x - from.x, to.y - from.y, to.z - from.z);
134023
134228
  if (distance27 < 1e-6)
134024
134229
  throw new Error("Cable wire exits must be at different positions");
134025
- const chord = vec35.fromValues(to.x - from.x, to.y - from.y, to.z - from.z);
134026
- const routeNormal = vec35.cross(vec35.create(), chord, [
134230
+ const chord = vec37.fromValues(to.x - from.x, to.y - from.y, to.z - from.z);
134231
+ const routeNormal = vec37.cross(vec37.create(), chord, [
134027
134232
  fromDirection.x,
134028
134233
  fromDirection.y,
134029
134234
  fromDirection.z
134030
134235
  ]);
134031
- if (vec35.length(routeNormal) < 1e-6) {
134032
- vec35.cross(
134236
+ if (vec37.length(routeNormal) < 1e-6) {
134237
+ vec37.cross(
134033
134238
  routeNormal,
134034
134239
  chord,
134035
134240
  Math.abs(chord[2]) / distance27 > 0.95 ? [0, 1, 0] : [0, 0, 1]
134036
134241
  );
134037
134242
  }
134038
- const archDirection = vec35.normalize(
134039
- vec35.create(),
134040
- vec35.cross(vec35.create(), routeNormal, chord)
134243
+ const archDirection = vec37.normalize(
134244
+ vec37.create(),
134245
+ vec37.cross(vec37.create(), routeNormal, chord)
134041
134246
  );
134042
134247
  const archSide = from.z < 0 && to.z < 0 ? -1 : 1;
134043
134248
  if (archDirection[2] * archSide < 0)
134044
- vec35.scale(archDirection, archDirection, -1);
134249
+ vec37.scale(archDirection, archDirection, -1);
134045
134250
  const clearance = Math.min(25, Math.max(8, distance27 / 3));
134046
134251
  const controlA = {
134047
134252
  x: from.x + fromDirection.x * clearance,
@@ -134112,8 +134317,8 @@ function resolveAssemblyCable(cable) {
134112
134317
  );
134113
134318
  const definition = fromDefinition;
134114
134319
  const endpointExit = (position, direction, depth) => {
134115
- const exit = vec36.scaleAndAdd(
134116
- vec36.create(),
134320
+ const exit = vec38.scaleAndAdd(
134321
+ vec38.create(),
134117
134322
  [position.x, position.y, position.z],
134118
134323
  [direction.x, direction.y, direction.z],
134119
134324
  depth
@@ -134192,6 +134397,9 @@ var analog = {
134192
134397
 
134193
134398
  // lib/namespaced-elements/assembly.ts
134194
134399
  var assembly = {
134400
+ referencesurface: createNamespacedElement(
134401
+ "assembly.referencesurface"
134402
+ ),
134195
134403
  part: createNamespacedElement("assembly.part"),
134196
134404
  cable: createNamespacedElement("assembly.cable"),
134197
134405
  printedpart: createNamespacedElement(
@@ -134448,6 +134656,7 @@ extendCatalogue({
134448
134656
  "assembly.device": AssemblyDevice,
134449
134657
  "assembly.motor": AssemblyMotor,
134450
134658
  "assembly.part": AssemblyPart,
134659
+ "assembly.referencesurface": AssemblyReferenceSurface,
134451
134660
  "assembly.printedpart": AssemblyPrintedPart,
134452
134661
  "assembly.screen": AssemblyScreen,
134453
134662
  "assembly.subassembly": AssemblySubassembly,
@@ -134477,6 +134686,7 @@ export {
134477
134686
  AssemblyMotor,
134478
134687
  AssemblyPart,
134479
134688
  AssemblyPrintedPart,
134689
+ AssemblyReferenceSurface,
134480
134690
  AssemblyScreen,
134481
134691
  AssemblySubassembly,
134482
134692
  AutoroutingPhase,