cosplay: Touhou/Houjuu Nue #4
|
@ -354,7 +354,8 @@ class Flexor:
|
||||||
line_slack: float = 0.0
|
line_slack: float = 0.0
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
assert self.line_slack <= self.line_length < self.actuator.stroke_length
|
assert self.line_slack <= self.line_length, f"Insufficient length: {self.line_slack} >= {self.line_length}"
|
||||||
|
assert self.line_slack < self.actuator.stroke_length
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mount_height(self):
|
def mount_height(self):
|
||||||
|
|
|
@ -1121,8 +1121,6 @@ class ElbowJoint(Model):
|
||||||
parent_arm_radius: float = 40.0
|
parent_arm_radius: float = 40.0
|
||||||
|
|
||||||
lip_thickness: float = 5.0
|
lip_thickness: float = 5.0
|
||||||
# Extra bit on top of the lip to connect to actuator mount
|
|
||||||
child_lip_extra_length: float = 1.0
|
|
||||||
lip_length: float = 60.0
|
lip_length: float = 60.0
|
||||||
hole_pos: list[float] = field(default_factory=lambda: [12, 24])
|
hole_pos: list[float] = field(default_factory=lambda: [12, 24])
|
||||||
parent_arm_width: float = 10.0
|
parent_arm_width: float = 10.0
|
||||||
|
@ -1149,6 +1147,8 @@ class ElbowJoint(Model):
|
||||||
flexor_child_arm_radius: Optional[float] = None
|
flexor_child_arm_radius: Optional[float] = None
|
||||||
flexor_line_length: float = 0.0
|
flexor_line_length: float = 0.0
|
||||||
flexor_line_slack: float = 0.0
|
flexor_line_slack: float = 0.0
|
||||||
|
flexor_parent_angle_fix: Optional[float] = 180.0
|
||||||
|
flexor_child_angle_fix: Optional[float] = None
|
||||||
|
|
||||||
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
|
||||||
|
@ -1220,10 +1220,15 @@ class ElbowJoint(Model):
|
||||||
# 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)
|
||||||
alpha = (-mount_loc_angle if child else 0) + 180 - self.flexor_offset_angle
|
if self.flexor_parent_angle_fix is not None:
|
||||||
|
alpha = (-mount_loc_angle if child else 0) + self.flexor_parent_angle_fix - self.flexor_offset_angle
|
||||||
|
elif self.flexor_child_angle_fix is not None:
|
||||||
|
alpha = self.flexor_child_angle_fix + (0 if child else mount_loc_angle)
|
||||||
|
else:
|
||||||
|
raise ValueError("One of flexor_{parent,child}_angle_fix must be set")
|
||||||
loc_rot = Cq.Location.rot2d(alpha)
|
loc_rot = Cq.Location.rot2d(alpha)
|
||||||
loc = loc_rot * loc_span * loc_mount_orient * loc_mount
|
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
|
return loc.flip_y() if self.flip and not unflip else loc
|
||||||
|
|
||||||
def lip(self) -> Cq.Workplane:
|
def lip(self) -> Cq.Workplane:
|
||||||
sign = -1 if self.flip else 1
|
sign = -1 if self.flip else 1
|
||||||
|
@ -1248,7 +1253,7 @@ class ElbowJoint(Model):
|
||||||
return mbox.generate()
|
return mbox.generate()
|
||||||
|
|
||||||
@target(name="child")
|
@target(name="child")
|
||||||
def child_joint(self) -> Cq.Assembly:
|
def child_joint(self, generate_mount: bool=False) -> Cq.Assembly:
|
||||||
angle = -self.disk_joint.tongue_span / 2
|
angle = -self.disk_joint.tongue_span / 2
|
||||||
dz = self.disk_joint.disk_thickness / 2
|
dz = self.disk_joint.disk_thickness / 2
|
||||||
# We need to ensure the disk is on the "other" side so
|
# We need to ensure the disk is on the "other" side so
|
||||||
|
@ -1265,31 +1270,40 @@ class ElbowJoint(Model):
|
||||||
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)
|
||||||
lip_extra = Cq.Solid.makeBox(
|
#lip_extra = Cq.Solid.makeBox(
|
||||||
length=self.child_lip_extra_length,
|
# length=self.child_lip_extra_length,
|
||||||
width=self.total_thickness,
|
# width=self.total_thickness,
|
||||||
height=self.lip_thickness,
|
# height=self.lip_thickness,
|
||||||
).located(Cq.Location((
|
#).located(Cq.Location((
|
||||||
self.lip_length / 2,
|
# self.lip_length / 2,
|
||||||
-self.total_thickness / 2,
|
# -self.total_thickness / 2,
|
||||||
0,
|
# 0,
|
||||||
)))
|
#)))
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
.add(self.disk_joint.disk(), name="disk",
|
.add(self.disk_joint.disk(), name="disk",
|
||||||
loc=loc_rot_neutral * Cq.Location((0, 0, -dz), (0,0,1), angle))
|
loc=loc_rot_neutral * Cq.Location((0, 0, -dz), (0,0,1), angle))
|
||||||
.add(self.lip().cut(disk_cut), name="lip",
|
.add(self.lip().cut(disk_cut), name="lip",
|
||||||
loc=loc_rot_neutral * loc_disk.inverse * loc_lip)
|
loc=loc_rot_neutral * loc_disk.inverse * loc_lip)
|
||||||
.add(lip_extra, name="lip_extra",
|
#.add(lip_extra, name="lip_extra",
|
||||||
loc=loc_rot_neutral * loc_disk.inverse * loc_lip)
|
# loc=loc_rot_neutral * loc_disk.inverse * loc_lip)
|
||||||
)
|
)
|
||||||
# Orientes the hole surface so it faces +X
|
# Orientes the hole surface so it faces +X
|
||||||
loc_thickness = Cq.Location((-self.lip_thickness, 0, 0), (0, 1, 0), 90)
|
loc_thickness = Cq.Location((-self.lip_thickness, 0, 0), (0, 1, 0), 90)
|
||||||
if self.flexor:
|
if self.flexor:
|
||||||
|
loc_mount = self.actuator_mount_loc(child=True, unflip=True)
|
||||||
result.add(
|
result.add(
|
||||||
self.actuator_mount(),
|
Cq.Edge.makeLine((-1,0,0), (1,0,0)),
|
||||||
name="act",
|
name="act",
|
||||||
loc=self.actuator_mount_loc(child=True) * loc_thickness)
|
loc=loc_mount)
|
||||||
|
if generate_mount:
|
||||||
|
# Orientes the hole surface so it faces +X
|
||||||
|
loc_thickness = Cq.Location((-self.lip_thickness, 0, 0), (0, 1, 0), 90)
|
||||||
|
result.add(
|
||||||
|
self.actuator_mount(),
|
||||||
|
name="act_mount",
|
||||||
|
loc=loc_mount * loc_thickness,
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@target(name="parent-lower")
|
@target(name="parent-lower")
|
||||||
|
@ -1342,17 +1356,18 @@ class ElbowJoint(Model):
|
||||||
#.solve()
|
#.solve()
|
||||||
)
|
)
|
||||||
if self.flexor:
|
if self.flexor:
|
||||||
|
loc_mount = self.actuator_mount_loc(child=False, unflip=True)
|
||||||
result.add(
|
result.add(
|
||||||
Cq.Edge.makeLine((-1,0,0), (1,0,0)),
|
Cq.Edge.makeLine((-1,0,0), (1,0,0)),
|
||||||
name="act",
|
name="act",
|
||||||
loc=self.actuator_mount_loc(child=False, unflip=True))
|
loc=loc_mount)
|
||||||
if generate_mount:
|
if generate_mount:
|
||||||
# Orientes the hole surface so it faces +X
|
# Orientes the hole surface so it faces +X
|
||||||
loc_thickness = Cq.Location((-self.lip_thickness, 0, 0), (0, 1, 0), 90)
|
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_mount",
|
name="act_mount",
|
||||||
loc=self.actuator_mount_loc(child=False, unflip=True) * loc_thickness
|
loc=loc_mount * loc_thickness
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -1364,7 +1379,7 @@ class ElbowJoint(Model):
|
||||||
assert 0 <= angle <= self.motion_span
|
assert 0 <= angle <= self.motion_span
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
.addS(self.child_joint(), name="child",
|
.addS(self.child_joint(generate_mount=generate_mount), name="child",
|
||||||
role=Role.CHILD, material=self.material)
|
role=Role.CHILD, material=self.material)
|
||||||
.addS(self.parent_joint_lower(), name="parent_lower",
|
.addS(self.parent_joint_lower(), name="parent_lower",
|
||||||
role=Role.CASING, material=self.material)
|
role=Role.CASING, material=self.material)
|
||||||
|
|
|
@ -23,9 +23,6 @@ from nhf.touhou.houjuu_nue.electronics import (
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
|
||||||
ELBOW_PARAMS = dict(
|
ELBOW_PARAMS = dict(
|
||||||
disk_joint=DiskJoint(
|
|
||||||
movement_angle=55,
|
|
||||||
),
|
|
||||||
hole_diam=4.0,
|
hole_diam=4.0,
|
||||||
actuator=LINEAR_ACTUATOR_50,
|
actuator=LINEAR_ACTUATOR_50,
|
||||||
parent_arm_width=15,
|
parent_arm_width=15,
|
||||||
|
@ -508,6 +505,8 @@ class WingProfile(Model):
|
||||||
loc_ext = loc_bot if bot else loc_top
|
loc_ext = loc_bot if bot else loc_top
|
||||||
loc_tip = loc_top if bot else loc_bot
|
loc_tip = loc_top if bot else loc_bot
|
||||||
theta = math.radians(angle_span * (median if child else 1 - median))
|
theta = math.radians(angle_span * (median if child else 1 - median))
|
||||||
|
if self.flip:
|
||||||
|
axle_pos = 1 - axle_pos
|
||||||
y_sign = -1 if bot else 1
|
y_sign = -1 if bot else 1
|
||||||
sign = -1 if child else 1
|
sign = -1 if child else 1
|
||||||
dh = axle_pos * height * (overestimate - 1)
|
dh = axle_pos * height * (overestimate - 1)
|
||||||
|
@ -736,11 +735,14 @@ 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()
|
rot_elbow = Cq.Location.rot2d(self.elbow_rotate)
|
||||||
|
loc_elbow = rot_elbow * self.elbow_joint.child_arm_loc()
|
||||||
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 = [
|
tags = [
|
||||||
("elbow", self.elbow_axle_loc * loc_elbow),
|
("elbow", self.elbow_axle_loc * loc_elbow),
|
||||||
|
("elbow_act", self.elbow_axle_loc * rot_elbow *
|
||||||
|
self.elbow_joint.actuator_mount_loc(child=True)),
|
||||||
("wrist", self.wrist_axle_loc * loc_wrist),
|
("wrist", self.wrist_axle_loc * loc_wrist),
|
||||||
("wrist_act", self.wrist_axle_loc * rot_wrist *
|
("wrist_act", self.wrist_axle_loc * rot_wrist *
|
||||||
self.wrist_joint.actuator_mount_loc()),
|
self.wrist_joint.actuator_mount_loc()),
|
||||||
|
@ -773,6 +775,17 @@ class WingProfile(Model):
|
||||||
segment_thickness=self.s2_thickness,
|
segment_thickness=self.s2_thickness,
|
||||||
child=True,
|
child=True,
|
||||||
)
|
)
|
||||||
|
@submodel(name="spacer-s1-elbow-act")
|
||||||
|
def spacer_s2_elbow_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.elbow_joint.hole_diam,
|
||||||
|
centre_left_right_tags=True,
|
||||||
|
)
|
||||||
@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_from_disk_joint(
|
return self._spacer_from_disk_joint(
|
||||||
|
@ -822,6 +835,7 @@ class WingProfile(Model):
|
||||||
)
|
)
|
||||||
for o, t in [
|
for o, t in [
|
||||||
(self.spacer_s2_elbow(), "elbow"),
|
(self.spacer_s2_elbow(), "elbow"),
|
||||||
|
(self.spacer_s2_elbow_act(), "elbow_act"),
|
||||||
(self.spacer_s2_wrist(), "wrist"),
|
(self.spacer_s2_wrist(), "wrist"),
|
||||||
(self.spacer_s2_wrist_act(), "wrist_act"),
|
(self.spacer_s2_wrist_act(), "wrist_act"),
|
||||||
]:
|
]:
|
||||||
|
@ -1055,11 +1069,13 @@ class WingR(WingProfile):
|
||||||
elbow_height: float = 111.0
|
elbow_height: float = 111.0
|
||||||
elbow_rotate: float = 10.0
|
elbow_rotate: float = 10.0
|
||||||
elbow_joint: ElbowJoint = field(default_factory=lambda: ElbowJoint(
|
elbow_joint: ElbowJoint = field(default_factory=lambda: ElbowJoint(
|
||||||
|
disk_joint=DiskJoint(
|
||||||
|
movement_angle=55,
|
||||||
|
),
|
||||||
flexor_offset_angle=15,
|
flexor_offset_angle=15,
|
||||||
flexor_mount_angle_child=-75,
|
flexor_mount_angle_child=-75,
|
||||||
flexor_child_arm_radius=None,
|
flexor_child_arm_radius=None,
|
||||||
angle_neutral=10.0,
|
angle_neutral=10.0,
|
||||||
child_lip_extra_length=8,
|
|
||||||
flip=False,
|
flip=False,
|
||||||
**ELBOW_PARAMS
|
**ELBOW_PARAMS
|
||||||
))
|
))
|
||||||
|
@ -1288,24 +1304,32 @@ class WingR(WingProfile):
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
class WingL(WingProfile):
|
class WingL(WingProfile):
|
||||||
|
|
||||||
elbow_bot_loc: Cq.Location = Cq.Location.from2d(260.0, 110.0, 0.0)
|
elbow_bot_loc: Cq.Location = Cq.Location.from2d(260.0, 105.0, 0.0)
|
||||||
elbow_height: float = 90.0
|
elbow_height: float = 95.0
|
||||||
elbow_rotate: float = 15.0
|
elbow_rotate: float = 15.0
|
||||||
elbow_joint: ElbowJoint = field(default_factory=lambda: ElbowJoint(
|
elbow_joint: ElbowJoint = field(default_factory=lambda: ElbowJoint(
|
||||||
angle_neutral=30.0,
|
disk_joint=DiskJoint(
|
||||||
flexor_mount_angle_child=170,
|
movement_angle=50,
|
||||||
flexor_mount_angle_parent=-30,
|
),
|
||||||
flexor_line_length=30.0,
|
angle_neutral=25.0,
|
||||||
flexor_line_slack=5.0,
|
flexor_mount_angle_child=220,
|
||||||
|
flexor_mount_angle_parent=0,
|
||||||
|
flexor_line_length=50.0,
|
||||||
|
flexor_line_slack=10.0,
|
||||||
#flexor_line_length=0.0,
|
#flexor_line_length=0.0,
|
||||||
#flexor_line_slack=0.0,
|
#flexor_line_slack=0.0,
|
||||||
flexor_offset_angle=15,
|
flexor_offset_angle=0,
|
||||||
child_lip_extra_length=5.0,
|
flexor_child_angle_fix=85,
|
||||||
flexor_child_arm_radius=60.0,
|
flexor_parent_angle_fix=None,
|
||||||
|
flexor_child_arm_radius=50.0,
|
||||||
|
parent_arm_radius=50.0,
|
||||||
|
child_arm_radius=50.0,
|
||||||
flexor_pos_smaller=False,
|
flexor_pos_smaller=False,
|
||||||
flip=True,
|
flip=True,
|
||||||
**ELBOW_PARAMS
|
**ELBOW_PARAMS
|
||||||
))
|
))
|
||||||
|
elbow_axle_pos: float = 0.53
|
||||||
|
elbow_joint_overlap_median: float = 0.5
|
||||||
|
|
||||||
wrist_angle: float = 0.0
|
wrist_angle: float = 0.0
|
||||||
wrist_bot_loc: Cq.Location = Cq.Location.from2d(460.0, -10.0, -45.0)
|
wrist_bot_loc: Cq.Location = Cq.Location.from2d(460.0, -10.0, -45.0)
|
||||||
|
@ -1324,9 +1348,7 @@ class WingL(WingProfile):
|
||||||
arrow_height: float = 120.0
|
arrow_height: float = 120.0
|
||||||
|
|
||||||
flip: bool = True
|
flip: bool = True
|
||||||
elbow_axle_pos: float = 0.5
|
|
||||||
wrist_axle_pos: float = 0.5
|
wrist_axle_pos: float = 0.5
|
||||||
elbow_joint_overlap_median: float = 0.5
|
|
||||||
wrist_joint_overlap_median: float = 0.5
|
wrist_joint_overlap_median: float = 0.5
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue