CVision-assitant commited on
Commit
f00ea7f
·
verified ·
1 Parent(s): 93a068f

Update modules/langchain_assistant/cvision_forge_v9.py

Browse files
modules/langchain_assistant/cvision_forge_v9.py CHANGED
@@ -13,68 +13,143 @@ import gradio as gr
13
  # EXTRACCIÓN SEGURA: Se elimina el token explícito para evitar bloqueos en el commit.
14
  # El token real debe ser configurado en la pestaña "Settings -> Variables and Secrets" de tu Space.
15
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
16
- SPACE_PROVEEDOR = "JeffreyXiang/TRELLIS"
17
- URL_TRELLIS_BASE = "https://trellis-community-trellis.hf.space/gradio_api"
 
 
 
 
 
 
 
 
 
18
 
19
  # ═════════════════════════════════════════════════════════════════════
20
  # SECCIÓN 2: NÚCLEO LOGÍSTICO Y PROCESAMIENTO MULTIMODAL (TRELLIS BACKEND)
21
  # 💡 Implementa el motor de inferencia oficial mediante hilos WebSockets.
 
 
22
  # ═════════════════════════════════════════════════════════════════════
23
- def ejecutar_forja_3d(imagen_generada_flux: str):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  """
25
- Toma la imagen nacida del motor FLUX y utiliza el cliente oficial de Gradio
26
- para conectarse al clúster de TRELLIS mediante WebSockets estables.
 
 
 
 
 
 
27
  Evita simular clics en el frontend para evadir el colapso por 'hidden tab'.
 
 
28
  """
29
- if not HF_TOKEN:
30
- print("[TRELLIS Error] No se detectó el 'HF_TOKEN' en las variables de entorno.")
31
- return "ERROR: Variable de entorno 'HF_TOKEN' faltante. Configúrala en los Secrets de tu Space."
32
-
33
  if not imagen_generada_flux or not str(imagen_generada_flux).strip():
34
  print("[TRELLIS Error] No se suministró una ruta de imagen válida.")
35
  return "ERROR: No se detectó ninguna imagen válida de FLUX para iniciar el esculpido 3D."
36
 
37
- try:
38
- from gradio_client import Client, handle_file
39
-
40
- print(f"[TRELLIS Engine] Conectando de forma segura con el clúster: {SPACE_PROVEEDOR}")
41
- # Inicialización del cliente encapsulado con paso de credenciales seguro
42
- client = Client(SPACE_PROVEEDOR, hf_token=HF_TOKEN)
43
-
44
- print(f"[TRELLIS Engine] Despachando asset estructural a la GPU remota... Origen: {imagen_generada_flux}")
45
-
46
- # Consumo del endpoint puro de inferencia remota
47
- resultado = client.predict(
48
- image=handle_file(imagen_generada_flux),
49
- inst_num_steps=12, # Muestreo estructural primario
50
- inst_cfg=7.5, # Guía de escala sintética
51
- preprocess_image=True, # Limpieza automática de la matriz 2D
52
- api_name="/image_to_3d" # Endpoint directo expuesto en el backend de TRELLIS
53
- )
54
-
55
- # El cliente descarga automáticamente el .glb en la caché local del sistema
56
- if resultado and isinstance(resultado, str) and resultado.endswith(".glb"):
57
- print(f"[TRELLIS Engine] ¡Malla .glb consolidada con éxito!: {resultado}")
58
  return resultado
59
-
60
- # Control de flujos secundarios si la estructura devuelve diccionarios mutados
61
- if isinstance(resultado, dict) and "name" in resultado:
62
- print(f"[TRELLIS Engine] Malla extraída desde nodo de diccionario: {resultado['name']}")
63
- return resultado["name"]
64
-
65
- # Desglose recursivo si la respuesta viene envuelta en colecciones
66
- if isinstance(resultado, list) and len(resultado) > 0:
67
- primer_nodo = resultado[0]
68
- if isinstance(primer_nodo, str) and primer_nodo.endswith(".glb"):
69
- return primer_nodo
70
- if isinstance(primer_nodo, dict) and "name" in primer_nodo:
71
- return primer_nodo["name"]
72
 
73
- raise Exception(f"El clúster remoto retornó un formato binario no reconocido: {str(resultado)}")
74
-
75
- except Exception as e:
76
- print(f"[TRELLIS Error] Falla en pipeline con Cliente Oficial: {e}")
77
- return f"ERROR_PIPELINE: {str(e)}"
78
 
79
  # ═════════════════════════════════════════════════════════════════════
80
  # SECCIÓN 3: COMPONENTE VISUAL Y RENDERIZADO INTERACTIVO (FRONTEND)
@@ -84,86 +159,93 @@ def obtener_html_visor(glb_url: str = None) -> str:
84
  """
85
  Construye de forma dinámica la interfaz del viewport interactivo
86
  utilizando Three.js para la manipulación libre de mallas.
 
 
87
  """
88
  if not glb_url or "ERROR" in str(glb_url) or "TIMEOUT" in str(glb_url):
89
  error_msg = glb_url if glb_url else "Esperando especificaciones en el panel de control..."
90
  return f"""
91
- <div style="width:100%; height:500px; background:#02060f; display:flex; flex-direction:column; justify-content:center; align-items:center; border:1px solid rgba(0,212,255,0.15); border-radius:8px; font-family:monospace; color:#00d4ff;">
92
- <h3 style="margin:0; letter-spacing: 2px; text-shadow: 0 0 10px rgba(0,212,255,0.5);">⚔️ CVISION FORGE LAB v9.0 ⚔️</h3>
 
 
 
 
93
  <p style="color:rgba(255,255,255,0.5); font-size:12px; margin-top:10px;">{error_msg}</p>
94
  </div>
95
  """
96
-
97
  return f"""
98
- <div id="canvas-3d-container" style="width:100%; height:500px; background:#02060f; border:1px solid #00d4ff; border-radius:8px; position:relative;">
 
99
  <script type="module">
100
  import * as THREE from 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.module.js';
101
  import {{ GLTFLoader }} from 'https://cdn.jsdelivr.net/npm/three@0.128.0/examples/jsm/loaders/GLTFLoader.js';
102
  import {{ OrbitControls }} from 'https://cdn.jsdelivr.net/npm/three@0.128.0/examples/jsm/controls/OrbitControls.js';
103
-
104
  const container = document.getElementById('canvas-3d-container');
105
  const scene = new THREE.Scene();
106
  scene.background = new THREE.Color(0x02060f);
107
-
108
  const camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 0.1, 100);
109
  camera.position.set(0, 1.2, 3);
110
-
111
  const renderer = new THREE.WebGLRenderer({{ antialias: true, alpha: false }});
112
  renderer.setSize(container.clientWidth, container.clientHeight);
113
  renderer.setPixelRatio(window.devicePixelRatio);
114
  renderer.shadowMap.enabled = true;
115
  container.appendChild(renderer.domElement);
116
-
117
  const controls = new OrbitControls(camera, renderer.domElement);
118
  controls.enableDamping = true;
119
  controls.dampingFactor = 0.05;
120
-
121
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
122
  scene.add(ambientLight);
123
-
124
  const dirLight1 = new THREE.DirectionalLight(0x00d4ff, 1.0);
125
  dirLight1.position.set(5, 5, 5);
126
  scene.add(dirLight1);
127
-
128
  const dirLight2 = new THREE.DirectionalLight(0xff00ff, 0.5);
129
  dirLight2.position.set(-5, 3, -5);
130
  scene.add(dirLight2);
131
-
132
  const loader = new GLTFLoader();
133
- const modelUrl = "{glb_url}";
134
-
135
  console.log("[CVision Visor] Intentando cargar geometría desde:", modelUrl);
136
-
137
  loader.load(modelUrl, (gltf) => {{
138
  const model = gltf.scene;
139
-
140
  // Centrado automático del modelo dentro del visor global
141
  const box = new THREE.Box3().setFromObject(model);
142
  const center = box.getCenter(new THREE.Vector3());
143
  const size = box.getSize(new THREE.Vector3());
144
-
145
  model.position.sub(center);
146
  scene.add(model);
147
-
148
  // Ajuste dinámico de cámara según el volumen del objeto forjado
149
  const maxDim = Math.max(size.x, size.y, size.z);
150
  camera.position.z = maxDim * 1.8;
151
  camera.lookAt(0, 0, 0);
152
-
153
  console.log("[CVision Forge Visor] Asset tridimensional acoplado y centrado.");
154
  }}, (xhr) => {{
155
  console.log("[CVision Visor] Progreso de carga mallas: " + (xhr.loaded / xhr.total * 100) + "%");
156
  }}, (error) => {{
157
  console.error("[CVision Forge Error] Error crítico al decodificar la estructura:", error);
158
  }});
159
-
160
  function animate() {{
161
  requestAnimationFrame(animate);
162
  controls.update();
163
  renderer.render(scene, camera);
164
  }}
165
  animate();
166
-
167
  window.addEventListener('resize', () => {{
168
  camera.aspect = container.clientWidth / container.clientHeight;
169
  camera.updateProjectionMatrix();
@@ -173,124 +255,158 @@ def obtener_html_visor(glb_url: str = None) -> str:
173
  </div>
174
  """
