Anyway, I’m having some trouble with extracting the transform values from a model. The process for fetching and extracting follows the recommendations from the dev docs (index the model, get the instances, extract the displayValue). This works great for extracting the transform from the instance reference, but I’m not sure how to resolve the transform look up for direct mesh values. Is this suppose to be a similar process?
The object in question is a Floor from revit, which has a direct mesh reference as a display value, and we just need to grab the transform matrix for some data stuff. Examining that object in the front end there is a null value for location, which seems to be the case for most direct mesh objects. I’m sorta guessing, since direct meshes don’t carry their transformation values and can rebuild their mesh just with the vertices list, that maybe a different proxy group is used to determine that location?
but it seems that SDK has been updated and some things has changed.
So, currently speckle uses DAG and object instances and definitions to reduce number of duplicates. Such elements as columns, windows and doors are affected the most since they don’t change shape and inside the model tree they’re represented as InstanceProxy and have properties:
units
id
applicationId
definitionId
transform
maxDepth
In the model tree there are revitInstancedObjects which are basically Objects.Geometry.Mesh with faces and vertices.
The problem is that I can’t find a way how Proxies are linked to Meshes. The documentation says that it’s done via “applicationId”, but I didn’t find any links in case of my model. The “definitionId” seems to be the most probable one which is 64 length uppercase string like “4C125527DCB339D21CF34F9B734491AF467DD8907ABD9CDCDBF4D251177D6374” but there is nothing like this in revitInstancedObjects. So I’d appreciate a hint on how are Proxies and Instances are linked for models sent via Revit connector v3.
If you see mesh in the display value of any data object, it means the mesh is located in global coordinates already. While converting objects we were baking their transform into local coordinates till 3.9, well, you can’t get any transform for that.
Note: with 3.10, we have started to support instancing for meshes to shrink the data footprint for various good reasons. Now you can see InstanceProxy with its transform in display value of data objects that are converted from FamilyInstance. In your case if it’s fioor, its not likely to see instances.
Small update. I’ve tried doing the same with an IFC file uploaded directly to Speckle. In this case, the instances have an application ID in the format “2101_mat0” whereas the proxies have an application ID in the format “3D3lDoPxD49OSWlqQlrPbS:DEFINITION:4997_mat0”. It’s clear that the instances and proxies could be mapped if I keep the part of the string after ‘DEFINITION’, but this complicates things more than the documentation suggests. In the case of this IFC model, the definition ID looks like ‘DEFINITION:4997_mat0’ which somehow replicates the application ID.
So, what is the best way to resolve proxies and instances in general?
May I also ask a question about transformation here. I’ve got a MEP model in IFC which I’ve uploaded directly to speckle
All the objects were converted to DataObjects with a detached displayValue stored as a definitionGeometry in another branch. Definition Geometry is flat list of Objects.Geometry.Mesh.
If I go in depth of a model tree, I have nested collections. Usually the first element is a DataObject that contains metadata about a group like IfcBuildingStorey where the displayValue is an empty list. The following DataObjects have geometrical representation. In this case displayValue returns a list of InstanceProxy. By splitting the applicationId I was able to map InstanceProxy to Objects.Geometry.Mesh. but I’ve got some issues in transforming the geometry.
Here is my workflow
# Make a 4x4 numpy matrix
transformation_matrix = np.array(proxy.transform).reshape(4,4)
# Make a trimesh from speckle mesh
trimesh_instance = trimesh.Trimesh(
vertices = speckle_mesh.vertices,
faces = speckle_mesh.faces)
# Copy of a trimesh
new_trimesh_instance = trimesh_instance.copy()
#transformed trimesh
new_trimesh_instance.apply_transform(transformation_matrix)
when visualizing the trimesh before transformation I’ve got this clean mesh
I’m aware of the nested objects matrix multiplications, but since metadata object has an empty displayValue, this makes me think that in my case only one transformation was applied for each object.
@oguzhan thanks for the info! Let me see if I got this right… maybe this might address @arsy question as well.
When an object is converted to speckle there are two possible outcomes for it’s transform.
The object is considered a InstanceProxy and the transformation data is stored in the reference proxy object.
The object stays atomic(ish), keeping the displayValue as a direct mesh and the transform values are no longer needed since it’s at global coordinates.
With the second part, does this mean that the transform values are baked into the vertices of the mesh? If that is the case then is it safe to assume, using the floor object example, that we can get the transform value of that floor by rebuilding the bounding box of the displayValue?
That might explain why @arsy is getting two different results. If the transformations are baked in, no need to apply em’ twice
@arsy As you’ve noticed Speckle’s Revit connector produces very different looking ids than IFC uploads. As far as Speckle is concerned, the only thing we care about is the uniqueness of the id. So we often use/build unique strings from data fed to us by the host app/format to create useful unique (and stable) ids.
Despite the application Ids from IFC imports looking like it might be useful to start using string manipulation to separate/trim parts of the id. This should not be necessary, and I’d urge you not to go down this route.
The instanceDefinitionProxies can be found on the root object, they are what provide the mapping
Not sure what you want a reverse transform for when the “transform” is baked into the object. Nevertheless, a Simple heuristic:
Object has a Mesh (or other geometry) in the displayValue array. This will be ‘baked’ in coordinated space. Or,
Object has InstanceProxy in the displayValue, which means:
the Transform property on that InstanceProxy is the transform from the origin (most likely) of,
the definitionId on that InstanceProxy is the applicationId of the InstanceDefinitionProxy found in a collection at the Root version object.
the object property (array) of the InstanceDefinitionProxy will list the objects found within the elements collection of the Root collection object that should have the transform applied.
If it helps, the floor position is used for some of our services and calculations. For example we might check where a floor position falls on a grid line in order to determine a structural or MEP component of the model as a basic check. Basically just a bounding box check.
This might be my simpleton mind at work, but I typically think mesh data and object transforms are separate entities. In 's case, it makes sense to combine them into a baked displayValue to reduce the model’s footprint. It does feel a bit awkward to have to use the vertices to get coordinates of the object but if that is the tradeoff for an optimized model, I’ll role with it.
We’ve had this debate. We were fully baked and have moved to where we are proxy+transform for instances.
This varies by connector and is a balance of performance and payload.
As an FYI Revit API ONLY returns baked coordinates, so creating the instance and transform means unbaking and applying a generated transform. Alternatively Navis could produce everything unbaked and with transforms
Thank you for the detailed schema, it helped me organise my understanding of the Speckle data model.
I had assumed that instanceDefinitionProxies were part of the elements in the root collection. Now I see that there is a specific property for them. Thank you very much!