Cosplay/nhf/touhou/houjuu_nue/harness.py

205 lines
7.1 KiB
Python

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 = 200.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