cosplay: Touhou/Houjuu Nue #4
|
@ -15,6 +15,10 @@ class FlatHeadBolt(Item):
|
|||
def name(self) -> str:
|
||||
return f"Bolt M{int(self.diam_thread)} h{int(self.height_thread)}mm"
|
||||
|
||||
@property
|
||||
def role(self) -> Role:
|
||||
return Role.CONNECTION
|
||||
|
||||
|
||||
def generate(self) -> Cq.Assembly:
|
||||
head = Cq.Solid.makeCylinder(
|
||||
|
@ -30,13 +34,8 @@ class FlatHeadBolt(Item):
|
|||
)
|
||||
rod.faces("<Z").tag("tip")
|
||||
rod.faces(">Z").tag("root")
|
||||
|
||||
return (
|
||||
Cq.Assembly()
|
||||
.addS(rod, name="thread", role=Role.CONNECTION)
|
||||
.addS(head, name="head", role=Role.CONNECTION,
|
||||
loc=Cq.Location((0, 0, self.height_thread)))
|
||||
)
|
||||
rod = rod.union(head.located(Cq.Location((0, 0, self.height_thread))))
|
||||
return rod
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
Electronic components
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
import cadquery as Cq
|
||||
from nhf.materials import Role
|
||||
from nhf.parts.item import Item
|
||||
from nhf.parts.fasteners import FlatHeadBolt, HexNut
|
||||
import nhf.utils
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LinearActuator(Item):
|
||||
|
@ -68,6 +70,7 @@ class LinearActuator(Item):
|
|||
combine='cut',
|
||||
)
|
||||
)
|
||||
front.copyWorkplane(Cq.Workplane('XZ')).tagPlane('conn')
|
||||
if stroke_x > 0:
|
||||
shaft = (
|
||||
Cq.Workplane('YZ')
|
||||
|
@ -119,6 +122,7 @@ class LinearActuator(Item):
|
|||
combine='cut',
|
||||
)
|
||||
)
|
||||
back.copyWorkplane(Cq.Workplane('XZ')).tagPlane('conn')
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(front, name="front",
|
||||
|
@ -134,6 +138,71 @@ class LinearActuator(Item):
|
|||
result.add(shaft, name="shaft")
|
||||
return result
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MountingBracket(Item):
|
||||
"""
|
||||
Mounting bracket for a linear actuator
|
||||
"""
|
||||
mass: float = 1.6
|
||||
hole_diam: float = 4.0
|
||||
width: float = 8.0
|
||||
height: float = 12.20
|
||||
thickness: float = 0.98
|
||||
length: float = 13.00
|
||||
hole_to_side_ext: float = 8.10
|
||||
|
||||
def __post_init__(self):
|
||||
assert self.hole_to_side_ext - self.hole_diam / 2 > 0
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return f"MountingBracket M{int(self.hole_diam)}"
|
||||
|
||||
@property
|
||||
def role(self) -> Role:
|
||||
return Role.MOTION
|
||||
|
||||
def generate(self) -> Cq.Workplane:
|
||||
result = (
|
||||
Cq.Workplane('XY')
|
||||
.box(
|
||||
length=self.hole_to_side_ext,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
centered=(False, True, True)
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('XY'))
|
||||
.cylinder(
|
||||
height=self.height,
|
||||
radius=self.width / 2,
|
||||
combine=True,
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('XY'))
|
||||
.box(
|
||||
length=2 * (self.hole_to_side_ext - self.thickness),
|
||||
width=self.width,
|
||||
height=self.height - self.thickness * 2,
|
||||
combine='cut',
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('XY'))
|
||||
.cylinder(
|
||||
height=self.height,
|
||||
radius=self.hole_diam / 2,
|
||||
combine='cut'
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('YZ'))
|
||||
.cylinder(
|
||||
height=self.hole_to_side_ext * 2,
|
||||
radius=self.hole_diam / 2,
|
||||
combine='cut'
|
||||
)
|
||||
)
|
||||
result.copyWorkplane(Cq.Workplane('YZ', origin=(self.hole_to_side_ext, 0, 0))).tagPlane("conn_side")
|
||||
result.copyWorkplane(Cq.Workplane('XY', origin=(0, 0, self.height/2))).tagPlane("conn_top")
|
||||
result.copyWorkplane(Cq.Workplane('YX', origin=(0, 0, -self.height/2))).tagPlane("conn_bot")
|
||||
result.copyWorkplane(Cq.Workplane('XY')).tagPlane("conn_mid")
|
||||
return result
|
||||
|
||||
|
||||
LINEAR_ACTUATOR_SHOULDER = LinearActuator(
|
||||
mass=34.0,
|
||||
|
@ -148,8 +217,67 @@ LINEAR_ACTUATOR_HEX_NUT = HexNut(
|
|||
)
|
||||
LINEAR_ACTUATOR_BOLT = FlatHeadBolt(
|
||||
mass=1.7,
|
||||
diam_head=16.68,
|
||||
diam_head=6.68,
|
||||
height_head=2.98,
|
||||
diam_thread=4.0,
|
||||
height_thread=15.83,
|
||||
)
|
||||
LINEAR_ACTUATOR_BRACKET = MountingBracket()
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LinearActuatorAssembly:
|
||||
|
||||
# FIXME: Measure
|
||||
actuator: LinearActuator = LINEAR_ACTUATOR_SHOULDER
|
||||
nut: HexNut = LINEAR_ACTUATOR_HEX_NUT
|
||||
bolt: FlatHeadBolt = LINEAR_ACTUATOR_BOLT
|
||||
bracket: MountingBracket = LINEAR_ACTUATOR_BRACKET
|
||||
|
||||
def add_to(
|
||||
self,
|
||||
a: Cq.Assembly,
|
||||
tag_prefix: Optional[str] = None,
|
||||
tag_hole_front: Optional[str] = None,
|
||||
tag_hole_back: Optional[str] = None,
|
||||
tag_dir: Optional[str] = None):
|
||||
"""
|
||||
Adds the necessary mechanical components to this assembly. Does not
|
||||
invoke `a.solve()`.
|
||||
"""
|
||||
if tag_prefix:
|
||||
tag_prefix = tag_prefix + "_"
|
||||
name_actuator = f"{tag_prefix}actuator"
|
||||
name_bracket_front = f"{tag_prefix}bracket_front"
|
||||
name_bracket_back = f"{tag_prefix}bracket_back"
|
||||
name_bolt_front = f"{tag_prefix}front_bolt"
|
||||
name_bolt_back = f"{tag_prefix}back_bolt"
|
||||
name_nut_front = f"{tag_prefix}front_nut"
|
||||
name_nut_back = f"{tag_prefix}back_nut"
|
||||
(
|
||||
a
|
||||
.add(self.actuator.assembly(), name=name_actuator)
|
||||
.add(self.bracket.assembly(), name=name_bracket_front)
|
||||
.add(self.bolt.assembly(), name=name_bolt_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",
|
||||
"Plane", param=0)
|
||||
.constrain(f"{name_nut_front}?bot", f"{name_bracket_front}?conn_bot",
|
||||
"Plane")
|
||||
.add(self.bracket.assembly(), name=name_bracket_back)
|
||||
.add(self.bolt.assembly(), name=name_bolt_back)
|
||||
.add(self.nut.assembly(), name=name_nut_back)
|
||||
.constrain(f"{name_actuator}/back?conn", f"{name_bracket_back}?conn_mid",
|
||||
"Plane", param=0)
|
||||
.constrain(f"{name_bolt_back}?root", f"{name_bracket_back}?conn_top",
|
||||
"Plane", param=0)
|
||||
.constrain(f"{name_nut_back}?bot", f"{name_bracket_back}?conn_bot",
|
||||
"Plane")
|
||||
)
|
||||
if tag_hole_front:
|
||||
a.constrain(tag_hole_front, f"{name_bracket_front}?conn_side", "Plane")
|
||||
if tag_hole_back:
|
||||
a.constrain(tag_hole_back, f"{name_bracket_back}?conn_side", "Plane")
|
||||
if tag_dir:
|
||||
a.constrain(tag_dir, f"{name_bracket_front}?conn_mid", "Axis", param=0)
|
||||
|
|
|
@ -484,10 +484,10 @@ class ShoulderJoint(Model):
|
|||
# Fasteners
|
||||
.addS(self.bolt.assembly(), name="bolt_top",
|
||||
loc=Cq.Location((0, 0, bolt_z)))
|
||||
.constrain("bolt_top/thread?root", 'Fixed')
|
||||
.constrain("bolt_top?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')
|
||||
.constrain("bolt_bot?root", 'Fixed')
|
||||
)
|
||||
TorsionJoint.add_constraints(
|
||||
result,
|
||||
|
|
Loading…
Reference in New Issue