
Planar object now have a thickness by default. This should make it more intuitive for users as there is no need to specify an object thickness.
1355 lines
48 KiB
Python
1355 lines
48 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# 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 2
|
|
# 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, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
# <pep8 compliant>
|
|
|
|
import bpy
|
|
from bpy.types import Menu, Panel
|
|
from bl_ui.utils import PresetPanel
|
|
from .properties_physics_common import (
|
|
effector_weights_ui,
|
|
)
|
|
|
|
|
|
class FLUID_PT_presets(PresetPanel, Panel):
|
|
bl_label = "Fluid Presets"
|
|
preset_subdir = "fluid"
|
|
preset_operator = "script.execute_preset"
|
|
preset_add_operator = "fluid.preset_add"
|
|
|
|
|
|
class PhysicButtonsPanel:
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "physics"
|
|
|
|
@staticmethod
|
|
def check_domain_has_unbaked_guide(domain):
|
|
return (
|
|
domain.use_guide and not domain.has_cache_baked_guide and
|
|
((domain.guide_source == 'EFFECTOR') or
|
|
(domain.guide_source == 'DOMAIN' and not domain.guide_parent))
|
|
)
|
|
|
|
@staticmethod
|
|
def poll_fluid(context):
|
|
ob = context.object
|
|
if not ((ob and ob.type == 'MESH') and (context.fluid)):
|
|
return False
|
|
|
|
md = context.fluid
|
|
return md and (context.fluid.fluid_type != 'NONE')
|
|
|
|
@staticmethod
|
|
def poll_fluid_domain(context):
|
|
if not PhysicButtonsPanel.poll_fluid(context):
|
|
return False
|
|
|
|
md = context.fluid
|
|
return md and (md.fluid_type == 'DOMAIN')
|
|
|
|
@staticmethod
|
|
def poll_gas_domain(context):
|
|
if not PhysicButtonsPanel.poll_fluid(context):
|
|
return False
|
|
|
|
md = context.fluid
|
|
if md and (md.fluid_type == 'DOMAIN'):
|
|
domain = md.domain_settings
|
|
return domain.domain_type in {'GAS'}
|
|
return False
|
|
|
|
@staticmethod
|
|
def poll_liquid_domain(context):
|
|
if not PhysicButtonsPanel.poll_fluid(context):
|
|
return False
|
|
|
|
md = context.fluid
|
|
if md and (md.fluid_type == 'DOMAIN'):
|
|
domain = md.domain_settings
|
|
return domain.domain_type in {'LIQUID'}
|
|
return False
|
|
|
|
@staticmethod
|
|
def poll_fluid_flow(context):
|
|
if not PhysicButtonsPanel.poll_fluid(context):
|
|
return False
|
|
|
|
md = context.fluid
|
|
return md and (md.fluid_type == 'FLOW')
|
|
|
|
@staticmethod
|
|
def poll_fluid_flow_outflow(context):
|
|
if not PhysicButtonsPanel.poll_fluid_flow(context):
|
|
return False
|
|
|
|
md = context.fluid
|
|
flow = md.flow_settings
|
|
if (flow.flow_behavior == 'OUTFLOW'):
|
|
return True
|
|
|
|
@staticmethod
|
|
def poll_fluid_flow_liquid(context):
|
|
if not PhysicButtonsPanel.poll_fluid_flow(context):
|
|
return False
|
|
|
|
md = context.fluid
|
|
flow = md.flow_settings
|
|
if (flow.flow_type == 'LIQUID'):
|
|
return True
|
|
|
|
|
|
class PHYSICS_PT_fluid(PhysicButtonsPanel, Panel):
|
|
bl_label = "Fluid"
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
ob = context.object
|
|
return (ob and ob.type == 'MESH') and (context.engine in cls.COMPAT_ENGINES) and (context.fluid)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
if not bpy.app.build_options.fluid:
|
|
col = layout.column(align=True)
|
|
col.alignment = 'RIGHT'
|
|
col.label(text="Built without Fluid modifier")
|
|
return
|
|
md = context.fluid
|
|
|
|
layout.prop(md, "fluid_type")
|
|
|
|
|
|
class PHYSICS_PT_settings(PhysicButtonsPanel, Panel):
|
|
bl_label = "Settings"
|
|
bl_parent_id = 'PHYSICS_PT_fluid'
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_fluid(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
md = context.fluid
|
|
ob = context.object
|
|
scene = context.scene
|
|
|
|
if md.fluid_type == 'DOMAIN':
|
|
domain = md.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_data = domain.has_cache_baked_data
|
|
|
|
row = layout.row()
|
|
row.enabled = not is_baking_any and not has_baked_data
|
|
row.prop(domain, "domain_type", expand=False)
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any and not has_baked_data
|
|
|
|
col = flow.column()
|
|
col.enabled = not domain.has_cache_baked_guide
|
|
col.prop(domain, "resolution_max", text="Resolution Divisions")
|
|
col.prop(domain, "time_scale", text="Time Scale")
|
|
col.prop(domain, "cfl_condition", text="CFL Number")
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "use_adaptive_timesteps")
|
|
col.active = domain.use_adaptive_timesteps
|
|
col.prop(domain, "timesteps_max", text="Timesteps Maximum")
|
|
col.prop(domain, "timesteps_min", text="Minimum")
|
|
|
|
col.separator()
|
|
|
|
col = flow.column()
|
|
if scene.use_gravity:
|
|
sub = col.column()
|
|
sub.enabled = False
|
|
sub.prop(domain, "gravity", text="Using Scene Gravity", icon='SCENE_DATA')
|
|
else:
|
|
col.prop(domain, "gravity", text="Gravity")
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "clipping", text="Empty Space")
|
|
col.prop(domain, "delete_in_obstacle", text="Delete In Obstacle")
|
|
|
|
if domain.cache_type == 'MODULAR':
|
|
col.separator()
|
|
|
|
# Deactivate bake operator if guides are enabled but not baked yet.
|
|
note_flag = True
|
|
if self.check_domain_has_unbaked_guide(domain) and domain.cache_type == 'MODULAR':
|
|
note = layout.split()
|
|
note_flag = False
|
|
note.enabled = note_flag
|
|
note.label(icon='INFO', text="Unbaked Guides: Bake Guides or disable them")
|
|
|
|
split = layout.split()
|
|
split.enabled = note_flag
|
|
|
|
bake_incomplete = (domain.cache_frame_pause_data < domain.cache_frame_end)
|
|
if domain.has_cache_baked_data and not domain.is_cache_baking_data and bake_incomplete:
|
|
col = split.column()
|
|
col.operator("fluid.bake_data", text="Resume")
|
|
col = split.column()
|
|
col.operator("fluid.free_data", text="Free")
|
|
elif domain.is_cache_baking_data and not domain.has_cache_baked_data:
|
|
split.enabled = False
|
|
split.operator("fluid.pause_bake", text="Baking Data - ESC to pause")
|
|
elif not domain.has_cache_baked_data and not domain.is_cache_baking_data:
|
|
split.operator("fluid.bake_data", text="Bake Data")
|
|
else:
|
|
split.operator("fluid.free_data", text="Free Data")
|
|
|
|
elif md.fluid_type == 'FLOW':
|
|
flow = md.flow_settings
|
|
|
|
row = layout.row()
|
|
row.prop(flow, "flow_type", expand=False)
|
|
|
|
grid = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
|
|
col = grid.column()
|
|
col.prop(flow, "flow_behavior", expand=False)
|
|
if flow.flow_behavior in {'INFLOW'}:
|
|
col.prop(flow, "use_inflow", text="Use Inflow")
|
|
|
|
col.prop(flow, "subframes", text="Sampling Substeps")
|
|
|
|
if not flow.flow_behavior == 'OUTFLOW' and flow.flow_type in {'SMOKE', 'BOTH', 'FIRE'}:
|
|
|
|
if flow.flow_type in {'SMOKE', 'BOTH'}:
|
|
col.prop(flow, "smoke_color", text="Smoke Color")
|
|
|
|
col = grid.column(align=True)
|
|
col.prop(flow, "use_absolute", text="Absolute Density")
|
|
|
|
if flow.flow_type in {'SMOKE', 'BOTH'}:
|
|
col.prop(flow, "temperature", text="Initial Temperature")
|
|
col.prop(flow, "density", text="Density")
|
|
|
|
if flow.flow_type in {'FIRE', 'BOTH'}:
|
|
col.prop(flow, "fuel_amount", text="Fuel")
|
|
|
|
col.separator()
|
|
col.prop_search(flow, "density_vertex_group", ob, "vertex_groups", text="Vertex Group")
|
|
|
|
elif md.fluid_type == 'EFFECTOR':
|
|
effector_settings = md.effector_settings
|
|
|
|
row = layout.row()
|
|
row.prop(effector_settings, "effector_type")
|
|
|
|
grid = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
|
|
col = grid.column()
|
|
col.prop(effector_settings, "subframes", text="Sampling Substeps")
|
|
col.prop(effector_settings, "surface_distance", text="Surface Thickness")
|
|
|
|
col = grid.column()
|
|
|
|
col.prop(effector_settings, "use_effector", text="Use Effector")
|
|
col.prop(effector_settings, "use_plane_init", text="Is Planar")
|
|
|
|
if effector_settings.effector_type == 'GUIDE':
|
|
col.prop(effector_settings, "velocity_factor", text="Velocity Factor")
|
|
col.prop(effector_settings, "guide_mode", text="Guide Mode")
|
|
|
|
|
|
class PHYSICS_PT_borders(PhysicButtonsPanel, Panel):
|
|
bl_label = "Border Collisions"
|
|
bl_parent_id = 'PHYSICS_PT_settings'
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_fluid_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
md = context.fluid
|
|
domain = md.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_data = domain.has_cache_baked_data
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any and not has_baked_data
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "use_collision_border_front", text="Front")
|
|
col = flow.column()
|
|
col.prop(domain, "use_collision_border_back", text="Back")
|
|
col = flow.column()
|
|
col.prop(domain, "use_collision_border_right", text="Right")
|
|
col = flow.column()
|
|
col.prop(domain, "use_collision_border_left", text="Left")
|
|
col = flow.column()
|
|
col.prop(domain, "use_collision_border_top", text="Top")
|
|
col = flow.column()
|
|
col.prop(domain, "use_collision_border_bottom", text="Bottom")
|
|
|
|
|
|
class PHYSICS_PT_smoke(PhysicButtonsPanel, Panel):
|
|
bl_label = "Gas"
|
|
bl_parent_id = 'PHYSICS_PT_fluid'
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_gas_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
md = context.fluid
|
|
domain = md.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_data = domain.has_cache_baked_data
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any and not has_baked_data
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "alpha", text="Buoyancy Density")
|
|
col.prop(domain, "beta", text="Heat")
|
|
col = flow.column()
|
|
col.prop(domain, "vorticity")
|
|
|
|
|
|
class PHYSICS_PT_smoke_dissolve(PhysicButtonsPanel, Panel):
|
|
bl_label = "Dissolve"
|
|
bl_parent_id = 'PHYSICS_PT_smoke'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_gas_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw_header(self, context):
|
|
md = context.fluid.domain_settings
|
|
domain = context.fluid.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
|
|
self.layout.enabled = not is_baking_any
|
|
self.layout.prop(md, "use_dissolve_smoke", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
md = context.fluid
|
|
domain = md.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_data = domain.has_cache_baked_data
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any and not has_baked_data
|
|
|
|
layout.active = domain.use_dissolve_smoke
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "dissolve_speed", text="Time")
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "use_dissolve_smoke_log", text="Slow")
|
|
|
|
|
|
class PHYSICS_PT_fire(PhysicButtonsPanel, Panel):
|
|
bl_label = "Fire"
|
|
bl_parent_id = 'PHYSICS_PT_smoke'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_gas_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
md = context.fluid
|
|
domain = md.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_data = domain.has_cache_baked_data
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any and not has_baked_data
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "burning_rate", text="Reaction Speed")
|
|
row = col.row()
|
|
sub = row.column(align=True)
|
|
sub.prop(domain, "flame_smoke", text="Flame Smoke")
|
|
sub.prop(domain, "flame_vorticity", text="Vorticity")
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "flame_max_temp", text="Temperature Maximum")
|
|
col.prop(domain, "flame_ignition", text="Minimum")
|
|
row = col.row()
|
|
row.prop(domain, "flame_smoke_color", text="Flame Color")
|
|
|
|
|
|
class PHYSICS_PT_liquid(PhysicButtonsPanel, Panel):
|
|
bl_label = "Liquid"
|
|
bl_parent_id = 'PHYSICS_PT_fluid'
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_OPENGL'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_liquid_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw_header(self, context):
|
|
md = context.fluid.domain_settings
|
|
domain = context.fluid.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
|
|
self.layout.enabled = not is_baking_any
|
|
self.layout.prop(md, "use_flip_particles", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
md = context.fluid
|
|
domain = md.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_data = domain.has_cache_baked_data
|
|
|
|
layout.enabled = not is_baking_any and not has_baked_data
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "simulation_method", expand=False)
|
|
col.prop(domain, "flip_ratio", text="FLIP Ratio")
|
|
row = col.row()
|
|
sub = row.column(align=True)
|
|
sub.prop(domain, "particle_radius", text="Particle Radius")
|
|
sub.prop(domain, "particle_number", text="Sampling")
|
|
sub.prop(domain, "particle_randomness", text="Randomness")
|
|
|
|
col = flow.column()
|
|
row = col.row()
|
|
sub = row.column(align=True)
|
|
sub.prop(domain, "particle_max", text="Particles Maximum")
|
|
sub.prop(domain, "particle_min", text="Minimum")
|
|
col.prop(domain, "particle_band_width", text="Narrow Band Width")
|
|
row = col.row()
|
|
sub = row.column(align=True)
|
|
sub.prop(domain, "use_fractions", text="Fractional Obstacles")
|
|
sub.active = domain.use_fractions
|
|
sub.prop(domain, "fractions_threshold", text="Obstacle-Fluid Threshold")
|
|
|
|
|
|
class PHYSICS_PT_flow_source(PhysicButtonsPanel, Panel):
|
|
bl_label = "Flow Source"
|
|
bl_parent_id = 'PHYSICS_PT_settings'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_fluid_flow(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
ob = context.object
|
|
flow = context.fluid.flow_settings
|
|
|
|
col = layout.column()
|
|
col.prop(flow, "flow_source", expand=False, text="Flow Source")
|
|
if flow.flow_source == 'PARTICLES':
|
|
col.prop_search(flow, "particle_system", ob, "particle_systems", text="Particle System")
|
|
|
|
grid = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
|
|
col = grid.column()
|
|
if flow.flow_source == 'MESH':
|
|
col.prop(flow, "use_plane_init", text="Is Planar")
|
|
col.prop(flow, "surface_distance", text="Surface Emission")
|
|
if flow.flow_type in {'SMOKE', 'BOTH', 'FIRE'}:
|
|
col = grid.column()
|
|
col.prop(flow, "volume_density", text="Volume Emission")
|
|
|
|
if flow.flow_source == 'PARTICLES':
|
|
col.prop(flow, "use_particle_size", text="Set Size")
|
|
sub = col.column()
|
|
sub.active = flow.use_particle_size
|
|
sub.prop(flow, "particle_size")
|
|
|
|
|
|
class PHYSICS_PT_flow_initial_velocity(PhysicButtonsPanel, Panel):
|
|
bl_label = "Initial Velocity"
|
|
bl_parent_id = 'PHYSICS_PT_settings'
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_OPENGL'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_fluid_flow(context):
|
|
return False
|
|
|
|
if PhysicButtonsPanel.poll_fluid_flow_outflow(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw_header(self, context):
|
|
md = context.fluid
|
|
flow_smoke = md.flow_settings
|
|
|
|
self.layout.prop(flow_smoke, "use_initial_velocity", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=True)
|
|
|
|
md = context.fluid
|
|
flow_smoke = md.flow_settings
|
|
|
|
flow.active = flow_smoke.use_initial_velocity
|
|
|
|
col = flow.column()
|
|
col.prop(flow_smoke, "velocity_factor")
|
|
|
|
if flow_smoke.flow_source == 'MESH':
|
|
col.prop(flow_smoke, "velocity_normal")
|
|
# col.prop(flow_smoke, "velocity_random")
|
|
col = flow.column()
|
|
col.prop(flow_smoke, "velocity_coord")
|
|
|
|
|
|
class PHYSICS_PT_flow_texture(PhysicButtonsPanel, Panel):
|
|
bl_label = "Texture"
|
|
bl_parent_id = 'PHYSICS_PT_settings'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_fluid_flow(context):
|
|
return False
|
|
|
|
if PhysicButtonsPanel.poll_fluid_flow_outflow(context):
|
|
return False
|
|
|
|
if PhysicButtonsPanel.poll_fluid_flow_liquid(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw_header(self, context):
|
|
md = context.fluid
|
|
flow_smoke = md.flow_settings
|
|
|
|
self.layout.prop(flow_smoke, "use_texture", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
|
|
ob = context.object
|
|
flow_smoke = context.fluid.flow_settings
|
|
|
|
sub = flow.column()
|
|
sub.active = flow_smoke.use_texture
|
|
sub.prop(flow_smoke, "noise_texture")
|
|
sub.prop(flow_smoke, "texture_map_type", text="Mapping")
|
|
|
|
col = flow.column()
|
|
sub = col.column()
|
|
sub.active = flow_smoke.use_texture
|
|
|
|
if flow_smoke.texture_map_type == 'UV':
|
|
sub.prop_search(flow_smoke, "uv_layer", ob.data, "uv_layers")
|
|
|
|
if flow_smoke.texture_map_type == 'AUTO':
|
|
sub.prop(flow_smoke, "texture_size")
|
|
|
|
sub.prop(flow_smoke, "texture_offset")
|
|
|
|
|
|
class PHYSICS_PT_adaptive_domain(PhysicButtonsPanel, Panel):
|
|
bl_label = "Adaptive Domain"
|
|
bl_parent_id = 'PHYSICS_PT_settings'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_OPENGL'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_gas_domain(context):
|
|
return False
|
|
|
|
md = context.fluid
|
|
domain = md.domain_settings
|
|
# Effector guides require a fixed domain size
|
|
if domain.use_guide and domain.guide_source == 'EFFECTOR':
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw_header(self, context):
|
|
md = context.fluid.domain_settings
|
|
domain = context.fluid.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_any = domain.has_cache_baked_any
|
|
|
|
self.layout.enabled = not is_baking_any and not has_baked_any
|
|
self.layout.prop(md, "use_adaptive_domain", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
domain = context.fluid.domain_settings
|
|
layout.active = domain.use_adaptive_domain
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_any = domain.has_cache_baked_any
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
|
|
flow.enabled = not is_baking_any and not has_baked_any
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "additional_res", text="Add Resolution")
|
|
col.prop(domain, "adapt_margin")
|
|
|
|
col.separator()
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "adapt_threshold", text="Threshold")
|
|
|
|
|
|
class PHYSICS_PT_noise(PhysicButtonsPanel, Panel):
|
|
bl_label = "Noise"
|
|
bl_parent_id = 'PHYSICS_PT_smoke'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_gas_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw_header(self, context):
|
|
md = context.fluid.domain_settings
|
|
domain = context.fluid.domain_settings
|
|
is_baking_any = domain.is_cache_baking_any
|
|
self.layout.enabled = not is_baking_any
|
|
self.layout.prop(md, "use_noise", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
domain = context.fluid.domain_settings
|
|
layout.active = domain.use_noise
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_noise = domain.has_cache_baked_noise
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any and not has_baked_noise
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "noise_scale", text="Upres Factor")
|
|
# TODO (sebbas): Mantaflow only supports wavelet noise. Maybe get rid of noise type field.
|
|
col.prop(domain, "noise_type", text="Noise Method")
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "noise_strength", text="Strength")
|
|
col.prop(domain, "noise_pos_scale", text="Scale")
|
|
col.prop(domain, "noise_time_anim", text="Time")
|
|
|
|
if domain.cache_type == 'MODULAR':
|
|
col.separator()
|
|
|
|
# Deactivate bake operator if data has not been baked yet.
|
|
note_flag = True
|
|
if domain.use_noise and not domain.has_cache_baked_data and domain.cache_type == 'MODULAR':
|
|
note = layout.split()
|
|
note_flag = False
|
|
note.enabled = note_flag
|
|
note.label(icon='INFO', text="Unbaked Data: Bake Data first")
|
|
|
|
split = layout.split()
|
|
split.enabled = domain.has_cache_baked_data and note_flag
|
|
|
|
bake_incomplete = (domain.cache_frame_pause_noise < domain.cache_frame_end)
|
|
if domain.has_cache_baked_noise and not domain.is_cache_baking_noise and bake_incomplete:
|
|
col = split.column()
|
|
col.operator("fluid.bake_noise", text="Resume")
|
|
col = split.column()
|
|
col.operator("fluid.free_noise", text="Free")
|
|
elif not domain.has_cache_baked_noise and domain.is_cache_baking_noise:
|
|
split.enabled = False
|
|
split.operator("fluid.pause_bake", text="Baking Noise - ESC to pause")
|
|
elif not domain.has_cache_baked_noise and not domain.is_cache_baking_noise:
|
|
split.operator("fluid.bake_noise", text="Bake Noise")
|
|
else:
|
|
split.operator("fluid.free_noise", text="Free Noise")
|
|
|
|
|
|
class PHYSICS_PT_mesh(PhysicButtonsPanel, Panel):
|
|
bl_label = "Mesh"
|
|
bl_parent_id = 'PHYSICS_PT_liquid'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_liquid_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw_header(self, context):
|
|
md = context.fluid.domain_settings
|
|
domain = context.fluid.domain_settings
|
|
is_baking_any = domain.is_cache_baking_any
|
|
self.layout.enabled = not is_baking_any
|
|
self.layout.prop(md, "use_mesh", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
domain = context.fluid.domain_settings
|
|
layout.active = domain.use_mesh
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_mesh = domain.has_cache_baked_mesh
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any and not has_baked_mesh
|
|
|
|
col = flow.column()
|
|
|
|
col.prop(domain, "mesh_scale", text="Upres Factor")
|
|
col.prop(domain, "mesh_particle_radius", text="Particle Radius")
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "use_speed_vectors", text="Use Speed Vectors")
|
|
|
|
col.separator()
|
|
col.prop(domain, "mesh_generator", text="Mesh Generator")
|
|
|
|
if domain.mesh_generator in {'IMPROVED'}:
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "mesh_smoothen_pos", text="Smoothing Positive")
|
|
col.prop(domain, "mesh_smoothen_neg", text="Negative")
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "mesh_concave_upper", text="Concavity Upper")
|
|
col.prop(domain, "mesh_concave_lower", text="Lower")
|
|
|
|
# TODO (sebbas): for now just interpolate any upres grids, ie not sampling highres grids
|
|
#col.prop(domain, "highres_sampling", text="Flow Sampling:")
|
|
|
|
if domain.cache_type == 'MODULAR':
|
|
col.separator()
|
|
|
|
# Deactivate bake operator if data has not been baked yet.
|
|
note_flag = True
|
|
if domain.use_mesh and not domain.has_cache_baked_data and domain.cache_type == 'MODULAR':
|
|
note = layout.split()
|
|
note_flag = False
|
|
note.enabled = note_flag
|
|
note.label(icon='INFO', text="Unbaked Data: Bake Data first")
|
|
|
|
split = layout.split()
|
|
split.enabled = domain.has_cache_baked_data and note_flag
|
|
|
|
bake_incomplete = (domain.cache_frame_pause_mesh < domain.cache_frame_end)
|
|
if domain.has_cache_baked_mesh and not domain.is_cache_baking_mesh and bake_incomplete:
|
|
col = split.column()
|
|
col.operator("fluid.bake_mesh", text="Resume")
|
|
col = split.column()
|
|
col.operator("fluid.free_mesh", text="Free")
|
|
elif not domain.has_cache_baked_mesh and domain.is_cache_baking_mesh:
|
|
split.enabled = False
|
|
split.operator("fluid.pause_bake", text="Baking Mesh - ESC to pause")
|
|
elif not domain.has_cache_baked_mesh and not domain.is_cache_baking_mesh:
|
|
split.operator("fluid.bake_mesh", text="Bake Mesh")
|
|
else:
|
|
split.operator("fluid.free_mesh", text="Free Mesh")
|
|
|
|
|
|
class PHYSICS_PT_particles(PhysicButtonsPanel, Panel):
|
|
bl_label = "Particles"
|
|
bl_parent_id = 'PHYSICS_PT_liquid'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_OPENGL'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_liquid_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
domain = context.fluid.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_particles = domain.has_cache_baked_particles
|
|
using_particles = domain.use_spray_particles or domain.use_foam_particles or domain.use_bubble_particles
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any
|
|
|
|
sndparticle_combined_export = domain.sndparticle_combined_export
|
|
row = flow.row()
|
|
row.enabled = sndparticle_combined_export in {'OFF', 'FOAM + BUBBLES'}
|
|
row.prop(domain, "use_spray_particles", text="Spray")
|
|
row.prop(domain, "use_foam_particles", text="Foam")
|
|
row.prop(domain, "use_bubble_particles", text="Bubbles")
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "sndparticle_combined_export")
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any and not has_baked_particles
|
|
flow.active = using_particles
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "particle_scale", text="Upres Factor")
|
|
col.separator()
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "sndparticle_potential_max_wavecrest", text="Wave Crest Potential Maximum")
|
|
col.prop(domain, "sndparticle_potential_min_wavecrest", text="Minimum")
|
|
col.separator()
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "sndparticle_potential_max_trappedair", text="Trapped Air Potential Maximum")
|
|
col.prop(domain, "sndparticle_potential_min_trappedair", text="Minimum")
|
|
col.separator()
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "sndparticle_potential_max_energy", text="Kinetic Energy Potential Maximum")
|
|
col.prop(domain, "sndparticle_potential_min_energy", text="Minimum")
|
|
col.separator()
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "sndparticle_potential_radius", text="Potential Radius")
|
|
col.prop(domain, "sndparticle_update_radius", text="Particle Update Radius")
|
|
col.separator()
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "sndparticle_sampling_wavecrest", text="Wave Crest Particle Sampling")
|
|
col.prop(domain, "sndparticle_sampling_trappedair", text="Trapped Air Particle Sampling")
|
|
col.separator()
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "sndparticle_life_max", text="Particle Life Maximum")
|
|
col.prop(domain, "sndparticle_life_min", text="Minimum")
|
|
col.separator()
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "sndparticle_bubble_buoyancy", text="Bubble Buoyancy")
|
|
col.prop(domain, "sndparticle_bubble_drag", text="Bubble Drag")
|
|
col.separator()
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "sndparticle_boundary", text="Particles in Boundary:")
|
|
|
|
if domain.cache_type == 'MODULAR':
|
|
col.separator()
|
|
|
|
# Deactivate bake operator if data has not been baked yet.
|
|
note_flag = True
|
|
if using_particles and not domain.has_cache_baked_data and domain.cache_type == 'MODULAR':
|
|
note = layout.split()
|
|
note_flag = False
|
|
note.enabled = note_flag
|
|
note.label(icon='INFO', text="Unbaked Data: Bake Data first")
|
|
|
|
split = layout.split()
|
|
split.enabled = (
|
|
note_flag and
|
|
domain.has_cache_baked_data and
|
|
(domain.use_spray_particles or
|
|
domain.use_bubble_particles or
|
|
domain.use_foam_particles or
|
|
domain.use_tracer_particles)
|
|
)
|
|
|
|
bake_incomplete = (domain.cache_frame_pause_particles < domain.cache_frame_end)
|
|
if domain.has_cache_baked_particles and not domain.is_cache_baking_particles and bake_incomplete:
|
|
col = split.column()
|
|
col.operator("fluid.bake_particles", text="Resume")
|
|
col = split.column()
|
|
col.operator("fluid.free_particles", text="Free")
|
|
elif not domain.has_cache_baked_particles and domain.is_cache_baking_particles:
|
|
split.enabled = False
|
|
split.operator("fluid.pause_bake", text="Baking Particles - ESC to pause")
|
|
elif not domain.has_cache_baked_particles and not domain.is_cache_baking_particles:
|
|
split.operator("fluid.bake_particles", text="Bake Particles")
|
|
else:
|
|
split.operator("fluid.free_particles", text="Free Particles")
|
|
|
|
|
|
class PHYSICS_PT_diffusion(PhysicButtonsPanel, Panel):
|
|
bl_label = "Diffusion"
|
|
bl_parent_id = 'PHYSICS_PT_liquid'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
# Fluid diffusion only enabled for liquids (surface tension and viscosity not relevant for smoke)
|
|
if not PhysicButtonsPanel.poll_liquid_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw_header(self, context):
|
|
md = context.fluid.domain_settings
|
|
domain = context.fluid.domain_settings
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_any = domain.has_cache_baked_any
|
|
self.layout.enabled = not is_baking_any and not has_baked_any
|
|
self.layout.prop(md, "use_diffusion", text="")
|
|
|
|
def draw_header_preset(self, _context):
|
|
FLUID_PT_presets.draw_panel_header(self.layout)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
domain = context.fluid.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_any = domain.has_cache_baked_any
|
|
has_baked_data = domain.has_cache_baked_data
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any and not has_baked_any and not has_baked_data
|
|
|
|
col = flow.column(align=True)
|
|
col.prop(domain, "viscosity_base", text="Base")
|
|
col.prop(domain, "viscosity_exponent", text="Exponent", slider=True)
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "domain_size", text="Real World Size")
|
|
col.prop(domain, "surface_tension", text="Surface Tension")
|
|
|
|
|
|
class PHYSICS_PT_guide(PhysicButtonsPanel, Panel):
|
|
bl_label = "Guides"
|
|
bl_parent_id = 'PHYSICS_PT_fluid'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_OPENGL'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_fluid_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw_header(self, context):
|
|
md = context.fluid.domain_settings
|
|
domain = context.fluid.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
|
|
self.layout.enabled = not is_baking_any
|
|
self.layout.prop(md, "use_guide", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
domain = context.fluid.domain_settings
|
|
|
|
layout.active = domain.use_guide
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_data = domain.has_cache_baked_data
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any and not has_baked_data
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "guide_alpha", text="Weight")
|
|
col.prop(domain, "guide_beta", text="Size")
|
|
col.prop(domain, "guide_vel_factor", text="Velocity Factor")
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "guide_source", text="Velocity Source")
|
|
if domain.guide_source == 'DOMAIN':
|
|
col.prop(domain, "guide_parent", text="Guide Parent")
|
|
|
|
if domain.cache_type == 'MODULAR':
|
|
col.separator()
|
|
|
|
if domain.guide_source == 'EFFECTOR':
|
|
split = layout.split()
|
|
bake_incomplete = (domain.cache_frame_pause_guide < domain.cache_frame_end)
|
|
if domain.has_cache_baked_guide and not domain.is_cache_baking_guide and bake_incomplete:
|
|
col = split.column()
|
|
col.operator("fluid.bake_guides", text="Resume")
|
|
col = split.column()
|
|
col.operator("fluid.free_guides", text="Free")
|
|
elif not domain.has_cache_baked_guide and domain.is_cache_baking_guide:
|
|
split.enabled = False
|
|
split.operator("fluid.pause_bake", text="Baking Guides - ESC to pause")
|
|
elif not domain.has_cache_baked_guide and not domain.is_cache_baking_guide:
|
|
split.operator("fluid.bake_guides", text="Bake Guides")
|
|
else:
|
|
split.operator("fluid.free_guides", text="Free Guides")
|
|
|
|
|
|
class PHYSICS_PT_collections(PhysicButtonsPanel, Panel):
|
|
bl_label = "Collections"
|
|
bl_parent_id = 'PHYSICS_PT_fluid'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_fluid_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
domain = context.fluid.domain_settings
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "fluid_group", text="Flow")
|
|
|
|
# col.prop(domain, "effector_group", text="Forces")
|
|
col.prop(domain, "effector_group", text="Effector")
|
|
|
|
|
|
class PHYSICS_PT_cache(PhysicButtonsPanel, Panel):
|
|
bl_label = "Cache"
|
|
bl_parent_id = 'PHYSICS_PT_fluid'
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_fluid_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
md = context.fluid
|
|
domain = context.fluid.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_data = domain.has_cache_baked_data
|
|
has_baked_noise = domain.has_cache_baked_noise
|
|
has_baked_mesh = domain.has_cache_baked_mesh
|
|
has_baked_particles = domain.has_cache_baked_particles
|
|
|
|
col = layout.column()
|
|
col.prop(domain, "cache_directory", text="")
|
|
col.enabled = not is_baking_any
|
|
|
|
layout.use_property_split = True
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "cache_type", expand=False)
|
|
col.enabled = not is_baking_any
|
|
|
|
col.separator()
|
|
|
|
row = col.row()
|
|
col = row.column(align=True)
|
|
col.prop(domain, "cache_frame_start", text="Frame Start")
|
|
col.prop(domain, "cache_frame_end", text="End")
|
|
row.enabled = not is_baking_any
|
|
|
|
col.separator()
|
|
|
|
col = flow.column()
|
|
row = col.row()
|
|
row.enabled = not is_baking_any and not has_baked_data
|
|
row.prop(domain, "cache_data_format", text="Data File Format")
|
|
|
|
if md.domain_settings.domain_type in {'GAS'}:
|
|
if domain.use_noise:
|
|
row = col.row()
|
|
row.enabled = not is_baking_any and not has_baked_noise
|
|
row.prop(domain, "cache_noise_format", text="Noise File Format")
|
|
|
|
if md.domain_settings.domain_type in {'LIQUID'}:
|
|
# File format for all particle systemes (FLIP and secondary)
|
|
row = col.row()
|
|
row.enabled = not is_baking_any and not has_baked_particles and not has_baked_data
|
|
row.prop(domain, "cache_particle_format", text="Particle File Format")
|
|
|
|
if domain.use_mesh:
|
|
row = col.row()
|
|
row.enabled = not is_baking_any and not has_baked_mesh
|
|
row.prop(domain, "cache_mesh_format", text="Mesh File Format")
|
|
|
|
if domain.cache_type == 'FINAL':
|
|
|
|
col.separator()
|
|
split = layout.split()
|
|
|
|
if domain.is_cache_baking_data and not domain.has_cache_baked_data:
|
|
split.enabled = False
|
|
split.operator("fluid.pause_bake", text="Baking All - ESC to pause")
|
|
elif not domain.has_cache_baked_data and not domain.is_cache_baking_data:
|
|
split.operator("fluid.bake_all", text="Bake All")
|
|
else:
|
|
split.operator("fluid.free_all", text="Free All")
|
|
|
|
|
|
class PHYSICS_PT_export(PhysicButtonsPanel, Panel):
|
|
bl_label = "Advanced"
|
|
bl_parent_id = 'PHYSICS_PT_cache'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
# Only show the advanced panel to advanced users who know Mantaflow's birthday :)
|
|
if not PhysicButtonsPanel.poll_fluid_domain(context) or bpy.app.debug_value != 3001:
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
domain = context.fluid.domain_settings
|
|
|
|
is_baking_any = domain.is_cache_baking_any
|
|
has_baked_any = domain.has_cache_baked_any
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
flow.enabled = not is_baking_any and not has_baked_any
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "export_manta_script", text="Export Mantaflow Script")
|
|
|
|
|
|
class PHYSICS_PT_field_weights(PhysicButtonsPanel, Panel):
|
|
bl_label = "Field Weights"
|
|
bl_parent_id = 'PHYSICS_PT_fluid'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not PhysicButtonsPanel.poll_fluid_domain(context):
|
|
return False
|
|
|
|
return (context.engine in cls.COMPAT_ENGINES)
|
|
|
|
def draw(self, context):
|
|
domain = context.fluid.domain_settings
|
|
effector_weights_ui(self, domain.effector_weights, 'SMOKE')
|
|
|
|
|
|
class PHYSICS_PT_viewport_display(PhysicButtonsPanel, Panel):
|
|
bl_label = "Viewport Display"
|
|
bl_parent_id = 'PHYSICS_PT_fluid'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (PhysicButtonsPanel.poll_gas_domain(context))
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
|
|
|
|
domain = context.fluid.domain_settings
|
|
slice_method = domain.slice_method
|
|
axis_slice_method = domain.axis_slice_method
|
|
|
|
do_axis_slicing = (slice_method == 'AXIS_ALIGNED')
|
|
do_full_slicing = (axis_slice_method == 'FULL')
|
|
|
|
col = flow.column(align=False)
|
|
col.prop(domain, "display_thickness")
|
|
col.prop(domain, "display_interpolation")
|
|
col.separator()
|
|
|
|
col = flow.column()
|
|
col.prop(domain, "slice_method", text="Slicing")
|
|
|
|
col = col.column()
|
|
col.active = do_axis_slicing
|
|
col.prop(domain, "axis_slice_method")
|
|
|
|
if not do_full_slicing and do_axis_slicing:
|
|
col.prop(domain, "slice_axis")
|
|
col.prop(domain, "slice_depth")
|
|
|
|
col = col.column()
|
|
col.active = do_full_slicing or not do_axis_slicing
|
|
col.prop(domain, "slice_per_voxel")
|
|
|
|
|
|
class PHYSICS_PT_viewport_display_color(PhysicButtonsPanel, Panel):
|
|
bl_label = "Color Mapping"
|
|
bl_parent_id = 'PHYSICS_PT_viewport_display'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (PhysicButtonsPanel.poll_gas_domain(context))
|
|
|
|
def draw_header(self, context):
|
|
md = context.fluid.domain_settings
|
|
|
|
self.layout.prop(md, "use_color_ramp", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
domain = context.fluid.domain_settings
|
|
col = layout.column()
|
|
col.active = domain.use_color_ramp
|
|
col.prop(domain, "coba_field")
|
|
|
|
col.use_property_split = False
|
|
|
|
col = col.column()
|
|
col.template_color_ramp(domain, "color_ramp", expand=True)
|
|
|
|
|
|
class PHYSICS_PT_viewport_display_debug(PhysicButtonsPanel, Panel):
|
|
bl_label = "Debug Velocity"
|
|
bl_parent_id = 'PHYSICS_PT_viewport_display'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (PhysicButtonsPanel.poll_gas_domain(context))
|
|
|
|
def draw_header(self, context):
|
|
md = context.fluid.domain_settings
|
|
|
|
self.layout.prop(md, "show_velocity", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
|
|
|
|
domain = context.fluid.domain_settings
|
|
|
|
col = flow.column()
|
|
col.active = domain.show_velocity
|
|
col.prop(domain, "vector_display_type", text="Display As")
|
|
col.prop(domain, "vector_scale")
|
|
|
|
|
|
classes = (
|
|
FLUID_PT_presets,
|
|
PHYSICS_PT_fluid,
|
|
PHYSICS_PT_settings,
|
|
PHYSICS_PT_borders,
|
|
PHYSICS_PT_adaptive_domain,
|
|
PHYSICS_PT_smoke,
|
|
PHYSICS_PT_smoke_dissolve,
|
|
PHYSICS_PT_noise,
|
|
PHYSICS_PT_fire,
|
|
PHYSICS_PT_liquid,
|
|
PHYSICS_PT_diffusion,
|
|
PHYSICS_PT_particles,
|
|
PHYSICS_PT_mesh,
|
|
PHYSICS_PT_guide,
|
|
PHYSICS_PT_collections,
|
|
PHYSICS_PT_cache,
|
|
PHYSICS_PT_export,
|
|
PHYSICS_PT_field_weights,
|
|
PHYSICS_PT_flow_source,
|
|
PHYSICS_PT_flow_initial_velocity,
|
|
PHYSICS_PT_flow_texture,
|
|
PHYSICS_PT_viewport_display,
|
|
PHYSICS_PT_viewport_display_color,
|
|
PHYSICS_PT_viewport_display_debug,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__": # only for live edit.
|
|
from bpy.utils import register_class
|
|
for cls in classes:
|
|
register_class(cls)
|