Add support for global custom numeric fields.

PiperOrigin-RevId: 495865065
Change-Id: I59ed65e84a0bcaa90220b938ed34fdc26165aa09
This commit is contained in:
Tom Erez
2022-12-16 07:02:47 -08:00
committed by Copybara-Service
parent 516d13d678
commit f70403d00a
6 changed files with 90 additions and 93 deletions
+47 -26
View File
@@ -143,32 +143,18 @@ public struct MjcfOptionFlag {
[Serializable] [Serializable]
public struct MjSizeStruct { public struct MjSizeStruct {
public int Njmax; public String Memory;
public int Nconmax;
public int Nstack;
public static MjSizeStruct Default = new MjSizeStruct() { public static MjSizeStruct Default = new MjSizeStruct() {
Njmax = 0, Memory = "-1"
Nconmax = 0,
Nstack = 0
}; };
public void ParseMjcf(XmlElement mjcf) { public void ParseMjcf(XmlElement mjcf) {
var localDefault = MjSizeStruct.Default; var localDefault = MjSizeStruct.Default;
Njmax = (int)mjcf.GetFloatAttribute("njmax", localDefault.Njmax); Memory = mjcf.GetAttribute("memory", localDefault.Memory);
Nconmax = (int)mjcf.GetFloatAttribute("nconmax", localDefault.Nconmax);
Nstack = (int)mjcf.GetFloatAttribute("nstack", localDefault.Nstack);
} }
public XmlElement ToMjcf(XmlElement mjcf) { public XmlElement ToMjcf(XmlElement mjcf) {
if (Njmax > 0) { mjcf.SetAttribute("memory", $"{Memory}");
mjcf.SetAttribute("njmax", $"{Njmax}");
}
if (Nconmax > 0) {
mjcf.SetAttribute("nconmax", $"{Nconmax}");
}
if (Nstack > 0) {
mjcf.SetAttribute("nstack", $"{Nstack}");
}
return mjcf; return mjcf;
} }
} }
@@ -327,6 +313,13 @@ public struct MjOptionStruct {
} }
} }
[Serializable]
public class NumericEntry {
public String Name;
[Tooltip("Space-separated list of floats.")]
public String Data;
}
public class MjGlobalSettings : MonoBehaviour { public class MjGlobalSettings : MonoBehaviour {
[Tooltip("Filename for the generated scene XML.")] [Tooltip("Filename for the generated scene XML.")]
@@ -339,6 +332,8 @@ public class MjGlobalSettings : MonoBehaviour {
public MjSizeStruct GlobalSizes = MjSizeStruct.Default; public MjSizeStruct GlobalSizes = MjSizeStruct.Default;
public List<NumericEntry> CustomNumeric = new List<NumericEntry>() {};
public static MjGlobalSettings Instance { public static MjGlobalSettings Instance {
get { get {
if (_instance == null) { if (_instance == null) {
@@ -365,18 +360,44 @@ public class MjGlobalSettings : MonoBehaviour {
} }
} }
public void ParseOptionSizeMjcf(XmlElement option, XmlElement size) { public void ParseGlobalMjcfSections(XmlElement mujocoNode) {
if (option != null) {
GlobalOptions.ParseMjcf(option); var optionNode = mujocoNode.SelectSingleNode("option") as XmlElement;
var sizeNode = mujocoNode.SelectSingleNode("size") as XmlElement;
var customNode = mujocoNode.SelectSingleNode("custom") as XmlElement;
if (optionNode != null) {
GlobalOptions.ParseMjcf(optionNode);
} }
if (size != null) { if (sizeNode != null) {
GlobalSizes.ParseMjcf(size); GlobalSizes.ParseMjcf(sizeNode);
}
if (customNode != null) {
foreach (var childNode in customNode.ChildNodes) {
var child = childNode as XmlElement;
if (child.Name == "numeric") {
var numeric = new NumericEntry();
numeric.Name = child.GetAttribute("name");
numeric.Data = child.GetAttribute("data");
CustomNumeric.Add(numeric);
}
}
} }
} }
public void OptionSizeToMjcf(XmlElement option, XmlElement size) { public void GlobalsToMjcf(XmlElement mjcf) {
GlobalOptions.ToMjcf(option); var doc = mjcf.OwnerDocument;
GlobalSizes.ToMjcf(size); var optionMjcf = (XmlElement)mjcf.AppendChild(doc.CreateElement("option"));
GlobalOptions.ToMjcf(optionMjcf);
var sizeMjcf = (XmlElement)mjcf.AppendChild(doc.CreateElement("size"));
GlobalSizes.ToMjcf(sizeMjcf);
var customMjcf = (XmlElement)mjcf.AppendChild(doc.CreateElement("custom"));
foreach (var numeric in CustomNumeric) {
var numericMjcf = (XmlElement)customMjcf.AppendChild(doc.CreateElement("numeric"));
numericMjcf.SetAttribute("name", numeric.Name);
// TODO: add validation that data is a space-separated list of floating numbers?
numericMjcf.SetAttribute("data", numeric.Data);
}
} }
} }
} }
+17 -22
View File
@@ -133,30 +133,25 @@ public class MjcfImporter {
} }
} }
protected virtual void ParseRoot(GameObject parentObject, XmlElement parentNode) { protected virtual void ParseRoot(GameObject rootObject, XmlElement mujocoNode) {
// This makes no references nor being referred into, so it can be parsed whenever. var globalsObject = CreateGameObjectInParent("Global Settings", rootObject);
var optionNode = parentNode.SelectSingleNode("option") as XmlElement; var settingsComponent = globalsObject.AddComponent<MjGlobalSettings>();
var sizeNode = parentNode.SelectSingleNode("size") as XmlElement; settingsComponent.ParseGlobalMjcfSections(mujocoNode);
if (optionNode != null || sizeNode != null) {
var globalsObject = CreateGameObjectInParent("Global Settings", parentObject);
var settingsComponent = globalsObject.AddComponent<MjGlobalSettings>();
settingsComponent.ParseOptionSizeMjcf(optionNode, sizeNode);
}
// This makes references to assets. // This makes references to assets.
var worldBodyNode = parentNode.SelectSingleNode("worldbody") as XmlElement; var worldBodyNode = mujocoNode.SelectSingleNode("worldbody") as XmlElement;
ParseBodyChildren(parentObject, worldBodyNode); ParseBodyChildren(rootObject, worldBodyNode);
// This section references bodies, must be parsed after worldbody. // This section references bodies, must be parsed after worldbody.
var excludeNode = parentNode.SelectSingleNode("contact") as XmlElement; var excludeNode = mujocoNode.SelectSingleNode("contact") as XmlElement;
if (excludeNode != null) { if (excludeNode != null) {
var excludesParentObject = CreateGameObjectInParent("excludes", parentObject); var excludesParentObject = CreateGameObjectInParent("excludes", rootObject);
foreach (var child in excludeNode.OfType<XmlElement>()) { foreach (var child in excludeNode.OfType<XmlElement>()) {
if (child.Name != "exclude") { if (child.Name != "exclude") {
Debug.LogWarning( Debug.LogWarning(
$"Only 'exclude' is supported - {child.Name} isn't supported yet.", $"Only 'exclude' is supported - {child.Name} isn't supported yet.",
parentObject); rootObject);
} else { } else {
_modifiers.ApplyModifiersToElement(child); _modifiers.ApplyModifiersToElement(child);
CreateGameObjectWithUniqueName<MjExclude>(excludesParentObject, child); CreateGameObjectWithUniqueName<MjExclude>(excludesParentObject, child);
@@ -165,9 +160,9 @@ public class MjcfImporter {
} }
// This section references joints/sites/geoms, must be parsed after worldbody. // This section references joints/sites/geoms, must be parsed after worldbody.
var tendonNode = parentNode.SelectSingleNode("tendon") as XmlElement; var tendonNode = mujocoNode.SelectSingleNode("tendon") as XmlElement;
if (tendonNode != null) { if (tendonNode != null) {
var tendonsParentObject = CreateGameObjectInParent("tendons", parentObject); var tendonsParentObject = CreateGameObjectInParent("tendons", rootObject);
foreach (var child in tendonNode.OfType<XmlElement>()) { foreach (var child in tendonNode.OfType<XmlElement>()) {
if (child.Name == "fixed") { if (child.Name == "fixed") {
CreateGameObjectWithUniqueName<MjFixedTendon>(tendonsParentObject, child); CreateGameObjectWithUniqueName<MjFixedTendon>(tendonsParentObject, child);
@@ -180,9 +175,9 @@ public class MjcfImporter {
} }
// This section references worldbody elements + tendons, must be parsed after them. // This section references worldbody elements + tendons, must be parsed after them.
var equalityNode = parentNode.SelectSingleNode("equality") as XmlElement; var equalityNode = mujocoNode.SelectSingleNode("equality") as XmlElement;
if (equalityNode != null) { if (equalityNode != null) {
var equalitiesParentObject = CreateGameObjectInParent("equality constraints", parentObject); var equalitiesParentObject = CreateGameObjectInParent("equality constraints", rootObject);
foreach (var child in equalityNode.OfType<XmlElement>()) { foreach (var child in equalityNode.OfType<XmlElement>()) {
var equalityType = ParseEqualityType(child); var equalityType = ParseEqualityType(child);
_modifiers.ApplyModifiersToElement(child); _modifiers.ApplyModifiersToElement(child);
@@ -191,9 +186,9 @@ public class MjcfImporter {
} }
// This section references joints and tendons, must be parsed after worldbody and tendon. // This section references joints and tendons, must be parsed after worldbody and tendon.
var actuatorNode = parentNode.SelectSingleNode("actuator") as XmlElement; var actuatorNode = mujocoNode.SelectSingleNode("actuator") as XmlElement;
if (actuatorNode != null) { if (actuatorNode != null) {
var actuatorsParentObject = CreateGameObjectInParent("actuators", parentObject); var actuatorsParentObject = CreateGameObjectInParent("actuators", rootObject);
foreach (var child in actuatorNode.OfType<XmlElement>()) { foreach (var child in actuatorNode.OfType<XmlElement>()) {
_modifiers.ApplyModifiersToElement(child); _modifiers.ApplyModifiersToElement(child);
CreateGameObjectWithUniqueName<MjActuator>(actuatorsParentObject, child); CreateGameObjectWithUniqueName<MjActuator>(actuatorsParentObject, child);
@@ -201,9 +196,9 @@ public class MjcfImporter {
} }
// This section references tendons, actuators and worldbody elements, must be parsed last. // This section references tendons, actuators and worldbody elements, must be parsed last.
var sensorNode = parentNode.SelectSingleNode("sensor") as XmlElement; var sensorNode = mujocoNode.SelectSingleNode("sensor") as XmlElement;
if (sensorNode != null) { if (sensorNode != null) {
var sensorParentObject = CreateGameObjectInParent("sensors", parentObject); var sensorParentObject = CreateGameObjectInParent("sensors", rootObject);
foreach (var child in sensorNode.OfType<XmlElement>()) { foreach (var child in sensorNode.OfType<XmlElement>()) {
_modifiers.ApplyModifiersToElement(child); _modifiers.ApplyModifiersToElement(child);
var sensorType = ParseSensorType(child); var sensorType = ParseSensorType(child);
+1 -1
View File
@@ -74,7 +74,7 @@ public class MjcfGenerationContext {
var settings = MjGlobalSettings.Instance; var settings = MjGlobalSettings.Instance;
if (settings) { if (settings) {
settings.OptionSizeToMjcf(optionMjcf, null); settings.GlobalsToMjcf(mjcf);
} }
} }
@@ -26,16 +26,13 @@ public class MjGlobalSettingsGenerationTests {
private MjGlobalSettings _settings; private MjGlobalSettings _settings;
private XmlDocument _doc; private XmlDocument _doc;
private XmlElement _option; private XmlElement _root;
private XmlElement _size;
[SetUp] [SetUp]
public void SetUp() { public void SetUp() {
_settings = new GameObject("settings").AddComponent<MjGlobalSettings>(); _settings = new GameObject("settings").AddComponent<MjGlobalSettings>();
_doc = new XmlDocument(); _doc = new XmlDocument();
var root = (XmlElement)_doc.AppendChild(_doc.CreateElement("root")); _root = (XmlElement)_doc.AppendChild(_doc.CreateElement("root"));
_option = (XmlElement)root.AppendChild(_doc.CreateElement("option"));
_size = (XmlElement)root.AppendChild(_doc.CreateElement("size"));
} }
[TearDown] [TearDown]
@@ -61,9 +58,9 @@ public class MjGlobalSettingsGenerationTests {
_settings.GlobalOptions.NoSlipTolerance = 6.7f; _settings.GlobalOptions.NoSlipTolerance = 6.7f;
_settings.GlobalOptions.MprIterations = 8; _settings.GlobalOptions.MprIterations = 8;
_settings.GlobalOptions.MprTolerance = 0.9f; _settings.GlobalOptions.MprTolerance = 0.9f;
_settings.GlobalSizes.Nconmax = 321; _settings.GlobalSizes.Memory = "1M";
_settings.OptionSizeToMjcf(_option, _size); _settings.GlobalsToMjcf(_root);
Assert.That(_doc.OuterXml, Does.Contain(@"impratio=""1.2""")); Assert.That(_doc.OuterXml, Does.Contain(@"impratio=""1.2"""));
Assert.That(_doc.OuterXml, Does.Contain(@"magnetic=""3.4 4.5 5.6""")); Assert.That(_doc.OuterXml, Does.Contain(@"magnetic=""3.4 4.5 5.6"""));
Assert.That(_doc.OuterXml, Does.Contain(@"wind=""6.7 7.8 8.9""")); Assert.That(_doc.OuterXml, Does.Contain(@"wind=""6.7 7.8 8.9"""));
@@ -89,7 +86,7 @@ public class MjGlobalSettingsGenerationTests {
[Test] [Test]
public void GenerateFlag() { public void GenerateFlag() {
_settings.GlobalOptions.Flag.Gravity = EnableDisableFlag.disable; _settings.GlobalOptions.Flag.Gravity = EnableDisableFlag.disable;
_settings.OptionSizeToMjcf(_option, _size); _settings.GlobalsToMjcf(_root);
Assert.That(_doc.OuterXml, Does.Contain(@"gravity=""disable""")); Assert.That(_doc.OuterXml, Does.Contain(@"gravity=""disable"""));
Assert.That(_doc.OuterXml, Does.Contain(@"constraint=")); Assert.That(_doc.OuterXml, Does.Contain(@"constraint="));
Assert.That(_doc.OuterXml, Does.Contain(@"equality=")); Assert.That(_doc.OuterXml, Does.Contain(@"equality="));
@@ -113,18 +110,22 @@ public class MjGlobalSettingsGenerationTests {
public class MjGlobalSettingsParsingTests { public class MjGlobalSettingsParsingTests {
private MjGlobalSettings _settings; private MjGlobalSettings _settings;
private XmlElement _root;
private XmlElement _option; private XmlElement _option;
private XmlElement _size; private XmlElement _size;
private XmlElement _flag; private XmlElement _flag;
private XmlElement _numeric;
[SetUp] [SetUp]
public void SetUp() { public void SetUp() {
_settings = new GameObject("settings").AddComponent<MjGlobalSettings>(); _settings = new GameObject("settings").AddComponent<MjGlobalSettings>();
var doc = new XmlDocument(); var doc = new XmlDocument();
var root = (XmlElement)doc.AppendChild(doc.CreateElement("root")); _root = (XmlElement)doc.AppendChild(doc.CreateElement("root"));
_option = (XmlElement)root.AppendChild(doc.CreateElement("option")); _option = (XmlElement)_root.AppendChild(doc.CreateElement("option"));
_size = (XmlElement)root.AppendChild(doc.CreateElement("size")); _size = (XmlElement)_root.AppendChild(doc.CreateElement("size"));
_flag = (XmlElement)_option.AppendChild(doc.CreateElement("flag")); _flag = (XmlElement)_option.AppendChild(doc.CreateElement("flag"));
var custom = (XmlElement)_root.AppendChild(doc.CreateElement("custom"));
_numeric = (XmlElement)custom.AppendChild(doc.CreateElement("numeric"));
} }
[TearDown] [TearDown]
@@ -154,9 +155,12 @@ public class MjGlobalSettingsParsingTests {
_flag.SetAttribute("gravity", "disable"); _flag.SetAttribute("gravity", "disable");
_size.SetAttribute("nconmax", "432"); _size.SetAttribute("memory", "1M");
_settings.ParseOptionSizeMjcf(_option, _size); _numeric.SetAttribute("name", "numeric_name");
_numeric.SetAttribute("data", "1 2 3");
_settings.ParseGlobalMjcfSections(_root);
Assert.That(_settings.GlobalOptions.ImpRatio, Is.EqualTo(1.2f)); Assert.That(_settings.GlobalOptions.ImpRatio, Is.EqualTo(1.2f));
Assert.That(_settings.GlobalOptions.Magnetic, Is.EqualTo(new Vector3(3.4f, 4.5f, 5.6f))); Assert.That(_settings.GlobalOptions.Magnetic, Is.EqualTo(new Vector3(3.4f, 4.5f, 5.6f)));
Assert.That(_settings.GlobalOptions.Wind, Is.EqualTo(new Vector3(6.7f, 7.8f, 8.9f))); Assert.That(_settings.GlobalOptions.Wind, Is.EqualTo(new Vector3(6.7f, 7.8f, 8.9f)));
@@ -176,22 +180,11 @@ public class MjGlobalSettingsParsingTests {
Assert.That(_settings.GlobalOptions.Flag.Gravity, Is.EqualTo(EnableDisableFlag.disable)); Assert.That(_settings.GlobalOptions.Flag.Gravity, Is.EqualTo(EnableDisableFlag.disable));
Assert.That(_settings.GlobalSizes.Nconmax, Is.EqualTo(432)); Assert.That(_settings.GlobalSizes.Memory, Is.EqualTo("1M"));
// these statements shouldn't fail:
_settings.ParseOptionSizeMjcf(_option, null);
_settings.ParseOptionSizeMjcf(null, _size);
}
[Test] Assert.That(_settings.CustomNumeric.Count, Is.EqualTo(1));
public void AvoidMultipleFlagClauses() { Assert.That(_settings.CustomNumeric[0].Name, Is.EqualTo("numeric_name"));
var doubleFlagXML = @"<option> Assert.That(_settings.CustomNumeric[0].Data, Is.EqualTo("1 2 3"));
<flag/> <flag/>
</option>";
var doc = new XmlDocument();
doc.LoadXml(doubleFlagXML);
Assert.That(
() => { _settings.ParseOptionSizeMjcf(doc.DocumentElement, null); },
Throws.ArgumentException);
} }
} }
} }
@@ -101,18 +101,6 @@ public class MjcfGenerationContextTests {
Assert.That(element.OuterXml, Does.Contain("vertex=\"1 0 0 0 0 1 0 1 0 \"")); Assert.That(element.OuterXml, Does.Contain("vertex=\"1 0 0 0 0 1 0 1 0 \""));
} }
[Test]
public void SettingNUserSensorValuesKeepsTheHigherValue() {
var context = new MjcfGenerationContext();
context.NUserSensor = 5;
context.NUserSensor = 6;
context.NUserSensor = 1;
Assert.That(context.NUserSensor, Is.EqualTo(6));
var element = new XmlDocument().CreateElement("test");
context.GenerateMjcf(element);
Assert.That(element.OuterXml, Does.Contain("nuser_sensor=\"6\""));
}
#region Test setup. #region Test setup.
private MjBody _componentA; private MjBody _componentA;
@@ -247,27 +247,27 @@ public class MjcfImporterTests {
[Test] [Test]
public void ReadingSize() { public void ReadingSize() {
var mjcfString = @"<mujoco> var mjcfString = @"<mujoco>
<size njmax='1234'/> <size memory='1M'/>
<worldbody/> <worldbody/>
</mujoco>"; </mujoco>";
_sceneRoot = _importer.ImportString( _sceneRoot = _importer.ImportString(
name: string.Empty, mjcfString: mjcfString); name: string.Empty, mjcfString: mjcfString);
var settings = _sceneRoot.GetComponentInChildren<MjGlobalSettings>(); var settings = _sceneRoot.GetComponentInChildren<MjGlobalSettings>();
Assert.That(settings.GlobalSizes.Njmax, Is.EqualTo(1234)); Assert.That(settings.GlobalSizes.Memory, Is.EqualTo("1M"));
} }
[Test] [Test]
public void ReadingOptionAndSize() { public void ReadingOptionAndSize() {
var mjcfString = @"<mujoco> var mjcfString = @"<mujoco>
<option impratio='5.4321'/> <option impratio='5.4321'/>
<size njmax='4321'/> <size memory='16K'/>
<worldbody/> <worldbody/>
</mujoco>"; </mujoco>";
_sceneRoot = _importer.ImportString( _sceneRoot = _importer.ImportString(
name: string.Empty, mjcfString: mjcfString); name: string.Empty, mjcfString: mjcfString);
var settings = _sceneRoot.GetComponentInChildren<MjGlobalSettings>(); var settings = _sceneRoot.GetComponentInChildren<MjGlobalSettings>();
Assert.That(settings.GlobalOptions.ImpRatio, Is.EqualTo(5.4321f)); Assert.That(settings.GlobalOptions.ImpRatio, Is.EqualTo(5.4321f));
Assert.That(settings.GlobalSizes.Njmax, Is.EqualTo(4321)); Assert.That(settings.GlobalSizes.Memory, Is.EqualTo("16K"));
} }
[Test] [Test]