175
 
 
176
  # ═════════════════════════════════════════════════════════════════════
177
  # SECCIÓN 4: ORQUESTADOR INTERNO Y ENLACE INTER-MÓDULO
178
  # 💡 Gestiona los flujos cruzados que consumen respuestas asíncronas en HTML.
179
  # ═════════════════════════════════════════════════════════════════════
180
- def procesar_flujo_completo(prompt):
181
  """
182
  Middleware encargado de coordinar flujos que requieran el visor embebido
183
  de Three.js en lugar de los componentes declarativos estándar.
 
184
  """
185
  print("[CVision Middleware] Solicitando pipeline completo para visor web...")
186
  ruta_glb = ejecutar_forja_3d(prompt)
187
  html_visor = obtener_html_visor(ruta_glb)
188
  return html_visor
189
 
 
190
  # ═════════════════════════════════════════════════════════════════════
191
  # SECCIÓN 5: INTERFAZ DECLARATIVA E INYECCIÓN DE GRADIO
192
  # 💡 Manejo de flujo local, bypass de flujos binarios y montaje estructural de UI.
193
  # ═════════════════════════════════════════════════════════════════════
194
- def puente_forja_y_descarga(ruta_imagen_2d):
195
  """
196
  Monitorea la ejecución del pipeline, extrae la malla de la caché temporal
197
- y estabiliza su persistencia en el espacio local para su lectura segura.
 
 
 
 
 
 
198
  """
199
  if not ruta_imagen_2d:
200
  print("[CVision Error] Entrada vacía. No se suministró una matriz 2D válida.")
201
  return None
202
-
203
  try:
204
  print("[CVision Forge] Lanzando pipeline de pooling asíncrono en cliente...")
205
  resultado_pooling = ejecutar_forja_3d(ruta_imagen_2d)
206
-
207
- if not resultado_pooling or "ERROR" in str(resultado_pooling) or "TIMEOUT" in str(resultado_pooling):
208
  print(f"[CVision Error] Interrupción detectada en el canal: {resultado_pooling}")
209
  return None
210
-
211
- # Si el cliente oficial descargó el archivo de forma nativa en el disco local
212
  if isinstance(resultado_pooling, str) and os.path.exists(resultado_pooling):
213
  print(f"[CVision Forge] Copiando asset desde la caché del cliente: {resultado_pooling}")
214
  ruta_estabilizada_glb = os.path.abspath("cvision_forge_output.glb")
215
-
216
  # Transferencia segura del flujo de bytes local para consolidar persistencia
217
  with open(resultado_pooling, "rb") as f_origen:
218
  datos_binarios = f_origen.read()
219
  with open(ruta_estabilizada_glb, "wb") as f_destino:
220
  f_destino.write(datos_binarios)
221
-
222
- print(f"[CVision Forge] Malla consolidada localmente de forma exitosa: {ruta_estabilizada_glb}")
223
  return ruta_estabilizada_glb
224
-
225
- # Canal de contingencia si el retorno muta a una URL remota de Hugging Face
226
  if isinstance(resultado_pooling, str) and resultado_pooling.startswith("http"):
227
  print(f"[CVision Forge] Interceptando URL remota: {resultado_pooling}")
228
  print("[CVision Forge] Transfiriendo flujo binario desde el clúster...")
229
-
230
  cabeceras = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
231
- peticion = requests.get(resultado_pooling, headers=cabeceras, stream=True, timeout=60)
232
-
 
 
 
 
 
233
  if peticion.status_code == 200:
234
  ruta_estabilizada_glb = os.path.abspath("cvision_forge_output.glb")
235
  with open(ruta_estabilizada_glb, "wb") as archivo_binario:
236
  for bloque in peticion.iter_content(chunk_size=8192):
237
  archivo_binario.write(bloque)
238
-
239
  print(f"[CVision Forge] Binario remoto estabilizado con éxito: {ruta_estabilizada_glb}")
240
  return ruta_estabilizada_glb
241
  else:
242
- print(f"[CVision Error] El clúster denegó el flujo de bytes. Código HTTP: {peticion.status_code}")
 
 
 
243
  return None
244
-
245
  except Exception as error:
246
  print(f"[CVision Falla Crítica] Error en la intercepción de la malla binaria: {str(error)}")
247
-
248
  return None
249
 
250
- def construir_bloque_forge():
 
251
  """
252
  Inicializa el módulo estructural dentro de la interfaz global de CVision.
253
  Aplica la inyección visual Cybernova y conecta los listeners reactivos.
 
254
  """
255
- # Bloque modular aislado para evitar colisiones con otros orquestadores de la app
256
  with gr.Blocks(analytics_enabled=False) as forge_block:
