Welcome to the Onshape forum! Ask questions and join in the discussions about everything Onshape.

First time visiting? Here are some places to start:
  1. Looking for a certain topic? Check out the categories filter or use Search (upper right).
  2. Need support? Ask a question to our Community Support category.
  3. Please submit support tickets for bugs but you can request improvements in the Product Feedback category.
  4. Be respectful, on topic and if you see a problem, Flag it.

If you would like to contact our Community Manager personally, feel free to send a private message or an email.

What do "transforms" and their "paths" mean in the API?

tom_nicksontom_nickson Member Posts: 2
I'm playing around with the API and trying to understand what "transform" means in the occurrences list. I've put some Python at the bottom, but what I'm currently doing is:

* Find all part instances in the assembly/sub assembly and get their STLs
* Iterate over the occurrences in the root assembly, apply the transform to the STL (using numpy stl, literally mesh.transform(t))
* Draw a picture and see if it looks right

I can't find any docs on what "transform" means. There seems to be a transforms for both parts and subAssemblies, and some elements (only parts?) in occurrences have a multi-step "path" defined, which seems to go through their assembly. My first guess at what to do with these was that I had to chain the transforms, however actually I can just use the given transform to get stuff into the right places.

So: do I need to worry about the "path" stuff at all? Will ignoring it come back to bite me?

Python code to draw a picture:

```
import stl
import logging
from typing import Any
import tqdm
from matplotlib import pyplot as plt
from mpl_toolkits import mplot3d
import numpy as np
import requests
import base64
import io


did = 'f04c564452bd0cb296c72c67'
wvm = 'w'
wvmid = '04e05af0f1e879340a28c118'
eid = '18393f3e87087c3e57b64373'

def render(figure, axes, stl):
    poly3d = mplot3d.art3d.Poly3DCollection(stl.vectors)
    poly3d.set_edgecolor('k')
    axes.add_collection3d(poly3d)
    scale = stl.points.flatten()
    axes.auto_scale_xyz(scale, scale, scale)

def auth_headers():
    auth_string = f"{access_key}:{secret_key}".encode('utf-8')
    auth = base64.b64encode(auth_string).decode('utf-8')
    return {
        'Authorization': f'Basic {auth}'
    }

def get_stl(did, wvm, wvmid, eid, pid):
    sub_url = f"parts/d/{did}/{wvm}/{wvmid}/e/{eid}/partid/{pid}/stl"
    url = base_url.format(endpoint=sub_url)
    params = {
        "mode": "binary",
        "grouping": True,
        "scale": 1,
        "units": "meter"
    }
    response = requests.get(url,
                       params=params,
                       headers=auth_headers(),
                       allow_redirects=False)
    response.raise_for_status()
    if response.is_redirect:
        url = response.headers['Location']
        response = requests.get(url,
                       params=params,
                       headers=auth_headers())
        response.raise_for_status()
    return stl.Mesh.from_file("part", fh=io.BytesIO(response.content))


params = {"includeMateFeatures": True, "includeMateConnectors": True, "includeNonSolids": True}
sub_url = f"assemblies/d/{did}/{wvm}/{wvmid}/e/{eid}"
url = base_url.format(endpoint=sub_url)
assembly_info = requests.get(url, headers=auth_headers(), params=params).json()

# find all instances in this assembly and split if they're parts
instances = assembly_info['rootAssembly']['instances'] + [el for sub in assembly_info['subAssemblies'] for el in sub['instances']]
parts = [el for el in instances if el['type'] == 'Part']

# process parts lists: lookup for occurences, get stls
occ2part = {}
for p in parts:
    p['stl'] = get_stl(did=p['documentId'],
                       wvm='m',
                       wvmid=p['documentMicroversion'],
                       eid=p['elementId'], 
                       pid=p['partId'])
    occ2part[p['id']] = p

transforms = {}
occurences = {}
for occ in assembly_info['rootAssembly']['occurrences']:
    if len(occ['path']) == 1:
        transform = np.array(occ['transform']).reshape(4,4)
        if occ['path'][0] not in occ2part:
            transforms[occ['path'][0]] = transform
            continue
    elif len(occ['path']) == 2:
        t1 = transforms[occ['path'][0]]
        t2 = np.array(occ['transform']).reshape(4,4)
        # transform = t1 @ t2
        transform = t2
    else:
        raise ValueError()    
    if part := occ2part.get(occ['path'][-1]):
        occurences[tuple(occ['path'])] = (part['stl'], transform)

f, ax = plt.subplots(1,1, subplot_kw={'projection':'3d'}, figsize=(10,10))
for part, transform in occurences.values():
    mesh = stl.mesh.Mesh(part.data.copy())
    mesh.transform(transform)
    render(f, ax, mesh)
```

Comments

  • Paul_J_PremakumarPaul_J_Premakumar Member, Onshape Employees Posts: 220
    @tom_nickson

     Your observation is correct. Onshape only looks at the transformation matrix of the instances  (leaf nodes if you will) and not the sub-assemblies. The Path simply, provides information on path to the instance from the root. The transformation is not cumulative. You can ignore the path in your transformation calculations.

    Paul
Sign In or Register to comment.