diff --git a/devTools/types/FC/RA.d.ts b/devTools/types/FC/RA.d.ts index 1639e177dfdf3fa2c3265376e1929736966dd4cc..fd1d1916fde2448c3bf10c9770b9c506032889b4 100644 --- a/devTools/types/FC/RA.d.ts +++ b/devTools/types/FC/RA.d.ts @@ -250,6 +250,8 @@ declare namespace FC { positivePrompt: string; negativePrompt: string; overridePrompts: boolean; + openPoseType: "Library" | "JSON" | "PNG"; + openPoseName: string; } interface Rule { diff --git a/src/js/DefaultRules.js b/src/js/DefaultRules.js index ccf42efad3cf45b235ac38dda75f0fb9ef8ff077..2054bd069ecfde7caf8a9b20e8a39232ee5355c5 100644 --- a/src/js/DefaultRules.js +++ b/src/js/DefaultRules.js @@ -3256,7 +3256,7 @@ globalThis.DefaultRules = function(slave) { /** * @param {string} promptField "positive","negative" */ - function myFunc(promptField) { + function assignPrompts(promptField) { let newPrompts; let oldPrompts; let override; @@ -3313,12 +3313,60 @@ globalThis.DefaultRules = function(slave) { // custom positive prompts if (rule.positivePrompt != null && rule.positivePrompt !== '') { - myFunc("positive"); + assignPrompts("positive"); } // custom negative prompts if (rule.negativePrompt != null && rule.negativePrompt !== '') { - myFunc("negative"); + assignPrompts("negative"); + } + + // OpenPose set + if (rule.openPoseType != null) { + const c = slave.custom; + + if (rule.openPoseName == null) { + if (c.aiPose) { + message(`${slave.slaveName} is no longer being posed with OpenPose.`, sourceRecord.openPoseType); + } + c.aiPose = null; + return; + } + + if (!c.aiPose) { + c.aiPose = new App.Entity.SlaveCustomAIPose(); + c.aiPose.type = rule.openPoseType; + } else { + c.aiPose.type = rule.openPoseType; + } + + if (rule.openPoseType === 'Library') { + if (c.aiPose.name === rule.openPoseName) { return; } + message(`${slave.slaveName} has been posed with the OpenPose Library.`, sourceRecord.openPoseName); + c.aiPose.name = rule.openPoseName; + } else { + if (rule.openPoseName === '') { + message(`${slave.slaveName} has NOT been posed with OpenPose ("Resource is missing").`, sourceRecord.openPoseName); + c.aiPose = null; + } else { + let success; + if (c.aiPose.filename === rule.openPoseName) { return; } + // TODO: fetch cannot be used from a local file system to find files + fetch(`resources/poses/${rule.openPoseName}.${c.aiPose.type.toLowerCase()}`) + .then(r => { + success = true; + c.aiPose.filename = rule.openPoseName; + }).catch(r => { + success = false; + c.aiPose = null; + }); + if (!success) { + message(`${slave.slaveName} has NOT been posed with OpenPose ("Unable to fetch the requested resource").`); + } else { + message(`${slave.slaveName} has been posed with a custom OpenPose image.`, sourceRecord.openPoseName); + } + } + } } } diff --git a/src/js/rulesAssistant.js b/src/js/rulesAssistant.js index e3c17cb9c2d86e7861e3cc53973d53da17a660a9..38bcfe7b065cc8c8952d087c5b038898d704b23c 100644 --- a/src/js/rulesAssistant.js +++ b/src/js/rulesAssistant.js @@ -304,6 +304,8 @@ App.RA.newRule = function() { positivePrompt: null, negativePrompt: null, overridePrompts: null, + openPoseName: null, + openPoseType: null, }; } diff --git a/src/js/rulesAssistantOptions.js b/src/js/rulesAssistantOptions.js index 2e836f98bfdff7468e71bd345ce4bce0415dcfb4..154bd79bd68f3da52855f5a74445b2712e64fcbb 100644 --- a/src/js/rulesAssistantOptions.js +++ b/src/js/rulesAssistantOptions.js @@ -1557,6 +1557,9 @@ App.RA.options = (function() { this.appendChild(new OverridePromptSwitch()); this.appendChild(new AddCustomPosPrompt()); this.appendChild(new AddCustomNegPrompt()); + if (V.aiOpenPose) { + this.appendChild(new OpenPose()); + } } } } @@ -4194,6 +4197,278 @@ App.RA.options = (function() { } } + class ListSelectorLabeless extends Element { + constructor(data = [], allowNullValue = true) { + super(data, allowNullValue); + } + + render(data, allowNullValue) { + const elem = document.createElement("div"); + this.value = document.createElement("select"); + elem.appendChild(this.value); + elem.classList.add("rajs-list"); + this.values_ = new Map(); + // now add options + if (allowNullValue) { + const nullOpt = document.createElement("option"); + nullOpt.value = noDefaultSetting.value; + nullOpt.text = capFirstChar(noDefaultSetting.text); + this.value.appendChild(nullOpt); + this.values_.set(nullOpt.value, null); + } + for (const dr of data) { + const dv = Array.isArray(dr) ? (dr.length > 1 ? [dr[1], dr[0]] : [dr[0], dr[0]]) : [dr, dr]; + const opt = document.createElement("option"); + opt.value = dv[0]; + opt.text = capFirstChar(dv[1]); + this.value.appendChild(opt); + this.values_.set(opt.value, dv[0]); + } + this.value.onchange = () => { + this.inputEdited(); + }; + return elem; + } + + getData() { + return this.values_.get(this.value.value); + } + + setValue(what) { + this.value.value = what === null ? noDefaultSetting.value : what; + } + + inputEdited() { + this.propagateChange(); + } + + propagateChange() { + if (this.onchange instanceof Function) { + this.onchange(this.getData()); + } + } + } + + class StringEditorLabeless extends Element { + /** + * + * @param {Array} [data=[]] + * @param {boolean} [allowNullValue=true] + * @param {boolean} [editor=false] + * @param {boolean} [capitalizeShortcuts] + * @param {...any} args + */ + constructor(data = [], allowNullValue = true, editor = true, capitalizeShortcuts = false, ...args) { + super(editor, ...args); + this[_blockCallback] = false; + this.selectedItem = null; + /** @protected */ + this._allowNullValue = allowNullValue; + /** @private */ + this._capitalizeShortcuts = capitalizeShortcuts; + if (allowNullValue) { + this.appendChild(new ListItem(capFirstChar(noDefaultSetting.text), null)); + } + data.forEach(item => this.appendChild(this._createListItem(item))); + } + + createEditor() { + const res = document.createElement("input"); + res.setAttribute("type", "text"); + res.classList.add("rajs-value"); + // call the variable binding when the input field is no longer being edited, and when the enter key is pressed + res.onblur = () => { + this.inputEdited(); + }; + res.onkeypress = (e) => { + if (returnP(e)) { this.inputEdited(); } + }; + $(res).click(() => res.setAttribute("placeholder", "")); + return res; + } + + createValueElement() { return document.createElement("strong"); } + + render(editor, ...args) { + const elem = document.createElement("div"); + this.value = editor ? this.createEditor(...args) : this.createValueElement(); + if (this.value !== null) { + elem.appendChild(this.value); + } + elem.classList.add("rajs-list"); + return elem; + } + + inputEdited() { + if (this.selectedItem) { this.selectedItem.deselect(); } + this.setValue(this.getTextData()); + this.propagateChange(); + } + + selectItem(item) { + if (this.selectedItem) { this.selectedItem.deselect(); } + this.setValue(item.data); + this.propagateChange(); + } + + trySetValue(what) { + if (what == null && this._allowNullValue) { + this.setValue(what); + return; + } + const selected = this.children.filter(listItem => _.isEqual(listItem.data, what)); + if (selected != null && selected.length === 1) { + this.selectItem(selected[0]); + } else if (this._allowNullValue) { + this.setValue(null); + } + } + + setValue(what) { + if (what == null && !this._allowNullValue) { what = ""; } + this.realValue = what; + if (this[_blockCallback]) { return; } + try { + this[_blockCallback] = true; + this.setTextValue(what); + this.updateSelected(); + } finally { + this[_blockCallback] = false; + } + this.value.setAttribute("placeholder", what == null + ? `(${capFirstChar(noDefaultSetting.text)})` + : ''); + } + + setTextValue(what) { + this.value.value = what; + } + + getData() { + return this.realValue; + } + + getTextData() { + return this.value.value; + } + + // customizable input field parser / sanity checker + parse(what) { return what; } + + propagateChange() { + if (this.onchange instanceof Function) { + this.onchange(this.getData()); + } + } + + dataEqual(left, right) { + return _.isEqual(left, right); + } + + updateSelected() { + const dataValue = this.getData(); + let selected; + if (dataValue == null) { + selected = this.children.filter(listItem => listItem.data == null); + } else { + selected = this.children.filter(listItem => this.dataEqual(listItem.data, dataValue)); + } + if (selected.length > 1) { throw Error(`Multiple shortcuts matched ${JSON.stringify(dataValue)}`); } + if (selected.length === 1) { + const listItem = selected[0]; + listItem.select(false); + if ( + this.selectedItem != null && + !_.isEqual(this.selectedItem, listItem) + ) { + this.selectedItem.deselect(); + } + this.selectedItem = listItem; + } + } + + /** + * @private + * @param {string|string[]} item + * @returns {ListItem} + */ + _createListItem(item) { + let display = ''; + let data = null; + if (Array.isArray(item)) { + display = item[0]; + data = item.length > 1 ? item[1] : display; + } else { + display = item; + data = item; + } + if (this._capitalizeShortcuts) { + display = capFirstChar(display); + } + return new ListItem(display, data); + } + } + + class OpenPoseLibrary extends ListSelectorLabeless { + constructor() { + let items = []; + Object.keys(App.Data.Art.Poses).forEach((pose) => { + items.push(pose); + }); + super(items); + this.setValue(current_rule.set.openPoseName); + this.onchange = (value) => { + current_rule.set.openPoseName = value; + }; + } + } + + class OpenPoseFile extends StringEditorLabeless { + constructor() { + super(); + this.setValue(current_rule.set.openPoseName); + this.onchange = (value) => { + current_rule.set.openPoseName = value; + }; + } + } + + class OpenPose extends ListSelector { + constructor() { + let items = [ + "PNG", + "JSON", + "Library" + ]; + super("Assign a custom pose using OpenPose:", items); + this.setValue(current_rule.set.openPoseType); + + this.appendPoseType(); + } + + onchange() { + this.children.forEach((v, i) => { + if (this.children[i]) { + this.children[i].remove(); + } + }); + + current_rule.set.openPoseName = null; + + this.appendPoseType(); + // @ts-ignore + current_rule.set.openPoseType = this.getData(); + } + + appendPoseType() { + if (this.value.value === "Library") { + this.appendChild(new OpenPoseLibrary()); + } else if (this.value.value === "PNG" || this.value.value === "JSON") { + this.appendChild(new OpenPoseFile()); + } + } + } + class SkinColorList extends ListSelector { constructor() { const items = [