Repository navigation
Expand file tree
/
Copy pathwardrobe_node.py
More file actions
231 lines (209 loc) · 9.35 KB
/
Copy pathwardrobe_node.py
File metadata and controls
231 lines (209 loc) · 9.35 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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
"""PerfectLab Wardrobe: the looks composer.
Holds the looks of one shoot as tabs. A look is built from separate slots --
top, bottom, footwear, accessories, a free detail line -- not one baked
phrase, so every piece stays adjustable on its own (and RND-able from the
Shot Series in a later step). The output is one clothing paragraph per look;
the Shot Series walks them in blocks and restarts the framing walk per look,
so each outfit is shot wide and close.
A look replaces the whole outfit -- nothing of one look survives into the
next, which keeps costume changes clean on a shared face.
"""
import json
import re
TOPS = [
"a shiny oversized cropped puffer jacket worn open",
"a tiny baby tee",
"an oversized blazer",
"a crop top",
"an oversized white crop hoodie",
"a vintage oversized jacket",
"a cropped tank top",
"an open flannel shirt",
"a turtleneck",
"a cozy oversized sweater",
"a biker jacket over a band tee",
"a track jacket with piping details",
"a slip top",
"a sheer blouse",
"a preppy cardigan",
"a trench coat",
"a bustier top",
"an oversized varsity jacket",
"a mesh long-sleeve top",
"a wrap blouse",
"a faux-fur hooded jacket",
"a cropped button-down shirt",
"a thermal long-sleeve top",
"a mock-neck top",
"a crop top with a chest cutout",
"a boxy cropped blazer",
# new families -- swim, sport, sleep, evening
"a bomber jacket", "a denim jacket", "a plain white tee", "an off-shoulder top",
"a one-piece swimsuit", "a sports bra top", "a silk pyjama set",
"an evening gown", "a tailored suit",
# everyday and statement tops
"a ribbed tank top", "an oversized plain hoodie", "a pinstripe waistcoat",
"a halter neck top", "a cable-knit turtleneck sweater", "a satin camisole"]
BOTTOMS = [
"low-rise baggy sweatpants",
"a short pleated skirt",
"cycling shorts",
"a short sundress with tiny flowers",
"baggy cargo pants",
"a pleated mini skirt",
"sheer tights",
"distressed skinny jeans",
"loose track pants",
"high-waisted wide-leg trousers",
"a pleated tennis skirt",
"a satin midi skirt",
"distressed denim shorts",
"a maxi skirt",
"a pencil skirt",
"cargo shorts",
"high-waisted flared yoga pants",
"flared jeans",
"low-slung black trousers",
"leggings", "leather pants", "a denim mini skirt", "bermuda shorts",
"swim shorts", "running shorts", "a cocktail dress",
# everyday bottoms
"a leather midi skirt", "a tennis skort", "wide-leg linen trousers",
"a frayed denim mini skirt", "utility cargo joggers", "high-rise skinny jeans"]
FOOTWEAR = [
"chunky sneakers", "slim stiletto heels", "classic pumps",
"tall boots", "sneakers", "lug-sole combat boots",
"platform loafers", "strappy sandals", "knee-high riding boots",
"retro skate sneakers", "black ballet flats", "worn cowboy boots",
"mary janes", "kitten heels", "hiking boots", "pool slides",
"chunky platform moon boots", "fur leg warmers over platform boots",
"loafers", "Chelsea boots", "platform sneakers", "mules",
"slingback heels", "chunky rain boots", "men's leather derby shoes",
"espadrilles", "square-toe boots", "glitter party heels"]
COLORS = ["jet black", "off-white", "charcoal gray", "glossy red", "chrome silver",
"champagne gold", "pastel pink", "warm beige", "navy blue", "olive green",
"steel blue", "burgundy", "mustard yellow", "emerald green",
"bright bubblegum pink", "crisp white", "light wash denim"]
ACCESSORIES = ["thin choker", "layered necklaces", "hoop earrings",
"stacked thin bracelets", "tinted sunglasses", "small shoulder bag",
"belt bag", "chunky watch", "a single strand necklace",
"a wide belt", "a neck scarf", "long drop earrings",
"fingerless gloves", "a hair clip", "an anklet",
"aviator sunglasses", "a tote bag", "a chunky chain necklace",
"a cuff bracelet", "a charm bracelet",
"pearl drop earrings", "a glasses chain", "a waist chain",
"a chunky metal cuff",
"an oversized wool scarf", "fuzzy white earmuffs",
"a slim necktie", "a leather garter strap",
"a silk hair ribbon", "a statement necklace", "a pearl choker",
"an ear cuff", "a chain belt", "a vintage brooch",
"cat-eye glasses", "a wrist corsage"]
LINGERIE = [
"a lace bralette and briefs set", "a silk slip nightgown",
"a satin pyjama short set", "a lace bodysuit",
"a strappy lace bralette", "a silk robe over a lace set",
"a mesh babydoll set", "a high-waisted lace briefs set",
"a velvet lingerie set", "a sporty bralette set",
]
NONE_PLACEHOLDERS = {"— none —", "- none -", "none"}
def compose_look(look):
"""One clothing paragraph out of slotted look dict.
Slots: top, top_color, outerwear, bottom, bottom_color, footwear,
accessories, details, lingerie. A literal "none" placeholder in any
slot reads as an empty slot. Lingerie is the garment when top and
bottom are empty -- covered by them otherwise."""
slot = lambda key: "" if (look.get(key) or "").strip().lower() in NONE_PLACEHOLDERS \
else (look.get(key) or "").strip()
top = slot("top")
top_color = slot("top_color")
top_material = slot("top_material")
outer = slot("outerwear")
bottom = slot("bottom")
bottom_color = slot("bottom_color")
bottom_material = slot("bottom_material")
headwear = slot("headwear")
headwear_color = slot("headwear_color")
acc_color = slot("acc_color")
shoes = slot("footwear")
lingerie = slot("lingerie")
lingerie_color = slot("lingerie_color")
lingerie_material = slot("lingerie_material")
acc = look.get("accessories") or []
if isinstance(acc, str):
acc = [acc] if acc.strip() else []
acc = [a.strip() for a in acc if a.strip() and a.strip().lower() not in NONE_PLACEHOLDERS]
# a look may be footwear- or headwear- or accessories-only (a shoes
# catalog tile, a hat close-up) -- the tail carries those on its own
if not top and not bottom and not shoes and not headwear and not acc and not lingerie:
return ""
piece = lambda name, color, material: " ".join(
p for p in ((material + " " + name[2:].strip()) if material and name[:2].lower() in ("a ", "an") and name.lower().startswith(("a ", "an "))
else (material + " " + name) if material else name, ) if p
) + (f" in {color}" if color else "")
if top or bottom:
if top and bottom:
body = f"wearing {piece(top, top_color, top_material)} with {piece(bottom, bottom_color, bottom_material)}"
elif top:
body = f"wearing {piece(top, top_color, top_material)}"
else:
body = f"wearing {piece(bottom, bottom_color, bottom_material)}"
if outer:
body += f", layered with {outer}"
elif lingerie:
# the set is the whole outfit
body = f"wearing {piece(lingerie, lingerie_color, lingerie_material)}"
else:
body = ""
tail = []
if shoes:
tail.append(piece(shoes, slot("footwear_color"), slot("footwear_material")))
if acc:
if acc_color:
acc = [f"{a} in {acc_color}" for a in acc]
tail.append(" and ".join(acc))
if headwear:
if headwear_color:
m = re.match(r"^(a|an|the)\s+(.*)$", headwear, re.I)
hw = f"{m.group(1)} {headwear_color} {m.group(2)}" if m else f"{headwear_color} {headwear}"
else:
hw = headwear
tail.append(f"wearing {hw}")
details = slot("details")
if details:
tail.append(details)
return ", ".join([p for p in [body] + tail if p])
def looks_from_state(state):
"""[{"name", "text"}] -- unnamed looks still count ("Look 2")."""
out = []
for i, look in enumerate(state.get("looks") or []):
if not isinstance(look, dict):
continue
text = compose_look(look)
if not text:
continue
name = (look.get("name") or "").strip() or f"Look {i + 1}"
out.append({"name": name, "text": text})
return out
class PerfectLabWardrobe:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"wardrobe_data": ("STRING", {"default": "{}", "multiline": False}),
},
}
RETURN_TYPES = ("STRING", "INT")
RETURN_NAMES = ("looks", "look_count")
OUTPUT_IS_LIST = (True, False)
FUNCTION = "build"
CATEGORY = "PerfectLab"
DESCRIPTION = "Holds the looks of one shoot as tabs. A look is assembled from separate slots -- top and its color, outerwear, bottom and its color, footwear, accessories, a free detail -- so every piece stays editable on its own. Wire 'looks' into the Shot Series: looks walk in blocks (or mixed) and the framing walk restarts per look, so each outfit is shot wide and close."
def build(self, wardrobe_data=None):
try:
state = json.loads(wardrobe_data) if wardrobe_data else {}
except (ValueError, TypeError):
state = {}
looks = looks_from_state(state)
texts = [l["text"] for l in looks] or [""]
return (texts, max(1, len(looks)))
NODE_CLASS_MAPPINGS = {"PerfectLabWardrobe": PerfectLabWardrobe}
NODE_DISPLAY_NAME_MAPPINGS = {"PerfectLabWardrobe": "🧪 PerfectLab – Wardrobe"}