I am testing out a web-based application that uses a database of equipment models stored in Speckle. I use the manager to Send real Revit geometry into Speckle, and it is stored correctly.
But, when I use the .NET SDK to Receive from my equipment Database (A), and then Send into another Database (B), I see no geometry in B and am missing many parameters. It appears to be Sent as a single default Base object, not the nested Collection that I had stored in A.
If I want to do this, will I need to carefully Cast the received base model back into the correct Collection / FamilyInstance objects that inherit from Base? It is unlikely that I will ever be able to predict this structure. I’m hoping the Receive method, or a helper function, exists. How should I do this? Am I correct that Sending a generic Base class will not automatically detect the more complicated structure that was received?
The goal is to create a configurator type app where the user can choose from a central database of templatized objects (Database A). Once selected, these bespoke models are configured into a specific project (Database B).
We are using the Speckle Desktop Connector in Revit to push selected objects into Database A. See image below. I have made some progress from my previous post. Rather than being stored as a single Objects.Other.Revit.RevitInstance object, I now see it is stored in the nested structure shown below
Here is where the problem begins. When I send this nested structure to DatabaseB using Helpers.Send(), my Speckle DatabaseB shows that a Base object has been added with no geometry. See image from previous post. It appears that the Helper.Send does not properly send nested structures.
If I create a traversal function I am able to get to the RevitInstance and send only that. This then shows up properly in DatabaseB with geometry. But this requires I write complicated custom code to identify the objects I want and cast them as their derived Object type.
Now I have a few questions:
why does the Revit DesktopConnector send the object as a nested structure?
what is the correct way to handle these nested objects if my goal is to access the core object, update it’s properties, and then push back into Speckle DatabaseB. Later I will pull this from Speckle and convertToNative into Revit.
What is the proper way to do this?
Thanks for the extra context, this is helpful!
I believe the reason is that the element is an Instance, and so we’re storing its geometry definition elsewhere to optimize send & receive performance
Thank you Matteo. The structure makes perfect sense to me now.
What confuses me is why the Helper.Send() function doesn’t appear to send the entire tree of data. It appears to only send the highest level parent as a single Base object. Am I missing a step?
It does, Helper.Send just wraps Operation.Send which will send an object and all children (i.e. the full tree)
Probably the most useful function to look at in C# SDK is the Flatten extension method:
- Provides a simple way to flatten all objects in a Speckle model.
- You can filter things out by type (e.g. use Linqs’s OfType to filter for specific types of Speckle objects)
- You can mutate these objects, and re-send the root Base object back to Speckle.
If a simple flatten function isn’t enough for you, and you need to consume more structured data with some context about where they are in the tree (plus handle Instances with more precision) then there’s a more advanced GraphTraversal function, but I’d recommend trying to keep things simple with Flatten if possible.
Any more questions, feel free to ask. I can probably share some more specific code samples if needed.
@Jedd , that is so odd, it is now working perfectly for me, but I’ve hit another problem.
I swear previously Sending a Base object that included many nested objects resulted in data being transferred, but no visible geometry in the Speckle Project viewer. Notice in this old commit how there is no geometry but I have all the data. Oddly, the object is a Base object not the expected RevitInstance object.
Not sure what I changed, but now when I pass data it looks like this:
Notice the nested object is a RevitInstance, not Base, and I see geometry as expected.
Does this have something to do with Displayable Geometry?
I ask because I am trying to create my own Kit to add functionality to RevitInstance objects. I successfully created a new class that inherits from RevitInstance and I am able to modify all the parameters I want. BUT, when I push to Speckle I see no geometry, similar to the problem discussed above.
I solved this by implementing IDisplayValue, but I had to use the custom method below to assign geoemtry to displayValue. Strangely, revitInstance?.TryGetDisplayValue(); returns null.
What is the correct way to handle geometry and display values? Is there a better way?
private List<Base> GetDisplayableGeoFromRevitInstance(RevitInstance revitInstance)
{
var transformedGeo = revitInstance?.GetTransformedGeometry();
List<Base> displayEditableList = new List<Base>();
foreach (var item in transformedGeo)
{
if (item is Mesh mesh)
{
displayEditableList.Add(mesh);
}
}
return displayEditableList;
}
If you’re seeing Most Speckle objects receiving as Base rather than their proper type e.g. Objects.Other.RevitInstance (like in your first screenshot)
Then this can be one of two issues.
Somehow, the Speckle.Objects nuget is not being loaded.
You either need to manually reference the Speckle.Objects in your csproj.
Or dynamically load via loading a Kit via the KitManager.
If you are doing the former, then we’ve also seen cases where simply referencing the Speckle.Objects nuget is not sufficient to actually load it. The runtime needs to see it used.
You can force the runtime to load the assembly, by adding a line that uses any type from Objects. e.g.
_ = typeof(Mesh).assembly
//Now Objects will be fully loaded, and Operations.Receive will pick up on the correct types
All Speckle Instances do not have a displayValue, instead they have geometry under the definition that is transformed by the transform on the instance.
GetTransformedGeometry function will make a copy of simple geometry (ITransformable) e.g. Meshes, and bake the instance transform into the vertex/curve data of the geometry.
So its a lossy process, but the correct one if you simply want the simple transformed geometry.
definition!!! of course! This was hiding in the inherited Instance class. That is the missing key. Thank you for helping me navigate the object model. I am now able to see display geometry without my previous hack to assign geometry to the displayValue. I also don’t need to implement IDisplayValue<IReadOnlyList<Base>>.
For others on here that are trying to create their own custom objects, I’ll share a little more detail:
I am trying to create a new custom object based on a RevitInstance object as a way of adding extra features to the existing object model. This requires I copy all properties in my constructor as shown below.
I am able to access the property typedDefinition, but not definition, which has an internal set. I assume this is ok because it appears the typedDefinition overrides definition for RevitInstances.
Support docs recommend inheriting from Base, but I don’t want to reinvent the wheel and would rather inherit from a higher-level object. Am I approaching this properly?
public class CustomObject: RevitInstance
{
public CustomObject(RevitInstance revitInstance)
{
typedDefinition = revitInstance.typedDefinition;
//definition = revitInstance.definition; // set accessor inaccessible
level = revitInstance.level;
facingFlipped = revitInstance.facingFlipped;
// copy all other properties