diff --git a/nhf/touhou/yasaka_kanako/onbashira.py b/nhf/touhou/yasaka_kanako/onbashira.py index 6a82d0c..9263e4c 100644 --- a/nhf/touhou/yasaka_kanako/onbashira.py +++ b/nhf/touhou/yasaka_kanako/onbashira.py @@ -3,7 +3,7 @@ from nhf.materials import Role, Material import nhf.utils import math -from dataclasses import dataclass +from dataclasses import dataclass, field import cadquery as Cq @dataclass @@ -13,28 +13,43 @@ class Onbashira(Model): # Dimensions of each side panel side_width: float = 200.0 side_length: float = 600.0 - side_thickness: float = 25.4 / 8 + # Joints between two sets of side panels + angle_joint_thickness: float = 25.4 / 4 + # Z-axis size of each angle joint + angle_joint_depth: float = 50.0 + # Gap of each angle joint to connect the outside to the inside + angle_joint_gap: float = 10.0 + angle_joint_bolt_length: float = 50.0 + angle_joint_bolt_diam: float = 10.0 + # Position of the holes, with (0, 0) being the centre of each side + angle_joint_hole_position: list[float] = field(default_factory=lambda: [ + (20, 20), + (70, 20), + ]) + # Dimensions of gun barrels barrel_diam: float = 25.4 * 2 barrel_length: float = 300.0 # Radius from barrel centre to axis - rotation_radius: float = 75.0 + rotation_radius: float = 90.0 n_bearing_balls: int = 24 # Size of ball bearings bearing_ball_diam: float = 25.4 * 1/2 bearing_ball_gap: float = .5 # Thickness of bearing disks bearing_thickness: float = 20.0 - bearing_track_radius: float = 120.0 + bearing_track_radius: float = 135.0 # Gap between the inner and outer bearing disks bearing_gap: float = 10.0 bearing_disk_thickness: float = 25.4 / 8 + rotor_inner_radius: float = 55.0 + rotor_bind_bolt_diam: float = 10.0 - rotor_bind_radius: float = 50.0 - stator_bind_radius: float = 150.0 + rotor_bind_radius: float = 110.0 + stator_bind_radius: float = 170.0 material_side: Material = Material.WOOD_BIRCH material_bearing: Material = Material.PLASTIC_PLA @@ -47,18 +62,37 @@ class Onbashira(Model): assert self.bulk_radius - self.side_thickness - self.bearing_thickness - self.bearing_diam > self.rotation_radius + self.barrel_diam / 2 assert self.bearing_gap < 0.95 * self.bearing_ball_diam assert self.rotor_bind_bolt_diam < self.rotor_bind_radius < self.bearing_track_radius - assert self.bearing_track_radius < self.stator_bind_radius + assert self.rotor_inner_radius < self.bearing_track_radius < self.stator_bind_radius + assert self.angle_joint_thickness > self.side_thickness @property def angle_side(self) -> float: return 360 / self.n_side + @property + def side_width_inner(self) -> float: + """ + Interior side width + + If outer width is `wi`, inner width is `wo`, each side's cross section + is a trapezoid with sides `wi`, `wo`, and height `h` (side thickness) + """ + theta = math.pi / self.n_side + dt = self.side_thickness * math.tan(theta) + return self.side_width - dt * 2 + @property + def angle_joint_extra_width(self) -> float: + theta = math.pi / self.n_side + dt = self.angle_joint_thickness * math.tan(theta) + return dt * 2 + + @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 + Radius of the bulk (surface of each side) to the centre """ return self.side_width / 2 / math.tan(math.radians(self.angle_side / 2)) @property @@ -78,7 +112,7 @@ class Onbashira(Model): def profile_bearing_stator(self) -> Cq.Sketch: return ( Cq.Sketch() - .regularPolygon(self.side_width, self.n_side) + .regularPolygon(self.side_width - self.side_thickness, self.n_side) .circle(self.bearing_track_radius + self.bearing_gap/2, mode="s") .reset() .regularPolygon( @@ -99,6 +133,7 @@ class Onbashira(Model): return ( Cq.Sketch() .circle(self.bearing_track_radius - self.bearing_gap/2) + .circle(self.rotor_inner_radius, mode="s") .reset() .regularPolygon( self.rotation_radius, self.n_side, @@ -143,6 +178,9 @@ class Onbashira(Model): @target(name="pipe", kind=TargetKind.DXF) def pipe(self) -> Cq.Sketch: + """ + The rotating pipes. Purely for decoration + """ pass @target(name="side-panel", kind=TargetKind.DXF) @@ -160,6 +198,7 @@ class Onbashira(Model): .placeSketch(self.profile_side_panel()) .extrude(self.side_thickness) ) + # Bevel the edges intersector = ( Cq.Workplane('XZ') .polyline([ @@ -174,6 +213,71 @@ class Onbashira(Model): # Intersect the side panel return result * intersector + def angle_joint(self) -> Cq.Workplane: + """ + Angular joint between two side panels. This sits at the intersection of + 4 side panels to provide compressive, shear, and tensile strength. + + To provide tensile strength along the Z-axis, the panels must be bolted + onto the angle joint. + """ + + # Create the slot carving + slot = ( + Cq.Sketch() + .regularPolygon( + self.side_width, + self.n_side + ) + .regularPolygon( + self.side_width_inner, + self.n_side, mode="s", + ) + ) + slot = ( + Cq.Workplane() + .placeSketch(slot) + .extrude(self.angle_joint_depth) + ) + + # Construct the overall shape of the joint, and divide it into sections for printing later. + sketch = ( + Cq.Sketch() + .regularPolygon( + self.side_width + self.angle_joint_extra_width, + self.n_side + ) + .regularPolygon( + self.side_width - self.angle_joint_extra_width, + self.n_side, mode="s" + ) + ) + + h = (self.bulk_radius + self.angle_joint_extra_width) * 2 + # Intersector for 1/n of the ring + intersector = ( + Cq.Workplane() + .sketch() + .polygon([ + (0, 0), + (h, 0), + (h, h * math.tan(2 * math.pi / self.n_side)) + ]) + .finalize() + .extrude(self.angle_joint_depth*4) + .translate((0, 0, -self.angle_joint_depth*2)) + ) + result = ( + Cq.Workplane() + .placeSketch(sketch) + .extrude(self.angle_joint_depth) + .translate((0, 0, -self.angle_joint_depth/2)) + .cut(slot.translate((0, 0, self.angle_joint_gap/2))) + .cut(slot.translate((0, 0, -self.angle_joint_depth-self.angle_joint_gap/2))) + .intersect(intersector) + ) + return result + def bearing_ball(self) -> Cq.Solid: