jscad-electronics 0.0.184 → 0.0.186

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, HelicalGearModelPropsInput, 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,62 @@ 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 HelicalGearMesh = SpurGearMesh;
97
+ /**
98
+ * Twisted transverse involute teeth from Z=0 to faceWidth, with a stationary
99
+ * round bore and optional hub. Zero helix angle reproduces the spur mesh.
100
+ */
101
+ declare function createHelicalGearMesh(input: HelicalGearModelPropsInput): HelicalGearMesh;
102
+
103
+ type HelicalGearProps = HelicalGearModelPropsInput & {
104
+ color?: string;
105
+ };
106
+ /** Axis +Z, lower face Z=0; optional hub extends above the gear face. */
107
+ declare function createHelicalGearGeom(input: HelicalGearModelPropsInput): _jscad_modeling_src_geometries_types.Geom3;
108
+ declare function HelicalGear({ color, ...props }: HelicalGearProps): react_jsx_runtime.JSX.Element;
109
+
110
+ type SpurGearProps = SpurGearModelPropsInput & {
111
+ color?: string;
112
+ };
113
+ /** Axis +Z, lower face Z=0; optional hub extends above the gear face. */
114
+ declare function createSpurGearGeom(input: SpurGearModelPropsInput): _jscad_modeling_src_geometries_types.Geom3;
115
+ declare function SpurGear({ color, ...props }: SpurGearProps): react_jsx_runtime.JSX.Element;
116
+
117
+ /** Indexed triangles, counterclockwise from outside; millimeters, Z up. */
118
+ interface WormGearMesh {
119
+ positions: number[];
120
+ indices: number[];
121
+ }
122
+ /**
123
+ * A cylindrical worm from Z=0 to Z=length, with square-cut ends.
124
+ * The axial trapezoidal rack profile is swept around +Z. A right-hand crest
125
+ * winds counterclockwise as Z increases; phase rotates the entire solid.
126
+ * This is a faceted visual worm, not a hobbed wheel or a conjugate gear pair.
127
+ */
128
+ declare const createWormGearMesh: (input: WormGearModelPropsInput) => WormGearMesh;
129
+
130
+ type WormGearProps = WormGearModelPropsInput & {
131
+ color?: string;
132
+ };
133
+ /** Square-cut worm along +Z, from Z=0 to length. Threads are approximate. */
134
+ declare function createWormGearGeom(input: WormGearModelPropsInput): _jscad_modeling_src_geometries_types.Geom3;
135
+ declare function WormGear({ color, ...props }: WormGearProps): react_jsx_runtime.JSX.Element;
136
+
81
137
  declare function getJscadModelForFootprint(footprint: string, jscad: typeof jscadModeling): RenderResult;
82
138
  declare function getJscadModelForFootprintWithPads(footprint: string, jscad: typeof jscadModeling): RenderResult;
83
139
  declare function createJSCADRenderer(jscad: typeof jscadModeling): {
@@ -86,4 +142,4 @@ declare function createJSCADRenderer(jscad: typeof jscadModeling): {
86
142
  };
87
143
  };
88
144
 
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 };
145
+ export { type ColoredGeom, Fragment, HelicalGear, type HelicalGearMesh, type HelicalGearProps, 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, createHelicalGearGeom, createHelicalGearMesh, createHexSocketBoltGeom, createHexSocketBoltMesh, createJSCADRenderer, createSheetMetalGeom, createSheetMetalMesh, createSpurGearGeom, createSpurGearMesh, createWormGearGeom, createWormGearMesh, getJscadModelForFootprint, getJscadModelForFootprintWithPads, h };
package/dist/vanilla.js CHANGED
@@ -172,9 +172,14 @@ var ExtrudedPads = ({
172
172
  circuitJson,
173
173
  footprint
174
174
  }) => {
175
- if (!circuitJson && footprint && ["nema", "hexsocketbolt", "sheetmetal"].includes(
176
- mp.string(footprint.split("_", 1)[0]).params().fn
177
- )) {
175
+ if (!circuitJson && footprint && [
176
+ "nema",
177
+ "hexsocketbolt",
178
+ "sheetmetal",
179
+ "spurgear",
180
+ "wormgear",
181
+ "helicalgear"
182
+ ].includes(mp.string(footprint.split("_", 1)[0]).params().fn)) {
178
183
  mp.string(footprint).json();
179
184
  return null;
180
185
  }
@@ -201,6 +206,220 @@ var ExtrudedPads = ({
201
206
  ] });
202
207
  };
