How to export rendered forge viewer as glTF file? - three.js

I gonna export the rendered forge viewer as glTF file using three.js so I can save that.
The code is like below.
const exportGLTF = () => {
var scene = viewer.impl.sceneAfter;
exporter.parse( scene, function ( gltf ) {
const output = JSON.stringify( gltf, null, 2 );
saveString( output, 'scene.gltf' );
}, {trs: true} );
}
But it output the empty gltf file.(Like the result below.)
{
"asset": {
"version": "2.0",
"generator": "THREE.GLTFExporter"
},
"scenes": [
{
"nodes": [],
"name": "AuxScene"
}
],
"scene": 0
}
I think getting scene from forge viewer is not working.(var scene = viewer.impl.sceneAfter;)
So if anybody has experience in this part, help me, please.

Related

Exporting forge viewer scene to GLTF

I am trying to convert some isolated elements on forge viewer to GLTF I have included this script
<script src="https://unpkg.com/three#0.126.0/examples/js/exporters/GLTFExporter.js"></script>
I am using this code to export to GLTF
const exporter=new THREE.GLTFExporter()
const exportGLTF = () => {
var scene = viewer.impl.scene
exporter.parse( scene, function ( gltf ) {
const output = JSON.stringify( gltf, null, 2 );
console.log(output)
//saveString( output, 'scene.gltf' );
}, {trs: true} );
}
but unfortunately I receive empty GLTF output like this
{
"asset": {
"version": "2.0",
"generator": "THREE.GLTFExporter"
},
"scenes": [
{}
],
"scene": 0
}
R103+ THREE.GLTFExporter() is not compatible with THREE R71 (since we use a modified BufferGeometry), hence why you are getting no geometry.
Alex is right, use glTF-convert-utils.
Here's an example node.js script, with DBID filter. Use this to filter out a sub-set of the full drawing:
// convert SVF to dstPath/output.glb (with zeux compression)
// but only convert a subset of objects (see filter on line 23)
// INSTALL:
// > npm install forge-convert-utils forge-server-utils fs-extra gltfpack
// RUN:
// > node convert url guid token dstPath
const path = require('path');
const fs = require('fs-extra');
var gltfpack = require('gltfpack');
const { SvfReader, GltfWriter } = require('forge-convert-utils');
async function convert(urn, guid, token, dstPath) {
// Convert SVF to glTF
const reader = await SvfReader.FromDerivativeService(urn, guid, { token });
const writer = new GltfWriter({
ignoreLineGeometry: true,
ignorePointGeometry: true,
skipUnusedUvs: false,
center: false,
filter: (dbid) => (
[34044, 40936, 41095, 39471, 40933, 40939, 41092, 41097, 41090, 40946].indexOf(dbid)>-1)
});
const svf = await reader.read();
const gltfDir = path.join(path.dirname(dstPath), 'gltf');
fs.ensureDirSync(gltfDir);
await writer.write(svf, gltfDir);
gltfpack.pack(['-cc', '-i', './gltf/output.gltf', '-o', 'output.glb'], { read: fs.readFileSync, write: fs.writeFileSync})
}
const urn = `dXJuOm...j0z`;
const guid = `2588ca45-8487-cddf-b974-7f04179909a2`;
const token = `eyJhbG......gJHNA`
const dstPath = `out`;
convert(urn, guid, token, dstPath);
Remember to fill in the urn, guid & token with your forge details. The script will connect to forge, download svf, convert to gltf, then convert to glb (with MeshOpt compression using glTFpack).
I am not sure it's possible to extract the Forge scene with ThreeJS exporters.
If you want to extract your Forge model into glTF format, one of the best way (as of today) is to use the Forge Convert Utils package.

How to add lightmap to three js?

I baked some lightmap from on blender 2.8(Bake type - combined, Influence - "Direct", "Indirect", "Diffuse").
After that, I tried to add lightmap to part of my model.
function initColor(parent, type, mtl) {
parent.traverse(o => {
if (o.isMesh) {
if (o.name.includes(type)) {
o.material = mtl;
}
}
});
}
var lightmapTexture = new THREE.ImageUtils.loadTexture( "images/FrontLightmap.png" );
var front_material = new THREE.MeshBasicMaterial({lightMap: lightmapTexture });
const INITIAL_MAP = [
{childID: "Front", mtl: front_material}
];
for (let object of INITIAL_MAP) {
initColor(theModel, object.childID, object.mtl);
}
But the result - black material(front part).
Is there is possible to separately usage of lightmap and different material colors at the same time?

