From a74f919a5b70135b1b6a4831abd704603cd32d46 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Tue, 25 Feb 2025 21:04:25 -0800 Subject: [PATCH] Onbashira rotor-stator mechanism --- nhf/materials.py | 6 ++ nhf/touhou/yasaka_kanako/onbashira.py | 120 +++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 4 deletions(-) diff --git a/nhf/materials.py b/nhf/materials.py index 909e771..86e0ae9 100644 --- a/nhf/materials.py +++ b/nhf/materials.py @@ -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), diff --git a/nhf/touhou/yasaka_kanako/onbashira.py b/nhf/touhou/yasaka_kanako/onbashira.py index 4169081..5dab33e 100644 --- a/nhf/touhou/yasaka_kanako/onbashira.py +++ b/nhf/touhou/yasaka_kanako/onbashira.py @@ -16,11 +16,27 @@ class Onbashira(Model): 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.ALUMINUM 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: @@ -29,11 +45,17 @@ class Onbashira(Model): def angle_dihedral(self) -> float: return 180 - self.angle_side @property - def barrel_radius(self) -> float: + def bulk_radius(self) -> float: """ - Calculate radius of the barrel to the centre + 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: @@ -55,7 +77,7 @@ class Onbashira(Model): .polyline([ (-w/2, 0), (w/2, 0), - (0, self.barrel_radius), + (0, self.bulk_radius), ]) .close() .extrude(l) @@ -64,10 +86,100 @@ class Onbashira(Model): # 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.barrel_radius + r = self.bulk_radius for i in range(6): a = a.addS( side,