From 60bb160a0d0bec04a2d33d5f1afbd45936cd023a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinhagen?= <88777268+bjoernsteinhagen@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:41:14 +0200 Subject: [PATCH] feat(gh): expand speckle props to now show set not union (#1322) --- .../Objects/ExpandSpeckleProperties.cs | 112 +++++++++++------- 1 file changed, 71 insertions(+), 41 deletions(-) diff --git a/Connectors/Rhino/Speckle.Connectors.GrasshopperShared/Components/Objects/ExpandSpeckleProperties.cs b/Connectors/Rhino/Speckle.Connectors.GrasshopperShared/Components/Objects/ExpandSpeckleProperties.cs index 7d1cace40..83fca41e8 100644 --- a/Connectors/Rhino/Speckle.Connectors.GrasshopperShared/Components/Objects/ExpandSpeckleProperties.cs +++ b/Connectors/Rhino/Speckle.Connectors.GrasshopperShared/Components/Objects/ExpandSpeckleProperties.cs @@ -40,57 +40,87 @@ public class ExpandSpeckleProperties : GH_Component, IGH_VariableParameterCompon protected override void SolveInstance(IGH_DataAccess da) { + // ALWAYS run port generation on the first iteration, BEFORE validating the current item + // ensure that a null at index 0 doesn't prevent ports from being created. + if (da.Iteration == 0) + { + // gather all property groups from the input (skipNulls = true) + var allData = Params.Input[0].VolatileData.AllData(true).OfType().ToList(); + + var outputParamsDict = new Dictionary(); + + foreach (var propGroup in allData) + { + if (propGroup?.Value == null) + { + continue; + } + + foreach (var key in propGroup.Value.Keys) + { + ISpecklePropertyGoo value = propGroup.Value[key]; + object? outputValue = value switch + { + SpecklePropertyGoo prop => prop.Value, + SpecklePropertyGroupGoo pg => pg, + _ => value + }; + + if (!outputParamsDict.TryGetValue(key, out var existingWrapper)) + { + var param = new SpeckleOutputParam + { + Name = key, + NickName = key, + Access = outputValue is IList ? GH_ParamAccess.list : GH_ParamAccess.item + }; + outputParamsDict[key] = new OutputParamWrapper(param, outputValue); + } + else if (existingWrapper.Param.Access == GH_ParamAccess.item && outputValue is IList) + { + existingWrapper.Param.Access = GH_ParamAccess.list; + } + } + } + + var outputParams = outputParamsDict.Values.ToList(); + + Name = $"Properties ({outputParams.Count})"; + NickName = Name; + + if (OutputMismatch(outputParams)) + { + OnPingDocument()?.ScheduleSolution(5, _ => CreateOutputs(outputParams)); + return; + } + } + SpecklePropertyGroupGoo? properties = null; if (!da.GetData(0, ref properties) || properties?.Value == null) { return; } - Name = $"Properties ({properties.Value.Count})"; - NickName = Name; - - var outputParams = new List(); - - foreach (var key in properties.Value.Keys) + for (int i = 0; i < Params.Output.Count; i++) { - ISpecklePropertyGoo value = properties.Value[key]; - object? outputValue = value switch - { - SpecklePropertyGoo prop => prop.Value, - SpecklePropertyGroupGoo propGroup => propGroup, - _ => value - }; + var outParam = Params.Output[i]; - var param = new SpeckleOutputParam + if (properties.Value.TryGetValue(outParam.Name, out ISpecklePropertyGoo? value)) { - Name = key, - NickName = key, - Access = outputValue is IList ? GH_ParamAccess.list : GH_ParamAccess.item - }; - - outputParams.Add(new OutputParamWrapper(param, outputValue)); - } - - // handle parameter creation/update (only on first iteration) - if (da.Iteration == 0 && OutputMismatch(outputParams)) - { - OnPingDocument()?.ScheduleSolution(5, _ => CreateOutputs(outputParams)); - return; // exit early - } - // only set data if we have the correct parameter structure - if (Params.Output.Count == outputParams.Count) - { - for (int i = 0; i < outputParams.Count; i++) - { - var outputParam = outputParams[i]; - switch (outputParam.Param.Access) + object? outputValue = value switch { - case GH_ParamAccess.item: - da.SetData(i, outputParam.Value); - break; - case GH_ParamAccess.list: - da.SetDataList(i, outputParam.Value as IList ?? new List()); - break; + SpecklePropertyGoo prop => prop.Value, + SpecklePropertyGroupGoo propGroup => propGroup, + _ => value + }; + + if (outParam.Access == GH_ParamAccess.item) + { + da.SetData(i, outputValue); + } + else + { + da.SetDataList(i, outputValue as IList ?? new List()); } } }