feat: Shoulder parent joint

This commit is contained in:
Leni Aniva 2024-07-07 12:15:47 -07:00
parent fc0edd995b
commit 54593b9a4e
Signed by: aniva
GPG Key ID: 4D9B1C8D10EA4C50
5 changed files with 102 additions and 30 deletions

View File

@ -379,7 +379,9 @@ class TorsionJoint:
forConstruction=True).tag("directrix")
return result
def rider(self):
def rider(self, rider_slot_begin=None):
if not rider_slot_begin:
rider_slot_begin = self.rider_slot_begin
def slot(loc):
wire = Cq.Wire.makePolygon(self._slot_polygon(flip=False))
face = Cq.Face.makeFromWires(wire)

View File

@ -24,6 +24,7 @@ class TestJoints(unittest.TestCase):
joints.comma_assembly()
def torsion_joint_case(self, joint: joints.TorsionJoint, slot: int):
assert 0 <= slot and slot < joint.rider_n_slots
assembly = joint.rider_track_assembly(slot)
bbox = assembly.toCompound().BoundingBox()
self.assertAlmostEqual(bbox.zlen, joint.total_height)
@ -45,6 +46,11 @@ class TestJoints(unittest.TestCase):
spring_hole_cover_rider=True,
)
self.torsion_joint_case(j, 1)
def test_torsion_joint_slot(self):
j = joints.TorsionJoint(
rider_slot_begin=90,
)
self.torsion_joint_case(j, 1)

View File

@ -92,8 +92,10 @@ class Parameters(Model):
wing_root_radius: float = 40
wing_root_wall_thickness: float = 8
shoulder_joint: TorsionJoint = field(default_factory=lambda: TorsionJoint(
radius_axle=8
shoulder_torsion_joint: TorsionJoint = field(default_factory=lambda: TorsionJoint(
track_disk_height=5.0,
rider_disk_height=7.0,
radius_axle=8.0,
))
# Two holes on each side (top and bottom) are used to attach the shoulder
@ -104,10 +106,10 @@ class Parameters(Model):
"""
Heights for various wing joints, where the numbers start from the first joint.
"""
wing_s0_thickness: float = 40
wing_s0_height: float = 100
wing_r1_height: float = 100
wing_r1_width: float = 400
wing_r2_height: float = 100
wing_r3_height: float = 100
trident_handle: Handle = field(default_factory=lambda: Handle(
diam=38,
@ -267,18 +269,20 @@ class Parameters(Model):
shoulder_attach_dist=self.shoulder_attach_dist,
shoulder_attach_diam=self.shoulder_attach_diam,
wall_thickness=self.wing_root_wall_thickness,
conn_height=self.wing_s0_height,
conn_thickness=self.wing_s0_thickness,
)
@target(name="shoulder")
def shoulder_parent_joint(self) -> Cq.Assembly:
@target(name="shoulder_parent")
def shoulder_parent_joint(self) -> Cq.Workplane:
result = (
self.shoulder_joint.rider()
self.shoulder_torsion_joint.rider()
.copyWorkplane(Cq.Workplane(
'YZ', origin=Cq.Vector((100, 0, self.wing_root_wall_thickness))))
.rect(30, 10, centered=(True, False))
'YZ', origin=Cq.Vector((90, 0, self.wing_root_wall_thickness))))
.rect(25, 7, centered=(True, False))
.extrude("next")
.copyWorkplane(Cq.Workplane(
'YX', origin=Cq.Vector((60, 0, self.wing_root_wall_thickness))))
'YX', origin=Cq.Vector((55, 0, self.wing_root_wall_thickness))))
.hole(self.shoulder_attach_diam)
.moveTo(0, self.shoulder_attach_dist)
.hole(self.shoulder_attach_diam)
@ -287,6 +291,27 @@ class Parameters(Model):
result.moveTo(0, self.shoulder_attach_dist).tagPlane('conn1')
return result
@target(name="shoulder_child")
def shoulder_child_joint(self) -> Cq.Assembly:
# FIXME: half of conn_height
h = 100 / 2
dh = h - self.shoulder_torsion_joint.total_height
core = (
Cq.Workplane('XY')
.moveTo(0, 15)
.box(50, 40, 2 * dh, centered=(True, False, True))
)
loc_rotate = Cq.Location((0, 0, 0), (1, 0, 0), 180)
result = (
Cq.Assembly()
.add(core, name="core", loc=Cq.Location())
.add(self.shoulder_torsion_joint.track(), name="track_top",
loc=Cq.Location((0, 0, dh), (0, 0, 1), -90))
.add(self.shoulder_torsion_joint.track(), name="track_bot",
loc=Cq.Location((0, 0, -dh), (0, 0, 1), -90) * loc_rotate)
)
return result
def wing_r1_profile(self) -> Cq.Sketch:
"""
Generates the first wing segment profile, with the wing root pointing in
@ -352,14 +377,44 @@ class Parameters(Model):
result.solve()
return result
def shoulder_assembly(self) -> Cq.Assembly:
result = (
Cq.Assembly()
.add(self.shoulder_child_joint(), name="child",
color=Role.CHILD.color)
.constrain("child/core", "Fixed")
# Top parent joint
.add(self.shoulder_torsion_joint.spring(), name="spring_top",
color=Role.DAMPING.color)
.constrain("child/track_top?spring", "spring_top?top", "Plane")
.constrain("child/track_top?directrix", "spring_top?directrix_bot", "Axis")
.add(self.shoulder_parent_joint(), name="parent_top",
color=Role.PARENT.color)
.constrain("parent_top?spring", "spring_top?bot", "Plane")
.constrain("parent_top?directrix0", "spring_top?directrix_top", "Axis")
# Bottom parent joint
.add(self.shoulder_torsion_joint.spring(), name="spring_bot",
color=Role.DAMPING.color)
.constrain("child/track_bot?spring", "spring_bot?top", "Plane")
.constrain("child/track_bot?directrix", "spring_bot?directrix_bot", "Axis")
.add(self.shoulder_parent_joint(), name="parent_bot",
color=Role.PARENT.color)
.constrain("parent_bot?spring", "spring_bot?bot", "Plane")
.constrain("parent_bot?directrix0", "spring_bot?directrix_top", "Axis")
.solve()
)
return result
def wing_r1_assembly(self) -> Cq.Assembly:
result = (
Cq.Assembly()
.add(self.wing_root(), name="r1")
.add(self.shoulder_parent_joint(), name="shoulder_parent_top",
color=Material.RESIN_TRANSPARENT.color)
.constrain("r1/scaffold?conn_top0", "shoulder_parent_top?conn0", "Plane")
.constrain("r1/scaffold?conn_top1", "shoulder_parent_top?conn1", "Plane")
.add(self.shoulder_assembly(), name="shoulder")
.constrain("r1/scaffold", "Fixed")
.constrain("r1/scaffold?conn_top0", "shoulder/parent_top?conn0", "Plane")
.constrain("r1/scaffold?conn_top1", "shoulder/parent_top?conn1", "Plane")
.constrain("r1/scaffold?conn_bot0", "shoulder/parent_bot?conn0", "Plane")
.constrain("r1/scaffold?conn_bot1", "shoulder/parent_bot?conn1", "Plane")
.solve()
)
return result

View File

@ -1,6 +1,7 @@
import unittest
import cadquery as Cq
import nhf.touhou.houjuu_nue as M
from nhf.checks import pairwise_intersection
class Test(unittest.TestCase):
@ -8,19 +9,23 @@ class Test(unittest.TestCase):
p = M.Parameters()
obj = p.hs_joint_parent()
self.assertIsInstance(obj.val().solids(), Cq.Solid, msg="H-S joint must be in one piece")
def test_shoulder_joint(self):
p = M.Parameters()
shoulder = p.shoulder_assembly()
assert isinstance(shoulder, Cq.Assembly)
self.assertEqual(pairwise_intersection(shoulder), [])
def test_wing_root(self):
p = M.Parameters()
obj = p.wing_root()
assert isinstance(obj, Cq.Assembly)
#self.assertIsInstance(obj.solids(), Cq.Solid, msg="Wing root must be in one piece")
bbox = obj.val().BoundingBox()
bbox = obj.toCompound().BoundingBox()
msg = "Must fix 256^3 bbox"
self.assertLess(bbox.xlen, 255, msg=msg)
self.assertLess(bbox.ylen, 255, msg=msg)
self.assertLess(bbox.zlen, 255, msg=msg)
def test_wing_root(self):
p = M.Parameters()
p.wing_root()
def test_wings_assembly(self):
p = M.Parameters()
p.wings_assembly()

View File

@ -14,7 +14,7 @@ def wing_root_profiles(
base_radius=40,
middle_offset=30,
middle_height=80,
conn_width=40,
conn_thickness=40,
conn_height=100) -> tuple[Cq.Wire, Cq.Wire]:
assert base_sweep < 180
assert middle_offset > 0
@ -74,7 +74,7 @@ def wing_root_profiles(
# If the exterior sweep is theta', it has to satisfy
#
# sin(theta) * r2 + wall_thickness = sin(theta') * r1
x, y = conn_width / 2, middle_height / 2
x, y = conn_thickness / 2, middle_height / 2
t = wall_thickness
dx = middle_offset
middle = (
@ -109,7 +109,7 @@ def wing_root_profiles(
)
assert isinstance(middle, Cq.Wire)
x, y = conn_width / 2, conn_height / 2
x, y = conn_thickness / 2, conn_height / 2
t = wall_thickness
tip = (
Cq.Sketch()
@ -132,30 +132,32 @@ def wing_root(joint: HirthJoint,
union_tol=1e-4,
shoulder_attach_diam=8,
shoulder_attach_dist=25,
conn_width=40,
conn_thickness=40,
conn_height=100,
wall_thickness=8) -> Cq.Assembly:
"""
Generate the contiguous components of the root wing segment
"""
tip_centre = Cq.Vector((-150, 0, -80))
attach_theta = math.radians(5)
c, s = math.cos(attach_theta), math.sin(attach_theta)
attach_points = [
(15, 0),
(15 + shoulder_attach_dist, 0),
(15, 4),
(15 + shoulder_attach_dist * c, 4 + shoulder_attach_dist * s),
]
root_profile, middle_profile, tip_profile = wing_root_profiles(
conn_width=conn_width,
conn_thickness=conn_thickness,
conn_height=conn_height,
wall_thickness=8,
)
middle_profile = middle_profile.located(Cq.Location(
(-40, 0, -40), (0, 30, 0)
(-40, 0, -40), (0, 1, 0), 30
))
antetip_profile = tip_profile.located(Cq.Location(
(-95, 0, -75), (0, 60, 0)
(-95, 0, -75), (0, 1, 0), 60
))
tip_profile = tip_profile.located(Cq.Location(
tip_centre, (0, 90, 0)
tip_centre, (0, 1, 0), 90
))
profiles = [
root_profile,
@ -201,9 +203,11 @@ def wing_root(joint: HirthJoint,
Cq.Vector((0, y, 0)))
)
)
if side == "bottom":
side = "bot"
for i, (px, py) in enumerate(attach_points):
tag = f"conn_{side}{i}"
plane.moveTo(px, py).tagPlane(tag)
plane.moveTo(px, -py if side == "top" else py).tagPlane(tag)
result.faces("<Z").tag("base")
result.faces(">X").tag("conn")