Compare commits

...

4 Commits

Author SHA1 Message Date
Claire Kuang 6d7ffb5e63 Merge branch 'dev' into claire/display-value-proxies 2025-11-10 12:32:16 +00:00
Claire Kuang 1a0467cb1a fix parent context bug 2025-11-05 16:24:26 +00:00
Claire Kuang b0743bc0b3 removes display instance list 2025-11-05 16:19:58 +00:00
Claire Kuang b8f6ce61ea poc on fixing display instance proxies in rhino 2025-11-05 16:00:58 +00:00
5 changed files with 170 additions and 22 deletions
@@ -452,7 +452,7 @@ public sealed class ReceiveComponentWorker : WorkerInstance<ReceiveAsyncComponen
var unpackedRoot = scope.Get<RootObjectUnpacker>().Unpack(Root);
// separate atomic objects from block instances
var (atomicObjects, blockInstances) = scope
var (atomicObjects, blockInstances, atomicObjectsWithInstances) = scope
.Get<RootObjectUnpacker>()
.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
@@ -183,7 +183,7 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
var unpackedRoot = rootObjectUnpacker.Unpack(root);
// split atomic objects from block components before conversion
var (atomicObjects, blockInstances) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
var (atomicObjects, blockInstances, atomicObjectsWithInstances) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
@@ -16,6 +16,7 @@ using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.Rhino.Operations.Receive;
@@ -64,9 +65,9 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
_conversionHandler = conversionHandler;
}
#pragma warning disable CA1506
#pragma warning disable CA1506, CA1502
public Task<HostObjectBuilderResult> Build(
#pragma warning restore CA1506
#pragma warning restore CA1506, CA1502
Base rootObject,
string projectName,
string modelName,
@@ -85,13 +86,38 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
var unpackedRoot = _rootObjectUnpacker.Unpack(rootObject);
// 2 - Split atomic objects and instance components with their path
var (atomicObjectsWithoutInstanceComponentsForConverter, instanceComponents) =
var (atomicObjectsWithoutInstanceComponents, instanceComponents, atomicObjectsWithInstanceComponents) =
_rootObjectUnpacker.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
var atomicObjectsWithoutInstanceComponentsWithPath = _layerBaker.GetAtomicObjectsWithPath(
atomicObjectsWithoutInstanceComponentsForConverter
atomicObjectsWithoutInstanceComponents
);
var instanceComponentsWithPath = _layerBaker.GetInstanceComponentsWithPath(instanceComponents);
var atomicObjectsWithInstanceComponentsWithPath = _layerBaker.GetAtomicObjectsWithPath(
atomicObjectsWithInstanceComponents
);
// 2.0 - POC!! this could be done with a traversal helper!!
// create a map between atomic objects with display instances, and the display instances of that atomic object (index)
Dictionary<int, List<int>> displayInstanceIdMap = new();
for (int i = 0; i < atomicObjectsWithInstanceComponents.Count; i++)
{
TraversalContext atomicObjectWithInstanceDisplay = atomicObjectsWithInstanceComponents.ElementAt(i);
for (int j = 0; j < instanceComponents.Count; j++)
{
if (atomicObjectWithInstanceDisplay == instanceComponents.ElementAt(j).Parent)
{
if (displayInstanceIdMap.TryGetValue(i, out List<int>? value))
{
value.Add(j);
}
else
{
displayInstanceIdMap.Add(i, new List<int>() { j });
}
}
}
}
// 2.1 - these are not captured by traversal, so we need to re-add them here
if (unpackedRoot.DefinitionProxies != null && unpackedRoot.DefinitionProxies.Count > 0)
@@ -99,6 +125,7 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
var transformed = unpackedRoot.DefinitionProxies.Select(proxy =>
(Array.Empty<Collection>(), proxy as IInstanceComponent)
);
instanceComponentsWithPath.AddRange(transformed);
}
@@ -129,13 +156,14 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
{
using var layerNoDraw = new DisableRedrawScope(_converterSettings.Current.Document.Views);
var paths = atomicObjectsWithoutInstanceComponentsWithPath.Select(t => t.path).ToList();
paths.AddRange(atomicObjectsWithInstanceComponentsWithPath.Select(t => t.path));
paths.AddRange(instanceComponentsWithPath.Select(t => t.path));
_layerBaker.CreateAllLayersForReceive(paths, baseLayerName);
})
.Wait(cancellationToken);
}
// 5 - Convert atomic objects
// 5 - Convert atomic objects without instances first!!
var bakedObjectIds = new HashSet<string>();
Dictionary<string, IReadOnlyCollection<string>> applicationIdMap = new(); // This map is used in converting blocks in stage 2. keeps track of original app id => resulting new app ids post baking
HashSet<ReceiveConversionResult> conversionResults = new();
@@ -146,7 +174,7 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
foreach (var (path, obj) in atomicObjectsWithoutInstanceComponentsWithPath)
{
onOperationProgressed.Report(
new("Converting objects", (double)++count / atomicObjectsWithoutInstanceComponentsForConverter.Count)
new("Converting objects", (double)++count / atomicObjectsWithoutInstanceComponents.Count)
);
var ex = _conversionHandler.TryConvert(() =>
{
@@ -182,7 +210,7 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
}
else if (result is List<(GeometryBase, Base)> fallbackConversionResult) // one to many fallback conversion
{
var guids = BakeObjectsAsFallbackGroup(fallbackConversionResult, obj, atts, baseLayerName);
var guids = BakeObjectsAsFallbackGroup(fallbackConversionResult, new(), obj, atts, baseLayerName);
conversionIds.AddRange(guids.Select(id => id.ToString()));
}
@@ -217,8 +245,10 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
}
// 6 - Convert instances
IReadOnlyCollection<string> createdDisplayIds;
using (var _ = _activityFactory.Start("Converting instances"))
{
// bake atomic instances
var (createdInstanceIds, consumedObjectIds, instanceConversionResults) = _instanceBaker.BakeInstances(
instanceComponentsWithPath,
applicationIdMap,
@@ -226,12 +256,90 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
onOperationProgressed
);
createdDisplayIds = createdInstanceIds;
bakedObjectIds.RemoveWhere(id => consumedObjectIds.Contains(id)); // remove all objects that have been "consumed"
bakedObjectIds.UnionWith(createdInstanceIds); // add instance ids
conversionResults.RemoveWhere(result => result.ResultId != null && consumedObjectIds.Contains(result.ResultId)); // remove all conversion results for atomic objects that have been consumed (POC: not that cool, but prevents problems on object highlighting)
conversionResults.UnionWith(instanceConversionResults); // add instance conversion results to our list
}
// 7 - Convert atomic objects with instance components
using (var _ = _activityFactory.Start("Converting objects"))
{
for (int i = 0; i < atomicObjectsWithInstanceComponentsWithPath.Count; i++)
{
var (path, obj) = atomicObjectsWithInstanceComponentsWithPath.ElementAt(i);
onOperationProgressed.Report(
new("Converting objects", (double)++count / atomicObjectsWithInstanceComponentsWithPath.Count)
);
var ex = _conversionHandler.TryConvert(() =>
{
// 0: get pre-created layer from cache in layer baker
int layerIndex = _layerBaker.GetLayerIndex(path, baseLayerName);
cancellationToken.ThrowIfCancellationRequested();
// 1: create object attributes for baking
ObjectAttributes atts = obj.GetAttributes();
atts.LayerIndex = layerIndex;
// 2: convert
var result = _converter.Convert(obj);
// 3: bake
var conversionIds = new List<string>();
List<string> createdInstanceIds = new();
if (result is List<(GeometryBase, Base)> fallbackConversionResult) // one to many fallback conversion, this should be the only type of non instance atomic object with instances in display value
{
// add display instances here
if (displayInstanceIdMap.TryGetValue(i, out List<int>? value))
{
foreach (var instanceIndex in value)
{
createdInstanceIds.Add(createdDisplayIds.ElementAt(instanceIndex));
}
}
var guids = BakeObjectsAsFallbackGroup(
fallbackConversionResult,
createdInstanceIds,
obj,
atts,
baseLayerName
);
conversionIds.AddRange(guids.Select(id => id.ToString()));
}
if (conversionIds.Count == 0)
{
// TODO: add this condition to report object - same as in autocad
throw new ConversionException("Object did not convert to any native geometry");
}
// 4: log
var id = conversionIds[0]; // this is group id if it is a one to many conversion, otherwise id of object itself
conversionResults.Add(new(Status.SUCCESS, obj, id, result.GetType().ToString()));
if (conversionIds.Count == 1)
{
bakedObjectIds.Add(id);
}
else
{
// first item always a group id if it is a one-to-many,
// we do not want to deal with later groups and its sub elements. It causes a huge issue on performance.
bakedObjectIds.AddRange(conversionIds.Skip(1));
}
// 5: populate app id map
applicationIdMap[obj.applicationId ?? obj.id.NotNull()] = conversionIds;
});
if (ex is not null)
{
conversionResults.Add(new(Status.ERROR, obj, null, null, ex));
}
}
}
// 7 - Create groups
if (unpackedRoot.GroupProxies is not null)
{
@@ -334,6 +442,7 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
private List<Guid> BakeObjectsAsFallbackGroup(
IEnumerable<(GeometryBase, Base)> fallbackConversionResult,
List<string> createdInstanceIds,
Base originatingObject,
ObjectAttributes atts,
string baseLayerName
@@ -349,6 +458,21 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
objCount++;
}
// now add already created instances
foreach (string instanceId in createdInstanceIds)
{
var instanceGuid = new Guid(instanceId);
var docObject = _converterSettings.Current.Document.Objects.FindId(instanceGuid);
if (docObject is null)
{
continue;
}
docObject.Attributes = atts;
docObject.CommitChanges();
objCount++;
objectIds.Add(instanceGuid);
}
// only create groups if we really need to, ie if the fallback conversion result count is bigger than one.
if (objCount > 1)
{
@@ -4,6 +4,7 @@ using Speckle.Objects.Data;
using Speckle.Sdk.Common;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Converters.Rhino.ToHost.TopLevel;
@@ -83,6 +84,7 @@ public class DataObjectConverter
SOG.Polyline polyline => new() { _polylineConverter.Convert(polyline) },
SOG.Region region => new() { _regionConverter.Convert(region) },
SOG.SubDX subd => _subdConverter.Convert(subd),
InstanceProxy => [],
_ => throw new ConversionException($"Found unsupported fallback geometry: {b.GetType()}")
};
}
@@ -1,4 +1,4 @@
using Speckle.Objects.Data;
using Speckle.Objects.Data;
using Speckle.Objects.Other;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
@@ -48,13 +48,21 @@ public class RootObjectUnpacker
public IReadOnlyCollection<LevelProxy>? TryGetLevelProxies(Base root) =>
TryGetProxies<LevelProxy>(root, ProxyKeys.LEVEL);
/// <summary>
/// POC!!! super hacky, we need an official way to differentiate between atomic instances, display value instances, atomic instance definitions, and display value instance definitions.
/// This should be reflected on the root collection.
/// </summary>
/// <param name="objectsToSplit"></param>
/// <returns></returns>
public (
IReadOnlyCollection<TraversalContext> atomicObjects,
IReadOnlyCollection<TraversalContext> instanceComponents
IReadOnlyCollection<TraversalContext> atomicNonInstanceObjects,
IReadOnlyCollection<TraversalContext> instanceComponents,
IReadOnlyCollection<TraversalContext> atomicNonInstanceObjectsWithInstanceComponents
) SplitAtomicObjectsAndInstances(IEnumerable<TraversalContext> objectsToSplit)
{
List<TraversalContext> atomicObjects = [];
List<TraversalContext> atomicObjectsWithoutInstanceComponents = [];
List<TraversalContext> instanceComponents = [];
List<TraversalContext> atomicObjectsWithInstanceComponents = [];
foreach (TraversalContext tc in objectsToSplit)
{
if (tc.Current is IInstanceComponent)
@@ -63,21 +71,35 @@ public class RootObjectUnpacker
}
else
{
atomicObjects.Add(tc);
}
if (tc.Current is DataObject dataObject)
{
foreach (var displayValue in dataObject.displayValue)
if (tc.Current is DataObject dataObject)
{
if (displayValue is IInstanceComponent)
bool containsInstanceComponents = false;
foreach (var displayValue in dataObject.displayValue)
{
instanceComponents.Add(new TraversalContext(displayValue, parent: tc));
if (displayValue is IInstanceComponent)
{
containsInstanceComponents = true;
instanceComponents.Add(new TraversalContext(displayValue, parent: tc));
}
}
if (containsInstanceComponents)
{
atomicObjectsWithInstanceComponents.Add(tc);
}
else
{
atomicObjectsWithoutInstanceComponents.Add(tc);
}
}
else
{
atomicObjectsWithoutInstanceComponents.Add(tc);
}
}
}
return (atomicObjects, instanceComponents);
return (atomicObjectsWithoutInstanceComponents, instanceComponents, atomicObjectsWithInstanceComponents);
}
private IReadOnlyCollection<T>? TryGetProxies<T>(Base root, string key) =>