From b3a472add41308e73c7629722079269f59c2ee29 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sun, 21 Jul 2024 05:46:18 -0700 Subject: [PATCH] feat: Linear actuator assembly --- nhf/parts/fasteners.py | 13 ++- nhf/touhou/houjuu_nue/electronics.py | 130 ++++++++++++++++++++++++++- nhf/touhou/houjuu_nue/joints.py | 4 +- 3 files changed, 137 insertions(+), 10 deletions(-) diff --git a/nhf/parts/fasteners.py b/nhf/parts/fasteners.py index 38c8f84..9dfd20f 100644 --- a/nhf/parts/fasteners.py +++ b/nhf/parts/fasteners.py @@ -15,6 +15,10 @@ class FlatHeadBolt(Item): def name(self) -> str: return f"Bolt M{int(self.diam_thread)} h{int(self.height_thread)}mm" + @property + def role(self) -> Role: + return Role.CONNECTION + def generate(self) -> Cq.Assembly: head = Cq.Solid.makeCylinder( @@ -30,13 +34,8 @@ class FlatHeadBolt(Item): ) rod.faces("Z").tag("root") - - return ( - Cq.Assembly() - .addS(rod, name="thread", role=Role.CONNECTION) - .addS(head, name="head", role=Role.CONNECTION, - loc=Cq.Location((0, 0, self.height_thread))) - ) + rod = rod.union(head.located(Cq.Location((0, 0, self.height_thread)))) + return rod @dataclass(frozen=True) diff --git a/nhf/touhou/houjuu_nue/electronics.py b/nhf/touhou/houjuu_nue/electronics.py index 5e4f690..90d4574 100644 --- a/nhf/touhou/houjuu_nue/electronics.py +++ b/nhf/touhou/houjuu_nue/electronics.py @@ -2,10 +2,12 @@ Electronic components """ from dataclasses import dataclass +from typing import Optional import cadquery as Cq from nhf.materials import Role from nhf.parts.item import Item from nhf.parts.fasteners import FlatHeadBolt, HexNut +import nhf.utils @dataclass(frozen=True) class LinearActuator(Item): @@ -68,6 +70,7 @@ class LinearActuator(Item): combine='cut', ) ) + front.copyWorkplane(Cq.Workplane('XZ')).tagPlane('conn') if stroke_x > 0: shaft = ( Cq.Workplane('YZ') @@ -119,6 +122,7 @@ class LinearActuator(Item): combine='cut', ) ) + back.copyWorkplane(Cq.Workplane('XZ')).tagPlane('conn') result = ( Cq.Assembly() .add(front, name="front", @@ -134,6 +138,71 @@ class LinearActuator(Item): result.add(shaft, name="shaft") return result +@dataclass(frozen=True) +class MountingBracket(Item): + """ + Mounting bracket for a linear actuator + """ + mass: float = 1.6 + hole_diam: float = 4.0 + width: float = 8.0 + height: float = 12.20 + thickness: float = 0.98 + length: float = 13.00 + hole_to_side_ext: float = 8.10 + + def __post_init__(self): + assert self.hole_to_side_ext - self.hole_diam / 2 > 0 + + @property + def name(self) -> str: + return f"MountingBracket M{int(self.hole_diam)}" + + @property + def role(self) -> Role: + return Role.MOTION + + def generate(self) -> Cq.Workplane: + result = ( + Cq.Workplane('XY') + .box( + length=self.hole_to_side_ext, + width=self.width, + height=self.height, + centered=(False, True, True) + ) + .copyWorkplane(Cq.Workplane('XY')) + .cylinder( + height=self.height, + radius=self.width / 2, + combine=True, + ) + .copyWorkplane(Cq.Workplane('XY')) + .box( + length=2 * (self.hole_to_side_ext - self.thickness), + width=self.width, + height=self.height - self.thickness * 2, + combine='cut', + ) + .copyWorkplane(Cq.Workplane('XY')) + .cylinder( + height=self.height, + radius=self.hole_diam / 2, + combine='cut' + ) + .copyWorkplane(Cq.Workplane('YZ')) + .cylinder( + height=self.hole_to_side_ext * 2, + radius=self.hole_diam / 2, + combine='cut' + ) + ) + result.copyWorkplane(Cq.Workplane('YZ', origin=(self.hole_to_side_ext, 0, 0))).tagPlane("conn_side") + result.copyWorkplane(Cq.Workplane('XY', origin=(0, 0, self.height/2))).tagPlane("conn_top") + result.copyWorkplane(Cq.Workplane('YX', origin=(0, 0, -self.height/2))).tagPlane("conn_bot") + result.copyWorkplane(Cq.Workplane('XY')).tagPlane("conn_mid") + return result + LINEAR_ACTUATOR_SHOULDER = LinearActuator( mass=34.0, @@ -148,8 +217,67 @@ LINEAR_ACTUATOR_HEX_NUT = HexNut( ) LINEAR_ACTUATOR_BOLT = FlatHeadBolt( mass=1.7, - diam_head=16.68, + diam_head=6.68, height_head=2.98, diam_thread=4.0, height_thread=15.83, ) +LINEAR_ACTUATOR_BRACKET = MountingBracket() + +@dataclass(frozen=True) +class LinearActuatorAssembly: + + # FIXME: Measure + actuator: LinearActuator = LINEAR_ACTUATOR_SHOULDER + nut: HexNut = LINEAR_ACTUATOR_HEX_NUT + bolt: FlatHeadBolt = LINEAR_ACTUATOR_BOLT + bracket: MountingBracket = LINEAR_ACTUATOR_BRACKET + + def add_to( + self, + a: Cq.Assembly, + tag_prefix: Optional[str] = None, + tag_hole_front: Optional[str] = None, + tag_hole_back: Optional[str] = None, + tag_dir: Optional[str] = None): + """ + Adds the necessary mechanical components to this assembly. Does not + invoke `a.solve()`. + """ + if tag_prefix: + tag_prefix = tag_prefix + "_" + name_actuator = f"{tag_prefix}actuator" + name_bracket_front = f"{tag_prefix}bracket_front" + name_bracket_back = f"{tag_prefix}bracket_back" + name_bolt_front = f"{tag_prefix}front_bolt" + name_bolt_back = f"{tag_prefix}back_bolt" + name_nut_front = f"{tag_prefix}front_nut" + name_nut_back = f"{tag_prefix}back_nut" + ( + a + .add(self.actuator.assembly(), name=name_actuator) + .add(self.bracket.assembly(), name=name_bracket_front) + .add(self.bolt.assembly(), name=name_bolt_front) + .add(self.nut.assembly(), name=name_nut_front) + .constrain(f"{name_actuator}/front?conn", f"{name_bracket_front}?conn_mid", + "Plane", param=0) + .constrain(f"{name_bolt_front}?root", f"{name_bracket_front}?conn_top", + "Plane", param=0) + .constrain(f"{name_nut_front}?bot", f"{name_bracket_front}?conn_bot", + "Plane") + .add(self.bracket.assembly(), name=name_bracket_back) + .add(self.bolt.assembly(), name=name_bolt_back) + .add(self.nut.assembly(), name=name_nut_back) + .constrain(f"{name_actuator}/back?conn", f"{name_bracket_back}?conn_mid", + "Plane", param=0) + .constrain(f"{name_bolt_back}?root", f"{name_bracket_back}?conn_top", + "Plane", param=0) + .constrain(f"{name_nut_back}?bot", f"{name_bracket_back}?conn_bot", + "Plane") + ) + if tag_hole_front: + a.constrain(tag_hole_front, f"{name_bracket_front}?conn_side", "Plane") + if tag_hole_back: + a.constrain(tag_hole_back, f"{name_bracket_back}?conn_side", "Plane") + if tag_dir: + a.constrain(tag_dir, f"{name_bracket_front}?conn_mid", "Axis", param=0) diff --git a/nhf/touhou/houjuu_nue/joints.py b/nhf/touhou/houjuu_nue/joints.py index df67ece..a545d48 100644 --- a/nhf/touhou/houjuu_nue/joints.py +++ b/nhf/touhou/houjuu_nue/joints.py @@ -484,10 +484,10 @@ class ShoulderJoint(Model): # Fasteners .addS(self.bolt.assembly(), name="bolt_top", loc=Cq.Location((0, 0, bolt_z))) - .constrain("bolt_top/thread?root", 'Fixed') + .constrain("bolt_top?root", 'Fixed') .addS(self.bolt.assembly(), name="bolt_bot", loc=Cq.Location((0, 0, -bolt_z), (1,0,0), 180)) - .constrain("bolt_bot/thread?root", 'Fixed') + .constrain("bolt_bot?root", 'Fixed') ) TorsionJoint.add_constraints( result,