diff --git a/nhf/parts/fibre.py b/nhf/parts/fibre.py new file mode 100644 index 0000000..003e64b --- /dev/null +++ b/nhf/parts/fibre.py @@ -0,0 +1,58 @@ +""" +A fibre, for bearing tension +""" +import cadquery as Cq +from dataclasses import dataclass +import nhf.utils + +def tension_fibre( + length: float, + hole_diam: float, + hole_twist: float=0, + thickness: float=0.5) -> Cq.Workplane: + """ + A fibre which holds tension, with an eyes on each end. + + """ + eye_female = Cq.Solid.makeTorus( + radius1=hole_diam/2 + thickness/2, + radius2=thickness/2, + dir=(1,0,0), + ) + hole_length_male = hole_diam * 2.5 + hole_height_male = hole_diam * 1.2 + eye_male = Cq.Solid.makeBox( + length=hole_length_male + thickness * 2, + width=thickness, + height=hole_height_male + thickness * 2, + ).located( + Cq.Location((-hole_length_male/2-thickness, -thickness/2, -hole_height_male/2-thickness)) + ).cut(Cq.Solid.makeBox( + length=hole_length_male, + width=thickness, + height=hole_height_male, + ).located(Cq.Location((-hole_length_male/2, -thickness/2, -hole_height_male/2)))) + height = length - hole_diam - thickness + assert height > 0, "String is too short to support the given hole sizes" + h1 = length/2 - hole_diam/2 - thickness/2 + h2 = length/2 - hole_height_male - thickness/2 + result = ( + Cq.Workplane('XY') + .cylinder( + radius=thickness/2, + height=h1, + centered=(True, True, False), + ) + .copyWorkplane(Cq.Workplane('YX')) + .cylinder( + radius=thickness/2, + height=h2, + centered=(True, True, False), + ) + .union(eye_female.located(Cq.Location((0, 0,length/2)))) + .union(eye_male.located(Cq.Location((0, 0,-length/2+hole_height_male/2+thickness/2), (0,0,1), hole_twist))) + ) + result.copyWorkplane(Cq.Workplane(Cq.Plane(origin=(0,0,length/2), normal=(1,0,0)))).tagPlane("female") + conn1_normal, _ = (Cq.Location((0,0,0),(0,0,1),hole_twist) * Cq.Location((1,0,0))).toTuple() + result.copyWorkplane(Cq.Workplane(Cq.Plane(origin=(0,0,-length/2), normal=conn1_normal))).tagPlane("male") + return result diff --git a/nhf/touhou/houjuu_nue/electronics.py b/nhf/touhou/houjuu_nue/electronics.py index e2ab9d6..84dc49f 100644 --- a/nhf/touhou/houjuu_nue/electronics.py +++ b/nhf/touhou/houjuu_nue/electronics.py @@ -8,6 +8,7 @@ import cadquery as Cq from nhf.build import Model, TargetKind, target, assembly, submodel from nhf.materials import Role, Material from nhf.parts.box import MountingBox, Hole +from nhf.parts.fibre import tension_fibre from nhf.parts.item import Item from nhf.parts.fasteners import FlatHeadBolt, HexNut from nhf.touhou.houjuu_nue.common import NUT_COMMON, BOLT_COMMON @@ -333,7 +334,7 @@ LINEAR_ACTUATOR_BRACKET = MountingBracket() BATTERY_BOX = BatteryBox18650() -@dataclass +@dataclass(kw_only=True) class Flexor: """ Actuator assembly which flexes, similar to biceps @@ -346,17 +347,30 @@ class Flexor: nut: HexNut = LINEAR_ACTUATOR_HEX_NUT bolt: FlatHeadBolt = LINEAR_ACTUATOR_BOLT bracket: MountingBracket = LINEAR_ACTUATOR_BRACKET + # Length of line attached to the flexor + line_length: float = 0.0 + line_thickness: float = 0.5 + # By how much is the line permitted to slack. This reduces the effective stroke length + line_slack: float = 0.0 - # FIXME: Add a compression spring so the serviceable distances are not as fixed + def __post_init__(self): + assert self.line_slack <= self.line_length < self.actuator.stroke_length @property def mount_height(self): return self.bracket.hole_to_side_ext + @property + def d_open(self): + return self.actuator.conn_length + self.actuator.stroke_length + self.line_length - self.line_slack + @property + def d_closed(self): + return self.actuator.conn_length + self.line_length + def open_pos(self) -> Tuple[float, float, float]: r, phi, r_ = nhf.geometry.contraction_span_pos_from_radius( - d_open=self.actuator.conn_length + self.actuator.stroke_length, - d_closed=self.actuator.conn_length, + d_open=self.d_open, + d_closed=self.d_closed, theta=math.radians(self.motion_span), r=self.arm_radius, smaller=self.pos_smaller, @@ -376,8 +390,8 @@ class Flexor: result = math.sqrt(r * r + rp * rp - 2 * r * rp * math.cos(th)) #result = math.sqrt((r * math.cos(th) - rp) ** 2 + (r * math.sin(th)) ** 2) - assert self.actuator.conn_length <= result <= self.actuator.conn_length + self.actuator.stroke_length, \ - f"Illegal length: {result} in {self.actuator.conn_length}+{self.actuator.stroke_length}" + assert self.d_closed -1e-6 <= result <= self.d_open + 1e-6,\ + f"Illegal length: {result} not in [{self.d_closed}, {self.d_open}]" return result @@ -393,7 +407,9 @@ class Flexor: Adds the necessary mechanical components to this assembly. Does not invoke `a.solve()`. """ - pos = (target_length - self.actuator.conn_length) / self.actuator.stroke_length + draft = max(0, target_length - self.d_closed - self.line_length) + pos = draft / self.actuator.stroke_length + line_l = target_length - draft - self.actuator.conn_length if tag_prefix: tag_prefix = tag_prefix + "_" else: @@ -411,8 +427,6 @@ class Flexor: .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", @@ -427,6 +441,30 @@ class Flexor: .constrain(f"{name_nut_back}?bot", f"{name_bracket_back}?conn_bot", "Plane") ) + if self.line_length == 0.0: + a.constrain( + f"{name_actuator}/front?conn", + f"{name_bracket_front}?conn_mid", + "Plane", param=0) + else: + ( + a + .addS(tension_fibre( + length=line_l, + hole_diam=self.nut.diam_thread, + thickness=self.line_thickness, + ), name="fibre", role=Role.CONNECTION) + .constrain( + f"{name_actuator}/front?conn", + "fibre?male", + "Plane" + ) + .constrain( + f"{name_bracket_front}?conn_mid", + "fibre?female", + "Plane" + ) + ) if tag_hole_front: a.constrain(tag_hole_front, f"{name_bracket_front}?conn_side", "Plane") if tag_hole_back: diff --git a/nhf/touhou/houjuu_nue/joints.py b/nhf/touhou/houjuu_nue/joints.py index 5f99f19..d7fd70a 100644 --- a/nhf/touhou/houjuu_nue/joints.py +++ b/nhf/touhou/houjuu_nue/joints.py @@ -1067,7 +1067,7 @@ class ElbowJoint(Model): flip: bool = False angle_neutral: float = 30.0 - actuator: Optional[LinearActuator] + actuator: Optional[LinearActuator] = None flexor: Optional[Flexor] = None # Rotates the entire flexor flexor_offset_angle: float = 0 @@ -1076,6 +1076,8 @@ class ElbowJoint(Model): flexor_mount_angle_child: float = -90 flexor_pos_smaller: bool = True flexor_child_arm_radius: Optional[float] = None + flexor_line_length: float = 0.0 + flexor_line_slack: float = 0.0 def __post_init__(self): assert self.child_arm_radius > self.disk_joint.radius_housing @@ -1087,6 +1089,8 @@ class ElbowJoint(Model): motion_span=self.motion_span, pos_smaller=self.flexor_pos_smaller, arm_radius=self.flexor_child_arm_radius, + line_length=self.flexor_line_length, + line_slack=self.flexor_line_slack, ) def hole_loc_tags(self): diff --git a/nhf/touhou/houjuu_nue/wing.py b/nhf/touhou/houjuu_nue/wing.py index 9a91e89..5d2e4a3 100644 --- a/nhf/touhou/houjuu_nue/wing.py +++ b/nhf/touhou/houjuu_nue/wing.py @@ -1291,6 +1291,10 @@ class WingL(WingProfile): angle_neutral=30.0, flexor_mount_angle_child=170, flexor_mount_angle_parent=-30, + flexor_line_length=30.0, + flexor_line_slack=5.0, + #flexor_line_length=0.0, + #flexor_line_slack=0.0, flexor_offset_angle=15, child_lip_extra_length=5.0, flexor_child_arm_radius=60.0,