diff --git a/nhf/touhou/houjuu_nue/joints.py b/nhf/touhou/houjuu_nue/joints.py index 70c5d03..e9f78ff 100644 --- a/nhf/touhou/houjuu_nue/joints.py +++ b/nhf/touhou/houjuu_nue/joints.py @@ -76,8 +76,8 @@ class RootJoint(Model): hirth_joint: HirthJoint = field(default_factory=lambda: HirthJoint( radius=25.0, radius_inner=15.0, - tooth_height=7.0, - base_height=5.0, + tooth_height=5.60, + base_height=4.0, n_tooth=24, )) parent_width: float = 85 @@ -100,6 +100,10 @@ class RootJoint(Model): child_width: float = 50.0 child_mount_thickness: float = 25.4 / 4 + def __post_init__(self): + assert self.child_extra_thickness > 0.0 + assert self.parent_thickness >= self.hex_nut.thickness + def corner_pos(self) -> list[tuple[int, int]]: """ Generates a set of points corresponding to the connectorss @@ -113,8 +117,15 @@ class RootJoint(Model): ] @property - def total_height(self) -> float: - return self.parent_thickness + self.hirth_joint.total_height + def child_extra_thickness(self) -> float: + """ + Extra thickness allocated to child for padding + """ + return self.knob.height_thread - self.hirth_joint.joint_height - self.child_mount_thickness - self.parent_thickness + + @property + def base_to_surface_thickness(self) -> float: + return self.hirth_joint.joint_height + self.child_extra_thickness @target(name="parent") def parent(self): @@ -127,9 +138,9 @@ class RootJoint(Model): result = ( Cq.Workplane('XY') .box( - self.parent_width, - self.parent_width, - self.parent_thickness, + length=self.parent_width, + width=self.parent_width, + height=self.parent_thickness, centered=(True, True, False)) .translate((0, 0, -self.parent_thickness)) .edges("|Z") @@ -177,11 +188,11 @@ class RootJoint(Model): result = ( Cq.Workplane('XY') .box( - self.child_height, - self.child_width, - self.hirth_joint.base_height, + length=self.child_height, + width=self.child_width, + height=self.child_extra_thickness + self.hirth_joint.base_height, centered=(True, True, False)) - #.translate((0, 0, -self.base_joint.base_height)) + .translate((0, 0, -self.child_extra_thickness)) #.edges("|Z") #.fillet(self.hs_joint_corner_fillet) .faces(">Z") @@ -190,7 +201,7 @@ class RootJoint(Model): .hole(self.corner_hole_diam) ) # Creates a plane parallel to the holes but shifted to the base - plane = result.faces(">Z").workplane(offset=-self.hirth_joint.base_height) + plane = result.copyWorkplane(Cq.Workplane('XY', origin=(0, 0, -self.child_extra_thickness))) for i, (px, py) in enumerate(conn): plane.moveTo(px, py).tagPlane(f"conn{i}") @@ -232,7 +243,7 @@ class RootJoint(Model): result .addS(self.hex_nut.assembly(), name="hex_nut") .addS(self.knob.assembly(), name="knob", - loc=Cq.Location((0, 0, knob_h * fastener_pos))) + loc=Cq.Location((0, 0, knob_h * fastener_pos - self.parent_thickness))) .constrain("knob/thread", "Fixed") .constrain("hex_nut?bot", "parent?base", "Plane", param=0) .constrain("hex_nut?dirX", "parent@faces@>X", "Axis", param=0) @@ -1071,6 +1082,14 @@ class ElbowJoint(Model): motion_span=self.motion_span ) + def hole_loc_tags(self): + """ + An iterator which iterates through positions of the hole and tags + """ + for i, x in enumerate(self.hole_pos): + yield x, f"conn_top{i}" + yield -x, f"conn_bot{i}" + @property def total_thickness(self): return self.disk_joint.total_thickness diff --git a/nhf/touhou/houjuu_nue/wing.py b/nhf/touhou/houjuu_nue/wing.py index 86a0e23..2be8c1e 100644 --- a/nhf/touhou/houjuu_nue/wing.py +++ b/nhf/touhou/houjuu_nue/wing.py @@ -63,6 +63,7 @@ class WingProfile(Model): angle_neutral=10.0, actuator=LINEAR_ACTUATOR_50, flexor_offset_angle=30, + parent_arm_width=15, flip=False, )) # Distance between the two spacers on the elbow, halved @@ -353,7 +354,7 @@ class WingProfile(Model): def surface_s0(self, top: bool = False) -> Cq.Workplane: base_dx = -(self.base_width - self.root_joint.child_width) / 2 - 10 - base_dy = self.root_joint.hirth_joint.joint_height + base_dy = self.root_joint.base_to_surface_thickness #mid_spacer_loc = ( # Cq.Location.from2d(0, -self.shoulder_width/2) * # self.shoulder_axle_loc * @@ -593,6 +594,25 @@ class WingProfile(Model): ) return mbox + def _spacer_from_disk_joint( + self, + joint: ElbowJoint, + segment_thickness: float, + ) -> MountingBox: + holes = [ + Hole(x, tag=tag) + for x, tag in joint.hole_loc_tags() + ] + mbox = MountingBox( + length=joint.lip_length, + width=segment_thickness, + thickness=self.spacer_thickness, + holes=holes, + hole_diam=joint.hole_diam, + centred=(True, True), + centre_left_right_tags=True, + ) + return mbox @target(name="profile-s1", kind=TargetKind.DXF) def profile_s1(self) -> Cq.Sketch: @@ -615,23 +635,17 @@ class WingProfile(Model): ) return profile def surface_s1(self, front: bool = True) -> Cq.Workplane: - tags_shoulder = [ + rot_elbow = Cq.Location.rot2d(self.elbow_rotate) + loc_elbow = rot_elbow * self.elbow_joint.parent_arm_loc() + tags = [ ("shoulder", Cq.Location((0, self.shoulder_height / 2, 0)) * self.shoulder_joint.child_lip_loc()), - ] - rot_elbow = Cq.Location.rot2d(self.elbow_rotate) - loc_elbow = rot_elbow * self.elbow_joint.parent_arm_loc() - tags_elbow = [ - ("elbow_bot", self.elbow_axle_loc * loc_elbow * - Cq.Location.from2d(0, -self.elbow_h2)), - ("elbow_top", self.elbow_axle_loc * loc_elbow * - Cq.Location.from2d(0, self.elbow_h2)), + ("elbow", self.elbow_axle_loc * loc_elbow), ("elbow_act", self.elbow_axle_loc * rot_elbow * self.elbow_joint.actuator_mount_loc()), ] profile = self.profile_s1() - tags = tags_shoulder + tags_elbow return extrude_with_markers( profile, self.panel_thickness, tags, reverse=front) @submodel(name="spacer-s1-shoulder") @@ -653,10 +667,9 @@ class WingProfile(Model): ) @submodel(name="spacer-s1-elbow") def spacer_s1_elbow(self) -> MountingBox: - return self.spacer_of_joint( + return self._spacer_from_disk_joint( joint=self.elbow_joint, segment_thickness=self.s1_thickness, - dx=self.elbow_h2, ) @submodel(name="spacer-s1-elbow-act") def spacer_s1_elbow_act(self) -> MountingBox: @@ -683,16 +696,14 @@ class WingProfile(Model): ) for o, t in [ (self.spacer_s1_shoulder(), "shoulder"), - (self.spacer_s1_elbow(), "elbow_top"), - (self.spacer_s1_elbow(), "elbow_bot"), + (self.spacer_s1_elbow(), "elbow"), (self.spacer_s1_elbow_act(), "elbow_act"), ]: - is_top = t.endswith("_top") self._assembly_insert_spacer( result, o.generate(), point_tag=t, - flipped=not is_top, + flipped=True, ) return result.solve() @@ -743,24 +754,21 @@ 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() - tags_elbow = [ - ("elbow_bot", self.elbow_axle_loc * loc_elbow * - Cq.Location.from2d(0, self.elbow_h2)), - ("elbow_top", self.elbow_axle_loc * loc_elbow * - Cq.Location.from2d(0, -self.elbow_h2)), - ] rot_wrist = Cq.Location.rot2d(self.wrist_rotate) loc_wrist = rot_wrist * self.wrist_joint.parent_arm_loc() - tags_wrist = [ + tags = [ + ("elbow", self.elbow_axle_loc * loc_elbow), + ("wrist", self.wrist_axle_loc * loc_wrist), + ("wrist_act", self.wrist_axle_loc * rot_wrist * + self.wrist_joint.actuator_mount_loc()), + + # for mounting the bridge only ("wrist_bot", self.wrist_axle_loc * loc_wrist * Cq.Location.from2d(0, -self.wrist_h2)), ("wrist_top", self.wrist_axle_loc * loc_wrist * Cq.Location.from2d(0, self.wrist_h2)), - ("wrist_act", self.wrist_axle_loc * rot_wrist * - self.wrist_joint.actuator_mount_loc()), ] profile = self.profile_s2() - tags = tags_elbow + tags_wrist return extrude_with_markers(profile, self.panel_thickness, tags, reverse=front) def surface_s2_bridge(self, front: bool = True) -> Optional[Cq.Workplane]: profile = self.profile_s2_bridge() @@ -777,17 +785,15 @@ class WingProfile(Model): profile, self.panel_thickness, tags, reverse=not front) @submodel(name="spacer-s2-elbow") def spacer_s2_elbow(self) -> MountingBox: - return self.spacer_of_joint( + return self._spacer_from_disk_joint( joint=self.elbow_joint, segment_thickness=self.s2_thickness, - dx=self.elbow_h2, ) @submodel(name="spacer-s2-wrist") def spacer_s2_wrist(self) -> MountingBox: - return self.spacer_of_joint( + return self._spacer_from_disk_joint( joint=self.wrist_joint, segment_thickness=self.s2_thickness, - dx=self.wrist_h2, ) @submodel(name="spacer-s2-wrist-act") def spacer_s2_wrist_act(self) -> MountingBox: @@ -831,20 +837,16 @@ class WingProfile(Model): .constrain("back?wrist_top", "bridge_back?wrist_top", "Plane") ) for o, t in [ - (self.spacer_s2_elbow(), "elbow_bot"), - (self.spacer_s2_elbow(), "elbow_top"), - (self.spacer_s2_wrist(), "wrist_bot"), - (self.spacer_s2_wrist(), "wrist_top"), + (self.spacer_s2_elbow(), "elbow"), + (self.spacer_s2_wrist(), "wrist"), (self.spacer_s2_wrist_act(), "wrist_act"), ]: - is_top = t.endswith("_top") is_parent = t.startswith("elbow") self._assembly_insert_spacer( result, o.generate(), point_tag=t, - flipped=is_top == is_parent, - #rotate=not is_parent, + flipped=is_parent, ) return result.solve() @@ -878,6 +880,7 @@ class WingProfile(Model): front: bool = True) -> Cq.Workplane: loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc() tags = [ + ("wrist", self.wrist_axle_loc * loc_wrist), ("wrist_bot", self.wrist_axle_loc * loc_wrist * Cq.Location.from2d(0, self.wrist_h2)), ("wrist_top", self.wrist_axle_loc * loc_wrist * @@ -900,10 +903,9 @@ class WingProfile(Model): return extrude_with_markers(profile, self.panel_thickness, tags, reverse=not front) @submodel(name="spacer-s3-wrist") def spacer_s3_wrist(self) -> MountingBox: - return self.spacer_of_joint( + return self._spacer_from_disk_joint( joint=self.wrist_joint, segment_thickness=self.s3_thickness, - dx=self.wrist_h2, ) @assembly() def assembly_s3(self) -> Cq.Assembly: @@ -929,28 +931,26 @@ class WingProfile(Model): .constrain("back?wrist_bot", "extra_back?wrist_bot", "Plane") .constrain("back?wrist_top", "extra_back?wrist_top", "Plane") ) - for t in ["wrist_bot", "wrist_top"]: - is_top = t.endswith("_top") - o = self.spacer_s3_wrist() - self._assembly_insert_spacer( - result, - o.generate(), - point_tag=t, - flipped=is_top, - ) + self._assembly_insert_spacer( + result, + self.spacer_s3_wrist().generate(), + point_tag="wrist", + flipped=True, + ) return result.solve() @assembly() - def assembly(self, - parts: Optional[list[str]] = None, - shoulder_deflection: float = 0.0, - elbow_wrist_deflection: float = 0.0, - root_offset: int = 5, - fastener_pos: float = 0.0, - ignore_fasteners: bool = False, - ignore_electronics: bool = False, - ignore_actuators: bool = False, - ) -> Cq.Assembly(): + def assembly( + self, + parts: Optional[list[str]] = None, + shoulder_deflection: float = 0.0, + elbow_wrist_deflection: float = 0.0, + root_offset: int = 5, + fastener_pos: float = 0.0, + ignore_fasteners: bool = False, + ignore_electronics: bool = False, + ignore_actuators: bool = False, + ) -> Cq.Assembly(): if parts is None: parts = [ "root", @@ -1013,55 +1013,44 @@ class WingProfile(Model): angle=angle, ignore_actuators=ignore_actuators), name="elbow") if "s1" in parts and "elbow" in parts: - ( - result - .constrain("s1/elbow_top?conn0", "elbow/parent_upper/lip?conn_top0", "Plane") - .constrain("s1/elbow_top?conn1", "elbow/parent_upper/lip?conn_top1", "Plane") - .constrain("s1/elbow_bot?conn0", "elbow/parent_upper/lip?conn_bot0", "Plane") - .constrain("s1/elbow_bot?conn1", "elbow/parent_upper/lip?conn_bot1", "Plane") - ) + for _, tag in self.elbow_joint.hole_loc_tags(): + result.constrain( + f"s1/elbow?{tag}", + f"elbow/parent_upper/lip?{tag}", "Plane") if not ignore_actuators: - ( - result - .constrain("elbow/bracket_back?conn_side", "s1/elbow_act?conn0", "Plane") - ) + result.constrain( + "elbow/bracket_back?conn_side", + "s1/elbow_act?conn0", + "Plane") if "s2" in parts: result.add(self.assembly_s2(), name="s2") if "s2" in parts and "elbow" in parts: - ( - result - .constrain("s2/elbow_top?conn0", "elbow/child/lip?conn_top0", "Plane") - .constrain("s2/elbow_top?conn1", "elbow/child/lip?conn_top1", "Plane") - .constrain("s2/elbow_bot?conn0", "elbow/child/lip?conn_bot0", "Plane") - .constrain("s2/elbow_bot?conn1", "elbow/child/lip?conn_bot1", "Plane") - ) + for _, tag in self.elbow_joint.hole_loc_tags(): + result.constrain( + f"s2/elbow?{tag}", + f"elbow/child/lip?{tag}", "Plane") if "wrist" in parts: angle = self.wrist_joint.motion_span * elbow_wrist_deflection result.add(self.wrist_joint.assembly( angle=angle, ignore_actuators=ignore_actuators), name="wrist") - wrist_n_holes = len(self.wrist_joint.hole_pos) if "s2" in parts and "wrist" in parts: - for i in range(wrist_n_holes): - ( - result - .constrain(f"s2/wrist_top?conn{i}", f"wrist/parent_upper/lip?conn_top{i}", "Plane") - .constrain(f"s2/wrist_bot?conn{i}", f"wrist/parent_upper/lip?conn_bot{i}", "Plane") - ) + for _, tag in self.wrist_joint.hole_loc_tags(): + result.constrain( + f"s2/wrist?{tag}", + f"wrist/parent_upper/lip?{tag}", "Plane") if "s3" in parts: result.add(self.assembly_s3(), name="s3") if "s3" in parts and "wrist" in parts: - for i in range(wrist_n_holes): - ( - result - .constrain(f"s3/wrist_top?conn{i}", f"wrist/child/lip?conn_top{i}", "Plane") - .constrain(f"s3/wrist_bot?conn{i}", f"wrist/child/lip?conn_bot{i}", "Plane") - ) + for _, tag in self.wrist_joint.hole_loc_tags(): + result.constrain( + f"s3/wrist?{tag}", + f"wrist/child/lip?{tag}", "Plane") if not ignore_actuators: - ( - result - .constrain("wrist/bracket_back?conn_side", "s2/wrist_act?conn0", "Plane") - ) + result.constrain( + "wrist/bracket_back?conn_side", + "s2/wrist_act?conn0", + "Plane") if len(parts) > 1: result.solve()