|
14 | 14 | # You should have received a copy of the GNU General Public License
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16 | 16 |
|
17 |
| -import bpy, os, stat, subprocess, platform, math, mathutils, fnmatch, random |
| 17 | +import bpy, os, stat, subprocess, platform, math, mathutils, fnmatch, random, mathutils, datetime |
18 | 18 | from bpy.props import (
|
19 | 19 | BoolProperty,
|
20 | 20 | StringProperty
|
|
23 | 23 | from ..utils import version_compatibility_utils as vcu
|
24 | 24 | from ..utils import export_utils
|
25 | 25 | from ..utils import audio_utils
|
| 26 | +from ..objects import flip_fluid_cache |
26 | 27 | from ..objects import flip_fluid_aabb
|
27 | 28 | from . import bake_operators
|
28 | 29 | from .. import render
|
@@ -2796,6 +2797,160 @@ def execute(self, context):
|
2796 | 2797 | return {'FINISHED'}
|
2797 | 2798 |
|
2798 | 2799 |
|
| 2800 | +class FlipFluidMeasureObjectSpeed(bpy.types.Operator): |
| 2801 | + bl_idname = "flip_fluid_operators.measure_object_speed" |
| 2802 | + bl_label = "Measure Object Speed" |
| 2803 | + bl_description = ("Measure and display the speed of the active object within the simulation" + |
| 2804 | + " for the current frame. The measured speed depends on the object animation, simulation" + |
| 2805 | + " world scale and time scale, and animation frame rate." + |
| 2806 | + " The objects center speed, min vertex speed, and max vertex speed will be computed. Using" + |
| 2807 | + " this operator on an object with complex geometry or a high polycount may cause Blender to" + |
| 2808 | + " pause during computation") |
| 2809 | + |
| 2810 | + |
| 2811 | + @classmethod |
| 2812 | + def poll(cls, context): |
| 2813 | + selected_objects = bpy.context.selected_objects |
| 2814 | + bl_object = vcu.get_active_object(context) |
| 2815 | + if selected_objects: |
| 2816 | + if bl_object not in selected_objects: |
| 2817 | + bl_object = selected_objects[0] |
| 2818 | + else: |
| 2819 | + bl_object = None |
| 2820 | + return bl_object is not None |
| 2821 | + |
| 2822 | + |
| 2823 | + def frame_set(self, context, frameno): |
| 2824 | + from ..properties import helper_properties |
| 2825 | + helper_properties.DISABLE_FRAME_CHANGE_POST_HANDLER = True |
| 2826 | + flip_fluid_cache.DISABLE_MESH_CACHE_LOAD = True |
| 2827 | + context.scene.frame_set(frameno) |
| 2828 | + flip_fluid_cache.DISABLE_MESH_CACHE_LOAD = False |
| 2829 | + helper_properties.DISABLE_FRAME_CHANGE_POST_HANDLER = False |
| 2830 | + |
| 2831 | + |
| 2832 | + def get_object_vertices_and_center(self, context, bl_object, frameno): |
| 2833 | + self.frame_set(context, frameno) |
| 2834 | + |
| 2835 | + vertices = [] |
| 2836 | + center = None |
| 2837 | + if bl_object.type == 'EMPTY': |
| 2838 | + vertices.append(mathutils.Vector(bl_object.matrix_world.translation)) |
| 2839 | + center = mathutils.Vector(bl_object.matrix_world.translation) |
| 2840 | + else: |
| 2841 | + depsgraph = context.evaluated_depsgraph_get() |
| 2842 | + obj_eval = bl_object.evaluated_get(depsgraph) |
| 2843 | + evaluated_mesh = obj_eval.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph) |
| 2844 | + for mv in evaluated_mesh.vertices: |
| 2845 | + vertices.append(obj_eval.matrix_world @ mv.co) |
| 2846 | + |
| 2847 | + local_bbox_center = 0.125 * sum((mathutils.Vector(b) for b in bl_object.bound_box), mathutils.Vector()) |
| 2848 | + center = bl_object.matrix_world @ local_bbox_center |
| 2849 | + |
| 2850 | + return vertices, center |
| 2851 | + |
| 2852 | + |
| 2853 | + def execute(self, context): |
| 2854 | + timer_start = datetime.datetime.now() |
| 2855 | + |
| 2856 | + hprops = context.scene.flip_fluid_helper |
| 2857 | + hprops.is_translation_data_available = False |
| 2858 | + |
| 2859 | + selected_objects = bpy.context.selected_objects |
| 2860 | + bl_object = vcu.get_active_object(context) |
| 2861 | + if selected_objects: |
| 2862 | + if bl_object not in selected_objects: |
| 2863 | + bl_object = selected_objects[0] |
| 2864 | + else: |
| 2865 | + bl_object = None |
| 2866 | + |
| 2867 | + if bl_object is None: |
| 2868 | + err_msg = "No active object selected." |
| 2869 | + self.report({'ERROR'}, err_msg) |
| 2870 | + print("Measure Object Speed Error: " + err_msg + "\n") |
| 2871 | + return {'CANCELLED'} |
| 2872 | + |
| 2873 | + valid_object_types = ['MESH', 'EMPTY', 'CURVE'] |
| 2874 | + if bl_object.type not in valid_object_types: |
| 2875 | + err_msg = "Invalid object type <" + bl_object.type + ">. Object must be a Mesh, Curve, or Empty to measure speed." |
| 2876 | + self.report({'ERROR'}, err_msg) |
| 2877 | + print("Measure Object Speed Error: " + err_msg + "\n") |
| 2878 | + return {'CANCELLED'} |
| 2879 | + |
| 2880 | + original_frame = context.scene.frame_current |
| 2881 | + frame1 = original_frame - 1 |
| 2882 | + frame2 = original_frame + 1 |
| 2883 | + |
| 2884 | + print("Measure Object Speed: Exporting <" + bl_object.name + "> geometry for frame " + str(frame1) + "...", end=' ') |
| 2885 | + vertices1, center1 = self.get_object_vertices_and_center(context, bl_object, frame1) |
| 2886 | + print("Exported " + str(len(vertices1)) + " vertices.") |
| 2887 | + |
| 2888 | + if len(vertices1) == 0: |
| 2889 | + err_msg = "Object does not contain geometry." |
| 2890 | + self.report({'ERROR'}, err_msg) |
| 2891 | + print("Measure Object Speed Error: " + err_msg + "\n") |
| 2892 | + self.frame_set(context, original_frame) |
| 2893 | + return {'CANCELLED'} |
| 2894 | + |
| 2895 | + print("Measure Object Speed: Exporting <" + bl_object.name + "> geometry for frame " + str(frame2) + "...", end=' ') |
| 2896 | + vertices2, center2 = self.get_object_vertices_and_center(context, bl_object, frame2) |
| 2897 | + print("Exported " + str(len(vertices2)) + " vertices.") |
| 2898 | + self.frame_set(context, original_frame) |
| 2899 | + |
| 2900 | + if len(vertices1) != len(vertices1): |
| 2901 | + err_msg = "Cannot measure velocity of object with changing topology." |
| 2902 | + self.report({'ERROR'}, err_msg) |
| 2903 | + print("Measure Object Speed Error: " + err_msg + "\n") |
| 2904 | + return {'CANCELLED'} |
| 2905 | + |
| 2906 | + center_translation = (center2 - center1).length / float(frame2 - frame1) |
| 2907 | + min_translation = float('inf') |
| 2908 | + max_translation = -float('inf') |
| 2909 | + sum_translation = 0.0 |
| 2910 | + for i in range(len(vertices1)): |
| 2911 | + translation = (vertices2[i] - vertices1[i]).length / float(frame2 - frame1) |
| 2912 | + min_translation = min(min_translation, translation) |
| 2913 | + max_translation = max(max_translation, translation) |
| 2914 | + sum_translation += translation |
| 2915 | + avg_translation = sum_translation / len(vertices1) |
| 2916 | + |
| 2917 | + timer_end = datetime.datetime.now() |
| 2918 | + ms_duration = int((timer_end - timer_start).microseconds / 1000) |
| 2919 | + |
| 2920 | + hprops.min_vertex_translation = min_translation |
| 2921 | + hprops.max_vertex_translation = max_translation |
| 2922 | + hprops.avg_vertex_translation = avg_translation |
| 2923 | + hprops.center_translation = center_translation |
| 2924 | + hprops.translation_data_object_name = bl_object.name |
| 2925 | + hprops.translation_data_object_vertices = len(vertices1) |
| 2926 | + hprops.translation_data_object_frame = original_frame |
| 2927 | + hprops.translation_data_object_compute_time = ms_duration |
| 2928 | + hprops.is_translation_data_available = True |
| 2929 | + |
| 2930 | + info_str = "Measure Object Speed: Finished computing <" + bl_object.name + "> vertex translations for frame " |
| 2931 | + info_str += str(original_frame) + " in " + str(ms_duration) + " milliseconds.\n" |
| 2932 | + print(info_str) |
| 2933 | + |
| 2934 | + return {'FINISHED'} |
| 2935 | + |
| 2936 | + |
| 2937 | +class FlipFluidClearMeasureObjectSpeed(bpy.types.Operator): |
| 2938 | + bl_idname = "flip_fluid_operators.clear_measure_object_speed" |
| 2939 | + bl_label = "Clear Speed Data" |
| 2940 | + bl_description = "Clear the measured object speed display" |
| 2941 | + |
| 2942 | + |
| 2943 | + @classmethod |
| 2944 | + def poll(cls, context): |
| 2945 | + return True |
| 2946 | + |
| 2947 | + |
| 2948 | + def execute(self, context): |
| 2949 | + hprops = context.scene.flip_fluid_helper |
| 2950 | + hprops.is_translation_data_available = False |
| 2951 | + return {'FINISHED'} |
| 2952 | + |
| 2953 | + |
2799 | 2954 | def register():
|
2800 | 2955 | classes = [
|
2801 | 2956 | FlipFluidHelperRemesh,
|
@@ -2851,6 +3006,8 @@ def register():
|
2851 | 3006 | FlipFluidMakePrefixFilenameRenderOutput,
|
2852 | 3007 | FlipFluidAutoLoadBakedFramesCMD,
|
2853 | 3008 | FlipFluidCopySettingsFromActive,
|
| 3009 | + FlipFluidMeasureObjectSpeed, |
| 3010 | + FlipFluidClearMeasureObjectSpeed, |
2854 | 3011 | ]
|
2855 | 3012 |
|
2856 | 3013 | # Workaround for a bug in FLIP Fluids 1.6.0
|
@@ -2920,3 +3077,5 @@ def unregister():
|
2920 | 3077 | bpy.utils.unregister_class(FlipFluidMakePrefixFilenameRenderOutput)
|
2921 | 3078 | bpy.utils.unregister_class(FlipFluidAutoLoadBakedFramesCMD)
|
2922 | 3079 | bpy.utils.unregister_class(FlipFluidCopySettingsFromActive)
|
| 3080 | + bpy.utils.unregister_class(FlipFluidMeasureObjectSpeed) |
| 3081 | + bpy.utils.unregister_class(FlipFluidClearMeasureObjectSpeed) |
0 commit comments