cosplay: Touhou/Houjuu Nue #4

Open
aniva wants to merge 189 commits from touhou/houjuu-nue into main
3 changed files with 73 additions and 15 deletions
Showing only changes of commit 2aeeaae061 - Show all commits

View File

@ -15,6 +15,7 @@ class Role(Enum):
# Parent and child components in a load bearing joint # Parent and child components in a load bearing joint
PARENT = _color('blue4', 0.6) PARENT = _color('blue4', 0.6)
CASING = _color('dodgerblue3', 0.6)
CHILD = _color('darkorange2', 0.6) CHILD = _color('darkorange2', 0.6)
DAMPING = _color('springgreen', 0.5) DAMPING = _color('springgreen', 0.5)
STRUCTURE = _color('gray', 0.4) STRUCTURE = _color('gray', 0.4)

View File

@ -1,4 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional, Tuple
import cadquery as Cq import cadquery as Cq
from nhf import Role from nhf import Role
from nhf.build import Model, target from nhf.build import Model, target
@ -25,8 +26,12 @@ class DiskJoint(Model):
spring_thickness: float = 1.3 spring_thickness: float = 1.3
spring_height: float = 6.5 spring_height: float = 6.5
spring_tail_length: float = 45.0 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 wall_inset: float = 2.0
# Angular span of movement # Angular span of movement
@ -55,15 +60,22 @@ class DiskJoint(Model):
) )
@property @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 return self.housing_thickness * 2 + self.disk_thickness
@property @property
def opening_span(self): def opening_span(self) -> float:
return self.movement_angle + self.tongue_span return self.movement_angle + self.tongue_span
@property @property
def housing_upper_carve_offset(self): def housing_upper_carve_offset(self) -> float:
return self.housing_thickness + self.disk_thickness - self.spring_height return self.housing_thickness + self.disk_thickness - self.spring_height
@property @property
@ -147,8 +159,7 @@ class DiskJoint(Model):
result = result.cut( result = result.cut(
self self
.wall() .wall()
.mirror("XY") .located(Cq.Location((0, 0, self.disk_thickness - self.wall_inset)))
.located(Cq.Location((0, 0, self.housing_thickness + self.disk_thickness)))
#.rotate((0, 0, 0), (1, 0, 0), 180) #.rotate((0, 0, 0), (1, 0, 0), 180)
#.located(Cq.Location((0, 0, self.disk_thickness + self.housing_thickness))) #.located(Cq.Location((0, 0, self.disk_thickness + self.housing_thickness)))
) )
@ -165,7 +176,7 @@ class DiskJoint(Model):
width=self.spring_thickness, width=self.spring_thickness,
height=self.housing_thickness height=self.housing_thickness
).located(Cq.Location((0, self.radius_spring_internal, 0)))) ).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 = ( result = (
Cq.Workplane('XY') Cq.Workplane('XY')
.cylinder( .cylinder(
@ -192,7 +203,8 @@ class DiskJoint(Model):
result = result.union(tube) result = result.union(tube)
wall = ( wall = (
self.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))) .located(Cq.Location((0, 0, self.disk_thickness + self.housing_thickness + self.wall_inset)))
) )
result = ( result = (
@ -202,18 +214,47 @@ class DiskJoint(Model):
) )
return result 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 = ( result = (
Cq.Assembly() Cq.Assembly()
.add(self.disk(), name="disk", color=Role.CHILD.color) .add(self.disk(), name="disk", color=Role.CHILD.color)
.add(self.housing_lower(), name="housing_lower", color=Role.PARENT.color) .add(self.housing_lower(), name="housing_lower", color=Role.PARENT.color)
.add(self.housing_upper(), name="housing_upper", color=Role.PARENT.color) .add(self.housing_upper(), name="housing_upper", color=Role.CASING.color)
.constrain("disk?mate_bot", "housing_lower?mate", "Plane") .constrain("housing_lower", "Fixed")
.constrain("disk?mate_top", "housing_upper?mate", "Plane")
.solve()
) )
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__': if __name__ == '__main__':
p = DiskJoint() p = DiskJoint()

View File

@ -1,8 +1,24 @@
import unittest import unittest
import cadquery as Cq import cadquery as Cq
import nhf.touhou.houjuu_nue as M import nhf.touhou.houjuu_nue as M
import nhf.touhou.houjuu_nue.parts as MP
from nhf.checks import pairwise_intersection 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): class Test(unittest.TestCase):
def test_hs_joint_parent(self): def test_hs_joint_parent(self):