diff --git a/operators/io_import_georaster.py b/operators/io_import_georaster.py index d74de1d1..feb1100b 100755 --- a/operators/io_import_georaster.py +++ b/operators/io_import_georaster.py @@ -25,6 +25,8 @@ import math from mathutils import Vector import numpy as np#Ship with Blender since 2.70 +import glob +import re import logging log = logging.getLogger(__name__) @@ -139,6 +141,12 @@ def listSubdivisionModes(self, context): default=False ) # + animate: BoolProperty( + name="Animate multiple DEMs", + description="Animate across other DEM rasters in the same folder", + default=False + ) + # step: IntProperty(name = "Step", default=1, description="Pixel step", min=1) buildFaces: BoolProperty(name="Build faces", default=True, description='Build quad faces connecting pixel point cloud') @@ -180,6 +188,7 @@ def draw(self, context): layout.prop(self, 'buildFaces') layout.prop(self, 'step') layout.prop(self, 'clip') + layout.prop(self, 'animate') if self.clip: if geoscn.isGeoref and len(self.objectsLst) > 0: layout.prop(self, 'objectsLst') @@ -460,6 +469,64 @@ def execute(self, context): obj = placeObj(mesh, name) #grid.unload() + if self.animate: + log.info("Animate mode") + # Find all files in the same directory with the same extension + basedir = os.path.dirname(filePath) + file_extension = os.path.splitext(filePath)[1] + files = glob.glob(f"{basedir}{os.sep}*{file_extension}") + files.sort() + frames = [] + # Try set the name of the shapekey based on the detected frame (number in filename) + try: + frame = int(re.search(r'\d+', name).group()) + except: + frame = 1 + frames.append(frame) + obj.shape_key_add(name=str(frame)) + for f in files: + if f == filePath or f.endswith("_bgis.tif"): + continue + log.info(f"Loading {f}") + name = os.path.splitext(os.path.basename(f))[0] + try: + frame = int(re.search(r'\d+', name).group()) + except: + frame += 1 + frames.append(frame) + # Load file for frame, and copy the mesh data in as a shape key + try: + grid = GeoRaster(f, subBoxGeo=subBox, useGDAL=HAS_GDAL) + except IOError as e: + log.error("Unable to open raster", exc_info=True) + self.report({'ERROR'}, "Unable to open raster, check logs for more infos") + return {'CANCELLED'} + except OverlapError: + self.report({'ERROR'}, "Non overlap data") + return {'CANCELLED'} + new_mesh = exportAsMesh(grid, dx, dy, self.step, reproj=rprjToScene, subset=self.clip, flat=False, buildFaces=self.buildFaces) + obj.shape_key_add(name=str(frame)) + if len(new_mesh.vertices) != len(mesh.vertices): + error = f"Different number of vertices - {len(new_mesh.vertices)} != {len(mesh.vertices)}" + print(error) + self.report({'ERROR'}, error) + return {'CANCELLED'} + for j, vertex in enumerate(new_mesh.vertices): + obj.data.shape_keys.key_blocks[str(frame)].data[j].co = vertex.co + + frames.sort() + for k, frame in enumerate(frames): + # Create keyframes + sframe = str(frame) + if k > 0: + obj.data.shape_keys.key_blocks[sframe].value = 0.0 + obj.data.shape_keys.key_blocks[sframe].keyframe_insert(data_path='value', frame=frames[k - 1]) + obj.data.shape_keys.key_blocks[sframe].value = 1.0 + obj.data.shape_keys.key_blocks[sframe].keyframe_insert(data_path='value', frame=frames[k]) + if k < len(frames) - 1: + obj.data.shape_keys.key_blocks[sframe].value = 0.0 + obj.data.shape_keys.key_blocks[sframe].keyframe_insert(data_path='value', frame=frames[k + 1]) + ###################################### #Flag if a new object as been created... diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index 8a190f1a..339d6e93 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -1,5 +1,5 @@ # -*- coding:utf-8 -*- -import os, sys, time +import os, sys, time, glob, re import bpy from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty from bpy.types import Operator @@ -104,7 +104,7 @@ class IMPORTGIS_OT_shapefile_props_dialog(Operator): def check(self, context): return True - def listFields(self, context): + def listFields(self, context=None): fieldsItems = [] try: shp = shpReader(self.filepath) @@ -169,6 +169,12 @@ def listObjects(self, context): description = "Choose field", items = listFields ) + #Animate across multiple elevation values in different fields + animateElevationFields: BoolProperty( + name="Animate elevation fields", + description="Animate elevation from multiple fields", + default=False ) + #Extrusion field useFieldExtrude: BoolProperty( name="Extrusion from field", @@ -193,6 +199,12 @@ def listObjects(self, context): description="Warning : can be very slow with lot of features", default=False ) + #Animate across shapefiles + animate: BoolProperty( + name="Animate multiple shapefiles", + description="Animate across other shapefiles in the same folder", + default=False ) + #Name objects from field useFieldName: BoolProperty( name="Object name from field", @@ -215,6 +227,7 @@ def draw(self, context): #layout.prop(self, 'useFieldElev') if self.vertsElevSource == 'FIELD': layout.prop(self, 'fieldElevName') + layout.prop(self, 'animateElevationFields') elif self.vertsElevSource == 'OBJ': layout.prop(self, 'objElevLst') # @@ -227,6 +240,7 @@ def draw(self, context): if self.separateObjects: layout.prop(self, 'useFieldName') else: + layout.prop(self, 'animate') self.useFieldName = False if self.separateObjects and self.useFieldName: layout.prop(self, 'fieldObjName') @@ -285,9 +299,123 @@ def execute(self, context): shpCRS = self.shpCRS try: - bpy.ops.importgis.shapefile('INVOKE_DEFAULT', filepath=self.filepath, shpCRS=shpCRS, elevSource=self.vertsElevSource, - fieldElevName=elevField, objElevName=objElevName, fieldExtrudeName=extrudField, fieldObjName=nameField, - extrusionAxis=self.extrusionAxis, separateObjects=self.separateObjects) + if self.animate: + basedir = os.path.dirname(self.filepath) + files = glob.glob(os.path.join(basedir, "*.shp")) + files.sort() + objects = [] + frame = 0 + for f in files: + bpy.ops.importgis.shapefile('INVOKE_DEFAULT', filepath=f, shpCRS=shpCRS, elevSource=self.vertsElevSource, + fieldElevName=elevField, objElevName=objElevName, fieldExtrudeName=extrudField, fieldObjName=nameField, + extrusionAxis=self.extrusionAxis, separateObjects=self.separateObjects) + obj = bpy.context.selected_objects[0] + try: + frame = int(re.search(r'\d+', obj.name).group()) + except: + frame += 1 + obj["frame"] = frame + + vertex_count = len(obj.data.vertices) + area = sum(p.area for p in obj.data.polygons) + obj["area"] = area + log.info("{} has {} vertices and an area of {}".format(obj.name, vertex_count, area)) + triangulate = obj.modifiers.new("TRIANGULATE", "TRIANGULATE") + bpy.ops.object.modifier_apply(modifier="TRIANGULATE") + + # Subsurf to smooth edges + n_cuts_required = int(round(math.log(500.0 / vertex_count, 2))) + if n_cuts_required >= 1: + print(n_cuts_required) + subsurf = obj.modifiers.new("SUBSURF", "SUBSURF") + subsurf.levels = n_cuts_required + bpy.ops.object.modifier_apply(modifier="SUBSURF") + objects.append(obj) + log.info("After triangulation / subsurf {} has {} vertices".format(obj.name, len(obj.data.vertices))) + + # Strategy here is to take the largest object, then progressively shrinkwrap it onto each smaller object in turn + # To do this, first sort objects by area descending + objects.sort(key=lambda x: x["area"], reverse=True) + base_obj = objects[0] # This is the largest object / the object that will hold the shape keys + print("base_obj is " + base_obj.name) + base_obj.name = base_obj.name.replace(str(base_obj["frame"]), "") + + bpy.ops.object.duplicate() + duplicated_base = bpy.context.view_layer.objects.active + # Use a duplicated object as a reference for iterative shrinkwrapping + + base_obj.shape_key_add(name=str(base_obj["frame"])) + + for obj in objects: + if obj == base_obj: + continue + shrinkwrap = duplicated_base.modifiers.new("SHRINKWRAP", "SHRINKWRAP") + shrinkwrap.target = obj + #shrinkwrap.wrap_method = "NEAREST_VERTEX" + bpy.ops.object.modifier_apply(modifier="SHRINKWRAP") + # Copy vertex data from shrinkwrapped duplicate as a shape key + base_obj.shape_key_add(name=str(obj["frame"])) + for j, vertex in enumerate(duplicated_base.data.vertices): + base_obj.data.shape_keys.key_blocks[str(obj["frame"])].data[j].co = vertex.co + + bpy.data.objects.remove(duplicated_base, do_unlink = True) + frames = sorted(o["frame"] for o in objects) + + for k, frame in enumerate(frames): + # Create keyframes + sframe = str(frame) + if k > 0: + base_obj.data.shape_keys.key_blocks[sframe].value = 0.0 + base_obj.data.shape_keys.key_blocks[sframe].keyframe_insert(data_path='value', frame=frames[k - 1]) + base_obj.data.shape_keys.key_blocks[sframe].value = 1.0 + base_obj.data.shape_keys.key_blocks[sframe].keyframe_insert(data_path='value', frame=frames[k]) + if k < len(frames) - 1: + base_obj.data.shape_keys.key_blocks[sframe].value = 0.0 + base_obj.data.shape_keys.key_blocks[sframe].keyframe_insert(data_path='value', frame=frames[k + 1]) + + for obj in objects: + if obj != base_obj: + bpy.data.objects.remove(obj, do_unlink = True) + + elif self.animateElevationFields: + base = elevField.rstrip('0123456789') + fields = [field[0] for field in self.listFields() if field[0].startswith(base)] + frames = [int(field.replace(base, "")) for field in fields] + print(fields) + # Load first elevation field + bpy.ops.importgis.shapefile('INVOKE_DEFAULT', filepath=self.filepath, shpCRS=shpCRS, elevSource=self.vertsElevSource, + fieldElevName=fields[0], objElevName=objElevName, fieldExtrudeName=extrudField, fieldObjName=nameField, + extrusionAxis=self.extrusionAxis, separateObjects=self.separateObjects) + base_obj = bpy.context.selected_objects[0] + base_obj.shape_key_add(name=fields[0]) + + for field in fields[1:]: + print(field) + bpy.ops.importgis.shapefile('INVOKE_DEFAULT', filepath=self.filepath, shpCRS=shpCRS, elevSource=self.vertsElevSource, + fieldElevName=field, objElevName=objElevName, fieldExtrudeName=extrudField, fieldObjName=nameField, + extrusionAxis=self.extrusionAxis, separateObjects=self.separateObjects) + obj = bpy.context.selected_objects[0] + base_obj.shape_key_add(name=field) + for j, vertex in enumerate(obj.data.vertices): + base_obj.data.shape_keys.key_blocks[field].data[j].co = vertex.co + bpy.data.objects.remove(obj, do_unlink = True) + + # Create keyframes + for k, field in enumerate(fields): + frame = int(field.replace(base, "")) + if k > 0: + base_obj.data.shape_keys.key_blocks[field].value = 0.0 + base_obj.data.shape_keys.key_blocks[field].keyframe_insert(data_path='value', frame=frames[k - 1]) + base_obj.data.shape_keys.key_blocks[field].value = 1.0 + base_obj.data.shape_keys.key_blocks[field].keyframe_insert(data_path='value', frame=frames[k]) + if k < len(frames) - 1: + base_obj.data.shape_keys.key_blocks[field].value = 0.0 + base_obj.data.shape_keys.key_blocks[field].keyframe_insert(data_path='value', frame=frames[k + 1]) + + else: + bpy.ops.importgis.shapefile('INVOKE_DEFAULT', filepath=self.filepath, shpCRS=shpCRS, elevSource=self.vertsElevSource, + fieldElevName=elevField, objElevName=objElevName, fieldExtrudeName=extrudField, fieldObjName=nameField, + extrusionAxis=self.extrusionAxis, separateObjects=self.separateObjects) except Exception as e: log.error('Shapefile import fails', exc_info=True) self.report({'ERROR'}, 'Shapefile import fails, check logs.')