From 5d7137c0377e1fad557c552e13ddab4bf64f0b15 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Tue, 3 Jun 2025 09:11:06 -0700 Subject: [PATCH] Shimenawa geometry --- nhf/touhou/yasaka_kanako/__init__.py | 22 ++- nhf/touhou/yasaka_kanako/shimenawa.py | 236 ++++++++++++++++++++++++++ 2 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 nhf/touhou/yasaka_kanako/shimenawa.py diff --git a/nhf/touhou/yasaka_kanako/__init__.py b/nhf/touhou/yasaka_kanako/__init__.py index ab34d4e..63364f7 100644 --- a/nhf/touhou/yasaka_kanako/__init__.py +++ b/nhf/touhou/yasaka_kanako/__init__.py @@ -1,25 +1,31 @@ +import nhf.touhou.yasaka_kanako.mirror as MM +import nhf.touhou.yasaka_kanako.onbashira as MO +import nhf.touhou.yasaka_kanako.shimenawa as MS +from nhf.build import Model, TargetKind, target, assembly, submodel +import nhf.utils + from dataclasses import dataclass, field import cadquery as Cq -from nhf.build import Model, TargetKind, target, assembly, submodel -import nhf.touhou.yasaka_kanako.onbashira as MO -import nhf.touhou.yasaka_kanako.mirror as MM -import nhf.utils @dataclass class Parameters(Model): - onbashira: MO.Onbashira = field(default_factory=lambda: MO.Onbashira()) mirror: MM.Mirror = field(default_factory=lambda: MM.Mirror()) + onbashira: MO.Onbashira = field(default_factory=lambda: MO.Onbashira()) + shimenawa: MS.Shimenawa = field(default_factory=lambda: MS.Shimenawa()) def __post_init__(self): super().__init__(name="yasaka-kanako") - @submodel(name="onbashira") - def submodel_onbashira(self) -> Model: - return self.onbashira @submodel(name="mirror") def submodel_mirror(self) -> Model: return self.mirror + @submodel(name="onbashira") + def submodel_onbashira(self) -> Model: + return self.onbashira + @submodel(name="shimenawa") + def submodel_shimenawa(self) -> Model: + return self.shimenawa if __name__ == '__main__': diff --git a/nhf/touhou/yasaka_kanako/shimenawa.py b/nhf/touhou/yasaka_kanako/shimenawa.py new file mode 100644 index 0000000..813316e --- /dev/null +++ b/nhf/touhou/yasaka_kanako/shimenawa.py @@ -0,0 +1,236 @@ +from nhf.build import Model, TargetKind, target, assembly, submodel +from nhf.materials import Role, Material +import nhf.utils +from nhf.parts.fasteners import FlatHeadBolt, HexNut, Washer +from nhf.parts.electronics import ArduinoUnoR3, BatteryBox18650 + +from typing import Optional, Union +import math +from dataclasses import dataclass, field +import cadquery as Cq + +BOLT_COMMON = FlatHeadBolt( + # FIXME: weigh + mass=0.0, + diam_head=12.8, + height_head=2.8, + diam_thread=6.0, + height_thread=30.0, + pitch=1.0, +) + +@dataclass +class Shimenawa(Model): + """ + The ring + """ + + diam_inner: float = 43.0 + diam_outer: float = 43.0 + 9 * 2 + + diam_hole_outer: float = 8.0 + hole_ext: float = 2.0 + hole_z: float = 15.0 + + pipe_fitting_angle_span: float = 6.0 + + pipe_joint_length: float = 120.0 + pipe_joint_outer_thickness: float = 8.0 + pipe_joint_inner_thickness: float = 4.0 + + pipe_joint_inner_angle_span: float = 120.0 + pipe_joint_taper: float = 5.0 + pipe_joint_taper_length: float = 10.0 + + ear_dr: float = 6.0 + ear_hole_diam: float = 10.0 + ear_radius: float = 12.0 + ear_thickness: float = 10.0 + + main_circumference: float = 3600.0 + + material_fitting: Material = Material.PLASTIC_PLA + + def __post_init__(self): + assert self.diam_inner < self.diam_outer + + @property + def main_radius(self) -> float: + return self.main_circumference / (2 * math.pi) + + @target(name="pipe-fitting-curved") + def pipe_fitting_curved(self) -> Cq.Workplane: + r_minor = self.diam_outer/2 + self.pipe_joint_outer_thickness + a1 = self.pipe_fitting_angle_span + outer = Cq.Solid.makeTorus( + radius1=self.main_radius, + radius2=r_minor, + ) + inner = Cq.Solid.makeTorus( + radius1=self.main_radius, + radius2=self.diam_outer/2, + ) + angle_intersector = Cq.Solid.makeCylinder( + radius=self.main_radius + r_minor, + height=r_minor*2, + angleDegrees=a1, + pnt=(0,0,-r_minor) + ).rotate((0,0,0),(0,0,1),-a1/2) + result = (outer - inner) * angle_intersector + + ear_outer = Cq.Solid.makeCylinder( + radius=self.ear_radius, + height=self.ear_thickness, + ) + ear_hole = Cq.Solid.makeCylinder( + radius=self.ear_hole_diam/2, + height=self.ear_thickness, + ) + ear = (ear_outer - ear_hole).moved(self.main_radius - r_minor - self.ear_dr, 0, 0) + result += ear - inner + return result + @target(name="pipe-joint-outer") + def pipe_joint_outer(self) -> Cq.Workplane: + """ + Used to joint two pipes together (outside) + """ + r1 = self.diam_outer / 2 + self.pipe_joint_outer_thickness + h = self.pipe_joint_length + result = ( + Cq.Workplane() + .cylinder( + radius=r1, + height=self.pipe_joint_length, + ) + ) + cut_interior = Cq.Solid.makeCylinder( + radius=self.diam_outer/2, + height=h, + pnt=(0, 0, -h/2) + ) + rh = r1 + self.hole_ext + add_hole = Cq.Solid.makeCylinder( + radius=self.diam_hole_outer/2, + height=rh*2, + pnt=(-rh, 0, 0), + dir=(1, 0, 0), + ) + cut_hole = Cq.Solid.makeCylinder( + radius=BOLT_COMMON.diam_thread/2, + height=rh*2, + pnt=(-rh, 0, 0), + dir=(1, 0, 0), + ) + z = self.hole_z + result = ( + result + + add_hole.moved(0, 0, -z) + + add_hole.moved(0, 0, z) + - cut_hole.moved(0, 0, -z) + - cut_hole.moved(0, 0, z) + - cut_interior + ) + return result + + @target(name="pipe-joint-inner") + def pipe_joint_inner(self) -> Cq.Workplane: + """ + Used to joint two pipes together (inside) + """ + r1 = self.diam_inner / 2 + r2 = r1 - self.pipe_joint_taper + r3 = r2 - self.pipe_joint_inner_thickness + h = self.pipe_joint_length + h0 = h - self.pipe_joint_taper_length*2 + core = Cq.Solid.makeCylinder( + radius=r2, + height=h0/2, + ) + centre_cut = Cq.Solid.makeCylinder( + radius=r3, + height=h0/2, + ) + taper = Cq.Solid.makeCone( + radius1=r2, + radius2=r1, + height=(h - h0) / 2, + pnt=(0, 0, h0/2), + ) + centre_cut_taper = Cq.Solid.makeCone( + radius1=r3, + radius2=r3 + self.pipe_joint_taper, + height=(h - h0) / 2, + pnt=(0, 0, h0/2), + ) + angle_intersector = Cq.Solid.makeCylinder( + radius=r1, + height=h, + angleDegrees=self.pipe_joint_inner_angle_span + ).rotate((0,0,0), (0,0,1), -self.pipe_joint_inner_angle_span/2) + result = (taper + core - centre_cut - centre_cut_taper) * angle_intersector + + result += result.mirror("XY") + + add_hole = Cq.Solid.makeCylinder( + radius=self.diam_hole_outer/2, + height=self.hole_ext, + pnt=(r3, 0, 0), + dir=(-1, 0, 0), + ) + cut_hole = Cq.Solid.makeCylinder( + radius=BOLT_COMMON.diam_thread/2, + height=r1, + pnt=(0, 0, 0), + dir=(r1, 0, 0), + ) + z = self.hole_z + result = ( + result + + add_hole.moved(0, 0, z) + + add_hole.moved(0, 0, -z) + - cut_hole.moved(0, 0, z) + - cut_hole.moved(0, 0, -z) + ) + return result + @assembly() + def assembly_pipe_joint(self) -> Cq.Assembly: + a = ( + Cq.Assembly() + .addS( + self.pipe_joint_outer(), + name="joint_outer", + material=self.material_fitting, + role=Role.STRUCTURE, + ) + .addS( + self.pipe_joint_inner(), + name="joint_inner1", + material=self.material_fitting, + role=Role.STRUCTURE, + ) + .addS( + self.pipe_joint_inner(), + name="joint_inner2", + material=self.material_fitting, + role=Role.STRUCTURE, + loc=Cq.Location.rot2d(180), + ) + ) + return a + + @assembly() + def assembly(self) -> Cq.Assembly: + a = ( + Cq.Assembly() + .addS( + self.pipe_fitting_curved(), + name="fitting1", + material=self.material_fitting, + role=Role.STRUCTURE, + ) + .add( + self.assembly_pipe_joint(), + name="pipe_joint", + ) + ) + return a