flame_channel_parser 1.3.1 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,7 @@
1
+ === 1.3.2 / 2011-05-30
2
+
3
+ * Fix linear extrapolation occurring after linear keyframes, enforce the tangent/increment of zero for linear extrap. segments
4
+
1
5
  === 1.3.1 / 2011-05-30
2
6
 
3
7
  * Add minor fixes and imporvements to the curve extraction bin
@@ -8,16 +12,6 @@
8
12
  * Add bake_flame_channel binary
9
13
  * Ensure linear prepolation works when counting to a linear interpolated keyframe
10
14
 
11
- === 1.2.0 / 2011-05-20
12
-
13
- * Ensure single-key channels interpolate correctly
14
-
15
- === 1.1.1 / 2011-05-17
16
-
17
- * Added support for 2012 Bezier splines
18
-
19
- === 1.1.1 / 2011-05-17
20
-
21
15
  * Added support for 2012 Bezier splines
22
16
 
23
17
  === 1.0.0 / 2011-03-21
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
- require File.dirname(__FILE__) + '/../lib/flame_channel_parser'
2
+ require File.expand_path(File.dirname(__FILE__)) + '/../lib/flame_channel_parser'
3
3
  require 'optparse'
4
4
 
5
5
  options = {}
6
6
 
7
7
  op = OptionParser.new
8
- 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" +
8
+ 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" +
9
9
  "The output file can be used as Time+Value ASCII input for Nuke " +
10
10
  "or parsed with any simple script"
