cosplay: Touhou/Houjuu Nue #4

Open
aniva wants to merge 189 commits from touhou/houjuu-nue into main
4 changed files with 114 additions and 10 deletions
Showing only changes of commit bbfeb50f8e - Show all commits

58
nhf/parts/fibre.py Normal file
View File

@ -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

View File

@ -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:

View File

@ -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):

View File

@ -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,