From 340aa7c6daecadad350926ab12cb3053e8a62180 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sun, 21 Jul 2024 22:13:56 -0700 Subject: [PATCH] feat: Subduct s2 into s1. Off-centre elbow --- nhf/touhou/houjuu_nue/wing.py | 137 ++++++++++++++++------------------ 1 file changed, 63 insertions(+), 74 deletions(-) diff --git a/nhf/touhou/houjuu_nue/wing.py b/nhf/touhou/houjuu_nue/wing.py index f34869a..fe99ad4 100644 --- a/nhf/touhou/houjuu_nue/wing.py +++ b/nhf/touhou/houjuu_nue/wing.py @@ -52,8 +52,6 @@ class WingProfile(Model): # Distance between the two spacers on the elbow, halved elbow_h2: float = 5.0 - s2_thickness: float = 25.0 - wrist_joint: ElbowJoint = field(default_factory=lambda: ElbowJoint( disk_joint=DiskJoint( movement_angle=30, @@ -71,8 +69,6 @@ class WingProfile(Model): # Distance between the two spacers on the elbow, halved wrist_h2: float = 5.0 - s3_thickness: float = 25.0 - mat_panel: Material = Material.ACRYLIC_TRANSLUSCENT mat_bracket: Material = Material.ACRYLIC_TRANSPARENT mat_hs_joint: Material = Material.PLASTIC_PLA @@ -85,6 +81,8 @@ class WingProfile(Model): wrist_height: float elbow_rotate: float = -5.0 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 # False for the right side, True for the left side flip: bool @@ -94,7 +92,9 @@ class WingProfile(Model): self.elbow_top_loc = self.elbow_bot_loc * Cq.Location.from2d(0, self.elbow_height) self.wrist_top_loc = self.wrist_bot_loc * Cq.Location.from2d(0, self.wrist_height) - self.elbow_axle_loc = self.elbow_bot_loc * Cq.Location.from2d(0, self.elbow_height / 2) + if self.flip: + 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: @@ -108,6 +108,19 @@ class WingProfile(Model): self.shoulder_joint.child_guard_width = self.s1_thickness + self.panel_thickness * 2 assert self.spacer_thickness == self.root_joint.child_mount_thickness + @property + def s2_thickness(self) -> float: + """ + s2 needs to duck under s1, so its thinner + """ + return self.s1_thickness - 2 * self.panel_thickness + @property + def s3_thickness(self) -> float: + """ + s3 does not need to duck under s2 + """ + return self.s1_thickness - 2 * self.panel_thickness + @submodel(name="shoulder-joint") def submodel_shoulder_joint(self) -> Model: return self.shoulder_joint @@ -343,27 +356,6 @@ class WingProfile(Model): """ return None - def _elbow_joint_retract_cut_polygon(self, loc: Cq.Location) -> Cq.Sketch: - """ - Creates a cutting polygon for removing the contraction part of a joint - """ - theta = math.radians(self.elbow_joint.motion_span) - h = self.elbow_height - dx = h * math.tan(theta / 2) - dy = h - sign = -1 if self.flip else 1 - points = [ - (0, 0), - (dx, sign * dy), - (-dx, sign * dy), - ] - return ( - Cq.Sketch() - .polygon([ - (loc * Cq.Location.from2d(*p)).to2d_pos() - for p in points - ]) - ) def _wrist_joint_retract_cut_polygon(self, loc: Cq.Location) -> Optional[Cq.Sketch]: """ Creates a cutting polygon for removing the contraction part of a joint @@ -422,6 +414,44 @@ class WingProfile(Model): .assemble() ) + def _parent_joint_extension_profile( + self, + loc_axle: Cq.Location, + loc_bot: Cq.Location, + loc_top: Cq.Location, + angle_span: float, + bot: bool = True + ) -> Cq.Sketch: + """ + Generates a sector-like profile on the child side of a panel to + accomodate for joint rotation + """ + sign = -1 if bot else 1 + + loc_tip = loc_top if bot else loc_bot + loc_arc_right = loc_bot if bot else loc_top + loc_rel_arc_right = loc_axle.inverse * loc_arc_right + loc_arc_left = loc_axle * Cq.Location.rot2d(sign * angle_span) * loc_rel_arc_right + loc_arc_middle = loc_axle * Cq.Location.rot2d(sign * angle_span / 2) * loc_rel_arc_right + + return ( + Cq.Sketch() + .segment( + loc_tip.to2d_pos(), + loc_arc_right.to2d_pos(), + ) + .arc( + loc_arc_right.to2d_pos(), + loc_arc_middle.to2d_pos(), + loc_arc_left.to2d_pos(), + ) + .segment( + loc_tip.to2d_pos(), + loc_arc_left.to2d_pos(), + ) + .assemble() + ) + def _assembly_insert_spacer( self, @@ -492,9 +522,6 @@ class WingProfile(Model): self.profile() .reset() .polygon(self._mask_elbow(), mode='i') - .reset() - .push([self.elbow_axle_loc]) - .each(self._elbow_joint_retract_cut_polygon, mode='s') ) return profile def surface_s1(self, front: bool = True) -> Cq.Workplane: @@ -516,35 +543,6 @@ class WingProfile(Model): tags = tags_shoulder + tags_elbow return extrude_with_markers( profile, self.panel_thickness, tags, reverse=front) - @target(name="profile-s1-bridge", kind=TargetKind.DXF) - def profile_s1_bridge(self) -> Cq.Workplane: - return ( - self.profile() - #.reset() - #.polygon(self._mask_elbow(), mode='i') - .reset() - .push([self.elbow_axle_loc]) - .each(self._elbow_joint_retract_cut_polygon, mode='i') - .reset() - .push([self.elbow_axle_loc]) - .each(lambda loc: self._joint_extension_profile( - axle_loc=self.elbow_axle_loc, - radius=self.elbow_height / 2, - angle_span=self.elbow_joint.motion_span, - bot=not self.flip, - ), mode='a') - ) - def surface_s1_bridge(self, front: bool = True) -> Cq.Workplane: - profile = self.profile_s1_bridge() - loc_elbow = Cq.Location.rot2d(self.elbow_rotate) * self.elbow_joint.parent_arm_loc() - tags = [ - ("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)), - ] - return extrude_with_markers( - profile, self.panel_thickness, tags, reverse=not front) @submodel(name="spacer-s1-shoulder") def spacer_s1_shoulder(self) -> MountingBox: holes = [ @@ -576,14 +574,6 @@ class WingProfile(Model): material=self.mat_panel, role=self.role_panel) .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)