cosplay: Touhou/Yasaka Kanako #11
|
@ -25,6 +25,9 @@ class Role(Flag):
|
|||
PARENT = auto()
|
||||
CHILD = auto()
|
||||
CASING = auto()
|
||||
STATOR = auto()
|
||||
ROTOR = auto()
|
||||
BEARING = auto()
|
||||
# Springs, cushions
|
||||
DAMPING = auto()
|
||||
# Main structural support
|
||||
|
@ -59,6 +62,9 @@ ROLE_COLOR_MAP = {
|
|||
Role.PARENT: _color('blue4', 0.6),
|
||||
Role.CASING: _color('dodgerblue3', 0.6),
|
||||
Role.CHILD: _color('darkorange2', 0.6),
|
||||
Role.STATOR: _color('gray', 0.5),
|
||||
Role.ROTOR: _color('blue3', 0.5),
|
||||
Role.BEARING: _color('green3', 0.8),
|
||||
Role.DAMPING: _color('springgreen', 1.0),
|
||||
Role.STRUCTURE: _color('gray', 0.4),
|
||||
Role.DECORATION: _color('lightseagreen', 0.4),
|
||||
|
@ -84,6 +90,7 @@ class Material(Enum):
|
|||
ACRYLIC_TRANSLUSCENT = 1.18, _color('ivory2', 0.8)
|
||||
ACRYLIC_TRANSPARENT = 1.18, _color('ghostwhite', 0.5)
|
||||
STEEL_SPRING = 7.8, _color('gray', 0.8)
|
||||
METAL_AL = 2.7, _color('gray', 0.6)
|
||||
METAL_BRASS = 8.5, _color('gold1', 0.8)
|
||||
|
||||
def __init__(self, density: float, color: Cq.Color):
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
from nhf.materials import Role, Material
|
||||
import nhf.utils
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
import cadquery as Cq
|
||||
|
||||
@dataclass
|
||||
class Onbashira(Model):
|
||||
|
||||
n_side: int = 6
|
||||
# Dimensions of each side panel
|
||||
side_width: float = 200.0
|
||||
side_length: float = 600.0
|
||||
|
||||
side_thickness: float = 25.4 / 8
|
||||
|
||||
# Dimensions of gun barrels
|
||||
barrel_diam: float = 45.0
|
||||
barrel_length: float = 300.0
|
||||
# Radius from barrel centre to axis
|
||||
rotation_radius: float = 75.0
|
||||
# Radius of ball bearings
|
||||
bearing_ball_diam: float = 30.0
|
||||
bearing_ball_gap: float = 1.0
|
||||
bearing_height: float = 40.0
|
||||
bearing_thickness: float = 20.0
|
||||
|
||||
material_side: Material = Material.WOOD_BIRCH
|
||||
material_bearing: Material = Material.PLASTIC_PLA
|
||||
material_bearing_ball: Material = Material.ACRYLIC_TRANSPARENT
|
||||
material_brace: Material = Material.METAL_AL
|
||||
|
||||
def __post_init__(self):
|
||||
assert self.n_side >= 3
|
||||
# Bulk must be large enough for the barrel + bearing to rotate
|
||||
assert self.bulk_radius - self.side_thickness - self.bearing_thickness - self.bearing_diam > self.rotation_radius + self.barrel_diam / 2
|
||||
assert self.bearing_height > self.bearing_diam
|
||||
|
||||
@property
|
||||
def angle_side(self) -> float:
|
||||
return 360 / self.n_side
|
||||
@property
|
||||
def angle_dihedral(self) -> float:
|
||||
return 180 - self.angle_side
|
||||
@property
|
||||
def bulk_radius(self) -> float:
|
||||
"""
|
||||
Calculate radius of the bulk to the centre
|
||||
"""
|
||||
return self.side_width / 2 / math.tan(math.radians(self.angle_side / 2))
|
||||
@property
|
||||
def bearing_diam(self) -> float:
|
||||
return self.bearing_ball_diam + self.bearing_ball_gap
|
||||
@property
|
||||
def bearing_radius(self) -> float:
|
||||
return self.bulk_radius - self.side_thickness - self.bearing_thickness - self.bearing_diam / 2
|
||||
|
||||
@target(name="side-panel", kind=TargetKind.DXF)
|
||||
def profile_side_panel(self) -> Cq.Sketch:
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.rect(self.side_width, self.side_length)
|
||||
)
|
||||
|
||||
def side_panel(self) -> Cq.Workplane:
|
||||
w = self.side_width
|
||||
l = self.side_length
|
||||
result = (
|
||||
Cq.Workplane()
|
||||
.placeSketch(self.profile_side_panel())
|
||||
.extrude(self.side_thickness)
|
||||
)
|
||||
intersector = (
|
||||
Cq.Workplane('XZ')
|
||||
.polyline([
|
||||
(-w/2, 0),
|
||||
(w/2, 0),
|
||||
(0, self.bulk_radius),
|
||||
])
|
||||
.close()
|
||||
.extrude(l)
|
||||
.translate(Cq.Vector(0, l/2,0))
|
||||
)
|
||||
# Intersect the side panel
|
||||
return result * intersector
|
||||
|
||||
def bearing_channel(self) -> Cq.Solid:
|
||||
"""
|
||||
Generates a toroidal channel for the ball bearings
|
||||
"""
|
||||
return Cq.Solid.makeTorus(
|
||||
radius1=self.bearing_radius,
|
||||
radius2=self.bearing_diam/2,
|
||||
)
|
||||
@target(name="inner-rotor")
|
||||
def inner_rotor(self) -> Cq.Workplane:
|
||||
r_outer = self.bearing_radius
|
||||
base = Cq.Solid.makeCylinder(
|
||||
radius=r_outer,
|
||||
height=self.bearing_height
|
||||
).translate(Cq.Vector(0,0,-self.bearing_height/2))
|
||||
r_rot = self.rotation_radius
|
||||
channel = self.bearing_channel()
|
||||
return (
|
||||
Cq.Workplane()
|
||||
.add(base - channel)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.polygon(
|
||||
nSides=self.n_side,
|
||||
diameter=2 * r_rot,
|
||||
forConstruction=True
|
||||
)
|
||||
.vertices()
|
||||
.hole(self.barrel_diam)
|
||||
)
|
||||
return base - channel
|
||||
@target(name="outer-rotor")
|
||||
def outer_rotor(self) -> Cq.Workplane:
|
||||
polygon_radius = (self.bulk_radius - self.side_thickness) / math.cos(math.radians(self.angle_side / 2))
|
||||
profile = (
|
||||
Cq.Sketch()
|
||||
.regularPolygon(
|
||||
r=polygon_radius,
|
||||
n=self.n_side,
|
||||
)
|
||||
)
|
||||
inner = Cq.Solid.makeCylinder(
|
||||
radius=self.bearing_radius,
|
||||
height=self.bearing_height,
|
||||
)
|
||||
base = (
|
||||
Cq.Workplane()
|
||||
.placeSketch(profile)
|
||||
.extrude(self.bearing_height)
|
||||
.cut(inner)
|
||||
.translate(Cq.Vector(0,0,-self.bearing_height/2))
|
||||
.cut(self.bearing_channel())
|
||||
)
|
||||
r = self.bearing_radius * 2
|
||||
subtractor = Cq.Solid.makeBox(
|
||||
length=r * 2,
|
||||
width=r * 2,
|
||||
height=self.bearing_height,
|
||||
).translate(Cq.Vector(-r, -r, -self.bearing_height))
|
||||
return base - subtractor
|
||||
def bearing_ball(self) -> Cq.Solid:
|
||||
return Cq.Solid.makeSphere(radius=self.bearing_ball_diam/2, angleDegrees1=-90)
|
||||
|
||||
def assembly_bearing(self) -> Cq.Assembly:
|
||||
a = (
|
||||
Cq.Assembly()
|
||||
.addS(
|
||||
self.inner_rotor(),
|
||||
name="inner",
|
||||
material=self.material_bearing,
|
||||
role=Role.ROTOR,
|
||||
)
|
||||
.addS(
|
||||
self.outer_rotor(),
|
||||
name="outer",
|
||||
material=self.material_bearing,
|
||||
role=Role.STATOR,
|
||||
)
|
||||
)
|
||||
for i in range(self.n_side):
|
||||
ball = self.bearing_ball()
|
||||
a = a.addS(
|
||||
ball,
|
||||
name=f"bearing_ball{i}",
|
||||
material=self.material_bearing_ball,
|
||||
role=Role.BEARING,
|
||||
loc=Cq.Location.rot2d(i * 360/self.n_side) * Cq.Location(self.bearing_radius, 0, 0),
|
||||
)
|
||||
return a
|
||||
|
||||
def assembly(self) -> Cq.Assembly:
|
||||
a = Cq.Assembly()
|
||||
side = self.side_panel()
|
||||
r = self.bulk_radius
|
||||
for i in range(6):
|
||||
a = a.addS(
|
||||
side,
|
||||
name=f"side{i}",
|
||||
material=self.material_side,
|
||||
role=Role.STRUCTURE | Role.DECORATION,
|
||||
loc=Cq.Location(0,0,0,0,i*60,0) * Cq.Location(0,0,-r)
|
||||
)
|
||||
return a
|
Loading…
Reference in New Issue