| """Write the instruction board into the Starter workflow, in place. |
| |
| Idempotent: cards are identified by `properties.htc_card`, so re-running |
| replaces the board rather than stacking a second copy beside it. Operates on the |
| shipped `.json` itself rather than rebuilding from a private source workflow, so |
| anyone with the pack can run it. |
| |
| It also strips `widgets_values_named` from every node. Both shipped workflows |
| carried a stale copy inherited from the dev workflow they were cloned out of -- |
| `chains: 3`, `duration: 10 s`, `control_after_generate: randomize`, and a legacy |
| `ref_plan` naming pictures that do not ship, 25 entries against a 28-widget node. |
| It is dormant while `Comfy.Workflow.NamedValuesRestore` stays off (experimental, |
| default false), but anyone who turns that on would load a Starter that randomizes |
| its seed and dies on a missing reference. The frontend re-emits the block |
| correctly on the next save. |
| """ |
| import io |
| import json |
| import os |
| import shutil |
| import sys |
|
|
| HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
|
|
| import notes |
|
|
| WF = os.path.join(HERE, "workflows", "HandTieClips_Starter.json") |
| |
| |
| ALSO_STRIP = [os.path.join(HERE, "workflows", "HandTieClips_Showcase.json")] |
| MARKER = "htc_card" |
| FIRST_ID = 20 |
|
|
|
|
| def strip_named(path): |
| """Drop `widgets_values_named`, and refresh the Showcase's plain Note. |
| |
| The Showcase carries one `Note` rather than the card board; its text lives |
| in `notes.SHOWCASE_NOTE` so there is still a single source for it. |
| """ |
| wf = json.load(io.open(path, encoding="utf-8")) |
| n = sum(1 for node in wf["nodes"] |
| if node.pop("widgets_values_named", None) is not None) |
| for node in wf["nodes"]: |
| if node["type"] == "Note" and node.get("widgets_values") != [notes.SHOWCASE_NOTE]: |
| node["widgets_values"] = [notes.SHOWCASE_NOTE] |
| |
| |
| node["size"] = [440, 900] |
| n += 1 |
| if n: |
| bak = path + ".bak-notes" |
| if not os.path.exists(bak): |
| shutil.copyfile(path, bak) |
| io.open(path, "w", encoding="utf-8", newline="\n").write( |
| json.dumps(wf, indent=2) + "\n") |
| return n |
|
|
|
|
| def main(): |
| wf = json.load(io.open(WF, encoding="utf-8")) |
|
|
| bak = WF + ".bak-notes" |
| if not os.path.exists(bak): |
| shutil.copyfile(WF, bak) |
|
|
| |
| |
| before = len(wf["nodes"]) |
| wf["nodes"] = [n for n in wf["nodes"] |
| if MARKER not in (n.get("properties") or {}) |
| and n["type"] != "Note"] |
| dropped = before - len(wf["nodes"]) |
|
|
| order = max((n.get("order", 0) for n in wf["nodes"]), default=0) |
| for i, (key, title, pos, size, colour, text) in enumerate(notes.CARDS): |
| order += 1 |
| wf["nodes"].append({ |
| "id": FIRST_ID + i, |
| "type": "MarkdownNote", |
| "pos": list(pos), |
| "size": list(size), |
| "flags": {}, |
| "order": order, |
| "mode": 0, |
| "inputs": [], |
| "outputs": [], |
| "title": title, |
| "properties": {MARKER: key}, |
| "widgets_values": [text], |
| "color": colour[0], |
| "bgcolor": colour[1], |
| }) |
|
|
| wf["groups"] = [dict(notes.GROUP)] |
| wf["extra"]["ds"] = dict(notes.DS) |
| wf["last_node_id"] = max(wf["last_node_id"], FIRST_ID + len(notes.CARDS) - 1) |
|
|
| stripped = 0 |
| for n in wf["nodes"]: |
| if n.pop("widgets_values_named", None) is not None: |
| stripped += 1 |
|
|
| io.open(WF, "w", encoding="utf-8", newline="\n").write( |
| json.dumps(wf, indent=2) + "\n") |
|
|
| print("%s\n dropped %d old note/card node(s), wrote %d card(s)" |
| % (os.path.basename(WF), dropped, len(notes.CARDS))) |
| print(" stripped widgets_values_named from %d node(s)" % stripped) |
| print(" %d nodes, %d bytes" % (len(wf["nodes"]), os.path.getsize(WF))) |
|
|
| for path in ALSO_STRIP: |
| print("%s\n stripped widgets_values_named from %d node(s)" |
| % (os.path.basename(path), strip_named(path))) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|