fix: Torsion joint directrix and collision problem

This commit is contained in:
Leni Aniva 2024-07-06 23:43:55 -07:00
parent 9e7369c6f8
commit 58028579a9
Signed by: aniva
GPG Key ID: 4D9B1C8D10EA4C50
2 changed files with 95 additions and 31 deletions

View File

@ -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') )
if not self.spring_hole_cover_rider:
contact_profile = (
contact_profile
.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')
.reset()
) )
middle_height = self.spring_height - self.groove_depth - self.rider_gap #.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

View File

@ -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):