257
- gr.Markdown("### ⚔️ CVision Forge OS v9.0 — Panel de Manufactura Fotorrealista 3D ⚔️")
258
-
 
 
259
  with gr.Row():
 
260
  with gr.Column(scale=2):
261
  gr.Markdown("#### 📥 Entrada de Matriz Estructural")
262
-
263
  input_imagen_2d = gr.Image(
264
  label="Matriz Estructural de Perfil (Asset de Entrada)",
265
  type="filepath",
266
- interactive=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  )
268
-
269
- # Botón de disparo con alta prioridad de renderizado
270
- btn_forjar = gr.Button("⚡ Iniciar Forja Tridimensional", variant="primary", size="lg")
271
-
272
- gr.Markdown("""
273
- <div style="margin-top:20px; padding:10px; background:rgba(0,212,255,0.05); border-left:3px solid #00d4ff; font-size:11px; line-height:1.4; color:rgba(255,255,255,0.7);">
274
- <strong>⚙️ NOTA DE LOGÍSTICA:</strong> El procesamiento utiliza el motor de difusión de matrices densas de TRELLIS. El tiempo de resolución estimado oscila entre 20 y 45 segundos según la demanda del clúster de Hugging Face.
275
- </div>
276
- """, sanitize_html=False)
277
-
278
  with gr.Column(scale=3):
279
  gr.Markdown("#### 🌌 Matriz de Renderizado Tridimensional")
280
-
281
  # Visor nativo con parámetros de color acoplados a la estética Cybernova
282
  visor_3d = gr.Model3D(
283
  label="Forja Geométrica Realizada (Motor TRELLIS)",
284
  clear_color=[0.04, 0.06, 0.1, 1.0],
285
- camera_position=[0, 0, 2]
286
  )
287
-
288
- # Vinculación del evento click con animación interna de carga de Gradio
289
  btn_forjar.click(
290
  fn=puente_forja_y_descarga,
291
  inputs=[input_imagen_2d],
292
  outputs=[visor_3d],
293
- api_name="solicitud_forja_3d"
294
  )
295
-
296
  return forge_block
 
13
  # EXTRACCIÓN SEGURA: Se elimina el token explícito para evitar bloqueos en el commit.
14
  # El token real debe ser configurado en la pestaña "Settings -> Variables and Secrets" de tu Space.
15
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
16
+
17
+ # ─── TABLA DE ESPACIOS CANDIDATOS (ordenados por prioridad de disponibilidad) ────
18
+ # Primario → microsoft/TRELLIS.2 : Space oficial Microsoft, activo y con endpoint /image_to_3d estable.
19
+ # Secundario→ trellis-community/TRELLIS : Fork comunitario mantenido, también activo.
20
+ # Terciario → theseanlavery/TRELLIS-3D : Fork independiente de respaldo adicional.
21
+ # NOTA: Si el primario devuelve CONFIG_ERROR o cae por carga, el orquestador rota automáticamente.
22
+ ESPACIOS_TRELLIS = [
23
+ "microsoft/TRELLIS.2",
24
+ "trellis-community/TRELLIS",
25
+ "theseanlavery/TRELLIS-3D",
26
+ ]
27
 
28
  # ═════════════════════════════════════════════════════════════════════
29
  # SECCIÓN 2: NÚCLEO LOGÍSTICO Y PROCESAMIENTO MULTIMODAL (TRELLIS BACKEND)
30
  # 💡 Implementa el motor de inferencia oficial mediante hilos WebSockets.
31
+ # Usa firma de parámetros completa compatible con TRELLIS v1 y TRELLIS.2.
32
+ # Rota automáticamente entre Spaces si el primario está caído.
33
  # ═════════════════════════════════════════════════════════════════════
