Get IDs that have a viewable property

Hello guys!

I would need only the IDs that have a viewable property. A solutions already exists for javascript here. How can I achieve the same with specklepy?

Her @ayansengupta17 do you want a flat list of IDs? or to traverse the Speckle data and do something with the displayable objects?

We don’t have a succinct comparison with the Viewer API in specklepy, but many options exist. It will depend a little on the source data.

The Speckle viewer typically identifies objects to display based on the presence of a displayValue, which acts similarly to a nominal is_visible property. In scenarios where displayValue is absent, the viewer falls back to displaying objects based on their “Geometry” type. To replicate this logic in specklepy, two separate checks are defined:

def has_display_value(o) -> bool:
    """Check if an object has a 'displayValue.'  or its alias."""
    display_value_property_aliases = {
        "displayValue", "@displayValue"}
    return any(getattr(o, alias, None) is not
               None for alias in display_value_property_aliases)

def is_geometry_type(o) -> bool:
    """Check if an object's type includes 'Geometry'."""
    return "Geometry" in getattr(o, "speckle_type", "")

In specklepy, GraphTraversal provides functionality for navigating through and processing the hierarchical Speckle data structure. This feature allows developers to define specific rules (via TraversalRule) for identifying and acting upon objects within the data structure based on custom criteria. The primary aim is to facilitate the examination and manipulation of complex object hierarchies, enabling targeted actions on subsets of the data.

By establishing specific TraversalRule instances, you can instruct GraphTraversal on navigating the data structure and what to do when certain conditions are met. This approach enables sophisticated data handling strategies, such as identifying “viewable” objects within a Speckle stream, by applying user-defined logic.

The following illustrates how GraphTraversal is utilized to identify objects considered “viewable” based on the presence of displayValue or their classification as “Geometry”:

from specklepy.objects.graph_traversal.traversal \
    import GraphTraversal, TraversalRule

def get_data_traversal() -> GraphTraversal:
    """
    Navigates the Speckle data hierarchy, applying
    rules to identify 'viewable' objects based on
    'displayValue' or 'Geometry' type.
    """
    elements_property_aliases = {"elements", "@elements"}

    display_value_rule = TraversalRule(
        [
            lambda o: has_display_value(o),
            lambda o: is_geometry_type(o),
        ],
        lambda o: elements_property_aliases,
    )

    default_rule = TraversalRule( [lambda _: True], lambda o: o.get_member_names())

    return GraphTraversal(
        [display_value_rule, default_rule]
    )

With GraphTraversal set up, the next step is to traverse the data structure from a root object and extract IDs for objects meeting our “viewable” criteria:


def find_viewable_ids(root_object):
    """
    Finds IDs of objects considered 'viewable' or 'clickable', prioritizing
    those with 'displayValue' or considering 'Geometry' type.
    """
    viewable_ids = []
    speckle_data = get_data_traversal()  # Use the configured traversal rules
    traversal_contexts_collection = speckle_data.traverse(root_object)

    for context in traversal_contexts_collection:
        current_object = context.current

        # Apply separate checks for displayValue and Geometry type
        if has_display_value(current_object) or is_geometry_type(current_object):
            viewable_ids.append(current_object.id)

    return viewable_ids
1 Like

the provided script targets objects with displayValue or those classified as “Geometry.” However, it’s crucial to highlight a potential limitation regarding instanced geometry, particularly relevant for data from applications like Revit or Rhino.

This approach identifies definition objects directly, which could be entirely suitable depending on your project’s needs. Yet, if your work requires detailed insights into instanced geometries (where geometries are referenced rather than directly attached), the script may need further adaptation to meet your requirements fully.

Warning Pseudocode only:

def process_object(current_object):
    if is_instance(current_object):
        # Adapt or extend logic for instances
        definition_object = get_definition(current_object)
        # Process definition as needed
    else:
        # Proceed with direct geometry or displayValue

This consideration ensures the script aligns with your specific use case, especially when working with complex AEC models. Whether this caveat impacts your use case depends on the specifics of your data and the intended analysis or processing.

1 Like