import bpy from bpy.props import StringProperty from bpy_extras.io_utils import ImportHelper import rasterio class GridVertexGenerator(): def __init__(self, grid_size_x, grid_size_y, cell_size_x, cell_size_y, dem_array): self.cell_size_x = cell_size_x if cell_size_y is not None: self.cell_size_y = cell_size_y else: self.cell_size_y = cell_size_x self.grid_size_x = grid_size_x self.grid_size_y = grid_size_y self.dem_array = dem_array def __len__(self): return self.grid_size_x * self.grid_size_y def __iter__(self): return self._generator() def _generator(self): for j in range(self.grid_size_y): for i in range(self.grid_size_x): yield (i * self.cell_size_x, j * self.cell_size_y, self.dem_array[j][i]) class GridTriangleGenerator(): def __init__(self, grid_size_x, grid_size_y, cell_size_x, cell_size_y=None): self.cell_size_x = cell_size_x if cell_size_y is not None: self.cell_size_y = cell_size_y else: self.cell_size_y = cell_size_x self.grid_size_x = grid_size_x self.grid_size_y = grid_size_y def __len__(self): return (self.grid_size_x - 1) * (self.grid_size_y - 1) def __iter__(self): return self._generator() def _generator(self): for i in range(self.grid_size_x - 1): for j in range(self.grid_size_y - 1): v00 = j * self.grid_size_x + i v01 = (j + 1) * self.grid_size_x + i v10 = j * self.grid_size_x + i + 1 v11 = (j + 1) * self.grid_size_x + i + 1 yield (v00, v10, v11, v01) class CreateMeshFromGeotiffOperator(bpy.types.Operator, ImportHelper): """Create a new object from a GeoTIFF""" bl_idname = "object.create_mesh_from_geotiff" bl_label = "Mesh from GeoTIFF" filter_glob = StringProperty(default="*.tif;*.tiff", options={"HIDDEN"}) def execute(self, context): with rasterio.open(self.properties.filepath) as geotiff: grid_size_x = geotiff.width grid_size_y = geotiff.height print(f"GeoTIFF is {grid_size_x}x{grid_size_y}") bounds = geotiff.bounds print(f"GeoTIFF bounds: {bounds}") cell_size_x = (bounds.right - bounds.left) / geotiff.width cell_size_y = (bounds.bottom - bounds.top) / geotiff.height print(f"Calculated cell size: {cell_size_x} x {cell_size_y}") print("Reading GeoTIFF...") dem_array = geotiff.read(1) print("Processing values...") print("Done.") mesh = bpy.data.meshes.new("mesh") dem_obj = bpy.data.objects.new("DEM", mesh) collection = bpy.data.collections["Collection"] collection.objects.link(dem_obj) context.view_layer.objects.active = dem_obj print("Creating mesh data...") (vertices, edges, faces) = self.construct_mesh(grid_size_x, grid_size_y, cell_size_x, cell_size_y, dem_array) print("Constructing mesh...") mesh.from_pydata(vertices, edges, faces, shade_flat=False) print("Done.") mesh.validate(verbose=True) return {'FINISHED'} def construct_mesh(self, grid_size_x, grid_size_y, cell_size_x, cell_size_y, dem_array): return (GridVertexGenerator(grid_size_x, grid_size_y, cell_size_x, cell_size_y, dem_array), [], GridTriangleGenerator(grid_size_x, grid_size_y, cell_size_x, cell_size_y)) def menu_func(self, context): self.layout.operator(CreateMeshFromGeotiffOperator.bl_idname, text=CreateMeshFromGeotiffOperator.bl_label) def register(): bpy.utils.register_class(CreateMeshFromGeotiffOperator) bpy.types.VIEW3D_MT_add.append(menu_func) def unregister(): bpy.utils.unregister_class(CreateMeshFromGeotiffOperator) bpy.types.VIEW3D_MT_add.remove(menu_func) if __name__ == "__main__": register()