tracksperanto 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,6 +1,7 @@
1
1
  # -*- ruby -*-
2
2
  source :rubygems
3
3
 
4
+ gem "tickly", "~> 0.0.2"
4
5
  gem "obuf", "~> 1.1.0"
5
6
  gem "progressive_io", "~> 1.0"
6
7
  gem "flame_channel_parser", "~> 4.0"
@@ -13,4 +14,5 @@ group :development do
13
14
  gem "rake"
14
15
  gem "flexmock", "~>0.8"
15
16
  gem "cli_test", "~>1.0"
17
+ gem "rake-hooks"
16
18
  end
data/History.txt CHANGED
@@ -1,3 +1,7 @@
1
+ === 3.2.0
2
+
3
+ * Add Nuke 7 import support
4
+
1
5
  === 3.1.0
2
6
 
3
7
  * Add a Maxscript export module
data/README.rdoc CHANGED
@@ -54,26 +54,46 @@ converted files already exist <b>they will be overwritten without warning</b>.
54
54
 
55
55
  == Format support
56
56
 
57
- Import and export support:
58
- * Nuke v5 script (Tracker3 nodes, also known as Tracker in the UI - this is the node that you track with, and Reconcile3D)
59
- * Shake tracker node export (textfile with many tracks per file)
60
- * PFTrack 2dt (version 4 and 5) anf PFMatchit/PFTrack 2011 point track exports
61
- * Syntheyes 2D tracking data exports
62
- * 3DE point exports (as output by the default script) - versions 3 and 4
63
- * MatchMover Pro .rz2
64
- * MayaLive track export (square pixel aspect only, you will need to write some extra code if your tracks are based on nonsquare pixels)
57
+ ---
58
+ === Formats Tracksperanto can read
59
+
60
+ * 3DE v3 point export file
61
+ * 3DE v4 point export file
62
+ * Boujou feature tracks export
65
63
  * Flame .stabilizer file
66
- * Boujou feature track export
64
+ * MatchMover REALVIZ Ascii Point Tracks .rz2 file
65
+ * MatchMover RZML .rzml file
66
+ * Maya Live track export file
67
+ * Nuke .nk script file with Tracker, Reconcile3D and PlanarTracker nodes
68
+ * PFTrack/PFMatchit .2dt file
69
+ * Shake .shk script file
70
+ * Shake .txt tracker file and Nuke CameraTracker auto tracks export
71
+ * Syntheyes "All Tracker Paths" export .txt file
72
+ * Syntheyes 2D tracker paths file
73
+
74
+ === Formats Tracksperanto can export to
75
+
76
+ * 3DE v3 point export .txt file
77
+ * 3DE v4 point export .txt file
78
+ * AfterEffects .jsx script generating null layers
79
+ * Autodesk 3dsmax script for nulls on an image plane
80
+ * Autodesk Softimage nulls Python script
81
+ * Bare Ruby code
82
+ * Flame/Smoke 2D Stabilizer setup
83
+ * Flame/Smoke 2D Stabilizer setup for bilinear corner pins
84
+ * MatchMover REALVIZ Ascii Point Tracks .rz2 file
85
+ * Maya ASCII scene with locators on an image plane
86
+ * MayaLive track export
87
+ * Nuke .nk script
88
+ * Nuke CameraTracker node autotracks (enable import/export in the Tracking tab)
89
+ * PFTrack v4 .2dt file
90
+ * PFTrack v5 .2dt file (single camera)
91
+ * PFTrack2011/PFMatchit .txt file (single camera)
92
+ * Shake trackers in a .txt file
93
+ * Syntheyes 2D tracker paths file
94
+ * boujou feature tracks
95
+ ---
67
96
 
68
- Import only:
69
- * Shake scripts (Tracker, Matchmove and Stabilize nodes embedded in ANY shake script)
70
- * MatchMover RZML files
71
-
72
- Export only:
73
- * AfterEffects .jsx script that creates one null object per tracker (run through the script editor)
74
- * Ruby (will make calls to Tracksperanto to create trackers/keyframes) - useful if you want to play with trackers as data
75
- * Maya locators (animated locators on an image plane)
76
- * Softimage/XSI Python script (animated nulls on an image plane)
77
97
 
78
98
  == Editing your tracks while converting
79
99
 
data/Rakefile CHANGED
@@ -1,5 +1,7 @@
1
1
  require './lib/tracksperanto'
2
2
  require 'jeweler'
3
+ require 'rake/hooks'
4
+
3
5
  Jeweler::Tasks.new do |gem|
4
6
  gem.version = Tracksperanto::VERSION