203
208
 
209
+ // lib/mechanical/indexedMeshToGeom3.ts
210
+ import jscad from "@jscad/modeling";
211
+ function indexedMeshToGeom3(mesh) {
212
+ const polygons = [];
213
+ for (let i = 0; i < mesh.indices.length; i += 3)
214
+ polygons.push(
215
+ mesh.indices.slice(i, i + 3).map((index) => [
216
+ mesh.positions[index * 3],
217
+ mesh.positions[index * 3 + 1],
218
+ mesh.positions[index * 3 + 2]
219
+ ])
220
+ );
221
+ return jscad.geometries.geom3.fromPoints(polygons);
222
+ }
223
+
224
+ // lib/mechanical/helical-gear-mesh.ts
225
+ import {
226
+ getHelicalGearDimensions,
227
+ helicalGearModelPropsSchema
228
+ } from "@tscircuit/modelprinter";
229
+
230
+ // lib/mechanical/spur-gear-mesh.ts
231
+ import {
232
+ getSpurGearDimensions,
233
+ spurGearModelPropsSchema
234
+ } from "@tscircuit/modelprinter";
235
+ import earcut from "earcut";
236
+ function createSpurGearMesh(input) {
237
+ return createTwistedSpurGearMesh(input);
238
+ }
239
+ function createTwistedSpurGearMesh(input, twist = 0, segmentsPerTurn = 32) {
240
+ const props = spurGearModelPropsSchema.parse(input);
241
+ const dimensions = getSpurGearDimensions(props);
242
+ const pitchRadius = dimensions.pitchDiameter / 2;
243
+ const baseRadius = dimensions.baseDiameter / 2;
244
+ const outsideRadius = dimensions.outsideDiameter / 2;
245
+ const rootRadius = dimensions.rootDiameter / 2;
246
+ const pressureAngle = props.pressureAngle * Math.PI / 180;
247
+ const toothAngle = Math.PI * 2 / props.toothCount;
248
+ const phase = (props.phase % 360 + 360) % 360 * (Math.PI / 180);
249
+ const baseHalfAngle = dimensions.toothThickness / (2 * pitchRadius) + Math.tan(pressureAngle) - pressureAngle;
250
+ const startRadius = Math.max(baseRadius, rootRadius);
251
+ const startT = Math.sqrt(Math.max(0, (startRadius / baseRadius) ** 2 - 1));
252
+ const outsideT = Math.sqrt((outsideRadius / baseRadius) ** 2 - 1);
253
+ const halfAngle = (t) => baseHalfAngle - (t - Math.atan(t));
254
+ const rootHalfAngle = halfAngle(startT);
255
+ const tipHalfAngle = halfAngle(outsideT);
256
+ const flankSteps = Math.max(2, Math.ceil(props.segmentsPerTooth / 2));
257
+ const arcSteps = Math.max(1, Math.ceil(props.segmentsPerTooth / 4));
258
+ const rootValleyAngle = toothAngle - 2 * rootHalfAngle;
259
+ const innerRadius = Math.max(props.boreDiameter, props.hubDiameter) / 2;
260
+ const safeRootStep = innerRadius > 0 ? (twist === 0 ? 1.8 : 0.8) * Math.acos(innerRadius / rootRadius) : Math.PI;
261
+ const rootArcSteps = Math.max(
262
+ arcSteps,
263
+ Math.ceil(rootValleyAngle / safeRootStep)
264
+ );
265
+ const twistStep = Math.min(
266
+ toothAngle / 16,
267
+ 2 * Math.PI / segmentsPerTurn,
268
+ safeRootStep
269
+ );
270
+ const layers = Math.max(1, Math.ceil(Math.abs(twist) / twistStep));
271
+ const pointsPerTooth = 2 * flankSteps + arcSteps + rootArcSteps + (rootRadius < baseRadius ? 2 : 0);
272
+ if (!Number.isFinite(rootArcSteps) || !Number.isFinite(layers) || (layers + 1) * props.toothCount * pointsPerTooth + 1024 > 1e6)
273
+ throw new Error(
274
+ "Involute gear exceeds mesh resolution limit (1 million vertices); reduce twist or resolution, or increase bore/hub wall thickness"
275
+ );
276
+ const outline = [];
277
+ const polar = (radius, angle) => {
278
+ outline.push([radius * Math.cos(angle), radius * Math.sin(angle)]);
279
+ };
280
+ for (let tooth = 0; tooth < props.toothCount; tooth++) {
281
+ const center = phase + tooth * toothAngle;
282
+ polar(rootRadius, center - rootHalfAngle);
283
+ if (rootRadius < baseRadius) polar(baseRadius, center - baseHalfAngle);
284
+ for (let step = 1; step <= flankSteps; step++) {
285
+ const t = startT + (outsideT - startT) * step / flankSteps;
286
+ polar(baseRadius * Math.sqrt(1 + t * t), center - halfAngle(t));
287
+ }
288
+ for (let step = 1; step <= arcSteps; step++)
289
+ polar(
290
+ outsideRadius,
291
+ center - tipHalfAngle + 2 * tipHalfAngle * step / arcSteps
292
+ );
293
+ for (let step = flankSteps - 1; step >= 0; step--) {
294
+ const t = startT + (outsideT - startT) * step / flankSteps;
295
+ polar(baseRadius * Math.sqrt(1 + t * t), center + halfAngle(t));
296
+ }
297
+ if (rootRadius < baseRadius) polar(rootRadius, center + baseHalfAngle);
298
+ for (let step = 1; step < rootArcSteps; step++)
299
+ polar(
300
+ rootRadius,
301
+ center + rootHalfAngle + rootValleyAngle * step / rootArcSteps
302
+ );
303
+ }
304
+ const positions = [];
305
+ const indices = [];
306
+ const ring = (points, z) => {
307
+ const start = positions.length / 3;
308
+ for (const [x, y] of points) positions.push(x, y, z);
309
+ return start;
310
+ };
311
+ const circleSegments = Math.max(48, Math.min(256, props.toothCount * 4));
312
+ const circle2 = (radius) => Array.from({ length: circleSegments }, (_, segment) => {
313
+ const angle = phase + segment * Math.PI * 2 / circleSegments;
314
+ return [radius * Math.cos(angle), radius * Math.sin(angle)];
315
+ });
316
+ const connect = (bottom2, top2, count, inward = false) => {
317
+ for (let i = 0; i < count; i++) {
318
+ const next = (i + 1) % count;
319
+ if (inward)
320
+ indices.push(
321
+ bottom2 + i,
322
+ top2 + next,
323
+ bottom2 + next,
324
+ bottom2 + i,
325
+ top2 + i,
326
+ top2 + next
327
+ );
328
+ else
329
+ indices.push(
330
+ bottom2 + i,
331
+ bottom2 + next,
332
+ top2 + next,
333
+ bottom2 + i,
334
+ top2 + next,
335
+ top2 + i
336
+ );
337
+ }
338
+ };
339
+ const cap = (outer, outerStart, upward, inner) => {
340
+ const points = inner ? [...outer, ...inner.points] : outer;
341
+ const triangles = earcut(
342
+ points.flatMap(([x, y]) => [x, y]),
343
+ inner ? [outer.length] : []
344
+ );
345
+ const vertex = (index) => index < outer.length ? outerStart + index : inner.start + index - outer.length;
346
+ for (let i = 0; i < triangles.length; i += 3) {
347
+ const a = triangles[i];
348
+ const b = triangles[i + 1];
349
+ const c = triangles[i + 2];
350
+ const pa = points[a];
351
+ const pb = points[b];
352
+ const pc = points[c];
353
+ const area = (pb[0] - pa[0]) * (pc[1] - pa[1]) - (pb[1] - pa[1]) * (pc[0] - pa[0]);
354
+ if (area > 0 === upward) indices.push(vertex(a), vertex(b), vertex(c));
355
+ else indices.push(vertex(a), vertex(c), vertex(b));
356
+ }
357
+ };
358
+ const bottom = ring(outline, 0);
359
+ let top = bottom;
360
+ let topOutline = outline;
361
+ for (let layer = 1; layer <= layers; layer++) {
362
+ const angle = twist * (layer / layers);
363
+ topOutline = twist === 0 ? outline : outline.map(
364
+ ([x, y]) => [
365
+ x * Math.cos(angle) - y * Math.sin(angle),
366
+ x * Math.sin(angle) + y * Math.cos(angle)
367
+ ]
368
+ );
369
+ const next = ring(topOutline, props.faceWidth * (layer / layers));
370
+ connect(top, next, outline.length);
371
+ top = next;
372
+ }
373
+ const hasHub = props.hubDiameter > 0 && props.hubLength > 0;
374
+ const height10 = props.faceWidth + (hasHub ? props.hubLength : 0);
375
+ const bore = props.boreDiameter > 0 ? circle2(props.boreDiameter / 2) : void 0;
376
+ const boreBottom = bore ? ring(bore, 0) : void 0;
377
+ const boreTop = bore ? ring(bore, height10) : void 0;
378
+ if (bore) connect(boreBottom, boreTop, bore.length, true);
379
+ cap(
380
+ outline,
381
+ bottom,
382
+ false,
383
+ bore ? { points: bore, start: boreBottom } : void 0
384
+ );
385
+ if (hasHub) {
386
+ const hub = circle2(props.hubDiameter / 2);
387
+ const hubBottom = ring(hub, props.faceWidth);
388
+ const hubTop = ring(hub, height10);
389
+ cap(topOutline, top, true, { points: hub, start: hubBottom });
390
+ connect(hubBottom, hubTop, hub.length);
391
+ if (bore) connect(hubTop, boreTop, hub.length);
392
+ else cap(hub, hubTop, true);
393
+ } else
394
+ cap(
395
+ topOutline,
396
+ top,
397
+ true,
398
+ bore ? { points: bore, start: boreTop } : void 0
399
+ );
400
+ return { positions, indices };
401
+ }
402
+
403
+ // lib/mechanical/helical-gear-mesh.ts
404
+ function createHelicalGearMesh(input) {
405
+ const props = helicalGearModelPropsSchema.parse(input);
406
+ const { twistAngle } = getHelicalGearDimensions(props);
407
+ const { helixAngle, handedness, segmentsPerTurn, ...spur } = props;
408
+ return createTwistedSpurGearMesh(
409
+ spur,
410
+ twistAngle * Math.PI / 180,
411
+ segmentsPerTurn
412
+ );
413
+ }
414
+
415
+ // lib/HelicalGear.tsx
416
+ function createHelicalGearGeom(input) {
417
+ return indexedMeshToGeom3(createHelicalGearMesh(input));
418
+ }
419
+ function HelicalGear({ color = "#737e8f", ...props }) {
420
+ return /* @__PURE__ */ jsx(Colorize, { color, children: /* @__PURE__ */ jsx(Custom, { geometry: createHelicalGearGeom(props) }) });
421
+ }
422
+
204
423
  // lib/mechanical/hex-socket-bolt-mesh.ts
