ply 0.9

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.extra_files << "../some/external/dependency.rb"
7
+ #
8
+ # at.libs << ":../some/external"
9
+ #
10
+ # at.add_exception 'vendor'
11
+ #
12
+ # at.add_mapping(/dependency.rb/) do |f, _|
13
+ # at.files_matching(/test_.*rb$/)
14
+ # end
15
+ #
16
+ # %w(TestA TestB).each do |klass|
17
+ # at.extra_class_map[klass] = "test/test_misc.rb"
18
+ # end
19
+ # end
20
+
21
+ # Autotest.add_hook :run_command do |at|
22
+ # system "rake build"
23
+ # end
data/.gemtest ADDED
File without changes
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ === 0.9.0 / 2013-02-04
2
+
3
+ * Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,28 @@
1
+ == LICENSE:
2
+
3
+ (The BSD 2-clause License)
4
+
5
+ Copyright (c) 2013 Jim Wise
6
+ All rights reserved.
7
+
8
+ Redistribution and use in source and binary forms, with or without
9
+ modification, are permitted provided that the following conditions
10
+ are met:
11
+
12
+ 1. Redistributions of source code must retain the above copyright
13
+ notice, this list of conditions and the following disclaimer.
14
+ 2. Redistributions in binary form must reproduce the above copyright
15
+ notice, this list of conditions and the following disclaimer in the
16
+ documentation and/or other materials provided with the distribution.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
22
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ POSSIBILITY OF SUCH DAMAGE.
data/Manifest.txt ADDED
@@ -0,0 +1,15 @@
1
+ .autotest
2
+ History.txt
3
+ LICENSE.txt
4
+ Manifest.txt
5
+ README.txt
6
+ README.rdoc
7
+ Rakefile
8
+ bin/ply2ascii
9
+ examples/cube_ascii.ply
10
+ examples/cube_binary_big_endian.ply
11
+ examples/cube_binary_little_endian.ply
12
+ examples/examples.rb
13
+ lib/ply.rb
14
+ test/test_ply.rb
15
+ test/test_ply2ascii.rb
data/README.rdoc ADDED
@@ -0,0 +1,227 @@
1
+ = ply
2
+
3
+ https://github.com/jimwise/ply
4
+
5
+ Author:: Jim Wise (mailto:jwise@draga.com)
6
+ Copyright:: Copyright (c) 2013 Jim Wise
7
+ License:: 2-clause BSD-Style (see LICENSE.txt)
8
+
9
+ == DESCRIPTION:
10
+
11
+ Ply is a ruby gem for reading Stanford PLY-format 3D model files.
12
+
13
+ The PLY file format is a flexible format for storing semi-structured binary data,
14
+ and is often used to stored polygonalized 3D models generated with range
15
+ scanning hardware. You can find some examples of the format at the the
16
+ {Stanford 3D Scanning Repository}[http://graphics.stanford.edu/data/3Dscanrep/].
17
+
18
+ Ply provides a simple API for quick access to the data in a PLY file
19
+ (including examining the structure of a particular file's content), and
20
+ an almost-as-simple event-driven API which can be used to process extremely
21
+ large ply files in a streaming fashion, without needing to keep the full
22
+ dataset represented in the file in memory. Ply handles all three types of
23
+ PLY files (ascii, binary-big-endian and binary-little-endian).
24
+
25
+ If you don't have any Stanford PLY files on hand, you probably don't need
26
+ this gem, but if you're curious, the PLY file format, described at
27
+ Wikipedia[http://en.wikipedia.org/wiki/PLY_(file_format)]
28
+
29
+
30
+ == REQUIREMENTS:
31
+
32
+ Ply currently requires Ruby 1.9.3 -- if you have a need to run Ply with Ruby
33
+ 1.8.7, drop me an email[mailto:jwise@draga.com], and I'll look into
34
+ backporting it. Ply has no other dependencies.
35
+
36
+ == INSTALL:
37
+
38
+ $ gem install ply
39
+
40
+ == SYNOPSIS:
41
+
42
+ === How to Use This Gem
43
+
44
+ To get started, include this gem using
45
+
46
+ require 'ply'
47
+
48
+ This gem provides the Ply module. This module provides a single class,
49
+ Ply::PlyFile, which can be used directly to parse a PLY file into memory, or
50
+ subclassed to take advantage of Ply's event-driven API for handling large
51
+ PLY files.
52
+
53
+ === The Simple API
54
+
55
+ To parse a PLY file into memory, simply instantiate the Ply::PlyFile class,
56
+ passing either the name of the PLY file or an IO object open on a PLY file
57
+ to Ply::PlyFile.new. Thus this:
58
+
59
+ pf = Ply::PlyFile.new "horse.ply"
60
+
61
+ and this:
62
+
63
+ pf = Ply::PlyFile.new File.new("horse.ply")
64
+
65
+ do the same thing.
66
+
67
+ The PLY file is parsed at construction time, and provides the following
68
+ read-only accessors:
69
+
70
+ Ply::PlyFile#version::
71
+ The version of the PLY format used in this file, as a String -- at this
72
+ time the only defined PLY version is 1.0.
73
+
74
+ Ply::PlyFile#format::
75
+ The format of this PLY file as a String-- one of +ascii+,
76
+ +binary_big_endian+, or +binary_little_endian+.
77
+
78
+ Ply::PlyFile#elements::
79
+ The structure of the data in this PLY file, as an array of Hashes, in the
80
+ order the elements will appear in the file. Each Hash contains the
81
+ following keys:
82
+
83
+ :name:: the name of this element type
84
+
85
+ :count:: the number of elements of this type in this PLY file
86
+
87
+ :properties::
88
+ an array of Hashes describing the properties of this element
89
+ type. Each Hash in the +:properties+ array contains the following keys:
90
+
91
+ :name:: the name of this property of the current element
92
+
93
+ :type::
94
+ the type of this property (an integral or floating point type, as
95
+ defined in the PLY file format, or +list+. If the current property is
96
+ of type +list+ (a PLY array type), its property Hash also contains the
97
+ following keys:
98
+
99
+ :index_type::
100
+ the type of the index of this list
101
+
102
+ :element_type::
103
+ the type of the elements in this list
104
+
105
+ If you are using the Ply::PlyFile class directly (as opposed to subclassing
106
+ it in order to use Ply's event-driven API for handling large PLY files), an
107
+ additional read-only accessor is available
108
+
109
+ Ply::PlyFile#data::
110
+
111
+ The actual data in this file, in the structure defined by the return value
112
+ of Ply::PlyFile#elements, as a Hash of Arrays of Hashes. This data is returned
113
+ as a Hash keyed by the name of each element type in the file (as a
114
+ String), and containing an array of Hashes for each element type in the
115
+ file, keyed by the property names of that element, as Strings.
116
+
117
+ That's not as complicated as it sounds! If the file +horse.ply+ defines a
118
+ +vertex+ element, with properties +x+, +y+, and +z+, then
119
+
120
+ pf = Ply::PlyFile.new "horse.ply"
121
+ verts = pf.data["vertex"]
122
+
123
+ will return an array of all vertices in the file, and
124
+
125
+ verts[0]["x"]
126
+
127
+ will give you the value of the +x+ property of the first +vertex+ element
128
+ in the file, as an Integer, Float, or Array, depending on the declared
129
+ type of that property in the PLY file.
130
+
131
+ === The Event-Driven API
132
+
133
+ The above API is easy to use, but has the disadvantage that the entire data
134
+ stored in the PLY file must be able to fit in memory, in order to be
135
+ returned by Ply::PlyFile::data. For this reason, a simple event-driven API
136
+ for handling PLY data is also provided. To make use of this, simply
137
+ subclass the class Ply::PlyFile, and provide your own version of the method
138
+ Ply::PlyFile#element_callback. When a new object of your subclass is
139
+ constructed from a PLY file, your implementation of this method will be
140
+ called once for each element in the file, and passed two arguments:
141
+
142
+ * The type of the current element, in the same format as a member of the
143
+ array returned by Ply::PlyFile::elements
144
+
145
+ * The current element, in the same format as the members of the arrays
146
+ returned by Ply::PlyFile::data
147
+
148
+ Note that if your subclass provides a #initialize method, it is your
149
+ responsibility to ensure that PlyFile
150
+
151
+ As an example, the following code defines a subclass of PlyFile which merely
152
+ *counts* the elements of each type present in PLY file, without reading them
153
+ all into memory at the same time:
154
+
155
+ class PlyFileCounter < Ply::PlyFile
156
+ attr_reader :counts
157
+
158
+ def initialize f
159
+ @counts = {}
160
+ super f
161
+ end
162
+
163
+ def element_callback e, v
164
+ @counts[e[:name]] ||= 0
165
+ @counts[e[:name]] = @counts[e[:name]] + 1
166
+ end
167
+ end
168
+
169
+ p = PlyFileCounter.new "horse.ply"
170
+ puts "There are #{p.counts["vertex"]} vertices in the file."
171
+
172
+ === Example Files
173
+
174
+ In addition to the large models in the {Stanford 3D Scanning
175
+ Repository}[http://graphics.stanford.edu/data/3Dscanrep/], the +examples/+
176
+ subdirectory of this gem includes a simple triangulated cube model in all
177
+ three PLY file formats, as well as a script to generate these three files
178
+ which you may modify for your own purposes.
179
+
180
+ === ply2ascii
181
+
182
+ Finally, this gem includes a simple script, +ply2ascii+, which can be used
183
+ to parse any of the three PLY file formats containing a triangulated scene
184
+ with vertices in an element named +vertex+ with (at least) scalar properties
185
+ +x+, +y+, and +z+, and faces in an elemnt named +face+ containing a list
186
+ property +vertex_indices+, and produce a simple ASCII dump of the described
187
+ scene in two files.
188
+
189
+ Run +ply2ascii+ with no arguments for (a little) more information.
190
+
191
+ == DEVELOPERS:
192
+
193
+ After checking out the source, run:
194
+
195
+ $ rake newb
196
+
197
+ This task will install any missing dependencies, run the tests/specs,
198
+ and generate the RDoc.
199
+
200
+ == LICENSE:
201
+
202
+ (The BSD 2-clause License)
203
+
204
+ Copyright (c) 2013 Jim Wise
205
+ All rights reserved.
206
+
207
+ Redistribution and use in source and binary forms, with or without
208
+ modification, are permitted provided that the following conditions
209
+ are met:
210
+
211
+ 1. Redistributions of source code must retain the above copyright
212
+ notice, this list of conditions and the following disclaimer.
213
+ 2. Redistributions in binary form must reproduce the above copyright
214
+ notice, this list of conditions and the following disclaimer in the
215
+ documentation and/or other materials provided with the distribution.
216
+
217
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
218
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
219
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
220
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
221
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
222
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
223
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
224
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
225
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
226
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
227
+ POSSIBILITY OF SUCH DAMAGE.
data/README.txt ADDED
@@ -0,0 +1,227 @@
1
+ = ply
2
+
3
+ https://github.com/jimwise/ply
4
+
5
+ Author:: Jim Wise (mailto:jwise@draga.com)
6
+ Copyright:: Copyright (c) 2013 Jim Wise
7
+ License:: 2-clause BSD-Style (see LICENSE.txt)
8
+
9
+ == DESCRIPTION:
10
+
11
+ Ply is a ruby gem for reading Stanford PLY-format 3D model files.
12
+
13
+ The PLY file format is a flexible format for storing semi-structured binary data,
14
+ and is often used to stored polygonalized 3D models generated with range
15
+ scanning hardware. You can find some examples of the format at the the
16
+ {Stanford 3D Scanning Repository}[http://graphics.stanford.edu/data/3Dscanrep/].
17
+
18
+ Ply provides a simple API for quick access to the data in a PLY file
19
+ (including examining the structure of a particular file's content), and
20
+ an almost-as-simple event-driven API which can be used to process extremely
21
+ large ply files in a streaming fashion, without needing to keep the full
22
+ dataset represented in the file in memory. Ply handles all three types of
23
+ PLY files (ascii, binary-big-endian and binary-little-endian).
24
+
25
+ If you don't have any Stanford PLY files on hand, you probably don't need
26
+ this gem, but if you're curious, the PLY file format, described at
27
+ Wikipedia[http://en.wikipedia.org/wiki/PLY_(file_format)]
28
+
29
+
30
+ == REQUIREMENTS:
31
+
32
+ Ply currently requires Ruby 1.9.3 -- if you have a need to run Ply with Ruby
33
+ 1.8.7, drop me an email[mailto:jwise@draga.com], and I'll look into
34
+ backporting it. Ply has no other dependencies.
35
+
36
+ == INSTALL:
37
+
38
+ $ gem install ply
39
+
40
+ == SYNOPSIS:
41
+
42
+ === How to Use This Gem
43
+
44
+ To get started, include this gem using
45
+
46
+ require 'ply'
47
+
48
+ This gem provides the Ply module. This module provides a single class,
49
+ Ply::PlyFile, which can be used directly to parse a PLY file into memory, or
50
+ subclassed to take advantage of Ply's event-driven API for handling large
51
+ PLY files.
52
+
53
+ === The Simple API
54
+
55
+ To parse a PLY file into memory, simply instantiate the Ply::PlyFile class,
56
+ passing either the name of the PLY file or an IO object open on a PLY file
57
+ to Ply::PlyFile.new. Thus this:
58
+
59
+ pf = Ply::PlyFile.new "horse.ply"
60
+
61
+ and this:
62
+
63
+ pf = Ply::PlyFile.new File.new("horse.ply")
64
+
65
+ do the same thing.
66
+
67
+ The PLY file is parsed at construction time, and provides the following
68
+ read-only accessors:
69
+
70
+ Ply::PlyFile#version::
71
+ The version of the PLY format used in this file, as a String -- at this
72
+ time the only defined PLY version is 1.0.
73
+
74
+ Ply::PlyFile#format::
75
+ The format of this PLY file as a String-- one of +ascii+,
76
+ +binary_big_endian+, or +binary_little_endian+.
77
+
78
+ Ply::PlyFile#elements::
79
+ The structure of the data in this PLY file, as an array of Hashes, in the
80
+ order the elements will appear in the file. Each Hash contains the
81
+ following keys:
82
+
83
+ :name:: the name of this element type
84
+
85
+ :count:: the number of elements of this type in this PLY file
86
+
87
+ :properties::
88
+ an array of Hashes describing the properties of this element
89
+ type. Each Hash in the +:properties+ array contains the following keys:
90
+
91
+ :name:: the name of this property of the current element
92
+
93
+ :type::
94
+ the type of this property (an integral or floating point type, as
95
+ defined in the PLY file format, or +list+. If the current property is
96
+ of type +list+ (a PLY array type), its property Hash also contains the
97
+ following keys:
98
+
99
+ :index_type::
100
+ the type of the index of this list
101
+
102
+ :element_type::
103
+ the type of the elements in this list
104
+
105
+ If you are using the Ply::PlyFile class directly (as opposed to subclassing
106
+ it in order to use Ply's event-driven API for handling large PLY files), an
107
+ additional read-only accessor is available
108
+
109
+ Ply::PlyFile#data::
110
+
111
+ The actual data in this file, in the structure defined by the return value
112
+ of Ply::PlyFile#elements, as a Hash of Arrays of Hashes. This data is returned
113
+ as a Hash keyed by the name of each element type in the file (as a
114
+ String), and containing an array of Hashes for each element type in the
115
+ file, keyed by the property names of that element, as Strings.
116
+
117
+ That's not as complicated as it sounds! If the file +horse.ply+ defines a
118
+ +vertex+ element, with properties +x+, +y+, and +z+, then
119
+
120
+ pf = Ply::PlyFile.new "horse.ply"
121
+ verts = pf.data["vertex"]
122
+
123
+ will return an array of all vertices in the file, and
124
+
125
+ verts[0]["x"]
126
+
127
+ will give you the value of the +x+ property of the first +vertex+ element
128
+ in the file, as an Integer, Float, or Array, depending on the declared
129
+ type of that property in the PLY file.
130
+
131
+ === The Event-Driven API
132
+
133
+ The above API is easy to use, but has the disadvantage that the entire data
134
+ stored in the PLY file must be able to fit in memory, in order to be
135
+ returned by Ply::PlyFile::data. For this reason, a simple event-driven API
136
+ for handling PLY data is also provided. To make use of this, simply
137
+ subclass the class Ply::PlyFile, and provide your own version of the method
138
+ Ply::PlyFile#element_callback. When a new object of your subclass is
139
+ constructed from a PLY file, your implementation of this method will be
140
+ called once for each element in the file, and passed two arguments:
141
+
142
+ * The type of the current element, in the same format as a member of the
143
+ array returned by Ply::PlyFile::elements
144
+
145
+ * The current element, in the same format as the members of the arrays
146
+ returned by Ply::PlyFile::data
147
+
148
+ Note that if your subclass provides a #initialize method, it is your
149
+ responsibility to ensure that PlyFile
150
+
151
+ As an example, the following code defines a subclass of PlyFile which merely
152
+ *counts* the elements of each type present in PLY file, without reading them
153
+ all into memory at the same time:
154
+
155
+ class PlyFileCounter < Ply::PlyFile
156
+ attr_reader :counts
157
+
158
+ def initialize f
159
+ @counts = {}
160
+ super f
161
+ end
162
+
163
+ def element_callback e, v
164
+ @counts[e[:name]] ||= 0
165
+ @counts[e[:name]] = @counts[e[:name]] + 1
166
+ end
167
+ end
168
+
169
+ p = PlyFileCounter.new "horse.ply"
170
+ puts "There are #{p.counts["vertex"]} vertices in the file."
171
+
172
+ === Example Files
173
+
174
+ In addition to the large models in the {Stanford 3D Scanning
175
+ Repository}[http://graphics.stanford.edu/data/3Dscanrep/], the +examples/+
176
+ subdirectory of this gem includes a simple triangulated cube model in all
177
+ three PLY file formats, as well as a script to generate these three files
178
+ which you may modify for your own purposes.
179
+
180
+ === ply2ascii
181
+
182
+ Finally, this gem includes a simple script, +ply2ascii+, which can be used
183
+ to parse any of the three PLY file formats containing a triangulated scene
184
+ with vertices in an element named +vertex+ with (at least) scalar properties
185
+ +x+, +y+, and +z+, and faces in an elemnt named +face+ containing a list
186
+ property +vertex_indices+, and produce a simple ASCII dump of the described
187
+ scene in two files.
188
+
189
+ Run +ply2ascii+ with no arguments for (a little) more information.
190
+
191
+ == DEVELOPERS:
192
+
193
+ After checking out the source, run:
194
+
195
+ $ rake newb
196
+
197
+ This task will install any missing dependencies, run the tests/specs,
198
+ and generate the RDoc.
199
+
200
+ == LICENSE:
201
+
202
+ (The BSD 2-clause License)
203
+
204
+ Copyright (c) 2013 Jim Wise
205
+ All rights reserved.
206
+
207
+ Redistribution and use in source and binary forms, with or without
208
+ modification, are permitted provided that the following conditions
209
+ are met:
210
+
211
+ 1. Redistributions of source code must retain the above copyright
212
+ notice, this list of conditions and the following disclaimer.
213
+ 2. Redistributions in binary form must reproduce the above copyright
214
+ notice, this list of conditions and the following disclaimer in the
215
+ documentation and/or other materials provided with the distribution.
216
+
217
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
218
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
219
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
220
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
221
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
222
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
223
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
224
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
225
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
226
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
227
+ POSSIBILITY OF SUCH DAMAGE.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.spec 'ply' do
7
+ developer('Jim Wise', 'jwise@draga.com')
8
+ end
data/bin/ply2ascii ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ply'
4
+
5
+ USAGE = "usage: #{$0} <ply file> ..."
6
+ HELP = <<EOL
7
+ This utility translates ply files with vertex and triangle information
8
+ into simple ASCII files.
9
+
10
+ Each plyfile specified on the command line is read, and corresponding
11
+ triangle and vertex files are created in the current directory.
12
+ EOL
13
+
14
+ class Ply2Ascii < Ply::PlyFile
15
+ def initialize infile, outname
16
+ @verts = File.new("vertices_#{outname}.ascii", "w")
17
+ @verts.puts("# written by ply.rb")
18
+ @tris = File.new("triangles_#{outname}.ascii", "w")
19
+ @tris.puts("# written by ply.rb")
20
+ super infile
21
+ end
22
+
23
+ def element_callback elt, val
24
+ case elt[:name]
25
+ when "vertex"
26
+ @verts.puts "#{val["x"]} #{val["y"]} #{val["z"]}"
27
+ when "face"
28
+ @tris.puts val["vertex_indices"].join " "
29
+ else
30
+ puts "skipping unknown element type #{elt[:name]}"
31
+ end
32
+ end
33
+ end
34
+
35
+ if ARGV.size == 0
36
+ puts USAGE
37
+ puts HELP
38
+ end
39
+
40
+ ARGV.each do |fname|
41
+ begin
42
+ outname = File.basename(fname, ".ply")
43
+ Ply2Ascii.new(fname, outname)
44
+ rescue Ply::BadFile => bf
45
+ puts "#{fname}: #{bf}"
46
+ end
47
+ end
48
+
49
+ # -*- ruby -*-
@@ -0,0 +1,30 @@
1
+ ply
2
+ format ascii 1.0
3
+ comment simple triangulated cube example
4
+ element vertex 8
5
+ property float32 x
6
+ property float32 y
7
+ property float32 z
8
+ element face 12
9
+ property list uint8 uint32 vertex_indices
10
+ end_header
11
+ 0.0 0.0 0.0
12
+ 0.0 1.0 0.0
13
+ 1.0 0.0 0.0
14
+ 1.0 1.0 0.0
15
+ 0.0 0.0 1.0
16
+ 0.0 1.0 1.0
17
+ 1.0 0.0 1.0
18
+ 1.0 1.0 1.0
19
+ 3 0 1 3
20
+ 3 1 3 2
21
+ 3 0 1 5
22
+ 3 0 5 4
23
+ 3 4 0 2
24
+ 3 4 2 6
25
+ 3 2 3 7
26
+ 3 2 7 6
27
+ 3 4 5 7
28
+ 3 4 7 6
29
+ 3 1 5 7
30
+ 3 1 7 3
Binary file
@@ -0,0 +1,100 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ # 6---------7
4
+ # /: /|
5
+ # / : / |
6
+ # / : / |
7
+ # 2---------3 |
8
+ # | 4.....|...5
9
+ # | ' | /
10
+ # | ' | /
11
+ # |' |/
12
+ # 0---------1
13
+ #
14
+ # Y Z
15
+ # |/
16
+ # *-X
17
+
18
+ Cube_vertices = [
19
+ [0.0, 0.0, 0.0],
20
+ [0.0, 1.0, 0.0],
21
+ [1.0, 0.0, 0.0],
22
+ [1.0, 1.0, 0.0],
23
+ [0.0, 0.0, 1.0],
24
+ [0.0, 1.0, 1.0],
25
+ [1.0, 0.0, 1.0],
26
+ [1.0, 1.0, 1.0]
27
+ ]
28
+
29
+ Cube_faces = [
30
+ [0, 1, 3],
31
+ [1, 3, 2],
32
+
33
+ [0, 1, 5],
34
+ [0, 5, 4],
35
+
36
+ [4, 0, 2],
37
+ [4, 2, 6],
38
+
39
+ [2, 3, 7],
40
+ [2, 7, 6],
41
+
42
+ [4, 5, 7],
43
+ [4, 7, 6],
44
+
45
+ [1, 5, 7],
46
+ [1, 7, 3]
47
+ ]
48
+
49
+ def header f, format
50
+ f.puts "ply"
51
+ f.puts "format #{format} 1.0"
52
+ f.puts "comment simple triangulated cube example"
53
+ f.puts "element vertex 8"
54
+ f.puts "property float32 x"
55
+ f.puts "property float32 y"
56
+ f.puts "property float32 z"
57
+ f.puts "element face 12"
58
+ f.puts "property list uint8 uint32 vertex_indices"
59
+ f.puts "end_header"
60
+ end
61
+
62
+ def ascii_cube fname
63
+ f = File.new(fname, "w")
64
+ header f, "ascii"
65
+ Cube_vertices.each do |vert|
66
+ f.puts vert.map{|p| p.to_s}.join(" ")
67
+ end
68
+ Cube_faces.each do |face|
69
+ f.print "#{face.size} "
70
+ f.puts face.map{|p| p.to_s}.join(" ")
71
+ end
72
+ end
73
+
74
+ def bin_be_cube fname
75
+ f = File.new(fname, "w")
76
+ header f, "binary_big_endian"
77
+ Cube_vertices.each do |vert|
78
+ f.write vert.pack("g*")
79
+ end
80
+ Cube_faces.each do |face|
81
+ f.write [ face.size ].pack("C")
82
+ f.write face.pack("L>*")
83
+ end
84
+ end
85
+
86
+ def bin_le_cube fname
87
+ f = File.new(fname, "w")
88
+ header f, "binary_little_endian"
89
+ Cube_vertices.each do |vert|
90
+ f.write vert.pack("e*")
91
+ end
92
+ Cube_faces.each do |face|
93
+ f.write [ face.size ].pack("C")
94
+ f.write face.pack("L<*")
95
+ end
96
+ end
97
+
98
+ ascii_cube "cube_ascii.ply"
99
+ bin_be_cube "cube_binary_big_endian.ply"
100
+ bin_le_cube "cube_binary_little_endian.ply"
data/lib/ply.rb ADDED
@@ -0,0 +1,188 @@
1
+ # ply.rb
2
+
3
+ # Copyright (c) 2013 Jim Wise
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions
8
+ # are met:
9
+ #
10
+ # 1. Redistributions of source code must retain the above copyright
11
+ # notice, this list of conditions and the following disclaimer.
12
+ # 2. Redistributions in binary form must reproduce the above copyright
13
+ # notice, this list of conditions and the following disclaimer in the
14
+ # documentation and/or other materials provided with the distribution.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
+ # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
20
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ # POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ module Ply
29
+ VERSION = '0.9'
30
+
31
+ # module for working with ply file format -- see
32
+ # http://en.wikipedia.org/wiki/PLY_(file_format)
33
+ # right now, this supports reading, but not writing ply files
34
+
35
+ class PlyFile
36
+ attr_reader :format, :version, :elements, :data
37
+
38
+ # versions of the ply file format we know how to parse (this is the only defined version as of the time of writing)
39
+ Versions = %w{1.0}
40
+ # formats of the ply file format we know about (we don't yet implement ascii)
41
+ Formats = %w{binary_big_endian binary_little_endian ascii}
42
+ # property types defined by the ply format; we assume ILP32 meaning of types without explicit widths
43
+ Types = %w{char uchar short ushort int uint float double int8 uint8 int16 uint16 int32 uint32 float32 float64 list}
44
+
45
+ # parse a ply file takes a file name or an IO stream as argument
46
+ def initialize f
47
+ unless f.instance_of? IO
48
+ f = File.new(f)
49
+ end
50
+ @source = f
51
+ @elements = []
52
+ parse_header f
53
+ parse_body f
54
+ end
55
+
56
+ private
57
+
58
+ def parse_header f
59
+ raise BadFile.new("missing magic") unless f.gets.chomp == "ply"
60
+
61
+ raise BadFile.new("malformed format line") unless md =
62
+ f.gets.chomp.match(/^format\s+(?<format>[^\s]+)\s+(?<version>[\d.]+)$/)
63
+ @format = md[:format]
64
+ @version = md[:version]
65
+
66
+ raise BadFile.new("unknown version: #{version}") unless Versions.find(version)
67
+ raise BadFile.new("unknown format: #{format}") unless Formats.find(version)
68
+
69
+ current_element = false
70
+ f.each do |s|
71
+ cmd = s.chomp.split
72
+ case cmd[0]
73
+ when "comment"
74
+ when "obj_info"
75
+ when "end_header"
76
+ # stash last element
77
+ @elements << current_element if current_element
78
+ break
79
+ when "element"
80
+ # stash previous element
81
+ @elements << current_element if current_element
82
+ # puts "new element #{cmd[1]}"
83
+ current_element = { name: cmd[1], count: cmd[2].to_i, properties: [] }
84
+ when "property"
85
+ type = cmd[1]
86
+ raise BadFile.new("unknown element type: #{type}") unless Types.find(type)
87
+
88
+ current_element[:properties] << if type == "list"
89
+ # puts "new property #{cmd[4]} of element #{current_element[:name]} has type list indexed by #{cmd[2]} of #{cmd[3]}"
90
+ { name: cmd[4], type: "list", index_type: cmd[2], element_type: cmd[3]}
91
+ else
92
+ # puts "new property #{cmd[2]} of element #{current_element[:name]} has type #{type}"
93
+ { name: cmd[2], type: type}
94
+ end
95
+ else
96
+ raise BadFile.new("unknown ply command #{cmd[0]}")
97
+ end
98
+ end
99
+ end
100
+
101
+ # parse the body of a ply file, using the structures already parsed out of the header
102
+ # for each element type foo defined in the header, fills in @data[foo] with an array of hashes keyed on the property names
103
+ def parse_body f
104
+ @elements.each do |elt|
105
+ elt[:count].times do
106
+ element_callback elt, read_element(f, elt)
107
+ end
108
+ end
109
+ end
110
+
111
+ # by default, we accumulate all elements of a given type e, in-memory, into an array @data[e]
112
+ # this is non-ideal for big files, of course -- to change this behavior, subclass PlyFile, and provide your own
113
+ # element callback -- it will be called once for each read element, and passed the element definition (in elt), and
114
+ # the actual values just read (in val)
115
+ def element_callback elt, val
116
+ @data ||= {}
117
+ @data[elt[:name]] ||= []
118
+ @data[elt[:name]] << val
119
+ end
120
+
121
+ def read_element f, elt
122
+ current_element = {}
123
+ if @format == "ascii"
124
+ fields = f.gets.chomp.split
125
+ elt[:properties].each do |prop|
126
+ current_element[prop[:name]] = read_property_ascii(fields, prop[:type], prop[:index_type], prop[:element_type])
127
+ end
128
+ else
129
+ elt[:properties].each do |prop|
130
+ # last two will be nil for non-list
131
+ current_element[prop[:name]] = read_property_binary(f, prop[:type], prop[:index_type], prop[:element_type])
132
+ end
133
+ end
134
+ current_element
135
+ end
136
+
137
+ def read_property_ascii fields, type, index_type=false, element_type=false
138
+ #print "reading one #{type}: "
139
+ case type
140
+ when "char", "int8", "uchar", "uint8", "short", "int16", "ushort", "uint16", "int", "int32", "uint", "uint32"
141
+ # integer types
142
+ fields.shift.to_i
143
+ when "float", "float32", "double", "float64"
144
+ # floating point types
145
+ fields.shift.to_f
146
+ when "list"
147
+ count = read_property_ascii(fields, index_type)
148
+ count.times.collect { read_property_ascii(fields, element_type) }
149
+ end
150
+ end
151
+
152
+ # read one property from an IO stream in ply binary format, assuming ILP32 widths for generic types
153
+ def read_property_binary f, type, index_type=false, element_type=false
154
+ # print "reading one #{type}: "
155
+ # pick String#unpack specifiers based on our endian-ness
156
+ case @format
157
+ when "binary_big_endian"
158
+ e, f32, f64 = ">", "g", "G"
159
+ when "binary_little_endian"
160
+ e, f32, f64 = "<", "e", "E"
161
+ end
162
+ case type
163
+ when "char", "int8"
164
+ f.read(1).unpack("c").first
165
+ when "uchar", "uint8"
166
+ f.read(1).unpack("C").first
167
+ when "short", "int16"
168
+ f.read(2).unpack("s#{e}").first
169
+ when "ushort", "uint16"
170
+ f.read(2).unpack("S#{e}").first
171
+ when "int", "int32"
172
+ f.read(4).unpack("l#{e}").first
173
+ when "uint", "uint32"
174
+ f.read(4).unpack("L#{e}").first
175
+ when "float", "float32"
176
+ f.read(4).unpack(f32).first
177
+ when "double", "float64"
178
+ f.read(8).unpack(f64).first
179
+ when "list"
180
+ count = read_property_binary(f, index_type)
181
+ count.times.collect { read_property_binary(f, element_type) }
182
+ end
183
+ end
184
+ end
185
+
186
+ class BadFile < Exception
187
+ end
188
+ end
data/test/test_ply.rb ADDED
@@ -0,0 +1,56 @@
1
+ require "test/unit"
2
+ require "ply"
3
+
4
+ class TestPly < Test::Unit::TestCase
5
+ def test_ascii
6
+ verify_cube_file "examples/cube_ascii.ply"
7
+ end
8
+
9
+ def test_binary_big_endian
10
+ verify_cube_file "examples/cube_binary_big_endian.ply"
11
+ end
12
+
13
+ def test_binary_little_endian
14
+ verify_cube_file "examples/cube_binary_little_endian.ply"
15
+ end
16
+
17
+ def test_ascii_callback
18
+ verify_cube_callback "examples/cube_ascii.ply"
19
+ end
20
+
21
+ def test_binary_big_endian_callback
22
+ verify_cube_callback "examples/cube_binary_big_endian.ply"
23
+ end
24
+
25
+ def test_binary_little_endian_callback
26
+ verify_cube_callback "examples/cube_binary_little_endian.ply"
27
+ end
28
+
29
+ def verify_cube_file c
30
+ p = Ply::PlyFile.new c
31
+ assert p.data["vertex"].size == 8
32
+ assert p.data["face"].size == 12
33
+ assert p.data["vertex"][7]["x"] == 1.0
34
+ assert p.data["face"][11]["vertex_indices"][2] == 3
35
+ end
36
+
37
+ class PlyFileCounter < Ply::PlyFile
38
+ attr_reader :counts
39
+
40
+ def initialize f
41
+ @counts = {}
42
+ super f
43
+ end
44
+
45
+ def element_callback e, v
46
+ @counts[e[:name]] ||= 0
47
+ @counts[e[:name]] = @counts[e[:name]] + 1
48
+ end
49
+ end
50
+
51
+ def verify_cube_callback c
52
+ p = PlyFileCounter.new c
53
+ assert p.counts["vertex"] == 8
54
+ assert p.counts["face"] == 12
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ require "test/unit"
2
+ require "ply"
3
+
4
+ class TestPly2Ascii < Test::Unit::TestCase
5
+ def test_ply2ascii
6
+ assert system("env RUBYOPT=-I./lib bin/ply2ascii examples/cube_binary_big_endian.ply")
7
+ verts = File.new "vertices_cube_binary_big_endian.ascii"
8
+ tris = File.new "triangles_cube_binary_big_endian.ascii"
9
+ nv, nt = 0, 0
10
+ verts.lines {|l| nv = nv + 1}
11
+ tris.lines {|l| nt = nt + 1}
12
+ assert nv == 9
13
+ assert nt == 13
14
+ ensure
15
+ File.unlink "vertices_cube_binary_big_endian.ascii"
16
+ File.unlink "triangles_cube_binary_big_endian.ascii"
17
+ end
18
+
19
+ def test_help
20
+ out = `env RUBYOPT=-I./lib bin/ply2ascii`
21
+ assert out.match(/^This utility/)
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ply
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.9'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jim Wise
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rdoc
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.10'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.10'
30
+ - !ruby/object:Gem::Dependency
31
+ name: hoe
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '3.5'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '3.5'
46
+ description: ! "Ply is a ruby gem for reading Stanford PLY-format 3D model files.\n\nThe
47
+ PLY file format is a flexible format for storing semi-structured binary data,\nand
48
+ is often used to stored polygonalized 3D models generated with range\nscanning hardware.
49
+ \ You can find some examples of the format at the the\n{Stanford 3D Scanning Repository}[http://graphics.stanford.edu/data/3Dscanrep/].\n
50
+ \nPly provides a simple API for quick access to the data in a PLY file\n(including
51
+ examining the structure of a particular file's content), and\nan almost-as-simple
52
+ event-driven API which can be used to process extremely\nlarge ply files in a streaming
53
+ fashion, without needing to keep the full\ndataset represented in the file in memory.
54
+ \ Ply handles all three types of\nPLY files (ascii, binary-big-endian and binary-little-endian).\n\nIf
55
+ you don't have any Stanford PLY files on hand, you probably don't need\nthis gem,
56
+ but if you're curious, the PLY file format, described at\nWikipedia[http://en.wikipedia.org/wiki/PLY_(file_format)]"
57
+ email:
58
+ - jwise@draga.com
59
+ executables:
60
+ - ply2ascii
61
+ extensions: []
62
+ extra_rdoc_files:
63
+ - History.txt
64
+ - LICENSE.txt
65
+ - Manifest.txt
66
+ - README.txt
67
+ - README.rdoc
68
+ files:
69
+ - .autotest
70
+ - History.txt
71
+ - LICENSE.txt
72
+ - Manifest.txt
73
+ - README.txt
74
+ - README.rdoc
75
+ - Rakefile
76
+ - bin/ply2ascii
77
+ - examples/cube_ascii.ply
78
+ - examples/cube_binary_big_endian.ply
79
+ - examples/cube_binary_little_endian.ply
80
+ - examples/examples.rb
81
+ - lib/ply.rb
82
+ - test/test_ply.rb
83
+ - test/test_ply2ascii.rb
84
+ - .gemtest
85
+ homepage: https://github.com/jimwise/ply
86
+ licenses: []
87
+ post_install_message:
88
+ rdoc_options:
89
+ - --main
90
+ - README.txt
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project: ply
107
+ rubygems_version: 1.8.24
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Ply is a ruby gem for reading Stanford PLY-format 3D model files
111
+ test_files:
112
+ - test/test_ply.rb
113
+ - test/test_ply2ascii.rb