cosplay: Touhou/Shiki Eiki #7
|
@ -1,5 +1,6 @@
|
|||
import math
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Tuple
|
||||
import cadquery as Cq
|
||||
from nhf import Material, Role
|
||||
from nhf.build import Model, target, assembly, TargetKind
|
||||
|
@ -14,8 +15,15 @@ class Rod(Model):
|
|||
width_tail: float = 60.0
|
||||
margin: float = 10.0
|
||||
|
||||
thickness_top: float = 25.4 / 4
|
||||
thickness_side: float = 25.4 / 8
|
||||
thickness_top: 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
|
||||
fac_bar_top: float = 0.1
|
||||
|
@ -23,10 +31,21 @@ class Rod(Model):
|
|||
fac_window_tsumi_bot: float = 0.63
|
||||
fac_window_tsumi_top: float = 0.88
|
||||
|
||||
fac_window_footer_bot: float = 0.33
|
||||
fac_window_footer_top: float = 0.59
|
||||
fac_window_footer_bot: float = 0.36
|
||||
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):
|
||||
super().__init__(name="rod")
|
||||
|
@ -49,6 +68,18 @@ class Rod(Model):
|
|||
def _reduced_tail_y(self):
|
||||
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:
|
||||
dxh = self._reduced_tip_x
|
||||
dy = self._reduced_y
|
||||
|
@ -173,7 +204,7 @@ class Rod(Model):
|
|||
y_skip = dy_eye + dy_border
|
||||
|
||||
# Construction of the bottom part
|
||||
x_bot = dx * 0.6
|
||||
x_bot = dx * 0.65
|
||||
y3 = dy * 0.4
|
||||
y2 = dy * 0.2
|
||||
y1 = dy * 0.1
|
||||
|
@ -280,21 +311,17 @@ class Rod(Model):
|
|||
.moved(loc)
|
||||
)
|
||||
|
||||
@target(name="surface", kind=TargetKind.DXF)
|
||||
def profile_top(self) -> Cq.Sketch:
|
||||
sketch = (
|
||||
@target(name="bottom", kind=TargetKind.DXF)
|
||||
def profile_bottom(self) -> Cq.Sketch:
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.polygon([
|
||||
(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),
|
||||
])
|
||||
.polygon([p for _, p in self.profile_points()])
|
||||
)
|
||||
|
||||
sketch = (
|
||||
sketch
|
||||
@target(name="top", kind=TargetKind.DXF)
|
||||
def profile_top(self) -> Cq.Sketch:
|
||||
return (
|
||||
self.profile_bottom()
|
||||
.boolean(self._window_tip(), mode='s')
|
||||
.boolean(self._window_eye(True), 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_footer(), mode='s')
|
||||
)
|
||||
return sketch
|
||||
|
||||
def surface_top(self) -> Cq.Workplane:
|
||||
return (
|
||||
Cq.Workplane('XZ')
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(self.profile_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()
|
||||
def assembly(self) -> Cq.Assembly:
|
||||
a = (
|
||||
|
@ -321,5 +533,55 @@ class Rod(Model):
|
|||
material=self.material_shell,
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue