fix: Collision problem with Hirth joints
This commit is contained in:
parent
914bc23582
commit
87e99ac4ce
|
@ -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')
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue