flame_channel_parser 2.0.1 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -0
- data/Rakefile +5 -4
- data/bin/bake_flame_channel +4 -0
- data/lib/extractor.rb +67 -23
- data/lib/flame_channel_parser.rb +1 -1
- data/test/snaps/RefT_Steadicam_Extraction.txt +40 -0
- data/test/test_cli.rb +27 -28
- data/test/test_extractor.rb +24 -11
- metadata +15 -4
data/History.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
=== 2.1.1 / 2011-23-06
|
2
|
+
|
3
|
+
* Fixed exceptions for for channel not found errors
|
4
|
+
* Uses cli_test for testing the application
|
5
|
+
|
6
|
+
=== 2.1.0 / 2011-23-06
|
7
|
+
|
8
|
+
* Fixed range detection for setups (we now properly detect the last frame for setups that support it)
|
9
|
+
|
1
10
|
=== 2.0.1 / 2011-23-06
|
2
11
|
|
3
12
|
* Fix the bake_flame_channel binary to use the proper extractor API
|
data/Rakefile
CHANGED
@@ -3,14 +3,15 @@
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'hoe'
|
5
5
|
|
6
|
+
Hoe::RUBY_FLAGS.gsub!(/^\-w/, '') # No thanks undefined ivar warnings
|
6
7
|
Hoe.spec 'flame_channel_parser' do | p |
|
7
|
-
|
8
|
-
|
8
|
+
p.developer('Julik Tarkhanov', 'me@julik.nl')
|
9
|
+
|
9
10
|
p.readme_file = 'README.rdoc'
|
10
11
|
p.extra_rdoc_files = FileList['*.rdoc'] + FileList['*.txt']
|
11
12
|
p.extra_deps = {"update_hints" => ">=0" }
|
12
|
-
p.
|
13
|
-
p.clean_globs =
|
13
|
+
p.extra_dev_deps = {"cli_test" => "~> 1.0.0" }
|
14
|
+
p.clean_globs = File.read(".gitignore").split("\n")
|
14
15
|
end
|
15
16
|
|
16
17
|
# vim: syntax=ruby
|
data/bin/bake_flame_channel
CHANGED
@@ -30,6 +30,10 @@ op.on(" -s", "--startframe FRAME", Integer,
|
|
30
30
|
op.on(" -e", "--endframe FRAME", Integer,
|
31
31
|
"Bake the curve upto this specific frame (defaults to the last keyframe in the setup)"
|
32
32
|
) {|upto| options[:end_frame] = upto }
|
33
|
+
op.on(" -k", "--keyframed-range-only",
|
34
|
+
"Bake the curve from the first keyframe to the last only (overrides --startframe and --endframe)"
|
35
|
+
) { options[:on_curve_limits] = true }
|
36
|
+
|
33
37
|
op.on(" -f", "--to-file FILENAME", String,
|
34
38
|
"Write the curve to a file at this path instead of printing it to STDOUT"
|
35
39
|
) {|path| options[:destination] = File.open(path, "wb") }
|
data/lib/extractor.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
class FlameChannelParser::Extractor
|
3
3
|
|
4
4
|
DEFAULT_CHANNEL_TO_EXTRACT = "Timing/Timing"
|
5
|
-
DEFAULTS = {:destination => $stdout, :start_frame => nil, :end_frame => nil, :channel => DEFAULT_CHANNEL_TO_EXTRACT }
|
6
5
|
|
7
6
|
# Raised when a channel is not found in the setup file
|
8
7
|
class ChannelNotFoundError < RuntimeError; end
|
@@ -10,9 +9,6 @@ class FlameChannelParser::Extractor
|
|
10
9
|
# Raised when you try to autodetect the length of a channel that has no keyframes
|
11
10
|
class NoKeyframesError < RuntimeError; end
|
12
11
|
|
13
|
-
# Raised when you try to bake 0 or negative amount of frames
|
14
|
-
class EmptySegmentError < RuntimeError; end
|
15
|
-
|
16
12
|
# Pass the path to Flame setup here and you will get the animation curve on the object passed in
|
17
13
|
# the :destionation option (defaults to STDOUT). The following options are accepted:
|
18
14
|
#
|
@@ -27,43 +23,91 @@ class FlameChannelParser::Extractor
|
|
27
23
|
# 1 123.456
|
28
24
|
# 2 124.567
|
29
25
|
def self.extract(path, options = {})
|
30
|
-
|
26
|
+
new.extract(path, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract(path, options)
|
30
|
+
options = DEFAULTS.dup.merge(options)
|
31
31
|
File.open(path) do |f|
|
32
|
+
|
33
|
+
# Then parse
|
32
34
|
channels = FlameChannelParser.parse(f)
|
33
35
|
selected_channel = find_channel_in(channels, options[:channel])
|
34
36
|
interpolator = FlameChannelParser::Interpolator.new(selected_channel)
|
37
|
+
|
38
|
+
# Configure the range
|
39
|
+
configure_start_and_end_frame(f, options, interpolator)
|
40
|
+
|
41
|
+
# And finally...
|
35
42
|
write_channel(interpolator, options[:destination], options[:start_frame], options[:end_frame])
|
36
43
|
end
|
37
44
|
end
|
38
45
|
|
39
46
|
private
|
40
47
|
|
41
|
-
|
48
|
+
DEFAULT_START_FRAME = 1
|
49
|
+
DEFAULTS = {:destination => $stdout, :start_frame => nil, :end_frame => nil, :channel => DEFAULT_CHANNEL_TO_EXTRACT, :on_curve_limits => false }
|
50
|
+
|
51
|
+
SETUP_END_FRAME_PATTERN = /(MaxFrames|Frames)(\s+)(\d+)/
|
52
|
+
SETUP_START_FRAME_PATTERN = /(MinFrame)(\s+)(\d+)/
|
53
|
+
|
54
|
+
def configure_start_and_end_frame(f, options, interpolator)
|
55
|
+
# If the settings specify last and first frame...
|
56
|
+
if options[:on_curve_limits]
|
57
|
+
options[:start_frame] = interpolator.first_defined_frame.to_i
|
58
|
+
options[:end_frame] = interpolator.last_defined_frame.to_i
|
59
|
+
if (!options[:start_frame] || !options[:end_frame])
|
60
|
+
raise NoKeyframesError, "This channel probably has no animation so there " +
|
61
|
+
"is no way to automatically tell how many keyframes it has. " +
|
62
|
+
"Please set the start and end frame explicitly."
|
63
|
+
elsif options[:end_frame] == options[:start_frame]
|
64
|
+
raise NoKeyframesError, "This channel has only one keyframe " +
|
65
|
+
"at frame #{options[:start_frame]}and baking it makes no sense."
|
66
|
+
end
|
67
|
+
else # Detect from the setup itself (the default)
|
68
|
+
# First try to detect start and end frames from the known flags
|
69
|
+
f.rewind
|
70
|
+
detected_start, detected_end = detect_start_and_end_frame_in_io(f)
|
71
|
+
|
72
|
+
options[:start_frame] = options[:start_frame] || detected_start || DEFAULT_START_FRAME
|
73
|
+
options[:end_frame] ||= detected_end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def detect_start_and_end_frame_in_io(io)
|
79
|
+
cur_offset, s, e = io.pos, nil, nil
|
80
|
+
io.rewind
|
81
|
+
while line = io.gets
|
82
|
+
if (elements = line.scan(SETUP_START_FRAME_PATTERN)).any?
|
83
|
+
s = elements.flatten[-1].to_i
|
84
|
+
elsif (elements = line.scan(SETUP_END_FRAME_PATTERN)).any?
|
85
|
+
e = elements.flatten[-1].to_i
|
86
|
+
return [s, e]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def compose_channel_not_found_message(for_channel, other_channels)
|
92
|
+
message = "Channel #{for_channel.inspect} not found in this setup (set the channel with the :channel option). Found other channels though:"
|
93
|
+
message << "\n"
|
94
|
+
message += other_channels.map{|c| "\t%s\n" % c.path }.join
|
95
|
+
end
|
96
|
+
|
97
|
+
def find_channel_in(channels, channel_path)
|
42
98
|
selected_channel = channels.find{|c| channel_path == c.path }
|
43
99
|
unless selected_channel
|
44
|
-
|
45
|
-
message << "\n"
|
46
|
-
message += channels.map{|c| "\t%s\n" % c.path }.join
|
47
|
-
raise ChannelNotFoundError, message
|
100
|
+
raise ChannelNotFoundError, compose_channel_not_found_message(channel_path, channels)
|
48
101
|
end
|
49
102
|
selected_channel
|
50
103
|
end
|
51
|
-
|
52
|
-
def
|
53
|
-
|
54
|
-
from_frame = start_frame || interpolator.first_defined_frame
|
55
|
-
to_frame = end_frame || interpolator.last_defined_frame
|
104
|
+
|
105
|
+
def write_channel(interpolator, to_io, from_frame_i, to_frame_i)
|
56
106
|
|
57
|
-
|
58
|
-
|
59
|
-
"Please set the start and end frame explicitly."
|
107
|
+
if (to_frame_i - from_frame_i) == 1
|
108
|
+
$stderr.puts "WARNING: You are extracting one animation frame. Check the length of your setup, or set the range manually"
|
60
109
|
end
|
61
110
|
|
62
|
-
raise EmptySegmentError, "The segment you are trying to bake is too small (it has nothing in it)" if to_frame - from_frame < 1
|
63
|
-
|
64
|
-
from_frame_i = from_frame.to_f.floor
|
65
|
-
to_frame_i = to_frame.to_f.ceil
|
66
|
-
|
67
111
|
(from_frame_i..to_frame_i).each do | frame |
|
68
112
|
line = "%d\t%.5f\n" % [frame, interpolator.sample_at(frame)]
|
69
113
|
to_io << line
|
data/lib/flame_channel_parser.rb
CHANGED
@@ -1,3 +1,43 @@
|
|
1
|
+
1 459.00000
|
2
|
+
2 457.00000
|
3
|
+
3 455.00000
|
4
|
+
4 453.00000
|
5
|
+
5 451.00000
|
6
|
+
6 449.00000
|
7
|
+
7 447.00000
|
8
|
+
8 445.00000
|
9
|
+
9 443.00000
|
10
|
+
10 441.00000
|
11
|
+
11 439.00000
|
12
|
+
12 437.00000
|
13
|
+
13 435.00000
|
14
|
+
14 433.00000
|
15
|
+
15 431.00000
|
16
|
+
16 429.00000
|
17
|
+
17 427.00000
|
18
|
+
18 425.00000
|
19
|
+
19 423.00000
|
20
|
+
20 421.00000
|
21
|
+
21 419.00000
|
22
|
+
22 417.00000
|
23
|
+
23 415.00000
|
24
|
+
24 413.00000
|
25
|
+
25 411.00000
|
26
|
+
26 409.00000
|
27
|
+
27 407.00000
|
28
|
+
28 405.00000
|
29
|
+
29 403.00000
|
30
|
+
30 401.00000
|
31
|
+
31 399.00000
|
32
|
+
32 397.00000
|
33
|
+
33 395.00000
|
34
|
+
34 393.00000
|
35
|
+
35 391.00000
|
36
|
+
36 389.00000
|
37
|
+
37 387.00000
|
38
|
+
38 385.00000
|
39
|
+
39 383.00000
|
40
|
+
40 381.00000
|
1
41
|
41 379.00000
|
2
42
|
42 377.00000
|
3
43
|
43 375.00000
|
data/test/test_cli.rb
CHANGED
@@ -1,58 +1,57 @@
|
|
1
1
|
require "test/unit"
|
2
2
|
require "stringio"
|
3
3
|
require "tempfile"
|
4
|
+
require "cli_test"
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
class CliTest < Test::Unit::TestCase
|
8
|
-
BIN_P = File.expand_path(File.dirname(__FILE__) + "/../bin/bake_flame_channel")
|
6
|
+
class TestCli < Test::Unit::TestCase
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
os, es = StringIO.new, StringIO.new
|
14
|
-
begin
|
15
|
-
$stdout, $stderr, verbosity = os, es, $VERBOSE
|
16
|
-
ARGV.replace(commandline_arguments.split)
|
17
|
-
$VERBOSE = false
|
18
|
-
load(BIN_P)
|
19
|
-
return [0, os.string, es.string]
|
20
|
-
rescue SystemExit => boom # The binary uses exit(), we use that to preserve the output code
|
21
|
-
return [boom.status, os.string, es.string]
|
22
|
-
ensure
|
23
|
-
$VERBOSE = verbosity
|
24
|
-
ARGV.replace(old_argv)
|
25
|
-
$stdout, $stderr = old_stdout, old_stderr
|
26
|
-
end
|
8
|
+
def setup
|
9
|
+
binary = File.expand_path(File.dirname(__FILE__) + "/../bin/bake_flame_channel")
|
10
|
+
@app = CLITest.new(binary)
|
27
11
|
end
|
28
12
|
|
29
13
|
def test_cli_with_no_args_produces_usage
|
30
|
-
status, o, e =
|
14
|
+
status, o, e = @app.run('')
|
31
15
|
assert_equal -1, status
|
32
16
|
assert_match /No input file path provided/, e
|
33
17
|
assert_match /--help for usage information/, e
|
34
18
|
end
|
35
19
|
|
36
20
|
def test_cli_with_nonexisting_file
|
37
|
-
status, o, e =
|
21
|
+
status, o, e = @app.run(" amazing.action")
|
38
22
|
assert_equal -1, status
|
39
23
|
assert_match /does not exist/, e
|
40
24
|
end
|
41
25
|
|
42
26
|
def test_cli_with_proper_output
|
43
27
|
full_path = File.expand_path(File.dirname(__FILE__)) + "/snaps/TW.timewarp"
|
44
|
-
status, output, e =
|
28
|
+
status, output, e = @app.run(" " + full_path)
|
45
29
|
assert_equal 0, status
|
46
|
-
assert_equal
|
30
|
+
assert_equal 747, output.split("\n").length, "Should have output 816 frames"
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_cli_with_file_length
|
34
|
+
full_path = File.expand_path(File.dirname(__FILE__)) + "/snaps/TW_015_010_v03.timewarp"
|
35
|
+
status, output, e = @app.run(full_path)
|
36
|
+
assert_equal 0, status
|
37
|
+
assert_equal 476, output.split("\n").length, "Should have output 476 frames"
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_cli_with_curve_limits
|
41
|
+
full_path = File.expand_path(File.dirname(__FILE__)) + "/snaps/TW_015_010_v03.timewarp"
|
42
|
+
status, output, e = @app.run(" --keyframed-range-only " + full_path)
|
43
|
+
assert_equal 0, status
|
44
|
+
assert_equal 531, output.split("\n").length, "Should have output 513 frames"
|
47
45
|
end
|
48
46
|
|
49
47
|
def test_cli_with_output_to_file
|
50
48
|
tf = Tempfile.new("experiment")
|
51
49
|
full_path = File.expand_path(File.dirname(__FILE__)) + "/snaps/TW.timewarp"
|
52
|
-
status, output, e =
|
53
|
-
|
50
|
+
status, output, e = @app.run(" --to-file " + tf.path + " " + full_path)
|
51
|
+
|
54
52
|
assert_equal 0, status
|
55
|
-
assert_equal
|
53
|
+
assert_equal 0, output.length
|
54
|
+
assert_equal 747, File.read(tf.path).split("\n").length, "Should have output 816 frames"
|
56
55
|
ensure
|
57
56
|
tf.close!
|
58
57
|
end
|
data/test/test_extractor.rb
CHANGED
@@ -4,20 +4,14 @@ require "stringio"
|
|
4
4
|
require File.dirname(__FILE__) + "/../lib/flame_channel_parser"
|
5
5
|
|
6
6
|
class TestExtractor < Test::Unit::TestCase
|
7
|
+
|
7
8
|
def test_basic_operation
|
8
9
|
io = StringIO.new
|
9
|
-
|
10
|
+
opts = {:destination => io}
|
11
|
+
FlameChannelParser::Extractor.extract(File.dirname(__FILE__) + "/snaps/RefT_Steadicam.timewarp", opts)
|
10
12
|
assert_equal File.read(File.dirname(__FILE__) + "/snaps/RefT_Steadicam_Extraction.txt"), io.string
|
11
13
|
end
|
12
14
|
|
13
|
-
def test_channel_selection_by_path_raises_with_not_animated_channel_and_no_start_and_end
|
14
|
-
io = StringIO.new
|
15
|
-
ops = {:destination => io, :channel => "axis1/position/z"}
|
16
|
-
assert_raise(FlameChannelParser::Extractor::NoKeyframesError) do
|
17
|
-
FlameChannelParser::Extractor.extract(File.dirname(__FILE__) + "/snaps/FLEM_curves_example_migrated_to_2012.action", ops)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
15
|
def test_channel_selection_by_path_outputs_properly
|
22
16
|
io = StringIO.new
|
23
17
|
ops = {:destination => io, :channel => "axis1/position/y"}
|
@@ -38,13 +32,32 @@ class TestExtractor < Test::Unit::TestCase
|
|
38
32
|
io = StringIO.new
|
39
33
|
o = {:destination => io, :start_frame => 19, :end_frame => 347 }
|
40
34
|
FlameChannelParser::Extractor.extract(File.dirname(__FILE__) + "/snaps/RefT_Steadicam.timewarp", o)
|
41
|
-
|
42
35
|
assert_equal File.read(File.dirname(__FILE__) + "/snaps/RefT_Steadicam_Extraction_F19_to_347.txt"), io.string
|
43
36
|
end
|
44
37
|
|
38
|
+
def test_properly_recognizes_timewarp_length_in_timewarp
|
39
|
+
io = StringIO.new
|
40
|
+
o = {:destination => io }
|
41
|
+
FlameChannelParser::Extractor.extract(File.dirname(__FILE__) + "/snaps/TW_015_010_v03.timewarp", o)
|
42
|
+
lines = io.string.split("\n")
|
43
|
+
|
44
|
+
assert_equal 476, lines.length, "Should have parsed out 476 frames even though animation curves go further"
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_properly_recognizes_timewarp_length_in_action
|
48
|
+
io = StringIO.new
|
49
|
+
o = {:destination => io , :channel => "axis1/position/z"}
|
50
|
+
FlameChannelParser::Extractor.extract(File.dirname(__FILE__) + "/snaps/FLEM_BrokenTangents.action", o)
|
51
|
+
lines = io.string.split("\n")
|
52
|
+
|
53
|
+
assert_equal 125, lines.length, "Should have parsed out 125 frames even though animation curves go further"
|
54
|
+
end
|
55
|
+
|
45
56
|
def test_constant_channels_need_domain_of_definition_on_time
|
57
|
+
opts = {:channel => "Mix/Mix", :on_curve_limits => true}
|
58
|
+
|
46
59
|
assert_raise(FlameChannelParser::Extractor::NoKeyframesError) do
|
47
|
-
FlameChannelParser::Extractor.extract(File.dirname(__FILE__) + "/snaps/RefT_Steadicam.timewarp",
|
60
|
+
FlameChannelParser::Extractor.extract(File.dirname(__FILE__) + "/snaps/RefT_Steadicam.timewarp", opts)
|
48
61
|
end
|
49
62
|
end
|
50
63
|
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: flame_channel_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 2.
|
5
|
+
version: 2.1.1
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Julik Tarkhanov
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-06-
|
13
|
+
date: 2011-06-24 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: update_hints
|
@@ -24,16 +24,27 @@ dependencies:
|
|
24
24
|
type: :runtime
|
25
25
|
version_requirements: *id001
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
27
|
+
name: cli_test
|
28
28
|
prerelease: false
|
29
29
|
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.0.0
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: hoe
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
30
41
|
none: false
|
31
42
|
requirements:
|
32
43
|
- - ">="
|
33
44
|
- !ruby/object:Gem::Version
|
34
45
|
version: 2.9.4
|
35
46
|
type: :development
|
36
|
-
version_requirements: *
|
47
|
+
version_requirements: *id003
|
37
48
|
description: |-
|
38
49
|
Includes a small library for extracting, parsing and baking animation curves made on Discrodesk Floke/Inflinto, also known as flame.
|
39
50
|
Thanks to Marijn Eken, Philippe Soeiro and Andre Gagnon for their support and advice.
|