34
+
35
+ # ─── FIRMA DE PARÁMETROS COMPLETA (compatible con JeffreyXiang/TRELLIS y forks) ─
36
+ # Confirmada mediante análisis del app.py oficial y discusiones de la comunidad HF.
37
+ # Parámetros mínimos requeridos para el endpoint /image_to_3d en todos los forks activos:
38
+ # image → archivo de entrada (handle_file)
39
+ # multiimages → lista vacía [] para modo single-image
40
+ # seed → semilla de generación (0 = determinista)
41
+ # ss_guidance_strength → guía de escala estructural (recomendado: 7.5)
42
+ # ss_sampling_steps → pasos de muestreo estructural (recomendado: 12)
43
+ # slat_guidance_strength→ guía de latente SLAT (recomendado: 3.0)
44
+ # slat_sampling_steps → pasos de muestreo SLAT (recomendado: 12)
45
+ # multiimage_algo → algoritmo multi-imagen (valor: "stochastic")
46
+ PARAMS_INFERENCIA = {
47
+ "multiimages": [],
48
+ "seed": 0,
49
+ "ss_guidance_strength": 7.5,
50
+ "ss_sampling_steps": 12,
51
+ "slat_guidance_strength": 3.0,
52
+ "slat_sampling_steps": 12,
53
+ "multiimage_algo": "stochastic",
54
+ }
55
+
56
+
57
+ def _intentar_cliente_gradio(espacio: str, ruta_imagen: str) -> str | None:
58
+ """
59
+ Intenta conectar a un Space específico de TRELLIS usando el cliente oficial
60
+ de Gradio vía WebSockets. Retorna la ruta local del .glb si tiene éxito,
61
+ o None si falla (para que el orquestador pruebe el siguiente candidato).
62
+ """
63
+ try:
64
+ from gradio_client import Client, handle_file
65
+
66
+ print(f"[TRELLIS Engine] Conectando con el clúster: {espacio}")
67
+ cliente = Client(espacio, hf_token=HF_TOKEN if HF_TOKEN else None)
68
+
69
+ print(f"[TRELLIS Engine] Despachando asset a la GPU remota... Origen: {ruta_imagen}")
70
+ resultado = cliente.predict(
71
+ image=handle_file(ruta_imagen),
72
+ **PARAMS_INFERENCIA,
73
+ api_name="/image_to_3d",
74
+ )
75
+
76
+ # ── Normalización del resultado ──────────────────────────────────────────
77
+ # El cliente puede retornar: str con path local, dict con 'name', o lista
78
+ ruta_glb = None
79
+
80
+ if isinstance(resultado, str) and resultado.endswith(".glb"):
81
+ ruta_glb = resultado
82
+
83
+ elif isinstance(resultado, dict) and "name" in resultado:
84
+ ruta_glb = resultado["name"]
85
+
86
+ elif isinstance(resultado, list) and len(resultado) > 0:
87
+ primer_nodo = resultado[0]
88
+ if isinstance(primer_nodo, str) and primer_nodo.endswith(".glb"):
89
+ ruta_glb = primer_nodo
90
+ elif isinstance(primer_nodo, dict) and "name" in primer_nodo:
91
+ ruta_glb = primer_nodo["name"]
92
+ # Algunos forks devuelven una tupla (video_path, glb_path)
93
+ elif isinstance(resultado, (list, tuple)) and len(resultado) >= 2:
94
+ for nodo in resultado:
95
+ if isinstance(nodo, str) and nodo.endswith(".glb"):
96
+ ruta_glb = nodo
97
+ break
98
+ if isinstance(nodo, dict) and nodo.get("name", "").endswith(".glb"):
99
+ ruta_glb = nodo["name"]
100
+ break
101
+
102
+ if ruta_glb:
103
+ print(f"[TRELLIS Engine] ¡Malla .glb consolidada con éxito en {espacio}!: {ruta_glb}")
104
+ return ruta_glb
105
+
106
+ raise ValueError(f"Formato binario no reconocido devuelto por {espacio}: {type(resultado)} → {str(resultado)[:200]}")
107
+
108
+ except Exception as error:
109
+ # Distinguir errores estructurales (CONFIG_ERROR, SPACE_ERROR) de errores de red
110
+ mensaje_error = str(error).lower()
111
+ if any(kw in mensaje_error for kw in ["config_error", "invalid state", "space_error", "runtime error"]):
112
+ print(f"[TRELLIS Warn] Space '{espacio}' está caído o en estado inválido: {error}")
113
+ else:
114
+ print(f"[TRELLIS Warn] Falla de pipeline en '{espacio}': {error}")
115
+ return None
116
+
117
+
118
+ def ejecutar_forja_3d(imagen_generada_flux: str) -> str:
119
  """
120
+ Orquestador principal de inferencia 3D. Toma la imagen nacida del motor FLUX
121
+ (o cualquier imagen 2D válida) y utiliza el cliente oficial de Gradio para
122
+ conectarse a los clústeres de TRELLIS mediante WebSockets estables.
123
+
124
+ Implementa rotación automática de Spaces: si el primario falla por
125
+ CONFIG_ERROR u otro error estructural, prueba los candidatos secundarios
126
+ en orden de prioridad, garantizando máxima resiliencia del pipeline.
127
+
128
  Evita simular clics en el frontend para evadir el colapso por 'hidden tab'.
129
+ La firma de parámetros es la completa y validada por la comunidad HF,
130
+ distinta a la versión reducida de ejemplos básicos que causaba errores.
131
  """
 
 
 
 
132
  if not imagen_generada_flux or not str(imagen_generada_flux).strip():
133
  print("[TRELLIS Error] No se suministró una ruta de imagen válida.")
