Files
HWDigi-Factory-Prompts_comfyui/Factory_negative_generator.py

614 lines
27 KiB
Python

import random
from typing import List, Dict, Any
class FactoryPromptsNegativeGenerator:
"""
Comprehensive negative prompt generator for all checkpoint types
Provides categorized negative prompts for quality control and style management
"""
def __init__(self):
# Base quality negatives - always included
self.base_quality = [
"lowres", "bad_anatomy", "bad_hands", "text", "error",
"missing_fingers", "extra_digit", "fewer_digits", "cropped",
"worst_quality", "low_quality", "normal_quality",
"jpeg_artifacts", "signature", "watermark", "username", "blurry"
]
# Enhanced quality negatives for higher standards
self.enhanced_quality = [
"bad_proportions", "bad_perspective", "bad_shading",
"bad_composition", "bad_lighting", "overexposed", "underexposed",
"noise", "grainy", "pixelated", "compression_artifacts",
"aliasing", "distorted", "deformed", "malformed"
]
# Anatomy and body negatives
self.anatomy_negatives = [
"bad_anatomy", "bad_hands", "bad_feet", "bad_face",
"missing_fingers", "extra_digit", "fewer_digits", "extra_fingers",
"mutated_hands", "poorly_drawn_hands", "poorly_drawn_face",
"mutation", "deformed", "ugly", "blurry_face", "disfigured",
"extra_limbs", "missing_limbs", "extra_arms", "missing_arms",
"extra_legs", "missing_legs", "fused_fingers", "too_many_fingers",
"long_neck", "duplicate", "morbid", "mutilated", "extra_head",
"poorly_drawn_eyes", "bad_eyes", "crossed_eyes", "extra_tongue",
"multiple_tongues", "deformed_tongue", "elongated_tongue"
]
# Non-realistic style control negatives
self.non_realistic_style_control = [
"source_cartoon", "source_furry", "source_pony", "western_comic",
"cartoon", "disney", "pixar", "anime_style", "manga_style",
"chibi", "cel_shading", "flat_colors", "simplified_art"
]
# Realistic style control negatives
self.realistic_style_control = [
"3d", "realistic", "photorealistic", "photo", "photography",
"real_person", "live_action", "cgi", "render", "blender",
"unreal_engine", "ray_tracing", "hyperrealistic", "lifelike"
]
# Art medium negatives
self.unwanted_mediums = [
"sketch", "pencil_sketch", "rough_sketch", "draft",
"unfinished", "incomplete", "work_in_progress", "wip",
"traditional_media", "pencil_drawing", "charcoal",
"crayon", "chalk", "pastel", "watercolor_paper",
"canvas_texture", "paper_texture", "scan_artifacts"
]
# Color and saturation control
self.color_negatives = [
"monochrome", "grayscale", "greyscale", "black_and_white",
"sepia", "desaturated", "oversaturated", "color_bleeding",
"bad_colors", "ugly_colors", "neon_colors", "garish",
"overexposed_colors", "washed_out", "faded_colors"
]
# Composition and framing negatives
self.composition_negatives = [
"bad_composition", "bad_framing", "cut_off", "cropped_head",
"cropped_body", "out_of_frame", "off_center", "unbalanced",
"awkward_pose", "stiff_pose", "unnatural_pose", "forced_smile",
"bad_perspective", "wrong_perspective", "floating"
]
# Background and environment negatives
self.background_negatives = [
"cluttered_background", "busy_background", "distracting_background",
"bad_background", "messy_background", "noisy_background",
"artifacts_in_background", "floating_objects", "inconsistent_lighting",
"multiple_light_sources", "confusing_background"
]
# Text and UI element negatives
self.text_negatives = [
"text", "words", "letters", "numbers", "symbols",
"watermark", "signature", "artist_name", "copyright",
"logo", "brand", "website", "url", "username",
"speech_bubble", "thought_bubble", "text_bubble",
"caption", "subtitle", "overlay_text", "ui_elements"
]
# Clothing and fashion negatives
self.clothing_negatives = [
"bad_clothing", "poorly_drawn_clothes", "floating_clothes",
"clipping_clothes", "transparent_clothes", "see_through_clothes",
"torn_clothes", "damaged_clothes", "wrinkled_clothes",
"ill_fitting_clothes", "oversized_clothes", "undersized_clothes"
]
# Expression and emotion negatives
self.expression_negatives = [
"dead_eyes", "empty_eyes", "soulless_eyes", "scary_eyes",
"angry_expression", "sad_expression", "depressed_expression",
"forced_expression", "unnatural_expression", "blank_expression",
"emotionless", "creepy_smile", "evil_smile"
]
# Hair negatives
self.hair_negatives = [
"bad_hair", "poorly_drawn_hair", "floating_hair", "messy_hair",
"tangled_hair", "unrealistic_hair", "bad_hair_physics",
"clipping_hair", "hair_artifacts", "inconsistent_hair_color"
]
# NSFW content filters (for SFW generations)
self.nsfw_filters = [
"nsfw", "explicit", "nude", "naked", "topless", "bottomless",
"underwear", "lingerie", "bikini", "swimsuit", "revealing_clothes",
"cleavage", "exposed_skin", "suggestive", "provocative",
"sexual", "erotic", "adult_content", "mature_content"
]
# Violence and disturbing content filters
self.violence_filters = [
"violence", "blood", "gore", "death", "killing", "murder",
"torture", "pain", "suffering", "injury", "wound", "scar",
"bruise", "cut", "bleeding", "dark_themes", "disturbing",
"nightmare", "horror", "scary", "frightening"
]
# Technical artifact negatives
self.technical_negatives = [
"compression_artifacts", "jpeg_artifacts", "aliasing", "moire",
"banding", "posterization", "color_banding", "pixelation",
"blur_artifacts", "sharpening_artifacts", "noise_artifacts",
"upscaling_artifacts", "interpolation_artifacts"
]
def get_base_negatives(self) -> List[str]:
"""Always included base negative prompts"""
return self.base_quality.copy()
def get_category_negatives(self, categories: List[str]) -> List[str]:
"""Get negatives from specific categories"""
negatives = []
category_map = {
"enhanced_quality": self.enhanced_quality,
"anatomy": self.anatomy_negatives,
"non_realistic_style_control": self.non_realistic_style_control,
"realistic_style_control": self.realistic_style_control,
"unwanted_mediums": self.unwanted_mediums,
"color_control": self.color_negatives,
"composition": self.composition_negatives,
"background": self.background_negatives,
"text_elements": self.text_negatives,
"clothing": self.clothing_negatives,
"expressions": self.expression_negatives,
"hair": self.hair_negatives,
"nsfw_filters": self.nsfw_filters,
"violence_filters": self.violence_filters,
"technical": self.technical_negatives
}
for category in categories:
if category in category_map:
negatives.extend(category_map[category])
return negatives
def build_negative_prompt(self,
preset: str = "standard",
custom_categories: List[str] = None,
include_style_control: bool = True,
include_nsfw_filters: bool = False,
include_violence_filters: bool = False,
custom_negatives: str = "",
strength_boost: bool = False) -> str:
"""
Build comprehensive negative prompt based on settings
Args:
preset: Predefined negative prompt preset
custom_categories: List of specific categories to include
include_style_control: Add style control negatives
include_nsfw_filters: Add NSFW content filters
include_violence_filters: Add violence/disturbing content filters
custom_negatives: Additional custom negative terms
strength_boost: Add emphasis to critical negatives
"""
negatives = self.get_base_negatives()
# Apply presets
if preset == "minimal":
# Just base quality negatives
pass
elif preset == "standard":
negatives.extend(self.get_category_negatives([
"enhanced_quality", "anatomy", "composition", "text_elements"
]))
elif preset == "comprehensive":
negatives.extend(self.get_category_negatives([
"enhanced_quality", "anatomy", "composition", "background",
"text_elements", "clothing", "hair", "technical"
]))
elif preset == "professional":
negatives.extend(self.get_category_negatives([
"enhanced_quality", "anatomy", "composition", "background",
"text_elements", "clothing", "expressions", "hair",
"unwanted_mediums", "technical"
]))
elif preset == "anime_focused":
negatives.extend(self.get_category_negatives([
"enhanced_quality", "anatomy", "non_realistic_style_control", "composition",
"text_elements", "unwanted_mediums"
]))
# Add custom categories
if custom_categories:
negatives.extend(self.get_category_negatives(custom_categories))
# Add optional filters
if include_style_control:
negatives.extend(self.non_realistic_style_control)
negatives.extend(self.realistic_style_control)
if include_nsfw_filters:
negatives.extend(self.nsfw_filters)
if include_violence_filters:
negatives.extend(self.violence_filters)
# Add custom negatives
if custom_negatives.strip():
custom_terms = [term.strip() for term in custom_negatives.split(",") if term.strip()]
negatives.extend(custom_terms)
# Remove duplicates while preserving order
seen = set()
unique_negatives = []
for negative in negatives:
if negative not in seen:
unique_negatives.append(negative)
seen.add(negative)
# Apply strength boost to critical terms
if strength_boost:
critical_terms = ["bad_anatomy", "bad_hands", "bad_face", "worst_quality", "low_quality"]
boosted_negatives = []
for negative in unique_negatives:
if negative in critical_terms:
boosted_negatives.append(f"({negative}:1.2)")
else:
boosted_negatives.append(negative)
unique_negatives = boosted_negatives
return ", ".join(unique_negatives)
def get_preset_description(self, preset: str) -> str:
"""Get description of what each preset includes"""
descriptions = {
"minimal": "Basic quality control only - fastest generation",
"standard": "Quality + anatomy + composition + text removal",
"comprehensive": "Standard + background + clothing + hair + technical",
"professional": "Comprehensive + expressions + unwanted mediums",
"anime_focused": "Optimized for anime style with strong style control"
}
return descriptions.get(preset, "Unknown preset")
class PonyNegativeNode:
"""ComfyUI Node for Pony Negative Prompt Generation"""
CATEGORY = "Prompt Factory"
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("negative_prompt",)
FUNCTION = "generate_negative"
def __init__(self):
self.generator = FactoryPromptsNegativeGenerator()
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"preset": (["minimal", "standard", "comprehensive", "professional", "anime_focused"], {"default": "standard"}),
"include_style_control": ("BOOLEAN", {"default": True}),
"include_nsfw_filters": ("BOOLEAN", {"default": False}),
"include_violence_filters": ("BOOLEAN", {"default": False}),
"strength_boost": ("BOOLEAN", {"default": False}),
},
"optional": {
"custom_negatives": ("STRING", {"multiline": True, "default": ""}),
"extra_categories": (["none", "enhanced_quality", "anatomy", "unwanted_mediums",
"color_control", "composition", "background", "text_elements",
"clothing", "expressions", "hair", "technical"], {"default": "none"}),
}
}
def generate_negative(self, preset, include_style_control, include_nsfw_filters,
include_violence_filters, strength_boost, custom_negatives="",
extra_categories="none"):
# Convert extra_categories to list
extra_cats = [extra_categories] if extra_categories != "none" else []
# Generate negative prompt
negative = self.generator.build_negative_prompt(
preset=preset,
custom_categories=extra_cats,
include_style_control=include_style_control,
include_nsfw_filters=include_nsfw_filters,
include_violence_filters=include_violence_filters,
custom_negatives=custom_negatives,
strength_boost=strength_boost
)
return (negative,)
class PonyNegativeCategorizedNode:
"""Advanced ComfyUI Node for Categorized Negative Prompt Control"""
CATEGORY = "Prompt Factory"
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("negative_prompt",)
FUNCTION = "generate_categorized_negative"
def __init__(self):
self.generator = FactoryPromptsNegativeGenerator()
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"base_quality": ("BOOLEAN", {"default": True}),
"enhanced_quality": ("BOOLEAN", {"default": True}),
"anatomy_control": ("BOOLEAN", {"default": True}),
"non_realistic_style_control": ("BOOLEAN", {"default": True}),
"realistic_style_control": ("BOOLEAN", {"default": False}),
"composition": ("BOOLEAN", {"default": True}),
"text_removal": ("BOOLEAN", {"default": True}),
},
"optional": {
"unwanted_mediums": ("BOOLEAN", {"default": False}),
"color_control": ("BOOLEAN", {"default": False}),
"background_control": ("BOOLEAN", {"default": False}),
"clothing_control": ("BOOLEAN", {"default": False}),
"expression_control": ("BOOLEAN", {"default": False}),
"hair_control": ("BOOLEAN", {"default": False}),
"nsfw_filters": ("BOOLEAN", {"default": False}),
"violence_filters": ("BOOLEAN", {"default": False}),
"technical_artifacts": ("BOOLEAN", {"default": False}),
"strength_boost": ("BOOLEAN", {"default": False}),
"custom_negatives": ("STRING", {"multiline": True, "default": ""}),
}
}
def generate_categorized_negative(self, base_quality, enhanced_quality, anatomy_control,
non_realistic_style_control, realistic_style_control, composition, text_removal, unwanted_mediums=False,
color_control=False, background_control=False, clothing_control=False,
expression_control=False, hair_control=False, nsfw_filters=False,
violence_filters=False, technical_artifacts=False, strength_boost=False,
custom_negatives=""):
negatives = []
# Add base categories based on selections
if base_quality:
negatives.extend(self.generator.get_base_negatives())
# Build category list
categories = []
if enhanced_quality:
categories.append("enhanced_quality")
if anatomy_control:
categories.append("anatomy")
if unwanted_mediums:
categories.append("unwanted_mediums")
if color_control:
categories.append("color_control")
if composition:
categories.append("composition")
if background_control:
categories.append("background")
if text_removal:
categories.append("text_elements")
if clothing_control:
categories.append("clothing")
if expression_control:
categories.append("expressions")
if hair_control:
categories.append("hair")
if technical_artifacts:
categories.append("technical")
# Add category negatives
negatives.extend(self.generator.get_category_negatives(categories))
# Add optional style control filters
if non_realistic_style_control:
negatives.extend(self.generator.non_realistic_style_control)
if realistic_style_control:
negatives.extend(self.generator.realistic_style_control)
if nsfw_filters:
negatives.extend(self.generator.nsfw_filters)
if violence_filters:
negatives.extend(self.generator.violence_filters)
# Add custom negatives
if custom_negatives.strip():
custom_terms = [term.strip() for term in custom_negatives.split(",") if term.strip()]
negatives.extend(custom_terms)
# Remove duplicates
seen = set()
unique_negatives = []
for negative in negatives:
if negative not in seen:
unique_negatives.append(negative)
seen.add(negative)
# Apply strength boost
if strength_boost:
critical_terms = ["bad_anatomy", "bad_hands", "bad_face", "worst_quality", "low_quality"]
boosted_negatives = []
for negative in unique_negatives:
if negative in critical_terms:
boosted_negatives.append(f"({negative}:1.2)")
else:
boosted_negatives.append(negative)
unique_negatives = boosted_negatives
return (", ".join(unique_negatives),)
class PonyNegativeToggleNode:
"""Comprehensive ComfyUI Node for Negative Prompt Control with Individual Toggles"""
CATEGORY = "Prompt Factory"
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("negative_prompt", "selected_categories")
FUNCTION = "generate_negative_toggles"
def __init__(self):
self.generator = FactoryPromptsNegativeGenerator()
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
},
"optional": {
# Quality Control Toggles
"base_quality": ("BOOLEAN", {"default": True}),
"enhanced_quality": ("BOOLEAN", {"default": False}),
"technical_artifacts": ("BOOLEAN", {"default": False}),
# Anatomy Control Toggles
"anatomy_control": ("BOOLEAN", {"default": True}),
"expression_control": ("BOOLEAN", {"default": False}),
"hair_control": ("BOOLEAN", {"default": False}),
# Style Control Toggles
"non_realistic_style_control": ("BOOLEAN", {"default": True}),
"realistic_style_control": ("BOOLEAN", {"default": False}),
"unwanted_mediums": ("BOOLEAN", {"default": False}),
"color_control": ("BOOLEAN", {"default": False}),
# Composition Toggles
"composition": ("BOOLEAN", {"default": True}),
"background_control": ("BOOLEAN", {"default": False}),
"text_removal": ("BOOLEAN", {"default": True}),
# Content Toggles
"clothing_control": ("BOOLEAN", {"default": False}),
"nsfw_filters": ("BOOLEAN", {"default": False}),
"violence_filters": ("BOOLEAN", {"default": False}),
# Enhancement Options
"strength_boost": ("BOOLEAN", {"default": False}),
"custom_negatives": ("STRING", {"multiline": True, "default": ""}),
}
}
def generate_negative_toggles(self, seed, base_quality=True, enhanced_quality=False,
technical_artifacts=False, anatomy_control=True,
expression_control=False, hair_control=False,
non_realistic_style_control=True, realistic_style_control=False, unwanted_mediums=False,
color_control=False, composition=True,
background_control=False, text_removal=True,
clothing_control=False, nsfw_filters=False,
violence_filters=False, strength_boost=False,
custom_negatives=""):
# Set random seed for reproducible results
if seed != 0:
random.seed(seed)
negatives = []
enabled_categories = []
# Add base categories based on toggles
if base_quality:
negatives.extend(self.generator.get_base_negatives())
enabled_categories.append("Base Quality")
# Build category list based on toggles
categories = []
if enhanced_quality:
categories.append("enhanced_quality")
enabled_categories.append("Enhanced Quality")
if technical_artifacts:
categories.append("technical")
enabled_categories.append("Technical Artifacts")
if anatomy_control:
categories.append("anatomy")
enabled_categories.append("Anatomy Control")
if expression_control:
categories.append("expressions")
enabled_categories.append("Expression Control")
if hair_control:
categories.append("hair")
enabled_categories.append("Hair Control")
if non_realistic_style_control:
categories.append("non_realistic_style_control")
enabled_categories.append("Non-Realistic Style Control")
if realistic_style_control:
categories.append("realistic_style_control")
enabled_categories.append("Realistic Style Control")
if unwanted_mediums:
categories.append("unwanted_mediums")
enabled_categories.append("Unwanted Mediums")
if color_control:
categories.append("color_control")
enabled_categories.append("Color Control")
if composition:
categories.append("composition")
enabled_categories.append("Composition")
if background_control:
categories.append("background")
enabled_categories.append("Background Control")
if text_removal:
categories.append("text_elements")
enabled_categories.append("Text Removal")
if clothing_control:
categories.append("clothing")
enabled_categories.append("Clothing Control")
# Add category negatives
if categories:
negatives.extend(self.generator.get_category_negatives(categories))
# Add optional filters
if nsfw_filters:
negatives.extend(self.generator.get_category_negatives(["nsfw_filters"]))
enabled_categories.append("NSFW Filters")
if violence_filters:
negatives.extend(self.generator.get_category_negatives(["violence_filters"]))
enabled_categories.append("Violence Filters")
# Add custom negatives
if custom_negatives.strip():
custom_terms = [term.strip() for term in custom_negatives.split(",") if term.strip()]
negatives.extend(custom_terms)
enabled_categories.append(f"Custom ({len(custom_terms)} terms)")
# Remove duplicates while preserving order
seen = set()
unique_negatives = []
for negative in negatives:
if negative not in seen:
unique_negatives.append(negative)
seen.add(negative)
# Apply strength boost to critical terms
if strength_boost:
critical_terms = ["bad_anatomy", "bad_hands", "bad_face", "worst_quality", "low_quality"]
boosted_negatives = []
for negative in unique_negatives:
if negative in critical_terms:
boosted_negatives.append(f"({negative}:1.2)")
else:
boosted_negatives.append(negative)
unique_negatives = boosted_negatives
enabled_categories.append("Strength Boost")
# Create category summary
category_summary = ", ".join(enabled_categories) if enabled_categories else "No categories enabled"
return (", ".join(unique_negatives), category_summary)
# Node registration
NODE_CLASS_MAPPINGS = {
"FactoryPromptsNegative": PonyNegativeNode,
"FactoryPromptsNegativeCategorized": PonyNegativeCategorizedNode,
"FactoryPromptsNegativeToggle": PonyNegativeToggleNode
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FactoryPromptsNegative": "Factory Prompts Negative Generator",
"FactoryPromptsNegativeCategorized": "Factory Prompts Negative (Categorized)",
"FactoryPromptsNegativeToggle": "Factory Prompts Negative Generator (Toggles)"
}