Skip to content

Commit 1584dbd

Browse files
committed
support recursive models and registering with sdf_version = nil
1 parent 3953ecf commit 1584dbd

2 files changed

Lines changed: 76 additions & 14 deletions

File tree

lib/sdf/xml.rb

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ def self.gazebo_models(sdf_version = nil)
165165
#
166166
# @param [String] model_name the target name in the cache
167167
# @param [REXML::Document,REXML::Element,String] xml_doc the XML model representation
168-
def self.register_in_memory_model(model_name, xml_doc, sdf_version: nil)
168+
# @param [Hash,nil] metadata the pre-resolved include metadata
169+
def self.register_in_memory_model(model_name, xml_doc, sdf_version: nil, metadata: nil)
169170
xml = case xml_doc
170171
when REXML::Document
171172
xml_doc
@@ -186,19 +187,24 @@ def self.register_in_memory_model(model_name, xml_doc, sdf_version: nil)
186187
end
187188
end
188189

190+
virtual_path = "virtual://#{model_name}"
191+
metadata ||= {}
192+
metadata["includes"] ||= {}
193+
metadata["path"] ||= virtual_path
194+
189195
@gazebo_models[sdf_version] ||= {}
190196
cache = (@gazebo_models[sdf_version][model_name] ||= ModelCacheEntry.new)
191-
cache.path = "virtual://#{model_name}"
192-
cache.xml = xml
193-
cache.metadata = { "includes" => {}, "path" => cache.path }
197+
cache.path = metadata["path"]
198+
cache.xml = xml
199+
cache.metadata = metadata
194200

195201
# Also register under nil as a generic fallback
196202
if sdf_version
197203
@gazebo_models[nil] ||= {}
198204
cache_nil = (@gazebo_models[nil][model_name] ||= ModelCacheEntry.new)
199-
cache_nil.path = "virtual://#{model_name}"
200-
cache_nil.xml = xml
201-
cache_nil.metadata = { "includes" => {}, "path" => cache_nil.path }
205+
cache_nil.path = cache.path
206+
cache_nil.xml = xml
207+
cache_nil.metadata = metadata
202208
end
203209
end
204210

