from dataclasses import dataclass import math import cadquery as Cq from typing import Optional from nhf import Item, Role import nhf.utils @dataclass(frozen=True) class FlatHeadBolt(Item): diam_head: float height_head: float diam_thread: float height_thread: float @property def name(self) -> str: return f"Bolt M{int(self.diam_thread)} h{int(self.height_thread)}mm" @property def role(self) -> Role: return Role.CONNECTION def generate(self) -> Cq.Assembly: head = Cq.Solid.makeCylinder( radius=self.diam_head / 2, height=self.height_head, ) rod = ( Cq.Workplane('XY') .cylinder( radius=self.diam_thread/ 2, height=self.height_thread, centered=(True, True, False)) ) rod.faces("Z").tag("root") rod = rod.union(head.located(Cq.Location((0, 0, self.height_thread)))) return rod @dataclass(frozen=True) class ThreaddedKnob(Item): """ A threaded rod with knob on one side """ diam_thread: float height_thread: float diam_knob: float diam_neck: float height_neck: float height_knob: float @property def name(self) -> str: return f"Knob M{int(self.diam_thread)} h{int(self.height_thread)}mm" def generate(self) -> Cq.Assembly: knob = Cq.Solid.makeCylinder( radius=self.diam_knob / 2, height=self.height_knob, ) neck = Cq.Solid.makeCylinder( radius=self.diam_neck / 2, height=self.height_neck, ) thread = ( Cq.Workplane('XY') .cylinder( radius=self.diam_thread / 2, height=self.height_thread, centered=(True, True, False)) ) thread.faces("Z").tag("root") return ( Cq.Assembly() .addS(thread, name="thread", role=Role.CONNECTION) .addS(neck, name="neck", role=Role.HANDLE, loc=Cq.Location((0, 0, self.height_thread))) .addS(knob, name="knob", role=Role.HANDLE, loc=Cq.Location((0, 0, self.height_thread + self.height_neck))) ) @dataclass(frozen=True) class HexNut(Item): diam_thread: float pitch: float thickness: float width: float def __post_init__(self): assert self.width > self.diam_thread @property def name(self): return f"HexNut M{int(self.diam_thread)}-{self.pitch}" @property def role(self) -> Role: return Role.CONNECTION @property def radius(self) -> float: return self.width / math.sqrt(3) def generate(self) -> Cq.Workplane: result = ( Cq.Workplane("XY") .sketch() .regularPolygon(r=self.radius, n=6) .circle(r=self.diam_thread/2, mode='s') .finalize() .extrude(self.thickness) ) result.faces("Z").tag("top") result.copyWorkplane(Cq.Workplane('XY')).tagPlane("dirX", direction="+X") return result def cutting_face(self) -> Cq.Face: return ( Cq.Sketch() .regularPolygon(r=self.radius, n=6) ._faces ) @dataclass(frozen=True) class Washer(Item): diam_thread: float diam_outer: float thickness: float material_name: Optional[float] = None def __post_init__(self): assert self.diam_outer > self.diam_thread @property def name(self): suffix = (" " + self.material_name) if self.material_name else "" return f"Washer M{int(self.diam_thread)}{suffix}" @property def role(self) -> Role: return Role.CONNECTION def generate(self) -> Cq.Workplane: result = ( Cq.Workplane('XY') .cylinder( radius=self.diam_outer/2, height=self.thickness, ) .faces(">Z") .hole(self.diam_thread) ) result.faces("Z").tag("top") return result