Repository navigation
Expand file tree
/
Copy pathshelf.py
More file actions
129 lines (114 loc) · 5.53 KB
/
Copy pathshelf.py
File metadata and controls
129 lines (114 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""RedNode Shelf - a place to put pictures while you move them around.
Your ask (2026-09-23): moving a picture between tabs meant right-click, copy,
find the other tab, paste, and a core Load Image only ever takes a drop, it
never gives one back. The shelf holds as many pictures as you drop on it, in a
column you can scroll, and every one of them drags back OUT: onto a Workspace
gallery, the Paint pane, another shelf, or into a folder on your desktop.
Dragging out COPIES. The picture stays on the shelf until you take it off by
hand, so a misfired drag never loses one.
It is a Load Image as well, without being asked to be: the picture picked on
the shelf comes out of `image`, so a shelf beside the graph can feed anything
that wants a picture. Nothing is copied on disk; the shelf stores the same
"name.png [output]" entries the galleries use, so it points at pictures that
already exist.
"""
import json
from . import workspace as _ws
# WHERE AN OVERRIDING SHELF PUTS ITS PICTURE. The same tabs the right-click "send
# to" offers, because it is the same act said once instead of every time: this
# picture, on that tab. Ticking one is as deliberate as a send, so an overridden
# tab is switched on for the run the way a send switches it on.
OVERRIDE_TABS = ("i2i", "editor_src", "subject", "scene", "moodboard")
# Img2Img and the Editor's source to begin with: both are "the picture I am
# working on", and which of the two renders depends on the Editor's own switch
# (Edit is the output), so an override aimed at one alone looked broken when the
# other was the one rendering (you, 2026-09-25). Subject or Moodboard would rewrite
# the render, so they stay a choice.
OVERRIDE_DEFAULT = ("i2i", "editor_src")
class RedNodeShelf:
CATEGORY = "RedNode/Tools"
DESCRIPTION = ("A shelf for pictures. Drop them on, drag them off onto any "
"gallery, the Paint pane or a folder, and right-click one to "
"send it straight to a Workspace tab. The picture picked here "
"also comes out of the image socket, so it doubles as a Load "
"Image you can drag out of.")
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "pick"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"config": ("STRING", {
"multiline": True, "default": "{}",
"tooltip": "The shelf's own state: the pictures on it and which "
"one is picked. The panel writes this; there is "
"nothing to type here.",
}),
},
"optional": {
"resize": ("INT", {
"default": 0, "min": 0, "max": 8192, "step": 64,
"tooltip": "Long edge for the picture on the image socket. 0 "
"hands it over at its own size.",
}),
},
}
@staticmethod
def _entries(config):
try:
cfg = json.loads(config or "{}")
except (TypeError, ValueError):
return [], 0
if not isinstance(cfg, dict):
return [], 0
# items/sel are the stack on show; the panel keeps four more under
# "stacks" by name, and only the one on show is what a run reads
items = [str(x) for x in (cfg.get("items") or []) if str(x).strip()]
try:
sel = int(cfg.get("sel", 0))
except (TypeError, ValueError):
sel = 0
return items, max(0, min(sel, len(items) - 1)) if items else 0
@classmethod
def parse(cls, config):
"""{items, sel, entry, override, tabs, at} for one shelf's config widget.
`entry` is the picked picture, "" for an empty shelf. `at` is when the
override was switched on, which is how two of them are told apart.
Override with nothing ticked feeds nothing, and that is not an error: it
is a switch waiting to be told where to put the picture.
"""
try:
raw = json.loads(config or "{}")
except (TypeError, ValueError):
raw = {}
if not isinstance(raw, dict):
raw = {}
items, sel = cls._entries(config)
want = raw.get("override_tabs")
tabs = ([t for t in OVERRIDE_TABS if t in want] if isinstance(want, list)
else list(OVERRIDE_DEFAULT))
try:
at = float(raw.get("override_at") or 0)
except (TypeError, ValueError):
at = 0.0
return {"items": items, "sel": sel,
"entry": items[sel] if items else "",
"override": bool(raw.get("override")), "tabs": tabs, "at": at}
def pick(self, config="{}", resize=0):
"""The picked picture, or a blocked socket when the shelf is empty.
Empty is the normal state of a shelf that is only being used to carry
pictures between tabs, so it must not be an error: the socket simply has
nothing on it and whatever is wired downstream is skipped.
"""
items, sel = self._entries(config)
if not items:
return (_ws.blocked(),)
try:
return (_ws.load_image(items[sel], int(resize) or 0),)
except Exception as exc: # a picture deleted since
print("[RedNode Shelf] %r could not be read: %s" % (items[sel], exc),
flush=True)
return (_ws.blocked(),)
NODE_CLASS_MAPPINGS = {"RedNodeShelf": RedNodeShelf}
NODE_DISPLAY_NAME_MAPPINGS = {"RedNodeShelf": "RedNode Shelf"}