Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Preparing 3Dtiles for csviewer

Setup the Y2I software stack using docker

  1. Download the latest Y2I release here. Unzip the file. You will see the following folders.

    yun2infinity-x.x.x
    |-- bimserver
    |-- django
    |-- example
    |-- grafana
    |-- jupyterbook
    |-- nginx
    |-- shellscript
    |-- LICENSE
    |-- README.md
  2. Go to the django/yun2inf_project folder.

    cd django/yun2inf_project
    • build the docker container using the ‘Dockerfile’. Install Docker here.

      sudo docker build . -t your-username/yun2inf:x.x.x
  3. Now go to the shellscript/setup_yun2inf.sh and change the container name of the django container

    echo '------------------------------------------------------'
    echo 'Trying to start django container now ...'
    echo '------------------------------------------------------'
    docker run -d --name "$CONTAINERNAME4"\
        -h "$CONTAINERNAME4"\
        --network "yun2inf"\
        -p $YPORT:8000\
        -v "y2i:/yun2inf_project/www/static/"\
        your-username/yun2inf:x.x.x # CHANGE THIS LINE TO THE CONTAINER NAME YOU HAVE BUILT
  4. Execute the setup_yun2inf.sh script using the following command. Use default for all the parameters. If you are using ubuntu you might have to stop apache2 from running using systemctl before executing the setup script.

    sudo systemctl stop apache2
    sudo sh setup_yun2inf.sh
  5. Once the setup is complete you can go to localhost/csviewer to see the 3D view.

