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 = [