首页 > 其他分享 >Cq thread

Cq thread

时间:2024-01-15 11:47:45浏览次数:23  
标签:end thread apex self radius fade Cq

   1 """
   2 
   3 Parametric Threads
   4 
   5 name: thread.py
   6 by:   Gumyr
   7 date: November 11th 2021
   8 
   9 desc: This python/cadquery code is a parameterized thread generator.
  10 
  11 license:
  12 
  13     Copyright 2021 Gumyr
  14 
  15     Licensed under the Apache License, Version 2.0 (the "License");
  16     you may not use this file except in compliance with the License.
  17     You may obtain a copy of the License at
  18 
  19         http://www.apache.org/licenses/LICENSE-2.0
  20 
  21     Unless required by applicable law or agreed to in writing, software
  22     distributed under the License is distributed on an "AS IS" BASIS,
  23     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24     See the License for the specific language governing permissions and
  25     limitations under the License.
  26 
  27 """
  28 import re
  29 from warnings import warn
  30 from abc import ABC, abstractmethod
  31 from typing import Literal, Optional, Tuple, List
  32 from math import sin, cos, tan, radians, pi
  33 import cadquery as cq
  34 from cadquery import Solid, Compound
  35 from OCP.TopoDS import TopoDS_Shape
  36 
  37 # from functools import cached_property, cache
  38 
  39 MM = 1
  40 IN = 25.4 * MM
  41 
  42 
  43 def is_safe(value: str) -> bool:
  44     """Evaluate if the given string is a fractional number safe for eval()"""
  45     return len(value) <= 10 and all(c in "0123456789./ " for c in set(value))
  46 
  47 
  48 def imperial_str_to_float(measure: str) -> float:
  49     """Convert an imperial measurement (possibly a fraction) to a float value"""
  50     if is_safe(measure):
  51         # pylint: disable=eval-used
  52         # Before eval() is called the string extracted from the csv file is verified as safe
  53         result = eval(measure.strip().replace(" ", "+")) * IN
  54     else:
  55         result = measure
  56     return result
  57 
  58 
  59 class Thread(Solid):
  60     """Helical thread
  61 
  62     The most general thread class used to build all of the other threads.
  63     Creates right or left hand helical thread with the given
  64     root and apex radii.
  65 
  66     Args:
  67         apex_radius: Radius at the narrow tip of the thread.
  68         apex_width: Radius at the wide base of the thread.
  69         root_radius: Radius at the wide base of the thread.
  70         root_width: Thread base width.
  71         pitch: Length of 360° of thread rotation.
  72         length: End to end length of the thread.
  73         apex_offset: Asymmetric thread apex offset from center. Defaults to 0.0.
  74         hand: Twist direction. Defaults to "right".
  75         taper_angle: Cone angle for tapered thread. Defaults to None.
  76         end_finishes: Profile of each end, one of:
  77 
  78             "raw"
  79                 unfinished which typically results in the thread
  80                 extended below z=0 or above z=length
  81             "fade"
  82                 the thread height drops to zero over 90° of arc
  83                 (or 1/4 pitch)
  84             "square"
  85                 clipped by the z=0 or z=length plane
  86             "chamfer"
  87                 conical ends which facilitates alignment of a bolt
  88                 into a nut
  89 
  90             Defaults to ("raw","raw").
  91         simple: Stop at thread calculation, don't create thread. Defaults to False.
  92 
  93     Raises:
  94         ValueError: if end_finishes not in ["raw", "square", "fade", "chamfer"]:
  95     """
  96 
  97     def fade_helix(
  98         self, t: float, apex: bool, vertical_displacement: float
  99     ) -> Tuple[float, float, float]:
 100         """A helical function used to create the faded tips of threads that spirals
 101         self.tooth_height in self.pitch/4"""
 102         if self.external:
 103             radius = (
 104                 self.apex_radius - sin(t * pi / 2) * self.tooth_height
 105                 if apex
 106                 else self.root_radius
 107             )
 108         else:
 109             radius = (
 110                 self.apex_radius + sin(t * pi / 2) * self.tooth_height
 111                 if apex
 112                 else self.root_radius
 113             )
 114 
 115         z_pos = t * self.pitch / 4 + t * vertical_displacement
 116         x_pos = radius * cos(t * pi / 2)
 117         y_pos = radius * sin(t * pi / 2)
 118         return (x_pos, y_pos, z_pos)
 119 
 120     @property
 121     def cq_object(self):
 122         """A cadquery Solid thread as defined by class attributes"""
 123         warn("cq_object will be deprecated.", DeprecationWarning, stacklevel=2)
 124         return Solid(self.wrapped)
 125 
 126     def __init__(
 127         self,
 128         apex_radius: float,
 129         apex_width: float,
 130         root_radius: float,
 131         root_width: float,
 132         pitch: float,
 133         length: float,
 134         apex_offset: float = 0.0,
 135         hand: Literal["right", "left"] = "right",
 136         taper_angle: Optional[float] = None,
 137         end_finishes: Tuple[
 138             Literal["raw", "square", "fade", "chamfer"],
 139             Literal["raw", "square", "fade", "chamfer"],
 140         ] = ("raw", "raw"),
 141         simple: bool = False,
 142     ):
 143         """Store the parameters and create the thread object"""
 144         for finish in end_finishes:
 145             if finish not in ["raw", "square", "fade", "chamfer"]:
 146                 raise ValueError(
 147                     'end_finishes invalid, must be tuple() of "raw, square, taper, or chamfer"'
 148                 )
 149         self.external = apex_radius > root_radius
 150         self.apex_radius = apex_radius
 151         self.apex_width = apex_width
 152         # Unfortunately, when creating "fade" ends inaccuracies in parametric curve calculations
 153         # can result in a gap which causes the OCCT core to fail when combining with other
 154         # object (like the core of the thread). To avoid this, subtract (or add) a fudge factor
 155         # to the root radius to make it small enough to intersect the given radii.
 156         self.root_radius = root_radius - (0.001 if self.external else -0.001)
 157         self.root_width = root_width
 158         self.pitch = pitch
 159         self.length = length
 160         self.apex_offset = apex_offset
 161         self.right_hand = hand == "right"
 162         self.end_finishes = end_finishes
 163         self.tooth_height = abs(self.apex_radius - self.root_radius)
 164         self.taper = 360 if taper_angle is None else taper_angle
 165         self.simple = simple
 166 
 167         if not simple:
 168             # Create base cylindrical thread
 169             number_faded_ends = self.end_finishes.count("fade")
 170             cylindrical_thread_length = self.length + self.pitch * (
 171                 1 - 1 * number_faded_ends
 172             )
 173             if self.end_finishes[0] == "fade":
 174                 cylindrical_thread_displacement = self.pitch / 2
 175             else:
 176                 cylindrical_thread_displacement = -self.pitch / 2
 177 
 178             # Either create a cylindrical thread for further processing
 179             # or create a cylindrical thread segment with faded ends
 180             if number_faded_ends == 0:
 181                 cq_object = self.make_thread_solid(cylindrical_thread_length).translate(
 182                     (0, 0, cylindrical_thread_displacement)
 183                 )
 184             else:
 185                 cq_object = self.make_thread_with_faded_ends(
 186                     number_faded_ends,
 187                     cylindrical_thread_length,
 188                     cylindrical_thread_displacement,
 189                 )
 190 
 191             # Square off ends if requested
 192             cq_object = self.square_off_ends(cq_object)
 193             # Chamfer ends if requested
 194             cq_object = self.chamfer_ends(cq_object)
 195             if isinstance(cq_object, Compound) and len(cq_object.Solids()) == 1:
 196                 super().__init__(cq_object.Solids()[0].wrapped)
 197             else:
 198                 super().__init__(cq_object.wrapped)
 199         else:
 200             # Initialize with a valid shape then nullify
 201             super().__init__(Solid.makeBox(1, 1, 1).wrapped)
 202             self.wrapped = TopoDS_Shape()
 203 
 204     def make_thread_with_faded_ends(
 205         self,
 206         number_faded_ends,
 207         cylindrical_thread_length,
 208         cylindrical_thread_displacement,
 209     ):
 210         """Build the thread object from cylindrical thread faces and
 211         faded ends faces"""
 212         (thread_faces, end_faces) = self.make_thread_faces(cylindrical_thread_length)
 213 
 214         # Need to operate on each face below
 215         thread_faces = [
 216             f.translate((0, 0, cylindrical_thread_displacement)) for f in thread_faces
 217         ]
 218         end_faces = [
 219             f.translate((0, 0, cylindrical_thread_displacement)) for f in end_faces
 220         ]
 221         cylindrical_thread_angle = (
 222             (360 if self.right_hand else -360) * cylindrical_thread_length / self.pitch
 223         )
 224         (fade_faces, _fade_ends) = self.make_thread_faces(
 225             self.pitch / 4, fade_helix=True
 226         )
 227         if not self.right_hand:
 228             fade_faces = [f.mirror("XZ") for f in fade_faces]
 229 
 230         if self.end_finishes[0] == "fade":
 231             # If the thread is asymmetric the bottom fade end needs to be recreated as
 232             # no amount of flipping or rotating can generate the shape
 233             if self.apex_offset != 0:
 234                 (fade_faces_bottom, _fade_ends) = self.make_thread_faces(
 235                     self.pitch / 4, fade_helix=True, asymmetric_flip=True
 236                 )
 237                 if not self.right_hand:
 238                     fade_faces_bottom = [f.mirror("XZ") for f in fade_faces_bottom]
 239             else:
 240                 fade_faces_bottom = fade_faces
 241             fade_faces_bottom = [
 242                 f.mirror("XZ").mirror("XY").translate(cq.Vector(0, 0, self.pitch / 2))
 243                 for f in fade_faces_bottom
 244             ]
 245         if self.end_finishes[1] == "fade":
 246             fade_faces_top = [
 247                 f.translate(
 248                     cq.Vector(
 249                         0,
 250                         0,
 251                         cylindrical_thread_length + cylindrical_thread_displacement,
 252                     )
 253                 ).rotate((0, 0, 0), (0, 0, 1), cylindrical_thread_angle)
 254                 for f in fade_faces
 255             ]
 256         if number_faded_ends == 2:
 257             thread_shell = cq.Shell.makeShell(
 258                 thread_faces + fade_faces_bottom + fade_faces_top
 259             )
 260         elif self.end_finishes[0] == "fade":
 261             thread_shell = cq.Shell.makeShell(
 262                 thread_faces + fade_faces_bottom + [end_faces[1]]
 263             )
 264         else:
 265             thread_shell = cq.Shell.makeShell(
 266                 thread_faces + fade_faces_top + [end_faces[0]]
 267             )
 268         return cq.Solid.makeSolid(thread_shell)
 269 
 270     def square_off_ends(self, cq_object: Solid):
 271         """Square off the ends of the thread"""
 272 
 273         squared = cq_object
 274         if self.end_finishes.count("square") != 0:
 275             # Note: box_size must be > max(apex,root) radius or the core doesn't cut correctly
 276             half_box_size = 2 * max(self.apex_radius, self.root_radius)
 277             box_size = 2 * half_box_size
 278             cutter = cq.Solid.makeBox(
 279                 length=box_size,
 280                 width=box_size,
 281                 height=self.length,
 282                 pnt=cq.Vector(-half_box_size, -half_box_size, -self.length),
 283             )
 284             for i in range(2):
 285                 if self.end_finishes[i] == "square":
 286                     squared = cq_object.cut(
 287                         cutter.translate(cq.Vector(0, 0, 2 * i * self.length))
 288                     )
 289         return squared
 290 
 291     def chamfer_ends(self, cq_object: Solid):
 292         """Chamfer the ends of the thread"""
 293 
 294         chamfered = cq_object
 295         if self.end_finishes.count("chamfer") != 0:
 296             cutter = (
 297                 cq.Workplane("XY")
 298                 .circle(self.root_radius)
 299                 .circle(self.apex_radius)
 300                 .extrude(self.length)
 301             )
 302             face_selectors = ["<Z", ">Z"]
 303             edge_radius_selector = 1 if self.apex_radius > self.root_radius else 0
 304             for i in range(2):
 305                 if self.end_finishes[i] == "chamfer":
 306                     cutter = (
 307                         cutter.faces(face_selectors[i])
 308                         .edges(cq.selectors.RadiusNthSelector(edge_radius_selector))
 309                         .chamfer(self.tooth_height * 0.5, self.tooth_height * 0.75)
 310                     )
 311             chamfered = cq_object.intersect(cutter.val())
 312         return chamfered
 313 
 314     def make_thread_faces(
 315         self, length: float, fade_helix: bool = False, asymmetric_flip: bool = False
 316     ) -> Tuple[List[cq.Face]]:
 317         """Create the thread object from basic CadQuery objects
 318 
 319         This method creates three types of thread objects:
 320         1. cylindrical - i.e. following a simple helix
 321         2. tapered - i.e. following a conical helix
 322         3. faded - cylindrical but spiralling towards the root in 90°
 323 
 324         After testing many alternatives (sweep, extrude with rotation, etc.) the
 325         following algorithm was found to be the fastest and most reliable:
 326         a. first create all the edges - helical, linear or parametric
 327         b. create either 5 or 6 faces from the edges (faded needs 5)
 328         c. create a shell from the faces
 329         d. create a solid from the shell
 330         """
 331         local_apex_offset = -self.apex_offset if asymmetric_flip else self.apex_offset
 332         apex_helix_wires = [
 333             cq.Workplane("XY")
 334             .parametricCurve(
 335                 lambda t: self.fade_helix(t, apex=True, vertical_displacement=0)
 336             )
 337             .val()
 338             .translate((0, 0, i * self.apex_width + local_apex_offset))
 339             if fade_helix
 340             else cq.Wire.makeHelix(
 341                 pitch=self.pitch,
 342                 height=length,
 343                 radius=self.apex_radius,
 344                 angle=self.taper,
 345                 lefthand=not self.right_hand,
 346             ).translate((0, 0, i * self.apex_width + local_apex_offset))
 347             for i in [-0.5, 0.5]
 348         ]
 349         assert apex_helix_wires[0].isValid()
 350         root_helix_wires = [
 351             cq.Workplane("XY")
 352             .parametricCurve(
 353                 lambda t: self.fade_helix(
 354                     t,
 355                     apex=False,
 356                     vertical_displacement=-i * (self.root_width - self.apex_width),
 357                 )
 358             )
 359             .val()
 360             .translate((0, 0, i * self.root_width))
 361             if fade_helix
 362             else cq.Wire.makeHelix(
 363                 pitch=self.pitch,
 364                 height=length,
 365                 radius=self.root_radius,
 366                 angle=self.taper,
 367                 lefthand=not self.right_hand,
 368             ).translate((0, 0, i * self.root_width))
 369             for i in [-0.5, 0.5]
 370         ]
 371         # When creating a cylindrical or tapered thread two end faces are required
 372         # to enclose the thread object, while faded thread only has one end face
 373         end_caps = [0] if fade_helix else [0, 1]
 374         end_cap_wires = [
 375             cq.Wire.makePolygon(
 376                 [
 377                     apex_helix_wires[0].positionAt(i),
 378                     apex_helix_wires[1].positionAt(i),
 379                     root_helix_wires[1].positionAt(i),
 380                     root_helix_wires[0].positionAt(i),
 381                     apex_helix_wires[0].positionAt(i),
 382                 ]
 383             )
 384             for i in end_caps
 385         ]
 386         thread_faces = [
 387             cq.Face.makeRuledSurface(apex_helix_wires[0], apex_helix_wires[1]),
 388             cq.Face.makeRuledSurface(apex_helix_wires[1], root_helix_wires[1]),
 389             cq.Face.makeRuledSurface(root_helix_wires[1], root_helix_wires[0]),
 390             cq.Face.makeRuledSurface(root_helix_wires[0], apex_helix_wires[0]),
 391         ]
 392         end_faces = [cq.Face.makeFromWires(end_cap_wires[i]) for i in end_caps]
 393         return (thread_faces, end_faces)
 394 
 395     def make_thread_solid(
 396         self,
 397         length: float,
 398         fade_helix: bool = False,
 399     ) -> cq.Solid:
 400         """Create a solid object by first creating the faces"""
 401         (thread_faces, end_faces) = self.make_thread_faces(length, fade_helix)
 402 
 403         thread_shell = cq.Shell.makeShell(thread_faces + end_faces)
 404         thread_solid = cq.Solid.makeSolid(thread_shell)
 405         return thread_solid
 406 
 407 
 408 class IsoThread(Solid):
 409     """ISO Standard Thread
 410 
 411     Both external and internal ISO standard 60° threads as shown in
 412     the following diagram (from https://en.wikipedia.org/wiki/ISO_metric_screw_thread):
 413 
 414     .. image:: https://upload.wikimedia.org/wikipedia/commons/4/4b/ISO_and_UTS_Thread_Dimensions.svg
 415 
 416     The following is an example of an internal thread with a chamfered end as might
 417     be found inside a nut:
 418 
 419     .. image:: internal_iso_thread.png
 420 
 421     Args:
 422         major_diameter (float): Primary thread diameter
 423         pitch (float): Length of 360° of thread rotation
 424         length (float): End to end length of the thread
 425         external (bool, optional): External or internal thread selector. Defaults to True.
 426         hand (Literal[, optional): Twist direction. Defaults to "right".
 427         end_finishes (Tuple[ Literal[, optional): Profile of each end, one of:
 428 
 429             "raw"
 430                 unfinished which typically results in the thread
 431                 extended below z=0 or above z=length
 432             "fade"
 433                 the thread height drops to zero over 90° of arc
 434                 (or 1/4 pitch)
 435             "square"
 436                 clipped by the z=0 or z=length plane
 437             "chamfer"
 438                 conical ends which facilitates alignment of a bolt
 439                 into a nut
 440 
 441             Defaults to ("fade", "square").
 442         simple: Stop at thread calculation, don't create thread. Defaults to False.
 443 
 444     Attributes:
 445         thread_angle (int): 60 degrees
 446         h_parameter (float): Value of `h` as shown in the thread diagram
 447         min_radius (float): Inside radius of the thread diagram
 448 
 449     Raises:
 450         ValueError: if hand not in ["right", "left"]:
 451         ValueError: end_finishes not in ["raw", "square", "fade", "chamfer"]
 452 
 453     """
 454 
 455     @property
 456     def h_parameter(self) -> float:
 457         """Calculate the h parameter"""
 458         return (self.pitch / 2) / tan(radians(self.thread_angle / 2))
 459 
 460     @property
 461     def min_radius(self) -> float:
 462         """The radius of the root of the thread"""
 463         return (self.major_diameter - 2 * (5 / 8) * self.h_parameter) / 2
 464 
 465     @property
 466     def cq_object(self):
 467         """A cadquery Solid thread as defined by class attributes"""
 468         warn("cq_object will be deprecated.", DeprecationWarning, stacklevel=2)
 469         return Solid(self.wrapped)
 470 
 471     def __init__(
 472         self,
 473         major_diameter: float,
 474         pitch: float,
 475         length: float,
 476         external: bool = True,
 477         hand: Literal["right", "left"] = "right",
 478         end_finishes: Tuple[
 479             Literal["raw", "square", "fade", "chamfer"],
 480             Literal["raw", "square", "fade", "chamfer"],
 481         ] = ("fade", "square"),
 482         simple: bool = False,
 483     ):
 484 
 485         self.major_diameter = major_diameter
 486         self.pitch = pitch
 487         self.length = length
 488         self.external = external
 489         self.thread_angle = 60
 490         if hand not in ["right", "left"]:
 491             raise ValueError(f'hand must be one of "right" or "left" not {hand}')
 492         self.hand = hand
 493         for finish in end_finishes:
 494             if finish not in ["raw", "square", "fade", "chamfer"]:
 495                 raise ValueError(
 496                     'end_finishes invalid, must be tuple() of "raw, square, taper, or chamfer"'
 497                 )
 498         self.end_finishes = end_finishes
 499         self.simple = simple
 500         self.apex_radius = self.major_diameter / 2 if external else self.min_radius
 501         apex_width = self.pitch / 8 if external else self.pitch / 4
 502         self.root_radius = self.min_radius if external else self.major_diameter / 2
 503         root_width = 3 * self.pitch / 4 if external else 7 * self.pitch / 8
 504         cq_object = Thread(
 505             apex_radius=self.apex_radius,
 506             apex_width=apex_width,
 507             root_radius=self.root_radius,
 508             root_width=root_width,
 509             pitch=self.pitch,
 510             length=self.length,
 511             end_finishes=self.end_finishes,
 512             hand=self.hand,
 513             simple=simple,
 514         )
 515         if simple:
 516             # Initialize with a valid shape then nullify
 517             super().__init__(Solid.makeBox(1, 1, 1).wrapped)
 518             self.wrapped = TopoDS_Shape()
 519         else:
 520             super().__init__(cq_object.wrapped)
 521 
 522 
 523 class TrapezoidalThread(ABC, Solid):
 524     """Trapezoidal Thread Base Class
 525 
 526     Trapezoidal Thread base class for Metric and Acme derived classes
 527 
 528     Trapezoidal thread forms are screw thread profiles with trapezoidal outlines. They are
 529     the most common forms used for leadscrews (power screws). They offer high strength
 530     and ease of manufacture. They are typically found where large loads are required, as
 531     in a vise or the leadscrew of a lathe.
 532 
 533     Args:
 534         size (str): specified by derived class
 535         length (float): thread length
 536         external (bool, optional): external or internal thread selector. Defaults to True.
 537         hand (Literal[, optional): twist direction. Defaults to "right".
 538         end_finishes (Tuple[ Literal[, optional): Profile of each end, one of:
 539 
 540             "raw"
 541                 unfinished which typically results in the thread
 542                 extended below z=0 or above z=length
 543             "fade"
 544                 the thread height drops to zero over 90° of arc
 545                 (or 1/4 pitch)
 546             "square"
 547                 clipped by the z=0 or z=length plane
 548             "chamfer"
 549                 conical ends which facilitates alignment of a bolt
 550                 into a nut
 551 
 552             Defaults to ("fade", "fade").
 553 
 554     Raises:
 555         ValueError: hand must be one of "right" or "left"
 556         ValueError: end_finishes invalid, must be tuple() of "raw, square, taper, or chamfer"
 557 
 558     Attributes:
 559         thread_angle (int): thread angle in degrees
 560         diameter (float): thread diameter
 561         pitch (float): thread pitch
 562 
 563     """
 564 
 565     @property
 566     def cq_object(self):
 567         """A cadquery Solid thread as defined by class attributes"""
 568         warn("cq_object will be deprecated.", DeprecationWarning, stacklevel=2)
 569         return Solid(self.wrapped)
 570 
 571     @property
 572     @abstractmethod
 573     def thread_angle(self) -> float:  # pragma: no cover
 574         """The thread angle in degrees"""
 575         return NotImplementedError
 576 
 577     @classmethod
 578     @abstractmethod
 579     def parse_size(cls, size: str) -> Tuple[float, float]:  # pragma: no cover
 580         """Convert the provided size into a tuple of diameter and pitch"""
 581         return NotImplementedError
 582 
 583     def __init__(
 584         self,
 585         size: str,
 586         length: float,
 587         external: bool = True,
 588         hand: Literal["right", "left"] = "right",
 589         end_finishes: tuple[
 590             Literal["raw", "square", "fade", "chamfer"],
 591             Literal["raw", "square", "fade", "chamfer"],
 592         ] = ("fade", "fade"),
 593     ):
 594         self.size = size
 595         self.external = external
 596         self.length = length
 597         (self.diameter, self.pitch) = self.parse_size(self.size)
 598         shoulder_width = (self.pitch / 2) * tan(radians(self.thread_angle / 2))
 599         apex_width = (self.pitch / 2) - shoulder_width
 600         root_width = (self.pitch / 2) + shoulder_width
 601         if self.external:
 602             self.apex_radius = self.diameter / 2
 603             self.root_radius = self.diameter / 2 - self.pitch / 2
 604         else:
 605             self.apex_radius = self.diameter / 2 - self.pitch / 2
 606             self.root_radius = self.diameter / 2
 607 
 608         if hand not in ["right", "left"]:
 609             raise ValueError(f'hand must be one of "right" or "left" not {hand}')
 610         self.hand = hand
 611         for finish in end_finishes:
 612             if not finish in ["raw", "square", "fade", "chamfer"]:
 613                 raise ValueError(
 614                     'end_finishes invalid, must be tuple() of "raw, square, taper, or chamfer"'
 615                 )
 616         self.end_finishes = end_finishes
 617         cq_object = Thread(
 618             apex_radius=self.apex_radius,
 619             apex_width=apex_width,
 620             root_radius=self.root_radius,
 621             root_width=root_width,
 622             pitch=self.pitch,
 623             length=self.length,
 624             end_finishes=self.end_finishes,
 625             hand=self.hand,
 626         )
 627         super().__init__(cq_object.wrapped)
 628 
 629 
 630 class AcmeThread(TrapezoidalThread):
 631     """ACME Thread
 632 
 633     The original trapezoidal thread form, and still probably the one most commonly encountered
 634     worldwide, with a 29° thread angle, is the Acme thread form.
 635 
 636     The following is the acme thread with faded ends:
 637 
 638     .. image:: acme_thread.png
 639 
 640     Args:
 641         size (str): size as a string (i.e. "3/4" or "1 1/4")
 642         length (float): thread length
 643         external (bool, optional): external or internal thread selector. Defaults to True.
 644         hand (Literal[, optional): twist direction. Defaults to "right".
 645         end_finishes (Tuple[ Literal[, optional): Profile of each end, one of:
 646 
 647             "raw"
 648                 unfinished which typically results in the thread
 649                 extended below z=0 or above z=length
 650             "fade"
 651                 the thread height drops to zero over 90° of arc
 652                 (or 1/4 pitch)
 653             "square"
 654                 clipped by the z=0 or z=length plane
 655             "chamfer"
 656                 conical ends which facilitates alignment of a bolt
 657                 into a nut
 658 
 659             Defaults to ("fade", "fade").
 660 
 661     Raises:
 662         ValueError: hand must be one of "right" or "left"
 663         ValueError: end_finishes invalid, must be tuple() of "raw, square, taper, or chamfer"
 664 
 665     Attributes:
 666         thread_angle (int): thread angle in degrees
 667         diameter (float): thread diameter
 668         pitch (float): thread pitch
 669 
 670     """
 671 
 672     acme_pitch = {
 673         "1/4": (1 / 16) * IN,
 674         "5/16": (1 / 14) * IN,
 675         "3/8": (1 / 12) * IN,
 676         "1/2": (1 / 10) * IN,
 677         "5/8": (1 / 8) * IN,
 678         "3/4": (1 / 6) * IN,
 679         "7/8": (1 / 6) * IN,
 680         "1": (1 / 5) * IN,
 681         "1 1/4": (1 / 5) * IN,
 682         "1 1/2": (1 / 4) * IN,
 683         "1 3/4": (1 / 4) * IN,
 684         "2": (1 / 4) * IN,
 685         "2 1/2": (1 / 3) * IN,
 686         "3": (1 / 2) * IN,
 687     }
 688 
 689     thread_angle = 29.0  # in degrees
 690 
 691     @classmethod
 692     def sizes(cls) -> List[str]:
 693         """Return a list of the thread sizes"""
 694         return list(AcmeThread.acme_pitch.keys())
 695 
 696     @classmethod
 697     def parse_size(cls, size: str) -> Tuple[float, float]:
 698         """Convert the provided size into a tuple of diameter and pitch"""
 699         if not size in AcmeThread.acme_pitch.keys():
 700             raise ValueError(
 701                 f"size invalid, must be one of {AcmeThread.acme_pitch.keys()}"
 702             )
 703         diameter = imperial_str_to_float(size)
 704         pitch = AcmeThread.acme_pitch[size]
 705         return (diameter, pitch)
 706 
 707 
 708 class MetricTrapezoidalThread(TrapezoidalThread):
 709     """Metric Trapezoidal Thread
 710 
 711     The ISO 2904 standard metric trapezoidal thread with a thread angle of 30°
 712 
 713     Args:
 714         size (str): specified as a sting with diameter x pitch in mm (i.e. "8x1.5")
 715         length (float): End to end length of the thread
 716         external (bool, optional): external or internal thread selector. Defaults to True.
 717         hand (Literal[, optional): twist direction. Defaults to "right".
 718         end_finishes (Tuple[ Literal[, optional): Profile of each end, one of:
 719 
 720             "raw"
 721                 unfinished which typically results in the thread
 722                 extended below z=0 or above z=length
 723             "fade"
 724                 the thread height drops to zero over 90° of arc
 725                 (or 1/4 pitch)
 726             "square"
 727                 clipped by the z=0 or z=length plane
 728             "chamfer"
 729                 conical ends which facilitates alignment of a bolt
 730                 into a nut
 731 
 732             Defaults to ("fade", "fade").
 733 
 734     Raises:
 735         ValueError: hand must be one of "right" or "left"
 736         ValueError: end_finishes invalid, must be tuple() of "raw, square, taper, or chamfer"
 737 
 738     Attributes:
 739         thread_angle (int): thread angle in degrees
 740         diameter (float): thread diameter
 741         pitch (float): thread pitch
 742     """
 743 
 744     # Turn off black auto-format for this array as it will be spread over hundreds of lines
 745     # fmt: off
 746     standard_sizes = [
 747         "8x1.5","9x1.5","9x2","10x1.5","10x2","11x2","11x3","12x2","12x3","14x2",
 748         "14x3","16x2","16x3","16x4","18x2","18x3","18x4","20x2","20x3","20x4",
 749         "22x3","22x5","22x8","24x3","24x5","24x8","26x3","26x5","26x8","28x3",
 750         "28x5","28x8","30x3","30x6","30x10","32x3","32x6","32x10","34x3","34x6",
 751         "34x10","36x3","36x6","36x10","38x3","38x7","38x10","40x3","40x7","40x10",
 752         "42x3","42x7","42x10","44x3","44x7","44x12","46x3","46x8","46x12","48x3",
 753         "48x8","48x12","50x3","50x8","50x12","52x3","52x8","52x12","55x3","55x9",
 754         "55x14","60x3","60x9","60x14","65x4","65x10","65x16","70x4","70x10","70x16",
 755         "75x4","75x10","75x16","80x4","80x10","80x16","85x4","85x12","85x18","90x4",
 756         "90x12","90x18","95x4","95x12","95x18","100x4","100x12","100x20","105x4",
 757         "105x12","105x20","110x4","110x12","110x20","115x6","115x12","115x14",
 758         "115x22","120x6","120x12","120x14","120x22","125x6","125x12","125x14",
 759         "125x22","130x6","130x12","130x14","130x22","135x6","135x12","135x14",
 760         "135x24","140x6","140x12","140x14","140x24","145x6","145x12","145x14",
 761         "145x24","150x6","150x12","150x16","150x24","155x6","155x12","155x16",
 762         "155x24","160x6","160x12","160x16","160x28","165x6","165x12","165x16",
 763         "165x28","170x6","170x12","170x16","170x28","175x8","175x12","175x16",
 764         "175x28","180x8","180x12","180x18","180x28","185x8","185x12","185x18",
 765         "185x24","185x32","190x8","190x12","190x18","190x24","190x32","195x8",
 766         "195x12","195x18","195x24","195x32","200x8","200x12","200x18","200x24",
 767         "200x32","205x4","210x4","210x8","210x12","210x20","210x24","210x36","215x4",
 768         "220x4","220x8","220x12","220x20","220x24","220x36","230x4","230x8","230x12",
 769         "230x20","230x24","230x36","235x4","240x4","240x8","240x12","240x20",
 770         "240x22","240x24","240x36","250x4","250x12","250x22","250x24","250x40",
 771         "260x4","260x12","260x20","260x22","260x24","260x40","270x12","270x24",
 772         "270x40","275x4","280x4","280x12","280x24","280x40","290x4","290x12",
 773         "290x24","290x44","295x4","300x4","300x12","300x24","300x44","310x5","315x5"
 774     ]
 775     # fmt: on
 776 
 777     thread_angle = 30.0  # in degrees
 778 
 779     @classmethod
 780     def sizes(cls) -> List[str]:
 781         """Return a list of the thread sizes"""
 782         return MetricTrapezoidalThread.standard_sizes
 783 
 784     @classmethod
 785     def parse_size(cls, size: str) -> Tuple[float, float]:
 786         """Convert the provided size into a tuple of diameter and pitch"""
 787         if not size in MetricTrapezoidalThread.standard_sizes:
 788             raise ValueError(
 789                 f"size invalid, must be one of {MetricTrapezoidalThread.standard_sizes}"
 790             )
 791         (diameter, pitch) = (float(part) for part in size.split("x"))
 792         return (diameter, pitch)
 793 
 794 
 795 class PlasticBottleThread(Solid):
 796     """ASTM D2911 Plastic Bottle Thread
 797 
 798     The `ASTM D2911 Standard <https://www.astm.org/d2911-10.html>`_ Plastic Bottle Thread.
 799 
 800     L Style:
 801         All-Purpose Thread - trapezoidal shape with 30° shoulders, metal or platsic closures
 802     M Style:
 803         Modified Buttress Thread - asymmetric shape with 10° and 40/45/50°
 804         shoulders, plastic closures
 805 
 806     .. image:: plasticThread.png
 807 
 808     Args:
 809         size (str): as defined by the ASTM is specified as
 810             [L|M][diameter(mm)]SP[100|103|110|200|400|410|415|425|444]
 811         external (bool, optional): external or internal thread selector. Defaults to True.
 812         hand (Literal[, optional): twist direction. Defaults to "right".
 813         manufacturingCompensation (float, optional): used to compensate for over-extrusion of 3D
 814             printers. A value of 0.2mm will reduce the radius of an external thread by 0.2mm (and
 815             increase the radius of an internal thread) such that the resulting 3D printed part
 816             matches the target dimensions. Defaults to 0.0.
 817 
 818     Raises:
 819         ValueError: hand must be one of "right" or "left"
 820         ValueError: size invalid, must match
 821             [L|M][diameter(mm)]SP[100|103|110|200|400|410|415:425|444]
 822         ValueError: finish invalid
 823         ValueError: diameter invalid
 824 
 825     Example:
 826         .. code-block:: python
 827 
 828             thread = PlasticBottleThread(
 829                 size="M38SP444", external=False, manufacturingCompensation=0.2 * MM
 830             )
 831 
 832     """
 833 
 834     # {TPI: [root_width,thread_height]}
 835     l_style_thread_dimensions = {
 836         4: [3.18, 1.57],
 837         5: [3.05, 1.52],
 838         6: [2.39, 1.19],
 839         8: [2.13, 1.07],
 840         12: [1.14, 0.76],
 841     }
 842     m_style_thread_dimensions = {
 843         4: [3.18, 1.57],
 844         5: [3.05, 1.52],
 845         6: [2.39, 1.19],
 846         8: [2.13, 1.07],
 847         12: [1.29, 0.76],
 848     }
 849 
 850     thread_angles = {
 851         "L100": [30, 30],
 852         "M100": [10, 40],
 853         "L103": [30, 30],
 854         "M103": [10, 40],
 855         "L110": [30, 30],
 856         "M110": [10, 50],
 857         "L200": [30, 30],
 858         "M200": [10, 40],
 859         "L400": [30, 30],
 860         "M400": [10, 45],
 861         "L410": [30, 30],
 862         "M410": [10, 45],
 863         "L415": [30, 30],
 864         "M415": [10, 45],
 865         "L425": [30, 30],
 866         "M425": [10, 45],
 867         "L444": [30, 30],
 868         "M444": [10, 45],
 869     }
 870 
 871     # {finish:[min turns,[diameters,...]]}
 872     # fmt: off
 873     finish_data = {
 874         100: [1.125,[22,24,28,30,33,35,38]],
 875         103: [1.125,[26]],
 876         110: [1.125,[28]],
 877         200: [1.5,[24.28]],
 878         400: [1.0,[18,20,22,24,28,30,33,35,38,40,43,45,48,51,53,58,60,63,66,70,75,77,83,89,100,110,120]],
 879         410: [1.5,[18,20,22,24,28]],
 880         415: [2.0,[13,15,18,20,22,24,28,30,33]],
 881         425: [2.0,[13,15]],
 882         444: [1.125,[24,28,30,33,35,38,40,43,45,48,51,53,58,60,63,66,70,75,77,83]]
 883     }
 884     # fmt: on
 885 
 886     # {thread_size:[max,min,TPI]}
 887     thread_dimensions = {
 888         13: [13.06, 12.75, 12],
 889         15: [14.76, 14.45, 12],
 890         18: [17.88, 17.47, 8],
 891         20: [19.89, 19.48, 8],
 892         22: [21.89, 21.49, 8],
 893         24: [23.88, 23.47, 8],
 894         26: [25.63, 25.12, 8],
 895         28: [27.64, 27.13, 6],
 896         30: [28.62, 28.12, 6],
 897         33: [32.13, 31.52, 6],
 898         35: [34.64, 34.04, 6],
 899         38: [37.49, 36.88, 6],
 900         40: [40.13, 39.37, 6],
 901         43: [42.01, 41.25, 6],
 902         45: [44.20, 43.43, 6],
 903         48: [47.50, 46.74, 6],
 904         51: [49.99, 49.10, 6],
 905         53: [52.50, 51.61, 6],
 906         58: [56.49, 55.60, 6],
 907         60: [59.49, 58.60, 6],
 908         63: [62.51, 61.62, 6],
 909         66: [65.51, 64.62, 6],
 910         70: [69.49, 68.60, 6],
 911         75: [73.99, 73.10, 6],
 912         77: [77.09, 76.20, 6],
 913         83: [83.01, 82.12, 5],
 914         89: [89.18, 88.29, 5],
 915         100: [100.00, 99.11, 5],
 916         110: [110.01, 109.12, 5],
 917         120: [119.99, 119.10, 5],
 918     }
 919 
 920     @property
 921     def cq_object(self):
 922         """A cadquery Solid thread as defined by class attributes"""
 923         warn("cq_object will be deprecated.", DeprecationWarning, stacklevel=2)
 924         return Solid(self.wrapped)
 925 
 926     def __init__(
 927         self,
 928         size: str,
 929         external: bool = True,
 930         hand: Literal["right", "left"] = "right",
 931         manufacturingCompensation: float = 0.0,
 932     ):
 933         self.size = size
 934         self.external = external
 935         if hand not in ["right", "left"]:
 936             raise ValueError(f'hand must be one of "right" or "left" not {hand}')
 937         self.hand = hand
 938         size_match = re.match(r"([LM])(\d+)SP(\d+)", size)
 939         if not size_match:
 940             raise ValueError(
 941                 "size invalid, must match \
 942                     [L|M][diameter(mm)]SP[100|103|110|200|400|410|415:425|444]"
 943             )
 944         self.style = size_match.group(1)
 945         self.diameter = int(size_match.group(2))
 946         self.finish = int(size_match.group(3))
 947         if self.finish not in PlasticBottleThread.finish_data.keys():
 948             raise ValueError(
 949                 f"finish ({self.finish}) invalid, must be one of"
 950                 f" {list(PlasticBottleThread.finish_data.keys())}"
 951             )
 952         if not self.diameter in PlasticBottleThread.finish_data[self.finish][1]:
 953             raise ValueError(
 954                 f"diameter ({self.diameter}) invalid, must be one"
 955                 f" of {PlasticBottleThread.finish_data[self.finish][1]}"
 956             )
 957         (diameter_max, diameter_min, self.tpi) = PlasticBottleThread.thread_dimensions[
 958             self.diameter
 959         ]
 960         if self.style == "L":
 961             (
 962                 self.root_width,
 963                 thread_height,
 964             ) = PlasticBottleThread.l_style_thread_dimensions[self.tpi]
 965         else:
 966             (
 967                 self.root_width,
 968                 thread_height,
 969             ) = PlasticBottleThread.m_style_thread_dimensions[self.tpi]
 970         if self.external:
 971             self.apex_radius = diameter_min / 2 - manufacturingCompensation
 972             self.root_radius = (
 973                 diameter_min / 2 - thread_height - manufacturingCompensation
 974             )
 975         else:
 976             self.root_radius = diameter_max / 2 + manufacturingCompensation
 977             self.apex_radius = (
 978                 diameter_max / 2 - thread_height + manufacturingCompensation
 979             )
 980         self.thread_angles = PlasticBottleThread.thread_angles[
 981             self.style + str(self.finish)
 982         ]
 983         shoulders = [thread_height * tan(radians(a)) for a in self.thread_angles]
 984         self.apex_width = self.root_width - sum(shoulders)
 985         self.apex_offset = shoulders[0] + self.apex_width / 2 - self.root_width / 2
 986         if not self.external:
 987             self.apex_offset = -self.apex_offset
 988         self.pitch = 25.4 * MM / self.tpi
 989         self.length = (
 990             PlasticBottleThread.finish_data[self.finish][0] + 0.75
 991         ) * self.pitch
 992         cq_object = Thread(
 993             apex_radius=self.apex_radius,
 994             apex_width=self.apex_width,
 995             root_radius=self.root_radius,
 996             root_width=self.root_width,
 997             pitch=self.pitch,
 998             length=self.length,
 999             apex_offset=self.apex_offset,
1000             hand=self.hand,
1001             end_finishes=("fade", "fade"),
1002         )
1003         super().__init__(cq_object.wrapped)

 

 

