diff --git a/nhf/touhou/houjuu_nue/electronics.py b/nhf/touhou/houjuu_nue/electronics.py index 84dc49f..a17c819 100644 --- a/nhf/touhou/houjuu_nue/electronics.py +++ b/nhf/touhou/houjuu_nue/electronics.py @@ -354,7 +354,8 @@ class Flexor: line_slack: float = 0.0 def __post_init__(self): - assert self.line_slack <= self.line_length < self.actuator.stroke_length + assert self.line_slack <= self.line_length, f"Insufficient length: {self.line_slack} >= {self.line_length}" + assert self.line_slack < self.actuator.stroke_length @property def mount_height(self): diff --git a/nhf/touhou/houjuu_nue/joints.py b/nhf/touhou/houjuu_nue/joints.py index 4be4bae..9884a88 100644 --- a/nhf/touhou/houjuu_nue/joints.py +++ b/nhf/touhou/houjuu_nue/joints.py @@ -1121,8 +1121,6 @@ class ElbowJoint(Model): parent_arm_radius: float = 40.0 lip_thickness: float = 5.0 - # Extra bit on top of the lip to connect to actuator mount - child_lip_extra_length: float = 1.0 lip_length: float = 60.0 hole_pos: list[float] = field(default_factory=lambda: [12, 24]) parent_arm_width: float = 10.0 @@ -1149,6 +1147,8 @@ class ElbowJoint(Model): flexor_child_arm_radius: Optional[float] = None flexor_line_length: float = 0.0 flexor_line_slack: float = 0.0 + flexor_parent_angle_fix: Optional[float] = 180.0 + flexor_child_angle_fix: Optional[float] = None def __post_init__(self): assert self.child_arm_radius > self.disk_joint.radius_housing @@ -1220,10 +1220,15 @@ class ElbowJoint(Model): # Moves the hole to be some distance apart from 0 mount_r, mount_loc_angle, mount_parent_r = self.flexor.open_pos() loc_span = Cq.Location.from2d(mount_r if child else mount_parent_r, 0) - alpha = (-mount_loc_angle if child else 0) + 180 - self.flexor_offset_angle + if self.flexor_parent_angle_fix is not None: + alpha = (-mount_loc_angle if child else 0) + self.flexor_parent_angle_fix - self.flexor_offset_angle + elif self.flexor_child_angle_fix is not None: + alpha = self.flexor_child_angle_fix + (0 if child else mount_loc_angle) + else: + raise ValueError("One of flexor_{parent,child}_angle_fix must be set") loc_rot = Cq.Location.rot2d(alpha) loc = loc_rot * loc_span * loc_mount_orient * loc_mount - return loc.flip_y() if self.flip and not child and not unflip else loc + return loc.flip_y() if self.flip and not unflip else loc def lip(self) -> Cq.Workplane: sign = -1 if self.flip else 1 @@ -1248,7 +1253,7 @@ class ElbowJoint(Model): return mbox.generate() @target(name="child") - def child_joint(self) -> Cq.Assembly: + def child_joint(self, generate_mount: bool=False) -> Cq.Assembly: angle = -self.disk_joint.tongue_span / 2 dz = self.disk_joint.disk_thickness / 2 # We need to ensure the disk is on the "other" side so @@ -1265,31 +1270,40 @@ class ElbowJoint(Model): loc_cut_rel = Cq.Location((0, self.disk_joint.spring.radius_inner, -self.disk_joint.disk_bot_thickness)) disk_cut = self.disk_joint._disk_cut().located( loc_lip.inverse * loc_cut_rel * loc_disk) - lip_extra = Cq.Solid.makeBox( - length=self.child_lip_extra_length, - width=self.total_thickness, - height=self.lip_thickness, - ).located(Cq.Location(( - self.lip_length / 2, - -self.total_thickness / 2, - 0, - ))) + #lip_extra = Cq.Solid.makeBox( + # length=self.child_lip_extra_length, + # width=self.total_thickness, + # height=self.lip_thickness, + #).located(Cq.Location(( + # self.lip_length / 2, + # -self.total_thickness / 2, + # 0, + #))) result = ( Cq.Assembly() .add(self.disk_joint.disk(), name="disk", loc=loc_rot_neutral * Cq.Location((0, 0, -dz), (0,0,1), angle)) .add(self.lip().cut(disk_cut), name="lip", loc=loc_rot_neutral * loc_disk.inverse * loc_lip) - .add(lip_extra, name="lip_extra", - loc=loc_rot_neutral * loc_disk.inverse * loc_lip) + #.add(lip_extra, name="lip_extra", + # loc=loc_rot_neutral * loc_disk.inverse * loc_lip) ) # Orientes the hole surface so it faces +X loc_thickness = Cq.Location((-self.lip_thickness, 0, 0), (0, 1, 0), 90) if self.flexor: + loc_mount = self.actuator_mount_loc(child=True, unflip=True) result.add( - self.actuator_mount(), + Cq.Edge.makeLine((-1,0,0), (1,0,0)), name="act", - loc=self.actuator_mount_loc(child=True) * loc_thickness) + loc=loc_mount) + if generate_mount: + # Orientes the hole surface so it faces +X + loc_thickness = Cq.Location((-self.lip_thickness, 0, 0), (0, 1, 0), 90) + result.add( + self.actuator_mount(), + name="act_mount", + loc=loc_mount * loc_thickness, + ) return result @target(name="parent-lower") @@ -1342,17 +1356,18 @@ class ElbowJoint(Model): #.solve() ) if self.flexor: + loc_mount = self.actuator_mount_loc(child=False, unflip=True) result.add( Cq.Edge.makeLine((-1,0,0), (1,0,0)), name="act", - loc=self.actuator_mount_loc(child=False, unflip=True)) + loc=loc_mount) if generate_mount: # Orientes the hole surface so it faces +X loc_thickness = Cq.Location((-self.lip_thickness, 0, 0), (0, 1, 0), 90) result.add( self.actuator_mount(), name="act_mount", - loc=self.actuator_mount_loc(child=False, unflip=True) * loc_thickness + loc=loc_mount * loc_thickness ) return result @@ -1364,7 +1379,7 @@ class ElbowJoint(Model): assert 0 <= angle <= self.motion_span result = ( Cq.Assembly() - .addS(self.child_joint(), name="child", + .addS(self.child_joint(generate_mount=generate_mount), name="child", role=Role.CHILD, material=self.material) .addS(self.parent_joint_lower(), name="parent_lower", role=Role.CASING, material=self.material) diff --git a/nhf/touhou/houjuu_nue/wing.py b/nhf/touhou/houjuu_nue/wing.py index 0e0873b..9d057dd 100644 --- a/nhf/touhou/houjuu_nue/wing.py +++ b/nhf/touhou/houjuu_nue/wing.py @@ -23,9 +23,6 @@ from nhf.touhou.houjuu_nue.electronics import ( import nhf.utils ELBOW_PARAMS = dict( - disk_joint=DiskJoint( - movement_angle=55, - ), hole_diam=4.0, actuator=LINEAR_ACTUATOR_50, parent_arm_width=15, @@ -508,6 +505,8 @@ class WingProfile(Model): loc_ext = loc_bot if bot else loc_top loc_tip = loc_top if bot else loc_bot theta = math.radians(angle_span * (median if child else 1 - median)) + if self.flip: + axle_pos = 1 - axle_pos y_sign = -1 if bot else 1 sign = -1 if child else 1 dh = axle_pos * height * (overestimate - 1) @@ -736,11 +735,14 @@ class WingProfile(Model): ) return profile def surface_s2(self, front: bool = True) -> Cq.Workplane: - loc_elbow = Cq.Location.rot2d(self.elbow_rotate) * self.elbow_joint.child_arm_loc() + rot_elbow = Cq.Location.rot2d(self.elbow_rotate) + loc_elbow = rot_elbow * self.elbow_joint.child_arm_loc() rot_wrist = Cq.Location.rot2d(self.wrist_rotate) loc_wrist = rot_wrist * self.wrist_joint.parent_arm_loc() tags = [ ("elbow", self.elbow_axle_loc * loc_elbow), + ("elbow_act", self.elbow_axle_loc * rot_elbow * + self.elbow_joint.actuator_mount_loc(child=True)), ("wrist", self.wrist_axle_loc * loc_wrist), ("wrist_act", self.wrist_axle_loc * rot_wrist * self.wrist_joint.actuator_mount_loc()), @@ -773,6 +775,17 @@ class WingProfile(Model): segment_thickness=self.s2_thickness, child=True, ) + @submodel(name="spacer-s1-elbow-act") + def spacer_s2_elbow_act(self) -> MountingBox: + return MountingBox( + length=self.s2_thickness, + width=self.s2_thickness, + thickness=self.spacer_thickness, + holes=[Hole(x=0,y=0)], + centred=(True, True), + hole_diam=self.elbow_joint.hole_diam, + centre_left_right_tags=True, + ) @submodel(name="spacer-s2-wrist") def spacer_s2_wrist(self) -> MountingBox: return self._spacer_from_disk_joint( @@ -822,6 +835,7 @@ class WingProfile(Model): ) for o, t in [ (self.spacer_s2_elbow(), "elbow"), + (self.spacer_s2_elbow_act(), "elbow_act"), (self.spacer_s2_wrist(), "wrist"), (self.spacer_s2_wrist_act(), "wrist_act"), ]: @@ -1055,11 +1069,13 @@ class WingR(WingProfile): elbow_height: float = 111.0 elbow_rotate: float = 10.0 elbow_joint: ElbowJoint = field(default_factory=lambda: ElbowJoint( + disk_joint=DiskJoint( + movement_angle=55, + ), flexor_offset_angle=15, flexor_mount_angle_child=-75, flexor_child_arm_radius=None, angle_neutral=10.0, - child_lip_extra_length=8, flip=False, **ELBOW_PARAMS )) @@ -1288,24 +1304,32 @@ class WingR(WingProfile): @dataclass(kw_only=True) class WingL(WingProfile): - elbow_bot_loc: Cq.Location = Cq.Location.from2d(260.0, 110.0, 0.0) - elbow_height: float = 90.0 + elbow_bot_loc: Cq.Location = Cq.Location.from2d(260.0, 105.0, 0.0) + elbow_height: float = 95.0 elbow_rotate: float = 15.0 elbow_joint: ElbowJoint = field(default_factory=lambda: ElbowJoint( - angle_neutral=30.0, - flexor_mount_angle_child=170, - flexor_mount_angle_parent=-30, - flexor_line_length=30.0, - flexor_line_slack=5.0, + disk_joint=DiskJoint( + movement_angle=50, + ), + angle_neutral=25.0, + flexor_mount_angle_child=220, + flexor_mount_angle_parent=0, + flexor_line_length=50.0, + flexor_line_slack=10.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, + flexor_offset_angle=0, + flexor_child_angle_fix=85, + flexor_parent_angle_fix=None, + flexor_child_arm_radius=50.0, + parent_arm_radius=50.0, + child_arm_radius=50.0, flexor_pos_smaller=False, flip=True, **ELBOW_PARAMS )) + elbow_axle_pos: float = 0.53 + elbow_joint_overlap_median: float = 0.5 wrist_angle: float = 0.0 wrist_bot_loc: Cq.Location = Cq.Location.from2d(460.0, -10.0, -45.0) @@ -1324,9 +1348,7 @@ class WingL(WingProfile): arrow_height: float = 120.0 flip: bool = True - elbow_axle_pos: float = 0.5 wrist_axle_pos: float = 0.5 - elbow_joint_overlap_median: float = 0.5 wrist_joint_overlap_median: float = 0.5