2024-06-24 16:13:15 -07:00
|
|
|
"""
|
|
|
|
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.
|
|
|
|
"""
|
2024-06-19 21:23:41 -07:00
|
|
|
from dataclasses import dataclass
|
2024-06-20 23:29:18 -07:00
|
|
|
import unittest
|
|
|
|
import cadquery as Cq
|
2024-06-22 13:40:06 -07:00
|
|
|
import nhf.joints
|
2024-06-23 22:27:15 -07:00
|
|
|
from nhf import Material
|
2024-06-24 16:13:15 -07:00
|
|
|
import nhf.touhou.houjuu_nue.wing as MW
|
2024-06-19 15:54:09 -07:00
|
|
|
|
2024-06-19 21:23:41 -07:00
|
|
|
@dataclass(frozen=True)
|
|
|
|
class Parameters:
|
|
|
|
"""
|
2024-06-24 16:13:15 -07:00
|
|
|
Defines dimensions for the Houjuu Nue cosplay
|
2024-06-19 21:23:41 -07:00
|
|
|
"""
|
2024-06-24 16:13:15 -07:00
|
|
|
|
|
|
|
# Thickness of the exoskeleton panel in millimetres
|
2024-06-19 21:23:41 -07:00
|
|
|
panel_thickness: float = 25.4 / 16
|
|
|
|
|
2024-06-23 22:27:15 -07:00
|
|
|
# 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
|
2024-06-24 16:13:15 -07:00
|
|
|
harness_to_root_conn_diam = 6
|
2024-06-23 22:27:15 -07:00
|
|
|
|
2024-06-19 21:23:41 -07:00
|
|
|
# Wing root properties
|
2024-06-24 16:13:15 -07:00
|
|
|
#
|
|
|
|
# The Houjuu-Scarlett joint mechanism at the base of the wing
|
2024-06-23 22:27:15 -07:00
|
|
|
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
|
2024-06-24 11:05:03 -07:00
|
|
|
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
|
|
|
|
|
2024-06-24 16:13:15 -07:00
|
|
|
# Exterior radius of the wing root assembly
|
|
|
|
wing_root_radius = 40
|
2024-06-19 21:23:41 -07:00
|
|
|
|
2024-06-20 23:29:18 -07:00
|
|
|
"""
|
|
|
|
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
|
|
|
|
|
2024-06-24 16:13:15 -07:00
|
|
|
def __post_init__(self):
|
|
|
|
assert self.wing_root_radius > self.hs_joint_radius,\
|
|
|
|
"Wing root must be large enough to accomodate joint"
|
|
|
|
|
2024-06-22 13:40:06 -07:00
|
|
|
|
|
|
|
def print_geometries(self):
|
|
|
|
return [
|
|
|
|
self.hs_joint_parent()
|
|
|
|
]
|
|
|
|
|
2024-06-23 22:27:15 -07:00
|
|
|
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)
|
2024-06-24 16:13:15 -07:00
|
|
|
.circle(self.harness_to_root_conn_diam / 2, mode='s')
|
2024-06-23 22:27:15 -07:00
|
|
|
.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),
|
|
|
|
]
|
|
|
|
|
2024-06-22 13:40:06 -07:00
|
|
|
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,
|
2024-06-24 11:05:03 -07:00
|
|
|
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()
|
|
|
|
#)
|
2024-06-23 22:27:15 -07:00
|
|
|
conn = self.hs_joint_harness_conn()
|
2024-06-22 13:40:06 -07:00
|
|
|
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()
|
2024-06-23 22:27:15 -07:00
|
|
|
.pushPoints(conn)
|
2024-06-22 13:40:06 -07:00
|
|
|
.cboreHole(
|
2024-06-24 16:13:15 -07:00
|
|
|
diameter=self.harness_to_root_conn_diam,
|
2024-06-22 13:40:06 -07:00
|
|
|
cboreDiameter=self.hs_joint_corner_cbore_diam,
|
|
|
|
cboreDepth=self.hs_joint_corner_cbore_depth)
|
2024-06-23 22:27:15 -07:00
|
|
|
)
|
|
|
|
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
|
2024-06-22 13:40:06 -07:00
|
|
|
.faces(">Z")
|
|
|
|
.workplane()
|
2024-06-23 22:27:15 -07:00
|
|
|
.union(hirth.move(Cq.Location((0, 0, self.hs_joint_base_thickness))), tol=0.1)
|
2024-06-22 13:40:06 -07:00
|
|
|
.clean()
|
|
|
|
)
|
2024-06-24 11:05:03 -07:00
|
|
|
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()
|
|
|
|
)
|
2024-06-23 22:27:15 -07:00
|
|
|
result.faces("<Z").tag("base")
|
2024-06-22 13:40:06 -07:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-06-20 23:29:18 -07:00
|
|
|
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
|
|
|
|
|
2024-06-20 23:45:24 -07:00
|
|
|
def wing_r1(self) -> Cq.Solid:
|
|
|
|
profile = self.wing_r1_profile()
|
|
|
|
result = (
|
|
|
|
Cq.Workplane("XY")
|
|
|
|
.placeSketch(profile)
|
|
|
|
.extrude(self.panel_thickness)
|
|
|
|
.val()
|
|
|
|
)
|
|
|
|
return result
|
|
|
|
|
2024-06-24 16:13:15 -07:00
|
|
|
def wing_root(self) -> Cq.Shape:
|
2024-06-19 21:23:41 -07:00
|
|
|
"""
|
|
|
|
Generate the wing root which contains a Hirth joint at its base and a
|
|
|
|
rectangular opening on its side, with the necessary interfaces.
|
|
|
|
"""
|
2024-06-24 16:13:15 -07:00
|
|
|
return MW.wing_base().val()#self.wing_root_radius)
|
|
|
|
|
|
|
|
######################
|
|
|
|
# Assemblies #
|
|
|
|
######################
|
2024-06-20 23:29:18 -07:00
|
|
|
|
2024-06-23 22:27:15 -07:00
|
|
|
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
|