1f52f768676a2b50beb052ba25bc2a722f7ee72a
[blender.git] / release / scripts / startup / bl_ui / properties_physics_smoke.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 .properties_physics_common import (
26     point_cache_ui,
27     effector_weights_ui,
28 )
29
30
31 class PhysicButtonsPanel:
32     bl_space_type = 'PROPERTIES'
33     bl_region_type = 'WINDOW'
34     bl_context = "physics"
35
36     def poll_smoke(context):
37         ob = context.object
38         if not ((ob and ob.type == 'MESH') and (context.smoke)):
39             return False
40
41         md = context.smoke
42         return md and (context.smoke.smoke_type != 'NONE') and (bpy.app.build_options.mod_smoke)
43
44     def poll_smoke_domain(context):
45         if not PhysicButtonsPanel.poll_smoke(context):
46             return False
47
48         md = context.smoke
49         return md and (md.smoke_type == 'DOMAIN')
50
51
52 class PHYSICS_PT_smoke(PhysicButtonsPanel, Panel):
53     bl_label = "Smoke"
54     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
55
56     @classmethod
57     def poll(cls, context):
58         ob = context.object
59         return (ob and ob.type == 'MESH') and (context.engine in cls.COMPAT_ENGINES) and (context.smoke)
60
61     def draw(self, context):
62         layout = self.layout
63         layout.use_property_split = True
64
65         if not bpy.app.build_options.mod_smoke:
66             col = layout.column(align=True)
67             col.alignment = 'RIGHT'
68             col.label(text="Built without Smoke modifier")
69             return
70
71         md = context.smoke
72
73         layout.prop(md, "smoke_type")
74
75
76 class PHYSICS_PT_smoke_settings(PhysicButtonsPanel, Panel):
77     bl_label = "Settings"
78     bl_parent_id = 'PHYSICS_PT_smoke'
79     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
80
81     @classmethod
82     def poll(cls, context):
83         if not PhysicButtonsPanel.poll_smoke(context):
84             return False
85
86         return (context.engine in cls.COMPAT_ENGINES)
87
88     def draw(self, context):
89         layout = self.layout
90         layout.use_property_split = True
91
92         md = context.smoke
93         ob = context.object
94
95         if md.smoke_type == 'DOMAIN':
96             domain = md.domain_settings
97
98             flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
99
100             col = flow.column()
101             col.enabled = (not domain.point_cache.is_baked)
102             col.prop(domain, "resolution_max", text="Resolution Divisions")
103             col.prop(domain, "time_scale", text="Time Scale")
104
105             col.separator()
106
107             col = flow.column()
108             sub = col.row()
109             sub.enabled = (not domain.point_cache.is_baked)
110             sub.prop(domain, "collision_extents", text="Border Collisions")
111
112             # This can be tweaked after baking, for render.
113             col.prop(domain, "clipping", text="Empty Space")
114
115         elif md.smoke_type == 'FLOW':
116             flow_smoke = md.flow_settings
117
118             col = layout.column()
119             col.prop(flow_smoke, "smoke_flow_type", expand=False)
120
121             col.separator()
122
123             flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=True)
124             col = flow.column()
125
126             if flow_smoke.smoke_flow_type != 'OUTFLOW':
127                 col.prop(flow_smoke, "smoke_flow_source", expand=False, text="Flow Source")
128
129                 if flow_smoke.smoke_flow_source == 'PARTICLES':
130                     col.prop_search(
131                         flow_smoke, "particle_system", ob, "particle_systems",
132                         text="Particle System"
133                     )
134                 else:
135                     col.prop(flow_smoke, "surface_distance")
136                     col.prop(flow_smoke, "volume_density")
137
138                 col = flow.column()
139                 col.prop(flow_smoke, "use_absolute")
140
141                 if flow_smoke.smoke_flow_type in {'SMOKE', 'BOTH'}:
142                     col.prop(flow_smoke, "density")
143                     col.prop(flow_smoke, "temperature", text="Temperature Diff.")
144
145                     col.separator()
146
147                     col = flow.column()
148                     col.prop(flow_smoke, "smoke_color")
149
150                 if flow_smoke.smoke_flow_type in {'FIRE', 'BOTH'}:
151                     col.prop(flow_smoke, "fuel_amount")
152
153                 col.prop(flow_smoke, "subframes", text="Sampling Subframes")
154
155             col.separator()
156
157             col.prop_search(flow_smoke, "density_vertex_group", ob, "vertex_groups", text="Vertex Group")
158
159         elif md.smoke_type == 'COLLISION':
160             coll = md.coll_settings
161
162             col = layout.column()
163             col.prop(coll, "collision_type")
164
165
166 class PHYSICS_PT_smoke_settings_initial_velocity(PhysicButtonsPanel, Panel):
167     bl_label = "Initial Velocity"
168     bl_parent_id = 'PHYSICS_PT_smoke_settings'
169     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
170
171     @classmethod
172     def poll(cls, context):
173         if not PhysicButtonsPanel.poll_smoke(context):
174             return False
175
176         md = context.smoke
177         return (md and (md.smoke_type == 'FLOW')
178                 and md.flow_settings and md.flow_settings.smoke_flow_type != 'OUTFLOW'
179                 and context.engine in cls.COMPAT_ENGINES)
180
181     def draw_header(self, context):
182         md = context.smoke
183         flow_smoke = md.flow_settings
184
185         self.layout.prop(flow_smoke, "use_initial_velocity", text="")
186
187     def draw(self, context):
188         layout = self.layout
189         layout.use_property_split = True
190         flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=True)
191
192         md = context.smoke
193         flow_smoke = md.flow_settings
194
195         flow.active = flow_smoke.use_initial_velocity
196
197         col = flow.column(align=True)
198         col.prop(flow_smoke, "velocity_factor")
199
200         if flow_smoke.smoke_flow_source == 'MESH':
201             col = flow.column()
202             col.prop(flow_smoke, "velocity_normal")
203             # sub.prop(flow_smoke, "velocity_random")
204
205
206 class PHYSICS_PT_smoke_settings_particle_size(PhysicButtonsPanel, Panel):
207     bl_label = "Particle Size"
208     bl_parent_id = 'PHYSICS_PT_smoke_settings'
209     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
210
211     @classmethod
212     def poll(cls, context):
213         if not PhysicButtonsPanel.poll_smoke(context):
214             return False
215
216         md = context.smoke
217         return (md and (md.smoke_type == 'FLOW')
218                 and md.flow_settings and md.flow_settings.smoke_flow_type != 'OUTFLOW'
219                 and md.flow_settings.smoke_flow_source == 'PARTICLES'
220                 and context.engine in cls.COMPAT_ENGINES)
221
222     def draw_header(self, context):
223         md = context.smoke
224         flow_smoke = md.flow_settings
225
226         self.layout.prop(flow_smoke, "use_particle_size", text="")
227
228     def draw(self, context):
229         layout = self.layout
230         layout.use_property_split = True
231
232         md = context.smoke
233         flow_smoke = md.flow_settings
234
235         layout.active = flow_smoke.use_particle_size
236
237         layout.prop(flow_smoke, "particle_size")
238
239
240 class PHYSICS_PT_smoke_behavior(PhysicButtonsPanel, Panel):
241     bl_label = "Behavior"
242     bl_parent_id = 'PHYSICS_PT_smoke_settings'
243     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
244
245     @classmethod
246     def poll(cls, context):
247         if not PhysicButtonsPanel.poll_smoke_domain(context):
248             return False
249
250         return (context.engine in cls.COMPAT_ENGINES)
251
252     def draw(self, context):
253         layout = self.layout
254         layout.use_property_split = True
255
256         md = context.smoke
257         domain = md.domain_settings
258
259         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
260         flow.enabled = (not domain.point_cache.is_baked)
261
262         col = flow.column()
263         col.prop(domain, "alpha")
264         col.prop(domain, "beta", text="Temperature Diff.")
265         col = flow.column()
266         col.prop(domain, "vorticity")
267
268
269 class PHYSICS_PT_smoke_behavior_dissolve(PhysicButtonsPanel, Panel):
270     bl_label = "Dissolve"
271     bl_parent_id = 'PHYSICS_PT_smoke_behavior'
272     bl_options = {'DEFAULT_CLOSED'}
273     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
274
275     @classmethod
276     def poll(cls, context):
277         if not PhysicButtonsPanel.poll_smoke_domain(context):
278             return False
279
280         return (context.engine in cls.COMPAT_ENGINES)
281
282     def draw_header(self, context):
283         md = context.smoke
284         domain = md.domain_settings
285
286         self.layout.prop(domain, "use_dissolve_smoke", text="")
287
288     def draw(self, context):
289         layout = self.layout
290         layout.use_property_split = True
291
292         md = context.smoke
293         domain = md.domain_settings
294
295         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
296         flow.enabled = (not domain.point_cache.is_baked)
297
298         layout.active = domain.use_dissolve_smoke
299
300         col = flow.column()
301         col.prop(domain, "dissolve_speed", text="Time")
302
303         col = flow.column()
304         col.prop(domain, "use_dissolve_smoke_log", text="Slow")
305
306
307 class PHYSICS_PT_smoke_flow_texture(PhysicButtonsPanel, Panel):
308     bl_label = "Texture"
309     bl_parent_id = 'PHYSICS_PT_smoke'
310     bl_options = {'DEFAULT_CLOSED'}
311     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
312
313     @classmethod
314     def poll(cls, context):
315         if not PhysicButtonsPanel.poll_smoke(context):
316             return False
317
318         md = context.smoke
319         return (md and (md.smoke_type == 'FLOW')
320                 and (md.flow_settings.smoke_flow_source == 'MESH')
321                 and (context.engine in cls.COMPAT_ENGINES))
322
323     def draw_header(self, context):
324         md = context.smoke
325         flow_smoke = md.flow_settings
326
327         self.layout.prop(flow_smoke, "use_texture", text="")
328
329     def draw(self, context):
330         layout = self.layout
331         layout.use_property_split = True
332         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
333
334         ob = context.object
335         flow_smoke = context.smoke.flow_settings
336
337         sub = flow.column()
338         sub.active = flow_smoke.use_texture
339         sub.prop(flow_smoke, "noise_texture")
340         sub.prop(flow_smoke, "texture_map_type", text="Mapping")
341
342         col = flow.column()
343         sub = col.column()
344         sub.active = flow_smoke.use_texture
345
346         if flow_smoke.texture_map_type == 'UV':
347             sub.prop_search(flow_smoke, "uv_layer", ob.data, "uv_layers")
348
349         if flow_smoke.texture_map_type == 'AUTO':
350             sub.prop(flow_smoke, "texture_size")
351
352         sub.prop(flow_smoke, "texture_offset")
353
354
355 class PHYSICS_PT_smoke_fire(PhysicButtonsPanel, Panel):
356     bl_label = "Flames"
357     bl_parent_id = 'PHYSICS_PT_smoke'
358     bl_options = {'DEFAULT_CLOSED'}
359     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
360
361     @classmethod
362     def poll(cls, context):
363         if not PhysicButtonsPanel.poll_smoke_domain(context):
364             return False
365
366         return (context.engine in cls.COMPAT_ENGINES)
367
368     def draw(self, context):
369         layout = self.layout
370         layout.use_property_split = True
371
372         domain = context.smoke.domain_settings
373
374         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
375         flow.enabled = (not domain.point_cache.is_baked)
376
377         col = flow.column()
378         col.prop(domain, "burning_rate", text="Reaction Speed")
379         col.prop(domain, "flame_smoke")
380         col.prop(domain, "flame_vorticity")
381
382         col.separator()
383
384         col = flow.column(align=True)
385         col.prop(domain, "flame_ignition", text="Temperature Ignition")
386         col.prop(domain, "flame_max_temp")
387
388         col.separator()
389
390         sub = col.column()
391         sub.prop(domain, "flame_smoke_color")
392
393
394 class PHYSICS_PT_smoke_adaptive_domain(PhysicButtonsPanel, Panel):
395     bl_label = "Adaptive Domain"
396     bl_parent_id = 'PHYSICS_PT_smoke'
397     bl_options = {'DEFAULT_CLOSED'}
398     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
399
400     @classmethod
401     def poll(cls, context):
402         if not PhysicButtonsPanel.poll_smoke_domain(context):
403             return False
404
405         return (context.engine in cls.COMPAT_ENGINES)
406
407     def draw_header(self, context):
408         md = context.smoke.domain_settings
409
410         self.layout.prop(md, "use_adaptive_domain", text="")
411
412     def draw(self, context):
413         layout = self.layout
414         layout.use_property_split = True
415
416         domain = context.smoke.domain_settings
417         layout.active = domain.use_adaptive_domain
418
419         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
420         flow.enabled = (not domain.point_cache.is_baked)
421
422         col = flow.column()
423         col.prop(domain, "additional_res", text="Add Resolution")
424         col.prop(domain, "adapt_margin")
425
426         col.separator()
427
428         col = flow.column()
429         col.prop(domain, "adapt_threshold", text="Threshold")
430
431
432 class PHYSICS_PT_smoke_highres(PhysicButtonsPanel, Panel):
433     bl_label = "High Resolution"
434     bl_parent_id = 'PHYSICS_PT_smoke'
435     bl_options = {'DEFAULT_CLOSED'}
436     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
437
438     @classmethod
439     def poll(cls, context):
440         if not PhysicButtonsPanel.poll_smoke_domain(context):
441             return False
442
443         return (context.engine in cls.COMPAT_ENGINES)
444
445     def draw_header(self, context):
446         md = context.smoke.domain_settings
447
448         self.layout.prop(md, "use_high_resolution", text="")
449
450     def draw(self, context):
451         layout = self.layout
452         layout.use_property_split = True
453
454         md = context.smoke.domain_settings
455         layout.active = md.use_high_resolution
456
457         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
458
459         col = flow.column()
460         col.enabled = not md.point_cache.is_baked
461         col.prop(md, "amplify", text="Resolution Divisions")
462         col.prop(md, "highres_sampling", text="Flow Sampling")
463
464         col.separator()
465
466         col = flow.column()
467         col.enabled = not md.point_cache.is_baked
468         col.prop(md, "noise_type", text="Noise Method")
469         col.prop(md, "strength")
470
471         layout.prop(md, "show_high_resolution")
472
473
474 class PHYSICS_PT_smoke_collections(PhysicButtonsPanel, Panel):
475     bl_label = "Collections"
476     bl_parent_id = 'PHYSICS_PT_smoke'
477     bl_options = {'DEFAULT_CLOSED'}
478     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
479
480     @classmethod
481     def poll(cls, context):
482         if not PhysicButtonsPanel.poll_smoke_domain(context):
483             return False
484
485         return (context.engine in cls.COMPAT_ENGINES)
486
487     def draw(self, context):
488         layout = self.layout
489         layout.use_property_split = True
490
491         domain = context.smoke.domain_settings
492
493         col = layout.column()
494         col.prop(domain, "fluid_collection", text="Flow")
495
496         # col = layout.column()
497         # col.prop(domain, "effector_collection", text="Effector")
498         col.prop(domain, "collision_collection", text="Collision")
499
500
501 class PHYSICS_PT_smoke_cache(PhysicButtonsPanel, Panel):
502     bl_label = "Cache"
503     bl_parent_id = 'PHYSICS_PT_smoke'
504     bl_options = {'DEFAULT_CLOSED'}
505     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
506
507     @classmethod
508     def poll(cls, context):
509         if not PhysicButtonsPanel.poll_smoke_domain(context):
510             return False
511
512         return (context.engine in cls.COMPAT_ENGINES)
513
514     def draw(self, context):
515         layout = self.layout
516         layout.use_property_split = True
517         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
518
519         domain = context.smoke.domain_settings
520         cache_file_format = domain.cache_file_format
521
522         col = flow.column()
523         col.prop(domain, "cache_file_format")
524
525         if cache_file_format == 'POINTCACHE':
526             col = flow.column()
527             col.prop(domain, "point_cache_compress_type", text="Compression")
528             col.separator()
529
530         elif cache_file_format == 'OPENVDB':
531             if not bpy.app.build_options.openvdb:
532                 row = layout.row(align=True)
533                 row.alignment = 'RIGHT'
534                 row.label(text="Built without OpenVDB support")
535                 return
536
537             col = flow.column()
538             col.prop(domain, "openvdb_cache_compress_type", text="Compression")
539             col.prop(domain, "data_depth", text="Data Depth")
540             col.separator()
541
542         cache = domain.point_cache
543         point_cache_ui(self, context, cache, (cache.is_baked is False), 'SMOKE')
544
545
546 class PHYSICS_PT_smoke_field_weights(PhysicButtonsPanel, Panel):
547     bl_label = "Field Weights"
548     bl_parent_id = 'PHYSICS_PT_smoke'
549     bl_options = {'DEFAULT_CLOSED'}
550     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
551
552     @classmethod
553     def poll(cls, context):
554         if not PhysicButtonsPanel.poll_smoke_domain(context):
555             return False
556
557         return (context.engine in cls.COMPAT_ENGINES)
558
559     def draw(self, context):
560         domain = context.smoke.domain_settings
561         effector_weights_ui(self, context, domain.effector_weights, 'SMOKE')
562
563
564 class PHYSICS_PT_smoke_viewport_display(PhysicButtonsPanel, Panel):
565     bl_label = "Viewport Display"
566     bl_parent_id = 'PHYSICS_PT_smoke'
567     bl_options = {'DEFAULT_CLOSED'}
568
569     @classmethod
570     def poll(cls, context):
571         return (PhysicButtonsPanel.poll_smoke_domain(context))
572
573     def draw(self, context):
574         layout = self.layout
575         layout.use_property_split = True
576         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
577
578         domain = context.smoke.domain_settings
579
580         col = flow.column()
581         col.prop(domain, "display_thickness")
582
583         col.separator()
584
585         col.prop(domain, "slice_method", text="Slicing")
586
587         slice_method = domain.slice_method
588         axis_slice_method = domain.axis_slice_method
589
590         do_axis_slicing = (slice_method == 'AXIS_ALIGNED')
591         do_full_slicing = (axis_slice_method == 'FULL')
592
593         col = col.column()
594         col.enabled = do_axis_slicing
595         col.prop(domain, "axis_slice_method")
596
597         col = flow.column()
598         sub = col.column()
599         sub.enabled = not do_full_slicing and do_axis_slicing
600         sub.prop(domain, "slice_axis")
601         sub.prop(domain, "slice_depth")
602
603         row = col.row()
604         row.enabled = do_full_slicing or not do_axis_slicing
605         row.prop(domain, "slice_per_voxel")
606
607         col.prop(domain, "display_interpolation")
608
609
610 class PHYSICS_PT_smoke_viewport_display_color(PhysicButtonsPanel, Panel):
611     bl_label = "Color Mapping"
612     bl_parent_id = 'PHYSICS_PT_smoke_viewport_display'
613     bl_options = {'DEFAULT_CLOSED'}
614
615     @classmethod
616     def poll(cls, context):
617         return (PhysicButtonsPanel.poll_smoke_domain(context))
618
619     def draw_header(self, context):
620         md = context.smoke.domain_settings
621
622         self.layout.prop(md, "use_color_ramp", text="")
623
624     def draw(self, context):
625         layout = self.layout
626         layout.use_property_split = True
627
628         domain = context.smoke.domain_settings
629         col = layout.column()
630         col.enabled = domain.use_color_ramp
631
632         col.prop(domain, "coba_field")
633
634         col.use_property_split = False
635
636         col = col.column()
637         col.template_color_ramp(domain, "color_ramp", expand=True)
638
639
640 class PHYSICS_PT_smoke_viewport_display_debug(PhysicButtonsPanel, Panel):
641     bl_label = "Debug Velocity"
642     bl_parent_id = 'PHYSICS_PT_smoke_viewport_display'
643     bl_options = {'DEFAULT_CLOSED'}
644
645     @classmethod
646     def poll(cls, context):
647         return (PhysicButtonsPanel.poll_smoke_domain(context))
648
649     def draw_header(self, context):
650         md = context.smoke.domain_settings
651
652         self.layout.prop(md, "display_velocity", text="")
653
654     def draw(self, context):
655         layout = self.layout
656         layout.use_property_split = True
657         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
658
659         domain = context.smoke.domain_settings
660
661         col = flow.column()
662         col.enabled = domain.display_velocity
663         col.prop(domain, "vector_display_type", text="Display As")
664         col.prop(domain, "vector_scale")
665
666
667 classes = (
668     PHYSICS_PT_smoke,
669     PHYSICS_PT_smoke_settings,
670     PHYSICS_PT_smoke_settings_initial_velocity,
671     PHYSICS_PT_smoke_settings_particle_size,
672     PHYSICS_PT_smoke_behavior,
673     PHYSICS_PT_smoke_behavior_dissolve,
674     PHYSICS_PT_smoke_adaptive_domain,
675     PHYSICS_PT_smoke_cache,
676     PHYSICS_PT_smoke_field_weights,
677     PHYSICS_PT_smoke_fire,
678     PHYSICS_PT_smoke_flow_texture,
679     PHYSICS_PT_smoke_collections,
680     PHYSICS_PT_smoke_highres,
681     PHYSICS_PT_smoke_viewport_display,
682     PHYSICS_PT_smoke_viewport_display_color,
683     PHYSICS_PT_smoke_viewport_display_debug,
684 )
685
686
687 if __name__ == "__main__":  # only for live edit.
688     from bpy.utils import register_class
689     for cls in classes:
690         register_class(cls)