feat: Stable positioning of actuators

This commit is contained in:
Leni Aniva 2024-07-23 19:13:06 -07:00
parent ac509a1625
commit 656a2ae5bb
Signed by: aniva
GPG Key ID: 4D9B1C8D10EA4C50
3 changed files with 109 additions and 52 deletions

View File

@ -59,6 +59,7 @@ class MountingBox(Model):
generate_reverse_tags: bool = False generate_reverse_tags: bool = False
centre_bot_top_tags: bool = False centre_bot_top_tags: bool = False
centre_left_right_tags: bool = False
# Determines the position of side tags # Determines the position of side tags
flip_y: bool = False flip_y: bool = False
@ -103,8 +104,12 @@ class MountingBox(Model):
reverse_plane.moveTo(hole.x, hole.y).tagPlane(hole.rev_tag, '-Z') reverse_plane.moveTo(hole.x, hole.y).tagPlane(hole.rev_tag, '-Z')
if self.generate_side_tags: if self.generate_side_tags:
result.faces("<Y").workplane(origin=result.vertices("<X and <Y and >Z").val().Center()).tagPlane("left") if self.centre_left_right_tags:
result.faces(">Y").workplane(origin=result.vertices("<X and >Y and >Z").val().Center()).tagPlane("right") result.faces("<Y").workplane(origin=result.edges("<Y and >Z").val().Center()).tagPlane("left")
result.faces(">Y").workplane(origin=result.edges(">Y and >Z").val().Center()).tagPlane("right")
else:
result.faces("<Y").workplane(origin=result.vertices("<X and <Y and >Z").val().Center()).tagPlane("left")
result.faces(">Y").workplane(origin=result.vertices("<X and >Y and >Z").val().Center()).tagPlane("right")
c_y = ">Y" if self.flip_y else "<Y" c_y = ">Y" if self.flip_y else "<Y"
if self.centre_bot_top_tags: if self.centre_bot_top_tags:

View File

