Skip to content
Cyrus Harrison edited this page Dec 12, 2022 · 6 revisions

parse binary stl file and convert to blueprint

import conduit


def parse_binary(data):
    res = conduit.Node()
    # todo: optional header parse
    # read # of triangles
    tmp = conduit.Node()
    stmp = conduit.Schema()
    stmp.set(conduit.DataType.uint32(1,80))
    tmp.set_external(stmp,data)
    ntris = tmp.value()
    # setup mesh bp
    # coords
    res["coordsets/coords/type"] = "explicit"
    res["coordsets/coords/values/x"].set(conduit.DataType.float64(ntris * 3))
    res["coordsets/coords/values/y"].set(conduit.DataType.float64(ntris * 3))
    res["coordsets/coords/values/z"].set(conduit.DataType.float64(ntris * 3))
    x_vals = res["coordsets/coords/values/x"]
    y_vals = res["coordsets/coords/values/y"]
    z_vals = res["coordsets/coords/values/z"]
    # topo
    res["topologies/topo/type"] = "unstructured"
    res["topologies/topo/coordset"] = "coords"
    res["topologies/topo/elements/shape"] = "tri"
    res["topologies/topo/elements/connectivity"].set(conduit.DataType.uint32(ntris * 3))
    conn_vals = res["topologies/topo/elements/connectivity"]

    # also expose coordset as point mesh
    res["topologies/topo_pts/type"] = "points"
    res["topologies/topo_pts/coordset"] = "coords"

    # fields
    # normals (vec)
    res["fields/normal/association"] = "element"
    res["fields/normal/topology"] = "topo"
    res["fields/normal/values/x"].set(conduit.DataType.float64(ntris))
    res["fields/normal/values/y"].set(conduit.DataType.float64(ntris))
    res["fields/normal/values/z"].set(conduit.DataType.float64(ntris))
    nx_vals = res["fields/normal/values/x"]
    ny_vals = res["fields/normal/values/y"]
    nz_vals = res["fields/normal/values/z"]

    # attrib (store as 32-bit int)
    res["fields/attrib/association"] = "element"
    res["fields/attrib/topology"]    = "topo"
    res["fields/attrib/values"].set(conduit.DataType.int64(ntris))
    attr_vals = res["fields/attrib/values"]

    #
    # start reading tris
    #
    byte_offset = 84
    print("number of triangles {0}".format(ntris))
    for i in range(ntris):
        # per tri schema
        stmp = conduit.Schema()
        stmp["normal"].set(conduit.DataType.float32(3,byte_offset))
        stmp["v1"].set(conduit.DataType.float32(3,byte_offset + 12))
        stmp["v2"].set(conduit.DataType.float32(3,byte_offset + 24))
        stmp["v3"].set(conduit.DataType.float32(3,byte_offset + 36))
        stmp["attr"].set(conduit.DataType.uint16(1,byte_offset + 48))
        tmp.set_external(stmp,data)
        byte_offset += stmp.total_bytes_compact()

        # add verts to coordset
        tri_offset = i*3
        x_vals[tri_offset] = tmp["v1"][0]
        y_vals[tri_offset] = tmp["v1"][1]
        z_vals[tri_offset] = tmp["v1"][2]

        x_vals[tri_offset+1] = tmp["v2"][0]
        y_vals[tri_offset+1] = tmp["v2"][1]
        z_vals[tri_offset+1] = tmp["v2"][2]

        x_vals[tri_offset+2] = tmp["v3"][0]
        y_vals[tri_offset+2] = tmp["v3"][1]
        z_vals[tri_offset+2] = tmp["v3"][2]

        # add entry to topo
        conn_vals[tri_offset + 2 ] = tri_offset 
        conn_vals[tri_offset + 1 ] = tri_offset + 1
        conn_vals[tri_offset + 0 ] = tri_offset + 2

        # add atts to atts field
        attr_vals[i] = tmp["attr"]
        # add normal to normals field
        nx_vals[i] = tmp["normal"][0]
        ny_vals[i] = tmp["normal"][1]
        nz_vals[i] = tmp["normal"][2]
    print("done parsing")
    return res

def check_stl_style(data):
    n = conduit.Node()
    s = conduit.Schema()
    s.set(conduit.DataType.char8_str(80))
    n.set(s,data)
    sval = n.value()
    res = conduit.Node()
    if sval.count("shape") > 0:
        return "ascii"
    else:
        return "binary"

