This commit is contained in:
JannisX11 2019-09-06 00:16:54 +02:00
parent e2e7b70905
commit fd62f66ef5
41 changed files with 854 additions and 719 deletions

View File

@ -276,7 +276,7 @@
}
#keybindlist {
max-height: 600px;
margin-bottom: 20px;
padding-bottom: 25px;
overflow-y: scroll;
}
#keybindlist > li {
@ -643,6 +643,9 @@
#uv_dialog h2.dialog_handle.entity_mode_only {
margin: 0;
}
#uv_dialog_all .UVEditor .uv_transform_info {
top: 30px;
}
/*Action Control*/
#action_selector {

View File

@ -170,13 +170,18 @@
z-index: 101;
min-width: 100px;
max-width: 200px;
background-color: var(--color-ui);
color: var(--color-light);
background-color: var(--color-bright_ui);
color: var(--color-text_acc);
box-shadow: 0 0 2px black;
text-align: center;
cursor: default;
top: 40px;
left: 60px;
right: 0px;
left: 0px;
pointer-events: none;
}
#uv_dialog_all .uv_message_box {
top: 60px;
}
.selection_rectangle {
position: absolute;
@ -488,8 +493,10 @@
padding: 24px;
max-height: 600px;
}
#start_screen section right {
#start_screen section right > ul {
max-height: 465px;
overflow-y: auto;
padding-right: 5px;
}
#start_screen left {
flex-grow: 0;

View File

@ -581,7 +581,7 @@
<a class="open-in-browser" href="https://bgrins.github.io/spectrum">Spectrum</a>,
<a class="open-in-browser" href="https://github.com/jnordberg/gif.js">gif.js</a>,
<a class="open-in-browser" href="https://stuk.github.io/jszip/">JSZip</a>,
<a class="open-in-browser" href="https://github.com/rotemdan/lzutf8.js">LZ-UTF8</a>
<a class="open-in-browser" href="https://github.com/rotemdan/lzutf8.js">LZ-UTF8</a>,
<a class="open-in-browser" href="https://github.com/markedjs/marked">Marked</a>
</p>
</div>
@ -854,10 +854,7 @@
<div class="texture_name">{{ texture.name }}</div>
<div class="texture_res">{{ texture.error
? texture.getErrorMessage()
: (Format.single_texture
? (texture.width + ' x ' + texture.height + 'px')
: (texture.ratio == 1? texture.width + 'px': (texture.width + 'px, ' + texture.frameCount+'f'))
)
: texture.width + ' x ' + texture.height + 'px'
}}</div>
</li>
</ul>

View File