@ -944,6 +944,8 @@ class ElbowJoint(Model):
material: Material = Material.RESIN_TRANSPERENT material: Material = Material.RESIN_TRANSPERENT
# If set to true, the joint is flipped upside down.
flip: bool = False
angle_neutral: float = 30.0 angle_neutral: float = 30.0
actuator: Optional[LinearActuator] actuator: Optional[LinearActuator]
@ -951,7 +953,7 @@ class ElbowJoint(Model):
# Rotates the entire flexor # Rotates the entire flexor
flexor_offset_angle: float = 0 flexor_offset_angle: float = 0
# Rotates the surface of the mount # Rotates the surface of the mount
flexor_mount_rot: float = 90.0 flexor_mount_rot: float = 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
@ -977,15 +979,13 @@ class ElbowJoint(Model):
axle is at position 0, and parent direction is -X axle is at position 0, and parent direction is -X
""" """
return Cq.Location.from2d(-self.parent_arm_radius, 0, 0) return Cq.Location.from2d(-self.parent_arm_radius, 0, 0)
def child_arm_loc(self, flip: bool = False, angle: float = 0.0) -> Cq.Location: def child_arm_loc(self, angle: float = 0.0) -> Cq.Location:
""" """
2d Location of the centre of the arm surface on the child side, assuming 2d Location of the centre of the arm surface on the child side, assuming
axle is at position 0, and parent direction is -X axle is at position 0, and parent direction is -X
Set `flip=True` to indicate that the joint is supposed to be installed upside down
""" """
result = Cq.Location.rot2d(self.angle_neutral + angle) * Cq.Location.from2d(self.child_arm_radius, 0, 180) result = Cq.Location.rot2d(self.angle_neutral + angle) * Cq.Location.from2d(self.child_arm_radius, 0, 180)
return result.flip_y() if flip else result return result.flip_y() if self.flip else result
def actuator_mount(self) -> Cq.Workplane: def actuator_mount(self) -> Cq.Workplane:
holes = [ holes = [
Hole(x=0, y=0, tag="mount"), Hole(x=0, y=0, tag="mount"),
@ -1000,26 +1000,31 @@ class ElbowJoint(Model):
generate_side_tags=False, generate_side_tags=False,
) )
return mbox.generate() return mbox.generate()
def actuator_mount_loc(self, child: bool) -> Cq.Location: def actuator_mount_loc(
# Orientes the hole surface so it faces +X self,
loc_thickness = Cq.Location((-self.lip_thickness, 0, 0), (0, 1, 0), 90) child: bool = False,
# If set to true, use the local coordinate system
unflip: bool = False,
) -> Cq.Location:
# Moves the hole so the axle of the mount is perpendicular to it # Moves the hole so the axle of the mount is perpendicular to it
loc_mount = Cq.Location.from2d(self.flexor.mount_height, 0) * Cq.Location.rot2d(180) loc_mount = Cq.Location.from2d(self.flexor.mount_height, 0) * Cq.Location.rot2d(180)
loc_mount_orient = Cq.Location.rot2d(self.flexor_mount_rot * (-1 if child else 1)) loc_mount_orient = Cq.Location.rot2d(self.flexor_mount_rot * (-1 if child else 1))
# Moves the hole to be some distance apart from 0 # Moves the hole to be some distance apart from 0
mount_r, mount_loc_angle, mount_parent_r = self.flexor.open_pos() mount_r, mount_loc_angle, mount_parent_r = self.flexor.open_pos()
loc_span = Cq.Location.from2d(mount_r if child else mount_parent_r, 0) loc_span = Cq.Location.from2d(mount_r if child else mount_parent_r, 0)
r = (-mount_loc_angle if child else 0) + 180 r = (-mount_loc_angle - self.angle_neutral if child else 0) + 180 + self.flexor_offset_angle
loc_rot = Cq.Location.rot2d(r + self.flexor_offset_angle) loc_rot = Cq.Location.rot2d(r)
return loc_rot * loc_span * loc_mount_orient * loc_mount * loc_thickness loc = loc_rot * loc_span * loc_mount_orient * loc_mount
return loc.flip_y() if self.flip and not child and not unflip else loc
def lip(self) -> Cq.Workplane: def lip(self) -> Cq.Workplane:
sign = -1 if self.flip else 1
holes = [ holes = [
h h
for i, x in enumerate(self.hole_pos) for i, x in enumerate(self.hole_pos)
for h in [ for h in [
Hole(x=x, tag=f"conn_top{i}"), Hole(x=sign * x, tag=f"conn_top{i}"),
Hole(x=-x, tag=f"conn_bot{i}") Hole(x=-sign * x, tag=f"conn_bot{i}")
] ]
] ]
mbox = MountingBox( mbox = MountingBox(
@ -1047,17 +1052,22 @@ class ElbowJoint(Model):
Cq.Location((-lip_dz, 0, 0), (1, 0, 0), 90) * Cq.Location((-lip_dz, 0, 0), (1, 0, 0), 90) *
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, 0), (0, 0, 1), angle) loc_disk = flip_x * flip_z * Cq.Location((-self.child_arm_radius, 0, 0))
loc_cut_rel = Cq.Location((0, self.disk_joint.spring.radius_inner, -self.disk_joint.disk_bot_thickness)) 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 * loc_cut_rel * loc_disk) loc_lip.inverse * loc_cut_rel * loc_disk)
result = ( result = (
Cq.Assembly() Cq.Assembly()
.add(self.disk_joint.disk(), name="disk", loc=Cq.Location((0, 0, -dz))) .add(self.disk_joint.disk(), name="disk", loc=Cq.Location((0, 0, -dz), (0,0,1), angle))
.add(self.lip().cut(disk_cut), name="lip", loc=loc_disk.inverse * loc_lip) .add(self.lip().cut(disk_cut), name="lip", loc=loc_disk.inverse * loc_lip)
) )
# Orientes the hole surface so it faces +X
loc_thickness = Cq.Location((-self.lip_thickness, 0, 0), (0, 1, 0), 90)
if self.flexor: if self.flexor:
result.add(self.actuator_mount(), name="act", loc=self.actuator_mount_loc(child=True)) result.add(
self.actuator_mount(),
name="act",
loc=self.actuator_mount_loc(child=True) * loc_thickness)
return result return result
@target(name="parent-lower") @target(name="parent-lower")
@ -1090,31 +1100,32 @@ class ElbowJoint(Model):
) )
housing = self.disk_joint.housing_upper() housing = self.disk_joint.housing_upper()
housing_loc = Cq.Location( housing_loc = Cq.Location(
(0, 0, 0), (0, 0, housing_dz),
(0, 0, 1), (0, 0, 1),
-self.disk_joint.tongue_span / 2 + self.angle_neutral -self.disk_joint.tongue_span / 2 + self.angle_neutral
) )
lip_dz = self.lip_thickness lip_dz = self.lip_thickness
loc_net_housing = axial_offset * housing_loc
result = ( result = (
Cq.Assembly() Cq.Assembly()
.add(housing, name="housing", loc=Cq.Location((0, 0, housing_dz))) # rotate so 0 degree is at +X
.add(housing, name="housing", loc=housing_loc)
.add(self.lip(), name="lip", loc= .add(self.lip(), name="lip", loc=
loc_net_housing.inverse * axial_offset.inverse *
Cq.Location((0, 0, 0), (0, 1, 0), 180) * Cq.Location((0, 0, 0), (0, 1, 0), 180) *
Cq.Location((-lip_dz, 0, 0), (1, 0, 0), 90) * Cq.Location((-lip_dz, 0, 0), (1, 0, 0), 90) *
Cq.Location((0, 0, 0), (0, 1, 0), 90)) Cq.Location((0, 0, 0), (0, 1, 0), 90))
.add(connector, name="connector", .add(connector, name="connector")
loc=loc_net_housing.inverse * axial_offset)
#.constrain("housing", "Fixed") #.constrain("housing", "Fixed")
#.constrain("connector", "Fixed") #.constrain("connector", "Fixed")
#.solve() #.solve()
) )
if self.flexor: if self.flexor:
# Orientes the hole surface so it faces +X
loc_thickness = Cq.Location((-self.lip_thickness, 0, 0), (0, 1, 0), 90)
result.add( result.add(
self.actuator_mount(), self.actuator_mount(),
name="act", name="act",
loc=self.actuator_mount_loc(child=False)) loc=self.actuator_mount_loc(child=False) * loc_thickness)
return result return result
@assembly() @assembly()