134
  return "ERROR: No se detectó ninguna imagen válida de FLUX para iniciar el esculpido 3D."
135
 
136
+ # ── Rotación de Spaces candidatos ───────────────────────────────────────────
137
+ for espacio in ESPACIOS_TRELLIS:
138
+ resultado = _intentar_cliente_gradio(espacio, imagen_generada_flux)
139
+ if resultado:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  return resultado
141
+ print(f"[TRELLIS Engine] Rotando al siguiente Space candidato...")
142
+ time.sleep(1.5) # Pausa mínima entre intentos para evitar rate-limiting
143
+
144
+ # Si todos los Spaces fallaron, retornar mensaje de error estructurado
145
+ mensaje_final = (
146
+ "ERROR_PIPELINE: Todos los clústeres de TRELLIS están temporalmente no disponibles. "
147
+ "Spaces intentados: " + ", ".join(ESPACIOS_TRELLIS) + ". "
148
+ "Verifica el estado de los Spaces en https://status.huggingface.co o intenta más tarde."
149
+ )
150
+ print(f"[TRELLIS Error] {mensaje_final}")
151
+ return mensaje_final
 
 
152
 
 
 
 
 
 
153
 
154
  # ═════════════════════════════════════════════════════════════════════
155
  # SECCIÓN 3: COMPONENTE VISUAL Y RENDERIZADO INTERACTIVO (FRONTEND)
 
159
  """
160
  Construye de forma dinámica la interfaz del viewport interactivo
161
  utilizando Three.js para la manipulación libre de mallas.
162
+ Se usa como visor de respaldo cuando gr.Model3D no puede renderizar
163
+ una URL remota directamente (ej. assets servidos desde HF caché).
164
  """
165
  if not glb_url or "ERROR" in str(glb_url) or "TIMEOUT" in str(glb_url):
166
  error_msg = glb_url if glb_url else "Esperando especificaciones en el panel de control..."
167
  return f"""
168
+ <div style="width:100%; height:500px; background:#02060f; display:flex; flex-direction:column;
169
+ justify-content:center; align-items:center; border:1px solid rgba(0,212,255,0.15);
170
+ border-radius:8px; font-family:monospace; color:#00d4ff;">
171
+ <h3 style="margin:0; letter-spacing:2px; text-shadow:0 0 10px rgba(0,212,255,0.5);">
172
+ ⚔️ CVISION FORGE LAB v9.0 ⚔️
173
+ </h3>
174
  <p style="color:rgba(255,255,255,0.5); font-size:12px; margin-top:10px;">{error_msg}</p>
175
  </div>
176
  """
177
+
178
  return f"""
179
+ <div id="canvas-3d-container" style="width:100%; height:500px; background:#02060f;
180
+ border:1px solid #00d4ff; border-radius:8px; position:relative; overflow:hidden;">
181
  <script type="module">
182
  import * as THREE from 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.module.js';
183
  import {{ GLTFLoader }} from 'https://cdn.jsdelivr.net/npm/three@0.128.0/examples/jsm/loaders/GLTFLoader.js';
184
  import {{ OrbitControls }} from 'https://cdn.jsdelivr.net/npm/three@0.128.0/examples/jsm/controls/OrbitControls.js';
185
+
186
  const container = document.getElementById('canvas-3d-container');
187
  const scene = new THREE.Scene();
188
  scene.background = new THREE.Color(0x02060f);
189
+
190
  const camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 0.1, 100);
191
  camera.position.set(0, 1.2, 3);
192
+
193
  const renderer = new THREE.WebGLRenderer({{ antialias: true, alpha: false }});
194
  renderer.setSize(container.clientWidth, container.clientHeight);
195
  renderer.setPixelRatio(window.devicePixelRatio);
196
  renderer.shadowMap.enabled = true;
197
  container.appendChild(renderer.domElement);
198
+
199
  const controls = new OrbitControls(camera, renderer.domElement);
200
  controls.enableDamping = true;
201
  controls.dampingFactor = 0.05;
202
+
203
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
204
  scene.add(ambientLight);
205
+
206
  const dirLight1 = new THREE.DirectionalLight(0x00d4ff, 1.0);
207
  dirLight1.position.set(5, 5, 5);
208
  scene.add(dirLight1);
209
+
210
  const dirLight2 = new THREE.DirectionalLight(0xff00ff, 0.5);
211
  dirLight2.position.set(-5, 3, -5);
212
  scene.add(dirLight2);
213
+
214
  const loader = new GLTFLoader();
215
+ const modelUrl = "{glb_url}";
216
+
217
  console.log("[CVision Visor] Intentando cargar geometría desde:", modelUrl);
218
+
219
  loader.load(modelUrl, (gltf) => {{
220
  const model = gltf.scene;
221
+
222
  // Centrado automático del modelo dentro del visor global
223
  const box = new THREE.Box3().setFromObject(model);
224
  const center = box.getCenter(new THREE.Vector3());
225
  const size = box.getSize(new THREE.Vector3());
226
+
227
  model.position.sub(center);
228
  scene.add(model);
229
+
230
  // Ajuste dinámico de cámara según el volumen del objeto forjado
231
  const maxDim = Math.max(size.x, size.y, size.z);
232
  camera.position.z = maxDim * 1.8;
233
  camera.lookAt(0, 0, 0);
234
+
235
  console.log("[CVision Forge Visor] Asset tridimensional acoplado y centrado.");
236
  }}, (xhr) => {{
237
  console.log("[CVision Visor] Progreso de carga mallas: " + (xhr.loaded / xhr.total * 100) + "%");
238
  }}, (error) => {{
239
  console.error("[CVision Forge Error] Error crítico al decodificar la estructura:", error);
240
  }});
241
+
242
  function animate() {{
243
  requestAnimationFrame(animate);
244
  controls.update();
245
  renderer.render(scene, camera);
246
  }}
247
  animate();
248
+
249
  window.addEventListener('resize', () => {{
250
  camera.aspect = container.clientWidth / container.clientHeight;
251
  camera.updateProjectionMatrix();
 
255
  </div>
256
  """
