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