11
11
from .jobs import JobRegion
12
12
from .control import ControlLayerList
13
13
from .settings import settings
14
+ from .text import evaluate_dynamic_prompt
14
15
15
16
16
17
class RegionLink (Enum ):
@@ -26,6 +27,7 @@ class Region(QObject, ObservableProperties):
26
27
27
28
_parent : RootRegion
28
29
_layers : list [QUuid ]
30
+ _positive_evaluated : str | None = None # None indicates not yet evaluated
29
31
30
32
layer_ids = Property ("" , persist = True , setter = "_set_layer_ids" )
31
33
positive = Property ("" , persist = True )
@@ -142,6 +144,19 @@ async def translate_prompt(self, client: Client):
142
144
if positive == self .positive :
143
145
self .positive = translated
144
146
147
+ @property
148
+ def positive_evaluated (self ) -> str :
149
+ """Return evaluated version if it exists, otherwise return original"""
150
+ return self ._positive_evaluated if self ._positive_evaluated is not None else self .positive
151
+
152
+ def evaluate_dynamic_prompt (self ):
153
+ """Evaluate the dynamic prompt and store result. Should only be called once during processing."""
154
+ self ._positive_evaluated = evaluate_dynamic_prompt (self .positive )
155
+
156
+ def clear_dynamic_prompt_eval (self ):
157
+ """Clear the evaluated prompt, must be called when processing of dynamic prompt syntax is complete"""
158
+ self ._positive_evaluated = None
159
+
145
160
146
161
class RootRegion (QObject , ObservableProperties ):
147
162
"""Manages a collection of regions, each of which is linked to one or more layers in the document.
@@ -153,6 +168,7 @@ class RootRegion(QObject, ObservableProperties):
153
168
_regions : list [Region ]
154
169
_active : Region | None = None
155
170
_active_layer : QUuid | None = None
171
+ _positive_evaluated : str | None = None # None indicates not yet evaluated
156
172
157
173
positive = Property ("" , persist = True )
158
174
negative = Property ("" , persist = True )
@@ -344,6 +360,19 @@ async def translate_prompt(self, client: Client):
344
360
if negative == self .negative :
345
361
self .negative = translated
346
362
363
+ @property
364
+ def positive_evaluated (self ) -> str :
365
+ """Return evaluated version if it exists, otherwise return original"""
366
+ return self ._positive_evaluated if self ._positive_evaluated is not None else self .positive
367
+
368
+ def evaluate_dynamic_prompt (self ):
369
+ """Evaluate the dynamic prompt and store result. Should only be called once during processing."""
370
+ self ._positive_evaluated = evaluate_dynamic_prompt (self .positive )
371
+
372
+ def clear_dynamic_prompt_eval (self ):
373
+ """Clear the evaluated prompt, must be called when processing of dynamic prompt syntax is complete"""
374
+ self ._positive_evaluated = None
375
+
347
376
def __len__ (self ):
348
377
return len (self ._regions )
349
378
@@ -368,13 +397,24 @@ def get_region_inpaint_mask(region_layer: Layer, max_extent: Extent, min_size=0)
368
397
return mask_image .to_mask (bounds )
369
398
370
399
400
+ def clear_dynamic_prompt_evals (root : RootRegion ):
401
+ """Clear the evaluated prompts for all regions."""
402
+ root .clear_dynamic_prompt_eval ()
403
+ for region in root ._regions :
404
+ region .clear_dynamic_prompt_eval ()
405
+
406
+
371
407
def process_regions (
372
408
root : RootRegion ,
373
409
bounds : Bounds ,
374
410
parent_layer : Layer | None = None ,
375
411
min_coverage = 0.02 ,
376
412
time : int | None = None ,
377
413
):
414
+ if settings .dynamic_prompts :
415
+ root .evaluate_dynamic_prompt ()
416
+ for region in root ._regions :
417
+ region .evaluate_dynamic_prompt ()
378
418
parent_region = None
379
419
if parent_layer and not parent_layer .is_root :
380
420
parent_region = root .find_linked (parent_layer )
@@ -383,11 +423,11 @@ def process_regions(
383
423
job_info = []
384
424
control = root .control .to_api (bounds , time )
385
425
if parent_layer and parent_region :
386
- parent_prompt = parent_region .positive
426
+ parent_prompt = parent_region .positive_evaluated
387
427
control += parent_region .control .to_api (bounds , time )
388
428
job_info = [JobRegion (parent_layer .id_string , parent_prompt , bounds )]
389
429
result = ConditioningInput (
390
- positive = workflow .merge_prompt (parent_prompt , root .positive ),
430
+ positive = workflow .merge_prompt (parent_prompt , root .positive_evaluated ),
391
431
negative = root .negative ,
392
432
control = control ,
393
433
)
@@ -401,6 +441,7 @@ def process_regions(
401
441
layer_regions = ((l , root .find_linked (l , RegionLink .direct )) for l in child_layers )
402
442
layer_regions = [(l , r ) for l , r in layer_regions if r is not None ]
403
443
if len (layer_regions ) == 0 :
444
+ clear_dynamic_prompt_evals (root )
404
445
return result , job_info
405
446
406
447
# Get region masks. Filter out regions with:
@@ -416,13 +457,14 @@ def process_regions(
416
457
if coverage_rough < 2 * min_coverage :
417
458
continue
418
459
460
+ region_positive = region .positive_evaluated
419
461
region_result = RegionInput (
420
462
layer .get_mask (bounds ),
421
463
layer_bounds ,
422
- workflow .merge_prompt (region . positive , root .positive ),
464
+ workflow .merge_prompt (region_positive , root .positive_evaluated ),
423
465
control = region .control .to_api (bounds , time ),
424
466
)
425
- job_params = JobRegion (layer .id_string , region . positive , layer_bounds )
467
+ job_params = JobRegion (layer .id_string , region_positive , layer_bounds )
426
468
result_regions .append ((region_result , job_params ))
427
469
428
470
# Remove from each region mask any overlapping areas from regions above it.
@@ -437,8 +479,9 @@ def process_regions(
437
479
coverage = mask .average ()
438
480
if coverage > 0.9 and min_coverage > 0 :
439
481
# Single region covers (almost) entire image, don't use regional conditioning.
440
- result .positive = region .positive
482
+ result .positive = region .positive_evaluated
441
483
result .control += region .control
484
+ clear_dynamic_prompt_evals (root )
442
485
return result , [job_region ]
443
486
elif coverage < min_coverage :
444
487
# Region has less than minimum coverage, remove it.
@@ -452,6 +495,7 @@ def process_regions(
452
495
453
496
# If there are no regions left, don't use regional conditioning.
454
497
if len (result_regions ) == 0 :
498
+ clear_dynamic_prompt_evals (root )
455
499
return result , job_info
456
500
457
501
# If the region(s) don't cover the entire image, add a final region for the remaining area.
@@ -463,5 +507,6 @@ def process_regions(
463
507
job = JobRegion (parent_layer .id_string , "background" , bounds , is_background = True )
464
508
result_regions .insert (0 , (input , job ))
465
509
510
+ clear_dynamic_prompt_evals (root )
466
511
result .regions = [r for r , _ in result_regions ]
467
512
return result , [j for _ , j in result_regions ]
0 commit comments