From 013caf0eedc804cbcc318902e5df902495517810 Mon Sep 17 00:00:00 2001 From: Christopher Short Date: Fri, 28 Aug 2026 19:11:28 +0100 Subject: [PATCH] Add new QuantityFamilies and To/From SI --- Units_Engine/Convert/FromUnit.cs | 71 +++++++++++++++ Units_Engine/Convert/ToUnit.cs | 69 ++++++++++++++ Units_Engine/Query/ResolveUnit.cs | 145 ++++++++++++++++++++++++++++++ Units_oM/Enums/QuantityFamily.cs | 49 ++++++++++ Units_oM/UnitSpec.cs | 33 +++++++ 5 files changed, 367 insertions(+) create mode 100644 Units_Engine/Convert/FromUnit.cs create mode 100644 Units_Engine/Convert/ToUnit.cs create mode 100644 Units_Engine/Query/ResolveUnit.cs create mode 100644 Units_oM/Enums/QuantityFamily.cs create mode 100644 Units_oM/UnitSpec.cs diff --git a/Units_Engine/Convert/FromUnit.cs b/Units_Engine/Convert/FromUnit.cs new file mode 100644 index 0000000..12d4082 --- /dev/null +++ b/Units_Engine/Convert/FromUnit.cs @@ -0,0 +1,71 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using BH.oM.Base.Attributes; +using BH.oM.Units; + +namespace BH.Engine.Units +{ + public static partial class Convert + { + [Description("Converts a value from the given unit symbol to SI, e.g. 12 \"mm\" → 0.012 m.")] + [Input("value", "The value in the specified unit.")] + [Input("unitSymbol", "Unit symbol (e.g. \"mm\", \"kN\", \"MPa\"). Case-sensitive.")] + [Output("siValue", "The value in SI units, or NaN if the unit is unrecognised.")] + public static double FromUnit(this double value, string unitSymbol) + { + UnitSpec spec = Query.ResolveUnit(unitSymbol); + if (spec == null) + return double.NaN; + + return FromUnit(value, spec); + } + + private static double FromUnit(double value, UnitSpec spec) + { + switch (spec.Family) + { + case QuantityFamily.Length: return value.FromLength(spec.Unit); + case QuantityFamily.Area: return value.FromArea(spec.Unit); + case QuantityFamily.Volume: return value.FromVolume(spec.Unit); + case QuantityFamily.AreaMomentOfInertia: return value.FromAreaMomentOfInertia(spec.Unit); + case QuantityFamily.Pressure: return value.FromPressure(spec.Unit); + case QuantityFamily.Force: return value.FromForce(spec.Unit); + case QuantityFamily.Torque: return value.FromTorque(spec.Unit); + case QuantityFamily.ForcePerLength: return value.FromForcePerLength(spec.Unit); + case QuantityFamily.Angle: return value.FromAngle(spec.Unit); + case QuantityFamily.Mass: return value.FromMass(spec.Unit); + case QuantityFamily.Acceleration: return value.FromAcceleration(spec.Unit); + case QuantityFamily.Density: return value.FromDensity(spec.Unit); + case QuantityFamily.Energy: return value.FromEnergy(spec.Unit); + case QuantityFamily.Speed: return value.FromSpeed(spec.Unit); + case QuantityFamily.Temperature: return value.FromTemperature(spec.Unit); + case QuantityFamily.Time: return value.FromDuration(spec.Unit); + case QuantityFamily.None: return value; + default: return double.NaN; + } + } + } +} diff --git a/Units_Engine/Convert/ToUnit.cs b/Units_Engine/Convert/ToUnit.cs new file mode 100644 index 0000000..21485d6 --- /dev/null +++ b/Units_Engine/Convert/ToUnit.cs @@ -0,0 +1,69 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using System.ComponentModel; +using BH.oM.Base.Attributes; +using BH.oM.Units; + +namespace BH.Engine.Units +{ + public static partial class Convert + { + [Description("Converts an SI value to the given unit symbol, e.g. 0.012 m → 12 \"mm\".")] + [Input("siValue", "The value in SI units.")] + [Input("unitSymbol", "Unit symbol to convert to (e.g. \"mm\", \"kN\", \"MPa\"). Case-sensitive.")] + [Output("value", "The value in the specified unit, or NaN if the unit is unrecognised.")] + public static double ToUnit(this double siValue, string unitSymbol) + { + UnitSpec spec = Query.ResolveUnit(unitSymbol); + if (spec == null) + return double.NaN; + + return ToUnit(siValue, spec); + } + + private static double ToUnit(double siValue, UnitSpec spec) + { + switch (spec.Family) + { + case QuantityFamily.Length: return siValue.ToLength(spec.Unit); + case QuantityFamily.Area: return siValue.ToArea(spec.Unit); + case QuantityFamily.Volume: return siValue.ToVolume(spec.Unit); + case QuantityFamily.AreaMomentOfInertia: return siValue.ToAreaMomentOfInertia(spec.Unit); + case QuantityFamily.Pressure: return siValue.ToPressure(spec.Unit); + case QuantityFamily.Force: return siValue.ToForce(spec.Unit); + case QuantityFamily.Torque: return siValue.ToTorque(spec.Unit); + case QuantityFamily.ForcePerLength: return siValue.ToForcePerLength(spec.Unit); + case QuantityFamily.Angle: return siValue.ToAngle(spec.Unit); + case QuantityFamily.Mass: return siValue.ToMass(spec.Unit); + case QuantityFamily.Acceleration: return siValue.ToAcceleration(spec.Unit); + case QuantityFamily.Density: return siValue.ToDensity(spec.Unit); + case QuantityFamily.Energy: return siValue.ToEnergy(spec.Unit); + case QuantityFamily.Speed: return siValue.ToSpeed(spec.Unit); + case QuantityFamily.Temperature: return siValue.ToTemperature(spec.Unit); + case QuantityFamily.Time: return siValue.ToDuration(spec.Unit); + case QuantityFamily.None: return siValue; + default: return double.NaN; + } + } + } +} \ No newline at end of file diff --git a/Units_Engine/Query/ResolveUnit.cs b/Units_Engine/Query/ResolveUnit.cs new file mode 100644 index 0000000..e8053fe --- /dev/null +++ b/Units_Engine/Query/ResolveUnit.cs @@ -0,0 +1,145 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Globalization; + +using UN = UnitsNet; //This is to avoid clashes between UnitsNet quantity attributes and BHoM quantity attributes +using UNU = UnitsNet.Units; + +using System.ComponentModel; +using BH.oM.Base.Attributes; +using BH.oM.Units; +using BH.Engine.Base; + +namespace BH.Engine.Units +{ + public static partial class Query + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [Description("Resolves a unit symbol string to its corresponding UnitSpec, identifying the quantity family and unit.")] + [Input("unitSymbol", "Unit symbol to resolve (e.g. \"mm\", \"kN\", \"MPa\"). Case-sensitive.")] + [Output("unitSpec", "The resolved UnitSpec, or null if the unit is unrecognised.")] + public static UnitSpec ResolveUnit(string unitSymbol) + { + string key = unitSymbol?.Trim() ?? ""; + if (key == "" || key == "-") + return new UnitSpec { Family = QuantityFamily.None, Unit = null }; + + if (m_UnitTable.TryGetValue(key, out UnitSpec spec)) + return spec; + + Compute.RecordError($"Unit '{unitSymbol}' is not recognised. Unit symbols are case-sensitive."); + return null; + } + + /***************************************************/ + /**** Private Methods ****/ + /***************************************************/ + + private static Dictionary BuildUnitTable() + { + var table = new Dictionary(StringComparer.Ordinal); + + foreach (var family in m_Families) + { + foreach (object unit in Enum.GetValues(family.Key)) + { + foreach (string symbol in GetAbbreviations(family.Key, (int)(object)unit)) + { + TryAdd(table, symbol, family.Value, unit); + TryAdd(table, Ascii(symbol), family.Value, unit); + } + } + } + + // Torque aliases UnitsNet doesn't publish + TryAdd(table, "Nm", QuantityFamily.Torque, UNU.TorqueUnit.NewtonMeter); + TryAdd(table, "N.m", QuantityFamily.Torque, UNU.TorqueUnit.NewtonMeter); + TryAdd(table, "kNm", QuantityFamily.Torque, UNU.TorqueUnit.KilonewtonMeter); + TryAdd(table, "kN.m", QuantityFamily.Torque, UNU.TorqueUnit.KilonewtonMeter); + + return table; + } + + /***************************************************/ + + private static IEnumerable GetAbbreviations(Type unitType, int unitValue) + { + try + { + return UN.UnitsNetSetup.Default.UnitAbbreviations + .GetUnitAbbreviations(unitType, unitValue, CultureInfo.InvariantCulture); + } + catch + { + return Array.Empty(); + } + } + + /***************************************************/ + + private static void TryAdd(Dictionary table, string symbol, + QuantityFamily family, object unit) + { + if (!string.IsNullOrWhiteSpace(symbol) && !table.ContainsKey(symbol)) + table[symbol] = new UnitSpec { Family = family, Unit = unit }; + } + + /***************************************************/ + + private static string Ascii(string symbol) + { + return symbol? + .Replace("²", "2") + .Replace("³", "3") + .Replace("⁴", "4") + .Replace("⁶", "6"); + } + + /***************************************************/ + /**** Private Fields ****/ + /***************************************************/ + + private static readonly Dictionary m_Families = new Dictionary + { + { typeof(UNU.LengthUnit), QuantityFamily.Length }, + { typeof(UNU.AreaUnit), QuantityFamily.Area }, + { typeof(UNU.VolumeUnit), QuantityFamily.Volume }, + { typeof(UNU.AreaMomentOfInertiaUnit), QuantityFamily.AreaMomentOfInertia }, + { typeof(UNU.PressureUnit), QuantityFamily.Pressure }, + { typeof(UNU.ForceUnit), QuantityFamily.Force }, + { typeof(UNU.TorqueUnit), QuantityFamily.Torque }, + { typeof(UNU.ForcePerLengthUnit), QuantityFamily.ForcePerLength }, + { typeof(UNU.AngleUnit), QuantityFamily.Angle }, + { typeof(UNU.MassUnit), QuantityFamily.Mass }, + }; + + private static readonly Dictionary m_UnitTable = BuildUnitTable(); + + /***************************************************/ + } +} \ No newline at end of file diff --git a/Units_oM/Enums/QuantityFamily.cs b/Units_oM/Enums/QuantityFamily.cs new file mode 100644 index 0000000..cff33ab --- /dev/null +++ b/Units_oM/Enums/QuantityFamily.cs @@ -0,0 +1,49 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using System.ComponentModel; + +namespace BH.oM.Units +{ + [Description("Categorises physical quantities for dispatch to the correct conversion method.")] + public enum QuantityFamily + { + None = 0, + Length = 1, + Area = 2, + Volume = 3, + AreaMomentOfInertia = 4, + Pressure = 5, + Force = 6, + Torque = 7, + ForcePerLength = 8, + Angle = 9, + Mass = 10, + Acceleration = 11, + Density = 12, + Energy = 13, + Power = 14, + Speed = 15, + Temperature = 16, + Time = 17, + } +} diff --git a/Units_oM/UnitSpec.cs b/Units_oM/UnitSpec.cs new file mode 100644 index 0000000..af4c25e --- /dev/null +++ b/Units_oM/UnitSpec.cs @@ -0,0 +1,33 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using System.ComponentModel; + +namespace BH.oM.Units +{ + [Description("Specification linking a unit symbol to its quantity family and unit enumeration value.")] + public class UnitSpec + { + public virtual QuantityFamily Family { get; set; } = QuantityFamily.None; + public virtual object Unit { get; set; } = null; + } +} \ No newline at end of file