//Textures class Texture { constructor(data) { this.path = '' this.name = '' this.folder = ''; this.namespace = ''; this.id = ''; this.source = '' this.particle = false this.selected = false this.error = false; this.ratio = 1 this.show_icon = true this.average_color = {r:0, g:0, b:0} this.dark_box = false this.img = 0; this.res = 0; this.mode = 'link' //link, bitmap (internally used) this.saved = true if (!isApp) this.mode = 'bitmap' this.uuid = guid() if (typeof data === 'object') { this.extend(data) } if (!this.id) { var i = textures.length; while (true) { var c = 0 var duplicates = false; while (c < textures.length) { if (textures[c].id == i) { duplicates = true; } c++; } if (duplicates === true) { i++; } else { this.id = i.toString(); return; } } } } get frameCount() { if (this.ratio !== 1) { return 1/this.ratio } } getUndoCopy(bitmap) { var copy = { path: this.path, name: this.name, folder: this.folder, namespace: this.namespace, id: this.id, particle: this.particle, selected: this.selected, mode: this.mode, saved: this.saved, uuid: this.uuid, old_width: this.old_width, old_height: this.old_height } if (bitmap || this.mode === 'bitmap') { copy.source = this.source } return copy } extend(data) { Merge.string(this, data, 'path') Merge.string(this, data, 'name') Merge.string(this, data, 'folder') Merge.string(this, data, 'namespace') Merge.string(this, data, 'id') Merge.boolean(this, data, 'particle') Merge.string(this, data, 'mode') Merge.boolean(this, data, 'saved') if (this.mode === 'bitmap') { Merge.string(this, data, 'source') } return this; } //Loading load(isDefault, reloading, cb) { var scope = this; if (Painter.current.texture === this) { Painter.current = {} } this.error = false; this.show_icon = true var img = this.img = new Image() if (Canvas.materials[scope.uuid] !== undefined) { Canvas.materials[scope.uuid].dispose() } function onerror() { if (isApp && !(isDefault || scope.isDefault) && scope.mode !== 'bitmap' && scope.fromDefaultPack() ) { return true; } else { scope.img.src = 'assets/missing.png' scope.error = true; scope.show_icon = false console.log('Error loading '+scope.source) } } if (isApp && this.mode === 'link' && !fs.existsSync(this.source.replace(/\?\d+$/, ''))) { if (onerror()) { return } } else { img.src = this.source img.onerror = onerror } var tex = new THREE.Texture(img) img.tex = tex; img.tex.magFilter = THREE.NearestFilter img.tex.minFilter = THREE.NearestFilter img.onload = function() { this.tex.needsUpdate = true; scope.res = img.naturalWidth; scope.ratio = img.naturalWidth / img.naturalHeight; if (isDefault) { console.log('Successfully loaded '+scope.name+' from default pack') } scope.average_color = getAverageRGB(this) scope.dark_box = (scope.average_color.r + scope.average_color.g + scope.average_color.b) >= 383 //Width / Animation if (img.naturalWidth !== img.naturalHeight && Blockbench.entity_mode === false) { if (img.naturalHeight % img.naturalWidth === 0) { Canvas.updateAllUVs() BARS.updateConditions() } else { scope.error = true; Blockbench.showQuickMessage('message.square_textures') } } if (Blockbench.entity_mode && textures.indexOf(scope) === 0) { if (!scope.keep_size) { var size = { pw: Project.texture_width, ph: Project.texture_height, nw: img.naturalWidth, nh: img.naturalHeight } if ((scope.old_width != size.nw || scope.old_height != size.nh) && (size.pw != size.nw || size.ph != size.nh)) { Blockbench.showMessageBox({ translateKey: 'update_res', icon: 'photo_size_select_small', buttons: [tl('message.update_res.update'), tl('dialog.cancel')], confirm: 0, cancel: 1 }, function(result) { if (result === 0) { var lockUV = ( // EG. Texture Optimierung > Modulo geht nicht in allen Bereichen auf (size.pw%size.nw || size.ph%size.nh) && (size.nw%size.pw || size.nh%size.ph) ) entityMode.setResolution(img.naturalWidth, img.naturalHeight, lockUV) if (selected.length) { main_uv.loadData() main_uv.setGrid() } } }) } scope.old_width = img.naturalWidth scope.old_height = img.naturalHeight } } if ($('.dialog#texture_edit:visible').length >= 1 && scope.selected === true) { scope.openMenu() } TextureAnimator.updateButton() Canvas.updateAllFaces(scope) } if (Canvas.materials[this.uuid]) { Canvas.materials[this.uuid].map.dispose() Canvas.materials[this.uuid].dispose() delete Canvas.materials[this.uuid] } var mat = new THREE.MeshLambertMaterial({ color: 0xffffff, map: tex, transparent: settings.transparency.value, side: display_mode || Blockbench.entity_mode ? 2 : 0, alphaTest: 0.2 }); Canvas.materials[this.uuid] = mat return this; } fromJavaLink(link, path_array) { if (typeof link !== 'string' || (link.substr(0, 1) === '#' && !link.includes('/'))) { this.load(); return this; } if (link.substr(0, 22) === 'data:image/png;base64,') { this.fromDataURL(link) return this; } if (isApp && (link.substr(1, 2) === ':\\' || link.substr(1, 2) === ':/')) { var path = link.replace(/\\|\//g, osfs).replace(/\?\d+/g, '') this.fromPath(path) return this; } var can_load = !!path_array.length var spaces = link.split(':') if (spaces.length > 1) { this.namespace = spaces[0] link = spaces[1] path_array[path_array.length-1] = this.namespace } path_array.push('textures', link.replace(/\//g, osfs)) var path = path_array.join(osfs)+'.png' if (path && can_load) { this.fromPath(path) } else { this.path = path this.folder = link.replace(/\\/g, '/').split('/') this.folder = this.folder.splice(0, this.folder.length-1).join('/') this.name = pathToName(path, true) this.mode = 'link' this.saved = true this.load() } return this; } fromFile(file) { if (!file) return this; if (file.name) this.name = file.name if (typeof file.content === 'string' && file.content.substr(0, 4) === 'data') { this.fromDataURL(file.content) if (!file.path) { } else if (pathToExtension(file.path) === 'png') { this.path = file.path } else if (pathToExtension(file.path) === 'tga') { this.path = '' } } else if (isApp) { this.fromPath(file.path) } this.saved = true return this; } fromPath(path) { var scope = this; if (path && pathToExtension(path) === 'tga') { var targa_loader = new Targa() targa_loader.open(path, function() { scope.fromFile({ name: pathToName(path, true), path: path, content: targa_loader.getDataURL() }) }) return this; } this.path = path this.name = pathToName(path, true) this.mode = 'link' this.saved = true if (path.includes('data:image')) { this.source = path } else { this.source = path + '?' + tex_version } this.generateFolder(path) this.startWatcher() if (!isApp && Project.dataURLTextures) { if (this.img && this.img.src) { this.img.src = 'assets/missing.png' } this.error = true; this.show_icon = false } else { this.load() } return this; } fromDataURL(data_url) { this.source = data_url this.mode = 'bitmap' this.saved = false; this.load() return this; } fromDefaultPack() { if (isApp && settings.default_path && settings.default_path.value) { if (Blockbench.entity_mode) { var path = findEntityTexture(Project.parent, 'raw') if (path) { this.isDefault = true path = settings.default_path.value + osfs + path if (fs.existsSync(path + '.png')) { this.fromPath(path + '.png') delete this.isDefault return true; } else if (fs.existsSync(path + '.tga')) { this.fromPath(path + '.tga') delete this.isDefault return true; } delete this.isDefault } } else { var path = settings.default_path.value + osfs + this.folder.replace(/\//g, osfs) + osfs + this.name if (fs.existsSync(path)) { this.fromPath(path) return true; } } } } updateSource(dataUrl) { this.source = dataUrl this.img.src = dataUrl this.updateMaterial() if (main_uv.texture === this) { main_uv.loadData() } if (open_dialog === 'uv_dialog') { for (var editor in uv_dialog.editors) { if (uv_dialog.editors.hasOwnProperty(editor) && uv_dialog.editors[editor].texture === this) { uv_dialog.editors[editor].loadData() } } } return this; } updateMaterial() { var scope = this; var img = new Image() try { img.src = scope.source } catch(err) { } img.onload = function() { Canvas.materials[scope.uuid].map.dispose() var tex = new THREE.Texture(img) img.tex = tex; img.tex.magFilter = THREE.NearestFilter img.tex.minFilter = THREE.NearestFilter img.tex.needsUpdate = true; scope.img = img Canvas.materials[scope.uuid].map = tex } return this; } reopen(force) { var scope = this; this.stopWatcher() function _replace() { Blockbench.import({ extensions: ['png', 'tga'], type: 'PNG Texture', readtype: 'image', startpath: scope.path }, function(files) { scope.fromFile(files[0]) }) Painter.current = {} Blockbench.dispatchEvent( 'change_texture_path', {texture: scope} ) } if (scope.saved || force) { _replace() } else { Blockbench.showMessageBox({ translateKey: 'unsaved_texture', icon: 'warning', buttons: [tl('dialog.continue'), tl('dialog.cancel')], confirm: 0, cancel: 1 }, function(result) { if (result === 0) { _replace() } }) } } refresh(single) { if (this.mode === 'bitmap') { return false; } if (single) { tex_version++; } //this.source = this.path + '?' + tex_version; this.source = this.source.replace(/\?\d+$/, '?' + tex_version) this.load(undefined, true) if (single) { main_uv.loadData() loadTextureDraggable() } } reloadTexture() { this.refresh(true) } startWatcher() { if (this.mode !== 'link' || !isApp || !this.path.match(/\.[a-zA-Z]+$/) || !fs.existsSync(this.path)) { return; } var scope = this; fs.watchFile(scope.path, {interval: 50}, function(curr, prev) { if (curr.mtime !== prev.mtime) { scope.reloadTexture() } }) } stopWatcher() { if (this.mode !== 'link' || !isApp || !fs.existsSync(this.path)) { return; } fs.unwatchFile(this.path) } generateFolder(path) { var scope = this if (path.includes(osfs+'textures'+osfs)) { var arr = path.split(osfs+'textures'+osfs) var arr1 = arr[0].split(osfs) scope.namespace = arr1[arr1.length-1] var arr2 = arr[arr.length-1].split(osfs) arr2.pop() scope.folder = arr2.join('/') } else { var arr = path.split(osfs) scope.folder = arr[arr.length-2] if (Blockbench.entity_mode === false && isApp) { Blockbench.showMessageBox({ translateKey: 'loose_texture', icon: 'folder_open', buttons: [tl('message.loose_texture.change'), tl('dialog.ok')], confirm: 0, cancel: 1 }, function(result) { if (result === 0) { scope.reopen() } }) } } return this; } //Management select(event) { textures.forEach(s => { if (s.selected) s.selected = false; }) if (event) { Prop.active_panel = 'textures' } this.selected = true textures.selected = this return this; } add(undo) { if (undo !== false) { Undo.initEdit({textures: []}) } var scope = this if (!textures.includes(this)) { textures.push(this) } Blockbench.dispatchEvent( 'add_texture', {texture: this}) loadTextureDraggable() if (Blockbench.entity_mode && elements.length) { var sides = ['north', 'east', 'south', 'west', 'up', 'down'] elements.forEach(function(s) { sides.forEach(function(side) { s.faces[side].texture = scope.uuid }) }) Canvas.updateAllFaces() if (selected.length) { main_uv.loadData() } textures.forEach(function (t, i) { if (t !== scope) { textures.splice(i, 1) } }) } if (undo === true) { Undo.finishEdit('add_texture', {textures: [this]}) } return this; } remove(no_update) { if (!no_update) { Undo.initEdit({textures: [this]}) } this.stopWatcher() textures.splice(textures.indexOf(this), 1) if (!no_update) { Canvas.updateAllFaces() $('#uv_frame').css('background', 'transparent') TextureAnimator.updateButton() hideDialog() BARS.updateConditions() Undo.finishEdit('remove_textures', {textures: []}) } } //Use enableParticle() { textures.forEach(function(s) { s.particle = false; }) if (!Blockbench.entity_mode) { this.particle = true } return this; } fillParticle() { var particle_tex = false textures.forEach(function(t) { if (t.particle) { particle_tex = t } }) if (!particle_tex) { this.enableParticle() } return this; } apply(all) { if (selected.length === 0) return; var scope = this; Undo.initEdit({cubes: selected}) selected.forEach(function(obj) { for (var face in obj.faces) { if (all || Blockbench.entity_mode || face === main_uv.face) { var f = obj.faces[face] if (all !== 'blank' || (f.texture !== null && !f.getTexture())) { f.texture = scope.uuid } } } }) Canvas.updateSelectedFaces() main_uv.loadData() Undo.finishEdit('applied_texture') return this; } //Interface openFolder() { if (!isApp || !this.path) return; shell.showItemInFolder(this.path) return this; } openEditor() { var scope = this; if (!settings.image_editor.value) { changeImageEditor(scope) } else { if (fs.existsSync(settings.image_editor.value)) { require('child_process').spawn(settings.image_editor.value, [this.path]) } else { var answer = electron.dialog.showMessageBox(currentwindow, { type: 'info', noLink: true, title: tl('message.image_editor_missing.title'), message: tl('message.image_editor_missing.message'), detail: tl('message.image_editor_missing.detail') }) selectImageEditorFile(scope) } } return this; } showContextMenu(event) { var scope = this; scope.select() this.menu.open(event, scope) } openMenu() { var scope = this scope.select() showDialog('texture_edit') if (scope.path) { var arr = scope.path.split(osfs) arr.splice(-1) var path = arr.join('/') + '/' + scope.name + '' $('#texture_edit #te_path').html(path) } else { $('#texture_edit #te_path').html('') } $('#texture_edit #te_title').text(scope.name + ' ('+scope.img.naturalWidth+' x '+scope.img.naturalHeight+')') $('#texture_edit input#te_variable').val(scope.id) $('#texture_edit input#te_name').val(scope.name) $('#texture_edit input#te_folder').val(scope.folder) $('#texture_edit input#te_namespace').val(scope.namespace) $('#texture_menu_thumbnail').html(scope.img) if (scope.mode === 'link') { $('#texture_edit .tool.link_only').show() $('#texture_edit .tool.bitmap_only').hide() } else { $('#texture_edit .tool.link_only').hide() $('#texture_edit .tool.bitmap_only').show() } } //Export javaTextureLink(backup) { if (backup) { return this.source; } var link = this.name.replace(/\.png$/, '') if (this.folder) { link = this.folder + '/' + link } if (this.namespace && this.namespace !== 'minecraft') { link = this.namespace + ':' + link } return link; } save(as) { var scope = this; if (scope.saved && !as) { return this; } if (isApp) { //overwrite path if (scope.mode === 'link') { var image = nativeImage.createFromPath(scope.source.replace(/\?\d+$/, '')).toPNG() } else { var image = nativeImage.createFromDataURL(scope.source).toPNG() } tex_version++; if (!as && this.path && this.path.substr(1,1) === ':') { fs.writeFile(this.path, image, function (err) { scope.fromPath(scope.path) }) } else { var find_path; if (Blockbench.entity_mode) { find_path = findEntityTexture(Project.parent, true) } Blockbench.export({ type: 'PNG Texture', extensions: ['png'], name: scope.name, content: image, startpath: find_path, savetype: 'image' }, function(path) { scope.fromPath(path) }) } } else { //Download Blockbench.export({ type: 'PNG Texture', extensions: ['png'], name: scope.name, content: scope.source, savetype: 'image' }, function() { scope.saved = true; }) } return this; } toBitmap(cb) { var scope = this; if (isApp && scope.mode === 'link') { var canvas = document.createElement('canvas') canvas.width = scope.img.naturalWidth; canvas.height = scope.img.naturalHeight; var ctx = canvas.getContext('2d'); ctx.drawImage(scope.img, 0, 0) scope.mode = 'bitmap' scope.saved = false scope.source = canvas.toDataURL('image/png') cb() } } edit(cb, options) { var scope = this; if (typeof options !== 'object') { options = {} } if (scope.mode === 'link') { scope.toBitmap(function() { Painter.edit(scope, cb, options) }) } else { Painter.edit(scope, cb, options) } scope.saved = false; } } Texture.prototype.menu = new Menu([ { icon: 'crop_original', name: 'menu.texture.face', condition: function() {return !Blockbench.entity_mode && selected.length > 0}, click: function(texture) {texture.apply()} }, { icon: 'texture', name: 'menu.texture.blank', condition: function() {return !Blockbench.entity_mode && selected.length > 0}, click: function(texture) {texture.apply('blank')} }, { icon: 'fa-cube', name: 'menu.texture.cube', condition: function() {return !Blockbench.entity_mode && selected.length > 0}, click: function(texture) {texture.apply(true)} }, { icon: 'bubble_chart', name: 'menu.texture.particle', condition: function() {return !Blockbench.entity_mode}, click: function(texture) { if (texture.particle) { texture.particle = false } else { texture.enableParticle() } } }, '_', { icon: 'edit', name: 'menu.texture.edit', condition: function(texture) {return texture.mode == 'link'}, click: function(texture) { texture.openEditor()} }, { icon: 'folder', name: 'menu.texture.folder', condition: function(texture) {return isApp && texture.path}, click: function(texture) {texture.openFolder()} }, { icon: 'save', name: 'menu.texture.save', condition: function(texture) {return !texture.saved && texture.path}, click: function(texture) {texture.save()} }, { icon: 'file_download', name: 'menu.texture.export', click: function(texture) {texture.save(true)} }, '_', { icon: 'refresh', name: 'menu.texture.refresh', condition: function(texture) {return texture.mode == 'link'}, click: function(texture) {texture.reloadTexture()} }, { icon: 'file_upload', name: 'menu.texture.change', click: function(texture) { texture.reopen()} }, { icon: 'delete', name: 'generic.delete', click: function(texture) { Undo.initEdit({textures: [texture], bitmap: true}) texture.remove() Undo.initEdit({textures: [], bitmap: true}) }}, '_', { icon: 'list', name: 'menu.texture.properties', click: function(texture) { texture.openMenu()} } ]) function openTexture() { var start_path; if (!isApp) {} else if (textures.length > 0) { var arr = textures[0].path.split(osfs) arr.splice(-1) start_path = arr.join(osfs) } else if (Prop.file_path) { var arr = Prop.file_path.split(osfs) arr.splice(-3) arr.push('textures') start_path = arr.join(osfs) } Blockbench.import({ readtype: 'image', type: 'PNG Texture', extensions: ['png', 'tga'], multiple: true, startpath: start_path }, function(results) { var new_textures = [] Undo.initEdit({textures: new_textures}) results.forEach(function(f) { var t = new Texture({name: f.name}).fromFile(f).add(false).fillParticle() new_textures.push(t) }) Undo.finishEdit('add_texture') }) } function reloadTextures() { tex_version++; textures.forEach(function(t) { if (t.mode === 'link') { t.source = t.path + '?' + tex_version; t.refresh(false) } }) Canvas.updateAllFaces() main_uv.loadData() loadTextureDraggable() } function saveTextures() { textures.forEach(function(t) { if (!t.saved) { t.save() } }) } function saveTextureMenu() { hideDialog() Undo.initEdit({textures}) var tex = textures.selected tex.name = $('#texture_edit input#te_name').val() tex.id = $('#texture_edit input#te_variable').val() tex.folder = $('#texture_edit input#te_folder').val() tex.namespace = $('#texture_edit input#te_namespace').val() $('#texture_edit #change_file_button').unbind('click') $('#texture_edit #file_upload').unbind('input') Undo.finishEdit('texture_edit') } function loadTextureDraggable() { Vue.nextTick(function() { setTimeout(function() { $('li.texture:not(.ui-draggable)').draggable({ revertDuration: 0, helper: function(e) { var t = $(e.target) if (!t.hasClass('texture')) t = t.parent() if (!t.hasClass('texture')) t = t.parent() return t.find('.texture_icon_wrapper').clone().addClass('texture_drag_helper').attr('texid', t.attr('texid')) }, cursorAt: { left: 2, top: -5 }, revert: 'invalid', appendTo: 'body', zIndex: 19, distance: 4, drag: function(event, ui) { $('.outliner_node[order]').attr('order', null) var tar = $('#cubes_list li .drag_hover.outliner_node').deepest() var element = TreeElements.findRecursive('uuid', tar.attr('id')) if (element) { tar.attr('order', '0') } }, stop: function(event, ui) { setTimeout(function() { if ($('canvas.preview:hover').length > 0) { var data = Canvas.getCurrentPreview().raycast() if (data.cube && data.face) { var tex = textures.findInArray('uuid', ui.helper.attr('texid')); var cubes_list = data.cube.selected ? selected : [data.cube]; Undo.initEdit({}) if (tex) { data.cube.applyTexture(tex, [data.face]) } Undo.finishEdit('apply texture') } } }, 10) } }) }, 42) }) } function unselectTextures() { textures.forEach(function(s) { s.selected = false; }) textures.selected = false } function changeTexturesFolder() { var path = undefined; var i = 0; while (i < textures.length && path === undefined) { if (typeof textures[i].path == 'string' && textures[i].path.length > 8) { path = textures[i].path } i++; } if (!path) {return;} var path = path.split(osfs) path.splice(-1) path = path.join(osfs) electron.dialog.showOpenDialog(currentwindow, { title: tl('message.default_textures.select'), properties: ['openDirectory'], defaultPath: path }, function(filePaths) { if (filePaths && filePaths.length) { var new_path = filePaths[0] Undo.initEdit({textures}) textures.forEach(function(t) { if (typeof t.path === 'string' && t.path.includes(path)) { t.fromPath(t.path.replace(path, new_path)) } }) Undo.finishEdit('folder_changed') } }) } function getTextureById(id) { if (id === undefined || id === false) return; if (id == null) { return {material: transparentMaterial}; } id = id.replace('#', ''); return $.grep(textures, function(e) {return e.id == id})[0]; } function getTexturesById(id) { if (id === undefined) return; id = id.replace('#', ''); return $.grep(textures, function(e) {return e.id == id}); } TextureAnimator = { isPlaying: false, interval: false, start: function() { clearInterval(TextureAnimator.interval) TextureAnimator.isPlaying = true TextureAnimator.updateButton() TextureAnimator.interval = setInterval(TextureAnimator.nextFrame, 1000/settings.texture_fps.value) }, stop: function() { TextureAnimator.isPlaying = false clearInterval(TextureAnimator.interval) TextureAnimator.updateButton() }, toggle: function() { if (TextureAnimator.isPlaying) { TextureAnimator.stop() } else { TextureAnimator.start() } }, updateSpeed: function() { if (TextureAnimator.isPlaying) { TextureAnimator.stop() TextureAnimator.start() } }, nextFrame: function() { var animated_tex = [] textures.forEach(function(tex, i) { if (tex.frameCount > 1) { if (tex.currentFrame === undefined) { tex.currentFrame = 0 } else if (tex.currentFrame >= tex.frameCount-1) { tex.currentFrame = 0 } else { tex.currentFrame++; } $($('.texture').get(i)).find('img').css('margin-top', (tex.currentFrame*-48)+'px') animated_tex.push(tex) } }) elements.forEach(function(obj) { var update = false for (var face in obj.faces) { update = update || animated_tex.includes(obj.faces[face].getTexture()); } if (update) { Canvas.updateUV(obj, true) } }) }, reset: function() { TextureAnimator.stop() textures.forEach(function(tex, i) { if (tex.frameCount) { tex.currentFrame = 0 $($('.texture').get(i)).find('img').css('margin-top', '0') } }) while (i < elements.length) { Canvas.updateUV(elements[i], true) i++; } }, updateButton: function() { BarItems.animated_textures.setIcon( TextureAnimator.isPlaying ? 'pause' : 'play_arrow' ) } } onVueSetup(function() { texturelist = new Vue({ el: '#texture_list', data: {textures} }) texturelist._data.elements = textures }) BARS.defineActions(function() { new Action({ id: 'import_texture', icon: 'library_add', category: 'textures', keybind: new Keybind({key: 84, ctrl: true}), click: function () { openTexture() } }) new Action({ id: 'create_texture', icon: 'icon-create_bitmap', category: 'textures', keybind: new Keybind({key: 84, ctrl: true, shift: true}), click: function () { Painter.addBitmapDialog() } }) new Action({ id: 'reload_textures', icon: 'refresh', category: 'textures', keybind: new Keybind({key: 82, ctrl: true}), condition: isApp, click: reloadTextures }) new Action({ id: 'save_textures', icon: 'save', category: 'textures', keybind: new Keybind({key: 83, ctrl: true, alt: true}), click: function () {saveTextures()} }) new Action({ id: 'change_textures_folder', icon: 'fa-hdd-o', category: 'textures', condition: () => textures.length > 0, click: function () {changeTexturesFolder()} }) new Action({ id: 'animated_textures', icon: 'play_arrow', category: 'textures', condition: function() { if (Blockbench.entity_mode) return false; var i = 0; var show = false; while (i < textures.length) { if (textures[i].frameCount > 1) { show = true; i = textures.length } i++; } return show; }, click: function () { TextureAnimator.toggle() } }) })