@ -611,10 +611,8 @@ class Keyframe {
}
extend(data) {
Merge.number(this, data, 'time')
cl('0' , data)
if (this.transform) {
cl('1' , this)
if (data.values != undefined) {
if (typeof data.values == 'number' || typeof data.values == 'string') {
data.x = data.y = data.z = data.values;
@ -632,7 +630,6 @@ class Keyframe {
Merge.string(this, data, 'w')
Merge.boolean(this, data, 'isQuaternion')
} else {
cl('2' , data)
if (data.values) {
data.effect = data.values.effect;
data.locator = data.values.locator;
@ -784,7 +781,7 @@ class Keyframe {
if (kf.channel != scope.channel) select_tool = false;
})
this.selected = true
updateKeyframeSelection()
TickUpdates.keyframe_selection = true;
if (select_tool) {
switch (this.channel) {
case 'rotation': BarItems.rotate_tool.select(); break;
@ -960,6 +957,76 @@ function findBedrockAnimation() {
}
}
Clipbench.setKeyframes = function() {
var keyframes = Timeline.selected;
Clipbench.keyframes = []
if (!keyframes || keyframes.length === 0) {
return;
}
var first = keyframes[0];
var single_animator;
keyframes.forEach(function(kf) {
if (kf.time < first.time) {
first = kf
}
if (single_animator && single_animator !== kf.animator.uuid) {
single_animator = false;
} else if (single_animator == undefined) {
single_animator = kf.animator.uuid;
}
})
keyframes.forEach(function(kf) {
var copy = kf.getUndoCopy();
copy.time_offset = kf.time - first.time;
if (single_animator != false) {
delete copy.animator;
}
Clipbench.keyframes.push(copy)
})
if (isApp) {
clipboard.writeHTML(JSON.stringify({type: 'keyframes', content: Clipbench.keyframes}))
}
}
Clipbench.pasteKeyframes = function() {
if (isApp) {
var raw = clipboard.readHTML()
try {
var data = JSON.parse(raw)
if (data.type === 'keyframes' && data.content) {
Clipbench.keyframes = data.content
}
} catch (err) {}
}
if (Clipbench.keyframes && Clipbench.keyframes.length) {
if (!Animator.selected) return;
var keyframes = [];
Undo.initEdit({keyframes});
Clipbench.keyframes.forEach(function(data, i) {
if (data.animator) {
var animator = Animator.selected.animators[data.animator];
if (animator && !Timeline.animators.includes(animator)) {
animator.select();
}
} else {
var animator = Timeline.selected_animator;
}
if (animator) {
var kf = animator.createKeyframe(data, Timeline.time + data.time_offset, data.channel)
keyframes.push(kf);
kf.select(i ? {ctrlOrCmd: true} : null)
}
})
Animator.preview()
Undo.finishEdit('paste keyframes');
}
}
const Animator = {
possible_channels: {rotation: true, position: true, scale: true, sound: true, particle: true},
open: false,
@ -1553,7 +1620,7 @@ const Timeline = {
}
kf.selected = false
})
updateKeyframeSelection()
TickUpdates.keyframe_selection = true;
},
start() {
if (!Animator.selected) return;
@ -1716,12 +1783,16 @@ BARS.defineActions(function() {
if (m_index > 3) {
path = path.substr(0, m_index) + osfs + 'animations' + osfs + pathToName(ModelMeta.export_path, true)
}
path.replace(/\.geo\./, 'animation')
if (path.match(/\.geo\.json$/)) {
path = path.replace(/\.geo\.json$/, '.animation.json')
} else {
path = path.replace(/\.json$/, '.animation.json')
}
}
Blockbench.export({
type: 'JSON Animation',
extensions: ['json'],
name: Project.geometry_name||'animation',
name: (Project.geometry_name||'model')+'.animation',
startpath: path,
content: content,
}, (real_path) => {

View File

@ -164,7 +164,8 @@ const Blockbench = {
jq_dialog.addClass('draggable')
jq_dialog.draggable({
handle: ".dialog_handle"
handle: ".dialog_handle",
containment: '#page_wrapper'
})
var x = ($(window).width()-540)/2
jq_dialog.css('left', x+'px')
@ -250,6 +251,7 @@ const Blockbench = {
currentwindow,
{
title: options.title ? options.title : '',
dontAddToRecent: true,
filters: [{
name: options.type ? options.type : options.extensions[0],
extensions: options.extensions
@ -423,6 +425,7 @@ const Blockbench = {
}
} else {
ElecDialogs.showSaveDialog(currentwindow, {
dontAddToRecent: true,
filters: [ {
name: options.type,
extensions: options.extensions

View File

@ -52,6 +52,13 @@ const mouse_pos = {x:0,y:0}
const sort_collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
$.ajaxSetup({ cache: false });
(function() {
var last_welcome = localStorage.getItem('welcomed_version');
if (!last_welcome || last_welcome.replace(/.\d+$/, '') != appVersion.replace(/.\d+$/, '')) {
Blockbench.addFlag('after_update');
}
localStorage.setItem('welcomed_version', appVersion);
})();
function initializeApp() {
@ -81,11 +88,6 @@ function initializeApp() {
} else {
$('.web_only').remove()
}
var last_welcome = localStorage.getItem('welcomed_version');
if (!last_welcome || last_welcome.replace(/.\d+$/, '') != appVersion.replace(/.\d+$/, '')) {
Blockbench.addFlag('after_update')
localStorage.setItem('welcomed_version', appVersion)
}
BARS.setupActions()
BARS.setupToolbars()
BARS.setupVue()
@ -524,6 +526,10 @@ const TickUpdates = {
delete TickUpdates.keyframes;
Vue.nextTick(Timeline.update)
}
if (TickUpdates.keyframe_selection) {
delete TickUpdates.keyframe_selection;
Vue.nextTick(updateKeyframeSelection)
}
}
}
const Clipbench = {
@ -542,7 +548,7 @@ const Clipbench = {
} else if (Animator.open) {
if (Timeline.selected.length) {
Clipbench.setKeyframes(Timeline.selected)
Clipbench.setKeyframes()
if (cut) {
BarItems.delete.trigger()
}
@ -577,44 +583,11 @@ const Clipbench = {
} else if (display_mode) {
DisplayMode.paste()
} else if (Animator.open) {
//
if (isApp) {
var raw = clipboard.readHTML()
try {
var data = JSON.parse(raw)
if (data.type === 'keyframes' && data.content) {
Clipbench.keyframes = data.content
}
} catch (err) {}
}
if (Clipbench.keyframes && Clipbench.keyframes.length) {
if (!Animator.selected) return;
var animator = Timeline.selected_animator
if (animator) {
var keyframes = [];
Undo.initEdit({keyframes});
Clipbench.keyframes.forEach(function(data, i) {
var kf = animator.createKeyframe(data, Timeline.time + data.time_offset, data.channel)
keyframes.push(kf);
kf.select(i ? {ctrlOrCmd: true} : null)
})
Animator.preview()
Undo.finishEdit('paste keyframes');
}
}
Clipbench.pasteKeyframes()
} else if (p == 'uv' || p == 'preview') {
main_uv.paste(event)
} else if (p == 'textures' && isApp) {
var img = clipboard.readImage()
if (img) {
var dataUrl = img.toDataURL()
var texture = new Texture({name: 'pasted', folder: 'block' }).fromDataURL(dataUrl).fillParticle().add(true)
setTimeout(function() {
texture.openMenu()
},40)
}
Clipbench.pasteTextures();
} else if (p == 'outliner') {
Undo.initEdit({outliner: true, elements: [], selection: true});
@ -669,17 +642,6 @@ const Clipbench = {
Undo.finishEdit('paste', {outliner: true, elements: selected, selection: true});
}
},
setTexture(texture) {
//Sets the raw image of the texture
if (!isApp) return;
if (texture.mode === 'bitmap') {
var img = nativeImage.createFromDataURL(texture.source)
} else {
var img = nativeImage.createFromPath(texture.source.split('?')[0])
}
clipboard.writeImage(img)
},
setGroup(group) {
if (!group) {
Clipbench.group = undefined
@ -702,27 +664,6 @@ const Clipbench = {
clipboard.writeHTML(JSON.stringify({type: 'elements', content: Clipbench.elements}))
}
},
setKeyframes(keyframes) {
Clipbench.keyframes = []
if (!keyframes || keyframes.length === 0) {
return;
}
var first = keyframes[0];
keyframes.forEach(function(kf) {
if (kf.time < first.time) {
first = kf
}
})
keyframes.forEach(function(kf) {
var copy = kf.getUndoCopy();
copy.time_offset = kf.time - first.time;
delete copy.animator;
Clipbench.keyframes.push(copy)
})
if (isApp) {
clipboard.writeHTML(JSON.stringify({type: 'keyframes', content: Clipbench.keyframes}))
}
},
setText(text) {
if (isApp) {
clipboard.writeText(text)

View File

@ -119,6 +119,7 @@ function addRecentProject(data) {
icon: data.icon,
day: new Date().dayOfYear()
})
app.addRecentDocument(data.path)
if (recent_projects.length > Math.clamp(settings.recent_projects.value, 0, 256)) {
recent_projects.pop()
}

View File

@ -1760,7 +1760,7 @@ window.changeDisplaySkin = function() {
settings.display_skin.value = 'username:'+text
updateDisplaySkin()
})
} else {
} else if (result < buttons.length-1) {
settings.display_skin.value = false
updateDisplaySkin()
}

View File

@ -1351,7 +1351,7 @@ const BARS = {
//
Toolbars = {}
var stored = localStorage.getItem('toolbars')
if (stored && localStorage.getItem('welcomed_version') == appVersion) {
if (stored && !Blockbench.hasFlag('after_update')) {
stored = JSON.parse(stored)
if (typeof stored === 'object') {
BARS.stored = stored

View File

@ -202,7 +202,8 @@ function Dialog(settings) {
if (this.draggable !== false) {
jq_dialog.addClass('draggable')
jq_dialog.draggable({
handle: ".dialog_handle"
handle: ".dialog_handle",
containment: '#page_wrapper'
})
var x = Math.clamp(($(window).width()-540)/2, 0, 2000)
jq_dialog.css('left', x+'px')

View File

@ -115,7 +115,7 @@ class ResizeLine {
this.node = jq.get(0)
jq.draggable({
axis: this.horizontal ? 'y' : 'y',
containment: 'document',
containment: '#page_wrapper',
revert: true,
start: function(e, u) {
scope.before = data.get()
@ -721,7 +721,7 @@ function showDialog(dialog) {
if (obj.hasClass('draggable')) {
obj.draggable({
handle: ".dialog_handle",
containment: 'body'
containment: '#page_wrapper'
})
var x = ($(window).width()-obj.outerWidth()) / 2;
var top = ($(window).height() - obj.outerHeight()) / 2;

View File

@ -36,11 +36,9 @@ var codec = new Codec('project', {
model.parent = Project.parent;
model.ambientocclusion = Project.ambientocclusion
}
if (Project.box_uv) {
model.resolution = {
width: Project.texture_width || 16,
height: Project.texture_height || 16,
}
model.resolution = {
width: Project.texture_width || 16,
height: Project.texture_height || 16,
}
if (options.flag) {
model.flag = options.flag;

View File

@ -56,6 +56,7 @@ function findEntityTexture(mob, return_path) {
'tropicalfish_b': 'fish/tropical_b',
'panda': 'panda/panda',
'fishing_hook': 'fishhook',
'ravager': 'illager/ravager',
}
mob = mob.split(':')[0].replace(/^geometry\./, '')
var path = textures[mob]

View File

@ -74,6 +74,9 @@ class ModelFormat {
}
var center = Format.bone_rig ? 8 : 0;
previews.forEach(preview => {
if (preview.isOrtho) {
preview.setOrthographicCamera(preview.angle);
}
preview.camOrtho.position.y += center - preview.controls.target.y;
preview.controls.target.set(0, center, 0);
})
@ -251,11 +254,11 @@ class Codec {
export() {
var scope = this;
Blockbench.export({
type: this.name,
extensions: [this.extension],
name: this.fileName(),
startpath: this.startPath(),
content: this.compile(),
type: scope.name,
extensions: [scope.extension],
name: scope.fileName(),
startpath: scope.startPath(),
content: scope.compile(),
custom_writer: isApp ? (a, b) => scope.write(a, b) : null,
}, path => scope.afterDownload(path))
}

View File

@ -209,7 +209,20 @@ var codec = new Codec('modded_entity', {
}
}
})
},
export() {
var scope = this;
Blockbench.showMessageBox({translateKey: 'no_format_import'}, function() {
Blockbench.export({
type: scope.name,
extensions: [scope.extension],
name: scope.fileName(),
startpath: scope.startPath(),
content: scope.compile(),
custom_writer: isApp ? (a, b) => scope.write(a, b) : null,
}, path => scope.afterDownload(path))
})
}
})

View File

@ -77,6 +77,7 @@ var part_codec = new Codec('optifine_part', {
parse(model, path, add) {
Project.box_uv = false;
var new_cubes = [];
var box_uv_changed = false;
var import_group = add ? new Group({
name: pathToName(path)
}).init() : 'root';
@ -118,7 +119,7 @@ var part_codec = new Codec('optifine_part', {
-submodel.rotate[1],
submodel.rotate[2],
]
base_cube = new Cube({
var base_cube = new Cube({
from: [
-cs[0]-cs[3],
-cs[1]-cs[4],
@ -133,8 +134,15 @@ var part_codec = new Codec('optifine_part', {
rotation,
origin
})
if (box.uvNorth) {
if (!add) Project.box_uv = false;
if (box.textureOffset) {
if (!add && !box_uv_changed) Project.box_uv = true;
box_uv_changed = true;
base_cube.extend({
uv_offset: box.textureOffset
})
} else {
if (!add && !box_uv_changed) Project.box_uv = false;
box_uv_changed = true;
base_cube.extend({
faces: {
north: {uv: convertUVCoords(box.uvNorth)},
@ -145,11 +153,6 @@ var part_codec = new Codec('optifine_part', {
down: {uv: convertUVCoords(box.uvDown)},
}
})
} else {
if (!add) Project.box_uv = true;
base_cube.extend({
uv_offset: box.textureOffset
})
}
if (submodel.translate) {

View File

@ -497,11 +497,11 @@ class Cube extends NonGroup {
pos.y -= this.origin[1]
pos.z -= this.origin[2]
var r = m.getWorldQuaternion(new THREE.Quaternion())
pos.applyQuaternion(r)
pos.add(m.getWorldPosition(new THREE.Vector3()))
if (m) {
var r = m.getWorldQuaternion(new THREE.Quaternion())
pos.applyQuaternion(r)
pos.add(m.getWorldPosition(new THREE.Vector3()))
}
return pos;
}
setColor(index) {
@ -657,34 +657,23 @@ class Cube extends NonGroup {
Canvas.updateUV(scope)
}
}
move(val, axis, absolute, opts, no_update) {
if (!opts) opts = 0;
if (val instanceof THREE.Vector3) {
return this.move(val.x, 0, absolute, opts, true)
&& this.move(val.y, 1, absolute, opts, true)
&& this.move(val.z, 2, absolute, opts, true);
}
var size = this.size(axis)
if (!absolute) {
val = val + this.from[axis]
}
move(val, axis, move_origin) {
var size = this.size(axis);
val+= this.from[axis];
var in_box = val;
val = limitToBox(limitToBox(val, -this.inflate) + size, this.inflate) - size
val = limitToBox(limitToBox(val, -this.inflate) + size, this.inflate) - size;
in_box = Math.abs(in_box - val) < 1e-4;
val -= this.from[axis]
val -= this.from[axis];
//Move
if (opts.applyRot) {
var m = new THREE.Vector3()
m[getAxisLetter(axis)] = val
}
if (Blockbench.globalMovement && Format.bone_rig && !opts) {
var m = new THREE.Vector3()
m[getAxisLetter(axis)] = val
if (Blockbench.globalMovement && Format.bone_rig && !move_origin) {
var m = new THREE.Vector3();
m[getAxisLetter(axis)] = val;
var rotation = new THREE.Quaternion()
this.mesh.getWorldQuaternion(rotation)
m.applyQuaternion(rotation.inverse())
var rotation = new THREE.Quaternion();
this.mesh.getWorldQuaternion(rotation);
m.applyQuaternion(rotation.inverse());
this.from[0] += m.x;
this.from[1] += m.y;
@ -698,14 +687,12 @@ class Cube extends NonGroup {
this.from[axis] += val;
}
//Origin
if (Blockbench.globalMovement && opts) {
this.origin[axis] += val
}
if (!no_update) {
this.mapAutoUV()
Canvas.adaptObjectPosition(this);
TickUpdates.selection = true;
if (Blockbench.globalMovement && move_origin) {
this.origin[axis] += val;
}
this.mapAutoUV()
Canvas.adaptObjectPosition(this);
TickUpdates.selection = true;
return in_box;
}
moveVector(arr, axis) {
@ -719,21 +706,17 @@ class Cube extends NonGroup {
var scope = this;
var in_box = true;
arr.forEach((val, i) => {
cl('-------------------' + val);
var size = scope.size(i);
val += scope.from[i];
cl(val)
var val_before = val;
val = limitToBox(limitToBox(val, -scope.inflate) + size, scope.inflate) - size
if (Math.abs(val_before - val) >= 1e-4) in_box = false;
cl(val)
val -= scope.from[i]
scope.from[i] += val;
scope.to[i] += val;
cl(val)
})
this.mapAutoUV()
Canvas.adaptObjectPosition(this);

View File

@ -415,6 +415,7 @@ class Group extends OutlinerElement {
Outliner.buttons.shading,
Outliner.buttons.autouv
];
Group.prototype.needsUniqueName = () => Format.bone_rig;
Group.prototype.menu = new Menu([
'copy',
'paste',
@ -484,7 +485,7 @@ function addGroup() {
if (Format.bone_rig) {
base_group.createUniqueName()
}
if (add_group instanceof NonGroup) {
if (add_group instanceof NonGroup && selected.length > 1) {
selected.forEach(function(s, i) {
s.addTo(base_group)
})

View File

@ -46,23 +46,28 @@ class Locator extends NonGroup {
return this;
}
getWorldCenter() {
var m = this.parent ? this.parent.mesh : scene;
var pos = new THREE.Vector3(
this.from[0],
this.from[1],
this.from[2]
)
var pos = this.parent.mesh.getWorldPosition(new THREE.Vector3());
var q = this.parent.mesh.getWorldQuaternion(new THREE.Quaternion());
var r = m.getWorldQuaternion(new THREE.Quaternion())
pos.applyQuaternion(r)
var offset = new THREE.Vector3().fromArray(this.from).applyQuaternion(q);
var offset2 = new THREE.Vector3().fromArray(this.parent.origin).applyQuaternion(q);
pos.add(m.getWorldPosition(new THREE.Vector3()))
pos.add(offset).sub(offset2);
return pos;
}
move(val, axis, absolute) {
if (absolute) {
this.from[axis] = val
move(val, axis) {
if (Blockbench.globalMovement) {
var m = new THREE.Vector3();
m[getAxisLetter(axis)] = val;
var rotation = new THREE.Quaternion();
this.parent.mesh.getWorldQuaternion(rotation);
m.applyQuaternion(rotation.inverse());
this.from[0] += m.x;
this.from[1] += m.y;
this.from[2] += m.z;
} else {
this.from[axis] += val
}
@ -79,6 +84,7 @@ Locator.prototype.buttons = [
Outliner.buttons.remove,
Outliner.buttons.export
];
Locator.prototype.needsUniqueName = true;
Locator.prototype.menu = new Menu([
'copy',
'rename',
@ -93,9 +99,11 @@ BARS.defineActions(function() {
category: 'edit',
condition: () => {return Format.locators && Modes.edit},
click: function () {
var elements = []
Undo.initEdit({elements, outliner: true});
elements.push(new Locator().addTo(Group.selected||selected[0]).init().select());
var objs = []
Undo.initEdit({elements: objs, outliner: true});
objs.push(
new Locator().addTo(Group.selected||selected[0]).init().select().createUniqueName()
);
Undo.finishEdit('add locator');
}
})

View File

@ -237,7 +237,7 @@ class OutlinerElement {
}
scope.name = name
delete scope.old_name
if (Format.bone_rig && scope instanceof Group) {
if (Condition(scope.needsUniqueName)) {
scope.createUniqueName()
}
Undo.finishEdit('rename')
@ -394,7 +394,7 @@ class NonGroup extends OutlinerElement {
}
select(event, isOutlinerClick) {
var scope = this;
if (scope === undefined) return false;
if (scope === undefined || Modes.animate) return false;
//Shiftv
var just_selected = []
if (event && event.shiftKey === true && scope.getParentArray().includes(selected[selected.length-1]) && !Modes.paint && isOutlinerClick) {

View File

@ -86,15 +86,15 @@ const Painter = {
Painter.current.face = data.face;
Painter.current.cube = data.cube;
var texture = data.cube.faces[data.face].getTexture()
if (!texture) {
if (!texture || (texture.error && texture.error !== 2)) {
Blockbench.showQuickMessage('message.untextured')
return;
}
if (texture) {
var x = Math.floor( data.intersects[0].uv.x * texture.img.naturalWidth )
var y = Math.floor( (1-data.intersects[0].uv.y) * texture.img.naturalHeight )
Painter.startBrush(texture, x, y, data.cube.faces[data.face].uv, event)
}
if (Toolbox.selected.id !== 'color_picker' && texture) {
var x = Math.floor( data.intersects[0].uv.x * texture.img.naturalWidth )
var y = Math.floor( (1-data.intersects[0].uv.y) * texture.img.naturalHeight )
Painter.startBrush(texture, x, y, data.cube.faces[data.face].uv, event)
if (Toolbox.selected.id !== 'color_picker') {
document.addEventListener('mousemove', Painter.moveBrushCanvas, false );
document.addEventListener('mouseup', Painter.stopBrushCanvas, false );
}

View File

@ -1,5 +1,4 @@
//Display
function getRescalingFactor(angle) {
switch (Math.abs(angle)) {
case 0:
@ -73,6 +72,42 @@ const Canvas = {
var canvas = $('canvas.preview:hover').get(0)
if (canvas) return canvas.preview
},
withoutGizmos(cb) {
function editVis(edit) {
edit(three_grid)
edit(Canvas.side_grids.x)
edit(Canvas.side_grids.z)
edit(Transformer)
edit(outlines)
edit(rot_origin)
edit(Vertexsnap.vertexes)
Cube.selected.forEach(function(obj) {
var m = obj.mesh
if (m && m.outline) {
edit(m.outline)
}
})
}
editVis(obj => {
obj.was_visible = obj.visible
obj.visible = false
})
var ground_anim_before = ground_animation
if (display_mode && ground_animation) {
ground_animation = false
}
cb()
editVis(obj => {
obj.visible = obj.was_visible
delete obj.was_visible
})
if (display_mode && ground_anim_before) {
ground_animation = ground_anim_before
}
},
//Main updaters
clear() {
var objects = []
@ -335,7 +370,6 @@ const Canvas = {
},
//Object handlers
addCube(obj) {
//This does NOT remove old cubes
var mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1))
Canvas.adaptObjectFaces(obj, mesh)
@ -345,11 +379,13 @@ const Canvas = {
mesh.type = 'cube';
mesh.isElement = true;
//scene.add(mesh)
Canvas.meshes[obj.uuid] = mesh
Canvas.meshes[obj.uuid] = mesh;
if (Prop.wireframe === false) {
Canvas.updateUV(obj)
Canvas.updateUV(obj);
} else {
mesh.visible = false;
}
Canvas.buildOutline(obj)
Canvas.buildOutline(obj);
},
adaptObjectPosition(cube, mesh, parent) {
if (!mesh || mesh > 0) mesh = cube.mesh

View File

@ -33,10 +33,10 @@ class Preview {
//Cameras
this.isOrtho = false
this.camPers = new THREE.PerspectiveCamera(45, 16 / 9, 1, 3000)
this.camOrtho = new THREE.OrthographicCamera(-600, 600, -400, 400, 1, 100)
this.camOrtho = new THREE.OrthographicCamera(-600, 600, -400, 400, 0.5, 200);
this.camOrtho.backgroundHandle = [{n: false, a: 'x'}, {n: false, a: 'y'}]
this.camOrtho.axis = null
this.camOrtho.zoom = 0.5
this.camOrtho.zoom = 0.4
this.camPers.preview = this.camOrtho.preview = this;
for (var i = 4; i <= 6; i++) {
this.camPers.layers.enable(i);
@ -45,7 +45,7 @@ class Preview {
//Controls
this.controls = new THREE.OrbitControls(this.camPers, this);
this.controls.minDistance = 1;
this.controls.maxDistance = 320;
this.controls.maxDistance = 512;
this.controls.enableKeys = false;
this.controls.zoomSpeed = 1.5
@ -269,7 +269,7 @@ class Preview {
return this;
}
resetCamera(init) {
var dis = 24
var dis = 40;
this.controls.target.set(0, 8+scene.position.y, 0);
this.camPers.position.set(-dis, dis*0.8, -dis)
if (!init) {
@ -816,6 +816,7 @@ const Screencam = {
cancel: 0
}, function(result) {
if (result === 1) {
Blockbench.export()
ElecDialogs.showSaveDialog(currentwindow, {filters: [ {name: tl('data.image'), extensions: [is_gif ? 'gif' : 'png']} ]}, function (fileName) {
if (fileName === undefined) {
return;
@ -1297,7 +1298,7 @@ BARS.defineActions(function() {
condition: () => Toolbox && Toolbox.selected && Toolbox.selected.allowWireframe,
click: function () {
Prop.wireframe = !Prop.wireframe
Canvas.updateAll()
Canvas.updateAllFaces()
if (Modes.id === 'animate') {
Animator.preview()
}

View File

@ -859,6 +859,9 @@
} else if (Group.selected && !Blockbench.globalMovement) {
Transformer.rotation_ref = rotation_object.mesh;
} else if (Group.selected && Blockbench.globalMovement && Group.selected.parent && Format.bone_rig) {
Transformer.rotation_ref = Group.selected.parent.mesh;
} else if (!Blockbench.globalMovement && Cube.selected[0] && Cube.selected[0].mesh) {
Transformer.rotation_ref = Cube.selected[0].mesh;
@ -1081,7 +1084,7 @@
}
selected.forEach(function(obj, i) {
if (obj.movable) {
obj.move(difference, axisNumber, false , _has_groups||!Format.bone_rig)
obj.move(difference, axisNumber , _has_groups||!Format.bone_rig)
}
})
scope.updateSelection()

View File

@ -179,7 +179,7 @@ class Texture {
switch (this.error) {
case 0: return ''; break;
case 1: return tl('texture.error.file'); break;
case 1: return tl('texture.error.invalid'); break;
//case 1: return tl('texture.error.invalid'); break;
case 2: return tl('texture.error.ratio'); break;
case 3: return tl('texture.error.parent'); break;
}
@ -957,6 +957,28 @@ function getTexturesById(id) {
id = id.replace('#', '');
return $.grep(textures, function(e) {return e.id == id});
}
Clipbench.setTextures = function(texture) {
//Sets the raw image of the texture
if (!isApp) return;
if (texture.mode === 'bitmap') {
var img = nativeImage.createFromDataURL(texture.source)
} else {
var img = nativeImage.createFromPath(texture.source.split('?')[0])
}
clipboard.writeImage(img)
}
Clipbench.pasteTextures = function() {
if (!isApp) return;
var img = clipboard.readImage()
if (img) {
var dataUrl = img.toDataURL()
var texture = new Texture({name: 'pasted', folder: 'block' }).fromDataURL(dataUrl).fillParticle().add(true)
setTimeout(function() {
texture.openMenu()
}, 40)
}
}
TextureAnimator = {
isPlaying: false,

View File

@ -46,14 +46,6 @@ function getSelectionCenter() {
center[0] += pos.x
center[1] += pos.y
center[2] += pos.z
/*
center[0] += pos.x
center[1] += pos.y
center[2] += pos.z
center[0] -= obj.from[0]//-scene.position.x;
center[1] -= obj.from[1]//-scene.position.y;
center[2] -= obj.from[2]//-scene.position.z;
*/
}
})
for (var i = 0; i < 3; i++) {
@ -70,7 +62,6 @@ function isMovementGlobal() {
if (selected.length === 0 || (!settings.local_move.value && Toolbox.selected.id !== 'resize_tool')) {
return true;
}
if (Format.rotate_cubes) {
if (Cube.selected.length > 1) {
if (Cube.selected[0].rotation.equals([0,0,0])) return true;
@ -82,7 +73,13 @@ function isMovementGlobal() {
i++;
}
}
return false;
return Format.bone_rig && Group.selected;
/*
if (!Format.bone_rig || !Group.selected) {
return false;
} else {
return true;
}*/
}
if (Format.bone_rig) {
if (Cube.selected[0] && Cube.selected[0].parent.type === 'group') {
@ -371,10 +368,19 @@ const Vertexsnap = {
} else {
Vertexsnap.cubes.forEach(function(obj) {
var cube_pos = new THREE.Vector3().copy(global_delta)
if (Format.rotate_cubes && !Blockbench.globalMovement) {
obj.origin[0] += cube_pos.getComponent(0)
obj.origin[1] += cube_pos.getComponent(1)
obj.origin[2] += cube_pos.getComponent(2)
if (Format.bone_rig && obj.parent instanceof Group && obj.mesh.parent) {
var q = obj.mesh.parent.getWorldQuaternion(new THREE.Quaternion()).inverse();
cube_pos.applyQuaternion(q);
}
if (Format.rotate_cubes) {
obj.origin[0] += cube_pos.getComponent(0);
obj.origin[1] += cube_pos.getComponent(1);
obj.origin[2] += cube_pos.getComponent(2);
} else {
var q = obj.mesh.getWorldQuaternion(new THREE.Quaternion()).inverse();
cube_pos.applyQuaternion(q);
}
var in_box = obj.moveVector(cube_pos.toArray());
if (!in_box && Format.canvas_limit) {
@ -555,7 +561,6 @@ function getRotationInterval(event) {
function getRotationObject() {
if (Format.bone_rig && Group.selected) return Group.selected;
if (Format.rotate_cubes && Cube.selected.length) return Cube.selected;
if (Locator.selected.length) return Locator.selected[0].parent;
}
function rotateOnAxis(value, fixed, axis) {
if (Format.bone_rig && Group.selected) {
@ -643,16 +648,23 @@ BARS.defineActions(function() {
selected.forEach(function(obj, i) {
if (obj.movable) {
var val = value;
var size = obj.size(axis)
if (!fixed) {
val += obj.from[axis]
}
val = limitToBox(limitToBox(val, -obj.inflate) + size, obj.inflate) - size
if (Format.canvas_limit) {
var size = obj.resizable ? obj.size(axis) : 0;
val = limitToBox(limitToBox(val, -obj.inflate) + size, obj.inflate) - size
}
val -= obj.from[axis]
obj.to[axis] += val;
obj.from[axis] += val;
obj.mapAutoUV()
Canvas.adaptObjectPosition(obj);
if (obj.resizable) {
obj.to[axis] += val;
}
if (obj instanceof Cube) {
obj.mapAutoUV()
Canvas.adaptObjectPosition(obj);
}
}
})
TickUpdates.selection = true;
@ -864,26 +876,28 @@ BARS.defineActions(function() {
//Origin
function moveOriginOnAxis(value, fixed, axis) {
if (Group.selected) {
var rotation_object = getRotationObject()
if (rotation_object instanceof Group) {
var diff = value
if (fixed) {
diff -= Group.selected.origin[axis]
diff -= rotation_object.origin[axis]
}
Group.selected.origin[axis] += diff
rotation_object.origin[axis] += diff
Canvas.updatePositions()
if (Format.bone_rig) {
Canvas.updateAllBones()
}
return;
} else {
rotation_object.forEach(function(obj, i) {
var diff = value
if (fixed) {
diff -= obj.origin[axis]
}
obj.origin[axis] += diff
})
Canvas.updatePositions()
}
selected.forEach(function(obj, i) {
var diff = value
if (fixed) {
diff -= obj.origin[axis]
}
obj.origin[axis] += diff
})
Canvas.updatePositions()
}
new NumSlider('slider_origin_x', {
condition: () => (Modes.edit && getRotationObject()),

View File

@ -145,6 +145,7 @@ function trimFloatNumber(val) {
if (val == '') return val;
var string = val.toFixed(4)
string = string.replace(/0+$/g, '').replace(/\.$/g, '')
if (string == -0) return 0;
return string;
}
function getAxisLetter(number) {

View File

@ -276,7 +276,7 @@ class UVEditor {
p.top = o.top + (p.top - o.top)
p.left = limitNumber(p.left, 0, scope.inner_size-scope.jquery.size.width()+1)
p.top = limitNumber(p.top, 0, scope.inner_size-scope.jquery.size.height()+1)
p.top = limitNumber(p.top, 0, scope.inner_height-scope.jquery.size.height()+1)
p.left = p.left - p.left % (scope.inner_size/scope.grid);
p.top = p.top - p.top % (scope.inner_size/scope.grid);
@ -377,7 +377,7 @@ class UVEditor {
message(msg, vars) {
msg = tl(msg, vars)
var box = $('<div class="uv_message_box">' + msg + '</div>')
this.jquery.frame.append(box)
this.jquery.main.append(box)
setTimeout(function() {
box.fadeOut(200)
setTimeout(function() {
@ -1036,14 +1036,14 @@ class UVEditor {
face.uv[0] = Math.min(face.uv[0], face.uv[2]);
face.uv[1] = Math.min(face.uv[1], face.uv[3]);
if (side == 'north' || side == 'south') {
left2 = limitNumber(obj.size('0'), 0, 16)
top2 = limitNumber(obj.size('1'), 0, 16)
left2 = limitNumber(obj.size('0'), 0, Project.texture_width)
top2 = limitNumber(obj.size('1'), 0, Project.texture_height)
} else if (side == 'east' || side == 'west') {
left2 = limitNumber(obj.size('2'), 0, 16)
top2 = limitNumber(obj.size('1'), 0, 16)
left2 = limitNumber(obj.size('2'), 0, Project.texture_width)
top2 = limitNumber(obj.size('1'), 0, Project.texture_height)
} else if (side == 'up' || side == 'down') {
left2 = limitNumber(obj.size('0'), 0, 16)
top2 = limitNumber(obj.size('2'), 0, 16)
left2 = limitNumber(obj.size('0'), 0, Project.texture_width)
top2 = limitNumber(obj.size('2'), 0, Project.texture_height)
}
if (face.rotation % 180) {
[left2, top2] = [top2, left2];
@ -1378,7 +1378,7 @@ class UVEditor {
'uv_maximize',
'uv_auto',
'uv_rel_auto',
{icon: 'rotate_90_degrees_ccw', condition: () => Format.id == 'java_block', name: 'menu.uv.mapping.rotation', children: function() {
{icon: 'rotate_90_degrees_ccw', condition: () => Format.id == 'java_block' || Format.id == 'free', name: 'menu.uv.mapping.rotation', children: function() {
var off = 'radio_button_unchecked'
var on = 'radio_button_checked'
return [
@ -1791,7 +1791,7 @@ BARS.defineActions(function() {
new BarSlider('uv_rotation', {
category: 'uv',
condition: () => !Project.box_uv && Format.id == 'java_block' && Cube.selected.length,
condition: () => !Project.box_uv && (Format.id == 'java_block' || Format.id == 'free') && Cube.selected.length,
min: 0, max: 270, step: 90, width: 80,
onBefore: () => {
Undo.initEdit({elements: Cube.selected, uv_only: true})

View File

@ -983,5 +983,7 @@
"format.optifine_part": "OptiFine Part",
"format.optifine_part.desc": "JPM Part für OptiFine-Entitymodelle",
"action.reverse_keyframes": "Keyframes umkehren",
"action.reverse_keyframes.desc": "Kehrt die Reihenfolge der ausgewählten Keyframes um"
"action.reverse_keyframes.desc": "Kehrt die Reihenfolge der ausgewählten Keyframes um",
"message.no_format_import.title": "Nicht-lesbares Format",
"message.no_format_import.message": "Dieses Format kann nur exportiert und nicht wieder geladen werden. Um das Modell später erneut öffnen zu können, speichere es zusätzlich als Projekt."
}

View File

@ -109,6 +109,8 @@
"message.rotation_limit.message": "Rotations are limited by Minecraft to one axis and 22.5 degree increments. Rotating on a different axis will clear all rotations on the other axes. Convert the model to \"Free Model\" if you are modeling for other purposes and need free rotations.",
"message.file_not_found.title": "File Not Found",
"message.file_not_found.message": "Blockbench could not find the requested file. Make sure it is saved locally and not in a cloud.",
"message.no_format_import.title": "Write-only Format",
"message.no_format_import.message": "Please note that this export format is write-only. To be able to open the model again, you need to also save it as a project.",
"message.recover_backup.title": "Recover Model",
"message.recover_backup.message": "Blockbench was closed without saving. Do you want to recover the model?",
"message.screenshot.title": "Screenshot",

View File

@ -959,29 +959,31 @@
"panel.element.size": "Tamaño",
"panel.element.origin": "Punto de Pivote",
"panel.element.rotation": "Rotación",
"message.canvas_limit_error.title": "Canvas Limit Error",
"message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the pivot point to prevent this.",
"data.effect": "Effect",
"generic.name": "Name",
"settings.recent_projects": "Recent Model Cap",
"settings.recent_projects.desc": "Maximum number of recent models to remember",
"settings.volume": "Volume",
"settings.volume.desc": "Volume control for sound effects in animations",
"action.change_keyframe_file": "Select File",
"action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
"action.clear_timeline": "Clear Timeline",
"action.clear_timeline.desc": "Clear all unselected bones from the timeline",
"action.select_effect_animator": "Animate Effects",
"action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
"action.timeline_focus": "Channel",
"action.timeline_focus.desc": "Select the animation channels to display in the timeline",
"action.timeline_focus.all": "All",
"timeline.particle": "Particle",
"timeline.sound": "Sound",
"timeline.effects": "Effects",
"data.format": "Format",
"format.optifine_part": "OptiFine Part",
"format.optifine_part.desc": "JPM part for OptiFine entity models",
"action.reverse_keyframes": "Reverse Keyframes",
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
"message.canvas_limit_error.title": "Error de Límite del Lienzo",
"message.canvas_limit_error.message": "La acción no pudo ser ejecutada correctamente porque el formato limita el lienzo a 48 unidades. Cambia el punto pivote para prevenir esto.",
"data.effect": "Efecto",
"generic.name": "Nombre",
"settings.recent_projects": "Límite de Modelos Recientes",
"settings.recent_projects.desc": "Número máximo de modelos recientes para recordar",
"settings.volume": "Volumen",
"settings.volume.desc": "Control del volumen para efectos de sonido en animaciones",
"action.change_keyframe_file": "Seleccionar Archivo",
"action.change_keyframe_file.desc": "Selecciona un archivo de audio para previsualizar un efecto de sonido",
"action.clear_timeline": "Limpiar Línea de Tiempo",
"action.clear_timeline.desc": "Borra todos los huesos no seleccionados en la línea de tiempo",
"action.select_effect_animator": "Animar Efectos",
"action.select_effect_animator.desc": "Abre la línea de tiempo para añadir sonidos y efectos de partícula",
"action.timeline_focus": "Canal",
"action.timeline_focus.desc": "Selecciona los canales de animación para mostrar en la línea de tiempo",
"action.timeline_focus.all": "Todos",
"timeline.particle": "Partícula",
"timeline.sound": "Sonido",
"timeline.effects": "Efectos",
"data.format": "Formato",
"format.optifine_part": "Parte de Optifima",
"format.optifine_part.desc": "Parte JPM para modelos de entidad de OptiFine",
"action.reverse_keyframes": "Invertir Frames Clave",
"action.reverse_keyframes.desc": "Invierte el orden de los frames clave seleccionados",
"message.no_format_import.title": "Write-only Format",
"message.no_format_import.message": "Please note that this export format is write-only. To be able to open the model again, you need to also save it as a project."
}

File diff suppressed because it is too large Load Diff

View File

@ -983,5 +983,7 @@
"format.optifine_part": "OptiFine Part",
"format.optifine_part.desc": "JPM part for OptiFine entity models",
"action.reverse_keyframes": "Reverse Keyframes",
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes",
"message.no_format_import.title": "Write-only Format",
"message.no_format_import.message": "Please note that this export format is write-only. To be able to open the model again, you need to also save it as a project."
}

View File

@ -831,7 +831,7 @@
"dialog.sketchfab_uploader.tags": "Tag",
"settings.sketchfab_token": "Sketchfab Token",
"settings.sketchfab_token.desc": "BlockbenchにSketchfabアカウントへのアップロードを許可する",
"panel.color": "カラー",
"panel.color": "Color",
"data.origin": "原点",
"message.sketchfab.success": "モデルのアップロードに成功",
"message.sketchfab.error": "Sketchfab:モデルアップロードに失敗しました",
@ -954,22 +954,22 @@
"action.import_optifine_part.desc": "OptiFineエンティティモデルをインポートします",
"data.locator": "ロケーター",
"mode.start.no_recents": "最近開いたモデルはありません",
"panel.element": "エレメント",
"panel.element": "Element",
"panel.element.position": "Position",
"panel.element.size": "Size",
"panel.element.origin": "Center",
"panel.element.rotation": "Rotation",
"message.canvas_limit_error.title": "キャンバス制限エラー",
"message.canvas_limit_error.message": "フォーマットによってキャンバスが48単位に制限されているため、アクションを正しく実行できませんでした。",
"data.effect": "Effect",
"generic.name": "Name",
"data.effect": "エフェクト",
"generic.name": "名前",
"settings.recent_projects": "Recent Model Cap",
"settings.recent_projects.desc": "Maximum number of recent models to remember",
"settings.volume": "Volume",
"settings.volume.desc": "Volume control for sound effects in animations",
"action.change_keyframe_file": "Select File",
"action.change_keyframe_file": "ファイルを選択",
"action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
"action.clear_timeline": "Clear Timeline",
"action.clear_timeline": "タイムラインをクリアー",
"action.clear_timeline.desc": "Clear all unselected bones from the timeline",
"action.select_effect_animator": "Animate Effects",
"action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
@ -983,5 +983,7 @@
"format.optifine_part": "OptiFine Part",
"format.optifine_part.desc": "JPM part for OptiFine entity models",
"action.reverse_keyframes": "Reverse Keyframes",
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes",
"message.no_format_import.title": "Write-only Format",
"message.no_format_import.message": "Please note that this export format is write-only. To be able to open the model again, you need to also save it as a project."
}

View File

@ -983,5 +983,7 @@
"format.optifine_part": "OptiFine Part",
"format.optifine_part.desc": "JPM part for OptiFine entity models",
"action.reverse_keyframes": "Reverse Keyframes",
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes",
"message.no_format_import.title": "Write-only Format",
"message.no_format_import.message": "Please note that this export format is write-only. To be able to open the model again, you need to also save it as a project."
}

View File

@ -983,5 +983,7 @@
"format.optifine_part": "OptiFine Part",
"format.optifine_part.desc": "JPM part for OptiFine entity models",
"action.reverse_keyframes": "Reverse Keyframes",
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes",
"message.no_format_import.title": "Write-only Format",
"message.no_format_import.message": "Please note that this export format is write-only. To be able to open the model again, you need to also save it as a project."
}

View File

@ -983,5 +983,7 @@
"format.optifine_part": "OptiFine Part",
"format.optifine_part.desc": "JPM part for OptiFine entity models",
"action.reverse_keyframes": "Reverse Keyframes",
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes",
"message.no_format_import.title": "Write-only Format",
"message.no_format_import.message": "Please note that this export format is write-only. To be able to open the model again, you need to also save it as a project."
}

View File

@ -1,19 +1,19 @@
{
"dialog.ok": "Ок",
"dialog.cancel": "Отмена",
"dialog.confirm": "Подтвердить",
"dialog.close": "Закрыть",
"dialog.import": "Импорт",
"dialog.save": "Сохранить",
"dialog.discard": "Не сохранять",
"dialog.dontshowagain": "Не показывать снова",
"data.cube": "Куб",
"data.group": "Группа",
"data.texture": "Текстура",
"data.plugin": "Плагин",
"data.preview": "Предпросмотр",
"data.toolbar": "Инструменты",
"data.image": "Изображение",
"dialog.ok": "OK",
"dialog.cancel": "Cancel",
"dialog.confirm": "Confirm",
"dialog.close": "Close",
"dialog.import": "Import",
"dialog.save": "Save",
"dialog.discard": "Discard",
"dialog.dontshowagain": "Don't Show Again",
"data.cube": "Cube",
"data.group": "Group",
"data.texture": "Texture",
"data.plugin": "Plugin",
"data.preview": "Preview",
"data.toolbar": "Toolbar",
"data.image": "Image",
"keys.ctrl": "Control",
"keys.shift": "Shift",
"keys.alt": "Alt",
@ -983,5 +983,7 @@
"format.optifine_part": "OptiFine Part",
"format.optifine_part.desc": "JPM part for OptiFine entity models",
"action.reverse_keyframes": "Reverse Keyframes",
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes",
"message.no_format_import.title": "Write-only Format",
"message.no_format_import.message": "Please note that this export format is write-only. To be able to open the model again, you need to also save it as a project."
}

View File

@ -983,5 +983,7 @@
"format.optifine_part": "OptiFine Part",
"format.optifine_part.desc": "JPM part for OptiFine entity models",
"action.reverse_keyframes": "Reverse Keyframes",
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes",
"message.no_format_import.title": "Write-only Format",
"message.no_format_import.message": "Please note that this export format is write-only. To be able to open the model again, you need to also save it as a project."
}

View File

@ -983,5 +983,7 @@
"format.optifine_part": "OptiFine Part",
"format.optifine_part.desc": "JPM part for OptiFine entity models",
"action.reverse_keyframes": "Reverse Keyframes",
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
"action.reverse_keyframes.desc": "Reverse the order of the selected keyframes",
"message.no_format_import.title": "Write-only Format",
"message.no_format_import.message": "Please note that this export format is write-only. To be able to open the model again, you need to also save it as a project."
}

View File

@ -1,7 +1,7 @@
{
"name": "Blockbench",
"description": "Model editing and animation software",
"version": "3.1.0",
"version": "3.1.1",
"license": "MIT",
"author": {
"name": "JannisX11",