mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-03-19 17:01:55 +08:00
Add glTF Exporter
This commit is contained in:
parent
209ea0f5cf
commit
70b211a9da
@ -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 => {
|
||||
|
@ -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
104
js/io/gltf.js
Normal 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()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
})()
|
85
js/io/io.js
85
js/io/io.js
@ -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 = ''
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
2340
lib/GLTFExporter.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user