blender-3d 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 14b1b6b25b75bc3144579623d6ebaf1eb20ed295
4
+ data.tar.gz: 94d505a329ebe729c2970ad9cf563829b365bcf4
5
+ SHA512:
6
+ metadata.gz: b10f4977194b4c18d5790b7bcb2aaf2674404751bf0aa63843eaafbd6df47626ad5db875099e6a5f090f2e4e49a2b76258ed97ce84db43eb9fb5afa4ac1a491f
7
+ data.tar.gz: 4eea3fb816ffb01d0fcf08e1a77a7ba7cad124a813dcc953b837bb7b7c2e64feba455f62635053fc3c033b0c7aec11f97a7014bcd1cd728c3885481ec2610e00
@@ -0,0 +1,31 @@
1
+ require 'stringio'
2
+
3
+ require_relative 'blender-3d/version'
4
+ require_relative 'blender-3d/object_reader'
5
+
6
+ require_relative 'blender-3d/pointer'
7
+ require_relative 'blender-3d/types'
8
+ require_relative 'blender-3d/field'
9
+ require_relative 'blender-3d/char_field_parser'
10
+ require_relative 'blender-3d/generic_field_parser'
11
+ require_relative 'blender-3d/field_parser_factory'
12
+
13
+ require_relative 'blender-3d/structure'
14
+ require_relative 'blender-3d/structure_definition'
15
+
16
+ require_relative 'blender-3d/file_header'
17
+ require_relative 'blender-3d/file_block'
18
+ require_relative 'blender-3d/dna_block'
19
+ require_relative 'blender-3d/model'
20
+
21
+
22
+ =begin
23
+
24
+ Usage:
25
+
26
+ require 'blender-3d'
27
+ # require_relative 'blender-3d'
28
+
29
+ model = Blender3d::Model.from_file('cube.blend');
30
+
31
+ =end
@@ -0,0 +1,39 @@
1
+ module Blender3d
2
+ class CharFieldParser
3
+ def initialize(name)
4
+ @name = name
5
+ end
6
+
7
+ def parse
8
+ type = case @name
9
+ when GenericFieldParser::FUNCTION_POINTER
10
+ FunctionPointerType.new(SimpleType.new(FieldParserFactory::CHAR_TYPE))
11
+
12
+ when GenericFieldParser::POINTER_ARRAY
13
+ array_size = $2.to_i
14
+ ArrayType.new(NullTerminatedStringType.new, array_size)
15
+
16
+ when GenericFieldParser::MULTI_POINTER
17
+ PointerType.new(NullTerminatedStringType.new)
18
+
19
+ when GenericFieldParser::POINTER
20
+ NullTerminatedStringType.new
21
+
22
+ when GenericFieldParser::MULTI_ARRAY
23
+ array_size, string_size = $2.to_i, $3.to_i
24
+ ArrayType.new(FixedLengthStringType.new(string_size), array_size)
25
+
26
+ when GenericFieldParser::ARRAY
27
+ array_size = $2.to_i
28
+ FixedLengthStringType.new(array_size)
29
+
30
+ else
31
+ SimpleType.new(FieldParserFactory::CHAR_TYPE)
32
+
33
+ end
34
+
35
+ name = $1 || @name
36
+ Field.new(type, name)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,74 @@
1
+ module Blender3d
2
+ class DnaBlock
3
+ attr_accessor :code, :types, :structures
4
+
5
+ def initialize(reader = nil)
6
+ deserialize(reader) if reader
7
+ end
8
+
9
+ def deserialize(reader)
10
+ Reader.new(reader, self).read
11
+ end
12
+
13
+ class Reader
14
+ def initialize(reader, dna_block = DnaBlock.new)
15
+ @reader, @dna_block = reader, dna_block
16
+ end
17
+
18
+ def read
19
+ @dna_block.code = @reader.read(4)
20
+ @names = read_names
21
+ read_types
22
+ read_structures
23
+ @dna_block
24
+ end
25
+
26
+ private
27
+
28
+ def read_names
29
+ @reader.read(4) # identifier 'NAME'
30
+ count = @reader.read_int32
31
+ count.times.map { @reader.read_string.chop.freeze }
32
+ end
33
+
34
+ def read_types
35
+ read_aligned # identifier 'TYPE' 4 byte aligned
36
+
37
+ count = @reader.read_int32
38
+ types = count.times.map { @reader.read_string.chop.freeze }
39
+
40
+ read_aligned # identifier 'TLEN' 4 byte aligned
41
+ lengths = count.times.map { @reader.read_int16 }
42
+
43
+ @dna_block.types = types.zip(lengths)
44
+ end
45
+
46
+ def read_aligned
47
+ offset = 4 + (( 4 - (@reader.tell % 4) ) % 4)
48
+ @reader.read(offset)
49
+ end
50
+
51
+ def read_structures
52
+ read_aligned # identifier 'STRC' 4 byte aligned
53
+ count = @reader.read_int32
54
+ @dna_block.structures = count.times.map { read_structure }
55
+ end
56
+
57
+ def read_structure
58
+ type_index = @reader.read_int16
59
+ num_fields = @reader.read_int16
60
+
61
+ fields = num_fields.times.map { read_field }
62
+
63
+ StructureDefinition.new(*@dna_block.types[type_index], fields)
64
+ end
65
+
66
+ def read_field
67
+ field_type = @dna_block.types[@reader.read_int16][0]
68
+ field_name = @names[@reader.read_int16]
69
+ factory = FieldParserFactory.create_for(field_type, field_name)
70
+ factory.parse
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,17 @@
1
+ module Blender3d
2
+ class Field
3
+ attr_reader :type, :name
4
+
5
+ def initialize(type, name)
6
+ @type, @name = type, name.to_sym
7
+ end
8
+
9
+ def to_s
10
+ "Field[#@name](#@type)"
11
+ end
12
+
13
+ def inspect
14
+ "#{@type.inspect} #@name"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module Blender3d
2
+ module FieldParserFactory
3
+ CHAR_TYPE = 'char'.freeze
4
+
5
+ def self.create_for(type, name)
6
+ return CharFieldParser.new(name) if type == CHAR_TYPE
7
+ GenericFieldParser.new(type, name)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,44 @@
1
+ module Blender3d
2
+ class FileBlock
3
+ attr_accessor :code, :size, :pointer, :type_index, :count, :data, :type
4
+
5
+ def initialize(reader = nil)
6
+ deserialize(reader) if reader
7
+ end
8
+
9
+ def deserialize(reader)
10
+ Reader.new(reader, self).read
11
+ end
12
+
13
+ def parse_data(model)
14
+ file = StringIO.new(self.data)
15
+ reader = model.create_reader(file)
16
+ self.data = self.count.times.map { self.type.read(reader) }
17
+ end
18
+
19
+ class Reader
20
+ def initialize(reader, file_block = FileBlock.new)
21
+ @reader, @file_block = reader, file_block
22
+ end
23
+
24
+ def read
25
+ @file_block.code = @reader.read(4).gsub(/\0.*$/, '')
26
+ @file_block.size = @reader.read_uint32
27
+ @file_block.pointer = Pointer.new(@reader.read_pointer)
28
+ @file_block.type_index = @reader.read_uint32
29
+ @file_block.count = @reader.read_uint32
30
+ @file_block.data = @reader.read(@file_block.size)
31
+ read_dna if @file_block.code == 'DNA1'
32
+ @file_block
33
+ end
34
+
35
+ private def read_dna
36
+ file = StringIO.new(@file_block.data)
37
+ @reader = @reader.model.create_reader(file)
38
+ @file_block.data = DnaBlock.new(@reader)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+
@@ -0,0 +1,41 @@
1
+ module Blender3d
2
+ class FileHeader
3
+ attr_accessor :identifier,
4
+ :version
5
+
6
+ attr_reader :pointer_size, :size_code,
7
+ :endianness, :endian_code
8
+
9
+ def initialize(file = nil)
10
+ deserialize(file) if file
11
+ end
12
+
13
+ def deserialize(file)
14
+ self.identifier = file.read(7)
15
+ self.pointer_size = file.read(1) == '-'.freeze ? 8 : 4
16
+ self.endianness = file.read(1) == 'v'.freeze ? :little : :big
17
+ self.version = file.read(1) + '.'.freeze + file.read(2)
18
+ self
19
+ end
20
+
21
+ def pointer_size=(value)
22
+ @pointer_size = value
23
+ @size_code = @pointer_size == 8 ? 'Q'.freeze : 'L'.freeze
24
+ end
25
+
26
+ def endianness=(value)
27
+ @endianness = value
28
+ @endian_code = little_endian? ? '<'.freeze : '>'.freeze
29
+ end
30
+
31
+ def little_endian?
32
+ @endianness == :little
33
+ end
34
+
35
+ def big_endian?
36
+ !little_endian?
37
+ end
38
+ end
39
+ end
40
+
41
+
@@ -0,0 +1,54 @@
1
+ module Blender3d
2
+ class GenericFieldParser
3
+ FUNCTION_POINTER = /^\(\*([^\)]+)\)\(\)$/
4
+ POINTER_ARRAY = /^\*([^\[]+)\[(\d+)\]$/
5
+ MULTI_POINTER = /^\*{2}(.+)$/
6
+ POINTER = /^\*(.+)$/
7
+ MULTI_ARRAY = /^([^\[]+)\[(\d+)\]\[(\d+)\]$/
8
+ ARRAY = /^([^\[]+)\[(\d+)\]$/
9
+
10
+ def initialize(type, name)
11
+ @type, @name = type, name
12
+ end
13
+
14
+ def parse
15
+ type = case @name
16
+ when FUNCTION_POINTER
17
+ FunctionPointerType.new(@type)
18
+
19
+ when POINTER_ARRAY
20
+ array_size = $2.to_i
21
+ ArrayType.new(build_pointer_type, array_size)
22
+
23
+ when MULTI_POINTER
24
+ PointerType.new(build_pointer_type)
25
+
26
+ when POINTER
27
+ build_pointer_type
28
+
29
+ when MULTI_ARRAY
30
+ array1_size, array2_size = $2.to_i, $3.to_i
31
+ ArrayType.new(ArrayType.new(build_simple_type, array2_size), array1_size)
32
+
33
+ when ARRAY
34
+ array_size = $2.to_i
35
+ ArrayType.new(build_simple_type, array_size)
36
+
37
+ else
38
+ build_simple_type
39
+
40
+ end
41
+
42
+ name = $1 || @name
43
+ Field.new(type, name)
44
+ end
45
+
46
+ private def build_pointer_type
47
+ PointerType.new(build_simple_type)
48
+ end
49
+
50
+ private def build_simple_type
51
+ SimpleType.new(@type)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,56 @@
1
+ module Blender3d
2
+ class Model
3
+ attr_reader :header, :blocks, :pointers
4
+
5
+ def self.from_file(path)
6
+ File.open(path, 'rb') { |file| new(file) }
7
+ end
8
+
9
+ def initialize(file = nil)
10
+ @blocks = []
11
+ deserialize(file) if file
12
+ end
13
+
14
+ def deserialize(file)
15
+ @header = FileHeader.new(file)
16
+ reader = create_reader(file)
17
+ @blocks.clear
18
+
19
+ loop do
20
+ @blocks << FileBlock.new(reader)
21
+ break if reader.tell == reader.file.size
22
+ end
23
+
24
+ dna_block = self.dna_block
25
+ return self unless dna_block
26
+
27
+ @blocks.select { |block| block.type_index != 0 }.each do |block|
28
+ block.type = dna_block.data.structures[block.type_index]
29
+ block.parse_data(self)
30
+ end
31
+
32
+ @pointers = {}
33
+ @blocks.each do |block|
34
+ if block.data.is_a?(Array)
35
+ pointer = block.pointer.location
36
+ block.data.each do |data|
37
+ @pointers[Pointer.new(pointer)] = data
38
+ pointer += data.class.definition.size
39
+ end
40
+ else
41
+ @pointers[block.pointer] = block.data
42
+ end
43
+ end
44
+
45
+ self
46
+ end
47
+
48
+ def dna_block
49
+ @blocks.find { |block| block.code == 'DNA1' }
50
+ end
51
+
52
+ def create_reader(file)
53
+ ObjectReader.new(file, self)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,47 @@
1
+ module Blender3d
2
+ class ObjectReader
3
+ attr_reader :file
4
+ attr_accessor :model
5
+
6
+ def initialize(file, model = nil)
7
+ @file, @model = file, model
8
+ endian = @model.header.endian_code
9
+
10
+ %w'c s l q'.each.with_index do |code, i|
11
+ bytes = 1 << i
12
+ bits = 8 * bytes
13
+ name = 'int' + bits.to_s
14
+ define_reader name, bytes, code + endian
15
+ define_reader 'u' + name, bytes, code.upcase + endian
16
+ end
17
+
18
+ code = @model.header.little_endian? ? 'e' : 'g'
19
+ define_reader 'float', 4, code
20
+ define_reader 'double', 8, code.upcase
21
+
22
+ define_reader 'pointer', @model.header.pointer_size, @model.header.size_code + endian
23
+ end
24
+
25
+ private def define_reader(name, num_bytes, unpack_type)
26
+ instance_eval "def read_#{name}; @file.read(#{num_bytes}).unpack('#{unpack_type}').first end"
27
+ end
28
+
29
+ def read(length)
30
+ @file.read(length)
31
+ end
32
+
33
+ def read_string
34
+ @file.gets("\0")
35
+ end
36
+
37
+ def seek(position)
38
+ @file.seek(position)
39
+ end
40
+
41
+ def tell
42
+ @file.tell
43
+ end
44
+ end
45
+ end
46
+
47
+
@@ -0,0 +1,27 @@
1
+ module Blender3d
2
+ class Pointer
3
+ attr_reader :location
4
+
5
+ def initialize(location)
6
+ @location = location
7
+ end
8
+
9
+ def to_s
10
+ "Pointer(#{inspect})"
11
+ end
12
+
13
+ def inspect
14
+ return '0x0' if @location == 0
15
+ return '0x%08x' % @location if @location < (1 << 32)
16
+ '0x%016x' % @location
17
+ end
18
+
19
+ def <=>(other)
20
+ other.is_a?(Pointer) ? location <=> other.location : 1
21
+ end
22
+
23
+ def ==(other)
24
+ (self <=> other) == 0
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ module Blender3d
2
+ class Structure
3
+ class << self
4
+ attr_reader :definition
5
+ end
6
+
7
+ def initialize(reader = nil)
8
+ deserialize(reader) if reader
9
+ end
10
+
11
+ def deserialize(reader)
12
+ self.class.definition.fields.each do |field|
13
+ value = field.type.read(reader)
14
+ instance_variable_set "@#{field.name}", value
15
+ end
16
+ self
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ module Blender3d
2
+ class StructureDefinition
3
+ attr_reader :name, :size, :fields
4
+
5
+ def initialize(name, size, fields = [])
6
+ name = name[0].upcase + name[1..-1]
7
+ @name, @size, @fields = name, size, fields
8
+ end
9
+
10
+ def read(reader)
11
+ structure_class.new(reader)
12
+ end
13
+
14
+ def structure_class
15
+ Blender3d.const_defined?(name, false) ? Blender3d.const_get(name, false) : define_structure_class
16
+ end
17
+
18
+ private
19
+
20
+ def define_structure_class
21
+ fields = self.fields
22
+ struct_class = Class.new(Structure) { attr_accessor(*fields.map(&:name)) }
23
+ struct_class.instance_variable_set :@definition, self
24
+ Blender3d.const_set(name, struct_class)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,140 @@
1
+ module Blender3d
2
+ class SimpleType
3
+ attr_reader :name
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+
9
+ def to_s
10
+ @name
11
+ end
12
+
13
+ def inspect
14
+ @name
15
+ end
16
+
17
+ def read(reader)
18
+ case @name
19
+ when 'char' then reader.read(1)
20
+ when 'double' then reader.read_double
21
+ when 'float' then reader.read_float
22
+ when 'int' then reader.read_int32
23
+ when 'int64_t' then reader.read_int64
24
+ when 'long' then reader.read_int32
25
+ when 'short' then reader.read_int16
26
+ when 'uchar' then reader.read_uint8
27
+ when 'uint64_t' then reader.read_uint64
28
+ when 'ulong' then reader.read_uint32
29
+ when 'ushort' then reader.read_uint16
30
+ else
31
+ dna = reader.model.dna_block.data
32
+ struct = dna.structures.find { |struct| struct.name == @name }
33
+ return struct.read(reader) if struct
34
+ length = dna.types.find { |name, size| name == @name }&.last
35
+ return reader.read(length) if length
36
+ raise 'type not found'
37
+ end
38
+ end
39
+ end
40
+
41
+ class PointerType
42
+ attr_reader :type
43
+
44
+ def initialize(type)
45
+ @type = type
46
+ end
47
+
48
+ def to_s
49
+ "Pointer(#@type)"
50
+ end
51
+
52
+ def inspect
53
+ "#{@type.inspect}*"
54
+ end
55
+
56
+ def read(reader)
57
+ Pointer.new(reader.read_pointer)
58
+ end
59
+ end
60
+
61
+ class FunctionPointerType
62
+ attr_reader :return_type
63
+
64
+ def initialize(return_type)
65
+ @return_type = return_type
66
+ end
67
+
68
+ def to_s
69
+ "Function(#@return_type)"
70
+ end
71
+
72
+ def inspect
73
+ "#{@return_type.inspect} (*)()"
74
+ end
75
+
76
+ def read(reader)
77
+ Pointer.new(reader.read_pointer)
78
+ end
79
+ end
80
+
81
+ # equivalent to PointerType<SimpleType<char>> (char*)
82
+ class NullTerminatedStringType
83
+ def to_s
84
+ "NullTerminatedString"
85
+ end
86
+
87
+ def inspect
88
+ 'char*'
89
+ end
90
+
91
+ def read(reader)
92
+ reader.read_string.chop
93
+ end
94
+ end
95
+
96
+ # equivalent to ArrayType<SimpleType<char>, length> (char[length])
97
+ class FixedLengthStringType
98
+ attr_reader :length
99
+
100
+ def initialize(length)
101
+ @length = length
102
+ end
103
+
104
+ def to_s
105
+ "FixedLengthString(#@length)"
106
+ end
107
+
108
+ def inspect
109
+ "char[#@length]"
110
+ end
111
+
112
+ def read(reader)
113
+ reader.read(@length).gsub(/\0.*$/, '')
114
+ end
115
+ end
116
+
117
+ class ArrayType
118
+ attr_reader :type, :size
119
+
120
+ def initialize(type, size)
121
+ @type, @size = type, size
122
+ end
123
+
124
+ def dimensions
125
+ @sizes.size
126
+ end
127
+
128
+ def to_s
129
+ "Array[#@size](#@type)"
130
+ end
131
+
132
+ def inspect
133
+ "#{@type.inspect}[#@size]"
134
+ end
135
+
136
+ def read(reader)
137
+ @size.times.map { @type.read(reader) }
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,3 @@
1
+ module Blender3d
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blender-3d
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - SilverPhoenix99
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Blender 3d file parser.
14
+ email:
15
+ - silver.phoenix99@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/blender-3d.rb
21
+ - lib/blender-3d/char_field_parser.rb
22
+ - lib/blender-3d/dna_block.rb
23
+ - lib/blender-3d/field.rb
24
+ - lib/blender-3d/field_parser_factory.rb
25
+ - lib/blender-3d/file_block.rb
26
+ - lib/blender-3d/file_header.rb
27
+ - lib/blender-3d/generic_field_parser.rb
28
+ - lib/blender-3d/model.rb
29
+ - lib/blender-3d/object_reader.rb
30
+ - lib/blender-3d/pointer.rb
31
+ - lib/blender-3d/structure.rb
32
+ - lib/blender-3d/structure_definition.rb
33
+ - lib/blender-3d/types.rb
34
+ - lib/blender-3d/version.rb
35
+ homepage:
36
+ licenses:
37
+ - MIT
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 2.6.6
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Blender 3d file parser.
59
+ test_files: []