""" This schematics file contains all designs related to tool handles """ from dataclasses import dataclass import cadquery as Cq import nhf.metric_threads as NMt @dataclass class Handle: """ Characteristic of a tool handle This assumes the handle segment material does not have threads. Each segment attaches to two insertions, which have threads on the inside. A connector has threads on the outside and joints two insertions. Note that all the radial sizes are diameters (in mm). """ # Outer and inner radius for the handle usually come in standard sizes diam: float = 38 diam_inner: float = 33 # Major diameter of the internal threads, following ISO metric screw thread # standard. This determines the wall thickness of the insertion. diam_threading: float = 27.0 thread_pitch: float = 3.0 # Internal cavity diameter. This determines the wall thickness of the connector diam_connector_internal: float = 18.0 # If set to true, do not generate threads simplify_geometry: bool = True # Length for the rim on the female connector rim_length: float = 5 insertion_length: float = 30 # Amount by which the connector goes into the segment connector_length: float = 60 def __post_init__(self): assert self.diam > self.diam_inner, "Material thickness cannot be <= 0" assert self.diam_inner > self.diam_insertion_internal, "Threading radius is too big" assert self.diam_insertion_internal > self.diam_connector_external assert self.diam_connector_external > self.diam_connector_internal, "Internal diameter is too large" assert self.insertion_length > self.rim_length @property def diam_insertion_internal(self): r = NMt.metric_thread_major_radius( self.diam_threading, self.thread_pitch, internal=True) return r * 2 @property def diam_connector_external(self): r = NMt.metric_thread_minor_radius( self.diam_threading, self.thread_pitch) return r * 2 def segment(self, length: float): result = ( Cq.Workplane() .cylinder( radius=self.diam / 2, height=length) ) result.faces("Z").tag("mate2") return result def _external_thread(self, length=None): if length is None: length = self.insertion_length return NMt.external_metric_thread( self.diam_threading, self.thread_pitch, length, top_lead_in=True) def _internal_thread(self): return NMt.internal_metric_thread( self.diam_threading, self.thread_pitch, self.insertion_length) def insertion(self, holes=[]): """ This type of joint is used to connect two handlebar pieces. Each handlebar piece is a tube which cannot be machined, so the joint connects to the handle by glue. Tags: * lip: Co-planar Mates to the rod * mate: Mates to the connector WARNING: A tolerance lower than the defualt (maybe 5e-4) is required for STL export. Set `holes` to the heights for drilling holes into the model for resin to flow out. """ result = ( Cq.Workplane('XY') .cylinder( radius=self.diam_inner / 2, height=self.insertion_length - self.rim_length, centered=[True, True, False]) ) result.faces(">Z").tag("rim") if self.rim_length > 0: result = ( result.faces(">Z") .workplane() .circle(self.diam / 2) .extrude(self.rim_length) .faces(">Z") .hole(self.diam_insertion_internal) ) result.faces(">Z").tag("mate") if not self.simplify_geometry: thread = self._internal_thread().val() result = result.union(thread) for h in holes: cyl = Cq.Solid.makeCylinder( radius=2, height=self.diam * 2, pnt=(-self.diam, 0, h), dir=(1, 0, 0)) result = result.cut(cyl) return result def connector(self, solid: bool = True): """ Tags: * mate{1,2}: Mates to the connector WARNING: A tolerance lower than the defualt (maybe 2e-4) is required for STL export. """ result = ( Cq.Workplane('XY') .cylinder( radius=self.diam / 2, height=self.connector_length, ) ) for (tag, selector) in [("mate1", "Z")]: result.faces(selector).tag(tag) result = ( result .faces(selector) .workplane() .circle(self.diam_connector_external / 2) .extrude(self.insertion_length) ) if not solid: result = result.faces(">Z").hole(self.diam_connector_internal) if not self.simplify_geometry: thread = self._external_thread().val() result = ( result .union( thread .located(Cq.Location((0, 0, self.connector_length / 2)))) .union( thread .rotate((0,0,0), (1,0,0), angleDegrees=180) .located(Cq.Location((0, 0, -self.connector_length / 2)))) ) return result def one_side_connector(self, height=None): if height is None: height = self.rim_length result = ( Cq.Workplane('XY') .cylinder( radius=self.diam / 2, height=height, centered=(True, True, False) ) ) result.faces(">Z").tag("mate") result.faces("Z") .workplane() .circle(self.diam_connector_external / 2) .extrude(self.insertion_length) ) if not self.simplify_geometry: thread = self._external_thread().val() result = ( result .union( thread .located(Cq.Location((0, 0, height)))) ) return result def threaded_core(self, length): """ Generates a threaded core for unioning with other components """ result = ( Cq.Workplane('XY') .cylinder( radius=self.diam_connector_external / 2, height=length, centered=(True, True, False), ) ) result.faces(">Z").tag("mate") result.faces("