5
7
  gem.name = "tracksperanto"
@@ -31,4 +33,33 @@ Rake::TestTask.new("test") do |t|
31
33
  t.verbose = true
32
34
  end
33
35
 
34
- task :default => [ :test ]
36
+ # Automatically update the supported format list
37
+ after :test do
38
+ formats = StringIO.new
39
+
40
+ formats.puts(" ")
41
+ formats.puts('=== Formats Tracksperanto can read')
42
+ formats.puts(" ")
43
+ Tracksperanto.importers.each do | import_mdoule |
44
+ formats.puts("* %s" % import_mdoule.human_name)
45
+ end
46
+
47
+ formats.puts(" ")
48
+ formats.puts('=== Formats Tracksperanto can export to')
49
+ formats.puts(" ")
50
+ Tracksperanto.exporters.each do | export_module |
51
+ formats.puts("* %s" % export_module.human_name)
52
+ end
53
+
54
+ readme_text = File.read(File.dirname(__FILE__) + "/README.rdoc")
55
+ three = readme_text.split('---')
56
+ raise "Should split in 3" unless three.length == 3
57
+ three[1] = formats.string
58
+
59
+ File.open(File.dirname(__FILE__) + "/README.rdoc", "w") do | f |
60
+ f.write(three.join('---'))
61
+ end
62
+ end
63
+
64
+ task :default => [ :test ]
65
+
@@ -4,9 +4,10 @@ class Tracksperanto::NukeGrammarUtils
4
4
  KEYFRAME = /^([-\d\.]+)$/
5
5
 
6
6
  # Scan a TCL curve expression into a number of tuples of [frame, value]
7
- def parse_curve(curve_text)
7
+ def parse_curve(atoms)
8
8
  # Replace the closing curly brace with a curly brace with space so that it gets caught by split
9
- atoms, tuples = curve_text.gsub(/\}/m, ' }').split, []
9
+ atoms.shift # remove the "curve" keyword
10
+ tuples = []
10
11
  # Nuke saves curves very efficiently. x(keyframe_number) means that an uninterrupted sequence of values will start,
11
12
  # after which values follow. When the curve is interrupted in some way a new x(keyframe_number) will signifu that we
12
13
  # skip to that specified keyframe and the curve continues from there, in gap size defined by the last fragment.
@@ -24,8 +25,6 @@ class Tracksperanto::NukeGrammarUtils
24
25
  elsif atom =~ KEYFRAME
25
26
  tuples << [last_processed_keyframe, $1.to_f]
26
27
  last_processed_keyframe += intraframe_gap_size
27
- elsif atom == '}'
28
- return tuples
29
28
  end
30
29
  end
31
30
  tuples
@@ -1,6 +1,7 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  require 'delegate'
3
3
  require File.expand_path(File.dirname(__FILE__)) + "/nuke_grammar/utils"
4
+ require 'tickly'
4
5
 
5
6
  class Tracksperanto::Import::NukeScript < Tracksperanto::Import::Base
6
7
 
@@ -13,104 +14,121 @@ class Tracksperanto::Import::NukeScript < Tracksperanto::Import::Base
13
14
  end
14
15
 
15
16
  def self.known_snags
16
- 'The only supported nodes that we can extract tracks from are Reconcile3D, PlanarTracker and Tracker'
17
+ 'The only supported nodes that we can extract tracks from are Reconcile3D, PlanarTracker and Tracker (supported Nuke versions are 5, 6 and 7)'
17
18
  end
18
19
 
19
20
  def each
20
- io = Tracksperanto::ExtIO.new(@io)
21
- while line = io.gets_and_strip
22
- if line =~ TRACKER_3_PATTERN
23
- scan_tracker_node(io).each(&Proc.new)
24
- elsif line =~ RECONCILE_PATTERN
25
- scan_reconcile_node(io).each(&Proc.new)
26
- elsif line =~ PLANAR_PATTERN
27
- scan_planar_tracker_node(io).each(&Proc.new)
21
+ script_tree = Tickly::Parser.new.parse(@io)
22
+ evaluator = Tickly::Evaluator.new
23
+ evaluator.add_node_handler_class(Tracker3)
24
+ evaluator.add_node_handler_class(Reconcile3D)
25
+ evaluator.add_node_handler_class(PlanarTracker1_0)
26
+ evaluator.add_node_handler_class(Tracker4)
27
+
28
+ script_tree.each do | node |
29
+ result = evaluator.evaluate(node)
30
+ if result
31
+ result.trackers.each do | t |
32
+ report_progress("Scavenging tracker #{t.name}")
33
+ yield t
34
+ end
28
35
  end