def parse_stl(fname,ofname):
    print(fname)
    with open(fname,"rb")as f:
        data = f.read()
        print("done reading")
        print(len(data))
        # read the first 80 bytes, first as string to 
        # test if we have ascii or binary stl file
        ftype = check_stl_style(data)
        if ftype == "ascii":
            print("ascii, not supported yet")
            return
            #res = parse_ascii(fname)
        else:
            print("binary")
            res= parse_binary(data)
        if not res.dtype().is_empty():
            conduit.relay.io.blueprint.save_mesh(res, ofname,"hdf5")

helper to convert a bov (bof) file to a mesh bp uniform mesh

import conduit
import conduit.blueprint
import conduit.relay
import numpy as np

def bov_to_blueprint(fname):
    res = conduit.Node()
    ls = open(fname).readlines()
    res["coordsets/coords/type"] = "uniform";
    dinfo = conduit.Node()
    # parse meta data lines
    for l in ls:
        if not l.strip() == "" and not l.strip().startswith("#"):
            key, val = l.strip().split(":") 
            print(key,val)
            # entries we care about:
            if key == "DATA FILE":
                # read data
                pass
            elif key == "DATA SIZE":
                # shape of grid
                vals = [ int(v) for v in val.split()]
                res["coordsets/coords/dims/i"] = vals[0]
                dinfo["npts"] = vals[0]
                dinfo["neles"] = vals[0]-1
                if len(vals) > 1:
                    res["coordsets/coords/dims/j"] = vals[1]
                    dinfo["npts"]  *= vals[1]
                    dinfo["neles"] *= vals[1]-1
                if len(vals) > 2:
                    res["coordsets/coords/dims/k"] = vals[2]
                    dinfo["npts"]  *= vals[2]
                    dinfo["neles"] *= vals[2]-1
            elif key == "BRICK ORIGIN":
                # where we start in space
                vals = [ float(v) for v in val.split()]
                res["coordsets/coords/origin/x"] = vals[0]
                if len(vals) > 1:
                    res["coordsets/coords/origin/y"] = vals[1]
                if len(vals) > 2:
                    res["coordsets/coords/origin/z"] = vals[2]
            elif key == "BRICK SIZE":
                # use to get dx,dy,dz
                # note, this assumes that DATA SIZE
                # is presented before we parse this info
                vals = [ float(v) for v in val.split()]
                res["coordsets/coords/spacing/dx"] = vals[0]/ float(res["coordsets/coords/dims/i"]-1)
                if len(vals) > 1:
                    res["coordsets/coords/spacing/dy"]= vals[1]/ float(res["coordsets/coords/dims/j"]-1)
                if len(vals) > 2:
                    res["coordsets/coords/spacing/dz"]= vals[2]/ float(res["coordsets/coords/dims/k"]-1)
            elif key == "DATA_FILE":
                dinfo["file"] = val.strip()
            elif key == "DATA FORMAT":
                dinfo["format"] = val.strip().lower()
            elif key == "VARIABLE":
                if val.count('"') > 0:
                    val = val.replace('"','')
                if val.count('/') > 0:
                    val = val[val.find("/"):]
                dinfo["name"] = val
    # describe our topo
    res["topologies/topo/type"] = "uniform"
    res["topologies/topo/coordset"] = "coords"
    # now read the data and create a field
    np_dtype = np.float64
    if dinfo["format"] == "floats":
        np_dtype = np.float32
    val_array =np.fromfile(dinfo["file"],np_dtype)
    n_field = res["fields"][dinfo["name"]]
    n_field["association"]="vertex"
    n_field["topology"]="topo"
    n_field["values"]= val_array
    info = conduit.Node()
    if not conduit.blueprint.mesh.verify(res,info):
        print("Mesh Verify Failed!")
        print(info.to_yaml())
    return res

Python to create blueprint json file with a uniform mesh, readable by VisIt's Blueprint plugin. (VisIt 3.0)

import conduit
import conduit.blueprint
import numpy as np

ofname = "basic_uniform.root"
protocol = "json"

n = conduit.Node()
n["mesh/coordsets/coords/type"] = "uniform"
n["mesh/coordsets/coords/dims/i"] = 21
n["mesh/coordsets/coords/dims/j"] = 31
n["mesh/topologies/topo/type"] = "uniform"
n["mesh/topologies/topo/coordset"] = "coords"
n["mesh/fields/vals/association"] = "element"
n["mesh/fields/vals/topology"] = "topo"
n["mesh/fields/vals/values"] = np.linspace(0.0, 20 * 30, num=(20 * 30))

conduit.blueprint.mesh.generate_index(n["mesh"],
                                      "mesh",
                                      1,
                                      n["blueprint_index/mesh"])

n["protocol/name"] = protocol
n["protocol/version"] = "0.4.0";
n["number_of_files"] = 1;
n["number_of_trees"] = 1;
n["file_pattern"] = ofname;
n["tree_pattern"] = "/";

n.save(ofname,protocol)