2025-12-01
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# 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 = [
|
||||
'preferences_properties',
|
||||
'custom_properties',
|
||||
'preset_properties',
|
||||
'flip_fluid_properties',
|
||||
'object_properties',
|
||||
'material_properties',
|
||||
'helper_properties'
|
||||
]
|
||||
for module_name in reloadable_modules:
|
||||
if module_name in locals():
|
||||
importlib.reload(locals()[module_name])
|
||||
|
||||
import bpy
|
||||
|
||||
from . import (
|
||||
preferences_properties,
|
||||
custom_properties,
|
||||
preset_properties,
|
||||
flip_fluid_properties,
|
||||
object_properties,
|
||||
material_properties,
|
||||
helper_properties
|
||||
)
|
||||
|
||||
|
||||
def scene_update_post(scene):
|
||||
object_properties.scene_update_post(scene)
|
||||
helper_properties.scene_update_post(scene)
|
||||
|
||||
|
||||
def frame_change_post(scene, depsgraph=None):
|
||||
object_properties.frame_change_post(scene, depsgraph)
|
||||
helper_properties.frame_change_post(scene, depsgraph)
|
||||
|
||||
|
||||
def load_pre():
|
||||
object_properties.load_pre()
|
||||
|
||||
|
||||
def load_post():
|
||||
flip_fluid_properties.load_post()
|
||||
preferences_properties.load_post()
|
||||
object_properties.load_post()
|
||||
helper_properties.load_post()
|
||||
|
||||
|
||||
def save_pre():
|
||||
object_properties.save_pre()
|
||||
|
||||
|
||||
def save_post():
|
||||
object_properties.save_post()
|
||||
helper_properties.save_post()
|
||||
|
||||
|
||||
def register():
|
||||
preferences_properties.register()
|
||||
custom_properties.register()
|
||||
preset_properties.register()
|
||||
flip_fluid_properties.register()
|
||||
object_properties.register()
|
||||
material_properties.register()
|
||||
helper_properties.register()
|
||||
|
||||
|
||||
def unregister():
|
||||
preferences_properties.unregister()
|
||||
custom_properties.unregister()
|
||||
preset_properties.unregister()
|
||||
flip_fluid_properties.unregister()
|
||||
object_properties.unregister()
|
||||
material_properties.unregister()
|
||||
helper_properties.unregister()
|
||||
@@ -0,0 +1,102 @@
|
||||
# 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 bpy.props import (
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
BoolProperty,
|
||||
PointerProperty
|
||||
)
|
||||
|
||||
def NewMinMaxIntProperty(**args):
|
||||
dmin, dmax = _format_min_max_properties(args)
|
||||
|
||||
def update_value_min(self, context):
|
||||
if self.value_min > self.value_max:
|
||||
self.value_max = self.value_min
|
||||
|
||||
def update_value_max(self, context):
|
||||
if self.value_max < self.value_min:
|
||||
self.value_min = self.value_max
|
||||
|
||||
dmin['update'] = update_value_min
|
||||
dmax['update'] = update_value_max
|
||||
|
||||
class MinMaxIntProperty(bpy.types.PropertyGroup):
|
||||
@classmethod
|
||||
def register(cls):
|
||||
cls.value_min = IntProperty(**dmin)
|
||||
cls.value_max = IntProperty(**dmax)
|
||||
cls.is_min_max_property = BoolProperty(default=True)
|
||||
def min(self):
|
||||
return self.value_min
|
||||
def max(self):
|
||||
return self.value_max
|
||||
|
||||
bpy.utils.register_class(MinMaxIntProperty)
|
||||
return PointerProperty(type=MinMaxIntProperty)
|
||||
|
||||
|
||||
def NewMinMaxFloatProperty(**args):
|
||||
dmin, dmax = _format_min_max_properties(args)
|
||||
|
||||
def update_value_min(self, context):
|
||||
if self.value_min > self.value_max:
|
||||
self.value_max = self.value_min
|
||||
|
||||
def update_value_max(self, context):
|
||||
if self.value_max < self.value_min:
|
||||
self.value_min = self.value_max
|
||||
|
||||
dmin['update'] = update_value_min
|
||||
dmax['update'] = update_value_max
|
||||
|
||||
class MinMaxFloatProperty(bpy.types.PropertyGroup):
|
||||
@classmethod
|
||||
def register(cls):
|
||||
cls.value_min = FloatProperty(**dmin)
|
||||
cls.value_max = FloatProperty(**dmax)
|
||||
cls.is_min_max_property = BoolProperty(default=True)
|
||||
def min(self):
|
||||
return self.value_min
|
||||
def max(self):
|
||||
return self.value_max
|
||||
|
||||
bpy.utils.register_class(MinMaxFloatProperty)
|
||||
return PointerProperty(type=MinMaxFloatProperty)
|
||||
|
||||
|
||||
def _format_min_max_properties(args):
|
||||
min_str_suffix = "_min"
|
||||
max_str_suffix = "_max"
|
||||
dmin, dmax = {}, {}
|
||||
for k,v in args.items():
|
||||
if k.endswith(min_str_suffix):
|
||||
k = k[:-len(min_str_suffix)]
|
||||
dmin[k] = v
|
||||
elif k.endswith(max_str_suffix):
|
||||
k = k[:-len(max_str_suffix)]
|
||||
dmax[k] = v
|
||||
return dmin, dmax
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
@@ -0,0 +1,288 @@
|
||||
# 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, os
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
PointerProperty,
|
||||
StringProperty
|
||||
)
|
||||
|
||||
from .custom_properties import NewMinMaxIntProperty
|
||||
from .. import types
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
class DomainAdvancedProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
min_max_time_steps_per_frame = NewMinMaxIntProperty(
|
||||
name_min="Min Substeps",
|
||||
description_min="Minimum number of substeps per frame calculation",
|
||||
min_min=1, max_min=1000000,
|
||||
soft_max_min=100,
|
||||
default_min=1,
|
||||
|
||||
name_max="Max Substeps",
|
||||
description_max="Maximum number of substeps per frame calculation",
|
||||
min_max=1, max_max=1000000,
|
||||
soft_max_max=100,
|
||||
default_max=24,
|
||||
); exec(conv("min_max_time_steps_per_frame"))
|
||||
enable_adaptive_obstacle_time_stepping = BoolProperty(
|
||||
name="Enable Adaptive Time Stepping for Obstacles",
|
||||
description="Include obstacle velocities when calculating number"
|
||||
" of frame substeps. Enabling may improve the accuracy of"
|
||||
" fluid-solid interaction for fast moving obstacles, but"
|
||||
" may take longer to simulate",
|
||||
default = False,
|
||||
); exec(conv("enable_adaptive_obstacle_time_stepping"))
|
||||
enable_adaptive_force_field_time_stepping = BoolProperty(
|
||||
name="Enable Adaptive Time Stepping for Force Fields",
|
||||
description="Include force field velocities when calculating number"
|
||||
" of frame substeps. Enabling may improve the accuracy of"
|
||||
" fluid-forcefield interaction for fast moving force fields, but"
|
||||
" will take longer to simulate",
|
||||
default = False,
|
||||
); exec(conv("enable_adaptive_force_field_time_stepping"))
|
||||
particle_jitter_factor = FloatProperty(
|
||||
name="Particle Jitter",
|
||||
description="Amount of random jitter that is added to newly spawned"
|
||||
" fluid particles. Higher values may improve simulation accuracy",
|
||||
min=0.0, max=1.0,
|
||||
default=1.0,
|
||||
precision=2,
|
||||
subtype='FACTOR',
|
||||
); exec(conv("particle_jitter_factor"))
|
||||
jitter_surface_particles = BoolProperty(
|
||||
name="Jitter Surface Particles",
|
||||
description="If disabled, a random jitter position will only be applied to particles within"
|
||||
" the interior of the Inflow/Fluid object shape. If enabled, all emitted particles"
|
||||
" will be jittered. Enabling may cause bumpy mesh artifacts on the"
|
||||
" initial surface mesh shape and may require additional smoothing."
|
||||
" Enabling is recommended for fluid particle effects and results in"
|
||||
" more natural particle generation",
|
||||
default=False,
|
||||
); exec(conv("jitter_surface_particles"))
|
||||
pressure_solver_max_iterations = IntProperty(
|
||||
name="Pressure Solver Max Iterations",
|
||||
description="Maximum number of iterations that the pressure solver is allowed"
|
||||
" to run during a substep. The default value of 900 is often a good choice and does"
|
||||
" not need to be changed. See documentation for more information on this setting",
|
||||
min=1, soft_max=10000,
|
||||
default=900,
|
||||
); exec(conv("pressure_solver_max_iterations"))
|
||||
viscosity_solver_max_iterations = IntProperty(
|
||||
name="Viscosity Solver Max Iterations",
|
||||
description="Maximum number of iterations that the viscosity solver is allowed"
|
||||
" to run during a substep. The default value of 900 is often a good choice and does"
|
||||
" not need to be changed. See documentation for more information on this setting",
|
||||
min=1, soft_max=10000,
|
||||
default=900,
|
||||
); exec(conv("viscosity_solver_max_iterations"))
|
||||
velocity_transfer_method = EnumProperty(
|
||||
name="Velocity Transfer Method",
|
||||
description="Simulation method to use",
|
||||
items=types.velocity_transfer_methods,
|
||||
default='VELOCITY_TRANSFER_METHOD_FLIP',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("velocity_transfer_method"))
|
||||
PICFLIP_ratio = FloatProperty(
|
||||
name="PIC/FLIP Ratio",
|
||||
description="Ratio of PIC velocity to FLIP velocity update mixture."
|
||||
" PIC velocity method is not very accurate, but stable. FLIP"
|
||||
" velocity method is very accurate, but less stable. Using a"
|
||||
" value of 0.0 results in a completely FLIP simulator, while"
|
||||
" using a value of 1.0 results in a completely PIC simulator",
|
||||
min=0.0, max=1.0,
|
||||
default=0.05,
|
||||
precision=2,
|
||||
subtype='FACTOR',
|
||||
); exec(conv("PICFLIP_ratio"))
|
||||
PICAPIC_ratio = FloatProperty(
|
||||
name="PIC/APIC Ratio",
|
||||
description="Placeholder",
|
||||
min=0.0, max=1.0,
|
||||
default=0.00,
|
||||
precision=2,
|
||||
subtype='FACTOR',
|
||||
); exec(conv("PICAPIC_ratio"))
|
||||
CFL_condition_number = IntProperty(
|
||||
name="Safety Factor (CFL Number)",
|
||||
description="Maximum number of voxels that a particle can travel"
|
||||
" in a single substep. A larger number may speed up simulation"
|
||||
" baking by reducing the number of required substeps at the"
|
||||
" cost of simulation accuracy",
|
||||
min=1, max=30,
|
||||
default=5,
|
||||
); exec(conv("CFL_condition_number"))
|
||||
enable_extreme_velocity_removal = BoolProperty(
|
||||
name="Remove particles with extreme velocities",
|
||||
description="Attempt to remove extreme particle velocities that"
|
||||
" cause the simulator to exceed the maximum number of allowed"
|
||||
" frame substeps. Enabling this option may prevent simulation"
|
||||
" blow-up in extreme cases. It is not recommended to disable"
|
||||
" this option outside of experimentation and testing. Disabling"
|
||||
" can result in unstable simulations and/or extreme simulation times",
|
||||
default=True,
|
||||
); exec(conv("enable_extreme_velocity_removal"))
|
||||
enable_gpu_features = BoolProperty(
|
||||
name="Enable GPU Features",
|
||||
description="Enable simulator to accelerate some computations"
|
||||
" with your GPU device. TIP: Compare simulation performance"
|
||||
" with this setting on/off to test what is faster on your"
|
||||
" hardware setup. Note: you may only notice a difference on"
|
||||
" higher resolution simulations",
|
||||
default=True
|
||||
); exec(conv("enable_gpu_features"))
|
||||
num_threads_auto_detect = IntProperty(
|
||||
name="Threads",
|
||||
description="Number of threads to use simultaneously while simulating",
|
||||
min=1, max=1024,
|
||||
default=1,
|
||||
); exec(conv("num_threads_auto_detect"))
|
||||
num_threads_fixed = IntProperty(
|
||||
name="Threads",
|
||||
description="Number of threads to use simultaneously while simulating",
|
||||
min=1, max=1024,
|
||||
default=4,
|
||||
); exec(conv("num_threads_fixed"))
|
||||
threading_mode = EnumProperty(
|
||||
name="Threading Mode",
|
||||
description="Determing the amount of simulation threads used",
|
||||
items=types.threading_modes,
|
||||
default='THREADING_MODE_AUTO_DETECT',
|
||||
update=lambda self, context: self.initialize_num_threads_auto_detect(),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("threading_mode"))
|
||||
enable_fracture_optimization = BoolProperty(
|
||||
name="Enable Fracture Optimizations",
|
||||
description="Enable optimizations when using animated fracture simulations as"
|
||||
" FLIP obstacles. These optimizations can greatly improve simulation performance"
|
||||
" when there are a large number of small separate FLIP Obstacle objects, such"
|
||||
" as in Cell Fracture and RBDLab simulations. Not recommended for fracture simulations"
|
||||
" where all pieces are contained within a single FLIP Obstacle as this can harm performance. Enabling"
|
||||
" this option can increase memory requirements",
|
||||
default = False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fracture_optimization"))
|
||||
enable_asynchronous_meshing = BoolProperty(
|
||||
name="Enable Async Meshing",
|
||||
description="Run mesh generation process in a separate thread while"
|
||||
" the simulation is running. May increase simulation performance"
|
||||
" but will use more RAM if enabled",
|
||||
default = True,
|
||||
); exec(conv("enable_asynchronous_meshing"))
|
||||
precompute_static_obstacles = BoolProperty(
|
||||
name="Precompute Static Obstacles",
|
||||
description="Precompute data for static obstacles. If enabled,"
|
||||
" the simulator will avoid recomputing data for non-animated"
|
||||
" obstacles. Increases simulation performance but will use"
|
||||
" more RAM if enabled",
|
||||
default = True,
|
||||
); exec(conv("precompute_static_obstacles"))
|
||||
reserve_temporary_grids = BoolProperty(
|
||||
name="Reserve Temporary Grid Memory",
|
||||
description="Reserve space in memory for temporary grids. Increases"
|
||||
" simulation performance for scenes with animated or keyframed"
|
||||
" obstacles but will use more RAM if enabled",
|
||||
default = True,
|
||||
); exec(conv("reserve_temporary_grids"))
|
||||
disable_changing_topology_warning = BoolProperty(
|
||||
name="Disable Changing Topology Warning",
|
||||
description="Disable warning that is displayed when exporting an"
|
||||
" animated mesh with changing topology. WARNING: mesh velocity"
|
||||
" data cannot be computed for meshes that change topology. This may"
|
||||
" result in unexpected object-fluid interaction as these objects will"
|
||||
" not be able to push around the fluid",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("disable_changing_topology_warning"))
|
||||
|
||||
surface_tension_substeps_exceeded_tooltip = BoolProperty(
|
||||
name="Warning: Not Enough Max Substeps",
|
||||
description="The estimated number of Surface Tension substeps per frame exceeds the Max Frame"
|
||||
" Substeps value. This can cause an unstable simulation. Either decrease the amount of"
|
||||
" Surface Tension in the FLIP Fluid World panel to lower the number of required substeps or"
|
||||
" increase the number of allowed Max Frame Substeps in the FLIP Fluid Advanced panel",
|
||||
default=True,
|
||||
); exec(conv("surface_tension_substeps_exceeded_tooltip"))
|
||||
|
||||
frame_substeps_expanded = BoolProperty(default=True); exec(conv("frame_substeps_expanded"))
|
||||
simulation_method_expanded = BoolProperty(default=True); exec(conv("simulation_method_expanded"))
|
||||
simulation_stability_expanded = BoolProperty(default=False); exec(conv("simulation_stability_expanded"))
|
||||
multithreading_expanded = BoolProperty(default=True); exec(conv("multithreading_expanded"))
|
||||
warnings_and_errors_expanded = BoolProperty(default=False); exec(conv("warnings_and_errors_expanded"))
|
||||
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
add = registry.add_property
|
||||
add(path + ".min_max_time_steps_per_frame", "Min-Max Time Steps", group_id=0)
|
||||
add(path + ".enable_adaptive_obstacle_time_stepping", "Adaptive Obstacle Stepping", group_id=0)
|
||||
add(path + ".enable_adaptive_force_field_time_stepping", "Adaptive Force Field Stepping", group_id=0)
|
||||
add(path + ".particle_jitter_factor", "Jitter Factor", group_id=0)
|
||||
add(path + ".jitter_surface_particles", "Jitter Surface Particles", group_id=0)
|
||||
add(path + ".pressure_solver_max_iterations", "Pressure Solver Iterations", group_id=0)
|
||||
add(path + ".viscosity_solver_max_iterations", "Viscosity Solver Iterations", group_id=0)
|
||||
add(path + ".velocity_transfer_method", "Velocity Transfer Method", group_id=0)
|
||||
add(path + ".PICFLIP_ratio", "PIC/FLIP Ratio", group_id=0)
|
||||
add(path + ".PICAPIC_ratio", "PIC/APIC Ratio", group_id=0)
|
||||
add(path + ".CFL_condition_number", "CFL", group_id=0)
|
||||
add(path + ".enable_extreme_velocity_removal", "Enable Extreme Velocity Removal", group_id=0)
|
||||
add(path + ".enable_gpu_features", "Enable GPU Features", group_id=1)
|
||||
add(path + ".threading_mode", "Threading Mode", group_id=1)
|
||||
add(path + ".num_threads_fixed", "Num Threads (fixed)", group_id=1)
|
||||
add(path + ".enable_asynchronous_meshing", "Async Meshing", group_id=1)
|
||||
add(path + ".enable_fracture_optimization", "Enable Fracture Optimization", group_id=1)
|
||||
add(path + ".precompute_static_obstacles", "Precompute Static Obstacles", group_id=1)
|
||||
add(path + ".reserve_temporary_grids", "Reserve Temporary Grid Memory", group_id=1)
|
||||
add(path + ".disable_changing_topology_warning", "Disable Changing Topology Warning", group_id=1)
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self.initialize_num_threads_auto_detect()
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self.initialize_num_threads_auto_detect()
|
||||
|
||||
|
||||
def initialize_num_threads_auto_detect(self):
|
||||
original_threads_mode = bpy.context.scene.render.threads_mode
|
||||
bpy.context.scene.render.threads_mode = 'AUTO'
|
||||
self.num_threads_auto_detect = bpy.context.scene.render.threads
|
||||
bpy.context.scene.render.threads_mode = original_threads_mode
|
||||
|
||||
|
||||
def _update_min_time_steps_per_frame(self, context):
|
||||
if self.min_time_steps_per_frame > self.max_time_steps_per_frame:
|
||||
self.max_time_steps_per_frame = self.min_time_steps_per_frame
|
||||
|
||||
def _update_max_time_steps_per_frame(self, context):
|
||||
if self.max_time_steps_per_frame < self.min_time_steps_per_frame:
|
||||
self.min_time_steps_per_frame = self.max_time_steps_per_frame
|
||||
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DomainAdvancedProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(DomainAdvancedProperties)
|
||||
@@ -0,0 +1,186 @@
|
||||
# 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, os, json
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
PointerProperty,
|
||||
StringProperty
|
||||
)
|
||||
|
||||
from .. import types
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
SAVESTATE_ENUMS = []
|
||||
IS_SAVESTATE_ENUMS_INITIALIZED = False
|
||||
|
||||
|
||||
class DomainBakeProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
is_export_operator_running = BoolProperty(default=False); exec(conv("is_export_operator_running"))
|
||||
is_export_operator_cancelled = BoolProperty(default=False); exec(conv("is_export_operator_cancelled"))
|
||||
export_progress = FloatProperty(default=0.0); exec(conv("export_progress"))
|
||||
export_stage = StringProperty(default=""); exec(conv("export_stage"))
|
||||
|
||||
export_filename = StringProperty(default='flipdata.sim'); exec(conv("export_filename"))
|
||||
export_directory_name = StringProperty(default='export'); exec(conv("export_directory_name"))
|
||||
export_filepath = StringProperty(default=""); exec(conv("export_filepath"))
|
||||
export_success = BoolProperty(default=False); exec(conv("export_success"))
|
||||
|
||||
is_simulation_running = BoolProperty(default=False); exec(conv("is_simulation_running"))
|
||||
bake_progress = FloatProperty(default=0.0); exec(conv("bake_progress"))
|
||||
is_bake_initialized = BoolProperty(default=False); exec(conv("is_bake_initialized"))
|
||||
is_bake_cancelled = BoolProperty(default=False); exec(conv("is_bake_cancelled"))
|
||||
num_baked_frames = IntProperty(default=0); exec(conv("num_baked_frames"))
|
||||
|
||||
is_autosave_available = BoolProperty(default=False); exec(conv("is_autosave_available"))
|
||||
is_autosave_last_frame = BoolProperty(default=False); exec(conv("is_autosave_last_frame"))
|
||||
is_safe_to_exit = BoolProperty(default=False); exec(conv("is_safe_to_exit"))
|
||||
autosave_frame_id = IntProperty(default=-1); exec(conv("autosave_frame_id"))
|
||||
autosave_frame = IntProperty(default=-1); exec(conv("autosave_frame"))
|
||||
|
||||
original_frame_start = IntProperty(
|
||||
name="Start Frame",
|
||||
description="First frame of the simulation cache. Cannot be changed"
|
||||
" after beginning a simulation",
|
||||
default=-1,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("original_frame_start"))
|
||||
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
pass
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self._check_properties_valid()
|
||||
self.check_autosave()
|
||||
|
||||
|
||||
def check_autosave(self):
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
|
||||
cache_directory = dprops.cache.get_cache_abspath()
|
||||
autosave_directory = os.path.join(cache_directory, "savestates", "autosave")
|
||||
|
||||
if not os.path.isdir(autosave_directory):
|
||||
self.is_autosave_available = False
|
||||
return
|
||||
|
||||
autosave_info_file = os.path.join(autosave_directory, "autosave.state")
|
||||
if not os.path.isfile(autosave_info_file):
|
||||
self.is_autosave_available = False
|
||||
return
|
||||
|
||||
try:
|
||||
with open(autosave_info_file, 'r', encoding='utf-8') as f:
|
||||
autosave_info = json.loads(f.read())
|
||||
except:
|
||||
# Autosave file might not be completely written. Wait and try again.
|
||||
import time
|
||||
time.sleep(0.25)
|
||||
try:
|
||||
with open(autosave_info_file, 'r', encoding='utf-8') as f:
|
||||
autosave_info = json.loads(f.read())
|
||||
except:
|
||||
# skip this autosave frame if it still cannot be read. The autosave
|
||||
# should be able to be reinitialized when reloading the .blend file.
|
||||
return
|
||||
|
||||
self.is_autosave_available = True
|
||||
self.autosave_frame_id = autosave_info['frame_id']
|
||||
self.autosave_frame = autosave_info['frame']
|
||||
self.is_autosave_last_frame = autosave_info['frame_id'] == autosave_info['last_frame_id']
|
||||
self.original_frame_start = autosave_info['frame_start']
|
||||
|
||||
self._update_savestate_enums()
|
||||
|
||||
|
||||
def frame_complete_callback(self):
|
||||
self.check_autosave()
|
||||
|
||||
|
||||
def get_savestate_enums(self):
|
||||
global SAVESTATE_ENUMS
|
||||
global IS_SAVESTATE_ENUMS_INITIALIZED
|
||||
if not IS_SAVESTATE_ENUMS_INITIALIZED:
|
||||
self._update_savestate_enums()
|
||||
return SAVESTATE_ENUMS
|
||||
|
||||
|
||||
def _check_properties_valid(self):
|
||||
if self.is_simulation_running:
|
||||
self.is_simulation_running = False
|
||||
if self.is_bake_initialized:
|
||||
self.is_bake_initialize = False
|
||||
if self.is_bake_cancelled:
|
||||
self.is_bake_cancelled = False
|
||||
|
||||
def _update_savestate_enums(self):
|
||||
global SAVESTATE_ENUMS
|
||||
global IS_SAVESTATE_ENUMS_INITIALIZED
|
||||
SAVESTATE_ENUMS = []
|
||||
IS_SAVESTATE_ENUMS_INITIALIZED = True
|
||||
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
cache_directory = dprops.cache.get_cache_abspath()
|
||||
savestates_directory = os.path.join(cache_directory, "savestates")
|
||||
|
||||
# Note: os.listdir is not guaranteed to be sorted
|
||||
subdirs = [d for d in os.listdir(savestates_directory) if os.path.isdir(os.path.join(savestates_directory, d))]
|
||||
subdirs.sort()
|
||||
|
||||
if "autosave" in subdirs:
|
||||
subdirs.remove("autosave")
|
||||
autosave_frame = self.autosave_frame
|
||||
|
||||
savestate_frames = []
|
||||
for d in subdirs:
|
||||
try:
|
||||
savestate_frames.append(int(d[-6:]))
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if autosave_frame in savestate_frames:
|
||||
savestate_frames.remove(autosave_frame)
|
||||
|
||||
for frameno in savestate_frames:
|
||||
name ="Resume from frame " + str(frameno + 1)
|
||||
if frameno > autosave_frame:
|
||||
name += " (outdated)"
|
||||
e = (str(frameno), name, "")
|
||||
SAVESTATE_ENUMS.append(e)
|
||||
e = (str(autosave_frame), "Resume from frame " + str(autosave_frame + 1) + " (most recent)", "")
|
||||
SAVESTATE_ENUMS.append(e)
|
||||
|
||||
try:
|
||||
dprops.simulation.selected_savestate = str(autosave_frame)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DomainBakeProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(DomainBakeProperties)
|
||||
@@ -0,0 +1,259 @@
|
||||
# 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, os, shutil, platform
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
PointerProperty,
|
||||
StringProperty
|
||||
)
|
||||
|
||||
from .. import exit_handler
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
class DomainCacheProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
temp_directory = vcu.get_blender_preferences_temporary_directory()
|
||||
default_cache_directory_str = os.path.join(temp_directory, "untitled_flip_fluid_cache")
|
||||
|
||||
option_path_supports_blend_relative = set()
|
||||
if vcu.is_blender_45():
|
||||
# required for relative path support in Blender 4.5+
|
||||
# https://docs.blender.org/api/4.5/bpy_types_enum_items/property_flag_items.html#rna-enum-property-flag-items
|
||||
option_path_supports_blend_relative = {'PATH_SUPPORTS_BLEND_RELATIVE'}
|
||||
|
||||
|
||||
cache_directory = StringProperty(
|
||||
name="",
|
||||
description="Simulation files will be saved to this directory."
|
||||
" It is recommended to save your .blend file before beginning a simulation",
|
||||
default=default_cache_directory_str,
|
||||
subtype='DIR_PATH',
|
||||
options=option_path_supports_blend_relative,
|
||||
update=lambda self, context: self._update_cache_directory(context),
|
||||
); exec(conv("cache_directory"))
|
||||
default_cache_directory = StringProperty(
|
||||
default=default_cache_directory_str,
|
||||
subtype='DIR_PATH',
|
||||
options=option_path_supports_blend_relative,
|
||||
); exec(conv("default_cache_directory"))
|
||||
move_cache_directory = StringProperty(
|
||||
name="",
|
||||
description="Cache directory will be moved to this location",
|
||||
default=temp_directory,
|
||||
subtype='DIR_PATH',
|
||||
options=option_path_supports_blend_relative,
|
||||
); exec(conv("move_cache_directory"))
|
||||
rename_cache_directory = StringProperty(
|
||||
name="",
|
||||
description="Cache directory will be renamed to this value",
|
||||
default="untitled_flip_fluid_cache",
|
||||
); exec(conv("rename_cache_directory"))
|
||||
copy_cache_directory = StringProperty(
|
||||
name="",
|
||||
description="Cache directory contents will be copied to this location",
|
||||
default=default_cache_directory_str,
|
||||
subtype='DIR_PATH',
|
||||
options=option_path_supports_blend_relative,
|
||||
); exec(conv("copy_cache_directory"))
|
||||
clear_cache_directory_logs = BoolProperty(
|
||||
name="Clear log files",
|
||||
description="Also delete log files when freeing cache directory",
|
||||
default=False,
|
||||
); exec(conv("clear_cache_directory_logs"))
|
||||
clear_cache_directory_export = BoolProperty(
|
||||
name="Clear export files",
|
||||
description="Also delete exported settings and objects when freeing cache directory",
|
||||
default=False,
|
||||
); exec(conv("clear_cache_directory_export"))
|
||||
logfile_name = StringProperty(
|
||||
default=os.path.join(temp_directory, "flip_fluid_log.txt"),
|
||||
subtype='FILE_NAME',
|
||||
); exec(conv("logfile_name"))
|
||||
linked_geometry_directory = StringProperty(
|
||||
name="",
|
||||
description="select an existing cache directory. Link exported geometry data from another cache directory."
|
||||
" Use if you want to re-use exported geometry that is located in another cache. Useful if you have a"
|
||||
" lot of geometry in your scene that you do not want to re-export",
|
||||
default="",
|
||||
subtype='DIR_PATH',
|
||||
options=option_path_supports_blend_relative,
|
||||
); exec(conv("linked_geometry_directory"))
|
||||
|
||||
is_cache_directory_set = BoolProperty(default=False); exec(conv("is_cache_directory_set"))
|
||||
|
||||
cache_directory_expanded = BoolProperty(default=True); exec(conv("cache_directory_expanded"))
|
||||
link_exported_geometry_expanded = BoolProperty(default=False); exec(conv("link_exported_geometry_expanded"))
|
||||
cache_operators_expanded = BoolProperty(default=False); exec(conv("cache_operators_expanded"))
|
||||
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
pass
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self._check_cache_directory()
|
||||
|
||||
|
||||
def get_abspath(self, path_prop):
|
||||
relprefix = "//"
|
||||
path = path_prop
|
||||
if path_prop.startswith(relprefix):
|
||||
path_prop = path_prop[len(relprefix):]
|
||||
blend_directory = os.path.dirname(bpy.data.filepath)
|
||||
path = os.path.join(blend_directory, path_prop)
|
||||
path = os.path.abspath(os.path.normpath(path))
|
||||
if platform.system() != "Windows":
|
||||
# Blend file may have been saved on windows and opened on macOS/Linux. In this case,
|
||||
# backslash should be converted to forward slash.
|
||||
path = os.path.join(*path.split("\\"))
|
||||
return path
|
||||
|
||||
|
||||
def get_cache_abspath(self):
|
||||
return self.get_abspath(self.cache_directory)
|
||||
|
||||
|
||||
def get_linked_geometry_abspath(self):
|
||||
if not self.linked_geometry_directory:
|
||||
return None
|
||||
return self.get_abspath(self.linked_geometry_directory)
|
||||
|
||||
|
||||
def is_linked_geometry_directory(self):
|
||||
linked_geometry_directory = self.get_linked_geometry_abspath()
|
||||
if linked_geometry_directory is None:
|
||||
return False
|
||||
|
||||
if not os.path.isdir(linked_geometry_directory):
|
||||
return False
|
||||
|
||||
linked_export_directory = os.path.join(linked_geometry_directory, "export")
|
||||
if os.path.isdir(linked_export_directory):
|
||||
return True
|
||||
else:
|
||||
database_filename = "export_data.sqlite3"
|
||||
incorrect_filepath_test = os.path.join(linked_geometry_directory, database_filename)
|
||||
if os.path.isfile(incorrect_filepath_test):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_geometry_database_abspath(self, export_directory=None, database_filename=None):
|
||||
if export_directory is None:
|
||||
export_directory = os.path.join(self.get_cache_abspath(), "export")
|
||||
if database_filename is None:
|
||||
database_filename = "export_data.sqlite3"
|
||||
|
||||
default_filepath = os.path.join(export_directory, database_filename)
|
||||
|
||||
linked_geometry_directory = self.get_linked_geometry_abspath()
|
||||
if linked_geometry_directory is None:
|
||||
return default_filepath
|
||||
|
||||
if not os.path.isdir(linked_geometry_directory):
|
||||
return default_filepath
|
||||
|
||||
linked_export_directory = os.path.join(linked_geometry_directory, "export")
|
||||
if os.path.isdir(linked_export_directory):
|
||||
return os.path.join(linked_export_directory, database_filename)
|
||||
else:
|
||||
incorrect_filepath_test = os.path.join(linked_geometry_directory, database_filename)
|
||||
if os.path.isfile(incorrect_filepath_test):
|
||||
return incorrect_filepath_test
|
||||
else:
|
||||
return default_filepath
|
||||
|
||||
|
||||
def mark_cache_directory_set(self):
|
||||
self.is_cache_directory_set = True
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self._check_cache_directory()
|
||||
|
||||
|
||||
def save_post(self):
|
||||
self._check_cache_directory()
|
||||
|
||||
|
||||
def _update_cache_directory(self, context):
|
||||
self.is_cache_directory_set = True
|
||||
dprops = context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
|
||||
relprefix = "//"
|
||||
if self.cache_directory == "" or self.cache_directory == relprefix:
|
||||
# Don't want the user to set an empty path
|
||||
if bpy.data.filepath:
|
||||
base = os.path.basename(bpy.data.filepath)
|
||||
save_file = os.path.splitext(base)[0]
|
||||
cache_folder_parent = os.path.dirname(bpy.data.filepath)
|
||||
|
||||
cache_folder = save_file + "_flip_fluid_cache"
|
||||
cache_path = os.path.join(cache_folder_parent, cache_folder)
|
||||
relpath = os.path.relpath(cache_path, cache_folder_parent)
|
||||
|
||||
default_cache_directory_str = relprefix + relpath
|
||||
else:
|
||||
temp_directory = vcu.get_blender_preferences_temporary_directory()
|
||||
default_cache_directory_str = os.path.join(temp_directory, "untitled_flip_fluid_cache")
|
||||
self["cache_directory"] = default_cache_directory_str
|
||||
|
||||
dprops.stats.refresh_stats()
|
||||
dprops.bake.check_autosave()
|
||||
exit_handler.set_cache_directory(self.get_cache_abspath())
|
||||
|
||||
|
||||
def _check_cache_directory(self):
|
||||
if self.is_cache_directory_set:
|
||||
return
|
||||
|
||||
base = os.path.basename(bpy.data.filepath)
|
||||
save_file = os.path.splitext(base)[0]
|
||||
if not base or not save_file:
|
||||
directory = self.default_cache_directory
|
||||
if os.path.exists(directory):
|
||||
for i in range(1, 1000):
|
||||
test_directory = directory + str(i)
|
||||
if not os.path.exists(test_directory):
|
||||
directory = test_directory
|
||||
break
|
||||
self.cache_directory = directory
|
||||
self.is_cache_directory_set = False
|
||||
return
|
||||
|
||||
cache_folder_parent = os.path.dirname(bpy.data.filepath)
|
||||
cache_folder = save_file + "_flip_fluid_cache"
|
||||
cache_path = os.path.join(cache_folder_parent, cache_folder)
|
||||
relpath = os.path.relpath(cache_path, cache_folder_parent)
|
||||
|
||||
relprefix = "//"
|
||||
self.cache_directory = relprefix + relpath
|
||||
self.is_cache_directory_set = True
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DomainCacheProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(DomainCacheProperties)
|
||||
@@ -0,0 +1,601 @@
|
||||
# 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, os, sys, platform, traceback
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
BoolVectorProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
IntProperty,
|
||||
StringProperty,
|
||||
PointerProperty,
|
||||
CollectionProperty
|
||||
)
|
||||
|
||||
from .custom_properties import (
|
||||
NewMinMaxFloatProperty
|
||||
)
|
||||
|
||||
from .. import (
|
||||
types,
|
||||
bake
|
||||
)
|
||||
|
||||
from ..operators import draw_grid_operators
|
||||
from ..operators import draw_particles_operators
|
||||
from ..operators import draw_force_field_operators
|
||||
from ..operators import preferences_operators
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
from ..utils import installation_utils
|
||||
from ..objects import flip_fluid_cache
|
||||
from .. import bl_info
|
||||
|
||||
_LOGGING_DISABLED_MESSAGE = "(Blend file logging disabled in host preferences)"
|
||||
|
||||
|
||||
class VersionHistoryItem(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
blender_version = StringProperty(default="-1"); exec(conv("blender_version"))
|
||||
flip_fluids_version = StringProperty(default="-1"); exec(conv("flip_fluids_version"))
|
||||
flip_fluids_label = StringProperty(default="-1"); exec(conv("flip_fluids_label"))
|
||||
operating_system = StringProperty(default="-1"); exec(conv("operating_system"))
|
||||
|
||||
|
||||
def get_info_string(self):
|
||||
return self.blender_version + "\t" + self.flip_fluids_version + "\t" + self.flip_fluids_label + "\t" + self.operating_system
|
||||
|
||||
|
||||
class DomainDebugProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
display_simulation_grid = BoolProperty(
|
||||
name="Display Domain Grid",
|
||||
description="Visualize the domain voxel grid in the 3D viewport."
|
||||
" Try scaling different sides of the domain to better understand how the grid works."
|
||||
" Try enabling the Lock Cell Size option in the FLIP Fluid Simulation panel and compare"
|
||||
" the differences in how the grid changes as the domain is resized",
|
||||
default=False,
|
||||
update=lambda self, context: self._update_display_simulation_grid(context),
|
||||
); exec(conv("display_simulation_grid"))
|
||||
grid_display_mode = EnumProperty(
|
||||
name="Grid Display Mode",
|
||||
description="Type of grid debug info to display",
|
||||
items=types.grid_display_modes,
|
||||
default='GRID_DISPLAY_SIMULATION',
|
||||
update=lambda self, context: self._update_debug_grid_geometry(context),
|
||||
); exec(conv("grid_display_mode"))
|
||||
grid_display_scale = IntProperty(
|
||||
name="Grid Display Scale",
|
||||
description="Number of voxels that a single grid spacing in the"
|
||||
" viewport represents",
|
||||
min = 1, soft_max = 10,
|
||||
default=1,
|
||||
step=1,
|
||||
update=lambda self, context: self._update_debug_grid_geometry(context),
|
||||
); exec(conv("grid_display_scale"))
|
||||
enabled_debug_grids = BoolVectorProperty(
|
||||
name="Enabled Debug Grids",
|
||||
description="Select which debug grids are displayed in the viewport",
|
||||
default=(True, True, True),
|
||||
size=3,
|
||||
subtype='XYZ',
|
||||
update=lambda self, context: self._update_debug_grid_geometry(context),
|
||||
); exec(conv("enabled_debug_grids"))
|
||||
x_grid_color = FloatVectorProperty(
|
||||
name="X Grid Color",
|
||||
subtype='COLOR',
|
||||
default=(0.5, 0.0, 0.0),
|
||||
min=0.0, max=1.0,
|
||||
description="X grid display color"
|
||||
); exec(conv("x_grid_color"))
|
||||
y_grid_color = FloatVectorProperty(
|
||||
name="Y Grid Color",
|
||||
subtype='COLOR',
|
||||
default=(0.0, 0.5, 0.0),
|
||||
min=0.0, max=1.0,
|
||||
description="Y grid display color"
|
||||
); exec(conv("y_grid_color"))
|
||||
z_grid_color = FloatVectorProperty(
|
||||
name="Z Grid Color",
|
||||
subtype='COLOR',
|
||||
default=(0.0, 0.0, 0.5),
|
||||
min=0.0, max=1.0,
|
||||
description="Z grid display color"
|
||||
); exec(conv("z_grid_color"))
|
||||
debug_grid_offsets = FloatVectorProperty(
|
||||
name="Debug Grid Offsets",
|
||||
description="Offset at which an axis' grid is displayed in the viewport",
|
||||
min = 0.0, max = 1.0,
|
||||
default=(0.0, 0.0, 0.0),
|
||||
size=3,
|
||||
step=1,
|
||||
subtype='XYZ',
|
||||
update=lambda self, context: self._update_debug_grid_geometry(context),
|
||||
); exec(conv("debug_grid_offsets"))
|
||||
snap_offsets_to_grid = BoolProperty(
|
||||
name="Snap Offsets to Grid",
|
||||
description="Align debug grids to gridcell locations",
|
||||
default=True,
|
||||
update=lambda self, context: self._update_debug_grid_geometry(context),
|
||||
); exec(conv("snap_offsets_to_grid"))
|
||||
display_domain_bounds = BoolProperty(
|
||||
name="Display Bounds",
|
||||
description="Display the true bounds of the domain object." +
|
||||
" The domain boundary contains a thin solid layer. Enabling" +
|
||||
" this visualization will display the actual fluid region of" +
|
||||
" the domain",
|
||||
default=False,
|
||||
update=lambda self, context: self._update_display_domain_bounds(context),
|
||||
); exec(conv("display_domain_bounds"))
|
||||
domain_bounds_color = FloatVectorProperty(
|
||||
name="Domain Bounds Color",
|
||||
subtype='COLOR',
|
||||
default=(1.0, 1.0, 0.0),
|
||||
min=0.0, max=1.0,
|
||||
description="Color of the domain bounds visualization",
|
||||
update=lambda self, context: self._update_debug_grid_geometry(context),
|
||||
); exec(conv("domain_bounds_color"))
|
||||
|
||||
enable_fluid_particle_debug_output = BoolProperty(
|
||||
name="Enable Fluid Particle Debugging",
|
||||
description="Enable to export simulator fluid particle data and to"
|
||||
" visualize and debug problems with fluid behaviour. Enable"
|
||||
" this option before baking a simulation to use this feature",
|
||||
default=False,
|
||||
update=lambda self, context: self._update_enable_fluid_particle_debug_output(context),
|
||||
); exec(conv("enable_fluid_particle_debug_output"))
|
||||
fluid_particles_visibility = BoolProperty(
|
||||
name="Fluid Particle Visibility",
|
||||
description="Show fluid particles in the viewport",
|
||||
default=True,
|
||||
update=lambda self, context: self._update_enable_fluid_particle_debug_output(context),
|
||||
); exec(conv("fluid_particles_visibility"))
|
||||
low_speed_particle_color = FloatVectorProperty(
|
||||
name="Low Speed Color",
|
||||
subtype='COLOR',
|
||||
default=(0.0, 0.0, 1.0),
|
||||
min=0.0, max=1.0,
|
||||
description="Color for low velocity fluid particles",
|
||||
update=lambda self, context: self._update_debug_particle_geometry(context),
|
||||
); exec(conv("low_speed_particle_color"))
|
||||
high_speed_particle_color = FloatVectorProperty(
|
||||
name="High Speed Color",
|
||||
subtype='COLOR',
|
||||
default=(1.0, 1.0, 1.0),
|
||||
min=0.0, max=1.0,
|
||||
description="Color for high velocity fluid particles",
|
||||
update=lambda self, context: self._update_debug_particle_geometry(context),
|
||||
); exec(conv("high_speed_particle_color"))
|
||||
min_gradient_speed = FloatProperty(
|
||||
name="Low Color Speed",
|
||||
description="Low speed value for visualizing fluid particle velocity",
|
||||
min=0,
|
||||
default=0.0,
|
||||
precision=2,
|
||||
update=lambda self, context: self._update_min_gradient_speed(context),
|
||||
); exec(conv("min_gradient_speed"))
|
||||
max_gradient_speed = FloatProperty(
|
||||
name="High Color Speed",
|
||||
description="High speed value for visualizing fluid particle velocity",
|
||||
min=0,
|
||||
default=5.0,
|
||||
precision=2,
|
||||
update=lambda self, context: self._update_max_gradient_speed(context),
|
||||
); exec(conv("max_gradient_speed"))
|
||||
fluid_particle_gradient_mode = EnumProperty(
|
||||
name="Gradient Mode",
|
||||
description="Type of color gradient",
|
||||
items=types.gradient_interpolation_modes,
|
||||
default='GRADIENT_RGB',
|
||||
update=lambda self, context: self._update_max_gradient_speed(context),
|
||||
); exec(conv("fluid_particle_gradient_mode"))
|
||||
particle_size = IntProperty(
|
||||
name="Particle Size",
|
||||
description="Size to draw particles for visualization",
|
||||
min=1, soft_max=10,
|
||||
default=1,
|
||||
update=lambda self, context: self._update_debug_particle_geometry(context),
|
||||
); exec(conv("particle_size"))
|
||||
particle_draw_aabb = PointerProperty(
|
||||
name="Visualization Bounds",
|
||||
description="If set, only particles inside the object's axis-aligned"
|
||||
" bounding box will be drawn",
|
||||
type=bpy.types.Object,
|
||||
update=lambda self, context: self._update_debug_particle_geometry(context),
|
||||
); exec(conv("particle_draw_aabb"))
|
||||
|
||||
export_force_field = BoolProperty(
|
||||
name="Enable Force Field Debugging",
|
||||
description="Enable to export simulator force field data and to"
|
||||
" visualize force field lines. Enable this option before baking"
|
||||
" a simulation to use this feature",
|
||||
default=False,
|
||||
update=lambda self, context: self._update_export_force_field(context),
|
||||
); exec(conv("export_force_field"))
|
||||
force_field_visibility = BoolProperty(
|
||||
name="Force Field Visibility",
|
||||
description="Show force fields in the viewport",
|
||||
default=True,
|
||||
update=lambda self, context: self._update_export_force_field(context),
|
||||
); exec(conv("force_field_visibility"))
|
||||
low_force_field_color = FloatVectorProperty(
|
||||
name="Low Force Color",
|
||||
subtype='COLOR',
|
||||
default=(1.0, 1.0, 1.0),
|
||||
min=0.0, max=1.0,
|
||||
description="Color for low strength forces",
|
||||
update=lambda self, context: self._update_export_force_field(context),
|
||||
); exec(conv("low_force_field_color"))
|
||||
high_force_field_color = FloatVectorProperty(
|
||||
name="High Force Color",
|
||||
subtype='COLOR',
|
||||
default=(1.0, 0.0, 0.0),
|
||||
min=0.0, max=1.0,
|
||||
description="Color for high strength forces",
|
||||
update=lambda self, context: self._update_export_force_field(context),
|
||||
); exec(conv("high_force_field_color"))
|
||||
min_gradient_force = FloatProperty(
|
||||
name="Low Color Force",
|
||||
description="Low force strength value for visualizing force field lines",
|
||||
min=0,
|
||||
default=0.0,
|
||||
precision=2,
|
||||
update=lambda self, context: self._update_min_gradient_force(context),
|
||||
); exec(conv("min_gradient_force"))
|
||||
max_gradient_force = FloatProperty(
|
||||
name="High Color Force",
|
||||
description="High force strength value for visualizing force field lines",
|
||||
min=0,
|
||||
default=15.0,
|
||||
precision=2,
|
||||
update=lambda self, context: self._update_max_gradient_force(context),
|
||||
); exec(conv("max_gradient_force"))
|
||||
force_field_gradient_mode = EnumProperty(
|
||||
name="Gradient Mode",
|
||||
description="Type of color gradient",
|
||||
items=types.gradient_interpolation_modes,
|
||||
default='GRADIENT_RGB',
|
||||
update=lambda self, context: self._update_max_gradient_force(context),
|
||||
); exec(conv("force_field_gradient_mode"))
|
||||
force_field_display_amount = IntProperty(
|
||||
name="Display Amount",
|
||||
description="Amount of force field lines to display in the viewport",
|
||||
min=0, max=100,
|
||||
default=25,
|
||||
subtype='PERCENTAGE',
|
||||
update=lambda self, context: self._update_force_field_geometry(context),
|
||||
); exec(conv("force_field_display_amount"))
|
||||
force_field_line_size = IntProperty(
|
||||
name="Line Size",
|
||||
description="Line thickness for force field visualization",
|
||||
min=1, soft_max=10,
|
||||
default=2,
|
||||
update=lambda self, context: self._update_force_field_geometry(context),
|
||||
); exec(conv("force_field_line_size"))
|
||||
|
||||
export_internal_obstacle_mesh = BoolProperty(
|
||||
name="Enable Obstacle Debugging",
|
||||
description="Enable to export simulator obstacle data"
|
||||
" and to visualize and debug problems with obstacles."
|
||||
" Enable this setting before baking a simulation to"
|
||||
" use this feature",
|
||||
default=False,
|
||||
update=lambda self, context: self._update_export_internal_obstacle_mesh(context),
|
||||
); exec(conv("export_internal_obstacle_mesh"))
|
||||
internal_obstacle_mesh_visibility = BoolProperty(
|
||||
name="Obstacle Debugging Visibility",
|
||||
description="Show obstacle debug mesh in the viewport. If disabled, this prevents debug obstacle"
|
||||
" mesh data from being loaded into Blender. Frame must be reloaded after enabling this option for"
|
||||
" mesh to reload and become visible",
|
||||
default=True,
|
||||
update=lambda self, context: self._update_export_internal_obstacle_mesh(context),
|
||||
); exec(conv("internal_obstacle_mesh_visibility"))
|
||||
|
||||
display_console_output = BoolProperty(
|
||||
name="Display Console Output",
|
||||
description="Display simulation info in the Blender system console",
|
||||
default=True,
|
||||
update=lambda self, context: self._update_display_console_output(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("display_console_output"))
|
||||
|
||||
display_render_passes_console_output = BoolProperty(
|
||||
name="Display Render Passes Console Output",
|
||||
description="Display Compositing Tools Passes Rendering debug info in the Blender system console",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("display_render_passes_console_output"))
|
||||
|
||||
is_draw_debug_grid_operator_running = BoolProperty(default=False); exec(conv("is_draw_debug_grid_operator_running"))
|
||||
is_draw_gl_particles_operator_running = BoolProperty(default=False); exec(conv("is_draw_gl_particles_operator_running"))
|
||||
is_draw_gl_force_field_operator_running = BoolProperty(default=False); exec(conv("is_draw_gl_force_field_operator_running"))
|
||||
|
||||
grid_display_settings_expanded = BoolProperty(default=True); exec(conv("grid_display_settings_expanded"))
|
||||
particle_debug_settings_expanded = BoolProperty(default=False); exec(conv("particle_debug_settings_expanded"))
|
||||
force_field_debug_settings_expanded = BoolProperty(default=False); exec(conv("force_field_debug_settings_expanded"))
|
||||
|
||||
version_history = CollectionProperty(type=VersionHistoryItem); exec(conv("version_history"))
|
||||
system_info = StringProperty(default=""); exec(conv("system_info"))
|
||||
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
add = registry.add_property
|
||||
add(path + ".display_simulation_grid", "Display Domain Grid", group_id=0)
|
||||
add(path + ".grid_display_mode", "Grid Display Mode", group_id=0)
|
||||
add(path + ".grid_display_scale", "Grid Scale", group_id=0)
|
||||
add(path + ".enabled_debug_grids", "Draw Grids", group_id=0)
|
||||
add(path + ".x_grid_color", "X Grid Color", group_id=0)
|
||||
add(path + ".y_grid_color", "Y Grid Color", group_id=0)
|
||||
add(path + ".z_grid_color", "Z Grid Color", group_id=0)
|
||||
add(path + ".debug_grid_offsets", "Grid Offsets", group_id=0)
|
||||
add(path + ".snap_offsets_to_grid", "Snap Offsets to Grid", group_id=0)
|
||||
add(path + ".enable_fluid_particle_debug_output", "Enable Fluid Particle Debugging", group_id=1)
|
||||
add(path + ".fluid_particles_visibility", "Fluid Particle Visibility", group_id=1)
|
||||
add(path + ".low_speed_particle_color", "Low Velocity Particle Color", group_id=1)
|
||||
add(path + ".high_speed_particle_color", "High Velocity Particle Color", group_id=1)
|
||||
add(path + ".min_gradient_speed", "Low-High Particle Velocities", group_id=1)
|
||||
add(path + ".max_gradient_speed", "Low-High Particle Velocities", group_id=1)
|
||||
add(path + ".fluid_particle_gradient_mode", "Fluid Speed Gradient Mode", group_id=1)
|
||||
add(path + ".particle_size", "Particle Size", group_id=1)
|
||||
add(path + ".low_force_field_color", "Low Force Field Color", group_id=2)
|
||||
add(path + ".high_force_field_color", "High Force Field Color", group_id=2)
|
||||
add(path + ".min_gradient_force", "Low-High Force Strength", group_id=2)
|
||||
add(path + ".max_gradient_force", "Low-High Force Strength", group_id=2)
|
||||
add(path + ".force_field_gradient_mode", "Fluid Speed Gradient Mode", group_id=2)
|
||||
add(path + ".export_force_field", "Enable Force Field Debugging", group_id=2)
|
||||
add(path + ".force_field_visibility", "Force Field Visibility", group_id=2)
|
||||
add(path + ".force_field_line_size", "Line Size", group_id=2)
|
||||
add(path + ".export_internal_obstacle_mesh", "Enable Obstacle Debugging", group_id=3)
|
||||
add(path + ".internal_obstacle_mesh_visibility", "Obstacle Debugging Visibility", group_id=3)
|
||||
add(path + ".display_console_output", "Display Console Output", group_id=3)
|
||||
add(path + ".display_render_passes_console_output", "Display Render Passes Console Output", group_id=3)
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self.print_system_info()
|
||||
|
||||
self.is_draw_debug_grid_operator_running = False
|
||||
self.is_draw_gl_particles_operator_running = False
|
||||
|
||||
# Draw operators should not be run when Blender is launched from the command line
|
||||
# in background mode - context will be incorrect.
|
||||
if not bpy.app.background:
|
||||
if self.enable_fluid_particle_debug_output:
|
||||
self._update_debug_particle_geometry(bpy.context)
|
||||
bpy.ops.flip_fluid_operators.draw_gl_particles('INVOKE_DEFAULT')
|
||||
if self.is_simulation_grid_debugging_enabled():
|
||||
self._update_debug_grid_geometry(bpy.context)
|
||||
bpy.ops.flip_fluid_operators.draw_debug_grid('INVOKE_DEFAULT')
|
||||
if self.export_force_field:
|
||||
self._update_force_field_geometry(bpy.context)
|
||||
bpy.ops.flip_fluid_operators.draw_force_field('INVOKE_DEFAULT')
|
||||
|
||||
|
||||
def print_system_and_blend_info(self):
|
||||
try:
|
||||
preferences = vcu.get_addon_preferences()
|
||||
if preferences.enable_support_tools and preferences.enable_blend_file_logging:
|
||||
print("*** Developer Tools: FLIP Fluids Version History ***")
|
||||
self.print_version_history()
|
||||
print()
|
||||
print("*** Developer Tools: FLIP Fluids System Info ***")
|
||||
self.print_system_info()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
||||
|
||||
def scene_update_post(self, scene):
|
||||
self._update_debug_grid_geometry(bpy.context)
|
||||
|
||||
|
||||
def save_pre(self):
|
||||
# We don't want a potential error/failure during a save
|
||||
try:
|
||||
preferences = vcu.get_addon_preferences()
|
||||
if preferences.enable_blend_file_logging:
|
||||
# Save Version History
|
||||
if vcu.is_blender_42():
|
||||
bl_info_dict = bl_info
|
||||
else:
|
||||
bl_info_dict = sys.modules[installation_utils.get_module_name()].bl_info
|
||||
|
||||
vdata = self.version_history.add()
|
||||
vdata.blender_version = bpy.app.version_string
|
||||
vdata.flip_fluids_version = str(bl_info_dict.get('version', (-1, -1, -1)))
|
||||
vdata.flip_fluids_label = bl_info_dict.get('description', "-1")
|
||||
vdata.operating_system = platform.system()
|
||||
if len(self.version_history) > 250:
|
||||
self.version_history.remove(0)
|
||||
|
||||
# Save System and Blend File Info
|
||||
self.system_info = preferences_operators.get_system_info_string()
|
||||
else:
|
||||
global _LOGGING_DISABLED_MESSAGE
|
||||
self.version_history.clear()
|
||||
self.system_info = "No System and Blend File Info " + _LOGGING_DISABLED_MESSAGE
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
print(e)
|
||||
|
||||
|
||||
def print_version_history(self):
|
||||
if len(self.version_history) == 0:
|
||||
global _LOGGING_DISABLED_MESSAGE
|
||||
msg = "No version history"
|
||||
if _LOGGING_DISABLED_MESSAGE in self.system_info:
|
||||
msg += " " + _LOGGING_DISABLED_MESSAGE
|
||||
print(msg)
|
||||
else:
|
||||
for idx,vdata in enumerate(self.version_history):
|
||||
print(idx, vdata.get_info_string())
|
||||
|
||||
|
||||
def print_system_info(self):
|
||||
if len(self.system_info) == 0:
|
||||
print("No System and Blend File Info")
|
||||
else:
|
||||
print(self.system_info)
|
||||
|
||||
|
||||
def get_last_saved_blender_version(self):
|
||||
unresolved_version = (-1, -1, -1)
|
||||
if len(self.version_history) == 0:
|
||||
return unresolved_version
|
||||
|
||||
try:
|
||||
valid_chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.']
|
||||
bl_version_str = self.version_history[-1].blender_version
|
||||
bl_version_str = ''.join([c for c in bl_version_str if c in valid_chars])
|
||||
version_numbers = bl_version_str.split('.')
|
||||
major = int(version_numbers[0])
|
||||
minor = int(version_numbers[1])
|
||||
revision = int(version_numbers[2])
|
||||
version_tuple = (major, minor, revision)
|
||||
except:
|
||||
return unresolved_version
|
||||
|
||||
return version_tuple
|
||||
|
||||
|
||||
def clear_version_history(self):
|
||||
self.version_history.clear()
|
||||
|
||||
|
||||
def clear_system_info(self):
|
||||
self.system_info = ""
|
||||
|
||||
|
||||
def get_particle_draw_aabb_object(self):
|
||||
obj = None
|
||||
try:
|
||||
obj = self.particle_draw_aabb
|
||||
except:
|
||||
pass
|
||||
return obj
|
||||
|
||||
|
||||
def is_simulation_grid_debugging_enabled(self):
|
||||
return self.display_simulation_grid or self.display_domain_bounds
|
||||
|
||||
|
||||
def _update_enable_fluid_particle_debug_output(self, context):
|
||||
dprops = context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
|
||||
if self.enable_fluid_particle_debug_output:
|
||||
self._update_debug_particle_geometry(context)
|
||||
dprops.mesh_cache.gl_particles.enable()
|
||||
if not self.is_draw_gl_particles_operator_running:
|
||||
bpy.ops.flip_fluid_operators.draw_gl_particles('INVOKE_DEFAULT')
|
||||
else:
|
||||
dprops.mesh_cache.gl_particles.disable()
|
||||
|
||||
|
||||
def _update_export_internal_obstacle_mesh(self, context):
|
||||
dprops = context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
|
||||
if self.export_internal_obstacle_mesh and self.internal_obstacle_mesh_visibility:
|
||||
objects_to_initialize = flip_fluid_cache.EnabledMeshCacheObjects()
|
||||
objects_to_initialize.debug_obstacle = True
|
||||
dprops.mesh_cache.initialize_cache_objects(objects_to_initialize)
|
||||
elif self.export_internal_obstacle_mesh and not self.internal_obstacle_mesh_visibility:
|
||||
dprops.mesh_cache.obstacle.reset_cache_object()
|
||||
else:
|
||||
dprops.mesh_cache.delete_obstacle_cache_object()
|
||||
|
||||
|
||||
def _update_display_simulation_grid(self, context):
|
||||
dprops = context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
|
||||
self._update_debug_grid_geometry(context)
|
||||
if self.is_simulation_grid_debugging_enabled() and not self.is_draw_debug_grid_operator_running:
|
||||
bpy.ops.flip_fluid_operators.draw_debug_grid('INVOKE_DEFAULT')
|
||||
|
||||
|
||||
def _update_export_force_field(self, context):
|
||||
dprops = context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
|
||||
if self.export_force_field:
|
||||
self._update_force_field_geometry(context)
|
||||
dprops.mesh_cache.gl_force_field.enable()
|
||||
if not self.is_draw_gl_force_field_operator_running:
|
||||
bpy.ops.flip_fluid_operators.draw_force_field('INVOKE_DEFAULT')
|
||||
else:
|
||||
dprops.mesh_cache.gl_force_field.disable()
|
||||
|
||||
|
||||
def _update_display_domain_bounds(self, context):
|
||||
self._update_display_simulation_grid(context)
|
||||
|
||||
|
||||
def _update_debug_grid_geometry(self, context):
|
||||
draw_grid_operators.update_debug_grid_geometry(context)
|
||||
|
||||
|
||||
def _update_min_gradient_speed(self, context):
|
||||
if self.min_gradient_speed > self.max_gradient_speed:
|
||||
self.max_gradient_speed = self.min_gradient_speed
|
||||
self._update_debug_particle_geometry(context)
|
||||
|
||||
|
||||
def _update_max_gradient_speed(self, context):
|
||||
if self.max_gradient_speed < self.min_gradient_speed:
|
||||
self.min_gradient_speed = self.max_gradient_speed
|
||||
self._update_debug_particle_geometry(context)
|
||||
|
||||
|
||||
def _update_debug_particle_geometry(self, context):
|
||||
draw_particles_operators.update_debug_particle_geometry(context)
|
||||
|
||||
|
||||
def _update_force_field_geometry(self, context):
|
||||
draw_force_field_operators.update_debug_force_field_geometry(context)
|
||||
|
||||
|
||||
def _update_min_gradient_force(self, context):
|
||||
if self.min_gradient_force > self.max_gradient_force:
|
||||
self.max_gradient_force = self.min_gradient_force
|
||||
self._update_force_field_geometry(context)
|
||||
|
||||
|
||||
def _update_max_gradient_force(self, context):
|
||||
if self.max_gradient_force < self.min_gradient_force:
|
||||
self.min_gradient_force = self.max_gradient_force
|
||||
self._update_force_field_geometry(context)
|
||||
|
||||
|
||||
def _update_display_console_output(self, context):
|
||||
bake.set_console_output(self.display_console_output)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(VersionHistoryItem)
|
||||
bpy.utils.register_class(DomainDebugProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(VersionHistoryItem)
|
||||
bpy.utils.unregister_class(DomainDebugProperties)
|
||||
@@ -0,0 +1,420 @@
|
||||
# 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, os
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
StringProperty
|
||||
)
|
||||
|
||||
from ..materials import material_library
|
||||
from ..operators import helper_operators
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
class DomainMaterialsProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
surface_material = EnumProperty(
|
||||
name="Fluid Surface",
|
||||
description="Select a material for the fluid surface. Tip: materials can also be"
|
||||
" created and assigned to the fluid_surface object in Blender's Material"
|
||||
" Properties panel",
|
||||
items=material_library.get_surface_material_enums_ui,
|
||||
update=lambda self, context: self._update_surface_material(context),
|
||||
); exec(conv("surface_material"))
|
||||
fluid_particles_material = EnumProperty(
|
||||
name="Fluid Particles",
|
||||
description="Select a material for the fluid particles. Tip: If the object contains"
|
||||
" a Point Cloud geometry node modifier, the material can be set in the modifier settings",
|
||||
items=material_library.get_fluid_particles_material_enums_ui,
|
||||
update=lambda self, context: self._update_fluid_particles_material(context),
|
||||
); exec(conv("fluid_particles_material"))
|
||||
whitewater_foam_material = EnumProperty(
|
||||
name="Whitewater Foam",
|
||||
description="Select a material for the foam particles. Tip: materials can also be"
|
||||
" created and assigned to the whitewater_foam object in Blender's Material"
|
||||
" Properties panel. If the object contains a Point Cloud geometry node modifier,"
|
||||
" the material can be set in the modifier settings",
|
||||
items=material_library.get_whitewater_material_enums_ui,
|
||||
update=lambda self, context: self._update_whitewater_foam_material(context),
|
||||
); exec(conv("whitewater_foam_material"))
|
||||
whitewater_bubble_material = EnumProperty(
|
||||
name="Whitewater Bubble",
|
||||
description="Select a material for the bubble particles. Tip: materials can also be"
|
||||
" created and assigned to the whitewater_bubble object in Blender's Material"
|
||||
" Properties panel. If the object contains a Point Cloud geometry node modifier,"
|
||||
" the material can be set in the modifier settings",
|
||||
items=material_library.get_whitewater_material_enums_ui,
|
||||
update=lambda self, context: self._update_whitewater_bubble_material(context),
|
||||
); exec(conv("whitewater_bubble_material"))
|
||||
whitewater_spray_material = EnumProperty(
|
||||
name="Whitewater Spray",
|
||||
description="Select a material for the spray particles. Tip: materials can also be"
|
||||
" created and assigned to the whitewater_spray object in Blender's Material"
|
||||
" Properties panel. If the object contains a Point Cloud geometry node modifier,"
|
||||
" the material can be set in the modifier settings",
|
||||
items=material_library.get_whitewater_material_enums_ui,
|
||||
update=lambda self, context: self._update_whitewater_spray_material(context),
|
||||
); exec(conv("whitewater_spray_material"))
|
||||
whitewater_dust_material = EnumProperty(
|
||||
name="Whitewater Dust",
|
||||
description="Select a material for the dust particles. Tip: materials can also be"
|
||||
" created and assigned to the whitewater_dust object in Blender's Material"
|
||||
" Properties panel. If the object contains a Point Cloud geometry node modifier,"
|
||||
" the material can be set in the modifier settings",
|
||||
items=material_library.get_whitewater_material_enums_ui,
|
||||
update=lambda self, context: self._update_whitewater_dust_material(context),
|
||||
); exec(conv("whitewater_dust_material"))
|
||||
material_import = EnumProperty(
|
||||
name="Import",
|
||||
description="Import materials into this scene",
|
||||
items=material_library.get_material_import_enums_ui,
|
||||
); exec(conv("material_import"))
|
||||
|
||||
last_surface_material = StringProperty(default=""); exec(conv("last_surface_material"))
|
||||
last_fluid_particles_material = StringProperty(default=""); exec(conv("last_fluid_particles_material"))
|
||||
last_whitewater_foam_material = StringProperty(default=""); exec(conv("last_whitewater_foam_material"))
|
||||
last_whitewater_bubble_material = StringProperty(default=""); exec(conv("last_whitewater_bubble_material"))
|
||||
last_whitewater_spray_material = StringProperty(default=""); exec(conv("last_whitewater_spray_material"))
|
||||
last_whitewater_dust_material = StringProperty(default=""); exec(conv("last_whitewater_dust_material"))
|
||||
|
||||
|
||||
def load_post(self):
|
||||
#self._check_material_properties_valid()
|
||||
pass
|
||||
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
add = registry.add_property
|
||||
add(path + ".surface_material", "Surface Material", group_id=0)
|
||||
add(path + ".fluid_particles_material", "Fluid Particles Material", group_id=0)
|
||||
add(path + ".whitewater_foam_material", "Foam Material", group_id=0)
|
||||
add(path + ".whitewater_bubble_material", "Bubble Material", group_id=0)
|
||||
add(path + ".whitewater_spray_material", "Spray Material", group_id=0)
|
||||
add(path + ".whitewater_dust_material", "Dust Material", group_id=0)
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self.surface_material = 'MATERIAL_NONE'
|
||||
self.fluid_particles_material = 'MATERIAL_NONE'
|
||||
self.whitewater_foam_material = 'MATERIAL_NONE'
|
||||
self.whitewater_bubble_material = 'MATERIAL_NONE'
|
||||
self.whitewater_spray_material = 'MATERIAL_NONE'
|
||||
self.whitewater_dust_material = 'MATERIAL_NONE'
|
||||
self.material_import = 'ALL_MATERIALS'
|
||||
|
||||
|
||||
def scene_update_post(self, scene):
|
||||
self._check_material_properties()
|
||||
|
||||
|
||||
def save_pre(self):
|
||||
pass
|
||||
|
||||
|
||||
def _is_domain_set(self):
|
||||
return bpy.context.scene.flip_fluid.get_num_domain_objects() != 0
|
||||
|
||||
|
||||
def _get_domain_object(self):
|
||||
return bpy.context.scene.flip_fluid.get_domain_object()
|
||||
|
||||
|
||||
def _get_domain_properties(self):
|
||||
return bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
|
||||
|
||||
def _update_surface_material(self, context):
|
||||
if not self._is_domain_set():
|
||||
return
|
||||
if not context.scene.flip_fluid.is_domain_in_active_scene():
|
||||
return
|
||||
dprops = self._get_domain_properties()
|
||||
surface_object = dprops.mesh_cache.surface.get_cache_object()
|
||||
self._update_cache_object_material(
|
||||
surface_object,
|
||||
'surface_material', 'last_surface_material'
|
||||
)
|
||||
|
||||
|
||||
def _update_fluid_particles_material(self, context):
|
||||
if not self._is_domain_set():
|
||||
return
|
||||
if not context.scene.flip_fluid.is_domain_in_active_scene():
|
||||
return
|
||||
dprops = self._get_domain_properties()
|
||||
particles_object = dprops.mesh_cache.particles.get_cache_object()
|
||||
self._update_cache_object_material(
|
||||
particles_object,
|
||||
'fluid_particles_material', 'last_fluid_particles_material'
|
||||
)
|
||||
|
||||
helper_operators.update_geometry_node_material(particles_object, "FF_GeometryNodesFluidParticles")
|
||||
|
||||
|
||||
def _update_whitewater_foam_material(self, context):
|
||||
if not self._is_domain_set():
|
||||
return
|
||||
if not context.scene.flip_fluid.is_domain_in_active_scene():
|
||||
return
|
||||
dprops = self._get_domain_properties()
|
||||
foam_object = dprops.mesh_cache.foam.get_cache_object()
|
||||
self._update_cache_object_material(
|
||||
foam_object,
|
||||
'whitewater_foam_material', 'last_whitewater_foam_material'
|
||||
)
|
||||
|
||||
helper_operators.update_geometry_node_material(foam_object, "FF_GeometryNodesWhitewaterFoam")
|
||||
|
||||
|
||||
def _update_whitewater_bubble_material(self, context):
|
||||
if not self._is_domain_set():
|
||||
return
|
||||
if not context.scene.flip_fluid.is_domain_in_active_scene():
|
||||
return
|
||||
dprops = self._get_domain_properties()
|
||||
bubble_object = dprops.mesh_cache.bubble.get_cache_object()
|
||||
self._update_cache_object_material(
|
||||
bubble_object,
|
||||
'whitewater_bubble_material', 'last_whitewater_bubble_material'
|
||||
)
|
||||
|
||||
helper_operators.update_geometry_node_material(bubble_object, "FF_GeometryNodesWhitewaterBubble")
|
||||
|
||||
|
||||
def _update_whitewater_spray_material(self, context):
|
||||
if not self._is_domain_set():
|
||||
return
|
||||
if not context.scene.flip_fluid.is_domain_in_active_scene():
|
||||
return
|
||||
dprops = self._get_domain_properties()
|
||||
spray_object = dprops.mesh_cache.spray.get_cache_object()
|
||||
self._update_cache_object_material(
|
||||
spray_object,
|
||||
'whitewater_spray_material', 'last_whitewater_spray_material'
|
||||
)
|
||||
|
||||
helper_operators.update_geometry_node_material(spray_object, "FF_GeometryNodesWhitewaterSpray")
|
||||
|
||||
|
||||
def _update_whitewater_dust_material(self, context):
|
||||
if not self._is_domain_set():
|
||||
return
|
||||
if not context.scene.flip_fluid.is_domain_in_active_scene():
|
||||
return
|
||||
dprops = self._get_domain_properties()
|
||||
dust_object = dprops.mesh_cache.dust.get_cache_object()
|
||||
self._update_cache_object_material(
|
||||
dust_object,
|
||||
'whitewater_dust_material', 'last_whitewater_dust_material'
|
||||
)
|
||||
|
||||
helper_operators.update_geometry_node_material(dust_object, "FF_GeometryNodesWhitewaterDust")
|
||||
|
||||
|
||||
def _remove_cache_object_material(self, cache_object, enum_ident):
|
||||
if cache_object is None:
|
||||
return
|
||||
mesh = cache_object.data
|
||||
for i in range(len(mesh.materials)):
|
||||
mesh.materials.pop(index=0)
|
||||
|
||||
|
||||
def _add_cache_object_material(self, cache_object, enum_ident):
|
||||
if cache_object is None:
|
||||
return
|
||||
|
||||
mesh = cache_object.data
|
||||
material_name = material_library.import_material(enum_ident)
|
||||
material_object = bpy.data.materials.get(material_name)
|
||||
for i in range(len(mesh.materials)):
|
||||
mesh.materials.pop(index=0)
|
||||
|
||||
mesh.materials.append(material_object)
|
||||
cache_object.active_material_index = 0
|
||||
|
||||
|
||||
def _update_cache_object_material(self, cache_object,
|
||||
material_prop, last_material_prop):
|
||||
if not getattr(self, last_material_prop):
|
||||
setattr(self, last_material_prop, 'MATERIAL_NONE')
|
||||
|
||||
oldval = getattr(self, last_material_prop)
|
||||
newval = getattr(self, material_prop)
|
||||
|
||||
if newval == 'MATERIAL_NONE':
|
||||
self._remove_cache_object_material(cache_object, oldval)
|
||||
elif oldval == 'MATERIAL_NONE':
|
||||
self._add_cache_object_material(cache_object, newval)
|
||||
else:
|
||||
self._remove_cache_object_material(cache_object, oldval)
|
||||
self._add_cache_object_material(cache_object, newval)
|
||||
|
||||
setattr(self, last_material_prop, newval)
|
||||
|
||||
|
||||
def _get_material_identifier_from_name(self, material_name, material_enums):
|
||||
for e in material_enums:
|
||||
if e[1] == material_name:
|
||||
return e[0]
|
||||
return None
|
||||
|
||||
|
||||
def _check_material_properties_valid(self):
|
||||
try:
|
||||
self.surface_material = self.surface_material
|
||||
except:
|
||||
self.surface_material = 'MATERIAL_NONE'
|
||||
|
||||
try:
|
||||
self.fluid_particles_material = self.fluid_particles_material
|
||||
except:
|
||||
self.fluid_particles_material = 'MATERIAL_NONE'
|
||||
|
||||
try:
|
||||
self.whitewater_foam_material = self.whitewater_foam_material
|
||||
except:
|
||||
self.whitewater_foam_material = 'MATERIAL_NONE'
|
||||
|
||||
try:
|
||||
self.whitewater_bubble_material = self.whitewater_bubble_material
|
||||
except:
|
||||
self.whitewater_bubble_material = 'MATERIAL_NONE'
|
||||
|
||||
try:
|
||||
self.whitewater_spray_material = self.whitewater_spray_material
|
||||
except:
|
||||
self.whitewater_spray_material = 'MATERIAL_NONE'
|
||||
|
||||
try:
|
||||
self.whitewater_dust_material = self.whitewater_dust_material
|
||||
except:
|
||||
self.whitewater_dust_material = 'MATERIAL_NONE'
|
||||
|
||||
try:
|
||||
self.material_import = self.material_import
|
||||
except:
|
||||
self.material_import = 'ALL_MATERIALS'
|
||||
|
||||
|
||||
def _check_material_properties(self):
|
||||
if not self._is_domain_set():
|
||||
return
|
||||
self._check_surface_material()
|
||||
self._check_fluid_particles_material()
|
||||
self._check_foam_material()
|
||||
self._check_bubble_material()
|
||||
self._check_spray_material()
|
||||
self._check_dust_material()
|
||||
|
||||
|
||||
def _check_surface_material(self):
|
||||
dprops = self._get_domain_properties()
|
||||
surface_object = dprops.mesh_cache.surface.get_cache_object()
|
||||
if surface_object is None:
|
||||
return
|
||||
|
||||
if len(surface_object.data.materials) == 0:
|
||||
if self.surface_material != 'MATERIAL_NONE':
|
||||
self.surface_material = 'MATERIAL_NONE'
|
||||
return
|
||||
|
||||
material_idx = surface_object.active_material_index
|
||||
material = surface_object.data.materials[material_idx]
|
||||
if material is None:
|
||||
self.surface_material = 'MATERIAL_NONE'
|
||||
return
|
||||
|
||||
material_enums = material_library.get_surface_material_enums_ui()
|
||||
material_id = self._get_material_identifier_from_name(material.name, material_enums)
|
||||
if material_id is not None and self.surface_material != material_id:
|
||||
self.surface_material = material_id
|
||||
|
||||
|
||||
def _check_fluid_particles_material(self):
|
||||
dprops = self._get_domain_properties()
|
||||
particles_object = dprops.mesh_cache.particles.get_cache_object()
|
||||
if particles_object is None:
|
||||
return
|
||||
|
||||
if len(particles_object.data.materials) == 0:
|
||||
if self.fluid_particles_material != 'MATERIAL_NONE':
|
||||
self.fluid_particles_material = 'MATERIAL_NONE'
|
||||
return
|
||||
|
||||
material_idx = particles_object.active_material_index
|
||||
material = particles_object.data.materials[material_idx]
|
||||
if material is None:
|
||||
self.fluid_particles_material = 'MATERIAL_NONE'
|
||||
return
|
||||
|
||||
material_enums = material_library.get_fluid_particles_material_enums_ui()
|
||||
material_id = self._get_material_identifier_from_name(material.name, material_enums)
|
||||
if material_id is not None and self.fluid_particles_material != material_id:
|
||||
self.fluid_particles_material = material_id
|
||||
|
||||
|
||||
def _check_foam_material(self):
|
||||
dprops = self._get_domain_properties()
|
||||
self._check_whitewater_material(dprops.mesh_cache.foam, "whitewater_foam_material")
|
||||
|
||||
|
||||
def _check_bubble_material(self):
|
||||
dprops = self._get_domain_properties()
|
||||
self._check_whitewater_material(dprops.mesh_cache.bubble, "whitewater_bubble_material")
|
||||
|
||||
|
||||
def _check_spray_material(self):
|
||||
dprops = self._get_domain_properties()
|
||||
self._check_whitewater_material(dprops.mesh_cache.spray, "whitewater_spray_material")
|
||||
|
||||
|
||||
def _check_dust_material(self):
|
||||
dprops = self._get_domain_properties()
|
||||
self._check_whitewater_material(dprops.mesh_cache.dust, "whitewater_dust_material")
|
||||
|
||||
|
||||
def _check_whitewater_material(self, mesh_cache_object, material_prop):
|
||||
dprops = self._get_domain_properties()
|
||||
new_object_material = None
|
||||
|
||||
whitewater_object = mesh_cache_object.get_cache_object()
|
||||
if whitewater_object is None:
|
||||
return
|
||||
|
||||
if len(whitewater_object.data.materials) == 0:
|
||||
new_object_material = 'MATERIAL_NONE'
|
||||
else:
|
||||
material_idx = whitewater_object.active_material_index
|
||||
material = whitewater_object.data.materials[material_idx]
|
||||
if material is not None:
|
||||
material_enums = material_library.get_whitewater_material_enums_ui()
|
||||
material_id = self._get_material_identifier_from_name(material.name, material_enums)
|
||||
new_object_material = material_id
|
||||
|
||||
if new_object_material is not None and getattr(self, material_prop) != new_object_material:
|
||||
setattr(self, material_prop, new_object_material)
|
||||
return
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DomainMaterialsProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(DomainMaterialsProperties)
|
||||
@@ -0,0 +1,228 @@
|
||||
# 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 bpy.props import (
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
PointerProperty
|
||||
)
|
||||
|
||||
from ..objects import flip_fluid_cache
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
class DomainParticlesProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
enable_fluid_particle_output = BoolProperty(
|
||||
name="Enable Fluid Particle Export",
|
||||
description="Enable fluid particle data to be exported to the simulation cache",
|
||||
default=False,
|
||||
update=lambda self, context: self._update_enable_fluid_particle_output(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fluid_particle_output"))
|
||||
fluid_particle_output_amount = FloatProperty(
|
||||
name="Particle Export Amount",
|
||||
description="Amount of fluid particles to export. A value of 1.0 will export all fluid particles."
|
||||
" Decrease this value to reduce cache size if not all particles will need to be displayed or"
|
||||
" rendered. The number of particles to display/render can be further reduced in the display settings",
|
||||
soft_min=0.001,
|
||||
min=0.00001, max=1.0,
|
||||
default=1.0,
|
||||
precision=5,
|
||||
subtype='FACTOR',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("fluid_particle_output_amount"))
|
||||
enable_fluid_particle_surface_output = BoolProperty(
|
||||
name="Export Surface Particles",
|
||||
description="Export fluid particles near the fluid surface. Particles are considered"
|
||||
" to be surface particles if they are near empty air, but are not near the domain boundary",
|
||||
default=True,
|
||||
); exec(conv("enable_fluid_particle_surface_output"))
|
||||
enable_fluid_particle_boundary_output = BoolProperty(
|
||||
name="Export Boundary Particles",
|
||||
description="Export fluid particles near the domain boundary. Particles are considered to"
|
||||
" be boundary particles if they are near the boundary of the domain. If a surface"
|
||||
" Meshing Volume object is set, particles near the surface of this object are considered"
|
||||
" boundary particles",
|
||||
default=True,
|
||||
); exec(conv("enable_fluid_particle_boundary_output"))
|
||||
enable_fluid_particle_interior_output = BoolProperty(
|
||||
name="Export Interior Particles",
|
||||
description="Export fluid particles inside of the fluid surface. Particles are considered"
|
||||
" to be interior particles if they are not classified as either surface or boundary particles",
|
||||
default=True,
|
||||
); exec(conv("enable_fluid_particle_interior_output"))
|
||||
fluid_particle_source_id_blacklist = IntProperty(
|
||||
name="Skip Source ID",
|
||||
description="If the Source ID attribute is enabled, do not export fluid particles with the specified"
|
||||
" Source ID value. Useful to reduce cache size and speed up playback in situations where particles"
|
||||
" are not needed from specific Fluid or Inflow objects",
|
||||
min=-1,
|
||||
default=-1,
|
||||
); exec(conv("fluid_particle_source_id_blacklist"))
|
||||
enable_fluid_particle_velocity_vector_attribute = BoolProperty(
|
||||
name="Generate Velocity Attributes",
|
||||
description="Generate fluid 3D velocity vector attributes for the fluid particles. After"
|
||||
" baking, the velocity vectors (in m/s) can be accessed in a Cycles Attribute"
|
||||
" Node or in Geometry Nodes with the name 'flip_velocity' from the Vector output."
|
||||
" This attribute is required for motion blur rendering. If the velocity"
|
||||
" direction is not needed, use Generate Speed Attributes instead",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fluid_particle_velocity_vector_attribute"))
|
||||
enable_fluid_particle_speed_attribute = BoolProperty(
|
||||
name="Generate Speed Attributes",
|
||||
description="Generate fluid speed attributes for the fluid particles. After"
|
||||
" baking, the speed values (in m/s) can be accessed in a Cycles Attribute"
|
||||
" Node or in Geometry Nodes with the name 'flip_speed' from the Fac output",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fluid_particle_speed_attribute"))
|
||||
enable_fluid_particle_vorticity_vector_attribute = BoolProperty(
|
||||
name="Generate Vorticity Attributes",
|
||||
description="Generate fluid 3D vorticity vector attributes for the fluid particles. After"
|
||||
" baking, the vorticity vectors can be accessed in a Cycles Attribute"
|
||||
" Node or in Geometry Nodes with the name 'flip_vorticity' from the Vector output",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fluid_particle_vorticity_vector_attribute"))
|
||||
enable_fluid_particle_color_attribute = BoolProperty(
|
||||
name="Generate Color Attributes",
|
||||
description="Generate fluid color attributes for the fluid particles. Each"
|
||||
" Inflow/Fluid object can set to assign color to the generated fluid. After"
|
||||
" baking, the color values can be accessed in a Cycles Attribute Node or in Geometry Nodes"
|
||||
" with the name 'flip_color' from the Color output. This can be used to create varying color"
|
||||
" liquid effects",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fluid_particle_color_attribute"))
|
||||
enable_fluid_particle_age_attribute = BoolProperty(
|
||||
name="Generate Age Attributes",
|
||||
description="Generate fluid age attributes for the fluid particles."
|
||||
" The age attribute starts at 0.0 when the liquid is spawned and counts up in"
|
||||
" seconds. After baking, the age values can be accessed in a Cycles Attribute"
|
||||
" Node or in Geometry Nodes with the name 'flip_age' from the Fac output",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fluid_particle_age_attribute"))
|
||||
enable_fluid_particle_lifetime_attribute = BoolProperty(
|
||||
name="Generate Lifetime Attributes",
|
||||
description="Generate fluid lifetime attributes for the fluid particles. This attribute allows the"
|
||||
" fluid to start with a lifetime value that counts down in seconds and once the lifetime reaches 0,"
|
||||
" the fluid is removed from the simulation. Each Inflow/Fluid object can be set to assign a"
|
||||
" starting lifetime to the generated fluid. After baking, the lifetime remaining values"
|
||||
" can be accessed in a Cycles Attribute Node or in Geometry Nodes with the name 'flip_lifetime' from"
|
||||
" the Fac output",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fluid_particle_lifetime_attribute"))
|
||||
enable_fluid_particle_whitewater_proximity_attribute = BoolProperty(
|
||||
name="Generate Whitewater Proximity Attributes",
|
||||
description="Generate whitewater proximity attributes for the fluid particles. The attribute values represent"
|
||||
" how many foam, bubble, or spray particles are near a fluid particle and can be used in a material to shade"
|
||||
" particles that are near whitewater particles. After baking, the proximity attribute can be accessed"
|
||||
" in a Cycles Attribute Node or in Geometry Nodes with the names 'flip_foam_proximity', 'flip_bubble_proximity',"
|
||||
" and 'flip_spray_proximity' from the Fac output",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fluid_particle_whitewater_proximity_attribute"))
|
||||
enable_fluid_particle_source_id_attribute = BoolProperty(
|
||||
name="Generate Source ID Attributes",
|
||||
description="Generate fluid source identifiers for the fluid particles. Each"
|
||||
" Inflow/Fluid object can set to assign a source ID to the generated particles. After"
|
||||
" baking, the ID values can be accessed in a Cycles Attribute Node or in Geometry nodes with the name"
|
||||
" 'flip_source_id' from the Fac output. This can be used to identifty fluid from"
|
||||
" different sources in a material or geometry node group. Warning: this attribute is"
|
||||
" not supported with sheeting effects or resolution upscaling features",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fluid_particle_source_id_attribute"))
|
||||
enable_fluid_particle_uid_attribute = BoolProperty(
|
||||
name="Generate UID Attributes",
|
||||
description="Generate Unique IDs for fluid particles. After"
|
||||
" baking, the UID values can be accessed in a Cycles Attribute Node or in Geometry nodes with the name"
|
||||
" 'flip_uid' from the Fac output. This can be used to uniquely identify particles which can be useful"
|
||||
" for attribute storage and tracking in simulation nodes. Warning: this attribute can require a larger"
|
||||
" amount of cache storage compared to the built-in 'flip_id' attribute. If unique IDs are not required,"
|
||||
" leave this attribute disabled",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fluid_particle_uid_attribute"))
|
||||
enable_fluid_particle_uid_attribute_reuse = BoolProperty(
|
||||
name="Reuse UIDs",
|
||||
description="Reuse UID attribute values. If enabled, particles that are removed from the simulation may have"
|
||||
" their UID reused in a later frame. If a particle is removed from the simulation, the UID will not be"
|
||||
" reused until at least a 1 frame gap has passed. UID values will only be unique to a single frame."
|
||||
" Enabling is recommended for use in simulation nodes to reduce resource usage. If disabled, UID values"
|
||||
" will be unique to the entire simulation. Disabling is recommended for tracking individual particles"
|
||||
" in geometry nodes",
|
||||
default=True,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_fluid_particle_uid_attribute_reuse"))
|
||||
|
||||
fluid_particles_expanded = BoolProperty(default=True); exec(conv("fluid_particles_expanded"))
|
||||
fluid_particle_generation_expanded = BoolProperty(default=False); exec(conv("fluid_particle_generation_expanded"))
|
||||
fluid_particle_display_settings_expanded = BoolProperty(default=False); exec(conv("fluid_particle_display_settings_expanded"))
|
||||
geometry_attributes_expanded = BoolProperty(default=False); exec(conv("geometry_attributes_expanded"))
|
||||
velocity_attributes_expanded = BoolProperty(default=False); exec(conv("velocity_attributes_expanded"))
|
||||
color_attributes_expanded = BoolProperty(default=False); exec(conv("color_attributes_expanded"))
|
||||
other_attributes_expanded = BoolProperty(default=False); exec(conv("other_attributes_expanded"))
|
||||
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
add = registry.add_property
|
||||
add(path + ".enable_fluid_particle_output", "Enable Fluid Particle Export", group_id=0)
|
||||
add(path + ".fluid_particle_output_amount", "Export Amount", group_id=0)
|
||||
add(path + ".enable_fluid_particle_surface_output", "Export Surface Particles", group_id=0)
|
||||
add(path + ".enable_fluid_particle_boundary_output", "Export Boundary Particles", group_id=0)
|
||||
add(path + ".enable_fluid_particle_interior_output", "Export Interior Particles", group_id=0)
|
||||
add(path + ".fluid_particle_source_id_blacklist", "Skip Source ID", group_id=0)
|
||||
add(path + ".enable_fluid_particle_velocity_vector_attribute", "Velocity Attribute", group_id=0)
|
||||
add(path + ".enable_fluid_particle_speed_attribute", "Speed Attribute", group_id=0)
|
||||
add(path + ".enable_fluid_particle_vorticity_vector_attribute", "Vorticity Attribute", group_id=0)
|
||||
add(path + ".enable_fluid_particle_color_attribute", "Color Attribute", group_id=0)
|
||||
add(path + ".enable_fluid_particle_age_attribute", "Age Attribute", group_id=0)
|
||||
add(path + ".enable_fluid_particle_lifetime_attribute", "Lifetime Attribute", group_id=0)
|
||||
add(path + ".enable_fluid_particle_whitewater_proximity_attribute", "Lifetime Attribute", group_id=0)
|
||||
add(path + ".enable_fluid_particle_source_id_attribute", "Source ID Attribute", group_id=0)
|
||||
add(path + ".enable_fluid_particle_uid_attribute", "UID Attribute", group_id=0)
|
||||
add(path + ".enable_fluid_particle_uid_attribute_reuse", "Reuse UIDs", group_id=0)
|
||||
|
||||
|
||||
def _update_enable_fluid_particle_output(self, context):
|
||||
dprops = context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
|
||||
if self.enable_fluid_particle_output:
|
||||
objects_to_initialize = flip_fluid_cache.EnabledMeshCacheObjects()
|
||||
objects_to_initialize.fluid_particles = True
|
||||
|
||||
dprops.mesh_cache.initialize_cache_objects(objects_to_initialize)
|
||||
else:
|
||||
dprops.mesh_cache.particles.reset_cache_object()
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DomainParticlesProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(DomainParticlesProperties)
|
||||
@@ -0,0 +1,225 @@
|
||||
# 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, os
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
StringProperty,
|
||||
PointerProperty
|
||||
)
|
||||
|
||||
from . import preset_properties
|
||||
from .. import types
|
||||
from ..presets import preset_library
|
||||
from ..objects import flip_fluid_preset_stack
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
class DomainPresetsProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
enable_presets = BoolProperty(
|
||||
name="Enable Presets",
|
||||
description="Enable functionality to apply fluid presets",
|
||||
default=False,
|
||||
update=lambda self, context: self._update_enable_presets(context),
|
||||
); exec(conv("enable_presets"))
|
||||
current_package = EnumProperty(
|
||||
name="Package",
|
||||
description="Preset package",
|
||||
items=preset_library.get_all_package_enums,
|
||||
update=lambda self, context: self._update_current_package(context),
|
||||
); exec(conv("current_package"))
|
||||
current_preset = EnumProperty(
|
||||
items=preset_library.get_current_package_preset_enums,
|
||||
name="Preset",
|
||||
description="Fluid Preset",
|
||||
update=lambda self, context: self._update_current_preset(context),
|
||||
); exec(conv("current_preset"))
|
||||
preview_preset = BoolProperty(
|
||||
name="Preview",
|
||||
description="Automatically assign preset on change (without"
|
||||
" needing to add to the preset stack)",
|
||||
default=False,
|
||||
update=lambda self, context: self._update_preview_preset(context),
|
||||
); exec(conv("preview_preset"))
|
||||
new_package_settings = PointerProperty(
|
||||
name="New Package Settings",
|
||||
description="",
|
||||
type=preset_properties.NewPresetPackageSettings,
|
||||
); exec(conv("new_package_settings"))
|
||||
delete_package_settings = PointerProperty(
|
||||
name="Delete Package Settings",
|
||||
description="",
|
||||
type=preset_properties.DeletePresetPackageSettings,
|
||||
); exec(conv("delete_package_settings"))
|
||||
new_preset_settings = PointerProperty(
|
||||
name="New Preset Settings",
|
||||
description="",
|
||||
type=preset_properties.NewPresetSettings,
|
||||
); exec(conv("new_preset_settings"))
|
||||
delete_preset_settings = PointerProperty(
|
||||
name="Delete Preset Settings",
|
||||
description="",
|
||||
type=preset_properties.DeletePresetSettings,
|
||||
); exec(conv("delete_preset_settings"))
|
||||
edit_preset_settings = PointerProperty(
|
||||
name="Edit Preset Settings",
|
||||
description="",
|
||||
type=preset_properties.EditPresetSettings,
|
||||
); exec(conv("edit_preset_settings"))
|
||||
display_preset_settings = PointerProperty(
|
||||
name="Display Preset Settings",
|
||||
description="",
|
||||
type=preset_properties.DisplayPresetInfoSettings,
|
||||
); exec(conv("display_preset_settings"))
|
||||
export_package_settings = PointerProperty(
|
||||
name="Export Package Settings",
|
||||
description="",
|
||||
type=preset_properties.ExportPresetPackageSettings,
|
||||
); exec(conv("export_package_settings"))
|
||||
import_package_settings = PointerProperty(
|
||||
name="Import Package Settings",
|
||||
description="",
|
||||
type=preset_properties.ImportPresetPackageSettings,
|
||||
); exec(conv("import_package_settings"))
|
||||
preset_stack = PointerProperty(
|
||||
name="Flip Fluid Preset Stack",
|
||||
description="",
|
||||
type=flip_fluid_preset_stack.FlipFluidPresetStack,
|
||||
); exec(conv("preset_stack"))
|
||||
|
||||
preset_manager_expanded = BoolProperty(default=False); exec(conv("preset_manager_expanded"))
|
||||
deprecated_presets_disabled_on_load = BoolProperty(default=False); exec(conv("deprecated_presets_disabled_on_load"))
|
||||
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
pass
|
||||
|
||||
|
||||
def initialize(self):
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
preset_library.initialize()
|
||||
preset_library.load_default_settings(dprops)
|
||||
self._initialize_default_current_package()
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self.preset_stack.validate_stack()
|
||||
self.check_preset_enums()
|
||||
self._check_presets_disable_on_load()
|
||||
|
||||
|
||||
def check_preset_enums(self):
|
||||
# Avoids blank package/preset enums if package or preset is missing
|
||||
current_preset = self.current_preset
|
||||
try:
|
||||
self.current_package = self.current_package
|
||||
except:
|
||||
enums = preset_library.get_all_package_enums(self, bpy.context)
|
||||
self.current_package = enums[0][0]
|
||||
|
||||
try:
|
||||
# preset does not exist in this addon installation
|
||||
self.current_preset = current_preset
|
||||
except:
|
||||
self.current_preset = 'PRESET_NONE'
|
||||
|
||||
|
||||
def _check_presets_disable_on_load(self):
|
||||
if self.deprecated_presets_disabled_on_load:
|
||||
return
|
||||
self.enable_presets = False
|
||||
self.deprecated_presets_disabled_on_load = True
|
||||
|
||||
|
||||
def _initialize_default_current_package(self):
|
||||
default_name = "Basic Fluids"
|
||||
enums = preset_library.get_all_package_enums(self, bpy.context)
|
||||
for e in enums:
|
||||
if e[1] == default_name:
|
||||
self.current_package = e[0]
|
||||
break
|
||||
|
||||
# Prevents UI from displaying blank enum property
|
||||
try:
|
||||
self.current_package = self.current_package
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def _update_current_package(self, context):
|
||||
preset_enums = preset_library.get_current_package_preset_enums(self, context)
|
||||
package_info = preset_library.package_identifier_to_info(self.current_package)
|
||||
if len(preset_enums) > 1:
|
||||
if package_info['use_custom_icons']:
|
||||
for e in preset_enums:
|
||||
if e[0] != 'PRESET_NONE':
|
||||
self.current_preset = e[0]
|
||||
break
|
||||
else:
|
||||
for e in reversed(preset_enums):
|
||||
if e[0] != 'PRESET_NONE':
|
||||
self.current_preset = e[0]
|
||||
break
|
||||
else:
|
||||
self.current_preset = self.current_preset
|
||||
|
||||
|
||||
def _update_enable_presets(self, context):
|
||||
stack = self.preset_stack
|
||||
if self.enable_presets:
|
||||
stack.enable()
|
||||
if self.preview_preset:
|
||||
if not stack.is_preset_in_stack(self.current_preset):
|
||||
stack.stage_preset(self.current_preset)
|
||||
else:
|
||||
stack.unstage_preset()
|
||||
else:
|
||||
stack.disable()
|
||||
|
||||
|
||||
def _update_current_preset(self, context):
|
||||
if not self.enable_presets or not self.preview_preset:
|
||||
return
|
||||
|
||||
stack = self.preset_stack
|
||||
if not stack.is_preset_in_stack(self.current_preset):
|
||||
stack.stage_preset(self.current_preset)
|
||||
else:
|
||||
stack.unstage_preset()
|
||||
|
||||
|
||||
def _update_preview_preset(self, context):
|
||||
if not self.enable_presets:
|
||||
return
|
||||
|
||||
stack = self.preset_stack
|
||||
if self.preview_preset and not stack.is_preset_in_stack(self.current_preset):
|
||||
stack.stage_preset(self.current_preset)
|
||||
else:
|
||||
stack.unstage_preset()
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DomainPresetsProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(DomainPresetsProperties)
|
||||
@@ -0,0 +1,567 @@
|
||||
# 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_render_properties',
|
||||
'domain_bake_properties',
|
||||
'domain_simulation_properties',
|
||||
'domain_cache_properties',
|
||||
'domain_particles_properties',
|
||||
'domain_surface_properties',
|
||||
'domain_whitewater_properties',
|
||||
'domain_world_properties',
|
||||
'domain_presets_properties',
|
||||
'domain_materials_properties',
|
||||
'domain_advanced_properties',
|
||||
'domain_debug_properties',
|
||||
'domain_stats_properties'
|
||||
]
|
||||
for module_name in reloadable_modules:
|
||||
if module_name in locals():
|
||||
importlib.reload(locals()[module_name])
|
||||
|
||||
import bpy, os, math
|
||||
from mathutils import Vector, Color
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
PointerProperty,
|
||||
StringProperty
|
||||
)
|
||||
|
||||
from . import (
|
||||
domain_render_properties,
|
||||
domain_bake_properties,
|
||||
domain_simulation_properties,
|
||||
domain_cache_properties,
|
||||
domain_particles_properties,
|
||||
domain_surface_properties,
|
||||
domain_whitewater_properties,
|
||||
domain_world_properties,
|
||||
domain_presets_properties,
|
||||
domain_materials_properties,
|
||||
domain_advanced_properties,
|
||||
domain_stats_properties,
|
||||
domain_debug_properties,
|
||||
preset_properties,
|
||||
)
|
||||
from .. import types
|
||||
from ..objects import flip_fluid_cache
|
||||
from ..operators import helper_operators
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
from ..utils import api_workaround_utils
|
||||
|
||||
|
||||
class FlipFluidDomainProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
render = PointerProperty(
|
||||
name="Domain Render Properties",
|
||||
description="",
|
||||
type=domain_render_properties.DomainRenderProperties,
|
||||
); exec(conv("render"))
|
||||
bake = PointerProperty(
|
||||
name="Domain Bake Properties",
|
||||
description="",
|
||||
type=domain_bake_properties.DomainBakeProperties,
|
||||
); exec(conv("bake"))
|
||||
simulation = PointerProperty(
|
||||
name="Domain Simulation Properties",
|
||||
description="",
|
||||
type=domain_simulation_properties.DomainSimulationProperties,
|
||||
); exec(conv("simulation"))
|
||||
cache = PointerProperty(
|
||||
name="Domain Cache Properties",
|
||||
description="",
|
||||
type=domain_cache_properties.DomainCacheProperties,
|
||||
); exec(conv("cache"))
|
||||
particles = PointerProperty(
|
||||
name="Domain Surface Properties",
|
||||
description="",
|
||||
type=domain_particles_properties.DomainParticlesProperties,
|
||||
); exec(conv("particles"))
|
||||
surface = PointerProperty(
|
||||
name="Domain Surface Properties",
|
||||
description="",
|
||||
type=domain_surface_properties.DomainSurfaceProperties,
|
||||
); exec(conv("surface"))
|
||||
whitewater = PointerProperty(
|
||||
name="Domain Whitewater Properties",
|
||||
description="",
|
||||
type=domain_whitewater_properties.DomainWhitewaterProperties,
|
||||
); exec(conv("whitewater"))
|
||||
world = PointerProperty(
|
||||
name="Domain World Properties",
|
||||
description="",
|
||||
type=domain_world_properties.DomainWorldProperties,
|
||||
); exec(conv("world"))
|
||||
presets = PointerProperty(
|
||||
name="Domain Presets Properties",
|
||||
description="",
|
||||
type=domain_presets_properties.DomainPresetsProperties,
|
||||
); exec(conv("presets"))
|
||||
materials = PointerProperty(
|
||||
name="Domain Materials Properties",
|
||||
description="",
|
||||
type=domain_materials_properties.DomainMaterialsProperties,
|
||||
); exec(conv("materials"))
|
||||
advanced = PointerProperty(
|
||||
name="Domain Advanced Properties",
|
||||
description="",
|
||||
type=domain_advanced_properties.DomainAdvancedProperties,
|
||||
); exec(conv("advanced"))
|
||||
debug = PointerProperty(
|
||||
name="Domain Debug Properties",
|
||||
description="",
|
||||
type=domain_debug_properties.DomainDebugProperties,
|
||||
); exec(conv("debug"))
|
||||
stats = PointerProperty(
|
||||
name="Domain Stats Properties",
|
||||
description="",
|
||||
type=domain_stats_properties.DomainStatsProperties,
|
||||
); exec(conv("stats"))
|
||||
mesh_cache = PointerProperty(
|
||||
name="Domain Mesh Cache",
|
||||
description="",
|
||||
type=flip_fluid_cache.FlipFluidCache,
|
||||
); exec(conv("mesh_cache"))
|
||||
property_registry = PointerProperty(
|
||||
name="Domain Property Registry",
|
||||
description="",
|
||||
type=preset_properties.PresetRegistry,
|
||||
); exec(conv("property_registry"))
|
||||
|
||||
domain_settings_tabbed_panel_view = EnumProperty(
|
||||
name="Domain Panel View",
|
||||
description="Select settings panel to display",
|
||||
items=types.domain_settings_panel,
|
||||
default='DOMAIN_SETTINGS_PANEL_SIMULATION',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("domain_settings_tabbed_panel_view"))
|
||||
|
||||
is_updated_to_flip_fluids_version_180 = BoolProperty(default=False)
|
||||
exec(conv("is_updated_to_flip_fluids_version_180"));
|
||||
|
||||
is_updated_to_flip_fluids_version_184 = BoolProperty(default=False)
|
||||
exec(conv("is_updated_to_flip_fluids_version_184"));
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self.simulation.initialize()
|
||||
self.cache.initialize()
|
||||
self.advanced.initialize()
|
||||
self.materials.initialize()
|
||||
self._initialize_cache()
|
||||
self._initialize_property_registry()
|
||||
self.presets.initialize()
|
||||
|
||||
self.is_updated_to_flip_fluids_version_180 = True
|
||||
self.is_updated_to_flip_fluids_version_184 = True
|
||||
|
||||
|
||||
def dummy_initialize(self):
|
||||
self.simulation.initialize()
|
||||
self.materials.initialize()
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def destroy(self):
|
||||
self._delete_cache()
|
||||
|
||||
|
||||
def get_property_from_path(self, path):
|
||||
elements = path.split(".")
|
||||
if elements[0] == "domain":
|
||||
elements.pop(0)
|
||||
|
||||
prop = self
|
||||
for e in elements:
|
||||
if not hasattr(prop, e):
|
||||
return None
|
||||
prop = getattr(prop, e)
|
||||
if (isinstance(prop, Vector) or isinstance(prop, Color) or
|
||||
(hasattr(prop, "__iter__") and not isinstance(prop, str))):
|
||||
new_prop = []
|
||||
for x in prop:
|
||||
new_prop.append(x)
|
||||
prop = new_prop
|
||||
elif hasattr(prop, "is_min_max_property"):
|
||||
prop = [prop.value_min, prop.value_max]
|
||||
return prop
|
||||
|
||||
|
||||
def set_property_from_path(self, path, value):
|
||||
elements = path.split(".")
|
||||
if elements[0] == "domain":
|
||||
elements.pop(0)
|
||||
identifier = elements.pop()
|
||||
|
||||
prop_group = self
|
||||
for e in elements:
|
||||
if not hasattr(prop_group, e):
|
||||
return False
|
||||
prop_group = getattr(prop_group, e)
|
||||
|
||||
if not hasattr(prop_group, identifier):
|
||||
return False
|
||||
|
||||
try:
|
||||
prop = getattr(prop_group, identifier)
|
||||
if hasattr(prop, "is_min_max_property"):
|
||||
prop.value_min = value[0]
|
||||
prop.value_max = value[1]
|
||||
else:
|
||||
setattr(prop_group, identifier, value)
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _initialize_cache(self):
|
||||
self.mesh_cache.initialize_cache_objects()
|
||||
|
||||
|
||||
def _delete_cache(self):
|
||||
domain_object = bpy.context.scene.flip_fluid.get_domain_object()
|
||||
if domain_object is None:
|
||||
return
|
||||
self.mesh_cache.delete_cache_objects()
|
||||
|
||||
|
||||
def refresh_property_registry(self):
|
||||
# Call to re-initialize properties. The object in the scene is not
|
||||
# guaranteed to contain all properties of the current version of the addon,
|
||||
# such as a domain imported from the asset browser that was created in an earlier
|
||||
# version.
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def _initialize_property_registry(self):
|
||||
self.property_registry.clear()
|
||||
self.render.register_preset_properties( self.property_registry, "domain.render")
|
||||
self.bake.register_preset_properties( self.property_registry, "domain.bake")
|
||||
self.simulation.register_preset_properties( self.property_registry, "domain.simulation")
|
||||
self.cache.register_preset_properties( self.property_registry, "domain.cache")
|
||||
self.particles.register_preset_properties( self.property_registry, "domain.particles")
|
||||
self.surface.register_preset_properties( self.property_registry, "domain.surface")
|
||||
self.whitewater.register_preset_properties( self.property_registry, "domain.whitewater")
|
||||
self.world.register_preset_properties( self.property_registry, "domain.world")
|
||||
self.presets.register_preset_properties( self.property_registry, "domain.presets")
|
||||
self.materials.register_preset_properties( self.property_registry, "domain.materials")
|
||||
self.advanced.register_preset_properties( self.property_registry, "domain.advanced")
|
||||
self.debug.register_preset_properties( self.property_registry, "domain.debug")
|
||||
self.stats.register_preset_properties( self.property_registry, "domain.stats")
|
||||
self._validate_property_registry()
|
||||
|
||||
|
||||
def _validate_property_registry(self):
|
||||
for p in self.property_registry.properties:
|
||||
path = p.path
|
||||
base, group, identifier = path.split('.', 2)
|
||||
if not hasattr(self, group):
|
||||
print("Property Registry Error: Unknown Property Group <" +
|
||||
group + ", " + path + ">")
|
||||
continue
|
||||
prop_group = getattr(self, group)
|
||||
if not hasattr(prop_group, identifier):
|
||||
print("Property Registry Error: Unknown Identifier <" +
|
||||
identifier + ", " + path + ">")
|
||||
continue
|
||||
|
||||
|
||||
def _update_to_flip_fluids_version_180(self):
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
|
||||
if self.is_updated_to_flip_fluids_version_180:
|
||||
return
|
||||
|
||||
print("\n*** Begin updating FLIP Domain to FLIP Fluids version 1.8.0+ ***")
|
||||
|
||||
rprops = dprops.render
|
||||
if rprops.whitewater_view_settings_mode == 'VIEW_SETTINGS_WHITEWATER':
|
||||
viewport_whitewater_pct = rprops.viewport_whitewater_pct
|
||||
render_whitewater_pct = rprops.render_whitewater_pct
|
||||
|
||||
print("\tSetting viewport whitewater foam display percent to " + str(viewport_whitewater_pct) + "%")
|
||||
rprops.viewport_foam_pct = viewport_whitewater_pct
|
||||
print("\tSetting viewport whitewater bubble display percent to " + str(viewport_whitewater_pct) + "%")
|
||||
rprops.viewport_bubble_pct = viewport_whitewater_pct
|
||||
print("\tSetting viewport whitewater spray display percent to " + str(viewport_whitewater_pct) + "%")
|
||||
rprops.viewport_spray_pct = viewport_whitewater_pct
|
||||
print("\tSetting viewport whitewater dust display percent to " + str(viewport_whitewater_pct) + "%")
|
||||
rprops.viewport_dust_pct = viewport_whitewater_pct
|
||||
print("\tSetting render whitewater foam display percent to " + str(render_whitewater_pct) + "%")
|
||||
rprops.render_foam_pct = render_whitewater_pct
|
||||
print("\tSetting render whitewater bubble display percent to " + str(render_whitewater_pct) + "%")
|
||||
rprops.render_bubble_pct = render_whitewater_pct
|
||||
print("\tSetting render whitewater spray display percent to " + str(render_whitewater_pct) + "%")
|
||||
rprops.render_spray_pct = render_whitewater_pct
|
||||
print("\tSetting render whitewater dust display percent to " + str(render_whitewater_pct) + "%")
|
||||
rprops.render_dust_pct = render_whitewater_pct
|
||||
|
||||
parent_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
blend_resource_filename = "geometry_nodes_library.blend"
|
||||
resource_filepath = os.path.join(parent_path, "resources", "geometry_nodes", blend_resource_filename)
|
||||
|
||||
if rprops.whitewater_particle_object_settings_mode == 'WHITEWATER_OBJECT_SETTINGS_WHITEWATER':
|
||||
foam_scale = bubble_scale = spray_scale = dust_scale = rprops.whitewater_particle_scale
|
||||
else:
|
||||
foam_scale = rprops.foam_particle_scale
|
||||
bubble_scale = rprops.bubble_particle_scale
|
||||
spray_scale = rprops.spray_particle_scale
|
||||
dust_scale = rprops.dust_particle_scale
|
||||
particle_scales = [foam_scale, bubble_scale, spray_scale, dust_scale]
|
||||
|
||||
mesh_cache_foam = dprops.mesh_cache.foam.get_cache_object()
|
||||
mesh_cache_bubble = dprops.mesh_cache.bubble.get_cache_object()
|
||||
mesh_cache_spray = dprops.mesh_cache.spray.get_cache_object()
|
||||
mesh_cache_dust = dprops.mesh_cache.dust.get_cache_object()
|
||||
mesh_caches = [mesh_cache_foam, mesh_cache_bubble, mesh_cache_spray, mesh_cache_dust]
|
||||
|
||||
resource_names = [
|
||||
"FF_GeometryNodesWhitewaterFoam",
|
||||
"FF_GeometryNodesWhitewaterBubble",
|
||||
"FF_GeometryNodesWhitewaterSpray",
|
||||
"FF_GeometryNodesWhitewaterDust"
|
||||
]
|
||||
|
||||
for idx, mesh_cache in enumerate(mesh_caches):
|
||||
if mesh_cache is None:
|
||||
continue
|
||||
if helper_operators.is_geometry_node_point_cloud_detected(mesh_cache):
|
||||
continue
|
||||
|
||||
print("\tInitializing geometry node modifier on <" + mesh_cache.name + ">")
|
||||
gn_modifier = helper_operators.add_geometry_node_modifier(mesh_cache, resource_filepath, resource_names[idx])
|
||||
if gn_modifier:
|
||||
try:
|
||||
# Material
|
||||
if mesh_cache.active_material is not None:
|
||||
print("\t\tSetting point cloud material to <" + mesh_cache.active_material.name + ">")
|
||||
gn_modifier["Input_5"] = mesh_cache.active_material
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Input flip_velocity
|
||||
gn_modifier["Input_2_use_attribute"] = True
|
||||
gn_modifier["Input_2_attribute_name"] = 'flip_velocity'
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Output velocity
|
||||
gn_modifier["Output_3_attribute_name"] = 'velocity'
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Particle Scale
|
||||
print("\t\tSetting point cloud particle scale to " + str(particle_scales[idx]))
|
||||
gn_modifier["Input_6"] = particle_scales[idx]
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Enable Point Cloud
|
||||
gn_modifier["Input_9"] = True
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Enable Instancing
|
||||
gn_modifier["Input_10"] = False
|
||||
except:
|
||||
pass
|
||||
|
||||
print("*** Finished updating FLIP Domain to FLIP Fluids version 1.8.0+ ***\n")
|
||||
self.is_updated_to_flip_fluids_version_180 = True
|
||||
|
||||
|
||||
def _update_to_flip_fluids_version_184(self):
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
|
||||
if self.is_updated_to_flip_fluids_version_184:
|
||||
return
|
||||
|
||||
print("\n*** Begin updating FLIP Domain to FLIP Fluids version 1.8.4+ ***")
|
||||
|
||||
mesh_cache_surface = dprops.mesh_cache.surface.get_cache_object()
|
||||
mesh_cache_particles = dprops.mesh_cache.particles.get_cache_object()
|
||||
mesh_cache_foam = dprops.mesh_cache.foam.get_cache_object()
|
||||
mesh_cache_bubble = dprops.mesh_cache.bubble.get_cache_object()
|
||||
mesh_cache_spray = dprops.mesh_cache.spray.get_cache_object()
|
||||
mesh_cache_dust = dprops.mesh_cache.dust.get_cache_object()
|
||||
mesh_caches = [mesh_cache_surface, mesh_cache_particles, mesh_cache_foam, mesh_cache_bubble, mesh_cache_spray, mesh_cache_dust]
|
||||
|
||||
for mcache in mesh_caches:
|
||||
if mcache is None:
|
||||
continue
|
||||
for mod in mcache.modifiers:
|
||||
if mod.type != 'NODES':
|
||||
continue
|
||||
if mod.name.startswith("FF_MotionBlur"):
|
||||
old_name = mod.name
|
||||
new_name = old_name.replace("FF_MotionBlur", "FF_GeometryNodes", 1)
|
||||
mod.name = new_name
|
||||
print("\tUpdated modifier name on <" + mcache.name + "> from <" + old_name + ">" + " to <" + new_name + ">")
|
||||
|
||||
for mcache in mesh_caches:
|
||||
if mcache is None:
|
||||
continue
|
||||
for mod in mcache.modifiers:
|
||||
if mod.type != 'NODES':
|
||||
continue
|
||||
if mod.node_group.name.startswith("FF_MotionBlur"):
|
||||
old_name = mod.node_group.name
|
||||
new_name = old_name.replace("FF_MotionBlur", "FF_GeometryNodes", 1)
|
||||
mod.node_group.name = new_name
|
||||
print("\tUpdated modifier node group name on <" + mcache.name + "> from <" + old_name + ">" + " to <" + new_name + ">")
|
||||
|
||||
print("*** Finished updating FLIP Domain to FLIP Fluids version 1.8.4+ ***\n")
|
||||
self.is_updated_to_flip_fluids_version_184 = True
|
||||
|
||||
|
||||
def scene_update_post(self, scene):
|
||||
self.render.scene_update_post(scene)
|
||||
self.simulation.scene_update_post(scene)
|
||||
self.surface.scene_update_post(scene)
|
||||
self.world.scene_update_post(scene)
|
||||
self.debug.scene_update_post(scene)
|
||||
self.stats.scene_update_post(scene)
|
||||
self.materials.scene_update_post(scene)
|
||||
|
||||
|
||||
def frame_change_post(self, scene, depsgraph=None):
|
||||
api_workaround_utils.frame_change_post_apply_T71908_workaround(bpy.context, depsgraph)
|
||||
self.world.frame_change_post(scene)
|
||||
self.stats.frame_change_post(scene, depsgraph)
|
||||
|
||||
|
||||
def load_pre(self):
|
||||
pass
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self.simulation.load_post()
|
||||
self.bake.load_post()
|
||||
self.cache.load_post()
|
||||
self.surface.load_post()
|
||||
self.stats.load_post()
|
||||
self.debug.load_post()
|
||||
self.presets.load_post()
|
||||
self.advanced.load_post()
|
||||
self.materials.load_post()
|
||||
self.mesh_cache.load_post()
|
||||
self._initialize_property_registry()
|
||||
|
||||
api_workaround_utils.load_post_update_cycles_visibility_forward_compatibility_from_blender_3()
|
||||
self._update_to_flip_fluids_version_180()
|
||||
self._update_to_flip_fluids_version_184()
|
||||
|
||||
|
||||
def save_pre(self):
|
||||
self.debug.save_pre()
|
||||
|
||||
|
||||
def save_post(self):
|
||||
self.cache.save_post()
|
||||
|
||||
|
||||
def scene_update_post(scene):
|
||||
dprops = scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
if not scene.flip_fluid.is_domain_in_active_scene():
|
||||
return
|
||||
dprops.scene_update_post(scene)
|
||||
|
||||
|
||||
def frame_change_post(scene, depsgraph=None):
|
||||
dprops = scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
dprops.frame_change_post(scene, depsgraph)
|
||||
|
||||
|
||||
def load_pre():
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
dprops.load_pre()
|
||||
|
||||
|
||||
def load_post():
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
dprops.load_post()
|
||||
|
||||
|
||||
def save_pre():
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
dprops.save_pre()
|
||||
|
||||
|
||||
def save_post():
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
dprops.save_post()
|
||||
|
||||
|
||||
def register():
|
||||
domain_render_properties.register()
|
||||
domain_bake_properties.register()
|
||||
domain_simulation_properties.register()
|
||||
domain_cache_properties.register()
|
||||
domain_particles_properties.register()
|
||||
domain_surface_properties.register()
|
||||
domain_whitewater_properties.register()
|
||||
domain_world_properties.register()
|
||||
domain_presets_properties.register()
|
||||
domain_materials_properties.register()
|
||||
domain_advanced_properties.register()
|
||||
domain_debug_properties.register()
|
||||
domain_stats_properties.register()
|
||||
bpy.utils.register_class(FlipFluidDomainProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
domain_render_properties.unregister()
|
||||
domain_bake_properties.unregister()
|
||||
domain_simulation_properties.unregister()
|
||||
domain_cache_properties.unregister()
|
||||
domain_particles_properties.unregister()
|
||||
domain_surface_properties.unregister()
|
||||
domain_whitewater_properties.unregister()
|
||||
domain_world_properties.unregister()
|
||||
domain_presets_properties.unregister()
|
||||
domain_materials_properties.unregister()
|
||||
domain_advanced_properties.unregister()
|
||||
domain_debug_properties.unregister()
|
||||
domain_stats_properties.unregister()
|
||||
bpy.utils.unregister_class(FlipFluidDomainProperties)
|
||||
@@ -0,0 +1,383 @@
|
||||
# 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 bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
PointerProperty,
|
||||
StringProperty
|
||||
)
|
||||
|
||||
from .. import types
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
def object_is_mesh_type_poll(self, obj):
|
||||
return obj.type == 'MESH'
|
||||
|
||||
|
||||
class DomainRenderProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
render_display = EnumProperty(
|
||||
name="Render Display Mode",
|
||||
description="How to display the surface mesh for rendering",
|
||||
items=types.display_modes,
|
||||
default='DISPLAY_FINAL',
|
||||
); exec(conv("render_display"))
|
||||
viewport_display = EnumProperty(
|
||||
name="Viewport Display Mode",
|
||||
description="How to display the surface mesh in the viewport",
|
||||
items=types.display_modes,
|
||||
default='DISPLAY_FINAL',
|
||||
); exec(conv("viewport_display"))
|
||||
render_surface_motion_blur = BoolProperty(
|
||||
name="Render Motion Blur",
|
||||
description="Enable surface motion blur rendering. Motion blur"
|
||||
" vectors must be generated to render motion blur. See"
|
||||
" Surface panel to enable motion blur vector generation."
|
||||
" Motion blur must also be enabled in the Cycles render"
|
||||
" properties",
|
||||
default=True,
|
||||
); exec(conv("render_surface_motion_blur"))
|
||||
surface_motion_blur_scale = FloatProperty(
|
||||
name="Scale",
|
||||
description="Scale of the surface motion blur vectors. Increasing this"
|
||||
" value will increase the amount of motion blur. Negative"
|
||||
" values will reverse the direction of blur",
|
||||
default=1.00,
|
||||
min=-10.0, max=10.0,
|
||||
step=0.1,
|
||||
precision=3,
|
||||
); exec(conv("surface_motion_blur_scale"))
|
||||
|
||||
|
||||
fluid_particle_render_display = EnumProperty(
|
||||
name="Fluid Particle Render Display Mode",
|
||||
description="How to display the fluid particles for rendering",
|
||||
items=types.display_modes,
|
||||
default='DISPLAY_FINAL',
|
||||
); exec(conv("fluid_particle_render_display"))
|
||||
fluid_particle_viewport_display = EnumProperty(
|
||||
name="Whitewater Viewport Display Mode",
|
||||
description="How to display the fluid particles in the viewport",
|
||||
items=types.display_modes,
|
||||
default='DISPLAY_PREVIEW',
|
||||
); exec(conv("fluid_particle_viewport_display"))
|
||||
render_fluid_particle_surface_pct = FloatProperty(
|
||||
name="Surface",
|
||||
description="Amount of total surface fluid particles to display during render. Surface"
|
||||
" particles are near the fluid surface and border empty air, but are not near"
|
||||
" the domain boundary",
|
||||
min=0.0, max=1.0,
|
||||
default=1.0,
|
||||
precision=5,
|
||||
); exec(conv("render_fluid_particle_surface_pct"))
|
||||
render_fluid_particle_boundary_pct = FloatProperty(
|
||||
name="Boundary",
|
||||
description="Amount of total boundary fluid particles to display during render. Boundary"
|
||||
" particles are located near the domain boundary",
|
||||
min=0.0, max=1.0,
|
||||
default=1.0,
|
||||
precision=5,
|
||||
); exec(conv("render_fluid_particle_boundary_pct"))
|
||||
render_fluid_particle_interior_pct = FloatProperty(
|
||||
name="Interior",
|
||||
description="Amount of total interior fluid particles to display during render. Interior"
|
||||
" particles are within the fluid and are particles that have not been classified"
|
||||
" as either surface or boundary particles",
|
||||
min=0.0, max=1.0,
|
||||
default=1.0,
|
||||
precision=5,
|
||||
); exec(conv("render_fluid_particle_interior_pct"))
|
||||
viewport_fluid_particle_surface_pct = FloatProperty(
|
||||
name="Surface",
|
||||
description="Amount of total surface fluid particles to display in the viewport. Surface"
|
||||
" particles are near the fluid surface or obstacles, but are not near"
|
||||
" the domain boundary",
|
||||
min=0.0, max=1.0,
|
||||
default=0.5,
|
||||
precision=5,
|
||||
); exec(conv("viewport_fluid_particle_surface_pct"))
|
||||
viewport_fluid_particle_boundary_pct = FloatProperty(
|
||||
name="Boundary",
|
||||
description="Amount of total boundary fluid particles to display in the viewport. Boundary"
|
||||
" particles are located near the domain boundary",
|
||||
min=0.0, max=1.0,
|
||||
default=0.25,
|
||||
precision=5,
|
||||
); exec(conv("viewport_fluid_particle_boundary_pct"))
|
||||
viewport_fluid_particle_interior_pct = FloatProperty(
|
||||
name="Interior",
|
||||
description="Amount of total interior fluid particles to display in the viewport. Interior"
|
||||
" particles are within the fluid and are particles that have not been classified"
|
||||
" as either surface or boundary particles ",
|
||||
min=0.0, max=1.0,
|
||||
default=0.05,
|
||||
precision=5,
|
||||
); exec(conv("viewport_fluid_particle_interior_pct"))
|
||||
|
||||
|
||||
whitewater_render_display = EnumProperty(
|
||||
name="Whitewater Render Display Mode",
|
||||
description="How to display the whitewater particles for rendering",
|
||||
items=types.display_modes,
|
||||
default='DISPLAY_FINAL',
|
||||
); exec(conv("whitewater_render_display"))
|
||||
whitewater_viewport_display = EnumProperty(
|
||||
name="Whitewater Viewport Display Mode",
|
||||
description="How to display the whitewater particles in the viewport",
|
||||
items=types.display_modes,
|
||||
default='DISPLAY_FINAL',
|
||||
); exec(conv("whitewater_viewport_display"))
|
||||
render_whitewater_motion_blur = BoolProperty(
|
||||
name="Render Motion Blur",
|
||||
description="Enable whitewater motion blur rendering. Motion blur"
|
||||
" vectors must be generated to render motion blur. See"
|
||||
" Whitewater panel to enable motion blur vector generation."
|
||||
" Motion blur must also be enabled in the Cycles render"
|
||||
" properties",
|
||||
default=True,
|
||||
); exec(conv("render_whitewater_motion_blur"))
|
||||
whitewater_motion_blur_scale = FloatProperty(
|
||||
name="Scale",
|
||||
description="Scale of the whitewater motion blur vectors. Increasing this"
|
||||
" value will increase the amount of motion blur. Negative"
|
||||
" values will reverse the direction of blur",
|
||||
default=1.00,
|
||||
min=-10.0, max=10.0,
|
||||
step=0.1,
|
||||
precision=3,
|
||||
); exec(conv("whitewater_motion_blur_scale"))
|
||||
render_whitewater_pct = IntProperty(
|
||||
name="Whitewater",
|
||||
description="Percentage of total whitewater particles to display",
|
||||
min=0, max=100,
|
||||
default=100,
|
||||
subtype='PERCENTAGE',
|
||||
); exec(conv("render_whitewater_pct"))
|
||||
render_foam_pct = IntProperty(
|
||||
name="Foam",
|
||||
description="Percentage of total foam particles to display",
|
||||
min=0, max=100,
|
||||
default=100,
|
||||
subtype='PERCENTAGE',
|
||||
); exec(conv("render_foam_pct"))
|
||||
render_bubble_pct = IntProperty(
|
||||
name="Bubble",
|
||||
description="Percentage of total bubble particles to display",
|
||||
min=0, max=100,
|
||||
default=100,
|
||||
subtype='PERCENTAGE',
|
||||
); exec(conv("render_bubble_pct"))
|
||||
render_spray_pct = IntProperty(
|
||||
name="Spray",
|
||||
description="Percentage of total spray particles to display",
|
||||
min=0, max=100,
|
||||
default=100,
|
||||
subtype='PERCENTAGE',
|
||||
); exec(conv("render_spray_pct"))
|
||||
render_dust_pct = IntProperty(
|
||||
name="Dust",
|
||||
description="Percentage of total dust particles to display",
|
||||
min=0, max=100,
|
||||
default=100,
|
||||
subtype='PERCENTAGE',
|
||||
); exec(conv("render_dust_pct"))
|
||||
viewport_whitewater_pct = IntProperty(
|
||||
name="Whitewater",
|
||||
description="Percentage of total whitewater particles to display",
|
||||
min=0, max=100,
|
||||
default=5,
|
||||
subtype='PERCENTAGE',
|
||||
); exec(conv("viewport_whitewater_pct"))
|
||||
viewport_foam_pct = IntProperty(
|
||||
name="Foam",
|
||||
description="Percentage of total foam particles to display",
|
||||
min=0, max=100,
|
||||
default=5,
|
||||
subtype='PERCENTAGE',
|
||||
); exec(conv("viewport_foam_pct"))
|
||||
viewport_bubble_pct = IntProperty(
|
||||
name="Bubble",
|
||||
description="Percentage of total bubble particles to display",
|
||||
min=0, max=100,
|
||||
default=5,
|
||||
subtype='PERCENTAGE',
|
||||
); exec(conv("viewport_bubble_pct"))
|
||||
viewport_spray_pct = IntProperty(
|
||||
name="Spray",
|
||||
description="Percentage of total spray particles to display",
|
||||
min=0, max=100,
|
||||
default=5,
|
||||
subtype='PERCENTAGE',
|
||||
); exec(conv("viewport_spray_pct"))
|
||||
viewport_dust_pct = IntProperty(
|
||||
name="Dust",
|
||||
description="Percentage of total dust particles to display",
|
||||
min=0, max=100,
|
||||
default=5,
|
||||
subtype='PERCENTAGE',
|
||||
); exec(conv("viewport_dust_pct"))
|
||||
|
||||
whitewater_view_settings_mode = EnumProperty(
|
||||
name="View Settings Mode",
|
||||
description="How display settings will be applied to whitewater particles",
|
||||
items=types.whitewater_view_settings_modes,
|
||||
default='VIEW_SETTINGS_WHITEWATER',
|
||||
); exec(conv("whitewater_view_settings_mode"))
|
||||
whitewater_particle_object_settings_mode = EnumProperty(
|
||||
name="Particle Object Settings Mode",
|
||||
description="How particle object settings will be applied to whitewater particles",
|
||||
items=types.whitewater_object_settings_modes,
|
||||
default='WHITEWATER_OBJECT_SETTINGS_WHITEWATER',
|
||||
); exec(conv("whitewater_particle_object_settings_mode"))
|
||||
|
||||
# Particle scale settings are no longer used in FLIP Fluids 1.8.0+
|
||||
# Only used to update Blend files created in FLIP Fluids 1.7.5 and earlier
|
||||
# to newer addon versions.
|
||||
whitewater_particle_scale = FloatProperty(
|
||||
name="Scale",
|
||||
description="Scale of the whitewater particle object",
|
||||
min=0.0,
|
||||
default=0.008,
|
||||
step=0.01,
|
||||
precision=4,
|
||||
); exec(conv("whitewater_particle_scale"))
|
||||
foam_particle_scale = FloatProperty(
|
||||
name="Scale",
|
||||
description="Scale of the foam particle object",
|
||||
min=0.0,
|
||||
default=0.008,
|
||||
step=0.01,
|
||||
precision=4,
|
||||
); exec(conv("foam_particle_scale"))
|
||||
bubble_particle_scale = FloatProperty(
|
||||
name="Scale",
|
||||
description="Scale of the bubble particle object",
|
||||
min=0.0,
|
||||
default=0.008,
|
||||
step=0.01,
|
||||
precision=4,
|
||||
); exec(conv("bubble_particle_scale"))
|
||||
spray_particle_scale = FloatProperty(
|
||||
name="Scale",
|
||||
description="Scale of the spray particle object",
|
||||
min=0.0,
|
||||
default=0.008,
|
||||
step=0.01,
|
||||
precision=4,
|
||||
); exec(conv("spray_particle_scale"))
|
||||
dust_particle_scale = FloatProperty(
|
||||
name="Scale",
|
||||
description="Scale of the dust particle object",
|
||||
min=0.0,
|
||||
default=0.008,
|
||||
step=0.01,
|
||||
precision=4,
|
||||
); exec(conv("dust_particle_scale"))
|
||||
|
||||
simulation_playback_mode = EnumProperty(
|
||||
name="Simulation Playback Mode",
|
||||
description="How to playback the simulation animation",
|
||||
items=types.simulation_playback_mode,
|
||||
default='PLAYBACK_MODE_TIMELINE',
|
||||
); exec(conv("simulation_playback_mode"))
|
||||
override_frame = FloatProperty(
|
||||
name="Override Frame",
|
||||
description="The custom frame number to override. If this value is not a whole number,"
|
||||
" the frame to be loaded will be rounded down. TIP: This value can be keyframed for"
|
||||
" complex control of simulation playback",
|
||||
default=1.000,
|
||||
); exec(conv("override_frame"))
|
||||
hold_frame_number = IntProperty(
|
||||
name="Hold Frame",
|
||||
description="Frame number to be held in place",
|
||||
min=0,
|
||||
default=0,
|
||||
options = {'HIDDEN'},
|
||||
); exec(conv("hold_frame_number"))
|
||||
|
||||
|
||||
whitewater_display_settings_expanded = BoolProperty(default=False); exec(conv("whitewater_display_settings_expanded"))
|
||||
fluid_particle_display_settings_expanded = BoolProperty(default=False); exec(conv("fluid_particle_display_settings_expanded"))
|
||||
surface_display_settings_expanded = BoolProperty(default=True); exec(conv("surface_display_settings_expanded"))
|
||||
simulation_display_settings_expanded = BoolProperty(default=False); exec(conv("simulation_display_settings_expanded"))
|
||||
current_frame = IntProperty(default=-1); exec(conv("current_frame"))
|
||||
is_hold_frame_number_set = BoolProperty(default=False); exec(conv("is_hold_frame_number_set"))
|
||||
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
add = registry.add_property
|
||||
add(path + ".render_display", "Surface Render", group_id=0)
|
||||
add(path + ".viewport_display", "Surface Viewport", group_id=0)
|
||||
add(path + ".fluid_particle_render_display", "Particle Render", group_id=0)
|
||||
add(path + ".fluid_particle_viewport_display", "Particle Viewport", group_id=0)
|
||||
add(path + ".whitewater_render_display", "Whitewater Render", group_id=0)
|
||||
add(path + ".whitewater_viewport_display", "Whitewater Viewport", group_id=0)
|
||||
add(path + ".render_surface_motion_blur", "Render Motion Blur", group_id=0)
|
||||
add(path + ".override_frame", "Override Frame", group_id=0)
|
||||
|
||||
add(path + ".whitewater_view_settings_mode", "Whitewater View Mode", group_id=1)
|
||||
add(path + ".whitewater_particle_object_settings_mode", "Whitewater View Mode", group_id=1)
|
||||
|
||||
add(path + ".render_whitewater_pct", "Whitewater Render Pct", group_id=1)
|
||||
add(path + ".viewport_whitewater_pct", "Whitewater Viewport Pct", group_id=1)
|
||||
add(path + ".render_foam_pct", "Foam Render Pct", group_id=1)
|
||||
add(path + ".render_bubble_pct", "Bubble Render Pct", group_id=1)
|
||||
add(path + ".render_spray_pct", "Spray Render Pct", group_id=1)
|
||||
add(path + ".render_dust_pct", "Dust Render Pct", group_id=1)
|
||||
add(path + ".viewport_foam_pct", "Foam Viewport Pct", group_id=1)
|
||||
add(path + ".viewport_bubble_pct", "Bubble Viewport Pct", group_id=1)
|
||||
add(path + ".viewport_spray_pct", "Spray Viewport Pct", group_id=1)
|
||||
add(path + ".viewport_dust_pct", "Dust Viewport Pct", group_id=1)
|
||||
|
||||
add(path + ".whitewater_particle_scale", "Whitewater Particle Scale", group_id=2)
|
||||
add(path + ".foam_particle_scale", "Foam Particle Scale", group_id=2)
|
||||
add(path + ".bubble_particle_scale", "Bubble Particle Scale", group_id=2)
|
||||
add(path + ".spray_particle_scale", "Spray Particle Scale", group_id=2)
|
||||
add(path + ".dust_particle_scale", "Dust Particle Scale", group_id=2)
|
||||
|
||||
|
||||
def scene_update_post(self, scene):
|
||||
self._scene_update_post_update_hold_frame_number(scene)
|
||||
|
||||
|
||||
def reset_bake(self):
|
||||
self.is_hold_frame_number_set = False
|
||||
|
||||
|
||||
def _update_hold_frame(self, context):
|
||||
if self.simulation_playback_mode == 'PLAYBACK_MODE_HOLD_FRAME':
|
||||
self.is_hold_frame_number_set = True
|
||||
|
||||
|
||||
def _scene_update_post_update_hold_frame_number(self, scene):
|
||||
if self.simulation_playback_mode == 'PLAYBACK_MODE_HOLD_FRAME' or self.is_hold_frame_number_set:
|
||||
return
|
||||
if self.hold_frame_number != scene.frame_current:
|
||||
self.hold_frame_number = scene.frame_current
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DomainRenderProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(DomainRenderProperties)
|
||||
@@ -0,0 +1,759 @@
|
||||
# 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, os, json
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
BoolVectorProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
PointerProperty,
|
||||
StringProperty
|
||||
)
|
||||
|
||||
from .custom_properties import (
|
||||
NewMinMaxIntProperty,
|
||||
)
|
||||
|
||||
from .. import types
|
||||
from ..objects.flip_fluid_aabb import AABB
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
from ..utils import export_utils
|
||||
|
||||
|
||||
class DomainSimulationProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
frame_range_mode = EnumProperty(
|
||||
name="Frame Range Mode",
|
||||
description="Frame range to use for baking the simulation",
|
||||
items=types.frame_range_modes,
|
||||
default='FRAME_RANGE_TIMELINE',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("frame_range_mode"))
|
||||
frame_range_custom = NewMinMaxIntProperty(
|
||||
name_min="Start Frame",
|
||||
description_min="First frame of the simulation cache",
|
||||
min_min=0,
|
||||
default_min=1,
|
||||
options_min={'HIDDEN'},
|
||||
|
||||
name_max="End Frame",
|
||||
description_max="Final frame of the simulation cache",
|
||||
min_max=0,
|
||||
default_max=250,
|
||||
options_max={'HIDDEN'},
|
||||
); exec(conv("frame_range_custom"))
|
||||
update_settings_on_resume = BoolProperty(
|
||||
name="Update Settings on Resume",
|
||||
description="Update simulation settings and meshes when resuming a bake."
|
||||
" If disabled, the simulator will use the original settings and meshes"
|
||||
" from when the bake was started",
|
||||
default=True,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("update_settings_on_resume"))
|
||||
mesh_reexport_type_filter = EnumProperty(
|
||||
name="Object Motion Type",
|
||||
description="Filter objects by motion type for skip re-export list display",
|
||||
items=types.motion_filter_types,
|
||||
default='MOTION_FILTER_TYPE_ANIMATED',
|
||||
); exec(conv("mesh_reexport_type_filter"))
|
||||
enable_savestates = BoolProperty(
|
||||
name="Enable Savestates",
|
||||
description="Generate savestates/checkpoints as the simulation progresses."
|
||||
" Savestates will allow you to rollback the simulation to an earlier"
|
||||
" point so that you can re-simulate from a previous frame",
|
||||
default=True,
|
||||
options = {'HIDDEN'},
|
||||
); exec(conv("enable_savestates"))
|
||||
savestate_interval = IntProperty(
|
||||
name="Savestate Interval",
|
||||
description="Number of frames between each savestate",
|
||||
min=1,
|
||||
default=50,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("savestate_interval"))
|
||||
delete_outdated_savestates = BoolProperty(
|
||||
name="Delete Outdated Savestates on Resume",
|
||||
description="When resuming a simulation from a previous frame, delete"
|
||||
" all savestates that are ahead in the timeline",
|
||||
default=True,
|
||||
options = {'HIDDEN'},
|
||||
); exec(conv("delete_outdated_savestates"))
|
||||
delete_outdated_meshes = BoolProperty(
|
||||
name="Delete Outdated Meshes on Resume",
|
||||
description="When resuming a simulation from a previous frame, delete"
|
||||
" all simulation meshes that are ahead in the timeline",
|
||||
default=True,
|
||||
options = {'HIDDEN'},
|
||||
); exec(conv("delete_outdated_meshes"))
|
||||
selected_savestate = EnumProperty(
|
||||
name="Selected Savestate",
|
||||
description="Resume simulation from this savestate frame",
|
||||
update=lambda self, context: self._update_selected_savestate(context),
|
||||
items=lambda self, context: self._get_savestate_enums(context),
|
||||
); exec(conv("selected_savestate"))
|
||||
selected_savestate_int = IntProperty(
|
||||
name="Selected Savestate",
|
||||
description="Resume simulation from this savestate frame",
|
||||
update=lambda self, context: self._update_selected_savestate_int(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("selected_savestate_int"))
|
||||
resolution = IntProperty(
|
||||
name="Resolution",
|
||||
description="Domain grid resolution. This value specifies the number of"
|
||||
" grid voxels on the longest side of the domain. See the tooltips in"
|
||||
" the 'Domain > Simulation > Grid Info' submenu for a detailed explanation"
|
||||
" of the domain grid",
|
||||
min=10,
|
||||
default=65,
|
||||
update=lambda self, context: self._update_resolution(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("resolution"))
|
||||
preview_resolution = IntProperty(
|
||||
name="Preview Resolution",
|
||||
description="The resolution to use for generating lower quality meshes for"
|
||||
" the preview fluid surface. Increasing this value will take no extra time"
|
||||
" to simulate, but will increase cache size",
|
||||
min=1, soft_max=150,
|
||||
default=45,
|
||||
update=lambda self, context: self._update_preview_resolution(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("preview_resolution"))
|
||||
auto_preview_resolution = BoolProperty(
|
||||
name="Recommended",
|
||||
description="Set recommended preview resolution based on domain resolution and"
|
||||
" mesh generation settings",
|
||||
default=True,
|
||||
update=lambda self, context: self._update_auto_preview_resolution(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("auto_preview_resolution"))
|
||||
lock_cell_size = BoolProperty(
|
||||
name="Lock Voxel Size",
|
||||
description="Lock the current voxel size and update the grid"
|
||||
" resolution as the domain dimensions are changed. Enable this"
|
||||
" option before resizing the domain to maintain a constant level"
|
||||
" of simulation detail",
|
||||
default=False,
|
||||
update=lambda self, context: self._update_lock_cell_size(context),
|
||||
options = {'HIDDEN'},
|
||||
); exec(conv("lock_cell_size"))
|
||||
fluid_boundary_collisions = BoolVectorProperty(
|
||||
name="",
|
||||
description="Enable collisions on the corresponding side of the domain."
|
||||
" If disabled, this side of the boundary will be open and will act"
|
||||
" as an outflow",
|
||||
default=(True, True, True, True, True, True),
|
||||
size=6,
|
||||
); exec(conv("fluid_boundary_collisions"))
|
||||
fluid_open_boundary_width = IntProperty(
|
||||
name="Open Boundary Width",
|
||||
description="The distance (in number of voxels) from the domain boundary that fluid will be"
|
||||
" removed for open boundary sides. Note: This setting is for testing purposes and may"
|
||||
" be removed in a future update if not needed",
|
||||
soft_min=2, min=1,
|
||||
soft_max=10,
|
||||
default=4,
|
||||
); exec(conv("fluid_open_boundary_width"))
|
||||
frame_rate_mode = EnumProperty(
|
||||
name="Frame Rate Mode",
|
||||
description="Select the frame rate for the simulation animation",
|
||||
items=types.frame_rate_modes,
|
||||
default='FRAME_RATE_MODE_SCENE',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("frame_rate_mode"))
|
||||
frame_rate_custom = FloatProperty(
|
||||
name="Frame Rate",
|
||||
description="Frame rate in frames per second",
|
||||
min=0.001,
|
||||
default=60.0,
|
||||
precision=2,
|
||||
); exec(conv("frame_rate_custom"))
|
||||
time_scale_mode = EnumProperty(
|
||||
name="Time Scale Mode",
|
||||
description="Select the time scale mode for the simulation. Use either a custom"
|
||||
" value or match the value of another simulation. The simulation speed will be"
|
||||
" scaled by this value",
|
||||
items=types.time_scale_modes,
|
||||
default='TIME_SCALE_MODE_CUSTOM',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("time_scale_mode"))
|
||||
|
||||
def time_scale_object_soft_body_poll(self, bl_object):
|
||||
for mod in bl_object.modifiers:
|
||||
if mod.type == 'SOFT_BODY':
|
||||
return True
|
||||
return False
|
||||
def get_selected_time_scale_object_soft_body_modifier(self):
|
||||
if self.time_scale_object_soft_body is None:
|
||||
return None
|
||||
for mod in self.time_scale_object_soft_body.modifiers:
|
||||
if mod.type == 'SOFT_BODY':
|
||||
return mod
|
||||
return None
|
||||
time_scale_object_soft_body = PointerProperty(
|
||||
name="Soft Body Object",
|
||||
type=bpy.types.Object,
|
||||
poll=time_scale_object_soft_body_poll,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("time_scale_object_soft_body"))
|
||||
|
||||
def time_scale_object_cloth_poll(self, bl_object):
|
||||
for mod in bl_object.modifiers:
|
||||
if mod.type == 'CLOTH':
|
||||
return True
|
||||
return False
|
||||
def get_selected_time_scale_object_cloth_modifier(self):
|
||||
if self.time_scale_object_cloth is None:
|
||||
return None
|
||||
for mod in self.time_scale_object_cloth.modifiers:
|
||||
if mod.type == 'CLOTH':
|
||||
return mod
|
||||
return None
|
||||
time_scale_object_cloth = PointerProperty(
|
||||
name="Cloth Object",
|
||||
type=bpy.types.Object,
|
||||
poll=time_scale_object_cloth_poll,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("time_scale_object_cloth"))
|
||||
|
||||
def time_scale_object_fluid_poll(self, bl_object):
|
||||
if not vcu.is_blender_282():
|
||||
return False
|
||||
for mod in bl_object.modifiers:
|
||||
if mod.type == 'FLUID' and mod.fluid_type == 'DOMAIN':
|
||||
return True
|
||||
return False
|
||||
def get_selected_time_scale_object_fluid_modifier(self):
|
||||
if not vcu.is_blender_282():
|
||||
return False
|
||||
if self.time_scale_object_fluid is None:
|
||||
return None
|
||||
for mod in self.time_scale_object_fluid.modifiers:
|
||||
if mod.type == 'FLUID' and mod.fluid_type == 'DOMAIN':
|
||||
return mod
|
||||
return None
|
||||
time_scale_object_fluid = PointerProperty(
|
||||
name="Fluid Domain Object",
|
||||
type=bpy.types.Object,
|
||||
poll=time_scale_object_fluid_poll,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("time_scale_object_fluid"))
|
||||
|
||||
time_scale = FloatProperty(
|
||||
name="Custom Time Scale",
|
||||
description="Scale the simulation speed by this value. If set to less than"
|
||||
" 1.0, the simulation will appear in slow motion. If set to greater than"
|
||||
" 1.0, the simulation will appear sped up",
|
||||
min=0.0,
|
||||
default=1.0,
|
||||
precision=3,
|
||||
); exec(conv("time_scale"))
|
||||
|
||||
locked_cell_size = FloatProperty(default=-1.0); exec(conv("locked_cell_size"))
|
||||
frame_start = IntProperty(default=-1); exec(conv("frame_start"))
|
||||
frame_end = IntProperty(default=-1); exec(conv("frame_end"))
|
||||
|
||||
more_bake_settings_expanded = BoolProperty(default=False); exec(conv("more_bake_settings_expanded"))
|
||||
skip_mesh_reexport_expanded = BoolProperty(default=False); exec(conv("skip_mesh_reexport_expanded"))
|
||||
simulation_resolution_expanded = BoolProperty(default=False); exec(conv("simulation_resolution_expanded"))
|
||||
grid_info_expanded = BoolProperty(default=False); exec(conv("grid_info_expanded"))
|
||||
simulation_method_expanded = BoolProperty(default=False); exec(conv("simulation_method_expanded"))
|
||||
world_scale_expanded = BoolProperty(default=False); exec(conv("world_scale_expanded"))
|
||||
boundary_collisions_expanded = BoolProperty(default=False); exec(conv("boundary_collisions_expanded"))
|
||||
frame_rate_and_time_scale_expanded = BoolProperty(default=False); exec(conv("frame_rate_and_time_scale_expanded"))
|
||||
last_selected_savestate_int = IntProperty(default=-1); exec(conv("last_selected_savestate_int"))
|
||||
selected_savestate_int_label = StringProperty(default=""); exec(conv("selected_savestate_int_label"))
|
||||
|
||||
current_isize = IntProperty(default=-1); exec(conv("current_isize"))
|
||||
current_jsize = IntProperty(default=-1); exec(conv("current_jsize"))
|
||||
current_ksize = IntProperty(default=-1); exec(conv("current_ksize"))
|
||||
current_dx = FloatProperty(default=-1.0); exec(conv("current_dx"))
|
||||
|
||||
savestate_isize = IntProperty(default=-1); exec(conv("savestate_isize"))
|
||||
savestate_jsize = IntProperty(default=-1); exec(conv("savestate_jsize"))
|
||||
savestate_ksize = IntProperty(default=-1); exec(conv("savestate_ksize"))
|
||||
savestate_dx = FloatProperty(default=-1.0); exec(conv("savestate_dx"))
|
||||
|
||||
upscale_trigger_factor = FloatProperty(default=0.05); exec(conv("upscale_trigger_factor"))
|
||||
|
||||
upscale_resolution_tooltip = BoolProperty(
|
||||
name="Upscale Resolution Tooltip",
|
||||
description="Upscaling converts a lower resolution savestate to a higher resolution savestate"
|
||||
" so that the simulation can resume baking at the increased resolution",
|
||||
default=True,
|
||||
); exec(conv("upscale_resolution_tooltip"))
|
||||
|
||||
grid_voxels_tooltip = BoolProperty(
|
||||
name="Grid Voxels Tooltip",
|
||||
description="The domain is a 3D grid of cubes called voxels, or cells. This info shows the"
|
||||
" number of voxels on each of the X/Y/Z axis of the domain. The voxels in the 3D grid are"
|
||||
" similar to the 2D pixels in a 2D image, except instead of storing color data, the voxels store"
|
||||
" physics data",
|
||||
default=True,
|
||||
); exec(conv("grid_voxels_tooltip"))
|
||||
|
||||
grid_dimensions_tooltip = BoolProperty(
|
||||
name="Grid Dimensions Tooltip",
|
||||
description="Displays the physical scale of the domain on the X/Y/Z axis in meters."
|
||||
" Setting an appropriate scale can be an important factor for realistic motion and speed"
|
||||
" of your simulated fluid",
|
||||
default=True,
|
||||
); exec(conv("grid_dimensions_tooltip"))
|
||||
|
||||
grid_voxel_size_tooltip = BoolProperty(
|
||||
name="Voxel Size Tooltip",
|
||||
description="Displays the physical size of a single voxel. You can think of a voxel as"
|
||||
" the 3D version of a 2D image pixel. In an image, the pixel size is the minimum"
|
||||
" amount of detail that can be resolved in the picture. In the domain, the voxel size is"
|
||||
" the minimum amount of physics detail that can be resolved in the simulation such as the"
|
||||
" smallest droplets and ripples or the thinnest splashes",
|
||||
default=True,
|
||||
); exec(conv("grid_voxel_size_tooltip"))
|
||||
|
||||
grid_voxel_count_tooltip = BoolProperty(
|
||||
name="Voxel Count Tooltip",
|
||||
description="Displays the total number of voxels in the domain. Physics are computed for each"
|
||||
" voxel and the total count can be a measure for how much work your system will be doing."
|
||||
" Small simulation = around 2 Million. Medium = around 10M. Large = around 40M."
|
||||
" Very Large = over 80M",
|
||||
default=True,
|
||||
); exec(conv("grid_voxel_count_tooltip"))
|
||||
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
add = registry.add_property
|
||||
add(path + ".resolution", "Resolution", group_id=0)
|
||||
add(path + ".preview_resolution", "Preview Resolution", group_id=0)
|
||||
add(path + ".auto_preview_resolution", "Auto Preview Resolution", group_id=0)
|
||||
add(path + ".lock_cell_size", "Lock Cell Size", group_id=0)
|
||||
add(path + ".frame_rate_mode", "Frame Rate Mode", group_id=0)
|
||||
add(path + ".frame_rate_custom", "Frame Rate", group_id=0)
|
||||
add(path + ".time_scale_mode", "Time Scale Mode", group_id=0)
|
||||
add(path + ".time_scale", "Time Scale", group_id=0)
|
||||
add(path + ".fluid_boundary_collisions", "Boundary Collisions", group_id=0)
|
||||
add(path + ".fluid_open_boundary_width", "Open Boundary Width", group_id=0)
|
||||
add(path + ".frame_range_mode", "Frame Range Mode", group_id=1)
|
||||
add(path + ".frame_range_custom", "Frame Range (Custom)", group_id=1)
|
||||
add(path + ".update_settings_on_resume", "Update Settings on Resume", group_id=1)
|
||||
add(path + ".enable_savestates", "Enable Savestates", group_id=1)
|
||||
add(path + ".savestate_interval", "Savestate Interval", group_id=1)
|
||||
add(path + ".delete_outdated_savestates", "Delete Outdated Savestates", group_id=1)
|
||||
add(path + ".delete_outdated_meshes", "Delete Outdated Meshes", group_id=1)
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self.frame_rate_custom = bpy.context.scene.render.fps
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self._update_current_settings_grid_dimensions()
|
||||
|
||||
|
||||
def scene_update_post(self, scene):
|
||||
# This section does not seem to be required any longer. Not sure why.
|
||||
# This section also seems to cause errors in Octane Render
|
||||
# for Blender 3.1 when the renderer is set to Octane.
|
||||
"""
|
||||
if self.grid_info_expanded:
|
||||
# Workaround to get UI panel to redraw when grid info is displayed
|
||||
selected_object = vcu.get_active_object()
|
||||
if selected_object is not None and selected_object.flip_fluid.is_domain():
|
||||
# Possible bug in Blender where this could crash if the domain is not
|
||||
# selected
|
||||
self.resolution = self.resolution
|
||||
"""
|
||||
|
||||
self._update_locked_cell_size_resolution()
|
||||
self._set_recommended_preview_resolution()
|
||||
self._update_current_settings_grid_dimensions()
|
||||
|
||||
|
||||
# World scale is not applied to dx, which will match dimensions within viewport
|
||||
def get_viewport_grid_dimensions(self, resolution=None, lock_cell_size=None):
|
||||
domain_object = bpy.context.scene.flip_fluid.get_domain_object()
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None or resolution == 0:
|
||||
return 1, 1, 1, 1.0
|
||||
|
||||
if lock_cell_size is None:
|
||||
lock_cell_size = self.lock_cell_size
|
||||
if resolution is None:
|
||||
resolution = self.resolution
|
||||
else:
|
||||
lock_cell_size = False
|
||||
|
||||
domain_bbox = AABB.from_blender_object(domain_object)
|
||||
max_dim = max(domain_bbox.xdim, domain_bbox.ydim, domain_bbox.zdim)
|
||||
if lock_cell_size:
|
||||
unlocked_dx = max_dim / resolution
|
||||
locked_dx = self.locked_cell_size
|
||||
dx = locked_dx
|
||||
if abs(locked_dx - unlocked_dx) < 1e-6:
|
||||
dx = unlocked_dx
|
||||
else:
|
||||
dx = max_dim / resolution
|
||||
|
||||
precision = 5
|
||||
isize = math.ceil(round(domain_bbox.xdim / dx, precision))
|
||||
jsize = math.ceil(round(domain_bbox.ydim / dx, precision))
|
||||
ksize = math.ceil(round(domain_bbox.zdim / dx, precision))
|
||||
|
||||
return isize, jsize, ksize, dx
|
||||
|
||||
|
||||
# World scale will be applied to dx, which will match dimensions within the simulation
|
||||
def get_simulation_grid_dimensions(self, resolution=None, lock_cell_size=None):
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
isize, jsize, ksize, dx = self.get_viewport_grid_dimensions(resolution, lock_cell_size)
|
||||
dx *= dprops.world.get_world_scale()
|
||||
return isize, jsize, ksize, dx
|
||||
|
||||
|
||||
def get_viewport_preview_dx(self):
|
||||
domain_object = bpy.context.scene.flip_fluid.get_domain_object()
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return 1.0
|
||||
domain_bbox = AABB.from_blender_object(domain_object)
|
||||
max_dim = max(domain_bbox.xdim, domain_bbox.ydim, domain_bbox.zdim)
|
||||
return max_dim / self.preview_resolution
|
||||
|
||||
|
||||
def get_simulation_preview_dx(self):
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
preview_dx = self.get_viewport_preview_dx()
|
||||
return preview_dx * dprops.world.get_world_scale()
|
||||
|
||||
|
||||
def get_frame_range(self):
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if self.frame_range_mode == 'FRAME_RANGE_TIMELINE':
|
||||
frame_start = bpy.context.scene.frame_start
|
||||
if dprops.bake.is_autosave_available:
|
||||
frame_start = dprops.bake.original_frame_start
|
||||
return frame_start, bpy.context.scene.frame_end
|
||||
else:
|
||||
frame_start = self.frame_range_custom.value_min
|
||||
if dprops.bake.is_autosave_available:
|
||||
frame_start = dprops.bake.original_frame_start
|
||||
return frame_start, self.frame_range_custom.value_max
|
||||
|
||||
|
||||
def get_selected_savestate_id(self):
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
savestate_id = None
|
||||
if dprops.bake.is_autosave_available:
|
||||
if self.enable_savestates:
|
||||
savestate_id = int(self.selected_savestate)
|
||||
else:
|
||||
return dprops.bake.autosave_frame
|
||||
return savestate_id
|
||||
|
||||
|
||||
def get_num_savestate_enums(self):
|
||||
return len(self._get_savestate_enums())
|
||||
|
||||
|
||||
def get_frame_rate(self):
|
||||
if self.frame_rate_mode == 'FRAME_RATE_MODE_SCENE':
|
||||
return bpy.context.scene.render.fps
|
||||
elif self.frame_rate_mode == 'FRAME_RATE_MODE_CUSTOM':
|
||||
return self.frame_rate_custom
|
||||
|
||||
|
||||
def get_frame_rate_data_dict(self):
|
||||
if self.frame_rate_mode == 'FRAME_RATE_MODE_SCENE':
|
||||
prop_group = bpy.context.scene.render
|
||||
return export_utils.get_property_data_dict(bpy.context.scene, prop_group, 'fps')
|
||||
elif self.frame_rate_mode == 'FRAME_RATE_MODE_CUSTOM':
|
||||
domain_object = bpy.context.scene.flip_fluid.get_domain_object()
|
||||
return export_utils.get_property_data_dict(domain_object, self, 'frame_rate_custom')
|
||||
|
||||
|
||||
def get_time_scale_data_dict(self):
|
||||
property_path = bpy.context.scene.flip_fluid.get_domain_object()
|
||||
property_group = self
|
||||
property_name = "time_scale"
|
||||
|
||||
if self.time_scale_mode == 'TIME_SCALE_MODE_CUSTOM':
|
||||
pass # uses default
|
||||
|
||||
elif self.time_scale_mode == 'TIME_SCALE_MODE_RIGID_BODY':
|
||||
if bpy.context.scene.rigidbody_world is not None:
|
||||
property_path = bpy.context.scene
|
||||
property_group = bpy.context.scene.rigidbody_world
|
||||
property_name = "time_scale"
|
||||
|
||||
elif self.time_scale_mode == 'TIME_SCALE_MODE_SOFT_BODY':
|
||||
soft_body_object = self.time_scale_object_soft_body
|
||||
soft_body_modifier = self.get_selected_time_scale_object_soft_body_modifier()
|
||||
if soft_body_object is not None and soft_body_modifier is not None:
|
||||
property_path = soft_body_object
|
||||
property_group = soft_body_modifier.settings
|
||||
property_name = "speed"
|
||||
|
||||
elif self.time_scale_mode == 'TIME_SCALE_MODE_CLOTH':
|
||||
cloth_object = self.time_scale_object_cloth
|
||||
cloth_modifier = self.get_selected_time_scale_object_cloth_modifier()
|
||||
if cloth_object is not None and cloth_modifier is not None:
|
||||
property_path = cloth_object
|
||||
property_group = cloth_modifier.settings
|
||||
property_name = "time_scale"
|
||||
|
||||
elif self.time_scale_mode == 'TIME_SCALE_MODE_FLUID':
|
||||
fluid_object = self.time_scale_object_fluid
|
||||
fluid_modifier = self.get_selected_time_scale_object_fluid_modifier()
|
||||
if fluid_object is not None and fluid_modifier is not None:
|
||||
property_path = fluid_object
|
||||
property_group = fluid_modifier.domain_settings
|
||||
property_name = "time_scale"
|
||||
|
||||
return export_utils.get_property_data_dict(property_path, property_group, property_name)
|
||||
|
||||
|
||||
def get_current_frame_time_scale(self):
|
||||
property_group = self
|
||||
property_name = "time_scale"
|
||||
|
||||
if self.time_scale_mode == 'TIME_SCALE_MODE_CUSTOM':
|
||||
pass # uses default
|
||||
|
||||
elif self.time_scale_mode == 'TIME_SCALE_MODE_RIGID_BODY':
|
||||
if bpy.context.scene.rigidbody_world is not None:
|
||||
property_group = bpy.context.scene.rigidbody_world
|
||||
property_name = "time_scale"
|
||||
|
||||
elif self.time_scale_mode == 'TIME_SCALE_MODE_SOFT_BODY':
|
||||
soft_body_object = self.time_scale_object_soft_body
|
||||
soft_body_modifier = self.get_selected_time_scale_object_soft_body_modifier()
|
||||
if soft_body_object is not None and soft_body_modifier is not None:
|
||||
property_group = soft_body_modifier.settings
|
||||
property_name = "speed"
|
||||
|
||||
elif self.time_scale_mode == 'TIME_SCALE_MODE_CLOTH':
|
||||
cloth_object = self.time_scale_object_cloth
|
||||
cloth_modifier = self.get_selected_time_scale_object_cloth_modifier()
|
||||
if cloth_object is not None and cloth_modifier is not None:
|
||||
property_group = cloth_modifier.settings
|
||||
property_name = "time_scale"
|
||||
|
||||
elif self.time_scale_mode == 'TIME_SCALE_MODE_FLUID':
|
||||
fluid_object = self.time_scale_object_fluid
|
||||
fluid_modifier = self.get_selected_time_scale_object_fluid_modifier()
|
||||
if fluid_object is not None and fluid_modifier is not None:
|
||||
property_group = fluid_modifier.domain_settings
|
||||
property_name = "time_scale"
|
||||
|
||||
return getattr(property_group, property_name)
|
||||
|
||||
|
||||
def is_current_grid_upscaled(self):
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if not dprops.bake.is_autosave_available:
|
||||
return False
|
||||
if self.savestate_isize <= 0 or self.savestate_jsize <= 0 or self.savestate_ksize <= 0 or self.savestate_dx <= 0.0:
|
||||
return False
|
||||
if self.current_isize <= 0 or self.current_jsize <= 0 or self.current_ksize <= 0 or self.current_dx <= 0.0:
|
||||
return False
|
||||
if self.current_dx > self.savestate_dx:
|
||||
# downscaled
|
||||
return False
|
||||
|
||||
res_changed = (self.current_isize != self.savestate_isize or
|
||||
self.current_jsize != self.savestate_jsize or
|
||||
self.current_ksize != self.savestate_ksize)
|
||||
|
||||
dx_eps = self.upscale_trigger_factor * self.savestate_dx
|
||||
dx_changed = abs(self.current_dx - self.savestate_dx) > dx_eps
|
||||
return res_changed and dx_changed
|
||||
|
||||
|
||||
def _update_resolution(self, context):
|
||||
self._set_recommended_preview_resolution(context)
|
||||
if self.preview_resolution > self.resolution:
|
||||
self.preview_resolution = self.resolution
|
||||
self._update_current_settings_grid_dimensions()
|
||||
|
||||
|
||||
def _update_preview_resolution(self, context):
|
||||
self._update_resolution(context)
|
||||
|
||||
|
||||
def _update_auto_preview_resolution(self, context):
|
||||
self._set_recommended_preview_resolution(context)
|
||||
|
||||
|
||||
def _set_recommended_preview_resolution(self, context=None):
|
||||
if not self.auto_preview_resolution:
|
||||
return
|
||||
if context is None:
|
||||
context = bpy.context
|
||||
dprops = context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops.surface.subdivisions > 1:
|
||||
preview = self.resolution
|
||||
else:
|
||||
preview = self.resolution // 2
|
||||
if self.preview_resolution != preview:
|
||||
self.preview_resolution = preview
|
||||
|
||||
|
||||
def _update_lock_cell_size(self, context):
|
||||
if self.lock_cell_size:
|
||||
domain_object = context.scene.flip_fluid.get_domain_object()
|
||||
bbox = AABB.from_blender_object(domain_object)
|
||||
max_dim = max(bbox.xdim, bbox.ydim, bbox.zdim)
|
||||
self.locked_cell_size = max(max_dim / self.resolution, 1e-6)
|
||||
else:
|
||||
self.locked_cell_size = -1.0
|
||||
|
||||
|
||||
def _update_locked_cell_size_resolution(self):
|
||||
domain_object = bpy.context.scene.flip_fluid.get_domain_object()
|
||||
if domain_object is None:
|
||||
return
|
||||
if not self.lock_cell_size:
|
||||
return
|
||||
bbox = AABB.from_blender_object(domain_object)
|
||||
max_dim = max(bbox.xdim, bbox.ydim, bbox.zdim)
|
||||
ratio = max_dim / self.locked_cell_size
|
||||
if abs(ratio - math.floor(ratio + 0.5)) < 1e-6:
|
||||
ratio = math.floor(ratio + 0.5)
|
||||
resolution = math.ceil(ratio)
|
||||
if self.resolution != resolution:
|
||||
self.resolution = resolution
|
||||
|
||||
|
||||
def _get_savestate_enums(self, context=None):
|
||||
if context is None:
|
||||
context = bpy.context
|
||||
dprops = context.scene.flip_fluid.get_domain_properties()
|
||||
return dprops.bake.get_savestate_enums()
|
||||
|
||||
|
||||
def _update_selected_savestate(self, context):
|
||||
self["selected_savestate_int"] = int(self.selected_savestate) + 1
|
||||
self.last_selected_savestate_int = self.selected_savestate_int
|
||||
|
||||
self.selected_savestate_int_label = ""
|
||||
enums = self._get_savestate_enums()
|
||||
for e in enums:
|
||||
if e[0] == self.selected_savestate:
|
||||
label = e[1]
|
||||
if not label:
|
||||
break
|
||||
idx1 = label.find("(")
|
||||
idx2 = label.find(")")
|
||||
if idx1 == -1 or idx2 == -1:
|
||||
break
|
||||
self.selected_savestate_int_label = label[idx1:idx2+1]
|
||||
|
||||
self._update_selected_savestate_grid_dimensions(context)
|
||||
|
||||
|
||||
def _update_selected_savestate_int(self, context):
|
||||
last_id = self.last_selected_savestate_int - 1
|
||||
next_id = self.selected_savestate_int - 1
|
||||
|
||||
enums = self._get_savestate_enums()
|
||||
ids = [int(e[0]) for e in enums]
|
||||
ids.sort()
|
||||
|
||||
if next_id in ids:
|
||||
next_valid_id = next_id
|
||||
else:
|
||||
if next_id == last_id + 1:
|
||||
next_valid_id = ids[min(ids.index(last_id) + 1, len(ids) - 1)]
|
||||
elif next_id == last_id - 1:
|
||||
next_valid_id = ids[max(ids.index(last_id) - 1, 0)]
|
||||
else:
|
||||
nearest_id = min(ids, key=lambda x:abs(x-next_id))
|
||||
next_valid_id = nearest_id
|
||||
|
||||
self.selected_savestate = str(next_valid_id)
|
||||
self["selected_savestate_int"] = next_valid_id + 1
|
||||
|
||||
self.last_selected_savestate_int = self.selected_savestate_int
|
||||
|
||||
self._update_selected_savestate_grid_dimensions(context)
|
||||
|
||||
|
||||
def _update_current_settings_grid_dimensions(self):
|
||||
isize, jsize, ksize, dx = self.get_simulation_grid_dimensions()
|
||||
|
||||
eps = 1e-6
|
||||
if self.current_isize != isize:
|
||||
self.current_isize = isize
|
||||
if self.current_jsize != jsize:
|
||||
self.current_jsize = jsize
|
||||
if self.current_ksize != ksize:
|
||||
self.current_ksize = ksize
|
||||
if abs(self.current_dx - dx) > eps:
|
||||
self.current_dx = dx
|
||||
|
||||
|
||||
def _update_selected_savestate_grid_dimensions(self, context):
|
||||
dprops = context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops.bake.is_simulation_running:
|
||||
return
|
||||
|
||||
savestate_id = self.get_selected_savestate_id()
|
||||
|
||||
cache_directory = dprops.cache.get_cache_abspath()
|
||||
savestate_directory = os.path.join(cache_directory, "savestates")
|
||||
savestate_name = "autosave" + str(savestate_id).zfill(6)
|
||||
autosave_directory = os.path.join(savestate_directory, savestate_name)
|
||||
if not os.path.isdir(autosave_directory):
|
||||
autosave_directory = os.path.join(savestate_directory, "autosave")
|
||||
|
||||
if not os.path.isdir(autosave_directory):
|
||||
return
|
||||
|
||||
autosave_info_file = os.path.join(autosave_directory, "autosave.state")
|
||||
if not os.path.isfile(autosave_info_file):
|
||||
return
|
||||
|
||||
with open(autosave_info_file, 'r', encoding='utf-8') as f:
|
||||
autosave_info = json.loads(f.read())
|
||||
|
||||
try:
|
||||
isize = autosave_info["isize"]
|
||||
jsize = autosave_info["jsize"]
|
||||
ksize = autosave_info["ksize"]
|
||||
dx = autosave_info["dx"]
|
||||
except KeyError:
|
||||
# Cache created in older FLIP Fluids version which does not contain
|
||||
# this info. Fill in properties with the current grid dimension settings.
|
||||
isize, jsize, ksize, dx = self.get_simulation_grid_dimensions()
|
||||
|
||||
eps = 1e-6
|
||||
if self.savestate_isize != isize:
|
||||
self.savestate_isize = isize
|
||||
if self.savestate_jsize != jsize:
|
||||
self.savestate_jsize = jsize
|
||||
if self.savestate_ksize != ksize:
|
||||
self.savestate_ksize = ksize
|
||||
if abs(self.savestate_dx - dx) > eps:
|
||||
self.savestate_dx = dx
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DomainSimulationProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(DomainSimulationProperties)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,543 @@
|
||||
# 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, os, math
|
||||
from bpy.props import (
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
BoolProperty,
|
||||
BoolVectorProperty,
|
||||
EnumProperty,
|
||||
PointerProperty
|
||||
)
|
||||
|
||||
from .. import types
|
||||
from ..objects.flip_fluid_aabb import AABB
|
||||
from ..objects import flip_fluid_cache
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
class DomainSurfaceProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
enable_surface_mesh_generation = BoolProperty(
|
||||
name="Enable Surface Mesh Generation",
|
||||
description="Enable the generation of the liquid surface mesh. If disabled, "
|
||||
"the surface mesh and any surface attributes will not be generated or exported"
|
||||
" to the simulation cache",
|
||||
default=True,
|
||||
update=lambda self, context: self._update_enable_surface_mesh_generation(context),
|
||||
); exec(conv("enable_surface_mesh_generation"))
|
||||
subdivisions = IntProperty(
|
||||
name="Subdivisions",
|
||||
description="The level of detail of the generated surface mesh."
|
||||
" This value is the number of times that the simulation grid"
|
||||
" cells are split. A value of 1 is recommended for"
|
||||
" most final simulations. A value of 2 or 3 is recommended"
|
||||
" to reduce flickering in slow motion simulations."
|
||||
" Use a value of 0 to speed up baking during testing",
|
||||
min=0,
|
||||
soft_max=2,
|
||||
default=1,
|
||||
); exec(conv("subdivisions"))
|
||||
compute_chunks_auto = IntProperty(
|
||||
name="Compute Chunks",
|
||||
description="Number of chunks to break up mesh into during"
|
||||
" computation. Increase to reduce memory usage",
|
||||
min=1,
|
||||
default=1,
|
||||
); exec(conv("compute_chunks_auto"))
|
||||
compute_chunks_fixed = IntProperty(
|
||||
name="Compute Chunks",
|
||||
description="Number of chunks to break up surface into during"
|
||||
" mesh generation. Increase to reduce memory usage",
|
||||
min=1,
|
||||
default=1,
|
||||
); exec(conv("compute_chunks_fixed"))
|
||||
compute_chunk_mode = EnumProperty(
|
||||
name="Threading Mode",
|
||||
description="Determine the number of compute chunks to use when"
|
||||
" generating the surface mesh",
|
||||
items=types.surface_compute_chunk_modes,
|
||||
default='COMPUTE_CHUNK_MODE_AUTO',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("compute_chunk_mode"))
|
||||
meshing_volume_mode = EnumProperty(
|
||||
name="Meshing Volume Mode",
|
||||
description="Determing which parts of the fluid will be meshed",
|
||||
items=types.meshing_volume_modes,
|
||||
default='MESHING_VOLUME_MODE_DOMAIN',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("meshing_volume_mode"))
|
||||
meshing_volume_object = PointerProperty(
|
||||
name="Meshing Object",
|
||||
description="Only fluid that is inside of this object will be meshed",
|
||||
type=bpy.types.Object,
|
||||
update=lambda self, context: self._update_meshing_volume_object(context),
|
||||
poll=lambda self, obj: self._poll_meshing_volume_object(obj),
|
||||
); exec(conv("meshing_volume_object"))
|
||||
export_animated_meshing_volume_object = BoolProperty(
|
||||
name="Export Animated Mesh",
|
||||
description="Export this mesh as an animated one (slower, only use"
|
||||
" if really necessary [e.g. armatures or parented objects],"
|
||||
" animated pos/rot/scale F-curves do not require it",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("export_animated_meshing_volume_object"))
|
||||
enable_meshing_offset = BoolProperty(
|
||||
name="Enable",
|
||||
description="Enable smooth meshing against obstacles. If disabled,"
|
||||
" obstacles will not be considered during meshing and all fluid"
|
||||
" particles will be converted to a mesh",
|
||||
default=True,
|
||||
); exec(conv("enable_meshing_offset"))
|
||||
obstacle_meshing_mode = EnumProperty(
|
||||
name="Obstacle Meshing Mode",
|
||||
description="How the fluid surface will be meshed against obstacles",
|
||||
items=types.obstacle_meshing_modes,
|
||||
default='MESHING_MODE_INSIDE_SURFACE',
|
||||
); exec(conv("obstacle_meshing_mode"))
|
||||
remove_mesh_near_domain = BoolProperty(
|
||||
name="Remove Mesh Near Boundary",
|
||||
description="Remove parts of the surface mesh that are near the"
|
||||
" domain boundary. If a meshing volume object is set, parts"
|
||||
" of the mesh that are near the volume object boundary will"
|
||||
" also be removed",
|
||||
default=False,
|
||||
); exec(conv("remove_mesh_near_domain"))
|
||||
remove_mesh_near_domain_distance = IntProperty(
|
||||
name="Distance",
|
||||
description="Distance from domain boundary to remove mesh parts."
|
||||
" This value is in number of voxels. If a meshing volume"
|
||||
" object is set, this distance will be limited to 1 voxel",
|
||||
min=1,
|
||||
default=1,
|
||||
); exec(conv("remove_mesh_near_domain_distance"))
|
||||
remove_mesh_near_domain_sides = BoolVectorProperty(
|
||||
name="",
|
||||
description="Remove mesh on the corresponding side of the domain."
|
||||
" If disabled, this side of the mesh will not be removed",
|
||||
default=(True, True, True, True, True, True),
|
||||
size=6,
|
||||
); exec(conv("remove_mesh_near_domain_sides"))
|
||||
smoothing_value = FloatProperty(
|
||||
name="Factor",
|
||||
description="Amount of surface smoothing. Tip: use a smooth modifier"
|
||||
" to increase amount of smoothing",
|
||||
min=0.0, max=1.0,
|
||||
default=0.5,
|
||||
precision=3,
|
||||
subtype='FACTOR',
|
||||
); exec(conv("smoothing_value"))
|
||||
smoothing_iterations = IntProperty(
|
||||
name="Repeat",
|
||||
description="Number of smoothing iterations Tip: use a smooth modifier"
|
||||
" to increase amount of iterations",
|
||||
min=0, max=30,
|
||||
default=2,
|
||||
); exec(conv("smoothing_iterations"))
|
||||
particle_scale = FloatProperty(
|
||||
name="Particle Scale",
|
||||
description = "Size of particles for mesh generation. A value less than 1.0"
|
||||
" is not recommended and may result in an incomplete mesh",
|
||||
soft_min=1.0, soft_max=3.0,
|
||||
default=1.0,
|
||||
precision=2,
|
||||
); exec(conv("particle_scale"))
|
||||
invert_contact_normals = BoolProperty(
|
||||
name="Invert Fluid-Obstacle Contact Normals",
|
||||
description="Invert surface mesh normals that contact obstacle"
|
||||
" surfaces. Enable for correct refraction rendering with"
|
||||
" water-glass interfaces. Note: 'Mesh Around Obstacles'"
|
||||
" should be enabled when using this feature",
|
||||
default=False,
|
||||
); exec(conv("invert_contact_normals"))
|
||||
generate_motion_blur_data = BoolProperty(
|
||||
name="Generate Motion Blur Vectors",
|
||||
description="Generate fluid surface speed vectors for motion blur"
|
||||
" rendering. See documentation for limitations",
|
||||
default=False,
|
||||
); exec(conv("generate_motion_blur_data"))
|
||||
enable_velocity_vector_attribute = BoolProperty(
|
||||
name="Generate Velocity Attributes",
|
||||
description="Generate fluid 3D velocity vector attributes for the fluid surface. After"
|
||||
" baking, the velocity vectors (in m/s) can be accessed in a Cycles Attribute"
|
||||
" Node or in Geometry Nodes with the name 'flip_velocity' from the Vector output."
|
||||
" This attribute is required for motion blur rendering. If the velocity"
|
||||
" direction is not needed, use Generate Speed Attributes instead",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_velocity_vector_attribute"))
|
||||
enable_velocity_vector_attribute_against_obstacles = BoolProperty(
|
||||
name="Generate Against Obstacles",
|
||||
description="Generate velocity-based attribute data against obstacles."
|
||||
" Velocity-based attributes are the velocity/speed/vorticity attributes."
|
||||
" If enabled, correct attributes will be generated where liquid and obstacles"
|
||||
" meet, but at the cost of simulation performance. If disabled, attributes where"
|
||||
" liquids and obstacles meet may be incorrect. This option is only needed if"
|
||||
" rendering with velocity-based shaders and/or motion blur where the liquid-obstacle"
|
||||
" interface will be visible such as when there are transparent/invisible obstacles"
|
||||
" in the render",
|
||||
default=True,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_velocity_vector_attribute_against_obstacles"))
|
||||
enable_speed_attribute = BoolProperty(
|
||||
name="Generate Speed Attributes",
|
||||
description="Generate fluid speed attributes for the fluid surface. After"
|
||||
" baking, the speed values (in m/s) can be accessed in a Cycles Attribute"
|
||||
" Node or in Geometry Nodes with the name 'flip_speed' from the Fac output",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_speed_attribute"))
|
||||
enable_vorticity_vector_attribute = BoolProperty(
|
||||
name="Generate Vorticity Attributes",
|
||||
description="Generate fluid 3D vorticity vector attributes for the fluid surface. After"
|
||||
" baking, the vorticity vectors can be accessed in a Cycles Attribute"
|
||||
" Node or in Geometry Nodes with the name 'flip_vorticity' from the Vector output",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_vorticity_vector_attribute"))
|
||||
enable_age_attribute = BoolProperty(
|
||||
name="Generate Age Attributes",
|
||||
description="Generate fluid age attributes for the fluid surface."
|
||||
" The age attribute starts at 0.0 when the liquid is spawned and counts up in"
|
||||
" seconds. After baking, the age values can be accessed in a Cycles Attribute"
|
||||
" Node or in Geometry Nodes with the name 'flip_age' from the Fac output",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_age_attribute"))
|
||||
age_attribute_radius = FloatProperty(
|
||||
name="Smoothing Radius",
|
||||
description = "Amount of smoothing when transferring the age attribute to the surface mesh."
|
||||
" Higher values result in smoother attribute transitions at the cost of simulation"
|
||||
" performance. Value is the search radius in number of voxels for nearby particles",
|
||||
soft_min=1.0, soft_max=4.0,
|
||||
min=0.0,
|
||||
default=3.0,
|
||||
precision=1,
|
||||
); exec(conv("age_attribute_radius"))
|
||||
enable_lifetime_attribute = BoolProperty(
|
||||
name="Generate Lifetime Attributes",
|
||||
description="Generate fluid lifetime attributes for the fluid surface. This attribute allows the"
|
||||
" fluid to start with a lifetime value that counts down in seconds and once the lifetime reaches 0,"
|
||||
" the fluid is removed from the simulation. Each Inflow/Fluid object can be set to assign a"
|
||||
" starting lifetime to the generated fluid. After baking, the lifetime remaining values"
|
||||
" can be accessed in a Cycles Attribute Node or in Geometry Nodes with the name 'flip_lifetime' from"
|
||||
" the Fac output",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_lifetime_attribute"))
|
||||
lifetime_attribute_radius = FloatProperty(
|
||||
name="Smoothing Radius",
|
||||
description = "Amount of smoothing when transferring the lifetime attribute to the surface mesh."
|
||||
" Higher values result in smoother attribute transitions at the cost of simulation"
|
||||
" performance. Value is the search radius in number of voxels for nearby particles",
|
||||
soft_min=1.0, soft_max=4.0,
|
||||
min=0.0,
|
||||
default=3.0,
|
||||
precision=1,
|
||||
); exec(conv("lifetime_attribute_radius"))
|
||||
lifetime_attribute_death_time = FloatProperty(
|
||||
name="Base Death Time",
|
||||
description = "Base time in seconds at which fluid is removed from the simulation. At the default of 0.0,"
|
||||
" fluid will be removed when their lifetime attribute counts down to 0.0. Increase or decrease this"
|
||||
" value to offset the base time of death. Increasing will result in fluid dying earlier."
|
||||
" Decreasing will result in fluid dying later",
|
||||
default=0.0,
|
||||
precision=2,
|
||||
); exec(conv("lifetime_attribute_death_time"))
|
||||
enable_whitewater_proximity_attribute = BoolProperty(
|
||||
name="Whitewater Proximity Attributes",
|
||||
description="Generate whitewater proximity attributes for the fluid surface. The attribute values represent"
|
||||
" how many foam, bubble, or spray particles are near the surface mesh and can be used in a material to shade"
|
||||
" parts of the surface that are near whitewater particles. After baking, the proximity attribute can be accessed"
|
||||
" in a Cycles Attribute Node or in Geometry Nodes with the names 'flip_foam_proximity', 'flip_bubble_proximity',"
|
||||
" and 'flip_spray_proximity' from the Fac output",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_whitewater_proximity_attribute"))
|
||||
whitewater_proximity_attribute_radius = FloatProperty(
|
||||
name="Smoothing Radius",
|
||||
description = "Amount of smoothing when transferring the whitewater proximity attribute to the surface mesh."
|
||||
" Higher values result in smoother attribute transitions at the cost of simulation"
|
||||
" performance. Value is the search radius in number of voxels for nearby particles",
|
||||
soft_min=1.0, soft_max=4.0,
|
||||
min=0.0,
|
||||
default=2.0,
|
||||
precision=1,
|
||||
); exec(conv("whitewater_proximity_attribute_radius"))
|
||||
enable_color_attribute = BoolProperty(
|
||||
name="Generate Color Attributes",
|
||||
description="Generate fluid color attributes for the fluid surface. Each"
|
||||
" Inflow/Fluid object can set to assign color to the generated fluid. After"
|
||||
" baking, the color values can be accessed in a Cycles Attribute Node or in Geometry Nodes"
|
||||
" with the name 'flip_color' from the Color output. This can be used to create varying color"
|
||||
" liquid effects",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_color_attribute"))
|
||||
color_attribute_radius = FloatProperty(
|
||||
name="Smoothing Radius",
|
||||
description = "Amount of smoothing when transferring the color attribute to the surface mesh."
|
||||
" Higher values result in smoother attribute transitions at the cost of simulation"
|
||||
" performance. Value is the search radius in number of voxels for nearby particles",
|
||||
soft_min=1.0, soft_max=4.0,
|
||||
min=0.0,
|
||||
default=3.0,
|
||||
precision=1,
|
||||
); exec(conv("color_attribute_radius"))
|
||||
enable_color_attribute_mixing = BoolProperty(
|
||||
name="Enable Mixing",
|
||||
description="Simulate basic color mixing. If enabled, particles will absorb color attributes"
|
||||
" from nearby particles. If disabled, particles will hold a static color value",
|
||||
default=False,
|
||||
); exec(conv("enable_color_attribute_mixing"))
|
||||
color_attribute_mixing_rate = FloatProperty(
|
||||
name="Mixing Rate",
|
||||
description = "Controls how quickly particles will absorb color from nearby particles. Higher"
|
||||
" values will cause colors to mix and spread more quickly. Lower values will cause colors to"
|
||||
" mix and spread more slowly",
|
||||
soft_max=25.0,
|
||||
min=0.0,
|
||||
default=12,
|
||||
precision=2,
|
||||
); exec(conv("color_attribute_mixing_rate"))
|
||||
color_attribute_mixing_radius = FloatProperty(
|
||||
name="Mixing Radius",
|
||||
description = "Radius in which a particle can absorb color from nearby particles. Increasing"
|
||||
" this value can result in smoother mixing transitions at the cost of simulation performance."
|
||||
" This value is the search radius in number of voxels",
|
||||
soft_max=3.0,
|
||||
min=0.0,
|
||||
default=1.0,
|
||||
precision=2,
|
||||
); exec(conv("color_attribute_mixing_radius"))
|
||||
color_attribute_mixing_mode = EnumProperty(
|
||||
name="Mixing Mode",
|
||||
description="Method of simulating color attribute mixing",
|
||||
items=types.color_mixing_modes,
|
||||
default='COLOR_MIXING_MODE_MIXBOX',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("color_attribute_mixing_mode"))
|
||||
enable_source_id_attribute = BoolProperty(
|
||||
name="Generate Source ID Attributes",
|
||||
description="Generate fluid source identifiers for the fluid surface. Each"
|
||||
" Inflow/Fluid object can set to assign a source ID to the generated fluid. After"
|
||||
" baking, the ID values can be accessed in a Cycles Attribute Node or in Geometry Nodes with the name"
|
||||
" 'flip_source_id' from the Fac output. This can be used to identifty fluid from"
|
||||
" different sources in a material or geometry node group. Warning: this attribute is"
|
||||
" not supported with sheeting effects or resolution upscaling features",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_source_id_attribute"))
|
||||
enable_viscosity_attribute = BoolProperty(
|
||||
name="Enable Variable Viscosity",
|
||||
description="Enable the variable viscosity solver for mixed viscosity simulations."
|
||||
" After enabling, each Fluid/Inflow object can be set to assign a viscosity value"
|
||||
" to the generated fluid. When enabled, viscosity value attributes will also"
|
||||
" be generated for the fluid surface. After baking, the viscosity values can"
|
||||
" be accessed in a Cycles Attribute Node with the name 'flip_viscosity' from"
|
||||
" the Fac output",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_viscosity_attribute"))
|
||||
|
||||
native_particle_scale = FloatProperty(default=3.0); exec(conv("native_particle_scale"))
|
||||
default_cells_per_compute_chunk = FloatProperty(default=15.0); exec(conv("default_cells_per_compute_chunk")) # in millions
|
||||
|
||||
surface_mesh_expanded = BoolProperty(default=True); exec(conv("surface_mesh_expanded"))
|
||||
meshing_volume_expanded = BoolProperty(default=False); exec(conv("meshing_volume_expanded"))
|
||||
meshing_against_boundary_expanded = BoolProperty(default=False); exec(conv("meshing_against_boundary_expanded"))
|
||||
meshing_against_obstacles_expanded = BoolProperty(default=False); exec(conv("meshing_against_obstacles_expanded"))
|
||||
surface_display_settings_expanded = BoolProperty(default=False); exec(conv("surface_display_settings_expanded"))
|
||||
geometry_attributes_expanded = BoolProperty(default=False); exec(conv("geometry_attributes_expanded"))
|
||||
velocity_attributes_expanded = BoolProperty(default=False); exec(conv("velocity_attributes_expanded"))
|
||||
color_attributes_expanded = BoolProperty(default=False); exec(conv("color_attributes_expanded"))
|
||||
other_attributes_expanded = BoolProperty(default=False); exec(conv("other_attributes_expanded"))
|
||||
|
||||
show_smoothing_radius_in_ui = BoolProperty(default=False); exec(conv("show_smoothing_radius_in_ui"))
|
||||
is_smoothing_radius_updated_to_default = BoolProperty(default=False); exec(conv("is_smoothing_radius_updated_to_default"))
|
||||
|
||||
preview_mode_attributes_tooltip = BoolProperty(
|
||||
name="Preview Mode Attributes Tooltip",
|
||||
description="The fluid surface mesh is currently set to Preview Mode within the viewport and attributes"
|
||||
" will not be loaded. Attributes will not be displayed correctly in viewport render mode."
|
||||
" Surface attributes will only be loaded in Final Mode. The surface mesh display"
|
||||
" mode can be set in the 'Domain > Display Settings' panel",
|
||||
default=True,
|
||||
); exec(conv("preview_mode_attributes_tooltip"))
|
||||
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
add = registry.add_property
|
||||
add(path + ".enable_surface_mesh_generation", "Enable Surface Mesh", group_id=0)
|
||||
add(path + ".subdivisions", "Subdivisions", group_id=0)
|
||||
add(path + ".particle_scale", "Particle Scale", group_id=0)
|
||||
add(path + ".compute_chunk_mode", "Compute Chunk Mode", group_id=0)
|
||||
add(path + ".compute_chunks_fixed", "Num Compute Chunks (fixed)", group_id=0)
|
||||
add(path + ".meshing_volume_mode", "Meshing Volume Mode", group_id=0)
|
||||
add(path + ".export_animated_meshing_volume_object", "Export Animated Mesh", group_id=0)
|
||||
add(path + ".enable_meshing_offset", "Enable Obstacle Meshing", group_id=0)
|
||||
add(path + ".obstacle_meshing_mode", "Obstacle Meshing Mode", group_id=0)
|
||||
add(path + ".remove_mesh_near_domain", "Remove Mesh Near Domain", group_id=0)
|
||||
add(path + ".remove_mesh_near_domain_distance", "Distance", group_id=0)
|
||||
add(path + ".remove_mesh_near_domain_sides", "Remove Mesh Sides", group_id=0)
|
||||
add(path + ".smoothing_value", "Smoothing Value", group_id=0)
|
||||
add(path + ".smoothing_iterations", "Smoothing Iterations", group_id=0)
|
||||
add(path + ".invert_contact_normals", "Invert Contact Normals", group_id=0)
|
||||
add(path + ".generate_motion_blur_data", "Generate Motion Blur Data", group_id=0)
|
||||
add(path + ".enable_velocity_vector_attribute", "Generate Velocity Attributes", group_id=0)
|
||||
add(path + ".enable_velocity_vector_attribute_against_obstacles", "Generate Velocity Attributes Against Obstacles", group_id=0)
|
||||
add(path + ".enable_speed_attribute", "Generate Speed Attributes", group_id=0)
|
||||
add(path + ".enable_vorticity_vector_attribute", "Generate Vorticity Attributes", group_id=0)
|
||||
add(path + ".enable_age_attribute", "Generate Age Attributes", group_id=0)
|
||||
add(path + ".age_attribute_radius", "Age Attribute Smoothing", group_id=0)
|
||||
add(path + ".enable_lifetime_attribute", "Generate Lifetime Attributes", group_id=0)
|
||||
add(path + ".lifetime_attribute_radius", "Lifetime Attribute Smoothing", group_id=0)
|
||||
add(path + ".lifetime_attribute_death_time", "Death Time", group_id=0)
|
||||
add(path + ".enable_whitewater_proximity_attribute", "Whitewater Proximity", group_id=0)
|
||||
add(path + ".whitewater_proximity_attribute_radius", "Whitewater Proximity Attribute Smoothing", group_id=0)
|
||||
add(path + ".enable_color_attribute", "Generate Color Attributes", group_id=0)
|
||||
add(path + ".color_attribute_radius", "Color Attribute Smoothing", group_id=0)
|
||||
add(path + ".enable_color_attribute_mixing", "Enable Color Attribute Mixing", group_id=0)
|
||||
add(path + ".color_attribute_mixing_rate", "Color Attribute Mixing Rate", group_id=0)
|
||||
add(path + ".color_attribute_mixing_radius", "Color Attribute Mixing Radius", group_id=0)
|
||||
add(path + ".color_attribute_mixing_mode", "Color Attribute Mixing Mode", group_id=0)
|
||||
add(path + ".enable_source_id_attribute", "Generate Source ID Attributes", group_id=0)
|
||||
add(path + ".enable_viscosity_attribute", "Generate Viscosity Attributes", group_id=0)
|
||||
|
||||
|
||||
def scene_update_post(self, scene):
|
||||
self._update_auto_compute_chunks()
|
||||
|
||||
|
||||
def load_post(self):
|
||||
# In earlier addons versions, the attribute smoothing radius could be set by the user.
|
||||
# Now that Blender >= 3.5 has a blur attribute node, this should be used instead for
|
||||
# further smoothing.
|
||||
#
|
||||
# Update Blend files upon the first load by setting the attribute smoothing radii back to
|
||||
# the default value of 3.0
|
||||
if self.is_smoothing_radius_updated_to_default:
|
||||
return
|
||||
default_smoothing_radius = 3.0
|
||||
self.color_attribute_radius = default_smoothing_radius
|
||||
self.age_attribute_radius = default_smoothing_radius
|
||||
self.lifetime_attribute_radius = default_smoothing_radius
|
||||
self.is_smoothing_radius_updated_to_default = True
|
||||
|
||||
|
||||
def get_meshing_volume_object(self):
|
||||
obj = None
|
||||
try:
|
||||
all_objects = vcu.get_all_scene_objects()
|
||||
obj = self.meshing_volume_object
|
||||
obj = all_objects.get(obj.name)
|
||||
except:
|
||||
pass
|
||||
return obj
|
||||
|
||||
|
||||
def is_meshing_volume_object_valid(self):
|
||||
return (self.meshing_volume_mode == 'MESHING_VOLUME_MODE_OBJECT' and
|
||||
self.get_meshing_volume_object() is not None)
|
||||
|
||||
|
||||
def _update_enable_surface_mesh_generation(self, context):
|
||||
dprops = context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
|
||||
if self.enable_surface_mesh_generation:
|
||||
objects_to_initialize = flip_fluid_cache.EnabledMeshCacheObjects()
|
||||
objects_to_initialize.fluid_surface = True
|
||||
|
||||
dprops.mesh_cache.initialize_cache_objects(objects_to_initialize)
|
||||
dprops.materials.surface_material = dprops.materials.surface_material
|
||||
else:
|
||||
dprops.mesh_cache.surface.reset_cache_object()
|
||||
|
||||
|
||||
def _update_auto_compute_chunks(self):
|
||||
domain_object = bpy.context.scene.flip_fluid.get_domain_object()
|
||||
if domain_object is None:
|
||||
return
|
||||
bbox = AABB.from_blender_object(domain_object)
|
||||
max_dim = max(bbox.xdim, bbox.ydim, bbox.zdim)
|
||||
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops.simulation.lock_cell_size:
|
||||
unlocked_dx = max_dim / dprops.simulation.resolution
|
||||
locked_dx = dprops.simulation.locked_cell_size
|
||||
dx = locked_dx
|
||||
if abs(locked_dx - unlocked_dx) < 1e-6:
|
||||
dx = unlocked_dx
|
||||
else:
|
||||
dx = max_dim / dprops.simulation.resolution
|
||||
|
||||
subdivisions = self.subdivisions + 1
|
||||
max_chunks = subdivisions * subdivisions * subdivisions
|
||||
isize = math.ceil(bbox.xdim / dx) * subdivisions
|
||||
jsize = math.ceil(bbox.ydim / dx) * subdivisions
|
||||
ksize = math.ceil(bbox.zdim / dx) * subdivisions
|
||||
total_cells = isize * jsize * ksize
|
||||
cells_per_chunk = self.default_cells_per_compute_chunk * 1e6
|
||||
num_chunks = math.ceil(total_cells / cells_per_chunk)
|
||||
num_chunks = max(min(num_chunks, min(isize, jsize, ksize), max_chunks), 1)
|
||||
if self.compute_chunks_auto != num_chunks:
|
||||
self.compute_chunks_auto = num_chunks
|
||||
|
||||
|
||||
def _update_meshing_volume_object(self, context):
|
||||
obj = self.get_meshing_volume_object()
|
||||
if obj is None:
|
||||
return
|
||||
|
||||
obj.hide_render = True
|
||||
vcu.set_object_display_type(obj, 'WIRE')
|
||||
obj.show_name = True
|
||||
|
||||
try:
|
||||
# Cycles may not be enabled in the user's preferences
|
||||
if vcu.is_blender_30():
|
||||
obj.visible_camera = is_enabled
|
||||
obj.visible_diffuse = is_enabled
|
||||
obj.visible_glossy = is_enabled
|
||||
obj.visible_transmission = is_enabled
|
||||
obj.visible_volume_scatter = is_enabled
|
||||
obj.visible_shadow = is_enabled
|
||||
else:
|
||||
obj.cycles_visibility.camera = is_enabled
|
||||
obj.cycles_visibility.transmission = is_enabled
|
||||
obj.cycles_visibility.diffuse = is_enabled
|
||||
obj.cycles_visibility.scatter = is_enabled
|
||||
obj.cycles_visibility.glossy = is_enabled
|
||||
obj.cycles_visibility.shadow = is_enabled
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def _poll_meshing_volume_object(self, obj):
|
||||
if obj.type == 'MESH':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DomainSurfaceProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(DomainSurfaceProperties)
|
||||
@@ -0,0 +1,600 @@
|
||||
# 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, os
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
BoolVectorProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty
|
||||
)
|
||||
|
||||
from .custom_properties import (
|
||||
NewMinMaxIntProperty,
|
||||
NewMinMaxFloatProperty
|
||||
)
|
||||
from .. import types
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
from ..objects import flip_fluid_cache
|
||||
|
||||
|
||||
class DomainWhitewaterProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
whitewater_ui_mode = EnumProperty(
|
||||
name="Whitewater UI Mode",
|
||||
description="Whitewater UI mode",
|
||||
items=types.whitewater_ui_modes,
|
||||
default='WHITEWATER_UI_MODE_BASIC',
|
||||
); exec(conv("whitewater_ui_mode"))
|
||||
highlight_advanced_settings = BoolProperty(
|
||||
name="Highlight Advanced Settings",
|
||||
description="Highlight advanced parameters in red",
|
||||
default=True,
|
||||
); exec(conv("highlight_advanced_settings"))
|
||||
enable_whitewater_simulation = BoolProperty(
|
||||
name="Enable Whitewater Simulation",
|
||||
description="Enable whitewater foam/bubble/spray particle solver",
|
||||
default=False,
|
||||
update=lambda self, context: self._update_enable_whitewater_simulation(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_whitewater_simulation"))
|
||||
enable_foam = BoolProperty(
|
||||
name="Foam",
|
||||
description="Enable solving for foam particles. Foam particles form"
|
||||
" a layer on the fluid surface and are advected with the fluid"
|
||||
" velocity. If disabled, any particles that enter the foam layer"
|
||||
" will be destroyed",
|
||||
default=True,
|
||||
); exec(conv("enable_foam"))
|
||||
enable_bubbles = BoolProperty(
|
||||
name = "Bubbles",
|
||||
description="Enable solving for bubble particles. Bubble particles"
|
||||
" below the foam layer are advected with the fluid velocity and"
|
||||
" float towards the foam layer. If disabled, any particles that"
|
||||
" move below the foam layer will be destroyed. WARNING: Bubble"
|
||||
" particles are a large contributor to the foam layer and"
|
||||
" disabling may severely limit the amount of generated foam",
|
||||
default=True,
|
||||
); exec(conv("enable_bubbles"))
|
||||
enable_spray = BoolProperty(
|
||||
name="Spray",
|
||||
description="Enable solving for spray particles. Spray particles"
|
||||
" above the foam layer are simulated ballistically with"
|
||||
" gravity. If disabled, any particles that move above the foam"
|
||||
" layer will be destroyed",
|
||||
default=True,
|
||||
); exec(conv("enable_spray"))
|
||||
enable_dust = BoolProperty(
|
||||
name="Dust",
|
||||
description="Enable solving for dust particles. Dust particles are"
|
||||
" generated near obstacle surfaces and are advected with the"
|
||||
" fluid velocity while sinking towards the ground. If disabled,"
|
||||
" these particles will not be generated",
|
||||
default=False,
|
||||
); exec(conv("enable_dust"))
|
||||
generate_whitewater_motion_blur_data = BoolProperty(
|
||||
name="Generate Motion Blur Vectors",
|
||||
description="Generate whitewater speed vectors for motion blur"
|
||||
" rendering",
|
||||
default=False,
|
||||
); exec(conv("generate_whitewater_motion_blur_data"))
|
||||
enable_velocity_vector_attribute = BoolProperty(
|
||||
name="Generate Velocity Attributes",
|
||||
description="Generate fluid 3D velocity vector attributes for whitewater particles. After"
|
||||
" baking, the velocity vectors (in m/s) can be accessed in a Cycles Attribute"
|
||||
" Node or in Geometry Nodes with the name 'flip_velocity' from the Vector output. Not supported on"
|
||||
" instanced particles, only supported on pointclouds",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_velocity_vector_attribute"))
|
||||
enable_id_attribute = BoolProperty(
|
||||
name="Generate ID Attributes",
|
||||
description="Generate stable ID attributes for whitewater particles. After"
|
||||
" baking, the ID values can be accessed in a Cycles Attribute"
|
||||
" Node or in Geometry Nodes with the name 'flip_id'. Use where consistent"
|
||||
" particle attributes are needed between frames, such as for varying particle"
|
||||
" size or color. Not supported on instanced particles, only supported on pointclouds",
|
||||
default=True,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_id_attribute"))
|
||||
enable_lifetime_attribute = BoolProperty(
|
||||
name="Generate Lifetime Attributes",
|
||||
description="Generate remaining lifetime attributes for whitewater particles. After"
|
||||
" baking, the lifetime values can be accessed in a Cycles Attribute Node or in"
|
||||
" Geometry Nodes with the name 'flip_lifetime'. When the lifetime of a particle"
|
||||
" reaches 0, the particle will despawn. Not supported on instanced particles,"
|
||||
" only supported on pointclouds",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("enable_lifetime_attribute"))
|
||||
enable_whitewater_emission = bpy.props.BoolProperty(
|
||||
name="Enable Whitewater Emission",
|
||||
description="Allow whitewater emitters to generate new particles",
|
||||
default=True,
|
||||
); exec(conv("enable_whitewater_emission"))
|
||||
whitewater_emitter_generation_rate = IntProperty(
|
||||
name="Emitter Generation Rate (Percent)",
|
||||
description="Controls how many whitewater emitters are generated."
|
||||
" Emitters are generated at wavecrests and in areas high"
|
||||
" turbulence where fluid is likely to be aerated",
|
||||
min=0, max=100,
|
||||
default=100,
|
||||
); exec(conv("whitewater_emitter_generation_rate"))
|
||||
wavecrest_emission_rate = FloatProperty(
|
||||
name="Max Wavecrest Emission Rate",
|
||||
description="Maximum number of whitewater particles that a"
|
||||
" single wavecrest emitter may generate per simulation second",
|
||||
min=0, soft_max=1000,
|
||||
default=175,
|
||||
step=30,
|
||||
precision=0,
|
||||
); exec(conv("wavecrest_emission_rate"))
|
||||
turbulence_emission_rate = FloatProperty(
|
||||
name="Max Turbulence Emission Rate",
|
||||
description="Maximum number of whitewater particles that a"
|
||||
" single turbulence emitter may generate per simulation second",
|
||||
min=0, soft_max=1000,
|
||||
default=175,
|
||||
step=30,
|
||||
precision=0,
|
||||
); exec(conv("turbulence_emission_rate"))
|
||||
dust_emission_rate = FloatProperty(
|
||||
name="Max Dust Emission Rate",
|
||||
description="Maximum number of dust particles that a"
|
||||
" single dust emitter may generate per simulation second",
|
||||
min=0, soft_max=1000,
|
||||
default=175,
|
||||
step=30,
|
||||
precision=0,
|
||||
); exec(conv("dust_emission_rate"))
|
||||
spray_emission_speed = FloatProperty(
|
||||
name="Spray Emission Speed",
|
||||
description="Speed scaling factor for spray particle emission. Increasing"
|
||||
" this value will generate more spread out and exaggerated spray effects",
|
||||
min=0.0, soft_min=1.0,
|
||||
soft_max=3.0,
|
||||
default=1.2,
|
||||
); exec(conv("spray_emission_speed"))
|
||||
min_max_whitewater_energy_speed = NewMinMaxFloatProperty(
|
||||
name_min="Min Energy Speed",
|
||||
description_min="Fluid with speed less than this value will generate"
|
||||
" no whitewater",
|
||||
min_min=0,
|
||||
default_min=0.2,
|
||||
precision_min=2,
|
||||
|
||||
name_max="Max Energy Speed",
|
||||
description_max="When fluid speed is greater than the min value, and"
|
||||
" less than the max value, proportionally increase the amount"
|
||||
" of whitewater emitted based on emission rate of the emitter",
|
||||
min_max=0,
|
||||
default_max=3.0,
|
||||
precision_max=2,
|
||||
); exec(conv("min_max_whitewater_energy_speed"))
|
||||
min_max_whitewater_wavecrest_curvature = NewMinMaxFloatProperty(
|
||||
name_min="Min Curvature",
|
||||
description_min="Wavecrests with curvature less than this value will"
|
||||
" generate no whitewater. This value rarely needs to be changed",
|
||||
min_min=0.0, max_min=5.0,
|
||||
default_min=0.4,
|
||||
precision_min=2,
|
||||
|
||||
name_max="Max Curvature",
|
||||
description_max="When wavecrest curvature is greater than the min value,"
|
||||
" and less than the max value, proportionally increase the amount"
|
||||
" of whitewater emitted based on the Wavecrest Emission Rate."
|
||||
" This value rarely needs to be changed",
|
||||
min_max=0.0, max_max=5.0,
|
||||
default_max=1.0,
|
||||
precision_max=2,
|
||||
); exec(conv("min_max_whitewater_wavecrest_curvature"))
|
||||
min_max_whitewater_turbulence = NewMinMaxFloatProperty(
|
||||
name_min="Min Turbulence",
|
||||
description_min="Fluid with turbulence less than this value will"
|
||||
" generate no whitewater. This value rarely needs to be changed",
|
||||
min_min=0,
|
||||
default_min=100,
|
||||
precision_min=0,
|
||||
|
||||
name_max="Max Turbulence",
|
||||
description_max="When the fluid turbulence is greater than the min value,"
|
||||
" and less than the max value, proportionally increase the amount"
|
||||
" of whitewater emitted based on the Turbulence Emission Rate."
|
||||
" This value rarely needs to be changed",
|
||||
min_max=0,
|
||||
default_max=200,
|
||||
precision_max=0,
|
||||
); exec(conv("min_max_whitewater_turbulence"))
|
||||
max_whitewater_particles = FloatProperty(
|
||||
name="Max Particles (in millions)",
|
||||
description="Maximum number of whitewater particles (in millions)"
|
||||
" to simulate. The solver will stop generating new whitewater"
|
||||
" particles to prevent exceeding this limit. If set to 0, the"
|
||||
" solver will not limit the number of whitewater particles,"
|
||||
" however this may require large amounts of storage space depending"
|
||||
" on the simulation and is not recommended",
|
||||
min=0, max=357,
|
||||
default=12,
|
||||
precision=2,
|
||||
); exec(conv("max_whitewater_particles"))
|
||||
enable_whitewater_emission_near_boundary = BoolProperty(
|
||||
name="Enable Emission Near Domain Boundary",
|
||||
description="Allow whitewater emitters to generate particles at"
|
||||
" the domain boundary",
|
||||
default=False,
|
||||
); exec(conv("enable_whitewater_emission_near_boundary"))
|
||||
enable_dust_emission_near_boundary = BoolProperty(
|
||||
name="Enable Dust Emission Near Domain Boundary",
|
||||
description="Allow whitewater emitters to generate dust particles near"
|
||||
" the domain floor",
|
||||
default=False,
|
||||
); exec(conv("enable_dust_emission_near_boundary"))
|
||||
min_max_whitewater_lifespan = NewMinMaxFloatProperty(
|
||||
name_min="Min Lifespan",
|
||||
description_min="Minimum whitewater particle lifespan in seconds",
|
||||
min_min=0.0,
|
||||
default_min=0.5,
|
||||
precision_min=2,
|
||||
|
||||
name_max="Max Lifespan",
|
||||
description_max="Maximum whitewater particle lifespan in seconds",
|
||||
min_max=0.0,
|
||||
default_max=6.0,
|
||||
precision_max=2,
|
||||
); exec(conv("min_max_whitewater_lifespan"))
|
||||
whitewater_lifespan_variance = FloatProperty(
|
||||
name="Lifespan Variance",
|
||||
description ="A random number of seconds in this range will be added"
|
||||
" or subtracted from the whitewater particle lifespan",
|
||||
min=0.0,
|
||||
default=3.0,
|
||||
precision=2,
|
||||
); exec(conv("whitewater_lifespan_variance"))
|
||||
foam_lifespan_modifier = FloatProperty(
|
||||
name="Foam Lifespan Modifier",
|
||||
description="Multiply the lifespan of a foam particle by this value",
|
||||
min=0.0,
|
||||
default=1.0,
|
||||
precision=1,
|
||||
); exec(conv("foam_lifespan_modifier"))
|
||||
bubble_lifespan_modifier = FloatProperty(
|
||||
name="Bubble Lifespan Modifier",
|
||||
description="Multiply the lifespan of a bubble particle by this value",
|
||||
min=0.0,
|
||||
default=4.0,
|
||||
precision=1,
|
||||
); exec(conv("bubble_lifespan_modifier"))
|
||||
spray_lifespan_modifier = FloatProperty(
|
||||
name="Spray Lifespan Modifier",
|
||||
description="Multiply the lifespan of a spray particle by this value",
|
||||
min=0.0,
|
||||
default=5.0,
|
||||
precision=1,
|
||||
); exec(conv("spray_lifespan_modifier"))
|
||||
dust_lifespan_modifier = FloatProperty(
|
||||
name="Dust Lifespan Modifier",
|
||||
description="Multiply the lifespan of a dust particle by this value",
|
||||
min=0.0,
|
||||
default=2.0,
|
||||
precision=1,
|
||||
); exec(conv("dust_lifespan_modifier"))
|
||||
foam_advection_strength = FloatProperty(
|
||||
name="Foam Advection Strength",
|
||||
description="Controls how much the foam moves along with the motion"
|
||||
" of the fluid surface. High values cause tighter streaks of"
|
||||
" foam that closely follow the fluid motion. Lower values will"
|
||||
" cause more diffuse and spread out foam",
|
||||
min=0.0, max=1.0,
|
||||
default=1.0,
|
||||
precision=2,
|
||||
subtype='FACTOR',
|
||||
); exec(conv("foam_advection_strength"))
|
||||
foam_layer_depth = FloatProperty(
|
||||
name="Foam Layer Depth",
|
||||
description="Set the thickness of the whitewater foam layer",
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
default=0.4,
|
||||
precision=2,
|
||||
subtype='FACTOR',
|
||||
); exec(conv("foam_layer_depth"))
|
||||
foam_layer_offset = FloatProperty(
|
||||
name="Foam Layer Offset",
|
||||
description="Set the offset of the whitewater foam layer above/below"
|
||||
" the fluid surface. If set to a value of 1, the foam layer will"
|
||||
" rest entirely above the fluid surface. A value of -1 will have"
|
||||
" the foam layer rest entirely below the fluid surface",
|
||||
min=-1.0,
|
||||
max=1.0,
|
||||
default=0.25,
|
||||
precision=2,
|
||||
subtype='FACTOR',
|
||||
); exec(conv("foam_layer_offset"))
|
||||
preserve_foam = BoolProperty(
|
||||
name="Preserve Foam",
|
||||
description="Increase the lifespan of foam particles based on the"
|
||||
" local density of foam particles, which can help create clumps"
|
||||
" and streaks of foam on the liquid surface over time",
|
||||
default=True,
|
||||
); exec(conv("preserve_foam"))
|
||||
foam_preservation_rate = FloatProperty(
|
||||
name="Foam Preservation Rate",
|
||||
description="Rate to add to the lifetime of preserved foam. This"
|
||||
" value is the number of seconds to add per second, so if"
|
||||
" greater than one can effectively preserve high density foam"
|
||||
" clumps from every being killed",
|
||||
default=0.75,
|
||||
precision=2,
|
||||
); exec(conv("foam_preservation_rate"))
|
||||
min_max_foam_density = NewMinMaxIntProperty(
|
||||
name_min="Min Foam Density",
|
||||
description_min="Foam densities less than this value will not increase"
|
||||
" the lifetime of a foam particle. Foam density units are in"
|
||||
" number of particles per voxel",
|
||||
min_min=0,
|
||||
default_min=20,
|
||||
|
||||
name_max="Max Foam Density",
|
||||
description_max="Foam densities that are greater than the min value,"
|
||||
" and less than the max value, proportionally increase the"
|
||||
" particle lifetime based on the Foam Preservation Rate. Foam"
|
||||
" density units are in number of particles per voxel",
|
||||
min_max=0,
|
||||
default_max=45,
|
||||
); exec(conv("min_max_foam_density"))
|
||||
bubble_drag_coefficient = FloatProperty(
|
||||
name="Bubble Drag Coefficient",
|
||||
description="Controls how quickly bubble particles are dragged with"
|
||||
" the fluid velocity. If set to 1, bubble particles will be"
|
||||
" immediately dragged into the flow direction of the fluid",
|
||||
min=0.0, max=1.0,
|
||||
default=0.8,
|
||||
precision=2,
|
||||
subtype='FACTOR',
|
||||
); exec(conv("bubble_drag_coefficient"))
|
||||
bubble_bouyancy_coefficient = FloatProperty(
|
||||
name="Bubble Buoyancy Coefficient",
|
||||
description="Controls how quickly bubble particles float towards"
|
||||
" the fluid surface. If set to a negative value, bubbles will"
|
||||
" sink away from the fluid surface",
|
||||
default=2.5,
|
||||
precision=2,
|
||||
step=0.3,
|
||||
); exec(conv("bubble_bouyancy_coefficient"))
|
||||
spray_drag_coefficient = FloatProperty(
|
||||
name="Spray Drag Coefficient",
|
||||
description="Controls amount of air resistance on a spray particle",
|
||||
min=0.0, max=5.0,
|
||||
default=3.0,
|
||||
precision=2,
|
||||
); exec(conv("spray_drag_coefficient"))
|
||||
dust_drag_coefficient = FloatProperty(
|
||||
name="Dust Drag Coefficient",
|
||||
description="Controls how quickly dust particles are dragged with"
|
||||
" the fluid velocity. If set to 1, dust particles will be"
|
||||
" immediately dragged into the flow direction of the fluid",
|
||||
min=0.0, max=1.0,
|
||||
default=0.75,
|
||||
precision=2,
|
||||
subtype='FACTOR',
|
||||
); exec(conv("dust_drag_coefficient"))
|
||||
dust_bouyancy_coefficient = FloatProperty(
|
||||
name="Dust Buoyancy Coefficient",
|
||||
description="Controls how quickly dust particles sink towards"
|
||||
" the ground. Decreasing this value will cause particles to sink"
|
||||
" more quickly. If set to a positive value, dust will float towards"
|
||||
" fluid surface",
|
||||
default=-3.0,
|
||||
precision=2,
|
||||
step=0.3,
|
||||
); exec(conv("dust_bouyancy_coefficient"))
|
||||
foam_boundary_behaviour = EnumProperty(
|
||||
name="Foam Behaviour At Limits",
|
||||
description="Specifies the foam particle behavior when hitting the"
|
||||
" domain boundary",
|
||||
items=types.boundary_behaviours,
|
||||
default='BEHAVIOUR_COLLIDE',
|
||||
); exec(conv("foam_boundary_behaviour"))
|
||||
bubble_boundary_behaviour = EnumProperty(
|
||||
name="Bubble Behaviour At Limits",
|
||||
description="Specifies the bubble particle behavior when hitting"
|
||||
" the domain boundary",
|
||||
items=types.boundary_behaviours,
|
||||
default='BEHAVIOUR_COLLIDE',
|
||||
); exec(conv("bubble_boundary_behaviour"))
|
||||
spray_boundary_behaviour = EnumProperty(
|
||||
name="Spray Behaviour At Limits",
|
||||
description="Specifies the spray particle behavior when hitting the"
|
||||
" domain boundary",
|
||||
items=types.boundary_behaviours,
|
||||
default='BEHAVIOUR_COLLIDE',
|
||||
); exec(conv("spray_boundary_behaviour"))
|
||||
foam_boundary_active = BoolVectorProperty(
|
||||
name="",
|
||||
description="Activate behaviour on the corresponding side of the domain",
|
||||
default=(True, True, True, True, False, True),
|
||||
size=6,
|
||||
); exec(conv("foam_boundary_active"))
|
||||
bubble_boundary_active = BoolVectorProperty(
|
||||
name="",
|
||||
description="Activate behaviour on the corresponding side of the domain",
|
||||
default=(True, True, True, True, False, True),
|
||||
size=6,
|
||||
); exec(conv("bubble_boundary_active"))
|
||||
spray_boundary_active = BoolVectorProperty(
|
||||
name="",
|
||||
description="Activate behaviour on the corresponding side of the domain",
|
||||
default=(True, True, True, True, False, True),
|
||||
size=6,
|
||||
); exec(conv("spray_boundary_active"))
|
||||
whitewater_boundary_collisions_mode = EnumProperty(
|
||||
name="Domain Boundary Collisions Mode",
|
||||
description="Select how to set the domain boundary collisions",
|
||||
items=types.boundary_collisions_modes,
|
||||
default='BOUNDARY_COLLISIONS_MODE_INHERIT',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("whitewater_boundary_collisions_mode"))
|
||||
foam_boundary_collisions = BoolVectorProperty(
|
||||
name="",
|
||||
description="Enable collisions on the corresponding side of the domain for whitewater foam particles."
|
||||
" If disabled, this side of the boundary will be open and will act"
|
||||
" as an outflow",
|
||||
default=(True, True, True, True, True, True),
|
||||
size=6,
|
||||
); exec(conv("foam_boundary_collisions"))
|
||||
bubble_boundary_collisions = BoolVectorProperty(
|
||||
name="",
|
||||
description="Enable collisions on the corresponding side of the domain for whitewater bubble particles."
|
||||
" If disabled, this side of the boundary will be open and will act"
|
||||
" as an outflow",
|
||||
default=(True, True, True, True, True, True),
|
||||
size=6,
|
||||
); exec(conv("bubble_boundary_collisions"))
|
||||
spray_boundary_collisions = BoolVectorProperty(
|
||||
name="",
|
||||
description="Enable collisions on the corresponding side of the domain for whitewater spray particles."
|
||||
" If disabled, this side of the boundary will be open and will act"
|
||||
" as an outflow",
|
||||
default=(True, True, True, True, True, True),
|
||||
size=6,
|
||||
); exec(conv("spray_boundary_collisions"))
|
||||
dust_boundary_collisions = BoolVectorProperty(
|
||||
name="",
|
||||
description="Enable collisions on the corresponding side of the domain for whitewater dust particles."
|
||||
" If disabled, this side of the boundary will be open and will act"
|
||||
" as an outflow",
|
||||
default=(True, True, True, True, True, True),
|
||||
size=6,
|
||||
); exec(conv("dust_boundary_collisions"))
|
||||
obstacle_influence_base_level = FloatProperty(
|
||||
name="Influence Base Level",
|
||||
description="The default value of whitewater influence. If a location"
|
||||
" is not affected by an obstacle's influence, the amount"
|
||||
" of whitewater generated at this location will be scaled by"
|
||||
" this value. A value of 1.0 will generate a normal amount"
|
||||
" of whitewater, a value greater than 1.0 will generate more,"
|
||||
" a value less than 1.0 will generate less",
|
||||
min=0.0,
|
||||
default=1.0,
|
||||
precision=2,
|
||||
); exec(conv("obstacle_influence_base_level"))
|
||||
obstacle_influence_decay_rate = FloatProperty(
|
||||
name="Influence Decay Rate",
|
||||
description="The rate at which influence will decay towards the"
|
||||
" base level. If a keyframed/animated obstacle leaves an"
|
||||
" influence above/below the base level at some location,"
|
||||
" the value of influence at this location will adjust towards"
|
||||
" the base level value at this rate. This value is in amount"
|
||||
" of influence per second",
|
||||
min=0.0,
|
||||
default=5.0,
|
||||
precision=2,
|
||||
); exec(conv("obstacle_influence_decay_rate"))
|
||||
|
||||
settings_view_mode_expanded = BoolProperty(default=False); exec(conv("settings_view_mode_expanded"))
|
||||
whitewater_simulation_particles_expanded = BoolProperty(default=False); exec(conv("whitewater_simulation_particles_expanded"))
|
||||
emitter_settings_expanded = BoolProperty(default=True); exec(conv("emitter_settings_expanded"))
|
||||
particle_settings_expanded = BoolProperty(default=False); exec(conv("particle_settings_expanded"))
|
||||
boundary_behaviour_settings_expanded = BoolProperty(default=False); exec(conv("boundary_behaviour_settings_expanded"))
|
||||
obstacle_settings_expanded = BoolProperty(default=False); exec(conv("obstacle_settings_expanded"))
|
||||
whitewater_display_settings_expanded = BoolProperty(default=False); exec(conv("whitewater_display_settings_expanded"))
|
||||
geometry_attributes_expanded = BoolProperty(default=False); exec(conv("geometry_attributes_expanded"))
|
||||
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
add = registry.add_property
|
||||
add(path + ".enable_whitewater_simulation", "Enable Whitewater", group_id=0)
|
||||
add(path + ".enable_foam", "Enable Foam", group_id=0)
|
||||
add(path + ".enable_bubbles", "Enable Bubbles", group_id=0)
|
||||
add(path + ".enable_spray", "Enable Spray", group_id=0)
|
||||
add(path + ".enable_dust", "Enable Dust", group_id=0)
|
||||
add(path + ".generate_whitewater_motion_blur_data", "Generate Motion Blur Data", group_id=0)
|
||||
add(path + ".enable_velocity_vector_attribute", "Generate Velocity Attributes", group_id=0)
|
||||
add(path + ".enable_id_attribute", "Generate ID Attributes", group_id=0)
|
||||
add(path + ".enable_lifetime_attribute", "Generate Lifetime Attributes", group_id=0)
|
||||
add(path + ".enable_whitewater_emission", "Enable Emission", group_id=0)
|
||||
add(path + ".whitewater_emitter_generation_rate", "Emission Rate", group_id=0)
|
||||
add(path + ".wavecrest_emission_rate", "Wavecrest Emission Rate", group_id=0)
|
||||
add(path + ".turbulence_emission_rate", "Turbulence Emission Rate", group_id=0)
|
||||
add(path + ".dust_emission_rate", "Dust Emission Rate", group_id=0)
|
||||
add(path + ".spray_emission_speed", "Spray Emission Speed", group_id=0)
|
||||
add(path + ".min_max_whitewater_energy_speed", "Min-Max Energy Speed", group_id=0)
|
||||
add(path + ".min_max_whitewater_wavecrest_curvature", "Min-Max Curvature", group_id=0)
|
||||
add(path + ".min_max_whitewater_turbulence", "Min-Max Turbulence", group_id=0)
|
||||
add(path + ".max_whitewater_particles", "Max Particles", group_id=0)
|
||||
add(path + ".enable_whitewater_emission_near_boundary", "Emit Near Boundary", group_id=0)
|
||||
add(path + ".enable_dust_emission_near_boundary", "Emit Dust Near Boundary", group_id=0)
|
||||
add(path + ".min_max_whitewater_lifespan", "Min-Max Lifespane", group_id=1)
|
||||
add(path + ".whitewater_lifespan_variance", "Lifespan Variance", group_id=1)
|
||||
add(path + ".foam_lifespan_modifier", "Foam Lifespan Modifier", group_id=1)
|
||||
add(path + ".bubble_lifespan_modifier", "Bubble Lifespan Modifier", group_id=1)
|
||||
add(path + ".spray_lifespan_modifier", "Spray Lifespan Modifier", group_id=1)
|
||||
add(path + ".dust_lifespan_modifier", "Dust Lifespan Modifier", group_id=1)
|
||||
add(path + ".foam_advection_strength", "Foam Advection Strength", group_id=1)
|
||||
add(path + ".foam_layer_depth", "Foam Depth", group_id=1)
|
||||
add(path + ".foam_layer_offset", "Foam Offset", group_id=1)
|
||||
add(path + ".preserve_foam", "Preserve Foam", group_id=1)
|
||||
add(path + ".foam_preservation_rate", "Preservation Rate", group_id=1)
|
||||
add(path + ".min_max_foam_density", "Min-Max Density", group_id=1)
|
||||
add(path + ".bubble_drag_coefficient", "Bubble Drag", group_id=2)
|
||||
add(path + ".bubble_bouyancy_coefficient", "Bubble Buoyancy", group_id=2)
|
||||
add(path + ".spray_drag_coefficient", "Spray Drag", group_id=2)
|
||||
add(path + ".dust_drag_coefficient", "Dust Drag", group_id=2)
|
||||
add(path + ".dust_bouyancy_coefficient", "Dust Buoyancy", group_id=2)
|
||||
add(path + ".foam_boundary_behaviour", "Foam Boundary Behaviour", group_id=2)
|
||||
add(path + ".bubble_boundary_behaviour", "Bubble Boundary Behaviour", group_id=2)
|
||||
add(path + ".spray_boundary_behaviour", "Spray Boundary Behaviour", group_id=2)
|
||||
add(path + ".foam_boundary_active", "Foam Boundary X–/+ Y–/+ Z–/+", group_id=2)
|
||||
add(path + ".bubble_boundary_active", "Bubble Boundary X–/+ Y–/+ Z–/+", group_id=2)
|
||||
add(path + ".spray_boundary_active", "Spray Boundary X–/+ Y–/+ Z–/+", group_id=2)
|
||||
add(path + ".whitewater_boundary_collisions_mode", "Boundary Collisions Mode", group_id=2)
|
||||
add(path + ".foam_boundary_collisions", "Foam Boundary Collisions", group_id=2)
|
||||
add(path + ".bubble_boundary_collisions", "Bubble Boundary Collisions", group_id=2)
|
||||
add(path + ".spray_boundary_collisions", "Spray Boundary Collisions", group_id=2)
|
||||
add(path + ".dust_boundary_collisions", "Dust Boundary Collisions", group_id=2)
|
||||
add(path + ".obstacle_influence_base_level", "Obstacle Influence Base Level", group_id=2)
|
||||
add(path + ".obstacle_influence_decay_rate", "Obstacle Influence Base Level", group_id=2)
|
||||
|
||||
|
||||
def _update_enable_whitewater_simulation(self, context):
|
||||
dprops = context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is None:
|
||||
return
|
||||
|
||||
if self.enable_whitewater_simulation:
|
||||
objects_to_initialize = flip_fluid_cache.EnabledMeshCacheObjects()
|
||||
objects_to_initialize.whitewater_particles = True
|
||||
|
||||
dprops.mesh_cache.initialize_cache_objects(objects_to_initialize)
|
||||
dprops.materials.whitewater_foam_material = dprops.materials.whitewater_foam_material
|
||||
dprops.materials.whitewater_bubble_material = dprops.materials.whitewater_bubble_material
|
||||
dprops.materials.whitewater_spray_material = dprops.materials.whitewater_spray_material
|
||||
dprops.materials.whitewater_dust_material = dprops.materials.whitewater_dust_material
|
||||
else:
|
||||
dprops.mesh_cache.foam.reset_cache_object()
|
||||
dprops.mesh_cache.bubble.reset_cache_object()
|
||||
dprops.mesh_cache.spray.reset_cache_object()
|
||||
dprops.mesh_cache.dust.reset_cache_object()
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DomainWhitewaterProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(DomainWhitewaterProperties)
|
||||
@@ -0,0 +1,454 @@
|
||||
# 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, os, math
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
IntProperty
|
||||
)
|
||||
|
||||
from .. import types
|
||||
from ..utils import export_utils
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
from ..objects.flip_fluid_aabb import AABB
|
||||
|
||||
|
||||
class DomainWorldProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
world_scale_mode = EnumProperty(
|
||||
name="World Scaling Mode",
|
||||
description="Scaling mode for the physical size of the domain",
|
||||
items=types.world_scale_mode,
|
||||
default='WORLD_SCALE_MODE_RELATIVE',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("world_scale_mode"))
|
||||
world_scale_relative = FloatProperty(
|
||||
name="Meters",
|
||||
description="Size of a Blender unit in meters. If set to 1.0, each blender unit will be equal to 1.0 meter in the simulation",
|
||||
min=0.0001,
|
||||
default=1.0,
|
||||
precision=3,
|
||||
update=lambda self, context: self._update_world_scale_relative(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("world_scale_relative"))
|
||||
world_scale_absolute = FloatProperty(
|
||||
name="Meters",
|
||||
description="Size of the longest side of the domain in meters",
|
||||
min=0.001,
|
||||
default=10.0,
|
||||
precision=3,
|
||||
update=lambda self, context: self._update_world_scale_absolute(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("world_scale_absolute"))
|
||||
gravity_type = EnumProperty(
|
||||
name="Gravity Type",
|
||||
description="Gravity Type",
|
||||
items=types.gravity_types,
|
||||
default='GRAVITY_TYPE_SCENE',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("gravity_type"))
|
||||
gravity = FloatVectorProperty(
|
||||
name="Gravity",
|
||||
description="Gravity in X, Y, and Z direction",
|
||||
default=(0.0, 0.0, -9.81),
|
||||
precision=3,
|
||||
size=3,
|
||||
subtype='ACCELERATION',
|
||||
); exec(conv("gravity"))
|
||||
force_field_resolution = EnumProperty(
|
||||
name="Force Field Resolution",
|
||||
description="Amount of grid resolution to use when evaluating force fields."
|
||||
" Higher resolution improves force field accuracy at the cost of speed"
|
||||
" and RAM. Increase to resolve smaller/sharper details in your force"
|
||||
" field setup. Ultra is recommended for static force fields. High/Med/Low is"
|
||||
" recommended for animated force fields to optimize performance",
|
||||
items=types.force_field_resolution_modes,
|
||||
default='FORCE_FIELD_RESOLUTION_ULTRA',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("force_field_resolution"))
|
||||
force_field_weight_fluid_particles = FloatProperty(
|
||||
name="Fluid Particles",
|
||||
description="Force field object weight for fluid particles",
|
||||
soft_min=0.0,
|
||||
soft_max=1.0,
|
||||
default=1.0,
|
||||
precision=3,
|
||||
); exec(conv("force_field_weight_fluid_particles"))
|
||||
force_field_weight_whitewater_foam = FloatProperty(
|
||||
name="Whitewater Foam",
|
||||
description="Force field object weight for whitewater foam particles. Note: This setting"
|
||||
" currently has no effect on the simulation. The movement of foam particles is"
|
||||
" controlled only by the motion of the fluid surface. Force fields and gravity"
|
||||
" currently have no effect on foam particles",
|
||||
soft_min=0.0,
|
||||
soft_max=1.0,
|
||||
default=1.0,
|
||||
precision=3,
|
||||
); exec(conv("force_field_weight_whitewater_foam"))
|
||||
force_field_weight_whitewater_bubble = FloatProperty(
|
||||
name="Whitewater Bubble",
|
||||
description="Force field object weight for whitewater bubble particles. Note: Bubble particles"
|
||||
" are assumed to be lower density than the containing liquid and due to buoyancy, bubbles"
|
||||
" will drift the opposite direction of the force fields. If you desire bubble particles"
|
||||
" to move with the direction of the force fields, set this weight to a negative value",
|
||||
soft_min=0.0,
|
||||
soft_max=1.0,
|
||||
default=1.0,
|
||||
precision=3,
|
||||
); exec(conv("force_field_weight_whitewater_bubble"))
|
||||
force_field_weight_whitewater_spray = FloatProperty(
|
||||
name="Whitewater Spray",
|
||||
description="Force field object weight for whitewater spray particles. Tip: Spray particles are"
|
||||
" often lower a lower density than the fluid. Depending on the desired effect, setting a higher"
|
||||
" weight value can be a good choice",
|
||||
soft_min=0.0,
|
||||
soft_max=1.0,
|
||||
default=1.0,
|
||||
precision=3,
|
||||
); exec(conv("force_field_weight_whitewater_spray"))
|
||||
force_field_weight_whitewater_dust = FloatProperty(
|
||||
name="Whitewater Dust",
|
||||
description="Force field object weight for whitewater dust particles",
|
||||
soft_min=0.0,
|
||||
soft_max=1.0,
|
||||
default=1.0,
|
||||
precision=3,
|
||||
); exec(conv("force_field_weight_whitewater_dust"))
|
||||
force_field_resolution_tooltip = BoolProperty(
|
||||
name="Force Field Grid Resolution",
|
||||
description="Exact force field grid resolution calculated from the domain"
|
||||
" resolution. See Debug Panel for force field visualization tools",
|
||||
default=True,
|
||||
); exec(conv("force_field_resolution_tooltip"))
|
||||
enable_viscosity = BoolProperty(
|
||||
name="Enable Viscosity",
|
||||
description="Enable viscosity solver",
|
||||
default=False,
|
||||
); exec(conv("enable_viscosity"))
|
||||
viscosity = FloatProperty(
|
||||
name="Viscosity",
|
||||
description="Viscosity base value. This value is multipled by 10 to the"
|
||||
" power of (exponent * -1)",
|
||||
min=0.0,
|
||||
default=5.0,
|
||||
precision=3,
|
||||
); exec(conv("viscosity"))
|
||||
viscosity_exponent = IntProperty(
|
||||
name="Viscosity Exponent",
|
||||
description="Viscosity exponent. Negative exponent for the viscosity value"
|
||||
" to simplify entering small values (ex: 5.0 * 10^-3 = 0.005)",
|
||||
min=0,
|
||||
soft_max=4, max=8,
|
||||
default=0,
|
||||
update=lambda self, context: self._update_viscosity_exponent(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("viscosity_exponent"))
|
||||
viscosity_solver_error_tolerance = IntProperty(
|
||||
description="Accuracy of the viscosity solver. Decrease to speed up baking at the cost of accuracy,"
|
||||
" increase to improve accuracy at the cost of baking speed. High viscosity thick or stiff fluids"
|
||||
" benefit the most from increasing accuracy. Low viscosity thin fluids often work well at the lowest"
|
||||
" accuracy. Setting above a value of 4 may have greatly diminishing returns on improvement and is not"
|
||||
" recommended",
|
||||
min=1, max=6,
|
||||
soft_max=4,
|
||||
default=4,
|
||||
); exec(conv("viscosity_solver_error_tolerance"))
|
||||
enable_surface_tension = BoolProperty(
|
||||
name="Enable Surface Tension",
|
||||
description="Enable surface tension forces",
|
||||
default=False,
|
||||
); exec(conv("enable_surface_tension"))
|
||||
surface_tension = FloatProperty(
|
||||
name="Surface Tension",
|
||||
description="Surface tension base value. This value is multipled by 10 to the"
|
||||
" power of (exponent * -1)",
|
||||
min=0.0,
|
||||
default=0.25,
|
||||
precision=3,
|
||||
); exec(conv("surface_tension"))
|
||||
surface_tension_exponent = IntProperty(
|
||||
name="Viscosity Exponent",
|
||||
description="Viscosity exponent. Negative exponent for the surface tension value"
|
||||
" to simplify entering small values (ex: 5.0 * 10^-3 = 0.005)",
|
||||
min=0,
|
||||
soft_max=4, max=8,
|
||||
default=0,
|
||||
update=lambda self, context: self._update_surface_tension_exponent(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("surface_tension_exponent"))
|
||||
surface_tension_accuracy = IntProperty(
|
||||
name="Surface Tension Accuracy",
|
||||
description="Amount of accuracy when calculating surface tension."
|
||||
" Increasing accuracy will produce more accurate surface tension"
|
||||
" results and reduce rippling artifacts but will require more substeps"
|
||||
" and increase baking time",
|
||||
min=0, max=100,
|
||||
default=95,
|
||||
subtype='PERCENTAGE',
|
||||
); exec(conv("surface_tension_accuracy"))
|
||||
surface_tension_solver_method = EnumProperty(
|
||||
name="Surface Tension Solver",
|
||||
description="Surface tension solving method to use",
|
||||
items=types.surface_tension_solver_methods,
|
||||
default='SURFACE_TENSION_SOLVER_METHOD_REGULAR',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("surface_tension_solver_method"))
|
||||
enable_sheet_seeding = BoolProperty(
|
||||
name="Enable Sheeting Effects",
|
||||
description="Fluid sheeting fills in gaps between fluid particles to"
|
||||
" help preserve thin fluid sheets and splashes. Tip: Sheeting will"
|
||||
" add fluid to the domain and prolonged use can result in an increased"
|
||||
" fluid volume. Keyframing the Sheeting Strength down to 0.0 when no longer"
|
||||
" needed can help prevent increased volume",
|
||||
default=False,
|
||||
); exec(conv("enable_sheet_seeding"))
|
||||
sheet_fill_rate = FloatProperty(
|
||||
name="Sheeting Strength",
|
||||
description="The rate at which new sheeting particles are added."
|
||||
" A higher value will add sheeting particles more often and"
|
||||
" fill in gaps more quickly",
|
||||
min=0.0, max=1.0,
|
||||
default=0.5,
|
||||
precision=2,
|
||||
); exec(conv("sheet_fill_rate"))
|
||||
sheet_fill_threshold = FloatProperty(
|
||||
name="Sheeting Thickness",
|
||||
description="Controls how thick to fill in gaps",
|
||||
min=0.0, max=1.0,
|
||||
soft_min=0.05,
|
||||
default=0.1,
|
||||
precision=2,
|
||||
); exec(conv("sheet_fill_threshold"))
|
||||
boundary_friction = FloatProperty(
|
||||
name="Boundary Friction",
|
||||
description="Amount of friction on the domain boundary walls",
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
default=0.0,
|
||||
precision=2,
|
||||
subtype='FACTOR',
|
||||
); exec(conv("boundary_friction"))
|
||||
|
||||
last_viscosity_exponent = IntProperty(default=0)
|
||||
exec(conv("last_viscosity_exponent"))
|
||||
|
||||
last_surface_tension_exponent = IntProperty(default=0)
|
||||
exec(conv("last_surface_tension_exponent"))
|
||||
|
||||
native_surface_tension_scale = FloatProperty(default=0.1)
|
||||
exec(conv("native_surface_tension_scale"))
|
||||
|
||||
minimum_surface_tension_substeps = IntProperty(default=-1)
|
||||
exec(conv("minimum_surface_tension_substeps"))
|
||||
|
||||
surface_tension_substeps_tooltip = BoolProperty(
|
||||
name="Estimated Substeps",
|
||||
description="The estimated number of substeps per frame that the"
|
||||
" simulator will run in order to keep simulation stable during surface"
|
||||
" tension computation. This number will depend on domain resolution"
|
||||
" and size, framerate, amount of surface tension, and surface tension"
|
||||
" accuracy",
|
||||
default=True,
|
||||
); exec(conv("surface_tension_substeps_tooltip"))
|
||||
|
||||
surface_tension_substeps_exceeded_tooltip = BoolProperty(
|
||||
name="Warning: Too Many Substeps",
|
||||
description="The estimated number of Surface Tension substeps per frame exceeds the Max Frame"
|
||||
" Substeps value. This can cause an unstable simulation. Either decrease the amount of"
|
||||
" Surface Tension in the FLIP Fluid World panel to lower the number of required substeps or"
|
||||
" increase the number of allowed Max Frame Substeps in the FLIP Fluid Advanced panel",
|
||||
default=True,
|
||||
); exec(conv("surface_tension_substeps_exceeded_tooltip"))
|
||||
|
||||
minimum_surface_tension_cfl = FloatProperty(default=0.25)
|
||||
exec(conv("minimum_surface_tension_cfl"))
|
||||
|
||||
maximum_surface_tension_cfl = FloatProperty(default=5.0)
|
||||
exec(conv("maximum_surface_tension_cfl"))
|
||||
|
||||
world_scale_settings_expanded = BoolProperty(default=True); exec(conv("world_scale_settings_expanded"))
|
||||
force_field_settings_expanded = BoolProperty(default=False); exec(conv("force_field_settings_expanded"))
|
||||
viscosity_settings_expanded = BoolProperty(default=False); exec(conv("viscosity_settings_expanded"))
|
||||
surface_tension_settings_expanded = BoolProperty(default=False); exec(conv("surface_tension_settings_expanded"))
|
||||
sheeting_settings_expanded = BoolProperty(default=False); exec(conv("sheeting_settings_expanded"))
|
||||
friction_settings_expanded = BoolProperty(default=False); exec(conv("friction_settings_expanded"))
|
||||
obstacle_friction_expanded = BoolProperty(default=False); exec(conv("obstacle_friction_expanded"))
|
||||
|
||||
|
||||
def scene_update_post(self, scene):
|
||||
self._update_surface_tension_info()
|
||||
self._update_world_scale_relative(bpy.context)
|
||||
self._update_world_scale_absolute(bpy.context)
|
||||
|
||||
|
||||
def frame_change_post(self, scene):
|
||||
# Accounts for keyframed value changes after a frame change
|
||||
self._update_surface_tension_info()
|
||||
|
||||
def register_preset_properties(self, registry, path):
|
||||
add = registry.add_property
|
||||
add(path + ".world_scale_mode", "World Scaling Mode", group_id=0)
|
||||
add(path + ".world_scale_relative", "Relative Scale", group_id=0)
|
||||
add(path + ".world_scale_absolute", "Absolute Scale", group_id=0)
|
||||
add(path + ".gravity_type", "Gravity Type", group_id=0)
|
||||
add(path + ".gravity", "Gravity", group_id=0)
|
||||
add(path + ".force_field_resolution", "Force Field Resolution", group_id=0)
|
||||
add(path + ".force_field_weight_fluid_particles", "Force Field Weight Fluid", group_id=0)
|
||||
add(path + ".force_field_weight_whitewater_foam", "Force Field Weight Foam", group_id=0)
|
||||
add(path + ".force_field_weight_whitewater_bubble", "Force Field Weight Bubble", group_id=0)
|
||||
add(path + ".force_field_weight_whitewater_spray", "Force Field Weight Spray", group_id=0)
|
||||
add(path + ".force_field_weight_whitewater_dust", "Force Field Weight Dust", group_id=0)
|
||||
add(path + ".enable_viscosity", "Enable Viscosity", group_id=0)
|
||||
add(path + ".viscosity", "Viscosity Base", group_id=0)
|
||||
add(path + ".viscosity_exponent", "Viscosity Exponent", group_id=0)
|
||||
add(path + ".viscosity_solver_error_tolerance", "Viscosity Accuracy", group_id=0)
|
||||
add(path + ".enable_surface_tension", "Enable Surface Tension", group_id=0)
|
||||
add(path + ".surface_tension", "Surface Tension", group_id=0)
|
||||
add(path + ".surface_tension_exponent", "Surface Tension Exponent", group_id=0)
|
||||
add(path + ".surface_tension_accuracy", "Surface Tension Accuracy", group_id=0)
|
||||
add(path + ".surface_tension_solver_method", "Surface Tension Solver", group_id=0)
|
||||
add(path + ".enable_sheet_seeding", "Enable Sheeting Effects", group_id=0)
|
||||
add(path + ".sheet_fill_rate", "Sheeting Strength", group_id=0)
|
||||
add(path + ".sheet_fill_threshold", "Sheeting Thickness", group_id=0)
|
||||
add(path + ".boundary_friction", "Boundary Friction", group_id=0)
|
||||
|
||||
|
||||
def get_gravity_data_dict(self):
|
||||
domain_object = bpy.context.scene.flip_fluid.get_domain_object()
|
||||
if self.gravity_type == 'GRAVITY_TYPE_SCENE':
|
||||
scene = bpy.context.scene
|
||||
return export_utils.get_vector_property_data_dict(scene, scene, 'gravity', use_exact_path=True)
|
||||
elif self.gravity_type == 'GRAVITY_TYPE_CUSTOM':
|
||||
return export_utils.get_vector_property_data_dict(domain_object, self, 'gravity')
|
||||
|
||||
|
||||
def get_scene_use_gravity_data_dict(self):
|
||||
scene = bpy.context.scene
|
||||
return export_utils.get_property_data_dict(scene, scene, 'use_gravity', use_exact_path=True)
|
||||
|
||||
|
||||
def get_gravity_vector(self):
|
||||
if self.gravity_type == 'GRAVITY_TYPE_SCENE':
|
||||
gravity = bpy.context.scene.gravity
|
||||
if not bpy.context.scene.use_gravity:
|
||||
gravity = (0.0, 0.0, 0.0)
|
||||
return gravity
|
||||
elif self.gravity_type == 'GRAVITY_TYPE_CUSTOM':
|
||||
return self.gravity
|
||||
|
||||
|
||||
def get_world_scale(self):
|
||||
return self.world_scale_relative
|
||||
|
||||
|
||||
def get_viewport_dimensions(self, context):
|
||||
domain = context.scene.flip_fluid.get_domain_object()
|
||||
minx = miny = minz = float("inf")
|
||||
maxx = maxy = maxz = -float("inf")
|
||||
for v in domain.data.vertices:
|
||||
p = vcu.element_multiply(v.co, domain.matrix_world)
|
||||
minx, miny, minz = min(p.x, minx), min(p.y, miny), min(p.z, minz)
|
||||
maxx, maxy, maxz = max(p.x, maxx), max(p.y, maxy), max(p.z, maxz)
|
||||
|
||||
return maxx - minx, maxy - miny, maxz - minz
|
||||
|
||||
|
||||
def get_simulation_dimensions(self, context):
|
||||
view_x, view_y, view_z = self.get_viewport_dimensions(context)
|
||||
if self.world_scale_mode == 'WORLD_SCALE_MODE_RELATIVE':
|
||||
scale = self.world_scale_relative
|
||||
else:
|
||||
longest_side = max(view_x, view_y, view_z, 1e-6)
|
||||
scale = self.world_scale_absolute / longest_side
|
||||
|
||||
return view_x * scale, view_y * scale, view_z * scale
|
||||
|
||||
|
||||
def get_surface_tension_value(self):
|
||||
return self.surface_tension * (10**(-self.surface_tension_exponent))
|
||||
|
||||
|
||||
def _update_world_scale_relative(self, context):
|
||||
if self.world_scale_mode == 'WORLD_SCALE_MODE_ABSOLUTE':
|
||||
return
|
||||
xdims, ydims, zdims = self.get_simulation_dimensions(context)
|
||||
absolute_scale = max(xdims, ydims, zdims)
|
||||
if self.world_scale_absolute != absolute_scale:
|
||||
self.world_scale_absolute = absolute_scale
|
||||
|
||||
|
||||
def _update_world_scale_absolute(self, context):
|
||||
if self.world_scale_mode == 'WORLD_SCALE_MODE_RELATIVE':
|
||||
return
|
||||
xdims, ydims, zdims = self.get_simulation_dimensions(context)
|
||||
xview, yview, zview = self.get_viewport_dimensions(context)
|
||||
relative_scale = max(xdims, ydims, zdims, 1e-6) / max(xview, yview, zview)
|
||||
if self.world_scale_relative != relative_scale:
|
||||
self.world_scale_relative = relative_scale
|
||||
|
||||
|
||||
def _update_surface_tension_info(self):
|
||||
domain = bpy.context.scene.flip_fluid.get_domain_object()
|
||||
if domain is None:
|
||||
return
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
|
||||
_, _, _, dx = dprops.simulation.get_simulation_grid_dimensions()
|
||||
|
||||
time_scale = dprops.simulation.time_scale
|
||||
frame_rate = dprops.simulation.get_frame_rate()
|
||||
dt = (1.0 / frame_rate) * time_scale
|
||||
|
||||
mincfl, maxcfl = dprops.world.minimum_surface_tension_cfl, dprops.world.maximum_surface_tension_cfl
|
||||
accuracy_pct = dprops.world.surface_tension_accuracy / 100.0
|
||||
safety_factor = mincfl + (1.0 - accuracy_pct) * (maxcfl - mincfl)
|
||||
|
||||
surface_tension = self.get_surface_tension_value() * dprops.world.native_surface_tension_scale
|
||||
eps = 1e-6
|
||||
|
||||
restriction = safety_factor * math.sqrt(dx * dx * dx) * math.sqrt(1.0 / (surface_tension + eps));
|
||||
num_substeps = math.ceil(dt / restriction)
|
||||
|
||||
if self.minimum_surface_tension_substeps != num_substeps:
|
||||
self.minimum_surface_tension_substeps = num_substeps
|
||||
|
||||
|
||||
def _update_viscosity_exponent(self, context):
|
||||
last_value = self.last_viscosity_exponent
|
||||
new_value = self.viscosity_exponent
|
||||
multiplier = 10**(new_value - last_value)
|
||||
self.viscosity = multiplier * self.viscosity
|
||||
self.last_viscosity_exponent = new_value
|
||||
|
||||
|
||||
def _update_surface_tension_exponent(self, context):
|
||||
last_value = self.last_surface_tension_exponent
|
||||
new_value = self.surface_tension_exponent
|
||||
multiplier = 10**(new_value - last_value)
|
||||
self.surface_tension = multiplier * self.surface_tension
|
||||
self.last_surface_tension_exponent = new_value
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DomainWorldProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(DomainWorldProperties)
|
||||
@@ -0,0 +1,264 @@
|
||||
# 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, os
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
StringProperty,
|
||||
PointerProperty
|
||||
)
|
||||
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
class FlipFluidProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
show_render = BoolProperty(
|
||||
name="Show Render",
|
||||
description="Display simulation in render. If disabled, simulation data will not be loaded in the render",
|
||||
default=True,
|
||||
); exec(conv("show_render"))
|
||||
show_viewport = BoolProperty(
|
||||
name="Show Viewport",
|
||||
description="Display simulation in viewport. If disabled, simulation data will not be loaded in the viewport."
|
||||
" Disable to speed up playback while working on other areas of your scene",
|
||||
default=True,
|
||||
); exec(conv("show_viewport"))
|
||||
|
||||
logo_name = StringProperty("flip_fluids_logo"); exec(conv("logo_name"))
|
||||
domain_object_name = StringProperty(default=""); exec(conv("domain_object_name"))
|
||||
|
||||
|
||||
@classmethod
|
||||
def register(cls):
|
||||
bpy.types.Scene.flip_fluid = PointerProperty(
|
||||
name="Flip Fluid Properties",
|
||||
description="",
|
||||
type=cls,
|
||||
)
|
||||
cls.custom_icons = bpy.utils.previews.new()
|
||||
|
||||
|
||||
@classmethod
|
||||
def unregister(cls):
|
||||
bpy.utils.previews.remove(cls.custom_icons)
|
||||
del bpy.types.Scene.flip_fluid
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self._initialize_custom_icons()
|
||||
|
||||
|
||||
def is_addon_disabled_in_blend_file(self):
|
||||
return bpy.context.scene.flip_fluid_helper.is_addon_disabled_in_blend_file()
|
||||
|
||||
|
||||
def is_domain_object_set(self):
|
||||
return self.get_domain_object() is not None
|
||||
|
||||
|
||||
def get_num_domain_objects(self):
|
||||
n = 0
|
||||
for obj in bpy.data.objects:
|
||||
if obj.flip_fluid.is_domain():
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def get_domain_object(self):
|
||||
domain = bpy.data.objects.get(self.domain_object_name)
|
||||
if not domain or not domain.flip_fluid.is_domain():
|
||||
for obj in bpy.data.objects:
|
||||
if obj.flip_fluid.is_domain():
|
||||
domain = obj
|
||||
try:
|
||||
# This operation may not be allowed depending on
|
||||
# the Blender context. It's harmless if this fails.
|
||||
self.domain_object_name = domain.name
|
||||
except:
|
||||
pass
|
||||
break
|
||||
if domain is not None and domain.flip_fluid.object_type != 'TYPE_DOMAIN':
|
||||
return None
|
||||
return domain
|
||||
|
||||
|
||||
def get_domain_properties(self):
|
||||
domain_object = self.get_domain_object()
|
||||
if domain_object is None:
|
||||
return
|
||||
return domain_object.flip_fluid.domain
|
||||
|
||||
|
||||
def is_domain_in_active_scene(self):
|
||||
domain_object = self.get_domain_object()
|
||||
if domain_object is None:
|
||||
return False
|
||||
return domain_object.name in bpy.context.scene.collection.all_objects
|
||||
|
||||
|
||||
def get_num_fluid_objects(self):
|
||||
n = 0
|
||||
for obj in vcu.get_all_scene_objects():
|
||||
if obj.flip_fluid.is_fluid():
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def get_fluid_objects(self, skip_hide_viewport=False):
|
||||
objects = []
|
||||
for obj in vcu.get_all_scene_objects():
|
||||
if not obj.flip_fluid.is_active:
|
||||
continue
|
||||
if skip_hide_viewport and obj.hide_viewport:
|
||||
continue
|
||||
if obj.flip_fluid.is_fluid():
|
||||
objects.append(obj)
|
||||
return objects
|
||||
|
||||
|
||||
def get_num_obstacle_objects(self):
|
||||
n = 0
|
||||
for obj in vcu.get_all_scene_objects():
|
||||
if not obj.flip_fluid.is_active:
|
||||
continue
|
||||
if obj.flip_fluid.is_obstacle():
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def get_obstacle_objects(self, skip_hide_viewport=False):
|
||||
objects = []
|
||||
for obj in vcu.get_all_scene_objects():
|
||||
if not obj.flip_fluid.is_active:
|
||||
continue
|
||||
if skip_hide_viewport and obj.hide_viewport:
|
||||
continue
|
||||
if obj.flip_fluid.is_obstacle():
|
||||
objects.append(obj)
|
||||
return objects
|
||||
|
||||
|
||||
def get_num_inflow_objects(self):
|
||||
n = 0
|
||||
for obj in vcu.get_all_scene_objects():
|
||||
if not obj.flip_fluid.is_active:
|
||||
continue
|
||||
if obj.flip_fluid.is_inflow():
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def get_inflow_objects(self, skip_hide_viewport=False):
|
||||
objects = []
|
||||
for obj in vcu.get_all_scene_objects():
|
||||
if not obj.flip_fluid.is_active:
|
||||
continue
|
||||
if skip_hide_viewport and obj.hide_viewport:
|
||||
continue
|
||||
if obj.flip_fluid.is_inflow():
|
||||
objects.append(obj)
|
||||
return objects
|
||||
|
||||
|
||||
def get_num_outflow_objects(self):
|
||||
n = 0
|
||||
for obj in vcu.get_all_scene_objects():
|
||||
if not obj.flip_fluid.is_active:
|
||||
continue
|
||||
if obj.flip_fluid.is_outflow():
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def get_outflow_objects(self, skip_hide_viewport=False):
|
||||
objects = []
|
||||
for obj in vcu.get_all_scene_objects():
|
||||
if not obj.flip_fluid.is_active:
|
||||
continue
|
||||
if skip_hide_viewport and obj.hide_viewport:
|
||||
continue
|
||||
if obj.flip_fluid.is_outflow():
|
||||
objects.append(obj)
|
||||
return objects
|
||||
|
||||
|
||||
def get_num_force_field_objects(self):
|
||||
n = 0
|
||||
for obj in vcu.get_all_scene_objects():
|
||||
if not obj.flip_fluid.is_active:
|
||||
continue
|
||||
if obj.flip_fluid.is_force_field():
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def get_force_field_objects(self, skip_hide_viewport=False):
|
||||
objects = []
|
||||
for obj in vcu.get_all_scene_objects():
|
||||
if not obj.flip_fluid.is_active:
|
||||
continue
|
||||
if skip_hide_viewport and obj.hide_viewport:
|
||||
continue
|
||||
if obj.flip_fluid.is_force_field():
|
||||
objects.append(obj)
|
||||
return objects
|
||||
|
||||
|
||||
def get_simulation_objects(self, skip_hide_viewport=False):
|
||||
objects = []
|
||||
for obj in vcu.get_all_scene_objects():
|
||||
if obj.flip_fluid.object_type == 'TYPE_NONE':
|
||||
continue
|
||||
if not obj.flip_fluid.is_active:
|
||||
continue
|
||||
if skip_hide_viewport and obj.hide_viewport:
|
||||
continue
|
||||
if obj.flip_fluid.is_domain():
|
||||
# get all FLIP Fluid objects that are not a domain
|
||||
continue
|
||||
objects.append(obj)
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
def get_logo_icon(self):
|
||||
return self.custom_icons.get(self.logo_name)
|
||||
|
||||
|
||||
def _initialize_custom_icons(self):
|
||||
addon_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if vcu.is_blender_28():
|
||||
icon_filename = "flip_fluids_logo_28.png"
|
||||
else:
|
||||
icon_filename = "flip_fluids_logo_27.png"
|
||||
logo_path = os.path.join(addon_dir, "icons", icon_filename)
|
||||
self.custom_icons.clear()
|
||||
if os.path.isfile(logo_path):
|
||||
self.custom_icons.load(self.logo_name, logo_path, 'IMAGE')
|
||||
|
||||
|
||||
def load_post():
|
||||
bpy.context.scene.flip_fluid.load_post()
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(FlipFluidProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(FlipFluidProperties)
|
||||
@@ -0,0 +1,304 @@
|
||||
# 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 bpy.props import (
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
StringProperty,
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
IntProperty,
|
||||
PointerProperty
|
||||
)
|
||||
|
||||
from . import preset_properties
|
||||
from .. import types
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
class FlipFluidFluidProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
initial_velocity = FloatVectorProperty(
|
||||
name="Initial Velocity",
|
||||
description="Initial velocity of fluid (m/s)",
|
||||
default =(0.0, 0.0, 0.0),
|
||||
size=3,
|
||||
precision=3,
|
||||
subtype='VELOCITY',
|
||||
); exec(conv("initial_velocity"))
|
||||
append_object_velocity = BoolProperty(
|
||||
name="Add Object Velocity to Fluid",
|
||||
description="Add the velocity of the object to the initial velocity"
|
||||
" of the fluid. Object mesh must be rigid (non-deformable)",
|
||||
default=False,
|
||||
); exec(conv("append_object_velocity"))
|
||||
append_object_velocity_influence = FloatProperty(
|
||||
name="Influence",
|
||||
description="Amount of velocity that is added to the fluid."
|
||||
" A value of 1.0 is normal, less than 1.0 will dampen the"
|
||||
" velocity, greater than 1.0 will exaggerate the velocity,"
|
||||
" negative values will reverse velocity direction",
|
||||
subtype='FACTOR',
|
||||
soft_min=0.0, soft_max=1.0,
|
||||
default=1.0,
|
||||
precision=2,
|
||||
); exec(conv("append_object_velocity_influence"))
|
||||
priority = IntProperty(
|
||||
name="Priority",
|
||||
description="Priority that this fluid object is added to the simulation"
|
||||
" during a frame. If multiple fluid/inflow objects are adding fluid"
|
||||
" to the simulation during a frame, the object with the higher priority"
|
||||
" value will be added first. Use to control which fluid attributes are"
|
||||
" prioritized and set first such as with intersecting objects or nested"
|
||||
" objects",
|
||||
min=-1000000, max=1000000,
|
||||
default=0,
|
||||
); exec(conv("priority"))
|
||||
use_initial_velocity_target = BoolProperty(
|
||||
name ="Set towards target",
|
||||
description="Set initial velocity towards a target object",
|
||||
default=False,
|
||||
options={'HIDDEN'}
|
||||
); exec(conv("use_initial_velocity_target"))
|
||||
fluid_velocity_mode = EnumProperty(
|
||||
name="Velocity Mode",
|
||||
description="Set how the inital fluid velocity is calculated",
|
||||
items=types.fluid_velocity_modes,
|
||||
default='FLUID_VELOCITY_MANUAL',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("fluid_velocity_mode"))
|
||||
initial_speed = bpy.props.FloatProperty(
|
||||
name="Speed",
|
||||
description="Initial speed of fluid towards target (m/s)",
|
||||
default=0.0,
|
||||
precision=3,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("initial_speed"))
|
||||
fluid_axis_mode = EnumProperty(
|
||||
name="Local Axis",
|
||||
description="Set local axis direction of fluid",
|
||||
items=types.local_axis_directions,
|
||||
default='LOCAL_AXIS_POS_X',
|
||||
); exec(conv("fluid_axis_mode"))
|
||||
target_object = PointerProperty(
|
||||
name="Target Object",
|
||||
type=bpy.types.Object
|
||||
); exec(conv("target_object"))
|
||||
source_id = IntProperty(
|
||||
name="Source ID Attribute",
|
||||
description="Assign this identifier value to the fluid generated by this object. After"
|
||||
" baking, the source ID attribute values can be accessed in a Cycles Attribute Node"
|
||||
" with the name 'flip_source_id' from the Fac output. This can be used to create"
|
||||
" basic multiple material liquid effects. Enable this feature in the Domain FLIP"
|
||||
" Fluid Surface panel",
|
||||
min=0, soft_max=16,
|
||||
default=0,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("source_id"))
|
||||
viscosity = FloatProperty(
|
||||
name="Viscosity Attribute",
|
||||
description="Assign this viscosity value to the fluid generated by this object. After"
|
||||
" baking, the viscosity attribute values can be accessed in a Cycles Attribute Node"
|
||||
" with the name 'flip_viscosity' from the Fac output. This feature can be used to create"
|
||||
" variable viscosity liquid effects. Enable this viscosity feature in the Domain FLIP"
|
||||
" Fluid World panel",
|
||||
min=0.0,
|
||||
default=0.0,
|
||||
); exec(conv("viscosity"))
|
||||
lifetime = FloatProperty(
|
||||
name="Lifetime Attribute",
|
||||
description="Assign this starting lifetime value to the fluid generated by this object."
|
||||
" This value is the amount of time remaining (in seconds) before the fluid is removed from the"
|
||||
" simulation. After baking, the lifetime attribute values can be accessed in a Cycles Attribute Node"
|
||||
" with the name 'flip_lifetime' from the Fac output. Enable this feature in the Domain FLIP"
|
||||
" Fluid Surface panel",
|
||||
min=0.0,
|
||||
default=1000.0,
|
||||
); exec(conv("lifetime"))
|
||||
lifetime_variance = FloatProperty(
|
||||
name="Lifetime Variance",
|
||||
description="Add or subtract a random value in seconds to the starting lifetime within the range of this variance value",
|
||||
min=0.0,
|
||||
default=0.0,
|
||||
); exec(conv("lifetime_variance"))
|
||||
color = FloatVectorProperty(
|
||||
name="Color Attribute",
|
||||
description="Assign this color to the fluid generated by this object. After"
|
||||
" baking, the color attribute values can be accessed in a Cycles Attribute Node"
|
||||
" with the name 'flip_color' from the Color output. This can be used to create"
|
||||
" basic varying color liquid effects. Enable this feature in the Domain FLIP"
|
||||
" Fluid Surface panel",
|
||||
default=(1.0, 1.0, 1.0),
|
||||
min=0.0, max=1.0,
|
||||
size=3,
|
||||
precision=3,
|
||||
subtype='COLOR',
|
||||
); exec(conv("color"))
|
||||
export_animated_target = BoolProperty(
|
||||
name="Export Animated Target",
|
||||
description="Export this object as an animated mesh. Exporting animated meshes are"
|
||||
" slower, only use when necessary. This option is required for any animation that"
|
||||
" is more complex than just keyframed loc/rot/scale or F-Curves, such as parented"
|
||||
" relations, armatures, animated modifiers, deformable meshes, etc. This option is"
|
||||
" not needed for static objects",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("export_animated_target"))
|
||||
export_animated_mesh = BoolProperty(
|
||||
name="Export Animated Mesh",
|
||||
description="Export this mesh as an animated one (slower, only use"
|
||||
" if really necessary [e.g. armatures or parented objects],"
|
||||
" animated pos/rot/scale F-curves do not require it",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("export_animated_mesh"))
|
||||
skip_reexport = BoolProperty(
|
||||
name="Skip Re-Export",
|
||||
description="Skip re-exporting this mesh when starting or resuming"
|
||||
" a bake. If this mesh has not been exported or is missing files,"
|
||||
" the addon will automatically export the required files",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("skip_reexport"))
|
||||
force_reexport_on_next_bake = BoolProperty(
|
||||
name="Force Re-Export On Next Bake",
|
||||
description="Override the 'Skip Re-Export' option and force this mesh to be"
|
||||
" re-exported and updated on the next time a simulation start/resumes"
|
||||
" baking. Afting starting/resuming the baking process, this option"
|
||||
" will automatically be disabled once the object has been fully exported."
|
||||
" This option is only applicable if 'Skip Re-Export' is enabled",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("force_reexport_on_next_bake"))
|
||||
frame_offset_type = EnumProperty(
|
||||
name="Trigger Type",
|
||||
description="When to trigger fluid object",
|
||||
items=types.frame_offset_types,
|
||||
default='OFFSET_TYPE_FRAME',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("frame_offset_type"))
|
||||
frame_offset = IntProperty(
|
||||
name="",
|
||||
description="Frame offset from start of simulation to add fluid object"
|
||||
" to domain",
|
||||
min=0,
|
||||
default=0,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("frame_offset"))
|
||||
timeline_offset = bpy.props.IntProperty(
|
||||
name="",
|
||||
description="Timeline frame to add fluid object to domain",
|
||||
min=0,
|
||||
default=0,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("timeline_offset"))
|
||||
property_registry = PointerProperty(
|
||||
name="Fluid Property Registry",
|
||||
description="",
|
||||
type=preset_properties.PresetRegistry,
|
||||
); exec(conv("property_registry"))
|
||||
|
||||
disabled_in_viewport_tooltip = BoolProperty(
|
||||
name="Object Disabled in Viewport",
|
||||
description="This fluid object is currently disabled in the viewport within the"
|
||||
" outliner (Monitor Icon) and will not be included in the simulation. If you"
|
||||
" want the object hidden in the viewport, but still have the object included in the"
|
||||
" simulation, use the outliner Hide in Viewport option instead (Eye Icon)",
|
||||
default=True,
|
||||
); exec(conv("disabled_in_viewport_tooltip"))
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def refresh_property_registry(self):
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def _initialize_property_registry(self):
|
||||
try:
|
||||
self.property_registry.clear()
|
||||
add = self.property_registry.add_property
|
||||
add("fluid.initial_velocity", "")
|
||||
add("fluid.append_object_velocity", "")
|
||||
add("fluid.append_object_velocity_influence", "")
|
||||
add("fluid.priority", "")
|
||||
add("fluid.fluid_velocity_mode", "")
|
||||
add("fluid.initial_speed", "")
|
||||
add("fluid.fluid_axis_mode", "")
|
||||
add("fluid.source_id", "")
|
||||
add("fluid.viscosity", "")
|
||||
add("inflow.lifetime", "")
|
||||
add("inflow.lifetime_variance", "")
|
||||
add("fluid.color", "")
|
||||
add("fluid.target_object", "")
|
||||
add("fluid.export_animated_target", "")
|
||||
add("fluid.export_animated_mesh", "")
|
||||
add("fluid.skip_reexport", "")
|
||||
add("fluid.force_reexport_on_next_bake", "")
|
||||
add("fluid.frame_offset_type", "")
|
||||
add("fluid.frame_offset", "")
|
||||
add("fluid.timeline_offset", "")
|
||||
self._validate_property_registry()
|
||||
except:
|
||||
# Object is immutable if it is a linked library or library_override
|
||||
# In this case, pass on modifying the object
|
||||
pass
|
||||
|
||||
|
||||
def _validate_property_registry(self):
|
||||
for p in self.property_registry.properties:
|
||||
path = p.path
|
||||
base, identifier = path.split('.', 1)
|
||||
if not hasattr(self, identifier):
|
||||
print("Property Registry Error: Unknown Identifier <" + identifier + ", " + path + ">")
|
||||
|
||||
|
||||
def get_target_object(self):
|
||||
obj = None
|
||||
try:
|
||||
all_objects = vcu.get_all_scene_objects()
|
||||
obj = self.target_object
|
||||
obj = all_objects.get(obj.name)
|
||||
except:
|
||||
pass
|
||||
return obj
|
||||
|
||||
|
||||
def is_target_valid(self):
|
||||
return (self.fluid_velocity_mode == 'FLUID_VELOCITY_TARGET' and
|
||||
self.get_target_object() is not None)
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self.initialize()
|
||||
|
||||
|
||||
def load_post():
|
||||
fluid_objects = bpy.context.scene.flip_fluid.get_fluid_objects()
|
||||
for fluid in fluid_objects:
|
||||
fluid.flip_fluid.fluid.load_post()
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(FlipFluidFluidProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(FlipFluidFluidProperties)
|
||||
@@ -0,0 +1,360 @@
|
||||
# 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 bpy.props import (
|
||||
BoolProperty,
|
||||
FloatProperty,
|
||||
PointerProperty,
|
||||
EnumProperty
|
||||
)
|
||||
|
||||
from .custom_properties import (
|
||||
NewMinMaxFloatProperty
|
||||
)
|
||||
|
||||
from . import preset_properties
|
||||
from .. import types
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
class FlipFluidForceFieldProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
force_field_type = EnumProperty(
|
||||
name="Type",
|
||||
description="Type of force field",
|
||||
items=types.force_field_types,
|
||||
default='FORCE_FIELD_TYPE_POINT',
|
||||
update=lambda self, context: self._update_force_field_type(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("force_field_type"))
|
||||
is_enabled = BoolProperty(
|
||||
name="Enabled",
|
||||
description="Force field is active in the fluid simulation",
|
||||
default=True,
|
||||
); exec(conv("is_enabled"))
|
||||
strength = FloatProperty(
|
||||
name="Strength",
|
||||
description="Strength of the force field. A negative value pulls fluid in,"
|
||||
" a positive value pushes fluid away",
|
||||
default=-9.81,
|
||||
precision=2,
|
||||
); exec(conv("strength"))
|
||||
falloff_power = FloatProperty(
|
||||
name="Falloff Power",
|
||||
description="How quickly force strength decreases with distance. If "
|
||||
" r is the distance from the force object, the force strength changes"
|
||||
" with (1 / r^power). A value of 0 = constant force, 1 = linear falloff,"
|
||||
" 2 = gravitational falloff",
|
||||
default=1.0,
|
||||
min=0.0,
|
||||
soft_max=3.0, max=6.0,
|
||||
precision=2,
|
||||
); exec(conv("falloff_power"))
|
||||
enable_min_distance = BoolProperty(
|
||||
name="Enable Min Distance",
|
||||
description="Use a minimum distance for the force field falloff",
|
||||
default=False,
|
||||
); exec(conv("enable_min_distance"))
|
||||
enable_max_distance = BoolProperty(
|
||||
name="Enable Max Distance",
|
||||
description="Use a maximum distance for the force field to work",
|
||||
default=False,
|
||||
); exec(conv("enable_max_distance"))
|
||||
min_max_distance = NewMinMaxFloatProperty(
|
||||
name_min="Min Distance",
|
||||
description_min="The distance from the force object at which the strength"
|
||||
" begins to falloff",
|
||||
min_min=0,
|
||||
default_min=0.0,
|
||||
precision_min=3,
|
||||
|
||||
name_max="Max Distance",
|
||||
description_max="Maximum distance from the force object that the force"
|
||||
" field will have an effect on the simulation. Limiting max distance"
|
||||
" can help speed up force field calculations",
|
||||
min_max=0,
|
||||
default_max=0.0,
|
||||
precision_max=3,
|
||||
); exec(conv("min_max_distance"))
|
||||
maximum_force_limit_factor = FloatProperty(
|
||||
name="Max Force Limit Factor",
|
||||
description="The maximum force in the field will be limited to the Strength"
|
||||
" multiplied by this value",
|
||||
default=3.0,
|
||||
min=0.0,
|
||||
soft_max=10.0,
|
||||
precision=2,
|
||||
); exec(conv("maximum_force_limit_factor"))
|
||||
export_animated_mesh = bpy.props.BoolProperty(
|
||||
name="Export Animated Mesh",
|
||||
description="Export this object as an animated mesh. Exporting animated meshes are"
|
||||
" slower, only use when necessary. This option is required for any animation that"
|
||||
" is more complex than just keyframed loc/rot/scale or F-Curves, such as parented"
|
||||
" relations, armatures, animated modifiers, deformable meshes, etc. This option is"
|
||||
" not needed for static objects",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("export_animated_mesh"))
|
||||
skip_reexport = BoolProperty(
|
||||
name="Skip re-export",
|
||||
description="Skip re-exporting this mesh when starting or resuming"
|
||||
" a bake. If this mesh has not been exported or is missing files,"
|
||||
" the addon will automatically export the required files",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("skip_reexport"))
|
||||
force_reexport_on_next_bake = BoolProperty(
|
||||
name="Force Re-Export On Next Bake",
|
||||
description="Override the 'Skip Re-Export' option and force this mesh to be"
|
||||
" re-exported and updated on the next time a simulation start/resumes"
|
||||
" baking. Afting starting/resuming the baking process, this option"
|
||||
" will automatically be disabled once the object has been fully exported."
|
||||
" This option is only applicable if 'Skip Re-Export' is enabled",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("force_reexport_on_next_bake"))
|
||||
|
||||
maximum_strength_tooltip = BoolProperty(
|
||||
name="Maximum Force",
|
||||
description="This value estimates the maximum possible force field strength"
|
||||
" generated by this object. Force field strengths are inversely proportional"
|
||||
" to distance and can become very large as distances decrease. Use the Max"
|
||||
" Force Limit Factor to reduce the maximum force. For reference, a force strength"
|
||||
" value of 9.81 is equal to the default strength of gravity",
|
||||
default=True,
|
||||
); exec(conv("maximum_strength_tooltip"))
|
||||
|
||||
|
||||
#
|
||||
# Properties for specific force field type
|
||||
#
|
||||
|
||||
# Point Force Field
|
||||
falloff_shape = EnumProperty(
|
||||
name="Falloff Shape",
|
||||
description="(Placeholder, TODO) Specifies the shape of the force field. Only takes"
|
||||
" effect if the Falloff Power is greater than 0",
|
||||
items=types.force_field_falloff_shapes,
|
||||
default='FORCE_FIELD_FALLOFF_SPHERE',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("falloff_shape"))
|
||||
gravity_scale_point = FloatProperty(
|
||||
name="Gravity Scale",
|
||||
description="Scale the force of gravity around this point by this value. A scale"
|
||||
" of 0.0 is zero gravity, a scale of 1.0 is full gravity",
|
||||
default=1.0,
|
||||
soft_min=0.0, soft_max=1.0,
|
||||
precision=2,
|
||||
); exec(conv("gravity_scale_point"))
|
||||
gravity_scale_width_point = FloatProperty(
|
||||
name="Gravity Scale Width",
|
||||
description="The distance around this point that gravity scaling will take effect",
|
||||
default=1.0,
|
||||
min=0.0, soft_max=5.0,
|
||||
precision=2,
|
||||
); exec(conv("gravity_scale_width_point"))
|
||||
|
||||
# Surface Force Field
|
||||
enable_frontfacing = BoolProperty(
|
||||
name="Front",
|
||||
description="Enable force field on the front-facing side of the surface."
|
||||
" This is the side where face normals point outwards",
|
||||
default=True,
|
||||
); exec(conv("enable_frontfacing"))
|
||||
enable_backfacing = BoolProperty(
|
||||
name="Back",
|
||||
description="Enable force field on back-facing polygons."
|
||||
" This is the side opposite of the face normal",
|
||||
default=True,
|
||||
); exec(conv("enable_backfacing"))
|
||||
enable_edgefacing = BoolProperty(
|
||||
name="Edge",
|
||||
description="Enable force field on planar edges."
|
||||
" These are edges of the object that are not connected to any other polygons."
|
||||
" Must have at lease one of Front or Back sides enabled",
|
||||
default=True,
|
||||
); exec(conv("enable_edgefacing"))
|
||||
gravity_scale_surface = FloatProperty(
|
||||
name="Gravity Scale",
|
||||
description="Scale the force of gravity near the surface by this value. A scale"
|
||||
" of 0.0 is zero gravity, a scale of 1.0 is full gravity",
|
||||
default=1.0,
|
||||
soft_min=0.0, soft_max=1.0,
|
||||
precision=2,
|
||||
); exec(conv("gravity_scale_surface"))
|
||||
gravity_scale_width_surface = FloatProperty(
|
||||
name="Gravity Scale Width",
|
||||
description="The distance from the surface that gravity scaling will take effect",
|
||||
default=1.0,
|
||||
min=0.0, soft_max=5.0,
|
||||
precision=2,
|
||||
); exec(conv("gravity_scale_width_surface"))
|
||||
|
||||
# Volume Force Field
|
||||
gravity_scale_volume = FloatProperty(
|
||||
name="Gravity Scale",
|
||||
description="Scale the force of gravity inside the volume by this value. A scale"
|
||||
" of 0.0 is zero gravity, a scale of 1.0 is full gravity",
|
||||
default=1.0,
|
||||
soft_min=0.0, soft_max=1.0,
|
||||
precision=2,
|
||||
); exec(conv("gravity_scale_volume"))
|
||||
gravity_scale_width_volume = FloatProperty(
|
||||
name="Gravity Scale Width",
|
||||
description="The distance from the outside of the volume's surface that gravity"
|
||||
" scaling will take effect",
|
||||
default=0.0,
|
||||
min=0.0, soft_max=5.0,
|
||||
precision=2,
|
||||
); exec(conv("gravity_scale_width_volume"))
|
||||
|
||||
# Curve Force Field
|
||||
flow_strength = FloatProperty(
|
||||
name="Flow Strength",
|
||||
description="Strength of the flow along the direction of the curve. The curve direction"
|
||||
" is in the vertex order of the Blender Curve object. A negative value will reverse"
|
||||
" the direction",
|
||||
default=0.0,
|
||||
precision=2,
|
||||
); exec(conv("flow_strength"))
|
||||
spin_strength = FloatProperty(
|
||||
name="Spin Strength",
|
||||
description="Strength of the the force that directs fluid to spin around the curve. A positive"
|
||||
" strength uses the 'Right Hand Rule:' take your right hand and point your thumb in"
|
||||
" the direction of the curve (first vertex to last vertex). Curling your rght hand fingers"
|
||||
" around the curve will be the direction of spin. A negative strength will reverse the spin"
|
||||
" direction",
|
||||
default=0.0,
|
||||
precision=2,
|
||||
); exec(conv("spin_strength"))
|
||||
gravity_scale_curve = FloatProperty(
|
||||
name="Gravity Scale",
|
||||
description="Scale the force of gravity near the curve by this value. A scale"
|
||||
" of 0.0 is zero gravity, a scale of 1.0 is full gravity",
|
||||
default=1.0,
|
||||
soft_min=0.0, soft_max=1.0,
|
||||
precision=2,
|
||||
); exec(conv("gravity_scale_curve"))
|
||||
gravity_scale_width_curve = FloatProperty(
|
||||
name="Gravity Scale Width",
|
||||
description="The distance from the curve that gravity"
|
||||
" scaling will take effect",
|
||||
default=1.0,
|
||||
min=0.0, soft_max=5.0,
|
||||
precision=2,
|
||||
); exec(conv("gravity_scale_width_curve"))
|
||||
enable_endcaps = BoolProperty(
|
||||
name="Enable End Caps",
|
||||
description="Whether fluid is attracted towards the ends of the curve segment. Disable"
|
||||
" to allow fluid to flow past the ends of the curve segment",
|
||||
default=True,
|
||||
); exec(conv("enable_endcaps"))
|
||||
|
||||
|
||||
property_registry = PointerProperty(
|
||||
name="Outflow Property Registry",
|
||||
description="",
|
||||
type=preset_properties.PresetRegistry,
|
||||
); exec(conv("property_registry"))
|
||||
|
||||
|
||||
disabled_in_viewport_tooltip = BoolProperty(
|
||||
name="Object Disabled in Viewport",
|
||||
description="This force field object is currently disabled in the viewport within the"
|
||||
" outliner (Monitor Icon) and will not be included in the simulation. If you"
|
||||
" want the object hidden in the viewport, but still have the object included in the"
|
||||
" simulation, use the outliner Hide in Viewport option instead (Eye Icon)",
|
||||
default=True,
|
||||
); exec(conv("disabled_in_viewport_tooltip"))
|
||||
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def refresh_property_registry(self):
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def _initialize_property_registry(self):
|
||||
try:
|
||||
self.property_registry.clear()
|
||||
add = self.property_registry.add_property
|
||||
add("force_field.force_field_type", "")
|
||||
add("force_field.is_enabled", "")
|
||||
add("force_field.strength", "")
|
||||
add("force_field.flow_strength", "")
|
||||
add("force_field.spin_strength", "")
|
||||
add("force_field.enable_endcaps", "")
|
||||
add("force_field.falloff_power", "")
|
||||
add("force_field.falloff_shape", "")
|
||||
add("force_field.enable_min_distance", "")
|
||||
add("force_field.enable_max_distance", "")
|
||||
add("force_field.min_max_distance", "")
|
||||
add("force_field.maximum_force_limit_factor", "")
|
||||
add("force_field.gravity_scale_point", "")
|
||||
add("force_field.gravity_scale_surface", "")
|
||||
add("force_field.enable_frontfacing", "")
|
||||
add("force_field.enable_backfacing", "")
|
||||
add("force_field.enable_edgefacing", "")
|
||||
add("force_field.gravity_scale_volume", "")
|
||||
add("force_field.gravity_scale_curve", "")
|
||||
add("force_field.gravity_scale_width_point", "")
|
||||
add("force_field.gravity_scale_width_surface", "")
|
||||
add("force_field.gravity_scale_width_volume", "")
|
||||
add("force_field.gravity_scale_width_curve", "")
|
||||
add("force_field.export_animated_mesh", "")
|
||||
add("force_field.skip_reexport", "")
|
||||
add("force_field.force_reexport_on_next_bake", "")
|
||||
self._validate_property_registry()
|
||||
except:
|
||||
# Object is immutable if it is a linked library or library_override
|
||||
# In this case, pass on modifying the object
|
||||
pass
|
||||
|
||||
|
||||
def _validate_property_registry(self):
|
||||
for p in self.property_registry.properties:
|
||||
path = p.path
|
||||
base, identifier = path.split('.', 1)
|
||||
if not hasattr(self, identifier):
|
||||
print("Property Registry Error: Unknown Identifier <" +
|
||||
identifier + ", " + path + ">")
|
||||
|
||||
|
||||
def _update_force_field_type(self, context):
|
||||
pass
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self.initialize()
|
||||
|
||||
|
||||
def load_post():
|
||||
force_field_objects = bpy.context.scene.flip_fluid.get_force_field_objects()
|
||||
for force_field in force_field_objects:
|
||||
force_field.flip_fluid.force_field.load_post()
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(FlipFluidForceFieldProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(FlipFluidForceFieldProperties)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,301 @@
|
||||
# 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 bpy.props import (
|
||||
BoolProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
IntProperty,
|
||||
StringProperty,
|
||||
PointerProperty,
|
||||
EnumProperty,
|
||||
)
|
||||
|
||||
from . import preset_properties
|
||||
from .. import types
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
class FlipFluidInflowProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
is_enabled = BoolProperty(
|
||||
name="Enabled",
|
||||
description="Inflow emits fluid into the domain. Tip: keyframe this option on/off to start and stop inflow emission",
|
||||
default=True,
|
||||
); exec(conv("is_enabled"))
|
||||
substep_emissions = IntProperty(
|
||||
name="Substep Emissions",
|
||||
description="Number of times fluid is emitted from the inflow"
|
||||
" per simulation substep. Increase to reduce stuttering"
|
||||
" fluid artifacts with fast moving keyframed/animated inflow"
|
||||
" objects. If set to 0, the inflow will only emit on the first"
|
||||
" substep of a frame",
|
||||
min=0, soft_max=8,
|
||||
default=1,
|
||||
); exec(conv("substep_emissions"))
|
||||
inflow_velocity = FloatVectorProperty(
|
||||
name="Inflow Velocity",
|
||||
description="Initial velocity of fluid (m/s)",
|
||||
default=(0.0, 0.0, 0.0),
|
||||
subtype='VELOCITY',
|
||||
precision=3,
|
||||
size=3,
|
||||
); exec(conv("inflow_velocity"))
|
||||
append_object_velocity = BoolProperty(
|
||||
name="Add Object Velocity to Infow",
|
||||
description="Add the velocity of the object to the inflow fluid"
|
||||
" velocity",
|
||||
default=False,
|
||||
); exec(conv("append_object_velocity"))
|
||||
append_object_velocity_influence = FloatProperty(
|
||||
name="Influence",
|
||||
description="Amount of velocity that is added to the inflow fluid."
|
||||
" A value of 1.0 is normal, less than 1.0 will dampen the"
|
||||
" velocity, greater than 1.0 will exaggerate the velocity,"
|
||||
" negative values will reverse velocity direction",
|
||||
subtype='FACTOR',
|
||||
soft_min=0.0, soft_max=1.0,
|
||||
default=1.0,
|
||||
precision=2,
|
||||
); exec(conv("append_object_velocity_influence"))
|
||||
priority = IntProperty(
|
||||
name="Priority",
|
||||
description="Priority that this fluid object is added to the simulation"
|
||||
" during a frame. If multiple fluid/inflow objects are adding fluid"
|
||||
" to the simulation during a frame, the object with the higher priority"
|
||||
" value will be added first. Use to control which fluid attributes are"
|
||||
" prioritized and set first such as with intersecting objects or nested"
|
||||
" objects",
|
||||
min=-1000000, max=1000000,
|
||||
default=0,
|
||||
); exec(conv("priority"))
|
||||
inflow_velocity_mode = EnumProperty(
|
||||
name="Velocity Mode",
|
||||
description="Set how the inflow fluid velocity is calculated",
|
||||
items=types.inflow_velocity_modes,
|
||||
default='INFLOW_VELOCITY_MANUAL',
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("inflow_velocity_mode"))
|
||||
inflow_speed = FloatProperty(
|
||||
name="Speed",
|
||||
description="Initial speed of fluid towards target (m/s)",
|
||||
default=0.0,
|
||||
precision=3,
|
||||
); exec(conv("inflow_speed"))
|
||||
inflow_axis_mode = EnumProperty(
|
||||
name="Local Axis",
|
||||
description="Set local axis direction of fluid",
|
||||
items=types.local_axis_directions,
|
||||
default='LOCAL_AXIS_POS_X',
|
||||
); exec(conv("inflow_axis_mode"))
|
||||
target_object = PointerProperty(
|
||||
name="Target Object",
|
||||
type=bpy.types.Object
|
||||
); exec(conv("target_object"))
|
||||
export_animated_target = BoolProperty(
|
||||
name="Export Animated Target",
|
||||
description="Export this object as an animated mesh. Exporting animated meshes are"
|
||||
" slower, only use when necessary. This option is required for any animation that"
|
||||
" is more complex than just keyframed loc/rot/scale or F-Curves, such as parented"
|
||||
" relations, armatures, animated modifiers, deformable meshes, etc. This option is"
|
||||
" not needed for static objects",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("export_animated_target"))
|
||||
constrain_fluid_velocity = BoolProperty(
|
||||
name="Constrain Fluid Velocity",
|
||||
description="Force fluid inside of the inflow to match the inflow" +
|
||||
" emission velocity. If enabled, the inflow will continue to" +
|
||||
" push around fluid when submerged. Setting low inflow velocity" +
|
||||
" values will have the effect of slowing down fluid emission",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("constrain_fluid_velocity"))
|
||||
source_id = IntProperty(
|
||||
name="Source ID Attribute",
|
||||
description="Assign this identifier value to the fluid generated by this inflow. After"
|
||||
" baking, the source ID attribute values can be accessed in a Cycles Attribute Node"
|
||||
" with the name 'flip_source_id' from the Fac output. This can be used to create"
|
||||
" basic multiple material liquid effects. Enable this feature in the Domain FLIP"
|
||||
" Fluid Surface panel",
|
||||
min=0, soft_max=16,
|
||||
default=0,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("source_id"))
|
||||
viscosity = FloatProperty(
|
||||
name="Viscosity Attribute",
|
||||
description="Assign this viscosity value to the fluid generated by this object. After"
|
||||
" baking, the viscosity attribute values can be accessed in a Cycles Attribute Node"
|
||||
" with the name 'flip_viscosity' from the Fac output. This feature can be used to create"
|
||||
" variable viscosity liquid effects. Enable this viscosity feature in the Domain FLIP"
|
||||
" Fluid World panel",
|
||||
min=0.0,
|
||||
default=0.0,
|
||||
); exec(conv("viscosity"))
|
||||
lifetime = FloatProperty(
|
||||
name="Lifetime Attribute",
|
||||
description="Assign this starting lifetime value to the fluid generated by this object."
|
||||
" This value is the amount of time remaining (in seconds) before the fluid is removed from the"
|
||||
" simulation. After baking, the lifetime attribute values can be accessed in a Cycles Attribute Node"
|
||||
" with the name 'flip_lifetime' from the Fac output. Enable this feature in the Domain FLIP"
|
||||
" Fluid Surface panel",
|
||||
min=0.0,
|
||||
default=1000.0,
|
||||
); exec(conv("lifetime"))
|
||||
lifetime_variance = FloatProperty(
|
||||
name="Lifetime Variance",
|
||||
description="Add or subtract a random value in seconds to the starting lifetime within the range of this variance value",
|
||||
min=0.0,
|
||||
default=0.0,
|
||||
); exec(conv("lifetime_variance"))
|
||||
color = FloatVectorProperty(
|
||||
name="Color Attribute",
|
||||
description="Assign this color to the fluid generated by this object. After"
|
||||
" baking, the color attribute values can be accessed in a Cycles Attribute Node"
|
||||
" with the name 'flip_color' from the Color output. This can be used to create"
|
||||
" basic varying color liquid effects. Enable this feature in the Domain FLIP"
|
||||
" Fluid Surface panel",
|
||||
default=(1.0, 1.0, 1.0),
|
||||
min=0.0, max=1.0,
|
||||
size=3,
|
||||
precision=3,
|
||||
subtype='COLOR',
|
||||
); exec(conv("color"))
|
||||
export_animated_mesh = BoolProperty(
|
||||
name="Export Animated Mesh",
|
||||
description="Export this mesh as an animated one (slower, only use"
|
||||
" if really necessary [e.g. armatures or parented objects],"
|
||||
" animated pos/rot/scale F-curves do not require it",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("export_animated_mesh"))
|
||||
skip_reexport = BoolProperty(
|
||||
name="Skip re-export",
|
||||
description="Skip re-exporting this mesh when starting or resuming"
|
||||
" a bake. If this mesh has not been exported or is missing files,"
|
||||
" the addon will automatically export the required files",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("skip_reexport"))
|
||||
force_reexport_on_next_bake = BoolProperty(
|
||||
name="Force Re-Export On Next Bake",
|
||||
description="Override the 'Skip Re-Export' option and force this mesh to be"
|
||||
" re-exported and updated on the next time a simulation start/resumes"
|
||||
" baking. Afting starting/resuming the baking process, this option"
|
||||
" will automatically be disabled once the object has been fully exported."
|
||||
" This option is only applicable if 'Skip Re-Export' is enabled",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("force_reexport_on_next_bake"))
|
||||
property_registry = PointerProperty(
|
||||
name="Inflow Property Registry",
|
||||
description="",
|
||||
type=preset_properties.PresetRegistry,
|
||||
); exec(conv("property_registry"))
|
||||
|
||||
|
||||
disabled_in_viewport_tooltip = BoolProperty(
|
||||
name="Object Disabled in Viewport",
|
||||
description="This inflow object is currently disabled in the viewport within the"
|
||||
" outliner (Monitor Icon) and will not be included in the simulation. If you"
|
||||
" want the object hidden in the viewport, but still have the object included in the"
|
||||
" simulation, use the outliner Hide in Viewport option instead (Eye Icon)",
|
||||
default=True,
|
||||
); exec(conv("disabled_in_viewport_tooltip"))
|
||||
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def refresh_property_registry(self):
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def _initialize_property_registry(self):
|
||||
try:
|
||||
self.property_registry.clear()
|
||||
add = self.property_registry.add_property
|
||||
add("inflow.is_enabled", "")
|
||||
add("inflow.substep_emissions", "")
|
||||
add("inflow.inflow_velocity_mode", "")
|
||||
add("inflow.inflow_velocity", "")
|
||||
add("inflow.append_object_velocity", "")
|
||||
add("inflow.append_object_velocity_influence", "")
|
||||
add("inflow.priority", "")
|
||||
add("inflow.constrain_fluid_velocity", "")
|
||||
add("inflow.inflow_speed", "")
|
||||
add("inflow.inflow_axis_mode", "")
|
||||
add("inflow.source_id", "")
|
||||
add("inflow.viscosity", "")
|
||||
add("inflow.lifetime", "")
|
||||
add("inflow.lifetime_variance", "")
|
||||
add("inflow.color", "")
|
||||
add("inflow.target_object", "")
|
||||
add("inflow.export_animated_target", "")
|
||||
add("inflow.export_animated_mesh", "")
|
||||
add("inflow.skip_reexport", "")
|
||||
add("inflow.force_reexport_on_next_bake", "")
|
||||
self._validate_property_registry()
|
||||
except:
|
||||
# Object is immutable if it is a linked library or library_override
|
||||
# In this case, pass on modifying the object
|
||||
pass
|
||||
|
||||
|
||||
def _validate_property_registry(self):
|
||||
for p in self.property_registry.properties:
|
||||
path = p.path
|
||||
base, identifier = path.split('.', 1)
|
||||
if not hasattr(self, identifier):
|
||||
print("Property Registry Error: Unknown Identifier <" + identifier + ", " + path + ">")
|
||||
|
||||
|
||||
def get_target_object(self):
|
||||
obj = None
|
||||
try:
|
||||
all_objects = vcu.get_all_scene_objects()
|
||||
obj = self.target_object
|
||||
obj = all_objects.get(obj.name)
|
||||
except:
|
||||
pass
|
||||
return obj
|
||||
|
||||
|
||||
def is_target_valid(self):
|
||||
return (self.inflow_velocity_mode == 'INFLOW_VELOCITY_TARGET' and
|
||||
self.get_target_object() is not None)
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self.initialize()
|
||||
|
||||
|
||||
def load_post():
|
||||
inflow_objects = bpy.context.scene.flip_fluid.get_inflow_objects()
|
||||
for inflow in inflow_objects:
|
||||
inflow.flip_fluid.inflow.load_post()
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(FlipFluidInflowProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(FlipFluidInflowProperties)
|
||||
@@ -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/>.
|
||||
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
StringProperty,
|
||||
PointerProperty
|
||||
)
|
||||
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
class FlipFluidMaterialLibraryProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
# Material Library Data
|
||||
is_library_material = BoolProperty(default=False); exec(conv("is_library_material"))
|
||||
library_name = StringProperty(default=""); exec(conv("library_name"))
|
||||
imported_name = StringProperty(default=""); exec(conv("imported_name"))
|
||||
data_block_id = StringProperty(default="-1"); exec(conv("data_block_id"))
|
||||
|
||||
# Preset Library Data
|
||||
is_preset_material = BoolProperty(default=False); exec(conv("is_preset_material"))
|
||||
preset_identifier = StringProperty(default=""); exec(conv("preset_identifier"))
|
||||
preset_blend_identifier = StringProperty(default=""); exec(conv("preset_blend_identifier"))
|
||||
is_fake_user_set_by_addon = BoolProperty(default=False); exec(conv("is_fake_user_set_by_addon"))
|
||||
skip_preset_unload = BoolProperty(default=False); exec(conv("skip_preset_unload"))
|
||||
|
||||
|
||||
@classmethod
|
||||
def register(cls):
|
||||
bpy.types.Material.flip_fluid_material_library = PointerProperty(
|
||||
name="Flip Fluid Material Library Properties",
|
||||
description="",
|
||||
type=cls,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def unregister(cls):
|
||||
del bpy.types.Material.flip_fluid_material_library
|
||||
|
||||
|
||||
def activate(self, material_object, library_name):
|
||||
self.is_library_material = True
|
||||
self.library_name = library_name
|
||||
self.imported_name = material_object.name
|
||||
self.data_block_id = str(material_object.as_pointer())
|
||||
|
||||
|
||||
def deactivate(self):
|
||||
self.property_unset("is_library_material")
|
||||
self.property_unset("library_name")
|
||||
self.property_unset("imported_name")
|
||||
self.property_unset("data_block_id")
|
||||
|
||||
|
||||
def reinitialize_data_block_id(self, material_object):
|
||||
self.data_block_id = str(material_object.as_pointer())
|
||||
|
||||
|
||||
def is_original_data_block(self, material_object):
|
||||
return self.data_block_id == str(material_object.as_pointer())
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(FlipFluidMaterialLibraryProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(FlipFluidMaterialLibraryProperties)
|
||||
@@ -0,0 +1,624 @@
|
||||
# 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 = [
|
||||
'types',
|
||||
'fluid_properties',
|
||||
'obstacle_properties',
|
||||
'inflow_properties',
|
||||
'outflow_properties',
|
||||
'force_field_properties',
|
||||
'domain_properties',
|
||||
]
|
||||
for module_name in reloadable_modules:
|
||||
if module_name in locals():
|
||||
importlib.reload(locals()[module_name])
|
||||
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
BoolVectorProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
PointerProperty,
|
||||
StringProperty
|
||||
)
|
||||
|
||||
from . import (
|
||||
domain_properties,
|
||||
fluid_properties,
|
||||
obstacle_properties,
|
||||
inflow_properties,
|
||||
outflow_properties,
|
||||
force_field_properties
|
||||
)
|
||||
from .. import types
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
from ..utils import api_workaround_utils
|
||||
|
||||
|
||||
class ObjectViewSettings(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
hide_render = BoolProperty(default=False); exec(conv("hide_render"))
|
||||
show_name = BoolProperty(default=False); exec(conv("show_name"))
|
||||
draw_type = StringProperty(default=""); exec(conv("draw_type"))
|
||||
layers = BoolVectorProperty(size=20); exec(conv("layers"))
|
||||
|
||||
|
||||
class FlipFluidObjectProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
domain = PointerProperty(
|
||||
name="Flip Fluid Domain Properties",
|
||||
description="",
|
||||
type=domain_properties.FlipFluidDomainProperties,
|
||||
); exec(conv("domain"))
|
||||
fluid = PointerProperty(
|
||||
name="Flip Fluid Fluid Properties",
|
||||
description="",
|
||||
type=fluid_properties.FlipFluidFluidProperties,
|
||||
); exec(conv("fluid"))
|
||||
obstacle = PointerProperty(
|
||||
name="Flip Fluid Obstacle Properties",
|
||||
description="",
|
||||
type=obstacle_properties.FlipFluidObstacleProperties,
|
||||
); exec(conv("obstacle"))
|
||||
inflow = PointerProperty(
|
||||
name="Flip Fluid Inflow Properties",
|
||||
description="",
|
||||
type=inflow_properties.FlipFluidInflowProperties,
|
||||
); exec(conv("inflow"))
|
||||
outflow = PointerProperty(
|
||||
name="Flip Fluid Outflow Properties",
|
||||
description="",
|
||||
type=outflow_properties.FlipFluidOutflowProperties,
|
||||
); exec(conv("outflow"))
|
||||
force_field = PointerProperty(
|
||||
name="Flip Fluid Force Field Properties",
|
||||
description="",
|
||||
type=force_field_properties.FlipFluidForceFieldProperties,
|
||||
); exec(conv("force_field"))
|
||||
object_type = EnumProperty(
|
||||
name="Type",
|
||||
description="Type of participation in the FLIP fluid simulation",
|
||||
items=types.object_types,
|
||||
get=lambda self: self._get_object_type(),
|
||||
set=lambda self, value: self._set_object_type(value),
|
||||
update=lambda self, context: self._update_object_type(context),
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("object_type"))
|
||||
saved_view_settings = PointerProperty(
|
||||
name="Saved View Settings",
|
||||
description="",
|
||||
type=ObjectViewSettings,
|
||||
); exec(conv("saved_view_settings"))
|
||||
|
||||
is_active = BoolProperty(default=False); exec(conv("is_active"))
|
||||
is_view_settings_saved = BoolProperty(default=False); exec(conv("is_view_settings_saved"))
|
||||
last_hide_render_state = BoolProperty(default=False); exec(conv("last_hide_render_state"))
|
||||
|
||||
|
||||
@classmethod
|
||||
def register(cls):
|
||||
bpy.types.Object.flip_fluid = PointerProperty(
|
||||
name="Flip Fluid Object Properties",
|
||||
description="",
|
||||
type=cls,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def unregister(cls):
|
||||
del bpy.types.Object.flip_fluid
|
||||
|
||||
|
||||
def scene_update_post(self, scene, bl_object):
|
||||
if bl_object.hide_render == self.last_hide_render_state:
|
||||
return
|
||||
self._toggle_cycles_ray_visibility(bl_object, not bl_object.hide_render)
|
||||
self.last_hide_render_state = bl_object.hide_render
|
||||
|
||||
# In some versions of Blender the viewport rendered view is
|
||||
# not updated to reflect the above 'hide_render' change. Toggling
|
||||
# the hide_viewport option on and off is a workaround to get the viewport
|
||||
# to update.
|
||||
api_workaround_utils.toggle_viewport_visibility_to_update_rendered_viewport_workaround(bl_object)
|
||||
|
||||
|
||||
def get_object_type():
|
||||
return self.object_type
|
||||
|
||||
|
||||
def is_none(self):
|
||||
return self.object_type == 'TYPE_NONE'
|
||||
|
||||
|
||||
def is_domain(self):
|
||||
return self.is_active and self.object_type == 'TYPE_DOMAIN'
|
||||
|
||||
|
||||
def is_fluid(self):
|
||||
return self.is_active and self.object_type == 'TYPE_FLUID'
|
||||
|
||||
|
||||
def is_obstacle(self):
|
||||
return self.is_active and self.object_type == 'TYPE_OBSTACLE'
|
||||
|
||||
|
||||
def is_inflow(self):
|
||||
return self.is_active and self.object_type == 'TYPE_INFLOW'
|
||||
|
||||
|
||||
def is_outflow(self):
|
||||
return self.is_active and self.object_type == 'TYPE_OUTFLOW'
|
||||
|
||||
|
||||
def is_force_field(self):
|
||||
return self.is_active and self.object_type == 'TYPE_FORCE_FIELD'
|
||||
|
||||
|
||||
def get_property_group(self):
|
||||
if self.is_domain():
|
||||
return self.domain
|
||||
if self.is_fluid():
|
||||
return self.fluid
|
||||
if self.is_obstacle():
|
||||
return self.obstacle
|
||||
if self.is_inflow():
|
||||
return self.inflow
|
||||
if self.is_outflow():
|
||||
return self.outflow
|
||||
if self.is_force_field():
|
||||
return self.force_field
|
||||
|
||||
|
||||
def _get_object_type(self):
|
||||
try:
|
||||
return self["object_type"]
|
||||
except:
|
||||
return 0
|
||||
|
||||
|
||||
def _toggle_cycles_ray_visibility(self, obj, is_enabled):
|
||||
# Cycles may not be enabled in the user's preferences
|
||||
try:
|
||||
if vcu.is_blender_30():
|
||||
obj.visible_camera = is_enabled
|
||||
obj.visible_diffuse = is_enabled
|
||||
obj.visible_glossy = is_enabled
|
||||
obj.visible_transmission = is_enabled
|
||||
obj.visible_volume_scatter = is_enabled
|
||||
obj.visible_shadow = is_enabled
|
||||
else:
|
||||
obj.cycles_visibility.camera = is_enabled
|
||||
obj.cycles_visibility.transmission = is_enabled
|
||||
obj.cycles_visibility.diffuse = is_enabled
|
||||
obj.cycles_visibility.scatter = is_enabled
|
||||
obj.cycles_visibility.glossy = is_enabled
|
||||
obj.cycles_visibility.shadow = is_enabled
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def _lock_interface(self):
|
||||
# Should only be executed upon creation of a domain object in Blender 2.8x.
|
||||
# Locking the Blender interface is necessary to prevent crashes in Blender >= v2.81
|
||||
# and helps prevent crashes in Blender 2.80
|
||||
if not vcu.is_blender_28():
|
||||
return
|
||||
bpy.context.scene.render.use_lock_interface = True
|
||||
|
||||
|
||||
def _set_object_type(self, value):
|
||||
oldtype = self.object_type
|
||||
self['object_type'] = value
|
||||
|
||||
if value == 0:
|
||||
newtype = 'TYPE_NONE'
|
||||
elif value == 1:
|
||||
newtype = 'TYPE_DOMAIN'
|
||||
elif value == 2:
|
||||
newtype = 'TYPE_FLUID'
|
||||
elif value == 3:
|
||||
newtype = 'TYPE_OBSTACLE'
|
||||
elif value == 4:
|
||||
newtype = 'TYPE_INFLOW'
|
||||
elif value == 5:
|
||||
newtype = 'TYPE_OUTFLOW'
|
||||
elif value == 6:
|
||||
newtype = 'TYPE_FORCE_FIELD'
|
||||
else:
|
||||
newtype = 'TYPE_NONE'
|
||||
|
||||
active_object = vcu.get_active_object()
|
||||
if oldtype == 'TYPE_NONE' and newtype != 'TYPE_NONE':
|
||||
self._save_object_view_settings(active_object)
|
||||
if oldtype != 'TYPE_NONE' and newtype == 'TYPE_NONE':
|
||||
self._reset_object_view_settings(active_object)
|
||||
self._toggle_cycles_ray_visibility(active_object, True)
|
||||
|
||||
if oldtype != 'TYPE_DOMAIN' and newtype == 'TYPE_DOMAIN':
|
||||
if bpy.context.scene.flip_fluid.get_num_domain_objects() <= 1:
|
||||
active_object.flip_fluid.domain.initialize()
|
||||
active_object.lock_rotation = (True, True, True)
|
||||
self._toggle_cycles_ray_visibility(active_object, False)
|
||||
self._lock_interface()
|
||||
if oldtype == 'TYPE_DOMAIN' and newtype != 'TYPE_DOMAIN':
|
||||
active_object.lock_rotation = (False, False, False)
|
||||
active_object.flip_fluid.domain.destroy()
|
||||
|
||||
if newtype != oldtype:
|
||||
if newtype == 'TYPE_FLUID':
|
||||
active_object.flip_fluid.fluid.initialize()
|
||||
self._toggle_cycles_ray_visibility(active_object, False)
|
||||
if newtype == 'TYPE_OBSTACLE':
|
||||
active_object.flip_fluid.obstacle.initialize()
|
||||
self._toggle_cycles_ray_visibility(active_object, True)
|
||||
if newtype == 'TYPE_INFLOW':
|
||||
active_object.flip_fluid.inflow.initialize()
|
||||
self._toggle_cycles_ray_visibility(active_object, False)
|
||||
if newtype == 'TYPE_OUTFLOW':
|
||||
active_object.flip_fluid.outflow.initialize()
|
||||
self._toggle_cycles_ray_visibility(active_object, False)
|
||||
if newtype == 'TYPE_FORCE_FIELD':
|
||||
active_object.flip_fluid.force_field.initialize()
|
||||
self._toggle_cycles_ray_visibility(active_object, False)
|
||||
|
||||
|
||||
def _update_object_type(self, context):
|
||||
obj = vcu.get_active_object(context)
|
||||
primary_layer = 0
|
||||
object_layer = 14
|
||||
|
||||
if self.object_type == 'TYPE_DOMAIN':
|
||||
if bpy.context.scene.flip_fluid.get_num_domain_objects() > 1:
|
||||
self.object_type = 'TYPE_NONE'
|
||||
errmsg = "Only 1 Domain object is supported per Blend file."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description="",
|
||||
popup_width=600
|
||||
)
|
||||
return
|
||||
|
||||
if (obj.type == 'EMPTY' or obj.type == 'CURVE') and self.object_type != 'TYPE_FORCE_FIELD' and self.object_type != 'TYPE_NONE':
|
||||
flip_type = ""
|
||||
if self.object_type == 'TYPE_DOMAIN':
|
||||
flip_type = "Domain"
|
||||
elif self.object_type == 'TYPE_FLUID':
|
||||
flip_type = "Fluid"
|
||||
elif self.object_type == 'TYPE_OBSTACLE':
|
||||
flip_type = "Obstacle"
|
||||
elif self.object_type == 'TYPE_INFLOW':
|
||||
flip_type = "Inflow"
|
||||
elif self.object_type == 'TYPE_OUTFLOW':
|
||||
flip_type = "Outflow"
|
||||
|
||||
self.object_type = 'TYPE_NONE'
|
||||
|
||||
if obj.type == 'EMPTY':
|
||||
errmsg = "Empty type objects cannot be set as a FLIP Fluid " + flip_type + "."
|
||||
errdesc = "Empty type objects are only supported as a FLIP Fluid Force Field."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
elif obj.type == 'CURVE':
|
||||
errmsg = "Curve type objects cannot be set as a FLIP Fluid " + flip_type + "."
|
||||
errdesc = "Curve type objects are only supported as a FLIP Fluid Force Field > Curve Guide Force."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
if obj.type == 'FONT' and self.object_type != 'TYPE_NONE':
|
||||
errmsg = "Text type objects are not supported."
|
||||
errdesc = "Text type objects are not supported. Please convert to a mesh object before setting as FLIP Fluid object."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
self.object_type = 'TYPE_NONE'
|
||||
return
|
||||
|
||||
if obj.type == 'META' and self.object_type != 'TYPE_NONE':
|
||||
errmsg = "Metaball type objects are not supported."
|
||||
errdesc = "Metaball type objects are not supported. Please convert to a mesh object before setting as FLIP Fluid object."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
self.object_type = 'TYPE_NONE'
|
||||
return
|
||||
|
||||
if obj.type == 'VOLUME' and self.object_type != 'TYPE_NONE':
|
||||
errmsg = "Volume type objects are not supported."
|
||||
errdesc = "Volume type objects are not supported. Please convert to a mesh object before setting as FLIP Fluid object."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
self.object_type = 'TYPE_NONE'
|
||||
return
|
||||
|
||||
if obj.type == 'ARMATURE' and self.object_type != 'TYPE_NONE':
|
||||
errmsg = "Armature type objects are not supported."
|
||||
errdesc = "Armature type objects are not supported. Use the mesh associated with the armature before setting as FLIP Fluid object."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
self.object_type = 'TYPE_NONE'
|
||||
return
|
||||
|
||||
if obj.type == 'LIGHT' and self.object_type != 'TYPE_NONE':
|
||||
errmsg = "Light type objects are not supported."
|
||||
errdesc = "Light objects are not supported as a FLIP Fluid object type."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
self.object_type = 'TYPE_NONE'
|
||||
return
|
||||
|
||||
if obj.type == 'LIGHT_PROBE' and self.object_type != 'TYPE_NONE':
|
||||
errmsg = "Light Probe type objects are not supported."
|
||||
errdesc = "Light Probe objects are not supported as a FLIP Fluid object type."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
self.object_type = 'TYPE_NONE'
|
||||
return
|
||||
|
||||
if obj.type == 'GPENCIL' and self.object_type != 'TYPE_NONE':
|
||||
errmsg = "Grease Pencil type objects are not supported."
|
||||
errdesc = "Grease Pencil objects are not supported as a FLIP Fluid object type."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
self.object_type = 'TYPE_NONE'
|
||||
return
|
||||
|
||||
if obj.type == 'LATTICE' and self.object_type != 'TYPE_NONE':
|
||||
errmsg = "Lattice type objects are not supported."
|
||||
errdesc = "Lattice objects are not supported as a FLIP Fluid object type."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
self.object_type = 'TYPE_NONE'
|
||||
return
|
||||
|
||||
if obj.type == 'CAMERA' and self.object_type != 'TYPE_NONE':
|
||||
errmsg = "Camera type objects are not supported."
|
||||
errdesc = "Camera objects are not supported as a FLIP Fluid object type."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
self.object_type = 'TYPE_NONE'
|
||||
return
|
||||
|
||||
if obj.type == 'SPEAKER' and self.object_type != 'TYPE_NONE':
|
||||
errmsg = "Speaker type objects are not supported."
|
||||
errdesc = "Speaker objects are not supported as a FLIP Fluid object type."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message=errmsg,
|
||||
error_description=errdesc,
|
||||
popup_width=600
|
||||
)
|
||||
self.object_type = 'TYPE_NONE'
|
||||
return
|
||||
|
||||
|
||||
if self.object_type == 'TYPE_DOMAIN':
|
||||
obj.hide_render = True
|
||||
vcu.set_object_display_type(obj, 'BOUNDS')
|
||||
obj.show_name = True
|
||||
self._set_object_layer(obj, object_layer)
|
||||
self._set_scene_layer(context.scene, object_layer)
|
||||
|
||||
elif self.object_type == 'TYPE_FLUID':
|
||||
obj.hide_render = True
|
||||
vcu.set_object_display_type(obj, 'WIRE')
|
||||
obj.show_name = True
|
||||
self._set_object_layer(obj, object_layer)
|
||||
self._set_scene_layer(context.scene, object_layer)
|
||||
|
||||
elif self.object_type == 'TYPE_OBSTACLE':
|
||||
obj.hide_render = False
|
||||
vcu.set_object_display_type(obj, 'TEXTURED')
|
||||
obj.show_name = True
|
||||
self._set_object_layers(obj, [primary_layer, object_layer])
|
||||
self._set_scene_layer(context.scene, primary_layer)
|
||||
self._set_scene_layer(context.scene, object_layer)
|
||||
|
||||
elif self.object_type == 'TYPE_INFLOW':
|
||||
obj.hide_render = True
|
||||
vcu.set_object_display_type(obj, 'WIRE')
|
||||
obj.show_name = True
|
||||
self._set_object_layer(obj, object_layer)
|
||||
self._set_scene_layer(context.scene, object_layer)
|
||||
|
||||
elif self.object_type == 'TYPE_OUTFLOW':
|
||||
obj.hide_render = True
|
||||
vcu.set_object_display_type(obj, 'WIRE')
|
||||
obj.show_name = True
|
||||
self._set_object_layer(obj, object_layer)
|
||||
self._set_scene_layer(context.scene, object_layer)
|
||||
|
||||
elif self.object_type == 'TYPE_FORCE_FIELD':
|
||||
obj.hide_render = True
|
||||
vcu.set_object_display_type(obj, 'WIRE')
|
||||
obj.show_name = True
|
||||
self._set_object_layer(obj, object_layer)
|
||||
self._set_scene_layer(context.scene, object_layer)
|
||||
|
||||
if obj.type == 'CURVE':
|
||||
ff_props = obj.flip_fluid.get_property_group()
|
||||
ff_props.force_field_type = 'FORCE_FIELD_TYPE_CURVE'
|
||||
|
||||
|
||||
def _save_object_view_settings(self, obj):
|
||||
if self.is_view_settings_saved:
|
||||
return
|
||||
|
||||
self.saved_view_settings.hide_render = obj.hide_render
|
||||
self.saved_view_settings.show_name = obj.show_name
|
||||
self.saved_view_settings.draw_type = vcu.get_object_display_type(obj)
|
||||
|
||||
# Layers do not seem to be in Blender 2.80
|
||||
if not vcu.is_blender_28():
|
||||
for i in range(20):
|
||||
self.saved_view_settings.layers[i] = obj.layers[i]
|
||||
|
||||
self.is_view_settings_saved = True
|
||||
|
||||
|
||||
def _reset_object_view_settings(self, obj):
|
||||
if not self.is_view_settings_saved:
|
||||
return
|
||||
|
||||
obj.hide_render = self.saved_view_settings.hide_render
|
||||
obj.show_name = self.saved_view_settings.show_name
|
||||
vcu.set_object_display_type(obj, self.saved_view_settings.draw_type)
|
||||
|
||||
# Layers do not seem to be in Blender 2.80
|
||||
if not vcu.is_blender_28():
|
||||
for i in range(20):
|
||||
obj.layers[i] = True
|
||||
for i in range(20):
|
||||
obj.layers[i] = self.saved_view_settings.layers[i]
|
||||
|
||||
self.is_view_settings_saved = False
|
||||
|
||||
|
||||
def _set_object_layer(self, obj, layeridx):
|
||||
if vcu.is_blender_28():
|
||||
# Layers do not seem to be in Blender 2.80
|
||||
return
|
||||
|
||||
obj.layers[layeridx] = True
|
||||
for i in range(20):
|
||||
obj.layers[i] = (i == layeridx)
|
||||
|
||||
|
||||
def _set_object_layers(self, obj, layers):
|
||||
if vcu.is_blender_28():
|
||||
# Layers do not seem to be in Blender 2.80
|
||||
return
|
||||
|
||||
obj.layers[layers[0]] = True
|
||||
for i in range(20):
|
||||
obj.layers[i] = (i in layers)
|
||||
|
||||
|
||||
def _set_scene_layer(self, scene, layeridx):
|
||||
if vcu.is_blender_28():
|
||||
# Layers do not seem to be in Blender 2.80
|
||||
return
|
||||
|
||||
scene.layers[layeridx] = True
|
||||
|
||||
|
||||
def scene_update_post(scene):
|
||||
domain_properties.scene_update_post(scene)
|
||||
|
||||
flip_objects = scene.flip_fluid.get_simulation_objects()
|
||||
domain_object = scene.flip_fluid.get_domain_object()
|
||||
if domain_object is not None:
|
||||
flip_objects.append(domain_object)
|
||||
|
||||
for obj in flip_objects:
|
||||
obj.flip_fluid.scene_update_post(scene, obj)
|
||||
|
||||
|
||||
def frame_change_post(scene, depsgraph=None):
|
||||
domain_properties.frame_change_post(scene, depsgraph)
|
||||
|
||||
|
||||
def load_pre():
|
||||
domain_properties.load_pre()
|
||||
|
||||
|
||||
def load_post():
|
||||
domain_properties.load_post()
|
||||
obstacle_properties.load_post()
|
||||
fluid_properties.load_post()
|
||||
inflow_properties.load_post()
|
||||
outflow_properties.load_post()
|
||||
force_field_properties.load_post()
|
||||
|
||||
|
||||
def save_pre():
|
||||
domain_properties.save_pre()
|
||||
|
||||
|
||||
def save_post():
|
||||
domain_properties.save_post()
|
||||
|
||||
|
||||
def register():
|
||||
domain_properties.register()
|
||||
obstacle_properties.register()
|
||||
fluid_properties.register()
|
||||
inflow_properties.register()
|
||||
outflow_properties.register()
|
||||
force_field_properties.register()
|
||||
bpy.utils.register_class(ObjectViewSettings)
|
||||
bpy.utils.register_class(FlipFluidObjectProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
domain_properties.unregister()
|
||||
obstacle_properties.unregister()
|
||||
fluid_properties.unregister()
|
||||
inflow_properties.unregister()
|
||||
outflow_properties.unregister()
|
||||
force_field_properties.unregister()
|
||||
bpy.utils.unregister_class(ObjectViewSettings)
|
||||
bpy.utils.unregister_class(FlipFluidObjectProperties)
|
||||
@@ -0,0 +1,209 @@
|
||||
# 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 bpy.props import (
|
||||
BoolProperty,
|
||||
FloatProperty,
|
||||
PointerProperty
|
||||
)
|
||||
|
||||
from . import preset_properties
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
class FlipFluidObstacleProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
is_enabled = BoolProperty(
|
||||
name="Enabled",
|
||||
description="Obstacle is present in the fluid simulation",
|
||||
default=True,
|
||||
); exec(conv("is_enabled"))
|
||||
is_inversed = BoolProperty(
|
||||
name="Inverse",
|
||||
description="Turn the obstacle 'inside-out'. Enabling this option will make the inside solid parts"
|
||||
" of this obstacle empty while everything outside of the obstacle will become solid."
|
||||
" This option is useful for turning a closed shape into a perfect container to hold"
|
||||
" liquid without leakage. This option is not suitable for containing fluid inside of an open"
|
||||
" shape such as a glass or bottle where the fluid needs to exit into the domain. Enabling this"
|
||||
" option on non-manifold geometry or when not appropriate can result in incorrect collisions or"
|
||||
" can prevent fluid from generating",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("is_inversed"))
|
||||
export_animated_mesh = BoolProperty(
|
||||
name="Export Animated Mesh",
|
||||
description="Export this object as an animated mesh. Exporting animated meshes are"
|
||||
" slower, only use when necessary. This option is required for any animation that"
|
||||
" is more complex than just keyframed loc/rot/scale or F-Curves, such as parented"
|
||||
" relations, armatures, animated modifiers, deformable meshes, etc. This option is"
|
||||
" not needed for static objects",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("export_animated_mesh"))
|
||||
skip_reexport = BoolProperty(
|
||||
name="Skip Mesh Re-Export",
|
||||
description="Skip re-exporting this mesh when starting or resuming"
|
||||
" a bake. If this mesh has not been exported or is missing files,"
|
||||
" the addon will automatically export the required files",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("skip_reexport"))
|
||||
force_reexport_on_next_bake = BoolProperty(
|
||||
name="Force Re-Export On Next Bake",
|
||||
description="Override the 'Skip Re-Export' option and force this mesh to be"
|
||||
" re-exported and updated on the next time a simulation start/resumes"
|
||||
" baking. Afting starting/resuming the baking process, this option"
|
||||
" will automatically be disabled once the object has been fully exported."
|
||||
" This option is only applicable if 'Skip Re-Export' is enabled",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("force_reexport_on_next_bake"))
|
||||
friction = FloatProperty(
|
||||
name="Friction",
|
||||
description="Amount of friction between the fluid and the surface"
|
||||
" of the obstacle",
|
||||
min=0.0, max=1.0,
|
||||
default=0.0,
|
||||
precision=2,
|
||||
); exec(conv("friction"))
|
||||
velocity_scale = FloatProperty(
|
||||
name="Velocity Scale",
|
||||
description="Scale the object velocity by this amount. Values greater than 1.0"
|
||||
" will exaggerate the velocity and the simulation will behave as if the object"
|
||||
" is moving faster than it actually is. Values between 0.0 and 1.0 will dampen"
|
||||
" the velocity. Negative values will reverse the velocity. This setting is for"
|
||||
" artistic control and any value other than 1.0 will not be physically accurate",
|
||||
soft_min=0.0, soft_max=5.0,
|
||||
default=1.0,
|
||||
precision=2,
|
||||
); exec(conv("velocity_scale"))
|
||||
whitewater_influence = FloatProperty(
|
||||
name="Whitewater Influence",
|
||||
description="Scale the amount of whitewater generated near this"
|
||||
" obstacle by this value. A value of 1.0 will generate the"
|
||||
" normal amount of whitewater, a value greater than 1.0 will"
|
||||
" generate more, a value less than 1.0 will generate less",
|
||||
min=0.0,
|
||||
default=1.0,
|
||||
precision=2,
|
||||
); exec(conv("whitewater_influence"))
|
||||
dust_emission_strength = FloatProperty(
|
||||
name="Dust Emission Strength",
|
||||
description="Scale the amount of whitewater dust particles generated"
|
||||
" near this obstacle by this value. A value of 1.0 will generate the"
|
||||
" normal amount of dust, a value greater than 1.0 will"
|
||||
" generate more, a value less than 1.0 will generate less. Whitewater"
|
||||
" dust particle simulation must be enabled for this setting to take effect",
|
||||
min=0.0,
|
||||
default=1.0,
|
||||
precision=2,
|
||||
); exec(conv("dust_emission_strength"))
|
||||
sheeting_strength = FloatProperty(
|
||||
name="Sheeting Strength Multiplier",
|
||||
description="Scale the amount of fluid sheeting strength against this"
|
||||
" obstacle by this value. This parameter will only take effect if"
|
||||
" sheeting effects are enabled in the World Panel",
|
||||
min=0.0,
|
||||
default=1.0,
|
||||
precision=2,
|
||||
); exec(conv("sheeting_strength"))
|
||||
mesh_expansion = FloatProperty(
|
||||
name="Expand Geometry",
|
||||
description="Expand the obstacle mesh by this value. This setting"
|
||||
" can be used to prevent fluid from slipping through small"
|
||||
" cracks between touching obstacles. This setting is meant only to be"
|
||||
" used to prevent leakage in fractured objects and only small values"
|
||||
" should be used. This setting is not applicable for preventing leakage"
|
||||
" in thin-walled obstacles",
|
||||
default=0.0,
|
||||
soft_min=-0.05, soft_max=0.05,
|
||||
step=0.01,
|
||||
precision=5,
|
||||
); exec(conv("mesh_expansion"))
|
||||
property_registry = PointerProperty(
|
||||
name="Obstacle Property Registry",
|
||||
description="",
|
||||
type=preset_properties.PresetRegistry,
|
||||
); exec(conv("property_registry"))
|
||||
|
||||
|
||||
disabled_in_viewport_tooltip = BoolProperty(
|
||||
name="Object Disabled in Viewport",
|
||||
description="This obstacle object is currently disabled in the viewport within the"
|
||||
" outliner (Monitor Icon) and will not be included in the simulation. If you"
|
||||
" want the object hidden in the viewport, but still have the object included in the"
|
||||
" simulation, use the outliner Hide in Viewport option instead (Eye Icon)",
|
||||
default=True,
|
||||
); exec(conv("disabled_in_viewport_tooltip"))
|
||||
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def refresh_property_registry(self):
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def _initialize_property_registry(self):
|
||||
try:
|
||||
self.property_registry.clear()
|
||||
add = self.property_registry.add_property
|
||||
add("obstacle.is_enabled", "")
|
||||
add("obstacle.is_inversed", "")
|
||||
add("obstacle.export_animated_mesh", "")
|
||||
add("obstacle.skip_reexport", "")
|
||||
add("obstacle.force_reexport_on_next_bake", "")
|
||||
add("obstacle.friction", "")
|
||||
add("obstacle.velocity_scale", "")
|
||||
add("obstacle.whitewater_influence", "")
|
||||
add("obstacle.dust_emission_strength", "")
|
||||
add("obstacle.sheeting_strength", "")
|
||||
add("obstacle.mesh_expansion", "")
|
||||
self._validate_property_registry()
|
||||
except:
|
||||
# Object is immutable if it is a linked library or library_override
|
||||
# In this case, pass on modifying the object
|
||||
pass
|
||||
|
||||
|
||||
def _validate_property_registry(self):
|
||||
for p in self.property_registry.properties:
|
||||
path = p.path
|
||||
base, identifier = path.split('.', 1)
|
||||
if not hasattr(self, identifier):
|
||||
print("Property Registry Error: Unknown Identifier <" + identifier + ", " + path + ">")
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self.initialize()
|
||||
|
||||
|
||||
def load_post():
|
||||
obstacle_objects = bpy.context.scene.flip_fluid.get_obstacle_objects()
|
||||
for obstacle in obstacle_objects:
|
||||
obstacle.flip_fluid.obstacle.load_post()
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(FlipFluidObstacleProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(FlipFluidObstacleProperties)
|
||||
@@ -0,0 +1,149 @@
|
||||
# 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 bpy.props import (
|
||||
BoolProperty,
|
||||
PointerProperty
|
||||
)
|
||||
|
||||
from . import preset_properties
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
class FlipFluidOutflowProperties(bpy.types.PropertyGroup):
|
||||
conv = vcu.convert_attribute_to_28
|
||||
|
||||
is_enabled = BoolProperty(
|
||||
name="Enabled",
|
||||
description="Object is active in the fluid simulation",
|
||||
default=True,
|
||||
); exec(conv("is_enabled"))
|
||||
remove_fluid = BoolProperty(
|
||||
name="Remove Fluid",
|
||||
description="Enable removing fluid particles from the domain",
|
||||
default=True,
|
||||
); exec(conv("remove_fluid"))
|
||||
remove_whitewater = bpy.props.BoolProperty(
|
||||
name="Remove Whitewater",
|
||||
description="Enable removing whitewater particles from the domain",
|
||||
default=True,
|
||||
); exec(conv("remove_whitewater"))
|
||||
is_inversed = BoolProperty(
|
||||
name="Inverse",
|
||||
description="Turn the outflow object 'inside-out'. If enabled,"
|
||||
" the outflow will remove fluid that is outside of the mesh"
|
||||
" instead of removing fluid that is inside of the mesh",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("is_inversed"))
|
||||
export_animated_mesh = bpy.props.BoolProperty(
|
||||
name="Export Animated Mesh",
|
||||
description="Export this object as an animated mesh. Exporting animated meshes are"
|
||||
" slower, only use when necessary. This option is required for any animation that"
|
||||
" is more complex than just keyframed loc/rot/scale or F-Curves, such as parented"
|
||||
" relations, armatures, animated modifiers, deformable meshes, etc. This option is"
|
||||
" not needed for static objects",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("export_animated_mesh"))
|
||||
skip_reexport = BoolProperty(
|
||||
name="Skip re-export",
|
||||
description="Skip re-exporting this mesh when starting or resuming"
|
||||
" a bake. If this mesh has not been exported or is missing files,"
|
||||
" the addon will automatically export the required files",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("skip_reexport"))
|
||||
force_reexport_on_next_bake = BoolProperty(
|
||||
name="Force Re-Export On Next Bake",
|
||||
description="Override the 'Skip Re-Export' option and force this mesh to be"
|
||||
" re-exported and updated on the next time a simulation start/resumes"
|
||||
" baking. Afting starting/resuming the baking process, this option"
|
||||
" will automatically be disabled once the object has been fully exported."
|
||||
" This option is only applicable if 'Skip Re-Export' is enabled",
|
||||
default=False,
|
||||
options={'HIDDEN'},
|
||||
); exec(conv("force_reexport_on_next_bake"))
|
||||
property_registry = PointerProperty(
|
||||
name="Outflow Property Registry",
|
||||
description="",
|
||||
type=preset_properties.PresetRegistry,
|
||||
); exec(conv("property_registry"))
|
||||
|
||||
|
||||
disabled_in_viewport_tooltip = BoolProperty(
|
||||
name="Object Disabled in Viewport",
|
||||
description="This outflow object is currently disabled in the viewport within the"
|
||||
" outliner (Monitor Icon) and will not be included in the simulation. If you"
|
||||
" want the object hidden in the viewport, but still have the object included in the"
|
||||
" simulation, use the outliner Hide in Viewport option instead (Eye Icon)",
|
||||
default=True,
|
||||
); exec(conv("disabled_in_viewport_tooltip"))
|
||||
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def refresh_property_registry(self):
|
||||
self._initialize_property_registry()
|
||||
|
||||
|
||||
def _initialize_property_registry(self):
|
||||
try:
|
||||
self.property_registry.clear()
|
||||
add = self.property_registry.add_property
|
||||
add("outflow.is_enabled", "")
|
||||
add("outflow.remove_fluid", "")
|
||||
add("outflow.remove_whitewater", "")
|
||||
add("outflow.is_inversed", "")
|
||||
add("outflow.export_animated_mesh", "")
|
||||
add("outflow.skip_reexport", "")
|
||||
add("outflow.force_reexport_on_next_bake", "")
|
||||
self._validate_property_registry()
|
||||
except:
|
||||
# Object is immutable if it is a linked library or library_override
|
||||
# In this case, pass on modifying the object
|
||||
pass
|
||||
|
||||
|
||||
def _validate_property_registry(self):
|
||||
for p in self.property_registry.properties:
|
||||
path = p.path
|
||||
base, identifier = path.split('.', 1)
|
||||
if not hasattr(self, identifier):
|
||||
print("Property Registry Error: Unknown Identifier <" +
|
||||
identifier + ", " + path + ">")
|
||||
|
||||
|
||||
def load_post(self):
|
||||
self.initialize()
|
||||
|
||||
|
||||
def load_post():
|
||||
outflow_objects = bpy.context.scene.flip_fluid.get_outflow_objects()
|
||||
for outflow in outflow_objects:
|
||||
outflow.flip_fluid.outflow.load_post()
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(FlipFluidOutflowProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(FlipFluidOutflowProperties)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user