Spaces:
Running
Running
Pass actual 2D pattern pieces to 3D builder — 3D view now matches 2D patterns
Browse files
app.py
CHANGED
|
@@ -5,7 +5,7 @@ import json, os, re, traceback, copy
|
|
| 5 |
from typing import Dict, Optional, Tuple, List
|
| 6 |
import gradio as gr
|
| 7 |
from PIL import Image
|
| 8 |
-
from pattern_generator import generate_pattern_from_analysis
|
| 9 |
from garment_3d import create_3d_figure
|
| 10 |
|
| 11 |
GARMENT_ANALYSIS_PROMPT = """You are a professional fashion pattern maker. Analyze this garment image and extract precise sewing pattern parameters.
|
|
@@ -90,7 +90,6 @@ def _extract_json_from_text(text):
|
|
| 90 |
return None
|
| 91 |
|
| 92 |
def _call_vlm(messages, timeout=180):
|
| 93 |
-
"""Call a VLM model via HF Inference Providers."""
|
| 94 |
import requests, base64
|
| 95 |
from io import BytesIO
|
| 96 |
hf_token = os.environ.get("HF_TOKEN", "")
|
|
@@ -153,13 +152,24 @@ def get_default_analysis(garment_type="shirt"):
|
|
| 153 |
}
|
| 154 |
return defaults.get(garment_type, defaults["shirt"])
|
| 155 |
|
| 156 |
-
# Global state for current analysis
|
| 157 |
_current_analysis = {"data": None}
|
| 158 |
|
| 159 |
def _generate_all_outputs(analysis):
|
| 160 |
-
"""Generate 2D pattern, 3D figure, and summary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
pattern_image, summary = generate_pattern_from_analysis(analysis)
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
| 163 |
display = {k: v for k, v in analysis.items() if k != '_model_used'}
|
| 164 |
model_info = f"\n\n*AI: {analysis.get('_model_used', 'Default')}*" if analysis.get('_model_used') else ""
|
| 165 |
desc = analysis.get('description', 'No description')
|
|
@@ -229,7 +239,6 @@ def process_manual(gt,bust,waist,hip,shoulder,bodice,sleeve,skirt,pant,neck,flar
|
|
| 229 |
return None, None, f"Error: {e}", "{}", []
|
| 230 |
|
| 231 |
def chat_edit(message, history):
|
| 232 |
-
"""Chat-based pattern editing."""
|
| 233 |
if not message.strip():
|
| 234 |
return history, None, None, "Please enter an edit request.", "{}"
|
| 235 |
current = _current_analysis.get("data")
|
|
@@ -238,9 +247,7 @@ def chat_edit(message, history):
|
|
| 238 |
_current_analysis["data"] = current
|
| 239 |
current_clean = {k: v for k, v in current.items() if k != '_model_used'}
|
| 240 |
edit_prompt = EDIT_PROMPT_TEMPLATE.format(
|
| 241 |
-
current_json=json.dumps(current_clean, indent=2),
|
| 242 |
-
user_message=message
|
| 243 |
-
)
|
| 244 |
updated = None
|
| 245 |
hf_token = os.environ.get("HF_TOKEN", "")
|
| 246 |
if hf_token:
|
|
@@ -257,18 +264,15 @@ def chat_edit(message, history):
|
|
| 257 |
elif "short sleeve" in msg_lower or "shorter sleeve" in msg_lower:
|
| 258 |
updated['measurements']['sleeve_length'] = 25
|
| 259 |
if "no collar" in msg_lower or "remove collar" in msg_lower:
|
| 260 |
-
updated['features']['has_collar'] = False
|
| 261 |
-
updated['features']['collar_type'] = 'none'
|
| 262 |
if "add collar" in msg_lower:
|
| 263 |
-
updated['features']['has_collar'] = True
|
| 264 |
-
updated['features']['collar_type'] = 'standard'
|
| 265 |
if "add hood" in msg_lower or "hoodie" in msg_lower:
|
| 266 |
updated['features']['has_hood'] = True
|
| 267 |
if "no hood" in msg_lower or "remove hood" in msg_lower:
|
| 268 |
updated['features']['has_hood'] = False
|
| 269 |
if "add pocket" in msg_lower:
|
| 270 |
-
updated['features']['has_pockets'] = True
|
| 271 |
-
updated['features']['pocket_type'] = 'patch'
|
| 272 |
if "no pocket" in msg_lower or "remove pocket" in msg_lower:
|
| 273 |
updated['features']['has_pockets'] = False
|
| 274 |
if "oversized" in msg_lower or "loose" in msg_lower:
|
|
@@ -278,14 +282,10 @@ def chat_edit(message, history):
|
|
| 278 |
updated['features']['fit'] = 'fitted'
|
| 279 |
if "flare" in msg_lower or "a-line" in msg_lower:
|
| 280 |
updated['measurements']['flare'] = max(updated['measurements'].get('flare', 0), 8)
|
| 281 |
-
if "straight" in msg_lower:
|
| 282 |
-
|
| 283 |
-
if "
|
| 284 |
-
|
| 285 |
-
if "midi" in msg_lower:
|
| 286 |
-
updated['measurements']['skirt_length'] = 80
|
| 287 |
-
if "maxi" in msg_lower:
|
| 288 |
-
updated['measurements']['skirt_length'] = 110
|
| 289 |
updated['_model_used'] = 'Rule-based edit'
|
| 290 |
if 'garment_type' not in updated:
|
| 291 |
updated['garment_type'] = current.get('garment_type', 'shirt')
|
|
@@ -296,22 +296,14 @@ def chat_edit(message, history):
|
|
| 296 |
p2d, p3d, summary, j = None, None, f"Error: {e}", "{}"
|
| 297 |
ai_msg = f"Applied edit: {message}\n\n"
|
| 298 |
changes = []
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
old_f = current.get('features', {})
|
| 302 |
-
new_f = updated.get('features', {})
|
| 303 |
-
for k in set(list(old_m.keys()) + list(new_m.keys())):
|
| 304 |
-
ov, nv = old_m.get(k), new_m.get(k)
|
| 305 |
if ov != nv and ov is not None and nv is not None:
|
| 306 |
changes.append(f" {k}: {ov} -> {nv}")
|
| 307 |
-
for k in set(list(
|
| 308 |
-
ov, nv =
|
| 309 |
-
if ov != nv:
|
| 310 |
-
|
| 311 |
-
if changes:
|
| 312 |
-
ai_msg += "Changes:\n" + "\n".join(changes)
|
| 313 |
-
else:
|
| 314 |
-
ai_msg += "No changes detected."
|
| 315 |
history = history or []
|
| 316 |
history.append((message, ai_msg))
|
| 317 |
return history, p2d, p3d, summary, j
|
|
@@ -326,21 +318,14 @@ with gr.Blocks(title="Garment Pattern Studio") as demo:
|
|
| 326 |
gr.HTML("""
|
| 327 |
<div class="main-header">
|
| 328 |
<h1>🧵 Garment Pattern Studio</h1>
|
| 329 |
-
<p style="font-size: 1.1em; color: #555;">
|
| 330 |
-
Analyze garments, edit with chat, preview in 3D
|
| 331 |
-
</p>
|
| 332 |
</div>
|
| 333 |
<div class="info-box">
|
| 334 |
<b>Powered by:</b> Qwen 3.5 · Gemma 4 · Kimi K2.5 via
|
| 335 |
-
<a href="https://huggingface.co/docs/inference-providers"
|
| 336 |
-
|
|
| 337 |
-
|
| 338 |
-
<a href="https://arxiv.org/abs/2412.17811">ChatGarment</a> &
|
| 339 |
-
<a href="https://arxiv.org/abs/2602.20700">NGL-Prompter</a>
|
| 340 |
-
</div>
|
| 341 |
-
""")
|
| 342 |
|
| 343 |
-
# === Tab 1: From Image ===
|
| 344 |
with gr.Tab("From Image"):
|
| 345 |
with gr.Row():
|
| 346 |
with gr.Column(scale=1):
|
|
@@ -349,16 +334,12 @@ with gr.Blocks(title="Garment Pattern Studio") as demo:
|
|
| 349 |
analyze_btn = gr.Button("Analyze & Generate", variant="primary", size="lg")
|
| 350 |
with gr.Column(scale=2):
|
| 351 |
with gr.Row():
|
| 352 |
-
with gr.Column():
|
| 353 |
-
|
| 354 |
-
with gr.Column():
|
| 355 |
-
out_3d = gr.Plot(label="3D Garment Preview")
|
| 356 |
out_summary = gr.Markdown(label="Pattern Summary")
|
| 357 |
-
with gr.Accordion("Raw JSON", open=False):
|
| 358 |
-
out_json = gr.Code(language="json", label="Analysis")
|
| 359 |
analyze_btn.click(process_image, inputs=[input_image, garment_override], outputs=[out_pattern_2d, out_3d, out_summary, out_json])
|
| 360 |
|
| 361 |
-
# === Tab 2: From Text ===
|
| 362 |
with gr.Tab("From Text"):
|
| 363 |
with gr.Row():
|
| 364 |
with gr.Column(scale=1):
|
|
@@ -374,16 +355,12 @@ with gr.Blocks(title="Garment Pattern Studio") as demo:
|
|
| 374 |
], inputs=text_input)
|
| 375 |
with gr.Column(scale=2):
|
| 376 |
with gr.Row():
|
| 377 |
-
with gr.Column():
|
| 378 |
-
|
| 379 |
-
with gr.Column():
|
| 380 |
-
txt_3d = gr.Plot(label="3D Preview")
|
| 381 |
txt_summary = gr.Markdown()
|
| 382 |
-
with gr.Accordion("Raw JSON", open=False):
|
| 383 |
-
txt_json = gr.Code(language="json")
|
| 384 |
text_btn.click(process_text, inputs=[text_input], outputs=[txt_pattern_2d, txt_3d, txt_summary, txt_json])
|
| 385 |
|
| 386 |
-
# === Tab 3: Manual Parameters ===
|
| 387 |
with gr.Tab("Manual Parameters"):
|
| 388 |
with gr.Row():
|
| 389 |
with gr.Column(scale=1):
|
|
@@ -417,18 +394,14 @@ with gr.Blocks(title="Garment Pattern Studio") as demo:
|
|
| 417 |
manual_btn = gr.Button("Generate Pattern", variant="primary", size="lg")
|
| 418 |
with gr.Column(scale=2):
|
| 419 |
with gr.Row():
|
| 420 |
-
with gr.Column():
|
| 421 |
-
|
| 422 |
-
with gr.Column():
|
| 423 |
-
man_3d = gr.Plot(label="3D Preview")
|
| 424 |
man_summary = gr.Markdown()
|
| 425 |
-
with gr.Accordion("Raw JSON", open=False):
|
| 426 |
-
man_json = gr.Code(language="json")
|
| 427 |
manual_btn.click(process_manual, inputs=[m_type,m_bust,m_waist,m_hip,m_shoulder,m_bodice,m_sleeve,m_skirt,m_pant,m_neck,m_flare,m_collar,m_ctype,m_cuffs,m_pockets,m_hood,m_fit], outputs=[man_pattern_2d, man_3d, man_summary, man_json])
|
| 428 |
|
| 429 |
-
# === Tab 4: Chat & Edit ===
|
| 430 |
with gr.Tab("Chat & Edit"):
|
| 431 |
-
gr.Markdown("### Edit your pattern with natural language\nFirst generate a pattern
|
| 432 |
with gr.Row():
|
| 433 |
with gr.Column(scale=1):
|
| 434 |
chatbot = gr.Chatbot(label="Pattern Editor", height=400)
|
|
@@ -438,39 +411,21 @@ with gr.Blocks(title="Garment Pattern Studio") as demo:
|
|
| 438 |
chat_clear = gr.Button("Clear Chat")
|
| 439 |
with gr.Column(scale=2):
|
| 440 |
with gr.Row():
|
| 441 |
-
with gr.Column():
|
| 442 |
-
|
| 443 |
-
with gr.Column():
|
| 444 |
-
chat_3d = gr.Plot(label="Updated 3D Preview")
|
| 445 |
chat_summary = gr.Markdown()
|
| 446 |
-
with gr.Accordion("Updated JSON", open=False):
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
def clear_chat():
|
| 450 |
-
return [], None, None, "", "{}"
|
| 451 |
-
|
| 452 |
chat_send.click(chat_edit, inputs=[chat_input, chatbot], outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
|
| 453 |
chat_input.submit(chat_edit, inputs=[chat_input, chatbot], outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
|
| 454 |
chat_clear.click(clear_chat, outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
|
| 455 |
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
<
|
| 460 |
-
<
|
| 461 |
-
|
| 462 |
-
[<a href="https://arxiv.org/abs/2412.17811">Paper</a>]
|
| 463 |
-
[<a href="https://github.com/biansy000/ChatGarment">Code</a>]
|
| 464 |
-
[<a href="https://huggingface.co/datasets/sy000/ChatGarmentDataset">Dataset</a>]</li>
|
| 465 |
-
<li><b>NGL-Prompter</b> (2025) -- Training-free VLM pattern estimation
|
| 466 |
-
[<a href="https://arxiv.org/abs/2602.20700">Paper</a>]</li>
|
| 467 |
-
<li><b>SewFormer</b> (Liu et al., 2023) -- Two-level Transformer pattern reconstruction
|
| 468 |
-
[<a href="https://arxiv.org/abs/2311.04218">Paper</a>]</li>
|
| 469 |
-
<li><b>GarmentDiffusion</b> (2025) -- DiT-based multimodal pattern generation
|
| 470 |
-
[<a href="https://arxiv.org/abs/2504.21476">Paper</a>]</li>
|
| 471 |
-
</ul>
|
| 472 |
-
</div>
|
| 473 |
-
""")
|
| 474 |
|
| 475 |
if __name__ == "__main__":
|
| 476 |
demo.launch(server_name="0.0.0.0", server_port=7860, css=CSS, theme=gr.themes.Soft())
|
|
|
|
| 5 |
from typing import Dict, Optional, Tuple, List
|
| 6 |
import gradio as gr
|
| 7 |
from PIL import Image
|
| 8 |
+
from pattern_generator import generate_pattern_from_analysis, get_pattern_pieces
|
| 9 |
from garment_3d import create_3d_figure
|
| 10 |
|
| 11 |
GARMENT_ANALYSIS_PROMPT = """You are a professional fashion pattern maker. Analyze this garment image and extract precise sewing pattern parameters.
|
|
|
|
| 90 |
return None
|
| 91 |
|
| 92 |
def _call_vlm(messages, timeout=180):
|
|
|
|
| 93 |
import requests, base64
|
| 94 |
from io import BytesIO
|
| 95 |
hf_token = os.environ.get("HF_TOKEN", "")
|
|
|
|
| 152 |
}
|
| 153 |
return defaults.get(garment_type, defaults["shirt"])
|
| 154 |
|
|
|
|
| 155 |
_current_analysis = {"data": None}
|
| 156 |
|
| 157 |
def _generate_all_outputs(analysis):
|
| 158 |
+
"""Generate 2D pattern, 3D figure (from pattern pieces), and summary."""
|
| 159 |
+
garment_type = analysis.get('garment_type', 'shirt')
|
| 160 |
+
measurements = analysis.get('measurements', {})
|
| 161 |
+
features = analysis.get('features', {})
|
| 162 |
+
params = {**measurements, **features}
|
| 163 |
+
|
| 164 |
+
# Generate 2D pattern pieces
|
| 165 |
+
pattern_pieces = get_pattern_pieces(garment_type, params)
|
| 166 |
+
|
| 167 |
+
# Generate 2D pattern image and summary
|
| 168 |
pattern_image, summary = generate_pattern_from_analysis(analysis)
|
| 169 |
+
|
| 170 |
+
# Generate 3D figure FROM the actual pattern pieces
|
| 171 |
+
fig_3d = create_3d_figure(analysis, pattern_pieces=pattern_pieces)
|
| 172 |
+
|
| 173 |
display = {k: v for k, v in analysis.items() if k != '_model_used'}
|
| 174 |
model_info = f"\n\n*AI: {analysis.get('_model_used', 'Default')}*" if analysis.get('_model_used') else ""
|
| 175 |
desc = analysis.get('description', 'No description')
|
|
|
|
| 239 |
return None, None, f"Error: {e}", "{}", []
|
| 240 |
|
| 241 |
def chat_edit(message, history):
|
|
|
|
| 242 |
if not message.strip():
|
| 243 |
return history, None, None, "Please enter an edit request.", "{}"
|
| 244 |
current = _current_analysis.get("data")
|
|
|
|
| 247 |
_current_analysis["data"] = current
|
| 248 |
current_clean = {k: v for k, v in current.items() if k != '_model_used'}
|
| 249 |
edit_prompt = EDIT_PROMPT_TEMPLATE.format(
|
| 250 |
+
current_json=json.dumps(current_clean, indent=2), user_message=message)
|
|
|
|
|
|
|
| 251 |
updated = None
|
| 252 |
hf_token = os.environ.get("HF_TOKEN", "")
|
| 253 |
if hf_token:
|
|
|
|
| 264 |
elif "short sleeve" in msg_lower or "shorter sleeve" in msg_lower:
|
| 265 |
updated['measurements']['sleeve_length'] = 25
|
| 266 |
if "no collar" in msg_lower or "remove collar" in msg_lower:
|
| 267 |
+
updated['features']['has_collar'] = False; updated['features']['collar_type'] = 'none'
|
|
|
|
| 268 |
if "add collar" in msg_lower:
|
| 269 |
+
updated['features']['has_collar'] = True; updated['features']['collar_type'] = 'standard'
|
|
|
|
| 270 |
if "add hood" in msg_lower or "hoodie" in msg_lower:
|
| 271 |
updated['features']['has_hood'] = True
|
| 272 |
if "no hood" in msg_lower or "remove hood" in msg_lower:
|
| 273 |
updated['features']['has_hood'] = False
|
| 274 |
if "add pocket" in msg_lower:
|
| 275 |
+
updated['features']['has_pockets'] = True; updated['features']['pocket_type'] = 'patch'
|
|
|
|
| 276 |
if "no pocket" in msg_lower or "remove pocket" in msg_lower:
|
| 277 |
updated['features']['has_pockets'] = False
|
| 278 |
if "oversized" in msg_lower or "loose" in msg_lower:
|
|
|
|
| 282 |
updated['features']['fit'] = 'fitted'
|
| 283 |
if "flare" in msg_lower or "a-line" in msg_lower:
|
| 284 |
updated['measurements']['flare'] = max(updated['measurements'].get('flare', 0), 8)
|
| 285 |
+
if "straight" in msg_lower: updated['measurements']['flare'] = 0
|
| 286 |
+
if "mini" in msg_lower: updated['measurements']['skirt_length'] = 30
|
| 287 |
+
if "midi" in msg_lower: updated['measurements']['skirt_length'] = 80
|
| 288 |
+
if "maxi" in msg_lower: updated['measurements']['skirt_length'] = 110
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
updated['_model_used'] = 'Rule-based edit'
|
| 290 |
if 'garment_type' not in updated:
|
| 291 |
updated['garment_type'] = current.get('garment_type', 'shirt')
|
|
|
|
| 296 |
p2d, p3d, summary, j = None, None, f"Error: {e}", "{}"
|
| 297 |
ai_msg = f"Applied edit: {message}\n\n"
|
| 298 |
changes = []
|
| 299 |
+
for k in set(list(current.get('measurements',{}).keys()) + list(updated.get('measurements',{}).keys())):
|
| 300 |
+
ov, nv = current.get('measurements',{}).get(k), updated.get('measurements',{}).get(k)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
if ov != nv and ov is not None and nv is not None:
|
| 302 |
changes.append(f" {k}: {ov} -> {nv}")
|
| 303 |
+
for k in set(list(current.get('features',{}).keys()) + list(updated.get('features',{}).keys())):
|
| 304 |
+
ov, nv = current.get('features',{}).get(k), updated.get('features',{}).get(k)
|
| 305 |
+
if ov != nv: changes.append(f" {k}: {ov} -> {nv}")
|
| 306 |
+
ai_msg += ("Changes:\n" + "\n".join(changes)) if changes else "No changes detected."
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
history = history or []
|
| 308 |
history.append((message, ai_msg))
|
| 309 |
return history, p2d, p3d, summary, j
|
|
|
|
| 318 |
gr.HTML("""
|
| 319 |
<div class="main-header">
|
| 320 |
<h1>🧵 Garment Pattern Studio</h1>
|
| 321 |
+
<p style="font-size: 1.1em; color: #555;">Analyze garments, edit with chat, preview in 3D</p>
|
|
|
|
|
|
|
| 322 |
</div>
|
| 323 |
<div class="info-box">
|
| 324 |
<b>Powered by:</b> Qwen 3.5 · Gemma 4 · Kimi K2.5 via
|
| 325 |
+
<a href="https://huggingface.co/docs/inference-providers">HF Inference Providers</a>
|
| 326 |
+
| <b>3D view is built from the actual 2D pattern pieces</b>
|
| 327 |
+
</div>""")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
|
|
|
|
| 329 |
with gr.Tab("From Image"):
|
| 330 |
with gr.Row():
|
| 331 |
with gr.Column(scale=1):
|
|
|
|
| 334 |
analyze_btn = gr.Button("Analyze & Generate", variant="primary", size="lg")
|
| 335 |
with gr.Column(scale=2):
|
| 336 |
with gr.Row():
|
| 337 |
+
with gr.Column(): out_pattern_2d = gr.Image(label="2D Sewing Pattern", height=400)
|
| 338 |
+
with gr.Column(): out_3d = gr.Plot(label="3D Garment Preview")
|
|
|
|
|
|
|
| 339 |
out_summary = gr.Markdown(label="Pattern Summary")
|
| 340 |
+
with gr.Accordion("Raw JSON", open=False): out_json = gr.Code(language="json", label="Analysis")
|
|
|
|
| 341 |
analyze_btn.click(process_image, inputs=[input_image, garment_override], outputs=[out_pattern_2d, out_3d, out_summary, out_json])
|
| 342 |
|
|
|
|
| 343 |
with gr.Tab("From Text"):
|
| 344 |
with gr.Row():
|
| 345 |
with gr.Column(scale=1):
|
|
|
|
| 355 |
], inputs=text_input)
|
| 356 |
with gr.Column(scale=2):
|
| 357 |
with gr.Row():
|
| 358 |
+
with gr.Column(): txt_pattern_2d = gr.Image(label="2D Pattern", height=400)
|
| 359 |
+
with gr.Column(): txt_3d = gr.Plot(label="3D Preview")
|
|
|
|
|
|
|
| 360 |
txt_summary = gr.Markdown()
|
| 361 |
+
with gr.Accordion("Raw JSON", open=False): txt_json = gr.Code(language="json")
|
|
|
|
| 362 |
text_btn.click(process_text, inputs=[text_input], outputs=[txt_pattern_2d, txt_3d, txt_summary, txt_json])
|
| 363 |
|
|
|
|
| 364 |
with gr.Tab("Manual Parameters"):
|
| 365 |
with gr.Row():
|
| 366 |
with gr.Column(scale=1):
|
|
|
|
| 394 |
manual_btn = gr.Button("Generate Pattern", variant="primary", size="lg")
|
| 395 |
with gr.Column(scale=2):
|
| 396 |
with gr.Row():
|
| 397 |
+
with gr.Column(): man_pattern_2d = gr.Image(label="2D Pattern", height=400)
|
| 398 |
+
with gr.Column(): man_3d = gr.Plot(label="3D Preview")
|
|
|
|
|
|
|
| 399 |
man_summary = gr.Markdown()
|
| 400 |
+
with gr.Accordion("Raw JSON", open=False): man_json = gr.Code(language="json")
|
|
|
|
| 401 |
manual_btn.click(process_manual, inputs=[m_type,m_bust,m_waist,m_hip,m_shoulder,m_bodice,m_sleeve,m_skirt,m_pant,m_neck,m_flare,m_collar,m_ctype,m_cuffs,m_pockets,m_hood,m_fit], outputs=[man_pattern_2d, man_3d, man_summary, man_json])
|
| 402 |
|
|
|
|
| 403 |
with gr.Tab("Chat & Edit"):
|
| 404 |
+
gr.Markdown("### Edit your pattern with natural language\nFirst generate a pattern, then edit here. Both 2D and 3D update together.")
|
| 405 |
with gr.Row():
|
| 406 |
with gr.Column(scale=1):
|
| 407 |
chatbot = gr.Chatbot(label="Pattern Editor", height=400)
|
|
|
|
| 411 |
chat_clear = gr.Button("Clear Chat")
|
| 412 |
with gr.Column(scale=2):
|
| 413 |
with gr.Row():
|
| 414 |
+
with gr.Column(): chat_pattern_2d = gr.Image(label="Updated 2D Pattern", height=400)
|
| 415 |
+
with gr.Column(): chat_3d = gr.Plot(label="Updated 3D Preview")
|
|
|
|
|
|
|
| 416 |
chat_summary = gr.Markdown()
|
| 417 |
+
with gr.Accordion("Updated JSON", open=False): chat_json = gr.Code(language="json")
|
| 418 |
+
def clear_chat(): return [], None, None, "", "{}"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
chat_send.click(chat_edit, inputs=[chat_input, chatbot], outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
|
| 420 |
chat_input.submit(chat_edit, inputs=[chat_input, chatbot], outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
|
| 421 |
chat_clear.click(clear_chat, outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
|
| 422 |
|
| 423 |
+
gr.HTML("""<div class="ref-box" style="margin-top: 20px;"><h4>Research References</h4><ul>
|
| 424 |
+
<li><b>ChatGarment</b> (2024) [<a href="https://arxiv.org/abs/2412.17811">Paper</a>] [<a href="https://huggingface.co/datasets/sy000/ChatGarmentDataset">Dataset</a>]</li>
|
| 425 |
+
<li><b>NGL-Prompter</b> (2025) [<a href="https://arxiv.org/abs/2602.20700">Paper</a>]</li>
|
| 426 |
+
<li><b>SewFormer</b> (2023) [<a href="https://arxiv.org/abs/2311.04218">Paper</a>]</li>
|
| 427 |
+
<li><b>GarmentDiffusion</b> (2025) [<a href="https://arxiv.org/abs/2504.21476">Paper</a>]</li>
|
| 428 |
+
</ul></div>""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
|
| 430 |
if __name__ == "__main__":
|
| 431 |
demo.launch(server_name="0.0.0.0", server_port=7860, css=CSS, theme=gr.themes.Soft())
|