From f353224a59f6a90bfc96395787195c35038c246d Mon Sep 17 00:00:00 2001 From: Ryan G Date: Tue, 23 May 2023 12:30:25 -0700 Subject: [PATCH] new demo version 0.6.4! --- .gitignore | 3 +- README.md | 2 +- cmake/CMakeLists.txt | 11 +- src/addon/bake.py | 4 +- .../filesystem/filesystem_protection_layer.py | 2 +- src/addon/objects/flip_fluid_cache.py | 9 + src/addon/operators/bake_operators.py | 12 + src/addon/operators/cache_operators.py | 105 ++++++- src/addon/operators/export_operators.py | 6 + src/addon/operators/helper_operators.py | 291 +++++++++++++++--- src/addon/operators/preferences_operators.py | 113 ++++--- .../properties/domain_advanced_properties.py | 2 +- .../properties/domain_debug_properties.py | 4 +- .../properties/domain_materials_properties.py | 10 + src/addon/properties/domain_properties.py | 2 + .../properties/domain_stats_properties.py | 21 ++ src/addon/properties/flip_fluid_properties.py | 12 +- src/addon/properties/helper_properties.py | 31 +- src/addon/properties/object_properties.py | 9 +- src/addon/render.py | 51 ++- .../run_simulation_and_render.py | 1 + src/addon/types.py | 4 +- src/addon/ui/cache_object_ui.py | 2 + src/addon/ui/domain_cache_ui.py | 4 +- src/addon/ui/domain_simulation_ui.py | 16 + src/addon/ui/domain_stats_ui.py | 11 + src/addon/ui/helper_ui.py | 136 +++++--- src/engine/diffuseparticlesimulation.cpp | 9 - src/engine/fluidsimulation.cpp | 31 +- src/engine/fluidsimulation.h | 7 +- src/engine/forcefield.h | 1 + src/engine/forcefieldcurve.cpp | 17 +- src/engine/forcefieldpoint.cpp | 16 +- src/engine/forcefieldsurface.cpp | 17 +- src/engine/forcefieldutils.cpp | 4 +- src/engine/forcefieldvolume.cpp | 18 +- src/engine/pyfluid/fluidsimulation.py | 1 + src/engine/stopwatch.h | 4 +- 38 files changed, 784 insertions(+), 215 deletions(-) diff --git a/.gitignore b/.gitignore index fa5bc41f..e087382f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ src/engine/versionutils.cpp src/addon/__init__.py src/addon/utils/installation_utils.py CMakeLists.txt -build \ No newline at end of file +build +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index c7cb6e47..e58b6221 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Want to try the FLIP Fluids addon before buying the [full marketplace product](h ### Getting Started -Download the latest FLIP Fluids Demo installation file here: [FLIP_Fluids_addon_0.6.3_demo_(20_apr_2023.zip)](https://github.com/rlguy/Blender-FLIP-Fluids/releases/download/v0.6.3/FLIP_Fluids_addon_0.6.3_demo_.20_apr_2023.zip) +Download the latest FLIP Fluids Demo installation file here: [FLIP_Fluids_addon_0.6.4_demo_(23_may_2023.zip)](https://github.com/rlguy/Blender-FLIP-Fluids/releases/download/v0.6.3/FLIP_Fluids_addon_0.6.4_demo_.23_may_2023.zip) After downloading the demo addon, follow our [Installation Instructions](https://github.com/rlguy/Blender-FLIP-Fluids/wiki/Addon-Installation-and-Uninstallation). The instructions are similar to installing any other Blender addon. diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 77f4c213..8cb31973 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -53,8 +53,8 @@ set(CMAKE_BUILD_TYPE Release) set(FLUIDENGINE_VERSION_TYPE_IS_STABLE_BUILD TRUE) set(FLUIDENGINE_VERSION_MAJOR 0) set(FLUIDENGINE_VERSION_MINOR 6) -set(FLUIDENGINE_VERSION_REVISION 3) -set(FLUIDENGINE_VERSION_DATE "20-APR-2023") +set(FLUIDENGINE_VERSION_REVISION 4) +set(FLUIDENGINE_VERSION_DATE "23-MAY-2023") if(FLUIDENGINE_VERSION_TYPE_IS_STABLE_BUILD) set(FLUIDENGINE_VERSION_TYPE_LABEL "Demo") @@ -65,10 +65,9 @@ else() endif() set(FLUIDENGINE_VERSION_LABEL "${FLUIDENGINE_VERSION_MAJOR}.${FLUIDENGINE_VERSION_MINOR}.${FLUIDENGINE_VERSION_REVISION} ${FLUIDENGINE_VERSION_TYPE_LABEL} ${FLUIDENGINE_VERSION_DATE}") - -if(BUILD_DEBUG) - set(FLUIDENGINE_VERSION_LABEL "${FLUIDENGINE_VERSION_LABEL} (DEBUG BUILD)") -endif() +#if(BUILD_DEBUG) +# set(FLUIDENGINE_VERSION_LABEL "${FLUIDENGINE_VERSION_LABEL} (DEBUG BUILD)") +#endif() message(STATUS "FLIP Fluids version ${FLUIDENGINE_VERSION_LABEL}") if(BUILD_DEBUG) diff --git a/src/addon/bake.py b/src/addon/bake.py index 81e5d024..8ce66fc7 100644 --- a/src/addon/bake.py +++ b/src/addon/bake.py @@ -722,7 +722,6 @@ def __delete_outdated_savestates(cache_directory, savestate_id): if savestate_number > savestate_id: path = os.path.join(savestate_directory, d) try: - print("Delete savestate", path) fpl.delete_files_in_directory(path, extensions, remove_directory=True) except: print("Error: unable to delete directory <" + path + "> (skipping)") @@ -2371,6 +2370,7 @@ def __get_frame_stats_dict(cstats): stats["fluid_particles"] = cstats.fluid_particles stats["diffuse_particles"] = cstats.diffuse_particles stats["substeps"] = cstats.substeps + stats["performance_score"] = cstats.performance_score stats["pressure_solver_enabled"] = cstats.pressure_solver_enabled stats["pressure_solver_success"] = cstats.pressure_solver_success stats["pressure_solver_error"] = cstats.pressure_solver_error @@ -2664,7 +2664,7 @@ def __write_autosave_data(domain_data, cache_directory, fluidsim, frameno): savestate_dir = os.path.join(cache_directory, "savestates", "autosave" + numstr) if os.path.isdir(savestate_dir): fpl.delete_files_in_directory(savestate_dir, [".state", ".data"], remove_directory=True) - shutil.copytree(autosave_dir, savestate_dir) + shutil.copytree(autosave_dir, savestate_dir, dirs_exist_ok=True) def __write_simulation_output(domain_data, fluidsim, frameno, cache_directory): diff --git a/src/addon/filesystem/filesystem_protection_layer.py b/src/addon/filesystem/filesystem_protection_layer.py index a90abdeb..043de8da 100644 --- a/src/addon/filesystem/filesystem_protection_layer.py +++ b/src/addon/filesystem/filesystem_protection_layer.py @@ -219,7 +219,7 @@ def clear_cache_directory(cache_directory, clear_export=False, clear_logs=False, savestates_dir = os.path.join(cache_directory, "savestates") if os.path.isdir(savestates_dir): - extensions = [".data", ".state"] + extensions = [".data", ".state", ".backup"] savestate_subdirs = [d for d in os.listdir(savestates_dir) if os.path.isdir(os.path.join(savestates_dir, d))] for subd in savestate_subdirs: if subd.startswith("autosave"): diff --git a/src/addon/objects/flip_fluid_cache.py b/src/addon/objects/flip_fluid_cache.py index 1db9b319..6990ff38 100644 --- a/src/addon/objects/flip_fluid_cache.py +++ b/src/addon/objects/flip_fluid_cache.py @@ -219,6 +219,8 @@ def initialize_cache_object(self): def delete_cache_object(self): if self.cache_object is None: return + if not bpy.context.scene.flip_fluid.is_domain_in_active_scene(): + return self.unload_duplivert_object() cache_object = self.cache_object vcu.delete_object(cache_object) @@ -850,6 +852,8 @@ def unload_duplivert_object(self): def get_cache_object(self): + if not bpy.context.scene.flip_fluid.is_domain_in_active_scene(): + return None if self.cache_object is None: return None @@ -1731,6 +1735,8 @@ def initialize_cache_objects(self): self.initialize_cache_settings() if not self._is_domain_set(): return + if not bpy.context.scene.flip_fluid.is_domain_in_active_scene(): + return self.surface.initialize_cache_object() @@ -1785,6 +1791,9 @@ def reset_cache_objects(self): self.initialize_cache_settings() if not self._is_domain_set(): return + if not bpy.context.scene.flip_fluid.is_domain_in_active_scene(): + return + self.surface.reset_cache_object() self.foam.reset_cache_object() self.bubble.reset_cache_object() diff --git a/src/addon/operators/bake_operators.py b/src/addon/operators/bake_operators.py index ab9581db..6a627c1e 100644 --- a/src/addon/operators/bake_operators.py +++ b/src/addon/operators/bake_operators.py @@ -307,6 +307,12 @@ def execute(self, context): self.cancel(context) return {'CANCELLED'} + if not context.scene.flip_fluid.is_domain_in_active_scene(): + self.report({"ERROR"}, + "Active scene must contain domain object to begin baking. Select the scene that contains the domain object and try again.") + self.cancel(context) + return {'CANCELLED'} + dprops = self._get_domain_properties() if dprops.bake.is_simulation_running: self.cancel(context) @@ -525,6 +531,12 @@ def execute(self, context): self.cancel(context) return {'CANCELLED'} + if not context.scene.flip_fluid.is_domain_in_active_scene(): + self.report({"ERROR"}, + "Active scene must contain domain object to begin baking. Select the scene that contains the domain object, save, and try again.") + self.cancel(context) + return {'CANCELLED'} + self._reset_bake(context) self._initialize_domain(context) success = self._export_simulation_data(context) diff --git a/src/addon/operators/cache_operators.py b/src/addon/operators/cache_operators.py index c8f47850..cf659beb 100644 --- a/src/addon/operators/cache_operators.py +++ b/src/addon/operators/cache_operators.py @@ -411,7 +411,7 @@ def execute(self, context): class FlipFluidIncreaseDecreaseCacheDirectory(bpy.types.Operator): - bl_idname = "flip_fluid_operators.increment_decrease_cache_directory" + bl_idname = "flip_fluid_operators.increase_decrease_cache_directory" bl_label = "Increase/Decrease Cache Directory" bl_description = ("Increase or decrease a numbered suffix on the cache directory." + " Note: this will not rename an existing cache directory") @@ -468,6 +468,105 @@ def execute(self, context): return {'FINISHED'} +class FlipFluidIncreaseDecreaseRenderDirectory(bpy.types.Operator): + bl_idname = "flip_fluid_operators.increase_decrease_render_directory" + bl_label = "Increase/Decrease Render Directory" + bl_description = ("Increase or decrease a numbered suffix on the render output directory." + + " Note: this will not rename an existing render output directory") + + increment_mode = StringProperty(default="INCREASE") + exec(vcu.convert_attribute_to_28("increment_mode")) + + + @classmethod + def poll(cls, context): + return True + + + def get_trailing_number(self, s): + m = re.search(r'\d+$', s) + return int(m.group()) if m else None + + + def ends_with_slash(self, s): + return s.endswith("/") or s.endswith("\\") + + + def ends_with_underscore(self, s): + return s.endswith("_") + + + def execute(self, context): + render_directory = context.scene.render.filepath + + basename = render_directory + endswith_slash = self.ends_with_slash(basename) + endswith_underscore = self.ends_with_underscore(basename) + + slash_character = "" + if endswith_slash: + slash_character = basename[-1] + basename = basename[:-1] + elif endswith_underscore: + basename = basename[:-1] + + suffix_number = self.get_trailing_number(basename) + if suffix_number: + basename = basename[:-len(str(suffix_number))] + if endswith_underscore: + if self.ends_with_underscore(basename): + basename = basename[:-1] + + if self.increment_mode == 'INCREASE': + if not suffix_number: + suffix_number = 0 + suffix_number += 1 + suffix_string = str(suffix_number) + else: + if not suffix_number: + return {'FINISHED'} + if suffix_number <= 1: + suffix_string = "" + + else: + suffix_string = str(suffix_number - 1) + + if endswith_slash: + new_basename = basename + suffix_string + slash_character + elif endswith_underscore: + if suffix_string: + new_basename = basename + "_" + suffix_string + "_" + else: + new_basename = basename + "_" + else: + new_basename = basename + suffix_string + + context.scene.render.filepath = new_basename + return {'FINISHED'} + + +class FlipFluidIncreaseDecreaseCacheRenderVersion(bpy.types.Operator): + bl_idname = "flip_fluid_operators.increase_decrease_cache_render_version" + bl_label = "Increase/Decrease Cache and Render Version" + bl_description = ("Increase or decrease a numbered suffix on both the cache" + + " directory and render output directory. Note: this will not rename an" + + " existing cache our render output directory") + + increment_mode = StringProperty(default="INCREASE") + exec(vcu.convert_attribute_to_28("increment_mode")) + + + @classmethod + def poll(cls, context): + return True + + + def execute(self, context): + bpy.ops.flip_fluid_operators.increase_decrease_cache_directory(increment_mode=self.increment_mode) + bpy.ops.flip_fluid_operators.increase_decrease_render_directory(increment_mode=self.increment_mode) + return {'FINISHED'} + + class FlipFluidRelativeLinkedGeometryDirectory(bpy.types.Operator): bl_idname = "flip_fluid_operators.relative_linked_geometry_directory" bl_label = "Make Relative" @@ -569,6 +668,8 @@ def register(): bpy.utils.register_class(FlipFluidAbsoluteCacheDirectory) bpy.utils.register_class(FlipFluidMatchFilenameCacheDirectory) bpy.utils.register_class(FlipFluidIncreaseDecreaseCacheDirectory) + bpy.utils.register_class(FlipFluidIncreaseDecreaseRenderDirectory) + bpy.utils.register_class(FlipFluidIncreaseDecreaseCacheRenderVersion) bpy.utils.register_class(FlipFluidRelativeLinkedGeometryDirectory) bpy.utils.register_class(FlipFluidAbsoluteLinkedGeometryDirectory) bpy.utils.register_class(FlipFluidClearLinkedGeometryDirectory) @@ -588,6 +689,8 @@ def unregister(): bpy.utils.unregister_class(FlipFluidAbsoluteCacheDirectory) bpy.utils.unregister_class(FlipFluidMatchFilenameCacheDirectory) bpy.utils.unregister_class(FlipFluidIncreaseDecreaseCacheDirectory) + bpy.utils.unregister_class(FlipFluidIncreaseDecreaseRenderDirectory) + bpy.utils.unregister_class(FlipFluidIncreaseDecreaseCacheRenderVersion) bpy.utils.unregister_class(FlipFluidRelativeLinkedGeometryDirectory) bpy.utils.unregister_class(FlipFluidAbsoluteLinkedGeometryDirectory) bpy.utils.unregister_class(FlipFluidClearLinkedGeometryDirectory) diff --git a/src/addon/operators/export_operators.py b/src/addon/operators/export_operators.py index 38f176e6..f7036c47 100644 --- a/src/addon/operators/export_operators.py +++ b/src/addon/operators/export_operators.py @@ -133,6 +133,12 @@ def modal(self, context, event): self.cancel(context) return {'CANCELLED'} + if not context.scene.flip_fluid.is_domain_in_active_scene(): + self.report({"ERROR"}, + "Active scene must contain domain object during export - halting export and baking process. Please do not switch active scenes during export.") + self.cancel(context) + return {'CANCELLED'} + if dprops.bake.is_export_operator_cancelled: self.cancel(context) return {'FINISHED'} diff --git a/src/addon/operators/helper_operators.py b/src/addon/operators/helper_operators.py index ad06914e..045e90f7 100644 --- a/src/addon/operators/helper_operators.py +++ b/src/addon/operators/helper_operators.py @@ -1,5 +1,5 @@ # Blender FLIP Fluids Add-on -# Copyright (C) 2023 Ryan L. Guy +# Copyright (C) 2022 Ryan L. Guy # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import bpy, os, stat, subprocess, platform, math, mathutils, fnmatch, random, shutil +import bpy, os, stat, subprocess, platform, math, mathutils, fnmatch, random from bpy.props import ( BoolProperty, StringProperty @@ -35,6 +35,159 @@ def _select_make_active(context, active_object): vcu.set_active_object(active_object, context) +class FlipFluidHelperRemesh(bpy.types.Operator): + bl_idname = "flip_fluid_operators.helper_remesh" + bl_label = "FLIP Fluids Remesh Collection" + bl_description = ("Combine object geometry within a collection and remesh into a single object for use in the simulator." + + " Optionally convert non-mesh objects to mesh, apply modifiers, and skip objects hidden from render." + + " Saving is recommended before using this operator - this operator may take some time to compute depending on complexity" + + " of the input geometry. Use the link next to this operator to view documentation and a video guide" + + " for using this feature") + + skip_hide_render_objects = BoolProperty(True) + exec(vcu.convert_attribute_to_28("skip_hide_render_objects")) + + apply_object_modifiers = BoolProperty(True) + exec(vcu.convert_attribute_to_28("apply_object_modifiers")) + + convert_objects_to_mesh = BoolProperty(True) + exec(vcu.convert_attribute_to_28("convert_objects_to_mesh")) + + + @classmethod + def poll(cls, context): + return True + + + def display_convert_to_mesh_popup(self, object_list): + def draw_func(self, context): + text_label = "The following (" + str(len(object_list)) + text_label += ") selected objects are required be converted to a mesh type before using this operator:" + self.layout.label(text=text_label) + column = self.layout.column(align=True) + split = column.split(align=True) + column_left = split.column(align=True) + column_right = split.column(align=True) + for obj in object_list: + column_left.label(text=5*" " + "Name: " + obj.name) + column_right.label(text="Type: " + obj.type) + self.layout.label(text="") + self.layout.label(text="Solutions: ", icon="INFO") + self.layout.label(text=5*" " + "(1) Convert objects to a Blender mesh object") + self.layout.label(text=5*" " + "(2) Or disable objects in viewport (outliner monitor icon)") + self.layout.label(text=5*" " + "(3) Or enable 'Convert Objects to Mesh' option") + + bpy.context.window_manager.popup_menu(draw_func, title="FLIP Fluids Remesh: Actions Required", icon='INFO') + + + def display_apply_modifiers_popup(self, object_list): + def draw_func(self, context): + text_label = "The following (" + str(len(object_list)) + text_label += ") selected objects are required to have modifiers applied before using this operator:" + self.layout.label(text=text_label) + column = self.layout.column(align=True) + split = column.split(align=True) + column_left = split.column(align=True) + column_right = split.column(align=True) + for obj in object_list: + column_left.label(text=5*" " + "Name: " + obj.name) + column_right.label(text="# Modifiers: " + str(len(obj.modifiers))) + self.layout.label(text="") + self.layout.label(text="Solutions: ", icon="INFO") + self.layout.label(text=5*" " + "(1) Apply modifiers to objects") + self.layout.label(text=5*" " + "(2) Or disable objects in viewport (outliner monitor icon)") + self.layout.label(text=5*" " + "(3) Or enable 'Apply Object Modifiers' option") + + bpy.context.window_manager.popup_menu(draw_func, title="FLIP Fluids Remesh: Actions Required", icon='INFO') + + + def set_blender_object_selection(self, context, object_list): + # Select valid objects and set a valid active object if possible + bpy.ops.object.select_all(action='DESELECT') + if object_list: + context.view_layer.objects.active = object_list[0] + for obj in object_list: + obj.select_set(True) + + + def execute(self, context): + # Object types that can be converted to a mesh and can have modifiers applied + valid_object_types = ['MESH', 'CURVE', 'SURFACE', 'META', 'FONT'] + + # Operator can only function in Object mode + if bpy.context.object.mode != 'OBJECT': + err_msg = "FLIP Fluids Remesh: Viewport must be in Object mode to use this operator. " + err_msg += " Current viewport mode: <" + bpy.context.object.mode + ">." + self.report({'ERROR'}, err_msg) + return {'CANCELLED'} + + # Objects will be operated on if they are contained in this collection or any valid subcollections + active_collection = context.view_layer.active_layer_collection.collection + if active_collection is None: + self.report({'ERROR'}, "FLIP Fluids Remesh: No collection selected. Select a collection to remesh.") + return {'CANCELLED'} + + # Filter out objects that are invalid types that cannot be converted to a mesh and have modifiers applied + skipped_invalid_type_objects = [obj for obj in active_collection.all_objects if obj.type not in valid_object_types] + valid_collection_objects = [obj for obj in active_collection.all_objects if obj.type in valid_object_types] + + # Filter out objects that are disabled in the viewport and are unable to be operated on + skipped_non_visible_objects = [obj for obj in valid_collection_objects if not obj.visible_get()] + valid_collection_objects = [obj for obj in valid_collection_objects if obj.visible_get()] + + # Filter out objects that have selection disabled and are unable to be operated on + skipped_hide_select_objects = [obj for obj in valid_collection_objects if obj.hide_select] + valid_collection_objects = [obj for obj in valid_collection_objects if not obj.hide_select] + + # Filter out objects that have render visibility disabled + skipped_hide_render_objects = [] + if self.skip_hide_render_objects: + skipped_hide_render_objects = [obj for obj in valid_collection_objects if obj.hide_render] + valid_collection_objects = [obj for obj in valid_collection_objects if not obj.hide_render] + + # If in the case there are no objects that can be operated on, there is nothing that can be done + if not valid_collection_objects: + self.report({'ERROR'}, "FLIP Fluids Remesh: No valid objects to remesh.") + return {'CANCELLED'} + + # Objects that can be automatically converted to a mesh + objects_to_convert_to_mesh = [obj for obj in valid_collection_objects if obj.type != 'MESH' and obj.type in valid_object_types] + if not self.convert_objects_to_mesh and objects_to_convert_to_mesh: + self.set_blender_object_selection(context, objects_to_convert_to_mesh) + self.display_convert_to_mesh_popup(objects_to_convert_to_mesh) + return {'CANCELLED'} + + # Objects that have modifiers to be applied + objects_with_modifiers = [obj for obj in valid_collection_objects if len(obj.modifiers) >= 1] + if not self.apply_object_modifiers and objects_with_modifiers: + self.set_blender_object_selection(context, objects_with_modifiers) + self.display_apply_modifiers_popup(objects_with_modifiers) + return {'CANCELLED'} + + valid_object_count = len(valid_collection_objects) + self.set_blender_object_selection(context, valid_collection_objects) + + # Covert all selected objects to MESH type and apply modifiers + bpy.ops.object.convert(target='MESH') + + # Join all selected objects and rename object + bpy.ops.object.join() + context.view_layer.objects.active.name = active_collection.name + "_joined" + + # Add REMESH modifier + remesh_modifier = context.view_layer.objects.active.modifiers.new("FLIP Fluids Remesh", 'REMESH') + remesh_modifier.mode = 'VOXEL' + remesh_modifier.voxel_size = 0.04 + remesh_modifier.adaptivity = 0.1 + remesh_modifier.use_smooth_shade = True + + info_msg = "FLIP Fluids Remesh: Successfully merged and remeshed " + info_msg += str(valid_object_count) + " valid objects into object <" + context.view_layer.objects.active.name + ">" + self.report({'INFO'}, info_msg) + + return {'FINISHED'} + + class FlipFluidHelperSelectDomain(bpy.types.Operator): bl_idname = "flip_fluid_operators.helper_select_domain" bl_label = "Select Domain" @@ -107,6 +260,10 @@ def execute(self, context): dprops = context.scene.flip_fluid.get_domain_properties() if dprops is None: return {'CANCELLED'} + if not context.scene.flip_fluid.is_domain_in_active_scene(): + self.report({'ERROR'}, "Unable to select Surface object: Domain object is not located in the active scene>") + return {'CANCELLED'} + dprops.mesh_cache.initialize_cache_objects() surface_object = dprops.mesh_cache.surface.get_cache_object() if surface_object is None: @@ -131,6 +288,10 @@ def execute(self, context): dprops = context.scene.flip_fluid.get_domain_properties() if dprops is None: return {'CANCELLED'} + if not context.scene.flip_fluid.is_domain_in_active_scene(): + self.report({'ERROR'}, "Unable to select Whitewater Foam object: Domain object is not located in the active scene>") + return {'CANCELLED'} + dprops.mesh_cache.initialize_cache_objects() foam_object = dprops.mesh_cache.foam.get_cache_object() if foam_object is None: @@ -156,6 +317,10 @@ def execute(self, context): dprops = context.scene.flip_fluid.get_domain_properties() if dprops is None: return {'CANCELLED'} + if not context.scene.flip_fluid.is_domain_in_active_scene(): + self.report({'ERROR'}, "Unable to select Whitewater Bubble object: Domain object is not located in the active scene>") + return {'CANCELLED'} + dprops.mesh_cache.initialize_cache_objects() bubble_object = dprops.mesh_cache.bubble.get_cache_object() if bubble_object is None: @@ -180,6 +345,10 @@ def execute(self, context): dprops = context.scene.flip_fluid.get_domain_properties() if dprops is None: return {'CANCELLED'} + if not context.scene.flip_fluid.is_domain_in_active_scene(): + self.report({'ERROR'}, "Unable to select Whitewater Spray object: Domain object is not located in the active scene>") + return {'CANCELLED'} + dprops.mesh_cache.initialize_cache_objects() spray_object = dprops.mesh_cache.spray.get_cache_object() if spray_object is None: @@ -204,6 +373,10 @@ def execute(self, context): dprops = context.scene.flip_fluid.get_domain_properties() if dprops is None: return {'CANCELLED'} + if not context.scene.flip_fluid.is_domain_in_active_scene(): + self.report({'ERROR'}, "Unable to select Whitewater Dust object: Domain object is not located in the active scene>") + return {'CANCELLED'} + dprops.mesh_cache.initialize_cache_objects() dust_object = dprops.mesh_cache.dust.get_cache_object() if dust_object is None: @@ -1057,6 +1230,11 @@ def execute(self, context): # Setting a frame during render will disrupt the render process return {'CANCELLED'} + if not context.scene.flip_fluid.is_domain_in_active_scene(): + # Active scene does not contain the simulation and should not load the frame + return {'CANCELLED'} + + dprops = context.scene.flip_fluid.get_domain_properties() cache_dir = dprops.cache.get_cache_abspath() bakefiles_dir = os.path.join(cache_dir, "bakefiles") @@ -1297,6 +1475,11 @@ def execute(self, context): if domain is None: return {'CANCELLED'} + if not context.scene.flip_fluid.is_domain_in_active_scene(): + self.report({"ERROR"}, + "Active scene must contain domain object to launch bake. Select the scene that contains the domain object, save, and try again.") + return {'CANCELLED'} + hprops = context.scene.flip_fluid_helper if hprops.cmd_launch_render_after_bake and not is_render_output_directory_createable(): errmsg = "Render output directory is not valid or writeable: <" + get_render_output_directory() + ">" @@ -1307,7 +1490,7 @@ def execute(self, context): if hprops.cmd_launch_render_after_bake: system = platform.system() render_mode = hprops.cmd_launch_render_mode - if system != "WINDOWS": + if system != "Windows": render_mode = 'CMD_RENDER_MODE_NORMAL' if render_mode == 'CMD_RENDER_MODE_NORMAL': @@ -1396,7 +1579,6 @@ def execute(self, context): popup_width=600 ) - else: # Platform not found return {'CANCELLED'} @@ -1463,9 +1645,6 @@ class FlipFluidHelperCommandLineRender(bpy.types.Operator): bl_description = ("Launch a new command line window and start rendering the animation." + " The .blend file will need to be saved before using this operator") - use_turbo_tools = BoolProperty(False) - exec(vcu.convert_attribute_to_28("use_turbo_tools")) - @classmethod def poll(cls, context): @@ -1479,11 +1658,6 @@ def execute(self, context): self.report({'ERROR'}, errmsg) return {'CANCELLED'} - if self.use_turbo_tools: - command_text = "\"" + bpy.app.binary_path + "\" -b \"" + bpy.data.filepath + "\" --python-expr \"import bpy; bpy.ops.threedi.render_animation()\"" - else: - command_text = "\"" + bpy.app.binary_path + "\" -b \"" + bpy.data.filepath + "\" -a" - system = platform.system() if system == "Windows": restore_blender_original_cwd() @@ -1498,14 +1672,12 @@ def execute(self, context): # changed Blender's working directory blender_exe_path = "blender.exe" - if self.use_turbo_tools: - command = ["start", "cmd", "/k", blender_exe_path, "-b", bpy.data.filepath, "--python-expr", "import bpy; bpy.ops.threedi.render_animation()"] - else: - command = ["start", "cmd", "/k", blender_exe_path, "-b", bpy.data.filepath, "-a"] - + command_text = "\"" + bpy.app.binary_path + "\" --background \"" + bpy.data.filepath + "\" -a" + command = ["start", "cmd", "/k", blender_exe_path, "--background", bpy.data.filepath, "-a"] subprocess.call(command, shell=True) elif system == "Darwin" or system == "Linux": + command_text = "\"" + bpy.app.binary_path + "\" --background \"" + bpy.data.filepath + "\" -a" script_text = "#!/bin/bash\n" + command_text script_name = "RENDER_ANIMATION_" + bpy.path.basename(context.blend_data.filepath) + ".sh" script_filepath = os.path.join(os.path.dirname(bpy.data.filepath), script_name) @@ -1534,7 +1706,6 @@ def execute(self, context): error_description=errmsg, popup_width=600 ) - else: # Platform not found return {'CANCELLED'} @@ -1557,8 +1728,6 @@ class FlipFluidHelperCommandLineRenderToClipboard(bpy.types.Operator): bl_description = ("Copy command for rendering to your system clipboard." + " The .blend file will need to be saved before using this operator") - use_turbo_tools = BoolProperty(False) - exec(vcu.convert_attribute_to_28("use_turbo_tools")) @classmethod def poll(cls, context): @@ -1567,12 +1736,7 @@ def poll(cls, context): def execute(self, context): - if self.use_turbo_tools: - command_text = "\"" + bpy.app.binary_path + "\" -b \"" + bpy.data.filepath + "\" --python-expr \"import bpy; bpy.ops.threedi.render_animation()\"" - else: - command_text = "\"" + bpy.app.binary_path + "\" -b \"" + bpy.data.filepath + "\" -a" - - + command_text = "\"" + bpy.app.binary_path + "\" --background \"" + bpy.data.filepath + "\" -a" bpy.context.window_manager.clipboard = command_text info_msg = "Copied the following render command to your clipboard:\n\n" @@ -1590,9 +1754,6 @@ class FlipFluidHelperCommandLineRenderFrame(bpy.types.Operator): bl_description = ("Launch a new command line window and start rendering the current timeline frame." + " The .blend file will need to be saved before using this operator") - use_turbo_tools = BoolProperty(False) - exec(vcu.convert_attribute_to_28("use_turbo_tools")) - @classmethod def poll(cls, context): @@ -1610,13 +1771,9 @@ def execute(self, context): self.report({'ERROR'}, "Render output format must be an image format. Change render output to an image, save, and try again.") return {'CANCELLED'} - script_name = "render_single_frame.py" - if self.use_turbo_tools: - script_name = "render_single_frame_turbo_tools.py" - script_path = os.path.dirname(os.path.realpath(__file__)) script_path = os.path.dirname(script_path) - script_path = os.path.join(script_path, "resources", "command_line_scripts", script_name) + script_path = os.path.join(script_path, "resources", "command_line_scripts", "render_single_frame.py") frame_string = str(bpy.context.scene.frame_current) @@ -1699,9 +1856,6 @@ class FlipFluidHelperCmdRenderFrameToClipboard(bpy.types.Operator): bl_description = ("Copy command for frame rendering to your system clipboard." + " The .blend file will need to be saved before using this operator") - use_turbo_tools = BoolProperty(False) - exec(vcu.convert_attribute_to_28("use_turbo_tools")) - @classmethod def poll(cls, context): @@ -1709,14 +1863,9 @@ def poll(cls, context): def execute(self, context): - script_name = "render_single_frame.py" - if self.use_turbo_tools: - script_name = "render_single_frame_turbo_tools.py" - script_path = os.path.dirname(os.path.realpath(__file__)) script_path = os.path.dirname(script_path) - script_path = os.path.join(script_path, "resources", "command_line_scripts", script_name) - + script_path = os.path.join(script_path, "resources", "command_line_scripts", "render_single_frame.py") frame_string = str(bpy.context.scene.frame_current) hprops = context.scene.flip_fluid_helper open_image_after = "0" @@ -1840,6 +1989,11 @@ def execute(self, context): self.report({'INFO'}, "Blender 3.1 or later is required for this feature") return {'CANCELLED'} + if not context.scene.flip_fluid.is_domain_in_active_scene(): + self.report({"ERROR"}, + "Active scene must contain domain object to use this operator. Select the scene that contains the domain object and try again.") + return {'CANCELLED'} + if context.scene.render.engine != 'CYCLES': context.scene.render.engine = 'CYCLES' self.report({'INFO'}, "Setting render engine to Cycles") @@ -2018,7 +2172,7 @@ def generate_file_string(self, missing_frames): blender_exe_path = "\"" + bpy.app.binary_path + "\"" blend_path = "\"" + bpy.data.filepath + "\"" - file_text = "echo.\nchcp 65001\n" + file_text = "echo.\n" for n in missing_frames: command_text = blender_exe_path + " -b " + blend_path + " -f " + str(n) file_text += command_text + "\n" @@ -2255,6 +2409,30 @@ def _get_simulation_objects_by_filtered_motion_type(context): return filtered_objects +class FlipFluidHelperBatchExportAnimatedMesh(bpy.types.Operator): + bl_idname = "flip_fluid_operators.helper_batch_export_animated_mesh" + bl_label = "" + bl_description = "Enable or Disable the 'Export Animated Mesh' option for all objects in list" + + + enable_state = BoolProperty(True) + exec(vcu.convert_attribute_to_28("enable_state")) + + + @classmethod + def poll(cls, context): + return context.scene.flip_fluid.get_domain_object() is not None + + + def execute(self, context): + filtered_objects = _get_simulation_objects_by_filtered_motion_type(context) + for obj in filtered_objects: + oprops = obj.flip_fluid.get_property_group() + oprops.export_animated_mesh = self.enable_state + + return {'FINISHED'} + + class FlipFluidHelperBatchSkipReexport(bpy.types.Operator): bl_idname = "flip_fluid_operators.helper_batch_skip_reexport" bl_label = "" @@ -2409,6 +2587,8 @@ def _get_max_bakefile_frame(self, bakefiles_directory): def _update_frame(self, context, frameno): + if not context.scene.flip_fluid.is_domain_in_active_scene(): + return if context.scene.frame_current != frameno and frameno >= 0: context.scene.frame_set(frameno) @@ -2424,6 +2604,8 @@ def _update_modal(self, context): if dprops is None: return + print("Update Modal") + if dprops.bake.is_simulation_running: # Don't update if a simulation bake is already running in the UI return @@ -2443,6 +2625,11 @@ def _update_modal(self, context): self._num_bakefiles_changed_handler(context, bakefiles_directory) + def set_running_state(self, is_running): + for scene in bpy.data.scenes: + scene.flip_fluid_helper.is_auto_frame_load_cmd_operator_running = is_running + + def modal(self, context, event): if self.is_modal_update_required: self.is_modal_update_required = False @@ -2478,7 +2665,7 @@ def func(): self.modal_timer = context.window_manager.event_timer_add(0.1, window=context.window) bpy.app.timers.register(self.modal_ups_timer) - context.scene.flip_fluid_helper.is_auto_frame_load_cmd_operator_running = True + self.set_running_state(True) return {'RUNNING_MODAL'} @@ -2489,11 +2676,11 @@ def cancel(self, context): if bpy.app.timers.is_registered(self.modal_ups_timer): bpy.app.timers.unregister(self.modal_ups_timer) - - context.scene.flip_fluid_helper.is_auto_frame_load_cmd_operator_running = False + + self.set_running_state(False) -class FlipFluidCopySettingsToSelected(bpy.types.Operator): +class FlipFluidCopySettingsFromActive(bpy.types.Operator): bl_idname = "flip_fluid_operators.copy_setting_to_selected" bl_label = "Copy Active Object Settings to Selected Objects" bl_description = ("Copy the settings of the active FLIP object (highlighted object) to all other selected" @@ -2611,6 +2798,7 @@ def execute(self, context): def register(): classes = [ + FlipFluidHelperRemesh, FlipFluidHelperSelectDomain, FlipFluidHelperSelectSurface, FlipFluidHelperSelectFoam, @@ -2643,6 +2831,7 @@ def register(): FlipFluidHelperStableRendering28, FlipFluidHelperSetLinearOverrideKeyframes, FlipFluidHelperSaveBlendFile, + FlipFluidHelperBatchExportAnimatedMesh, FlipFluidHelperBatchSkipReexport, FlipFluidHelperBatchForceReexport, FlipFluidEnableWhitewaterSimulation, @@ -2661,7 +2850,7 @@ def register(): FlipFluidMakeRelativeToBlendRenderOutput, FlipFluidMakePrefixFilenameRenderOutput, FlipFluidAutoLoadBakedFramesCMD, - FlipFluidCopySettingsToSelected, + FlipFluidCopySettingsFromActive, ] # Workaround for a bug in FLIP Fluids 1.6.0 @@ -2678,6 +2867,7 @@ def register(): def unregister(): + bpy.utils.unregister_class(FlipFluidHelperRemesh) bpy.utils.unregister_class(FlipFluidHelperSelectDomain) bpy.utils.unregister_class(FlipFluidHelperSelectSurface) bpy.utils.unregister_class(FlipFluidHelperSelectFoam) @@ -2710,6 +2900,7 @@ def unregister(): bpy.utils.unregister_class(FlipFluidHelperStableRendering28) bpy.utils.unregister_class(FlipFluidHelperSetLinearOverrideKeyframes) bpy.utils.unregister_class(FlipFluidHelperSaveBlendFile) + bpy.utils.unregister_class(FlipFluidHelperBatchExportAnimatedMesh) bpy.utils.unregister_class(FlipFluidHelperBatchSkipReexport) bpy.utils.unregister_class(FlipFluidHelperBatchForceReexport) bpy.utils.unregister_class(FlipFluidEnableWhitewaterSimulation) @@ -2728,4 +2919,4 @@ def unregister(): bpy.utils.unregister_class(FlipFluidMakeRelativeToBlendRenderOutput) bpy.utils.unregister_class(FlipFluidMakePrefixFilenameRenderOutput) bpy.utils.unregister_class(FlipFluidAutoLoadBakedFramesCMD) - bpy.utils.unregister_class(FlipFluidCopySettingsToSelected) + bpy.utils.unregister_class(FlipFluidCopySettingsFromActive) diff --git a/src/addon/operators/preferences_operators.py b/src/addon/operators/preferences_operators.py index 9170f21f..25d92219 100644 --- a/src/addon/operators/preferences_operators.py +++ b/src/addon/operators/preferences_operators.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import bpy, os, shutil, json, zipfile, urllib.request, sys, textwrap, platform, random +import bpy, os, shutil, json, zipfile, urllib.request, sys, textwrap, platform, random, traceback from bpy_extras.io_utils import ImportHelper from bpy.props import ( @@ -913,15 +913,24 @@ def prepr(v): try: import gpu gpu_string = gpu.platform.renderer_get() + " " + gpu.platform.vendor_get() + " " + gpu.platform.version_get() - except: - pass + except Exception as e: + print(traceback.format_exc()) + print(e) cpu_string = "Unknown (fill in)" try: from ..third_party import cpuinfo cpu_string = cpuinfo.cpu.info[0]['ProcessorNameString'] - except: - pass + except KeyError: + # Apple Silicon systems may not contain the ProcessorNameString + try: + cpu_string = cpuinfo.cpu.info['arch'].decode("utf-8") + except Exception as e: + print(traceback.format_exc()) + print(e) + except Exception as e: + print(traceback.format_exc()) + print(e) threads_string = "Unknown" try: @@ -930,8 +939,9 @@ def prepr(v): num_threads = bpy.context.scene.render.threads bpy.context.scene.render.threads_mode = original_threads_mode threads_string = str(num_threads) - except: - pass + except Exception as e: + print(traceback.format_exc()) + print(e) addon_path_string = _get_addon_directory() @@ -949,8 +959,9 @@ def prepr(v): if os.path.isdir(logs_directory): log_files = [f for f in os.listdir(logs_directory) if os.path.isfile(os.path.join(logs_directory, f))] log_files_string = str(len(log_files)) - except: - pass + except Exception as e: + print(traceback.format_exc()) + print(e) default_addons = [ "Pose Library", @@ -974,23 +985,26 @@ def prepr(v): if addon_name not in default_addons: addons_string += addon_name + ", " addons_string = addons_string.removesuffix(", ") - except: + except Exception as e: + print(traceback.format_exc()) + print(e) addons_string = "Unknown" - pass developer_tools_string = "Uknown" try: preferences = vcu.get_addon_preferences() developer_tools_string = "Enabled" if preferences.enable_developer_tools else "Disabled" - except: - pass + except Exception as e: + print(traceback.format_exc()) + print(e) mixbox_installed_string = "Unknown" try: is_mixbox_installed = installation_utils.is_mixbox_installation_complete() mixbox_installed_string = "Installed" if is_mixbox_installed else "Not Installed" - except: - pass + except Exception as e: + print(traceback.format_exc()) + print(e) features_string = "N/A" try: @@ -1011,44 +1025,50 @@ def prepr(v): features_string = features_string.removesuffix(", ") if not features_string: features_string = "Default" - except: + except Exception as e: + print(traceback.format_exc()) + print(e) features_string = "Unknown" - pass attributes_string = "N/A" try: dprops = bpy.context.scene.flip_fluid.get_domain_properties() if dprops is not None: d = api_utils.get_enabled_features_affected_by_T88811() - if d["attributes"]["surface"] or d["attributes"]["whitewater"] or d["viscosity"]: - attributes_string = "" - for att in d["attributes"]["surface"]: - attributes_string += "Surface " + att + ", " - if d["viscosity"]: - attributes_string += "Surface Viscosity, " - for att in d["attributes"]["whitewater"]: - attributes_string += "Whitewater " + att + ", " - attributes_string = attributes_string.removesuffix(", ") - except: - pass + if d is not None: + if d["attributes"]["surface"] or d["attributes"]["whitewater"] or d["viscosity"]: + attributes_string = "" + for att in d["attributes"]["surface"]: + attributes_string += "Surface " + att + ", " + if d["viscosity"]: + attributes_string += "Surface Viscosity, " + for att in d["attributes"]["whitewater"]: + attributes_string += "Whitewater " + att + ", " + attributes_string = attributes_string.removesuffix(", ") + except Exception as e: + print(traceback.format_exc()) + print(e) lock_interface_string = "Unknown" try: lock_interface_string = "Enabled" if bpy.context.scene.render.use_lock_interface else "Disabled" - except: - pass + except Exception as e: + print(traceback.format_exc()) + print(e) persistent_data_string = "Disabled" try: persistent_data_string = "Enabled" if api_utils.is_persistent_data_issue_relevant() else "Disabled" - except: - pass + except Exception as e: + print(traceback.format_exc()) + print(e) blender_binary_string = "Unknown" try: blender_binary_string = bpy.app.binary_path - except: - pass + except Exception as e: + print(traceback.format_exc()) + print(e) viewport_modes_string = "N/A" try: @@ -1063,9 +1083,10 @@ def prepr(v): for mode in shading_modes: viewport_modes_string += mode + ", " viewport_modes_string = viewport_modes_string.removesuffix(", ") - except: + except Exception as e: + print(traceback.format_exc()) + print(e) viewport_modes_string = "Unknown" - pass domains_string = "N/A" domain_count = 0 @@ -1100,8 +1121,8 @@ def prepr(v): skip_export_outflows_string = "Unknown" skip_export_force_fields_string = "Unknown" flip_objects_string = "Unknown" + found_domains = [] try: - found_domains = [] for scene in bpy.data.scenes: for obj in scene.objects: if obj.flip_fluid.is_domain(): @@ -1173,15 +1194,15 @@ def prepr(v): force_fields_string = str(force_field_count) + animated_force_fields_string + skip_export_force_fields_string flip_objects_string = str(domain_count + obstacle_count + fluid_count + inflow_count + outflow_count + force_field_count) except Exception as e: + print(traceback.format_exc()) print(e) - domains_string = "Unknown" - pass objects_string = "Unknown" try: objects_string = str(len(bpy.data.objects)) - except: - pass + except Exception as e: + print(traceback.format_exc()) + print(e) renderer_string = "Unknown" cycles_device_string = "N/A" @@ -1189,8 +1210,9 @@ def prepr(v): renderer_string = bpy.context.scene.render.engine if renderer_string == 'CYCLES': cycles_device_string = bpy.context.scene.cycles.device - except: - pass + except Exception as e: + print(traceback.format_exc()) + print(e) simulation_visibility_string = "N/A" surface_visibility_string = "N/A" @@ -1218,8 +1240,9 @@ def prepr(v): whitewater_viewport = str(dprops.render.whitewater_viewport_display) whitewater_render = str(dprops.render.whitewater_render_display) whitewater_visibility_string = " " - except: - pass + except Exception as e: + print(traceback.format_exc()) + print(e) d = {} d['blender_version'] = blender_version diff --git a/src/addon/properties/domain_advanced_properties.py b/src/addon/properties/domain_advanced_properties.py index c4b9f776..68412367 100644 --- a/src/addon/properties/domain_advanced_properties.py +++ b/src/addon/properties/domain_advanced_properties.py @@ -157,7 +157,7 @@ class DomainAdvancedProperties(bpy.types.PropertyGroup): name="Threads", description="Number of threads to use simultaneously while simulating", min=1, max=1024, - default=1, + default=4, ); exec(conv("num_threads_fixed")) threading_mode = EnumProperty( name="Threading Mode", diff --git a/src/addon/properties/domain_debug_properties.py b/src/addon/properties/domain_debug_properties.py index c8198964..9f52a59b 100644 --- a/src/addon/properties/domain_debug_properties.py +++ b/src/addon/properties/domain_debug_properties.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import bpy, os, sys, platform +import bpy, os, sys, platform, traceback from bpy.props import ( BoolProperty, BoolVectorProperty, @@ -413,8 +413,8 @@ def save_pre(self): self.version_history.clear() self.system_info = "No System and Blend File Info " + _LOGGING_DISABLED_MESSAGE except Exception as e: + print(traceback.format_exc()) print(e) - pass def print_version_history(self): diff --git a/src/addon/properties/domain_materials_properties.py b/src/addon/properties/domain_materials_properties.py index 6e103d0c..3de5516c 100644 --- a/src/addon/properties/domain_materials_properties.py +++ b/src/addon/properties/domain_materials_properties.py @@ -127,6 +127,8 @@ def _get_domain_properties(self): def _update_surface_material(self, context): if not self._is_domain_set(): return + if not context.scene.flip_fluid.is_domain_in_active_scene(): + return dprops = self._get_domain_properties() surface_object = dprops.mesh_cache.surface.get_cache_object() self._update_cache_object_material( @@ -140,6 +142,8 @@ def _update_surface_material(self, context): def _update_whitewater_foam_material(self, context): if not self._is_domain_set(): return + if not context.scene.flip_fluid.is_domain_in_active_scene(): + return dprops = self._get_domain_properties() foam_object = dprops.mesh_cache.foam.get_cache_object() self._update_cache_object_material( @@ -154,6 +158,8 @@ def _update_whitewater_foam_material(self, context): def _update_whitewater_bubble_material(self, context): if not self._is_domain_set(): return + if not context.scene.flip_fluid.is_domain_in_active_scene(): + return dprops = self._get_domain_properties() bubble_object = dprops.mesh_cache.bubble.get_cache_object() self._update_cache_object_material( @@ -168,6 +174,8 @@ def _update_whitewater_bubble_material(self, context): def _update_whitewater_spray_material(self, context): if not self._is_domain_set(): return + if not context.scene.flip_fluid.is_domain_in_active_scene(): + return dprops = self._get_domain_properties() spray_object = dprops.mesh_cache.spray.get_cache_object() self._update_cache_object_material( @@ -182,6 +190,8 @@ def _update_whitewater_spray_material(self, context): def _update_whitewater_dust_material(self, context): if not self._is_domain_set(): return + if not context.scene.flip_fluid.is_domain_in_active_scene(): + return dprops = self._get_domain_properties() dust_object = dprops.mesh_cache.dust.get_cache_object() self._update_cache_object_material( diff --git a/src/addon/properties/domain_properties.py b/src/addon/properties/domain_properties.py index f68b0222..98aeff7a 100644 --- a/src/addon/properties/domain_properties.py +++ b/src/addon/properties/domain_properties.py @@ -307,6 +307,8 @@ def scene_update_post(scene): dprops = scene.flip_fluid.get_domain_properties() if dprops is None: return + if not scene.flip_fluid.is_domain_in_active_scene(): + return dprops.scene_update_post(scene) diff --git a/src/addon/properties/domain_stats_properties.py b/src/addon/properties/domain_stats_properties.py index 775251fe..71351df0 100644 --- a/src/addon/properties/domain_stats_properties.py +++ b/src/addon/properties/domain_stats_properties.py @@ -173,6 +173,8 @@ class DomainStatsProperties(bpy.types.PropertyGroup): cache_info_viscosity_solver_stats_expanded = BoolProperty(default=True); exec(conv("cache_info_viscosity_solver_stats_expanded")) is_cache_info_available = BoolProperty(default=False); exec(conv("is_cache_info_available")) num_cache_frames = IntProperty(default=-1); exec(conv("num_cache_frames")) + is_average_performance_score_enabled = BoolProperty(default=False); exec(conv("is_average_performance_score_enabled")) + average_performance_score = IntProperty(default=-1); exec(conv("average_performance_score")) estimated_frame_speed = FloatProperty(default=-1); exec(conv("estimated_frame_speed")) estimated_time_remaining = IntProperty(default=-1); exec(conv("estimated_time_remaining")) estimated_time_remaining_timestamp = IntProperty(default=-1); exec(conv("estimated_time_remaining_timestamp")) @@ -215,6 +217,7 @@ class DomainStatsProperties(bpy.types.PropertyGroup): frame_delta_time = FloatProperty(default=0.0); exec(conv("frame_delta_time")) frame_fluid_particles = IntProperty(default=-1); exec(conv("frame_fluid_particles")) frame_diffuse_particles = IntProperty(default=-1); exec(conv("frame_diffuse_particles")) + frame_performance_score = IntProperty(default=-1); exec(conv("frame_performance_score")) frame_pressure_solver_enabled = BoolProperty(default=False); exec(conv("frame_pressure_solver_enabled")) frame_pressure_solver_success = BoolProperty(default=True); exec(conv("frame_pressure_solver_success")) @@ -305,6 +308,7 @@ def reset_stats_values(self): "frame_delta_time", "frame_fluid_particles", "frame_diffuse_particles", + "frame_performance_score", "frame_pressure_solver_enabled", "frame_pressure_solver_success", "frame_pressure_solver_error", @@ -462,6 +466,9 @@ def _update_frame_stats(self): self.frame_fluid_particles = data['fluid_particles'] self.frame_diffuse_particles = data['diffuse_particles'] + if 'performance_score' in data: + self.frame_performance_score = data['performance_score'] + if 'pressure_solver_enabled' in data: self.frame_pressure_solver_enabled = bool(data['pressure_solver_enabled']) self.frame_pressure_solver_success = bool(data['pressure_solver_success']) @@ -781,6 +788,8 @@ def _update_cache_stats(self): time_other = 0.0 is_data_in_cache = False num_cache_frames = 0 + average_performance_score = 0 + is_average_performance_score_enabled = False for key in cachedata.keys(): if not key.isdigit(): continue @@ -789,6 +798,10 @@ def _update_cache_stats(self): num_cache_frames += 1 fdata = cachedata[key] + if 'performance_score' in fdata: + is_average_performance_score_enabled = True + average_performance_score += fdata['performance_score'] + if fdata['surface']['enabled']: is_surface_enabled = True surface_bytes += fdata['surface']['bytes'] @@ -899,7 +912,15 @@ def _update_cache_stats(self): self.is_cache_info_available = False return + if num_cache_frames > 0 and is_average_performance_score_enabled: + average_performance_score /= num_cache_frames + else: + is_average_performance_score_enabled = False + average_performance_score = 0 + self.num_cache_frames = num_cache_frames + self.is_average_performance_score_enabled = is_average_performance_score_enabled + self.average_performance_score = int(average_performance_score) self.surface_mesh.enabled = is_surface_enabled self.preview_mesh.enabled = is_preview_enabled diff --git a/src/addon/properties/flip_fluid_properties.py b/src/addon/properties/flip_fluid_properties.py index aa3e9d35..877f9895 100644 --- a/src/addon/properties/flip_fluid_properties.py +++ b/src/addon/properties/flip_fluid_properties.py @@ -69,7 +69,7 @@ def is_domain_object_set(self): def get_num_domain_objects(self): n = 0 - for obj in vcu.get_all_scene_objects(): + for obj in bpy.data.objects: if obj.flip_fluid.is_domain(): n += 1 return n @@ -100,6 +100,16 @@ def get_domain_properties(self): return domain_object.flip_fluid.domain + def is_domain_in_active_scene(self): + domain_object = self.get_domain_object() + if domain_object is None: + return False + for obj in bpy.context.scene.collection.all_objects: + if obj == domain_object: + return True + return False + + def get_num_fluid_objects(self): n = 0 for obj in vcu.get_all_scene_objects(): diff --git a/src/addon/properties/helper_properties.py b/src/addon/properties/helper_properties.py index b2e6d249..3279cd37 100644 --- a/src/addon/properties/helper_properties.py +++ b/src/addon/properties/helper_properties.py @@ -1,5 +1,5 @@ # Blender FLIP Fluids Add-on -# Copyright (C) 2023 Ryan L. Guy +# Copyright (C) 2022 Ryan L. Guy # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -83,16 +83,31 @@ class FlipFluidHelperProperties(bpy.types.PropertyGroup): default=True, ); exec(conv("unsaved_blend_file_tooltip")) - turbo_tools_render_tooltip = BoolProperty( - name="Turbo Tools command line rendering support", - description="An installation of the Turbo Tools addon has been detected. Use these operators to launch" - " a Turbo Tools render process or copy the render command. Refer to the Turbo Tools documentation for more info" - " on command line rendering", + flip_fluids_remesh_skip_hide_render_objects = BoolProperty( + name="Skip Hidden Render Objects", + description="Skip remeshing objects in the collection that are hidden from render (outliner camera icon)", + default=False, + ); exec(conv("flip_fluids_remesh_skip_hide_render_objects")) + flip_fluids_remesh_apply_object_modifiers = BoolProperty( + name="Apply Object Modifiers", + description="Automatically apply modifiers to objects in collection. If disabled, objects with modifiers will" + " need to have modifiers applied manually or excluded from the viewport (disable outliner monitor icon)" + " before proceeding with the remesh process. Modifiers may not be applied in the intended order and objects" + " with complex modifier dependencies may need to be applied manually for accuracy", + default=True, + ); exec(conv("flip_fluids_remesh_apply_object_modifiers")) + flip_fluids_remesh_convert_objects_to_mesh = BoolProperty( + name="Convert Objects to Mesh", + description="Automatically convert non-mesh type objects in the collection to a mesh type if applicable. If an object cannot" + " be converted to a mesh (empties, armatures, etc), the object will be skipped from the remeshing process." + " If disabled, non-mesh type objects will need to be manually converted to a mesh or excluded from the viewport" + " (disable outliner monitor icon) before proceeding with the remesh process", default=True, - ); exec(conv("turbo_tools_render_tooltip")) + ); exec(conv("flip_fluids_remesh_convert_objects_to_mesh")) is_auto_frame_load_cmd_operator_running = BoolProperty(default=False); exec(conv("is_auto_frame_load_cmd_operator_running")) + prepare_geometry_tools_expanded = BoolProperty(default=False); exec(conv("prepare_geometry_tools_expanded")) bake_simulation_expanded = BoolProperty(default=True); exec(conv("bake_simulation_expanded")) add_remove_objects_expanded = BoolProperty(default=True); exec(conv("add_remove_objects_expanded")) outliner_organization_expanded = BoolProperty(default=False); exec(conv("outliner_organization_expanded")) @@ -159,4 +174,4 @@ def register(): def unregister(): - bpy.utils.unregister_class(FlipFluidHelperProperties) \ No newline at end of file + bpy.utils.unregister_class(FlipFluidHelperProperties) diff --git a/src/addon/properties/object_properties.py b/src/addon/properties/object_properties.py index 649f0dca..49d8496e 100644 --- a/src/addon/properties/object_properties.py +++ b/src/addon/properties/object_properties.py @@ -254,10 +254,11 @@ def _set_object_type(self, value): self._toggle_cycles_ray_visibility(active_object, True) if oldtype != 'TYPE_DOMAIN' and newtype == 'TYPE_DOMAIN': - active_object.flip_fluid.domain.initialize() - active_object.lock_rotation = (True, True, True) - self._toggle_cycles_ray_visibility(active_object, False) - self._lock_interface() + if bpy.context.scene.flip_fluid.get_num_domain_objects() <= 1: + active_object.flip_fluid.domain.initialize() + active_object.lock_rotation = (True, True, True) + self._toggle_cycles_ray_visibility(active_object, False) + self._lock_interface() if oldtype == 'TYPE_DOMAIN' and newtype != 'TYPE_DOMAIN': active_object.lock_rotation = (False, False, False) active_object.flip_fluid.domain.destroy() diff --git a/src/addon/render.py b/src/addon/render.py index 9f8e9ec4..34a2309d 100644 --- a/src/addon/render.py +++ b/src/addon/render.py @@ -48,6 +48,16 @@ def __is_domain_set(): return bpy.context.scene.flip_fluid.get_domain_object() is not None +def __is_domain_in_scene(): + bl_domain = bpy.context.scene.flip_fluid.get_domain_object() + if bl_domain is None: + return False + for obj in bpy.context.scene.collection.all_objects: + if obj == bl_domain: + return True + return False + + def get_current_simulation_frame(): dprops = bpy.context.scene.flip_fluid.get_domain_properties() if dprops is None: @@ -664,6 +674,11 @@ def __load_frame(frameno, force_reload=False, depsgraph=None): if not __is_domain_set(): return + if not __is_domain_in_scene(): + # Domain shouldn't be operated on if it is not contained in the + # active scene + return + dprops = __get_domain_properties() dprops.mesh_cache.initialize_cache_objects() @@ -677,12 +692,16 @@ def __load_frame(frameno, force_reload=False, depsgraph=None): def reload_frame(frameno): if not __is_domain_set(): return + if not __is_domain_in_scene(): + return __load_frame(frameno, True) def render_init(scene): if not __is_domain_set(): return + if not __is_domain_in_scene(): + return global IS_RENDERING IS_RENDERING = True @@ -700,34 +719,30 @@ def render_init(scene): def render_complete(scene): if not __is_domain_set(): return + if not __is_domain_in_scene(): + return global IS_RENDERING global IS_FRAME_REQUIRING_RELOAD IS_RENDERING = False IS_FRAME_REQUIRING_RELOAD = True - # Testing potential fix for whitewater not rendering on first frame (v1.0.8) - # For part of this fix, we no longer want to unload the duplivert object - # after render completes - """ - dprops = __get_domain_properties() - if dprops.whitewater.enable_whitewater_simulation: - if not __get_whitewater_particle_object_display_bool('FOAM'): - dprops.mesh_cache.foam.unload_duplivert_object() - if not __get_whitewater_particle_object_display_bool('BUBBLE'): - dprops.mesh_cache.bubble.unload_duplivert_object() - if not __get_whitewater_particle_object_display_bool('SPRAY'): - dprops.mesh_cache.spray.unload_duplivert_object() - if not __get_whitewater_particle_object_display_bool('DUST'): - dprops.mesh_cache.dust.unload_duplivert_object() - """ - def render_cancel(scene): + if not __is_domain_set(): + return + if not __is_domain_in_scene(): + return + render_complete(scene) def render_pre(scene): + if not __is_domain_set(): + return + if not __is_domain_in_scene(): + return + global RENDER_PRE_FRAME_NUMBER RENDER_PRE_FRAME_NUMBER = __get_render_pre_current_frame() @@ -749,6 +764,8 @@ def render_pre(scene): def frame_change_post(scene, depsgraph=None): if not __is_domain_set(): return + if not __is_domain_in_scene(): + return if is_rendering() and vcu.is_blender_28(): if not scene.render.use_lock_interface: @@ -766,6 +783,8 @@ def frame_change_post(scene, depsgraph=None): def scene_update_post(scene): if not __is_domain_set(): return + if not __is_domain_in_scene(): + return global IS_FRAME_REQUIRING_RELOAD if IS_FRAME_REQUIRING_RELOAD: diff --git a/src/addon/resources/command_line_scripts/run_simulation_and_render.py b/src/addon/resources/command_line_scripts/run_simulation_and_render.py index 103f4fa3..0f367678 100644 --- a/src/addon/resources/command_line_scripts/run_simulation_and_render.py +++ b/src/addon/resources/command_line_scripts/run_simulation_and_render.py @@ -33,6 +33,7 @@ def play_sound(json_audio_filepath, block=False): bpy.ops.flip_fluid_operators.bake_fluid_simulation_cmd() +bpy.ops.wm.revert_mainfile() bpy.ops.render.render(animation=True) resources_directory = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) diff --git a/src/addon/types.py b/src/addon/types.py index 0e19aba6..c9f82883 100644 --- a/src/addon/types.py +++ b/src/addon/types.py @@ -194,8 +194,8 @@ def object_types(self, context): ) threading_modes = ( - ('THREADING_MODE_AUTO_DETECT', "Auto-detect", "Automatically determine the number of threads, based on CPUs"), - ('THREADING_MODE_FIXED', "Fixed", "Manually determine the number of threads") + ('THREADING_MODE_AUTO_DETECT', "Auto-detect", "Use the maximum number of threads available on the CPU for the simulation. Tip: Running smaller low resolution simulations with too many threads may actually harm performance due to overhead of thread management - this mode may be more performant for running medium to high resolution simulations."), + ('THREADING_MODE_FIXED', "Fixed", "Use a specified fixed number of threads for the simulation. TIP: Running smaller low resolution simulations with less threads may boost baking speed due to reducing overhead from thread management - try values around 4 threads for default lower resolution simulations.") ) diff --git a/src/addon/ui/cache_object_ui.py b/src/addon/ui/cache_object_ui.py index bc042124..530d72b7 100644 --- a/src/addon/ui/cache_object_ui.py +++ b/src/addon/ui/cache_object_ui.py @@ -32,6 +32,8 @@ def poll(cls, context): dprops = context.scene.flip_fluid.get_domain_properties() if dprops is None: return False + if not context.scene.flip_fluid.is_domain_in_active_scene(): + return False obj = vcu.get_active_object(context) return dprops.mesh_cache.is_cache_object(obj) diff --git a/src/addon/ui/domain_cache_ui.py b/src/addon/ui/domain_cache_ui.py index 9f2f9e6e..571413ab 100644 --- a/src/addon/ui/domain_cache_ui.py +++ b/src/addon/ui/domain_cache_ui.py @@ -77,8 +77,8 @@ def draw(self, context): subcolumn.enabled = not dprops.bake.is_simulation_running row = subcolumn.row(align=True) row.prop(cprops, "cache_directory") - row.operator("flip_fluid_operators.increment_decrease_cache_directory", text="", icon="REMOVE").increment_mode = "DECREASE" - row.operator("flip_fluid_operators.increment_decrease_cache_directory", text="", icon="ADD").increment_mode = "INCREASE" + row.operator("flip_fluid_operators.increase_decrease_cache_directory", text="", icon="REMOVE").increment_mode = "DECREASE" + row.operator("flip_fluid_operators.increase_decrease_cache_directory", text="", icon="ADD").increment_mode = "INCREASE" row = column.row(align=True) row.operator("flip_fluid_operators.relative_cache_directory") diff --git a/src/addon/ui/domain_simulation_ui.py b/src/addon/ui/domain_simulation_ui.py index bce097e8..b50630f8 100644 --- a/src/addon/ui/domain_simulation_ui.py +++ b/src/addon/ui/domain_simulation_ui.py @@ -207,6 +207,7 @@ def draw_more_bake_settings(self, context, box): flip_props.get_force_field_objects()) flip_objects.sort(key=lambda x: x.name) + is_all_filter_selected = sprops.mesh_reexport_type_filter == 'MOTION_FILTER_TYPE_ALL' if sprops.mesh_reexport_type_filter == 'MOTION_FILTER_TYPE_ALL': filtered_objects = flip_objects motion_type_string = "simulation" @@ -225,6 +226,9 @@ def draw_more_bake_settings(self, context, box): else: split = column.split() column_left = split.column(align=True) + if is_all_filter_selected: + column_animated = split.column(align=True) + column_middle = split.column(align=True) column_right = split.column(align=True) @@ -233,6 +237,16 @@ def draw_more_bake_settings(self, context, box): op_box = column_left.box() op_box.label(text="") + if is_all_filter_selected: + column_animated.label(text="") + column_animated.label(text="Export Animated") + op_box = column_animated.box() + row = op_box.row(align=True) + row.alignment = 'LEFT' + row.operator("flip_fluid_operators.helper_batch_export_animated_mesh", icon='CHECKBOX_HLT', text="").enable_state = True + row.operator("flip_fluid_operators.helper_batch_export_animated_mesh", icon='CHECKBOX_DEHLT', text="").enable_state = False + row.label(text="All") + column_middle.label(text="") column_middle.label(text="Skip Re-Export") op_box = column_middle.box() @@ -254,6 +268,8 @@ def draw_more_bake_settings(self, context, box): for ob in filtered_objects: pgroup = ob.flip_fluid.get_property_group() column_left.label(text=ob.name, icon="OBJECT_DATA") + if is_all_filter_selected: + column_animated.prop(pgroup, "export_animated_mesh", text="animated", toggle=True) column_middle.prop(pgroup, "skip_reexport", text="skip", toggle=True) column_right.prop(pgroup, "force_reexport_on_next_bake", text="force", toggle=True) diff --git a/src/addon/ui/domain_stats_ui.py b/src/addon/ui/domain_stats_ui.py index d83eadfa..e4f679d3 100644 --- a/src/addon/ui/domain_stats_ui.py +++ b/src/addon/ui/domain_stats_ui.py @@ -93,6 +93,8 @@ def draw_frame_info_simulation_stats(self, context, box): column.label(text=" Bubble:") column.label(text=" Spray:") column.label(text=" Dust:") + if sprops.frame_performance_score != -1: + column.label(text="Performance Score:") column = split.column() column.label(text=str(sprops.frame_info_id)) @@ -107,6 +109,9 @@ def draw_frame_info_simulation_stats(self, context, box): column.label(text=self.format_number(sprops.spray_mesh.verts).lstrip()) column.label(text=self.format_number(sprops.dust_mesh.verts).lstrip()) + if sprops.frame_performance_score != -1: + column.label(text=str(sprops.frame_performance_score)) + def draw_frame_info_solver_stats(self, context, box): sprops = vcu.get_active_object(context).flip_fluid.domain.stats @@ -570,6 +575,9 @@ def draw_cache_info_simulation_stats(self, context, box): column.label(text="Start Frame:") column.label(text="End Frame:") + if sprops.is_average_performance_score_enabled: + column.label(text="Average Performance Score:") + column = split.column() if num_baked_frames > num_frames: column.label(text=str(num_baked_frames)) @@ -580,6 +588,9 @@ def draw_cache_info_simulation_stats(self, context, box): #column.label(text=str(simprops.frame_end)) column.label(text=str(simprops.frame_start + num_baked_frames - 1)) + if sprops.is_average_performance_score_enabled: + column.label(text=str(sprops.average_performance_score)) + if dprops.bake.is_simulation_running: column = subbox.column() split = column.split() diff --git a/src/addon/ui/helper_ui.py b/src/addon/ui/helper_ui.py index aa273ea0..9b966a4e 100644 --- a/src/addon/ui/helper_ui.py +++ b/src/addon/ui/helper_ui.py @@ -1,5 +1,5 @@ # Blender FLIP Fluids Add-on -# Copyright (C) 2023 Ryan L. Guy +# Copyright (C) 2022 Ryan L. Guy # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,7 +22,6 @@ from ..utils import version_compatibility_utils as vcu from ..utils import installation_utils - class FLIPFLUID_PT_HelperPanelMain(bpy.types.Panel): bl_label = "Simulation Setup" bl_category = "FLIP Fluids" @@ -58,7 +57,7 @@ def draw(self, context): if is_persistent_data_enabled and not preferences.dismiss_persistent_data_render_warning: box = self.layout.box() api_utils.draw_persistent_data_warning(box, preferences) - + # # Bake Simulation # @@ -96,12 +95,25 @@ def draw(self, context): column.prop(dprops.simulation, "resolution") column.separator() + split = column.split(align=True) + column_left = split.column(align=True) + column_left.label(text="Multithreading:") + row = column_left.row(align=True) + row.prop(dprops.advanced, "threading_mode", expand=True) + row = column_left.row(align=True) + if dprops.advanced.threading_mode == 'THREADING_MODE_AUTO_DETECT': + row.enabled = False + row.prop(dprops.advanced, "num_threads_auto_detect") + elif dprops.advanced.threading_mode == 'THREADING_MODE_FIXED': + row.prop(dprops.advanced, "num_threads_fixed") + column.separator() + column.label(text="Cache Directory:") subcolumn = column.column(align=True) row = subcolumn.row(align=True) row.prop(dprops.cache, "cache_directory") - row.operator("flip_fluid_operators.increment_decrease_cache_directory", text="", icon="REMOVE").increment_mode = "DECREASE" - row.operator("flip_fluid_operators.increment_decrease_cache_directory", text="", icon="ADD").increment_mode = "INCREASE" + row.operator("flip_fluid_operators.increase_decrease_cache_directory", text="", icon="REMOVE").increment_mode = "DECREASE" + row.operator("flip_fluid_operators.increase_decrease_cache_directory", text="", icon="ADD").increment_mode = "INCREASE" row = column.row(align=True) row.operator("flip_fluid_operators.relative_cache_directory") row.operator("flip_fluid_operators.absolute_cache_directory") @@ -110,15 +122,92 @@ def draw(self, context): column = box.column(align=True) column.label(text="Render Output:") - column.prop(context.scene.render, "filepath", text="") + row = column.row(align=True) + row.prop(context.scene.render, "filepath", text="") + row.operator("flip_fluid_operators.increase_decrease_render_directory", text="", icon="REMOVE").increment_mode = "DECREASE" + row.operator("flip_fluid_operators.increase_decrease_render_directory", text="", icon="ADD").increment_mode = "INCREASE" row = column.row(align=True) row.operator("flip_fluid_operators.relative_to_blend_render_output") row.operator("flip_fluid_operators.prefix_to_filename_render_output") + column = box.column(align=True) + column.label(text="Cache and Render Output:") + row = column.row(align=True) + row.operator("flip_fluid_operators.increase_decrease_cache_render_version", text="Decrease Version", icon="REMOVE").increment_mode = "DECREASE" + row.operator("flip_fluid_operators.increase_decrease_cache_render_version", text="Increase Version", icon="ADD").increment_mode = "INCREASE" + else: box.label(text="Please create a domain object") box.operator("flip_fluid_operators.helper_create_domain") + # + # New Prepare Geometry Tools: + # + + box = self.layout.box() + row = box.row(align=True) + row.prop(hprops, "prepare_geometry_tools_expanded", + icon="TRIA_DOWN" if hprops.prepare_geometry_tools_expanded else "TRIA_RIGHT", + icon_only=True, + emboss=False + ) + row.label(text="Prepare Geometry Tools:") + + if hprops.prepare_geometry_tools_expanded: + column = box.column(align=True) + + if not vcu.is_blender_31(): + column.label(text="Blender 3.1 or later required") + + prefs = vcu.get_addon_preferences() + is_developer_mode = prefs.is_developer_tools_enabled() + if is_developer_mode: + active_collection = context.view_layer.active_layer_collection.collection + is_active_collection_selected = False + active_collection_name = "No Collection Selected" + if active_collection is not None: + is_active_collection_selected = True + active_collection_name = active_collection.name + + options_box = column.box() + options_box.label(text="Remesh Options:", icon='TOOL_SETTINGS') + options_column = options_box.column(align=True) + options_column.prop(hprops, "flip_fluids_remesh_convert_objects_to_mesh") + options_column.prop(hprops, "flip_fluids_remesh_apply_object_modifiers") + options_column.prop(hprops, "flip_fluids_remesh_skip_hide_render_objects") + options_column.label(text="Active collection will be remeshed:") + options_column.label(text=active_collection_name, icon="OUTLINER_COLLECTION") + + column = box.column(align=True) + column.enabled = vcu.is_blender_31() and is_developer_mode + column.alert = column.enabled and not is_active_collection_selected + + row = column.row(align=True) + op = row.operator("flip_fluid_operators.helper_remesh", icon="MOD_REMESH") + op.skip_hide_render_objects = hprops.flip_fluids_remesh_skip_hide_render_objects + op.apply_object_modifiers = hprops.flip_fluids_remesh_apply_object_modifiers + op.convert_objects_to_mesh = hprops.flip_fluids_remesh_convert_objects_to_mesh + + row.operator( + "wm.url_open", + text="", + icon="URL" + ).url = "https://github.com/rlguy/Blender-FLIP-Fluids/wiki/Helper-Menu-Settings#prepare-geometry-tools" + else: + warn_box = box.box() + warn_column = warn_box.column(align=True) + warn_column.enabled = True + warn_column.label(text=" Experimental Developer Tools must be") + warn_column.label(text=" enabled in preferences to use this feature") + warn_column.separator() + warn_column.prop(prefs, "enable_developer_tools", text="Enable Developer Tools in Preferences") + warn_column.separator() + warn_column.operator( + "wm.url_open", + text="Important Info and Limitations", + icon="WORLD" + ).url = "https://github.com/rlguy/Blender-FLIP-Fluids/wiki/Preferences-Menu-Settings#developer-tools" + # # Add Objects # @@ -255,7 +344,7 @@ def draw(self, context): ) row.label(text="Organize Outliner:") if not hprops.outliner_organization_expanded: - row.operator("flip_fluid_operators.helper_organize_outliner", text="Organize") + row.operator("flip_fluid_operators.helper_organize_outliner", text="Organize", icon="GROUP") if hprops.outliner_organization_expanded: column = box.column(align=True) @@ -304,8 +393,8 @@ def draw(self, context): subbox.label(text="Render Animation:") column = subbox.column(align=True) row = column.row(align=True) - row.operator("flip_fluid_operators.helper_command_line_render").use_turbo_tools = False - row.operator("flip_fluid_operators.helper_command_line_render_to_clipboard", text="", icon='COPYDOWN').use_turbo_tools = False + row.operator("flip_fluid_operators.helper_command_line_render") + row.operator("flip_fluid_operators.helper_command_line_render_to_clipboard", text="", icon='COPYDOWN') system = platform.system() if system == "Windows": @@ -319,8 +408,8 @@ def draw(self, context): subbox.label(text="Render Frame:") column = subbox.column(align=True) row = column.row(align=True) - row.operator("flip_fluid_operators.helper_command_line_render_frame").use_turbo_tools = False - row.operator("flip_fluid_operators.helper_cmd_render_frame_to_clipboard", text="", icon='COPYDOWN').use_turbo_tools = False + row.operator("flip_fluid_operators.helper_command_line_render_frame") + row.operator("flip_fluid_operators.helper_cmd_render_frame_to_clipboard", text="", icon='COPYDOWN') row = column.row(align=True) row.prop(hprops, "cmd_open_image_after_render") @@ -328,29 +417,6 @@ def draw(self, context): row = column.row(align=True) row.prop(hprops, "cmd_close_window_after_render") - - if installation_utils.is_turbo_tools_addon_enabled(): - subbox = box.box() - subbox.label(text="Turbo Tools Command Line Render:") - row = subbox.row(align=True) - row.alignment = 'LEFT' - row.prop(hprops, "turbo_tools_render_tooltip", icon="QUESTION", emboss=False, text="") - row.label(text="Turbo Tools Addon Detected") - - column = subbox.column(align=True) - row = column.row(align=True) - row.operator("flip_fluid_operators.helper_command_line_render", text="Render Animation").use_turbo_tools = True - row.operator("flip_fluid_operators.helper_command_line_render_to_clipboard", text="", icon='COPYDOWN').use_turbo_tools = True - row = column.row(align=True) - row.operator("flip_fluid_operators.helper_command_line_render_frame", text="Render Frame").use_turbo_tools = True - row.operator("flip_fluid_operators.helper_cmd_render_frame_to_clipboard", text="", icon='COPYDOWN').use_turbo_tools = True - row = column.row(align=True) - row.prop(hprops, "cmd_open_image_after_render") - - if system == "Windows": - row = column.row(align=True) - row.prop(hprops, "cmd_close_window_after_render") - # # Geometry Node Tools # @@ -570,7 +636,6 @@ def draw(self, context): column.separator() column.operator("flip_fluid_operators.reload_frame", text="Reload Frame") - class FLIPFLUID_PT_HelperTechnicalSupport(bpy.types.Panel): bl_label = "Technical Support Tools" bl_category = "FLIP Fluids" @@ -608,6 +673,7 @@ def draw(self, context): def register(): # These panels will be registered in properties.preferences_properties.py + # Small fix: Itīs properties.helper_properties.py pass diff --git a/src/engine/diffuseparticlesimulation.cpp b/src/engine/diffuseparticlesimulation.cpp index f95bb214..8b0afa21 100644 --- a/src/engine/diffuseparticlesimulation.cpp +++ b/src/engine/diffuseparticlesimulation.cpp @@ -1655,21 +1655,15 @@ void DiffuseParticleSimulation:: std::vector velocities(surface.size()); _trilinearInterpolate(surface, _vfield, velocities); - int count1 = 0; - int count2 = 0; - vmath::vec3 hdx(0.5*_dx, 0.5*_dx, 0.5*_dx); double eps = 1e-6; for (size_t i = 0; i < surface.size(); i++) { vmath::vec3 p = surface[i]; vmath::vec3 v = velocities[i]; - count1++; - double dist = Interpolation::trilinearInterpolate(p - hdx, _dx, *_surfaceSDF); if (dist > -0.75 * _dx) { v *= _randomDouble(1.0f, _sprayEmissionSpeedFactor); - count2++; } double Ie = _getEnergyPotential(v); @@ -1989,13 +1983,10 @@ void DiffuseParticleSimulation::_computeNewDiffuseParticleVelocities(std::vector _trilinearInterpolate(data, _vfield, data); - int count = 0; - for (size_t i = 0; i < particles.size(); i++) { vmath::vec3 v = data[i]; if (particles[i].type == DiffuseParticleType::spray) { v *= _randomDouble(1.0, _sprayEmissionSpeedFactor); - count++; } particles[i].velocity = v; diff --git a/src/engine/fluidsimulation.cpp b/src/engine/fluidsimulation.cpp index 82be7a04..59116ee8 100644 --- a/src/engine/fluidsimulation.cpp +++ b/src/engine/fluidsimulation.cpp @@ -11303,16 +11303,22 @@ float FluidSimulation::_getMarkerParticleSpeedLimit(double dt) { maxspeed = std::max(i + _minTimeStepIncreaseForRemoval, _maxFrameTimeSteps) * speedLimitStep; } + double extremeSpeedOutlierThresholdLower = _extremeParticleVelocityThresholdLower * maxParticleSpeed; double extremeSpeedOutlierThreshold = _extremeParticleVelocityThreshold * maxParticleSpeed; + int extremeParticleVelocityOutlierCountLower = 0; int extremeParticleVelocityOutlierCount = 0; for (unsigned int i = 0; i < velocities->size(); i++) { double speed = (double)velocities->at(i).length(); + if (speed >= extremeSpeedOutlierThresholdLower && speed < extremeSpeedOutlierThreshold) { + extremeParticleVelocityOutlierCountLower++; + } if (speed >= extremeSpeedOutlierThreshold) { extremeParticleVelocityOutlierCount++; } } - if (extremeParticleVelocityOutlierCount <= _maxExtremeVelocityOutlierRemovalAbsolute) { + if (extremeParticleVelocityOutlierCount <= _maxExtremeVelocityOutlierRemovalAbsolute && + extremeParticleVelocityOutlierCountLower <= _maxExtremeVelocityOutlierRemovalAbsolute) { maxspeed = std::min(extremeSpeedOutlierThreshold, maxspeed); } @@ -11342,6 +11348,7 @@ void FluidSimulation::_removeMarkerParticles(double dt) { float maxspeed = _getMarkerParticleSpeedLimit(dt); double maxspeedsq = maxspeed * maxspeed; + int numExtremeVelocityParticlesRemoved = 0; std::vector *positions, *velocities; _markerParticles.getAttributeValues("POSITION", positions); @@ -11375,11 +11382,13 @@ void FluidSimulation::_removeMarkerParticles(double dt) { if (_isExtremeVelocityRemovalEnabled && vmath::dot(velocity, velocity) > maxspeedsq) { isRemoved[i] = true; + numExtremeVelocityParticlesRemoved++; continue; } } _markerParticles.removeParticles(isRemoved); + _currentExtremeVelocityParticlesRemoved = numExtremeVelocityParticlesRemoved; } void FluidSimulation::_advanceMarkerParticles(double dt) { @@ -13359,6 +13368,8 @@ void FluidSimulation::_logFrameInfo() { _logfile.logString(pstring); } + _logfile.newline(); + _logfile.log("Performance Score: ", _currentPerformanceScore, 0); _logfile.newline(); _logfile.log("Frame Time: ", tdata.frameTime, 3); _logfile.log("Total Time: ", _totalSimulationTime, 3); @@ -13372,7 +13383,10 @@ void FluidSimulation::_logStepInfo() { std::stringstream ss; ss << "Fluid Particles: " << _markerParticles.size() << std::endl << - "Fluid Cells: " << _getNumFluidCells(); + "Fluid Cells: " << _currentNumFluidCells; + if (_currentExtremeVelocityParticlesRemoved > 0) { + ss << std::endl << "Extreme Velocity Fluid Particles Removed: " << _currentExtremeVelocityParticlesRemoved; + } _logfile.logString(ss.str()); if (_isDiffuseMaterialOutputEnabled) { @@ -13449,6 +13463,9 @@ void FluidSimulation::update(double dt) { _viscositySolverIterations = 0; _viscositySolverError = 0.0f; + int totalFluidParticlesProcessed = 0; + float totalFluidParticlesProcessedTime = 0.0f; + double eps = 1e-9; do { StopWatch stepTimer; @@ -13473,8 +13490,6 @@ void FluidSimulation::update(double dt) { double frameProgress = 100 * (1.0 - _currentFrameDeltaTimeRemaining/dt); int numFrames = _timelineFrameEnd - _timelineFrameStart + 1; std::ostringstream ss; - // ss << "Frame: " << _currentFrame << " (Step " << _currentFrameTimeStepNumber + 1 << ")\n" << - // "Step time: " << _currentFrameTimeStep << " (" << frameProgress << "% of frame)\n"; ss << "Simulation Frame: " << _currentFrame + 1 << " / " << numFrames << "\n" " Timeline Frame: " << _currentFrame + _timelineFrameStart << " / " << _timelineFrameEnd << "\n" @@ -13488,12 +13503,17 @@ void FluidSimulation::update(double dt) { _logfile.newline(); _stepFluid(_currentFrameTimeStep); + _currentNumFluidCells = _getNumFluidCells(); + _logStepInfo(); stepTimer.stop(); _logfile.log("Step Update Time: ", stepTimer.getTime(), 3); _logfile.newline(); + totalFluidParticlesProcessed += _markerParticles.size(); + totalFluidParticlesProcessedTime += (float)stepTimer.getTime(); + _currentFrameTimeStepNumber++; } while (_currentFrameDeltaTimeRemaining > eps); @@ -13501,6 +13521,8 @@ void FluidSimulation::update(double dt) { _timingData.frameTime = frameTimer.getTime(); _totalSimulationTime += frameTimer.getTime(); + _currentPerformanceScore = (int)((float)totalFluidParticlesProcessed / totalFluidParticlesProcessedTime) / (1000.0f); + _updateTimingData(); _logFrameInfo(); @@ -13510,6 +13532,7 @@ void FluidSimulation::update(double dt) { _outputData.frameData.timing.total = frameTimer.getTime(); _outputData.frameData.fluidParticles = (int)_markerParticles.size(); _outputData.frameData.diffuseParticles = (int)(_diffuseMaterial.getDiffuseParticles()->size()); + _outputData.frameData.performanceScore = _currentPerformanceScore; _outputData.frameData.pressureSolverEnabled = 1; _outputData.frameData.pressureSolverSuccess = (int)_pressureSolverSuccess; diff --git a/src/engine/fluidsimulation.h b/src/engine/fluidsimulation.h index 612bf887..133a82b2 100644 --- a/src/engine/fluidsimulation.h +++ b/src/engine/fluidsimulation.h @@ -85,6 +85,7 @@ struct FluidSimulationFrameStats { double deltaTime = 0.0; int fluidParticles = 0; int diffuseParticles = 0; + int performanceScore = 0; int pressureSolverEnabled = 1; int pressureSolverSuccess = 0; @@ -1869,6 +1870,9 @@ class FluidSimulation double _currentFrameTimeStep = 0.0; double _currentFrameDeltaTime = 0.0; double _currentFrameDeltaTimeRemaining = 0.0; + int _currentNumFluidCells = 0; + int _currentPerformanceScore = 0; + int _currentExtremeVelocityParticlesRemoved = 0; bool _isLastFrameTimeStep = false; bool _isZeroLengthDeltaTime = false; bool _isSkippedFrame = false; @@ -2075,7 +2079,8 @@ class FluidSimulation bool _isAdaptiveObstacleTimeSteppingEnabled = false; bool _isAdaptiveForceFieldTimeSteppingEnabled = false; bool _isExtremeVelocityRemovalEnabled = true; - double _extremeParticleVelocityThreshold = 0.999; + double _extremeParticleVelocityThreshold = 0.99999; + double _extremeParticleVelocityThresholdLower = 0.90; double _maxExtremeVelocityRemovalPercent = 0.0005; int _maxExtremeVelocityRemovalAbsolute = 35; int _maxExtremeVelocityOutlierRemovalAbsolute = 6; diff --git a/src/engine/forcefield.h b/src/engine/forcefield.h index ca01aeec..a7dafeed 100644 --- a/src/engine/forcefield.h +++ b/src/engine/forcefield.h @@ -132,6 +132,7 @@ class ForceField float _gravityScale = 1.0f; float _gravityScaleWidth = 0.0f; + float _gravityScaleFalloffThreshold = 0.90f; // in % of the width }; diff --git a/src/engine/forcefieldcurve.cpp b/src/engine/forcefieldcurve.cpp index 0f080305..7c92c642 100644 --- a/src/engine/forcefieldcurve.cpp +++ b/src/engine/forcefieldcurve.cpp @@ -84,12 +84,19 @@ void ForceFieldCurve::addGravityScaleToGrid(ForceFieldGravityScaleGrid &scaleGri for (int k = 0; k < _ksizeSDF + 1; k++) { for (int j = 0; j < _jsizeSDF + 1; j++) { for (int i = 0; i < _isizeSDF + 1; i++) { - float d = _sdf(i, j, k); - if (d < scaleWidth) { - float factor = 1.0f - (d / scaleWidth); - float scale = factor * _gravityScale + (1.0f - factor); - scaleGrid.addScale(i + _ioffsetSDF, j + _joffsetSDF, k + _koffsetSDF, scale); + vmath::vec3 vectToCurve = _vectorField(i, j, k); + float distanceToCurve = vectToCurve.length(); + if (distanceToCurve > scaleWidth) { + scaleGrid.addScale(i + _ioffsetSDF, j + _joffsetSDF, k + _koffsetSDF, 1.0f); + continue; } + + float scaleFactor = 1.0f; + float distanceFactor = distanceToCurve / scaleWidth; + if (distanceFactor > _gravityScaleFalloffThreshold) { + scaleFactor = 1.0f - (distanceFactor - _gravityScaleFalloffThreshold) / (1.0f - _gravityScaleFalloffThreshold); + } + scaleGrid.addScale(i + _ioffsetSDF, j + _joffsetSDF, k + _koffsetSDF, scaleFactor * _gravityScale); } } } diff --git a/src/engine/forcefieldpoint.cpp b/src/engine/forcefieldpoint.cpp index a1724be1..8c61b3b9 100644 --- a/src/engine/forcefieldpoint.cpp +++ b/src/engine/forcefieldpoint.cpp @@ -65,12 +65,18 @@ void ForceFieldPoint::addGravityScaleToGrid(ForceFieldGravityScaleGrid &scaleGri for (int i = 0; i < scaleGrid.gravityScale.width; i++) { vmath::vec3 gp = Grid3d::GridIndexToPosition(i, j, k, _dx); vmath::vec3 v = gp - p; - float d = vmath::length(v); - if (d < scaleWidth) { - float factor = 1.0f - (d / scaleWidth); - float scale = factor * _gravityScale + (1.0f - factor); - scaleGrid.addScale(i, j, k, scale); + float distanceToPoint = vmath::length(v); + if (distanceToPoint > scaleWidth) { + scaleGrid.addScale(i, j, k, 1.0f); + continue; } + + float scaleFactor = 1.0f; + float distanceFactor = distanceToPoint / scaleWidth; + if (distanceFactor > _gravityScaleFalloffThreshold) { + scaleFactor = 1.0f - (distanceFactor - _gravityScaleFalloffThreshold) / (1.0f - _gravityScaleFalloffThreshold); + } + scaleGrid.addScale(i, j, k, scaleFactor * _gravityScale); } } } diff --git a/src/engine/forcefieldsurface.cpp b/src/engine/forcefieldsurface.cpp index 968f4cc6..67f7b06d 100644 --- a/src/engine/forcefieldsurface.cpp +++ b/src/engine/forcefieldsurface.cpp @@ -84,12 +84,19 @@ void ForceFieldSurface::addGravityScaleToGrid(ForceFieldGravityScaleGrid &scaleG for (int k = 0; k < _ksizeSDF + 1; k++) { for (int j = 0; j < _jsizeSDF + 1; j++) { for (int i = 0; i < _isizeSDF + 1; i++) { - float d = _sdf(i, j, k); - if (d < scaleWidth) { - float factor = 1.0f - (d / scaleWidth); - float scale = factor * _gravityScale + (1.0f - factor); - scaleGrid.addScale(i + _ioffsetSDF, j + _joffsetSDF, k + _koffsetSDF, scale); + vmath::vec3 vectToSurface = _vectorField(i, j, k); + float distanceToSurface = vectToSurface.length(); + if (distanceToSurface > scaleWidth) { + scaleGrid.addScale(i + _ioffsetSDF, j + _joffsetSDF, k + _koffsetSDF, 1.0f); + continue; } + + float scaleFactor = 1.0f; + float distanceFactor = distanceToSurface / scaleWidth; + if (distanceFactor > _gravityScaleFalloffThreshold) { + scaleFactor = 1.0f - (distanceFactor - _gravityScaleFalloffThreshold) / (1.0f - _gravityScaleFalloffThreshold); + } + scaleGrid.addScale(i + _ioffsetSDF, j + _joffsetSDF, k + _koffsetSDF, scaleFactor * _gravityScale); } } } diff --git a/src/engine/forcefieldutils.cpp b/src/engine/forcefieldutils.cpp index 155c64c0..41c8b07f 100644 --- a/src/engine/forcefieldutils.cpp +++ b/src/engine/forcefieldutils.cpp @@ -52,7 +52,7 @@ void generateSurfaceVectorField(MeshLevelSet &sdf, TriangleMesh &mesh, Array3d(isize, jsize, ksize, distUpperBound); - data.closestTriangle = Array3d(isize, jsize, ksize); + data.closestTriangle = Array3d(isize, jsize, ksize, -1); data.closestPoint = Array3d(isize, jsize, ksize); data.isClosestPointSet = Array3d(isize, jsize, ksize, false); data.dx = dx; @@ -102,7 +102,7 @@ void generateSurfaceVectorField(MeshLevelSet &sdf, TriangleMesh &mesh, Array3d= 0 && tidx <= (int)mesh.triangles.size()) { tn = mesh.getTriangleNormal(tidx); } diff --git a/src/engine/forcefieldvolume.cpp b/src/engine/forcefieldvolume.cpp index 28376dfa..00c82ba2 100644 --- a/src/engine/forcefieldvolume.cpp +++ b/src/engine/forcefieldvolume.cpp @@ -86,10 +86,20 @@ void ForceFieldVolume::addGravityScaleToGrid(ForceFieldGravityScaleGrid &scaleGr float d = _sdf(i, j, k); if (d < 0.0f) { scaleGrid.addScale(i + _ioffsetSDF, j + _joffsetSDF, k + _koffsetSDF, _gravityScale); - } else if (d > 0.0f && d < scaleWidth) { - float factor = 1.0f - (d / scaleWidth); - float scale = factor * _gravityScale + (1.0f - factor); - scaleGrid.addScale(i + _ioffsetSDF, j + _joffsetSDF, k + _koffsetSDF, scale); + } else if (d > 0.0f) { + vmath::vec3 vectToSurface = _vectorField(i, j, k); + float distanceToSurface = vectToSurface.length(); + if (distanceToSurface > scaleWidth) { + scaleGrid.addScale(i + _ioffsetSDF, j + _joffsetSDF, k + _koffsetSDF, 1.0f); + continue; + } + + float scaleFactor = 1.0f; + float distanceFactor = distanceToSurface / scaleWidth; + if (distanceFactor > _gravityScaleFalloffThreshold) { + scaleFactor = 1.0f - (distanceFactor - _gravityScaleFalloffThreshold) / (1.0f - _gravityScaleFalloffThreshold); + } + scaleGrid.addScale(i + _ioffsetSDF, j + _joffsetSDF, k + _koffsetSDF, scaleFactor * _gravityScale); } } } diff --git a/src/engine/pyfluid/fluidsimulation.py b/src/engine/pyfluid/fluidsimulation.py index e1c5f927..f5472188 100644 --- a/src/engine/pyfluid/fluidsimulation.py +++ b/src/engine/pyfluid/fluidsimulation.py @@ -2730,6 +2730,7 @@ class FluidSimulationFrameStats_t(ctypes.Structure): ("delta_time", c_double), ("fluid_particles", c_int), ("diffuse_particles", c_int), + ("performance_score", c_int), ("pressure_solver_enabled", c_int), ("pressure_solver_success", c_int), ("pressure_solver_error", c_double), diff --git a/src/engine/stopwatch.h b/src/engine/stopwatch.h index 6b81b8d8..7e59958a 100644 --- a/src/engine/stopwatch.h +++ b/src/engine/stopwatch.h @@ -28,8 +28,8 @@ SOFTWARE. #if defined(__linux__) || defined(__APPLE__) || defined(__MACOSX) #include #elif defined(_WIN32) - #include - #include + #include + #include #else #endif