From b565ab05a08fd66bfbcf52a83c080c448a98abee Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Thu, 29 May 2025 00:13:48 -0700 Subject: [PATCH] Additional mounting points for machinery on first 3 rings --- nhf/touhou/yasaka_kanako/onbashira.py | 380 ++++++++++++++------------ 1 file changed, 204 insertions(+), 176 deletions(-) diff --git a/nhf/touhou/yasaka_kanako/onbashira.py b/nhf/touhou/yasaka_kanako/onbashira.py index 3ed6b86..fcba7ba 100644 --- a/nhf/touhou/yasaka_kanako/onbashira.py +++ b/nhf/touhou/yasaka_kanako/onbashira.py @@ -84,7 +84,10 @@ class Onbashira(Model): angle_joint_bolt_position: list[float] = field(default_factory=lambda: [ (40, 10), ]) - 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 @@ -658,8 +661,154 @@ class Onbashira(Model): ) return a + @target(name="angle-joint-chamber-back") + def angle_joint_chamber_back(self) -> Cq.Workplane: + slot = ( + Cq.Workplane() + .sketch() + .regularPolygon( + self.side_width, + self.n_side + ) + .finalize() + .extrude(self.angle_joint_depth) + ) + thickness = self.chamber_bulk_radius - self.bulk_radius + + 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)) + ) + # The mating structure + z1 = self.bulk_radius + (thickness - self.angle_joint_conn_thickness) / 2 + z2 = z1 + self.angle_joint_conn_thickness + mating1n = ( + Cq.Workplane() + .sketch() + .polygon([ + (z1, 0), + (z1, self.angle_joint_conn_width), + (z2, self.angle_joint_conn_width), + (z2, 0), + ]) + .finalize() + .extrude(self.angle_joint_conn_depth) + ) + mating1p = mating1n.rotate((0,0,0), (1,0,0), 180) + angle = 360 / self.n_side + + chamber_intersector = ( + Cq.Workplane() + .sketch() + .regularPolygon(self.chamber_side_width, self.n_side) + .regularPolygon(self.chamber_side_width - self.delta_side_width, self.n_side, mode="s") + .finalize() + .extrude(self.angle_joint_depth) + .translate((0,0,self.angle_joint_gap/2)) + ) + result = ( + Cq.Workplane() + .sketch() + .regularPolygon( + self.chamber_side_width, + self.n_side + ) + .regularPolygon( + self.side_width_inner, + self.n_side, mode="s" + ) + .finalize() + .extrude(self.angle_joint_depth) + .translate((0, 0, -self.angle_joint_depth/2)) + .cut(slot.translate((0, 0, self.angle_joint_gap/2))) + .intersect(intersector) + .cut(chamber_intersector) + .cut(mating1n) + .union(mating1p) + .union(mating1n.rotate((0,0,0),(0,0,1),angle)) + .cut(mating1p.rotate((0,0,0),(0,0,1),angle)) + ) + h = self.chamber_bulk_radius + hole_negative = Cq.Solid.makeCylinder( + radius=self.angle_joint_bolt_diam/2, + height=h, + pnt=(0,0,0), + dir=(1,0,0), + ) + Cq.Solid.makeCylinder( + radius=self.angle_joint_bolt_head_diam/2, + height=self.angle_joint_bolt_head_depth, + pnt=(h,0,0), + dir=(-1,0,0), + ) + dy = self.angle_joint_gap / 2 + locrot = Cq.Location(0, 0, 0, 0, 0, 360/self.n_side) + for (x, y) in self.angle_joint_bolt_position: + p1 = Cq.Location((0, x, dy+y)) + p1r = locrot * Cq.Location((0, -x, dy+y)) + result = result \ + - hole_negative.moved(p1) \ + - hole_negative.moved(p1r) + # Mark the absolute locations of the mount points + dr = self.chamber_bulk_radius - self.side_thickness + locrot = Cq.Location(0, 0, 0, 0, 0, 360/self.n_side) + dr = self.chamber_bulk_radius - self.side_thickness + dy = self.angle_joint_gap / 2 + for i, (x, y) in enumerate(self.angle_joint_bolt_position): + py = dy + y + #result.tagAbsolute(f"holeLPO{i}", (dr, x, py), direction="+X") + result.tagAbsolute(f"holeLPO{i}", (dr, x, py), direction="+X") + #result.tagAbsolute(f"holeLSO{i}", locrot * Cq.Location(dr, -x, py), direction="+X") + result.tagAbsolute(f"holeLSO{i}", locrot * Cq.Location(dr, -x, py), direction="+X") + + th = math.pi / self.n_side + r = self.bulk_radius + flange_z = self.angle_joint_depth / 2 - self.side_thickness + flange = ( + Cq.Workplane() + .sketch() + .push([ + (r, r * math.tan(th)) + ]) + .circle(self.angle_joint_flange_extension) + .reset() + .regularPolygon(self.side_width_inner, self.n_side, mode="i") + .finalize() + .extrude(self.angle_joint_gap) + .translate((0, 0, -flange_z)) + ) + ri = self.stator_bind_radius + h = self.angle_joint_gap + # Drill holes for connectors + cyl = Cq.Solid.makeCylinder( + radius=self.rotor_bind_bolt_diam/2, + height=h, + pnt=(0, 0, -flange_z), + ) + result = ( + result + + flange + - cyl.moved(ri * math.cos(th), ri * math.sin(th), 0) + ) + result.tagAbsolute("holeStatorO", (ri * math.cos(th), ri * math.sin(th), -flange_z), direction="-Z") + result.tagAbsolute("holeStatorI", (ri * math.cos(th), ri * math.sin(th), -flange_z+h), direction="+Z") + return result + + @target(name="angle-joint-chamber-front") def angle_joint_chamber_front(self) -> Cq.Workplane: + """ + Angle joint for connecting the chamber to the chassis of the barrel + """ # This slot cuts the interior of the joint slot = ( Cq.Workplane() @@ -782,173 +931,41 @@ class Onbashira(Model): #result.tagAbsolute(f"holeLSO{i}", locrot * Cq.Location(dr, -x, py), direction="+X") result.tagAbsolute(f"holeRSO{i}", locrot * Cq.Location(dr, -x, -py), direction="+X") + # Generate the flange geometry th = math.pi / self.n_side - r = self.bulk_radius flange = ( Cq.Workplane() .sketch() - .push([ - (r, r * math.tan(th)) - ]) - .circle(self.angle_joint_flange_radius) - .reset() - .regularPolygon(self.side_width_inner, self.n_side, mode="i") + .regularPolygon(self.side_width_inner, self.n_side) + .regularPolygon(self.side_width_inner - self.angle_joint_flange_extension, self.n_side, mode="s") .finalize() .extrude(self.angle_joint_gap) .translate((0, 0, -self.angle_joint_gap/2)) ) + flange = flange * intersector ri = self.stator_bind_radius h = self.angle_joint_gap + # Drill holes for connectors cyl = Cq.Solid.makeCylinder( radius=self.rotor_bind_bolt_diam/2, height=h, - pnt=(ri * math.cos(th), ri * math.sin(th), -h/2), + pnt=(0, 0, -h/2), + ) + side_pos = Cq.Location(ri * math.cos(th), self.angle_joint_extra_hole_offset, 0) + side_pos2 = Cq.Location.rot2d(360/self.n_side) * side_pos.flip_y() + result = ( + result + + flange + - cyl.moved(ri * math.cos(th), ri * math.sin(th), 0) + - cyl.moved(side_pos.toTuple()) + - cyl.moved(side_pos2.toTuple()) ) - result = result + flange - cyl 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 - @target(name="angle-joint-chamber-back") - def angle_joint_chamber_back(self) -> Cq.Workplane: - slot = ( - Cq.Workplane() - .sketch() - .regularPolygon( - self.side_width, - self.n_side - ) - .finalize() - .extrude(self.angle_joint_depth) - ) - thickness = self.chamber_bulk_radius - self.bulk_radius - - 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)) - ) - # The mating structure - z1 = self.bulk_radius + (thickness - self.angle_joint_conn_thickness) / 2 - z2 = z1 + self.angle_joint_conn_thickness - mating1n = ( - Cq.Workplane() - .sketch() - .polygon([ - (z1, 0), - (z1, self.angle_joint_conn_width), - (z2, self.angle_joint_conn_width), - (z2, 0), - ]) - .finalize() - .extrude(self.angle_joint_conn_depth) - ) - mating1p = mating1n.rotate((0,0,0), (1,0,0), 180) - angle = 360 / self.n_side - - chamber_intersector = ( - Cq.Workplane() - .sketch() - .regularPolygon(self.chamber_side_width, self.n_side) - .regularPolygon(self.chamber_side_width - self.delta_side_width, self.n_side, mode="s") - .finalize() - .extrude(self.angle_joint_depth) - .translate((0,0,self.angle_joint_gap/2)) - ) - result = ( - Cq.Workplane() - .sketch() - .regularPolygon( - self.chamber_side_width, - self.n_side - ) - .regularPolygon( - self.side_width_inner, - self.n_side, mode="s" - ) - .finalize() - .extrude(self.angle_joint_depth) - .translate((0, 0, -self.angle_joint_depth/2)) - .cut(slot.translate((0, 0, self.angle_joint_gap/2))) - .intersect(intersector) - .cut(chamber_intersector) - .cut(mating1n) - .union(mating1p) - .union(mating1n.rotate((0,0,0),(0,0,1),angle)) - .cut(mating1p.rotate((0,0,0),(0,0,1),angle)) - ) - h = self.chamber_bulk_radius - hole_negative = Cq.Solid.makeCylinder( - radius=self.angle_joint_bolt_diam/2, - height=h, - pnt=(0,0,0), - dir=(1,0,0), - ) + Cq.Solid.makeCylinder( - radius=self.angle_joint_bolt_head_diam/2, - height=self.angle_joint_bolt_head_depth, - pnt=(h,0,0), - dir=(-1,0,0), - ) - dy = self.angle_joint_gap / 2 - locrot = Cq.Location(0, 0, 0, 0, 0, 360/self.n_side) - for (x, y) in self.angle_joint_bolt_position: - p1 = Cq.Location((0, x, dy+y)) - p1r = locrot * Cq.Location((0, -x, dy+y)) - result = result \ - - hole_negative.moved(p1) \ - - hole_negative.moved(p1r) - # Mark the absolute locations of the mount points - dr = self.chamber_bulk_radius - self.side_thickness - dr0 = self.bulk_radius - locrot = Cq.Location(0, 0, 0, 0, 0, 360/self.n_side) - dr = self.chamber_bulk_radius - self.side_thickness - dy = self.angle_joint_gap / 2 - for i, (x, y) in enumerate(self.angle_joint_bolt_position): - py = dy + y - #result.tagAbsolute(f"holeLPO{i}", (dr, x, py), direction="+X") - result.tagAbsolute(f"holeLPO{i}", (dr, x, py), direction="+X") - #result.tagAbsolute(f"holeLSO{i}", locrot * Cq.Location(dr, -x, py), direction="+X") - result.tagAbsolute(f"holeLSO{i}", locrot * Cq.Location(dr, -x, py), direction="+X") - - th = math.pi / self.n_side - r = self.bulk_radius - flange_z = self.angle_joint_depth / 2 - self.side_thickness - flange = ( - Cq.Workplane() - .sketch() - .push([ - (r, r * math.tan(th)) - ]) - .circle(self.angle_joint_flange_radius) - .reset() - .regularPolygon(self.side_width_inner, self.n_side, mode="i") - .finalize() - .extrude(self.angle_joint_gap) - .translate((0, 0, -flange_z)) - ) - ri = self.stator_bind_radius - h = self.angle_joint_gap - cyl = Cq.Solid.makeCylinder( - radius=self.rotor_bind_bolt_diam/2, - height=h, - pnt=(ri * math.cos(th), ri * math.sin(th), -flange_z), - ) - result = result + flange - cyl - result.tagAbsolute("holeStatorO", (ri * math.cos(th), ri * math.sin(th), -flange_z), direction="-Z") - result.tagAbsolute("holeStatorI", (ri * math.cos(th), ri * math.sin(th), -flange_z+h), direction="+Z") - return result - @target(name="angle-joint") - def angle_joint(self, add_flange=True) -> Cq.Workplane: + def angle_joint(self) -> Cq.Workplane: """ Angular joint between two side panels (excluding chamber). This sits at the intersection of 4 side panels to provide compressive, shear, and tensile strength. @@ -1064,32 +1081,36 @@ class Onbashira(Model): result.tagAbsolute(f"holeLSM{i}", locrot * Cq.Location(dr0, -x, py), direction="-X") result.tagAbsolute(f"holeRSM{i}", locrot * Cq.Location(dr0, -x, -py), direction="-X") - if add_flange: - th = math.pi / self.n_side - r = self.bulk_radius - flange = ( - Cq.Workplane() - .sketch() - .push([ - (r, r * math.tan(th)) - ]) - .circle(self.angle_joint_flange_radius) - .reset() - .regularPolygon(self.side_width_inner, self.n_side, mode="i") - .finalize() - .extrude(self.angle_joint_gap) - .translate((0, 0, -self.angle_joint_gap/2)) - ) - ri = self.stator_bind_radius - h = self.angle_joint_gap - cyl = Cq.Solid.makeCylinder( - radius=self.rotor_bind_bolt_diam/2, - height=h, - pnt=(ri * math.cos(th), ri * math.sin(th), -h/2), - ) - result = result + flange - cyl - 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") + # Generate the flange geometry + th = math.pi / self.n_side + flange = ( + Cq.Workplane() + .sketch() + .regularPolygon(self.side_width_inner, self.n_side) + .regularPolygon(self.side_width_inner - self.angle_joint_flange_extension, self.n_side, mode="s") + .finalize() + .extrude(self.angle_joint_gap) + .translate((0, 0, -self.angle_joint_gap/2)) + ) + flange = flange * intersector + ri = self.stator_bind_radius + h = self.angle_joint_gap + cyl = Cq.Solid.makeCylinder( + radius=self.rotor_bind_bolt_diam/2, + height=h, + pnt=(0, 0, -h/2), + ) + side_pos = Cq.Location(ri * math.cos(th), self.angle_joint_extra_hole_offset, 0) + side_pos2 = Cq.Location.rot2d(360/self.n_side) * side_pos.flip_y() + result = ( + result + + flange + - cyl.moved(ri * math.cos(th), ri * math.sin(th), 0) + - cyl.moved(side_pos.toTuple()) + - cyl.moved(side_pos2.toTuple()) + ) + 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 def assembly_ring(self, base) -> Cq.Assembly: @@ -1184,7 +1205,7 @@ class Onbashira(Model): material=self.material_side, role=Role.STRUCTURE | Role.DECORATION, ) - #.add(self.assembly_barrel(), name="barrel") + .add(self.assembly_barrel(), name="barrel") ) for i in range(self.n_side): j = (i + 1) % self.n_side @@ -1205,6 +1226,13 @@ class Onbashira(Model): "Plane", ) + a = a.constrain( + f"barrel/stator2?holeB{i}", + f"ring1/side{i}?holeStatorR", + "Plane", + ) + + # Generate bolts for the chamber back name_bolt =f"chamber_back{i}boltFPI{ih}" a = a.addS( BOLT_COMMON.generate(),