Motor seat and coupler

This commit is contained in:
Leni Aniva 2025-05-29 23:40:12 -07:00
parent 2d6d65235d
commit e94546b017
Signed by: aniva
GPG Key ID: D5F96287843E8DFB
1 changed files with 369 additions and 85 deletions

View File

@ -3,10 +3,23 @@ from nhf.materials import Role, Material
import nhf.utils
from nhf.parts.fasteners import FlatHeadBolt, HexNut, Washer
from typing import Optional, Union
import math
from dataclasses import dataclass, field
import cadquery as Cq
def has_part(li: Optional[list[str]], name: Union[str, list[str]]) -> bool:
"""
Check if a part exists in a name list
"""
if li:
if isinstance(name, list):
return all(n in li for n in name)
else:
return name in li
else:
return True
NUT_COMMON = HexNut(
# FIXME: measure
mass=0.0,
@ -227,6 +240,8 @@ class Onbashira(Model):
barrel_diam: float = 25.4 * 1.5
barrel_wall_thickness: float = 25.4 / 8
barrel_length: float = 25.4 * 12
# Longitudinal shift
barrel_shift: float = -20.0
# Gap between the stator edge and the inner face of the barrel
stator_gap: float = 3.0
@ -267,6 +282,16 @@ class Onbashira(Model):
flange_coupler: FlangeCoupler = FlangeCoupler()
auxiliary_thickness: float = 25.4 / 8
# Distance between bind point and motor's mount points
motor_driver_radius: float = 110.0
motor_seat_depth: float = 95.0
motor_seat_radius: float = 45.0
motor_coupler_flange_thickness: float = 10.0
motor_coupler_flange_radius: float = 8.0
motor_coupler_height: float = 100.0
motor_coupler_conn_dx: float = 30.0
motor_coupler_wall_thickness: float = 5.0
motor_coupler_inner_gap: float = 1.0
turning_bar_width: float = 15.0
electronic_mount_dx: float = 50.0
@ -365,29 +390,70 @@ class Onbashira(Model):
.extrude(self.side_width * 1.5)
)
@target(name="motor-driver-shaft")
def motor_driver_shaft(self) -> Cq.Workplane:
@target(name="motor-coupler")
def motor_coupler(self) -> Cq.Workplane:
"""
Driver shaft which connects to each barrel to move them.
The driver shaft reaches
Coupler which connects to each barrel to move them.
"""
return (
x = self.motor_coupler_conn_dx
y0 = self.barrel_diam/2 + self.motor_coupler_wall_thickness
y = self.motor_coupler_flange_radius
t = self.motor_coupler_flange_thickness
flange = (
Cq.Workplane()
.sketch()
.polygon([
(x, y),
(0, y0),
(-x, y),
(-x, -y),
(0, -y0),
(x, -y),
])
.reset()
.push([
(x, 0), (-x, 0)
])
.circle(y, mode="a")
.circle(BOLT_BEARING.diam_thread/2, mode="s")
.reset()
.circle(self.barrel_diam/2, mode="s")
.finalize()
.extrude(t)
)
body = (
Cq.Workplane()
.cylinder(
radius=self.barrel_diam/2,
height=20.0
radius=self.barrel_diam/2 + self.motor_coupler_wall_thickness,
height=self.motor_coupler_height,
centered=(True, True, False)
)
.faces(">Z")
.hole(self.barrel_diam + self.motor_coupler_inner_gap*2)
)
result = body + flange
result.tagAbsolute("holeT1", (x, 0, t), direction="+Z")
result.tagAbsolute("holeT2", (-x, 0, t), direction="+Z")
result.tagAbsolute("holeB1", (x, 0, 0), direction="-Z")
result.tagAbsolute("holeB2", (-x, 0, 0), direction="-Z")
return result
@target(name="motor-driver-disk", kind=TargetKind.DXF)
def profile_motor_driver_disk(self) -> Cq.Sketch:
"""
A drive disk mounts onto the motor, and extends into gun barrels to turn them.
"""
hole_diam = self.barrel_diam - self.barrel_wall_thickness * 2
coupler_holes = [
Cq.Location.rot2d(i * 360 / self.n_side) *
Cq.Location.from2d(self.rotation_radius + sx * self.motor_coupler_conn_dx, 0)
for i in range(self.n_side)
for sx in (-1, 1)
]
return (
Cq.Sketch()
.circle(self.rotor_radius)
.circle(self.motor_driver_radius)
# Drill out the centre which will accomodate the motor shaft
.circle(self.motor.diam_shaft/2, mode="s")
# Drill out couplers
@ -404,11 +470,19 @@ class Onbashira(Model):
.regularPolygon(
self.rotation_radius,
self.n_side,
angle=180 / self.n_side,
mode="c",
tag="const",
)
.vertices(tag="const")
.circle(BOLT_COMMON.diam_thread/2, mode="s")
.circle(hole_diam/2, mode="s")
.reset()
# Create coupler holes
.push([
loc.to2d_pos()
for loc in coupler_holes
])
.circle(BOLT_BEARING.diam_thread /2, mode="s")
)
def motor_driver_disk(self) -> Cq.Workplane:
result = (
@ -421,19 +495,26 @@ class Onbashira(Model):
loc = Cq.Location.rot2d(i * 360 / n) * Cq.Location(self.flange_coupler.r_hole_flange, 0)
result.tagAbsolute(f"holeT{i}", loc * Cq.Location(0, 0, self.auxiliary_thickness), direction="+Z")
result.tagAbsolute(f"holeB{i}", loc, direction="-Z")
loc_z = Cq.Location(0, 0, self.auxiliary_thickness)
loc_outer = Cq.Location.from2d(self.rotation_radius + self.motor_coupler_conn_dx, 0)
loc_inner = Cq.Location.from2d(self.rotation_radius - self.motor_coupler_conn_dx, 0)
for i in range(self.n_side):
loc_rot = Cq.Location.rot2d(i * 360 / self.n_side)
p_outer, _ = (loc_z * loc_rot * loc_outer).toTuple()
p_inner, _ = (loc_z * loc_rot * loc_inner).toTuple()
result.tagAbsolute(f"holeCOF{i}", p_outer, direction="+Z")
result.tagAbsolute(f"holeCIF{i}", p_inner, direction="+Z")
return result
@target(name="motor-mount-plate", kind=TargetKind.DXF)
def profile_motor_mount_plate(self) -> Cq.Sketch:
assert self.n_side == 6
bx, by = self.angle_joint_bind_pos.to2d_pos()
r = self.motor_seat_radius
gap = 10.0
hole_dx = self.motor.dx_anchor
return (
Cq.Sketch()
.rect((bx + gap) * 2, (by + gap) * 2)
.reset()
.rect(bx * 2, by * 2, mode="c", tag="corner")
.circle(r + gap)
.regularPolygon(r, self.n_side, mode="c", tag="corner")
.vertices(tag="corner")
.circle(BOLT_COMMON.diam_thread/2, mode="s")
.reset()
@ -451,12 +532,10 @@ class Onbashira(Model):
)
result.tagAbsolute("anchor1", (self.motor.dx_anchor, 0, 0), direction="-Z")
result.tagAbsolute("anchor2", (-self.motor.dx_anchor, 0, 0), direction="-Z")
bp = self.angle_joint_bind_pos
r = self.motor_seat_radius
for i in range(self.n_side):
if i in {1, 4}:
continue
angle = i * 360 / self.n_side
x, y = (Cq.Location.rot2d(angle) * bp).to2d_pos()
x, y = (Cq.Location.rot2d(angle) * Cq.Location.from2d(0, r)).to2d_pos()
result.tagAbsolute(f"holeF{i}", (x, y, self.auxiliary_thickness), direction="+Z")
result.tagAbsolute(f"holeB{i}", (x, -y, 0), direction="-Z")
return result
@ -510,6 +589,49 @@ class Onbashira(Model):
f"driver_disk?holeB{i}",
"Plane",
)
# Add the motor seats
assert self.n_side % 2 == 0
for i in range(self.n_side // 2):
name_seat = f"seat{i}"
a = (
a.addS(
self.motor_seat(),
name=name_seat,
role=Role.STRUCTURE,
material=self.material_brace
)
.constrain(
f"{name_seat}?holeMF1",
f"mount_plate?holeB{i*2}",
"Plane"
)
.constrain(
f"{name_seat}?holeMF2",
f"mount_plate?holeB{i*2+1}",
"Plane"
)
)
for i in range(self.n_side):
name_coupler = f"coupler{i}"
a = (
a.addS(
self.motor_coupler(),
name=name_coupler,
role=Role.CONNECTION,
material=self.material_brace,
)
.constrain(
f"{name_coupler}?holeB1",
f"driver_disk?holeCOF{i}",
"Plane",
)
.constrain(
f"{name_coupler}?holeB2",
f"driver_disk?holeCIF{i}",
"Plane",
)
)
return a.solve()
@target(name="stator-coupler")
@ -784,7 +906,7 @@ class Onbashira(Model):
da_bind_rotor_minor = 360 / self.n_side / (1 + self.rotor_bind_extra)
for i in range(self.n_side):
loc_barrel = Cq.Location.rot2d((i+1/2) * 360/self.n_side) * \
Cq.Location(self.rotation_radius, 0, -self.barrel_length/2)
Cq.Location(self.rotation_radius, 0, self.barrel_shift-self.barrel_length/2)
a = a.addS(
self.barrel(),
name=f"barrel{i}",
@ -877,30 +999,172 @@ class Onbashira(Model):
result.tagAbsolute("holeMO1", (dxe, 0, t))
result.tagAbsolute("holeMO2", (-dxe, 0, t))
return result
@target(name="raising-bar")
def raising_bar(self) -> Cq.Workplane:
@target(name="motor-seat")
def motor_seat(self) -> Cq.Workplane:
"""
Create new longitudinal mount points closer to the centre axis, and a
ring for mounting lights
"""
_, dx = self.angle_joint_bind_pos.to2d_pos()
bx, by = self.angle_joint_bind_pos.to2d_pos()
gap = 10
t1 = 5
t1 = 10
base_w = 17.0
theta = math.pi / self.n_side
theta2 = theta * 0.5
track_width = 7.0
r0 = self.bulk_radius
result = (
Cq.Workplane()
.sketch()
.circle(self.rotation_radius + gap)
.circle(self.rotation_radius - gap, mode="s")
r1 = self.rotation_radius + gap
r2 = self.rotation_radius - gap
profile_arc = (
Cq.Sketch()
.circle(r1)
.circle(r2, mode="s")
.polygon([
(0, 0),
(r0 * math.cos(theta), r0 * math.sin(theta)),
(r0 * math.cos(theta), -r0 * math.sin(theta)),
], mode="i")
)
profile_base = (
profile_arc
.reset()
.polygon([
(bx - base_w/2, by),
(bx + base_w/2, by),
(bx + base_w/2, -by),
(bx - base_w/2, -by),
])
.reset()
.polygon([
(r1 * math.cos(theta), r1 * math.sin(theta)),
(r1 * math.cos(theta2), r1 * math.sin(theta2)),
(r0 * math.cos(theta2), r0 * math.sin(theta2)),
(r0 * math.cos(theta), r0 * math.sin(theta)),
])
.polygon([
(r1 * math.cos(theta), -r1 * math.sin(theta)),
(r1 * math.cos(theta2), -r1 * math.sin(theta2)),
(r0 * math.cos(theta2), -r0 * math.sin(theta2)),
(r0 * math.cos(theta), -r0 * math.sin(theta)),
])
.reset()
.push([
(bx, by), (bx, -by),
])
.circle(base_w/2, mode="a")
.reset()
.push([
(bx, by), (bx, -by),
])
.circle(BOLT_COMMON.diam_thread/2, mode="s")
)
base = (
Cq.Workplane()
.placeSketch(profile_base)
.extrude(t1)
)
r3 = self.motor_seat_radius
r2_5 = r3 + BOLT_COMMON.diam_thread/2
mount_x = r3 * math.cos(theta)
mount_y = r3 * math.sin(theta)
front = (
Cq.Workplane()
.sketch()
.circle(r1)
.circle(self.rotation_radius+track_width/2, mode="s")
.circle(self.rotation_radius-track_width/2)
.circle(r2_5, mode="s")
.polygon([
(0, 0),
(r0 * math.cos(theta), r0 * math.sin(theta)),
(r0 * math.cos(theta), -r0 * math.sin(theta)),
], mode="i")
.push([
(mount_x, mount_y),
(mount_x, -mount_y),
])
.circle(base_w/2)
.circle(BOLT_COMMON.diam_thread/2, mode="s")
.finalize()
.extrude(t1)
)
# Construct the connection between the front and back
profile_bridge_outer_base = (
Cq.Sketch()
.polygon([
(bx - base_w/2, by - base_w/2),
(bx + base_w/2, by - base_w/2),
(bx + base_w/2, by - base_w*1.5),
(bx - base_w/2, by - base_w*1.5),
])
.wires()
.val()
.moved(0, 0, t1)
)
profile_bridge_outer_top = (
Cq.Sketch()
.circle(r1)
.circle(self.rotation_radius+track_width/2, mode="s")
.polygon([
(0, 0),
(r0 * math.cos(theta), r0 * math.sin(theta)),
(r0 * math.cos(theta2), r0 * math.sin(theta2)),
], mode="i")
.wires()
.val()
.moved(0, 0, self.motor_seat_depth)
)
profile_bridge_inner_base = (
Cq.Sketch()
.circle(r1)
.circle(r2, mode="s")
.polygon([
(0, 0),
(r0 * math.cos(theta), r0 * math.sin(theta)),
(r0 * math.cos(theta2), r0 * math.sin(theta2)),
], mode="i")
.wires()
.val()
)
profile_bridge_inner_top = (
Cq.Sketch()
.circle(r1)
.circle(r2, mode="s")
.polygon([
(0, 0),
(r0 * math.cos(theta), r0 * math.sin(theta)),
(r0 * math.cos(theta2), r0 * math.sin(theta2)),
], mode="i")
.wires()
.val()
.moved(0, 0, self.motor_seat_depth - t1)
)
bridge_outer = Cq.Solid.makeLoft([profile_bridge_outer_base, profile_bridge_outer_top])
bridge_inner = Cq.Solid.makeLoft([profile_bridge_inner_base, profile_bridge_inner_top])
hole_subtractor = Cq.Solid.makeCylinder(
radius=BOLT_COMMON.diam_thread/2,
height=t1,
pnt=((r1+r2)/2, 0, 0)
)
result = (
base
+ front.translate((0, 0, self.motor_seat_depth - t1))
+ bridge_outer
+ bridge_outer.mirror("XZ")
+ bridge_inner
+ bridge_inner.mirror("XZ")
- hole_subtractor
)
# Mark the mount points
result.tagAbsolute("holeBB1", (bx, +by, 0), direction="-Z")
result.tagAbsolute("holeBB2", (bx, -by, 0), direction="-Z")
result.tagAbsolute("holeMF1", (mount_x, +mount_y, self.motor_seat_depth), direction="+Z")
result.tagAbsolute("holeMF2", (mount_x, -mount_y, self.motor_seat_depth), direction="+Z")
return result
@ -1676,81 +1940,107 @@ class Onbashira(Model):
result.tagAbsolute(f"holeRSI{i}", (-x, -y, -t), direction="-Z")
return result
@assembly()
def assembly(self) -> Cq.Assembly:
def assembly(self, parts: Optional[list[str]] = None) -> Cq.Assembly:
a = Cq.Assembly()
a = (
a
.add(
if has_part(parts, "section1"):
a = a.add(
self.assembly_section1(),
name="section1",
)
.add(
if has_part(parts, "ring1"):
a = a.add(
self.assembly_ring(self.angle_joint()),
name="ring1",
)
.add(
if has_part(parts, "section2"):
a = a.add(
self.assembly_section(length=self.side_length2, hasFrontHole=True, hasBackHole=True),
name="section2",
)
.add(
if has_part(parts, "ring2"):
a = a.add(
self.assembly_ring(self.angle_joint()),
name="ring2",
)
.add(
self.assembly_section(length=self.side_length3, hasFrontHole=True, hasBackHole=True),
name="section3",
)
.add(
self.assembly_ring(self.angle_joint_chamber_front()),
name="ring3",
)
.add(
self.assembly_chamber(),
name="chamber",
)
.add(
self.assembly_ring(self.angle_joint_chamber_back()),
name="ring4",
)
.addS(
self.chamber_back(),
name="chamber_back",
material=self.material_side,
role=Role.STRUCTURE | Role.DECORATION,
)
.addS(
a = a.addS(
self.handle(),
name="handle",
material=self.material_brace,
role=Role.HANDLE,
)
.add(self.assembly_motor(), name="motor")
.add(self.assembly_machine(), name="machine")
.add(self.turning_bar(), name="turning_bar1")
)
a = a.constrain(
f"turning_bar1?holeBO2",
f"ring3/side0?holeStatorL",
"Plane",
)
a = a.constrain(
f"turning_bar1?holeBO1",
f"ring3/side1?holeStatorL",
"Plane",
)
# Add handle
for ih, (x, y) in enumerate(self.angle_joint_bolt_position):
# Handle constrain
for ih, (x, y) in enumerate(self.angle_joint_bolt_position):
a = a.constrain(
f"handle?holeLPI{ih}",
f"ring2/side0?holeLPO{ih}",
"Plane",
)
a = a.constrain(
f"handle?holeRPI{ih}",
f"ring2/side0?holeRPO{ih}",
"Plane",
)
if has_part(parts, "section3"):
a = a.add(
self.assembly_section(length=self.side_length3, hasFrontHole=True, hasBackHole=True),
name="section3",
)
if has_part(parts, "ring3"):
a = a.add(
self.assembly_ring(self.angle_joint_chamber_front()),
name="ring3",
)
if has_part(parts, "chamber"):
a = a.add(
self.assembly_chamber(),
name="chamber",
)
if has_part(parts, "ring4"):
a = a.add(
self.assembly_ring(self.angle_joint_chamber_back()),
name="ring4",
)
if has_part(parts, "chamber_back"):
a = a.addS(
self.chamber_back(),
name="chamber_back",
material=self.material_side,
role=Role.STRUCTURE | Role.DECORATION,
)
if has_part(parts, "motor"):
a = a.add(self.assembly_motor(), name="motor")
if has_part(parts, "machine"):
a = a.add(self.assembly_machine(), name="machine")
if has_part(parts, "turning_bar"):
a = a.add(self.turning_bar(), name="turning_bar1")
if has_part(parts, ["turning_bar", "ring3"]):
a = a.constrain(
f"handle?holeLPI{ih}",
f"ring2/side0?holeLPO{ih}",
f"turning_bar1?holeBO2",
f"ring3/side0?holeStatorL",
"Plane",
)
a = a.constrain(
f"handle?holeRPI{ih}",
f"ring2/side0?holeRPO{ih}",
f"turning_bar1?holeBO1",
f"ring3/side1?holeStatorL",
"Plane",
)
# FIXME: Filter
if has_part(parts, ["motor", "ring2"]):
for i in range(self.n_side // 2):
j = self.n_side // 2 - 1 - i
a = a.constrain(
f"motor/seat{j}?holeBB1",
f"ring2/side{i*2}?holeStatorL",
"Plane",
)
#a = a.constrain(
# f"motor/seat{j}?holeBB2",
# f"ring2/side{i*2+1}?holeStatorL",
# "Plane",
#)
for i in range(self.n_side):
j = (i + 1) % self.n_side
ir = (self.n_side - i) % self.n_side
@ -1772,12 +2062,6 @@ class Onbashira(Model):
f"machine/stator2?holeB{ir}",
"Plane",
)
if i not in {1, 4}:
a = a.constrain(
f"motor/mount_plate?holeF{i}",
f"ring2/side{(ir+2)%self.n_side}?holeStatorR",
"Plane",
)
name_bolt =f"stator_outer_bolt{i}"
a = a.addS(