cosplay: Touhou/Houjuu Nue #1

Closed
aniva wants to merge 37 commits from touhou/houjuu-nue into main
2 changed files with 209 additions and 28 deletions
Showing only changes of commit 32e5f543d9 - Show all commits

View File

@ -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()

View File

@ -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