Change the 3D tiles

  1. Lets create a simple box using the python script below.

    generate a gltf box
    import numpy as np
    import geomie3d
    import py3dtileslib
    
    from pygltflib import GLTF2, Node, Scene, Mesh, Primitive, Buffer, BufferView, Accessor, Material, ELEMENT_ARRAY_BUFFER, ARRAY_BUFFER, UNSIGNED_SHORT, FLOAT, SCALAR, VEC3
    #=================================================================================================================================
    # region: PARAMETERS
    #=================================================================================================================================
    tileset_path = '/yun2inf/django/yun2inf_project/csviewer/static/3dtiles/example' 
    gltf_respath = '/yun2inf/example/gltf/test.gltf'
    geo_loc = [103.78244865132135, 1.4957790803607318, 5.0] # this corresponds to local model_loc
    model_loc = [0.0, 0.0, 0.0] # corresponding location of the geo-location in the 3d model
    # gltf_respath2 = '/cesiumjs/gltf/test2.gltf'
    # endregion: Parameters
    #=================================================================================================================================
    # region: MAIN
    #=================================================================================================================================
    # region: CREATE A SIMPLE GLTF BOX
    box = geomie3d.create.box(10, 10, 10)
    edges = geomie3d.get.wires_frm_solid(box)
    # geomie3d.viz.viz([{'topo_list':edges, 'colour':'red'}])
    face_list = geomie3d.get.faces_frm_solid(box)
    
    pos_list = []
    nrml_list = []
    idx_list = []
    fid_list = []
    
    prev_idx = 0
    for cnt,f in enumerate(face_list):
        verts_idxs = geomie3d.modify.triangulate_face(f, indices=True)
        verts = verts_idxs[0]
        # idxs = np.flip(verts_idxs[1], 1)
        idxs = verts_idxs[1]
        idxs = idxs.flatten() + prev_idx
        #get the normals of the points
        nrml = geomie3d.get.face_normal(f)
        nrml = np.reshape(nrml, (1,3))
        nrml = np.repeat(nrml, len(verts), axis=0)
        cnt = np.repeat(cnt, len(verts))
        pos_list.extend(verts)
        idx_list.extend(idxs)
        nrml_list.extend(nrml)
        fid_list.extend(cnt)
        # print(verts)
        # print(idxs)
        prev_idx += len(verts)
    
    pos_list = np.array(pos_list)
    nrml_list = np.array(nrml_list)
    idx_list = np.array(idx_list)
    fid_list = np.array(fid_list)
    
    bbox_pos = geomie3d.calculate.bbox_frm_xyzs(pos_list).bbox_arr.tolist()
    bbox_nrml = geomie3d.calculate.bbox_frm_xyzs(nrml_list).bbox_arr.tolist()
    
    pack_pos = py3dtileslib.utils.pack_att(pos_list, '<fff')
    pack_nrml = py3dtileslib.utils.pack_att(nrml_list, '<fff')
    # ----- make sure the index buffer is in multiple of 4 need to be padded ----- 
    is_mltp4 = (len(idx_list)*2)%4
    if is_mltp4 != 0:    
        idx_list_4 = idx_list[:]
        idx_list_4.append(0)
        pack_indices = py3dtileslib.utils.pack_att(idx_list_4, '<H')
    else:
        pack_indices = py3dtileslib.utils.pack_att(idx_list, '<H')
    
    # ----- append them into a single bytearray -----
    data_arr = bytearray()
    data_arr.extend(pack_pos)
    data_arr.extend(pack_nrml)
    data_arr.extend(pack_indices)
    data_bytes = bytes(data_arr)
    
    # create a new gltf file
    gltf = GLTF2()
    gltf.scene = 0
    
    scene = Scene()
    scene.nodes = [0]
    gltf.scenes.append(scene)
    
    node = Node()
    node.mesh = 0
    node.matrix = [1, 0, 0, 0,
                0, 0,-1, 0,
                0, 1, 0, 0,
                0, 0, 0, 1] #column major transformation
    
    gltf.nodes.append(node)
    
    # add data
    buffer = Buffer()
    py3dtileslib.utils.write_buffer(buffer, data_bytes)
    gltf.buffers.append(buffer)
    
    material = Material()
    material.pbrMetallicRoughness = {"baseColorFactor" : [ 0.3, 0.3, 0.3, 1.0 ], "metallicFactor" : 0.0, "roughnessFactor" : 1.0}
    material.alphaMode = 'OPAQUE'
    material.doubleSided = True
    gltf.materials.append(material)
    
    mesh = Mesh()
    primitive = Primitive()
    primitive.indices = 2 #index of the accessor
    primitive.attributes.POSITION = 0 #index of the accessor
    primitive.attributes.NORMAL = 1 #index of the accessor
    primitive.material = 0 # index of the material
    mesh.primitives.append(primitive)
    gltf.meshes.append(mesh)
    
    #this view is for both the nrml and the pos
    bufferView1 = BufferView()
    bufferView1.buffer = 0
    bufferView1.byteOffset = 0
    bufferView1.byteStride = 12
    bufferView1.byteLength = len(pack_pos) + len(pack_nrml)
    bufferView1.target = ARRAY_BUFFER 
    gltf.bufferViews.append(bufferView1)
    #this view is for the indices
    bufferView2 = BufferView()
    bufferView2.buffer = 0
    bufferView2.byteOffset = len(pack_pos) + len(pack_nrml)
    bufferView2.byteLength = len(pack_indices)
    bufferView2.target = ELEMENT_ARRAY_BUFFER 
    gltf.bufferViews.append(bufferView2)
    
    # ----- this accessor is for the position -----
    accessor1 = Accessor()
    accessor1.bufferView = 0
    accessor1.byteOffset = 0
    accessor1.componentType = FLOAT
    accessor1.count = len(pos_list)
    accessor1.type = VEC3
    accessor1.max = [bbox_pos[3], bbox_pos[4], bbox_pos[5]]
    accessor1.min = [bbox_pos[0], bbox_pos[1], bbox_pos[2]]
    gltf.accessors.append(accessor1)
    
    #this accessor is for the nrml
    accessor2 = Accessor()
    accessor2.bufferView = 0
    accessor2.byteOffset = len(pack_pos)
    accessor2.componentType = FLOAT
    accessor2.count = len(nrml_list)
    accessor2.type = VEC3
    accessor2.max = [bbox_nrml[3], bbox_nrml[4], bbox_nrml[5]]
    accessor2.min = [bbox_nrml[0], bbox_nrml[1], bbox_nrml[2]]
    gltf.accessors.append(accessor2)
    #this accessor is for the indices
    accessor3 = Accessor()
    accessor3.bufferView = 1
    accessor3.byteOffset = 0
    accessor3.componentType = UNSIGNED_SHORT
    accessor3.count = len(idx_list)
    accessor3.type = SCALAR
    accessor3.max = [int(max(idx_list))]
    accessor3.min = [0]
    gltf.accessors.append(accessor3)
    
    gltf.save(gltf_respath)
    # endregion: create a simple gltf box
    #---------------------------------------------------------------------------------------------------------------------------------
    # region: CREATE A GLTF with EXT_mesh_features and EXT_structural_metadata
    # read the gltf file of interest
    gltf = GLTF2().load(gltf_respath)
    # this gltf file only have 1 scene, 1 node, 1 mesh and 1 primitive, the primitive is a box of 1m x 1m x 1m
    # lets id the box based on each surface which has 4 vertices
    prim = gltf.meshes[0].primitives[0]
    prim_verts = py3dtileslib.utils.get_pos_frm_primitive(prim, gltf)
    fid_list = []
    id = 0
    for cnt,pv in enumerate(prim_verts):
        if cnt%4 == 0 and cnt != 0:
            id += 1
        fid_list.append(id)
    
    # add EXT_mesh_features onto the gltf
    py3dtileslib.mesh_features.add_extmeshfeatures(gltf)
    # add the EXT_mesh_features onto the primitive
    py3dtileslib.mesh_features.add_extmeshfeatures_by_vertex(prim, gltf, fid_list)
    #---------------------------------------------------------------------------------------------------------------------------------
    # add CESIUM_primitive_outline onto the gltf
    buffer_indx = py3dtileslib.cesium_prim_outline.add_cs_prim_outline(gltf) # make sure the buffer index is not None, if None means already got existing extension
    py3dtileslib.cesium_prim_outline.add_outline2prim(prim, gltf, buffer_indx)
    
    #---------------------------------------------------------------------------------------------------------------------------------
    # add EXT_structural_metadata to the gltf
    # define and create the classes in the EXT_structural_metadata
    class_name = 'example_class'
    classes = py3dtileslib.struct_metadata.create_classes(class_name, 'this is an example class')
    #---------------------------------------------------------------------------------------------------------------------------------
    # define and create the properties
    prop_id1 = 'example_string'
    prop_id2 = 'example_float'
    prop_id3 = 'example_enum'
    enumtpye = 'example_enum'
    
    str_prop = py3dtileslib.struct_metadata.create_classes_prop('example_str', 'example string property', 'STRING')
    float_prop = py3dtileslib.struct_metadata.create_classes_prop('example_float', 'example float property', 'SCALAR', comptype = 'FLOAT32')
    enum_prop = py3dtileslib.struct_metadata.create_classes_prop('example_enum', 'example enum property', 'ENUM', enumtype = enumtpye)
    
    py3dtileslib.struct_metadata.add_property2classes(classes, prop_id1, str_prop)
    py3dtileslib.struct_metadata.add_property2classes(classes, prop_id2, float_prop)
    py3dtileslib.struct_metadata.add_property2classes(classes, prop_id3, enum_prop)
    #---------------------------------------------------------------------------------------------------------------------------------
    # create the enums
    enum_dict_ls = [{'name': 'this0', 'value': 0}, {'name': 'this1', 'value': 1}, {'name': 'this2', 'value': 2},
                    {'name': 'this3', 'value': 3}, {'name': 'this4', 'value': 4}, {'name': 'this5', 'value': 5}]
    enum = py3dtileslib.struct_metadata.create_enum('example_enum', 'example of enums', enum_dict_ls)
    enums = {}
    py3dtileslib.struct_metadata.add_enum2enums(enums, enumtpye, enum)
    #---------------------------------------------------------------------------------------------------------------------------------
    # change the metadata into buffers
    buffer_data = bytearray()
    example_string_list = ['surface10', 'surface2', 'surface300', 'surface4', 'surface50', 'surface06']
    packed_string, offset = py3dtileslib.utils.pack_att_string(example_string_list)
    packed_offset = py3dtileslib.utils.pack_att(offset, '<l')
    
    example_float_list = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]
    packed_float = py3dtileslib.utils.pack_att(example_float_list, '<f')
    
    example_enum_list = [0, 1, 2, 3, 4, 5]
    packed_enum = py3dtileslib.utils.pack_att(example_enum_list, '<H')
    
    buffer2 = Buffer()
    buffer_data.extend(packed_string)
    buffer_data.extend(packed_offset)
    buffer_data.extend(packed_float)
    buffer_data.extend(packed_enum)
    py3dtileslib.utils.write_buffer(buffer2, buffer_data)
    gltf.buffers.append(buffer2)
    #---------------------------------------------------------------------------------------------------------------------------------
    # create bufferviews for the metadata
    buffer_view_meta1 = BufferView()
    buffer_view_meta1.buffer = 2
    buffer_view_meta1.byteOffset = 0
    buffer_view_meta1.byteLength = len(packed_string)
    gltf.bufferViews.append(buffer_view_meta1)
    
    buffer_view_meta2 = BufferView()
    buffer_view_meta2.buffer = 2
    buffer_view_meta2.byteOffset = len(packed_string)
    buffer_view_meta2.byteLength = len(packed_offset)
    gltf.bufferViews.append(buffer_view_meta2)
    
    buffer_view_meta3 = BufferView()
    buffer_view_meta3.buffer = 2
    buffer_view_meta3.byteOffset = len(packed_string) + len(packed_offset)
    buffer_view_meta3.byteLength = len(packed_float)
    gltf.bufferViews.append(buffer_view_meta3)
    
    buffer_view_meta4 = BufferView()
    buffer_view_meta4.buffer = 2
    buffer_view_meta4.byteOffset = len(packed_string) + len(packed_offset) + len(packed_float)
    buffer_view_meta4.byteLength = len(packed_enum)
    gltf.bufferViews.append(buffer_view_meta4)
    #---------------------------------------------------------------------------------------------------------------------------------
    # create the property table in the EXT_structural_metadata
    prop_table = py3dtileslib.struct_metadata.create_prop_table('example property table', class_name, 6)
    py3dtileslib.struct_metadata.add_table_property(prop_table, prop_id1, len(gltf.bufferViews)-4, len(gltf.bufferViews)-3)
    py3dtileslib.struct_metadata.add_table_property(prop_table, prop_id2, len(gltf.bufferViews)-2)
    py3dtileslib.struct_metadata.add_table_property(prop_table, prop_id3, len(gltf.bufferViews)-1)
    
    #---------------------------------------------------------------------------------------------------------------------------------
    # add the classes and property table into the ext_structural_metadata extension 
    py3dtileslib.struct_metadata.add_extstructmetadata(gltf, 'example_schema', classes, [prop_table], enums=enums)
    sel_featureid = gltf.meshes[0].primitives[0].extensions['EXT_mesh_features']['featureIds'][0]
    py3dtileslib.struct_metadata.add_prop_table2featureid(0, sel_featureid)
    # gltf.save(gltf_respath2)
    # endregion: CREATE A GLTF with EXT_mesh_features and EXT_structural_metadata
    
    #---------------------------------------------------------------------------------------------------------------------------------
    # region: CREATE A SIMPLE TILESET
    # create the tileset 
    tileset = py3dtileslib.Tileset(tileset_path, 1.1)
    root_node = py3dtileslib.Node('root')
    
    mat = np.array([[1, 0, 0, 0],
                    [0, 1, 0, 0],
                    [0, 0, 1, 0],
                    [0, 0, 0, 1]])
    
    pos_list = py3dtileslib.utils.get_pos_frm_gltf(gltf)
    pos_list = geomie3d.calculate.trsf_xyzs(pos_list, mat)
    bbox = geomie3d.calculate.bbox_frm_xyzs(pos_list)
    midpt_xyz = geomie3d.calculate.bbox_centre(bbox)
    
    xdim = bbox.maxx - bbox.minx
    ydim = bbox.maxy - bbox.miny
    zdim = bbox.maxz - bbox.minz
    bbox = py3dtileslib.utils.define_bbox(midpt_xyz, xdim, ydim, zdim)
    
    # if model_loc is not [0,0,0] we have to transfer the model_loc to the model origin
    trsl_mat = geomie3d.calculate.translate_matrice(0-model_loc[0], 0-model_loc[1], 0-model_loc[2])
    trsf_mat = py3dtileslib.utils.compute_trsfmat4enu_frm_gcs_coord(geo_loc)
    trsf_mat = trsf_mat@trsl_mat
    trsf_mat = trsf_mat.T.flatten().tolist()
    root_node.add_transform(trsf_mat)
    
    bbox = py3dtileslib.utils.compute_tile_bbox(pos_list)
    root_node.add_box(bbox)
    root_node.add_error(0.0)
    root_node.edit_refine('REPLACE')
    root_node.add_tile_content(gltf)
    
    tileset.add_root(root_node)
    tileset.add_error(10.0)
    tileset.to_tileset()
    
    # endregion: CREATE A SIMPLE TILESET
    # endregion: MAIN
    #=================================================================================================================================
  2. Copy the generated 3dtiles file into the static folder of the django container.

    sudo docker cp simplebox/ yun2inf_proj:/yun2inf_project/csviewer/static/3dtiles/
  3. Once you have generated the 3Dtiles. Go to the yun2inf_project/csviewer/views.py on your local computer and change the following function to point to the right url of where you are going to put the static 3d files in the django app.

    def tiles_url(request):
        url = "http://localhost/static/3dtiles/simplebox/tileset.json"
        return JsonResponse({"tiles_url": url})
  4. Copy the view.py file into the yun2inf_proj container.

    sudo docker cp views.py yun2inf_proj:/yun2inf_project/csviewer/views.py
  5. Restart both the yun2inf_proj and yun2inf_nginx container. You will see that the 3D scene has changed to the new generated box.

    sudo docker restart yun2inf_proj
    sudo docker restart yun2inf_nginx
  6. For processing GLB exported from FreeCAD, please refer to the example script in yun2infinity-x.x.x/example/write3dtiles1.1/write_freecad_gltftile.py

    • you can change the paramters in the script and use it to process the .glb exported from your freecad file. Follow step 2-5 in this section to visualize the file in your app.