Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -2,6 +2,7 @@ import os
|
|
| 2 |
import logging
|
| 3 |
import json
|
| 4 |
import tempfile
|
|
|
|
| 5 |
import shapely.geometry
|
| 6 |
import pyprt
|
| 7 |
import glob
|
|
@@ -102,55 +103,61 @@ def _clean_attributes(raw: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 102 |
return cleaned
|
| 103 |
|
| 104 |
|
| 105 |
-
def
|
| 106 |
"""
|
| 107 |
-
|
| 108 |
-
the Material API
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
Returns ``(modified_attrs, temp_dir)``
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
"""
|
| 120 |
-
if not MATERIAL_API_BASE:
|
| 121 |
-
logger.warning("MATERIAL_API_URL not set β texture attributes will not be resolved")
|
| 122 |
-
return attrs, None
|
| 123 |
-
|
| 124 |
texture_attrs = {
|
| 125 |
k: v for k, v in attrs.items()
|
| 126 |
if isinstance(v, str) and v.startswith("/texture/")
|
| 127 |
}
|
| 128 |
if not texture_attrs:
|
| 129 |
-
return attrs, None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
-
temp_dir = tempfile.mkdtemp(prefix="pyprt_tex_")
|
| 132 |
modified = dict(attrs)
|
| 133 |
|
| 134 |
import requests as _requests
|
| 135 |
for key, rel_path in texture_attrs.items():
|
| 136 |
url = f"{MATERIAL_API_BASE}{rel_path}"
|
| 137 |
filename = os.path.basename(rel_path)
|
| 138 |
-
|
|
|
|
| 139 |
try:
|
| 140 |
logger.info(f"Downloading texture '{key}': {url}")
|
| 141 |
resp = _requests.get(url, timeout=20)
|
| 142 |
resp.raise_for_status()
|
| 143 |
-
with
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
modified[key] = file_uri
|
| 148 |
-
logger.info(f"Texture saved β {local_path}")
|
| 149 |
except Exception as exc:
|
| 150 |
-
logger.warning(f"Texture
|
| 151 |
-
modified.pop(key, None)
|
| 152 |
|
| 153 |
-
return modified, temp_dir
|
| 154 |
|
| 155 |
|
| 156 |
# RPKs that control report emission via boolean attributes β always force them ON.
|
|
@@ -349,8 +356,8 @@ async def generate_model(request: GenerateRequest):
|
|
| 349 |
|
| 350 |
# Sanitise attributes
|
| 351 |
clean_attributes = _clean_attributes(request.attributes)
|
| 352 |
-
#
|
| 353 |
-
clean_attributes, tex_temp_dir =
|
| 354 |
logger.info(f"Generating with RPK: {rpk_path}")
|
| 355 |
logger.info(f"Clean attributes: {clean_attributes}")
|
| 356 |
|
|
@@ -536,8 +543,8 @@ async def generate_i3s(request: GenerateRequest):
|
|
| 536 |
|
| 537 |
# Sanitise attributes
|
| 538 |
clean_attributes = _clean_attributes(request.attributes)
|
| 539 |
-
#
|
| 540 |
-
clean_attributes, tex_temp_dir =
|
| 541 |
|
| 542 |
initial_shape = pyprt.InitialShape(flattened_coords, indices, face_counts)
|
| 543 |
|
|
@@ -792,7 +799,8 @@ async def get_model_report(request: GenerateRequest):
|
|
| 792 |
|
| 793 |
# --- Sanitise attributes ---
|
| 794 |
clean_attributes = _clean_attributes(request.attributes)
|
| 795 |
-
|
|
|
|
| 796 |
initial_shape = pyprt.InitialShape(flattened_coords, indices, face_counts)
|
| 797 |
|
| 798 |
# --- Run PyEncoder report pass ---
|
|
|
|
| 2 |
import logging
|
| 3 |
import json
|
| 4 |
import tempfile
|
| 5 |
+
import zipfile
|
| 6 |
import shapely.geometry
|
| 7 |
import pyprt
|
| 8 |
import glob
|
|
|
|
| 103 |
return cleaned
|
| 104 |
|
| 105 |
|
| 106 |
+
def _inject_textures_into_rpk(rpk_path: str, attrs: Dict[str, Any]) -> tuple:
|
| 107 |
"""
|
| 108 |
+
For any texture attribute (value starts with '/texture/'), download the
|
| 109 |
+
texture from the Material API and inject it into a temporary copy of the
|
| 110 |
+
RPK (which is a ZIP file). The attribute value is replaced with the
|
| 111 |
+
RPK-relative path so CGA's asset resolution finds it normally.
|
| 112 |
+
|
| 113 |
+
Returns ``(rpk_to_use, modified_attrs, temp_dir)``.
|
| 114 |
+
- ``rpk_to_use`` : path to use for generation (temp copy if textures injected)
|
| 115 |
+
- ``modified_attrs``: attrs with texture values replaced by RPK-relative paths
|
| 116 |
+
- ``temp_dir`` : directory to clean up after generation (or ``None``)
|
| 117 |
+
|
| 118 |
+
Why RPK injection instead of file:// URIs
|
| 119 |
+
-----------------------------------------
|
| 120 |
+
CGA's ``set(material.colormap, WallTexture)`` resolves asset paths relative
|
| 121 |
+
to the RPK, not the filesystem. Passing a ``file://`` URI as an attribute
|
| 122 |
+
value results in "Unknown Texture" because PRT's asset pipeline never sees
|
| 123 |
+
it. Injecting the texture directly into the RPK ZIP at a known path lets
|
| 124 |
+
PRT find it through the normal resolution mechanism.
|
| 125 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
texture_attrs = {
|
| 127 |
k: v for k, v in attrs.items()
|
| 128 |
if isinstance(v, str) and v.startswith("/texture/")
|
| 129 |
}
|
| 130 |
if not texture_attrs:
|
| 131 |
+
return rpk_path, attrs, None
|
| 132 |
+
if not MATERIAL_API_BASE:
|
| 133 |
+
logger.warning("MATERIAL_API_URL not set β texture attributes will use CGA defaults")
|
| 134 |
+
return rpk_path, attrs, None
|
| 135 |
+
|
| 136 |
+
temp_dir = tempfile.mkdtemp(prefix="pyprt_rpk_")
|
| 137 |
+
temp_rpk = os.path.join(temp_dir, os.path.basename(rpk_path))
|
| 138 |
+
shutil.copy(rpk_path, temp_rpk)
|
| 139 |
|
|
|
|
| 140 |
modified = dict(attrs)
|
| 141 |
|
| 142 |
import requests as _requests
|
| 143 |
for key, rel_path in texture_attrs.items():
|
| 144 |
url = f"{MATERIAL_API_BASE}{rel_path}"
|
| 145 |
filename = os.path.basename(rel_path)
|
| 146 |
+
# Store under assets/custom/ inside the RPK to avoid collision with existing assets
|
| 147 |
+
rpk_asset_path = f"assets/custom/{filename}"
|
| 148 |
try:
|
| 149 |
logger.info(f"Downloading texture '{key}': {url}")
|
| 150 |
resp = _requests.get(url, timeout=20)
|
| 151 |
resp.raise_for_status()
|
| 152 |
+
with zipfile.ZipFile(temp_rpk, "a", compression=zipfile.ZIP_STORED) as zf:
|
| 153 |
+
zf.writestr(rpk_asset_path, resp.content)
|
| 154 |
+
modified[key] = rpk_asset_path
|
| 155 |
+
logger.info(f"Texture '{key}' injected into RPK as: {rpk_asset_path}")
|
|
|
|
|
|
|
| 156 |
except Exception as exc:
|
| 157 |
+
logger.warning(f"Texture injection failed for '{key}' ({url}): {exc}. Using CGA default.")
|
| 158 |
+
modified.pop(key, None)
|
| 159 |
|
| 160 |
+
return temp_rpk, modified, temp_dir
|
| 161 |
|
| 162 |
|
| 163 |
# RPKs that control report emission via boolean attributes β always force them ON.
|
|
|
|
| 356 |
|
| 357 |
# Sanitise attributes
|
| 358 |
clean_attributes = _clean_attributes(request.attributes)
|
| 359 |
+
# Inject any Material-API textures into a temp RPK copy so CGA resolves them
|
| 360 |
+
rpk_path, clean_attributes, tex_temp_dir = _inject_textures_into_rpk(rpk_path, clean_attributes)
|
| 361 |
logger.info(f"Generating with RPK: {rpk_path}")
|
| 362 |
logger.info(f"Clean attributes: {clean_attributes}")
|
| 363 |
|
|
|
|
| 543 |
|
| 544 |
# Sanitise attributes
|
| 545 |
clean_attributes = _clean_attributes(request.attributes)
|
| 546 |
+
# Inject any Material-API textures into a temp RPK copy so CGA resolves them
|
| 547 |
+
rpk_path, clean_attributes, tex_temp_dir = _inject_textures_into_rpk(rpk_path, clean_attributes)
|
| 548 |
|
| 549 |
initial_shape = pyprt.InitialShape(flattened_coords, indices, face_counts)
|
| 550 |
|
|
|
|
| 799 |
|
| 800 |
# --- Sanitise attributes ---
|
| 801 |
clean_attributes = _clean_attributes(request.attributes)
|
| 802 |
+
# Inject any Material-API textures into a temp RPK copy so CGA resolves them
|
| 803 |
+
rpk_path, clean_attributes, tex_temp_dir = _inject_textures_into_rpk(rpk_path, clean_attributes)
|
| 804 |
initial_shape = pyprt.InitialShape(flattened_coords, indices, face_counts)
|
| 805 |
|
| 806 |
# --- Run PyEncoder report pass ---
|