Trouble understanding how to receive Base Objects with SpecklePy

Hi everyone,

I am currently facing this issue:

  • Objective: I am trying to receive Speckle data as Base objects, in the ideal JSON form represented in the SpecklePy+Streamlit tutorial
  • Issue: Even if I use the same code in the above mentioned example usage of SpecklePy, there seems to be the issue that what I receive is a Collection, and not a Base Object.
    Moreover, if I try to deserialize what I receive with the following code:
def get_stream_from_url(url):
    return url.split("/streams/")[1].split("/")[0]

def get_referenced_objs_from_commits(commits, latest_only='N'):
    if latest_only=='N':
        refobjs = []
        for commit in commits:
            refobjs.append(commit.referencedObject)
    else:
        refobjs = [commits[0].referencedObject]
    return refobjs

# provide any stream, branch, commit, object, or globals url
def speckle2json(url, last_only):
    wrapper = StreamWrapper(url)

    # get an authenticated SpeckleClient if you have a local account for the server
    client = wrapper.get_client()

    l = client.commit.list(get_stream_from_url(url))
    onjsss = get_referenced_objs_from_commits(l)
    # get an authenticated ServerTransport if you have a local account for the server
    transport = wrapper.get_transport()
    files = []
    for onj in onjsss:
        rec = operations.receive(onj, transport)
        serializer = BaseObjectSerializer()
        hash, obj_dict = serializer.traverse_base(rec)
        hash, serialized = serializer.write_json(rec)
        jsonfile = open(f"object_{onj}.json", "w")
        jsonfile.write(serialized)
        jsonfile.close()
        files.append(f"object_{onj}.json")

    return files

I get huge JSON files (>20 MB), encompassing multiple objects flooded with numeric arrays (that, I assume, represent the FacesValue).

  • Example: If necessary, I can share the JSON file I get back from the above code
  • Speckle link: Link to the URL I am using to download it: Speckle

Hi @AstraBert !
Your code seems to be fine! The Collection might look confusing but it’s a type inheriting from Base, so it can be treated exactly like a Base.

The serializer function you have chosen is supposed to traverse the Base object up to the primitive types, this is why you are seeing the huge numeric arrays. If you only need to access the immediate properties on the commit Base object, you might just loop through its properties without nested traversal that serializer provides:

dict_object = {}

dynamic_props = rec.get_dynamic_member_names()
for d_prop in dynamic_props: 
    dict_object[d_prop] = rec[d_prop]

// you can add more typed properties, e.g. dict_object[id] = rec.id, dict_object[units] = rec.units

json_data = json.dumps(dict_object)

Hope this helps!

1 Like

Hi @Kateryna!

I’m so thankful for your response :smiling_face_with_three_hearts:
However, I had to tweak the code a little bit because I was getting KeyError with typed_typed props.

dict_object = {}
typed_props = rec.get_typed_member_names()
for t_prop in typed_props: 
      try:
           dict_object[t_prop] = rec.__getitem__(t_prop)
       except KeyError:
            continue
dynamic_props = rec.get_dynamic_member_names()
for d_prop in dynamic_props: 
      try:
           dict_object[d_prop] = rec.__getitem__(d_prop)
       except KeyError:
           continue
print(dict_object)

What I get out is: {‘id’: ‘e5262a6fb51540974e6d07ac60b7fe5c’, ‘totalChildrenCount’: 610, ‘applicationId’: None, ‘name’: ‘Rhino Model’, ‘collectionType’: ‘rhino model’, ‘elements’: [Collection(id: 85c66ce4a2654f68e13fd2f292ea5231, speckle_type: Speckle.Core.Models.Collection, totalChildrenCount: 0), Collection(id: fe7b8733254f25544e5d3b674f26846e, speckle_type: Speckle.Core.Models.Collection, totalChildrenCount: 0), Collection(id: 0faa4efb5f3890661972a46c0c2713b4, speckle_type: Speckle.Core.Models.Collection, totalChildrenCount: 0), Collection(id: ae56dc0eee1d55313dc288dc9a68096a, speckle_type: Speckle.Core.Models.Collection, totalChildrenCount: 0), Collection(id: 968b2ad2937bcfdcbd140dd54122c042, speckle_type: Speckle.Core.Models.Collection, totalChildrenCount: 0), Collection(id: 581a822cdaa5c2972783510d57617f73, speckle_type: Speckle.Core.Models.Collection, totalChildrenCount: 0)], ‘speckleType’: ‘Speckle.Core.Models.Collection’}

But I would like to see something like this:

{
    "id":  "idfcaf8b9e145241dsdfa915885d87cda2",
    "speckle_type": "Base",
    "data": [
        {
            "id":  "ide6acabd37e865ce87a5sdf444d733877",
            "speckle_type": "Base",
            "@facade": [ { ... }, ... ],
            "@columns": [ { ... }, ... ],
            "@banister": { ... },
            "@floorSlab": { ... }
        },
        {
            ...
        }
    ]
}

As you can read here from the code you can find here, that I unfortunately did not get to work.

Could you please help me also with this? :pleading_face:

Thank you so much! :heart_hands:

1 Like

Hey @AstraBert,

The Stream/Model that you are trying to serialize is a Rhino Model, so what are you receiving is a huge Mesh (thousands of vertecies, faces etc…). You will never see something like this { "@facade": [ { ... }, ... ], "@columns": [ { ... }, ... ], "@banister": { ... }, "@floorSlab": { ... } },

You will need to use a Revit model, to see the JSON structure you mentioned.

Use this Revit Model Stream and follow the code below.

from specklepy.api.client import SpeckleClient
from specklepy.api.credentials import get_default_account, get_local_accounts
from specklepy.api import operations
from specklepy.transports.server import ServerTransport
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
import json

# initialise the client
client = SpeckleClient(host="app.speckle.systems")

# authenticate the client
account = get_default_account()
client.authenticate_with_account(account)

stream_id = "379cf3a777"  #  stream ID
object_id = "0d58bb2fe058eac533b5abfc87536436"  # Model ID

transport = ServerTransport(stream_id, client)
data = operations.receive(object_id, remote_transport=transport)  # get the top level Collection

types_referenceId = data["@Types"].id  # here you get the referenceID from @Types in order to access the "@facade", "@columns", etc...

types_data = operations.receive(types_referenceId, remote_transport=transport)

serializer = BaseObjectSerializer()
json_data = serializer.write_json(types_data)

print(json_data)

I hope this is helpful!

Hi @Nikos,

I’m really grateful for your reply, it was very helpful! I actually did not know that models coming from different design tool got represented in different ways, so thanks also for that clarification :smiling_face_with_three_hearts:

Cheers,
Astra :))

2 Likes