Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b24d93b778 | ||
|
|
c8f925c4ea | ||
|
|
1a733e71af | ||
|
|
e36eff4356 | ||
|
|
c6137ddc49 | ||
|
|
64417230a3 | ||
|
|
0a2ceb94e9 | ||
|
|
6e3fce9dcb | ||
|
|
b581cf7f24 | ||
|
|
d7a992b96d | ||
|
|
19123570e1 | ||
|
|
e6bb57cd25 |
@@ -11,6 +11,9 @@ A `Basic Text to Image` template is included with the extension, and can be load
|
||||
> The parser was rewritten using parsy. It is intended to have the same behaviour as the old parser, but is **significantly** faster.
|
||||
> Please report any bugs or incompatibilities you find.
|
||||
|
||||
## Notable changes
|
||||
|
||||
- `PC: Schedule Prompt` now strips surrounding whitespace by default, which may change some prompts. Add `NOSTRIP()` to your prompt to restore previous behaviour.
|
||||
|
||||
## What can it do?
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
The `NODE` function allows you to use any other text encoding node within `PC: Schedule Prompt`, replacing the default `PCTextEncode` and allowing for example video model scheduling.
|
||||
|
||||
> [!NOTE]
|
||||
> When using NODE, you lose access to *all* special syntax provided by `PCTextEncode`. Only SEGs, macros and scheduling will continue to work since those are processed at graph expansion time before the text prompt is passed into the node.
|
||||
|
||||
## Basic usage
|
||||
|
||||
Use `NODE(NodeClassName, textinputname)` in a prompt to generate a graph using any node that's compatible. The requirements are as follows:
|
||||
|
||||
@@ -14,6 +14,9 @@ Besides the syntax documented below, the [basic syntax](/doc/basic.md) and [prom
|
||||
a [large::0.1] [cat|dog:0.05] [<lora:somelora:0.5:0.6>::0.5]
|
||||
[in a park:in space:0.4]
|
||||
```
|
||||
## Note on whitespace
|
||||
`PC: Schedule Prompt` will strip leading and following whitespace from the prompt automatically. If you really want whitespace in your prompt, include `NOSTRIP()` in your prompt.
|
||||
|
||||
## Comments and escaping
|
||||
|
||||
In schedules, any text on a line following a `#` is considered a comment and removed, including the `#` character.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -197,8 +197,12 @@ def build_scheduled_prompts(graph, schedules, clip):
|
||||
start_pct = 0.0
|
||||
for end_pct, c in schedules:
|
||||
p = c["prompt"]
|
||||
# Need to explicitly expand SEGs here *before* NODE is processed
|
||||
p = expand_segs(p)
|
||||
p, classnames = get_function(p, "NODE", defaults=None)
|
||||
realargs = ["PCTextEncode", "text", ""]
|
||||
if len(classnames) > 1:
|
||||
log.warning("You have more than one NODE call in your prompt. Only the first one will be used")
|
||||
if classnames:
|
||||
args = classnames[0].args[0]
|
||||
if not args.strip():
|
||||
@@ -208,6 +212,8 @@ def build_scheduled_prompts(graph, schedules, clip):
|
||||
classname, paramname, magic_spec = realargs
|
||||
node = graph.node(classname.strip())
|
||||
node.set_input("clip", clip)
|
||||
# We should strip extra whitespace so that people don't have to worry about functions.
|
||||
p = p.replace("NOSTRIP()", "") if "NOSTRIP()" in p else p.strip()
|
||||
node.set_input(paramname.strip(), p)
|
||||
magic_spec.replace(r"\;", "__ESCAPED_SEMICOLON__")
|
||||
extra_inputs = magic_spec.split(";") if magic_spec.strip() else []
|
||||
|
||||
@@ -185,28 +185,64 @@ class PCMacroExpand(io.ComfyNode):
|
||||
|
||||
|
||||
class PCLinkHelper(io.ComfyNode):
|
||||
# a-z
|
||||
NAMES = [chr(97 + i) for i in range(26)]
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls):
|
||||
inputs: list = [io.String.Input("text", multiline=True)]
|
||||
for x in "abcdefghijklmn":
|
||||
inputs.append(io.AnyType.Input(x, optional=True, lazy=True, extra_dict={"rawLink": True}))
|
||||
t1 = io.Autogrow.TemplateNames(io.AnyType.Input("link", raw_link=True), min=0, names=cls.NAMES)
|
||||
t2 = io.Autogrow.TemplateNames(
|
||||
io.AnyType.Input("value", lazy=True), min=0, names=[f"var{i + 1}" for i in range(50)]
|
||||
)
|
||||
return io.Schema(
|
||||
node_id="PCNODELinkHelper",
|
||||
display_name="PC: Extra argument helper for NODE",
|
||||
category="promptcontrol/tools",
|
||||
description="Takes in arbitrary inputs and renders them as NODE-compatible values, replacing $a -> $n with JSON link values",
|
||||
description="Takes in arbitrary inputs and renders them as NODE-compatible values, replacing $a -> $z with JSON link values.",
|
||||
is_experimental=True,
|
||||
inputs=inputs,
|
||||
inputs=[
|
||||
io.Autogrow.Input("links", template=t1),
|
||||
io.Autogrow.Input(
|
||||
"vars",
|
||||
template=t2,
|
||||
),
|
||||
io.String.Input(
|
||||
"template",
|
||||
tooltip="The variables $a to $z will be replaced in this text with their corresponding input's JSON link value",
|
||||
placeholder="In this text you can refer to the input links as $a, $b etc. and the var inputs as either $var1 or $json1 etc. (the latter will be rendered through Python's json.dumps function which will cause strings to be quoted)",
|
||||
multiline=True,
|
||||
),
|
||||
],
|
||||
outputs=[io.String.Output()],
|
||||
)
|
||||
|
||||
# This requires https://github.com/Comfy-Org/ComfyUI/pull/15103 to work properly
|
||||
@classmethod
|
||||
def execute(cls, text, **vars) -> io.NodeOutput:
|
||||
for k in "abcdefghijklmn":
|
||||
def check_lazy_status(cls, template, links, vars):
|
||||
r = []
|
||||
for name, (v, input_name) in vars.items():
|
||||
if v is None and f"${name}" in template or v is None and f"$json{name[3:]}" in template:
|
||||
r.append(input_name)
|
||||
return r
|
||||
|
||||
@classmethod
|
||||
def execute(cls, template, links, vars) -> io.NodeOutput:
|
||||
text = template
|
||||
for k in cls.NAMES:
|
||||
v = "__EMPTY__"
|
||||
if k in vars:
|
||||
v = json.dumps(vars[k])
|
||||
if k in links:
|
||||
v = json.dumps(links[k])
|
||||
text = substitute_var(text, k, v)
|
||||
for i in range(50):
|
||||
v = "__EMPTY__"
|
||||
k = f"var{i + 1}"
|
||||
if k in vars:
|
||||
v = vars[k]
|
||||
text = substitute_var(text, k, str(v))
|
||||
if f"$json{i + 1}" in text:
|
||||
v = v if v == "__EMPTY__" else json.dumps(v)
|
||||
text = substitute_var(text, f"json{i + 1}", v)
|
||||
|
||||
return io.NodeOutput(text)
|
||||
|
||||
|
||||
|
||||
@@ -370,7 +370,17 @@ ctlweight = seq(number, (string("@") >> number).optional(0)).sep_by(comma, min=1
|
||||
loractl = (string("<loractl:") >> filename * 1 + lora_weights(ctlweight) << string(">")).combine(LoRACTL)
|
||||
emb = (string("<emb:") >> filename << string(">")).map(lambda f: Text(f"embedding:{f}"))
|
||||
|
||||
expr = escape | comment | non_special | bracketed | emphasis.combine(combine_prompt) | lora | loractl | emb
|
||||
expr = (
|
||||
escape
|
||||
| comment
|
||||
| non_special
|
||||
| bracketed
|
||||
| emphasis.combine(combine_prompt)
|
||||
| lora
|
||||
| loractl
|
||||
| emb
|
||||
| char_from("<>").map(Text)
|
||||
)
|
||||
prompt_ = expr.at_least(1).combine(combine_prompt)
|
||||
prompt.become(prompt_)
|
||||
# Treat any character that isn't valid prompt syntax as just text
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-prompt-control"
|
||||
description = "Nodes for prompt editing and LoRA scheduling, advanced regional prompting (including attention masking) and advanced prompt encoding, all controlled through your text prompt. Feature keywords: comfyui-prompt-control, schedule, macros, attention couple, loractl, A1111"
|
||||
version = "3.0.0-beta.7"
|
||||
version = "3.0.0-beta.9"
|
||||
license = { file = "LICENSE" }
|
||||
|
||||
requires-python = ">= 3.10"
|
||||
|
||||
+2
-7
@@ -461,7 +461,7 @@ def test_textencode_lora_with_schedule():
|
||||
|
||||
|
||||
def test_textencode_custom():
|
||||
r = te("NODE(CLIPTextEncode)simple [test:0.1,0.5] prompt")
|
||||
r = te("NODE(CLIPTextEncode)simple [test:0.1,0.5] $p SEG(p) prompt")
|
||||
assert r == {
|
||||
"result": (["UID.0.0.8", 0],),
|
||||
"expand": {
|
||||
@@ -538,12 +538,7 @@ def test_textencode_custom_extra():
|
||||
},
|
||||
"UID.0.0.5": {
|
||||
"class_type": "CustomTextEncode",
|
||||
"inputs": {
|
||||
"clip": [0, 0],
|
||||
"prompt": "simple prompt",
|
||||
"image": ["1", 0],
|
||||
"option": "test"
|
||||
},
|
||||
"inputs": {"clip": [0, 0], "prompt": "simple prompt", "image": ["1", 0], "option": "test"},
|
||||
},
|
||||
"UID.0.0.6": {
|
||||
"class_type": "ConditioningSetTimestepRange",
|
||||
|
||||
@@ -342,6 +342,12 @@ def test_cornercase_corrected(parse):
|
||||
assert p.parsed_prompt[1:] == p2.parsed_prompt
|
||||
|
||||
|
||||
def test_ltgt_in_schedule(parse):
|
||||
p = parse("This should [<parse> correctly:be <Picture 1>:0.1]<lora:test:1>")
|
||||
assert_prompt(p, 0.1, 0.1, "This should <parse> correctly", ("test", 1.0, 1.0))
|
||||
assert_prompt(p, 0.15, 1.0, "This should be <Picture 1>", ("test", 1.0, 1.0))
|
||||
|
||||
|
||||
def test_floats(parse):
|
||||
p = parse("[a:b:0.5] [c:d:e:0.2,0.7] <lora:test:-0.3>")
|
||||
p2 = parse("[a:b:.5] [c:d:e:.2,.7] <lora:test:-.3>")
|
||||
|
||||
Reference in New Issue
Block a user