cosplay: Touhou/Shiki Eiki #7
|
@ -2,12 +2,16 @@ from dataclasses import dataclass, field
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||||
import nhf.touhou.shiki_eiki.rod as MR
|
import nhf.touhou.shiki_eiki.rod as MR
|
||||||
|
import nhf.touhou.shiki_eiki.crown as MC
|
||||||
|
import nhf.touhou.shiki_eiki.epaulette as ME
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Parameters(Model):
|
class Parameters(Model):
|
||||||
|
|
||||||
rod: MR.Rod = field(default_factory=lambda: MR.Rod())
|
rod: MR.Rod = field(default_factory=lambda: MR.Rod())
|
||||||
|
crown: MC.Crown = field(default_factory=lambda: MC.Crown())
|
||||||
|
epaulette: ME.Epaulette = field(default_factory=lambda: ME.Epaulette())
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__init__(name="shiki-eiki")
|
super().__init__(name="shiki-eiki")
|
||||||
|
@ -15,6 +19,12 @@ class Parameters(Model):
|
||||||
@submodel(name="rod")
|
@submodel(name="rod")
|
||||||
def submodel_rod(self) -> Model:
|
def submodel_rod(self) -> Model:
|
||||||
return self.rod
|
return self.rod
|
||||||
|
@submodel(name="crown")
|
||||||
|
def submodel_crown(self) -> Model:
|
||||||
|
return self.crown
|
||||||
|
@submodel(name="epaulette")
|
||||||
|
def submodel_epaulette(self) -> Model:
|
||||||
|
return self.epaulette
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -0,0 +1,294 @@
|
||||||
|
import math
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
import cadquery as Cq
|
||||||
|
from nhf import Material, Role
|
||||||
|
from nhf.build import Model, target, assembly
|
||||||
|
import nhf.utils
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Crown(Model):
|
||||||
|
|
||||||
|
facets: int = 5
|
||||||
|
# Lower circumference
|
||||||
|
base_circ: float = 570.0
|
||||||
|
# Upper circumference
|
||||||
|
tilt_circ: float = 670.0
|
||||||
|
height: float = 120.0
|
||||||
|
|
||||||
|
margin: float = 10.0
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
super().__init__(name="crown")
|
||||||
|
|
||||||
|
assert self.tilt_circ > self.base_circ
|
||||||
|
|
||||||
|
@property
|
||||||
|
def facet_width_lower(self):
|
||||||
|
return self.base_circ / self.facets
|
||||||
|
@property
|
||||||
|
def facet_width_upper(self):
|
||||||
|
return self.tilt_circ / self.facets
|
||||||
|
|
||||||
|
@target(name="side")
|
||||||
|
def profile_base(self) -> Cq.Sketch:
|
||||||
|
# Generate the pentagonal shape
|
||||||
|
|
||||||
|
dx_l = self.facet_width_lower
|
||||||
|
dx_u = self.facet_width_upper
|
||||||
|
dy = self.height
|
||||||
|
return (
|
||||||
|
Cq.Sketch()
|
||||||
|
.polygon([
|
||||||
|
(dx_l/2, 0),
|
||||||
|
(dx_u/2, dy/2),
|
||||||
|
(0, dy),
|
||||||
|
(-dx_u/2, dy/2),
|
||||||
|
(-dx_l/2, 0),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
@target(name="front")
|
||||||
|
def profile_front(self) -> Cq.Sketch:
|
||||||
|
dx_l = self.facet_width_lower
|
||||||
|
dx_u = self.facet_width_upper
|
||||||
|
dy = self.height
|
||||||
|
|
||||||
|
window_length = dy / 5
|
||||||
|
window_height = self.margin / 2
|
||||||
|
window = (
|
||||||
|
Cq.Sketch()
|
||||||
|
.rect(window_length, window_height)
|
||||||
|
)
|
||||||
|
window_p1 = Cq.Location.from2d(
|
||||||
|
dx_u/2 - self.margin - window_length * 0.4,
|
||||||
|
dy/2 + self.margin,
|
||||||
|
math.degrees(math.atan2(dy/2, -dx_u/2)),
|
||||||
|
)
|
||||||
|
window_p2 = Cq.Location.from2d(
|
||||||
|
dx_l/2 - self.margin + window_length * 0.15,
|
||||||
|
window_length/2 + self.margin,
|
||||||
|
math.degrees(math.atan2(dy/2, (dx_u-dx_l)/2)),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Carve the scale
|
||||||
|
z = dy * 1/64 # "Pen" Thickness
|
||||||
|
scale_pan_x = dx_l / 2 * 0.6
|
||||||
|
scale_pan_y = dy / 2 * 0.7
|
||||||
|
pan_dx = dx_l * 1/4
|
||||||
|
pan_dy = dy * 1/16
|
||||||
|
|
||||||
|
scale_pan = (
|
||||||
|
Cq.Sketch()
|
||||||
|
.arc(
|
||||||
|
(- pan_dx/2, pan_dy),
|
||||||
|
(0, 0),
|
||||||
|
(+ pan_dx/2, pan_dy),
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(+pan_dx/2, pan_dy),
|
||||||
|
(+pan_dx/2 - z, pan_dy),
|
||||||
|
)
|
||||||
|
.arc(
|
||||||
|
(-pan_dx/2 + z, pan_dy),
|
||||||
|
(0, z),
|
||||||
|
(+pan_dx/2 - z, pan_dy),
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(-pan_dx/2, pan_dy),
|
||||||
|
(-pan_dx/2 + z, pan_dy),
|
||||||
|
)
|
||||||
|
.assemble()
|
||||||
|
)
|
||||||
|
loc_scale_pan = Cq.Location.from2d(scale_pan_x, scale_pan_y)
|
||||||
|
loc_scale_pan2 = Cq.Location.from2d(-scale_pan_x, scale_pan_y)
|
||||||
|
|
||||||
|
scale_base_y = dy / 2 * 0.36
|
||||||
|
scale_base_x = dx_l / 10
|
||||||
|
assert scale_base_y < scale_pan_y
|
||||||
|
assert scale_base_x < scale_pan_x
|
||||||
|
|
||||||
|
scale_body = (
|
||||||
|
Cq.Sketch()
|
||||||
|
.arc(
|
||||||
|
(scale_pan_x, scale_pan_y),
|
||||||
|
(0, scale_base_y),
|
||||||
|
(-scale_pan_x, scale_pan_y),
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(-scale_pan_x, scale_pan_y),
|
||||||
|
(-scale_pan_x+z, scale_pan_y+z),
|
||||||
|
)
|
||||||
|
.arc(
|
||||||
|
(scale_pan_x - z, scale_pan_y+z),
|
||||||
|
(0, scale_base_y + z),
|
||||||
|
(-scale_pan_x + z, scale_pan_y+z),
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(scale_pan_x, scale_pan_y),
|
||||||
|
(scale_pan_x-z, scale_pan_y+z),
|
||||||
|
)
|
||||||
|
.assemble()
|
||||||
|
.polygon([
|
||||||
|
(scale_base_x, scale_base_y + z/2),
|
||||||
|
(scale_base_x, self.margin),
|
||||||
|
(scale_base_x-z, self.margin),
|
||||||
|
(scale_base_x-z, scale_base_y-z),
|
||||||
|
|
||||||
|
(-scale_base_x+z, scale_base_y-z),
|
||||||
|
(-scale_base_x+z, self.margin),
|
||||||
|
(-scale_base_x, self.margin),
|
||||||
|
(-scale_base_x, scale_base_y + z/2),
|
||||||
|
], mode='a')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Needle
|
||||||
|
needle_y_top = dy - self.margin
|
||||||
|
needle_y_mid = dy * 0.7
|
||||||
|
needle_dx = scale_base_x * 2
|
||||||
|
needle = (
|
||||||
|
Cq.Sketch()
|
||||||
|
.segment(
|
||||||
|
(z, needle_y_mid),
|
||||||
|
(z, scale_base_y),
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(z, scale_base_y),
|
||||||
|
(-z, scale_base_y),
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(-z, scale_base_y),
|
||||||
|
(-z, needle_y_mid),
|
||||||
|
)
|
||||||
|
.bezier([
|
||||||
|
(0, needle_y_top),
|
||||||
|
(0, (needle_y_top + needle_y_mid)/2),
|
||||||
|
(needle_dx, (needle_y_top + needle_y_mid)/2),
|
||||||
|
(z, needle_y_mid),
|
||||||
|
])
|
||||||
|
.bezier([
|
||||||
|
(0, needle_y_top),
|
||||||
|
(0, (needle_y_top + needle_y_mid)/2),
|
||||||
|
(-needle_dx, (needle_y_top + needle_y_mid)/2),
|
||||||
|
(-z, needle_y_mid),
|
||||||
|
])
|
||||||
|
.assemble()
|
||||||
|
)
|
||||||
|
z2 = z * 2
|
||||||
|
needle_inner = (
|
||||||
|
Cq.Sketch()
|
||||||
|
.segment(
|
||||||
|
(z2, needle_y_mid - z2),
|
||||||
|
(-z2, needle_y_mid - z2)
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(z2, needle_y_mid - z2),
|
||||||
|
(z2, needle_y_mid + z2),
|
||||||
|
)
|
||||||
|
.segment(
|
||||||
|
(-z2, needle_y_mid - z2),
|
||||||
|
(-z2, needle_y_mid + z2),
|
||||||
|
)
|
||||||
|
.bezier([
|
||||||
|
(0, needle_y_top - z2),
|
||||||
|
(0, (needle_y_top + needle_y_mid)/2),
|
||||||
|
(needle_dx-z2*2, (needle_y_top + needle_y_mid)/2),
|
||||||
|
(z2, needle_y_mid + z2),
|
||||||
|
])
|
||||||
|
.bezier([
|
||||||
|
(0, needle_y_top - z2),
|
||||||
|
(0, (needle_y_top + needle_y_mid)/2),
|
||||||
|
(-needle_dx+z2*2, (needle_y_top + needle_y_mid)/2),
|
||||||
|
(-z2, needle_y_mid + z2),
|
||||||
|
])
|
||||||
|
.assemble()
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.profile_base()
|
||||||
|
.boolean(window.moved(window_p1), mode='s')
|
||||||
|
.boolean(window.moved(window_p1.flip_x()), mode='s')
|
||||||
|
.boolean(window.moved(window_p2), mode='s')
|
||||||
|
.boolean(window.moved(window_p2.flip_x()), mode='s')
|
||||||
|
.boolean(scale_pan.moved(loc_scale_pan), mode='s')
|
||||||
|
.boolean(scale_pan.moved(loc_scale_pan2), mode='s')
|
||||||
|
.boolean(scale_body, mode='s')
|
||||||
|
.boolean(needle, mode='s')
|
||||||
|
.boolean(needle_inner, mode='a')
|
||||||
|
.clean()
|
||||||
|
)
|
||||||
|
|
||||||
|
@target(name="side-guard")
|
||||||
|
def profile_side_guard(self) -> Cq.Sketch:
|
||||||
|
dx = self.facet_width_lower / 2
|
||||||
|
dy = self.height
|
||||||
|
|
||||||
|
# Main control points
|
||||||
|
p_mid = Cq.Location.from2d(0, 0.5 * dy)
|
||||||
|
p_mid_v = Cq.Location.from2d(10/57 * dx, 0)
|
||||||
|
p_top1 = Cq.Location.from2d(0.408 * dx, 5/24 * dy)
|
||||||
|
p_top1_v = Cq.Location.from2d(0.13 * dx, 0)
|
||||||
|
p_top2 = Cq.Location.from2d(0.737 * dx, 0.255 * dy)
|
||||||
|
p_top2_c1 = p_top2 * Cq.Location.from2d(-0.105 * dx, 0.033 * dy)
|
||||||
|
p_top2_c2 = p_top2 * Cq.Location.from2d(-0.053 * dx, -0.09 * dy)
|
||||||
|
p_top3 = Cq.Location.from2d(0.929 * dx, 0.145 * dy)
|
||||||
|
p_top3_v = Cq.Location.from2d(0.066 * dx, 0.033 * dy)
|
||||||
|
p_top4 = Cq.Location.from2d(0.85 * dx, 0.374 * dy)
|
||||||
|
p_top4_v = Cq.Location.from2d(-0.053 * dx, 0.008 * dy)
|
||||||
|
p_top5 = Cq.Location.from2d(0.54 * dx, 0.349 * dy)
|
||||||
|
p_top5_c1 = p_top5 * Cq.Location.from2d(0.103 * dx, 0.017 * dy)
|
||||||
|
p_top5_c2 = p_top5 * Cq.Location.from2d(0.158 * dx, 0.034 * dy)
|
||||||
|
p_base_c = Cq.Location.from2d(1.245 * dx, 0.55 * dy)
|
||||||
|
p_base = Cq.Location.from2d(dx, 0)
|
||||||
|
|
||||||
|
bezier_groups = [
|
||||||
|
[
|
||||||
|
p_base,
|
||||||
|
p_base_c,
|
||||||
|
p_top5_c2,
|
||||||
|
p_top5,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
p_top5,
|
||||||
|
p_top5_c1,
|
||||||
|
p_top4 * p_top4_v,
|
||||||
|
p_top4,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
p_top4,
|
||||||
|
p_top4 * p_top4_v.inverse.scale(4),
|
||||||
|
p_top3 * p_top3_v,
|
||||||
|
p_top3,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
p_top3,
|
||||||
|
p_top3 * p_top3_v.inverse,
|
||||||
|
p_top2_c2,
|
||||||
|
p_top2,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
p_top2,
|
||||||
|
p_top2_c1,
|
||||||
|
p_top1 * p_top1_v,
|
||||||
|
p_top1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
p_top1,
|
||||||
|
p_top1 * p_top1_v.inverse,
|
||||||
|
p_mid * p_mid_v,
|
||||||
|
p_mid,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
sketch = (
|
||||||
|
Cq.Sketch()
|
||||||
|
.segment(
|
||||||
|
p_base.to2d_pos(),
|
||||||
|
p_base.flip_x().to2d_pos(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for bezier_group in bezier_groups:
|
||||||
|
sketch = (
|
||||||
|
sketch
|
||||||
|
.bezier([p.to2d_pos() for p in bezier_group])
|
||||||
|
.bezier([p.flip_x().to2d_pos() for p in bezier_group])
|
||||||
|
)
|
||||||
|
return sketch.assemble()
|
|
@ -0,0 +1,12 @@
|
||||||
|
import math
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
import cadquery as Cq
|
||||||
|
from nhf import Material, Role
|
||||||
|
from nhf.build import Model, target, assembly
|
||||||
|
import nhf.utils
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Epaulette(Model):
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
super().__init__(name="epaulette")
|
|
@ -24,6 +24,7 @@ class Rod(Model):
|
||||||
fac_window_footer_top: float = 0.59
|
fac_window_footer_top: float = 0.59
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
super().__init__(name="rod")
|
||||||
self.loc_core = Cq.Location.from2d(self.length - self.length_tip, 0)
|
self.loc_core = Cq.Location.from2d(self.length - self.length_tip, 0)
|
||||||
assert self.length_tip * 2 < self.length
|
assert self.length_tip * 2 < self.length
|
||||||
#assert self.fac_bar_top + self.fac_window_tsumi_top < 1
|
#assert self.fac_bar_top + self.fac_window_tsumi_top < 1
|
||||||
|
|
|
@ -53,6 +53,11 @@ def is2d(self: Cq.Location) -> bool:
|
||||||
return z == 0 and rx == 0 and ry == 0
|
return z == 0 and rx == 0 and ry == 0
|
||||||
Cq.Location.is2d = is2d
|
Cq.Location.is2d = is2d
|
||||||
|
|
||||||
|
def scale(self: Cq.Location, fac: float) -> bool:
|
||||||
|
(x, y, z), (rx, ry, rz) = self.toTuple()
|
||||||
|
return Cq.Location(x*fac, y*fac, z*fac, rx, ry, rz)
|
||||||
|
Cq.Location.scale = scale
|
||||||
|
|
||||||
def to2d(self: Cq.Location) -> Tuple[Tuple[float, float], float]:
|
def to2d(self: Cq.Location) -> Tuple[Tuple[float, float], float]:
|
||||||
"""
|
"""
|
||||||
Returns position and angle
|
Returns position and angle
|
||||||
|
@ -91,7 +96,7 @@ Cq.Location.with_angle_2d = with_angle_2d
|
||||||
|
|
||||||
def flip_x(self: Cq.Location) -> Cq.Location:
|
def flip_x(self: Cq.Location) -> Cq.Location:
|
||||||
(x, y), a = self.to2d()
|
(x, y), a = self.to2d()
|
||||||
return Cq.Location.from2d(-x, y, 90 - a)
|
return Cq.Location.from2d(-x, y, 180 - a)
|
||||||
Cq.Location.flip_x = flip_x
|
Cq.Location.flip_x = flip_x
|
||||||
def flip_y(self: Cq.Location) -> Cq.Location:
|
def flip_y(self: Cq.Location) -> Cq.Location:
|
||||||
(x, y), a = self.to2d()
|
(x, y), a = self.to2d()
|
||||||
|
|
Loading…
Reference in New Issue