Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions docs/nema-motors.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ These flags are mutually exclusive. Direct props use `backFaceHoles`,
one true flag and false for the other rear-face flags.

Rear fasteners are representative configurable details, not guaranteed by a NEMA
frame number. The square rear pitches default to 16 / 31 / 47.14 / 40 mm,
independently overridden by `backFaceHoleSpacing` / `_backholespacing`.
frame number. Rear patterns default to 16 × 16 / 31 × 31 / 50 × 32 / 40 × 40 mm.
`backFaceHoleSpacing` / `_backholespacing` still sets both axes to a square
pattern when supplied. `backFaceHoleSpacingX` / `_backholespacingx` and
`backFaceHoleSpacingY` / `_backholespacingy` independently override each axis.
NEMA34 follows the rounded, stepped shoulders and plain rear casting visible in
STEPPERONLINE product photos. The rear follows the body profile instead of
repeating the mounting flange. Front assembly screws sit in edge recesses
Expand All @@ -80,13 +82,20 @@ configure the profiles. In stepped mode, the chamfer value divided by sqrt(2)
sets the corner shoulder depth, with fillets up to 4 mm. Default body and rear
values are both 24 mm. Rear bores/screws remain opt-in with an illustrative
40 mm square pitch and can be overridden to match a specific motor.
NEMA8/17/23 retain their existing rear profiles. Front flange holes are unchanged.
NEMA8/17 retain their existing profiles. NEMA23 uses matching curved body/rear
profiles with 9 mm shoulders, R5 recesses and R2.5 fillets; front holes are unchanged.

`rearStepped` / `_rearstepped` and `rearChamfered` / `_rearchamfered` override the
rear profile independently of `stepped` / `chamfered`. Body and rear arc radii
use `bodyCornerRecessRadius`, `bodyCornerFilletRadius`, `rearCornerRecessRadius`
and `rearCornerFilletRadius` (`bodyrecess`, `bodyfillet`, `rearrecess`, `rearfillet`).
`nema23_chamfered_backholespacing47.14mm_backscrewm4` retains the previous depiction.

| Rear default (mm) | NEMA8 | NEMA17 | NEMA23 | NEMA34 |
| --- | --- | --- | --- | --- |
| Screw size | M2 | M3 | M4 | M5 |
| Bore diameter / depth | 2 / 2 | 3 / 4.5 | 4 / 4.5 | 5 / 6 |
| Head diameter / height | 3.8 / 2 | 5.5 / 3 | 7 / 4 | 8.5 / 5 |
| Screw size | M2 | M3 | M3 | M5 |
| Bore diameter / depth | 2 / 2 | 3 / 4.5 | 3 / 4.5 | 5 / 6 |
| Head diameter / height | 3.8 / 2 | 5.5 / 3 | 5.5 / 3 | 8.5 / 5 |

`backFaceScrewSize` / `_backscrewm3` selects the existing ISO 4762 bolt
head/socket dimensions. `backFaceHoleDiameter` / `_backholediameter` and
Expand All @@ -98,7 +107,7 @@ screws larger than the bore, and holes or heads that cross the rear-face edge.

```ts
mp.string("nema17_backfaceholes").json()
mp.string("nema23_backfacescrews_backholespacing40mm_backscrewm3").json()
mp.string("nema23_backfacescrews_backholespacingx48mm_backholespacingy30mm").json()
```

## Wire connection aliases
Expand Down
111 changes: 111 additions & 0 deletions src/nema-motor-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/** Analytic cap/core clearance checks; no renderer or meshes are needed. */
type NemaProfile = {
width: number
chamfer: number
stepped: boolean
recessRadius: number
filletRadius: number
}

export function isInsideNemaFace(x: number, y: number, p: NemaProfile) {
const h = p.width / 2
x = Math.abs(x)
y = Math.abs(y)
if (x > h || y > h) return false
const chamfer = p.chamfer
if (!p.stepped) return x + y <= 2 * h - chamfer
const c = chamfer / Math.SQRT2
if (c === 0) return true
const recess = p.recessRadius
const fillet = p.filletRadius
const k = h - c + recess
if (x > h - c && y > h - c)
return x <= k && y <= k && Math.hypot(x - k, y - k) >= recess
if (y > h - fillet && x > h - c - fillet)
return Math.hypot(x - (h - c - fillet), y - (h - fillet)) <= fillet
if (x > h - fillet && y > h - c - fillet)
return Math.hypot(x - (h - fillet), y - (h - c - fillet)) <= fillet
return true
}

const segmentDistance = (
x: number,
y: number,
ax: number,
ay: number,
bx: number,
by: number,
) => {
const dx = bx - ax
const dy = by - ay
const t = Math.max(
0,
Math.min(1, ((x - ax) * dx + (y - ay) * dy) / (dx * dx + dy * dy || 1)),
)
return Math.hypot(x - ax - t * dx, y - ay - t * dy)
}

