cosplay: Touhou/Houjuu Nue #4
|
@ -21,7 +21,7 @@ class ArduinoUnoR3(Item):
|
||||||
# This is labeled in mirrored coordinates from top down (i.e. unmirrored from bottom up)
|
# This is labeled in mirrored coordinates from top down (i.e. unmirrored from bottom up)
|
||||||
holes: list[Tuple[float, float]] = field(default_factory=lambda: [
|
holes: list[Tuple[float, float]] = field(default_factory=lambda: [
|
||||||
(15.24, 2.54),
|
(15.24, 2.54),
|
||||||
(13.74, 50.80), # x coordinate not labeled on schematic
|
(15.24 - 1.270, 50.80), # x coordinate not labeled on schematic
|
||||||
(66.04, 17.78),
|
(66.04, 17.78),
|
||||||
(66.04, 45.72),
|
(66.04, 45.72),
|
||||||
])
|
])
|
||||||
|
@ -70,3 +70,66 @@ class ArduinoUnoR3(Item):
|
||||||
for i, (x, y) in enumerate(self.holes):
|
for i, (x, y) in enumerate(self.holes):
|
||||||
plane.moveTo(x, self.width - y).tagPlane(f"conn{i}", direction='-Z')
|
plane.moveTo(x, self.width - y).tagPlane(f"conn{i}", direction='-Z')
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BatteryBox18650(Item):
|
||||||
|
"""
|
||||||
|
A number of 18650 batteries in series
|
||||||
|
"""
|
||||||
|
mass: float = 17.4 + 68.80 * 3
|
||||||
|
length: float = 75.70
|
||||||
|
width_base: float = 61.46 - 18.48 - 20.18 * 2
|
||||||
|
battery_dist: float = 20.18
|
||||||
|
height: float = 19.66
|
||||||
|
# space from bottom to battery begin
|
||||||
|
thickness: float = 1.66
|
||||||
|
battery_diam: float = 18.48
|
||||||
|
battery_height: float = 68.80
|
||||||
|
n_batteries: int = 3
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
assert 2 * self.thickness < min(self.length, self.height)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return f"BatteryBox 18650*{self.n_batteries}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def role(self) -> Role:
|
||||||
|
return Role.ELECTRONIC
|
||||||
|
|
||||||
|
def generate(self) -> Cq.Workplane:
|
||||||
|
width = self.width_base + self.battery_dist * (self.n_batteries - 1) + self.battery_diam
|
||||||
|
return (
|
||||||
|
Cq.Workplane('XY')
|
||||||
|
.box(
|
||||||
|
length=self.length,
|
||||||
|
width=width,
|
||||||
|
height=self.height,
|
||||||
|
centered=(True, True, False),
|
||||||
|
)
|
||||||
|
.copyWorkplane(Cq.Workplane('XY', origin=(0, 0, self.thickness)))
|
||||||
|
.box(
|
||||||
|
length=self.length - self.thickness*2,
|
||||||
|
width=width - self.thickness*2,
|
||||||
|
height=self.height - self.thickness,
|
||||||
|
centered=(True, True, False),
|
||||||
|
combine='cut',
|
||||||
|
)
|
||||||
|
.copyWorkplane(Cq.Workplane('XY', origin=(-self.battery_height/2, 0, self.thickness + self.battery_diam/2)))
|
||||||
|
.rarray(
|
||||||
|
xSpacing=1,
|
||||||
|
ySpacing=self.battery_dist,
|
||||||
|
xCount=1,
|
||||||
|
yCount=self.n_batteries,
|
||||||
|
center=True,
|
||||||
|
)
|
||||||
|
.cylinder(
|
||||||
|
radius=self.battery_diam/2,
|
||||||
|
height=self.battery_height,
|
||||||
|
direct=(1, 0, 0),
|
||||||
|
centered=(True, True, False),
|
||||||
|
combine=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -11,7 +11,7 @@ from nhf.parts.box import MountingBox, Hole
|
||||||
from nhf.parts.fibre import tension_fibre
|
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.parts.electronics import ArduinoUnoR3
|
from nhf.parts.electronics import ArduinoUnoR3, BatteryBox18650
|
||||||
from nhf.touhou.houjuu_nue.common import NUT_COMMON, BOLT_COMMON
|
from nhf.touhou.houjuu_nue.common import NUT_COMMON, BOLT_COMMON
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
|
||||||
|
@ -210,68 +210,6 @@ class MountingBracket(Item):
|
||||||
result.copyWorkplane(Cq.Workplane('XY')).tagPlane("conn_mid")
|
result.copyWorkplane(Cq.Workplane('XY')).tagPlane("conn_mid")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class BatteryBox18650(Item):
|
|
||||||
"""
|
|
||||||
A number of 18650 batteries in series
|
|
||||||
"""
|
|
||||||
mass: float = 17.4 + 68.80 * 3
|
|
||||||
length: float = 75.70
|
|
||||||
width_base: float = 61.46 - 18.48 - 20.18 * 2
|
|
||||||
battery_dist: float = 20.18
|
|
||||||
height: float = 19.66
|
|
||||||
# space from bottom to battery begin
|
|
||||||
thickness: float = 1.66
|
|
||||||
battery_diam: float = 18.48
|
|
||||||
battery_height: float = 68.80
|
|
||||||
n_batteries: int = 3
|
|
||||||
|
|
||||||
def __post_init__(self):
|
|
||||||
assert 2 * self.thickness < min(self.length, self.height)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
return f"BatteryBox 18650*{self.n_batteries}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def role(self) -> Role:
|
|
||||||
return Role.ELECTRONIC
|
|
||||||
|
|
||||||
def generate(self) -> Cq.Workplane:
|
|
||||||
width = self.width_base + self.battery_dist * (self.n_batteries - 1) + self.battery_diam
|
|
||||||
return (
|
|
||||||
Cq.Workplane('XY')
|
|
||||||
.box(
|
|
||||||
length=self.length,
|
|
||||||
width=width,
|
|
||||||
height=self.height,
|
|
||||||
centered=(True, True, False),
|
|
||||||
)
|
|
||||||
.copyWorkplane(Cq.Workplane('XY', origin=(0, 0, self.thickness)))
|
|
||||||
.box(
|
|
||||||
length=self.length - self.thickness*2,
|
|
||||||
width=width - self.thickness*2,
|
|
||||||
height=self.height - self.thickness,
|
|
||||||
centered=(True, True, False),
|
|
||||||
combine='cut',
|
|
||||||
)
|
|
||||||
.copyWorkplane(Cq.Workplane('XY', origin=(-self.battery_height/2, 0, self.thickness + self.battery_diam/2)))
|
|
||||||
.rarray(
|
|
||||||
xSpacing=1,
|
|
||||||
ySpacing=self.battery_dist,
|
|
||||||
xCount=1,
|
|
||||||
yCount=self.n_batteries,
|
|
||||||
center=True,
|
|
||||||
)
|
|
||||||
.cylinder(
|
|
||||||
radius=self.battery_diam/2,
|
|
||||||
height=self.battery_height,
|
|
||||||
direct=(1, 0, 0),
|
|
||||||
centered=(True, True, False),
|
|
||||||
combine=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
LINEAR_ACTUATOR_50 = LinearActuator(
|
LINEAR_ACTUATOR_50 = LinearActuator(
|
||||||
mass=40.8,
|
mass=40.8,
|
||||||
|
@ -345,6 +283,15 @@ ELECTRONIC_MOUNT_HEXNUT = HexNut(
|
||||||
width=6.81,
|
width=6.81,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@dataclass(kw_only=True, frozen=True)
|
||||||
|
class Winch:
|
||||||
|
linear_motion_span: float
|
||||||
|
|
||||||
|
actuator: LinearActuator = LINEAR_ACTUATOR_21
|
||||||
|
nut: HexNut = LINEAR_ACTUATOR_HEX_NUT
|
||||||
|
bolt: FlatHeadBolt = LINEAR_ACTUATOR_BOLT
|
||||||
|
bracket: MountingBracket = LINEAR_ACTUATOR_BRACKET
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
class Flexor:
|
class Flexor:
|
||||||
"""
|
"""
|
||||||
|
@ -529,17 +476,10 @@ class ElectronicBoard(Model):
|
||||||
role=Role.ELECTRONIC | Role.STRUCTURE, material=self.material)
|
role=Role.ELECTRONIC | Role.STRUCTURE, material=self.material)
|
||||||
)
|
)
|
||||||
for hole in self.mount_holes:
|
for hole in self.mount_holes:
|
||||||
spacer_name = f"{hole.tag}_spacer"
|
|
||||||
bolt_name = f"{hole.tag}_bolt"
|
bolt_name = f"{hole.tag}_bolt"
|
||||||
(
|
(
|
||||||
result
|
result
|
||||||
.add(self.nut.assembly(), name=spacer_name)
|
|
||||||
.add(self.bolt.assembly(), name=bolt_name)
|
.add(self.bolt.assembly(), name=bolt_name)
|
||||||
.constrain(
|
|
||||||
f"{spacer_name}?top",
|
|
||||||
f"panel?{hole.rev_tag}",
|
|
||||||
"Plane"
|
|
||||||
)
|
|
||||||
.constrain(
|
.constrain(
|
||||||
f"{bolt_name}?root",
|
f"{bolt_name}?root",
|
||||||
f"panel?{hole.tag}",
|
f"panel?{hole.tag}",
|
||||||
|
@ -551,6 +491,7 @@ class ElectronicBoard(Model):
|
||||||
@dataclass
|
@dataclass
|
||||||
class ElectronicBoardBattery(ElectronicBoard):
|
class ElectronicBoardBattery(ElectronicBoard):
|
||||||
name: str = "electronic-board-battery"
|
name: str = "electronic-board-battery"
|
||||||
|
battery_box: BatteryBox18650 = BATTERY_BOX
|
||||||
|
|
||||||
@submodel(name="panel")
|
@submodel(name="panel")
|
||||||
def panel_out(self) -> MountingBox:
|
def panel_out(self) -> MountingBox:
|
||||||
|
|
|
@ -10,7 +10,7 @@ from nhf.parts.fasteners import FlatHeadBolt, HexNut, ThreaddedKnob, Washer
|
||||||
from nhf.parts.joints import TorsionJoint, HirthJoint
|
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
|
||||||
from nhf.touhou.houjuu_nue.electronics import (
|
from nhf.touhou.houjuu_nue.electronics import (
|
||||||
Flexor, LinearActuator, LINEAR_ACTUATOR_21,
|
Winch, Flexor, LinearActuator, LINEAR_ACTUATOR_21,
|
||||||
)
|
)
|
||||||
import nhf.geometry
|
import nhf.geometry
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
@ -390,7 +390,7 @@ class ShoulderJoint(Model):
|
||||||
parent_arm_width: float = 25.0
|
parent_arm_width: float = 25.0
|
||||||
parent_arm_height: float = 12.0
|
parent_arm_height: float = 12.0
|
||||||
# remove a bit of material from the base so it does not interfere with gluing
|
# remove a bit of material from the base so it does not interfere with gluing
|
||||||
parent_arm_base_shift: float = 2.0
|
parent_arm_base_shift: float = 1.0
|
||||||
|
|
||||||
# Generates a child guard which covers up the internals. The lip length is
|
# Generates a child guard which covers up the internals. The lip length is
|
||||||
# relative to the +X surface of the guard.
|
# relative to the +X surface of the guard.
|
||||||
|
@ -423,6 +423,7 @@ class ShoulderJoint(Model):
|
||||||
spool_groove_radius: float = 10.0
|
spool_groove_radius: float = 10.0
|
||||||
|
|
||||||
flip: bool = False
|
flip: bool = False
|
||||||
|
winch: Optional[Winch] = None # Initialized later
|
||||||
actuator: LinearActuator = LINEAR_ACTUATOR_21
|
actuator: LinearActuator = LINEAR_ACTUATOR_21
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
@ -431,6 +432,10 @@ class ShoulderJoint(Model):
|
||||||
assert self.spool_groove_radius < self.spool_inner_radius < self.spool_outer_radius
|
assert self.spool_groove_radius < self.spool_inner_radius < self.spool_outer_radius
|
||||||
assert self.child_lip_height < self.height
|
assert self.child_lip_height < self.height
|
||||||
assert self.draft_length <= self.actuator.stroke_length
|
assert self.draft_length <= self.actuator.stroke_length
|
||||||
|
self.winch = Winch(
|
||||||
|
actuator=self.actuator,
|
||||||
|
linear_motion_span=self.draft_length,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def spool_outer_radius(self):
|
def spool_outer_radius(self):
|
||||||
|
@ -457,6 +462,10 @@ class ShoulderJoint(Model):
|
||||||
"""
|
"""
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent_lip_gap(self):
|
||||||
|
return self.height - self.parent_lip_length * 2
|
||||||
|
|
||||||
def parent_lip_loc(self, left: bool=True) -> Cq.Location:
|
def parent_lip_loc(self, left: bool=True) -> Cq.Location:
|
||||||
"""
|
"""
|
||||||
2d location of the arm surface on the parent side, relative to axle
|
2d location of the arm surface on the parent side, relative to axle
|
||||||
|
|
|
@ -21,6 +21,7 @@ from nhf.touhou.houjuu_nue.electronics import (
|
||||||
ElectronicBoard,
|
ElectronicBoard,
|
||||||
ElectronicBoardBattery,
|
ElectronicBoardBattery,
|
||||||
LightStrip,
|
LightStrip,
|
||||||
|
ElectronicBoard,
|
||||||
ELECTRONIC_MOUNT_HEXNUT,
|
ELECTRONIC_MOUNT_HEXNUT,
|
||||||
)
|
)
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
@ -287,7 +288,7 @@ class WingProfile(Model):
|
||||||
plane.moveTo(t, self.root_height + t*2).tagPlane("top")
|
plane.moveTo(t, self.root_height + t*2).tagPlane("top")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@submodel(name="spacer-s0-shoulder")
|
@submodel(name="spacer-s0-shoulder-inner")
|
||||||
def spacer_s0_shoulder(self, left: bool=True) -> MountingBox:
|
def spacer_s0_shoulder(self, left: bool=True) -> MountingBox:
|
||||||
"""
|
"""
|
||||||
Shoulder side serves double purpose for mounting shoulder joint and
|
Shoulder side serves double purpose for mounting shoulder joint and
|
||||||
|
@ -302,6 +303,21 @@ class WingProfile(Model):
|
||||||
Hole(x=-x, y=sign * y, tag=f"conn_bot{i}"),
|
Hole(x=-x, y=sign * y, tag=f"conn_bot{i}"),
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
def post(sketch: Cq.Sketch) -> Cq.Sketch:
|
||||||
|
"""
|
||||||
|
Carve out the middle if this is closer to the front
|
||||||
|
"""
|
||||||
|
if left:
|
||||||
|
return sketch
|
||||||
|
return (
|
||||||
|
sketch
|
||||||
|
.push([(0,0)])
|
||||||
|
.rect(
|
||||||
|
w=self.shoulder_joint.parent_lip_gap,
|
||||||
|
h=self.shoulder_joint.parent_lip_width,
|
||||||
|
mode='s'
|
||||||
|
)
|
||||||
|
)
|
||||||
return MountingBox(
|
return MountingBox(
|
||||||
length=self.shoulder_joint.height,
|
length=self.shoulder_joint.height,
|
||||||
width=self.shoulder_joint.parent_lip_width,
|
width=self.shoulder_joint.parent_lip_width,
|
||||||
|
@ -311,7 +327,12 @@ class WingProfile(Model):
|
||||||
centred=(True, True),
|
centred=(True, True),
|
||||||
flip_y=self.flip,
|
flip_y=self.flip,
|
||||||
centre_bot_top_tags=True,
|
centre_bot_top_tags=True,
|
||||||
|
profile_callback=post,
|
||||||
)
|
)
|
||||||
|
@submodel(name="spacer-s0-shoulder-outer")
|
||||||
|
def spacer_s0_shoulder_outer(self) -> MountingBox:
|
||||||
|
return self.spacer_s0_shoulder_inner(left=False)
|
||||||
|
|
||||||
@submodel(name="spacer-s0-base")
|
@submodel(name="spacer-s0-base")
|
||||||
def spacer_s0_base(self) -> MountingBox:
|
def spacer_s0_base(self) -> MountingBox:
|
||||||
"""
|
"""
|
||||||
|
@ -341,11 +362,15 @@ class WingProfile(Model):
|
||||||
@submodel(name="spacer-s0-electronic")
|
@submodel(name="spacer-s0-electronic")
|
||||||
def spacer_s0_electronic_mount(self) -> MountingBox:
|
def spacer_s0_electronic_mount(self) -> MountingBox:
|
||||||
"""
|
"""
|
||||||
This one has circular holes for the screws
|
This one has hexagonal holes
|
||||||
"""
|
"""
|
||||||
|
face = ELECTRONIC_MOUNT_HEXNUT.cutting_face()
|
||||||
|
holes = [
|
||||||
|
Hole(x=h.x, y=h.y, face=face, tag=h.tag)
|
||||||
|
for h in self.electronic_board.mount_holes
|
||||||
|
]
|
||||||
return MountingBox(
|
return MountingBox(
|
||||||
holes=self.electronic_board.mount_holes,
|
holes=holes,
|
||||||
hole_diam=self.electronic_board.mount_hole_diam,
|
|
||||||
length=self.root_height,
|
length=self.root_height,
|
||||||
width=self.electronic_board.width,
|
width=self.electronic_board.width,
|
||||||
centred=(True, True),
|
centred=(True, True),
|
||||||
|
@ -356,13 +381,8 @@ class WingProfile(Model):
|
||||||
@submodel(name="spacer-s0-electronic2")
|
@submodel(name="spacer-s0-electronic2")
|
||||||
def spacer_s0_electronic_mount2(self) -> MountingBox:
|
def spacer_s0_electronic_mount2(self) -> MountingBox:
|
||||||
"""
|
"""
|
||||||
This one has hexagonal holes
|
This one has circular holes
|
||||||
"""
|
"""
|
||||||
face = ELECTRONIC_MOUNT_HEXNUT.cutting_face()
|
|
||||||
holes = [
|
|
||||||
Hole(x=h.x, y=h.y, face=face, tag=h.tag)
|
|
||||||
for h in self.electronic_board.mount_holes
|
|
||||||
]
|
|
||||||
def post(sketch: Cq.Sketch) -> Cq.Sketch:
|
def post(sketch: Cq.Sketch) -> Cq.Sketch:
|
||||||
return (
|
return (
|
||||||
sketch
|
sketch
|
||||||
|
@ -370,7 +390,7 @@ class WingProfile(Model):
|
||||||
.rect(70, 130, mode='s')
|
.rect(70, 130, mode='s')
|
||||||
)
|
)
|
||||||
return MountingBox(
|
return MountingBox(
|
||||||
holes=holes,
|
holes=self.electronic_board.mount_holes,
|
||||||
hole_diam=self.electronic_board.mount_hole_diam,
|
hole_diam=self.electronic_board.mount_hole_diam,
|
||||||
length=self.root_height,
|
length=self.root_height,
|
||||||
width=self.electronic_board.width,
|
width=self.electronic_board.width,
|
||||||
|
@ -408,7 +428,7 @@ class WingProfile(Model):
|
||||||
("shoulder_right",
|
("shoulder_right",
|
||||||
self.shoulder_axle_loc * axle_rotate * self.shoulder_joint.parent_lip_loc(left=False)),
|
self.shoulder_axle_loc * axle_rotate * self.shoulder_joint.parent_lip_loc(left=False)),
|
||||||
("shoulder_act",
|
("shoulder_act",
|
||||||
self.shoulder_axle_loc * axle_rotate * Cq.Location.from2d(100, -20)),
|
self.shoulder_axle_loc * axle_rotate * Cq.Location.from2d(120, -40)),
|
||||||
("base", Cq.Location.from2d(base_dx, base_dy, 90)),
|
("base", Cq.Location.from2d(base_dx, base_dy, 90)),
|
||||||
("electronic_mount", Cq.Location.from2d(-35, 65, 60)),
|
("electronic_mount", Cq.Location.from2d(-35, 65, 60)),
|
||||||
]
|
]
|
||||||
|
@ -474,17 +494,26 @@ class WingProfile(Model):
|
||||||
for hole in self.electronic_board.mount_holes:
|
for hole in self.electronic_board.mount_holes:
|
||||||
result.constrain(
|
result.constrain(
|
||||||
f"electronic_mount?{hole.tag}",
|
f"electronic_mount?{hole.tag}",
|
||||||
f"electronic_mount2?{hole.tag}_rev",
|
f"electronic_mount2?{hole.rev_tag}",
|
||||||
"Plane")
|
"Plane")
|
||||||
if not ignore_electronics:
|
if not ignore_electronics:
|
||||||
result.add(self.electronic_board.assembly(), name="electronic_board")
|
result.add(self.electronic_board.assembly(), name="electronic_board")
|
||||||
for hole in self.electronic_board.mount_holes:
|
for hole in self.electronic_board.mount_holes:
|
||||||
assert hole.tag
|
assert hole.tag
|
||||||
|
nut_name = f"{hole.tag}_nut"
|
||||||
|
(
|
||||||
|
result
|
||||||
|
.add(self.electronic_board.nut.assembly(), name=nut_name)
|
||||||
|
.constrain(
|
||||||
|
f"{nut_name}?top",
|
||||||
|
f"electronic_mount?{hole.tag}",
|
||||||
|
"Plane", param=0
|
||||||
|
)
|
||||||
|
)
|
||||||
result.constrain(
|
result.constrain(
|
||||||
f"electronic_mount2?{hole.tag}",
|
f"electronic_mount2?{hole.tag}",
|
||||||
f'electronic_board/{hole.tag}_spacer?top',
|
f'electronic_board/panel?{hole.rev_tag}',
|
||||||
"Plane",
|
"Plane",
|
||||||
param=0
|
|
||||||
)
|
)
|
||||||
return result.solve()
|
return result.solve()
|
||||||
|
|
||||||
|
@ -1073,6 +1102,27 @@ class WingProfile(Model):
|
||||||
result.add(self.assembly_s0(
|
result.add(self.assembly_s0(
|
||||||
ignore_electronics=ignore_electronics
|
ignore_electronics=ignore_electronics
|
||||||
), name="s0")
|
), name="s0")
|
||||||
|
if not ignore_electronics:
|
||||||
|
tag_act = "shoulder_act"
|
||||||
|
tag_bolt = "shoulder_act_bolt"
|
||||||
|
tag_nut = "shoulder_act_nut"
|
||||||
|
tag_bracket = "shoulder_act_bracket"
|
||||||
|
winch = self.shoulder_joint.winch
|
||||||
|
(
|
||||||
|
result
|
||||||
|
.add(winch.actuator.assembly(pos=0), name=tag_act)
|
||||||
|
.add(winch.bracket.assembly(), name=tag_bracket)
|
||||||
|
.add(winch.bolt.assembly(), name=tag_bolt)
|
||||||
|
.add(winch.nut.assembly(), name=tag_nut)
|
||||||
|
.constrain(f"{tag_bolt}?root", f"{tag_bracket}?conn_top",
|
||||||
|
"Plane", param=0)
|
||||||
|
.constrain(f"{tag_nut}?bot", f"{tag_bracket}?conn_bot",
|
||||||
|
"Plane")
|
||||||
|
.constrain(f"{tag_act}/back?conn", f"{tag_bracket}?conn_mid",
|
||||||
|
"Plane", param=0)
|
||||||
|
.constrain("s0/shoulder_act?conn0", f"{tag_bracket}?conn_side",
|
||||||
|
"Plane")
|
||||||
|
)
|
||||||
if "root" in parts:
|
if "root" in parts:
|
||||||
result.addS(self.root_joint.assembly(
|
result.addS(self.root_joint.assembly(
|
||||||
offset=root_offset,
|
offset=root_offset,
|
||||||
|
|
Loading…
Reference in New Issue