Passing arguments from Streamlit to JavaScript

Hi everyone,
I am trying to set up an IFCjs viewer inside of Streamlit.
I tried this on a simple example of an array of URLs, each one pointing to a specific IFC model saved on the web.
Using st.selectbox the user can pick a specific model, on selection the URL is passed to JavaScript which initialises the IFCjs viewer and opens the 3D model.

Here is the Homepage.py:

import streamlit as st
from ifc_viewer.ifc_viewer import ifc_viewer

# List of URLs
url_list = ["https://cropka.com/TEST.ifc", "http://example2.com", "http://example3.com"]

# Dropdown selector for URLs
selected_url = st.selectbox("Select URL", options=url_list)

# Call the IFC viewer with the selected URL
ifc_viewer(selected_url)

In the “ifc_viewer” folder the following code handles passing of the URL to Java:

import streamlit as st
from streamlit.components.v1 import declare_component

# Declare the component
_ifc_viewer = declare_component("ifc_viewer", path="ifc_viewer")

def ifc_viewer(url: str):
    # Call the component and pass the url
    _ifc_viewer(url=url)

However in my JavaScript I am clueless why this is not being received:

import { Color } from "three";
import { IfcViewerAPI } from "web-ifc-viewer";

console.log("Streamlit object: ", window.Streamlit);

const Streamlit = window.Streamlit;
const container = document.getElementById('viewer-container')
const viewer = new IfcViewerAPI({container, backgroundColor: new Color(0xffffff)});
viewer.axes.setAxes();
viewer.grid.setGrid();

async function loadIfc(url) {
    // Load the model
    const model = await viewer.IFC.loadIfcUrl(url);

    // Now that the model is loaded, tell Streamlit that the component is ready
    Streamlit.setComponentReady();
}

Streamlit.onRender((data) => {
    const url = data.args.url;
    loadIfc(url);
});

Clearly the problem I get in the console is that window.Streamlit is undefined. How can I receive the URL on JavaScript’s end? Any ideas much appreciated!

After applying and tweaking the snippet (Code snippet: create Components without any frontend tooling (no React, Babel, Webpack, etc)) I came up with this code:


import { Color } from "three";
import { IfcViewerAPI } from "web-ifc-viewer";

function sendMessageToStreamlitClient(type, data) {
    const message = { ...data, isStreamlitMessage: true, type };
    window.parent.postMessage(message, "*");
}

function init() {
    sendMessageToStreamlitClient("streamlit:componentReady", {apiVersion: 1});
}

function setFrameHeight(height) {
    sendMessageToStreamlitClient("streamlit:setFrameHeight", {height});
}

const viewer = new IfcViewerAPI({container: document.getElementById('viewer-container'), backgroundColor: new Color(0xffffff)});
viewer.axes.setAxes();
viewer.grid.setGrid();

async function loadIfc(url) {
    const model = await viewer.IFC.loadIfcUrl(url);
    await viewer.shadowDropper.renderShadow(model.modelID);
    viewer.context.renderer.postProduction.active = true;
}

function onDataFromPython(event) {
    if (event.data.type !== "streamlit:render") return;
    const url = event.data.args.url;
    if(url){
        loadIfc(url);
    }
}

// Hook things up!
window.addEventListener("message", onDataFromPython);
init();

// Hack to autoset the iframe height.
window.addEventListener("load", function() {
    window.setTimeout(function() {
        setFrameHeight(document.documentElement.clientHeight)
    }, 0);
});


According to console my model is being opened (parsing usinf IFC4 Schema) but no IFCjs viewer is being displayed inside streamlit. I have tested the viewer running it independently in Live Server and it is fine. It appears that the last function:

window.addEventListener("load", function() {
    window.setTimeout(function() {
        setFrameHeight(document.documentElement.clientHeight)
    }, 0);
});

Is not loading correctly. I can get the viewer to appear only if I provide hard coded height in setFrameHeight, e.g. setFrameHeight(300), but I cannot figure out how to autoset iframe height.

Hello,

Please take a look here:

1 Like

Hi Felipe,
thanks for the link. IFC-101 is an amazing tutorial, I know this by heart now :wink:
This works perfectly for a localhost application but not for StreamlitCloud deployment. Also the use of an ArrayBuffer means you have to use web-ifc-three instead of web-ifc-viewer, which is adding a lot of unnecessary work. With the URL solution you have the full power of IFCjs at your finger tips.
In the meantime I have just set the frame height manually and all is working great, the URL gets passed as intended. Also a little hint - the IFCs I am using are now located on Google Cloud Storage, which can generate URLs, this makes it much easier to pass them between streamlit and the IFCjs viewer.

1 Like