2024-06-25 06:11:48 -07:00
|
|
|
"""
|
|
|
|
This schematics file contains all designs related to tool handles
|
|
|
|
"""
|
2024-07-09 21:31:00 -07:00
|
|
|
from dataclasses import dataclass, field
|
|
|
|
from typing import Union, Optional
|
2024-06-25 06:11:48 -07:00
|
|
|
import cadquery as Cq
|
2024-07-04 00:42:14 -07:00
|
|
|
import nhf.parts.metric_threads as metric_threads
|
2024-06-25 06:11:48 -07:00
|
|
|
|
2024-07-09 21:31:00 -07:00
|
|
|
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
|
|
|
|
|
2024-07-03 18:45:16 -07:00
|
|
|
@dataclass
|
2024-06-25 06:11:48 -07:00
|
|
|
class Handle:
|
|
|
|
"""
|
|
|
|
Characteristic of a tool handle
|
2024-06-26 08:28:25 -07:00
|
|
|
|
|
|
|
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).
|
2024-06-25 06:11:48 -07:00
|
|
|
"""
|
|
|
|
|
2024-07-09 21:31:00 -07:00
|
|
|
# Outer and inner radii for the handle usually come in standard sizes
|
2024-06-26 08:28:25 -07:00
|
|
|
diam: float = 38
|
|
|
|
diam_inner: float = 33
|
|
|
|
|
2024-07-09 21:31:00 -07:00
|
|
|
joint: Optional[TubeJoint] = field(default_factory=lambda: ThreadedJoint())
|
2024-06-25 06:11:48 -07:00
|
|
|
|
2024-06-26 08:28:25 -07:00
|
|
|
# Internal cavity diameter. This determines the wall thickness of the connector
|
|
|
|
diam_connector_internal: float = 18.0
|
2024-06-25 06:11:48 -07:00
|
|
|
|
2024-07-09 21:31:00 -07:00
|
|
|
# If set to true, do not generate the connections
|
2024-06-26 08:28:25 -07:00
|
|
|
simplify_geometry: bool = True
|
2024-06-25 06:11:48 -07:00
|
|
|
|
|
|
|
# Length for the rim on the female connector
|
|
|
|
rim_length: float = 5
|
|
|
|
|
2024-07-02 19:59:09 -07:00
|
|
|
insertion_length: float = 30
|
2024-06-25 06:11:48 -07:00
|
|
|
|
2024-06-26 08:28:25 -07:00
|
|
|
# Amount by which the connector goes into the segment
|
2024-06-25 06:11:48 -07:00
|
|
|
connector_length: float = 60
|
|
|
|
|
|
|
|
def __post_init__(self):
|
2024-06-26 08:28:25 -07:00
|
|
|
assert self.diam > self.diam_inner, "Material thickness cannot be <= 0"
|
2024-07-09 21:31:00 -07:00
|
|
|
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"
|
2024-06-25 06:11:48 -07:00
|
|
|
assert self.insertion_length > self.rim_length
|
|
|
|
|
|
|
|
def segment(self, length: float):
|
|
|
|
result = (
|
|
|
|
Cq.Workplane()
|
2024-06-26 08:28:25 -07:00
|
|
|
.cylinder(
|
|
|
|
radius=self.diam / 2,
|
|
|
|
height=length)
|
2024-07-07 21:45:10 -07:00
|
|
|
.faces(">Z")
|
|
|
|
.hole(self.diam_inner)
|
2024-06-25 06:11:48 -07:00
|
|
|
)
|
|
|
|
result.faces("<Z").tag("mate1")
|
|
|
|
result.faces(">Z").tag("mate2")
|
|
|
|
return result
|
|
|
|
|
2024-07-01 17:59:42 -07:00
|
|
|
def insertion(self, holes=[]):
|
2024-06-25 06:11:48 -07:00
|
|
|
"""
|
|
|
|
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
|
2024-07-01 17:59:42 -07:00
|
|
|
|
|
|
|
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.
|
2024-06-25 06:11:48 -07:00
|
|
|
"""
|
|
|
|
result = (
|
|
|
|
Cq.Workplane('XY')
|
|
|
|
.cylinder(
|
2024-06-26 08:28:25 -07:00
|
|
|
radius=self.diam_inner / 2,
|
2024-06-25 06:11:48 -07:00
|
|
|
height=self.insertion_length - self.rim_length,
|
|
|
|
centered=[True, True, False])
|
|
|
|
)
|
2024-06-26 08:28:25 -07:00
|
|
|
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")
|
2024-07-09 21:31:00 -07:00
|
|
|
.hole(self.joint.diam_insertion_internal())
|
2024-06-26 08:28:25 -07:00
|
|
|
)
|
2024-06-25 06:11:48 -07:00
|
|
|
result.faces(">Z").tag("mate")
|
2024-06-26 08:28:25 -07:00
|
|
|
if not self.simplify_geometry:
|
2024-07-09 21:32:29 -07:00
|
|
|
thread = self.joint.internal_thread(self.insertion_length).val()
|
2024-06-26 08:28:25 -07:00
|
|
|
result = result.union(thread)
|
2024-07-01 17:59:42 -07:00
|
|
|
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)
|
2024-06-25 06:11:48 -07:00
|
|
|
return result
|
|
|
|
|
2024-07-01 17:59:42 -07:00
|
|
|
def connector(self, solid: bool = True):
|
2024-06-25 06:11:48 -07:00
|
|
|
"""
|
|
|
|
Tags:
|
|
|
|
* mate{1,2}: Mates to the connector
|
2024-07-01 17:59:42 -07:00
|
|
|
|
|
|
|
WARNING: A tolerance lower than the defualt (maybe 2e-4) is required for
|
|
|
|
STL export.
|
2024-06-25 06:11:48 -07:00
|
|
|
"""
|
|
|
|
result = (
|
|
|
|
Cq.Workplane('XY')
|
|
|
|
.cylinder(
|
2024-06-26 08:28:25 -07:00
|
|
|
radius=self.diam / 2,
|
2024-06-25 06:11:48 -07:00
|
|
|
height=self.connector_length,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
for (tag, selector) in [("mate1", "<Z"), ("mate2", ">Z")]:
|
|
|
|
result.faces(selector).tag(tag)
|
|
|
|
result = (
|
|
|
|
result
|
|
|
|
.faces(selector)
|
|
|
|
.workplane()
|
2024-07-09 21:31:00 -07:00
|
|
|
.circle(self.joint.diam_connector_external() / 2)
|
2024-06-25 06:11:48 -07:00
|
|
|
.extrude(self.insertion_length)
|
|
|
|
)
|
|
|
|
if not solid:
|
2024-06-26 08:28:25 -07:00
|
|
|
result = result.faces(">Z").hole(self.diam_connector_internal)
|
|
|
|
if not self.simplify_geometry:
|
2024-07-09 21:31:00 -07:00
|
|
|
thread = self.joint.external_thread(self.insertion_length).val()
|
2024-06-26 08:28:25 -07:00
|
|
|
result = (
|
|
|
|
result
|
|
|
|
.union(
|
|
|
|
thread
|
2024-06-26 16:27:36 -07:00
|
|
|
.located(Cq.Location((0, 0, self.connector_length / 2))))
|
2024-06-26 08:28:25 -07:00
|
|
|
.union(
|
|
|
|
thread
|
2024-07-01 17:59:42 -07:00
|
|
|
.rotate((0,0,0), (1,0,0), angleDegrees=180)
|
2024-06-26 16:27:36 -07:00
|
|
|
.located(Cq.Location((0, 0, -self.connector_length / 2))))
|
2024-06-26 08:28:25 -07:00
|
|
|
)
|
2024-06-25 06:11:48 -07:00
|
|
|
return result
|
|
|
|
|
2024-07-02 19:59:09 -07:00
|
|
|
def one_side_connector(self, height=None):
|
|
|
|
if height is None:
|
|
|
|
height = self.rim_length
|
2024-06-25 06:11:48 -07:00
|
|
|
result = (
|
|
|
|
Cq.Workplane('XY')
|
|
|
|
.cylinder(
|
2024-06-26 08:28:25 -07:00
|
|
|
radius=self.diam / 2,
|
2024-07-02 19:59:09 -07:00
|
|
|
height=height,
|
|
|
|
centered=(True, True, False)
|
2024-06-25 06:11:48 -07:00
|
|
|
)
|
|
|
|
)
|
2024-06-26 09:01:01 -07:00
|
|
|
result.faces(">Z").tag("mate")
|
|
|
|
result.faces("<Z").tag("base")
|
2024-06-25 06:11:48 -07:00
|
|
|
result = (
|
|
|
|
result
|
2024-06-26 09:01:01 -07:00
|
|
|
.faces(">Z")
|
2024-06-25 06:11:48 -07:00
|
|
|
.workplane()
|
2024-07-09 21:31:00 -07:00
|
|
|
.circle(self.joint.diam_connector_external() / 2)
|
2024-06-25 06:11:48 -07:00
|
|
|
.extrude(self.insertion_length)
|
|
|
|
)
|
2024-06-26 09:01:01 -07:00
|
|
|
if not self.simplify_geometry:
|
2024-07-09 21:31:00 -07:00
|
|
|
thread = self.joint.external_thread(self.insertion_length).val()
|
2024-06-26 09:01:01 -07:00
|
|
|
result = (
|
|
|
|
result
|
|
|
|
.union(
|
|
|
|
thread
|
2024-07-07 21:45:10 -07:00
|
|
|
# Avoids collision in some mating cases
|
|
|
|
.rotate((0,0,0), (1,0,0), angleDegrees=180)
|
2024-07-02 19:59:09 -07:00
|
|
|
.located(Cq.Location((0, 0, height))))
|
2024-06-26 09:01:01 -07:00
|
|
|
)
|
2024-06-25 06:11:48 -07:00
|
|
|
return result
|
2024-07-01 17:59:42 -07:00
|
|
|
|
|
|
|
def threaded_core(self, length):
|
|
|
|
"""
|
|
|
|
Generates a threaded core for unioning with other components
|
|
|
|
"""
|
|
|
|
result = (
|
|
|
|
Cq.Workplane('XY')
|
|
|
|
.cylinder(
|
2024-07-09 21:31:00 -07:00
|
|
|
radius=self.joint.diam_connector_external / 2,
|
2024-07-01 17:59:42 -07:00
|
|
|
height=length,
|
|
|
|
centered=(True, True, False),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
result.faces(">Z").tag("mate")
|
|
|
|
result.faces("<Z").tag("base")
|
|
|
|
if not self.simplify_geometry:
|
2024-07-09 21:31:00 -07:00
|
|
|
thread = self.joint.external_thread(length=length).val()
|
2024-07-01 17:59:42 -07:00
|
|
|
result = (
|
|
|
|
result
|
|
|
|
.union(thread)
|
|
|
|
)
|
|
|
|
return result
|
2024-06-25 06:11:48 -07:00
|
|
|
|
|
|
|
def connector_insertion_assembly(self):
|
|
|
|
connector_color = Cq.Color(0.8,0.8,0.5,0.3)
|
|
|
|
insertion_color = Cq.Color(0.7,0.7,0.7,0.3)
|
|
|
|
result = (
|
|
|
|
Cq.Assembly()
|
|
|
|
.add(self.connector(), name="c", color=connector_color)
|
|
|
|
.add(self.insertion(), name="i1", color=insertion_color)
|
|
|
|
.add(self.insertion(), name="i2", color=insertion_color)
|
|
|
|
.constrain("c?mate1", "i1?mate", "Plane")
|
|
|
|
.constrain("c?mate2", "i2?mate", "Plane")
|
|
|
|
.solve()
|
|
|
|
)
|
|
|
|
return result
|
|
|
|
def connector_one_side_insertion_assembly(self):
|
|
|
|
connector_color = Cq.Color(0.8,0.8,0.5,0.3)
|
|
|
|
insertion_color = Cq.Color(0.7,0.7,0.7,0.3)
|
|
|
|
result = (
|
|
|
|
Cq.Assembly()
|
|
|
|
.add(self.insertion(), name="i", color=connector_color)
|
|
|
|
.add(self.one_side_connector(), name="c", color=insertion_color)
|
|
|
|
.constrain("i?mate", "c?mate", "Plane")
|
|
|
|
.solve()
|
|
|
|
)
|
|
|
|
return result
|