""" This schematics file contains all designs related to tool handles """ from dataclasses import dataclass, field from typing import Union, Optional import cadquery as Cq import nhf.parts.metric_threads as metric_threads class TubeJoint: def diam_insertion_internal(self): """ Maximum permitted diameter of the internal cavity """ def diam_connector_external(self): """ Maximum permitted diameter of the external size of the insertion """ def external_thread(self, length: float) -> Cq.Shape: """ Generates the external connector """ def internal_thread(self, length: float) -> Cq.Shape: """ Generates the internal connector """ @dataclass class ThreadedJoint(TubeJoint): pitch: float = 3 # Major diameter of the internal threads, following ISO metric screw thread # standard. This determines the wall thickness of the insertion. diam_threading: float = 27 def diam_insertion_internal(self): r = metric_threads.metric_thread_major_radius( self.diam_threading, self.pitch, internal=True) return r * 2 def diam_connector_external(self): r = metric_threads.metric_thread_minor_radius( self.diam_threading, self.pitch) return r * 2 def external_thread(self, length: float): return metric_threads.external_metric_thread( self.diam_threading, self.pitch, length, top_lead_in=True) def internal_thread(self, length: float): return metric_threads.internal_metric_thread( self.diam_threading, self.pitch, length) @dataclass class BayonetJoint(TubeJoint): """ Bayonet type joint """ pass @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 radii for the handle usually come in standard sizes diam: float = 38 diam_inner: float = 33 joint: Optional[TubeJoint] = field(default_factory=lambda: ThreadedJoint()) # Internal cavity diameter. This determines the wall thickness of the connector diam_connector_internal: float = 18.0 # If set to true, do not generate the connections 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" if self.joint: assert self.diam_inner > self.joint.diam_insertion_internal(), "Threading radius is too big" assert self.joint.diam_insertion_internal() > self.joint.diam_connector_external() assert self.joint.diam_connector_external() > self.diam_connector_internal, "Internal diameter is too large" assert self.insertion_length > self.rim_length def segment(self, length: float): result = ( Cq.Workplane() .cylinder( radius=self.diam / 2, height=length) .faces(">Z") .hole(self.diam_inner) ) result.faces("Z").tag("mate2") return result 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.joint.diam_insertion_internal()) ) result.faces(">Z").tag("mate") if not self.simplify_geometry: thread = self.joint.internal_thread(self.insertion_length).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.joint.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.joint.external_thread(self.insertion_length).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.joint.diam_connector_external() / 2) .extrude(self.insertion_length) ) if not self.simplify_geometry: thread = self.joint.external_thread(self.insertion_length).val() result = ( result .union( thread # Avoids collision in some mating cases .rotate((0,0,0), (1,0,0), angleDegrees=180) .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.joint.diam_connector_external / 2, height=length, centered=(True, True, False), ) ) result.faces(">Z").tag("mate") result.faces("