feat: Simpler wrist and elbow mounts

This commit is contained in:
Leni Aniva 2024-07-24 16:17:07 -07:00
parent b6d429d272
commit 8b5906948e
Signed by: aniva
GPG Key ID: 4D9B1C8D10EA4C50
2 changed files with 115 additions and 107 deletions

View File

@ -76,8 +76,8 @@ class RootJoint(Model):
hirth_joint: HirthJoint = field(default_factory=lambda: HirthJoint( hirth_joint: HirthJoint = field(default_factory=lambda: HirthJoint(
radius=25.0, radius=25.0,
radius_inner=15.0, radius_inner=15.0,
tooth_height=7.0, tooth_height=5.60,
base_height=5.0, base_height=4.0,
n_tooth=24, n_tooth=24,
)) ))
parent_width: float = 85 parent_width: float = 85
@ -100,6 +100,10 @@ class RootJoint(Model):
child_width: float = 50.0 child_width: float = 50.0
child_mount_thickness: float = 25.4 / 4 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]]: def corner_pos(self) -> list[tuple[int, int]]:
""" """
Generates a set of points corresponding to the connectorss Generates a set of points corresponding to the connectorss
@ -113,8 +117,15 @@ class RootJoint(Model):
] ]
@property @property
def total_height(self) -> float: def child_extra_thickness(self) -> float:
return self.parent_thickness + self.hirth_joint.total_height """
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") @target(name="parent")
def parent(self): def parent(self):
@ -127,9 +138,9 @@ class RootJoint(Model):
result = ( result = (
Cq.Workplane('XY') Cq.Workplane('XY')
.box( .box(
self.parent_width, length=self.parent_width,
self.parent_width, width=self.parent_width,
self.parent_thickness, height=self.parent_thickness,
centered=(True, True, False)) centered=(True, True, False))
.translate((0, 0, -self.parent_thickness)) .translate((0, 0, -self.parent_thickness))
.edges("|Z") .edges("|Z")
@ -177,11 +188,11 @@ class RootJoint(Model):
result = ( result = (
Cq.Workplane('XY') Cq.Workplane('XY')
.box( .box(
self.child_height, length=self.child_height,
self.child_width, width=self.child_width,
self.hirth_joint.base_height, height=self.child_extra_thickness + self.hirth_joint.base_height,
centered=(True, True, False)) centered=(True, True, False))
#.translate((0, 0, -self.base_joint.base_height)) .translate((0, 0, -self.child_extra_thickness))
#.edges("|Z") #.edges("|Z")
#.fillet(self.hs_joint_corner_fillet) #.fillet(self.hs_joint_corner_fillet)
.faces(">Z") .faces(">Z")
@ -190,7 +201,7 @@ class RootJoint(Model):
.hole(self.corner_hole_diam) .hole(self.corner_hole_diam)
) )
# Creates a plane parallel to the holes but shifted to the base # 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): for i, (px, py) in enumerate(conn):
plane.moveTo(px, py).tagPlane(f"conn{i}") plane.moveTo(px, py).tagPlane(f"conn{i}")
@ -232,7 +243,7 @@ class RootJoint(Model):
result result
.addS(self.hex_nut.assembly(), name="hex_nut") .addS(self.hex_nut.assembly(), name="hex_nut")
.addS(self.knob.assembly(), name="knob", .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("knob/thread", "Fixed")
.constrain("hex_nut?bot", "parent?base", "Plane", param=0) .constrain("hex_nut?bot", "parent?base", "Plane", param=0)
.constrain("hex_nut?dirX", "parent@faces@>X", "Axis", param=0) .constrain("hex_nut?dirX", "parent@faces@>X", "Axis", param=0)
@ -1071,6 +1082,14 @@ class ElbowJoint(Model):
motion_span=self.motion_span 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 @property
def total_thickness(self): def total_thickness(self):
return self.disk_joint.total_thickness return self.disk_joint.total_thickness

View File

@ -63,6 +63,7 @@ class WingProfile(Model):
angle_neutral=10.0, angle_neutral=10.0,
actuator=LINEAR_ACTUATOR_50, actuator=LINEAR_ACTUATOR_50,
flexor_offset_angle=30, flexor_offset_angle=30,
parent_arm_width=15,
flip=False, flip=False,
)) ))
# Distance between the two spacers on the elbow, halved # 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: def surface_s0(self, top: bool = False) -> Cq.Workplane:
base_dx = -(self.base_width - self.root_joint.child_width) / 2 - 10 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 = ( #mid_spacer_loc = (
# Cq.Location.from2d(0, -self.shoulder_width/2) * # Cq.Location.from2d(0, -self.shoulder_width/2) *
# self.shoulder_axle_loc * # self.shoulder_axle_loc *
@ -593,6 +594,25 @@ class WingProfile(Model):
) )
return mbox 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) @target(name="profile-s1", kind=TargetKind.DXF)
def profile_s1(self) -> Cq.Sketch: def profile_s1(self) -> Cq.Sketch:
@ -615,23 +635,17 @@ class WingProfile(Model):
) )
return profile return profile
def surface_s1(self, front: bool = True) -> Cq.Workplane: 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", ("shoulder",
Cq.Location((0, self.shoulder_height / 2, 0)) * Cq.Location((0, self.shoulder_height / 2, 0)) *
self.shoulder_joint.child_lip_loc()), self.shoulder_joint.child_lip_loc()),
] ("elbow", self.elbow_axle_loc * loc_elbow),
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_act", self.elbow_axle_loc * rot_elbow * ("elbow_act", self.elbow_axle_loc * rot_elbow *
self.elbow_joint.actuator_mount_loc()), self.elbow_joint.actuator_mount_loc()),
] ]
profile = self.profile_s1() profile = self.profile_s1()
tags = tags_shoulder + tags_elbow
return extrude_with_markers( return extrude_with_markers(
profile, self.panel_thickness, tags, reverse=front) profile, self.panel_thickness, tags, reverse=front)
@submodel(name="spacer-s1-shoulder") @submodel(name="spacer-s1-shoulder")
@ -653,10 +667,9 @@ class WingProfile(Model):
) )
@submodel(name="spacer-s1-elbow") @submodel(name="spacer-s1-elbow")
def spacer_s1_elbow(self) -> MountingBox: def spacer_s1_elbow(self) -> MountingBox:
return self.spacer_of_joint( return self._spacer_from_disk_joint(
joint=self.elbow_joint, joint=self.elbow_joint,
segment_thickness=self.s1_thickness, segment_thickness=self.s1_thickness,
dx=self.elbow_h2,
) )
@submodel(name="spacer-s1-elbow-act") @submodel(name="spacer-s1-elbow-act")
def spacer_s1_elbow_act(self) -> MountingBox: def spacer_s1_elbow_act(self) -> MountingBox:
@ -683,16 +696,14 @@ class WingProfile(Model):
) )
for o, t in [ for o, t in [
(self.spacer_s1_shoulder(), "shoulder"), (self.spacer_s1_shoulder(), "shoulder"),
(self.spacer_s1_elbow(), "elbow_top"), (self.spacer_s1_elbow(), "elbow"),
(self.spacer_s1_elbow(), "elbow_bot"),
(self.spacer_s1_elbow_act(), "elbow_act"), (self.spacer_s1_elbow_act(), "elbow_act"),
]: ]:
is_top = t.endswith("_top")
self._assembly_insert_spacer( self._assembly_insert_spacer(
result, result,
o.generate(), o.generate(),
point_tag=t, point_tag=t,
flipped=not is_top, flipped=True,
) )
return result.solve() return result.solve()
@ -743,24 +754,21 @@ class WingProfile(Model):
return profile return profile
def surface_s2(self, front: bool = True) -> Cq.Workplane: def surface_s2(self, front: bool = True) -> Cq.Workplane:
loc_elbow = Cq.Location.rot2d(self.elbow_rotate) * self.elbow_joint.child_arm_loc() 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) rot_wrist = Cq.Location.rot2d(self.wrist_rotate)
loc_wrist = rot_wrist * self.wrist_joint.parent_arm_loc() 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 * ("wrist_bot", self.wrist_axle_loc * loc_wrist *
Cq.Location.from2d(0, -self.wrist_h2)), Cq.Location.from2d(0, -self.wrist_h2)),
("wrist_top", self.wrist_axle_loc * loc_wrist * ("wrist_top", self.wrist_axle_loc * loc_wrist *
Cq.Location.from2d(0, self.wrist_h2)), 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() profile = self.profile_s2()
tags = tags_elbow + tags_wrist
return extrude_with_markers(profile, self.panel_thickness, tags, reverse=front) return extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
def surface_s2_bridge(self, front: bool = True) -> Optional[Cq.Workplane]: def surface_s2_bridge(self, front: bool = True) -> Optional[Cq.Workplane]:
profile = self.profile_s2_bridge() profile = self.profile_s2_bridge()
@ -777,17 +785,15 @@ class WingProfile(Model):
profile, self.panel_thickness, tags, reverse=not front) profile, self.panel_thickness, tags, reverse=not front)
@submodel(name="spacer-s2-elbow") @submodel(name="spacer-s2-elbow")
def spacer_s2_elbow(self) -> MountingBox: def spacer_s2_elbow(self) -> MountingBox:
return self.spacer_of_joint( return self._spacer_from_disk_joint(
joint=self.elbow_joint, joint=self.elbow_joint,
segment_thickness=self.s2_thickness, segment_thickness=self.s2_thickness,
dx=self.elbow_h2,
) )
@submodel(name="spacer-s2-wrist") @submodel(name="spacer-s2-wrist")
def spacer_s2_wrist(self) -> MountingBox: def spacer_s2_wrist(self) -> MountingBox:
return self.spacer_of_joint( return self._spacer_from_disk_joint(
joint=self.wrist_joint, joint=self.wrist_joint,
segment_thickness=self.s2_thickness, segment_thickness=self.s2_thickness,
dx=self.wrist_h2,
) )
@submodel(name="spacer-s2-wrist-act") @submodel(name="spacer-s2-wrist-act")
def spacer_s2_wrist_act(self) -> MountingBox: def spacer_s2_wrist_act(self) -> MountingBox:
@ -831,20 +837,16 @@ class WingProfile(Model):
.constrain("back?wrist_top", "bridge_back?wrist_top", "Plane") .constrain("back?wrist_top", "bridge_back?wrist_top", "Plane")
) )
for o, t in [ for o, t in [
(self.spacer_s2_elbow(), "elbow_bot"), (self.spacer_s2_elbow(), "elbow"),
(self.spacer_s2_elbow(), "elbow_top"), (self.spacer_s2_wrist(), "wrist"),
(self.spacer_s2_wrist(), "wrist_bot"),
(self.spacer_s2_wrist(), "wrist_top"),
(self.spacer_s2_wrist_act(), "wrist_act"), (self.spacer_s2_wrist_act(), "wrist_act"),
]: ]:
is_top = t.endswith("_top")
is_parent = t.startswith("elbow") is_parent = t.startswith("elbow")
self._assembly_insert_spacer( self._assembly_insert_spacer(
result, result,
o.generate(), o.generate(),
point_tag=t, point_tag=t,
flipped=is_top == is_parent, flipped=is_parent,
#rotate=not is_parent,
) )
return result.solve() return result.solve()
@ -878,6 +880,7 @@ class WingProfile(Model):
front: bool = True) -> Cq.Workplane: front: bool = True) -> Cq.Workplane:
loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc() loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc()
tags = [ tags = [
("wrist", self.wrist_axle_loc * loc_wrist),
("wrist_bot", self.wrist_axle_loc * loc_wrist * ("wrist_bot", self.wrist_axle_loc * loc_wrist *
Cq.Location.from2d(0, self.wrist_h2)), Cq.Location.from2d(0, self.wrist_h2)),
("wrist_top", self.wrist_axle_loc * loc_wrist * ("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) return extrude_with_markers(profile, self.panel_thickness, tags, reverse=not front)
@submodel(name="spacer-s3-wrist") @submodel(name="spacer-s3-wrist")
def spacer_s3_wrist(self) -> MountingBox: def spacer_s3_wrist(self) -> MountingBox:
return self.spacer_of_joint( return self._spacer_from_disk_joint(
joint=self.wrist_joint, joint=self.wrist_joint,
segment_thickness=self.s3_thickness, segment_thickness=self.s3_thickness,
dx=self.wrist_h2,
) )
@assembly() @assembly()
def assembly_s3(self) -> Cq.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_bot", "extra_back?wrist_bot", "Plane")
.constrain("back?wrist_top", "extra_back?wrist_top", "Plane") .constrain("back?wrist_top", "extra_back?wrist_top", "Plane")
) )
for t in ["wrist_bot", "wrist_top"]: self._assembly_insert_spacer(
is_top = t.endswith("_top") result,
o = self.spacer_s3_wrist() self.spacer_s3_wrist().generate(),
self._assembly_insert_spacer( point_tag="wrist",
result, flipped=True,
o.generate(), )
point_tag=t,
flipped=is_top,
)
return result.solve() return result.solve()
@assembly() @assembly()
def assembly(self, def assembly(
parts: Optional[list[str]] = None, self,
shoulder_deflection: float = 0.0, parts: Optional[list[str]] = None,
elbow_wrist_deflection: float = 0.0, shoulder_deflection: float = 0.0,
root_offset: int = 5, elbow_wrist_deflection: float = 0.0,
fastener_pos: float = 0.0, root_offset: int = 5,
ignore_fasteners: bool = False, fastener_pos: float = 0.0,
ignore_electronics: bool = False, ignore_fasteners: bool = False,
ignore_actuators: bool = False, ignore_electronics: bool = False,
) -> Cq.Assembly(): ignore_actuators: bool = False,
) -> Cq.Assembly():
if parts is None: if parts is None:
parts = [ parts = [
"root", "root",
@ -1013,55 +1013,44 @@ class WingProfile(Model):
angle=angle, angle=angle,
ignore_actuators=ignore_actuators), name="elbow") ignore_actuators=ignore_actuators), name="elbow")
if "s1" in parts and "elbow" in parts: if "s1" in parts and "elbow" in parts:
( for _, tag in self.elbow_joint.hole_loc_tags():
result result.constrain(
.constrain("s1/elbow_top?conn0", "elbow/parent_upper/lip?conn_top0", "Plane") f"s1/elbow?{tag}",
.constrain("s1/elbow_top?conn1", "elbow/parent_upper/lip?conn_top1", "Plane") f"elbow/parent_upper/lip?{tag}", "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 not ignore_actuators: if not ignore_actuators:
( result.constrain(
result "elbow/bracket_back?conn_side",
.constrain("elbow/bracket_back?conn_side", "s1/elbow_act?conn0", "Plane") "s1/elbow_act?conn0",
) "Plane")
if "s2" in parts: if "s2" in parts:
result.add(self.assembly_s2(), name="s2") result.add(self.assembly_s2(), name="s2")
if "s2" in parts and "elbow" in parts: if "s2" in parts and "elbow" in parts:
( for _, tag in self.elbow_joint.hole_loc_tags():
result result.constrain(
.constrain("s2/elbow_top?conn0", "elbow/child/lip?conn_top0", "Plane") f"s2/elbow?{tag}",
.constrain("s2/elbow_top?conn1", "elbow/child/lip?conn_top1", "Plane") f"elbow/child/lip?{tag}", "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: if "wrist" in parts:
angle = self.wrist_joint.motion_span * elbow_wrist_deflection angle = self.wrist_joint.motion_span * elbow_wrist_deflection
result.add(self.wrist_joint.assembly( result.add(self.wrist_joint.assembly(
angle=angle, angle=angle,
ignore_actuators=ignore_actuators), name="wrist") ignore_actuators=ignore_actuators), name="wrist")
wrist_n_holes = len(self.wrist_joint.hole_pos)
if "s2" in parts and "wrist" in parts: if "s2" in parts and "wrist" in parts:
for i in range(wrist_n_holes): for _, tag in self.wrist_joint.hole_loc_tags():
( result.constrain(
result f"s2/wrist?{tag}",
.constrain(f"s2/wrist_top?conn{i}", f"wrist/parent_upper/lip?conn_top{i}", "Plane") f"wrist/parent_upper/lip?{tag}", "Plane")
.constrain(f"s2/wrist_bot?conn{i}", f"wrist/parent_upper/lip?conn_bot{i}", "Plane")
)
if "s3" in parts: if "s3" in parts:
result.add(self.assembly_s3(), name="s3") result.add(self.assembly_s3(), name="s3")
if "s3" in parts and "wrist" in parts: if "s3" in parts and "wrist" in parts:
for i in range(wrist_n_holes): for _, tag in self.wrist_joint.hole_loc_tags():
( result.constrain(
result f"s3/wrist?{tag}",
.constrain(f"s3/wrist_top?conn{i}", f"wrist/child/lip?conn_top{i}", "Plane") f"wrist/child/lip?{tag}", "Plane")
.constrain(f"s3/wrist_bot?conn{i}", f"wrist/child/lip?conn_bot{i}", "Plane")
)
if not ignore_actuators: if not ignore_actuators:
( result.constrain(
result "wrist/bracket_back?conn_side",
.constrain("wrist/bracket_back?conn_side", "s2/wrist_act?conn0", "Plane") "s2/wrist_act?conn0",
) "Plane")
if len(parts) > 1: if len(parts) > 1:
result.solve() result.solve()