diff --git a/nhf/materials.py b/nhf/materials.py index 863ee3e..6108dc1 100644 --- a/nhf/materials.py +++ b/nhf/materials.py @@ -15,6 +15,7 @@ class Role(Enum): # Parent and child components in a load bearing joint PARENT = _color('blue4', 0.6) + CASING = _color('dodgerblue3', 0.6) CHILD = _color('darkorange2', 0.6) DAMPING = _color('springgreen', 0.5) STRUCTURE = _color('gray', 0.4) diff --git a/nhf/touhou/houjuu_nue/parts.py b/nhf/touhou/houjuu_nue/parts.py index a3a5bfb..bdf458e 100644 --- a/nhf/touhou/houjuu_nue/parts.py +++ b/nhf/touhou/houjuu_nue/parts.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Optional, Tuple import cadquery as Cq from nhf import Role from nhf.build import Model, target @@ -25,8 +26,12 @@ class DiskJoint(Model): spring_thickness: float = 1.3 spring_height: float = 6.5 spring_tail_length: float = 45.0 - spring_angle: float = 90.0 - spring_angle_shift: float = 0 + + # Spring angle at 0 degrees of movement + spring_angle: float = 30.0 + # Angle at which the spring exerts no torque + spring_angle_neutral: float = 90.0 + spring_angle_shift: float = 30 wall_inset: float = 2.0 # Angular span of movement @@ -55,15 +60,22 @@ class DiskJoint(Model): ) @property - def total_thickness(self): + def neutral_movement_angle(self) -> Optional[float]: + a = self.spring_angle_neutral - self.spring_angle + if 0 <= a and a <= self.movement_angle: + return a + return None + + @property + def total_thickness(self) -> float: return self.housing_thickness * 2 + self.disk_thickness @property - def opening_span(self): + def opening_span(self) -> float: return self.movement_angle + self.tongue_span @property - def housing_upper_carve_offset(self): + def housing_upper_carve_offset(self) -> float: return self.housing_thickness + self.disk_thickness - self.spring_height @property @@ -147,8 +159,7 @@ class DiskJoint(Model): result = result.cut( self .wall() - .mirror("XY") - .located(Cq.Location((0, 0, self.housing_thickness + self.disk_thickness))) + .located(Cq.Location((0, 0, self.disk_thickness - self.wall_inset))) #.rotate((0, 0, 0), (1, 0, 0), 180) #.located(Cq.Location((0, 0, self.disk_thickness + self.housing_thickness))) ) @@ -165,7 +176,7 @@ class DiskJoint(Model): width=self.spring_thickness, height=self.housing_thickness ).located(Cq.Location((0, self.radius_spring_internal, 0)))) - ).rotate((0, 0, 0), (0, 0, 1), self.spring_angle - self.spring_angle_shift) + ).rotate((0, 0, 0), (0, 0, 1), 180 + self.spring_angle - self.spring_angle_shift) result = ( Cq.Workplane('XY') .cylinder( @@ -192,7 +203,8 @@ class DiskJoint(Model): result = result.union(tube) wall = ( self.wall() - .rotate((0, 0, 0), (1, 0, 0), 180) + .rotate((0, 0, 0), (0, 0, 1), self.tongue_span) + .mirror("XY") .located(Cq.Location((0, 0, self.disk_thickness + self.housing_thickness + self.wall_inset))) ) result = ( @@ -202,18 +214,47 @@ class DiskJoint(Model): ) return result + def add_constraints(self, + assembly: Cq.Assembly, + housing_lower: str, + housing_upper: str, + disk: str, + angle: Tuple[float, float, float] = (0, 0, 0), + ) -> Cq.Assembly: + """ + The angle supplied must be perpendicular to the disk normal. + """ + ( + assembly + .constrain(f"{disk}?mate_bot", f"{housing_lower}?mate", "Plane") + .constrain(f"{disk}?mate_top", f"{housing_upper}?mate", "Plane") + .constrain(f"{housing_lower}?dir", f"{housing_upper}?dir", "Axis") + .constrain(f"{disk}?dir", "FixedRotation", angle) + ) - def assembly(self) -> Cq.Assembly: + + def assembly(self, angle: Optional[float] = 0) -> Cq.Assembly: + if angle is None: + angle = self.movement_angle + if angle is None: + angle = 0 + else: + assert 0 <= angle <= self.movement_angle result = ( Cq.Assembly() .add(self.disk(), name="disk", color=Role.CHILD.color) .add(self.housing_lower(), name="housing_lower", color=Role.PARENT.color) - .add(self.housing_upper(), name="housing_upper", color=Role.PARENT.color) - .constrain("disk?mate_bot", "housing_lower?mate", "Plane") - .constrain("disk?mate_top", "housing_upper?mate", "Plane") - .solve() + .add(self.housing_upper(), name="housing_upper", color=Role.CASING.color) + .constrain("housing_lower", "Fixed") ) - return result + self.add_constraints( + result, + housing_lower="housing_lower", + housing_upper="housing_upper", + disk="disk", + angle=(0, 0, angle), + ) + return result.solve() if __name__ == '__main__': p = DiskJoint() diff --git a/nhf/touhou/houjuu_nue/test.py b/nhf/touhou/houjuu_nue/test.py index 2f610d5..c5a9d27 100644 --- a/nhf/touhou/houjuu_nue/test.py +++ b/nhf/touhou/houjuu_nue/test.py @@ -1,8 +1,24 @@ import unittest import cadquery as Cq import nhf.touhou.houjuu_nue as M +import nhf.touhou.houjuu_nue.parts as MP from nhf.checks import pairwise_intersection +class TestDiskJoint(unittest.TestCase): + + def test_collision_0(self): + j = MP.DiskJoint() + assembly = j.assembly(angle=0) + self.assertEqual(pairwise_intersection(assembly), []) + def test_collision_mid(self): + j = MP.DiskJoint() + assembly = j.assembly(angle=j.movement_angle / 2) + self.assertEqual(pairwise_intersection(assembly), []) + def test_collision_max(self): + j = MP.DiskJoint() + assembly = j.assembly(angle=j.movement_angle) + self.assertEqual(pairwise_intersection(assembly), []) + class Test(unittest.TestCase): def test_hs_joint_parent(self):