cosplay: Touhou/Houjuu Nue #4
|
@ -0,0 +1,58 @@
|
||||||
|
"""
|
||||||
|
A fibre, for bearing tension
|
||||||
|
"""
|
||||||
|
import cadquery as Cq
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import nhf.utils
|
||||||
|
|
||||||
|
def tension_fibre(
|
||||||
|
length: float,
|
||||||
|
hole_diam: float,
|
||||||
|
hole_twist: float=0,
|
||||||
|
thickness: float=0.5) -> Cq.Workplane:
|
||||||
|
"""
|
||||||
|
A fibre which holds tension, with an eyes on each end.
|
||||||
|
|
||||||
|
"""
|
||||||
|
eye_female = Cq.Solid.makeTorus(
|
||||||
|
radius1=hole_diam/2 + thickness/2,
|
||||||
|
radius2=thickness/2,
|
||||||
|
dir=(1,0,0),
|
||||||
|
)
|
||||||
|
hole_length_male = hole_diam * 2.5
|
||||||
|
hole_height_male = hole_diam * 1.2
|
||||||
|
eye_male = Cq.Solid.makeBox(
|
||||||
|
length=hole_length_male + thickness * 2,
|
||||||
|
width=thickness,
|
||||||
|
height=hole_height_male + thickness * 2,
|
||||||
|
).located(
|
||||||
|
Cq.Location((-hole_length_male/2-thickness, -thickness/2, -hole_height_male/2-thickness))
|
||||||
|
).cut(Cq.Solid.makeBox(
|
||||||
|
length=hole_length_male,
|
||||||
|
width=thickness,
|
||||||
|
height=hole_height_male,
|
||||||
|
).located(Cq.Location((-hole_length_male/2, -thickness/2, -hole_height_male/2))))
|
||||||
|
height = length - hole_diam - thickness
|
||||||
|
assert height > 0, "String is too short to support the given hole sizes"
|
||||||
|
h1 = length/2 - hole_diam/2 - thickness/2
|
||||||
|
h2 = length/2 - hole_height_male - thickness/2
|
||||||
|
result = (
|
||||||
|
Cq.Workplane('XY')
|
||||||
|
.cylinder(
|
||||||
|
radius=thickness/2,
|
||||||
|
height=h1,
|
||||||
|
centered=(True, True, False),
|
||||||
|
)
|
||||||
|
.copyWorkplane(Cq.Workplane('YX'))
|
||||||
|
.cylinder(
|
||||||
|
radius=thickness/2,
|
||||||
|
height=h2,
|
||||||
|
centered=(True, True, False),
|
||||||
|
)
|
||||||
|
.union(eye_female.located(Cq.Location((0, 0,length/2))))
|
||||||
|
.union(eye_male.located(Cq.Location((0, 0,-length/2+hole_height_male/2+thickness/2), (0,0,1), hole_twist)))
|
||||||
|
)
|
||||||
|
result.copyWorkplane(Cq.Workplane(Cq.Plane(origin=(0,0,length/2), normal=(1,0,0)))).tagPlane("female")
|
||||||
|
conn1_normal, _ = (Cq.Location((0,0,0),(0,0,1),hole_twist) * Cq.Location((1,0,0))).toTuple()
|
||||||
|
result.copyWorkplane(Cq.Workplane(Cq.Plane(origin=(0,0,-length/2), normal=conn1_normal))).tagPlane("male")
|
||||||
|
return result
|
|
@ -8,6 +8,7 @@ import cadquery as Cq
|
||||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||||
from nhf.materials import Role, Material
|
from nhf.materials import Role, Material
|
||||||
from nhf.parts.box import MountingBox, Hole
|
from nhf.parts.box import MountingBox, Hole
|
||||||
|
from nhf.parts.fibre import tension_fibre
|
||||||
from nhf.parts.item import Item
|
from nhf.parts.item import Item
|
||||||
from nhf.parts.fasteners import FlatHeadBolt, HexNut
|
from nhf.parts.fasteners import FlatHeadBolt, HexNut
|
||||||
from nhf.touhou.houjuu_nue.common import NUT_COMMON, BOLT_COMMON
|
from nhf.touhou.houjuu_nue.common import NUT_COMMON, BOLT_COMMON
|
||||||
|
@ -333,7 +334,7 @@ LINEAR_ACTUATOR_BRACKET = MountingBracket()
|
||||||
|
|
||||||
BATTERY_BOX = BatteryBox18650()
|
BATTERY_BOX = BatteryBox18650()
|
||||||
|
|
||||||
@dataclass
|
@dataclass(kw_only=True)
|
||||||
class Flexor:
|
class Flexor:
|
||||||
"""
|
"""
|
||||||
Actuator assembly which flexes, similar to biceps
|
Actuator assembly which flexes, similar to biceps
|
||||||
|
@ -346,17 +347,30 @@ class Flexor:
|
||||||
nut: HexNut = LINEAR_ACTUATOR_HEX_NUT
|
nut: HexNut = LINEAR_ACTUATOR_HEX_NUT
|
||||||
bolt: FlatHeadBolt = LINEAR_ACTUATOR_BOLT
|
bolt: FlatHeadBolt = LINEAR_ACTUATOR_BOLT
|
||||||
bracket: MountingBracket = LINEAR_ACTUATOR_BRACKET
|
bracket: MountingBracket = LINEAR_ACTUATOR_BRACKET
|
||||||
|
# Length of line attached to the flexor
|
||||||
|
line_length: float = 0.0
|
||||||
|
line_thickness: float = 0.5
|
||||||
|
# By how much is the line permitted to slack. This reduces the effective stroke length
|
||||||
|
line_slack: float = 0.0
|
||||||
|
|
||||||
# FIXME: Add a compression spring so the serviceable distances are not as fixed
|
def __post_init__(self):
|
||||||
|
assert self.line_slack <= self.line_length < self.actuator.stroke_length
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mount_height(self):
|
def mount_height(self):
|
||||||
return self.bracket.hole_to_side_ext
|
return self.bracket.hole_to_side_ext
|
||||||
|
|
||||||
|
@property
|
||||||
|
def d_open(self):
|
||||||
|
return self.actuator.conn_length + self.actuator.stroke_length + self.line_length - self.line_slack
|
||||||
|
@property
|
||||||
|
def d_closed(self):
|
||||||
|
return self.actuator.conn_length + self.line_length
|
||||||
|
|
||||||
def open_pos(self) -> Tuple[float, float, float]:
|
def open_pos(self) -> Tuple[float, float, float]:
|
||||||
r, phi, r_ = nhf.geometry.contraction_span_pos_from_radius(
|
r, phi, r_ = nhf.geometry.contraction_span_pos_from_radius(
|
||||||
d_open=self.actuator.conn_length + self.actuator.stroke_length,
|
d_open=self.d_open,
|
||||||
d_closed=self.actuator.conn_length,
|
d_closed=self.d_closed,
|
||||||
theta=math.radians(self.motion_span),
|
theta=math.radians(self.motion_span),
|
||||||
r=self.arm_radius,
|
r=self.arm_radius,
|
||||||
smaller=self.pos_smaller,
|
smaller=self.pos_smaller,
|
||||||
|
@ -376,8 +390,8 @@ class Flexor:
|
||||||
|
|
||||||
result = math.sqrt(r * r + rp * rp - 2 * r * rp * math.cos(th))
|
result = math.sqrt(r * r + rp * rp - 2 * r * rp * math.cos(th))
|
||||||
#result = math.sqrt((r * math.cos(th) - rp) ** 2 + (r * math.sin(th)) ** 2)
|
#result = math.sqrt((r * math.cos(th) - rp) ** 2 + (r * math.sin(th)) ** 2)
|
||||||
assert self.actuator.conn_length <= result <= self.actuator.conn_length + self.actuator.stroke_length, \
|
assert self.d_closed -1e-6 <= result <= self.d_open + 1e-6,\
|
||||||
f"Illegal length: {result} in {self.actuator.conn_length}+{self.actuator.stroke_length}"
|
f"Illegal length: {result} not in [{self.d_closed}, {self.d_open}]"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -393,7 +407,9 @@ class Flexor:
|
||||||
Adds the necessary mechanical components to this assembly. Does not
|
Adds the necessary mechanical components to this assembly. Does not
|
||||||
invoke `a.solve()`.
|
invoke `a.solve()`.
|
||||||
"""
|
"""
|
||||||
pos = (target_length - self.actuator.conn_length) / self.actuator.stroke_length
|
draft = max(0, target_length - self.d_closed - self.line_length)
|
||||||
|
pos = draft / self.actuator.stroke_length
|
||||||
|
line_l = target_length - draft - self.actuator.conn_length
|
||||||
if tag_prefix:
|
if tag_prefix:
|
||||||
tag_prefix = tag_prefix + "_"
|
tag_prefix = tag_prefix + "_"
|
||||||
else:
|
else:
|
||||||
|
@ -411,8 +427,6 @@ class Flexor:
|
||||||
.add(self.bracket.assembly(), name=name_bracket_front)
|
.add(self.bracket.assembly(), name=name_bracket_front)
|
||||||
.add(self.bolt.assembly(), name=name_bolt_front)
|
.add(self.bolt.assembly(), name=name_bolt_front)
|
||||||
.add(self.nut.assembly(), name=name_nut_front)
|
.add(self.nut.assembly(), name=name_nut_front)
|
||||||
.constrain(f"{name_actuator}/front?conn", f"{name_bracket_front}?conn_mid",
|
|
||||||
"Plane", param=0)
|
|
||||||
.constrain(f"{name_bolt_front}?root", f"{name_bracket_front}?conn_top",
|
.constrain(f"{name_bolt_front}?root", f"{name_bracket_front}?conn_top",
|
||||||
"Plane", param=0)
|
"Plane", param=0)
|
||||||
.constrain(f"{name_nut_front}?bot", f"{name_bracket_front}?conn_bot",
|
.constrain(f"{name_nut_front}?bot", f"{name_bracket_front}?conn_bot",
|
||||||
|
@ -427,6 +441,30 @@ class Flexor:
|
||||||
.constrain(f"{name_nut_back}?bot", f"{name_bracket_back}?conn_bot",
|
.constrain(f"{name_nut_back}?bot", f"{name_bracket_back}?conn_bot",
|
||||||
"Plane")
|
"Plane")
|
||||||
)
|
)
|
||||||
|
if self.line_length == 0.0:
|
||||||
|
a.constrain(
|
||||||
|
f"{name_actuator}/front?conn",
|
||||||
|
f"{name_bracket_front}?conn_mid",
|
||||||
|
"Plane", param=0)
|
||||||
|
else:
|
||||||
|
(
|
||||||
|
a
|
||||||
|
.addS(tension_fibre(
|
||||||
|
length=line_l,
|
||||||
|
hole_diam=self.nut.diam_thread,
|
||||||
|
thickness=self.line_thickness,
|
||||||
|
), name="fibre", role=Role.CONNECTION)
|
||||||
|
.constrain(
|
||||||
|
f"{name_actuator}/front?conn",
|
||||||
|
"fibre?male",
|
||||||
|
"Plane"
|
||||||
|
)
|
||||||
|
.constrain(
|
||||||
|
f"{name_bracket_front}?conn_mid",
|
||||||
|
"fibre?female",
|
||||||
|
"Plane"
|
||||||
|
)
|
||||||
|
)
|
||||||
if tag_hole_front:
|
if tag_hole_front:
|
||||||
a.constrain(tag_hole_front, f"{name_bracket_front}?conn_side", "Plane")
|
a.constrain(tag_hole_front, f"{name_bracket_front}?conn_side", "Plane")
|
||||||
if tag_hole_back:
|
if tag_hole_back:
|
||||||
|
|
|
@ -1067,7 +1067,7 @@ class ElbowJoint(Model):
|
||||||
flip: bool = False
|
flip: bool = False
|
||||||
angle_neutral: float = 30.0
|
angle_neutral: float = 30.0
|
||||||
|
|
||||||
actuator: Optional[LinearActuator]
|
actuator: Optional[LinearActuator] = None
|
||||||
flexor: Optional[Flexor] = None
|
flexor: Optional[Flexor] = None
|
||||||
# Rotates the entire flexor
|
# Rotates the entire flexor
|
||||||
flexor_offset_angle: float = 0
|
flexor_offset_angle: float = 0
|
||||||
|
@ -1076,6 +1076,8 @@ class ElbowJoint(Model):
|
||||||
flexor_mount_angle_child: float = -90
|
flexor_mount_angle_child: float = -90
|
||||||
flexor_pos_smaller: bool = True
|
flexor_pos_smaller: bool = True
|
||||||
flexor_child_arm_radius: Optional[float] = None
|
flexor_child_arm_radius: Optional[float] = None
|
||||||
|
flexor_line_length: float = 0.0
|
||||||
|
flexor_line_slack: float = 0.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
|
||||||
|
@ -1087,6 +1089,8 @@ class ElbowJoint(Model):
|
||||||
motion_span=self.motion_span,
|
motion_span=self.motion_span,
|
||||||
pos_smaller=self.flexor_pos_smaller,
|
pos_smaller=self.flexor_pos_smaller,
|
||||||
arm_radius=self.flexor_child_arm_radius,
|
arm_radius=self.flexor_child_arm_radius,
|
||||||
|
line_length=self.flexor_line_length,
|
||||||
|
line_slack=self.flexor_line_slack,
|
||||||
)
|
)
|
||||||
|
|
||||||
def hole_loc_tags(self):
|
def hole_loc_tags(self):
|
||||||
|
|
|
@ -1291,6 +1291,10 @@ class WingL(WingProfile):
|
||||||
angle_neutral=30.0,
|
angle_neutral=30.0,
|
||||||
flexor_mount_angle_child=170,
|
flexor_mount_angle_child=170,
|
||||||
flexor_mount_angle_parent=-30,
|
flexor_mount_angle_parent=-30,
|
||||||
|
flexor_line_length=30.0,
|
||||||
|
flexor_line_slack=5.0,
|
||||||
|
#flexor_line_length=0.0,
|
||||||
|
#flexor_line_slack=0.0,
|
||||||
flexor_offset_angle=15,
|
flexor_offset_angle=15,
|
||||||
child_lip_extra_length=5.0,
|
child_lip_extra_length=5.0,
|
||||||
flexor_child_arm_radius=60.0,
|
flexor_child_arm_radius=60.0,
|
||||||
|
|
Loading…
Reference in New Issue