Mantaflow [Part 3]: Customized UI for Manta fluids

With Mantaflow the current smoke modifier UI will accommodate both smoke and liquids.

In addition, there is now an option for Mantaflow liquids in the quick effects section ("Quick Liquid").

Reviewed By: sergey

Maniphest Tasks: T59995

Differential Revision: https://developer.blender.org/D3852
This commit is contained in:
Sebastián Barschkis
2019-12-16 15:42:07 +01:00
parent 2aa4301c88
commit 4235fe37d6
13 changed files with 1167 additions and 1066 deletions

View File

@@ -1,3 +1,3 @@
import bpy
bpy.context.fluid.settings.viscosity_base = 2.0
bpy.context.fluid.settings.viscosity_exponent = 3
bpy.context.fluid.domain_settings.viscosity_base = 2.0
bpy.context.fluid.domain_settings.viscosity_exponent = 3

View File

@@ -1,3 +1,3 @@
import bpy
bpy.context.fluid.settings.viscosity_base = 5.0
bpy.context.fluid.settings.viscosity_exponent = 5
bpy.context.fluid.domain_settings.viscosity_base = 5.0
bpy.context.fluid.domain_settings.viscosity_exponent = 5

View File

@@ -1,3 +1,3 @@
import bpy
bpy.context.fluid.settings.viscosity_base = 1.0
bpy.context.fluid.settings.viscosity_exponent = 6
bpy.context.fluid.domain_settings.viscosity_base = 1.0
bpy.context.fluid.domain_settings.viscosity_exponent = 6

View File

@@ -349,8 +349,8 @@ class QuickSmoke(ObjectModeOperator, Operator):
)
def execute(self, context):
if not bpy.app.build_options.mod_smoke:
self.report({'ERROR'}, "Built without Smoke modifier support")
if not bpy.app.build_options.fluid:
self.report({'ERROR'}, "Built without Fluid modifier")
return {'CANCELLED'}
fake_context = context.copy()
@@ -366,11 +366,17 @@ class QuickSmoke(ObjectModeOperator, Operator):
for obj in mesh_objects:
fake_context["object"] = obj
# make each selected object a smoke flow
bpy.ops.object.modifier_add(fake_context, type='SMOKE')
obj.modifiers[-1].smoke_type = 'FLOW'
bpy.ops.object.modifier_add(fake_context, type='FLUID')
obj.modifiers[-1].fluid_type = 'FLOW'
# set type
obj.modifiers[-1].flow_settings.smoke_flow_type = self.style
obj.modifiers[-1].flow_settings.flow_type = self.style
# set flow behavior
obj.modifiers[-1].flow_settings.flow_behavior = 'INFLOW'
# use some surface distance for smoke emission
obj.modifiers[-1].flow_settings.surface_distance = 1.5
if not self.show_flows:
obj.display_type = 'WIRE'
@@ -388,10 +394,13 @@ class QuickSmoke(ObjectModeOperator, Operator):
obj.scale = 0.5 * (max_co - min_co) + Vector((1.0, 1.0, 2.0))
# setup smoke domain
bpy.ops.object.modifier_add(type='SMOKE')
obj.modifiers[-1].smoke_type = 'DOMAIN'
bpy.ops.object.modifier_add(type='FLUID')
obj.modifiers[-1].fluid_type = 'DOMAIN'
if self.style == 'FIRE' or self.style == 'BOTH':
obj.modifiers[-1].domain_settings.use_high_resolution = True
obj.modifiers[-1].domain_settings.use_noise = True
# set correct cache file format for smoke
obj.modifiers[-1].domain_settings.cache_data_format = 'UNI'
# Setup material
@@ -431,47 +440,25 @@ class QuickSmoke(ObjectModeOperator, Operator):
return {'FINISHED'}
class QuickFluid(ObjectModeOperator, Operator):
"""Use selected objects in a fluid simulation"""
bl_idname = "object.quick_fluid"
bl_label = "Quick Fluid"
class QuickLiquid(Operator):
bl_idname = "object.quick_liquid"
bl_label = "Quick Liquid"
bl_options = {'REGISTER', 'UNDO'}
style: EnumProperty(
name="Fluid Style",
items=(
('INFLOW', "Inflow", ""),
('BASIC', "Basic", ""),
),
default='BASIC',
)
initial_velocity: FloatVectorProperty(
name="Initial Velocity",
description="Initial velocity of the fluid",
min=-100.0, max=100.0,
default=(0.0, 0.0, 0.0),
subtype='VELOCITY',
)
show_flows: BoolProperty(
name="Render Fluid Objects",
description="Keep the fluid objects visible during rendering",
default=False,
)
start_baking: BoolProperty(
name="Start Fluid Bake",
description=("Start baking the fluid immediately "
"after creating the domain object"),
default=False,
)
name="Render Liquid Objects",
description="Keep the liquid objects visible during rendering",
default=False,
)
def execute(self, context):
if not bpy.app.build_options.mod_fluid:
self.report({'ERROR'}, "Built without Fluid modifier support")
if not bpy.app.build_options.fluid:
self.report({'ERROR'}, "Built without Fluid modifier")
return {'CANCELLED'}
fake_context = context.copy()
mesh_objects = [obj for obj in context.selected_objects
if (obj.type == 'MESH' and 0.0 not in obj.dimensions)]
if obj.type == 'MESH']
min_co = Vector((100000.0, 100000.0, 100000.0))
max_co = -min_co
@@ -481,47 +468,51 @@ class QuickFluid(ObjectModeOperator, Operator):
for obj in mesh_objects:
fake_context["object"] = obj
# make each selected object a fluid
bpy.ops.object.modifier_add(fake_context, type='FLUID_SIMULATION')
# make each selected object a liquid flow
bpy.ops.object.modifier_add(fake_context, type='FLUID')
obj.modifiers[-1].fluid_type = 'FLOW'
# fluid has to be before constructive modifiers,
# so it might not be the last modifier
for mod in obj.modifiers:
if mod.type == 'FLUID_SIMULATION':
break
# set type
obj.modifiers[-1].flow_settings.flow_type = 'LIQUID'
if self.style == 'INFLOW':
mod.settings.type = 'INFLOW'
mod.settings.inflow_velocity = self.initial_velocity
else:
mod.settings.type = 'FLUID'
mod.settings.initial_velocity = self.initial_velocity
# set flow behavior
obj.modifiers[-1].flow_settings.flow_behavior = 'GEOMETRY'
# use some surface distance for smoke emission
obj.modifiers[-1].flow_settings.surface_distance = 0.0
obj.hide_render = not self.show_flows
if not self.show_flows:
obj.display_type = 'WIRE'
# store bounding box min/max for the domain object
obj_bb_minmax(obj, min_co, max_co)
# add the fluid domain object
# add the liquid domain object
bpy.ops.mesh.primitive_cube_add()
obj = context.active_object
obj.name = "Fluid Domain"
obj.name = "Liquid Domain"
# give the fluid some room below the flows
# and scale with initial velocity
v = 0.5 * self.initial_velocity
obj.location = 0.5 * (max_co + min_co) + Vector((0.0, 0.0, -1.0)) + v
obj.scale = (
0.5 * (max_co - min_co) +
Vector((1.0, 1.0, 2.0)) +
Vector((abs(v[0]), abs(v[1]), abs(v[2])))
)
# give the liquid some room above the flows
obj.location = 0.5 * (max_co + min_co) + Vector((0.0, 0.0, -1.0))
obj.scale = 0.5 * (max_co - min_co) + Vector((1.0, 1.0, 2.0))
# setup smoke domain
bpy.ops.object.modifier_add(type='FLUID_SIMULATION')
obj.modifiers[-1].settings.type = 'DOMAIN'
# setup liquid domain
bpy.ops.object.modifier_add(type='FLUID')
obj.modifiers[-1].fluid_type = 'DOMAIN'
obj.modifiers[-1].domain_settings.domain_type = 'LIQUID'
# set all domain borders to obstacle
obj.modifiers[-1].domain_settings.use_collision_border_front = True
obj.modifiers[-1].domain_settings.use_collision_border_back = True
obj.modifiers[-1].domain_settings.use_collision_border_right = True
obj.modifiers[-1].domain_settings.use_collision_border_left = True
obj.modifiers[-1].domain_settings.use_collision_border_top = True
obj.modifiers[-1].domain_settings.use_collision_border_bottom = True
# set correct cache file format for liquid
obj.modifiers[-1].domain_settings.cache_mesh_format = 'BOBJECT'
# allocate and show particle system for FLIP
obj.modifiers[-1].domain_settings.use_flip_particles = True
# make the domain smooth so it renders nicely
bpy.ops.object.shade_smooth()
@@ -529,7 +520,7 @@ class QuickFluid(ObjectModeOperator, Operator):
# create a ray-transparent material for the domain
bpy.ops.object.material_slot_add()
mat = bpy.data.materials.new("Fluid Domain Material")
mat = bpy.data.materials.new("Liquid Domain Material")
obj.material_slots[0].material = mat
# Make sure we use nodes
@@ -560,15 +551,12 @@ class QuickFluid(ObjectModeOperator, Operator):
links.new(node_absorption.outputs["Volume"], node_out.inputs["Volume"])
node_absorption.inputs["Color"].default_value = (0.8, 0.9, 1.0, 1.0)
if self.start_baking:
bpy.ops.fluid.bake('INVOKE_DEFAULT')
return {'FINISHED'}
classes = (
QuickExplode,
QuickFluid,
QuickFur,
QuickSmoke,
QuickLiquid,
)

