Hi Everyone,
When going over this forum and Reddit trying to find a way to display my 3D models in my Notes Without needing to host them online and use an website Embed. I found that there were currently no real ways of doing so.
So I decided to try something myself with the help of the dataviewjs plugin, and a base knowledge of the three.js library. My script currently supports .stl and .glb 3D models, allows you to use simple OrbitControls around the model, such as zoom in and out, or rotation. And allows for some basic manipulation via the code block such as scale and start rotation. I will expand it, and perhaps turn it into a plugin if I feel the need.
Watch out the following information is all case Sensitive!
You can achieve this by having the dataview plugin installed and adding the following codeblock in your note:
await dv.view("threejsinobsidian",{
el: dv,
name: "FloatingWizardTower.glb",
rotationX: 0,
rotationY: 0,
rotationZ: 0,
scale: 0.5,
});
Make a folder in your vault called “3DModels” in which you place your 3D models. Then in the codeblock shown above change the name variable to the name of the model.
In the plugin settings change the following to true:
Then make a new folder called “Scripts”, and add a js file called threejsinobsidian.js
in there with the following code:
// You always receive an input object
console.log("Succesfully loaded script")
console.log("Inputs received: " + input);
// Sets a div to the container that is provided by dataviewjs
const div = input.el.container;
// Gets the correct path to your model file within the vault
const modelPath = app.vault.adapter.getResourcePath("3DModels/" + input.name);
const modelType = input.name.substr(input.name.length - 3);
const inputRotationX = input.rotationX;
const inputRotationY = input.rotationY;
const inputRotationZ = input.rotationZ;
const scale = input.scale;
runThreeJs()
// Function to load external scripts
function loadScript(url, callback) {
const script = document.createElement('script');
script.src = url;
script.onload = callback;
script.onerror = () => console.error(`Failed to load script: ${url}`);
document.head.appendChild(script);
}
function runThreeJs() {
// Load Three.js, GLTFLoader and Orbitcontrols from the correct CDN
loadScript('https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js', () => {
console.log('Three.js loaded');
loadScript('https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js', () => {
console.log('OrbitControls loaded');
threeJS(modelType);
}, undefined, (error) => {
console.error('An error happened while loading the OrbitControls', error);
});
}, undefined, (error) => {
console.error('An error happened while loading the Three.js package', error);
});
}
function threeJS(modelExtension) {
// Set up scene
const scene = new THREE.Scene();
scene.background = new THREE.Color( 0xADD8E6 );
// Camera setup (ensure it's properly looking at the scene)
const camera = new THREE.PerspectiveCamera(75, div.clientWidth / 300, 0.1, 1000);
camera.position.z = 10;
// Create a renderer
const renderer = new THREE.WebGLRenderer();
renderer.setSize(div.clientWidth, 300); // Set the renderer size to fit the div
div.appendChild(renderer.domElement);
// Add a basic light source (e.g., directional light)
const light = new THREE.DirectionalLight(0xffffff, 1); // White light, full intensity
light.position.set(5, 10, 5); // Position the light
scene.add(light); // Add the light to the scene
// Optionally, add ambient light for more balanced lighting
const ambientLight = new THREE.AmbientLight(0x404040, 0.5); // Dim ambient light
scene.add(ambientLight);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
if (modelExtension == "stl") {
loadScript('https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/STLLoader.js', () => {
console.log('STLLoader loaded');
const loader = new THREE.STLLoader();
loader.load(modelPath, (geometry) => {
//const material = new THREE.MeshStandardMaterial({ color: 0x606060 });
const material = new THREE.MeshPhongMaterial({ color: 0x606060, shininess: 100 });
//const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const model = new THREE.Mesh(geometry, material);
model.scale.set(scale, scale, scale); // Adjust the scale if needed
model.rotation.x = THREE.Math.degToRad(inputRotationX);
model.rotation.y = THREE.Math.degToRad(inputRotationY);
model.rotation.z = THREE.Math.degToRad(inputRotationZ);
scene.add(model);
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera); // Render the scene from the camera's perspective
}
animate();
}, undefined, (error) => {
console.error('An error happened while loading the model', error);
});
});
} else if (modelExtension == "glb") {
loadScript('https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.js', () => {
console.log('GLTFLoader loaded');
const loader = new THREE.GLTFLoader();
loader.load(modelPath, (gltf) => {
console.log('Model loaded');
const model = gltf.scene;
model.scale.set(scale, scale, scale);
model.rotation.x = THREE.Math.degToRad(inputRotationX);
model.rotation.y = THREE.Math.degToRad(inputRotationY);
model.rotation.z = THREE.Math.degToRad(inputRotationZ);
// Add the model to the scene
scene.add(model);
//const controls = new THREE.OrbitControls(camera, renderer.domElement);
// Animate and render the scene
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera); // Render the scene from the camera's perspective
}
animate();
}, undefined, (error) => {
console.error('An error happened while loading the model', error);
});
});
}
renderer.render(scene, camera);
}
This should now display an object in your note! If it does not work, try to use the inspect element in obsidian to see if errors are logged. (Ctrl+Shift+i). See if all names of files are correct. And if that still doesnt fix it try it with another 3D model. And if you still have issues please leave a reply.
I hope this helps some people!