This is my second post about this but I’m trying to embed a 3js viewer in my streamlit app and am having trouble. The viewer just doesn’t render. What is the solution to this?
Thanks
import streamlit as st
import streamlit.components.v1 as components
html_string = '''
<h1>HTML</h1>
<script language="javascript">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
let camera, scene, renderer;
let controls, gui;
init();
animate();
function init() {
THREE.Object3D.DEFAULT_UP.set( 0, 0, 1 );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( 2 );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.outputEncoding = THREE.sRGBEncoding;
document.body.appendChild( renderer.domElement );
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( 40, -40, 50 );
scene = new THREE.Scene();
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight( 0xffffff, 2 );
directionalLight.position.set( 20, 40, 100);
scene.add( directionalLight );
const loader = new Rhino3dmLoader();
loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@7.15.0/' );
loader.load( '/upload/output.3dm', function ( object ) {
scene.add( object );
initGUI( object.userData.layers );
// hide spinner
document.getElementById( 'loader' ).style.display = 'none';
} );
controls = new OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
controls.enableDamping = true;
controls.dampingDactor = 0.05;
controls.minDistance = 100;
controls.maxDistance = 500;
controls.maxPolarAngle = Math.PI / 2;
window.addEventListener( 'resize', resize );
function resize() {
const width = window.innerWidth;
const height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize( width, height );
}
function animate() {
controls.update();
renderer.render( scene, camera );
renderer.setPixelRatio(2);
requestAnimationFrame( animate );
}
function initGUI( layers ) {
gui = new GUI({
title: 'test',
});
for ( let i = 0; i < layers.length; i ++ ) {
const layer = layers[ i ];
gui.add( layer, 'visible' ).name( layer.name ).onChange( function ( val ) {
const name = this.object.name;
scene.traverse( function ( child ) {
if ( child.userData.hasOwnProperty( 'attributes' ) ) {
if ( 'layerIndex' in child.userData.attributes ) {
const layerName = layers[ child.userData.attributes.layerIndex ].name;
if ( layerName === name ) {
child.visible = val;
layer.visible = val;
}
}
}
} );
} );
}
}
</script> '''
components.html(html_string)
st.markdown(html_string, unsafe_allow_html=True)
I dont know if this has been answered, but st.markdown doesnt execute javascript code. I believe there is a custom component that does (I will try to find it and edit this comment).
Edit: So I was thinking of another component, but the component.html should execute javascript fine.
I think you forgot to add the threejs script elements. An old project of mine had the following:
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
<script type="module">
import * as THREE from 'https://cdn.skypack.dev/three@0.128.0/build/three.module.js';
import { OrbitControls } from 'https://cdn.skypack.dev/three@0.128.0/examples/jsm/controls/OrbitControls.js';
// rest of code here
</script>
I think this shows two ways to import. You can use cdnjs links in import statement or separate script elements. Basically, there has to be a way to find/get the javascript modules you are referencing
One of the custom components I built has the ability to execute javascript, but its for html presentation slides.
If you didnt already find a solution and still need a component that allows you to execute threeJS code and display result, I could build one pretty easily using code from the aforementioned component.
I dont know how much demand there is for something like this. So let me know.
Thanks for the reply, I tried components, but it didn’t work. I don’t think I am missing any imports because I used this same code in my flask application and it worked fine.
This is what I tried.
import streamlit as st
import streamlit.components.v1 as components
components.html('''
<h1>HTML</h1>
<div id="viewer"></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
<script type="module">
import * as THREE from 'https://cdn.skypack.dev/three@0.128.0/build/three.module.js';
import { OrbitControls } from 'https://cdn.skypack.dev/three@0.128.0/examples/jsm/controls/OrbitControls.js';
import { Rhino3dmLoader } from 'https://cdn.skypack.dev/three/examples/jsm/loaders/3DMLoader.js';
import { GUI } from 'https://cdn.skypack.dev/three/examples/jsm/libs/dat.gui.module.js';
let camera, scene, renderer;
let controls, gui;
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('viewer').appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(40, -40, 50);
scene = new THREE.Scene();
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
directionalLight.position.set(20, 40, 100);
scene.add(directionalLight);
const loader = new Rhino3dmLoader();
loader.setLibraryPath('https://cdn.jsdelivr.net/npm/rhino3dm@7.15.0/');
loader.load('./output.3dm', function (object) {
scene.add(object);
initGUI(object.userData.layers);
});
controls = new OrbitControls(camera, renderer.domElement);
controls.enableZoom = true;
controls.enableDamping = true;
controls.dampingDactor = 0.05;
controls.minDistance = 100;
controls.maxDistance = 500;
controls.maxPolarAngle = Math.PI / 2;
window.addEventListener('resize', resize);
}
function resize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
function initGUI(layers) {
gui = new GUI({ title: 'test' });
for (let i = 0; i < layers.length; i++) {
const layer = layers[i];
gui.add(layer, 'visible').name(layer.name).onChange(function (val) {
const name = this.object.name;
scene.traverse(function (child) {
if (child.userData.hasOwnProperty('attributes')) {
if ('layerIndex' in child.userData.attributes) {
const layerName = layers[child.userData.attributes.layerIndex].name;
if (layerName === name) {
child.visible = val;
layer.visible = val;
}
}
}
});
});
}
}
</script>
''',
height=600)```
According to the console, something is wrong with the imports you added (response code 500). I just tested an example I have to see if I can get ThreeJS working and it works.
The example:
import streamlit as st
import streamlit.components.v1 as components
components.html('''
<style>
*
{
margin: 0;
padding: 0;
}
html,
body
{
overflow: hidden;
min-height: 700px;
}
.webgl
{
position: fixed;
top: 0;
left: 0;
outline: none;
}
</style>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<!--<script src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script> -->
<!-- <script src="http://threejs.org/examples/js/controls/TrackballControls.js"></script> -->
<script type="module">
import * as THREE from 'https://cdn.skypack.dev/three@0.128.0/build/three.module.js';
import { OrbitControls } from 'https://cdn.skypack.dev/three@0.128.0/examples/jsm/controls/OrbitControls.js';
//import { TrackballControls } from 'https://cdn.skypack.dev/three@0.128.0/examples/jsm/controls/TrackballControls.js';
// Base
// ----------
// Initialize scene
const scene = new THREE.Scene()
// Initialize camera
const camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.1, 60)
// Reposition camera
camera.position.set(6, 0, 0)
// Initialize renderer
const renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true
})
// Set renderer size
renderer.setSize(window.innerWidth, window.innerHeight)
// Append renderer to body
document.body.appendChild(renderer.domElement)
// Initialize controls
const controls = new OrbitControls(camera, renderer.domElement)
// World
// ----------
// Load world texture
const worldTexture = new THREE.TextureLoader().load("https://assets.codepen.io/141041/small-world.jpg")
// Initialize world geometry
const worldGeometry = new THREE.SphereGeometry(1, 40, 40)
// Initialize world material
const worldMaterial = new THREE.MeshLambertMaterial({
map: worldTexture
})
// Initialize world
const world = new THREE.Mesh(worldGeometry, worldMaterial)
// Add earth to scene
scene.add(world)
// Clouds
// ----------
// Load clouds texture
const cloudTexture = new THREE.TextureLoader().load("https://assets.codepen.io/141041/small-world-clouds.png")
// Initialize clouds geometry
const cloudGeometry = new THREE.SphereGeometry(1.01, 40, 40)
// Initialize clouds material
const cloudMaterial = new THREE.MeshBasicMaterial({
map: cloudTexture,
transparent: true
})
// Initialize clouds
const clouds = new THREE.Mesh(cloudGeometry, cloudMaterial)
// Add clouds to scene
scene.add(clouds)
// add subtle ambient lighting
const ambientLight = new THREE.AmbientLight(0xbbbbbb);
scene.add(ambientLight);
// directional lighting
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
// Animation
// ----------
// Prepare animation loop
function animate() {
// Request animation frame
requestAnimationFrame(animate)
// Rotate world
world.rotation.y += 0.0005
// Rotate clouds
clouds.rotation.y -= 0.001
// Render scene
renderer.render(scene, camera)
}
// Animate
animate()
// Resize
// ----------
// Listen for window resizing
window.addEventListener('resize', () => {
// Update camera aspect
camera.aspect = window.innerWidth / window.innerHeight
// Update camera projection matrix
camera.updateProjectionMatrix()
// Resize renderer
renderer.setSize(window.innerWidth, window.innerHeight)
});
</script>
<style>
body{
background: radial-gradient(circle at center, white, rgba(113,129,191,0.5) 50%);
}
</style>
''',
height=600)
see if this works for you so that you can identify if there is something wrong with your setup.
I think this comment might provide a solution. Alternatively, you can put the file somewhere in the cloud where you can access it by url and that might be a better solution.
I might just skip this and not pursue this issue further as streamlit currently just lacks the capabilities, but hopefully JS becomes more easily usable within streamlit
I believe the issue you’re facing is that the JavaScript code within the HTML string is not executed properly within the Streamlit app. Streamlit’s components.html function does not automatically execute JavaScript code. To embed a 3D viewer built with Three.js in a Streamlit app, you can follow these steps:
Move the JavaScript code to a separate JavaScript file (e.g., viewer.js). Remove the <script> tags from the HTML string and leave only the contents of the <script> tag.
// viewer.js
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
let camera, scene, renderer;
let controls, gui;
function init() {
// Remaining code...
}
function animate() {
// Remaining code...
}
function initGUI(layers) {
// Remaining code...
}
init();
animate();
Place the JavaScript code in a separate file (viewer.js) in the same directory as your Streamlit app script.
Modify your Streamlit app script to include the viewer.js file using the components.html function.
import streamlit as st
import streamlit.components.v1 as components
html_string = '''
<h1>HTML</h1>
'''
components.html(html_string)
# Load and embed the JavaScript file
with open("viewer.js", "r") as js_file:
js_code = js_file.read()
components.html(js_code)
Ensure the viewer.js file is in the same directory as your Streamlit app script. When the Streamlit app runs, it will embed the viewer.js file using the components.html function, and the JavaScript code will be executed correctly, rendering the 3D viewer.
I hope it helps you to find out the solution, if there is anything in which I can help you can reach out to me.
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking “Accept all”, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.