RUIC 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +3 -4
- data/README.md +278 -279
- 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 +175 -175
- data/lib/ruic/application.rb +5 -1
- data/lib/ruic/assets.rb +312 -289
- data/lib/ruic/attributes.rb +165 -165
- data/lib/ruic/behaviors.rb +1 -0
- data/lib/ruic/interfaces.rb +45 -7
- data/lib/ruic/presentation.rb +103 -39
- data/lib/ruic/ripl-after-result.rb +24 -11
- data/lib/ruic/statemachine.rb +3 -2
- 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/Paths/Paths.uia +4 -0
- data/test/projects/Paths/Paths.uip +98 -0
- 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 +29 -3
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,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::
|
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
|
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::AssetBase)
|
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
data/lib/ruic/interfaces.rb
CHANGED
@@ -1,35 +1,73 @@
|
|
1
|
+
# Supports classes that represent an XML file on disk (e.g. `.uia` and `.uip`).
|
1
2
|
module UIC::FileBacked
|
2
|
-
|
3
|
+
# @return [Nokogiri::XML::Document] the Nokogiri document representing the instance.
|
4
|
+
attr_accessor :doc
|
5
|
+
|
6
|
+
# @return [String] the absolute path to the underlying file.
|
7
|
+
attr_accessor :file
|
8
|
+
|
9
|
+
# @param relative [String] a file path relative to this file.
|
10
|
+
# @return [String] the full path resolved relative to this file.
|
3
11
|
def path_to( relative )
|
4
12
|
File.expand_path( relative.gsub('\\','/'), File.dirname(file) )
|
5
13
|
end
|
14
|
+
|
15
|
+
# @return [String] the name of the file (without any directories).
|
6
16
|
def filename
|
7
17
|
File.basename(file)
|
8
18
|
end
|
19
|
+
|
20
|
+
# @return [Boolean] `true` if the underlying file exists on disk.
|
9
21
|
def file_found?
|
10
|
-
|
22
|
+
@file && File.exist?(@file)
|
11
23
|
end
|
24
|
+
|
25
|
+
# Set the file for the class. Does **not** attempt to load the XML document.
|
26
|
+
# @param new_path [String] the file path for this class.
|
27
|
+
# @return [String]
|
12
28
|
def file=( new_path )
|
13
29
|
@file = File.expand_path(new_path)
|
14
|
-
@file_not_found = !File.exist?(new_path)
|
15
30
|
end
|
31
|
+
|
32
|
+
# @return [String] the XML representation of the document.
|
16
33
|
def to_xml
|
17
34
|
doc.to_xml( indent:1, indent_text:"\t" )
|
18
35
|
end
|
36
|
+
|
37
|
+
# Overwrite the associated file on disk with the {#to_xml} representation of this class.
|
38
|
+
# @return [true]
|
19
39
|
def save!
|
20
40
|
File.open(file,'w:utf-8'){ |f| f << to_xml }
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
# Save to the supplied file path. Subsequent calls to {#save!} will save to the new file, not the original file name.
|
45
|
+
def save_as(new_file)
|
46
|
+
File.open(new_file,'w:utf-8'){ |f| f << to_xml }
|
47
|
+
self.file = new_file
|
21
48
|
end
|
22
49
|
end
|
23
50
|
|
51
|
+
# Supports classes that represent an XML element (e.g. `<presentation id="main" src="foo.uip"/>`).
|
24
52
|
module UIC::ElementBacked
|
25
|
-
|
53
|
+
|
54
|
+
# @return [Object] the object in charge of this instance.
|
55
|
+
attr_accessor :owner
|
56
|
+
|
57
|
+
# @return [Nokogiri::XML::Element] the element backing this instance.
|
58
|
+
attr_accessor :el
|
59
|
+
|
60
|
+
# @private
|
26
61
|
def self.included(base)
|
27
62
|
base.extend(ClassMethods)
|
28
63
|
end
|
64
|
+
|
29
65
|
module ClassMethods
|
30
|
-
|
31
|
-
|
32
|
-
|
66
|
+
# @param name [String] the name of an XML attribute to expose.
|
67
|
+
# @param getblock [Proc] a proc to run
|
68
|
+
def xmlattribute(name,getblock=nil,&setblock)
|
69
|
+
define_method(name){ getblock ? getblock[@el[name]] : @el[name] }
|
70
|
+
define_method("#{name}="){ |new_value| @el[name] = (setblock ? setblock[new_value] : new_value).to_s }
|
33
71
|
end
|
34
72
|
end
|
35
73
|
end
|
data/lib/ruic/presentation.rb
CHANGED
@@ -52,16 +52,13 @@ class UIC::Presentation
|
|
52
52
|
@slides_by_el = {} # indexed by slide state element
|
53
53
|
end
|
54
54
|
|
55
|
+
# @return [String] the xml representation of this presentation. Formatted to match UI Composer Studio's formatting as closely as possible (for minimal diffs after update).
|
55
56
|
def to_xml
|
56
57
|
doc.to_xml( indent:1, indent_text:"\t" )
|
57
58
|
.gsub( %r{(<\w+(?: [\w:]+="[^"]*")*)(/?>)}i, '\1 \2' )
|
58
59
|
.sub('"?>','" ?>')
|
59
60
|
end
|
60
61
|
|
61
|
-
def save_as(new_file)
|
62
|
-
File.open(new_file,'w:utf-8'){ |f| f << to_xml }
|
63
|
-
end
|
64
|
-
|
65
62
|
# Update the presentation to be in-sync with the document.
|
66
63
|
# Must be called whenever the in-memory representation of the XML document is changed.
|
67
64
|
# Called automatically by all necessary methods; only necessary if script (dangerously)
|
@@ -90,12 +87,12 @@ class UIC::Presentation
|
|
90
87
|
|
91
88
|
# Find an asset in the presentation based on its internal XML identifier.
|
92
89
|
# @param id [String] the id of the asset (not an idref), e.g. `"Material_003"`.
|
93
|
-
# @return [MetaData::
|
90
|
+
# @return [MetaData::AssetBase] the found asset, or `nil` if could not be found.
|
94
91
|
def asset_by_id( id )
|
95
92
|
(@graph_by_id[id] && asset_for_el( @graph_by_id[id] ))
|
96
93
|
end
|
97
94
|
|
98
|
-
# @param asset [MetaData::
|
95
|
+
# @param asset [MetaData::AssetBase] an asset in the presentation
|
99
96
|
# @return [Integer] the index of the first slide where an asset is added (0 for master, non-zero for non-master).
|
100
97
|
def slide_index(asset)
|
101
98
|
# TODO: probably faster to .find the first @addsets_by_graph
|
@@ -104,8 +101,8 @@ class UIC::Presentation
|
|
104
101
|
(slide ? slide.xpath('count(ancestor::State) + count(preceding-sibling::State[ancestor::State])').to_i : 0) # the Scene is never added
|
105
102
|
end
|
106
103
|
|
107
|
-
# @param child_asset [MetaData::
|
108
|
-
# @return [MetaData::
|
104
|
+
# @param child_asset [MetaData::AssetBase] an asset in the presentation.
|
105
|
+
# @return [MetaData::AssetBase] the scene graph parent of the child asset, or `nil` for the Scene.
|
109
106
|
def parent_asset( child_asset )
|
110
107
|
child_graph_el = child_asset.el
|
111
108
|
unless child_graph_el==@scene || child_graph_el.parent.nil?
|
@@ -113,8 +110,8 @@ class UIC::Presentation
|
|
113
110
|
end
|
114
111
|
end
|
115
112
|
|
116
|
-
# @param parent_asset [MetaData::
|
117
|
-
# @return [Array<MetaData::
|
113
|
+
# @param parent_asset [MetaData::AssetBase] an asset in the presentation.
|
114
|
+
# @return [Array<MetaData::AssetBase>] array of scene graph children of the specified asset.
|
118
115
|
def child_assets( parent_asset )
|
119
116
|
parent_asset.el.element_children.map{ |child| asset_for_el(child) }
|
120
117
|
end
|
@@ -166,14 +163,13 @@ class UIC::Presentation
|
|
166
163
|
end
|
167
164
|
private :asset_for_el
|
168
165
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
end
|
166
|
+
# def referenced_files
|
167
|
+
# (
|
168
|
+
# (images + behaviors + effects + meshes + materials ).map(&:file)
|
169
|
+
# + effects.flat_map(&:images)
|
170
|
+
# + fonts
|
171
|
+
# ).sort_by{ |f| parts = f.split(/[\/\\]/); [parts.length,parts] }
|
172
|
+
# end
|
177
173
|
|
178
174
|
# @return [MetaData::Scene] the root scene asset for the presentation.
|
179
175
|
def scene
|
@@ -185,8 +181,8 @@ class UIC::Presentation
|
|
185
181
|
# * If `from_asset` is supplied the path will be relative to that asset (e.g. `"parent.parent.Group.Model"`).
|
186
182
|
# * If `from_asset` is omitted the path will be absolute (e.g. `"Scene.Layer.Group.Model"`).
|
187
183
|
#
|
188
|
-
# @param asset [MetaData::
|
189
|
-
# @param from_asset [MetaData::
|
184
|
+
# @param asset [MetaData::AssetBase] the asset to find the path to.
|
185
|
+
# @param from_asset [MetaData::AssetBase] the asset to find the path relative to.
|
190
186
|
# @return [String] the script path to the element.
|
191
187
|
def path_to( asset, from_asset=nil )
|
192
188
|
el = asset.el
|
@@ -244,7 +240,10 @@ class UIC::Presentation
|
|
244
240
|
#
|
245
241
|
# assert layer1==layer2 && layer2==layer3 && layer3==layer4
|
246
242
|
#
|
247
|
-
# @return [MetaData::
|
243
|
+
# @return [MetaData::AssetBase] The found asset, or `nil` if it cannot be found.
|
244
|
+
#
|
245
|
+
# @see Application#at
|
246
|
+
# @see MetaData::AssetBase#at
|
248
247
|
def at(path,root=@graph)
|
249
248
|
name,path = path.split('.',2)
|
250
249
|
root = root.el if root.respond_to?(:el)
|
@@ -267,7 +266,7 @@ class UIC::Presentation
|
|
267
266
|
#
|
268
267
|
# assert preso.get_attribute(camera,'position',0) == camera['position',0]
|
269
268
|
#
|
270
|
-
# @param asset [MetaData::
|
269
|
+
# @param asset [MetaData::AssetBase] the asset to fetch the attribute for.
|
271
270
|
# @param attr_name [String] the name of the attribute to get the value of.
|
272
271
|
# @param slide_name_or_index [String,Integer] the string name or integer index of the slide.
|
273
272
|
def get_attribute( asset, attr_name, slide_name_or_index )
|
@@ -293,7 +292,7 @@ class UIC::Presentation
|
|
293
292
|
# # …and the shorter way
|
294
293
|
# camera['endtime',0] = 1000
|
295
294
|
#
|
296
|
-
# @param asset [MetaData::
|
295
|
+
# @param asset [MetaData::AssetBase] the asset to fetch the attribute for.
|
297
296
|
# @param attr_name [String] the name of the attribute to get the value of.
|
298
297
|
# @param slide_name_or_index [String,Integer] the string name or integer index of the slide.
|
299
298
|
def set_attribute( asset, property_name, slide_name_or_index, str )
|
@@ -319,14 +318,14 @@ class UIC::Presentation
|
|
319
318
|
end
|
320
319
|
end
|
321
320
|
|
322
|
-
# @return [MetaData::
|
323
|
-
# @see MetaData::
|
321
|
+
# @return [MetaData::AssetBase] the component (or Scene) asset that owns the supplied asset.
|
322
|
+
# @see MetaData::AssetBase#component
|
324
323
|
def owning_component( asset )
|
325
324
|
asset_for_el( owning_component_element( asset.el ) )
|
326
325
|
end
|
327
326
|
|
328
|
-
# @return [MetaData::
|
329
|
-
# @see MetaData::
|
327
|
+
# @return [MetaData::AssetBase] the component asset that owns the supplied asset.
|
328
|
+
# @see MetaData::AssetBase#component
|
330
329
|
def owning_component_element( graph_element )
|
331
330
|
graph_element.at_xpath('(ancestor::Component[1] | ancestor::Scene[1])[last()]')
|
332
331
|
end
|
@@ -345,9 +344,9 @@ class UIC::Presentation
|
|
345
344
|
end
|
346
345
|
private :master_slide_for
|
347
346
|
|
348
|
-
# @param asset [MetaData::
|
347
|
+
# @param asset [MetaData::AssetBase] the asset to get the slides for.
|
349
348
|
# @return [SlideCollection] an array-like collection of all slides that the asset is available on.
|
350
|
-
# @see MetaData::
|
349
|
+
# @see MetaData::AssetBase#slides
|
351
350
|
def slides_for( asset )
|
352
351
|
graph_element = asset.el
|
353
352
|
@slides_for[graph_element] ||= begin
|
@@ -361,7 +360,7 @@ class UIC::Presentation
|
|
361
360
|
end
|
362
361
|
|
363
362
|
# @return [Boolean] true if the asset exists on the supplied slide.
|
364
|
-
# @see MetaData::
|
363
|
+
# @see MetaData::AssetBase#has_slide?
|
365
364
|
def has_slide?( asset, slide_name_or_index )
|
366
365
|
graph_element = asset.el
|
367
366
|
if graph_element == @scene
|
@@ -389,7 +388,7 @@ class UIC::Presentation
|
|
389
388
|
|
390
389
|
# 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
390
|
#
|
392
|
-
# @param asset [MetaData::
|
391
|
+
# @param asset [MetaData::AssetBase] the master asset to unlink the attribute on.
|
393
392
|
# @param attribute_name [String] the name of the attribute to unlink.
|
394
393
|
# @return [Boolean] `true` if the attribute was previously linked; `false` otherwise.
|
395
394
|
def unlink_attribute(asset,attribute_name)
|
@@ -409,10 +408,10 @@ class UIC::Presentation
|
|
409
408
|
|
410
409
|
# Replace an existing asset with a new kind of asset.
|
411
410
|
#
|
412
|
-
# @param existing_asset [MetaData::
|
411
|
+
# @param existing_asset [MetaData::AssetBase] the existing asset to replace.
|
413
412
|
# @param new_type [String] the name of the asset type, e.g. `"ReferencedMaterial"` or `"Group"`.
|
414
413
|
# @param attributes [Hash] initial attribute values for the new asset.
|
415
|
-
# @return [MetaData::
|
414
|
+
# @return [MetaData::AssetBase] the newly-created asset.
|
416
415
|
def replace_asset( existing_asset, new_type, attributes={} )
|
417
416
|
old_el = existing_asset.el
|
418
417
|
new_el = old_el.replace( "<#{new_type}/>" ).first
|
@@ -431,13 +430,65 @@ class UIC::Presentation
|
|
431
430
|
(graph_element == @scene) || !!(@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][0])
|
432
431
|
end
|
433
432
|
|
434
|
-
|
433
|
+
# Find assets in this presentation matching criteria.
|
434
|
+
#
|
435
|
+
# @example 1) Searching for simple values
|
436
|
+
# every_asset = preso.find # Every asset in the presentation
|
437
|
+
# master_assets = preso.find _master:true # Test for master/nonmaster
|
438
|
+
# models = preso.find _type:'Model' # …or based on type
|
439
|
+
# slide2_assets = preso.find _slide:2 # …or presence on a specific slide
|
440
|
+
# rectangles = preso.find sourcepath:'#Rectangle' # …or attribute values
|
441
|
+
# gamecovers = preso.find name:'Game Cover' # …including the name
|
442
|
+
#
|
443
|
+
# @example 2) Combine tests to get more specific
|
444
|
+
# master_models = preso.find _type:'Model', _master:true
|
445
|
+
# slide2_rects = preso.find _type:'Model', _slide:2, sourcepath:'#Rectangle'
|
446
|
+
# nonmaster_s2 = preso.find _slide:2, _master:false
|
447
|
+
# red_materials = preso.find _type:'Material', diffuse:[1,0,0]
|
448
|
+
#
|
449
|
+
# @example 3) Matching values more loosely
|
450
|
+
# pistons = preso.find name:/^Piston/ # Regex for batch finding
|
451
|
+
# bottom_row = preso.find position:[nil,-200,nil] # nil for wildcards in vectors
|
452
|
+
#
|
453
|
+
# @example 4) Restrict the search to a sub-tree
|
454
|
+
# group = preso/"Scene.Layer.Group"
|
455
|
+
# group_models = preso.find _under:group, _type:'Model' # All models under the group
|
456
|
+
# group_models = group.find _type:'Model' # alternatively start from the asset
|
457
|
+
#
|
458
|
+
# @example 5) Iterate the results as they are found
|
459
|
+
# preso.find _type:'Model', name:/^Piston/ do |model, index|
|
460
|
+
# show "Model #{index} is named #{model.name}"
|
461
|
+
# end
|
462
|
+
#
|
463
|
+
# # You do not need to receive the index if you do not need its value
|
464
|
+
# group.find _type:'Model' do |model|
|
465
|
+
# scale = model['scale'].value
|
466
|
+
# scale.x = scale.y = scale.z = 1
|
467
|
+
# end
|
468
|
+
#
|
469
|
+
# @param criteria [Hash] Attribute names and values, along with a few special keys.
|
470
|
+
# @option criteria :_type [String] asset must be of specified type, e.g. `"Model"` or `"Material"` or `"PathAnchorPoint"`.
|
471
|
+
# @option criteria :_slide [Integer,String] slide number or name that the asset must be present on.
|
472
|
+
# @option criteria :_master [Boolean] `true` for only master assets, `false` for only non-master assets.
|
473
|
+
# @option criteria :_under [MetaData::AssetBase] a root asset to require as an ancestor; this asset will never be in the search results.
|
474
|
+
# @option criteria :attribute_name [Numeric] numeric attribute value must be within `0.001` of the supplied value.
|
475
|
+
# @option criteria :attribute_name [String] string attribute value must match the supplied string exactly.
|
476
|
+
# @option criteria :attribute_name [Regexp] supplied regex must match the string attribute value.
|
477
|
+
# @option criteria :attribute_name [Array] each component of the attribute's vector value must be within `0.001` of the supplied value in the array;
|
478
|
+
# if a value in the array is `nil` that component of the vector may have any value.
|
479
|
+
#
|
480
|
+
# @yield [asset,index] Yields each found asset (and its index in the results) to the block (if supplied) as they are found.
|
481
|
+
#
|
482
|
+
# @return [Array<MetaData::AssetBase>] array of all matching assets (may be empty).
|
483
|
+
#
|
484
|
+
# @see MetaData::AssetBase#find
|
485
|
+
def find(criteria={},&block)
|
435
486
|
index = -1
|
436
|
-
start =
|
487
|
+
start = criteria.key?(:_under) ? criteria.delete(:_under).el : @graph
|
437
488
|
[].tap do |result|
|
438
489
|
start.xpath('./descendant::*').each do |el|
|
439
490
|
asset = asset_for_el(el)
|
440
|
-
next unless
|
491
|
+
next unless criteria.all? do |att,val|
|
441
492
|
case att
|
442
493
|
when :_type then el.name == val
|
443
494
|
when :_slide then has_slide?(asset,val)
|
@@ -460,6 +511,7 @@ class UIC::Presentation
|
|
460
511
|
end
|
461
512
|
end
|
462
513
|
|
514
|
+
# @private
|
463
515
|
def inspect
|
464
516
|
"<#{self.class} #{File.basename(file)}>"
|
465
517
|
end
|
@@ -471,14 +523,25 @@ end
|
|
471
523
|
|
472
524
|
class UIC::Application::Presentation < UIC::Presentation
|
473
525
|
include UIC::ElementBacked
|
474
|
-
|
475
|
-
|
526
|
+
# @!parse extend UIC::ElementBacked::ClassMethods
|
527
|
+
|
528
|
+
# @!attribute [rw] id
|
529
|
+
# @return [String] the id of the presentation asset
|
476
530
|
xmlattribute :id do |new_id|
|
477
531
|
main_preso = app.main_presentation
|
478
532
|
super(new_id)
|
479
533
|
app.main_presentation=self if main_preso==self
|
480
534
|
end
|
481
|
-
|
535
|
+
|
536
|
+
# @!attribute src
|
537
|
+
# @return [String] the path to the presentation file
|
538
|
+
xmlattribute :src
|
539
|
+
|
540
|
+
# @!attribute active
|
541
|
+
# @return [Boolean] is the presentation initially active?
|
542
|
+
xmlattribute :active, ->(val){ val=="True" } do |new_val|
|
543
|
+
new_val ? 'True' : 'False'
|
544
|
+
end
|
482
545
|
|
483
546
|
def initialize(application,el)
|
484
547
|
self.owner = application
|
@@ -492,6 +555,7 @@ class UIC::Application::Presentation < UIC::Presentation
|
|
492
555
|
end
|
493
556
|
end
|
494
557
|
|
558
|
+
# @private no need to document this monkeypatch
|
495
559
|
class Nokogiri::XML::Element
|
496
560
|
def index(kind='*') # Find the index of this element amongs its siblings
|
497
561
|
xpath("count(./preceding-sibling::#{kind})").to_i
|