Add glTF Exporter

This commit is contained in:
JannisX11 2020-02-07 19:06:24 +01:00
parent 209ea0f5cf
commit 70b211a9da
7 changed files with 2570 additions and 13 deletions

View File

@ -522,7 +522,7 @@ class BoneAnimator extends GeneralAnimator {
bone.rotation.z += added_rotation.z
} else {
arr.forEach((n, i) => {
bone.rotation[getAxisLetter(i)] += Math.PI / (180 / n) * (i == 2 ? 1 : -1)
bone.rotation[getAxisLetter(i)] += Math.degToRad(n) * (i == 2 ? 1 : -1)
})
}
return this;
@ -806,6 +806,30 @@ class Keyframe {
}
return arr;
}
getFixed() {
if (this.channel === 'rotation') {
let fix = this.animator.group.mesh.fix_rotation;
return new THREE.Quaternion().setFromEuler(new THREE.Euler(
fix.x - Math.degToRad(this.calc('x')),
fix.y - Math.degToRad(this.calc('y')),
fix.z + Math.degToRad(this.calc('z')),
'ZYX'
));
} else if (this.channel === 'position') {
let fix = this.animator.group.mesh.fix_position;
return new THREE.Vector3(
fix.x - this.calc('x'),
fix.y + this.calc('y'),
fix.z + this.calc('z')
)
} else if (this.channel === 'scale') {
return new THREE.Vector3(
this.calc('x'),
this.calc('y'),
this.calc('z')
)
}
}
replaceOthers(save) {
var scope = this;
var arr = this.animator[this.channel];
@ -1152,15 +1176,14 @@ const Animator = {
outlines.children.length = 0
Canvas.updateAllPositions()
}
if (Animator.selected) {
Animator.selected.select()
} else if (Animator.animations.length) {
if (!Animator.selected && Animator.animations.length) {
Animator.animations[0].select()
}
if (isApp && !ModelMeta.animation_path && !Animator.animations.length && ModelMeta.export_path) {
//Load
findBedrockAnimation()
}
Animator.preview()
},
leave() {
Timeline.pause()
@ -1174,20 +1197,26 @@ const Animator = {
}
Canvas.updateAllBones()
},
preview() {
showDefaultPose(no_matrix_update) {
Group.all.forEach(group => {
var bone = group.mesh;
bone.rotation.copy(bone.fix_rotation)
bone.position.copy(bone.fix_position)
bone.scale.x = bone.scale.y = bone.scale.z = 1;
if (!no_matrix_update) group.mesh.updateMatrixWorld()
})
console.trace('default')
},
preview() {
Animator.showDefaultPose(true);
Group.all.forEach(group => {
Animator.animations.forEach(animation => {
if (animation.playing) {
animation.getBoneAnimator(group).displayFrame(Timeline.time)
}
})
group.mesh.updateMatrixWorld()
})
Animator.animations.forEach(animation => {

View File

@ -414,6 +414,7 @@ const MenuBar = {
'export_optifine_full',
'export_optifine_part',
'export_obj',
'export_gltf',
'upload_sketchfab',
]},
'export_over',

104
js/io/gltf.js Normal file
View File

@ -0,0 +1,104 @@
(function() {
var codec = new Codec('gltf', {
name: 'GLTF Model',
extension: 'gltf',
compile(options = 0, cb) {
let exporter = new THREE.GLTFExporter();
let animations = [];
let gl_scene = new THREE.Scene();
gl_scene.name = 'blockbench_export'
scene.children.forEach(object => {
if (object.isGroup || object.isElement) {
gl_scene.add(object);
}
});
if (!Modes.edit) {
Animator.showDefaultPose();
}
if (options.animations !== false) {
Animator.animations.forEach(animation => {
let tracks = [];
for (var uuid in animation.animators) {
let animator = animation.animators[uuid];
if (animator instanceof BoneAnimator && animator.getGroup()) {
for (var channel of animator.channels) {
if (animator[channel] && animator[channel].length) {
let times = [];
let values = [];
animator[channel].forEach(kf => {
times.push(kf.time);
Timeline.time = kf.time;
kf.getFixed().toArray(values, values.length);
})
let trackType = THREE.VectorKeyframeTrack;
if (channel === 'rotation') {
trackType = THREE.QuaternionKeyframeTrack;
channel = 'quaternion';
}
let track = new trackType(animator.group.mesh.uuid+'.'+channel, times, values, THREE.InterpolateLinear);
tracks.push(track);
}
}
} else if (animator instanceof BoneAnimator) {
console.log(`Skip export of track ${uuid.substr(0, 7)}... - No connected bone`)
}
}
if (tracks.length) {
let clip = new THREE.AnimationClip(animation.name, animation.length, tracks)
animations.push(clip);
} else {
console.log(`Skip export of animation ${animation.name} - No tracks generated`)
}
})
}
exporter.parse(gl_scene, (json) => {
cb(JSON.stringify(json));
gl_scene.children.forEach(object => {
if (object.isGroup || object.isElement) {
scene.add(object);
}
});
}, {
animations,
onlyVisible: false,
trs: true,
truncateDrawRange: false,
forcePowerOfTwoTextures: true,
exportFaceColors: false
});
},
export() {
var scope = codec;
scope.compile(0, content => {
Blockbench.export({
type: scope.name,
extensions: [scope.extension],
name: scope.fileName(),
startpath: scope.startPath(),
content,
custom_writer: isApp ? (a, b) => scope.write(a, b) : null,
}, path => scope.afterDownload(path))
})
}
})
BARS.defineActions(function() {
codec.export_action = new Action({
id: 'export_gltf',
icon: 'fas.fa-ring',
category: 'file',
click: function () {
codec.export()
}
})
})
})()

View File

@ -33,7 +33,6 @@ class ModelFormat {
this.integer_size = false;
this.locators = false;
this.canvas_limit = false;
this.outliner_name_pattern = false;
this.rotation_limit = false;
this.uv_rotation = false;
this.display_mode = false;
@ -54,7 +53,6 @@ class ModelFormat {
Merge.boolean(this, data, 'integer_size');
Merge.boolean(this, data, 'locators');
Merge.boolean(this, data, 'canvas_limit');
Merge.string(this, data, 'outliner_name_pattern');
Merge.boolean(this, data, 'rotation_limit');
Merge.boolean(this, data, 'uv_rotation');
Merge.boolean(this, data, 'display_mode');
@ -650,6 +648,7 @@ function uploadSketchfabModel() {
name: {label: 'dialog.sketchfab_uploader.name'},
description: {label: 'dialog.sketchfab_uploader.description', type: 'textarea'},
tags: {label: 'dialog.sketchfab_uploader.tags', placeholder: 'Tag1 Tag2'},
animations: {label: 'dialog.sketchfab_uploader.animations', value: true, type: 'checkbox', conditions: (Format.animation_mode && Animator.animations.length)},
draft: {label: 'dialog.sketchfab_uploader.draft', type: 'checkbox'},
// isPublished (draft)
// options.background.color = '#ffffff' (Background Color)
@ -679,6 +678,9 @@ function uploadSketchfabModel() {
settings.sketchfab_token.value = formResult.token
/*
var archive = new JSZip();
var model_data = Codecs.obj.compile({all_files: true})
archive.file('model.obj', model_data.obj)
@ -689,10 +691,16 @@ function uploadSketchfabModel() {
archive.file(pathToName(tex.name) + '.png', tex.getBase64(), {base64: true});
}
}
archive.generateAsync({type: 'blob'}).then(blob => {
var file = new File([blob], 'model.zip', {type: 'application/x-zip-compressed'})
*/
Codecs.gltf.compile({animations: formResult.animations}, (content) => {
var blob = new Blob([content], {type: "text/plain;charset=utf-8"});
var file = new File([blob], 'model.gltf')
data.append('modelFile', file)
$.ajax({
@ -723,6 +731,75 @@ function uploadSketchfabModel() {
})
dialog.show()
}
/*function uploadPastebinModel() {
if (elements.length === 0) {
return;
}
var dialog = new Dialog({
id: 'sketchfab_uploader',
title: 'dialog.sketchfab_uploader.title',
width: 540,
form: {
token: {label: 'dialog.sketchfab_uploader.token', value: settings.sketchfab_token.value},
about_token: {type: 'text', text: tl('dialog.sketchfab_uploader.about_token', ['[sketchfab.com/settings/password](https://sketchfab.com/settings/password)'])},
name: {label: 'dialog.sketchfab_uploader.name'},
description: {label: 'dialog.sketchfab_uploader.description', type: 'textarea'},
tags: {label: 'dialog.sketchfab_uploader.tags', placeholder: 'Tag1 Tag2'},
draft: {label: 'dialog.sketchfab_uploader.draft', type: 'checkbox'},
// isPublished (draft)
// options.background.color = '#ffffff' (Background Color)
// Category
divider: '_',
private: {label: 'dialog.sketchfab_uploader.private', type: 'checkbox'},
password: {label: 'dialog.sketchfab_uploader.password'},
},
onConfirm: function(formResult) {
if (formResult.token && !formResult.name) {
Blockbench.showQuickMessage('message.sketchfab.name_or_token', 1800)
return;
}
var model = Codecs.project.compile({compressed: true})
var data = new FormData()
data.append('api_option', 'paste');
data.append('api_dev_key', '');
data.append('api_paste_code', model);
data.append('api_paste_private', 1);
data.append('api_paste_name', formResult.name)
data.append('api_paste_expire_date', '40M')
data.append('api_user_key', formResult.user_key)
//settings.sketchfab_token.value = formResult.token
$.ajax({
url: 'https://pastebin.com/api/api_post.php',
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(response) {
cl(response);
Blockbench.showMessageBox({
title: tl('message.sketchfab.success'),
message: `[${formResult.name} on Sketchfab](https://sketchfab.com/models/${response.uid})`,
icon: 'icon-sketchfab',
})
},
error: function(response) {
cl(response);
Blockbench.showQuickMessage('message.sketchfab.error', 1500)
console.error(response);
}
})
dialog.hide()
}
})
dialog.show()
}*/
//Json
function compileJSON(object, options) {
var output = ''

View File

@ -675,7 +675,9 @@ const Canvas = {
vs[6], vs[4],
vs[1], vs[3]
]
return new THREE.Line(geometry, Canvas.outlineMaterial)
var line = new THREE.Line(geometry, Canvas.outlineMaterial);
line.no_export = true;
return line;
},
buildOutline(obj) {
if (obj.visibility == false) return;
@ -807,6 +809,7 @@ const Canvas = {
var lines = new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({color: gizmo_colors.grid}));
lines.geometry.translate(-cube.origin[0], -cube.origin[1], -cube.origin[2]);
lines.no_export = true;
return lines;
}

View File

@ -327,6 +327,7 @@
"dialog.sketchfab_uploader.about_token": "The token is used to connect Blockbench to your Sketchfab account. You can find it on %0",
"dialog.sketchfab_uploader.name": "Model Name",
"dialog.sketchfab_uploader.description": "Description",
"dialog.sketchfab_uploader.animations": "Animations",
"dialog.sketchfab_uploader.tags": "Tags",
"dialog.sketchfab_uploader.draft": "Draft",
"dialog.sketchfab_uploader.private": "Private (Pro)",
@ -630,7 +631,9 @@
"action.export_optifine_part": "Export OptiFine Part",
"action.export_optifine_part.desc": "Export a single part for an OptiFine entity model",
"action.export_obj": "Export OBJ Model",
"action.export_obj.desc": "Export a Wavefront OBJ model for rendering or game engines",
"action.export_obj.desc": "Export a Wavefront OBJ model for rendering",
"action.export_gltf": "Export As glTF",
"action.export_gltf.desc": "Export model and animations as glTF file to use in other 3D applications",
"action.upload_sketchfab": "Sketchfab Upload",
"action.upload_sketchfab.desc": "Upload your model to Sketchfab",

2340
lib/GLTFExporter.js Normal file

File diff suppressed because it is too large Load Diff