cosplay: Touhou/Houjuu Nue #4
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue