refactor: HS Joint into its own class
This commit is contained in:
parent
3e5fe7bc5e
commit
4b6b05853e
|
@ -122,5 +122,5 @@ class HexNut(Item):
|
||||||
)
|
)
|
||||||
result.faces("<Z").tag("bot")
|
result.faces("<Z").tag("bot")
|
||||||
result.faces(">Z").tag("top")
|
result.faces(">Z").tag("top")
|
||||||
result.copyWorkplane(Cq.Workplane('XY')).tagPlane("dir", direction="+X")
|
result.copyWorkplane(Cq.Workplane('XY')).tagPlane("dirX", direction="+X")
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -77,22 +77,15 @@ class Parameters(Model):
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__init__(name="houjuu-nue")
|
super().__init__(name="houjuu-nue")
|
||||||
self.wing_r1.base_joint = self.harness.hs_hirth_joint
|
self.wing_r1.root_joint = self.harness.root_joint
|
||||||
self.wing_r2.base_joint = self.harness.hs_hirth_joint
|
self.wing_r2.root_joint = self.harness.root_joint
|
||||||
self.wing_r3.base_joint = self.harness.hs_hirth_joint
|
self.wing_r3.root_joint = self.harness.root_joint
|
||||||
self.wing_l1.base_joint = self.harness.hs_hirth_joint
|
self.wing_l1.root_joint = self.harness.root_joint
|
||||||
self.wing_l2.base_joint = self.harness.hs_hirth_joint
|
self.wing_l2.root_joint = self.harness.root_joint
|
||||||
self.wing_l3.base_joint = self.harness.hs_hirth_joint
|
self.wing_l3.root_joint = self.harness.root_joint
|
||||||
|
|
||||||
self.wing_r1.shoulder_joint.torsion_joint
|
self.wing_r1.shoulder_joint.torsion_joint
|
||||||
|
|
||||||
assert self.wing_r1.hs_joint_axis_diam == self.harness.hs_joint_axis_diam
|
|
||||||
assert self.wing_r2.hs_joint_axis_diam == self.harness.hs_joint_axis_diam
|
|
||||||
assert self.wing_r3.hs_joint_axis_diam == self.harness.hs_joint_axis_diam
|
|
||||||
assert self.wing_l1.hs_joint_axis_diam == self.harness.hs_joint_axis_diam
|
|
||||||
assert self.wing_l2.hs_joint_axis_diam == self.harness.hs_joint_axis_diam
|
|
||||||
assert self.wing_l3.hs_joint_axis_diam == self.harness.hs_joint_axis_diam
|
|
||||||
|
|
||||||
@submodel(name="harness")
|
@submodel(name="harness")
|
||||||
def submodel_harness(self) -> Model:
|
def submodel_harness(self) -> Model:
|
||||||
return self.harness
|
return self.harness
|
||||||
|
@ -126,19 +119,20 @@ class Parameters(Model):
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
.add(self.harness.assembly(), name="harness", loc=Cq.Location((0, 0, 0)))
|
.add(self.harness.assembly(), name="harness", loc=Cq.Location((0, 0, 0)))
|
||||||
.add(self.wing_r1.assembly(parts, **kwargs), name="wing_r1")
|
.add(self.wing_r1.assembly(parts, root_offset=9, **kwargs), name="wing_r1")
|
||||||
.add(self.wing_r2.assembly(parts, **kwargs), name="wing_r2")
|
.add(self.wing_r2.assembly(parts, root_offset=7, **kwargs), name="wing_r2")
|
||||||
.add(self.wing_r3.assembly(parts, **kwargs), name="wing_r3")
|
.add(self.wing_r3.assembly(parts, root_offset=6, **kwargs), name="wing_r3")
|
||||||
.add(self.wing_l1.assembly(parts, **kwargs), name="wing_l1")
|
.add(self.wing_l1.assembly(parts, root_offset=7, **kwargs), name="wing_l1")
|
||||||
.add(self.wing_l2.assembly(parts, **kwargs), name="wing_l2")
|
.add(self.wing_l2.assembly(parts, root_offset=8, **kwargs), name="wing_l2")
|
||||||
.add(self.wing_l3.assembly(parts, **kwargs), name="wing_l3")
|
.add(self.wing_l3.assembly(parts, root_offset=9, **kwargs), name="wing_l3")
|
||||||
)
|
)
|
||||||
for tag, offset in [("r1", 9), ("r2", 7), ("r3", 6), ("l1", 7), ("l2", 8), ("l3", 9)]:
|
for tag in ["r1", "r2", "r3", "l1", "l2", "l3"]:
|
||||||
self.harness.hs_hirth_joint.add_constraints(
|
self.harness.add_root_joint_constraint(
|
||||||
result,
|
result,
|
||||||
f"harness/{tag}",
|
"harness/base",
|
||||||
f"wing_{tag}/s0/hs",
|
f"wing_{tag}/root",
|
||||||
offset=offset)
|
tag
|
||||||
|
)
|
||||||
return result.solve()
|
return result.solve()
|
||||||
|
|
||||||
@submodel(name="trident")
|
@submodel(name="trident")
|
||||||
|
|
|
@ -2,7 +2,8 @@ from dataclasses import dataclass, field
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from nhf.parts.joints import HirthJoint
|
from nhf.parts.joints import HirthJoint
|
||||||
from nhf import Material, Role
|
from nhf import Material, Role
|
||||||
from nhf.build import Model, TargetKind, target, assembly
|
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||||
|
from nhf.touhou.houjuu_nue.joints import RootJoint
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
@ -63,33 +64,18 @@ class Harness(Model):
|
||||||
("r3", BASE_POS_X, -BASE_POS_Y),
|
("r3", BASE_POS_X, -BASE_POS_Y),
|
||||||
("l3", -BASE_POS_X, -BASE_POS_Y),
|
("l3", -BASE_POS_X, -BASE_POS_Y),
|
||||||
])
|
])
|
||||||
# Holes drilled onto harness for attachment with HS joint
|
|
||||||
harness_to_root_conn_diam: float = 6
|
|
||||||
|
|
||||||
hs_hirth_joint: HirthJoint = field(default_factory=lambda: HirthJoint(
|
root_joint: RootJoint = field(default_factory=lambda: RootJoint())
|
||||||
radius=25.0,
|
|
||||||
radius_inner=15.0,
|
|
||||||
tooth_height=7.0,
|
|
||||||
base_height=5.0,
|
|
||||||
n_tooth=24,
|
|
||||||
))
|
|
||||||
|
|
||||||
hs_joint_base_width: float = 85
|
|
||||||
hs_joint_base_thickness: float = 10
|
|
||||||
hs_joint_corner_fillet: float = 5
|
|
||||||
hs_joint_corner_cbore_diam: float = 12
|
|
||||||
hs_joint_corner_cbore_depth: float = 2
|
|
||||||
hs_joint_corner_inset: float = 12
|
|
||||||
|
|
||||||
hs_joint_axis_diam: float = 12.0
|
|
||||||
hs_joint_axis_cbore_diam: float = 20
|
|
||||||
hs_joint_axis_cbore_depth: float = 3
|
|
||||||
|
|
||||||
mannequin: Mannequin = Mannequin()
|
mannequin: Mannequin = Mannequin()
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__init__(name="harness")
|
super().__init__(name="harness")
|
||||||
|
|
||||||
|
@submodel(name="root-joint")
|
||||||
|
def submodel_root_joint(self) -> Model:
|
||||||
|
return self.root_joint
|
||||||
|
|
||||||
@target(name="profile", kind=TargetKind.DXF)
|
@target(name="profile", kind=TargetKind.DXF)
|
||||||
def profile(self) -> Cq.Sketch:
|
def profile(self) -> Cq.Sketch:
|
||||||
"""
|
"""
|
||||||
|
@ -115,12 +101,12 @@ class Harness(Model):
|
||||||
.fillet(self.fillet)
|
.fillet(self.fillet)
|
||||||
)
|
)
|
||||||
for tag, x, y in self.wing_base_pos:
|
for tag, x, y in self.wing_base_pos:
|
||||||
conn = [(px + x, py + y) for px, py in self.hs_joint_harness_conn()]
|
conn = [(px + x, py + y) for px, py in self.root_joint.corner_pos()]
|
||||||
sketch = (
|
sketch = (
|
||||||
sketch
|
sketch
|
||||||
.push(conn)
|
.push(conn)
|
||||||
.tag(tag)
|
.tag(tag)
|
||||||
.circle(self.harness_to_root_conn_diam / 2, mode='s')
|
.circle(self.root_joint.corner_hole_diam / 2, mode='s')
|
||||||
.reset()
|
.reset()
|
||||||
)
|
)
|
||||||
return sketch
|
return sketch
|
||||||
|
@ -138,78 +124,27 @@ class Harness(Model):
|
||||||
plane = result.faces(">Y").workplane()
|
plane = result.faces(">Y").workplane()
|
||||||
for tag, x, y in self.wing_base_pos:
|
for tag, x, y in self.wing_base_pos:
|
||||||
conn = [(px + x, py + y) for px, py
|
conn = [(px + x, py + y) for px, py
|
||||||
in self.hs_joint_harness_conn()]
|
in self.root_joint.corner_pos()]
|
||||||
for i, (px, py) in enumerate(conn):
|
for i, (px, py) in enumerate(conn):
|
||||||
plane.moveTo(px, py).tagPoint(f"{tag}_{i}")
|
plane.moveTo(px, py).tagPoint(f"{tag}_{i}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def hs_joint_harness_conn(self) -> list[tuple[int, int]]:
|
def add_root_joint_constraint(
|
||||||
"""
|
self,
|
||||||
Generates a set of points corresponding to the connectorss
|
a: Cq.Assembly,
|
||||||
"""
|
harness_tag: str,
|
||||||
dx = self.hs_joint_base_width / 2 - self.hs_joint_corner_inset
|
joint_tag: str,
|
||||||
return [
|
mount_tag: str):
|
||||||
(dx, dx),
|
for i in range(4):
|
||||||
(dx, -dx),
|
a.constrain(f"{harness_tag}?{mount_tag}_{i}", f"{joint_tag}/parent?h{i}", "Point")
|
||||||
(-dx, -dx),
|
|
||||||
(-dx, dx),
|
|
||||||
]
|
|
||||||
|
|
||||||
@target(name="hs-joint-parent")
|
|
||||||
def hs_joint_parent(self):
|
|
||||||
"""
|
|
||||||
Parent part of the Houjuu-Scarlett joint, which is composed of a Hirth
|
|
||||||
coupling, a cylindrical base, and a mounting base.
|
|
||||||
"""
|
|
||||||
hirth = self.hs_hirth_joint.generate()
|
|
||||||
conn = self.hs_joint_harness_conn()
|
|
||||||
result = (
|
|
||||||
Cq.Workplane('XY')
|
|
||||||
.box(
|
|
||||||
self.hs_joint_base_width,
|
|
||||||
self.hs_joint_base_width,
|
|
||||||
self.hs_joint_base_thickness,
|
|
||||||
centered=(True, True, False))
|
|
||||||
.translate((0, 0, -self.hs_joint_base_thickness))
|
|
||||||
.edges("|Z")
|
|
||||||
.fillet(self.hs_joint_corner_fillet)
|
|
||||||
.faces(">Z")
|
|
||||||
.workplane()
|
|
||||||
.pushPoints(conn)
|
|
||||||
.cboreHole(
|
|
||||||
diameter=self.harness_to_root_conn_diam,
|
|
||||||
cboreDiameter=self.hs_joint_corner_cbore_diam,
|
|
||||||
cboreDepth=self.hs_joint_corner_cbore_depth)
|
|
||||||
)
|
|
||||||
# Creates a plane parallel to the holes but shifted to the base
|
|
||||||
plane = result.faces(">Z").workplane(offset=-self.hs_joint_base_thickness)
|
|
||||||
|
|
||||||
for i, (px, py) in enumerate(conn):
|
|
||||||
plane.moveTo(px, py).tagPoint(f"h{i}")
|
|
||||||
result = (
|
|
||||||
result
|
|
||||||
.faces(">Z")
|
|
||||||
.workplane()
|
|
||||||
.union(hirth, tol=0.1)
|
|
||||||
.clean()
|
|
||||||
)
|
|
||||||
result = (
|
|
||||||
result.faces("<Z")
|
|
||||||
.workplane()
|
|
||||||
.cboreHole(
|
|
||||||
diameter=self.hs_joint_axis_diam,
|
|
||||||
cboreDiameter=self.hs_joint_axis_cbore_diam,
|
|
||||||
cboreDepth=self.hs_joint_axis_cbore_depth,
|
|
||||||
)
|
|
||||||
.clean()
|
|
||||||
)
|
|
||||||
result.faces("<Z").tag("base")
|
|
||||||
return result
|
|
||||||
|
|
||||||
@assembly()
|
@assembly()
|
||||||
def assembly(self) -> Cq.Assembly:
|
def assembly(self, with_root_joint: bool = False) -> Cq.Assembly:
|
||||||
harness = self.surface()
|
harness = self.surface()
|
||||||
mannequin_z = self.mannequin.shoulder_to_waist * 0.6
|
mannequin_z = self.mannequin.shoulder_to_waist * 0.6
|
||||||
|
|
||||||
|
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
.addS(
|
.addS(
|
||||||
|
@ -224,13 +159,12 @@ class Harness(Model):
|
||||||
loc=Cq.Location((0, -self.thickness, -mannequin_z), (0, 0, 1), 180))
|
loc=Cq.Location((0, -self.thickness, -mannequin_z), (0, 0, 1), 180))
|
||||||
.constrain("mannequin", "Fixed")
|
.constrain("mannequin", "Fixed")
|
||||||
)
|
)
|
||||||
for name in ["l1", "l2", "l3", "r1", "r2", "r3"]:
|
if with_root_joint:
|
||||||
j = self.hs_joint_parent()
|
for name in ["l1", "l2", "l3", "r1", "r2", "r3"]:
|
||||||
result.addS(
|
result.addS(
|
||||||
j, name=name,
|
self.root_joint.assembly(), name=name,
|
||||||
role=Role.PARENT,
|
role=Role.PARENT,
|
||||||
material=Material.PLASTIC_PLA)
|
material=Material.PLASTIC_PLA)
|
||||||
for i in range(4):
|
self.add_root_joint_constraint(result, "base", name, name)
|
||||||
result.constrain(f"base?{name}_{i}", f"{name}?h{i}", "Point")
|
|
||||||
result.solve()
|
result.solve()
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -5,13 +5,204 @@ import cadquery as Cq
|
||||||
from nhf import Material, Role
|
from nhf import Material, Role
|
||||||
from nhf.build import Model, target, assembly
|
from nhf.build import Model, target, assembly
|
||||||
from nhf.parts.springs import TorsionSpring
|
from nhf.parts.springs import TorsionSpring
|
||||||
from nhf.parts.fasteners import FlatHeadBolt
|
from nhf.parts.fasteners import FlatHeadBolt, HexNut, ThreaddedKnob
|
||||||
from nhf.parts.joints import TorsionJoint
|
from nhf.parts.joints import TorsionJoint, HirthJoint
|
||||||
from nhf.parts.box import Hole, MountingBox, box_with_centre_holes
|
from nhf.parts.box import Hole, MountingBox, box_with_centre_holes
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
|
||||||
TOL = 1e-6
|
TOL = 1e-6
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RootJoint(Model):
|
||||||
|
"""
|
||||||
|
The Houjuu-Scarlett Mechanism
|
||||||
|
"""
|
||||||
|
knob: ThreaddedKnob = ThreaddedKnob(
|
||||||
|
mass=float('nan'),
|
||||||
|
diam_thread=12.0,
|
||||||
|
height_thread=30.0,
|
||||||
|
diam_knob=50.0,
|
||||||
|
# FIXME: Undetermined
|
||||||
|
diam_neck=30.0,
|
||||||
|
height_neck=10.0,
|
||||||
|
height_knob=10.0,
|
||||||
|
)
|
||||||
|
hex_nut: HexNut = HexNut(
|
||||||
|
# FIXME: Undetermined
|
||||||
|
mass=float('nan'),
|
||||||
|
|
||||||
|
diam_thread=12.0,
|
||||||
|
pitch=1.75,
|
||||||
|
thickness=9.8,
|
||||||
|
width=18.9,
|
||||||
|
)
|
||||||
|
hirth_joint: HirthJoint = field(default_factory=lambda: HirthJoint(
|
||||||
|
radius=25.0,
|
||||||
|
radius_inner=15.0,
|
||||||
|
tooth_height=7.0,
|
||||||
|
base_height=5.0,
|
||||||
|
n_tooth=24,
|
||||||
|
))
|
||||||
|
parent_width: float = 85
|
||||||
|
parent_thickness: float = 10
|
||||||
|
parent_corner_fillet: float = 5
|
||||||
|
parent_corner_cbore_diam: float = 12
|
||||||
|
parent_corner_cbore_depth: float = 2
|
||||||
|
parent_corner_inset: float = 12
|
||||||
|
parent_mount_thickness: float = 25.4 / 16
|
||||||
|
|
||||||
|
child_corner_dx: float = 17.0
|
||||||
|
child_corner_dz: float = 24.0
|
||||||
|
|
||||||
|
axis_diam: float = 12.0
|
||||||
|
axis_cbore_diam: float = 20
|
||||||
|
axis_cbore_depth: float = 3
|
||||||
|
corner_hole_diam: float = 6.0
|
||||||
|
|
||||||
|
child_height: float = 60.0
|
||||||
|
child_width: float = 50.0
|
||||||
|
child_mount_thickness: float = 25.4 / 8
|
||||||
|
|
||||||
|
def corner_pos(self) -> list[tuple[int, int]]:
|
||||||
|
"""
|
||||||
|
Generates a set of points corresponding to the connectorss
|
||||||
|
"""
|
||||||
|
dx = self.parent_width / 2 - self.parent_corner_inset
|
||||||
|
return [
|
||||||
|
(dx, dx),
|
||||||
|
(dx, -dx),
|
||||||
|
(-dx, -dx),
|
||||||
|
(-dx, dx),
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_height(self) -> float:
|
||||||
|
return self.parent_thickness + self.hirth_joint.total_height
|
||||||
|
|
||||||
|
@target(name="parent")
|
||||||
|
def parent(self):
|
||||||
|
"""
|
||||||
|
Parent part of the Houjuu-Scarlett joint, which is composed of a Hirth
|
||||||
|
coupling, a cylindrical base, and a mounting base.
|
||||||
|
"""
|
||||||
|
hirth = self.hirth_joint.generate()
|
||||||
|
conn = self.corner_pos()
|
||||||
|
result = (
|
||||||
|
Cq.Workplane('XY')
|
||||||
|
.box(
|
||||||
|
self.parent_width,
|
||||||
|
self.parent_width,
|
||||||
|
self.parent_thickness,
|
||||||
|
centered=(True, True, False))
|
||||||
|
.translate((0, 0, -self.parent_thickness))
|
||||||
|
.edges("|Z")
|
||||||
|
.fillet(self.parent_corner_fillet)
|
||||||
|
.faces(">Z")
|
||||||
|
.workplane()
|
||||||
|
.pushPoints(conn)
|
||||||
|
.cboreHole(
|
||||||
|
diameter=self.corner_hole_diam,
|
||||||
|
cboreDiameter=self.parent_corner_cbore_diam,
|
||||||
|
cboreDepth=self.parent_corner_cbore_depth)
|
||||||
|
)
|
||||||
|
# Creates a plane parallel to the holes but shifted to the base
|
||||||
|
plane = result.faces(">Z").workplane(offset=-self.parent_thickness)
|
||||||
|
|
||||||
|
for i, (px, py) in enumerate(conn):
|
||||||
|
plane.moveTo(px, py).tagPoint(f"h{i}")
|
||||||
|
result = (
|
||||||
|
result
|
||||||
|
.faces(">Z")
|
||||||
|
.workplane()
|
||||||
|
.union(hirth, tol=0.1)
|
||||||
|
.clean()
|
||||||
|
)
|
||||||
|
result = (
|
||||||
|
result.faces("<Z")
|
||||||
|
.workplane()
|
||||||
|
.hole(diameter=self.axis_diam)
|
||||||
|
.cut(self.hex_nut.generate().translate((0, 0, -self.parent_thickness)))
|
||||||
|
)
|
||||||
|
result.faces("<Z").tag("base")
|
||||||
|
return result
|
||||||
|
|
||||||
|
@target(name="child")
|
||||||
|
def child(self) -> Cq.Workplane:
|
||||||
|
hirth = self.hirth_joint.generate(is_mated=True)
|
||||||
|
dy = self.child_corner_dx
|
||||||
|
dx = self.child_corner_dz
|
||||||
|
conn = [
|
||||||
|
(-dx, -dy),
|
||||||
|
(dx, -dy),
|
||||||
|
(dx, dy),
|
||||||
|
(-dx, dy),
|
||||||
|
]
|
||||||
|
result = (
|
||||||
|
Cq.Workplane('XY')
|
||||||
|
.box(
|
||||||
|
self.child_height,
|
||||||
|
self.child_width,
|
||||||
|
self.hirth_joint.base_height,
|
||||||
|
centered=(True, True, False))
|
||||||
|
#.translate((0, 0, -self.base_joint.base_height))
|
||||||
|
#.edges("|Z")
|
||||||
|
#.fillet(self.hs_joint_corner_fillet)
|
||||||
|
.faces(">Z")
|
||||||
|
.workplane()
|
||||||
|
.pushPoints(conn)
|
||||||
|
.hole(self.corner_hole_diam)
|
||||||
|
)
|
||||||
|
# Creates a plane parallel to the holes but shifted to the base
|
||||||
|
plane = result.faces(">Z").workplane(offset=-self.hirth_joint.base_height)
|
||||||
|
|
||||||
|
for i, (px, py) in enumerate(conn):
|
||||||
|
plane.moveTo(px, py).tagPlane(f"conn{i}")
|
||||||
|
result = (
|
||||||
|
result
|
||||||
|
.faces(">Z")
|
||||||
|
.workplane()
|
||||||
|
.union(hirth, tol=0.1)
|
||||||
|
.clean()
|
||||||
|
)
|
||||||
|
result = (
|
||||||
|
result.faces("<Z")
|
||||||
|
.workplane()
|
||||||
|
.hole(self.axis_diam)
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def assembly(self,
|
||||||
|
offset: int = 0,
|
||||||
|
fastener_pos: float = 0) -> Cq.Assembly:
|
||||||
|
"""
|
||||||
|
Specify knob position to determine the position of the knob from fully
|
||||||
|
inserted (0) or fully uninserted (1)
|
||||||
|
"""
|
||||||
|
knob_h = self.total_height + self.child_mount_thickness
|
||||||
|
result = (
|
||||||
|
Cq.Assembly()
|
||||||
|
.addS(self.parent(), name="parent",
|
||||||
|
material=Material.PLASTIC_PLA,
|
||||||
|
role=Role.PARENT)
|
||||||
|
.constrain("parent", "Fixed")
|
||||||
|
.addS(self.child(), name="child",
|
||||||
|
material=Material.PLASTIC_PLA,
|
||||||
|
role=Role.CHILD)
|
||||||
|
.addS(self.hex_nut.assembly(), name="hex_nut")
|
||||||
|
.addS(self.knob.assembly(), name="knob",
|
||||||
|
loc=Cq.Location((0, 0, knob_h * fastener_pos)))
|
||||||
|
.constrain("knob/thread", "Fixed")
|
||||||
|
.constrain("hex_nut?bot", "parent?base", "Plane", param=0)
|
||||||
|
.constrain("hex_nut?dirX", "parent@faces@>X", "Axis", param=0)
|
||||||
|
)
|
||||||
|
self.hirth_joint.add_constraints(
|
||||||
|
result,
|
||||||
|
"parent",
|
||||||
|
"child",
|
||||||
|
offset=offset
|
||||||
|
)
|
||||||
|
return result.solve()
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ShoulderJoint(Model):
|
class ShoulderJoint(Model):
|
||||||
|
|
||||||
|
@ -228,10 +419,11 @@ class ShoulderJoint(Model):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@assembly()
|
@assembly()
|
||||||
def assembly(self, deflection: float = 0) -> Cq.Assembly:
|
def assembly(self, fastener_pos: float = 0.0, deflection: float = 0) -> Cq.Assembly:
|
||||||
directrix = self.directrix_id
|
directrix = self.directrix_id
|
||||||
mat = Material.RESIN_TRANSPERENT
|
mat = Material.RESIN_TRANSPERENT
|
||||||
mat_spring = Material.STEEL_SPRING
|
mat_spring = Material.STEEL_SPRING
|
||||||
|
bolt_z = self.height / 2 + self.bolt.height_thread * (fastener_pos - 1)
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
.addS(self.child(), name="child",
|
.addS(self.child(), name="child",
|
||||||
|
@ -239,18 +431,21 @@ class ShoulderJoint(Model):
|
||||||
.constrain("child/core", "Fixed")
|
.constrain("child/core", "Fixed")
|
||||||
.addS(self.torsion_joint.spring.generate(deflection=-deflection), name="spring_top",
|
.addS(self.torsion_joint.spring.generate(deflection=-deflection), name="spring_top",
|
||||||
role=Role.DAMPING, material=mat_spring)
|
role=Role.DAMPING, material=mat_spring)
|
||||||
.addS(self.bolt.assembly(), name="bolt_top")
|
|
||||||
.addS(self.parent_top(),
|
.addS(self.parent_top(),
|
||||||
name="parent_top",
|
name="parent_top",
|
||||||
role=Role.PARENT, material=mat)
|
role=Role.PARENT, material=mat)
|
||||||
.addS(self.torsion_joint.spring.generate(deflection=deflection), name="spring_bot",
|
.addS(self.torsion_joint.spring.generate(deflection=deflection), name="spring_bot",
|
||||||
role=Role.DAMPING, material=mat_spring)
|
role=Role.DAMPING, material=mat_spring)
|
||||||
.addS(self.bolt.assembly(), name="bolt_bot")
|
|
||||||
.addS(self.parent_bot(),
|
.addS(self.parent_bot(),
|
||||||
name="parent_bot",
|
name="parent_bot",
|
||||||
role=Role.PARENT, material=mat)
|
role=Role.PARENT, material=mat)
|
||||||
.constrain("bolt_top/thread?root", "parent_top/track?bot", "Plane", param=0)
|
# Fasteners
|
||||||
.constrain("bolt_bot/thread?root", "parent_bot/track?bot", "Plane", param=0)
|
.addS(self.bolt.assembly(), name="bolt_top",
|
||||||
|
loc=Cq.Location((0, 0, bolt_z)))
|
||||||
|
.constrain("bolt_top/thread?root", 'Fixed')
|
||||||
|
.addS(self.bolt.assembly(), name="bolt_bot",
|
||||||
|
loc=Cq.Location((0, 0, -bolt_z), (1,0,0), 180))
|
||||||
|
.constrain("bolt_bot/thread?root", 'Fixed')
|
||||||
)
|
)
|
||||||
TorsionJoint.add_constraints(
|
TorsionJoint.add_constraints(
|
||||||
result,
|
result,
|
||||||
|
|
|
@ -11,7 +11,7 @@ from nhf import Material, Role
|
||||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||||
from nhf.parts.box import box_with_centre_holes, MountingBox, Hole
|
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.touhou.houjuu_nue.joints import ShoulderJoint, ElbowJoint, DiskJoint
|
from nhf.touhou.houjuu_nue.joints import RootJoint, ShoulderJoint, ElbowJoint, DiskJoint
|
||||||
from nhf.parts.planar import extrude_with_markers
|
from nhf.parts.planar import extrude_with_markers
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
|
||||||
|
@ -20,19 +20,8 @@ class WingProfile(Model):
|
||||||
|
|
||||||
name: str = "wing"
|
name: str = "wing"
|
||||||
|
|
||||||
base_joint: HirthJoint = field(default_factory=lambda: HirthJoint(
|
root_joint: RootJoint = field(default_factory=lambda: RootJoint())
|
||||||
radius=25.0,
|
|
||||||
radius_inner=15.0,
|
|
||||||
tooth_height=7.0,
|
|
||||||
base_height=5,
|
|
||||||
n_tooth=24,
|
|
||||||
))
|
|
||||||
base_width: float = 80.0
|
base_width: float = 80.0
|
||||||
hs_joint_corner_dx: float = 17.0
|
|
||||||
hs_joint_corner_dz: float = 24.0
|
|
||||||
hs_joint_corner_hole_diam: float = 6.0
|
|
||||||
hs_joint_axis_diam: float = 12.0
|
|
||||||
base_plate_width: float = 50.0
|
|
||||||
|
|
||||||
panel_thickness: float = 25.4 / 16
|
panel_thickness: float = 25.4 / 16
|
||||||
spacer_thickness: float = 25.4 / 8
|
spacer_thickness: float = 25.4 / 8
|
||||||
|
@ -108,6 +97,8 @@ class WingProfile(Model):
|
||||||
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
|
||||||
self.shoulder_axle_loc = Cq.Location.from2d(self.shoulder_tip_x, self.shoulder_tip_y - self.shoulder_width / 2, -self.shoulder_angle_bias)
|
self.shoulder_axle_loc = Cq.Location.from2d(self.shoulder_tip_x, self.shoulder_tip_y - self.shoulder_width / 2, -self.shoulder_angle_bias)
|
||||||
|
|
||||||
|
assert self.spacer_thickness == self.root_joint.child_mount_thickness
|
||||||
|
|
||||||
@submodel(name="shoulder-joint")
|
@submodel(name="shoulder-joint")
|
||||||
def submodel_shoulder_joint(self) -> Model:
|
def submodel_shoulder_joint(self) -> Model:
|
||||||
return self.shoulder_joint
|
return self.shoulder_joint
|
||||||
|
@ -126,55 +117,6 @@ class WingProfile(Model):
|
||||||
def shoulder_height(self) -> float:
|
def shoulder_height(self) -> float:
|
||||||
return self.shoulder_joint.height
|
return self.shoulder_joint.height
|
||||||
|
|
||||||
@target(name="base-hs-joint")
|
|
||||||
def base_hs_joint(self) -> Cq.Workplane:
|
|
||||||
"""
|
|
||||||
Parent part of the Houjuu-Scarlett joint, which is composed of a Hirth
|
|
||||||
coupling, a cylindrical base, and a mounting base.
|
|
||||||
"""
|
|
||||||
hirth = self.base_joint.generate(is_mated=True)
|
|
||||||
dy = self.hs_joint_corner_dx
|
|
||||||
dx = self.hs_joint_corner_dz
|
|
||||||
conn = [
|
|
||||||
(-dx, -dy),
|
|
||||||
(dx, -dy),
|
|
||||||
(dx, dy),
|
|
||||||
(-dx, dy),
|
|
||||||
]
|
|
||||||
result = (
|
|
||||||
Cq.Workplane('XY')
|
|
||||||
.box(
|
|
||||||
self.root_height,
|
|
||||||
self.base_plate_width,
|
|
||||||
self.base_joint.base_height,
|
|
||||||
centered=(True, True, False))
|
|
||||||
#.translate((0, 0, -self.base_joint.base_height))
|
|
||||||
#.edges("|Z")
|
|
||||||
#.fillet(self.hs_joint_corner_fillet)
|
|
||||||
.faces(">Z")
|
|
||||||
.workplane()
|
|
||||||
.pushPoints(conn)
|
|
||||||
.hole(self.hs_joint_corner_hole_diam)
|
|
||||||
)
|
|
||||||
# Creates a plane parallel to the holes but shifted to the base
|
|
||||||
plane = result.faces(">Z").workplane(offset=-self.base_joint.base_height)
|
|
||||||
|
|
||||||
for i, (px, py) in enumerate(conn):
|
|
||||||
plane.moveTo(px, py).tagPlane(f"conn{i}")
|
|
||||||
result = (
|
|
||||||
result
|
|
||||||
.faces(">Z")
|
|
||||||
.workplane()
|
|
||||||
.union(hirth, tol=0.1)
|
|
||||||
.clean()
|
|
||||||
)
|
|
||||||
result = (
|
|
||||||
result.faces("<Z")
|
|
||||||
.workplane()
|
|
||||||
.hole(self.hs_joint_axis_diam)
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def outer_profile_s0(self) -> Cq.Sketch:
|
def outer_profile_s0(self) -> Cq.Sketch:
|
||||||
"""
|
"""
|
||||||
The outer boundary of s0, used to produce the curved panel and the
|
The outer boundary of s0, used to produce the curved panel and the
|
||||||
|
@ -275,11 +217,11 @@ class WingProfile(Model):
|
||||||
"""
|
"""
|
||||||
Should be cut
|
Should be cut
|
||||||
"""
|
"""
|
||||||
assert self.base_plate_width < self.base_width
|
assert self.root_joint.child_width < self.base_width
|
||||||
assert self.hs_joint_corner_dx * 2 < self.base_width
|
assert self.root_joint.child_corner_dx * 2 < self.base_width
|
||||||
assert self.hs_joint_corner_dz * 2 < self.root_height
|
assert self.root_joint.child_corner_dz * 2 < self.root_height
|
||||||
dy = self.hs_joint_corner_dx
|
dy = self.root_joint.child_corner_dx
|
||||||
dx = self.hs_joint_corner_dz
|
dx = self.root_joint.child_corner_dz
|
||||||
holes = [
|
holes = [
|
||||||
Hole(x=-dx, y=-dy),
|
Hole(x=-dx, y=-dy),
|
||||||
Hole(x=dx, y=-dy),
|
Hole(x=dx, y=-dy),
|
||||||
|
@ -288,17 +230,17 @@ class WingProfile(Model):
|
||||||
]
|
]
|
||||||
return MountingBox(
|
return MountingBox(
|
||||||
length=self.root_height,
|
length=self.root_height,
|
||||||
width=self.base_plate_width,
|
width=self.root_joint.child_width,
|
||||||
thickness=self.spacer_thickness,
|
thickness=self.spacer_thickness,
|
||||||
holes=holes,
|
holes=holes,
|
||||||
hole_diam=self.hs_joint_corner_hole_diam,
|
hole_diam=self.root_joint.corner_hole_diam,
|
||||||
centred=(True, True),
|
centred=(True, True),
|
||||||
flip_y=self.flip,
|
flip_y=self.flip,
|
||||||
)
|
)
|
||||||
|
|
||||||
def surface_s0(self, top: bool = False) -> Cq.Workplane:
|
def surface_s0(self, top: bool = False) -> Cq.Workplane:
|
||||||
base_dx = -(self.base_width - self.base_plate_width) / 2
|
base_dx = -(self.base_width - self.root_joint.child_width) / 2
|
||||||
base_dy = self.base_joint.joint_height
|
base_dy = self.root_joint.hirth_joint.joint_height
|
||||||
loc_tip = Cq.Location(0, -self.shoulder_joint.parent_lip_width / 2)
|
loc_tip = Cq.Location(0, -self.shoulder_joint.parent_lip_width / 2)
|
||||||
tags = [
|
tags = [
|
||||||
("shoulder",
|
("shoulder",
|
||||||
|
@ -351,14 +293,6 @@ class WingProfile(Model):
|
||||||
.constrain(f"{tag}?{top_tag}", f"top?{tag}", "Plane")
|
.constrain(f"{tag}?{top_tag}", f"top?{tag}", "Plane")
|
||||||
.constrain(f"{tag}?dir", f"top?{tag}_dir", "Axis")
|
.constrain(f"{tag}?dir", f"top?{tag}_dir", "Axis")
|
||||||
)
|
)
|
||||||
hs_joint = self.base_hs_joint()
|
|
||||||
(
|
|
||||||
result
|
|
||||||
.addS(hs_joint, name="hs", role=Role.CHILD, material=self.mat_hs_joint)
|
|
||||||
.constrain("hs?conn0", "base?conn0", "Plane", param=0)
|
|
||||||
.constrain("hs?conn1", "base?conn1", "Plane", param=0)
|
|
||||||
.constrain("hs?conn2", "base?conn2", "Plane", param=0)
|
|
||||||
)
|
|
||||||
return result.solve()
|
return result.solve()
|
||||||
|
|
||||||
|
|
||||||
|
@ -627,9 +561,11 @@ class WingProfile(Model):
|
||||||
parts: Optional[list[str]] = None,
|
parts: Optional[list[str]] = None,
|
||||||
shoulder_deflection: float = 0.0,
|
shoulder_deflection: float = 0.0,
|
||||||
elbow_wrist_deflection: float = 0.0,
|
elbow_wrist_deflection: float = 0.0,
|
||||||
|
root_offset: int = 5,
|
||||||
|
fastener_pos: float = 0.0,
|
||||||
) -> Cq.Assembly():
|
) -> Cq.Assembly():
|
||||||
if parts is None:
|
if parts is None:
|
||||||
parts = ["s0", "shoulder", "s1", "elbow", "s2", "wrist", "s3"]
|
parts = ["root", "s0", "shoulder", "s1", "elbow", "s2", "wrist", "s3"]
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
)
|
)
|
||||||
|
@ -639,8 +575,23 @@ class WingProfile(Model):
|
||||||
|
|
||||||
if "s0" in parts:
|
if "s0" in parts:
|
||||||
result.add(self.assembly_s0(), name="s0")
|
result.add(self.assembly_s0(), name="s0")
|
||||||
|
if "root" in parts:
|
||||||
|
result.addS(self.root_joint.assembly(
|
||||||
|
offset=root_offset,
|
||||||
|
fastener_pos=fastener_pos,
|
||||||
|
), name="root")
|
||||||
|
result.constrain("root/parent", "Fixed")
|
||||||
|
if "s0" in parts and "root" in parts:
|
||||||
|
(
|
||||||
|
result
|
||||||
|
.constrain("s0/base?conn0", "root/child?conn0", "Plane", param=0)
|
||||||
|
.constrain("s0/base?conn1", "root/child?conn1", "Plane", param=0)
|
||||||
|
.constrain("s0/base?conn2", "root/child?conn2", "Plane", param=0)
|
||||||
|
)
|
||||||
if "shoulder" in parts:
|
if "shoulder" in parts:
|
||||||
result.add(self.shoulder_joint.assembly(deflection=shoulder_deflection * 80), name="shoulder")
|
result.add(self.shoulder_joint.assembly(
|
||||||
|
fastener_pos=fastener_pos,
|
||||||
|
deflection=shoulder_deflection * 80), name="shoulder")
|
||||||
if "s0" in parts and "shoulder" in parts:
|
if "s0" in parts and "shoulder" in parts:
|
||||||
(
|
(
|
||||||
result
|
result
|
||||||
|
|
Loading…
Reference in New Issue