Compare commits

..

2 Commits

Author SHA1 Message Date
Matthew Gordon 9e9de119f8 Added some polish 2025-06-05 16:58:44 -03:00
Matthew Gordon 5d3676ee43 Add Mac and Windows wheels
This should make the plugin compatible with those platforms, but I
haven't tested it.
2025-06-05 09:35:53 -03:00
6 changed files with 129 additions and 34 deletions

View File

@ -9,7 +9,9 @@ TODO: make a proper build system.
For Blender 4.4: For Blender 4.4:
```sh ```sh
pip download rasterio --dest ./wheels --only-binary==:all: --python-version=3.11 pip download rasterio --dest ./wheels --only-binary=:all: --python-version=3.11 --platform=manylinux_2_17_x86_64
pip download rasterio --dest ./wheels --only-binary=:all: --python-version=3.11 --platform=macosx_11_0_arm64
pip download rasterio --dest ./wheels --only-binary=:all: --python-version=3.11 --platform=win_amd64
# Or replcace ".." below with any directory. # Or replcace ".." below with any directory.
# blender_mapping_tools-0.1.0.zip will be placed in that directory. # blender_mapping_tools-0.1.0.zip will be placed in that directory.
blender --command extension build --output-dir .. blender --command extension build --output-dir ..

View File

@ -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(): 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(): 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)

View File

@ -20,7 +20,11 @@ wheels = [
"./wheels/click-8.2.1-py3-none-any.whl", "./wheels/click-8.2.1-py3-none-any.whl",
"./wheels/click_plugins-1.1.1-py2.py3-none-any.whl", "./wheels/click_plugins-1.1.1-py2.py3-none-any.whl",
"./wheels/cligj-0.7.2-py3-none-any.whl", "./wheels/cligj-0.7.2-py3-none-any.whl",
"./wheels/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl",
"./wheels/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", "./wheels/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"./wheels/numpy-2.2.6-cp311-cp311-win_amd64.whl",
"./wheels/pyparsing-3.2.3-py3-none-any.whl", "./wheels/pyparsing-3.2.3-py3-none-any.whl",
"./wheels/rasterio-1.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" "./wheels/rasterio-1.4.1-cp311-cp311-macosx_11_0_arm64.whl",
"./wheels/rasterio-1.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"./wheels/rasterio-1.4.3-cp311-cp311-win_amd64.whl"
] ]

View File

@ -4,7 +4,7 @@ from bpy_extras.io_utils import ImportHelper
import rasterio import rasterio
class GridVertexGenerator(): 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 self.cell_size_x = cell_size_x
if cell_size_y is not None: if cell_size_y is not None:
self.cell_size_y = cell_size_y self.cell_size_y = cell_size_y
@ -13,6 +13,8 @@ class GridVertexGenerator():
self.grid_size_x = grid_size_x self.grid_size_x = grid_size_x
self.grid_size_y = grid_size_y self.grid_size_y = grid_size_y
self.dem_array = dem_array self.dem_array = dem_array
self.map_scale = map_scale
self.vertical_exaggeration = vertical_exaggeration
def __len__(self): def __len__(self):
return self.grid_size_x * self.grid_size_y return self.grid_size_x * self.grid_size_y
@ -23,17 +25,12 @@ class GridVertexGenerator():
def _generator(self): def _generator(self):
for j in range(self.grid_size_y): for j in range(self.grid_size_y):
for i in range(self.grid_size_x): for i in range(self.grid_size_x):
yield (i * self.cell_size_x, yield (i * self.cell_size_x * self.map_scale,
j * self.cell_size_y, j * self.cell_size_y * self.map_scale,
self.dem_array[j][i]) self.dem_array[j][i] * self.map_scale * self.vertical_exaggeration)
class GridTriangleGenerator(): class GridQuadGenerator():
def __init__(self, grid_size_x, grid_size_y, cell_size_x, cell_size_y=None): def __init__(self, grid_size_x, grid_size_y):
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_x = grid_size_x
self.grid_size_y = grid_size_y self.grid_size_y = grid_size_y
@ -53,15 +50,21 @@ class GridTriangleGenerator():
yield (v00, v10, v11, v01) 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""" """Create a new object from a GeoTIFF"""
bl_idname = "object.create_mesh_from_geotiff" bl_idname = "object.create_mesh_from_geotiff"
bl_label = "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): 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_x = geotiff.width
grid_size_y = geotiff.height grid_size_y = geotiff.height
print(f"GeoTIFF is {grid_size_x}x{grid_size_y}") 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) collection.objects.link(dem_obj)
context.view_layer.objects.active = dem_obj context.view_layer.objects.active = dem_obj
print("Creating mesh data...") 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...") print("Constructing mesh...")
mesh.from_pydata(vertices, edges, faces, shade_flat=False) mesh.from_pydata(vertices, edges, faces, shade_flat=False)
print("Done.") print("Done.")
mesh.validate(verbose=True) mesh.validate(verbose=True)
return {'FINISHED'} return {'FINISHED'}
def construct_mesh(self, 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_y, cell_size_x, cell_size_y, dem_array), 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): def menu_func(self, context):
self.layout.operator(CreateMeshFromGeotiffOperator.bl_idname, self.layout.operator(CreateMeshFromGeotiffOperator.bl_idname,
text=CreateMeshFromGeotiffOperator.bl_label) 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()

25
import_geotiff_panel.py Normal file
View File

@ -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)

15
select_geotiff_file.py Normal file
View File

@ -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'}