How use MediaFilePicker and PhotoEditor plugins in Nativescript

I am trying to use MediaFilePicker on nativescript and at the same time use the PhotoEditor plugin to crop/edit the photo taken from the camera but I don't make it work... here is part of my code:
let options: ImagePickerOptions = {
android: {
isCaptureMood: true, // if true then camera will open directly.
isNeedCamera: true,
maxNumberFiles: 1,
isNeedFolderList: true
}, ios: {
isCaptureMood: true, // if true then camera will open directly.
maxNumberFiles: 1
}
};
let mediafilepicker = new Mediafilepicker();
mediafilepicker.openImagePicker(options);
mediafilepicker.on("getFiles", function (res) {
let results = res.object.get('results');
let result = results[0];
let source = new imageSourceModule.ImageSource();
source.fromAsset(result.rawData).then((source) => {
const photoEditor = new PhotoEditor();
photoEditor.editPhoto({
imageSource: source,
hiddenControls: [],
}).then((newImage) => {
}).catch((e) => {
reject();
});
});
});
The result object of the FilePicker comes like:
{
"type": "capturedImage",
"file": {},
"rawData": "[Circular]"
}
I believe if the picture was taken from the camera, then use the rawData field, but I dont know which format is coming and how to give it to PhotoEditor pluging to play with it.
Any suggestions?
Thanks!
The issue was at this line source.fromAsset(result.rawData) here, result.rawData is not an ImageAsset but it's PHAsset. You will have to create an ImageAsset from PHAsset and pass it on to fromAsset. So it would look like,
import { ImageAsset } from "tns-core-modules/image-asset";
....
....
imgSource.fromAsset(new ImageAsset(img)).then((source) => {
const photoEditor = new PhotoEditor();
console.log(source === imgSource);
photoEditor.editPhoto({
imageSource: source,
hiddenControls: [],
}).then((newImage: ImageSource) => {
console.log('Get files...');
// Here you can save newImage, send it to your backend or simply display it in your app
}).catch((e) => {
//reject();
});
});

Load a model into an a-entity

