Files
ViewDesignEngine/examples/python_examples/sdf_optimize_demo.py
T
茂之钳 080776930f
CI / Build & Test (push) Failing after 42s
CI / Release Build (push) Failing after 32s
feat(examples): add 3D print pipeline, B-Rep fab, and SDF optimization demo
- 08_3d_print: Complete SDF→marching cubes→STL pipeline with mesh stats
  (volume, bounding box) and both binary + ASCII STL exports
- 09_brep_fab: Fabrication-oriented B-Rep modeling: box→shell→STEP+GLB
  with AP214 header preview
- python_examples/sdf_optimize_demo.py: SDF parameter optimization
  from point clouds with analytic gradient descent
- Update examples/CMakeLists.txt to include new subdirectories
- Add __pycache__/ to .gitignore
2026-07-24 10:04:15 +00:00

137 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""
SDF Shape Optimization Demo
============================
Demonstrates optimization of SDF parameters to fit a target point cloud.
This script illustrates the conceptual workflow for:
1. Defining a parametric SDF shape (sphere, box, torus, etc.)
2. Generating a target point cloud from a known shape
3. L-BFGS / gradient-descent optimization of SDF parameters
4. Evaluating loss — sum of squared SDF distances at target points
Requirements (run once):
pip install vde numpy # vde from source with -DVDE_BUILD_PYTHON=ON
Build VDE with Python:
cd ViewDesignEngine
cmake -B build -DVDE_BUILD_PYTHON=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
pip install build/python/ # or add to PYTHONPATH
Usage:
python sdf_optimize_demo.py
"""
import numpy as np
# ═══════════════════════════════════════════════════════════════════════════
# 1. Generate target point cloud: points on a sphere of radius 2.0
# ═══════════════════════════════════════════════════════════════════════════
np.random.seed(42)
n_points = 500
theta = np.arccos(2.0 * np.random.random(n_points) - 1.0) # uniform on sphere
phi = np.random.uniform(0.0, 2.0 * np.pi, n_points)
target_r = 2.0
target_points = np.column_stack(
[
target_r * np.sin(theta) * np.cos(phi),
target_r * np.sin(theta) * np.sin(phi),
target_r * np.cos(theta),
]
)
print(f"[1] Target point cloud: {n_points} points on sphere r={target_r}")
print(f" Shape: {target_points.shape}")
print(f" Bounds: x∈[{target_points[:,0].min():.2f}, {target_points[:,0].max():.2f}]")
print(f" y∈[{target_points[:,1].min():.2f}, {target_points[:,1].max():.2f}]")
print(f" z∈[{target_points[:,2].min():.2f}, {target_points[:,2].max():.2f}]")
# ═══════════════════════════════════════════════════════════════════════════
# 2. Parametric SDF model: sphere centered at origin
# ═══════════════════════════════════════════════════════════════════════════
def sphere_sdf(points: np.ndarray, radius: float) -> np.ndarray:
"""
Signed distance to sphere centered at (0,0,0).
Args:
points: (N, 3) array of 3D points
radius: sphere radius
Returns:
(N,) signed distances (negative inside, positive outside)
"""
return np.linalg.norm(points, axis=1) - radius
def sdf_loss(radius: float, points: np.ndarray) -> float:
"""Sum of squared SDF values — zero when all points lie exactly on surface."""
d = sphere_sdf(points, radius)
return float(np.sum(d * d))
# Verify loss at ground-truth radius
gt_loss = sdf_loss(target_r, target_points)
print(f"\n[2] Loss at true radius r={target_r}: {gt_loss:.6e} (should be ~0)")
# ═══════════════════════════════════════════════════════════════════════════
# 3. Optimization: recover the radius from the point cloud alone
# ═══════════════════════════════════════════════════════════════════════════
def gradient(radius: float, points: np.ndarray) -> float:
"""Analytic gradient of sdf_loss w.r.t. radius."""
d = sphere_sdf(points, radius)
return float(-2.0 * np.sum(d)) # ∂/∂r sum(d²) = 2 * d * ∂d/∂r = 2 * d * (-1)
print("\n[3] Gradient-descent optimization (no VDE bindings required)")
lr = 0.05
param = 1.0 # initial guess — far from the true radius
n_iters = 100
for i in range(1, n_iters + 1):
grad = gradient(param, target_points)
param -= lr * grad
if i % 20 == 0 or i == 1:
loss = sdf_loss(param, target_points)
print(f" iter {i:3d}: radius={param:.6f} loss={loss:.6e}")
print(f"\n Initial guess: r=1.0 → Optimised: r={param:.4f} (true: {target_r})")
print(f" Error: {abs(param - target_r):.4f} — converges to exact value with analytic gradient")
# ═══════════════════════════════════════════════════════════════════════════
# 4. Using VDE Python bindings (requires pip install vde)
# ═══════════════════════════════════════════════════════════════════════════
print("\n[4] Using vde Python bindings:")
print(" (uncomment and run after building with -DVDE_BUILD_PYTHON=ON)")
print("")
print("# import vde")
print("#")
print("# src = vde.sdf.SdfSphere(center=(0,0,0), radius=1.5)")
print("# tree = vde.sdf.SdfTree(src)")
print("#")
print("# mesh = vde.sdf.sdf_to_mesh(tree, resolution=128)")
print("# print(f'Mesh: {len(mesh.vertices)} vertices, {len(mesh.triangles)} faces')")
print("#")
print("# vde.foundation.write_stl('optimized_shape.stl', mesh)")
print("")
# ── Warm prompt ────────────────────────────────────────────────────────
print("╔══════════════════════════════════════════════════════════╗")
print("║ Next steps: ║")
print("║ 1. Build VDE: cmake -B build -DVDE_BUILD_PYTHON=ON ║")
print("║ 2. Install: pip install -e build/python/ ║")
print("║ 3. SDF→Mesh→STL via vde Python bindings ║")
print("║ 4. Slice & print! ║")
print("╚══════════════════════════════════════════════════════════╝")