11
11
  op.on(" -c", "--channel CHANNEL_NAME", String,
data/lib/extractor.rb CHANGED
@@ -10,27 +10,40 @@ class FlameChannelParser::Extractor
10
10
  # Raised when you try to autodetect the length of a channel that has no keyframes
11
11
  class NoKeyframesError < RuntimeError; end
12
12
 
13
- # Pass the path to Flame setup here and you will get the timewarp curve on STDOUT
13
+ # Raised when you try to bake 0 or negative amount of frames
14
+ class EmptySegmentError < RuntimeError; end
15
+
16
+ # Pass the path to Flame setup here and you will get the animation curve on the object passed in
17
+ # the :destionation option (defaults to STDOUT). The following options are accepted:
18
+ #
19
+ # :destination - The object to write the output to, anything that responds to shovel (<<) will do
20
+ # :start_frame - From which frame the curve should be baked. Will default to the first keyframe of the curve
21
+ # :end_frame - Upto which frame to bake. Will default to the last keyframe of the curve
22
+ # :channel - Name of the channel to extract from the setup. Defaults to "Timing/Timing" (timewarp frame)
14
23
  def self.extract(path, options = {})
15
24
  options = DEFAULTS.merge(options)
16
- File.open(path) do | f |
25
+ File.open(path) do |f|
17
26
  channels = FlameChannelParser.parse(f)
18
- selected_channel = channels.find{|c| options[:channel] == c.path }
19
- unless selected_channel
20
- message = "Channel #{options[:channel]} not found in this setup (set the channel with the --channel option). Found other channels though:"
21
- message << "\n"
22
- message += channels.map{|c| "\t%s\n" % c.path }.join
23
- raise ChannelNotFoundError, message
24
- end
25
-
26
- write_channel(selected_channel, options[:destination], options[:start_frame], options[:end_frame])
27
+ selected_channel = find_channel_in(channels, options[:channel])
28
+ interpolator = FlameChannelParser::Interpolator.new(selected_channel)
29
+ write_channel(interpolator, options[:destination], options[:start_frame], options[:end_frame])
27
30
  end
28
31
  end
29
32
 
30
33
  private
31
34
 
32
- def self.write_channel(channel, to_io, start_frame, end_frame)
33
- interpolator = FlameChannelParser::Interpolator.new(channel)
35
+ def self.find_channel_in(channels, channel_path)
36
+ selected_channel = channels.find{|c| channel_path == c.path }
37
+ unless selected_channel
38
+ message = "Channel #{channel_path.inspect} not found in this setup (set the channel with the --channel option). Found other channels though:"
39
+ message << "\n"
40
+ message += channels.map{|c| "\t%s\n" % c.path }.join
41
+ raise ChannelNotFoundError, message
42
+ end
43
+ selected_channel
44
+ end
45
+
46
+ def self.write_channel(interpolator, to_io, start_frame, end_frame)
34
47
 
35
48
  from_frame = start_frame || interpolator.first_defined_frame
36
49
  to_frame = end_frame || interpolator.last_defined_frame
@@ -39,8 +52,11 @@ class FlameChannelParser::Extractor
39
52
  "Please set the start and end frame explicitly."
40
53
  end
41
54
 
55
+ raise EmptySegmentError, "The segment you are trying to bake is too small (it has nothing in it)" if to_frame - from_frame < 1
56
+
42
57
  (from_frame..to_frame).each do | frame |
43
- to_io.puts("%d\t%.5f" % [frame, interpolator.sample_at(frame)])
58
+ line = "%d\t%.5f\n" % [frame, interpolator.sample_at(frame)]
59
+ to_io << line
44
60
  end
45
61
  end
46
62
 
@@ -1,7 +1,7 @@
1
1
  require "delegate"
2
2
 
3
3
  module FlameChannelParser
4
- VERSION = '1.3.1'
4
+ VERSION = '1.3.2'
5
5
 
6
6
  # Parse a Flame setup into an array of ChannelBlock objects
7
7
  def self.parse(io)
data/lib/interpolator.rb CHANGED
@@ -16,6 +16,7 @@ class FlameChannelParser::Interpolator
16
16
  # segments from which samples can be made
17
17
  def initialize(channel)
18
18
  @segments = []
19
+ @extrap = channel.extrapolation
19
20
 
20
21
  # Edge case - channel has no anim at all
21
22
  if channel.length.zero?
@@ -31,16 +32,27 @@ class FlameChannelParser::Interpolator
31
32
  end
32
33
 
33
34
  # so we just output it separately
34
- @segments << pick_extrapolation(channel.extrapolation, channel[-1])
35
+ @segments << pick_extrapolation(channel.extrapolation, channel[-2], channel[-1])
35
36
  end
36
37
  end
37
38
 
38
39
  # Sample the value of the animation curve at this frame
39
40
  def sample_at(frame)
40
- segment = @segments.find{|s| s.defines?(frame) }
41
- raise "No segment on this curve that can interpolate the value at #{frame}" unless segment
41
+ # No test files present for now so we turn this off
42
+ # if [:cycle, :rev_cycle].include?(frame)
43
+ # # Recompute the frame number and retry
44
+ # unless frame >= first_defined_frame && frame <= last_defined_frame
45
+ # animated_on = (last_defined_frame - first_defined_frame)
46
+ # fdiff = if frame < first_defined_frame
47
+ # (first_defined_frame - frame) % animated_on
48
+ # else
49
+ # (frame - last_defined_frame) % animated_on
50
+ # end
51
+ # sample_from_segments(first_defined_frame + fdiff)
52
+ # end
53
+ # end
42
54
 
43
- segment.value_at(frame)
55
+ sample_from_segments(frame)
44
56
  end
45
57
 
46
58
  # Returns the first frame number that is concretely defined as a keyframe
@@ -61,6 +73,12 @@ class FlameChannelParser::Interpolator
61
73
 
62
74
  private
63
75
 
76
+ def sample_from_segments(at_frame)
77
+ segment = @segments.find{|s| s.defines?(at_frame) }
78
+ raise "No segment on this curve that can interpolate the value at #{frame}" unless segment
79
+ segment.value_at(at_frame)
80
+ end
81
+
64
82
  def pick_prepolation(extrap_symbol, first_key, second_key)
65
83
  if extrap_symbol == :linear && second_key
66
84
  if first_key.interpolation != :linear
@@ -76,9 +94,16 @@ class FlameChannelParser::Interpolator
76
94
  end
77
95
  end
78
96
 
79
- def pick_extrapolation(extrap_symbol, last_key)
97
+ def pick_extrapolation(extrap_symbol, previous_key, last_key)
80
98
  if extrap_symbol == :linear
81
- LinearExtrapolate.new(last_key.frame, last_key.value, last_key.right_slope)
99
+ if previous_key && last_key.interpolation == :linear
100
+ # For linear keys the tangent actually does not do anything, so we need to look a frame
101
+ # ahead and compute the increment
102
+ increment = (last_key.value - previous_key.value) / (last_key.frame - previous_key.frame)
103
+ LinearExtrapolate.new(last_key.frame, last_key.value, increment)
104
+ else
105
+ LinearExtrapolate.new(last_key.frame, last_key.value, last_key.right_slope)
106
+ end
82
107
  else
83
108
  ConstantExtrapolate.new(last_key.frame, last_key.value)
84
109
  end
data/lib/segments.rb CHANGED
@@ -1,11 +1,9 @@
1
1
  require "matrix"
2
2
 
3
-
4
- # :nodoc:
5
- module FlameChannelParser::Segments
3
+ module FlameChannelParser::Segments #:nodoc:
6
4
 
7
5
  # This segment just stays on the value of it's keyframe
8
- class ConstantSegment
6
+ class ConstantSegment #:nodoc:
9
7
 
10
8
  NEG_INF = (-1.0/0.0)
11
9
  POS_INF = (1.0/0.0)
@@ -30,7 +28,7 @@ module FlameChannelParser::Segments
30
28
  end
31
29
 
32
30
  # This segment linearly interpolates
33
- class LinearSegment < ConstantSegment
31
+ class LinearSegment < ConstantSegment #:nodoc:
34
32
 
35
33
  def initialize(from_frame, to_frame, value1, value2)
36
34
  @vint = (value2 - value1)
@@ -46,7 +44,7 @@ module FlameChannelParser::Segments
46
44
 
47
45
  # This segment does Hermite interpolation
48
46
  # using the Flame algo.
49
- class HermiteSegment < LinearSegment
47
+ class HermiteSegment < LinearSegment #:nodoc:
50
48
 
51
49
  # In Ruby matrix columns are arrays, so here we go
52
50
  HERMATRIX = Matrix[
@@ -94,7 +92,7 @@ module FlameChannelParser::Segments
94
92
 
95
93
  end
96
94
 
97
- class BezierSegment < LinearSegment
95
+ class BezierSegment < LinearSegment #:nodoc:
98
96
  Pt = Struct.new(:x, :y, :tanx, :tany)
99
97
 
100
98
  def initialize(x1, x2, y1, y2, t1x, t1y, t2x, t2y)
@@ -185,7 +183,7 @@ module FlameChannelParser::Segments
185
183
  end
186
184
 
187
185
  # This segment does prepolation of a constant value
188
- class ConstantPrepolate < LinearSegment
186
+ class ConstantPrepolate < LinearSegment #:nodoc:
189
187
  def initialize(upto_frame, base_value)
190
188
  @value = base_value
191
189
  @end_frame = upto_frame
@@ -198,13 +196,12 @@ module FlameChannelParser::Segments
198
196
  end
199
197
 
200
198
  # This segment does prepolation with a linear coefficient
201
- class LinearPrepolate < LinearSegment
199
+ class LinearPrepolate < LinearSegment #:nodoc:
202
200
  def initialize(upto_frame, base_value, tangent)
203
201
  @value = base_value
204
202
  @end_frame = upto_frame
205
203
  @start_frame = NEG_INF
206
204
  @tangent = tangent.to_f
207
-
208
205
  end
209
206
 
210
207
  def value_at(frame)
@@ -214,7 +211,7 @@ module FlameChannelParser::Segments
214
211
  end
215
212
 
216
213
  # This segment does extrapolation using a constant value
217
- class ConstantExtrapolate < LinearSegment
214
+ class ConstantExtrapolate < LinearSegment #:nodoc:
218
215
  def initialize(from_frame, base_value)
219
216
  @start_frame = from_frame
220
217
  @base_value = base_value
@@ -227,10 +224,10 @@ module FlameChannelParser::Segments
227
224
  end
228
225
 
229
226
  # This segment does extrapolation using the tangent from the preceding keyframe
230
- class LinearExtrapolate < ConstantExtrapolate
227
+ class LinearExtrapolate < ConstantExtrapolate #:nodoc:
231
228
  def initialize(from_frame, base_value, tangent)
232
229
  super(from_frame, base_value)
233
- @tangent = tangent
230
+ @tangent = tangent.to_f
234
231
  end
235
232
 
236
233
  def value_at(frame)
@@ -240,7 +237,7 @@ module FlameChannelParser::Segments
240
237
  end
241
238
 
242
239
  # This can be used for an anim curve that stays constant all along
243
- class ConstantFunction < ConstantSegment
240
+ class ConstantFunction < ConstantSegment #:nodoc:
244
241
 
245
242
  def defines?(frame)
246
243
  true
@@ -255,5 +252,3 @@ module FlameChannelParser::Segments
255
252
  end
256
253
  end
257
254
  end
258
-
259
- # :nodoc:
@@ -210,120 +210,120 @@
210
210
  228 5.00000
211
211
  229 3.00000
212
212
  230 1.00000
213
- 231 -0.57143
214
- 232 -2.14286
215
- 233 -3.71429
216
- 234 -5.28572
217
- 235 -6.85715
218
- 236 -8.42858
219
- 237 -10.00001
220
- 238 -11.57144
221
- 239 -13.14287
222
- 240 -14.71430
223
- 241 -16.28573
224
- 242 -17.85716
225
- 243 -19.42859
226
- 244 -21.00002
227
- 245 -22.57145
228
- 246 -24.14288
229
- 247 -25.71431
230
- 248 -27.28574
231
- 249 -28.85717
232
- 250 -30.42860
233
- 251 -32.00003
234
- 252 -33.57146
235
- 253 -35.14289
236
- 254 -36.71432
237
- 255 -38.28575
238
- 256 -39.85718
239
- 257 -41.42861
240
- 258 -43.00004
241
- 259 -44.57147
242
- 260 -46.14290
243
- 261 -47.71433
244
- 262 -49.28576
245
- 263 -50.85719
246
- 264 -52.42862
247
- 265 -54.00005
248
- 266 -55.57148
249
- 267 -57.14291
250
- 268 -58.71434
251
- 269 -60.28577
252
- 270 -61.85720
253
- 271 -63.42863
254
- 272 -65.00006
255
- 273 -66.57149
256
- 274 -68.14292
257
- 275 -69.71435
258
- 276 -71.28578
259
- 277 -72.85721
260
- 278 -74.42864
261
- 279 -76.00007
262
- 280 -77.57150
263
- 281 -79.14293
264
- 282 -80.71436
265
- 283 -82.28579
266
- 284 -83.85722
267
- 285 -85.42865
268
- 286 -87.00008
269
- 287 -88.57151
270
- 288 -90.14294
271
- 289 -91.71437
272
- 290 -93.28580
273
- 291 -94.85723
274
- 292 -96.42866
275
- 293 -98.00009
276
- 294 -99.57152
277
- 295 -101.14295
278
- 296 -102.71438
279
- 297 -104.28581
280
- 298 -105.85724
281
- 299 -107.42867
282
- 300 -109.00010
283
- 301 -110.57153
284
- 302 -112.14296
285
- 303 -113.71439
286
- 304 -115.28582
287
- 305 -116.85725
288
- 306 -118.42868
289
- 307 -120.00011
290
- 308 -121.57154
291
- 309 -123.14297
292
- 310 -124.71440
293
- 311 -126.28583
294
- 312 -127.85726
295
- 313 -129.42869
296
- 314 -131.00012
297
- 315 -132.57155
298
- 316 -134.14298
299
- 317 -135.71441
300
- 318 -137.28584
301
- 319 -138.85727
302
- 320 -140.42870
303
- 321 -142.00013
304
- 322 -143.57156
305
- 323 -145.14299
306
- 324 -146.71442
307
- 325 -148.28585
308
- 326 -149.85728
309
- 327 -151.42871
310
- 328 -153.00014
311
- 329 -154.57157
312
- 330 -156.14300
313
- 331 -157.71443
314
- 332 -159.28586
315
- 333 -160.85729
316
- 334 -162.42872
317
- 335 -164.00015
318
- 336 -165.57158
319
- 337 -167.14301
320
- 338 -168.71444
321
- 339 -170.28587
322
- 340 -171.85730
323
- 341 -173.42873
324
- 342 -175.00016
325
- 343 -176.57159
326
- 344 -178.14302
327
- 345 -179.71445
328
- 346 -181.28588
329
- 347 -182.85731
213
+ 231 -1.00000
214
+ 232 -3.00000
215
+ 233 -5.00000
216
+ 234 -7.00000
217
+ 235 -9.00000
218
+ 236 -11.00000
219
+ 237 -13.00000
220
+ 238 -15.00000
221
+ 239 -17.00000
222
+ 240 -19.00000
223
+ 241 -21.00000
224
+ 242 -23.00000
225
+ 243 -25.00000
226
+ 244 -27.00000
227
+ 245 -29.00000
228
+ 246 -31.00000
229
+ 247 -33.00000
230
+ 248 -35.00000
231
+ 249 -37.00000
232
+ 250 -39.00000
233
+ 251 -41.00000
234
+ 252 -43.00000
235
+ 253 -45.00000
236
+ 254 -47.00000
237
+ 255 -49.00000
238
+ 256 -51.00000
239
+ 257 -53.00000
240
+ 258 -55.00000
241
+ 259 -57.00000
242
+ 260 -59.00000
243
+ 261 -61.00000
244
+ 262 -63.00000
245
+ 263 -65.00000
246
+ 264 -67.00000
247
+ 265 -69.00000
248
+ 266 -71.00000
249
+ 267 -73.00000
250
+ 268 -75.00000
251
+ 269 -77.00000
252
+ 270 -79.00000
253
+ 271 -81.00000
254
+ 272 -83.00000
255
+ 273 -85.00000
256
+ 274 -87.00000
257
+ 275 -89.00000
258
+ 276 -91.00000
259
+ 277 -93.00000
260
+ 278 -95.00000
261
+ 279 -97.00000
262
+ 280 -99.00000
263
+ 281 -101.00000
264
+ 282 -103.00000
265
+ 283 -105.00000
266
+ 284 -107.00000
267
+ 285 -109.00000
268
+ 286 -111.00000
269
+ 287 -113.00000
270
+ 288 -115.00000
271
+ 289 -117.00000
272
+ 290 -119.00000
273
+ 291 -121.00000
274
+ 292 -123.00000
275
+ 293 -125.00000
276
+ 294 -127.00000
277
+ 295 -129.00000
278
+ 296 -131.00000
279
+ 297 -133.00000
280
+ 298 -135.00000
281
+ 299 -137.00000
282
+ 300 -139.00000
283
+ 301 -141.00000
284
+ 302 -143.00000
285
+ 303 -145.00000
286
+ 304 -147.00000
287
+ 305 -149.00000
288
+ 306 -151.00000
289
+ 307 -153.00000
290
+ 308 -155.00000
291
+ 309 -157.00000
292
+ 310 -159.00000
293
+ 311 -161.00000
294
+ 312 -163.00000
295
+ 313 -165.00000
296
+ 314 -167.00000
297
+ 315 -169.00000
298
+ 316 -171.00000
299
+ 317 -173.00000
300
+ 318 -175.00000
301
+ 319 -177.00000
302
+ 320 -179.00000
303
+ 321 -181.00000
304
+ 322 -183.00000
305
+ 323 -185.00000
306
+ 324 -187.00000
307
+ 325 -189.00000
308
+ 326 -191.00000
309
+ 327 -193.00000
310
+ 328 -195.00000
311
+ 329 -197.00000
312
+ 330 -199.00000
313
+ 331 -201.00000
314
+ 332 -203.00000
315
+ 333 -205.00000
316
+ 334 -207.00000
317
+ 335 -209.00000
318
+ 336 -211.00000
319
+ 337 -213.00000
320
+ 338 -215.00000
321
+ 339 -217.00000
322
+ 340 -219.00000
323
+ 341 -221.00000
324
+ 342 -223.00000
325
+ 343 -225.00000
326
+ 344 -227.00000
327
+ 345 -229.00000
328
+ 346 -231.00000
329
+ 347 -233.00000
@@ -22,14 +22,23 @@ class TestExtractor < Test::Unit::TestCase
22
22
  io = StringIO.new
23
23
  ops = {:destination => io, :channel => "axis1/position/y"}
24
24
  FlameChannelParser::Extractor.extract(File.dirname(__FILE__) + "/snaps/FLEM_curves_example_migrated_to_2012.action", ops)
25
- assert_match /12 -101.80433/, io.string
25
+ line_re = /12 -101.80433/
26
+ assert_match line_re , io.string
26
27
  end
27
28
 
29
+ def test_extraction_succeeds_for_tw_with_odd_end
30
+ io = StringIO.new
31
+ ops = {:start_frame => 1, :end_frame => 504, :destination => io, :channel => "Timing/Timing"}
32
+ FlameChannelParser::Extractor.extract(File.dirname(__FILE__) + "/snaps/timewarp_where_interp_fails_at_end.timewarp", ops)
33
+ line_re = /1\t-7.00000\n2\t-6.00000/
34
+ assert_match line_re , io.string
35
+ end
28
36
 
29
37
  def test_frame_overrides
30
38
  io = StringIO.new
31
39
  o = {:destination => io, :start_frame => 19, :end_frame => 347 }
32
40
  FlameChannelParser::Extractor.extract(File.dirname(__FILE__) + "/snaps/RefT_Steadicam.timewarp", o)
41
+
33
42
  assert_equal File.read(File.dirname(__FILE__) + "/snaps/RefT_Steadicam_Extraction_F19_to_347.txt"), io.string
34
43
  end
35
44
 
@@ -116,6 +116,17 @@ class TestInterpolator < Test::Unit::TestCase
116
116
  assert_in_delta 1, interp.sample_at(230), DELTA
117
117
  end
118
118
 
119
+ def test_ascending_linear_extrapolation_on_baked_curve
120
+ data = File.open(File.dirname(__FILE__) + "/snaps/timewarp_where_interp_fails_at_end.timewarp")
121
+ channels_in_tw = FlameChannelParser.parse(data)
122
+ chan = channels_in_tw.find{|c| c.name == "Timing/Timing"}
123
+ interp = chan.to_interpolator
124
+ assert_in_delta -7, interp.sample_at(1), DELTA
125
+ assert_in_delta 492, interp.sample_at(502), DELTA
126
+ assert_in_delta 492, interp.sample_at(502), DELTA
127
+ assert_in_delta 494, interp.sample_at(504), DELTA
128
+ end
129
+
119
130
  def test_descending_linear_prepolate_two_KFs
120
131
  data = File.open(File.dirname(__FILE__) + "/snaps/RefT_Steadicam_TwoKFs.timewarp")
121
132
  channels_in_tw = FlameChannelParser.parse(data)
@@ -125,7 +136,7 @@ class TestInterpolator < Test::Unit::TestCase
125
136
  assert_in_delta 459, interp.sample_at(1), DELTA
126
137
  assert_in_delta 421, interp.sample_at(20), DELTA
127
138
  assert_in_delta 1, interp.sample_at(230), DELTA
128
- assert_in_delta -39, interp.sample_at(250), DELTA
139
+ assert_in_delta( -39, interp.sample_at(250), DELTA)
129
140
  end
130
141
 
131
142
  def test_descending_linear_prepolate_two_KFs_different_slope
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flame_channel_parser
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 3
9
- - 1
10
- version: 1.3.1
9
+ - 2
10
+ version: 1.3.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Julik Tarkhanov
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-06-03 00:00:00 +02:00
18
+ date: 2011-06-06 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency