feat: Bent elbow joint
This commit is contained in:
parent
9795f7b714
commit
4c5985fa08
|
@ -313,6 +313,9 @@ class Beam:
|
||||||
class DiskJoint(Model):
|
class DiskJoint(Model):
|
||||||
"""
|
"""
|
||||||
Sandwiched disk joint for the wrist and elbow
|
Sandwiched disk joint for the wrist and elbow
|
||||||
|
|
||||||
|
We embed a spring inside the joint, with one leg in the disk and one leg in
|
||||||
|
the housing. This provides torsion resistance.
|
||||||
"""
|
"""
|
||||||
spring: TorsionSpring = field(default_factory=lambda: TorsionSpring(
|
spring: TorsionSpring = field(default_factory=lambda: TorsionSpring(
|
||||||
radius=9 / 2,
|
radius=9 / 2,
|
||||||
|
@ -328,14 +331,15 @@ class DiskJoint(Model):
|
||||||
|
|
||||||
housing_thickness: float = 4.0
|
housing_thickness: float = 4.0
|
||||||
disk_thickness: float = 7.0
|
disk_thickness: float = 7.0
|
||||||
# Gap between disk and the housing
|
|
||||||
#disk_thickness_gap: float = 0.1
|
# Amount by which the wall carves in
|
||||||
|
wall_inset: float = 2.0
|
||||||
|
# Height of the spring hole; if you make it too short the spring can't enter
|
||||||
|
spring_tail_hole_height: float = 2.0
|
||||||
|
|
||||||
# Spring angle at 0 degrees of movement
|
# Spring angle at 0 degrees of movement
|
||||||
spring_angle_at_0: float = 60.0
|
spring_angle_at_0: float = 90.0
|
||||||
spring_slot_offset: float = 15.0
|
spring_slot_offset: float = 5.0
|
||||||
|
|
||||||
wall_inset: float = 2.0
|
|
||||||
|
|
||||||
# Angular span of movement
|
# Angular span of movement
|
||||||
movement_angle: float = 120.0
|
movement_angle: float = 120.0
|
||||||
|
@ -348,10 +352,11 @@ class DiskJoint(Model):
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__init__(name="disk-joint")
|
super().__init__(name="disk-joint")
|
||||||
assert self.housing_thickness > self.wall_inset
|
assert self.radius_housing > self.radius_disk > self.radius_axle
|
||||||
assert self.radius_housing > self.radius_disk
|
assert self.spring.height < self.housing_thickness + self.disk_thickness
|
||||||
assert self.radius_disk > self.radius_axle
|
|
||||||
assert self.housing_upper_carve_offset > 0
|
assert self.housing_upper_carve_offset > 0
|
||||||
|
assert self.spring_tail_hole_height > self.spring.thickness
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def neutral_movement_angle(self) -> Optional[float]:
|
def neutral_movement_angle(self) -> Optional[float]:
|
||||||
|
@ -363,6 +368,12 @@ class DiskJoint(Model):
|
||||||
@property
|
@property
|
||||||
def total_thickness(self) -> float:
|
def total_thickness(self) -> float:
|
||||||
return self.housing_thickness * 2 + self.disk_thickness
|
return self.housing_thickness * 2 + self.disk_thickness
|
||||||
|
@property
|
||||||
|
def disk_bot_thickness(self) -> float:
|
||||||
|
"""
|
||||||
|
Pads the bottom of the disk up to spring height
|
||||||
|
"""
|
||||||
|
return max(0, self.disk_thickness + self.spring.thickness - self.spring.height)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def opening_span(self) -> float:
|
def opening_span(self) -> float:
|
||||||
|
@ -373,7 +384,7 @@ class DiskJoint(Model):
|
||||||
"""
|
"""
|
||||||
Distance between the spring track and the outside of the upper housing
|
Distance between the spring track and the outside of the upper housing
|
||||||
"""
|
"""
|
||||||
return self.housing_thickness + self.disk_thickness - self.spring.height
|
return self.spring_tail_hole_height + (self.disk_thickness - self.disk_bot_thickness) - self.spring.height
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def housing_upper_dz(self) -> float:
|
def housing_upper_dz(self) -> float:
|
||||||
|
@ -387,9 +398,9 @@ class DiskJoint(Model):
|
||||||
Cq.Solid.makeBox(
|
Cq.Solid.makeBox(
|
||||||
length=self.spring.tail_length,
|
length=self.spring.tail_length,
|
||||||
width=self.spring.thickness,
|
width=self.spring.thickness,
|
||||||
height=self.disk_thickness,
|
height=self.spring.height-self.disk_bot_thickness,
|
||||||
)
|
)
|
||||||
.located(Cq.Location((0, self.spring.radius_inner, 0)))
|
.located(Cq.Location((0, self.spring.radius_inner, self.disk_bot_thickness)))
|
||||||
.rotate((0, 0, 0), (0, 0, 1), self.spring_slot_offset)
|
.rotate((0, 0, 0), (0, 0, 1), self.spring_slot_offset)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -427,6 +438,7 @@ class DiskJoint(Model):
|
||||||
theta = math.radians(self.spring_slot_offset)
|
theta = math.radians(self.spring_slot_offset)
|
||||||
plane.tagPlane("dir", direction=(math.cos(theta), math.sin(theta), 0))
|
plane.tagPlane("dir", direction=(math.cos(theta), math.sin(theta), 0))
|
||||||
plane.workplane(offset=self.disk_thickness).tagPlane("mate_top")
|
plane.workplane(offset=self.disk_thickness).tagPlane("mate_top")
|
||||||
|
plane.workplane(offset=self.disk_bot_thickness).tagPlane("mate_spring")
|
||||||
result.copyWorkplane(Cq.Workplane('YX')).tagPlane("mate_bot")
|
result.copyWorkplane(Cq.Workplane('YX')).tagPlane("mate_bot")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -474,11 +486,11 @@ class DiskJoint(Model):
|
||||||
carve = (
|
carve = (
|
||||||
Cq.Solid.makeCylinder(
|
Cq.Solid.makeCylinder(
|
||||||
radius=self.spring.radius,
|
radius=self.spring.radius,
|
||||||
height=self.housing_thickness
|
height=self.spring_tail_hole_height,
|
||||||
).fuse(Cq.Solid.makeBox(
|
).fuse(Cq.Solid.makeBox(
|
||||||
length=self.spring.tail_length,
|
length=self.spring.tail_length,
|
||||||
width=self.spring.thickness,
|
width=self.spring.thickness,
|
||||||
height=self.housing_thickness
|
height=self.spring_tail_hole_height,
|
||||||
).located(Cq.Location((0, -self.spring.radius, 0))))
|
).located(Cq.Location((0, -self.spring.radius, 0))))
|
||||||
).rotate((0, 0, 0), (0, 0, 1), carve_angle)
|
).rotate((0, 0, 0), (0, 0, 1), carve_angle)
|
||||||
result = (
|
result = (
|
||||||
|
@ -515,6 +527,7 @@ class DiskJoint(Model):
|
||||||
result = (
|
result = (
|
||||||
result
|
result
|
||||||
.union(wall, tol=TOL)
|
.union(wall, tol=TOL)
|
||||||
|
#.cut(carve)
|
||||||
.cut(carve.located(Cq.Location((0, 0, -self.housing_upper_carve_offset))))
|
.cut(carve.located(Cq.Location((0, 0, -self.housing_upper_carve_offset))))
|
||||||
)
|
)
|
||||||
return result.clean()
|
return result.clean()
|
||||||
|
@ -541,7 +554,7 @@ class DiskJoint(Model):
|
||||||
.constrain(f"{housing_lower}?dirX", f"{housing_upper}?dirX", "Axis", param=0)
|
.constrain(f"{housing_lower}?dirX", f"{housing_upper}?dirX", "Axis", param=0)
|
||||||
.constrain(f"{housing_upper}?dir", f"{spring_name}?dir_top", "Axis", param=0)
|
.constrain(f"{housing_upper}?dir", f"{spring_name}?dir_top", "Axis", param=0)
|
||||||
.constrain(f"{spring_name}?dir_bot", f"{disk}?dir", "Axis", param=0)
|
.constrain(f"{spring_name}?dir_bot", f"{disk}?dir", "Axis", param=0)
|
||||||
.constrain(f"{disk}?mate_bot", f"{spring_name}?bot", "Plane", param=0)
|
.constrain(f"{disk}?mate_spring", f"{spring_name}?bot", "Plane")
|
||||||
#.constrain(f"{housing_lower}?dirX", f"{housing_upper}?dir", "Axis", param=0)
|
#.constrain(f"{housing_lower}?dirX", f"{housing_upper}?dir", "Axis", param=0)
|
||||||
#.constrain(f"{housing_lower}?dirX", f"{disk}?dir", "Axis", param=angle)
|
#.constrain(f"{housing_lower}?dirX", f"{disk}?dir", "Axis", param=angle)
|
||||||
#.constrain(f"{housing_lower}?dirY", f"{disk}?dir", "Axis", param=angle - 90)
|
#.constrain(f"{housing_lower}?dirY", f"{disk}?dir", "Axis", param=angle - 90)
|
||||||
|
@ -608,13 +621,26 @@ class ElbowJoint(Model):
|
||||||
|
|
||||||
material: Material = Material.RESIN_TRANSPERENT
|
material: Material = Material.RESIN_TRANSPERENT
|
||||||
|
|
||||||
angle_neutral: float = 0.0
|
angle_neutral: float = 30.0
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
assert self.child_arm_radius > self.disk_joint.radius_housing
|
assert self.child_arm_radius > self.disk_joint.radius_housing
|
||||||
assert self.parent_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 - self.lip_thickness / 2
|
self.disk_joint.tongue_length = self.child_arm_radius - self.disk_joint.radius_disk - self.lip_thickness / 2
|
||||||
|
|
||||||
|
def parent_arm_loc(self) -> Cq.Location:
|
||||||
|
"""
|
||||||
|
2d Location of the centre of the arm surface on the parent side, assuming
|
||||||
|
axle is at position 0, and parent direction is -X
|
||||||
|
"""
|
||||||
|
return Cq.Location.from2d(-self.parent_arm_radius, 0, 0)
|
||||||
|
def child_arm_loc(self) -> Cq.Location:
|
||||||
|
"""
|
||||||
|
2d Location of the centre of the arm surface on the child side, assuming
|
||||||
|
axle is at position 0, and parent direction is -X
|
||||||
|
"""
|
||||||
|
return Cq.Location.rot2d(self.angle_neutral) * Cq.Location.from2d(self.child_arm_radius, 0, 180)
|
||||||
|
|
||||||
def lip(self) -> Cq.Workplane:
|
def lip(self) -> Cq.Workplane:
|
||||||
holes = [
|
holes = [
|
||||||
h
|
h
|
||||||
|
@ -649,8 +675,9 @@ class ElbowJoint(Model):
|
||||||
Cq.Location((0, 0, 0), (0, 1, 0), 90)
|
Cq.Location((0, 0, 0), (0, 1, 0), 90)
|
||||||
)
|
)
|
||||||
loc_disk = flip_x * flip_z * Cq.Location((-self.child_arm_radius, 0, -dz), (0, 0, 1), angle)
|
loc_disk = flip_x * flip_z * Cq.Location((-self.child_arm_radius, 0, -dz), (0, 0, 1), angle)
|
||||||
|
loc_cut_rel = Cq.Location((0, self.disk_joint.spring.radius_inner, -self.disk_joint.disk_bot_thickness))
|
||||||
disk_cut = self.disk_joint._disk_cut().located(
|
disk_cut = self.disk_joint._disk_cut().located(
|
||||||
loc_lip.inverse * Cq.Location((0, self.disk_joint.spring.radius_inner, 0)) * loc_disk)
|
loc_lip.inverse * loc_cut_rel * loc_disk)
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
.add(self.lip().cut(disk_cut), name="lip", loc=loc_lip)
|
.add(self.lip().cut(disk_cut), name="lip", loc=loc_lip)
|
||||||
|
|
|
@ -53,6 +53,7 @@ class WingProfile(Model):
|
||||||
movement_angle=55,
|
movement_angle=55,
|
||||||
),
|
),
|
||||||
hole_diam=6.0,
|
hole_diam=6.0,
|
||||||
|
angle_neutral=15.0,
|
||||||
))
|
))
|
||||||
# Distance between the two spacers on the elbow, halved
|
# Distance between the two spacers on the elbow, halved
|
||||||
elbow_h2: float = 5.0
|
elbow_h2: float = 5.0
|
||||||
|
@ -70,6 +71,7 @@ class WingProfile(Model):
|
||||||
child_arm_radius=23.0,
|
child_arm_radius=23.0,
|
||||||
parent_arm_radius=30.0,
|
parent_arm_radius=30.0,
|
||||||
hole_diam=4.0,
|
hole_diam=4.0,
|
||||||
|
angle_neutral=30.0,
|
||||||
))
|
))
|
||||||
# Distance between the two spacers on the elbow, halved
|
# Distance between the two spacers on the elbow, halved
|
||||||
wrist_h2: float = 5.0
|
wrist_h2: float = 5.0
|
||||||
|
@ -86,6 +88,8 @@ class WingProfile(Model):
|
||||||
elbow_height: float
|
elbow_height: float
|
||||||
wrist_bot_loc: Cq.Location
|
wrist_bot_loc: Cq.Location
|
||||||
wrist_height: float
|
wrist_height: float
|
||||||
|
elbow_rotate: float = -5
|
||||||
|
wrist_rotate: float = 30.0
|
||||||
|
|
||||||
flip: bool = False
|
flip: bool = False
|
||||||
|
|
||||||
|
@ -94,6 +98,8 @@ class WingProfile(Model):
|
||||||
|
|
||||||
self.elbow_top_loc = self.elbow_bot_loc * Cq.Location.from2d(0, self.elbow_height)
|
self.elbow_top_loc = self.elbow_bot_loc * Cq.Location.from2d(0, self.elbow_height)
|
||||||
self.wrist_top_loc = self.wrist_bot_loc * Cq.Location.from2d(0, self.wrist_height)
|
self.wrist_top_loc = self.wrist_bot_loc * Cq.Location.from2d(0, self.wrist_height)
|
||||||
|
self.elbow_axle_loc = self.elbow_bot_loc * Cq.Location.from2d(0, self.elbow_height / 2)
|
||||||
|
self.wrist_axle_loc = self.wrist_bot_loc * Cq.Location.from2d(0, self.wrist_height / 2)
|
||||||
|
|
||||||
self.shoulder_joint.angle_neutral = -self.shoulder_angle_neutral - self.shoulder_angle_bias
|
self.shoulder_joint.angle_neutral = -self.shoulder_angle_neutral - self.shoulder_angle_bias
|
||||||
|
|
||||||
|
@ -439,13 +445,12 @@ class WingProfile(Model):
|
||||||
("shoulder_top", Cq.Location.from2d(0, h + shoulder_h, 270)),
|
("shoulder_top", Cq.Location.from2d(0, h + shoulder_h, 270)),
|
||||||
]
|
]
|
||||||
h = self.elbow_height / 2
|
h = self.elbow_height / 2
|
||||||
|
loc_elbow = Cq.Location.rot2d(self.elbow_rotate) * self.elbow_joint.parent_arm_loc()
|
||||||
tags_elbow = [
|
tags_elbow = [
|
||||||
("elbow_bot", self.elbow_bot_loc * Cq.Location.from2d(
|
("elbow_bot", self.elbow_axle_loc * loc_elbow *\
|
||||||
-self.elbow_joint.parent_arm_radius,
|
Cq.Location.from2d(0, -self.elbow_h2)),
|
||||||
h - self.elbow_h2)),
|
("elbow_top", self.elbow_axle_loc * loc_elbow *\
|
||||||
("elbow_top", self.elbow_bot_loc * Cq.Location.from2d(
|
Cq.Location.from2d(0, self.elbow_h2)),
|
||||||
-self.elbow_joint.parent_arm_radius,
|
|
||||||
h + self.elbow_h2)),
|
|
||||||
]
|
]
|
||||||
profile = self.profile_s1()
|
profile = self.profile_s1()
|
||||||
tags = tags_shoulder + tags_elbow
|
tags = tags_shoulder + tags_elbow
|
||||||
|
@ -507,24 +512,20 @@ 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:
|
||||||
h = self.elbow_height / 2
|
h = self.elbow_height / 2
|
||||||
|
loc_elbow = Cq.Location.rot2d(self.elbow_rotate) * self.elbow_joint.child_arm_loc()
|
||||||
tags_elbow = [
|
tags_elbow = [
|
||||||
("elbow_bot", self.elbow_bot_loc * Cq.Location.from2d(
|
("elbow_bot", self.elbow_axle_loc * loc_elbow *\
|
||||||
self.elbow_joint.child_arm_radius,
|
Cq.Location.from2d(0, self.elbow_h2)),
|
||||||
h - self.elbow_h2,
|
("elbow_top", self.elbow_axle_loc * loc_elbow *\
|
||||||
180)),
|
Cq.Location.from2d(0, -self.elbow_h2)),
|
||||||
("elbow_top", self.elbow_bot_loc * Cq.Location.from2d(
|
|
||||||
self.elbow_joint.child_arm_radius,
|
|
||||||
h + self.elbow_h2,
|
|
||||||
180)),
|
|
||||||
]
|
]
|
||||||
h = self.wrist_height / 2
|
h = self.wrist_height / 2
|
||||||
|
loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.parent_arm_loc().flip_y()
|
||||||
tags_wrist = [
|
tags_wrist = [
|
||||||
("wrist_bot", self.wrist_bot_loc * Cq.Location.from2d(
|
("wrist_bot", self.wrist_axle_loc * loc_wrist *\
|
||||||
-self.wrist_joint.parent_arm_radius,
|
Cq.Location.from2d(0, -self.wrist_h2)),
|
||||||
h - self.wrist_h2)),
|
("wrist_top", self.wrist_axle_loc * loc_wrist *\
|
||||||
("wrist_top", self.wrist_bot_loc * Cq.Location.from2d(
|
Cq.Location.from2d(0, self.wrist_h2)),
|
||||||
-self.wrist_joint.parent_arm_radius,
|
|
||||||
h + self.wrist_h2)),
|
|
||||||
]
|
]
|
||||||
profile = self.profile_s2()
|
profile = self.profile_s2()
|
||||||
tags = tags_elbow + tags_wrist
|
tags = tags_elbow + tags_wrist
|
||||||
|
@ -564,7 +565,7 @@ class WingProfile(Model):
|
||||||
o.generate(),
|
o.generate(),
|
||||||
point_tag=t,
|
point_tag=t,
|
||||||
flipped=is_top == is_parent,
|
flipped=is_top == is_parent,
|
||||||
#rotate=True,
|
#rotate=not is_parent,
|
||||||
)
|
)
|
||||||
return result.solve()
|
return result.solve()
|
||||||
|
|
||||||
|
@ -579,15 +580,12 @@ class WingProfile(Model):
|
||||||
def surface_s3(self,
|
def surface_s3(self,
|
||||||
front: bool = True) -> Cq.Workplane:
|
front: bool = True) -> Cq.Workplane:
|
||||||
h = self.wrist_height / 2
|
h = self.wrist_height / 2
|
||||||
|
loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc().flip_y()
|
||||||
tags = [
|
tags = [
|
||||||
("wrist_bot", self.wrist_bot_loc * Cq.Location.from2d(
|
("wrist_bot", self.wrist_axle_loc * loc_wrist *\
|
||||||
self.wrist_joint.child_arm_radius,
|
Cq.Location.from2d(0, self.wrist_h2)),
|
||||||
h - self.wrist_h2,
|
("wrist_top", self.wrist_axle_loc * loc_wrist *\
|
||||||
180)),
|
Cq.Location.from2d(0, -self.wrist_h2)),
|
||||||
("wrist_top", self.wrist_bot_loc * Cq.Location.from2d(
|
|
||||||
self.wrist_joint.child_arm_radius,
|
|
||||||
h + self.wrist_h2,
|
|
||||||
180)),
|
|
||||||
]
|
]
|
||||||
profile = self.profile_s3()
|
profile = self.profile_s3()
|
||||||
return extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
return extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
||||||
|
|
|
@ -82,6 +82,15 @@ def with_angle_2d(self: Cq.Location, angle: float) -> Tuple[float, float]:
|
||||||
return Cq.Location.from2d(x, y, angle)
|
return Cq.Location.from2d(x, y, angle)
|
||||||
Cq.Location.with_angle_2d = with_angle_2d
|
Cq.Location.with_angle_2d = with_angle_2d
|
||||||
|
|
||||||
|
def flip_x(self: Cq.Location) -> Cq.Location:
|
||||||
|
(x, y), a = self.to2d()
|
||||||
|
return Cq.Location.from2d(-x, y, 90 - a)
|
||||||
|
Cq.Location.flip_x = flip_x
|
||||||
|
def flip_y(self: Cq.Location) -> Cq.Location:
|
||||||
|
(x, y), a = self.to2d()
|
||||||
|
return Cq.Location.from2d(x, -y, -a)
|
||||||
|
Cq.Location.flip_y = flip_y
|
||||||
|
|
||||||
### Tags
|
### Tags
|
||||||
|
|
||||||
def tagPoint(self, tag: str):
|
def tagPoint(self, tag: str):
|
||||||
|
|
Loading…
Reference in New Issue