flame_channel_parser 1.4.0 → 2.0.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.0.1 / 2011-23-06
2
+
3
+ * Fix the bake_flame_channel binary to use the proper extractor API
4
+ * Add a test for the binary
5
+
6
+ === 2.0.0 / 2011-08-06
7
+
8
+ * Remove most of the Array methods from the Channel object
9
+
1
10
  === 1.4.0 / 2011-06-06
2
11
 
3
12
  * Implement cycle and reverse-cycle extrapolation
@@ -6,11 +15,11 @@
6
15
 
7
16
  * Fix linear extrapolation occurring after linear keyframes, enforce the tangent/increment of zero for linear extrap. segments
8
17
 
9
- === 1.3.1 / 2011-05-30
18
+ === 1.3.1 / 2011-30-05
10
19
 
11
20
  * Add minor fixes and imporvements to the curve extraction bin
12
21
 
13
- === 1.3.0 / 2011-05-20
22
+ === 1.3.0 / 2011-20-05
14
23
 
15
24
  * Added the Extractor class that does simple per-frame baking to STDOUT
16
25
  * Add bake_flame_channel binary
data/Manifest.txt CHANGED
@@ -4,11 +4,12 @@ Manifest.txt
4
4
  README.rdoc
5
5
  Rakefile
6
6
  bin/bake_flame_channel
7
+ lib/channel.rb
7
8
  lib/extractor.rb
8
9
  lib/flame_channel_parser.rb
9
10
  lib/interpolator.rb
10
- lib/parser_2011.rb
11
- lib/parser_2012.rb
11
+ lib/key.rb
12
+ lib/parser.rb
12
13
  lib/segments.rb
13
14
  test/channel_with_constants.dat
14
15
  test/sample_channel.dat
@@ -30,7 +31,10 @@ test/snaps/RefT_Steadicam_TwoKFs_HermiteAtBegin.timewarp
30
31
  test/snaps/TW.timewarp
31
32
  test/snaps/TW_SingleFrameExtrapolated_from2011.timewarp
32
33
  test/snaps/timewarp_where_interp_fails_at_end.timewarp
34
+ test/test_channel.rb
35
+ test/test_cli.rb
33
36
  test/test_extractor.rb
34
37
  test/test_flame_channel_parser.rb
35
38
  test/test_interpolator.rb
39
+ test/test_key.rb
36
40
  test/test_segments.rb
data/README.rdoc CHANGED
@@ -32,7 +32,7 @@ To use the library:
32
32
  frame_channel = channels.find{|c| c.name == "Timing/Timing" }
33
33
 
34
34
  # Grab the interpolator object for this channel.
35
- interpolator = FlameChannelParser::Interpolator.new(frame_channel)
35
+ interpolator = frame_channel.to_interpolator
36
36
 
37
37
  # Now sample from frame 20 to frame 250.
38
38
  # You can also sample at fractional frames if you want to.
data/Rakefile CHANGED
@@ -4,6 +4,8 @@ require 'rubygems'
4
4
  require 'hoe'
5
5
 
6
6
  Hoe.spec 'flame_channel_parser' do | p |
7
+ Hoe::RUBY_FLAGS.gsub!(/^\-w/, '') # No thanks undefined ivar warnings
8
+
7
9
  p.readme_file = 'README.rdoc'
8
10
  p.extra_rdoc_files = FileList['*.rdoc'] + FileList['*.txt']
9
11
  p.extra_deps = {"update_hints" => ">=0" }
@@ -1,8 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
- require File.expand_path(File.dirname(__FILE__)) + '/../lib/flame_channel_parser'
2
+ # == Synopsis
3
+ #
4
+ # Extract an animation channel from a Flame setup
5
+ #
6
+ # == Usage
7
+ #
8
+ # bake_flame_channel TW_015_v03.timewarp
9
+ #
10
+ # == Author
11
+ # Julik <me@julik.nl>
12
+
13
+ require File.expand_path(File.dirname(__FILE__)) + '/../lib/flame_channel_parser' unless defined?(FlameChannelParser)
3
14
  require 'optparse'
15
+ require 'rubygems'
16
+ require "update_hints"
4
17
 
5
- options = {}
18
+ options = {:destination => $stdout}
6
19
 
7
20
  op = OptionParser.new
8
21
  op.banner = "Usage: bake_flame_channel --channel Timing/Timing -e 123 /usr/discreet/projects/Luxury/timewarp/shot2_tw.timewarp > /mnt/3d/curves/shot2_tw.framecurve.txt\n" +
