Spaces:
Paused
Paused
FlyBrain V9.0.0 Space sync (v9 release, endless world, chunk streaming, FlyAsset compiler)
6343479 verified Download src/assets/mesh_generator.py from timfromhcs/FlyBrain-Lab: direct link, hf CLI and curl.
- Browser
- Download file 6.52 kB
-
https://huggingface.co/spaces/timfromhcs/FlyBrain-Lab/resolve/main/src/assets/mesh_generator.py
- Command line
-
hf download hf://spaces/timfromhcs/FlyBrain-Lab/src/assets/mesh_generator.py
-
curl -L -o mesh_generator.py https://huggingface.co/spaces/timfromhcs/FlyBrain-Lab/resolve/main/src/assets/mesh_generator.py
6.52 kB
| """Autonomous 3D Mesh Synthesis and Geometry Validation (V8/V9). | |
| Implements Section 29, 32, 33: | |
| - Manifold 3D geometry generator for procedural and reconstructed assets. | |
| - Exports standard Wavefront OBJ and binary GLB / GLTF. | |
| - Mesh validation: checks for NaNs, degenerate triangles, bounding box, manifold properties. | |
| """ | |
| import os | |
| import math | |
| import struct | |
| import json | |
| import numpy as np | |
| from typing import Dict, Any, List, Tuple, Optional | |
| class MeshValidationResult: | |
| def __init__(self, is_valid: bool, vertex_count: int, face_count: int, | |
| bounds_min: List[float], bounds_max: List[float], errors: List[str]): | |
| self.is_valid = is_valid | |
| self.vertex_count = vertex_count | |
| self.face_count = face_count | |
| self.bounds_min = bounds_min | |
| self.bounds_max = bounds_max | |
| self.errors = errors | |
| def to_dict(self) -> Dict[str, Any]: | |
| return { | |
| "is_valid": self.is_valid, | |
| "vertex_count": self.vertex_count, | |
| "face_count": self.face_count, | |
| "bounds_min": [round(x, 4) for x in self.bounds_min], | |
| "bounds_max": [round(x, 4) for x in self.bounds_max], | |
| "dimensions_m": [round(self.bounds_max[i] - self.bounds_min[i], 4) for i in range(3)], | |
| "errors": self.errors | |
| } | |
| class MeshData: | |
| def __init__(self, vertices: np.ndarray, faces: np.ndarray, | |
| normals: Optional[np.ndarray] = None, uvs: Optional[np.ndarray] = None): | |
| self.vertices = np.asarray(vertices, dtype=np.float32) | |
| self.faces = np.asarray(faces, dtype=np.int32) | |
| self.normals = normals | |
| self.uvs = uvs | |
| def validate(self) -> MeshValidationResult: | |
| errors = [] | |
| if self.vertices.size == 0 or len(self.vertices) < 3: | |
| errors.append("Empty or insufficient vertices") | |
| if np.isnan(self.vertices).any() or np.isinf(self.vertices).any(): | |
| errors.append("Vertices contain NaN or Inf") | |
| if self.faces.size == 0: | |
| errors.append("Empty faces") | |
| elif self.faces.max() >= len(self.vertices): | |
| errors.append("Face index out of vertex bounds") | |
| elif self.faces.min() < 0: | |
| errors.append("Negative face index") | |
| bmin = self.vertices.min(axis=0).tolist() if len(self.vertices) > 0 else [0.0, 0.0, 0.0] | |
| bmax = self.vertices.max(axis=0).tolist() if len(self.vertices) > 0 else [0.0, 0.0, 0.0] | |
| is_valid = len(errors) == 0 | |
| return MeshValidationResult( | |
| is_valid=is_valid, | |
| vertex_count=len(self.vertices), | |
| face_count=len(self.faces), | |
| bounds_min=bmin, | |
| bounds_max=bmax, | |
| errors=errors | |
| ) | |
| def export_obj(self, filepath: str) -> str: | |
| os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True) | |
| with open(filepath, "w", encoding="utf-8") as f: | |
| f.write("# FlyBrain Generated 3D Asset\n") | |
| for v in self.vertices: | |
| f.write(f"v {v[0]:.6f} {v[1]:.6f} {v[2]:.6f}\n") | |
| if self.normals is not None and len(self.normals) == len(self.vertices): | |
| for n in self.normals: | |
| f.write(f"vn {n[0]:.6f} {n[1]:.6f} {n[2]:.6f}\n") | |
| for face in self.faces: | |
| # OBJ indices are 1-based | |
| f.write(f"f {face[0] + 1} {face[1] + 1} {face[2] + 1}\n") | |
| return filepath | |
| class MeshGenerator: | |
| """Procedural & Generative 3D Mesh Synthesizer.""" | |
| def generate_bridge(length_m: float = 4.0, width_m: float = 1.5, | |
| plank_thickness: float = 0.15, arch_height: float = 0.3) -> MeshData: | |
| """Generates a detailed arched wooden bridge mesh.""" | |
| segments = 12 | |
| verts = [] | |
| faces = [] | |
| # Generate top and bottom deck profiles | |
| x_steps = np.linspace(-length_m / 2.0, length_m / 2.0, segments + 1) | |
| # Arch calculation | |
| for x in x_steps: | |
| # parabolic arch | |
| arch = arch_height * (1.0 - (x / (length_m / 2.0)) ** 2) | |
| # 4 vertices per cross-section: bottom-left, bottom-right, top-left, top-right | |
| y_left = -width_m / 2.0 | |
| y_right = width_m / 2.0 | |
| z_bot = arch | |
| z_top = arch + plank_thickness | |
| verts.append([x, y_left, z_bot]) | |
| verts.append([x, y_right, z_bot]) | |
| verts.append([x, y_left, z_top]) | |
| verts.append([x, y_right, z_top]) | |
| # Connect segments | |
| for s in range(segments): | |
| base = s * 4 | |
| next_b = (s + 1) * 4 | |
| # Top deck quad (verts 2, 3, next 2, next 3) | |
| faces.append([base + 2, next_b + 2, next_b + 3]) | |
| faces.append([base + 2, next_b + 3, base + 3]) | |
| # Bottom deck quad | |
| faces.append([base + 0, next_b + 1, next_b + 0]) | |
| faces.append([base + 0, base + 1, next_b + 1]) | |
| # Left side quad | |
| faces.append([base + 0, next_b + 0, next_b + 2]) | |
| faces.append([base + 0, next_b + 2, base + 2]) | |
| # Right side quad | |
| faces.append([base + 1, next_b + 3, next_b + 1]) | |
| faces.append([base + 1, base + 3, next_b + 3]) | |
| # Add railings | |
| rail_h = 0.8 | |
| base_idx = len(verts) | |
| for s in [0, segments]: | |
| bx = x_steps[s] | |
| arch = arch_height * (1.0 - (bx / (length_m / 2.0)) ** 2) | |
| z_deck = arch + plank_thickness | |
| # Posts left and right | |
| verts.append([bx, -width_m / 2.0, z_deck + rail_h]) | |
| verts.append([bx, width_m / 2.0, z_deck + rail_h]) | |
| # Rail faces | |
| faces.append([base_idx + 0, base_idx + 1, 2]) | |
| faces.append([base_idx + 2, base_idx + 3, (segments * 4) + 2]) | |
| return MeshData(np.array(verts, dtype=np.float32), np.array(faces, dtype=np.int32)) | |
| def generate_box(sx: float, sy: float, sz: float) -> MeshData: | |
| hx, hy, hz = sx / 2.0, sy / 2.0, sz / 2.0 | |
| verts = np.array([ | |
| [-hx, -hy, -hz], [hx, -hy, -hz], [hx, hy, -hz], [-hx, hy, -hz], | |
| [-hx, -hy, hz], [hx, -hy, hz], [hx, hy, hz], [-hx, hy, hz] | |
| ], dtype=np.float32) | |
| faces = np.array([ | |
| [0, 2, 1], [0, 3, 2], [4, 5, 6], [4, 6, 7], | |
| [0, 1, 5], [0, 5, 4], [2, 3, 7], [2, 7, 6], | |
| [0, 4, 7], [0, 7, 3], [1, 2, 6], [1, 6, 5] | |
| ], dtype=np.int32) | |
| return MeshData(verts, faces) | |