Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -298,103 +298,209 @@ class UNetNoCondWrapper(nn.Module):
|
|
| 298 |
# return (os.path.abspath(obj_path), tmpdir)
|
| 299 |
|
| 300 |
|
| 301 |
-
def build_textured_cube(pil_imgs):
|
| 302 |
"""
|
| 303 |
-
|
| 304 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
"""
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
}
|
| 323 |
|
| 324 |
-
#
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
#
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
#
|
| 342 |
-
|
| 343 |
-
#
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
(
|
| 353 |
-
(
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
quads = {
|
| 366 |
-
|
| 367 |
-
"
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
}
|
| 373 |
|
| 374 |
-
#
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
f.write("
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
|
| 399 |
# def build_textured_cube(pil_imgs, face_rotations=None):
|
| 400 |
# """
|
|
|
|
| 298 |
# return (os.path.abspath(obj_path), tmpdir)
|
| 299 |
|
| 300 |
|
| 301 |
+
def build_textured_cube(pil_imgs, face_rotations=None):
|
| 302 |
"""
|
| 303 |
+
Crée un parallélépipède texturé (OBJ + MTL + textures).
|
| 304 |
+
- pil_imgs: liste/tuple de 4 PIL.Image dans l'ordre [front, right, back, left]
|
| 305 |
+
- Retour: (chemin_absolu_obj, tmpdir)
|
| 306 |
+
Defaults:
|
| 307 |
+
default_rots = {"front": 0, "right": 270, "back": 180, "left": 90, "top": 0, "bottom": 0}
|
| 308 |
+
face_order = ["top","right","bottom","left","front","back"]
|
| 309 |
+
Notes:
|
| 310 |
+
- Ecrit les fichiers dans /tmp/gradio si possible (HF Spaces).
|
| 311 |
+
- front/back utilisent la taille de pil_imgs[0] ; left/right utilisent leur propre largeur (rectangles).
|
| 312 |
"""
|
| 313 |
+
import os
|
| 314 |
+
import tempfile
|
| 315 |
+
from PIL import Image
|
| 316 |
+
|
| 317 |
+
# validation
|
| 318 |
+
if not (isinstance(pil_imgs, (list, tuple)) and len(pil_imgs) >= 4):
|
| 319 |
+
raise ValueError("build_textured_cube attend une liste/tuple de 4 images PIL (front, right, back, left).")
|
| 320 |
+
|
| 321 |
+
# defaults rotation & ordre
|
| 322 |
+
default_rots = {"front": 0, "right": 270, "back": 180, "left": 90, "top": 0, "bottom": 0}
|
| 323 |
+
if face_rotations is None:
|
| 324 |
+
face_rotations = default_rots
|
| 325 |
+
else:
|
| 326 |
+
for k, v in default_rots.items():
|
| 327 |
+
face_rotations.setdefault(k, v)
|
| 328 |
+
|
| 329 |
+
# dossier temporaire (préférer /tmp/gradio sur HF)
|
| 330 |
+
base_dir = "/tmp/gradio"
|
| 331 |
+
if os.path.isdir(base_dir) and os.access(base_dir, os.W_OK):
|
| 332 |
+
tmpdir = tempfile.mkdtemp(prefix="parallelep_", dir=base_dir)
|
| 333 |
+
else:
|
| 334 |
+
tmpdir = tempfile.mkdtemp(prefix="parallelep_")
|
| 335 |
+
|
| 336 |
+
# noms relatifs pour textures (mtl utilisera ces noms)
|
| 337 |
+
tex_names = {
|
| 338 |
+
"front": "tex_front.png",
|
| 339 |
+
"right": "tex_right.png",
|
| 340 |
+
"back": "tex_back.png",
|
| 341 |
+
"left": "tex_left.png",
|
| 342 |
+
"top": "tex_top.png",
|
| 343 |
+
"bottom": "tex_bottom.png",
|
| 344 |
}
|
| 345 |
|
| 346 |
+
# récupérer tailles (on suppose que inference a déjà redimensionné left/right si besoin)
|
| 347 |
+
front_w, front_h = pil_imgs[0].size
|
| 348 |
+
right_w, right_h = pil_imgs[1].size
|
| 349 |
+
back_w, back_h = pil_imgs[2].size
|
| 350 |
+
left_w, left_h = pil_imgs[3].size
|
| 351 |
+
|
| 352 |
+
# définir dimensions physiques du parallélépipède (en "px" puis on normalise)
|
| 353 |
+
width_px = float(front_w) # largeur X (front width)
|
| 354 |
+
height_px = float(front_h) # hauteur Z
|
| 355 |
+
# profondeur Y : on prend la largeur des faces latérales (moyenne left/right)
|
| 356 |
+
depth_px = float((right_w + left_w) / 2.0)
|
| 357 |
+
|
| 358 |
+
# normalisation pour garder des coordonnées de l'ordre de ±0.5
|
| 359 |
+
max_dim = max(width_px, depth_px, height_px, 1.0)
|
| 360 |
+
scale = 1.0 / max_dim
|
| 361 |
+
half_x = (width_px * 0.5) * scale # demi-largeur en X
|
| 362 |
+
half_y = (depth_px * 0.5) * scale # demi-profondeur en Y
|
| 363 |
+
half_z = (height_px * 0.5) * scale # demi-hauteur en Z
|
| 364 |
+
|
| 365 |
+
# mapping attendu pour pil_imgs
|
| 366 |
+
mapping_order = ["front", "right", "back", "left"]
|
| 367 |
+
# sauvegarder textures (avec rotation demandée) dans tmpdir
|
| 368 |
+
for img, face_name in zip(pil_imgs[:4], mapping_order):
|
| 369 |
+
im = img.convert("RGB")
|
| 370 |
+
angle = face_rotations.get(face_name, 0)
|
| 371 |
+
if angle % 360 != 0:
|
| 372 |
+
# PIL rotate: angle en degrés, positif = CCW
|
| 373 |
+
im = im.rotate(angle, resample=Image.BICUBIC, expand=False)
|
| 374 |
+
path = os.path.join(tmpdir, tex_names[face_name])
|
| 375 |
+
im.save(path, format="PNG")
|
| 376 |
+
try:
|
| 377 |
+
os.chmod(path, 0o644)
|
| 378 |
+
except Exception:
|
| 379 |
+
pass
|
| 380 |
+
|
| 381 |
+
# top/bottom textures noires (même taille que front pour cohérence)
|
| 382 |
+
black = Image.new("RGB", (front_w, front_h), (0, 0, 0))
|
| 383 |
+
for face_name in ("top", "bottom"):
|
| 384 |
+
im = black
|
| 385 |
+
angle = face_rotations.get(face_name, 0)
|
| 386 |
+
if angle % 360 != 0:
|
| 387 |
+
im = im.rotate(angle, resample=Image.BICUBIC, expand=False)
|
| 388 |
+
p = os.path.join(tmpdir, tex_names[face_name])
|
| 389 |
+
im.save(p, format="PNG")
|
| 390 |
+
try:
|
| 391 |
+
os.chmod(p, 0o644)
|
| 392 |
+
except Exception:
|
| 393 |
+
pass
|
| 394 |
+
|
| 395 |
+
# --- écrire le .mtl (références relatives) ---
|
| 396 |
+
mtl_path = os.path.join(tmpdir, "parallelep.mtl")
|
| 397 |
+
with open(mtl_path, "w", encoding="utf-8") as f:
|
| 398 |
+
f.write("# Material file for parallelepiped\n")
|
| 399 |
+
for mat_name, tex_file in tex_names.items():
|
| 400 |
+
f.write(f"newmtl m_{mat_name}\n")
|
| 401 |
+
f.write("Ka 1.000 1.000 1.000\n")
|
| 402 |
+
f.write("Kd 1.000 1.000 1.000\n")
|
| 403 |
+
f.write("Ks 0.000 0.000 0.000\n")
|
| 404 |
+
f.write("Ns 10.000\n")
|
| 405 |
+
f.write("illum 2\n")
|
| 406 |
+
f.write(f"map_Kd {tex_file}\n\n")
|
| 407 |
+
try:
|
| 408 |
+
os.chmod(mtl_path, 0o644)
|
| 409 |
+
except Exception:
|
| 410 |
+
pass
|
| 411 |
+
|
| 412 |
+
# --- géométrie : définir quads par face (CCW en regardant la face de l'extérieur)
|
| 413 |
+
# Convention: +X = right, +Y = front, +Z = up
|
| 414 |
+
# Les 8 coins (pour référence)
|
| 415 |
+
# (-x,-y,-z), ( x,-y,-z), ( x, y,-z), (-x, y,-z),
|
| 416 |
+
# (-x,-y, z), ( x,-y, z), ( x, y, z), (-x, y, z)
|
| 417 |
quads = {
|
| 418 |
+
# top (+Z) : regarder depuis +Z (au-dessus)
|
| 419 |
+
"top": [
|
| 420 |
+
(-half_x, -half_y, half_z),
|
| 421 |
+
( half_x, -half_y, half_z),
|
| 422 |
+
( half_x, half_y, half_z),
|
| 423 |
+
(-half_x, half_y, half_z),
|
| 424 |
+
],
|
| 425 |
+
# right (+X) : regarder depuis +X (côté droit)
|
| 426 |
+
"right": [
|
| 427 |
+
( half_x, -half_y, -half_z),
|
| 428 |
+
( half_x, half_y, -half_z),
|
| 429 |
+
( half_x, half_y, half_z),
|
| 430 |
+
( half_x, -half_y, half_z),
|
| 431 |
+
],
|
| 432 |
+
# bottom (-Z) : regarder depuis -Z (dessous)
|
| 433 |
+
"bottom": [
|
| 434 |
+
(-half_x, half_y, -half_z),
|
| 435 |
+
( half_x, half_y, -half_z),
|
| 436 |
+
( half_x, -half_y, -half_z),
|
| 437 |
+
(-half_x, -half_y, -half_z),
|
| 438 |
+
],
|
| 439 |
+
# left (-X) : regarder depuis -X (côté gauche)
|
| 440 |
+
"left": [
|
| 441 |
+
(-half_x, -half_y, half_z),
|
| 442 |
+
(-half_x, half_y, half_z),
|
| 443 |
+
(-half_x, half_y, -half_z),
|
| 444 |
+
(-half_x, -half_y, -half_z),
|
| 445 |
+
],
|
| 446 |
+
# front (+Y) : regarder depuis +Y (face avant)
|
| 447 |
+
"front": [
|
| 448 |
+
(-half_x, half_y, -half_z),
|
| 449 |
+
(-half_x, half_y, half_z),
|
| 450 |
+
( half_x, half_y, half_z),
|
| 451 |
+
( half_x, half_y, -half_z),
|
| 452 |
+
],
|
| 453 |
+
# back (-Y) : regarder depuis -Y (face arrière)
|
| 454 |
+
"back": [
|
| 455 |
+
( half_x, -half_y, -half_z),
|
| 456 |
+
( half_x, -half_y, half_z),
|
| 457 |
+
(-half_x, -half_y, half_z),
|
| 458 |
+
(-half_x, -half_y, -half_z),
|
| 459 |
+
],
|
| 460 |
}
|
| 461 |
|
| 462 |
+
# --- écrire l'OBJ (24 vertices, 24 vt, triangulé) selon face_order demandé ---
|
| 463 |
+
face_order = ["top", "right", "bottom", "left", "front", "back"]
|
| 464 |
+
obj_path = os.path.join(tmpdir, "parallelep.obj")
|
| 465 |
+
with open(obj_path, "w", encoding="utf-8") as f:
|
| 466 |
+
f.write("# Parallelepiped OBJ generated by build_textured_cube\n")
|
| 467 |
+
f.write("mtllib parallelep.mtl\n\n")
|
| 468 |
+
|
| 469 |
+
# écrire vertices : 4 par face, dans l'ordre face_order
|
| 470 |
+
for face_name in face_order:
|
| 471 |
+
for v in quads[face_name]:
|
| 472 |
+
f.write("v {:.6f} {:.6f} {:.6f}\n".format(*v))
|
| 473 |
+
f.write("\n")
|
| 474 |
+
|
| 475 |
+
# écrire UVs : 4 UVs par face (0..1). On garde origine UV bas-gauche.
|
| 476 |
+
uvs = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
|
| 477 |
+
for _ in range(6):
|
| 478 |
+
for (u, v) in uvs:
|
| 479 |
+
f.write("vt {:.6f} {:.6f}\n".format(u, v))
|
| 480 |
+
f.write("\n")
|
| 481 |
+
|
| 482 |
+
# faces (2 triangles par face), en s'assurant que les matériaux correspondent
|
| 483 |
+
for i, face_name in enumerate(face_order):
|
| 484 |
+
f.write(f"usemtl m_{face_name}\n")
|
| 485 |
+
v_base = i * 4 + 1
|
| 486 |
+
t_base = i * 4 + 1
|
| 487 |
+
v1, v2, v3, v4 = v_base, v_base + 1, v_base + 2, v_base + 3
|
| 488 |
+
t1, t2, t3, t4 = t_base, t_base + 1, t_base + 2, t_base + 3
|
| 489 |
+
# deux triangles (v/vt)
|
| 490 |
+
f.write(f"f {v1}/{t1} {v2}/{t2} {v3}/{t3}\n")
|
| 491 |
+
f.write(f"f {v1}/{t1} {v3}/{t3} {v4}/{t4}\n\n")
|
| 492 |
+
try:
|
| 493 |
+
os.chmod(obj_path, 0o644)
|
| 494 |
+
except Exception:
|
| 495 |
+
pass
|
| 496 |
+
|
| 497 |
+
# vérifications rapides
|
| 498 |
+
for fname in ["parallelep.obj", "parallelep.mtl"] + list(tex_names.values()):
|
| 499 |
+
p = os.path.join(tmpdir, fname)
|
| 500 |
+
if not os.path.exists(p):
|
| 501 |
+
raise FileNotFoundError(f"Fichier attendu introuvable : {p}")
|
| 502 |
+
|
| 503 |
+
return (os.path.abspath(obj_path), tmpdir)
|
| 504 |
|
| 505 |
# def build_textured_cube(pil_imgs, face_rotations=None):
|
| 506 |
# """
|