From 3d4923e6009914b5b77f5064e555e84a8ba19e92 Mon Sep 17 00:00:00 2001 From: Rostyslav Nahornyi Date: Mon, 3 Jun 2024 13:58:24 +0300 Subject: [PATCH 1/3] feat: glTF export to cloud --- src/editor/api/index.js | 3 +- src/editor/api/scene.js | 51 ++++++++++++++++++- .../ScreenshotModal.component.jsx | 25 ++++++++- src/editor/components/scenegraph/Toolbar.js | 41 +++++++++------ 4 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/editor/api/index.js b/src/editor/api/index.js index f7d2052f0..63e82dff9 100644 --- a/src/editor/api/index.js +++ b/src/editor/api/index.js @@ -3,7 +3,8 @@ export { updateScene, isSceneAuthor, getCommunityScenes, - checkIfImagePathIsEmpty + checkIfImagePathIsEmpty, + uploadGlbScene } from './scene'; export { signIn } from './auth'; diff --git a/src/editor/api/scene.js b/src/editor/api/scene.js index 1a8fbfd5c..4f0581c05 100644 --- a/src/editor/api/scene.js +++ b/src/editor/api/scene.js @@ -14,7 +14,8 @@ import { where } from 'firebase/firestore'; import { v4 as uuidv4 } from 'uuid'; -import { db } from '../services/firebase'; +import { getDownloadURL, ref, uploadBytesResumable } from 'firebase/storage'; +import { db, storage } from '../services/firebase'; const generateSceneId = async (authorId) => { const userScenesRef = collection(db, 'scenes'); @@ -189,6 +190,51 @@ const checkIfImagePathIsEmpty = async (sceneId) => { } }; +const uploadGlbScene = async (glbBlobFile, sceneId) => { + if (!sceneId || !glbBlobFile) { + throw new Error('Scene id or blob file is not exist'); + } + + try { + const thumbnailRef = ref(storage, `scenes/${sceneId}/files/scene.glb`); + const uploadedFile = uploadBytesResumable(thumbnailRef, glbBlobFile); + + uploadedFile.on( + 'state_changed', + (snapshot) => { + const progress = + (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + console.info(`uploading: ${progress}%`); + }, + (error) => { + console.log({ error }); + }, + async () => { + const downloadURL = await getDownloadURL(uploadedFile.snapshot.ref); + + const userScenesRef = collection(db, 'scenes'); + const sceneDocRef = doc(userScenesRef, sceneId); + const sceneSnapshot = await getDoc(sceneDocRef); + + if (sceneSnapshot.exists()) { + await updateDoc(sceneDocRef, { + glbPath: downloadURL, + updateTimestamp: serverTimestamp() + }); + STREET.notify.successMessage( + 'glTF has successfully uploaded to cloud!.' + ); + console.log('Firebase updateDoc fired'); + } else { + throw new Error('No existing sceneSnapshot exists.'); + } + } + ); + } catch (error) { + console.error(error); + } +}; + export { checkIfImagePathIsEmpty, deleteScene, @@ -197,5 +243,6 @@ export { getUserScenes, isSceneAuthor, updateScene, - updateSceneIdAndTitle + updateSceneIdAndTitle, + uploadGlbScene }; diff --git a/src/editor/components/modals/ScreenshotModal/ScreenshotModal.component.jsx b/src/editor/components/modals/ScreenshotModal/ScreenshotModal.component.jsx index 07a1fdda6..cae764727 100644 --- a/src/editor/components/modals/ScreenshotModal/ScreenshotModal.component.jsx +++ b/src/editor/components/modals/ScreenshotModal/ScreenshotModal.component.jsx @@ -18,7 +18,7 @@ import { db, storage } from '../../../services/firebase'; import { Button, Dropdown, Input } from '../../components'; import Toolbar from '../../scenegraph/Toolbar'; import Modal from '../Modal.jsx'; -// import { loginHandler } from '../SignInModal'; +import { uploadGlbScene } from '../../../api'; export const uploadThumbnailImage = async (uploadedFirstTime) => { try { @@ -99,6 +99,22 @@ export const uploadThumbnailImage = async (uploadedFirstTime) => { } }; +const uploadGlbToCloud = async (sceneId) => { + try { + uploadGlbScene(await Toolbar.convertSceneToGLTFBlob(), sceneId); + } catch (error) { + console.error('Error uploading and updating Firestore:', error); + + let errorMessage = `Error updating scene thumbnail: ${error}`; + if (error.code === 'storage/unauthorized') { + errorMessage = + 'Error updating glb file: only the scene author may upload glb file. Save this scene as your own for uploading.'; + } + + AFRAME.scenes[0].components['notify'].message(errorMessage, 'error'); + } +}; + const saveScreenshot = async (value) => { const screenshotEl = document.getElementById('screenshot'); screenshotEl.play(); @@ -148,7 +164,12 @@ function ScreenshotModal({ isOpen, onClose }) { { value: 'GLB glTF', label: 'GLB glTF', - onClick: Toolbar.exportSceneToGLTF + onClick: () => Toolbar.exportGLTFScene() + }, + { + value: 'Upload glTF to cloud', + label: 'Upload glTF to cloud', + onClick: () => uploadGlbToCloud(sceneId) }, { value: '.3dstreet.json', diff --git a/src/editor/components/scenegraph/Toolbar.js b/src/editor/components/scenegraph/Toolbar.js index 2cf2db943..86a1e98ea 100644 --- a/src/editor/components/scenegraph/Toolbar.js +++ b/src/editor/components/scenegraph/Toolbar.js @@ -271,24 +271,35 @@ export default class Toolbar extends Component { // AFRAME.INSPECTOR.close(); // } - static exportSceneToGLTF() { + static async convertSceneToGLTFBlob() { + const scene = AFRAME.scenes[0].object3D; + + try { + filterHelpers(scene, false); + const buffer = await AFRAME.INSPECTOR.exporters.gltf.parseAsync(scene, { + binary: true + }); + + const blobGlTFScene = new Blob([buffer], { + type: 'application/octet-stream' + }); + + return blobGlTFScene; + } catch (error) { + console.error(error); + } finally { + filterHelpers(scene, true); + } + } + + static async exportGLTFScene() { try { sendMetric('SceneGraph', 'exportGLTF'); const sceneName = getSceneName(AFRAME.scenes[0]); - const scene = AFRAME.scenes[0].object3D; - filterHelpers(scene, false); - AFRAME.INSPECTOR.exporters.gltf.parse( - scene, - function (buffer) { - filterHelpers(scene, true); - const blob = new Blob([buffer], { type: 'application/octet-stream' }); - saveBlob(blob, sceneName + '.glb'); - }, - function (error) { - console.error(error); - }, - { binary: true } - ); + + const blob = await this.convertSceneToGLTFBlob(); + saveBlob(blob, sceneName + '.glb'); + STREET.notify.successMessage('3DStreet scene exported as glTF file.'); } catch (error) { STREET.notify.errorMessage( From e5acaba44c03ef3aa45622c8b7fffce2f0a37a5f Mon Sep 17 00:00:00 2001 From: Rostyslav Nahornyi Date: Wed, 5 Jun 2024 16:24:51 +0300 Subject: [PATCH 2/3] fix: remove dist from git --- .gitignore | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index ceafe15fc..f8b7d6013 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,4 @@ npm-debug.log* build/ *.sw[pomn] # That's /src/lib/aframe-loader-3dtiles-component.min.js -dist/48406973e3ad4d65cbdd.js* -dist/aframe-mapbox-component.min.js -dist/notyf.min.css -dist/viewer-styles.css +dist \ No newline at end of file From 4439785768ec4de414fdb0464ca5b89c7e759726 Mon Sep 17 00:00:00 2001 From: Rostyslav Nahornyi Date: Wed, 5 Jun 2024 16:25:37 +0300 Subject: [PATCH 3/3] fix: linting --- .../modals/ScreenshotModal/ScreenshotModal.component.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/editor/components/modals/ScreenshotModal/ScreenshotModal.component.jsx b/src/editor/components/modals/ScreenshotModal/ScreenshotModal.component.jsx index cae764727..d52383ab9 100644 --- a/src/editor/components/modals/ScreenshotModal/ScreenshotModal.component.jsx +++ b/src/editor/components/modals/ScreenshotModal/ScreenshotModal.component.jsx @@ -9,7 +9,7 @@ import { updateDoc } from 'firebase/firestore'; -import { signIn } from '../../../api'; +import { signIn, uploadGlbScene } from '../../../api'; import { getDownloadURL, ref, uploadBytes } from 'firebase/storage'; import PropTypes from 'prop-types'; import { useAuthContext } from '../../../contexts'; @@ -18,7 +18,6 @@ import { db, storage } from '../../../services/firebase'; import { Button, Dropdown, Input } from '../../components'; import Toolbar from '../../scenegraph/Toolbar'; import Modal from '../Modal.jsx'; -import { uploadGlbScene } from '../../../api'; export const uploadThumbnailImage = async (uploadedFirstTime) => { try {