257
 
258
+
259
  # ═════════════════════════════════════════════════════════════════════
260
  # SECCIÓN 4: ORQUESTADOR INTERNO Y ENLACE INTER-MÓDULO
261
  # 💡 Gestiona los flujos cruzados que consumen respuestas asíncronas en HTML.
262
  # ═════════════════════════════════════════════════════════════════════
263
+ def procesar_flujo_completo(prompt: str) -> str:
264
  """
265
  Middleware encargado de coordinar flujos que requieran el visor embebido
266
  de Three.js en lugar de los componentes declarativos estándar.
267
+ Útil cuando el asset resultante es una URL remota de HF y no un path local.
268
  """
269
  print("[CVision Middleware] Solicitando pipeline completo para visor web...")
270
  ruta_glb = ejecutar_forja_3d(prompt)
271
  html_visor = obtener_html_visor(ruta_glb)
272
  return html_visor
273
 
274
+
275
  # ═════════════════════════════════════════════════════════════════════
276
  # SECCIÓN 5: INTERFAZ DECLARATIVA E INYECCIÓN DE GRADIO
277
  # 💡 Manejo de flujo local, bypass de flujos binarios y montaje estructural de UI.
278
  # ═════════════════════════════════════════════════════════════════════
279
+ def puente_forja_y_descarga(ruta_imagen_2d: str) -> str | None:
280
  """
281
  Monitorea la ejecución del pipeline, extrae la malla de la caché temporal
282
+ y estabiliza su persistencia en el espacio local para su lectura segura
283
+ por parte del componente gr.Model3D de Gradio.
284
+
285
+ Maneja tres canales de retorno posibles del cliente Gradio:
286
+ 1. Path local → el cliente descargó el .glb en disco directamente.
287
+ 2. URL remota → el clúster devolvió un enlace HTTPS; se descarga aquí.
288
+ 3. ERROR/None → se propaga None para mostrar estado vacío en el visor.
289
  """
290
  if not ruta_imagen_2d:
291
  print("[CVision Error] Entrada vacía. No se suministró una matriz 2D válida.")
292
  return None
293
+
294
  try:
295
  print("[CVision Forge] Lanzando pipeline de pooling asíncrono en cliente...")
296
  resultado_pooling = ejecutar_forja_3d(ruta_imagen_2d)
297
+
298
+ if not resultado_pooling or "ERROR" in str(resultado_pooling):
299
  print(f"[CVision Error] Interrupción detectada en el canal: {resultado_pooling}")
300
  return None
301
+
302
+ # ── Canal 1: El cliente oficial descargó el archivo de forma nativa en disco ──
303
  if isinstance(resultado_pooling, str) and os.path.exists(resultado_pooling):
304
  print(f"[CVision Forge] Copiando asset desde la caché del cliente: {resultado_pooling}")
305
  ruta_estabilizada_glb = os.path.abspath("cvision_forge_output.glb")
306
+
307
  # Transferencia segura del flujo de bytes local para consolidar persistencia
308
  with open(resultado_pooling, "rb") as f_origen:
309
  datos_binarios = f_origen.read()
310
  with open(ruta_estabilizada_glb, "wb") as f_destino:
311
  f_destino.write(datos_binarios)
