cosplay: Touhou/Houjuu Nue #4
|
@ -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
|
||||
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
|
||||
"""
|
||||
# Thickness of the exoskeleton panel in millimetres
|
||||
panel_thickness: float = 25.4 / 16
|
||||
|
||||
# Harness
|
||||
|
@ -28,12 +55,11 @@ class Parameters:
|
|||
]
|
||||
|
||||
# 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
|
||||
"""
|
||||
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_thickness = 10
|
||||
hs_joint_ring_thickness = 5
|
||||
|
@ -49,12 +75,8 @@ class Parameters:
|
|||
hs_joint_axis_cbore_diam = 20
|
||||
hs_joint_axis_cbore_depth = 3
|
||||
|
||||
|
||||
"""
|
||||
Radius of the mounting mechanism of the wing root. This is constrained by
|
||||
the size of the harness.
|
||||
"""
|
||||
root_radius: float = 60
|
||||
# Exterior radius of the wing root assembly
|
||||
wing_root_radius = 40
|
||||
|
||||
"""
|
||||
Heights for various wing joints, where the numbers start from the first joint.
|
||||
|
@ -64,6 +86,10 @@ class Parameters:
|
|||
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 [
|
||||
|
@ -95,7 +121,7 @@ class Parameters:
|
|||
sketch
|
||||
.push(conn)
|
||||
.tag(tag)
|
||||
.circle(self.harness_to_wing_base_hole_diam / 2, mode='s')
|
||||
.circle(self.harness_to_root_conn_diam / 2, mode='s')
|
||||
.reset()
|
||||
)
|
||||
return sketch
|
||||
|
@ -171,7 +197,7 @@ class Parameters:
|
|||
.workplane()
|
||||
.pushPoints(conn)
|
||||
.cboreHole(
|
||||
diameter=self.harness_to_wing_base_hole_diam,
|
||||
diameter=self.harness_to_root_conn_diam,
|
||||
cboreDiameter=self.hs_joint_corner_cbore_diam,
|
||||
cboreDepth=self.hs_joint_corner_cbore_depth)
|
||||
)
|
||||
|
@ -245,23 +271,16 @@ class Parameters:
|
|||
)
|
||||
return result
|
||||
|
||||
def wing_root(self,
|
||||
side_width=30,
|
||||
side_height=100) -> Cq.Shape:
|
||||
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.
|
||||
"""
|
||||
result = (
|
||||
Cq.Workplane("XY")
|
||||
.circle(self.root_radius)
|
||||
.transformed(offset=Cq.Vector(80, 0, 80),
|
||||
rotate=Cq.Vector(0, 45, 0))
|
||||
.rect(side_width, side_height)
|
||||
.loft()
|
||||
.val()
|
||||
)
|
||||
return result
|
||||
return MW.wing_base().val()#self.wing_root_radius)
|
||||
|
||||
######################
|
||||
# Assemblies #
|
||||
######################
|
||||
|
||||
def harness_assembly(self):
|
||||
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