diff --git a/nhf/parts/box.py b/nhf/parts/box.py index 57a5d63..9aac091 100644 --- a/nhf/parts/box.py +++ b/nhf/parts/box.py @@ -59,6 +59,7 @@ class MountingBox(Model): generate_reverse_tags: bool = False centre_bot_top_tags: bool = False + centre_left_right_tags: bool = False # Determines the position of side tags flip_y: bool = False @@ -103,8 +104,12 @@ class MountingBox(Model): reverse_plane.moveTo(hole.x, hole.y).tagPlane(hole.rev_tag, '-Z') if self.generate_side_tags: - result.faces("Z").val().Center()).tagPlane("left") - result.faces(">Y").workplane(origin=result.vertices("Y and >Z").val().Center()).tagPlane("right") + if self.centre_left_right_tags: + result.faces("Z").val().Center()).tagPlane("left") + result.faces(">Y").workplane(origin=result.edges(">Y and >Z").val().Center()).tagPlane("right") + else: + result.faces("Z").val().Center()).tagPlane("left") + result.faces(">Y").workplane(origin=result.vertices("Y and >Z").val().Center()).tagPlane("right") c_y = ">Y" if self.flip_y else " self.disk_joint.radius_housing @@ -977,15 +979,13 @@ class ElbowJoint(Model): axle is at position 0, and parent direction is -X """ return Cq.Location.from2d(-self.parent_arm_radius, 0, 0) - def child_arm_loc(self, flip: bool = False, angle: float = 0.0) -> Cq.Location: + def child_arm_loc(self, angle: float = 0.0) -> Cq.Location: """ 2d Location of the centre of the arm surface on the child side, assuming axle is at position 0, and parent direction is -X - - Set `flip=True` to indicate that the joint is supposed to be installed upside down """ result = Cq.Location.rot2d(self.angle_neutral + angle) * Cq.Location.from2d(self.child_arm_radius, 0, 180) - return result.flip_y() if flip else result + return result.flip_y() if self.flip else result def actuator_mount(self) -> Cq.Workplane: holes = [ Hole(x=0, y=0, tag="mount"), @@ -1000,26 +1000,31 @@ class ElbowJoint(Model): generate_side_tags=False, ) return mbox.generate() - def actuator_mount_loc(self, child: bool) -> Cq.Location: - # Orientes the hole surface so it faces +X - loc_thickness = Cq.Location((-self.lip_thickness, 0, 0), (0, 1, 0), 90) + def actuator_mount_loc( + self, + child: bool = False, + # If set to true, use the local coordinate system + unflip: bool = False, + ) -> Cq.Location: # Moves the hole so the axle of the mount is perpendicular to it loc_mount = Cq.Location.from2d(self.flexor.mount_height, 0) * Cq.Location.rot2d(180) loc_mount_orient = Cq.Location.rot2d(self.flexor_mount_rot * (-1 if child else 1)) # 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) - r = (-mount_loc_angle if child else 0) + 180 - loc_rot = Cq.Location.rot2d(r + self.flexor_offset_angle) - return loc_rot * loc_span * loc_mount_orient * loc_mount * loc_thickness + r = (-mount_loc_angle - self.angle_neutral if child else 0) + 180 + self.flexor_offset_angle + loc_rot = Cq.Location.rot2d(r) + 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 def lip(self) -> Cq.Workplane: + sign = -1 if self.flip else 1 holes = [ h for i, x in enumerate(self.hole_pos) for h in [ - Hole(x=x, tag=f"conn_top{i}"), - Hole(x=-x, tag=f"conn_bot{i}") + Hole(x=sign * x, tag=f"conn_top{i}"), + Hole(x=-sign * x, tag=f"conn_bot{i}") ] ] mbox = MountingBox( @@ -1047,17 +1052,22 @@ class ElbowJoint(Model): Cq.Location((-lip_dz, 0, 0), (1, 0, 0), 90) * Cq.Location((0, 0, 0), (0, 1, 0), 90) ) - loc_disk = flip_x * flip_z * Cq.Location((-self.child_arm_radius, 0, 0), (0, 0, 1), angle) + loc_disk = flip_x * flip_z * Cq.Location((-self.child_arm_radius, 0, 0)) 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) result = ( Cq.Assembly() - .add(self.disk_joint.disk(), name="disk", loc=Cq.Location((0, 0, -dz))) + .add(self.disk_joint.disk(), name="disk", loc=Cq.Location((0, 0, -dz), (0,0,1), angle)) .add(self.lip().cut(disk_cut), name="lip", loc=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: - result.add(self.actuator_mount(), name="act", loc=self.actuator_mount_loc(child=True)) + result.add( + self.actuator_mount(), + name="act", + loc=self.actuator_mount_loc(child=True) * loc_thickness) return result @target(name="parent-lower") @@ -1090,31 +1100,32 @@ class ElbowJoint(Model): ) housing = self.disk_joint.housing_upper() housing_loc = Cq.Location( - (0, 0, 0), + (0, 0, housing_dz), (0, 0, 1), -self.disk_joint.tongue_span / 2 + self.angle_neutral ) lip_dz = self.lip_thickness - loc_net_housing = axial_offset * housing_loc result = ( Cq.Assembly() - .add(housing, name="housing", loc=Cq.Location((0, 0, housing_dz))) + # rotate so 0 degree is at +X + .add(housing, name="housing", loc=housing_loc) .add(self.lip(), name="lip", loc= - loc_net_housing.inverse * + axial_offset.inverse * Cq.Location((0, 0, 0), (0, 1, 0), 180) * Cq.Location((-lip_dz, 0, 0), (1, 0, 0), 90) * Cq.Location((0, 0, 0), (0, 1, 0), 90)) - .add(connector, name="connector", - loc=loc_net_housing.inverse * axial_offset) + .add(connector, name="connector") #.constrain("housing", "Fixed") #.constrain("connector", "Fixed") #.solve() ) if self.flexor: + # 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", - loc=self.actuator_mount_loc(child=False)) + loc=self.actuator_mount_loc(child=False) * loc_thickness) return result @assembly() diff --git a/nhf/touhou/houjuu_nue/wing.py b/nhf/touhou/houjuu_nue/wing.py index a548af8..2b0f7a2 100644 --- a/nhf/touhou/houjuu_nue/wing.py +++ b/nhf/touhou/houjuu_nue/wing.py @@ -14,7 +14,7 @@ from nhf.parts.box import box_with_centre_holes, MountingBox, Hole from nhf.parts.joints import HirthJoint from nhf.parts.planar import extrude_with_markers from nhf.touhou.houjuu_nue.joints import RootJoint, ShoulderJoint, ElbowJoint, DiskJoint -from nhf.touhou.houjuu_nue.electronics import LINEAR_ACTUATOR_10, LINEAR_ACTUATOR_50, ElectronicBoard +from nhf.touhou.houjuu_nue.electronics import LINEAR_ACTUATOR_21, LINEAR_ACTUATOR_50, ElectronicBoard import nhf.utils @dataclass(kw_only=True) @@ -57,7 +57,8 @@ class WingProfile(Model): hole_diam=4.0, angle_neutral=15.0, actuator=LINEAR_ACTUATOR_50, - flexor_offset_angle=-15, + flexor_offset_angle=0, + flip=False, )) # Distance between the two spacers on the elbow, halved elbow_h2: float = 5.0 @@ -74,7 +75,9 @@ class WingProfile(Model): parent_arm_radius=30.0, hole_diam=4.0, angle_neutral=-30.0, - actuator=LINEAR_ACTUATOR_10, + actuator=LINEAR_ACTUATOR_21, + flexor_offset_angle=0, + flip=True, )) # Distance between the two spacers on the elbow, halved wrist_h2: float = 5.0 @@ -608,12 +611,15 @@ class WingProfile(Model): ("shoulder_top", Cq.Location.from2d(0, h + shoulder_h, 270)), ] h = self.elbow_height / 2 - loc_elbow = Cq.Location.rot2d(self.elbow_rotate) * self.elbow_joint.parent_arm_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_act", self.elbow_axle_loc * rot_elbow * + self.elbow_joint.actuator_mount_loc()), ] profile = self.profile_s1() tags = tags_shoulder + tags_elbow @@ -639,6 +645,17 @@ class WingProfile(Model): segment_thickness=self.s1_thickness, dx=self.elbow_h2, ) + @submodel(name="spacer-s1-elbow-act") + def spacer_s1_elbow_act(self) -> MountingBox: + return MountingBox( + length=self.s1_thickness, + width=self.s1_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, + ) @assembly() def assembly_s1(self) -> Cq.Assembly: result = ( @@ -651,13 +668,17 @@ class WingProfile(Model): .constrain("front@faces@>Z", "back@faces@ Cq.Workplane: - loc_elbow = Cq.Location.rot2d(self.elbow_rotate) * self.elbow_joint.child_arm_loc(flip=self.flip) + 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)), ] - loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.parent_arm_loc() + rot_wrist = Cq.Location.rot2d(self.wrist_rotate) + loc_wrist = rot_wrist * self.wrist_joint.parent_arm_loc() tags_wrist = [ ("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 @@ -750,6 +774,17 @@ class WingProfile(Model): segment_thickness=self.s2_thickness, dx=self.wrist_h2, ) + @submodel(name="spacer-s2-wrist-act") + def spacer_s2_wrist_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.wrist_joint.hole_diam, + centre_left_right_tags=True, + ) @assembly() def assembly_s2(self) -> Cq.Assembly: result = ( @@ -770,10 +805,15 @@ class WingProfile(Model): .constrain("back?wrist_bot", "bridge_back?wrist_bot", "Plane") .constrain("back?wrist_top", "bridge_back?wrist_top", "Plane") ) - for t in ["elbow_bot", "elbow_top", "wrist_bot", "wrist_top"]: + 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_wrist_act(), "wrist_act"), + ]: is_top = t.endswith("_top") is_parent = t.startswith("elbow") - o = self.spacer_s2_elbow() if is_parent else self.spacer_s2_wrist() self._assembly_insert_spacer( result, o.generate(), @@ -793,7 +833,7 @@ class WingProfile(Model): return profile def surface_s3(self, front: bool = True) -> Cq.Workplane: - loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc(flip=not self.flip) + loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc() tags = [ ("wrist_bot", self.wrist_axle_loc * loc_wrist * Cq.Location.from2d(0, self.wrist_h2)), @@ -807,7 +847,7 @@ class WingProfile(Model): profile = self.profile_s3_extra() if profile is None: return None - loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc(flip=not self.flip) + loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc() tags = [ ("wrist_bot", self.wrist_axle_loc * loc_wrist * Cq.Location.from2d(0, self.wrist_h2)), @@ -929,32 +969,31 @@ class WingProfile(Model): if "s1" in parts and "elbow" in parts: ( result - .constrain("s1/elbow_top?conn0", f"elbow/parent_upper/lip?conn_{tag_top}0", "Plane") - .constrain("s1/elbow_top?conn1", f"elbow/parent_upper/lip?conn_{tag_top}1", "Plane") - .constrain("s1/elbow_bot?conn0", f"elbow/parent_upper/lip?conn_{tag_bot}0", "Plane") - .constrain("s1/elbow_bot?conn1", f"elbow/parent_upper/lip?conn_{tag_bot}1", "Plane") + .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") ) 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", f"elbow/child/lip?conn_{tag_top}0", "Plane") - .constrain("s2/elbow_top?conn1", f"elbow/child/lip?conn_{tag_top}1", "Plane") - .constrain("s2/elbow_bot?conn0", f"elbow/child/lip?conn_{tag_bot}0", "Plane") - .constrain("s2/elbow_bot?conn1", f"elbow/child/lip?conn_{tag_bot}1", "Plane") + .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") ) 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 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") + .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") ) if "s3" in parts: result.add(self.assembly_s3(), name="s3") @@ -962,8 +1001,8 @@ class WingProfile(Model): 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") + .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") ) if len(parts) > 1: result.solve() @@ -1167,9 +1206,11 @@ class WingL(WingProfile): assert self.wrist_height <= self.shoulder_joint.height self.wrist_bot_loc = self.wrist_bot_loc.with_angle_2d(self.wrist_angle) self.elbow_joint.angle_neutral = 15.0 + self.elbow_joint.flip = True self.elbow_rotate = 5.0 self.wrist_joint.angle_neutral = self.wrist_bot_loc.to2d_rot() + 30.0 self.wrist_rotate = -self.wrist_joint.angle_neutral + self.wrist_joint.flip = False super().__post_init__()