diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | geometry.go | 20 | ||||
-rw-r--r-- | go.mod | 8 | ||||
-rw-r--r-- | go.sum | 4 | ||||
-rw-r--r-- | graphics.go | 151 | ||||
-rw-r--r-- | main.go | 50 | ||||
-rw-r--r-- | stl.go | 66 |
8 files changed, 304 insertions, 1 deletions
@@ -1 +1,5 @@ archive +vendor +stlscope +*.FCStd +*.stl @@ -11,6 +11,6 @@ This little program will allow you to view STL files from your terminal. ## Expected Usage ``` -$ stlscope model.stl +$ stlscope -file model.stl ``` diff --git a/geometry.go b/geometry.go new file mode 100644 index 0000000..c9453a9 --- /dev/null +++ b/geometry.go @@ -0,0 +1,20 @@ +// vim: shiftwidth=4 tabstop=4 noexpandtab + +package main + +type Point struct { + x float32 + y float32 + z float32 +} + +type Triangle struct { + a *Point + b *Point + c *Point +} + +type Surface struct { + triangles []*Triangle +} + @@ -0,0 +1,8 @@ +module src.xengineering.eu/xengineering/stlscope + +go 1.16 + +require ( + github.com/go-gl/gl v0.0.0-20210315015930-ae072cafe09d + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 +) @@ -0,0 +1,4 @@ +github.com/go-gl/gl v0.0.0-20210315015930-ae072cafe09d h1:o81yRlBATU4PRn97lydmsq8hTRNXI4wlR/VvUQhFRVY= +github.com/go-gl/gl v0.0.0-20210315015930-ae072cafe09d/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 h1:QrUfZrT8n72FUuiABt4tbu8PwDnOPAbnj3Mql1UhdRI= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= diff --git a/graphics.go b/graphics.go new file mode 100644 index 0000000..812be3a --- /dev/null +++ b/graphics.go @@ -0,0 +1,151 @@ +// vim: shiftwidth=4 tabstop=4 noexpandtab + +package main + +import ( + "fmt" + "log" + "strings" + + "github.com/go-gl/gl/v4.6-core/gl" + "github.com/go-gl/glfw/v3.3/glfw" +) + +var ( + // a single triangle + triangle = []float32{ + 0.0, 0.5, 0.0, + -0.5, -0.5, 0.0, + 0.5, -0.5, 0.0, + } +) + +const ( + WINDOW_WIDTH = 500 + WINDOW_HEIGHT = 500 + WINDOW_TITLE = "stlscope" + + // vertex shader to draw points + VERTEX_SHADER = ` + #version 410 + in vec3 vp; + void main() { + gl_Position = vec4(vp, 1.0); + } + ` + "\x00" + + // fragment shader to draw surfaces + FRAGMENT_SHADER = ` + #version 410 + out vec4 frag_colour; + void main() { + frag_colour = vec4(0, 0, 1, 1); // RGBA color format + } + ` + "\x00" +) + +func initGlfw() *glfw.Window { + log.Println("GLFW init") + + if err := glfw.Init(); err != nil { + panic(err) + } + + glfw.WindowHint(glfw.Resizable, glfw.False) + glfw.WindowHint(glfw.ContextVersionMajor, 4) + glfw.WindowHint(glfw.ContextVersionMinor, 6) + glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) + glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) + glfw.WindowHint(glfw.Samples, 16) // anti-aliasing + + window, err := glfw.CreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE, nil, nil) + if err != nil { + panic(err) + } + window.MakeContextCurrent() + + return window +} + +// initOpenGL initializes OpenGL and returns an intiialized program +func initOpenGL() uint32 { + log.Println("OpenGL init") + + if err := gl.Init(); err != nil { + panic(err) + } + version := gl.GoStr(gl.GetString(gl.VERSION)) + log.Println("OpenGL version", version) + + vertexShader, err := compileShader(VERTEX_SHADER, gl.VERTEX_SHADER) + if err != nil { + panic(err) + } + fragmentShader, err := compileShader(FRAGMENT_SHADER, gl.FRAGMENT_SHADER) + if err != nil { + panic(err) + } + + prog := gl.CreateProgram() + gl.AttachShader(prog, vertexShader) + gl.AttachShader(prog, fragmentShader) + gl.LinkProgram(prog) + return prog +} + +func draw(vao uint32, window *glfw.Window, program uint32) { + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + gl.UseProgram(program) + + gl.BindVertexArray(vao) + gl.DrawArrays(gl.TRIANGLES, 0, int32(len(triangle)/3)) + + glfw.PollEvents() + window.SwapBuffers() +} + +// makeVao initializes and returns a vertex array from the points provided. +func makeVao(points []float32) uint32 { + log.Println("Creating VAO") + + // start with vertex buffer object (VBO): + var vbo uint32 // VBO ID + gl.GenBuffers(1, &vbo) + gl.BindBuffer(gl.ARRAY_BUFFER, vbo) + gl.BufferData(gl.ARRAY_BUFFER, 4*len(points), gl.Ptr(points), gl.STATIC_DRAW) + + // vertex array objects (VAO) are a feature of newer GL implementations: + var vao uint32 // VAO ID + gl.GenVertexArrays(1, &vao) + gl.BindVertexArray(vao) + gl.EnableVertexAttribArray(0) + gl.BindBuffer(gl.ARRAY_BUFFER, vbo) + gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil) // tell GL to use 3D float vectors + + return vao +} + +func compileShader(source string, shaderType uint32) (uint32, error) { + log.Println("Compiling shader") + + shader := gl.CreateShader(shaderType) + + csources, free := gl.Strs(source) + gl.ShaderSource(shader, 1, csources, nil) + free() + gl.CompileShader(shader) + + var status int32 + gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status) + if status == gl.FALSE { + var logLength int32 + gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength) + + log := strings.Repeat("\x00", int(logLength+1)) + gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log)) + + return 0, fmt.Errorf("failed to compile %v: %v", source, log) + } + + return shader, nil +} @@ -0,0 +1,50 @@ +// vim: shiftwidth=4 tabstop=4 noexpandtab + +package main + +import ( + "runtime" + "log" + "flag" + + "github.com/go-gl/glfw/v3.3/glfw" +) + +var ( + stlFilePath string +) + +func main() { + + // read command line arguments + parseFlags() + + // parse STL file + _, err := ReadBinaryStlFile(stlFilePath) + if err != nil { + log.Fatal(err) + } + + // lock this program to one OS thread (details: https://golang.org/pkg/runtime/#LockOSThread) + log.Println("Locking OS thread") + runtime.LockOSThread() + + // init GLFW and assert termination at end of main + window := initGlfw() + defer glfw.Terminate() + + // init OpenGL + program := initOpenGL() + + vao := makeVao(triangle) + + // main loop + for !window.ShouldClose() { + draw(vao, window, program) + } +} + +func parseFlags() { + flag.StringVar(&stlFilePath, "file", "myfile.stl", "path to the binary STL file") + flag.Parse() +} @@ -0,0 +1,66 @@ +// vim: shiftwidth=4 tabstop=4 noexpandtab + +package main + +import ( + "io/ioutil" + "log" + "encoding/binary" + "math" +) + +type BinaryStl struct { + header []byte + numberOfTriangles uint32 + surface Surface +} + +func ReadBinaryStlFile(filePath string) (BinaryStl, error) { + + fileContent, err := ioutil.ReadFile(filePath) + if err != nil { + return BinaryStl{}, err + } + + model := BinaryStl{} + model.surface = Surface{} + + model.header = fileContent[0:80] + log.Printf("STL header: '%s'\n", string(model.header)) + + model.numberOfTriangles = binary.LittleEndian.Uint32(fileContent[80:84]) + log.Printf("STL model has %d triangles\n", model.numberOfTriangles) + + var i uint32 + for i = 0; i < model.numberOfTriangles; i++ { // for each expected triangle + start := 84 + i*50 // 50 bytes is length of one triangle + end := 84 + (i+1)*50 + model.surface.triangles = append(model.surface.triangles, + ParseBinaryStlTriangle(fileContent[start:end])) + } + + return model,nil +} + +func ParseBinaryStlTriangle(data []byte) *Triangle { // FIXME: This function should only accept 50 byte slices/arrays + + triangle := new(Triangle) + triangle.a = new(Point) + triangle.b = new(Point) + triangle.c = new(Point) + + triangle.a.x = math.Float32frombits(binary.LittleEndian.Uint32(data[12:16])) + triangle.a.y = math.Float32frombits(binary.LittleEndian.Uint32(data[16:20])) + triangle.a.z = math.Float32frombits(binary.LittleEndian.Uint32(data[20:24])) + + triangle.b.x = math.Float32frombits(binary.LittleEndian.Uint32(data[24:28])) + triangle.b.y = math.Float32frombits(binary.LittleEndian.Uint32(data[28:32])) + triangle.b.z = math.Float32frombits(binary.LittleEndian.Uint32(data[32:36])) + + triangle.c.x = math.Float32frombits(binary.LittleEndian.Uint32(data[36:40])) + triangle.c.y = math.Float32frombits(binary.LittleEndian.Uint32(data[40:44])) + triangle.c.z = math.Float32frombits(binary.LittleEndian.Uint32(data[44:48])) + + return triangle +} + |