RUIC 0.4.1 → 0.4.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.
- 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
|