diff --git a/src/Libraries/RevitNodes/Elements/Element.cs b/src/Libraries/RevitNodes/Elements/Element.cs index bf1aae2eb..81eb4a986 100644 --- a/src/Libraries/RevitNodes/Elements/Element.cs +++ b/src/Libraries/RevitNodes/Elements/Element.cs @@ -310,6 +310,47 @@ public void Tessellate(IRenderPackage package, TessellationParameters parameters // or transactions and which must necessarily be threaded in a specific way. } + + /// + /// Get a parameter by name of an element + /// + /// The name of the parameter. + /// + private Autodesk.Revit.DB.Parameter GetParameterByName(string parameterName) + { + // + // Parameter names are not unique on a given element. There are several valid cases where + // duplicated parameter names can be found in the Parameter Set. + // + // The most common ones are: + // + // 1. Multiple built-in parameters with the same name + // This is a common implementation pattern when a different parameter behavior is wanted + // for different scopes. For example, lets say that you want a parameter to be writable + // in the property palette but read-only in a schedule view. The easiest way to accomplish + // this would be to add two parameters. One that is read-write and one that is read-only. + // These parameters will both have the same name and they will share the same getter. + // + // 2. Built-in parameters and User parameters with the same name + // This happens when a loadable family defines a user parameter with the same name + // as a built-in parameter. + // + // Currently, we try to resolve this and get consistent results by + // 1. Get all parameters for the given name + // 2. Sort parameters by ElementId - This will give us built-in parameters first (ID's for built-ins are always < -1) + // 3. If it exist: Use the first writable parameter + // 4. Otherwise: Use the first read-only parameter + // + var allParams = + InternalElement.Parameters.Cast() + .Where(x => string.CompareOrdinal(x.Definition.Name, parameterName) == 0) + .OrderBy(x => x.Id.IntegerValue); + + var param = allParams.FirstOrDefault(x => x.IsReadOnly == false) ?? allParams.FirstOrDefault(); + + return param; + } + /// /// Get the value of one of the element's parameters. /// @@ -317,15 +358,7 @@ public void Tessellate(IRenderPackage package, TessellationParameters parameters /// public object GetParameterValueByName(string parameterName) { - - var param = - // We don't use Element.GetOrderedParameters(), it only returns ordered parameters - // as show in the UI - InternalElement.Parameters.Cast() - // Element.Parameters returns a differently ordered list on every invocation. - // We must sort it to get sensible results. - .OrderBy(x => x.Id.IntegerValue) - .FirstOrDefault(x => x.Definition.Name == parameterName); + var param = GetParameterByName(parameterName); if (param == null || !param.HasValue) return string.Empty; @@ -382,7 +415,7 @@ public Element OverrideInView(Revit.Filter.OverrideGraphicSettings overrides, bo /// The value. public Element SetParameterByName(string parameterName, object value) { - var param = InternalElement.Parameters.Cast().FirstOrDefault(x => x.Definition.Name == parameterName); + var param = GetParameterByName(parameterName); if (param == null) throw new Exception(Properties.Resources.ParameterNotFound);