From 274334b1118fb6bae494d577c9c23a64e87b281f Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 13 Dec 2020 15:10:08 +0100 Subject: [PATCH] Refactor codec, format and IO files Add new format property animation_files --- index.html | 4 +- js/io/codec.js | 212 +++++++++++++++++++++ js/io/format.js | 231 ++++++++++++++++++++++ js/io/formats/bedrock.js | 1 + js/io/formats/bedrock_old.js | 1 + js/io/io.js | 360 ----------------------------------- 6 files changed, 448 insertions(+), 361 deletions(-) create mode 100644 js/io/codec.js create mode 100644 js/io/format.js diff --git a/index.html b/index.html index 34937a2a..a0846194 100644 --- a/index.html +++ b/index.html @@ -104,8 +104,10 @@ - + + + diff --git a/js/io/codec.js b/js/io/codec.js new file mode 100644 index 00000000..5885baac --- /dev/null +++ b/js/io/codec.js @@ -0,0 +1,212 @@ +const Codecs = {}; +class Codec { + constructor(id, data) { + if (!data) data = 0; + this.id = id; + Codecs[id] = this; + this.name = data.name || 'Unknown Format'; + this.events = {}; + Merge.function(this, data, 'load'); + Merge.function(this, data, 'compile'); + Merge.function(this, data, 'parse'); + Merge.function(this, data, 'write'); + Merge.function(this, data, 'overwrite'); + Merge.function(this, data, 'export'); + Merge.function(this, data, 'fileName'); + Merge.function(this, data, 'afterSave'); + Merge.function(this, data, 'afterDownload'); + Merge.string(this, data, 'extension'); + Merge.boolean(this, data, 'remember'); + this.load_filter = data.load_filter; + this.export_action = data.export_action; + } + //Import + load(model, file, add) { + if (!this.parse) return false; + if (!add) { + newProject(this.format) + } + if (file.path && isApp && this.remember && !file.no_file ) { + var name = pathToName(file.path, true); + ModelMeta.name = pathToName(name, false); + ModelMeta.export_path = file.path; + addRecentProject({ + name, + path: file.path, + icon: Format.icon + }) + } + this.parse(model, file.path) + } + //parse(model, path) + + //Export + compile() { + this.dispatchEvent('compile', {content: ''}) + return ''; + } + export() { + var scope = this; + Blockbench.export({ + resource_id: 'model', + 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)) + } + fileName() { + return ModelMeta.name||Project.name||'model'; + } + startPath() { + return ModelMeta.export_path; + } + write(content, path) { + var scope = this; + if (fs.existsSync(path) && this.overwrite) { + this.overwrite(content, path, path => scope.afterSave(path)) + } else { + Blockbench.writeFile(path, {content}, path => scope.afterSave(path)); + } + } + //overwrite(content, path, cb) + afterDownload(path) { + if (this.remember) { + Prop.project_saved = true; + } + Blockbench.showQuickMessage(tl('message.save_file', [path ? pathToName(path, true) : this.fileName()])); + } + afterSave(path) { + var name = pathToName(path, true) + if (Format.codec == this || this.id == 'project') { + if (this.id == 'project') { + ModelMeta.save_path = path; + } else { + ModelMeta.export_path = path; + } + ModelMeta.name = pathToName(path, false); + Prop.project_saved = true; + } + if (this.remember) { + addRecentProject({ + name, + path: path, + icon: this.id == 'project' ? 'icon-blockbench_file' : Format.icon + }); + } + Blockbench.showQuickMessage(tl('message.save_file', [name])); + } + //Events + dispatchEvent(event_name, data) { + var list = this.events[event_name] + if (!list) return; + for (var i = 0; i < list.length; i++) { + if (typeof list[i] === 'function') { + list[i](data) + } + } + } + on(event_name, cb) { + if (!this.events[event_name]) { + this.events[event_name] = [] + } + this.events[event_name].safePush(cb) + } + //Delete + delete() { + delete Codecs[this.id]; + if (this.format && this.format.codec == this) delete this.format.codec; + } +} +Codec.getAllExtensions = function() { + let extensions = []; + for (var id in Codecs) { + if (Codecs[id].load_filter && Codecs[id].load_filter.extensions) { + extensions.safePush(...Codecs[id].load_filter.extensions); + } + } + return extensions; +} + + +//Import +function setupDragHandlers() { + Blockbench.addDragHandler( + 'model', + {extensions: Codec.getAllExtensions}, + function(files) { + loadModelFile(files[0]) + } + ) + Blockbench.addDragHandler( + 'style', + {extensions: ['bbstyle', 'bbtheme']}, + function(files) { + CustomTheme.import(files[0]); + } + ) + Blockbench.addDragHandler( + 'plugin', + {extensions: ['bbplugin', 'js']}, + function(files) { + new Plugin().loadFromFile(files[0], true) + } + ) + Blockbench.addDragHandler( + 'texture', + {extensions: ['png', 'tga'], propagate: true, readtype: 'image'}, + function(files, event) { + var texture_li = $(event.target).parents('li.texture') + if (texture_li.length) { + var tex = textures.findInArray('uuid', texture_li.attr('texid')) + if (tex) { + tex.fromFile(files[0]) + TickUpdates.selection = true; + return; + } + } + files.forEach(function(f) { + new Texture().fromFile(f).add().fillParticle() + }) + } + ) +} +function loadModelFile(file) { + if (showSaveDialog()) { + resetProject(); + + (function() { + var extension = pathToExtension(file.path); + // Text + for (var id in Codecs) { + let codec = Codecs[id]; + if (codec.load_filter && codec.load_filter.type == 'text') { + if (codec.load_filter.extensions.includes(extension) && Condition(codec.load_filter.condition, file.content)) { + codec.load(file.content, file); + return; + } + } + } + // JSON + var model = autoParseJSON(file.content); + for (var id in Codecs) { + let codec = Codecs[id]; + if (codec.load_filter && codec.load_filter.type == 'json') { + if (codec.load_filter.extensions.includes(extension) && Condition(codec.load_filter.condition, model)) { + codec.load(model, file); + return; + } + } + } + })(); + + EditSession.initNewModel() + if (!Format) { + Modes.options.start.select() + Modes.vue.$forceUpdate() + Blockbench.dispatchEvent('close_project'); + } + } +} diff --git a/js/io/format.js b/js/io/format.js new file mode 100644 index 00000000..7f913a93 --- /dev/null +++ b/js/io/format.js @@ -0,0 +1,231 @@ +var Format = 0; +const Formats = {}; +const ModelMeta = { + save_path: '', + export_path: '', + animation_path: '', + _name: '', + get name() {return this._name}, + set name(name) { + this._name = name; + Project.name = this._name; + setProjectTitle(this._name) + }, + get saved() {return Prop.project_saved}, + set saved(s) {Prop.project_saved = !!s}, +} + +//Formats +class ModelFormat { + constructor(data) { + Formats[data.id] = this; + this.id = data.id; + this.name = data.name || tl('format.'+this.id); + this.description = data.description || tl('format.'+this.id+'.desc'); + this.show_on_start_screen = true; + + this.box_uv = false; + this.optional_box_uv = false; + this.single_texture = false; + this.animated_textures = false; + this.bone_rig = false; + this.centered_grid = false; + this.rotate_cubes = false; + this.integer_size = false; + this.locators = false; + this.canvas_limit = false; + this.rotation_limit = false; + this.uv_rotation = false; + this.animation_files = false; + this.display_mode = false; + this.animation_mode = false; + + this.codec = data.codec; + this.onActivation = data.onActivation; + this.onDeactivation = data.onDeactivation; + Merge.string(this, data, 'icon'); + Merge.boolean(this, data, 'show_on_start_screen'); + + Merge.boolean(this, data, 'box_uv'); + Merge.boolean(this, data, 'optional_box_uv'); + Merge.boolean(this, data, 'single_texture'); + Merge.boolean(this, data, 'animated_textures'); + Merge.boolean(this, data, 'bone_rig'); + Merge.boolean(this, data, 'centered_grid'); + Merge.boolean(this, data, 'rotate_cubes'); + Merge.boolean(this, data, 'integer_size'); + Merge.boolean(this, data, 'locators'); + Merge.boolean(this, data, 'canvas_limit'); + Merge.boolean(this, data, 'rotation_limit'); + Merge.boolean(this, data, 'uv_rotation'); + Merge.boolean(this, data, 'animation_files'); + Merge.boolean(this, data, 'display_mode'); + Merge.boolean(this, data, 'animation_mode'); + } + select(converting) { + if (Format && typeof Format.onDeactivation == 'function') { + Format.onDeactivation() + } + Format = this; + if (typeof this.onActivation == 'function') { + Format.onActivation() + } + if (!converting || !this.optional_box_uv) { + Project.box_uv = Format.box_uv; + } + buildGrid() + if (Format.centered_grid) { + scene.position.set(0, 0, 0); + } else { + scene.position.set(-8, -8, -8); + } + Preview.all.forEach(preview => { + if (preview.isOrtho && typeof preview.angle == 'number') { + preview.loadAnglePreset(DefaultCameraPresets[preview.angle+1]) + } + }) + uv_dialog.all_editors.forEach(editor => { + editor.img.style.objectFit = Format.animated_textures ? 'cover' : 'fill'; + }) + for (var key in ModelProject.properties) { + if (Project[key] == undefined) { + ModelProject.properties[key].reset(Project); + } + } + updateSelection() + Modes.vue.$forceUpdate() + updateInterfacePanels() + updateShading(); + Canvas.updateRenderSides() + return this; + } + new() { + if (newProject(this)) { + BarItems.project_window.click(); + return true; + } + } + convertTo() { + + Undo.history.length = 0; + Undo.index = 0; + ModelMeta.export_path = ''; + + var old_format = Format + this.select(true) + Modes.options.edit.select() + + //Bone Rig + if (!Format.bone_rig && old_format.bone_rig) { + Group.all.forEach(group => { + group.rotation.V3_set(0, 0, 0); + }) + } + if (Format.bone_rig && !old_format.bone_rig) { + var loose_stuff = [] + Outliner.root.forEach(el => { + if (el instanceof Group == false) { + loose_stuff.push(el) + } + }) + if (loose_stuff.length) { + var root_group = new Group().init().addTo() + loose_stuff.forEach(el => { + el.addTo(root_group) + }) + } + if (!Project.geometry_name && Project.name) { + Project.geometry_name = Project.name; + } + } + if (Format.bone_rig) { + Group.all.forEach(group => { + group.createUniqueName(); + }) + } + + //Rotate Cubes + if (!Format.rotate_cubes && old_format.rotate_cubes) { + Cube.all.forEach(cube => { + cube.rotation.V3_set(0, 0, 0) + }) + } + + //Locators + if (!Format.locators && old_format.locators) { + Locator.all.slice().forEach(locator => { + locator.remove() + }) + } + + //Canvas Limit + if (Format.canvas_limit && !old_format.canvas_limit && !settings.deactivate_size_limit.value) { + + Cube.all.forEach(function(s, i) { + //Push elements into 3x3 block box + [0, 1, 2].forEach(function(ax) { + var overlap = s.to[ax] + s.inflate - 32 + if (overlap > 0) { + //If positive site overlaps + s.from[ax] -= overlap + s.to[ax] -= overlap + + if (16 + s.from[ax] - s.inflate < 0) { + s.from[ax] = -16 + s.inflate + } + } else { + overlap = s.from[ax] - s.inflate + 16 + if (overlap < 0) { + s.from[ax] -= overlap + s.to[ax] -= overlap + + if (s.to[ax] + s.inflate > 32) { + s.to[ax] = 32 - s.inflate + } + } + } + }) + }) + } + + //Rotation Limit + if (Format.rotation_limit && !old_format.rotation_limit && Format.rotate_cubes) { + Cube.all.forEach(cube => { + if (!cube.rotation.allEqual(0)) { + var axis = (cube.rotation_axis && getAxisNumber(cube.rotation_axis)) || 0; + var angle = limitNumber( Math.round(cube.rotation[axis]/22.5)*22.5, -45, 45 ); + cube.rotation.V3_set(0, 0, 0) + cube.rotation[axis] = angle; + } + }) + } + + //Animation Mode + if (!Format.animation_mode && old_format.animation_mode) { + Animator.animations.length = 0; + } + Canvas.updateAllPositions() + Canvas.updateAllBones() + Canvas.updateAllFaces() + updateSelection() + EditSession.initNewModel() + } + delete() { + delete Formats[this.id]; + if (this.codec && this.codec.format == this) delete this.codec.format; + } +} + +BARS.defineActions(function() { + new ModelFormat({ + id: 'free', + icon: 'icon-format_free', + rotate_cubes: true, + bone_rig: true, + centered_grid: true, + optional_box_uv: true, + uv_rotation: true, + animation_mode: true, + codec: Codecs.project + }) +}) diff --git a/js/io/formats/bedrock.js b/js/io/formats/bedrock.js index e9548891..be1a0644 100644 --- a/js/io/formats/bedrock.js +++ b/js/io/formats/bedrock.js @@ -899,6 +899,7 @@ var format = new ModelFormat({ bone_rig: true, centered_grid: true, animated_textures: true, + animation_files: true, animation_mode: true, locators: true, codec, diff --git a/js/io/formats/bedrock_old.js b/js/io/formats/bedrock_old.js index 336670d7..a858cc80 100644 --- a/js/io/formats/bedrock_old.js +++ b/js/io/formats/bedrock_old.js @@ -476,6 +476,7 @@ var format = new ModelFormat({ bone_rig: true, centered_grid: true, animated_textures: true, + animation_files: true, animation_mode: true, locators: true, codec, diff --git a/js/io/io.js b/js/io/io.js index c06b2b38..728fb0eb 100644 --- a/js/io/io.js +++ b/js/io/io.js @@ -1,351 +1,3 @@ -var Format = 0; -const Formats = {}; -const ModelMeta = { - save_path: '', - export_path: '', - animation_path: '', - _name: '', - get name() {return this._name}, - set name(name) { - this._name = name; - Project.name = this._name; - setProjectTitle(this._name) - }, - get saved() {return Prop.project_saved}, - set saved(s) {Prop.project_saved = !!s}, -} - -//Formats -class ModelFormat { - constructor(data) { - Formats[data.id] = this; - this.id = data.id; - this.name = data.name || tl('format.'+this.id); - this.description = data.description || tl('format.'+this.id+'.desc'); - this.show_on_start_screen = true; - - this.box_uv = false; - this.optional_box_uv = false; - this.single_texture = false; - this.animated_textures = false; - this.bone_rig = false; - this.centered_grid = false; - this.rotate_cubes = false; - this.integer_size = false; - this.locators = false; - this.canvas_limit = false; - this.rotation_limit = false; - this.uv_rotation = false; - this.ro_mode = false; - this.animation_mode = false; - - this.codec = data.codec; - this.onActivation = data.onActivation; - this.onDeactivation = data.onDeactivation; - Merge.string(this, data, 'icon'); - Merge.boolean(this, data, 'show_on_start_screen'); - - Merge.boolean(this, data, 'box_uv'); - Merge.boolean(this, data, 'optional_box_uv'); - Merge.boolean(this, data, 'single_texture'); - Merge.boolean(this, data, 'animated_textures'); - Merge.boolean(this, data, 'bone_rig'); - Merge.boolean(this, data, 'centered_grid'); - Merge.boolean(this, data, 'rotate_cubes'); - Merge.boolean(this, data, 'integer_size'); - Merge.boolean(this, data, 'locators'); - Merge.boolean(this, data, 'canvas_limit'); - Merge.boolean(this, data, 'rotation_limit'); - Merge.boolean(this, data, 'uv_rotation'); - Merge.boolean(this, data, 'display_mode'); - Merge.boolean(this, data, 'animation_mode'); - } - select(converting) { - if (Format && typeof Format.onDeactivation == 'function') { - Format.onDeactivation() - } - Format = this; - if (typeof this.onActivation == 'function') { - Format.onActivation() - } - if (!converting || !this.optional_box_uv) { - Project.box_uv = Format.box_uv; - } - buildGrid() - if (Format.centered_grid) { - scene.position.set(0, 0, 0); - } else { - scene.position.set(-8, -8, -8); - } - Preview.all.forEach(preview => { - if (preview.isOrtho && typeof preview.angle == 'number') { - preview.loadAnglePreset(DefaultCameraPresets[preview.angle+1]) - } - }) - uv_dialog.all_editors.forEach(editor => { - editor.img.style.objectFit = Format.animated_textures ? 'cover' : 'fill'; - }) - for (var key in ModelProject.properties) { - if (Project[key] == undefined) { - ModelProject.properties[key].reset(Project); - } - } - updateSelection() - Modes.vue.$forceUpdate() - updateInterfacePanels() - updateShading(); - Canvas.updateRenderSides() - return this; - } - new() { - if (newProject(this)) { - BarItems.project_window.click(); - return true; - } - } - convertTo() { - - Undo.history.length = 0; - Undo.index = 0; - ModelMeta.export_path = ''; - - var old_format = Format - this.select(true) - Modes.options.edit.select() - - //Bone Rig - if (!Format.bone_rig && old_format.bone_rig) { - Group.all.forEach(group => { - group.rotation.V3_set(0, 0, 0); - }) - } - if (Format.bone_rig && !old_format.bone_rig) { - var loose_stuff = [] - Outliner.root.forEach(el => { - if (el instanceof Group == false) { - loose_stuff.push(el) - } - }) - if (loose_stuff.length) { - var root_group = new Group().init().addTo() - loose_stuff.forEach(el => { - el.addTo(root_group) - }) - } - if (!Project.geometry_name && Project.name) { - Project.geometry_name = Project.name; - } - } - if (Format.bone_rig) { - Group.all.forEach(group => { - group.createUniqueName(); - }) - } - - //Rotate Cubes - if (!Format.rotate_cubes && old_format.rotate_cubes) { - Cube.all.forEach(cube => { - cube.rotation.V3_set(0, 0, 0) - }) - } - - //Locators - if (!Format.locators && old_format.locators) { - Locator.all.slice().forEach(locator => { - locator.remove() - }) - } - - //Canvas Limit - if (Format.canvas_limit && !old_format.canvas_limit && !settings.deactivate_size_limit.value) { - - Cube.all.forEach(function(s, i) { - //Push elements into 3x3 block box - [0, 1, 2].forEach(function(ax) { - var overlap = s.to[ax] + s.inflate - 32 - if (overlap > 0) { - //If positive site overlaps - s.from[ax] -= overlap - s.to[ax] -= overlap - - if (16 + s.from[ax] - s.inflate < 0) { - s.from[ax] = -16 + s.inflate - } - } else { - overlap = s.from[ax] - s.inflate + 16 - if (overlap < 0) { - s.from[ax] -= overlap - s.to[ax] -= overlap - - if (s.to[ax] + s.inflate > 32) { - s.to[ax] = 32 - s.inflate - } - } - } - }) - }) - } - - //Rotation Limit - if (Format.rotation_limit && !old_format.rotation_limit && Format.rotate_cubes) { - Cube.all.forEach(cube => { - if (!cube.rotation.allEqual(0)) { - var axis = (cube.rotation_axis && getAxisNumber(cube.rotation_axis)) || 0; - var angle = limitNumber( Math.round(cube.rotation[axis]/22.5)*22.5, -45, 45 ); - cube.rotation.V3_set(0, 0, 0) - cube.rotation[axis] = angle; - } - }) - } - - //Animation Mode - if (!Format.animation_mode && old_format.animation_mode) { - Animator.animations.length = 0; - } - Canvas.updateAllPositions() - Canvas.updateAllBones() - Canvas.updateAllFaces() - updateSelection() - EditSession.initNewModel() - } - delete() { - delete Formats[this.id]; - if (this.codec && this.codec.format == this) delete this.codec.format; - } -} -const Codecs = {}; -class Codec { - constructor(id, data) { - if (!data) data = 0; - this.id = id; - Codecs[id] = this; - this.name = data.name || 'Unknown Format'; - this.events = {}; - Merge.function(this, data, 'load'); - Merge.function(this, data, 'compile'); - Merge.function(this, data, 'parse'); - Merge.function(this, data, 'write'); - Merge.function(this, data, 'overwrite'); - Merge.function(this, data, 'export'); - Merge.function(this, data, 'fileName'); - Merge.function(this, data, 'afterSave'); - Merge.function(this, data, 'afterDownload'); - Merge.string(this, data, 'extension'); - Merge.boolean(this, data, 'remember'); - this.load_filter = data.load_filter; - this.export_action = data.export_action; - } - //Import - load(model, file, add) { - if (!this.parse) return false; - if (!add) { - newProject(this.format) - } - if (file.path && isApp && this.remember && !file.no_file ) { - var name = pathToName(file.path, true); - ModelMeta.name = pathToName(name, false); - ModelMeta.export_path = file.path; - addRecentProject({ - name, - path: file.path, - icon: Format.icon - }) - } - this.parse(model, file.path) - } - //parse(model, path) - - //Export - compile() { - this.dispatchEvent('compile', {content: ''}) - return ''; - } - export() { - var scope = this; - Blockbench.export({ - resource_id: 'model', - 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)) - } - fileName() { - return ModelMeta.name||Project.name||'model'; - } - startPath() { - return ModelMeta.export_path; - } - write(content, path) { - var scope = this; - if (fs.existsSync(path) && this.overwrite) { - this.overwrite(content, path, path => scope.afterSave(path)) - } else { - Blockbench.writeFile(path, {content}, path => scope.afterSave(path)); - } - } - //overwrite(content, path, cb) - afterDownload(path) { - if (this.remember) { - Prop.project_saved = true; - } - Blockbench.showQuickMessage(tl('message.save_file', [path ? pathToName(path, true) : this.fileName()])); - } - afterSave(path) { - var name = pathToName(path, true) - if (Format.codec == this || this.id == 'project') { - if (this.id == 'project') { - ModelMeta.save_path = path; - } else { - ModelMeta.export_path = path; - } - ModelMeta.name = pathToName(path, false); - Prop.project_saved = true; - } - if (this.remember) { - addRecentProject({ - name, - path: path, - icon: this.id == 'project' ? 'icon-blockbench_file' : Format.icon - }); - } - Blockbench.showQuickMessage(tl('message.save_file', [name])); - } - //Events - dispatchEvent(event_name, data) { - var list = this.events[event_name] - if (!list) return; - for (var i = 0; i < list.length; i++) { - if (typeof list[i] === 'function') { - list[i](data) - } - } - } - on(event_name, cb) { - if (!this.events[event_name]) { - this.events[event_name] = [] - } - this.events[event_name].safePush(cb) - } - //Delete - delete() { - delete Codecs[this.id]; - if (this.format && this.format.codec == this) delete this.format.codec; - } -} -Codec.getAllExtensions = function() { - let extensions = []; - for (var id in Codecs) { - if (Codecs[id].load_filter && Codecs[id].load_filter.extensions) { - extensions.safePush(...Codecs[id].load_filter.extensions); - } - } - return extensions; -} - - //Import function setupDragHandlers() { Blockbench.addDragHandler( @@ -818,18 +470,6 @@ function autoParseJSON(data, feedback) { BARS.defineActions(function() { - new ModelFormat({ - id: 'free', - icon: 'icon-format_free', - rotate_cubes: true, - bone_rig: true, - centered_grid: true, - optional_box_uv: true, - uv_rotation: true, - animation_mode: true, - codec: Codecs.project - }) - //Import new Action('open_model', { icon: 'assessment',