diff --git a/nhf/touhou/yasaka_kanako/mirror.py b/nhf/touhou/yasaka_kanako/mirror.py index 25eef43..c8133d9 100644 --- a/nhf/touhou/yasaka_kanako/mirror.py +++ b/nhf/touhou/yasaka_kanako/mirror.py @@ -1,6 +1,7 @@ from dataclasses import dataclass, field import cadquery as Cq from nhf.build import Model, TargetKind, target, assembly, submodel +from nhf.materials import Role, Material import nhf.touhou.yasaka_kanako.onbashira as MO import nhf.utils @@ -8,36 +9,178 @@ import nhf.utils class Mirror(Model): """ Kanako's mirror, made of three levels. + + The mirror suface is sandwiched between two layers of wood. As such, its + dimensions have to sit in between that of the aperature on the surface, and + the outer walls. The width/height here refers to the outer edge's width and height """ width: float = 50.0 - height: float = 70.0 + height: float = 60.0 - wall_thickness: float = 10.0 + inner_gap: float = 3.0 + outer_gap: float = 3.0 + + core_thickness: float = 25.4 / 8 + casing_thickness: float = 25.4 / 16 + + flange_r0: float = 5.0 + flange_r1: float = 15.0 + flange_y1: float = 12.0 + flange_y2: float = 25.0 + flange_hole_r: float = 8.0 + wing_x1: float = 12.0 + wing_x2: float = 20.0 + wing_r1: float = 6.0 + wing_r2: float = 12.0 + tail_r0: float = 5.0 + tail_r1: float = 10.0 + tail_y1: float = 12.0 + tail_y2: float = 25.0 + + # Necklace hole + hole_diam: float = 5.0 + + material_mirror: Material = Material.ACRYLIC_TRANSPARENT + material_casing: Material = Material.WOOD_BIRCH @target(name="core", kind=TargetKind.DXF) def profile_core(self) -> Cq.Sketch: - return Cq.Sketch().ellipse(self.width/2, self.height/2) + rx = self.width/2 - self.outer_gap + ry = self.height/2 - self.outer_gap + return Cq.Sketch().ellipse(rx, ry) + + def core(self) -> Cq.Workplane: + return ( + Cq.Workplane() + .placeSketch(self.profile_core()) + .extrude(self.core_thickness) + ) @target(name="casing-bot", kind=TargetKind.DXF) - def profile_casing(self) -> Cq.Sketch: + def profile_casing_bot(self) -> Cq.Sketch: """ Base of the casing with no holes carved out """ - dx = self.wall_thickness return ( Cq.Sketch() - .ellipse(self.width/2+dx, self.height/2+dx) + .ellipse(self.width/2, self.height/2) + ) + def casing_bot(self) -> Cq.Workplane: + return ( + Cq.Workplane() + .placeSketch(self.profile_casing_bot()) + .extrude(self.casing_thickness) + ) + def profile_wing(self, sign: float=1) -> Cq.Sketch: + xt = self.width / 2 - self.outer_gap + return ( + Cq.Sketch() + .polygon([ + (sign*xt, self.wing_r1), + (sign*(xt+self.wing_x1), self.wing_r1), + (sign*(xt+self.wing_x1), self.wing_r2), + (sign*(xt+self.wing_x2), self.wing_r2), + (sign*(xt+self.wing_x2), -self.wing_r2), + (sign*(xt+self.wing_x1), -self.wing_r2), + (sign*(xt+self.wing_x1), -self.wing_r1), + (sign*xt, -self.wing_r1), + ]) + ) + @target(name="casing-mid", kind=TargetKind.DXF) + def profile_casing_mid(self) -> Cq.Sketch: + yt = self.height / 2 - self.outer_gap + rx = self.width/2 - self.outer_gap + ry = self.height/2 - self.outer_gap + yh = (self.flange_y1 + self.flange_y2) / 2 + flange = ( + Cq.Sketch() + .polygon([ + (self.flange_r0, yt), + (self.flange_r0, yt + self.flange_y1), + (self.flange_r1, yt + self.flange_y1), + (self.flange_r1, yt + self.flange_y2), + (-self.flange_r1, yt + self.flange_y2), + (-self.flange_r1, yt + self.flange_y1), + (-self.flange_r0, yt + self.flange_y1), + (-self.flange_r0, yt), + ]) + .push([ + (self.flange_hole_r, yt+yh), + (-self.flange_hole_r, yt+yh), + ]) + .circle(self.hole_diam/2, mode="s") + ) + tail = ( + Cq.Sketch() + .polygon([ + (+self.tail_r0, -yt), + (+self.tail_r0, -yt - self.tail_y1), + (+self.tail_r1, -yt - self.tail_y1), + (+self.tail_r1, -yt - self.tail_y2), + (-self.tail_r1, -yt - self.tail_y2), + (-self.tail_r1, -yt - self.tail_y1), + (-self.tail_r0, -yt - self.tail_y1), + (-self.tail_r0, -yt), + ]) + ) + return ( + self.profile_casing_bot() + .ellipse(rx, ry, mode="s") + .boolean(flange, mode="a") + .boolean(tail, mode="a") + .boolean(self.profile_wing(-1), mode="a") + .boolean(self.profile_wing(1), mode="a") + ) + def casing_mid(self) -> Cq.Workplane: + return ( + Cq.Workplane() + .placeSketch(self.profile_casing_mid()) + .extrude(self.core_thickness) ) @target(name="casing-top", kind=TargetKind.DXF) def profile_casing_top(self) -> Cq.Sketch: - """ - Base of the casing with no holes carved out - """ + rx = self.width/2 - self.outer_gap - self.inner_gap + ry = self.height/2 - self.outer_gap - self.inner_gap return ( - self.profile_casing() - .ellipse(self.width/2, self.height/2, mode="s") + self.profile_casing_bot() + .ellipse(rx, ry, mode="s") + ) + def casing_top(self) -> Cq.Workplane: + return ( + Cq.Workplane() + .placeSketch(self.profile_casing_top()) + .extrude(self.casing_thickness) ) @assembly() def assembly(self) -> Cq.Assembly: - pass + return ( + Cq.Assembly() + .addS( + self.core(), + name="core", + material=self.material_mirror, + role=Role.DECORATION, + loc=Cq.Location(0, 0, self.casing_thickness) + ) + .addS( + self.casing_bot(), + name="casing_bot", + material=self.material_casing, + role=Role.CASING, + ) + .addS( + self.casing_mid(), + name="casing_mid", + material=self.material_casing, + role=Role.CASING, + loc=Cq.Location(0, 0, self.casing_thickness) + ) + .addS( + self.casing_top(), + name="casing_top", + material=self.material_casing, + role=Role.CASING, + loc=Cq.Location(0, 0, self.core_thickness + self.casing_thickness) + ) + ) diff --git a/nhf/touhou/yasaka_kanako/onbashira.py b/nhf/touhou/yasaka_kanako/onbashira.py index 4ce8bb0..03ad4f6 100644 --- a/nhf/touhou/yasaka_kanako/onbashira.py +++ b/nhf/touhou/yasaka_kanako/onbashira.py @@ -54,9 +54,9 @@ class Onbashira(Model): bearing_disk_thickness: float = 25.4 / 8 rotor_inner_radius: float = 40.0 - rotor_bind_bolt_diam: float = 8.0 rotor_bind_radius: float = 85.0 + rotor_spacer_outer_diam: float = 15.0 stator_bind_radius: float = 140.0 material_side: Material = Material.WOOD_BIRCH @@ -194,6 +194,74 @@ 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: + outer = Cq.Solid.makeCylinder( + radius=self.rotor_spacer_outer_diam/2, + height=self.bearing_disk_gap, + ) + inner = Cq.Solid.makeCylinder( + radius=self.rotor_bind_bolt_diam/2, + height=self.bearing_disk_gap + ) + return outer - inner + + def assembly_rotor(self) -> Cq.Assembly: + z_lower = -self.bearing_disk_gap/2 - self.bearing_disk_thickness + a = ( + Cq.Assembly() + .addS( + self.bearing_stator(), + name="stator1", + material=self.material_bearing, + role=Role.STATOR, + loc=Cq.Location(0, 0, self.bearing_disk_gap/2) + ) + .addS( + self.bearing_rotor(), + name="rotor1", + material=self.material_bearing, + role=Role.ROTOR, + loc=Cq.Location(0, 0, self.bearing_disk_gap/2) + ) + .addS( + self.bearing_stator(), + name="stator2", + material=self.material_bearing, + role=Role.STATOR, + loc=Cq.Location(0, 0, z_lower) + ) + .addS( + self.bearing_rotor(), + name="rotor2", + material=self.material_bearing, + role=Role.ROTOR, + loc=Cq.Location(0, 0, z_lower) + ) + .addS( + self.bearing_gasket(), + name="gasket", + material=self.material_bearing, + role=Role.ROTOR, + loc=Cq.Location(0, 0, -self.bearing_disk_thickness/2) + ) + ) + for i in range(self.n_bearing_balls): + ball = self.bearing_ball() + 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}", + material=self.material_bearing_ball, + role=Role.BEARING, + loc=loc, + ) + return a + + def profile_side_panel( self, length: float, @@ -394,61 +462,6 @@ class Onbashira(Model): result.tagAbsolute("holeStatorR", (ri * math.cos(th), ri * math.sin(th), -h/2), direction="-Z") return result - def bearing_ball(self) -> Cq.Solid: - return Cq.Solid.makeSphere(radius=self.bearing_ball_diam/2, angleDegrees1=-90) - - def assembly_rotor(self) -> Cq.Assembly: - z_lower = -self.bearing_disk_gap/2 - self.bearing_disk_thickness - a = ( - Cq.Assembly() - .addS( - self.bearing_stator(), - name="stator1", - material=self.material_bearing, - role=Role.STATOR, - loc=Cq.Location(0, 0, self.bearing_disk_gap/2) - ) - .addS( - self.bearing_rotor(), - name="rotor1", - material=self.material_bearing, - role=Role.ROTOR, - loc=Cq.Location(0, 0, self.bearing_disk_gap/2) - ) - .addS( - self.bearing_stator(), - name="stator2", - material=self.material_bearing, - role=Role.STATOR, - loc=Cq.Location(0, 0, z_lower) - ) - .addS( - self.bearing_rotor(), - name="rotor2", - material=self.material_bearing, - role=Role.ROTOR, - loc=Cq.Location(0, 0, z_lower) - ) - .addS( - self.bearing_gasket(), - name="gasket", - material=self.material_bearing, - role=Role.ROTOR, - loc=Cq.Location(0, 0, -self.bearing_disk_thickness/2) - ) - ) - for i in range(self.n_bearing_balls): - ball = self.bearing_ball() - 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}", - material=self.material_bearing_ball, - role=Role.BEARING, - loc=loc, - ) - return a - def assembly_section(self, **kwargs) -> Cq.Assembly: a = Cq.Assembly() side = self.side_panel(**kwargs)