cosplay: Touhou/Shiki Eiki #7

Open
aniva wants to merge 11 commits from touhou/shiki-eiki into main
1 changed files with 282 additions and 20 deletions
Showing only changes of commit 21b3c98856 - Show all commits

View File

@ -1,5 +1,6 @@
import math import math
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Tuple
import cadquery as Cq import cadquery as Cq
from nhf import Material, Role from nhf import Material, Role
from nhf.build import Model, target, assembly, TargetKind from nhf.build import Model, target, assembly, TargetKind
@ -14,8 +15,15 @@ class Rod(Model):
width_tail: float = 60.0 width_tail: float = 60.0
margin: float = 10.0 margin: float = 10.0
thickness_top: float = 25.4 / 4 thickness_top: float = 25.4 / 8
thickness_side: float = 25.4 / 8 # The side which has mounted hinges must be thicker
thickness_side: float = 25.4 / 4
height_internal: float = 30.0
material_shell: Material = Material.WOOD_BIRCH
# Considering the glyph on the top ...
# counted from middle to the bottom # counted from middle to the bottom
fac_bar_top: float = 0.1 fac_bar_top: float = 0.1
@ -23,10 +31,21 @@ class Rod(Model):
fac_window_tsumi_bot: float = 0.63 fac_window_tsumi_bot: float = 0.63
fac_window_tsumi_top: float = 0.88 fac_window_tsumi_top: float = 0.88
fac_window_footer_bot: float = 0.33 fac_window_footer_bot: float = 0.36
fac_window_footer_top: float = 0.59 fac_window_footer_top: float = 0.6
material_shell: Material = Material.WOOD_BIRCH # Considering the side ...
hinge_plate_pos: list[float] = field(default_factory=lambda: [0.1, 0.9])
hinge_plate_length: float = 30.0
hinge_hole_diam: float = 2.5
# Hole distance to axis
hinge_hole_axis_dist: float = 12.5 / 2
# Distance between holes
hinge_hole_sep: float = 15.89
# Consider the reference objects
ref_object_width: float = 50.0
ref_object_length: float = 50.0
def __post_init__(self): def __post_init__(self):
super().__init__(name="rod") super().__init__(name="rod")
@ -49,6 +68,18 @@ class Rod(Model):
def _reduced_tail_y(self): def _reduced_tail_y(self):
return self.width_tail / 2 - self.margin return self.width_tail / 2 - self.margin
def profile_points(self) -> list[Tuple[str, Tuple[float, float]]]:
"""
Points in polygon line order, labaled
"""
return [
("tip", (self.length, 0)),
("mid_r", (self.length - self.length_tip, self.width/2)),
("bot_r", (0, self.width_tail / 2)),
("bot_l", (0, -self.width_tail / 2)),
("mid_l", (self.length - self.length_tip, -self.width/2)),
]
def _window_tip(self) -> Cq.Sketch: def _window_tip(self) -> Cq.Sketch:
dxh = self._reduced_tip_x dxh = self._reduced_tip_x
dy = self._reduced_y dy = self._reduced_y
@ -173,7 +204,7 @@ class Rod(Model):
y_skip = dy_eye + dy_border y_skip = dy_eye + dy_border
# Construction of the bottom part # Construction of the bottom part
x_bot = dx * 0.6 x_bot = dx * 0.65
y3 = dy * 0.4 y3 = dy * 0.4
y2 = dy * 0.2 y2 = dy * 0.2
y1 = dy * 0.1 y1 = dy * 0.1
@ -280,21 +311,17 @@ class Rod(Model):
.moved(loc) .moved(loc)
) )
@target(name="surface", kind=TargetKind.DXF) @target(name="bottom", kind=TargetKind.DXF)
def profile_top(self) -> Cq.Sketch: def profile_bottom(self) -> Cq.Sketch:
sketch = ( return (
Cq.Sketch() Cq.Sketch()
.polygon([ .polygon([p for _, p in self.profile_points()])
(self.length, 0),
(self.length - self.length_tip, self.width/2),
(0, self.width_tail / 2),
(0, -self.width_tail / 2),
(self.length - self.length_tip, -self.width/2),
])
) )
sketch = ( @target(name="top", kind=TargetKind.DXF)
sketch def profile_top(self) -> Cq.Sketch:
return (
self.profile_bottom()
.boolean(self._window_tip(), mode='s') .boolean(self._window_tip(), mode='s')
.boolean(self._window_eye(True), mode='s') .boolean(self._window_eye(True), mode='s')
.boolean(self._window_eye(False), mode='s') .boolean(self._window_eye(False), mode='s')
@ -302,15 +329,200 @@ class Rod(Model):
.boolean(self._window_tsumi(), mode='s') .boolean(self._window_tsumi(), mode='s')
.boolean(self._window_footer(), mode='s') .boolean(self._window_footer(), mode='s')
) )
return sketch
def surface_top(self) -> Cq.Workplane: def surface_top(self) -> Cq.Workplane:
return ( return (
Cq.Workplane('XZ') Cq.Workplane('XY')
.placeSketch(self.profile_top()) .placeSketch(self.profile_top())
.extrude(self.thickness_top) .extrude(self.thickness_top)
) )
def surface_bottom(self) -> Cq.Workplane:
surface = (
Cq.Workplane('XY')
.placeSketch(self.profile_bottom())
.extrude(self.thickness_top)
)
plane = surface.faces(">Z").workplane()
for (name, p) in self.profile_points():
plane.moveTo(*p).tagPlane(name)
return surface
# Properties of the side surfaces
@property
def length_edge_tip(self):
return math.sqrt(self.length_tip ** 2 + (self.width / 2) ** 2)
@property
def length_edge_tail(self):
dw = (self.width - self.width_tail) / 2
return math.sqrt(self.length_tail ** 2 + dw ** 2)
@property
def tip_incident_angle(self):
"""
Angle (measuring from vertical) at which the tip edge pieces must be
sanded in order to make them not collide into each other.
"""
return math.atan2(self.length_tip, self.width / 2)
@property
def shoulder_incident_angle(self) -> float:
angle_tip = math.atan2(self.width / 2, self.length_tip)
angle_tail = math.atan2((self.width - self.width_tail) / 2, self.length_tail)
return (angle_tip + angle_tail) / 2
@target(name="ref-tip")
def ref_tip(self) -> Cq.Workplane:
angle = self.tip_incident_angle
w = self.ref_object_width
drop = math.sin(angle) * w
profile = (
Cq.Sketch()
.polygon([
(0, 0),
(0, w),
(w, w),
(w - drop, 0),
])
)
return (
Cq.Workplane()
.placeSketch(profile)
.extrude(self.ref_object_length)
)
@target(name="ref-shoulder")
def ref_shoulder(self) -> Cq.Workplane:
angle = self.shoulder_incident_angle
w = self.ref_object_width
drop = math.sin(angle) * w
profile = (
Cq.Sketch()
.polygon([
(0, 0),
(0, w),
(w, w),
(w - drop, 0),
])
)
return (
Cq.Workplane()
.placeSketch(profile)
.extrude(self.ref_object_length)
)
@target(name="side-tip-2x", kind=TargetKind.DXF)
def profile_side_tip(self):
l = self.length_edge_tip
w = self.height_internal
return (
Cq.Sketch()
.push([(l/2, w/2)])
.rect(l, w)
)
@target(name="side-tail", kind=TargetKind.DXF)
def profile_side_tail(self):
"""
Plain side 2 with no hinge
"""
l = self.length_edge_tail
w = self.height_internal
return (
Cq.Sketch()
.push([(l/2, w/2)])
.rect(l, w)
)
@target(name="side-hinge-plate", kind=TargetKind.DXF)
def profile_side_hinge_plate(self):
l = self.hinge_plate_length
w = self.height_internal / 2
return (
Cq.Sketch()
.push([(l/2, w/2)])
.rect(l, w)
.push([
(self.hinge_hole_sep / 2, self.hinge_hole_axis_dist),
(-self.hinge_hole_sep / 2, self.hinge_hole_axis_dist),
])
.circle(self.hinge_hole_diam / 2, mode='s')
)
@target(name="side-tail-hinged", kind=TargetKind.DXF)
def profile_side_tail_hinged(self):
"""
Plain side 2 with no hinge
"""
l = self.length_edge_tail
w = self.height_internal
# Holes for hinge
plate_pos = [
(t * l, w * 3/4) for t in self.hinge_plate_pos
]
hole_pos = [
(self.hinge_hole_sep / 2, self.hinge_hole_axis_dist),
(-self.hinge_hole_sep / 2, self.hinge_hole_axis_dist),
]
return (
self.profile_side_tail()
.push(plate_pos)
.rect(self.hinge_plate_length, w/2, mode='s')
.push([
(hx + px, w/2 - hy)
for hx, hy in hole_pos
for px, _ in plate_pos
])
.circle(self.hinge_hole_diam / 2, mode='s')
)
@target(name="side-bot", kind=TargetKind.DXF)
def profile_side_bot(self):
l = self.width_tail - self.thickness_side * 2
w = self.height_internal
return (
Cq.Sketch()
.rect(l, w)
)
def surface_side_tip(self):
result = (
Cq.Workplane('XY')
.placeSketch(self.profile_side_tip())
.extrude(self.thickness_side)
)
plane = result.faces(">Y").workplane()
plane.moveTo(0, 0).tagPlane("bot")
plane.moveTo(-self.length_edge_tip, 0).tagPlane("top")
return result
def surface_side_tail(self):
result = (
Cq.Workplane('XY')
.placeSketch(self.profile_side_tail())
.extrude(self.thickness_side)
)
plane = result.faces(">Y").workplane()
plane.moveTo(0, 0).tagPlane("bot")
plane.moveTo(-self.length_edge_tail, 0).tagPlane("top")
return result
def surface_side_tail_hinged(self):
result = (
Cq.Workplane('XY')
.placeSketch(self.profile_side_tail_hinged())
.extrude(self.thickness_side)
)
plane = result.faces(">Y").workplane()
plane.moveTo(0, 0).tagPlane("bot")
plane.moveTo(-self.length_edge_tail, 0).tagPlane("top")
return result
def surface_side_bot(self):
result = (
Cq.Workplane('XY')
.placeSketch(self.profile_side_bot())
.extrude(self.thickness_side)
)
plane = result.faces(">Y").workplane()
plane.moveTo(self.width_tail / 2, 0).tagPlane("bot")
plane.moveTo(-self.width_tail / 2, 0).tagPlane("top")
return result
@assembly() @assembly()
def assembly(self) -> Cq.Assembly: def assembly(self) -> Cq.Assembly:
a = ( a = (
@ -321,5 +533,55 @@ class Rod(Model):
material=self.material_shell, material=self.material_shell,
role=Role.STRUCTURE | Role.DECORATION role=Role.STRUCTURE | Role.DECORATION
) )
.constrain("top", "Fixed")
.addS(
self.surface_bottom(),
name="bottom",
material=self.material_shell,
role=Role.STRUCTURE,
loc=Cq.Location(0, 0, -self.thickness_top - self.height_internal)
)
.constrain("bottom", "Fixed")
.addS(
self.surface_side_tip(),
name="side_tip_l",
material=self.material_shell,
role=Role.STRUCTURE,
)
.constrain("bottom?tip", "side_tip_l?top", "Plane")
.constrain("bottom?mid_l", "side_tip_l?bot", "Plane")
.addS(
self.surface_side_tip(),
name="side_tip_r",
material=self.material_shell,
role=Role.STRUCTURE,
)
.constrain("bottom?tip", "side_tip_r?bot", "Plane")
.constrain("bottom?mid_r", "side_tip_r?top", "Plane")
.addS(
self.surface_side_tail(),
name="side_tail_l",
material=self.material_shell,
role=Role.STRUCTURE,
)
.constrain("bottom?mid_l", "side_tail_l?top", "Plane")
.constrain("bottom?bot_l", "side_tail_l?bot", "Plane")
.addS(
self.surface_side_tail_hinged(),
name="side_tail_r",
material=self.material_shell,
role=Role.STRUCTURE,
)
.constrain("bottom?mid_r", "side_tail_r?bot", "Plane")
.constrain("bottom?bot_r", "side_tail_r?top", "Plane")
.addS(
self.surface_side_bot(),
name="side_bot",
material=self.material_shell,
role=Role.STRUCTURE,
)
.constrain("bottom?bot_l", "side_bot?top", "Plane")
.constrain("bottom?bot_r", "side_bot?bot", "Plane")
.solve()
) )
return a return a