312
+
313
+ print(f"[CVision Forge] Malla consolidada localmente con éxito: {ruta_estabilizada_glb}")
314
  return ruta_estabilizada_glb
315
+
316
+ # ── Canal 2: El clúster retornó una URL remota de Hugging Face ──────────────
317
  if isinstance(resultado_pooling, str) and resultado_pooling.startswith("http"):
318
  print(f"[CVision Forge] Interceptando URL remota: {resultado_pooling}")
319
  print("[CVision Forge] Transfiriendo flujo binario desde el clúster...")
320
+
321
  cabeceras = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
322
+ peticion = requests.get(
323
+ resultado_pooling,
324
+ headers=cabeceras,
325
+ stream=True,
326
+ timeout=90, # Timeout ampliado para modelos pesados
327
+ )
328
+
329
  if peticion.status_code == 200:
330
  ruta_estabilizada_glb = os.path.abspath("cvision_forge_output.glb")
331
  with open(ruta_estabilizada_glb, "wb") as archivo_binario:
332
  for bloque in peticion.iter_content(chunk_size=8192):
333
  archivo_binario.write(bloque)
334
+
335
  print(f"[CVision Forge] Binario remoto estabilizado con éxito: {ruta_estabilizada_glb}")
336
  return ruta_estabilizada_glb
337
  else:
338
+ print(
339
+ f"[CVision Error] El clúster denegó el flujo de bytes. "
340
+ f"Código HTTP: {peticion.status_code}"
341
+ )
342
  return None
343
+
344
  except Exception as error:
345
  print(f"[CVision Falla Crítica] Error en la intercepción de la malla binaria: {str(error)}")
346
+
347
  return None
348
 
349
+
350
+ def construir_bloque_forge() -> gr.Blocks:
351
  """
352
  Inicializa el módulo estructural dentro de la interfaz global de CVision.
353
  Aplica la inyección visual Cybernova y conecta los listeners reactivos.
354
+ El bloque está aislado para evitar colisiones con otros orquestadores de la app.
355
  """
 
356
  with gr.Blocks(analytics_enabled=False) as forge_block:
357
+ gr.Markdown(
358
+ "### ⚔️ CVision Forge OS v9.0 — Panel de Manufactura Fotorrealista 3D ⚔️"
359
+ )
360
+
361
  with gr.Row():
362
+ # ── Panel izquierdo: entrada de la imagen 2D ─────────────────────────
363
  with gr.Column(scale=2):
364
  gr.Markdown("#### 📥 Entrada de Matriz Estructural")
365
+
366
  input_imagen_2d = gr.Image(
367
  label="Matriz Estructural de Perfil (Asset de Entrada)",
368
  type="filepath",
369
+ interactive=True,
370
+ )
371
+
372
+ btn_forjar = gr.Button(
373
+ "⚡ Iniciar Forja Tridimensional",
374
+ variant="primary",
375
+ size="lg",
376
+ )
377
+
378
+ gr.Markdown(
379
+ """
380
+ <div style="margin-top:20px; padding:10px; background:rgba(0,212,255,0.05);
381
+ border-left:3px solid #00d4ff; font-size:11px; line-height:1.4;
382
+ color:rgba(255,255,255,0.7);">
383
+ <strong>⚙️ NOTA DE LOGÍSTICA:</strong> El procesamiento utiliza el motor
384
+ de difusión de matrices densas de TRELLIS. El tiempo de resolución estimado
385
+ oscila entre 20 y 60 segundos según la demanda del clúster de Hugging Face.
386
+ El sistema rota automáticamente entre tres Spaces candidatos si el primario
387
+ está bajo mantenimiento o sobrecarga.
388
+ </div>
389
+ """,
390
+ sanitize_html=False,
391
  )
392
+
393
+ # ── Panel derecho: visor 3D nativo Gradio ────────────────────────────
 
 
 
 
 
 
 
 
394
  with gr.Column(scale=3):
395
  gr.Markdown("#### 🌌 Matriz de Renderizado Tridimensional")
396
+
397
  # Visor nativo con parámetros de color acoplados a la estética Cybernova
398
  visor_3d = gr.Model3D(
399
  label="Forja Geométrica Realizada (Motor TRELLIS)",
400
  clear_color=[0.04, 0.06, 0.1, 1.0],
401
+ camera_position=[0, 0, 2],
402
  )
403
+
404
+ # ── Vinculación del evento click con animación interna de carga de Gradio ─
405
  btn_forjar.click(
406
  fn=puente_forja_y_descarga,
407
  inputs=[input_imagen_2d],
408
  outputs=[visor_3d],
409
+ api_name="solicitud_forja_3d",
410
  )
411
+
412
  return forge_block