RUIC 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +5 -0
- data/README.md +279 -270
- data/bin/ruic +0 -0
- data/gui/TODO +2 -2
- data/gui/appattributesmodel.rb +51 -51
- data/gui/appelementsmodel.rb +126 -126
- data/gui/launch.rb +19 -19
- data/gui/makefile +14 -14
- data/gui/resources/style/dark.qss +459 -459
- data/gui/window.rb +90 -90
- data/gui/window.ui +753 -753
- data/lib/ruic.rb +80 -11
- data/lib/ruic/application.rb +68 -13
- data/lib/ruic/assets.rb +289 -287
- data/lib/ruic/attributes.rb +165 -165
- data/lib/ruic/behaviors.rb +0 -0
- data/lib/ruic/interfaces.rb +7 -2
- data/lib/ruic/presentation.rb +175 -40
- data/lib/ruic/ripl-after-result.rb +11 -11
- data/lib/ruic/statemachine.rb +0 -0
- data/lib/ruic/version.rb +2 -2
- data/ruic.gemspec +25 -25
- data/test/MetaData-simple.xml +28 -28
- data/test/MetaData.xml +435 -435
- data/test/customclasses.ruic +21 -21
- data/test/filtering.ruic +43 -43
- data/test/futureassets.ruic +8 -8
- data/test/nonmaster.ruic +0 -0
- data/test/paths.ruic +18 -18
- data/test/projects/CustomClasses/CustomClasses.uia +7 -7
- data/test/projects/CustomClasses/FutureAsset.uip +17 -17
- data/test/projects/SimpleScene/SimpleScene.uia +4 -4
- data/test/projects/SimpleScene/SimpleScene.uip +35 -35
- data/test/properties.ruic +82 -82
- data/test/referencematerials.ruic +52 -52
- data/test/usage.ruic +20 -20
- metadata +6 -26
data/lib/ruic/attributes.rb
CHANGED
@@ -1,165 +1,165 @@
|
|
1
|
-
#encoding: utf-8
|
2
|
-
class UIC::Property
|
3
|
-
class << self; attr_accessor :default; end
|
4
|
-
def initialize(el); @el = el; end
|
5
|
-
def name; @name||=@el['name']; end
|
6
|
-
def type; @type||=@el['type']; end
|
7
|
-
def formal; @formal||=@el['formalName'] || @el['name']; end
|
8
|
-
def min; @el['min']; end
|
9
|
-
def max; @el['max']; end
|
10
|
-
def description; @desc||=@el['description']; end
|
11
|
-
def default; @def ||= (@el['default'] || self.class.default); end
|
12
|
-
def get(asset,slide)
|
13
|
-
if asset.slide? || asset.has_slide?(slide)
|
14
|
-
asset.presentation.get_attribute(asset
|
15
|
-
end
|
16
|
-
end
|
17
|
-
def set(asset,new_value,slide_name_or_index)
|
18
|
-
asset.presentation.set_attribute(asset
|
19
|
-
end
|
20
|
-
def inspect
|
21
|
-
"<#{type} '#{name}'>"
|
22
|
-
end
|
23
|
-
|
24
|
-
class String < self
|
25
|
-
self.default = ''
|
26
|
-
end
|
27
|
-
MultiLineString = String
|
28
|
-
|
29
|
-
class Float < self
|
30
|
-
self.default = 0.0
|
31
|
-
def get(asset,slide); super.to_f; end
|
32
|
-
end
|
33
|
-
class Long < self
|
34
|
-
self.default = 0
|
35
|
-
def get(asset,slide); super.to_i; end
|
36
|
-
end
|
37
|
-
class Boolean < self
|
38
|
-
self.default = false
|
39
|
-
def get(asset,slide); super=='True'; end
|
40
|
-
def set(asset,new_value,slide_name_or_index)
|
41
|
-
super( asset, new_value ? 'True' : 'False', slide_name_or_index )
|
42
|
-
end
|
43
|
-
end
|
44
|
-
class Vector < self
|
45
|
-
self.default = '0 0 0'
|
46
|
-
def get(asset,slide)
|
47
|
-
VectorValue.new(asset,self,slide,super)
|
48
|
-
end
|
49
|
-
def set(asset,new_value,slide_name_or_index)
|
50
|
-
new_value = new_value.join(' ') if new_value.is_a?(Array)
|
51
|
-
super( asset, new_value, slide_name_or_index )
|
52
|
-
end
|
53
|
-
end
|
54
|
-
Rotation = Vector
|
55
|
-
Color = Vector
|
56
|
-
Float2 = Vector
|
57
|
-
class Image < self
|
58
|
-
self.default = nil
|
59
|
-
def get(asset,slide)
|
60
|
-
if idref = super
|
61
|
-
result = asset.presentation.asset_by_id( idref[1..-1] )
|
62
|
-
slide ? result.on_slide( slide ) : result
|
63
|
-
end
|
64
|
-
end
|
65
|
-
def set(asset,new_value,slide)
|
66
|
-
raise "Setting image attributes not yet supported"
|
67
|
-
end
|
68
|
-
end
|
69
|
-
class Texture < String
|
70
|
-
def get(asset,slide)
|
71
|
-
if path=super
|
72
|
-
path.empty? ? nil : path.gsub( '\\', '/' ).sub( /^.\// ,'' )
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
class ObjectRef < self
|
78
|
-
self.default = nil
|
79
|
-
def get(asset,slide)
|
80
|
-
ref = super
|
81
|
-
type = :absolute
|
82
|
-
obj = nil
|
83
|
-
unless ref=='' || ref.nil?
|
84
|
-
type = ref[0]=='#' ? :absolute : :path
|
85
|
-
ref = type==:absolute ? asset.presentation.asset_by_id( ref[1..-1] ) : asset.presentation.at( ref, asset
|
86
|
-
end
|
87
|
-
ObjectReference.new(asset,self,slide,ref,type)
|
88
|
-
end
|
89
|
-
def set(asset,new_object,slide)
|
90
|
-
get(asset,slide).object = new_object
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
class ObjectReference
|
95
|
-
attr_reader :object, :type
|
96
|
-
def initialize(asset,property,slide,object=nil,type=nil)
|
97
|
-
@asset = asset
|
98
|
-
@name = property.name
|
99
|
-
@slide = slide
|
100
|
-
@object = object
|
101
|
-
@type = type
|
102
|
-
end
|
103
|
-
def object=(new_object)
|
104
|
-
raise "ObjectRef must be set to an asset (not a #{new_object.class.name})" unless new_object.is_a?(UIC::
|
105
|
-
@object = new_object
|
106
|
-
write_value!
|
107
|
-
end
|
108
|
-
def type=(new_type)
|
109
|
-
raise "ObjectRef types must be either :absolute or :path (not #{new_type.inspect})" unless [:absolute,:path].include?(new_type)
|
110
|
-
@type = new_type
|
111
|
-
write_value!
|
112
|
-
end
|
113
|
-
private
|
114
|
-
def write_value!
|
115
|
-
path = case @object
|
116
|
-
when NilClass then ""
|
117
|
-
else case @type
|
118
|
-
when :absolute then "##{@object.el['id']}"
|
119
|
-
when :path then @asset.presentation.path_to( @object
|
120
|
-
# when :root then @asset.presentation.path_to( @object
|
121
|
-
end
|
122
|
-
end
|
123
|
-
@asset.presentation.set_attribute( @asset
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
Import = String #TODO: a real class
|
128
|
-
Mesh = String #TODO: a real class
|
129
|
-
Renderable = String #TODO: a real class
|
130
|
-
Font = String #TODO: a real class
|
131
|
-
FontSize = Long
|
132
|
-
|
133
|
-
StringListOrInt = String #TODO: a real class
|
134
|
-
|
135
|
-
class VectorValue
|
136
|
-
attr_reader :x, :y, :z
|
137
|
-
def initialize(asset,property,slide,str)
|
138
|
-
@asset = asset
|
139
|
-
@property = property
|
140
|
-
@slide = slide
|
141
|
-
@x, @y, @z = str.split(/\s+/).map(&:to_f)
|
142
|
-
end
|
143
|
-
def setall
|
144
|
-
@property.set( @asset, to_s, @slide )
|
145
|
-
end
|
146
|
-
def x=(n); @x=n; setall end
|
147
|
-
def y=(n); @y=n; setall end
|
148
|
-
def z=(n); @z=n; setall end
|
149
|
-
alias_method :r, :x
|
150
|
-
alias_method :g, :y
|
151
|
-
alias_method :b, :z
|
152
|
-
alias_method :r=, :x=
|
153
|
-
alias_method :g=, :y=
|
154
|
-
alias_method :b=, :z=
|
155
|
-
def inspect
|
156
|
-
"<#{@asset.path}.#{@property.name}: #{self}>"
|
157
|
-
end
|
158
|
-
def to_s
|
159
|
-
to_a.join(' ')
|
160
|
-
end
|
161
|
-
def to_a
|
162
|
-
[x,y,z]
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
1
|
+
#encoding: utf-8
|
2
|
+
class UIC::Property
|
3
|
+
class << self; attr_accessor :default; end
|
4
|
+
def initialize(el); @el = el; end
|
5
|
+
def name; @name||=@el['name']; end
|
6
|
+
def type; @type||=@el['type']; end
|
7
|
+
def formal; @formal||=@el['formalName'] || @el['name']; end
|
8
|
+
def min; @el['min']; end
|
9
|
+
def max; @el['max']; end
|
10
|
+
def description; @desc||=@el['description']; end
|
11
|
+
def default; @def ||= (@el['default'] || self.class.default); end
|
12
|
+
def get(asset,slide)
|
13
|
+
if asset.slide? || asset.has_slide?(slide)
|
14
|
+
asset.presentation.get_attribute(asset,name,slide) || default
|
15
|
+
end
|
16
|
+
end
|
17
|
+
def set(asset,new_value,slide_name_or_index)
|
18
|
+
asset.presentation.set_attribute(asset,name,slide_name_or_index,new_value)
|
19
|
+
end
|
20
|
+
def inspect
|
21
|
+
"<#{type} '#{name}'>"
|
22
|
+
end
|
23
|
+
|
24
|
+
class String < self
|
25
|
+
self.default = ''
|
26
|
+
end
|
27
|
+
MultiLineString = String
|
28
|
+
|
29
|
+
class Float < self
|
30
|
+
self.default = 0.0
|
31
|
+
def get(asset,slide); super.to_f; end
|
32
|
+
end
|
33
|
+
class Long < self
|
34
|
+
self.default = 0
|
35
|
+
def get(asset,slide); super.to_i; end
|
36
|
+
end
|
37
|
+
class Boolean < self
|
38
|
+
self.default = false
|
39
|
+
def get(asset,slide); super=='True'; end
|
40
|
+
def set(asset,new_value,slide_name_or_index)
|
41
|
+
super( asset, new_value ? 'True' : 'False', slide_name_or_index )
|
42
|
+
end
|
43
|
+
end
|
44
|
+
class Vector < self
|
45
|
+
self.default = '0 0 0'
|
46
|
+
def get(asset,slide)
|
47
|
+
VectorValue.new(asset,self,slide,super)
|
48
|
+
end
|
49
|
+
def set(asset,new_value,slide_name_or_index)
|
50
|
+
new_value = new_value.join(' ') if new_value.is_a?(Array)
|
51
|
+
super( asset, new_value, slide_name_or_index )
|
52
|
+
end
|
53
|
+
end
|
54
|
+
Rotation = Vector
|
55
|
+
Color = Vector
|
56
|
+
Float2 = Vector
|
57
|
+
class Image < self
|
58
|
+
self.default = nil
|
59
|
+
def get(asset,slide)
|
60
|
+
if idref = super
|
61
|
+
result = asset.presentation.asset_by_id( idref[1..-1] )
|
62
|
+
slide ? result.on_slide( slide ) : result
|
63
|
+
end
|
64
|
+
end
|
65
|
+
def set(asset,new_value,slide)
|
66
|
+
raise "Setting image attributes not yet supported"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
class Texture < String
|
70
|
+
def get(asset,slide)
|
71
|
+
if path=super
|
72
|
+
path.empty? ? nil : path.gsub( '\\', '/' ).sub( /^.\// ,'' )
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class ObjectRef < self
|
78
|
+
self.default = nil
|
79
|
+
def get(asset,slide)
|
80
|
+
ref = super
|
81
|
+
type = :absolute
|
82
|
+
obj = nil
|
83
|
+
unless ref=='' || ref.nil?
|
84
|
+
type = ref[0]=='#' ? :absolute : :path
|
85
|
+
ref = type==:absolute ? asset.presentation.asset_by_id( ref[1..-1] ) : asset.presentation.at( ref, asset )
|
86
|
+
end
|
87
|
+
ObjectReference.new(asset,self,slide,ref,type)
|
88
|
+
end
|
89
|
+
def set(asset,new_object,slide)
|
90
|
+
get(asset,slide).object = new_object
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class ObjectReference
|
95
|
+
attr_reader :object, :type
|
96
|
+
def initialize(asset,property,slide,object=nil,type=nil)
|
97
|
+
@asset = asset
|
98
|
+
@name = property.name
|
99
|
+
@slide = slide
|
100
|
+
@object = object
|
101
|
+
@type = type
|
102
|
+
end
|
103
|
+
def object=(new_object)
|
104
|
+
raise "ObjectRef must be set to an asset (not a #{new_object.class.name})" unless new_object.is_a?(UIC::MetaData::Root)
|
105
|
+
@object = new_object
|
106
|
+
write_value!
|
107
|
+
end
|
108
|
+
def type=(new_type)
|
109
|
+
raise "ObjectRef types must be either :absolute or :path (not #{new_type.inspect})" unless [:absolute,:path].include?(new_type)
|
110
|
+
@type = new_type
|
111
|
+
write_value!
|
112
|
+
end
|
113
|
+
private
|
114
|
+
def write_value!
|
115
|
+
path = case @object
|
116
|
+
when NilClass then ""
|
117
|
+
else case @type
|
118
|
+
when :absolute then "##{@object.el['id']}"
|
119
|
+
when :path then @asset.presentation.path_to( @object, @asset ).sub(/^[^:.]+:/,'')
|
120
|
+
# when :root then @asset.presentation.path_to( @object ).sub(/^[^:.]+:/,'')
|
121
|
+
end
|
122
|
+
end
|
123
|
+
@asset.presentation.set_attribute( @asset, @name, @slide, path )
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
Import = String #TODO: a real class
|
128
|
+
Mesh = String #TODO: a real class
|
129
|
+
Renderable = String #TODO: a real class
|
130
|
+
Font = String #TODO: a real class
|
131
|
+
FontSize = Long
|
132
|
+
|
133
|
+
StringListOrInt = String #TODO: a real class
|
134
|
+
|
135
|
+
class VectorValue
|
136
|
+
attr_reader :x, :y, :z
|
137
|
+
def initialize(asset,property,slide,str)
|
138
|
+
@asset = asset
|
139
|
+
@property = property
|
140
|
+
@slide = slide
|
141
|
+
@x, @y, @z = str.split(/\s+/).map(&:to_f)
|
142
|
+
end
|
143
|
+
def setall
|
144
|
+
@property.set( @asset, to_s, @slide )
|
145
|
+
end
|
146
|
+
def x=(n); @x=n; setall end
|
147
|
+
def y=(n); @y=n; setall end
|
148
|
+
def z=(n); @z=n; setall end
|
149
|
+
alias_method :r, :x
|
150
|
+
alias_method :g, :y
|
151
|
+
alias_method :b, :z
|
152
|
+
alias_method :r=, :x=
|
153
|
+
alias_method :g=, :y=
|
154
|
+
alias_method :b=, :z=
|
155
|
+
def inspect
|
156
|
+
"<#{@asset.path}.#{@property.name}: #{self}>"
|
157
|
+
end
|
158
|
+
def to_s
|
159
|
+
to_a.join(' ')
|
160
|
+
end
|
161
|
+
def to_a
|
162
|
+
[x,y,z]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/lib/ruic/behaviors.rb
CHANGED
File without changes
|
data/lib/ruic/interfaces.rb
CHANGED
@@ -12,7 +12,12 @@ module UIC::FileBacked
|
|
12
12
|
def file=( new_path )
|
13
13
|
@file = File.expand_path(new_path)
|
14
14
|
@file_not_found = !File.exist?(new_path)
|
15
|
-
|
15
|
+
end
|
16
|
+
def to_xml
|
17
|
+
doc.to_xml( indent:1, indent_text:"\t" )
|
18
|
+
end
|
19
|
+
def save!
|
20
|
+
File.open(file,'w:utf-8'){ |f| f << to_xml }
|
16
21
|
end
|
17
22
|
end
|
18
23
|
|
@@ -26,7 +31,7 @@ module UIC::ElementBacked
|
|
26
31
|
define_method(name){ @el[name] }
|
27
32
|
define_method("#{name}=", &(block || ->(new_value){ @el[name]=new_value.to_s }))
|
28
33
|
end
|
29
|
-
end
|
34
|
+
end
|
30
35
|
end
|
31
36
|
|
32
37
|
module UIC::PresentableHash
|
data/lib/ruic/presentation.rb
CHANGED
@@ -1,11 +1,21 @@
|
|
1
|
+
# A `Presentation` represents a `.uip` presentation, created and edited by UI Composer Studio.
|
1
2
|
class UIC::Presentation
|
2
3
|
include UIC::FileBacked
|
3
|
-
|
4
|
+
|
5
|
+
# Create a new presentation. If you do not specify the `uip_path` to load from, you must
|
6
|
+
# later set the `.file = `for the presentation, and then call the {#load_from_file} method.
|
7
|
+
# @param uip_path [String] path to the `.uip` to load.
|
8
|
+
def initialize( uip_path=nil )
|
4
9
|
self.file = uip_path
|
5
10
|
load_from_file if file_found?
|
6
11
|
end
|
7
12
|
|
13
|
+
# Load information for the presentation from disk.
|
14
|
+
# If you supply a path to a `.uip` file when creating the presentation
|
15
|
+
# this method is automatically called.
|
16
|
+
# @return [nil]
|
8
17
|
def load_from_file
|
18
|
+
# TODO: this method assumes an application to find the metadata on; the metadata should be part of this class instance instead, shared with the app when present
|
9
19
|
@doc = Nokogiri.XML( File.read( file, encoding:'utf-8' ), &:noblanks )
|
10
20
|
@graph = @doc.at('Graph')
|
11
21
|
@scene = @graph.at('Scene')
|
@@ -32,6 +42,7 @@ class UIC::Presentation
|
|
32
42
|
app.metadata.create_class( meta, from, reference.name )
|
33
43
|
end
|
34
44
|
@class_by_ref[ "##{reference['id']}" ] = metaklass
|
45
|
+
nil
|
35
46
|
end
|
36
47
|
|
37
48
|
rebuild_caches_from_document
|
@@ -42,19 +53,21 @@ class UIC::Presentation
|
|
42
53
|
end
|
43
54
|
|
44
55
|
def to_xml
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
def save!
|
51
|
-
File.open(file,'w:utf-8'){ |f| f << to_xml }
|
56
|
+
doc.to_xml( indent:1, indent_text:"\t" )
|
57
|
+
.gsub( %r{(<\w+(?: [\w:]+="[^"]*")*)(/?>)}i, '\1 \2' )
|
58
|
+
.sub('"?>','" ?>')
|
52
59
|
end
|
53
60
|
|
54
61
|
def save_as(new_file)
|
55
62
|
File.open(new_file,'w:utf-8'){ |f| f << to_xml }
|
56
63
|
end
|
57
64
|
|
65
|
+
# Update the presentation to be in-sync with the document.
|
66
|
+
# Must be called whenever the in-memory representation of the XML document is changed.
|
67
|
+
# Called automatically by all necessary methods; only necessary if script (dangerously)
|
68
|
+
# manipulates the `.doc` of the presentation directly.
|
69
|
+
#
|
70
|
+
# @return [nil]
|
58
71
|
def rebuild_caches_from_document
|
59
72
|
@graph_by_id = {}
|
60
73
|
@scene.traverse{ |x| @graph_by_id[x['id']]=x if x.is_a?(Nokogiri::XML::Element) }
|
@@ -72,27 +85,38 @@ class UIC::Presentation
|
|
72
85
|
@addsets_by_graph[graph][name] = addset
|
73
86
|
@addsets_by_graph[graph][index] = addset
|
74
87
|
end
|
88
|
+
nil
|
75
89
|
end
|
76
90
|
|
91
|
+
# Find an asset in the presentation based on its internal XML identifier.
|
92
|
+
# @param id [String] the id of the asset (not an idref), e.g. `"Material_003"`.
|
93
|
+
# @return [MetaData::Root] the found asset, or `nil` if could not be found.
|
77
94
|
def asset_by_id( id )
|
78
95
|
(@graph_by_id[id] && asset_for_el( @graph_by_id[id] ))
|
79
96
|
end
|
80
97
|
|
81
|
-
#
|
82
|
-
|
98
|
+
# @param asset [MetaData::Root] an asset in the presentation
|
99
|
+
# @return [Integer] the index of the first slide where an asset is added (0 for master, non-zero for non-master).
|
100
|
+
def slide_index(asset)
|
83
101
|
# TODO: probably faster to .find the first @addsets_by_graph
|
84
|
-
|
102
|
+
id = asset.el['id']
|
103
|
+
slide = @logic.at(".//Add[@ref='##{id}']/..")
|
85
104
|
(slide ? slide.xpath('count(ancestor::State) + count(preceding-sibling::State[ancestor::State])').to_i : 0) # the Scene is never added
|
86
105
|
end
|
87
106
|
|
88
|
-
|
107
|
+
# @param child_asset [MetaData::Root] an asset in the presentation.
|
108
|
+
# @return [MetaData::Root] the scene graph parent of the child asset, or `nil` for the Scene.
|
109
|
+
def parent_asset( child_asset )
|
110
|
+
child_graph_el = child_asset.el
|
89
111
|
unless child_graph_el==@scene || child_graph_el.parent.nil?
|
90
112
|
asset_for_el( child_graph_el.parent )
|
91
113
|
end
|
92
114
|
end
|
93
115
|
|
94
|
-
|
95
|
-
|
116
|
+
# @param parent_asset [MetaData::Root] an asset in the presentation.
|
117
|
+
# @return [Array<MetaData::Root>] array of scene graph children of the specified asset.
|
118
|
+
def child_assets( parent_asset )
|
119
|
+
parent_asset.el.element_children.map{ |child| asset_for_el(child) }
|
96
120
|
end
|
97
121
|
|
98
122
|
# Get an array of all assets in the scene graph, in document order
|
@@ -100,8 +124,9 @@ class UIC::Presentation
|
|
100
124
|
@graph_by_id.map{ |id,graph_element| asset_for_el(graph_element) }
|
101
125
|
end
|
102
126
|
|
103
|
-
#
|
127
|
+
# @return [Hash] a mapping of image paths to arrays of the assets referencing them.
|
104
128
|
def image_usage
|
129
|
+
# TODO: this returns the same asset multiple times, with no indication of which property is using it; should switch to an Asset/Property pair, or some such.
|
105
130
|
asset_types = app.metadata.by_name.values + @class_by_ref.values
|
106
131
|
|
107
132
|
image_properties_by_type = asset_types.flat_map do |type|
|
@@ -129,16 +154,18 @@ class UIC::Presentation
|
|
129
154
|
end ].tap{ |h| h.extend(UIC::PresentableHash) }
|
130
155
|
end
|
131
156
|
|
157
|
+
# @return [Array<String>] array of all image paths referenced by this presentation.
|
132
158
|
def image_paths
|
133
159
|
image_usage.keys
|
134
160
|
end
|
135
161
|
|
162
|
+
# Find or create an asset for a scene graph element.
|
163
|
+
# @param el [Nokogiri::XML::Element] the scene graph element.
|
136
164
|
def asset_for_el(el)
|
137
165
|
(@asset_by_el[el] ||= el['class'] ? @class_by_ref[el['class']].new(self,el) : app.metadata.new_instance(self,el))
|
138
166
|
end
|
167
|
+
private :asset_for_el
|
139
168
|
|
140
|
-
attr_reader :addsets_by_graph
|
141
|
-
protected :addsets_by_graph
|
142
169
|
|
143
170
|
def referenced_files
|
144
171
|
(
|
@@ -148,11 +175,22 @@ class UIC::Presentation
|
|
148
175
|
).sort_by{ |f| parts = f.split(/[\/\\]/); [parts.length,parts] }
|
149
176
|
end
|
150
177
|
|
178
|
+
# @return [MetaData::Scene] the root scene asset for the presentation.
|
151
179
|
def scene
|
152
180
|
asset_for_el( @scene )
|
153
181
|
end
|
154
182
|
|
155
|
-
|
183
|
+
# Generate the script path for an asset in the presentation.
|
184
|
+
#
|
185
|
+
# * If `from_asset` is supplied the path will be relative to that asset (e.g. `"parent.parent.Group.Model"`).
|
186
|
+
# * If `from_asset` is omitted the path will be absolute (e.g. `"Scene.Layer.Group.Model"`).
|
187
|
+
#
|
188
|
+
# @param asset [MetaData::Root] the asset to find the path to.
|
189
|
+
# @param from_asset [MetaData::Root] the asset to find the path relative to.
|
190
|
+
# @return [String] the script path to the element.
|
191
|
+
def path_to( asset, from_asset=nil )
|
192
|
+
el = asset.el
|
193
|
+
|
156
194
|
to_parts = if el.ancestors('Graph')
|
157
195
|
[].tap{ |parts|
|
158
196
|
until el==@graph
|
@@ -161,7 +199,8 @@ class UIC::Presentation
|
|
161
199
|
end
|
162
200
|
}
|
163
201
|
end
|
164
|
-
if
|
202
|
+
if from_asset && from_asset.el.ancestors('Graph')
|
203
|
+
from_el = from_asset.el
|
165
204
|
from_parts = [].tap{ |parts|
|
166
205
|
until from_el==@graph
|
167
206
|
parts.unshift asset_for_el(from_el).name
|
@@ -177,16 +216,38 @@ class UIC::Presentation
|
|
177
216
|
to_parts.join('.')
|
178
217
|
end
|
179
218
|
|
219
|
+
# @return [Boolean] true if there any errors with the presentation.
|
180
220
|
def errors?
|
181
221
|
(!errors.empty?)
|
182
222
|
end
|
183
223
|
|
224
|
+
# @return [Array<String>] an array (possibly empty) of all errors in this presentation.
|
184
225
|
def errors
|
185
226
|
(file_found? ? [] : ["File not found: '#{file}'"])
|
186
227
|
end
|
187
228
|
|
229
|
+
# Find an element or asset in this presentation by scripting path.
|
230
|
+
#
|
231
|
+
# * If `root` is supplied, the path is resolved relative to that asset.
|
232
|
+
# * If `root` is not supplied, the path is resolved as a root-level path.
|
233
|
+
#
|
234
|
+
# @example
|
235
|
+
# preso = app.main
|
236
|
+
# scene = preso.scene
|
237
|
+
# camera = scene/"Layer.Camera"
|
238
|
+
#
|
239
|
+
# # Four ways to find the same layer
|
240
|
+
# layer1 = preso/"Scene.Layer"
|
241
|
+
# layer2 = preso.at "Scene.Layer"
|
242
|
+
# layer3 = preso.at "Layer", scene
|
243
|
+
# layer4 = preso.at "parent", camera
|
244
|
+
#
|
245
|
+
# assert layer1==layer2 && layer2==layer3 && layer3==layer4
|
246
|
+
#
|
247
|
+
# @return [MetaData::Root] The found asset, or `nil` if it cannot be found.
|
188
248
|
def at(path,root=@graph)
|
189
249
|
name,path = path.split('.',2)
|
250
|
+
root = root.el if root.respond_to?(:el)
|
190
251
|
el = case name
|
191
252
|
when 'parent' then root==@scene ? nil : root.parent
|
192
253
|
when 'Scene' then @scene
|
@@ -196,16 +257,48 @@ class UIC::Presentation
|
|
196
257
|
end
|
197
258
|
alias_method :/, :at
|
198
259
|
|
199
|
-
|
260
|
+
# Fetch the value of an asset's attribute on a particular slide. Slide `0` is the Master Slide, slide `1` is the first non-master slide.
|
261
|
+
#
|
262
|
+
# This method is used internally by assets; accessing attributes directly from the asset is generally more appropriate.
|
263
|
+
#
|
264
|
+
# @example
|
265
|
+
# preso = app.main_presentation
|
266
|
+
# camera = preso/"Scene.Layer.Camera"
|
267
|
+
#
|
268
|
+
# assert preso.get_attribute(camera,'position',0) == camera['position',0]
|
269
|
+
#
|
270
|
+
# @param asset [MetaData::Root] the asset to fetch the attribute for.
|
271
|
+
# @param attr_name [String] the name of the attribute to get the value of.
|
272
|
+
# @param slide_name_or_index [String,Integer] the string name or integer index of the slide.
|
273
|
+
def get_attribute( asset, attr_name, slide_name_or_index )
|
274
|
+
graph_element = asset.el
|
200
275
|
((addsets=@addsets_by_graph[graph_element]) && ( # State (slide) don't have any addsets
|
201
|
-
( addsets[slide_name_or_index] && addsets[slide_name_or_index][
|
202
|
-
( addsets[0] && addsets[0][
|
203
|
-
) || graph_element[
|
276
|
+
( addsets[slide_name_or_index] && addsets[slide_name_or_index][attr_name] ) || # Try for a Set on the specific slide
|
277
|
+
( addsets[0] && addsets[0][attr_name] ) # …else try the master slide
|
278
|
+
) || graph_element[attr_name]) # …else try the graph
|
204
279
|
# TODO: handle animation (child of addset)
|
205
280
|
end
|
206
281
|
|
207
|
-
|
208
|
-
|
282
|
+
# Set the value of an asset's attribute on a particular slide. Slide `0` is the Master Slide, slide `1` is the first non-master slide.
|
283
|
+
#
|
284
|
+
# This method is used internally by assets; accessing attributes directly from the asset is generally more appropriate.
|
285
|
+
#
|
286
|
+
# @example
|
287
|
+
# preso = app.main_presentation
|
288
|
+
# camera = preso/"Scene.Layer.Camera"
|
289
|
+
#
|
290
|
+
# # The long way to set the attribute value
|
291
|
+
# preso.set_attribute(camera,'endtime',0,1000)
|
292
|
+
#
|
293
|
+
# # …and the shorter way
|
294
|
+
# camera['endtime',0] = 1000
|
295
|
+
#
|
296
|
+
# @param asset [MetaData::Root] the asset to fetch the attribute for.
|
297
|
+
# @param attr_name [String] the name of the attribute to get the value of.
|
298
|
+
# @param slide_name_or_index [String,Integer] the string name or integer index of the slide.
|
299
|
+
def set_attribute( asset, property_name, slide_name_or_index, str )
|
300
|
+
graph_element = asset.el
|
301
|
+
if attribute_linked?( asset, property_name )
|
209
302
|
if @addsets_by_graph[graph_element]
|
210
303
|
@addsets_by_graph[graph_element][0][property_name] = str
|
211
304
|
else
|
@@ -218,7 +311,7 @@ class UIC::Presentation
|
|
218
311
|
else
|
219
312
|
master = master_slide_for( graph_element )
|
220
313
|
slide_count = master.xpath('count(./State)').to_i
|
221
|
-
0.upto(slide_count).each{ |idx| set_attribute(
|
314
|
+
0.upto(slide_count).each{ |idx| set_attribute(asset,property_name,idx,str) }
|
222
315
|
end
|
223
316
|
else
|
224
317
|
raise "TODO"
|
@@ -226,24 +319,37 @@ class UIC::Presentation
|
|
226
319
|
end
|
227
320
|
end
|
228
321
|
|
229
|
-
|
230
|
-
|
322
|
+
# @return [MetaData::Root] the component (or Scene) asset that owns the supplied asset.
|
323
|
+
# @see MetaData::Root#component
|
324
|
+
def owning_component( asset )
|
325
|
+
asset_for_el( owning_component_element( asset.el ) )
|
231
326
|
end
|
232
327
|
|
328
|
+
# @return [MetaData::Root] the component asset that owns the supplied asset.
|
329
|
+
# @see MetaData::Root#component
|
233
330
|
def owning_component_element( graph_element )
|
234
331
|
graph_element.at_xpath('(ancestor::Component[1] | ancestor::Scene[1])[last()]')
|
235
332
|
end
|
333
|
+
private :owning_component_element
|
236
334
|
|
335
|
+
# @return [Nokogiri::XML::Element] the "time context" scene graph element that owns the supplied element.
|
237
336
|
def owning_or_self_component_element( graph_element )
|
238
337
|
graph_element.at_xpath('(ancestor-or-self::Component[1] | ancestor-or-self::Scene[1])[last()]')
|
239
338
|
end
|
339
|
+
private :owning_or_self_component_element
|
240
340
|
|
341
|
+
# @return [Nokogiri::XML::Element] the logic-graph element representing the master slide for a scene graph element
|
241
342
|
def master_slide_for( graph_element )
|
242
343
|
comp = owning_or_self_component_element( graph_element )
|
243
344
|
@logic.at("./State[@component='##{comp['id']}']")
|
244
345
|
end
|
346
|
+
private :master_slide_for
|
245
347
|
|
246
|
-
|
348
|
+
# @param asset [MetaData::Root] the asset to get the slides for.
|
349
|
+
# @return [SlideCollection] an array-like collection of all slides that the asset is available on.
|
350
|
+
# @see MetaData::Root#slides
|
351
|
+
def slides_for( asset )
|
352
|
+
graph_element = asset.el
|
247
353
|
@slides_for[graph_element] ||= begin
|
248
354
|
slides = []
|
249
355
|
master = master_slide_for( graph_element )
|
@@ -254,31 +360,59 @@ class UIC::Presentation
|
|
254
360
|
end
|
255
361
|
end
|
256
362
|
|
257
|
-
|
363
|
+
# @return [Boolean] true if the asset exists on the supplied slide.
|
364
|
+
# @see MetaData::Root#has_slide?
|
365
|
+
def has_slide?( asset, slide_name_or_index )
|
366
|
+
graph_element = asset.el
|
258
367
|
if graph_element == @scene
|
259
368
|
# The scene is never actually added, so we'll treat it just like the first add, which is on the master slide of the scene
|
260
|
-
has_slide?( @addsets_by_graph.first.first, slide_name_or_index )
|
369
|
+
has_slide?( asset_for_el( @addsets_by_graph.first.first ), slide_name_or_index )
|
261
370
|
else
|
262
371
|
@addsets_by_graph[graph_element][slide_name_or_index] || @addsets_by_graph[graph_element][0]
|
263
372
|
end
|
264
373
|
end
|
265
374
|
|
266
|
-
|
375
|
+
# @example
|
376
|
+
# preso = app.main
|
377
|
+
# camera = preso/"Scene.Layer.Camera"
|
378
|
+
#
|
379
|
+
# # Two ways of determining if an attribute for an asset is linked.
|
380
|
+
# if preso.attribute_linked?( camera, 'fov' )
|
381
|
+
# if camera['fov'].linked?
|
382
|
+
#
|
383
|
+
# @return [Boolean] true if this asset's attribute is linked on the master slide.
|
384
|
+
# @see ValuesPerSlide#linked?
|
385
|
+
def attribute_linked?( asset, attribute_name )
|
386
|
+
graph_element = asset.el
|
267
387
|
!(@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][1] && @addsets_by_graph[graph_element][1].key?(attribute_name))
|
268
388
|
end
|
269
389
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
390
|
+
# Unlinks a master attribute, yielding distinct values on each slide. If the asset is not on the master slide, or the attribute is already unlinked, no change occurs.
|
391
|
+
#
|
392
|
+
# @param asset [MetaData::Root] the master asset to unlink the attribute on.
|
393
|
+
# @param attribute_name [String] the name of the attribute to unlink.
|
394
|
+
# @return [Boolean] `true` if the attribute was previously linked; `false` otherwise.
|
395
|
+
def unlink_attribute(asset,attribute_name)
|
396
|
+
graph_element = asset.el
|
397
|
+
if master?(asset) && attribute_linked?(asset,attribute_name)
|
398
|
+
master_value = get_attribute( asset, attribute_name, 0 )
|
399
|
+
slides_for( asset ).to_ary[1..-1].each do |slide|
|
274
400
|
addset = slide.el.at_xpath( ".//*[@ref='##{graph_element['id']}']" ) || slide.el.add_child("<Set ref='##{graph_element['id']}'/>").first
|
275
401
|
addset[attribute_name] = master_value
|
276
402
|
end
|
277
403
|
rebuild_caches_from_document
|
278
404
|
true
|
405
|
+
else
|
406
|
+
false
|
279
407
|
end
|
280
408
|
end
|
281
409
|
|
410
|
+
# Replace an existing asset with a new kind of asset.
|
411
|
+
#
|
412
|
+
# @param existing_asset [MetaData::Root] the existing asset to replace.
|
413
|
+
# @param new_type [String] the name of the asset type, e.g. `"ReferencedMaterial"` or `"Group"`.
|
414
|
+
# @param attributes [Hash] initial attribute values for the new asset.
|
415
|
+
# @return [MetaData::Root] the newly-created asset.
|
282
416
|
def replace_asset( existing_asset, new_type, attributes={} )
|
283
417
|
old_el = existing_asset.el
|
284
418
|
new_el = old_el.replace( "<#{new_type}/>" ).first
|
@@ -291,8 +425,9 @@ class UIC::Presentation
|
|
291
425
|
end
|
292
426
|
end
|
293
427
|
|
294
|
-
#
|
295
|
-
def master?(
|
428
|
+
# @return [Boolean] `true` if the asset is added on the master slide.
|
429
|
+
def master?(asset)
|
430
|
+
graph_element = asset.el
|
296
431
|
(graph_element == @scene) || !!(@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][0])
|
297
432
|
end
|
298
433
|
|
@@ -305,8 +440,8 @@ class UIC::Presentation
|
|
305
440
|
next unless options.all? do |att,val|
|
306
441
|
case att
|
307
442
|
when :_type then el.name == val
|
308
|
-
when :_slide then has_slide?(
|
309
|
-
when :_master then master?(
|
443
|
+
when :_slide then has_slide?(asset,val)
|
444
|
+
when :_master then master?(asset)==val
|
310
445
|
else
|
311
446
|
if asset.properties[att.to_s]
|
312
447
|
value = asset[att.to_s].value
|