205
424
  import {
206
425
  hexSocketBoltDimensions,
@@ -295,21 +514,6 @@ var createHexSocketBoltMesh = (input) => {
295
514
  return { positions, indices };
296
515
  };
297
516
 
298
- // lib/mechanical/indexedMeshToGeom3.ts
299
- import jscad from "@jscad/modeling";
300
- function indexedMeshToGeom3(mesh) {
301
- const polygons = [];
302
- for (let i = 0; i < mesh.indices.length; i += 3)
303
- polygons.push(
304
- mesh.indices.slice(i, i + 3).map((index) => [
305
- mesh.positions[index * 3],
306
- mesh.positions[index * 3 + 1],
307
- mesh.positions[index * 3 + 2]
308
- ])
309
- );
310
- return jscad.geometries.geom3.fromPoints(polygons);
311
- }
312
-
313
517
  // lib/HexSocketBolt.tsx
314
518
  function createHexSocketBoltGeom(input) {
315
519
  return indexedMeshToGeom3(createHexSocketBoltMesh(input));
@@ -322,7 +526,7 @@ function HexSocketBolt({
322
526
  }
323
527
 
324
528
  // lib/mechanical/sheet-metal-mesh.ts
325
- import earcut from "earcut";
529
+ import earcut2 from "earcut";
326
530
  import {
327
531
  sheetMetalModelPropsSchema
328
532
  } from "@tscircuit/modelprinter";
@@ -390,7 +594,7 @@ function createSheetMetalMesh(input) {
390
594
  const loops = [outline, ...holes.map((hole) => [...hole].reverse())];
391
595
  const points = loops.flat();
392
596
  const starts = holes.map((_, i) => 4 + i * 64);
393
- const triangles = earcut(
597
+ const triangles = earcut2(
394
598
  points.flatMap((p) => [p.u, p.v]),
395
599
  starts
396
600
  );
@@ -474,6 +678,199 @@ function SheetMetal({ color = "#737e8f", ...props }) {
474
678
  return /* @__PURE__ */ jsx(Colorize, { color, children: /* @__PURE__ */ jsx(Custom, { geometry: createSheetMetalGeom(props) }) });
475
679
  }
476
680
 
681
+ // lib/SpurGear.tsx
682
+ function createSpurGearGeom(input) {
683
+ return indexedMeshToGeom3(createSpurGearMesh(input));
684
+ }
685
+ function SpurGear({ color = "#737e8f", ...props }) {
686
+ return /* @__PURE__ */ jsx(Colorize, { color, children: /* @__PURE__ */ jsx(Custom, { geometry: createSpurGearGeom(props) }) });
687
+ }
688
+
689
+ // lib/mechanical/worm-gear-mesh.ts
690
+ import {
691
+ getWormGearDimensions,
692
+ wormGearModelPropsSchema
693
+ } from "@tscircuit/modelprinter";
694
+ import earcut3 from "earcut";
695
+ var createWormGearMesh = (input) => {
696
+ const props = wormGearModelPropsSchema.parse(input);
697
+ const { outsideDiameter, rootDiameter, axialPitch, lead, toothThickness } = getWormGearDimensions(props);
698
+ const outsideRadius = outsideDiameter / 2;
699
+ const rootRadius = rootDiameter / 2;
700
+ const pitchRadius = props.pitchDiameter / 2;
701
+ const pressureTangent = Math.tan(props.pressureAngle * Math.PI / 180);
702
+ const tipHalfWidth = toothThickness / 2 - props.module * pressureTangent;
703
+ const rootHalfWidth = toothThickness / 2 + (props.module + props.clearance) * pressureTangent;
704
+ const boreRadius = props.boreDiameter / 2;
705
+ const angularStepLimit = boreRadius > 0 ? Math.acos(boreRadius / rootRadius) * 0.8 : Infinity;
706
+ const samplesPerPitch = Math.max(
707
+ Math.ceil(props.radialSegments / props.starts),
708
+ Math.ceil(Math.PI * 2 / (props.starts * angularStepLimit))
709
+ );
710
+ const stepsPerTurn = Math.max(
711
+ props.segmentsPerTurn,
712
+ props.radialSegments,
713
+ props.starts * 12,
714
+ Math.ceil(Math.PI * 2 / angularStepLimit)
715
+ );
716
+ const steps = Math.max(1, Math.ceil(props.length / lead * stepsPerTurn));
717
+ const extraVertices = boreRadius > 0 ? props.radialSegments * 2 : 2;
718
+ if (!Number.isFinite(samplesPerPitch) || !Number.isFinite(steps) || (steps + 1) * (samplesPerPitch + 6) * props.starts + extraVertices > 1e6) {
719
+ throw new Error(
720
+ "Worm gear exceeds mesh resolution limit (1000000 vertices); reduce length or resolution, or increase module or bore wall thickness"
721
+ );
722
+ }
723
+ const corners = [
724
+ 0,
725
+ tipHalfWidth / axialPitch,
726
+ toothThickness / (2 * axialPitch),
727
+ rootHalfWidth / axialPitch,
728
+ 1 - rootHalfWidth / axialPitch,
729
+ 1 - toothThickness / (2 * axialPitch),
730
+ 1 - tipHalfWidth / axialPitch,
731
+ 1
732
+ ];
733
+ const fractions = [];
734
+ for (let index = 0; index < corners.length - 1; index++) {
735
+ const from = corners[index];
736
+ const width10 = corners[index + 1] - from;
737
+ const subdivisions = Math.max(1, Math.ceil(width10 * samplesPerPitch));
738
+ for (let subdivision = 0; subdivision < subdivisions; subdivision++) {
739
+ const fraction = from + width10 * subdivision / subdivisions;
740
+ if (fractions.length === 0 || fraction - fractions[fractions.length - 1] > 1e-12)
741
+ fractions.push(fraction);
742
+ }
743
+ }
744
+ const profile = [];
745
+ for (let start = 0; start < props.starts; start++) {
746
+ for (const fraction of fractions) {
747
+ const distance2 = Math.min(fraction, 1 - fraction) * axialPitch;
748
+ profile.push({
749
+ angle: (start + fraction) * Math.PI * 2 / props.starts,
750
+ radius: distance2 <= tipHalfWidth ? outsideRadius : distance2 >= rootHalfWidth ? rootRadius : pitchRadius + (toothThickness / 2 - distance2) / pressureTangent
751
+ });
752
+ }
753
+ }
754
+ const count = profile.length;
755
+ const positions = [];
756
+ const indices = [];
757
+ const phase = props.phase % 360 * Math.PI / 180;
758
+ const direction = props.handedness === "right" ? 1 : -1;
759
+ const ring = (z) => {
760
+ const first = positions.length / 3;
761
+ const rotation = phase + direction * (z / lead) * Math.PI * 2;
762
+ for (const sample of profile) {
763
+ const angle = sample.angle + rotation;
764
+ positions.push(
765
+ sample.radius * Math.cos(angle),
766
+ sample.radius * Math.sin(angle),
767
+ z
768
+ );
769
+ }
770
+ return first;
771
+ };
772
+ const connect = (lower, upper) => {
773
+ for (let index = 0; index < count; index++) {
774
+ const next = (index + 1) % count;
775
+ indices.push(
776
+ lower + index,
777
+ lower + next,
778
+ upper + next,
779
+ lower + index,
780
+ upper + next,
781
+ upper + index
782
+ );
783
+ }
784
+ };
785
+ const bottom = ring(0);
786
+ let previous = bottom;
787
+ for (let step = 1; step <= steps; step++) {
788
+ const current = ring(props.length * (step / steps));
789
+ connect(previous, current);
790
+ previous = current;
791
+ }
792
+ const top = previous;
793
+ if (props.boreDiameter === 0) {
794
+ const cap = (boundary, z, upward) => {
795
+ const center = positions.length / 3;
796
+ positions.push(0, 0, z);
797
+ for (let index = 0; index < count; index++) {
798
+ const next = (index + 1) % count;
799
+ indices.push(
800
+ center,
801
+ boundary + (upward ? index : next),
802
+ boundary + (upward ? next : index)
803
+ );
804
+ }
805
+ };
806
+ cap(bottom, 0, false);
807
+ cap(top, props.length, true);
808
+ } else {
809
+ const holeCount = props.radialSegments;
810
+ const boreRing = (z) => {
811
+ const first = positions.length / 3;
812
+ for (let index = 0; index < holeCount; index++) {
813
+ const angle = phase + index * Math.PI * 2 / holeCount;
814
+ positions.push(
815
+ boreRadius * Math.cos(angle),
816
+ boreRadius * Math.sin(angle),
817
+ z
818
+ );
819
+ }
820
+ return first;
821
+ };
822
+ const holeBottom = boreRing(0);
823
+ const holeTop = boreRing(props.length);
824
+ for (let index = 0; index < holeCount; index++) {
825
+ const next = (index + 1) % holeCount;
826
+ indices.push(
827
+ holeBottom + index,
828
+ holeTop + next,
829
+ holeBottom + next,
830
+ holeBottom + index,
831
+ holeTop + index,
832
+ holeTop + next
833
+ );
834
+ }
835
+ const cap = (boundary, hole, upward) => {
836
+ const vertices = Array.from(
837
+ { length: count },
838
+ (_, index) => boundary + index
839
+ );
840
+ vertices.push(
841
+ ...Array.from({ length: holeCount }, (_, index) => hole + index)
842
+ );
843
+ const flat = vertices.flatMap((vertex) => [
844
+ positions[vertex * 3],
845
+ positions[vertex * 3 + 1]
846
+ ]);
847
+ const triangles = earcut3(flat, [count], 2);
848
+ for (let index = 0; index < triangles.length; index += 3) {
849
+ const a = vertices[triangles[index]];
850
+ const b = vertices[triangles[index + 1]];
851
+ const c = vertices[triangles[index + 2]];
852
+ 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]);
853
+ indices.push(
854
+ a,
855
+ signedArea > 0 === upward ? b : c,
856
+ signedArea > 0 === upward ? c : b
857
+ );
858
+ }
859
+ };
860
+ cap(bottom, holeBottom, false);
861
+ cap(top, holeTop, true);
862
+ }
863
+ return { positions, indices };
864
+ };
865
+
866
+ // lib/WormGear.tsx
867
+ function createWormGearGeom(input) {
868
+ return indexedMeshToGeom3(createWormGearMesh(input));
869
+ }
870
+ function WormGear({ color = "#737e8f", ...props }) {
871
+ return /* @__PURE__ */ jsx(Colorize, { color, children: /* @__PURE__ */ jsx(Custom, { geometry: createWormGearGeom(props) }) });
872
+ }
873
+
477
874
  // lib/NemaMotor.tsx
478
875
  import jscad3 from "@jscad/modeling";
479
876
 
@@ -8723,6 +9120,18 @@ var Footprinter3d = ({ footprint }) => {
8723
9120
  const { fn, ...props } = model;
8724
9121
  return /* @__PURE__ */ jsx(SheetMetal, { ...props });
8725
9122
  }
9123
+ case "helicalgear": {
9124
+ const { fn, ...props } = model;
9125
+ return /* @__PURE__ */ jsx(HelicalGear, { ...props });
9126
+ }
9127
+ case "spurgear": {
9128
+ const { fn, ...props } = model;
9129
+ return /* @__PURE__ */ jsx(SpurGear, { ...props });
9130
+ }
9131
+ case "wormgear": {
9132
+ const { fn, ...props } = model;
9133
+ return /* @__PURE__ */ jsx(WormGear, { ...props });
9134
+ }
8726
9135
  case "flexscreen": {
8727
9136
  const { fn: _, ...flexScreenProps } = model;
8728
9137
  return /* @__PURE__ */ jsx(FlexScreen, { ...flexScreenProps });
@@ -9644,18 +10053,27 @@ function createJSCADRenderer(jscad4) {
9644
10053
  }
9645
10054
  export {
9646
10055
  Fragment,
10056
+ HelicalGear,
9647
10057
  HexSocketBolt,
9648
10058
  NEMA17,
9649
10059
  NEMA23,
9650
10060
  NEMA8,
9651
10061
  NemaMotor,
9652
10062
  SheetMetal,
10063
+ SpurGear,
10064
+ WormGear,
9653
10065
  convertCSGToThreeGeom,
10066
+ createHelicalGearGeom,
10067
+ createHelicalGearMesh,
9654
10068
  createHexSocketBoltGeom,
9655
10069
  createHexSocketBoltMesh,
9656
10070
  createJSCADRenderer,
9657
10071
  createSheetMetalGeom,
9658
10072
  createSheetMetalMesh,
10073
+ createSpurGearGeom,
10074
+ createSpurGearMesh,
10075
+ createWormGearGeom,
10076
+ createWormGearMesh,
9659
10077
  getJscadModelForFootprint,
9660
10078
  getJscadModelForFootprintWithPads,
9661
10079
  h