Spaces:
Paused
Paused
Robledo Gularte Gonçalves
commited on
Commit
·
63b30ec
1
Parent(s):
ee55779
new front
Browse files
app.py
CHANGED
@@ -46,22 +46,10 @@ sys.path.append(os.path.join(TRIPOSG_CODE_DIR, "scripts"))
|
|
46 |
sys.path.append(MV_ADAPTER_CODE_DIR)
|
47 |
sys.path.append(os.path.join(MV_ADAPTER_CODE_DIR, "scripts"))
|
48 |
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
## State-of-the-art 3D Generation Using Large-Scale Rectified Flow Transformers
|
55 |
-
|
56 |
-
## 📋 Quick Start Guide:
|
57 |
-
1. **Upload an image** (single object works best)
|
58 |
-
2. Click **Generate Shape** to create the 3D mesh
|
59 |
-
3. Click **Apply Texture** to add textures
|
60 |
-
4. Use **Download GLB** to save the 3D model
|
61 |
-
5. Adjust parameters under **Generation Settings** for fine-tuning
|
62 |
-
|
63 |
-
Best results come from clean, well-lit images with clear subject isolation.
|
64 |
-
"""
|
65 |
|
66 |
# # triposg
|
67 |
from image_process import prepare_image
|
@@ -373,55 +361,720 @@ def run_texture(image: Image, mesh_path: str, seed: int, text_prompt: str, req:
|
|
373 |
|
374 |
return textured_glb_path
|
375 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
376 |
|
377 |
-
|
378 |
-
|
|
|
|
|
379 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
380 |
with gr.Row():
|
381 |
-
with gr.Column():
|
382 |
-
with gr.
|
383 |
-
|
384 |
-
|
385 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
386 |
)
|
387 |
-
|
388 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
389 |
text_prompt = gr.Textbox(label="Prompt", placeholder="Enter your prompt", value="high quality")
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
398 |
num_inference_steps = gr.Slider(
|
399 |
-
label="
|
400 |
minimum=8,
|
401 |
maximum=50,
|
402 |
step=1,
|
403 |
value=50,
|
|
|
404 |
)
|
|
|
405 |
guidance_scale = gr.Slider(
|
406 |
-
label="
|
407 |
minimum=0.0,
|
408 |
maximum=20.0,
|
409 |
step=0.1,
|
410 |
value=7.0,
|
|
|
411 |
)
|
412 |
|
413 |
with gr.Row():
|
414 |
-
reduce_face = gr.Checkbox(
|
415 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
416 |
|
417 |
-
|
418 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
419 |
|
420 |
-
|
421 |
-
|
422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
423 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
424 |
gen_button.click(
|
|
|
|
|
|
|
425 |
run_segmentation,
|
426 |
inputs=[image_prompts],
|
427 |
outputs=[seg_image]
|
@@ -440,15 +1093,32 @@ with gr.Blocks(title="Nestlé | Proof of Concept") as demo:
|
|
440 |
target_face_num
|
441 |
],
|
442 |
outputs=[model_output]
|
443 |
-
).then(
|
|
|
|
|
|
|
444 |
|
445 |
gen_texture_button.click(
|
446 |
run_texture,
|
447 |
inputs=[image_prompts, model_output, seed, text_prompt],
|
448 |
outputs=[textured_model_output]
|
449 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
450 |
|
451 |
demo.load(start_session)
|
452 |
demo.unload(end_session)
|
453 |
|
454 |
-
|
|
|
|
|
|
46 |
sys.path.append(MV_ADAPTER_CODE_DIR)
|
47 |
sys.path.append(os.path.join(MV_ADAPTER_CODE_DIR, "scripts"))
|
48 |
|
49 |
+
# Custom styling constants
|
50 |
+
NESTLE_BLUE = "#0066b1"
|
51 |
+
NESTLE_BLUE_DARK = "#004a82"
|
52 |
+
ACCENT_COLOR = "#10b981"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
# # triposg
|
55 |
from image_process import prepare_image
|
|
|
361 |
|
362 |
return textured_glb_path
|
363 |
|
364 |
+
# Custom UI components
|
365 |
+
def create_header():
|
366 |
+
return f"""
|
367 |
+
<div class="card" style="background: linear-gradient(135deg, {NESTLE_BLUE} 0%, {NESTLE_BLUE_DARK} 100%); color: white; border: none;">
|
368 |
+
<div style="display: flex; align-items: center; gap: 20px;">
|
369 |
+
<div style="background: white; padding: 12px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);">
|
370 |
+
<img src="https://logodownload.org/wp-content/uploads/2016/11/nestle-logo-1.png"
|
371 |
+
alt="Nestlé Logo" style="height: 48px; width: auto;">
|
372 |
+
</div>
|
373 |
+
<div style="flex: 1;">
|
374 |
+
<h1 style="margin: 0; font-size: 2.5rem; font-weight: 700; letter-spacing: -0.025em;">
|
375 |
+
Nestlé 3D Generator
|
376 |
+
</h1>
|
377 |
+
<p style="margin: 0.5rem 0 0 0; opacity: 0.9; font-size: 1.1rem;">
|
378 |
+
Transform your product images into stunning 3D models with AI
|
379 |
+
</p>
|
380 |
+
</div>
|
381 |
+
<div class="badge primary">Beta v2.0</div>
|
382 |
+
</div>
|
383 |
+
</div>
|
384 |
+
"""
|
385 |
+
|
386 |
+
def create_tabs():
|
387 |
+
return """
|
388 |
+
<div class="tabs-container">
|
389 |
+
<div class="tabs-list">
|
390 |
+
<button class="tab-button active" onclick="switchTab('segmentation')">
|
391 |
+
🔍 Segmentation
|
392 |
+
</button>
|
393 |
+
<button class="tab-button" onclick="switchTab('model')">
|
394 |
+
🎨 3D Model
|
395 |
+
</button>
|
396 |
+
<button class="tab-button" onclick="switchTab('textured')">
|
397 |
+
✨ Textured Model
|
398 |
+
</button>
|
399 |
+
</div>
|
400 |
+
|
401 |
+
<div id="segmentation-tab" class="tab-content active">
|
402 |
+
<div style="text-align: center; color: #1e293b;">
|
403 |
+
<div style="font-size: 4rem; margin-bottom: 1rem;">📤</div>
|
404 |
+
<p>Upload an image to see segmentation results</p>
|
405 |
+
</div>
|
406 |
+
</div>
|
407 |
+
|
408 |
+
<div id="model-tab" class="tab-content">
|
409 |
+
<div style="text-align: center; color: #1e293b;">
|
410 |
+
<div style="font-size: 4rem; margin-bottom: 1rem;">🎯</div>
|
411 |
+
<p>3D model will appear here after generation</p>
|
412 |
+
</div>
|
413 |
+
</div>
|
414 |
+
|
415 |
+
<div id="textured-tab" class="tab-content">
|
416 |
+
<div style="text-align: center; color: #1e293b;">
|
417 |
+
<div style="font-size: 4rem; margin-bottom: 1rem;">🎨</div>
|
418 |
+
<p>Textured model will appear here</p>
|
419 |
+
</div>
|
420 |
+
</div>
|
421 |
+
</div>
|
422 |
+
"""
|
423 |
+
|
424 |
+
def create_progress_bar():
|
425 |
+
return """
|
426 |
+
<div class="progress-container" style="display: none;" id="progress-container">
|
427 |
+
<div class="progress-header">
|
428 |
+
<span>Generating 3D model...</span>
|
429 |
+
<span id="progress-text">0%</span>
|
430 |
+
</div>
|
431 |
+
<div class="progress-bar-container">
|
432 |
+
<div class="progress-bar" id="progress-bar"></div>
|
433 |
+
</div>
|
434 |
+
</div>
|
435 |
+
"""
|
436 |
+
|
437 |
+
# JavaScript
|
438 |
+
ADVANCED_JS = """
|
439 |
+
<script>
|
440 |
+
// React-like state management simulation
|
441 |
+
window.appState = {
|
442 |
+
currentTab: 'segmentation',
|
443 |
+
isGenerating: false,
|
444 |
+
progress: 0
|
445 |
+
};
|
446 |
+
|
447 |
+
// Tab switching functionality
|
448 |
+
function switchTab(tabName) {
|
449 |
+
window.appState.currentTab = tabName;
|
450 |
+
|
451 |
+
// Hide all tab contents
|
452 |
+
document.querySelectorAll('.tab-content').forEach(el => {
|
453 |
+
el.style.display = 'none';
|
454 |
+
});
|
455 |
+
|
456 |
+
// Show selected tab
|
457 |
+
const selectedTab = document.getElementById(tabName + '-tab');
|
458 |
+
if (selectedTab) {
|
459 |
+
selectedTab.style.display = 'block';
|
460 |
+
}
|
461 |
+
|
462 |
+
// Update tab buttons
|
463 |
+
document.querySelectorAll('.tab-button').forEach(btn => {
|
464 |
+
btn.classList.remove('active');
|
465 |
+
});
|
466 |
+
|
467 |
+
const activeBtn = document.querySelector(`[onclick="switchTab('${tabName}')"]`);
|
468 |
+
if (activeBtn) {
|
469 |
+
activeBtn.classList.add('active');
|
470 |
+
}
|
471 |
+
}
|
472 |
+
|
473 |
+
// Progress simulation
|
474 |
+
function simulateProgress() {
|
475 |
+
window.appState.isGenerating = true;
|
476 |
+
window.appState.progress = 0;
|
477 |
+
|
478 |
+
const progressBar = document.getElementById('progress-bar');
|
479 |
+
const progressText = document.getElementById('progress-text');
|
480 |
+
|
481 |
+
const interval = setInterval(() => {
|
482 |
+
window.appState.progress += 10;
|
483 |
+
|
484 |
+
if (progressBar) {
|
485 |
+
progressBar.style.width = window.appState.progress + '%';
|
486 |
+
}
|
487 |
+
|
488 |
+
if (progressText) {
|
489 |
+
progressText.textContent = window.appState.progress + '%';
|
490 |
+
}
|
491 |
+
|
492 |
+
if (window.appState.progress >= 100) {
|
493 |
+
clearInterval(interval);
|
494 |
+
window.appState.isGenerating = false;
|
495 |
+
}
|
496 |
+
}, 300);
|
497 |
+
}
|
498 |
+
|
499 |
+
// Drag and drop simulation
|
500 |
+
function setupDragDrop() {
|
501 |
+
const uploadArea = document.querySelector('.upload-area');
|
502 |
+
if (uploadArea) {
|
503 |
+
uploadArea.addEventListener('dragover', (e) => {
|
504 |
+
e.preventDefault();
|
505 |
+
uploadArea.classList.add('drag-over');
|
506 |
+
});
|
507 |
+
|
508 |
+
uploadArea.addEventListener('dragleave', () => {
|
509 |
+
uploadArea.classList.remove('drag-over');
|
510 |
+
});
|
511 |
+
|
512 |
+
uploadArea.addEventListener('drop', (e) => {
|
513 |
+
e.preventDefault();
|
514 |
+
uploadArea.classList.remove('drag-over');
|
515 |
+
// Handle file drop
|
516 |
+
});
|
517 |
+
}
|
518 |
+
}
|
519 |
+
|
520 |
+
// Initialize when DOM is ready
|
521 |
+
document.addEventListener('DOMContentLoaded', function() {
|
522 |
+
setupDragDrop();
|
523 |
+
switchTab('segmentation');
|
524 |
+
});
|
525 |
+
</script>
|
526 |
+
"""
|
527 |
+
|
528 |
+
# CSS
|
529 |
+
ADVANCED_CSS = f"""
|
530 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
531 |
+
|
532 |
+
:root {{
|
533 |
+
--nestle-blue: {NESTLE_BLUE};
|
534 |
+
--nestle-blue-dark: {NESTLE_BLUE_DARK};
|
535 |
+
--accent: {ACCENT_COLOR};
|
536 |
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
|
537 |
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
|
538 |
+
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.1);
|
539 |
+
--border-radius: 12px;
|
540 |
+
}}
|
541 |
+
|
542 |
+
* {{
|
543 |
+
font-family: 'Inter', sans-serif !important;
|
544 |
+
}}
|
545 |
+
|
546 |
+
body, .gradio-container {{
|
547 |
+
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%) !important;
|
548 |
+
margin: 0 !important;
|
549 |
+
padding: 0 !important;
|
550 |
+
min-height: 100vh !important;
|
551 |
+
color: #ffffff !important;
|
552 |
+
font-size: 1rem !important;
|
553 |
+
}}
|
554 |
+
|
555 |
+
/* AGGRESSIVE TEXT COLOR FIXES - Higher specificity */
|
556 |
+
.gradio-container *,
|
557 |
+
.gradio-container div,
|
558 |
+
.gradio-container span,
|
559 |
+
.gradio-container p,
|
560 |
+
.gradio-container label,
|
561 |
+
.gradio-container h1,
|
562 |
+
.gradio-container h2,
|
563 |
+
.gradio-container h3,
|
564 |
+
.gradio-container h4,
|
565 |
+
.gradio-container h5,
|
566 |
+
.gradio-container h6 {{
|
567 |
+
color: #ffffff !important;
|
568 |
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
|
569 |
+
}}
|
570 |
+
|
571 |
+
/* Force white text on all Gradio components */
|
572 |
+
.gr-group *,
|
573 |
+
.gr-form *,
|
574 |
+
.gr-block *,
|
575 |
+
.gr-box *,
|
576 |
+
div[class*="gr-"] *,
|
577 |
+
div[class*="svelte-"] *,
|
578 |
+
span[class*="svelte-"] *,
|
579 |
+
label[class*="svelte-"] *,
|
580 |
+
p[class*="svelte-"] * {{
|
581 |
+
color: #ffffff !important;
|
582 |
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
|
583 |
+
font-weight: 600 !important;
|
584 |
+
}}
|
585 |
+
|
586 |
+
/* Specific targeting for card descriptions and titles */
|
587 |
+
.card-description,
|
588 |
+
.card-title,
|
589 |
+
div.card-description,
|
590 |
+
div.card-title,
|
591 |
+
p.card-description,
|
592 |
+
h3.card-title {{
|
593 |
+
color: #ffffff !important;
|
594 |
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
|
595 |
+
font-weight: 700 !important;
|
596 |
+
background: rgba(0, 0, 0, 0.3) !important;
|
597 |
+
padding: 4px 8px !important;
|
598 |
+
border-radius: 6px !important;
|
599 |
+
margin: 0.5rem 0 !important;
|
600 |
+
display: inline-block !important;
|
601 |
+
}}
|
602 |
+
|
603 |
+
/* Card Components */
|
604 |
+
.card {{
|
605 |
+
background: white;
|
606 |
+
border: 1px solid #e2e8f0;
|
607 |
+
border-radius: var(--border-radius);
|
608 |
+
box-shadow: var(--shadow-md);
|
609 |
+
padding: 1.5rem;
|
610 |
+
transition: all 0.2s ease;
|
611 |
+
margin-bottom: 1rem;
|
612 |
+
}}
|
613 |
+
|
614 |
+
.card:hover {{
|
615 |
+
box-shadow: var(--shadow-lg);
|
616 |
+
transform: translateY(-2px);
|
617 |
+
}}
|
618 |
+
|
619 |
+
.card-header {{
|
620 |
+
margin-bottom: 1rem;
|
621 |
+
padding-bottom: 1rem;
|
622 |
+
border-bottom: 1px solid #e2e8f0;
|
623 |
+
}}
|
624 |
+
|
625 |
+
/* Tabs */
|
626 |
+
.tabs-container {{
|
627 |
+
background: white;
|
628 |
+
border-radius: var(--border-radius);
|
629 |
+
box-shadow: var(--shadow-md);
|
630 |
+
overflow: hidden;
|
631 |
+
}}
|
632 |
+
|
633 |
+
.tabs-list {{
|
634 |
+
display: flex;
|
635 |
+
background: #f8fafc;
|
636 |
+
border-bottom: 1px solid #e2e8f0;
|
637 |
+
}}
|
638 |
+
|
639 |
+
.tab-button {{
|
640 |
+
flex: 1;
|
641 |
+
padding: 1rem;
|
642 |
+
background: none;
|
643 |
+
border: none;
|
644 |
+
cursor: pointer;
|
645 |
+
font-weight: 600;
|
646 |
+
color: #334155 !important;
|
647 |
+
font-size: 1rem;
|
648 |
+
transition: all 0.2s ease;
|
649 |
+
position: relative;
|
650 |
+
}}
|
651 |
+
|
652 |
+
.tab-button:hover {{
|
653 |
+
background: #f1f5f9;
|
654 |
+
color: #1e293b !important;
|
655 |
+
}}
|
656 |
+
|
657 |
+
.tab-button.active {{
|
658 |
+
color: var(--nestle-blue) !important;
|
659 |
+
background: white;
|
660 |
+
font-weight: 800;
|
661 |
+
}}
|
662 |
+
|
663 |
+
.tab-button.active::after {{
|
664 |
+
content: '';
|
665 |
+
position: absolute;
|
666 |
+
bottom: 0;
|
667 |
+
left: 0;
|
668 |
+
right: 0;
|
669 |
+
height: 2px;
|
670 |
+
background: var(--nestle-blue);
|
671 |
+
}}
|
672 |
+
|
673 |
+
.tab-content {{
|
674 |
+
padding: 2rem;
|
675 |
+
min-height: 400px;
|
676 |
+
display: none;
|
677 |
+
}}
|
678 |
+
|
679 |
+
.tab-content.active {{
|
680 |
+
display: block;
|
681 |
+
}}
|
682 |
+
|
683 |
+
.tab-content * {{
|
684 |
+
color: #1e293b !important;
|
685 |
+
text-shadow: none !important;
|
686 |
+
}}
|
687 |
+
|
688 |
+
/* Progress Component */
|
689 |
+
.progress-container {{
|
690 |
+
margin: 1rem 0;
|
691 |
+
padding: 1rem;
|
692 |
+
background: #f8fafc;
|
693 |
+
border-radius: var(--border-radius);
|
694 |
+
border: 1px solid #e2e8f0;
|
695 |
+
}}
|
696 |
+
|
697 |
+
.progress-header {{
|
698 |
+
display: flex;
|
699 |
+
justify-content: space-between;
|
700 |
+
margin-bottom: 0.5rem;
|
701 |
+
font-size: 1rem;
|
702 |
+
color: #334155 !important;
|
703 |
+
font-weight: 600;
|
704 |
+
}}
|
705 |
+
|
706 |
+
.progress-bar-container {{
|
707 |
+
width: 100%;
|
708 |
+
height: 8px;
|
709 |
+
background: #e2e8f0;
|
710 |
+
border-radius: 4px;
|
711 |
+
overflow: hidden;
|
712 |
+
}}
|
713 |
+
|
714 |
+
.progress-bar {{
|
715 |
+
height: 100%;
|
716 |
+
background: linear-gradient(90deg, var(--nestle-blue) 0%, var(--accent) 100%);
|
717 |
+
width: 0%;
|
718 |
+
transition: width 0.3s ease;
|
719 |
+
border-radius: 4px;
|
720 |
+
}}
|
721 |
+
|
722 |
+
/* Badge */
|
723 |
+
.badge {{
|
724 |
+
display: inline-flex;
|
725 |
+
align-items: center;
|
726 |
+
padding: 0.25rem 0.75rem;
|
727 |
+
background: #e2e8f0;
|
728 |
+
color: #1e293b !important;
|
729 |
+
border-radius: 9999px;
|
730 |
+
font-size: 0.85rem;
|
731 |
+
font-weight: 600;
|
732 |
+
}}
|
733 |
|
734 |
+
.badge.primary {{
|
735 |
+
background: var(--nestle-blue);
|
736 |
+
color: #fff !important;
|
737 |
+
}}
|
738 |
|
739 |
+
/* Button variants */
|
740 |
+
.btn, .btn-primary, .btn-secondary, .gr-button {{
|
741 |
+
display: inline-flex;
|
742 |
+
align-items: center;
|
743 |
+
justify-content: center;
|
744 |
+
gap: 0.5rem;
|
745 |
+
padding: 0.75rem 1.5rem;
|
746 |
+
border-radius: var(--border-radius);
|
747 |
+
font-weight: 700 !important;
|
748 |
+
font-size: 1rem !important;
|
749 |
+
border: none;
|
750 |
+
cursor: pointer;
|
751 |
+
transition: all 0.2s ease;
|
752 |
+
text-decoration: none;
|
753 |
+
letter-spacing: -0.01em;
|
754 |
+
}}
|
755 |
+
|
756 |
+
.btn-primary, .gr-button {{
|
757 |
+
background: linear-gradient(135deg, var(--nestle-blue) 0%, var(--nestle-blue-dark) 100%) !important;
|
758 |
+
color: white !important;
|
759 |
+
box-shadow: var(--shadow-sm) !important;
|
760 |
+
}}
|
761 |
+
|
762 |
+
.btn-primary:hover, .gr-button:hover {{
|
763 |
+
transform: translateY(-1px) !important;
|
764 |
+
box-shadow: var(--shadow-md) !important;
|
765 |
+
}}
|
766 |
+
|
767 |
+
.btn-secondary {{
|
768 |
+
background: white !important;
|
769 |
+
color: #374151 !important;
|
770 |
+
border: 1px solid #d1d5db !important;
|
771 |
+
}}
|
772 |
+
|
773 |
+
.btn-secondary:hover {{
|
774 |
+
background: #f9fafb !important;
|
775 |
+
}}
|
776 |
+
|
777 |
+
/* Enhanced Gradio component styling */
|
778 |
+
.gr-image, .gr-model3d {{
|
779 |
+
border: 2px solid #e2e8f0 !important;
|
780 |
+
border-radius: var(--border-radius) !important;
|
781 |
+
box-shadow: var(--shadow-sm) !important;
|
782 |
+
transition: all 0.2s ease !important;
|
783 |
+
}}
|
784 |
+
|
785 |
+
.gr-slider .noUi-connect {{
|
786 |
+
background: linear-gradient(90deg, var(--nestle-blue) 0%, var(--accent) 100%) !important;
|
787 |
+
}}
|
788 |
+
|
789 |
+
.gr-slider .noUi-handle {{
|
790 |
+
background: white !important;
|
791 |
+
border: 3px solid var(--nestle-blue) !important;
|
792 |
+
border-radius: 50% !important;
|
793 |
+
box-shadow: var(--shadow-md) !important;
|
794 |
+
}}
|
795 |
+
|
796 |
+
/* Responsive design */
|
797 |
+
@media (max-width: 768px) {{
|
798 |
+
.tabs-list {{
|
799 |
+
flex-direction: column;
|
800 |
+
}}
|
801 |
+
|
802 |
+
.card {{
|
803 |
+
padding: 1rem;
|
804 |
+
}}
|
805 |
+
}}
|
806 |
+
|
807 |
+
/* SUPER AGGRESSIVE TEXT FIXES */
|
808 |
+
/* Target every possible Gradio text element */
|
809 |
+
.gradio-container .gr-group .gr-form label,
|
810 |
+
.gradio-container .gr-group .gr-form span,
|
811 |
+
.gradio-container .gr-group .gr-form div,
|
812 |
+
.gradio-container .gr-group .gr-form p,
|
813 |
+
.gradio-container .gr-block label,
|
814 |
+
.gradio-container .gr-block span,
|
815 |
+
.gradio-container .gr-block div,
|
816 |
+
.gradio-container .gr-block p,
|
817 |
+
.gradio-container .gr-box label,
|
818 |
+
.gradio-container .gr-box span,
|
819 |
+
.gradio-container .gr-box div,
|
820 |
+
.gradio-container .gr-box p {{
|
821 |
+
color: #ffffff !important;
|
822 |
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
|
823 |
+
font-weight: 600 !important;
|
824 |
+
opacity: 1 !important;
|
825 |
+
}}
|
826 |
+
|
827 |
+
/* Target Svelte components specifically */
|
828 |
+
[class*="svelte-"] {{
|
829 |
+
color: #ffffff !important;
|
830 |
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
|
831 |
+
}}
|
832 |
+
|
833 |
+
/* Target slider labels and info text */
|
834 |
+
.gr-slider label,
|
835 |
+
.gr-slider .gr-text,
|
836 |
+
.gr-slider span,
|
837 |
+
.gr-checkbox label,
|
838 |
+
.gr-checkbox span {{
|
839 |
+
color: #ffffff !important;
|
840 |
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
|
841 |
+
font-weight: 600 !important;
|
842 |
+
}}
|
843 |
+
|
844 |
+
/* Target info text specifically */
|
845 |
+
.gr-info,
|
846 |
+
[class*="info"],
|
847 |
+
.info {{
|
848 |
+
color: #ffffff !important;
|
849 |
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
|
850 |
+
font-weight: 500 !important;
|
851 |
+
background: rgba(0, 0, 0, 0.2) !important;
|
852 |
+
padding: 2px 6px !important;
|
853 |
+
border-radius: 4px !important;
|
854 |
+
}}
|
855 |
+
|
856 |
+
/* Fix for image action icons */
|
857 |
+
.gr-image .image-button,
|
858 |
+
.gr-image button,
|
859 |
+
.gr-image .icon-button,
|
860 |
+
.gr-image [role="button"],
|
861 |
+
.gr-image .svelte-1pijsyv,
|
862 |
+
.gr-image .svelte-1pijsyv button {{
|
863 |
+
background: rgba(255, 255, 255, 0.95) !important;
|
864 |
+
border: 1px solid #e2e8f0 !important;
|
865 |
+
border-radius: 8px !important;
|
866 |
+
padding: 8px !important;
|
867 |
+
margin: 2px !important;
|
868 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
|
869 |
+
transition: all 0.2s ease !important;
|
870 |
+
color: #374151 !important;
|
871 |
+
font-size: 16px !important;
|
872 |
+
min-width: 36px !important;
|
873 |
+
min-height: 36px !important;
|
874 |
+
display: flex !important;
|
875 |
+
align-items: center !important;
|
876 |
+
justify-content: center !important;
|
877 |
+
}}
|
878 |
+
|
879 |
+
.gr-image .image-button:hover,
|
880 |
+
.gr-image button:hover,
|
881 |
+
.gr-image .icon-button:hover,
|
882 |
+
.gr-image [role="button"]:hover,
|
883 |
+
.gr-image .svelte-1pijsyv:hover,
|
884 |
+
.gr-image .svelte-1pijsyv button:hover {{
|
885 |
+
background: rgba(255, 255, 255, 1) !important;
|
886 |
+
transform: translateY(-1px) !important;
|
887 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) !important;
|
888 |
+
color: var(--nestle-blue) !important;
|
889 |
+
}}
|
890 |
+
|
891 |
+
/* Upload area text */
|
892 |
+
.gr-image .upload-text,
|
893 |
+
.gr-image .drag-text,
|
894 |
+
.gr-image .svelte-1ipelgc {{
|
895 |
+
color: #1e293b !important;
|
896 |
+
font-weight: 600 !important;
|
897 |
+
text-shadow: 0 0 4px white !important;
|
898 |
+
background: rgba(255, 255, 255, 0.9) !important;
|
899 |
+
padding: 8px 12px !important;
|
900 |
+
border-radius: 8px !important;
|
901 |
+
margin: 4px !important;
|
902 |
+
}}
|
903 |
+
|
904 |
+
/* Nuclear option - force all text to be white with shadow */
|
905 |
+
* {{
|
906 |
+
color: #ffffff !important;
|
907 |
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8) !important;
|
908 |
+
}}
|
909 |
+
|
910 |
+
/* But override for specific areas that should be dark */
|
911 |
+
.tabs-container *,
|
912 |
+
.tab-content *,
|
913 |
+
.badge *,
|
914 |
+
.btn *,
|
915 |
+
.gr-button *,
|
916 |
+
.upload-area *,
|
917 |
+
.gr-image .upload-text *,
|
918 |
+
.gr-image .drag-text *,
|
919 |
+
.gr-image .svelte-1ipelgc *,
|
920 |
+
.progress-container * {{
|
921 |
+
color: #1e293b !important;
|
922 |
+
text-shadow: 0 0 2px white !important;
|
923 |
+
}}
|
924 |
+
|
925 |
+
/* Header text should remain white */
|
926 |
+
.card[style*="linear-gradient"] *,
|
927 |
+
.card[style*="linear-gradient"] h1,
|
928 |
+
.card[style*="linear-gradient"] p {{
|
929 |
+
color: #ffffff !important;
|
930 |
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5) !important;
|
931 |
+
}}
|
932 |
+
"""
|
933 |
+
|
934 |
+
# interface
|
935 |
+
with gr.Blocks(
|
936 |
+
title="Nestlé 3D Generator",
|
937 |
+
css=ADVANCED_CSS,
|
938 |
+
head=ADVANCED_JS,
|
939 |
+
theme=gr.themes.Soft(
|
940 |
+
primary_hue="blue",
|
941 |
+
secondary_hue="slate",
|
942 |
+
neutral_hue="slate",
|
943 |
+
font=gr.themes.GoogleFont("Inter")
|
944 |
+
)
|
945 |
+
) as demo:
|
946 |
+
|
947 |
+
# Header
|
948 |
+
gr.HTML(create_header())
|
949 |
+
|
950 |
with gr.Row():
|
951 |
+
with gr.Column(scale=1):
|
952 |
+
with gr.Group():
|
953 |
+
gr.HTML("""
|
954 |
+
<div class="card-header">
|
955 |
+
<h3 class="card-title">📤 Product Image Upload</h3>
|
956 |
+
<p class="card-description">Upload a clear image of your Nestlé product</p>
|
957 |
+
</div>
|
958 |
+
""")
|
959 |
+
|
960 |
+
image_prompts = gr.Image(
|
961 |
+
label="",
|
962 |
+
type="filepath",
|
963 |
+
show_label=False,
|
964 |
+
height=350,
|
965 |
+
elem_classes=["upload-area"]
|
966 |
)
|
967 |
+
|
968 |
+
# Settings Card
|
969 |
+
with gr.Group():
|
970 |
+
gr.HTML("""
|
971 |
+
<div class="card-header">
|
972 |
+
<h3 class="card-title">⚙️ Generation Settings</h3>
|
973 |
+
<p class="card-description">Configure your 3D model generation</p>
|
974 |
+
</div>
|
975 |
+
""")
|
976 |
+
|
977 |
text_prompt = gr.Textbox(label="Prompt", placeholder="Enter your prompt", value="high quality")
|
978 |
+
|
979 |
+
with gr.Row():
|
980 |
+
randomize_seed = gr.Checkbox(
|
981 |
+
label="🎲 Randomize Seed",
|
982 |
+
value=True
|
983 |
+
)
|
984 |
+
seed = gr.Slider(
|
985 |
+
label="Seed Value",
|
986 |
+
minimum=0,
|
987 |
+
maximum=MAX_SEED,
|
988 |
+
step=1,
|
989 |
+
value=0
|
990 |
+
)
|
991 |
+
|
992 |
num_inference_steps = gr.Slider(
|
993 |
+
label="🔄 Inference Steps",
|
994 |
minimum=8,
|
995 |
maximum=50,
|
996 |
step=1,
|
997 |
value=50,
|
998 |
+
info="Higher values = better quality, slower generation"
|
999 |
)
|
1000 |
+
|
1001 |
guidance_scale = gr.Slider(
|
1002 |
+
label="🎯 Guidance Scale",
|
1003 |
minimum=0.0,
|
1004 |
maximum=20.0,
|
1005 |
step=0.1,
|
1006 |
value=7.0,
|
1007 |
+
info="Controls how closely the model follows the input"
|
1008 |
)
|
1009 |
|
1010 |
with gr.Row():
|
1011 |
+
reduce_face = gr.Checkbox(
|
1012 |
+
label="🔧 Optimize Mesh",
|
1013 |
+
value=True,
|
1014 |
+
info="Reduce polygon count for better performance"
|
1015 |
+
)
|
1016 |
+
target_face_num = gr.Slider(
|
1017 |
+
label="Target Faces",
|
1018 |
+
maximum=1_000_000,
|
1019 |
+
minimum=10_000,
|
1020 |
+
value=DEFAULT_FACE_NUMBER,
|
1021 |
+
step=1000
|
1022 |
+
)
|
1023 |
|
1024 |
+
with gr.Column(scale=2):
|
1025 |
+
gr.HTML("""
|
1026 |
+
<div class="card-header">
|
1027 |
+
<h3 class="card-title">3D Model Generation</h3>
|
1028 |
+
<p class="card-description">View your generated 3D models and apply textures</p>
|
1029 |
+
</div>
|
1030 |
+
""")
|
1031 |
+
|
1032 |
+
# CT React-like
|
1033 |
+
gr.HTML(create_tabs())
|
1034 |
+
|
1035 |
+
# PB
|
1036 |
+
gr.HTML(create_progress_bar())
|
1037 |
+
|
1038 |
+
# Hidden Gradio components for actual functionality
|
1039 |
+
with gr.Row(visible=False):
|
1040 |
+
seg_image = gr.Image(type="pil", format="png", interactive=False)
|
1041 |
+
model_output = gr.Model3D(interactive=False)
|
1042 |
+
textured_model_output = gr.Model3D(interactive=False)
|
1043 |
|
1044 |
+
# Action Buttons
|
1045 |
+
with gr.Row():
|
1046 |
+
gen_button = gr.Button(
|
1047 |
+
"🚀 Generate 3D Model",
|
1048 |
+
variant="primary",
|
1049 |
+
size="lg",
|
1050 |
+
elem_classes=["btn", "btn-primary"]
|
1051 |
+
)
|
1052 |
+
gen_texture_button = gr.Button(
|
1053 |
+
"🎨 Apply Texture",
|
1054 |
+
variant="secondary",
|
1055 |
+
size="lg",
|
1056 |
+
interactive=False,
|
1057 |
+
elem_classes=["btn", "btn-secondary"]
|
1058 |
+
)
|
1059 |
+
download_button = gr.Button(
|
1060 |
+
"💾 Download Model",
|
1061 |
+
variant="secondary",
|
1062 |
+
size="lg",
|
1063 |
+
elem_classes=["btn", "btn-secondary"]
|
1064 |
+
)
|
1065 |
|
1066 |
+
status_display = gr.HTML(
|
1067 |
+
"""<div style='text-align: center; padding: 1rem; color: #1e293b;'>
|
1068 |
+
<span style='display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #10b981; margin-right: 8px;'></span>
|
1069 |
+
Ready to generate your 3D model
|
1070 |
+
</div>"""
|
1071 |
+
)
|
1072 |
+
|
1073 |
+
# Event Handlers with JavaScript integration
|
1074 |
gen_button.click(
|
1075 |
+
fn=None,
|
1076 |
+
js="() => { simulateProgress(); document.getElementById('progress-container').style.display = 'block'; }",
|
1077 |
+
).then(
|
1078 |
run_segmentation,
|
1079 |
inputs=[image_prompts],
|
1080 |
outputs=[seg_image]
|
|
|
1093 |
target_face_num
|
1094 |
],
|
1095 |
outputs=[model_output]
|
1096 |
+
).then(
|
1097 |
+
fn=lambda: gr.Button(interactive=True),
|
1098 |
+
outputs=[gen_texture_button]
|
1099 |
+
)
|
1100 |
|
1101 |
gen_texture_button.click(
|
1102 |
run_texture,
|
1103 |
inputs=[image_prompts, model_output, seed, text_prompt],
|
1104 |
outputs=[textured_model_output]
|
1105 |
)
|
1106 |
+
|
1107 |
+
with gr.Row():
|
1108 |
+
examples = gr.Examples(
|
1109 |
+
examples=[
|
1110 |
+
f"./examples/{image}"
|
1111 |
+
for image in os.listdir(f"./examples/")
|
1112 |
+
],
|
1113 |
+
fn=run_full,
|
1114 |
+
inputs=[image_prompts],
|
1115 |
+
outputs=[seg_image, model_output, textured_model_output],
|
1116 |
+
cache_examples=False,
|
1117 |
+
)
|
1118 |
|
1119 |
demo.load(start_session)
|
1120 |
demo.unload(end_session)
|
1121 |
|
1122 |
+
|
1123 |
+
if __name__ == "__main__":
|
1124 |
+
demo.launch(share=False, show_error=True)
|