I used grasshopper to send 3 geometries (rooms) to speckle, and I want to receive them in python. I am newbie in receiving data in python, and I can now receive base object in python. Then how can I further extract info from base e.g. vertice, face and form the geometries back?
There are many approaches here (sorry, no simple answer).
In general, a nice way of seeing attributes associated to objects is to use the get_member_names() method. Combine that, with the selection info in the viewer (which you’re already doing), you can access the attributes you want with some simple dot notation and using the parameter names you see in the viewer.
An example below, seeing “what I have” in my commit using get_member_names():
This works nicely in a well protected and defined environment. But when working with larger commits, it’s probably nice to use the getattr() to handle attributes not existing. Consider the little example below with two different approaches:
Interesting. The get_member_names() return is not what I was expecting when comparing to the viewer. This could, however, be nested within the @Data detached property. Have you had a look in there?
# Explore what is within the detached property
received_base["@Data"]
# If this is directly an object
received_base["@Data"].get_member_names()
Also, does received_base.id from the Python script equal the id shown on the viewer (i.e. 344853b366…)
I seemed to have overread the part about the received commit coming from Grasshopper - my apologies! The extrapolating of information through a series of clunky number of “@{0;0;2;0}” tree indices is a bit awkward - AGREED!
With that, we can refine the code a bit, thankfully @jonathon has got your back with a neat flatten_base function. Modified a little to focus on searching for those indices coming from GH prepended with a “@” symbol:
"""Helper module for a simple speckle object tree flattening."""
from collections.abc import Iterable
from typing import Any
from specklepy.objects import Base
def flatten_base(base: Base) -> Iterable[Base]:
"""Flatten a base object into an iterable of bases.
This function recursively traverses any attribute that starts with '@' in the
base object, yielding each nested base object found within those attributes.
Args:
base (Base): The base object to flatten.
Yields:
Base: Each nested base object in the hierarchy.
"""
def _get_elements_from_attr(obj: Any) -> list[Base]:
"""Helper function to get Base objects from an attribute value."""
if isinstance(obj, (list, tuple)):
return [item for item in obj if isinstance(item, Base)]
elif isinstance(obj, Base):
return [obj]
return []
# Get all attributes that start with '@'
at_attributes = [attr for attr in dir(base) if attr.startswith('@')]
# Process each @ attribute
for attr in at_attributes:
elements = getattr(base, attr, None)
if elements is not None:
# Get all Base objects from this attribute
base_elements = _get_elements_from_attr(elements)
# Recursively process each Base object
for element in base_elements:
yield from flatten_base(element)
yield base
As you can see in the below screenshot, my beam objects are stored in ["@Data"]["@{0}]. In this approach, I just call flatten_base on the received_base and I can negate the need for awkward indexing.
Give it a go on your script! Note, this is now specific for GH and searching for a prepended “@” character. Take care when using the function on data from other sources. Other host apps we search for elements attribute as opposed to the “@”.
I think there is just a little variable naming conflict going on there. See two approaches below, the first one uses the variable name flattened_base and the second flatten_base. This should highlight the conflict in naming being the same as the function.