View File

@@ -382,16 +382,16 @@ class AddPresetFluid(AddPresetBase, Operator):
"""Add or remove a Fluid Preset"""
bl_idname = "fluid.preset_add"
bl_label = "Add Fluid Preset"
preset_menu = "FLUID_PT_presets"
preset_menu = "FLUID_MT_presets"
preset_defines = [
"fluid = bpy.context.fluid"
]
]
preset_values = [
"fluid.settings.viscosity_base",
"fluid.settings.viscosity_exponent",
]
"fluid.domain_settings.viscosity_base",
"fluidanta.domain_settings.viscosity_exponent",
]
preset_subdir = "fluid"

View File

@@ -54,10 +54,9 @@ _modules = [
"properties_physics_common",
"properties_physics_dynamicpaint",
"properties_physics_field",
"properties_physics_fluid",
"properties_physics_rigidbody",
"properties_physics_rigidbody_constraint",
"properties_physics_smoke",
"properties_physics_fluid",
"properties_physics_softbody",
"properties_render",
"properties_output",

View File

@@ -945,7 +945,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel):
col.prop(md, "angle")
col.prop(md, "limits", slider=True)
def SMOKE(self, layout, _ob, _md):
def FLUID(self, layout, _ob, _md):
layout.label(text="Settings are inside the Physics tab")
def SMOOTH(self, layout, ob, md):

View File

@@ -55,7 +55,7 @@ def particle_panel_poll(cls, context):
if not settings:
return False
return settings.is_fluid is False and (engine in cls.COMPAT_ENGINES)
return (settings.is_fluid is False) and (engine in cls.COMPAT_ENGINES)
def particle_get_settings(context):
@@ -207,7 +207,7 @@ class PARTICLE_PT_context_particles(ParticleButtonsPanel, Panel):
col = layout.column()
if part.is_fluid is False:
if (part.is_fluid is False):
row = col.row()
row.enabled = particle_panel_enabled(context, psys)
row.template_ID(psys, "settings", new="particle.new")

View File

@@ -99,8 +99,7 @@ class PHYSICS_PT_add(PhysicButtonsPanel, Panel):
physics_add(col, context.soft_body, "Soft Body", 'SOFT_BODY', 'MOD_SOFT', True)
if obj.type == 'MESH':
physics_add(col, context.fluid, "Fluid", 'FLUID_SIMULATION', 'MOD_FLUIDSIM', True)
physics_add(col, context.smoke, "Smoke", 'SMOKE', 'MOD_SMOKE', True)
physics_add(col, context.fluid, "Fluid", 'FLUID', 'MOD_FLUIDSIM', True)
physics_add_special(
col, obj.rigid_body, "Rigid Body",
@@ -118,7 +117,7 @@ class PHYSICS_PT_add(PhysicButtonsPanel, Panel):
)
# cache-type can be 'PSYS' 'HAIR' 'SMOKE' etc.
# cache-type can be 'PSYS' 'HAIR' 'FLUID' etc.
def point_cache_ui(self, cache, enabled, cachetype):
layout = self.layout
@@ -141,10 +140,10 @@ def point_cache_ui(self, cache, enabled, cachetype):
col.operator("ptcache.add", icon='ADD', text="")
col.operator("ptcache.remove", icon='REMOVE', text="")
if cachetype in {'PSYS', 'HAIR', 'SMOKE'}:
if cachetype in {'PSYS', 'HAIR', 'FLUID'}:
col = layout.column()
if cachetype == 'SMOKE':
if cachetype == 'FLUID':
col.prop(cache, "use_library_path", text="Use Library Path")
col.prop(cache, "use_external")
@@ -160,14 +159,14 @@ def point_cache_ui(self, cache, enabled, cachetype):
col.alignment = 'RIGHT'
col.label(text=cache_info)
else:
if cachetype in {'SMOKE', 'DYNAMIC_PAINT'}:
if cachetype in {'FLUID', 'DYNAMIC_PAINT'}:
if not is_saved:
col = layout.column(align=True)
col.alignment = 'RIGHT'
col.label(text="Cache is disabled until the file is saved")
layout.enabled = False
if not cache.use_external or cachetype == 'SMOKE':
if not cache.use_external or cachetype == 'FLUID':
col = layout.column(align=True)
if cachetype not in {'PSYS', 'DYNAMIC_PAINT'}:
@@ -175,18 +174,18 @@ def point_cache_ui(self, cache, enabled, cachetype):
col.prop(cache, "frame_start", text="Simulation Start")
col.prop(cache, "frame_end")
if cachetype not in {'SMOKE', 'CLOTH', 'DYNAMIC_PAINT', 'RIGID_BODY'}:
if cachetype not in {'FLUID', 'CLOTH', 'DYNAMIC_PAINT', 'RIGID_BODY'}:
col.prop(cache, "frame_step")
cache_info = cache.info
if cachetype != 'SMOKE' and cache_info: # avoid empty space.
if cachetype != 'FLUID' and cache_info: # avoid empty space.
col = layout.column(align=True)
col.alignment = 'RIGHT'
col.label(text=cache_info)
can_bake = True
if cachetype not in {'SMOKE', 'DYNAMIC_PAINT', 'RIGID_BODY'}:
if cachetype not in {'FLUID', 'DYNAMIC_PAINT', 'RIGID_BODY'}:
if not is_saved:
col = layout.column(align=True)
col.alignment = 'RIGHT'
@@ -269,7 +268,7 @@ def effector_weights_ui(self, weights, weight_type):
col.prop(weights, "curve_guide", slider=True)
col.prop(weights, "texture", slider=True)
if weight_type != 'SMOKE':
if weight_type != 'FLUID':
col.prop(weights, "smokeflow", slider=True)
col = flow.column()