@@ -23,9 +36,17 @@ op.on(" -f", "--to-file FILENAME", String,
23
36
 
24
37
  op.parse!
25
38
  setup_path = ARGV.shift
26
- raise "No input file path provided. Please see bake_flame_channel --help for usage information" unless setup_path
39
+ unless setup_path
40
+ $stderr.puts "No input file path provided. Please see bake_flame_channel --help for usage information."
41
+ exit -1
42
+ end
43
+
44
+ unless File.exist?(setup_path)
45
+ $stderr.puts "File does not exist."
46
+ exit -1
47
+ end
27
48
 
28
- FlameChannelParser::Extractor.new.extract(setup_path, options)
49
+ FlameChannelParser::Extractor.extract(setup_path, options)
29
50
  options[:destination].close if options[:destination].respond_to?(:close)
30
51
 
31
- UpdateHints.version_check("flame_channel_parser", FlameChannelParser::VERSION)
52
+ UpdateHints.version_check("flame_channel_parser", FlameChannelParser::VERSION, $stderr)
data/lib/channel.rb ADDED
@@ -0,0 +1,36 @@
1
+ require "forwardable"
2
+
3
+ module FlameChannelParser
4
+ # Represents a channel parsed from the Flame setup. Contains
5
+ # the channel metadata and keyframes (Key objects).
6
+ # Supports the following standard Array methods:
7
+ # :empty?, :size, :each, :[], :push
8
+ class Channel
9
+ include Enumerable
10
+ extend Forwardable
11
+
12
+ attr_reader :node_type, :node_name
13
+ attr_accessor :base_value, :name, :extrapolation
14
+
15
+ def_delegators :@keys, :empty?, :length, :each, :[], :push, :<<
16
+
17
+ def initialize(channel_name, node_type, node_name)
18
+ @keys = []
19
+ @node_type, @node_name, @name = node_type, node_name, channel_name.strip
20
+ end
21
+
22
+ # Returns path to the channel (like axis1/position/x)
23
+ def path
24
+ [@node_name, name].compact.join("/")
25
+ end
26
+
27
+ # Get an Interpolator for this channel
28
+ def to_interpolator
29
+ FlameChannelParser::Interpolator.new(self)
30
+ end
31
+
32
+ def inspect
33
+ "<Channel (%s %s) with %d keys>" % [@node_type, path, @keys.size]
34
+ end
35
+ end
36
+ end
data/lib/extractor.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Extracts and bakes a specific animation channel
1
+ # Extracts and bakes a specific animation channel to a given buffer, one string per frame
2
2
  class FlameChannelParser::Extractor
3
3
 
4
4
  DEFAULT_CHANNEL_TO_EXTRACT = "Timing/Timing"
@@ -20,6 +20,12 @@ class FlameChannelParser::Extractor
20
20
  # :start_frame - From which frame the curve should be baked. Will default to the first keyframe of the curve
21
21
  # :end_frame - Upto which frame to bake. Will default to the last keyframe of the curve
22
22
  # :channel - Name of the channel to extract from the setup. Defaults to "Timing/Timing" (timewarp frame)
23
+ #
24
+ # Note that start_frame and end_frame will be converted to integers.
25
+ # The output will look like this:
26
+ #
27
+ # 1 123.456
28
+ # 2 124.567
23
29
  def self.extract(path, options = {})
24
30
  options = DEFAULTS.merge(options)
25
31
  File.open(path) do |f|
@@ -47,6 +53,7 @@ class FlameChannelParser::Extractor
47
53
 
48
54
  from_frame = start_frame || interpolator.first_defined_frame
49
55
  to_frame = end_frame || interpolator.last_defined_frame
56
+
50
57
  unless (from_frame && to_frame)
51
58
  raise NoKeyframesError, "This channel probably has no animation so there is no way to automatically tell how many keyframes it has. " +
52
59
  "Please set the start and end frame explicitly."
@@ -54,7 +61,10 @@ class FlameChannelParser::Extractor
54
61
 
55
62
  raise EmptySegmentError, "The segment you are trying to bake is too small (it has nothing in it)" if to_frame - from_frame < 1
56
63
 
57
- (from_frame..to_frame).each do | frame |
64
+ from_frame_i = from_frame.to_f.floor
65
+ to_frame_i = to_frame.to_f.ceil
66
+
67
+ (from_frame_i..to_frame_i).each do | frame |
58
68
  line = "%d\t%.5f\n" % [frame, interpolator.sample_at(frame)]
59
69
  to_io << line
60
70
  end
@@ -1,33 +1,12 @@
1
- require "delegate"
2
-
3
1
  module FlameChannelParser
4
- VERSION = '1.4.0'
2
+ VERSION = '2.0.1'
5
3
 
6
- # Parse a Flame setup into an array of ChannelBlock objects
4
+ # Parse a Flame setup into an array of Channel
7
5
  def self.parse(io)
8
- parser_class = detect_parser_class_from(io)
9
- parser_class.new.parse(io)
10
- end
11
-
12
- private
13
-
14
- def self.detect_parser_class_from(io)
15
- # Scan the IO
16
- until io.eof?
17
- str = io.gets
18
- if str =~ /RHandleX/ # Flame 2012, use that parser
19
- io.rewind
20
- return Parser2012
21
- end
22
- end
23
-
24
- io.rewind
25
- Parser2011
6
+ Parser.new.parse(io)
26
7
  end
27
8
  end
28
9
 
29
- require File.expand_path(File.dirname(__FILE__)) + "/parser_2011"
30
- require File.expand_path(File.dirname(__FILE__)) + "/parser_2012"
31
- require File.expand_path(File.dirname(__FILE__)) + "/segments"
32
- require File.expand_path(File.dirname(__FILE__)) + "/interpolator"
33
- require File.expand_path(File.dirname(__FILE__)) + "/extractor"
10
+ %w(
11
+ key channel parser segments interpolator extractor
12
+ ).each {|f| require File.expand_path(File.dirname(__FILE__) + "/" + f ) }
data/lib/interpolator.rb CHANGED
@@ -5,7 +5,7 @@ require File.expand_path(File.dirname(__FILE__)) + "/segments"
5
5
  # frames.
6
6
  #
7
7
  # i = Interpolator.new(parsed_channel)
8
- # i.value_at(245.5) # => will interpolate and return the value
8
+ # i.value_at(245.5) # => will interpolate and return the value at frame 245.5
9
9
  class FlameChannelParser::Interpolator
10
10
  include FlameChannelParser::Segments
11
11
 
@@ -19,20 +19,10 @@ class FlameChannelParser::Interpolator
19
19
  @extrap = channel.extrapolation
20
20
 
21
21
  # Edge case - channel has no anim at all
22
- if channel.length.zero?
23
- @segments << ConstantFunction.new(channel.base_value)
22
+ @segments = if channel.length.zero?
23
+ [ConstantFunction.new(channel.base_value)]
24
24
  else
25
-
26
- # First the prepolating segment
27
- @segments << pick_prepolation(channel.extrapolation, channel[0], channel[1])
28
-
29
- # Then all the intermediate segments, one segment between each pair of keys
30
- channel[0..-2].each_with_index do | key, index |
31
- @segments << key_pair_to_segment(key, channel[index + 1])
32
- end
33
-
34
- # so we just output it separately
35
- @segments << pick_extrapolation(channel.extrapolation, channel[-2], channel[-1])
25
+ create_segments_from_channel(channel)
36
26
  end
37
27
  end
38
28
 
@@ -65,24 +55,37 @@ class FlameChannelParser::Interpolator
65
55
 
66
56
  private
67
57
 
58
+ def create_segments_from_channel(channel)
59
+ # First the prepolating segment
60
+ segments = [pick_prepolation(channel.extrapolation, channel[0], channel[1])]
61
+
62
+ # Then all the intermediate segments, one segment between each pair of keys
63
+ channel[0..-2].each_with_index do | key, index |
64
+ segments << key_pair_to_segment(key, channel[index + 1])
65
+ end
66
+
67
+ # and the extrapolator
68
+ segments << pick_extrapolation(channel.extrapolation, channel[-2], channel[-1])
69
+ end
70
+
68
71
  def frame_number_in_revcycle(frame)
69
72
  animated_across = (last_defined_frame - first_defined_frame)
70
- anchor_frame = (frame < first_defined_frame) ? first_defined_frame : last_defined_frame
71
- frame_within_loop = (frame - anchor_frame) % animated_across
72
-
73
- coeff = ((frame - first_defined_frame) / animated_across).abs
74
- if (coeff % 2).zero?
75
- first_defined_frame + frame_within_loop
73
+ # Absolute offset from the first keyframe of the animated segment
74
+ offset = (frame - first_defined_frame).abs
75
+ absolute_unit = offset % animated_across
76
+ cycles = (offset / animated_across).floor
77
+ if (cycles % 2).zero?
78
+ first_defined_frame + absolute_unit
76
79
  else
77
- last_defined_frame - frame_within_loop
80
+ last_defined_frame - absolute_unit
78
81
  end
79
82
  end
80
83
 
81
84
  def frame_number_in_cycle(frame)
82
85
  animated_across = (last_defined_frame - first_defined_frame)
83
- anchor_frame = (frame < first_defined_frame) ? first_defined_frame : last_defined_frame
84
- frame_within_loop = (frame - anchor_frame) % animated_across
85
- first_defined_frame + frame_within_loop
86
+ offset = (frame - first_defined_frame)
87
+ modulo = (offset % animated_across)
88
+ first_defined_frame + modulo
86
89
  end
87
90
 
88
91
  def sample_from_segments(at_frame)
data/lib/key.rb ADDED
@@ -0,0 +1,76 @@
1
+ # Represents a keyframe
2
+ class FlameChannelParser::Key
3
+
4
+ # Frame on which the keyframe is set
5
+ attr_accessor :frame
6
+
7
+ # Value at this keyframe
8
+ attr_accessor :value
9
+
10
+ # Inteprolation type from this key onwards
11
+ attr_accessor :interpolation
12
+
13
+ # Curve order (relevant for 2012 only)
14
+ attr_accessor :curve_order, :curve_mode
15
+
16
+ # Left and right slope (will return raw slope values for pre-2012, and computed ones for 2012)
17
+ attr_accessor :left_slope, :right_slope
18
+
19
+ # Whether the tangents are broken at this keyframe
20
+ attr_accessor :break_slope
21
+
22
+ # Coordinates of the handles for 2012 setups
23
+ attr_accessor :l_handle_x, :l_handle_y, :r_handle_x, :r_handle_y
24
+
25
+ # Default value is 0.f
26
+ def value
27
+ @value.to_f
28
+ end
29
+
30
+ # Default frame is 1.0f
31
+ def frame
32
+ (@frame || 1).to_f
33
+ end
34
+
35
+ # Returns the RightSlope parameter of the keyframe which we use for interpolations
36
+ def left_slope
37
+ return right_slope unless break_slope
38
+
39
+ if has_2012_tangents? # 2012 setups do not have slopes but have tangents
40
+ dy = @value - @l_handle_y
41
+ dx = @l_handle_x.to_f - @frame
42
+ (dy / dx * -1)
43
+ else
44
+ @left_slope.to_f
45
+ end
46
+ end
47
+
48
+ # Returns the LeftSlope parameter of the keyframe which we use for interpolations
49
+ def right_slope
50
+ if has_2012_tangents?
51
+ dy = @value - @r_handle_y
52
+ dx = @frame.to_f - @r_handle_x
53
+ dy / dx
54
+ else
55
+ (@right_slope || nil).to_f
56
+ end
57
+ end
58
+
59
+ # Tells if this keyframe has 2012 tangents in it
60
+ def has_2012_tangents?
61
+ @has_tangents ||= !!(l_handle_x && l_handle_y)
62
+ end
63
+
64
+ # Adapter for old interpolation
65
+ def interpolation
66
+ # Just return the interpolation type for pre-2012 setups
67
+ return (@interpolation || :constant) unless has_2012_tangents?
68
+
69
+ return :constant if curve_order.to_s == "constant"
70
+ return :hermite if curve_order.to_s == "cubic" && (curve_mode.to_s == "hermite" || curve_mode.to_s == "natural")
71
+ return :bezier if curve_order.to_s == "cubic" && curve_mode.to_s == "bezier"
72
+ return :linear if curve_order.to_s == "linear"
73
+
74
+ raise "Cannot determine interpolation for #{inspect}"
75
+ end
76
+ end
data/lib/parser.rb ADDED
@@ -0,0 +1,115 @@
1
+ require "forwardable"
2
+
3
+ # Basic parser used for setups from versions up to 2011
4
+ module FlameChannelParser
5
+ class Parser
6
+ # Parses the setup passed in the IO
7
+ def parse(io)
8
+ channels = []
9
+ node_name, node_type = nil, nil
10
+
11
+ until io.eof?
12
+ line = io.gets
13
+ if line =~ NODE_TYPE_MATCHER
14
+ node_type = $1
15
+ elsif line =~ NODE_NAME_MATCHER
16
+ node_name = $1
17
+ elsif line =~ CHANNEL_MATCHER && channel_is_useful?($1)
18
+ channels << parse_channel(io, $1, node_type, node_name)
19
+ end
20
+ end
21
+
22
+ channels
23
+ end
24
+
25
+ # Override this method to skip some channels, this will speedup
26
+ # your code alot
27
+ def channel_is_useful?(channel_name)
28
+ true
29
+ end
30
+
31
+ # Defines a number of regular expression matchers applied to the file as it is being parsed
32
+ def key_matchers #:nodoc:
33
+ [
34
+ # Previously:
35
+
36
+ [:frame, :to_f, /Frame ([\-\d\.]+)/],
37
+ [:value, :to_f, /Value ([\-\d\.]+)/],
38
+ [:left_slope, :to_f, /LeftSlope ([\-\d\.]+)/],
39
+ [:right_slope, :to_f, /RightSlope ([\-\d\.]+)/],
40
+ [:interpolation, :to_s, /Interpolation (\w+)/],
41
+ [:break_slope, :to_s, /BreakSlope (\w+)/],
42
+
43
+ # 2012 intoroduces:
44
+
45
+ [:r_handle_x, :to_f, /RHandleX ([\-\d\.]+)/],
46
+ [:l_handle_x, :to_f, /LHandleX ([\-\d\.]+)/],
47
+ [:r_handle_y, :to_f, /RHandleY ([\-\d\.]+)/],
48
+ [:l_handle_y, :to_f, /LHandleY ([\-\d\.]+)/],
49
+ [:curve_mode, :to_s, /CurveMode (\w+)/],
50
+ [:curve_order, :to_s, /CurveOrder (\w+)/],
51
+ ]
52
+ end
53
+ base_value_matcher = /Value ([\-\d\.]+)/
54
+ keyframe_count_matcher = /Size (\d+)/
55
+
56
+ BASE_VALUE_MATCHER = /Value ([\-\d\.]+)/
57
+ KF_COUNT_MATCHER = /Size (\d+)/
58
+ EXTRAP_MATCHER = /Extrapolation (\w+)/
59
+ CHANNEL_MATCHER = /Channel (.+)\n/
60
+ NODE_TYPE_MATCHER = /Node (\w+)/
61
+ NODE_NAME_MATCHER = /Name (\w+)/
62
+ LITERALS = %w( linear constant natural hermite cubic bezier cycle revcycle )
63
+
64
+ def parse_channel(io, channel_name, node_type, node_name)
65
+ c = Channel.new(channel_name, node_type, node_name)
66
+
67
+ indent, end_mark = nil, "ENDMARK"
68
+
69
+ while line = io.gets
70
+
71
+ unless indent
72
+ indent = line.scan(/^(\s+)/)[1]
73
+ end_mark = "#{indent}End"
74
+ end
75
+
76
+ if line =~ KF_COUNT_MATCHER
77
+ $1.to_i.times { c.push(extract_key_from(io)) }
78
+ elsif line =~ BASE_VALUE_MATCHER# && empty?
79
+ c.base_value = $1.to_f
80
+ elsif line =~ EXTRAP_MATCHER
81
+ c.extrapolation = symbolize_literal($1)
82
+ elsif line.strip == end_mark
83
+ break
84
+ end
85
+ end
86
+
87
+ return c
88
+ end
89
+
90
+ def extract_key_from(io)
91
+ frame = nil
92
+ end_matcher = /End/
93
+ key = Key.new
94
+
95
+ until io.eof?
96
+ line = io.gets
97
+ if line =~ end_matcher
98
+ return key
99
+ else
100
+ key_matchers.each do | property, cast_method, pattern |
101
+ if line =~ pattern
102
+ v = symbolize_literal($1.send(cast_method))
103
+ key.send("#{property}=", v)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ raise "Did not detect any keyframes!"
109
+ end
110
+
111
+ def symbolize_literal(v)
112
+ LITERALS.include?(v) ? v.to_sym : v
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,22 @@
1
+ require "test/unit"
2
+ require File.dirname(__FILE__) + "/../lib/flame_channel_parser"
3
+
4
+ D = 0.001
5
+
6
+ class TestChannel < Test::Unit::TestCase
7
+ def test_responds_to_array_methods
8
+ c = FlameChannelParser::Channel.new("b/c", "d", "f")
9
+ [:empty?, :length, :each, :[], :push, :<<].each {|m| assert c.respond_to?(m) }
10
+ end
11
+
12
+ def test_path_for_present_nodename
13
+ c = FlameChannelParser::Channel.new("position/x", "Axis", "axis1")
14
+ assert_equal "axis1/position/x", c.path
15
+ end
16
+
17
+ def test_path_for_absent
18
+ c = FlameChannelParser::Channel.new("Frame/Frame", nil, nil)
19
+ assert_equal "Frame/Frame", c.path
20
+ end
21
+
22
+ end
data/test/test_cli.rb ADDED
@@ -0,0 +1,59 @@
1
+ require "test/unit"
2
+ require "stringio"
3
+ require "tempfile"
4
+
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")
9
+
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
27
+ end
28
+
29
+ def test_cli_with_no_args_produces_usage
30
+ status, o, e = cli('')
31
+ assert_equal -1, status
32
+ assert_match /No input file path provided/, e
33
+ assert_match /--help for usage information/, e
34
+ end
35
+
36
+ def test_cli_with_nonexisting_file
37
+ status, o, e = cli(" amazing.action")
38
+ assert_equal -1, status
39
+ assert_match /does not exist/, e
40
+ end
41
+
42
+ def test_cli_with_proper_output
43
+ full_path = File.expand_path(File.dirname(__FILE__)) + "/snaps/TW.timewarp"
44
+ status, output, e = cli(" " + full_path)
45
+ assert_equal 0, status
46
+ assert_equal 816, output.split("\n").length, "Should have output 816 frames"
47
+ end
48
+
49
+ def test_cli_with_output_to_file
50
+ tf = Tempfile.new("experiment")
51
+ full_path = File.expand_path(File.dirname(__FILE__)) + "/snaps/TW.timewarp"
52
+ status, output, e = cli(" --to-file " + tf.path + " " + full_path)
53
+
54
+ assert_equal 0, status
55
+ assert_equal 816, File.read(tf.path).split("\n").length, "Should have output 816 frames"
56
+ ensure
57
+ tf.close!
58
+ end
59
+ end
@@ -18,7 +18,7 @@ class TestFlameChannelParser < Test::Unit::TestCase
18
18
  assert_kind_of Array, channels
19
19
  assert_equal 1, channels.length, "Should find one channel"
20
20
 
21
- assert_kind_of FlameChannelParser::Parser2011::Channel, channels[0]
21
+ assert_kind_of FlameChannelParser::Channel, channels[0]
22
22
 
23
23
  ch = channels[0]
24
24
  assert_equal 4, ch.length
@@ -57,11 +57,11 @@ class TestInterpolator < Test::Unit::TestCase
57
57
 
58
58
  def test_simple_setup_from_2011
59
59
  data = File.open(File.dirname(__FILE__) + "/snaps/FLEM_curves_example.action")
60
- channels_in_action = FlameChannelParser::Parser2011.new.parse(data)
60
+ channels_in_action = FlameChannelParser.parse(data)
61
61
  channels_in_action.reject!{|c| c.length < 4 }
62
62
 
63
- reference = channels_in_action.find{|c| c.name == "position/x" && c.length > 2}
64
- sampled = channels_in_action.find{|c| c.name == "position/y" && c.length > 2}
63
+ reference = channels_in_action.find{|c| c.path == "axis1/position/x" }
64
+ sampled = channels_in_action.find{|c| c.path == "axis1/position/y"}
65
65
  assert_same_interpolation(1..200, reference, sampled)
66
66
  end
67
67
 
@@ -121,7 +121,7 @@ class TestInterpolator < Test::Unit::TestCase
121
121
  channels_in_tw = FlameChannelParser.parse(data)
122
122
  chan = channels_in_tw.find{|c| c.name == "Timing/Timing"}
123
123
  interp = chan.to_interpolator
124
- assert_in_delta -7, interp.sample_at(1), DELTA
124
+ assert_in_delta( -7, interp.sample_at(1), DELTA)
125
125
  assert_in_delta 492, interp.sample_at(502), DELTA
126
126
  assert_in_delta 492, interp.sample_at(502), DELTA
127
127
  assert_in_delta 494, interp.sample_at(504), DELTA
@@ -159,7 +159,6 @@ class TestInterpolator < Test::Unit::TestCase
159
159
  assert_in_delta 683.772, interp.sample_at(1), DELTA
160
160
  end
161
161
 
162
-
163
162
  def test_cycle_extrapolation
164
163
  data = File.open(File.dirname(__FILE__) + "/snaps/Cycle_and_revcycle.action")
165
164
  channels_in_ac = FlameChannelParser.parse(data)
data/test/test_key.rb ADDED
@@ -0,0 +1,86 @@
1
+ require "test/unit"
2
+ require File.dirname(__FILE__) + "/../lib/flame_channel_parser"
3
+
4
+ class TestKey < Test::Unit::TestCase
5
+ D = 0.001
6
+ def test_default_values
7
+ k = FlameChannelParser::Key.new
8
+ assert_in_delta 1.0, k.frame, D
9
+ assert_in_delta 0.0, k.value, D
10
+ assert_equal :constant, k.interpolation
11
+ assert_equal false, k.has_2012_tangents?
12
+
13
+ assert_nil k.l_handle_x
14
+ assert_nil k.l_handle_y
15
+ assert_nil k.r_handle_y
16
+ assert_nil k.r_handle_x
17
+ end
18
+
19
+ def test_constant_curve_mode_translated_to_interpolation
20
+ k = FlameChannelParser::Key.new
21
+ k.curve_mode = :constant
22
+ assert_equal :constant, k.interpolation
23
+ end
24
+
25
+ def test_hermite_curve_mode_translated_to_interpolation
26
+ k = FlameChannelParser::Key.new
27
+ assign_tangents_to(k)
28
+
29
+ k.curve_order = :cubic
30
+ k.curve_mode = :hermite
31
+ assert_equal :hermite, k.interpolation
32
+ end
33
+
34
+ def test_natural_curve_mode_translated_to_interpolation
35
+ k = FlameChannelParser::Key.new
36
+ assign_tangents_to(k)
37
+
38
+ k.curve_order = :cubic
39
+ k.curve_mode = :natural
40
+ assert_equal :hermite, k.interpolation
41
+ end
42
+
43
+ def test_bezier_curve_mode
44
+ k = FlameChannelParser::Key.new
45
+ assign_tangents_to(k)
46
+
47
+ k.curve_order = :cubic
48
+ k.curve_mode = :bezier
49
+ assert_equal :bezier, k.interpolation
50
+ end
51
+
52
+ def test_tangent_computed_from_right_handle
53
+ k = FlameChannelParser::Key.new
54
+ k.frame = 123
55
+ k.value = 123
56
+
57
+ k.l_handle_x = 120
58
+ k.l_handle_y = 110
59
+ k.r_handle_x = 124
60
+ k.r_handle_y = 145
61
+
62
+ assert_in_delta 22, k.right_slope, D
63
+ assert_in_delta 22, k.left_slope, D
64
+ end
65
+
66
+ def test_tangent_computed_from_right_handle_with_broken_slope
67
+ k = FlameChannelParser::Key.new
68
+ k.frame = 123
69
+ k.value = 123
70
+ k.break_slope = true
71
+
72
+ k.l_handle_x = 120
73
+ k.l_handle_y = 110
74
+ k.r_handle_x = 124
75
+ k.r_handle_y = 145
76
+
77
+ assert_in_delta 22, k.right_slope, D
78
+ assert_in_delta 4.333, k.left_slope, D
79
+ end
80
+
81
+ private
82
+
83
+ def assign_tangents_to(to_key)
84
+ to_key.l_handle_x = to_key.l_handle_y = to_key.r_handle_x = to_key.r_handle_y = 1
85
+ end
86
+ end
@@ -1,11 +1,11 @@
1
1
  require "test/unit"
2
2
  require File.dirname(__FILE__) + "/../lib/flame_channel_parser"
3
3
 
4
- D = 0.001
5
4
 
6
5
  include FlameChannelParser::Segments
7
6
 
8
7
  class TestConstantFunction < Test::Unit::TestCase
8
+ D = 0.001
9
9
  def test_segment
10
10
  seg = ConstantFunction.new(123.4)
11
11
  assert seg.defines?(-1), "The segment should define this frame"
@@ -16,6 +16,7 @@ end
16
16
 
17
17
 
18
18
  class TestConstantSegment < Test::Unit::TestCase
19
+ D = 0.001
19
20
  def test_segment
20
21
  seg = ConstantSegment.new(12, 25, 2.5)
21
22
 
@@ -31,7 +32,7 @@ class TestConstantSegment < Test::Unit::TestCase
31
32
  end
32
33
 
33
34
  class TestBezierSegment < Test::Unit::TestCase
34
-
35
+ D = 0.001
35
36
  def test_segment
36
37
  seg = BezierSegment.new(
37
38
  frame_from = 117,
@@ -54,6 +55,8 @@ class TestBezierSegment < Test::Unit::TestCase
54
55
  end
55
56
 
56
57
  class TestLinearSegment < Test::Unit::TestCase
58
+ D = 0.001
59
+
57
60
  def test_segment
58
61
  seg = LinearSegment.new(12, 25, 2.5, 4.5)
59
62
 
@@ -80,6 +83,8 @@ class TestConstantPrepolate < Test::Unit::TestCase
80
83
  end
81
84
 
82
85
  class TestHermiteSegment < Test::Unit::TestCase
86
+ D = 0.001
87
+
83
88
  def test_last_curve_segment
84
89
  refdata = %w(
85
90
  258.239
metadata CHANGED
@@ -1,13 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flame_channel_parser
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
5
4
  prerelease:
6
- segments:
7
- - 1
8
- - 4
9
- - 0
10
- version: 1.4.0
5
+ version: 2.0.1
11
6
  platform: ruby
12
7
  authors:
13
8
  - Julik Tarkhanov
@@ -15,8 +10,7 @@ autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
12
 
18
- date: 2011-06-06 00:00:00 +02:00
19
- default_executable:
13
+ date: 2011-06-23 00:00:00 Z
20
14
  dependencies:
21
15
  - !ruby/object:Gem::Dependency
22
16
  name: update_hints
@@ -26,9 +20,6 @@ dependencies:
26
20
  requirements:
27
21
  - - ">="
28
22
  - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
23
  version: "0"
33
24
  type: :runtime
34
25
  version_requirements: *id001
@@ -40,12 +31,7 @@ dependencies:
40
31
  requirements:
41
32
  - - ">="
42
33
  - !ruby/object:Gem::Version
43
- hash: 41
44
- segments:
45
- - 2
46
- - 9
47
- - 1
48
- version: 2.9.1
34
+ version: 2.9.4
49
35
  type: :development
50
36
  version_requirements: *id002
51
37
  description: |-
@@ -68,11 +54,12 @@ files:
68
54
  - README.rdoc
69
55
  - Rakefile
70
56
  - bin/bake_flame_channel
57
+ - lib/channel.rb
71
58
  - lib/extractor.rb
72
59
  - lib/flame_channel_parser.rb
73
60
  - lib/interpolator.rb
74
- - lib/parser_2011.rb
75
- - lib/parser_2012.rb
61
+ - lib/key.rb
62
+ - lib/parser.rb
76
63
  - lib/segments.rb
77
64
  - test/channel_with_constants.dat
78
65
  - test/sample_channel.dat
@@ -94,12 +81,14 @@ files:
94
81
  - test/snaps/TW.timewarp
95
82
  - test/snaps/TW_SingleFrameExtrapolated_from2011.timewarp
96
83
  - test/snaps/timewarp_where_interp_fails_at_end.timewarp
84
+ - test/test_channel.rb
85
+ - test/test_cli.rb
97
86
  - test/test_extractor.rb
98
87
  - test/test_flame_channel_parser.rb
99
88
  - test/test_interpolator.rb
89
+ - test/test_key.rb
100
90
  - test/test_segments.rb
101
91
  - .gemtest
102
- has_rdoc: true
103
92
  homepage: http://guerilla-di.org/flame-channel-parser
104
93
  licenses: []
105
94
 
@@ -114,28 +103,25 @@ required_ruby_version: !ruby/object:Gem::Requirement
114
103
  requirements:
115
104
  - - ">="
116
105
  - !ruby/object:Gem::Version
117
- hash: 3
118
- segments:
119
- - 0
120
106
  version: "0"
121
107
  required_rubygems_version: !ruby/object:Gem::Requirement
122
108
  none: false
123
109
  requirements:
124
110
  - - ">="
125
111
  - !ruby/object:Gem::Version
126
- hash: 3
127
- segments:
128
- - 0
129
112
  version: "0"
130
113
  requirements: []
131
114
 
132
115
  rubyforge_project: flame_channel_parser
133
- rubygems_version: 1.4.1
116
+ rubygems_version: 1.8.5
134
117
  signing_key:
135
118
  specification_version: 3
136
119
  summary: Includes a small library for extracting, parsing and baking animation curves made on Discrodesk Floke/Inflinto, also known as flame
137
120
  test_files:
121
+ - test/test_channel.rb
122
+ - test/test_cli.rb
138
123
  - test/test_extractor.rb
139
124
  - test/test_flame_channel_parser.rb
140
125
  - test/test_interpolator.rb
126
+ - test/test_key.rb
141
127
  - test/test_segments.rb
data/lib/parser_2011.rb DELETED
@@ -1,156 +0,0 @@
1
- require "delegate"
2
-
3
- # Basic parser used for setups from versions up to 2011
4
- class FlameChannelParser::Parser2011
5
-
6
- # Represents a keyframe
7
- class Key < Struct.new(:frame, :value, :interpolation, :extrapolation, :left_slope, :right_slope, :break_slope)
8
-
9
- # Unless the key is broken? we should just use the right slope
10
- def left_slope
11
- return right_slope unless broken?
12
- super
13
- end
14
-
15
- # Tells whether the slope of this keyframe is broken (not smooth)
16
- def broken?
17
- break_slope
18
- end
19
- end
20
-
21
- # Defines a number of regular expression matchers applied to the file as it is being parsed
22
- def matchers
23
- [
24
- [:frame, :to_i, /Frame ([\-\d\.]+)/],
25
- [:value, :to_f, /Value ([\-\d\.]+)/],
26
- [:left_slope, :to_f, /LeftSlope ([\-\d\.]+)/],
27
- [:right_slope, :to_f, /RightSlope ([\-\d\.]+)/],
28
- [:interpolation, :to_s, /Interpolation (\w+)/],
29
- [:extrapolation, :to_s, /Extrapolation (\w+)/],
30
- [:break_slope, :to_s, /BreakSlope (\w+)/]
31
- ]
32
- end
33
-
34
- def create_key
35
- Key.new
36
- end
37
-
38
- # Represents a channel parsed from the Flame setup. Contains
39
- # the channel metadata and keyframes
40
- class Channel < DelegateClass(Array)
41
- attr_reader :node_type
42
- attr_reader :node_name
43
- attr_accessor :base_value
44
- attr_accessor :name
45
- attr_accessor :extrapolation
46
-
47
- def initialize(io, channel_name, parent_parser, node_type, node_name)
48
- super([])
49
-
50
- @node_type = node_type
51
- @node_name = node_name
52
- @parser = parent_parser
53
- @name = channel_name.strip
54
-
55
- base_value_matcher = /Value ([\-\d\.]+)/
56
- keyframe_count_matcher = /Size (\d+)/
57
- extrapolation_matcher = /Extrapolation (\w+)/
58
-
59
- indent = nil
60
-
61
- while line = io.gets
62
-
63
- unless indent
64
- indent = line.scan(/^(\s+)/)[1]
65
- end_mark = "#{indent}End"
66
- end
67
-
68
- if line =~ keyframe_count_matcher
69
- $1.to_i.times { push(extract_key_from(io)) }
70
- elsif line =~ base_value_matcher && empty?
71
- self.base_value = $1.to_f
72
- elsif line =~ extrapolation_matcher
73
- self.extrapolation = symbolize_literal($1)
74
- elsif line.strip == end_mark
75
- break
76
- end
77
- end
78
-
79
- end
80
-
81
- def path
82
- [@node_name, name].compact.join("/")
83
- end
84
-
85
- # Get an Interpolator from this channel
86
- def to_interpolator
87
- FlameChannelParser::Interpolator.new(self)
88
- end
89
-
90
- private
91
-
92
- def create_key
93
- Key.new
94
- end
95
-
96
- INTERPS = [:constant, :linear, :hermite, :natural, :bezier]
97
-
98
- def extract_key_from(io)
99
- frame = nil
100
- end_matcher = /End/
101
-
102
- key = @parser.create_key
103
- matchers = @parser.matchers
104
-
105
- until io.eof?
106
- line = io.gets
107
- if line =~ end_matcher
108
- return key
109
- else
110
- matchers.each do | property, cast_method, pattern |
111
- if line =~ pattern
112
- v = symbolize_literal($1.send(cast_method))
113
- key.send("#{property}=", v)
114
- end
115
- end
116
- end
117
- end
118
- raise "Did not detect any keyframes!"
119
- end
120
-
121
- LITERALS = %w( linear constant natural hermite cubic bezier cycle revcycle )
122
-
123
- def symbolize_literal(v)
124
- LITERALS.include?(v) ? v.to_sym : v
125
- end
126
-
127
- end
128
-
129
- CHANNEL_MATCHER = /Channel (.+)\n/
130
- NODE_TYPE_MATCHER = /Node (\w+)/
131
- NODE_NAME_MATCHER = /Name (\w+)/
132
-
133
- def parse(io)
134
- channels = []
135
- node_name, node_type = nil, nil
136
-
137
- until io.eof?
138
- line = io.gets
139
- if line =~ NODE_TYPE_MATCHER
140
- node_type = $1
141
- elsif line =~ NODE_NAME_MATCHER
142
- node_name = $1
143
- elsif line =~ CHANNEL_MATCHER && channel_is_useful?($1)
144
- channels << Channel.new(io, $1, self, node_type, node_name)
145
- end
146
- end
147
- channels
148
- end
149
-
150
- # Override this method to skip some channels, this will speedup
151
- # your code alot
152
- def channel_is_useful?(channel_name)
153
- true
154
- end
155
-
156
- end
data/lib/parser_2012.rb DELETED
@@ -1,59 +0,0 @@
1
- require "delegate"
2
- require File.dirname(__FILE__) + "/interpolator"
3
-
4
- # This parser is automatically used for 2012 setups
5
- class FlameChannelParser::Parser2012 < FlameChannelParser::Parser2011
6
-
7
- class ModernKey < Struct.new(:frame, :value, :r_handle_x, :l_handle_x, :r_handle_y, :l_handle_y, :curve_mode, :curve_order, :break_slope)
8
- alias_method :to_s, :inspect
9
-
10
- # Adapter for old interpolation
11
- def interpolation
12
- return :constant if curve_order.to_s == "constant"
13
- return :hermite if curve_order.to_s == "cubic" && (curve_mode.to_s == "hermite" || curve_mode.to_s == "natural")
14
- return :bezier if curve_order.to_s == "cubic" && curve_mode.to_s == "bezier"
15
- return :linear if curve_order.to_s == "linear"
16
-
17
- raise "Cannot determine interpolation for #{self.inspect}"
18
- end
19
-
20
- # Compute pre-212 slope which we use for interpolations
21
- def left_slope
22
- return right_slope unless broken?
23
-
24
- dy = value - l_handle_y
25
- dx = l_handle_x - frame
26
- dy / dx * -1
27
- end
28
-
29
- # Compute pre-212 slope which we use for interpolations
30
- def right_slope
31
- dy = value - r_handle_y
32
- dx = frame - r_handle_x
33
- dy / dx
34
- end
35
-
36
- def broken?
37
- break_slope
38
- end
39
- end
40
-
41
- def matchers
42
- [
43
- [:frame, :to_i, /Frame ([\-\d\.]+)/],
44
- [:value, :to_f, /Value ([\-\d\.]+)/],
45
- [:r_handle_x, :to_f, /RHandleX ([\-\d\.]+)/],
46
- [:l_handle_x, :to_f, /LHandleX ([\-\d\.]+)/],
47
- [:r_handle_y, :to_f, /RHandleY ([\-\d\.]+)/],
48
- [:l_handle_y, :to_f, /LHandleY ([\-\d\.]+)/],
49
- [:curve_mode, :to_s, /CurveMode (\w+)/],
50
- [:curve_order, :to_s, /CurveOrder (\w+)/],
51
- [:break_slope, :to_s, /BreakSlope (\w+)/],
52
- ]
53
- end
54
-
55
-
56
- def create_key
57
- ModernKey.new
58
- end
59
- end