Loading files into viewer by clicking on link in popup

Hi I am currently using a script that uses Unity Model Viewer with React. The script was created by someone else. Not being a seasoned coder I have managed to customise the script for myself but would like to replace the viewer with Babylon. This is what I would like to do:

  1. select a link from the side navbar list.
  2. the link opens a popup that displays thumbnail of model/s in a specified folder.
  3. click on one of the thumbnails and add it to the viewer closing the popup.
    The script I am using does this by reading the content of a catalog.json file that is read into the pop up depending on which side link (i.e. the left menu) is chosen. The catalog.json references the folder in which that particular model and thumbnail is stored and then sends it to the main viewer.
    If anyone can help me with this it would really help my learning of Babylon and js in general. Is this a relatively straight forward thing to do? Thank You:)

You can do that in different ways:

The simplest (and worse) way would be to simply have different viewer copies, and each link open one copy.

A still simple way but slightly better would be to link to the same viewer but passing the name of the model in the url, like: https://yoursite.com/viewer.html?model=xyz.glb .

And finally, the best one would be to have javascript code on the sidebar which would call a load function on the viewer, passing the model as parameter. This one is possible but slightly more involved to do.

Thanks for this. I believe that the last option is how the script i have works. I am a newbie so if you could point me in the direction of an example that would be great. Thanks again.

I got in touch with the guy who wrote the original script and his reply was as follows:
“Babylonjs is a complete different 3D engine. So you can use the same skeleton but you need to develop the loader.
Today, the 3D model loading is embedded in the Unity build and it is not possible to extract it. Indeed, the loader is developed in C and C#. Everything is compiled in wasm by IL2CPP and then Emscripten.
The library behind the loader is ASSIMP. You can see if there are people who have implemented ASSIMP for Babylon.”
So, my next move is to work out how to do this. Any suggestions would be much appreciated!

Thank you.

The script that I would like to replicate using Babylonjs is here on Github - GitHub - Rufus31415/react-webgl-3d-viewer-demo: A 45+ 3D file format viewer built with React and WebGL. The demo loads several different file types, however, I only need .gltf, .glb , obj and stl . It uses Unity and I would like to change this to use the Babylonjs viewer. The main aspect that I want to reproduce is the way in which the script works - reading a catalog.json file that holds info about models which this script does very well. As I mentioned before I am a newbie and I am learning fast but do need help. If anyone can advise so that I can learn that would be great.
here is the demo link - A WebGL multiple file 3D viewer
Thanks again.

It seems to me that, in addition to Babylon.js, you also need to better understand Html5 and Javascript, as some of the features you are looking after are not specific to Babylon.js.

You need to learn, for example, how to create a html5 menu, how to load and parse a json file and how to call a function from the menu.

Although not exactly complex, it takes some time to write that code.

I am overwhelmed by my own tasks, but who knows, maybe someone jumps in and helps you further.

Thanks for this and yes you are correct. Thank you very much for your advice.

Well, after several weeks of learning about Babylon and React I have managed to identify that I need to change the viewer.js page. I have posted the original code here to see if anyone can help me change the unity viewer to the babylon viewer - that is what I am trying to achieve. I don’t have enough experience to do this myself at this stage, however, I have tried… hundreds of times!!
If anyone can help this would be absolutely fantastic. The viewer.js code is below in its original state. I understand that I have to declare babylon etc.


import React, { useEffect, useState } from 'react'

import Unity, { UnityContent } from "react-unity-webgl"; // specific to Unity

import { makeStyles } from '@material-ui/core/styles';

const unityContent = new UnityContent(

    "Build/build.json",                                 // loading Unity viewer

    "Build/UnityLoader.js",                             //  loading unity viewer etc

    {                                                   // I essentially need to change this page to display the Babylon Viewer

        adjustOnWindowResize: true

    }

);

const useStyles = makeStyles((theme) => ({

    root: {

        width: "100%",

        height: "100%"

    },

    unityContent: {

        background: "white",

        width: "100%",

        height: "100%",

    },

    paper: {

        padding: theme.spacing(2),

        textAlign: 'center',

        color: theme.palette.text.secondary,

    },

}));

