jscad-electronics 0.0.183 → 0.0.185

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/vanilla.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { BufferGeometry } from 'three';
2
2
  import * as jscadModeling from '@jscad/modeling';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
- import { NemaMotorModelPropsInput, HexSocketBoltModelPropsInput, SheetMetalModelPropsInput } from '@tscircuit/modelprinter';
4
+ import { NemaMotorModelPropsInput, HexSocketBoltModelPropsInput, SheetMetalModelPropsInput, SpurGearModelPropsInput, WormGearModelPropsInput } from '@tscircuit/modelprinter';
5
5
  import * as _jscad_modeling_src_geometries_types from '@jscad/modeling/src/geometries/types';
6
6
 
7
7
  type VNode = {
@@ -78,6 +78,48 @@ type SheetMetalProps = SheetMetalModelPropsInput & {
78
78
  declare function createSheetMetalGeom(input: SheetMetalModelPropsInput): _jscad_modeling_src_geometries_types.Geom3;
79
79
  declare function SheetMetal({ color, ...props }: SheetMetalProps): react_jsx_runtime.JSX.Element;
80
80
 
81
+ /** Indexed triangles, counterclockwise from outside; millimeters, Z up. */
82
+ interface SpurGearMesh {
83
+ positions: number[];
84
+ indices: number[];
85
+ }
86
+ /**
87
+ * An external involute spur gear with its lower face at Z=0. Tooth centers
88
+ * start on +X and rotate counterclockwise with phase. The optional hub extends
89
+ * above the upper face, and the round bore passes through both gear and hub.
90
+ * Flanks are sampled involutes; tips and root valleys follow circular arcs.
91
+ * Below the base circle the flanks extend radially, without a generated
92
+ * trochoidal root fillet or cutter undercut. No CSG or renderer is required.
93
+ */
94
+ declare function createSpurGearMesh(input: SpurGearModelPropsInput): SpurGearMesh;
95
+
96
+ type SpurGearProps = SpurGearModelPropsInput & {
97
+ color?: string;
98
+ };
99
+ /** Axis +Z, lower face Z=0; optional hub extends above the gear face. */
100
+ declare function createSpurGearGeom(input: SpurGearModelPropsInput): _jscad_modeling_src_geometries_types.Geom3;
101
+ declare function SpurGear({ color, ...props }: SpurGearProps): react_jsx_runtime.JSX.Element;
102
+
103
+ /** Indexed triangles, counterclockwise from outside; millimeters, Z up. */
104
+ interface WormGearMesh {
105
+ positions: number[];
106
+ indices: number[];
107
+ }
108
+ /**
109
+ * A cylindrical worm from Z=0 to Z=length, with square-cut ends.
110
+ * The axial trapezoidal rack profile is swept around +Z. A right-hand crest
111
+ * winds counterclockwise as Z increases; phase rotates the entire solid.
112
+ * This is a faceted visual worm, not a hobbed wheel or a conjugate gear pair.
113
+ */
114
+ declare const createWormGearMesh: (input: WormGearModelPropsInput) => WormGearMesh;
115
+
116
+ type WormGearProps = WormGearModelPropsInput & {
117
+ color?: string;
118
+ };
119
+ /** Square-cut worm along +Z, from Z=0 to length. Threads are approximate. */
120
+ declare function createWormGearGeom(input: WormGearModelPropsInput): _jscad_modeling_src_geometries_types.Geom3;
121
+ declare function WormGear({ color, ...props }: WormGearProps): react_jsx_runtime.JSX.Element;
122
+
81
123
  declare function getJscadModelForFootprint(footprint: string, jscad: typeof jscadModeling): RenderResult;
82
124
  declare function getJscadModelForFootprintWithPads(footprint: string, jscad: typeof jscadModeling): RenderResult;
83
125
  declare function createJSCADRenderer(jscad: typeof jscadModeling): {
@@ -86,4 +128,4 @@ declare function createJSCADRenderer(jscad: typeof jscadModeling): {
86
128
  };
87
129
  };
88
130
 
89
- export { type ColoredGeom, Fragment, HexSocketBolt, type HexSocketBoltMesh, type HexSocketBoltProps, NEMA17, NEMA23, NEMA8, type NemaFrameMotorProps, NemaMotor, type NemaMotorProps, type RenderResult, SheetMetal, type SheetMetalMesh, type SheetMetalProps, type VNode, convertCSGToThreeGeom, createHexSocketBoltGeom, createHexSocketBoltMesh, createJSCADRenderer, createSheetMetalGeom, createSheetMetalMesh, getJscadModelForFootprint, getJscadModelForFootprintWithPads, h };
131
+ export { type ColoredGeom, Fragment, HexSocketBolt, type HexSocketBoltMesh, type HexSocketBoltProps, NEMA17, NEMA23, NEMA8, type NemaFrameMotorProps, NemaMotor, type NemaMotorProps, type RenderResult, SheetMetal, type SheetMetalMesh, type SheetMetalProps, SpurGear, type SpurGearMesh, type SpurGearProps, type VNode, WormGear, type WormGearMesh, type WormGearProps, convertCSGToThreeGeom, createHexSocketBoltGeom, createHexSocketBoltMesh, createJSCADRenderer, createSheetMetalGeom, createSheetMetalMesh, createSpurGearGeom, createSpurGearMesh, createWormGearGeom, createWormGearMesh, getJscadModelForFootprint, getJscadModelForFootprintWithPads, h };
package/dist/vanilla.js CHANGED
@@ -172,7 +172,7 @@ var ExtrudedPads = ({
172
172
  circuitJson,
173
173
  footprint
174
174
  }) => {
175
- if (!circuitJson && footprint && ["nema", "hexsocketbolt", "sheetmetal"].includes(
175
+ if (!circuitJson && footprint && ["nema", "hexsocketbolt", "sheetmetal", "spurgear", "wormgear"].includes(
176
176
  mp.string(footprint.split("_", 1)[0]).params().fn
177
177
  )) {
178
178
  mp.string(footprint).json();
@@ -218,7 +218,7 @@ var createHexSocketBoltMesh = (input) => {
218
218
  } = hexSocketBoltDimensions[props.metricSize];
219
219
  const { length, showThreads } = props;
220
220
  const segments = 96;
221
- const steps = Math.max(2, Math.ceil(length / pitch * 24));
221
+ const steps = showThreads ? Math.max(2, Math.ceil(length / pitch * 24)) : 2;
222
222
  if (steps > 24e3)
223
223
  throw new Error(
224
224
  "Bolt length exceeds mesh resolution limit (1000 thread turns)"
@@ -254,8 +254,11 @@ var createHexSocketBoltMesh = (input) => {
254
254
  }
255
255
  };
256
256
  let previous = -1;
257
- for (let step = 0; step <= steps; step++) {
258
- const z = -length + length * step / steps;
257
+ const shaftLevels = showThreads ? Array.from(
258
+ { length: steps + 1 },
259
+ (_, step) => -length + length * step / steps
260
+ ) : [-length, -length + tipBevel, 0];
261
+ for (const z of shaftLevels) {
259
262
  const current = ring(z, (angle) => {
260
263
  const phase = ((z / pitch - angle / (2 * Math.PI)) % 1 + 1) % 1;
261
264
  const distance2 = Math.min(phase, 1 - phase);
@@ -471,8 +474,357 @@ function SheetMetal({ color = "#737e8f", ...props }) {
471
474
  return /* @__PURE__ */ jsx(Colorize, { color, children: /* @__PURE__ */ jsx(Custom, { geometry: createSheetMetalGeom(props) }) });
472
475
  }
473
476
 
477
+ // lib/mechanical/spur-gear-mesh.ts
478
+ import {
479
+ getSpurGearDimensions,
480
+ spurGearModelPropsSchema
481
+ } from "@tscircuit/modelprinter";
482
+ import earcut2 from "earcut";
483
+ function createSpurGearMesh(input) {
484
+ const props = spurGearModelPropsSchema.parse(input);
485
+ const dimensions = getSpurGearDimensions(props);
486
+ const pitchRadius = dimensions.pitchDiameter / 2;
487
+ const baseRadius = dimensions.baseDiameter / 2;
488
+ const outsideRadius = dimensions.outsideDiameter / 2;
489
+ const rootRadius = dimensions.rootDiameter / 2;
490
+ const pressureAngle = props.pressureAngle * Math.PI / 180;
491
+ const toothAngle = Math.PI * 2 / props.toothCount;
492
+ const phase = (props.phase % 360 + 360) % 360 * (Math.PI / 180);
493
+ const baseHalfAngle = dimensions.toothThickness / (2 * pitchRadius) + Math.tan(pressureAngle) - pressureAngle;
494
+ const startRadius = Math.max(baseRadius, rootRadius);
495
+ const startT = Math.sqrt(Math.max(0, (startRadius / baseRadius) ** 2 - 1));
496
+ const outsideT = Math.sqrt((outsideRadius / baseRadius) ** 2 - 1);
497
+ const halfAngle = (t) => baseHalfAngle - (t - Math.atan(t));
498
+ const rootHalfAngle = halfAngle(startT);
499
+ const tipHalfAngle = halfAngle(outsideT);
500
+ const flankSteps = Math.max(2, Math.ceil(props.segmentsPerTooth / 2));
501
+ const arcSteps = Math.max(1, Math.ceil(props.segmentsPerTooth / 4));
502
+ const rootValleyAngle = toothAngle - 2 * rootHalfAngle;
503
+ const innerRadius = Math.max(props.boreDiameter, props.hubDiameter) / 2;
504
+ const safeRootStep = innerRadius > 0 ? 1.8 * Math.acos(innerRadius / rootRadius) : Math.PI;
505
+ const rootArcSteps = Math.max(
506
+ arcSteps,
507
+ Math.ceil(rootValleyAngle / safeRootStep)
508
+ );
509
+ const pointsPerTooth = 2 * flankSteps + arcSteps + rootArcSteps + (rootRadius < baseRadius ? 2 : 0);
510
+ if (!Number.isFinite(rootArcSteps) || 2 * props.toothCount * pointsPerTooth + 1024 > 1e6)
511
+ throw new Error(
512
+ "Spur gear bore or hub clearance exceeds mesh resolution limit (1 million vertices)"
513
+ );
514
+ const outline = [];
515
+ const polar = (radius, angle) => {
516
+ outline.push([radius * Math.cos(angle), radius * Math.sin(angle)]);
517
+ };
518
+ for (let tooth = 0; tooth < props.toothCount; tooth++) {
519
+ const center = phase + tooth * toothAngle;
520
+ polar(rootRadius, center - rootHalfAngle);
521
+ if (rootRadius < baseRadius) polar(baseRadius, center - baseHalfAngle);
522
+ for (let step = 1; step <= flankSteps; step++) {
523
+ const t = startT + (outsideT - startT) * step / flankSteps;
524
+ polar(baseRadius * Math.sqrt(1 + t * t), center - halfAngle(t));
525
+ }
526
+ for (let step = 1; step <= arcSteps; step++)
527
+ polar(
528
+ outsideRadius,
529
+ center - tipHalfAngle + 2 * tipHalfAngle * step / arcSteps
530
+ );
531
+ for (let step = flankSteps - 1; step >= 0; step--) {
532
+ const t = startT + (outsideT - startT) * step / flankSteps;
533
+ polar(baseRadius * Math.sqrt(1 + t * t), center + halfAngle(t));
534
+ }
535
+ if (rootRadius < baseRadius) polar(rootRadius, center + baseHalfAngle);
536
+ for (let step = 1; step < rootArcSteps; step++)
537
+ polar(
538
+ rootRadius,
539
+ center + rootHalfAngle + rootValleyAngle * step / rootArcSteps
540
+ );
541
+ }
542
+ const positions = [];
543
+ const indices = [];
544
+ const ring = (points, z) => {
545
+ const start = positions.length / 3;
546
+ for (const [x, y] of points) positions.push(x, y, z);
547
+ return start;
548
+ };
549
+ const circleSegments = Math.max(48, Math.min(256, props.toothCount * 4));
550
+ const circle2 = (radius) => Array.from({ length: circleSegments }, (_, segment) => {
551
+ const angle = phase + segment * Math.PI * 2 / circleSegments;
552
+ return [radius * Math.cos(angle), radius * Math.sin(angle)];
553
+ });
554
+ const connect = (bottom2, top2, count, inward = false) => {
555
+ for (let i = 0; i < count; i++) {
556
+ const next = (i + 1) % count;
557
+ if (inward)
558
+ indices.push(
559
+ bottom2 + i,
560
+ top2 + next,
561
+ bottom2 + next,
562
+ bottom2 + i,
563
+ top2 + i,
564
+ top2 + next
565
+ );
566
+ else
567
+ indices.push(
568
+ bottom2 + i,
569
+ bottom2 + next,
570
+ top2 + next,
571
+ bottom2 + i,
572
+ top2 + next,
573
+ top2 + i
574
+ );
575
+ }
576
+ };
577
+ const cap = (outer, outerStart, upward, inner) => {
578
+ const points = inner ? [...outer, ...inner.points] : outer;
579
+ const triangles = earcut2(
580
+ points.flatMap(([x, y]) => [x, y]),
581
+ inner ? [outer.length] : []
582
+ );
583
+ const vertex = (index) => index < outer.length ? outerStart + index : inner.start + index - outer.length;
584
+ for (let i = 0; i < triangles.length; i += 3) {
585
+ const a = triangles[i];
586
+ const b = triangles[i + 1];
587
+ const c = triangles[i + 2];
588
+ const pa = points[a];
589
+ const pb = points[b];
590
+ const pc = points[c];
591
+ const area = (pb[0] - pa[0]) * (pc[1] - pa[1]) - (pb[1] - pa[1]) * (pc[0] - pa[0]);
592
+ if (area > 0 === upward) indices.push(vertex(a), vertex(b), vertex(c));
593
+ else indices.push(vertex(a), vertex(c), vertex(b));
594
+ }
595
+ };
596
+ const bottom = ring(outline, 0);
597
+ const top = ring(outline, props.faceWidth);
598
+ connect(bottom, top, outline.length);
599
+ const hasHub = props.hubDiameter > 0 && props.hubLength > 0;
600
+ const height10 = props.faceWidth + (hasHub ? props.hubLength : 0);
601
+ const bore = props.boreDiameter > 0 ? circle2(props.boreDiameter / 2) : void 0;
602
+ const boreBottom = bore ? ring(bore, 0) : void 0;
603
+ const boreTop = bore ? ring(bore, height10) : void 0;
604
+ if (bore) connect(boreBottom, boreTop, bore.length, true);
605
+ cap(
606
+ outline,
607
+ bottom,
608
+ false,
609
+ bore ? { points: bore, start: boreBottom } : void 0
610
+ );
611
+ if (hasHub) {
612
+ const hub = circle2(props.hubDiameter / 2);
613
+ const hubBottom = ring(hub, props.faceWidth);
614
+ const hubTop = ring(hub, height10);
615
+ cap(outline, top, true, { points: hub, start: hubBottom });
616
+ connect(hubBottom, hubTop, hub.length);
617
+ if (bore) connect(hubTop, boreTop, hub.length);
618
+ else cap(hub, hubTop, true);
619
+ } else
620
+ cap(
621
+ outline,
622
+ top,
623
+ true,
624
+ bore ? { points: bore, start: boreTop } : void 0
625
+ );
626
+ return { positions, indices };
627
+ }
628
+
629
+ // lib/SpurGear.tsx
630
+ function createSpurGearGeom(input) {
631
+ return indexedMeshToGeom3(createSpurGearMesh(input));
632
+ }
633
+ function SpurGear({ color = "#737e8f", ...props }) {
634
+ return /* @__PURE__ */ jsx(Colorize, { color, children: /* @__PURE__ */ jsx(Custom, { geometry: createSpurGearGeom(props) }) });
635
+ }
636
+
637
+ // lib/mechanical/worm-gear-mesh.ts
638
+ import {
639
+ getWormGearDimensions,
640
+ wormGearModelPropsSchema
641
+ } from "@tscircuit/modelprinter";
642
+ import earcut3 from "earcut";
643
+ var createWormGearMesh = (input) => {
644
+ const props = wormGearModelPropsSchema.parse(input);
645
+ const { outsideDiameter, rootDiameter, axialPitch, lead, toothThickness } = getWormGearDimensions(props);
646
+ const outsideRadius = outsideDiameter / 2;
647
+ const rootRadius = rootDiameter / 2;
648
+ const pitchRadius = props.pitchDiameter / 2;
649
+ const pressureTangent = Math.tan(props.pressureAngle * Math.PI / 180);
650
+ const tipHalfWidth = toothThickness / 2 - props.module * pressureTangent;
651
+ const rootHalfWidth = toothThickness / 2 + (props.module + props.clearance) * pressureTangent;
652
+ const boreRadius = props.boreDiameter / 2;
653
+ const angularStepLimit = boreRadius > 0 ? Math.acos(boreRadius / rootRadius) * 0.8 : Infinity;
654
+ const samplesPerPitch = Math.max(
655
+ Math.ceil(props.radialSegments / props.starts),
656
+ Math.ceil(Math.PI * 2 / (props.starts * angularStepLimit))
657
+ );
658
+ const stepsPerTurn = Math.max(
659
+ props.segmentsPerTurn,
660
+ props.radialSegments,
661
+ props.starts * 12,
662
+ Math.ceil(Math.PI * 2 / angularStepLimit)
663
+ );
664
+ const steps = Math.max(1, Math.ceil(props.length / lead * stepsPerTurn));
665
+ const extraVertices = boreRadius > 0 ? props.radialSegments * 2 : 2;
666
+ if (!Number.isFinite(samplesPerPitch) || !Number.isFinite(steps) || (steps + 1) * (samplesPerPitch + 6) * props.starts + extraVertices > 1e6) {
667
+ throw new Error(
668
+ "Worm gear exceeds mesh resolution limit (1000000 vertices); reduce length or resolution, or increase module or bore wall thickness"
669
+ );
670
+ }
671
+ const corners = [
672
+ 0,
673
+ tipHalfWidth / axialPitch,
674
+ toothThickness / (2 * axialPitch),
675
+ rootHalfWidth / axialPitch,
676
+ 1 - rootHalfWidth / axialPitch,
677
+ 1 - toothThickness / (2 * axialPitch),
678
+ 1 - tipHalfWidth / axialPitch,
679
+ 1
680
+ ];
681
+ const fractions = [];
682
+ for (let index = 0; index < corners.length - 1; index++) {
683
+ const from = corners[index];
684
+ const width10 = corners[index + 1] - from;
685
+ const subdivisions = Math.max(1, Math.ceil(width10 * samplesPerPitch));
686
+ for (let subdivision = 0; subdivision < subdivisions; subdivision++) {
687
+ const fraction = from + width10 * subdivision / subdivisions;
688
+ if (fractions.length === 0 || fraction - fractions[fractions.length - 1] > 1e-12)
689
+ fractions.push(fraction);
690
+ }
691
+ }
692
+ const profile = [];
693
+ for (let start = 0; start < props.starts; start++) {
694
+ for (const fraction of fractions) {
695
+ const distance2 = Math.min(fraction, 1 - fraction) * axialPitch;
696
+ profile.push({
697
+ angle: (start + fraction) * Math.PI * 2 / props.starts,
698
+ radius: distance2 <= tipHalfWidth ? outsideRadius : distance2 >= rootHalfWidth ? rootRadius : pitchRadius + (toothThickness / 2 - distance2) / pressureTangent
699
+ });
700
+ }
701
+ }
702
+ const count = profile.length;
703
+ const positions = [];
704
+ const indices = [];
705
+ const phase = props.phase % 360 * Math.PI / 180;
706
+ const direction = props.handedness === "right" ? 1 : -1;
707
+ const ring = (z) => {
708
+ const first = positions.length / 3;
709
+ const rotation = phase + direction * (z / lead) * Math.PI * 2;
710
+ for (const sample of profile) {
711
+ const angle = sample.angle + rotation;
712
+ positions.push(
713
+ sample.radius * Math.cos(angle),
714
+ sample.radius * Math.sin(angle),
715
+ z
716
+ );
717
+ }
718
+ return first;
719
+ };
720
+ const connect = (lower, upper) => {
721
+ for (let index = 0; index < count; index++) {
722
+ const next = (index + 1) % count;
723
+ indices.push(
724
+ lower + index,
725
+ lower + next,
726
+ upper + next,
727
+ lower + index,
728
+ upper + next,
729
+ upper + index
730
+ );
731
+ }
732
+ };
733
+ const bottom = ring(0);
734
+ let previous = bottom;
735
+ for (let step = 1; step <= steps; step++) {
736
+ const current = ring(props.length * (step / steps));
737
+ connect(previous, current);
738
+ previous = current;
739
+ }
740
+ const top = previous;
741
+ if (props.boreDiameter === 0) {
742
+ const cap = (boundary, z, upward) => {
743
+ const center = positions.length / 3;
744
+ positions.push(0, 0, z);
745
+ for (let index = 0; index < count; index++) {
746
+ const next = (index + 1) % count;
747
+ indices.push(
748
+ center,
749
+ boundary + (upward ? index : next),
750
+ boundary + (upward ? next : index)
751
+ );
752
+ }
753
+ };
754
+ cap(bottom, 0, false);
755
+ cap(top, props.length, true);
756
+ } else {
757
+ const holeCount = props.radialSegments;
758
+ const boreRing = (z) => {
759
+ const first = positions.length / 3;
760
+ for (let index = 0; index < holeCount; index++) {
761
+ const angle = phase + index * Math.PI * 2 / holeCount;
762
+ positions.push(
763
+ boreRadius * Math.cos(angle),
764
+ boreRadius * Math.sin(angle),
765
+ z
766
+ );
767
+ }
768
+ return first;
769
+ };
770
+ const holeBottom = boreRing(0);
771
+ const holeTop = boreRing(props.length);
772
+ for (let index = 0; index < holeCount; index++) {
773
+ const next = (index + 1) % holeCount;
774
+ indices.push(
775
+ holeBottom + index,
776
+ holeTop + next,
777
+ holeBottom + next,
778
+ holeBottom + index,
779
+ holeTop + index,
780
+ holeTop + next
781
+ );
782
+ }
783
+ const cap = (boundary, hole, upward) => {
784
+ const vertices = Array.from(
785
+ { length: count },
786
+ (_, index) => boundary + index
787
+ );
788
+ vertices.push(
789
+ ...Array.from({ length: holeCount }, (_, index) => hole + index)
790
+ );
791
+ const flat = vertices.flatMap((vertex) => [
792
+ positions[vertex * 3],
793
+ positions[vertex * 3 + 1]
794
+ ]);
795
+ const triangles = earcut3(flat, [count], 2);
796
+ for (let index = 0; index < triangles.length; index += 3) {
797
+ const a = vertices[triangles[index]];
798
+ const b = vertices[triangles[index + 1]];
799
+ const c = vertices[triangles[index + 2]];
800
+ const signedArea = (positions[b * 3] - positions[a * 3]) * (positions[c * 3 + 1] - positions[a * 3 + 1]) - (positions[b * 3 + 1] - positions[a * 3 + 1]) * (positions[c * 3] - positions[a * 3]);
801
+ indices.push(
802
+ a,
803
+ signedArea > 0 === upward ? b : c,
804
+ signedArea > 0 === upward ? c : b
805
+ );
806
+ }
807
+ };
808
+ cap(bottom, holeBottom, false);
809
+ cap(top, holeTop, true);
810
+ }
811
+ return { positions, indices };
812
+ };
813
+
814
+ // lib/WormGear.tsx
815
+ function createWormGearGeom(input) {
816
+ return indexedMeshToGeom3(createWormGearMesh(input));
817
+ }
818
+ function WormGear({ color = "#737e8f", ...props }) {
819
+ return /* @__PURE__ */ jsx(Colorize, { color, children: /* @__PURE__ */ jsx(Custom, { geometry: createWormGearGeom(props) }) });
820
+ }
821
+
474
822
  // lib/NemaMotor.tsx
823
+ import jscad3 from "@jscad/modeling";
824
+
825
+ // lib/utils/nemaMotorWireGeometry.ts
475
826
  import jscad2 from "@jscad/modeling";
827
+ import { getNemaMotorReferencePoints } from "@tscircuit/modelprinter";
476
828
 
477
829
  // lib/utils/nemaMotorParameters.ts
478
830
  import { mp as mp2, nemaMotorModelPropsSchema } from "@tscircuit/modelprinter";
@@ -480,6 +832,66 @@ var resolveNemaMotorProps = nemaMotorModelPropsSchema.parse.bind(
480
832
  nemaMotorModelPropsSchema
481
833
  );
482
834
 
835
+ // lib/utils/nemaMotorWireGeometry.ts
836
+ function createNemaMotorWireGeometry(input) {
837
+ const p = resolveNemaMotorProps(input);
838
+ if (p.wireConnection === "none") return [];
839
+ const { position } = getNemaMotorReferencePoints(p).wireside;
840
+ const angle = p.wireSideAngle * Math.PI / 180;
841
+ const place = (geometry) => jscad2.transforms.translate(
842
+ [position.x, position.y, position.z],
843
+ jscad2.transforms.rotateZ(angle, geometry)
844
+ );
845
+ if (p.wireConnection === "stubs") {
846
+ const colors = [
847
+ "#d53f3f",
848
+ "#2463af",
849
+ "#3b934b",
850
+ "#252830",
851
+ "#e5d049",
852
+ "#eeeeee",
853
+ "#ce7432",
854
+ "#803e91"
855
+ ];
856
+ return Array.from({ length: p.wireCount }, (_, i) => ({
857
+ color: colors[i],
858
+ geometry: place(
859
+ jscad2.transforms.translate(
860
+ [
861
+ (p.wireLength - 0.15) / 2,
862
+ (i - (p.wireCount - 1) / 2) * p.wireDiameter * 1.5,
863
+ 0
864
+ ],
865
+ jscad2.transforms.rotateY(
866
+ Math.PI / 2,
867
+ jscad2.primitives.cylinder({
868
+ height: p.wireLength + 0.15,
869
+ radius: p.wireDiameter / 2,
870
+ segments: 24
871
+ })
872
+ )
873
+ )
874
+ )
875
+ }));
876
+ }
877
+ const shell = jscad2.booleans.subtract(
878
+ jscad2.primitives.cuboid({ size: [6.1, 13.9, 4.5], center: [2.95, 0, 0] }),
879
+ jscad2.primitives.cuboid({ size: [5.5, 12.7, 3.3], center: [3.55, 0, 0] })
880
+ );
881
+ return [
882
+ { color: "#eeeeea", geometry: place(shell) },
883
+ ...Array.from({ length: 6 }, (_, i) => ({
884
+ color: "#b8bec6",
885
+ geometry: place(
886
+ jscad2.primitives.cuboid({
887
+ size: [3.4, 0.5, 0.5],
888
+ center: [1.7, (i - 2.5) * 2, 0]
889
+ })
890
+ )
891
+ }))
892
+ ];
893
+ }
894
+
483
895
  // lib/utils/nemaMotorGeometry.ts
484
896
  var circle = (radius, x = 0, y = 0) => Array.from({ length: 96 }, (_, i) => [
485
897
  x + radius * Math.cos(i * Math.PI / 48),
@@ -579,6 +991,7 @@ function createNemaMotorSections(input) {
579
991
  }
580
992
 
581
993
  // lib/NemaMotor.tsx
994
+ var generalize = jscad3.modifiers.generalize;
582
995
  function NemaMotor({
583
996
  bodyColor = "#242830",
584
997
  capColor = "#aeb5bd",
@@ -587,7 +1000,7 @@ function NemaMotor({
587
1000
  ...props
588
1001
  }) {
589
1002
  const p = resolveNemaMotorProps(props);
590
- const screw = p.backFace === "screws" ? jscad2.transforms.rotateX(
1003
+ const screw = p.backFace === "screws" ? jscad3.transforms.rotateX(
591
1004
  Math.PI,
592
1005
  createHexSocketBoltGeom({
593
1006
  metricSize: p.backFaceScrewSize,
@@ -598,14 +1011,14 @@ function NemaMotor({
598
1011
  const halfPitch = p.backFaceHoleSpacing / 2;
599
1012
  return /* @__PURE__ */ jsxs(Fragment2, { children: [
600
1013
  createNemaMotorSections(p).map((section, index) => {
601
- const outline = jscad2.geometries.geom2.fromPoints(section.outline);
602
- const profile = section.holes.length ? jscad2.booleans.subtract(
1014
+ const outline = jscad3.geometries.geom2.fromPoints(section.outline);
1015
+ const profile = section.holes.length ? jscad3.booleans.subtract(
603
1016
  outline,
604
- ...section.holes.map((h2) => jscad2.geometries.geom2.fromPoints(h2))
1017
+ ...section.holes.map((h2) => jscad3.geometries.geom2.fromPoints(h2))
605
1018
  ) : outline;
606
- const geometry = jscad2.transforms.translate(
1019
+ const geometry = jscad3.transforms.translate(
607
1020
  [0, 0, section.zMin],
608
- jscad2.extrusions.extrudeLinear(
1021
+ jscad3.extrusions.extrudeLinear(
609
1022
  { height: section.zMax - section.zMin },
610
1023
  profile
611
1024
  )
@@ -614,18 +1027,19 @@ function NemaMotor({
614
1027
  Colorize,
615
1028
  {
616
1029
  color: section.name === "body" ? bodyColor : section.name === "shaft" ? shaftColor : capColor,
617
- children: /* @__PURE__ */ jsx(Custom, { geometry })
1030
+ children: /* @__PURE__ */ jsx(Custom, { geometry: generalize({ simplify: true }, geometry) })
618
1031
  },
619
1032
  `${section.name}-${index}`
620
1033
  );
621
1034
  }),
1035
+ createNemaMotorWireGeometry(p).map(({ geometry, color }, index) => /* @__PURE__ */ jsx(Colorize, { color, children: /* @__PURE__ */ jsx(Custom, { geometry: generalize({ simplify: true }, geometry) }) }, `wire-${index}`)),
622
1036
  screw && [-halfPitch, halfPitch].flatMap(
623
1037
  (x) => [-halfPitch, halfPitch].map((y) => /* @__PURE__ */ jsx(Colorize, { color: screwColor, children: /* @__PURE__ */ jsx(
624
1038
  Custom,
625
1039
  {
626
- geometry: jscad2.transforms.translate(
1040
+ geometry: jscad3.transforms.translate(
627
1041
  [x, y, -p.bodyLength],
628
- screw
1042
+ generalize({ simplify: true }, screw)
629
1043
  )
630
1044
  }
631
1045
  ) }, `rear-screw-${x}-${y}`))
@@ -8654,6 +9068,14 @@ var Footprinter3d = ({ footprint }) => {
8654
9068
  const { fn, ...props } = model;
8655
9069
  return /* @__PURE__ */ jsx(SheetMetal, { ...props });
8656
9070
  }
9071
+ case "spurgear": {
9072
+ const { fn, ...props } = model;
9073
+ return /* @__PURE__ */ jsx(SpurGear, { ...props });
9074
+ }
9075
+ case "wormgear": {
9076
+ const { fn, ...props } = model;
9077
+ return /* @__PURE__ */ jsx(WormGear, { ...props });
9078
+ }
8657
9079
  case "flexscreen": {
8658
9080
  const { fn: _, ...flexScreenProps } = model;
8659
9081
  return /* @__PURE__ */ jsx(FlexScreen, { ...flexScreenProps });
@@ -9460,8 +9882,8 @@ function renderNode(node, colorCtx, renderCtx) {
9460
9882
  }
9461
9883
  return (children ?? []).flatMap((c) => renderNode(c, colorCtx, renderCtx));
9462
9884
  }
9463
- function render(root, jscad3) {
9464
- const geometries = renderNode(root, void 0, { jscad: jscad3 });
9885
+ function render(root, jscad4) {
9886
+ const geometries = renderNode(root, void 0, { jscad: jscad4 });
9465
9887
  return { geometries };
9466
9888
  }
9467
9889
 
@@ -9549,24 +9971,24 @@ function convertCSGToThreeGeom(csg) {
9549
9971
  }
9550
9972
 
9551
9973
  // lib/vanilla/index.ts
9552
- function getJscadModelForFootprint(footprint, jscad3) {
9974
+ function getJscadModelForFootprint(footprint, jscad4) {
9553
9975
  const vnode = h(Footprinter3d, { footprint });
9554
- return render(vnode, jscad3);
9976
+ return render(vnode, jscad4);
9555
9977
  }
9556
- function getJscadModelForFootprintWithPads(footprint, jscad3) {
9978
+ function getJscadModelForFootprintWithPads(footprint, jscad4) {
9557
9979
  const vnode = h(
9558
9980
  Fragment,
9559
9981
  {},
9560
9982
  h(Footprinter3d, { footprint }),
9561
9983
  h(ExtrudedPads, { footprint })
9562
9984
  );
9563
- return render(vnode, jscad3);
9985
+ return render(vnode, jscad4);
9564
9986
  }
9565
- function createJSCADRenderer(jscad3) {
9987
+ function createJSCADRenderer(jscad4) {
9566
9988
  function createJSCADRoot(container) {
9567
9989
  return {
9568
9990
  render(element) {
9569
- const { geometries } = render(element, jscad3);
9991
+ const { geometries } = render(element, jscad4);
9570
9992
  container.splice(0, container.length, ...geometries);
9571
9993
  }
9572
9994
  };
@@ -9581,12 +10003,18 @@ export {
9581
10003
  NEMA8,
9582
10004
  NemaMotor,
9583
10005
  SheetMetal,
10006
+ SpurGear,
10007
+ WormGear,
9584
10008
  convertCSGToThreeGeom,
9585
10009
  createHexSocketBoltGeom,
9586
10010
  createHexSocketBoltMesh,
9587
10011
  createJSCADRenderer,
9588
10012
  createSheetMetalGeom,
9589
10013
  createSheetMetalMesh,
10014
+ createSpurGearGeom,
10015
+ createSpurGearMesh,
10016
+ createWormGearGeom,
10017
+ createWormGearMesh,
9590
10018
  getJscadModelForFootprint,
9591
10019
  getJscadModelForFootprintWithPads,
9592
10020
  h