convert_workflow.py: resolve unwired promoted subgraph inputs from sibling widget defaults
Browse files
workflows/scripts/convert_workflow.py
CHANGED
|
@@ -28,6 +28,13 @@ SUBGRAPH_INPUT_ID = -10
|
|
| 28 |
SUBGRAPH_OUTPUT_ID = -20
|
| 29 |
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
class Scope:
|
| 32 |
"""One graph namespace: either the root workflow or a subgraph instance."""
|
| 33 |
|
|
@@ -85,9 +92,13 @@ class Converter:
|
|
| 85 |
# ones live in proxyWidgets instead), so match by name, not slot index
|
| 86 |
name = self.subgraph_defs[instance["type"]]["inputs"][origin_slot]["name"]
|
| 87 |
entry = next((i for i in instance.get("inputs", []) if i["name"] == name), None)
|
| 88 |
-
if entry is None:
|
| 89 |
-
return
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
node = scope.nodes[origin_id]
|
| 93 |
ntype = node["type"]
|
|
@@ -117,6 +128,25 @@ class Converter:
|
|
| 117 |
|
| 118 |
return (scope.fid(origin_id), origin_slot)
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
def widget_inputs(self, node):
|
| 121 |
"""Map a node's widgets_values onto named inputs using /object_info order."""
|
| 122 |
if "Power Lora Loader" in node["type"]:
|
|
@@ -188,7 +218,11 @@ class Converter:
|
|
| 188 |
inputs = self.widget_inputs(node)
|
| 189 |
for inp in node.get("inputs", []):
|
| 190 |
source = self.resolve(scope, inp.get("link"))
|
| 191 |
-
if source
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
inputs[inp["name"]] = list(source)
|
| 193 |
elif inp.get("link") is not None and not inp.get("widget"):
|
| 194 |
print(f"warning: {scope.fid(nid)} ({ntype}) input {inp['name']!r} "
|
|
|
|
| 28 |
SUBGRAPH_OUTPUT_ID = -20
|
| 29 |
|
| 30 |
|
| 31 |
+
class WidgetDefault:
|
| 32 |
+
"""Literal carried across an unwired promoted subgraph input."""
|
| 33 |
+
|
| 34 |
+
def __init__(self, value):
|
| 35 |
+
self.value = value
|
| 36 |
+
|
| 37 |
+
|
| 38 |
class Scope:
|
| 39 |
"""One graph namespace: either the root workflow or a subgraph instance."""
|
| 40 |
|
|
|
|
| 92 |
# ones live in proxyWidgets instead), so match by name, not slot index
|
| 93 |
name = self.subgraph_defs[instance["type"]]["inputs"][origin_slot]["name"]
|
| 94 |
entry = next((i for i in instance.get("inputs", []) if i["name"] == name), None)
|
| 95 |
+
if entry is not None and entry.get("link") is not None:
|
| 96 |
+
return self.resolve(parent_scope, entry["link"])
|
| 97 |
+
# unwired promoted widget: the UI backs it with the widget of the
|
| 98 |
+
# first interior node this boundary slot fans out to, so inputs
|
| 99 |
+
# without a widget of their own (e.g. math variables) still get a
|
| 100 |
+
# value -- mirror that lookup here
|
| 101 |
+
return self._boundary_default(scope, origin_slot)
|
| 102 |
|
| 103 |
node = scope.nodes[origin_id]
|
| 104 |
ntype = node["type"]
|
|
|
|
| 128 |
|
| 129 |
return (scope.fid(origin_id), origin_slot)
|
| 130 |
|
| 131 |
+
def _boundary_default(self, scope, origin_slot):
|
| 132 |
+
"""Widget value backing an unwired promoted subgraph input, taken from
|
| 133 |
+
the first fan-out target of the slot that has that widget itself."""
|
| 134 |
+
for _, (oid, oslot, tid, tslot) in sorted(scope.links.items()):
|
| 135 |
+
if oid != SUBGRAPH_INPUT_ID or oslot != origin_slot:
|
| 136 |
+
continue
|
| 137 |
+
target = scope.nodes.get(tid)
|
| 138 |
+
if (target is None or target.get("mode", 0) in (2, 4)
|
| 139 |
+
or target["type"] in self.subgraph_defs
|
| 140 |
+
or target["type"] not in self.oi):
|
| 141 |
+
continue
|
| 142 |
+
if tslot >= len(target.get("inputs", [])):
|
| 143 |
+
continue
|
| 144 |
+
widgets = self.widget_inputs(target)
|
| 145 |
+
name = target["inputs"][tslot]["name"]
|
| 146 |
+
if name in widgets:
|
| 147 |
+
return WidgetDefault(widgets[name])
|
| 148 |
+
return None
|
| 149 |
+
|
| 150 |
def widget_inputs(self, node):
|
| 151 |
"""Map a node's widgets_values onto named inputs using /object_info order."""
|
| 152 |
if "Power Lora Loader" in node["type"]:
|
|
|
|
| 218 |
inputs = self.widget_inputs(node)
|
| 219 |
for inp in node.get("inputs", []):
|
| 220 |
source = self.resolve(scope, inp.get("link"))
|
| 221 |
+
if isinstance(source, WidgetDefault):
|
| 222 |
+
# the node's own widget value (if any) already won via
|
| 223 |
+
# widget_inputs; only fill inputs that have none
|
| 224 |
+
inputs.setdefault(inp["name"], source.value)
|
| 225 |
+
elif source is not None:
|
| 226 |
inputs[inp["name"]] = list(source)
|
| 227 |
elif inp.get("link") is not None and not inp.get("widget"):
|
| 228 |
print(f"warning: {scope.fid(nid)} ({ntype}) input {inp['name']!r} "
|