2025-12-01

This commit is contained in:
2026-03-17 14:58:51 -06:00
parent 183e865f8b
commit 4b82b57113
6846 changed files with 954887 additions and 162606 deletions
@@ -0,0 +1,188 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if "bpy" in locals():
import importlib
reloadable_modules = [
'none_ui',
'fluid_ui',
'obstacle_ui',
'inflow_ui',
'outflow_ui',
'force_field_ui',
'domain_ui',
'cache_object_ui',
'helper_ui',
'flip_fluids_addon_disabled_ui',
]
for module_name in reloadable_modules:
if module_name in locals():
importlib.reload(locals()[module_name])
import bpy
from . import(
none_ui,
fluid_ui,
obstacle_ui,
inflow_ui,
outflow_ui,
force_field_ui,
domain_ui,
cache_object_ui,
helper_ui,
flip_fluids_addon_disabled_ui,
)
from ..utils import version_compatibility_utils as vcu
from ..utils import installation_utils
from ..utils import api_workaround_utils as api_utils
def append_to_PHYSICS_PT_add_panel(self, context):
obj = vcu.get_active_object(context)
if not (obj.type == 'MESH' or obj.type == 'EMPTY', obj.type == 'CURVE'):
return
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
column = self.layout.column(align=True)
split = vcu.ui_split(column, factor=0.5)
column_left = split.column()
column_right = split.column()
if obj.flip_fluid.is_active:
row = column_right.row(align=True)
row.operator(
"flip_fluid_operators.flip_fluid_remove",
text="FLIP Fluid",
icon='X'
)
if obj.flip_fluid.is_domain():
row.enabled = not is_addon_disabled
row.prop(context.scene.flip_fluid, "show_viewport", icon="RESTRICT_VIEW_OFF", text="")
row.prop(context.scene.flip_fluid, "show_render", icon="RESTRICT_RENDER_OFF", text="")
# Experimental Build Warning
addon_prefs = vcu.get_addon_preferences(context)
if installation_utils.is_experimental_build() and addon_prefs.enable_experimental_build_warning:
box = self.layout.box()
column = box.column(align=True)
column.label(text="This is an experimental build of the FLIP Fluids addon", icon='ERROR')
column.label(text="Not for production. Use at your own risk.", icon='ERROR')
column.label(text="Please read before using:", icon='ERROR')
column.operator(
"wm.url_open",
text="Experimental Builds",
icon="WORLD"
).url = "https://github.com/rlguy/Blender-FLIP-Fluids/wiki/Experimental-Builds"
is_saved = bool(bpy.data.filepath)
if not is_saved and obj.flip_fluid.is_domain() and not is_addon_disabled:
hprops = context.scene.flip_fluid_helper
box = self.layout.box()
row = box.row(align=True)
row.prop(hprops, "unsaved_blend_file_tooltip", icon="ERROR", emboss=False, text="")
row = row.row(align=True)
row.alert = True
row.label(text="Unsaved File")
row = row.row(align=True)
row.alert = False
row.operator("flip_fluid_operators.helper_save_blend_file", icon='FILE_TICK', text="Save")
else:
if not installation_utils.is_installation_complete():
column_right.operator(
"flip_fluid_operators.flip_fluid_add",
text="FLIP Fluid",
icon='ERROR'
)
else:
use_custom_icon = True
icon = context.scene.flip_fluid.get_logo_icon()
if use_custom_icon and icon is not None:
column_right.operator(
"flip_fluid_operators.flip_fluid_add",
text="FLIP Fluid",
icon_value=context.scene.flip_fluid.get_logo_icon().icon_id
)
else:
column_right.operator(
"flip_fluid_operators.flip_fluid_add",
text="FLIP Fluid",
icon='MOD_FLUIDSIM'
)
if not installation_utils.is_installation_complete():
box = self.layout.box()
box.label(text="IMPORTANT: Please Complete Installation", icon="ERROR")
box.label(text="Click here to complete installation of the FLIP Fluids Addon:")
box.operator("flip_fluid_operators.complete_installation", icon='MOD_FLUIDSIM')
addon_prefs = vcu.get_addon_preferences(context)
flip_fluids_installations = installation_utils.get_enabled_flip_fluids_addon_installations()
if len(flip_fluids_installations) > 1:
box = self.layout.box()
box.label(text="Installation Error Detected", icon="ERROR")
box.label(text="Multiple version of the FLIP Fluids addon enabled:", icon="ERROR")
for install in flip_fluids_installations:
box.label(text=" "*10 + install['addon_name'] + " (" + install['module_name'] + ")")
box.label(text="Only 1 version of the add-on can be enabled", icon="ERROR")
box.label(text="Disable all other versions in the Blender addon preferences", icon="ERROR")
box.label(text="Restart Blender", icon="ERROR")
is_installation_complete = installation_utils.is_installation_complete()
feature_dict = api_utils.get_enabled_features_affected_by_T88811()
if feature_dict is not None and not addon_prefs.dismiss_T88811_crash_warning and is_installation_complete:
box = self.layout.box()
api_utils.draw_T88811_ui_warning(box, addon_prefs, feature_dict)
is_persistent_data_enabled = api_utils.is_persistent_data_issue_relevant()
if is_persistent_data_enabled and not addon_prefs.dismiss_persistent_data_render_warning and is_installation_complete:
box = self.layout.box()
api_utils.draw_persistent_data_warning(box, addon_prefs)
def register():
none_ui.register()
fluid_ui.register()
obstacle_ui.register()
inflow_ui.register()
outflow_ui.register()
force_field_ui.register()
domain_ui.register()
cache_object_ui.register()
helper_ui.register()
flip_fluids_addon_disabled_ui.register()
bpy.types.PHYSICS_PT_add.append(append_to_PHYSICS_PT_add_panel)
def unregister():
none_ui.unregister()
fluid_ui.unregister()
obstacle_ui.unregister()
inflow_ui.unregister()
outflow_ui.unregister()
force_field_ui.unregister()
domain_ui.unregister()
cache_object_ui.unregister()
helper_ui.unregister()
flip_fluids_addon_disabled_ui.unregister()
bpy.types.PHYSICS_PT_add.remove(append_to_PHYSICS_PT_add_panel)
@@ -0,0 +1,342 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import domain_display_ui
from ..operators import helper_operators
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_CacheObjectTypePanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Mesh Display"
@classmethod
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)
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return dprops.mesh_cache.is_cache_object(obj) and not is_addon_disabled
def get_domain_properties(self):
return bpy.context.scene.flip_fluid.get_domain_properties()
def draw_surface_viewport_render_display(self):
dprops = self.get_domain_properties()
rprops = dprops.render
box = self.layout.box()
column = box.column()
split = vcu.ui_split(column, factor=0.5)
column_left = split.column()
column_left.label(text="Render Display Mode:")
column_left.prop(rprops, "render_display", expand=True)
column_right = split.column()
column_right.label(text="Viewport Display Mode:")
column_right.prop(rprops, "viewport_display", expand=True)
def draw_fluid_particle_viewport_render_display(self):
dprops = self.get_domain_properties()
rprops = dprops.render
box = self.layout.box()
column = box.column()
split = vcu.ui_split(column, factor=0.5)
column_left = split.column()
column_left.label(text="Render Display Mode:")
column_left.prop(rprops, "fluid_particle_render_display", expand=True)
column_right = split.column()
column_right.label(text="Viewport Display Mode:")
column_right.prop(rprops, "fluid_particle_viewport_display", expand=True)
box = self.layout.box()
column = box.column(align=True)
split = vcu.ui_split(column, factor=0.5)
column_left = split.column(align=True)
column_left.label(text="Final Display Settings:")
column_left.prop(rprops, "render_fluid_particle_surface_pct", slider=True)
column_left.prop(rprops, "render_fluid_particle_boundary_pct", slider=True)
column_left.prop(rprops, "render_fluid_particle_interior_pct", slider=True)
column_right = split.column(align=True)
column_right.label(text="Preview Display Settings:")
column_right.prop(rprops, "viewport_fluid_particle_surface_pct", slider=True)
column_right.prop(rprops, "viewport_fluid_particle_boundary_pct", slider=True)
column_right.prop(rprops, "viewport_fluid_particle_interior_pct", slider=True)
bl_fluid_particles_mesh_cache = dprops.mesh_cache.particles.get_cache_object()
point_cloud_detected = helper_operators.is_geometry_node_point_cloud_detected(bl_fluid_particles_mesh_cache)
box = self.layout.box()
column = box.column(align=True)
column.label(text="Particle Object Settings:")
column.separator()
bl_mod = domain_display_ui.get_motion_blur_geometry_node_modifier(bl_fluid_particles_mesh_cache)
row = column.row(align=True)
row.alignment = 'LEFT'
row.label(text="Fluid Particles:")
domain_display_ui.draw_fluid_particles_motion_blur_geometry_node_properties(row, bl_mod)
def draw_whitewater_viewport_render_display(self):
dprops = self.get_domain_properties()
rprops = dprops.render
box = self.layout.box()
column = box.column()
split = vcu.ui_split(column, factor=0.5)
column_left = split.column()
column_left.label(text="Render Display Mode:")
column_left.prop(rprops, "whitewater_render_display", expand=True)
column_right = split.column()
column_right.label(text="Viewport Display Mode:")
column_right.prop(rprops, "whitewater_viewport_display", expand=True)
def draw_whitewater_viewport_render_settings(self, render_pct_prop, viewport_pct_prop):
dprops = self.get_domain_properties()
rprops = dprops.render
box = self.layout.box()
column = box.column(align=True)
split = column.split()
column = split.column(align = True)
column.label(text="Final Display Amount:")
column.prop(rprops, render_pct_prop, slider = True)
column = split.column(align = True)
column.label(text="Preview Display Amount:")
column.prop(rprops, viewport_pct_prop, slider = True)
def draw_whitewater_particle_object_settings(self, prop_str, material_prop):
dprops = self.get_domain_properties()
rprops = dprops.render
bl_object = vcu.get_active_object()
box = self.layout.box()
box.label(text="Particle Display Settings:")
column = box.column(align=True)
column.separator()
split = vcu.ui_split(column, factor=0.1)
column1 = split.column(align=True)
column2 = split.column(align=True)
cache_props = dprops.mesh_cache.get_mesh_cache_from_blender_object(bl_object)
if cache_props is not None:
if cache_props.cache_object_type == 'CACHE_OBJECT_TYPE_FOAM':
whitewater_label = "Foam:"
elif cache_props.cache_object_type == 'CACHE_OBJECT_TYPE_BUBBLE':
whitewater_label = "Bubble:"
elif cache_props.cache_object_type == 'CACHE_OBJECT_TYPE_SPRAY':
whitewater_label = "Spray:"
elif cache_props.cache_object_type == 'CACHE_OBJECT_TYPE_DUST':
whitewater_label = "Dust:"
bl_mod = domain_display_ui.get_motion_blur_geometry_node_modifier(bl_object)
row = column1.row(align=True)
row.label(text=whitewater_label)
row = column2.row(align=True)
domain_display_ui.draw_whitewater_particles_motion_blur_geometry_node_properties(row, bl_mod)
dprops = self.get_domain_properties()
self.layout.separator()
box = self.layout.box()
box.label(text="Material Library")
box.prop(dprops.materials, material_prop, text=prop_str)
box.separator()
def draw_surface(self, cache_props, domain_props):
dprops = self.get_domain_properties()
column = self.layout.column()
column.label(text="Fluid Surface")
column.separator()
self.draw_surface_viewport_render_display()
column = self.layout.column()
column.separator()
split = column.split()
column_left = split.column(align=True)
column_right = split.column(align=True)
column_left.label(text="Surface Material")
column_right.prop(dprops.materials, "surface_material", text="")
self.layout.separator()
column = self.layout.column()
column.operator("flip_fluid_operators.helper_delete_surface_objects", icon="X")
def draw_fluid_particles(self, cache_props, domain_props):
dprops = self.get_domain_properties()
column = self.layout.column()
column.label(text="Fluid Particles")
column.separator()
self.draw_fluid_particle_viewport_render_display()
self.layout.separator()
box = self.layout.box()
box.label(text="Material Library")
box.prop(dprops.materials, "fluid_particles_material", text="Fluid Particles")
box.separator()
self.layout.separator()
column = self.layout.column()
column.operator("flip_fluid_operators.helper_delete_particle_objects", icon="X")
def draw_foam(self, cache_props, domain_props):
dprops = self.get_domain_properties()
rprops = dprops.render
column = self.layout.column()
column.label(text="Whitewater Foam")
column.separator()
self.draw_whitewater_viewport_render_display()
self.draw_whitewater_viewport_render_settings('render_foam_pct', 'viewport_foam_pct')
self.draw_whitewater_particle_object_settings("Foam", 'whitewater_foam_material')
self.layout.separator()
column = self.layout.column()
column.operator("flip_fluid_operators.helper_delete_whitewater_objects",
text="Delete Whitewater Foam Mesh Objects",
icon="X").whitewater_type = 'TYPE_FOAM'
column.operator("flip_fluid_operators.helper_delete_whitewater_objects",
text="Delete All Whitewater Mesh Objects",
icon="X").whitewater_type = 'TYPE_ALL'
def draw_bubble(self, cache_props, domain_props):
dprops = self.get_domain_properties()
rprops = dprops.render
column = self.layout.column()
column.label(text="Whitewater Bubble")
column.separator()
self.draw_whitewater_viewport_render_display()
self.draw_whitewater_viewport_render_settings('render_bubble_pct', 'viewport_bubble_pct')
self.draw_whitewater_particle_object_settings("Bubble", 'whitewater_bubble_material')
self.layout.separator()
column = self.layout.column()
column.operator("flip_fluid_operators.helper_delete_whitewater_objects",
text="Delete Whitewater Bubble Mesh Objects",
icon="X").whitewater_type = 'TYPE_BUBBLE'
column.operator("flip_fluid_operators.helper_delete_whitewater_objects",
text="Delete All Whitewater Mesh Objects",
icon="X").whitewater_type = 'TYPE_ALL'
def draw_spray(self, cache_props, domain_props):
dprops = self.get_domain_properties()
rprops = dprops.render
column = self.layout.column()
column.label(text="Whitewater Spray")
column.separator()
self.draw_whitewater_viewport_render_display()
self.draw_whitewater_viewport_render_settings('render_spray_pct', 'viewport_spray_pct')
self.draw_whitewater_particle_object_settings("Spray", 'whitewater_spray_material')
self.layout.separator()
column = self.layout.column()
column.operator("flip_fluid_operators.helper_delete_whitewater_objects",
text="Delete Whitewater Spray Mesh Objects",
icon="X").whitewater_type = 'TYPE_SPRAY'
column.operator("flip_fluid_operators.helper_delete_whitewater_objects",
text="Delete All Whitewater Mesh Objects",
icon="X").whitewater_type = 'TYPE_ALL'
def draw_dust(self, cache_props, domain_props):
dprops = self.get_domain_properties()
rprops = dprops.render
column = self.layout.column()
column.label(text="Whitewater Dust")
column.separator()
self.draw_whitewater_viewport_render_display()
self.draw_whitewater_viewport_render_settings('render_dust_pct', 'viewport_dust_pct')
self.draw_whitewater_particle_object_settings("Dust", 'whitewater_dust_material')
self.layout.separator()
column = self.layout.column()
column.operator("flip_fluid_operators.helper_delete_whitewater_objects",
text="Delete Whitewater Dust Mesh Objects",
icon="X").whitewater_type = 'TYPE_DUST'
column.operator("flip_fluid_operators.helper_delete_whitewater_objects",
text="Delete All Whitewater Mesh Objects",
icon="X").whitewater_type = 'TYPE_ALL'
def draw(self, context):
dprops = self.get_domain_properties()
obj = vcu.get_active_object(context)
cache_props = dprops.mesh_cache.get_mesh_cache_from_blender_object(obj)
if cache_props is None:
return
if cache_props.cache_object_type == 'CACHE_OBJECT_TYPE_SURFACE':
self.draw_surface(cache_props, dprops)
if cache_props.cache_object_type == 'CACHE_OBJECT_TYPE_FLUID_PARTICLES':
self.draw_fluid_particles(cache_props, dprops)
elif cache_props.cache_object_type == 'CACHE_OBJECT_TYPE_FOAM':
self.draw_foam(cache_props, dprops)
elif cache_props.cache_object_type == 'CACHE_OBJECT_TYPE_BUBBLE':
self.draw_bubble(cache_props, dprops)
elif cache_props.cache_object_type == 'CACHE_OBJECT_TYPE_SPRAY':
self.draw_spray(cache_props, dprops)
elif cache_props.cache_object_type == 'CACHE_OBJECT_TYPE_DUST':
self.draw_dust(cache_props, dprops)
def register():
bpy.utils.register_class(FLIPFLUID_PT_CacheObjectTypePanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_CacheObjectTypePanel)
@@ -0,0 +1,192 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_DomainTypeAdvancedPanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Advanced Settings"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
if vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN" and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
aprops = obj.flip_fluid.domain.advanced
wprops = obj.flip_fluid.domain.world
box = self.layout.box()
column = box.column(align=True)
split = vcu.ui_split(column, factor=0.45)
column_left = split.column(align=True)
column_right = split.column(align=True)
row = column_left.row(align=True)
row.prop(aprops, "frame_substeps_expanded",
icon="TRIA_DOWN" if aprops.frame_substeps_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Frame Substeps:")
if not aprops.frame_substeps_expanded:
row = column_right.row(align=True)
row.prop(aprops.min_max_time_steps_per_frame, "value_min", text="Min")
row.prop(aprops.min_max_time_steps_per_frame, "value_max", text="Max")
if aprops.frame_substeps_expanded:
column = box.column(align=True)
if wprops.enable_surface_tension and aprops.min_max_time_steps_per_frame.value_max < wprops.minimum_surface_tension_substeps:
row = column.row(align=True)
row.alert = True
row.prop(aprops, "surface_tension_substeps_exceeded_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text=" Warning: Not Enough Max Substeps")
row = column.row(align=True)
row.prop(aprops.min_max_time_steps_per_frame, "value_min", text="Min")
row.prop(aprops.min_max_time_steps_per_frame, "value_max", text="Max")
column.prop(aprops, "CFL_condition_number")
column.prop(aprops, "enable_adaptive_obstacle_time_stepping")
column.prop(aprops, "enable_adaptive_force_field_time_stepping")
box = self.layout.box()
column = box.column(align=True)
split = vcu.ui_split(column, factor=0.5)
column_left = split.column(align=True)
column_right = split.column(align=True)
row = column_left.row(align=True)
row.prop(aprops, "simulation_method_expanded",
icon="TRIA_DOWN" if aprops.simulation_method_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Simulation Method:")
if not aprops.simulation_method_expanded:
row = column_right.row(align=True)
row.prop(aprops, "velocity_transfer_method", expand=True)
if aprops.simulation_method_expanded:
column = box.column(align=True)
row = column.row(align=True)
row.prop(aprops, "velocity_transfer_method", expand=True)
if aprops.velocity_transfer_method == 'VELOCITY_TRANSFER_METHOD_FLIP':
column.prop(aprops, "PICFLIP_ratio", slider=True)
else:
column.label(text="")
box = self.layout.box()
row = box.row(align=True)
row.prop(aprops, "simulation_stability_expanded",
icon="TRIA_DOWN" if aprops.simulation_stability_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Simulation and Particle Stability:")
if aprops.simulation_stability_expanded:
column = box.column()
row = column.row(align=True)
row.prop(aprops, "particle_jitter_factor", slider=True)
row.prop(aprops, "jitter_surface_particles")
column.prop(aprops, "enable_extreme_velocity_removal")
column.separator()
column = box.column(align=True)
column.prop(aprops, "pressure_solver_max_iterations")
column.prop(aprops, "viscosity_solver_max_iterations")
box = self.layout.box()
row = box.row(align=True)
row.prop(aprops, "multithreading_expanded",
icon="TRIA_DOWN" if aprops.multithreading_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Multithreading and Performance:")
if not aprops.multithreading_expanded:
info_text = ""
if aprops.threading_mode == 'THREADING_MODE_AUTO_DETECT':
info_text = "Auto-detect " + str(aprops.num_threads_auto_detect) + " threads"
elif aprops.threading_mode == 'THREADING_MODE_FIXED':
info_text = "Fixed " + str(aprops.num_threads_fixed) + " threads"
row = row.row(align=True)
row.alignment = 'RIGHT'
row.label(text=info_text)
if aprops.multithreading_expanded:
column = box.column()
split = column.split(align=True)
column_left = split.column(align=True)
row = column_left.row(align=True)
row.prop(aprops, "threading_mode", expand=True)
row = column_left.row(align=True)
if aprops.threading_mode == 'THREADING_MODE_AUTO_DETECT':
row.enabled = False
row.prop(aprops, "num_threads_auto_detect")
elif aprops.threading_mode == 'THREADING_MODE_FIXED':
row.prop(aprops, "num_threads_fixed")
column = box.column()
column.prop(aprops, "enable_fracture_optimization")
# Performance and optimization settings are hidden from the UI.
# These should always be enabled for performance.
"""
column = self.layout.column(align=True)
column.separator()
column.label(text="Performance and Optimization:")
column.prop(aprops, "enable_asynchronous_meshing")
column.prop(aprops, "precompute_static_obstacles")
column.prop(aprops, "reserve_temporary_grids")
"""
box = self.layout.box()
row = box.row(align=True)
row.prop(aprops, "warnings_and_errors_expanded",
icon="TRIA_DOWN" if aprops.warnings_and_errors_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Warnings and Errors:")
if aprops.warnings_and_errors_expanded:
column = box.column(align=True)
column.prop(aprops, "disable_changing_topology_warning")
def register():
bpy.utils.register_class(FLIPFLUID_PT_DomainTypeAdvancedPanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypeAdvancedPanel)
@@ -0,0 +1,166 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy, math
from ..utils import version_compatibility_utils as vcu
def format_bytes(self, num):
# Method adapted from: http://stackoverflow.com/a/10171475
unit_list = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB']
decimal_list = [0, 0, 1, 2, 2, 2]
if num > 1:
exponent = min(int(math.log(num, 1024)), len(unit_list) - 1)
quotient = float(num) / 1024**exponent
unit, num_decimals = unit_list[exponent], decimal_list[exponent]
format_string = '{:.%sf} {}' % (num_decimals)
return format_string.format(quotient, unit)
if num == 0:
return '0 bytes'
if num == 1:
return '1 byte'
class FLIPFLUID_PT_DomainTypeCachePanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Cache"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
if vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN" and not is_addon_disabled
def draw(self, context):
domain_object = vcu.get_active_object(context)
dprops = domain_object.flip_fluid.domain
cprops = dprops.cache
box = self.layout.box()
row = box.row(align=True)
row.prop(cprops, "cache_directory_expanded",
icon="TRIA_DOWN" if cprops.cache_directory_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Cache Directory:")
if not cprops.cache_directory_expanded:
row = row.row()
row.prop(cprops, "cache_directory")
if cprops.cache_directory_expanded:
column = box.column(align=True)
subcolumn = column.column(align=True)
subcolumn.enabled = not dprops.bake.is_simulation_running
row = subcolumn.row(align=True)
row.prop(cprops, "cache_directory")
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")
row.operator("flip_fluid_operators.match_filename_cache_directory")
box = self.layout.box()
row = box.row(align=True)
row.prop(cprops, "link_exported_geometry_expanded",
icon="TRIA_DOWN" if cprops.link_exported_geometry_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Link existing exported geometry:")
if cprops.link_exported_geometry_expanded:
column = box.column(align=True)
subcolumn = column.column(align=True)
subcolumn.enabled = not dprops.bake.is_simulation_running
subcolumn.prop(cprops, "linked_geometry_directory")
row = column.row(align=True)
row.operator("flip_fluid_operators.relative_linked_geometry_directory")
row.operator("flip_fluid_operators.absolute_linked_geometry_directory")
column = column.column(align=True)
column.operator("flip_fluid_operators.clear_linked_geometry_directory")
column.separator()
box = self.layout.box()
row = box.row(align=True)
row.prop(cprops, "cache_operators_expanded",
icon="TRIA_DOWN" if cprops.cache_operators_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Cache Operators:")
if not cprops.cache_operators_expanded:
if dprops.stats.is_cache_info_available:
free_text = "Free (" + format_bytes(self, dprops.stats.cache_bytes.get()) + ")"
else:
free_text = "Free"
row.operator("flip_fluid_operators.free_cache", text=free_text)
if cprops.cache_operators_expanded:
column = box.column(align=True)
# The move, rename, and copy cache operations should not be performed
# in Blender and are removed from the UI. There is a potential for Blender
# to crash, which could lead to loss of data. It is best to perform these
# operations through the OS filesystem which is cabable of handling failures.
"""
row = column.row(align=True)
row.operator("flip_fluid_operators.move_cache", text="Move")
row.prop(cprops, "move_cache_directory")
row = column.row(align=True)
row.operator("flip_fluid_operators.rename_cache", text="Rename")
row.prop(cprops, "rename_cache_directory")
row = column.row(align=True)
row.operator("flip_fluid_operators.copy_cache", text="Copy")
row.prop(cprops, "copy_cache_directory")
"""
if dprops.stats.is_cache_info_available:
free_text = "Free (" + format_bytes(self, dprops.stats.cache_bytes.get()) + ")"
else:
free_text = "Free"
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
column_left.operator("flip_fluid_operators.free_cache", text=free_text)
column_right.prop(cprops, "clear_cache_directory_logs", text="Free log files")
column_right.prop(cprops, "clear_cache_directory_export", text="Free export files")
def register():
bpy.utils.register_class(FLIPFLUID_PT_DomainTypeCachePanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypeCachePanel)
@@ -0,0 +1,204 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_DomainTypeDebugPanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Debug"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
if vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN" and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
gprops = obj.flip_fluid.domain.debug
box = self.layout.box()
row = box.row(align=True)
row.prop(gprops, "grid_display_settings_expanded",
icon="TRIA_DOWN" if gprops.grid_display_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
if not gprops.grid_display_settings_expanded:
row.prop(gprops, "display_simulation_grid", text="")
row.label(text="Grid Visualization:")
if gprops.grid_display_settings_expanded:
split = vcu.ui_split(box, align=True, factor=0.3)
column = split.column(align=True)
column.prop(gprops, "display_simulation_grid", text="Display Grid")
column = split.column(align=True)
column.enabled = gprops.display_simulation_grid
split = column.split(align=True)
column = split.column(align=True)
column.prop(gprops, "grid_display_mode", text="")
column = split.column(align=True)
column.prop(gprops, "grid_display_scale", text="Draw Scale")
split = vcu.ui_split(box, align=True, factor=0.3)
column = split.column(align=True)
column.enabled = gprops.display_simulation_grid
column.label(text="Enabled Grids:")
column.label(text="Grid Colors:")
column.label(text="Grid Offsets:")
column = split.column(align=True)
column.enabled = gprops.display_simulation_grid
row = column.row(align=True)
row.prop(gprops, "enabled_debug_grids", text="", toggle=True)
row = column.row(align=True)
row.prop(gprops, "x_grid_color", text="")
row.prop(gprops, "y_grid_color", text="")
row.prop(gprops, "z_grid_color", text="")
row = column.row(align=True)
row.prop(gprops, "debug_grid_offsets", text="", slider=True)
column.prop(gprops, "snap_offsets_to_grid")
column = box.column(align=True)
split = vcu.ui_split(column, align=True, factor=0.3)
column = split.column(align=True)
column.prop(gprops, "display_domain_bounds")
column = split.column(align=True)
column.enabled = gprops.display_simulation_grid or gprops.display_domain_bounds
column.prop(gprops, "domain_bounds_color", text="")
box = self.layout.box()
row = box.row(align=True)
row.prop(gprops, "particle_debug_settings_expanded",
icon="TRIA_DOWN" if gprops.particle_debug_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
if not gprops.particle_debug_settings_expanded:
row.prop(gprops, "enable_fluid_particle_debug_output", text="")
row.label(text="Fluid Particle Debugging:")
next_row = row.row()
next_row.alignment = 'RIGHT'
next_row.prop(gprops, "fluid_particles_visibility",
text="",
icon=vcu.get_hide_off_icon() if gprops.fluid_particles_visibility else vcu.get_hide_on_icon(),
emboss=False
)
if gprops.particle_debug_settings_expanded:
box.prop(gprops, "enable_fluid_particle_debug_output")
column = box.column(align=True)
column.enabled = gprops.enable_fluid_particle_debug_output
column.label(text="Particle Display Settings:")
row = column.row(align=True)
row.prop(gprops, "min_gradient_speed")
row.prop(gprops, "max_gradient_speed")
row = column.row(align=True)
row.prop(gprops, "low_speed_particle_color", text="")
row.prop(gprops, "high_speed_particle_color", text="")
row = column.row(align=True)
row.prop(gprops, "fluid_particle_gradient_mode", expand=True)
column = box.column()
column.enabled = gprops.enable_fluid_particle_debug_output
split = vcu.ui_split(column, factor=0.33)
column = split.column()
column.label(text="Particle Size:")
column.label(text="Draw Bounds:")
column = split.column()
column.prop(gprops, "particle_size", text="")
column.prop_search(gprops, "particle_draw_aabb", bpy.data, "objects", text="")
box = self.layout.box()
row = box.row(align=True)
row.prop(gprops, "force_field_debug_settings_expanded",
icon="TRIA_DOWN" if gprops.force_field_debug_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
if not gprops.force_field_debug_settings_expanded:
row.prop(gprops, "export_force_field", text="")
row.label(text="Force Field Debugging:")
next_row = row.row()
next_row.alignment = 'RIGHT'
next_row.prop(gprops, "force_field_visibility",
text="",
icon=vcu.get_hide_off_icon() if gprops.force_field_visibility else vcu.get_hide_on_icon(),
emboss=False
)
if gprops.force_field_debug_settings_expanded:
box.prop(gprops, "export_force_field")
column = box.column(align=True)
column.enabled = gprops.export_force_field
column.label(text="Force Field Display Settings:")
row = column.row(align=True)
row.prop(gprops, "min_gradient_force")
row.prop(gprops, "max_gradient_force")
row = column.row(align=True)
row.prop(gprops, "low_force_field_color", text="")
row.prop(gprops, "high_force_field_color", text="")
row = column.row(align=True)
row.prop(gprops, "force_field_gradient_mode", expand=True)
column = box.column()
column.enabled = gprops.export_force_field
split = vcu.ui_split(column, factor=0.33)
column = split.column()
column.label(text="Display Amount:")
column.label(text="Line Size:")
column = split.column()
column.prop(gprops, "force_field_display_amount", text="")
column.prop(gprops, "force_field_line_size", text="")
box = self.layout.box()
row = box.row(align=True)
row.prop(gprops, "export_internal_obstacle_mesh")
next_row = row.row()
next_row.alignment = 'RIGHT'
next_row.prop(gprops, "internal_obstacle_mesh_visibility",
text="",
icon=vcu.get_hide_off_icon() if gprops.internal_obstacle_mesh_visibility else vcu.get_hide_on_icon(),
emboss=False
)
box = self.layout.box()
column = box.column(align=True)
column.prop(gprops, "display_render_passes_console_output")
column.prop(gprops, "display_console_output")
def register():
bpy.utils.register_class(FLIPFLUID_PT_DomainTypeDebugPanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypeDebugPanel)
@@ -0,0 +1,464 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..operators import helper_operators
from ..utils import version_compatibility_utils as vcu
def draw_simulation_display_settings(self, context):
domain_object = vcu.get_active_object(context)
rprops = domain_object.flip_fluid.domain.render
scene_props = context.scene.flip_fluid
box = self.layout.box()
column = box.column()
row = column.row(align=True)
row.prop(rprops, "simulation_display_settings_expanded",
icon="TRIA_DOWN" if rprops.simulation_display_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Simulation Visibility:")
if not scene_props.show_viewport or not scene_props.show_render:
visibility_text = ""
if not scene_props.show_viewport and not scene_props.show_render:
visibility_text += "Disabled in Viewport + Render"
elif not scene_props.show_viewport:
visibility_text += "Disabled in Viewport"
elif not scene_props.show_render:
visibility_text += "Disabled in Render"
row = row.row(align=True)
row.alert = True
row.alignment = 'RIGHT'
row.label(text=visibility_text, icon="CANCEL")
if scene_props.show_viewport and scene_props.show_render:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Visibility Enabled", icon="CHECKMARK")
if not rprops.simulation_display_settings_expanded:
return
split = vcu.ui_split(column)
column_left = split.column()
column_left.prop(scene_props, "show_render", text="Show In Render", icon="RESTRICT_RENDER_OFF")
column_right = split.column()
column_right.prop(scene_props, "show_viewport", text="Show In Viewport", icon="RESTRICT_VIEW_OFF")
def draw_surface_display_settings(self, context, menu_expand_prop_group=None):
domain_object = vcu.get_active_object(context)
rprops = domain_object.flip_fluid.domain.render
mprops = domain_object.flip_fluid.domain.materials
box = self.layout.box()
column = box.column()
row = column.row(align=True)
row.prop(menu_expand_prop_group, "surface_display_settings_expanded",
icon="TRIA_DOWN" if getattr(menu_expand_prop_group, "surface_display_settings_expanded") else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Surface Display and Render:")
if not getattr(menu_expand_prop_group, "surface_display_settings_expanded"):
info_text = ""
if rprops.render_display == 'DISPLAY_FINAL':
info_text += "Render Final"
elif rprops.render_display == 'DISPLAY_PREVIEW':
info_text += "Render Preview"
elif rprops.render_display == 'DISPLAY_NONE':
info_text += "Render None"
info_text += " / "
if rprops.viewport_display == 'DISPLAY_FINAL':
info_text += "View Final"
elif rprops.viewport_display == 'DISPLAY_PREVIEW':
info_text += "View Preview"
elif rprops.viewport_display == 'DISPLAY_NONE':
info_text += "View None"
row = row.row(align=True)
row.alignment='RIGHT'
row.label(text=info_text)
return
split = vcu.ui_split(column, factor=0.5)
column_left = split.column()
column_left.label(text="Render Display Mode:")
column_left.prop(rprops, "render_display", expand=True)
column_right = split.column()
column_right.label(text="Viewport Display Mode:")
column_right.prop(rprops, "viewport_display", expand=True)
column_left.label(text="Surface Material:")
column_right.prop(mprops, "surface_material", text="")
def draw_fluid_particle_display_settings(self, context, menu_expand_prop_group=None):
domain_object = vcu.get_active_object(context)
dprops = domain_object.flip_fluid.domain
rprops = domain_object.flip_fluid.domain.render
mprops = domain_object.flip_fluid.domain.materials
is_fluid_particles_enabled = domain_object.flip_fluid.domain.particles.enable_fluid_particle_output
box = self.layout.box()
column = box.column()
if is_fluid_particles_enabled:
row = column.row(align=True)
row.prop(menu_expand_prop_group, "fluid_particle_display_settings_expanded",
icon="TRIA_DOWN" if getattr(menu_expand_prop_group, "fluid_particle_display_settings_expanded") else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Fluid Particle Display and Render:")
else:
split = column.split()
left_column = split.column()
row = left_column.row(align=True)
row.prop(menu_expand_prop_group, "fluid_particle_display_settings_expanded",
icon="TRIA_DOWN" if getattr(menu_expand_prop_group, "fluid_particle_display_settings_expanded") else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Fluid Particle Display and Render:")
right_column = split.column()
row = right_column.row()
row.alignment = 'RIGHT'
c = row.row(align=True)
c.alignment = 'RIGHT'
c.enabled = False
c.label(text="Enable in 'Particles' panel")
row.operator("flip_fluid_operators.display_enable_fluid_particles_tooltip",
text="", icon="QUESTION", emboss=False)
if not getattr(menu_expand_prop_group, "fluid_particle_display_settings_expanded"):
if is_fluid_particles_enabled:
info_text = ""
if rprops.fluid_particle_render_display == 'DISPLAY_FINAL':
info_text += "Render Final"
elif rprops.fluid_particle_render_display == 'DISPLAY_PREVIEW':
info_text += "Render Preview"
elif rprops.fluid_particle_render_display == 'DISPLAY_NONE':
info_text += "Render None"
info_text += " / "
if rprops.fluid_particle_viewport_display == 'DISPLAY_FINAL':
info_text += "View Final"
elif rprops.fluid_particle_viewport_display == 'DISPLAY_PREVIEW':
info_text += "View Preview"
elif rprops.fluid_particle_viewport_display == 'DISPLAY_NONE':
info_text += "View None"
row = row.row(align=True)
row.alignment='RIGHT'
row.label(text=info_text)
return
if getattr(menu_expand_prop_group, "fluid_particle_display_settings_expanded"):
subbox = box.box()
column = subbox.column(align=True)
column.enabled = is_fluid_particles_enabled
split = vcu.ui_split(column, factor=0.5)
column_left = split.column()
column_left.label(text="Render Display Mode:")
column_left.prop(rprops, "fluid_particle_render_display", expand=True)
column_right = split.column()
column_right.label(text="Viewport Display Mode:")
column_right.prop(rprops, "fluid_particle_viewport_display", expand=True)
subbox = box.box()
column = subbox.column(align=True)
column.enabled = is_fluid_particles_enabled
split = vcu.ui_split(column, factor=0.5)
column_left = split.column(align=True)
column_left.label(text="Final Display Amount:")
column_left.prop(rprops, "render_fluid_particle_surface_pct", slider=True)
column_left.prop(rprops, "render_fluid_particle_boundary_pct", slider=True)
column_left.prop(rprops, "render_fluid_particle_interior_pct", slider=True)
column_right = split.column(align=True)
column_right.label(text="Preview Display Amount:")
column_right.prop(rprops, "viewport_fluid_particle_surface_pct", slider=True)
column_right.prop(rprops, "viewport_fluid_particle_boundary_pct", slider=True)
column_right.prop(rprops, "viewport_fluid_particle_interior_pct", slider=True)
bl_fluid_particles_mesh_cache = dprops.mesh_cache.particles.get_cache_object()
subbox = box.box()
subbox.enabled = is_fluid_particles_enabled
column = subbox.column(align=True)
column.label(text="Particle Display Settings:")
column.separator()
bl_mod = get_motion_blur_geometry_node_modifier(bl_fluid_particles_mesh_cache)
row = column.row(align=True)
row.alignment = 'LEFT'
row.label(text="Fluid Particles:")
if is_fluid_particles_enabled:
draw_fluid_particles_motion_blur_geometry_node_properties(row, bl_mod)
else:
row.label(text="Enable Fluid Particle feature to view full particle settings", icon='INFO')
subbox = box.box()
column = subbox.column(align=True)
column.enabled = is_fluid_particles_enabled
split = vcu.ui_split(column, factor=0.5)
column_left = split.column()
column_right = split.column()
column_left.label(text="Fluid Particle Material:")
column_right.prop(mprops, "fluid_particles_material", text="")
def get_motion_blur_geometry_node_modifier(bl_object):
if bl_object is None:
return None
for mod in bl_object.modifiers:
if mod.type == "NODES" and mod.node_group and mod.node_group.name.startswith("FF_GeometryNodes"):
return mod
def draw_whitewater_particles_motion_blur_geometry_node_properties(ui_row, bl_mod):
if bl_mod is None:
ui_row.alert = True
ui_row.operator(
"flip_fluid_operators.helper_initialize_cache_objects",
text="Initialize Geometry Nodes - Missing FF_GeometryNodesWhitewater modifier",
icon="ERROR"
).cache_object_type = 'CACHE_OBJECT_TYPE_WHITEWATER_PARTICLES'
return
ui_row.alignment = 'LEFT'
if "Input_6" in bl_mod:
ui_row.prop(bl_mod, '["Input_6"]', text="Scale")
if "Input_4" in bl_mod:
ui_row.prop(bl_mod, '["Input_4"]', text="Blur Scale")
if "Input_8" in bl_mod:
ui_row.prop(bl_mod, '["Input_8"]', text="Motion Blur")
if "Input_9" in bl_mod:
ui_row.prop(bl_mod, '["Input_9"]', text="Point Cloud")
def draw_fluid_particles_motion_blur_geometry_node_properties(ui_row, bl_mod):
if bl_mod is None:
ui_row.alert = True
ui_row.operator(
"flip_fluid_operators.helper_initialize_cache_objects",
text="Initialize Geometry Nodes - Missing FF_GeometryNodesFluidParticles modifier",
icon="ERROR"
).cache_object_type = 'CACHE_OBJECT_TYPE_FLUID_PARTICLES'
return
ui_row.alignment = 'LEFT'
if "Input_6" in bl_mod:
ui_row.prop(bl_mod, '["Input_6"]', text="Scale")
if "Input_4" in bl_mod:
ui_row.prop(bl_mod, '["Input_4"]', text="Blur Scale")
if "Input_8" in bl_mod:
ui_row.prop(bl_mod, '["Input_8"]', text="Motion Blur")
if "Input_9" in bl_mod:
ui_row.prop(bl_mod, '["Input_9"]', text="Point Cloud")
def draw_whitewater_display_settings(self, context, menu_expand_prop_group=None):
obj = vcu.get_active_object(context)
dprops = obj.flip_fluid.domain
rprops = dprops.render
is_whitewater_enabled = dprops.whitewater.enable_whitewater_simulation
master_box = self.layout.box()
column = master_box.column()
if is_whitewater_enabled:
row = column.row(align=True)
row.prop(menu_expand_prop_group, "whitewater_display_settings_expanded",
icon="TRIA_DOWN" if getattr(menu_expand_prop_group, "whitewater_display_settings_expanded") else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Whitewater Display and Render:")
else:
split = column.split()
left_column = split.column()
row = left_column.row(align=True)
row.prop(menu_expand_prop_group, "whitewater_display_settings_expanded",
icon="TRIA_DOWN" if getattr(menu_expand_prop_group, "whitewater_display_settings_expanded") else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Whitewater Display and Render:")
right_column = split.column()
row = right_column.row()
row.alignment = 'RIGHT'
c = row.row(align=True)
c.alignment = 'RIGHT'
c.enabled = False
c.label(text="Enable in 'Whitewater' panel")
row.operator("flip_fluid_operators.display_enable_whitewater_tooltip",
text="", icon="QUESTION", emboss=False)
if not getattr(menu_expand_prop_group, "whitewater_display_settings_expanded"):
if is_whitewater_enabled:
info_text = ""
if rprops.whitewater_render_display == 'DISPLAY_FINAL':
info_text += "Render Final"
elif rprops.whitewater_render_display == 'DISPLAY_PREVIEW':
info_text += "Render Preview"
elif rprops.whitewater_render_display == 'DISPLAY_NONE':
info_text += "Render None"
info_text += " / "
if rprops.whitewater_viewport_display == 'DISPLAY_FINAL':
info_text += "View Final"
elif rprops.whitewater_viewport_display == 'DISPLAY_PREVIEW':
info_text += "View Preview"
elif rprops.whitewater_viewport_display == 'DISPLAY_NONE':
info_text += "View None"
row = row.row(align=True)
row.alignment='RIGHT'
row.label(text=info_text)
return
if getattr(menu_expand_prop_group, "whitewater_display_settings_expanded"):
box = master_box.box()
box.enabled = is_whitewater_enabled
column = box.column(align=True)
split = column.split()
column = split.column(align=True)
column.label(text="Render Display Mode:")
column.prop(rprops, "whitewater_render_display", expand=True)
column = split.column(align=True)
column.label(text="Viewport Display Mode:")
column.prop(rprops, "whitewater_viewport_display", expand=True)
# Whitewater motion blur rendering is currently too resource intensive
# for Blender Cycles
"""
column = box.column()
column.label(text="Motion Blur:")
split = vcu.ui_split(column, factor=0.5)
column_left = split.column()
column_left.prop(rprops, "render_whitewater_motion_blur")
column_right = split.column()
column_right.prop(rprops, "whitewater_motion_blur_scale")
"""
box = master_box.box()
box.enabled = is_whitewater_enabled
column = box.column(align=True)
split = column.split()
column = split.column(align=True)
column.label(text="Final Display Amount:")
column.prop(rprops, "render_foam_pct", slider=True)
column.prop(rprops, "render_bubble_pct", slider=True)
column.prop(rprops, "render_spray_pct", slider=True)
column.prop(rprops, "render_dust_pct", slider=True)
column = split.column(align=True)
column.label(text="Preview Display Amount:")
column.prop(rprops, "viewport_foam_pct", slider=True)
column.prop(rprops, "viewport_bubble_pct", slider=True)
column.prop(rprops, "viewport_spray_pct", slider=True)
column.prop(rprops, "viewport_dust_pct", slider=True)
box = master_box.box()
box.enabled = is_whitewater_enabled
column = box.column(align=True)
column.label(text="Particle Display Settings:")
column.separator()
split = vcu.ui_split(column, factor=0.1)
column1 = split.column(align=True)
column2 = split.column(align=True)
whitewater_labels = ["Foam:", "Bubble:", "Spray:", "Dust:"]
mesh_cache_objects = [
dprops.mesh_cache.foam.get_cache_object(),
dprops.mesh_cache.bubble.get_cache_object(),
dprops.mesh_cache.spray.get_cache_object(),
dprops.mesh_cache.dust.get_cache_object()
]
for idx, bl_object in enumerate(mesh_cache_objects):
bl_mod = get_motion_blur_geometry_node_modifier(bl_object)
row = column1.row(align=True)
row.label(text=whitewater_labels[idx])
row = column2.row(align=True)
if is_whitewater_enabled:
draw_whitewater_particles_motion_blur_geometry_node_properties(row, bl_mod)
else:
row.label(text="Enable Whitewater feature to view full particle settings", icon='INFO')
box = master_box.box()
box.enabled = is_whitewater_enabled
mprops = dprops.materials
column = box.column(align=True)
column.label(text="Particle Materials:")
column.prop(mprops, "whitewater_foam_material", text="Foam")
column.prop(mprops, "whitewater_bubble_material", text="Bubble")
column.prop(mprops, "whitewater_spray_material", text="Spray")
column.prop(mprops, "whitewater_dust_material", text="Dust")
class FLIPFLUID_PT_DomainTypeDisplayPanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Display and Render Settings"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
if vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN" and not is_addon_disabled
def draw(self, context):
domain_object = vcu.get_active_object(context)
rprops = domain_object.flip_fluid.domain.render
draw_simulation_display_settings(self, context)
draw_surface_display_settings(self, context, rprops)
draw_fluid_particle_display_settings(self, context, rprops)
draw_whitewater_display_settings(self, context, rprops)
def register():
bpy.utils.register_class(FLIPFLUID_PT_DomainTypeDisplayPanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypeDisplayPanel)
@@ -0,0 +1,69 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..materials import material_library
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_DomainTypeMaterialsPanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Materials"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
if vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN" and not is_addon_disabled
def draw(self, context):
if not material_library.is_material_library_available():
self.layout.label(text="This feature is missing data and will be disabled.")
self.layout.label(text="Please contact the developers if you think this is an error.")
return
obj = vcu.get_active_object(context)
mprops = obj.flip_fluid.domain.materials
column = self.layout.column()
column.prop(mprops, "surface_material", text="Surface")
column.prop(mprops, "fluid_particles_material", text="Fluid Particles")
column.prop(mprops, "whitewater_foam_material", text="Foam")
column.prop(mprops, "whitewater_bubble_material", text="Bubble")
column.prop(mprops, "whitewater_spray_material", text="Spray")
column.prop(mprops, "whitewater_dust_material", text="Dust")
self.layout.row().separator()
row = self.layout.row(align = True)
row.prop(mprops, "material_import", text = "")
column = row.column(align=True)
column.operator("flip_fluid_operators.import_material")
column.operator("flip_fluid_operators.import_material_copy")
def register():
bpy.utils.register_class(FLIPFLUID_PT_DomainTypeMaterialsPanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypeMaterialsPanel)
@@ -0,0 +1,275 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..operators import helper_operators
from ..ui import domain_display_ui
from ..utils import version_compatibility_utils as vcu
from ..utils import installation_utils
def _draw_fluid_particle_display_settings(self, context):
obj = vcu.get_active_object(context)
dprops = obj.flip_fluid.domain
domain_display_ui.draw_fluid_particle_display_settings(self, context, dprops.particles)
def _draw_geometry_attributes_menu(self, context):
obj = vcu.get_active_object(context)
pprops = obj.flip_fluid.domain.particles
sprops = obj.flip_fluid.domain.surface
#
# Geometry Attributes
#
box = self.layout.box()
row = box.row(align=True)
row.prop(pprops, "geometry_attributes_expanded",
icon="TRIA_DOWN" if pprops.geometry_attributes_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Fluid Particle Attributes:")
if pprops.geometry_attributes_expanded:
if not vcu.is_blender_31():
column = box.column(align=True)
column.enabled = False
column.label(text="Geometry attribute features for fluid particles are only available in", icon='ERROR')
column.label(text="Blender 3.1 or later", icon='ERROR')
return
#
# Velocity Attributes
#
subbox = box.box()
row = subbox.row(align=True)
row.prop(pprops, "velocity_attributes_expanded",
icon="TRIA_DOWN" if pprops.velocity_attributes_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Velocity Based Attributes:")
if pprops.velocity_attributes_expanded:
column = subbox.column(align=True)
column.prop(pprops, "enable_fluid_particle_velocity_vector_attribute", text="Velocity Attributes")
column.prop(pprops, "enable_fluid_particle_speed_attribute", text="Speed Attributes")
column.prop(pprops, "enable_fluid_particle_vorticity_vector_attribute", text="Vorticity Attributes")
column.operator("flip_fluid_operators.helper_initialize_motion_blur")
else:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.prop(pprops, "enable_fluid_particle_velocity_vector_attribute", text="Velocity")
row.prop(pprops, "enable_fluid_particle_speed_attribute", text="Speed")
row.prop(pprops, "enable_fluid_particle_vorticity_vector_attribute", text="Vorticity")
#
# Color Attributes
#
subbox = box.box()
row = subbox.row(align=True)
row.prop(pprops, "color_attributes_expanded",
icon="TRIA_DOWN" if pprops.color_attributes_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Color and Mixing Attributes:")
if pprops.color_attributes_expanded:
column = subbox.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
column_left.prop(pprops, "enable_fluid_particle_color_attribute", text="Color Attributes")
column = subbox.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
column_left.enabled = pprops.enable_fluid_particle_color_attribute
column_left.prop(sprops, "enable_color_attribute_mixing", text="Enable Mixing")
column_right.enabled = pprops.enable_fluid_particle_color_attribute and sprops.enable_color_attribute_mixing
column_right.prop(sprops, "color_attribute_mixing_rate", text="Mix Rate", slider=True)
column = subbox.column(align=True)
column.enabled = pprops.enable_fluid_particle_color_attribute and sprops.enable_color_attribute_mixing
column.label(text="Mixing Mode:")
row = column.row(align=True)
row.enabled = pprops.enable_fluid_particle_color_attribute
row.prop(sprops, "color_attribute_mixing_mode", expand=True)
if sprops.color_attribute_mixing_mode == 'COLOR_MIXING_MODE_MIXBOX':
if not installation_utils.is_mixbox_supported():
column.label(text="Mixbox feature is not supported", icon="ERROR")
column.label(text="in this version of the FLIP Fluids Addon", icon="ERROR")
if installation_utils.is_mixbox_supported():
if installation_utils.is_mixbox_installation_complete():
column.label(text="Mixbox Plugin Status: Installed", icon="CHECKMARK")
else:
column.label(text="Install the Mixbox plugin in the", icon="INFO")
column.label(text="FLIP Fluids Addon preferences", icon="INFO")
column.operator(
"flip_fluid_operators.open_preferences",
text="Open Preferences", icon="PREFERENCES"
).view_mode = 'PREFERENCES_MENU_VIEW_MIXBOX'
else:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.prop(pprops, "enable_fluid_particle_color_attribute", text="Color")
row = row.row(align=True)
row.alignment = 'RIGHT'
row.enabled = pprops.enable_fluid_particle_color_attribute
row.prop(sprops, "enable_color_attribute_mixing", text="Mixing")
#
# Other Attributes
#
subbox = box.box()
row = subbox.row(align=True)
row.prop(pprops, "other_attributes_expanded",
icon="TRIA_DOWN" if pprops.other_attributes_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Other Attributes:")
if pprops.other_attributes_expanded:
column = subbox.column(align=True)
column.prop(pprops, "enable_fluid_particle_age_attribute", text="Age Attributes")
row = column.row(align=True)
row.prop(pprops, "enable_fluid_particle_lifetime_attribute", text="Lifetime Attributes")
row.prop(sprops, "lifetime_attribute_death_time")
column.prop(pprops, "enable_fluid_particle_whitewater_proximity_attribute", text="Whitewater Proximity Attributes")
column.prop(pprops, "enable_fluid_particle_source_id_attribute", text="Source ID Attributes")
row = column.row(align=True)
row.prop(pprops, "enable_fluid_particle_uid_attribute", text="UID Attributes")
row.prop(pprops, "enable_fluid_particle_uid_attribute_reuse", text="Reuse UIDs")
else:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.prop(pprops, "enable_fluid_particle_age_attribute", text="Age")
row.prop(pprops, "enable_fluid_particle_lifetime_attribute", text="Life")
row.prop(pprops, "enable_fluid_particle_whitewater_proximity_attribute", text="WW Prox.")
row.prop(pprops, "enable_fluid_particle_source_id_attribute", text="Source ID")
row.prop(pprops, "enable_fluid_particle_uid_attribute", text="UID")
class FLIPFLUID_PT_DomainTypeFluidParticlesPanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Particles"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
if vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN" and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
dprops = obj.flip_fluid.domain
pprops = obj.flip_fluid.domain.particles
sprops = obj.flip_fluid.domain.surface
prefs = vcu.get_addon_preferences()
if not prefs.is_extra_features_enabled():
warn_box = self.layout.box()
warn_column = warn_box.column(align=True)
warn_column.enabled = True
warn_column.label(text=" This feature is affected by a current bug in Blender.", icon='ERROR')
warn_column.label(text=" The Extra Features option must be enabled in preferences")
warn_column.label(text=" to use this feature.")
warn_column.separator()
warn_column.prop(prefs, "enable_extra_features", text="Enable Extra Features 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"
return
box = self.layout.box()
row = box.row(align=True)
row.prop(pprops, "fluid_particles_expanded",
icon="TRIA_DOWN" if pprops.fluid_particles_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Fluid Particle Export:")
if not pprops.fluid_particles_expanded:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.prop(pprops, "enable_fluid_particle_output")
if pprops.fluid_particles_expanded:
column = box.column(align=True)
column.prop(pprops, "enable_fluid_particle_output")
subbox = column.box()
subbox.enabled = pprops.enable_fluid_particle_output
subcolumn = subbox.column(align=True)
subcolumn.prop(pprops, "fluid_particle_output_amount", slider=True)
subcolumn.prop(pprops, "enable_fluid_particle_surface_output")
subcolumn.prop(pprops, "enable_fluid_particle_boundary_output")
subcolumn.prop(pprops, "enable_fluid_particle_interior_output")
subcolumn.separator()
subcolumn.prop(pprops, "fluid_particle_source_id_blacklist", text="Skip Particles With Source ID Value")
box = self.layout.box()
row = box.row(align=True)
row.prop(pprops, "fluid_particle_generation_expanded",
icon="TRIA_DOWN" if pprops.fluid_particle_generation_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Fluid Particle Generation:")
aprops = dprops.advanced
if not pprops.fluid_particle_generation_expanded:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.prop(aprops, "jitter_surface_particles")
if pprops.fluid_particle_generation_expanded:
column = box.column()
row = column.row(align=True)
row.prop(aprops, "particle_jitter_factor", slider=True)
row.prop(aprops, "jitter_surface_particles")
_draw_fluid_particle_display_settings(self, context)
_draw_geometry_attributes_menu(self, context)
self.layout.separator()
column = self.layout.column(align=True)
column.operator("flip_fluid_operators.helper_delete_particle_objects", icon="X")
def register():
bpy.utils.register_class(FLIPFLUID_PT_DomainTypeFluidParticlesPanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypeFluidParticlesPanel)
@@ -0,0 +1,282 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..presets import preset_library
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_DomainTypePresetsPanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Presets"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
if vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN"
def draw_preset_selector(self, context):
obj = vcu.get_active_object(context)
dprops = obj.flip_fluid.domain
preprops = dprops.presets
column = self.layout.column()
column.prop(preprops, "enable_presets")
column = self.layout.column()
column.enabled = preprops.enable_presets
column.label(text="Preset Package:")
column.prop(preprops, "current_package", text="")
current_package_info = \
preset_library.package_identifier_to_info(preprops.current_package)
if current_package_info["use_custom_icons"]:
column.label(text="Preset:")
row = column.row()
row.prop(preprops, "current_preset", text="")
if preprops.current_preset != "PRESET_NONE":
row.operator(
"flip_fluid_operators.preset_display_info",
text="",
icon="INFO",
)
row = column.row()
subcol = row.column()
subcol.scale_y = 6
subcol.operator(
"flip_fluid_operators.preset_select_previous",
text="",
icon="TRIA_LEFT"
)
subcol = row.column()
subcol.template_icon_view(preprops, "current_preset", show_labels=True)
subcol = row.column()
subcol.scale_y = 6
subcol.operator(
"flip_fluid_operators.preset_select_next",
text="",
icon="TRIA_RIGHT"
)
split = column.split()
column_left = split.column()
column_right = split.column()
row_left = column_left.row()
row_right = column_right.row()
row_left.alignment = 'LEFT'
row_left.prop(preprops, "preview_preset", text="Auto Assign Preset")
if preprops.current_preset != "PRESET_NONE":
is_on_stack = preprops.preset_stack.is_preset_in_stack(preprops.current_preset)
op_text = "Added to Stack" if is_on_stack else "Add to Stack"
row_right.enabled = not is_on_stack
row_right.alignment = 'RIGHT'
row_right.operator(
"flip_fluid_operators.preset_add_to_stack",
text=op_text,
)
else:
column.label(text="Preset:")
row = column.row()
row.prop(preprops, "current_preset", text="")
if preprops.current_preset != "PRESET_NONE":
row.operator(
"flip_fluid_operators.preset_display_info",
text="",
icon="INFO",
)
split = column.split()
column_left = split.column()
column_right = split.column()
row_left = column_left.row()
row_right = column_right.row()
row_left.alignment = 'LEFT'
row_left.prop(preprops, "preview_preset", text="Auto Assign Preset")
if preprops.current_preset != "PRESET_NONE":
is_on_stack = preprops.preset_stack.is_preset_in_stack(preprops.current_preset)
op_text = "Added to Stack" if is_on_stack else "Add to Stack"
row_right.enabled = not is_on_stack
row_right.alignment = 'RIGHT'
row_right.operator(
"flip_fluid_operators.preset_add_to_stack",
text=op_text,
)
def draw_preset_stack(self, context):
obj = vcu.get_active_object(context)
preprops = obj.flip_fluid.domain.presets
column = self.layout.column()
column.enabled = preprops.enable_presets
column.separator()
column.separator()
box = column.box()
box.label(text="Preset Stack:")
column = box.column(align=True)
preset_icons = preset_library.get_custom_icons()
if len(preprops.preset_stack.preset_stack) == 0:
column.label(text="No presets loaded...")
for pidx,p in enumerate(preprops.preset_stack.preset_stack):
info = preset_library.preset_identifier_to_info(p.identifier)
subbox = column.box()
split = vcu.ui_split(subbox, factor=0.5, align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
row_left = column_left.row(align=True)
row_right = column_right.row()
if "icon" in info and info['icon'] in preset_icons:
row_left.label(text=info['name'], icon_value=preset_icons.get(info['icon']).icon_id)
else:
row_left.label(text=" "*5 + info['name'])
row_right.alignment='RIGHT'
row_right.operator(
"flip_fluid_operators.preset_display_info",
text="",
icon="INFO",
emboss=False,
).identifier = p.identifier
row_right.operator(
"flip_fluid_operators.preset_apply_remove_from_stack",
).stack_index=pidx
row_right.prop(p, "is_enabled", text="", icon='RESTRICT_VIEW_OFF')
row_right = row_right.row(align=True)
row_right.operator(
"flip_fluid_operators.preset_move_up_in_stack",
text="",
icon="TRIA_UP",
).stack_index=pidx
row_right.operator(
"flip_fluid_operators.preset_move_down_in_stack",
text="",
icon="TRIA_DOWN",
).stack_index=pidx
row_right = row_right.row()
row_right.operator(
"flip_fluid_operators.preset_remove_from_stack",
text="",
icon="X",
emboss=False,
).stack_index=pidx
def draw_preset_manager(self, context):
obj = vcu.get_active_object(context)
preprops = obj.flip_fluid.domain.presets
self.layout.separator()
box = self.layout.box()
row = box.row()
row.prop(preprops, "preset_manager_expanded",
icon="TRIA_DOWN" if preprops.preset_manager_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Preset Manager")
if preprops.preset_manager_expanded:
column = box.column(align=True)
column.label(text="Package Operators:")
column.operator("flip_fluid_operators.preset_create_new_package")
column.operator("flip_fluid_operators.preset_delete_package")
column.separator()
# These operators need to be reworked to support both 2.79 and 2.80
"""
split = column.split(align=True)
split_column = split.column(align=True)
split_column.enabled = bool(preprops.import_package_settings.package_filepath)
split_column.operator("flip_fluid_operators.preset_import_package")
split_column = split.column(align=True)
row = split_column.row(align=True)
row.prop(preprops.import_package_settings, "package_filepath")
row.operator("flip_fluid_operators.select_package_zipfile", text="", icon=vcu.get_file_folder_icon())
row = column.row(align=True)
row.operator("flip_fluid_operators.preset_export_package")
row.prop(preprops.export_package_settings, "export_directory")
"""
column = box.column(align=True)
column.label(text="Preset Operators:")
column.operator("flip_fluid_operators.preset_create_new_preset")
column.operator("flip_fluid_operators.preset_delete_preset")
column.operator("flip_fluid_operators.preset_edit_preset")
def draw_default_settings_operators(self, context):
self.layout.separator()
box = self.layout.box()
column = box.column(align=True)
column.label(text="Default Settings:")
split = vcu.ui_split(column, align=True, factor=0.66)
column = split.column(align=True)
column.operator(
"flip_fluid_operators.preset_save_user_default_settings",
text="Save",
icon='FILE_TICK'
)
column = split.column(align=True)
column.operator(
"flip_fluid_operators.preset_restore_system_default_settings",
text="Restore",
)
def draw(self, context):
if not preset_library.get_user_package_info_list():
self.layout.label(text="This feature is missing data and will be disabled.")
self.layout.label(text="Please contact the developers if you think this is an error.")
return
self.draw_preset_selector(context)
self.draw_preset_stack(context)
self.draw_preset_manager(context)
self.draw_default_settings_operators(context)
def register():
preferences = vcu.get_addon_preferences()
if preferences.enable_presets:
bpy.utils.register_class(FLIPFLUID_PT_DomainTypePresetsPanel)
def unregister():
try:
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypePresetsPanel)
except:
pass
@@ -0,0 +1,714 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy, math
from ..utils import export_utils
from ..utils import version_compatibility_utils as vcu
DRAW_OBJECT_FLIP_TYPE_PROPERTY = True
def draw_bake_operator_UI_element(context, ui_box):
dprops = context.scene.flip_fluid.get_domain_properties()
if dprops is None:
return
bakeprops = dprops.bake
simprops = dprops.simulation
if not bakeprops.is_simulation_running:
if bakeprops.is_autosave_available:
frame_str = str(bakeprops.autosave_frame + 1)
if simprops.enable_savestates:
frame_str = str(int(simprops.selected_savestate) + 1)
operator_text = "Resume Baking (from frame " + frame_str + ")"
else:
operator_text = "Bake"
elif bakeprops.is_export_operator_running:
progress = bakeprops.export_progress
stage = bakeprops.export_stage
pct_string = str(round(progress * 100, 1)) + "%"
if stage == 'STATIC':
operator_text = "Exporting static data... " + pct_string
elif stage == 'KEYFRAMED':
operator_text = "Exporting keyframe data... " + pct_string
elif stage == 'ANIMATED':
operator_text = "Exporting animated data... " + pct_string
else:
operator_text = "Exporting data... "
elif not bakeprops.is_bake_initialized:
operator_text = "Baking in progress... initializing"
elif bakeprops.is_bake_cancelled:
if bakeprops.is_safe_to_exit:
safety_str = "Safe to quit Blender"
else:
safety_str = "Do NOT quit Blender"
operator_text = "Cancelling... " + safety_str
else:
num_frames = simprops.frame_end - simprops.frame_start + 1
frame_progress_string = str(bakeprops.num_baked_frames) + " / " + str(num_frames)
pct_string = str(round((bakeprops.num_baked_frames / num_frames) * 100, 1)) + "%"
frames_string = pct_string + " (" + frame_progress_string + ")"
frames_string += " (" + str(simprops.frame_start + bakeprops.num_baked_frames) + ")"
operator_text = "Baking in progress... " + frames_string
column = ui_box.column(align=True)
if not bakeprops.is_simulation_running and bakeprops.is_autosave_available:
split = vcu.ui_split(column, factor=0.75, align=True)
column_left = split.column(align=True)
column_left.operator("flip_fluid_operators.bake_fluid_simulation",
text=operator_text)
column_right = split.column(align=True)
column_right.alert = True
column_right.operator("flip_fluid_operators.reset_bake",
text="Reset")
if simprops.enable_savestates:
if simprops.get_num_savestate_enums() < 100:
column_left.prop(simprops, "selected_savestate", text="")
else:
column_right.alert = False
row = column_left.row(align=True)
row.prop(simprops, "selected_savestate_int", text="Resume from frame")
column_right.label(text=simprops.selected_savestate_int_label)
else:
column.operator("flip_fluid_operators.bake_fluid_simulation",
text=operator_text)
if bakeprops.is_simulation_running:
column.operator("flip_fluid_operators.cancel_bake_fluid_simulation",
text="Stop / Pause")
if dprops.bake.is_simulation_running:
row = column.row()
row.alignment = "RIGHT"
if dprops.stats.is_estimated_time_remaining_available:
row.label(text="Estimated Time Remaining: " + dprops.stats.get_time_remaining_string(context))
else:
row.label(text="Calculating time remaining...")
def draw_bake_operator(self, context, box):
box.label(text="Bake Simulation:")
draw_bake_operator_UI_element(context, box)
def draw_more_bake_settings(self, context, box):
obj = vcu.get_active_object(context)
dprops = obj.flip_fluid.domain
sprops = dprops.simulation
row = box.row(align=True)
row.prop(sprops, "more_bake_settings_expanded",
icon="TRIA_DOWN" if sprops.more_bake_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="More Bake Settings")
if not sprops.more_bake_settings_expanded:
return
subbox = box.box()
column = subbox.column(align=True)
column.label(text="Frame Range:")
row = column.row()
row.prop(sprops, "frame_range_mode", expand=True)
row = column.row(align=True)
if sprops.frame_range_mode == 'FRAME_RANGE_TIMELINE':
row_left = row.row(align=True)
row_right = row.row(align=True)
if dprops.bake.is_autosave_available:
row_left.enabled = False
row_left.prop(dprops.bake, "original_frame_start")
else:
row_left.prop(context.scene, "frame_start")
row_right.prop(context.scene, "frame_end")
else:
row_left = row.row(align=True)
row_right = row.row(align=True)
if dprops.bake.is_autosave_available:
row_left.enabled = False
row_left.prop(dprops.bake, "original_frame_start")
else:
row_left.prop(sprops.frame_range_custom, "value_min")
row_right.prop(sprops.frame_range_custom, "value_max")
subbox = box.box()
column = subbox.column(align=True)
column.label(text="Settings and Mesh Export:")
column.prop(sprops, "update_settings_on_resume")
indent_str = 5 * " "
subbox = subbox.box()
row = subbox.row(align=True)
row.prop(sprops, "skip_mesh_reexport_expanded",
icon="TRIA_DOWN" if sprops.skip_mesh_reexport_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Skip Mesh Re-Export:")
if sprops.skip_mesh_reexport_expanded:
flip_props = context.scene.flip_fluid
flip_objects = (flip_props.get_obstacle_objects() +
flip_props.get_fluid_objects() +
flip_props.get_inflow_objects() +
flip_props.get_outflow_objects() +
flip_props.get_force_field_objects())
num_flip_objects = len(flip_objects)
flip_object_count_limit = 128
if num_flip_objects > flip_object_count_limit:
column = subbox.column()
column.alert = True
column.label(text="Menu Unavailable", icon="ERROR")
column.label(text="This menu is only available in scenes containing " + str(flip_object_count_limit) + " FLIP objects or fewer", icon="ERROR")
column.label(text="Current number of FLIP objects: " + str(num_flip_objects), icon="ERROR")
else:
column = subbox.column()
column.label(text="Object Motion Type:")
row = column.row(align=True)
row.prop(sprops, "mesh_reexport_type_filter", expand=True)
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"
elif sprops.mesh_reexport_type_filter == 'MOTION_FILTER_TYPE_STATIC':
filtered_objects = [x for x in flip_objects if _get_object_motion_type(self, x) == 'STATIC']
motion_type_string = "static"
elif sprops.mesh_reexport_type_filter == 'MOTION_FILTER_TYPE_KEYFRAMED':
filtered_objects = [x for x in flip_objects if _get_object_motion_type(self, x) == 'KEYFRAMED']
motion_type_string = "keyframed"
elif sprops.mesh_reexport_type_filter == 'MOTION_FILTER_TYPE_ANIMATED':
filtered_objects = [x for x in flip_objects if _get_object_motion_type(self, x) == 'ANIMATED']
motion_type_string = "animated"
if len(filtered_objects) == 0:
column.label(text=indent_str + "No " + motion_type_string + " objects found...")
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)
column_left.label(text="")
column_left.label(text="Object")
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()
row = op_box.row(align=True)
row.alignment = 'LEFT'
row.operator("flip_fluid_operators.helper_batch_skip_reexport", icon='CHECKBOX_HLT', text="").enable_state = True
row.operator("flip_fluid_operators.helper_batch_skip_reexport", icon='CHECKBOX_DEHLT', text="").enable_state = False
row.label(text="All")
column_right.label(text="Force Export")
column_right.label(text="On Next Bake")
op_box = column_right.box()
row = op_box.row(align=True)
row.alignment = 'LEFT'
row.operator("flip_fluid_operators.helper_batch_force_reexport", icon='CHECKBOX_HLT', text="").enable_state = True
row.operator("flip_fluid_operators.helper_batch_force_reexport", icon='CHECKBOX_DEHLT', text="").enable_state = False
row.label(text="All")
is_export_hint_enabled = not vcu.get_addon_preferences().dismiss_export_animated_mesh_parented_relation_hint
for ob in filtered_objects:
pgroup = ob.flip_fluid.get_property_group()
column_left_row = column_left.row(align=True)
column_left_row.alignment = 'LEFT'
column_left_row.label(text=ob.name, icon="OBJECT_DATA")
is_child_object = ob.parent is not None
if is_export_hint_enabled and not pgroup.export_animated_mesh and is_child_object:
column_left_row.prop(context.scene.flip_fluid_helper, "export_animated_mesh_parent_tooltip",
icon="INFO", emboss=False, text=""
)
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)
subbox = box.box()
column = subbox.column(align=True)
column.label(text="Savestates:")
column.prop(sprops, "enable_savestates")
column = subbox.column(align=True)
column.enabled = sprops.enable_savestates
split = column.split()
column = split.column()
row = column.row()
row.alignment = 'RIGHT'
row.label(text="Generate savestate every")
column = split.column()
split = column.split()
column = split.column()
row = column.row(align=True)
row.prop(sprops, "savestate_interval", text="")
row.label(text="frames")
column = subbox.column(align=True)
column.enabled = sprops.enable_savestates
column.prop(sprops, "delete_outdated_savestates")
column.prop(sprops, "delete_outdated_meshes")
def draw_resolution_settings(self, context, master_column):
obj = vcu.get_active_object(context)
dprops = obj.flip_fluid.domain
sprops = dprops.simulation
wprops = dprops.world
aprops = dprops.advanced
box = master_column.box()
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
row = column_left.row(align=True)
row.prop(sprops, "simulation_resolution_expanded",
icon="TRIA_DOWN" if sprops.simulation_resolution_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Grid Resolution:")
if not sprops.simulation_resolution_expanded:
row = column_right.row(align=True)
row.prop(sprops, "resolution", text="Resolution")
else:
column = box.column(align=True)
split = vcu.ui_split(column, factor=0.5, align=True)
column_left = split.column(align=True)
column_left.enabled = not sprops.lock_cell_size
column_left.prop(sprops, "resolution")
column_right = split.column(align=True)
column_right.enabled = not sprops.auto_preview_resolution
column_right.prop(sprops, "preview_resolution")
column = box.column(align=True)
split = vcu.ui_split(column, factor=0.5)
column_left = split.column(align=True)
column_right = split.column(align=True)
column_left.prop(sprops, "lock_cell_size")
column_right.prop(sprops, "auto_preview_resolution", text="Use Recommended")
if not dprops.bake.is_simulation_running and sprops.is_current_grid_upscaled():
old_resolution = max(sprops.savestate_isize, sprops.savestate_jsize, sprops.savestate_ksize)
indent = 7
subbox = box.box()
column = subbox.column(align=True)
column.label(text="Increased resolution detected")
row = column.row()
row.prop(sprops, "upscale_resolution_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text="Simulation will be upscaled on resume.")
column.label(text=indent*" " + " Cache resolution: " + str(old_resolution))
column.label(text=indent*" " + " Current resolution: " + str(sprops.resolution))
box = master_column.box()
row = box.row(align=True)
row.prop(sprops, "grid_info_expanded",
icon="TRIA_DOWN" if sprops.grid_info_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Grid Info:")
row = row.row()
row.alignment = 'RIGHT'
row.prop(dprops.debug, "grid_display_mode", text="")
row.prop(dprops.debug, "display_simulation_grid", text="Visualize Grid")
if sprops.grid_info_expanded:
column = box.column(align=True)
split = vcu.ui_split(column, factor=0.05, align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
split = vcu.ui_split(column_right, factor=0.4, align=True)
column_middle = split.column(align=True)
column_right = split.column(align=True)
if dprops.debug.grid_display_mode == 'GRID_DISPLAY_SIMULATION':
column_left.prop(sprops, "grid_voxels_tooltip", icon="QUESTION", emboss=False, text="")
column_left.prop(sprops, "grid_dimensions_tooltip", icon="QUESTION", emboss=False, text="")
column_left.prop(sprops, "grid_voxel_size_tooltip", icon="QUESTION", emboss=False, text="")
column_left.prop(sprops, "grid_voxel_count_tooltip", icon="QUESTION", emboss=False, text="")
row = column_middle.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Voxels 3D =")
row = column_middle.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Dimensions 3D =")
row = column_middle.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Voxel Size =")
row = column_middle.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Voxel Count =")
isize, jsize, ksize, dx = sprops.get_simulation_grid_dimensions()
if dprops.debug.grid_display_mode == 'GRID_DISPLAY_PREVIEW':
isize, jsize, ksize, dx = sprops.get_simulation_grid_dimensions(resolution=sprops.preview_resolution)
if dprops.debug.grid_display_mode == 'GRID_DISPLAY_MESH':
subdivision_multiplier = 1 + dprops.surface.subdivisions
isize *= subdivision_multiplier
jsize *= subdivision_multiplier
ksize *= subdivision_multiplier
dx /= subdivision_multiplier
elif dprops.debug.grid_display_mode == 'GRID_DISPLAY_FORCE_FIELD':
reduction_multiplier = 1
if dprops.world.force_field_resolution == 'FORCE_FIELD_RESOLUTION_HIGH':
reduction_multiplier = 2
elif dprops.world.force_field_resolution == 'FORCE_FIELD_RESOLUTION_NORMAL':
reduction_multiplier = 3
elif dprops.world.force_field_resolution == 'FORCE_FIELD_RESOLUTION_LOW':
reduction_multiplier = 4
isize = math.ceil(isize / reduction_multiplier)
jsize = math.ceil(jsize / reduction_multiplier)
ksize = math.ceil(ksize / reduction_multiplier)
dx *= reduction_multiplier
voxel_str = str(isize) + " x " + str(jsize) + " x " + str(ksize)
xdims, ydims, zdims = wprops.get_simulation_dimensions(context)
xdims_str = '{:.2f}'.format(round(xdims, 2))
ydims_str = '{:.2f}'.format(round(ydims, 2))
zdims_str = '{:.2f}'.format(round(zdims, 2))
xdims_str = xdims_str.rstrip("0").rstrip(".") + "m"
ydims_str = ydims_str.rstrip("0").rstrip(".") + "m"
zdims_str = zdims_str.rstrip("0").rstrip(".") + "m"
dimensions_str = str(xdims_str) + " x " + str(ydims_str) + " x " + str(zdims_str)
display_dx = dx
suffix = "m"
if int(display_dx) == 0:
display_dx *= 100
suffix = "cm"
if int(display_dx) == 0:
display_dx *= 10
suffix = "mm"
voxel_size_str = '{:.3f}'.format(round(display_dx, 3)) + " " + suffix
voxel_count_str = '{:,}'.format(isize * jsize * ksize).replace(',', ' ')
column_right.label(text=voxel_str)
column_right.label(text=dimensions_str)
column_right.label(text=voxel_size_str)
column_right.label(text=voxel_count_str)
box = master_column.box()
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
row = column_left.row(align=True)
row.prop(sprops, "simulation_method_expanded",
icon="TRIA_DOWN" if sprops.simulation_method_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Simulation Method:")
if not sprops.simulation_method_expanded:
row = column_right.row()
row.prop(aprops, "velocity_transfer_method", expand=True)
pass
else:
column = box.column(align=True)
row = column.row(align=True)
row.prop(aprops, "velocity_transfer_method", expand=True)
row = column.row(align=True)
if aprops.velocity_transfer_method == 'VELOCITY_TRANSFER_METHOD_FLIP':
row.prop(aprops, "PICFLIP_ratio", slider=True)
else:
row.label(text="")
box = master_column.box()
row = box.row(align=True)
row.prop(sprops, "world_scale_expanded",
icon="TRIA_DOWN" if sprops.world_scale_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
xdims, ydims, zdims = wprops.get_simulation_dimensions(context)
xdims_str = '{:.2f}'.format(round(xdims, 2))
ydims_str = '{:.2f}'.format(round(ydims, 2))
zdims_str = '{:.2f}'.format(round(zdims, 2))
xdims_str = xdims_str.rstrip("0").rstrip(".") + "m"
ydims_str = ydims_str.rstrip("0").rstrip(".") + "m"
zdims_str = zdims_str.rstrip("0").rstrip(".") + "m"
dimensions_str = str(xdims_str) + " x " + str(ydims_str) + " x " + str(zdims_str)
row.label(text="World Scale:")
row = row.row(align=True)
row.alignment = 'LEFT'
row.label(text=dimensions_str)
if not sprops.world_scale_expanded:
pass
else:
row = box.row(align=True)
row.prop(wprops, "world_scale_mode", expand=True)
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column()
column_right = split.column()
if wprops.world_scale_mode == 'WORLD_SCALE_MODE_RELATIVE':
row = column_left.row(align=True)
row.alignment = 'RIGHT'
row.label(text="1 Blender Unit = ")
column_right.prop(wprops, "world_scale_relative")
else:
row = column_left.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Domain Length = ")
column_right.prop(wprops, "world_scale_absolute")
box = master_column.box()
row = box.row(align=True)
row.prop(sprops, "boundary_collisions_expanded",
icon="TRIA_DOWN" if sprops.boundary_collisions_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Domain Boundary Collisions:")
if all(sprops.fluid_boundary_collisions):
boundary_collision_status_str = "Closed"
elif not any(sprops.fluid_boundary_collisions):
boundary_collision_status_str = "Open"
else:
boundary_collision_status_str = "Mixed"
row = row.row(align=True)
row.alignment = 'RIGHT'
row.label(text=boundary_collision_status_str)
if not sprops.boundary_collisions_expanded:
pass
else:
column = box.column(align=True)
row = column.row(align=True)
row.prop(sprops, "fluid_boundary_collisions", index=0, text="X ")
row.prop(sprops, "fluid_boundary_collisions", index=1, text="X+")
row = column.row(align=True)
row.prop(sprops, "fluid_boundary_collisions", index=2, text="Y ")
row.prop(sprops, "fluid_boundary_collisions", index=3, text="Y+")
row = column.row(align=True)
row.prop(sprops, "fluid_boundary_collisions", index=4, text="Z ")
row.prop(sprops, "fluid_boundary_collisions", index=5, text="Z+")
column.prop(sprops, "fluid_open_boundary_width", slider=True)
def _get_object_motion_type(self, obj):
props = obj.flip_fluid.get_property_group()
if hasattr(props, 'export_animated_mesh') and props.export_animated_mesh:
return 'ANIMATED'
if export_utils.is_object_keyframe_animated(obj):
return 'KEYFRAMED'
return 'STATIC'
def draw_time_settings(self, context, box):
obj = vcu.get_active_object(context)
sprops = obj.flip_fluid.domain.simulation
row = box.row(align=True)
row.prop(sprops, "frame_rate_and_time_scale_expanded",
icon="TRIA_DOWN" if sprops.frame_rate_and_time_scale_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Frame Rate and Time Scale:")
if not sprops.frame_rate_and_time_scale_expanded:
fps_str = "{:.2f}".format(sprops.get_frame_rate()) + " FPS"
row = row.row(align =True)
row.alignment = 'RIGHT'
row.label(text=fps_str)
return
column = box.column(align=True)
column.label(text="Frame Rate:")
row = column.row(align=True)
row.prop(sprops, "frame_rate_mode", expand=True)
column = column.column(align=True)
column.enabled = sprops.frame_rate_mode == 'FRAME_RATE_MODE_CUSTOM'
if sprops.frame_rate_mode == 'FRAME_RATE_MODE_SCENE':
column.prop(context.scene.render, "fps", text="Scene Frame Rate")
else:
column.prop(sprops, "frame_rate_custom", text="Custom Frame Rate")
column = box.column(align=True)
column.label(text="Time Scale:")
row = column.row(align=True)
row.prop(sprops, "time_scale_mode", expand=True)
column = column.column(align=True)
if sprops.time_scale_mode == 'TIME_SCALE_MODE_CUSTOM':
column.prop(sprops, "time_scale", text="Custom Time Scale")
elif sprops.time_scale_mode == 'TIME_SCALE_MODE_RIGID_BODY':
if bpy.context.scene.rigidbody_world is not None:
column.prop(bpy.context.scene.rigidbody_world, "time_scale", text="Rigid Body Time Scale")
else:
row = column.row(align=True)
row.label(text="No Rigid Body World: ")
row.operator("rigidbody.world_add")
elif sprops.time_scale_mode == 'TIME_SCALE_MODE_SOFT_BODY':
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
column_left.label(text="Soft Body Object:")
column_right.prop(sprops, "time_scale_object_soft_body", text="")
row = column.row(align=True)
soft_body_modifier = sprops.get_selected_time_scale_object_soft_body_modifier()
if soft_body_modifier is not None:
row.prop(soft_body_modifier.settings, "speed", text="Soft Body Time Scale")
else:
if sprops.time_scale_object_soft_body is not None:
row.label(text="No soft body simulation found on object")
else:
row.label(text="No soft body object selected")
elif sprops.time_scale_mode == 'TIME_SCALE_MODE_CLOTH':
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
column_left.label(text="Cloth Object:")
column_right.prop(sprops, "time_scale_object_cloth", text="")
row = column.row(align=True)
cloth_modifier = sprops.get_selected_time_scale_object_cloth_modifier()
if cloth_modifier is not None:
row.prop(cloth_modifier.settings, "time_scale", text="Cloth Time Scale")
else:
if sprops.time_scale_object_cloth is not None:
row.label(text="No cloth simulation found on object")
else:
row.label(text="No cloth object selected")
elif sprops.time_scale_mode == 'TIME_SCALE_MODE_FLUID':
if vcu.is_blender_282():
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
column_left.label(text="Fluid Domain Object:")
column_right.prop(sprops, "time_scale_object_fluid", text="")
row = column.row(align=True)
fluid_modifier = sprops.get_selected_time_scale_object_fluid_modifier()
if fluid_modifier is not None:
row.prop(fluid_modifier.domain_settings, "time_scale", text="Fluid Time Scale")
else:
if sprops.time_scale_object_fluid is not None:
row.label(text="No Mantaflow domain found on object")
else:
row.label(text="No Mantaflow domain object selected")
else:
column.label(text="Mantaflow fluid simulation time scale mode")
column.label(text="only supported in Blender 2.82 or later")
class FLIPFLUID_PT_DomainTypePanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Simulation"
@classmethod
def poll(cls, context):
if vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN" and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
obj_props = vcu.get_active_object(context).flip_fluid
dprops = context.scene.flip_fluid.get_domain_properties()
if dprops is None:
return
is_simulation_running = dprops.bake.is_simulation_running
global DRAW_OBJECT_FLIP_TYPE_PROPERTY
if DRAW_OBJECT_FLIP_TYPE_PROPERTY:
column = self.layout.column()
column.enabled = not is_simulation_running
column.prop(obj_props, "object_type")
box = self.layout.box()
draw_bake_operator(self, context, box)
draw_more_bake_settings(self, context, box)
column = self.layout.column()
draw_resolution_settings(self, context, column)
box = self.layout.box()
draw_time_settings(self, context, box)
def register():
bpy.utils.register_class(FLIPFLUID_PT_DomainTypePanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypePanel)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,398 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..ui import domain_display_ui
from ..utils import version_compatibility_utils as vcu
from ..utils import installation_utils
def _draw_fluid_surface_display_settings(self, context):
obj = vcu.get_active_object(context)
dprops = obj.flip_fluid.domain
domain_display_ui.draw_surface_display_settings(self, context, dprops.surface)
def _draw_geometry_attributes_menu(self, context):
obj = vcu.get_active_object(context)
sprops = obj.flip_fluid.domain.surface
rprops = obj.flip_fluid.domain.render
#
# Geometry Attributes
#
box = self.layout.box()
row = box.row(align=True)
row.alert = not sprops.enable_surface_mesh_generation
row.prop(sprops, "geometry_attributes_expanded",
icon="TRIA_DOWN" if sprops.geometry_attributes_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Surface Attributes:")
if sprops.geometry_attributes_expanded:
prefs = vcu.get_addon_preferences()
if not prefs.is_extra_features_enabled():
warn_box = box.box()
warn_column = warn_box.column(align=True)
warn_column.enabled = True
warn_column.label(text=" This feature is affected by a current bug in Blender.", icon='ERROR')
warn_column.label(text=" The Extra Features option must be enabled in preferences")
warn_column.label(text=" to use this feature.")
warn_column.separator()
warn_column.prop(prefs, "enable_extra_features", text="Enable Extra Features 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"
return
if not vcu.is_blender_293():
column = box.column(align=True)
column.enabled = False
column.label(text="Geometry attribute features for the fluid surface are only available in", icon='ERROR')
column.label(text="Blender 2.93 or later. Blender 3.1 or later recommended.", icon='ERROR')
return
is_preview_mode_enabled = rprops.viewport_display == 'DISPLAY_PREVIEW'
is_attributes_enabled = (
sprops.enable_velocity_vector_attribute or
sprops.enable_speed_attribute or
sprops.enable_vorticity_vector_attribute or
sprops.enable_color_attribute or
sprops.enable_age_attribute or
sprops.enable_lifetime_attribute or
sprops.enable_whitewater_proximity_attribute or
sprops.enable_source_id_attribute or
sprops.enable_viscosity_attribute
)
if is_preview_mode_enabled and is_attributes_enabled:
row = box.row(align=True)
row.alert = True
row.alignment = 'LEFT'
row.prop(sprops, "preview_mode_attributes_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text="Warning: Surface attributes will not be loaded in mesh Preview Mode")
#
# Velocity Attributes
#
subbox = box.box()
row = subbox.row(align=True)
row.prop(sprops, "velocity_attributes_expanded",
icon="TRIA_DOWN" if sprops.velocity_attributes_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Velocity Based Attributes:")
if sprops.velocity_attributes_expanded:
column = subbox.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
column_right.enabled = sprops.enable_velocity_vector_attribute or sprops.enable_speed_attribute or sprops.enable_vorticity_vector_attribute
column_left.prop(sprops, "enable_velocity_vector_attribute", text="Velocity Attributes")
# This option should always be on. Hiding option from UI, and always enabling this in the simulator.
#column_right.prop(sprops, "enable_velocity_vector_attribute_against_obstacles", text="Generate Against Obstacles")
column.prop(sprops, "enable_speed_attribute", text="Speed Attributes")
column.prop(sprops, "enable_vorticity_vector_attribute", text="Vorticity Attributes")
column.separator()
column.operator("flip_fluid_operators.helper_initialize_motion_blur")
else:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.prop(sprops, "enable_velocity_vector_attribute", text="Velocity")
row.prop(sprops, "enable_speed_attribute", text="Speed")
row.prop(sprops, "enable_vorticity_vector_attribute", text="Vorticity")
#
# Color Attributes
#
subbox = box.box()
row = subbox.row(align=True)
row.prop(sprops, "color_attributes_expanded",
icon="TRIA_DOWN" if sprops.color_attributes_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Color and Mixing Attributes:")
if sprops.color_attributes_expanded:
column = subbox.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
column_left.prop(sprops, "enable_color_attribute", text="Color Attributes")
if sprops.show_smoothing_radius_in_ui:
column_right.prop(sprops, "color_attribute_radius", text="Smoothing", slider=True)
column = subbox.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
column_left.enabled = sprops.enable_color_attribute
column_left.prop(sprops, "enable_color_attribute_mixing", text="Enable Mixing")
column_right.enabled = sprops.enable_color_attribute and sprops.enable_color_attribute_mixing
column_right.prop(sprops, "color_attribute_mixing_rate", text="Mix Rate", slider=True)
column = subbox.column(align=True)
column.enabled = sprops.enable_color_attribute and sprops.enable_color_attribute_mixing
column.label(text="Mixing Mode:")
row = column.row(align=True)
row.enabled = sprops.enable_color_attribute
row.prop(sprops, "color_attribute_mixing_mode", expand=True)
if sprops.color_attribute_mixing_mode == 'COLOR_MIXING_MODE_MIXBOX':
if not installation_utils.is_mixbox_supported():
column.label(text="Mixbox feature is not supported", icon="ERROR")
column.label(text="in this version of the FLIP Fluids Addon", icon="ERROR")
if installation_utils.is_mixbox_supported():
if installation_utils.is_mixbox_installation_complete():
column.label(text="Mixbox Plugin Status: Installed", icon="CHECKMARK")
else:
column.label(text="Install the Mixbox plugin in the", icon="INFO")
column.label(text="FLIP Fluids Addon preferences", icon="INFO")
column.operator(
"flip_fluid_operators.open_preferences",
text="Open Preferences", icon="PREFERENCES"
).view_mode = 'PREFERENCES_MENU_VIEW_MIXBOX'
else:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.prop(sprops, "enable_color_attribute", text="Color")
row = row.row(align=True)
row.alignment = 'RIGHT'
row.enabled = sprops.enable_color_attribute
row.prop(sprops, "enable_color_attribute_mixing", text="Mixing")
#
# Other Attributes
#
subbox = box.box()
row = subbox.row(align=True)
row.prop(sprops, "other_attributes_expanded",
icon="TRIA_DOWN" if sprops.other_attributes_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Other Attributes:")
if sprops.other_attributes_expanded:
column = subbox.column(align=True)
row = column.row(align=True)
row.prop(sprops, "enable_age_attribute", text="Age Attributes")
if sprops.show_smoothing_radius_in_ui:
row.prop(sprops, "age_attribute_radius", text="Smoothing", slider=True)
else:
row.label(text="")
row = column.row(align=True)
row.prop(sprops, "enable_lifetime_attribute", text="Lifetime Attributes")
row.prop(sprops, "lifetime_attribute_death_time")
if sprops.show_smoothing_radius_in_ui:
row.prop(sprops, "lifetime_attribute_radius", text="Smoothing", slider=True)
row = column.row(align=True)
row.prop(sprops, "enable_whitewater_proximity_attribute", text="Whitewater Proximity Attributes")
if sprops.show_smoothing_radius_in_ui:
row.prop(sprops, "whitewater_proximity_attribute_radius", text="Smoothing", slider=True)
column.prop(sprops, "enable_source_id_attribute", text="Source ID Attributes")
else:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.prop(sprops, "enable_age_attribute", text="Age")
row.prop(sprops, "enable_lifetime_attribute", text="Life")
row.prop(sprops, "enable_whitewater_proximity_attribute", text="WW Prox.")
row.prop(sprops, "enable_source_id_attribute", text="Source ID")
class FLIPFLUID_PT_DomainTypeFluidSurfacePanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Surface"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
if vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN" and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
sprops = obj.flip_fluid.domain.surface
column = self.layout.column(align=True)
column.prop(sprops, "enable_surface_mesh_generation")
box = self.layout.box()
row = box.row(align=True)
row.alert = not sprops.enable_surface_mesh_generation
row.prop(sprops, "surface_mesh_expanded",
icon="TRIA_DOWN" if sprops.surface_mesh_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Surface Mesh:")
if not sprops.surface_mesh_expanded:
info_text = "Subdivisions " + str(sprops.subdivisions) + " / "
info_text += "Scale " + "{:.2f}".format(sprops.particle_scale)
row = row.row(align=True)
row.alignment = 'RIGHT'
if sprops.particle_scale < 0.999:
row.alert = True
row.label(text=info_text)
if sprops.surface_mesh_expanded:
column = box.column(align=True)
column.prop(sprops, "subdivisions")
row = column.row(align=True)
if sprops.particle_scale < 0.999:
row.alert = True
row.prop(sprops, "particle_scale")
object_collection = vcu.get_scene_collection()
if vcu.is_blender_28():
search_group = "all_objects"
else:
search_group = "objects"
box = self.layout.box()
row = box.row(align=True)
row.alert = not sprops.enable_surface_mesh_generation
row.prop(sprops, "meshing_volume_expanded",
icon="TRIA_DOWN" if sprops.meshing_volume_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Meshing Volume:")
if not sprops.meshing_volume_expanded:
info_text = ""
if sprops.meshing_volume_mode == "MESHING_VOLUME_MODE_DOMAIN":
info_text = "Domain Volume"
elif sprops.meshing_volume_mode == "MESHING_VOLUME_MODE_OBJECT":
info_text = "Object Volume"
row = row.row(align=True)
row.alignment = 'RIGHT'
row.label(text=info_text)
if sprops.meshing_volume_expanded:
row = box.row(align=True)
row.prop(sprops, "meshing_volume_mode", expand=True)
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
column_right.enabled = sprops.meshing_volume_mode == "MESHING_VOLUME_MODE_OBJECT"
column_right.prop_search(sprops, "meshing_volume_object", object_collection, search_group, text="Object")
column_right.prop(sprops, "export_animated_meshing_volume_object")
box = self.layout.box()
row = box.row(align=True)
row.alert = not sprops.enable_surface_mesh_generation
row.prop(sprops, "meshing_against_boundary_expanded",
icon="TRIA_DOWN" if sprops.meshing_against_boundary_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Meshing Against Boundary:")
if not sprops.meshing_against_boundary_expanded:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.prop(sprops, "remove_mesh_near_domain", text="Remove")
if sprops.meshing_against_boundary_expanded:
column = box.column(align=True)
column.prop(sprops, "remove_mesh_near_domain")
column = box.column(align=True)
column.enabled = sprops.remove_mesh_near_domain
row = column.row(align=True)
row.prop(sprops, "remove_mesh_near_domain_sides", index=0, text="X ")
row.prop(sprops, "remove_mesh_near_domain_sides", index=1, text="X+")
row = column.row(align=True)
row.prop(sprops, "remove_mesh_near_domain_sides", index=2, text="Y ")
row.prop(sprops, "remove_mesh_near_domain_sides", index=3, text="Y+")
row = column.row(align=True)
row.prop(sprops, "remove_mesh_near_domain_sides", index=4, text="Z ")
row.prop(sprops, "remove_mesh_near_domain_sides", index=5, text="Z+")
column.prop(sprops, "remove_mesh_near_domain_distance")
box = self.layout.box()
row = box.row(align=True)
row.alert = not sprops.enable_surface_mesh_generation
row.prop(sprops, "meshing_against_obstacles_expanded",
icon="TRIA_DOWN" if sprops.meshing_against_obstacles_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Meshing Against Obstacles:")
if not sprops.meshing_against_obstacles_expanded:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.prop(sprops, "enable_meshing_offset", text="Enable ")
if sprops.meshing_against_obstacles_expanded:
column = box.column(align=True)
column.prop(sprops, "enable_meshing_offset")
row = box.row(align=True)
row.enabled = sprops.enable_meshing_offset
row.prop(sprops, "obstacle_meshing_mode", expand=True)
# Removed surface smoothing options. These are better set
# using a Blender smooth modifier.
"""
box = self.layout.box()
box.label(text="Smoothing:")
row = box.row(align=True)
row.prop(sprops, "smoothing_value")
row.prop(sprops, "smoothing_iterations")
"""
# Motion Blur is no longer supported
#column = self.layout.column(align=True)
#column.separator()
#column.prop(sprops, "generate_motion_blur_data")
_draw_fluid_surface_display_settings(self, context)
_draw_geometry_attributes_menu(self, context)
self.layout.separator()
column = self.layout.column(align=True)
column.operator("flip_fluid_operators.helper_delete_surface_objects", icon="X")
def register():
bpy.utils.register_class(FLIPFLUID_PT_DomainTypeFluidSurfacePanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypeFluidSurfacePanel)
@@ -0,0 +1,127 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..utils import version_compatibility_utils as vcu
from . import domain_simulation_ui
from . import domain_cache_ui
from . import domain_display_ui
from . import domain_particles_ui
from . import domain_surface_ui
from . import domain_whitewater_ui
from . import domain_world_ui
from . import domain_materials_ui
from . import domain_advanced_ui
from . import domain_debug_ui
from . import domain_stats_ui
class FLIPFLUID_PT_DomainTypeTabbedPanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Domain"
@classmethod
def poll(cls, context):
if not vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN" and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
obj_props = vcu.get_active_object(context).flip_fluid
dprops = context.scene.flip_fluid.get_domain_properties()
if dprops is None:
return
is_simulation_running = dprops.bake.is_simulation_running
column = self.layout.column()
column.enabled = not is_simulation_running
column.prop(obj_props, "object_type")
column.separator()
column = self.layout.column(align=True)
row = column.row(align=True)
row.prop_enum(dprops, "domain_settings_tabbed_panel_view", 'DOMAIN_SETTINGS_PANEL_SIMULATION')
row = column.row(align=True)
row.prop_enum(dprops, "domain_settings_tabbed_panel_view", 'DOMAIN_SETTINGS_PANEL_CACHE')
row.prop_enum(dprops, "domain_settings_tabbed_panel_view", 'DOMAIN_SETTINGS_PANEL_DISPLAY')
row.prop_enum(dprops, "domain_settings_tabbed_panel_view", 'DOMAIN_SETTINGS_PANEL_SURFACE')
row.prop_enum(dprops, "domain_settings_tabbed_panel_view", 'DOMAIN_SETTINGS_PANEL_PARTICLES')
row.prop_enum(dprops, "domain_settings_tabbed_panel_view", 'DOMAIN_SETTINGS_PANEL_WHITEWATER')
row = column.row(align=True)
row.prop_enum(dprops, "domain_settings_tabbed_panel_view", 'DOMAIN_SETTINGS_PANEL_WORLD')
row.prop_enum(dprops, "domain_settings_tabbed_panel_view", 'DOMAIN_SETTINGS_PANEL_MATERIALS')
row.prop_enum(dprops, "domain_settings_tabbed_panel_view", 'DOMAIN_SETTINGS_PANEL_ADVANCED')
row.prop_enum(dprops, "domain_settings_tabbed_panel_view", 'DOMAIN_SETTINGS_PANEL_DEBUG')
row.prop_enum(dprops, "domain_settings_tabbed_panel_view", 'DOMAIN_SETTINGS_PANEL_STATS')
column.separator()
selected_panel = dprops.domain_settings_tabbed_panel_view
if selected_panel == 'DOMAIN_SETTINGS_PANEL_SIMULATION':
column.label(text="FLIP Fluid Simulation")
domain_simulation_ui.DRAW_OBJECT_FLIP_TYPE_PROPERTY = False
try:
domain_simulation_ui.FLIPFLUID_PT_DomainTypePanel.draw(self, context)
except Exception as e:
domain_simulation_ui.DRAW_OBJECT_FLIP_TYPE_PROPERTY = True
raise Exception(e)
domain_simulation_ui.DRAW_OBJECT_FLIP_TYPE_PROPERTY = True
elif selected_panel == 'DOMAIN_SETTINGS_PANEL_CACHE':
column.label(text="FLIP Fluid Cache")
domain_cache_ui.FLIPFLUID_PT_DomainTypeCachePanel.draw(self, context)
elif selected_panel == 'DOMAIN_SETTINGS_PANEL_DISPLAY':
column.label(text="FLIP Fluid Display Settings")
domain_display_ui.FLIPFLUID_PT_DomainTypeDisplayPanel.draw(self, context)
elif selected_panel == 'DOMAIN_SETTINGS_PANEL_PARTICLES':
column.label(text="FLIP Fluid Particles")
domain_particles_ui.FLIPFLUID_PT_DomainTypeFluidParticlesPanel.draw(self, context)
elif selected_panel == 'DOMAIN_SETTINGS_PANEL_SURFACE':
column.label(text="FLIP Fluid Surface")
domain_surface_ui.FLIPFLUID_PT_DomainTypeFluidSurfacePanel.draw(self, context)
elif selected_panel == 'DOMAIN_SETTINGS_PANEL_WHITEWATER':
column.label(text="FLIP Fluid Whitewater")
domain_whitewater_ui.FLIPFLUID_PT_DomainTypeWhitewaterPanel.draw(self, context)
elif selected_panel == 'DOMAIN_SETTINGS_PANEL_WORLD':
column.label(text="FLIP Fluid World")
domain_world_ui.FLIPFLUID_PT_DomainTypeFluidWorldPanel.draw(self, context)
elif selected_panel == 'DOMAIN_SETTINGS_PANEL_MATERIALS':
column.label(text="FLIP Fluid Materials")
domain_materials_ui.FLIPFLUID_PT_DomainTypeMaterialsPanel.draw(self, context)
elif selected_panel == 'DOMAIN_SETTINGS_PANEL_ADVANCED':
column.label(text="FLIP Fluid Advanced Settings")
domain_advanced_ui.FLIPFLUID_PT_DomainTypeAdvancedPanel.draw(self, context)
elif selected_panel == 'DOMAIN_SETTINGS_PANEL_DEBUG':
column.label(text="FLIP Fluid Debug")
domain_debug_ui.FLIPFLUID_PT_DomainTypeDebugPanel.draw(self, context)
elif selected_panel == 'DOMAIN_SETTINGS_PANEL_STATS':
column.label(text="FLIP Fluid Stats")
domain_stats_ui.FLIPFLUID_PT_DomainTypeStatsPanel.draw(self, context)
def register():
bpy.utils.register_class(FLIPFLUID_PT_DomainTypeTabbedPanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypeTabbedPanel)
@@ -0,0 +1,86 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if "bpy" in locals():
import importlib
reloadable_modules = [
'domain_simulation_ui',
'domain_cache_ui',
'domain_display_ui',
'domain_surface_ui',
'domain_particles_ui',
'domain_whitewater_ui',
'domain_world_ui',
'domain_presets_ui',
'domain_materials_ui',
'domain_advanced_ui',
'domain_debug_ui',
'domain_stats_ui',
'domain_tabbed_ui',
]
for module_name in reloadable_modules:
if module_name in locals():
importlib.reload(locals()[module_name])
import bpy
from . import(
domain_simulation_ui,
domain_cache_ui,
domain_display_ui,
domain_surface_ui,
domain_particles_ui,
domain_whitewater_ui,
domain_world_ui,
domain_presets_ui,
domain_materials_ui,
domain_advanced_ui,
domain_debug_ui,
domain_stats_ui,
domain_tabbed_ui,
)
def register():
domain_simulation_ui.register()
domain_cache_ui.register()
domain_display_ui.register()
domain_surface_ui.register()
domain_particles_ui.register()
domain_whitewater_ui.register()
domain_world_ui.register()
domain_presets_ui.register()
domain_materials_ui.register()
domain_advanced_ui.register()
domain_debug_ui.register()
domain_stats_ui.register()
domain_tabbed_ui.register()
def unregister():
domain_simulation_ui.unregister()
domain_cache_ui.unregister()
domain_display_ui.unregister()
domain_surface_ui.unregister()
domain_particles_ui.unregister()
domain_whitewater_ui.unregister()
domain_world_ui.unregister()
domain_materials_ui.unregister()
domain_presets_ui.unregister()
domain_advanced_ui.unregister()
domain_debug_ui.unregister()
domain_stats_ui.unregister()
domain_tabbed_ui.unregister()
@@ -0,0 +1,471 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..operators import helper_operators
from . import domain_display_ui
from ..utils import version_compatibility_utils as vcu
def _draw_whitewater_display_settings(self, context):
obj = vcu.get_active_object(context)
dprops = obj.flip_fluid.domain
domain_display_ui.draw_whitewater_display_settings(self, context, dprops.whitewater)
def _draw_geometry_attributes_menu(self, context):
obj = vcu.get_active_object(context)
dprops = obj.flip_fluid.domain
wprops = dprops.whitewater
prefs = vcu.get_addon_preferences()
box = self.layout.box()
row = box.row(align=True)
row.prop(wprops, "geometry_attributes_expanded",
icon="TRIA_DOWN" if wprops.geometry_attributes_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Whitewater Attributes:")
if wprops.geometry_attributes_expanded:
if not prefs.is_extra_features_enabled():
warn_box = box.box()
warn_column = warn_box.column(align=True)
warn_column.enabled = True
warn_column.label(text=" This feature is affected by a current bug in Blender.", icon='ERROR')
warn_column.label(text=" The Extra Features option must be enabled in preferences")
warn_column.label(text=" to use this feature.")
warn_column.separator()
warn_column.prop(prefs, "enable_extra_features", text="Enable Extra Features 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"
return
column = box.column(align=True)
if not vcu.is_blender_31():
column.enabled = False
column.label(text="Geometry attribute features for whitewater are only available in", icon='ERROR')
column.label(text="Blender 3.1 or later", icon='ERROR')
return
column = box.column(align=True)
column.prop(wprops, "enable_velocity_vector_attribute")
column.prop(wprops, "enable_id_attribute")
column.prop(wprops, "enable_lifetime_attribute")
column.separator()
column.operator("flip_fluid_operators.helper_initialize_motion_blur")
else:
if not vcu.is_blender_31():
row = row.row(align=True)
row.enabled = False
row.alignment = 'RIGHT'
row.label(text="(Blender 3.1 or later required)")
return
if not prefs.is_extra_features_enabled():
return
row = row.row(align=True)
row.alignment = 'RIGHT'
row.prop(wprops, "enable_velocity_vector_attribute", text="Velocity")
row.prop(wprops, "enable_id_attribute", text="ID")
row.prop(wprops, "enable_lifetime_attribute", text="Lifetime")
class FLIPFLUID_PT_DomainTypeWhitewaterPanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid Whitewater"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
if vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN" and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
dprops = obj.flip_fluid.domain
wprops = dprops.whitewater
is_whitewater_enabled = wprops.enable_whitewater_simulation
show_advanced_whitewater = (wprops.whitewater_ui_mode == 'WHITEWATER_UI_MODE_ADVANCED')
highlight_advanced = wprops.highlight_advanced_settings
column = self.layout.column(align=True)
column.prop(wprops, "enable_whitewater_simulation")
column.separator()
box = self.layout.box()
box.enabled = is_whitewater_enabled
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
row = column_left.row(align=True)
row.prop(wprops, "settings_view_mode_expanded",
icon="TRIA_DOWN" if wprops.settings_view_mode_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Settings View Mode:")
if not wprops.settings_view_mode_expanded:
row = column_right.row(align=True)
row.alignment = 'RIGHT'
row.prop(wprops, "whitewater_ui_mode", expand=True)
if wprops.settings_view_mode_expanded:
column = box.column(align=True)
row = column.row()
row.prop(wprops, "whitewater_ui_mode", expand=True)
split = column.split()
split.column()
column_right = split.column()
column_right.enabled = show_advanced_whitewater
column_right.prop(wprops, "highlight_advanced_settings")
box = self.layout.box()
box.enabled = is_whitewater_enabled
row = box.row(align=True)
row.prop(wprops, "whitewater_simulation_particles_expanded",
icon="TRIA_DOWN" if wprops.whitewater_simulation_particles_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Whitewater Particles:")
if not wprops.whitewater_simulation_particles_expanded:
info_text = ""
enabled_particles = []
if wprops.enable_foam:
enabled_particles.append("Foam")
if wprops.enable_bubbles:
enabled_particles.append("Bubble")
if wprops.enable_spray:
enabled_particles.append("Spray")
if wprops.enable_dust:
enabled_particles.append("Dust")
if enabled_particles:
for ptype in enabled_particles:
info_text += ptype + "/"
info_text = info_text.rstrip("/")
else:
info_text = "None"
row = row.row(align=True)
row.alignment = 'RIGHT'
row.label(text=info_text)
if wprops.whitewater_simulation_particles_expanded:
column = box.column(align=True)
column.enabled = is_whitewater_enabled
row = column.row()
row.prop(wprops, "enable_foam")
row.prop(wprops, "enable_bubbles")
row.prop(wprops, "enable_spray")
row.prop(wprops, "enable_dust")
box = self.layout.box()
box.enabled = is_whitewater_enabled
row = box.row(align=True)
row.prop(wprops, "emitter_settings_expanded",
icon="TRIA_DOWN" if wprops.emitter_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Emitter Settings:")
if wprops.emitter_settings_expanded:
column = box.column(align=True)
column.prop(wprops, "enable_whitewater_emission")
if show_advanced_whitewater:
column = box.column(align=True)
column.alert = highlight_advanced
column.prop(wprops, "whitewater_emitter_generation_rate")
column = box.column(align=True)
column.prop(wprops, "wavecrest_emission_rate")
column.prop(wprops, "turbulence_emission_rate")
column = column.column(align=True)
column.enabled = wprops.enable_dust
column.prop(wprops, "dust_emission_rate")
column = box.column(align=True)
column.prop(wprops, "spray_emission_speed", slider=True)
if show_advanced_whitewater:
column = box.column(align=True)
row = column.row(align=True)
row.prop(wprops.min_max_whitewater_energy_speed, "value_min")
row.prop(wprops.min_max_whitewater_energy_speed, "value_max")
row = column.row(align=True)
row.alert = highlight_advanced
row.prop(wprops.min_max_whitewater_wavecrest_curvature, "value_min")
row.prop(wprops.min_max_whitewater_wavecrest_curvature, "value_max")
row = column.row(align=True)
row.alert = highlight_advanced
row.prop(wprops.min_max_whitewater_turbulence, "value_min")
row.prop(wprops.min_max_whitewater_turbulence, "value_max")
else:
column = box.column()
row = column.row(align=True)
row.prop(wprops.min_max_whitewater_energy_speed, "value_min")
row.prop(wprops.min_max_whitewater_energy_speed, "value_max")
column = box.column(align=True)
column.prop(wprops, "max_whitewater_particles")
if show_advanced_whitewater:
column = box.column(align=True)
column.alert = highlight_advanced
column.prop(wprops, "enable_whitewater_emission_near_boundary")
column = box.column(align=True)
column.enabled = wprops.enable_dust
column.prop(wprops, "enable_dust_emission_near_boundary", text="Enable dust emission near domain floor")
box = self.layout.box()
box.enabled = is_whitewater_enabled
row = box.row(align=True)
row.prop(wprops, "particle_settings_expanded",
icon="TRIA_DOWN" if wprops.particle_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Particle Behavior Settings:")
if wprops.particle_settings_expanded:
column = box.column()
column.label(text="Foam:")
row = column.row()
row.prop(wprops, "foam_advection_strength", text="Advection Strength", slider=True)
if show_advanced_whitewater:
row = column.row()
row.alert = highlight_advanced
row.prop(wprops, "foam_layer_depth", text="Depth", slider=True)
row = column.row()
row.alert = highlight_advanced
row.prop(wprops, "foam_layer_offset", text="Offset", slider=True)
column = box.column(align=True)
column.label(text="Bubble:")
column.prop(wprops, "bubble_drag_coefficient", text="Drag Coefficient", slider=True)
column.prop(wprops, "bubble_bouyancy_coefficient", text="Buoyancy Coefficient")
column = box.column(align=True)
column.label(text="Spray:")
column.prop(wprops, "spray_drag_coefficient", text="Drag Coefficient", slider=True)
column = box.column(align=True)
column.enabled = wprops.enable_dust
column.label(text="Dust:")
column.prop(wprops, "dust_drag_coefficient", text="Drag Coefficient", slider=True)
column.prop(wprops, "dust_bouyancy_coefficient", text="Buoyancy Coefficient")
column = box.column(align=True)
split = column.split()
column = split.column(align=True)
column.label(text="Lifespan:")
column.prop(wprops.min_max_whitewater_lifespan, "value_min", text="Min")
column.prop(wprops.min_max_whitewater_lifespan, "value_max", text="Max")
column.prop(wprops, "whitewater_lifespan_variance", text="Variance")
column = split.column(align=True)
column.label(text="Lifespan Modifiers:")
column.prop(wprops, "foam_lifespan_modifier", text="Foam")
column.prop(wprops, "bubble_lifespan_modifier", text="Bubble")
column.prop(wprops, "spray_lifespan_modifier", text="Spray")
column = column.column(align=True)
column.enabled = wprops.enable_dust
column.prop(wprops, "dust_lifespan_modifier", text="Dust")
box = self.layout.box()
row = box.row(align=True)
row.prop(wprops, "boundary_behaviour_settings_expanded",
icon="TRIA_DOWN" if wprops.boundary_behaviour_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Domain Boundary Collisions:")
if not wprops.boundary_behaviour_settings_expanded:
info_text = ""
if wprops.whitewater_boundary_collisions_mode == 'BOUNDARY_COLLISIONS_MODE_INHERIT':
info_text = "Inherit"
elif wprops.whitewater_boundary_collisions_mode == 'BOUNDARY_COLLISIONS_MODE_CUSTOM':
info_text = "Custom"
row = row.row(align=True)
row.alignment = 'RIGHT'
row.label(text=info_text)
if wprops.boundary_behaviour_settings_expanded:
column = box.column()
row = column.row(align=True)
row.prop(wprops, "whitewater_boundary_collisions_mode", expand=True)
if wprops.whitewater_boundary_collisions_mode == 'BOUNDARY_COLLISIONS_MODE_INHERIT':
sprops = dprops.simulation
column = box.column()
column.enabled = False
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(sprops, "fluid_boundary_collisions", index=0, text="X ")
row.prop(sprops, "fluid_boundary_collisions", index=1, text="X+")
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(sprops, "fluid_boundary_collisions", index=2, text="Y ")
row.prop(sprops, "fluid_boundary_collisions", index=3, text="Y+")
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(sprops, "fluid_boundary_collisions", index=4, text="Z ")
row.prop(sprops, "fluid_boundary_collisions", index=5, text="Z+")
else:
split = column.split(align=True)
column1 = split.column(align=True)
column2 = split.column(align=True)
column3 = split.column(align=True)
column4 = split.column(align=True)
column1.label(text="Foam:")
row = column1.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "foam_boundary_collisions", index=0, text="X ")
row.prop(wprops, "foam_boundary_collisions", index=1, text="X+")
row = column1.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "foam_boundary_collisions", index=2, text="Y ")
row.prop(wprops, "foam_boundary_collisions", index=3, text="Y+")
row = column1.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "foam_boundary_collisions", index=4, text="Z ")
row.prop(wprops, "foam_boundary_collisions", index=5, text="Z+")
column2.label(text="Bubble:")
row = column2.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "bubble_boundary_collisions", index=0, text="X ")
row.prop(wprops, "bubble_boundary_collisions", index=1, text="X+")
row = column2.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "bubble_boundary_collisions", index=2, text="Y ")
row.prop(wprops, "bubble_boundary_collisions", index=3, text="Y+")
row = column2.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "bubble_boundary_collisions", index=4, text="Z ")
row.prop(wprops, "bubble_boundary_collisions", index=5, text="Z+")
column3.label(text="Spray:")
row = column3.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "spray_boundary_collisions", index=0, text="X ")
row.prop(wprops, "spray_boundary_collisions", index=1, text="X+")
row = column3.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "spray_boundary_collisions", index=2, text="Y ")
row.prop(wprops, "spray_boundary_collisions", index=3, text="Y+")
row = column3.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "spray_boundary_collisions", index=4, text="Z ")
row.prop(wprops, "spray_boundary_collisions", index=5, text="Z+")
column4.label(text="Dust:")
row = column4.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "dust_boundary_collisions", index=0, text="X ")
row.prop(wprops, "dust_boundary_collisions", index=1, text="X+")
row = column4.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "dust_boundary_collisions", index=2, text="Y ")
row.prop(wprops, "dust_boundary_collisions", index=3, text="Y+")
row = column4.row(align=True)
row.alignment = 'LEFT'
row.prop(wprops, "dust_boundary_collisions", index=4, text="Z ")
row.prop(wprops, "dust_boundary_collisions", index=5, text="Z+")
box = self.layout.box()
box.enabled = is_whitewater_enabled
row = box.row(align=True)
row.prop(wprops, "obstacle_settings_expanded",
icon="TRIA_DOWN" if wprops.obstacle_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Obstacle Influence Settings:")
if wprops.obstacle_settings_expanded:
column = box.column(align=True)
# The following properties are probably set at reasonable values and
# are not needed by the user
"""
column.prop(wprops, "obstacle_influence_base_level", text="Base Level")
column.prop(wprops, "obstacle_influence_decay_rate", text="Decay Rate")
"""
obstacle_objects = context.scene.flip_fluid.get_obstacle_objects()
indent_str = 5 * " "
column.label(text="Obstacle Object Influence:")
if len(obstacle_objects) == 0:
column.label(text=indent_str + "No obstacle objects found...")
else:
split = vcu.ui_split(column, factor=0.25, align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
for ob in obstacle_objects:
pgroup = ob.flip_fluid.get_property_group()
column_left.label(text=ob.name, icon="OBJECT_DATA")
row = column_right.row()
row.alignment = 'RIGHT'
row.prop(pgroup, "whitewater_influence", text="influence")
row.prop(pgroup, "dust_emission_strength", text="dust emission")
_draw_whitewater_display_settings(self, context)
_draw_geometry_attributes_menu(self, context)
self.layout.separator()
column = self.layout.column(align=True)
column.operator("flip_fluid_operators.helper_delete_whitewater_objects", icon="X").whitewater_type = 'TYPE_ALL'
def register():
bpy.utils.register_class(FLIPFLUID_PT_DomainTypeWhitewaterPanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypeWhitewaterPanel)
@@ -0,0 +1,385 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy, math
from ..utils import version_compatibility_utils as vcu
def format_number_precision(self, value):
value_str = '{:.9f}'.format(value)
return value_str
class FLIPFLUID_PT_DomainTypeFluidWorldPanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid World"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
if vcu.get_addon_preferences(context).enable_tabbed_domain_settings_view:
return False
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_DOMAIN" and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
sprops = obj.flip_fluid.domain.simulation
attrprops = obj.flip_fluid.domain.surface
wprops = obj.flip_fluid.domain.world
aprops = obj.flip_fluid.domain.advanced
box = self.layout.box()
row = box.row(align=True)
row.prop(wprops, "world_scale_settings_expanded",
icon="TRIA_DOWN" if wprops.world_scale_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="World Scale:")
if not wprops.world_scale_settings_expanded:
xdims, ydims, zdims = wprops.get_simulation_dimensions(context)
xdims_str = '{:.2f}'.format(round(xdims, 2)) + " m"
ydims_str = '{:.2f}'.format(round(ydims, 2)) + " m"
zdims_str = '{:.2f}'.format(round(zdims, 2)) + " m"
info_text = xdims_str + " x " + ydims_str + " x " + zdims_str
row = row.row(align=True)
row.alignment = 'RIGHT'
row.label(text=info_text)
if wprops.world_scale_settings_expanded:
row = box.row(align=True)
row.prop(wprops, "world_scale_mode", expand=True)
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column()
column_right = split.column()
if wprops.world_scale_mode == 'WORLD_SCALE_MODE_RELATIVE':
row = column_left.row(align=True)
row.alignment = 'RIGHT'
row.label(text="1 Blender Unit = ")
column_right.prop(wprops, "world_scale_relative")
else:
row = column_left.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Domain Length = ")
column_right.prop(wprops, "world_scale_absolute")
row = column_left.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Simulation dimensions: X = ")
row = column_left.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Y = ")
row = column_left.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Z = ")
xdims, ydims, zdims = wprops.get_simulation_dimensions(context)
xdims_str = '{:.2f}'.format(round(xdims, 2)) + " m"
ydims_str = '{:.2f}'.format(round(ydims, 2)) + " m"
zdims_str = '{:.2f}'.format(round(zdims, 2)) + " m"
column_right.label(text=xdims_str)
column_right.label(text=ydims_str)
column_right.label(text=zdims_str)
box = self.layout.box()
row = box.row(align=True)
row.prop(wprops, "force_field_settings_expanded",
icon="TRIA_DOWN" if wprops.force_field_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Gravity and Force Fields:")
if wprops.force_field_settings_expanded:
subbox = box.box()
subbox.label(text="Gravity:")
row = subbox.row(align=True)
row.prop(wprops, "gravity_type", expand=True)
column = subbox.column(align=True)
split = column.split(align=True)
column_left = split.column()
column_right = split.column()
gvector = wprops.get_gravity_vector()
magnitude = (gvector[0] * gvector[0] + gvector[1] * gvector[1] + gvector[2] * gvector[2])**(1.0/2.0)
gforce = magnitude / 9.81
mag_str = '{:.2f}'.format(round(magnitude, 2))
gforce_str = '{:.2f}'.format(round(gforce, 2))
if wprops.gravity_type == 'GRAVITY_TYPE_SCENE':
column_left.label(text="")
row = column_left.row(align=True)
row.alignment = 'RIGHT'
row.label(text="magnitude = " + mag_str)
row = column_left.row(align=True)
row.alignment = 'RIGHT'
row.label(text="g-force = " + gforce_str)
column_right.enabled = not (wprops.gravity_type == 'GRAVITY_TYPE_SCENE')
if wprops.gravity_type == 'GRAVITY_TYPE_SCENE':
column_right.prop(context.scene, "use_gravity", text="Gravity Enabled")
column_right.prop(context.scene, "gravity", text="")
elif wprops.gravity_type == 'GRAVITY_TYPE_CUSTOM':
column_right.prop(wprops, "gravity", text="")
column = subbox.column(align=True)
column.operator("flip_fluid_operators.make_zero_gravity")
subbox = box.box()
subbox.label(text="Force Field Resolution:")
column = subbox.column(align=True)
row = column.row(align=True)
row.prop(wprops, "force_field_resolution", expand=True)
column = subbox.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
field_resolution = sprops.resolution
if wprops.force_field_resolution == 'FORCE_FIELD_RESOLUTION_LOW':
field_resolution = int(math.ceil(field_resolution / 4))
elif wprops.force_field_resolution == 'FORCE_FIELD_RESOLUTION_NORMAL':
field_resolution = int(math.ceil(field_resolution / 3))
elif wprops.force_field_resolution == 'FORCE_FIELD_RESOLUTION_HIGH':
field_resolution = int(math.ceil(field_resolution / 2))
row = column_left.row()
row.prop(wprops, "force_field_resolution_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text="Grid resolution: ")
column_right.label(text=str(field_resolution))
subbox = box.box()
subbox.label(text="Force Field Weights:")
column = subbox.column(align=True)
column.prop(wprops, "force_field_weight_fluid_particles", slider=True)
column.prop(wprops, "force_field_weight_whitewater_foam", slider=True)
column.prop(wprops, "force_field_weight_whitewater_bubble", slider=True)
column.prop(wprops, "force_field_weight_whitewater_spray", slider=True)
column.prop(wprops, "force_field_weight_whitewater_dust", slider=True)
box = self.layout.box()
row = box.row(align=True)
row.prop(wprops, "viscosity_settings_expanded",
icon="TRIA_DOWN" if wprops.viscosity_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
if not wprops.viscosity_settings_expanded:
row.prop(wprops, "enable_viscosity", text="")
row.label(text="Viscosity:")
is_variable_viscosity_enabled = attrprops.enable_viscosity_attribute
if not wprops.viscosity_settings_expanded:
if not is_variable_viscosity_enabled:
total_viscosity = wprops.viscosity * (10**(-wprops.viscosity_exponent))
total_viscosity_str = format_number_precision(self, total_viscosity)
row = row.row(align=True)
row.alignment = 'RIGHT'
row.enabled = wprops.enable_viscosity
row.label(text=total_viscosity_str)
if wprops.viscosity_settings_expanded:
column = box.column(align=True)
row = column.row(align=True)
row.prop(wprops, "enable_viscosity")
if vcu.get_addon_preferences().is_extra_features_enabled():
row = row.row(align=True)
row.enabled = wprops.enable_viscosity
row.prop(attrprops, "enable_viscosity_attribute", text="Variable Viscosity")
column = box.column(align=True)
column.enabled = wprops.enable_viscosity
if is_variable_viscosity_enabled:
column.label(text="Variable viscosity values can be set in the", icon='INFO')
column.label(text="Fluid or Inflow physics properties menu", icon='INFO')
else:
column.prop(wprops, "viscosity", text="Base")
column.prop(wprops, "viscosity_exponent", text="Exponent")
column.prop(wprops, "viscosity_solver_error_tolerance", text="Solver Accuracy", slider=True)
if is_variable_viscosity_enabled:
column.label(text="")
else:
total_viscosity = wprops.viscosity * (10**(-wprops.viscosity_exponent))
total_viscosity_str = "Total viscosity = " + format_number_precision(self, total_viscosity)
column.label(text=total_viscosity_str)
box = self.layout.box()
row = box.row(align=True)
row.prop(wprops, "surface_tension_settings_expanded",
icon="TRIA_DOWN" if wprops.surface_tension_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
if not wprops.surface_tension_settings_expanded:
row.prop(wprops, "enable_surface_tension", text="")
row.label(text="Surface Tension:")
if not wprops.surface_tension_settings_expanded:
total_surface_tension = wprops.get_surface_tension_value()
surface_tension_str = format_number_precision(self, total_surface_tension)
row = row.row(align=True)
row.alignment = 'RIGHT'
row.enabled = wprops.enable_surface_tension
row.alert = wprops.enable_surface_tension and wprops.minimum_surface_tension_substeps > aprops.min_max_time_steps_per_frame.value_max
row.label(text=surface_tension_str)
if wprops.surface_tension_settings_expanded:
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_left.prop(wprops, "enable_surface_tension")
column_left.label(text="")
column_left.label(text="")
column_left = column_left.column(align=True)
column_left.enabled = wprops.enable_surface_tension
row = column_left.row(align=True)
row.enabled = wprops.enable_surface_tension
row.alignment='RIGHT'
row.label(text="Total Surface Tension =")
row = column_left.row(align=True)
row.enabled = wprops.enable_surface_tension
row.alignment='RIGHT'
row.prop(wprops, "surface_tension_substeps_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text="Estimated substeps =")
total_surface_tension = wprops.get_surface_tension_value()
surface_tension_str = format_number_precision(self, total_surface_tension)
column_right = split.column(align=True)
column_right.enabled = wprops.enable_surface_tension
column_right.prop(wprops, "surface_tension", text="Base")
column_right.prop(wprops, "surface_tension_exponent", text="Exponent")
column_right.prop(wprops, "surface_tension_accuracy", text="Solver Accuracy")
column_right.label(text=surface_tension_str)
column_right.label(text=str(wprops.minimum_surface_tension_substeps))
if wprops.enable_surface_tension and wprops.minimum_surface_tension_substeps > aprops.min_max_time_steps_per_frame.value_max:
row = column.row(align=True)
row.alert = True
row.prop(wprops, "surface_tension_substeps_exceeded_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text=" Warning: Too Many Substeps")
box = self.layout.box()
row = box.row(align=True)
row.prop(wprops, "sheeting_settings_expanded",
icon="TRIA_DOWN" if wprops.sheeting_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
if not wprops.sheeting_settings_expanded:
row.prop(wprops, "enable_sheet_seeding", text="")
row.label(text="Sheeting Effects:")
if wprops.sheeting_settings_expanded:
box.label(text="Sheeting Effects:")
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_left.prop(wprops, "enable_sheet_seeding")
column_right = split.column(align=True)
column_right.enabled = wprops.enable_sheet_seeding
column_right.prop(wprops, "sheet_fill_rate")
column_right.prop(wprops, "sheet_fill_threshold")
obstacle_objects = context.scene.flip_fluid.get_obstacle_objects()
indent_str = 5 * " "
column.label(text="Obstacle Sheeting:")
if len(obstacle_objects) == 0:
column.label(text=indent_str + "No obstacle objects found...")
else:
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
for ob in obstacle_objects:
pgroup = ob.flip_fluid.get_property_group()
column_left.label(text=ob.name, icon="OBJECT_DATA")
column_right.prop(pgroup, "sheeting_strength", text="Strength Scale")
box = self.layout.box()
row = box.row(align=True)
row.prop(wprops, "friction_settings_expanded",
icon="TRIA_DOWN" if wprops.friction_settings_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Friction:")
if not wprops.friction_settings_expanded:
row = row.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Boundary Friction ")
row.prop(wprops, "boundary_friction", text="")
if wprops.friction_settings_expanded:
column = box.column()
split = column.split(align=True)
column_left = split.column()
column_left.label(text="Boundary Friction:")
column_right = split.column()
column_right.prop(wprops, "boundary_friction", text="")
row = box.row(align=True)
row.prop(wprops, "obstacle_friction_expanded",
icon="TRIA_DOWN" if wprops.obstacle_friction_expanded else "TRIA_RIGHT",
icon_only=True,
emboss=False
)
row.label(text="Obstacle Friction:")
if wprops.obstacle_friction_expanded:
obstacle_objects = context.scene.flip_fluid.get_obstacle_objects()
column = box.column(align=True)
indent_str = 5 * " "
if len(obstacle_objects) == 0:
column.label(text=indent_str + "No obstacle objects found...")
else:
split = column.split(align=True)
column_left = split.column(align=True)
column_right = split.column(align=True)
for ob in obstacle_objects:
pgroup = ob.flip_fluid.get_property_group()
column_left.label(text=ob.name, icon="OBJECT_DATA")
column_right.prop(pgroup, "friction")
def register():
bpy.utils.register_class(FLIPFLUID_PT_DomainTypeFluidWorldPanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_DomainTypeFluidWorldPanel)
@@ -0,0 +1,56 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_FLIPFluidsAddonDisabledPanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluids Addon Disabled"
@classmethod
def poll(cls, context):
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return is_addon_disabled
def draw(self, context):
column = self.layout.column(align=True)
row = column.row(align=True)
row.alert = True
row.label(text="FLIP Fluids Addon has been disabled in this Blend file", icon="INFO")
row = column.row(align=True)
row.alert = True
row.label(text="Click to re-enable and use the addon", icon="INFO")
operator_name = "flip_fluid_operators.enable_addon_in_blend_file"
icon = context.scene.flip_fluid.get_logo_icon()
if icon is not None:
column.operator(operator_name, icon_value=icon.icon_id)
else:
column.operator(operator_name, icon='X')
def register():
bpy.utils.register_class(FLIPFLUID_PT_FLIPFluidsAddonDisabledPanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_FLIPFluidsAddonDisabledPanel)
@@ -0,0 +1,247 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_FluidTypePanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid"
@classmethod
def poll(cls, context):
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == 'TYPE_FLUID' and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
obj_props = obj.flip_fluid
fluid_props = obj_props.fluid
dprops = context.scene.flip_fluid.get_domain_properties()
show_disabled_in_viewport_warning = True
if show_disabled_in_viewport_warning and obj.hide_viewport:
box = self.layout.box()
box.alert = True
column = box.column(align=True)
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(fluid_props, "disabled_in_viewport_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text="Object is currently disabled in the viewport")
row.label(text="", icon="RESTRICT_VIEW_ON")
column.label(text="This object will not be included within the simulation")
column = self.layout.column()
column.prop(obj_props, "object_type")
column.separator()
column.label(text="Trigger:")
row = column.row(align= True)
row.prop(fluid_props, "frame_offset_type", text = "")
if fluid_props.frame_offset_type == 'OFFSET_TYPE_FRAME':
row.prop(fluid_props, "frame_offset")
elif fluid_props.frame_offset_type == 'OFFSET_TYPE_TIMELINE':
row.prop(fluid_props, "timeline_offset")
self.layout.separator()
column.prop(fluid_props, "priority", text="Priority Level")
box = self.layout.box()
box.label(text="Fluid Velocity Mode:")
row = box.row(align=True)
row.prop(fluid_props, "fluid_velocity_mode", expand=True)
if fluid_props.fluid_velocity_mode == 'FLUID_VELOCITY_MANUAL':
column = box.column(align=True)
column.label(text="Fluid Velocity:")
row = column.row(align=True)
row.prop(fluid_props, "initial_velocity", text="")
row = column.row(align=True)
row.label(text="")
elif fluid_props.fluid_velocity_mode == 'FLUID_VELOCITY_AXIS':
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_left.label(text="Fluid Speed:")
column_left.prop(fluid_props, "initial_speed")
column_right = split.column(align=True)
column_right.label(text="Local Axis:")
row = column_right.row(align=True)
row.prop_enum(fluid_props, "fluid_axis_mode", 'LOCAL_AXIS_POS_X')
row.prop_enum(fluid_props, "fluid_axis_mode", 'LOCAL_AXIS_POS_Y')
row.prop_enum(fluid_props, "fluid_axis_mode", 'LOCAL_AXIS_POS_Z')
row = column_right.row(align=True)
row.prop_enum(fluid_props, "fluid_axis_mode", 'LOCAL_AXIS_NEG_X')
row.prop_enum(fluid_props, "fluid_axis_mode", 'LOCAL_AXIS_NEG_Y')
row.prop_enum(fluid_props, "fluid_axis_mode", 'LOCAL_AXIS_NEG_Z')
elif fluid_props.fluid_velocity_mode == 'FLUID_VELOCITY_TARGET':
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_left.label(text="Fluid Speed:")
column_left.prop(fluid_props, "initial_speed")
target_collection = vcu.get_scene_collection()
if vcu.is_blender_28():
search_group = "all_objects"
else:
search_group = "objects"
column_right = split.column(align=True)
column_right.label(text="Target Object:")
column_right.prop_search(fluid_props, "target_object", target_collection, search_group, text="")
target_object = fluid_props.get_target_object()
if target_object is not None:
is_target_domain = target_object.flip_fluid.is_domain()
target_props = target_object.flip_fluid.get_property_group()
if target_props is not None and not is_target_domain:
column_right.prop(target_props, "export_animated_mesh", text="Export Animated Target")
else:
column_right.prop(fluid_props, "export_animated_target")
else:
column_right.prop(fluid_props, "export_animated_target")
column = box.column(align=True)
split = vcu.ui_split(column, factor=0.66)
column = split.column()
column.prop(fluid_props, "append_object_velocity")
column = split.column()
column.enabled = fluid_props.append_object_velocity
column.prop(fluid_props, "append_object_velocity_influence")
if vcu.get_addon_preferences().is_extra_features_enabled():
box = self.layout.box()
box.label(text="Geometry Attributes:")
column = box.column(align=True)
if vcu.is_blender_293():
is_color_attribute_enabled = dprops is not None and (dprops.surface.enable_color_attribute or
dprops.particles.enable_fluid_particle_color_attribute)
show_color = dprops is not None and is_color_attribute_enabled
split = column.split(align=True)
column_left = split.column(align=True)
column_left.enabled = show_color
column_left.prop(fluid_props, "color")
column_right = split.column(align=True)
column_right.label(text="")
row = column_right.row(align=True)
row.alignment = 'LEFT'
if dprops is not None and not show_color:
row.operator("flip_fluid_operators.enable_color_attribute_tooltip",
text="Enable Color Attribute", icon="PLUS", emboss=False)
if dprops is None:
row.label(text="Domain required for this option")
column.separator()
show_viscosity = dprops is not None and dprops.surface.enable_viscosity_attribute
split = column.split(align=True)
column_left = split.column(align=True)
column_left.enabled = show_viscosity
column_left.prop(fluid_props, "viscosity")
column_right = split.column(align=True)
row = column_right.row(align=True)
row.alignment = 'LEFT'
if dprops is not None and not show_viscosity:
row.operator("flip_fluid_operators.enable_viscosity_attribute_tooltip",
text="Enable Viscosity Attribute", icon="PLUS", emboss=False)
if dprops is None:
row.label(text="Domain required for this option")
column.separator()
is_lifetime_attribute_enabled = dprops is not None and (dprops.surface.enable_lifetime_attribute or
dprops.particles.enable_fluid_particle_lifetime_attribute)
show_lifetime = dprops is not None and is_lifetime_attribute_enabled
split = column.split(align=True)
column_left = split.column(align=True)
column_left.enabled = show_lifetime
column_left.prop(fluid_props, "lifetime")
column_right = split.column(align=True)
row = column_right.row(align=True)
row.alignment = 'LEFT'
if dprops is not None and not show_lifetime:
row.operator("flip_fluid_operators.enable_lifetime_attribute_tooltip",
text="Enable Lifetime Attribute", icon="PLUS", emboss=False)
elif dprops is not None:
row.alignment = 'EXPAND'
row.prop(fluid_props, "lifetime_variance", text="Variance")
if dprops is None:
row.label(text="Domain required for this option")
column.separator()
is_source_id_attribute_enabled = dprops is not None and (dprops.surface.enable_source_id_attribute or
dprops.particles.enable_fluid_particle_source_id_attribute)
show_source_id = dprops is not None and is_source_id_attribute_enabled
split = column.split(align=True)
column_left = split.column(align=True)
column_left.enabled = show_source_id
column_left.prop(fluid_props, "source_id")
column_right = split.column(align=True)
row = column_right.row(align=True)
row.alignment = 'LEFT'
if dprops is not None and not show_source_id:
row.operator("flip_fluid_operators.enable_source_id_attribute_tooltip",
text="Enable Source ID Attribute", icon="PLUS", emboss=False)
if dprops is None:
row.label(text="Domain required for this option")
column.separator()
else:
column.enabled = False
column.label(text="Geometry attribute features are only available in", icon='ERROR')
column.label(text="Blender 2.93 or later", icon='ERROR')
box = self.layout.box()
box.label(text="Mesh Data Export:")
column = box.column(align=True)
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(fluid_props, "export_animated_mesh")
is_child_object = obj.parent is not None
is_hint_enabled = not vcu.get_addon_preferences().dismiss_export_animated_mesh_parented_relation_hint
if is_hint_enabled and not fluid_props.export_animated_mesh and is_child_object:
row.prop(context.scene.flip_fluid_helper, "export_animated_mesh_parent_tooltip",
icon="QUESTION", emboss=False, text=""
)
row.label(text="←Hint: export option may be required")
column.prop(fluid_props, "skip_reexport")
column.separator()
column = box.column(align=True)
column.enabled = fluid_props.skip_reexport
column.prop(fluid_props, "force_reexport_on_next_bake", toggle=True)\
column = self.layout.column(align=True)
column.separator()
column.operator("flip_fluid_operators.copy_setting_to_selected", icon='COPYDOWN')
def register():
bpy.utils.register_class(FLIPFLUID_PT_FluidTypePanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_FluidTypePanel)
@@ -0,0 +1,243 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy, math
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_ForceFieldTypePanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid"
@classmethod
def poll(cls, context):
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_FORCE_FIELD" and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
obj_props = obj.flip_fluid
force_field_props = obj_props.force_field
show_disabled_in_viewport_warning = True
if show_disabled_in_viewport_warning and obj.hide_viewport:
box = self.layout.box()
box.alert = True
column = box.column(align=True)
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(force_field_props, "disabled_in_viewport_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text="Object is currently disabled in the viewport")
row.label(text="", icon="RESTRICT_VIEW_ON")
column.label(text="This object will not be included within the simulation")
column = self.layout.column()
column.prop(obj_props, "object_type")
column = self.layout.column()
column.prop(force_field_props, "force_field_type", text="Mode")
if obj.type == 'CURVE' and force_field_props.force_field_type != 'FORCE_FIELD_TYPE_CURVE':
column = self.layout.column()
column.label(text="Curve objects can only be set as a Curve Guide Force Field")
return
if obj.type == 'EMPTY' and force_field_props.force_field_type != 'FORCE_FIELD_TYPE_POINT':
column = self.layout.column()
column.label(text="Empty objects can only be set as a Point Force Field")
return
if obj.type == 'MESH' and force_field_props.force_field_type == 'FORCE_FIELD_TYPE_CURVE':
column = self.layout.column()
column.label(text="Mesh objects cannot be used as a Curve Guide Force Field")
column.label(text="Curve Guide Force Fields only support Curve objects")
return
elif force_field_props.force_field_type == 'FORCE_FIELD_TYPE_VORTEX':
column = self.layout.column()
column.label(text="Vortex force is not yet available")
column.label(text="Feature implementation in progress")
return
elif force_field_props.force_field_type == 'FORCE_FIELD_TYPE_TURBULENCE':
column = self.layout.column()
column.label(text="Turbulence guided force is not yet available")
column.label(text="Feature implementation in progress")
return
elif force_field_props.force_field_type == 'FORCE_FIELD_TYPE_PROGRAMMABLE':
column = self.layout.column()
column.label(text="Programmable guided force is not yet available")
column.label(text="Feature implementation in progress")
return
elif force_field_props.force_field_type == 'FORCE_FIELD_TYPE_OTHER':
column = self.layout.column()
column.label(text="More force field modes are in development")
column.label(text="Try out our experimental builds for the latest features!")
column.operator(
"wm.url_open",
text="Experimental Builds",
icon="WORLD"
).url = "https://github.com/rlguy/Blender-FLIP-Fluids/wiki/Experimental-Builds"
return
column = self.layout.column()
column.prop(force_field_props, "is_enabled")
strength_text = "Strength"
if force_field_props.force_field_type == 'FORCE_FIELD_TYPE_CURVE':
strength_text = "Attraction Strength"
box = self.layout.box()
box.label(text="Field Strength and Falloff:")
column = box.column(align=True)
column.prop(force_field_props, "strength", text=strength_text)
if force_field_props.force_field_type == 'FORCE_FIELD_TYPE_CURVE':
column.prop(force_field_props, "flow_strength")
column.prop(force_field_props, "spin_strength")
row = column.row(align=True)
row.alignment = 'RIGHT'
row.prop(force_field_props, "enable_endcaps")
column = box.column(align=True)
if force_field_props.force_field_type == 'FORCE_FIELD_TYPE_POINT':
split = column.split()
column_left = split.column(align=True)
column_right = split.column(align=True)
column_left.prop(force_field_props, "falloff_power")
column_right.enabled=False
# Todo: implement point shapes
#column_right.prop(force_field_props, "falloff_shape", text="Shape")
else:
column.prop(force_field_props, "falloff_power")
split = column.split()
col = split.column()
row = col.row(align=True)
row.prop(force_field_props, "enable_min_distance", text="")
sub = row.row(align=True)
sub.active = force_field_props.enable_min_distance
sub.prop(force_field_props.min_max_distance, "value_min")
col = split.column()
row = col.row(align=True)
row.prop(force_field_props, "enable_max_distance", text="")
sub = row.row(align=True)
sub.active = force_field_props.enable_max_distance
sub.prop(force_field_props.min_max_distance, "value_max")
eps = 1e-12
power = force_field_props.falloff_power
distance = eps
if force_field_props.enable_min_distance:
distance = max(force_field_props.min_max_distance.value_min, eps)
denominator = math.pow(distance, force_field_props.falloff_power)
if denominator == 0.0:
max_strength_str = "infinite"
else:
strength = abs(force_field_props.strength)
max_strength = strength * (1.0 / denominator)
limit_factor = force_field_props.maximum_force_limit_factor
max_strength = min(max_strength, limit_factor * strength)
max_strength_str = self._format_strength_value(max_strength)
column.separator()
column.separator()
column.prop(force_field_props, "maximum_force_limit_factor")
split = column.split()
column = split.column()
row = column.row()
row.alignment = 'LEFT'
row.prop(force_field_props, "maximum_strength_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text="Max Force:")
column = split.column()
row = column.row()
row.label(text=max_strength_str)
if force_field_props.force_field_type == 'FORCE_FIELD_TYPE_SURFACE':
box = self.layout.box()
row = box.row(align=True)
row.label(text="Enabled Sides:")
row.prop(force_field_props, "enable_frontfacing")
row.prop(force_field_props, "enable_backfacing")
row.prop(force_field_props, "enable_edgefacing")
self.layout.separator()
box = self.layout.box()
box.label(text="Antigravity")
column = box.column(align=True)
if force_field_props.force_field_type == 'FORCE_FIELD_TYPE_POINT':
column.prop(force_field_props, "gravity_scale_point", slider=True)
column.prop(force_field_props, "gravity_scale_width_point", text="Width", slider=True)
elif force_field_props.force_field_type == 'FORCE_FIELD_TYPE_SURFACE':
column.prop(force_field_props, "gravity_scale_surface", slider=True)
column.prop(force_field_props, "gravity_scale_width_surface", text="Width", slider=True)
elif force_field_props.force_field_type == 'FORCE_FIELD_TYPE_VOLUME':
column.prop(force_field_props, "gravity_scale_volume", slider=True)
column.prop(force_field_props, "gravity_scale_width_volume", text="Width", slider=True)
elif force_field_props.force_field_type == 'FORCE_FIELD_TYPE_CURVE':
column.prop(force_field_props, "gravity_scale_curve", slider=True)
column.prop(force_field_props, "gravity_scale_width_curve", text="Width", slider=True)
box = self.layout.box()
box.label(text="Mesh Data Export:")
column = box.column(align=True)
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(force_field_props, "export_animated_mesh")
is_child_object = obj.parent is not None
is_hint_enabled = not vcu.get_addon_preferences().dismiss_export_animated_mesh_parented_relation_hint
if is_hint_enabled and not force_field_props.export_animated_mesh and is_child_object:
row.prop(context.scene.flip_fluid_helper, "export_animated_mesh_parent_tooltip",
icon="QUESTION", emboss=False, text=""
)
row.label(text="←Hint: export option may be required")
column.prop(force_field_props, "skip_reexport")
column.separator()
column = box.column(align=True)
column.enabled = force_field_props.skip_reexport
column.prop(force_field_props, "force_reexport_on_next_bake", toggle=True)
column = self.layout.column(align=True)
column.separator()
column.operator("flip_fluid_operators.copy_setting_to_selected", icon='COPYDOWN')
def _format_strength_value(self, s):
if s > 10:
return '{0:.1f}'.format(s)
if s > 1:
return '{0:.2f}'.format(s)
return '{0:.3f}'.format(s)
def register():
bpy.utils.register_class(FLIPFLUID_PT_ForceFieldTypePanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_ForceFieldTypePanel)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,242 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_InflowTypePanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid"
@classmethod
def poll(cls, context):
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == 'TYPE_INFLOW' and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
obj_props = obj.flip_fluid
inflow_props = obj_props.inflow
dprops = context.scene.flip_fluid.get_domain_properties()
show_disabled_in_viewport_warning = True
if show_disabled_in_viewport_warning and obj.hide_viewport:
box = self.layout.box()
box.alert = True
column = box.column(align=True)
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(inflow_props, "disabled_in_viewport_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text="Object is currently disabled in the viewport")
row.label(text="", icon="RESTRICT_VIEW_ON")
column.label(text="This object will not be included within the simulation")
column = self.layout.column()
column.prop(obj_props, "object_type")
column = self.layout.column()
column.prop(inflow_props, "is_enabled")
column.prop(inflow_props, "substep_emissions")
column.prop(inflow_props, "priority", text="Priority Level")
column.separator()
box = self.layout.box()
box.label(text="Inflow Velocity Mode:")
row = box.row(align=True)
row.prop(inflow_props, "inflow_velocity_mode", expand=True)
if inflow_props.inflow_velocity_mode == 'INFLOW_VELOCITY_MANUAL':
column = box.column(align=True)
column.label(text="Inflow Velocity:")
row = column.row(align=True)
row.prop(inflow_props, "inflow_velocity", text="")
row = column.row(align=True)
row.label(text="")
elif inflow_props.inflow_velocity_mode == 'INFLOW_VELOCITY_AXIS':
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_left.label(text="Inflow Speed:")
column_left.prop(inflow_props, "inflow_speed")
column_right = split.column(align=True)
column_right.label(text="Local Axis:")
row = column_right.row(align=True)
row.prop_enum(inflow_props, "inflow_axis_mode", 'LOCAL_AXIS_POS_X')
row.prop_enum(inflow_props, "inflow_axis_mode", 'LOCAL_AXIS_POS_Y')
row.prop_enum(inflow_props, "inflow_axis_mode", 'LOCAL_AXIS_POS_Z')
row = column_right.row(align=True)
row.prop_enum(inflow_props, "inflow_axis_mode", 'LOCAL_AXIS_NEG_X')
row.prop_enum(inflow_props, "inflow_axis_mode", 'LOCAL_AXIS_NEG_Y')
row.prop_enum(inflow_props, "inflow_axis_mode", 'LOCAL_AXIS_NEG_Z')
elif inflow_props.inflow_velocity_mode == 'INFLOW_VELOCITY_TARGET':
column = box.column(align=True)
split = column.split(align=True)
column_left = split.column(align=True)
column_left.label(text="Inflow Speed:")
column_left.prop(inflow_props, "inflow_speed")
target_collection = vcu.get_scene_collection()
if vcu.is_blender_28():
search_group = "all_objects"
else:
search_group = "objects"
column_right = split.column(align=True)
column_right.label(text="Target Object:")
column_right.prop_search(inflow_props, "target_object", target_collection, search_group, text="")
target_object = inflow_props.get_target_object()
if target_object is not None:
is_target_domain = target_object.flip_fluid.is_domain()
target_props = target_object.flip_fluid.get_property_group()
if target_props is not None and not is_target_domain:
column_right.prop(target_props, "export_animated_mesh", text="Export Animated Target")
else:
column_right.prop(inflow_props, "export_animated_target")
else:
column_right.prop(inflow_props, "export_animated_target")
box.separator()
column = box.column(align=True)
split = vcu.ui_split(column, factor=0.60)
column = split.column()
column.prop(inflow_props, "append_object_velocity")
column = split.column(align=True)
column.enabled = inflow_props.append_object_velocity
column.prop(inflow_props, "append_object_velocity_influence")
column = box.column(align=True)
column.prop(inflow_props, "constrain_fluid_velocity")
if vcu.get_addon_preferences().is_extra_features_enabled():
box = self.layout.box()
box.label(text="Geometry Attributes:")
column = box.column(align=True)
if vcu.is_blender_293():
is_color_attribute_enabled = dprops is not None and (dprops.surface.enable_color_attribute or
dprops.particles.enable_fluid_particle_color_attribute)
show_color = dprops is not None and is_color_attribute_enabled
split = column.split(align=True)
column_left = split.column(align=True)
column_left.enabled = show_color
column_left.prop(inflow_props, "color")
column_right = split.column(align=True)
column_right.label(text="")
row = column_right.row(align=True)
row.alignment = 'LEFT'
if dprops is not None and not show_color:
row.operator("flip_fluid_operators.enable_color_attribute_tooltip",
text="Enable Color Attribute", icon="PLUS", emboss=False)
if dprops is None:
row.label(text="Domain required for this option")
column.separator()
show_viscosity = dprops is not None and dprops.surface.enable_viscosity_attribute
split = column.split(align=True)
column_left = split.column(align=True)
column_left.enabled = show_viscosity
column_left.prop(inflow_props, "viscosity")
column_right = split.column(align=True)
row = column_right.row(align=True)
row.alignment = 'LEFT'
if dprops is not None and not show_viscosity:
row.operator("flip_fluid_operators.enable_viscosity_attribute_tooltip",
text="Enable Viscosity Attribute", icon="PLUS", emboss=False)
if dprops is None:
row.label(text="Domain required for this option")
column.separator()
is_lifetime_attribute_enabled = dprops is not None and (dprops.surface.enable_lifetime_attribute or
dprops.particles.enable_fluid_particle_lifetime_attribute)
show_lifetime = dprops is not None and is_lifetime_attribute_enabled
split = column.split(align=True)
column_left = split.column(align=True)
column_left.enabled = show_lifetime
column_left.prop(inflow_props, "lifetime")
column_right = split.column(align=True)
row = column_right.row(align=True)
row.alignment = 'LEFT'
if dprops is not None and not show_lifetime:
row.operator("flip_fluid_operators.enable_lifetime_attribute_tooltip",
text="Enable Lifetime Attribute", icon="PLUS", emboss=False)
elif dprops is not None:
row.alignment = 'EXPAND'
row.prop(inflow_props, "lifetime_variance", text="Variance")
if dprops is None:
row.label(text="Domain required for this option")
column.separator()
is_source_id_attribute_enabled = dprops is not None and (dprops.surface.enable_source_id_attribute or
dprops.particles.enable_fluid_particle_source_id_attribute)
show_source_id = dprops is not None and is_source_id_attribute_enabled
split = column.split(align=True)
column_left = split.column(align=True)
column_left.enabled = show_source_id
column_left.prop(inflow_props, "source_id")
column_right = split.column(align=True)
row = column_right.row(align=True)
row.alignment = 'LEFT'
if dprops is not None and not show_source_id:
row.operator("flip_fluid_operators.enable_source_id_attribute_tooltip",
text="Enable Source ID Attribute", icon="PLUS", emboss=False)
if dprops is None:
row.label(text="Domain required for this option")
column.separator()
else:
column.enabled = False
column.label(text="Geometry attribute features are only available in", icon='ERROR')
column.label(text="Blender 2.93 or later", icon='ERROR')
box = self.layout.box()
box.label(text="Mesh Data Export:")
column = box.column(align=True)
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(inflow_props, "export_animated_mesh")
is_child_object = obj.parent is not None
is_hint_enabled = not vcu.get_addon_preferences().dismiss_export_animated_mesh_parented_relation_hint
if is_hint_enabled and not inflow_props.export_animated_mesh and is_child_object:
row.prop(context.scene.flip_fluid_helper, "export_animated_mesh_parent_tooltip",
icon="QUESTION", emboss=False, text=""
)
row.label(text="←Hint: export option may be required")
column.prop(inflow_props, "skip_reexport")
column.separator()
column = box.column(align=True)
column.enabled = inflow_props.skip_reexport
column.prop(inflow_props, "force_reexport_on_next_bake", toggle=True)
column = self.layout.column(align=True)
column.separator()
column.operator("flip_fluid_operators.copy_setting_to_selected", icon='COPYDOWN')
def register():
bpy.utils.register_class(FLIPFLUID_PT_InflowTypePanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_InflowTypePanel)
@@ -0,0 +1,49 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_NoneTypePanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid"
@classmethod
def poll(cls, context):
obj_props = vcu.get_active_object(context).flip_fluid
return obj_props.is_active and obj_props.object_type == 'TYPE_NONE'
def draw(self, context):
obj = vcu.get_active_object(context)
obj_props = obj.flip_fluid
column = self.layout.column()
column.prop(obj_props, "object_type")
def register():
bpy.utils.register_class(FLIPFLUID_PT_NoneTypePanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_NoneTypePanel)
@@ -0,0 +1,120 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_ObstacleTypePanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid"
@classmethod
def poll(cls, context):
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_OBSTACLE" and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
obj_props = obj.flip_fluid
obstacle_props = obj_props.obstacle
preferences = vcu.get_addon_preferences(context)
show_disabled_in_viewport_warning = True
if show_disabled_in_viewport_warning and obj.hide_viewport:
box = self.layout.box()
box.alert = True
column = box.column(align=True)
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(obstacle_props, "disabled_in_viewport_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text="Object is currently disabled in the viewport")
row.label(text="", icon="RESTRICT_VIEW_ON")
column.label(text="This object will not be included within the simulation")
column = self.layout.column()
column.prop(obj_props, "object_type")
column = self.layout.column()
column.prop(obstacle_props, "is_enabled")
column = self.layout.column()
column.prop(obstacle_props, "is_inversed")
box = self.layout.box()
box.label(text="Obstacle Properties")
column = box.column()
column.prop(obstacle_props, "friction", slider=True)
column = box.column()
column.prop(obstacle_props, "velocity_scale")
column = box.column()
column.prop(obstacle_props, "whitewater_influence")
column = box.column()
column.prop(obstacle_props, "dust_emission_strength")
column = box.column()
column.prop(obstacle_props, "sheeting_strength")
column = box.column()
alert_threshold = 0.05 + 1e-5
if abs(obstacle_props.mesh_expansion) > alert_threshold:
column.alert = True
column.prop(obstacle_props, "mesh_expansion")
box = self.layout.box()
box.label(text="Mesh Data Export:")
column = box.column(align=True)
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(obstacle_props, "export_animated_mesh")
is_child_object = obj.parent is not None
is_hint_enabled = not vcu.get_addon_preferences().dismiss_export_animated_mesh_parented_relation_hint
if is_hint_enabled and not obstacle_props.export_animated_mesh and is_child_object:
row.prop(context.scene.flip_fluid_helper, "export_animated_mesh_parent_tooltip",
icon="QUESTION", emboss=False, text=""
)
row.label(text="←Hint: export option may be required")
column.prop(obstacle_props, "skip_reexport")
column.separator()
column = box.column(align=True)
column.enabled = obstacle_props.skip_reexport
column.prop(obstacle_props, "force_reexport_on_next_bake", toggle=True)
column = self.layout.column(align=True)
column.separator()
column.operator("flip_fluid_operators.copy_setting_to_selected", icon='COPYDOWN')
def register():
bpy.utils.register_class(FLIPFLUID_PT_ObstacleTypePanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_ObstacleTypePanel)
@@ -0,0 +1,101 @@
# Blender FLIP Fluids Add-on
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ..utils import version_compatibility_utils as vcu
class FLIPFLUID_PT_OutflowTypePanel(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "physics"
bl_category = "FLIP Fluid"
bl_label = "FLIP Fluid"
@classmethod
def poll(cls, context):
obj_props = vcu.get_active_object(context).flip_fluid
is_addon_disabled = context.scene.flip_fluid.is_addon_disabled_in_blend_file()
return obj_props.is_active and obj_props.object_type == "TYPE_OUTFLOW" and not is_addon_disabled
def draw(self, context):
obj = vcu.get_active_object(context)
obj_props = obj.flip_fluid
outflow_props = obj_props.outflow
show_disabled_in_viewport_warning = True
if show_disabled_in_viewport_warning and obj.hide_viewport:
box = self.layout.box()
box.alert = True
column = box.column(align=True)
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(outflow_props, "disabled_in_viewport_tooltip", icon="QUESTION", emboss=False, text="")
row.label(text="Object is currently disabled in the viewport")
row.label(text="", icon="RESTRICT_VIEW_ON")
column.label(text="This object will not be included within the simulation")
column = self.layout.column()
column.prop(obj_props, "object_type")
column = self.layout.column()
column.prop(outflow_props, "is_enabled")
column = self.layout.column()
split = column.split()
column = split.column()
column.prop(outflow_props, "remove_fluid")
column = split.column()
column.prop(outflow_props, "remove_whitewater")
self.layout.separator()
column = self.layout.column()
column.prop(outflow_props, "is_inversed")
box = self.layout.box()
box.label(text="Mesh Data Export:")
column = box.column(align=True)
row = column.row(align=True)
row.alignment = 'LEFT'
row.prop(outflow_props, "export_animated_mesh")
is_child_object = obj.parent is not None
is_hint_enabled = not vcu.get_addon_preferences().dismiss_export_animated_mesh_parented_relation_hint
if is_hint_enabled and not outflow_props.export_animated_mesh and is_child_object:
row.prop(context.scene.flip_fluid_helper, "export_animated_mesh_parent_tooltip",
icon="QUESTION", emboss=False, text=""
)
row.label(text="←Hint: export option may be required")
column.prop(outflow_props, "skip_reexport")
column.separator()
column = box.column(align=True)
column.enabled = outflow_props.skip_reexport
column.prop(outflow_props, "force_reexport_on_next_bake", toggle=True)
column = self.layout.column(align=True)
column.separator()
column.operator("flip_fluid_operators.copy_setting_to_selected", icon='COPYDOWN')
def register():
bpy.utils.register_class(FLIPFLUID_PT_OutflowTypePanel)
def unregister():
bpy.utils.unregister_class(FLIPFLUID_PT_OutflowTypePanel)