flame_channel_parser 2.0.1 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|