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 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
- Hoe::RUBY_FLAGS.gsub!(/^\-w/, '') # No thanks undefined ivar warnings
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.developer('Julik Tarkhanov', 'me@julik.nl')
13
- p.clean_globs = %w( **/.DS_Store coverage.info **/*.rbc .idea .yardoc *.numbers)
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
@@ -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
- options = DEFAULTS.merge(options)
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
- def self.find_channel_in(channels, channel_path)
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
- message = "Channel #{channel_path.inspect} not found in this setup (set the channel with the --channel option). Found other channels though:"
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 self.write_channel(interpolator, to_io, start_frame, end_frame)
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
- unless (from_frame && to_frame)
58
- raise NoKeyframesError, "This channel probably has no animation so there is no way to automatically tell how many keyframes it has. " +
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
@@ -1,5 +1,5 @@
1
1
  module FlameChannelParser
2
- VERSION = '2.0.1'
2
+ VERSION = '2.1.1'
3
3
 
4
4
  # Parse a Flame setup into an array of Channel
5
5
  def self.parse(io)
@@ -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
- require File.dirname(__FILE__) + "/../lib/flame_channel_parser"
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
- # Run the binary under test with passed options, and return [exit_code, stdout_content, stderr_content]
11
- def cli(commandline_arguments)
12
- old_stdout, old_stderr, old_argv = $stdout, $stderr, ARGV.dup
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 = cli('')
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 = cli(" amazing.action")
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 = cli(" " + full_path)
28
+ status, output, e = @app.run(" " + full_path)
45
29
  assert_equal 0, status
46
- assert_equal 816, output.split("\n").length, "Should have output 816 frames"
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 = cli(" --to-file " + tf.path + " " + full_path)
53
-
50
+ status, output, e = @app.run(" --to-file " + tf.path + " " + full_path)
51
+
54
52
  assert_equal 0, status
55
- assert_equal 816, File.read(tf.path).split("\n").length, "Should have output 816 frames"
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
@@ -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
- FlameChannelParser::Extractor.extract(File.dirname(__FILE__) + "/snaps/RefT_Steadicam.timewarp", :destination => io)
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", :channel => "Mix/Mix")
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.0.1
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-23 00:00:00 Z
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: hoe
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: *id002
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.