From cfd487c315c41beea617f4f6b8f5d412b0fb805a Mon Sep 17 00:00:00 2001 From: Joan Llobera Date: Wed, 8 Oct 2025 16:50:22 +0200 Subject: [PATCH 1/6] BUGFIX: when parsing a unity scene with mujoco element,s the mujoco package generates a value for the attribute "passive", but the attribute has been droped, it is not valid anymore in unity 3.3.6 this makes old mujoco scenes that work in unity not work anymore when upgrading the mujoco version. --- unity/Runtime/Components/MjGlobalSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unity/Runtime/Components/MjGlobalSettings.cs b/unity/Runtime/Components/MjGlobalSettings.cs index d41cefe5..b336d58a 100644 --- a/unity/Runtime/Components/MjGlobalSettings.cs +++ b/unity/Runtime/Components/MjGlobalSettings.cs @@ -121,7 +121,7 @@ public struct MjcfOptionFlag { mjcf.SetAttribute("frictionloss", FrictionLoss.ToString()); mjcf.SetAttribute("limit", Limit.ToString()); mjcf.SetAttribute("contact", Contact.ToString()); - mjcf.SetAttribute("passive", Passive.ToString()); + // mjcf.SetAttribute("passive", Passive.ToString()); mjcf.SetAttribute("gravity", Gravity.ToString()); mjcf.SetAttribute("clampctrl", ClampCtrl.ToString()); mjcf.SetAttribute("warmstart", WarmStart.ToString()); From 655b961c89bf1edd93b790449b6933a659a4a728 Mon Sep 17 00:00:00 2001 From: Joan Llobera Date: Mon, 13 Oct 2025 14:50:08 +0200 Subject: [PATCH 2/6] redid the global settings according to suggested guidelines --- unity/Runtime/Components/MjGlobalSettings.cs | 762 ++++++++++--------- 1 file changed, 402 insertions(+), 360 deletions(-) diff --git a/unity/Runtime/Components/MjGlobalSettings.cs b/unity/Runtime/Components/MjGlobalSettings.cs index b336d58a..161eaf0a 100644 --- a/unity/Runtime/Components/MjGlobalSettings.cs +++ b/unity/Runtime/Components/MjGlobalSettings.cs @@ -13,382 +13,424 @@ // limitations under the License. using System; -using System.Collections; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; using System.Xml; using UnityEngine; -using UnityEngine.Profiling; -namespace Mujoco { +namespace Mujoco +{ -// The MuJoCo parser is case-sensitive. The case format of the entries in the enums below is -// exactly what the documentation specifies: http://mujoco.org/book/XMLreference.html#option . -public enum IntegratorType { - Euler, - RK4, - @implicit, - @implicitfast -} - -public enum FrictionConeType { - pyramidal, - elliptic -} - -public enum JacobianType { - dense, - sparse, - auto -} - -public enum ConstraintSolverType { - PGS, - CG, - Newton -} - -public enum EnableDisableFlag { - enable, - disable -} - -[Serializable] -public struct MjcfOptionFlag { - public EnableDisableFlag Constraint; - public EnableDisableFlag Equality; - public EnableDisableFlag FrictionLoss; - public EnableDisableFlag Limit; - public EnableDisableFlag Contact; - public EnableDisableFlag Passive; - public EnableDisableFlag Gravity; - public EnableDisableFlag ClampCtrl; - public EnableDisableFlag WarmStart; - public EnableDisableFlag FilterParent; - public EnableDisableFlag Actuation; - public EnableDisableFlag RefSafe; - public EnableDisableFlag Override; - public EnableDisableFlag Energy; - public EnableDisableFlag FwdInv; - public EnableDisableFlag MultiCCD; - public static MjcfOptionFlag Default = new MjcfOptionFlag() { - Constraint = EnableDisableFlag.enable, - Equality = EnableDisableFlag.enable, - FrictionLoss = EnableDisableFlag.enable, - Limit = EnableDisableFlag.enable, - Contact = EnableDisableFlag.enable, - Passive = EnableDisableFlag.enable, - Gravity = EnableDisableFlag.enable, - ClampCtrl = EnableDisableFlag.enable, - WarmStart = EnableDisableFlag.enable, - FilterParent = EnableDisableFlag.enable, - Actuation = EnableDisableFlag.enable, - RefSafe = EnableDisableFlag.enable, - Override = EnableDisableFlag.disable, - Energy = EnableDisableFlag.disable, - FwdInv = EnableDisableFlag.disable, - MultiCCD = EnableDisableFlag.disable - }; - - public void FromMjcf(XmlElement mjcf) { - var localDefault = MjcfOptionFlag.Default; - Constraint = mjcf.GetEnumAttribute("constraint", localDefault.Constraint); - Equality = mjcf.GetEnumAttribute("equality", localDefault.Equality); - FrictionLoss = mjcf.GetEnumAttribute("frictionloss", - localDefault.FrictionLoss); - Limit = mjcf.GetEnumAttribute("limit", localDefault.Limit); - Contact = mjcf.GetEnumAttribute("contact", localDefault.Contact); - Passive = mjcf.GetEnumAttribute("passive", localDefault.Passive); - Gravity = mjcf.GetEnumAttribute("gravity", localDefault.Gravity); - ClampCtrl = mjcf.GetEnumAttribute("clampctrl", localDefault.ClampCtrl); - WarmStart = mjcf.GetEnumAttribute("warmstart", localDefault.WarmStart); - FilterParent = mjcf.GetEnumAttribute("filterparent", - localDefault.FilterParent); - Actuation = mjcf.GetEnumAttribute("actuation", localDefault.Actuation); - RefSafe = mjcf.GetEnumAttribute("refsafe", localDefault.RefSafe); - Override = mjcf.GetEnumAttribute("override", localDefault.Override); - Energy = mjcf.GetEnumAttribute("energy", localDefault.Energy); - FwdInv = mjcf.GetEnumAttribute("fwdinv", localDefault.FwdInv); - MultiCCD = mjcf.GetEnumAttribute("multiccd", localDefault.MultiCCD); - } - - public void ToMjcf(XmlElement mjcf) { - mjcf.SetAttribute("constraint", Constraint.ToString()); - mjcf.SetAttribute("equality", Equality.ToString()); - mjcf.SetAttribute("frictionloss", FrictionLoss.ToString()); - mjcf.SetAttribute("limit", Limit.ToString()); - mjcf.SetAttribute("contact", Contact.ToString()); - // mjcf.SetAttribute("passive", Passive.ToString()); - mjcf.SetAttribute("gravity", Gravity.ToString()); - mjcf.SetAttribute("clampctrl", ClampCtrl.ToString()); - mjcf.SetAttribute("warmstart", WarmStart.ToString()); - mjcf.SetAttribute("filterparent", FilterParent.ToString()); - mjcf.SetAttribute("actuation", Actuation.ToString()); - mjcf.SetAttribute("refsafe", RefSafe.ToString()); - mjcf.SetAttribute("override", Override.ToString()); - mjcf.SetAttribute("energy", Energy.ToString()); - mjcf.SetAttribute("fwdinv", FwdInv.ToString()); - mjcf.SetAttribute("multiccd", MultiCCD.ToString()); - } -} - -[Serializable] -public struct MjSizeStruct { - public String Memory; - public static MjSizeStruct Default = new MjSizeStruct() { - Memory = "-1" - }; - - public void ParseMjcf(XmlElement mjcf) { - Memory = mjcf.GetStringAttribute("memory", "-1"); - } - - public XmlElement ToMjcf(XmlElement mjcf) { - mjcf.SetAttribute("memory", $"{Memory}"); - return mjcf; - } -} - -[Serializable] -public struct MjOptionStruct { - - // "timestep" and "gravity" come from global settings. - // "apirate" is only relevant to HAPTIX. - [Tooltip("Ratio of frictional-to-normal constraint impedance.")] - public float ImpRatio; - [Tooltip("Global magnetic flux used by magnetometer sensors.")] - public Vector3 Magnetic; - [Tooltip("Velocity vector of the medium, scales with the values of Density and Viscosity below.")] - public Vector3 Wind; - [Tooltip("Density of the medium for lift and drag forces. 0 disables lift and drag forces.")] - public float Density; - [Tooltip("Viscosity of the medium for viscous forces. 0 disables viscous forces.")] - public float Viscosity; - [Tooltip("Contact pair margin used when ContactOverride flag is set.")] - public float OverrideMargin; - [Tooltip("Contact pair solref used when ContactOverride flag is set.")] - public SolverReference OverrideSolRef; - [Tooltip("Contact pair solimp used when ContactOverride flag is set.")] - public SolverImpedance OverrideSolImp; - [Tooltip("Numerical integrator.")] - public IntegratorType Integrator; - [Tooltip("How to model the friction cone.")] - public FrictionConeType Cone; - [Tooltip("How to represent the constraint Jacobian.")] - public JacobianType Jacobian; - [Tooltip("Constraint solver algorithm.")] - public ConstraintSolverType Solver; - [Tooltip("Maximum iterations for the constraint solver.")] - public int Iterations; - [Tooltip("Threshold used for early termination of the constraint solver.")] - public float Tolerance; - [Tooltip("Maximum iterations for the no-slip phase of the constraint solver.")] - public int NoSlipIterations; - [Tooltip("Threshold used for early termination of the Noslip solver.")] - public float NoSlipTolerance; - [Tooltip("Maximum iterations for convex mesh collisions.")] - public int CcdIterations; - [Tooltip("Threshold used for early termination of the MPR algorithm.")] - public float CcdTolerance; - - public MjcfOptionFlag Flag; - - // Default values copied from the "XML reference" page in the MuJoCo documentation. - public static MjOptionStruct Default = new MjOptionStruct() { - ImpRatio = 1.0f, - Magnetic = Vector3.zero, - Wind = new Vector3(0.0f, 0.0f, 0.0f), - Density = 0.0f, - Viscosity = 0.0f, - OverrideMargin = 0.0f, - OverrideSolRef = SolverReference.Default, - OverrideSolImp = SolverImpedance.Default, - Integrator = IntegratorType.Euler, - Cone = FrictionConeType.pyramidal, - Jacobian = JacobianType.auto, - Solver = ConstraintSolverType.Newton, - Iterations = 100, - Tolerance = 1e-8f, - NoSlipIterations = 0, - NoSlipTolerance = 1e-6f, - CcdIterations = 50, - CcdTolerance = 1e-6f, - Flag = MjcfOptionFlag.Default - }; - - public void ParseMjcf(XmlElement mjcf) { - if (mjcf.HasAttribute("timestep")) { - var mjTimestep = mjcf.GetFloatAttribute("timestep", Time.fixedDeltaTime); - if (mjTimestep != Time.fixedDeltaTime) { - Debug.unityLogger.LogWarning( - "MuJoCo", - $"MuJoCo's timestep ({mjTimestep}s) ignored, please set fixedDeltaTime manually.", - null); - } + // The MuJoCo parser is case-sensitive. The case format of the entries in the enums below is + // exactly what the documentation specifies: http://mujoco.org/book/XMLreference.html#option . + public enum IntegratorType + { + Euler, + RK4, + @implicit, + @implicitfast } - if (mjcf.HasAttribute("gravity")) { - var mjGravity = mjcf.GetVector3Attribute("gravity", Vector3.zero); - Debug.unityLogger.LogWarning( - "MuJoCo", - $"MuJoCo's gravity ({mjGravity.x} {mjGravity.z} {mjGravity.y}) ignored, " + - "please set gravity in the Physics Manager.", - null); + + public enum FrictionConeType + { + pyramidal, + elliptic } - var localDefault = MjOptionStruct.Default; - ImpRatio = mjcf.GetFloatAttribute("impratio", localDefault.ImpRatio); - Magnetic = mjcf.GetVector3Attribute("magnetic", localDefault.Magnetic); - Wind = mjcf.GetVector3Attribute("wind", localDefault.Wind); - Density = mjcf.GetFloatAttribute("density", localDefault.Density); - Viscosity = mjcf.GetFloatAttribute("viscosity", localDefault.Viscosity); - OverrideMargin = mjcf.GetFloatAttribute("o_margin", localDefault.OverrideMargin); - OverrideSolRef.FromMjcf(mjcf, "o_solref"); - OverrideSolImp.FromMjcf(mjcf, "o_solimp"); - - Integrator = mjcf.GetEnumAttribute("integrator", localDefault.Integrator); - Cone = mjcf.GetEnumAttribute("cone", localDefault.Cone); - Jacobian = mjcf.GetEnumAttribute("jacobian", localDefault.Jacobian); - Solver = mjcf.GetEnumAttribute("solver", localDefault.Solver); - - Iterations = (int)mjcf.GetFloatAttribute("iterations", localDefault.Iterations); - Tolerance = mjcf.GetFloatAttribute("tolerance", localDefault.Tolerance); - NoSlipIterations = (int)mjcf.GetFloatAttribute( - "noslip_iterations", localDefault.NoSlipIterations); - NoSlipTolerance = mjcf.GetFloatAttribute("noslip_tolerance", localDefault.NoSlipTolerance); - CcdIterations = (int)mjcf.GetFloatAttribute("ccd_iterations", localDefault.CcdIterations); - CcdTolerance = mjcf.GetFloatAttribute("ccd_tolerance", localDefault.CcdTolerance); - - var flagElements = mjcf.GetElementsByTagName("flag"); - if (flagElements.Count == 1) { - Flag.FromMjcf(flagElements[0] as XmlElement); - } else if (flagElements.Count > 1) { - throw new ArgumentException("More than one node is not supported."); + public enum JacobianType + { + dense, + sparse, + auto } - } - public XmlElement ToMjcf(XmlElement mjcf) { - mjcf.SetAttribute("impratio", MjEngineTool.MakeLocaleInvariant($"{ImpRatio}")); + public enum ConstraintSolverType + { + PGS, + CG, + Newton + } - mjcf.SetAttribute("magnetic", MjEngineTool.MakeLocaleInvariant($"{Magnetic.x} {Magnetic.y} {Magnetic.z}")); - mjcf.SetAttribute("wind", MjEngineTool.MakeLocaleInvariant($"{Wind.x} {Wind.y} {Wind.z}")); + public enum EnableDisableFlag + { + enable, + disable + } - mjcf.SetAttribute("density", MjEngineTool.MakeLocaleInvariant($"{Density}")); - mjcf.SetAttribute("viscosity", MjEngineTool.MakeLocaleInvariant($"{Viscosity}")); - mjcf.SetAttribute("o_margin", MjEngineTool.MakeLocaleInvariant($"{OverrideMargin}")); + [Serializable] + public struct MjcfOptionFlag + { + public EnableDisableFlag Constraint; + public EnableDisableFlag Equality; + public EnableDisableFlag FrictionLoss; + public EnableDisableFlag Limit; + public EnableDisableFlag Contact; + public EnableDisableFlag Spring; + public EnableDisableFlag Damper; + public EnableDisableFlag Gravity; + public EnableDisableFlag ClampCtrl; + public EnableDisableFlag WarmStart; + public EnableDisableFlag FilterParent; + public EnableDisableFlag Actuation; + public EnableDisableFlag RefSafe; + public EnableDisableFlag Override; + public EnableDisableFlag Energy; + public EnableDisableFlag FwdInv; + public EnableDisableFlag MultiCCD; + public static MjcfOptionFlag Default = new MjcfOptionFlag() + { + Constraint = EnableDisableFlag.enable, + Equality = EnableDisableFlag.enable, + FrictionLoss = EnableDisableFlag.enable, + Limit = EnableDisableFlag.enable, + Contact = EnableDisableFlag.enable, + Spring = EnableDisableFlag.enable, + Damper = EnableDisableFlag.enable, + Gravity = EnableDisableFlag.enable, + ClampCtrl = EnableDisableFlag.enable, + WarmStart = EnableDisableFlag.enable, + FilterParent = EnableDisableFlag.enable, + Actuation = EnableDisableFlag.enable, + RefSafe = EnableDisableFlag.enable, + Override = EnableDisableFlag.disable, + Energy = EnableDisableFlag.disable, + FwdInv = EnableDisableFlag.disable, + MultiCCD = EnableDisableFlag.disable + }; - OverrideSolRef.ToMjcf(mjcf, "o_solref"); - OverrideSolImp.ToMjcf(mjcf, "o_solimp"); - - mjcf.SetAttribute("integrator", Integrator.ToString()); - mjcf.SetAttribute("cone", Cone.ToString()); - mjcf.SetAttribute("jacobian", Jacobian.ToString()); - mjcf.SetAttribute("solver", Solver.ToString()); - - mjcf.SetAttribute("iterations", MjEngineTool.MakeLocaleInvariant($"{Iterations}")); - mjcf.SetAttribute("tolerance", MjEngineTool.MakeLocaleInvariant($"{Tolerance}")); - mjcf.SetAttribute("noslip_iterations", MjEngineTool.MakeLocaleInvariant($"{NoSlipIterations}")); - mjcf.SetAttribute("noslip_tolerance", MjEngineTool.MakeLocaleInvariant($"{NoSlipTolerance}")); - mjcf.SetAttribute("ccd_iterations", MjEngineTool.MakeLocaleInvariant($"{CcdIterations}")); - mjcf.SetAttribute("ccd_tolerance", MjEngineTool.MakeLocaleInvariant($"{CcdTolerance}")); - - var flags = (XmlElement)mjcf.AppendChild( - mjcf.OwnerDocument.CreateElement("flag")); - Flag.ToMjcf(flags); - return mjcf; - } -} - -[Serializable] -public class NumericEntry { - public String Name; - [Tooltip("Space-separated list of floats.")] - public String Data; -} - -public class MjGlobalSettings : MonoBehaviour { - - [Tooltip("Filename for the generated scene XML.")] - public string DebugFileName; - - [Tooltip("Scales the force applied by the mouse spring.")] - public float MouseSpringStiffness = 100; - - [Tooltip("If false, numerical suffixes will be aded to ensure name uniqueness.")] - public bool UseRawGameObjectNames; - - public MjOptionStruct GlobalOptions = MjOptionStruct.Default; - - public MjSizeStruct GlobalSizes = MjSizeStruct.Default; - - public List CustomNumeric = new List() {}; - - public static MjGlobalSettings Instance { - get { - if (_instance == null) { - var instances = FindObjectsOfType(); - if (instances.Length > 1) { - throw new InvalidOperationException( - "Only one MjGlobalSettings instance is allowed - please resolve manually."); - } else if (instances.Length == 1) { - _instance = instances[0]; + public void FromMjcf(XmlElement mjcf) + { + var localDefault = MjcfOptionFlag.Default; + Constraint = mjcf.GetEnumAttribute("constraint", localDefault.Constraint); + Equality = mjcf.GetEnumAttribute("equality", localDefault.Equality); + FrictionLoss = mjcf.GetEnumAttribute("frictionloss", + localDefault.FrictionLoss); + Limit = mjcf.GetEnumAttribute("limit", localDefault.Limit); + Contact = mjcf.GetEnumAttribute("contact", localDefault.Contact); + Spring = mjcf.GetEnumAttribute("spring", localDefault.Spring); + Damper = mjcf.GetEnumAttribute("damper", localDefault.Damper); + Gravity = mjcf.GetEnumAttribute("gravity", localDefault.Gravity); + ClampCtrl = mjcf.GetEnumAttribute("clampctrl", localDefault.ClampCtrl); + WarmStart = mjcf.GetEnumAttribute("warmstart", localDefault.WarmStart); + FilterParent = mjcf.GetEnumAttribute("filterparent", + localDefault.FilterParent); + Actuation = mjcf.GetEnumAttribute("actuation", localDefault.Actuation); + RefSafe = mjcf.GetEnumAttribute("refsafe", localDefault.RefSafe); + Override = mjcf.GetEnumAttribute("override", localDefault.Override); + Energy = mjcf.GetEnumAttribute("energy", localDefault.Energy); + FwdInv = mjcf.GetEnumAttribute("fwdinv", localDefault.FwdInv); + MultiCCD = mjcf.GetEnumAttribute("multiccd", localDefault.MultiCCD); } - } - return _instance; - } - } - private static MjGlobalSettings _instance = null; - - public void Awake() { - if (_instance == null) { - _instance = this; - } else if (_instance != this) { - throw new InvalidOperationException( - "At most one MjGlobalSettings should be present."); - } - } - - public void ParseGlobalMjcfSections(XmlElement mujocoNode) { - - 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 (sizeNode != null) { - 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 ToMjcf(XmlElement mjcf) + { + mjcf.SetAttribute("constraint", Constraint.ToString()); + mjcf.SetAttribute("equality", Equality.ToString()); + mjcf.SetAttribute("frictionloss", FrictionLoss.ToString()); + mjcf.SetAttribute("limit", Limit.ToString()); + mjcf.SetAttribute("contact", Contact.ToString()); + mjcf.SetAttribute("spring", Spring.ToString()); + mjcf.SetAttribute("damper", Damper.ToString()); + mjcf.SetAttribute("gravity", Gravity.ToString()); + mjcf.SetAttribute("clampctrl", ClampCtrl.ToString()); + mjcf.SetAttribute("warmstart", WarmStart.ToString()); + mjcf.SetAttribute("filterparent", FilterParent.ToString()); + mjcf.SetAttribute("actuation", Actuation.ToString()); + mjcf.SetAttribute("refsafe", RefSafe.ToString()); + mjcf.SetAttribute("override", Override.ToString()); + mjcf.SetAttribute("energy", Energy.ToString()); + mjcf.SetAttribute("fwdinv", FwdInv.ToString()); + mjcf.SetAttribute("multiccd", MultiCCD.ToString()); } - } } - } - public void GlobalsToMjcf(XmlElement mjcf) { - var doc = mjcf.OwnerDocument; - 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); + [Serializable] + public struct MjSizeStruct + { + public String Memory; + public static MjSizeStruct Default = new MjSizeStruct() + { + Memory = "-1" + }; + + public void ParseMjcf(XmlElement mjcf) + { + Memory = mjcf.GetStringAttribute("memory", "-1"); + } + + public XmlElement ToMjcf(XmlElement mjcf) + { + mjcf.SetAttribute("memory", $"{Memory}"); + return mjcf; + } } - } -} -} + + [Serializable] + public struct MjOptionStruct + { + + // "timestep" and "gravity" come from global settings. + [Tooltip("Ratio of frictional-to-normal constraint impedance.")] + public float ImpRatio; + [Tooltip("Global magnetic flux used by magnetometer sensors.")] + public Vector3 Magnetic; + [Tooltip("Velocity vector of the medium, scales with the values of Density and Viscosity below.")] + public Vector3 Wind; + [Tooltip("Density of the medium for lift and drag forces. 0 disables lift and drag forces.")] + public float Density; + [Tooltip("Viscosity of the medium for viscous forces. 0 disables viscous forces.")] + public float Viscosity; + [Tooltip("Contact pair margin used when ContactOverride flag is set.")] + public float OverrideMargin; + [Tooltip("Contact pair solref used when ContactOverride flag is set.")] + public SolverReference OverrideSolRef; + [Tooltip("Contact pair solimp used when ContactOverride flag is set.")] + public SolverImpedance OverrideSolImp; + [Tooltip("Numerical integrator.")] + public IntegratorType Integrator; + [Tooltip("How to model the friction cone.")] + public FrictionConeType Cone; + [Tooltip("How to represent the constraint Jacobian.")] + public JacobianType Jacobian; + [Tooltip("Constraint solver algorithm.")] + public ConstraintSolverType Solver; + [Tooltip("Maximum iterations for the constraint solver.")] + public int Iterations; + [Tooltip("Threshold used for early termination of the constraint solver.")] + public float Tolerance; + [Tooltip("Maximum iterations for the no-slip phase of the constraint solver.")] + public int NoSlipIterations; + [Tooltip("Threshold used for early termination of the Noslip solver.")] + public float NoSlipTolerance; + [Tooltip("Maximum iterations for convex mesh collisions.")] + public int CcdIterations; + [Tooltip("Threshold used for early termination of the MPR algorithm.")] + public float CcdTolerance; + + public MjcfOptionFlag Flag; + + // Default values copied from the "XML reference" page in the MuJoCo documentation. + public static MjOptionStruct Default = new MjOptionStruct() + { + ImpRatio = 1.0f, + Magnetic = Vector3.zero, + Wind = new Vector3(0.0f, 0.0f, 0.0f), + Density = 0.0f, + Viscosity = 0.0f, + OverrideMargin = 0.0f, + OverrideSolRef = SolverReference.Default, + OverrideSolImp = SolverImpedance.Default, + Integrator = IntegratorType.Euler, + Cone = FrictionConeType.pyramidal, + Jacobian = JacobianType.auto, + Solver = ConstraintSolverType.Newton, + Iterations = 100, + Tolerance = 1e-8f, + NoSlipIterations = 0, + NoSlipTolerance = 1e-6f, + CcdIterations = 50, + CcdTolerance = 1e-6f, + Flag = MjcfOptionFlag.Default + }; + + public void ParseMjcf(XmlElement mjcf) + { + if (mjcf.HasAttribute("timestep")) + { + var mjTimestep = mjcf.GetFloatAttribute("timestep", Time.fixedDeltaTime); + if (mjTimestep != Time.fixedDeltaTime) + { + Debug.unityLogger.LogWarning( + "MuJoCo", + $"MuJoCo's timestep ({mjTimestep}s) ignored, please set fixedDeltaTime manually.", + null); + } + } + if (mjcf.HasAttribute("gravity")) + { + var mjGravity = mjcf.GetVector3Attribute("gravity", Vector3.zero); + Debug.unityLogger.LogWarning( + "MuJoCo", + $"MuJoCo's gravity ({mjGravity.x} {mjGravity.z} {mjGravity.y}) ignored, " + + "please set gravity in the Physics Manager.", + null); + } + var localDefault = MjOptionStruct.Default; + ImpRatio = mjcf.GetFloatAttribute("impratio", localDefault.ImpRatio); + Magnetic = mjcf.GetVector3Attribute("magnetic", localDefault.Magnetic); + Wind = mjcf.GetVector3Attribute("wind", localDefault.Wind); + Density = mjcf.GetFloatAttribute("density", localDefault.Density); + Viscosity = mjcf.GetFloatAttribute("viscosity", localDefault.Viscosity); + OverrideMargin = mjcf.GetFloatAttribute("o_margin", localDefault.OverrideMargin); + + OverrideSolRef.FromMjcf(mjcf, "o_solref"); + OverrideSolImp.FromMjcf(mjcf, "o_solimp"); + + Integrator = mjcf.GetEnumAttribute("integrator", localDefault.Integrator); + Cone = mjcf.GetEnumAttribute("cone", localDefault.Cone); + Jacobian = mjcf.GetEnumAttribute("jacobian", localDefault.Jacobian); + Solver = mjcf.GetEnumAttribute("solver", localDefault.Solver); + + Iterations = (int)mjcf.GetFloatAttribute("iterations", localDefault.Iterations); + Tolerance = mjcf.GetFloatAttribute("tolerance", localDefault.Tolerance); + NoSlipIterations = (int)mjcf.GetFloatAttribute( + "noslip_iterations", localDefault.NoSlipIterations); + NoSlipTolerance = mjcf.GetFloatAttribute("noslip_tolerance", localDefault.NoSlipTolerance); + CcdIterations = (int)mjcf.GetFloatAttribute("ccd_iterations", localDefault.CcdIterations); + CcdTolerance = mjcf.GetFloatAttribute("ccd_tolerance", localDefault.CcdTolerance); + + var flagElements = mjcf.GetElementsByTagName("flag"); + if (flagElements.Count == 1) + { + Flag.FromMjcf(flagElements[0] as XmlElement); + } + else if (flagElements.Count > 1) + { + throw new ArgumentException("More than one node is not supported."); + } + } + + public XmlElement ToMjcf(XmlElement mjcf) + { + mjcf.SetAttribute("impratio", MjEngineTool.MakeLocaleInvariant($"{ImpRatio}")); + + mjcf.SetAttribute("magnetic", + MjEngineTool.MakeLocaleInvariant($"{Magnetic.x} {Magnetic.y} {Magnetic.z}")); + mjcf.SetAttribute("wind", MjEngineTool.MakeLocaleInvariant($"{Wind.x} {Wind.y} {Wind.z}")); + + mjcf.SetAttribute("density", MjEngineTool.MakeLocaleInvariant($"{Density}")); + mjcf.SetAttribute("viscosity", MjEngineTool.MakeLocaleInvariant($"{Viscosity}")); + mjcf.SetAttribute("o_margin", MjEngineTool.MakeLocaleInvariant($"{OverrideMargin}")); + + OverrideSolRef.ToMjcf(mjcf, "o_solref"); + OverrideSolImp.ToMjcf(mjcf, "o_solimp"); + + mjcf.SetAttribute("integrator", Integrator.ToString()); + mjcf.SetAttribute("cone", Cone.ToString()); + mjcf.SetAttribute("jacobian", Jacobian.ToString()); + mjcf.SetAttribute("solver", Solver.ToString()); + + mjcf.SetAttribute("iterations", MjEngineTool.MakeLocaleInvariant($"{Iterations}")); + mjcf.SetAttribute("tolerance", MjEngineTool.MakeLocaleInvariant($"{Tolerance}")); + mjcf.SetAttribute("noslip_iterations", MjEngineTool.MakeLocaleInvariant($"{NoSlipIterations}")); + mjcf.SetAttribute("noslip_tolerance", MjEngineTool.MakeLocaleInvariant($"{NoSlipTolerance}")); + mjcf.SetAttribute("ccd_iterations", MjEngineTool.MakeLocaleInvariant($"{CcdIterations}")); + mjcf.SetAttribute("ccd_tolerance", MjEngineTool.MakeLocaleInvariant($"{CcdTolerance}")); + + var flags = (XmlElement)mjcf.AppendChild( + mjcf.OwnerDocument.CreateElement("flag")); + Flag.ToMjcf(flags); + return mjcf; + } + } + + [Serializable] + public class NumericEntry + { + public String Name; + [Tooltip("Space-separated list of floats.")] + public String Data; + } + + public class MjGlobalSettings : MonoBehaviour + { + + [Tooltip("Filename for the generated scene XML.")] + public string DebugFileName; + + [Tooltip("Scales the force applied by the mouse spring.")] + public float MouseSpringStiffness = 100; + + [Tooltip("If false, numerical suffixes will be aded to ensure name uniqueness.")] + public bool UseRawGameObjectNames; + + public MjOptionStruct GlobalOptions = MjOptionStruct.Default; + + public MjSizeStruct GlobalSizes = MjSizeStruct.Default; + + public List CustomNumeric = new List() { }; + + public static MjGlobalSettings Instance + { + get + { + if (_instance == null) + { + var instances = FindObjectsOfType(); + if (instances.Length > 1) + { + throw new InvalidOperationException( + "Only one MjGlobalSettings instance is allowed - please resolve manually."); + } + else if (instances.Length == 1) + { + _instance = instances[0]; + } + } + return _instance; + } + } + + private static MjGlobalSettings _instance = null; + + public void Awake() + { + if (_instance == null) + { + _instance = this; + } + else if (_instance != this) + { + throw new InvalidOperationException( + "At most one MjGlobalSettings should be present."); + } + } + + public void ParseGlobalMjcfSections(XmlElement mujocoNode) + { + + 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 (sizeNode != null) + { + 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 GlobalsToMjcf(XmlElement mjcf) + { + var doc = mjcf.OwnerDocument; + 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); + } + } + } +} \ No newline at end of file From 0001fe221cc42aa2f71f089e3bbed22c90e4adbd Mon Sep 17 00:00:00 2001 From: Joan Llobera Date: Fri, 17 Oct 2025 09:42:40 +0200 Subject: [PATCH 3/6] Revert "redid the global settings according to suggested guidelines" This reverts commit 655b961c89bf1edd93b790449b6933a659a4a728. --- unity/Runtime/Components/MjGlobalSettings.cs | 756 +++++++++---------- 1 file changed, 357 insertions(+), 399 deletions(-) diff --git a/unity/Runtime/Components/MjGlobalSettings.cs b/unity/Runtime/Components/MjGlobalSettings.cs index 161eaf0a..b336d58a 100644 --- a/unity/Runtime/Components/MjGlobalSettings.cs +++ b/unity/Runtime/Components/MjGlobalSettings.cs @@ -13,424 +13,382 @@ // limitations under the License. using System; +using System.Collections; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; using System.Xml; using UnityEngine; +using UnityEngine.Profiling; -namespace Mujoco -{ +namespace Mujoco { - // The MuJoCo parser is case-sensitive. The case format of the entries in the enums below is - // exactly what the documentation specifies: http://mujoco.org/book/XMLreference.html#option . - public enum IntegratorType - { - Euler, - RK4, - @implicit, - @implicitfast +// The MuJoCo parser is case-sensitive. The case format of the entries in the enums below is +// exactly what the documentation specifies: http://mujoco.org/book/XMLreference.html#option . +public enum IntegratorType { + Euler, + RK4, + @implicit, + @implicitfast +} + +public enum FrictionConeType { + pyramidal, + elliptic +} + +public enum JacobianType { + dense, + sparse, + auto +} + +public enum ConstraintSolverType { + PGS, + CG, + Newton +} + +public enum EnableDisableFlag { + enable, + disable +} + +[Serializable] +public struct MjcfOptionFlag { + public EnableDisableFlag Constraint; + public EnableDisableFlag Equality; + public EnableDisableFlag FrictionLoss; + public EnableDisableFlag Limit; + public EnableDisableFlag Contact; + public EnableDisableFlag Passive; + public EnableDisableFlag Gravity; + public EnableDisableFlag ClampCtrl; + public EnableDisableFlag WarmStart; + public EnableDisableFlag FilterParent; + public EnableDisableFlag Actuation; + public EnableDisableFlag RefSafe; + public EnableDisableFlag Override; + public EnableDisableFlag Energy; + public EnableDisableFlag FwdInv; + public EnableDisableFlag MultiCCD; + public static MjcfOptionFlag Default = new MjcfOptionFlag() { + Constraint = EnableDisableFlag.enable, + Equality = EnableDisableFlag.enable, + FrictionLoss = EnableDisableFlag.enable, + Limit = EnableDisableFlag.enable, + Contact = EnableDisableFlag.enable, + Passive = EnableDisableFlag.enable, + Gravity = EnableDisableFlag.enable, + ClampCtrl = EnableDisableFlag.enable, + WarmStart = EnableDisableFlag.enable, + FilterParent = EnableDisableFlag.enable, + Actuation = EnableDisableFlag.enable, + RefSafe = EnableDisableFlag.enable, + Override = EnableDisableFlag.disable, + Energy = EnableDisableFlag.disable, + FwdInv = EnableDisableFlag.disable, + MultiCCD = EnableDisableFlag.disable + }; + + public void FromMjcf(XmlElement mjcf) { + var localDefault = MjcfOptionFlag.Default; + Constraint = mjcf.GetEnumAttribute("constraint", localDefault.Constraint); + Equality = mjcf.GetEnumAttribute("equality", localDefault.Equality); + FrictionLoss = mjcf.GetEnumAttribute("frictionloss", + localDefault.FrictionLoss); + Limit = mjcf.GetEnumAttribute("limit", localDefault.Limit); + Contact = mjcf.GetEnumAttribute("contact", localDefault.Contact); + Passive = mjcf.GetEnumAttribute("passive", localDefault.Passive); + Gravity = mjcf.GetEnumAttribute("gravity", localDefault.Gravity); + ClampCtrl = mjcf.GetEnumAttribute("clampctrl", localDefault.ClampCtrl); + WarmStart = mjcf.GetEnumAttribute("warmstart", localDefault.WarmStart); + FilterParent = mjcf.GetEnumAttribute("filterparent", + localDefault.FilterParent); + Actuation = mjcf.GetEnumAttribute("actuation", localDefault.Actuation); + RefSafe = mjcf.GetEnumAttribute("refsafe", localDefault.RefSafe); + Override = mjcf.GetEnumAttribute("override", localDefault.Override); + Energy = mjcf.GetEnumAttribute("energy", localDefault.Energy); + FwdInv = mjcf.GetEnumAttribute("fwdinv", localDefault.FwdInv); + MultiCCD = mjcf.GetEnumAttribute("multiccd", localDefault.MultiCCD); + } + + public void ToMjcf(XmlElement mjcf) { + mjcf.SetAttribute("constraint", Constraint.ToString()); + mjcf.SetAttribute("equality", Equality.ToString()); + mjcf.SetAttribute("frictionloss", FrictionLoss.ToString()); + mjcf.SetAttribute("limit", Limit.ToString()); + mjcf.SetAttribute("contact", Contact.ToString()); + // mjcf.SetAttribute("passive", Passive.ToString()); + mjcf.SetAttribute("gravity", Gravity.ToString()); + mjcf.SetAttribute("clampctrl", ClampCtrl.ToString()); + mjcf.SetAttribute("warmstart", WarmStart.ToString()); + mjcf.SetAttribute("filterparent", FilterParent.ToString()); + mjcf.SetAttribute("actuation", Actuation.ToString()); + mjcf.SetAttribute("refsafe", RefSafe.ToString()); + mjcf.SetAttribute("override", Override.ToString()); + mjcf.SetAttribute("energy", Energy.ToString()); + mjcf.SetAttribute("fwdinv", FwdInv.ToString()); + mjcf.SetAttribute("multiccd", MultiCCD.ToString()); + } +} + +[Serializable] +public struct MjSizeStruct { + public String Memory; + public static MjSizeStruct Default = new MjSizeStruct() { + Memory = "-1" + }; + + public void ParseMjcf(XmlElement mjcf) { + Memory = mjcf.GetStringAttribute("memory", "-1"); + } + + public XmlElement ToMjcf(XmlElement mjcf) { + mjcf.SetAttribute("memory", $"{Memory}"); + return mjcf; + } +} + +[Serializable] +public struct MjOptionStruct { + + // "timestep" and "gravity" come from global settings. + // "apirate" is only relevant to HAPTIX. + [Tooltip("Ratio of frictional-to-normal constraint impedance.")] + public float ImpRatio; + [Tooltip("Global magnetic flux used by magnetometer sensors.")] + public Vector3 Magnetic; + [Tooltip("Velocity vector of the medium, scales with the values of Density and Viscosity below.")] + public Vector3 Wind; + [Tooltip("Density of the medium for lift and drag forces. 0 disables lift and drag forces.")] + public float Density; + [Tooltip("Viscosity of the medium for viscous forces. 0 disables viscous forces.")] + public float Viscosity; + [Tooltip("Contact pair margin used when ContactOverride flag is set.")] + public float OverrideMargin; + [Tooltip("Contact pair solref used when ContactOverride flag is set.")] + public SolverReference OverrideSolRef; + [Tooltip("Contact pair solimp used when ContactOverride flag is set.")] + public SolverImpedance OverrideSolImp; + [Tooltip("Numerical integrator.")] + public IntegratorType Integrator; + [Tooltip("How to model the friction cone.")] + public FrictionConeType Cone; + [Tooltip("How to represent the constraint Jacobian.")] + public JacobianType Jacobian; + [Tooltip("Constraint solver algorithm.")] + public ConstraintSolverType Solver; + [Tooltip("Maximum iterations for the constraint solver.")] + public int Iterations; + [Tooltip("Threshold used for early termination of the constraint solver.")] + public float Tolerance; + [Tooltip("Maximum iterations for the no-slip phase of the constraint solver.")] + public int NoSlipIterations; + [Tooltip("Threshold used for early termination of the Noslip solver.")] + public float NoSlipTolerance; + [Tooltip("Maximum iterations for convex mesh collisions.")] + public int CcdIterations; + [Tooltip("Threshold used for early termination of the MPR algorithm.")] + public float CcdTolerance; + + public MjcfOptionFlag Flag; + + // Default values copied from the "XML reference" page in the MuJoCo documentation. + public static MjOptionStruct Default = new MjOptionStruct() { + ImpRatio = 1.0f, + Magnetic = Vector3.zero, + Wind = new Vector3(0.0f, 0.0f, 0.0f), + Density = 0.0f, + Viscosity = 0.0f, + OverrideMargin = 0.0f, + OverrideSolRef = SolverReference.Default, + OverrideSolImp = SolverImpedance.Default, + Integrator = IntegratorType.Euler, + Cone = FrictionConeType.pyramidal, + Jacobian = JacobianType.auto, + Solver = ConstraintSolverType.Newton, + Iterations = 100, + Tolerance = 1e-8f, + NoSlipIterations = 0, + NoSlipTolerance = 1e-6f, + CcdIterations = 50, + CcdTolerance = 1e-6f, + Flag = MjcfOptionFlag.Default + }; + + public void ParseMjcf(XmlElement mjcf) { + if (mjcf.HasAttribute("timestep")) { + var mjTimestep = mjcf.GetFloatAttribute("timestep", Time.fixedDeltaTime); + if (mjTimestep != Time.fixedDeltaTime) { + Debug.unityLogger.LogWarning( + "MuJoCo", + $"MuJoCo's timestep ({mjTimestep}s) ignored, please set fixedDeltaTime manually.", + null); + } } - - public enum FrictionConeType - { - pyramidal, - elliptic + if (mjcf.HasAttribute("gravity")) { + var mjGravity = mjcf.GetVector3Attribute("gravity", Vector3.zero); + Debug.unityLogger.LogWarning( + "MuJoCo", + $"MuJoCo's gravity ({mjGravity.x} {mjGravity.z} {mjGravity.y}) ignored, " + + "please set gravity in the Physics Manager.", + null); } + var localDefault = MjOptionStruct.Default; + ImpRatio = mjcf.GetFloatAttribute("impratio", localDefault.ImpRatio); + Magnetic = mjcf.GetVector3Attribute("magnetic", localDefault.Magnetic); + Wind = mjcf.GetVector3Attribute("wind", localDefault.Wind); + Density = mjcf.GetFloatAttribute("density", localDefault.Density); + Viscosity = mjcf.GetFloatAttribute("viscosity", localDefault.Viscosity); + OverrideMargin = mjcf.GetFloatAttribute("o_margin", localDefault.OverrideMargin); - public enum JacobianType - { - dense, - sparse, - auto + OverrideSolRef.FromMjcf(mjcf, "o_solref"); + OverrideSolImp.FromMjcf(mjcf, "o_solimp"); + + Integrator = mjcf.GetEnumAttribute("integrator", localDefault.Integrator); + Cone = mjcf.GetEnumAttribute("cone", localDefault.Cone); + Jacobian = mjcf.GetEnumAttribute("jacobian", localDefault.Jacobian); + Solver = mjcf.GetEnumAttribute("solver", localDefault.Solver); + + Iterations = (int)mjcf.GetFloatAttribute("iterations", localDefault.Iterations); + Tolerance = mjcf.GetFloatAttribute("tolerance", localDefault.Tolerance); + NoSlipIterations = (int)mjcf.GetFloatAttribute( + "noslip_iterations", localDefault.NoSlipIterations); + NoSlipTolerance = mjcf.GetFloatAttribute("noslip_tolerance", localDefault.NoSlipTolerance); + CcdIterations = (int)mjcf.GetFloatAttribute("ccd_iterations", localDefault.CcdIterations); + CcdTolerance = mjcf.GetFloatAttribute("ccd_tolerance", localDefault.CcdTolerance); + + var flagElements = mjcf.GetElementsByTagName("flag"); + if (flagElements.Count == 1) { + Flag.FromMjcf(flagElements[0] as XmlElement); + } else if (flagElements.Count > 1) { + throw new ArgumentException("More than one node is not supported."); } + } - public enum ConstraintSolverType - { - PGS, - CG, - Newton + public XmlElement ToMjcf(XmlElement mjcf) { + mjcf.SetAttribute("impratio", MjEngineTool.MakeLocaleInvariant($"{ImpRatio}")); + + mjcf.SetAttribute("magnetic", MjEngineTool.MakeLocaleInvariant($"{Magnetic.x} {Magnetic.y} {Magnetic.z}")); + mjcf.SetAttribute("wind", MjEngineTool.MakeLocaleInvariant($"{Wind.x} {Wind.y} {Wind.z}")); + + mjcf.SetAttribute("density", MjEngineTool.MakeLocaleInvariant($"{Density}")); + mjcf.SetAttribute("viscosity", MjEngineTool.MakeLocaleInvariant($"{Viscosity}")); + mjcf.SetAttribute("o_margin", MjEngineTool.MakeLocaleInvariant($"{OverrideMargin}")); + + OverrideSolRef.ToMjcf(mjcf, "o_solref"); + OverrideSolImp.ToMjcf(mjcf, "o_solimp"); + + mjcf.SetAttribute("integrator", Integrator.ToString()); + mjcf.SetAttribute("cone", Cone.ToString()); + mjcf.SetAttribute("jacobian", Jacobian.ToString()); + mjcf.SetAttribute("solver", Solver.ToString()); + + mjcf.SetAttribute("iterations", MjEngineTool.MakeLocaleInvariant($"{Iterations}")); + mjcf.SetAttribute("tolerance", MjEngineTool.MakeLocaleInvariant($"{Tolerance}")); + mjcf.SetAttribute("noslip_iterations", MjEngineTool.MakeLocaleInvariant($"{NoSlipIterations}")); + mjcf.SetAttribute("noslip_tolerance", MjEngineTool.MakeLocaleInvariant($"{NoSlipTolerance}")); + mjcf.SetAttribute("ccd_iterations", MjEngineTool.MakeLocaleInvariant($"{CcdIterations}")); + mjcf.SetAttribute("ccd_tolerance", MjEngineTool.MakeLocaleInvariant($"{CcdTolerance}")); + + var flags = (XmlElement)mjcf.AppendChild( + mjcf.OwnerDocument.CreateElement("flag")); + Flag.ToMjcf(flags); + return mjcf; + } +} + +[Serializable] +public class NumericEntry { + public String Name; + [Tooltip("Space-separated list of floats.")] + public String Data; +} + +public class MjGlobalSettings : MonoBehaviour { + + [Tooltip("Filename for the generated scene XML.")] + public string DebugFileName; + + [Tooltip("Scales the force applied by the mouse spring.")] + public float MouseSpringStiffness = 100; + + [Tooltip("If false, numerical suffixes will be aded to ensure name uniqueness.")] + public bool UseRawGameObjectNames; + + public MjOptionStruct GlobalOptions = MjOptionStruct.Default; + + public MjSizeStruct GlobalSizes = MjSizeStruct.Default; + + public List CustomNumeric = new List() {}; + + public static MjGlobalSettings Instance { + get { + if (_instance == null) { + var instances = FindObjectsOfType(); + if (instances.Length > 1) { + throw new InvalidOperationException( + "Only one MjGlobalSettings instance is allowed - please resolve manually."); + } else if (instances.Length == 1) { + _instance = instances[0]; + } + } + return _instance; } + } - public enum EnableDisableFlag - { - enable, - disable + private static MjGlobalSettings _instance = null; + + public void Awake() { + if (_instance == null) { + _instance = this; + } else if (_instance != this) { + throw new InvalidOperationException( + "At most one MjGlobalSettings should be present."); } + } - [Serializable] - public struct MjcfOptionFlag - { - public EnableDisableFlag Constraint; - public EnableDisableFlag Equality; - public EnableDisableFlag FrictionLoss; - public EnableDisableFlag Limit; - public EnableDisableFlag Contact; - public EnableDisableFlag Spring; - public EnableDisableFlag Damper; - public EnableDisableFlag Gravity; - public EnableDisableFlag ClampCtrl; - public EnableDisableFlag WarmStart; - public EnableDisableFlag FilterParent; - public EnableDisableFlag Actuation; - public EnableDisableFlag RefSafe; - public EnableDisableFlag Override; - public EnableDisableFlag Energy; - public EnableDisableFlag FwdInv; - public EnableDisableFlag MultiCCD; - public static MjcfOptionFlag Default = new MjcfOptionFlag() - { - Constraint = EnableDisableFlag.enable, - Equality = EnableDisableFlag.enable, - FrictionLoss = EnableDisableFlag.enable, - Limit = EnableDisableFlag.enable, - Contact = EnableDisableFlag.enable, - Spring = EnableDisableFlag.enable, - Damper = EnableDisableFlag.enable, - Gravity = EnableDisableFlag.enable, - ClampCtrl = EnableDisableFlag.enable, - WarmStart = EnableDisableFlag.enable, - FilterParent = EnableDisableFlag.enable, - Actuation = EnableDisableFlag.enable, - RefSafe = EnableDisableFlag.enable, - Override = EnableDisableFlag.disable, - Energy = EnableDisableFlag.disable, - FwdInv = EnableDisableFlag.disable, - MultiCCD = EnableDisableFlag.disable - }; + public void ParseGlobalMjcfSections(XmlElement mujocoNode) { - public void FromMjcf(XmlElement mjcf) - { - var localDefault = MjcfOptionFlag.Default; - Constraint = mjcf.GetEnumAttribute("constraint", localDefault.Constraint); - Equality = mjcf.GetEnumAttribute("equality", localDefault.Equality); - FrictionLoss = mjcf.GetEnumAttribute("frictionloss", - localDefault.FrictionLoss); - Limit = mjcf.GetEnumAttribute("limit", localDefault.Limit); - Contact = mjcf.GetEnumAttribute("contact", localDefault.Contact); - Spring = mjcf.GetEnumAttribute("spring", localDefault.Spring); - Damper = mjcf.GetEnumAttribute("damper", localDefault.Damper); - Gravity = mjcf.GetEnumAttribute("gravity", localDefault.Gravity); - ClampCtrl = mjcf.GetEnumAttribute("clampctrl", localDefault.ClampCtrl); - WarmStart = mjcf.GetEnumAttribute("warmstart", localDefault.WarmStart); - FilterParent = mjcf.GetEnumAttribute("filterparent", - localDefault.FilterParent); - Actuation = mjcf.GetEnumAttribute("actuation", localDefault.Actuation); - RefSafe = mjcf.GetEnumAttribute("refsafe", localDefault.RefSafe); - Override = mjcf.GetEnumAttribute("override", localDefault.Override); - Energy = mjcf.GetEnumAttribute("energy", localDefault.Energy); - FwdInv = mjcf.GetEnumAttribute("fwdinv", localDefault.FwdInv); - MultiCCD = mjcf.GetEnumAttribute("multiccd", localDefault.MultiCCD); - } + var optionNode = mujocoNode.SelectSingleNode("option") as XmlElement; + var sizeNode = mujocoNode.SelectSingleNode("size") as XmlElement; + var customNode = mujocoNode.SelectSingleNode("custom") as XmlElement; - public void ToMjcf(XmlElement mjcf) - { - mjcf.SetAttribute("constraint", Constraint.ToString()); - mjcf.SetAttribute("equality", Equality.ToString()); - mjcf.SetAttribute("frictionloss", FrictionLoss.ToString()); - mjcf.SetAttribute("limit", Limit.ToString()); - mjcf.SetAttribute("contact", Contact.ToString()); - mjcf.SetAttribute("spring", Spring.ToString()); - mjcf.SetAttribute("damper", Damper.ToString()); - mjcf.SetAttribute("gravity", Gravity.ToString()); - mjcf.SetAttribute("clampctrl", ClampCtrl.ToString()); - mjcf.SetAttribute("warmstart", WarmStart.ToString()); - mjcf.SetAttribute("filterparent", FilterParent.ToString()); - mjcf.SetAttribute("actuation", Actuation.ToString()); - mjcf.SetAttribute("refsafe", RefSafe.ToString()); - mjcf.SetAttribute("override", Override.ToString()); - mjcf.SetAttribute("energy", Energy.ToString()); - mjcf.SetAttribute("fwdinv", FwdInv.ToString()); - mjcf.SetAttribute("multiccd", MultiCCD.ToString()); - } + if (optionNode != null) { + GlobalOptions.ParseMjcf(optionNode); } - - [Serializable] - public struct MjSizeStruct - { - public String Memory; - public static MjSizeStruct Default = new MjSizeStruct() - { - Memory = "-1" - }; - - public void ParseMjcf(XmlElement mjcf) - { - Memory = mjcf.GetStringAttribute("memory", "-1"); - } - - public XmlElement ToMjcf(XmlElement mjcf) - { - mjcf.SetAttribute("memory", $"{Memory}"); - return mjcf; - } + if (sizeNode != null) { + GlobalSizes.ParseMjcf(sizeNode); } - - [Serializable] - public struct MjOptionStruct - { - - // "timestep" and "gravity" come from global settings. - [Tooltip("Ratio of frictional-to-normal constraint impedance.")] - public float ImpRatio; - [Tooltip("Global magnetic flux used by magnetometer sensors.")] - public Vector3 Magnetic; - [Tooltip("Velocity vector of the medium, scales with the values of Density and Viscosity below.")] - public Vector3 Wind; - [Tooltip("Density of the medium for lift and drag forces. 0 disables lift and drag forces.")] - public float Density; - [Tooltip("Viscosity of the medium for viscous forces. 0 disables viscous forces.")] - public float Viscosity; - [Tooltip("Contact pair margin used when ContactOverride flag is set.")] - public float OverrideMargin; - [Tooltip("Contact pair solref used when ContactOverride flag is set.")] - public SolverReference OverrideSolRef; - [Tooltip("Contact pair solimp used when ContactOverride flag is set.")] - public SolverImpedance OverrideSolImp; - [Tooltip("Numerical integrator.")] - public IntegratorType Integrator; - [Tooltip("How to model the friction cone.")] - public FrictionConeType Cone; - [Tooltip("How to represent the constraint Jacobian.")] - public JacobianType Jacobian; - [Tooltip("Constraint solver algorithm.")] - public ConstraintSolverType Solver; - [Tooltip("Maximum iterations for the constraint solver.")] - public int Iterations; - [Tooltip("Threshold used for early termination of the constraint solver.")] - public float Tolerance; - [Tooltip("Maximum iterations for the no-slip phase of the constraint solver.")] - public int NoSlipIterations; - [Tooltip("Threshold used for early termination of the Noslip solver.")] - public float NoSlipTolerance; - [Tooltip("Maximum iterations for convex mesh collisions.")] - public int CcdIterations; - [Tooltip("Threshold used for early termination of the MPR algorithm.")] - public float CcdTolerance; - - public MjcfOptionFlag Flag; - - // Default values copied from the "XML reference" page in the MuJoCo documentation. - public static MjOptionStruct Default = new MjOptionStruct() - { - ImpRatio = 1.0f, - Magnetic = Vector3.zero, - Wind = new Vector3(0.0f, 0.0f, 0.0f), - Density = 0.0f, - Viscosity = 0.0f, - OverrideMargin = 0.0f, - OverrideSolRef = SolverReference.Default, - OverrideSolImp = SolverImpedance.Default, - Integrator = IntegratorType.Euler, - Cone = FrictionConeType.pyramidal, - Jacobian = JacobianType.auto, - Solver = ConstraintSolverType.Newton, - Iterations = 100, - Tolerance = 1e-8f, - NoSlipIterations = 0, - NoSlipTolerance = 1e-6f, - CcdIterations = 50, - CcdTolerance = 1e-6f, - Flag = MjcfOptionFlag.Default - }; - - public void ParseMjcf(XmlElement mjcf) - { - if (mjcf.HasAttribute("timestep")) - { - var mjTimestep = mjcf.GetFloatAttribute("timestep", Time.fixedDeltaTime); - if (mjTimestep != Time.fixedDeltaTime) - { - Debug.unityLogger.LogWarning( - "MuJoCo", - $"MuJoCo's timestep ({mjTimestep}s) ignored, please set fixedDeltaTime manually.", - null); - } - } - if (mjcf.HasAttribute("gravity")) - { - var mjGravity = mjcf.GetVector3Attribute("gravity", Vector3.zero); - Debug.unityLogger.LogWarning( - "MuJoCo", - $"MuJoCo's gravity ({mjGravity.x} {mjGravity.z} {mjGravity.y}) ignored, " + - "please set gravity in the Physics Manager.", - null); - } - var localDefault = MjOptionStruct.Default; - ImpRatio = mjcf.GetFloatAttribute("impratio", localDefault.ImpRatio); - Magnetic = mjcf.GetVector3Attribute("magnetic", localDefault.Magnetic); - Wind = mjcf.GetVector3Attribute("wind", localDefault.Wind); - Density = mjcf.GetFloatAttribute("density", localDefault.Density); - Viscosity = mjcf.GetFloatAttribute("viscosity", localDefault.Viscosity); - OverrideMargin = mjcf.GetFloatAttribute("o_margin", localDefault.OverrideMargin); - - OverrideSolRef.FromMjcf(mjcf, "o_solref"); - OverrideSolImp.FromMjcf(mjcf, "o_solimp"); - - Integrator = mjcf.GetEnumAttribute("integrator", localDefault.Integrator); - Cone = mjcf.GetEnumAttribute("cone", localDefault.Cone); - Jacobian = mjcf.GetEnumAttribute("jacobian", localDefault.Jacobian); - Solver = mjcf.GetEnumAttribute("solver", localDefault.Solver); - - Iterations = (int)mjcf.GetFloatAttribute("iterations", localDefault.Iterations); - Tolerance = mjcf.GetFloatAttribute("tolerance", localDefault.Tolerance); - NoSlipIterations = (int)mjcf.GetFloatAttribute( - "noslip_iterations", localDefault.NoSlipIterations); - NoSlipTolerance = mjcf.GetFloatAttribute("noslip_tolerance", localDefault.NoSlipTolerance); - CcdIterations = (int)mjcf.GetFloatAttribute("ccd_iterations", localDefault.CcdIterations); - CcdTolerance = mjcf.GetFloatAttribute("ccd_tolerance", localDefault.CcdTolerance); - - var flagElements = mjcf.GetElementsByTagName("flag"); - if (flagElements.Count == 1) - { - Flag.FromMjcf(flagElements[0] as XmlElement); - } - else if (flagElements.Count > 1) - { - throw new ArgumentException("More than one node is not supported."); - } - } - - public XmlElement ToMjcf(XmlElement mjcf) - { - mjcf.SetAttribute("impratio", MjEngineTool.MakeLocaleInvariant($"{ImpRatio}")); - - mjcf.SetAttribute("magnetic", - MjEngineTool.MakeLocaleInvariant($"{Magnetic.x} {Magnetic.y} {Magnetic.z}")); - mjcf.SetAttribute("wind", MjEngineTool.MakeLocaleInvariant($"{Wind.x} {Wind.y} {Wind.z}")); - - mjcf.SetAttribute("density", MjEngineTool.MakeLocaleInvariant($"{Density}")); - mjcf.SetAttribute("viscosity", MjEngineTool.MakeLocaleInvariant($"{Viscosity}")); - mjcf.SetAttribute("o_margin", MjEngineTool.MakeLocaleInvariant($"{OverrideMargin}")); - - OverrideSolRef.ToMjcf(mjcf, "o_solref"); - OverrideSolImp.ToMjcf(mjcf, "o_solimp"); - - mjcf.SetAttribute("integrator", Integrator.ToString()); - mjcf.SetAttribute("cone", Cone.ToString()); - mjcf.SetAttribute("jacobian", Jacobian.ToString()); - mjcf.SetAttribute("solver", Solver.ToString()); - - mjcf.SetAttribute("iterations", MjEngineTool.MakeLocaleInvariant($"{Iterations}")); - mjcf.SetAttribute("tolerance", MjEngineTool.MakeLocaleInvariant($"{Tolerance}")); - mjcf.SetAttribute("noslip_iterations", MjEngineTool.MakeLocaleInvariant($"{NoSlipIterations}")); - mjcf.SetAttribute("noslip_tolerance", MjEngineTool.MakeLocaleInvariant($"{NoSlipTolerance}")); - mjcf.SetAttribute("ccd_iterations", MjEngineTool.MakeLocaleInvariant($"{CcdIterations}")); - mjcf.SetAttribute("ccd_tolerance", MjEngineTool.MakeLocaleInvariant($"{CcdTolerance}")); - - var flags = (XmlElement)mjcf.AppendChild( - mjcf.OwnerDocument.CreateElement("flag")); - Flag.ToMjcf(flags); - return mjcf; + 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); } + } } + } - [Serializable] - public class NumericEntry - { - public String Name; - [Tooltip("Space-separated list of floats.")] - public String Data; + public void GlobalsToMjcf(XmlElement mjcf) { + var doc = mjcf.OwnerDocument; + 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); } - - public class MjGlobalSettings : MonoBehaviour - { - - [Tooltip("Filename for the generated scene XML.")] - public string DebugFileName; - - [Tooltip("Scales the force applied by the mouse spring.")] - public float MouseSpringStiffness = 100; - - [Tooltip("If false, numerical suffixes will be aded to ensure name uniqueness.")] - public bool UseRawGameObjectNames; - - public MjOptionStruct GlobalOptions = MjOptionStruct.Default; - - public MjSizeStruct GlobalSizes = MjSizeStruct.Default; - - public List CustomNumeric = new List() { }; - - public static MjGlobalSettings Instance - { - get - { - if (_instance == null) - { - var instances = FindObjectsOfType(); - if (instances.Length > 1) - { - throw new InvalidOperationException( - "Only one MjGlobalSettings instance is allowed - please resolve manually."); - } - else if (instances.Length == 1) - { - _instance = instances[0]; - } - } - return _instance; - } - } - - private static MjGlobalSettings _instance = null; - - public void Awake() - { - if (_instance == null) - { - _instance = this; - } - else if (_instance != this) - { - throw new InvalidOperationException( - "At most one MjGlobalSettings should be present."); - } - } - - public void ParseGlobalMjcfSections(XmlElement mujocoNode) - { - - 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 (sizeNode != null) - { - 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 GlobalsToMjcf(XmlElement mjcf) - { - var doc = mjcf.OwnerDocument; - 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); - } - } - } -} \ No newline at end of file + } +} +} From 92cb306ba582306e9d07a9395b79844f584f5c5b Mon Sep 17 00:00:00 2001 From: Joan Llobera Date: Fri, 17 Oct 2025 09:58:07 +0200 Subject: [PATCH 4/6] removed Passive, added Damper and Spring objects --- unity/Runtime/Components/MjGlobalSettings.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/unity/Runtime/Components/MjGlobalSettings.cs b/unity/Runtime/Components/MjGlobalSettings.cs index b336d58a..c9f489e4 100644 --- a/unity/Runtime/Components/MjGlobalSettings.cs +++ b/unity/Runtime/Components/MjGlobalSettings.cs @@ -63,7 +63,8 @@ public struct MjcfOptionFlag { public EnableDisableFlag FrictionLoss; public EnableDisableFlag Limit; public EnableDisableFlag Contact; - public EnableDisableFlag Passive; + public EnableDisableFlag Spring; + public EnableDisableFlag Damper; public EnableDisableFlag Gravity; public EnableDisableFlag ClampCtrl; public EnableDisableFlag WarmStart; @@ -80,7 +81,8 @@ public struct MjcfOptionFlag { FrictionLoss = EnableDisableFlag.enable, Limit = EnableDisableFlag.enable, Contact = EnableDisableFlag.enable, - Passive = EnableDisableFlag.enable, + Spring = EnableDisableFlag.enable, + Damper = EnableDisableFlag.enable, Gravity = EnableDisableFlag.enable, ClampCtrl = EnableDisableFlag.enable, WarmStart = EnableDisableFlag.enable, @@ -101,7 +103,8 @@ public struct MjcfOptionFlag { localDefault.FrictionLoss); Limit = mjcf.GetEnumAttribute("limit", localDefault.Limit); Contact = mjcf.GetEnumAttribute("contact", localDefault.Contact); - Passive = mjcf.GetEnumAttribute("passive", localDefault.Passive); + Spring = mjcf.GetEnumAttribute("spring", localDefault.Spring); + Damper = mjcf.GetEnumAttribute("damper", localDefault.Damper); Gravity = mjcf.GetEnumAttribute("gravity", localDefault.Gravity); ClampCtrl = mjcf.GetEnumAttribute("clampctrl", localDefault.ClampCtrl); WarmStart = mjcf.GetEnumAttribute("warmstart", localDefault.WarmStart); @@ -121,7 +124,8 @@ public struct MjcfOptionFlag { mjcf.SetAttribute("frictionloss", FrictionLoss.ToString()); mjcf.SetAttribute("limit", Limit.ToString()); mjcf.SetAttribute("contact", Contact.ToString()); - // mjcf.SetAttribute("passive", Passive.ToString()); + mjcf.SetAttribute("spring", Spring.ToString()); + mjcf.SetAttribute("damper", Damper.ToString()); mjcf.SetAttribute("gravity", Gravity.ToString()); mjcf.SetAttribute("clampctrl", ClampCtrl.ToString()); mjcf.SetAttribute("warmstart", WarmStart.ToString()); @@ -328,7 +332,7 @@ public class MjGlobalSettings : MonoBehaviour { public static MjGlobalSettings Instance { get { if (_instance == null) { - var instances = FindObjectsOfType(); + var instances = FindObjectsByType(FindObjectsSortMode.None); if (instances.Length > 1) { throw new InvalidOperationException( "Only one MjGlobalSettings instance is allowed - please resolve manually."); From 36bb957db170873ddd3ab7dd04b51828d92488e8 Mon Sep 17 00:00:00 2001 From: Joan Llobera Date: Fri, 17 Oct 2025 10:42:27 +0200 Subject: [PATCH 5/6] cut extra long line to make sure it passes the syntax checks --- unity/Runtime/Components/MjGlobalSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unity/Runtime/Components/MjGlobalSettings.cs b/unity/Runtime/Components/MjGlobalSettings.cs index c9f489e4..926030ea 100644 --- a/unity/Runtime/Components/MjGlobalSettings.cs +++ b/unity/Runtime/Components/MjGlobalSettings.cs @@ -276,7 +276,8 @@ public struct MjOptionStruct { public XmlElement ToMjcf(XmlElement mjcf) { mjcf.SetAttribute("impratio", MjEngineTool.MakeLocaleInvariant($"{ImpRatio}")); - mjcf.SetAttribute("magnetic", MjEngineTool.MakeLocaleInvariant($"{Magnetic.x} {Magnetic.y} {Magnetic.z}")); + mjcf.SetAttribute("magnetic", + MjEngineTool.MakeLocaleInvariant($"{Magnetic.x} {Magnetic.y} {Magnetic.z}")); mjcf.SetAttribute("wind", MjEngineTool.MakeLocaleInvariant($"{Wind.x} {Wind.y} {Wind.z}")); mjcf.SetAttribute("density", MjEngineTool.MakeLocaleInvariant($"{Density}")); From 1a9469765f3aa25e37c81f3cc72a58b0ff900a32 Mon Sep 17 00:00:00 2001 From: Joan Llobera Date: Tue, 21 Oct 2025 10:58:17 +0200 Subject: [PATCH 6/6] small syntax fixes --- unity/Runtime/Components/MjGlobalSettings.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/unity/Runtime/Components/MjGlobalSettings.cs b/unity/Runtime/Components/MjGlobalSettings.cs index 926030ea..e85c0778 100644 --- a/unity/Runtime/Components/MjGlobalSettings.cs +++ b/unity/Runtime/Components/MjGlobalSettings.cs @@ -275,11 +275,9 @@ public struct MjOptionStruct { public XmlElement ToMjcf(XmlElement mjcf) { mjcf.SetAttribute("impratio", MjEngineTool.MakeLocaleInvariant($"{ImpRatio}")); - mjcf.SetAttribute("magnetic", - MjEngineTool.MakeLocaleInvariant($"{Magnetic.x} {Magnetic.y} {Magnetic.z}")); + MjEngineTool.MakeLocaleInvariant($"{Magnetic.x} {Magnetic.y} {Magnetic.z}")); mjcf.SetAttribute("wind", MjEngineTool.MakeLocaleInvariant($"{Wind.x} {Wind.y} {Wind.z}")); - mjcf.SetAttribute("density", MjEngineTool.MakeLocaleInvariant($"{Density}")); mjcf.SetAttribute("viscosity", MjEngineTool.MakeLocaleInvariant($"{Viscosity}")); mjcf.SetAttribute("o_margin", MjEngineTool.MakeLocaleInvariant($"{OverrideMargin}")); @@ -291,7 +289,6 @@ public struct MjOptionStruct { mjcf.SetAttribute("cone", Cone.ToString()); mjcf.SetAttribute("jacobian", Jacobian.ToString()); mjcf.SetAttribute("solver", Solver.ToString()); - mjcf.SetAttribute("iterations", MjEngineTool.MakeLocaleInvariant($"{Iterations}")); mjcf.SetAttribute("tolerance", MjEngineTool.MakeLocaleInvariant($"{Tolerance}")); mjcf.SetAttribute("noslip_iterations", MjEngineTool.MakeLocaleInvariant($"{NoSlipIterations}"));