flame_channel_parser 1.4.0 → 2.0.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 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