Spaces:
Running
Running
material api integration
Browse files
app.py
CHANGED
|
@@ -49,6 +49,10 @@ RPK_DIR = os.path.join(os.path.dirname(__file__), "rpk")
|
|
| 49 |
if not os.path.exists(RPK_DIR):
|
| 50 |
os.makedirs(RPK_DIR)
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
class GenerateRequest(BaseModel):
|
| 53 |
rpk_name: str
|
| 54 |
geometry: Dict[str, Any] # GeoJSON Feature or Geometry
|
|
@@ -125,6 +129,26 @@ def _ensure_report_attrs(rpk_name: str, attrs: Dict[str, Any]) -> Dict[str, Any]
|
|
| 125 |
return merged
|
| 126 |
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
def _extract_reports(
|
| 129 |
initial_shape: "pyprt.InitialShape",
|
| 130 |
clean_attrs: Dict[str, Any],
|
|
@@ -291,26 +315,23 @@ async def generate_model(request: GenerateRequest):
|
|
| 291 |
indices = list(range(len(coords)))
|
| 292 |
face_counts = [len(coords)]
|
| 293 |
|
| 294 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
|
| 296 |
# Setup Model Generator
|
| 297 |
-
# Prepare Options
|
| 298 |
-
# We use OBJ encoder then convert to GLB.
|
| 299 |
-
# "emitReport": True is needed to get the attributes back? No, that's get_attributes.
|
| 300 |
export_options = {
|
| 301 |
-
"outputPath": tempfile.mkdtemp(),
|
| 302 |
-
"outputFilename": "model",
|
| 303 |
"emitReport": True,
|
| 304 |
"emitGeometry": True
|
| 305 |
}
|
| 306 |
-
|
| 307 |
-
# Generate
|
| 308 |
-
logger.info(f"Generating with RPK: {rpk_path}")
|
| 309 |
-
logger.info(f"Using attributes: {request.attributes}")
|
| 310 |
-
|
| 311 |
-
# Sanitise attributes
|
| 312 |
-
clean_attributes = _clean_attributes(request.attributes)
|
| 313 |
-
logger.info(f"Clean attributes: {clean_attributes}")
|
| 314 |
|
| 315 |
is_geometry_only = request.rpk_name in GEOMETRY_ONLY_RPKS
|
| 316 |
|
|
@@ -475,7 +496,13 @@ async def generate_i3s(request: GenerateRequest):
|
|
| 475 |
indices = list(range(len(ecef_coords_all)))
|
| 476 |
face_counts = [len(ecef_coords_all)]
|
| 477 |
|
| 478 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 479 |
|
| 480 |
# 4. Encoder Options
|
| 481 |
layer_id = str(uuid.uuid4())
|
|
@@ -495,8 +522,6 @@ async def generate_i3s(request: GenerateRequest):
|
|
| 495 |
'globalOffset': anchor_ecef
|
| 496 |
}
|
| 497 |
|
| 498 |
-
# Sanitise attributes
|
| 499 |
-
clean_attributes = _clean_attributes(request.attributes)
|
| 500 |
logger.info(f"Generating I3S to {output_dir}")
|
| 501 |
|
| 502 |
# Pass 1 – extract CGA reports via PyEncoder
|
|
@@ -721,10 +746,13 @@ async def get_model_report(request: GenerateRequest):
|
|
| 721 |
|
| 722 |
indices = list(range(len(coords)))
|
| 723 |
face_counts = [len(coords)]
|
| 724 |
-
initial_shape = pyprt.InitialShape(flattened_coords, indices, face_counts)
|
| 725 |
|
| 726 |
-
# --- Sanitise attributes ---
|
| 727 |
clean_attributes = _clean_attributes(request.attributes)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
|
| 729 |
# --- Run PyEncoder report pass ---
|
| 730 |
try:
|
|
|
|
| 49 |
if not os.path.exists(RPK_DIR):
|
| 50 |
os.makedirs(RPK_DIR)
|
| 51 |
|
| 52 |
+
# Base URL of the Material Library API — used to build absolute texture URIs for PyPRT's
|
| 53 |
+
# built-in HTTP resolver. Set MATERIAL_API_URL env var in production (HF Spaces, Docker).
|
| 54 |
+
MATERIAL_API_BASE = os.environ.get("MATERIAL_API_URL", "http://localhost:8000")
|
| 55 |
+
|
| 56 |
class GenerateRequest(BaseModel):
|
| 57 |
rpk_name: str
|
| 58 |
geometry: Dict[str, Any] # GeoJSON Feature or Geometry
|
|
|
|
| 129 |
return merged
|
| 130 |
|
| 131 |
|
| 132 |
+
def _build_texture_resolve_map(clean_attrs: Dict[str, Any]) -> Dict[str, str]:
|
| 133 |
+
"""Build a PyPRT resolve_map for Material API texture paths.
|
| 134 |
+
|
| 135 |
+
When the user picks a texture from the Material Library UI the frontend stores
|
| 136 |
+
the API path (e.g. ``/texture/Architectural/Walls/Brick/.../Color.jpg``) as a
|
| 137 |
+
string attribute value. The CGA rule calls ``texture(WallTexture)`` so PRT looks
|
| 138 |
+
up that exact string in the ResolveMap. This helper maps every such value to an
|
| 139 |
+
absolute HTTP URL so PRT's built-in HTTP resolver can fetch and embed the texture.
|
| 140 |
+
|
| 141 |
+
Only string values starting with ``/texture/`` are mapped; all other attributes
|
| 142 |
+
are left to the RPK's internal ResolveMap (assets bundled inside the RPK).
|
| 143 |
+
"""
|
| 144 |
+
resolve_map: Dict[str, str] = {}
|
| 145 |
+
for val in clean_attrs.values():
|
| 146 |
+
if isinstance(val, str) and val.startswith("/texture/"):
|
| 147 |
+
resolve_map[val] = f"{MATERIAL_API_BASE}{val}"
|
| 148 |
+
logger.info(f"Texture resolve_map: {val!r} → {resolve_map[val]!r}")
|
| 149 |
+
return resolve_map
|
| 150 |
+
|
| 151 |
+
|
| 152 |
def _extract_reports(
|
| 153 |
initial_shape: "pyprt.InitialShape",
|
| 154 |
clean_attrs: Dict[str, Any],
|
|
|
|
| 315 |
indices = list(range(len(coords)))
|
| 316 |
face_counts = [len(coords)]
|
| 317 |
|
| 318 |
+
# Sanitise attributes before InitialShape so we can build the texture resolve_map
|
| 319 |
+
clean_attributes = _clean_attributes(request.attributes)
|
| 320 |
+
logger.info(f"Generating with RPK: {rpk_path}")
|
| 321 |
+
logger.info(f"Clean attributes: {clean_attributes}")
|
| 322 |
+
|
| 323 |
+
initial_shape = pyprt.InitialShape(
|
| 324 |
+
flattened_coords, indices, face_counts,
|
| 325 |
+
resolve_map=_build_texture_resolve_map(clean_attributes),
|
| 326 |
+
)
|
| 327 |
|
| 328 |
# Setup Model Generator
|
|
|
|
|
|
|
|
|
|
| 329 |
export_options = {
|
| 330 |
+
"outputPath": tempfile.mkdtemp(),
|
| 331 |
+
"outputFilename": "model",
|
| 332 |
"emitReport": True,
|
| 333 |
"emitGeometry": True
|
| 334 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
|
| 336 |
is_geometry_only = request.rpk_name in GEOMETRY_ONLY_RPKS
|
| 337 |
|
|
|
|
| 496 |
indices = list(range(len(ecef_coords_all)))
|
| 497 |
face_counts = [len(ecef_coords_all)]
|
| 498 |
|
| 499 |
+
# Sanitise attributes before InitialShape so we can build the texture resolve_map
|
| 500 |
+
clean_attributes = _clean_attributes(request.attributes)
|
| 501 |
+
|
| 502 |
+
initial_shape = pyprt.InitialShape(
|
| 503 |
+
flattened_coords, indices, face_counts,
|
| 504 |
+
resolve_map=_build_texture_resolve_map(clean_attributes),
|
| 505 |
+
)
|
| 506 |
|
| 507 |
# 4. Encoder Options
|
| 508 |
layer_id = str(uuid.uuid4())
|
|
|
|
| 522 |
'globalOffset': anchor_ecef
|
| 523 |
}
|
| 524 |
|
|
|
|
|
|
|
| 525 |
logger.info(f"Generating I3S to {output_dir}")
|
| 526 |
|
| 527 |
# Pass 1 – extract CGA reports via PyEncoder
|
|
|
|
| 746 |
|
| 747 |
indices = list(range(len(coords)))
|
| 748 |
face_counts = [len(coords)]
|
|
|
|
| 749 |
|
| 750 |
+
# --- Sanitise attributes before InitialShape so we can build the texture resolve_map ---
|
| 751 |
clean_attributes = _clean_attributes(request.attributes)
|
| 752 |
+
initial_shape = pyprt.InitialShape(
|
| 753 |
+
flattened_coords, indices, face_counts,
|
| 754 |
+
resolve_map=_build_texture_resolve_map(clean_attributes),
|
| 755 |
+
)
|
| 756 |
|
| 757 |
# --- Run PyEncoder report pass ---
|
| 758 |
try:
|