cosplay: Touhou/Houjuu Nue #4
|
@ -76,8 +76,8 @@ class RootJoint(Model):
|
|||
hirth_joint: HirthJoint = field(default_factory=lambda: HirthJoint(
|
||||
radius=25.0,
|
||||
radius_inner=15.0,
|
||||
tooth_height=7.0,
|
||||
base_height=5.0,
|
||||
tooth_height=5.60,
|
||||
base_height=4.0,
|
||||
n_tooth=24,
|
||||
))
|
||||
parent_width: float = 85
|
||||
|
@ -100,6 +100,10 @@ class RootJoint(Model):
|
|||
child_width: float = 50.0
|
||||
child_mount_thickness: float = 25.4 / 4
|
||||
|
||||
def __post_init__(self):
|
||||
assert self.child_extra_thickness > 0.0
|
||||
assert self.parent_thickness >= self.hex_nut.thickness
|
||||
|
||||
def corner_pos(self) -> list[tuple[int, int]]:
|
||||
"""
|
||||
Generates a set of points corresponding to the connectorss
|
||||
|
@ -113,8 +117,15 @@ class RootJoint(Model):
|
|||
]
|
||||
|
||||
@property
|
||||
def total_height(self) -> float:
|
||||
return self.parent_thickness + self.hirth_joint.total_height
|
||||
def child_extra_thickness(self) -> float:
|
||||
"""
|
||||
Extra thickness allocated to child for padding
|
||||
"""
|
||||
return self.knob.height_thread - self.hirth_joint.joint_height - self.child_mount_thickness - self.parent_thickness
|
||||
|
||||
@property
|
||||
def base_to_surface_thickness(self) -> float:
|
||||
return self.hirth_joint.joint_height + self.child_extra_thickness
|
||||
|
||||
@target(name="parent")
|
||||
def parent(self):
|
||||
|
@ -127,9 +138,9 @@ class RootJoint(Model):
|
|||
result = (
|
||||
Cq.Workplane('XY')
|
||||
.box(
|
||||
self.parent_width,
|
||||
self.parent_width,
|
||||
self.parent_thickness,
|
||||
length=self.parent_width,
|
||||
width=self.parent_width,
|
||||
height=self.parent_thickness,
|
||||
centered=(True, True, False))
|
||||
.translate((0, 0, -self.parent_thickness))
|
||||
.edges("|Z")
|
||||
|
@ -177,11 +188,11 @@ class RootJoint(Model):
|
|||
result = (
|
||||
Cq.Workplane('XY')
|
||||
.box(
|
||||
self.child_height,
|
||||
self.child_width,
|
||||
self.hirth_joint.base_height,
|
||||
length=self.child_height,
|
||||
width=self.child_width,
|
||||
height=self.child_extra_thickness + self.hirth_joint.base_height,
|
||||
centered=(True, True, False))
|
||||
#.translate((0, 0, -self.base_joint.base_height))
|
||||
.translate((0, 0, -self.child_extra_thickness))
|
||||
#.edges("|Z")
|
||||
#.fillet(self.hs_joint_corner_fillet)
|
||||
.faces(">Z")
|
||||
|
@ -190,7 +201,7 @@ class RootJoint(Model):
|
|||
.hole(self.corner_hole_diam)
|
||||
)
|
||||
# Creates a plane parallel to the holes but shifted to the base
|
||||
plane = result.faces(">Z").workplane(offset=-self.hirth_joint.base_height)
|
||||
plane = result.copyWorkplane(Cq.Workplane('XY', origin=(0, 0, -self.child_extra_thickness)))
|
||||
|
||||
for i, (px, py) in enumerate(conn):
|
||||
plane.moveTo(px, py).tagPlane(f"conn{i}")
|
||||
|
@ -232,7 +243,7 @@ class RootJoint(Model):
|
|||
result
|
||||
.addS(self.hex_nut.assembly(), name="hex_nut")
|
||||
.addS(self.knob.assembly(), name="knob",
|
||||
loc=Cq.Location((0, 0, knob_h * fastener_pos)))
|
||||
loc=Cq.Location((0, 0, knob_h * fastener_pos - self.parent_thickness)))
|
||||
.constrain("knob/thread", "Fixed")
|
||||
.constrain("hex_nut?bot", "parent?base", "Plane", param=0)
|
||||
.constrain("hex_nut?dirX", "parent@faces@>X", "Axis", param=0)
|
||||
|
@ -1071,6 +1082,14 @@ class ElbowJoint(Model):
|
|||
motion_span=self.motion_span
|
||||
)
|
||||
|
||||
def hole_loc_tags(self):
|
||||
"""
|
||||
An iterator which iterates through positions of the hole and tags
|
||||
"""
|
||||
for i, x in enumerate(self.hole_pos):
|
||||
yield x, f"conn_top{i}"
|
||||
yield -x, f"conn_bot{i}"
|
||||
|
||||
@property
|
||||
def total_thickness(self):
|
||||
return self.disk_joint.total_thickness
|
||||
|
|
|
@ -63,6 +63,7 @@ class WingProfile(Model):
|
|||
angle_neutral=10.0,
|
||||
actuator=LINEAR_ACTUATOR_50,
|
||||
flexor_offset_angle=30,
|
||||
parent_arm_width=15,
|
||||
flip=False,
|
||||
))
|
||||
# Distance between the two spacers on the elbow, halved
|
||||
|
@ -353,7 +354,7 @@ class WingProfile(Model):
|
|||
|
||||
def surface_s0(self, top: bool = False) -> Cq.Workplane:
|
||||
base_dx = -(self.base_width - self.root_joint.child_width) / 2 - 10
|
||||
base_dy = self.root_joint.hirth_joint.joint_height
|
||||
base_dy = self.root_joint.base_to_surface_thickness
|
||||
#mid_spacer_loc = (
|
||||
# Cq.Location.from2d(0, -self.shoulder_width/2) *
|
||||
# self.shoulder_axle_loc *
|
||||
|
@ -593,6 +594,25 @@ class WingProfile(Model):
|
|||
)
|
||||
return mbox
|
||||
|
||||
def _spacer_from_disk_joint(
|
||||
self,
|
||||
joint: ElbowJoint,
|
||||
segment_thickness: float,
|
||||
) -> MountingBox:
|
||||
holes = [
|
||||
Hole(x, tag=tag)
|
||||
for x, tag in joint.hole_loc_tags()
|
||||
]
|
||||
mbox = MountingBox(
|
||||
length=joint.lip_length,
|
||||
width=segment_thickness,
|
||||
thickness=self.spacer_thickness,
|
||||
holes=holes,
|
||||
hole_diam=joint.hole_diam,
|
||||
centred=(True, True),
|
||||
centre_left_right_tags=True,
|
||||
)
|
||||
return mbox
|
||||
|
||||
@target(name="profile-s1", kind=TargetKind.DXF)
|
||||
def profile_s1(self) -> Cq.Sketch:
|
||||
|
@ -615,23 +635,17 @@ class WingProfile(Model):
|
|||
)
|
||||
return profile
|
||||
def surface_s1(self, front: bool = True) -> Cq.Workplane:
|
||||
tags_shoulder = [
|
||||
rot_elbow = Cq.Location.rot2d(self.elbow_rotate)
|
||||
loc_elbow = rot_elbow * self.elbow_joint.parent_arm_loc()
|
||||
tags = [
|
||||
("shoulder",
|
||||
Cq.Location((0, self.shoulder_height / 2, 0)) *
|
||||
self.shoulder_joint.child_lip_loc()),
|
||||
]
|
||||
rot_elbow = Cq.Location.rot2d(self.elbow_rotate)
|
||||
loc_elbow = rot_elbow * self.elbow_joint.parent_arm_loc()
|
||||
tags_elbow = [
|
||||
("elbow_bot", self.elbow_axle_loc * loc_elbow *
|
||||
Cq.Location.from2d(0, -self.elbow_h2)),
|
||||
("elbow_top", self.elbow_axle_loc * loc_elbow *
|
||||
Cq.Location.from2d(0, self.elbow_h2)),
|
||||
("elbow", self.elbow_axle_loc * loc_elbow),
|
||||
("elbow_act", self.elbow_axle_loc * rot_elbow *
|
||||
self.elbow_joint.actuator_mount_loc()),
|
||||
]
|
||||
profile = self.profile_s1()
|
||||
tags = tags_shoulder + tags_elbow
|
||||
return extrude_with_markers(
|
||||
profile, self.panel_thickness, tags, reverse=front)
|
||||
@submodel(name="spacer-s1-shoulder")
|
||||
|
@ -653,10 +667,9 @@ class WingProfile(Model):
|
|||
)
|
||||
@submodel(name="spacer-s1-elbow")
|
||||
def spacer_s1_elbow(self) -> MountingBox:
|
||||
return self.spacer_of_joint(
|
||||
return self._spacer_from_disk_joint(
|
||||
joint=self.elbow_joint,
|
||||
segment_thickness=self.s1_thickness,
|
||||
dx=self.elbow_h2,
|
||||
)
|
||||
@submodel(name="spacer-s1-elbow-act")
|
||||
def spacer_s1_elbow_act(self) -> MountingBox:
|
||||
|
@ -683,16 +696,14 @@ class WingProfile(Model):
|
|||
)
|
||||
for o, t in [
|
||||
(self.spacer_s1_shoulder(), "shoulder"),
|
||||
(self.spacer_s1_elbow(), "elbow_top"),
|
||||
(self.spacer_s1_elbow(), "elbow_bot"),
|
||||
(self.spacer_s1_elbow(), "elbow"),
|
||||
(self.spacer_s1_elbow_act(), "elbow_act"),
|
||||
]:
|
||||
is_top = t.endswith("_top")
|
||||
self._assembly_insert_spacer(
|
||||
result,
|
||||
o.generate(),
|
||||
point_tag=t,
|
||||
flipped=not is_top,
|
||||
flipped=True,
|
||||
)
|
||||
return result.solve()
|
||||
|
||||
|
@ -743,24 +754,21 @@ class WingProfile(Model):
|
|||
return profile
|
||||
def surface_s2(self, front: bool = True) -> Cq.Workplane:
|
||||
loc_elbow = Cq.Location.rot2d(self.elbow_rotate) * self.elbow_joint.child_arm_loc()
|
||||
tags_elbow = [
|
||||
("elbow_bot", self.elbow_axle_loc * loc_elbow *
|
||||
Cq.Location.from2d(0, self.elbow_h2)),
|
||||
("elbow_top", self.elbow_axle_loc * loc_elbow *
|
||||
Cq.Location.from2d(0, -self.elbow_h2)),
|
||||
]
|
||||
rot_wrist = Cq.Location.rot2d(self.wrist_rotate)
|
||||
loc_wrist = rot_wrist * self.wrist_joint.parent_arm_loc()
|
||||
tags_wrist = [
|
||||
tags = [
|
||||
("elbow", self.elbow_axle_loc * loc_elbow),
|
||||
("wrist", self.wrist_axle_loc * loc_wrist),
|
||||
("wrist_act", self.wrist_axle_loc * rot_wrist *
|
||||
self.wrist_joint.actuator_mount_loc()),
|
||||
|
||||
# for mounting the bridge only
|
||||
("wrist_bot", self.wrist_axle_loc * loc_wrist *
|
||||
Cq.Location.from2d(0, -self.wrist_h2)),
|
||||
("wrist_top", self.wrist_axle_loc * loc_wrist *
|
||||
Cq.Location.from2d(0, self.wrist_h2)),
|
||||
("wrist_act", self.wrist_axle_loc * rot_wrist *
|
||||
self.wrist_joint.actuator_mount_loc()),
|
||||
]
|
||||
profile = self.profile_s2()
|
||||
tags = tags_elbow + tags_wrist
|
||||
return extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
||||
def surface_s2_bridge(self, front: bool = True) -> Optional[Cq.Workplane]:
|
||||
profile = self.profile_s2_bridge()
|
||||
|
@ -777,17 +785,15 @@ class WingProfile(Model):
|
|||
profile, self.panel_thickness, tags, reverse=not front)
|
||||
@submodel(name="spacer-s2-elbow")
|
||||
def spacer_s2_elbow(self) -> MountingBox:
|
||||
return self.spacer_of_joint(
|
||||
return self._spacer_from_disk_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:
|
||||
return self.spacer_of_joint(
|
||||
return self._spacer_from_disk_joint(
|
||||
joint=self.wrist_joint,
|
||||
segment_thickness=self.s2_thickness,
|
||||
dx=self.wrist_h2,
|
||||
)
|
||||
@submodel(name="spacer-s2-wrist-act")
|
||||
def spacer_s2_wrist_act(self) -> MountingBox:
|
||||
|
@ -831,20 +837,16 @@ class WingProfile(Model):
|
|||
.constrain("back?wrist_top", "bridge_back?wrist_top", "Plane")
|
||||
)
|
||||
for o, t in [
|
||||
(self.spacer_s2_elbow(), "elbow_bot"),
|
||||
(self.spacer_s2_elbow(), "elbow_top"),
|
||||
(self.spacer_s2_wrist(), "wrist_bot"),
|
||||
(self.spacer_s2_wrist(), "wrist_top"),
|
||||
(self.spacer_s2_elbow(), "elbow"),
|
||||
(self.spacer_s2_wrist(), "wrist"),
|
||||
(self.spacer_s2_wrist_act(), "wrist_act"),
|
||||
]:
|
||||
is_top = t.endswith("_top")
|
||||
is_parent = t.startswith("elbow")
|
||||
self._assembly_insert_spacer(
|
||||
result,
|
||||
o.generate(),
|
||||
point_tag=t,
|
||||
flipped=is_top == is_parent,
|
||||
#rotate=not is_parent,
|
||||
flipped=is_parent,
|
||||
)
|
||||
return result.solve()
|
||||
|
||||
|
@ -878,6 +880,7 @@ class WingProfile(Model):
|
|||
front: bool = True) -> Cq.Workplane:
|
||||
loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc()
|
||||
tags = [
|
||||
("wrist", self.wrist_axle_loc * loc_wrist),
|
||||
("wrist_bot", self.wrist_axle_loc * loc_wrist *
|
||||
Cq.Location.from2d(0, self.wrist_h2)),
|
||||
("wrist_top", self.wrist_axle_loc * loc_wrist *
|
||||
|
@ -900,10 +903,9 @@ class WingProfile(Model):
|
|||
return extrude_with_markers(profile, self.panel_thickness, tags, reverse=not front)
|
||||
@submodel(name="spacer-s3-wrist")
|
||||
def spacer_s3_wrist(self) -> MountingBox:
|
||||
return self.spacer_of_joint(
|
||||
return self._spacer_from_disk_joint(
|
||||
joint=self.wrist_joint,
|
||||
segment_thickness=self.s3_thickness,
|
||||
dx=self.wrist_h2,
|
||||
)
|
||||
@assembly()
|
||||
def assembly_s3(self) -> Cq.Assembly:
|
||||
|
@ -929,28 +931,26 @@ class WingProfile(Model):
|
|||
.constrain("back?wrist_bot", "extra_back?wrist_bot", "Plane")
|
||||
.constrain("back?wrist_top", "extra_back?wrist_top", "Plane")
|
||||
)
|
||||
for t in ["wrist_bot", "wrist_top"]:
|
||||
is_top = t.endswith("_top")
|
||||
o = self.spacer_s3_wrist()
|
||||
self._assembly_insert_spacer(
|
||||
result,
|
||||
o.generate(),
|
||||
point_tag=t,
|
||||
flipped=is_top,
|
||||
)
|
||||
self._assembly_insert_spacer(
|
||||
result,
|
||||
self.spacer_s3_wrist().generate(),
|
||||
point_tag="wrist",
|
||||
flipped=True,
|
||||
)
|
||||
return result.solve()
|
||||
|
||||
@assembly()
|
||||
def assembly(self,
|
||||
parts: Optional[list[str]] = None,
|
||||
shoulder_deflection: float = 0.0,
|
||||
elbow_wrist_deflection: float = 0.0,
|
||||
root_offset: int = 5,
|
||||
fastener_pos: float = 0.0,
|
||||
ignore_fasteners: bool = False,
|
||||
ignore_electronics: bool = False,
|
||||
ignore_actuators: bool = False,
|
||||
) -> Cq.Assembly():
|
||||
def assembly(
|
||||
self,
|
||||
parts: Optional[list[str]] = None,
|
||||
shoulder_deflection: float = 0.0,
|
||||
elbow_wrist_deflection: float = 0.0,
|
||||
root_offset: int = 5,
|
||||
fastener_pos: float = 0.0,
|
||||
ignore_fasteners: bool = False,
|
||||
ignore_electronics: bool = False,
|
||||
ignore_actuators: bool = False,
|
||||
) -> Cq.Assembly():
|
||||
if parts is None:
|
||||
parts = [
|
||||
"root",
|
||||
|
@ -1013,55 +1013,44 @@ class WingProfile(Model):
|
|||
angle=angle,
|
||||
ignore_actuators=ignore_actuators), name="elbow")
|
||||
if "s1" in parts and "elbow" in parts:
|
||||
(
|
||||
result
|
||||
.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")
|
||||
)
|
||||
for _, tag in self.elbow_joint.hole_loc_tags():
|
||||
result.constrain(
|
||||
f"s1/elbow?{tag}",
|
||||
f"elbow/parent_upper/lip?{tag}", "Plane")
|
||||
if not ignore_actuators:
|
||||
(
|
||||
result
|
||||
.constrain("elbow/bracket_back?conn_side", "s1/elbow_act?conn0", "Plane")
|
||||
)
|
||||
result.constrain(
|
||||
"elbow/bracket_back?conn_side",
|
||||
"s1/elbow_act?conn0",
|
||||
"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/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")
|
||||
)
|
||||
for _, tag in self.elbow_joint.hole_loc_tags():
|
||||
result.constrain(
|
||||
f"s2/elbow?{tag}",
|
||||
f"elbow/child/lip?{tag}", "Plane")
|
||||
if "wrist" in parts:
|
||||
angle = self.wrist_joint.motion_span * elbow_wrist_deflection
|
||||
result.add(self.wrist_joint.assembly(
|
||||
angle=angle,
|
||||
ignore_actuators=ignore_actuators), name="wrist")
|
||||
wrist_n_holes = len(self.wrist_joint.hole_pos)
|
||||
if "s2" in parts and "wrist" in parts:
|
||||
for i in range(wrist_n_holes):
|
||||
(
|
||||
result
|
||||
.constrain(f"s2/wrist_top?conn{i}", f"wrist/parent_upper/lip?conn_top{i}", "Plane")
|
||||
.constrain(f"s2/wrist_bot?conn{i}", f"wrist/parent_upper/lip?conn_bot{i}", "Plane")
|
||||
)
|
||||
for _, tag in self.wrist_joint.hole_loc_tags():
|
||||
result.constrain(
|
||||
f"s2/wrist?{tag}",
|
||||
f"wrist/parent_upper/lip?{tag}", "Plane")
|
||||
if "s3" in parts:
|
||||
result.add(self.assembly_s3(), name="s3")
|
||||
if "s3" in parts and "wrist" in parts:
|
||||
for i in range(wrist_n_holes):
|
||||
(
|
||||
result
|
||||
.constrain(f"s3/wrist_top?conn{i}", f"wrist/child/lip?conn_top{i}", "Plane")
|
||||
.constrain(f"s3/wrist_bot?conn{i}", f"wrist/child/lip?conn_bot{i}", "Plane")
|
||||
)
|
||||
for _, tag in self.wrist_joint.hole_loc_tags():
|
||||
result.constrain(
|
||||
f"s3/wrist?{tag}",
|
||||
f"wrist/child/lip?{tag}", "Plane")
|
||||
if not ignore_actuators:
|
||||
(
|
||||
result
|
||||
.constrain("wrist/bracket_back?conn_side", "s2/wrist_act?conn0", "Plane")
|
||||
)
|
||||
result.constrain(
|
||||
"wrist/bracket_back?conn_side",
|
||||
"s2/wrist_act?conn0",
|
||||
"Plane")
|
||||
if len(parts) > 1:
|
||||
result.solve()
|
||||
|
||||
|
|
Loading…
Reference in New Issue