cosplay: Touhou/Houjuu Nue #4
|
@ -593,44 +593,45 @@ class ElbowJoint(Model):
|
|||
child_arm_radius: float = 40.0
|
||||
parent_arm_radius: float = 40.0
|
||||
|
||||
child_beam: Beam = field(default_factory=lambda: Beam())
|
||||
parent_beam: Beam = field(default_factory=lambda: Beam(
|
||||
spine_thickness=8.0,
|
||||
))
|
||||
parent_arm_span: float = 40.0
|
||||
lip_thickness: float = 5.0
|
||||
lip_length: float = 60.0
|
||||
hole_pos: list[float] = field(default_factory=lambda: [15, 25])
|
||||
parent_arm_span: float = 30.0
|
||||
# Angle of the beginning of the parent arm
|
||||
parent_arm_angle: float = 180.0
|
||||
parent_binding_hole_radius: float = 30.0
|
||||
|
||||
# Size of the mounting holes
|
||||
hole_diam: float = 8.0
|
||||
hole_diam: float = 6.0
|
||||
|
||||
material: Material = Material.RESIN_TRANSPERENT
|
||||
|
||||
# If true, flip the top and bottom tags
|
||||
flip: bool = False
|
||||
angle_neutral: float = 20.0
|
||||
|
||||
def __post_init__(self):
|
||||
assert self.child_arm_radius > self.disk_joint.radius_housing
|
||||
assert self.parent_arm_radius > self.disk_joint.radius_housing
|
||||
self.disk_joint.tongue_length = self.child_arm_radius - self.disk_joint.radius_disk
|
||||
assert self.disk_joint.movement_angle < self.parent_arm_angle < 360 - self.parent_arm_span
|
||||
assert self.parent_binding_hole_radius - self.hole_diam / 2 > self.disk_joint.radius_housing
|
||||
|
||||
def child_hole_pos(self) -> list[float]:
|
||||
"""
|
||||
List of hole positions measured from axle
|
||||
"""
|
||||
dx = self.child_beam.hole_dist / 2
|
||||
r = self.child_arm_radius
|
||||
return [r - dx, r + dx]
|
||||
def parent_hole_pos(self) -> list[float]:
|
||||
"""
|
||||
List of hole positions measured from axle
|
||||
"""
|
||||
dx = self.parent_beam.hole_dist / 2
|
||||
r = self.parent_arm_radius
|
||||
return [r - dx, r + dx]
|
||||
def lip(self) -> Cq.Workplane:
|
||||
holes = [
|
||||
h
|
||||
for i, x in enumerate(self.hole_pos)
|
||||
for h in [
|
||||
Hole(x=x, tag=f"conn_top{i}"),
|
||||
Hole(x=-x, tag=f"conn_bot{i}")
|
||||
]
|
||||
]
|
||||
mbox = MountingBox(
|
||||
length=self.lip_length,
|
||||
width=self.disk_joint.total_thickness,
|
||||
thickness=self.lip_thickness,
|
||||
holes=holes,
|
||||
hole_diam=self.hole_diam,
|
||||
centred=(True, True),
|
||||
generate_side_tags=False,
|
||||
)
|
||||
return mbox.generate()
|
||||
|
||||
@target(name="child")
|
||||
def child_joint(self) -> Cq.Assembly:
|
||||
|
@ -639,14 +640,15 @@ class ElbowJoint(Model):
|
|||
# We need to ensure the disk is on the "other" side so
|
||||
flip_x = Cq.Location((0, 0, 0), (1, 0, 0), 180)
|
||||
flip_z = Cq.Location((0, 0, 0), (0, 0, 1), 180)
|
||||
lip_dz = self.lip_thickness / 2
|
||||
result = (
|
||||
self.child_beam.generate(flip=self.flip)
|
||||
Cq.Assembly()
|
||||
.add(self.lip(), name="lip", loc=
|
||||
Cq.Location((0, 0, 0), (0, 1, 0), 180) *
|
||||
Cq.Location((-lip_dz, 0, 0), (1, 0, 0), 90) *
|
||||
Cq.Location((0, 0, 0), (0, 1, 0), 90))
|
||||
.add(self.disk_joint.disk(), name="disk",
|
||||
loc=flip_x * flip_z * Cq.Location((-self.child_arm_radius, 0, -dz), (0, 0, 1), angle))
|
||||
#.constrain("disk", "Fixed")
|
||||
#.constrain("top", "Fixed")
|
||||
#.constrain("bot", "Fixed")
|
||||
#.solve()
|
||||
)
|
||||
return result
|
||||
|
||||
|
@ -658,7 +660,7 @@ class ElbowJoint(Model):
|
|||
def parent_joint_upper(self):
|
||||
axial_offset = Cq.Location((self.parent_arm_radius, 0, 0))
|
||||
housing_dz = self.disk_joint.housing_upper_dz
|
||||
conn_h = self.parent_beam.spine_thickness
|
||||
conn_h = self.lip_thickness
|
||||
connector = (
|
||||
Cq.Solid.makeCylinder(
|
||||
height=conn_h,
|
||||
|
@ -675,10 +677,15 @@ class ElbowJoint(Model):
|
|||
housing_loc = Cq.Location(
|
||||
(0, 0, housing_dz),
|
||||
(0, 0, 1),
|
||||
-self.disk_joint.tongue_span / 2
|
||||
-self.disk_joint.tongue_span / 2 + self.angle_neutral
|
||||
)
|
||||
lip_dz = self.lip_thickness / 2
|
||||
result = (
|
||||
self.parent_beam.generate(flip=self.flip)
|
||||
Cq.Assembly()
|
||||
.add(self.lip(), name="lip", loc=
|
||||
Cq.Location((0, 0, 0), (0, 1, 0), 180) *
|
||||
Cq.Location((-lip_dz, 0, 0), (1, 0, 0), 90) *
|
||||
Cq.Location((0, 0, 0), (0, 1, 0), 90))
|
||||
.add(housing, name="housing",
|
||||
loc=axial_offset * housing_loc)
|
||||
.add(connector, name="connector",
|
||||
|
|
|
@ -50,8 +50,9 @@ class WingProfile(Model):
|
|||
disk_joint=DiskJoint(
|
||||
movement_angle=55,
|
||||
),
|
||||
flip=False,
|
||||
))
|
||||
# Distance between the two spacers on the elbow, halved
|
||||
elbow_h2: float = 5.0
|
||||
|
||||
s2_thickness: float = 25.0
|
||||
|
||||
|
@ -61,8 +62,9 @@ class WingProfile(Model):
|
|||
radius_disk=13.0,
|
||||
radius_housing=15.0,
|
||||
),
|
||||
flip=True,
|
||||
))
|
||||
# Distance between the two spacers on the elbow, halved
|
||||
wrist_h2: float = 5.0
|
||||
|
||||
s3_thickness: float = 25.0
|
||||
|
||||
|
@ -367,6 +369,7 @@ class WingProfile(Model):
|
|||
front_tag: str = "front",
|
||||
back_tag: str = "back",
|
||||
flipped: bool = False,
|
||||
rotate: bool = False,
|
||||
):
|
||||
"""
|
||||
For a child joint facing up, front panel should be on the right, back
|
||||
|
@ -375,7 +378,7 @@ class WingProfile(Model):
|
|||
site_front, site_back = "right", "left"
|
||||
if flipped:
|
||||
site_front, site_back = site_back, site_front
|
||||
angle = 0
|
||||
angle = 180 if rotate else 0
|
||||
(
|
||||
a
|
||||
.addS(
|
||||
|
@ -410,6 +413,27 @@ class WingProfile(Model):
|
|||
Polygon shape to mask wrist
|
||||
"""
|
||||
|
||||
def spacer_of_joint(
|
||||
self,
|
||||
joint: ElbowJoint,
|
||||
segment_thickness: float,
|
||||
dx: float,
|
||||
bot=False) -> MountingBox:
|
||||
length = joint.lip_length / 2 - dx
|
||||
holes = [
|
||||
Hole(x - dx)
|
||||
for x in joint.hole_pos
|
||||
]
|
||||
mbox = MountingBox(
|
||||
length=length,
|
||||
width=segment_thickness,
|
||||
thickness=self.spacer_thickness,
|
||||
holes=holes,
|
||||
hole_diam=joint.hole_diam,
|
||||
centred=(False, True),
|
||||
)
|
||||
return mbox
|
||||
|
||||
|
||||
@target(name="profile-s1", kind=TargetKind.DXF)
|
||||
def profile_s1(self) -> Cq.Sketch:
|
||||
|
@ -429,15 +453,14 @@ class WingProfile(Model):
|
|||
("shoulder_bot", (shoulder_mount_inset, h), 90),
|
||||
("shoulder_top", (shoulder_mount_inset, h + shoulder_h), 270),
|
||||
]
|
||||
elbow_h = self.elbow_joint.parent_beam.total_height
|
||||
h = (self.elbow_height - elbow_h) / 2
|
||||
h = self.elbow_height / 2
|
||||
tags_elbow = [
|
||||
("elbow_bot",
|
||||
self.elbow_to_abs(-elbow_mount_inset, h),
|
||||
self.elbow_angle + 90),
|
||||
self.elbow_to_abs(-elbow_mount_inset, h - self.elbow_h2),
|
||||
self.elbow_angle + 0),
|
||||
("elbow_top",
|
||||
self.elbow_to_abs(-elbow_mount_inset, h + elbow_h),
|
||||
self.elbow_angle + 270),
|
||||
self.elbow_to_abs(-elbow_mount_inset, h + self.elbow_h2),
|
||||
self.elbow_angle + 0),
|
||||
]
|
||||
profile = self.profile_s1()
|
||||
tags = tags_shoulder + tags_elbow
|
||||
|
@ -457,16 +480,10 @@ class WingProfile(Model):
|
|||
)
|
||||
@submodel(name="spacer-s1-elbow")
|
||||
def spacer_s1_elbow(self) -> MountingBox:
|
||||
holes = [
|
||||
Hole(x)
|
||||
for x in self.elbow_joint.parent_hole_pos()
|
||||
]
|
||||
return MountingBox(
|
||||
length=70.0, # FIXME: magic
|
||||
width=self.s1_thickness,
|
||||
thickness=self.spacer_thickness,
|
||||
holes=holes,
|
||||
hole_diam=self.elbow_joint.hole_diam,
|
||||
return self.spacer_of_joint(
|
||||
joint=self.elbow_joint,
|
||||
segment_thickness=self.s1_thickness,
|
||||
dx=self.elbow_h2,
|
||||
)
|
||||
@assembly()
|
||||
def assembly_s1(self) -> Cq.Assembly:
|
||||
|
@ -488,7 +505,7 @@ class WingProfile(Model):
|
|||
result,
|
||||
o,
|
||||
point_tag=t,
|
||||
flipped=is_top != is_parent,
|
||||
flipped=is_top != True #is_parent,
|
||||
)
|
||||
return result.solve()
|
||||
|
||||
|
@ -503,58 +520,43 @@ class WingProfile(Model):
|
|||
)
|
||||
return profile
|
||||
def surface_s2(self,
|
||||
thickness: float = 25.4/16,
|
||||
elbow_mount_inset: float = 0,
|
||||
wrist_mount_inset: float = 0,
|
||||
front: bool = True) -> Cq.Workplane:
|
||||
elbow_h = self.elbow_joint.child_beam.total_height
|
||||
h = (self.elbow_height - elbow_h) / 2
|
||||
h = self.elbow_height / 2
|
||||
tags_elbow = [
|
||||
("elbow_bot",
|
||||
self.elbow_to_abs(elbow_mount_inset, h),
|
||||
self.elbow_angle + 90),
|
||||
self.elbow_to_abs(elbow_mount_inset, h - self.elbow_h2),
|
||||
self.elbow_angle),
|
||||
("elbow_top",
|
||||
self.elbow_to_abs(elbow_mount_inset, h + elbow_h),
|
||||
self.elbow_angle - 90),
|
||||
self.elbow_to_abs(elbow_mount_inset, h + self.elbow_h2),
|
||||
self.elbow_angle),
|
||||
]
|
||||
wrist_h = self.wrist_joint.parent_beam.total_height
|
||||
h = (self.wrist_height - wrist_h) / 2
|
||||
h = self.wrist_height / 2
|
||||
tags_wrist = [
|
||||
("wrist_bot",
|
||||
self.wrist_to_abs(-wrist_mount_inset, h),
|
||||
self.wrist_angle + 90),
|
||||
self.wrist_to_abs(-wrist_mount_inset, h - self.wrist_h2),
|
||||
self.wrist_angle),
|
||||
("wrist_top",
|
||||
self.wrist_to_abs(-wrist_mount_inset, h + wrist_h),
|
||||
self.wrist_angle - 90),
|
||||
self.wrist_to_abs(-wrist_mount_inset, h + self.wrist_h2),
|
||||
self.wrist_angle),
|
||||
]
|
||||
profile = self.profile_s2()
|
||||
tags = tags_elbow + tags_wrist
|
||||
return nhf.utils.extrude_with_markers(profile, thickness, tags, reverse=front)
|
||||
return nhf.utils.extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
||||
@submodel(name="spacer-s2-elbow")
|
||||
def spacer_s2_elbow(self) -> MountingBox:
|
||||
holes = [
|
||||
Hole(x)
|
||||
for x in self.elbow_joint.child_hole_pos()
|
||||
]
|
||||
return MountingBox(
|
||||
length=50.0, # FIXME: magic
|
||||
width=self.s2_thickness,
|
||||
thickness=self.spacer_thickness,
|
||||
holes=holes,
|
||||
hole_diam=self.elbow_joint.hole_diam,
|
||||
return self.spacer_of_joint(
|
||||
joint=self.elbow_joint,
|
||||
segment_thickness=self.s2_thickness,
|
||||
dx=self.elbow_h2,
|
||||
)
|
||||
@submodel(name="spacer-s2-wrist")
|
||||
def spacer_s2_wrist(self) -> MountingBox:
|
||||
holes = [
|
||||
Hole(x)
|
||||
for x in self.wrist_joint.parent_hole_pos()
|
||||
]
|
||||
return MountingBox(
|
||||
length=70.0, # FIXME: magic
|
||||
width=self.s1_thickness,
|
||||
thickness=self.spacer_thickness,
|
||||
holes=holes,
|
||||
hole_diam=self.wrist_joint.hole_diam,
|
||||
return self.spacer_of_joint(
|
||||
joint=self.wrist_joint,
|
||||
segment_thickness=self.s2_thickness,
|
||||
dx=self.wrist_h2,
|
||||
)
|
||||
@assembly()
|
||||
def assembly_s2(self) -> Cq.Assembly:
|
||||
|
@ -576,7 +578,8 @@ class WingProfile(Model):
|
|||
result,
|
||||
o.generate(),
|
||||
point_tag=t,
|
||||
flipped=is_top != is_parent,
|
||||
flipped=is_top,# != is_parent,
|
||||
rotate=is_parent,
|
||||
)
|
||||
return result.solve()
|
||||
|
||||
|
@ -591,30 +594,23 @@ class WingProfile(Model):
|
|||
def surface_s3(self,
|
||||
front: bool = True) -> Cq.Workplane:
|
||||
wrist_mount_inset = 0
|
||||
wrist_h = self.wrist_joint.child_beam.total_height
|
||||
h = (self.wrist_height - wrist_h) / 2
|
||||
h = self.wrist_height / 2
|
||||
tags = [
|
||||
("wrist_bot",
|
||||
self.wrist_to_abs(wrist_mount_inset, h),
|
||||
self.wrist_angle + 90),
|
||||
self.wrist_to_abs(wrist_mount_inset, h - self.wrist_h2),
|
||||
self.wrist_angle),
|
||||
("wrist_top",
|
||||
self.wrist_to_abs(wrist_mount_inset, h + wrist_h),
|
||||
self.wrist_angle - 90),
|
||||
self.wrist_to_abs(wrist_mount_inset, h + self.wrist_h2),
|
||||
self.wrist_angle),
|
||||
]
|
||||
profile = self.profile_s3()
|
||||
return nhf.utils.extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
||||
@submodel(name="spacer-s3-wrist")
|
||||
def spacer_s3_wrist(self) -> MountingBox:
|
||||
holes = [
|
||||
Hole(x)
|
||||
for x in self.wrist_joint.child_hole_pos()
|
||||
]
|
||||
return MountingBox(
|
||||
length=70.0, # FIXME: magic
|
||||
width=self.s1_thickness,
|
||||
thickness=self.spacer_thickness,
|
||||
holes=holes,
|
||||
hole_diam=self.wrist_joint.hole_diam
|
||||
return self.spacer_of_joint(
|
||||
joint=self.wrist_joint,
|
||||
segment_thickness=self.s3_thickness,
|
||||
dx=self.wrist_h2,
|
||||
)
|
||||
@assembly()
|
||||
def assembly_s3(self) -> Cq.Assembly:
|
||||
|
@ -645,8 +641,6 @@ class WingProfile(Model):
|
|||
parts: Optional[list[str]] = None,
|
||||
angle_elbow_wrist: float = 0.0,
|
||||
) -> Cq.Assembly():
|
||||
assert not self.elbow_joint.flip
|
||||
assert self.wrist_joint.flip
|
||||
if parts is None:
|
||||
parts = ["s0", "shoulder", "s1", "elbow", "s2", "wrist", "s3"]
|
||||
result = (
|
||||
|
@ -683,20 +677,20 @@ class WingProfile(Model):
|
|||
if "s1" in parts and "elbow" in parts:
|
||||
(
|
||||
result
|
||||
.constrain("s1/elbow_top?conn0", "elbow/parent_upper/top?conn0", "Plane")
|
||||
.constrain("s1/elbow_top?conn1", "elbow/parent_upper/top?conn1", "Plane")
|
||||
.constrain("s1/elbow_bot?conn0", "elbow/parent_upper/bot?conn0", "Plane")
|
||||
.constrain("s1/elbow_bot?conn1", "elbow/parent_upper/bot?conn1", "Plane")
|
||||
.constrain("s1/elbow_top?conn0", "elbow/parent_upper/lip?conn_top0", "Plane")
|
||||
.constrain("s1/elbow_top?conn1", "elbow/parent_upper/lip?conn_top1", "Plane")
|
||||
.constrain("s1/elbow_bot?conn0", "elbow/parent_upper/lip?conn_bot0", "Plane")
|
||||
.constrain("s1/elbow_bot?conn1", "elbow/parent_upper/lip?conn_bot1", "Plane")
|
||||
)
|
||||
if "s2" in parts:
|
||||
result.add(self.assembly_s2(), name="s2")
|
||||
if "s2" in parts and "elbow" in parts:
|
||||
(
|
||||
result
|
||||
.constrain("s2/elbow_top?conn0", "elbow/child/top?conn0", "Plane")
|
||||
.constrain("s2/elbow_top?conn1", "elbow/child/top?conn1", "Plane")
|
||||
.constrain("s2/elbow_bot?conn0", "elbow/child/bot?conn0", "Plane")
|
||||
.constrain("s2/elbow_bot?conn1", "elbow/child/bot?conn1", "Plane")
|
||||
.constrain("s2/elbow_top?conn0", "elbow/child/lip?conn_top0", "Plane")
|
||||
.constrain("s2/elbow_top?conn1", "elbow/child/lip?conn_top1", "Plane")
|
||||
.constrain("s2/elbow_bot?conn0", "elbow/child/lip?conn_bot0", "Plane")
|
||||
.constrain("s2/elbow_bot?conn1", "elbow/child/lip?conn_bot1", "Plane")
|
||||
)
|
||||
if "wrist" in parts:
|
||||
result.add(self.wrist_joint.assembly(angle=angle_elbow_wrist), name="wrist")
|
||||
|
@ -704,20 +698,20 @@ class WingProfile(Model):
|
|||
# Mounted backwards to bend in other direction
|
||||
(
|
||||
result
|
||||
.constrain("s2/wrist_top?conn0", "wrist/parent_upper/top?conn0", "Plane")
|
||||
.constrain("s2/wrist_top?conn1", "wrist/parent_upper/top?conn1", "Plane")
|
||||
.constrain("s2/wrist_bot?conn0", "wrist/parent_upper/bot?conn0", "Plane")
|
||||
.constrain("s2/wrist_bot?conn1", "wrist/parent_upper/bot?conn1", "Plane")
|
||||
.constrain("s2/wrist_top?conn0", "wrist/parent_upper/bot?conn0", "Plane")
|
||||
.constrain("s2/wrist_top?conn1", "wrist/parent_upper/bot?conn1", "Plane")
|
||||
.constrain("s2/wrist_bot?conn0", "wrist/parent_upper/top?conn0", "Plane")
|
||||
.constrain("s2/wrist_bot?conn1", "wrist/parent_upper/top?conn1", "Plane")
|
||||
)
|
||||
if "s3" in parts:
|
||||
result.add(self.assembly_s3(), name="s3")
|
||||
if "s3" in parts and "wrist" in parts:
|
||||
(
|
||||
result
|
||||
.constrain("s3/wrist_top?conn0", "wrist/child/top?conn0", "Plane")
|
||||
.constrain("s3/wrist_top?conn1", "wrist/child/top?conn1", "Plane")
|
||||
.constrain("s3/wrist_bot?conn0", "wrist/child/bot?conn0", "Plane")
|
||||
.constrain("s3/wrist_bot?conn1", "wrist/child/bot?conn1", "Plane")
|
||||
.constrain("s3/wrist_top?conn0", "wrist/child/bot?conn0", "Plane")
|
||||
.constrain("s3/wrist_top?conn1", "wrist/child/bot?conn1", "Plane")
|
||||
.constrain("s3/wrist_bot?conn0", "wrist/child/top?conn0", "Plane")
|
||||
.constrain("s3/wrist_bot?conn1", "wrist/child/top?conn1", "Plane")
|
||||
)
|
||||
if len(parts) > 1:
|
||||
result.solve()
|
||||
|
|
Loading…
Reference in New Issue