b01d806630c9888a9c5b270947f5837b9ba611fa
[blender.git] / release / scripts / startup / bl_ui / properties_physics_fluid.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 import bpy
22 from bpy.types import (
23     Panel,
24 )
25 from bpy.app.translations import pgettext_iface as iface_
26 from bl_ui.utils import PresetPanel
27
28
29 class FLUID_PT_presets(PresetPanel, Panel):
30     bl_label = "Fluid Presets"
31     preset_subdir = "fluid"
32     preset_operator = "script.execute_preset"
33     preset_add_operator = "fluid.preset_add"
34
35
36 class PhysicButtonsPanel:
37     bl_space_type = 'PROPERTIES'
38     bl_region_type = 'WINDOW'
39     bl_context = "physics"
40
41     def poll_fluid(context):
42         ob = context.object
43         if not ((ob and ob.type == 'MESH') and (context.fluid)):
44             return False
45
46         return (bpy.app.build_options.mod_fluid)
47
48     def poll_fluid_settings(context):
49         if not (PhysicButtonsPanel.poll_fluid(context)):
50             return False
51
52         md = context.fluid
53         return md and md.settings and (md.settings.type != 'NONE')
54
55     def poll_fluid_domain(context):
56         if not PhysicButtonsPanel.poll_fluid(context):
57             return False
58
59         md = context.fluid
60         return md and md.settings and (md.settings.type == 'DOMAIN')
61
62
63 class PHYSICS_PT_fluid(PhysicButtonsPanel, Panel):
64     bl_label = "Fluid"
65     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
66
67     @classmethod
68     def poll(cls, context):
69         ob = context.object
70         return (ob and ob.type == 'MESH') and context.engine in cls.COMPAT_ENGINES and (context.fluid)
71
72     def draw(self, context):
73         layout = self.layout
74         layout.use_property_split = True
75
76         if not bpy.app.build_options.mod_fluid:
77             col = layout.column()
78             col.alignment = 'RIGHT'
79             col.label(text="Built without fluids")
80             return
81
82         md = context.fluid
83         fluid = md.settings
84
85         col = layout.column()
86         col.prop(fluid, "type")
87
88
89 class PHYSICS_PT_fluid_flow(PhysicButtonsPanel, Panel):
90     bl_label = "Flow"
91     bl_parent_id = "PHYSICS_PT_fluid"
92     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
93
94     @classmethod
95     def poll(cls, context):
96         md = context.fluid
97         fluid = md.settings
98
99         if not PhysicButtonsPanel.poll_fluid_settings(context):
100             return False
101         return fluid.type in {'INFLOW', 'OUTFLOW', 'CONTROL'} and (context.engine in cls.COMPAT_ENGINES)
102
103     def draw_header(self, context):
104         md = context.fluid
105         fluid = md.settings
106
107         self.layout.prop(fluid, "use", text="")
108
109     def draw(self, context):
110         layout = self.layout
111         layout.use_property_split = True
112
113         md = context.fluid
114         fluid = md.settings
115
116         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
117
118         flow.active = fluid.use
119
120         if fluid.type == 'INFLOW':
121             col = flow.column()
122             col.prop(fluid, "volume_initialization", text="Volume Initialization")
123             col.prop(fluid, "use_animated_mesh")
124
125             row = col.row()
126             row.active = not fluid.use_animated_mesh
127             row.prop(fluid, "use_local_coords")
128
129             col = flow.column()
130             col.prop(fluid, "inflow_velocity", text="Inflow Velocity")
131
132         elif fluid.type == 'OUTFLOW':
133             col = flow.column()
134             col.prop(fluid, "volume_initialization", text="Volume Initialization")
135
136             col = flow.column()
137             col.prop(fluid, "use_animated_mesh")
138
139         elif fluid.type == 'CONTROL':
140             col = flow.column()
141             col.prop(fluid, "quality", slider=True)
142             col.prop(fluid, "use_reverse_frames")
143
144             col = flow.column()
145             col.prop(fluid, "start_time", text="Time Start")
146             col.prop(fluid, "end_time", text="End")
147
148             col.separator()
149
150             col = flow.column()
151             col.prop(fluid, "attraction_strength", text="Attraction Strength")
152             col.prop(fluid, "attraction_radius", text="Radius")
153
154             col.separator()
155
156             col = flow.column(align=True)
157             col.prop(fluid, "velocity_strength", text="Velocity Strength")
158             col.prop(fluid, "velocity_radius", text="Radius")
159
160
161 class PHYSICS_PT_fluid_settings(PhysicButtonsPanel, Panel):
162     bl_label = "Settings"
163     bl_parent_id = "PHYSICS_PT_fluid"
164     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
165
166     @classmethod
167     def poll(cls, context):
168         md = context.fluid
169         fluid = md.settings
170
171         if not PhysicButtonsPanel.poll_fluid_settings(context):
172             return False
173         return fluid.type in {'DOMAIN', 'FLUID', 'OBSTACLE', 'PARTICLE'} and (context.engine in cls.COMPAT_ENGINES)
174
175     def draw(self, context):
176         layout = self.layout
177         layout.use_property_split = True
178
179         md = context.fluid
180         fluid = md.settings
181
182         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
183
184         if fluid.type not in {'NONE', 'DOMAIN', 'PARTICLE', 'FLUID', 'OBSTACLE'}:
185             flow.active = fluid.use
186
187         if fluid.type == 'DOMAIN':
188             col = flow.column()
189
190             if bpy.app.build_options.openmp:
191                 col.prop(fluid, "threads", text="Simulation Threads")
192                 col.separator()
193
194             col.prop(fluid, "resolution", text="Final Resolution")
195             col.prop(fluid, "preview_resolution", text="Preview")
196
197             col.separator()
198
199             col = flow.column()
200             col.prop(fluid, "render_display_mode", text="Render Display")
201             col.prop(fluid, "viewport_display_mode", text="Viewport")
202
203             col.separator()
204
205             col = flow.column()
206             sub = col.column(align=True)
207             sub.prop(fluid, "start_time", text="Time Start")
208             sub.prop(fluid, "end_time", text="End")
209             col.prop(fluid, "simulation_rate", text="Speed")
210
211             col = flow.column()
212             col.prop(fluid, "use_speed_vectors")
213             col.prop(fluid, "use_reverse_frames")
214             col.prop(fluid, "frame_offset", text="Offset")
215
216         elif fluid.type == 'FLUID':
217             col = flow.column()
218             col.prop(fluid, "volume_initialization", text="Volume Initialization")
219             col.prop(fluid, "use_animated_mesh")
220
221             col = flow.column()
222             col.prop(fluid, "initial_velocity", text="Initial Velocity")
223
224         elif fluid.type == 'OBSTACLE':
225             col = flow.column()
226             col.prop(fluid, "volume_initialization", text="Volume Initialization")
227             col.prop(fluid, "use_animated_mesh")
228
229             col = flow.column()
230             subcol = col.column()
231             subcol.enabled = not fluid.use_animated_mesh
232             subcol.prop(fluid, "slip_type", text="Slip Type")
233
234             if fluid.slip_type == 'PARTIALSLIP':
235                 subcol.prop(fluid, "partial_slip_factor", text="Amount", slider=True)
236
237             col.prop(fluid, "impact_factor", text="Impact Factor")
238
239         elif fluid.type == 'PARTICLE':
240             col = flow.column()
241             col.prop(fluid, "particle_influence", text="Influence Size")
242             col.prop(fluid, "alpha_influence", text="Alpha")
243
244             col = flow.column()
245             col.prop(fluid, "use_drops")
246             col.prop(fluid, "use_floats")
247             col.prop(fluid, "show_tracer")
248
249
250 class PHYSICS_PT_fluid_particle_cache(PhysicButtonsPanel, Panel):
251     bl_label = "Cache"
252     bl_parent_id = "PHYSICS_PT_fluid"
253     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
254
255     @classmethod
256     def poll(cls, context):
257         if not PhysicButtonsPanel.poll_fluid_settings(context):
258             return False
259
260         md = context.fluid
261         return md and md.settings and (md.settings.type == 'PARTICLE') and (context.engine in cls.COMPAT_ENGINES)
262
263     def draw(self, context):
264         layout = self.layout
265
266         md = context.fluid
267         fluid = md.settings
268
269         layout.prop(fluid, "filepath", text="")
270
271
272 class PHYSICS_PT_domain_bake(PhysicButtonsPanel, Panel):
273     bl_label = "Bake"
274     bl_parent_id = 'PHYSICS_PT_fluid'
275     bl_options = {'DEFAULT_CLOSED'}
276     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
277
278     @classmethod
279     def poll(cls, context):
280         if not PhysicButtonsPanel.poll_fluid_domain(context):
281             return False
282
283         return (context.engine in cls.COMPAT_ENGINES)
284
285     def draw(self, context):
286         layout = self.layout
287
288         md = context.fluid
289         fluid = md.settings
290
291         row = layout.row(align=True)
292         row.alignment = 'RIGHT'
293         row.label(text="Cache Path")
294
295         layout.prop(fluid, "filepath", text="")
296
297         # odd formatting here so translation script can extract string
298         layout.operator(
299             "fluid.bake", text=iface_("Bake (Req. Memory: %s)") % fluid.memory_estimate,
300             translate=False, icon='MOD_FLUIDSIM'
301         )
302
303
304 class PHYSICS_PT_domain_gravity(PhysicButtonsPanel, Panel):
305     bl_label = "World"
306     bl_parent_id = 'PHYSICS_PT_fluid'
307     bl_options = {'DEFAULT_CLOSED'}
308     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
309
310     @classmethod
311     def poll(cls, context):
312         if not PhysicButtonsPanel.poll_fluid_domain(context):
313             return False
314
315         return (context.engine in cls.COMPAT_ENGINES)
316
317     def draw(self, context):
318         layout = self.layout
319         layout.use_property_split = True
320
321         fluid = context.fluid.settings
322         scene = context.scene
323
324         col = layout.column()
325
326         use_gravity = scene.use_gravity
327         use_units = scene.unit_settings.system != 'NONE'
328
329         if use_gravity or use_units:
330             s_gravity = " Gravity" if use_gravity else ""
331             s_units = " Units" if use_units else ""
332             s_and = " and " if use_gravity and use_units else ""
333             warn = f"Using {s_gravity}{s_and}{s_units} from Scene"
334
335             sub = col.column()
336             sub.alignment = 'RIGHT'
337             sub.label(text=warn)
338
339         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
340
341         col = flow.column()
342         sub = col.column()
343         sub.enabled = not use_gravity
344         sub.prop(fluid, "gravity", text="Gravity")
345
346         sub = col.column()
347         sub.enabled = not use_units
348         sub.prop(fluid, "simulation_scale", text="Scene Size Meters" if use_units else "World Size Meters")
349
350         col.separator()
351
352         col = flow.column()
353         col.prop(fluid, "grid_levels", text="Optimization", slider=True)
354         col.prop(fluid, "compressibility", slider=True)
355
356
357 class PHYSICS_PT_domain_viscosity(PhysicButtonsPanel, Panel):
358     bl_label = "Viscosity"
359     bl_parent_id = 'PHYSICS_PT_fluid'
360     bl_options = {'DEFAULT_CLOSED'}
361     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
362
363     @classmethod
364     def poll(cls, context):
365         if not PhysicButtonsPanel.poll_fluid_domain(context):
366             return False
367
368         return (context.engine in cls.COMPAT_ENGINES)
369
370     def draw_header_preset(self, _context):
371         FLUID_PT_presets.draw_panel_header(self.layout)
372
373     def draw(self, context):
374         layout = self.layout
375         layout.use_property_split = True
376         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
377
378         fluid = context.fluid.settings
379
380         col = flow.column()
381         col.prop(fluid, "viscosity_base", text="Base")
382
383         col = flow.column()
384         col.prop(fluid, "viscosity_exponent", text="Exponent", slider=True)
385
386
387 class PHYSICS_PT_domain_boundary(PhysicButtonsPanel, Panel):
388     bl_label = "Boundary"
389     bl_parent_id = 'PHYSICS_PT_fluid'
390     bl_options = {'DEFAULT_CLOSED'}
391     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
392
393     @classmethod
394     def poll(cls, context):
395         if not PhysicButtonsPanel.poll_fluid_domain(context):
396             return False
397
398         return (context.engine in cls.COMPAT_ENGINES)
399
400     def draw(self, context):
401         layout = self.layout
402         layout.use_property_split = True
403         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
404
405         fluid = context.fluid.settings
406
407         col = flow.column()
408         col.prop(fluid, "slip_type", text="Type")
409
410         col.separator()
411
412         if fluid.slip_type == 'PARTIALSLIP':
413             col.prop(fluid, "partial_slip_factor", slider=True, text="Amount")
414
415         col = flow.column()
416         col.prop(fluid, "surface_smooth", text="Surface Smoothing")
417         col.prop(fluid, "surface_subdivisions", text="Subdivisions")
418         col.prop(fluid, "use_surface_noobs")
419
420
421 class PHYSICS_PT_domain_particles(PhysicButtonsPanel, Panel):
422     bl_label = "Particles"
423     bl_parent_id = 'PHYSICS_PT_fluid'
424     bl_options = {'DEFAULT_CLOSED'}
425     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
426
427     @classmethod
428     def poll(cls, context):
429         if not PhysicButtonsPanel.poll_fluid_domain(context):
430             return False
431
432         return (context.engine in cls.COMPAT_ENGINES)
433
434     def draw(self, context):
435         layout = self.layout
436         layout.use_property_split = True
437         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
438
439         fluid = context.fluid.settings
440
441         col = flow.column()
442         col.prop(fluid, "tracer_particles", text="Tracer")
443
444         col = flow.column()
445         col.prop(fluid, "generate_particles", text="Generate")
446
447
448 classes = (
449     FLUID_PT_presets,
450     PHYSICS_PT_fluid,
451     PHYSICS_PT_fluid_settings,
452     PHYSICS_PT_fluid_flow,
453     PHYSICS_PT_fluid_particle_cache,
454     PHYSICS_PT_domain_bake,
455     PHYSICS_PT_domain_boundary,
456     PHYSICS_PT_domain_particles,
457     PHYSICS_PT_domain_gravity,
458     PHYSICS_PT_domain_viscosity,
459 )
460
461 if __name__ == "__main__":  # only for live edit.
462     from bpy.utils import register_class
463     for cls in classes:
464         register_class(cls)