I have a gltf file which exist in firebase storage folders and related textures are also located in that folders. I want to load that object into my view. I using THREE js for do this.
I tried get download url of gltf file and pass it it GLTFLoader. But model was not loaded to view.
I tried with this :
const loader = new GLTFLoader()
loader.load(
url,
(gltf) => {
gltf.scene.traverse( ( child ) => {
if ( child instanceof THREE.Mesh ) {
console.log(child.material.metalness)
if(child.material.metalness){
child.material.envMap = texture;
}
}
} );
var parent = gltf.scene;
var box = new THREE.Box3().setFromObject(parent)
var center = box.getCenter(new THREE.Vector3())
var size = box.getSize(new THREE.Vector3())
var maxAxis = Math.max(size.x,size.y,size.z)
parent.scale.multiplyScalar(1/maxAxis)
box.setFromObject(parent);
box.getCenter(center)
box.getSize(size)
parent.position.copy(center).multiplyScalar(-1)
scene.add(gltf.scene)
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
},
(error) => {
console.log(error)
}
)
If I load this file from local device, it is works fine and model display in view(all textures are load properly).
If anyone can help me how to load gltf file from firebase storage
I believe you should use getDownloadURL() from firebase like any other file. check this out Firebase Cloud
May not be the best solution but if you save the file as a .glb (binary version of .gltf), then there is only one file with textures already wrapped up in it. If not, you will need to store all the gltf data in one folder and load the whole lot including textures.
Related
I'm trying to build a Flask app where I upload pdf's and I'm working on previewing them before submitting to the back-end.
The script I'm using is as follows:
const imageUploadValidation = (function () {
"use strict";
pdfjsLib.GlobalWorkerOptions.workerSrc =
"https://mozilla.github.io/pdf.js/build/pdf.js";
const onFilePicked = function (event) {
// Select file Nodelist containing 1 file
const files = event.target.files;
const filename = files[0].name;
if (filename.lastIndexOf(".") <= 0) {
return alert("Please add a valid file!");
}
const fileReader = new FileReader();
fileReader.onload = function (e) {
const pdfData = e.target.result;
let loadingTask = pdfjsLib.getDocument({ data: pdfData })
loadingTask.promise.then(function (pdf) {
console.log("PDF loaded", pdf);
pdf.getPage(1).then((page) => {
console.log("page loaded", page);
// var scale = 1.5;
// var viewport = page.getViewport({ scale: scale });
var iframe = document.getElementById("image-preview");
iframe.src = page
// var context = canvas.getContext("2d");
// canvas.height = viewport.height;
// canvas.width = viewport.width;
// var renderContext = {
// canvasContext: context,
// viewport: viewport,
// };
// var renderTask = page.render(renderContext);
// renderTask.promise.then(function () {
// console.log("Page rendered");
// });
});
})
.catch((error) => {
console.log(error);
});
};
const pdf = fileReader.readAsArrayBuffer(files[0]);
console.log("read as Data URL", pdf);
};
const Constructor = function (selector) {
const publicAPI = {};
const changeHandler = (e) => {
// console.log(e)
onFilePicked(e);
};
publicAPI.init = function (selector) {
// Check for errors.
const fileInput = document.querySelector(selector);
if (!selector || typeof selector !== "string") {
throw new Error("Please provide a valid selector");
}
fileInput.addEventListener("change", changeHandler);
};
publicAPI.init(selector);
return publicAPI;
};
return Constructor;
})();
imageUploadValidation("form input[type=file]");
The loading task promise never seems to run. Everything seems to work up until that point. I'm not familiar with this Promise syntax, so I can't be sure if the problem is there or how I'm passing in the pdf file.
P.S. The commented out code is the original way I had this setup, what
s uncommented was just me testing a different way.
Check Datatype
First you might want to check what your getting back from your FileReader, specifically what is the datatype for pdfData. If you have a look at the documentation (direct link) getDocument is expecting a Unit8Array or a binary string.
Add Missing Parameters
The next problem you have is your missing required parameters in your call to getDocument. Here is the minimum required arguments:
var args = {
url: 'https://example.com/the-pdf-to-load.pdf',
cMapUrl: "./cmaps/",
cMapPacked: true,
}
I have never used the data argument in place of the url but as long as you supply the correct datatype you should be fine. Notice that cMapUrl should be a relative or absolute path to the cmap folder. PDFJS often needs these files to actually interpret a PDF file. Here are all the files from the demo repository (GitHub pages): cmaps You'll need to add these to your project.
Instead of using data I would recommend uploading your files as blobs and then all you have to do is supply the blob URL as url. I am not familiar with how to do that, I just know its possible in modern browsers.
Where Is Your Viewer / You Don't Need iFrame or Canvas
PDFJS just needs a div to place the PDF inside of. It's picky about some of the CSS rules, for exmaple it MUST be positioned absolute, otherwise PDFJS generates the pages as 0px height.
I don't see PDFViewer or PDFLinkService in your code. It looks like you are trying to build the entire viewer from scratch yourself. This is no small endeavor. When you get loadingTask working correctly the response should be handled something like this:
loadingTask.promise.then(
// Success function.
function( doc ) {
// viewer is holding: new pdfjsViewer.PDFViewer()
// linkService is: new pdfjsViewer.PDFLinkService()
viewer.setDocument( doc );
linkService.setDocument( doc );
},
// Error function.
function( exception ) {
// What type of error occurred?
if ( exception.name == 'PasswordException' ) {
// Password missing, prompt the user and try again.
elem.appendChild( getPdfPasswordBox() );
} else {
// Some other error, stop trying to load this PDF.
console.error( exception );
}
/**
* Additional exceptions can be reversed engineered from here:
* https://github.com/mozilla/pdf.js/blob/master/examples/mobile-viewer/viewer.js
*/
}
);
Notice that PDFViewer does all the hard work for you. PDFLinkService is needed if you want links in the PDF to work. You really should checkout the live demo and the example files.
Its a lot of work but these example files specifically can teach you all you need to know about PDFJS.
Example / Sample Code
Here is some sample code from a project I did with PDFJS. The code is a bit advanced but it should help you reverse engineer how PDFJS is working under the hood a bit better.
pdfObj = An object to store all the info and objects for this PDF file. I load multiple PDFs on a single page so I need this to keep them separate from each other.
updatePageInfo = My custome function that is called by PDFJS's eventBus when the user changes pages in the PDF; this happens as they scroll from page to page.
pdfjsViewer.DownloadManager = I allow users to download the PDFs so I need to use this.
pdfjsViewer.EventBus = Handles events like loading, page changing, and so on for the PDF. I am not 100% certain but I think the PDFViewer requires this.
pdfjsViewer.PDFViewer = What handles actually showing your PDF to users. container is the element on the page to render in, remember it must be positioned absolute.
// Create a new PDF object for this PDF.
var pdfObj = {
'container': elem.querySelector('.pdf-view-wrapper'),
'document': null,
'download': new pdfjsViewer.DownloadManager(),
'eventBus': new pdfjsViewer.EventBus(),
'history': null,
'id': id,
'linkService': null,
'loaded': 0,
'loader': null,
'pageTotal': 0,
'src': elem.dataset.pdf,
'timeoutCount': 0,
'viewer': null
};
// Update the eventBus to dispatch page change events to our own function.
pdfObj.eventBus.on( 'pagechanging', function pagechange(evt) {
updatePageInfo( evt );
} );
// Create and attach the PDFLinkService that handles links and navigation in the viewer.
var linkService = new pdfjsViewer.PDFLinkService( {
'eventBus': pdfObj.eventBus,
'externalLinkEnabled': true,
'externalLinkRel': 'noopener noreferrer nofollow',
'externalLinkTarget': 2 // Blank
} );
pdfObj.linkService = linkService;
// Create the actual PDFViewer that shows the PDF to the user.
var pdfViewer = new pdfjsViewer.PDFViewer(
{
'container': pdfObj.container,
'enableScripting': false, // Block embeded scripts for security
'enableWebGL': true,
'eventBus': pdfObj.eventBus,
'linkService': pdfObj.linkService,
'renderInteractiveForms': true, // Allow form fields to be editable
'textLayerMode': 2
}
);
pdfObj.viewer = pdfViewer;
pdfObj.linkService.setViewer( pdfObj.viewer );
I am using three.js and Capacitor to create a native iOS and Android app. I have some GLTF models that I want to load from some sort of asset folder that is bundled up with the code and delivered to a user, but I am not sure how to go about this.
This documentation on the GLTF Loader's load function, requires some sort of url. Parse, however, takes in a "glTF asset to parse, as an ArrayBuffer". How can I load this glTF file into memory and what is the best practice for doing so? I tried import * as Model from './models/myModel.gltf' but got a Cannot find module error.
Here my method to load a gltf file, I'm using reader to load any file in my scene, then it's parsing with loader.parse.
But if you want sample models folder I think you don't need reader method and parser. Just using load GLTF basic method and store your samples in an array or object.
I hope I helped you.
loadGltf(file, filename) {
reader.onload = readerEvent => {
const contents = readerEvent.target.result;
const loader = new THREE.GLTFLoader()
try {
loader.parse(contents, '', function (gltf) {
gltf.scene.traverse(function(child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
currentModel = gltf.scene;
scene.add(gltf.scene);
});
}
catch(error) {
alert("Your file " + Load.filename + " was not parsed correctly." + "\n\n" + "ERROR MESSAGE : " + error.message);
}
}
reader.readAsArrayBuffer(file);
}
I'm trying to load gltf model into my page using the following code :
var loader = new THREE.GLTFLoader().setPath('models/football_goal/' );
loader.load('scene.gltf', function (gltf) {
gltf.scene.traverse( function( node ) {
if ( node instanceof THREE.Mesh ) { node.castShadow = true; }
} );
The problem is when the I open the page the browser fail loading the model and downloading the scene.bin file instead.
This is only on my device and other collaborators don't have this issue.
I am trying to display 2d file in viewer in Offline mode using pure Javascript. I have already uploaded and extracted dwg using https://extract.autodesk.io/ .
Extracted file contains many json.gz files and one folder. In this folder, it has manifest, metadata (json.gz file) and one .f2d file
I have given this file location to my viewer options
var docs = [{ "path": "./{foldername}/primaryGraphics.f2d", "name": "2D view" }];
var options = { 'docid': docs[0].path, env: 'Local' };
And my viewer initialization is
viewer = new Autodesk.Viewing.Private.GuiViewer3D(document.getElementById('MyViewerDiv'), {});
Autodesk.Viewing.Initializer(options, function () {
viewer.initialize();
viewer.loadModel(options.docid);
});
It is giving me error message in the viewer saying "We cant display the item you are looking for. It may not have been processed yet...." And giving me error code as 5 (Specified type is invalid).
Please help.
Please make sure that you have completely downloaded all extracted viewable bubbles of your DWG and the path of the model you want to load is correct, since error code 5 stands for NETWORK_FILE_NOT_FOUND.
I just tested this blocks_and_tables_-_metric.dwg from the AutoCAD Sample Files with the below code snippet, and it works fine.
var options = {
env: 'Local',
};
var doc = { 'rootFolder': 'Model', 'path': '29c9e407-f76f-a1c0-0972-dcb5b496fff9_f2d/primaryGraphics.f2d', 'name': '2D view' };
var viewerDiv = document.getElementById( 'MyViewerDiv' );
var viewer = new Autodesk.Viewing.Private.GuiViewer3D( viewerDiv );
Autodesk.Viewing.Initializer(options, function() {
if( viewer.initialize() != 0 ) return console.error( 'Failed to initialize viewer' );
var basePath = getCurrentBaseURL();
var modelFolderPath = basePath + doc.rootFolder + '/';
var modelFilePath = modelFolderPath + doc.path;
var modelOptions = {
sharedPropertyDbPath: modelFolderPath
};
viewer.loadModel( modelFilePath, modelOptions, onLoadModelSuccess, onLoadModelError );
});
function getCurrentBaseURL() {
var basePath = '';
var lastSlash = document.location.href.lastIndexOf( '/' );
if( lastSlash != -1 )
basePath = document.location.href.substr( 0, lastSlash + 1 );
return basePath;
}
/**
* viewer.loadModel() success callback.
* Invoked after the model's SVF has been initially loaded.
* It may trigger before any geometry has been downloaded and displayed on-screen.
*/
function onLoadModelSuccess( model ) {
console.log( 'onLoadModelSuccess()!' );
console.log( 'Validate model loaded: ' + ( viewer.model === model ) );
console.log( model );
}
/**
* viewer.loadModel() failure callback.
* Invoked when there's an error fetching the SVF file.
*/
function onLoadModelError( viewerErrorCode ) {
console.error( 'onLoadModelError() - errorCode:' + viewerErrorCode );
}
The file structure of the extracted model of blocks_and_tables_-_metric.dwg is shown below:
The file structure of 2D model I used is:
I have Collada and related image data stored in a multipart file that is outputted from an application. I need to be able to load the Collada object and images for display in the Web via three.js. Can three.js interpret the multipart file or the data within the file, or does this need to be parsed out into actual files, since the Collada loader example shows it linking to a .dae file (ex. loader.load('models/monster.dae') and not the actual data.
In any case, how would I achieve this? The end goal is to load and view the "box" represented by the multipart data in the file.
Here is the sample structure of the multipart file I receive from the application:
MIME-Version:1.0
Content-Type:multipart/mixed;
boundary="----=_Part_4_153315749.1440434094461"
------=_Part_4_153315749.1440434094461
Content-Type: application/octet-stream; name=Texture_1.png
Content-ID: response-1
Content-Disposition: attachment; filename=Texture_1.png
‰PNG
"blob data here"
------=_Part_4_153315749.1440434094461
Content-Type: application/octet-stream; name=manifest.xml
Content-ID: response-2
Content-Disposition: attachment; filename=manifest.xml
<?xml version="1.0"?>
<dae_root>blank_3D.dae</dae_root>
------=_Part_4_153315749.1440434094461
Content-Type: application/octet-stream; name=texture_3D.dae
Content-ID: response-3
Content-Disposition: attachment; filename=texture_3D.dae
<xml data here... lots of xml data>
------=_Part_4_153315749.1440434094461
Content-Type: application/octet-stream; name=Texture_0.png
Content-ID: response-4
Content-Disposition: attachment; filename=Texture_0.png
‰PNG
"blob image data"
UPDATE:
By having the vendor split up the files, I am able to load the collada file directly into the viewer... but I don't know how to get the images along with it loaded into memory or elsewise. Any ideas of how to include the images/textures that belong with the collada file? Here is my current loading code:
// instantiate a loader
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load('http://applicationvendorapi.com/dae', function (collada) {
box = collada.scene;
box.traverse(function (child) {
if (child instanceof THREE.SkinnedMesh) {
var animation = new THREE.Animation(child, child.geometry.animation);
animation.play();
}
});
box.scale.x = box.scale.y = box.scale.z = .2;
box.updateMatrix();
init();
animate();
});
The location of the textures tend to be in the same directory as the Collada file. Here is the XML of the Collada. The question is can I reference these by pulling in the other images over the Web service (ex. /endpoint/texture_0 and /endpoint/texture_1
<library_images>
<image id="Texture_0_png">
<init_from>./Texture_0.png</init_from>
</image>
<image id="Texture_1_png">
<init_from>./Texture_1.png</init_from>
</image>
UPDATE 2:
Using the advice of #gaitat, I am attempting to load the texture first via the ImageUtils. However, it never makes the call to the URL for the texture/load the texture. It does however, still make the object call. Here is the current object wrapped in a texture call:
// Load texture before loading and initializing 3D object
var texture0 = THREE.ImageUtils.loadTexture('http://vendorwebservice/texture_0', {}, function loaded() {
// Instantiate a Collada loader
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load('http://vendorwebservice/dae', function (collada) {
box = collada.scene;
box.traverse(function (child) {
if (child instanceof THREE.SkinnedMesh) {
var animation = new THREE.Animation(child, child.geometry.animation);
animation.play();
}
});
box.scale.x = box.scale.y = box.scale.z = .2;
box.updateMatrix();
init();
animate();
});
});
Here is where it gets interesting. Since the DAE file I pull in has the following xml references to its textures, it actually tries to "get" these as well, but an endpoint does not exist with the .png extension so it fails (ex. http://vendorwebservice/texture_0.png):
<library_images>
<image id="Texture_0_png">
<init_from>./Texture_0.png</init_from>
</image>
<image id="Texture_1_png">
<init_from>./Texture_1.png</init_from>
</image>
As your application is a web-service where data does not have static URI's you need to download the data the service returns locally. As long as the texture files that the Collada file references are in a relative path (to the .dae), the THREE.ColladaLoader() will be able to find them, load them and display them.
I know it's too late but I hope my answer to help future readers.
I was in a similar situation where the files are not referenced by static URI. I didn't want to download the files because I concerned about some inefficiency of data traveling through network and taking up the server's resource. So I decided to handle it manually on browser by this process:
Read the name of the image file that the collada's texture is referencing
Find the image file with the name you found in step 1
Generate Threejs texture using the file you found in step 2
Connect the texture you made in step 3 to the texture slot in collada material
And here's an example code. In my case, the URLs in the code is data URL
const loadCollada = async (dae, img) => {
const collada = new ColladaLoader().load(dae.url);
// Get all materials in the loaded collada
const materials = new Set();
collada.traverse((object) => {
const mat = object.material
if (mat) {
if (mat.constructor.name === "Array") {
mat.forEach((e) => {
materials.add(e);
})
} else {
materials.add(mat);
}
}
});
// Parse collada file
const daeText = await fetch(dae.url).then((r) => r.text())
const daeXml = new DOMParser().parseFromString(daeText,"text/xml")
const materialLibrary = daeXml.getElementsByTagName("material")
// Get material name and texture name(image file name)
Array.from(materialLibrary).forEach((e) => {
const materialName = e.getAttribute("name")
const effect = e.getElementsByTagName("instance_effect")[0]
const textureUrl = effect.attributes.url.value.slice(1)
const textureTag = daeXml.getElementById(textureUrl)
const textureName = textureTag.getElementsByTagName("init_from")[0]?.innerHTML
// Find the material that matches name
if (textureName) {
Array.from(materials).forEach((mat) => {
if (mat.name === materialName) {
// Generate a texture and override it to the material we found
const texture = new THREE.TextureLoader().load(img[textureName].url)
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
mat.map = texture
}
});
}
})
}