commit 37cdc242d989d6d6baac447c38509085bac82cb3 Author: dqn Date: Wed Jun 5 01:43:08 2024 +0800 sss diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..608d3c6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}" + } + ] +} \ No newline at end of file diff --git a/cmd/stl2gltf.go b/cmd/stl2gltf.go new file mode 100644 index 0000000..bff2ceb --- /dev/null +++ b/cmd/stl2gltf.go @@ -0,0 +1,13 @@ +package main + +import ( + "git.dengqn.com/dqn/stl2gltf" +) + +func main() { + stl := stl2gltf.Load("D:\\src\\go\\stl2gltf\\example\\0c448109-7ac9-4f6d-aaf6-ce6bb744032f.stl") + // log.Println(stl) + + stl2gltf.Convert(stl, "C:\\Users\\13040\\Downloads\\0c448109-7ac9-4f6d-aaf6-ce6bb744032f.gltf") + +} diff --git a/convert.go b/convert.go new file mode 100644 index 0000000..01aac17 --- /dev/null +++ b/convert.go @@ -0,0 +1,134 @@ +package stl2gltf + +import ( + "encoding/base64" + "encoding/binary" + "encoding/json" + "fmt" + "log" + "os" +) + +func Convert(stl STL, outFile string) { + // buf + indexBuf := make([]byte, 0) + normalBuf := make([]byte, 0) + positionBuf := make([]byte, 0) + + padding := func(buf []byte) []byte { + log.Println("padding: ", len(buf)%4) + for len(buf)%4 != 0 { + log.Println("padding") + buf = append(buf, 0x00) + } + return buf + } + + // 写入顶点 + for i := range stl.TriangleNum { + t := stl.Triangles[i] + positionBuf = append(positionBuf, t.Position...) + } + positionBuf = padding(positionBuf) + + // 写入法向量 + for i := range stl.TriangleNum { + t := stl.Triangles[i] + normalBuf = append(normalBuf, t.Normal...) + normalBuf = append(normalBuf, t.Normal...) + normalBuf = append(normalBuf, t.Normal...) + } + normalBuf = padding(normalBuf) + // 写入索引 + for i := range stl.TriangleNum * 3 { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(i)) + indexBuf = append(indexBuf, buf...) + } + indexBuf = padding(indexBuf) + + log.Println(len(positionBuf), len(normalBuf), len(indexBuf)) + + allData := make([]byte, 0) + allData = append(allData, positionBuf...) + allData = append(allData, normalBuf...) + allData = append(allData, indexBuf...) + + base := base64.StdEncoding.EncodeToString(allData) + + attributes := make(map[string]int, 0) + attributes["POSITION"] = 0 + attributes["NORMAL"] = 1 + + gltf := GLTF{ + Asset: Asset{Version: "2.0"}, + Scenes: []Scene{{Nodes: []int{0}}}, + Nodes: []Node{{Mesh: 0}}, + Meshes: []Mesh{{Primitives: []Primitive{{Attributes: attributes, Indices: 2, Mode: 4}}}}, + Buffers: []Buffer{{Uri: fmt.Sprintf("data:application/gltf-buffer;base64,%s", base), ByteLength: len(allData)}}, + BufferViews: []BufferView{ + // position + { + Buffer: 0, + ByteOffset: 0, + ByteLength: len(positionBuf), + Target: 34962, + }, + // normal + { + Buffer: 0, + ByteOffset: len(positionBuf), + ByteLength: len(normalBuf), + Target: 34962, + }, + // index + { + Buffer: 0, + ByteOffset: len(positionBuf) + len(normalBuf), + ByteLength: len(indexBuf), + Target: 34963, + }, + }, + Accessors: []Accessor{ + { + BufferView: 0, + ByteOffset: 0, + ComponentType: 5126, + Type: "VEC3", + Count: len(positionBuf) / 12, + Max: stl.Max, + Min: stl.Min, + }, + { + BufferView: 1, + ByteOffset: 0, + ComponentType: 5126, + Type: "VEC3", + Count: int(stl.TriangleNum), + // Max: []float32{1, 1, 1}, + // Min: []float32{-1, -1, -1}, + }, + { + BufferView: 2, + ByteOffset: 0, + ComponentType: 5123, + Type: "SCALAR", + Count: stl.TriangleNum, + }, + }, + } + + data, err := json.MarshalIndent(gltf, " ", " ") + if err != nil { + log.Println("marshal error: ", err.Error()) + return + } + os.Remove(outFile) + f, err := os.OpenFile(outFile, os.O_CREATE, os.ModePerm) + if err != nil { + log.Println("write file error: ", err.Error()) + return + } + defer f.Close() + f.Write(data) +} diff --git a/data.go b/data.go new file mode 100644 index 0000000..5ee0fae --- /dev/null +++ b/data.go @@ -0,0 +1,77 @@ +package stl2gltf + +// Triangle 50B +type Triangle struct { + Normal []byte // 4 * 3 12 + Position []byte // 4 * 3 * 3 36 + Attribute []byte // 2 02 +} + +type STL struct { + Header string + TriangleNum int + Triangles []Triangle + + Max []float32 + Min []float32 +} + +type Asset struct { + Version string `json:"version"` +} + +type Scene struct { + Nodes []int `json:"nodes"` +} + +type Node struct { + Mesh int `json:"mesh"` +} + +type Primitive struct { + Attributes map[string]int `json:"attributes"` + Indices int `json:"indices"` + Mode int `json:"mode"` +} + +type Mesh struct { + Primitives []Primitive `json:"primitives"` +} + +type Buffer struct { + Uri string `json:"uri"` + ByteLength int `json:"byteLength"` +} + +type BufferView struct { + Buffer int `json:"buffer"` + ByteOffset int `json:"byteOffset"` + ByteLength int `json:"byteLength"` + Target int `json:"target"` +} + +type Accessor struct { + BufferView int `json:"bufferView"` + ByteOffset int `json:"byteOffset"` + ComponentType int `json:"componentType"` + Count int `json:"count"` + Type string `json:"type"` + Max []float32 `json:"max,omitempty"` + Min []float32 `json:"min,omitempty"` +} + +type GLTF struct { + Asset Asset `json:"asset"` + + Scenes []Scene `json:"scenes"` + + Nodes []Node `json:"nodes"` + + Meshes []Mesh `json:"meshes"` + + Buffers []Buffer `json:"buffers"` + + BufferViews []BufferView `json:"bufferViews"` + + Accessors []Accessor `json:"accessors"` +} diff --git a/example/0c448109-7ac9-4f6d-aaf6-ce6bb744032f.stl b/example/0c448109-7ac9-4f6d-aaf6-ce6bb744032f.stl new file mode 100644 index 0000000..6b9be43 Binary files /dev/null and b/example/0c448109-7ac9-4f6d-aaf6-ce6bb744032f.stl differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..47254b9 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.dengqn.com/dqn/stl2gltf + +go 1.22.0 diff --git a/load.go b/load.go new file mode 100644 index 0000000..ab1aa1f --- /dev/null +++ b/load.go @@ -0,0 +1,72 @@ +package stl2gltf + +import ( + "encoding/binary" + "log" + "math" + "os" +) + +func Load(filePath string) (stl STL) { + file, err := os.OpenFile(filePath, os.O_RDONLY, os.ModePerm) + if err != nil { + log.Panic("open file " + filePath + " error: " + err.Error()) + } + headerBuf := make([]byte, 80) + file.Read(headerBuf) + triangleNumBuf := make([]byte, 4) + file.Read(triangleNumBuf) + + stl.Header = string(headerBuf) + stl.TriangleNum = int(binary.LittleEndian.Uint32(triangleNumBuf)) + + stl.Triangles = make([]Triangle, 0) + + max := []float32{0, 0, 0} + min := []float32{0, 0, 0} + for range stl.TriangleNum { + normalBuf := make([]byte, 4*3) + file.Read(normalBuf) + + positionBuf := make([]byte, 4*3*3) + file.Read(positionBuf) + + for r := range 3 { + x := math.Float32frombits(binary.LittleEndian.Uint32(positionBuf[:4+(r*4)])) + y := math.Float32frombits(binary.LittleEndian.Uint32(positionBuf[(r * 4) : (r*4)+4])) + z := math.Float32frombits(binary.LittleEndian.Uint32(positionBuf[(r*4)+4 : (r*4)+8])) + if max[0] < x { + max[0] = x + } + if max[1] < y { + max[1] = y + } + if max[2] < z { + max[2] = z + } + if min[0] > x { + min[0] = x + } + if min[1] > y { + min[1] = y + } + if min[2] > z { + min[2] = z + } + } + + attributeBuf := make([]byte, 2) + file.Read(attributeBuf) + + stl.Triangles = append(stl.Triangles, Triangle{ + Normal: normalBuf, + Position: positionBuf, + Attribute: attributeBuf, + }) + } + + stl.Max = max + stl.Min = min + + return stl +}