cosplay: Touhou/Houjuu Nue #4

Open
aniva wants to merge 189 commits from touhou/houjuu-nue into main
2 changed files with 95 additions and 31 deletions
Showing only changes of commit 58028579a9 - Show all commits

View File

@ -3,6 +3,9 @@ import math
import cadquery as Cq
import nhf.parts.springs as springs
from nhf import Role
import nhf.utils
TOL = 1e-6
@dataclass
class HirthJoint:
@ -232,17 +235,31 @@ class TorsionJoint:
"""
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 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: float = 40
disk_height: float = 10
track_disk_height: float = 10
rider_disk_height: float = 8
radius_spring: float = 15
radius_axle: float = 6
# Offset of the spring hole w.r.t. surface
spring_hole_depth: float = 4
# If true, cover the spring hole. May make it difficult to insert the spring
# 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
spring_thickness: float = 2
@ -253,13 +270,17 @@ class TorsionJoint:
groove_radius_outer: float = 35
groove_radius_inner: float = 20
groove_depth: float = 5
rider_gap: float = 2
n_slots: float = 8
rider_gap: float = 1
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
def __post_init__(self):
assert self.disk_height > self.spring_hole_depth
assert self.radius > self.groove_radius_outer
assert self.groove_radius_outer > self.groove_radius_inner
assert self.groove_radius_inner > self.radius_spring
@ -268,7 +289,7 @@ class TorsionJoint:
@property
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
def _radius_spring_internal(self):
@ -295,6 +316,10 @@ class TorsionJoint:
r2 = -r2
# This is (0, r2) and (l, r2) transformed by rotation matrix
# [[c, s], [-s, c]]
return [
(0, 0, height),
(c, s, height)
]
return [
(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 = (
Cq.Sketch()
.circle(self.radius)
.polygon(self._slot_polygon(flip=False), 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 = (
Cq.Workplane('XY')
.cylinder(
radius=self.radius,
height=self.disk_height,
height=self.track_disk_height,
centered=(True, True, False))
.faces('>Z')
.tag("spring")
@ -340,9 +375,10 @@ class TorsionJoint:
.extrude(self.groove_depth)
.faces('>Z')
.hole(self.radius_axle * 2)
.cut(slot.moved(Cq.Location((0, 0, self.track_disk_height))))
)
# Insert directrix`
result.polyline(self._directrix(self.disk_height),
result.polyline(self._directrix(self.track_disk_height),
forConstruction=True).tag("directrix")
return result
@ -357,9 +393,9 @@ class TorsionJoint:
.circle(self.radius_spring, mode='s')
.parray(
r=0,
a1=0,
da=360,
n=self.n_slots)
a1=self.rider_slot_begin,
da=self.rider_slot_span,
n=self.rider_n_slots)
.each(slot, mode='s')
#.circle(self._radius_wall, mode='a')
)
@ -367,43 +403,58 @@ class TorsionJoint:
Cq.Sketch()
.circle(self.groove_radius_outer, mode='a')
.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(
r=0,
a1=0,
da=360,
n=self.n_slots)
a1=self.rider_slot_begin,
da=self.rider_slot_span,
n=self.rider_n_slots)
.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 = (
Cq.Workplane('XY')
.cylinder(
radius=self.radius,
height=self.disk_height,
height=self.rider_disk_height,
centered=(True, True, False))
.faces('>Z')
.tag("spring")
.workplane()
.placeSketch(wall_profile)
.extrude(middle_height)
.faces(tag="spring")
.workplane()
# The top face might not be in one piece.
#.faces('>Z')
.workplane(offset=middle_height)
.placeSketch(contact_profile)
.extrude(self.groove_depth + self.rider_gap)
.faces(tag="spring")
.workplane()
.circle(self._radius_spring_internal)
.extrude(self.spring_height)
.faces('>Z')
#.faces(tag="spring")
#.workplane()
.hole(self.radius_axle * 2)
)
for i in range(self.n_slots):
theta = 2 * math.pi * i / self.n_slots
result.polyline(self._directrix(self.disk_height, theta),
theta_begin = math.radians(self.rider_slot_begin) + math.pi
theta_span = math.radians(self.rider_slot_span)
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}")
return result
def rider_track_assembly(self):
def rider_track_assembly(self, directrix=0):
rider = self.rider()
track = self.track()
spring = self.spring()
@ -412,10 +463,10 @@ class TorsionJoint:
.add(spring, name="spring", color=Role.DAMPING.color)
.add(track, name="track", color=Role.PARENT.color)
.constrain("track?spring", "spring?top", "Plane")
.constrain("track?directrix", "spring?directrix_bot", "Axis")
.add(rider, name="rider", color=Role.CHILD.color)
.constrain("rider?spring", "spring?bot", "Plane")
.constrain("track?directrix", "spring?directrix_bot", "Axis")
.constrain("rider?directrix0", "spring?directrix_top", "Axis")
.constrain(f"rider?directrix{directrix}", "spring?directrix_top", "Axis")
.solve()
)
return result

View File

@ -1,6 +1,6 @@
import unittest
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
class TestJoints(unittest.TestCase):
@ -22,12 +22,25 @@ class TestJoints(unittest.TestCase):
"Hirth joint assembly must not have intersection")
def test_joints_comma_assembly(self):
joints.comma_assembly()
def test_torsion_joint(self):
j = joints.TorsionJoint()
assembly = j.rider_track_assembly()
bbox = assembly.toCompound().BoundingBox()
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):