feat: Eiki rod
This commit is contained in:
parent
bfa96e7cef
commit
95313b76eb
|
@ -1,3 +1,4 @@
|
|||
import math
|
||||
from dataclasses import dataclass, field
|
||||
import cadquery as Cq
|
||||
from nhf import Material, Role
|
||||
|
@ -11,6 +12,270 @@ class Rod(Model):
|
|||
length: float = 550.0
|
||||
length_tip: float = 100.0
|
||||
width_tail: float = 60.0
|
||||
margin: float = 10.0
|
||||
|
||||
# counted from middle to the bottom
|
||||
fac_bar_top: float = 0.1
|
||||
# counted from bottom to top
|
||||
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
|
||||
|
||||
def __post_init__(self):
|
||||
self.loc_core = Cq.Location.from2d(self.length - self.length_tip, 0)
|
||||
assert self.length_tip * 2 < self.length
|
||||
#assert self.fac_bar_top + self.fac_window_tsumi_top < 1
|
||||
assert self.fac_window_tsumi_bot < self.fac_window_tsumi_top
|
||||
|
||||
@property
|
||||
def length_tail(self):
|
||||
return self.length - self.length_tip
|
||||
|
||||
@property
|
||||
def _reduced_tip_x(self):
|
||||
return self.length_tip - self.margin
|
||||
@property
|
||||
def _reduced_y(self):
|
||||
return self.width / 2 - self.margin
|
||||
@property
|
||||
def _reduced_tail_y(self):
|
||||
return self.width_tail / 2 - self.margin
|
||||
|
||||
def _window_tip(self) -> Cq.Sketch:
|
||||
dxh = self._reduced_tip_x
|
||||
dy = self._reduced_y
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.segment(
|
||||
(dxh, 0),
|
||||
(dxh / 2, dy / 2),
|
||||
)
|
||||
.bezier([
|
||||
(dxh / 2, dy / 2),
|
||||
(dxh * 0.6, dy * 0.4),
|
||||
(dxh * 0.6, -dy * 0.4),
|
||||
(dxh / 2, -dy / 2),
|
||||
])
|
||||
.segment(
|
||||
(dxh, 0),
|
||||
)
|
||||
.assemble()
|
||||
.moved(self.loc_core.to2d_pos())
|
||||
)
|
||||
def _window_eye(self, refl: bool = False) -> Cq.Sketch:
|
||||
|
||||
sign = -1 if refl else 1
|
||||
dxh = self._reduced_tip_x
|
||||
xm = dxh * 0.45
|
||||
dy = sign * self._reduced_y
|
||||
fac = 0.05
|
||||
|
||||
p1 = Cq.Location.from2d(xm, sign * self.margin / 2)
|
||||
p2 = Cq.Location.from2d(dxh * 0.1, sign * self.margin / 2)
|
||||
p3 = Cq.Location.from2d(dxh * 0.15, dy * 0.55)
|
||||
p4 = Cq.Location.from2d(dxh * 0.4, dy * 0.45)
|
||||
d4 = Cq.Location.from2d(dxh * fac, -dy * fac)
|
||||
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.segment(
|
||||
p1.to2d_pos(),
|
||||
p2.to2d_pos(),
|
||||
)
|
||||
.bezier([
|
||||
p2.to2d_pos(),
|
||||
(p2 * Cq.Location.from2d(0, dy * fac)).to2d_pos(),
|
||||
(p3 * Cq.Location.from2d(-dxh * fac, -dy * fac)).to2d_pos(),
|
||||
p3.to2d_pos(),
|
||||
])
|
||||
.bezier([
|
||||
p3.to2d_pos(),
|
||||
(p3 * Cq.Location.from2d(0, dy * fac)).to2d_pos(),
|
||||
(p4 * d4.inverse).to2d_pos(),
|
||||
p4.to2d_pos(),
|
||||
])
|
||||
.bezier([
|
||||
p4.to2d_pos(),
|
||||
(p4 * d4).to2d_pos(),
|
||||
(p1 * Cq.Location.from2d(0, dy * fac)).to2d_pos(),
|
||||
p1.to2d_pos(),
|
||||
])
|
||||
.assemble()
|
||||
.moved(self.loc_core.to2d_pos())
|
||||
)
|
||||
|
||||
def _window_bar(self) -> Cq.Sketch():
|
||||
dxh = self._reduced_tip_x
|
||||
dy = self._reduced_y
|
||||
dyt = self._reduced_tail_y
|
||||
dxt = self.length_tail
|
||||
|
||||
ext_fac = self.fac_bar_top
|
||||
|
||||
p_corner = Cq.Location.from2d(0, dy)
|
||||
p_top = Cq.Location.from2d(0.3 * dxh, 0.7 * dy)
|
||||
p_bot = Cq.Location.from2d(-ext_fac * dxt, dy + ext_fac * (dyt - dy))
|
||||
p_top_int = p_corner * Cq.Location.from2d(.05 * dxh, -.2 * dy)
|
||||
p_top_ctrl = Cq.Location.from2d(0, .3 * dy)
|
||||
p_bot_int = p_corner * Cq.Location.from2d(-.15 * dxh, -.2 * dy)
|
||||
p_bot_ctrl = Cq.Location.from2d(-.25 * dxh, .3 * dy)
|
||||
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.segment(
|
||||
p_corner.to2d_pos(),
|
||||
p_top.to2d_pos(),
|
||||
)
|
||||
.segment(p_top_int.to2d_pos())
|
||||
.bezier([
|
||||
p_top_int.to2d_pos(),
|
||||
p_top_ctrl.to2d_pos(),
|
||||
p_top_ctrl.flip_y().to2d_pos(),
|
||||
p_top_int.flip_y().to2d_pos(),
|
||||
])
|
||||
.segment(p_top.flip_y().to2d_pos())
|
||||
.segment(p_corner.flip_y().to2d_pos())
|
||||
.segment(p_bot.flip_y().to2d_pos())
|
||||
.segment(p_bot_int.flip_y().to2d_pos())
|
||||
.bezier([
|
||||
p_bot_int.flip_y().to2d_pos(),
|
||||
p_bot_ctrl.flip_y().to2d_pos(),
|
||||
p_bot_ctrl.to2d_pos(),
|
||||
p_bot_int.to2d_pos(),
|
||||
])
|
||||
.segment(p_bot.to2d_pos())
|
||||
.segment(p_corner.to2d_pos())
|
||||
.assemble()
|
||||
.moved(self.loc_core.to2d_pos())
|
||||
)
|
||||
|
||||
def _window_tsumi(self) -> Cq.Sketch:
|
||||
dx = (self.fac_window_tsumi_top - self.fac_window_tsumi_bot) * self.length_tail
|
||||
dy = 2 * self._reduced_y * 0.8
|
||||
loc = Cq.Location(self.fac_window_tsumi_bot * self.length_tail, 0)
|
||||
|
||||
# Construction of the top part of the kanji
|
||||
|
||||
dx_top = dx * 0.3
|
||||
x_top = dx - dx_top / 2
|
||||
dy_top = dy
|
||||
dy_eye = dy * 0.2
|
||||
dy_border = (dy_top - 3 * dy_eye) / 4
|
||||
# The skip must follow 3 * eye + 4 * border = dy_top
|
||||
y_skip = dy_eye + dy_border
|
||||
|
||||
# Construction of the bottom part
|
||||
x_bot = dx * 0.6
|
||||
y3 = dy * 0.4
|
||||
y2 = dy * 0.2
|
||||
y1 = dy * 0.1
|
||||
# x/y-centers of the legs
|
||||
x_leg0 = x_bot / 14
|
||||
dx_leg = x_bot / 7
|
||||
y_leg = (y3 + y1) / 2
|
||||
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.push([(x_top, 0)])
|
||||
.rect(dx_top, dy_top)
|
||||
.push([
|
||||
(x_top, -y_skip),
|
||||
(x_top, 0),
|
||||
(x_top, y_skip),
|
||||
])
|
||||
.rect(dx_top / 3, dy_eye, mode='s')
|
||||
|
||||
# Construct the two sides
|
||||
.push([
|
||||
(x_bot / 2, (y2 + y1) / 2),
|
||||
(x_bot / 2, -(y2 + y1) / 2),
|
||||
])
|
||||
.rect(x_bot, y2 - y1, mode='a')
|
||||
.push([
|
||||
(x_leg0 + dx_leg, y_leg),
|
||||
(x_leg0 + 3 * dx_leg, y_leg),
|
||||
(x_leg0 + 5 * dx_leg, y_leg),
|
||||
(x_leg0 + dx_leg, -y_leg),
|
||||
(x_leg0 + 3 * dx_leg, -y_leg),
|
||||
(x_leg0 + 5 * dx_leg, -y_leg),
|
||||
])
|
||||
.rect(dx_leg, y3 - y1, mode='a')
|
||||
|
||||
.moved(loc)
|
||||
)
|
||||
|
||||
def _window_footer(self) -> Cq.Sketch:
|
||||
x_bot = self.fac_window_footer_bot * self.length_tail
|
||||
dx = (self.fac_window_footer_top - self.fac_window_footer_bot) * self.length_tail
|
||||
loc = Cq.Location(x_bot, 0)
|
||||
|
||||
dy = self._reduced_y * 0.8
|
||||
|
||||
# eyes
|
||||
eye_y2 = dy * .5
|
||||
eye_y1 = dy * .2
|
||||
eye_width = eye_y2 - eye_y1
|
||||
eye_x = dx - eye_width / 2
|
||||
|
||||
# bar polygon
|
||||
bar_x0 = dx * 0.65
|
||||
bar_dx = dx * 0.1
|
||||
bar_x1 = bar_x0 + bar_dx
|
||||
bar_x2 = bar_x0 + bar_dx * 2
|
||||
bar_x3 = bar_x0 + bar_dx * 3
|
||||
bar_y1 = dy * .75
|
||||
assert bar_y1 > eye_y2
|
||||
bar_y2 = dy * .9
|
||||
assert bar_y1 < bar_y2
|
||||
|
||||
# Construction of the cross
|
||||
cross_dx = dx * 0.7 / math.sqrt(2)
|
||||
cross_dy = dy * 0.2
|
||||
|
||||
cross = (
|
||||
Cq.Sketch()
|
||||
.rect(cross_dx, cross_dy)
|
||||
.rect(cross_dy, cross_dx, mode='a')
|
||||
.moved(Cq.Location.from2d(dx * 0.5, 0, 45))
|
||||
)
|
||||
return (
|
||||
Cq.Sketch()
|
||||
# eyes
|
||||
.push([
|
||||
(eye_x, (eye_y1 + eye_y2)/2),
|
||||
(eye_x, -(eye_y1 + eye_y2)/2),
|
||||
])
|
||||
.rect(eye_width, eye_width, mode='a')
|
||||
# middle bar
|
||||
.push([(0,0)])
|
||||
.polygon([
|
||||
(bar_x1, bar_y1),
|
||||
(bar_x0, bar_y1),
|
||||
(bar_x0, bar_y2),
|
||||
(bar_x3, bar_y2),
|
||||
(bar_x3, bar_y1),
|
||||
(bar_x2, bar_y1),
|
||||
|
||||
(bar_x2, -bar_y1),
|
||||
(bar_x3, -bar_y1),
|
||||
(bar_x3, -bar_y2),
|
||||
(bar_x0, -bar_y2),
|
||||
(bar_x0, -bar_y1),
|
||||
(bar_x1, -bar_y1),
|
||||
], mode='a')
|
||||
# cross
|
||||
.boolean(cross, mode='a')
|
||||
|
||||
#.push([(0,0)])
|
||||
#.rect(10, 10)
|
||||
|
||||
.moved(loc)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@target(name="surface")
|
||||
def top_profile(self) -> Cq.Sketch:
|
||||
|
@ -24,6 +289,16 @@ class Rod(Model):
|
|||
(self.length - self.length_tip, -self.width/2),
|
||||
])
|
||||
)
|
||||
|
||||
sketch = (
|
||||
sketch
|
||||
.boolean(self._window_tip(), mode='s')
|
||||
.boolean(self._window_eye(True), mode='s')
|
||||
.boolean(self._window_eye(False), mode='s')
|
||||
.boolean(self._window_bar(), mode='s')
|
||||
.boolean(self._window_tsumi(), mode='s')
|
||||
.boolean(self._window_footer(), mode='s')
|
||||
)
|
||||
return sketch
|
||||
|
||||
@assembly()
|
||||
|
|
15
nhf/utils.py
15
nhf/utils.py
|
@ -1,13 +1,11 @@
|
|||
"""
|
||||
Utility functions for cadquery objects
|
||||
"""
|
||||
import functools
|
||||
import math
|
||||
from typing import Optional
|
||||
import functools, math
|
||||
from typing import Optional, Union, Tuple, cast
|
||||
import cadquery as Cq
|
||||
from cadquery.occ_impl.solver import ConstraintSpec
|
||||
from nhf import Role
|
||||
from typing import Union, Tuple, cast
|
||||
from nhf.materials import KEY_ITEM, KEY_MATERIAL
|
||||
|
||||
# Bug fixes
|
||||
|
@ -100,10 +98,17 @@ def flip_y(self: Cq.Location) -> Cq.Location:
|
|||
return Cq.Location.from2d(x, -y, -a)
|
||||
Cq.Location.flip_y = flip_y
|
||||
|
||||
def boolean(self: Cq.Sketch, obj, **kwargs) -> Cq.Sketch:
|
||||
def boolean(
|
||||
self: Cq.Sketch,
|
||||
obj: Union[Cq.Face, Cq.Sketch, Cq.Compound],
|
||||
**kwargs) -> Cq.Sketch:
|
||||
"""
|
||||
Performs Boolean operation between a sketch and a sketch-like object
|
||||
"""
|
||||
return (
|
||||
self
|
||||
.reset()
|
||||
# Has to be 0, 0. Translation doesn't work.
|
||||
.push([(0, 0)])
|
||||
.each(lambda _: obj, **kwargs)
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue