cosplay: Touhou/Houjuu Nue #1
|
@ -1,15 +1,42 @@
|
||||||
|
"""
|
||||||
|
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
|
from dataclasses import dataclass
|
||||||
import unittest
|
import unittest
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
import nhf.joints
|
import nhf.joints
|
||||||
from nhf import Material
|
from nhf import Material
|
||||||
|
import nhf.touhou.houjuu_nue.wing as MW
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Parameters:
|
class Parameters:
|
||||||
|
"""
|
||||||
|
Defines dimensions for the Houjuu Nue cosplay
|
||||||
|
"""
|
||||||
|
|
||||||
"""
|
# Thickness of the exoskeleton panel in millimetres
|
||||||
Thickness of the exoskeleton panel in millimetres
|
|
||||||
"""
|
|
||||||
panel_thickness: float = 25.4 / 16
|
panel_thickness: float = 25.4 / 16
|
||||||
|
|
||||||
# Harness
|
# Harness
|
||||||
|
@ -28,12 +55,11 @@ class Parameters:
|
||||||
]
|
]
|
||||||
|
|
||||||
# Holes drilled onto harness for attachment with HS joint
|
# Holes drilled onto harness for attachment with HS joint
|
||||||
harness_to_wing_base_hole_diam = 6
|
harness_to_root_conn_diam = 6
|
||||||
|
|
||||||
# Wing root properties
|
# Wing root properties
|
||||||
"""
|
#
|
||||||
The Houjuu-Scarlett joint mechanism at the base of the wing
|
# The Houjuu-Scarlett joint mechanism at the base of the wing
|
||||||
"""
|
|
||||||
hs_joint_base_width = 85
|
hs_joint_base_width = 85
|
||||||
hs_joint_base_thickness = 10
|
hs_joint_base_thickness = 10
|
||||||
hs_joint_ring_thickness = 5
|
hs_joint_ring_thickness = 5
|
||||||
|
@ -49,12 +75,8 @@ class Parameters:
|
||||||
hs_joint_axis_cbore_diam = 20
|
hs_joint_axis_cbore_diam = 20
|
||||||
hs_joint_axis_cbore_depth = 3
|
hs_joint_axis_cbore_depth = 3
|
||||||
|
|
||||||
|
# Exterior radius of the wing root assembly
|
||||||
"""
|
wing_root_radius = 40
|
||||||
Radius of the mounting mechanism of the wing root. This is constrained by
|
|
||||||
the size of the harness.
|
|
||||||
"""
|
|
||||||
root_radius: float = 60
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Heights for various wing joints, where the numbers start from the first joint.
|
Heights for various wing joints, where the numbers start from the first joint.
|
||||||
|
@ -64,6 +86,10 @@ class Parameters:
|
||||||
wing_r2_height = 100
|
wing_r2_height = 100
|
||||||
wing_r3_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):
|
def print_geometries(self):
|
||||||
return [
|
return [
|
||||||
|
@ -95,7 +121,7 @@ class Parameters:
|
||||||
sketch
|
sketch
|
||||||
.push(conn)
|
.push(conn)
|
||||||
.tag(tag)
|
.tag(tag)
|
||||||
.circle(self.harness_to_wing_base_hole_diam / 2, mode='s')
|
.circle(self.harness_to_root_conn_diam / 2, mode='s')
|
||||||
.reset()
|
.reset()
|
||||||
)
|
)
|
||||||
return sketch
|
return sketch
|
||||||
|
@ -171,7 +197,7 @@ class Parameters:
|
||||||
.workplane()
|
.workplane()
|
||||||
.pushPoints(conn)
|
.pushPoints(conn)
|
||||||
.cboreHole(
|
.cboreHole(
|
||||||
diameter=self.harness_to_wing_base_hole_diam,
|
diameter=self.harness_to_root_conn_diam,
|
||||||
cboreDiameter=self.hs_joint_corner_cbore_diam,
|
cboreDiameter=self.hs_joint_corner_cbore_diam,
|
||||||
cboreDepth=self.hs_joint_corner_cbore_depth)
|
cboreDepth=self.hs_joint_corner_cbore_depth)
|
||||||
)
|
)
|
||||||
|
@ -245,23 +271,16 @@ class Parameters:
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def wing_root(self,
|
def wing_root(self) -> Cq.Shape:
|
||||||
side_width=30,
|
|
||||||
side_height=100) -> Cq.Shape:
|
|
||||||
"""
|
"""
|
||||||
Generate the wing root which contains a Hirth joint at its base and a
|
Generate the wing root which contains a Hirth joint at its base and a
|
||||||
rectangular opening on its side, with the necessary interfaces.
|
rectangular opening on its side, with the necessary interfaces.
|
||||||
"""
|
"""
|
||||||
result = (
|
return MW.wing_base().val()#self.wing_root_radius)
|
||||||
Cq.Workplane("XY")
|
|
||||||
.circle(self.root_radius)
|
######################
|
||||||
.transformed(offset=Cq.Vector(80, 0, 80),
|
# Assemblies #
|
||||||
rotate=Cq.Vector(0, 45, 0))
|
######################
|
||||||
.rect(side_width, side_height)
|
|
||||||
.loft()
|
|
||||||
.val()
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def harness_assembly(self):
|
def harness_assembly(self):
|
||||||
harness = self.harness()
|
harness = self.harness()
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
import math
|
||||||
|
import cadquery as Cq
|
||||||
|
|
||||||
|
def wing_root_profiles(
|
||||||
|
base_sweep=150,
|
||||||
|
wall_thickness=8,
|
||||||
|
base_radius=60,
|
||||||
|
middle_offset=30,
|
||||||
|
conn_width=40,
|
||||||
|
conn_height=100) -> tuple[Cq.Wire, Cq.Wire]:
|
||||||
|
assert base_sweep < 180
|
||||||
|
assert middle_offset > 0
|
||||||
|
theta = math.pi * base_sweep / 180
|
||||||
|
c, s = math.cos(theta), math.sin(theta)
|
||||||
|
c_1, s_1 = math.cos(theta * 0.75), math.sin(theta * 0.75)
|
||||||
|
c_2, s_2 = math.cos(theta / 2), math.sin(theta / 2)
|
||||||
|
r1 = base_radius
|
||||||
|
r2 = base_radius - wall_thickness
|
||||||
|
base = (
|
||||||
|
Cq.Sketch()
|
||||||
|
.arc(
|
||||||
|
(c * r1, s * r1),
|
||||||
|
(c_1 * r1, s_1 * r1),
|
||||||
|
(c_2 * r1, s_2 * r1),
|
||||||
|
)
|
||||||
|
.arc(
|
||||||
|
(c_2 * r1, s_2 * r1),
|
||||||
|
(r1, 0),
|
||||||
|
(c_2 * r1, -s_2 * r1),
|
||||||
|
)
|
||||||
|
.arc(
|
||||||
|
(c_2 * r1, -s_2 * r1),
|
||||||
|
(c_1 * r1, -s_1 * r1),
|
||||||
|
(c * r1, -s * r1),
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(c * r1, -s * r1),
|
||||||
|
(c * r2, -s * r2),
|
||||||
|
)
|
||||||
|
.arc(
|
||||||
|
(c * r2, -s * r2),
|
||||||
|
(c_1 * r2, -s_1 * r2),
|
||||||
|
(c_2 * r2, -s_2 * r2),
|
||||||
|
)
|
||||||
|
.arc(
|
||||||
|
(c_2 * r2, -s_2 * r2),
|
||||||
|
(r2, 0),
|
||||||
|
(c_2 * r2, s_2 * r2),
|
||||||
|
)
|
||||||
|
.arc(
|
||||||
|
(c_2 * r2, s_2 * r2),
|
||||||
|
(c_1 * r2, s_1 * r2),
|
||||||
|
(c * r2, s * r2),
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(c * r2, s * r2),
|
||||||
|
(c * r1, s * r1),
|
||||||
|
)
|
||||||
|
.assemble(tag="wire")
|
||||||
|
.wires().val()
|
||||||
|
)
|
||||||
|
assert isinstance(base, Cq.Wire)
|
||||||
|
|
||||||
|
# The interior sweep is given by theta, but the exterior sweep exceeds the
|
||||||
|
# interior sweep so the wall does not become thinner towards the edges.
|
||||||
|
# If the exterior sweep is theta', it has to satisfy
|
||||||
|
#
|
||||||
|
# sin(theta) * r2 + wall_thickness = sin(theta') * r1
|
||||||
|
x, y = conn_width / 2, conn_height / 2
|
||||||
|
t = wall_thickness
|
||||||
|
dx = middle_offset
|
||||||
|
middle = (
|
||||||
|
Cq.Sketch()
|
||||||
|
# Interior arc, top point
|
||||||
|
.arc(
|
||||||
|
(x - t, y - t),
|
||||||
|
(x - t + dx, 0),
|
||||||
|
(x - t, -y + t),
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(x - t, -y + t),
|
||||||
|
(-x, -y+t)
|
||||||
|
)
|
||||||
|
.segment((-x, -y))
|
||||||
|
.segment((x, -y))
|
||||||
|
# Outer arc, bottom point
|
||||||
|
.arc(
|
||||||
|
(x, -y),
|
||||||
|
(x + dx, 0),
|
||||||
|
(x, y),
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(x, y),
|
||||||
|
(-x, y)
|
||||||
|
)
|
||||||
|
.segment((-x, y-t))
|
||||||
|
#.segment((x2, a))
|
||||||
|
.close()
|
||||||
|
.assemble(tag="wire")
|
||||||
|
.wires().val()
|
||||||
|
)
|
||||||
|
assert isinstance(middle, Cq.Wire)
|
||||||
|
|
||||||
|
x, y = conn_width / 2, conn_height / 2
|
||||||
|
t = wall_thickness
|
||||||
|
tip = (
|
||||||
|
Cq.Sketch()
|
||||||
|
.segment((-x, y), (x, y))
|
||||||
|
.segment((x, -y))
|
||||||
|
.segment((-x, -y))
|
||||||
|
.segment((-x, -y+t))
|
||||||
|
.segment((x-t, -y+t))
|
||||||
|
.segment((x-t, y-t))
|
||||||
|
.segment((-x, y-t))
|
||||||
|
.close()
|
||||||
|
.assemble(tag="wire")
|
||||||
|
.wires().val()
|
||||||
|
)
|
||||||
|
return base, middle, tip
|
||||||
|
def wing_base():
|
||||||
|
root_profile, middle_profile, tip_profile = wing_root_profiles()
|
||||||
|
|
||||||
|
rotate_centre = Cq.Vector(-200, 0, -25)
|
||||||
|
rotate_axis = Cq.Vector(0, 1, 0)
|
||||||
|
terminal_offset = Cq.Vector(-80, 0, 80)
|
||||||
|
terminal_rotate = Cq.Vector(0, -45, 0)
|
||||||
|
|
||||||
|
#middle_profile = middle_profile.moved(Cq.Location((0, 0, -100)))
|
||||||
|
#tip_profile = tip_profile.moved(Cq.Location((0, 0, -200)))
|
||||||
|
middle_profile = middle_profile.rotate(
|
||||||
|
startVector=rotate_centre,
|
||||||
|
endVector=rotate_centre + rotate_axis,
|
||||||
|
angleDegrees = 35,
|
||||||
|
)
|
||||||
|
tip_profile = tip_profile.rotate(
|
||||||
|
startVector=rotate_centre,
|
||||||
|
endVector=rotate_centre + rotate_axis,
|
||||||
|
angleDegrees = 70,
|
||||||
|
)
|
||||||
|
seg1 = (
|
||||||
|
Cq.Workplane('XY')
|
||||||
|
.add(root_profile)
|
||||||
|
.toPending()
|
||||||
|
.transformed(
|
||||||
|
offset=terminal_offset,
|
||||||
|
rotate=terminal_rotate)
|
||||||
|
#.add(middle_profile.moved(Cq.Location((-15, 0, 15))))
|
||||||
|
.add(middle_profile)
|
||||||
|
.toPending()
|
||||||
|
.loft()
|
||||||
|
)
|
||||||
|
seg2 = (
|
||||||
|
Cq.Workplane('XY')
|
||||||
|
.add(middle_profile)
|
||||||
|
.toPending()
|
||||||
|
.workplane()
|
||||||
|
.add(tip_profile)
|
||||||
|
.toPending()
|
||||||
|
.loft()
|
||||||
|
)
|
||||||
|
seg1 = seg1.union(seg2)
|
||||||
|
return seg1
|
Loading…
Reference in New Issue