DEV Community

Cover image for 3D Spinning Molecule Using Avogadro, Blender and Three.js
Desmond Gilmour
Desmond Gilmour

Posted on

3D Spinning Molecule Using Avogadro, Blender and Three.js

TL;DR

In this blog post, we'll explore how to convert any molecule of interest into an .obj file, which will enable you to create and display molecular animations on your personal website.

Intro

As a chemist and computer scientist, you might find your creativity expressed in various ways. When I come across well-designed tools or personal websites with engaging features, I tend to get excited about what they are doing. I imagine this might help you with the same.

What You Will build

In this blog post, we'll guide you through adding a molecule to your personal site. The molecule displayed here is called epinephrine:

Image description

This will be done in two steps.

  1. We will create the object file based of this youtube video using Avogadro, OpenBabel and final blender.

  2. Implement your new molecule object as a Next.js component.

Getting Started

overing the entire process would result in an excessively long blog post, so we'll rely on a detailed YouTube video for the initial steps. This video will walk you through the creation of the .mol object, and its instructions can easily be adapted to different molecules.

The only deviation from the video is that I exported the fully-created molecule as an '.fbx' file.

Building the component

We'll briefly explain how to build this component before presenting the representative code.

  1. Import necessary dependencies and a 3D model file in FBX format (Epinephrine.fbx).

  2. Create a functional component Molecule that uses the useRef hook to create a reference to a DOM element (the container for the 3D scene).

  3. Use the useEffect hook to set up the Three.js scene when the component is mounted. This hook is called once when the component is mounted, and the clean-up function is called when the component is unmounted.

  4. Inside the useEffect hook, create a new Three.js scene, camera, and renderer. The renderer is added to the container element (retrieved using the containerRef).

  5. Load the 3D object (molecule) using the FBXLoader. Once the object is loaded, add it to the scene, set up lighting (ambient, directional, and point lights), and add the lights to the scene.

  6. Define an animate function that animates the 3D object by rotating it around the Z-axis. The renderer then renders the scene, and the animate function is called again using requestAnimationFrame. This creates an animation loop.

  7. As a clean-up function, remove the renderer DOM element from the container and dispose of the renderer when the component is unmounted.

  8. Render a div element with the reference to containerRef and a specified width and height, which serves as the container for the Three.js scene.

Full Code

import React, { useRef, useEffect } from 'react';
import * as THREE from 'three';
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader';
import molecule from "./Epinephrine.fbx";

export default function Molecule() {
    const containerRef = useRef();

    useEffect(() => {

        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(
            75, 
            containerRef.current.clientWidth / containerRef.current.clientHeight,
            0.1, 
            2000 
        );
        camera.position.z = 625;


        // Create a Three.js renderer
        const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
        renderer.setClearColor(0x000000, 0);
        renderer.setSize(containerRef.current.clientWidth, containerRef.current.clientHeight);

        // Add the renderer to the container
        containerRef.current.appendChild(renderer.domElement);

        // Load the 3D object
        const loader = new FBXLoader();
        loader.load(molecule, (object) => {
            object.rotation.x = Math.PI / 2;
            scene.add(object);
            const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
            scene.add(ambientLight);

            const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.5);
            directionalLight1.position.set(0, 1, 0);
            scene.add(directionalLight1);

            const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.5);
            directionalLight2.position.set(0, -1, 0);
            scene.add(directionalLight2);
            const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
            directionalLight.position.set(-1, 1, 1);
            scene.add(directionalLight);

            const pointLight1 = new THREE.PointLight(0xffffff, 1, 100);
            pointLight1.position.set(-5, 5, 5);
            scene.add(pointLight1);

            const pointLight2 = new THREE.PointLight(0xffffff, 1, 100);
            pointLight2.position.set(5, -5, 5);
            scene.add(pointLight2);

            // Animate the object
            function animate() {
                object.rotation.z += 0.01;
                renderer.render(scene, camera);
                requestAnimationFrame(animate);
            }
            animate();
        });

    // Clean up
        return () => {
            containerRef.current.removeChild(renderer.domElement);
            renderer.dispose();
        };
    }, []);

    return <div ref={containerRef} style={{
        width: '200px', 
        height: '300px',  
    }}/>;
}
Enter fullscreen mode Exit fullscreen mode

If you have any questions or want to discuss this further, feel free to reach out to me through the social media links available on my personal website. I'd be happy to help!

Top comments (0)