螺纹基础知识(五要素)
螺纹包括五个要素:牙型、公称直径、线数、螺距(或导程)和旋向。-
牙型
-
直径
-
线数
-
螺距和导程
螺距(p)是相邻两牙在中径线上对应两点间的轴向距离。 导程(ph)是同一条螺旋线上的相邻两牙在中径线上对应两点间的轴向距离。 单线螺纹时,导程=螺距;多线螺纹时,导程=螺距×线数
-
旋向
顺时针旋转时旋入的螺纹称为右旋螺纹; 逆时针旋转时旋入的螺纹称为左旋螺纹。
class Thread(Solid):
继承solid大类- 参数:
- apex_radius: 螺纹尖端的半径 外螺纹最大半径 内螺纹最小半径
- apex_width: 螺纹宽基底的半径
- root_radius: 螺纹宽基底的半径
- root_width: 螺纹基底宽度
- pitch: 360°螺纹旋转的长度
- length: 螺纹的端到端长度
- apex_offset: 不对称螺纹顶点偏移量,默认为0.0
- hand: 旋转方向,默认为"right"
- taper_angle: 锥形螺纹的角度,默认为None
-
end_finishes: 每个端部的轮廓,可以是:
- "raw" - 未完成的,通常会导致螺纹延伸到z=0以下或z=length以上
- "fade" - 螺纹高度在90°的弧度上降为零(或1/4螺距)
- "square" - 通过z=0或z=length平面剪切
-
"chamfer" - 圆锥形端部,有助于螺栓对准螺母
- 默认为("raw","raw")。 simple: 停止在螺纹计算阶段,不创建螺纹,默认为False。
def fade_helix(self, t: float, apex: bool, vertical_displacement: float)
用于创建螺旋形螺纹的衰减尖端,一般为螺距的1/4def fade_helix(
self, t: float, apex: bool, vertical_displacement: float
) -> Tuple[float, float, float]:
"""A helical function used to create the faded tips of threads that spirals
self.tooth_height in self.pitch/4"""
if self.external:
radius = (
self.apex_radius - sin(t * pi / 2) * self.tooth_height
if apex
else self.root_radius
)
else:
radius = (
self.apex_radius + sin(t * pi / 2) * self.tooth_height
if apex
else self.root_radius
)
z_pos = t * self.pitch / 4 + t * vertical_displacement
x_pos = radius * cos(t * pi / 2)
y_pos = radius * sin(t * pi / 2)
return (x_pos, y_pos, z_pos)
def cq_object(self)
@property
def cq_object(self):
"""A cadquery Solid thread as defined by class attributes"""
warn("cq_object will be deprecated.", DeprecationWarning, stacklevel=2)
return Solid(self.wrapped)
全部代码
class Thread(Solid):
"""
螺旋螺纹 Helical thread
最通用的螺纹类,用于构建所有其他类型的螺纹。
创建具有给定根部和顶部半径的右旋或左旋螺旋螺纹。
参数:
apex_radius: 螺纹尖端的半径。
apex_width: 螺纹宽基底的半径。
root_radius: 螺纹宽基底的半径。
root_width: 螺纹基底宽度。
pitch: 360°螺纹旋转的长度。
length: 螺纹的端到端长度。
apex_offset: 不对称螺纹顶点偏移量,默认为0.0。
hand: 旋转方向,默认为"right"。
taper_angle: 锥形螺纹的角度,默认为None。
end_finishes: 每个端部的轮廓,可以是:
"raw" - 未完成的,通常会导致螺纹延伸到z=0以下或z=length以上
"fade" - 螺纹高度在90°的弧度上降为零(或1/4螺距)
"square" - 通过z=0或z=length平面剪切
"chamfer" - 圆锥形端部,有助于螺栓对准螺母
默认为("raw","raw")。
simple: 停止在螺纹计算阶段,不创建螺纹,默认为False。
异常:
ValueError: 如果end_finishes不在["raw", "square", "fade", "chamfer"]中:
"""
def fade_helix(
self, t: float, apex: bool, vertical_displacement: float
) -> Tuple[float, float, float]:
"""A helical function used to create the faded tips of threads that spirals
self.tooth_height in self.pitch/4"""
if self.external:
radius = (
self.apex_radius - sin(t * pi / 2) * self.tooth_height
if apex
else self.root_radius
)
else:
radius = (
self.apex_radius + sin(t * pi / 2) * self.tooth_height
if apex
else self.root_radius
)
z_pos = t * self.pitch / 4 + t * vertical_displacement
x_pos = radius * cos(t * pi / 2)
y_pos = radius * sin(t * pi / 2)
return (x_pos, y_pos, z_pos)
@property
def cq_object(self):
"""A cadquery Solid thread as defined by class attributes"""
warn("cq_object will be deprecated.", DeprecationWarning, stacklevel=2)
return Solid(self.wrapped)
def __init__(
self,
apex_radius: float,
apex_width: float,
root_radius: float,
root_width: float,
pitch: float,
length: float,
apex_offset: float = 0.0,
hand: Literal["right", "left"] = "right",
taper_angle: Optional[float] = None,
end_finishes: Tuple[
Literal["raw", "square", "fade", "chamfer"],
Literal["raw", "square", "fade", "chamfer"],
] = ("raw", "raw"),
simple: bool = False,
):
"""Store the parameters and create the thread object"""
for finish in end_finishes:
if finish not in ["raw", "square", "fade", "chamfer"]:
raise ValueError(
'end_finishes invalid, must be tuple() of "raw, square, taper, or chamfer"'
)
self.external = apex_radius > root_radius
self.apex_radius = apex_radius
self.apex_width = apex_width
# Unfortunately, when creating "fade" ends inaccuracies in parametric curve calculations
# can result in a gap which causes the OCCT core to fail when combining with other
# object (like the core of the thread). To avoid this, subtract (or add) a fudge factor
# to the root radius to make it small enough to intersect the given radii.
self.root_radius = root_radius - (0.001 if self.external else -0.001)
self.root_width = root_width
self.pitch = pitch
self.length = length
self.apex_offset = apex_offset
self.right_hand = hand == "right"
self.end_finishes = end_finishes
self.tooth_height = abs(self.apex_radius - self.root_radius)
self.taper = 360 if taper_angle is None else taper_angle
self.simple = simple
if not simple:
# Create base cylindrical thread
number_faded_ends = self.end_finishes.count("fade")
cylindrical_thread_length = self.length + self.pitch * (
1 - 1 * number_faded_ends
)
if self.end_finishes[0] == "fade":
cylindrical_thread_displacement = self.pitch / 2
else:
cylindrical_thread_displacement = -self.pitch / 2
# Either create a cylindrical thread for further processing
# or create a cylindrical thread segment with faded ends
if number_faded_ends == 0:
cq_object = self.make_thread_solid(cylindrical_thread_length).translate(
(0, 0, cylindrical_thread_displacement)
)
else:
cq_object = self.make_thread_with_faded_ends(
number_faded_ends,
cylindrical_thread_length,
cylindrical_thread_displacement,
)
# Square off ends if requested
cq_object = self.square_off_ends(cq_object)
# Chamfer ends if requested
cq_object = self.chamfer_ends(cq_object)
if isinstance(cq_object, Compound) and len(cq_object.Solids()) == 1:
super().__init__(cq_object.Solids()[0].wrapped)
else:
super().__init__(cq_object.wrapped)
else:
# Initialize with a valid shape then nullify
super().__init__(Solid.makeBox(1, 1, 1).wrapped)
self.wrapped = TopoDS_Shape()
def make_thread_with_faded_ends(
self,
number_faded_ends,
cylindrical_thread_length,
cylindrical_thread_displacement,
):
"""Build the thread object from cylindrical thread faces and
faded ends faces"""
(thread_faces, end_faces) = self.make_thread_faces(cylindrical_thread_length)
# Need to operate on each face below
thread_faces = [
f.translate((0, 0, cylindrical_thread_displacement)) for f in thread_faces
]
end_faces = [
f.translate((0, 0, cylindrical_thread_displacement)) for f in end_faces
]
cylindrical_thread_angle = (
(360 if self.right_hand else -360) * cylindrical_thread_length / self.pitch
)
(fade_faces, _fade_ends) = self.make_thread_faces(
self.pitch / 4, fade_helix=True
)
if not self.right_hand:
fade_faces = [f.mirror("XZ") for f in fade_faces]
if self.end_finishes[0] == "fade":
# If the thread is asymmetric the bottom fade end needs to be recreated as
# no amount of flipping or rotating can generate the shape
if self.apex_offset != 0:
(fade_faces_bottom, _fade_ends) = self.make_thread_faces(
self.pitch / 4, fade_helix=True, asymmetric_flip=True
)
if not self.right_hand:
fade_faces_bottom = [f.mirror("XZ") for f in fade_faces_bottom]
else:
fade_faces_bottom = fade_faces
fade_faces_bottom = [
f.mirror("XZ").mirror("XY").translate(cq.Vector(0, 0, self.pitch / 2))
for f in fade_faces_bottom
]
if self.end_finishes[1] == "fade":
fade_faces_top = [
f.translate(
cq.Vector(
0,
0,
cylindrical_thread_length + cylindrical_thread_displacement,
)
).rotate((0, 0, 0), (0, 0, 1), cylindrical_thread_angle)
for f in fade_faces
]
if number_faded_ends == 2:
thread_shell = cq.Shell.makeShell(
thread_faces + fade_faces_bottom + fade_faces_top
)
elif self.end_finishes[0] == "fade":
thread_shell = cq.Shell.makeShell(
thread_faces + fade_faces_bottom + [end_faces[1]]
)
else:
thread_shell = cq.Shell.makeShell(
thread_faces + fade_faces_top + [end_faces[0]]
)
return cq.Solid.makeSolid(thread_shell)
def square_off_ends(self, cq_object: Solid):
"""Square off the ends of the thread"""
squared = cq_object
if self.end_finishes.count("square") != 0:
# Note: box_size must be > max(apex,root) radius or the core doesn't cut correctly
half_box_size = 2 * max(self.apex_radius, self.root_radius)
box_size = 2 * half_box_size
cutter = cq.Solid.makeBox(
length=box_size,
width=box_size,
height=self.length,
pnt=cq.Vector(-half_box_size, -half_box_size, -self.length),
)
for i in range(2):
if self.end_finishes[i] == "square":
squared = cq_object.cut(
cutter.translate(cq.Vector(0, 0, 2 * i * self.length))
)
return squared
def chamfer_ends(self, cq_object: Solid):
"""Chamfer the ends of the thread"""
chamfered = cq_object
if self.end_finishes.count("chamfer") != 0:
cutter = (
cq.Workplane("XY")
.circle(self.root_radius)
.circle(self.apex_radius)
.extrude(self.length)
)
face_selectors = ["<Z", ">Z"]
edge_radius_selector = 1 if self.apex_radius > self.root_radius else 0
for i in range(2):
if self.end_finishes[i] == "chamfer":
cutter = (
cutter.faces(face_selectors[i])
.edges(cq.selectors.RadiusNthSelector(edge_radius_selector))
.chamfer(self.tooth_height * 0.5, self.tooth_height * 0.75)
)
chamfered = cq_object.intersect(cutter.val())
return chamfered
def make_thread_faces(
self, length: float, fade_helix: bool = False, asymmetric_flip: bool = False
) -> Tuple[List[cq.Face]]:
"""Create the thread object from basic CadQuery objects
This method creates three types of thread objects:
1. cylindrical - i.e. following a simple helix
2. tapered - i.e. following a conical helix
3. faded - cylindrical but spiralling towards the root in 90°
After testing many alternatives (sweep, extrude with rotation, etc.) the
following algorithm was found to be the fastest and most reliable:
a. first create all the edges - helical, linear or parametric
b. create either 5 or 6 faces from the edges (faded needs 5)
c. create a shell from the faces
d. create a solid from the shell
"""
local_apex_offset = -self.apex_offset if asymmetric_flip else self.apex_offset
apex_helix_wires = [
cq.Workplane("XY")
.parametricCurve(
lambda t: self.fade_helix(t, apex=True, vertical_displacement=0)
)
.val()
.translate((0, 0, i * self.apex_width + local_apex_offset))
if fade_helix
else cq.Wire.makeHelix(
pitch=self.pitch,
height=length,
radius=self.apex_radius,
angle=self.taper,
lefthand=not self.right_hand,
).translate((0, 0, i * self.apex_width + local_apex_offset))
for i in [-0.5, 0.5]
]
assert apex_helix_wires[0].isValid()
root_helix_wires = [
cq.Workplane("XY")
.parametricCurve(
lambda t: self.fade_helix(
t,
apex=False,
vertical_displacement=-i * (self.root_width - self.apex_width),
)
)
.val()
.translate((0, 0, i * self.root_width))
if fade_helix
else cq.Wire.makeHelix(
pitch=self.pitch,
height=length,
radius=self.root_radius,
angle=self.taper,
lefthand=not self.right_hand,
).translate((0, 0, i * self.root_width))
for i in [-0.5, 0.5]
]
# When creating a cylindrical or tapered thread two end faces are required
# to enclose the thread object, while faded thread only has one end face
end_caps = [0] if fade_helix else [0, 1]
end_cap_wires = [
cq.Wire.makePolygon(
[
apex_helix_wires[0].positionAt(i),
apex_helix_wires[1].positionAt(i),
root_helix_wires[1].positionAt(i),
root_helix_wires[0].positionAt(i),
apex_helix_wires[0].positionAt(i),
]
)
for i in end_caps
]
thread_faces = [
cq.Face.makeRuledSurface(apex_helix_wires[0], apex_helix_wires[1]),
cq.Face.makeRuledSurface(apex_helix_wires[1], root_helix_wires[1]),
cq.Face.makeRuledSurface(root_helix_wires[1], root_helix_wires[0]),
cq.Face.makeRuledSurface(root_helix_wires[0], apex_helix_wires[0]),
]
end_faces = [cq.Face.makeFromWires(end_cap_wires[i]) for i in end_caps]
return (thread_faces, end_faces)
def make_thread_solid(
self,
length: float,
fade_helix: bool = False,
) -> cq.Solid:
"""Create a solid object by first creating the faces"""
(thread_faces, end_faces) = self.make_thread_faces(length, fade_helix)
thread_shell = cq.Shell.makeShell(thread_faces + end_faces)
thread_solid = cq.Solid.makeSolid(thread_shell)
return thread_solid
demo1
代码
from cadquery import *
from math import *
def helix(r0,r_eps,p,h,d=0,frac=1e-1):
def func(t):
if t>frac and t<1-frac:
z = h*t + d
r = r0+r_eps
elif t<=frac:
z = h*t + d*sin(pi/2 *t/frac)
r = r0 + r_eps*sin(pi/2 *t/frac)
else:
z = h*t - d*sin(2*pi - pi/2*(1-t)/frac)
r = r0 - r_eps*sin(2*pi - pi/2*(1-t)/frac)
x = r*sin(-2*pi/(p/h)*t)
y = r*cos(2*pi/(p/h)*t)
return x,y,z
return func
def thread(radius, pitch, height, d, radius_eps, aspect= 10):
e1_bottom = (cq.Workplane("XY")
.parametricCurve(helix(radius,0,pitch,height,-d)).val()
)
e1_top = (cq.Workplane("XY")
.parametricCurve(helix(radius,0,pitch,height,d)).val()
)
e2_bottom = (cq.Workplane("XY")
.parametricCurve(helix(radius,radius_eps,pitch,height,-d/aspect)).val()
)
e2_top = (cq.Workplane("XY")
.parametricCurve(helix(radius,radius_eps,pitch,height,d/aspect)).val()
)
f1 = Face.makeRuledSurface(e1_bottom, e1_top)
f2 = Face.makeRuledSurface(e2_bottom, e2_top)
f3 = Face.makeRuledSurface(e1_bottom, e2_bottom)
f4 = Face.makeRuledSurface(e1_top, e2_top)
sh = Shell.makeShell([f1,f2,f3,f4])
rv = Solid.makeSolid(sh)
return rv
radius = 4
pitch = 2
height = 4
d = pitch/4
radius_eps = 0.5
eps=1e-3
core = cq.Workplane("XY",origin=(0,0,-d)).circle(radius-1-eps).circle(radius+eps).extrude(height+1.75*d)
th1 = thread(radius,pitch,height,d,radius_eps)
th2 =thread(radius-1,pitch,height,d,-radius_eps)
res = core.union(Compound.makeCompound([th1,th2]))
show_object(res)
代码解释
helix
函数
这个函数用于生成一个螺旋线(helix)的参数方程。螺旋线是通过圆半径、螺距、高度等参数定义的三维曲线。
r0
,r_eps
: 控制螺旋线半径的参数。p
: 螺距,即螺旋线在一个完整周期内沿轴向的移动距离。h
: 螺旋线的总高度。d
: 控制螺旋线在起始和结束位置的偏移量。frac
: 控制螺旋线在起始和结束位置过渡段的长度比例。
thread
函数
这个函数利用 helix
函数生成的螺旋线创建一个螺纹的3D模型。
radius
,pitch
,height
: 分别代表螺纹的基本半径、螺距和高度。d
: 控制螺纹在起始和结束位置的偏移量。radius_eps
: 控制螺纹半径的扩展量,用于创建螺纹的外形。aspect
: 控制螺纹在顶部和底部的过渡形状。
创建螺纹模型
代码的最后部分使用thread
函数创建了两个螺纹模型(th1
和 th2
),并将它们与一个中心的圆柱体(core
)合并,形成最终的3D模型。
radius
,pitch
,height
,d
,radius_eps
: 这些参数定义了螺纹的基本形状和尺寸。core
: 一个圆柱体,作为螺纹模型的中心部分。th1
和th2
: 通过thread
函数创建的两个螺纹模型,分别位于圆柱体的外侧和内侧。res
: 最终的3D模型,通过合并core
、th1
和th2
得到。
cadquery
库通过参数化的方法创建具有特定螺纹形状的3D模型。这种方法非常适合于需要精确控制几何形状的CAD设计任务。
demo2
https://sourceforge.net/p/nl10/code/HEAD/tree/cq-code/common/metric_threads.pymale = external_metric_thread(3.0, 0.5, 4.0, z_start= -0.85,top_lead_in=True)
female = internal_metric_thread(3.0, 0.5, 1.5, bottom_chamfer=True, base_tube_od= 4.5)
代码
# Copyright (c) 2020-2024, Nerius Anthony Landys. All rights reserved.
# neri-engineering 'at' protonmail.com
# https://svn.code.sf.net/p/nl10/code/cq-code/common/metric_threads.py
#
# Simple code example to create meshing M3x0.5 threads:
###############################################################################
#
# male = external_metric_thread(3.0, 0.5, 4.0, z_start= -0.85,
# top_lead_in=True)
#
# # Please note that the female thread is meant for a hole which has
# # radius equal to metric_thread_major_radius(3.0, 0.5, internal=True),
# # which is in fact very slightly larger than a 3.0 diameter hole.
#
# female = internal_metric_thread(3.0, 0.5, 1.5,
# bottom_chamfer=True, base_tube_od= 4.5)
#
###############################################################################
# Left hand threads can be created by employing one of the "mirror" operations.
# Thanks for taking the time to understand and use this code!
import math
import cadquery as cq
###############################################################################
# The functions which have names preceded by '__' are not meant to be called
# externally; the remaining functions are written with the intention that they
# will be called by external code. The first section of code consists of
# lightweight helper functions; the meat and potatoes of this library is last.
###############################################################################
# Return value is in degrees, and currently it's fixed at 30. Essentially this
# results in a typical 60 degree equilateral triangle cutting bit for threads.
def metric_thread_angle():
return 30
# Helper func. to make code more intuitive and succinct. Degrees --> radians.
def __deg2rad(degrees):
return degrees * math.pi / 180
# In the absence of flat thread valley and flattened thread tip, returns the
# amount by which the thread "triangle" protrudes outwards (radially) from base
# cylinder in the case of external thread, or the amount by which the thread
# "triangle" protrudes inwards from base tube in the case of internal thread.
def metric_thread_perfect_height(pitch):
return pitch / (2 * math.tan(__deg2rad(metric_thread_angle())))
# Up the radii of internal (female) thread in order to provide a little bit of
# wiggle room around male thread. Right now input parameter 'diameter' is
# ignored. This function is only used for internal/female threads. Currently
# there is no practical way to adjust the male/female thread clearance besides
# to manually edit this function. This design route was chosen for the sake of
# code simplicity.
def __metric_thread_internal_radius_increase(diameter, pitch):
return 0.1 * metric_thread_perfect_height(pitch)
# Returns the major radius of thread, which is always the greater of the two.
def metric_thread_major_radius(diameter, pitch, internal=False):
return (__metric_thread_internal_radius_increase(diameter, pitch) if
internal else 0.0) + (diameter / 2)
# What portion of the total pitch is taken up by the angled thread section (and
# not the squared off valley and tip). The remaining portion (1 minus ratio)
# will be divided equally between the flattened valley and flattened tip.
def __metric_thread_effective_ratio():
return 0.7
# Returns the minor radius of thread, which is always the lesser of the two.
def metric_thread_minor_radius(diameter, pitch, internal=False):
return (metric_thread_major_radius(diameter, pitch, internal)
- (__metric_thread_effective_ratio() *
metric_thread_perfect_height(pitch)))
# What the major radius would be if the cuts were perfectly triangular, without
# flat spots in the valleys and without flattened tips.
def metric_thread_perfect_major_radius(diameter, pitch, internal=False):
return (metric_thread_major_radius(diameter, pitch, internal)
+ ((1.0 - __metric_thread_effective_ratio()) *
metric_thread_perfect_height(pitch) / 2))
# What the minor radius would be if the cuts were perfectly triangular, without
# flat spots in the valleys and without flattened tips.
def metric_thread_perfect_minor_radius(diameter, pitch, internal=False):
return (metric_thread_perfect_major_radius(diameter, pitch, internal)
- metric_thread_perfect_height(pitch))
# Returns the lead-in and/or chamfer distance along the z axis of rotation.
# The lead-in/chamfer only depends on the pitch and is made with the same angle
# as the thread, that being 30 degrees offset from radial.
def metric_thread_lead_in(pitch, internal=False):
return (math.tan(__deg2rad(metric_thread_angle()))
* (metric_thread_major_radius(256.0, pitch, internal)
- metric_thread_minor_radius(256.0, pitch, internal)))
# Returns the width of the flat spot in thread valley of a standard thread.
# This is also equal to the width of the flat spot on thread tip, on a standard
# thread.
def metric_thread_relief(pitch):
return (1.0 - __metric_thread_effective_ratio()) * pitch / 2
###############################################################################
# A few words on modules external_metric_thread() and internal_metric_thread().
# The parameter 'z_start' is added as a convenience in order to make the male
# and female threads align perfectly. When male and female threads are created
# having the same diameter, pitch, and n_starts (usually 1), then so long as
# they are not translated or rotated (or so long as they are subjected to the
# same exact translation and rotation), they will intermesh perfectly,
# regardless of the value of 'z_start' used on each. This is in order that
# assemblies be able to depict perfectly aligning threads.
# Generates threads with base cylinder unless 'base_cylinder' is overridden.
# Please note that 'use_epsilon' is activated by default, which causes a slight
# budge in the minor radius, inwards, so that overlaps would be created with
# inner cylinders. (Does not affect thread profile outside of cylinder.)
###############################################################################
def external_metric_thread(diameter, # Required parameter, e.g. 3.0 for M3x0.5
pitch, # Required parameter, e.g. 0.5 for M3x0.5
length, # Required parameter, e.g. 2.0
z_start=0.0,
n_starts=1,
bottom_lead_in=False, # Lead-in is at same angle as
top_lead_in =False, # thread, namely 30 degrees.
bottom_relief=False, # Add relief groove to start or
top_relief =False, # end of threads (shorten).
force_outer_radius=-1.0, # Set close to diameter/2.
use_epsilon=True, # For inner cylinder overlap.
base_cylinder=True, # Whether to include base cyl.
cyl_extend_bottom=-1.0,
cyl_extend_top=-1.0,
envelope=False): # Draw only envelope, don't cut.
cyl_extend_bottom = max(0.0, cyl_extend_bottom)
cyl_extend_top = max(0.0, cyl_extend_top)
z_off = (1.0 - __metric_thread_effective_ratio()) * pitch / 4
t_start = z_start
t_length = length
if bottom_relief:
t_start = t_start + (2 * z_off)
t_length = t_length - (2 * z_off)
if top_relief:
t_length = t_length - (2 * z_off)
outer_r = (force_outer_radius if (force_outer_radius > 0.0) else
metric_thread_major_radius(diameter,pitch))
inner_r = metric_thread_minor_radius(diameter,pitch)
epsilon = 0
inner_r_adj = inner_r
inner_z_budge = 0
if use_epsilon:
epsilon = (z_off/3) / math.tan(__deg2rad(metric_thread_angle()))
inner_r_adj = inner_r - epsilon
inner_z_budge = math.tan(__deg2rad(metric_thread_angle())) * epsilon
if envelope:
threads = cq.Workplane("XZ")
threads = threads.moveTo(inner_r_adj, -pitch)
threads = threads.lineTo(outer_r, -pitch)
threads = threads.lineTo(outer_r, t_length + pitch)
threads = threads.lineTo(inner_r_adj, t_length + pitch)
threads = threads.close()
threads = threads.revolve()
else: # Not envelope, cut the threads.
wire = cq.Wire.makeHelix(pitch=pitch*n_starts,
height=t_length+pitch,
radius=inner_r)
wire = wire.translate((0,0,-pitch/2))
wire = wire.rotate(startVector=(0,0,0), endVector=(0,0,1),
angleDegrees=360*(-pitch/2)/(pitch*n_starts))
d_mid = ((metric_thread_major_radius(diameter,pitch) - outer_r)
* math.tan(__deg2rad(metric_thread_angle())))
thread = cq.Workplane("XZ")
thread = thread.moveTo(inner_r_adj, -pitch/2 + z_off - inner_z_budge)
thread = thread.lineTo(outer_r, -(z_off + d_mid))
thread = thread.lineTo(outer_r, z_off + d_mid)
thread = thread.lineTo(inner_r_adj, pitch/2 - z_off + inner_z_budge)
thread = thread.close()
thread = thread.sweep(wire, isFrenet=True)
threads = thread
for addl_start in range(1, n_starts):
thread = thread.rotate(axisStartPoint=(0,0,0),
axisEndPoint=(0,0,1),
angleDegrees=360/n_starts)
threads = threads.union(thread)
square_shave = cq.Workplane("XY")
square_shave = square_shave.box(length=outer_r*3, width=outer_r*3,
height=pitch*2, centered=True)
square_shave = square_shave.translate((0,0,-pitch)) # Because centered.
# Always cut the top and bottom square. Otherwise things don't play nice.
threads = threads.cut(square_shave)
if bottom_lead_in:
delta_r = outer_r - inner_r
rise = math.tan(__deg2rad(metric_thread_angle())) * delta_r
lead_in = cq.Workplane("XZ")
lead_in = lead_in.moveTo(inner_r - delta_r, -rise)
lead_in = lead_in.lineTo(outer_r + delta_r, 2 * rise)
lead_in = lead_in.lineTo(outer_r + delta_r, -pitch - rise)
lead_in = lead_in.lineTo(inner_r - delta_r, -pitch - rise)
lead_in = lead_in.close()
lead_in = lead_in.revolve()
threads = threads.cut(lead_in)
# This was originally a workaround to the anomalous B-rep computation where
# the top of base cylinder is flush with top of threads, without the use of
# lead-in. It turns out that preferring the use of the 'render_cyl_early'
# strategy alleviates other problems as well.
render_cyl_early = (base_cylinder and ((not top_relief) and
(not (cyl_extend_top > 0.0)) and
(not envelope)))
render_cyl_late = (base_cylinder and (not render_cyl_early))
if render_cyl_early:
cyl = cq.Workplane("XY")
cyl = cyl.circle(radius=inner_r)
cyl = cyl.extrude(until=length+pitch+cyl_extend_bottom)
# Make rotation of cylinder consistent with non-workaround case.
cyl = cyl.rotate(axisStartPoint=(0,0,0), axisEndPoint=(0,0,1),
angleDegrees=-(360*t_start/(pitch*n_starts)))
cyl = cyl.translate((0,0,-t_start+(z_start-cyl_extend_bottom)))
threads = threads.union(cyl)
# Next, make cuts at the top.
square_shave = square_shave.translate((0,0,pitch*2+t_length))
threads = threads.cut(square_shave)
if top_lead_in:
delta_r = outer_r - inner_r
rise = math.tan(__deg2rad(metric_thread_angle())) * delta_r
lead_in = cq.Workplane("XZ")
lead_in = lead_in.moveTo(inner_r - delta_r, t_length + rise)
lead_in = lead_in.lineTo(outer_r + delta_r, t_length - (2 * rise))
lead_in = lead_in.lineTo(outer_r + delta_r, t_length + pitch + rise)
lead_in = lead_in.lineTo(inner_r - delta_r, t_length + pitch + rise)
lead_in = lead_in.close()
lead_in = lead_in.revolve()
threads = threads.cut(lead_in)
# Place the threads into position.
threads = threads.translate((0,0,t_start))
if (not envelope):
threads = threads.rotate(axisStartPoint=(0,0,0), axisEndPoint=(0,0,1),
angleDegrees=360*t_start/(pitch*n_starts))
if render_cyl_late:
cyl = cq.Workplane("XY")
cyl = cyl.circle(radius=inner_r)
cyl = cyl.extrude(until=length+cyl_extend_bottom+cyl_extend_top)
cyl = cyl.translate((0,0,z_start-cyl_extend_bottom))
threads = threads.union(cyl)
return threads
###############################################################################
# Generates female threads without a base tube, unless 'base_tube_od' is set to
# something which is sufficiently greater than 'diameter' parameter. Please
# note that 'use_epsilon' is activated by default, which causes a slight budge
# in the major radius, outwards, so that overlaps would be created with outer
# tubes. (Does not affect thread profile inside of tube or beyond extents.)
###############################################################################
def internal_metric_thread(diameter, # Required parameter, e.g. 3.0 for M3x0.5
pitch, # Required parameter, e.g. 0.5 for M3x0.5
length, # Required parameter, e.g. 2.0.
z_start=0.0,
n_starts=1,
bottom_chamfer=False, # Chamfer is at same angle as
top_chamfer =False, # thread, namely 30 degrees.
bottom_relief=False, # Add relief groove to start or
top_relief =False, # end of threads (shorten).
use_epsilon=True, # For outer cylinder overlap.
# The base tube outer diameter must be sufficiently
# large for tube to be rendered. Otherwise ignored.
base_tube_od=-1.0,
tube_extend_bottom=-1.0,
tube_extend_top=-1.0,
envelope=False): # Draw only envelope, don't cut.
tube_extend_bottom = max(0.0, tube_extend_bottom)
tube_extend_top = max(0.0, tube_extend_top)
z_off = (1.0 - __metric_thread_effective_ratio()) * pitch / 4
t_start = z_start
t_length = length
if bottom_relief:
t_start = t_start + (2 * z_off)
t_length = t_length - (2 * z_off)
if top_relief:
t_length = t_length - (2 * z_off)
outer_r = metric_thread_major_radius(diameter,pitch,
internal=True)
inner_r = metric_thread_minor_radius(diameter,pitch,
internal=True)
epsilon = 0
outer_r_adj = outer_r
outer_z_budge = 0
if use_epsilon:
# High values of 'epsilon' sometimes cause entire starts to disappear.
epsilon = (z_off/5) / math.tan(__deg2rad(metric_thread_angle()))
outer_r_adj = outer_r + epsilon
outer_z_budge = math.tan(__deg2rad(metric_thread_angle())) * epsilon
if envelope:
threads = cq.Workplane("XZ")
threads = threads.moveTo(outer_r_adj, -pitch)
threads = threads.lineTo(inner_r, -pitch)
threads = threads.lineTo(inner_r, t_length + pitch)
threads = threads.lineTo(outer_r_adj, t_length + pitch)
threads = threads.close()
threads = threads.revolve()
else: # Not envelope, cut the threads.
wire = cq.Wire.makeHelix(pitch=pitch*n_starts,
height=t_length+pitch,
radius=inner_r)
wire = wire.translate((0,0,-pitch/2))
wire = wire.rotate(startVector=(0,0,0), endVector=(0,0,1),
angleDegrees=360*(-pitch/2)/(pitch*n_starts))
thread = cq.Workplane("XZ")
thread = thread.moveTo(outer_r_adj, -pitch/2 + z_off - outer_z_budge)
thread = thread.lineTo(inner_r, -z_off)
thread = thread.lineTo(inner_r, z_off)
thread = thread.lineTo(outer_r_adj, pitch/2 - z_off + outer_z_budge)
thread = thread.close()
thread = thread.sweep(wire, isFrenet=True)
threads = thread
for addl_start in range(1, n_starts):
thread = thread.rotate(axisStartPoint=(0,0,0),
axisEndPoint=(0,0,1),
angleDegrees=360/n_starts)
threads = threads.union(thread)
# Rotate so that the external threads would align.
threads = threads.rotate(axisStartPoint=(0,0,0), axisEndPoint=(0,0,1),
angleDegrees=180/n_starts)
square_len = max(outer_r*3, base_tube_od*1.125)
square_shave = cq.Workplane("XY")
square_shave = square_shave.box(length=square_len, width=square_len,
height=pitch*2, centered=True)
square_shave = square_shave.translate((0,0,-pitch)) # Because centered.
# Always cut the top and bottom square. Otherwise things don't play nice.
threads = threads.cut(square_shave)
if bottom_chamfer:
delta_r = outer_r - inner_r
rise = math.tan(__deg2rad(metric_thread_angle())) * delta_r
chamfer = cq.Workplane("XZ")
chamfer = chamfer.moveTo(inner_r - delta_r, 2 * rise)
chamfer = chamfer.lineTo(outer_r + delta_r, -rise)
chamfer = chamfer.lineTo(outer_r + delta_r, -pitch - rise)
chamfer = chamfer.lineTo(inner_r - delta_r, -pitch - rise)
chamfer = chamfer.close()
chamfer = chamfer.revolve()
threads = threads.cut(chamfer)
# This was originally a workaround to the anomalous B-rep computation where
# the top of base tube is flush with top of threads w/o the use of chamfer.
# This is now being made consistent with the 'render_cyl_early' strategy in
# external_metric_thread() whereby we prefer the "render early" plan of
# action even in cases where a top chamfer or lead-in is used.
render_tube_early = ((base_tube_od > (outer_r * 2)) and
(not top_relief) and
(not (tube_extend_top > 0.0)) and
(not envelope))
render_tube_late = ((base_tube_od > (outer_r * 2)) and
(not render_tube_early))
if render_tube_early:
tube = cq.Workplane("XY")
tube = tube.circle(radius=base_tube_od/2)
tube = tube.circle(radius=outer_r)
tube = tube.extrude(until=length+pitch+tube_extend_bottom)
# Make rotation of cylinder consistent with non-workaround case.
tube = tube.rotate(axisStartPoint=(0,0,0), axisEndPoint=(0,0,1),
angleDegrees=-(360*t_start/(pitch*n_starts)))
tube = tube.translate((0,0,-t_start+(z_start-tube_extend_bottom)))
threads = threads.union(tube)
# Next, make cuts at the top.
square_shave = square_shave.translate((0,0,pitch*2+t_length))
threads = threads.cut(square_shave)
if top_chamfer:
delta_r = outer_r - inner_r
rise = math.tan(__deg2rad(metric_thread_angle())) * delta_r
chamfer = cq.Workplane("XZ")
chamfer = chamfer.moveTo(inner_r - delta_r, t_length - (2 * rise))
chamfer = chamfer.lineTo(outer_r + delta_r, t_length + rise)
chamfer = chamfer.lineTo(outer_r + delta_r, t_length + pitch + rise)
chamfer = chamfer.lineTo(inner_r - delta_r, t_length + pitch + rise)
chamfer = chamfer.close()
chamfer = chamfer.revolve()
threads = threads.cut(chamfer)
# Place the threads into position.
threads = threads.translate((0,0,t_start))
if (not envelope):
threads = threads.rotate(axisStartPoint=(0,0,0), axisEndPoint=(0,0,1),
angleDegrees=360*t_start/(pitch*n_starts))
if render_tube_late:
tube = cq.Workplane("XY")
tube = tube.circle(radius=base_tube_od/2)
tube = tube.circle(radius=outer_r)
tube = tube.extrude(until=length+tube_extend_bottom+tube_extend_top)
tube = tube.translate((0,0,z_start-tube_extend_bottom))
threads = threads.union(tube)
return threads
解释
# 版权声明和作者信息
# 本代码用于演示如何创建匹配的M3x0.5螺纹:
###############################################################################
# 生成外部螺纹的示例
# male = external_metric_thread(3.0, 0.5, 4.0, z_start= -0.85,
# top_lead_in=True)
# 生成内部螺纹的示例,适用于直径稍大于3.0mm的孔
# female = internal_metric_thread(3.0, 0.5, 1.5,
# bottom_chamfer=True, base_tube_od= 4.5)
###############################################################################
# 通过镜像操作创建左旋螺纹
# 代码使用说明
import math
import cadquery as cq
###############################################################################
# 以下函数以'__'开头的不建议外部调用;其他函数则是为了被外部代码调用而编写的。
# 代码的第一部分是一些轻量级的辅助函数;库的核心功能位于最后。
# 返回螺纹角度,固定为30度,代表典型的60度等边三角形切削螺纹。
def metric_thread_angle():
return 30
# 辅助函数,将度数转换为弧度。
def __deg2rad(degrees):
return degrees * math.pi / 180
# 返回螺纹"三角形"从基础圆柱体向外(对于外螺纹)或向内(对于内螺纹)突出的高度。
def metric_thread_perfect_height(pitch):
return pitch / (2 * math.tan(__deg2rad(metric_thread_angle())))
# 为内螺纹增加半径,提供一些间隙。
def __metric_thread_internal_radius_increase(diameter, pitch):
return 0.1 * metric_thread_perfect_height(pitch)
# 返回螺纹的主半径,总是较大的那个。
def metric_thread_major_radius(diameter, pitch, internal=False):
return (__metric_thread_internal_radius_increase(diameter, pitch) if
internal else 0.0) + (diameter / 2)
# 返回螺纹的次半径,总是较小的那个。
def metric_thread_minor_radius(diameter, pitch, internal=False):
return (metric_thread_major_radius(diameter, pitch, internal)
- (__metric_thread_effective_ratio() *
metric_thread_perfect_height(pitch)))
# 创建外部螺纹的函数,除非覆盖'base_cylinder'参数,否则会生成基础圆柱体。
def external_metric_thread(diameter, pitch, length, z_start=0.0, ...):
# 函数实现...
# 创建内部螺纹的函数,除非设置了'base_tube_od'参数,否则不会生成基础管。
def internal_metric_thread(diameter, pitch, length, z_start=0.0, ...):
# 函数实现...
demo3(cq绘制螺旋修饰线+solid)
import cadquery as cq
from cadquery import Workplane, Assembly
from cadquery.vis import show_object
wire = cq.Wire.makeHelix(pitch=0.5, height=30, radius=3, center=(0,0,-15))
s = Workplane(origin=(0,0,0)).cylinder(30,3)
result = Assembly(s)
result1= result.add(wire)
show_object(result1)
import cadquery as cq
from cadquery import Workplane, Assembly
from cadquery.occ_impl.exporters import export
from cadquery.vis import show_object
wire = cq.Wire.makeHelix(pitch=0.5, height=30, radius=3, center=(0,0,-15))
s = Workplane(origin=(0,0,0)).cylinder(30,3)
result = s.add(wire)
export(result, 'output.step')
show_object(result)
标签:Thread,thread,self,radius,pitch,threads,cq
From: https://www.cnblogs.com/arwen-xu/p/18134452