Cosplay/nhf/touhou/houjuu_nue/harness.py

205 lines
7.1 KiB
Python
Raw Normal View History

from dataclasses import dataclass, field
import cadquery as Cq
from nhf.parts.joints import HirthJoint
from nhf import Material, Role
2024-07-19 15:06:57 -07:00
from nhf.build import Model, TargetKind, target, assembly, submodel
from nhf.touhou.houjuu_nue.joints import RootJoint
2024-07-25 09:47:41 -07:00
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))
2024-07-25 09:47:41 -07:00
BASE_POS_X = 70.0
BASE_POS_Y = 100.0
@dataclass(kw_only=True)
2024-07-16 15:42:39 -07:00
class Harness(Model):
thickness: float = 25.4 / 8
width: float = 220.0
2024-07-25 09:47:41 -07:00
height: float = 304.8
fillet: float = 10.0
wing_base_pos: list[tuple[str, float, float]] = field(default_factory=lambda: [
2024-07-25 09:47:41 -07:00
("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),
])
2024-07-19 15:06:57 -07:00
root_joint: RootJoint = field(default_factory=lambda: RootJoint())
mannequin: Mannequin = Mannequin()
2024-07-16 15:42:39 -07:00
def __post_init__(self):
super().__init__(name="harness")
2024-07-25 09:47:41 -07:00
@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)
2024-07-19 15:06:57 -07:00
2024-07-16 15:42:39 -07:00
@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:
2024-07-19 15:06:57 -07:00
conn = [(px + x, py + y) for px, py in self.root_joint.corner_pos()]
sketch = (
sketch
.push(conn)
.tag(tag)
2024-07-19 15:06:57 -07:00
.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
2024-07-19 15:06:57 -07:00
in self.root_joint.corner_pos()]
for i, (px, py) in enumerate(conn):
2024-07-25 09:47:41 -07:00
plane.moveTo(px, py).tagPlane(f"{tag}_{i}")
return result
2024-07-19 15:06:57 -07:00
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")
2024-07-16 15:42:39 -07:00
@assembly()
2024-07-19 15:06:57 -07:00
def assembly(self, with_root_joint: bool = False) -> Cq.Assembly:
harness = self.surface()
mannequin_z = self.mannequin.shoulder_to_waist * 0.6
2024-07-19 15:06:57 -07:00
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")
)
2024-07-25 09:47:41 -07:00
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')
)
2024-07-19 15:06:57 -07:00
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