export default function Viewer(props) {

    const classes = useStyles();

    const [ready, setReady] = useState(false);

    const [fileName, setFileName] = useState(null);

    unityContent.on("Ready", () => {

        setReady(true)

        loadFile();

        if (typeof props.onReady == "function") props.onReady();

    }

    );

    unityContent.on("OnLoaded", () => {

        try {

            if (typeof props.onLoaded == "function") props.onLoaded();

        }

        catch{ }

    }

    );

    unityContent.on("OnError", () => {

        try {

            if (typeof props.onError == "function") props.onError();

        }

        catch{ }

    }

    );

    useEffect(() => {

        if (ready) loadFile()

    }, [props.file]);

    const loadFile = () => {

        try {

            if (props.file && typeof props.file == "object") {

                var reader = new FileReader();

                reader.onload = (function (file) {

                    return function (e) {

                        (window.filedata = window.filedata ? window.filedata : {})[file.name] = e.target.result;

                        unityContent.send("root", "FileUpload", file.name)

                        setFileName(file.name);

                    }

                })(props.file);

                reader.readAsArrayBuffer(props.file);

            }

            else if (typeof props.file == "string") {

                unityContent.send("root", "Load", JSON.stringify({ file: props.file }))

                setFileName(props.file);

            }

            else {

                unityContent.send("root", "Clear");

                setFileName("");

            }

        }

        catch (e) {

            console.log(e);

            if (typeof props.onError == "function") props.onError();

        }

    }

    return (

        <div className={classes.root} >

            <Unity unityContent={unityContent} height="100%" width="100%" className={classes.unityContent} />

        </div>

    )

}

Here is the link to what I am trying to achieve using this script
https://digital-archaeology.co.uk/arcvis/RED/

I have sust out how react chooses the info to populate the navbar from a json file and sends it to a pop up from which the model is selected and then sets the file that the viewer will show. I just cant seem to get the viewer integrated. This viewer.js page is the key to this.

Well, after bashing away React and Babylonjs for weeks now I still haven’t been able to achieve what I set out to do. I have however learned a great deal.
If anyone can help me with the integration of the viewer I would be really grateful - i would also stop crying a lot and bashing my head against the wall :).
The truth is that I am not experienced enough to do this. But I am willing to learn so if there is anyone out there who can solve this for me it would be great.
I am quite happy to grovel and lose self respect :)!!


Okay, this is where I have got to. I am hoping that Brian Zinn will pick up on this!!
The image shows the issue.
I have used Brian’s react-babylon-viewer-1 script and partially integrated it into the main UI script modified from Girauld (see links in other posts) - it all looks very nice except that I need now to pass the selected model into Brian’s viewer script to achieve the change.
If anyone can help that would be awesome.
I am learning react and babylonjs by actually trying to create something that I will use as I do a lot of photogrammetry. So, I picked a script that did what I needed from a functional point of view and modified it. It originally had a unity component using unity-react-webgl as the unityloader and I wanted to replace it with babylon viewer. I just now need to understand how to pass the selected model to Brian’s viewer component.
Any advice and help is very much appreciated - and thank you to imerso for his replies too!
Here is the code from the FormatPopup.js component shown in the figure above. At present th emodel shown in the Viewer is simply one I have set in the Render.tsx file i.e. pointing the rootUrl and sceneFilename to my model folder.

import React from 'react';
import Popup from './Popup'
import { makeStyles } from '@material-ui/core/styles';
import GridList from '@material-ui/core/GridList';
import Link from '@material-ui/core/Link';
import GridListTile from '@material-ui/core/GridListTile';
import GridListTileBar from '@material-ui/core/GridListTileBar';
import IconButton from '@material-ui/core/IconButton';
import OpenInNewIcon from '@material-ui/icons/OpenInNew';

const useStyles = makeStyles((theme) => ({
  root: {
    display: 'flex',
    flexWrap: 'wrap',
    justifyContent: 'space-around',
    overflow: 'hidden',
    backgroundColor: theme.palette.background.paper,
    margin: 0,
    padding: 0
  },
  gridList: {
  },
  icon: {
    color: 'rgba(255, 255, 255, 0.5)',
  },
  header: {
    marginBottom: 20,
  },
  tile: {
    cursor: "pointer",
    opacity: 0.7,
    '&:hover': {
      opacity: 1
    }
  },
}));


