-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathblend.go
197 lines (176 loc) · 4.66 KB
/
blend.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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// Package blend implements parsing of Blender files.
package blend
import (
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"os"
"strconv"
"github.com/mewspring/blend/block"
)
// Blend represents the information contained within a blend file. It contains a
// file header and a slice of file blocks.
type Blend struct {
Hdr *Header
Blocks []*block.Block
// io.Closer of the underlying file.
io.Closer
}
// Parse parsers the provided blend file. An *io.SectionReader is stored in each
// individual block body. By default, blocks must be parsed manually using the
// ParseBody method. Use ParseAll for a complete parsing of the blend file,
// including all block bodies.
//
// Caller should close b when done reading from it.
func Parse(filePath string) (b *Blend, err error) {
b = new(Blend)
f, err := os.Open(filePath)
if err != nil {
return nil, err
}
b.Closer = f
// Parse file header.
b.Hdr, err = ParseHeader(f)
if err != nil {
return nil, err
}
// Parse file blocks.
for {
blk, err := block.Parse(f, b.Hdr.Order, b.Hdr.PtrSize)
if err != nil {
return nil, err
}
if blk.Hdr.Code == block.CodeENDB {
break
}
_, ok := block.Addr[blk.Hdr.OldAddr]
if ok {
return nil, fmt.Errorf("blend.Parse: multiple occurances of struct address %#x", blk.Hdr.OldAddr)
}
block.Addr[blk.Hdr.OldAddr] = blk
b.Blocks = append(b.Blocks, blk)
}
return b, nil
}
// ParseAll parses the blend file and all block bodies.
//
// ParseAll closes b when done reading from it.
func ParseAll(filePath string) (b *Blend, err error) {
// Parse file header and block headers.
b, err = Parse(filePath)
if err != nil {
return nil, err
}
defer b.Close()
// Parse DNA block.
dna, err := b.GetDNA()
if err != nil {
return nil, err
}
// Parse block bodies.
for _, blk := range b.Blocks {
blk.ParseBody(b.Hdr.Order, dna)
}
return b, nil
}
// GetDNA locates, parses and returns the DNA block.
func (b *Blend) GetDNA() (dna *block.DNA, err error) {
for _, blk := range b.Blocks {
dna, ok := blk.Body.(*block.DNA)
if ok {
// DNA block already parsed.
return dna, nil
}
if blk.Hdr.Code == block.CodeDNA1 {
// Parse the DNA block body and store it in blk.Body.
r, ok := blk.Body.(io.Reader)
if !ok {
return nil, errors.New("Blend.GetDNA: unable to locate DNA block body reader")
}
dna, err = block.ParseDNA(r, b.Hdr.Order)
if err != nil {
return nil, err
}
blk.Body = dna
return dna, nil
}
}
return nil, errors.New("Blend.GetDNA: unable to locate DNA block")
}
// A Header is present at the beginning of each blend file.
type Header struct {
// Pointer size.
PtrSize int
// Byte order.
Order binary.ByteOrder
// Blender version.
Ver int
}
// ParseHeader parses and returns the header of a blend file.
//
// Example file header:
// "BLENDER_V100"
// // 0-6 magic ("BLENDER")
// // 7 pointer size ("_" or "-")
// // 8 endianness ("V" or "v")
// // 9-11 version ("100")
func ParseHeader(r io.Reader) (hdr *Header, err error) {
buf := make([]byte, 12)
_, err = io.ReadFull(r, buf)
if err != nil {
return nil, err
}
// File identifier.
magic := string(buf[0:7])
if magic != "BLENDER" {
return nil, fmt.Errorf("blend.ParseHeader: invalid file identifier %q", magic)
}
// Pointer size.
hdr = new(Header)
size := buf[7]
switch size {
case '_':
// _ = 4 byte pointer
hdr.PtrSize = 4
case '-':
// - = 8 byte pointer
hdr.PtrSize = 8
default:
return nil, fmt.Errorf("blend.ParseHeader: invalid pointer size character '%c'", size)
}
// Byte order.
order := buf[8]
switch order {
case 'v':
// v = little endian
hdr.Order = binary.LittleEndian
case 'V':
// V = big endian
hdr.Order = binary.BigEndian
default:
return nil, fmt.Errorf("blend.ParseHeader: invalid byte order character '%c'", order)
}
// Version.
hdr.Ver, err = strconv.Atoi(string(buf[9:12]))
if err != nil {
return nil, fmt.Errorf("blend.ParseHeader: invalid version; %s", err)
}
if WarnVersion {
if hdr.Ver != block.BlenderVer {
log.Printf("Warning: Version mismatch between file (v%d) and block package (v%d).\n", hdr.Ver, block.BlenderVer)
if hdr.Ver < block.BlenderVer {
log.Println("The file's Blender version is too old. Use Blender [1] to open and resave the file.")
log.Println("[1]: http://www.blender.org/")
} else {
log.Println("The block package Blender version is too old. Use blendef [1] to regenerate the block package.")
log.Println("[1]: go get github.com/mewspring/blend/cmd/blendef")
}
}
}
return hdr, nil
}
// When WarnVersion is set to true, ParseHeader will report warnings if the
// Blender version of the blend file and the block package differs.
var WarnVersion = true