The module was primarily created for viewing 3D data as a Drupal extension for a WissKI based repository. During development it became also possible to use as a standalone version to be integrated with more environments. The Viewer is written in JavaScript, based on the three.js library for viewing 3D models and uses PHP/bash scripts for server-side operations. Supported 3D file formats: OBJ, DAE, FBX, PLY, IFC, STL, XYZ, JSON, 3DS, glTF. There is also a pre-configured complete workflow to handle more file formats and allow to render thumbnails for entries. If an uploaded file is saved in one of the compression-supported formats (obj, fbx, ply, dae, abc, blend, stl, wrl, x3d, glb, gltf), it is compressed on-the-fly and converted into GLB format and triggers automatic rendering (based on Blender utility).
Client: JavaScript, three.js, CSS, HTML, PHP, Drupal
Server: PHP, Drupal, bash, blender
The main part is located in the viewer
directory and the main.js
is responsible for delivering 3D content.
The viewer at first imports all necessary three.js components:
import * as THREE from './build/three.module.js';
import { TWEEN } from './js/jsm/libs/tween.module.min.js';
import Stats from './js/jsm/libs/stats.module.js';
import { OrbitControls } from './js/jsm/controls/OrbitControls.js';
import { TransformControls } from './js/jsm/controls/TransformControls.js';
import { GUI } from './js/jsm/libs/lil-gui.module.min.js';
import { FBXLoader } from './js/jsm/loaders/FBXLoader.js';
import { DDSLoader } from './js/jsm/loaders/DDSLoader.js';
import { MTLLoader } from './js/jsm/loaders/MTLLoader.js';
import { OBJLoader } from './js/jsm/loaders/OBJLoader.js';
import { GLTFLoader } from './js/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from './js/jsm/loaders/DRACOLoader.js';
import { KTX2Loader } from './js/jsm/loaders/KTX2Loader.js';
import { MeshoptDecoder } from './js/jsm/libs/meshopt_decoder.module.js';
import { IFCLoader } from './js/jsm/loaders/IFCLoader.js';
import { PLYLoader } from './js/jsm/loaders/PLYLoader.js';
import { ColladaLoader } from './js/jsm/loaders/ColladaLoader.js';
import { STLLoader } from './js/jsm/loaders/STLLoader.js';
import { XYZLoader } from './js/jsm/loaders/XYZLoader.js';
import { TDSLoader } from './js/jsm/loaders/TDSLoader.js';
import { PCDLoader } from './js/jsm/loaders/PCDLoader.js';
import { FontLoader } from './js/jsm/loaders/FontLoader.js';
import { TextGeometry } from './js/jsm/geometries/TextGeometry.js';
Basic Viewer configuration
Then, basic configuration should be changed:
const CONFIG = {
"domain": "https://3d-repository.hs-mainz.de",
"metadataDomain": "https://3d-repository.hs-mainz.de",
"container": "DFG_3DViewer",
"galleryContainer": "block-bootstrap5-content",
"galleryImageClass": "field--type-image",
"basePath": "/modules/dfg_3dviewer/viewer",
"entityIdUri": "/wisski/navigate/(.*)/view",
"viewEntityPath": "/wisski/navigate/",
"attributeId": "wisski_id",
"lightweight": false
};
domain, metadataDomain - domains that deliver 3D content and metadata content respectively
container - container to attach to and will contain 3D content
galleryContainer - container with generated thumbnails
galleryImageClass - class for gallery
basePath - relative path where this script is placed
entityIdUri - WissKI side URI that can deliver ID of the entity
attributeId - the ID name that container will be given
lightweight - use Viewer as simple version - mostly for usage as a standalone 3D viewer
3D DFG Viewer functions overview
createClippingPlaneGroup(geometry, plane, renderOrder) - creates group of planes for clipping 3D (available via GUI controller)
showToast (_str) - shows information about current operations
addTextWatermark (_text, _scale) - adds watermark
addTextPoint (_text, _scale, _point) - adds text at given point
selectObjectHierarchy (_id) - selects object from hierarchy
fetchMetadata (_object, _type) - fetches metadata about object
recreateBoundingBox (object) - creates new bounding box
setupObject (_object, _light, _data, _controls) - setups basic properties for object, lights, camera and controls
invertHexColor(hexTripletColor) - inverts color in hex
setupClippingPlanes (_geometry, _size, _distance) - setups clipping planes for given geometry
fitCameraToCenteredObject (camera, object, offset, orbitControls, _fit)- setups camera view centered to given object
buildGallery() - builds gallery of thumbnails
render() - main function for rendering
setupCamera (_object, _camera, _light, _data, _controls) - setups camera according to object properties
distanceBetweenPoints(pointA, pointB) - calculates distance between two given points
distanceBetweenPointsVector(vector) - calculates distance of given vector
vectorBetweenPoints (pointA, pointB) - creates vector between two given points
halfwayBetweenPoints(pointA, pointB) - calculates halfway between two given points
interpolateDistanceBetweenPoints(pointA, vector, length, scalar) - calculates distance from given point, with vector and it’s length
pickFaces(_id) - picks face with given id
buildRuler(_id) - creates ruler for measurement geometry
onWindowResize() - handler for window resize
addWissKIMetadata(label, value) - adds metadata fetched from WissKI repository
truncateString(str, n) - truncates given string with n length
getProxyPath(url) - gets proxy path from given url
expandMetadata () - expands metadata
fullscreen() - handler for fullscreen mode
exitFullscreenHandler() - handler for fullscreen exit
appendMetadata (metadataContent, canvasText, metadataContainer, container) - appends metadata to given container
fetchSettings (path, basename, filename, object, camera, light, controls, orgExtension, extension) - fetches settings for object
onError = function (_event) - error loading handler
onErrorMTL = function (_event) - MTL error loading handler
onProgress = function (xhr) - loading progress handler
setupMaterials (_object) - setups materials of an object
getMaterialByID (_object, _uuid) - gets material with given ID
traverseMesh (object) - traverses mesh to get more detailed info
loadModel (path, basename, filename, extension, orgExtension) - main function for loading object
animate() - main animation funcion
onPointerDown(e) - mouse down click handler
onPointerUp(e)- mouse up click handler
onPointerMove(e)- mouse move handler
changeScale()- changes scale of an object (available via GUI controller)
calculateObjectScale() - calculates object scale
changeLightRotation() - changes light rotation (available via GUI controller)
takeScreenshot()- takes screenshot for thumbnail rendering\
mainLoadModel (_ext)- main function for loading objects
init() - main function that inits workflow
Main workflow is divided into two automatic parts:
Scripts needed there are placed under scripts
and php
directory.
After uploading 3D model into repository there are triggered following steps:
dfg_3dviewer_entity_presave
and supports following archive formats: zip, rar, tar, xz, gz. According to the format, the bash script is triggered with following arguments:
/scripts/uncompress.sh archiveType -i inputPath -o extractPath -n fileName
handle_file
handle_ifc_file
handle_blend_file
render_preview
This step is performed inside scripts/convert.sh
bash script.
Script defaults (need to be changed if used in different environment):
BLENDER_PATH=''
#Defaults:
COMPRESSION=false
COMPRESSION_LEVEL=3
GLTF="gltf"
FORCE="false"
isOutput=false
IS_ARCHIVE=false
SPATH="/var/www/html/3drepository/modules/dfg_3dviewer"
Functions used during this step:
handle_file
- uses python script (downloaded and modified version of https://github.com/ux3d/2gltf2/tree/master) triggered by blender
${BLENDER_PATH}blender -b -P ${SPATH}/scripts/2gltf2/2gltf2.py -- "$INPATH/$FILENAME" "$GLTF" "$COMPRESSION" "$COMPRESSION_LEVEL" "$OUTPUT$OUTPUTPATH"
handle_ifc_file
- uses IfcConvert script (available at https://ifcopenshell.sourceforge.net/ifcconvert.html)
${SPATH}/scripts/IfcConvert "$INPATH/$FILENAME" "$INPATH/gltf/$NAME.glb"
handle_blend_file
- (not fully tested yet) uses python script triggered by blender
${BLENDER_PATH}blender -b -P ${SPATH}/scripts/convert-blender-to-gltf.py "$INPATH/$FILENAME" "$INPATH/gltf/$NAME.glb"
automatic rendering of 3D model for thumbnails - function render_preview
, which uses wrapper for triggering virtual environment (xvfb-run) for blender and it’s python script
xvfb-run --auto-servernum --server-args="-screen 0 512x512x16" sudo ${BLENDER_PATH}blender -b -P ${SPATH}/scripts/render.py -- "$INPATH/$NAME.glb" "glb" $1 "$INPATH/views/" $IS_ARCHIVE -E BLENDER_EEVEE -f 1
This step needs some steps to be performed before rendering:
create scene containing loaded 3D model
calculate bounding box (for camera and lights settlement)
scale scene according to bounding box
setup basic properties for rendering engine, output quality, lights, camera
prepare rendering from camera placed in 9 different positions (left, left top, front, front top, right, right top, back, back top, top)
write rendering outputs into png files with consecutive naming
<!doctype html>
<html>
<head>
<title>Three.js 3D-DFG-Viewer</title>
<link rel="stylesheet" href="https://github.com/thedworak/dfg_3dviewer/blob/main/3D-DFG-Viewer/css/spinner.css">
<link rel="stylesheet" href="https://github.com/thedworak/dfg_3dviewer/blob/main/3D-DFG-Viewer/css/main.css">
</head>
<body>
<script type="module" src="https://github.com/thedworak/dfg_3dviewer/raw/main/3D-DFG-Viewer/main/jquery-3.6.0.min.js"></script>
<script src="https://github.com/thedworak/dfg_3dviewer/raw/main/3D-DFG-Viewer/main/toastify.js"></script>
<script src="https://github.com/thedworak/dfg_3dviewer/raw/main/3D-DFG-Viewer/main/spinner/main.js"></script>
<script type="module" src="https://github.com/thedworak/dfg_3dviewer/raw/main/3D-DFG-Viewer/main/main.js"></script>
<div id="DFG_3DViewer" 3d="your_model_name"></div>
</body>
</html>