Robledo Gularte Gonçalves commited on
Commit
63b30ec
·
1 Parent(s): ee55779
Files changed (1) hide show
  1. app.py +714 -44
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
- HEADER = """
50
- # <img src="https://compass.uol/content/dam/aircompanycompass/header/logo-desktop.png" alt="Compass.UOL">
51
-
52
- # Compass.UOL | Nestlé| Image to 3D | Proof of Concept
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
- with gr.Blocks(title="Nestlé | Proof of Concept") as demo:
378
- gr.Markdown(HEADER)
 
 
379
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  with gr.Row():
381
- with gr.Column():
382
- with gr.Row():
383
- image_prompts = gr.Image(label="Input Image", type="filepath")
384
- seg_image = gr.Image(
385
- label="Segmentation Result", type="pil", format="png", interactive=False
 
 
 
 
 
 
 
 
 
 
386
  )
387
-
388
- with gr.Accordion("Generation Settings", open=True):
 
 
 
 
 
 
 
 
389
  text_prompt = gr.Textbox(label="Prompt", placeholder="Enter your prompt", value="high quality")
390
- seed = gr.Slider(
391
- label="Seed",
392
- minimum=0,
393
- maximum=MAX_SEED,
394
- step=0,
395
- value=0
396
- )
397
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
 
 
 
 
 
 
398
  num_inference_steps = gr.Slider(
399
- label="Number of inference steps",
400
  minimum=8,
401
  maximum=50,
402
  step=1,
403
  value=50,
 
404
  )
 
405
  guidance_scale = gr.Slider(
406
- label="CFG scale",
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(label="Simplify Mesh", value=True)
415
- target_face_num = gr.Slider(maximum=1000000, minimum=10000, value=DEFAULT_FACE_NUMBER, label="Target Face Number")
 
 
 
 
 
 
 
 
 
 
416
 
417
- gen_button = gr.Button("Generate Shape", variant="primary")
418
- gen_texture_button = gr.Button("Apply Texture", interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
 
420
- with gr.Column():
421
- model_output = gr.Model3D(label="Generated GLB", interactive=False)
422
- textured_model_output = gr.Model3D(label="Textured GLB", interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(lambda: gr.Button(interactive=True), outputs=[gen_texture_button])
 
 
 
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
- demo.launch()
 
 
 
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)