diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..341b9d4b5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +build +node_modules +**/*.js \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..fd5ef5439 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/prettierrc", + "tabWidth": 4, + "useTabs": true, + "semi": true, + "singleQuote": true, + "arrowParens": "avoid", + "printWidth": 100, + "trailingComma": "es5", + "bracketSameLine": false, + "endOfLine": "auto", + "overrides": [ + { + "files": ["**/*.yml", "**/*.yaml"], + "options": { + "parser": "yaml", + "tabWidth": 2, + "useTabs": false + } + }, + { + "files": "**/*.ts", + "options": { + "parser": "typescript" + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index c082db11c..e39aa4103 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,9 @@ { - "editor.indentSize": "tabSize", - "editor.tabSize": 4, - "editor.insertSpaces": false + "editor.indentSize": "tabSize", + "editor.tabSize": 4, + "editor.insertSpaces": false, + "editor.formatOnSave": true, + // Disable foramtting and error checking of node_modules to reduce clutter and improve performance. + "npm.exclude": ["**/node_modules"], + "errorLens.excludePatterns": ["**/node_modules/**/*"] } diff --git a/js/animations/molang_editor.ts b/js/animations/molang_editor.ts index b3db12fd1..c17bf2ec9 100644 --- a/js/animations/molang_editor.ts +++ b/js/animations/molang_editor.ts @@ -1,21 +1,21 @@ -import { MolangAutocomplete } from "./molang"; +import { MolangAutocomplete } from './molang'; interface MolangEditorOptions { - autocomplete_context: MolangAutocomplete.Context - text: string + autocomplete_context: MolangAutocomplete.Context; + text: string; } -export function openMolangEditor(options: MolangEditorOptions, callback: ((result: string) => void)) { +export function openMolangEditor(options: MolangEditorOptions, callback: (result: string) => void) { interface VueData { - text: string + text: string; } let dialog = new Dialog('expression_editor', { title: 'menu.text_edit.expression_editor', resizable: true, width: 800, component: { - components: {VuePrismEditor}, + components: { VuePrismEditor }, data: { - text: options.text + text: options.text, }, methods: { prettyPrint(this: VueData) { @@ -25,15 +25,19 @@ export function openMolangEditor(options: MolangEditorOptions, callback: ((resul this.text = this.text.replace(/\n/g, '').replace(/\s{2,}/g, ' '); }, findReplace(this: VueData) { - this + this; let scope = this; new Dialog({ id: 'find_replace', title: 'action.find_replace', form: { - find: {label: 'dialog.find_replace.find', type: 'text'}, - replace: {label: 'dialog.find_replace.replace', type: 'text'}, - regex: {label: 'dialog.find_replace.regex', type: 'checkbox', value: false}, + find: { label: 'dialog.find_replace.find', type: 'text' }, + replace: { label: 'dialog.find_replace.replace', type: 'text' }, + regex: { + label: 'dialog.find_replace.regex', + type: 'checkbox', + value: false, + }, }, onConfirm(form) { if (!form.find) return; @@ -46,14 +50,14 @@ export function openMolangEditor(options: MolangEditorOptions, callback: ((resul } } scope.text = replace(scope.text); - } + }, }).show(); }, autocomplete(text: string, position: number) { if (Settings.get('autocomplete_code') == false) return []; let test = options.autocomplete_context.autocomplete(text, position); return test; - } + }, }, template: `
@@ -72,19 +76,23 @@ export function openMolangEditor(options: MolangEditorOptions, callback: ((resul :line-numbers="true" />
- ` + `, }, onOpen() { - let element = document.querySelector('#expression_editor_prism.molang_input') as HTMLElement; - element.style.height = (dialog.object.clientHeight - 50) + 'px'; + let element = document.querySelector( + '#expression_editor_prism.molang_input' + ) as HTMLElement; + element.style.height = dialog.object.clientHeight - 50 + 'px'; }, onResize() { - let element = document.querySelector('#expression_editor_prism.molang_input') as HTMLElement; - element.style.height = (dialog.object.clientHeight - 50) + 'px'; + let element = document.querySelector( + '#expression_editor_prism.molang_input' + ) as HTMLElement; + element.style.height = dialog.object.clientHeight - 50 + 'px'; }, onConfirm() { callback(dialog.content_vue.$data.text); - } - }) + }, + }); dialog.show(); } diff --git a/js/api.ts b/js/api.ts index 297f404e0..610b76ad5 100644 --- a/js/api.ts +++ b/js/api.ts @@ -1,40 +1,40 @@ -import { FormElementOptions } from "./interface/form"; -import { ModelFormat } from "./io/format"; -import { Prop } from "./misc"; -import { EventSystem } from "./util/event_system"; -import { compareVersions } from "./util/util"; -import { Filesystem } from "./file_system"; -import { MessageBoxOptions } from "./interface/dialog"; -import { currentwindow, shell, SystemInfo } from "./native_apis"; +import { FormElementOptions } from './interface/form'; +import { ModelFormat } from './io/format'; +import { Prop } from './misc'; +import { EventSystem } from './util/event_system'; +import { compareVersions } from './util/util'; +import { Filesystem } from './file_system'; +import { MessageBoxOptions } from './interface/dialog'; +import { currentwindow, shell, SystemInfo } from './native_apis'; declare const appVersion: string; -declare let Format: ModelFormat - +declare let Format: ModelFormat; interface ToastNotificationOptions { /** * Text message */ - text: string + text: string; /** * Blockbench icon string */ - icon?: string + icon?: string; /** * Expire time in miliseconds */ - expire?: number + expire?: number; /** * Background color, accepts any CSS color string */ - color?: string + color?: string; /** - * Method to run on click. + * Method to run on click. * @returns Return `true` to close toast */ - click?: (event: Event) => boolean + click?: (event: Event) => boolean; } -export const LastVersion = localStorage.getItem('last_version') || localStorage.getItem('welcomed_version') || appVersion; +export const LastVersion = + localStorage.getItem('last_version') || localStorage.getItem('welcomed_version') || appVersion; export const Blockbench = { ...window.Blockbench, @@ -57,18 +57,18 @@ export const Blockbench = { * @deprecated Use Undo.initEdit and Undo.finishEdit instead */ edit(aspects: UndoAspects, cb: () => void) { - Undo.initEdit(aspects) + Undo.initEdit(aspects); cb(); - Undo.finishEdit('Edit') + Undo.finishEdit('Edit'); }, reload() { if (isApp) { - Blockbench.setProgress(0) - Blockbench.addFlag('allow_closing') - Blockbench.addFlag('allow_reload') - location.reload() + Blockbench.setProgress(0); + Blockbench.addFlag('allow_closing'); + Blockbench.addFlag('allow_reload'); + location.reload(); } else { - location.reload() + location.reload(); } }, isNewerThan(version: string): boolean { @@ -78,13 +78,18 @@ export const Blockbench = { return compareVersions(version, Blockbench.version); }, registerEdit() { - console.warn('Blockbench.registerEdit is outdated. Please use Undo.initEdit and Undo.finishEdit') + console.warn( + 'Blockbench.registerEdit is outdated. Please use Undo.initEdit and Undo.finishEdit' + ); }, //Interface - getIconNode(icon: IconString | boolean | HTMLElement | (() => (IconString | boolean | HTMLElement)), color?: string) { + getIconNode( + icon: IconString | boolean | HTMLElement | (() => IconString | boolean | HTMLElement), + color?: string + ) { let node; if (typeof icon === 'function') { - icon = icon() + icon = icon(); } if (icon === undefined) { //Missing @@ -93,18 +98,16 @@ export const Blockbench = { node.innerText = 'help_outline'; } else if (icon instanceof HTMLElement) { //Node - node = icon + node = icon; } else if (icon === true || icon === false) { //Boolean node = document.createElement('i'); node.classList.add('material-icons', 'notranslate', 'icon'); node.innerText = icon ? 'check_box' : 'check_box_outline_blank'; - } else if (icon === null) { //Node node = document.createElement('i'); node.classList.add('fa_big', 'icon'); - } else if (icon.match(/^(fa[.-])|(fa[rsb]\.)/)) { //Font Awesome node = document.createElement('i'); @@ -136,25 +139,29 @@ export const Blockbench = { node.classList.add('color_y'); } else if (color === 'z') { node.classList.add('color_z'); - } else if (color === 'u') { + } else if (color === 'u') { node.classList.add('color_u'); - } else if (color === 'v') { + } else if (color === 'v') { node.classList.add('color_v'); - } else if (color === 'w') { + } else if (color === 'w') { node.classList.add('color_w'); } else if (typeof color === 'string') { node.style.color = color; } } - return node + return node; }, showQuickMessage(message, time = 1000) { document.getElementById('quick_message_box')?.remove(); - let quick_message_box = Interface.createElement('div', {id: 'quick_message_box'}, tl(message)); + let quick_message_box = Interface.createElement( + 'div', + { id: 'quick_message_box' }, + tl(message) + ); document.body.append(quick_message_box); - setTimeout(function() { - quick_message_box.remove() + setTimeout(function () { + quick_message_box.remove(); }, time); }, @@ -172,22 +179,26 @@ export const Blockbench = { let close_button = document.createElement('div'); close_button.innerHTML = 'clear'; close_button.className = 'toast_close_button'; - close_button.addEventListener('click', (event) => { + close_button.addEventListener('click', event => { notification.remove(); - }) + }); notification.append(close_button); if (options.color) { notification.style.backgroundColor = options.color; } if (typeof options.click == 'function') { - notification.addEventListener('click', (event) => { - if (event.target == close_button || (event.target as HTMLElement).parentElement == close_button) return; + notification.addEventListener('click', event => { + if ( + event.target == close_button || + (event.target as HTMLElement).parentElement == close_button + ) + return; let result = options.click(event); if (result == true) { notification.remove(); } - }) + }); notification.style.cursor = 'pointer'; } @@ -200,119 +211,133 @@ export const Blockbench = { document.getElementById('toast_notification_list').append(notification); function deletableToast(node: HTMLElement) { - this.delete = function() { + this.delete = function () { node.remove(); - } + }; } return new deletableToast(notification); }, setCursorTooltip(text?: string): void {}, setProgress(progress: number, time: number = 0, bar?: string): void {}, showStatusMessage(message: string, time: number = 800) { - Blockbench.setStatusBarText(tl(message)) - setTimeout(function() { - Blockbench.setStatusBarText() + Blockbench.setStatusBarText(tl(message)); + setTimeout(function () { + Blockbench.setStatusBarText(); }, time); }, setStatusBarText(text?: string) { if (text !== undefined) { - Prop.file_name = text + Prop.file_name = text; } else { - Prop.file_name = Prop.file_name_alt||'' + Prop.file_name = Prop.file_name_alt || ''; } }, showMessage(message, location) { if (location === 'status_bar') { - Blockbench.showStatusMessage(message) + Blockbench.showStatusMessage(message); } else if (location === 'center') { - Blockbench.showQuickMessage(message) + Blockbench.showQuickMessage(message); } }, - showMessageBox(options: MessageBoxOptions, cb?: (button: number | string, result?: Record, event?: Event) => void) { + showMessageBox( + options: MessageBoxOptions, + cb?: (button: number | string, result?: Record, event?: Event) => void + ) { return new MessageBox(options, cb).show(); }, /** - * - * @param {*} title - * @param {*} value - * @param {*} callback + * + * @param {*} title + * @param {*} value + * @param {*} callback * @param {object} options Options * @param {string} options.info Info text * @param {string} options.description Description for the text input * @returns {Promise} Input value */ - async textPrompt(title: string, value: string, callback: (text: string) => void, options: {placeholder?: string, description?: string, info?: string} = {}) { + async textPrompt( + title: string, + value: string, + callback: (text: string) => void, + options: { placeholder?: string; description?: string; info?: string } = {} + ) { if (typeof options == 'string') { - options = {placeholder: options}; + options = { placeholder: options }; console.warn('textPrompt: 4th argument is expected to be an object'); } - let answer = await new Promise((resolve) => { + let answer = await new Promise(resolve => { let form: Record = { - text: {type: 'text', full_width: true, placeholder: options.placeholder, value, description: options.description}, + text: { + type: 'text', + full_width: true, + placeholder: options.placeholder, + value, + description: options.description, + }, }; if (options.info) { form.description = { type: 'info', - text: tl(options.info) - } + text: tl(options.info), + }; } new Dialog({ id: 'text_input', title: title || 'dialog.input.title', form, - onConfirm({text}) { + onConfirm({ text }) { if (callback) callback(text); resolve(text); }, onOpen() { this.object.querySelector('input')?.focus(); - } + }, }).show(); }); return answer; }, addMenuEntry(name: string, icon: IconString, click) { - console.warn('Blockbench.addMenuEntry is deprecated. Please use Actions instead.') + console.warn('Blockbench.addMenuEntry is deprecated. Please use Actions instead.'); let id = name.replace(/\s/g, '').toLowerCase(); - var action = new Action(id, {icon: icon, name: name, click: click}) - MenuBar.addAction(action, 'tools') + var action = new Action(id, { icon: icon, name: name, click: click }); + MenuBar.addAction(action, 'tools'); }, removeMenuEntry(name: string) { let id = name.replace(/\s/g, '').toLowerCase(); - MenuBar.removeAction('tools.'+id); + MenuBar.removeAction('tools.' + id); }, openLink(link: string) { if (isApp) { - shell.openExternal(link) + shell.openExternal(link); } else { - window.open(link) + window.open(link); } }, notification(title: string, text: string, icon?: string) { Notification.requestPermission().then(status => { if (status == 'granted') { - let n = new Notification(title, {body: text, icon: icon||'favicon.png'}) - n.onclick = function() { + let n = new Notification(title, { body: text, icon: icon || 'favicon.png' }); + n.onclick = function () { if (isApp) { // @ts-ignore currentwindow.focus(); } else { window.focus(); } - } + }; } - }) + }); }, //CSS addCSS(css: string): Deletable { let style_node = document.createElement('style'); - style_node.type ='text/css'; + style_node.type = 'text/css'; style_node.appendChild(document.createTextNode(css)); document.getElementsByTagName('head')[0].appendChild(style_node); function deletableStyle(node) { - this.delete = function() { + this.delete = function () { node.remove(); - } + }; } return new deletableStyle(style_node); }, @@ -358,13 +383,17 @@ export const Blockbench = { }, // Update onUpdateTo(version, callback) { - if (LastVersion && compareVersions(version, LastVersion) && !Blockbench.isOlderThan(version)) { + if ( + LastVersion && + compareVersions(version, LastVersion) && + !Blockbench.isOlderThan(version) + ) { callback(LastVersion); } }, // Globals - Format: 0 as (ModelFormat | number), - Project: 0 as (ModelProject | number), + Format: 0 as ModelFormat | number, + Project: 0 as ModelProject | number, get Undo() { return Project?.undo; }, @@ -381,8 +410,7 @@ export const Blockbench = { addDragHandler: Filesystem.addDragHandler, removeDragHandler: Filesystem.removeDragHandler, }; - -(function() { +(function () { if (!LastVersion || LastVersion.replace(/.\d+$/, '') != appVersion.replace(/.\d+$/, '')) { Blockbench.addFlag('after_update'); } else if (LastVersion != appVersion) { @@ -398,9 +426,15 @@ export const Blockbench = { if (isApp) { Blockbench.platform = SystemInfo.platform; switch (Blockbench.platform) { - case 'win32': Blockbench.operating_system = 'Windows'; break; - case 'darwin': Blockbench.operating_system = 'macOS'; break; - default: Blockbench.operating_system = 'Linux'; break; + case 'win32': + Blockbench.operating_system = 'Windows'; + break; + case 'darwin': + Blockbench.operating_system = 'macOS'; + break; + default: + Blockbench.operating_system = 'Linux'; + break; } // @ts-ignore if (Blockbench.platform.includes('win32') === true) window.osfs = '\\'; @@ -409,5 +443,5 @@ if (isApp) { Object.assign(window, { LastVersion, Blockbench, - isApp + isApp, }); diff --git a/js/display_mode/DisplayModePanel.vue b/js/display_mode/DisplayModePanel.vue index c266f6b6b..592a13779 100644 --- a/js/display_mode/DisplayModePanel.vue +++ b/js/display_mode/DisplayModePanel.vue @@ -3,123 +3,299 @@

{{ tl('display.slot') }}

- - - - + + + + - - - - + + + + - - + + - - + + - - - - - + + - - + + + + +

{{ tl('display.reference') }}

-
-
+
-

{{ tl('display.rotation') }}

-
replay
+
+ replay +
-
- - +
+ +
- +

{{ tl('display.translation') }}

-
replay
+
+ replay
-
- +
+ - + value="0" + @input="change(axis, 'translation')" + @mousedown="start()" + @change="save" + /> +

{{ tl('display.scale') }}

-
flip
-
replay
+
+ flip +
+
+ replay +
-
-
+
+
{{ tl('display.mirror') }}
- {{ slot.mirror[axis] ? 'check_box' : 'check_box_outline_blank' }} + {{ + slot.mirror[axis] ? 'check_box' : 'check_box_outline_blank' + }}
- - + value="0" + @input="change(axis, 'scale')" + @mousedown="start(axis, 'scale')" + @change="save(axis, 'scale')" + /> +
-
- - +
+ +
- + - + -
` +
`, }, onButton() { Settings.save(); function hasSettingChanged(id) { - return (Settings.old && settings[id].value !== Settings.old[id]) + return Settings.old && settings[id].value !== Settings.old[id]; } let changed_settings = []; for (let id in settings) { @@ -352,7 +364,7 @@ onVueSetup(function() { setting.onChange(setting.value); } if (isApp && setting.launch_setting) { - ipcRenderer.send('edit-launch-setting', {key: id, value: setting.value}) + ipcRenderer.send('edit-launch-setting', { key: id, value: setting.value }); } } } @@ -360,6 +372,6 @@ onVueSetup(function() { if (restart_settings.length) { Settings.showRestartMessage(restart_settings); } - } - }) -}) \ No newline at end of file + }, + }); +}); diff --git a/js/interface/themes.ts b/js/interface/themes.ts index 2ad4104ba..93007653a 100644 --- a/js/interface/themes.ts +++ b/js/interface/themes.ts @@ -1,40 +1,40 @@ -import DarkTheme from '../../themes/dark.bbtheme' -import LightTheme from '../../themes/light.bbtheme' -import ContrastTheme from '../../themes/contrast.bbtheme' -import { compareVersions, patchedAtob } from '../util/util' -import { Dialog } from './dialog' -import { settings, Settings } from './settings' -import tinycolor from 'tinycolor2' -import { BBYaml } from '../util/yaml' -import { Blockbench } from '../api' -import { InputFormConfig } from './form' -import { Filesystem } from '../file_system' -import { fs } from '../native_apis' +import DarkTheme from '../../themes/dark.bbtheme'; +import LightTheme from '../../themes/light.bbtheme'; +import ContrastTheme from '../../themes/contrast.bbtheme'; +import { compareVersions, patchedAtob } from '../util/util'; +import { Dialog } from './dialog'; +import { settings, Settings } from './settings'; +import tinycolor from 'tinycolor2'; +import { BBYaml } from '../util/yaml'; +import { Blockbench } from '../api'; +import { InputFormConfig } from './form'; +import { Filesystem } from '../file_system'; +import { fs } from '../native_apis'; type ThemeSource = 'built_in' | 'file' | 'repository' | 'custom'; type ThemeData = { - name: string - author: string - version?: string - borders?: boolean - thumbnail?: string - css: string - id: string - main_font?: string - headline_font?: string - code_font?: string - colors: Record - source?: ThemeSource - path?: string - desktop_only?: boolean + name: string; + author: string; + version?: string; + borders?: boolean; + thumbnail?: string; + css: string; + id: string; + main_font?: string; + headline_font?: string; + code_font?: string; + colors: Record; + source?: ThemeSource; + path?: string; + desktop_only?: boolean; options?: { [key: string]: { - name: string - options: Record - } - } - option_values: Record -} + name: string; + options: Record; + }; + }; + option_values: Record; +}; const DEFAULT_COLORS = { ui: '#1e2127', @@ -55,30 +55,30 @@ const DEFAULT_COLORS = { grid: '#495061', wireframe: '#576f82', checkerboard: '#14171b', -} +}; export class CustomTheme { - name: string - author: string - version?: string - borders: boolean - thumbnail?: string - css: string - id: string - main_font: string - headline_font: string - code_font: string - colors: Record + name: string; + author: string; + version?: string; + borders: boolean; + thumbnail?: string; + css: string; + id: string; + main_font: string; + headline_font: string; + code_font: string; + colors: Record; source?: 'built_in' | 'file' | 'repository' | 'custom'; - path?: string - desktop_only?: boolean - options: null|{ + path?: string; + desktop_only?: boolean; + options: null | { [key: string]: { - name: string - options: Record - } - } - option_values: Record + name: string; + options: Record; + }; + }; + option_values: Record; constructor(data?: Partial) { this.id = ''; @@ -99,17 +99,17 @@ export class CustomTheme { } } extend(data: Partial) { - Merge.string(this, data, 'id') - Merge.string(this, data, 'name') - Merge.string(this, data, 'author') - Merge.string(this, data, 'version') - Merge.string(this, data, 'source') - Merge.string(this, data, 'path') - Merge.boolean(this, data, 'desktop_only') - Merge.boolean(this, data, 'borders') - Merge.string(this, data, 'main_font') - Merge.string(this, data, 'headline_font') - Merge.string(this, data, 'code_font') + Merge.string(this, data, 'id'); + Merge.string(this, data, 'name'); + Merge.string(this, data, 'author'); + Merge.string(this, data, 'version'); + Merge.string(this, data, 'source'); + Merge.string(this, data, 'path'); + Merge.boolean(this, data, 'desktop_only'); + Merge.boolean(this, data, 'borders'); + Merge.string(this, data, 'main_font'); + Merge.string(this, data, 'headline_font'); + Merge.string(this, data, 'code_font'); Merge.string(this, data, 'css'); Merge.string(this, data, 'thumbnail'); @@ -132,14 +132,16 @@ export class CustomTheme { let theme = this; for (let key in this.options) { let opt = this.options[key]; - let chars = Object.keys(opt.options).reduce((val, key) => val + opt.options[key].length); + let chars = Object.keys(opt.options).reduce( + (val, key) => val + opt.options[key].length + ); form[key] = { label: opt.name, type: chars.length > 28 ? 'select' : 'inline_select', options: opt.options, value: this.option_values[key], default: this.option_values[key], - } + }; } new Dialog('theme_configuration', { title: 'layout.theme.configure', @@ -153,26 +155,21 @@ export class CustomTheme { }).show(); } - static selected: CustomTheme = new CustomTheme({id: 'dark'}) + static selected: CustomTheme = new CustomTheme({ id: 'dark' }); static get data(): CustomTheme { - return CustomTheme.selected + return CustomTheme.selected; } - static backup_data: string | null = null - static themes: CustomTheme[] = [ - DarkTheme, - LightTheme, - ContrastTheme - ].map(theme_data => { + static backup_data: string | null = null; + static themes: CustomTheme[] = [DarkTheme, LightTheme, ContrastTheme].map(theme_data => { let theme = new CustomTheme().parseBBTheme(theme_data, true); theme.source = 'built_in'; return theme; - }) - static defaultColors = DEFAULT_COLORS - static sideloaded_themes: string[] = [] - static dialog: Dialog|null = null + }); + static defaultColors = DEFAULT_COLORS; + static sideloaded_themes: string[] = []; + static dialog: Dialog | null = null; static setup() { - - fs + fs; const theme_watchers: Record = {}; let remote_themes_loaded = false; CustomTheme.dialog = new Dialog({ @@ -201,8 +198,10 @@ export class CustomTheme { name: 'layout.documentation', icon: 'fa-book', click() { - Blockbench.openLink('https://www.blockbench.net/wiki/blockbench/themes'); - } + Blockbench.openLink( + 'https://www.blockbench.net/wiki/blockbench/themes' + ); + }, }, 'import_theme', 'export_theme', @@ -210,30 +209,36 @@ export class CustomTheme { onPageSwitch(page) { CustomTheme.dialog.content_vue.open_category = page; if (page == 'color' && !CustomTheme.dialog_is_setup) { - CustomTheme.setupDialog() + CustomTheme.setupDialog(); } - } + }, }, onOpen() { this.content_vue.data = CustomTheme.data; if (!remote_themes_loaded) { remote_themes_loaded = true; - $.getJSON('https://api.github.com/repos/JannisX11/blockbench-themes/contents/themes').then(files => { - files.forEach(async (file) => { - try { - let {content} = await $.getJSON(file.git_url); - let theme = new CustomTheme().parseBBTheme(patchedAtob(content)); - if (theme.desktop_only && Blockbench.isMobile) return false; - theme.id = file.name.replace(/\.\w+/, ''); - theme.source = 'repository'; - if (!CustomTheme.themes.find(t2 => t2.id == theme.id)) { - CustomTheme.themes.push(theme); + $.getJSON( + 'https://api.github.com/repos/JannisX11/blockbench-themes/contents/themes' + ) + .then(files => { + files.forEach(async file => { + try { + let { content } = await $.getJSON(file.git_url); + let theme = new CustomTheme().parseBBTheme( + patchedAtob(content) + ); + if (theme.desktop_only && Blockbench.isMobile) return false; + theme.id = file.name.replace(/\.\w+/, ''); + theme.source = 'repository'; + if (!CustomTheme.themes.find(t2 => t2.id == theme.id)) { + CustomTheme.themes.push(theme); + } + } catch (err) { + console.error(err); } - } catch (err) { - console.error(err); - } + }); }) - }).catch(console.error) + .catch(console.error); } }, component: { @@ -246,10 +251,10 @@ export class CustomTheme { built_in: '', repository: 'globe', file: 'draft', - } + }, }, components: { - VuePrismEditor + VuePrismEditor, }, watch: { 'data.main_font'() { @@ -281,8 +286,8 @@ export class CustomTheme { CustomTheme.updateSettings(); CustomTheme.selected.save(); }, - deep: true - } + deep: true, + }, }, methods: { selectTheme(theme: CustomTheme) { @@ -327,7 +332,7 @@ export class CustomTheme { return; } Filesystem.showFileInFolder(theme.path); - } + }, }, { name: 'layout.file.watch_changes', @@ -337,22 +342,24 @@ export class CustomTheme { if (theme_watchers[theme.path]) { theme_watchers[theme.path].close(); delete theme_watchers[theme.path]; - } else if (fs.existsSync(theme.path)) { let timeout: number = 0; - theme_watchers[theme.path] = fs.watch(theme.path, (eventType) => { - if (eventType == 'change') { - if (timeout) { - clearTimeout(timeout); - timeout = 0; + theme_watchers[theme.path] = fs.watch( + theme.path, + eventType => { + if (eventType == 'change') { + if (timeout) { + clearTimeout(timeout); + timeout = 0; + } + timeout = window.setTimeout(() => { + theme.reloadThemeFile(); + }, 60); } - timeout = window.setTimeout(() => { - theme.reloadThemeFile(); - }, 60) } - }) + ); } - } + }, }, { name: 'generic.reload', @@ -360,7 +367,7 @@ export class CustomTheme { condition: isApp && selected, click: () => { theme.reloadThemeFile(); - } + }, }, { name: 'generic.remove', @@ -368,13 +375,16 @@ export class CustomTheme { click: () => { this.themes.remove(theme); CustomTheme.sideloaded_themes.remove(theme.path); - localStorage.setItem('themes_sideloaded', JSON.stringify(CustomTheme.sideloaded_themes)); - } - } - ]) + localStorage.setItem( + 'themes_sideloaded', + JSON.stringify(CustomTheme.sideloaded_themes) + ); + }, + }, + ]); menu.open(event); }, - tl + tl, }, computed: { listed_themes() { @@ -383,7 +393,7 @@ export class CustomTheme { themes.splice(0, 0, this.data); } return themes; - } + }, }, template: `
@@ -518,12 +528,12 @@ export class CustomTheme {
-
` +
`, }, onButton() { Settings.save(); - } - }) + }, + }); } static setupDialog() { let wrapper = $('#color_wrapper'); @@ -534,7 +544,7 @@ export class CustomTheme { let field = wrapper.find(`#color_field_${scope_key} .layout_color_preview`); field.spectrum({ - preferredFormat: "hex", + preferredFormat: 'hex', color: hex, showAlpha: false, showInput: true, @@ -557,7 +567,7 @@ export class CustomTheme { beforeShow() { last_color = CustomTheme.selected.colors[scope_key]; field.spectrum('set', last_color); - } + }, }); } CustomTheme.dialog_is_setup = true; @@ -567,23 +577,26 @@ export class CustomTheme { if (CustomTheme.selected.source != 'custom') { let theme = new CustomTheme(CustomTheme.selected); theme.extend({ - name: theme.name ? ('Copy of ' + theme.name) : 'Custom Theme', + name: theme.name ? 'Copy of ' + theme.name : 'Custom Theme', author: settings.username.value as string, id: 'custom_theme', source: 'custom', - }) + }); let i = 0; while (CustomTheme.themes.find(t2 => theme.id == t2.id)) { i++; - theme.id = 'custom_theme_'+i; + theme.id = 'custom_theme_' + i; } if (CustomTheme.dialog.content_vue) CustomTheme.dialog.content_vue.data = theme; theme.load(); } } - static updateColors() { + static updateColors() { $('meta[name=theme-color]').attr('content', CustomTheme.selected.colors.frame); - document.body.classList.toggle('light_mode', new tinycolor(CustomTheme.selected.colors.ui).isLight()); + document.body.classList.toggle( + 'light_mode', + new tinycolor(CustomTheme.selected.colors.ui).isLight() + ); let gizmo_colors = Canvas.gizmo_colors; if (typeof gizmo_colors != 'undefined') { @@ -605,50 +618,63 @@ export class CustomTheme { update(gizmo_colors.outline, '--color-outline'); update(gizmo_colors.gizmo_hover, '--color-gizmohover'); update(Canvas.outlineMaterial.color, '--color-outline'); - update((Canvas.ground_plane.material as THREE.MeshBasicMaterial).color, '--color-ground'); - update((Canvas.brush_outline.material as THREE.ShaderMaterial).uniforms.color.value, '--color-brush-outline'); + update( + (Canvas.ground_plane.material as THREE.MeshBasicMaterial).color, + '--color-ground' + ); + update( + (Canvas.brush_outline.material as THREE.ShaderMaterial).uniforms.color.value, + '--color-brush-outline' + ); update(gizmo_colors.spline_handle_aligned, '--color-spline-handle-aligned'); update(gizmo_colors.spline_handle_mirrored, '--color-spline-handle-mirrored'); update(gizmo_colors.spline_handle_free, '--color-spline-handle-free'); - + Canvas.pivot_marker.children.forEach(c => { // @ts-ignore c.updateColors(); - }) + }); } } static updateSettings() { let theme = CustomTheme.selected; let variables = {}; for (let key in CustomTheme.selected.colors) { - variables['--color-'+key] = CustomTheme.selected.colors[key]; + variables['--color-' + key] = CustomTheme.selected.colors[key]; } variables['--font-custom-main'] = `'${theme.main_font}'`; variables['--font-custom-headline'] = `'${theme.headline_font}'`; variables['--font-custom-code'] = `'${theme.code_font}'`; let variable_section = `body {\n`; for (let key in variables) { - variable_section += `\n\t${key}: ${variables[key]};` + variable_section += `\n\t${key}: ${variables[key]};`; } variable_section += '\n}\n'; - document.getElementById('theme_css').textContent = `@layer theme {${variable_section}${theme.css}};` + document.getElementById('theme_css').textContent = + `@layer theme {${variable_section}${theme.css}};`; document.body.classList.toggle('theme_borders', !!theme.borders); // Options for (let attribute of document.body.attributes) { - if (attribute.name.startsWith('theme-') && (!theme.options || theme.options[attribute.name.substring(6)] == undefined)) { + if ( + attribute.name.startsWith('theme-') && + (!theme.options || theme.options[attribute.name.substring(6)] == undefined) + ) { document.body.removeAttribute(attribute.name); } } - for (let key in (theme.options??{})) { + for (let key in theme.options ?? {}) { if (theme.option_values[key] == undefined) continue; - document.body.setAttribute('theme-'+key, theme.option_values[key]); + document.body.setAttribute('theme-' + key, theme.option_values[key]); } CustomTheme.loadThumbnailStyles(); CustomTheme.updateColors(); } static loadThumbnailStyles() { // @ts-ignore - let split_regex = (isApp || window.chrome) ? new RegExp('(? `[theme_id="${theme.id}"] ${e.trim()}`).join(", "); + const selector = (rule as CSSStyleRule).selectorText + .split(split_regex) + .map(e => `[theme_id="${theme.id}"] ${e.trim()}`) + .join(', '); thumbnailStyles += `${selector} { ${(rule as CSSStyleRule).style.cssText} }\n`; } } @@ -668,8 +697,11 @@ export class CustomTheme { const sheet = style.sheet; for (const rule of sheet.cssRules) { if (!(rule as CSSStyleRule).selectorText) continue; - - const selector = (rule as CSSStyleRule).selectorText.split(split_regex).map(e => `[theme_id="${CustomTheme.selected.id}"] ${e.trim()}`).join(", "); + + const selector = (rule as CSSStyleRule).selectorText + .split(split_regex) + .map(e => `[theme_id="${CustomTheme.selected.id}"] ${e.trim()}`) + .join(', '); thumbnailStyles += `${selector} { ${(rule as CSSStyleRule).style.cssText} }\n`; } } @@ -693,7 +725,7 @@ export class CustomTheme { theme.load(); } reloadThemeFile() { - let content = fs.readFileSync(this.path, {encoding: 'utf8'}); + let content = fs.readFileSync(this.path, { encoding: 'utf8' }); if (!content) return; let new_theme = new CustomTheme().parseBBTheme(content); delete new_theme.id; @@ -718,7 +750,10 @@ export class CustomTheme { if (isApp) { CustomTheme.sideloaded_themes.safePush(file.path); - localStorage.setItem('themes_sideloaded', JSON.stringify(CustomTheme.sideloaded_themes)); + localStorage.setItem( + 'themes_sideloaded', + JSON.stringify(CustomTheme.sideloaded_themes) + ); } } parseBBTheme(content: string, include_id?: boolean): this { @@ -726,14 +761,13 @@ export class CustomTheme { // Lecagy format let json_data = JSON.parse(content); this.extend(json_data); - } else { function extractSection(start_match: string, end_match: string): string | undefined { content = content.trim(); if (content.startsWith(start_match) == false) return; let end = content.indexOf(end_match); let section = content.substring(start_match.length, end); - content = content.substring(end+end_match.length); + content = content.substring(end + end_match.length); return section; } @@ -754,9 +788,15 @@ export class CustomTheme { let [key, value] = line.replace('--font-custom-', '').split(/:\s*/); value = value.replace(';', ''); switch (key) { - case 'main': this.main_font = value; break; - case 'headline': this.headline_font = value; break; - case 'code': this.code_font = value; break; + case 'main': + this.main_font = value; + break; + case 'headline': + this.headline_font = value; + break; + case 'code': + this.code_font = value; + break; } } } @@ -775,7 +815,7 @@ export class CustomTheme { author: this.author, version: this.version, borders: this.borders, - } + }; for (let key in metadata) { if (metadata[key] == undefined) continue; theme += `\n${key}: ${metadata[key].toString()}`; @@ -787,13 +827,13 @@ export class CustomTheme { let color_value = this.colors[color]; theme += `\n\t--color-${color}: ${color_value};`; } - if (this.main_font) theme += `\n\t--font-custom-main: ${this.main_font};`; + if (this.main_font) theme += `\n\t--font-custom-main: ${this.main_font};`; if (this.headline_font) theme += `\n\t--font-custom-headline: ${this.headline_font};`; - if (this.code_font) theme += `\n\t--font-custom-code: ${this.code_font};`; + if (this.code_font) theme += `\n\t--font-custom-code: ${this.code_font};`; theme += '\n}\n'; if (this.thumbnail) { - theme += '@scope (thumbnail) {\n' + theme += '@scope (thumbnail) {\n'; theme += this.thumbnail.replace(/\n/g, '\n\t'); theme += '\n}\n'; } @@ -805,7 +845,7 @@ export class CustomTheme { save() { localStorage.setItem('theme', JSON.stringify(this)); } -}; +} if (isApp && localStorage.getItem('themes_sideloaded')) { try { @@ -816,9 +856,12 @@ if (isApp && localStorage.getItem('themes_sideloaded')) { if (!fs.existsSync(path)) { CustomTheme.sideloaded_themes.remove(path); } - }) - localStorage.setItem('themes_sideloaded', JSON.stringify(CustomTheme.sideloaded_themes)); - Blockbench.read(CustomTheme.sideloaded_themes, {errorbox: false}, files => { + }); + localStorage.setItem( + 'themes_sideloaded', + JSON.stringify(CustomTheme.sideloaded_themes) + ); + Blockbench.read(CustomTheme.sideloaded_themes, { errorbox: false }, files => { files.forEach(file => { let id = file.name.replace(/\.\w+$/, ''); let theme = new CustomTheme().parseBBTheme(file.content as string); @@ -827,8 +870,8 @@ if (isApp && localStorage.getItem('themes_sideloaded')) { theme.source = 'file'; theme.path = file.path; CustomTheme.themes.push(theme); - }) - }) + }); + }); } } catch (err) {} @@ -839,7 +882,7 @@ export function loadThemes() { let stored_theme_data: ThemeData | undefined; try { if (localStorage.getItem('theme')) { - stored_theme_data = JSON.parse(localStorage.getItem('theme')) + stored_theme_data = JSON.parse(localStorage.getItem('theme')); } } catch (err) {} @@ -849,12 +892,19 @@ export function loadThemes() { // Check for updates if (stored_theme.source == 'repository' && stored_theme.id) { CustomTheme.loadTheme(stored_theme); - fetch(`https://cdn.jsdelivr.net/gh/JannisX11/blockbench-themes/themes/${stored_theme.id}.bbtheme`).then(async (result) => { + fetch( + `https://cdn.jsdelivr.net/gh/JannisX11/blockbench-themes/themes/${stored_theme.id}.bbtheme` + ).then(async result => { let text_content = await result.text(); if (!text_content) return; let theme = new CustomTheme().parseBBTheme(text_content); - if ((theme.version && !stored_theme.version) || (theme.version && stored_theme.version && compareVersions(theme.version, stored_theme.version))) { + if ( + (theme.version && !stored_theme.version) || + (theme.version && + stored_theme.version && + compareVersions(theme.version, stored_theme.version)) + ) { // Update theme stored_theme.extend(theme); stored_theme.source = 'repository'; @@ -862,41 +912,46 @@ export function loadThemes() { console.log(`Updated Theme "${stored_theme.id}" to v${theme.version}`); } }); - } else if ((stored_theme.source == 'built_in' || stored_theme.source == 'file') && stored_theme.id) { + } else if ( + (stored_theme.source == 'built_in' || stored_theme.source == 'file') && + stored_theme.id + ) { let match = CustomTheme.themes.find(t => t.id == stored_theme.id); if (match) { match.option_values = stored_theme.option_values; match.load(); } - } else if (stored_theme.source == 'custom') { CustomTheme.loadTheme(stored_theme); } } } -BARS.defineActions(function() { +BARS.defineActions(function () { new Action('theme_window', { name: tl('dialog.settings.theme') + '...', icon: 'style', category: 'blockbench', click: function () { CustomTheme.dialog.show(); - } - }) + }, + }); new Action('import_theme', { icon: 'folder', category: 'blockbench', click: function () { - Blockbench.import({ - resource_id: 'config', - extensions: ['bbtheme'], - type: 'Blockbench Theme' - }, function(files) { - CustomTheme.import(files[0]); - }) - } - }) + Blockbench.import( + { + resource_id: 'config', + extensions: ['bbtheme'], + type: 'Blockbench Theme', + }, + function (files) { + CustomTheme.import(files[0]); + } + ); + }, + }); new Action('export_theme', { icon: 'style', category: 'blockbench', @@ -908,13 +963,13 @@ BARS.defineActions(function() { type: 'Blockbench Theme', extensions: ['bbtheme'], name: id, - content - }) - } - }) - BarItems.import_theme.toElement('#layout_title_bar') - BarItems.export_theme.toElement('#layout_title_bar') -}) + content, + }); + }, + }); + BarItems.import_theme.toElement('#layout_title_bar'); + BarItems.export_theme.toElement('#layout_title_bar'); +}); Object.assign(window, { CustomTheme, diff --git a/js/io/format.ts b/js/io/format.ts index 2bcc4c4cc..02cceb967 100644 --- a/js/io/format.ts +++ b/js/io/format.ts @@ -1,28 +1,28 @@ -import { Vue } from "../lib/libs"; -import { Blockbench } from "../api"; -import { setProjectTitle } from "../interface/interface"; -import { settings, Settings } from "../interface/settings"; -import { TickUpdates } from "../misc"; -import { Mode, Modes } from "../modes"; -import { Group } from "../outliner/group"; -import { Canvas } from "../preview/canvas"; -import { DefaultCameraPresets } from "../preview/preview"; -import { Property } from "../util/property"; -import { SplineMesh } from "../outliner/spline_mesh"; +import { Vue } from '../lib/libs'; +import { Blockbench } from '../api'; +import { setProjectTitle } from '../interface/interface'; +import { settings, Settings } from '../interface/settings'; +import { TickUpdates } from '../misc'; +import { Mode, Modes } from '../modes'; +import { Group } from '../outliner/group'; +import { Canvas } from '../preview/canvas'; +import { DefaultCameraPresets } from '../preview/preview'; +import { Property } from '../util/property'; +import { SplineMesh } from '../outliner/spline_mesh'; interface FormatPage { - component?: Vue.Component + component?: Vue.Component; content?: ( | { - type?: 'image' | 'h2' | 'h3' | 'h4' | 'text' | 'label' | 'image' | '' - text?: string - source?: string - width?: number - height?: number + type?: 'image' | 'h2' | 'h3' | 'h4' | 'text' | 'label' | 'image' | ''; + text?: string; + source?: string; + width?: number; + height?: number; } | string - )[] - button_text?: string + )[]; + button_text?: string; } interface CubeSizeLimiter { /** @@ -31,11 +31,11 @@ interface CubeSizeLimiter { test: ( cube: Cube, values?: { from: ArrayVector3; to: ArrayVector3; inflate: number } - ) => boolean + ) => boolean; /** * Move the cube back into the restructions */ - move(cube: Cube, values?: { from: ArrayVector3; to: ArrayVector3; inflate: number }): void + move(cube: Cube, values?: { from: ArrayVector3; to: ArrayVector3; inflate: number }): void; /** * Clamp the cube to fit into the restrictions. When an axis and direction is provided, clamp the element on that side to prevent wandering. */ @@ -44,264 +44,262 @@ interface CubeSizeLimiter { values?: { from: ArrayVector3; to: ArrayVector3; inflate: number }, axis?: number, direction?: boolean | null - ) => void + ) => void; /** * Set to true to tell Blockbench to check and adjust the cube limit after rotating a cube */ - rotation_affected?: boolean + rotation_affected?: boolean; /** * Optionally set the coordinate limits of cubes in local space */ - coordinate_limits?: [number, number] + coordinate_limits?: [number, number]; } /** * The current format */ -declare const Format: ModelFormat +declare const Format: ModelFormat; export const Formats = {}; Object.defineProperty(window, 'Format', { get() { return Blockbench.Format; - } -}) + }, +}); //Formats interface FormatOptions { - id: string - icon: string - name?: string - description?: string - category?: string - target?: string | string[] - confidential?: boolean - condition?: ConditionResolvable - show_on_start_screen?: boolean - can_convert_to?: boolean - format_page?: FormatPage - onFormatPage?(): void - onStart?(): void - onSetup?(project: ModelProject, newModel?: boolean): void - convertTo?(): void - new?(): boolean + id: string; + icon: string; + name?: string; + description?: string; + category?: string; + target?: string | string[]; + confidential?: boolean; + condition?: ConditionResolvable; + show_on_start_screen?: boolean; + can_convert_to?: boolean; + format_page?: FormatPage; + onFormatPage?(): void; + onStart?(): void; + onSetup?(project: ModelProject, newModel?: boolean): void; + convertTo?(): void; + new?(): boolean; /** * Enables Box UV on cubes by default */ - box_uv: boolean + box_uv: boolean; /** * If true, box UV is optional and can be toggled on the project or per cube */ - optional_box_uv: boolean + optional_box_uv: boolean; /** * If true, only one texture can be assigned to the model at a time, instead of textures being assigned per face */ - single_texture: boolean + single_texture: boolean; /** * If true, a single texture is used as default, but textures can still be assigned to faces */ - single_texture_default: boolean + single_texture_default: boolean; /** * If true, textures can be assigned per group instead of per face */ - per_group_texture: boolean + per_group_texture: boolean; /** * If true, UV size (the size of the texture in UV space) will be defined per texture and not per project */ - per_texture_uv_size: boolean + per_texture_uv_size: boolean; /** * Enable a model identifier field in the project settings. Default is true */ - model_identifier: boolean + model_identifier: boolean; /** * If true, the file name of a project will be editable in the project settings */ - legacy_editable_file_name: boolean + legacy_editable_file_name: boolean; /** * If true, enables a field in the project settings to set a parent model ID */ - parent_model_id: boolean + parent_model_id: boolean; /** * Adds a toggle in the project settings to enable project wide vertex color ambient occlusion */ - vertex_color_ambient_occlusion: boolean + vertex_color_ambient_occlusion: boolean; /** * Enable flipbook animated textures */ - animated_textures: boolean + animated_textures: boolean; /** * Enable groups to work as bones and rig the model */ - bone_rig: boolean + bone_rig: boolean; /** * Enable armatures to rig meshes */ - armature_rig: boolean + armature_rig: boolean; /** * Align the grid center with the model origin, instead of the grid corner */ - centered_grid: boolean + centered_grid: boolean; /** * Add the ability to rotate cubes */ - rotate_cubes: boolean + rotate_cubes: boolean; /** * Add the ability to stretch cubes. Stretch scales cubes from the center without affecting UV */ - stretch_cubes: boolean + stretch_cubes: boolean; /** * If true, cube sizes are limited to integer values */ - integer_size: boolean + integer_size: boolean; /** * Enable mesh elements */ - meshes: boolean + meshes: boolean; /** * Enable spline elements */ - splines: boolean + splines: boolean; /** * Enable texture meshes */ - texture_meshes: boolean + texture_meshes: boolean; /** * Enable billboard elements */ - billboards: boolean + billboards: boolean; /** * Enable locators */ - locators: boolean + locators: boolean; /** * Enable PBR texture materials yay */ - pbr: boolean + pbr: boolean; /** * Enforces a rotation limit for cubes of up to 45 degrees in either direction and one axis at a time */ - rotation_limit: boolean + rotation_limit: boolean; /** * Forces cube rotations to snap to 22.5 degree increments */ - rotation_snap: boolean + rotation_snap: boolean; /** * Allows cube UVs to be rotated */ - uv_rotation: boolean + uv_rotation: boolean; /** * Enables Minecraft Java block/item model specific cube face features (tint and export) */ - java_face_properties: boolean + java_face_properties: boolean; /** * Allows assigning one texture to be used as a texture for particles related to the model */ - select_texture_for_particles: boolean + select_texture_for_particles: boolean; /** * Enable mcmeta files for animated texture files */ - texture_mcmeta: boolean + texture_mcmeta: boolean; /** * Enables an option to set an expression for bone bindings */ - bone_binding_expression: boolean + bone_binding_expression: boolean; /** * If true, animations will be saved into files */ - animation_files: boolean + animation_files: boolean; /** * Enables a folder path per texture that can be set in the texture properties window */ - texture_folder: boolean + texture_folder: boolean; /** * Enables the 2D image editor */ - image_editor: boolean + image_editor: boolean; /** * Enables edit mode. Default is true */ - edit_mode: boolean + edit_mode: boolean; /** * Enables paint mode. Default is true */ - paint_mode: boolean + paint_mode: boolean; /** * Enables display mode */ - display_mode: boolean + display_mode: boolean; /** * Emaböes animation mode */ - animation_mode: boolean + animation_mode: boolean; /** * Enables pose mode */ - pose_mode: boolean + pose_mode: boolean; /** * Enables animation controllers */ - animation_controllers: boolean + animation_controllers: boolean; /** * If true, cube sizes will not be floored to calculate UV sizes with box UV. This can result in UVs not aligning with pixel edges */ - box_uv_float_size: boolean + box_uv_float_size: boolean; /** * Enables properties for Minecraft Java block/item models related to block shading (shading option and light emission value) */ - java_cube_shading_properties: boolean + java_cube_shading_properties: boolean; /** * Enables cullfaces, the ability on faces in Minecraft block models to set a direction, that, if covered by another block, will cause the face to unrender */ - cullfaces: boolean + cullfaces: boolean; /** * A set of characters that is allowed in node names (names of elements and groups that can be referenced externally, this does not apply to cubes or meshes) */ - node_name_regex: string + node_name_regex: string; /** * Set the default render sides for textures */ - render_sides: 'front' | 'double' | 'back' | (() => 'front' | 'double' | 'back') + render_sides: 'front' | 'double' | 'back' | (() => 'front' | 'double' | 'back'); /** * Options to limit the size of cubes */ - cube_size_limiter?: CubeSizeLimiter + cube_size_limiter?: CubeSizeLimiter; - codec?: Codec - onActivation?(): void - onDeactivation?(): void + codec?: Codec; + onActivation?(): void; + onDeactivation?(): void; } export interface ModelFormat extends FormatOptions {} export class ModelFormat implements FormatOptions { - id: string - icon: string - name: string - description: string - category: string - target: string | string[] - confidential: boolean - condition: ConditionResolvable - show_on_start_screen: boolean - show_in_new_list: boolean - can_convert_to: boolean - plugin: string - format_page?: FormatPage - onFormatPage?(): void - onStart?(): void - onSetup?(project: ModelProject, newModel?: boolean): void - - - - cube_size_limiter?: CubeSizeLimiter - - codec?: Codec - onActivation?(): void - onDeactivation?(): void - - static properties: Record + id: string; + icon: string; + name: string; + description: string; + category: string; + target: string | string[]; + confidential: boolean; + condition: ConditionResolvable; + show_on_start_screen: boolean; + show_in_new_list: boolean; + can_convert_to: boolean; + plugin: string; + format_page?: FormatPage; + onFormatPage?(): void; + onStart?(): void; + onSetup?(project: ModelProject, newModel?: boolean): void; + + cube_size_limiter?: CubeSizeLimiter; + + codec?: Codec; + onActivation?(): void; + onDeactivation?(): void; + + static properties: Record; constructor(id: string, data: Partial) { if (typeof id == 'object') { @@ -310,9 +308,9 @@ export class ModelFormat implements FormatOptions { } Formats[id] = this; this.id = id; - this.name = data.name || tl('format.'+this.id); - this.description = data.description || tl('format.'+this.id+'.desc'); - if (this.description == 'format.'+this.id+'.desc') this.description = ''; + this.name = data.name || tl('format.' + this.id); + this.description = data.description || tl('format.' + this.id + '.desc'); + if (this.description == 'format.' + this.id + '.desc') this.description = ''; this.category = data.category || 'other'; this.target = data.target; this.show_on_start_screen = true; @@ -346,20 +344,20 @@ export class ModelFormat implements FormatOptions { ModelFormat.properties[id].merge(this, data); } if (this.format_page && this.format_page.component) { - Vue.component(`format_page_${this.id}`, this.format_page.component) + Vue.component(`format_page_${this.id}`, this.format_page.component); } - Blockbench.dispatchEvent('construct_format', {format: this}); + Blockbench.dispatchEvent('construct_format', { format: this }); } select() { if (Format && typeof Format.onDeactivation == 'function') { - Format.onDeactivation() + Format.onDeactivation(); } // @ts-ignore Incompatible internal and external types Blockbench.Format = Blockbench.Project.format = this; if (typeof this.onActivation == 'function') { - Format.onActivation() + Format.onActivation(); } - Canvas.buildGrid() + Canvas.buildGrid(); if (Format.centered_grid) { scene.position.set(0, 0, 0); Canvas.ground_plane.position.x = Canvas.ground_plane.position.z = 8; @@ -369,14 +367,14 @@ export class ModelFormat implements FormatOptions { } PreviewModel.getActiveModels().forEach(model => { model.update(); - }) + }); Settings.updateSettingsInProfiles(); Preview.all.forEach(preview => { if (preview.isOrtho && typeof preview.angle == 'number') { // @ts-ignore Incompatible internal and external types - preview.loadAnglePreset(DefaultCameraPresets[preview.angle+1] as AnglePreset) + preview.loadAnglePreset(DefaultCameraPresets[preview.angle + 1] as AnglePreset); } - }) + }); if (Mode.selected && !Condition(Mode.selected.condition)) { (this.pose_mode ? Modes.options.paint : Modes.options.edit).select(); } @@ -387,8 +385,8 @@ export class ModelFormat implements FormatOptions { if (Modes.vue) Modes.vue.$forceUpdate(); TickUpdates.interface = true; Canvas.updateShading(); - Canvas.updateRenderSides() - Blockbench.dispatchEvent('select_format', {format: this, project: Project}); + Canvas.updateRenderSides(); + Blockbench.dispatchEvent('select_format', { format: this, project: Project }); return this; } new(): boolean { @@ -400,7 +398,6 @@ export class ModelFormat implements FormatOptions { return false; } convertTo() { - Undo.history.empty(); Undo.index = 0; Project.export_path = ''; @@ -408,14 +405,14 @@ export class ModelFormat implements FormatOptions { var old_format = Blockbench.Format as ModelFormat; this.select(); - Modes.options.edit.select() + Modes.options.edit.select(); // Box UV if (!this.optional_box_uv) { Project.box_uv = this.box_uv; Cube.all.forEach(cube => { cube.setUVMode(this.box_uv); - }) + }); } if (!this.per_texture_uv_size && old_format.per_texture_uv_size) { @@ -429,27 +426,27 @@ export class ModelFormat implements FormatOptions { Texture.all.forEach(tex => { tex.uv_width = Project.texture_width; tex.uv_height = Project.texture_height; - }) + }); } //Bone Rig if (!this.bone_rig && old_format.bone_rig) { Group.all.forEach(group => { group.rotation.V3_set(0, 0, 0); - }) + }); } if (this.bone_rig && !old_format.bone_rig) { - var loose_stuff = [] + var loose_stuff = []; Outliner.root.forEach(el => { if (el instanceof Group == false) { - loose_stuff.push(el) + loose_stuff.push(el); } - }) + }); if (loose_stuff.length) { - var root_group = new Group().init().addTo() + var root_group = new Group().init().addTo(); loose_stuff.forEach(el => { - el.addTo(root_group) - }) + el.addTo(root_group); + }); } // @ts-ignore if (!Project.geometry_name && Project.name) { @@ -460,7 +457,7 @@ export class ModelFormat implements FormatOptions { if (this.bone_rig) { Group.all.forEach(group => { group.createUniqueName(); - }) + }); } if (this.centered_grid != old_format.centered_grid) { let offset = this.centered_grid ? -8 : 8; @@ -470,85 +467,93 @@ export class ModelFormat implements FormatOptions { cube.to[axis] += offset; cube.origin[axis] += offset; } - }) + }); Group.all.forEach(group => { group.origin[0] += offset; group.origin[2] += offset; - }) + }); } if (!this.single_texture && old_format.single_texture && Texture.all.length) { let texture = Texture.getDefault(); - Outliner.elements.filter((el: OutlinerElement) => 'applyTexture' in el).forEach(el => { - // @ts-ignore - el.applyTexture(texture, true) - }) + Outliner.elements + .filter((el: OutlinerElement) => 'applyTexture' in el) + .forEach(el => { + // @ts-ignore + el.applyTexture(texture, true); + }); } //Rotate Cubes if (!this.rotate_cubes && old_format.rotate_cubes) { Cube.all.forEach(cube => { - cube.rotation.V3_set(0, 0, 0) - }) + cube.rotation.V3_set(0, 0, 0); + }); } //Meshes if (!this.meshes && old_format.meshes) { Mesh.all.slice().forEach(mesh => { - mesh.remove() - }) + mesh.remove(); + }); } //Splines if (!this.splines && old_format.splines) { SplineMesh.all.slice().forEach(spline => { - spline.remove() - }) + spline.remove(); + }); } //Locators if (!this.locators && old_format.locators) { Locator.all.slice().forEach(locator => { - locator.remove() - }) + locator.remove(); + }); } //Texture Meshes if (!this.texture_meshes && old_format.texture_meshes) { TextureMesh.all.slice().forEach(tm => { - tm.remove() - }) + tm.remove(); + }); } //Billboards if (!this.billboards && old_format.billboards) { // @ts-ignore Billboard.all.slice().forEach(b => { - b.remove() - }) + b.remove(); + }); } //Canvas Limit - if (this.cube_size_limiter && !old_format.cube_size_limiter && !settings.deactivate_size_limit.value) { + if ( + this.cube_size_limiter && + !old_format.cube_size_limiter && + !settings.deactivate_size_limit.value + ) { Cube.all.forEach(cube => { this.cube_size_limiter.move(cube); - }) + }); Cube.all.forEach(cube => { this.cube_size_limiter.clamp(cube); - }) + }); } //Rotation Limit if (this.rotation_limit && !old_format.rotation_limit && this.rotate_cubes) { Cube.all.forEach(cube => { if (!cube.rotation.allEqual(0)) { - var axis = (getAxisNumber(cube.rotationAxis())) || 0; - var cube_rotation = this.rotation_snap ? Math.round(cube.rotation[axis]/22.5)*22.5 : cube.rotation[axis]; - var angle = limitNumber( cube_rotation, -45, 45 ); - cube.rotation.V3_set(0, 0, 0) + var axis = getAxisNumber(cube.rotationAxis()) || 0; + var cube_rotation = this.rotation_snap + ? Math.round(cube.rotation[axis] / 22.5) * 22.5 + : cube.rotation[axis]; + var angle = limitNumber(cube_rotation, -45, 45); + cube.rotation.V3_set(0, 0, 0); cube.rotation[axis] = angle; } - }) + }); } //Animation Mode @@ -559,22 +564,22 @@ export class ModelFormat implements FormatOptions { Project.saved = false; setProjectTitle(); - Blockbench.dispatchEvent('convert_format', {format: this, old_format}) + Blockbench.dispatchEvent('convert_format', { format: this, old_format }); if (typeof this.onSetup == 'function') { - this.onSetup(Project) + this.onSetup(Project); } - Canvas.updateAllPositions() - Canvas.updateAllBones() - Canvas.updateAllFaces() - updateSelection() + Canvas.updateAllPositions(); + Canvas.updateAllBones(); + Canvas.updateAllFaces(); + updateSelection(); } delete() { delete Formats[this.id]; // @ts-ignore if (this.codec && this.codec.format == this) delete this.codec.format; - Blockbench.dispatchEvent('delete_format', {format: this}); + Blockbench.dispatchEvent('delete_format', { format: this }); } } @@ -586,7 +591,7 @@ new Property(ModelFormat, 'boolean', 'single_texture'); new Property(ModelFormat, 'boolean', 'single_texture_default'); new Property(ModelFormat, 'boolean', 'per_group_texture'); new Property(ModelFormat, 'boolean', 'per_texture_uv_size'); -new Property(ModelFormat, 'boolean', 'model_identifier', {default: true}); +new Property(ModelFormat, 'boolean', 'model_identifier', { default: true }); new Property(ModelFormat, 'boolean', 'legacy_editable_file_name'); new Property(ModelFormat, 'boolean', 'parent_model_id'); new Property(ModelFormat, 'boolean', 'vertex_color_ambient_occlusion'); @@ -614,16 +619,15 @@ new Property(ModelFormat, 'boolean', 'bone_binding_expression'); new Property(ModelFormat, 'boolean', 'animation_files'); new Property(ModelFormat, 'boolean', 'animation_controllers'); new Property(ModelFormat, 'boolean', 'image_editor'); -new Property(ModelFormat, 'boolean', 'edit_mode', {default: true}); -new Property(ModelFormat, 'boolean', 'paint_mode', {default: true}); +new Property(ModelFormat, 'boolean', 'edit_mode', { default: true }); +new Property(ModelFormat, 'boolean', 'paint_mode', { default: true }); new Property(ModelFormat, 'boolean', 'pose_mode'); new Property(ModelFormat, 'boolean', 'display_mode'); new Property(ModelFormat, 'boolean', 'animation_mode'); new Property(ModelFormat, 'boolean', 'texture_folder'); new Property(ModelFormat, 'boolean', 'pbr'); - Object.assign(window, { ModelFormat, - Formats + Formats, }); diff --git a/js/io/formats/fbx.ts b/js/io/formats/fbx.ts index d7bf6505d..23199163a 100644 --- a/js/io/formats/fbx.ts +++ b/js/io/formats/fbx.ts @@ -1,30 +1,30 @@ -import { Blockbench } from "../../api"; -import { Filesystem } from "../../file_system"; -import { Armature } from "../../outliner/armature"; -import { adjustFromAndToForInflateAndStretch } from "../../outliner/cube"; -import { patchedAtob } from "../../util/util"; -import { JSZip, THREE } from './../../lib/libs' +import { Blockbench } from '../../api'; +import { Filesystem } from '../../file_system'; +import { Armature } from '../../outliner/armature'; +import { adjustFromAndToForInflateAndStretch } from '../../outliner/cube'; +import { patchedAtob } from '../../util/util'; +import { JSZip, THREE } from './../../lib/libs'; const _FBX_VERSION = 7300; -type TNumType = 'I'|'D'|'F'|'L'|'C'|'Y'; +type TNumType = 'I' | 'D' | 'F' | 'L' | 'C' | 'Y'; type TNumVal = { - type: TNumType, - value: number | BigInt, - isTNum: true, - toString: () => string -} + type: TNumType; + value: number | BigInt; + isTNum: true; + toString: () => string; +}; /** * Wraps a number to include the type - * @param {TNumType} type - * @param {number} value + * @param {TNumType} type + * @param {number} value */ function TNum(type: TNumType, value: number | BigInt): TNumVal { return { type, value, isTNum: true, - toString: () => 'key' + value.toString() + toString: () => 'key' + value.toString(), }; } @@ -33,15 +33,14 @@ function printAttributeList(list, type = 'i', key = 'a') { _values: [`_*${list.length}`], _type: type, [key]: list, - } + }; } type FBXNode = { - _key?: string - _values: (string|number|TNumVal)[] - [key: string]: any -} - + _key?: string; + _values: (string | number | TNumVal)[]; + [key: string]: any; +}; var codec = new Codec('fbx', { name: 'FBX Model', @@ -50,21 +49,26 @@ var codec = new Codec('fbx', { compile(options) { options = Object.assign(this.getExportOptions(), options); let scope = this; - let export_scale = (options.scale||16) / 100; + let export_scale = (options.scale || 16) / 100; let model = []; let armature_scale_const = new THREE.Vector3(1, 1, 1); - model.push([ - '; FBX 7.3.0 project file', - '; Created by the Blockbench FBX Exporter', - '; ----------------------------------------------------', - '; ', - '', - '', - ].join('\n')); + model.push( + [ + '; FBX 7.3.0 project file', + '; Created by the Blockbench FBX Exporter', + '; ----------------------------------------------------', + '; ', + '', + '', + ].join('\n') + ); function formatFBXComment(comment) { - return '\n; ' + comment.split(/\n/g).join('\n; ') - + '\n;------------------------------------------------------------------\n\n'; + return ( + '\n; ' + + comment.split(/\n/g).join('\n; ') + + '\n;------------------------------------------------------------------\n\n' + ); } let UUIDMap = {}; function getID(uuid: string | number): TNumVal { @@ -72,7 +76,7 @@ var codec = new Codec('fbx', { if (UUIDMap[uuid]) return UUIDMap[uuid]; let string_array = []; for (let i = 0; i < 8; i++) { - string_array.push(Math.floor(Math.random()*10)); + string_array.push(Math.floor(Math.random() * 10)); } string_array[0] = '7'; let s = string_array.join(''); @@ -111,72 +115,185 @@ var codec = new Codec('fbx', { CreationTimeStamp: { Version: 1000, Year: date.getFullYear(), - Month: date.getMonth()+1, + Month: date.getMonth() + 1, Day: date.getDate(), Hour: date.getHours(), Minute: date.getMinutes(), Second: date.getSeconds(), - Millisecond: date.getMilliseconds() + Millisecond: date.getMilliseconds(), }, Creator: 'Blockbench ' + Blockbench.version, SceneInfo: { - _values: ['SceneInfo::GlobalInfo', "UserData"], - Type: "UserData", + _values: ['SceneInfo::GlobalInfo', 'UserData'], + Type: 'UserData', Version: 100, - MetaData: { + MetaData: { Version: 100, - Title: "", - Subject: "", - Author: "", - Keywords: "", - Revision: "", - Comment: "" + Title: '', + Subject: '', + Author: '', + Keywords: '', + Revision: '', + Comment: '', + }, + Properties70: { + P01: { + _key: 'P', + _values: ['DocumentUrl', 'KString', 'Url', '', model_url], + }, + P02: { + _key: 'P', + _values: ['SrcDocumentUrl', 'KString', 'Url', '', model_url], + }, + P03: { _key: 'P', _values: ['Original', 'Compound', '', ''] }, + P04: { + _key: 'P', + _values: [ + 'Original|ApplicationVendor', + 'KString', + '', + '', + 'Blockbench', + ], + }, + P05: { + _key: 'P', + _values: [ + 'Original|ApplicationName', + 'KString', + '', + '', + 'Blockbench FBX Exporter', + ], + }, + P06: { + _key: 'P', + _values: [ + 'Original|ApplicationVersion', + 'KString', + '', + '', + Blockbench.version, + ], + }, + P07: { + _key: 'P', + _values: [ + 'Original|DateTime_GMT', + 'DateTime', + '', + '', + '01/01/1970 00:00:00.000', + ], + }, + P08: { + _key: 'P', + _values: ['Original|FileName', 'KString', '', '', '/foobar.fbx'], + }, + P09: { _key: 'P', _values: ['LastSaved', 'Compound', '', ''] }, + P10: { + _key: 'P', + _values: [ + 'LastSaved|ApplicationVendor', + 'KString', + '', + '', + 'Blockbench', + ], + }, + P11: { + _key: 'P', + _values: [ + 'LastSaved|ApplicationName', + 'KString', + '', + '', + 'Blockbench FBX Exporter', + ], + }, + P12: { + _key: 'P', + _values: [ + 'LastSaved|ApplicationVersion', + 'KString', + '', + '', + Blockbench.version, + ], + }, + P13: { + _key: 'P', + _values: [ + 'LastSaved|DateTime_GMT', + 'DateTime', + '', + '', + '01/01/1970 00:00:00.000', + ], + }, + P14: { + _key: 'P', + _values: ['Original|ApplicationNativeFile', 'KString', '', '', ''], + }, }, - Properties70: { - P01: {_key: 'P', _values: ["DocumentUrl", "KString", "Url", "", model_url]}, - P02: {_key: 'P', _values: ["SrcDocumentUrl", "KString", "Url", "", model_url]}, - P03: {_key: 'P', _values: ["Original", "Compound", "", ""]}, - P04: {_key: 'P', _values: ["Original|ApplicationVendor", "KString", "", "", "Blockbench"]}, - P05: {_key: 'P', _values: ["Original|ApplicationName", "KString", "", "", "Blockbench FBX Exporter"]}, - P06: {_key: 'P', _values: ["Original|ApplicationVersion", "KString", "", "", Blockbench.version]}, - P07: {_key: 'P', _values: ["Original|DateTime_GMT", "DateTime", "", "", "01/01/1970 00:00:00.000"]}, - P08: {_key: 'P', _values: ["Original|FileName", "KString", "", "", "/foobar.fbx"]}, - P09: {_key: 'P', _values: ["LastSaved", "Compound", "", ""]}, - P10: {_key: 'P', _values: ["LastSaved|ApplicationVendor", "KString", "", "", "Blockbench"]}, - P11: {_key: 'P', _values: ["LastSaved|ApplicationName", "KString", "", "", "Blockbench FBX Exporter"]}, - P12: {_key: 'P', _values: ["LastSaved|ApplicationVersion", "KString", "", "", Blockbench.version]}, - P13: {_key: 'P', _values: ["LastSaved|DateTime_GMT", "DateTime", "", "", "01/01/1970 00:00:00.000"]}, - P14: {_key: 'P', _values: ["Original|ApplicationNativeFile", "KString", "", "", ""]}, - } }, }, - FileId: "iVFoobar", + FileId: 'iVFoobar', CreationTime: dateString, Creator: Settings.get('credit'), - }) + }); model.push({ GlobalSettings: { Version: 1000, Properties70: { - P01: {_key: 'P', _values: ["UpAxis", "int", "Integer", "",1]}, - P02: {_key: 'P', _values: ["UpAxisSign", "int", "Integer", "",1]}, - P03: {_key: 'P', _values: ["FrontAxis", "int", "Integer", "",2]}, - P04: {_key: 'P', _values: ["FrontAxisSign", "int", "Integer", "",1]}, - P05: {_key: 'P', _values: ["CoordAxis", "int", "Integer", "",0]}, - P08: {_key: 'P', _values: ["CoordAxisSign", "int", "Integer", "",1]}, - P09: {_key: 'P', _values: ["OriginalUpAxis", "int", "Integer", "",-1]}, - P10: {_key: 'P', _values: ["OriginalUpAxisSign", "int", "Integer", "",1]}, - P11: {_key: 'P', _values: ["UnitScaleFactor", "double", "Number", "",TNum('D', 1)]}, - P12: {_key: 'P', _values: ["OriginalUnitScaleFactor", "double", "Number", "",TNum('D', 1)]}, - P13: {_key: 'P', _values: ["AmbientColor", "ColorRGB", "Color", "",TNum('D',0),TNum('D',0),TNum('D',0)]}, - P14: {_key: 'P', _values: ["DefaultCamera", "KString", "", "", "Producer Perspective"]}, - P15: {_key: 'P', _values: ["TimeMode", "enum", "", "",0]}, - P16: {_key: 'P', _values: ["TimeSpanStart", "KTime", "Time", "",TNum('L', 0)]}, - P17: {_key: 'P', _values: ["TimeSpanStop", "KTime", "Time", "",TNum('L', 46186158000)]}, - P18: {_key: 'P', _values: ["CustomFrameRate", "double", "Number", "",TNum('D', 24)]}, - } - } + P01: { _key: 'P', _values: ['UpAxis', 'int', 'Integer', '', 1] }, + P02: { _key: 'P', _values: ['UpAxisSign', 'int', 'Integer', '', 1] }, + P03: { _key: 'P', _values: ['FrontAxis', 'int', 'Integer', '', 2] }, + P04: { _key: 'P', _values: ['FrontAxisSign', 'int', 'Integer', '', 1] }, + P05: { _key: 'P', _values: ['CoordAxis', 'int', 'Integer', '', 0] }, + P08: { _key: 'P', _values: ['CoordAxisSign', 'int', 'Integer', '', 1] }, + P09: { _key: 'P', _values: ['OriginalUpAxis', 'int', 'Integer', '', -1] }, + P10: { _key: 'P', _values: ['OriginalUpAxisSign', 'int', 'Integer', '', 1] }, + P11: { + _key: 'P', + _values: ['UnitScaleFactor', 'double', 'Number', '', TNum('D', 1)], + }, + P12: { + _key: 'P', + _values: ['OriginalUnitScaleFactor', 'double', 'Number', '', TNum('D', 1)], + }, + P13: { + _key: 'P', + _values: [ + 'AmbientColor', + 'ColorRGB', + 'Color', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P14: { + _key: 'P', + _values: ['DefaultCamera', 'KString', '', '', 'Producer Perspective'], + }, + P15: { _key: 'P', _values: ['TimeMode', 'enum', '', '', 0] }, + P16: { + _key: 'P', + _values: ['TimeSpanStart', 'KTime', 'Time', '', TNum('L', 0)], + }, + P17: { + _key: 'P', + _values: ['TimeSpanStop', 'KTime', 'Time', '', TNum('L', 46186158000)], + }, + P18: { + _key: 'P', + _values: ['CustomFrameRate', 'double', 'Number', '', TNum('D', 24)], + }, + }, + }, }); // Documents Description @@ -187,24 +304,22 @@ var codec = new Codec('fbx', { Count: 1, Document: { _values: [TNum('L', uuid)], - Scene: "Scene", + Scene: 'Scene', Properties70: { - P01: {_key: 'P', _values: ["SourceObject", "object", "", ""]}, - P02: {_key: 'P', _values: ["ActiveAnimStackName", "KString", "", "", ""]} + P01: { _key: 'P', _values: ['SourceObject', 'object', '', ''] }, + P02: { _key: 'P', _values: ['ActiveAnimStackName', 'KString', '', '', ''] }, }, - RootNode: 0 - } + RootNode: 0, + }, }, - }) + }); // Document References model.push(formatFBXComment('Document References')); model.push({ - References: { - } + References: {}, }); - let DefinitionCounter = { node_attributes: 0, model: 0, @@ -221,14 +336,14 @@ var codec = new Codec('fbx', { }; let Objects: Record = {}; let Connections: { - name: string[], - id: (string|TNumVal)[] - property?: string + name: string[]; + id: (string | TNumVal)[]; + property?: string; }[] = []; let Takes = { - Current: '' + Current: '', }; - let root = {name: 'RootNode', uuid: 0}; + let root = { name: 'RootNode', uuid: 0 }; function getElementPos(element) { let arr = element.origin.slice(); @@ -240,30 +355,81 @@ var codec = new Codec('fbx', { function addNodeBase(node, fbx_type) { let unique_name = getUniqueName('object', node.uuid, node.name); let rotation_order = node.mesh.rotation.order == 'XYZ' ? 5 : 0; - Objects['key'+node.uuid] = { + Objects['key' + node.uuid] = { _key: 'Model', _values: [getID(node.uuid), `Model::${unique_name}`, fbx_type], Version: 232, Properties70: { - P1: {_key: 'P', _values: ["RotationActive", "bool", "", "",TNum('I', 1)]}, - P2: {_key: 'P', _values: ["InheritType", "enum", "", "",1]}, - P3: {_key: 'P', _values: ["ScalingMax", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P4: {_key: 'P', _values: ["Lcl Translation", "Lcl Translation", "", "A", ...getElementPos(node).map(v => TNum('D', v))]}, - P5: node.rotation ? {_key: 'P', _values: ["RotationPivot", "Vector3D", "Vector", "", TNum('D',0), TNum('D',0), TNum('D',0)]} : undefined, - P6: node.rotation ? {_key: 'P', _values: ["Lcl Rotation", "Lcl Rotation", "", "A", ...node.rotation.map(v => TNum('D', v))]} : undefined, - P7: node.rotation ? {_key: 'P', _values: ["RotationOrder", "enum", "", "", rotation_order]} : undefined, - P8: node.faces ? {_key: 'P', _values: ["DefaultAttributeIndex", "int", "Integer", "",0]} : undefined, + P1: { _key: 'P', _values: ['RotationActive', 'bool', '', '', TNum('I', 1)] }, + P2: { _key: 'P', _values: ['InheritType', 'enum', '', '', 1] }, + P3: { + _key: 'P', + _values: [ + 'ScalingMax', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P4: { + _key: 'P', + _values: [ + 'Lcl Translation', + 'Lcl Translation', + '', + 'A', + ...getElementPos(node).map(v => TNum('D', v)), + ], + }, + P5: node.rotation + ? { + _key: 'P', + _values: [ + 'RotationPivot', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + } + : undefined, + P6: node.rotation + ? { + _key: 'P', + _values: [ + 'Lcl Rotation', + 'Lcl Rotation', + '', + 'A', + ...node.rotation.map(v => TNum('D', v)), + ], + } + : undefined, + P7: node.rotation + ? { _key: 'P', _values: ['RotationOrder', 'enum', '', '', rotation_order] } + : undefined, + P8: node.faces + ? { _key: 'P', _values: ['DefaultAttributeIndex', 'int', 'Integer', '', 0] } + : undefined, }, Shading: true, - Culling: "CullingOff", + Culling: 'CullingOff', }; let parent = node.parent == 'root' ? root : node.parent; Connections.push({ - name: [`Model::${unique_name}`, `Model::${getUniqueName('object', parent.uuid, parent.name)}`], + name: [ + `Model::${unique_name}`, + `Model::${getUniqueName('object', parent.uuid, parent.name)}`, + ], id: [getID(node.uuid), getID(parent.uuid)], }); DefinitionCounter.model++; - return Objects['key'+node.uuid]; + return Objects['key' + node.uuid]; } // Groups @@ -292,7 +458,7 @@ var codec = new Codec('fbx', { let mesh_normals = mesh.calculateNormals(); function addPosition(x, y, z) { - positions.push(x/export_scale, y/export_scale, z/export_scale); + positions.push(x / export_scale, y / export_scale, z / export_scale); } for (let vkey in mesh.vertices) { @@ -312,18 +478,21 @@ var codec = new Codec('fbx', { textures.push(tex); vertices.forEach(vkey => { - uv.push(face.uv[vkey][0] / Project.getUVWidth(tex), 1 - face.uv[vkey][1] / Project.getUVHeight(tex)); - }) + uv.push( + face.uv[vkey][0] / Project.getUVWidth(tex), + 1 - face.uv[vkey][1] / Project.getUVHeight(tex) + ); + }); if (mesh.smooth_shading == false) { normals.push(...face.getNormal(true)); } - + vertices.forEach((vkey, vi) => { let index = vertex_keys.indexOf(vkey); - if (vi+1 == vertices.length) index = -1 -index; + if (vi + 1 == vertices.length) index = -1 - index; indices.push(index); - }) + }); } } @@ -331,7 +500,7 @@ var codec = new Codec('fbx', { let used_textures = Texture.all.filter(t => textures.includes(t)); - let geo_id = getID(mesh.uuid + '_geo') + let geo_id = getID(mesh.uuid + '_geo'); let geometry: FBXNode = { _key: 'Geometry', _values: [geo_id, `Geometry::${unique_name}`, 'Mesh'], @@ -339,95 +508,101 @@ var codec = new Codec('fbx', { Vertices: { _values: [`_*${positions.length}`], _type: 'd', - a: positions + a: positions, }, PolygonVertexIndex: { _values: [`_*${indices.length}`], _type: 'i', - a: indices + a: indices, }, GeometryVersion: 124, LayerElementNormal: { _values: [0], Version: 101, - Name: "", - MappingInformationType: mesh.smooth_shading ? "ByVertex" : "ByPolygon", - ReferenceInformationType: "Direct", + Name: '', + MappingInformationType: mesh.smooth_shading ? 'ByVertex' : 'ByPolygon', + ReferenceInformationType: 'Direct', Normals: { _values: [`_*${normals.length}`], _type: 'd', - a: normals - } + a: normals, + }, }, LayerElementUV: { _values: [0], Version: 101, - Name: "", - MappingInformationType: "ByPolygonVertex", - ReferenceInformationType: "Direct", + Name: '', + MappingInformationType: 'ByPolygonVertex', + ReferenceInformationType: 'Direct', UV: { _values: [`_*${uv.length}`], _type: 'd', - a: uv - } - }, - LayerElementMaterial: used_textures.length <= 1 ? { - _values: [0], - Version: 101, - Name: "", - MappingInformationType: "AllSame", - ReferenceInformationType: "IndexToDirect", - Materials: { - _values: [`_*1`], - _type: 'i', - a: 0 - }, - } : { - // Multitexture - _values: [0], - Version: 101, - Name: "", - MappingInformationType: "ByPolygon", - ReferenceInformationType: "IndexToDirect", - Materials: { - _values: [`_*${textures.length}`], - _type: 'i', - a: textures.map(t => used_textures.indexOf(t)) + a: uv, }, }, + LayerElementMaterial: + used_textures.length <= 1 + ? { + _values: [0], + Version: 101, + Name: '', + MappingInformationType: 'AllSame', + ReferenceInformationType: 'IndexToDirect', + Materials: { + _values: [`_*1`], + _type: 'i', + a: 0, + }, + } + : { + // Multitexture + _values: [0], + Version: 101, + Name: '', + MappingInformationType: 'ByPolygon', + ReferenceInformationType: 'IndexToDirect', + Materials: { + _values: [`_*${textures.length}`], + _type: 'i', + a: textures.map(t => used_textures.indexOf(t)), + }, + }, Layer: { _values: [0], Version: 100, LayerElement1: { _key: 'LayerElement', - Type: "LayerElementNormal", - TypedIndex: 0 + Type: 'LayerElementNormal', + TypedIndex: 0, }, LayerElement2: { _key: 'LayerElement', - Type: "LayerElementMaterial", - TypedIndex: 0 + Type: 'LayerElementMaterial', + TypedIndex: 0, }, LayerElement3: { _key: 'LayerElement', - Type: "LayerElementUV", - TypedIndex: 0 + Type: 'LayerElementUV', + TypedIndex: 0, }, - } + }, }; Objects[geo_id.toString()] = geometry; Connections.push({ name: [`Geometry::${unique_name}`, `Model::${unique_name}`], id: [geo_id, getID(mesh.uuid)], - }) + }); used_textures.forEach(tex => { Connections.push({ - name: [`Material::${getUniqueName('material', tex.uuid, tex.name)}`, `Model::${unique_name}`], - id: [getID(tex.uuid+'_m'), getID(mesh.uuid)], - }) - }) - }) + name: [ + `Material::${getUniqueName('material', tex.uuid, tex.name)}`, + `Model::${unique_name}`, + ], + id: [getID(tex.uuid + '_m'), getID(mesh.uuid)], + }); + }); + }); Armature.all.forEach(armature => { let mesh = Mesh.all.find(m => m.getArmature() == armature); let armature_name = getUniqueName('armature', armature.uuid, armature.name); @@ -437,37 +612,66 @@ var codec = new Codec('fbx', { let armature_attribute_id = getID(armature.uuid + '_attribute'); Objects[armature_attribute_id.toString()] = { _key: 'NodeAttribute', - _values: [armature_attribute_id, `NodeAttribute::${armature_name}`, 'Null'], - TypeFlags: "Null" + _values: [armature_attribute_id, `NodeAttribute::${armature_name}`, 'Null'], + TypeFlags: 'Null', }; DefinitionCounter.node_attributes++; let armature_id = getID(armature.uuid); Objects[armature_id.toString()] = { _key: 'Model', - _values: [armature_id, `Model::${armature_name}`, 'Null'], + _values: [armature_id, `Model::${armature_name}`, 'Null'], Version: 232, - Properties70: { - P1: {_key: 'P', _values: ["InheritType", "enum", "", "",1]}, - P2: {_key: 'P', _values: ["DefaultAttributeIndex", "int", "Integer", "",0]}, - P4: {_key: 'P', _values: ["Lcl Translation", "Lcl Translation", "", "A", ...[0, 0, 0].map(v => TNum('D', v))]}, - P5: {_key: 'P', _values: ["Lcl Rotation", "Lcl Rotation", "", "A", ...[0, 0, 0].map(v => TNum('D', v))]}, - P6: {_key: 'P', _values: ["Lcl Scaling", "Lcl Scaling", "", "A", ...[1, 1, 1].map(v => TNum('D', v))]}, + Properties70: { + P1: { _key: 'P', _values: ['InheritType', 'enum', '', '', 1] }, + P2: { _key: 'P', _values: ['DefaultAttributeIndex', 'int', 'Integer', '', 0] }, + P4: { + _key: 'P', + _values: [ + 'Lcl Translation', + 'Lcl Translation', + '', + 'A', + ...[0, 0, 0].map(v => TNum('D', v)), + ], + }, + P5: { + _key: 'P', + _values: [ + 'Lcl Rotation', + 'Lcl Rotation', + '', + 'A', + ...[0, 0, 0].map(v => TNum('D', v)), + ], + }, + P6: { + _key: 'P', + _values: [ + 'Lcl Scaling', + 'Lcl Scaling', + '', + 'A', + ...[1, 1, 1].map(v => TNum('D', v)), + ], + }, }, - Culling: "CullingOff" + Culling: 'CullingOff', }; let parent = armature.parent == 'root' ? root : armature.parent; Connections.push({ - name: [`Model::${armature_name}`, `Model::${getUniqueName('object', parent.uuid as string, parent.name)}`], + name: [ + `Model::${armature_name}`, + `Model::${getUniqueName('object', parent.uuid as string, parent.name)}`, + ], id: [getID(armature.uuid), getID(parent.uuid)], }); DefinitionCounter.model++; Connections.push({ - name: [`NodeAttribute::${armature_name}`, `Model::${armature_name}`], + name: [`NodeAttribute::${armature_name}`, `Model::${armature_name}`], id: [armature_attribute_id, armature_id], }); - // Bind pose let pose: FBXNode; @@ -475,21 +679,24 @@ var codec = new Codec('fbx', { let pose_id = getID(mesh.uuid + '_bind_pose'); pose = { _key: 'Pose', - _values: [pose_id, `Pose::${getUniqueName('object', mesh.uuid, mesh.name)}`, 'BindPose'], - Type: "BindPose", + _values: [ + pose_id, + `Pose::${getUniqueName('object', mesh.uuid, mesh.name)}`, + 'BindPose', + ], + Type: 'BindPose', Version: 100, NbPoseNodes: 0, - } + }; - // Mesh bind pose let matrix = new THREE.Matrix4(); matrix.scale(armature_scale_const); pose['PoseNode_object'] = { _key: 'PoseNode', Node: getID(mesh.uuid), - Matrix: printAttributeList(matrix.elements, 'd') - } + Matrix: printAttributeList(matrix.elements, 'd'), + }; pose.NbPoseNodes++; // Armature bind pose @@ -498,8 +705,8 @@ var codec = new Codec('fbx', { pose['PoseNode_object'] = { _key: 'PoseNode', Node: getID(armature.uuid), - Matrix: printAttributeList(matrix2.elements, 'd') - } + Matrix: printAttributeList(matrix2.elements, 'd'), + }; pose.NbPoseNodes++; Objects[pose_id.toString()] = pose; @@ -510,72 +717,130 @@ var codec = new Codec('fbx', { const bone_list = []; const bind_matrix_list = []; function processBone(bone) { - console.log(bone) + console.log(bone); bone_list.push(bone); let unique_name = getUniqueName('bone', bone.uuid, bone.name); let attribute_id = getID(bone.uuid + '_attribute'); Objects[attribute_id.toString()] = { _key: 'NodeAttribute', - _values: [attribute_id, `NodeAttribute::${unique_name}`, 'LimbNode'], - Properties70: { - P2: {_key: 'P', _values: ["Size", "double", "Number", "",TNum('D', bone.length / export_scale)]}, + _values: [attribute_id, `NodeAttribute::${unique_name}`, 'LimbNode'], + Properties70: { + P2: { + _key: 'P', + _values: [ + 'Size', + 'double', + 'Number', + '', + TNum('D', bone.length / export_scale), + ], + }, }, - TypeFlags: "Skeleton" + TypeFlags: 'Skeleton', }; DefinitionCounter.node_attributes++; - + let object_id = getID(bone.uuid); Objects[object_id.toString()] = { _key: 'Model', - _values: [object_id, `Model::${unique_name}`, 'LimbNode'], + _values: [object_id, `Model::${unique_name}`, 'LimbNode'], Version: 232, - Properties70: { - P1: {_key: 'P', _values: ["InheritType", "enum", "", "",1]}, - P2: {_key: 'P', _values: ["DefaultAttributeIndex", "int", "Integer", "",0]}, - P4: {_key: 'P', _values: ["Lcl Translation", "Lcl Translation", "", "A", ...getElementPos(bone).map(v => TNum('D', v))]}, - P5: {_key: 'P', _values: ["Lcl Rotation", "Lcl Rotation", "", "A", ...bone.rotation.map(v => TNum('D', v))]}, + Properties70: { + P1: { _key: 'P', _values: ['InheritType', 'enum', '', '', 1] }, + P2: { + _key: 'P', + _values: ['DefaultAttributeIndex', 'int', 'Integer', '', 0], + }, + P4: { + _key: 'P', + _values: [ + 'Lcl Translation', + 'Lcl Translation', + '', + 'A', + ...getElementPos(bone).map(v => TNum('D', v)), + ], + }, + P5: { + _key: 'P', + _values: [ + 'Lcl Rotation', + 'Lcl Rotation', + '', + 'A', + ...bone.rotation.map(v => TNum('D', v)), + ], + }, //P4: {_key: 'P', _values: ["RotationOrder", "enum", "", "", rotation_order]}, }, - Culling: "CullingOff" + Culling: 'CullingOff', }; let parent = bone.parent == 'root' ? root : bone.parent; Connections.push({ - name: [`Model::${unique_name}`, `Model::${getUniqueName('object', parent.uuid, parent.name)}`], + name: [ + `Model::${unique_name}`, + `Model::${getUniqueName('object', parent.uuid, parent.name)}`, + ], id: [getID(bone.uuid), getID(parent.uuid)], }); DefinitionCounter.model++; Connections.push({ - name: [`NodeAttribute::${unique_name}`, `Model::${unique_name}`], + name: [`NodeAttribute::${unique_name}`, `Model::${unique_name}`], id: [attribute_id, object_id], }); - - if (bone.children.length == 0) { // End bone node let attribute_id_end = getID(bone.uuid + '_end_attribute'); Objects[attribute_id_end.toString()] = { _key: 'NodeAttribute', - _values: [attribute_id_end, `NodeAttribute::${unique_name}_end`, 'LimbNode'], - Properties70: { - P2: {_key: 'P', _values: ["Size", "double", "Number", "",TNum('D', bone.length * 10)]}, + _values: [ + attribute_id_end, + `NodeAttribute::${unique_name}_end`, + 'LimbNode', + ], + Properties70: { + P2: { + _key: 'P', + _values: [ + 'Size', + 'double', + 'Number', + '', + TNum('D', bone.length * 10), + ], + }, }, - TypeFlags: "Skeleton" + TypeFlags: 'Skeleton', }; DefinitionCounter.node_attributes++; - - let object_id_end = getID(bone.uuid+'_end'); + + let object_id_end = getID(bone.uuid + '_end'); Objects[object_id_end.toString()] = { _key: 'Model', - _values: [object_id_end, `Model::${unique_name}_end`, 'LimbNode'], + _values: [object_id_end, `Model::${unique_name}_end`, 'LimbNode'], Version: 232, - Properties70: { - P1: {_key: 'P', _values: ["InheritType", "enum", "", "",1]}, - P2: {_key: 'P', _values: ["DefaultAttributeIndex", "int", "Integer", "",0]}, - P4: {_key: 'P', _values: ["Lcl Translation", "Lcl Translation", "", "A", 0, bone.length / export_scale, 0]}, + Properties70: { + P1: { _key: 'P', _values: ['InheritType', 'enum', '', '', 1] }, + P2: { + _key: 'P', + _values: ['DefaultAttributeIndex', 'int', 'Integer', '', 0], + }, + P4: { + _key: 'P', + _values: [ + 'Lcl Translation', + 'Lcl Translation', + '', + 'A', + 0, + bone.length / export_scale, + 0, + ], + }, }, - Culling: "CullingOff" + Culling: 'CullingOff', }; Connections.push({ name: [`Model::${unique_name}_end`, `Model::${unique_name}`], @@ -593,11 +858,11 @@ var codec = new Codec('fbx', { if (mesh) { let matrix = new THREE.Matrix4().copy(bone.mesh.inverse_bind_matrix).invert(); matrix.scale(armature_scale_const); - pose['PoseNode'+object_id] = { + pose['PoseNode' + object_id] = { _key: 'PoseNode', Node: object_id, - Matrix: printAttributeList(matrix.elements, 'd') - } + Matrix: printAttributeList(matrix.elements, 'd'), + }; pose.NbPoseNodes++; bind_matrix_list.push(matrix); } @@ -613,23 +878,26 @@ var codec = new Codec('fbx', { // Mesh deformers if (mesh) { - let deformer_id = getID(armature.uuid+'_deformer'); + let deformer_id = getID(armature.uuid + '_deformer'); Objects[deformer_id.toString()] = { _key: 'Deformer', _values: [deformer_id, `Deformer::${armature_name}`, 'Skin'], Version: 101, - Link_DeformAcuracy: 50 + Link_DeformAcuracy: 50, }; DefinitionCounter.deformer++; Connections.push({ - name: [`Deformer::${armature_name}`, `Geometry::${getUniqueName('object', mesh.uuid, mesh.name)}`], + name: [ + `Deformer::${armature_name}`, + `Geometry::${getUniqueName('object', mesh.uuid, mesh.name)}`, + ], id: [deformer_id, getID(mesh.uuid + '_geo')], }); let mesh_transform = new THREE.Matrix4().copy(mesh.mesh.matrixWorld); let vertex_keys = Object.keys(mesh.vertices); for (let bone of bone_list) { - let sub_deformer_id = getID(bone.uuid+'_deformer'); + let sub_deformer_id = getID(bone.uuid + '_deformer'); let bone_name = getUniqueName('bone', bone.uuid, bone.name); let indices = []; let weights = []; @@ -645,7 +913,7 @@ var codec = new Codec('fbx', { _key: 'Deformer', _values: [sub_deformer_id, `SubDeformer::${bone_name}`, 'Cluster'], Version: 100, - UserData: ["", ""], + UserData: ['', ''], Indexes: printAttributeList(indices, 'i'), Weights: printAttributeList(weights, 'd'), Transform: printAttributeList(mesh_transform.elements, 'd'), @@ -658,12 +926,15 @@ var codec = new Codec('fbx', { id: [sub_deformer_id, deformer_id], }); Connections.push({ - name: [`Model::${getUniqueName('bone', bone.uuid, bone.name)}`, `SubDeformer::${bone_name}`], + name: [ + `Model::${getUniqueName('bone', bone.uuid, bone.name)}`, + `SubDeformer::${bone_name}`, + ], id: [getID(bone.uuid), sub_deformer_id], }); } } - }) + }); // Cubes const cube_face_normals = { @@ -673,7 +944,7 @@ var codec = new Codec('fbx', { west: [-1, 0, 0], up: [0, 1, 0], down: [0, -1, 0], - } + }; Cube.all.forEach(cube => { if (!cube.export) return; addNodeBase(cube, 'Mesh'); @@ -697,14 +968,14 @@ var codec = new Codec('fbx', { var adjustedTo = cube.to.slice(); adjustFromAndToForInflateAndStretch(adjustedFrom, adjustedTo, cube); - addPosition(adjustedTo[0], adjustedTo[1], adjustedTo[2] ); - addPosition(adjustedTo[0], adjustedTo[1], adjustedFrom[2]); - addPosition(adjustedTo[0], adjustedFrom[1], adjustedTo[2] ); - addPosition(adjustedTo[0], adjustedFrom[1], adjustedFrom[2]); - addPosition(adjustedFrom[0], adjustedTo[1], adjustedFrom[2]); - addPosition(adjustedFrom[0], adjustedTo[1], adjustedTo[2] ); + addPosition(adjustedTo[0], adjustedTo[1], adjustedTo[2]); + addPosition(adjustedTo[0], adjustedTo[1], adjustedFrom[2]); + addPosition(adjustedTo[0], adjustedFrom[1], adjustedTo[2]); + addPosition(adjustedTo[0], adjustedFrom[1], adjustedFrom[2]); + addPosition(adjustedFrom[0], adjustedTo[1], adjustedFrom[2]); + addPosition(adjustedFrom[0], adjustedTo[1], adjustedTo[2]); addPosition(adjustedFrom[0], adjustedFrom[1], adjustedFrom[2]); - addPosition(adjustedFrom[0], adjustedFrom[1], adjustedTo[2] ); + addPosition(adjustedFrom[0], adjustedFrom[1], adjustedTo[2]); let textures = []; @@ -716,10 +987,22 @@ var codec = new Codec('fbx', { normals.push(...cube_face_normals[fkey]); let uv_outputs = [ - [face.uv[0] / Project.getUVWidth(texture), 1 - face.uv[3] / Project.getUVHeight(texture)], - [face.uv[2] / Project.getUVWidth(texture), 1 - face.uv[3] / Project.getUVHeight(texture)], - [face.uv[2] / Project.getUVWidth(texture), 1 - face.uv[1] / Project.getUVHeight(texture)], - [face.uv[0] / Project.getUVWidth(texture), 1 - face.uv[1] / Project.getUVHeight(texture)], + [ + face.uv[0] / Project.getUVWidth(texture), + 1 - face.uv[3] / Project.getUVHeight(texture), + ], + [ + face.uv[2] / Project.getUVWidth(texture), + 1 - face.uv[3] / Project.getUVHeight(texture), + ], + [ + face.uv[2] / Project.getUVWidth(texture), + 1 - face.uv[1] / Project.getUVHeight(texture), + ], + [ + face.uv[0] / Project.getUVWidth(texture), + 1 - face.uv[1] / Project.getUVHeight(texture), + ], ]; var rot = face.rotation || 0; while (rot > 0) { @@ -728,16 +1011,28 @@ var codec = new Codec('fbx', { } uv_outputs.forEach(coord => { uv.push(...coord); - }) + }); let vertices; switch (fkey) { - case 'north': vertices = [3, 6, 4, -1-1]; break; - case 'east': vertices = [2, 3, 1, -1-0]; break; - case 'south': vertices = [7, 2, 0, -1-5]; break; - case 'west': vertices = [6, 7, 5, -1-4]; break; - case 'up': vertices = [5, 0, 1, -1-4]; break; - case 'down': vertices = [6, 3, 2, -1-7]; break; + case 'north': + vertices = [3, 6, 4, -1 - 1]; + break; + case 'east': + vertices = [2, 3, 1, -1 - 0]; + break; + case 'south': + vertices = [7, 2, 0, -1 - 5]; + break; + case 'west': + vertices = [6, 7, 5, -1 - 4]; + break; + case 'up': + vertices = [5, 0, 1, -1 - 4]; + break; + case 'down': + vertices = [6, 3, 2, -1 - 7]; + break; } indices.push(...vertices); } @@ -746,7 +1041,7 @@ var codec = new Codec('fbx', { let used_textures = Texture.all.filter(t => textures.includes(t)); - let geo_id = getID(cube.uuid + '_geo') + let geo_id = getID(cube.uuid + '_geo'); let geometry = { _key: 'Geometry', _values: [geo_id, `Geometry::${unique_name}`, 'Mesh'], @@ -754,95 +1049,101 @@ var codec = new Codec('fbx', { Vertices: { _values: [`_*${positions.length}`], _type: 'd', - a: positions + a: positions, }, PolygonVertexIndex: { _values: [`_*${indices.length}`], _type: 'i', - a: indices + a: indices, }, GeometryVersion: 124, LayerElementNormal: { _values: [0], Version: 101, - Name: "", - MappingInformationType: "ByPolygon", - ReferenceInformationType: "Direct", + Name: '', + MappingInformationType: 'ByPolygon', + ReferenceInformationType: 'Direct', Normals: { _values: [`_*${normals.length}`], _type: 'd', - a: normals - } + a: normals, + }, }, LayerElementUV: { _values: [0], Version: 101, - Name: "", - MappingInformationType: "ByPolygonVertex", - ReferenceInformationType: "Direct", + Name: '', + MappingInformationType: 'ByPolygonVertex', + ReferenceInformationType: 'Direct', UV: { _values: [`_*${uv.length}`], _type: 'd', - a: uv - } - }, - LayerElementMaterial: used_textures.length <= 1 ? { - _values: [0], - Version: 101, - Name: "", - MappingInformationType: "AllSame", - ReferenceInformationType: "IndexToDirect", - Materials: { - _values: [`_*1`], - _type: 'i', - a: 0 - }, - } : { - // Multitexture - _values: [0], - Version: 101, - Name: "", - MappingInformationType: "ByPolygon", - ReferenceInformationType: "IndexToDirect", - Materials: { - _values: [`_*${textures.length}`], - _type: 'i', - a: textures.map(t => used_textures.indexOf(t)) + a: uv, }, }, + LayerElementMaterial: + used_textures.length <= 1 + ? { + _values: [0], + Version: 101, + Name: '', + MappingInformationType: 'AllSame', + ReferenceInformationType: 'IndexToDirect', + Materials: { + _values: [`_*1`], + _type: 'i', + a: 0, + }, + } + : { + // Multitexture + _values: [0], + Version: 101, + Name: '', + MappingInformationType: 'ByPolygon', + ReferenceInformationType: 'IndexToDirect', + Materials: { + _values: [`_*${textures.length}`], + _type: 'i', + a: textures.map(t => used_textures.indexOf(t)), + }, + }, Layer: { _values: [0], Version: 100, LayerElement1: { _key: 'LayerElement', - Type: "LayerElementNormal", - TypedIndex: 0 + Type: 'LayerElementNormal', + TypedIndex: 0, }, LayerElement2: { _key: 'LayerElement', - Type: "LayerElementMaterial", - TypedIndex: 0 + Type: 'LayerElementMaterial', + TypedIndex: 0, }, LayerElement3: { _key: 'LayerElement', - Type: "LayerElementUV", - TypedIndex: 0 + Type: 'LayerElementUV', + TypedIndex: 0, }, - } + }, }; - Objects['key'+geo_id] = geometry; + Objects['key' + geo_id] = geometry; Connections.push({ name: [`Geometry::${unique_name}`, `Model::${unique_name}`], id: [geo_id, getID(cube.uuid)], - }) + }); used_textures.forEach(tex => { Connections.push({ - name: [`Material::${getUniqueName('texture', tex.uuid, tex.name)}`, `Model::${unique_name}`], - id: [getID(tex.uuid+'_m'), getID(cube.uuid)], - }) - }) - }) + name: [ + `Material::${getUniqueName('texture', tex.uuid, tex.name)}`, + `Model::${unique_name}`, + ], + id: [getID(tex.uuid + '_m'), getID(cube.uuid)], + }); + }); + }); // Textures Texture.all.forEach(tex => { @@ -867,57 +1168,90 @@ var codec = new Codec('fbx', { let mat_object = { _key: 'Material', - _values: [getID(tex.uuid+'_m'), `Material::${unique_name}`, ''], + _values: [getID(tex.uuid + '_m'), `Material::${unique_name}`, ''], Version: 102, - ShadingModel: "lambert", + ShadingModel: 'lambert', MultiLayer: 0, - Properties70: { - P2: {_key: 'P', _values: ["Emissive", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P3: {_key: 'P', _values: ["Ambient", "Vector3D", "Vector", "",TNum('D',0.2), TNum('D',0.2), TNum('D',0.2)]}, - P4: {_key: 'P', _values: ["Diffuse", "Vector3D", "Vector", "",TNum('D',0.8), TNum('D',0.8), TNum('D',0.8)]}, - P5: {_key: 'P', _values: ["Opacity", "double", "Number", "",TNum('D', 1)]}, - } + Properties70: { + P2: { + _key: 'P', + _values: [ + 'Emissive', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P3: { + _key: 'P', + _values: [ + 'Ambient', + 'Vector3D', + 'Vector', + '', + TNum('D', 0.2), + TNum('D', 0.2), + TNum('D', 0.2), + ], + }, + P4: { + _key: 'P', + _values: [ + 'Diffuse', + 'Vector3D', + 'Vector', + '', + TNum('D', 0.8), + TNum('D', 0.8), + TNum('D', 0.8), + ], + }, + P5: { _key: 'P', _values: ['Opacity', 'double', 'Number', '', TNum('D', 1)] }, + }, }; let tex_object = { _key: 'Texture', - _values: [getID(tex.uuid+'_t'), `Texture::${unique_name}`, ''], - Type: "TextureVideoClip", + _values: [getID(tex.uuid + '_t'), `Texture::${unique_name}`, ''], + Type: 'TextureVideoClip', Version: 202, TextureName: `Texture::${unique_name}`, Media: `Video::${unique_name}`, FileName: fileName, RelativeFilename: relativeName, - ModelUVTranslation: [TNum('D',0),TNum('D',0)], - ModelUVScaling: [TNum('D',1),TNum('D',1)], - Texture_Alpha_Source: "None", - Cropping: [0,0,0,0], + ModelUVTranslation: [TNum('D', 0), TNum('D', 0)], + ModelUVScaling: [TNum('D', 1), TNum('D', 1)], + Texture_Alpha_Source: 'None', + Cropping: [0, 0, 0, 0], }; let image_object = { _key: 'Video', - _values: [getID(tex.uuid+'_i'), `Video::${unique_name}`, 'Clip'], - Type: "Clip", - Properties70: { - P: ["Path", "KString", "XRefUrl", "", tex.path || tex.name] + _values: [getID(tex.uuid + '_i'), `Video::${unique_name}`, 'Clip'], + Type: 'Clip', + Properties70: { + P: ['Path', 'KString', 'XRefUrl', '', tex.path || tex.name], }, UseMipMap: 0, Filename: fileName, RelativeFilename: relativeName, - Content: fileContent + Content: fileContent, }; - Objects['key'+tex.uuid+'_m'] = mat_object; - Objects['key'+tex.uuid+'_t'] = tex_object; - Objects['key'+tex.uuid+'_i'] = image_object; + Objects['key' + tex.uuid + '_m'] = mat_object; + Objects['key' + tex.uuid + '_t'] = tex_object; + Objects['key' + tex.uuid + '_i'] = image_object; Connections.push({ - name: [`Texture::${unique_name}`, `Material::${unique_name}`], - id: [getID(tex.uuid+'_t'), getID(tex.uuid+'_m')], - property: "DiffuseColor" + name: [`Texture::${unique_name}`, `Material::${unique_name}`], + id: [getID(tex.uuid + '_t'), getID(tex.uuid + '_m')], + property: 'DiffuseColor', }); Connections.push({ - name: [`Video::${unique_name}`, `Texture::${unique_name}`], - id: [getID(tex.uuid+'_i'), getID(tex.uuid+'_t')], + name: [`Video::${unique_name}`, `Texture::${unique_name}`], + id: [getID(tex.uuid + '_i'), getID(tex.uuid + '_t')], }); - }) + }); // Animations if (options.include_animations) { @@ -927,8 +1261,8 @@ var codec = new Codec('fbx', { DefinitionCounter.animation_stack++; DefinitionCounter.animation_layer++; - let stack_id = getID(clip.uuid+'_s'); - let layer_id = getID(clip.uuid+'_l'); + let stack_id = getID(clip.uuid + '_s'); + let layer_id = getID(clip.uuid + '_l'); let unique_name = getUniqueName('animation', clip.uuid, clip.name); let fbx_duration = Math.round(clip.duration * time_factor); @@ -936,17 +1270,29 @@ var codec = new Codec('fbx', { _key: 'AnimationStack', _values: [stack_id, `AnimStack::${unique_name}`, ''], Properties70: { - p1: {_key: 'P', _values: ['LocalStop', 'KTime', 'Time', '', TNum('L', fbx_duration)]}, - p2: {_key: 'P', _values: ['ReferenceStop', 'KTime', 'Time', '', TNum('L', fbx_duration)]}, - } + p1: { + _key: 'P', + _values: ['LocalStop', 'KTime', 'Time', '', TNum('L', fbx_duration)], + }, + p2: { + _key: 'P', + _values: [ + 'ReferenceStop', + 'KTime', + 'Time', + '', + TNum('L', fbx_duration), + ], + }, + }, }; let layer = { _key: 'AnimationLayer', _values: [layer_id, `AnimLayer::${unique_name}`, ''], - _force_compound: true + _force_compound: true, }; - Objects['key'+clip.uuid+'_s'] = stack; - Objects['key'+clip.uuid+'_l'] = layer; + Objects['key' + clip.uuid + '_s'] = stack; + Objects['key' + clip.uuid + '_l'] = layer; Connections.push({ name: [`AnimLayer::${unique_name}`, `AnimStack::${unique_name}`], id: [layer_id, stack_id], @@ -955,42 +1301,48 @@ var codec = new Codec('fbx', { clip.tracks.forEach(track => { // Track = CurveNode DefinitionCounter.animation_curve_node++; - let track_id = getID(clip.uuid + '.' + track.name) + let track_id = getID(clip.uuid + '.' + track.name); let track_name = `AnimCurveNode::${unique_name}.${track.channel[0].toUpperCase()}`; let curve_node = { _key: 'AnimationCurveNode', _values: [track_id, track_name, ''], Properties70: { - p1: {_key: 'P', _values: [`d|X`, 'Number', '', 'A', TNum('D',1)]}, - p2: {_key: 'P', _values: [`d|Y`, 'Number', '', 'A', TNum('D',1)]}, - p3: {_key: 'P', _values: [`d|Z`, 'Number', '', 'A', TNum('D',1)]}, - } + p1: { _key: 'P', _values: [`d|X`, 'Number', '', 'A', TNum('D', 1)] }, + p2: { _key: 'P', _values: [`d|Y`, 'Number', '', 'A', TNum('D', 1)] }, + p3: { _key: 'P', _values: [`d|Z`, 'Number', '', 'A', TNum('D', 1)] }, + }, }; let timecodes = track.times.map(second => Math.round(second * time_factor)); - Objects['key'+clip.uuid + '.' + track.name] = curve_node; + Objects['key' + clip.uuid + '.' + track.name] = curve_node; // Connect to bone Connections.push({ - name: [track_name, `Model::${getUniqueName('object', track.group_uuid, track.name)}`], + name: [ + track_name, + `Model::${getUniqueName('object', track.group_uuid, track.name)}`, + ], id: [track_id, getID(track.group_uuid)], - property: track.channel == 'position' ? "Lcl Translation" : (track.channel == 'rotation' ? "Lcl Rotation" : "Lcl Scaling") + property: + track.channel == 'position' + ? 'Lcl Translation' + : track.channel == 'rotation' + ? 'Lcl Rotation' + : 'Lcl Scaling', }); // Connect to layer Connections.push({ name: [track_name, `AnimLayer::${unique_name}`], id: [track_id, layer_id], }); - - ['X', 'Y', 'Z'].forEach((axis_letter, axis_number) => { DefinitionCounter.animation_curve++; let curve_id = getID(clip.uuid + '.' + track.name + '.' + axis_letter); let curve_name = `AnimCurve::${unique_name}.${track.channel[0].toUpperCase()}${axis_letter}`; - let values = track.values.filter((val, i) => (i % 3) == axis_number); + let values = track.values.filter((val, i) => i % 3 == axis_number); if (track.channel == 'rotation') { - values.forEach((v, i) => values[i] = Math.radToDeg(v)); + values.forEach((v, i) => (values[i] = Math.radToDeg(v))); } let curve = { _key: 'AnimationCurve', @@ -1000,39 +1352,39 @@ var codec = new Codec('fbx', { KeyTime: { _values: [`_*${timecodes.length}`], _type: 'd', - a: timecodes + a: timecodes, }, KeyValueFloat: { _values: [`_*${values.length}`], _type: 'f', - a: values + a: values, }, KeyAttrFlags: { _values: [`_*${1}`], _type: 'i', - a: [24836] + a: [24836], }, KeyAttrDataFloat: { _values: [`_*${4}`], _type: 'f', - a: [0,0,255790911,0] + a: [0, 0, 255790911, 0], }, KeyAttrRefCount: { _values: [`_*${1}`], _type: 'i', - a: [timecodes.length] + a: [timecodes.length], }, }; - Objects['key'+clip.uuid + '.' + track.name + axis_letter] = curve; + Objects['key' + clip.uuid + '.' + track.name + axis_letter] = curve; // Connect to track Connections.push({ name: [curve_name, track_name], id: [curve_id, track_id], - property: `d|${axis_letter}` + property: `d|${axis_letter}`, }); }); - }) + }); Takes[clip.uuid] = { _key: 'Take', @@ -1041,7 +1393,7 @@ var codec = new Codec('fbx', { LocalTime: [0, fbx_duration], ReferenceTime: [0, fbx_duration], }; - }) + }); } // Object definitions @@ -1057,298 +1409,1217 @@ var codec = new Codec('fbx', { global_settings: { _key: 'ObjectType', _values: ['GlobalSettings'], - Count: 1 + Count: 1, }, - node_attribute: DefinitionCounter.node_attributes ? { - _key: 'ObjectType', - _values: ['NodeAttribute'], - Count: DefinitionCounter.node_attributes, - PropertyTemplate: { - _values: ['FbxNull'], - Properties70: { - P1: {_key: 'P', _values: ["Color", "ColorRGB", "Color", "",TNum('D',0.8),TNum('D',0.8),TNum('D',0.8)]}, - P2: {_key: 'P', _values: ["Size", "double", "Number", "",TNum('D', 100)]}, - P3: {_key: 'P', _values: ["Look", "enum", "", "",1]}, + node_attribute: DefinitionCounter.node_attributes + ? { + _key: 'ObjectType', + _values: ['NodeAttribute'], + Count: DefinitionCounter.node_attributes, + PropertyTemplate: { + _values: ['FbxNull'], + Properties70: { + P1: { + _key: 'P', + _values: [ + 'Color', + 'ColorRGB', + 'Color', + '', + TNum('D', 0.8), + TNum('D', 0.8), + TNum('D', 0.8), + ], + }, + P2: { + _key: 'P', + _values: ['Size', 'double', 'Number', '', TNum('D', 100)], + }, + P3: { _key: 'P', _values: ['Look', 'enum', '', '', 1] }, + }, + }, } - } - } : undefined, - model: DefinitionCounter.model ? { - _key: 'ObjectType', - _values: ['Model'], - Count: DefinitionCounter.model, - PropertyTemplate: { - _values: ['FbxNode'], - Properties70: { - P01: {_key: 'P', _values: ["QuaternionInterpolate", "enum", "", "",0]}, - P02: {_key: 'P', _values: ["RotationOffset", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P03: {_key: 'P', _values: ["RotationPivot", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P04: {_key: 'P', _values: ["ScalingOffset", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P05: {_key: 'P', _values: ["ScalingPivot", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P06: {_key: 'P', _values: ["TranslationActive", "bool", "", "",TNum('I', 0)]}, - P07: {_key: 'P', _values: ["TranslationMin", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P08: {_key: 'P', _values: ["TranslationMax", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P09: {_key: 'P', _values: ["TranslationMinX", "bool", "", "",TNum('I', 0)]}, - P10: {_key: 'P', _values: ["TranslationMinY", "bool", "", "",TNum('I', 0)]}, - P11: {_key: 'P', _values: ["TranslationMinZ", "bool", "", "",TNum('I', 0)]}, - P12: {_key: 'P', _values: ["TranslationMaxX", "bool", "", "",TNum('I', 0)]}, - P13: {_key: 'P', _values: ["TranslationMaxY", "bool", "", "",TNum('I', 0)]}, - P14: {_key: 'P', _values: ["TranslationMaxZ", "bool", "", "",TNum('I', 0)]}, - P15: {_key: 'P', _values: ["RotationOrder", "enum", "", "",5]}, - P16: {_key: 'P', _values: ["RotationSpaceForLimitOnly", "bool", "", "",TNum('I', 0)]}, - P17: {_key: 'P', _values: ["RotationStiffnessX", "double", "Number", "",TNum('D', 0)]}, - P18: {_key: 'P', _values: ["RotationStiffnessY", "double", "Number", "",TNum('D', 0)]}, - P19: {_key: 'P', _values: ["RotationStiffnessZ", "double", "Number", "",TNum('D', 0)]}, - P20: {_key: 'P', _values: ["AxisLen", "double", "Number", "",TNum('D', 10)]}, - P21: {_key: 'P', _values: ["PreRotation", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P22: {_key: 'P', _values: ["PostRotation", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P23: {_key: 'P', _values: ["RotationActive", "bool", "", "",TNum('I', 0)]}, - P24: {_key: 'P', _values: ["RotationMin", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P25: {_key: 'P', _values: ["RotationMax", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P26: {_key: 'P', _values: ["RotationMinX", "bool", "", "",TNum('I', 0)]}, - P27: {_key: 'P', _values: ["RotationMinY", "bool", "", "",TNum('I', 0)]}, - P28: {_key: 'P', _values: ["RotationMinZ", "bool", "", "",TNum('I', 0)]}, - P29: {_key: 'P', _values: ["RotationMaxX", "bool", "", "",TNum('I', 0)]}, - P30: {_key: 'P', _values: ["RotationMaxY", "bool", "", "",TNum('I', 0)]}, - P31: {_key: 'P', _values: ["RotationMaxZ", "bool", "", "",TNum('I', 0)]}, - P32: {_key: 'P', _values: ["InheritType", "enum", "", "",0]}, - P33: {_key: 'P', _values: ["ScalingActive", "bool", "", "",TNum('I', 0)]}, - P34: {_key: 'P', _values: ["ScalingMin", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P35: {_key: 'P', _values: ["ScalingMax", "Vector3D", "Vector", "",TNum('D',1), TNum('D',1), TNum('D',1)]}, - P36: {_key: 'P', _values: ["ScalingMinX", "bool", "", "",TNum('I', 0)]}, - P37: {_key: 'P', _values: ["ScalingMinY", "bool", "", "",TNum('I', 0)]}, - P38: {_key: 'P', _values: ["ScalingMinZ", "bool", "", "",TNum('I', 0)]}, - P39: {_key: 'P', _values: ["ScalingMaxX", "bool", "", "",TNum('I', 0)]}, - P40: {_key: 'P', _values: ["ScalingMaxY", "bool", "", "",TNum('I', 0)]}, - P41: {_key: 'P', _values: ["ScalingMaxZ", "bool", "", "",TNum('I', 0)]}, - P42: {_key: 'P', _values: ["GeometricTranslation", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P43: {_key: 'P', _values: ["GeometricRotation", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P44: {_key: 'P', _values: ["GeometricScaling", "Vector3D", "Vector", "",TNum('D',1), TNum('D',1), TNum('D',1)]}, - P45: {_key: 'P', _values: ["MinDampRangeX", "double", "Number", "",TNum('D', 0)]}, - P46: {_key: 'P', _values: ["MinDampRangeY", "double", "Number", "",TNum('D', 0)]}, - P47: {_key: 'P', _values: ["MinDampRangeZ", "double", "Number", "",TNum('D', 0)]}, - P48: {_key: 'P', _values: ["MaxDampRangeX", "double", "Number", "",TNum('D', 0)]}, - P49: {_key: 'P', _values: ["MaxDampRangeY", "double", "Number", "",TNum('D', 0)]}, - P50: {_key: 'P', _values: ["MaxDampRangeZ", "double", "Number", "",TNum('D', 0)]}, - P51: {_key: 'P', _values: ["MinDampStrengthX", "double", "Number", "",TNum('D', 0)]}, - P52: {_key: 'P', _values: ["MinDampStrengthY", "double", "Number", "",TNum('D', 0)]}, - P53: {_key: 'P', _values: ["MinDampStrengthZ", "double", "Number", "",TNum('D', 0)]}, - P54: {_key: 'P', _values: ["MaxDampStrengthX", "double", "Number", "",TNum('D', 0)]}, - P55: {_key: 'P', _values: ["MaxDampStrengthY", "double", "Number", "",TNum('D', 0)]}, - P56: {_key: 'P', _values: ["MaxDampStrengthZ", "double", "Number", "",TNum('D', 0)]}, - P57: {_key: 'P', _values: ["PreferedAngleX", "double", "Number", "",TNum('D', 0)]}, - P58: {_key: 'P', _values: ["PreferedAngleY", "double", "Number", "",TNum('D', 0)]}, - P59: {_key: 'P', _values: ["PreferedAngleZ", "double", "Number", "",TNum('D', 0)]}, - P60: {_key: 'P', _values: ["LookAtProperty", "object", "", ""]}, - P61: {_key: 'P', _values: ["UpVectorProperty", "object", "", ""]}, - P62: {_key: 'P', _values: ["Show", "bool", "", "",TNum('I', 1)]}, - P63: {_key: 'P', _values: ["NegativePercentShapeSupport", "bool", "", "",TNum('I', 1)]}, - P64: {_key: 'P', _values: ["DefaultAttributeIndex", "int", "Integer", "",-1]}, - P65: {_key: 'P', _values: ["Freeze", "bool", "", "",TNum('I', 0)]}, - P66: {_key: 'P', _values: ["LODBox", "bool", "", "",TNum('I', 0)]}, - P67: {_key: 'P', _values: ["Lcl Translation", "Lcl Translation", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P68: {_key: 'P', _values: ["Lcl Rotation", "Lcl Rotation", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P69: {_key: 'P', _values: ["Lcl Scaling", "Lcl Scaling", "", "A",TNum('D',1), TNum('D',1), TNum('D',1)]}, - P70: {_key: 'P', _values: ["Visibility", "Visibility", "", "A",TNum('D',1)]}, - P71: {_key: 'P', _values: ["Visibility Inheritance", "Visibility Inheritance", "", "",1]}, + : undefined, + model: DefinitionCounter.model + ? { + _key: 'ObjectType', + _values: ['Model'], + Count: DefinitionCounter.model, + PropertyTemplate: { + _values: ['FbxNode'], + Properties70: { + P01: { + _key: 'P', + _values: ['QuaternionInterpolate', 'enum', '', '', 0], + }, + P02: { + _key: 'P', + _values: [ + 'RotationOffset', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P03: { + _key: 'P', + _values: [ + 'RotationPivot', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P04: { + _key: 'P', + _values: [ + 'ScalingOffset', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P05: { + _key: 'P', + _values: [ + 'ScalingPivot', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P06: { + _key: 'P', + _values: [ + 'TranslationActive', + 'bool', + '', + '', + TNum('I', 0), + ], + }, + P07: { + _key: 'P', + _values: [ + 'TranslationMin', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P08: { + _key: 'P', + _values: [ + 'TranslationMax', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P09: { + _key: 'P', + _values: ['TranslationMinX', 'bool', '', '', TNum('I', 0)], + }, + P10: { + _key: 'P', + _values: ['TranslationMinY', 'bool', '', '', TNum('I', 0)], + }, + P11: { + _key: 'P', + _values: ['TranslationMinZ', 'bool', '', '', TNum('I', 0)], + }, + P12: { + _key: 'P', + _values: ['TranslationMaxX', 'bool', '', '', TNum('I', 0)], + }, + P13: { + _key: 'P', + _values: ['TranslationMaxY', 'bool', '', '', TNum('I', 0)], + }, + P14: { + _key: 'P', + _values: ['TranslationMaxZ', 'bool', '', '', TNum('I', 0)], + }, + P15: { + _key: 'P', + _values: ['RotationOrder', 'enum', '', '', 5], + }, + P16: { + _key: 'P', + _values: [ + 'RotationSpaceForLimitOnly', + 'bool', + '', + '', + TNum('I', 0), + ], + }, + P17: { + _key: 'P', + _values: [ + 'RotationStiffnessX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P18: { + _key: 'P', + _values: [ + 'RotationStiffnessY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P19: { + _key: 'P', + _values: [ + 'RotationStiffnessZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P20: { + _key: 'P', + _values: ['AxisLen', 'double', 'Number', '', TNum('D', 10)], + }, + P21: { + _key: 'P', + _values: [ + 'PreRotation', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P22: { + _key: 'P', + _values: [ + 'PostRotation', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P23: { + _key: 'P', + _values: ['RotationActive', 'bool', '', '', TNum('I', 0)], + }, + P24: { + _key: 'P', + _values: [ + 'RotationMin', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P25: { + _key: 'P', + _values: [ + 'RotationMax', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P26: { + _key: 'P', + _values: ['RotationMinX', 'bool', '', '', TNum('I', 0)], + }, + P27: { + _key: 'P', + _values: ['RotationMinY', 'bool', '', '', TNum('I', 0)], + }, + P28: { + _key: 'P', + _values: ['RotationMinZ', 'bool', '', '', TNum('I', 0)], + }, + P29: { + _key: 'P', + _values: ['RotationMaxX', 'bool', '', '', TNum('I', 0)], + }, + P30: { + _key: 'P', + _values: ['RotationMaxY', 'bool', '', '', TNum('I', 0)], + }, + P31: { + _key: 'P', + _values: ['RotationMaxZ', 'bool', '', '', TNum('I', 0)], + }, + P32: { _key: 'P', _values: ['InheritType', 'enum', '', '', 0] }, + P33: { + _key: 'P', + _values: ['ScalingActive', 'bool', '', '', TNum('I', 0)], + }, + P34: { + _key: 'P', + _values: [ + 'ScalingMin', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P35: { + _key: 'P', + _values: [ + 'ScalingMax', + 'Vector3D', + 'Vector', + '', + TNum('D', 1), + TNum('D', 1), + TNum('D', 1), + ], + }, + P36: { + _key: 'P', + _values: ['ScalingMinX', 'bool', '', '', TNum('I', 0)], + }, + P37: { + _key: 'P', + _values: ['ScalingMinY', 'bool', '', '', TNum('I', 0)], + }, + P38: { + _key: 'P', + _values: ['ScalingMinZ', 'bool', '', '', TNum('I', 0)], + }, + P39: { + _key: 'P', + _values: ['ScalingMaxX', 'bool', '', '', TNum('I', 0)], + }, + P40: { + _key: 'P', + _values: ['ScalingMaxY', 'bool', '', '', TNum('I', 0)], + }, + P41: { + _key: 'P', + _values: ['ScalingMaxZ', 'bool', '', '', TNum('I', 0)], + }, + P42: { + _key: 'P', + _values: [ + 'GeometricTranslation', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P43: { + _key: 'P', + _values: [ + 'GeometricRotation', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P44: { + _key: 'P', + _values: [ + 'GeometricScaling', + 'Vector3D', + 'Vector', + '', + TNum('D', 1), + TNum('D', 1), + TNum('D', 1), + ], + }, + P45: { + _key: 'P', + _values: [ + 'MinDampRangeX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P46: { + _key: 'P', + _values: [ + 'MinDampRangeY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P47: { + _key: 'P', + _values: [ + 'MinDampRangeZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P48: { + _key: 'P', + _values: [ + 'MaxDampRangeX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P49: { + _key: 'P', + _values: [ + 'MaxDampRangeY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P50: { + _key: 'P', + _values: [ + 'MaxDampRangeZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P51: { + _key: 'P', + _values: [ + 'MinDampStrengthX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P52: { + _key: 'P', + _values: [ + 'MinDampStrengthY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P53: { + _key: 'P', + _values: [ + 'MinDampStrengthZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P54: { + _key: 'P', + _values: [ + 'MaxDampStrengthX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P55: { + _key: 'P', + _values: [ + 'MaxDampStrengthY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P56: { + _key: 'P', + _values: [ + 'MaxDampStrengthZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P57: { + _key: 'P', + _values: [ + 'PreferedAngleX', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P58: { + _key: 'P', + _values: [ + 'PreferedAngleY', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P59: { + _key: 'P', + _values: [ + 'PreferedAngleZ', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P60: { + _key: 'P', + _values: ['LookAtProperty', 'object', '', ''], + }, + P61: { + _key: 'P', + _values: ['UpVectorProperty', 'object', '', ''], + }, + P62: { + _key: 'P', + _values: ['Show', 'bool', '', '', TNum('I', 1)], + }, + P63: { + _key: 'P', + _values: [ + 'NegativePercentShapeSupport', + 'bool', + '', + '', + TNum('I', 1), + ], + }, + P64: { + _key: 'P', + _values: [ + 'DefaultAttributeIndex', + 'int', + 'Integer', + '', + -1, + ], + }, + P65: { + _key: 'P', + _values: ['Freeze', 'bool', '', '', TNum('I', 0)], + }, + P66: { + _key: 'P', + _values: ['LODBox', 'bool', '', '', TNum('I', 0)], + }, + P67: { + _key: 'P', + _values: [ + 'Lcl Translation', + 'Lcl Translation', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P68: { + _key: 'P', + _values: [ + 'Lcl Rotation', + 'Lcl Rotation', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P69: { + _key: 'P', + _values: [ + 'Lcl Scaling', + 'Lcl Scaling', + '', + 'A', + TNum('D', 1), + TNum('D', 1), + TNum('D', 1), + ], + }, + P70: { + _key: 'P', + _values: [ + 'Visibility', + 'Visibility', + '', + 'A', + TNum('D', 1), + ], + }, + P71: { + _key: 'P', + _values: [ + 'Visibility Inheritance', + 'Visibility Inheritance', + '', + '', + 1, + ], + }, + }, + }, } - } - } : undefined, - geometry: DefinitionCounter.geometry ? { - _key: 'ObjectType', - _values: ['Geometry'], - Count: DefinitionCounter.geometry, - PropertyTemplate: { - _values: ['FbxMesh'], - Properties70: { - P1: {_key: 'P', _values: ["Color", "ColorRGB", "Color", "",TNum('D',0.8),TNum('D',0.8),TNum('D',0.8)]}, - P2: {_key: 'P', _values: ["BBoxMin", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P3: {_key: 'P', _values: ["BBoxMax", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P4: {_key: 'P', _values: ["Primary Visibility", "bool", "", "",TNum('I', 1)]}, - P5: {_key: 'P', _values: ["Casts Shadows", "bool", "", "",TNum('I', 1)]}, - P6: {_key: 'P', _values: ["Receive Shadows", "bool", "", "",TNum('I', 1)]}, + : undefined, + geometry: DefinitionCounter.geometry + ? { + _key: 'ObjectType', + _values: ['Geometry'], + Count: DefinitionCounter.geometry, + PropertyTemplate: { + _values: ['FbxMesh'], + Properties70: { + P1: { + _key: 'P', + _values: [ + 'Color', + 'ColorRGB', + 'Color', + '', + TNum('D', 0.8), + TNum('D', 0.8), + TNum('D', 0.8), + ], + }, + P2: { + _key: 'P', + _values: [ + 'BBoxMin', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P3: { + _key: 'P', + _values: [ + 'BBoxMax', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P4: { + _key: 'P', + _values: [ + 'Primary Visibility', + 'bool', + '', + '', + TNum('I', 1), + ], + }, + P5: { + _key: 'P', + _values: ['Casts Shadows', 'bool', '', '', TNum('I', 1)], + }, + P6: { + _key: 'P', + _values: ['Receive Shadows', 'bool', '', '', TNum('I', 1)], + }, + }, + }, } - } - } : undefined, - material: DefinitionCounter.material ? { - _key: 'ObjectType', - _values: ['Material'], - Count: DefinitionCounter.material, - PropertyTemplate: { - _values: ['FbxSurfaceLambert'], - Properties70: { - P01: {_key: 'P', _values: ["ShadingModel", "KString", "", "", "Lambert"]}, - P02: {_key: 'P', _values: ["MultiLayer", "bool", "", "",TNum('I', 0)]}, - P03: {_key: 'P', _values: ["EmissiveColor", "Color", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P04: {_key: 'P', _values: ["EmissiveFactor", "Number", "", "A",TNum('D',1)]}, - P05: {_key: 'P', _values: ["AmbientColor", "Color", "", "A",TNum('D',0.2), TNum('D',0.2), TNum('D',0.2)]}, - P06: {_key: 'P', _values: ["AmbientFactor", "Number", "", "A",TNum('D',1)]}, - P07: {_key: 'P', _values: ["DiffuseColor", "Color", "", "A",TNum('D',0.8), TNum('D',0.8), TNum('D',0.8)]}, - P08: {_key: 'P', _values: ["DiffuseFactor", "Number", "", "A",TNum('D',1)]}, - P09: {_key: 'P', _values: ["Bump", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P10: {_key: 'P', _values: ["NormalMap", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P11: {_key: 'P', _values: ["BumpFactor", "double", "Number", "",TNum('D', 1)]}, - P12: {_key: 'P', _values: ["TransparentColor", "Color", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P13: {_key: 'P', _values: ["TransparencyFactor", "Number", "", "A",TNum('D',0)]}, - P14: {_key: 'P', _values: ["DisplacementColor", "ColorRGB", "Color", "",TNum('D',0),TNum('D',0),TNum('D',0)]}, - P15: {_key: 'P', _values: ["DisplacementFactor", "double", "Number", "",TNum('D', 1)]}, - P16: {_key: 'P', _values: ["VectorDisplacementColor", "ColorRGB", "Color", "",TNum('D',0),TNum('D',0),TNum('D',0)]}, - P17: {_key: 'P', _values: ["VectorDisplacementFactor", "double", "Number", "",TNum('D', 1)]}, + : undefined, + material: DefinitionCounter.material + ? { + _key: 'ObjectType', + _values: ['Material'], + Count: DefinitionCounter.material, + PropertyTemplate: { + _values: ['FbxSurfaceLambert'], + Properties70: { + P01: { + _key: 'P', + _values: ['ShadingModel', 'KString', '', '', 'Lambert'], + }, + P02: { + _key: 'P', + _values: ['MultiLayer', 'bool', '', '', TNum('I', 0)], + }, + P03: { + _key: 'P', + _values: [ + 'EmissiveColor', + 'Color', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P04: { + _key: 'P', + _values: [ + 'EmissiveFactor', + 'Number', + '', + 'A', + TNum('D', 1), + ], + }, + P05: { + _key: 'P', + _values: [ + 'AmbientColor', + 'Color', + '', + 'A', + TNum('D', 0.2), + TNum('D', 0.2), + TNum('D', 0.2), + ], + }, + P06: { + _key: 'P', + _values: ['AmbientFactor', 'Number', '', 'A', TNum('D', 1)], + }, + P07: { + _key: 'P', + _values: [ + 'DiffuseColor', + 'Color', + '', + 'A', + TNum('D', 0.8), + TNum('D', 0.8), + TNum('D', 0.8), + ], + }, + P08: { + _key: 'P', + _values: ['DiffuseFactor', 'Number', '', 'A', TNum('D', 1)], + }, + P09: { + _key: 'P', + _values: [ + 'Bump', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P10: { + _key: 'P', + _values: [ + 'NormalMap', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P11: { + _key: 'P', + _values: [ + 'BumpFactor', + 'double', + 'Number', + '', + TNum('D', 1), + ], + }, + P12: { + _key: 'P', + _values: [ + 'TransparentColor', + 'Color', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P13: { + _key: 'P', + _values: [ + 'TransparencyFactor', + 'Number', + '', + 'A', + TNum('D', 0), + ], + }, + P14: { + _key: 'P', + _values: [ + 'DisplacementColor', + 'ColorRGB', + 'Color', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P15: { + _key: 'P', + _values: [ + 'DisplacementFactor', + 'double', + 'Number', + '', + TNum('D', 1), + ], + }, + P16: { + _key: 'P', + _values: [ + 'VectorDisplacementColor', + 'ColorRGB', + 'Color', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P17: { + _key: 'P', + _values: [ + 'VectorDisplacementFactor', + 'double', + 'Number', + '', + TNum('D', 1), + ], + }, + }, + }, } - } - } : undefined, - texture: DefinitionCounter.texture ? { - _key: 'ObjectType', - _values: ['Texture'], - Count: DefinitionCounter.texture, - PropertyTemplate: { - _values: ['FbxFileTexture'], - Properties70: { - P01: {_key: 'P', _values: ["TextureTypeUse", "enum", "", "",0]}, - P02: {_key: 'P', _values: ["Texture alpha", "Number", "", "A",TNum('D',1)]}, - P03: {_key: 'P', _values: ["CurrentMappingType", "enum", "", "",0]}, - P04: {_key: 'P', _values: ["WrapModeU", "enum", "", "",0]}, - P05: {_key: 'P', _values: ["WrapModeV", "enum", "", "",0]}, - P06: {_key: 'P', _values: ["UVSwap", "bool", "", "",TNum('I', 0)]}, - P07: {_key: 'P', _values: ["PremultiplyAlpha", "bool", "", "",TNum('I', 1)]}, - P08: {_key: 'P', _values: ["Translation", "Vector", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P09: {_key: 'P', _values: ["Rotation", "Vector", "", "A",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P10: {_key: 'P', _values: ["Scaling", "Vector", "", "A",TNum('D',1), TNum('D',1), TNum('D',1)]}, - P11: {_key: 'P', _values: ["TextureRotationPivot", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P12: {_key: 'P', _values: ["TextureScalingPivot", "Vector3D", "Vector", "",TNum('D',0), TNum('D',0), TNum('D',0)]}, - P13: {_key: 'P', _values: ["CurrentTextureBlendMode", "enum", "", "",1]}, - P14: {_key: 'P', _values: ["UVSet", "KString", "", "", "default"]}, - P15: {_key: 'P', _values: ["UseMaterial", "bool", "", "",TNum('I', 0)]}, - P16: {_key: 'P', _values: ["UseMipMap", "bool", "", "",TNum('I', 0)]}, + : undefined, + texture: DefinitionCounter.texture + ? { + _key: 'ObjectType', + _values: ['Texture'], + Count: DefinitionCounter.texture, + PropertyTemplate: { + _values: ['FbxFileTexture'], + Properties70: { + P01: { + _key: 'P', + _values: ['TextureTypeUse', 'enum', '', '', 0], + }, + P02: { + _key: 'P', + _values: ['Texture alpha', 'Number', '', 'A', TNum('D', 1)], + }, + P03: { + _key: 'P', + _values: ['CurrentMappingType', 'enum', '', '', 0], + }, + P04: { _key: 'P', _values: ['WrapModeU', 'enum', '', '', 0] }, + P05: { _key: 'P', _values: ['WrapModeV', 'enum', '', '', 0] }, + P06: { + _key: 'P', + _values: ['UVSwap', 'bool', '', '', TNum('I', 0)], + }, + P07: { + _key: 'P', + _values: ['PremultiplyAlpha', 'bool', '', '', TNum('I', 1)], + }, + P08: { + _key: 'P', + _values: [ + 'Translation', + 'Vector', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P09: { + _key: 'P', + _values: [ + 'Rotation', + 'Vector', + '', + 'A', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P10: { + _key: 'P', + _values: [ + 'Scaling', + 'Vector', + '', + 'A', + TNum('D', 1), + TNum('D', 1), + TNum('D', 1), + ], + }, + P11: { + _key: 'P', + _values: [ + 'TextureRotationPivot', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P12: { + _key: 'P', + _values: [ + 'TextureScalingPivot', + 'Vector3D', + 'Vector', + '', + TNum('D', 0), + TNum('D', 0), + TNum('D', 0), + ], + }, + P13: { + _key: 'P', + _values: ['CurrentTextureBlendMode', 'enum', '', '', 1], + }, + P14: { + _key: 'P', + _values: ['UVSet', 'KString', '', '', 'default'], + }, + P15: { + _key: 'P', + _values: ['UseMaterial', 'bool', '', '', TNum('I', 0)], + }, + P16: { + _key: 'P', + _values: ['UseMipMap', 'bool', '', '', TNum('I', 0)], + }, + }, + }, } - } - } : undefined, - image: DefinitionCounter.image ? { - _key: 'ObjectType', - _values: ['Video'], - Count: DefinitionCounter.image, - PropertyTemplate: { - _values: ['FbxVideo'], - Properties70: { - P01: {_key: 'P', _values: ["ImageSequence", "bool", "", "",TNum('I', 0)]}, - P02: {_key: 'P', _values: ["ImageSequenceOffset", "int", "Integer", "",0]}, - P03: {_key: 'P', _values: ["FrameRate", "double", "Number", "",TNum('D', 0)]}, - P04: {_key: 'P', _values: ["LastFrame", "int", "Integer", "",0]}, - P05: {_key: 'P', _values: ["Width", "int", "Integer", "",0]}, - P06: {_key: 'P', _values: ["Height", "int", "Integer", "",0]}, - P07: {_key: 'P', _values: ["Path", "KString", "XRefUrl", "", ""]}, - P08: {_key: 'P', _values: ["StartFrame", "int", "Integer", "",0]}, - P09: {_key: 'P', _values: ["StopFrame", "int", "Integer", "",0]}, - P10: {_key: 'P', _values: ["PlaySpeed", "double", "Number", "",TNum('D', 0)]}, - P11: {_key: 'P', _values: ["Offset", "KTime", "Time", "",TNum('L', 0)]}, - P12: {_key: 'P', _values: ["InterlaceMode", "enum", "", "",0]}, - P13: {_key: 'P', _values: ["FreeRunning", "bool", "", "",TNum('I', 0)]}, - P14: {_key: 'P', _values: ["Loop", "bool", "", "",TNum('I', 0)]}, - P15: {_key: 'P', _values: ["AccessMode", "enum", "", "",0]}, + : undefined, + image: DefinitionCounter.image + ? { + _key: 'ObjectType', + _values: ['Video'], + Count: DefinitionCounter.image, + PropertyTemplate: { + _values: ['FbxVideo'], + Properties70: { + P01: { + _key: 'P', + _values: ['ImageSequence', 'bool', '', '', TNum('I', 0)], + }, + P02: { + _key: 'P', + _values: ['ImageSequenceOffset', 'int', 'Integer', '', 0], + }, + P03: { + _key: 'P', + _values: [ + 'FrameRate', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P04: { + _key: 'P', + _values: ['LastFrame', 'int', 'Integer', '', 0], + }, + P05: { _key: 'P', _values: ['Width', 'int', 'Integer', '', 0] }, + P06: { + _key: 'P', + _values: ['Height', 'int', 'Integer', '', 0], + }, + P07: { + _key: 'P', + _values: ['Path', 'KString', 'XRefUrl', '', ''], + }, + P08: { + _key: 'P', + _values: ['StartFrame', 'int', 'Integer', '', 0], + }, + P09: { + _key: 'P', + _values: ['StopFrame', 'int', 'Integer', '', 0], + }, + P10: { + _key: 'P', + _values: [ + 'PlaySpeed', + 'double', + 'Number', + '', + TNum('D', 0), + ], + }, + P11: { + _key: 'P', + _values: ['Offset', 'KTime', 'Time', '', TNum('L', 0)], + }, + P12: { + _key: 'P', + _values: ['InterlaceMode', 'enum', '', '', 0], + }, + P13: { + _key: 'P', + _values: ['FreeRunning', 'bool', '', '', TNum('I', 0)], + }, + P14: { + _key: 'P', + _values: ['Loop', 'bool', '', '', TNum('I', 0)], + }, + P15: { _key: 'P', _values: ['AccessMode', 'enum', '', '', 0] }, + }, + }, } - } - } : undefined, + : undefined, - - pose: DefinitionCounter.pose ? { - _key: 'ObjectType', - _values: ['Pose'], - Count: DefinitionCounter.pose, - } : undefined, - deformer: DefinitionCounter.deformer ? { - _key: 'ObjectType', - _values: ['Deformer'], - Count: DefinitionCounter.deformer, - } : undefined, - - animation_stack: DefinitionCounter.animation_stack ? { - _key: 'ObjectType', - _values: ['AnimationStack'], - Count: DefinitionCounter.animation_stack, - PropertyTemplate: { - _values: ['FbxAnimStack'], - Properties70: { - P01: {_key: 'P', _values: ["Description", "KString", "", "", ""]}, - P02: {_key: 'P', _values: ["LocalStart", "KTime", "Time", "",TNum('L', 0)]}, - P03: {_key: 'P', _values: ["LocalStop", "KTime", "Time", "",TNum('L', 0)]}, - P04: {_key: 'P', _values: ["ReferenceStart", "KTime", "Time", "",TNum('L', 0)]}, - P05: {_key: 'P', _values: ["ReferenceStop", "KTime", "Time", "",TNum('L', 0)]}, + pose: DefinitionCounter.pose + ? { + _key: 'ObjectType', + _values: ['Pose'], + Count: DefinitionCounter.pose, } - } - } : undefined, - animation_layer: DefinitionCounter.animation_layer ? { - _key: 'ObjectType', - _values: ['AnimationLayer'], - Count: DefinitionCounter.animation_layer, - PropertyTemplate: { - _values: ['FbxAnimLayer'], - Properties70: { - P01: {_key: 'P', _values: ["Weight", "Number", "", "A",TNum('D',100)]}, - P02: {_key: 'P', _values: ["Mute", "bool", "", "",TNum('I', 0)]}, - P03: {_key: 'P', _values: ["Solo", "bool", "", "",TNum('I', 0)]}, - P04: {_key: 'P', _values: ["Lock", "bool", "", "",TNum('I', 0)]}, - P05: {_key: 'P', _values: ["Color", "ColorRGB", "Color", "",TNum('D',0.8),TNum('D',0.8),TNum('D',0.8)]}, - P06: {_key: 'P', _values: ["BlendMode", "enum", "", "",0]}, - P07: {_key: 'P', _values: ["RotationAccumulationMode", "enum", "", "",0]}, - P08: {_key: 'P', _values: ["ScaleAccumulationMode", "enum", "", "",0]}, - P09: {_key: 'P', _values: ["BlendModeBypass", "ULongLong", "", "",0]}, + : undefined, + deformer: DefinitionCounter.deformer + ? { + _key: 'ObjectType', + _values: ['Deformer'], + Count: DefinitionCounter.deformer, } - } - } : undefined, - animation_curve_node: DefinitionCounter.animation_curve_node ? { - _key: 'ObjectType', - _values: ['AnimationCurveNode'], - Count: DefinitionCounter.animation_curve_node, - PropertyTemplate: { - _values: ['FbxAnimCurveNode'], - Properties70: { - P01: {_key: 'P', _values: ["d", "Compound", "", ""]}, + : undefined, + + animation_stack: DefinitionCounter.animation_stack + ? { + _key: 'ObjectType', + _values: ['AnimationStack'], + Count: DefinitionCounter.animation_stack, + PropertyTemplate: { + _values: ['FbxAnimStack'], + Properties70: { + P01: { + _key: 'P', + _values: ['Description', 'KString', '', '', ''], + }, + P02: { + _key: 'P', + _values: ['LocalStart', 'KTime', 'Time', '', TNum('L', 0)], + }, + P03: { + _key: 'P', + _values: ['LocalStop', 'KTime', 'Time', '', TNum('L', 0)], + }, + P04: { + _key: 'P', + _values: [ + 'ReferenceStart', + 'KTime', + 'Time', + '', + TNum('L', 0), + ], + }, + P05: { + _key: 'P', + _values: [ + 'ReferenceStop', + 'KTime', + 'Time', + '', + TNum('L', 0), + ], + }, + }, + }, } - } - } : undefined, - animation_curve: DefinitionCounter.animation_curve ? { - _key: 'ObjectType', - _values: ['AnimationCurve'], - Count: DefinitionCounter.animation_curve, - } : undefined - } - }) + : undefined, + animation_layer: DefinitionCounter.animation_layer + ? { + _key: 'ObjectType', + _values: ['AnimationLayer'], + Count: DefinitionCounter.animation_layer, + PropertyTemplate: { + _values: ['FbxAnimLayer'], + Properties70: { + P01: { + _key: 'P', + _values: ['Weight', 'Number', '', 'A', TNum('D', 100)], + }, + P02: { + _key: 'P', + _values: ['Mute', 'bool', '', '', TNum('I', 0)], + }, + P03: { + _key: 'P', + _values: ['Solo', 'bool', '', '', TNum('I', 0)], + }, + P04: { + _key: 'P', + _values: ['Lock', 'bool', '', '', TNum('I', 0)], + }, + P05: { + _key: 'P', + _values: [ + 'Color', + 'ColorRGB', + 'Color', + '', + TNum('D', 0.8), + TNum('D', 0.8), + TNum('D', 0.8), + ], + }, + P06: { _key: 'P', _values: ['BlendMode', 'enum', '', '', 0] }, + P07: { + _key: 'P', + _values: ['RotationAccumulationMode', 'enum', '', '', 0], + }, + P08: { + _key: 'P', + _values: ['ScaleAccumulationMode', 'enum', '', '', 0], + }, + P09: { + _key: 'P', + _values: ['BlendModeBypass', 'ULongLong', '', '', 0], + }, + }, + }, + } + : undefined, + animation_curve_node: DefinitionCounter.animation_curve_node + ? { + _key: 'ObjectType', + _values: ['AnimationCurveNode'], + Count: DefinitionCounter.animation_curve_node, + PropertyTemplate: { + _values: ['FbxAnimCurveNode'], + Properties70: { + P01: { _key: 'P', _values: ['d', 'Compound', '', ''] }, + }, + }, + } + : undefined, + animation_curve: DefinitionCounter.animation_curve + ? { + _key: 'ObjectType', + _values: ['AnimationCurve'], + Count: DefinitionCounter.animation_curve, + } + : undefined, + }, + }); model.push(formatFBXComment('Object properties')); model.push({ - Objects + Objects, }); // Object connections model.push(formatFBXComment('Object connections')); let connections = {}; Connections.forEach((connection, i) => { - connections[`connection_${i}_comment`] = {_comment: connection.name.join(', ')} + connections[`connection_${i}_comment`] = { _comment: connection.name.join(', ') }; connections[`connection_${i}`] = { _key: 'C', - _values: [connection.property ? 'OP' : 'OO', ...connection.id] - } + _values: [connection.property ? 'OP' : 'OO', ...connection.id], + }; if (connection.property) { connections[`connection_${i}`]._values.push(connection.property); } //model += `\t;${connection.name.join(', ')}\n`; //model += `\tC: "${property ? 'OP' : 'OO'}",${connection.id.join(',')}${property}\n\n`; - }) + }); model.push({ - Connections: connections + Connections: connections, }); // Takes (Animation) model.push(formatFBXComment('Takes section')); model.push({ - Takes - }) + Takes, + }); - scope.dispatchEvent('compile', {model, options}); + scope.dispatchEvent('compile', { model, options }); let compiled_model; if (options.encoding == 'binary') { - let top_level_object = {}; model.forEach(section => { if (typeof section == 'object') { @@ -1356,17 +2627,18 @@ var codec = new Codec('fbx', { top_level_object[key] = section[key]; } } - }) + }); compiled_model = compileBinaryFBXModel(top_level_object); - } else { - compiled_model = model.map(section => { - if (typeof section == 'object') { - return compileASCIIFBXSection(section); - } else { - return section; - } - }).join(''); + compiled_model = model + .map(section => { + if (typeof section == 'object') { + return compileASCIIFBXSection(section); + } else { + return section; + } + }) + .join(''); } return compiled_model; @@ -1374,7 +2646,7 @@ var codec = new Codec('fbx', { write(content, path) { var scope = this; - Blockbench.writeFile(path, {content}, path => scope.afterSave(path)); + Blockbench.writeFile(path, { content }, path => scope.afterSave(path)); Texture.all.forEach(tex => { if (tex.error) return; @@ -1386,15 +2658,27 @@ var codec = new Codec('fbx', { image_path.splice(-1, 1, name); Blockbench.writeFile(image_path.join(osfs), { content: tex.source, - savetype: 'image' - }) - }) + savetype: 'image', + }); + }); }, export_options: { - encoding: {type: 'select', label: 'codec.common.encoding', options: {ascii: 'ASCII', binary: 'Binary (Experimental)'}}, - scale: {label: 'settings.model_export_scale', type: 'number', value: Settings.get('model_export_scale')}, - embed_textures: {type: 'checkbox', label: 'codec.common.embed_textures', value: false}, - include_animations: {label: 'codec.common.export_animations', type: 'checkbox', value: true} + encoding: { + type: 'select', + label: 'codec.common.encoding', + options: { ascii: 'ASCII', binary: 'Binary (Experimental)' }, + }, + scale: { + label: 'settings.model_export_scale', + type: 'number', + value: Settings.get('model_export_scale'), + }, + embed_textures: { type: 'checkbox', label: 'codec.common.embed_textures', value: false }, + include_animations: { + label: 'codec.common.export_animations', + type: 'checkbox', + value: true, + }, }, async export() { if (Object.keys(this.export_options).length) { @@ -1403,21 +2687,23 @@ var codec = new Codec('fbx', { } var scope = this; if (isApp) { - Filesystem.exportFile({ - resource_id: 'fbx', - type: this.name, - extensions: [this.extension], - startpath: this.startPath(), - content: this.compile(), - name: this.fileName(), - custom_writer: (a, b) => scope.write(a, b), - }, path => this.afterDownload(path)) - + Filesystem.exportFile( + { + resource_id: 'fbx', + type: this.name, + extensions: [this.extension], + startpath: this.startPath(), + content: this.compile(), + name: this.fileName(), + custom_writer: (a, b) => scope.write(a, b), + }, + path => this.afterDownload(path) + ); } else { var archive = new JSZip(); - var content = this.compile() + var content = this.compile(); - archive.file((Project.name||'model')+'.fbx', content) + archive.file((Project.name || 'model') + '.fbx', content); Texture.all.forEach(tex => { if (tex.error) return; @@ -1425,38 +2711,43 @@ var codec = new Codec('fbx', { if (name.substr(-4).toLowerCase() !== '.png') { name += '.png'; } - archive.file(name, tex.source.replace('data:image/png;base64,', ''), {base64: true}); - }) - archive.generateAsync({type: 'blob'}).then(content => { - Filesystem.exportFile({ - type: 'Zip Archive', - extensions: ['zip'], - name: 'assets', - content: content, - savetype: 'zip' - }, path => scope.afterDownload(path)); - }) + archive.file(name, tex.source.replace('data:image/png;base64,', ''), { + base64: true, + }); + }); + archive.generateAsync({ type: 'blob' }).then(content => { + Filesystem.exportFile( + { + type: 'Zip Archive', + extensions: ['zip'], + name: 'assets', + content: content, + savetype: 'zip', + }, + path => scope.afterDownload(path) + ); + }); } - } -}) + }, +}); -BARS.defineActions(function() { +BARS.defineActions(function () { codec.export_action = new Action('export_fbx', { icon: 'icon-fbx', category: 'file', condition: () => Project, click: function () { - codec.export() - } - }) -}) + codec.export(); + }, + }); +}); class BinaryWriter { array: Uint8Array; buffer: ArrayBuffer; - view: DataView - cursor: number - little_endian: boolean + view: DataView; + cursor: number; + little_endian: boolean; textEncoder: TextEncoder; constructor(minimal_length, little_endian) { this.array = new Uint8Array(minimal_length); @@ -1468,13 +2759,13 @@ class BinaryWriter { this.textEncoder = new TextEncoder(); } expand(n) { - if (this.cursor+n > this.buffer.byteLength) { + if (this.cursor + n > this.buffer.byteLength) { var oldArray = this.array; // Expand by at least 160 bytes at a time to improve performance. Only works for FBX since 176+ arbitrary bytes are added to the file end. this.array = new Uint8Array(this.cursor + Math.max(n, 176)); this.buffer = this.array.buffer; this.array.set(oldArray); - this.view = new DataView(this.buffer) + this.view = new DataView(this.buffer); } } WriteUInt8(value) { @@ -1517,8 +2808,8 @@ class BinaryWriter { this.view.setFloat64(this.cursor, value, this.little_endian); this.cursor += 8; } - WriteBoolean(value:boolean) { - this.WriteUInt8(value ? 1 : 0) + WriteBoolean(value: boolean) { + this.WriteUInt8(value ? 1 : 0); } Write7BitEncodedInt(value) { while (value >= 0x80) { @@ -1598,7 +2889,7 @@ class BinaryWriter { EncodeString(string: string) { return this.textEncoder.encode(string); } -}; +} export function compileBinaryFBXModel(top_level_object) { // https://code.blender.org/2013/08/fbx-binary-file-format-specification/ @@ -1606,18 +2897,13 @@ export function compileBinaryFBXModel(top_level_object) { let _BLOCK_SENTINEL_DATA; if (_FBX_VERSION < 7500) { - _BLOCK_SENTINEL_DATA = new Uint8Array( - Array(13).fill(0x00) - ); - } - else { - _BLOCK_SENTINEL_DATA = new Uint8Array( - Array(25).fill(0x00) - ); + _BLOCK_SENTINEL_DATA = new Uint8Array(Array(13).fill(0x00)); + } else { + _BLOCK_SENTINEL_DATA = new Uint8Array(Array(25).fill(0x00)); } // Awful exceptions from Blender: those "classes" of elements seem to need block sentinel even when having no children and some props. - const _KEYS_IGNORE_BLOCK_SENTINEL = ["AnimationStack", "AnimationLayer"]; + const _KEYS_IGNORE_BLOCK_SENTINEL = ['AnimationStack', 'AnimationLayer']; // TODO: if FBX_VERSION >= 7500, use 64-bit offsets (for read_fbx_elem_uint) @@ -1625,14 +2911,12 @@ export function compileBinaryFBXModel(top_level_object) { // Header writer.WriteRawString('Kaydara FBX Binary '); writer.WriteUInt8(0x00); - writer.WriteUInt8(0x1A); + writer.WriteUInt8(0x1a); writer.WriteUInt8(0x00); // Version writer.WriteUInt32(_FBX_VERSION); - function writeObjectRecursively(key, object) { - let tuple; if (typeof object == 'object' && typeof object.map === 'function') { tuple = object; @@ -1643,8 +2927,10 @@ export function compileBinaryFBXModel(top_level_object) { } else { tuple = []; } - let is_data_array = object.hasOwnProperty('_values') && object.hasOwnProperty('a') && - object._type != undefined; + let is_data_array = + object.hasOwnProperty('_values') && + object.hasOwnProperty('a') && + object._type != undefined; // EndOffset, change later let end_offset_index = writer.cursor; @@ -1657,15 +2943,14 @@ export function compileBinaryFBXModel(top_level_object) { // Name writer.WriteString(key); - let property_start_index = writer.cursor; // Data Array if (is_data_array) { let type = object._type || 'i'; - if (!object._type) console.log('default', key, 'to int') + if (!object._type) console.log('default', key, 'to int'); let array = object.a; if (array instanceof Array == false) array = [array]; - + writer.WriteRawString(type); writer.WriteUInt32(array.length); // Encoding (compression, unused by Blockbench) @@ -1689,15 +2974,24 @@ export function compileBinaryFBXModel(top_level_object) { // Contents for (let v of array) { switch (type) { - case 'f': writer.WriteFloat32(v); break; - case 'd': writer.WriteFloat64(v); break; - case 'l': writer.WriteInt64(v); break; - case 'i': writer.WriteInt32(v); break; - case 'b': writer.WriteBoolean(v); break; + case 'f': + writer.WriteFloat32(v); + break; + case 'd': + writer.WriteFloat64(v); + break; + case 'l': + writer.WriteInt64(v); + break; + case 'i': + writer.WriteInt32(v); + break; + case 'b': + writer.WriteBoolean(v); + break; } } } else { - // Tuple tuple.forEach((value, i) => { let type: string = typeof value; @@ -1722,12 +3016,10 @@ export function compileBinaryFBXModel(top_level_object) { if (type == 'boolean') { writer.WriteRawString('C'); writer.WriteBoolean(value); - } else if (type == 'string' && value.startsWith('iV')) { // base64 writer.WriteRawString('R'); writer.WriteU32Base64(value); - } else if (type == 'string') { // Replace '::' with 0x00 0x01 and swap the order // E.g. "Geometry::cube" becomes "cube\x00\x01Geometry" @@ -1735,46 +3027,42 @@ export function compileBinaryFBXModel(top_level_object) { if (value.includes('::')) { const hex00 = String.fromCharCode(0x00); const hex01 = String.fromCharCode(0x01); - - const parts = value.split("::"); + + const parts = value.split('::'); value = parts[1] + hex00 + hex01 + parts[0]; } // string writer.WriteRawString('S'); if (value.startsWith('_')) value = value.substring(1); writer.WriteU32String(value); - } else if (type == 'Y') { writer.WriteRawString('Y'); writer.WriteInt16(value); - } else if (type == 'I') { writer.WriteRawString('I'); writer.WriteInt32(value); - } else if (type == 'F') { writer.WriteRawString('F'); writer.WriteFloat32(value); - } else if (type == 'D') { writer.WriteRawString('D'); writer.WriteFloat64(value); - } else if (type == 'L') { writer.WriteRawString('L'); writer.WriteInt64(value); - } - - }) + }); } // Property Byte Length - writer.view.setUint32(property_length_index, writer.cursor - property_start_index, writer.little_endian); + writer.view.setUint32( + property_length_index, + writer.cursor - property_start_index, + writer.little_endian + ); // Nested List if (typeof object == 'object' && object instanceof Array == false && !is_data_array) { - let is_nested = false; for (let key in object) { if (typeof key == 'string' && key.startsWith('_')) continue; @@ -1788,7 +3076,10 @@ export function compileBinaryFBXModel(top_level_object) { writeObjectRecursively(key, child); } // Null Record, indicating a nested list. - if (is_nested || (Object.keys(object).length === 0 && !(_KEYS_IGNORE_BLOCK_SENTINEL.includes(key)))) { + if ( + is_nested || + (Object.keys(object).length === 0 && !_KEYS_IGNORE_BLOCK_SENTINEL.includes(key)) + ) { writer.WriteBytes(_BLOCK_SENTINEL_DATA); } } @@ -1805,8 +3096,9 @@ export function compileBinaryFBXModel(top_level_object) { // Footer // Write the FOOT ID let footer_id = [ - 0xfa, 0xbc, 0xab, 0x09, 0xd0, 0xc8, 0xd4, 0x66, 0xb1, 0x76, 0xfb, 0x83, 0x1c, 0xf7, 0x26, 0x7e, - 0x00, 0x00, 0x00, 0x00]; + 0xfa, 0xbc, 0xab, 0x09, 0xd0, 0xc8, 0xd4, 0x66, 0xb1, 0x76, 0xfb, 0x83, 0x1c, 0xf7, 0x26, + 0x7e, 0x00, 0x00, 0x00, 0x00, + ]; writer.WriteBytes(new Uint8Array(footer_id)); // padding for alignment (values between 1 & 16 observed) @@ -1822,12 +3114,11 @@ export function compileBinaryFBXModel(top_level_object) { writer.WriteUInt32(_FBX_VERSION); // Write some footer magic - writer.WriteBytes(new Uint8Array( - Array(120).fill(0x00)) - ); + writer.WriteBytes(new Uint8Array(Array(120).fill(0x00))); let footer_magic = [ - 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b - ]; + 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, + 0x0b, + ]; writer.WriteBytes(new Uint8Array(footer_magic)); // Cut the array to the cursor location (because the writer expand method can have added extra bytes) diff --git a/js/io/formats/generic.ts b/js/io/formats/generic.ts index fe0e3dada..966c7f44c 100644 --- a/js/io/formats/generic.ts +++ b/js/io/formats/generic.ts @@ -1,16 +1,24 @@ new ModelFormat('free', { icon: 'icon-format_free', category: 'general', - target: ['Godot', 'Unity', 'Unreal Engine', 'Sketchfab', 'Blender', tl('format.free.info.3d_printing')], + target: [ + 'Godot', + 'Unity', + 'Unreal Engine', + 'Sketchfab', + 'Blender', + tl('format.free.info.3d_printing'), + ], format_page: { content: [ - {type: 'h3', text: tl('mode.start.format.informations')}, - {text: `* ${tl('format.free.info.meshes')} - * ${tl('format.free.info.limitation')}`.replace(/\t+/g, '') + { type: 'h3', text: tl('mode.start.format.informations') }, + { + text: `* ${tl('format.free.info.meshes')} + * ${tl('format.free.info.limitation')}`.replace(/\t+/g, ''), }, - {type: 'h3', text: tl('mode.start.format.resources')}, - {text: `* [Low-Poly Modeling Tutorial](https://www.youtube.com/watch?v=WbyCbA1c8BM)`} - ] + { type: 'h3', text: tl('mode.start.format.resources') }, + { text: `* [Low-Poly Modeling Tutorial](https://www.youtube.com/watch?v=WbyCbA1c8BM)` }, + ], }, meshes: true, billboards: true, @@ -26,4 +34,4 @@ new ModelFormat('free', { animated_textures: true, locators: true, pbr: true, -}) +}); diff --git a/js/io/formats/skin.ts b/js/io/formats/skin.ts index f233a79d2..c67b9dc65 100644 --- a/js/io/formats/skin.ts +++ b/js/io/formats/skin.ts @@ -1,30 +1,30 @@ -import { CanvasFrame } from "../../lib/CanvasFrame" -import StateMemory from "../../util/state_memory"; -import { Panels, setProjectTitle } from "../../interface/interface" -import { getAllGroups } from "../../outliner/group" -import { DefaultCameraPresets } from "../../preview/preview" -import { MinecraftEULA } from "../../preview/preview_scenes" -import { TextureGenerator } from "../../texturing/texture_generator" -import { Panel } from "../../interface/panels"; -import { Blockbench } from "../../api"; -import { FormResultValue } from "../../interface/form"; +import { CanvasFrame } from '../../lib/CanvasFrame'; +import StateMemory from '../../util/state_memory'; +import { Panels, setProjectTitle } from '../../interface/interface'; +import { getAllGroups } from '../../outliner/group'; +import { DefaultCameraPresets } from '../../preview/preview'; +import { MinecraftEULA } from '../../preview/preview_scenes'; +import { TextureGenerator } from '../../texturing/texture_generator'; +import { Panel } from '../../interface/panels'; +import { Blockbench } from '../../api'; +import { FormResultValue } from '../../interface/form'; type SkinPreset = { - display_name: string - pose?: boolean - model?: string - model_java?: string - model_bedrock?: string + display_name: string; + pose?: boolean; + model?: string; + model_java?: string; + model_bedrock?: string; variants?: { [key: string]: { - name: string - model: string - } - } -} + name: string; + model: string; + }; + }; +}; export const skin_presets: Record = {}; -type SkinPoseData = Record +type SkinPoseData = Record; const DefaultPoses: Record = { none: { Head: [0, 0, 0], @@ -48,15 +48,15 @@ const DefaultPoses: Record = { RightArm: [-35, 0, 0], LeftArm: [35, 0, 0], RightLeg: [42, 0, 2], - LeftLeg: [-42, 0, -2] + LeftLeg: [-42, 0, -2], }, crouching: { - Head: {rotation: [-5, 0, 0], offset: [0, -1, 0]}, - Body: {rotation: [-28, 0, 0], offset: [0, 0, -1]}, + Head: { rotation: [-5, 0, 0], offset: [0, -1, 0] }, + Body: { rotation: [-28, 0, 0], offset: [0, 0, -1] }, RightArm: [-15, 0, 0], LeftArm: [-40, 0, 0], - RightLeg: {rotation: [-14, 0, 0], offset: [0, 3, 3.75]}, - LeftLeg: {rotation: [14, 0, 0], offset: [0, 3, 4]} + RightLeg: { rotation: [-14, 0, 0], offset: [0, 3, 3.75] }, + LeftLeg: { rotation: [14, 0, 0], offset: [0, 3, 4] }, }, sitting: { Head: [5.5, 0, 0], @@ -64,23 +64,23 @@ const DefaultPoses: Record = { RightArm: [36, 0, 0], LeftArm: [36, 0, 0], RightLeg: [72, -18, 0], - LeftLeg: [72, 18, 0] + LeftLeg: [72, 18, 0], }, jumping: { Head: [20, 0, 0], Body: [0, 0, 0], - RightArm: {rotation: [-175, 0, -20], offset: [0, 2, 0]}, - LeftArm: {rotation: [-170, 0, 15], offset: [0, 2, 0]}, - RightLeg: {rotation: [-5, 0, 15], offset: [0, -1, 0]}, - LeftLeg: {rotation: [2.5, 0, -10], offset: [0, 6, -3.75]} + RightArm: { rotation: [-175, 0, -20], offset: [0, 2, 0] }, + LeftArm: { rotation: [-170, 0, 15], offset: [0, 2, 0] }, + RightLeg: { rotation: [-5, 0, 15], offset: [0, -1, 0] }, + LeftLeg: { rotation: [2.5, 0, -10], offset: [0, 6, -3.75] }, }, aiming: { Head: [8, -35, 0], Body: [-2, 0, 0], - RightArm: {rotation: [97, -17, -2], offset: [-1, 1, -1]}, + RightArm: { rotation: [97, -17, -2], offset: [-1, 1, -1] }, LeftArm: [104, -44, -10], - RightLeg: {rotation: [2.5, 0, 0], offset: [0, 1, -2]}, - LeftLeg: [-28, 0, 0] + RightLeg: { rotation: [2.5, 0, 0], offset: [0, 1, -2] }, + LeftLeg: [-28, 0, 0], }, }; @@ -90,134 +90,136 @@ export const codec = new Codec('skin_model', { compile(options) { if (options === undefined) options = 0; type BoneData = { - name: string - parent?: string - pivot?: ArrayVector3 - rotation?: ArrayVector3 - reset?: boolean - mirror?: boolean - cubes?: CubeData[] - } + name: string; + parent?: string; + pivot?: ArrayVector3; + rotation?: ArrayVector3; + reset?: boolean; + mirror?: boolean; + cubes?: CubeData[]; + }; type CubeData = { - origin: ArrayVector3 - size: ArrayVector3 - inflate?: number - } + origin: ArrayVector3; + size: ArrayVector3; + inflate?: number; + }; let entitymodel = { name: Project.geometry_name.split('.')[0], texturewidth: Project.texture_width, textureheight: Project.texture_height, - bones: undefined as undefined | BoneData[] - } - let bones = [] + bones: undefined as undefined | BoneData[], + }; + let bones = []; let groups = getAllGroups(); - groups.forEach(function(g) { + groups.forEach(function (g) { if (g.type !== 'group') return; //Bone let bone: BoneData = { - name: g.name - } - bone.name = g.name + name: g.name, + }; + bone.name = g.name; if (g.parent.type === 'group') { - bone.parent = g.parent.name + bone.parent = g.parent.name; } - bone.pivot = g.origin.slice() - bone.pivot[0] *= -1 + bone.pivot = g.origin.slice(); + bone.pivot[0] *= -1; if (!g.rotation.allEqual(0)) { - bone.rotation = [ - -g.rotation[0], - -g.rotation[1], - g.rotation[2] - ] + bone.rotation = [-g.rotation[0], -g.rotation[1], g.rotation[2]]; } if (g.reset) bone.reset = true; if (g.mirror_uv) bone.mirror = true; //Elements - let cubes = [] + let cubes = []; for (let obj of g.children) { if (obj.export && obj instanceof Cube) { // @ts-ignore let template = Codecs.bedrock.compileCube(obj, g); - cubes.push(template) + cubes.push(template); } } if (cubes.length) { - bone.cubes = cubes + bone.cubes = cubes; } - bones.push(bone) - }) + bones.push(bone); + }); if (bones.length) { - entitymodel.bones = bones + entitymodel.bones = bones; } - this.dispatchEvent('compile', {model: entitymodel, options}); - return entitymodel + this.dispatchEvent('compile', { model: entitymodel, options }); + return entitymodel; }, // @ts-ignore - parse(data: any, resolution: number, texture_file: undefined | false | {name: string, path: string, content: string}, pose = true, layer_template) { - this.dispatchEvent('parse', {model: data}); + parse( + data: any, + resolution: number, + texture_file: undefined | false | { name: string; path: string; content: string }, + pose = true, + layer_template + ) { + this.dispatchEvent('parse', { model: data }); Project.texture_width = data.texturewidth || 64; Project.texture_height = data.textureheight || 64; if (data.texture_resolution_factor) resolution *= data.texture_resolution_factor; Interface.Panels.skin_pose.inside_vue.pose = Project.skin_pose = pose ? 'natural' : 'none'; - let bones = {} + let bones = {}; let template_cubes = {}; if (data.bones) { - let included_bones = [] - data.bones.forEach(function(b) { - included_bones.push(b.name) - }) - data.bones.forEach(function(b, bi) { + let included_bones = []; + data.bones.forEach(function (b) { + included_bones.push(b.name); + }); + data.bones.forEach(function (b, bi) { let group = new Group({ name: b.name, origin: b.pivot, - rotation: (pose && b.pose) ? b.pose : b.rotation - }).init() + rotation: pose && b.pose ? b.pose : b.rotation, + }).init(); group.isOpen = true; - bones[b.name] = group + bones[b.name] = group; if (b.pivot) { - group.origin[0] *= -1 + group.origin[0] *= -1; } group.rotation[0] *= -1; group.rotation[1] *= -1; - - group.mirror_uv = b.mirror === true - group.reset = b.reset === true + + group.mirror_uv = b.mirror === true; + group.reset = b.reset === true; group.skin_original_origin = group.origin.slice() as ArrayVector3; if (b.cubes) { - b.cubes.forEach(function(cube) { - + b.cubes.forEach(function (cube) { let base_cube = Codecs.bedrock.parseCube(cube, group); template_cubes[Cube.all.indexOf(base_cube)] = cube; - - }) + }); } if (b.children) { - b.children.forEach(function(cg) { - cg.addTo(group) - }) + b.children.forEach(function (cg) { + cg.addTo(group); + }); } let parent_group: 'root' | OutlinerNode = 'root'; if (b.parent) { if (bones[b.parent]) { - parent_group = bones[b.parent] + parent_group = bones[b.parent]; } else { - data.bones.forEach(function(ib) { + data.bones.forEach(function (ib) { if (ib.name === b.parent) { - ib.children && ib.children.length ? ib.children.push(group) : ib.children = [group] + ib.children && ib.children.length + ? ib.children.push(group) + : (ib.children = [group]); } - }) + }); } } - group.addTo(parent_group) - }) + group.addTo(parent_group); + }); } if (!Cube.all.find(cube => cube.box_uv)) { Project.box_uv = false; @@ -227,13 +229,13 @@ export const codec = new Codec('skin_model', { texture = new Texture().fromFile(texture_file).add(false); } else if (texture_file != false && resolution) { texture = generateTemplate( - Project.texture_width*resolution, - Project.texture_height*resolution, + Project.texture_width * resolution, + Project.texture_height * resolution, template_cubes, data.name, data.eyes, layer_template - ) + ); } for (let index in template_cubes) { if (template_cubes[index].visibility === false) { @@ -241,35 +243,37 @@ export const codec = new Codec('skin_model', { } } if (texture) { - texture.load_callback = function() { + texture.load_callback = function () { Modes.options.paint.select(); - } + }; } if (data.camera_angle) { // @ts-ignore - main_preview.loadAnglePreset(DefaultCameraPresets.find(p => p.id == data.camera_angle)) + main_preview.loadAnglePreset(DefaultCameraPresets.find(p => p.id == data.camera_angle)); } - Canvas.updateAllBones() - Canvas.updateVisibility() - setProjectTitle() - updateSelection() + Canvas.updateAllBones(); + Canvas.updateVisibility(); + setProjectTitle(); + updateSelection(); }, -}) +}); codec.export = null; -codec.rebuild = function(model_id: string, pose?: string) { +codec.rebuild = function (model_id: string, pose?: string) { let [preset_id, variant] = model_id.split('.'); let preset = skin_presets[preset_id]; - let model_raw = preset.model || (variant == 'java' ? preset.model_java : preset.model_bedrock) || preset.variants[variant].model; + let model_raw = + preset.model || + (variant == 'java' ? preset.model_java : preset.model_bedrock) || + preset.variants[variant].model; let model = JSON.parse(model_raw); // @ts-ignore codec.parse(model, undefined, true, pose && pose !== 'none'); if (pose && pose !== 'none' && pose !== 'natural') { setTimeout(() => { setDefaultPose(pose); - }, 1) + }, 1); } -} - +}; export const format = new ModelFormat('skin', { icon: 'icon-player', @@ -277,13 +281,14 @@ export const format = new ModelFormat('skin', { target: ['Minecraft: Java Edition', 'Minecraft: Bedrock Edition'], format_page: { content: [ - {type: 'h3', text: tl('mode.start.format.informations')}, - {text: `* ${tl('format.skin.info.skin')} - * ${tl('format.skin.info.model')}`.replace(/\t+/g, '') + { type: 'h3', text: tl('mode.start.format.informations') }, + { + text: `* ${tl('format.skin.info.skin')} + * ${tl('format.skin.info.model')}`.replace(/\t+/g, ''), }, - {type: 'h3', text: tl('mode.start.format.resources')}, - {text: `* [Skin Design Tutorial](https://youtu.be/xC81Q3HGraE)`} - ] + { type: 'h3', text: tl('mode.start.format.resources') }, + { text: `* [Skin Design Tutorial](https://youtu.be/xC81Q3HGraE)` }, + ], }, can_convert_to: false, model_identifier: false, @@ -295,15 +300,14 @@ export const format = new ModelFormat('skin', { rotate_cubes: false, edit_mode: false, pose_mode: true, - codec -}) -format.new = function() { + codec, +}); +format.new = function () { skin_dialog.show(); return true; -} +}; skin_presets; - function setDefaultPose(pose_id: string) { let angles = DefaultPoses[pose_id]; loadPose(angles); @@ -316,22 +320,22 @@ function loadPose(pose_data: SkinPoseData) { if (!group.skin_original_origin) return; let offset = group.origin.slice().V3_subtract(group.skin_original_origin); group.origin.V3_set(group.skin_original_origin); - }) + }); for (let name in pose_data) { let group = Group.all.find(g => g.name == name || g.name.replace(/\s/g, '') == name); if (group) { if (pose_data[name] instanceof Array) { - group.extend({rotation: pose_data[name]}); + group.extend({ rotation: pose_data[name] }); } else { - group.extend({rotation: pose_data[name].rotation}); + group.extend({ rotation: pose_data[name].rotation }); group.origin.V3_add(pose_data[name].offset); } } } Canvas.updateView({ groups: Group.all, - group_aspects: {transform: true} - }) + group_aspects: { transform: true }, + }); } function getPoseData(): SkinPoseData { const data: SkinPoseData = {}; @@ -341,8 +345,9 @@ function getPoseData(): SkinPoseData { let rotation = group.rotation.slice() as ArrayVector3; if (offset.allEqual(0) == false) { data[group.name] = { - offset, rotation - } + offset, + rotation, + }; } else if (rotation.allEqual(0) == false) { data[group.name] = rotation; } @@ -350,14 +355,20 @@ function getPoseData(): SkinPoseData { return data; } -export function generateTemplate(width = 64, height = 64, cubes, name = 'name', eyes, layer_template): Texture { - +export function generateTemplate( + width = 64, + height = 64, + cubes, + name = 'name', + eyes, + layer_template +): Texture { let texture = new Texture({ internal: true, - name: name+'.png' - }) + name: name + '.png', + }); - let canvas = document.createElement('canvas') + let canvas = document.createElement('canvas'); let ctx = canvas.getContext('2d'); canvas.width = width; canvas.height = height; @@ -366,26 +377,32 @@ export function generateTemplate(width = 64, height = 64, cubes, name = 'name', Cube.all.forEach((cube, i) => { let template_cube = cubes[i]; if (layer_template || !template_cube.layer) { - TextureGenerator.paintCubeBoxTemplate(cube, texture, canvas, null, template_cube.layer); + TextureGenerator.paintCubeBoxTemplate( + cube, + texture, + canvas, + null, + template_cube.layer + ); } - }) + }); } else if (cubes[0] && !cubes[0].layer) { ctx.fillStyle = TextureGenerator.face_data.up.c1; - ctx.fillRect(0, 0, width, height) + ctx.fillRect(0, 0, width, height); ctx.fillStyle = TextureGenerator.face_data.up.c2; - ctx.fillRect(1, 1, width-2, height-2) + ctx.fillRect(1, 1, width - 2, height - 2); } if (eyes) { - let res_multiple = canvas.width/Project.texture_width; + let res_multiple = canvas.width / Project.texture_width; ctx.fillStyle = '#cdefff'; eyes.forEach(eye => { ctx.fillRect( - eye[0]*res_multiple, - eye[1]*res_multiple, - (eye[2]||2)*res_multiple, - (eye[3]||2)*res_multiple - ) - }) + eye[0] * res_multiple, + eye[1] * res_multiple, + (eye[2] || 2) * res_multiple, + (eye[3] || 2) * res_multiple + ); + }); } let dataUrl = canvas.toDataURL(); texture.fromDataURL(dataUrl).add(false); @@ -401,7 +418,7 @@ export const skin_dialog = new Dialog({ model: { label: 'dialog.skin.model', type: 'select', - options: model_options + options: model_options, }, game_edition: { label: 'dialog.skin.variant', @@ -413,37 +430,46 @@ export const skin_dialog = new Dialog({ }, condition(form: Record) { return skin_presets[form.model as string].model_bedrock; - } + }, }, variant: { label: 'dialog.skin.variant', type: 'select', options() { - return (selected_model && skin_presets[selected_model].variants) || {} + return (selected_model && skin_presets[selected_model].variants) || {}; }, condition(form) { return skin_presets[form.model].variants; - } + }, + }, + resolution: { + label: 'dialog.create_texture.resolution', + type: 'select', + value: 1, + options: { + 1: 'generic.default', + 16: '16x', + 32: '32x', + 64: '64x', + 128: '128x', + }, }, - resolution: {label: 'dialog.create_texture.resolution', type: 'select', value: 1, options: { - 1: 'generic.default', - 16: '16x', - 32: '32x', - 64: '64x', - 128: '128x', - }}, resolution_warning: { - type: 'info', text: 'dialog.skin.high_res_texture', - condition: (form) => form.resolution > 16 && (form.model == 'steve' || form.model == 'alex') + type: 'info', + text: 'dialog.skin.high_res_texture', + condition: form => + form.resolution > 16 && (form.model == 'steve' || form.model == 'alex'), }, texture_source: { label: 'dialog.skin.texture_source', type: 'select', options: { template: 'dialog.skin.texture_source.template', - load_texture: navigator.onLine ? 'dialog.skin.texture_source.load_texture' : undefined, + load_texture: navigator.onLine + ? 'dialog.skin.texture_source.load_texture' + : undefined, upload_texture: 'dialog.skin.texture_file', - } + }, }, texture_file: { label: 'dialog.skin.texture_file', @@ -452,10 +478,15 @@ export const skin_dialog = new Dialog({ extensions: ['png'], readtype: 'image', filetype: 'PNG', - return_as: 'file' + return_as: 'file', + }, + pose: { + type: 'checkbox', + label: 'dialog.skin.pose', + value: true, + condition: form => !!skin_presets[form.model].pose, }, - pose: {type: 'checkbox', label: 'dialog.skin.pose', value: true, condition: form => (!!skin_presets[form.model].pose)}, - layer_template: {type: 'checkbox', label: 'dialog.skin.layer_template', value: false} + layer_template: { type: 'checkbox', label: 'dialog.skin.layer_template', value: false }, }, onFormChange(result) { selected_model = result.model as string; @@ -464,26 +495,28 @@ export const skin_dialog = new Dialog({ for (let key in variants) { if (!result.variant || !variants[result.variant as string]) { result.variant = key; - skin_dialog.setFormValues({variant: key}, false); + skin_dialog.setFormValues({ variant: key }, false); break; } } } }, - onConfirm: function(result) { + onConfirm: function (result) { if (result.model == 'flat_texture') { if (result.texture) { Codecs.image.load(result.texture); } else { Formats.image.new(); } - } else { if (newProject(format)) { let preset = skin_presets[result.model]; let raw_model: string; if (preset.model_bedrock) { - raw_model = result.game_edition == 'java_edition' ? preset.model_java : preset.model_bedrock; + raw_model = + result.game_edition == 'java_edition' + ? preset.model_java + : preset.model_bedrock; } else if (preset.variants) { raw_model = preset.variants[result.variant].model; } else { @@ -499,9 +532,15 @@ export const skin_dialog = new Dialog({ texture = result.texture_file; } else if (result.texture_source == 'load_texture') { if (!model.external_textures) { - Blockbench.showQuickMessage('This skin model does not support loading textures from Minecraft at the moment', 3000); + Blockbench.showQuickMessage( + 'This skin model does not support loading textures from Minecraft at the moment', + 3000 + ); } else if (!navigator.onLine) { - Blockbench.showQuickMessage('Failed to load skin texture from Minecraft. Check your internet connection.', 3000); + Blockbench.showQuickMessage( + 'Failed to load skin texture from Minecraft. Check your internet connection.', + 3000 + ); } else { texture = false; } @@ -510,12 +549,13 @@ export const skin_dialog = new Dialog({ codec.parse(model, resolution / 16, texture, result.pose, result.layer_template); Project.skin_model = result.model; if (preset.model_bedrock) { - Project.skin_model += '.' + (result.game_edition == 'java_edition' ? 'java' : 'bedrock'); + Project.skin_model += + '.' + (result.game_edition == 'java_edition' ? 'java' : 'bedrock'); } else if (preset.variants) { Project.skin_model += '.' + result.variant; } if (result.texture_source == 'load_texture' && navigator.onLine) { - MinecraftEULA.promptUser('skin').then(async function(accepted) { + MinecraftEULA.promptUser('skin').then(async function (accepted) { if (accepted != true) return; if (!model.external_textures) return; for (let path of model.external_textures) { @@ -525,7 +565,7 @@ export const skin_dialog = new Dialog({ let dataUrl = (frame.canvas as HTMLCanvasElement).toDataURL(); let texture = new Texture({ internal: true, - name: pathToName(path, true) + name: pathToName(path, true), }); texture.fromDataURL(dataUrl).add(false); }); @@ -538,50 +578,50 @@ export const skin_dialog = new Dialog({ onCancel() { Blockbench.Format = 0; Settings.updateSettingsInProfiles(); - } + }, }); // @ts-ignore format.setup_dialog = skin_dialog; type SkinPose = { - name: string - data: SkinPoseData -} + name: string; + data: SkinPoseData; +}; StateMemory.init('skin_poses', 'array'); -BARS.defineActions(function() { +BARS.defineActions(function () { new Mode('pose', { icon: 'emoji_people', default_tool: 'rotate_tool', category: 'navigate', condition: () => Format && Format.pose_mode, - }) + }); new Action('toggle_skin_layer', { icon: 'layers_clear', category: 'edit', - condition: {formats: ['skin']}, + condition: { formats: ['skin'] }, click: function () { let edited = []; Cube.all.forEach(cube => { if (cube.name.toLowerCase().includes('layer')) { edited.push(cube); } - }) + }); if (!edited.length) return; - Undo.initEdit({elements: edited}); + Undo.initEdit({ elements: edited }); let value = !edited[0].visibility; edited.forEach(cube => { cube.visibility = value; - }) + }); Undo.finishEdit('Toggle skin layer'); - Canvas.updateVisibility() - } - }) + Canvas.updateVisibility(); + }, + }); new Action('convert_minecraft_skin_variant', { icon: 'compare_arrows', category: 'edit', - condition: {formats: ['skin'], method: () => Group.all.find(g => g.name == 'Right Arm')}, + condition: { formats: ['skin'], method: () => Group.all.find(g => g.name == 'Right Arm') }, click() { let is_slim = Cube.all.find(c => c.name.match(/arm/i)).size(0) == 3; new Dialog('convert_minecraft_skin_variant', { @@ -594,134 +634,159 @@ BARS.defineActions(function() { options: { steve: skin_presets.steve.display_name, alex: skin_presets.alex.display_name, - } + }, }, adjust_texture: { label: 'dialog.convert_skin.adjust_texture', type: 'checkbox', value: true, - } + }, }, onConfirm(result) { - let right_arm = Group.all.find(g => g.name == 'Right Arm')?.children?.filter(el => el instanceof Cube) ?? []; - let left_arm = Group.all.find(g => g.name == 'Left Arm')?.children?.filter(el => el instanceof Cube) ?? []; + let right_arm = + Group.all + .find(g => g.name == 'Right Arm') + ?.children?.filter(el => el instanceof Cube) ?? []; + let left_arm = + Group.all + .find(g => g.name == 'Left Arm') + ?.children?.filter(el => el instanceof Cube) ?? []; let elements = right_arm.concat(left_arm); - Undo.initEdit({elements}); + Undo.initEdit({ elements }); for (let cube of right_arm) { cube.to[0] = result.model == 'alex' ? 7 : 8; } for (let cube of left_arm) { cube.from[0] = result.model == 'alex' ? -7 : -8; } - Canvas.updateView({elements: right_arm.concat(left_arm), element_aspects: {geometry: true, uv: true}, selection: true}); + Canvas.updateView({ + elements: right_arm.concat(left_arm), + element_aspects: { geometry: true, uv: true }, + selection: true, + }); Undo.finishEdit('Convert Minecraft skin model'); if (result.adjust_texture) { - let textures = Texture.all.filter(tex => tex.selected || tex.multi_selected); - if (!textures.length) textures = [Texture.getDefault()] + let textures = Texture.all.filter( + tex => tex.selected || tex.multi_selected + ); + if (!textures.length) textures = [Texture.getDefault()]; if (!textures[0]) return; - + const arm_uv_positions: [number, number][] = [ [40, 16], [40, 32], [32, 48], [48, 48], ]; - type Translation = {area: [number, number, number, number], offset: [number, number]}; + type Translation = { + area: [number, number, number, number]; + offset: [number, number]; + }; let translations: Translation[]; if (result.model == 'alex') { translations = [ - {area: [6, 0, 10, 16], offset: [-1, 0]}, - {area: [9, 0, 2, 4], offset: [-1, 0]}, - {area: [13, 4, 2, 12], offset: [-1, 0]}, + { area: [6, 0, 10, 16], offset: [-1, 0] }, + { area: [9, 0, 2, 4], offset: [-1, 0] }, + { area: [13, 4, 2, 12], offset: [-1, 0] }, ]; } else { translations = [ - {area: [5, 0, 10, 16], offset: [1, 0]}, - {area: [9, 0, 2, 4], offset: [1, 0]}, - {area: [13, 4, 2, 12], offset: [1, 0]}, + { area: [5, 0, 10, 16], offset: [1, 0] }, + { area: [9, 0, 2, 4], offset: [1, 0] }, + { area: [13, 4, 2, 12], offset: [1, 0] }, ]; } - Undo.initEdit({textures, bitmap: true}); + Undo.initEdit({ textures, bitmap: true }); for (let texture of textures) { if (texture.layers_enabled) { texture.layers_enabled = false; texture.selected_layer = null; texture.layers.empty(); } - texture.edit(() => { - let ctx = texture.ctx; - for (let position of arm_uv_positions) { - for (let translation of translations) { - let data = ctx.getImageData( - position[0] + translation.area[0], - position[1] + translation.area[1], - translation.area[2], - translation.area[3], - ); - ctx.putImageData(data, - position[0] + translation.area[0] + translation.offset[0], - position[1] + translation.area[1] + translation.offset[1] - ); - } - if (result.model == 'alex') { - ctx.clearRect(position[0] + 10, position[1] + 0, 2, 4); - ctx.clearRect(position[0] + 14, position[1] + 4, 2, 12); + texture.edit( + () => { + let ctx = texture.ctx; + for (let position of arm_uv_positions) { + for (let translation of translations) { + let data = ctx.getImageData( + position[0] + translation.area[0], + position[1] + translation.area[1], + translation.area[2], + translation.area[3] + ); + ctx.putImageData( + data, + position[0] + + translation.area[0] + + translation.offset[0], + position[1] + + translation.area[1] + + translation.offset[1] + ); + } + if (result.model == 'alex') { + ctx.clearRect(position[0] + 10, position[1] + 0, 2, 4); + ctx.clearRect(position[0] + 14, position[1] + 4, 2, 12); + } } - } - }, {no_undo: true}); + }, + { no_undo: true } + ); } UVEditor.vue.layer = null; updateSelection(); Undo.finishEdit('Convert Minecraft skin texture'); } - } + }, }).show(); - } - }) + }, + }); new Action('export_minecraft_skin', { icon: 'icon-player', category: 'file', condition: () => Format == format && Texture.all[0], click: function () { Texture.all[0].save(true); - } - }) - + }, + }); + let explode_skin_model = new Toggle('explode_skin_model', { icon: () => 'open_in_full', category: 'edit', - condition: {formats: ['skin']}, + condition: { formats: ['skin'] }, default: false, onChange(exploded_view) { - Undo.initEdit({elements: Cube.all, exploded_view: !exploded_view}); + Undo.initEdit({ elements: Cube.all, exploded_view: !exploded_view }); Cube.all.forEach(cube => { let center = [ cube.from[0] + (cube.to[0] - cube.from[0]) / 2, cube.from[1], cube.from[2] + (cube.to[2] - cube.from[2]) / 2, - ] + ]; let offset = cube.name.toLowerCase().includes('leg') ? 1 : 0.5; - center.V3_multiply(exploded_view ? offset : -offset/(1+offset)); + center.V3_multiply(exploded_view ? offset : -offset / (1 + offset)); cube.from.V3_add(center as ArrayVector3); cube.to.V3_add(center as ArrayVector3); - }) + }); Project.exploded_view = exploded_view; - Undo.finishEdit(exploded_view ? 'Explode skin model' : 'Revert exploding skin model', {elements: Cube.all, exploded_view: exploded_view}); - Canvas.updateView({elements: Cube.all, element_aspects: {geometry: true}}); + Undo.finishEdit(exploded_view ? 'Explode skin model' : 'Revert exploding skin model', { + elements: Cube.all, + exploded_view: exploded_view, + }); + Canvas.updateView({ elements: Cube.all, element_aspects: { geometry: true } }); this.setIcon(this.icon); - } - }) + }, + }); Blockbench.on('select_project', () => { explode_skin_model.value = !!Project.exploded_view; explode_skin_model.updateEnabledState(); - }) + }); - new Action('custom_skin_poses', { icon: 'format_list_bulleted', category: 'view', - condition: {formats: ['skin'], modes: ['pose']}, + condition: { formats: ['skin'], modes: ['pose'] }, click(e) { new Menu(this.children()).open(e.target as HTMLElement); }, @@ -737,71 +802,76 @@ BARS.defineActions(function() { loadPose(pose.data); }, children: [ - {icon: 'update', name: 'action.custom_skin_poses.update', description: 'action.custom_skin_poses.update.desc', click() { - pose.data = getPoseData(); - StateMemory.save('skin_poses'); - }}, - {icon: 'delete', name: 'generic.delete', click() { - memory_list.remove(pose); - StateMemory.save('skin_poses'); - }} - ] - } + { + icon: 'update', + name: 'action.custom_skin_poses.update', + description: 'action.custom_skin_poses.update.desc', + click() { + pose.data = getPoseData(); + StateMemory.save('skin_poses'); + }, + }, + { + icon: 'delete', + name: 'generic.delete', + click() { + memory_list.remove(pose); + StateMemory.save('skin_poses'); + }, + }, + ], + }; options.push(option); - }) - - options.push( - '_', - 'add_custom_skin_pose' - ); + }); + + options.push('_', 'add_custom_skin_pose'); return options; - } - }) + }, + }); new Action('add_custom_skin_pose', { icon: 'add', category: 'view', - condition: {formats: ['skin'], modes: ['pose']}, + condition: { formats: ['skin'], modes: ['pose'] }, click(e) { Blockbench.textPrompt('generic.name', 'new pose', value => { let pose: SkinPose = { name: value, - data: getPoseData() + data: getPoseData(), }; let memory_list = StateMemory.get('skin_poses') as SkinPose[]; memory_list.push(pose); StateMemory.save('skin_poses'); - }) - } - }) -}) + }); + }, + }); +}); // @ts-ignore -Interface.definePanels(function() { +Interface.definePanels(function () { new Panel('skin_pose', { icon: 'icon-player', - condition: {modes: ['pose']}, + condition: { modes: ['pose'] }, default_position: { slot: 'right_bar', float_position: [0, 0], float_size: [300, 80], - height: 80 + height: 80, }, toolbars: [ new Toolbar('skin_pose', { - children: [ - 'custom_skin_poses', - 'add_custom_skin_pose' - ] - }) + children: ['custom_skin_poses', 'add_custom_skin_pose'], + }), ], component: { - data() {return { - pose: 'default' - }}, + data() { + return { + pose: 'default', + }; + }, methods: { setPose(pose) { setDefaultPose(pose); - } + }, }, template: `
@@ -815,10 +885,10 @@ Interface.definePanels(function() {
  • - ` - } - }) -}) + `, + }, + }); +}); // Source: https://github.com/Mojang/bedrock-samples/, licensed under the Minecraft EULA // With modifications for usability @@ -905,7 +975,7 @@ skin_presets.steve = { ] } ] - }` + }`, }; skin_presets.alex = { display_name: 'Player - Slim', @@ -1069,7 +1139,7 @@ skin_presets.alex = { ] } ] - }` + }`, }; skin_presets.flat_texture = { @@ -1095,7 +1165,7 @@ skin_presets.flat_texture = { ] } ] - }` + }`, }; skin_presets.block = { display_name: 'Block', @@ -1123,7 +1193,7 @@ skin_presets.block = { ] } ] - }` + }`, }; skin_presets.allay = { @@ -1198,8 +1268,8 @@ skin_presets.allay = { ] } ] - }` -} + }`, +}; skin_presets.armadillo = { display_name: 'Armadillo', model: `{ @@ -1289,8 +1359,8 @@ skin_presets.armadillo = { ] } ] - }` -} + }`, +}; skin_presets.armor_main = { display_name: 'Armor (Main)', pose: true, @@ -1355,7 +1425,7 @@ skin_presets.armor_main = { ] } ] - }` + }`, }; skin_presets.armor_leggings = { display_name: 'Armor (Leggings)', @@ -1393,7 +1463,7 @@ skin_presets.armor_leggings = { ] } ] - }` + }`, }; skin_presets.armor_stand = { display_name: 'Armor Stand', @@ -1469,7 +1539,7 @@ skin_presets.armor_stand = { ] } ] - }` + }`, }; skin_presets.axolotl = { display_name: 'Axolotl', @@ -1574,7 +1644,7 @@ skin_presets.axolotl = { ] } ] - }` + }`, }; skin_presets.bamboo_raft = { display_name: 'Bamboo Raft', @@ -1612,7 +1682,7 @@ skin_presets.bamboo_raft = { ] } ] - }` + }`, }; skin_presets.banner = { display_name: 'Banner', @@ -1639,12 +1709,12 @@ skin_presets.banner = { ] } ] - }` + }`, }; skin_presets.bat = { display_name: 'Bat', pose: true, - variants: { + variants: { new: { name: 'New', model: `{ @@ -1728,7 +1798,7 @@ skin_presets.bat = { ] } ] - }` + }`, }, old: { name: 'Classic', @@ -1810,9 +1880,9 @@ skin_presets.bat = { ] } ] - }` + }`, }, - } + }, }; skin_presets.bed = { display_name: 'Bed', @@ -1893,7 +1963,7 @@ skin_presets.bed = { ] } ] - }` + }`, }; skin_presets.bee = { display_name: 'Bee', @@ -1967,7 +2037,7 @@ skin_presets.bee = { ] } ] - }` + }`, }; skin_presets.bell = { display_name: 'Bell', @@ -1986,7 +2056,7 @@ skin_presets.bell = { ] } ] - }` + }`, }; skin_presets.blaze = { display_name: 'Blaze', @@ -2091,7 +2161,7 @@ skin_presets.blaze = { ] } ] - }` + }`, }; skin_presets.boat = { display_name: 'Boat', @@ -2166,7 +2236,7 @@ skin_presets.boat = { ] } ] - }` + }`, }; skin_presets.bogged = { display_name: 'Bogged', @@ -2259,7 +2329,7 @@ skin_presets.bogged = { ] } ] - }` + }`, }; skin_presets.bogged_layer = { display_name: 'Bogged/Stray Layer', @@ -2326,7 +2396,7 @@ skin_presets.bogged_layer = { ] } ] - }` + }`, }; skin_presets.breeze = { display_name: 'Breeze', @@ -2373,8 +2443,8 @@ skin_presets.breeze = { ] } ] - }` -} + }`, +}; skin_presets.breeze_tornado = { display_name: 'Breeze Tornado', model: `{ @@ -2416,8 +2486,8 @@ skin_presets.breeze_tornado = { ] } ] - }` -} + }`, +}; skin_presets.camel = { display_name: 'Camel', model: `{ @@ -2549,8 +2619,8 @@ skin_presets.camel = { ] } ] - }` -} + }`, +}; skin_presets.cat = { display_name: 'Cat', model: `{ @@ -2634,7 +2704,7 @@ skin_presets.cat = { ] } ] - }` + }`, }; skin_presets.cape_elytra = { display_name: 'Cape + Elytra', @@ -2674,7 +2744,7 @@ skin_presets.cape_elytra = { ] } ] - }` + }`, }; skin_presets.chest = { display_name: 'Chest', @@ -2695,7 +2765,7 @@ skin_presets.chest = { ] } ] - }` + }`, }; skin_presets.chest_left = { display_name: 'Chest Left', @@ -2716,7 +2786,7 @@ skin_presets.chest_left = { ] } ] - }` + }`, }; skin_presets.chest_right = { display_name: 'Chest Right', @@ -2737,7 +2807,7 @@ skin_presets.chest_right = { ] } ] - }` + }`, }; skin_presets.chicken = { display_name: 'Chicken', @@ -2810,7 +2880,7 @@ skin_presets.chicken = { ] } ] - }` + }`, }, cold: { name: 'Cold', @@ -2881,9 +2951,9 @@ skin_presets.chicken = { ] } ] - }` - } - } + }`, + }, + }, }; skin_presets.cod = { display_name: 'Cod', @@ -2943,7 +3013,7 @@ skin_presets.cod = { "pivot": [0, 0, 0] } ] - }` + }`, }; skin_presets.copper_golem = { display_name: 'Copper Golem', @@ -3017,8 +3087,8 @@ skin_presets.copper_golem = { ] } ] - }` -} + }`, +}; skin_presets.cow = { display_name: 'Cow', variants: { @@ -3093,11 +3163,11 @@ skin_presets.cow = { ] } ] - }` + }`, }, cold: { name: 'Cold', - model: `{ + model: `{ "name": "cow_cold", "external_textures": ["entity/cow/cow_cold.png"], "texturewidth": 64, @@ -3167,11 +3237,11 @@ skin_presets.cow = { ] } ] - }` + }`, }, warm: { name: 'Warm', - model: `{ + model: `{ "name": "cow_warm", "external_textures": ["entity/cow/cow_warm.png"], "texturewidth": 64, @@ -3242,11 +3312,11 @@ skin_presets.cow = { ] } ] - }` + }`, }, old: { name: 'Classic', - model: `{ + model: `{ "name": "cow", "texturewidth": 64, "textureheight": 32, @@ -3304,9 +3374,9 @@ skin_presets.cow = { ] } ] - }` - } - } + }`, + }, + }, }; skin_presets.creaking = { display_name: 'Creaking', @@ -3389,7 +3459,7 @@ skin_presets.creaking = { ] } ] - }` + }`, }; skin_presets.creeper = { display_name: 'Creeper', @@ -3451,7 +3521,7 @@ skin_presets.creeper = { ] } ] - }` + }`, }; skin_presets.dolphin = { display_name: 'Dolphin', @@ -3607,7 +3677,7 @@ skin_presets.dolphin = { ] } ] - }` + }`, }; skin_presets.enderdragon = { display_name: 'Ender Dragon', @@ -3968,7 +4038,7 @@ skin_presets.enderdragon = { ] } ] - }` + }`, }; skin_presets.enderman = { display_name: 'Enderman', @@ -4023,7 +4093,7 @@ skin_presets.enderman = { ] } ] - }` + }`, }; skin_presets.endermite = { display_name: 'Endermite', @@ -4065,7 +4135,7 @@ skin_presets.endermite = { ] } ] - }` + }`, }; skin_presets.evocation_fang = { display_name: 'Evocation Fang', @@ -4101,7 +4171,7 @@ skin_presets.evocation_fang = { ] } ] - }` + }`, }; skin_presets.evoker = { display_name: 'Evoker', @@ -4180,7 +4250,7 @@ skin_presets.evoker = { ] } ] - }` + }`, }; skin_presets.fox = { display_name: 'Fox', @@ -4307,7 +4377,7 @@ skin_presets.fox = { ] } ] - }` + }`, }; skin_presets.frog = { display_name: 'Frog', @@ -4417,7 +4487,7 @@ skin_presets.frog = { ] } ] - }` + }`, }; skin_presets.ghast = { display_name: 'Ghast', @@ -4512,7 +4582,7 @@ skin_presets.ghast = { ] } ] - }` + }`, }; skin_presets.goat = { display_name: 'Goat', @@ -4580,7 +4650,7 @@ skin_presets.goat = { ] } ] - }` + }`, }; skin_presets.guardian = { display_name: 'Guardian', @@ -4747,7 +4817,7 @@ skin_presets.guardian = { ] } ] - }` + }`, }; skin_presets.happy_ghast = { display_name: 'Happy Ghast', @@ -4843,7 +4913,7 @@ skin_presets.happy_ghast = { ] } ] - }` + }`, }; skin_presets.harness = { display_name: 'Happy Ghast Harness', @@ -4870,7 +4940,7 @@ skin_presets.harness = { ] } ] - }` + }`, }; skin_presets.hoglin = { display_name: 'Hoglin', @@ -4946,7 +5016,7 @@ skin_presets.hoglin = { ] } ] - }` + }`, }; skin_presets.horse = { display_name: 'Horse', @@ -5112,7 +5182,7 @@ skin_presets.horse = { ] } ] - }` + }`, }; skin_presets.irongolem = { display_name: 'Iron Golem', @@ -5173,7 +5243,7 @@ skin_presets.irongolem = { ] } ] - }` + }`, }; skin_presets.llama = { display_name: 'Llama', @@ -5250,7 +5320,7 @@ skin_presets.llama = { ] } ] - }` + }`, }; skin_presets.lavaslime = { display_name: 'Magma Cube', @@ -5290,7 +5360,7 @@ skin_presets.lavaslime = { ] } ] - }` + }`, }, old: { name: 'Classic', @@ -5377,9 +5447,9 @@ skin_presets.lavaslime = { ] } ] - }` - } - } + }`, + }, + }, }; skin_presets.minecart = { display_name: 'Minecart', @@ -5434,7 +5504,7 @@ skin_presets.minecart = { ] } ] - }` + }`, }; skin_presets.panda = { display_name: 'Panda', @@ -5495,7 +5565,7 @@ skin_presets.panda = { ] } ] - }` + }`, }; skin_presets.parrot = { display_name: 'Parrot', @@ -5565,7 +5635,7 @@ skin_presets.parrot = { ] } ] - }` + }`, }; skin_presets.phantom = { display_name: 'Phantom', @@ -5651,7 +5721,7 @@ skin_presets.phantom = { ] } ] - }` + }`, }; skin_presets.pig = { display_name: 'Pig', @@ -5725,7 +5795,7 @@ skin_presets.pig = { ] } ] - }` + }`, }, old: { name: 'Classic', @@ -5786,9 +5856,9 @@ skin_presets.pig = { ] } ] - }` - } - } + }`, + }, + }, }; skin_presets.piglin = { display_name: 'Piglin', @@ -5875,7 +5945,7 @@ skin_presets.piglin = { "pivot": [6, 15, 1] } ] - }` + }`, }; skin_presets.pillager = { display_name: 'Pillager', @@ -5944,7 +6014,7 @@ skin_presets.pillager = { ] } ] - }` + }`, }; skin_presets.polarbear = { display_name: 'Polarbear', @@ -6003,7 +6073,7 @@ skin_presets.polarbear = { ] } ] - }` + }`, }; skin_presets.pufferfish = { display_name: 'Pufferfish', @@ -6266,7 +6336,7 @@ skin_presets.pufferfish = { ] } ] - }` + }`, }; skin_presets.rabbit = { display_name: 'Rabbit', @@ -6381,7 +6451,7 @@ skin_presets.rabbit = { ] } ] - }` + }`, }; skin_presets.ravager = { display_name: 'Ravager', @@ -6463,7 +6533,7 @@ skin_presets.ravager = { ] } ] - }` + }`, }; skin_presets.salmon = { display_name: 'Salmon', @@ -6539,7 +6609,7 @@ skin_presets.salmon = { ] } ] - }` + }`, }; skin_presets.sheep = { display_name: 'Sheep', @@ -6598,7 +6668,7 @@ skin_presets.sheep = { ] } ] - }` + }`, }; skin_presets.shield = { display_name: 'Shield', @@ -6617,7 +6687,7 @@ skin_presets.shield = { ] } ] - }` + }`, }; skin_presets.shulker = { display_name: 'Shulker', @@ -6651,7 +6721,7 @@ skin_presets.shulker = { ] } ] - }` + }`, }; skin_presets.shulker_bullet = { display_name: 'Shulker Bullet', @@ -6671,7 +6741,7 @@ skin_presets.shulker_bullet = { ] } ] - }` + }`, }; skin_presets.silverfish = { display_name: 'Silverfish', @@ -6761,7 +6831,7 @@ skin_presets.silverfish = { ] } ] - }` + }`, }; skin_presets.skeleton = { display_name: 'Skeleton/Stray', @@ -6831,7 +6901,7 @@ skin_presets.skeleton = { ] } ] - }` + }`, }; skin_presets.slime = { display_name: 'Slime', @@ -6862,7 +6932,7 @@ skin_presets.slime = { ] } ] - }` + }`, }; skin_presets.sniffer = { display_name: 'Sniffer', @@ -6980,8 +7050,8 @@ skin_presets.sniffer = { ] } ] - }` -} + }`, +}; skin_presets.snowgolem = { display_name: 'Snowgolem', model: `{ @@ -7036,7 +7106,7 @@ skin_presets.snowgolem = { ] } ] - }` + }`, }; skin_presets.spider = { display_name: 'Spider', @@ -7131,7 +7201,7 @@ skin_presets.spider = { ] } ] - }` + }`, }; skin_presets.spyglass = { display_name: 'Spyglass', @@ -7150,8 +7220,8 @@ skin_presets.spyglass = { ] } ] - }` -} + }`, +}; skin_presets.squid = { display_name: 'Squid', model: `{ @@ -7235,7 +7305,7 @@ skin_presets.squid = { ] } ] - }` + }`, }; skin_presets.strider = { display_name: 'Strider', @@ -7329,7 +7399,7 @@ skin_presets.strider = { ] } ] - }` + }`, }; skin_presets.tadpole = { display_name: 'Tadpole', @@ -7364,7 +7434,7 @@ skin_presets.tadpole = { ] } ] - }` + }`, }; skin_presets.tropicalfish_a = { display_name: 'Tropicalfish A', @@ -7409,7 +7479,7 @@ skin_presets.tropicalfish_a = { ] } ] - }` + }`, }; skin_presets.tropicalfish_b = { display_name: 'Tropicalfish B', @@ -7455,7 +7525,7 @@ skin_presets.tropicalfish_b = { ] } ] - }` + }`, }; skin_presets.turtle = { display_name: 'Turtle', @@ -7582,7 +7652,7 @@ skin_presets.turtle = { ] } ] - }` + }`, }; skin_presets.vex = { display_name: 'Vex', @@ -7650,7 +7720,7 @@ skin_presets.vex = { ] } ] - }` + }`, }; skin_presets.villager = { display_name: 'Villager (Old)', @@ -7708,7 +7778,7 @@ skin_presets.villager = { ] } ] - }` + }`, }; skin_presets.villager_v2 = { display_name: 'Villager (New)', @@ -7862,7 +7932,7 @@ skin_presets.villager_v2 = { ] } ] - }` + }`, }; skin_presets.vindicator = { display_name: 'Vindicator', @@ -7935,7 +8005,7 @@ skin_presets.vindicator = { ] } ] - }` + }`, }; skin_presets.warden = { display_name: 'Warden', @@ -8033,8 +8103,8 @@ skin_presets.warden = { ] } ] - }` -} + }`, +}; skin_presets.witch = { display_name: 'Witch', model: `{ @@ -8127,7 +8197,7 @@ skin_presets.witch = { ] } ] - }` + }`, }; skin_presets.witherBoss = { display_name: 'Wither', @@ -8189,7 +8259,7 @@ skin_presets.witherBoss = { ] } ] - }` + }`, }; skin_presets.wolf = { display_name: 'Wolf', @@ -8262,7 +8332,7 @@ skin_presets.wolf = { ] } ] - }` + }`, }; skin_presets.zombie = { display_name: 'Zombie', @@ -8390,7 +8460,7 @@ skin_presets.zombie = { ] } ] - }` + }`, }; skin_presets.zombie_villager_1 = { display_name: 'Zombie Villager (Old)', @@ -8451,7 +8521,7 @@ skin_presets.zombie_villager_1 = { ] } ] - }` + }`, }; skin_presets.zombie_villager_2 = { display_name: 'Zombie Villager (New)', @@ -8606,7 +8676,7 @@ skin_presets.zombie_villager_2 = { ] } ] - }` + }`, }; for (let id in skin_presets) { diff --git a/js/io/share.ts b/js/io/share.ts index 3b64fc47c..3f014b24b 100644 --- a/js/io/share.ts +++ b/js/io/share.ts @@ -1,16 +1,16 @@ -import { Blockbench } from "../api"; -import { Dialog } from "../interface/dialog"; -import { FormInputType } from "../interface/form"; -import { settings } from "../interface/settings"; -import { BARS } from "../interface/toolbars"; -import { tl } from "../languages"; -import { Mesh } from "../outliner/mesh"; -import { Outliner } from "../outliner/outliner"; -import { ReferenceImage } from "../preview/reference_images"; -import { capitalizeFirstLetter } from "../util/util"; -import { Codecs } from "./codec"; +import { Blockbench } from '../api'; +import { Dialog } from '../interface/dialog'; +import { FormInputType } from '../interface/form'; +import { settings } from '../interface/settings'; +import { BARS } from '../interface/toolbars'; +import { tl } from '../languages'; +import { Mesh } from '../outliner/mesh'; +import { Outliner } from '../outliner/outliner'; +import { ReferenceImage } from '../preview/reference_images'; +import { capitalizeFirstLetter } from '../util/util'; +import { Codecs } from './codec'; -BARS.defineActions(function() { +BARS.defineActions(function () { function uploadSketchfabModel() { if (Outliner.elements.length === 0 || !Format) { return; @@ -19,93 +19,137 @@ BARS.defineActions(function() { if (Format.id !== 'free') tag_suggestions.push('minecraft'); if (Format.id === 'skin') tag_suggestions.push('skin'); if (!Mesh.all.length) tag_suggestions.push('voxel'); - let clean_project_name = Project.name.toLowerCase().replace(/[_.-]+/g, '-').replace(/[^a-z0-9-]+/, '').replace(/-geo/, ''); + let clean_project_name = Project.name + .toLowerCase() + .replace(/[_.-]+/g, '-') + .replace(/[^a-z0-9-]+/, '') + .replace(/-geo/, ''); if (Project.name) tag_suggestions.push(clean_project_name); - if (clean_project_name.includes('-')) tag_suggestions.safePush(...clean_project_name.split('-').filter(s => s.length > 2 && s != 'geo').reverse()); - + if (clean_project_name.includes('-')) + tag_suggestions.safePush( + ...clean_project_name + .split('-') + .filter(s => s.length > 2 && s != 'geo') + .reverse() + ); + let categories = { - "": "-", - "animals-pets": "Animals & Pets", - "architecture": "Architecture", - "art-abstract": "Art & Abstract", - "cars-vehicles": "Cars & Vehicles", - "characters-creatures": "Characters & Creatures", - "cultural-heritage-history": "Cultural Heritage & History", - "electronics-gadgets": "Electronics & Gadgets", - "fashion-style": "Fashion & Style", - "food-drink": "Food & Drink", - "furniture-home": "Furniture & Home", - "music": "Music", - "nature-plants": "Nature & Plants", - "news-politics": "News & Politics", - "people": "People", - "places-travel": "Places & Travel", - "science-technology": "Science & Technology", - "sports-fitness": "Sports & Fitness", - "weapons-military": "Weapons & Military", - } - + '': '-', + 'animals-pets': 'Animals & Pets', + architecture: 'Architecture', + 'art-abstract': 'Art & Abstract', + 'cars-vehicles': 'Cars & Vehicles', + 'characters-creatures': 'Characters & Creatures', + 'cultural-heritage-history': 'Cultural Heritage & History', + 'electronics-gadgets': 'Electronics & Gadgets', + 'fashion-style': 'Fashion & Style', + 'food-drink': 'Food & Drink', + 'furniture-home': 'Furniture & Home', + music: 'Music', + 'nature-plants': 'Nature & Plants', + 'news-politics': 'News & Politics', + people: 'People', + 'places-travel': 'Places & Travel', + 'science-technology': 'Science & Technology', + 'sports-fitness': 'Sports & Fitness', + 'weapons-military': 'Weapons & Military', + }; + var dialog = new Dialog('sketchfab_uploader', { title: 'dialog.sketchfab_uploader.title', width: 640, form: { - token: {label: 'dialog.sketchfab_uploader.token', value: settings.sketchfab_token.value, type: 'password'}, - about_token: {type: 'info', text: tl('dialog.sketchfab_uploader.about_token', ['[sketchfab.com/settings/password](https://sketchfab.com/settings/password)'])}, - name: {label: 'dialog.sketchfab_uploader.name', value: capitalizeFirstLetter(Project.name.replace(/\..+/, '').replace(/[_.-]/g, ' '))}, - description: {label: 'dialog.sketchfab_uploader.description', type: 'textarea'}, - category1: {label: 'dialog.sketchfab_uploader.category', type: 'select', options: categories, value: ''}, - category2: {label: 'dialog.sketchfab_uploader.category2', type: 'select', options: categories, value: ''}, - tags: {label: 'dialog.sketchfab_uploader.tags', placeholder: 'Tag1 Tag2'}, - tag_suggestions: {label: 'dialog.sketchfab_uploader.suggested_tags', type: 'buttons', buttons: tag_suggestions, click(index) { - let {tags} = dialog.getFormResult(); - let new_tag = tag_suggestions[index]; - if (!(tags as string).split(/\s/g).includes(new_tag)) { - tags += ' ' + new_tag; - dialog.setFormValues({tags}); - } - }}, - animations: {label: 'dialog.sketchfab_uploader.animations', value: true, type: 'checkbox', condition: (Format.animation_mode && Animator.animations.length)}, - draft: {label: 'dialog.sketchfab_uploader.draft', type: 'checkbox', value: true}, + token: { + label: 'dialog.sketchfab_uploader.token', + value: settings.sketchfab_token.value, + type: 'password', + }, + about_token: { + type: 'info', + text: tl('dialog.sketchfab_uploader.about_token', [ + '[sketchfab.com/settings/password](https://sketchfab.com/settings/password)', + ]), + }, + name: { + label: 'dialog.sketchfab_uploader.name', + value: capitalizeFirstLetter( + Project.name.replace(/\..+/, '').replace(/[_.-]/g, ' ') + ), + }, + description: { label: 'dialog.sketchfab_uploader.description', type: 'textarea' }, + category1: { + label: 'dialog.sketchfab_uploader.category', + type: 'select', + options: categories, + value: '', + }, + category2: { + label: 'dialog.sketchfab_uploader.category2', + type: 'select', + options: categories, + value: '', + }, + tags: { label: 'dialog.sketchfab_uploader.tags', placeholder: 'Tag1 Tag2' }, + tag_suggestions: { + label: 'dialog.sketchfab_uploader.suggested_tags', + type: 'buttons', + buttons: tag_suggestions, + click(index) { + let { tags } = dialog.getFormResult(); + let new_tag = tag_suggestions[index]; + if (!(tags as string).split(/\s/g).includes(new_tag)) { + tags += ' ' + new_tag; + dialog.setFormValues({ tags }); + } + }, + }, + animations: { + label: 'dialog.sketchfab_uploader.animations', + value: true, + type: 'checkbox', + condition: Format.animation_mode && Animator.animations.length, + }, + draft: { label: 'dialog.sketchfab_uploader.draft', type: 'checkbox', value: true }, divider: '_', - private: {label: 'dialog.sketchfab_uploader.private', type: 'checkbox'}, - password: {label: 'dialog.sketchfab_uploader.password', type: 'password'}, + private: { label: 'dialog.sketchfab_uploader.private', type: 'checkbox' }, + password: { label: 'dialog.sketchfab_uploader.password', type: 'password' }, }, onConfirm(formResult) { - if (!formResult.token || !formResult.name) { - Blockbench.showQuickMessage('message.sketchfab.name_or_token', 1800) + Blockbench.showQuickMessage('message.sketchfab.name_or_token', 1800); return; } if (!(formResult.tags as string).split(' ').includes('blockbench')) { formResult.tags += ' blockbench'; } - var data = new FormData() - data.append('token', formResult.token as string) - data.append('name', formResult.name as string) - data.append('description', formResult.description as string) - data.append('tags', formResult.tags as string) - data.append('isPublished', (!formResult.draft as boolean).toString()) + var data = new FormData(); + data.append('token', formResult.token as string); + data.append('name', formResult.name as string); + data.append('description', formResult.description as string); + data.append('tags', formResult.tags as string); + data.append('isPublished', (!formResult.draft as boolean).toString()); //data.append('background', JSON.stringify({color: '#00ff00'})) - data.append('private', (formResult.private as boolean).toString()) - data.append('password', formResult.password as string) - data.append('source', 'blockbench') - + data.append('private', (formResult.private as boolean).toString()); + data.append('password', formResult.password as string); + data.append('source', 'blockbench'); + if (formResult.category1 || formResult.category2) { let selected_categories: string[] = []; - if (formResult.category1) selected_categories.push(formResult.category1 as string); - if (formResult.category2) selected_categories.push(formResult.category2 as string); + if (formResult.category1) + selected_categories.push(formResult.category1 as string); + if (formResult.category2) + selected_categories.push(formResult.category2 as string); data.append('categories', selected_categories.join(' ')); } - + settings.sketchfab_token.set(formResult.token); - - Codecs.gltf.compile({animations: formResult.animations}).then(content => { - - var blob = new Blob([content], {type: "text/plain;charset=utf-8"}); - var file = new File([blob], 'model.gltf') - - data.append('modelFile', file) - + + Codecs.gltf.compile({ animations: formResult.animations }).then(content => { + var blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); + var file = new File([blob], 'model.gltf'); + + data.append('modelFile', file); + $.ajax({ url: 'https://api.sketchfab.com/v3/models', data: data, @@ -113,18 +157,26 @@ BARS.defineActions(function() { contentType: false, processData: false, type: 'POST', - success: function(response) { - let url = `https://sketchfab.com/models/${response.uid}` + success: function (response) { + let url = `https://sketchfab.com/models/${response.uid}`; new Dialog('sketchfab_link', { title: tl('message.sketchfab.success'), icon: 'icon-sketchfab', form: { - message: {type: 'info', text: `[${formResult.name} on Sketchfab](${url})`}, - link: {type: 'text', value: url, readonly: true, share_text: true} - } + message: { + type: 'info', + text: `[${formResult.name} on Sketchfab](${url})`, + }, + link: { + type: 'text', + value: url, + readonly: true, + share_text: true, + }, + }, }).show(); }, - error: function(response) { + error: function (response) { let response_types = { [400]: 'Bad Request', [401]: 'Unauthorized', @@ -135,26 +187,30 @@ BARS.defineActions(function() { [407]: 'Proxy Authentication Required', [408]: 'Request Timeout', [415]: 'Unsupported File Type', - } - Blockbench.showQuickMessage(tl('message.sketchfab.error') + `: Error ${response.status} - ${response_types[response.status]||''}`, 1500) + }; + Blockbench.showQuickMessage( + tl('message.sketchfab.error') + + `: Error ${response.status} - ${response_types[response.status] || ''}`, + 1500 + ); console.error(response); - } - }) - }) - - dialog.hide() - } - }) - dialog.show() + }, + }); + }); + + dialog.hide(); + }, + }); + dialog.show(); } new Action('upload_sketchfab', { icon: 'icon-sketchfab', category: 'file', condition: () => Project && Outliner.elements.length, click() { - uploadSketchfabModel() - } - }) + uploadSketchfabModel(); + }, + }); new Action('share_model', { icon: 'share', @@ -162,7 +218,7 @@ BARS.defineActions(function() { async click() { let thumbnail = await new Promise(resolve => { // @ts-ignore - Preview.selected.screenshot({width: 640, height: 480}, resolve); + Preview.selected.screenshot({ width: 640, height: 480 }, resolve); }); let image = new Image(); image.src = thumbnail as string; @@ -175,18 +231,35 @@ BARS.defineActions(function() { id: 'share_model', title: 'dialog.share_model.title', form: { - name: {type: 'text', label: 'generic.name', value: Project.name}, - expire_time: {label: 'dialog.share_model.expire_time', type: 'select', default: '2d', options: { - '10m': tl('dates.minutes', [10]), - '1h': tl('dates.hour', [1]), - '1d': tl('dates.day', [1]), - '2d': tl('dates.days', [2]), - '1w': tl('dates.week', [1]), - '2w': tl('dates.weeks', [2]), - }}, - info: {type: 'info', text: 'The model and thumbnail will be stored on the Blockbench servers for the duration specified above. [Learn more](https://blockbench.net/blockbench-model-sharing-service/)'}, - reference_images: {type: 'checkbox', label: 'dialog.share_model.reference_images', value: true, condition: () => ReferenceImage.current_project.length}, - thumbnail: {type: 'checkbox', label: 'dialog.share_model.thumbnail', value: true}, + name: { type: 'text', label: 'generic.name', value: Project.name }, + expire_time: { + label: 'dialog.share_model.expire_time', + type: 'select', + default: '2d', + options: { + '10m': tl('dates.minutes', [10]), + '1h': tl('dates.hour', [1]), + '1d': tl('dates.day', [1]), + '2d': tl('dates.days', [2]), + '1w': tl('dates.week', [1]), + '2w': tl('dates.weeks', [2]), + }, + }, + info: { + type: 'info', + text: 'The model and thumbnail will be stored on the Blockbench servers for the duration specified above. [Learn more](https://blockbench.net/blockbench-model-sharing-service/)', + }, + reference_images: { + type: 'checkbox', + label: 'dialog.share_model.reference_images', + value: true, + condition: () => ReferenceImage.current_project.length, + }, + thumbnail: { + type: 'checkbox', + label: 'dialog.share_model.thumbnail', + value: true, + }, }, lines: [image], part_order: ['form', 'lines'], @@ -194,16 +267,15 @@ BARS.defineActions(function() { image.style.display = form.thumbnail ? 'block' : 'none'; }, buttons: ['generic.share', 'dialog.cancel'], - onConfirm: function(formResult) { - + onConfirm: function (formResult) { let name = formResult.name; let expire_time = formResult.expire_time; let model = Codecs.project.compile({ compressed: false, absolute_paths: false, - reference_images: formResult.reference_images + reference_images: formResult.reference_images, }); - let data = {name, expire_time, model, thumbnail: undefined}; + let data = { name, expire_time, model, thumbnail: undefined }; if (formResult.thumbnail) data.thumbnail = thumbnail; $.ajax({ @@ -213,23 +285,30 @@ BARS.defineActions(function() { contentType: 'application/json; charset=utf-8', dataType: 'json', type: 'POST', - success: function(response) { - let link = `https://blckbn.ch/${response.id}` + success: function (response) { + let link = `https://blckbn.ch/${response.id}`; new Dialog({ id: 'share_model_link', title: 'dialog.share_model.title', singleButton: true, form: { - link: {type: 'text', value: link, readonly: true, share_text: true} - } + link: { + type: 'text', + value: link, + readonly: true, + share_text: true, + }, + }, }).show(); - }, - error: function(response) { + error: function (response) { let error_text = 'dialog.share_model.failed' + ' - ' + response.status; if (response.status == 413) { - if (ReferenceImage.current_project.length && formResult.reference_images) { + if ( + ReferenceImage.current_project.length && + formResult.reference_images + ) { error_text = 'dialog.share_model.too_large_references'; } else { error_text = 'dialog.share_model.too_large'; @@ -238,16 +317,16 @@ BARS.defineActions(function() { Blockbench.showMessageBox({ title: tl('generic.error'), message: error_text, - icon: 'error' - }) + icon: 'error', + }); console.error(response); - } - }) - - dialog.hide() - } - }) - dialog.show() - } - }) -}) \ No newline at end of file + }, + }); + + dialog.hide(); + }, + }); + dialog.show(); + }, + }); +}); diff --git a/js/languages.ts b/js/languages.ts index 1081c33d3..58c910e68 100644 --- a/js/languages.ts +++ b/js/languages.ts @@ -45,9 +45,13 @@ export const data: Record = { * @param variables Array of variables that replace anchors (%0, etc.) in the translation. Items can be strings or anything that can be converted to strings * @param default_value String value to default to if the translation is not available */ -export const tl = function(string: string, variables?: string | number | (string|number)[], default_value?: string): string { +export const tl = function ( + string: string, + variables?: string | number | (string | number)[], + default_value?: string +): string { if (string && string.length > 100) return string; - var result = Language.data[string] + var result = Language.data[string]; if (result && result.length > 0) { if (variables) { if (variables instanceof Array == false) { @@ -56,7 +60,7 @@ export const tl = function(string: string, variables?: string | number | (string var i = variables.length; while (i > 0) { i--; - result = result.replace(new RegExp('%'+i, 'g'), variables[i]) + result = result.replace(new RegExp('%' + i, 'g'), variables[i]); } } return result; @@ -66,13 +70,13 @@ export const tl = function(string: string, variables?: string | number | (string //console.warn('Unable to find translation for key', string); return string; } -} -export const translateUI = function() { - $('.tl').each(function(i, obj) { - var text = tl($(obj).text()) - $(obj).text(text) - }) -} +}; +export const translateUI = function () { + $('.tl').each(function (i, obj) { + var text = tl($(obj).text()); + $(obj).text(text); + }); +}; export const Language = { /** @@ -90,8 +94,8 @@ export const Language = { es: 'Espa\u00F1ol (Spanish)', fr: 'Fran\u00E7ais (French)', it: 'Italiano (Italian)', - ja: '\u65E5\u672C\u8A9E (Japanese)',//日本語 - ko: '\uD55C\uAD6D\uC5B4 (Korean)',//日本語 + ja: '\u65E5\u672C\u8A9E (Japanese)', //日本語 + ko: '\uD55C\uAD6D\uC5B4 (Korean)', //日本語 nl: 'Nederlands (Dutch)', pl: 'Polski (Polish)', pt: 'Portugu\u00EAs (Portuguese)', @@ -100,8 +104,8 @@ export const Language = { tr: 'Türkçe (Turkish)', uk: 'Українська (Ukrainian)', vi: 'Tiếng việt (Vietnamese)', - zh: '\u4e2d\u6587 (Chinese)',//中文 - zh_tw: '\u4E2D\u6587(\u81FA\u7063) (Traditional Chinese)',//中文(臺灣) + zh: '\u4e2d\u6587 (Chinese)', //中文 + zh_tw: '\u4E2D\u6587(\u81FA\u7063) (Traditional Chinese)', //中文(臺灣) }, /** * Add translations for custom translation strings @@ -110,33 +114,34 @@ export const Language = { */ addTranslations(language: string, strings: Record): void { for (var key in strings) { - if (language == Language.code || (language == 'en' && Language.data[key] == undefined)) { + if ( + language == Language.code || + (language == 'en' && Language.data[key] == undefined) + ) { Language.data[key] = strings[key]; } } }, - toString: () => Language.code -} - + toString: () => Language.code, +}; // Get language code let code; try { - code = JSON.parse(localStorage.getItem('settings')).language.value + code = JSON.parse(localStorage.getItem('settings')).language.value; } catch (err) {} if (!code) { - code = navigator.language.replace(/-\w+/, '') + code = navigator.language.replace(/-\w+/, ''); } if (code && Language.options[code]) { - Language.code = code + Language.code = code; document.body.parentElement.setAttribute('lang', Language.code); } - Language.data = data[Language.code]; Object.assign(window, { tl, - Language -}) + Language, +}); diff --git a/js/lib/CanvasFrame.ts b/js/lib/CanvasFrame.ts index 7b7b3850d..0266db63c 100644 --- a/js/lib/CanvasFrame.ts +++ b/js/lib/CanvasFrame.ts @@ -2,60 +2,65 @@ Utility to modify images with a canvas */ export class CanvasFrame { - canvas: HTMLCanvasElement - ctx: CanvasRenderingContext2D - - constructor() - constructor(width: number, height: number) - constructor(source?: HTMLCanvasElement | HTMLImageElement | number, copy_canvas?: number | boolean) { + canvas: HTMLCanvasElement; + ctx: CanvasRenderingContext2D; + + constructor(); + constructor(width: number, height: number); + constructor( + source?: HTMLCanvasElement | HTMLImageElement | number, + copy_canvas?: number | boolean + ) { if (source instanceof HTMLCanvasElement) { if (source.getContext('2d') && copy_canvas !== true) { this.canvas = source; } else { - this.createCanvas(source.width, source.height) - this.loadFromImage(source) + this.createCanvas(source.width, source.height); + this.loadFromImage(source); } - } else if (source instanceof HTMLImageElement) { - this.createCanvas(source.naturalWidth, source.naturalHeight) - this.loadFromImage(source) - + this.createCanvas(source.naturalWidth, source.naturalHeight); + this.loadFromImage(source); } else { this.createCanvas(source || 16, typeof copy_canvas == 'number' ? copy_canvas : 16); } - this.ctx = this.canvas.getContext('2d') + this.ctx = this.canvas.getContext('2d'); + } + get width() { + return this.canvas.width; } - get width() {return this.canvas.width;} - get height() {return this.canvas.height;} - + get height() { + return this.canvas.height; + } + createCanvas(width: number, height: number) { this.canvas = document.createElement('canvas'); this.canvas.width = width; this.canvas.height = height; - this.ctx = this.canvas.getContext('2d') + this.ctx = this.canvas.getContext('2d'); } async loadFromURL(url: string) { - let img = new Image() + let img = new Image(); img.src = url.replace(/#/g, '%23'); await new Promise((resolve, reject) => { img.onload = () => { this.loadFromImage(img); resolve(); - } + }; img.onerror = reject; - }) + }); } loadFromImage(img: HTMLImageElement | HTMLCanvasElement) { if ('naturalWidth' in img) { this.canvas.width = img.naturalWidth; this.canvas.height = img.naturalHeight; } - this.ctx.drawImage(img, 0, 0) + this.ctx.drawImage(img, 0, 0); } loadFromCanvas(canvas: HTMLCanvasElement) { this.canvas.width = canvas.width; this.canvas.height = canvas.height; - this.ctx.drawImage(canvas, 0, 0) + this.ctx.drawImage(canvas, 0, 0); } autoCrop() { // Based on code by remy, licensed under MIT @@ -64,34 +69,34 @@ export class CanvasFrame { let copy = document.createElement('canvas').getContext('2d'); let pixels = this.ctx.getImageData(0, 0, this.width, this.height); let bound = { - top: null as null|number, - left: null as null|number, - right: null as null|number, - bottom: null as null|number + top: null as null | number, + left: null as null | number, + right: null as null | number, + bottom: null as null | number, }; let x: number, y: number; - + for (let i = 0; i < pixels.data.length; i += 4) { - if (pixels.data[i+3] !== 0) { + if (pixels.data[i + 3] !== 0) { x = (i / 4) % this.width; - y = ~~((i / 4) / this.width); - + y = ~~(i / 4 / this.width); + if (bound.top === null) { bound.top = y; } - + if (bound.left === null) { - bound.left = x; + bound.left = x; } else if (x < bound.left) { bound.left = x; } - + if (bound.right === null) { - bound.right = x; + bound.right = x; } else if (bound.right < x) { bound.right = x; } - + if (bound.bottom === null) { bound.bottom = y; } else if (bound.bottom < y) { @@ -99,11 +104,11 @@ export class CanvasFrame { } } } - + let trimHeight = bound.bottom - bound.top + 1, trimWidth = bound.right - bound.left + 1, trimmed = this.ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight); - + copy.canvas.width = trimWidth; copy.canvas.height = trimHeight; copy.putImageData(trimmed, 0, 0); @@ -111,13 +116,13 @@ export class CanvasFrame { this.ctx = copy; } isEmpty(): boolean { - let {data} = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); + let { data } = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); for (let i = 0; i < data.length; i += 4) { - let alpha = data[i+3]; + let alpha = data[i + 3]; if (alpha) return false; } return true; } } -Object.assign(window, {CanvasFrame}); +Object.assign(window, { CanvasFrame }); diff --git a/js/lib/libs.ts b/js/lib/libs.ts index 57f9ebbf6..f698abbb2 100644 --- a/js/lib/libs.ts +++ b/js/lib/libs.ts @@ -1,51 +1,51 @@ -import * as GIFEnc from 'gifenc' -import $ from 'jquery' -import * as threejs from "three" -import * as FIK from './fik' -import Vue from 'vue/dist/vue.js' -import JSZip from 'jszip' -import Prism from 'prismjs' -import GIF from 'gif.js' -import vSortable from 'vue-sortable' -import Sortable from 'sortablejs' -import {marked} from 'marked' -import { APNGencoder } from './canvas2apng' -import DOMPurify from 'dompurify' +import * as GIFEnc from 'gifenc'; +import $ from 'jquery'; +import * as threejs from 'three'; +import * as FIK from './fik'; +import Vue from 'vue/dist/vue.js'; +import JSZip from 'jszip'; +import Prism from 'prismjs'; +import GIF from 'gif.js'; +import vSortable from 'vue-sortable'; +import Sortable from 'sortablejs'; +import { marked } from 'marked'; +import { APNGencoder } from './canvas2apng'; +import DOMPurify from 'dompurify'; -Vue.use(vSortable) +Vue.use(vSortable); Vue.directive('sortable', { - inserted: function (el, binding) { - new Sortable(el, binding.value || {}) - } -}) + inserted: function (el, binding) { + new Sortable(el, binding.value || {}); + }, +}); const THREE = Object.assign({}, threejs); export { - GIFEnc, - GIF, - THREE, - $, - $ as jQuery, - FIK, - Vue, - JSZip, - Prism, - marked, - APNGencoder, - DOMPurify, -} + GIFEnc, + GIF, + THREE, + $, + $ as jQuery, + FIK, + Vue, + JSZip, + Prism, + marked, + APNGencoder, + DOMPurify, +}; Object.assign(window, { - GIFEnc, - GIF, - THREE, - jQuery: $, - $, - FIK, - Vue, - JSZip, - Prism, - marked, - APNGencoder, - DOMPurify, -}) \ No newline at end of file + GIFEnc, + GIF, + THREE, + jQuery: $, + $, + FIK, + Vue, + JSZip, + Prism, + marked, + APNGencoder, + DOMPurify, +}); diff --git a/js/main.ts b/js/main.ts index 644e870ef..06d19a333 100644 --- a/js/main.ts +++ b/js/main.ts @@ -1,124 +1,124 @@ //import { createApp } from 'vue' //import App from './App.vue' -import "./lib/libs" -import "./lib/jquery-ui.min" -import "./lib/targa" -import "./lib/VuePrismEditor.min" -import "./lib/molang-prism-syntax" -import "./lib/lzutf8" -import "./lib/spectrum.js" -import "./lib/color-picker.min" -import "./lib/GLTFExporter" -import "./lib/CanvasFrame" -import "./lib/canvas2apng" -import "./lib/easing" -import "./native_apis" -import "./preview/OrbitControls" +import './lib/libs'; +import './lib/jquery-ui.min'; +import './lib/targa'; +import './lib/VuePrismEditor.min'; +import './lib/molang-prism-syntax'; +import './lib/lzutf8'; +import './lib/spectrum.js'; +import './lib/color-picker.min'; +import './lib/GLTFExporter'; +import './lib/CanvasFrame'; +import './lib/canvas2apng'; +import './lib/easing'; +import './native_apis'; +import './preview/OrbitControls'; -import './languages' -import "./util/util" -import "./util/json" -import "./util/three_custom" -import "./util/math_util" -import "./util/array_util" -import "./util/event_system" -import "./util/property" -import "./interface/menu" -import "./interface/actions" -import "./interface/shared_actions" -import "./interface/keyboard" -import "./misc" -import "./api" -import "./modes" -import "./file_system" -import "./interface/vue_components" -import "./interface/panels" -import "./interface/interface" -import "./interface/menu_bar" -import "./interface/start_screen" -import "./interface/form" -import "./interface/dialog" -import "./interface/keybinding" -import "./interface/settings" -import "./interface/about" -import "./interface/action_control" -import "./copy_paste" -import "./undo" +import './languages'; +import './util/util'; +import './util/json'; +import './util/three_custom'; +import './util/math_util'; +import './util/array_util'; +import './util/event_system'; +import './util/property'; +import './interface/menu'; +import './interface/actions'; +import './interface/shared_actions'; +import './interface/keyboard'; +import './misc'; +import './api'; +import './modes'; +import './file_system'; +import './interface/vue_components'; +import './interface/panels'; +import './interface/interface'; +import './interface/menu_bar'; +import './interface/start_screen'; +import './interface/form'; +import './interface/dialog'; +import './interface/keybinding'; +import './interface/settings'; +import './interface/about'; +import './interface/action_control'; +import './copy_paste'; +import './undo'; import './desktop.js'; -import "./interface/setup_settings" -import "./interface/settings_window" -import "./edit_sessions" -import "./validator" -import "./outliner/outliner" -import "./outliner/element_panel" -import "./outliner/collections" -import "./outliner/group" -import "./outliner/mesh" -import "./outliner/cube" -import "./outliner/billboard" -import "./outliner/texture_mesh" -import "./outliner/armature" -import "./outliner/armature_bone" -import "./outliner/locator" -import "./outliner/null_object" -import "./outliner/spline_mesh" -import "./preview/preview" -import "./preview/reference_images" -import "./preview/screenshot" -import "./preview/canvas" -import "./interface/themes" -import "./modeling/edit" -import "./modeling/transform_gizmo" -import "./modeling/transform" -import "./modeling/scale" -import "./modeling/mesh_editing" -import "./modeling/mirror_modeling" -import "./modeling/spline_editing" -import "./modeling/weight_paint" -import "./texturing/textures" -import "./texturing/layers" -import "./texturing/texture_groups" -import "./texturing/texture_flipbook" -import "./texturing/uv" -import "./texturing/painter" -import "./texturing/texture_generator" -import "./texturing/edit_image" -import "./display_mode/display_mode" -import "./display_mode/attachable_preview" -import "./animations/animation_mode" -import "./animations/animation" -import "./animations/molang" -import "./animations/timeline_animators" -import "./animations/keyframe" -import "./animations/timeline" -import "./animations/animation_controllers" -import "./preview/preview_scenes" -import "./predicate_editor" -import "./plugin_loader" -import "./io/codec" -import "./io/format" -import "./io/project" -import "./io/io" -import "./io/share" -import "./texturing/color" -import "./io/formats/generic" -import "./io/formats/bbmodel" -import "./io/formats/java_block" -import "./io/formats/bedrock" -import "./io/formats/bedrock_old" -import "./io/formats/obj" -import "./io/formats/gltf" -import "./io/formats/fbx" -import "./io/formats/collada" -import "./io/formats/stl" -import "./io/formats/modded_entity" -import "./io/formats/optifine_jem" -import "./io/formats/optifine_jpm" -import "./io/formats/skin" -import "./io/formats/image" -import "./boot_loader" -import "./globals" -import "./global_types" +import './interface/setup_settings'; +import './interface/settings_window'; +import './edit_sessions'; +import './validator'; +import './outliner/outliner'; +import './outliner/element_panel'; +import './outliner/collections'; +import './outliner/group'; +import './outliner/mesh'; +import './outliner/cube'; +import './outliner/billboard'; +import './outliner/texture_mesh'; +import './outliner/armature'; +import './outliner/armature_bone'; +import './outliner/locator'; +import './outliner/null_object'; +import './outliner/spline_mesh'; +import './preview/preview'; +import './preview/reference_images'; +import './preview/screenshot'; +import './preview/canvas'; +import './interface/themes'; +import './modeling/edit'; +import './modeling/transform_gizmo'; +import './modeling/transform'; +import './modeling/scale'; +import './modeling/mesh_editing'; +import './modeling/mirror_modeling'; +import './modeling/spline_editing'; +import './modeling/weight_paint'; +import './texturing/textures'; +import './texturing/layers'; +import './texturing/texture_groups'; +import './texturing/texture_flipbook'; +import './texturing/uv'; +import './texturing/painter'; +import './texturing/texture_generator'; +import './texturing/edit_image'; +import './display_mode/display_mode'; +import './display_mode/attachable_preview'; +import './animations/animation_mode'; +import './animations/animation'; +import './animations/molang'; +import './animations/timeline_animators'; +import './animations/keyframe'; +import './animations/timeline'; +import './animations/animation_controllers'; +import './preview/preview_scenes'; +import './predicate_editor'; +import './plugin_loader'; +import './io/codec'; +import './io/format'; +import './io/project'; +import './io/io'; +import './io/share'; +import './texturing/color'; +import './io/formats/generic'; +import './io/formats/bbmodel'; +import './io/formats/java_block'; +import './io/formats/bedrock'; +import './io/formats/bedrock_old'; +import './io/formats/obj'; +import './io/formats/gltf'; +import './io/formats/fbx'; +import './io/formats/collada'; +import './io/formats/stl'; +import './io/formats/modded_entity'; +import './io/formats/optifine_jem'; +import './io/formats/optifine_jpm'; +import './io/formats/skin'; +import './io/formats/image'; +import './boot_loader'; +import './globals'; +import './global_types'; diff --git a/js/modeling/edit.ts b/js/modeling/edit.ts index 310d16fea..6a474ce8f 100644 --- a/js/modeling/edit.ts +++ b/js/modeling/edit.ts @@ -1,6 +1,6 @@ -import { Mode } from "../modes"; +import { Mode } from '../modes'; -BARS.defineActions(function() { +BARS.defineActions(function () { new Mode('edit', { icon: 'deployed_code', default_tool: 'move_tool', @@ -9,15 +9,17 @@ BARS.defineActions(function() { onSelect: () => { Outliner.elements.forEach(cube => { // @ts-ignore - if (cube.preview_controller.updatePixelGrid) cube.preview_controller.updatePixelGrid(cube); - }) + if (cube.preview_controller.updatePixelGrid) + cube.preview_controller.updatePixelGrid(cube); + }); }, onUnselect: () => { if (Undo) Undo.closeAmendEditMenu(); Outliner.elements.forEach(cube => { // @ts-ignore - if (cube.preview_controller.updatePixelGrid) cube.preview_controller.updatePixelGrid(cube); - }) - } - }) -}) + if (cube.preview_controller.updatePixelGrid) + cube.preview_controller.updatePixelGrid(cube); + }); + }, + }); +}); diff --git a/js/modeling/mesh/attach_armature.ts b/js/modeling/mesh/attach_armature.ts index 39e649aff..c93983c8c 100644 --- a/js/modeling/mesh/attach_armature.ts +++ b/js/modeling/mesh/attach_armature.ts @@ -1,35 +1,34 @@ -import { Armature } from "../../outliner/armature"; -import { ArmatureBone } from "../../outliner/armature_bone"; -import { sameMeshEdge } from "./util"; -import { THREE } from "../../lib/libs"; -import { pointInPolygon } from "../../util/util"; -import { Blockbench } from "../../api"; - +import { Armature } from '../../outliner/armature'; +import { ArmatureBone } from '../../outliner/armature_bone'; +import { sameMeshEdge } from './util'; +import { THREE } from '../../lib/libs'; +import { pointInPolygon } from '../../util/util'; +import { Blockbench } from '../../api'; interface BoneInfo { - bone: ArmatureBone, - name: string, - tail_offset: THREE.Vector3, - start: THREE.Vector3, - end: THREE.Vector3, - line: THREE.Line3, - _distance?: number - _distance_on_line?: number - _amount?: number - _is_inside?: boolean - _weight?: number + bone: ArmatureBone; + name: string; + tail_offset: THREE.Vector3; + start: THREE.Vector3; + end: THREE.Vector3; + line: THREE.Line3; + _distance?: number; + _distance_on_line?: number; + _amount?: number; + _is_inside?: boolean; + _weight?: number; } interface EdgeLoop { - loop: MeshEdge[] - plane: THREE.Plane - plane_quaternion: THREE.Quaternion - polygon: ArrayVector2[] - vkeys: string[] + loop: MeshEdge[]; + plane: THREE.Plane; + plane_quaternion: THREE.Quaternion; + polygon: ArrayVector2[]; + vkeys: string[]; } function calculateWeights(mesh: Mesh, armature: Armature) { let armature_bones = armature.getAllBones(); - Undo.initEdit({elements: [mesh, ...armature_bones]}); + Undo.initEdit({ elements: [mesh, ...armature_bones] }); mesh.preview_controller.updateTransform(mesh); if (armature) { @@ -42,7 +41,7 @@ function calculateWeights(mesh: Mesh, armature: Armature) { } else { tail_offset.y = bone.length; } - + let start = bone.getWorldCenter(); let end = bone.mesh.localToWorld(tail_offset); let data: BoneInfo = { @@ -60,7 +59,6 @@ function calculateWeights(mesh: Mesh, armature: Armature) { const vertex_edge_loops: Record = {}; for (let vkey in mesh.vertices) { - if (!vertex_edge_loops[vkey]) vertex_edge_loops[vkey] = []; if (vertex_edge_loops[vkey].length >= 4) continue; @@ -70,9 +68,18 @@ function calculateWeights(mesh: Mesh, armature: Armature) { loop[Math.floor(loop.length * 0.33)][0], loop[Math.floor(loop.length * 0.66)][0], ]; - let coplanar_points = coplanar_vertices.map(vkey => new THREE.Vector3().fromArray(mesh.vertices[vkey])); - let plane = new THREE.Plane().setFromCoplanarPoints(coplanar_points[0], coplanar_points[1], coplanar_points[2]); - let plane_quaternion = new THREE.Quaternion().setFromUnitVectors(plane.normal, new THREE.Vector3(0, 1, 0)); + let coplanar_points = coplanar_vertices.map(vkey => + new THREE.Vector3().fromArray(mesh.vertices[vkey]) + ); + let plane = new THREE.Plane().setFromCoplanarPoints( + coplanar_points[0], + coplanar_points[1], + coplanar_points[2] + ); + let plane_quaternion = new THREE.Quaternion().setFromUnitVectors( + plane.normal, + new THREE.Vector3(0, 1, 0) + ); let polygon: ArrayVector2[] = []; let vkeys: string[] = []; @@ -92,43 +99,56 @@ function calculateWeights(mesh: Mesh, armature: Armature) { vertex_edge_loops[vkey2] = [edge_loop]; } else { let match = vertex_edge_loops[vkey2].find(edge_loop2 => { - return edge_loop2.vkeys.length == edge_loop.vkeys.length && edge_loop2.vkeys.allAre(vkey3 => edge_loop.vkeys.includes(vkey3)); + return ( + edge_loop2.vkeys.length == edge_loop.vkeys.length && + edge_loop2.vkeys.allAre(vkey3 => edge_loop.vkeys.includes(vkey3)) + ); }); if (!match) vertex_edge_loops[vkey2].push(edge_loop); } } - }) + }); } - // Calculate base vertex weights const vertex_main_bone: Record = {}; for (let vkey in mesh.vertices) { let global_pos = new THREE.Vector3().fromArray(mesh.vertices[vkey]); let edge_loops = vertex_edge_loops[vkey]; - let shortest_edge_loop = edge_loops.findHighest((loop) => -loop.vkeys.length); + let shortest_edge_loop = edge_loops.findHighest(loop => -loop.vkeys.length); for (let bone_info of bone_infos) { - bone_info._is_inside = shortest_edge_loop && isBoneInsideLoops(edge_loops, bone_info) != false; + bone_info._is_inside = + shortest_edge_loop && isBoneInsideLoops(edge_loops, bone_info) != false; - let closest_point = bone_info.line.closestPointToPoint(global_pos, true, new THREE.Vector3); + let closest_point = bone_info.line.closestPointToPoint( + global_pos, + true, + new THREE.Vector3() + ); bone_info._distance = closest_point.distanceTo(global_pos); bone_info.line.closestPointToPoint(global_pos, false, closest_point); bone_info._distance_on_line = closest_point.distanceTo(global_pos); } - + let inside_bones = bone_infos.filter(bone_infos => bone_infos._is_inside); - let bone_matches = inside_bones.filter(bone_info => bone_info._distance < bone_info._distance_on_line * 1.2); + let bone_matches = inside_bones.filter( + bone_info => bone_info._distance < bone_info._distance_on_line * 1.2 + ); if (!bone_matches.length) { - bone_matches = inside_bones.filter(bone_info => bone_info._distance < bone_info._distance_on_line * 2); + bone_matches = inside_bones.filter( + bone_info => bone_info._distance < bone_info._distance_on_line * 2 + ); } - let full_match_bones = bone_matches.filter(bone_info => bone_info._distance < bone_info._distance_on_line * 2); + let full_match_bones = bone_matches.filter( + bone_info => bone_info._distance < bone_info._distance_on_line * 2 + ); if (full_match_bones.length) { let closest_bone = full_match_bones.findHighest(bone => -bone._distance); vertex_main_bone[vkey] = closest_bone; - + closest_bone.bone.vertex_weights[vkey] = 1; } else { bone_matches.sort((a, b) => a._distance - b._distance); @@ -136,7 +156,10 @@ function calculateWeights(mesh: Mesh, armature: Armature) { vertex_main_bone[vkey] = bone_matches[0]; let amount_sum = 0; for (let match of bone_matches) { - match._amount = Math.min(Math.max(match._distance_on_line, 0.04) / match._distance, 1); + match._amount = Math.min( + Math.max(match._distance_on_line, 0.04) / match._distance, + 1 + ); amount_sum += match._amount; } for (let match of bone_matches) { @@ -149,8 +172,8 @@ function calculateWeights(mesh: Mesh, armature: Armature) { let closest_vertices = []; for (let loop of vertex_edge_loops[vkey]) { let index = loop.vkeys.indexOf(vkey); - closest_vertices.safePush(loop.vkeys.atWrapped(index+1)); - closest_vertices.safePush(loop.vkeys.atWrapped(index-1)); + closest_vertices.safePush(loop.vkeys.atWrapped(index + 1)); + closest_vertices.safePush(loop.vkeys.atWrapped(index - 1)); } if (!vertex_main_bone[vkey]) { let bones = []; @@ -171,10 +194,17 @@ function calculateWeights(mesh: Mesh, armature: Armature) { let vertex_position = new THREE.Vector3().fromArray(mesh.vertices[vkey]); let weight_sum = 0; let weighted_vertices = closest_vertices.map(vkey2 => { - let distance = Reusable.vec1.fromArray(mesh.vertices[vkey2]).distanceTo(vertex_position) + let distance = Reusable.vec1 + .fromArray(mesh.vertices[vkey2]) + .distanceTo(vertex_position); weight_sum += 1 / distance; - return { distance, bone: vertex_main_bone[vkey2], vkey: vkey2, weight: 1 / distance }; - }) + return { + distance, + bone: vertex_main_bone[vkey2], + vkey: vkey2, + weight: 1 / distance, + }; + }); for (let weighted of weighted_vertices) { if (!weighted.bone) continue; weighted.bone._weight += weighted.weight; @@ -187,7 +217,7 @@ function calculateWeights(mesh: Mesh, armature: Armature) { } Undo.finishEdit('Attach armature to mesh'); - Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true}}); + Canvas.updateView({ elements: Mesh.selected, element_aspects: { geometry: true } }); } function isBoneInsideLoops(edge_loops: EdgeLoop[], bone_info: BoneInfo): THREE.Vector3 | false { for (let loop of edge_loops) { @@ -202,7 +232,6 @@ function isBoneInsideLoops(edge_loops: EdgeLoop[], bone_info: BoneInfo): THREE.V return false; } function getEdgeLoops(mesh: Mesh, start_vkey: string) { - let vertices: string[] = []; let edges: MeshEdge[] = []; @@ -212,11 +241,14 @@ function getEdgeLoops(mesh: Mesh, start_vkey: string) { processed_faces.push(face); let sorted_vertices = face.vertices.slice(); - let side_index_diff = sorted_vertices.indexOf(side_vertices[0]) - sorted_vertices.indexOf(side_vertices[1]); + let side_index_diff = + sorted_vertices.indexOf(side_vertices[0]) - sorted_vertices.indexOf(side_vertices[1]); if (side_index_diff == -1 || side_index_diff > 2) side_vertices.reverse(); let opposite_vertices = sorted_vertices.filter(vkey => !side_vertices.includes(vkey)); - let opposite_index_diff = sorted_vertices.indexOf(opposite_vertices[0]) - sorted_vertices.indexOf(opposite_vertices[1]); + let opposite_index_diff = + sorted_vertices.indexOf(opposite_vertices[0]) - + sorted_vertices.indexOf(opposite_vertices[1]); if (opposite_index_diff == 1 || opposite_index_diff < -2) opposite_vertices.reverse(); vertices.safePush(...side_vertices); @@ -229,14 +261,19 @@ function getEdgeLoops(mesh: Mesh, start_vkey: string) { if (ref_face.vertices.length < 3 || processed_faces.includes(ref_face)) continue; let sorted_vertices = ref_face.vertices.slice(); - let vertices = ref_face.vertices.filter(vkey => vkey == side_vertices[index] || vkey == opposite_vertices[index]); + let vertices = ref_face.vertices.filter( + vkey => vkey == side_vertices[index] || vkey == opposite_vertices[index] + ); if (vertices.length >= 2) { let second_vertex = sorted_vertices.find((vkey, i) => { - return vkey !== side_vertices[index] - && vkey !== opposite_vertices[index] - && (sorted_vertices.length == 3 || Math.abs(sorted_vertices.indexOf(side_vertices[index]) - i) !== 2); - }) + return ( + vkey !== side_vertices[index] && + vkey !== opposite_vertices[index] && + (sorted_vertices.length == 3 || + Math.abs(sorted_vertices.indexOf(side_vertices[index]) - i) !== 2) + ); + }); checkFace(ref_face, [side_vertices[index], second_vertex]); break; } @@ -251,13 +288,13 @@ function getEdgeLoops(mesh: Mesh, start_vkey: string) { if (face.vertices.includes(start_vkey) == false) continue; for (let edge of face.getEdges()) { if (edge.includes(start_vkey) && !start_edges.find(e2 => sameMeshEdge(e2.edge, edge))) { - start_edges.push({edge, face}); + start_edges.push({ edge, face }); } } } let loops: MeshEdge[][] = []; - start_edges.forEach(({edge, face}) => { + start_edges.forEach(({ edge, face }) => { edges = []; checkFace(face, edge); if (edges.length > 1) loops.push(edges); @@ -266,13 +303,12 @@ function getEdgeLoops(mesh: Mesh, start_vkey: string) { } BARS.defineActions(() => { - new Action('calculate_vertex_weights', { icon: 'accessibility', condition: () => Mesh.selected[0]?.getArmature(), click(e) { let mesh = Mesh.selected[0]; calculateWeights(mesh, mesh.getArmature()); - } + }, }); -}) \ No newline at end of file +}); diff --git a/js/modeling/mesh/proportional_edit.ts b/js/modeling/mesh/proportional_edit.ts index f0934ba1c..771f0516b 100644 --- a/js/modeling/mesh/proportional_edit.ts +++ b/js/modeling/mesh/proportional_edit.ts @@ -8,12 +8,12 @@ export const ProportionalEdit = { }, calculateWeights(mesh: Mesh) { if (!pe_toggle.value) return; - + let selected_vertices = mesh.getSelectedVertices(); - let {range, falloff, selection} = ProportionalEdit.config; + let { range, falloff, selection } = ProportionalEdit.config; let linear_distance = selection == 'linear'; - - let all_mesh_connections: Record; + + let all_mesh_connections; if (!linear_distance) { all_mesh_connections = {}; for (let fkey in mesh.faces) { @@ -29,38 +29,42 @@ export const ProportionalEdit = { } else { all_mesh_connections[edge[1]].safePush(edge[0]); } - }) + }); } } ProportionalEdit.vertex_weights[mesh.uuid] = {}; - + for (let vkey in mesh.vertices) { if (selected_vertices.includes(vkey)) continue; - + let distance = Infinity; if (linear_distance) { // Linear Distance selected_vertices.forEach(vkey2 => { let pos1 = mesh.vertices[vkey]; let pos2 = mesh.vertices[vkey2]; - let distance_square = Math.pow(pos1[0] - pos2[0], 2) + Math.pow(pos1[1] - pos2[1], 2) + Math.pow(pos1[2] - pos2[2], 2); + let distance_square = + Math.pow(pos1[0] - pos2[0], 2) + + Math.pow(pos1[1] - pos2[1], 2) + + Math.pow(pos1[2] - pos2[2], 2); if (distance_square < distance) { distance = distance_square; } - }) + }); distance = Math.sqrt(distance); } else { // Connection Distance let found_match_depth = 0; let scanned = []; let frontier = [vkey]; - - depth_crawler: - for (let depth = 1; depth <= range; depth++) { + + depth_crawler: for (let depth = 1; depth <= range; depth++) { let new_frontier = []; for (let vkey1 of frontier) { - let connections = all_mesh_connections[vkey1]?.filter(vkey2 => !scanned.includes(vkey2)); + let connections = all_mesh_connections[vkey1]?.filter( + vkey2 => !scanned.includes(vkey2) + ); if (!connections || connections.length == 0) continue; scanned.push(...connections); new_frontier.push(...connections); @@ -78,74 +82,87 @@ export const ProportionalEdit = { } } if (distance > range) continue; - - let blend = 1 - (distance / (linear_distance ? range : range+1)); + + let blend = 1 - distance / (linear_distance ? range : range + 1); switch (falloff) { - case 'hermite_spline': blend = Math.hermiteBlend(blend); break; - case 'constant': blend = 1; break; + case 'hermite_spline': + blend = Math.hermiteBlend(blend); + break; + case 'constant': + blend = 1; + break; } ProportionalEdit.vertex_weights[mesh.uuid][vkey] = blend; } }, - editVertices(mesh: Mesh, per_vertex: (vkey: string, blend: number) => void) { + editVertices(mesh, per_vertex) { if (!pe_toggle.value) return; let selected_vertices = mesh.getSelectedVertices(); for (let vkey in mesh.vertices) { if (selected_vertices.includes(vkey)) continue; - + let blend = ProportionalEdit.vertex_weights[mesh.uuid][vkey]; per_vertex(vkey, blend); } - } -} + }, +}; const pe_toggle = new Toggle('proportional_editing', { icon: 'wifi_tethering', category: 'edit', - condition: {modes: ['edit'], features: ['meshes']}, + condition: { modes: ['edit'], features: ['meshes'] }, tool_config: new ToolConfig('proportional_editing_options', { title: 'action.proportional_editing', width: 400, form: { - enabled: {type: 'checkbox', label: 'menu.mirror_painting.enabled', value: false}, - range: {type: 'number', label: 'dialog.proportional_editing.range', value: 8}, - falloff: {type: 'select', label: 'dialog.proportional_editing.falloff', value: 'linear', options: { - linear: 'dialog.proportional_editing.falloff.linear', - hermite_spline: 'dialog.proportional_editing.falloff.hermite_spline', - constant: 'dialog.proportional_editing.falloff.constant', - }}, - selection: {type: 'select', label: 'dialog.proportional_editing.selection', value: 'linear', options: { - linear: 'dialog.proportional_editing.selection.linear', - connections: 'dialog.proportional_editing.selection.connections', - //path: 'Connection Path', - }}, + enabled: { type: 'checkbox', label: 'menu.mirror_painting.enabled', value: false }, + range: { type: 'number', label: 'dialog.proportional_editing.range', value: 8 }, + falloff: { + type: 'select', + label: 'dialog.proportional_editing.falloff', + value: 'linear', + options: { + linear: 'dialog.proportional_editing.falloff.linear', + hermite_spline: 'dialog.proportional_editing.falloff.hermite_spline', + constant: 'dialog.proportional_editing.falloff.constant', + }, + }, + selection: { + type: 'select', + label: 'dialog.proportional_editing.selection', + value: 'linear', + options: { + linear: 'dialog.proportional_editing.selection.linear', + connections: 'dialog.proportional_editing.selection.connections', + //path: 'Connection Path', + }, + }, + }, + onOpen() { + this.setFormValues({ enabled: pe_toggle.value }); }, onFormChange(formResult) { if (pe_toggle.value != formResult.enabled) { pe_toggle.trigger(); } (BarItems.proportional_editing_range as NumSlider).update(); - } + }, }), - onChange(value) { - ProportionalEdit.config.enabled = value; - } -}) +}); // @ts-ignore ProportionalEdit.config = (pe_toggle.tool_config as ToolConfig).options; - new NumSlider('proportional_editing_range', { category: 'edit', - condition: {modes: ['edit'], features: ['meshes']}, + condition: { modes: ['edit'], features: ['meshes'] }, get() { - return ProportionalEdit.config.range + return ProportionalEdit.config.range; }, change(modify) { ProportionalEdit.config.range = modify(ProportionalEdit.config.range); }, onAfter() { pe_toggle.tool_config.save(); - } -}) \ No newline at end of file + }, +}); diff --git a/js/modeling/mesh/set_vertex_weights.ts b/js/modeling/mesh/set_vertex_weights.ts index 69602ee08..01b89756a 100644 --- a/js/modeling/mesh/set_vertex_weights.ts +++ b/js/modeling/mesh/set_vertex_weights.ts @@ -1,10 +1,9 @@ -import { Armature } from "../../outliner/armature"; -import { ArmatureBone } from "../../outliner/armature_bone"; - +import { Armature } from '../../outliner/armature'; +import { ArmatureBone } from '../../outliner/armature_bone'; new Action('set_vertex_weights', { icon: 'weight', - condition: {modes: ['edit'], method: () => (Mesh.selected[0]?.getArmature())}, + condition: { modes: ['edit'], method: () => Mesh.selected[0]?.getArmature() }, click() { let mesh = Mesh.selected[0]; let selected_vertices = mesh.getSelectedVertices(); @@ -22,14 +21,19 @@ new Action('set_vertex_weights', { new Dialog('set_vertex_weights', { title: 'Set vertex weights', form: { - bone: {type: 'select', label: 'Bone', value: available_bones[0].name, options: bone_options}, - weight: {type: 'number', label: 'Weight', value: 1, min: 0, max: 1} + bone: { + type: 'select', + label: 'Bone', + value: available_bones[0].name, + options: bone_options, + }, + weight: { type: 'number', label: 'Weight', value: 1, min: 0, max: 1 }, }, onConfirm(result) { let target_bone = available_bones.find(b => b.uuid == result.bone); affected_bones.safePush(target_bone); // @ts-ignore Should be fixed once converting armature_bone.js to ts - Undo.initEdit({elements: affected_bones}); + Undo.initEdit({ elements: affected_bones }); for (let bone of affected_bones) { if (bone.uuid == result.bone) continue; for (let vkey of selected_vertices) { @@ -41,7 +45,7 @@ new Action('set_vertex_weights', { } Undo.finishEdit('Set vertex weights'); updateSelection(); - } + }, }).show(); - } -}) \ No newline at end of file + }, +}); diff --git a/js/modeling/mesh/util.ts b/js/modeling/mesh/util.ts index c5115fe54..f33db2c7e 100644 --- a/js/modeling/mesh/util.ts +++ b/js/modeling/mesh/util.ts @@ -1,3 +1,3 @@ export function sameMeshEdge(edge_a: MeshEdge, edge_b: MeshEdge): boolean { - return edge_a.equals(edge_b) || (edge_a[0] == edge_b[1] && edge_a[1] == edge_b[0]) -} \ No newline at end of file + return edge_a.equals(edge_b) || (edge_a[0] == edge_b[1] && edge_a[1] == edge_b[0]); +} diff --git a/js/modeling/mirror_modeling.ts b/js/modeling/mirror_modeling.ts index 13a5a8502..15041bcbe 100644 --- a/js/modeling/mirror_modeling.ts +++ b/js/modeling/mirror_modeling.ts @@ -1,7 +1,7 @@ -import { Blockbench } from "../api"; -import { ArmatureBone } from "../outliner/armature_bone"; -import { Billboard } from "../outliner/billboard"; -import { flipNameOnAxis } from "./transform"; +import { Blockbench } from '../api'; +import { ArmatureBone } from '../outliner/armature_bone'; +import { Billboard } from '../outliner/billboard'; +import { flipNameOnAxis } from './transform'; export const MirrorModeling = { initial_transformer_position: 0, @@ -11,18 +11,19 @@ export const MirrorModeling = { let element_type_options = MirrorModeling.element_types[element.type]; if (element_type_options.isCentered) { - let result = element_type_options.isCentered(element, {center}); + let result = element_type_options.isCentered(element, { center }); if (result == false) return false; } if (element_type_options.check_parent_symmetry != false) { - let isAsymmetrical = (parent) => { + let isAsymmetrical = parent => { if (parent instanceof OutlinerNode) { - if ("origin" in parent && parent.origin[0] != center) return true; - if ("rotation" in parent && (parent.rotation[1] || parent.rotation[2])) return true; + if ('origin' in parent && parent.origin[0] != center) return true; + if ('rotation' in parent && (parent.rotation[1] || parent.rotation[2])) + return true; return isAsymmetrical(parent.parent); } - } + }; if (isAsymmetrical(element.parent)) return false; } @@ -42,20 +43,20 @@ export const MirrorModeling = { if (mirror_element) { element_before_snapshot = mirror_element.getUndoCopy(undo_aspects); mirror_element.extend(original); - + mirror_element.flip(0, center); mirror_element.extend({ - name: element_before_snapshot.name + name: element_before_snapshot.name, }); if (!mirror_uv && element_type_options.maintainUV) { - element_type_options.maintainUV(mirror_element, element_before_snapshot) + element_type_options.maintainUV(mirror_element, element_before_snapshot); } if (element_type_options.updateCounterpart) { element_type_options.updateCounterpart(original, mirror_element, { element_before_snapshot, center, - }) + }); } // Update hierarchy up @@ -64,24 +65,36 @@ export const MirrorModeling = { let parent_b = child_b.parent; if (parent == parent_b) return; if (parent.type != parent_b.type) return; - if (parent instanceof OutlinerNode == false || parent.getTypeBehavior('parent') != true) return; - if (parent_b instanceof OutlinerNode == false || parent_b.getTypeBehavior('parent') != true) return; + if ( + parent instanceof OutlinerNode == false || + parent.getTypeBehavior('parent') != true + ) + return; + if ( + parent_b instanceof OutlinerNode == false || + parent_b.getTypeBehavior('parent') != true + ) + return; MirrorModeling.updateParentNodeCounterpart(parent_b, parent); updateParent(parent, parent_b); } updateParent(original, mirror_element); - } else { function getParentMirror(child: OutlinerNode) { let parent = child.parent; if (parent instanceof OutlinerNode == false) return 'root'; - if ('origin' in parent && parent.origin[0] == center && MirrorModeling.isParentTreeSymmetrical(child, {center})) { + if ( + 'origin' in parent && + parent.origin[0] == center && + MirrorModeling.isParentTreeSymmetrical(child, { center }) + ) { return parent; } else { - let mirror_group_parent = getParentMirror(parent) as OutlinerNode & OutlinerNodeParentTraits; + let mirror_group_parent = getParentMirror(parent) as OutlinerNode & + OutlinerNodeParentTraits; // @ts-ignore let mirror_group = new parent.constructor(parent); @@ -91,17 +104,25 @@ export const MirrorModeling = { mirror_group.rotation[2] *= -1; mirror_group.isOpen = parent.isOpen; - let parent_list = mirror_group_parent instanceof OutlinerNode ? mirror_group_parent.children : Outliner.root; - let match = parent_list.find((node) => { + let parent_list = + mirror_group_parent instanceof OutlinerNode + ? mirror_group_parent.children + : Outliner.root; + let match = parent_list.find(node => { if (node instanceof OutlinerNode == false) return false; if ( - (node.name == mirror_group.name || Condition(mirror_group.getTypeBehavior('unique_name'))) && - ('rotation' in node && node.rotation instanceof Array && node.rotation.equals(mirror_group.rotation)) && - ('origin' in node && node.origin instanceof Array && node.origin.equals(mirror_group.origin)) + (node.name == mirror_group.name || + Condition(mirror_group.getTypeBehavior('unique_name'))) && + 'rotation' in node && + node.rotation instanceof Array && + node.rotation.equals(mirror_group.rotation) && + 'origin' in node && + node.origin instanceof Array && + node.origin.equals(mirror_group.origin) ) { return true; } - }) + }); if (match) { return match; } else { @@ -121,19 +142,19 @@ export const MirrorModeling = { element_type_options.updateCounterpart(original, mirror_element, { options, center, - }) + }); } } MirrorModeling.insertElementIntoUndo(mirror_element, undo_aspects, element_before_snapshot); - let {preview_controller} = mirror_element; + let { preview_controller } = mirror_element; preview_controller.updateAll(mirror_element); return mirror_element; }, updateParentNodeCounterpart(node: OutlinerNode, original: OutlinerNode) { let keep_properties = { - name: node.name + name: node.name, }; node.extend(original); node.extend(keep_properties); @@ -148,7 +169,9 @@ export const MirrorModeling = { } }, getEditSide() { - return Math.sign(Transformer.position.x || MirrorModeling.initial_transformer_position) || 1; + return ( + Math.sign(Transformer.position.x || MirrorModeling.initial_transformer_position) || 1 + ); }, flipCoord(input: number): number { if (Format.centered_grid) { @@ -161,32 +184,39 @@ export const MirrorModeling = { let element_type_options = MirrorModeling.element_types[element.type]; let center = Format.centered_grid ? 0 : 8; if (element_type_options.getMirroredElement) { - return element_type_options.getMirroredElement(element, {center}) + return element_type_options.getMirroredElement(element, { center }); } return false; }, - isParentTreeSymmetrical(element: OutlinerNode, {center}) { - if (element.parent instanceof Group && Format.bone_rig == false) return true; + isParentTreeSymmetrical(element: OutlinerNode, { center }) { + if (element.parent instanceof Group && Format.bone_rig == false) return true; let parents = []; let subject = element; let symmetry_axes = [0]; let off_axes = [1, 2]; while (subject.parent instanceof OutlinerNode) { subject = subject.parent; - parents.push(subject) + parents.push(subject); } return parents.allAre(parent => { if (parent.rotation && off_axes.some(axis => parent.rotation[axis])) return false; - if (parent.origin && !symmetry_axes.allAre(axis => parent.origin[axis] == center)) return false; + if (parent.origin && !symmetry_axes.allAre(axis => parent.origin[axis] == center)) + return false; return true; - }) + }); }, - insertElementIntoUndo(element: OutlinerElement, undo_aspects: UndoAspects, element_before_snapshot: any) { + insertElementIntoUndo( + element: OutlinerElement, + undo_aspects: UndoAspects, + element_before_snapshot: any + ) { // pre if (element_before_snapshot) { - if (!Undo.current_save.elements[element.uuid]) Undo.current_save.elements[element.uuid] = element_before_snapshot; + if (!Undo.current_save.elements[element.uuid]) + Undo.current_save.elements[element.uuid] = element_before_snapshot; } else { - if (!Undo.current_save.outliner) Undo.current_save.outliner = MirrorModeling.outliner_snapshot; + if (!Undo.current_save.outliner) + Undo.current_save.outliner = MirrorModeling.outliner_snapshot; } // post @@ -195,24 +225,29 @@ export const MirrorModeling = { }, element_types: {} as Record, registerElementType(type_class: any, options: MirrorModelingElementTypeOptions) { - new Property(type_class, 'boolean', 'allow_mirror_modeling', {default: true}); + new Property(type_class, 'boolean', 'allow_mirror_modeling', { default: true }); let type = type_class.prototype.type; MirrorModeling.element_types[type] = options; }, - cached_elements: {} -} + cached_elements: {}, +}; interface MirrorModelingElementTypeOptions { - check_parent_symmetry?: boolean - isCentered?(element: OutlinerElement, options?: {center: number}): boolean - getMirroredElement?(element: OutlinerElement, options?: {center: number}): OutlinerElement | false - maintainUV?(element: OutlinerElement, original_data: any): void - discoverConnectionsPreEdit?(mesh: Mesh): {faces: Record, vertices: Record} - updateCounterpart?(original: OutlinerElement, counterpart: OutlinerElement, context: {}): void - createLocalSymmetry?(element: OutlinerElement, cached_data: any): void + check_parent_symmetry?: boolean; + isCentered?(element: OutlinerElement, options?: { center: number }): boolean; + getMirroredElement?( + element: OutlinerElement, + options?: { center: number } + ): OutlinerElement | false; + maintainUV?(element: OutlinerElement, original_data: any): void; + discoverConnectionsPreEdit?(mesh: Mesh): { + faces: Record; + vertices: Record; + }; + updateCounterpart?(original: OutlinerElement, counterpart: OutlinerElement, context: {}): void; + createLocalSymmetry?(element: OutlinerElement, cached_data: any): void; } - -Blockbench.on('init_edit', ({aspects}) => { +Blockbench.on('init_edit', ({ aspects }) => { if (!(BarItems.mirror_modeling as Toggle).value) return; MirrorModeling.initial_transformer_position = Transformer.position.x; @@ -222,27 +257,30 @@ Blockbench.on('init_edit', ({aspects}) => { MirrorModeling.outliner_snapshot = aspects.outliner ? null : Outliner.toJSON(); let edit_side = MirrorModeling.getEditSide(); - aspects.elements.forEach((element) => { + aspects.elements.forEach(element => { if (element.allow_mirror_modeling) { let is_centered = MirrorModeling.isCentered(element); - let data = MirrorModeling.cached_elements[element.uuid] = { + let data = (MirrorModeling.cached_elements[element.uuid] = { is_centered, is_copy: false, counterpart: false as false | OutlinerElement, - pre_part_connections: undefined - }; + pre_part_connections: undefined, + }); if (!is_centered) { data.is_copy = Math.sign(element.getWorldCenter().x) != edit_side; data.counterpart = MirrorModeling.getMirrorElement(element); if (!data.counterpart) data.is_copy = false; } else { if (MirrorModeling.element_types[element.type]?.discoverConnectionsPreEdit) { - data.pre_part_connections = MirrorModeling.element_types[element.type]?.discoverConnectionsPreEdit(element) + data.pre_part_connections = + MirrorModeling.element_types[element.type]?.discoverConnectionsPreEdit( + element + ); } } } - }) + }); } if (aspects.group || aspects.groups) { if (!MirrorModeling.cached_elements) MirrorModeling.cached_elements = {}; @@ -265,35 +303,45 @@ Blockbench.on('init_edit', ({aspects}) => { ) { return true; } - }) + }); if (mirror_group) { MirrorModeling.cached_elements[group.uuid] = { - counterpart: mirror_group - } + counterpart: mirror_group, + }; } - }) + }); } -}) -Blockbench.on('finish_edit', ({aspects}) => { +}); +Blockbench.on('finish_edit', ({ aspects }) => { if (!(BarItems.mirror_modeling as Toggle).value) return; if (aspects.elements) { aspects.elements = aspects.elements.slice(); let static_elements_copy = aspects.elements.slice(); - static_elements_copy.forEach((element) => { - let cached_data = MirrorModeling.cached_elements[element.uuid] + static_elements_copy.forEach(element => { + let cached_data = MirrorModeling.cached_elements[element.uuid]; if (element.allow_mirror_modeling && !element.locked) { let is_centered = MirrorModeling.isCentered(element); - if (is_centered && MirrorModeling.element_types[element.type]?.createLocalSymmetry) { + if ( + is_centered && + MirrorModeling.element_types[element.type]?.createLocalSymmetry + ) { // Complete other side of mesh - MirrorModeling.element_types[element.type].createLocalSymmetry(element, cached_data); + MirrorModeling.element_types[element.type].createLocalSymmetry( + element, + cached_data + ); } if (is_centered) { let mirror_element = cached_data?.counterpart; if (mirror_element && mirror_element.uuid != element.uuid) { - MirrorModeling.insertElementIntoUndo(mirror_element, Undo.current_save.aspects, mirror_element.getUndoCopy()); + MirrorModeling.insertElementIntoUndo( + mirror_element, + Undo.current_save.aspects, + mirror_element.getUndoCopy() + ); mirror_element.remove(); aspects.elements.remove(mirror_element); } @@ -302,7 +350,7 @@ Blockbench.on('finish_edit', ({aspects}) => { MirrorModeling.createClone(element, aspects); } } - }) + }); if (aspects.group || aspects.groups || aspects.outliner) { Canvas.updateAllBones(); } @@ -314,29 +362,33 @@ Blockbench.on('finish_edit', ({aspects}) => { if (mirror_group) { MirrorModeling.updateParentNodeCounterpart(mirror_group, group); } - }) + }); aspects.outliner = true; Canvas.updateAllBones(); } -}) +}); // Register element types MirrorModeling.registerElementType(Cube, { - isCentered(element: Cube, {center}) { - if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) return false; - if (!Math.epsilon(element.to[0], MirrorModeling.flipCoord(element.from[0]), 0.01)) return false; + isCentered(element: Cube, { center }) { + if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) + return false; + if (!Math.epsilon(element.to[0], MirrorModeling.flipCoord(element.from[0]), 0.01)) + return false; return true; }, - getMirroredElement(element: Cube, {center}) { + getMirroredElement(element: Cube, { center }) { let e = 0.01; let symmetry_axes = [0]; let off_axes = [1, 2]; if ( - symmetry_axes.find((axis) => !Math.epsilon(element.from[axis]-center, center-element.to[axis], e)) == undefined && + symmetry_axes.find( + axis => !Math.epsilon(element.from[axis] - center, center - element.to[axis], e) + ) == undefined && off_axes.find(axis => element.rotation[axis]) == undefined && - MirrorModeling.isParentTreeSymmetrical(element, {center}) + MirrorModeling.isParentTreeSymmetrical(element, { center }) ) { return element; } else { @@ -344,11 +396,25 @@ MirrorModeling.registerElementType(Cube, { if ( element2 != element && Math.epsilon(element.inflate, element2.inflate, e) && - off_axes.find(axis => !Math.epsilon(element.from[axis], element2.from[axis], e)) == undefined && - off_axes.find(axis => !Math.epsilon(element.to[axis], element2.to[axis], e)) == undefined && - symmetry_axes.find(axis => !Math.epsilon(element.size(axis), element2.size(axis), e)) == undefined && - symmetry_axes.find(axis => !Math.epsilon(element.to[axis]-center, center-element2.from[axis], e)) == undefined && - symmetry_axes.find(axis => !Math.epsilon(element.rotation[axis], element2.rotation[axis], e)) == undefined + off_axes.find( + axis => !Math.epsilon(element.from[axis], element2.from[axis], e) + ) == undefined && + off_axes.find(axis => !Math.epsilon(element.to[axis], element2.to[axis], e)) == + undefined && + symmetry_axes.find( + axis => !Math.epsilon(element.size(axis), element2.size(axis), e) + ) == undefined && + symmetry_axes.find( + axis => + !Math.epsilon( + element.to[axis] - center, + center - element2.from[axis], + e + ) + ) == undefined && + symmetry_axes.find( + axis => !Math.epsilon(element.rotation[axis], element2.rotation[axis], e) + ) == undefined ) { return element2; } @@ -362,39 +428,60 @@ MirrorModeling.registerElementType(Cube, { uv_offset: original_data.uv_offset, mirror_uv: original_data.mirror_uv, box_uv: original_data.box_uv, - autouv: original_data.autouv + autouv: original_data.autouv, }); - } -}) + }, +}); MirrorModeling.registerElementType(Mesh, { - isCentered(element: Mesh, {center}) { + isCentered(element: Mesh, { center }) { if (Math.roundTo(element.origin[0], 3) != center) return false; - if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) return false; + if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) + return false; return true; }, - getMirroredElement(element: Mesh, {center}) { + getMirroredElement(element: Mesh, { center }) { let e = 0.01; let symmetry_axes = [0]; - let off_axes = [ 1, 2]; + let off_axes = [1, 2]; let ep = 0.5; let this_center = element.getCenter(true); if ( - symmetry_axes.find((axis) => !Math.epsilon(element.origin[axis], center, e)) == undefined && - symmetry_axes.find((axis) => !Math.epsilon(this_center[axis], center, ep)) == undefined && + symmetry_axes.find(axis => !Math.epsilon(element.origin[axis], center, e)) == + undefined && + symmetry_axes.find(axis => !Math.epsilon(this_center[axis], center, ep)) == undefined && off_axes.find(axis => element.rotation[axis]) == undefined && - MirrorModeling.isParentTreeSymmetrical(element, {center}) + MirrorModeling.isParentTreeSymmetrical(element, { center }) ) { return element; } else { for (var element2 of Mesh.all) { let other_center = element2.getCenter(true); - if (Object.keys(element.vertices).length !== Object.keys(element2.vertices).length) continue; + if (Object.keys(element.vertices).length !== Object.keys(element2.vertices).length) + continue; if ( element2 != element && - symmetry_axes.find(axis => !Math.epsilon(element.origin[axis]-center, center-element2.origin[axis], e)) == undefined && - symmetry_axes.find(axis => !Math.epsilon(this_center[axis]-center, center-other_center[axis], ep)) == undefined && - off_axes.find(axis => !Math.epsilon(element.origin[axis], element2.origin[axis], e)) == undefined && - off_axes.find(axis => !Math.epsilon(this_center[axis], other_center[axis], ep)) == undefined + symmetry_axes.find( + axis => + !Math.epsilon( + element.origin[axis] - center, + center - element2.origin[axis], + e + ) + ) == undefined && + symmetry_axes.find( + axis => + !Math.epsilon( + this_center[axis] - center, + center - other_center[axis], + ep + ) + ) == undefined && + off_axes.find( + axis => !Math.epsilon(element.origin[axis], element2.origin[axis], e) + ) == undefined && + off_axes.find( + axis => !Math.epsilon(this_center[axis], other_center[axis], ep) + ) == undefined ) { return element2; } @@ -420,7 +507,7 @@ MirrorModeling.registerElementType(Mesh, { let data = { faces: {}, vertices: {}, - } + }; // Detect vertex counterparts for (let vkey in mesh.vertices) { if (data.vertices[vkey]) continue; @@ -445,7 +532,7 @@ MirrorModeling.registerElementType(Mesh, { let other_vkey = data.vertices[vkey]; if (!other_vkey) return false; return mesh.faces[fkey2].vertices.includes(other_vkey); - }) + }); if (match) { data.faces[fkey] = fkey2; data.faces[fkey2] = fkey; @@ -465,7 +552,7 @@ MirrorModeling.registerElementType(Mesh, { let deleted_vertices = {}; let deleted_vertices_by_position = {}; function positionKey(position) { - return position.map(p => Math.round(p*25)/25).join(','); + return position.map(p => Math.round(p * 25) / 25).join(','); } for (let vkey in mesh.vertices) { if (mesh.vertices[vkey][0] && Math.round(mesh.vertices[vkey][0] * edit_side * 50) < 0) { @@ -485,7 +572,11 @@ MirrorModeling.registerElementType(Mesh, { vertex_counterpart[vkey] = vkey; center_vertices.push(vkey); } else { - let position = [MirrorModeling.flipCoord(vertex[0]), vertex[1], vertex[2]] as ArrayVector3; + let position = [ + MirrorModeling.flipCoord(vertex[0]), + vertex[1], + vertex[2], + ] as ArrayVector3; let vkey_new = deleted_vertices_by_position[positionKey(position)]; if (vkey_new) { mesh.vertices[vkey_new] = position; @@ -501,8 +592,13 @@ MirrorModeling.registerElementType(Mesh, { let deleted_faces = {}; for (let fkey in mesh.faces) { let face = mesh.faces[fkey]; - let deleted_face_vertices = face.vertices.filter(vkey => deleted_vertices[vkey] || center_vertices.includes(vkey)); - if (deleted_face_vertices.length == face.vertices.length && !face.vertices.allAre(vkey => center_vertices.includes(vkey))) { + let deleted_face_vertices = face.vertices.filter( + vkey => deleted_vertices[vkey] || center_vertices.includes(vkey) + ); + if ( + deleted_face_vertices.length == face.vertices.length && + !face.vertices.allAre(vkey => center_vertices.includes(vkey)) + ) { deleted_faces[fkey] = mesh.faces[fkey]; delete mesh.faces[fkey]; } @@ -513,26 +609,37 @@ MirrorModeling.registerElementType(Mesh, { for (let fkey of original_fkeys) { let face = mesh.faces[fkey]; let deleted_face_vertices = face.vertices.filter(vkey => deleted_vertices[vkey]); - if (deleted_face_vertices.length && face.vertices.length != deleted_face_vertices.length*2 && face.vertices.filter(vkey => center_vertices.includes(vkey)).length + deleted_face_vertices.length*2 != face.vertices.length) { + if ( + deleted_face_vertices.length && + face.vertices.length != deleted_face_vertices.length * 2 && + face.vertices.filter(vkey => center_vertices.includes(vkey)).length + + deleted_face_vertices.length * 2 != + face.vertices.length + ) { // cannot flip. restore vertices instead? deleted_face_vertices.forEach(vkey => { mesh.vertices[vkey] = deleted_vertices[vkey]; //delete deleted_vertices[vkey]; - }) - + }); } else if (deleted_face_vertices.length) { // face across zero line //let kept_face_keys = face.vertices.filter(vkey => mesh.vertices[vkey] != 0 && !deleted_face_vertices.includes(vkey)); - let new_counterparts = face.vertices.filter(vkey => !deleted_vertices[vkey]).map(vkey => vertex_counterpart[vkey]); + let new_counterparts = face.vertices + .filter(vkey => !deleted_vertices[vkey]) + .map(vkey => vertex_counterpart[vkey]); face.vertices.forEach((vkey, i) => { if (deleted_face_vertices.includes(vkey)) { // Across //let kept_key = kept_face_keys[i%kept_face_keys.length]; new_counterparts.sort((a, b) => { - let a_distance = Math.pow(mesh.vertices[a][1] - deleted_vertices[vkey][1], 2) + Math.pow(mesh.vertices[a][2] - deleted_vertices[vkey][2], 2); - let b_distance = Math.pow(mesh.vertices[b][1] - deleted_vertices[vkey][1], 2) + Math.pow(mesh.vertices[b][2] - deleted_vertices[vkey][2], 2); + let a_distance = + Math.pow(mesh.vertices[a][1] - deleted_vertices[vkey][1], 2) + + Math.pow(mesh.vertices[a][2] - deleted_vertices[vkey][2], 2); + let b_distance = + Math.pow(mesh.vertices[b][1] - deleted_vertices[vkey][1], 2) + + Math.pow(mesh.vertices[b][2] - deleted_vertices[vkey][2], 2); return b_distance - a_distance; - }) + }); let counterpart = new_counterparts.pop(); if (vkey != counterpart && counterpart) { @@ -541,9 +648,11 @@ MirrorModeling.registerElementType(Mesh, { delete face.uv[vkey]; } } - }) - - } else if (deleted_face_vertices.length == 0 && face.vertices.find((vkey) => vkey != vertex_counterpart[vkey])) { + }); + } else if ( + deleted_face_vertices.length == 0 && + face.vertices.find(vkey => vkey != vertex_counterpart[vkey]) + ) { // Recreate face as mirrored let new_face_key = pre_part_connections && pre_part_connections.faces[fkey]; let original_face = deleted_faces[new_face_key]; @@ -562,7 +671,7 @@ MirrorModeling.registerElementType(Mesh, { new_face.uv[new_vkey] = original_face.uv[original_vkey].slice(); } } - }) + }); new_face.invert(); if (new_face_key) { mesh.faces[new_face_key] = new_face; @@ -574,38 +683,51 @@ MirrorModeling.registerElementType(Mesh, { let selected_vertices = mesh.getSelectedVertices(true); selected_vertices.replace(selected_vertices.filter(vkey => mesh.vertices[vkey])); let selected_edges = mesh.getSelectedEdges(true); - selected_edges.replace(selected_edges.filter(edge => edge.allAre(vkey => mesh.vertices[vkey]))); + selected_edges.replace( + selected_edges.filter(edge => edge.allAre(vkey => mesh.vertices[vkey])) + ); let selected_faces = mesh.getSelectedFaces(true); selected_faces.replace(selected_faces.filter(fkey => mesh.faces[fkey])); - let {preview_controller} = mesh; + let { preview_controller } = mesh; preview_controller.updateGeometry(mesh); preview_controller.updateFaces(mesh); preview_controller.updateUV(mesh); - } -}) + }, +}); MirrorModeling.registerElementType(ArmatureBone, { - isCentered(element: ArmatureBone, {center}) { + isCentered(element: ArmatureBone, { center }) { if (Math.roundTo(element.position[0], 3) != center) return false; - if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) return false; + if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) + return false; return true; }, - getMirroredElement(element: ArmatureBone, {center}) { + getMirroredElement(element: ArmatureBone, { center }) { let e = 0.01; let symmetry_axes = [0]; let off_axes = [1, 2]; if ( - symmetry_axes.find((axis) => !Math.epsilon(element.position[axis], center, e)) == undefined && + symmetry_axes.find(axis => !Math.epsilon(element.position[axis], center, e)) == + undefined && off_axes.find(axis => element.rotation[axis]) == undefined && - MirrorModeling.isParentTreeSymmetrical(element, {center}) + MirrorModeling.isParentTreeSymmetrical(element, { center }) ) { return element; } else { for (let element2 of ArmatureBone.all) { if (element == element2) continue; if ( - symmetry_axes.find(axis => !Math.epsilon(element.position[axis]-center, center-element2.position[axis], e)) == undefined && - off_axes.find(axis => !Math.epsilon(element.position[axis], element2.position[axis], e)) == undefined + symmetry_axes.find( + axis => + !Math.epsilon( + element.position[axis] - center, + center - element2.position[axis], + e + ) + ) == undefined && + off_axes.find( + axis => !Math.epsilon(element.position[axis], element2.position[axis], e) + ) == undefined ) { return element2; } @@ -621,29 +743,39 @@ MirrorModeling.registerElementType(ArmatureBone, { }, updateCounterpart(original, counterpart, context) { // Update vertex weights on off-centered bones - } -}) + }, +}); MirrorModeling.registerElementType(Billboard, { - isCentered(element: Billboard, {center}) { + isCentered(element: Billboard, { center }) { if (Math.roundTo(element.position[0], 3) != center) return false; //if (Math.roundTo(element.rotation[1], 3) || Math.roundTo(element.rotation[2], 3)) return false; return true; }, - getMirroredElement(element: Billboard, {center}) { + getMirroredElement(element: Billboard, { center }) { let e = 0.01; let symmetry_axes = [0]; let off_axes = [1, 2]; if ( - symmetry_axes.find((axis) => !Math.epsilon(element.position[axis], center, e)) == undefined + symmetry_axes.find(axis => !Math.epsilon(element.position[axis], center, e)) == + undefined //off_axes.find(axis => element.rotation[axis]) == undefined ) { return element; } else { - for (let element2 of (Billboard.all as Billboard[])) { + for (let element2 of Billboard.all as Billboard[]) { if (element == element2) continue; if ( - symmetry_axes.find(axis => !Math.epsilon(element.position[axis]-center, center-element2.position[axis], e)) == undefined && - off_axes.find(axis => !Math.epsilon(element.position[axis], element2.position[axis], e)) == undefined + symmetry_axes.find( + axis => + !Math.epsilon( + element.position[axis] - center, + center - element2.position[axis], + e + ) + ) == undefined && + off_axes.find( + axis => !Math.epsilon(element.position[axis], element2.position[axis], e) + ) == undefined ) { return element2; } @@ -655,15 +787,14 @@ MirrorModeling.registerElementType(Billboard, { element.extend({ faces: original_data.faces, }); - } -}) + }, +}); BARS.defineActions(() => { - let toggle = new Toggle('mirror_modeling', { icon: 'align_horizontal_center', category: 'edit', - condition: {modes: ['edit']}, + condition: { modes: ['edit'] }, onChange() { Project.mirror_modeling_enabled = this.value; MirrorModeling.cached_elements = {}; @@ -672,31 +803,36 @@ BARS.defineActions(() => { tool_config: new ToolConfig('mirror_modeling_options', { title: 'action.mirror_modeling', form: { - enabled: {type: 'checkbox', label: 'menu.mirror_painting.enabled', value: false}, - mirror_uv: {type: 'checkbox', label: 'menu.mirror_modeling.mirror_uv', value: true} + enabled: { type: 'checkbox', label: 'menu.mirror_painting.enabled', value: false }, + mirror_uv: { + type: 'checkbox', + label: 'menu.mirror_modeling.mirror_uv', + value: true, + }, }, onOpen() { - this.setFormValues({enabled: toggle.value}, false); + this.setFormValues({ enabled: toggle.value }, false); }, onFormChange(formResult) { if (toggle.value != formResult.enabled) { toggle.trigger(); } - } - }) - }) + }, + }), + }); let allow_toggle = new Toggle('allow_element_mirror_modeling', { icon: 'align_horizontal_center', category: 'edit', - condition: {modes: ['edit'], selected: {element: true}, method: () => toggle.value}, + condition: { modes: ['edit'], selected: { element: true }, method: () => toggle.value }, onChange(value) { Outliner.selected.forEach(element => { - if ('allow_mirror_modeling' in (element.constructor as any).properties == false) return; + if ('allow_mirror_modeling' in (element.constructor as any).properties == false) + return; // @ts-ignore element.allow_mirror_modeling = value; - }) - } - }) + }); + }, + }); Blockbench.on('update_selection', () => { if (!Condition(allow_toggle.condition)) return; // @ts-ignore @@ -705,19 +841,19 @@ BARS.defineActions(() => { allow_toggle.value = !disabled; allow_toggle.updateEnabledState(); } - }) + }); new Action('apply_mirror_modeling', { icon: 'align_horizontal_right', category: 'edit', - condition: {modes: ['edit']}, + condition: { modes: ['edit'] }, click() { let value_before = toggle.value; toggle.value = true; - Undo.initEdit({elements: Outliner.selected, groups: Group.selected}); + Undo.initEdit({ elements: Outliner.selected, groups: Group.selected }); Undo.finishEdit('Applied mirror modeling'); toggle.value = value_before; - } - }) -}) + }, + }); +}); -Object.assign(window, {MirrorModeling}); +Object.assign(window, { MirrorModeling }); diff --git a/js/modeling/weight_paint.ts b/js/modeling/weight_paint.ts index 4d46fad0d..60412cd53 100644 --- a/js/modeling/weight_paint.ts +++ b/js/modeling/weight_paint.ts @@ -4,29 +4,27 @@ import { Armature } from '../outliner/armature'; import { ArmatureBone } from '../outliner/armature_bone'; import { Preview } from '../preview/preview'; -type CanvasClickData = {event: MouseEvent} | { - event: MouseEvent - element: OutlinerElement - face: string - intersects: Array> -} +type CanvasClickData = + | { event: MouseEvent } + | { + event: MouseEvent; + element: OutlinerElement; + face: string; + intersects: Array>; + }; let brush_outline: HTMLElement; function updateBrushOutline(event: PointerEvent) { if (!brush_outline) return; let preview = Preview.selected as Preview; let preview_offset = $(preview.canvas).offset(); - let click_pos = [ - event.clientX - preview_offset.left, - event.clientY - preview_offset.top, - ] + let click_pos = [event.clientX - preview_offset.left, event.clientY - preview_offset.top]; preview.node.append(brush_outline); brush_outline.style.left = click_pos[0] + 'px'; brush_outline.style.top = click_pos[1] + 'px'; } - -let screen_space_vertex_positions: null | Record = null; +let screen_space_vertex_positions: null | Record = null; const raycaster = new THREE.Raycaster(); function updateScreenSpaceVertexPositions(mesh: Mesh) { if (screen_space_vertex_positions) return screen_space_vertex_positions; @@ -37,17 +35,17 @@ function updateScreenSpaceVertexPositions(mesh: Mesh) { let raycasts = 0; screen_space_vertex_positions = {}; - + for (let vkey in mesh.vertices) { let pos = mesh.mesh.localToWorld(vec.fromArray(mesh.vertices[vkey])); if (depth_check) { - raycaster.ray.direction.copy(pos).sub(raycaster.ray.origin) + raycaster.ray.direction.copy(pos).sub(raycaster.ray.origin); const z_distance = raycaster.ray.direction.length(); raycaster.ray.direction.normalize(); let intersection = raycaster.intersectObject(mesh.mesh, false)[0]; raycasts++; - if (intersection && intersection.distance < z_distance-0.001) { + if (intersection && intersection.distance < z_distance - 0.001) { continue; } } @@ -57,8 +55,8 @@ function updateScreenSpaceVertexPositions(mesh: Mesh) { return screen_space_vertex_positions; } Blockbench.on('update_camera_position', () => { - screen_space_vertex_positions = null -}) + screen_space_vertex_positions = null; +}); new Tool('weight_brush', { icon: 'stylus_highlighter', @@ -69,8 +67,8 @@ new Tool('weight_brush', { transformerMode: 'hidden', selectElements: false, modes: ['edit'], - condition: {modes: ['edit'], method: () => Armature.all.length}, - + condition: { modes: ['edit'], method: () => Armature.all.length }, + onCanvasClick(data: CanvasClickData) { if ('element' in data == false) return; let preview = Preview.selected as Preview; @@ -89,17 +87,21 @@ new Tool('weight_brush', { } let undo_tracked = [armature_bone]; - Undo.initEdit({elements: undo_tracked}); - + Undo.initEdit({ elements: undo_tracked }); + let last_click_pos = [0, 0]; - const draw = (event: MouseEvent, data?: CanvasClickData|false) => { + const draw = (event: MouseEvent, data?: CanvasClickData | false) => { let radius = (BarItems.slider_weight_brush_size as NumSlider).get(); let click_pos = [ event.clientX - preview_offset.left, event.clientY - preview_offset.top, ]; let subtract = event.ctrlOrCmd || Pressing.overrides.ctrl; - if (Math.pow(last_click_pos[0]-click_pos[0], 2) + Math.pow(last_click_pos[1]-click_pos[1], 2) < 30) { + if ( + Math.pow(last_click_pos[0] - click_pos[0], 2) + + Math.pow(last_click_pos[1] - click_pos[1], 2) < + 30 + ) { return; } last_click_pos = click_pos; @@ -111,29 +113,35 @@ new Tool('weight_brush', { let vec = new THREE.Vector2(); updateScreenSpaceVertexPositions(mesh); - + for (let vkey in mesh.vertices) { let screen_pos = screen_space_vertex_positions[vkey]; if (!screen_pos) continue; - let distance = vec.set(screen_pos.x - click_pos[0], screen_pos.y - click_pos[1]).length(); + let distance = vec + .set(screen_pos.x - click_pos[0], screen_pos.y - click_pos[1]) + .length(); let base_radius = 0.2; - let falloff = (1-(distance / radius)) * (1 + base_radius); + let falloff = (1 - distance / radius) * (1 + base_radius); let influence = Math.hermiteBlend(Math.clamp(falloff, 0, 1)); let value = armature_bone.vertex_weights[vkey] ?? 0; - + if (event.shiftKey || Pressing.overrides.shift) { influence /= 8; } if (subtract) { - value = value * (1-influence); + value = value * (1 - influence); } else { - value = value + (1-value) * influence; + value = value + (1 - value) * influence; } // Reduce weight on other bones for (let bone of other_bones) { if (bone.vertex_weights[vkey] && !subtract) { - bone.vertex_weights[vkey] = Math.clamp(bone.vertex_weights[vkey] - influence, 0, 1); + bone.vertex_weights[vkey] = Math.clamp( + bone.vertex_weights[vkey] - influence, + 0, + 1 + ); if (Undo.current_save && !undo_tracked.includes(bone)) { Undo.current_save.addElements([bone]); undo_tracked.push(bone); @@ -144,72 +152,80 @@ new Tool('weight_brush', { if (value < 0.04) { delete armature_bone.vertex_weights[vkey]; } else { - armature_bone.vertex_weights[vkey] = value + armature_bone.vertex_weights[vkey] = value; } } // @ts-ignore Mesh.preview_controller.updateGeometry(mesh); - } + }; const stop = (event: MouseEvent) => { document.removeEventListener('pointermove', draw); document.removeEventListener('pointerup', stop); Undo.finishEdit('Paint vertex weights'); - } + }; document.addEventListener('pointermove', draw); document.addEventListener('pointerup', stop); draw(data.event, data); - }, onSelect() { - Canvas.updateView({elements: Mesh.all, element_aspects: {faces: true}}); + Canvas.updateView({ elements: Mesh.all, element_aspects: { faces: true } }); (BarItems.slider_weight_brush_size as NumSlider).update(); Interface.addSuggestedModifierKey('ctrl', 'modifier_actions.subtract'); Interface.addSuggestedModifierKey('shift', 'modifier_actions.reduced_intensity'); // @ts-ignore - ArmatureBone.preview_controller.material.wireframe = ArmatureBone.preview_controller.material_selected.wireframe = true; + ArmatureBone.preview_controller.material.wireframe = + ArmatureBone.preview_controller.material_selected.wireframe = true; - brush_outline = Interface.createElement('div', {id: 'weight_brush_outline'}); + brush_outline = Interface.createElement('div', { id: 'weight_brush_outline' }); document.addEventListener('pointermove', updateBrushOutline); }, onUnselect() { setTimeout(() => { - Canvas.updateView({elements: Mesh.all, element_aspects: {faces: true}}); + Canvas.updateView({ elements: Mesh.all, element_aspects: { faces: true } }); }, 0); Interface.removeSuggestedModifierKey('ctrl', 'modifier_actions.subtract'); Interface.removeSuggestedModifierKey('shift', 'modifier_actions.reduced_intensity'); // @ts-ignore - ArmatureBone.preview_controller.material.wireframe = ArmatureBone.preview_controller.material_selected.wireframe = false; + ArmatureBone.preview_controller.material.wireframe = + ArmatureBone.preview_controller.material_selected.wireframe = false; - if (brush_outline) brush_outline.remove() + if (brush_outline) brush_outline.remove(); document.removeEventListener('pointermove', updateBrushOutline); - } -}) + }, +}); let slider = new NumSlider('slider_weight_brush_size', { condition: () => Toolbox?.selected?.id == 'weight_brush', tool_setting: 'weight_brush_size', category: 'edit', settings: { - min: 1, max: 1024, interval: 1, default: 50, - } -}) -slider.on('change', (data: {number: number}) => { + min: 1, + max: 1024, + interval: 1, + default: 50, + }, +}); +slider.on('change', (data: { number: number }) => { if (brush_outline) { brush_outline.style.setProperty('--radius', data.number.toString()); } -}) +}); new Toggle('weight_brush_xray', { icon: 'disabled_visible', category: 'edit', condition: () => Toolbox?.selected?.id == 'weight_brush', -}) +}); const vertex_weight_view_modes = ['vertex_weight', 'weighted_bone_colors']; function updateWeightPreview() { - if (Toolbox.selected.id == 'weight_brush' || + if ( + Toolbox.selected.id == 'weight_brush' || vertex_weight_view_modes.includes(Project.view_mode) ) { - Canvas.updateView({elements: Mesh.all.filter(mesh => mesh.getArmature()), element_aspects: {geometry: true}}); + Canvas.updateView({ + elements: Mesh.all.filter(mesh => mesh.getArmature()), + element_aspects: { geometry: true }, + }); if (Modes.animate) Animator.preview(); } } diff --git a/js/modes.ts b/js/modes.ts index 2ff878f08..c2868a2da 100644 --- a/js/modes.ts +++ b/js/modes.ts @@ -1,47 +1,47 @@ -import { Vue } from "./lib/libs" -import { Blockbench } from "./api" -import { Interface, Panels } from "./interface/interface" -import { MenuBar } from "./interface/menu_bar" -import { updatePanelSelector, updateSidebarOrder } from "./interface/panels" -import { Prop } from "./misc" -import { Outliner } from "./outliner/outliner" -import { ReferenceImage } from "./preview/reference_images" +import { Vue } from './lib/libs'; +import { Blockbench } from './api'; +import { Interface, Panels } from './interface/interface'; +import { MenuBar } from './interface/menu_bar'; +import { updatePanelSelector, updateSidebarOrder } from './interface/panels'; +import { Prop } from './misc'; +import { Outliner } from './outliner/outliner'; +import { ReferenceImage } from './preview/reference_images'; interface ModeOptions { - id?: string - name?: string - icon?: string - default_tool?: string - selectElements?: boolean - category?: string + id?: string; + name?: string; + icon?: string; + default_tool?: string; + selectElements?: boolean; + category?: string; /** * Hide certain types of nodes in the outliner, like cubes and meshes in animation mode */ - hidden_node_types?: string[] - hide_toolbars?: boolean - hide_sidebars?: boolean - hide_status_bar?: boolean - condition?: ConditionResolvable - component?: Vue.Component - onSelect?(): void - onUnselect?(): void + hidden_node_types?: string[]; + hide_toolbars?: boolean; + hide_sidebars?: boolean; + hide_status_bar?: boolean; + condition?: ConditionResolvable; + component?: Vue.Component; + onSelect?(): void; + onUnselect?(): void; } export class Mode extends KeybindItem { - id: string - name: string - icon: string - selected: boolean - tool: string - default_tool?: string - selectElements: boolean - hidden_node_types: string[] - hide_toolbars: boolean - hide_sidebars: boolean - hide_status_bar: boolean - vue?: Vue - - onSelect?: () => void - onUnselect?: () => void + id: string; + name: string; + icon: string; + selected: boolean; + tool: string; + default_tool?: string; + selectElements: boolean; + hidden_node_types: string[]; + hide_toolbars: boolean; + hide_sidebars: boolean; + hide_status_bar: boolean; + vue?: Vue; + + onSelect?: () => void; + onUnselect?: () => void; constructor(id: string, data: ModeOptions) { if (typeof id == 'object') { @@ -49,19 +49,20 @@ export class Mode extends KeybindItem { id = data.id; } // @ts-ignore - super(id, data) + super(id, data); this.id = id; - this.name = data.name || tl('mode.'+this.id); + this.name = data.name || tl('mode.' + this.id); this.icon = data.icon || 'video_label'; - this.selected = false + this.selected = false; this.default_tool = data.default_tool; - this.selectElements = data.selectElements !== false - this.hidden_node_types = data.hidden_node_types instanceof Array ? data.hidden_node_types.slice() : []; + this.selectElements = data.selectElements !== false; + this.hidden_node_types = + data.hidden_node_types instanceof Array ? data.hidden_node_types.slice() : []; - this.hide_toolbars = data.hide_toolbars - this.hide_sidebars = data.hide_sidebars - this.hide_status_bar = data.hide_status_bar + this.hide_toolbars = data.hide_toolbars; + this.hide_sidebars = data.hide_sidebars; + this.hide_status_bar = data.hide_status_bar; this.condition = data.condition; this.onSelect = data.onSelect; @@ -76,7 +77,7 @@ export class Mode extends KeybindItem { node.appendChild(mount); document.getElementById('center').appendChild(node); - this.vue = new Vue(data.component) + this.vue = new Vue(data.component); this.vue.$mount(mount); } } @@ -99,35 +100,41 @@ export class Mode extends KeybindItem { MenuBar.mode_switcher_button.classList.remove('hidden'); } - $('#main_toolbar .toolbar_wrapper').css('visibility', this.hide_toolbars ? 'hidden' : 'visible'); + $('#main_toolbar .toolbar_wrapper').css( + 'visibility', + this.hide_toolbars ? 'hidden' : 'visible' + ); $('#status_bar').css('display', this.hide_status_bar ? 'none' : 'flex'); Outliner.vue.options.hidden_types.replace(this.hidden_node_types); if (typeof this.onSelect === 'function') { - this.onSelect() + this.onSelect(); } updatePanelSelector(); ReferenceImage.updateAll(); - if (Interface.Panels[Prop.active_panel] && !Condition(Interface.Panels[Prop.active_panel].condition)) { + if ( + Interface.Panels[Prop.active_panel] && + !Condition(Interface.Panels[Prop.active_panel].condition) + ) { Prop.active_panel = 'preview'; } - + UVEditor.beforeMoving(); if (!Blockbench.isMobile) { for (let id in Panels) { let panel = Panels[id]; panel.updatePositionData(); panel.updateSlot(); - } updateSidebarOrder(); } - Canvas.updateRenderSides() + Canvas.updateRenderSides(); let selected_tool = BarItems[this.tool] instanceof Tool && BarItems[this.tool]; - let default_tool = BarItems[this.default_tool] instanceof Tool && BarItems[this.default_tool]; + let default_tool = + BarItems[this.default_tool] instanceof Tool && BarItems[this.default_tool]; if (selected_tool instanceof Tool && Condition(selected_tool.condition)) { selected_tool.select(); } else if (default_tool instanceof Tool) { @@ -137,15 +144,15 @@ export class Mode extends KeybindItem { } updateInterface(); updateSelection(); - Blockbench.dispatchEvent('select_mode', {mode: this}) + Blockbench.dispatchEvent('select_mode', { mode: this }); } /**Unselects the mode */ unselect() { delete Modes[this.id]; Modes.previous_id = this.id; if (typeof this.onUnselect === 'function') { - Blockbench.dispatchEvent('unselect_mode', {mode: this}) - this.onUnselect() + Blockbench.dispatchEvent('unselect_mode', { mode: this }); + this.onUnselect(); } this.selected = false; Mode.selected = Modes.selected = false; @@ -153,7 +160,7 @@ export class Mode extends KeybindItem { /**Activates the mode */ trigger() { if (Condition(this.condition)) { - this.select() + this.select(); } } delete() { @@ -166,7 +173,7 @@ export class Mode extends KeybindItem { } export const Modes = { get id() { - return Mode.selected ? Mode.selected.id : '' + return Mode.selected ? Mode.selected.id : ''; }, vue: null as Vue | null, selected: false as boolean | Mode, @@ -194,14 +201,14 @@ export const Modes = { } let menu = new Menu(entries).open(button); return menu; - } + }, }; -onVueSetup(function() { +onVueSetup(function () { if (!Blockbench.isMobile) { Modes.vue = new Vue({ el: '#mode_selector', data: { - options: Modes.options + options: Modes.options, }, methods: { showModes() { @@ -211,9 +218,9 @@ onVueSetup(function() { } return count > 1; }, - Condition - } - }) + Condition, + }, + }); } else { document.getElementById('mode_selector').remove(); } @@ -221,5 +228,5 @@ onVueSetup(function() { Object.assign(window, { Mode, - Modes + Modes, }); diff --git a/js/native_apis.ts b/js/native_apis.ts index e208e2110..b8990704d 100644 --- a/js/native_apis.ts +++ b/js/native_apis.ts @@ -1,25 +1,40 @@ -import { BBPlugin } from "./plugin_loader"; -import { createScopedFS } from "./util/scoped_fs"; +import { BBPlugin } from './plugin_loader'; +import { createScopedFS } from './util/scoped_fs'; -const electron: typeof import("@electron/remote") = require('@electron/remote'); -const {clipboard, shell, nativeImage, ipcRenderer, webUtils} = require('electron') as typeof import('electron'); +const electron: typeof import('@electron/remote') = require('@electron/remote'); +const { clipboard, shell, nativeImage, ipcRenderer, webUtils } = + require('electron') as typeof import('electron'); const app = electron.app; -const fs: typeof import("node:fs") = require('node:fs'); -const NodeBuffer: typeof import("node:buffer") = require('buffer'); -const zlib: typeof import("node:zlib") = require('zlib'); -const child_process: typeof import("node:child_process") = require('child_process'); -const https: typeof import("node:https") = require('https'); -const PathModule: typeof import("node:path") = require('path'); -const os: typeof import("node:os") = require('os'); +const fs: typeof import('node:fs') = require('node:fs'); +const NodeBuffer: typeof import('node:buffer') = require('buffer'); +const zlib: typeof import('node:zlib') = require('zlib'); +const child_process: typeof import('node:child_process') = require('child_process'); +const https: typeof import('node:https') = require('https'); +const PathModule: typeof import('node:path') = require('path'); +const os: typeof import('node:os') = require('os'); const currentwindow = electron.getCurrentWindow(); const dialog = electron.dialog; /** @internal */ export { - electron, clipboard, shell, ipcRenderer, webUtils, - app, fs, NodeBuffer, zlib, child_process, https, PathModule, os, currentwindow, dialog, - Buffer, nativeImage -} + electron, + clipboard, + shell, + ipcRenderer, + webUtils, + app, + fs, + NodeBuffer, + zlib, + child_process, + https, + PathModule, + os, + currentwindow, + dialog, + Buffer, + nativeImage, +}; /** * @internal @@ -27,7 +42,6 @@ export { export const process = window.process; delete window.process; - const { stringify, parse } = JSON; const SAFE_APIS = [ @@ -61,25 +75,30 @@ const API_DESCRIPTIONS = { dialog: 'open native dialogs', }; type PluginPermissions = { - allowed: Record -} + allowed: Record; +}; const PLUGIN_SETTINGS_PATH = PathModule.join(app.getPath('userData'), 'plugin_permissions.json'); const PluginSettings: Record = {}; try { - let content = fs.readFileSync(PLUGIN_SETTINGS_PATH, {encoding: 'utf-8'}); + let content = fs.readFileSync(PLUGIN_SETTINGS_PATH, { encoding: 'utf-8' }); let data = parse(content); if (typeof data == 'object') { Object.assign(PluginSettings, data); } } catch (err) {} function savePluginSettings() { - fs.writeFileSync(PLUGIN_SETTINGS_PATH, stringify(PluginSettings), {encoding: 'utf-8'}); + fs.writeFileSync(PLUGIN_SETTINGS_PATH, stringify(PluginSettings), { encoding: 'utf-8' }); } interface GetModuleOptions { - scope?: string - message?: string + scope?: string; + message?: string; } -function getModule(module_name: string, plugin_id: string, plugin: InstanceType, options: GetModuleOptions = {}) { +function getModule( + module_name: string, + plugin_id: string, + plugin: InstanceType, + options: GetModuleOptions = {} +) { const no_namespace_name = module_name.replace(/^node:/, ''); if (SAFE_APIS.includes(no_namespace_name)) { return originalRequire(module_name); @@ -115,29 +134,25 @@ function getModule(module_name: string, plugin_id: string, plugin: InstanceType< type: 'question', noLink: true, cancelId: 3, - buttons: [ - 'Allow once', - 'Always allow for this plugin', - 'Uninstall plugin', - 'Deny', - ] + buttons: ['Allow once', 'Always allow for this plugin', 'Uninstall plugin', 'Deny'], }); enum Result { Once = 0, Always = 1, Uninstall = 2, - Deny = 3 + Deny = 3, } if (result == Result.Always) { // Save permission if (!PluginSettings[plugin_id]?.allowed) { PluginSettings[plugin_id] = { - allowed: {} - } + allowed: {}, + }; } let allowed = PluginSettings[plugin_id].allowed; if (module_name == 'fs' && options2.scope) { - if (typeof allowed[module_name] != 'object') allowed[module_name] = {directories: []} + if (typeof allowed[module_name] != 'object') + allowed[module_name] = { directories: [] }; allowed[module_name].directories.push(options2.scope); } else { allowed[module_name] = true; @@ -150,7 +165,7 @@ function getModule(module_name: string, plugin_id: string, plugin: InstanceType< }, 20); } if (!(result == Result.Once || result == Result.Always)) { - console.warn(`User denied access to "${module_name}" module`) + console.warn(`User denied access to "${module_name}" module`); return; } @@ -179,7 +194,7 @@ export function getPluginScopedRequire(plugin: InstanceType) { const plugin_id = plugin.id; return function require(module_id: string, options?: GetModuleOptions) { return getModule(module_id, plugin_id, plugin, options); - } + }; } const originalRequire = window.require; delete window.require; @@ -192,7 +207,7 @@ export function revokePluginPermissions(plugin: InstanceType): } export function getPluginPermissions(plugin: InstanceType) { let data = PluginSettings[plugin.id]?.allowed; - if (data) return parse(stringify(data)) as Record; + if (data) return parse(stringify(data)) as Record; } export const SystemInfo = { @@ -201,7 +216,7 @@ export const SystemInfo = { appdata_directory: electron.process.env.APPDATA, arch: process.arch, os_version: os.version(), -} +}; /** * @internal @@ -214,9 +229,9 @@ export function getPCUsername() { */ export function openFileInEditor(file_path: string, editor: string) { if (SystemInfo.platform == 'darwin') { - child_process.exec(`open '${file_path}' -a '${editor}'`) + child_process.exec(`open '${file_path}' -a '${editor}'`); } else { - child_process.spawn(editor, [file_path]) + child_process.spawn(editor, [file_path]); } } @@ -229,4 +244,3 @@ Object.assign(window, { * TODO: * - Ensure it still works in the web app */ - diff --git a/js/native_apis_web.ts b/js/native_apis_web.ts index 5e62b084a..29300481a 100644 --- a/js/native_apis_web.ts +++ b/js/native_apis_web.ts @@ -24,7 +24,7 @@ export { NULL as process, NULL as SystemInfo, NULL as revokePluginPermissions, -} +}; /** * @internal @@ -32,5 +32,3 @@ export { export function getPCUsername() { return ''; } - - diff --git a/js/outliner/armature.ts b/js/outliner/armature.ts index cee7eaf02..94e4c7991 100644 --- a/js/outliner/armature.ts +++ b/js/outliner/armature.ts @@ -1,21 +1,21 @@ -import { Blockbench } from "../api"; -import { THREE, Vue } from "../lib/libs"; -import { ArmatureBone } from "./armature_bone"; +import { Blockbench } from '../api'; +import { THREE, Vue } from '../lib/libs'; +import { ArmatureBone } from './armature_bone'; interface ArmatureOptions { - name?: string - export?: boolean - locked?: boolean - visibility?: boolean + name?: string; + export?: boolean; + locked?: boolean; + visibility?: boolean; } export class Armature extends OutlinerElement { - children: ArmatureBone[] - isOpen: boolean - visibility: boolean - origin: ArrayVector3 + children: ArmatureBone[]; + isOpen: boolean; + visibility: boolean; + origin: ArrayVector3; - static preview_controller: NodePreviewController + static preview_controller: NodePreviewController; constructor(data?: ArmatureOptions, uuid?: UUID) { super(data, uuid); @@ -24,7 +24,7 @@ export class Armature extends OutlinerElement { Armature.properties[key].reset(this); } - this.name = 'armature' + this.name = 'armature'; this.children = []; this.selected = false; this.locked = false; @@ -35,20 +35,20 @@ export class Armature extends OutlinerElement { this.origin = [0, 0, 0]; if (typeof data === 'object') { - this.extend(data) + this.extend(data); } else if (typeof data === 'string') { - this.name = data + this.name = data; } } extend(object: ArmatureOptions) { for (let key in Armature.properties) { - Armature.properties[key].merge(this, object) + Armature.properties[key].merge(this, object); } - Merge.string(this, object, 'name') + Merge.string(this, object, 'name'); this.sanitizeName(); - Merge.boolean(this, object, 'export') - Merge.boolean(this, object, 'locked') - Merge.boolean(this, object, 'visibility') + Merge.boolean(this, object, 'export'); + Merge.boolean(this, object, 'locked'); + Merge.boolean(this, object, 'visibility'); return this; } getMesh() { @@ -76,21 +76,21 @@ export class Armature extends OutlinerElement { let match = true; for (let i = 0; i < selected.length; i++) { if (!selected[i].isChildOf(scope, 128)) { - return false + return false; } } this.forEachChild(obj => { if (!obj.selected) { - match = false + match = false; } - }) + }); return match; } openUp() { - this.isOpen = true - this.updateElement() + this.isOpen = true; + this.updateElement(); if (this.parent && this.parent !== 'root') { - this.parent.openUp() + this.parent.openUp(); } return this; } @@ -121,9 +121,9 @@ export class Armature extends OutlinerElement { return copy; } getChildlessCopy(keep_uuid?: boolean) { - let base_armature = new Armature({name: this.name}, keep_uuid ? this.uuid : null); + let base_armature = new Armature({ name: this.name }, keep_uuid ? this.uuid : null); for (let key in Armature.properties) { - Armature.properties[key].copy(this, base_armature) + Armature.properties[key].copy(this, base_armature); } base_armature.name = this.name; base_armature.locked = this.locked; @@ -132,18 +132,27 @@ export class Armature extends OutlinerElement { base_armature.isOpen = this.isOpen; return base_armature; } - forEachChild(cb: ((element: OutlinerElement) => void), type?: typeof OutlinerNode, forSelf?: boolean) { - let i = 0 + forEachChild( + cb: (element: OutlinerElement) => void, + type?: typeof OutlinerNode, + forSelf?: boolean + ) { + let i = 0; if (forSelf) { - cb(this) + cb(this); } while (i < this.children.length) { - if (!type || (type instanceof Array ? type.find(t2 => this.children[i] instanceof t2) : this.children[i] instanceof type)) { + if ( + !type || + (type instanceof Array + ? type.find(t2 => this.children[i] instanceof t2) + : this.children[i] instanceof type) + ) { // @ts-ignore - cb(this.children[i]) + cb(this.children[i]); } if (this.children[i].type === 'armature_bone') { - this.children[i].forEachChild(cb, type) + this.children[i].forEachChild(cb, type); } i++; } @@ -167,34 +176,34 @@ export class Armature extends OutlinerElement { parent: true, child_types: ['armature_bone', 'mesh'], hide_in_screenshot: true, - } - + }; + public title = tl('data.armature'); public type = 'armature'; public icon = 'accessibility'; - public name_regex = () => Format.bone_rig ? 'a-zA-Z0-9_' : false; - public buttons = [ - Outliner.buttons.locked, - Outliner.buttons.visibility, - ]; + public name_regex = () => (Format.bone_rig ? 'a-zA-Z0-9_' : false); + public buttons = [Outliner.buttons.locked, Outliner.buttons.visibility]; public menu = new Menu([ 'add_armature_bone', ...Outliner.control_menu_group, new MenuSeparator('settings'), new MenuSeparator('manage'), 'rename', - 'delete' + 'delete', ]); - - static all: Armature[] - static selected: Armature[] + + static all: Armature[]; + static selected: Armature[]; } OutlinerElement.registerType(Armature, 'armature'); new NodePreviewController(Armature, { setup(element: Armature) { - let object_3d = new THREE.Object3D() as {isElement: boolean, no_export: boolean} & THREE.Object3D; + let object_3d = new THREE.Object3D() as { + isElement: boolean; + no_export: boolean; + } & THREE.Object3D; object_3d.rotation.order = 'ZYX'; object_3d.uuid = element.uuid.toUpperCase(); object_3d.name = element.name; @@ -205,31 +214,34 @@ new NodePreviewController(Armature, { this.updateTransform(element); - this.dispatchEvent('setup', {element}); + this.dispatchEvent('setup', { element }); }, updateTransform(element: Armature) { let mesh = element.mesh; - if (Format.bone_rig && element.parent instanceof OutlinerNode && element.parent.scene_object) { + if ( + Format.bone_rig && + element.parent instanceof OutlinerNode && + element.parent.scene_object + ) { element.parent.scene_object.add(mesh); } else if (mesh.parent !== Project.model_3d) { - Project.model_3d.add(mesh) + Project.model_3d.add(mesh); } mesh.updateMatrixWorld(); - this.dispatchEvent('update_transform', {element}); - } -}) - + this.dispatchEvent('update_transform', { element }); + }, +}); -BARS.defineActions(function() { +BARS.defineActions(function () { new Action('add_armature', { icon: 'accessibility', category: 'edit', condition: () => Modes.edit && Project.format?.armature_rig, click: function () { - Undo.initEdit({outliner: true, elements: []}); + Undo.initEdit({ outliner: true, elements: [] }); let add_to_node = Outliner.selected[0] || Group.first_selected; if (!add_to_node && selected.length) { add_to_node = selected.last(); @@ -248,19 +260,19 @@ BARS.defineActions(function() { bone.addTo(armature).init(); // @ts-ignore - Undo.finishEdit('Add armature', {outliner: true, elements: [armature, bone]}); - Vue.nextTick(function() { - updateSelection() + Undo.finishEdit('Add armature', { outliner: true, elements: [armature, bone] }); + Vue.nextTick(function () { + updateSelection(); if (settings.create_rename.value) { - armature.rename() + armature.rename(); } - armature.showInOutliner() - Blockbench.dispatchEvent( 'add_armature', {object: armature} ) - }) - } - }) -}) + armature.showInOutliner(); + Blockbench.dispatchEvent('add_armature', { object: armature }); + }); + }, + }); +}); Object.assign(window, { - Armature -}) + Armature, +}); diff --git a/js/outliner/armature_bone.ts b/js/outliner/armature_bone.ts index ba0832817..2fafa533f 100644 --- a/js/outliner/armature_bone.ts +++ b/js/outliner/armature_bone.ts @@ -1,40 +1,38 @@ -import { Animation } from "../animations/animation"; -import { Blockbench } from "../api"; -import { THREE } from "../lib/libs"; -import { flipNameOnAxis } from "../modeling/transform"; -import { Armature } from "./armature"; -import { Vue } from '../lib/libs' +import { Animation } from '../animations/animation'; +import { Blockbench } from '../api'; +import { THREE } from '../lib/libs'; +import { flipNameOnAxis } from '../modeling/transform'; +import { Armature } from './armature'; +import { Vue } from '../lib/libs'; interface ArmatureBoneOptions { - name?: string - export?: boolean - locked?: boolean - visibility?: boolean - origin?: ArrayVector3 - rotation?: ArrayVector3 - vertex_weights?: Record - length?: number - width?: number - connected?: boolean - color?: number + name?: string; + export?: boolean; + locked?: boolean; + visibility?: boolean; + origin?: ArrayVector3; + rotation?: ArrayVector3; + vertex_weights?: Record; + length?: number; + width?: number; + connected?: boolean; + color?: number; } - export class ArmatureBone extends OutlinerElement { - children: ArmatureBone[] - isOpen: boolean - visibility: boolean - origin: ArrayVector3 - rotation: ArrayVector3 - vertex_weights: Record - length: number - width: number - connected: boolean - color: number - old_size?: number - + children: ArmatureBone[]; + isOpen: boolean; + visibility: boolean; + origin: ArrayVector3; + rotation: ArrayVector3; + vertex_weights: Record; + length: number; + width: number; + connected: boolean; + color: number; + old_size?: number; - static preview_controller: NodePreviewController + static preview_controller: NodePreviewController; constructor(data?: ArmatureBoneOptions, uuid?: UUID) { super(data, uuid); @@ -43,8 +41,8 @@ export class ArmatureBone extends OutlinerElement { ArmatureBone.properties[key].reset(this); } - this.name = 'bone' - this.children = [] + this.name = 'bone'; + this.children = []; this.selected = false; this.locked = false; this.export = true; @@ -52,12 +50,12 @@ export class ArmatureBone extends OutlinerElement { this.isOpen = false; this.visibility = true; this.vertex_weights = {}; - this.color = Math.floor(Math.random()*markerColors.length); + this.color = Math.floor(Math.random() * markerColors.length); if (typeof data === 'object') { - this.extend(data) + this.extend(data); } else if (typeof data === 'string') { - this.name = data + this.name = data; } } get position() { @@ -65,13 +63,13 @@ export class ArmatureBone extends OutlinerElement { } extend(object: ArmatureBoneOptions) { for (let key in ArmatureBone.properties) { - ArmatureBone.properties[key].merge(this, object) + ArmatureBone.properties[key].merge(this, object); } - Merge.string(this, object, 'name') + Merge.string(this, object, 'name'); this.sanitizeName(); - Merge.boolean(this, object, 'export') - Merge.boolean(this, object, 'locked') - Merge.boolean(this, object, 'visibility') + Merge.boolean(this, object, 'export'); + Merge.boolean(this, object, 'locked'); + Merge.boolean(this, object, 'visibility'); return this; } getArmature(): Armature { @@ -110,14 +108,14 @@ export class ArmatureBone extends OutlinerElement { let match = true; for (let i = 0; i < selected.length; i++) { if (!selected[i].isChildOf(scope, 128)) { - return false + return false; } } this.forEachChild(obj => { if (!obj.selected) { - match = false + match = false; } - }) + }); return match; } openUp() { @@ -130,16 +128,16 @@ export class ArmatureBone extends OutlinerElement { } transferOrigin(origin: ArrayVector3) { if (!this.mesh) return; - let q = new THREE.Quaternion().copy(this.mesh.quaternion) + let q = new THREE.Quaternion().copy(this.mesh.quaternion); let shift = new THREE.Vector3( this.origin[0] - origin[0], this.origin[1] - origin[1], - this.origin[2] - origin[2], - ) - let dq = new THREE.Vector3().copy(shift) - dq.applyQuaternion(q) - shift.sub(dq) - shift.applyQuaternion(q.invert()) + this.origin[2] - origin[2] + ); + let dq = new THREE.Vector3().copy(shift); + dq.applyQuaternion(q); + shift.sub(dq); + shift.applyQuaternion(q.invert()); this.origin.V3_set(origin); function iterateChild(obj: ArmatureBone) { @@ -150,7 +148,7 @@ export class ArmatureBone extends OutlinerElement { } this.children.forEach(child => iterateChild(child)); - Canvas.updatePositions() + Canvas.updatePositions(); return this; } getWorldCenter(): THREE.Vector3 { @@ -159,11 +157,11 @@ export class ArmatureBone extends OutlinerElement { return pos; } flip(axis: number, center: number): this { - var offset = this.position[axis] - center + var offset = this.position[axis] - center; this.position[axis] = center - offset; this.rotation.forEach((n, i) => { if (i != axis) this.rotation[i] = -n; - }) + }); // Name flipNameOnAxis(this, axis); @@ -171,8 +169,8 @@ export class ArmatureBone extends OutlinerElement { this.preview_controller.updateTransform(this); return this; } - size(): ArrayVector3 - size(axis: axisLetter): number + size(): ArrayVector3; + size(axis: axisLetter): number; size(axis?: axisLetter): number | ArrayVector3 { if (typeof axis == 'number') { return axis == 1 ? this.length : this.width; @@ -182,7 +180,11 @@ export class ArmatureBone extends OutlinerElement { getSize(axis) { return this.size(axis); } - resize(move_value: number | ((input: number) => number), axis_number?: axisNumber, invert?: boolean) { + resize( + move_value: number | ((input: number) => number), + axis_number?: axisNumber, + invert?: boolean + ) { if (axis_number == 1) { let previous_length = this.old_size ?? this.length; if (typeof move_value == 'function') { @@ -207,7 +209,7 @@ export class ArmatureBone extends OutlinerElement { // Update vertex colors Canvas.updateView({ elements: Mesh.all.filter(mesh => armature && mesh.getArmature() == armature), - element_aspects: {geometry: true} + element_aspects: { geometry: true }, }); return this; } @@ -238,9 +240,9 @@ export class ArmatureBone extends OutlinerElement { return copy; } getChildlessCopy(keep_uuid: boolean = false) { - let base_bone = new ArmatureBone({name: this.name}, keep_uuid ? this.uuid : null); + let base_bone = new ArmatureBone({ name: this.name }, keep_uuid ? this.uuid : null); for (let key in ArmatureBone.properties) { - ArmatureBone.properties[key].copy(this, base_bone) + ArmatureBone.properties[key].copy(this, base_bone); } base_bone.name = this.name; base_bone.origin.V3_set(this.origin); @@ -251,17 +253,22 @@ export class ArmatureBone extends OutlinerElement { base_bone.isOpen = this.isOpen; return base_bone; } - forEachChild(cb: ((element: ArmatureBone) => void), type?: any, forSelf?: boolean) { - let i = 0 + forEachChild(cb: (element: ArmatureBone) => void, type?: any, forSelf?: boolean) { + let i = 0; if (forSelf) { - cb(this) + cb(this); } while (i < this.children.length) { - if (!type || (type instanceof Array ? type.find(t2 => this.children[i] instanceof t2) : this.children[i] instanceof type)) { - cb(this.children[i]) + if ( + !type || + (type instanceof Array + ? type.find(t2 => this.children[i] instanceof t2) + : this.children[i] instanceof type) + ) { + cb(this.children[i]); } if (this.children[i].type === 'armature_bone') { - this.children[i].forEachChild(cb, type) + this.children[i].forEachChild(cb, type); } i++; } @@ -277,64 +284,70 @@ export class ArmatureBone extends OutlinerElement { select_children: 'self_first', hide_in_screenshot: true, marker_color: true, - } - static all: ArmatureBone[] - static selected: ArmatureBone[] - + }; + static all: ArmatureBone[]; + static selected: ArmatureBone[]; + public title = tl('data.armature_bone'); public type = 'armature_bone'; public icon = 'humerus'; - public name_regex = () => Format.bone_rig ? 'a-zA-Z0-9_' : false; - public buttons = [ - Outliner.buttons.locked, - Outliner.buttons.visibility, - ]; + public name_regex = () => (Format.bone_rig ? 'a-zA-Z0-9_' : false); + public buttons = [Outliner.buttons.locked, Outliner.buttons.visibility]; public menu = new Menu([ 'add_armature_bone', ...Outliner.control_menu_group, new MenuSeparator('settings'), 'set_element_marker_color', - "randomize_marker_colors", + 'randomize_marker_colors', 'apply_animation_preset', new MenuSeparator('manage'), 'rename', - 'delete' + 'delete', ]); } ArmatureBone.addBehaviorOverride({ - condition: {features: ['bone_rig']}, + condition: { features: ['bone_rig'] }, behavior: { - unique_name: true - } -}) + unique_name: true, + }, +}); OutlinerElement.registerType(ArmatureBone, 'armature_bone'); -new Property(ArmatureBone, 'vector', 'origin', {default: [0, 0, 0]}); +new Property(ArmatureBone, 'vector', 'origin', { default: [0, 0, 0] }); new Property(ArmatureBone, 'vector', 'rotation'); -new Property(ArmatureBone, 'number', 'length', {default: 8}); -new Property(ArmatureBone, 'number', 'width', {default: 2}); +new Property(ArmatureBone, 'number', 'length', { default: 8 }); +new Property(ArmatureBone, 'number', 'width', { default: 2 }); new Property(ArmatureBone, 'boolean', 'connected', { default: true, inputs: { element_panel: { - input: {label: 'armature_bone.connected', type: 'checkbox'}, + input: { label: 'armature_bone.connected', type: 'checkbox' }, onChange() { let parents = []; ArmatureBone.selected.forEach(b => { - if (b.parent instanceof ArmatureBone) parents.safePush(b.parent) + if (b.parent instanceof ArmatureBone) parents.safePush(b.parent); }); - console.log(parents) - Canvas.updateView({elements: parents, element_aspects: {transform: true}}); - } - } - } + console.log(parents); + Canvas.updateView({ elements: parents, element_aspects: { transform: true } }); + }, + }, + }, }); new Property(ArmatureBone, 'number', 'color'); new Property(ArmatureBone, 'object', 'vertex_weights'); -type FakeObjectType = {isElement: boolean, no_export: boolean, fix_position: THREE.Vector3, fix_rotation: THREE.Euler, inverse_bind_matrix: THREE.Matrix4}; -type PreviewControllerType = (NodePreviewController & {material: THREE.MeshLambertMaterial, material_selected: THREE.MeshLambertMaterial}); +type FakeObjectType = { + isElement: boolean; + no_export: boolean; + fix_position: THREE.Vector3; + fix_rotation: THREE.Euler; + inverse_bind_matrix: THREE.Matrix4; +}; +type PreviewControllerType = NodePreviewController & { + material: THREE.MeshLambertMaterial; + material_selected: THREE.MeshLambertMaterial; +}; new NodePreviewController(ArmatureBone, { material: new THREE.MeshLambertMaterial({ color: 0xc8c9cb, @@ -361,31 +374,28 @@ new NodePreviewController(ArmatureBone, { Project.nodes_3d[element.uuid] = object_3d; let geometry = new THREE.BufferGeometry(); - let r = 1, m = 0.2; + let r = 1, + m = 0.2; + // prettier-ignore let vertices = [ - 0,0,0,r,m,r,-r,m,r, - r,m,-r, 0,0,0, -r,m,-r, - r,m,r, 0,0,0, r,m,-r, - 0,0,0,-r,m,r,-r,m,-r, - r,m,r, 0,1,0, -r,m,r, - 0,1,0, r,m,-r, -r,m,-r, - 0,1,0, r,m,r, r,m,-r, - -r,m,r, 0,1,0, -r,m,-r, + 0, 0, 0, r, m, r, -r, m, r, + r, m, -r, 0, 0, 0, -r, m, -r, + r, m, r, 0, 0, 0, r, m, -r, + 0, 0, 0, -r, m, r, -r, m, -r, + r, m, r, 0, 1, 0, -r, m, r, + 0, 1, 0, r, m, -r, -r, m, -r, + 0, 1, 0, r, m, r, r, m, -r, + -r, m, r, 0, 1, 0, -r, m, -r, ]; + // prettier-ignore let normals = [ - 0.0,-0.5,0.4, - 0.0,-0.5,-0.4, - 0.4,-0.5,0.0, - -0.4,-0.5,0.0, - 0.0,0.2,0.6, - 0.0,0.2,-0.6, - 0.6,0.2,0.0, - -0.6,0.2,0.0, + 0.0, -0.5, 0.4, 0.0, -0.5, -0.4, 0.4, -0.5, 0.0, -0.4, -0.5, 0.0, 0.0, 0.2, 0.6, 0.0, + 0.2, -0.6, 0.6, 0.2, 0.0, -0.6, 0.2, 0.0, ]; let normals_array = []; for (let i = 0; i < normals.length; i += 3) { for (let j = 0; j < 3; j++) { - normals_array.push(normals[i], normals[i+1], normals[i+2]); + normals_array.push(normals[i], normals[i + 1], normals[i + 2]); } } geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); @@ -394,7 +404,8 @@ new NodePreviewController(ArmatureBone, { geometry.computeBoundingBox(); geometry.computeBoundingSphere(); let material = (ArmatureBone.preview_controller as PreviewControllerType).material; - let mesh: ({no_export?: boolean, isElement?: true, type?: string} & THREE.Mesh) = new THREE.Mesh(geometry, material); + let mesh: { no_export?: boolean; isElement?: true; type?: string } & THREE.Mesh = + new THREE.Mesh(geometry, material); mesh.renderOrder = 20; mesh.visible = element.visibility; mesh.no_export = true; @@ -403,7 +414,6 @@ new NodePreviewController(ArmatureBone, { mesh.isElement = true; object_3d.add(mesh); - object_3d.no_export = true; object_3d.fix_position = new THREE.Vector3(); object_3d.fix_rotation = new THREE.Euler(); @@ -413,7 +423,7 @@ new NodePreviewController(ArmatureBone, { this.updateFaces(element); this.updateSelection(element); - this.dispatchEvent('setup', {element}); + this.dispatchEvent('setup', { element }); }, updateFaces(element: ArmatureBone) { let color_material = Canvas.coloredSolidMaterials[element.color % markerColors.length]; @@ -422,11 +432,13 @@ new NodePreviewController(ArmatureBone, { for (let i = 0; i < 24; i++) { color_array.push(color_value.r, color_value.g, color_value.b); } - (element.mesh.children[0] as THREE.Mesh).geometry.setAttribute('color', new THREE.Float32BufferAttribute(color_array, 3)); + (element.mesh.children[0] as THREE.Mesh).geometry.setAttribute( + 'color', + new THREE.Float32BufferAttribute(color_array, 3) + ); }, updateTransform(element: ArmatureBone) { let bone = element.scene_object as FakeObjectType & THREE.Bone; - let armature = element.getArmature(); bone.rotation.order = 'ZYX'; // @ts-expect-error @@ -452,92 +464,96 @@ new NodePreviewController(ArmatureBone, { } let tail_offset = box.getCenter(Reusable.vec1); bone.children[0].scale.y = Math.max(2, tail_offset.length()); - bone.children[0].quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0), tail_offset.normalize()); - + bone.children[0].quaternion.setFromUnitVectors( + new THREE.Vector3(0, 1, 0), + tail_offset.normalize() + ); } else if (connected_children.length == 1) { let tail_offset = Reusable.vec1.fromArray(connected_children[0].position); bone.children[0].scale.y = tail_offset.length(); - bone.children[0].quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0), tail_offset.normalize()); - + bone.children[0].quaternion.setFromUnitVectors( + new THREE.Vector3(0, 1, 0), + tail_offset.normalize() + ); } else { - bone.children[0].rotation.set(0,0,0); - bone.children[0].scale.x = element.width/2; - bone.children[0].scale.z = element.width/2; + bone.children[0].rotation.set(0, 0, 0); + bone.children[0].scale.x = element.width / 2; + bone.children[0].scale.z = element.width / 2; bone.children[0].scale.y = element.length; } bone.fix_position.copy(bone.position); bone.fix_rotation.copy(bone.rotation); - + bone.inverse_bind_matrix.copy(bone.matrixWorld).invert(); + + /*for (let child of element.children) { + if (child.scene_object) this.updateTransform(child); + }*/ + bone.updateMatrixWorld(); - if (armature?.scene_object) { - bone.inverse_bind_matrix.copy(armature.scene_object.matrixWorld).invert(); - bone.inverse_bind_matrix.multiply(bone.matrixWorld); - bone.inverse_bind_matrix.invert(); - } else { - bone.inverse_bind_matrix.copy(bone.matrixWorld).invert(); - } - this.dispatchEvent('update_transform', {element}); + this.dispatchEvent('update_transform', { element }); }, updateSelection(element: ArmatureBone) { let material = element.selected ? this.material_selected : this.material; let preview_mesh = element.scene_object.children[0] as THREE.Mesh; preview_mesh.material = material; - } -}) - + }, +}); export function getAllArmatureBones() { - let ta = [] + let ta = []; function iterate(array) { for (let obj of array) { if (obj instanceof ArmatureBone) { - ta.push(obj) - iterate(obj.children) + ta.push(obj); + iterate(obj.children); } } } - iterate(Outliner.root) + iterate(Outliner.root); return ta; } -BARS.defineActions(function() { +BARS.defineActions(function () { new Action('add_armature_bone', { icon: 'humerus', category: 'edit', - keybind: new Keybind({key: 'e', shift: true}), + keybind: new Keybind({ key: 'e', shift: true }), condition: () => Modes.edit && (ArmatureBone.selected[0] || Armature.selected[0]), click: function () { - Undo.initEdit({outliner: true, elements: []}); + Undo.initEdit({ outliner: true, elements: [] }); let add_to_node = Outliner.selected[0] || Group.first_selected; if (!add_to_node && selected.length) { add_to_node = selected.last(); } let new_instance = new ArmatureBone({ - origin: add_to_node instanceof ArmatureBone ? [0, add_to_node.length??8, 0] : undefined, - }) - new_instance.addTo(add_to_node) - new_instance.isOpen = true - + origin: + add_to_node instanceof ArmatureBone + ? [0, add_to_node.length ?? 8, 0] + : undefined, + }); + new_instance.addTo(add_to_node); + new_instance.isOpen = true; + if (Format.bone_rig) { - new_instance.createUniqueName() + new_instance.createUniqueName(); } - new_instance.init().select() - Undo.finishEdit('Add armature bone', {outliner: true, elements: [new_instance]}); - Vue.nextTick(function() { - updateSelection() + new_instance.init().select(); + Undo.finishEdit('Add armature bone', { outliner: true, elements: [new_instance] }); + Vue.nextTick(function () { + updateSelection(); if (settings.create_rename.value) { - new_instance.rename() + new_instance.rename(); } - new_instance.showInOutliner() - Blockbench.dispatchEvent( 'add_armature_bone', {object: new_instance} ) - }) - } - }) -}) + new_instance.showInOutliner(); + Blockbench.dispatchEvent('add_armature_bone', { object: new_instance }); + }); + }, + }); +}); Object.assign(window, { ArmatureBone, - getAllArmatureBones -}) + getAllArmatureBones, +}); diff --git a/js/outliner/collections.ts b/js/outliner/collections.ts index 7fc6b5df1..7b432e657 100644 --- a/js/outliner/collections.ts +++ b/js/outliner/collections.ts @@ -1,59 +1,59 @@ -import { Animation } from "../animations/animation"; -import { SharedActions } from "../interface/shared_actions"; -import { Prop } from "../misc"; -import { guid } from "../util/math_util"; -import { Property } from "../util/property"; -import { OutlinerElement, OutlinerNode } from "./outliner"; -import { Toolbar } from '../interface/toolbars' -import { Group } from "./group"; -import { Interface } from "../interface/interface"; -import { Menu } from "../interface/menu"; -import { Blockbench } from "../api"; -import { removeEventListeners } from "../util/util"; -import { Action } from '../interface/actions' -import { Clipbench } from "../copy_paste"; -import { getFocusedTextInput } from "../interface/keyboard"; -import { tl } from "../languages"; -import { Panel } from "../interface/panels"; -import { Codecs } from "../io/codec"; +import { Animation } from '../animations/animation'; +import { SharedActions } from '../interface/shared_actions'; +import { Prop } from '../misc'; +import { guid } from '../util/math_util'; +import { Property } from '../util/property'; +import { OutlinerElement, OutlinerNode } from './outliner'; +import { Toolbar } from '../interface/toolbars'; +import { Group } from './group'; +import { Interface } from '../interface/interface'; +import { Menu } from '../interface/menu'; +import { Blockbench } from '../api'; +import { removeEventListeners } from '../util/util'; +import { Action } from '../interface/actions'; +import { Clipbench } from '../copy_paste'; +import { getFocusedTextInput } from '../interface/keyboard'; +import { tl } from '../languages'; +import { Panel } from '../interface/panels'; +import { Codecs } from '../io/codec'; interface CollectionOptions { - children?: string[] - name?: string - export_codec?: string - export_path?: string - visibility?: boolean + children?: string[]; + name?: string; + export_codec?: string; + export_path?: string; + visibility?: boolean; } /** * Collections are "selection presets" for a set of groups and elements in your project, independent from outliner hierarchy */ export class Collection { - uuid: string - name: string - selected: boolean + uuid: string; + name: string; + selected: boolean; /** * List of direct children, referenced by UUIDs */ - children: string[] - export_path: string - codec: string - menu: Menu - export_codec: string - visibility: boolean + children: string[]; + export_path: string; + codec: string; + menu: Menu; + export_codec: string; + visibility: boolean; - static properties: Record> + static properties: Record>; /** * Get all collections */ - static all: Collection[] + static all: Collection[]; /** * Get selected collections */ - static selected: Collection[] + static selected: Collection[]; constructor(data: CollectionOptions, uuid?: string) { - this.uuid = (uuid && isUUID(uuid)) ? uuid : guid(); + this.uuid = uuid && isUUID(uuid) ? uuid : guid(); this.selected = false; this.children = []; for (let key in Collection.properties) { @@ -63,15 +63,19 @@ export class Collection { } extend(data: CollectionOptions): this { for (var key in Collection.properties) { - Collection.properties[key].merge(this, data) + Collection.properties[key].merge(this, data); } return this; } select(event?: KeyboardEvent | MouseEvent): this { this.selected = true; - if ((!(event?.shiftKey || Pressing.overrides.shift) && !(event?.ctrlOrCmd || Pressing.overrides.ctrl)) || Modes.animate) { + if ( + (!(event?.shiftKey || Pressing.overrides.shift) && + !(event?.ctrlOrCmd || Pressing.overrides.ctrl)) || + Modes.animate + ) { unselectAllElements(); - Collection.all.forEach(c => c.selected = false); + Collection.all.forEach(c => (c.selected = false)); } this.selected = true; let i = 0; @@ -103,7 +107,7 @@ export class Collection { return this; } clickSelect(event) { - Undo.initSelection({collections: true, timeline: Modes.animate}); + Undo.initSelection({ collections: true, timeline: Modes.animate }); this.select(event); Undo.finishSelection('Select collection'); } @@ -111,7 +115,9 @@ export class Collection { * Get all direct children */ getChildren(): OutlinerNode[] { - return this.children.map(uuid => OutlinerNode.uuids[uuid]).filter(node => node != undefined); + return this.children + .map(uuid => OutlinerNode.uuids[uuid]) + .filter(node => node != undefined); } add(): this { Collection.all.safePush(this); @@ -187,11 +193,11 @@ export class Collection { } let all = groups.concat(elements); let state = all[0]?.visibility != true; - Undo.initEdit({groups, elements}); + Undo.initEdit({ groups, elements }); all.forEach(node => { node.visibility = state; - }) - Canvas.updateView({elements, element_aspects: {visibility: true}}); + }); + Canvas.updateView({ elements, element_aspects: { visibility: true } }); Undo.finishEdit('Toggle collection visibility'); } /** @@ -205,7 +211,7 @@ export class Collection { getUndoCopy() { let copy = { uuid: this.uuid, - index: Collection.all.indexOf(this) + index: Collection.all.indexOf(this), }; for (var key in Collection.properties) { Collection.properties[key].copy(this, copy); @@ -214,7 +220,7 @@ export class Collection { } getSaveCopy() { let copy = { - uuid: this.uuid + uuid: this.uuid, }; for (var key in Collection.properties) { Collection.properties[key].copy(this, copy); @@ -235,8 +241,8 @@ export class Collection { let collection = this; function getContentList() { let types = { - group: [] - } + group: [], + }; for (let child of collection.getChildren()) { // @ts-ignore let type = child.type; @@ -249,35 +255,38 @@ export class Collection { list.push({ name: node.name, uuid: node.uuid, - icon: key == 'group' ? Group.prototype.icon : OutlinerElement.types[key].prototype.icon - }) + icon: + key == 'group' + ? Group.prototype.icon + : OutlinerElement.types[key].prototype.icon, + }); } } return list; } type PropertiesComponentData = { content: { - name: string - uuid: string - icon: string - }[] - selected: string[] - } + name: string; + uuid: string; + icon: string; + }[]; + selected: string[]; + }; let dialog = new Dialog({ id: 'collection_properties', title: this.name, resizable: 'x', keyboard_actions: { delete: { - keybind: new Keybind({key: 46}), + keybind: new Keybind({ key: 46 }), run() { this.content_vue.remove(); - } - } + }, + }, }, part_order: ['form', 'component'], form: { - name: {type: 'text', label: 'generic.name', value: this.name}, + name: { type: 'text', label: 'generic.name', value: this.name }, export_path: { label: 'dialog.collection.export_path', value: this.export_path, @@ -285,13 +294,13 @@ export class Collection { condition: isApp && this.codec, extensions: ['json'], filetype: 'JSON collection', - } + }, }, component: { - components: {VuePrismEditor}, + components: { VuePrismEditor }, data: { content: getContentList(), - selected: [] + selected: [], } as PropertiesComponentData, methods: { selectAll(this: PropertiesComponentData) { @@ -310,21 +319,22 @@ export class Collection { }, addWithFilter(this: PropertiesComponentData, event) { // @ts-ignore - BarItems.select_window.click(event, {returnResult: ({elements, groups}) => { - for (let node of elements.concat(groups)) { - if (!this.content.find(node2 => node2.uuid == node.uuid)) { - this.content.push({ - uuid: node.uuid, - name: node.name, - icon: node.icon - }) + BarItems.select_window.click(event, { + returnResult: ({ elements, groups }) => { + for (let node of elements.concat(groups)) { + if (!this.content.find(node2 => node2.uuid == node.uuid)) { + this.content.push({ + uuid: node.uuid, + name: node.name, + icon: node.icon, + }); + } } - } - }}) + }, + }); }, }, - template: - `
    + template: `
    • @@ -337,7 +347,7 @@ export class Collection {
    -
    ` +
    `, }, onFormChange(form) { this.component.data.loop_mode = form.loop; @@ -348,18 +358,20 @@ export class Collection { form_data.name != this.name || form_data.export_path != this.export_path || vue_data.content.find(node => !collection.children.includes(node.uuid)) || - collection.children.find(uuid => !vue_data.content.find(node => node.uuid == uuid)) + collection.children.find( + uuid => !vue_data.content.find(node => node.uuid == uuid) + ) ) { - Undo.initEdit({collections: [this]}); + Undo.initEdit({ collections: [this] }); this.extend({ name: form_data.name, export_path: form_data.export_path, - }) + }); if (isApp) this.export_path = form_data.path; this.children.replace(vue_data.content.map(node => node.uuid)); - Blockbench.dispatchEvent('edit_collection_properties', {collection: this}) + Blockbench.dispatchEvent('edit_collection_properties', { collection: this }); Undo.finishEdit('Edit collection properties'); } @@ -367,8 +379,8 @@ export class Collection { }, onCancel() { dialog.hide().delete(); - } - }) + }, + }); dialog.show(); } } @@ -382,9 +394,13 @@ Collection.prototype.menu = new Menu([ 'duplicate', 'delete', new MenuSeparator('export'), - (collection) => { + collection => { let codec = Codecs[collection.codec]; - if (codec?.export_action && collection.export_path && Condition(codec.export_action.condition)) { + if ( + codec?.export_action && + collection.export_path && + Condition(codec.export_action.condition) + ) { let export_action = codec.export_action; return { id: 'export_as', @@ -393,19 +409,24 @@ Collection.prototype.menu = new Menu([ description: export_action.description, click() { codec.writeCollection(collection); - } - } + }, + }; } }, { id: 'export', name: 'generic.export', icon: 'insert_drive_file', - children: (collection) => { + children: collection => { let actions = []; for (let id in Codecs) { let codec = Codecs[id]; - if (!codec.export_action || !codec.support_partial_export || !Condition(codec.export_action.condition)) continue; + if ( + !codec.export_action || + !codec.support_partial_export || + !Condition(codec.export_action.condition) + ) + continue; let export_action = codec.export_action; let new_action = { @@ -414,8 +435,8 @@ Collection.prototype.menu = new Menu([ description: export_action.description, click() { codec.exportCollection(collection); - } - } + }, + }; if (id == 'project') { new_action = { name: 'menu.collection.export_project', @@ -423,59 +444,61 @@ Collection.prototype.menu = new Menu([ description: '', click() { codec.exportCollection(collection); - } - } + }, + }; } actions.push(new_action); } return actions; - } + }, }, new MenuSeparator('properties'), { icon: 'list', name: 'menu.texture.properties', - click(collection) { collection.propertiesDialog()} - } -]) -new Property(Collection, 'string', 'name', {default: 'collection'}); + click(collection) { + collection.propertiesDialog(); + }, + }, +]); +new Property(Collection, 'string', 'name', { default: 'collection' }); new Property(Collection, 'string', 'export_codec'); new Property(Collection, 'string', 'export_path'); new Property(Collection, 'array', 'children'); -new Property(Collection, 'boolean', 'visibility', {default: false}); +new Property(Collection, 'boolean', 'visibility', { default: false }); Object.defineProperty(Collection, 'all', { get() { // @ts-ignore - return Project.collections - } -}) + return Project.collections; + }, +}); Object.defineProperty(Collection, 'selected', { get() { // @ts-ignore return Project ? Project.collections.filter(c => c.selected) : []; - } -}) + }, +}); SharedActions.add('delete', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Collection.selected.length, run() { let selected = Collection.selected.slice(); - Undo.initEdit({collections: selected}); + Undo.initEdit({ collections: selected }); for (let c of selected) { - Collection.all.remove(c) + Collection.all.remove(c); } selected.empty(); Undo.finishEdit('Remove collection'); - } -}) + }, +}); SharedActions.add('duplicate', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Collection.selected.length, run() { let new_collections = []; - Undo.initEdit({collections: new_collections}); + Undo.initEdit({ collections: new_collections }); for (let original of Collection.selected.slice()) { let copy = new Collection(original); copy.name += ' - copy'; @@ -483,21 +506,21 @@ SharedActions.add('duplicate', { new_collections.push(copy); } Undo.finishEdit('Duplicate collection'); - } -}) + }, +}); SharedActions.add('copy', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Collection.selected.length, run() { Clipbench.collections = Collection.selected.map(collection => collection.getUndoCopy()); - } -}) + }, +}); SharedActions.add('paste', { subject: 'collection', condition: () => Prop.active_panel == 'collections' && Clipbench.collections?.length, run() { let new_collections = []; - Undo.initEdit({collections: new_collections}); + Undo.initEdit({ collections: new_collections }); for (let data of Clipbench.collections) { let copy = new Collection(data); copy.name += ' - copy'; @@ -505,58 +528,61 @@ SharedActions.add('paste', { new_collections.push(copy); } Undo.finishEdit('Paste collection'); - } -}) + }, +}); BARS.defineActions(() => { new Action('create_collection', { icon: 'inventory_2', category: 'select', - keybind: new Keybind({key: 'l', ctrl: true}), - condition: {modes: ['edit', 'paint', 'animate']}, + keybind: new Keybind({ key: 'l', ctrl: true }), + condition: { modes: ['edit', 'paint', 'animate'] }, click() { - Undo.initEdit({collections: []}); + Undo.initEdit({ collections: [] }); let collection = new Collection({}); collection.add().addSelection().select(); - Undo.finishEdit('Create collection', {collections: [collection]}); + Undo.finishEdit('Create collection', { collections: [collection] }); updateSelection(); - } - }) + }, + }); new Action('set_collection_content_to_selection', { icon: 'unarchive', category: 'select', condition: () => Collection.selected.length, click() { let collections = Collection.selected; - Undo.initEdit({collections}); + Undo.initEdit({ collections }); for (let collection of collections) { collection.children.empty(); collection.addSelection(); } Undo.finishEdit('Set collection content to selection'); - } - }) + }, + }); new Action('add_to_collection', { icon: 'box_add', category: 'select', condition: () => Collection.selected.length, click() { let collections = Collection.selected; - Undo.initEdit({collections}); + Undo.initEdit({ collections }); for (let collection of collections) { collection.addSelection(); } Undo.finishEdit('Add selection to collection'); - } - }) -}) - -Interface.definePanels(function() { + }, + }); +}); +Interface.definePanels(function () { function eventTargetToCollection(target: HTMLElement): [Collection?, HTMLElement?] { let target_node: HTMLElement | undefined = target; let i = 0; - while (target_node && target_node.classList && !target_node.classList.contains('collection')) { + while ( + target_node && + target_node.classList && + !target_node.classList.contains('collection') + ) { if (i < 3 && target_node) { target_node = target_node.parentElement; i++; @@ -585,24 +611,24 @@ Interface.definePanels(function() { float_position: [0, 0], float_size: [300, 300], height: 300, - folded: false + folded: false, }, - condition: {modes: ['edit', 'paint', 'animate'], method: () => (!Format.image_editor)}, + condition: { modes: ['edit', 'paint', 'animate'], method: () => !Format.image_editor }, toolbars: [ new Toolbar('collections', { - children: [ - 'create_collection', - ] - }) + children: ['create_collection'], + }), ], component: { name: 'panel-collections', - data() { return { - collections: [], - }}, + data() { + return { + collections: [], + }; + }, methods: { openMenu(event) { - Interface.Panels.collections.menu.show(event) + Interface.Panels.collections.menu.show(event); }, dragCollection(e1) { if (getFocusedTextInput()) return; @@ -619,32 +645,35 @@ Interface.definePanels(function() { function move(e2) { convertTouchEvent(e2); - let offset = [ - e2.clientX - e1.clientX, - e2.clientY - e1.clientY, - ] + let offset = [e2.clientX - e1.clientX, e2.clientY - e1.clientY]; if (!active) { - let distance = Math.sqrt(Math.pow(offset[0], 2) + Math.pow(offset[1], 2)) + let distance = Math.sqrt( + Math.pow(offset[0], 2) + Math.pow(offset[1], 2) + ); if (Blockbench.isTouch) { if (distance > 20 && timeout) { clearTimeout(timeout); timeout = null; } else { - document.getElementById('collections_list').scrollTop += last_event.clientY - e2.clientY; + document.getElementById('collections_list').scrollTop += + last_event.clientY - e2.clientY; } } else if (distance > 6) { active = true; } } else { if (e2) e2.preventDefault(); - + if (Menu.open) Menu.open.hide(); if (!helper) { helper = document.createElement('div'); helper.id = 'animation_drag_helper'; - let icon = Blockbench.getIconNode('inventory_2'); helper.append(icon); - let span = document.createElement('span'); span.innerText = collection.name; helper.append(span); + let icon = Blockbench.getIconNode('inventory_2'); + helper.append(icon); + let span = document.createElement('span'); + span.innerText = collection.name; + helper.append(span); document.body.append(helper); Blockbench.addFlag('dragging_collections'); } @@ -656,11 +685,13 @@ Interface.definePanels(function() { $('.collection[order]').attr('order', null); let target = document.elementFromPoint(e2.clientX, e2.clientY); - [drop_target, drop_target_node] = eventTargetToCollection(target as HTMLElement); + [drop_target, drop_target_node] = eventTargetToCollection( + target as HTMLElement + ); if (drop_target) { var location = e2.clientY - $(drop_target_node).offset().top; - order = getOrder(location, drop_target) - drop_target_node.setAttribute('order', order) + order = getOrder(location, drop_target); + drop_target_node.setAttribute('order', order); drop_target_node.classList.add('drag_hover'); } } @@ -673,7 +704,7 @@ Interface.definePanels(function() { $('.drag_hover').removeClass('drag_hover'); $('.collection[order]').attr('order', null); if (Blockbench.isTouch) clearTimeout(timeout); - + setTimeout(() => { Blockbench.removeFlag('dragging_collections'); }, 10); @@ -681,16 +712,18 @@ Interface.definePanels(function() { if (active && !Menu.open) { convertTouchEvent(e2); let target = document.elementFromPoint(e2.clientX, e2.clientY); - let [target_collection] = eventTargetToCollection(target as HTMLElement); - if (!target_collection || target_collection == collection ) return; + let [target_collection] = eventTargetToCollection( + target as HTMLElement + ); + if (!target_collection || target_collection == collection) return; let index = Collection.all.indexOf(target_collection); if (index == -1) return; if (Collection.all.indexOf(collection) < index) index--; if (order == 1) index++; if (Collection.all[index] == collection) return; - - Undo.initEdit({collections: [collection]}); + + Undo.initEdit({ collections: [collection] }); Collection.all.remove(collection); Collection.all.splice(index, 0, collection); @@ -703,23 +736,23 @@ Interface.definePanels(function() { timeout = setTimeout(() => { active = true; move(e1); - }, 320) + }, 320); } - addEventListeners(document, 'mousemove touchmove', move, {passive: false}); - addEventListeners(document, 'mouseup touchend', off, {passive: false}); + addEventListeners(document, 'mousemove touchmove', move, { passive: false }); + addEventListeners(document, 'mouseup touchend', off, { passive: false }); }, unselect() { if (Blockbench.hasFlag('dragging_collections')) return; Collection.all.forEach(collection => { collection.selected = false; - }) + }); updateSelection(); }, getContentList(collection: Collection) { let types = { - group: [] - } + group: [], + }; for (let child of collection.getChildren()) { // @ts-ignore let type = child.type; @@ -732,11 +765,14 @@ Interface.definePanels(function() { list.push({ count: types[key].length == 1 ? '' : types[key].length, name: types[key].length == 1 ? types[key][0].name : '', - icon: key == 'group' ? Group.prototype.icon : OutlinerElement.types[key].prototype.icon - }) + icon: + key == 'group' + ? Group.prototype.icon + : OutlinerElement.types[key].prototype.icon, + }); } return list; - } + }, }, template: `
    - ` + `, }, - menu: new Menu([ - 'create_collection', - 'copy', - ]) - }) -}) + menu: new Menu(['create_collection', 'copy']), + }); +}); Object.assign(window, { - Collection + Collection, }); diff --git a/js/outliner/element_panel.ts b/js/outliner/element_panel.ts index 3b501cf17..17688695e 100644 --- a/js/outliner/element_panel.ts +++ b/js/outliner/element_panel.ts @@ -1,19 +1,19 @@ -import { Blockbench } from "../api"; -import { InputForm } from "../interface/form"; -import { Interface } from "../interface/interface"; -import { Panel } from "../interface/panels"; -import { Property } from "../util/property"; +import { Blockbench } from '../api'; +import { InputForm } from '../interface/form'; +import { Interface } from '../interface/interface'; +import { Panel } from '../interface/panels'; +import { Property } from '../util/property'; -Interface.definePanels(function() { +Interface.definePanels(function () { new Panel('transform', { icon: 'arrows_output', - condition: {modes: ['edit', 'pose']}, + condition: { modes: ['edit', 'pose'] }, display_condition: () => Outliner.selected.length || Group.first_selected, default_position: { slot: 'right_bar', float_position: [0, 0], float_size: [300, 400], - height: 400 + height: 400, }, toolbars: [ Toolbars.element_position, @@ -22,10 +22,10 @@ Interface.definePanels(function() { Toolbars.element_origin, Toolbars.element_rotation, ], - }) + }); let element_properties_panel = new Panel('element', { icon: 'fas.fa-cube', - condition: {modes: ['edit']}, + condition: { modes: ['edit'] }, display_condition: () => Outliner.selected.length || Group.first_selected, default_position: { slot: 'right_bar', @@ -33,27 +33,27 @@ Interface.definePanels(function() { float_size: [300, 400], height: 400, attached_to: 'transform', - attached_index: 1 + attached_index: 1, }, - form: new InputForm({}) - }) + form: new InputForm({}), + }); function updateElementForm() { - const {form_config} = element_properties_panel.form; + const { form_config } = element_properties_panel.form; for (let key in form_config) { delete form_config[key]; } let onchanges = []; let registerInput = (type_id: string, prop_id: string, property: Property) => { if (!property?.inputs?.element_panel) return; - let {input, onChange} = property.inputs.element_panel; + let { input, onChange } = property.inputs.element_panel; let input_id = type_id + '_' + prop_id; input.condition = { - selected: {[type_id]: true}, + selected: { [type_id]: true }, method: () => Condition(property.condition), }; if (onChange) onchanges.push(onChange); form_config[input_id] = input; - } + }; for (let type_id in OutlinerElement.types) { let type = OutlinerElement.types[type_id]; for (let prop_id in type.properties) { @@ -66,14 +66,14 @@ Interface.definePanels(function() { registerInput('group', prop_id, Group.properties[prop_id]); } } - element_properties_panel.form.on('input', ({result, changed_keys}) => { + element_properties_panel.form.on('input', ({ result, changed_keys }) => { // Only one key should be changed at a time if (changed_keys[0]?.startsWith('group_')) { let groups = Group.multi_selected; - Undo.initEdit({groups}); + Undo.initEdit({ groups }); for (let group of groups) { for (let key in result) { - let property_id = key.replace(group.type+'_', ''); + let property_id = key.replace(group.type + '_', ''); // @ts-ignore if (group.constructor.properties[property_id]) { group[property_id] = result[key]; @@ -84,10 +84,10 @@ Interface.definePanels(function() { onchanges.forEach(onchange => onchange(result)); } else { let elements = Outliner.selected.slice(); - Undo.initEdit({elements}); + Undo.initEdit({ elements }); for (let element of elements) { for (let key in result) { - let property_id = key.replace(element.type+'_', ''); + let property_id = key.replace(element.type + '_', ''); // @ts-ignore if (element.constructor.properties[property_id]) { element[property_id] = result[key]; @@ -97,7 +97,7 @@ Interface.definePanels(function() { Undo.finishEdit('Change element property'); onchanges.forEach(onchange => onchange(result)); } - }) + }); element_properties_panel.form.buildForm(); } updateElementForm(); @@ -115,10 +115,10 @@ Interface.definePanels(function() { let property = type.properties[prop_id]; if (property?.inputs?.element_panel) { let input_id = type_id + '_' + prop_id; - if (typeof first_element[prop_id] === "object") { // Prevent object properties from using the same objects across elements. - values[input_id] = {...first_element[prop_id]}; - } - else { + if (typeof first_element[prop_id] === 'object') { + // Prevent object properties from using the same objects across elements. + values[input_id] = { ...first_element[prop_id] }; + } else { values[input_id] = first_element[prop_id]; } } @@ -138,5 +138,7 @@ Interface.definePanels(function() { element_properties_panel.form.update(values); element_properties_panel.form.updateLabelWidth(true); }); - Toolbars.element_origin.node.after(Interface.createElement('div', {id: 'element_origin_toolbar_anchor'})) -}) \ No newline at end of file + Toolbars.element_origin.node.after( + Interface.createElement('div', { id: 'element_origin_toolbar_anchor' }) + ); +}); diff --git a/js/plugin_loader.ts b/js/plugin_loader.ts index 6881fd95e..0b57311aa 100644 --- a/js/plugin_loader.ts +++ b/js/plugin_loader.ts @@ -1,26 +1,33 @@ -import { Blockbench } from "./api"; -import StateMemory from "./util/state_memory"; -import { Dialog } from "./interface/dialog"; -import { settings, Settings, SettingsProfile } from "./interface/settings"; -import { ModelLoader, StartScreen } from "./interface/start_screen"; -import { sort_collator } from "./misc"; -import { separateThousands } from "./util/math_util"; -import { getDateDisplay } from "./util/util"; -import { Filesystem } from "./file_system"; -import { Panels } from "./interface/interface"; -import { app, fs, getPluginPermissions, getPluginScopedRequire, https, revokePluginPermissions } from "./native_apis"; +import { Blockbench } from './api'; +import StateMemory from './util/state_memory'; +import { Dialog } from './interface/dialog'; +import { settings, Settings, SettingsProfile } from './interface/settings'; +import { ModelLoader, StartScreen } from './interface/start_screen'; +import { sort_collator } from './misc'; +import { separateThousands } from './util/math_util'; +import { getDateDisplay } from './util/util'; +import { Filesystem } from './file_system'; +import { Panels } from './interface/interface'; +import { + app, + fs, + getPluginPermissions, + getPluginScopedRequire, + https, + revokePluginPermissions, +} from './native_apis'; interface FileResult { - name: string - path: string - content: string | ArrayBuffer + name: string; + path: string; + content: string | ArrayBuffer; } export const Plugins = { /** * The plugins window */ - dialog: null as null|Dialog, + dialog: null as null | Dialog, Vue: null as Vue | null, /** * Data about which plugins are installed @@ -38,22 +45,24 @@ export const Plugins = { /** * The currently used path to the plugin API */ - api_path: settings.cdn_mirror.value ? 'https://blckbn.ch/cdn/plugins' : 'https://cdn.jsdelivr.net/gh/JannisX11/blockbench-plugins/plugins', + api_path: settings.cdn_mirror.value + ? 'https://blckbn.ch/cdn/plugins' + : 'https://cdn.jsdelivr.net/gh/JannisX11/blockbench-plugins/plugins', path: '', /** * Dev reload all side-loaded plugins */ devReload() { let reloads = 0; - for (let i = Plugins.all.length-1; i >= 0; i--) { + for (let i = Plugins.all.length - 1; i >= 0; i--) { let plugin = Plugins.all[i]; if (plugin.source == 'file' && plugin.isReloadable()) { - Plugins.all[i].reload() + Plugins.all[i].reload(); reloads++; } } - Blockbench.showQuickMessage(tl('message.plugin_reload', [reloads])) - console.log('Reloaded '+reloads+ ' plugin'+pluralS(reloads)) + Blockbench.showQuickMessage(tl('message.plugin_reload', [reloads])); + console.log('Reloaded ' + reloads + ' plugin' + pluralS(reloads)); }, /** * Update sort order of existing plugins @@ -62,184 +71,189 @@ export const Plugins = { Plugins.all.sort((a, b) => { if (a.tags.find(tag => tag.match(/deprecated/i))) return 1; if (b.tags.find(tag => tag.match(/deprecated/i))) return -1; - let download_difference = (Plugins.download_stats[b.id] || 0) - (Plugins.download_stats[a.id] || 0); + let download_difference = + (Plugins.download_stats[b.id] || 0) - (Plugins.download_stats[a.id] || 0); if (download_difference) { - return download_difference + return download_difference; } else { return sort_collator.compare(a.title, b.title); } }); - } -} -StateMemory.init('installed_plugins', 'array') + }, +}; +StateMemory.init('installed_plugins', 'array'); // @ts-ignore -Plugins.installed = StateMemory.installed_plugins = StateMemory.installed_plugins.filter(p => p && typeof p == 'object'); +Plugins.installed = StateMemory.installed_plugins = StateMemory.installed_plugins.filter( + p => p && typeof p == 'object' +); - -type PluginVariant = 'desktop'|'web'|'both'; -type PluginSource = 'store'|'file'|'url'; +type PluginVariant = 'desktop' | 'web' | 'both'; +type PluginSource = 'store' | 'file' | 'url'; type PluginDetails = { - version: string, - last_modified: string, - creation_date: string, - last_modified_full: string, - creation_date_full: string, - min_version: string - max_version: string - website: string - repository: string - bug_tracker: string - contributors: string - author: string, - variant: PluginVariant | string, - permissions: string - weekly_installations: string -} + version: string; + last_modified: string; + creation_date: string; + last_modified_full: string; + creation_date_full: string; + min_version: string; + max_version: string; + website: string; + repository: string; + bug_tracker: string; + contributors: string; + author: string; + variant: PluginVariant | string; + permissions: string; + weekly_installations: string; +}; type PluginInstallation = { - id: string - version: string - path: string - source: PluginSource - dependencies?: string[] - disabled?: boolean -} -type PluginChangelog = Record + id: string; + version: string; + path: string; + source: PluginSource; + dependencies?: string[]; + disabled?: boolean; +}; +type PluginChangelog = Record< + string, + { + title: string; + author?: string; + date?: string; + categories: { + title: string; + list: string[]; + }[]; + } +>; interface PluginOptions { - title: string - author: string + title: string; + author: string; /** * Description text in the plugin browser */ - description: string + description: string; /** * The about text appears when the user unfolds the plugin in the plugin browser. It can contain additional information and usage instructions */ - about?: string + about?: string; /** * The version of the plugin. */ - version?: string - icon: string + version?: string; + icon: string; /** * Plugin tags that will show up in the plugin store. You can provide up to 3 tags. */ - tags?: [string, string?, string?] + tags?: [string, string?, string?]; /** * Where the plugin can be installed. Desktop refers to the electron app, web refers to the web app and PWA */ - variant: 'both' | 'desktop' | 'web' + variant: 'both' | 'desktop' | 'web'; /** * Minimum Blockbench version in which the plugin can be installed */ - min_version?: string + min_version?: string; /** * Maximum Blockbench version in which the plugin can be installed */ - max_version?: string + max_version?: string; /** * Set to true if the plugin must finish loading before a project is opened, i. e. because it adds a format */ - await_loading?: boolean + await_loading?: boolean; /** * Use the new repository format where plugin, iron, and about are stored in a separate folder */ - new_repository_format?: boolean + new_repository_format?: boolean; /** * Can be used to specify which features a plugin adds. This allows Blockbench to be aware of and suggest even plugins that are not installed. */ contributes?: { - formats: string[] - } - has_changelog?: boolean + formats: string[]; + }; + has_changelog?: boolean; /** * In combination with a "Deprecated" tag, this can be used to provide context on why a plugin is deprecated */ - deprecation_note?: string + deprecation_note?: string; /* * Link to the plugin's website */ - website?: string + website?: string; /* * Link to the repository that contains the source for the plugin */ - repository?: string + repository?: string; /* * Link to where users can report issues with the plugin */ - bug_tracker?: string + bug_tracker?: string; /* * List of secondary contributors to the plugin, excluding the main author(s) */ - contributors?: string[] - disabled?: boolean + contributors?: string[]; + disabled?: boolean; /** * Runs when the plugin loads */ - onload?(): void + onload?(): void; /** * Runs when the plugin unloads */ - onunload?(): void + onunload?(): void; /** * Runs when the user manually installs the plugin */ - oninstall?(): void + oninstall?(): void; /** * Runs when the user manually uninstalls the plugin */ - onuninstall?(): void + onuninstall?(): void; } interface PluginSetupOptions { - disabled?: boolean + disabled?: boolean; } export class Plugin { - id: string - installed: boolean - title: string - author: string - description: string - about: string - icon: string - tags: string[] - dependencies: string[] - contributors: string[] - version: string - variant: PluginVariant - min_version: string - max_version: string - deprecation_note: string - path: string - website: string - repository: string - bug_tracker: string - source: PluginSource - creation_date: string|number - contributes: {} - await_loading: boolean - has_changelog: boolean - changelog: null|PluginChangelog - about_fetched: boolean - changelog_fetched: boolean - disabled: boolean - new_repository_format: boolean - cache_version: number - menu: Menu - details: null|PluginDetails - - onload?: () => void - onunload?: () => void - oninstall?: () => void - onuninstall?: () => void + id: string; + installed: boolean; + title: string; + author: string; + description: string; + about: string; + icon: string; + tags: string[]; + dependencies: string[]; + contributors: string[]; + version: string; + variant: PluginVariant; + min_version: string; + max_version: string; + deprecation_note: string; + path: string; + website: string; + repository: string; + bug_tracker: string; + source: PluginSource; + creation_date: string | number; + contributes: {}; + await_loading: boolean; + has_changelog: boolean; + changelog: null | PluginChangelog; + about_fetched: boolean; + changelog_fetched: boolean; + disabled: boolean; + new_repository_format: boolean; + cache_version: number; + menu: Menu; + details: null | PluginDetails; + + onload?: () => void; + onunload?: () => void; + oninstall?: () => void; + onuninstall?: () => void; constructor(id: string = 'unknown', data?: PluginOptions | PluginSetupOptions) { this.id = id; @@ -271,26 +285,26 @@ export class Plugin { this.new_repository_format = false; this.cache_version = 0; - this.extend(data) + this.extend(data); Plugins.all.safePush(this); } extend(data) { if (!(data instanceof Object)) return this; - Merge.boolean(this, data, 'installed') - Merge.string(this, data, 'title') - Merge.string(this, data, 'author') - Merge.string(this, data, 'description') - Merge.string(this, data, 'about') - Merge.string(this, data, 'icon') - Merge.string(this, data, 'version') - Merge.string(this, data, 'variant') - Merge.string(this, data, 'min_version') - Merge.string(this, data, 'max_version') - Merge.string(this, data, 'deprecation_note') - Merge.string(this, data, 'website') - Merge.string(this, data, 'repository') - Merge.string(this, data, 'bug_tracker') + Merge.boolean(this, data, 'installed'); + Merge.string(this, data, 'title'); + Merge.string(this, data, 'author'); + Merge.string(this, data, 'description'); + Merge.string(this, data, 'about'); + Merge.string(this, data, 'icon'); + Merge.string(this, data, 'version'); + Merge.string(this, data, 'variant'); + Merge.string(this, data, 'min_version'); + Merge.string(this, data, 'max_version'); + Merge.string(this, data, 'deprecation_note'); + Merge.string(this, data, 'website'); + Merge.string(this, data, 'repository'); + Merge.string(this, data, 'bug_tracker'); Merge.boolean(this, data, 'await_loading'); Merge.boolean(this, data, 'has_changelog'); Merge.boolean(this, data, 'disabled'); @@ -307,10 +321,10 @@ export class Plugin { this.contributes = data.contributes; } - Merge.function(this, data, 'onload') - Merge.function(this, data, 'onunload') - Merge.function(this, data, 'oninstall') - Merge.function(this, data, 'onuninstall') + Merge.function(this, data, 'onload'); + Merge.function(this, data, 'onunload'); + Merge.function(this, data, 'oninstall'); + Merge.function(this, data, 'onuninstall'); return this; } get name() { @@ -322,15 +336,18 @@ export class Plugin { if (this.deprecation_note) { message += '\n\n*' + this.deprecation_note + '*'; } - let answer = await new Promise((resolve) => { - Blockbench.showMessageBox({ - icon: 'warning', - title: this.title, - message, - cancelIndex: 0, - buttons: ['dialog.cancel', 'message.plugin_deprecated.install_anyway'] - }, resolve) - }) + let answer = await new Promise(resolve => { + Blockbench.showMessageBox( + { + icon: 'warning', + title: this.title, + message, + cancelIndex: 0, + buttons: ['dialog.cancel', 'message.plugin_deprecated.install_anyway'], + }, + resolve + ); + }); if (answer == 0) return; } return await this.download(true); @@ -340,28 +357,38 @@ export class Plugin { Plugins.registered[this.id] = this; return await new Promise((resolve, reject) => { let path = Plugins.path + scope.id + '.js'; - if (!isApp && this.new_repository_format) { + if (!isApp && this.new_repository_format) { path = `${Plugins.path}${scope.id}/${scope.id}.js`; } - this.#runPluginFile(path).then((content) => { - if (cb) cb.bind(scope)() - if (first && scope.oninstall) { - scope.oninstall() - } - if (first) Blockbench.showQuickMessage(tl('message.installed_plugin', [this.title])); - resolve() - }).catch((error) => { - if (isApp) { - console.log('Could not find file of plugin "'+scope.id+'". Uninstalling it instead.') - scope.uninstall() - } - if (first) Blockbench.showQuickMessage(tl('message.installed_plugin_fail', [this.title])); - reject() - console.error(error) - }) - this.#remember() + this.#runPluginFile(path) + .then(content => { + if (cb) cb.bind(scope)(); + if (first && scope.oninstall) { + scope.oninstall(); + } + if (first) + Blockbench.showQuickMessage(tl('message.installed_plugin', [this.title])); + resolve(); + }) + .catch(error => { + if (isApp) { + console.log( + 'Could not find file of plugin "' + + scope.id + + '". Uninstalling it instead.' + ); + scope.uninstall(); + } + if (first) + Blockbench.showQuickMessage( + tl('message.installed_plugin_fail', [this.title]) + ); + reject(); + console.error(error); + }); + this.#remember(); scope.installed = true; - }) + }); } async installDependencies(first) { let required_dependencies = []; @@ -379,7 +406,7 @@ export class Plugin { } if (required_dependencies.length) { let failed_dependency = required_dependencies.find(p => { - return !p.isInstallable || p.isInstallable() != true + return !p.isInstallable || p.isInstallable() != true; }); if (failed_dependency) { let error_message = failed_dependency; @@ -388,22 +415,30 @@ export class Plugin { } Blockbench.showMessageBox({ title: 'message.plugin_dependencies.title', - message: `Updating **${this.title||this.id}**:\n\n${tl('message.plugin_dependencies.invalid')}\n\n${error_message}`, + message: `Updating **${this.title || this.id}**:\n\n${tl('message.plugin_dependencies.invalid')}\n\n${error_message}`, }); return false; } - let list = required_dependencies.map(p => `**${p.title}** ${tl('dialog.plugins.author', [p.author])}`); + let list = required_dependencies.map( + p => `**${p.title}** ${tl('dialog.plugins.author', [p.author])}` + ); let response = await new Promise(resolve => { - Blockbench.showMessageBox({ - title: 'message.plugin_dependencies.title', - message: `${tl('message.plugin_dependencies.' + (first ? 'message1' : 'message1_update'), [this.title])} \n\n* ${ list.join('\n* ') }\n\n${tl('message.plugin_dependencies.message2')}`, - buttons: ['dialog.continue', first ? 'dialog.cancel' : 'dialog.plugins.uninstall'], - width: 512, - }, button => { - resolve(button == 0); - }) - }) + Blockbench.showMessageBox( + { + title: 'message.plugin_dependencies.title', + message: `${tl('message.plugin_dependencies.' + (first ? 'message1' : 'message1_update'), [this.title])} \n\n* ${list.join('\n* ')}\n\n${tl('message.plugin_dependencies.message2')}`, + buttons: [ + 'dialog.continue', + first ? 'dialog.cancel' : 'dialog.plugins.uninstall', + ], + width: 512, + }, + button => { + resolve(button == 0); + } + ); + }); if (!response) { if (this.installed) this.uninstall(); return false; @@ -426,20 +461,24 @@ export class Plugin { url: 'https://blckbn.ch/api/event/install_plugin', type: 'POST', data: { - plugin: scope.id - } - }) + plugin: scope.id, + }, + }); } if (!isApp) { if (first) register(); - return await scope.load(first) + return await scope.load(first); } // Download files - async function copyFileToDrive(origin_filename?: string, target_filename?: string, callback?: () => void) { + async function copyFileToDrive( + origin_filename?: string, + target_filename?: string, + callback?: () => void + ) { var file = fs.createWriteStream(PathModule.join(Plugins.path, target_filename)); // @ts-ignore - https.get(Plugins.api_path+'/'+origin_filename, function(response) { + https.get(Plugins.api_path + '/' + origin_filename, function (response) { response.pipe(file); if (callback) response.on('end', callback); }); @@ -449,27 +488,30 @@ export class Plugin { if (this.new_repository_format) { copyFileToDrive(`${this.id}/${this.id}.js`, `${this.id}.js`, () => { if (first) register(); - setTimeout(async function() { + setTimeout(async function () { await scope.load(first); - resolve() - }, 20) + resolve(); + }, 20); }); if (this.hasImageIcon()) { copyFileToDrive(`${this.id}/${this.icon}`, this.id + '.' + this.icon); } await this.fetchAbout(); if (this.about) { - fs.writeFileSync(PathModule.join(Plugins.path, this.id + '.about.md'), this.about, 'utf-8'); + fs.writeFileSync( + PathModule.join(Plugins.path, this.id + '.about.md'), + this.about, + 'utf-8' + ); } - } else { // Legacy system copyFileToDrive(`${this.id}.js`, `${this.id}.js`, () => { if (first) register(); - setTimeout(async function() { + setTimeout(async function () { await scope.load(first); - resolve() - }, 20) + resolve(); + }, 20); }); } }); @@ -492,19 +534,19 @@ export class Plugin { this.tags.safePush('Local'); if (isApp) { - let content = await this.#runPluginFile(file.path).catch((error) => { + let content = await this.#runPluginFile(file.path).catch(error => { console.error(error); }); if (content) { if (first && scope.oninstall) { - scope.oninstall() + scope.oninstall(); } scope.path = file.path; } } else { this.#runCode(file.content as string); if (first && scope.oninstall) { - scope.oninstall() + scope.oninstall(); } } this.installed = true; @@ -520,36 +562,38 @@ export class Plugin { } } - this.id = pathToName(url) + this.id = pathToName(url); Plugins.registered[this.id] = this; - Plugins.all.safePush(this) + Plugins.all.safePush(this); this.tags.safePush('Remote'); this.source = 'url'; - let content = await this.#runPluginFile(url).catch(async (error) => { + let content = await this.#runPluginFile(url).catch(async error => { if (isApp) { await this.load(); } console.error(error); - }) + }); if (content) { if (first && this.oninstall) { - this.oninstall() + this.oninstall(); } - this.installed = true - this.path = url - this.#remember() - Plugins.sort() + this.installed = true; + this.path = url; + this.#remember(); + Plugins.sort(); // Save if (isApp) { await new Promise((resolve, reject) => { - let file = fs.createWriteStream(Plugins.path+this.id+'.js') + let file = fs.createWriteStream(Plugins.path + this.id + '.js'); // @ts-ignore - https.get(url, (response) => { - response.pipe(file); - response.on('end', resolve) - }).on('error', reject); - }) + https + .get(url, response => { + response.pipe(file); + response.on('end', resolve); + }) + .on('error', reject); + }); } } return this; @@ -568,7 +612,7 @@ export class Plugin { if (!already_exists) Plugins.installed.push(entry); - StateMemory.save('installed_plugins') + StateMemory.save('installed_plugins'); return this; } uninstall() { @@ -583,17 +627,17 @@ export class Plugin { delete Plugins.registered[this.id]; let in_installed = Plugins.installed.find(plugin => plugin.id == this.id); Plugins.installed.remove(in_installed); - StateMemory.save('installed_plugins') + StateMemory.save('installed_plugins'); this.installed = false; this.disabled = false; if (isApp && this.source !== 'store') { - Plugins.all.remove(this) + Plugins.all.remove(this); } if (isApp && this.source != 'file') { function removeCachedFile(filepath) { if (fs.existsSync(filepath)) { - fs.unlink(filepath, (err) => { + fs.unlink(filepath, err => { if (err) console.log(err); }); } @@ -602,12 +646,12 @@ export class Plugin { removeCachedFile(Plugins.path + this.id + '.' + this.icon); removeCachedFile(Plugins.path + this.id + '.about.md'); } - StateMemory.save('installed_plugins') + StateMemory.save('installed_plugins'); return this; } unload() { if (this.onunload) { - this.onunload() + this.onunload(); } return this; } @@ -615,7 +659,7 @@ export class Plugin { if (!isApp && this.source == 'file') return this; this.cache_version++; - this.unload() + this.unload(); this.tags.empty(); this.contributors.empty(); this.dependencies.empty(); @@ -625,10 +669,9 @@ export class Plugin { this.changelog_fetched = false; if (this.source == 'file') { - this.loadFromFile({path: this.path, name: this.path, content: ''}, false) - + this.loadFromFile({ path: this.path, name: this.path, content: '' }, false); } else if (this.source == 'url') { - this.loadFromURL(this.path, false) + this.loadFromURL(this.path, false); } this.fetchAbout(true); @@ -654,15 +697,13 @@ export class Plugin { }, error() { reject('Failed to load plugin ' + this.id); - } + }, }); - }) - + }); } else if (isApp) { - file_content = fs.readFileSync(path, {encoding: 'utf-8'}); - + file_content = fs.readFileSync(path, { encoding: 'utf-8' }); } else { - throw 'Failed to load plugin: Unknown URL format' + throw 'Failed to load plugin: Unknown URL format'; } this.#runCode(file_content); return file_content; @@ -672,7 +713,11 @@ export class Plugin { throw `Issue loading plugin "${this.id}": Plugin file empty`; } try { - const func = new Function('requireNativeModule', 'require', code + `\n//# sourceURL=PLUGINS/(Plugin):${this.id}.js`); + const func = new Function( + 'requireNativeModule', + 'require', + code + `\n//# sourceURL=PLUGINS/(Plugin):${this.id}.js` + ); const scoped_require = isApp ? getPluginScopedRequire(this) : undefined; func(scoped_require, scoped_require); } catch (err) { @@ -682,10 +727,10 @@ export class Plugin { toggleDisabled() { if (!this.disabled) { this.disabled = true; - this.unload() + this.unload(); } else { if (this.onload) { - this.onload() + this.onload(); } this.disabled = false; } @@ -695,26 +740,27 @@ export class Plugin { Plugin.menu.open(event, this); } isReloadable() { - return this.installed && !this.disabled && ((this.source == 'file' && isApp) || (this.source == 'url')); + return ( + this.installed && + !this.disabled && + ((this.source == 'file' && isApp) || this.source == 'url') + ); } isInstallable() { var scope = this; - var result: string | boolean = + var result: string | boolean = scope.variant === 'both' || - ( - isApp === (scope.variant === 'desktop') && - isApp !== (scope.variant === 'web') - ); + (isApp === (scope.variant === 'desktop') && isApp !== (scope.variant === 'web')); if (result && scope.min_version) { result = Blockbench.isOlderThan(scope.min_version) ? 'outdated_client' : true; } if (result && scope.max_version) { - result = Blockbench.isNewerThan(scope.max_version) ? 'outdated_plugin' : true + result = Blockbench.isNewerThan(scope.max_version) ? 'outdated_plugin' : true; } if (result === false) { - result = (scope.variant === 'web') ? 'web_only' : 'app_only' + result = scope.variant === 'web' ? 'web_only' : 'app_only'; } - return (result === true) ? true : tl('dialog.plugins.'+result); + return result === true ? true : tl('dialog.plugins.' + result); } hasImageIcon() { return this.icon.endsWith('.png') || this.icon.endsWith('.svg'); @@ -726,8 +772,11 @@ export class Plugin { return Plugins.path + this.id + '.' + this.icon; } if (this.source != 'store') - return this.path.replace(/\w+\.js$/, this.icon + (this.cache_version ? '?'+this.cache_version : '')); - } + return this.path.replace( + /\w+\.js$/, + this.icon + (this.cache_version ? '?' + this.cache_version : '') + ); + } return `${Plugins.api_path}/${this.id}/${this.icon}`; } return this.icon; @@ -742,7 +791,7 @@ export class Plugin { } else { about_path = this.path.replace(/\w+\.js$/, 'about.md'); } - let content = fs.readFileSync(about_path, {encoding: 'utf-8'}); + let content = fs.readFileSync(about_path, { encoding: 'utf-8' }); this.about = content; this.about_fetched = true; return; @@ -766,13 +815,13 @@ export class Plugin { let output = {}; Object.keys(input).forEachReverse(key => { output[key] = input[key]; - }) + }); return output; } if (isApp && this.installed && this.source != 'store') { try { let changelog_path = this.path.replace(/\w+\.js$/, 'changelog.json'); - let content = fs.readFileSync(changelog_path, {encoding: 'utf-8'}); + let content = fs.readFileSync(changelog_path, { encoding: 'utf-8' }); this.changelog = reverseOrder(JSON.parse(content)); this.changelog_fetched = true; return; @@ -798,7 +847,7 @@ export class Plugin { creation_date: 'N/A', last_modified_full: '', creation_date_full: '', - min_version: this.min_version ? (this.min_version+'+') : '-', + min_version: this.min_version ? this.min_version + '+' : '-', max_version: this.max_version || '', website: this.website || '', repository: this.repository || '', @@ -828,7 +877,7 @@ export class Plugin { let date = getDateDisplay(input_date); this.details[key] = date.short; this.details[key + '_full'] = date.full; - } + }; if (this.source == 'store') { if (!this.details.bug_tracker) { this.details.bug_tracker = `https://github.com/JannisX11/blockbench-plugins/issues/new?title=[${this.title.replace(/[+&]/g, 'and')}]`; @@ -837,21 +886,26 @@ export class Plugin { this.details.repository = `https://github.com/JannisX11/blockbench-plugins/tree/master/plugins/${this.id + (this.new_repository_format ? '' : '.js')}`; } - let github_path = (this.new_repository_format ? (this.id+'/'+this.id) : this.id) + '.js'; + let github_path = + (this.new_repository_format ? this.id + '/' + this.id : this.id) + '.js'; let commit_url = `https://api.github.com/repos/JannisX11/blockbench-plugins/commits?path=plugins/${github_path}`; - fetch(commit_url).catch((err) => { - console.error('Cannot access commit info for ' + this.id, err); - }).then(async response => { - if (!response) return; - let commits = await response.json().catch(err => console.error(err)); - if (!commits || !commits.length) return; - trackDate(Date.parse(commits[0].commit.committer.date), 'last_modified'); - - if (!this.creation_date) { - trackDate(Date.parse(commits.last().commit.committer.date), 'creation_date'); - } - }); - + fetch(commit_url) + .catch(err => { + console.error('Cannot access commit info for ' + this.id, err); + }) + .then(async response => { + if (!response) return; + let commits = await response.json().catch(err => console.error(err)); + if (!commits || !commits.length) return; + trackDate(Date.parse(commits[0].commit.committer.date), 'last_modified'); + + if (!this.creation_date) { + trackDate( + Date.parse(commits.last().commit.committer.date), + 'creation_date' + ); + } + }); } if (this.creation_date) { trackDate(this.creation_date, 'creation_date'); @@ -859,8 +913,8 @@ export class Plugin { return this.details; } - static selected: Plugin|null = null - + static selected: Plugin | null = null; + static menu = new Menu([ new MenuSeparator('general'), { @@ -873,43 +927,43 @@ export class Plugin { title: tl('generic.share') + ': ' + plugin.title, icon: 'extension', form: { - link: {type: 'text', value: url, readonly: true, share_text: true} - } + link: { type: 'text', value: url, readonly: true, share_text: true }, + }, }).show(); - } + }, }, new MenuSeparator('installation'), { name: 'dialog.plugins.install', icon: 'add', - condition: plugin => (!plugin.installed && plugin.isInstallable() == true), + condition: plugin => !plugin.installed && plugin.isInstallable() == true, click(plugin) { plugin.install(); - } + }, }, { name: 'dialog.plugins.uninstall', icon: 'delete', - condition: plugin => (plugin.installed), + condition: plugin => plugin.installed, click(plugin) { plugin.uninstall(); - } + }, }, { name: 'dialog.plugins.disable', icon: 'bedtime', - condition: plugin => (plugin.installed && !plugin.disabled), + condition: plugin => plugin.installed && !plugin.disabled, click(plugin) { plugin.toggleDisabled(); - } + }, }, { name: 'dialog.plugins.enable', icon: 'bedtime', - condition: plugin => (plugin.installed && plugin.disabled), + condition: plugin => plugin.installed && plugin.disabled, click(plugin) { plugin.toggleDisabled(); - } + }, }, { name: 'dialog.plugins.revoke_permissions', @@ -917,32 +971,35 @@ export class Plugin { condition: isApp && ((plugin: Plugin) => getPluginPermissions(plugin)), click(plugin: Plugin) { let revoked = revokePluginPermissions(plugin); - Blockbench.showQuickMessage(`Revoked ${revoked.length} permissions. Restart to apply`, 2000); + Blockbench.showQuickMessage( + `Revoked ${revoked.length} permissions. Restart to apply`, + 2000 + ); plugin.getPluginDetails(true); - } + }, }, new MenuSeparator('developer'), { name: 'dialog.plugins.reload', icon: 'refresh', - condition: plugin => (plugin.installed && plugin.isReloadable()), + condition: plugin => plugin.installed && plugin.isReloadable(), click(plugin) { plugin.reload(); - } + }, }, { name: 'menu.animation.open_location', icon: 'folder', - condition: plugin => (isApp && plugin.source == 'file'), + condition: plugin => isApp && plugin.source == 'file', click(plugin) { Filesystem.showFileInFolder(plugin.path); - } + }, }, - ]) + ]); static register(id: string, data: PluginOptions) { if (typeof id !== 'string' || typeof data !== 'object') { - console.warn('Plugin.register: not enough arguments, string and object required.') + console.warn('Plugin.register: not enough arguments, string and object required.'); return; } var plugin = Plugins.registered[id]; @@ -957,11 +1014,11 @@ export class Plugin { if (!plugin) { Blockbench.showMessageBox({ translateKey: 'load_plugin_failed', - message: tl('message.load_plugin_failed.message', [id]) - }) + message: tl('message.load_plugin_failed.message', [id]), + }); return; - }; - plugin.extend(data) + } + plugin.extend(data); if (plugin.isInstallable() == true && plugin.disabled == false) { if (plugin.onload instanceof Function) { Plugins.currently_loading = id; @@ -973,36 +1030,34 @@ export class Plugin { } } - // Alias for typescript export const BBPlugin = Plugin; - if (isApp) { - Plugins.path = app.getPath('userData')+osfs+'plugins'+osfs - fs.readdir(Plugins.path, function(err) { + Plugins.path = app.getPath('userData') + osfs + 'plugins' + osfs; + fs.readdir(Plugins.path, function (err) { if (err) { - fs.mkdir(Plugins.path, function(a) {}) + fs.mkdir(Plugins.path, function (a) {}); } - }) + }); } else { - Plugins.path = Plugins.api_path+'/'; + Plugins.path = Plugins.api_path + '/'; } Plugins.loading_promise = new Promise((resolve, reject) => { $.ajax({ cache: false, - url: Plugins.api_path+'.json', + url: Plugins.api_path + '.json', dataType: 'json', success(data) { Plugins.json = data; - + resolve(); Plugins.loading_promise = null; }, error() { - console.log('Could not connect to plugin server') - $('#plugin_available_empty').text('Could not connect to plugin server') + console.log('Could not connect to plugin server'); + $('#plugin_available_empty').text('Could not connect to plugin server'); resolve(); Plugins.loading_promise = null; @@ -1010,16 +1065,16 @@ Plugins.loading_promise = new Promise((resolve, reject) => { settings.cdn_mirror.set(true); console.log('Switching to plugin CDN mirror. Restart to apply.'); } - } + }, }); -}) +}); $.getJSON('https://blckbn.ch/api/stats/plugins?weeks=2', data => { Plugins.download_stats = data; if (Plugins.json) { Plugins.sort(); } -}) +}); export async function loadInstalledPlugins() { if (Plugins.loading_promise) { @@ -1038,23 +1093,27 @@ export async function loadInstalledPlugins() { // Load plugins if (Plugins.installed.length > 0) { - // Resolve dependency order // TODO: solve dependency order on plugins that load asynchronously (on update from web etc.) function resolveDependencies(installation: PluginInstallation, depth) { - if (depth > 10) { - console.error(`Could not resolve plugin dependencies: Recursive dependency on plugin "${installation.id}"`, installation); + if (depth > 10) { + console.error( + `Could not resolve plugin dependencies: Recursive dependency on plugin "${installation.id}"`, + installation + ); return; } for (let dependency_id of installation.dependencies) { - let dependency_installation = Plugins.installed.find(inst => inst.id == dependency_id); + let dependency_installation = Plugins.installed.find( + inst => inst.id == dependency_id + ); let this_index = Plugins.installed.indexOf(installation); - let dep_index = Plugins.installed.indexOf(dependency_installation); + let dep_index = Plugins.installed.indexOf(dependency_installation); if (dependency_installation && dep_index > this_index) { Plugins.installed.remove(dependency_installation); Plugins.installed.splice(this_index, 0, dependency_installation); if (dependency_installation.dependencies?.length) { - resolveDependencies(dependency_installation, depth+1); + resolveDependencies(dependency_installation, depth + 1); } } } @@ -1068,40 +1127,49 @@ export async function loadInstalledPlugins() { // Install plugins var load_counter = 0; Plugins.installed.slice().forEach(function loadPlugin(installation) { - if (installation.source == 'file') { // Dev Plugins if (isApp && fs.existsSync(installation.path)) { - var instance = new Plugin(installation.id, {disabled: installation.disabled}); - install_promises.push(instance.loadFromFile({path: installation.path, name: installation.path, content: ''}, false)); + var instance = new Plugin(installation.id, { disabled: installation.disabled }); + install_promises.push( + instance.loadFromFile( + { path: installation.path, name: installation.path, content: '' }, + false + ) + ); load_counter++; - console.log(`🧩📁 Loaded plugin "${installation.id || installation.path}" from file`); + console.log( + `🧩📁 Loaded plugin "${installation.id || installation.path}" from file` + ); } else { Plugins.installed.remove(installation); } - } else if (installation.source == 'url') { // URL if (installation.path) { - var instance = new Plugin(installation.id, {disabled: installation.disabled}); + var instance = new Plugin(installation.id, { disabled: installation.disabled }); install_promises.push(instance.loadFromURL(installation.path, false)); load_counter++; - console.log(`🧩🌐 Loaded plugin "${installation.id || installation.path}" from URL`); + console.log( + `🧩🌐 Loaded plugin "${installation.id || installation.path}" from URL` + ); } else { Plugins.installed.remove(installation); } - } else if (online_access) { // Store plugin let plugin = Plugins.all.find(p => p.id == installation.id); if (plugin) { plugin.installed = true; if (installation.disabled) plugin.disabled = true; - - if (isApp && ( - (installation.version && plugin.version && !compareVersions(plugin.version, installation.version)) || - Blockbench.isOlderThan(plugin.min_version) - )) { + + if ( + isApp && + ((installation.version && + plugin.version && + !compareVersions(plugin.version, installation.version)) || + Blockbench.isOlderThan(plugin.min_version)) + ) { // Get from file let promise = plugin.load(false); install_promises.push(promise); @@ -1114,32 +1182,29 @@ export async function loadInstalledPlugins() { } load_counter++; console.log(`🧩🛒 Loaded plugin "${installation.id}" from store`); - } else if (Plugins.json instanceof Object && navigator.onLine) { Plugins.installed.remove(installation); } - } else if (isApp && installation.source == 'store') { // Offline install store plugin - let plugin = new Plugin(installation.id); + let plugin = new Plugin(installation.id); let promise = plugin.load(false); install_promises.push(promise); } else { Plugins.installed.remove(installation); } - }) - console.log(`Loaded ${load_counter} plugin${pluralS(load_counter)}`) + }); + console.log(`Loaded ${load_counter} plugin${pluralS(load_counter)}`); } - StateMemory.save('installed_plugins') - + StateMemory.save('installed_plugins'); install_promises.forEach(promise => { promise.catch(console.error); - }) + }); return await Promise.allSettled(install_promises); } -BARS.defineActions(function() { +BARS.defineActions(function () { let actions_setup = false; Plugins.dialog = new Dialog({ id: 'plugins', @@ -1150,7 +1215,9 @@ BARS.defineActions(function() { onOpen() { if (!actions_setup) { BarItems.load_plugin.toElement(document.getElementById('plugins_list_main_bar')); - BarItems.load_plugin_from_url.toElement(document.getElementById('plugins_list_main_bar')); + BarItems.load_plugin_from_url.toElement( + document.getElementById('plugins_list_main_bar') + ); actions_setup = true; } }, @@ -1178,7 +1245,7 @@ BARS.defineActions(function() { item.description.toUpperCase().includes(search_name) || item.author.toUpperCase().includes(search_name) || item.tags.find(tag => tag.toUpperCase().includes(search_name)) - ) + ); }); let installed = filtered.filter(p => p.installed); let not_installed = filtered.filter(p => !p.installed); @@ -1186,40 +1253,57 @@ BARS.defineActions(function() { } else { return this.items.filter(item => { return (this.tab == 'installed') == item.installed; - }) + }); } }, suggested_rows() { - let tags = ["Animation"]; + let tags = ['Animation']; this.items.forEach(plugin => { if (!plugin.installed) return; - tags.safePush(...plugin.tags) - }) - let rows = tags.map(tag => { - let plugins = this.items.filter(plugin => !plugin.installed && plugin.tags.includes(tag) && !plugin.tags.includes('Deprecated')).slice(0, 12); - return { - title: tag, - plugins, - } - }).filter(row => row.plugins.length > 2); + tags.safePush(...plugin.tags); + }); + let rows = tags + .map(tag => { + let plugins = this.items + .filter( + plugin => + !plugin.installed && + plugin.tags.includes(tag) && + !plugin.tags.includes('Deprecated') + ) + .slice(0, 12); + return { + title: tag, + plugins, + }; + }) + .filter(row => row.plugins.length > 2); //rows.sort((a, b) => a.plugins.length - b.plugins.length); rows.sort(() => Math.random() - 0.5); - let cutoff = Date.now() - (3_600_000 * 24 * 28); - let new_plugins = this.items.filter(plugin => !plugin.installed && plugin.creation_date > cutoff && !plugin.tags.includes('Deprecated')); + let cutoff = Date.now() - 3_600_000 * 24 * 28; + let new_plugins = this.items.filter( + plugin => + !plugin.installed && + plugin.creation_date > cutoff && + !plugin.tags.includes('Deprecated') + ); if (new_plugins.length) { new_plugins.sort((a, b) => a.creation_date - b.creation_date); let new_row = { title: 'New', - plugins: new_plugins.slice(0, 12) - } + plugins: new_plugins.slice(0, 12), + }; rows.splice(0, 0, new_row); } return rows.slice(0, 3); }, viewed_plugins() { - return this.plugin_search.slice(this.page * this.per_page, (this.page+1) * this.per_page); + return this.plugin_search.slice( + this.page * this.per_page, + (this.page + 1) * this.per_page + ); }, pages() { let pages = []; @@ -1238,7 +1322,7 @@ BARS.defineActions(function() { } } return plugin_settings; - } + }, }, methods: { setTab(tab) { @@ -1281,7 +1365,9 @@ BARS.defineActions(function() { }, getDependencyName(dependency: string) { let plugin = Plugins.all.find(p => p.id == dependency); - return plugin ? (plugin.title + (plugin.installed ? ' ✓' : '')) : (dependency + ' ⚠'); + return plugin + ? plugin.title + (plugin.installed ? ' ✓' : '') + : dependency + ' ⚠'; }, isDependencyInstalled(dependency: string) { let plugin = Plugins.all.find(p => p.id == dependency); @@ -1289,11 +1375,11 @@ BARS.defineActions(function() { }, getTagClass(tag: string): string { if (tag.match(/^(local|remote)$/i)) { - return 'plugin_tag_source' + return 'plugin_tag_source'; } else if (tag.match(/^minecraft/i)) { - return 'plugin_tag_mc' + return 'plugin_tag_mc'; } else if (tag.match(/^deprecated/i)) { - return 'plugin_tag_deprecated' + return 'plugin_tag_deprecated'; } }, formatAbout(about: string) { @@ -1302,7 +1388,7 @@ BARS.defineActions(function() { reduceLink(url: string): string { url = url.replace('https://', '').replace(/\/$/, ''); if (url.length > 50) { - return url.substring(0, 50)+'...'; + return url.substring(0, 50) + '...'; } else { return url; } @@ -1319,14 +1405,18 @@ BARS.defineActions(function() { for (let match of line.matchAll(/\[.+?\]\(.+?\)/g)) { let split = match[0].search(/\]\(/); let label = match[0].substring(1, split); - let href = match[0].substring(split+2, match[0].length-1); - let a = Interface.createElement('a', {href, title: href}, label); + let href = match[0].substring(split + 2, match[0].length - 1); + let a = Interface.createElement('a', { href, title: href }, label); content.push(line.substring(last_i, match.index)); content.push(a); last_i = match.index + match[0].length; } content.push(line.substring(last_i)); - let node = Interface.createElement('p', {}, content.filter(a => a)); + let node = Interface.createElement( + 'p', + {}, + content.filter(a => a) + ); return node.innerHTML; }, @@ -1340,7 +1430,7 @@ BARS.defineActions(function() { }, 20); }, openSettingInSettings(key, profile) { - Settings.openDialog({search_term: key, profile}); + Settings.openDialog({ search_term: key, profile }); }, settingContextMenu(setting, event) { new Menu([ @@ -1350,8 +1440,8 @@ BARS.defineActions(function() { click: () => { setting.ui_value = setting.default_value; Settings.saveLocalStorages(); - } - } + }, + }, ]).open(event); }, getProfileValuesForSetting(key) { @@ -1378,19 +1468,22 @@ BARS.defineActions(function() { name: format.name, icon: format.icon, description: format.description, - click: format.show_on_start_screen && (() => { - Dialog.open.close(); - StartScreen.open(); - StartScreen.vue.loadFormat(format); - }) - } - }) - }) + click: + format.show_on_start_screen && + (() => { + Dialog.open.close(); + StartScreen.open(); + StartScreen.vue.loadFormat(format); + }), + }; + }), + }); } let loaders = []; for (let id in ModelLoader.loaders) { - if (ModelLoader.loaders[id].plugin == plugin.id) loaders.push(ModelLoader.loaders[id]); + if (ModelLoader.loaders[id].plugin == plugin.id) + loaders.push(ModelLoader.loaders[id]); } if (loaders.length) { types.push({ @@ -1402,14 +1495,16 @@ BARS.defineActions(function() { name: loader.name, icon: loader.icon, description: loader.description, - click: loader.show_on_start_screen && (() => { - Dialog.open.close(); - StartScreen.open(); - StartScreen.vue.loadFormat(loader); - }) - } - }) - }) + click: + loader.show_on_start_screen && + (() => { + Dialog.open.close(); + StartScreen.open(); + StartScreen.vue.loadFormat(loader); + }), + }; + }), + }); } let codecs = []; @@ -1425,10 +1520,12 @@ BARS.defineActions(function() { id: codec.id, name: codec.name, icon: codec.export_action ? codec.export_action.icon : 'save', - description: codec.export_action ? codec.export_action.description : '' - } - }) - }) + description: codec.export_action + ? codec.export_action.description + : '', + }; + }), + }); } let bar_items = Keybinds.actions.filter(action => action.plugin == plugin.id); @@ -1446,12 +1543,14 @@ BARS.defineActions(function() { icon: tool.icon, description: tool.description, extra_info: tool.keybind.label, - click: Condition(tool.condition) && (() => { - ActionControl.select(tool.name); - }) - } - }) - }) + click: + Condition(tool.condition) && + (() => { + ActionControl.select(tool.name); + }), + }; + }), + }); } if (other_actions.length) { types.push({ @@ -1464,12 +1563,14 @@ BARS.defineActions(function() { icon: action.icon, description: action.description, extra_info: action.keybind.label, - click: Condition(action.condition) && (() => { - ActionControl.select(action.name); - }) - } - }) - }) + click: + Condition(action.condition) && + (() => { + ActionControl.select(action.name); + }), + }; + }), + }); } let panels = []; @@ -1484,10 +1585,10 @@ BARS.defineActions(function() { return { id: panel.id, name: panel.name, - icon: panel.icon - } - }) - }) + icon: panel.icon, + }; + }), + }); } let setting_list = []; @@ -1505,13 +1606,15 @@ BARS.defineActions(function() { icon: setting.icon, click: () => { this.page_tab = 'settings'; - } - } - }) - }) + }, + }; + }), + }); } - let validator_checks = Validator.checks.filter(check => check.plugin == plugin.id); + let validator_checks = Validator.checks.filter( + check => check.plugin == plugin.id + ); if (validator_checks.length) { types.push({ id: 'validator_checks', @@ -1520,10 +1623,10 @@ BARS.defineActions(function() { return { id: validator_check.id, name: validator_check.name, - icon: 'task_alt' - } - }) - }) + icon: 'task_alt', + }; + }), + }); } //TODO //Modes @@ -1535,7 +1638,7 @@ BARS.defineActions(function() { pureMarked, capitalizeFirstLetter, tl, - Condition + Condition, }, mount_directly: true, template: ` @@ -1812,17 +1915,14 @@ BARS.defineActions(function() {
    - ` - } - }) + `, + }, + }); new Action('plugins_window', { icon: 'extension', category: 'blockbench', - side_menu: new Menu('plugins_window', [ - 'load_plugin', - 'load_plugin_from_url' - ]), + side_menu: new Menu('plugins_window', ['load_plugin', 'load_plugin_from_url']), click(e) { if (settings.classroom_mode.value) { Blockbench.showQuickMessage('message.classroom_mode.install_plugin'); @@ -1831,16 +1931,16 @@ BARS.defineActions(function() { Plugins.dialog.show(); let none_installed = !Plugins.all.find(plugin => plugin.installed); if (none_installed) Plugins.dialog.content_vue.tab = 'available'; - $('dialog#plugins #plugin_search_bar input').trigger('focus') - } - }) + $('dialog#plugins #plugin_search_bar input').trigger('focus'); + }, + }); new Action('reload_plugins', { icon: 'sync', category: 'blockbench', click() { - Plugins.devReload() - } - }) + Plugins.devReload(); + }, + }); new Action('load_plugin', { icon: 'fa-file-code', category: 'blockbench', @@ -1849,15 +1949,18 @@ BARS.defineActions(function() { Blockbench.showQuickMessage('message.classroom_mode.install_plugin'); return; } - Blockbench.import({ - resource_id: 'dev_plugin', - extensions: ['js'], - type: 'Blockbench Plugin', - }, function(files) { - new Plugin().loadFromFile(files[0], true) - }) - } - }) + Blockbench.import( + { + resource_id: 'dev_plugin', + extensions: ['js'], + type: 'Blockbench Plugin', + }, + function (files) { + new Plugin().loadFromFile(files[0], true); + } + ); + }, + }); new Action('load_plugin_from_url', { icon: 'cloud_download', category: 'blockbench', @@ -1867,29 +1970,28 @@ BARS.defineActions(function() { return; } Blockbench.textPrompt('URL', '', url => { - new Plugin().loadFromURL(url, true) - }) - } - }) + new Plugin().loadFromURL(url, true); + }); + }, + }); new Action('add_plugin', { icon: 'add', category: 'blockbench', click() { setTimeout(_ => ActionControl.select('+plugin: '), 1); - } - }) + }, + }); new Action('remove_plugin', { icon: 'remove', category: 'blockbench', click() { setTimeout(_ => ActionControl.select('-plugin: '), 1); - } - }) -}) - + }, + }); +}); Object.assign(window, { Plugins, Plugin, - BBPlugin + BBPlugin, }); diff --git a/js/shaders/shader.ts b/js/shaders/shader.ts index 154fd4af1..5a918b404 100644 --- a/js/shaders/shader.ts +++ b/js/shaders/shader.ts @@ -1,15 +1,18 @@ -import { settings } from "../interface/settings"; +import { settings } from '../interface/settings'; /** * Prepare shader with the correct options depending on device and settings * @internal */ export function prepareShader(shader: string): string { - if (settings.antialiasing_bleed_fix.value == false || Preview.selected?.renderer.capabilities.isWebGL2 != true) { + if ( + settings.antialiasing_bleed_fix.value == false || + Preview.selected?.renderer.capabilities.isWebGL2 != true + ) { shader = shader.replace(/centroid /g, ''); } if (!isApp) { shader = shader.replace('precision highp', 'precision mediump'); } return shader; -} \ No newline at end of file +} diff --git a/js/texturing/ColorPickerNormal.vue b/js/texturing/ColorPickerNormal.vue index 6d2949b0f..518d335fc 100644 --- a/js/texturing/ColorPickerNormal.vue +++ b/js/texturing/ColorPickerNormal.vue @@ -1,9 +1,14 @@ @@ -17,108 +22,109 @@ export default { props: { width: Number, height: Number, - value: String + value: String, + }, + data() { + return {}; }, - data() {return { - }}, computed: { position() { let color = new tinycolor(this.value); let rgb = color.toRgb(); - let square = { - x: (rgb.r/255 - 0.5) * 100, - y: (rgb.g/255 - 0.5) * 100, + let square = { + x: (rgb.r / 255 - 0.5) * 100, + y: (rgb.g / 255 - 0.5) * 100, }; let angle = Math.atan2(square.y, square.x); let distance = Math.sqrt(Math.pow(square.x, 2) + Math.pow(square.y, 2)); distance = Math.clamp(distance, -50, 50); return { - x: (50 + Math.cos(angle) * distance) / 100 * (this.height-10) + 5, - y: (50 - Math.sin(angle) * distance) / 100 * (this.width-10) + 5, - } - } + x: ((50 + Math.cos(angle) * distance) / 100) * (this.height - 10) + 5, + y: ((50 - Math.sin(angle) * distance) / 100) * (this.width - 10) + 5, + }; + }, }, methods: { pointerDown(e1) { let bounding_box = this.$refs.reference.getBoundingClientRect(); - let onMove = (e2) => { + let onMove = e2 => { let x = e2.clientX - bounding_box.x; let y = e2.clientY - bounding_box.y; let color = new tinycolor({ r: (x / (this.width - 10)) * 255, g: (1 - y / (this.height - 10)) * 255, - b: 255 - }) - this.$emit('input', '#'+color.toHex()); - } - let onUp = (e2) => { + b: 255, + }); + this.$emit('input', '#' + color.toHex()); + }; + let onUp = e2 => { document.removeEventListener('pointermove', onMove); document.removeEventListener('pointerup', onUp); - } + }; document.addEventListener('pointermove', onMove); document.addEventListener('pointerup', onUp); onMove(e1); }, tl, }, -} +}; \ No newline at end of file +.normal_map_color_picker { + position: relative; + height: 300px; +} +.normal_map_color_picker--bg_x, +.normal_map_color_picker--bg_y { + top: 5px; + left: 5px; + height: calc(100% - 10px); + width: calc(100% - 10px); + position: absolute; + pointer-events: none; + border-radius: 50%; +} +.normal_map_color_picker--bg_x { + background: linear-gradient(to right, rgb(0, 0, 255), rgb(255, 0, 255)); +} +.normal_map_color_picker--bg_y { + background: linear-gradient(to top, rgb(0, 0, 255), rgb(0, 255, 255)); + mix-blend-mode: lighten; +} +.normal_map_color_picker--bg_x::before, +.normal_map_color_picker--bg_x::after { + content: ''; + width: 2px; + height: 2px; + position: absolute; + background-color: var(--color-light); + opacity: 0.2; + z-index: 1; +} +.normal_map_color_picker--bg_x::before { + width: 100%; + top: calc(50% - 1px); + left: 0; + right: 0; +} +.normal_map_color_picker--bg_x::after { + height: 100%; + top: 0; + bottom: 0; + left: calc(50% - 1px); +} +.normal_map_color_picker--cursor { + position: absolute; + border-radius: 6px; + height: 10px; + width: 10px; + border: 1px solid var(--color-border); + background: var(--color-light); + cursor: pointer; + top: 0; + left: 0; + z-index: 2; + margin: -5px; +} + diff --git a/js/util/event_system.ts b/js/util/event_system.ts index 76f25acd8..45c8410e6 100644 --- a/js/util/event_system.ts +++ b/js/util/event_system.ts @@ -1,10 +1,10 @@ type EventListener = (data: any) => void; type Deletable = { - delete(): void -} + delete(): void; +}; export class EventSystem { - events: Record + events: Record; constructor() { this.events = {}; } @@ -33,9 +33,8 @@ export class EventSystem { for (let name of event_names) { this.events[name].remove(cb); } - } - } - + }, + }; } else { if (!this.events[event_name]) { this.events[event_name] = []; @@ -44,8 +43,8 @@ export class EventSystem { return { delete: () => { this.events[event_name].remove(cb); - } - } + }, + }; } } once(event_name: string, cb: EventListener): Deletable { @@ -53,10 +52,10 @@ export class EventSystem { console.warn(cb, 'is not a function!'); return; } - let listener = (data) => { + let listener = data => { this.removeListener(event_name, listener); cb(data); - } + }; return this.on(event_name, listener); } addListener(event_name: string, cb: EventListener) { @@ -68,11 +67,10 @@ export class EventSystem { for (let name of event_names) { if (this.events[name]) this.events[name].remove(cb); } - } else if (this.events[event_name]) { this.events[event_name].remove(cb); } } } // @ts-ignore -window.EventSystem = EventSystem \ No newline at end of file +window.EventSystem = EventSystem; diff --git a/js/util/global.d.ts b/js/util/global.d.ts index 7a8ffa462..feefc6a6f 100644 --- a/js/util/global.d.ts +++ b/js/util/global.d.ts @@ -1,138 +1,136 @@ declare global { - const Transformer: any - + const Transformer: any; interface HTMLImageElement { - src: string + src: string; tex: THREE.Texture & { - magFilter: THREE.TextureFilter - minFilter: THREE.TextureFilter - } + magFilter: THREE.TextureFilter; + minFilter: THREE.TextureFilter; + }; } interface Date { /** * Returns a timestamp in the format 16:30 */ - getTimestamp(): string - getDateArray(): number[] - getDateString(): string - dayOfYear(): number + getTimestamp(): string; + getDateArray(): number[]; + getDateString(): string; + dayOfYear(): number; } interface Number { - toDigitString(digits: number): string + toDigitString(digits: number): string; } interface Event { - readonly ctrlOrCmd: boolean + readonly ctrlOrCmd: boolean; } interface Math { - limitNumber(number: number, min: number, max: number): number - radToDeg(radians: number): number - degToRad(degrees: number): number + limitNumber(number: number, min: number, max: number): number; + radToDeg(radians: number): number; + degToRad(degrees: number): number; /** * Rounds the input number to N digits */ - roundTo(number: number, digits: number): number + roundTo(number: number, digits: number): number; /** * Test if the number is between two other numbers */ - isBetween(number: number, limit1: number, limit2: number): boolean + isBetween(number: number, limit1: number, limit2: number): boolean; /** * Checks if the absolute difference between a and b is smaller than epsilon (defaults to 0.001) */ - epsilon(a: number, b: number, epsilon: number = 0.001): boolean + epsilon(a: number, b: number, epsilon: number = 0.001): boolean; /** * Take a rotation value in degrees, and trim it to a value between -180 and 180, while keeping the same angle */ - trimDeg(degrees: number): number - isPowerOfTwo(number: number): boolean + trimDeg(degrees: number): number; + isPowerOfTwo(number: number): boolean; /*** * Checks if the input is of type number */ - isNumber(number: any): boolean + isNumber(number: any): boolean; /** * Generates a random float between a and b */ - randomab(a: number, b: number): number - areMultiples(number1: number, number2: number): boolean + randomab(a: number, b: number): number; + areMultiples(number1: number, number2: number): boolean; /** * Return the next highest power of n * @param num Power * @param min Value that the result should be equal to or higher than */ - getNextPower(num: number, min: number): number + getNextPower(num: number, min: number): number; /** * Snap the value to the closest number in a list of 1 or more snap points */ - snapToValues(number: number, snap_points: number[], epsilon: number): number + snapToValues(number: number, snap_points: number[], epsilon: number): number; /** * Get the lerp alpha value of m, between points a and b */ - getLerp: (a: number, b: number, m: number) => number + getLerp: (a: number, b: number, m: number) => number; /** * Lerp between a and b using the 0-1 alpha value */ - lerp: (a: number, b: number, alpha: number) => number + lerp: (a: number, b: number, alpha: number) => number; /** * Return a random integer */ - randomInteger: (a: number, b: number) => number - hermiteBlend: (input: number) => number - clamp: (number: number, min: number, max: number) => number + randomInteger: (a: number, b: number) => number; + hermiteBlend: (input: number) => number; + clamp: (number: number, min: number, max: number) => number; } interface Array { - remove(item: any): boolean + remove(item: any): boolean; /*** * Adds items to the array if they are not yet in the array */ - safePush(...items: any): boolean - equals(array: Array): boolean - replace(items: T[]): boolean - allAre(callback: (item: T, index: number) => unknown): boolean - findInArray(key: string, value: any): number - positiveItems(): number + safePush(...items: any): boolean; + equals(array: Array): boolean; + replace(items: T[]): boolean; + allAre(callback: (item: T, index: number) => unknown): boolean; + findInArray(key: string, value: any): number; + positiveItems(): number; /** * Empties the array */ - empty(): this - last(): T - allEqual(item: T): boolean - random(): T - toggle(item: T, state?: boolean): boolean + empty(): this; + last(): T; + allEqual(item: T): boolean; + random(): T; + toggle(item: T, state?: boolean): boolean; /** * Same as forEach, but in reverse order */ - forEachReverse(callback: (item: T, index: number) => void): void + forEachReverse(callback: (item: T, index: number) => void): void; /** * Returns the amount of items that exist in this array and the reference array */ - overlap(arr: Array): number + overlap(arr: Array): number; - V3_set(x: number, y: number, z: number): ArrayVector3 - V3_set(values: ArrayVector3): ArrayVector3 + V3_set(x: number, y: number, z: number): ArrayVector3; + V3_set(values: ArrayVector3): ArrayVector3; // V3_set(value: THREE.Vector3): this - V3_add(x: number, y: number, z: number): ArrayVector3 - V3_add(values: ArrayVector3): ArrayVector3 - V3_add(value: THREE.Vector3): ArrayVector3 - V3_subtract(x: number, y: number, z: number): ArrayVector3 - V3_subtract(values: ArrayVector3): ArrayVector3 - V3_subtract(value: THREE.Vector3): ArrayVector3 - V3_multiply(scalar: number): ArrayVector3 - V3_multiply(x: number, y: number, z: number): ArrayVector3 - V3_multiply(values: ArrayVector3): ArrayVector3 - V3_multiply(value: THREE.Vector3): ArrayVector3 - V3_divide(x: number, y: number, z: number): ArrayVector3 - V3_divide(values: ArrayVector3): ArrayVector3 - V3_divide(value: THREE.Vector3): ArrayVector3 - V3_toThree(): THREE.Vector3 - V3_toEuler(): THREE.Euler + V3_add(x: number, y: number, z: number): ArrayVector3; + V3_add(values: ArrayVector3): ArrayVector3; + V3_add(value: THREE.Vector3): ArrayVector3; + V3_subtract(x: number, y: number, z: number): ArrayVector3; + V3_subtract(values: ArrayVector3): ArrayVector3; + V3_subtract(value: THREE.Vector3): ArrayVector3; + V3_multiply(scalar: number): ArrayVector3; + V3_multiply(x: number, y: number, z: number): ArrayVector3; + V3_multiply(values: ArrayVector3): ArrayVector3; + V3_multiply(value: THREE.Vector3): ArrayVector3; + V3_divide(x: number, y: number, z: number): ArrayVector3; + V3_divide(values: ArrayVector3): ArrayVector3; + V3_divide(value: THREE.Vector3): ArrayVector3; + V3_toThree(): THREE.Vector3; + V3_toEuler(): THREE.Euler; } interface Event { - shiftKey?: boolean + shiftKey?: boolean; } - let osfs: string + let osfs: string; } -export { } - +export {}; diff --git a/js/util/json.ts b/js/util/json.ts index ae4bbd8fb..4a03cad1f 100644 --- a/js/util/json.ts +++ b/js/util/json.ts @@ -5,7 +5,7 @@ export class oneLiner { if (data !== undefined) { for (var key in data) { if (data.hasOwnProperty(key)) { - this[key] = data[key] + this[key] = data[key]; } } } @@ -16,15 +16,15 @@ interface JSONCompileOptions { /** * Indentation string. If omitted, will default to the indentation from Blockbench's settings */ - indentation?: string + indentation?: string; /** * If true, minify everything into one line */ - small?: boolean + small?: boolean; /** * Whether to add a newline character at the end of the file. If omitted, use value from Blockbench settings */ - final_newline?: boolean + final_newline?: boolean; } /** * Compile an Object into a JSON string @@ -36,13 +36,22 @@ export function compileJSON(object: any, options: JSONCompileOptions = {}): stri let indentation = options.indentation; if (typeof indentation !== 'string') { switch (settings.json_indentation.value) { - case 'spaces_4': indentation = ' '; break; - case 'spaces_2': indentation = ' '; break; - case 'tabs': default: indentation = '\t'; break; + case 'spaces_4': + indentation = ' '; + break; + case 'spaces_2': + indentation = ' '; + break; + case 'tabs': + default: + indentation = '\t'; + break; } } function newLine(tabs) { - if (options.small === true) {return '';} + if (options.small === true) { + return ''; + } let s = '\n'; for (let i = 0; i < tabs; i++) { s += indentation; @@ -65,69 +74,84 @@ export function compileJSON(object: any, options: JSONCompileOptions = {}): stri return string; } function handleVar(o, tabs, breaks = true) { - var out = '' + var out = ''; let type = typeof o; if (type === 'string') { //String - out += '"' + escape(o) + '"' + out += '"' + escape(o) + '"'; } else if (type === 'boolean') { //Boolean - out += (o ? 'true' : 'false') + out += o ? 'true' : 'false'; } else if (o === null || o === Infinity || o === -Infinity) { //Null - out += 'null' + out += 'null'; } else if (type === 'number') { //Number - o = (Math.round(o*100000)/100000).toString() - if (o == 'NaN') o = null - out += o + o = (Math.round(o * 100000) / 100000).toString(); + if (o == 'NaN') o = null; + out += o; } else if (o instanceof Array) { //Array - let has_content = false + let has_content = false; let multiline = !!o.find(item => typeof item === 'object'); if (!multiline) { let length = 0; o.forEach(item => { - length += typeof item === 'string' ? (item.length+4) : 3; + length += typeof item === 'string' ? item.length + 4 : 3; }); if (length > 140) multiline = true; } - out += '[' + out += '['; for (var i = 0; i < o.length; i++) { - var compiled = handleVar(o[i], tabs+1) + var compiled = handleVar(o[i], tabs + 1); if (compiled) { - if (has_content) {out += ',' + ((options.small || multiline) ? '' : ' ')} - if (multiline) {out += newLine(tabs)} - out += compiled - has_content = true + if (has_content) { + out += ',' + (options.small || multiline ? '' : ' '); + } + if (multiline) { + out += newLine(tabs); + } + out += compiled; + has_content = true; } } - if (multiline) {out += newLine(tabs-1)} - out += ']' + if (multiline) { + out += newLine(tabs - 1); + } + out += ']'; } else if (type === 'object') { //Object breaks = breaks && !(o instanceof oneLiner); - var has_content = false - out += '{' + var has_content = false; + out += '{'; for (var key in o) { if (o.hasOwnProperty(key)) { - var compiled = handleVar(o[key], tabs+1, breaks) + var compiled = handleVar(o[key], tabs + 1, breaks); if (compiled) { - if (has_content) {out += ',' + (breaks || options.small?'':' ')} - if (breaks) {out += newLine(tabs)} - out += '"' + escape(key) + '":' + (options.small === true ? '' : ' ') - out += compiled - has_content = true + if (has_content) { + out += ',' + (breaks || options.small ? '' : ' '); + } + if (breaks) { + out += newLine(tabs); + } + out += '"' + escape(key) + '":' + (options.small === true ? '' : ' '); + out += compiled; + has_content = true; } } } - if (breaks && has_content) {out += newLine(tabs-1)} - out += '}' + if (breaks && has_content) { + out += newLine(tabs - 1); + } + out += '}'; } return out; } let file = handleVar(object, 1); - if ((settings.final_newline.value && options.final_newline != false) || options.final_newline == true) { + if ( + (settings.final_newline.value && options.final_newline != false) || + options.final_newline == true + ) { file += '\n'; } return file; @@ -141,17 +165,17 @@ export function compileJSON(object: any, options: JSONCompileOptions = {}): stri */ export function autoParseJSON(data: string, feedback = true): any { if (data.substr(0, 4) === '') { - data = LZUTF8.decompress(data.substr(4), {inputEncoding: 'StorageBinaryString'}) + data = LZUTF8.decompress(data.substr(4), { inputEncoding: 'StorageBinaryString' }); } - if (data.charCodeAt(0) === 0xFEFF) { - data = data.substr(1) + if (data.charCodeAt(0) === 0xfeff) { + data = data.substr(1); } try { - data = JSON.parse(data) + data = JSON.parse(data); } catch (err1) { - data = data.replace(/\/\*[^(\*\/)]*\*\/|\/\/.*/g, '') + data = data.replace(/\/\*[^(\*\/)]*\*\/|\/\/.*/g, ''); try { - data = JSON.parse(data) + data = JSON.parse(data); } catch (err) { if (feedback === false) return; if (data.match(/\n\r?[><]{7}/)) { @@ -159,39 +183,40 @@ export function autoParseJSON(data: string, feedback = true): any { Blockbench.showMessageBox({ title: 'message.invalid_file.title', icon: 'fab.fa-git-alt', - message: 'message.invalid_file.merge_conflict' - }) + message: 'message.invalid_file.merge_conflict', + }); return; } let error_part = ''; function logErrantPart(whole, start, length) { - var line = whole.substr(0, start).match(/\n/gm) - line = line ? line.length+1 : 1 + var line = whole.substr(0, start).match(/\n/gm); + line = line ? line.length + 1 : 1; var result = ''; - var lines = whole.substr(start, length).split(/\n/gm) + var lines = whole.substr(start, length).split(/\n/gm); lines.forEach((s, i) => { - result += `#${line+i} ${s}\n` - }) - error_part = result.substr(0, result.length-1) + ' <-- HERE'; + result += `#${line + i} ${s}\n`; + }); + error_part = result.substr(0, result.length - 1) + ' <-- HERE'; console.log(error_part); } - console.error(err) - var length = err.toString().split('at position ')[1] + console.error(err); + var length = err.toString().split('at position ')[1]; if (length) { - length = parseInt(length) - var start = limitNumber(length-32, 0, Infinity) + length = parseInt(length); + var start = limitNumber(length - 32, 0, Infinity); - logErrantPart(data, start, 1+length-start) + logErrantPart(data, start, 1 + length - start); } else if (err.toString().includes('Unexpected end of JSON input')) { - - logErrantPart(data, data.length-16, 10) + logErrantPart(data, data.length - 16, 10); } // @ts-ignore Blockbench.showMessageBox({ translateKey: 'invalid_file', icon: 'error', - message: tl('message.invalid_file.message', [err]) + (error_part ? `\n\n\`\`\`\n${error_part}\n\`\`\`` : '') - }) + message: + tl('message.invalid_file.message', [err]) + + (error_part ? `\n\n\`\`\`\n${error_part}\n\`\`\`` : ''), + }); return; } } @@ -202,4 +227,4 @@ Object.assign(window, { oneLiner, compileJSON, autoParseJSON, -}) +}); diff --git a/js/util/molang.ts b/js/util/molang.ts index e62c60d65..5c291c13a 100644 --- a/js/util/molang.ts +++ b/js/util/molang.ts @@ -5,9 +5,9 @@ function isStringNumber(string: string) { const BRACKET_OPEN = '{(['; const BRACKET_CLOSE = '})]'; -export function invertMolang(molang: string): string -export function invertMolang(molang: number): number -export function invertMolang(molang: number|string): number|string { +export function invertMolang(molang: string): string; +export function invertMolang(molang: number): number; +export function invertMolang(molang: number | string): number | string { if (typeof molang == 'number') { return -molang; } @@ -50,7 +50,7 @@ function testInvertMolang(input: string) { let inverted = invertMolang(input); let negative_result = Animator.MolangParser.parse(inverted); if (positive_result == -negative_result) { - return inverted + return inverted; } else { console.warn([positive_result, negative_result], inverted); } @@ -58,5 +58,5 @@ function testInvertMolang(input: string) { Object.assign(window, { invertMolang, - testInvertMolang -}) + testInvertMolang, +}); diff --git a/js/util/property.ts b/js/util/property.ts index 6ee2869f4..86c6dd800 100644 --- a/js/util/property.ts +++ b/js/util/property.ts @@ -1,79 +1,78 @@ -import { FormElementOptions } from "../interface/form" +import { FormElementOptions } from '../interface/form'; interface PropertyOptions { - default?: any - condition?: ConditionResolvable - exposed?: boolean - export?: boolean - copy_value?: boolean - description?: string - placeholder?: string - label?: string + default?: any; + condition?: ConditionResolvable; + exposed?: boolean; + export?: boolean; + copy_value?: boolean; + description?: string; + placeholder?: string; + label?: string; /** * Options used for select types */ - options?: any + options?: any; /** * Enum possible values */ - values?: string[] - merge?(instance: any, data: any): void - reset?(instance: any): void - merge_validation?(value: any): boolean + values?: string[]; + merge?(instance: any, data: any): void; + reset?(instance: any): void; + merge_validation?(value: any): boolean; inputs?: { element_panel: { - input: FormElementOptions, - onChange?: () => void - } - } + input: FormElementOptions; + onChange?: () => void; + }; + }; } interface IPropertyType { - string: string - enum: string - molang: string - number: number - boolean: boolean - array: any[] - object: any - instance: any - vector: ArrayVector3 - vector2: ArrayVector2 - vector4: ArrayVector2 + string: string; + enum: string; + molang: string; + number: number; + boolean: boolean; + array: any[]; + object: any; + instance: any; + vector: ArrayVector3; + vector2: ArrayVector2; + vector4: ArrayVector2; } /** * Creates a new property on the specified target class */ export class Property implements Deletable { + class: any; + name: string; + type: T; + default: IPropertyType[T]; + export?: boolean; - class: any - name: string - type: T - default: IPropertyType[T] - export?: boolean + isString: boolean; + isEnum: boolean; + isMolang: boolean; + isNumber: boolean; + isBoolean: boolean; + isArray: boolean; + isVector: boolean; + isVector2: boolean; + isObject: boolean; + isInstance: boolean; - isString: boolean - isEnum: boolean - isMolang: boolean - isNumber: boolean - isBoolean: boolean - isArray: boolean - isVector: boolean - isVector2: boolean - isObject: boolean - isInstance: boolean - - enum_values?: string[] - merge_validation: undefined | ((value: IPropertyType[T]) => boolean) - condition: ConditionResolvable - exposed: boolean - label: any - inputs?: any - copy_value?: boolean - description?: string - placeholder?: string - options?: Record + enum_values?: string[]; + merge_validation: undefined | ((value: IPropertyType[T]) => boolean); + condition: ConditionResolvable; + exposed: boolean; + label: any; + inputs?: any; + copy_value?: boolean; + description?: string; + placeholder?: string; + options?: Record; constructor(target_class: any, type: T, name: string, options: PropertyOptions = {}) { if (!target_class.properties) { @@ -89,42 +88,84 @@ export class Property implements Deletable { this.default = options.default; } else { switch (this.type) { - case 'string': this.default = '' as any; break; - case 'enum': this.default = options.values?.[0] || '' as any; break; - case 'molang': this.default = '0' as any; break; - case 'number': this.default = 0 as any; break; - case 'boolean': this.default = false as any; break; - case 'array': this.default = [] as any; break; - case 'object': this.default = {} as any; break; - case 'instance': this.default = null as any; break; - case 'vector': this.default = [0, 0, 0] as any; break; - case 'vector2': this.default = [0, 0] as any; break; - case 'vector4': this.default = [0, 0, 0, 0] as any; break; + case 'string': + this.default = '' as any; + break; + case 'enum': + this.default = options.values?.[0] || ('' as any); + break; + case 'molang': + this.default = '0' as any; + break; + case 'number': + this.default = 0 as any; + break; + case 'boolean': + this.default = false as any; + break; + case 'array': + this.default = [] as any; + break; + case 'object': + this.default = {} as any; + break; + case 'instance': + this.default = null as any; + break; + case 'vector': + this.default = [0, 0, 0] as any; + break; + case 'vector2': + this.default = [0, 0] as any; + break; + case 'vector4': + this.default = [0, 0, 0, 0] as any; + break; } } switch (this.type) { - case 'string': this.isString = true; break; - case 'enum': this.isEnum = true; break; - case 'molang': this.isMolang = true; break; - case 'number': this.isNumber = true; break; - case 'boolean': this.isBoolean = true; break; - case 'array': this.isArray = true; break; - case 'object': this.isObject = true; break; - case 'instance': this.isInstance = true; break; + case 'string': + this.isString = true; + break; + case 'enum': + this.isEnum = true; + break; + case 'molang': + this.isMolang = true; + break; + case 'number': + this.isNumber = true; + break; + case 'boolean': + this.isBoolean = true; + break; + case 'array': + this.isArray = true; + break; + case 'object': + this.isObject = true; + break; + case 'instance': + this.isInstance = true; + break; case 'vector': case 'vector2': - case 'vector4': this.isVector = true; break; + case 'vector4': + this.isVector = true; + break; } if (this.isMolang) { Object.defineProperty(target_class.prototype, `${name}_string`, { get() { - return typeof this[name] == 'number' ? trimFloatNumber(this[name]) || '0' : this[name]; + return typeof this[name] == 'number' + ? trimFloatNumber(this[name]) || '0' + : this[name]; }, set(val) { this[name] = val; - } - }) + }, + }); } if (this.isEnum) { this.enum_values = options.values; @@ -132,7 +173,8 @@ export class Property implements Deletable { if (typeof options.merge == 'function') this.merge = options.merge; if (typeof options.reset == 'function') this.reset = options.reset; - if (typeof options.merge_validation == 'function') this.merge_validation = options.merge_validation; + if (typeof options.merge_validation == 'function') + this.merge_validation = options.merge_validation; if (options.condition) this.condition = options.condition; if (options.exposed == false) this.exposed = false; if (options.export == false) this.export = false; @@ -152,7 +194,7 @@ export class Property implements Deletable { } else if (this.isArray) { return this.default ? this.default.slice() : []; } else if (this.isObject) { - return Object.keys(this.default).length ? structuredClone(this.default) : {} as any; + return Object.keys(this.default).length ? structuredClone(this.default) : ({} as any); } else { return this.default; } @@ -161,34 +203,32 @@ export class Property implements Deletable { if (data[this.name] == undefined || !Condition(this.condition, instance)) return; if (this.isString) { - Merge.string(instance, data, this.name, this.merge_validation) - } - else if (this.isEnum) { - Merge.string(instance, data, this.name, val => (!this.enum_values || this.enum_values.includes(val))); - } - else if (this.isNumber) { - Merge.number(instance, data, this.name) - } - else if (this.isMolang) { - Merge.molang(instance, data, this.name) - } - else if (this.isBoolean) { - Merge.boolean(instance, data, this.name, this.merge_validation) - } - else if (this.isArray || this.isVector) { + Merge.string(instance, data, this.name, this.merge_validation); + } else if (this.isEnum) { + Merge.string( + instance, + data, + this.name, + val => !this.enum_values || this.enum_values.includes(val) + ); + } else if (this.isNumber) { + Merge.number(instance, data, this.name); + } else if (this.isMolang) { + Merge.molang(instance, data, this.name); + } else if (this.isBoolean) { + Merge.boolean(instance, data, this.name, this.merge_validation); + } else if (this.isArray || this.isVector) { if (data[this.name] instanceof Array) { if (instance[this.name] instanceof Array == false) { instance[this.name] = []; } instance[this.name].replace(data[this.name]); } - } - else if (this.isObject) { + } else if (this.isObject) { if (typeof data[this.name] == 'object') { instance[this.name] = structuredClone(data[this.name]); } - } - else if (this.isInstance) { + } else if (this.isInstance) { if (typeof data[this.name] === 'object') { instance[this.name] = data[this.name]; } @@ -206,7 +246,7 @@ export class Property implements Deletable { if (typeof item == 'object') { instance[this.name][i] = JSON.parse(JSON.stringify(item)); } - }) + }); } catch (err) { console.error(err); } @@ -221,8 +261,9 @@ export class Property implements Deletable { } } reset(instance: IPropertyType[T], force: boolean = false): void { - if (instance[this.name] == undefined && !Condition(this.condition, instance) && !force) return; - var dft = this.getDefault(instance) + if (instance[this.name] == undefined && !Condition(this.condition, instance) && !force) + return; + var dft = this.getDefault(instance); if (this.isArray || this.isVector) { if (instance[this.name] instanceof Array == false) { @@ -233,14 +274,14 @@ export class Property implements Deletable { instance[this.name] = dft; } } - static resetUniqueValues = function(type: any, instance: any) { + static resetUniqueValues = function (type: any, instance: any) { for (var key in type.properties) { let property = type.properties[key]; if (property.copy_value == false) { property.reset(instance); } } - } + }; } -Object.assign(window, {Property}); +Object.assign(window, { Property }); diff --git a/js/util/scoped_fs.ts b/js/util/scoped_fs.ts index e62e247ba..d15c597a1 100644 --- a/js/util/scoped_fs.ts +++ b/js/util/scoped_fs.ts @@ -1,5 +1,5 @@ -const fs: typeof import("node:fs") = require('node:fs'); -const PathModule: typeof import("node:path") = require('node:path'); +const fs: typeof import('node:fs') = require('node:fs'); +const PathModule: typeof import('node:path') = require('node:path'); /** * @internal @@ -157,9 +157,9 @@ export function createScopedFS(scope?: string) { stat(path: string) { checkPath(path); return fs.promises.stat(path); - } - } - } + }, + }, + }; } //cp: cp(src, dest, options, callback) @@ -220,4 +220,4 @@ export function createScopedFS(scope?: string) { //write: write(fd, buffer, offsetOrOptions, length, position, callback) //writeSync: writeSync(fd, buffer, offsetOrOptions, length, position) //writev: writev(fd, buffers, position, callback) -//writevSync: writevSync(fd, buffers, position) \ No newline at end of file +//writevSync: writevSync(fd, buffers, position) diff --git a/js/util/state_memory.ts b/js/util/state_memory.ts index 5cf92b49c..891503eec 100644 --- a/js/util/state_memory.ts +++ b/js/util/state_memory.ts @@ -2,25 +2,40 @@ const StateMemory = { /** * Initialize a memorized property */ - init(key: string, type: 'string'|'number'|'boolean'|'object'|'array') { - let saved: any = localStorage.getItem(`StateMemory.${key}`) + init(key: string, type: 'string' | 'number' | 'boolean' | 'object' | 'array') { + let saved: any = localStorage.getItem(`StateMemory.${key}`); if (typeof saved == 'string') { try { - saved = JSON.parse(saved) + saved = JSON.parse(saved); } catch (err) { localStorage.removeItem(`StateMemory.${key}`); } } - if ( saved !== null && (typeof saved == type || (type == 'array' && (saved instanceof Array))) ) { + if ( + saved !== null && + (typeof saved == type || (type == 'array' && saved instanceof Array)) + ) { StateMemory[key] = saved; } else { - StateMemory[key] = (() => {switch (type) { - case 'string': return ''; break; - case 'number': return 0; break; - case 'boolean': return false; break; - case 'object': return {}; break; - case 'array': return []; break; - }})(); + StateMemory[key] = (() => { + switch (type) { + case 'string': + return ''; + break; + case 'number': + return 0; + break; + case 'boolean': + return false; + break; + case 'object': + return {}; + break; + case 'array': + return []; + break; + } + })(); } }, set(key: string, value) { @@ -32,13 +47,13 @@ const StateMemory = { StateMemory.save(key); }, save(key: string) { - let serialized = JSON.stringify(StateMemory[key]) - localStorage.setItem(`StateMemory.${key}`, serialized) + let serialized = JSON.stringify(StateMemory[key]); + localStorage.setItem(`StateMemory.${key}`, serialized); }, - get(key: string): string|number|[]|boolean|any { + get(key: string): string | number | [] | boolean | any { return StateMemory[key]; - } -} + }, +}; export default StateMemory; Object.assign(window, { diff --git a/js/util/yaml.ts b/js/util/yaml.ts index 4be31f039..f4df3ca29 100644 --- a/js/util/yaml.ts +++ b/js/util/yaml.ts @@ -4,11 +4,13 @@ export namespace BBYaml { function parseValue(value: string): any { switch (value) { - case 'true': return true; - case 'false': return false; + case 'true': + return true; + case 'false': + return false; } if (value.startsWith('"') && value.endsWith('"')) { - return value.substring(1, value.length-1); + return value.substring(1, value.length - 1); } // @ts-ignore if (!isNaN(value)) { @@ -19,24 +21,24 @@ export namespace BBYaml { export function parse(input: string): any { let lines = input.split(/(\r?\n)+/g); let root = {}; - let stack: (any)[] = [root]; + let stack: any[] = [root]; let last_key: string; - + for (let line of lines) { let indent_level = line.match(/^\s*/)[0]?.replace(/ /g, '\t').length; let [key, value] = line.split(/: *(.*)/s); key = key.trim(); if (!key || key.startsWith('#')) continue; - while (indent_level < stack.length-1) { + while (indent_level < stack.length - 1) { stack.pop(); } if (key.startsWith('- ')) { key = key.substring(2); - if (stack.last() instanceof Array == false && stack[stack.length-2]) { + if (stack.last() instanceof Array == false && stack[stack.length - 2]) { // Convert to array - stack[stack.length-2][last_key] = stack[stack.length-1] = []; + stack[stack.length - 2][last_key] = stack[stack.length - 1] = []; } - + if (typeof value == 'string') { let obj = {}; stack.last().push(obj); @@ -50,7 +52,7 @@ export namespace BBYaml { if (value) { stack.last()[key] = parseValue(value); } else { - let obj = stack.last()[key] = {}; + let obj = (stack.last()[key] = {}); stack.push(obj); } } diff --git a/package-lock.json b/package-lock.json index 90bc23359..3c2a4a512 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "esbuild": "^0.25.8", "esbuild-plugin-glsl": "^1.2.2", "esbuild-vue": "^0.0.1", + "prettier": "^3.6.2", "typedoc": "^0.28.2", "typescript": "^5.8.3", "workbox-build": "^6.5.3" @@ -3395,6 +3396,22 @@ "prettier": "^1.18.2 || ^2.0.0" } }, + "node_modules/@vue/compiler-sfc/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "license": "MIT", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@vue/component-compiler": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/@vue/component-compiler/-/component-compiler-4.2.4.tgz", @@ -3475,6 +3492,23 @@ "url": "https://opencollective.com/postcss/" } }, + "node_modules/@vue/component-compiler-utils/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@vue/component-compiler-utils/node_modules/yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", @@ -8134,16 +8168,16 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, "license": "MIT", - "optional": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" diff --git a/package.json b/package.json index 76da71010..3504ac781 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,8 @@ "generate-types": "tsc --project tsconfig.json && node ./scripts/convert_types.js", "publish-types": "npm publish ./types", "generate-docs": "node ./scripts/generate_docs.js", - "generate-wiki": "node ./scripts/generate_docs.js --out \"../../blockbench.net/content/api/\"" + "generate-wiki": "node ./scripts/generate_docs.js --out \"../../blockbench.net/content/api/\"", + "format": "prettier --write \"js/**/*.{ts,json,vue}\"" }, "overrides": { "three": "$three" @@ -153,6 +154,7 @@ "esbuild": "^0.25.8", "esbuild-plugin-glsl": "^1.2.2", "esbuild-vue": "^0.0.1", + "prettier": "^3.6.2", "typedoc": "^0.28.2", "typescript": "^5.8.3", "workbox-build": "^6.5.3" diff --git a/types/custom/animation.d.ts b/types/custom/animation.d.ts index 060bfe2e3..3987defe9 100644 --- a/types/custom/animation.d.ts +++ b/types/custom/animation.d.ts @@ -6,7 +6,7 @@ declare class AnimationItem { getUndoCopy?(options?: any, save?: any): AnimationOptions } -interface AnimationOptions { +declare interface AnimationOptions { name?: string uuid?: string path?: string @@ -19,7 +19,7 @@ interface AnimationOptions { animators?: any } -interface AnimationUndoCopy { +declare interface AnimationUndoCopy { uuid: any name: any loop: any @@ -40,7 +40,7 @@ interface AnimationUndoCopy { * Blockbench overwrites libdom's {@link Animation} type with its own `Animation` Class, but TypeScript doesn't include a way to overwrite UMD global types. * To get around this, we changed the name of this class type declaration to `_Animation` and use that in the type definitions. */ -interface Animation {} +declare interface Animation {} /** * ⚠️ THIS IS TYPE ONLY ⚠️ @@ -116,12 +116,6 @@ declare class _Animation extends AnimationItem { file_menu: Menu } -interface MolangAutoCompletionItem { - text: string - label: string | undefined - overlap: number -} - declare namespace Animator { const open: boolean const MolangParser: Molang @@ -150,11 +144,6 @@ declare namespace Animator { function exportAnimationFile(path: string, save_as?: boolean): void function exportAnimationControllerFile(path: string, save_as?: boolean): void function resetLastValues(): void - function autocompleteMolang( - text: string, - position: number, - type: string - ): MolangAutoCompletionItem[] } interface AddChannelOptions { diff --git a/types/custom/blockbench.d.ts b/types/custom/blockbench.d.ts index 970b7c02b..f6dc33bb2 100644 --- a/types/custom/blockbench.d.ts +++ b/types/custom/blockbench.d.ts @@ -3,6 +3,8 @@ /// /// /// +/// +/// /// /// @@ -60,11 +62,11 @@ declare const NativeGlobals: { /** * Shader support */ -declare module "*.glsl" { - const value: string; - export default value; +declare module '*.glsl' { + const value: string + export default value } -declare module "*.bbtheme" { - const value: string | any; - export default value; +declare module '*.bbtheme' { + const value: string | any + export default value } diff --git a/types/custom/display_mode.d.ts b/types/custom/display_mode.d.ts index 364e66397..936a11255 100644 --- a/types/custom/display_mode.d.ts +++ b/types/custom/display_mode.d.ts @@ -1,55 +1,86 @@ /// -declare const DisplayMode: { - slots: string[] -} -interface DisplaySlotOptions { - rotation?: ArrayVector3 - translation?: ArrayVector3 - scale?: ArrayVector3 - rotation_pivot?: ArrayVector3 - scale_pivot?: ArrayVector3 - mirror?: [boolean, boolean, boolean] -} +declare global { + let display_mode: boolean + + type DisplaySlotName = + | 'firstperson_lefthand' + | 'firstperson_righthand' + | 'fixed' + | 'ground' + | 'gui' + | 'head' + | 'thirdperson_lefthand' + | 'thirdperson_righthand' + + const DisplayMode: { + slots: DisplaySlotName[] + //Sets the Work Area to the given Space + setBase( + x: number, + y: number, + z: number, + rx: number, + ry: number, + rz: number, + sx: number, + sy: number, + sz: number + ): void + } + + let display_slot: DisplaySlotName + + interface DisplaySlotOptions { + rotation?: ArrayVector3 + translation?: ArrayVector3 + scale?: ArrayVector3 + rotation_pivot?: ArrayVector3 + scale_pivot?: ArrayVector3 + mirror?: [boolean, boolean, boolean] + } -/** - * Display Slots hold the transform values for a specific item slot in the Minecraft Java Edition "Display Mode" feature - */ -declare class DisplaySlot { - constructor(id: string, data: DisplaySlotOptions) - rotation: ArrayVector3 - translation: ArrayVector3 - scale: ArrayVector3 - rotation_pivot: ArrayVector3 - scale_pivot: ArrayVector3 - mirror: [boolean, boolean, boolean] /** - * Reset slot to default values + * Display Slots hold the transform values for a specific item slot in the Minecraft Java Edition "Display Mode" feature */ - default(): this - extend(data: DisplaySlotOptions): this - copy(): { + class DisplaySlot { + constructor(id: DisplaySlotName, data: DisplaySlotOptions) rotation: ArrayVector3 translation: ArrayVector3 scale: ArrayVector3 rotation_pivot: ArrayVector3 scale_pivot: ArrayVector3 mirror: [boolean, boolean, boolean] + /** + * Reset slot to default values + */ + default(): this + extend(data: DisplaySlotOptions): this + copy(): { + rotation: ArrayVector3 + translation: ArrayVector3 + scale: ArrayVector3 + rotation_pivot: ArrayVector3 + scale_pivot: ArrayVector3 + mirror: [boolean, boolean, boolean] + } + /** + * Generate the values of the slot for export + */ + export(): + | { + rotation: ArrayVector3 + translation: ArrayVector3 + scale: ArrayVector3 + rotation_pivot?: ArrayVector3 + scale_pivot?: ArrayVector3 + } + | undefined + /** + * Visually update the UI with the data from this slot if selected + */ + update(): this } - /** - * Generate the values of the slot for export - */ - export(): - | { - rotation: ArrayVector3 - translation: ArrayVector3 - scale: ArrayVector3 - rotation_pivot?: ArrayVector3 - scale_pivot?: ArrayVector3 - } - | undefined - /** - * Visually update the UI with the data from this slot if selected - */ - update(): this } + +export {} diff --git a/types/custom/display_references.d.ts b/types/custom/display_references.d.ts new file mode 100644 index 000000000..a38ba2070 --- /dev/null +++ b/types/custom/display_references.d.ts @@ -0,0 +1,41 @@ +declare global { + namespace DisplayReferenceModel { + interface Element { + name: string + size: ArrayVector3 + pos: ArrayVector3 + origin: ArrayVector3 + rotation?: ArrayVector3 + north: { uv: ArrayVector4 } + east: { uv: ArrayVector4 } + south: { uv: ArrayVector4 } + west: { uv: ArrayVector4 } + up: { uv: ArrayVector4 } + down: { uv: ArrayVector4 } + model?: string + } + + interface Model { + texture: string + texture_size: [number, number] + elements: Array + } + } + + const DisplayReferences: { + display_player: DisplayReferenceModel.Model + armor_stand: DisplayReferenceModel.Model + armor_stand_small: DisplayReferenceModel.Model + fox: DisplayReferenceModel.Model + zombie: DisplayReferenceModel.Model + baby_zombie: DisplayReferenceModel.Model + monitor: DisplayReferenceModel.Model + block: DisplayReferenceModel.Model + frame_block: DisplayReferenceModel.Model + frame: DisplayReferenceModel.Model + frame_top_block: DisplayReferenceModel.Model + frame_top: DisplayReferenceModel.Model + } +} + +export {} diff --git a/types/custom/project.d.ts b/types/custom/project.d.ts index 43a8ced76..c6e349ff7 100644 --- a/types/custom/project.d.ts +++ b/types/custom/project.d.ts @@ -1,4 +1,11 @@ /// +interface DisplaySettings { + translation: ArrayVector3 + rotation: ArrayVector3 + scale: ArrayVector3 + mirror: [boolean, boolean, boolean] + export?(...args: any[]): any +} interface ModelProjectOptions { format: ModelFormat } @@ -65,15 +72,7 @@ declare class ModelProject { outliner: OutlinerNode[] animations: _Animation[] timeline_animators: [] - display_settings: { - [slot: string]: { - translation: [number, number, number] - rotation: [number, number, number] - scale: [number, number, number] - mirror: [boolean, boolean, boolean] - export?(...args: any[]): any - } - } + display_settings: Record overrides?: any exploded_view: boolean tool: string