From 6709e4f32ea6c0b7fb9633044c40355cdce76156 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Thu, 29 May 2025 14:04:12 -0700 Subject: [PATCH] Bearing couplers --- nhf/touhou/yasaka_kanako/onbashira.py | 288 +++++++++++++++++++------- 1 file changed, 209 insertions(+), 79 deletions(-) diff --git a/nhf/touhou/yasaka_kanako/onbashira.py b/nhf/touhou/yasaka_kanako/onbashira.py index 6ea555b..93a6223 100644 --- a/nhf/touhou/yasaka_kanako/onbashira.py +++ b/nhf/touhou/yasaka_kanako/onbashira.py @@ -31,6 +31,15 @@ BOLT_COMMON = FlatHeadBolt( height_thread=30.0, pitch=1.0, ) +BOLT_BEARING = FlatHeadBolt( + # FIXME: measure + mass=0.0, + diam_head=12.8, + height_head=2.8, + diam_thread=4.0, + height_thread=30.0, + pitch=1.0, +) @dataclass(frozen=True) @@ -124,12 +133,12 @@ class Onbashira(Model): angle_joint_flange_thickness: float = 7.8 angle_joint_flange_radius: float = 23.0 angle_joint_flange_extension: float = 23.0 - angle_joint_extra_hole_offset: float = 20.0 # Mating structure on the angle joint angle_joint_conn_thickness: float = 4.0 angle_joint_conn_depth: float = 15.0 angle_joint_conn_width: float = 15.0 + angle_joint_bind_radius: float = 135.0 chamber_side_length: float = 400.0 chamber_side_width_ex: float = 20.0 @@ -140,26 +149,33 @@ class Onbashira(Model): barrel_length: float = 25.4 * 12 # Gap between the stator edge and the inner face of the barrel - stator_gap: float = 10.0 + stator_gap: float = 3.0 # Radius from barrel centre to axis - rotation_radius: float = 66.0 + rotation_radius: float = 64.0 n_bearing_balls: int = 12 - # 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 = 100.0 + bearing_track_radius: float = 97.0 # Gap between the inner and outer bearing disks bearing_gap: float = 10.0 + bearing_disk_gap: float = 10.0 bearing_spindle_max_diam: float = 13.0 - bearing_disk_thickness: float = 25.4 / 8 + bearing_gasket_extend: float = 12.0 + bearing_disk_thickness: float = 25.4 / 16 - rotor_inner_radius: float = 40.0 + # Coupling mechanism onto the chassis + stator_coupler_width: float = 14.0 + stator_coupler_thickness: float = 30.0 + stator_coupler_thickness_inner: float = 10.0 + + stator_bind_radius: float = 117.0 + # Extra bind sites for stator to prevent warping + stator_bind_extra: int = 2 + rotor_inner_radius: float = 36.0 rotor_bind_bolt_diam: float = BOLT_COMMON.diam_thread rotor_bind_radius: float = 82.0 + rotor_bind_extra: int = 1 rotor_spacer_outer_diam: float = 15.0 - stator_bind_radius: float = 135.0 handle_base_height: float = 10.0 handle_thickness: float = 17.0 @@ -171,7 +187,7 @@ class Onbashira(Model): material_side: Material = Material.WOOD_BIRCH material_bearing: Material = Material.PLASTIC_PLA material_spacer: Material = Material.PLASTIC_PLA - material_bearing_ball: Material = Material.ACRYLIC_TRANSPARENT + material_bearing_ball: Material = Material.PLASTIC_PLA material_barrel: Material = Material.ACRYLIC_BLACK material_brace: Material = Material.PLASTIC_PLA material_fastener: Material = Material.STEEL_STAINLESS @@ -179,10 +195,9 @@ class Onbashira(Model): 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_gap < 0.95 * self.bearing_ball_diam - assert self.rotor_bind_bolt_diam < self.rotor_bind_radius < self.bearing_track_radius - assert self.rotor_inner_radius < self.bearing_track_radius < self.stator_bind_radius + assert self.bulk_radius - self.side_thickness - self.bearing_thickness > self.rotation_radius + self.barrel_diam / 2 + assert BOLT_COMMON.diam_thread < self.rotor_bind_radius < self.bearing_track_radius + assert self.rotor_inner_radius < self.bearing_track_radius < self.angle_joint_bind_radius assert self.angle_joint_thickness > self.side_thickness for (x, y) in self.angle_joint_bolt_position: @@ -240,18 +255,6 @@ class Onbashira(Model): Radius of the bulk (surface of each side) to the centre """ return self.chamber_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_disk_gap(self) -> float: - """ - Gap between two bearing disks to touch the bearing balls - """ - diag = self.bearing_ball_diam - dx = self.bearing_gap - return math.sqrt(diag ** 2 - dx ** 2) @target(name="sanding-block") def sanding_block(self) -> Cq.Workplane: @@ -267,15 +270,64 @@ class Onbashira(Model): .extrude(self.side_width * 1.5) ) + @target(name="stator-coupler") + def stator_coupler(self) -> Cq.Workplane: + """ + Couples the stator to the chassis + """ + r1 = self.angle_joint_bind_radius + r2 = self.stator_bind_radius + assert r1 > r2 + + l = r1 - r2 + w = self.stator_coupler_width + h = self.stator_coupler_thickness + h_step = h - self.stator_coupler_thickness_inner + + intersector = Cq.Solid.makeBox( + length=l + w, + width=w, + height=h_step, + ).moved(0, -w/2, 0) + + profile = ( + Cq.Sketch() + .rect(l, w) + .push([ + (-l/2, 0), + (l/2, 0), + ]) + .circle(w/2, mode="a") + .push([ + (-l/2, 0), + (l/2, 0), + ]) + .circle(BOLT_COMMON.diam_thread/2, mode="s") + ) + result = ( + Cq.Workplane() + .placeSketch(profile) + .extrude(h) + ) + dx = l / 2 + result = result - intersector + result.tagAbsolute(f"holeOB", (-dx, 0, 0), direction="-Z") + result.tagAbsolute(f"holeIB", (+dx, 0, h_step), direction="-Z") + result.tagAbsolute(f"holeOF", (-dx, 0, h), direction="+Z") + result.tagAbsolute(f"holeIF", (+dx, 0, h), direction="+Z") + return result + @target(name="bearing-stator", kind=TargetKind.DXF) def profile_bearing_stator(self) -> Cq.Sketch: + assert self.stator_bind_radius < self.angle_joint_bind_radius return ( Cq.Sketch() - .regularPolygon(self.side_width - self.side_thickness, self.n_side) + .circle(self.bulk_radius - self.side_thickness - self.stator_gap) + #.regularPolygon(self.side_width - self.side_thickness - self.stator_gap, self.n_side*2) .circle(self.bearing_track_radius + self.bearing_gap/2, mode="s") .reset() .regularPolygon( - self.stator_bind_radius, self.n_side, + self.stator_bind_radius, self.n_side * (1 + self.stator_bind_extra), mode="c", tag="bolt") .vertices(tag="bolt") .circle(self.rotor_bind_bolt_diam/2, mode="s") @@ -286,20 +338,30 @@ class Onbashira(Model): .placeSketch(self.profile_bearing_stator()) .extrude(self.bearing_disk_thickness) ) + br = self.stator_bind_radius + th1 = math.radians(360 / self.n_side) + th2 = math.radians(360 / (self.n_side * (1 + self.stator_bind_extra))) for i in range(self.n_side): - angle = (i+0.5) * math.radians(360 / self.n_side) + angle = (i+0.5) * th1 result.faces(">Z").moveTo( - self.stator_bind_radius * math.cos(angle), - self.stator_bind_radius * math.sin(angle), + br * math.cos(angle), + br * math.sin(angle), ).tagPlane(f"holeF{i}") result.faces(" Cq.Sketch: - bolt_angle = 180 / self.n_side + bolt_angle = (180 / self.n_side) * 1.5 + n_binds = 1 + self.rotor_bind_extra return ( Cq.Sketch() .circle(self.bearing_track_radius - self.bearing_gap/2) @@ -312,8 +374,10 @@ class Onbashira(Model): .circle(self.barrel_diam/2, mode="s") .reset() .regularPolygon( - self.rotor_bind_radius, self.n_side, - mode="c", tag="bolt", angle=bolt_angle) + r=self.rotor_bind_radius, + n=self.n_side * n_binds, + mode="c", tag="bolt", + angle=bolt_angle) .vertices(tag="bolt") .circle(self.rotor_bind_bolt_diam/2, mode="s") ) @@ -325,7 +389,7 @@ class Onbashira(Model): ) @target(name="bearing-gasket", kind=TargetKind.DXF) def profile_bearing_gasket(self) -> Cq.Sketch: - dr = self.bearing_ball_diam + dr = self.bearing_gasket_extend eps = 0.05 return ( Cq.Sketch() @@ -336,7 +400,7 @@ class Onbashira(Model): self.bearing_track_radius, self.n_bearing_balls, mode="c", tag="corners") .vertices(tag="corners") - .circle(self.bearing_ball_diam/2 * (1+eps), mode="s") + .circle(BOLT_BEARING.diam_thread, mode="s") ) def bearing_gasket(self) -> Cq.Workplane: return ( @@ -353,11 +417,8 @@ class Onbashira(Model): """ pass - def bearing_ball(self) -> Cq.Solid: - return Cq.Solid.makeSphere(radius=self.bearing_ball_diam/2, angleDegrees1=-90) - - @target(name="rotor-spacer") - def rotor_spacer(self) -> Cq.Solid: + @target(name="stator-spacer") + def stator_spacer(self) -> Cq.Solid: outer = Cq.Solid.makeCylinder( radius=self.rotor_spacer_outer_diam/2, height=self.bearing_disk_gap, @@ -367,16 +428,31 @@ class Onbashira(Model): height=self.bearing_disk_gap ) return outer - inner + @target(name="rotor-spacer") + def rotor_spacer(self) -> Cq.Solid: + outer = Cq.Solid.makeCylinder( + radius=self.rotor_spacer_outer_diam/2, + height=self.bearing_disk_gap, + ) + inner = Cq.Solid.makeCylinder( + radius=BOLT_BEARING.diam_thread/2, + height=self.bearing_disk_gap + ) + return outer - inner + + @property + def bearing_spindle_height(self) -> float: + h = self.bearing_disk_gap + 2 * self.bearing_disk_thickness + return h * 2 @target(name="bearing-spindle") def bearing_spindle(self) -> Cq.Solid: r1 = self.bearing_gap / 2 r2 = self.bearing_spindle_max_diam - h = self.bearing_disk_gap - cone1 = Cq.Solid.makeCone( - radius1=r2, - radius2=r1, - height=h/2 + h = self.bearing_disk_gap + 2 * self.bearing_disk_thickness + cone1 = Cq.Solid.makeCylinder( + radius=r1, + height=h/2, ) cone2 = Cq.Solid.makeCone( radius1=r1, @@ -384,17 +460,33 @@ class Onbashira(Model): height=h/2, ) hole = Cq.Solid.makeCylinder( - radius=(BOLT_COMMON.diam_thread + 1)/2, + radius=(BOLT_BEARING.diam_thread + 1)/2, height=h*2 ).moved(0, 0, -h) top = (cone1 + cone2.moved(0, 0, h/2)) - hole return top + top.rotate((0,0,0),(1,0,0),180) - def assembly_barrel(self) -> Cq.Assembly: + def barrel(self) -> Cq.Compound: + """ + One gun barrel + """ + outer = Cq.Solid.makeCylinder( + radius=self.barrel_diam/2, + height=self.barrel_length, + ) + inner = Cq.Solid.makeCylinder( + radius=self.barrel_diam/2-self.barrel_wall_thickness, + height=self.barrel_length + ) + return outer - inner + + @assembly() + def assembly_machine(self) -> Cq.Assembly: """ The assembly with gun barrels """ z_lower = -self.bearing_disk_gap/2 - self.bearing_disk_thickness + gasket_h = self.bearing_spindle_height / 2 a = ( Cq.Assembly() .addS( @@ -427,36 +519,52 @@ class Onbashira(Model): ) .addS( self.bearing_gasket(), - name="gasket", + name="gasket_bot", material=self.material_bearing, role=Role.ROTOR, - loc=Cq.Location(0, 0, -self.bearing_disk_thickness/2) + loc=Cq.Location(0, 0, -gasket_h-self.bearing_disk_thickness) + ) + .addS( + self.bearing_gasket(), + name="gasket_top", + material=self.material_bearing, + role=Role.ROTOR, + loc=Cq.Location(0, 0, gasket_h) ) ) z = -self.bearing_disk_gap/2 for i in range(self.n_side): + loc_barrel = Cq.Location.rot2d((i+1/2) * 360/self.n_side) * \ + Cq.Location(self.rotation_radius, 0, -self.barrel_length/2) + a = a.addS( + self.barrel(), + name=f"barrel{i}", + material=self.material_barrel, + role=Role.DECORATION, + loc=loc_barrel, + ) loc = Cq.Location.rot2d(i * 360/self.n_side) * Cq.Location(self.rotor_bind_radius, 0, z) - a = a.addS( - self.rotor_spacer(), - name=f"spacerRotor{i}", - material=self.material_spacer, - role=Role.STRUCTURE, - loc=loc - ) - loc = Cq.Location.rot2d((i+0.5) * 360/self.n_side) * Cq.Location(self.stator_bind_radius, 0, z) - a = a.addS( - self.rotor_spacer(), - name=f"spacerStator{i}", - material=self.material_spacer, - role=Role.STRUCTURE, - loc=loc - ) + #a = a.addS( + # self.stator_spacer(), + # name=f"spacerRotor{i}", + # material=self.material_spacer, + # role=Role.STRUCTURE, + # loc=loc + #) + loc = Cq.Location.rot2d((i+0.5) * 360/self.n_side) * Cq.Location(self.angle_joint_bind_radius, 0, z) + #a = a.addS( + # self.rotor_spacer(), + # name=f"spacerStator{i}", + # material=self.material_spacer, + # role=Role.STRUCTURE, + # loc=loc + #) for i in range(self.n_bearing_balls): - ball = self.bearing_ball() + ball = self.bearing_spindle() loc = Cq.Location.rot2d(i * 360/self.n_bearing_balls) * Cq.Location(self.bearing_track_radius, 0, 0) a = a.addS( ball, - name=f"bearing_ball{i}", + name=f"bearing_spindle{i}", material=self.material_bearing_ball, role=Role.BEARING, loc=loc, @@ -692,7 +800,7 @@ class Onbashira(Model): .regularPolygon(self.side_width - self.side_thickness, self.n_side) .reset() .regularPolygon( - self.stator_bind_radius, self.n_side, + self.angle_joint_bind_radius, self.n_side, mode="c", tag="bolt") .vertices(tag="bolt") .circle(self.rotor_bind_bolt_diam/2, mode="s") @@ -707,8 +815,8 @@ class Onbashira(Model): # Mark all attachment points for i in range(self.n_side): angle = (i+0.5) * math.radians(360 / self.n_side) - x = self.stator_bind_radius * math.cos(angle) - y = self.stator_bind_radius * math.sin(angle) + x = self.angle_joint_bind_radius * math.cos(angle) + y = self.angle_joint_bind_radius * math.sin(angle) result.tagAbsolute(f"holeF{i}", (x, y, self.side_thickness), direction="+Z") result.tagAbsolute(f"holeB{i}", (x, -y, 0), direction="-Z") return result @@ -743,7 +851,7 @@ class Onbashira(Model): .extrude(self.angle_joint_flange_thickness) .translate((0, 0, -self.angle_joint_flange_thickness/2)) ) - ri = self.stator_bind_radius + ri = self.angle_joint_bind_radius h = self.angle_joint_flange_thickness # drill hole cyl = Cq.Solid.makeCylinder( @@ -878,7 +986,7 @@ class Onbashira(Model): .extrude(self.angle_joint_gap) .translate((0, 0, -flange_z)) ) - ri = self.stator_bind_radius + ri = self.angle_joint_bind_radius h = self.angle_joint_gap # Drill holes for connectors cyl = Cq.Solid.makeCylinder( @@ -1027,7 +1135,8 @@ class Onbashira(Model): flange = self.angle_joint_flange() result = result + self.angle_joint_flange() th = math.pi / self.n_side - ri = self.stator_bind_radius + ri = self.angle_joint_bind_radius + h = self.angle_joint_gap result.tagAbsolute("holeStatorL", (ri * math.cos(th), ri * math.sin(th), h/2), direction="+Z") result.tagAbsolute("holeStatorR", (ri * math.cos(th), ri * math.sin(th), -h/2), direction="-Z") return result @@ -1153,11 +1262,13 @@ class Onbashira(Model): flange = self.angle_joint_flange() result = result + self.angle_joint_flange() th = math.pi / self.n_side - ri = self.stator_bind_radius + ri = self.angle_joint_bind_radius + h = self.angle_joint_gap result.tagAbsolute("holeStatorL", (ri * math.cos(th), ri * math.sin(th), h/2), direction="+Z") result.tagAbsolute("holeStatorR", (ri * math.cos(th), ri * math.sin(th), -h/2), direction="-Z") return result + @assembly() def assembly_ring(self, base) -> Cq.Assembly: a = Cq.Assembly() r = self.bulk_radius @@ -1280,9 +1391,9 @@ class Onbashira(Model): self.handle(), name="handle", material=self.material_brace, - role=Role.STRUCTURE, + role=Role.HANDLE, ) - #.add(self.assembly_barrel(), name="barrel") + .add(self.assembly_machine(), name="machine") ) # Add handle for ih, (x, y) in enumerate(self.angle_joint_bolt_position): @@ -1298,6 +1409,25 @@ class Onbashira(Model): ) for i in range(self.n_side): j = (i + 1) % self.n_side + ir = (self.n_side - i) % self.n_side + + coupler_name = f"stator_coupler{i}" + a = a.addS( + self.stator_coupler(), + name=coupler_name, + material=self.material_brace, + role=Role.STRUCTURE, + ) + a = a.constrain( + f"{coupler_name}?holeOB", + f"ring1/side{i}?holeStatorL", + "Plane", + ) + a = a.constrain( + f"{coupler_name}?holeIF", + f"machine/stator2?holeB{ir}", + "Plane", + ) for ih in range(len(self.angle_joint_bolt_position)): a = a.constrain( f"chamber/side{i}?holeFPI{ih}",