Skip to content

Commit

Permalink
fix threads.Bolt
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Aug 25, 2024
1 parent ff7bf9c commit 15f34f9
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 80 deletions.
136 changes: 136 additions & 0 deletions examples/bolt/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package main

import (
"bufio"
"bytes"
"flag"
"fmt"
"log"
"os"
"runtime"
"time"

"github.com/soypat/gsdf"
"github.com/soypat/gsdf/forge/threads"
"github.com/soypat/gsdf/glbuild"
"github.com/soypat/gsdf/gleval"
"github.com/soypat/gsdf/glrender"
)

const visualization = "bolt.glsl"
const stl = "bolt.stl"

var useGPU = false

func init() {
flag.BoolVar(&useGPU, "gpu", useGPU, "Enable GPU usage")
flag.Parse()
if useGPU {
fmt.Println("enabled GPU usage")
runtime.LockOSThread() // For when using GPU this is required.
}
}

// scene generates the 3D object for rendering.
func scene() (gleval.SDF3, error) {
const L, shank = 5, 3
threader := threads.ISO{D: 3, P: 0.5, Ext: true}
M3, err := threads.Bolt(threads.BoltParams{
Thread: threader,
Style: threads.NutHex,
TotalLength: L + shank,
ShankLength: shank,
})
if err != nil {
return nil, err
}
return makeSDF(M3)
}

func main() {
if useGPU {
terminate, err := gleval.Init1x1GLFW()
if err != nil {
log.Fatal("failed to start GLFW", err.Error())
}
defer terminate()
}
sceneStart := time.Now()
sdf, err := scene()
if err != nil {
fmt.Println("error making scene:", err)
os.Exit(1)
}
elapsedScene := time.Since(sceneStart)
const resDiv = 200
const evaluationBufferSize = 1024 * 8
resolution := sdf.Bounds().Size().Max() / resDiv
renderer, err := glrender.NewOctreeRenderer(sdf, resolution, evaluationBufferSize)
if err != nil {
fmt.Println("error creating renderer:", err)
os.Exit(1)
}
start := time.Now()
triangles, err := glrender.RenderAll(renderer)
if err != nil {
fmt.Println("error rendering triangles:", err)
os.Exit(1)
}
elapsed := time.Since(start)
evals := sdf.(interface{ Evaluations() uint64 }).Evaluations()

fp, err := os.Create(stl)
if err != nil {
fmt.Println("error creating file:", err)
os.Exit(1)
}
defer fp.Close()
start = time.Now()
w := bufio.NewWriter(fp)
_, err = glrender.WriteBinarySTL(w, triangles)
if err != nil {
fmt.Println("error writing triangles to file:", err)
os.Exit(1)
}
w.Flush()
fmt.Println("SDF created in ", elapsedScene, "evaluated sdf", evals, "times, rendered", len(triangles), "triangles in", elapsed, "wrote file in", time.Since(start))
}

func makeSDF(s glbuild.Shader3D) (gleval.SDF3, error) {
err := glbuild.RewriteNames3D(&s, 32) // Shorten names to not crash GL tokenizer.
if err != nil {
return nil, err
}
if visualization != "" {
const sceneSize = 0.5
// We include the bounding box in the visualization.
bb := s.Bounds()
envelope, err := gsdf.NewBoundsBoxFrame(bb)
if err != nil {
return nil, err
}
visual := gsdf.Union(s, envelope)
// Scale size and translate to center so visualization is in camera range.
center := bb.Center()
visual = gsdf.Translate(visual, center.X, center.Y, center.Z)
visual = gsdf.Scale(visual, sceneSize/bb.Size().Max())
source := new(bytes.Buffer)
_, err = glbuild.NewDefaultProgrammer().WriteFragVisualizerSDF3(source, visual)
if err != nil {
return nil, err
}
err = os.WriteFile(visualization, source.Bytes(), 0666)
if err != nil {
return nil, err
}
}
if useGPU {
source := new(bytes.Buffer)
_, err := glbuild.NewDefaultProgrammer().WriteComputeSDF3(source, s)
if err != nil {
return nil, err
}
return gleval.NewComputeGPUSDF3(source, s.Bounds())
}
return gleval.NewCPUSDF3(s)
}
2 changes: 1 addition & 1 deletion examples/npt-flange/flange.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func scene() (gleval.SDF3, error) {
return nil, err
}