I'm attempting to do conditional loading of a model into an a-entity. The reason for this is that I've got a scene which I would like to give the user a choice to load a large model or not. So far I've got a scene with the following entity:
id="modelname-entity"
scale="2 2 2"
position="0 0 -5"
drag-rotate="rotateY:false;"
model-rotate-loadprogress="modelUrl:modelname.gltf;modelRef:modelname-model"
></a-entity>
which has a component model-rotate-loadprogress which loads the gltf model with THREE.js syntax:
AFRAME.registerComponent('model-rotate-loadprogress',{
schema : {
modelUrl: {
type: "string"
},
modelRef: {
type: "string"
}
},
init : function(){
this.loadModel();
},
loadModel: function() {
if (!this.data.modelUrl || !this.data.modelRef) {
console.warn("Model details not given for model rotate loader");
return;
}
if ( document.getElementById(this.data.modelRef) ) {
console.warn("Assets already has an asset with the ID of " , this.data.modelRef );
}
// Using THREE.js file loader
var dis = this;
var loader = new THREE.GLTFLoader().setPath( '/assets/static/models/' );
loader.load(
this.data.modelUrl,
gltf => {
// Add the model to the scene for now.
dis.el.sceneEl.object3D.add(gltf.scene);
},
xhr => {
console.log( (xhr.loaded / xhr.total * 100) + '% loaded' );
},
error => {
console.error( error );
}
);
}
}
The model loads and gets displayed to the scene but how to I attach it to the entity instead?
I got the model populating the <a-entity> in the similar way to how I got it attaching it to the scene. Here's the final code:
loadGLTFModel: function() {
var dis = this;
var loader = new THREE.GLTFLoader().setPath( this.PATH_MODELS );
loader.load(
`${this.PATH_MODELS}${this.data.gltfModel}`,
gltf => {
dis.el.object3D.add(gltf.scene)
},
progress => {
this.onProgress(progress);
},
error => {
console.error( "ERROR : " , error );
}
);
},
onProgress: function(progress) {
this.progressBar.setAttribute("geometry", {
thetaLength: (progress.loaded / progress.total * 360)
})
},
If I were to add the heavy model to the <a-assets> which is the recommended way of doing things, would result in the whole application being blocked until all the assets are loaded and ready. In my scenario the user has a choice of skipping the download. If the user chooses to load the model then he/she gets a progressbar (actually a ring) which gets updated.
Here's code how to load obj and mtl models:
loadOBJModel: function() {
var dis = this;
if (!this.data.mtlMaterial) {
console.error("No material given for model");
return;
}
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load(
`${this.PATH_MODELS}${this.data.mtlMaterial}`,
materials => {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials( materials );
objLoader.setPath( this.PATH_MODELS );
objLoader.load(
this.data.objModel,
object => {
dis.el.object3D.add(object)
},
progress => {
this.onProgress(progress);
},
error => {
console.error( "ERROR : " , error );
}
);
}
);
},
No need to invoke the GLTFLoader. Use the gltf-model component:
loadModel: function() {
if (!this.data.modelUrl || !this.data.modelRef) {
console.warn("Model details not given for model rotate loader");
return;
}
if ( document.getElementById(this.data.modelRef) ) {
console.warn("Assets already has an asset with the ID of " , this.data.modelRef );
}
this.el.setAttribute(‘gltf-model’, ‘url(‘ + this.data.modelUrl + ‘)’);
}
I recommend pre loading the model in a-assets so user doesn’t have to wait for network. Pre load both the large and small models.

how to put threejs building on mapbox to its real place

currently i've load eiffel tower obj file, and render it using threejs, but how can i put the building on map to its place in real world. i use mapgox-gl-js to handle map issues, for its convenience on 3d map.
style: {
"version": 8,
"sources": {
"satellite": {
"type": "raster",
"url": "mapbox://mapbox.satellite",
"tileSize": 256
},
"canvas": {
type: 'canvas',
canvas: 'idOfMyHTMLCanvas',
// animate: true,
coordinates: [
[-74.02204952394804, 40.706782422418456],
[-73.99115047610259, 40.706782422418456],
[-73.99115047610259, 40.72021689994298],
[-74.02204952394804, 40.72021689994298]
],
contextType: 'webgl'
}
},
"layers": [{
"id": "satellite",
"type": "raster",
"source": "satellite"
}, {
"id": "video",
"type": "raster",
"source": "canvas"
}]
}
thank you for any help.
You may want to check out Threebox, which is designed to sync a Three.js scene graph with a Mapbox GL JS map.
This question is quite old, but indeed as suggested by #lauren-budorick, it took me 5 minutes to do this sample using the latest version of threebox and the result is like this
<script>
mapboxgl.accessToken = 'PASTE HERE YOUR TOKEN';
var origin = [2.294514, 48.857475];
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/satellite-v9',
center: origin,
zoom: 18,
pitch: 60,
bearing: 0
});
map.on('style.load', function () {
map
.addLayer({
id: 'custom_layer',
type: 'custom',
renderingMode: '3d',
onAdd: function (map, mbxContext) {
window.tb = new Threebox(
map,
mbxContext,
{
defaultLights: true,
}
);
// import tower from an external glb file, downscaling it to real size
// IMPORTANT: .glb is not a standard MIME TYPE, you'll have to add it to your web server config,
// otherwise you'll receive a 404 error
var options = {
obj: '/3D/eiffel/eiffel.glb',
type: 'gltf',
scale: 0.01029,
units: 'meters',
rotation: { x: 0, y: 0, z: 0 }, //default rotation
adjustment: { x: -0.5, y: -0.5, z: 0 } // place the center in one corner for perfect positioning and rotation
}
tb.loadObj(options, function (model) {
model.setCoords(origin); //position
model.setRotation({ x: 0, y: 0, z: 45.7 }); //rotate it
tb.add(model);
})
},
render: function (gl, matrix) {
tb.update();
}
});
})
</script>
I just stumbled across this question and wanted to provide an updated answer for anyone else who ends up here. At the time the question was asked, this was not possible in Mapbox GL JS without a plugin but it can be achieved now with the CustomLayerInterface.
Here's an example of adding a Three.js model to a Mapbox GL JS map.

Resources