View File

@ -14,7 +14,7 @@ from nhf.parts.box import box_with_centre_holes, MountingBox, Hole
from nhf.parts.joints import HirthJoint from nhf.parts.joints import HirthJoint
from nhf.parts.planar import extrude_with_markers from nhf.parts.planar import extrude_with_markers
from nhf.touhou.houjuu_nue.joints import RootJoint, ShoulderJoint, ElbowJoint, DiskJoint from nhf.touhou.houjuu_nue.joints import RootJoint, ShoulderJoint, ElbowJoint, DiskJoint
from nhf.touhou.houjuu_nue.electronics import LINEAR_ACTUATOR_10, LINEAR_ACTUATOR_50, ElectronicBoard from nhf.touhou.houjuu_nue.electronics import LINEAR_ACTUATOR_21, LINEAR_ACTUATOR_50, ElectronicBoard
import nhf.utils import nhf.utils
@dataclass(kw_only=True) @dataclass(kw_only=True)
@ -57,7 +57,8 @@ class WingProfile(Model):
hole_diam=4.0, hole_diam=4.0,
angle_neutral=15.0, angle_neutral=15.0,
actuator=LINEAR_ACTUATOR_50, actuator=LINEAR_ACTUATOR_50,
flexor_offset_angle=-15, flexor_offset_angle=0,
flip=False,
)) ))
# 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
@ -74,7 +75,9 @@ class WingProfile(Model):
parent_arm_radius=30.0, parent_arm_radius=30.0,
hole_diam=4.0, hole_diam=4.0,
angle_neutral=-30.0, angle_neutral=-30.0,
actuator=LINEAR_ACTUATOR_10, actuator=LINEAR_ACTUATOR_21,
flexor_offset_angle=0,
flip=True,
)) ))
# 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
@ -608,12 +611,15 @@ 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() rot_elbow = Cq.Location.rot2d(self.elbow_rotate)
loc_elbow = rot_elbow * self.elbow_joint.parent_arm_loc()
tags_elbow = [ tags_elbow = [
("elbow_bot", self.elbow_axle_loc * loc_elbow * ("elbow_bot", self.elbow_axle_loc * loc_elbow *
Cq.Location.from2d(0, -self.elbow_h2)), Cq.Location.from2d(0, -self.elbow_h2)),
("elbow_top", self.elbow_axle_loc * loc_elbow * ("elbow_top", self.elbow_axle_loc * loc_elbow *
Cq.Location.from2d(0, self.elbow_h2)), Cq.Location.from2d(0, self.elbow_h2)),
("elbow_act", self.elbow_axle_loc * rot_elbow *
self.elbow_joint.actuator_mount_loc()),
] ]
profile = self.profile_s1() profile = self.profile_s1()
tags = tags_shoulder + tags_elbow tags = tags_shoulder + tags_elbow
@ -639,6 +645,17 @@ class WingProfile(Model):
segment_thickness=self.s1_thickness, segment_thickness=self.s1_thickness,
dx=self.elbow_h2, dx=self.elbow_h2,
) )
@submodel(name="spacer-s1-elbow-act")
def spacer_s1_elbow_act(self) -> MountingBox:
return MountingBox(
length=self.s1_thickness,
width=self.s1_thickness,
thickness=self.spacer_thickness,
holes=[Hole(x=0,y=0)],
centred=(True, True),
hole_diam=self.elbow_joint.hole_diam,
centre_left_right_tags=True,
)
@assembly() @assembly()
def assembly_s1(self) -> Cq.Assembly: def assembly_s1(self) -> Cq.Assembly:
result = ( result = (
@ -651,13 +668,17 @@ class WingProfile(Model):
.constrain("front@faces@>Z", "back@faces@<Z", "Point", .constrain("front@faces@>Z", "back@faces@<Z", "Point",
param=self.s1_thickness) param=self.s1_thickness)
) )
for t in ["shoulder_bot", "shoulder_top", "elbow_bot", "elbow_top"]: for o, t in [
(self.spacer_s1_shoulder(), "shoulder_bot"),
(self.spacer_s1_shoulder(), "shoulder_top"),
(self.spacer_s1_elbow(), "elbow_top"),
(self.spacer_s1_elbow(), "elbow_bot"),
(self.spacer_s1_elbow_act(), "elbow_act"),
]:
is_top = t.endswith("_top") is_top = t.endswith("_top")
is_parent = t.startswith("shoulder")
o = self.spacer_s1_shoulder().generate() if is_parent else self.spacer_s1_elbow().generate()
self._assembly_insert_spacer( self._assembly_insert_spacer(
result, result,
o, o.generate(),
point_tag=t, point_tag=t,
flipped=not is_top, flipped=not is_top,
) )
@ -683,19 +704,22 @@ 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(flip=self.flip) loc_elbow = Cq.Location.rot2d(self.elbow_rotate) * self.elbow_joint.child_arm_loc()
tags_elbow = [ tags_elbow = [
("elbow_bot", self.elbow_axle_loc * loc_elbow * ("elbow_bot", self.elbow_axle_loc * loc_elbow *
Cq.Location.from2d(0, self.elbow_h2)), Cq.Location.from2d(0, self.elbow_h2)),
("elbow_top", self.elbow_axle_loc * loc_elbow * ("elbow_top", self.elbow_axle_loc * loc_elbow *
Cq.Location.from2d(0, -self.elbow_h2)), Cq.Location.from2d(0, -self.elbow_h2)),
] ]
loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.parent_arm_loc() rot_wrist = Cq.Location.rot2d(self.wrist_rotate)
loc_wrist = rot_wrist * self.wrist_joint.parent_arm_loc()
tags_wrist = [ tags_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 *
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 tags = tags_elbow + tags_wrist
@ -750,6 +774,17 @@ class WingProfile(Model):
segment_thickness=self.s2_thickness, segment_thickness=self.s2_thickness,
dx=self.wrist_h2, dx=self.wrist_h2,
) )
@submodel(name="spacer-s2-wrist-act")
def spacer_s2_wrist_act(self) -> MountingBox:
return MountingBox(
length=self.s2_thickness,
width=self.s2_thickness,
thickness=self.spacer_thickness,
holes=[Hole(x=0,y=0)],
centred=(True, True),
hole_diam=self.wrist_joint.hole_diam,
centre_left_right_tags=True,
)
@assembly() @assembly()
def assembly_s2(self) -> Cq.Assembly: def assembly_s2(self) -> Cq.Assembly:
result = ( result = (
@ -770,10 +805,15 @@ class WingProfile(Model):
.constrain("back?wrist_bot", "bridge_back?wrist_bot", "Plane") .constrain("back?wrist_bot", "bridge_back?wrist_bot", "Plane")
.constrain("back?wrist_top", "bridge_back?wrist_top", "Plane") .constrain("back?wrist_top", "bridge_back?wrist_top", "Plane")
) )
for t in ["elbow_bot", "elbow_top", "wrist_bot", "wrist_top"]: 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_wrist_act(), "wrist_act"),
]:
is_top = t.endswith("_top") is_top = t.endswith("_top")
is_parent = t.startswith("elbow") is_parent = t.startswith("elbow")
o = self.spacer_s2_elbow() if is_parent else self.spacer_s2_wrist()
self._assembly_insert_spacer( self._assembly_insert_spacer(
result, result,
o.generate(), o.generate(),
@ -793,7 +833,7 @@ class WingProfile(Model):
return profile return profile
def surface_s3(self, def surface_s3(self,
front: bool = True) -> Cq.Workplane: front: bool = True) -> Cq.Workplane:
loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc(flip=not self.flip) loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc()
tags = [ tags = [
("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)),
@ -807,7 +847,7 @@ class WingProfile(Model):
profile = self.profile_s3_extra() profile = self.profile_s3_extra()
if profile is None: if profile is None:
return None return None
loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc(flip=not self.flip) loc_wrist = Cq.Location.rot2d(self.wrist_rotate) * self.wrist_joint.child_arm_loc()
tags = [ tags = [
("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)),
@ -929,32 +969,31 @@ class WingProfile(Model):
if "s1" in parts and "elbow" in parts: if "s1" in parts and "elbow" in parts:
( (
result result
.constrain("s1/elbow_top?conn0", f"elbow/parent_upper/lip?conn_{tag_top}0", "Plane") .constrain("s1/elbow_top?conn0", "elbow/parent_upper/lip?conn_top0", "Plane")
.constrain("s1/elbow_top?conn1", f"elbow/parent_upper/lip?conn_{tag_top}1", "Plane") .constrain("s1/elbow_top?conn1", "elbow/parent_upper/lip?conn_top1", "Plane")
.constrain("s1/elbow_bot?conn0", f"elbow/parent_upper/lip?conn_{tag_bot}0", "Plane") .constrain("s1/elbow_bot?conn0", "elbow/parent_upper/lip?conn_bot0", "Plane")
.constrain("s1/elbow_bot?conn1", f"elbow/parent_upper/lip?conn_{tag_bot}1", "Plane") .constrain("s1/elbow_bot?conn1", "elbow/parent_upper/lip?conn_bot1", "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:
( (
result result
.constrain("s2/elbow_top?conn0", f"elbow/child/lip?conn_{tag_top}0", "Plane") .constrain("s2/elbow_top?conn0", "elbow/child/lip?conn_top0", "Plane")
.constrain("s2/elbow_top?conn1", f"elbow/child/lip?conn_{tag_top}1", "Plane") .constrain("s2/elbow_top?conn1", "elbow/child/lip?conn_top1", "Plane")
.constrain("s2/elbow_bot?conn0", f"elbow/child/lip?conn_{tag_bot}0", "Plane") .constrain("s2/elbow_bot?conn0", "elbow/child/lip?conn_bot0", "Plane")
.constrain("s2/elbow_bot?conn1", f"elbow/child/lip?conn_{tag_bot}1", "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(angle=angle), name="wrist") result.add(self.wrist_joint.assembly(angle=angle), name="wrist")
wrist_n_holes = len(self.wrist_joint.hole_pos) 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:
# Mounted backwards to bend in other direction
for i in range(wrist_n_holes): for i in range(wrist_n_holes):
( (
result result
.constrain(f"s2/wrist_top?conn{i}", f"wrist/parent_upper/lip?conn_{tag_bot}{i}", "Plane") .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_{tag_top}{i}", "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")
@ -962,8 +1001,8 @@ class WingProfile(Model):
for i in range(wrist_n_holes): for i in range(wrist_n_holes):
( (
result result
.constrain(f"s3/wrist_top?conn{i}", f"wrist/child/lip?conn_{tag_bot}{i}", "Plane") .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_{tag_top}{i}", "Plane") .constrain(f"s3/wrist_bot?conn{i}", f"wrist/child/lip?conn_bot{i}", "Plane")
) )
if len(parts) > 1: if len(parts) > 1:
result.solve() result.solve()
@ -1167,9 +1206,11 @@ class WingL(WingProfile):
assert self.wrist_height <= self.shoulder_joint.height assert self.wrist_height <= self.shoulder_joint.height
self.wrist_bot_loc = self.wrist_bot_loc.with_angle_2d(self.wrist_angle) self.wrist_bot_loc = self.wrist_bot_loc.with_angle_2d(self.wrist_angle)
self.elbow_joint.angle_neutral = 15.0 self.elbow_joint.angle_neutral = 15.0
self.elbow_joint.flip = True
self.elbow_rotate = 5.0 self.elbow_rotate = 5.0
self.wrist_joint.angle_neutral = self.wrist_bot_loc.to2d_rot() + 30.0 self.wrist_joint.angle_neutral = self.wrist_bot_loc.to2d_rot() + 30.0
self.wrist_rotate = -self.wrist_joint.angle_neutral self.wrist_rotate = -self.wrist_joint.angle_neutral
self.wrist_joint.flip = False
super().__post_init__() super().__post_init__()