From a7847543bc2dd35a10812ba3fb85cd7334460cfa Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 3 May 2019 17:15:23 +1200 Subject: [PATCH 01/14] add animate option to shapefile load props dialog --- operators/io_import_shp.py | 64 +++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index 9bdd5750..e84058bb 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 @@ -192,6 +192,12 @@ def listObjects(self, context): description="Warning : can be very slow with lot of features", default=False ) + #Animate across shapefiles + animate = BoolProperty( + name="Animate", + description="Animate across other shapefiles in the same folder", + default=False ) + #Name objects from field useFieldName: BoolProperty( name="Object name from field", @@ -226,6 +232,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') @@ -284,9 +291,58 @@ 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 = [] + 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] + objects.append(obj) + log.info("{} has {} vertices".format(obj.name, len(obj.data.vertices))) + max_vertex_count = max([len(obj.data.vertices) for obj in objects]) + log.info("max vertex count is {}".format(max_vertex_count)) + base_obj = objects[0] + # This is object that will hold the shape keys + vertex_count = len(base_obj.data.vertices) + if vertex_count < max_vertex_count: + # Ensure there are enough vertices to represent the highest level of definition + n_cuts_required = int(math.ceil(float(max_vertex_count) / vertex_count)) + subsurf = base_obj.modifiers.new("SUBSURF", "SUBSURF") + subsurf.levels = n_cuts_required + bpy.context.view_layer.objects.active = base_obj + bpy.ops.object.modifier_apply(apply_as='DATA', modifier="SUBSURF") + + try: + index = re.search(r'\d+', base_obj.name).group() + except: + index = 0 + + base_obj.shape_key_add(name=index) + base_obj.name = base_obj.name.replace(index, "") + + for i, other_obj in enumerate(objects): + if i == 0: + continue + try: + index = re.search(r'\d+', other_obj.name).group() + except: + index += 1 + base_obj.shape_key_add(name=index) + for j, vertex in enumerate(other_obj.data.vertices): + base_obj.data.shape_keys.key_blocks[i].data[j].co = vertex.co + + for obj in objects[1:]: + bpy.data.objects.remove(obj, do_unlink = True) + + + 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.') From 3cb87740576e0c2997d9f9bcf6f9bc37a31e7918 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 17 May 2019 17:18:14 +1200 Subject: [PATCH 02/14] add keyframes --- operators/io_import_shp.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index e84058bb..0cf98d53 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -318,23 +318,37 @@ def execute(self, context): try: index = re.search(r'\d+', base_obj.name).group() + base_obj.name = base_obj.name.replace(index, "") + index = int(index) except: index = 0 - base_obj.shape_key_add(name=index) - base_obj.name = base_obj.name.replace(index, "") + base_obj.shape_key_add(name=str(index)) + + frames = [] for i, other_obj in enumerate(objects): if i == 0: continue try: - index = re.search(r'\d+', other_obj.name).group() + index = int(re.search(r'\d+', other_obj.name).group()) except: index += 1 - base_obj.shape_key_add(name=index) + base_obj.shape_key_add(name=str(index)) + frames.append(index) for j, vertex in enumerate(other_obj.data.vertices): base_obj.data.shape_keys.key_blocks[i].data[j].co = vertex.co + for k, frame in enumerate(frames): + if k > 0: + base_obj.data.shape_keys.key_blocks[k].value = 0.0 + base_obj.data.shape_keys.key_blocks[k].keyframe_insert(data_path='value', frame=frames[k - 1]) + base_obj.data.shape_keys.key_blocks[k].value = 1.0 + base_obj.data.shape_keys.key_blocks[k].keyframe_insert(data_path='value', frame=frames[k]) + if k < len(frames) - 1: + base_obj.data.shape_keys.key_blocks[k].value = 0.0 + base_obj.data.shape_keys.key_blocks[k].keyframe_insert(data_path='value', frame=frames[k + 1]) + for obj in objects[1:]: bpy.data.objects.remove(obj, do_unlink = True) From 6ed705d030be0aa90818271b42de85bd984d9f07 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 20 May 2019 16:52:43 +1200 Subject: [PATCH 03/14] subsurf all objects, using base 2 log to determine number of cuts --- operators/io_import_shp.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index 0cf98d53..ee9ecb01 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -305,16 +305,17 @@ def execute(self, context): log.info("{} has {} vertices".format(obj.name, len(obj.data.vertices))) max_vertex_count = max([len(obj.data.vertices) for obj in objects]) log.info("max vertex count is {}".format(max_vertex_count)) - base_obj = objects[0] - # This is object that will hold the shape keys - vertex_count = len(base_obj.data.vertices) - if vertex_count < max_vertex_count: - # Ensure there are enough vertices to represent the highest level of definition - n_cuts_required = int(math.ceil(float(max_vertex_count) / vertex_count)) - subsurf = base_obj.modifiers.new("SUBSURF", "SUBSURF") - subsurf.levels = n_cuts_required - bpy.context.view_layer.objects.active = base_obj - bpy.ops.object.modifier_apply(apply_as='DATA', modifier="SUBSURF") + base_obj = objects[0] # This is object that will hold the shape keys + for obj in objects: + vertex_count = len(obj.data.vertices) + n_cuts_required = int(math.ceil(math.log(float(max_vertex_count) / vertex_count, 2))) + if n_cuts_required >= 1: + # Ensure there are enough vertices to represent the highest level of definition + subsurf = obj.modifiers.new("SUBSURF", "SUBSURF") + subsurf.levels = n_cuts_required + bpy.context.view_layer.objects.active = obj + bpy.ops.object.modifier_apply(apply_as='DATA', modifier="SUBSURF") + log.info("after {} cuts, {} now has {} vertices".format(n_cuts_required, obj.name, len(obj.data.vertices))) try: index = re.search(r'\d+', base_obj.name).group() From 5be6e9af8b13576df487284f3bec51788c4df7b7 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 20 May 2019 18:04:54 +1200 Subject: [PATCH 04/14] triangulate, decimate --- operators/io_import_shp.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index ee9ecb01..301562d8 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -307,15 +307,23 @@ def execute(self, context): log.info("max vertex count is {}".format(max_vertex_count)) base_obj = objects[0] # This is object that will hold the shape keys for obj in objects: + bpy.context.view_layer.objects.active = obj + triangulate = obj.modifiers.new("TRIANGULATE", "TRIANGULATE") + bpy.ops.object.modifier_apply(apply_as='DATA', modifier="TRIANGULATE") vertex_count = len(obj.data.vertices) n_cuts_required = int(math.ceil(math.log(float(max_vertex_count) / vertex_count, 2))) if n_cuts_required >= 1: # Ensure there are enough vertices to represent the highest level of definition subsurf = obj.modifiers.new("SUBSURF", "SUBSURF") subsurf.levels = n_cuts_required - bpy.context.view_layer.objects.active = obj bpy.ops.object.modifier_apply(apply_as='DATA', modifier="SUBSURF") log.info("after {} cuts, {} now has {} vertices".format(n_cuts_required, obj.name, len(obj.data.vertices))) + vertex_count = len(obj.data.vertices) + ratio = max_vertex_count / vertex_count + decimate = obj.modifiers.new("DECIMATE", "DECIMATE") + decimate.ratio = ratio + bpy.ops.object.modifier_apply(apply_as='DATA', modifier="DECIMATE") + log.info("after decimating with ratio {}, {} now has {} vertices".format(ratio, obj.name, len(obj.data.vertices))) try: index = re.search(r'\d+', base_obj.name).group() From 6038023c21616b512c194ab134f6c6bede579662 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 21 May 2019 11:27:12 +1200 Subject: [PATCH 05/14] iterate closer to target vertex count --- operators/io_import_shp.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index 301562d8..a90e84e9 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -312,18 +312,21 @@ def execute(self, context): bpy.ops.object.modifier_apply(apply_as='DATA', modifier="TRIANGULATE") vertex_count = len(obj.data.vertices) n_cuts_required = int(math.ceil(math.log(float(max_vertex_count) / vertex_count, 2))) + if vertex_count < max_vertex_count and n_cuts_required < 1: + n_cuts_required = 1 if n_cuts_required >= 1: # Ensure there are enough vertices to represent the highest level of definition subsurf = obj.modifiers.new("SUBSURF", "SUBSURF") subsurf.levels = n_cuts_required bpy.ops.object.modifier_apply(apply_as='DATA', modifier="SUBSURF") log.info("after {} cuts, {} now has {} vertices".format(n_cuts_required, obj.name, len(obj.data.vertices))) - vertex_count = len(obj.data.vertices) - ratio = max_vertex_count / vertex_count - decimate = obj.modifiers.new("DECIMATE", "DECIMATE") - decimate.ratio = ratio - bpy.ops.object.modifier_apply(apply_as='DATA', modifier="DECIMATE") - log.info("after decimating with ratio {}, {} now has {} vertices".format(ratio, obj.name, len(obj.data.vertices))) + for i in range(5): + vertex_count = len(obj.data.vertices) + ratio = max_vertex_count / vertex_count + decimate = obj.modifiers.new("DECIMATE", "DECIMATE") + decimate.ratio = ratio + bpy.ops.object.modifier_apply(apply_as='DATA', modifier="DECIMATE") + log.info("after decimating with ratio {}, {} now has {} vertices".format(ratio, obj.name, len(obj.data.vertices))) try: index = re.search(r'\d+', base_obj.name).group() @@ -334,7 +337,7 @@ def execute(self, context): base_obj.shape_key_add(name=str(index)) - frames = [] + frames = [index] for i, other_obj in enumerate(objects): if i == 0: From 39cf87d61238048d21f9d28202cb79eb21cbac83 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 21 May 2019 14:31:56 +1200 Subject: [PATCH 06/14] use shrinkwrap modifier instead --- operators/io_import_shp.py | 65 +++++++++++--------------------------- 1 file changed, 19 insertions(+), 46 deletions(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index a90e84e9..428d4151 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -296,60 +296,32 @@ def execute(self, context): 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 objects.append(obj) log.info("{} has {} vertices".format(obj.name, len(obj.data.vertices))) - max_vertex_count = max([len(obj.data.vertices) for obj in objects]) - log.info("max vertex count is {}".format(max_vertex_count)) - base_obj = objects[0] # This is object that will hold the shape keys - for obj in objects: - bpy.context.view_layer.objects.active = obj - triangulate = obj.modifiers.new("TRIANGULATE", "TRIANGULATE") - bpy.ops.object.modifier_apply(apply_as='DATA', modifier="TRIANGULATE") - vertex_count = len(obj.data.vertices) - n_cuts_required = int(math.ceil(math.log(float(max_vertex_count) / vertex_count, 2))) - if vertex_count < max_vertex_count and n_cuts_required < 1: - n_cuts_required = 1 - if n_cuts_required >= 1: - # Ensure there are enough vertices to represent the highest level of definition - subsurf = obj.modifiers.new("SUBSURF", "SUBSURF") - subsurf.levels = n_cuts_required - bpy.ops.object.modifier_apply(apply_as='DATA', modifier="SUBSURF") - log.info("after {} cuts, {} now has {} vertices".format(n_cuts_required, obj.name, len(obj.data.vertices))) - for i in range(5): - vertex_count = len(obj.data.vertices) - ratio = max_vertex_count / vertex_count - decimate = obj.modifiers.new("DECIMATE", "DECIMATE") - decimate.ratio = ratio - bpy.ops.object.modifier_apply(apply_as='DATA', modifier="DECIMATE") - log.info("after decimating with ratio {}, {} now has {} vertices".format(ratio, obj.name, len(obj.data.vertices))) - - try: - index = re.search(r'\d+', base_obj.name).group() - base_obj.name = base_obj.name.replace(index, "") - index = int(index) - except: - index = 0 - base_obj.shape_key_add(name=str(index)) + base_obj = objects[-1] # This is object that will hold the shape keys + bpy.context.view_layer.objects.active = base_obj + base_obj.name = base_obj.name.replace(str(base_obj["frame"]), "") - frames = [index] + frames = [base_obj["frame"]] - for i, other_obj in enumerate(objects): - if i == 0: - continue - try: - index = int(re.search(r'\d+', other_obj.name).group()) - except: - index += 1 - base_obj.shape_key_add(name=str(index)) - frames.append(index) - for j, vertex in enumerate(other_obj.data.vertices): - base_obj.data.shape_keys.key_blocks[i].data[j].co = vertex.co + for obj in objects[-2::-1]: + shrinkwrap = base_obj.modifiers.new("SHRINKWRAP", "SHRINKWRAP") + shrinkwrap.target = obj + bpy.ops.object.modifier_apply(apply_as='SHAPE', modifier="SHRINKWRAP") + base_obj.data.shape_keys.key_blocks[-1].name = str(obj["frame"]) + frames.append(obj["frame"]) for k, frame in enumerate(frames): if k > 0: @@ -361,8 +333,9 @@ def execute(self, context): base_obj.data.shape_keys.key_blocks[k].value = 0.0 base_obj.data.shape_keys.key_blocks[k].keyframe_insert(data_path='value', frame=frames[k + 1]) - for obj in objects[1:]: - bpy.data.objects.remove(obj, do_unlink = True) + for obj in objects: + if obj != base_obj: + bpy.data.objects.remove(obj, do_unlink = True) else: From d00c258004a02f1e72cca99282a76b54759d7625 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 21 May 2019 15:07:05 +1200 Subject: [PATCH 07/14] use a duplicate as a temporary reference during iterative shrinkwrapping --- operators/io_import_shp.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index 428d4151..6d33afa7 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -311,19 +311,32 @@ def execute(self, context): log.info("{} has {} vertices".format(obj.name, len(obj.data.vertices))) base_obj = objects[-1] # This is object that will hold the shape keys - bpy.context.view_layer.objects.active = base_obj 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 + frames = [base_obj["frame"]] + base_obj.shape_key_add(name=str(base_obj["frame"])) - for obj in objects[-2::-1]: - shrinkwrap = base_obj.modifiers.new("SHRINKWRAP", "SHRINKWRAP") + for obj in objects[::-1]: + if obj == base_obj: + continue + shrinkwrap = duplicated_base.modifiers.new("SHRINKWRAP", "SHRINKWRAP") shrinkwrap.target = obj - bpy.ops.object.modifier_apply(apply_as='SHAPE', modifier="SHRINKWRAP") - base_obj.data.shape_keys.key_blocks[-1].name = str(obj["frame"]) + #shrinkwrap.wrap_method = "NEAREST_VERTEX" + bpy.ops.object.modifier_apply(apply_as='DATA', 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 frames.append(obj["frame"]) + bpy.data.objects.remove(duplicated_base, do_unlink = True) + for k, frame in enumerate(frames): + # Create keyframes if k > 0: base_obj.data.shape_keys.key_blocks[k].value = 0.0 base_obj.data.shape_keys.key_blocks[k].keyframe_insert(data_path='value', frame=frames[k - 1]) From 04daf2f38810f559796c57b8b4eebf4d6540d7c2 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 21 May 2019 15:47:44 +1200 Subject: [PATCH 08/14] triangulate / subsurf all objects --- operators/io_import_shp.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index 6d33afa7..38e6dd5a 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -307,8 +307,21 @@ def execute(self, context): except: frame += 1 obj["frame"] = frame + + vertex_count = len(obj.data.vertices) + log.info("{} has {} vertices".format(obj.name, vertex_count)) + triangulate = obj.modifiers.new("TRIANGULATE", "TRIANGULATE") + bpy.ops.object.modifier_apply(apply_as='DATA', 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(apply_as='DATA', modifier="SUBSURF") objects.append(obj) - log.info("{} has {} vertices".format(obj.name, len(obj.data.vertices))) + log.info("After triangulation / subsurf {} has {} vertices".format(obj.name, len(obj.data.vertices))) base_obj = objects[-1] # This is object that will hold the shape keys base_obj.name = base_obj.name.replace(str(base_obj["frame"]), "") From 76dbe7b1224aaa878d338d23bbd9e2971d2ad330 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 31 May 2019 17:41:51 +1200 Subject: [PATCH 09/14] don't assume files are sorted by size --- operators/io_import_shp.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index 38e6dd5a..96b4211c 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -309,7 +309,9 @@ def execute(self, context): obj["frame"] = frame vertex_count = len(obj.data.vertices) - log.info("{} has {} vertices".format(obj.name, vertex_count)) + 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(apply_as='DATA', modifier="TRIANGULATE") @@ -323,17 +325,20 @@ def execute(self, context): objects.append(obj) log.info("After triangulation / subsurf {} has {} vertices".format(obj.name, len(obj.data.vertices))) - base_obj = objects[-1] # This is object that will hold the shape keys + # 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 - frames = [base_obj["frame"]] base_obj.shape_key_add(name=str(base_obj["frame"])) - for obj in objects[::-1]: + for obj in objects: if obj == base_obj: continue shrinkwrap = duplicated_base.modifiers.new("SHRINKWRAP", "SHRINKWRAP") @@ -344,20 +349,21 @@ def execute(self, context): 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 - frames.append(obj["frame"]) 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[k].value = 0.0 - base_obj.data.shape_keys.key_blocks[k].keyframe_insert(data_path='value', frame=frames[k - 1]) - base_obj.data.shape_keys.key_blocks[k].value = 1.0 - base_obj.data.shape_keys.key_blocks[k].keyframe_insert(data_path='value', frame=frames[k]) + 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[k].value = 0.0 - base_obj.data.shape_keys.key_blocks[k].keyframe_insert(data_path='value', frame=frames[k + 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: From 711e31f9dbb01a5366086087428b13f084aea8bb Mon Sep 17 00:00:00 2001 From: Nick Young Date: Fri, 9 Jul 2021 17:10:20 +1200 Subject: [PATCH 10/14] Blender 2.90 fix --- operators/io_import_shp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index 55558163..2098c198 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -314,7 +314,7 @@ def execute(self, context): 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(apply_as='DATA', modifier="TRIANGULATE") + bpy.ops.object.modifier_apply(modifier="TRIANGULATE") # Subsurf to smooth edges n_cuts_required = int(round(math.log(500.0 / vertex_count, 2))) @@ -322,7 +322,7 @@ def execute(self, context): print(n_cuts_required) subsurf = obj.modifiers.new("SUBSURF", "SUBSURF") subsurf.levels = n_cuts_required - bpy.ops.object.modifier_apply(apply_as='DATA', modifier="SUBSURF") + bpy.ops.object.modifier_apply(modifier="SUBSURF") objects.append(obj) log.info("After triangulation / subsurf {} has {} vertices".format(obj.name, len(obj.data.vertices))) @@ -345,7 +345,7 @@ def execute(self, context): shrinkwrap = duplicated_base.modifiers.new("SHRINKWRAP", "SHRINKWRAP") shrinkwrap.target = obj #shrinkwrap.wrap_method = "NEAREST_VERTEX" - bpy.ops.object.modifier_apply(apply_as='DATA', modifier="SHRINKWRAP") + 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): From 5026917429c71461fbe5ec14dfe5d952ff48a0a6 Mon Sep 17 00:00:00 2001 From: Nick Young Date: Mon, 12 Jul 2021 10:12:01 +1200 Subject: [PATCH 11/14] fix missing animate option --- operators/io_import_shp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index 2098c198..848a5169 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -194,7 +194,7 @@ def listObjects(self, context): default=False ) #Animate across shapefiles - animate = BoolProperty( + animate: BoolProperty( name="Animate", description="Animate across other shapefiles in the same folder", default=False ) From 5ddc7d972b30a25dd62bace5b25d68ed28eac6aa Mon Sep 17 00:00:00 2001 From: Nick Young Date: Mon, 12 Jul 2021 11:12:30 +1200 Subject: [PATCH 12/14] ability to animate between multiple elevations stored in different fields in shapefile --- operators/io_import_shp.py | 45 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/operators/io_import_shp.py b/operators/io_import_shp.py index 848a5169..339d6e93 100644 --- a/operators/io_import_shp.py +++ b/operators/io_import_shp.py @@ -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", @@ -195,7 +201,7 @@ def listObjects(self, context): #Animate across shapefiles animate: BoolProperty( - name="Animate", + name="Animate multiple shapefiles", description="Animate across other shapefiles in the same folder", default=False ) @@ -221,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') # @@ -370,6 +377,40 @@ def execute(self, context): 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, From 8a3ea0845b7e425075778e0ee3618384570d3998 Mon Sep 17 00:00:00 2001 From: Nick Young Date: Tue, 21 Dec 2021 09:53:16 +1300 Subject: [PATCH 13/14] Option to animate across multiple Raw DEMs Loads files in the same folder, with the same extension Sets frame numbers based on numbers in filename, or falls back to consecutive numbers Copies mesh data in as shapekeys and sets keyframes --- operators/io_import_georaster.py | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/operators/io_import_georaster.py b/operators/io_import_georaster.py index d74de1d1..c17b1bb1 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,59 @@ 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)) + 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... From eaad1f68de9b02873804d9230889831653cc095c Mon Sep 17 00:00:00 2001 From: Nick Young Date: Thu, 23 Dec 2021 11:58:35 +1300 Subject: [PATCH 14/14] Raise error in event of differing number of vertices --- operators/io_import_georaster.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/operators/io_import_georaster.py b/operators/io_import_georaster.py index c17b1bb1..feb1100b 100755 --- a/operators/io_import_georaster.py +++ b/operators/io_import_georaster.py @@ -506,6 +506,11 @@ def execute(self, context): 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