зеркало из https://github.com/mozilla/Spoke.git
Export using THREE.GLTFExporter
This commit is contained in:
Родитель
3a94487f7c
Коммит
e775a60003
|
@ -12804,8 +12804,8 @@
|
|||
}
|
||||
},
|
||||
"three": {
|
||||
"version": "github:mozillareality/three.js#e86afa5bb698350c2ebddcda70b3e7929143444f",
|
||||
"from": "github:mozillareality/three.js#hubs-editor/dev"
|
||||
"version": "github:mozillareality/three.js#27dfd525502e876af2c8e6d195eb1579dc6bd2bc",
|
||||
"from": "github:mozillareality/three.js#hubs/dev-v2"
|
||||
},
|
||||
"three-pathfinding": {
|
||||
"version": "0.5.5",
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"react-tooltip": "^3.6.1",
|
||||
"selfsigned": "^1.10.3",
|
||||
"signals": "^1.0.0",
|
||||
"three": "github:mozillareality/three.js#hubs-editor/dev",
|
||||
"three": "github:mozillareality/three.js#hubs/dev-v2",
|
||||
"ws": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -22,11 +22,15 @@ export default class Project extends EventEmitter {
|
|||
}
|
||||
|
||||
getUrl(relativePath) {
|
||||
return new URL(`api/files/${relativePath}`, window.location).href;
|
||||
return new URL(relativePath, this.serverUrl).href;
|
||||
}
|
||||
|
||||
fetch(relativePath, options) {
|
||||
return fetch(this.getUrl(relativePath), options);
|
||||
}
|
||||
|
||||
async writeBlob(relativePath, blob) {
|
||||
const res = await fetch(this.serverUrl + relativePath, {
|
||||
const res = await this.fetch(relativePath, {
|
||||
method: "POST",
|
||||
body: blob
|
||||
});
|
||||
|
@ -37,7 +41,7 @@ export default class Project extends EventEmitter {
|
|||
}
|
||||
|
||||
async readBlob(relativePath) {
|
||||
const res = await fetch(this.serverUrl + relativePath);
|
||||
const res = await this.fetch(relativePath);
|
||||
|
||||
const blob = await res.blob();
|
||||
|
||||
|
@ -45,7 +49,7 @@ export default class Project extends EventEmitter {
|
|||
}
|
||||
|
||||
async readJSON(relativePath) {
|
||||
const res = await fetch(this.serverUrl + relativePath);
|
||||
const res = await this.fetch(relativePath);
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
|
@ -53,9 +57,8 @@ export default class Project extends EventEmitter {
|
|||
}
|
||||
|
||||
async writeJSON(relativePath, data) {
|
||||
const res = await fetch(this.serverUrl + relativePath, {
|
||||
const res = await this.fetch(relativePath, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
|
@ -65,7 +68,7 @@ export default class Project extends EventEmitter {
|
|||
}
|
||||
|
||||
async mkdir(relativePath) {
|
||||
const res = await fetch(this.serverUrl + relativePath + "?mkdir=true", { method: "POST" });
|
||||
const res = await this.fetch(relativePath + "?mkdir=true", { method: "POST" });
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
|
@ -73,7 +76,7 @@ export default class Project extends EventEmitter {
|
|||
}
|
||||
|
||||
async openFile(relativePath) {
|
||||
const res = await fetch(this.serverUrl + relativePath + "?open=true", {
|
||||
const res = await this.fetch(relativePath + "?open=true", {
|
||||
method: "POST"
|
||||
});
|
||||
|
||||
|
@ -126,7 +129,7 @@ export default class Project extends EventEmitter {
|
|||
}
|
||||
|
||||
async optimizeScene(sceneURI, outputURI) {
|
||||
const res = await fetch(this.serverUrl + "/api/optimize", {
|
||||
const res = await this.fetch("/api/optimize", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
|
|
|
@ -7,7 +7,7 @@ import RemoveObjectCommand from "./commands/RemoveObjectCommand";
|
|||
import AddObjectCommand from "./commands/AddObjectCommand";
|
||||
import { Components } from "./components";
|
||||
import SceneReferenceComponent from "./components/SceneReferenceComponent";
|
||||
import { loadScene, loadSerializedScene, serializeScene } from "./SceneLoader";
|
||||
import { loadScene, loadSerializedScene, serializeScene, exportScene } from "./SceneLoader";
|
||||
import DirectionalLightComponent from "./components/DirectionalLightComponent";
|
||||
import AmbientLightComponent from "./components/AmbientLightComponent";
|
||||
import ShadowComponent from "./components/ShadowComponent";
|
||||
|
@ -313,6 +313,10 @@ export default class Editor {
|
|||
return serializeScene(this.scene, sceneURI || this.scene.userData._uri);
|
||||
}
|
||||
|
||||
exportScene() {
|
||||
return exportScene(this.scene);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
addObject(object, parent) {
|
||||
|
@ -395,20 +399,6 @@ export default class Editor {
|
|||
this.textures[texture.uuid] = texture;
|
||||
}
|
||||
|
||||
exportScene() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const gltfExporter = new THREE.GLTFExporter();
|
||||
gltfExporter.parseParts(this.scene, resolve, {
|
||||
trs: true,
|
||||
embedImages: false
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
addHelper = (function() {
|
||||
|
|
|
@ -62,48 +62,39 @@ function loadGLTF(url) {
|
|||
});
|
||||
}
|
||||
|
||||
function postProcessGLTF(scene, sceneURI, gltf) {
|
||||
const { json } = gltf;
|
||||
export async function exportScene(scene) {
|
||||
// TODO: export animations
|
||||
const chunks = await new Promise((resolve, reject) => {
|
||||
new THREE.GLTFExporter().parseChunks(scene, resolve, reject, {
|
||||
mode: "gltf",
|
||||
onlyVisible: false
|
||||
});
|
||||
});
|
||||
|
||||
const buffers = json.buffers;
|
||||
const buffers = chunks.json.buffers;
|
||||
|
||||
if (buffers && buffers.length > 0 && buffers[0].uri === undefined) {
|
||||
buffers[0].uri = scene.name + ".bin";
|
||||
}
|
||||
|
||||
const absoluteSceneURI = new URL(sceneURI, window.location).href;
|
||||
|
||||
if (Array.isArray(json.images)) {
|
||||
for (const image of json.images) {
|
||||
image.uri = absoluteToRelativeURL(absoluteSceneURI, image.uri);
|
||||
}
|
||||
}
|
||||
|
||||
const componentNames = Components.map(component => component.componentName);
|
||||
|
||||
for (const node of gltf.json.nodes) {
|
||||
for (const node of chunks.json.nodes) {
|
||||
if (!node.extras) continue;
|
||||
if (!node.extensions) {
|
||||
node.extensions = [];
|
||||
}
|
||||
for (const component of node.extras._components) {
|
||||
if (componentNames.includes(component.name)) {
|
||||
node.extensions.push(component);
|
||||
if (node.extras._components) {
|
||||
for (const component of node.extras._components) {
|
||||
if (componentNames.includes(component.name)) {
|
||||
node.extensions.push(component);
|
||||
}
|
||||
}
|
||||
delete node.extras._components;
|
||||
}
|
||||
delete node.extras._components;
|
||||
}
|
||||
|
||||
return gltf;
|
||||
}
|
||||
|
||||
export function exportScene(scene, sceneURI) {
|
||||
return new Promise(resolve => {
|
||||
new THREE.GLTFExporter().parseParts(scene, resolve, {
|
||||
embedImages: false,
|
||||
onlyVisible: false
|
||||
});
|
||||
}).then(gltf => postProcessGLTF(scene, sceneURI, gltf));
|
||||
return chunks;
|
||||
}
|
||||
|
||||
function inflateGLTFComponents(scene, addComponent) {
|
||||
|
|
|
@ -279,22 +279,37 @@ class EditorContainer extends Component {
|
|||
};
|
||||
|
||||
onExport = async outputPath => {
|
||||
const scene = this.props.editor.scene;
|
||||
|
||||
const gltfPath = outputPath + "/" + scene.name + ".gltf";
|
||||
const binPath = outputPath + "/" + scene.name + ".bin";
|
||||
|
||||
const { json, bin } = await exportScene(scene, gltfPath);
|
||||
// Export current editor scene using THREE.GLTFExporter
|
||||
const { json, buffers, images } = await this.props.editor.exportScene();
|
||||
|
||||
// Ensure the output directory exists
|
||||
await this.props.project.mkdir(outputPath);
|
||||
|
||||
// Write the .gltf file
|
||||
const scene = this.props.editor.scene;
|
||||
const gltfPath = outputPath + "/" + scene.name + ".gltf";
|
||||
await this.props.project.writeJSON(gltfPath, json);
|
||||
|
||||
if (bin) {
|
||||
await this.props.project.writeBlob(binPath, bin);
|
||||
// Write .bin files
|
||||
for (const [index, buffer] of buffers.entries()) {
|
||||
if (buffer !== undefined) {
|
||||
const bufferName = json.buffers[index].uri;
|
||||
await this.props.project.writeBlob(outputPath + "/" + bufferName, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Write image files
|
||||
for (const [index, image] of images.entries()) {
|
||||
if (image !== undefined) {
|
||||
const imageName = json.images[index].uri;
|
||||
await this.props.project.writeBlob(outputPath + "/" + imageName, image);
|
||||
}
|
||||
}
|
||||
|
||||
// Run optimizations on .gltf and overwrite any existing files
|
||||
await this.props.project.optimizeScene(gltfPath, gltfPath);
|
||||
|
||||
// Close modal
|
||||
this.setState({ openModal: null });
|
||||
};
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ import fs from "fs-extra";
|
|||
import chokidar from "chokidar";
|
||||
import debounce from "lodash.debounce";
|
||||
import opn from "opn";
|
||||
import generateUnlitTextures from "gltf-unlit-generator";
|
||||
import { contentHashAndCopy } from "./gltf";
|
||||
// import generateUnlitTextures from "gltf-unlit-generator";
|
||||
|
||||
async function getProjectHierarchy(projectPath) {
|
||||
async function buildProjectNode(filePath, name, ext, isDirectory, uri) {
|
||||
|
@ -183,45 +183,45 @@ export default async function startServer(options) {
|
|||
)
|
||||
);
|
||||
|
||||
router.post("/api/files/:filePath*", koaBody({ multipart: true }), async ctx => {
|
||||
router.post("/api/files/:filePath*", async ctx => {
|
||||
const filePath = ctx.params.filePath ? path.resolve(projectPath, ctx.params.filePath) : projectPath;
|
||||
|
||||
if (ctx.request.query.open) {
|
||||
// Attempt to open file at filePath with the default application for that file type.
|
||||
opn(filePath);
|
||||
|
||||
ctx.body = {
|
||||
success: true
|
||||
};
|
||||
} else if (ctx.request.files && ctx.request.files.file) {
|
||||
const file = ctx.request.files.file;
|
||||
|
||||
await fs.rename(file.path, filePath);
|
||||
|
||||
ctx.body = {
|
||||
success: true
|
||||
};
|
||||
} else if (ctx.request.type === "application/json") {
|
||||
await fs.writeJSON(filePath, ctx.request.body, { spaces: 2 });
|
||||
ctx.body = {
|
||||
success: true
|
||||
};
|
||||
} else if (ctx.request.type === "application/octet-stream") {
|
||||
const bytes = await new Promise(resolve => {
|
||||
ctx.req.on("readable", () => {
|
||||
resolve(ctx.req.read());
|
||||
});
|
||||
});
|
||||
await fs.writeFile(filePath, bytes);
|
||||
ctx.body = { success: true };
|
||||
} else if (ctx.request.query.mkdir) {
|
||||
// Make the directory at filePath if it doesn't already exist.
|
||||
await fs.ensureDir(filePath);
|
||||
|
||||
ctx.body = {
|
||||
success: true
|
||||
};
|
||||
} else {
|
||||
ctx.throw(400, "Invalid request");
|
||||
// If uploading as text body, write it to filePath using the stream API.
|
||||
const writeStream = fs.createWriteStream(filePath, { flags: "w" });
|
||||
|
||||
ctx.req.pipe(writeStream);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
function cleanUp() {
|
||||
// eslint-disable-next-line
|
||||
writeStream.removeListener("finish", onFinish);
|
||||
// eslint-disable-next-line
|
||||
writeStream.removeListener("error", onError);
|
||||
}
|
||||
|
||||
function onFinish() {
|
||||
cleanUp();
|
||||
resolve();
|
||||
}
|
||||
|
||||
function onError(err) {
|
||||
cleanUp();
|
||||
reject(err);
|
||||
}
|
||||
|
||||
writeStream.on("finish", onFinish);
|
||||
writeStream.on("error", onError);
|
||||
});
|
||||
}
|
||||
|
||||
ctx.body = { success: true };
|
||||
});
|
||||
|
||||
router.post("/api/optimize", koaBody(), async ctx => {
|
||||
|
@ -236,11 +236,12 @@ export default async function startServer(options) {
|
|||
const outputPath = path.resolve(projectPath, outputURI.replace("/api/files/", ""));
|
||||
const outputDirPath = path.dirname(outputPath);
|
||||
|
||||
await generateUnlitTextures(scenePath, outputDirPath);
|
||||
// TODO: fix unlit texture generation
|
||||
// await generateUnlitTextures(scenePath, outputDirPath);
|
||||
|
||||
const json = await fs.readJSON(outputPath);
|
||||
|
||||
json.images = await contentHashAndCopy(json.images, sceneDirPath, outputDirPath);
|
||||
json.images = await contentHashAndCopy(json.images, sceneDirPath, outputDirPath, true);
|
||||
json.buffers = await contentHashAndCopy(json.buffers, sceneDirPath, outputDirPath, true);
|
||||
|
||||
await fs.writeJSON(outputPath, json);
|
||||
|
|
Загрузка…
Ссылка в новой задаче