""" 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): 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', ) ) front.copyWorkplane(Cq.Workplane('XZ')).tagPlane('conn') 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', ) ) back.copyWorkplane(Cq.Workplane('XZ')).tagPlane('conn') 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 @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, 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=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)