cosplay: Touhou/Houjuu Nue #4

Open
aniva wants to merge 189 commits from touhou/houjuu-nue into main
2 changed files with 115 additions and 107 deletions
Showing only changes of commit 8b5906948e - Show all commits

View File

@ -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

View File

@ -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()