https://cq-warehouse.readthedocs.io/en/latest/thread.html#isothread

标签:end,thread,apex,self,radius,fade,Cq
From: https://www.cnblogs.com/arwen-xu/p/17965030

相关文章

  • python多线程模块:threading使用方法(参数传递)
    先来看这段代码:importthreadingimporttimedefworker():print“worker”time.sleep(1)returnforiinxrange(5):t=threading.Thread(target=worker)t.start()这段代码就使用了多线程,但是没法传递参数,而实际使用多线程,往往是需要传递参数的......
  • 多线程(Java.Thread)学习
    多线程(Java.Thread)学习线程简介:1、线程是独立执行的路径2、程序运行时有很多后台进程比如主线程、young.gc、full.gc()3、main是主线程,系统入口,用于执行整个程序4、一个进程中、如果开辟多个线程,线程的运行由调度器安排调度、调度器的先后顺序不能人为干预5、对同一份资......
  • ThreadLocal真的会造成内存泄漏吗?
    ThreadLoca在并发场景中,应用非常多。那ThreadLocal是不是真的会造成内存泄漏?今天给大家做一个分享,个人见解,仅供参考。1、ThreadLocal的基本原理简单介绍一下ThreadLocal,在多线程并发访问同一个共享变量的情况下,如果不做同步控制的话,就可能会导致数据不一致的问题,所以,我们需要使......
  • 多线程(Java.Thread)学习
    多线程(Java.Thread)学习线程简介:1、线程是独立执行的路径2、程序运行时有很多后台进程比如主线程、young.gc、full.gc()3、main是主线程,系统入口,用于执行整个程序4、一个进程中、如果开辟多个线程,线程的运行由调度器安排调度、调度器的先后顺序不能人为干预5、对同一份资......
  • [ 20230308 CQYC省选模拟赛 T2 ] 塑料内存条
    题意给定\(n\)个不可重集,初始每个集合\(i\)有元素\(c_i\)。请你以下\(3\)种操作:1xy在集合\(x\)插入\(y\)。2xy将\(y\)集合所有数插入\(x\),并删除\(y\)集合(不影响别的集合的下标)3xy求\(x\)集合与\(y\)集合的交之和。Sol可塑性记忆。注意到前......
  • 全网最详细的线程池 ThreadPoolExecutor 详解,建议收藏!
    一、ThreadPoolExecutor类讲解1、线程池状态:五种状态:线程池的shutdown()方法,将线程池由RUNNING(运行状态)转换为SHUTDOWN状态线程池的shutdownNow()方法,将线程池由RUNNING或SHUTDOWN状态转换为STOP状态。注:SHUTDOWN状态和STOP状态先会转变为TIDYING状态,最终......
  • 从数据库读写分离到CQRS
    1.数据库读写分离对于数据库的操作就四种:CRUD我们把这四种操作,又划分为两类,读和写 当我们的系统并发量高的时候,自然会考虑到提高数据库性能,数据库读写分离, 但是,实际测试下来,总是有各种不满意的地方。其中最麻烦的就是各种复杂查询的性能,写库有单点故障问题2.CQRS有了......
  • Thread 之 interrupt、isInterrupted、interrupted 方法
    interrupt():打断sleep、wait、join的线程会抛出InterruptedException异常并清除打断标记,如果打断正在运行的线程、park的线程则会重新设置打断标记isInterrupted():不会清除打断标记interrupted():会清除打断标记一、调用interrupt()方法中断正在运行的线程@Slf4j......
  • Thread 之 join() 方法
    案例代码@Slf4jpublicclassJoinDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadt1=newThread(()->{log.info("{}线程启动",Thread.currentThread().getName());try{......
  • 测试SuspendThread、ResumeThread
    #include<iostream>#include<windows.h>#include<process.h>#include<conio.h>enum{ EVT_PAUSE=0, EVT_RESUME, EVT_QUIT, EVT_TOTAL};staticHANDLEevents[EVT_TOTAL]={NULL,NULL,NULL};staticunsignedint__stdcallhe......