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 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 = 60.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 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), ("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="root-joint") def submodel_root_joint(self) -> Model: return self.root_joint @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).tagPoint(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") ) 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