
sdfx
Repository is archived in favor of https://github.com/soypat/sdf. This repository will not be removed as long as the rewrite commit remains useful for reference: https://github.com/soypat/sdfx/commit/eeb4ade34961fbe42bb2fe164cdb819f7d033aca
A rewrite of the original CAD package sdfx
for generating 2D and 3D geometry using Go. See Why was this package rewritten?
- Objects are modelled with 2d and 3d signed distance functions (SDFs).
- Objects are defined with Go code.
- Objects are rendered to an STL file to be viewed and/or 3d printed.
How To
- See the examples.
- Write some Go code to define your own object.
- Build and run the Go code.
- Preview the STL output in an STL viewer (e.g. http://www.meshlab.net/)
- Print the STL file if you like it enough.
Why was sdfx rewritten?
The original sdfx
package is amazing. I thank deadsy for putting all that great work into making an amazing tool I use daily. That said, there are some things that were not compatible with my needs:
sdfx is needlessly slow. Here is a benchmark rendering a threaded bolt:
$ go test -bench=. -benchmem ./render/
goos: linux
goarch: amd64
pkg: github.com/soypat/sdfx/render
cpu: AMD Ryzen 5 3400G with Radeon Vega Graphics
BenchmarkLegacy-8 2 831917874 ns/op 62468752 B/op 466469 allocs/op
BenchmarkRenderer-8 2 530473109 ns/op 320487584 B/op 146134 allocs/op
PASS
ok github.com/soypat/sdfx/render 4.702s
Legacy
is the original sdfx
implementation.
Questionable API design
The vector math functions are methods which yield hard to follow operations. i.e:
return bb.Min.Add(bb.Size().Mul(i.ToV3().DivScalar(float64(node.meshSize)).
Div(node.cellCounts.ToV3().DivScalar(float64(node.meshSize))))) // actual code from original sdfx.
A more pressing issue was the Renderer3
interface definition method, Render
type Renderer3 interface {
// ...
Render(s sdf.SDF3, meshCells int, output chan<- *Triangle3)
}
This presented a few problems:
-
Raises many questions about usage of the function Render- who closes the channel? Does this function block? Do I have to call it as a goroutine?
-
To implement a renderer one needs to bake in concurrency which is a hard thing to get right from the start. This also means all rendering code besides having the responsibility of computing geometry, it also has to handle concurrency features of the language. This leads to rendering functions with dual responsibility- compute geometry and also handle the multi-core aspect of the computation making code harder to maintain in the long run
-
Using a channel to send individual triangles is probably a bottleneck.
-
I would liken meshCells
to an implementation detail of the renderer used. This can be passed as an argument when instantiating the renderer used.
-
Who's to say we have to limit ourselves to signed distance functions? With the new proposed Renderer
interface this is no longer the case.
That said there are some minor changes I'd also like to make. Error handling in Go is already one of the major pain points, and there is no reason to bring it to sdfx
in full force for simple shape generation. See the following code from sdfx
:
// Cylinder3D return an SDF3 for a cylinder (rounded edges with round > 0).
func Cylinder3D(height, radius, round float64) (SDF3, error) {
if radius <= 0 {
return nil, ErrMsg("radius <= 0")
}
if round < 0 {
return nil, ErrMsg("round < 0")
}
if round > radius {
return nil, ErrMsg("round > radius")
}
if height < 2.0*round {
return nil, ErrMsg("height < 2 * round")
}
//...
An error on a function like Cylinder3D
can only be handled one way really: correcting the argument to it in the source code as one generates the shape! This is even implied with the implementation of the ErrMsg
function: it includes the line number of the function that yielded the error. panic
already does that and saves us having to formally handle the error message.