flame_channel_parser 1.3.1 → 1.3.2

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,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