This repository has been archived by the owner on Aug 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
thread.go
110 lines (100 loc) · 3.08 KB
/
thread.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package thread
import (
"errors"
"math"
"github.com/soypat/sdf"
"gonum.org/v1/gonum/spatial/r2"
"gonum.org/v1/gonum/spatial/r3"
)
// Screws
// Screws are made by taking a 2D thread profile, rotating it about the z-axis and
// spiralling it upwards as we move along z.
//
// The 2D thread profiles are a polygon of a single thread centered on the y-axis with
// the x-axis as the screw axis. Most thread profiles are symmetric about the y-axis
// but a few aren't (E.g. buttress threads) so in general we build the profile of
// an entire pitch period.
//
// This code doesn't deal with thread tolerancing. If you want threads to fit properly
// the radius of the thread will need to be tweaked (+/-) to give internal/external thread
// clearance.
type Threader interface {
Thread() (sdf.SDF2, error)
ThreadParams() Parameters
}
type ScrewParameters struct {
Length float64
Taper float64
}
// screw is a 3d screw form.
type screw struct {
thread sdf.SDF2 // 2D thread profile
pitch float64 // thread to thread distance
lead float64 // distance per turn (starts * pitch)
length float64 // total length of screw
taper float64 // thread taper angle
// starts int // number of thread starts
bb r3.Box // bounding box
}
// Screw returns a screw SDF3.
// - length of screw
// - thread taper angle (radians)
// - pitch thread to thread distance
// - number of thread starts (< 0 for left hand threads)
func Screw(length float64, thread Threader) (sdf.SDF3, error) {
if thread == nil {
return nil, errors.New("nil threader")
}
if length <= 0 {
return nil, errors.New("need greater than zero length")
}
tsdf, err := thread.Thread()
if err != nil {
return nil, err
}
params := thread.ThreadParams()
s := screw{}
s.thread = tsdf
s.pitch = params.Pitch
s.length = length / 2
s.taper = params.Taper
s.lead = -s.pitch * float64(params.Starts)
// Work out the bounding box.
// The max-y axis of the sdf2 bounding box is the radius of the thread.
bb := s.thread.Bounds()
r := bb.Max.Y
// add the taper increment
r += s.length * math.Tan(s.taper)
s.bb = r3.Box{Min: r3.Vec{X: -r, Y: -r, Z: -s.length}, Max: r3.Vec{X: r, Y: r, Z: s.length}}
return &s, nil
}
// Evaluate returns the minimum distance to a 3d screw form.
func (s *screw) Evaluate(p r3.Vec) float64 {
// map the 3d point back to the xy space of the profile
p0 := r2.Vec{}
// the distance from the 3d z-axis maps to the 2d y-axis
p0.Y = math.Sqrt(p.X*p.X + p.Y*p.Y)
if s.taper != 0 {
p0.Y += p.Z * math.Atan(s.taper)
}
// the x/y angle and the z-height map to the 2d x-axis
// ie: the position along thread pitch
theta := math.Atan2(p.Y, p.X)
z := p.Z + s.lead*theta/(2*math.Pi)
p0.X = sawTooth(z, s.pitch)
// get the thread profile distance
d0 := s.thread.Evaluate(p0)
// create a region for the screw length
d1 := math.Abs(p.Z) - s.length
// return the intersection
return math.Max(d0, d1)
}
// BoundingBox returns the bounding box for a 3d screw form.
func (s *screw) Bounds() r3.Box {
return s.bb
}
func sawTooth(x, period float64) float64 {
x += period / 2
t := x / period
return period*(t-math.Floor(t)) - period/2
}