From c12ccf3495210613bf9325580f2292dda2ffdd15 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Tue, 16 Jul 2024 22:26:06 -0700 Subject: [PATCH] feat: Staggered shoulder joint --- nhf/touhou/houjuu_nue/__init__.py | 17 ++++-- nhf/touhou/houjuu_nue/joints.py | 89 +++++++++++++++++++------------ nhf/touhou/houjuu_nue/wing.py | 15 ++++-- 3 files changed, 81 insertions(+), 40 deletions(-) diff --git a/nhf/touhou/houjuu_nue/__init__.py b/nhf/touhou/houjuu_nue/__init__.py index 4602be3..af76e05 100644 --- a/nhf/touhou/houjuu_nue/__init__.py +++ b/nhf/touhou/houjuu_nue/__init__.py @@ -46,15 +46,23 @@ class Parameters(Model): Defines dimensions for the Houjuu Nue cosplay """ - # Harness harness: MH.Harness = field(default_factory=lambda: MH.Harness()) - wing_r1: MW.WingR = field(default_factory=lambda: MW.WingR(name="r1")) - wing_r2: MW.WingR = field(default_factory=lambda: MW.WingR(name="r2")) - wing_r3: MW.WingR = field(default_factory=lambda: MW.WingR(name="r3")) + wing_r1: MW.WingR = field(default_factory=lambda: MW.WingR( + name="r1", + shoulder_joint=MJ.ShoulderJoint(directrix_id=1), + )) + wing_r2: MW.WingR = field(default_factory=lambda: MW.WingR( + name="r2", + )) + wing_r3: MW.WingR = field(default_factory=lambda: MW.WingR( + name="r3", + shoulder_joint=MJ.ShoulderJoint(directrix_id=1), + )) wing_l1: MW.WingL = field(default_factory=lambda: MW.WingL( name="l1", wrist_angle=-45.0, + shoulder_joint=MJ.ShoulderJoint(directrix_id=1), )) wing_l2: MW.WingL = field(default_factory=lambda: MW.WingL( name="l2", @@ -63,6 +71,7 @@ class Parameters(Model): wing_l3: MW.WingL = field(default_factory=lambda: MW.WingL( name="l3", wrist_angle=0.0, + shoulder_joint=MJ.ShoulderJoint(directrix_id=1), )) trident: MT.Trident = field(default_factory=lambda: MT.Trident()) diff --git a/nhf/touhou/houjuu_nue/joints.py b/nhf/touhou/houjuu_nue/joints.py index cff350e..7840715 100644 --- a/nhf/touhou/houjuu_nue/joints.py +++ b/nhf/touhou/houjuu_nue/joints.py @@ -14,7 +14,7 @@ TOL = 1e-6 @dataclass class ShoulderJoint(Model): - height: float = 100.0 + height: float = 60.0 torsion_joint: TorsionJoint = field(default_factory=lambda: TorsionJoint( radius_track=18, radius_rider=18, @@ -30,19 +30,23 @@ class ShoulderJoint(Model): thickness=1.3, height=7.5, ), + rider_slot_begin=0, + rider_n_slots=2, + rider_slot_span=15, )) # On the parent side, drill vertical holes - parent_conn_hole_diam: float = 6.0 + parent_conn_hole_diam: float = 4.0 # Position of the holes relative - parent_conn_hole_pos: list[float] = field(default_factory=lambda: [20, 30]) + parent_conn_hole_pos: list[float] = field(default_factory=lambda: [15]) - parent_lip_length: float = 40.0 - parent_lip_width: float = 20.0 - parent_lip_thickness: float = 8.0 + parent_lip_length: float = 25.0 + parent_lip_width: float = 30.0 + parent_lip_thickness: float = 5.0 parent_lip_ext: float = 40.0 - parent_lip_guard_height: float = 10.0 + parent_lip_guard_height: float = 8.0 + parent_root_wall_thickness: float = 25.4 / 16 # Measured from centre of axle child_lip_length: float = 45.0 @@ -52,13 +56,19 @@ class ShoulderJoint(Model): child_conn_hole_pos: list[float] = field(default_factory=lambda: [25, 35]) child_core_thickness: float = 3.0 + # Rotates the torsion joint to avoid collisions or for some other purpose + axis_rotate_bot: float = 225.0 + axis_rotate_top: float = -225.0 - @target(name="shoulder-joint/parent") - def parent(self, - root_wall_thickness: float = 25.4 / 16) -> Cq.Assembly: + directrix_id: int = 0 + + def __post_init__(self): + assert self.parent_lip_length * 2 < self.height + + def parent(self, top: bool = False) -> Cq.Assembly: joint = self.torsion_joint # Thickness of the lip connecting this joint to the wing root - dz = root_wall_thickness + dz = self.parent_root_wall_thickness assert self.parent_lip_width <= joint.radius_track * 2 assert self.parent_lip_ext > joint.radius_track @@ -86,14 +96,24 @@ class ShoulderJoint(Model): loc_dir = Cq.Location((0,0,0), (0, 0, 1), 180) loc_pos = Cq.Location((self.parent_lip_ext - self.parent_lip_thickness, 0, dz)) + rot = -self.axis_rotate_top if top else self.axis_rotate_bot + result = ( Cq.Assembly() - .add(joint.track(), name="track") + .add(joint.track(), name="track", + loc=Cq.Location((0, 0, 0), (0, 0, 1), rot)) .add(lip_guard, name="lip_guard") .add(lip, name="lip", loc=loc_pos * loc_dir * loc_axis) ) return result + @target(name="parent-bot") + def parent_bot(self) -> Cq.Assembly: + return self.parent(top=False) + @target(name="parent-top") + def parent_top(self) -> Cq.Assembly: + return self.parent(top=True) + @property def child_height(self) -> float: """ @@ -103,7 +123,7 @@ class ShoulderJoint(Model): joint = self.torsion_joint return self.height - 2 * joint.total_height + 2 * joint.rider_disk_height - @target(name="shoulder-joint/child") + @target(name="child") def child(self) -> Cq.Assembly: """ Creates the top/bottom shoulder child joint @@ -165,14 +185,17 @@ class ShoulderJoint(Model): centered=(True, True, False), combine='cut') ) + theta = self.torsion_joint.spring.angle_neutral - self.torsion_joint.rider_slot_span loc_rotate = Cq.Location((0, 0, 0), (1, 0, 0), 180) + loc_axis_rotate_bot = Cq.Location((0, 0, 0), (0, 0, 1), self.axis_rotate_bot) + loc_axis_rotate_top = Cq.Location((0, 0, 0), (0, 0, 1), self.axis_rotate_top) result = ( Cq.Assembly() .add(core, name="core", loc=Cq.Location()) .add(joint.rider(rider_slot_begin=-90, reverse_directrix_label=True), name="rider_top", - loc=Cq.Location((0, 0, dh), (0, 0, 1), -90)) + loc=loc_axis_rotate_top * Cq.Location((0, 0, dh), (0, 0, 1), -90) * Cq.Location((0, 0, 0), (0, 0, 1), theta)) .add(joint.rider(rider_slot_begin=180), name="rider_bot", - loc=Cq.Location((0, 0, -dh), (0, 0, 1), -90) * loc_rotate) + loc=loc_axis_rotate_bot * Cq.Location((0, 0, -dh), (0, 0, 1), -90) * loc_rotate) .add(lip, name="lip_top", loc=Cq.Location((0, 0, dh))) .add(lip, name="lip_bot", @@ -181,10 +204,8 @@ class ShoulderJoint(Model): return result @assembly() - def assembly(self, - wing_root_wall_thickness: float = 25.4/16, - ) -> Cq.Assembly: - directrix = 0 + def assembly(self, deflection: float = 0) -> Cq.Assembly: + directrix = self.directrix_id mat = Material.RESIN_TRANSPERENT mat_spring = Material.STEEL_SPRING result = ( @@ -192,27 +213,29 @@ class ShoulderJoint(Model): .addS(self.child(), name="child", role=Role.CHILD, material=mat) .constrain("child/core", "Fixed") - .addS(self.torsion_joint.spring.generate(), name="spring_top", + .addS(self.torsion_joint.spring.generate(deflection=-deflection), name="spring_top", role=Role.DAMPING, material=mat_spring) - .addS(self.parent(wing_root_wall_thickness), + .addS(self.parent_top(), name="parent_top", role=Role.PARENT, material=mat) - .addS(self.torsion_joint.spring.generate(), name="spring_bot", + .addS(self.torsion_joint.spring.generate(deflection=deflection), name="spring_bot", role=Role.DAMPING, material=mat_spring) - .addS(self.parent(wing_root_wall_thickness), + .addS(self.parent_bot(), name="parent_bot", role=Role.PARENT, material=mat) ) - TorsionJoint.add_constraints(result, - rider="child/rider_top", - track="parent_top/track", - spring="spring_top", - directrix=directrix) - TorsionJoint.add_constraints(result, - rider="child/rider_bot", - track="parent_bot/track", - spring="spring_bot", - directrix=directrix) + TorsionJoint.add_constraints( + result, + rider="child/rider_top", + track="parent_top/track", + spring="spring_top", + directrix=directrix) + TorsionJoint.add_constraints( + result, + rider="child/rider_bot", + track="parent_bot/track", + spring="spring_bot", + directrix=directrix) return result.solve() diff --git a/nhf/touhou/houjuu_nue/wing.py b/nhf/touhou/houjuu_nue/wing.py index 16a23da..e7e352e 100644 --- a/nhf/touhou/houjuu_nue/wing.py +++ b/nhf/touhou/houjuu_nue/wing.py @@ -37,7 +37,6 @@ class WingProfile(Model): spacer_thickness: float = 25.4 / 8 shoulder_joint: ShoulderJoint = field(default_factory=lambda: ShoulderJoint( - height=60.0, )) shoulder_width: float = 30.0 shoulder_tip_x: float = -200.0 @@ -94,6 +93,16 @@ class WingProfile(Model): self.wrist_s = math.sin(self.wrist_theta) self.wrist_top_x, self.wrist_top_y = self.wrist_to_abs(0, self.wrist_height) + @submodel(name="shoulder-joint") + def submodel_shoulder_joint(self) -> Model: + return self.shoulder_joint + @submodel(name="elbow-joint") + def submodel_elbow_joint(self) -> Model: + return self.elbow_joint + @submodel(name="wrist-joint") + def submodel_wrist_joint(self) -> Model: + return self.wrist_joint + @property def root_height(self) -> float: return self.shoulder_joint.height @@ -634,9 +643,9 @@ class WingProfile(Model): ( result .constrain("s0/shoulder?conn_top0", "shoulder/parent_top/lip?conn0", "Plane") - .constrain("s0/shoulder?conn_top1", "shoulder/parent_top/lip?conn1", "Plane") + #.constrain("s0/shoulder?conn_top1", "shoulder/parent_top/lip?conn1", "Plane") .constrain("s0/shoulder?conn_bot0", "shoulder/parent_bot/lip?conn0", "Plane") - .constrain("s0/shoulder?conn_bot1", "shoulder/parent_bot/lip?conn1", "Plane") + #.constrain("s0/shoulder?conn_bot1", "shoulder/parent_bot/lip?conn1", "Plane") ) if "s1" in parts: result.add(self.assembly_s1(), name="s1")