const arcDistance = (
x: number,
y: number,
cx: number,
cy: number,
r: number,
start: number,
end: number,
) => {
const angle = (Math.atan2(y - cy, x - cx) + 2 * Math.PI) % (2 * Math.PI)
if (angle >= start && angle <= end)
return Math.abs(Math.hypot(x - cx, y - cy) - r)
return Math.min(
Math.hypot(x - cx - r * Math.cos(start), y - cy - r * Math.sin(start)),
Math.hypot(x - cx - r * Math.cos(end), y - cy - r * Math.sin(end)),
)
}

const boundaryDistance = (x: number, y: number, p: NemaProfile) => {
const h = p.width / 2
x = Math.abs(x)
y = Math.abs(y)
const chamfer = p.chamfer
if (!p.stepped || chamfer === 0)
return Math.min(
Math.abs(h - x),
Math.abs(h - y),
Math.abs(2 * h - chamfer - x - y) / Math.SQRT2,
)
const c = chamfer / Math.SQRT2
const recess = p.recessRadius
const fillet = p.filletRadius
const a = h - c - fillet
const k = h - c + recess
return Math.min(
segmentDistance(x, y, -a, h, a, h),
segmentDistance(x, y, h, -a, h, a),
segmentDistance(x, y, h - c, h - fillet, h - c, k),
segmentDistance(x, y, k, h - c, h - fillet, h - c),
arcDistance(x, y, a, h - fillet, fillet, 0, Math.PI / 2),
arcDistance(x, y, k, k, recess, Math.PI, (3 * Math.PI) / 2),
arcDistance(x, y, h - fillet, a, fillet, 0, Math.PI / 2),
)
}

/** Reject a bore/head that touches or crosses a straight edge or either kind of arc. */
export function fitsNemaCircle(
x: number,
y: number,
radius: number,
p: NemaProfile,
) {
return isInsideNemaFace(x, y, p) && boundaryDistance(x, y, p) > radius
}

/** A through mounting bore must remain entirely outside the core profile. */
export function clearsNemaCircle(
x: number,
y: number,
radius: number,
p: NemaProfile,
) {
return !isInsideNemaFace(x, y, p) && boundaryDistance(x, y, p) > radius
}
41 changes: 19 additions & 22 deletions src/nema-motor-reference-points.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
nemaMotorModelPropsSchema,
type NemaMotorModelPropsInput,
} from "./nema-motor-schema"
import { isInsideNemaFace } from "./nema-motor-profile"

export interface NemaMotorReferencePoint {
position: { x: number; y: number; z: number }
Expand All @@ -22,32 +23,28 @@ export function getNemaMotorReferencePoints(input: NemaMotorModelPropsInput) {
p.bodyWidth / 2 / Math.max(Math.abs(x), Math.abs(y)),
(p.bodyWidth - p.faceCornerChamfer) / (Math.abs(x) + Math.abs(y)),
)
if (p.nemaSize === 34 && p.stepped) {
const h = p.bodyWidth / 2,
c = p.bodyCornerChamfer / Math.SQRT2,
r = Math.min(4, c / 4)
const contains = (distance: number) => {
const a = Math.abs(distance * x),
b = Math.abs(distance * y)
if (a > h || b > h) return false
if (c === 0) return true
if (a > h - c && b > h - c)
return (
a <= h - c + r &&
b <= h - c + r &&
Math.hypot(a - (h - c + r), b - (h - c + r)) >= r
)
if (b > h - r && a > h - c - r)
return Math.hypot(a - (h - c - r), b - (h - r)) <= r
if (a > h - r && b > h - c - r)
return Math.hypot(a - (h - r), b - (h - c - r)) <= r
return true
}
if (p.nemaSize !== 34 || p.stepped) {
const profile =
p.nemaSize === 34
? {
width: p.bodyWidth,
chamfer: p.bodyCornerChamfer,
stepped: p.stepped,
recessRadius: p.bodyCornerRecessRadius,
filletRadius: p.bodyCornerFilletRadius,
}
: {
width: p.bodyWidth,
chamfer: p.rearCornerChamfer,
stepped: p.rearStepped,
recessRadius: p.rearCornerRecessRadius,
filletRadius: p.rearCornerFilletRadius,
}
let low = 0,
high = p.bodyWidth
for (let i = 0; i < 50; i++) {
const mid = (low + high) / 2
if (contains(mid)) low = mid
if (isInsideNemaFace(mid * x, mid * y, profile)) low = mid
else high = mid
}
radius = low
Expand Down
Loading
Loading