View File

@@ -21,16 +21,17 @@
import bpy
from bpy.types import (
Panel,
Menu,
)
from .properties_physics_common import (
effector_weights_ui,
)
from bpy.app.translations import pgettext_iface as iface_
from bl_ui.utils import PresetPanel
class FLUID_PT_presets(PresetPanel, Panel):
class FLUID_MT_presets(Menu):
bl_label = "Fluid Presets"
preset_subdir = "fluid"
preset_operator = "script.execute_preset"
preset_add_operator = "fluid.preset_add"
draw = Menu.draw_preset
class PhysicButtonsPanel:
@@ -44,15 +45,8 @@ class PhysicButtonsPanel:
if not ((ob and ob.type == 'MESH') and (context.fluid)):
return False
return (bpy.app.build_options.mod_fluid)
@staticmethod
def poll_fluid_settings(context):
if not (PhysicButtonsPanel.poll_fluid(context)):
return False
md = context.fluid
return md and md.settings and (md.settings.type != 'NONE')
return md and (context.fluid.fluid_type != 'NONE')
@staticmethod
def poll_fluid_domain(context):
@@ -60,7 +54,37 @@ class PhysicButtonsPanel:
return False
md = context.fluid
return md and md.settings and (md.settings.type == 'DOMAIN')
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')
class PHYSICS_PT_fluid(PhysicButtonsPanel, Panel):
@@ -70,249 +94,277 @@ class PHYSICS_PT_fluid(PhysicButtonsPanel, Panel):
@classmethod
def poll(cls, context):
ob = context.object
return (ob and ob.type == 'MESH') and context.engine in cls.COMPAT_ENGINES and (context.fluid)
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.mod_fluid:
col = layout.column()
if not bpy.app.build_options.fluid:
col = layout.column(align=True)
col.alignment = 'RIGHT'
col.label(text="Built without fluids")
col.label(text="Built without Fluid modifier")
return
md = context.fluid
fluid = md.settings
col = layout.column()
col.prop(fluid, "type")
layout.prop(md, "fluid_type")
class PHYSICS_PT_fluid_flow(PhysicButtonsPanel, Panel):
bl_label = "Flow"
bl_parent_id = "PHYSICS_PT_fluid"
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):
md = context.fluid
fluid = md.settings
if not PhysicButtonsPanel.poll_fluid_settings(context):
if not PhysicButtonsPanel.poll_fluid(context):
return False
return fluid.type in {'INFLOW', 'OUTFLOW', 'CONTROL'} and (context.engine in cls.COMPAT_ENGINES)
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
# Deactivate UI if guiding is enabled but not baked yet
layout.active = not (domain.use_guiding and not domain.cache_baked_guiding and (domain.guiding_source == "EFFECTOR" or (domain.guiding_source == "DOMAIN" and not domain.guiding_parent)))
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_any = domain.cache_baked_data or domain.cache_baked_mesh or domain.cache_baked_particles or domain.cache_baked_noise or domain.cache_baked_guiding
baked_data = domain.cache_baked_data
row = layout.row()
row.enabled = not baking_any and not 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 baking_any and not baked_data
col = flow.column()
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()
col.prop(domain, "use_adaptive_stepping", text="Use Adaptive Stepping")
col1 = col.column(align=True)
col1.enabled = domain.use_adaptive_stepping
col1.prop(domain, "timesteps_maximum", text="Timesteps Maximum")
col1.prop(domain, "timesteps_minimum", 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")
# TODO (sebbas): Clipping var useful for manta openvdb caching?
# col.prop(domain, "clipping", text="Empty Space")
if domain.cache_type == "MODULAR":
col.separator()
split = layout.split()
bake_incomplete = (domain.cache_frame_pause_data < domain.cache_frame_end)
if domain.cache_baked_data and not domain.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.cache_baking_data and not domain.cache_baked_data:
split.enabled = False
split.operator("fluid.pause_bake", text="Baking Data - ESC to pause")
elif not domain.cache_baked_data and not domain.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':
effec = md.effec_settings
row = layout.row()
row.prop(effec, "effec_type")
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
col = flow.column()
col.prop(effec, "use_plane_init", text="Is Planar")
col.prop(effec, "surface_distance", text="Surface Thickness")
if effec.effec_type == "GUIDE":
col.prop(effec, "velocity_factor", text="Velocity Factor")
col = flow.column()
col.prop(effec, "guiding_mode", text="Guiding 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
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_any = domain.cache_baked_data or domain.cache_baked_mesh or domain.cache_baked_particles or domain.cache_baked_noise or domain.cache_baked_guiding
baked_data = domain.cache_baked_data
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = not baking_any and not 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 = "Smoke"
bl_parent_id = 'PHYSICS_PT_settings'
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
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_any = domain.cache_baked_data or domain.cache_baked_mesh or domain.cache_baked_particles or domain.cache_baked_noise or domain.cache_baked_guiding
baked_data = domain.cache_baked_data
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = not baking_any and not baked_data
col = flow.column()
col.prop(domain, "alpha")
col.prop(domain, "beta", text="Temperature Diff.")
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
fluid = md.settings
domain = md.domain_settings
self.layout.prop(fluid, "use", text="")
self.layout.prop(domain, "use_dissolve_smoke", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
md = context.fluid
fluid = md.settings
domain = md.domain_settings
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_any = domain.cache_baked_data or domain.cache_baked_mesh or domain.cache_baked_particles or domain.cache_baked_noise or domain.cache_baked_guiding
baked_data = domain.cache_baked_data
flow.active = fluid.use
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = not baking_any and not baked_data
if fluid.type == 'INFLOW':
col = flow.column()
col.prop(fluid, "volume_initialization", text="Volume Initialization")
col.prop(fluid, "use_animated_mesh")
layout.active = domain.use_dissolve_smoke
row = col.row()
row.active = not fluid.use_animated_mesh
row.prop(fluid, "use_local_coords")
col = flow.column()
col.prop(domain, "dissolve_speed", text="Time")
col = flow.column()
col.prop(fluid, "inflow_velocity", text="Inflow Velocity")
elif fluid.type == 'OUTFLOW':
col = flow.column()
col.prop(fluid, "volume_initialization", text="Volume Initialization")
col = flow.column()
col.prop(fluid, "use_animated_mesh")
elif fluid.type == 'CONTROL':
col = flow.column()
col.prop(fluid, "quality", slider=True)
col.prop(fluid, "use_reverse_frames")
col = flow.column()
col.prop(fluid, "start_time", text="Time Start")
col.prop(fluid, "end_time", text="End")
col.separator()
col = flow.column()
col.prop(fluid, "attraction_strength", text="Attraction Strength")
col.prop(fluid, "attraction_radius", text="Radius")
col.separator()
col = flow.column(align=True)
col.prop(fluid, "velocity_strength", text="Velocity Strength")
col.prop(fluid, "velocity_radius", text="Radius")
col = flow.column()
col.prop(domain, "use_dissolve_smoke_log", text="Slow")
class PHYSICS_PT_fluid_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):
md = context.fluid
fluid = md.settings
if not PhysicButtonsPanel.poll_fluid_settings(context):
return False
return fluid.type in {'DOMAIN', 'FLUID', 'OBSTACLE', 'PARTICLE'} and (context.engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
md = context.fluid
fluid = md.settings
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
if fluid.type not in {'NONE', 'DOMAIN', 'PARTICLE', 'FLUID', 'OBSTACLE'}:
flow.active = fluid.use
if fluid.type == 'DOMAIN':
col = flow.column()
if bpy.app.build_options.openmp:
col.prop(fluid, "threads", text="Simulation Threads")
col.separator()
col.prop(fluid, "resolution", text="Final Resolution")
col.prop(fluid, "preview_resolution", text="Preview")
col.separator()
col = flow.column()
col.prop(fluid, "render_display_mode", text="Render Display")
col.prop(fluid, "viewport_display_mode", text="Viewport")
col.separator()
col = flow.column()
sub = col.column(align=True)
sub.prop(fluid, "start_time", text="Time Start")
sub.prop(fluid, "end_time", text="End")
col.prop(fluid, "simulation_rate", text="Speed")
col = flow.column()
col.prop(fluid, "use_speed_vectors")
col.prop(fluid, "use_reverse_frames")
col.prop(fluid, "frame_offset", text="Offset")
elif fluid.type == 'FLUID':
col = flow.column()
col.prop(fluid, "volume_initialization", text="Volume Initialization")
col.prop(fluid, "use_animated_mesh")
col = flow.column()
col.prop(fluid, "initial_velocity", text="Initial Velocity")
elif fluid.type == 'OBSTACLE':
col = flow.column()
col.prop(fluid, "volume_initialization", text="Volume Initialization")
col.prop(fluid, "use_animated_mesh")
col = flow.column()
subcol = col.column()
subcol.enabled = not fluid.use_animated_mesh
subcol.prop(fluid, "slip_type", text="Slip Type")
if fluid.slip_type == 'PARTIALSLIP':
subcol.prop(fluid, "partial_slip_factor", text="Amount", slider=True)
col.prop(fluid, "impact_factor", text="Impact Factor")
elif fluid.type == 'PARTICLE':
col = flow.column()
col.prop(fluid, "particle_influence", text="Influence Size")
col.prop(fluid, "alpha_influence", text="Alpha")
col = flow.column()
col.prop(fluid, "use_drops")
col.prop(fluid, "use_floats")
col.prop(fluid, "show_tracer")
class PHYSICS_PT_fluid_particle_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_settings(context):
return False
md = context.fluid
return md and md.settings and (md.settings.type == 'PARTICLE') and (context.engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
md = context.fluid
fluid = md.settings
layout.prop(fluid, "filepath", text="")
class PHYSICS_PT_domain_bake(PhysicButtonsPanel, Panel):
bl_label = "Bake"
bl_parent_id = 'PHYSICS_PT_fluid'
class PHYSICS_PT_fire(PhysicButtonsPanel, Panel):
bl_label = "Fire"
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_domain(context):
return False
return (context.engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
md = context.fluid
fluid = md.settings
row = layout.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Cache Path")
layout.prop(fluid, "filepath", text="")
# odd formatting here so translation script can extract string
layout.operator(
"fluid.bake", text=iface_("Bake (Req. Memory: %s)") % fluid.memory_estimate,
translate=False, icon='MOD_FLUIDSIM'
)
class PHYSICS_PT_domain_gravity(PhysicButtonsPanel, Panel):
bl_label = "World"
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):
if not PhysicButtonsPanel.poll_gas_domain(context):
return False
return (context.engine in cls.COMPAT_ENGINES)
@@ -321,110 +373,622 @@ class PHYSICS_PT_domain_gravity(PhysicButtonsPanel, Panel):
layout = self.layout
layout.use_property_split = True
fluid = context.fluid.settings
scene = context.scene
md = context.fluid
domain = md.domain_settings
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_any = domain.cache_baked_data or domain.cache_baked_mesh or domain.cache_baked_particles or domain.cache_baked_noise or domain.cache_baked_guiding
baked_data = domain.cache_baked_data
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = not baking_any and not baked_data
col = flow.column()
col.prop(domain, "burning_rate", text="Reaction Speed")
col = flow.column()
col.prop(domain, "flame_smoke", text="Flame Smoke")
col = flow.column()
col.prop(domain, "flame_vorticity", text="Flame Vorticity")
col = flow.column()
col.prop(domain, "flame_ignition", text="Temperature Ignition")
col = flow.column()
col.prop(domain, "flame_max_temp", text="Maximum Temperature")
col = flow.column()
col.prop(domain, "flame_smoke_color", text="Flame Color")
class PHYSICS_PT_liquid(PhysicButtonsPanel, Panel):
bl_label = "Liquid"
bl_parent_id = 'PHYSICS_PT_settings'
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
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
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_any = domain.cache_baked_data or domain.cache_baked_mesh or domain.cache_baked_particles or domain.cache_baked_noise or domain.cache_baked_guiding
baked_data = domain.cache_baked_data
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
col = flow.column()
col0 = col.column()
col0.enabled = not baking_any and not baked_data
col0.prop(domain, "simulation_method", expand=False)
col0.prop(domain, "flip_ratio", text="FLIP Ratio")
col0.prop(domain, "particle_radius", text="Particle Radius")
col1 = flow.column(align=True)
col1.enabled = not baking_any and not baked_data
col1.prop(domain, "particle_maximum", text="Particles Maximum")
col1.prop(domain, "particle_minimum", text="Minimum")
col1 = flow.column()
col1.enabled = not baking_any and not baked_data
col1.prop(domain, "particle_number", text="Particle Sampling")
col1.prop(domain, "particle_band_width", text="Narrow Band Width")
col1.prop(domain, "particle_randomness", text="Particle Randomness")
col2 = flow.column()
col2.enabled = not baking_any and not baked_data
col2.prop(domain, "use_fractions", text="Fractional Obstacles")
col3 = col2.column()
col3.enabled = domain.use_fractions and col2.enabled
col3.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")
use_gravity = scene.use_gravity
use_units = scene.unit_settings.system != 'NONE'
grid = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
if use_gravity or use_units:
s_gravity = " Gravity" if use_gravity else ""
s_units = " Units" if use_units else ""
s_and = " and " if use_gravity and use_units else ""
warn = f"Using {s_gravity}{s_and}{s_units} from Scene"
col = grid.column()
if flow.flow_source == 'MESH':
col.prop(flow, "use_plane_init", text="Is Planar")
col.prop(flow, "surface_distance", text="Surface Thickness")
if flow.flow_type in {'SMOKE', 'BOTH', 'FIRE'}:
col = grid.column()
col.prop(flow, "volume_density", text="Volume Density")
if flow.flow_source == 'PARTICLES':
col.prop(flow, "use_particle_size", text="Set Size")
sub = col.column()
sub.alignment = 'RIGHT'
sub.label(text=warn)
sub.active = flow.use_particle_size
sub.prop(flow, "particle_size")
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
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
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
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.enabled = not use_gravity
sub.prop(fluid, "gravity", text="Gravity")
sub.active = flow_smoke.use_texture
sub = col.column()
sub.enabled = not use_units
sub.prop(fluid, "simulation_scale", text="Scene Size Meters" if use_units else "World Size Meters")
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_fluid'
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
return (context.engine in cls.COMPAT_ENGINES)
def draw_header(self, context):
md = context.fluid.domain_settings
domain = context.fluid.domain_settings
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_any = domain.cache_baked_data or domain.cache_baked_mesh or domain.cache_baked_particles or domain.cache_baked_noise or domain.cache_baked_guiding
self.layout.enabled = not baking_any and not 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
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_any = domain.cache_baked_data or domain.cache_baked_mesh or domain.cache_baked_particles or domain.cache_baked_noise or domain.cache_baked_guiding
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
flow.enabled = not baking_any and not 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(fluid, "grid_levels", text="Optimization", slider=True)
col.prop(fluid, "compressibility", slider=True)
col.prop(domain, "adapt_threshold", text="Threshold")
class PHYSICS_PT_domain_viscosity(PhysicButtonsPanel, Panel):
bl_label = "Viscosity"
class PHYSICS_PT_noise(PhysicButtonsPanel, Panel):
bl_label = "Noise"
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):
if not PhysicButtonsPanel.poll_gas_domain(context):
return False
return (context.engine in cls.COMPAT_ENGINES)
def draw_header_preset(self, _context):
FLUID_PT_presets.draw_panel_header(self.layout)
def draw_header(self, context):
md = context.fluid.domain_settings
domain = context.fluid.domain_settings
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
self.layout.enabled = not baking_any
self.layout.prop(md, "use_noise", 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)
fluid = context.fluid.settings
domain = context.fluid.domain_settings
# Deactivate UI if guiding is enabled but not baked yet
layout.active = domain.use_noise and not (domain.use_guiding and not domain.cache_baked_guiding and (domain.guiding_source == "EFFECTOR" or (domain.guiding_source == "DOMAIN" and not domain.guiding_parent)))
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_noise = domain.cache_baked_noise
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = not baking_any and not baked_noise
col = flow.column()
col.prop(fluid, "viscosity_base", text="Base")
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(fluid, "viscosity_exponent", text="Exponent", slider=True)
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()
split = layout.split()
split.enabled = domain.cache_baked_data
bake_incomplete = (domain.cache_frame_pause_noise < domain.cache_frame_end)
if domain.cache_baked_noise and not domain.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.cache_baked_noise and domain.cache_baking_noise:
split.enabled = False
split.operator("fluid.pause_bake", text="Baking Noise - ESC to pause")
elif not domain.cache_baked_noise and not domain.cache_baking_noise:
split.operator("fluid.bake_noise", text="Bake Noise")
else:
split.operator("fluid.free_noise", text="Free Noise")
class PHYSICS_PT_domain_boundary(PhysicButtonsPanel, Panel):
bl_label = "Boundary"
class PHYSICS_PT_mesh(PhysicButtonsPanel, Panel):
bl_label = "Mesh"
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):
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
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
self.layout.enabled = not baking_any
self.layout.prop(md, "use_mesh", 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)
fluid = context.fluid.settings
domain = context.fluid.domain_settings
# Deactivate UI if guiding is enabled but not baked yet
layout.active = domain.use_mesh and not (domain.use_guiding and not domain.cache_baked_guiding and (domain.guiding_source == "EFFECTOR" or (domain.guiding_source == "DOMAIN" and not domain.guiding_parent)))
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_mesh = domain.cache_baked_mesh
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = not baking_any and not baked_mesh
col = flow.column()
col.prop(fluid, "slip_type", text="Type")
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 fluid.slip_type == 'PARTIALSLIP':
col.prop(fluid, "partial_slip_factor", slider=True, text="Amount")
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()
col.prop(fluid, "surface_smooth", text="Surface Smoothing")
col.prop(fluid, "surface_subdivisions", text="Subdivisions")
col.prop(fluid, "use_surface_noobs")
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()
split = layout.split()
split.enabled = domain.cache_baked_data
bake_incomplete = (domain.cache_frame_pause_mesh < domain.cache_frame_end)
if domain.cache_baked_mesh and not domain.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.cache_baked_mesh and domain.cache_baking_mesh:
split.enabled = False
split.operator("fluid.pause_bake", text="Baking Mesh - ESC to pause")
elif not domain.cache_baked_mesh and not domain.cache_baking_mesh:
split.operator("fluid.bake_mesh", text="Bake Mesh")
else:
split.operator("fluid.free_mesh", text="Free Mesh")
class PHYSICS_PT_domain_particles(PhysicButtonsPanel, Panel):
class PHYSICS_PT_particles(PhysicButtonsPanel, Panel):
bl_label = "Particles"
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_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
# Deactivate UI if guiding is enabled but not baked yet
layout.active = not (domain.use_guiding and not domain.cache_baked_guiding and (domain.guiding_source == "EFFECTOR" or (domain.guiding_source == "DOMAIN" and not domain.guiding_parent)))
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_particles = domain.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 baking_any
subSpray = flow.column()
subSpray.enabled = (domain.sndparticle_combined_export == 'OFF') or (domain.sndparticle_combined_export == 'FOAM + BUBBLES')
subSpray.prop(domain, "use_spray_particles", text="Spray")
subFoam = flow.column()
subFoam.enabled = (domain.sndparticle_combined_export == 'OFF') or (domain.sndparticle_combined_export == 'SPRAY + BUBBLES')
subFoam.prop(domain, "use_foam_particles", text="Foam")
subBubbles = flow.column()
subBubbles.enabled = (domain.sndparticle_combined_export == 'OFF') or (domain.sndparticle_combined_export == 'SPRAY + FOAM')
subBubbles.prop(domain, "use_bubble_particles", text="Bubbles")
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = not baking_any and not baked_particles and using_particles
col = flow.column()
col.prop(domain, "sndparticle_combined_export")
col.prop(domain, "particle_scale", text="Upres Factor")
col.separator()
col = flow.column(align=True)
col.prop(domain, "sndparticle_tau_max_wc", text="Wave Crest Potential Maximum")
col.prop(domain, "sndparticle_tau_min_wc", text="Minimum")
col.separator()
col = flow.column(align=True)
col.prop(domain, "sndparticle_tau_max_ta", text="Trapped Air Potential Maximum")
col.prop(domain, "sndparticle_tau_min_ta", text="Minimum")
col.separator()
col = flow.column(align=True)
col.prop(domain, "sndparticle_tau_max_k", text="Kinetic Energy Potential Maximum")
col.prop(domain, "sndparticle_tau_min_k", 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_k_wc", text="Wave Crest Particle Sampling")
col.prop(domain, "sndparticle_k_ta", text="Trapped Air Particle Sampling")
col.separator()
col = flow.column(align=True)
col.prop(domain, "sndparticle_l_max", text="Particle Life Maximum")
col.prop(domain, "sndparticle_l_min", text="Minimum")
col.separator()
col = flow.column(align=True)
col.prop(domain, "sndparticle_k_b", text="Bubble Buoyancy")
col.prop(domain, "sndparticle_k_d", text="Bubble Drag")
col.separator()
col = flow.column()
col.prop(domain, "sndparticle_boundary", text="Particles in Boundary:")
if domain.cache_type == "MODULAR":
col.separator()
split = layout.split()
split.enabled = domain.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.cache_baked_particles and not domain.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.cache_baked_particles and domain.cache_baking_particles:
split.enabled = False
split.operator("fluid.pause_bake", text="Baking Particles - ESC to pause")
elif not domain.cache_baked_particles and not domain.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_fluid'
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(self, context):
layout = self.layout
layout.use_property_split = True
domain = context.fluid.domain_settings
# Deactivate UI if guiding is enabled but not baked yet
layout.active = not (domain.use_guiding and not domain.cache_baked_guiding and (domain.guiding_source == "EFFECTOR" or (domain.guiding_source == "DOMAIN" and not domain.guiding_parent)))
split = layout.split()
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_any = domain.cache_baked_data or domain.cache_baked_mesh or domain.cache_baked_particles or domain.cache_baked_noise or domain.cache_baked_guiding
baked_data = domain.cache_baked_data
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = not baking_any and not baked_any and not baked_data
row = flow.row()
col = row.column()
col.label(text="Viscosity Presets:")
col.menu("FLUID_MT_presets", text=bpy.types.FLUID_MT_presets.bl_label)
col = row.column(align=True)
col.operator("fluid.preset_add", text="", icon='ADD')
col.operator("fluid.preset_add", text="", icon='REMOVE').remove_active = True
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_guiding(PhysicButtonsPanel, Panel):
bl_label = "Guiding"
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
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
self.layout.enabled = not baking_any
self.layout.prop(md, "use_guiding", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
domain = context.fluid.domain_settings
layout.active = domain.use_guiding
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_data = domain.cache_baked_data
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = not baking_any and not baked_data
col = flow.column()
col.prop(domain, "guiding_alpha", text="Weight")
col.prop(domain, "guiding_beta", text="Size")
col.prop(domain, "guiding_vel_factor", text="Velocity Factor")
col = flow.column()
col.prop(domain, "guiding_source", text="Velocity Source")
if domain.guiding_source == "DOMAIN":
col.prop(domain, "guiding_parent", text="Guiding Parent")
if domain.cache_type == "MODULAR":
col.separator()
if domain.guiding_source == "EFFECTOR":
split = layout.split()
bake_incomplete = (domain.cache_frame_pause_guiding < domain.cache_frame_end)
if domain.cache_baked_guiding and not domain.cache_baking_guiding and bake_incomplete:
col = split.column()
col.operator("fluid.bake_guiding", text="Resume")
col = split.column()
col.operator("fluid.free_guiding", text="Free")
elif not domain.cache_baked_guiding and domain.cache_baking_guiding:
split.operator("fluid.pause_bake", text="Pause Guiding")
elif not domain.cache_baked_guiding and not domain.cache_baking_guiding:
split.operator("fluid.bake_guiding", text="Bake Guiding")
else:
split.operator("fluid.free_guiding", text="Free Guiding")
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
@@ -434,33 +998,277 @@ class PHYSICS_PT_domain_particles(PhysicButtonsPanel, Panel):
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
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_any = domain.cache_baked_data or domain.cache_baked_mesh or domain.cache_baked_particles or domain.cache_baked_noise or domain.cache_baked_guiding
col = layout.column()
col.prop(domain, "cache_directory", text="")
col.enabled = not 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 baking_any
col = flow.column(align=True)
col.separator()
col.prop(domain, "cache_frame_start", text="Frame Start")
col.prop(domain, "cache_frame_end", text="End")
col.enabled = not baking_any
col.separator()
col = flow.column()
col.enabled = not baking_any and not baked_any
col.prop(domain, "cache_data_format", text="Data File Format")
if md.domain_settings.domain_type in {'GAS'}:
if domain.use_noise:
col.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)
col.prop(domain, "cache_particle_format", text="Particle File Format")
if domain.use_mesh:
col.prop(domain, "cache_mesh_format", text="Mesh File Format")
if domain.cache_type == "FINAL":
col.separator()
split = layout.split()
bake_incomplete = (domain.cache_frame_pause_data < domain.cache_frame_end)
if domain.cache_baked_data and not domain.cache_baking_data and bake_incomplete:
col = split.column()
col.operator("fluid.bake_all", text="Resume")
col = split.column()
col.operator("fluid.free_all", text="Free")
elif domain.cache_baking_data and not domain.cache_baked_data:
split.enabled = False
split.operator("fluid.pause_bake", text="Baking All - ESC to pause")
elif not domain.cache_baked_data and not domain.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):
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 = context.fluid.domain_settings
baking_any = domain.cache_baking_data or domain.cache_baking_mesh or domain.cache_baking_particles or domain.cache_baking_noise or domain.cache_baking_guiding
baked_any = domain.cache_baked_data or domain.cache_baked_mesh or domain.cache_baked_particles or domain.cache_baked_noise or domain.cache_baked_guiding
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = not baking_any and not 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)
fluid = context.fluid.settings
domain = context.fluid.domain_settings
col = flow.column()
col.prop(fluid, "tracer_particles", text="Tracer")
col.prop(domain, "display_thickness")
col.separator()
col.prop(domain, "slice_method", text="Slicing")
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 = col.column()
col.enabled = do_axis_slicing
col.prop(domain, "axis_slice_method")
col = flow.column()
col.prop(fluid, "generate_particles", text="Generate")
sub = col.column()
sub.enabled = not do_full_slicing and do_axis_slicing
sub.prop(domain, "slice_axis")
sub.prop(domain, "slice_depth")
row = col.row()
row.enabled = do_full_slicing or not do_axis_slicing
row.prop(domain, "slice_per_voxel")
col.prop(domain, "display_interpolation")
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.enabled = 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.enabled = domain.show_velocity
col.prop(domain, "vector_display_type", text="Display As")
col.prop(domain, "vector_scale")
classes = (
FLUID_PT_presets,
FLUID_MT_presets,
PHYSICS_PT_fluid,
PHYSICS_PT_fluid_settings,
PHYSICS_PT_fluid_flow,
PHYSICS_PT_fluid_particle_cache,
PHYSICS_PT_domain_bake,
PHYSICS_PT_domain_boundary,
PHYSICS_PT_domain_particles,
PHYSICS_PT_domain_gravity,
PHYSICS_PT_domain_viscosity,
PHYSICS_PT_settings,
PHYSICS_PT_borders,
PHYSICS_PT_smoke,
PHYSICS_PT_smoke_dissolve,
PHYSICS_PT_fire,
PHYSICS_PT_liquid,
PHYSICS_PT_flow_source,
PHYSICS_PT_flow_initial_velocity,
PHYSICS_PT_flow_texture,
PHYSICS_PT_adaptive_domain,
PHYSICS_PT_noise,
PHYSICS_PT_mesh,
PHYSICS_PT_particles,
PHYSICS_PT_diffusion,
PHYSICS_PT_guiding,
PHYSICS_PT_collections,
PHYSICS_PT_cache,
PHYSICS_PT_export,
PHYSICS_PT_field_weights,
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:

View File

@@ -1,692 +0,0 @@
# ##### 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 (
Panel,
)
from bl_ui.properties_physics_common import (
point_cache_ui,
effector_weights_ui,
)
class PhysicButtonsPanel:
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "physics"
@staticmethod
def poll_smoke(context):
ob = context.object
if not ((ob and ob.type == 'MESH') and (context.smoke)):
return False
md = context.smoke
return md and (context.smoke.smoke_type != 'NONE') and (bpy.app.build_options.mod_smoke)
@staticmethod
def poll_smoke_domain(context):
if not PhysicButtonsPanel.poll_smoke(context):
return False
md = context.smoke
return md and (md.smoke_type == 'DOMAIN')
class PHYSICS_PT_smoke(PhysicButtonsPanel, Panel):
bl_label = "Smoke"
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.smoke)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
if not bpy.app.build_options.mod_smoke:
col = layout.column(align=True)
col.alignment = 'RIGHT'
col.label(text="Built without Smoke modifier")
return
md = context.smoke
layout.prop(md, "smoke_type")
class PHYSICS_PT_smoke_settings(PhysicButtonsPanel, Panel):
bl_label = "Settings"
bl_parent_id = 'PHYSICS_PT_smoke'
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
@classmethod
def poll(cls, context):
if not PhysicButtonsPanel.poll_smoke(context):
return False
return (context.engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
md = context.smoke
ob = context.object
if md.smoke_type == 'DOMAIN':
domain = md.domain_settings
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
col = flow.column()
col.enabled = (not domain.point_cache.is_baked)
col.prop(domain, "resolution_max", text="Resolution Divisions")
col.prop(domain, "time_scale", text="Time Scale")
col.separator()
col = flow.column()
sub = col.row()
sub.enabled = (not domain.point_cache.is_baked)
sub.prop(domain, "collision_extents", text="Border Collisions")
# This can be tweaked after baking, for render.
col.prop(domain, "clipping", text="Empty Space")
elif md.smoke_type == 'FLOW':
flow_smoke = md.flow_settings
col = layout.column()
col.prop(flow_smoke, "smoke_flow_type", expand=False)
col.separator()
flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=True)
col = flow.column()
if flow_smoke.smoke_flow_type != 'OUTFLOW':
col.prop(flow_smoke, "smoke_flow_source", expand=False, text="Flow Source")
if flow_smoke.smoke_flow_source == 'PARTICLES':
col.prop_search(
flow_smoke, "particle_system", ob, "particle_systems",
text="Particle System"
)
else:
col.prop(flow_smoke, "surface_distance")
col.prop(flow_smoke, "volume_density")
col = flow.column()
col.prop(flow_smoke, "use_absolute")
if flow_smoke.smoke_flow_type in {'SMOKE', 'BOTH'}:
col.prop(flow_smoke, "density")
col.prop(flow_smoke, "temperature", text="Temperature Diff.")
col.separator()
col = flow.column()
col.prop(flow_smoke, "smoke_color")
if flow_smoke.smoke_flow_type in {'FIRE', 'BOTH'}:
col.prop(flow_smoke, "fuel_amount")
col.prop(flow_smoke, "subframes", text="Sampling Subframes")
col.separator()
col.prop_search(flow_smoke, "density_vertex_group", ob, "vertex_groups", text="Vertex Group")
elif md.smoke_type == 'COLLISION':
coll = md.coll_settings
col = layout.column()
col.prop(coll, "collision_type")
class PHYSICS_PT_smoke_settings_initial_velocity(PhysicButtonsPanel, Panel):
bl_label = "Initial Velocity"
bl_parent_id = 'PHYSICS_PT_smoke_settings'
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
@classmethod
def poll(cls, context):
if not PhysicButtonsPanel.poll_smoke(context):
return False
md = context.smoke
return (md and (md.smoke_type == 'FLOW')
and md.flow_settings and md.flow_settings.smoke_flow_type != 'OUTFLOW'
and context.engine in cls.COMPAT_ENGINES)
def draw_header(self, context):
md = context.smoke
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.smoke
flow_smoke = md.flow_settings
flow.active = flow_smoke.use_initial_velocity
col = flow.column(align=True)
col.prop(flow_smoke, "velocity_factor")
if flow_smoke.smoke_flow_source == 'MESH':
col = flow.column()
col.prop(flow_smoke, "velocity_normal")
# sub.prop(flow_smoke, "velocity_random")
class PHYSICS_PT_smoke_settings_particle_size(PhysicButtonsPanel, Panel):
bl_label = "Particle Size"
bl_parent_id = 'PHYSICS_PT_smoke_settings'
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
@classmethod
def poll(cls, context):
if not PhysicButtonsPanel.poll_smoke(context):
return False
md = context.smoke
return (md and (md.smoke_type == 'FLOW')
and md.flow_settings and md.flow_settings.smoke_flow_type != 'OUTFLOW'
and md.flow_settings.smoke_flow_source == 'PARTICLES'
and context.engine in cls.COMPAT_ENGINES)
def draw_header(self, context):
md = context.smoke
flow_smoke = md.flow_settings
self.layout.prop(flow_smoke, "use_particle_size", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
md = context.smoke
flow_smoke = md.flow_settings
layout.active = flow_smoke.use_particle_size
layout.prop(flow_smoke, "particle_size")
class PHYSICS_PT_smoke_behavior(PhysicButtonsPanel, Panel):
bl_label = "Behavior"
bl_parent_id = 'PHYSICS_PT_smoke_settings'
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
@classmethod
def poll(cls, context):
if not PhysicButtonsPanel.poll_smoke_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.smoke
domain = md.domain_settings
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = (not domain.point_cache.is_baked)
col = flow.column()
col.prop(domain, "alpha")
col.prop(domain, "beta", text="Temperature Diff.")
col = flow.column()
col.prop(domain, "vorticity")
class PHYSICS_PT_smoke_behavior_dissolve(PhysicButtonsPanel, Panel):
bl_label = "Dissolve"
bl_parent_id = 'PHYSICS_PT_smoke_behavior'
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
@classmethod
def poll(cls, context):
if not PhysicButtonsPanel.poll_smoke_domain(context):
return False
return (context.engine in cls.COMPAT_ENGINES)
def draw_header(self, context):
md = context.smoke
domain = md.domain_settings
self.layout.prop(domain, "use_dissolve_smoke", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
md = context.smoke
domain = md.domain_settings
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = (not domain.point_cache.is_baked)
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_smoke_flow_texture(PhysicButtonsPanel, Panel):
bl_label = "Texture"
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_smoke(context):
return False
md = context.smoke
return (md and (md.smoke_type == 'FLOW')
and (md.flow_settings.smoke_flow_source == 'MESH')
and (context.engine in cls.COMPAT_ENGINES))
def draw_header(self, context):
md = context.smoke
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.smoke.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_smoke_fire(PhysicButtonsPanel, Panel):
bl_label = "Flames"
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_smoke_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.smoke.domain_settings
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
flow.enabled = (not domain.point_cache.is_baked)
col = flow.column()
col.prop(domain, "burning_rate", text="Reaction Speed")
col.prop(domain, "flame_smoke")
col.prop(domain, "flame_vorticity")
col.separator()
col = flow.column(align=True)
col.prop(domain, "flame_ignition", text="Temperature Ignition")
col.prop(domain, "flame_max_temp")
col.separator()
sub = col.column()
sub.prop(domain, "flame_smoke_color")
class PHYSICS_PT_smoke_adaptive_domain(PhysicButtonsPanel, Panel):
bl_label = "Adaptive Domain"
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_smoke_domain(context):
return False
return (context.engine in cls.COMPAT_ENGINES)
def draw_header(self, context):
md = context.smoke.domain_settings
self.layout.prop(md, "use_adaptive_domain", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
domain = context.smoke.domain_settings
layout.active = domain.use_adaptive_domain
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
flow.enabled = (not domain.point_cache.is_baked)
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_smoke_highres(PhysicButtonsPanel, Panel):
bl_label = "High Resolution"
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_smoke_domain(context):
return False
return (context.engine in cls.COMPAT_ENGINES)
def draw_header(self, context):
md = context.smoke.domain_settings
self.layout.prop(md, "use_high_resolution", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
md = context.smoke.domain_settings
layout.active = md.use_high_resolution
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
col = flow.column()
col.enabled = not md.point_cache.is_baked
col.prop(md, "amplify", text="Resolution Divisions")
col.prop(md, "highres_sampling", text="Flow Sampling")
col.separator()
col = flow.column()
col.enabled = not md.point_cache.is_baked
col.prop(md, "noise_type", text="Noise Method")
col.prop(md, "strength")
layout.prop(md, "show_high_resolution")
class PHYSICS_PT_smoke_collections(PhysicButtonsPanel, Panel):
bl_label = "Collections"
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_smoke_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.smoke.domain_settings
col = layout.column()
col.prop(domain, "fluid_collection", text="Flow")
# col = layout.column()
# col.prop(domain, "effector_collection", text="Effector")
col.prop(domain, "collision_collection", text="Collision")
class PHYSICS_PT_smoke_cache(PhysicButtonsPanel, Panel):
bl_label = "Cache"
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_smoke_domain(context):
return False
return (context.engine in cls.COMPAT_ENGINES)
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.smoke.domain_settings
cache_file_format = domain.cache_file_format
col = flow.column()
col.prop(domain, "cache_file_format")
if cache_file_format == 'POINTCACHE':
col = flow.column()
col.prop(domain, "point_cache_compress_type", text="Compression")
col.separator()
elif cache_file_format == 'OPENVDB':
if not bpy.app.build_options.openvdb:
row = layout.row(align=True)
row.alignment = 'RIGHT'
row.label(text="Built without OpenVDB support")
return
col = flow.column()
col.prop(domain, "openvdb_cache_compress_type", text="Compression")
col.prop(domain, "data_depth", text="Data Depth")
col.separator()
cache = domain.point_cache
point_cache_ui(self, cache, (cache.is_baked is False), 'SMOKE')
class PHYSICS_PT_smoke_field_weights(PhysicButtonsPanel, Panel):
bl_label = "Field Weights"
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_smoke_domain(context):
return False
return (context.engine in cls.COMPAT_ENGINES)
def draw(self, context):
domain = context.smoke.domain_settings
effector_weights_ui(self, domain.effector_weights, 'SMOKE')
class PHYSICS_PT_smoke_viewport_display(PhysicButtonsPanel, Panel):
bl_label = "Viewport Display"
bl_parent_id = 'PHYSICS_PT_smoke'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return (PhysicButtonsPanel.poll_smoke_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.smoke.domain_settings
col = flow.column()
col.prop(domain, "display_thickness")
col.separator()
col.prop(domain, "slice_method", text="Slicing")
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 = col.column()
col.enabled = do_axis_slicing
col.prop(domain, "axis_slice_method")
col = flow.column()
sub = col.column()
sub.enabled = not do_full_slicing and do_axis_slicing
sub.prop(domain, "slice_axis")
sub.prop(domain, "slice_depth")
row = col.row()
row.enabled = do_full_slicing or not do_axis_slicing
row.prop(domain, "slice_per_voxel")
col.prop(domain, "display_interpolation")
class PHYSICS_PT_smoke_viewport_display_color(PhysicButtonsPanel, Panel):
bl_label = "Color Mapping"
bl_parent_id = 'PHYSICS_PT_smoke_viewport_display'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return (PhysicButtonsPanel.poll_smoke_domain(context))
def draw_header(self, context):
md = context.smoke.domain_settings
self.layout.prop(md, "use_color_ramp", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
domain = context.smoke.domain_settings
col = layout.column()
col.enabled = 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_smoke_viewport_display_debug(PhysicButtonsPanel, Panel):
bl_label = "Debug Velocity"
bl_parent_id = 'PHYSICS_PT_smoke_viewport_display'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return (PhysicButtonsPanel.poll_smoke_domain(context))
def draw_header(self, context):
md = context.smoke.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.smoke.domain_settings
col = flow.column()
col.enabled = domain.show_velocity
col.prop(domain, "vector_display_type", text="Display As")
col.prop(domain, "vector_scale")
classes = (
PHYSICS_PT_smoke,
PHYSICS_PT_smoke_settings,
PHYSICS_PT_smoke_settings_initial_velocity,
PHYSICS_PT_smoke_settings_particle_size,
PHYSICS_PT_smoke_behavior,
PHYSICS_PT_smoke_behavior_dissolve,
PHYSICS_PT_smoke_adaptive_domain,
PHYSICS_PT_smoke_cache,
PHYSICS_PT_smoke_field_weights,
PHYSICS_PT_smoke_fire,
PHYSICS_PT_smoke_flow_texture,
PHYSICS_PT_smoke_collections,
PHYSICS_PT_smoke_highres,
PHYSICS_PT_smoke_viewport_display,
PHYSICS_PT_smoke_viewport_display_color,
PHYSICS_PT_smoke_viewport_display_debug,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)

View File

@@ -2195,7 +2195,6 @@ class USERPREF_PT_experimental_ui(ExperimentalPanel, Panel):
col = split.column()
col.operator("wm.url_open", text=task, icon='URL').url = self.url_prefix + task
"""
# Example panel, leave it here so we always have a template to follow even
# after the features are gone from the experimental panel.

View File

@@ -2647,7 +2647,7 @@ class VIEW3D_MT_object_quick_effects(Menu):
layout.operator("object.quick_fur")
layout.operator("object.quick_explode")
layout.operator("object.quick_smoke")
layout.operator("object.quick_fluid")
layout.operator("object.quick_liquid")
class VIEW3D_MT_object_showhide(Menu):