Add H3 solo actions and categories

This commit is contained in:
GenorTG
2026-11-10 10:57:10 +01:00
parent 339f1470db
commit 7262d3cb89
5 changed files with 48 additions and 2 deletions
+30
View File
@@ -83,7 +83,37 @@ ACTIONS = {
"Position - piledriver": ((("female", "the woman upside down on her shoulders"), ("male", "the man above")), "{A} lies on her back with the hips raised and the legs folded back over her body, shoulders and head on the bed; {B} stands or kneels above and thrusts down into {A}'s vagina, her ankles near his shoulders.", "9:16"),
"Position - butterfly": ((("female", "the woman on her back, hips raised"), ("male", "the man kneels, holds her hips")), "{A} lies on her back with a pillow under the hips, the legs bent and open; {B} kneels close, lifts her hips with both hands and thrusts into {A} at an upward angle, her feet off the bed.", "16:9"),
"Position - V": ((("female", "the woman on her back, legs wide"), ("male", "the man kneels in front")), "{A} lies on her back with both legs lifted and spread wide in a V; {B} kneels in front holding her ankles and thrusts into {A}, the camera facing the point where the bodies meet.", "16:9"),
"Solo - female, sitting on the floor": ((("female", "the woman"),), "{A} sits on the floor with her back against a sofa, fully naked, knees up and apart, one hand on a breast and the other between her legs, fingers working the clitoris, the head tipped back.", "9:16"),
"Solo - female, kneeling upright": ((("female", "the woman"),), "{A} kneels upright on the bed, fully naked, thighs apart, one hand sliding down her stomach into the pubic hair, the other cupping a breast, eyes on the camera.", "9:16"),
"Solo - female, on all fours": ((("female", "the woman"),), "{A} is on hands and knees, fully naked, the back arched and the rear toward the camera, one hand reaching back between the legs, fingers on the vulva, the head turned to the lens.", "16:9"),
"Solo - female, lying with a pillow": ((("female", "the woman"),), "{A} lies on her back with a pillow under the hips, fully naked, legs open, one hand spreading the labia and the other circling the clitoris, the face to the camera.", "16:9"),
"Solo - female, two fingers inside": ((("female", "the woman"),), "{A} lies on her back, fully naked, two fingers pushed into the vagina and curling, the thumb on the clitoris, hips lifting into the hand, mouth open.", "16:9"),
"Solo - female, toy on the clitoris": ((("female", "the woman"),), "{A} lies back with a vibrator pressed to the clitoris, fully naked, legs wide, the other hand gripping the bedding, the hips twitching with the vibration.", "16:9"),
"Solo - female, toy from behind": ((("female", "the woman"),), "{A} is on all fours with a toy in the vagina, reaching back to thrust it, fully naked, the rear high toward the camera, the face turned over one shoulder.", "16:9"),
"Solo - female, in the bath": ((("female", "the woman"),), "{A} lies back in a bath, water at her waist, breasts above the surface, fully naked, one hand between her legs under the water, the other on the bath edge, steam around her.", "16:9"),
"Solo - female, sitting on a washing machine": ((("female", "the woman"),), "{A} sits on the edge of a washing machine, fully naked, legs apart, one hand braced behind her and the other between her legs, the vibration of the machine in her body.", "9:16"),
"Solo - female, mirror, standing": ((("female", "the woman"),), "{A} stands facing a tall mirror, fully naked, one leg forward, watching her own reflection while one hand works between her legs and the other holds a breast.", "9:16"),
"Solo - male, sitting, jerking off": ((("male", "the man"),), "{A} sits back on a sofa with the legs apart, fully naked, one hand stroking the erect penis base to glans, the other on his thigh, the head tipped back.", "16:9"),
"Solo - male, lying, jerking off": ((("male", "the man"),), "{A} lies on his back on the bed, fully naked, one hand stroking the erect penis in long strokes, the other behind his head, the hips pushing up into the fist.", "16:9"),
"Solo - male, standing in the shower": ((("male", "the man"),), "{A} stands under running water, one hand on the tiles, the other stroking the erect penis, water running over his chest and stomach.", "9:16"),
"Solo - male, toy, on all fours": ((("male", "the man"),), "{A} is on all fours, fully naked, reaching back to push a toy into himself, the erect penis hanging, the head turned to the camera.", "16:9"),
}
ACTION_CATEGORIES = ("Solo female", "Solo male", "Oral", "Hands", "Positions", "Anal", "Other")
def action_category(label):
gender = ACTIONS[label][0][0][0]
if label.startswith("Solo") and len(ACTIONS[label][0]) == 1:
return "Solo male" if gender == "male" else "Solo female"
if label.startswith("Oral"):
return "Oral"
if label.startswith("Hands"):
return "Hands"
if label.startswith("Position - anal"):
return "Anal"
if label.startswith("Position"):
return "Positions"
return "Other"
CAMERAS = {
+2 -1
View File
@@ -57,5 +57,6 @@ async def _delete(request):
@PromptServer.instance.routes.get("/promptforge/actions")
async def _actions(request):
return web.json_response({label: {"roles": h3_actions.role_info(label), "aspect": h3_actions.aspect(label)}
return web.json_response({label: {"roles": h3_actions.role_info(label), "aspect": h3_actions.aspect(label),
"category": h3_actions.action_category(label)}
for label in h3_actions.ACTIONS})
+1
View File
@@ -126,6 +126,7 @@ class PROMPTFORGE_H3Action(_Settings):
subjects[f"subject_{number}_look"] = ("STRING", {"default": "", "multiline": True, "tooltip": "The person in words: hair, body, face, age. Used when no picture is chosen for this role, and added to the picture when one is."})
return {
"required": {
"category": (list(h3_actions.ACTION_CATEGORIES), {"default": h3_actions.ACTION_CATEGORIES[0], "tooltip": "Filters the action list; the act itself is chosen below."}),
"action": (list(h3_actions.ACTIONS), {"default": next(iter(h3_actions.ACTIONS)), "tooltip": "The act or position. The roles of the subjects are shown below it."}),
"camera_angle": ([h3_actions.ANY_CAMERA] + list(h3_actions.CAMERAS), {"default": h3_actions.ANY_CAMERA, "tooltip": "How it is filmed. POV (the viewer is B) is what subject 2 sees."}),
},
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "ComfyUI-PromptForge"
description = "ComfyUI prompt enhancement suite: model/config node with GPU-aware auto settings, prompt enhancer with a preset catalog, and a standalone config finder."
version = "1.28.17"
version = "1.28.18"
license = { file = "LICENSE" }
dependencies = [
"transformers",
+14
View File
@@ -321,6 +321,8 @@ app.registerExtension({
const roles = node.addDOMWidget("subject_roles", "promptforge_subject_roles", label, { serialize: false, getMinHeight: () => 72 });
Object.defineProperty(roles, "width", { get: () => node.size[0], set: () => {}, configurable: true });
const action = find("action");
const category = find("category");
const allActions = [...(action.options?.values || [])];
node.widgets.splice(node.widgets.indexOf(roles), 1);
node.widgets.splice(node.widgets.indexOf(action) + 1, 0, roles);
const nameFor = (gender, index, list = []) => {
@@ -333,6 +335,9 @@ app.registerExtension({
let known = null;
const refresh = async () => {
known = known || await (await api.fetchApi("/promptforge/actions")).json();
const values = allActions.filter((value) => known[value]?.category === category.value);
action.options = { ...(action.options || {}), values };
if (!values.includes(action.value) && values.length) action.value = values[0];
const entry = known[String(action.value)] || { roles: [] };
const list = entry.roles || [];
const shape = entry.aspect === "9:16" ? "portrait" : "landscape";
@@ -372,6 +377,8 @@ app.registerExtension({
fromCallback[number] = from.callback;
from.callback = (...args) => { fromCallback[number]?.apply(from, args); refresh(); };
}
const categoryCallback = category.callback;
category.callback = (...args) => { categoryCallback?.apply(category, args); refresh(); };
const callback = action.callback;
action.callback = (...args) => { callback?.apply(action, args); refresh(); };
node.promptforgeRefreshAction = refresh;
@@ -387,6 +394,13 @@ app.registerExtension({
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const result = onConfigure?.apply(this, arguments);
const category = this.widgets.find((widget) => widget.name === "category");
const action = this.widgets.find((widget) => widget.name === "action");
if (category && action) api.fetchApi("/promptforge/actions").then((response) => response.json()).then((known) => {
const found = known[String(action.value)]?.category;
if (found) category.value = found;
this.promptforgeRefreshAction?.();
});
this.promptforgeRefreshAction?.();
setTimeout(() => this.promptforgeRestoreActionSize?.(), 0);
return result;