cosplay: Touhou/Houjuu Nue #4
|
@ -31,6 +31,7 @@ class Role(Flag):
|
||||||
STRUCTURE = auto()
|
STRUCTURE = auto()
|
||||||
DECORATION = auto()
|
DECORATION = auto()
|
||||||
ELECTRONIC = auto()
|
ELECTRONIC = auto()
|
||||||
|
MOTION = auto()
|
||||||
|
|
||||||
# Fasteners, etc.
|
# Fasteners, etc.
|
||||||
CONNECTION = auto()
|
CONNECTION = auto()
|
||||||
|
@ -61,7 +62,8 @@ ROLE_COLOR_MAP = {
|
||||||
Role.DAMPING: _color('springgreen', 1.0),
|
Role.DAMPING: _color('springgreen', 1.0),
|
||||||
Role.STRUCTURE: _color('gray', 0.4),
|
Role.STRUCTURE: _color('gray', 0.4),
|
||||||
Role.DECORATION: _color('lightseagreen', 0.4),
|
Role.DECORATION: _color('lightseagreen', 0.4),
|
||||||
Role.ELECTRONIC: _color('mediumorchid', 0.5),
|
Role.ELECTRONIC: _color('mediumorchid', 0.7),
|
||||||
|
Role.MOTION: _color('thistle3', 0.7),
|
||||||
Role.CONNECTION: _color('steelblue3', 0.8),
|
Role.CONNECTION: _color('steelblue3', 0.8),
|
||||||
Role.HANDLE: _color('tomato4', 0.8),
|
Role.HANDLE: _color('tomato4', 0.8),
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,10 +42,7 @@ class FlatHeadBolt(Item):
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ThreaddedKnob(Item):
|
class ThreaddedKnob(Item):
|
||||||
"""
|
"""
|
||||||
Sourced from:
|
A threaded rod with knob on one side
|
||||||
|
|
||||||
> Othmro Black 12mm(M12) x 50mm Thread Replacement Star Hand Knob Tightening
|
|
||||||
> Screws
|
|
||||||
"""
|
"""
|
||||||
diam_thread: float
|
diam_thread: float
|
||||||
height_thread: float
|
height_thread: float
|
||||||
|
@ -104,7 +101,7 @@ class HexNut(Item):
|
||||||
return f"HexNut M{int(self.diam_thread)}-{self.pitch}"
|
return f"HexNut M{int(self.diam_thread)}-{self.pitch}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def role(self):
|
def role(self) -> Role:
|
||||||
return Role.CONNECTION
|
return Role.CONNECTION
|
||||||
|
|
||||||
def generate(self) -> Cq.Workplane:
|
def generate(self) -> Cq.Workplane:
|
||||||
|
|
|
@ -44,9 +44,9 @@ class Item:
|
||||||
a = self.generate(**kwargs)
|
a = self.generate(**kwargs)
|
||||||
if isinstance(a, Cq.Workplane):
|
if isinstance(a, Cq.Workplane):
|
||||||
a = Cq.Assembly(a)
|
a = Cq.Assembly(a)
|
||||||
if role := self.role:
|
if role := self.role:
|
||||||
a.metadata[KEY_ROLE] = role
|
a.metadata[KEY_ROLE] = role
|
||||||
a.color = role.color_avg()
|
a.color = role.color_avg()
|
||||||
assert isinstance(a, Cq.Assembly)
|
assert isinstance(a, Cq.Assembly)
|
||||||
assert KEY_ITEM not in a.metadata
|
assert KEY_ITEM not in a.metadata
|
||||||
a.metadata[KEY_ITEM] = self
|
a.metadata[KEY_ITEM] = self
|
||||||
|
|
|
@ -393,7 +393,7 @@ class TorsionJoint:
|
||||||
def rider_track_assembly(self, directrix: int = 0, deflection: float = 0):
|
def rider_track_assembly(self, directrix: int = 0, deflection: float = 0):
|
||||||
rider = self.rider()
|
rider = self.rider()
|
||||||
track = self.track()
|
track = self.track()
|
||||||
spring = self.spring.generate(deflection=deflection)
|
spring = self.spring.assembly(deflection=deflection)
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
.addS(spring, name="spring", role=Role.DAMPING)
|
.addS(spring, name="spring", role=Role.DAMPING)
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
"""
|
||||||
|
Electronic components
|
||||||
|
"""
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import cadquery as Cq
|
||||||
|
from nhf.materials import Role
|
||||||
|
from nhf.parts.item import Item
|
||||||
|
from nhf.parts.fasteners import FlatHeadBolt, HexNut
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class LinearActuator(Item):
|
||||||
|
stroke_length: float
|
||||||
|
shaft_diam: float = 9.04
|
||||||
|
|
||||||
|
front_hole_ext: float = 4.41
|
||||||
|
front_hole_diam: float = 4.41
|
||||||
|
front_length: float = 9.55
|
||||||
|
front_width: float = 9.24
|
||||||
|
front_height: float = 5.98
|
||||||
|
|
||||||
|
segment1_length: float = 37.55
|
||||||
|
segment1_width: float = 15.95
|
||||||
|
segment1_height: float = 11.94
|
||||||
|
|
||||||
|
segment2_length: float = 37.47
|
||||||
|
segment2_width: float = 20.03
|
||||||
|
segment2_height: float = 15.03
|
||||||
|
|
||||||
|
back_hole_ext: float = 4.58
|
||||||
|
back_hole_diam: float = 4.18
|
||||||
|
back_length: float = 9.27
|
||||||
|
back_width: float = 10.16
|
||||||
|
back_height: float = 8.12
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return f"LinearActuator {self.stroke_length}mm"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def role(self) -> Role:
|
||||||
|
return Role.MOTION
|
||||||
|
|
||||||
|
@property
|
||||||
|
def conn_length(self):
|
||||||
|
return self.segment1_length + self.segment2_length + self.front_hole_ext + self.back_hole_ext
|
||||||
|
|
||||||
|
def generate(self, pos: float=0) -> Cq.Assembly:
|
||||||
|
stroke_x = pos * self.stroke_length
|
||||||
|
front = (
|
||||||
|
Cq.Workplane('XZ')
|
||||||
|
.cylinder(
|
||||||
|
radius=self.front_width / 2,
|
||||||
|
height=self.front_height,
|
||||||
|
centered=True,
|
||||||
|
)
|
||||||
|
.box(
|
||||||
|
length=self.front_hole_ext,
|
||||||
|
width=self.front_width,
|
||||||
|
height=self.front_height,
|
||||||
|
combine=True,
|
||||||
|
centered=(False, True, True)
|
||||||
|
)
|
||||||
|
.copyWorkplane(Cq.Workplane('XZ'))
|
||||||
|
.cylinder(
|
||||||
|
radius=self.front_hole_diam / 2,
|
||||||
|
height=self.front_height,
|
||||||
|
centered=True,
|
||||||
|
combine='cut',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if stroke_x > 0:
|
||||||
|
shaft = (
|
||||||
|
Cq.Workplane('YZ')
|
||||||
|
.cylinder(
|
||||||
|
radius=self.shaft_diam / 2,
|
||||||
|
height=stroke_x,
|
||||||
|
centered=(True, True, False)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
shaft = None
|
||||||
|
segment1 = (
|
||||||
|
Cq.Workplane()
|
||||||
|
.box(
|
||||||
|
length=self.segment1_length,
|
||||||
|
height=self.segment1_width,
|
||||||
|
width=self.segment1_height,
|
||||||
|
centered=(False, True, True),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
segment2 = (
|
||||||
|
Cq.Workplane()
|
||||||
|
.box(
|
||||||
|
length=self.segment2_length,
|
||||||
|
height=self.segment2_width,
|
||||||
|
width=self.segment2_height,
|
||||||
|
centered=(False, True, True),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
back = (
|
||||||
|
Cq.Workplane('XZ')
|
||||||
|
.cylinder(
|
||||||
|
radius=self.back_width / 2,
|
||||||
|
height=self.back_height,
|
||||||
|
centered=True,
|
||||||
|
)
|
||||||
|
.box(
|
||||||
|
length=self.back_hole_ext,
|
||||||
|
width=self.back_width,
|
||||||
|
height=self.back_height,
|
||||||
|
combine=True,
|
||||||
|
centered=(False, True, True)
|
||||||
|
)
|
||||||
|
.copyWorkplane(Cq.Workplane('XZ'))
|
||||||
|
.cylinder(
|
||||||
|
radius=self.back_hole_diam / 2,
|
||||||
|
height=self.back_height,
|
||||||
|
centered=True,
|
||||||
|
combine='cut',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result = (
|
||||||
|
Cq.Assembly()
|
||||||
|
.add(front, name="front",
|
||||||
|
loc=Cq.Location((-self.front_hole_ext, 0, 0)))
|
||||||
|
.add(segment1, name="segment1",
|
||||||
|
loc=Cq.Location((stroke_x, 0, 0)))
|
||||||
|
.add(segment2, name="segment2",
|
||||||
|
loc=Cq.Location((stroke_x + self.segment1_length, 0, 0)))
|
||||||
|
.add(back, name="back",
|
||||||
|
loc=Cq.Location((stroke_x + self.segment1_length + self.segment2_length + self.back_hole_ext, 0, 0), (0, 1, 0), 180))
|
||||||
|
)
|
||||||
|
if shaft:
|
||||||
|
result.add(shaft, name="shaft")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
LINEAR_ACTUATOR_SHOULDER = LinearActuator(
|
||||||
|
mass=34.0,
|
||||||
|
stroke_length=30,
|
||||||
|
)
|
||||||
|
LINEAR_ACTUATOR_HEX_NUT = HexNut(
|
||||||
|
mass=0.8,
|
||||||
|
diam_thread=4,
|
||||||
|
pitch=0.7,
|
||||||
|
thickness=4.16,
|
||||||
|
width=6.79,
|
||||||
|
)
|
||||||
|
LINEAR_ACTUATOR_BOLT = FlatHeadBolt(
|
||||||
|
mass=1.7,
|
||||||
|
diam_head=16.68,
|
||||||
|
height_head=2.98,
|
||||||
|
diam_thread=4.0,
|
||||||
|
height_thread=15.83,
|
||||||
|
)
|
|
@ -12,30 +12,64 @@ import nhf.utils
|
||||||
|
|
||||||
TOL = 1e-6
|
TOL = 1e-6
|
||||||
|
|
||||||
|
# Parts used
|
||||||
|
|
||||||
|
# uxcell 2 Pcs Star Knobs Grips M12 x 30mm Male Thread Steel Zinc Stud Replacement PP
|
||||||
|
HS_JOINT_KNOB = 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,
|
||||||
|
)
|
||||||
|
# Tom's world 8Pcs M12-1.75 Hex Nut Assortment Set Stainless Steel 304(18-8)
|
||||||
|
# Metric Hexagon Nut for Bolts, Bright Finish, Full Thread (M12)
|
||||||
|
HS_JOINT_HEX_NUT = HexNut(
|
||||||
|
mass=14.9,
|
||||||
|
diam_thread=12.0,
|
||||||
|
pitch=1.75,
|
||||||
|
thickness=9.7,
|
||||||
|
width=18.9,
|
||||||
|
)
|
||||||
|
SHOULDER_AXIS_BOLT = FlatHeadBolt(
|
||||||
|
# FIXME: measure
|
||||||
|
diam_head=10.0,
|
||||||
|
height_head=3.0,
|
||||||
|
diam_thread=6.0,
|
||||||
|
height_thread=20.0,
|
||||||
|
mass=float('nan'),
|
||||||
|
)
|
||||||
|
# Hoypeyfiy 10 Pieces Torsion Spring Woodworking DIY 90 Degrees Torsional
|
||||||
|
# Springs Repair Maintenance Spring
|
||||||
|
SHOULDER_TORSION_SPRING = TorsionSpring(
|
||||||
|
mass=2.2,
|
||||||
|
# inner diameter = 9
|
||||||
|
radius=9/2 + 1.2,
|
||||||
|
thickness=1.3,
|
||||||
|
height=7.5,
|
||||||
|
)
|
||||||
|
# KALIONE 10 Pieces Torsion Spring, Stainless Steel Small Torsion Springs, Tiny
|
||||||
|
# Torsional Spring, 90° Deflection Compression Spring Kit for Repair Tools
|
||||||
|
# Woodworking DIY, 50mm
|
||||||
|
ELBOW_TORSION_SPRING = TorsionSpring(
|
||||||
|
mass=1.7,
|
||||||
|
radius=9 / 2,
|
||||||
|
thickness=1.3,
|
||||||
|
height=6.5,
|
||||||
|
tail_length=45.0,
|
||||||
|
right_handed=False,
|
||||||
|
)
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RootJoint(Model):
|
class RootJoint(Model):
|
||||||
"""
|
"""
|
||||||
The Houjuu-Scarlett Mechanism
|
The Houjuu-Scarlett Mechanism
|
||||||
"""
|
"""
|
||||||
knob: ThreaddedKnob = ThreaddedKnob(
|
knob: ThreaddedKnob = HS_JOINT_KNOB
|
||||||
mass=float('nan'),
|
hex_nut: HexNut = HS_JOINT_HEX_NUT
|
||||||
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(
|
hirth_joint: HirthJoint = field(default_factory=lambda: HirthJoint(
|
||||||
radius=25.0,
|
radius=25.0,
|
||||||
radius_inner=15.0,
|
radius_inner=15.0,
|
||||||
|
@ -206,14 +240,7 @@ class RootJoint(Model):
|
||||||
@dataclass
|
@dataclass
|
||||||
class ShoulderJoint(Model):
|
class ShoulderJoint(Model):
|
||||||
|
|
||||||
bolt: FlatHeadBolt = FlatHeadBolt(
|
bolt: FlatHeadBolt = SHOULDER_AXIS_BOLT
|
||||||
# FIXME: measure
|
|
||||||
diam_head=10.0,
|
|
||||||
height_head=3.0,
|
|
||||||
diam_thread=6.0,
|
|
||||||
height_thread=20.0,
|
|
||||||
mass=float('nan'),
|
|
||||||
)
|
|
||||||
|
|
||||||
height: float = 70.0
|
height: float = 70.0
|
||||||
torsion_joint: TorsionJoint = field(default_factory=lambda: TorsionJoint(
|
torsion_joint: TorsionJoint = field(default_factory=lambda: TorsionJoint(
|
||||||
|
@ -225,13 +252,7 @@ class ShoulderJoint(Model):
|
||||||
track_disk_height=5.0,
|
track_disk_height=5.0,
|
||||||
rider_disk_height=5.0,
|
rider_disk_height=5.0,
|
||||||
radius_axle=3.0,
|
radius_axle=3.0,
|
||||||
spring=TorsionSpring(
|
spring=SHOULDER_TORSION_SPRING,
|
||||||
mass=float('nan'),
|
|
||||||
# inner diameter = 9
|
|
||||||
radius=9/2 + 1.2,
|
|
||||||
thickness=1.3,
|
|
||||||
height=7.5,
|
|
||||||
),
|
|
||||||
rider_slot_begin=0,
|
rider_slot_begin=0,
|
||||||
rider_n_slots=1,
|
rider_n_slots=1,
|
||||||
rider_slot_span=0,
|
rider_slot_span=0,
|
||||||
|
@ -451,12 +472,12 @@ class ShoulderJoint(Model):
|
||||||
.addS(self.child(), name="child",
|
.addS(self.child(), name="child",
|
||||||
role=Role.CHILD, material=mat)
|
role=Role.CHILD, material=mat)
|
||||||
.constrain("child/core", "Fixed")
|
.constrain("child/core", "Fixed")
|
||||||
.addS(self.torsion_joint.spring.generate(deflection=-deflection), name="spring_top",
|
.addS(self.torsion_joint.spring.assembly(deflection=-deflection), name="spring_top",
|
||||||
role=Role.DAMPING, material=mat_spring)
|
role=Role.DAMPING, material=mat_spring)
|
||||||
.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.assembly(deflection=deflection), name="spring_bot",
|
||||||
role=Role.DAMPING, material=mat_spring)
|
role=Role.DAMPING, material=mat_spring)
|
||||||
.addS(self.parent_bot(),
|
.addS(self.parent_bot(),
|
||||||
name="parent_bot",
|
name="parent_bot",
|
||||||
|
@ -558,14 +579,7 @@ class DiskJoint(Model):
|
||||||
We embed a spring inside the joint, with one leg in the disk and one leg in
|
We embed a spring inside the joint, with one leg in the disk and one leg in
|
||||||
the housing. This provides torsion resistance.
|
the housing. This provides torsion resistance.
|
||||||
"""
|
"""
|
||||||
spring: TorsionSpring = field(default_factory=lambda: TorsionSpring(
|
spring: TorsionSpring = ELBOW_TORSION_SPRING
|
||||||
mass=float('nan'),
|
|
||||||
radius=9 / 2,
|
|
||||||
thickness=1.3,
|
|
||||||
height=6.5,
|
|
||||||
tail_length=45.0,
|
|
||||||
right_handed=False,
|
|
||||||
))
|
|
||||||
|
|
||||||
radius_housing: float = 22.0
|
radius_housing: float = 22.0
|
||||||
radius_disk: float = 20.0
|
radius_disk: float = 20.0
|
||||||
|
@ -787,7 +801,7 @@ class DiskJoint(Model):
|
||||||
(
|
(
|
||||||
assembly
|
assembly
|
||||||
.addS(
|
.addS(
|
||||||
self.spring.generate(deflection=-deflection),
|
self.spring.assembly(deflection=-deflection),
|
||||||
name=spring_name,
|
name=spring_name,
|
||||||
role=Role.DAMPING,
|
role=Role.DAMPING,
|
||||||
material=Material.STEEL_SPRING)
|
material=Material.STEEL_SPRING)
|
||||||
|
|
Loading…
Reference in New Issue