From ddeaf1194f68804f01bc11aa240ccc453fecf421 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Mon, 22 Jul 2024 09:49:16 -0700 Subject: [PATCH] feat: Optional actuator on wrist --- nhf/touhou/houjuu_nue/electronics.py | 2 ++ nhf/touhou/houjuu_nue/joints.py | 23 +++++++----- nhf/touhou/houjuu_nue/wing.py | 53 +++++++++++++++------------- 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/nhf/touhou/houjuu_nue/electronics.py b/nhf/touhou/houjuu_nue/electronics.py index 75d8ea5..8469d52 100644 --- a/nhf/touhou/houjuu_nue/electronics.py +++ b/nhf/touhou/houjuu_nue/electronics.py @@ -300,6 +300,8 @@ LINEAR_ACTUATOR_10 = LinearActuator( back_hole_ext=4.5/2, segment1_length=30.0, segment2_length=30.0, + segment1_width=15.0, + segment2_width=21.0, ) LINEAR_ACTUATOR_HEX_NUT = HexNut( mass=0.8, diff --git a/nhf/touhou/houjuu_nue/joints.py b/nhf/touhou/houjuu_nue/joints.py index 1920ed0..36d1914 100644 --- a/nhf/touhou/houjuu_nue/joints.py +++ b/nhf/touhou/houjuu_nue/joints.py @@ -882,8 +882,8 @@ class ElbowJoint(Model): angle_neutral: float = 30.0 - actuator: LinearActuator - flexor: Flexor = None + actuator: Optional[LinearActuator] + flexor: Optional[Flexor] = None # Rotates the entire flexor flexor_offset_angle: float = 0 # Rotates the surface of the mount @@ -893,10 +893,11 @@ class ElbowJoint(Model): assert self.child_arm_radius > self.disk_joint.radius_housing assert self.parent_arm_radius > self.disk_joint.radius_housing self.disk_joint.tongue_length = self.child_arm_radius - self.disk_joint.radius_disk - self.lip_thickness / 2 - self.flexor = Flexor( - actuator=self.actuator, - motion_span=self.motion_span - ) + if self.actuator: + self.flexor = Flexor( + actuator=self.actuator, + motion_span=self.motion_span + ) @property def total_thickness(self): @@ -990,8 +991,9 @@ class ElbowJoint(Model): Cq.Assembly() .add(self.disk_joint.disk(), name="disk", loc=Cq.Location((0, 0, -dz))) .add(self.lip().cut(disk_cut), name="lip", loc=loc_disk.inverse * loc_lip) - .add(self.actuator_mount(), name="act", loc=self.actuator_mount_loc(child=True)) ) + if self.flexor: + result.add(self.actuator_mount(), name="act", loc=self.actuator_mount_loc(child=True)) return result @target(name="parent-lower") @@ -1040,12 +1042,15 @@ class ElbowJoint(Model): Cq.Location((0, 0, 0), (0, 1, 0), 90)) .add(connector, name="connector", loc=loc_net_housing.inverse * axial_offset) - .add(self.actuator_mount(), - name="act", loc=self.actuator_mount_loc(child=False)) #.constrain("housing", "Fixed") #.constrain("connector", "Fixed") #.solve() ) + if self.flexor: + result.add( + self.actuator_mount(), + name="act", + loc=self.actuator_mount_loc(child=False)) return result @assembly() diff --git a/nhf/touhou/houjuu_nue/wing.py b/nhf/touhou/houjuu_nue/wing.py index 0f7419f..3610d4c 100644 --- a/nhf/touhou/houjuu_nue/wing.py +++ b/nhf/touhou/houjuu_nue/wing.py @@ -51,7 +51,7 @@ class WingProfile(Model): disk_joint=DiskJoint( movement_angle=55, ), - hole_diam=6.0, + hole_diam=4.0, angle_neutral=15.0, actuator=LINEAR_ACTUATOR_50, flexor_offset_angle=-15, @@ -65,13 +65,14 @@ class WingProfile(Model): radius_disk=13.0, radius_housing=15.0, ), - hole_pos=[10, 20], - lip_length=50, + hole_pos=[10], + lip_length=30, child_arm_radius=23.0, parent_arm_radius=30.0, hole_diam=4.0, angle_neutral=-30.0, - actuator=LINEAR_ACTUATOR_10, + # The left side wrist is too small for an actuator to work + actuator=None, #LINEAR_ACTUATOR_10, )) # Distance between the two spacers on the elbow, halved wrist_h2: float = 5.0 @@ -90,6 +91,7 @@ class WingProfile(Model): wrist_rotate: float = -30.0 # Position of the elbow axle with 0 being bottom and 1 being top (flipped on the left side) elbow_axle_pos: float = 0.3 + wrist_axle_pos: float = 0.0 # False for the right side, True for the left side flip: bool @@ -103,9 +105,8 @@ class WingProfile(Model): self.elbow_axle_pos = 1 - self.elbow_axle_pos self.elbow_axle_loc = self.elbow_bot_loc * Cq.Location.from2d(0, self.elbow_height * self.elbow_axle_pos) if self.flip: - self.wrist_axle_loc = self.wrist_bot_loc * Cq.Location.from2d(0, self.wrist_height / 2) - else: - self.wrist_axle_loc = self.wrist_bot_loc + self.wrist_axle_pos = 1 - self.wrist_axle_pos + self.wrist_axle_loc = self.wrist_bot_loc * Cq.Location.from2d(0, self.wrist_height * self.wrist_axle_pos) assert self.elbow_joint.total_thickness < min(self.s1_thickness, self.s2_thickness) assert self.wrist_joint.total_thickness < min(self.s2_thickness, self.s3_thickness) @@ -126,7 +127,8 @@ class WingProfile(Model): """ s3 does not need to duck under s2 """ - return self.s1_thickness - 2 * self.panel_thickness + extra = 2 * self.panel_thickness if self.flip else 0 + return self.s1_thickness - 2 * self.panel_thickness - extra @submodel(name="shoulder-joint") def submodel_shoulder_joint(self) -> Model: @@ -395,7 +397,7 @@ class WingProfile(Model): for p in points ]) ) - def _joint_extension_profile( + def _child_joint_extension_profile( self, axle_loc: Cq.Location, radius: float, @@ -640,8 +642,10 @@ class WingProfile(Model): return extrude_with_markers(profile, self.panel_thickness, tags, reverse=front) @target(name="profile-s2-bridge", kind=TargetKind.DXF) def profile_s2_bridge(self) -> Cq.Workplane: + # FIXME: Leave some margin here so we can glue the panels + # Generates the extension profile, which is required on both sides - profile = self._joint_extension_profile( + profile = self._child_joint_extension_profile( axle_loc=self.wrist_axle_loc, radius=self.wrist_height * (0.5 if self.flip else 1), angle_span=self.wrist_joint.motion_span, @@ -881,25 +885,24 @@ class WingProfile(Model): if "wrist" in parts: angle = self.wrist_joint.motion_span * elbow_wrist_deflection result.add(self.wrist_joint.assembly(angle=angle), name="wrist") + wrist_n_holes = len(self.wrist_joint.hole_pos) if "s2" in parts and "wrist" in parts: # Mounted backwards to bend in other direction - ( - result - .constrain("s2/wrist_top?conn0", f"wrist/parent_upper/lip?conn_{tag_bot}0", "Plane") - .constrain("s2/wrist_top?conn1", f"wrist/parent_upper/lip?conn_{tag_bot}1", "Plane") - .constrain("s2/wrist_bot?conn0", f"wrist/parent_upper/lip?conn_{tag_top}0", "Plane") - .constrain("s2/wrist_bot?conn1", f"wrist/parent_upper/lip?conn_{tag_top}1", "Plane") - ) + for i in range(wrist_n_holes): + ( + result + .constrain(f"s2/wrist_top?conn{i}", f"wrist/parent_upper/lip?conn_{tag_bot}{i}", "Plane") + .constrain(f"s2/wrist_bot?conn{i}", f"wrist/parent_upper/lip?conn_{tag_top}{i}", "Plane") + ) if "s3" in parts: result.add(self.assembly_s3(), name="s3") if "s3" in parts and "wrist" in parts: - ( - result - .constrain("s3/wrist_top?conn0", f"wrist/child/lip?conn_{tag_bot}0", "Plane") - .constrain("s3/wrist_top?conn1", f"wrist/child/lip?conn_{tag_bot}1", "Plane") - .constrain("s3/wrist_bot?conn0", f"wrist/child/lip?conn_{tag_top}0", "Plane") - .constrain("s3/wrist_bot?conn1", f"wrist/child/lip?conn_{tag_top}1", "Plane") - ) + for i in range(wrist_n_holes): + ( + result + .constrain(f"s3/wrist_top?conn{i}", f"wrist/child/lip?conn_{tag_bot}{i}", "Plane") + .constrain(f"s3/wrist_bot?conn{i}", f"wrist/child/lip?conn_{tag_top}{i}", "Plane") + ) if len(parts) > 1: result.solve() @@ -1095,6 +1098,8 @@ class WingL(WingProfile): arrow_height: float = 120.0 flip: bool = True + elbow_axle_pos: float = 0.4 + wrist_axle_pos: float = 0.5 def __post_init__(self): assert self.wrist_height <= self.shoulder_joint.height