pipe, _ := threads.Nut(threads.NutParms{
pipe, _ := threads.Nut(threads.NutParams{
Thread: npt,
Style: threads.NutCircular,
})
Expand Down
52 changes: 20 additions & 32 deletions forge/threads/bolt.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"github.com/soypat/gsdf/glbuild"
)

// BoltParms defines the parameters for a bolt.
type BoltParms struct {
// BoltParams defines the parameters for a bolt.
type BoltParams struct {
Thread Threader
Style NutStyle // head style "hex" or "knurl"
Tolerance float32 // subtract from external thread radius
Expand All @@ -18,16 +18,16 @@ type BoltParms struct {
}

// Bolt returns a simple bolt suitable for 3d printing.
func Bolt(k BoltParms) (s glbuild.Shader3D, err error) {
func Bolt(k BoltParams) (s glbuild.Shader3D, err error) {
switch {
case k.Thread == nil:
err = errors.New("nil Threader")
case k.TotalLength < 0:
err = errors.New("total length < 0")
case k.ShankLength >= k.TotalLength:
err = errors.New("shank length must be less than total length")
case k.ShankLength < 0:
err = errors.New("shank length < 0")
case k.ShankLength <= 0:
err = errors.New("shank length <= 0")
case k.Tolerance < 0:
err = errors.New("tolerance < 0")
}
Expand All @@ -45,42 +45,30 @@ func Bolt(k BoltParms) (s glbuild.Shader3D, err error) {
}
switch k.Style {
case NutHex:
head, _ = HexHead(hr, hh, "b")
head, _ = HexHead(hr, hh, false, true) // Round top side only.
case NutKnurl:
head, _ = KnurledHead(hr, hh, hr*0.25)
default:
return nil, errors.New("unknown style for bolt: " + k.Style.String())
}

// shank
shankLength := k.ShankLength + hh/2
shankOffset := shankLength / 2
shank, err := gsdf.NewCylinder(param.Radius, shankLength, hh*0.08)
screwLen := k.TotalLength - k.ShankLength
screw, err := Screw(screwLen, k.Thread)
if err != nil {
return nil, err
}
shank = gsdf.Translate(shank, 0, 0, shankOffset)

// external thread
threadLength := k.TotalLength - k.ShankLength
if threadLength < 0 {
threadLength = 0
}
var thread glbuild.Shader3D
if threadLength != 0 {
thread, err = Screw(threadLength, k.Thread)
if err != nil {
return nil, err
}
// chamfer the thread
thread, err = chamferedCylinder(thread, 0, 0.5)
if err != nil {
return nil, err
}
threadOffset := threadLength/2 + shankLength
thread = gsdf.Translate(thread, 0, 0, threadOffset)
shank, err := gsdf.NewCylinder(param.Radius, k.ShankLength, hh*0.08)
if err != nil {
return nil, err
}
return gsdf.Union(gsdf.Union(head, shank), thread), nil
shankOff := k.ShankLength/2 + hh/2
shank = gsdf.Translate(shank, 0, 0, shankOff)
screw = gsdf.Translate(screw, 0, 0, shankOff+screwLen/2)
// Does not work:
// screw, err = chamferedCylinder(screw, 0, 0.5)
// if err != nil {
// return nil, err
// }
return gsdf.Union(screw, gsdf.SmoothUnion(shank, head, hh*0.12)), nil
}

// chamferedCylinder intersects a chamfered cylinder with an SDF3.
Expand Down
13 changes: 7 additions & 6 deletions forge/threads/hexhead.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import (
// Hex Heads for nuts and bolts.

// HexHead3D returns the rounded hex head for a nut or bolt.
// - round rounding control (t)top, (b)bottom, (tb)top/bottom
func HexHead(radius float32, height float32, round string) (s glbuild.Shader3D, err error) {
// Ability to round positive side and/or negative side of hex head
// provided. By convention the negative side is the top of the hex in this package.
func HexHead(radius float32, height float32, roundNeg, roundPos bool) (s glbuild.Shader3D, err error) {
// basic hex body
cornerRound := radius * 0.08
var poly ms2.PolygonBuilder
Expand All @@ -28,18 +29,18 @@ func HexHead(radius float32, height float32, round string) (s glbuild.Shader3D,
hex3d, _ := gsdf.Extrude(hex2d, height)

// round out the top and/or bottom as required
if round != "" {
if roundPos || roundNeg {
topRound := radius * 1.6
d := radius * math.Cos(30.0*math.Pi/180.0)
d := radius * cosd30
sphere, err := gsdf.NewSphere(topRound)
if err != nil {
return nil, err
}
zOfs := math.Sqrt(topRound*topRound-d*d) - height/2
if round == "t" || round == "tb" {
if roundNeg {
hex3d = gsdf.Intersection(hex3d, gsdf.Translate(sphere, 0, 0, -zOfs))
}
if round == "b" || round == "tb" {
if roundPos {
hex3d = gsdf.Intersection(hex3d, gsdf.Translate(sphere, 0, 0, zOfs))
}
}
Expand Down
10 changes: 5 additions & 5 deletions forge/threads/iso.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
)

const (
cosd30 = sqrt3 / 2 // math.Cos(30*math.Pi/180)
sind30 = 0.5 // math.Sin(30*math.Pi/180)
sqrt2d2 = math32.Sqrt2 / 2
sqrt3 = 1.7320508075688772935274463415058723669428052538103806280558069794
)
Expand Down Expand Up @@ -36,17 +38,15 @@ func (iso ISO) Thread() (glbuild.Shader2D, error) {
radius := iso.D / 2
// Trig functions for 30 degrees, the thread angle of ISO.
const (
cosTheta = sqrt3 / 2
sinTheta = 0.5
tanTheta = sinTheta / cosTheta
tanTheta = sind30 / cosd30
)
h := iso.P / (2.0 * tanTheta)
rMajor := radius
r0 := rMajor - (7.0/8.0)*h
var poly ms2.PolygonBuilder
if iso.Ext {
// External threeading.
rRoot := (iso.P / 8.0) / cosTheta
rRoot := (iso.P / 8.0) / cosd30
xOfs := (1.0 / 16.0) * iso.P
poly.AddXY(iso.P, 0)
poly.AddXY(iso.P, r0+h)
Expand All @@ -59,7 +59,7 @@ func (iso ISO) Thread() (glbuild.Shader2D, error) {
} else {
// Internal threading.
rMinor := r0 + (1.0/4.0)*h
rCrest := (iso.P / 16.0) / cosTheta
rCrest := (iso.P / 16.0) / cosd30
xOfs := (1.0 / 8.0) * iso.P
poly.AddXY(iso.P, 0)
poly.AddXY(iso.P, rMinor)
Expand Down
12 changes: 6 additions & 6 deletions forge/threads/nut.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ func (c NutStyle) String() (str string) {
return str
}

// NutParms defines the parameters for a nut.
type NutParms struct {
// NutParams defines the parameters for a nut.
type NutParams struct {
Thread Threader
Style NutStyle
Tolerance float32 // add to internal thread radius
}

// Nut returns a simple nut suitable for 3d printing.
func Nut(k NutParms) (s glbuild.Shader3D, err error) {
func Nut(k NutParams) (s glbuild.Shader3D, err error) {
switch {
case k.Thread == nil:
err = errors.New("nil threader")
Expand All @@ -58,14 +58,14 @@ func Nut(k NutParms) (s glbuild.Shader3D, err error) {
return nil, errors.New("bad hex nut dimensions")
}
switch k.Style {
case NutHex: // TODO error handling
nut, err = HexHead(nr, nh, "tb")
case NutHex:
nut, err = HexHead(nr, nh, true, true)
case NutKnurl:
nut, err = KnurledHead(nr, nh, nr*0.25)
case NutCircular:
nut, err = gsdf.NewCylinder(nr*1.1, nh, 0)
default:
err = errors.New("passed argument CylinderStyle not defined for Nut")
err = errors.New("passed argument NutStyle not defined for Nut")
}
if err != nil {
return nil, err
Expand Down
7 changes: 4 additions & 3 deletions forge/threads/plasticbuttress.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package threads

import (
math "github.com/chewxy/math32"
"github.com/soypat/glgl/math/ms2"
"github.com/soypat/gsdf"
"github.com/soypat/gsdf/glbuild"
Expand All @@ -25,8 +24,10 @@ func (butt PlasticButtress) ThreadParams() Parameters {
// radius is radius of thread. pitch is thread-to-thread distance.
func (butt PlasticButtress) Thread() (glbuild.Shader2D, error) {
radius := butt.D / 2
t0 := math.Tan(45.0 * math.Pi / 180)
t1 := math.Tan(7.0 * math.Pi / 180)
const (
t0 = 1.0 // math.Tan(45.0 * math.Pi / 180)
t1 = 0.1227845609029046 // math.Tan(7.0 * math.Pi / 180)
)
const threadEngage = 0.6 // thread engagement

h0 := butt.P / (t0 + t1)
Expand Down
Loading

0 comments on commit 15f34f9

Please sign in to comment.