|
|
def export_model(self, export_format="both", progress=gr.Progress()): |
|
|
"""Export model - supports point cloud, mesh, or both""" |
|
|
progress(0.90, desc="💾 Exporting 3D model...") |
|
|
|
|
|
try: |
|
|
|
|
|
models_dir = os.path.join(self.output_dir, "models") |
|
|
if not os.path.exists(models_dir): |
|
|
return {"status": "error", "message": "No trained model found"} |
|
|
|
|
|
|
|
|
model_dirs = [d for d in os.listdir(models_dir) if os.path.isdir(os.path.join(models_dir, d))] |
|
|
if not model_dirs: |
|
|
return {"status": "error", "message": "No model directories found"} |
|
|
|
|
|
latest_model = sorted(model_dirs)[-1] |
|
|
model_path = os.path.join(models_dir, latest_model) |
|
|
config_path = os.path.join(model_path, "config.yml") |
|
|
|
|
|
if not os.path.exists(config_path): |
|
|
return {"status": "error", "message": "Model config not found"} |
|
|
|
|
|
exports = {} |
|
|
|
|
|
|
|
|
if export_format in ["mesh", "both"]: |
|
|
progress(0.90, desc="🎨 Exporting textured mesh...") |
|
|
output_mesh = os.path.join(self.output_dir, "textured_mesh.ply") |
|
|
|
|
|
|
|
|
result = subprocess.run([ |
|
|
"ns-export", "poisson", |
|
|
"--load-config", config_path, |
|
|
"--output-dir", self.output_dir, |
|
|
"--target-num-faces", "500000", |
|
|
"--num-pixels-per-side", "2048", |
|
|
"--normal-method", "model_output", |
|
|
"--save-point-cloud", "False" |
|
|
], capture_output=True, text=True, timeout=900) |
|
|
|
|
|
if result.returncode == 0 and os.path.exists(output_mesh): |
|
|
file_size = os.path.getsize(output_mesh) / (1024 * 1024) |
|
|
exports["mesh"] = { |
|
|
"path": output_mesh, |
|
|
"size_mb": round(file_size, 2), |
|
|
"type": "Textured Mesh (Poisson)" |
|
|
} |
|
|
|
|
|
|
|
|
output_obj = os.path.join(self.output_dir, "textured_mesh.obj") |
|
|
result_obj = subprocess.run([ |
|
|
"ns-export", "poisson", |
|
|
"--load-config", config_path, |
|
|
"--output-dir", self.output_dir, |
|
|
"--target-num-faces", "500000", |
|
|
"--num-pixels-per-side", "2048", |
|
|
"--normal-method", "model_output", |
|
|
"--save-point-cloud", "False", |
|
|
"--output-format", "obj" |
|
|
], capture_output=True, text=True, timeout=900) |
|
|
|
|
|
if result_obj.returncode == 0 and os.path.exists(output_obj): |
|
|
exports["mesh_obj"] = { |
|
|
"path": output_obj, |
|
|
"type": "OBJ with Texture" |
|
|
} |
|
|
|
|
|
|
|
|
if export_format in ["pointcloud", "both"]: |
|
|
progress(0.92, desc="☁️ Exporting point cloud...") |
|
|
output_ply = os.path.join(self.output_dir, "point_cloud.ply") |
|
|
|
|
|
result = subprocess.run([ |
|
|
"ns-export", "pointcloud", |
|
|
"--load-config", config_path, |
|
|
"--output-dir", self.output_dir, |
|
|
"--num-points", "1000000", |
|
|
"--remove-outliers", "True", |
|
|
"--use-bounding-box", "True" |
|
|
], capture_output=True, text=True, timeout=600) |
|
|
|
|
|
if result.returncode == 0 and os.path.exists(output_ply): |
|
|
file_size = os.path.getsize(output_ply) / (1024 * 1024) |
|
|
exports["pointcloud"] = { |
|
|
"path": output_ply, |
|
|
"size_mb": round(file_size, 2), |
|
|
"type": "Point Cloud" |
|
|
} |
|
|
|
|
|
if exports: |
|
|
return {"status": "success", "exports": exports} |
|
|
|
|
|
|
|
|
colmap_sparse = os.path.join(self.colmap_dir, "sparse", "0") |
|
|
if os.path.exists(colmap_sparse): |
|
|
output_ply = os.path.join(self.output_dir, "colmap_points.ply") |
|
|
result = subprocess.run([ |
|
|
"colmap", "model_converter", |
|
|
"--input_path", colmap_sparse, |
|
|
"--output_path", output_ply, |
|
|
"--output_type", "PLY" |
|
|
], capture_output=True, text=True, timeout=300) |
|
|
|
|
|
if os.path.exists(output_ply): |
|
|
file_size = os.path.getsize(output_ply) / (1024 * 1024) |
|
|
return { |
|
|
"status": "success", |
|
|
"exports": { |
|
|
"pointcloud": { |
|
|
"path": output_ply, |
|
|
"size_mb": round(file_size, 2), |
|
|
"type": "COLMAP Points" |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return {"status": "error", "message": "All export methods failed"} |
|
|
|
|
|
except Exception as e: |
|
|
return {"status": "error", "message": str(e)} |
|
|
|