29
36
  end
30
37
  end
31
38
 
32
39
  private
33
-
34
- TRACKER_3_PATTERN = /^Tracker3 \{/
35
- RECONCILE_PATTERN = /^Reconcile3D \{/
36
- PLANAR_PATTERN = /^PlanarTracker1_0 \{/
37
- OUTPUT_PATTERN = /^output \{/
38
- TRACK_PATTERN = /^track(\d) \{/
39
- NODENAME = /^name ([^\n]+)/
40
- PLANAR_CORNERS = %w( outputBottomLeft outputBottomRight outputTopLeft outputTopRight)
41
-
42
- # Scans a Reconcile3D node and returs it's output
43
- def scan_reconcile_node(io)
44
- t = Tracksperanto::Tracker.new
45
- while line = io.gets_and_strip
46
- if line =~ OUTPUT_PATTERN
47
- t = extract_tracker(line)
48
- elsif line =~ NODENAME
49
- t.name = "Reconcile_#{$1}"
50
- report_progress("Scavenging Reconcile3D node #{t.name}")
51
- return [t] # Klunky
52
- end
40
+
41
+ class Tracker3
42
+ include Tracksperanto::ZipTuples
43
+ attr_reader :trackers
44
+
45
+ def initialize(options)
46
+ @trackers = []
47
+ point_channels.each do | point_name |
48
+ next unless options[point_name]
49
+ point_channel = options[point_name]
50
+
51
+ curves = extract_curves_from_channel(point_channel)
52
+
53
+ # We must always have 2 anim curves
54
+ next unless curves.length == 2
55
+
56
+ full_name = [options["name"], point_name].join('_')
57
+ tracker = package_tracker(full_name, curves[0], curves[1])
58
+
59
+ @trackers << tracker
53
60
  end
54
61
  end
55
62
 
56
- # Scans a PlanarTracker node and recovers corner pin
57
- def scan_planar_tracker_node(io)
58
- trackers, node_name = [], nil
59
- while line = io.gets_and_strip
60
- PLANAR_CORNERS.each do | corner_name |
61
- if line =~ /#{corner_name}/
62
- t = Tracksperanto::Tracker.new
63
- t = extract_tracker(line)
64
- t.name = corner_name
65
- trackers.push(t.dup)
66
- elsif line =~ NODENAME
67
- node_name = $1
68
- end
69
- end
70
-
71
- if node_name && trackers.length == 4
72
- trackers.each{|t| t.name = "%s_%s" % [node_name, t.name] }
73
- return trackers
63
+ def package_tracker(full_name, xcurve, ycurve)
64
+ frame_x_and_y = zip_curve_tuples(xcurve, ycurve)
65
+ Tracksperanto::Tracker.new(:name => full_name) do | t |
66
+ frame_x_and_y.each do | (f, x, y) |
67
+ t.keyframe!(:frame => (f -1), :abs_x => x, :abs_y => y)
74
68
  end
75
69
  end
76
-
77
- # Fail
78
- return []
79
70
  end
80
71
 
81
- # Scans a tracker node and return all tracks within that node (no more than 4)
82
- def scan_tracker_node(io)
83
- tracks_in_tracker = []
84
- while line = io.gets_and_strip
85
- if line =~ TRACK_PATTERN
86
- t = extract_tracker(line)
87
- tracks_in_tracker.push(t) if t
88
- elsif line =~ NODENAME
89
- tracks_in_tracker.each_with_index do | t, i |
90
- t.name = "#{$1}_track#{i+1}"
91
- report_progress("Scavenging Tracker3 node #{t.name}")
92
- end
93
- return tracks_in_tracker
72
+ def extract_curves_from_channel(point_channel)
73
+ u = Tracksperanto::NukeGrammarUtils.new
74
+ point_channel.to_a.map do | curve_argument |
75
+ if curve_argument[0] == "curve"
76
+ u.parse_curve(curve_argument.to_a)
77
+ else
78
+ nil
94
79
  end
95
- end
96
- raise "Tracker node went all the way to end of stream"
80
+ end.compact
97
81
  end
98
82
 
99
- def scan_track(line_with_curve)
100
- x_curve, y_curve = line_with_curve.split(/\}/).map do | one_curve|
101
- Tracksperanto::NukeGrammarUtils.new.parse_curve(one_curve)
102
- end
103
- return nil unless (x_curve && y_curve)
104
- zip_curve_tuples(x_curve, y_curve)
83
+ def point_channels
84
+ %w( track1 track2 track3 track4 )
105
85
  end
106
86
 
107
- def extract_tracker(line)
108
- tuples = scan_track(line)
109
- return nil unless (tuples && tuples.any?)
110
- Tracksperanto::Tracker.new do | t |
111
- tuples.each do | (f, x, y) |
112
- t.keyframe!(:frame => (f -1), :abs_x => x, :abs_y => y)
113
- end
87
+ end
88
+
89
+ class Reconcile3D < Tracker3
90
+ def point_channels
91
+ %w( output)
92
+ end
93
+ end
94
+
95
+ class PlanarTracker1_0 < Tracker3
96
+ def point_channels
97
+ %w( outputBottomLeft outputBottomRight outputTopLeft outputTopRight)
98
+ end
99
+ end
100
+
101
+ class Tracker4 < Tracker3
102
+
103
+ def initialize(options)
104
+
105
+ @name = options["name"]
106
+ @trackers = []
107
+ tracks = options["tracks"]
108
+ preamble = tracks[0]
109
+ headers = tracks[1]
110
+ values = tracks[2]
111
+
112
+ table_headers = headers[0].map{|header| header[0][-1]}
113
+ #puts table_headers.inspect
114
+ # When this was written, this was the order of the columns in the table:
115
+ # le("e", "name", "track_x", "track_y", "offset_x", "offset_y", "T", "R", "S", "error",
116
+ # "error_min", "error_max", "pattern_x", "pattern_y", "pattern_r", "pattern_t", "search_x",
117
+ # "search_y", "search_r", "search_t", "key_track", "key_search_x", "key_search_y", "key_search_r",
118
+ # "key_search_t", "key_track_x", "key_track_y", "key_track_r", "key_track_t", "key_centre_offset_x", "key_centre_offset_y")
119
+ tracker_rows = values[0]
120
+
121
+ u = Tracksperanto::NukeGrammarUtils.new
122
+
123
+ tracker_rows.each do | row |
124
+ row_content = row[0][0]
125
+ # For offsets see above
126
+ point_name, x_curve, y_curve = row_content[1], u.parse_curve(row_content[2].to_a), u.parse_curve(row_content[3].to_a)
127
+
128
+ full_name = [options["name"], point_name].join('_')
129
+ tracker = package_tracker(full_name, x_curve, y_curve)
130
+ @trackers << tracker
114
131
  end
115
132
  end
133
+ end
116
134
  end
data/lib/tracksperanto.rb CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Tracksperanto
5
5
  PATH = File.expand_path(File.dirname(__FILE__))
6
- VERSION = '3.1.0'
6
+ VERSION = '3.2.0'
7
7
 
8
8
  module Import; end
9
9
  module Export; end
@@ -45,7 +45,7 @@ class NukeImportTest < Test::Unit::TestCase
45
45
  assert_equal 4, trackers.length
46
46
 
47
47
  ref_names = %w( PlanarTracker2_outputBottomLeft PlanarTracker2_outputBottomRight
48
- PlanarTracker2_outputTopRight PlanarTracker2_outputTopLeft )
48
+ PlanarTracker2_outputTopLeft PlanarTracker2_outputTopRight )
49
49
  assert_equal ref_names, trackers.map{|e| e.name }
50
50
  end
51
51
 
@@ -106,6 +106,25 @@ class NukeImportTest < Test::Unit::TestCase
106
106
  assert_equal 11, t[5].frame
107
107
  end
108
108
 
109
+ def test_parsing_from_nuke7_tracker4
110
+ fixture = File.open(File.dirname(__FILE__) + '/samples/nuke/nuke7_tracker_2tracks.nk')
111
+
112
+ parser = Tracksperanto::Import::NukeScript.new(:io => fixture)
113
+ parser.width = 1280
114
+ parser.height = 720
115
+
116
+ trackers = parser.to_a
117
+ assert_equal 2, trackers.length
118
+ assert_equal "Tracker1_track_1", trackers[0].name
119
+ assert_equal "Tracker1_track_2", trackers[1].name
120
+ assert_equal 15, trackers[1].length
121
+
122
+ kf = trackers[1][5]
123
+ assert_in_delta 106.75, kf.abs_x, DELTA
124
+ assert_in_delta 77.01, kf.abs_y, DELTA
125
+ assert_equal 9, kf.frame
126
+ end
127
+
109
128
  def test_parsing_from_nuke_group
110
129
  fixture = File.open(File.dirname(__FILE__) + '/samples/nuke/one_tracker_with_break_in_grp.nk')
111
130
 
@@ -124,9 +143,9 @@ class NukeImportTest < Test::Unit::TestCase
124
143
  end
125
144
 
126
145
  def test_parsing_nuke_curve
127
- curve = '{curve x742 888 890.2463989 891.6602783 \
128
- 893.5056763 895.6155396 s95 897.2791748 899.1762695 \
129
- x754 912.0731812 x755 913.7190552 916.0959473 918.1025391 920.0751953 922.1898804}'
146
+ curve = %w( curve x742 888 890.2463989 891.6602783
147
+ 893.5056763 895.6155396 s95 897.2791748 899.1762695
148
+ x754 912.0731812 x755 913.7190552 916.0959473 918.1025391 920.0751953 922.1898804 )
130
149
  p = Tracksperanto::NukeGrammarUtils.new
131
150
  result = p.parse_curve(curve)
132
151
  assert_kind_of Array, result
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "tracksperanto"
8
- s.version = "3.1.0"
8
+ s.version = "3.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Julik Tarkhanov"]
12
- s.date = "2013-01-14"
12
+ s.date = "2013-03-02"
13
13
  s.description = "Converts 2D track exports between different apps like Flame, MatchMover, PFTrack..."
14
14
  s.email = "me@julik.nl"
15
15
  s.executables = ["tracksperanto"]
@@ -212,6 +212,7 @@ Gem::Specification.new do |s|
212
212
  s.specification_version = 3
213
213
 
214
214
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
215
+ s.add_runtime_dependency(%q<tickly>, ["~> 0.0.2"])
215
216
  s.add_runtime_dependency(%q<obuf>, ["~> 1.1.0"])
216
217
  s.add_runtime_dependency(%q<progressive_io>, ["~> 1.0"])
217
218
  s.add_runtime_dependency(%q<flame_channel_parser>, ["~> 4.0"])
@@ -222,7 +223,9 @@ Gem::Specification.new do |s|
222
223
  s.add_development_dependency(%q<rake>, [">= 0"])
223
224
  s.add_development_dependency(%q<flexmock>, ["~> 0.8"])
224
225
  s.add_development_dependency(%q<cli_test>, ["~> 1.0"])
226
+ s.add_development_dependency(%q<rake-hooks>, [">= 0"])
225
227
  else
228
+ s.add_dependency(%q<tickly>, ["~> 0.0.2"])
226
229
  s.add_dependency(%q<obuf>, ["~> 1.1.0"])
227
230
  s.add_dependency(%q<progressive_io>, ["~> 1.0"])
228
231
  s.add_dependency(%q<flame_channel_parser>, ["~> 4.0"])
@@ -233,8 +236,10 @@ Gem::Specification.new do |s|
233
236
  s.add_dependency(%q<rake>, [">= 0"])
234
237
  s.add_dependency(%q<flexmock>, ["~> 0.8"])
235
238
  s.add_dependency(%q<cli_test>, ["~> 1.0"])
239
+ s.add_dependency(%q<rake-hooks>, [">= 0"])
236
240
  end
237
241
  else
242
+ s.add_dependency(%q<tickly>, ["~> 0.0.2"])
238
243
  s.add_dependency(%q<obuf>, ["~> 1.1.0"])
239
244
  s.add_dependency(%q<progressive_io>, ["~> 1.0"])
240
245
  s.add_dependency(%q<flame_channel_parser>, ["~> 4.0"])
@@ -245,6 +250,7 @@ Gem::Specification.new do |s|
245
250
  s.add_dependency(%q<rake>, [">= 0"])
246
251
  s.add_dependency(%q<flexmock>, ["~> 0.8"])
247
252
  s.add_dependency(%q<cli_test>, ["~> 1.0"])
253
+ s.add_dependency(%q<rake-hooks>, [">= 0"])
248
254
  end
249
255
  end
250
256
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tracksperanto
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-14 00:00:00.000000000 Z
12
+ date: 2013-03-02 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: tickly
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.0.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.0.2
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: obuf
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -171,6 +187,22 @@ dependencies:
171
187
  - - ~>
172
188
  - !ruby/object:Gem::Version
173
189
  version: '1.0'
190
+ - !ruby/object:Gem::Dependency
191
+ name: rake-hooks
192
+ requirement: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ! '>='
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ type: :development
199
+ prerelease: false
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
174
206
  description: Converts 2D track exports between different apps like Flame, MatchMover,
175
207
  PFTrack...
176
208
  email: me@julik.nl
@@ -379,7 +411,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
379
411
  version: '0'
380
412
  segments:
381
413
  - 0
382
- hash: -3821362273456769612
414
+ hash: 2047932148332982909
383
415
  required_rubygems_version: !ruby/object:Gem::Requirement
384
416
  none: false
385
417
  requirements: