feat: Add linear actuator component

This commit is contained in:
Leni Aniva 2024-07-19 21:00:10 -07:00
parent 39110d0785
commit f5b048d0b9
Signed by: aniva
GPG Key ID: 4D9B1C8D10EA4C50
6 changed files with 223 additions and 55 deletions

View File

@ -31,6 +31,7 @@ class Role(Flag):
STRUCTURE = auto() STRUCTURE = auto()
DECORATION = auto() DECORATION = auto()
ELECTRONIC = auto() ELECTRONIC = auto()
MOTION = auto()
# Fasteners, etc. # Fasteners, etc.
CONNECTION = auto() CONNECTION = auto()
@ -61,7 +62,8 @@ ROLE_COLOR_MAP = {
Role.DAMPING: _color('springgreen', 1.0), Role.DAMPING: _color('springgreen', 1.0),
Role.STRUCTURE: _color('gray', 0.4), Role.STRUCTURE: _color('gray', 0.4),
Role.DECORATION: _color('lightseagreen', 0.4), Role.DECORATION: _color('lightseagreen', 0.4),
Role.ELECTRONIC: _color('mediumorchid', 0.5), Role.ELECTRONIC: _color('mediumorchid', 0.7),
Role.MOTION: _color('thistle3', 0.7),
Role.CONNECTION: _color('steelblue3', 0.8), Role.CONNECTION: _color('steelblue3', 0.8),
Role.HANDLE: _color('tomato4', 0.8), Role.HANDLE: _color('tomato4', 0.8),
} }

View File

@ -42,10 +42,7 @@ class FlatHeadBolt(Item):
@dataclass(frozen=True) @dataclass(frozen=True)
class ThreaddedKnob(Item): class ThreaddedKnob(Item):
""" """
Sourced from: A threaded rod with knob on one side
> Othmro Black 12mm(M12) x 50mm Thread Replacement Star Hand Knob Tightening
> Screws
""" """
diam_thread: float diam_thread: float
height_thread: float height_thread: float
@ -104,7 +101,7 @@ class HexNut(Item):
return f"HexNut M{int(self.diam_thread)}-{self.pitch}" return f"HexNut M{int(self.diam_thread)}-{self.pitch}"
@property @property
def role(self): def role(self) -> Role:
return Role.CONNECTION return Role.CONNECTION
def generate(self) -> Cq.Workplane: def generate(self) -> Cq.Workplane:

View File

@ -393,7 +393,7 @@ class TorsionJoint:
def rider_track_assembly(self, directrix: int = 0, deflection: float = 0): def rider_track_assembly(self, directrix: int = 0, deflection: float = 0):
rider = self.rider() rider = self.rider()
track = self.track() track = self.track()
spring = self.spring.generate(deflection=deflection) spring = self.spring.assembly(deflection=deflection)
result = ( result = (
Cq.Assembly() Cq.Assembly()
.addS(spring, name="spring", role=Role.DAMPING) .addS(spring, name="spring", role=Role.DAMPING)

View File

@ -0,0 +1,155 @@
"""
Electronic components
"""
from dataclasses import dataclass
import cadquery as Cq
from nhf.materials import Role
from nhf.parts.item import Item
from nhf.parts.fasteners import FlatHeadBolt, HexNut
@dataclass(frozen=True)
class LinearActuator(Item):
stroke_length: float
shaft_diam: float = 9.04
front_hole_ext: float = 4.41
front_hole_diam: float = 4.41
front_length: float = 9.55
front_width: float = 9.24
front_height: float = 5.98
segment1_length: float = 37.55
segment1_width: float = 15.95
segment1_height: float = 11.94
segment2_length: float = 37.47
segment2_width: float = 20.03
segment2_height: float = 15.03
back_hole_ext: float = 4.58
back_hole_diam: float = 4.18
back_length: float = 9.27
back_width: float = 10.16
back_height: float = 8.12
@property
def name(self) -> str:
return f"LinearActuator {self.stroke_length}mm"
@property
def role(self) -> Role:
return Role.MOTION
@property
def conn_length(self):
return self.segment1_length + self.segment2_length + self.front_hole_ext + self.back_hole_ext
def generate(self, pos: float=0) -> Cq.Assembly:
stroke_x = pos * self.stroke_length
front = (
Cq.Workplane('XZ')
.cylinder(
radius=self.front_width / 2,
height=self.front_height,
centered=True,
)
.box(
length=self.front_hole_ext,
width=self.front_width,
height=self.front_height,
combine=True,
centered=(False, True, True)
)
.copyWorkplane(Cq.Workplane('XZ'))
.cylinder(
radius=self.front_hole_diam / 2,
height=self.front_height,
centered=True,
combine='cut',
)
)
if stroke_x > 0:
shaft = (
Cq.Workplane('YZ')
.cylinder(
radius=self.shaft_diam / 2,
height=stroke_x,
centered=(True, True, False)
)
)
else:
shaft = None
segment1 = (
Cq.Workplane()
.box(
length=self.segment1_length,
height=self.segment1_width,
width=self.segment1_height,
centered=(False, True, True),
)
)
segment2 = (
Cq.Workplane()
.box(
length=self.segment2_length,
height=self.segment2_width,
width=self.segment2_height,
centered=(False, True, True),
)
)
back = (
Cq.Workplane('XZ')
.cylinder(
radius=self.back_width / 2,
height=self.back_height,
centered=True,
)
.box(
length=self.back_hole_ext,
width=self.back_width,
height=self.back_height,
combine=True,
centered=(False, True, True)
)
.copyWorkplane(Cq.Workplane('XZ'))
.cylinder(
radius=self.back_hole_diam / 2,
height=self.back_height,
centered=True,
combine='cut',
)
)
result = (
Cq.Assembly()
.add(front, name="front",
loc=Cq.Location((-self.front_hole_ext, 0, 0)))
.add(segment1, name="segment1",
loc=Cq.Location((stroke_x, 0, 0)))
.add(segment2, name="segment2",
loc=Cq.Location((stroke_x + self.segment1_length, 0, 0)))
.add(back, name="back",
loc=Cq.Location((stroke_x + self.segment1_length + self.segment2_length + self.back_hole_ext, 0, 0), (0, 1, 0), 180))
)
if shaft:
result.add(shaft, name="shaft")
return result
LINEAR_ACTUATOR_SHOULDER = LinearActuator(
mass=34.0,
stroke_length=30,
)
LINEAR_ACTUATOR_HEX_NUT = HexNut(
mass=0.8,
diam_thread=4,
pitch=0.7,
thickness=4.16,
width=6.79,
)
LINEAR_ACTUATOR_BOLT = FlatHeadBolt(
mass=1.7,
diam_head=16.68,
height_head=2.98,
diam_thread=4.0,
height_thread=15.83,
)

View File

@ -12,12 +12,10 @@ import nhf.utils
TOL = 1e-6 TOL = 1e-6
@dataclass # Parts used
class RootJoint(Model):
""" # uxcell 2 Pcs Star Knobs Grips M12 x 30mm Male Thread Steel Zinc Stud Replacement PP
The Houjuu-Scarlett Mechanism HS_JOINT_KNOB = ThreaddedKnob(
"""
knob: ThreaddedKnob = ThreaddedKnob(
mass=float('nan'), mass=float('nan'),
diam_thread=12.0, diam_thread=12.0,
height_thread=30.0, height_thread=30.0,
@ -27,15 +25,51 @@ class RootJoint(Model):
height_neck=10.0, height_neck=10.0,
height_knob=10.0, height_knob=10.0,
) )
hex_nut: HexNut = HexNut( # Tom's world 8Pcs M12-1.75 Hex Nut Assortment Set Stainless Steel 304(18-8)
# FIXME: Undetermined # Metric Hexagon Nut for Bolts, Bright Finish, Full Thread (M12)
mass=float('nan'), HS_JOINT_HEX_NUT = HexNut(
mass=14.9,
diam_thread=12.0, diam_thread=12.0,
pitch=1.75, pitch=1.75,
thickness=9.8, thickness=9.7,
width=18.9, width=18.9,
) )
SHOULDER_AXIS_BOLT = FlatHeadBolt(
# FIXME: measure
diam_head=10.0,
height_head=3.0,
diam_thread=6.0,
height_thread=20.0,
mass=float('nan'),
)
# Hoypeyfiy 10 Pieces Torsion Spring Woodworking DIY 90 Degrees Torsional
# Springs Repair Maintenance Spring
SHOULDER_TORSION_SPRING = TorsionSpring(
mass=2.2,
# inner diameter = 9
radius=9/2 + 1.2,
thickness=1.3,
height=7.5,
)
# KALIONE 10 Pieces Torsion Spring, Stainless Steel Small Torsion Springs, Tiny
# Torsional Spring, 90° Deflection Compression Spring Kit for Repair Tools
# Woodworking DIY, 50mm
ELBOW_TORSION_SPRING = TorsionSpring(
mass=1.7,
radius=9 / 2,
thickness=1.3,
height=6.5,
tail_length=45.0,
right_handed=False,
)
@dataclass
class RootJoint(Model):
"""
The Houjuu-Scarlett Mechanism
"""
knob: ThreaddedKnob = HS_JOINT_KNOB
hex_nut: HexNut = HS_JOINT_HEX_NUT
hirth_joint: HirthJoint = field(default_factory=lambda: HirthJoint( hirth_joint: HirthJoint = field(default_factory=lambda: HirthJoint(
radius=25.0, radius=25.0,
radius_inner=15.0, radius_inner=15.0,
@ -206,14 +240,7 @@ class RootJoint(Model):
@dataclass @dataclass
class ShoulderJoint(Model): class ShoulderJoint(Model):
bolt: FlatHeadBolt = FlatHeadBolt( bolt: FlatHeadBolt = SHOULDER_AXIS_BOLT
# FIXME: measure
diam_head=10.0,
height_head=3.0,
diam_thread=6.0,
height_thread=20.0,
mass=float('nan'),
)
height: float = 70.0 height: float = 70.0
torsion_joint: TorsionJoint = field(default_factory=lambda: TorsionJoint( torsion_joint: TorsionJoint = field(default_factory=lambda: TorsionJoint(
@ -225,13 +252,7 @@ class ShoulderJoint(Model):
track_disk_height=5.0, track_disk_height=5.0,
rider_disk_height=5.0, rider_disk_height=5.0,
radius_axle=3.0, radius_axle=3.0,
spring=TorsionSpring( spring=SHOULDER_TORSION_SPRING,
mass=float('nan'),
# inner diameter = 9
radius=9/2 + 1.2,
thickness=1.3,
height=7.5,
),
rider_slot_begin=0, rider_slot_begin=0,
rider_n_slots=1, rider_n_slots=1,
rider_slot_span=0, rider_slot_span=0,
@ -451,12 +472,12 @@ class ShoulderJoint(Model):
.addS(self.child(), name="child", .addS(self.child(), name="child",
role=Role.CHILD, material=mat) role=Role.CHILD, material=mat)
.constrain("child/core", "Fixed") .constrain("child/core", "Fixed")
.addS(self.torsion_joint.spring.generate(deflection=-deflection), name="spring_top", .addS(self.torsion_joint.spring.assembly(deflection=-deflection), name="spring_top",
role=Role.DAMPING, material=mat_spring) role=Role.DAMPING, material=mat_spring)
.addS(self.parent_top(), .addS(self.parent_top(),
name="parent_top", name="parent_top",
role=Role.PARENT, material=mat) role=Role.PARENT, material=mat)
.addS(self.torsion_joint.spring.generate(deflection=deflection), name="spring_bot", .addS(self.torsion_joint.spring.assembly(deflection=deflection), name="spring_bot",
role=Role.DAMPING, material=mat_spring) role=Role.DAMPING, material=mat_spring)
.addS(self.parent_bot(), .addS(self.parent_bot(),
name="parent_bot", name="parent_bot",
@ -558,14 +579,7 @@ class DiskJoint(Model):
We embed a spring inside the joint, with one leg in the disk and one leg in We embed a spring inside the joint, with one leg in the disk and one leg in
the housing. This provides torsion resistance. the housing. This provides torsion resistance.
""" """
spring: TorsionSpring = field(default_factory=lambda: TorsionSpring( spring: TorsionSpring = ELBOW_TORSION_SPRING
mass=float('nan'),
radius=9 / 2,
thickness=1.3,
height=6.5,
tail_length=45.0,
right_handed=False,
))
radius_housing: float = 22.0 radius_housing: float = 22.0
radius_disk: float = 20.0 radius_disk: float = 20.0
@ -787,7 +801,7 @@ class DiskJoint(Model):
( (
assembly assembly
.addS( .addS(
self.spring.generate(deflection=-deflection), self.spring.assembly(deflection=-deflection),
name=spring_name, name=spring_name,
role=Role.DAMPING, role=Role.DAMPING,
material=Material.STEEL_SPRING) material=Material.STEEL_SPRING)