Grid Operations¶
Handles the geometry pipeline from raw meshes to final output:
- Voxelization -- converts the max-volume mesh into a dense 3D boolean grid at the configured
voxel_size. - Surface sampling -- discretizes test-surface faces into evenly spaced points with outward-facing normals, respecting
grid_stepand coplanarity tolerance. - Pruning -- removes small disconnected voxel clusters below
min_voxelsusing connected-component labelling. - Mesh reconstruction -- converts the carved voxel mask back into a triangle mesh, either as cubic voxel faces or via SDF smoothing + marching cubes + Taubin polishing.
urbansolarcarver.grid
¶
UrbanSolarCarver — Grid utilities
Tools for sampling nearly planar geometry, converting meshes to voxels, cleaning occupancy, and building meshes. Used by the carving pipeline to generate point/normal sets, binary grids, and final surfaces.
Overview
Two meshing modes are supported:
1) Cubic (apply_smoothing == False) - Clean the binary grid with component pruning, a 3×3×3 majority filter, and one 6-connected closing. - Triangulate with a cubic voxel mesher (Kaolin dual-cubes).
2) Smoothed SDF (apply_smoothing == True) - Convert occupancy to a signed distance field (SDF) in voxel units. - Light Gaussian blur, then edge-preserving Perona–Malik diffusion applied only near the zero level. - Pick an iso value that preserves the original inside volume. - Extract a surface with marching cubes (trimesh + scikit-image). - Optionally run a tiny Taubin polish (0..6 iters) after cleanup.
Public API
sample_planar_surface(mesh, sample_step, include_boundary=True) Uniformly sample a strictly planar patch and return per-point normals.
discretize_surface_with_normals(mesh, sample_step, coplanarity_tol_deg=5.0) Collect point/normal pairs and an analysis mesh from near-planar components.
voxelize_mesh(mesh, voxel_size, margin_frac, device=None) Rasterize a watertight mesh into a padded cubic occupancy grid.
prune_voxels(voxels, min_voxels) Remove 26-connected components smaller than min_voxels.
prune_voxels_morph(voxels, min_voxels) Gentle cleanup for the cubic path: component prune, majority filter, single closing. Preserves thin slabs better than erosion/opening.
voxelize_and_clean(mesh, voxel_size, margin_frac, min_voxels) Convenience wrapper: voxelize_mesh then prune_voxels.
mesh_from_voxels(voxels, min_corner, voxel_size) Build a blocky mesh from the binary grid. Returns an unprocessed Trimesh.
mesh_from_voxels_smoothed(voxels, min_corner, voxel_size) Build a smooth mesh by contouring a smoothed SDF with marching cubes. Uses a volume-matched iso to avoid systematic thinning.
mesh_from_voxels_select(voxels, min_corner, voxel_size, apply_smoothing) Dispatch to the cubic or SDF path based on the flag above.
cleanup_mesh(mesh, min_face_count=100) Fix winding/normals, weld vertices, drop tiny fragments.
polish_mesh_taubin(mesh, iters=0) Optional micro-polish after SDF+MC. Scale-normalized. Safe for 0..6 iters.
Internal helpers
plane_frame(normal) Orthonormal in-plane basis for planar sampling.
_voxel_presmooth(field_bool) SDF build + Gaussian + narrow-band Perona–Malik diffusion.
_pm_anisotropic_diffuse(sdf, iters, k, tau) Edge-preserving diffusion stepper used by _voxel_presmooth.
_volume_matched_threshold(sdf, target_inside_voxels) Iso selection that matches the original inside count.
_median_edge_length(mesh) Median of unique edge lengths; used to normalize the Taubin polish.
Data types and units
- Points and normals: np.ndarray, shape (N, 3), world units.
- Voxel grids: torch.Tensor, shape (D, D, D), uint8/bool, 1 = inside.
- Meshes: trimesh.Trimesh in world coordinates.
- voxel_size is in world units and is passed to marching cubes as pitch.
Notes
- The cubic path aims for stable, interpretable “voxel look” with minimal flicker between runs. No erosion by default to protect thin volumes.
- The smoothed path operates in SDF space, not on the triangle mesh. This removes terracing while keeping the overall form.
- The Taubin step is deliberately tiny and optional. Heavy smoothing belongs in SDF space, not post-mesh.
AnalysisMesh(vertices, faces, face_normals=None)
¶
Lightweight quad mesh container. Holds raw numpy arrays and builds
a Ladybug Mesh3D or trimesh Trimesh on demand.
Attributes:
| Name | Type | Description |
|---|---|---|
vertices |
(V, 3) float64 ndarray — quad corner positions.
|
|
faces |
(N, 4) int32 ndarray — quad face vertex indices.
|
|
face_normals |
(N, 3) float32 ndarray or None — outward unit normals per face.
|
|
voxelize(envelope_mesh, config, device=None)
¶
Rasterize the envelope into a padded cubic voxel grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
envelope_mesh
|
Trimesh
|
|
required |
config
|
user_config
|
|
required |
device
|
device or None
|
|
None
|
Returns:
| Name | Type | Description |
|---|---|---|
voxel_grid |
(Tensor(D, D, D), uint8)
|
1 indicates inside/filled. |
grid_origin |
ndarray(3)
|
World-space min corner for index (0,0,0). |
grid_extent |
float
|
Physical cube size (meters). |
grid_resolution |
int
|
Number of voxels per side (D). |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If voxelization returns an empty grid. |
sample_surface(insolation_mesh, config)
¶
Sample quasi-planar patches to produce point/normal sets for carving.
Returns:
| Name | Type | Description |
|---|---|---|
sample_points |
ndarray(N, 3)
|
|
sample_normals |
ndarray(N, 3)
|
|
analysis_mesh |
Mesh3D or None
|
The analysis mesh whose face centroids are the sample points. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If no points are sampled from the insolation surface. |
finalize_mesh(carved_grid, grid_origin, config)
¶
Convert carved voxels to a mesh and clean results.
Behavior
• If apply_smoothing is False:
prune small components + gentle morphology (majority + closing),
cubic meshing, then cleanup_mesh.
• If apply_smoothing is True:
prune small components,
SDF presmooth + marching cubes, then cleanup_mesh,
optional micro Taubin polish capped to 0..6 iterations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
carved_grid
|
Tensor(D, D, D)
|
Binary occupancy after carving. |
required |
grid_origin
|
ndarray(3)
|
World-space min corner for index (0,0,0). |
required |
config
|
user_config
|
|
required |
Returns:
| Name | Type | Description |
|---|---|---|
cleaned_voxels |
Tensor(D, D, D)
|
Post-pruned (and possibly morphologically cleaned) occupancy. |
initial_mesh |
Trimesh
|
Mesh directly from the selected meshing path (pre-cleanup copy). |
final_mesh |
Trimesh
|
Cleaned (and optionally micro-polished) mesh. |
plane_frame(surface_normal)
¶
Return two orthonormal vectors spanning the plane orthogonal to surface_normal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
surface_normal
|
(3,) array_like
|
Plane normal (need not be unit length). |
required |
Returns:
| Type | Description |
|---|---|
axis_u, axis_v : (3,) np.ndarray
|
Unit vectors forming a right-handed 2D basis in the plane. |
Notes
- The normal is normalized internally.
- A stable reference axis is chosen to avoid degeneracy when the normal is almost aligned with X.
- Used by
sample_planar_surfaceto build a local 2D sampling frame.
sample_planar_surface(mesh, sample_step=1.0, include_boundary=True)
¶
Uniformly sample points on a strictly planar submesh and return per-point normals.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Trimesh
|
Single planar connected component (convex or with holes). |
required |
sample_step
|
float
|
Spacing of the sampling grid in world units. |
1.0
|
include_boundary
|
bool
|
If True, include points that fall on polygon boundaries; otherwise drop them. |
True
|
Returns:
| Name | Type | Description |
|---|---|---|
points |
(N, 3) float32 np.ndarray
|
Sample locations in world coordinates. |
normals |
(N, 3) float32 np.ndarray
|
Unit normals (constant over the patch, oriented consistently with the mesh). |
Notes
- Builds a local 2D grid in the plane (see
plane_frame) and rasterizes the polygon footprint to select in-polygon grid sites. - Used by
discretize_surface_with_normalsfor planar-only sampling.
discretize_surface_with_normals(mesh, sample_step=1.0, coplanarity_tol_deg=5.0)
¶
Sample nearly planar regions of a mesh and return point/normal pairs.
Uses the fast Shapely-based rasterizer (sample_planar_surface) for
point/normal generation and builds a quad analysis mesh from the same
2D grid. The mesh has a 1:1 face-to-point mapping suitable for
per-face coloring (e.g. "LB Spatial Heatmap" in GH/Rhino).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Trimesh
|
Input triangle mesh (test surface). |
required |
sample_step
|
float
|
Sampling grid spacing in world units. |
1.0
|
coplanarity_tol_deg
|
float
|
Maximum face-normal deviation (degrees) to consider a component planar. |
5.0
|
Returns:
| Name | Type | Description |
|---|---|---|
points |
(N, 3) float32 np.ndarray
|
Sample points (centroids of the analysis-mesh quads). |
normals |
(N, 3) float32 np.ndarray
|
Outward-facing normals at each sample point. |
analysis_mesh |
AnalysisMesh or None
|
Combined quad analysis mesh, or None if no points were sampled. |
voxelize_and_clean(mesh, voxel_size=1.0, margin_frac=0.2, min_voxels=100)
¶
Convenience wrapper: voxelize a mesh and prune tiny connected components.
Returns:
| Name | Type | Description |
|---|---|---|
voxels |
(D, D, D) uint8 torch.Tensor
|
Binary occupancy (1 = inside/filled). |
origin |
(3,) np.ndarray
|
World-space min corner of the grid. |
grid_extent |
float
|
Physical cube size covered by the grid. |
resolution |
int
|
Voxel resolution (D). |
See Also
voxelize_mesh, prune_voxels
voxelize_mesh(mesh, voxel_size=1.0, margin_frac=0.2, device=None)
¶
Rasterize a watertight mesh into a padded binary voxel grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Trimesh
|
|
required |
voxel_size
|
float
|
Edge length of one voxel in world units. |
1.0
|
margin_frac
|
float
|
Padding on each side as a fraction of the mesh AABB's max side. |
0.2
|
device
|
device or None
|
Target device for the returned tensor. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
voxels |
(D, D, D) uint8 torch.Tensor
|
1 where inside/filled, 0 otherwise. |
origin |
(3,) np.ndarray
|
World-space min corner of the grid. |
grid_extent |
float
|
Physical size of the cubic domain. |
resolution |
int
|
Number of voxels per side (D). |
Notes
- Uses trimesh ray-based voxelization and scipy cavity fill.
- Grid is axis-aligned and cubic by construction.
- No GPU dependency for voxelization itself (runs on CPU via trimesh).
prune_voxels(voxels, min_voxels)
¶
Remove connected components smaller than min_voxels (26-connectivity).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
voxels
|
(D, D, D) uint8/bool torch.Tensor
|
|
required |
min_voxels
|
int
|
Size threshold; components with fewer voxels are removed. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
cleaned |
(D, D, D) uint8 torch.Tensor
|
Binary occupancy on the same device as |
Notes
- Used on the smoothed path (
apply_smoothing=True) prior to SDF smoothing.
prune_voxels_morph(voxels, min_voxels)
¶
Gentle cleanup for the cubic (unsmoothed) path.
Steps
- Remove tiny connected components (26-connectivity).
- Apply a 3×3×3 majority filter (keeps voxels with ≥3 neighbors) to suppress isolated specks.
- Run a single binary closing with a 6-connected structuring element to seal pinholes without shrinking slabs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
voxels
|
(D, D, D) uint8/bool torch.Tensor
|
|
required |
min_voxels
|
int
|
|
required |
Returns:
| Name | Type | Description |
|---|---|---|
cleaned |
(D, D, D) uint8 torch.Tensor
|
|
polish_mesh_taubin(mesh, iters=2)
¶
Apply a tiny, scale-normalized Taubin pass to knock down micro-ripples.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Trimesh
|
|
required |
iters
|
int
|
Small integer in [0, 6]. If 0, the mesh is returned unchanged. |
2
|
Returns:
| Name | Type | Description |
|---|---|---|
mesh |
Trimesh
|
|
Notes
- Uses fixed stable parameters (λ≈0.28, ν≈−0.31) and normalizes by the median edge length to avoid scale sensitivity.
- Intended only after the SDF+MC path. Keep very small to avoid shape drift.
mesh_from_voxels(voxels, min_corner, voxel_size)
¶
Construct a blocky (cubic) mesh from a binary occupancy grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
voxels
|
(D, D, D) uint8/bool torch.Tensor
|
|
required |
min_corner
|
(3,) array_like
|
World-space origin of the grid (maps index [0,0,0] to this point). |
required |
voxel_size
|
float
|
Size of one voxel in world units. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
mesh |
Trimesh
|
Unprocessed triangle mesh (process=False). Call |
See Also
mesh_from_voxels_smoothed, mesh_from_voxels_select
mesh_from_voxels_smoothed(voxels, min_corner, voxel_size)
¶
Construct a smooth mesh by conturing a smoothed SDF with marching cubes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
voxels
|
(D, D, D) uint8/bool torch.Tensor
|
Binary occupancy (1 = inside). |
required |
min_corner
|
(3,) array_like
|
World-space grid origin. |
required |
voxel_size
|
float
|
World units per voxel (passed as |
required |
Returns:
| Name | Type | Description |
|---|---|---|
mesh |
Trimesh
|
Triangle mesh positioned in world coordinates. |
Notes
- Internally calls
_voxel_presmoothand chooses an iso-value via_volume_matched_thresholdto preserve volume.
mesh_from_voxels_select(voxels, min_corner, voxel_size, apply_smoothing)
¶
Dispatch to cubic or SDF-smoothed meshing based on apply_smoothing.
Returns:
| Name | Type | Description |
|---|---|---|
mesh |
Trimesh
|
|
See Also
mesh_from_voxels, mesh_from_voxels_smoothed
cleanup_mesh(mesh, min_face_count=100, light=False)
¶
Repair and prune small fragments from a mesh.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Trimesh
|
|
required |
min_face_count
|
int
|
Fragments with fewer faces than this are discarded. |
100
|
light
|
bool
|
If True, skip expensive repair steps (fill_holes, fix_inversion, merge_vertices). Use for cubic meshes which are already watertight with correct winding. ~10x faster on large meshes. |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
mesh |
Trimesh
|
Cleaned mesh (may be a concatenation of surviving components). |