@@ -231,6 +237,14 @@ def self.model_path_from_name(model_name, model_path: @model_path, sdf_version:
231237
cache = (@gazebo_models[sdf_version][model_name] ||= ModelCacheEntry.new)
232238
return cache.path if cache.path
233239

240+
# Fallback to the nil cache for virtual in-memory models
241+
if sdf_version && (nil_cache = @gazebo_models.dig(nil, model_name)) && nil_cache.path && nil_cache.path.start_with?("virtual://")
242+
cache.path = nil_cache.path
243+
cache.xml = nil_cache.xml if nil_cache.xml
244+
cache.metadata = nil_cache.metadata if nil_cache.metadata
245+
return cache.path
246+
end
247+
234248
model_path.each do |p|
235249
model_dir = File.join(p, model_name)
236250
if File.file?(File.join(model_dir, "model.config"))
@@ -508,16 +522,23 @@ def self.sdf_version_of(sdf)
508522
# @raise [NotSDF] if the file is not a SDF file
509523
# @raise [InvalidXML] if the file is not a valid XML file
510524
# @return [REXML::Element]
511-
def self.load_sdf(sdf_file, flatten: true, metadata: false)
512-
sdf = load_sdf_raw(sdf_file)
525+
# Processes an in-memory SDF XML tree, resolving its include tags and relative URIs
526+
#
527+
# @param [REXML::Document] sdf the XML tree
528+
# @param [Boolean] flatten flattens the XML model or not
529+
# @param [Boolean] metadata returns a metadata hash or not
530+
# @param [String,nil] path the file path or virtual path representing the SDF
531+
# @return [REXML::Element, [REXML::Element, Hash]]
532+
def self.resolve_sdf_xml(sdf, flatten: true, metadata: false, path: nil)
513533
sdf_version = sdf_version_of(sdf)
534+
base_path = path ? File.dirname(path) : nil
514535

515-
sdf_metadata = Hash["includes" => {}, "path" => sdf_file]
516-
includes = add_include_tags(sdf.root, sdf_version, File.dirname(sdf_file))
536+
sdf_metadata = Hash["includes" => {}, "path" => path]
537+
includes = add_include_tags(sdf.root, sdf_version, base_path)
517538
sdf_metadata["includes"].merge!(includes) do |_, old, new|
518539
old + new
519540
end
520-
resolve_relative_uris(sdf.root, sdf_version, File.dirname(sdf_file))
541+
resolve_relative_uris(sdf.root, sdf_version, base_path)
521542

522543
sdf = deep_copy_xml(sdf)
523544
flatten_model_tree(sdf.root) if flatten
@@ -527,6 +548,15 @@ def self.load_sdf(sdf_file, flatten: true, metadata: false)
527548
else
528549
sdf
529550
end
551+
end
552+
553+
# Loads a SDF file and returns the XML representation
554+
#
555+
# Unlike {.load_sdf_raw}, this resolves the include tags in the XML representation
556+
def self.load_sdf(sdf_file, flatten: true, metadata: false)
557+
sdf = load_sdf_raw(sdf_file)
558+
sdf = deep_copy_xml(sdf)
559+
resolve_sdf_xml(sdf, flatten: flatten, metadata: metadata, path: sdf_file)
530560
rescue Exception => e
531561
raise e, "while loading #{sdf_file}: #{e.message}", e.backtrace
532562
end

test/test_xml.rb

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ def sdf_model_in_model_that_replaces_pose_in_include
440440
SDF::XML.register_in_memory_model("virtual_model", doc)
441441

442442
assert SDF::XML.cached_model?("virtual_model")
443-
assert_equal doc, SDF::XML.model_from_name("virtual_model", flatten: false)
443+
assert_equal doc.to_s, SDF::XML.model_from_name("virtual_model", flatten: false).to_s
444444
end
445445

446446
it "allows registering a model as a REXML::Element" do
@@ -452,7 +452,7 @@ def sdf_model_in_model_that_replaces_pose_in_include
452452

453453
assert SDF::XML.cached_model?("virtual_el")
454454
loaded = SDF::XML.model_from_name("virtual_el", flatten: false)
455-
assert_equal element, loaded.root
455+
assert_equal element.to_s, loaded.root.to_s
456456
end
457457

458458
it "allows registering a model as a raw XML String" do
@@ -471,6 +471,38 @@ def sdf_model_in_model_that_replaces_pose_in_include
471471
SDF::XML.register_in_memory_model("invalid_model", 12_345)
472472
end
473473
end
474+
475+
it "falls back to the nil version cache if the requested version is not registered" do
476+
doc = REXML::Document.new("<model name='virtual_fallback'/>")
477+
# Register exclusively under nil (unversioned) cache by passing nil explicitly
478+
SDF::XML.register_in_memory_model("virtual_fallback", doc, sdf_version: nil)
479+
480+
# Requesting with specific version 160 should fall back and load successfully
481+
loaded = SDF::XML.model_from_name("virtual_fallback", 160, flatten: false)
482+
assert_equal "virtual_fallback", loaded.root.attributes["name"]
483+
end
484+
485+
it "resolves nested inclusions within in-memory models at registration time" do
486+
submodel_doc = REXML::Document.new("<sdf version='1.6'><model name='sub'><link name='sub_link'/></model></sdf>")
487+
SDF::XML.register_in_memory_model("submodel", submodel_doc)
488+
489+
# Register a parent model containing an include to the submodel
490+
parent_doc = REXML::Document.new(
491+
"<sdf version='1.6'>" \
492+
" <model name='parent'>" \
493+
" <include><uri>model://submodel</uri><name>included_sub</name></include>" \
494+
" </model>" \
495+
"</sdf>"
496+
)
497+
resolved_doc, metadata = SDF::XML.resolve_sdf_xml(parent_doc, flatten: false, metadata: true, path: "virtual://parent_model")
498+
SDF::XML.register_in_memory_model("parent_model", resolved_doc, metadata: metadata)
499+
500+
# Retrieve with flatten: true (which requires all inclusions to be resolved)
501+
loaded = SDF::XML.model_from_name("parent_model", flatten: true)
502+
503+
# Verify that the submodel's links are present in the flattened parent tree
504+
assert loaded.elements["//link[@name='included_sub::sub_link']"]
505+
end
474506
end
475507
it "raises if the model cannot be found" do
476508
exception = assert_raises(SDF::XML::NoSuchModel) do

0 commit comments

Comments
 (0)