from dataclasses import dataclass, field import cadquery as Cq 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) class Mannequin: """ A mannequin for calibration """ shoulder_width: float = 400 shoulder_to_waist: float = 440 waist_width: float = 250 head_height: float = 220.0 neck_height: float = 105.0 neck_diam: float = 140 head_diam: float = 210 torso_thickness: float = 150 def generate(self) -> Cq.Workplane: head_neck = ( Cq.Workplane("XY") .cylinder( radius=self.neck_diam/2, height=self.neck_height, centered=(True, True, False)) .faces(">Z") .workplane() .cylinder( radius=self.head_diam/2, height=self.head_height, combine=True, centered=(True, True, False)) ) result = ( Cq.Workplane("XY") .rect(self.waist_width, self.torso_thickness) .workplane(offset=self.shoulder_to_waist) .rect(self.shoulder_width, self.torso_thickness) .loft(combine=True) .union(head_neck.translate((0, 0, self.shoulder_to_waist))) ) return result.translate((0, self.torso_thickness / 2, 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 = 304.8 fillet: float = 10.0 wing_base_pos: list[tuple[str, float, float]] = field(default_factory=lambda: [ ("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), ]) root_joint: RootJoint = field(default_factory=lambda: RootJoint()) mannequin: Mannequin = Mannequin() def __post_init__(self): super().__init__(name="harness") @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: """ Creates the harness shape """ w, h = self.width / 2, self.height / 2 sketch = ( Cq.Sketch() .polygon([ (w, h), (w, -h), (-w, -h), (-w, h), #(0.7 * w, h), #(w, 0), #(0.7 * w, -h), #(0.7 * -w, -h), #(-w, 0), #(0.7 * -w, h), ]) #.rect(self.harness_width, self.harness_height) .vertices() .fillet(self.fillet) ) for tag, x, y in self.wing_base_pos: conn = [(px + x, py + y) for px, py in self.root_joint.corner_pos()] sketch = ( sketch .push(conn) .tag(tag) .circle(self.root_joint.corner_hole_diam / 2, mode='s') .reset() ) return sketch def surface(self) -> Cq.Workplane: """ Creates the harness shape """ result = ( Cq.Workplane('XZ') .placeSketch(self.profile()) .extrude(self.thickness) ) result.faces(">Y").tag("mount") plane = result.faces(">Y").workplane() for tag, x, y in self.wing_base_pos: 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).tagPlane(f"{tag}_{i}") return result def add_root_joint_constraint( self, a: Cq.Assembly, harness_tag: str, joint_tag: str, mount_tag: str): for i in range(4): a.constrain(f"{harness_tag}?{mount_tag}_{i}", f"{joint_tag}/parent?h{i}", "Point") @assembly() def assembly(self, with_root_joint: bool = False) -> Cq.Assembly: harness = self.surface() mannequin_z = self.mannequin.shoulder_to_waist * 0.6 result = ( Cq.Assembly() .addS( harness, name="base", material=Material.WOOD_BIRCH, role=Role.STRUCTURE) .constrain("base", "Fixed") .addS( self.mannequin.generate(), name="mannequin", role=Role.FIXTURE, 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( self.root_joint.assembly(), name=name, role=Role.PARENT, material=Material.PLASTIC_PLA) self.add_root_joint_constraint(result, "base", name, name) result.solve() return result