fix: Torsion joint directrix and collision problem
This commit is contained in:
parent
9e7369c6f8
commit
58028579a9
|
@ -3,6 +3,9 @@ import math
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
import nhf.parts.springs as springs
|
import nhf.parts.springs as springs
|
||||||
from nhf import Role
|
from nhf import Role
|
||||||
|
import nhf.utils
|
||||||
|
|
||||||
|
TOL = 1e-6
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class HirthJoint:
|
class HirthJoint:
|
||||||
|
@ -232,17 +235,31 @@ class TorsionJoint:
|
||||||
"""
|
"""
|
||||||
This jonit consists of a rider puck on a track puck. IT is best suited if
|
This jonit consists of a rider puck on a track puck. IT is best suited if
|
||||||
the radius has to be small and vertical space is abundant.
|
the radius has to be small and vertical space is abundant.
|
||||||
|
|
||||||
|
The rider part consists of:
|
||||||
|
1. A cylinderical base
|
||||||
|
2. A annular extrusion with the same radius as the base, but with slots
|
||||||
|
carved in
|
||||||
|
3. An annular rider
|
||||||
|
|
||||||
|
The track part consists of:
|
||||||
|
1. A cylindrical base
|
||||||
|
2. A slotted annular extrusion where the slot allows the spring to rest
|
||||||
|
3. An outer and an inner annuli which forms a track the rider can move on
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Radius limit for rotating components
|
# Radius limit for rotating components
|
||||||
radius: float = 40
|
radius: float = 40
|
||||||
disk_height: float = 10
|
track_disk_height: float = 10
|
||||||
|
rider_disk_height: float = 8
|
||||||
|
|
||||||
radius_spring: float = 15
|
radius_spring: float = 15
|
||||||
radius_axle: float = 6
|
radius_axle: float = 6
|
||||||
|
|
||||||
# Offset of the spring hole w.r.t. surface
|
# If true, cover the spring hole. May make it difficult to insert the spring
|
||||||
spring_hole_depth: float = 4
|
# considering the stiffness of torsion spring steel.
|
||||||
|
spring_hole_cover_track: bool = False
|
||||||
|
spring_hole_cover_rider: bool = False
|
||||||
|
|
||||||
# Also used for the height of the hole for the spring
|
# Also used for the height of the hole for the spring
|
||||||
spring_thickness: float = 2
|
spring_thickness: float = 2
|
||||||
|
@ -253,13 +270,17 @@ class TorsionJoint:
|
||||||
groove_radius_outer: float = 35
|
groove_radius_outer: float = 35
|
||||||
groove_radius_inner: float = 20
|
groove_radius_inner: float = 20
|
||||||
groove_depth: float = 5
|
groove_depth: float = 5
|
||||||
rider_gap: float = 2
|
rider_gap: float = 1
|
||||||
n_slots: float = 8
|
rider_n_slots: float = 4
|
||||||
|
|
||||||
|
# Degrees of the first and last rider slots
|
||||||
|
rider_slot_begin: float = 0
|
||||||
|
rider_slot_span: float = 90
|
||||||
|
|
||||||
right_handed: bool = False
|
right_handed: bool = False
|
||||||
|
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
assert self.disk_height > self.spring_hole_depth
|
|
||||||
assert self.radius > self.groove_radius_outer
|
assert self.radius > self.groove_radius_outer
|
||||||
assert self.groove_radius_outer > self.groove_radius_inner
|
assert self.groove_radius_outer > self.groove_radius_inner
|
||||||
assert self.groove_radius_inner > self.radius_spring
|
assert self.groove_radius_inner > self.radius_spring
|
||||||
|
@ -268,7 +289,7 @@ class TorsionJoint:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_height(self):
|
def total_height(self):
|
||||||
return 2 * self.disk_height + self.spring_height
|
return self.track_disk_height + self.rider_disk_height + self.spring_height
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _radius_spring_internal(self):
|
def _radius_spring_internal(self):
|
||||||
|
@ -295,6 +316,10 @@ class TorsionJoint:
|
||||||
r2 = -r2
|
r2 = -r2
|
||||||
# This is (0, r2) and (l, r2) transformed by rotation matrix
|
# This is (0, r2) and (l, r2) transformed by rotation matrix
|
||||||
# [[c, s], [-s, c]]
|
# [[c, s], [-s, c]]
|
||||||
|
return [
|
||||||
|
(0, 0, height),
|
||||||
|
(c, s, height)
|
||||||
|
]
|
||||||
return [
|
return [
|
||||||
(s * r2, -s * l + c * r2, height),
|
(s * r2, -s * l + c * r2, height),
|
||||||
(c * l + s * r2, -s * l + c * r2, height),
|
(c * l + s * r2, -s * l + c * r2, height),
|
||||||
|
@ -320,14 +345,24 @@ class TorsionJoint:
|
||||||
spring_hole_profile = (
|
spring_hole_profile = (
|
||||||
Cq.Sketch()
|
Cq.Sketch()
|
||||||
.circle(self.radius)
|
.circle(self.radius)
|
||||||
.polygon(self._slot_polygon(flip=False), mode='s')
|
|
||||||
.circle(self.radius_spring, mode='s')
|
.circle(self.radius_spring, mode='s')
|
||||||
)
|
)
|
||||||
|
slot_height = self.spring_thickness
|
||||||
|
if not self.spring_hole_cover_track:
|
||||||
|
slot_height += self.groove_depth
|
||||||
|
slot = (
|
||||||
|
Cq.Workplane('XY')
|
||||||
|
.sketch()
|
||||||
|
.polygon(self._slot_polygon(flip=False))
|
||||||
|
.finalize()
|
||||||
|
.extrude(slot_height)
|
||||||
|
.val()
|
||||||
|
)
|
||||||
result = (
|
result = (
|
||||||
Cq.Workplane('XY')
|
Cq.Workplane('XY')
|
||||||
.cylinder(
|
.cylinder(
|
||||||
radius=self.radius,
|
radius=self.radius,
|
||||||
height=self.disk_height,
|
height=self.track_disk_height,
|
||||||
centered=(True, True, False))
|
centered=(True, True, False))
|
||||||
.faces('>Z')
|
.faces('>Z')
|
||||||
.tag("spring")
|
.tag("spring")
|
||||||
|
@ -340,9 +375,10 @@ class TorsionJoint:
|
||||||
.extrude(self.groove_depth)
|
.extrude(self.groove_depth)
|
||||||
.faces('>Z')
|
.faces('>Z')
|
||||||
.hole(self.radius_axle * 2)
|
.hole(self.radius_axle * 2)
|
||||||
|
.cut(slot.moved(Cq.Location((0, 0, self.track_disk_height))))
|
||||||
)
|
)
|
||||||
# Insert directrix`
|
# Insert directrix`
|
||||||
result.polyline(self._directrix(self.disk_height),
|
result.polyline(self._directrix(self.track_disk_height),
|
||||||
forConstruction=True).tag("directrix")
|
forConstruction=True).tag("directrix")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -357,9 +393,9 @@ class TorsionJoint:
|
||||||
.circle(self.radius_spring, mode='s')
|
.circle(self.radius_spring, mode='s')
|
||||||
.parray(
|
.parray(
|
||||||
r=0,
|
r=0,
|
||||||
a1=0,
|
a1=self.rider_slot_begin,
|
||||||
da=360,
|
da=self.rider_slot_span,
|
||||||
n=self.n_slots)
|
n=self.rider_n_slots)
|
||||||
.each(slot, mode='s')
|
.each(slot, mode='s')
|
||||||
#.circle(self._radius_wall, mode='a')
|
#.circle(self._radius_wall, mode='a')
|
||||||
)
|
)
|
||||||
|
@ -367,43 +403,58 @@ class TorsionJoint:
|
||||||
Cq.Sketch()
|
Cq.Sketch()
|
||||||
.circle(self.groove_radius_outer, mode='a')
|
.circle(self.groove_radius_outer, mode='a')
|
||||||
.circle(self.groove_radius_inner, mode='s')
|
.circle(self.groove_radius_inner, mode='s')
|
||||||
#.circle(self._radius_wall, mode='a')
|
|
||||||
.parray(
|
|
||||||
r=0,
|
|
||||||
a1=0,
|
|
||||||
da=360,
|
|
||||||
n=self.n_slots)
|
|
||||||
.each(slot, mode='s')
|
|
||||||
)
|
)
|
||||||
middle_height = self.spring_height - self.groove_depth - self.rider_gap
|
if not self.spring_hole_cover_rider:
|
||||||
|
contact_profile = (
|
||||||
|
contact_profile
|
||||||
|
.parray(
|
||||||
|
r=0,
|
||||||
|
a1=self.rider_slot_begin,
|
||||||
|
da=self.rider_slot_span,
|
||||||
|
n=self.rider_n_slots)
|
||||||
|
.each(slot, mode='s')
|
||||||
|
.reset()
|
||||||
|
)
|
||||||
|
#.circle(self._radius_wall, mode='a')
|
||||||
|
middle_height = self.spring_height - self.groove_depth - self.rider_gap - self.spring_thickness
|
||||||
result = (
|
result = (
|
||||||
Cq.Workplane('XY')
|
Cq.Workplane('XY')
|
||||||
.cylinder(
|
.cylinder(
|
||||||
radius=self.radius,
|
radius=self.radius,
|
||||||
height=self.disk_height,
|
height=self.rider_disk_height,
|
||||||
centered=(True, True, False))
|
centered=(True, True, False))
|
||||||
.faces('>Z')
|
.faces('>Z')
|
||||||
.tag("spring")
|
.tag("spring")
|
||||||
|
.workplane()
|
||||||
.placeSketch(wall_profile)
|
.placeSketch(wall_profile)
|
||||||
.extrude(middle_height)
|
.extrude(middle_height)
|
||||||
|
.faces(tag="spring")
|
||||||
|
.workplane()
|
||||||
# The top face might not be in one piece.
|
# The top face might not be in one piece.
|
||||||
#.faces('>Z')
|
|
||||||
.workplane(offset=middle_height)
|
.workplane(offset=middle_height)
|
||||||
.placeSketch(contact_profile)
|
.placeSketch(contact_profile)
|
||||||
.extrude(self.groove_depth + self.rider_gap)
|
.extrude(self.groove_depth + self.rider_gap)
|
||||||
.faces(tag="spring")
|
.faces(tag="spring")
|
||||||
|
.workplane()
|
||||||
.circle(self._radius_spring_internal)
|
.circle(self._radius_spring_internal)
|
||||||
.extrude(self.spring_height)
|
.extrude(self.spring_height)
|
||||||
.faces('>Z')
|
#.faces(tag="spring")
|
||||||
|
#.workplane()
|
||||||
.hole(self.radius_axle * 2)
|
.hole(self.radius_axle * 2)
|
||||||
)
|
)
|
||||||
for i in range(self.n_slots):
|
theta_begin = math.radians(self.rider_slot_begin) + math.pi
|
||||||
theta = 2 * math.pi * i / self.n_slots
|
theta_span = math.radians(self.rider_slot_span)
|
||||||
result.polyline(self._directrix(self.disk_height, theta),
|
if abs(math.remainder(self.rider_slot_span, 360)) < TOL:
|
||||||
|
theta_step = theta_span / self.rider_n_slots
|
||||||
|
else:
|
||||||
|
theta_step = theta_span / (self.rider_n_slots - 1)
|
||||||
|
for i in range(self.rider_n_slots):
|
||||||
|
theta = theta_begin - i * theta_step
|
||||||
|
result.polyline(self._directrix(self.rider_disk_height, theta),
|
||||||
forConstruction=True).tag(f"directrix{i}")
|
forConstruction=True).tag(f"directrix{i}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def rider_track_assembly(self):
|
def rider_track_assembly(self, directrix=0):
|
||||||
rider = self.rider()
|
rider = self.rider()
|
||||||
track = self.track()
|
track = self.track()
|
||||||
spring = self.spring()
|
spring = self.spring()
|
||||||
|
@ -412,10 +463,10 @@ class TorsionJoint:
|
||||||
.add(spring, name="spring", color=Role.DAMPING.color)
|
.add(spring, name="spring", color=Role.DAMPING.color)
|
||||||
.add(track, name="track", color=Role.PARENT.color)
|
.add(track, name="track", color=Role.PARENT.color)
|
||||||
.constrain("track?spring", "spring?top", "Plane")
|
.constrain("track?spring", "spring?top", "Plane")
|
||||||
|
.constrain("track?directrix", "spring?directrix_bot", "Axis")
|
||||||
.add(rider, name="rider", color=Role.CHILD.color)
|
.add(rider, name="rider", color=Role.CHILD.color)
|
||||||
.constrain("rider?spring", "spring?bot", "Plane")
|
.constrain("rider?spring", "spring?bot", "Plane")
|
||||||
.constrain("track?directrix", "spring?directrix_bot", "Axis")
|
.constrain(f"rider?directrix{directrix}", "spring?directrix_top", "Axis")
|
||||||
.constrain("rider?directrix0", "spring?directrix_top", "Axis")
|
|
||||||
.solve()
|
.solve()
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from nhf.checks import binary_intersection
|
from nhf.checks import binary_intersection, pairwise_intersection
|
||||||
from nhf.parts import joints, handle, metric_threads
|
from nhf.parts import joints, handle, metric_threads
|
||||||
|
|
||||||
class TestJoints(unittest.TestCase):
|
class TestJoints(unittest.TestCase):
|
||||||
|
@ -22,12 +22,25 @@ class TestJoints(unittest.TestCase):
|
||||||
"Hirth joint assembly must not have intersection")
|
"Hirth joint assembly must not have intersection")
|
||||||
def test_joints_comma_assembly(self):
|
def test_joints_comma_assembly(self):
|
||||||
joints.comma_assembly()
|
joints.comma_assembly()
|
||||||
|
|
||||||
def test_torsion_joint(self):
|
def test_torsion_joint(self):
|
||||||
j = joints.TorsionJoint()
|
j = joints.TorsionJoint()
|
||||||
assembly = j.rider_track_assembly()
|
assembly = j.rider_track_assembly()
|
||||||
bbox = assembly.toCompound().BoundingBox()
|
bbox = assembly.toCompound().BoundingBox()
|
||||||
self.assertAlmostEqual(bbox.zlen, j.total_height)
|
self.assertAlmostEqual(bbox.zlen, j.total_height)
|
||||||
|
|
||||||
|
def torsion_joint_collision_case(self, joint: joints.TorsionJoint, slot: int):
|
||||||
|
assembly = joint.rider_track_assembly(slot)
|
||||||
|
bbox = assembly.toCompound().BoundingBox()
|
||||||
|
self.assertAlmostEqual(bbox.zlen, joint.total_height)
|
||||||
|
self.assertEqual(pairwise_intersection(assembly), [])
|
||||||
|
|
||||||
|
def test_torsion_joint_collision(self):
|
||||||
|
j = joints.TorsionJoint()
|
||||||
|
for slot in range(j.rider_n_slots):
|
||||||
|
with self.subTest(slot=slot):
|
||||||
|
self.torsion_joint_collision_case(j, slot)
|
||||||
|
|
||||||
|
|
||||||
class TestHandle(unittest.TestCase):
|
class TestHandle(unittest.TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue