mirror of
https://github.com/lllyasviel/stable-diffusion-webui-forge.git
synced 2026-04-26 17:29:09 +00:00
upload a cn
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
import gradio as gr
|
||||
from typing import List
|
||||
|
||||
|
||||
class ModalInterface(gr.Interface):
|
||||
modal_id_counter = 0
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
html_content: str,
|
||||
open_button_text: str,
|
||||
open_button_classes: List[str] = [],
|
||||
open_button_extra_attrs: str = ''
|
||||
):
|
||||
self.html_content = html_content
|
||||
self.open_button_text = open_button_text
|
||||
self.open_button_classes = open_button_classes
|
||||
self.open_button_extra_attrs = open_button_extra_attrs
|
||||
self.modal_id = ModalInterface.modal_id_counter
|
||||
ModalInterface.modal_id_counter += 1
|
||||
|
||||
def __call__(self):
|
||||
return self.create_modal()
|
||||
|
||||
def create_modal(self, visible=True):
|
||||
html_code = f"""
|
||||
<div id="cnet-modal-{self.modal_id}" class="cnet-modal">
|
||||
<span class="cnet-modal-close">×</span>
|
||||
<div class="cnet-modal-content">
|
||||
{self.html_content}
|
||||
</div>
|
||||
</div>
|
||||
<div id="cnet-modal-open-{self.modal_id}"
|
||||
class="cnet-modal-open {' '.join(self.open_button_classes)}"
|
||||
{self.open_button_extra_attrs}
|
||||
>{self.open_button_text}</div>
|
||||
"""
|
||||
return gr.HTML(value=html_code, visible=visible)
|
||||
@@ -0,0 +1,154 @@
|
||||
import base64
|
||||
import gradio as gr
|
||||
import json
|
||||
from typing import List, Dict, Any, Tuple
|
||||
|
||||
from annotator.openpose import decode_json_as_poses, draw_poses
|
||||
from annotator.openpose.animalpose import draw_animalposes
|
||||
from scripts.controlnet_ui.modal import ModalInterface
|
||||
from modules import shared
|
||||
from scripts.logging import logger
|
||||
|
||||
|
||||
def parse_data_url(data_url: str):
|
||||
# Split the URL at the comma
|
||||
media_type, data = data_url.split(",", 1)
|
||||
|
||||
# Check if the data is base64-encoded
|
||||
assert ";base64" in media_type
|
||||
|
||||
# Decode the base64 data
|
||||
return base64.b64decode(data)
|
||||
|
||||
|
||||
def encode_data_url(json_string: str) -> str:
|
||||
base64_encoded_json = base64.b64encode(json_string.encode("utf-8")).decode("utf-8")
|
||||
return f"data:application/json;base64,{base64_encoded_json}"
|
||||
|
||||
|
||||
class OpenposeEditor(object):
|
||||
# Filename used when user click the download link.
|
||||
download_file = "pose.json"
|
||||
# URL the openpose editor is mounted on.
|
||||
editor_url = "/openpose_editor_index"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.render_button = None
|
||||
self.pose_input = None
|
||||
self.download_link = None
|
||||
self.upload_link = None
|
||||
self.modal = None
|
||||
|
||||
def render_edit(self):
|
||||
"""Renders the buttons in preview image control button group."""
|
||||
# The hidden button to trigger a re-render of generated image.
|
||||
self.render_button = gr.Button(visible=False, elem_classes=["cnet-render-pose"])
|
||||
# The hidden element that stores the pose json for backend retrieval.
|
||||
# The front-end javascript will write the edited JSON data to the element.
|
||||
self.pose_input = gr.Textbox(visible=False, elem_classes=["cnet-pose-json"])
|
||||
|
||||
self.modal = ModalInterface(
|
||||
# Use about:blank here as placeholder so that the iframe does not
|
||||
# immediately navigate. Most of controlnet units do not need
|
||||
# openpose editor active. Only navigate when the user first click
|
||||
# 'Edit'. The navigation logic is in `openpose_editor.js`.
|
||||
f'<iframe src="about:blank"></iframe>',
|
||||
open_button_text="Edit",
|
||||
open_button_classes=["cnet-edit-pose"],
|
||||
open_button_extra_attrs=f'title="Send pose to {OpenposeEditor.editor_url} for edit."',
|
||||
).create_modal(visible=False)
|
||||
self.download_link = gr.HTML(
|
||||
value=f"""<a href='' download='{OpenposeEditor.download_file}'>JSON</a>""",
|
||||
visible=False,
|
||||
elem_classes=["cnet-download-pose"],
|
||||
)
|
||||
|
||||
def render_upload(self):
|
||||
"""Renders the button in input image control button group."""
|
||||
self.upload_link = gr.HTML(
|
||||
value="""
|
||||
<label>Upload JSON</label>
|
||||
<input type="file" accept=".json"/>
|
||||
""",
|
||||
visible=False,
|
||||
elem_classes=["cnet-upload-pose"],
|
||||
)
|
||||
|
||||
def register_callbacks(
|
||||
self,
|
||||
generated_image: gr.Image,
|
||||
use_preview_as_input: gr.Checkbox,
|
||||
model: gr.Dropdown,
|
||||
):
|
||||
def render_pose(pose_url: str) -> Tuple[Dict, Dict]:
|
||||
json_string = parse_data_url(pose_url).decode("utf-8")
|
||||
poses, animals, height, width = decode_json_as_poses(
|
||||
json.loads(json_string)
|
||||
)
|
||||
logger.info("Preview as input is enabled.")
|
||||
return (
|
||||
# Generated image.
|
||||
gr.update(
|
||||
value=(
|
||||
draw_poses(
|
||||
poses,
|
||||
height,
|
||||
width,
|
||||
draw_body=True,
|
||||
draw_hand=True,
|
||||
draw_face=True,
|
||||
)
|
||||
if poses
|
||||
else draw_animalposes(animals, height, width)
|
||||
),
|
||||
visible=True,
|
||||
),
|
||||
# Use preview as input.
|
||||
gr.update(value=True),
|
||||
# Self content.
|
||||
*self.update(json_string),
|
||||
)
|
||||
|
||||
self.render_button.click(
|
||||
fn=render_pose,
|
||||
inputs=[self.pose_input],
|
||||
outputs=[generated_image, use_preview_as_input, *self.outputs()],
|
||||
)
|
||||
|
||||
def update_upload_link(model: str) -> Dict:
|
||||
return gr.update(visible="openpose" in model.lower())
|
||||
|
||||
model.change(fn=update_upload_link, inputs=[model], outputs=[self.upload_link])
|
||||
|
||||
def outputs(self) -> List[Any]:
|
||||
return [
|
||||
self.download_link,
|
||||
self.modal,
|
||||
]
|
||||
|
||||
def update(self, json_string: str) -> List[Dict]:
|
||||
"""
|
||||
Called when there is a new JSON pose value generated by running
|
||||
preprocessor.
|
||||
|
||||
Args:
|
||||
json_string: The new JSON string generated by preprocessor.
|
||||
|
||||
Returns:
|
||||
An gr.update event.
|
||||
"""
|
||||
hint = "Download the pose as .json file"
|
||||
html = f"""<a href='{encode_data_url(json_string)}'
|
||||
download='{OpenposeEditor.download_file}' title="{hint}">
|
||||
JSON</a>"""
|
||||
|
||||
visible = json_string != ""
|
||||
return [
|
||||
# Download link update.
|
||||
gr.update(value=html, visible=visible),
|
||||
# Modal update.
|
||||
gr.update(
|
||||
visible=visible
|
||||
and not shared.opts.data.get("controlnet_disable_openpose_edit", False)
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,182 @@
|
||||
import gradio as gr
|
||||
|
||||
from scripts.controlnet_ui.modal import ModalInterface
|
||||
|
||||
PHOTOPEA_LOGO = """
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100%" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve"
|
||||
style="width: 0.75rem; height 0.75rem; margin-left: 2px;"
|
||||
>
|
||||
<path fill="#18A497" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M1.000000,228.000000
|
||||
C1.000000,162.312439 1.000000,96.624878 1.331771,30.719650
|
||||
C2.026278,30.171114 2.594676,29.904894 2.721949,29.500008
|
||||
C6.913495,16.165672 15.629609,7.322631 28.880219,2.875538
|
||||
C29.404272,2.699659 29.633436,1.645129 30.000000,1.000000
|
||||
C95.687561,1.000000 161.375122,1.000000 227.258057,1.317018
|
||||
C227.660217,1.893988 227.815079,2.296565 228.081207,2.393433
|
||||
C241.304657,7.206383 250.980164,15.550970 255.215851,29.410040
|
||||
C255.321625,29.756128 256.383850,29.809898 257.000000,30.000000
|
||||
C257.000000,95.687561 257.000000,161.375122 256.682983,227.257858
|
||||
C256.106049,227.659790 255.699371,227.815521 255.607178,228.080658
|
||||
C250.953033,241.462830 242.292618,250.822968 228.591782,255.214935
|
||||
C228.239929,255.327698 228.190491,256.383820 228.000000,257.000000
|
||||
C175.312439,257.000000 122.624878,257.000000 69.468582,256.531342
|
||||
C68.672188,244.948196 68.218323,233.835587 68.052299,222.718674
|
||||
C67.885620,211.557587 67.886772,200.390717 68.027298,189.229050
|
||||
C68.255180,171.129044 68.084618,152.997421 69.151917,134.942368
|
||||
C70.148468,118.083969 77.974228,103.689308 89.758743,91.961365
|
||||
C104.435837,77.354736 122.313736,69.841736 143.417328,69.901505
|
||||
C168.662338,69.972984 186.981964,90.486633 187.961487,114.156334
|
||||
C189.042435,140.277435 166.783981,163.607941 140.303482,160.823074
|
||||
C137.092346,160.485382 133.490692,158.365784 131.192612,155.987366
|
||||
C126.434669,151.063141 126.975357,144.720825 129.168777,138.834930
|
||||
C131.533630,132.489014 137.260605,130.548050 143.413757,130.046677
|
||||
C150.288467,129.486496 156.424942,123.757378 157.035324,117.320816
|
||||
C157.953949,107.633820 150.959381,101.769096 145.533951,101.194389
|
||||
C132.238846,99.786079 120.699944,104.963120 111.676735,114.167313
|
||||
C102.105782,123.930222 97.469498,136.194061 99.003151,150.234955
|
||||
C100.540352,164.308228 107.108505,175.507980 118.864334,183.311539
|
||||
C128.454544,189.677597 138.866959,191.786957 150.657837,190.245651
|
||||
C166.242554,188.208420 179.874283,182.443329 191.251801,172.056793
|
||||
C209.355011,155.530380 217.848694,134.938721 216.116119,110.085892
|
||||
C214.834335,91.699440 207.721039,76.015915 195.289444,62.978828
|
||||
C175.658447,42.391735 150.833389,37.257801 123.833740,42.281937
|
||||
C98.675804,46.963364 78.315033,60.084667 62.208153,80.157814
|
||||
C46.645889,99.552216 39.305275,121.796379 39.149052,146.201981
|
||||
C38.912663,183.131317 39.666767,220.067017 40.000000,257.000000
|
||||
C36.969406,257.000000 33.938812,257.000000 30.705070,256.668213
|
||||
C30.298622,256.078369 30.144913,255.669220 29.884926,255.583878
|
||||
C16.317770,251.131058 7.127485,242.317780 2.778462,228.591797
|
||||
C2.667588,228.241821 1.613958,228.190567 1.000000,228.000000
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M40.468658,257.000000
|
||||
C39.666767,220.067017 38.912663,183.131317 39.149052,146.201981
|
||||
C39.305275,121.796379 46.645889,99.552216 62.208153,80.157814
|
||||
C78.315033,60.084667 98.675804,46.963364 123.833740,42.281937
|
||||
C150.833389,37.257801 175.658447,42.391735 195.289444,62.978828
|
||||
C207.721039,76.015915 214.834335,91.699440 216.116119,110.085892
|
||||
C217.848694,134.938721 209.355011,155.530380 191.251801,172.056793
|
||||
C179.874283,182.443329 166.242554,188.208420 150.657837,190.245651
|
||||
C138.866959,191.786957 128.454544,189.677597 118.864334,183.311539
|
||||
C107.108505,175.507980 100.540352,164.308228 99.003151,150.234955
|
||||
C97.469498,136.194061 102.105782,123.930222 111.676735,114.167313
|
||||
C120.699944,104.963120 132.238846,99.786079 145.533951,101.194389
|
||||
C150.959381,101.769096 157.953949,107.633820 157.035324,117.320816
|
||||
C156.424942,123.757378 150.288467,129.486496 143.413757,130.046677
|
||||
C137.260605,130.548050 131.533630,132.489014 129.168777,138.834930
|
||||
C126.975357,144.720825 126.434669,151.063141 131.192612,155.987366
|
||||
C133.490692,158.365784 137.092346,160.485382 140.303482,160.823074
|
||||
C166.783981,163.607941 189.042435,140.277435 187.961487,114.156334
|
||||
C186.981964,90.486633 168.662338,69.972984 143.417328,69.901505
|
||||
C122.313736,69.841736 104.435837,77.354736 89.758743,91.961365
|
||||
C77.974228,103.689308 70.148468,118.083969 69.151917,134.942368
|
||||
C68.084618,152.997421 68.255180,171.129044 68.027298,189.229050
|
||||
C67.886772,200.390717 67.885620,211.557587 68.052299,222.718674
|
||||
C68.218323,233.835587 68.672188,244.948196 68.999924,256.531342
|
||||
C59.645771,257.000000 50.291542,257.000000 40.468658,257.000000
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M257.000000,29.531342
|
||||
C256.383850,29.809898 255.321625,29.756128 255.215851,29.410040
|
||||
C250.980164,15.550970 241.304657,7.206383 228.081207,2.393433
|
||||
C227.815079,2.296565 227.660217,1.893988 227.726715,1.317018
|
||||
C237.593155,1.000000 247.186295,1.000000 257.000000,1.000000
|
||||
C257.000000,10.353075 257.000000,19.707878 257.000000,29.531342
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M228.468658,257.000000
|
||||
C228.190491,256.383820 228.239929,255.327698 228.591782,255.214935
|
||||
C242.292618,250.822968 250.953033,241.462830 255.607178,228.080658
|
||||
C255.699371,227.815521 256.106049,227.659790 256.682983,227.726517
|
||||
C257.000000,237.593155 257.000000,247.186295 257.000000,257.000000
|
||||
C247.646927,257.000000 238.292114,257.000000 228.468658,257.000000
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M1.000000,228.468658
|
||||
C1.613958,228.190567 2.667588,228.241821 2.778462,228.591797
|
||||
C7.127485,242.317780 16.317770,251.131058 29.884926,255.583878
|
||||
C30.144913,255.669220 30.298622,256.078369 30.250959,256.668213
|
||||
C20.406853,257.000000 10.813705,257.000000 1.000000,257.000000
|
||||
C1.000000,247.646927 1.000000,238.292114 1.000000,228.468658
|
||||
z"/>
|
||||
<path fill="#000000" opacity="1.000000" stroke="none"
|
||||
d="
|
||||
M29.531342,1.000000
|
||||
C29.633436,1.645129 29.404272,2.699659 28.880219,2.875538
|
||||
C15.629609,7.322631 6.913495,16.165672 2.721949,29.500008
|
||||
C2.594676,29.904894 2.026278,30.171114 1.331771,30.250992
|
||||
C1.000000,20.406855 1.000000,10.813709 1.000000,1.000000
|
||||
C10.353074,1.000000 19.707878,1.000000 29.531342,1.000000
|
||||
z"/>
|
||||
</svg>"""
|
||||
|
||||
|
||||
class Photopea(object):
|
||||
def __init__(self) -> None:
|
||||
self.modal = None
|
||||
self.triggers = []
|
||||
self.render_editor()
|
||||
|
||||
def render_editor(self):
|
||||
"""Render the editor modal."""
|
||||
with gr.Group(elem_classes=["cnet-photopea-edit"]):
|
||||
self.modal = ModalInterface(
|
||||
# Use about:blank here as placeholder so that the iframe does not
|
||||
# immediately navigate. Only navigate when the user first click
|
||||
# 'Edit'. The navigation logic is in `photopea.js`.
|
||||
f"""
|
||||
<div class="photopea-button-group">
|
||||
<button class="photopea-button photopea-fetch">Fetch from ControlNet</button>
|
||||
<button class="photopea-button photopea-send">Send to ControlNet</button>
|
||||
</div>
|
||||
<iframe class="photopea-iframe" src="about:blank"></iframe>
|
||||
""",
|
||||
open_button_text="Edit",
|
||||
open_button_classes=["cnet-photopea-main-trigger"],
|
||||
open_button_extra_attrs="hidden",
|
||||
).create_modal(visible=True)
|
||||
|
||||
def render_child_trigger(self):
|
||||
self.triggers.append(
|
||||
gr.HTML(
|
||||
f"""<div class="cnet-photopea-child-trigger">
|
||||
Edit {PHOTOPEA_LOGO}
|
||||
</div>"""
|
||||
)
|
||||
)
|
||||
|
||||
def attach_photopea_output(self, generated_image: gr.Image):
|
||||
"""Called in ControlNetUiGroup to attach preprocessor preview image Gradio element
|
||||
as the photopea output. If the front-end directly change the img HTML element's src
|
||||
to reflect the edited image result from photopea, the backend won't be notified.
|
||||
|
||||
In this method we let the front-end upload the result image an invisible gr.Image
|
||||
instance and mirrors the value to preprocessor preview gr.Image. This is because
|
||||
the generated image gr.Image instance is inferred to be an output image by Gradio
|
||||
and has no ability to accept image upload directly.
|
||||
|
||||
Arguments:
|
||||
generated_image: preprocessor result Gradio Image output element.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
output = gr.Image(
|
||||
visible=False,
|
||||
source="upload",
|
||||
type="numpy",
|
||||
elem_classes=[f"cnet-photopea-output"],
|
||||
)
|
||||
|
||||
output.upload(
|
||||
fn=lambda img: img,
|
||||
inputs=[output],
|
||||
outputs=[generated_image],
|
||||
)
|
||||
@@ -0,0 +1,319 @@
|
||||
import os
|
||||
import gradio as gr
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
from modules import scripts
|
||||
from scripts.infotext import parse_unit, serialize_unit
|
||||
from scripts.controlnet_ui.tool_button import ToolButton
|
||||
from scripts.logging import logger
|
||||
from scripts.processor import preprocessor_filters
|
||||
from scripts import external_code
|
||||
|
||||
save_symbol = "\U0001f4be" # 💾
|
||||
delete_symbol = "\U0001f5d1\ufe0f" # 🗑️
|
||||
refresh_symbol = "\U0001f504" # 🔄
|
||||
reset_symbol = "\U000021A9" # ↩
|
||||
|
||||
NEW_PRESET = "New Preset"
|
||||
|
||||
|
||||
def load_presets(preset_dir: str) -> Dict[str, str]:
|
||||
if not os.path.exists(preset_dir):
|
||||
os.makedirs(preset_dir)
|
||||
return {}
|
||||
|
||||
presets = {}
|
||||
for filename in os.listdir(preset_dir):
|
||||
if filename.endswith(".txt"):
|
||||
with open(os.path.join(preset_dir, filename), "r") as f:
|
||||
name = filename.replace(".txt", "")
|
||||
if name == NEW_PRESET:
|
||||
continue
|
||||
presets[name] = f.read()
|
||||
return presets
|
||||
|
||||
|
||||
def infer_control_type(module: str, model: str) -> str:
|
||||
def matches_control_type(input_string: str, control_type: str) -> bool:
|
||||
return any(t.lower() in input_string for t in control_type.split("/"))
|
||||
|
||||
control_types = preprocessor_filters.keys()
|
||||
control_type_candidates = [
|
||||
control_type
|
||||
for control_type in control_types
|
||||
if (
|
||||
matches_control_type(module, control_type)
|
||||
or matches_control_type(model, control_type)
|
||||
)
|
||||
]
|
||||
if len(control_type_candidates) != 1:
|
||||
raise ValueError(
|
||||
f"Unable to infer control type from module {module} and model {model}"
|
||||
)
|
||||
return control_type_candidates[0]
|
||||
|
||||
|
||||
class ControlNetPresetUI(object):
|
||||
preset_directory = os.path.join(scripts.basedir(), "presets")
|
||||
presets = load_presets(preset_directory)
|
||||
|
||||
def __init__(self, id_prefix: str):
|
||||
with gr.Row():
|
||||
self.dropdown = gr.Dropdown(
|
||||
label="Presets",
|
||||
show_label=True,
|
||||
elem_classes=["cnet-preset-dropdown"],
|
||||
choices=ControlNetPresetUI.dropdown_choices(),
|
||||
value=NEW_PRESET,
|
||||
)
|
||||
self.reset_button = ToolButton(
|
||||
value=reset_symbol,
|
||||
elem_classes=["cnet-preset-reset"],
|
||||
tooltip="Reset preset",
|
||||
visible=False,
|
||||
)
|
||||
self.save_button = ToolButton(
|
||||
value=save_symbol,
|
||||
elem_classes=["cnet-preset-save"],
|
||||
tooltip="Save preset",
|
||||
)
|
||||
self.delete_button = ToolButton(
|
||||
value=delete_symbol,
|
||||
elem_classes=["cnet-preset-delete"],
|
||||
tooltip="Delete preset",
|
||||
)
|
||||
self.refresh_button = ToolButton(
|
||||
value=refresh_symbol,
|
||||
elem_classes=["cnet-preset-refresh"],
|
||||
tooltip="Refresh preset",
|
||||
)
|
||||
|
||||
with gr.Box(
|
||||
elem_classes=["popup-dialog", "cnet-preset-enter-name"],
|
||||
elem_id=f"{id_prefix}_cnet_preset_enter_name",
|
||||
) as self.name_dialog:
|
||||
with gr.Row():
|
||||
self.preset_name = gr.Textbox(
|
||||
label="Preset name",
|
||||
show_label=True,
|
||||
lines=1,
|
||||
elem_classes=["cnet-preset-name"],
|
||||
)
|
||||
self.confirm_preset_name = ToolButton(
|
||||
value=save_symbol,
|
||||
elem_classes=["cnet-preset-confirm-name"],
|
||||
tooltip="Save preset",
|
||||
)
|
||||
|
||||
def register_callbacks(
|
||||
self,
|
||||
uigroup,
|
||||
control_type: gr.Radio,
|
||||
*ui_states,
|
||||
):
|
||||
def apply_preset(name: str, control_type: str, *ui_states):
|
||||
if name == NEW_PRESET:
|
||||
return (
|
||||
gr.update(visible=False),
|
||||
*(
|
||||
(gr.skip(),)
|
||||
* (len(vars(external_code.ControlNetUnit()).keys()) + 1)
|
||||
),
|
||||
)
|
||||
|
||||
assert name in ControlNetPresetUI.presets
|
||||
|
||||
infotext = ControlNetPresetUI.presets[name]
|
||||
preset_unit = parse_unit(infotext)
|
||||
current_unit = external_code.ControlNetUnit(*ui_states)
|
||||
preset_unit.image = None
|
||||
current_unit.image = None
|
||||
|
||||
# Do not compare module param that are not used in preset.
|
||||
for module_param in ("processor_res", "threshold_a", "threshold_b"):
|
||||
if getattr(preset_unit, module_param) == -1:
|
||||
setattr(current_unit, module_param, -1)
|
||||
|
||||
# No update necessary.
|
||||
if vars(current_unit) == vars(preset_unit):
|
||||
return (
|
||||
gr.update(visible=False),
|
||||
*(
|
||||
(gr.skip(),)
|
||||
* (len(vars(external_code.ControlNetUnit()).keys()) + 1)
|
||||
),
|
||||
)
|
||||
|
||||
unit = preset_unit
|
||||
|
||||
try:
|
||||
new_control_type = infer_control_type(unit.module, unit.model)
|
||||
except ValueError as e:
|
||||
logger.error(e)
|
||||
new_control_type = control_type
|
||||
|
||||
if new_control_type != control_type:
|
||||
uigroup.prevent_next_n_module_update += 1
|
||||
|
||||
if preset_unit.module != current_unit.module:
|
||||
uigroup.prevent_next_n_slider_value_update += 1
|
||||
|
||||
if preset_unit.pixel_perfect != current_unit.pixel_perfect:
|
||||
uigroup.prevent_next_n_slider_value_update += 1
|
||||
|
||||
return (
|
||||
gr.update(visible=True),
|
||||
gr.update(value=new_control_type),
|
||||
*[
|
||||
gr.update(value=value) if value is not None else gr.update()
|
||||
for value in vars(unit).values()
|
||||
],
|
||||
)
|
||||
|
||||
for element, action in (
|
||||
(self.dropdown, "change"),
|
||||
(self.reset_button, "click"),
|
||||
):
|
||||
getattr(element, action)(
|
||||
fn=apply_preset,
|
||||
inputs=[self.dropdown, control_type, *ui_states],
|
||||
outputs=[self.delete_button, control_type, *ui_states],
|
||||
show_progress="hidden",
|
||||
).then(
|
||||
fn=lambda: gr.update(visible=False),
|
||||
inputs=None,
|
||||
outputs=[self.reset_button],
|
||||
)
|
||||
|
||||
def save_preset(name: str, *ui_states):
|
||||
if name == NEW_PRESET:
|
||||
return gr.update(visible=True), gr.update(), gr.update()
|
||||
|
||||
ControlNetPresetUI.save_preset(
|
||||
name, external_code.ControlNetUnit(*ui_states)
|
||||
)
|
||||
return (
|
||||
gr.update(), # name dialog
|
||||
gr.update(choices=ControlNetPresetUI.dropdown_choices(), value=name),
|
||||
gr.update(visible=False), # Reset button
|
||||
)
|
||||
|
||||
self.save_button.click(
|
||||
fn=save_preset,
|
||||
inputs=[self.dropdown, *ui_states],
|
||||
outputs=[self.name_dialog, self.dropdown, self.reset_button],
|
||||
show_progress="hidden",
|
||||
).then(
|
||||
fn=None,
|
||||
_js=f"""
|
||||
(name) => {{
|
||||
if (name === "{NEW_PRESET}")
|
||||
popup(gradioApp().getElementById('{self.name_dialog.elem_id}'));
|
||||
}}""",
|
||||
inputs=[self.dropdown],
|
||||
)
|
||||
|
||||
def delete_preset(name: str):
|
||||
ControlNetPresetUI.delete_preset(name)
|
||||
return gr.Dropdown.update(
|
||||
choices=ControlNetPresetUI.dropdown_choices(),
|
||||
value=NEW_PRESET,
|
||||
), gr.update(visible=False)
|
||||
|
||||
self.delete_button.click(
|
||||
fn=delete_preset,
|
||||
inputs=[self.dropdown],
|
||||
outputs=[self.dropdown, self.reset_button],
|
||||
show_progress="hidden",
|
||||
)
|
||||
|
||||
self.name_dialog.visible = False
|
||||
|
||||
def save_new_preset(new_name: str, *ui_states):
|
||||
if new_name == NEW_PRESET:
|
||||
logger.warn(f"Cannot save preset with reserved name '{NEW_PRESET}'")
|
||||
return gr.update(visible=False), gr.update()
|
||||
|
||||
ControlNetPresetUI.save_preset(
|
||||
new_name, external_code.ControlNetUnit(*ui_states)
|
||||
)
|
||||
return gr.update(visible=False), gr.update(
|
||||
choices=ControlNetPresetUI.dropdown_choices(), value=new_name
|
||||
)
|
||||
|
||||
self.confirm_preset_name.click(
|
||||
fn=save_new_preset,
|
||||
inputs=[self.preset_name, *ui_states],
|
||||
outputs=[self.name_dialog, self.dropdown],
|
||||
show_progress="hidden",
|
||||
).then(fn=None, _js="closePopup")
|
||||
|
||||
self.refresh_button.click(
|
||||
fn=ControlNetPresetUI.refresh_preset,
|
||||
inputs=None,
|
||||
outputs=[self.dropdown],
|
||||
show_progress="hidden",
|
||||
)
|
||||
|
||||
def update_reset_button(preset_name: str, *ui_states):
|
||||
if preset_name == NEW_PRESET:
|
||||
return gr.update(visible=False)
|
||||
|
||||
infotext = ControlNetPresetUI.presets[preset_name]
|
||||
preset_unit = parse_unit(infotext)
|
||||
current_unit = external_code.ControlNetUnit(*ui_states)
|
||||
preset_unit.image = None
|
||||
current_unit.image = None
|
||||
|
||||
# Do not compare module param that are not used in preset.
|
||||
for module_param in ("processor_res", "threshold_a", "threshold_b"):
|
||||
if getattr(preset_unit, module_param) == -1:
|
||||
setattr(current_unit, module_param, -1)
|
||||
|
||||
return gr.update(visible=vars(current_unit) != vars(preset_unit))
|
||||
|
||||
for ui_state in ui_states:
|
||||
if isinstance(ui_state, gr.Image):
|
||||
continue
|
||||
|
||||
for action in ("edit", "click", "change", "clear", "release"):
|
||||
if action == "release" and not isinstance(ui_state, gr.Slider):
|
||||
continue
|
||||
|
||||
if hasattr(ui_state, action):
|
||||
getattr(ui_state, action)(
|
||||
fn=update_reset_button,
|
||||
inputs=[self.dropdown, *ui_states],
|
||||
outputs=[self.reset_button],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def dropdown_choices() -> List[str]:
|
||||
return list(ControlNetPresetUI.presets.keys()) + [NEW_PRESET]
|
||||
|
||||
@staticmethod
|
||||
def save_preset(name: str, unit: external_code.ControlNetUnit):
|
||||
infotext = serialize_unit(unit)
|
||||
with open(
|
||||
os.path.join(ControlNetPresetUI.preset_directory, f"{name}.txt"), "w"
|
||||
) as f:
|
||||
f.write(infotext)
|
||||
|
||||
ControlNetPresetUI.presets[name] = infotext
|
||||
|
||||
@staticmethod
|
||||
def delete_preset(name: str):
|
||||
if name not in ControlNetPresetUI.presets:
|
||||
return
|
||||
|
||||
del ControlNetPresetUI.presets[name]
|
||||
|
||||
file = os.path.join(ControlNetPresetUI.preset_directory, f"{name}.txt")
|
||||
if os.path.exists(file):
|
||||
os.unlink(file)
|
||||
|
||||
@staticmethod
|
||||
def refresh_preset():
|
||||
ControlNetPresetUI.presets = load_presets(ControlNetPresetUI.preset_directory)
|
||||
return gr.update(choices=ControlNetPresetUI.dropdown_choices())
|
||||
@@ -0,0 +1,12 @@
|
||||
import gradio as gr
|
||||
|
||||
class ToolButton(gr.Button, gr.components.FormComponent):
|
||||
"""Small button with single emoji as text, fits inside gradio forms"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(variant="tool",
|
||||
elem_classes=kwargs.pop('elem_classes', []) + ["cnet-toolbutton"],
|
||||
**kwargs)
|
||||
|
||||
def get_block_name(self):
|
||||
return "button"
|
||||
Reference in New Issue
Block a user