cosplay: Touhou/Houjuu Nue #1

Closed
aniva wants to merge 37 commits from touhou/houjuu-nue into main
2 changed files with 51 additions and 20 deletions
Showing only changes of commit 87e99ac4ce - Show all commits

View File

@ -23,50 +23,69 @@ def hirth_joint(radius=60,
Creates a cylindrical Hirth Joint Creates a cylindrical Hirth Joint
is_mated: If set to true, rotate the teeth so they line up at 0 degrees. is_mated: If set to true, rotate the teeth so they line up at 0 degrees.
FIXME: The curves don't mate perfectly. See if non-planar lofts can solve
this issue.
""" """
# ensures secant doesn't blow up # ensures tangent doesn't blow up
assert n_tooth >= 5 assert n_tooth >= 5
assert radius > radius_inner assert radius > radius_inner
assert tooth_height >= tooth_height_inner
# angle of half of a single tooth # angle of half of a single tooth
theta = math.pi / n_tooth theta = math.pi / n_tooth
# Generate a tooth by lofting between two curves c, s, t = math.cos(theta), math.sin(theta), math.tan(theta)
span = radius * t
radius_proj = radius / c
span_inner = radius_inner * s
# 2 * raise + (inner tooth height) = (tooth height)
inner_raise = (tooth_height - tooth_height_inner) / 2 inner_raise = (tooth_height - tooth_height_inner) / 2
# Outer tooth triangle spans a curve of length `2 pi r / n_tooth`. This # Outer tooth triangle spans 2*theta radians. This profile is the radial
# creates the side profile (looking radially inwards) of each of the # profile projected onto a plane `radius` away from the centre of the
# triangles. # cylinder. The y coordinates on the edge must drop to compensate.
# The drop is equal to, via similar triangles
drop = inner_raise * (radius_proj - radius) / (radius - radius_inner)
outer = [ outer = [
(radius * math.tan(theta), 0), (span, -tol - drop),
(radius * math.tan(theta) - tol, 0), (span, -drop),
(0, tooth_height), (0, tooth_height),
(-radius * math.tan(theta) + tol, 0), (-span, -drop),
(-radius * math.tan(theta), 0), (-span, -tol - drop),
] ]
adj = radius_inner * c
# In the case of the inner triangle, it is projected onto a plane `adj` away
# from the centre. The apex must extrapolate
# Via similar triangles
#
# (inner_raise + tooth_height_inner) -
# (tooth_height - inner_raise - tooth_height_inner) * ((radius_inner - adj) / (radius - radius_inner))
apex = (inner_raise + tooth_height_inner) - \
inner_raise * (radius_inner - adj) / (radius - radius_inner)
inner = [ inner = [
(radius_inner * math.sin(theta), 0), (span_inner, -tol),
(radius_inner * math.sin(theta), inner_raise), (span_inner, inner_raise),
(0, inner_raise + tooth_height_inner), (0, apex),
(-radius_inner * math.sin(theta), inner_raise), (-span_inner, inner_raise),
(-radius_inner * math.sin(theta), 0), (-span_inner, -tol),
] ]
tooth = ( tooth = (
Cq.Workplane('YZ') Cq.Workplane('YZ')
.polyline(inner) .polyline(inner)
.close() .close()
.workplane(offset=radius - radius_inner) .workplane(offset=radius - adj)
.polyline(outer) .polyline(outer)
.close() .close()
.loft(ruled=True, combine=True) .loft(ruled=False, combine=True)
.val() .val()
) )
tooth_centre_radius = radius_inner * math.cos(theta)
angle_offset = hirth_tooth_angle(n_tooth) / 2 if is_mated else 0 angle_offset = hirth_tooth_angle(n_tooth) / 2 if is_mated else 0
teeth = ( teeth = (
Cq.Workplane('XY') Cq.Workplane('XY')
.polarArray( .polarArray(
radius=tooth_centre_radius, radius=adj,
startAngle=angle_offset, startAngle=angle_offset,
angle=360, angle=360,
count=n_tooth) count=n_tooth)
@ -75,6 +94,14 @@ def hirth_joint(radius=60,
height=base_height + tooth_height, height=base_height + tooth_height,
radius=radius, radius=radius,
)) ))
.intersect(Cq.Solid.makeCylinder(
height=base_height + tooth_height,
radius=radius,
))
.cut(Cq.Solid.makeCylinder(
height=base_height + tooth_height,
radius=radius_inner,
))
) )
base = ( base = (
Cq.Workplane('XY') Cq.Workplane('XY')

View File

@ -3,6 +3,7 @@ import cadquery as Cq
import nhf.joints import nhf.joints
import nhf.handle import nhf.handle
import nhf.metric_threads as NMt import nhf.metric_threads as NMt
from nhf.checks import binary_intersection
class TestJoints(unittest.TestCase): class TestJoints(unittest.TestCase):
@ -12,7 +13,10 @@ class TestJoints(unittest.TestCase):
j.val().solids(), Cq.Solid, j.val().solids(), Cq.Solid,
msg="Hirth joint must be in one piece") msg="Hirth joint must be in one piece")
def test_joints_hirth_assembly(self): def test_joints_hirth_assembly(self):
nhf.joints.hirth_assembly() assembly = nhf.joints.hirth_assembly()
isect = binary_intersection(assembly)
self.assertLess(isect.Volume(), 1e-6,
"Hirth joint assembly must not have intersection")
def test_joints_comma_assembly(self): def test_joints_comma_assembly(self):
nhf.joints.comma_assembly() nhf.joints.comma_assembly()
def test_torsion_joint(self): def test_torsion_joint(self):