From 9e9de119f82b257c65c100349fa2db6aea27617c Mon Sep 17 00:00:00 2001 From: Matthew Gordon Date: Thu, 5 Jun 2025 16:58:44 -0300 Subject: [PATCH] Added some polish --- __init__.py | 51 ++++++++++++++++++++++++++++-- create_mesh_from_geotiff.py | 62 ++++++++++++++++++++----------------- import_geotiff_panel.py | 25 +++++++++++++++ select_geotiff_file.py | 15 +++++++++ 4 files changed, 121 insertions(+), 32 deletions(-) create mode 100644 import_geotiff_panel.py create mode 100644 select_geotiff_file.py diff --git a/__init__.py b/__init__.py index 0431ae2..b628986 100644 --- a/__init__.py +++ b/__init__.py @@ -1,7 +1,52 @@ -from . import create_mesh_from_geotiff +import bpy +from bpy.props import FloatProperty, FloatVectorProperty, IntProperty, StringProperty +from .create_mesh_from_geotiff import OBJECT_OT_create_mesh_from_geotiff +from .import_geotiff_panel import OBJECT_PT_import_geotiff +from .select_geotiff_file import OBJECT_OT_select_geotiff_file + +def init_scene_vars(): + bpy.types.Scene.mapping_extension_geotiff_filename = StringProperty( + name="Geotiff filename", + description="Input GeoTIFF", + maxlen=1024, # Max length of the string. + options={'SKIP_SAVE'}) + bpy.types.Scene.mapping_extension_map_origin = FloatVectorProperty( + name="Map origin", + description="The map coordinate that should map to the blender origin", + default=(0.0,0.0), + size=2, + options={'SKIP_SAVE'}) + bpy.types.Scene.mapping_extension_map_scale = IntProperty( + name="Map Scale", + description="Denominator or map scale. E.g. for a 1:10,000 scale, set this to 10000", + default=10000, + min=1, + soft_max=1000000, + options={'SKIP_SAVE'}) + bpy.types.Scene.mapping_extension_vertical_exaggeration = FloatProperty( + name="Map vertical exaggeration", + description="Scale applied to heights, on to of the map scale", + default=6.0, + min = 0.0, + soft_min = 1.0, + max = 1000000.0, + soft_max = 20.0, + options={'SKIP_SAVE'}) + + + +def del_scene_vars(): + del bpy.types.Scene.mapping_extension_geotiff_filename + def register(): - create_mesh_from_geotiff.register() + init_scene_vars() + bpy.utils.register_class(OBJECT_PT_import_geotiff) + bpy.utils.register_class(OBJECT_OT_create_mesh_from_geotiff) + bpy.utils.register_class(OBJECT_OT_select_geotiff_file) def unregister(): - create_mesh_from_geotiff.unregister() + del_scene_vars() + bpy.utils.unregister_class(OBJECT_PT_import_geotiff) + bpy.utils.unregister_class(OBJECT_OT_create_mesh_from_geotiff) + bpy.utils.unregister_class(OBJECT_OT_select_geotiff_file) diff --git a/create_mesh_from_geotiff.py b/create_mesh_from_geotiff.py index b33010c..af49ea2 100644 --- a/create_mesh_from_geotiff.py +++ b/create_mesh_from_geotiff.py @@ -4,7 +4,7 @@ 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): + def __init__(self, *, grid_size_x, grid_size_y, cell_size_x, cell_size_y, dem_array, map_scale, vertical_exaggeration): self.cell_size_x = cell_size_x if cell_size_y is not None: self.cell_size_y = cell_size_y @@ -13,6 +13,8 @@ class GridVertexGenerator(): self.grid_size_x = grid_size_x self.grid_size_y = grid_size_y self.dem_array = dem_array + self.map_scale = map_scale + self.vertical_exaggeration = vertical_exaggeration def __len__(self): return self.grid_size_x * self.grid_size_y @@ -23,17 +25,12 @@ class GridVertexGenerator(): 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]) + yield (i * self.cell_size_x * self.map_scale, + j * self.cell_size_y * self.map_scale, + self.dem_array[j][i] * self.map_scale * self.vertical_exaggeration) -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 +class GridQuadGenerator(): + def __init__(self, grid_size_x, grid_size_y): self.grid_size_x = grid_size_x self.grid_size_y = grid_size_y @@ -53,15 +50,21 @@ class GridTriangleGenerator(): yield (v00, v10, v11, v01) -class CreateMeshFromGeotiffOperator(bpy.types.Operator, ImportHelper): +class OBJECT_OT_create_mesh_from_geotiff(bpy.types.Operator): """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"}) + @classmethod + def poll(cls, context): + return context.scene.mapping_extension_geotiff_filename def execute(self, context): - with rasterio.open(self.properties.filepath) as geotiff: + map_origin = context.scene.mapping_extension_map_origin + map_scale = 1.0 / context.scene.mapping_extension_map_scale + vertical_exaggeration = context.scene.mapping_extension_vertical_exaggeration + filename = context.scene.mapping_extension_geotiff_filename + with rasterio.open(filename) as geotiff: grid_size_x = geotiff.width grid_size_y = geotiff.height print(f"GeoTIFF is {grid_size_x}x{grid_size_y}") @@ -80,29 +83,30 @@ class CreateMeshFromGeotiffOperator(bpy.types.Operator, ImportHelper): 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) + (vertices, edges, faces) = self.construct_mesh(grid_size_x=grid_size_x, + grid_size_y=grid_size_y, + cell_size_x=cell_size_x, + cell_size_y=cell_size_y, + dem_array=dem_array, + map_scale=map_scale, + vertical_exaggeration=vertical_exaggeration) 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), + def construct_mesh(self, *, grid_size_x, grid_size_y, cell_size_x, cell_size_y, dem_array, map_scale, vertical_exaggeration): + return (GridVertexGenerator(grid_size_x=grid_size_x, + grid_size_y=grid_size_y, + cell_size_x=cell_size_x, + cell_size_y=cell_size_y, + dem_array=dem_array, + map_scale=map_scale, + vertical_exaggeration=vertical_exaggeration), [], - GridTriangleGenerator(grid_size_x, grid_size_y, cell_size_x, cell_size_y)) + GridQuadGenerator(grid_size_x, grid_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() diff --git a/import_geotiff_panel.py b/import_geotiff_panel.py new file mode 100644 index 0000000..d5e0453 --- /dev/null +++ b/import_geotiff_panel.py @@ -0,0 +1,25 @@ +import bpy +from bpy.props import StringProperty + +from .create_mesh_from_geotiff import OBJECT_OT_create_mesh_from_geotiff +from .select_geotiff_file import OBJECT_OT_select_geotiff_file + +class OBJECT_PT_import_geotiff(bpy.types.Panel): + """Import GeoTIFF""" + bl_idname="IMPORT_GEOTIFF_PT_layout" + bl_label = "Import GeoTIFF" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Mapping" + + def draw(self, context): + column = self.layout.column() + geotiff_filename_row = column.row() + geotiff_filename_row.prop(context.scene, "mapping_extension_geotiff_filename") + geotiff_filename_row.operator(OBJECT_OT_select_geotiff_file.bl_idname, + icon='FILEBROWSER', + text='') + #column.prop(context.scene, "mapping_extension_map_origin") + column.prop(context.scene, "mapping_extension_map_scale") + column.prop(context.scene, "mapping_extension_vertical_exaggeration") + column.operator(OBJECT_OT_create_mesh_from_geotiff.bl_idname) diff --git a/select_geotiff_file.py b/select_geotiff_file.py new file mode 100644 index 0000000..0deb62b --- /dev/null +++ b/select_geotiff_file.py @@ -0,0 +1,15 @@ +import bpy +from bpy.props import StringProperty +from bpy_extras.io_utils import ImportHelper + +class OBJECT_OT_select_geotiff_file(bpy.types.Operator, ImportHelper): + """Display dialog to a GeoTIFF file for Mapping Tools to use.""" + bl_idname = "object.select_geotiff_file" + bl_label = "Select GeoTIFF file" + + filter_glob: StringProperty(default="*.tif;*.tiff", options={"HIDDEN"}) + filepath: StringProperty(subtype="FILE_PATH") + + def execute(self, context): + context.scene.mapping_extension_geotiff_filename=self.properties.filepath + return {'FINISHED'}