Cosplay/nhf/touhou/houjuu_nue/__init__.py

304 lines
9.3 KiB
Python

"""
This cosplay consists of 3 components:
## Trident
The trident is composed of individual segments, made of acrylic, and a 3D
printed head (convention rule prohibits metal) with a metallic paint. To ease
transportation, the trident handle has individual segments with threads and can
be assembled on site.
## Snake
A 3D printed snake with a soft material so it can wrap around and bend
## Wings
This is the crux of the cosplay and the most complex component. The wings mount
on a wearable harness. Each wing consists of 4 segments with 3 joints. Parts of
the wing which demands transluscency are created from 1/16" acrylic panels.
These panels serve double duty as the exoskeleton.
The wings are labeled r1,r2,r3,l1,l2,l3. The segments of the wings are labeled
from root to tip s0 (root), s1, s2, s3. The joints are named (from root to tip)
shoulder, elbow, wrist in analogy with human anatomy.
"""
from dataclasses import dataclass
import unittest
import cadquery as Cq
import nhf.joints
from nhf import Material
import nhf.touhou.houjuu_nue.wing as MW
@dataclass(frozen=True)
class Parameters:
"""
Defines dimensions for the Houjuu Nue cosplay
"""
# Thickness of the exoskeleton panel in millimetres
panel_thickness: float = 25.4 / 16
# Harness
harness_thickness: float = 25.4 / 8
harness_width = 300
harness_height = 400
harness_fillet = 10
harness_wing_base_pos = [
("r1", 70, 150),
("l1", -70, 150),
("r2", 100, 0),
("l2", -100, 0),
("r3", 70, -150),
("l3", -70, -150),
]
# Holes drilled onto harness for attachment with HS joint
harness_to_root_conn_diam = 6
# Wing root properties
#
# The Houjuu-Scarlett joint mechanism at the base of the wing
hs_joint_base_width = 85
hs_joint_base_thickness = 10
hs_joint_ring_thickness = 5
hs_joint_tooth_height = 10
hs_joint_radius = 30
hs_joint_radius_inner = 20
hs_joint_corner_fillet = 5
hs_joint_corner_cbore_diam = 12
hs_joint_corner_cbore_depth = 2
hs_joint_corner_inset = 12
hs_joint_axis_diam = 12
hs_joint_axis_cbore_diam = 20
hs_joint_axis_cbore_depth = 3
# Exterior radius of the wing root assembly
wing_root_radius = 40
"""
Heights for various wing joints, where the numbers start from the first joint.
"""
wing_r1_height = 100
wing_r1_width = 400
wing_r2_height = 100
wing_r3_height = 100
def __post_init__(self):
assert self.wing_root_radius > self.hs_joint_radius,\
"Wing root must be large enough to accomodate joint"
def print_geometries(self):
return [
self.hs_joint_parent()
]
def harness_profile(self) -> Cq.Sketch:
"""
Creates the harness shape
"""
w, h = self.harness_width / 2, self.harness_height / 2
sketch = (
Cq.Sketch()
.polygon([
(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.harness_fillet)
)
for tag, x, y in self.harness_wing_base_pos:
conn = [(px + x, py + y) for px, py in self.hs_joint_harness_conn()]
sketch = (
sketch
.push(conn)
.tag(tag)
.circle(self.harness_to_root_conn_diam / 2, mode='s')
.reset()
)
return sketch
def harness(self) -> Cq.Shape:
"""
Creates the harness shape
"""
result = (
Cq.Workplane('XZ')
.placeSketch(self.harness_profile())
.extrude(self.harness_thickness)
)
result.faces(">Y").tag("mount")
plane = result.faces(">Y").workplane()
for tag, x, y in self.harness_wing_base_pos:
conn = [(px + x, py + y) for px, py in self.hs_joint_harness_conn()]
for i, (px, py) in enumerate(conn):
(
plane
.moveTo(px, py)
.circle(1, forConstruction='True')
.edges()
.tag(f"{tag}_{i}")
)
return result
def hs_joint_harness_conn(self) -> list[tuple[int, int]]:
"""
Generates a set of points corresponding to the connectorss
"""
dx = self.hs_joint_base_width / 2 - self.hs_joint_corner_inset
return [
(dx, dx),
(dx, -dx),
(-dx, -dx),
(-dx, dx),
]
def hs_joint_parent(self):
"""
Parent part of the Houjuu-Scarlett joint, which is composed of a Hirth
coupling, a cylindrical base, and a mounting base.
"""
hirth = nhf.joints.hirth_joint(
radius=self.hs_joint_radius,
radius_inner=self.hs_joint_radius_inner,
tooth_height=self.hs_joint_tooth_height,
base_height=self.hs_joint_ring_thickness).val()
#hirth = (
# hirth
# .faces("<Z")
# .workplane()
# .transformed(
# offset=Cq.Vector(0, 0, -self.hs_joint_ring_thickness / 2),
# rotate=Cq.Vector(90, 0, 0))
# # This hole will drill only to the centre and not through. This is
# # intended.
# .hole(5)
# .val()
#)
conn = self.hs_joint_harness_conn()
result = (
Cq.Workplane('XY')
.box(
self.hs_joint_base_width,
self.hs_joint_base_width,
self.hs_joint_base_thickness,
centered=(True, True, False))
.edges("|Z")
.fillet(self.hs_joint_corner_fillet)
.faces(">Z")
.workplane()
.pushPoints(conn)
.cboreHole(
diameter=self.harness_to_root_conn_diam,
cboreDiameter=self.hs_joint_corner_cbore_diam,
cboreDepth=self.hs_joint_corner_cbore_depth)
)
plane = result.faces(">Z").workplane(offset=-self.hs_joint_base_thickness)
for i, (px, py) in enumerate(conn):
(
plane
.moveTo(px, py)
.circle(1, forConstruction='True')
.edges()
.tag(f"h{i}")
)
result = (
result
.faces(">Z")
.workplane()
.union(hirth.move(Cq.Location((0, 0, self.hs_joint_base_thickness))), tol=0.1)
.clean()
)
result = (
result.faces("<Z")
.workplane()
.cboreHole(
diameter=self.hs_joint_axis_diam,
cboreDiameter=self.hs_joint_axis_cbore_diam,
cboreDepth=self.hs_joint_axis_cbore_depth,
)
.clean()
)
result.faces("<Z").tag("base")
return result
def wing_r1_profile(self) -> Cq.Sketch:
"""
Generates the first wing segment profile, with the wing root pointing in
the positive x axis.
"""
# Depression of the wing middle
bend = 200
factor = 0.7
result = (
Cq.Sketch()
.segment((0, 0), (0, self.wing_r1_height))
.spline([
(0, self.wing_r1_height),
(0.5 * self.wing_r1_width, self.wing_r1_height - factor * bend),
(self.wing_r1_width, self.wing_r1_height - bend),
])
.segment(
(self.wing_r1_width, self.wing_r1_height - bend),
(self.wing_r1_width, -bend),
)
.spline([
(self.wing_r1_width, - bend),
(0.5 * self.wing_r1_width, - factor * bend),
(0, 0),
])
.assemble()
)
return result
def wing_r1(self) -> Cq.Solid:
profile = self.wing_r1_profile()
result = (
Cq.Workplane("XY")
.placeSketch(profile)
.extrude(self.panel_thickness)
.val()
)
return result
def wing_root(self) -> Cq.Shape:
"""
Generate the wing root which contains a Hirth joint at its base and a
rectangular opening on its side, with the necessary interfaces.
"""
return MW.wing_base().val()#self.wing_root_radius)
######################
# Assemblies #
######################
def harness_assembly(self):
harness = self.harness()
result = (
Cq.Assembly()
.add(harness, name="harness", color=Material.WOOD_BIRCH.color)
.constrain("harness", "Fixed")
)
for name in ["l1", "l2", "l3", "r1", "r2", "r3"]:
j = self.hs_joint_parent()
(
result
.add(j, name=f"hs_{name}p", color=Material.PLASTIC_PLA.color)
#.constrain(f"harness?{name}", f"hs_{name}p?mate", "Point")
.constrain("harness?mount", f"hs_{name}p?base", "Axis")
)
for i in range(4):
result.constrain(f"harness?{name}_{i}", f"hs_{name}p?h{i}", "Point")
result.solve()
return result