Spaces:
Sleeping
Sleeping
update1.2
Browse files
app.py
CHANGED
|
@@ -69,34 +69,33 @@ async def get_rpk_info(filename: str):
|
|
| 69 |
|
| 70 |
formatted_attrs = []
|
| 71 |
|
| 72 |
-
# Handle Dictionary return type (common in some PyPRT versions)
|
| 73 |
-
if isinstance(attrs_info, dict):
|
| 74 |
-
for name, attr in attrs_info.items():
|
| 75 |
-
# attr is likely the Attribute object
|
| 76 |
-
default_val = ""
|
| 77 |
-
attr_type = "string"
|
| 78 |
-
|
| 79 |
-
if hasattr(attr, 'get_default_value'):
|
| 80 |
-
default_val = attr.get_default_value()
|
| 81 |
-
elif hasattr(attr, 'get_defaultValue'): # Try alternate naming
|
| 82 |
-
default_val = attr.get_defaultValue()
|
| 83 |
-
|
| 84 |
-
if hasattr(attr, 'get_type'):
|
| 85 |
-
attr_type = str(attr.get_type())
|
| 86 |
-
|
| 87 |
-
formatted_attrs.append({
|
| 88 |
-
"name": name,
|
| 89 |
-
"type": attr_type,
|
| 90 |
-
"defaultValue": default_val
|
| 91 |
-
})
|
| 92 |
# Handle List of Objects return type (standard PyPRT)
|
| 93 |
-
|
| 94 |
for attr in attrs_info:
|
| 95 |
if hasattr(attr, 'get_name'):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
formatted_attrs.append({
|
| 97 |
-
"name":
|
| 98 |
-
"type":
|
| 99 |
-
"defaultValue":
|
|
|
|
| 100 |
})
|
| 101 |
elif isinstance(attr, str):
|
| 102 |
# Fallback if list of strings
|
|
@@ -109,7 +108,7 @@ async def get_rpk_info(filename: str):
|
|
| 109 |
return {"attributes": formatted_attrs}
|
| 110 |
except Exception as e:
|
| 111 |
logger.error(f"Error inspecting RPK: {e}")
|
| 112 |
-
# Return empty attributes instead of 500 if inspection fails
|
| 113 |
return {"attributes": []}
|
| 114 |
|
| 115 |
@app.post("/generate")
|
|
@@ -136,41 +135,30 @@ async def generate_model(request: GenerateRequest):
|
|
| 136 |
if geom_dict.get("type") != "Polygon":
|
| 137 |
raise HTTPException(status_code=400, detail="Only Polygons are supported")
|
| 138 |
|
| 139 |
-
#
|
| 140 |
-
#
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
# Coordinate Processing for Local Tangent Plane
|
| 145 |
-
# The frontend now sends coordinates in proper local meters (ENU frame).
|
| 146 |
-
# We do NOT project or transform them. We explicitly trust the frontend input.
|
| 147 |
-
|
| 148 |
-
# We also don't need to re-center, as the frontend sends relative coordinates from the centroid (0,0).
|
| 149 |
-
# But to be safe, we can ensure centering if the frontend doesn't perfectly center around 0,0.
|
| 150 |
-
# Actually, DrawTools sends vectors from center, so they are practically centered.
|
| 151 |
-
# We'll calculate bounds just for logging.
|
| 152 |
-
bounds = shape.bounds
|
| 153 |
-
logger.info(f"Received geometry bounds: {bounds}")
|
| 154 |
|
| 155 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
centroid = shape.centroid
|
| 157 |
logger.info(f"Geometry Centroid: {centroid.x}, {centroid.y}")
|
| 158 |
-
|
| 159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
# Remove last point if duplicate (closed loop)
|
| 161 |
if coords[0] == coords[-1]:
|
| 162 |
coords = coords[:-1]
|
| 163 |
|
| 164 |
-
# Flatten and ensure 3D (x, z, y) or (x, y, z) - Mapbox/Cesium usually (lng, lat)
|
| 165 |
-
# PyPRT often expects local coordinates or projected.
|
| 166 |
-
# WARNING: PyPRT does not handle re-projection automatically usually.
|
| 167 |
-
# For this example, we assume coordinates are suitable for PyPRT (e.g. meter based if RPK expects meters)
|
| 168 |
-
# OR we just pass them and let PyPRT handle if it's geographic.
|
| 169 |
-
# Standard CityEngine works best with metric local coordinates.
|
| 170 |
-
|
| 171 |
# Flatten coordinates: [(x, y), ...] -> [x, 0, y, x, 0, y, ...]
|
| 172 |
-
# PyPRT expects a flattened list of coords.
|
| 173 |
-
flattened_coords = []
|
| 174 |
flattened_coords = []
|
| 175 |
for p in coords:
|
| 176 |
x = p[0]
|
|
@@ -190,10 +178,7 @@ async def generate_model(request: GenerateRequest):
|
|
| 190 |
output_filename = "model"
|
| 191 |
|
| 192 |
# Generate
|
| 193 |
-
#
|
| 194 |
-
# (self: ModelGenerator, shapeAttributes: list[dict], rulePackagePath: str, geometryEncoderName: str, geometryEncoderOptions: dict)
|
| 195 |
-
|
| 196 |
-
# Generate using OBJ Encoder (Workaround for broken GLB encoder)
|
| 197 |
models = model_generator.generate_model(
|
| 198 |
[request.attributes],
|
| 199 |
rpk_path,
|
|
|
|
| 69 |
|
| 70 |
formatted_attrs = []
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
# Handle List of Objects return type (standard PyPRT)
|
| 73 |
+
if hasattr(attrs_info, '__iter__'):
|
| 74 |
for attr in attrs_info:
|
| 75 |
if hasattr(attr, 'get_name'):
|
| 76 |
+
name = attr.get_name()
|
| 77 |
+
attr_type = str(attr.get_type())
|
| 78 |
+
default_val = attr.get_default_value()
|
| 79 |
+
annotations = []
|
| 80 |
+
|
| 81 |
+
if hasattr(attr, 'get_annotations'):
|
| 82 |
+
py_annotations = attr.get_annotations()
|
| 83 |
+
# Convert PyPRT annotations to serializable format
|
| 84 |
+
# Annotations are typically list of objects or similar
|
| 85 |
+
for anno in py_annotations:
|
| 86 |
+
# anno might be an object with get_key(), get_value_type(), get_arguments()
|
| 87 |
+
if hasattr(anno, 'get_key'):
|
| 88 |
+
key = anno.get_key()
|
| 89 |
+
args = []
|
| 90 |
+
if hasattr(anno, 'get_arguments'):
|
| 91 |
+
args = anno.get_arguments()
|
| 92 |
+
annotations.append({"key": key, "arguments": args})
|
| 93 |
+
|
| 94 |
formatted_attrs.append({
|
| 95 |
+
"name": name,
|
| 96 |
+
"type": attr_type,
|
| 97 |
+
"defaultValue": default_val,
|
| 98 |
+
"annotations": annotations
|
| 99 |
})
|
| 100 |
elif isinstance(attr, str):
|
| 101 |
# Fallback if list of strings
|
|
|
|
| 108 |
return {"attributes": formatted_attrs}
|
| 109 |
except Exception as e:
|
| 110 |
logger.error(f"Error inspecting RPK: {e}")
|
| 111 |
+
# Return empty attributes instead of 500 if inspection fails
|
| 112 |
return {"attributes": []}
|
| 113 |
|
| 114 |
@app.post("/generate")
|
|
|
|
| 135 |
if geom_dict.get("type") != "Polygon":
|
| 136 |
raise HTTPException(status_code=400, detail="Only Polygons are supported")
|
| 137 |
|
| 138 |
+
# 1. Enforce Counter-Clockwise (CCW) Winding
|
| 139 |
+
# This prevents the model from being generated upside-down (mirrored on Z)
|
| 140 |
+
if not shape.is_valid:
|
| 141 |
+
shape = shape.buffer(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
+
# shapely.ops.orient(geom, sign=1.0) -> CCW
|
| 144 |
+
shape = shapely.ops.orient(shape, sign=1.0)
|
| 145 |
+
|
| 146 |
+
# 2. Re-Center Geometry
|
| 147 |
+
# To ensure the model is generated around (0,0,0) so it can be placed correctly
|
| 148 |
+
# at the centroid by the frontend.
|
| 149 |
centroid = shape.centroid
|
| 150 |
logger.info(f"Geometry Centroid: {centroid.x}, {centroid.y}")
|
| 151 |
+
|
| 152 |
+
# Translate shape so centroid is at (0,0)
|
| 153 |
+
# origin = (0,0)
|
| 154 |
+
shape_centered = translate(shape, xoff=-centroid.x, yoff=-centroid.y)
|
| 155 |
+
|
| 156 |
+
coords = list(shape_centered.exterior.coords)
|
| 157 |
# Remove last point if duplicate (closed loop)
|
| 158 |
if coords[0] == coords[-1]:
|
| 159 |
coords = coords[:-1]
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
# Flatten coordinates: [(x, y), ...] -> [x, 0, y, x, 0, y, ...]
|
|
|
|
|
|
|
| 162 |
flattened_coords = []
|
| 163 |
for p in coords:
|
| 164 |
x = p[0]
|
|
|
|
| 178 |
output_filename = "model"
|
| 179 |
|
| 180 |
# Generate
|
| 181 |
+
# Using OBJ Encdoer for stability
|
|
|
|
|
|
|
|
|
|
| 182 |
models = model_generator.generate_model(
|
| 183 |
[request.attributes],
|
| 184 |
rpk_path,
|