export default function FormatPopup(props) {
  const classes = useStyles();

  const onModelClick = (model) => {
    props.setOpen(false);
    if(model.file.startsWith("http")) props.setFile(model.file);
    else props.setFile(window.location  + model.file);
  }

  const content = props.format ? (
    <div>
      <div className={classes.header}>
        {props.format.description}
        <span>  <Link href={props.format.url} target="_blank">Open booklet...<OpenInNewIcon fontSize="inherit" /></Link></span>
      </div>
      <div className={classes.root}>

        <GridList cellHeight={180} className={classes.gridList}>
          {props.format.models.map((model) => (
            <GridListTile
              key={model.file}
              onClick={() => onModelClick(model)}
              className={classes.tile}
            >
              <img src={model.thumbnail} alt = ""/>
              <GridListTileBar
                title={model.name}
                subtitle={<span>{model.size}</span>}
                actionIcon={
                  <Link href={model.file} target="_blank">
                    <IconButton
                      className={classes.icon}>
                      <OpenInNewIcon />
                    </IconButton>
                  </Link>
                }
              />
            </GridListTile>
          ))}
        </GridList>
      </div>
    </div>
  ) : null

  return (
    <Popup
      innerContent={content}
      icon={<img src={"formats/" + props.format?.id + "/icon.png"} alt = ""/>}
      closeText="Close"
      title={props.format?.name + " (" + props.format?.category + ")"}
      {...props}
    />
  )
}

I need to understand how this can update RenderModel found in the Render.tsx component that is sent to App.js.
Sorry if I sound like I am confused… its because I am learning!!!

And this is Brian’s Render.tsx code

import React from 'react';
import { useRef } from "react";
import { Engine, ILoadedModel, Scene } from "react-babylonjs";
import {
  Vector3,
  Color3,
  ArcRotateCamera,
  Nullable,
  FramingBehavior,
} from "@babylonjs/core";
import ScaledModelWithProgress from "./ScaledModelWithProgress";
import "@babylonjs/loaders";
import "@babylonjs/inspector";

export const RenderModel = () => {
  const camera = useRef<Nullable<ArcRotateCamera>>(null);

  const onModelLoaded = (e: ILoadedModel) => {
    if (camera && camera.current) {
      if (e.loaderName === "gltf") {
        camera.current.alpha += Math.PI;
      }

      // Enable camera's behaviors (done declaratively)
      camera.current.useFramingBehavior = true;
      var framingBehavior = camera.current.getBehaviorByName(
        "Framing"
      ) as FramingBehavior;
      framingBehavior.framingTime = 0;
      framingBehavior.elevationReturnTime = -1;

      if (e.rootMesh) {
        camera.current.lowerRadiusLimit = null;

        var worldExtends = e.rootMesh
          .getScene()
          .getWorldExtends(
            (mesh) =>
              mesh.isVisible &&
              mesh.isEnabled() &&
              !mesh.name.startsWith("Background") &&
              !mesh.name.startsWith("box")
          );

        framingBehavior.zoomOnBoundingInfo(worldExtends.min, worldExtends.max);
      } else {
        console.warn("no root mesh");
      }

      camera.current.pinchPrecision = 200 / camera.current.radius;
      camera.current.upperRadiusLimit = 5 * camera.current.radius;
    }
  };

  return (
    <Engine
      antialias
      adaptToDeviceRatio
      canvasId="babylonJS"
      canvasStyle={{ width: "100%", height: "89%" }}
    >
      <Scene>
        <arcRotateCamera
          ref={camera}
          name="arc"
          target={Vector3.Zero()}
          position={Vector3.Zero()}
          alpha={Math.PI}
          beta={0.5 + Math.PI / 4}
          minZ={0.001}
          wheelPrecision={50}
          useAutoRotationBehavior
          allowUpsideDown={false}
          checkCollisions
          radius={2}
          lowerRadiusLimit={25}
          upperRadiusLimit={75}
          useFramingBehavior={true}
          wheelDeltaPercentage={0.01}
          pinchDeltaPercentage={0.01}
        />

        <environmentHelper
          options={{
            enableGroundShadow: false,
            createGround: false,
            skyboxSize: 1000,
          }}
          setMainColor={[Color3.FromHexString("#ffffff")]}
        />


        <ScaledModelWithProgress
          rootUrl={'formats/1/models/'}
          sceneFilename="Roman_Ritual_Beaker.glb"
          progressBarColor={Color3.FromInts(135, 206, 235)}
          center={Vector3.Zero()}
          modelRotation={Vector3.Zero()}
          onModelLoaded={(e: ILoadedModel) => {
            onModelLoaded(e);
          }}
        />
      </Scene>
    </Engine>
  );
};

