Spaces:
Running on Zero
Running on Zero
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| import numpy as np | |
| import open3d as o3d | |
| import open3d.visualization.gui as gui | |
| import open3d.visualization.rendering as rendering | |
| import torch | |
| class Settings: | |
| UNLIT = "defaultUnlit" | |
| LIT = "defaultLit" | |
| NORMALS = "normals" | |
| DEPTH = "depth" | |
| LINE = "unlitLine" | |
| Transparency = "defaultLitTransparency" | |
| LitSSR = "defaultLitSSR" | |
| DEFAULT_PROFILE_NAME = "Bright day with sun at +Y [default]" | |
| POINT_CLOUD_PROFILE_NAME = "Cloudy day (no direct sun)" | |
| CUSTOM_PROFILE_NAME = "Custom" | |
| LIGHTING_PROFILES = { | |
| DEFAULT_PROFILE_NAME: { | |
| "ibl_intensity": 45000, | |
| "sun_intensity": 45000, | |
| "sun_dir": [0.577, -0.577, -0.577], | |
| # "ibl_rotation": | |
| "use_ibl": True, | |
| "use_sun": True, | |
| }, | |
| "Bright day with sun at -Y": { | |
| "ibl_intensity": 45000, | |
| "sun_intensity": 45000, | |
| "sun_dir": [0.577, 0.577, 0.577], | |
| # "ibl_rotation":[] | |
| "use_ibl": True, | |
| "use_sun": True, | |
| }, | |
| "Bright day with sun at +Z": { | |
| "ibl_intensity": 45000, | |
| "sun_intensity": 45000, | |
| "sun_dir": [0.577, 0.577, -0.577], | |
| # "ibl_rotation": | |
| "use_ibl": True, | |
| "use_sun": True, | |
| }, | |
| "Less Bright day with sun at +Y": { | |
| "ibl_intensity": 35000, | |
| "sun_intensity": 50000, | |
| "sun_dir": [0.577, -0.577, -0.577], | |
| # "ibl_rotation": | |
| "use_ibl": True, | |
| "use_sun": True, | |
| }, | |
| "Less Bright day with sun at -Y": { | |
| "ibl_intensity": 35000, | |
| "sun_intensity": 50000, | |
| "sun_dir": [0.577, 0.577, 0.577], | |
| # "ibl_rotation": | |
| "use_ibl": True, | |
| "use_sun": True, | |
| }, | |
| "Less Bright day with sun at +Z": { | |
| "ibl_intensity": 35000, | |
| "sun_intensity": 50000, | |
| "sun_dir": [0.577, 0.577, -0.577], | |
| # "ibl_rotation": | |
| "use_ibl": True, | |
| "use_sun": True, | |
| }, | |
| POINT_CLOUD_PROFILE_NAME: { | |
| "ibl_intensity": 60000, | |
| "sun_intensity": 50000, | |
| "use_ibl": True, | |
| "use_sun": False, | |
| # "ibl_rotation": | |
| }, | |
| } | |
| DEFAULT_MATERIAL_NAME = "Polished ceramic [default]" | |
| PREFAB = { | |
| DEFAULT_MATERIAL_NAME: { | |
| "metallic": 0.0, | |
| "roughness": 0.7, | |
| "reflectance": 0.5, | |
| "clearcoat": 0.2, | |
| "clearcoat_roughness": 0.2, | |
| "anisotropy": 0.0, | |
| }, | |
| "Metal (rougher)": { | |
| "metallic": 1.0, | |
| "roughness": 0.5, | |
| "reflectance": 0.9, | |
| "clearcoat": 0.0, | |
| "clearcoat_roughness": 0.0, | |
| "anisotropy": 0.0, | |
| }, | |
| "Metal (smoother)": { | |
| "metallic": 1.0, | |
| "roughness": 0.3, | |
| "reflectance": 0.9, | |
| "clearcoat": 0.0, | |
| "clearcoat_roughness": 0.0, | |
| "anisotropy": 0.0, | |
| }, | |
| "Plastic": { | |
| "metallic": 0.0, | |
| "roughness": 0.5, | |
| "reflectance": 0.5, | |
| "clearcoat": 0.5, | |
| "clearcoat_roughness": 0.2, | |
| "anisotropy": 0.0, | |
| }, | |
| "Glazed ceramic": { | |
| "metallic": 0.0, | |
| "roughness": 0.5, | |
| "reflectance": 0.9, | |
| "clearcoat": 1.0, | |
| "clearcoat_roughness": 0.1, | |
| "anisotropy": 0.0, | |
| }, | |
| "Clay": { | |
| "metallic": 0.0, | |
| "roughness": 1.0, | |
| "reflectance": 0.5, | |
| "clearcoat": 0.1, | |
| "clearcoat_roughness": 0.287, | |
| "anisotropy": 0.0, | |
| }, | |
| "Transparency": { | |
| "metallic": 0.0, | |
| "roughness": 0.0, | |
| "reflectance": 0.0, | |
| "clearcoat": 1.0, | |
| "clearcoat_roughness": 0.0, | |
| "anisotropy": 0.0, | |
| }, | |
| } | |
| def __init__(self): | |
| # self.mouse_model = gui.SceneWidget.Controls.ROTATE_CAMERA | |
| self.prefab = Settings.DEFAULT_MATERIAL_NAME | |
| self.bg_color = gui.Color(1, 1, 1) | |
| self.show_skybox = True | |
| self.show_ground_plane = True | |
| self.show_axes = False | |
| self.use_ibl = True | |
| self.use_sun = True | |
| self.new_ibl_name = None # clear to None after loading | |
| self.ibl_intensity = 45000 | |
| self.sun_intensity = 45000 | |
| self.sun_dir = [0.577, -0.577, -0.577] | |
| self.sun_color = gui.Color(1, 1, 1) | |
| self.apply_material = True # clear to False after processing | |
| self._materials = { | |
| Settings.LIT: rendering.MaterialRecord(), | |
| Settings.UNLIT: rendering.MaterialRecord(), | |
| Settings.NORMALS: rendering.MaterialRecord(), | |
| Settings.DEPTH: rendering.MaterialRecord(), | |
| Settings.LINE: rendering.MaterialRecord(), | |
| Settings.Transparency: rendering.MaterialRecord(), | |
| Settings.LitSSR: rendering.MaterialRecord(), | |
| } | |
| self._materials[Settings.LIT].base_color = [0.9, 0.9, 0.9, 1.0] | |
| self._materials[Settings.LIT].shader = Settings.LIT | |
| self._materials[Settings.UNLIT].base_color = [0.9, 0.9, 0.9, 1.0] | |
| self._materials[Settings.UNLIT].shader = Settings.UNLIT | |
| self._materials[Settings.LINE].base_color = [0.9, 0.9, 0.9, 1.0] | |
| self._materials[Settings.LINE].shader = Settings.LINE | |
| self._materials[Settings.LINE].line_width = 3 | |
| self._materials[Settings.Transparency].base_color = [0.467, 0.467, 0.467, 0.2] | |
| self._materials[Settings.Transparency].base_color = [0.9, 0.9, 0.9, 0.5] | |
| self._materials[Settings.Transparency].shader = Settings.Transparency | |
| self._materials[Settings.Transparency].thickness = 1.0 | |
| self._materials[Settings.Transparency].transmission = 1.0 | |
| self._materials[Settings.Transparency].absorption_distance = 10 | |
| self._materials[Settings.Transparency].absorption_color = [0.5, 0.5, 0.5] | |
| self._materials[Settings.LitSSR].base_color = [0.467, 0.467, 0.467, 0.2] | |
| self._materials[Settings.LitSSR].shader = Settings.LitSSR | |
| self._materials[Settings.LitSSR].thickness = 1.0 | |
| self._materials[Settings.LitSSR].transmission = 1.0 | |
| self._materials[Settings.LitSSR].absorption_distance = 10 | |
| self._materials[Settings.LitSSR].absorption_color = [0.5, 0.5, 0.5] | |
| self._materials[Settings.NORMALS].shader = Settings.NORMALS | |
| self._materials[Settings.DEPTH].shader = Settings.DEPTH | |
| # Conveniently, assigning from self._materials[...] assigns a reference, | |
| # not a copy, so if we change the property of a material, then switch | |
| # to another one, then come back, the old setting will still be there. | |
| self.material = self._materials[Settings.LIT] | |
| def set_material(self, name): | |
| self.material = self._materials[name] | |
| self.apply_material = True | |
| def apply_material_prefab(self, name): | |
| # assert (self.material.shader == Settings.LIT) | |
| self.prefab = name | |
| for key, val in Settings.PREFAB[name].items(): | |
| setattr(self.material, "base_" + key, val) | |
| def apply_lighting_profile(self, name): | |
| """ | |
| It takes a string as an argument, and then sets the attributes of the object to the values in the | |
| dictionary that is associated with that string | |
| Args: | |
| name: The name of the lighting profile. | |
| """ | |
| profile = Settings.LIGHTING_PROFILES[name] | |
| for key, val in profile.items(): | |
| setattr(self, key, val) | |
| def get_ground( | |
| scale, | |
| center_x, | |
| center_z, | |
| grid_size=0.5, | |
| color1=None, | |
| color2=None, | |
| ): | |
| if color1 is None: | |
| color1 = [0.8, 0.9, 0.9] | |
| if color2 is None: | |
| color2 = [0.6, 0.7, 0.7] | |
| half_size = scale / 2 | |
| min_x = center_x - half_size | |
| min_z = center_z - half_size | |
| max_x = center_x + half_size | |
| max_z = center_z + half_size | |
| # Snap to grid | |
| min_x = grid_size * np.floor(min_x / grid_size) | |
| min_z = grid_size * np.floor(min_z / grid_size) | |
| max_x = grid_size * np.ceil(max_x / grid_size) | |
| max_z = grid_size * np.ceil(max_z / grid_size) | |
| vertices = [] | |
| faces = [] | |
| vertex_colors = [] | |
| eps = 1e-4 # Small offset to prevent z-fighting | |
| for i, x in enumerate(np.arange(min_x, max_x, grid_size)): | |
| for j, z in enumerate(np.arange(min_z, max_z, grid_size)): | |
| # Add small offset to prevent z-fighting | |
| x_offset = ((i % 2 * 2) - 1) * eps | |
| z_offset = ((j % 2 * 2) - 1) * eps | |
| # Create vertices for this grid cell | |
| v1 = np.array([x + x_offset, 0, z + z_offset]) | |
| v2 = np.array([x + x_offset, 0, z + grid_size + z_offset]) | |
| v3 = np.array([x + grid_size + x_offset, 0, z + grid_size + z_offset]) | |
| v4 = np.array([x + grid_size + x_offset, 0, z + z_offset]) | |
| # Add vertices for both sides of the cell | |
| offset = np.array([0, -eps, 0]) # For bottom face | |
| vertices.extend([v1, v2, v3, v4, v1 + offset, v2 + offset, v3 + offset, v4 + offset]) | |
| # Create faces (triangles) | |
| idx = len(vertices) - 8 | |
| faces.extend( | |
| [ | |
| [idx, idx + 1, idx + 2], # Top face triangle 1 | |
| [idx + 2, idx + 3, idx], # Top face triangle 2 | |
| [idx + 4, idx + 7, idx + 6], # Bottom face triangle 1 | |
| [idx + 6, idx + 5, idx + 4], # Bottom face triangle 2 | |
| ] | |
| ) | |
| # Set vertex colors based on checkerboard pattern | |
| vertex_color = color1 if (i + j) % 2 == 0 else color2 | |
| vertex_colors.extend([vertex_color] * 8) | |
| return ( | |
| torch.from_numpy(np.array(vertices)), | |
| torch.from_numpy(np.array(faces)), | |
| torch.from_numpy(np.array(vertex_colors)), | |
| ) | |
| def create_meshes(verts, faces, colors): | |
| """ | |
| :param verts (B, V, 3) | |
| :param faces (B, F, 3) | |
| :param colors (B, V, 3) | |
| """ | |
| mesh = o3d.geometry.TriangleMesh( | |
| vertices=o3d.utility.Vector3dVector(verts.cpu().numpy()), | |
| triangles=o3d.utility.Vector3iVector(faces.cpu().numpy()), | |
| ) | |
| mesh.compute_vertex_normals() | |
| if len(colors.shape) == 1: | |
| colors = colors[None, :].repeat(len(verts), 1) | |
| mesh.vertex_colors = o3d.utility.Vector3dVector(colors.cpu().numpy()) | |
| return mesh | |