tracksperanto 3.1.0 → 3.2.0

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/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: