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
21import bpy
22from bpy.types import Menu, Panel
23from bl_ui.utils import PresetPanel
24from .properties_physics_common import (
25    effector_weights_ui,
26)
27
28
29class 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
36class PhysicButtonsPanel:
37    bl_space_type = 'PROPERTIES'
38    bl_region_type = 'WINDOW'
39    bl_context = "physics"
40
41    @staticmethod
42    def check_domain_has_unbaked_guide(domain):
43        return (
44            domain.use_guide and not domain.has_cache_baked_guide and
45            ((domain.guide_source == 'EFFECTOR') or
46             (domain.guide_source == 'DOMAIN' and not domain.guide_parent))
47        )
48
49    @staticmethod
50    def poll_fluid(context):
51        ob = context.object
52        if not ((ob and ob.type == 'MESH') and (context.fluid)):
53            return False
54
55        md = context.fluid
56        return md and (context.fluid.fluid_type != 'NONE')
57
58    @staticmethod
59    def poll_fluid_domain(context):
60        if not PhysicButtonsPanel.poll_fluid(context):
61            return False
62
63        md = context.fluid
64        return md and (md.fluid_type == 'DOMAIN')
65
66    @staticmethod
67    def poll_gas_domain(context):
68        if not PhysicButtonsPanel.poll_fluid(context):
69            return False
70
71        md = context.fluid
72        if md and (md.fluid_type == 'DOMAIN'):
73            domain = md.domain_settings
74            return domain.domain_type in {'GAS'}
75        return False
76
77    @staticmethod
78    def poll_liquid_domain(context):
79        if not PhysicButtonsPanel.poll_fluid(context):
80            return False
81
82        md = context.fluid
83        if md and (md.fluid_type == 'DOMAIN'):
84            domain = md.domain_settings
85            return domain.domain_type in {'LIQUID'}
86        return False
87
88    @staticmethod
89    def poll_fluid_flow(context):
90        if not PhysicButtonsPanel.poll_fluid(context):
91            return False
92
93        md = context.fluid
94        return md and (md.fluid_type == 'FLOW')
95
96    @staticmethod
97    def poll_fluid_flow_outflow(context):
98        if not PhysicButtonsPanel.poll_fluid_flow(context):
99            return False
100
101        md = context.fluid
102        flow = md.flow_settings
103        if (flow.flow_behavior == 'OUTFLOW'):
104            return True
105
106    @staticmethod
107    def poll_fluid_flow_liquid(context):
108        if not PhysicButtonsPanel.poll_fluid_flow(context):
109            return False
110
111        md = context.fluid
112        flow = md.flow_settings
113        if (flow.flow_type == 'LIQUID'):
114            return True
115
116
117class PHYSICS_PT_fluid(PhysicButtonsPanel, Panel):
118    bl_label = "Fluid"
119    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
120
121    @classmethod
122    def poll(cls, context):
123        ob = context.object
124        return (ob and ob.type == 'MESH') and (context.engine in cls.COMPAT_ENGINES) and (context.fluid)
125
126    def draw(self, context):
127        layout = self.layout
128        layout.use_property_split = True
129
130        if not bpy.app.build_options.fluid:
131            col = layout.column(align=True)
132            col.alignment = 'RIGHT'
133            col.label(text="Built without Fluid modifier")
134            return
135        md = context.fluid
136
137        layout.prop(md, "fluid_type")
138
139
140class PHYSICS_PT_settings(PhysicButtonsPanel, Panel):
141    bl_label = "Settings"
142    bl_parent_id = 'PHYSICS_PT_fluid'
143    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
144
145    @classmethod
146    def poll(cls, context):
147        if not PhysicButtonsPanel.poll_fluid(context):
148            return False
149
150        return (context.engine in cls.COMPAT_ENGINES)
151
152    def draw(self, context):
153        layout = self.layout
154        layout.use_property_split = True
155
156        md = context.fluid
157        ob = context.object
158        scene = context.scene
159
160        if md.fluid_type == 'DOMAIN':
161            domain = md.domain_settings
162
163            is_baking_any = domain.is_cache_baking_any
164            has_baked_data = domain.has_cache_baked_data
165
166            row = layout.row()
167            row.enabled = not is_baking_any and not has_baked_data
168            row.prop(domain, "domain_type", expand=False)
169
170            flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
171            flow.enabled = not is_baking_any and not has_baked_data
172
173            col = flow.column()
174            col.enabled = not domain.has_cache_baked_guide
175            col.prop(domain, "resolution_max", text="Resolution Divisions")
176            col.prop(domain, "time_scale", text="Time Scale")
177            col.prop(domain, "cfl_condition", text="CFL Number")
178
179            col = flow.column()
180            col.prop(domain, "use_adaptive_timesteps")
181            sub = col.column(align=True)
182            sub.active = domain.use_adaptive_timesteps
183            sub.prop(domain, "timesteps_max", text="Timesteps Maximum")
184            sub.prop(domain, "timesteps_min", text="Minimum")
185
186            col.separator()
187
188            col = flow.column()
189            if scene.use_gravity:
190                sub = col.column()
191                sub.enabled = False
192                sub.prop(domain, "gravity", text="Using Scene Gravity", icon='SCENE_DATA')
193            else:
194                col.prop(domain, "gravity", text="Gravity")
195
196            col = flow.column()
197            if PhysicButtonsPanel.poll_gas_domain(context):
198                col.prop(domain, "clipping", text="Empty Space")
199            col.prop(domain, "delete_in_obstacle", text="Delete In Obstacle")
200
201            if domain.cache_type == 'MODULAR':
202                col.separator()
203                label = ""
204
205                # Deactivate bake operator if guides are enabled but not baked yet.
206                note_flag = True
207                if self.check_domain_has_unbaked_guide(domain):
208                    note_flag = False
209                    label = "Unbaked Guides: Bake Guides or disable them"
210                elif not domain.cache_resumable and not label:
211                    label = "Non Resumable Cache: Baking "
212                    if PhysicButtonsPanel.poll_liquid_domain(context):
213                        label += "mesh or particles will not be possible"
214                    elif PhysicButtonsPanel.poll_gas_domain(context):
215                        label += "noise will not be possible"
216                    else:
217                        label = ""
218
219                if label:
220                    info = layout.split()
221                    note = info.row()
222                    note.enabled = note_flag
223                    note.alignment = 'RIGHT'
224                    note.label(icon='INFO', text=label)
225
226                split = layout.split()
227                split.enabled = note_flag and ob.mode == 'OBJECT'
228
229                bake_incomplete = (domain.cache_frame_pause_data < domain.cache_frame_end)
230                if domain.cache_resumable and domain.has_cache_baked_data and not domain.is_cache_baking_data and bake_incomplete:
231                    col = split.column()
232                    col.operator("fluid.bake_data", text="Resume")
233                    col = split.column()
234                    col.operator("fluid.free_data", text="Free")
235                elif domain.is_cache_baking_data and not domain.has_cache_baked_data:
236                    split.enabled = False
237                    split.operator("fluid.pause_bake", text="Baking Data - ESC to pause")
238                elif not domain.has_cache_baked_data and not domain.is_cache_baking_data:
239                    split.operator("fluid.bake_data", text="Bake Data")
240                else:
241                    split.operator("fluid.free_data", text="Free Data")
242
243        elif md.fluid_type == 'FLOW':
244            flow = md.flow_settings
245
246            row = layout.row()
247            row.prop(flow, "flow_type", expand=False)
248
249            grid = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
250
251            col = grid.column()
252            col.prop(flow, "flow_behavior", expand=False)
253            if flow.flow_behavior in {'INFLOW', 'OUTFLOW'}:
254                col.prop(flow, "use_inflow")
255
256            col.prop(flow, "subframes", text="Sampling Substeps")
257
258            if not flow.flow_behavior == 'OUTFLOW' and flow.flow_type in {'SMOKE', 'BOTH', 'FIRE'}:
259
260                if flow.flow_type in {'SMOKE', 'BOTH'}:
261                    col.prop(flow, "smoke_color", text="Smoke Color")
262
263                col = grid.column(align=True)
264                col.prop(flow, "use_absolute", text="Absolute Density")
265
266                if flow.flow_type in {'SMOKE', 'BOTH'}:
267                    col.prop(flow, "temperature", text="Initial Temperature")
268                    col.prop(flow, "density", text="Density")
269
270                if flow.flow_type in {'FIRE', 'BOTH'}:
271                    col.prop(flow, "fuel_amount", text="Fuel")
272
273                col.separator()
274                col.prop_search(flow, "density_vertex_group", ob, "vertex_groups", text="Vertex Group")
275
276        elif md.fluid_type == 'EFFECTOR':
277            effector_settings = md.effector_settings
278
279            row = layout.row()
280            row.prop(effector_settings, "effector_type")
281
282            grid = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
283
284            col = grid.column()
285            col.prop(effector_settings, "subframes", text="Sampling Substeps")
286            col.prop(effector_settings, "surface_distance", text="Surface Thickness")
287
288            col = grid.column()
289
290            col.prop(effector_settings, "use_effector", text="Use Effector")
291            col.prop(effector_settings, "use_plane_init", text="Is Planar")
292
293            if effector_settings.effector_type == 'GUIDE':
294                col.prop(effector_settings, "velocity_factor", text="Velocity Factor")
295                col.prop(effector_settings, "guide_mode", text="Guide Mode")
296
297
298class PHYSICS_PT_borders(PhysicButtonsPanel, Panel):
299    bl_label = "Border Collisions"
300    bl_parent_id = 'PHYSICS_PT_settings'
301    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
302
303    @classmethod
304    def poll(cls, context):
305        if not PhysicButtonsPanel.poll_fluid_domain(context):
306            return False
307
308        return (context.engine in cls.COMPAT_ENGINES)
309
310    def draw(self, context):
311        layout = self.layout
312        layout.use_property_split = True
313
314        md = context.fluid
315        domain = md.domain_settings
316
317        is_baking_any = domain.is_cache_baking_any
318        has_baked_data = domain.has_cache_baked_data
319
320        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
321        flow.enabled = not is_baking_any and not has_baked_data
322
323        col = flow.column()
324        col.prop(domain, "use_collision_border_front", text="Front")
325        col = flow.column()
326        col.prop(domain, "use_collision_border_back", text="Back")
327        col = flow.column()
328        col.prop(domain, "use_collision_border_right", text="Right")
329        col = flow.column()
330        col.prop(domain, "use_collision_border_left", text="Left")
331        col = flow.column()
332        col.prop(domain, "use_collision_border_top", text="Top")
333        col = flow.column()
334        col.prop(domain, "use_collision_border_bottom", text="Bottom")
335
336
337class PHYSICS_PT_smoke(PhysicButtonsPanel, Panel):
338    bl_label = "Gas"
339    bl_parent_id = 'PHYSICS_PT_fluid'
340    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
341
342    @classmethod
343    def poll(cls, context):
344        if not PhysicButtonsPanel.poll_gas_domain(context):
345            return False
346
347        return (context.engine in cls.COMPAT_ENGINES)
348
349    def draw(self, context):
350        layout = self.layout
351        layout.use_property_split = True
352
353        md = context.fluid
354        domain = md.domain_settings
355
356        is_baking_any = domain.is_cache_baking_any
357        has_baked_data = domain.has_cache_baked_data
358
359        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
360        flow.enabled = not is_baking_any and not has_baked_data
361
362        col = flow.column(align=True)
363        col.prop(domain, "alpha", text="Buoyancy Density")
364        col.prop(domain, "beta", text="Heat")
365        col = flow.column()
366        col.prop(domain, "vorticity")
367
368
369class PHYSICS_PT_smoke_dissolve(PhysicButtonsPanel, Panel):
370    bl_label = "Dissolve"
371    bl_parent_id = 'PHYSICS_PT_smoke'
372    bl_options = {'DEFAULT_CLOSED'}
373    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
374
375    @classmethod
376    def poll(cls, context):
377        if not PhysicButtonsPanel.poll_gas_domain(context):
378            return False
379
380        return (context.engine in cls.COMPAT_ENGINES)
381
382    def draw_header(self, context):
383        md = context.fluid.domain_settings
384        domain = context.fluid.domain_settings
385
386        is_baking_any = domain.is_cache_baking_any
387
388        self.layout.enabled = not is_baking_any
389        self.layout.prop(md, "use_dissolve_smoke", text="")
390
391    def draw(self, context):
392        layout = self.layout
393        layout.use_property_split = True
394
395        md = context.fluid
396        domain = md.domain_settings
397
398        is_baking_any = domain.is_cache_baking_any
399        has_baked_data = domain.has_cache_baked_data
400
401        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
402        flow.enabled = not is_baking_any and not has_baked_data
403
404        layout.active = domain.use_dissolve_smoke
405
406        col = flow.column()
407        col.prop(domain, "dissolve_speed", text="Time")
408
409        col = flow.column()
410        col.prop(domain, "use_dissolve_smoke_log", text="Slow")
411
412
413class PHYSICS_PT_fire(PhysicButtonsPanel, Panel):
414    bl_label = "Fire"
415    bl_parent_id = 'PHYSICS_PT_smoke'
416    bl_options = {'DEFAULT_CLOSED'}
417    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
418
419    @classmethod
420    def poll(cls, context):
421        if not PhysicButtonsPanel.poll_gas_domain(context):
422            return False
423
424        return (context.engine in cls.COMPAT_ENGINES)
425
426    def draw(self, context):
427        layout = self.layout
428        layout.use_property_split = True
429
430        md = context.fluid
431        domain = md.domain_settings
432
433        is_baking_any = domain.is_cache_baking_any
434        has_baked_data = domain.has_cache_baked_data
435
436        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
437        flow.enabled = not is_baking_any and not has_baked_data
438
439        col = flow.column()
440        col.prop(domain, "burning_rate", text="Reaction Speed")
441        row = col.row()
442        sub = row.column(align=True)
443        sub.prop(domain, "flame_smoke", text="Flame Smoke")
444        sub.prop(domain, "flame_vorticity", text="Vorticity")
445
446        col = flow.column(align=True)
447        col.prop(domain, "flame_max_temp", text="Temperature Maximum")
448        col.prop(domain, "flame_ignition", text="Minimum")
449        row = col.row()
450        row.prop(domain, "flame_smoke_color", text="Flame Color")
451
452
453class PHYSICS_PT_liquid(PhysicButtonsPanel, Panel):
454    bl_label = "Liquid"
455    bl_parent_id = 'PHYSICS_PT_fluid'
456    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
457
458    @classmethod
459    def poll(cls, context):
460        if not PhysicButtonsPanel.poll_liquid_domain(context):
461            return False
462
463        return (context.engine in cls.COMPAT_ENGINES)
464
465    def draw_header(self, context):
466        md = context.fluid.domain_settings
467        domain = context.fluid.domain_settings
468
469        is_baking_any = domain.is_cache_baking_any
470
471        self.layout.enabled = not is_baking_any
472        self.layout.prop(md, "use_flip_particles", text="")
473
474    def draw(self, context):
475        layout = self.layout
476        layout.use_property_split = True
477
478        md = context.fluid
479        domain = md.domain_settings
480
481        is_baking_any = domain.is_cache_baking_any
482        has_baked_data = domain.has_cache_baked_data
483
484        layout.enabled = not is_baking_any and not has_baked_data
485        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
486
487        col = flow.column()
488        col.prop(domain, "simulation_method", expand=False)
489        col.prop(domain, "flip_ratio", text="FLIP Ratio")
490        col.prop(domain, "sys_particle_maximum", text="System Maximum")
491        col = col.column(align=True)
492        col.prop(domain, "particle_radius", text="Particle Radius")
493        col.prop(domain, "particle_number", text="Sampling")
494        col.prop(domain, "particle_randomness", text="Randomness")
495
496        col = flow.column()
497        col = col.column(align=True)
498        col.prop(domain, "particle_max", text="Particles Maximum")
499        col.prop(domain, "particle_min", text="Minimum")
500
501        col.separator()
502
503        col = col.column()
504        col.prop(domain, "particle_band_width", text="Narrow Band Width")
505
506        col = col.column()
507        col.prop(domain, "use_fractions", text="Fractional Obstacles")
508        sub = col.column()
509        sub.active = domain.use_fractions
510        sub.prop(domain, "fractions_distance", text="Obstacle Distance")
511        sub.prop(domain, "fractions_threshold", text="Threshold")
512
513
514class PHYSICS_PT_flow_source(PhysicButtonsPanel, Panel):
515    bl_label = "Flow Source"
516    bl_parent_id = 'PHYSICS_PT_settings'
517    bl_options = {'DEFAULT_CLOSED'}
518    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
519
520    @classmethod
521    def poll(cls, context):
522        if not PhysicButtonsPanel.poll_fluid_flow(context):
523            return False
524
525        return (context.engine in cls.COMPAT_ENGINES)
526
527    def draw(self, context):
528        layout = self.layout
529        layout.use_property_split = True
530
531        ob = context.object
532        flow = context.fluid.flow_settings
533
534        col = layout.column()
535        col.prop(flow, "flow_source", expand=False, text="Flow Source")
536        if flow.flow_source == 'PARTICLES':
537            col.prop_search(flow, "particle_system", ob, "particle_systems", text="Particle System")
538
539        grid = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
540
541        col = grid.column()
542        if flow.flow_source == 'MESH':
543            col.prop(flow, "use_plane_init", text="Is Planar")
544            col.prop(flow, "surface_distance", text="Surface Emission")
545            if flow.flow_type in {'SMOKE', 'BOTH', 'FIRE'}:
546                col = grid.column()
547                col.prop(flow, "volume_density", text="Volume Emission")
548
549        if flow.flow_source == 'PARTICLES':
550            col.prop(flow, "use_particle_size", text="Set Size")
551            sub = col.column()
552            sub.active = flow.use_particle_size
553            sub.prop(flow, "particle_size")
554
555
556class PHYSICS_PT_flow_initial_velocity(PhysicButtonsPanel, Panel):
557    bl_label = "Initial Velocity"
558    bl_parent_id = 'PHYSICS_PT_settings'
559    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
560
561    @classmethod
562    def poll(cls, context):
563        if not PhysicButtonsPanel.poll_fluid_flow(context):
564            return False
565
566        if PhysicButtonsPanel.poll_fluid_flow_outflow(context):
567            return False
568
569        return (context.engine in cls.COMPAT_ENGINES)
570
571    def draw_header(self, context):
572        md = context.fluid
573        flow_smoke = md.flow_settings
574
575        self.layout.prop(flow_smoke, "use_initial_velocity", text="")
576
577    def draw(self, context):
578        layout = self.layout
579        layout.use_property_split = True
580        flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=True)
581
582        md = context.fluid
583        flow_smoke = md.flow_settings
584
585        flow.active = flow_smoke.use_initial_velocity
586
587        col = flow.column()
588        col.prop(flow_smoke, "velocity_factor")
589
590        if flow_smoke.flow_source == 'MESH':
591            col.prop(flow_smoke, "velocity_normal")
592            # col.prop(flow_smoke, "velocity_random")
593            col = flow.column()
594            col.prop(flow_smoke, "velocity_coord")
595
596
597class PHYSICS_PT_flow_texture(PhysicButtonsPanel, Panel):
598    bl_label = "Texture"
599    bl_parent_id = 'PHYSICS_PT_settings'
600    bl_options = {'DEFAULT_CLOSED'}
601    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
602
603    @classmethod
604    def poll(cls, context):
605        if not PhysicButtonsPanel.poll_fluid_flow(context):
606            return False
607
608        if PhysicButtonsPanel.poll_fluid_flow_outflow(context):
609            return False
610
611        if PhysicButtonsPanel.poll_fluid_flow_liquid(context):
612            return False
613
614        return (context.engine in cls.COMPAT_ENGINES)
615
616    def draw_header(self, context):
617        md = context.fluid
618        flow_smoke = md.flow_settings
619
620        self.layout.prop(flow_smoke, "use_texture", text="")
621
622    def draw(self, context):
623        layout = self.layout
624        layout.use_property_split = True
625        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
626
627        ob = context.object
628        flow_smoke = context.fluid.flow_settings
629
630        sub = flow.column()
631        sub.active = flow_smoke.use_texture
632        sub.prop(flow_smoke, "noise_texture")
633        sub.prop(flow_smoke, "texture_map_type", text="Mapping")
634
635        col = flow.column()
636        sub = col.column()
637        sub.active = flow_smoke.use_texture
638
639        if flow_smoke.texture_map_type == 'UV':
640            sub.prop_search(flow_smoke, "uv_layer", ob.data, "uv_layers")
641
642        if flow_smoke.texture_map_type == 'AUTO':
643            sub.prop(flow_smoke, "texture_size")
644
645        sub.prop(flow_smoke, "texture_offset")
646
647
648class PHYSICS_PT_adaptive_domain(PhysicButtonsPanel, Panel):
649    bl_label = "Adaptive Domain"
650    bl_parent_id = 'PHYSICS_PT_settings'
651    bl_options = {'DEFAULT_CLOSED'}
652    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
653
654    @classmethod
655    def poll(cls, context):
656        if not PhysicButtonsPanel.poll_gas_domain(context):
657            return False
658
659        md = context.fluid
660        domain = md.domain_settings
661        # Effector guides require a fixed domain size
662        if domain.use_guide and domain.guide_source == 'EFFECTOR':
663            return False
664
665        return (context.engine in cls.COMPAT_ENGINES)
666
667    def draw_header(self, context):
668        md = context.fluid.domain_settings
669        domain = context.fluid.domain_settings
670
671        is_baking_any = domain.is_cache_baking_any
672        has_baked_any = domain.has_cache_baked_any
673
674        self.layout.enabled = not is_baking_any and not has_baked_any
675        self.layout.prop(md, "use_adaptive_domain", text="")
676
677    def draw(self, context):
678        layout = self.layout
679        layout.use_property_split = True
680
681        domain = context.fluid.domain_settings
682        layout.active = domain.use_adaptive_domain
683
684        is_baking_any = domain.is_cache_baking_any
685        has_baked_any = domain.has_cache_baked_any
686
687        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
688        flow.enabled = not is_baking_any and not has_baked_any
689
690        col = flow.column()
691        col.prop(domain, "additional_res", text="Add Resolution")
692        col.prop(domain, "adapt_margin")
693
694        col.separator()
695
696        col = flow.column()
697        col.prop(domain, "adapt_threshold", text="Threshold")
698
699
700class PHYSICS_PT_noise(PhysicButtonsPanel, Panel):
701    bl_label = "Noise"
702    bl_parent_id = 'PHYSICS_PT_smoke'
703    bl_options = {'DEFAULT_CLOSED'}
704    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
705
706    @classmethod
707    def poll(cls, context):
708        if not PhysicButtonsPanel.poll_gas_domain(context):
709            return False
710
711        return (context.engine in cls.COMPAT_ENGINES)
712
713    def draw_header(self, context):
714        md = context.fluid.domain_settings
715        domain = context.fluid.domain_settings
716        is_baking_any = domain.is_cache_baking_any
717        self.layout.enabled = not is_baking_any
718        self.layout.prop(md, "use_noise", text="")
719
720    def draw(self, context):
721        layout = self.layout
722        layout.use_property_split = True
723
724        ob = context.object
725        domain = context.fluid.domain_settings
726        layout.active = domain.use_noise
727
728        is_baking_any = domain.is_cache_baking_any
729        has_baked_noise = domain.has_cache_baked_noise
730
731        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
732        flow.enabled = not is_baking_any and not has_baked_noise
733
734        col = flow.column()
735        col.prop(domain, "noise_scale", text="Upres Factor")
736        # TODO (sebbas): Mantaflow only supports wavelet noise. Maybe get rid of noise type field.
737        col.prop(domain, "noise_type", text="Noise Method")
738
739        col = flow.column()
740        col.prop(domain, "noise_strength", text="Strength")
741        col.prop(domain, "noise_pos_scale", text="Scale")
742        col.prop(domain, "noise_time_anim", text="Time")
743
744        if domain.cache_type == 'MODULAR':
745            col.separator()
746
747            # Deactivate bake operator if data has not been baked yet.
748            note_flag = True
749            if domain.use_noise:
750                label = ""
751                if not domain.cache_resumable:
752                    label = "Non Resumable Cache: Enable resumable option first"
753                elif not domain.has_cache_baked_data:
754                    label = "Unbaked Data: Bake Data first"
755
756                if label:
757                    info = layout.split()
758                    note = info.row()
759                    note_flag = False
760                    note.enabled = note_flag
761                    note.alignment = 'RIGHT'
762                    note.label(icon='INFO', text=label)
763
764            split = layout.split()
765            split.enabled = domain.has_cache_baked_data and note_flag and ob.mode == 'OBJECT'
766
767            bake_incomplete = (domain.cache_frame_pause_noise < domain.cache_frame_end)
768            if domain.has_cache_baked_noise and not domain.is_cache_baking_noise and bake_incomplete:
769                col = split.column()
770                col.operator("fluid.bake_noise", text="Resume")
771                col = split.column()
772                col.operator("fluid.free_noise", text="Free")
773            elif not domain.has_cache_baked_noise and domain.is_cache_baking_noise:
774                split.enabled = False
775                split.operator("fluid.pause_bake", text="Baking Noise - ESC to pause")
776            elif not domain.has_cache_baked_noise and not domain.is_cache_baking_noise:
777                split.operator("fluid.bake_noise", text="Bake Noise")
778            else:
779                split.operator("fluid.free_noise", text="Free Noise")
780
781
782class PHYSICS_PT_mesh(PhysicButtonsPanel, Panel):
783    bl_label = "Mesh"
784    bl_parent_id = 'PHYSICS_PT_liquid'
785    bl_options = {'DEFAULT_CLOSED'}
786    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
787
788    @classmethod
789    def poll(cls, context):
790        if not PhysicButtonsPanel.poll_liquid_domain(context):
791            return False
792
793        return (context.engine in cls.COMPAT_ENGINES)
794
795    def draw_header(self, context):
796        md = context.fluid.domain_settings
797        domain = context.fluid.domain_settings
798        is_baking_any = domain.is_cache_baking_any
799        self.layout.enabled = not is_baking_any
800        self.layout.prop(md, "use_mesh", text="")
801
802    def draw(self, context):
803        layout = self.layout
804        layout.use_property_split = True
805
806        ob = context.object
807        domain = context.fluid.domain_settings
808        layout.active = domain.use_mesh
809
810        is_baking_any = domain.is_cache_baking_any
811        has_baked_mesh = domain.has_cache_baked_mesh
812
813        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
814        flow.enabled = not is_baking_any and not has_baked_mesh
815
816        col = flow.column()
817
818        col.prop(domain, "mesh_scale", text="Upres Factor")
819        col.prop(domain, "mesh_particle_radius", text="Particle Radius")
820
821        col = flow.column()
822        col.prop(domain, "use_speed_vectors", text="Use Speed Vectors")
823
824        col.separator()
825        col.prop(domain, "mesh_generator", text="Mesh Generator")
826
827        if domain.mesh_generator in {'IMPROVED'}:
828            col = flow.column(align=True)
829            col.prop(domain, "mesh_smoothen_pos", text="Smoothing Positive")
830            col.prop(domain, "mesh_smoothen_neg", text="Negative")
831
832            col = flow.column(align=True)
833            col.prop(domain, "mesh_concave_upper", text="Concavity Upper")
834            col.prop(domain, "mesh_concave_lower", text="Lower")
835
836        # TODO (sebbas): for now just interpolate any upres grids, ie not sampling highres grids
837        #col.prop(domain, "highres_sampling", text="Flow Sampling:")
838
839        if domain.cache_type == 'MODULAR':
840            col.separator()
841
842            # Deactivate bake operator if data has not been baked yet.
843            note_flag = True
844            if domain.use_mesh:
845                label = ""
846                if not domain.cache_resumable:
847                    label = "Non Resumable Cache: Enable resumable option first"
848                elif not domain.has_cache_baked_data:
849                    label = "Unbaked Data: Bake Data first"
850
851                if label:
852                    info = layout.split()
853                    note = info.row()
854                    note_flag = False
855                    note.enabled = note_flag
856                    note.alignment = 'RIGHT'
857                    note.label(icon='INFO', text=label)
858
859            split = layout.split()
860            split.enabled = domain.has_cache_baked_data and note_flag and ob.mode == 'OBJECT'
861
862            bake_incomplete = (domain.cache_frame_pause_mesh < domain.cache_frame_end)
863            if domain.has_cache_baked_mesh and not domain.is_cache_baking_mesh and bake_incomplete:
864                col = split.column()
865                col.operator("fluid.bake_mesh", text="Resume")
866                col = split.column()
867                col.operator("fluid.free_mesh", text="Free")
868            elif not domain.has_cache_baked_mesh and domain.is_cache_baking_mesh:
869                split.enabled = False
870                split.operator("fluid.pause_bake", text="Baking Mesh - ESC to pause")
871            elif not domain.has_cache_baked_mesh and not domain.is_cache_baking_mesh:
872                split.operator("fluid.bake_mesh", text="Bake Mesh")
873            else:
874                split.operator("fluid.free_mesh", text="Free Mesh")
875
876
877class PHYSICS_PT_particles(PhysicButtonsPanel, Panel):
878    bl_label = "Particles"
879    bl_parent_id = 'PHYSICS_PT_liquid'
880    bl_options = {'DEFAULT_CLOSED'}
881    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
882
883    @classmethod
884    def poll(cls, context):
885        if not PhysicButtonsPanel.poll_liquid_domain(context):
886            return False
887
888        return (context.engine in cls.COMPAT_ENGINES)
889
890    def draw(self, context):
891        layout = self.layout
892        layout.use_property_split = True
893
894        ob = context.object
895        domain = context.fluid.domain_settings
896
897        is_baking_any = domain.is_cache_baking_any
898        has_baked_particles = domain.has_cache_baked_particles
899        using_particles = domain.use_spray_particles or domain.use_foam_particles or domain.use_bubble_particles
900
901        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
902        flow.enabled = not is_baking_any
903
904        sndparticle_combined_export = domain.sndparticle_combined_export
905        col = flow.column()
906        row = col.row()
907        row.enabled = sndparticle_combined_export in {'OFF', 'FOAM + BUBBLES'}
908        row.prop(domain, "use_spray_particles", text="Spray")
909        row.prop(domain, "use_foam_particles", text="Foam")
910        row.prop(domain, "use_bubble_particles", text="Bubbles")
911
912        col.separator()
913
914        col.prop(domain, "sndparticle_combined_export")
915
916        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
917        flow.enabled = not is_baking_any and not has_baked_particles
918        flow.active = using_particles
919
920        col = flow.column()
921        col.prop(domain, "particle_scale", text="Upres Factor")
922        col.separator()
923
924        col = flow.column(align=True)
925        col.prop(domain, "sndparticle_potential_max_wavecrest", text="Wave Crest Potential Maximum")
926        col.prop(domain, "sndparticle_potential_min_wavecrest", text="Minimum")
927        col.separator()
928
929        col = flow.column(align=True)
930        col.prop(domain, "sndparticle_potential_max_trappedair", text="Trapped Air Potential Maximum")
931        col.prop(domain, "sndparticle_potential_min_trappedair", text="Minimum")
932        col.separator()
933
934        col = flow.column(align=True)
935        col.prop(domain, "sndparticle_potential_max_energy", text="Kinetic Energy Potential Maximum")
936        col.prop(domain, "sndparticle_potential_min_energy", text="Minimum")
937        col.separator()
938
939        col = flow.column(align=True)
940        col.prop(domain, "sndparticle_potential_radius", text="Potential Radius")
941        col.prop(domain, "sndparticle_update_radius", text="Particle Update Radius")
942        col.separator()
943
944        col = flow.column(align=True)
945        col.prop(domain, "sndparticle_sampling_wavecrest", text="Wave Crest Particle Sampling")
946        col.prop(domain, "sndparticle_sampling_trappedair", text="Trapped Air Particle Sampling")
947        col.separator()
948
949        col = flow.column(align=True)
950        col.prop(domain, "sndparticle_life_max", text="Particle Life Maximum")
951        col.prop(domain, "sndparticle_life_min", text="Minimum")
952        col.separator()
953
954        col = flow.column(align=True)
955        col.prop(domain, "sndparticle_bubble_buoyancy", text="Bubble Buoyancy")
956        col.prop(domain, "sndparticle_bubble_drag", text="Bubble Drag")
957        col.separator()
958
959        col = flow.column()
960        col.prop(domain, "sndparticle_boundary", text="Particles in Boundary:")
961
962        if domain.cache_type == 'MODULAR':
963            col.separator()
964
965            # Deactivate bake operator if data has not been baked yet.
966            note_flag = True
967            if using_particles:
968                label = ""
969                if not domain.cache_resumable:
970                    label = "Non Resumable Cache: Enable resumable option first"
971                elif not domain.has_cache_baked_data:
972                    label = "Unbaked Data: Bake Data first"
973
974                if label:
975                    info = layout.split()
976                    note = info.row()
977                    note_flag = False
978                    note.enabled = note_flag
979                    note.alignment = 'RIGHT'
980                    note.label(icon='INFO', text=label)
981
982            split = layout.split()
983            split.enabled = (
984                note_flag and
985                ob.mode == 'OBJECT' and
986                domain.has_cache_baked_data and
987                (domain.use_spray_particles or
988                 domain.use_bubble_particles or
989                 domain.use_foam_particles or
990                 domain.use_tracer_particles)
991            )
992
993            bake_incomplete = (domain.cache_frame_pause_particles < domain.cache_frame_end)
994            if domain.has_cache_baked_particles and not domain.is_cache_baking_particles and bake_incomplete:
995                col = split.column()
996                col.operator("fluid.bake_particles", text="Resume")
997                col = split.column()
998                col.operator("fluid.free_particles", text="Free")
999            elif not domain.has_cache_baked_particles and domain.is_cache_baking_particles:
1000                split.enabled = False
1001                split.operator("fluid.pause_bake", text="Baking Particles - ESC to pause")
1002            elif not domain.has_cache_baked_particles and not domain.is_cache_baking_particles:
1003                split.operator("fluid.bake_particles", text="Bake Particles")
1004            else:
1005                split.operator("fluid.free_particles", text="Free Particles")
1006
1007
1008class PHYSICS_PT_diffusion(PhysicButtonsPanel, Panel):
1009    bl_label = "Diffusion"
1010    bl_parent_id = 'PHYSICS_PT_liquid'
1011    bl_options = {'DEFAULT_CLOSED'}
1012    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
1013
1014    @classmethod
1015    def poll(cls, context):
1016        # Fluid diffusion only enabled for liquids (surface tension and viscosity not relevant for smoke)
1017        if not PhysicButtonsPanel.poll_liquid_domain(context):
1018            return False
1019
1020        return (context.engine in cls.COMPAT_ENGINES)
1021
1022    def draw_header(self, context):
1023        md = context.fluid.domain_settings
1024        domain = context.fluid.domain_settings
1025        is_baking_any = domain.is_cache_baking_any
1026        has_baked_any = domain.has_cache_baked_any
1027        self.layout.enabled = not is_baking_any and not has_baked_any
1028        self.layout.prop(md, "use_diffusion", text="")
1029
1030    def draw_header_preset(self, _context):
1031        FLUID_PT_presets.draw_panel_header(self.layout)
1032
1033    def draw(self, context):
1034        layout = self.layout
1035        layout.use_property_split = True
1036
1037        domain = context.fluid.domain_settings
1038        layout.active = domain.use_diffusion
1039
1040        is_baking_any = domain.is_cache_baking_any
1041        has_baked_any = domain.has_cache_baked_any
1042        has_baked_data = domain.has_cache_baked_data
1043
1044        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
1045        flow.enabled = not is_baking_any and not has_baked_any and not has_baked_data
1046
1047        col = flow.column(align=True)
1048        col.prop(domain, "viscosity_base", text="Base")
1049        col.prop(domain, "viscosity_exponent", text="Exponent", slider=True)
1050
1051        col = flow.column()
1052        col.prop(domain, "surface_tension", text="Surface Tension")
1053
1054
1055class PHYSICS_PT_guide(PhysicButtonsPanel, Panel):
1056    bl_label = "Guides"
1057    bl_parent_id = 'PHYSICS_PT_fluid'
1058    bl_options = {'DEFAULT_CLOSED'}
1059    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
1060
1061    @classmethod
1062    def poll(cls, context):
1063        if not PhysicButtonsPanel.poll_fluid_domain(context):
1064            return False
1065
1066        return (context.engine in cls.COMPAT_ENGINES)
1067
1068    def draw_header(self, context):
1069        md = context.fluid.domain_settings
1070        domain = context.fluid.domain_settings
1071
1072        is_baking_any = domain.is_cache_baking_any
1073
1074        self.layout.enabled = not is_baking_any
1075        self.layout.prop(md, "use_guide", text="")
1076
1077    def draw(self, context):
1078        layout = self.layout
1079        layout.use_property_split = True
1080
1081        domain = context.fluid.domain_settings
1082
1083        layout.active = domain.use_guide
1084
1085        is_baking_any = domain.is_cache_baking_any
1086        has_baked_data = domain.has_cache_baked_data
1087
1088        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
1089        flow.enabled = not is_baking_any and not has_baked_data
1090
1091        col = flow.column()
1092        col.prop(domain, "guide_alpha", text="Weight")
1093        col.prop(domain, "guide_beta", text="Size")
1094        col.prop(domain, "guide_vel_factor", text="Velocity Factor")
1095
1096        col = flow.column()
1097        col.prop(domain, "guide_source", text="Velocity Source")
1098        if domain.guide_source == 'DOMAIN':
1099            col.prop(domain, "guide_parent", text="Guide Parent")
1100
1101        if domain.cache_type == 'MODULAR':
1102            col.separator()
1103
1104            if domain.guide_source == 'EFFECTOR':
1105                split = layout.split()
1106                bake_incomplete = (domain.cache_frame_pause_guide < domain.cache_frame_end)
1107                if domain.has_cache_baked_guide and not domain.is_cache_baking_guide and bake_incomplete:
1108                    col = split.column()
1109                    col.operator("fluid.bake_guides", text="Resume")
1110                    col = split.column()
1111                    col.operator("fluid.free_guides", text="Free")
1112                elif not domain.has_cache_baked_guide and domain.is_cache_baking_guide:
1113                    split.enabled = False
1114                    split.operator("fluid.pause_bake", text="Baking Guides - ESC to pause")
1115                elif not domain.has_cache_baked_guide and not domain.is_cache_baking_guide:
1116                    split.operator("fluid.bake_guides", text="Bake Guides")
1117                else:
1118                    split.operator("fluid.free_guides", text="Free Guides")
1119
1120
1121class PHYSICS_PT_collections(PhysicButtonsPanel, Panel):
1122    bl_label = "Collections"
1123    bl_parent_id = 'PHYSICS_PT_fluid'
1124    bl_options = {'DEFAULT_CLOSED'}
1125    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
1126
1127    @classmethod
1128    def poll(cls, context):
1129        if not PhysicButtonsPanel.poll_fluid_domain(context):
1130            return False
1131
1132        return (context.engine in cls.COMPAT_ENGINES)
1133
1134    def draw(self, context):
1135        layout = self.layout
1136        layout.use_property_split = True
1137
1138        domain = context.fluid.domain_settings
1139
1140        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
1141
1142        col = flow.column()
1143        col.prop(domain, "fluid_group", text="Flow")
1144
1145        # col.prop(domain, "effector_group", text="Forces")
1146        col.prop(domain, "effector_group", text="Effector")
1147
1148
1149class PHYSICS_PT_cache(PhysicButtonsPanel, Panel):
1150    bl_label = "Cache"
1151    bl_parent_id = 'PHYSICS_PT_fluid'
1152    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
1153
1154    @classmethod
1155    def poll(cls, context):
1156        if not PhysicButtonsPanel.poll_fluid_domain(context):
1157            return False
1158
1159        return (context.engine in cls.COMPAT_ENGINES)
1160
1161    def draw(self, context):
1162        layout = self.layout
1163
1164        ob = context.object
1165        md = context.fluid
1166        domain = context.fluid.domain_settings
1167
1168        is_baking_any = domain.is_cache_baking_any
1169        has_baked_data = domain.has_cache_baked_data
1170        has_baked_mesh = domain.has_cache_baked_mesh
1171
1172        col = layout.column()
1173        col.prop(domain, "cache_directory", text="")
1174        col.enabled = not is_baking_any
1175
1176        layout.use_property_split = True
1177
1178        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
1179
1180        col = flow.column()
1181        row = col.row()
1182        row = row.column(align=True)
1183        row.prop(domain, "cache_frame_start", text="Frame Start")
1184        row.prop(domain, "cache_frame_end", text="End")
1185        row = col.row()
1186        row.enabled = domain.cache_type in {'MODULAR', 'ALL'}
1187        row.prop(domain, "cache_frame_offset", text="Offset")
1188
1189        col.separator()
1190
1191        col = flow.column()
1192        col.prop(domain, "cache_type", expand=False)
1193
1194        row = col.row()
1195        row.enabled = not is_baking_any and not has_baked_data
1196        row.prop(domain, "cache_resumable", text="Is Resumable")
1197
1198        row = col.row()
1199        row.enabled = not is_baking_any and not has_baked_data
1200        row.prop(domain, "cache_data_format", text="Format Volumes")
1201
1202        if md.domain_settings.domain_type in {'LIQUID'} and domain.use_mesh:
1203            row = col.row()
1204            row.enabled = not is_baking_any and not has_baked_mesh
1205            row.prop(domain, "cache_mesh_format", text="Meshes")
1206
1207        if domain.cache_type == 'ALL':
1208            col.separator()
1209            split = layout.split()
1210            split.enabled = ob.mode == 'OBJECT'
1211
1212            bake_incomplete = (domain.cache_frame_pause_data < domain.cache_frame_end)
1213            if domain.cache_resumable and domain.has_cache_baked_data and not domain.is_cache_baking_data and bake_incomplete:
1214                col = split.column()
1215                col.operator("fluid.bake_all", text="Resume")
1216                col = split.column()
1217                col.operator("fluid.free_all", text="Free")
1218            elif domain.is_cache_baking_data and not domain.has_cache_baked_data:
1219                split.enabled = False
1220                split.operator("fluid.pause_bake", text="Baking All - ESC to pause")
1221            elif not domain.has_cache_baked_data and not domain.is_cache_baking_data:
1222                split.operator("fluid.bake_all", text="Bake All")
1223            else:
1224                split.operator("fluid.free_all", text="Free All")
1225
1226
1227class PHYSICS_PT_export(PhysicButtonsPanel, Panel):
1228    bl_label = "Advanced"
1229    bl_parent_id = 'PHYSICS_PT_cache'
1230    bl_options = {'DEFAULT_CLOSED'}
1231    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
1232
1233    @classmethod
1234    def poll(cls, context):
1235        domain = context.fluid.domain_settings
1236        if (
1237                not PhysicButtonsPanel.poll_fluid_domain(context) or
1238                (domain.cache_data_format != 'OPENVDB' and bpy.app.debug_value != 3001)
1239        ):
1240            return False
1241
1242        return (context.engine in cls.COMPAT_ENGINES)
1243
1244    def draw(self, context):
1245        layout = self.layout
1246        layout.use_property_split = True
1247
1248        domain = context.fluid.domain_settings
1249
1250        is_baking_any = domain.is_cache_baking_any
1251        has_baked_any = domain.has_cache_baked_any
1252        has_baked_data = domain.has_cache_baked_data
1253
1254        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
1255        flow.enabled = not is_baking_any and not has_baked_any
1256
1257        col = flow.column()
1258
1259        if domain.cache_data_format == 'OPENVDB':
1260            col.enabled = not is_baking_any and not has_baked_data
1261            col.prop(domain, "openvdb_cache_compress_type", text="Compression Volumes")
1262
1263            col = flow.column()
1264            col.prop(domain, "openvdb_data_depth", text="Precision Volumes")
1265
1266        # Only show the advanced panel to advanced users who know Mantaflow's birthday :)
1267        if bpy.app.debug_value == 3001:
1268            col = flow.column()
1269            col.prop(domain, "export_manta_script", text="Export Mantaflow Script")
1270
1271
1272class PHYSICS_PT_field_weights(PhysicButtonsPanel, Panel):
1273    bl_label = "Field Weights"
1274    bl_parent_id = 'PHYSICS_PT_fluid'
1275    bl_options = {'DEFAULT_CLOSED'}
1276    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
1277
1278    @classmethod
1279    def poll(cls, context):
1280        if not PhysicButtonsPanel.poll_fluid_domain(context):
1281            return False
1282
1283        return (context.engine in cls.COMPAT_ENGINES)
1284
1285    def draw(self, context):
1286        domain = context.fluid.domain_settings
1287        effector_weights_ui(self, domain.effector_weights, 'SMOKE')
1288
1289
1290class PHYSICS_PT_viewport_display(PhysicButtonsPanel, Panel):
1291    bl_label = "Viewport Display"
1292    bl_parent_id = 'PHYSICS_PT_fluid'
1293    bl_options = {'DEFAULT_CLOSED'}
1294
1295    @classmethod
1296    def poll(cls, context):
1297        return (PhysicButtonsPanel.poll_fluid_domain(context))
1298
1299    def draw(self, context):
1300        layout = self.layout
1301        layout.use_property_split = True
1302        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
1303
1304        domain = context.fluid.domain_settings
1305
1306        col = flow.column(align=False)
1307        col.prop(domain, "display_thickness")
1308
1309        sub = col.column()
1310        sub.prop(domain, "display_interpolation")
1311
1312        if domain.use_color_ramp and domain.color_ramp_field == 'FLAGS':
1313            sub.enabled = False
1314
1315        col = col.column()
1316        col.active = not domain.use_slice
1317        col.prop(domain, "slice_per_voxel")
1318
1319
1320class PHYSICS_PT_viewport_display_slicing(PhysicButtonsPanel, Panel):
1321    bl_label = "Slice"
1322    bl_parent_id = 'PHYSICS_PT_viewport_display'
1323    bl_options = {'DEFAULT_CLOSED'}
1324
1325    @classmethod
1326    def poll(cls, context):
1327        return (PhysicButtonsPanel.poll_fluid_domain(context))
1328
1329    def draw_header(self, context):
1330        md = context.fluid.domain_settings
1331
1332        self.layout.prop(md, "use_slice", text="")
1333
1334    def draw(self, context):
1335        layout = self.layout
1336        layout.use_property_split = True
1337
1338        domain = context.fluid.domain_settings
1339
1340        layout.active = domain.use_slice
1341
1342        col = layout.column()
1343        col.prop(domain, "slice_axis")
1344        col.prop(domain, "slice_depth")
1345
1346        sub = col.column()
1347        sub.prop(domain, "show_gridlines")
1348
1349        sub.active = domain.display_interpolation == 'CLOSEST' or domain.color_ramp_field == 'FLAGS'
1350
1351
1352class PHYSICS_PT_viewport_display_color(PhysicButtonsPanel, Panel):
1353    bl_label = "Grid Display"
1354    bl_parent_id = 'PHYSICS_PT_viewport_display'
1355    bl_options = {'DEFAULT_CLOSED'}
1356
1357    @classmethod
1358    def poll(cls, context):
1359        return (PhysicButtonsPanel.poll_fluid_domain(context))
1360
1361    def draw_header(self, context):
1362        md = context.fluid.domain_settings
1363
1364        self.layout.prop(md, "use_color_ramp", text="")
1365
1366    def draw(self, context):
1367        layout = self.layout
1368        layout.use_property_split = True
1369
1370        domain = context.fluid.domain_settings
1371        col = layout.column()
1372        col.active = domain.use_color_ramp
1373        col.prop(domain, "color_ramp_field")
1374
1375        if not domain.color_ramp_field == 'FLAGS':
1376            col.prop(domain, "color_ramp_field_scale")
1377
1378        col.use_property_split = False
1379
1380        if domain.color_ramp_field[:3] != 'PHI' and domain.color_ramp_field not in {'FLAGS', 'PRESSURE'}:
1381            col = col.column()
1382            col.template_color_ramp(domain, "color_ramp", expand=True)
1383
1384
1385class PHYSICS_PT_viewport_display_debug(PhysicButtonsPanel, Panel):
1386    bl_label = "Vector Display"
1387    bl_parent_id = 'PHYSICS_PT_viewport_display'
1388    bl_options = {'DEFAULT_CLOSED'}
1389
1390    @classmethod
1391    def poll(cls, context):
1392        return (PhysicButtonsPanel.poll_fluid_domain(context))
1393
1394    def draw_header(self, context):
1395        md = context.fluid.domain_settings
1396
1397        self.layout.prop(md, "show_velocity", text="")
1398
1399    def draw(self, context):
1400        layout = self.layout
1401        layout.use_property_split = True
1402        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
1403
1404        domain = context.fluid.domain_settings
1405
1406        col = flow.column()
1407        col.active = domain.show_velocity
1408        col.prop(domain, "vector_display_type", text="Display As")
1409
1410        if not domain.use_guide and domain.vector_field == 'GUIDE_VELOCITY':
1411            note = layout.split()
1412            note.label(icon='INFO', text="Enable Guides first! Defaulting to Fluid Velocity")
1413
1414        if domain.vector_display_type == 'MAC':
1415            sub = col.column(heading="MAC Grid")
1416            sub.prop(domain, "vector_show_mac_x")
1417            sub.prop(domain, "vector_show_mac_y")
1418            sub.prop(domain, "vector_show_mac_z")
1419        else:
1420            col.prop(domain, "vector_scale_with_magnitude")
1421
1422        col.prop(domain, "vector_field")
1423        col.prop(domain, "vector_scale")
1424
1425
1426class PHYSICS_PT_viewport_display_advanced(PhysicButtonsPanel, Panel):
1427    bl_label = "Advanced"
1428    bl_parent_id = 'PHYSICS_PT_viewport_display'
1429    bl_options = {'DEFAULT_CLOSED'}
1430
1431    @classmethod
1432    def poll(cls, context):
1433        domain = context.fluid.domain_settings
1434        return PhysicButtonsPanel.poll_fluid_domain(context) and domain.use_slice and domain.show_gridlines
1435
1436    def draw(self, context):
1437        layout = self.layout
1438        layout.use_property_split = True
1439
1440        domain = context.fluid.domain_settings
1441
1442        layout.active = domain.display_interpolation == 'CLOSEST' or domain.color_ramp_field == 'FLAGS'
1443
1444        col = layout.column()
1445        col.prop(domain, "gridlines_color_field", text="Color Gridlines")
1446
1447        if domain.gridlines_color_field == 'RANGE':
1448            if domain.use_color_ramp and domain.color_ramp_field != 'FLAGS':
1449                col.prop(domain, "gridlines_lower_bound")
1450                col.prop(domain, "gridlines_upper_bound")
1451                col.prop(domain, "gridlines_range_color")
1452                col.prop(domain, "gridlines_cell_filter")
1453            else:
1454                note = layout.split()
1455                if not domain.use_color_ramp:
1456                    note.label(icon='INFO', text="Enable Grid Display to use range highlighting!")
1457                else:
1458                    note.label(icon='INFO', text="Range highlighting for flags is not available!")
1459
1460
1461classes = (
1462    FLUID_PT_presets,
1463    PHYSICS_PT_fluid,
1464    PHYSICS_PT_settings,
1465    PHYSICS_PT_borders,
1466    PHYSICS_PT_adaptive_domain,
1467    PHYSICS_PT_smoke,
1468    PHYSICS_PT_smoke_dissolve,
1469    PHYSICS_PT_noise,
1470    PHYSICS_PT_fire,
1471    PHYSICS_PT_liquid,
1472    PHYSICS_PT_diffusion,
1473    PHYSICS_PT_particles,
1474    PHYSICS_PT_mesh,
1475    PHYSICS_PT_guide,
1476    PHYSICS_PT_collections,
1477    PHYSICS_PT_cache,
1478    PHYSICS_PT_export,
1479    PHYSICS_PT_field_weights,
1480    PHYSICS_PT_flow_source,
1481    PHYSICS_PT_flow_initial_velocity,
1482    PHYSICS_PT_flow_texture,
1483    PHYSICS_PT_viewport_display,
1484    PHYSICS_PT_viewport_display_slicing,
1485    PHYSICS_PT_viewport_display_color,
1486    PHYSICS_PT_viewport_display_debug,
1487    PHYSICS_PT_viewport_display_advanced,
1488)
1489
1490
1491if __name__ == "__main__":  # only for live edit.
1492    from bpy.utils import register_class
1493    for cls in classes:
1494        register_class(cls)
1495