2 Commits
Author SHA1 Message Date
Andrés Zsögön 140b32bcfb Release 1.1.3 with faster regional conditioning
Deduplicate identical global negative conditioning across regional and global branches, cutting redundant UNet and ControlNet evaluations while preserving the existing positive area, global strength, and fallback behavior.

Add a regression test for the single-negative graph structure.
2026-10-03 22:10:09 -03:00
Andrés Zsögön d81a3a2701 Add issue cleanup and Manager icon 2026-08-19 08:59:04 -03:00
5 changed files with 173 additions and 26 deletions
@@ -0,0 +1,33 @@
name: Close inactive issues
on:
schedule:
- cron: "17 3 * * *"
workflow_dispatch:
permissions:
contents: read
issues: write
pull-requests: read
jobs:
close-inactive-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
with:
repo-token: ${{ github.token }}
days-before-issue-stale: 23
days-before-issue-close: 7
stale-issue-label: stale
stale-issue-message: >
This issue has had no activity for 23 days. It will be
automatically closed in 7 days unless there is new activity.
close-issue-message: >
This issue was automatically closed after 30 days without
activity. If the problem still exists, please reopen it or
create a new issue with updated information.
close-issue-reason: not_planned
exempt-issue-labels: pinned,security,confirmed-bug
days-before-pr-stale: -1
days-before-pr-close: -1
+20
View File
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" role="img" aria-labelledby="title desc">
<title id="title">LoRA Pipeline icon</title>
<desc id="desc">LP monogram in violet and yellow.</desc>
<defs>
<linearGradient id="panel" x1="42" y1="32" x2="360" y2="370" gradientUnits="userSpaceOnUse">
<stop stop-color="#292632"/>
<stop offset="1" stop-color="#17171d"/>
</linearGradient>
<linearGradient id="accent" x1="82" y1="94" x2="318" y2="304" gradientUnits="userSpaceOnUse">
<stop stop-color="#b49aff"/>
<stop offset="1" stop-color="#ffd400"/>
</linearGradient>
</defs>
<rect width="400" height="400" rx="88" fill="#11131a"/>
<rect x="28" y="28" width="344" height="344" rx="72" fill="url(#panel)" stroke="url(#accent)" stroke-width="10"/>
<text x="200" y="252" fill="url(#accent)" font-family="Arial, Helvetica, sans-serif" font-size="150" font-weight="800" letter-spacing="-8" text-anchor="middle">LP</text>
<circle cx="86" cy="314" r="9" fill="#b49aff"/>
<circle cx="116" cy="314" r="9" fill="#ffd400"/>
<path d="M146 314h168" stroke="url(#accent)" stroke-width="10" stroke-linecap="round" opacity=".55"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+16 -24
View File
@@ -130,66 +130,58 @@ class ConditioningPipelineCombine:
graph = GraphBuilder()
# First entry: PairConditioningSetProperties (no previous combined state yet)
# First entry: set properties on the regional positive conditioning.
# The negative conditioning is global and identical for every region, so
# evaluating masked copies of it would repeat the same UNet prediction.
first = valid[0]
mask_0 = self._make_mask(
first["x"], first["y"], first["width"], first["height"], self.BASE_RES,
)
acc = graph.node(
"PairConditioningSetProperties",
positive_NEW=first["conditioning"],
negative_NEW=global_negative,
"ConditioningSetProperties",
cond_NEW=first["conditioning"],
strength=first.get("strength", 1.0),
set_cond_area="default",
mask=mask_0,
)
acc_pos = acc.out(0)
acc_neg = acc.out(1)
# Entries siguientes: PairConditioningSetPropertiesAndCombine
# Following entries: combine regional positive conditionings only.
for item in valid[1:]:
mask_i = self._make_mask(
item["x"], item["y"], item["width"], item["height"], self.BASE_RES,
)
acc = graph.node(
"PairConditioningSetPropertiesAndCombine",
positive=acc_pos,
negative=acc_neg,
positive_NEW=item["conditioning"],
negative_NEW=global_negative,
"ConditioningSetPropertiesAndCombine",
cond=acc_pos,
cond_NEW=item["conditioning"],
strength=item.get("strength", 1.0),
set_cond_area="default",
mask=mask_i,
)
acc_pos = acc.out(0)
acc_neg = acc.out(1)
# Global como entrada full-image con strength controlable
if global_strength > 0.0:
global_mask = self._make_mask(0.0, 0.0, 1.0, 1.0, self.BASE_RES)
acc = graph.node(
"PairConditioningSetPropertiesAndCombine",
positive=acc_pos,
negative=acc_neg,
positive_NEW=global_positive,
negative_NEW=global_negative,
"ConditioningSetPropertiesAndCombine",
cond=acc_pos,
cond_NEW=global_positive,
strength=global_strength,
set_cond_area="default",
mask=global_mask,
)
acc_pos = acc.out(0)
acc_neg = acc.out(1)
# Default fallback (safety net para pixeles sin cubrir)
final = graph.node(
"PairConditioningSetDefaultCombine",
positive=acc_pos,
negative=acc_neg,
positive_DEFAULT=global_positive,
negative_DEFAULT=global_negative,
"ConditioningSetDefaultCombine",
cond=acc_pos,
cond_DEFAULT=global_positive,
)
return {
"result": (final.out(0), final.out(1), areas_list),
"result": (final.out(0), global_negative, areas_list),
"expand": graph.finalize(),
}
+2 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "comfyui-lora-pipeline"
version = "1.1.2"
version = "1.1.3"
description = "Area-based LoRA scheduling and area conditioning wrappers for ComfyUI."
readme = "README.md"
requires-python = ">=3.10"
@@ -19,5 +19,5 @@ Documentation = "https://github.com/andreszs/comfyui-lora-pipeline#readme"
[tool.comfy]
PublisherId = "andreszs"
DisplayName = "LoRA Pipeline"
Icon = ""
Icon = "https://raw.githubusercontent.com/andreszs/comfyui-lora-pipeline/main/assets/icon.svg"
Banner = "https://raw.githubusercontent.com/andreszs/comfyui-lora-pipeline/refs/heads/main/assets/banner_112.gif"
+102
View File
@@ -0,0 +1,102 @@
import importlib.util
import sys
import types
import unittest
from pathlib import Path
_MODULE_PATH = Path(__file__).parents[1] / "nodes" / "cond_area_pipeline.py"
_SPEC = importlib.util.spec_from_file_location("lora_pipeline_cond_area", _MODULE_PATH)
_MODULE = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(_MODULE)
ConditioningPipelineCombine = _MODULE.ConditioningPipelineCombine
class _Output:
def __init__(self, node_type, index):
self.node_type = node_type
self.index = index
class _Node:
def __init__(self, node_type):
self.node_type = node_type
def out(self, index):
return _Output(self.node_type, index)
class _GraphBuilder:
instance = None
def __init__(self):
self.calls = []
_GraphBuilder.instance = self
def node(self, node_type, **inputs):
self.calls.append((node_type, inputs))
return _Node(node_type)
def finalize(self):
return self.calls
class ConditioningPipelineCombineTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
graph_utils = types.ModuleType("comfy_execution.graph_utils")
graph_utils.GraphBuilder = _GraphBuilder
comfy_execution = types.ModuleType("comfy_execution")
comfy_execution.graph_utils = graph_utils
sys.modules["comfy_execution"] = comfy_execution
sys.modules["comfy_execution.graph_utils"] = graph_utils
def test_negative_conditioning_is_not_duplicated_per_area(self):
global_positive = object()
global_negative = object()
pipeline = [
{
"conditioning": object(),
"x": 0.0,
"y": 0.0,
"width": 0.5,
"height": 1.0,
"strength": 0.8,
},
{
"conditioning": object(),
"x": 0.5,
"y": 0.0,
"width": 0.5,
"height": 1.0,
"strength": 0.8,
},
]
result = ConditioningPipelineCombine().run(
global_positive,
global_negative,
pipeline,
global_strength=0.3,
)
positive, negative, areas = result["result"]
calls = _GraphBuilder.instance.calls
self.assertIs(negative, global_negative)
self.assertEqual(len(areas), 2)
self.assertEqual(
[node_type for node_type, _ in calls],
[
"ConditioningSetProperties",
"ConditioningSetPropertiesAndCombine",
"ConditioningSetPropertiesAndCombine",
"ConditioningSetDefaultCombine",
],
)
self.assertEqual(positive.node_type, "ConditioningSetDefaultCombine")
self.assertFalse(any(node_type.startswith("PairConditioning") for node_type, _ in calls))
if __name__ == "__main__":
unittest.main()