diff --git a/nhf/parts/joints.py b/nhf/parts/joints.py index e0e0357..4245eff 100644 --- a/nhf/parts/joints.py +++ b/nhf/parts/joints.py @@ -8,7 +8,7 @@ import nhf.utils TOL = 1e-6 -@dataclass +@dataclass(frozen=True) class HirthJoint: """ A Hirth joint attached to a cylindrical base diff --git a/nhf/touhou/houjuu_nue/__init__.py b/nhf/touhou/houjuu_nue/__init__.py index 6022342..9d3c155 100644 --- a/nhf/touhou/houjuu_nue/__init__.py +++ b/nhf/touhou/houjuu_nue/__init__.py @@ -33,7 +33,6 @@ from dataclasses import dataclass, field from typing import Optional import cadquery as Cq from nhf.build import Model, TargetKind, target, assembly, submodel -from nhf.parts.joints import HirthJoint, TorsionJoint import nhf.touhou.houjuu_nue.wing as MW import nhf.touhou.houjuu_nue.trident as MT import nhf.touhou.houjuu_nue.joints as MJ @@ -53,40 +52,64 @@ class Parameters(Model): wing_r1: MW.WingR = field(default_factory=lambda: MW.WingR( name="r1", - shoulder_angle_bias = WING_DEFLECT_ODD, + root_joint=MJ.RootJoint( + parent_substrate_cull_corners=(0,1,1,1), + parent_substrate_cull_edges=(0,0,1,0), + ), + shoulder_angle_bias=WING_DEFLECT_ODD, s0_top_hole=False, s0_bot_hole=True, arrow_height=350.0 )) wing_r2: MW.WingR = field(default_factory=lambda: MW.WingR( name="r2", - shoulder_angle_bias = WING_DEFLECT_EVEN, + root_joint=MJ.RootJoint( + parent_substrate_cull_corners=(1,1,1,1), + parent_substrate_cull_edges=(0,0,1,0), + ), + shoulder_angle_bias=WING_DEFLECT_EVEN, s0_top_hole=True, s0_bot_hole=True, )) wing_r3: MW.WingR = field(default_factory=lambda: MW.WingR( name="r3", - shoulder_angle_bias = WING_DEFLECT_ODD, + root_joint=MJ.RootJoint( + parent_substrate_cull_corners=(1,1,1,0), + parent_substrate_cull_edges=(0,0,1,0), + ), + shoulder_angle_bias=WING_DEFLECT_ODD, s0_top_hole=True, s0_bot_hole=False, )) wing_l1: MW.WingL = field(default_factory=lambda: MW.WingL( name="l1", - shoulder_angle_bias = WING_DEFLECT_EVEN, + root_joint=MJ.RootJoint( + parent_substrate_cull_corners=(1,0,1,1), + parent_substrate_cull_edges=(1,0,0,0), + ), + shoulder_angle_bias=WING_DEFLECT_EVEN, wrist_angle=-60.0, s0_top_hole=False, s0_bot_hole=True, )) wing_l2: MW.WingL = field(default_factory=lambda: MW.WingL( name="l2", + root_joint=MJ.RootJoint( + parent_substrate_cull_corners=(1,1,1,1), + parent_substrate_cull_edges=(1,0,0,0), + ), wrist_angle=-30.0, - shoulder_angle_bias = WING_DEFLECT_ODD, + shoulder_angle_bias=WING_DEFLECT_ODD, s0_top_hole=True, s0_bot_hole=True, )) wing_l3: MW.WingL = field(default_factory=lambda: MW.WingL( name="l3", - shoulder_angle_bias = WING_DEFLECT_EVEN, + root_joint=MJ.RootJoint( + parent_substrate_cull_corners=(1,1,0,1), + parent_substrate_cull_edges=(1,0,0,0), + ), + shoulder_angle_bias=WING_DEFLECT_EVEN, wrist_angle=-0.0, s0_top_hole=True, s0_bot_hole=False, @@ -96,14 +119,6 @@ class Parameters(Model): def __post_init__(self): super().__init__(name="houjuu-nue") - self.wing_r1.root_joint = self.harness.root_joint - self.wing_r2.root_joint = self.harness.root_joint - self.wing_r3.root_joint = self.harness.root_joint - self.wing_l1.root_joint = self.harness.root_joint - self.wing_l2.root_joint = self.harness.root_joint - self.wing_l3.root_joint = self.harness.root_joint - - self.wing_r1.shoulder_joint.torsion_joint @submodel(name="harness") def submodel_harness(self) -> Model: diff --git a/nhf/touhou/houjuu_nue/harness.py b/nhf/touhou/houjuu_nue/harness.py index 8507873..1f31dfc 100644 --- a/nhf/touhou/houjuu_nue/harness.py +++ b/nhf/touhou/houjuu_nue/harness.py @@ -4,6 +4,7 @@ from nhf.parts.joints import HirthJoint from nhf import Material, Role from nhf.build import Model, TargetKind, target, assembly, submodel from nhf.touhou.houjuu_nue.joints import RootJoint +from nhf.parts.box import MountingBox import nhf.utils @dataclass(frozen=True, kw_only=True) @@ -46,21 +47,21 @@ class Mannequin: return result.translate((0, self.torso_thickness / 2, 0)) -BASE_POS_X = 60.0 +BASE_POS_X = 70.0 BASE_POS_Y = 100.0 @dataclass(kw_only=True) class Harness(Model): thickness: float = 25.4 / 8 width: float = 220.0 - height: float = 310.0 + height: float = 304.8 fillet: float = 10.0 wing_base_pos: list[tuple[str, float, float]] = field(default_factory=lambda: [ - ("r1", BASE_POS_X + 10, BASE_POS_Y), - ("l1", -BASE_POS_X - 10, BASE_POS_Y), - ("r2", BASE_POS_X + 10, 0), - ("l2", -BASE_POS_X - 10, 0), + ("r1", BASE_POS_X, BASE_POS_Y), + ("l1", -BASE_POS_X, BASE_POS_Y), + ("r2", BASE_POS_X, 0), + ("l2", -BASE_POS_X, 0), ("r3", BASE_POS_X, -BASE_POS_Y), ("l3", -BASE_POS_X, -BASE_POS_Y), ]) @@ -72,9 +73,12 @@ class Harness(Model): def __post_init__(self): super().__init__(name="harness") - @submodel(name="root-joint") - def submodel_root_joint(self) -> Model: - return self.root_joint + @submodel(name="bridge-pair-horizontal") + def bridge_pair_horizontal(self) -> MountingBox: + return self.root_joint.bridge_pair_horizontal(centre_dx=BASE_POS_X * 2) + @submodel(name="bridge-pair-vertical") + def bridge_pair_vertical(self) -> MountingBox: + return self.root_joint.bridge_pair_vertical(centre_dy=BASE_POS_Y) @target(name="profile", kind=TargetKind.DXF) def profile(self) -> Cq.Sketch: @@ -126,7 +130,7 @@ class Harness(Model): conn = [(px + x, py + y) for px, py in self.root_joint.corner_pos()] for i, (px, py) in enumerate(conn): - plane.moveTo(px, py).tagPoint(f"{tag}_{i}") + plane.moveTo(px, py).tagPlane(f"{tag}_{i}") return result def add_root_joint_constraint( @@ -144,7 +148,6 @@ class Harness(Model): harness = self.surface() mannequin_z = self.mannequin.shoulder_to_waist * 0.6 - result = ( Cq.Assembly() .addS( @@ -159,6 +162,37 @@ class Harness(Model): loc=Cq.Location((0, -self.thickness, -mannequin_z), (0, 0, 1), 180)) .constrain("mannequin", "Fixed") ) + bridge_h = self.bridge_pair_horizontal().generate() + for i in [1,2,3]: + name = f"r{i}l{i}_bridge" + ( + result + .addS( + bridge_h, name=name, + role=Role.FIXTURE, + material=Material.WOOD_BIRCH, + ) + .constrain(f"{name}?conn0_rev", f"base?r{i}_1", "Point") + .constrain(f"{name}?conn1_rev", f"base?l{i}_0", "Point") + .constrain(f"{name}?conn2_rev", f"base?l{i}_3", "Point") + .constrain(f"{name}?conn3_rev", f"base?r{i}_2", "Point") + ) + bridge_v = self.bridge_pair_vertical().generate() + ( + result + .addS(bridge_v, name="r1_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH) + .constrain("r1_bridge?conn0_rev", "base?r1_3", 'Plane') + .constrain("r1_bridge?conn1_rev", "base?r2_0", 'Plane') + .addS(bridge_v, name="r2_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH) + .constrain("r2_bridge?conn0_rev", "base?r2_3", 'Plane') + .constrain("r2_bridge?conn1_rev", "base?r3_0", 'Plane') + .addS(bridge_v, name="l1_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH) + .constrain("l1_bridge?conn0_rev", "base?l1_2", 'Plane') + .constrain("l1_bridge?conn1_rev", "base?l2_1", 'Plane') + .addS(bridge_v, name="l2_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH) + .constrain("l2_bridge?conn0_rev", "base?l2_2", 'Plane') + .constrain("l2_bridge?conn1_rev", "base?l3_1", 'Plane') + ) if with_root_joint: for name in ["l1", "l2", "l3", "r1", "r2", "r3"]: result.addS( diff --git a/nhf/touhou/houjuu_nue/joints.py b/nhf/touhou/houjuu_nue/joints.py index d7fd70a..4be4bae 100644 --- a/nhf/touhou/houjuu_nue/joints.py +++ b/nhf/touhou/houjuu_nue/joints.py @@ -4,6 +4,7 @@ from typing import Optional, Tuple import cadquery as Cq from nhf import Material, Role from nhf.build import Model, target, assembly +from nhf.parts.box import MountingBox from nhf.parts.springs import TorsionSpring from nhf.parts.fasteners import FlatHeadBolt, HexNut, ThreaddedKnob from nhf.parts.joints import TorsionJoint, HirthJoint @@ -88,6 +89,10 @@ class RootJoint(Model): parent_corner_inset: float = 12 parent_mount_thickness: float = 25.4 / 16 + parent_substrate_thickness: float = 25.4 / 16 + parent_substrate_cull_corners: Tuple[bool, bool, bool, bool] = (False, False, True, False) + parent_substrate_cull_edges: Tuple[bool, bool, bool, bool] = (False, False, True, False) + child_corner_dx: float = 17.0 child_corner_dz: float = 24.0 @@ -103,6 +108,8 @@ class RootJoint(Model): def __post_init__(self): assert self.child_extra_thickness > 0.0 assert self.parent_thickness >= self.hex_nut.thickness + # twice the buffer allocated for substratum + assert self.parent_width >= 4 * self.parent_corner_inset def corner_pos(self) -> list[tuple[int, int]]: """ @@ -111,9 +118,9 @@ class RootJoint(Model): dx = self.parent_width / 2 - self.parent_corner_inset return [ (dx, dx), - (dx, -dx), - (-dx, -dx), (-dx, dx), + (-dx, -dx), + (dx, -dx), ] @property @@ -127,6 +134,45 @@ class RootJoint(Model): def base_to_surface_thickness(self) -> float: return self.hirth_joint.joint_height + self.child_extra_thickness + @property + def substrate_inset(self) -> float: + return self.parent_corner_inset * 2 + + def bridge_pair_horizontal(self, centre_dx: float) -> MountingBox: + hole_dx = centre_dx / 2 - self.parent_width / 2 + self.parent_corner_inset + hole_dy = self.parent_width / 2 - self.parent_corner_inset + holes = [ + Hole(x=hole_dx, y=hole_dy), + Hole(x=-hole_dx, y=hole_dy), + Hole(x=-hole_dx, y=-hole_dy), + Hole(x=hole_dx, y=-hole_dy), + ] + return MountingBox( + length=centre_dx - self.parent_width + self.substrate_inset * 2, + width=self.parent_width, + thickness=self.parent_substrate_thickness, + hole_diam=self.corner_hole_diam, + holes=holes, + centred=(True, True), + generate_reverse_tags=True, + ) + def bridge_pair_vertical(self, centre_dy: float) -> MountingBox: + hole_dy = centre_dy / 2 - self.parent_width / 2 + self.parent_corner_inset + holes = [ + Hole(x=0, y=hole_dy), + Hole(x=0, y=-hole_dy), + ] + return MountingBox( + length=self.substrate_inset, + width=centre_dy - self.parent_width + self.substrate_inset * 2, + thickness=self.parent_substrate_thickness, + hole_diam=self.corner_hole_diam, + holes=holes, + centred=(True, True), + generate_reverse_tags=True, + ) + + @target(name="parent") def parent(self): """ @@ -142,7 +188,6 @@ class RootJoint(Model): width=self.parent_width, height=self.parent_thickness, centered=(True, True, False)) - .translate((0, 0, -self.parent_thickness)) .edges("|Z") .fillet(self.parent_corner_fillet) .faces(">Z") @@ -153,6 +198,32 @@ class RootJoint(Model): cboreDiameter=self.parent_corner_cbore_diam, cboreDepth=self.parent_corner_cbore_depth) ) + sso = self.parent_width / 2 - self.substrate_inset + loc_corner = Cq.Location((sso, sso, 0)) + loc_edge= Cq.Location((sso, -sso, 0)) + cut_corner = Cq.Solid.makeBox( + length=self.parent_width, + width=self.parent_width, + height=self.parent_substrate_thickness, + ) + cut_edge = Cq.Solid.makeBox( + length=self.parent_width, + width=self.parent_width - self.substrate_inset * 2, + height=self.parent_substrate_thickness, + ) + step = 90 + for i, flag in enumerate(self.parent_substrate_cull_corners): + if not flag: + continue + loc = Cq.Location((0,0,0),(0,0,1), i * step) * loc_corner + result = result.cut(cut_corner.located(loc)) + for i, flag in enumerate(self.parent_substrate_cull_edges): + if not flag: + continue + loc = Cq.Location((0,0,0),(0,0,1), i * step) * loc_edge + result = result.cut(cut_edge.located(loc)) + + result = result.translate((0, 0, -self.parent_thickness)) # Creates a plane parallel to the holes but shifted to the base plane = result.faces(">Z").workplane(offset=-self.parent_thickness) @@ -171,7 +242,7 @@ class RootJoint(Model): .hole(diameter=self.axis_diam) .cut(self.hex_nut.generate().translate((0, 0, -self.parent_thickness))) ) - result.faces(" Model: + return self.root_joint @submodel(name="shoulder-joint") def submodel_shoulder_joint(self) -> Model: return self.shoulder_joint @@ -312,6 +315,7 @@ class WingProfile(Model): Hole(x=dx, y=-dy), Hole(x=dx, y=dy), Hole(x=-dx, y=dy), + Hole(x=0, y=0, diam=self.root_joint.axis_diam, tag="axle"), ] return MountingBox( length=self.root_height,