feat: Bridging joints for root
This commit is contained in:
parent
bbfeb50f8e
commit
c1107aed2e
|
@ -8,7 +8,7 @@ import nhf.utils
|
|||
|
||||
TOL = 1e-6
|
||||
|
||||
@dataclass
|
||||
@dataclass(frozen=True)
|
||||
class HirthJoint:
|
||||
"""
|
||||
A Hirth joint attached to a cylindrical base
|
||||
|
|
|
@ -33,7 +33,6 @@ from dataclasses import dataclass, field
|
|||
from typing import Optional
|
||||
import cadquery as Cq
|
||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
from nhf.parts.joints import HirthJoint, TorsionJoint
|
||||
import nhf.touhou.houjuu_nue.wing as MW
|
||||
import nhf.touhou.houjuu_nue.trident as MT
|
||||
import nhf.touhou.houjuu_nue.joints as MJ
|
||||
|
@ -53,6 +52,10 @@ class Parameters(Model):
|
|||
|
||||
wing_r1: MW.WingR = field(default_factory=lambda: MW.WingR(
|
||||
name="r1",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(0,1,1,1),
|
||||
parent_substrate_cull_edges=(0,0,1,0),
|
||||
),
|
||||
shoulder_angle_bias=WING_DEFLECT_ODD,
|
||||
s0_top_hole=False,
|
||||
s0_bot_hole=True,
|
||||
|
@ -60,18 +63,30 @@ class Parameters(Model):
|
|||
))
|
||||
wing_r2: MW.WingR = field(default_factory=lambda: MW.WingR(
|
||||
name="r2",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(1,1,1,1),
|
||||
parent_substrate_cull_edges=(0,0,1,0),
|
||||
),
|
||||
shoulder_angle_bias=WING_DEFLECT_EVEN,
|
||||
s0_top_hole=True,
|
||||
s0_bot_hole=True,
|
||||
))
|
||||
wing_r3: MW.WingR = field(default_factory=lambda: MW.WingR(
|
||||
name="r3",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(1,1,1,0),
|
||||
parent_substrate_cull_edges=(0,0,1,0),
|
||||
),
|
||||
shoulder_angle_bias=WING_DEFLECT_ODD,
|
||||
s0_top_hole=True,
|
||||
s0_bot_hole=False,
|
||||
))
|
||||
wing_l1: MW.WingL = field(default_factory=lambda: MW.WingL(
|
||||
name="l1",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(1,0,1,1),
|
||||
parent_substrate_cull_edges=(1,0,0,0),
|
||||
),
|
||||
shoulder_angle_bias=WING_DEFLECT_EVEN,
|
||||
wrist_angle=-60.0,
|
||||
s0_top_hole=False,
|
||||
|
@ -79,6 +94,10 @@ class Parameters(Model):
|
|||
))
|
||||
wing_l2: MW.WingL = field(default_factory=lambda: MW.WingL(
|
||||
name="l2",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(1,1,1,1),
|
||||
parent_substrate_cull_edges=(1,0,0,0),
|
||||
),
|
||||
wrist_angle=-30.0,
|
||||
shoulder_angle_bias=WING_DEFLECT_ODD,
|
||||
s0_top_hole=True,
|
||||
|
@ -86,6 +105,10 @@ class Parameters(Model):
|
|||
))
|
||||
wing_l3: MW.WingL = field(default_factory=lambda: MW.WingL(
|
||||
name="l3",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(1,1,0,1),
|
||||
parent_substrate_cull_edges=(1,0,0,0),
|
||||
),
|
||||
shoulder_angle_bias=WING_DEFLECT_EVEN,
|
||||
wrist_angle=-0.0,
|
||||
s0_top_hole=True,
|
||||
|
@ -96,14 +119,6 @@ class Parameters(Model):
|
|||
|
||||
def __post_init__(self):
|
||||
super().__init__(name="houjuu-nue")
|
||||
self.wing_r1.root_joint = self.harness.root_joint
|
||||
self.wing_r2.root_joint = self.harness.root_joint
|
||||
self.wing_r3.root_joint = self.harness.root_joint
|
||||
self.wing_l1.root_joint = self.harness.root_joint
|
||||
self.wing_l2.root_joint = self.harness.root_joint
|
||||
self.wing_l3.root_joint = self.harness.root_joint
|
||||
|
||||
self.wing_r1.shoulder_joint.torsion_joint
|
||||
|
||||
@submodel(name="harness")
|
||||
def submodel_harness(self) -> Model:
|
||||
|
|
|
@ -4,6 +4,7 @@ from nhf.parts.joints import HirthJoint
|
|||
from nhf import Material, Role
|
||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
from nhf.touhou.houjuu_nue.joints import RootJoint
|
||||
from nhf.parts.box import MountingBox
|
||||
import nhf.utils
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
|
@ -46,21 +47,21 @@ class Mannequin:
|
|||
return result.translate((0, self.torso_thickness / 2, 0))
|
||||
|
||||
|
||||
BASE_POS_X = 60.0
|
||||
BASE_POS_X = 70.0
|
||||
BASE_POS_Y = 100.0
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Harness(Model):
|
||||
thickness: float = 25.4 / 8
|
||||
width: float = 220.0
|
||||
height: float = 310.0
|
||||
height: float = 304.8
|
||||
fillet: float = 10.0
|
||||
|
||||
wing_base_pos: list[tuple[str, float, float]] = field(default_factory=lambda: [
|
||||
("r1", BASE_POS_X + 10, BASE_POS_Y),
|
||||
("l1", -BASE_POS_X - 10, BASE_POS_Y),
|
||||
("r2", BASE_POS_X + 10, 0),
|
||||
("l2", -BASE_POS_X - 10, 0),
|
||||
("r1", BASE_POS_X, BASE_POS_Y),
|
||||
("l1", -BASE_POS_X, BASE_POS_Y),
|
||||
("r2", BASE_POS_X, 0),
|
||||
("l2", -BASE_POS_X, 0),
|
||||
("r3", BASE_POS_X, -BASE_POS_Y),
|
||||
("l3", -BASE_POS_X, -BASE_POS_Y),
|
||||
])
|
||||
|
@ -72,9 +73,12 @@ class Harness(Model):
|
|||
def __post_init__(self):
|
||||
super().__init__(name="harness")
|
||||
|
||||
@submodel(name="root-joint")
|
||||
def submodel_root_joint(self) -> Model:
|
||||
return self.root_joint
|
||||
@submodel(name="bridge-pair-horizontal")
|
||||
def bridge_pair_horizontal(self) -> MountingBox:
|
||||
return self.root_joint.bridge_pair_horizontal(centre_dx=BASE_POS_X * 2)
|
||||
@submodel(name="bridge-pair-vertical")
|
||||
def bridge_pair_vertical(self) -> MountingBox:
|
||||
return self.root_joint.bridge_pair_vertical(centre_dy=BASE_POS_Y)
|
||||
|
||||
@target(name="profile", kind=TargetKind.DXF)
|
||||
def profile(self) -> Cq.Sketch:
|
||||
|
@ -126,7 +130,7 @@ class Harness(Model):
|
|||
conn = [(px + x, py + y) for px, py
|
||||
in self.root_joint.corner_pos()]
|
||||
for i, (px, py) in enumerate(conn):
|
||||
plane.moveTo(px, py).tagPoint(f"{tag}_{i}")
|
||||
plane.moveTo(px, py).tagPlane(f"{tag}_{i}")
|
||||
return result
|
||||
|
||||
def add_root_joint_constraint(
|
||||
|
@ -144,7 +148,6 @@ class Harness(Model):
|
|||
harness = self.surface()
|
||||
mannequin_z = self.mannequin.shoulder_to_waist * 0.6
|
||||
|
||||
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.addS(
|
||||
|
@ -159,6 +162,37 @@ class Harness(Model):
|
|||
loc=Cq.Location((0, -self.thickness, -mannequin_z), (0, 0, 1), 180))
|
||||
.constrain("mannequin", "Fixed")
|
||||
)
|
||||
bridge_h = self.bridge_pair_horizontal().generate()
|
||||
for i in [1,2,3]:
|
||||
name = f"r{i}l{i}_bridge"
|
||||
(
|
||||
result
|
||||
.addS(
|
||||
bridge_h, name=name,
|
||||
role=Role.FIXTURE,
|
||||
material=Material.WOOD_BIRCH,
|
||||
)
|
||||
.constrain(f"{name}?conn0_rev", f"base?r{i}_1", "Point")
|
||||
.constrain(f"{name}?conn1_rev", f"base?l{i}_0", "Point")
|
||||
.constrain(f"{name}?conn2_rev", f"base?l{i}_3", "Point")
|
||||
.constrain(f"{name}?conn3_rev", f"base?r{i}_2", "Point")
|
||||
)
|
||||
bridge_v = self.bridge_pair_vertical().generate()
|
||||
(
|
||||
result
|
||||
.addS(bridge_v, name="r1_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH)
|
||||
.constrain("r1_bridge?conn0_rev", "base?r1_3", 'Plane')
|
||||
.constrain("r1_bridge?conn1_rev", "base?r2_0", 'Plane')
|
||||
.addS(bridge_v, name="r2_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH)
|
||||
.constrain("r2_bridge?conn0_rev", "base?r2_3", 'Plane')
|
||||
.constrain("r2_bridge?conn1_rev", "base?r3_0", 'Plane')
|
||||
.addS(bridge_v, name="l1_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH)
|
||||
.constrain("l1_bridge?conn0_rev", "base?l1_2", 'Plane')
|
||||
.constrain("l1_bridge?conn1_rev", "base?l2_1", 'Plane')
|
||||
.addS(bridge_v, name="l2_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH)
|
||||
.constrain("l2_bridge?conn0_rev", "base?l2_2", 'Plane')
|
||||
.constrain("l2_bridge?conn1_rev", "base?l3_1", 'Plane')
|
||||
)
|
||||
if with_root_joint:
|
||||
for name in ["l1", "l2", "l3", "r1", "r2", "r3"]:
|
||||
result.addS(
|
||||
|
|
|
@ -4,6 +4,7 @@ from typing import Optional, Tuple
|
|||
import cadquery as Cq
|
||||
from nhf import Material, Role
|
||||
from nhf.build import Model, target, assembly
|
||||
from nhf.parts.box import MountingBox
|
||||
from nhf.parts.springs import TorsionSpring
|
||||
from nhf.parts.fasteners import FlatHeadBolt, HexNut, ThreaddedKnob
|
||||
from nhf.parts.joints import TorsionJoint, HirthJoint
|
||||
|
@ -88,6 +89,10 @@ class RootJoint(Model):
|
|||
parent_corner_inset: float = 12
|
||||
parent_mount_thickness: float = 25.4 / 16
|
||||
|
||||
parent_substrate_thickness: float = 25.4 / 16
|
||||
parent_substrate_cull_corners: Tuple[bool, bool, bool, bool] = (False, False, True, False)
|
||||
parent_substrate_cull_edges: Tuple[bool, bool, bool, bool] = (False, False, True, False)
|
||||
|
||||
child_corner_dx: float = 17.0
|
||||
child_corner_dz: float = 24.0
|
||||
|
||||
|
@ -103,6 +108,8 @@ class RootJoint(Model):
|
|||
def __post_init__(self):
|
||||
assert self.child_extra_thickness > 0.0
|
||||
assert self.parent_thickness >= self.hex_nut.thickness
|
||||
# twice the buffer allocated for substratum
|
||||
assert self.parent_width >= 4 * self.parent_corner_inset
|
||||
|
||||
def corner_pos(self) -> list[tuple[int, int]]:
|
||||
"""
|
||||
|
@ -111,9 +118,9 @@ class RootJoint(Model):
|
|||
dx = self.parent_width / 2 - self.parent_corner_inset
|
||||
return [
|
||||
(dx, dx),
|
||||
(dx, -dx),
|
||||
(-dx, -dx),
|
||||
(-dx, dx),
|
||||
(-dx, -dx),
|
||||
(dx, -dx),
|
||||
]
|
||||
|
||||
@property
|
||||
|
@ -127,6 +134,45 @@ class RootJoint(Model):
|
|||
def base_to_surface_thickness(self) -> float:
|
||||
return self.hirth_joint.joint_height + self.child_extra_thickness
|
||||
|
||||
@property
|
||||
def substrate_inset(self) -> float:
|
||||
return self.parent_corner_inset * 2
|
||||
|
||||
def bridge_pair_horizontal(self, centre_dx: float) -> MountingBox:
|
||||
hole_dx = centre_dx / 2 - self.parent_width / 2 + self.parent_corner_inset
|
||||
hole_dy = self.parent_width / 2 - self.parent_corner_inset
|
||||
holes = [
|
||||
Hole(x=hole_dx, y=hole_dy),
|
||||
Hole(x=-hole_dx, y=hole_dy),
|
||||
Hole(x=-hole_dx, y=-hole_dy),
|
||||
Hole(x=hole_dx, y=-hole_dy),
|
||||
]
|
||||
return MountingBox(
|
||||
length=centre_dx - self.parent_width + self.substrate_inset * 2,
|
||||
width=self.parent_width,
|
||||
thickness=self.parent_substrate_thickness,
|
||||
hole_diam=self.corner_hole_diam,
|
||||
holes=holes,
|
||||
centred=(True, True),
|
||||
generate_reverse_tags=True,
|
||||
)
|
||||
def bridge_pair_vertical(self, centre_dy: float) -> MountingBox:
|
||||
hole_dy = centre_dy / 2 - self.parent_width / 2 + self.parent_corner_inset
|
||||
holes = [
|
||||
Hole(x=0, y=hole_dy),
|
||||
Hole(x=0, y=-hole_dy),
|
||||
]
|
||||
return MountingBox(
|
||||
length=self.substrate_inset,
|
||||
width=centre_dy - self.parent_width + self.substrate_inset * 2,
|
||||
thickness=self.parent_substrate_thickness,
|
||||
hole_diam=self.corner_hole_diam,
|
||||
holes=holes,
|
||||
centred=(True, True),
|
||||
generate_reverse_tags=True,
|
||||
)
|
||||
|
||||
|
||||
@target(name="parent")
|
||||
def parent(self):
|
||||
"""
|
||||
|
@ -142,7 +188,6 @@ class RootJoint(Model):
|
|||
width=self.parent_width,
|
||||
height=self.parent_thickness,
|
||||
centered=(True, True, False))
|
||||
.translate((0, 0, -self.parent_thickness))
|
||||
.edges("|Z")
|
||||
.fillet(self.parent_corner_fillet)
|
||||
.faces(">Z")
|
||||
|
@ -153,6 +198,32 @@ class RootJoint(Model):
|
|||
cboreDiameter=self.parent_corner_cbore_diam,
|
||||
cboreDepth=self.parent_corner_cbore_depth)
|
||||
)
|
||||
sso = self.parent_width / 2 - self.substrate_inset
|
||||
loc_corner = Cq.Location((sso, sso, 0))
|
||||
loc_edge= Cq.Location((sso, -sso, 0))
|
||||
cut_corner = Cq.Solid.makeBox(
|
||||
length=self.parent_width,
|
||||
width=self.parent_width,
|
||||
height=self.parent_substrate_thickness,
|
||||
)
|
||||
cut_edge = Cq.Solid.makeBox(
|
||||
length=self.parent_width,
|
||||
width=self.parent_width - self.substrate_inset * 2,
|
||||
height=self.parent_substrate_thickness,
|
||||
)
|
||||
step = 90
|
||||
for i, flag in enumerate(self.parent_substrate_cull_corners):
|
||||
if not flag:
|
||||
continue
|
||||
loc = Cq.Location((0,0,0),(0,0,1), i * step) * loc_corner
|
||||
result = result.cut(cut_corner.located(loc))
|
||||
for i, flag in enumerate(self.parent_substrate_cull_edges):
|
||||
if not flag:
|
||||
continue
|
||||
loc = Cq.Location((0,0,0),(0,0,1), i * step) * loc_edge
|
||||
result = result.cut(cut_edge.located(loc))
|
||||
|
||||
result = result.translate((0, 0, -self.parent_thickness))
|
||||
# Creates a plane parallel to the holes but shifted to the base
|
||||
plane = result.faces(">Z").workplane(offset=-self.parent_thickness)
|
||||
|
||||
|
@ -171,7 +242,7 @@ class RootJoint(Model):
|
|||
.hole(diameter=self.axis_diam)
|
||||
.cut(self.hex_nut.generate().translate((0, 0, -self.parent_thickness)))
|
||||
)
|
||||
result.faces("<Z").tag("base")
|
||||
result.copyWorkplane(Cq.Workplane('XY', origin=(0,0,-self.parent_thickness))).tagPlane("base", direction="-Z")
|
||||
return result
|
||||
|
||||
@target(name="child")
|
||||
|
|
|
@ -142,6 +142,9 @@ class WingProfile(Model):
|
|||
extra = 2 * self.panel_thickness if self.flip else 0
|
||||
return self.s1_thickness - 2 * self.panel_thickness - extra
|
||||
|
||||
@submodel(name="root-joint")
|
||||
def submodel_root_joint(self) -> Model:
|
||||
return self.root_joint
|
||||
@submodel(name="shoulder-joint")
|
||||
def submodel_shoulder_joint(self) -> Model:
|
||||
return self.shoulder_joint
|
||||
|
@ -312,6 +315,7 @@ class WingProfile(Model):
|
|||
Hole(x=dx, y=-dy),
|
||||
Hole(x=dx, y=dy),
|
||||
Hole(x=-dx, y=dy),
|
||||
Hole(x=0, y=0, diam=self.root_joint.axis_diam, tag="axle"),
|
||||
]
|
||||
return MountingBox(
|
||||
length=self.root_height,
|
||||
|
|
Loading…
Reference in New Issue