Hi @jonathon, @Rob,
I have looked into receiving and de-serializing objects sent from Rhino using the .NET Speckle v3 SDK.
I’ve managed to read some basic information from the Rhino blocks in the model:
- block definition name
- count of block definition instances
- block geometry volume
Following is a simplified implementation:
private async Task<Dictionary<string, (string Name, int Count, double? Volume)>> DeconstructObjectAsync(SpeckleStreamId streamId, SpeckleObjectId objectId, CancellationToken cancellationToken)
{
var result = new Dictionary<string, (string Name, int Count, double? Volume)>();
using var transport = _serverTransportFactory.Create(_account, streamId);
// Read object
var data = await _operations.Receive(
objectId,
transport,
cancellationToken: cancellationToken
);
// Flatten instances tree
var allObjects = data.Flatten().ToList();
// Get all instance definitions
var definitions = allObjects
.OfType<InstanceDefinitionProxy>()
.ToList();
// Get all instance proxies
var instances = allObjects
.OfType<InstanceProxy>()
.ToList();
// Get name, volume and count
foreach (var instanceGroup in instances.GroupBy(i => i.definitionId))
{
var definitionId = instanceGroup.Key;
// Find the corresponding definition
var definition = definitions.FirstOrDefault(d => d.applicationId == definitionId);
if (definition is null)
{
continue;
}
double? volume = null;
// The definition.objects contains the IDs of the geometry objects
foreach (var objId in definition.objects)
{
var geometryObj = allObjects.FirstOrDefault(obj =>
obj.applicationId == objId ||
obj.id == objId
);
var members = geometryObj?.GetMembers();
// The geometry objects contain a display value field which is a list of Base objects
var displayValue = members?.FirstOrDefault(m => m.Key == "displayValue").Value;
if (displayValue is not List<object> elements)
{
continue;
}
foreach (var element in elements)
{
if (element is not Base b)
{
continue;
}
var baseMembers = b.GetMembers();
// The display value Base objects contain a volume field
if (baseMembers.FirstOrDefault(bm => bm.Key == "volume").Value is double volumeValue)
{
volume = (volume ?? 0.0) + volumeValue;
}
}
}
var name = definition.name;
result.Add(definitionId, (name, instanceGroup.Count(), volume));
}
return result;
}
Seems like navigating the tree has changed significantly, now the applicationId is used for linking the parent with the child object. Is this true?
What is the displayValue field, how come is the volume stored in there instead of the parent “geometry” object?
I will try to expand this with other models (without blocks, from other sources than Rhino, etc…).
Please let me know if you have suggestions for a simplified general approach, maybe using the TraversalFunc() instead?
I would also like to read the area field like we do for volume above, but that seems to be missing from the displayValue objects, at least from the model I am currently testing this on. Do you have any hints on where that could be stored?
I can provide the v2 implementation for comparison if needed.