Basically trying to integrate the two scripts.

the JSON is called catalog.json as per Girauld’s original script

{
    "formats": [
        {
            "url": "formats/1/",
            "description": "By being able to sort pottery by shape and form we can begin to analyse pottery from contexts within a site. There may be a good mixture of different forms or there may only be blackened cooking pots. Alternatively, only small decorated ware may be present. Information of this type will begin to tell us about the usage of this context and hopefully its date, if similar dated pottery is known from elsewhere.  There should be 44 sherds of Roman pottery in this pack.  Unfortunately the following sherds are missing :Missing sherds 1.8, 1.12, 1.14, 1.16, 1.25, 1.33, 1.35, 1.37, 1.39 ",
            "name": "The first category",
            "category": "Introduction to Roman pottery 1",
            "id": "1",
            "models": [
                {
                    "file": "formats/1/Roman_Ritual_Beaker.glb",
                    "name": "Roman ritual beaker",
                    "size": "501,3 Kb",
                    "thumbnail": "formats/1/models/Roman Ritual Beaker.png"
                },
                {
                    "file": "formats/1/models/statue.glb",
                    "name": "statue",
                    "size": "16,8 Kb",
                    "thumbnail": "formats/1/models/placeholder.png"
                }
                
            ]
        },

cc @brianzinn to notify him :slight_smile:

Thanks :smiley: that’s great!

sorry - i have been out of commission for a good while. i think you would just need a state managment library to send the state update over. I would recomment using zustand, since it doesn’t suffer from the issues of crossing renderer boundaries. You will want to communicate it looks like from the materialUI DOM across to the renderer. I have some redux examples up here, but that show communicating DOM to babylon.js, but that was written in 2017 - it’s got too much boilerplate code that I wouldn’t recommend it:

Sending messages from Bootstrap components to babylon.js via state:
BabylonJS + Create React App (brianzinn.github.io)

You could do something cleaner with events or a message bus, but for small updates like changing models may be a bit overkill…

Thank you for replying Brian. I hope that you are not unwell… what on earth would we do without your valuable knowledge and skills? I think that what you have said will become more understandable once I have completed my JS course! Non of this is lost … just waiting for me to catch up. Thank you again. I expect I will be re-working things several times over while learning before I get it right!

One issue that I need to overcome is how to provide alternative Babylon code for the Unity build.json and UnityLoader.
The unity functionality is assigned to a constant named unityContent. This appears to be a loader with viewer that is exported as ‘Viewer’. If I could replace this with the relevant code for Babylonjs (loader, scene and engine) and assign it to a constant I think that this might work with the existing react code that changes the state. I have not been successful due to my ‘beginner level’ skills!
Could someone give me an idea of how to go about this? Thank you.

import React, { useEffect, useState } from 'react'
import Unity, { UnityContent } from "react-unity-webgl";
import { makeStyles } from '@material-ui/core/styles';

const unityContent = new UnityContent(
    "Build/build.json",
    "Build/UnityLoader.js",
    {
        adjustOnWindowResize: true
    }
);

Above is the code that I am trying to change

The react-unity-webgl is a wrapper to embed a Unity webgl build with React including forwarding an entire system of events. There’s not enough code there to suggest how to go about it in your case, but it looks would involve a lot of porting logic from one game platform to another. Can you share a complete working repo/code sandbox?

The script that I would like to replicate using Babylonjs is here on Github - GitHub - Rufus31415/react-webgl-3d-viewer-demo: A 45+ 3D file format viewer built with React and WebGL. The demo loads several different file types, however, I only need .gltf, .glb , obj and stl . It uses Unity and I would like to change this to use the Babylonjs viewer.

I liked the way that this programmer populated the sidebar nav from a json and then sent it to the viewer by a popup that allowed for selecting the models. I was trying to make a repository with viewer. I have figured out how the components sit together etc and how to customise the underlying json. It is probably better to look at the original script above.

OK, after many, many sleepless nights and several days of trawling the internet, I am now willing to pay someone to solve this just so I can see how it is done! Is there anyone out there who can do this? I am serious I wll pay! I really need to know how to do this so that I can learn from it.

here is a freebee - to show a popup lots of frameworks like Material/Bootstrap have modals:
floral-water-els13w - CodeSandbox

Thank you for this Brian. :smiley: