Merge branch 'master' into 28
[blender.git] / release / scripts / startup / bl_ui / properties_particle.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 import bpy
21 from bpy.types import Panel, Menu
22 from rna_prop_ui import PropertyPanel
23 from bpy.app.translations import pgettext_iface as iface_
24
25 from .properties_physics_common import (
26     point_cache_ui,
27     effector_weights_ui,
28     basic_force_field_settings_ui,
29     basic_force_field_falloff_ui,
30 )
31
32
33 def particle_panel_enabled(context, psys):
34     if psys is None:
35         return True
36     phystype = psys.settings.physics_type
37     if psys.settings.type in {'EMITTER', 'REACTOR'} and phystype in {'NO', 'KEYED'}:
38         return True
39     else:
40         return (psys.point_cache.is_baked is False) and (not psys.is_edited) and (not context.particle_system_editable)
41
42
43 def particle_panel_poll(cls, context):
44     psys = context.particle_system
45     engine = context.engine
46     settings = 0
47
48     if psys:
49         settings = psys.settings
50     elif isinstance(context.space_data.pin_id, bpy.types.ParticleSettings):
51         settings = context.space_data.pin_id
52
53     if not settings:
54         return False
55
56     return settings.is_fluid is False and (engine in cls.COMPAT_ENGINES)
57
58
59 def particle_get_settings(context):
60     if context.particle_system:
61         return context.particle_system.settings
62     elif isinstance(context.space_data.pin_id, bpy.types.ParticleSettings):
63         return context.space_data.pin_id
64     return None
65
66
67 class PARTICLE_MT_specials(Menu):
68     bl_label = "Particle Specials"
69     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
70
71     def draw(self, context):
72         layout = self.layout
73
74         props = layout.operator("particle.copy_particle_systems", text="Copy Active to Selected Objects")
75         props.use_active = True
76         props.remove_target_particles = False
77
78         props = layout.operator("particle.copy_particle_systems", text="Copy All to Selected Objects")
79         props.use_active = False
80         props.remove_target_particles = True
81
82         layout.operator("particle.duplicate_particle_system")
83
84
85 class PARTICLE_MT_hair_dynamics_presets(Menu):
86     bl_label = "Hair Dynamics Presets"
87     preset_subdir = "hair_dynamics"
88     preset_operator = "script.execute_preset"
89     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
90     draw = Menu.draw_preset
91
92
93 class ParticleButtonsPanel:
94     bl_space_type = 'PROPERTIES'
95     bl_region_type = 'WINDOW'
96     bl_context = "particle"
97
98     @classmethod
99     def poll(cls, context):
100         return particle_panel_poll(cls, context)
101
102
103 def find_modifier(ob, psys):
104     for md in ob.modifiers:
105         if md.type == 'PARTICLE_SYSTEM':
106             if md.particle_system == psys:
107                 return md
108
109
110 class PARTICLE_UL_particle_systems(bpy.types.UIList):
111
112     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
113         ob = data
114         psys = item
115
116         if self.layout_type in {'DEFAULT', 'COMPACT'}:
117             md = find_modifier(ob, psys)
118
119             layout.prop(psys, "name", text="", emboss=False, icon_value=icon)
120             if md:
121                 layout.prop(md, "show_render", emboss=False, icon_only=True,
122                             icon='RESTRICT_RENDER_OFF' if md.show_render else 'RESTRICT_RENDER_ON')
123                 layout.prop(md, "show_viewport", emboss=False, icon_only=True,
124                             icon='RESTRICT_VIEW_OFF' if md.show_viewport else 'RESTRICT_VIEW_ON')
125
126         elif self.layout_type == 'GRID':
127             layout.alignment = 'CENTER'
128             layout.label(text="", icon_value=icon)
129
130
131 class PARTICLE_PT_context_particles(ParticleButtonsPanel, Panel):
132     bl_label = ""
133     bl_options = {'HIDE_HEADER'}
134     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
135
136     @classmethod
137     def poll(cls, context):
138         engine = context.engine
139         return (context.particle_system or context.object or context.space_data.pin_id) and (engine in cls.COMPAT_ENGINES)
140
141     def draw(self, context):
142         layout = self.layout
143
144         ob = context.object
145         psys = context.particle_system
146         part = 0
147
148         if ob:
149             row = layout.row()
150
151             row.template_list("PARTICLE_UL_particle_systems", "particle_systems", ob, "particle_systems",
152                               ob.particle_systems, "active_index", rows=1)
153
154             col = row.column(align=True)
155             col.operator("object.particle_system_add", icon='ZOOMIN', text="")
156             col.operator("object.particle_system_remove", icon='ZOOMOUT', text="")
157             col.menu("PARTICLE_MT_specials", icon='DOWNARROW_HLT', text="")
158
159         if psys is None:
160             part = particle_get_settings(context)
161
162             layout.operator("object.particle_system_add", icon='ZOOMIN', text="New")
163
164             if part is None:
165                 return
166
167             layout.template_ID(context.space_data, "pin_id")
168
169             if part.is_fluid:
170                 layout.label(text="Settings used for fluid")
171                 return
172
173             layout.prop(part, "type", text="Type")
174
175         elif not psys.settings:
176             split = layout.split(percentage=0.32)
177
178             col = split.column()
179             col.label(text="Settings:")
180
181             col = split.column()
182             col.template_ID(psys, "settings", new="particle.new")
183         else:
184             part = psys.settings
185
186             split = layout.split(percentage=0.32)
187             col = split.column()
188             if part.is_fluid is False:
189                 col.label(text="Settings:")
190                 col.label(text="Type:")
191
192             col = split.column()
193             if part.is_fluid is False:
194                 row = col.row()
195                 row.enabled = particle_panel_enabled(context, psys)
196                 row.template_ID(psys, "settings", new="particle.new")
197
198             if part.is_fluid:
199                 layout.label(text=iface_("%d fluid particles for this frame") % part.count, translate=False)
200                 return
201
202             row = col.row()
203             row.enabled = particle_panel_enabled(context, psys)
204             row.prop(part, "type", text="")
205             row.prop(psys, "seed")
206
207         if part:
208             split = layout.split(percentage=0.65)
209             if part.type == 'HAIR':
210                 if psys is not None and psys.is_edited:
211                     split.operator("particle.edited_clear", text="Free Edit")
212                 else:
213                     row = split.row()
214                     row.enabled = particle_panel_enabled(context, psys)
215                     row.prop(part, "regrow_hair")
216                     row.prop(part, "use_advanced_hair")
217                 row = split.row()
218                 row.enabled = particle_panel_enabled(context, psys)
219                 row.prop(part, "hair_step")
220                 if psys is not None and psys.is_edited:
221                     if psys.is_global_hair:
222                         row = layout.row(align=True)
223                         row.operator("particle.connect_hair").all = False
224                         row.operator("particle.connect_hair", text="Connect All").all = True
225                     else:
226                         row = layout.row(align=True)
227                         row.operator("particle.disconnect_hair").all = False
228                         row.operator("particle.disconnect_hair", text="Disconnect All").all = True
229             elif psys is not None and part.type == 'REACTOR':
230                 split.enabled = particle_panel_enabled(context, psys)
231                 split.prop(psys, "reactor_target_object")
232                 split.prop(psys, "reactor_target_particle_system", text="Particle System")
233
234
235 class PARTICLE_PT_emission(ParticleButtonsPanel, Panel):
236     bl_label = "Emission"
237     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
238
239     @classmethod
240     def poll(cls, context):
241         psys = context.particle_system
242         settings = particle_get_settings(context)
243
244         if settings is None:
245             return False
246         if settings.is_fluid:
247             return False
248         if particle_panel_poll(PARTICLE_PT_emission, context):
249             return psys is None or not context.particle_system.point_cache.use_external
250         return False
251
252     def draw(self, context):
253         layout = self.layout
254
255         psys = context.particle_system
256         part = particle_get_settings(context)
257
258         layout.use_property_split = True
259
260         layout.enabled = particle_panel_enabled(context, psys) and (psys is None or not psys.has_multiple_caches)
261
262         col = layout.column()
263         col.active = part.emit_from == 'VERT' or part.distribution != 'GRID'
264         col.prop(part, "count")
265
266         if part.type == 'HAIR':
267             col.prop(part, "hair_length")
268             if not part.use_advanced_hair:
269                 row = layout.row()
270                 col.prop(part, "use_modifier_stack")
271                 return
272
273         if part.type != 'HAIR':
274
275             col = layout.column()
276
277             sub = col.column(align=True)
278             sub.prop(part, "frame_start", text="Frame Start")
279             sub.prop(part, "frame_end", text="End")
280
281             col.prop(part, "lifetime")
282             col.prop(part, "lifetime_random", slider=True, text="Lifetime Randomness")
283
284
285 class PARTICLE_PT_emission_source(ParticleButtonsPanel, Panel):
286     bl_label = "Source"
287     bl_parent_id = "PARTICLE_PT_emission"
288     bl_options = {'DEFAULT_CLOSED'}
289     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
290
291     def draw(self, context):
292         layout = self.layout
293
294         part = particle_get_settings(context)
295
296         layout.use_property_split = True
297
298         col = layout.column()
299         col.prop(part, "emit_from")
300         col.prop(part, "use_modifier_stack")
301         if part.emit_from == 'FACE' or part.emit_from == 'VOLUME':
302             col.prop(part, "distribution")
303
304         if part.emit_from == 'VERT':
305             col.prop(part, "use_emit_random", text="Random Order")
306         elif part.distribution == 'GRID':
307             col.label(text="Grid")
308             col.prop(part, "invert_grid")
309             col.prop(part, "hexagonal_grid")
310         else:
311             col.prop(part, "use_emit_random")
312             col.prop(part, "use_even_distribution")
313
314         if part.emit_from == 'FACE' or part.emit_from == 'VOLUME':
315
316             if part.distribution == 'JIT':
317                 col.prop(part, "userjit", text="Particles/Face")
318                 col.prop(part, "jitter_factor", text="Jittering Amount", slider=True)
319             elif part.distribution == 'GRID':
320                 col.prop(part, "grid_resolution")
321                 col.prop(part, "grid_random", text="Random", slider=True)
322
323
324 class PARTICLE_PT_hair_dynamics(ParticleButtonsPanel, Panel):
325     bl_label = "Hair Dynamics"
326     bl_options = {'DEFAULT_CLOSED'}
327     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
328
329     @classmethod
330     def poll(cls, context):
331         psys = context.particle_system
332         engine = context.engine
333         if psys is None:
334             return False
335         if psys.settings is None:
336             return False
337         return psys.settings.type == 'HAIR' and (engine in cls.COMPAT_ENGINES)
338
339     def draw_header(self, context):
340         psys = context.particle_system
341         self.layout.prop(psys, "use_hair_dynamics", text="")
342
343     def draw(self, context):
344         layout = self.layout
345
346         psys = context.particle_system
347
348         if not psys.cloth:
349             layout.label(text="Hair dynamics disabled")
350             return
351
352         cloth_md = psys.cloth
353         cloth = cloth_md.settings
354         result = cloth_md.solver_result
355
356         layout.enabled = psys.use_hair_dynamics and psys.point_cache.is_baked is False
357
358         row = layout.row(align=True)
359         row.menu("PARTICLE_MT_hair_dynamics_presets", text=bpy.types.PARTICLE_MT_hair_dynamics_presets.bl_label)
360         row.operator("particle.hair_dynamics_preset_add", text="", icon='ZOOMIN')
361         row.operator("particle.hair_dynamics_preset_add", text="", icon='ZOOMOUT').remove_active = True
362
363         layout.use_property_split = True
364
365         layout.separator()
366
367         col = layout.column()
368         col.prop(cloth, "quality", text="Quality Steps", slider=True)
369         col.prop(psys.settings, "show_hair_grid", text="Display Hair Grid")
370
371         layout.separator()
372
373         col = layout.column()
374         col.prop(cloth, "pin_stiffness", text="Pin Goal Strength")
375
376         layout.separator()
377
378         if result:
379             box = layout.box()
380
381             if not result.status:
382                 label = " "
383                 icon = 'NONE'
384             elif result.status == {'SUCCESS'}:
385                 label = "Success"
386                 icon = 'NONE'
387             elif result.status - {'SUCCESS'} == {'NO_CONVERGENCE'}:
388                 label = "No Convergence"
389                 icon = 'ERROR'
390             else:
391                 label = "ERROR"
392                 icon = 'ERROR'
393             box.label(label, icon=icon)
394             box.label("Iterations: %d .. %d (avg. %d)" %
395                       (result.min_iterations, result.max_iterations, result.avg_iterations))
396             box.label("Error: %.5f .. %.5f (avg. %.5f)" % (result.min_error, result.max_error, result.avg_error))
397
398
399 class PARTICLE_PT_hair_dynamics_structure(ParticleButtonsPanel, Panel):
400     bl_label = "Structure"
401     bl_parent_id = "PARTICLE_PT_hair_dynamics"
402     bl_options = {'DEFAULT_CLOSED'}
403     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
404
405     @classmethod
406     def poll(cls, context):
407         return context.particle_system.cloth is not None
408
409     def draw(self, context):
410         layout = self.layout
411
412         psys = context.particle_system
413         cloth_md = psys.cloth
414         cloth = cloth_md.settings
415         result = cloth_md.solver_result
416
417         layout.enabled = psys.use_hair_dynamics and psys.point_cache.is_baked is False
418
419         layout.use_property_split = True
420
421         col = layout.column()
422         col.prop(cloth, "mass")
423         sub = col.column(align=True)
424         sub.prop(cloth, "bending_stiffness", text="Stiffness")
425         sub.prop(psys.settings, "bending_random", text="Random")
426         col.prop(cloth, "bending_damping", text="Damping")
427         # XXX has no noticeable effect with stiff hair structure springs
428         #col.prop(cloth, "spring_damping", text="Damping")
429
430
431 class PARTICLE_PT_hair_dynamics_volume(ParticleButtonsPanel, Panel):
432     bl_label = "Volume"
433     bl_parent_id = "PARTICLE_PT_hair_dynamics"
434     bl_options = {'DEFAULT_CLOSED'}
435     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
436
437     @classmethod
438     def poll(cls, context):
439         return context.particle_system.cloth is not None
440
441     def draw(self, context):
442         layout = self.layout
443
444         psys = context.particle_system
445         cloth_md = psys.cloth
446         cloth = cloth_md.settings
447         result = cloth_md.solver_result
448
449         layout.enabled = psys.use_hair_dynamics and psys.point_cache.is_baked is False
450
451         layout.use_property_split = True
452
453         col = layout.column()
454         col.prop(cloth, "air_damping", text="Air Drag")
455         col.prop(cloth, "internal_friction", slider=True)
456         col.prop(cloth, "voxel_cell_size")
457
458         col.separator()
459
460         col.prop(cloth, "density_target", text="Density Target")
461         col.prop(cloth, "density_strength", slider=True, text="Density Strength")
462
463
464 class PARTICLE_PT_cache(ParticleButtonsPanel, Panel):
465     bl_label = "Cache"
466     bl_options = {'DEFAULT_CLOSED'}
467     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
468
469     @classmethod
470     def poll(cls, context):
471         psys = context.particle_system
472         engine = context.engine
473         if psys is None:
474             return False
475         if psys.settings is None:
476             return False
477         if psys.settings.is_fluid:
478             return False
479         phystype = psys.settings.physics_type
480         if phystype == 'NO' or phystype == 'KEYED':
481             return False
482         return (
483             (psys.settings.type in {'EMITTER', 'REACTOR'} or
484              (psys.settings.type == 'HAIR' and
485               (psys.use_hair_dynamics or psys.point_cache.is_baked))) and
486             engine in cls.COMPAT_ENGINES
487         )
488
489     def draw(self, context):
490         psys = context.particle_system
491
492         point_cache_ui(self, context, psys.point_cache, True, 'HAIR' if (psys.settings.type == 'HAIR') else 'PSYS')
493
494
495 class PARTICLE_PT_velocity(ParticleButtonsPanel, Panel):
496     bl_label = "Velocity"
497     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
498
499     @classmethod
500     def poll(cls, context):
501         if particle_panel_poll(PARTICLE_PT_velocity, context):
502             psys = context.particle_system
503             settings = particle_get_settings(context)
504
505             if settings.type == 'HAIR' and not settings.use_advanced_hair:
506                 return False
507             return settings.physics_type != 'BOIDS' and (psys is None or not psys.point_cache.use_external)
508         else:
509             return False
510
511     def draw(self, context):
512         layout = self.layout
513
514         psys = context.particle_system
515         part = particle_get_settings(context)
516
517         layout.enabled = particle_panel_enabled(context, psys)
518         layout.use_property_split = True
519
520         col = layout.column()
521         col.prop(part, "normal_factor")
522         sub = col.column(align=True)
523         sub.prop(part, "tangent_factor", text="Tangent")
524         sub.prop(part, "tangent_phase", slider=True, text="Tangent Phase")
525
526         col.separator()
527
528         col.prop(part, "object_align_factor")
529
530         col.separator()
531
532         if part.emit_from == 'PARTICLE':
533             col.prop(part, "particle_factor")
534         else:
535             col.prop(part, "object_factor", slider=True)
536         col.prop(part, "factor_random", text="Randomize")
537
538         # if part.type=='REACTOR':
539         #     sub.prop(part, "reactor_factor")
540         #     sub.prop(part, "reaction_shape", slider=True)
541
542
543 class PARTICLE_PT_rotation(ParticleButtonsPanel, Panel):
544     bl_label = "Rotation"
545     bl_options = {'DEFAULT_CLOSED'}
546     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
547
548     @classmethod
549     def poll(cls, context):
550         if particle_panel_poll(PARTICLE_PT_rotation, context):
551             psys = context.particle_system
552             settings = particle_get_settings(context)
553
554             if settings.type == 'HAIR' and not settings.use_advanced_hair:
555                 return False
556             return settings.physics_type != 'BOIDS' and (psys is None or not psys.point_cache.use_external)
557         else:
558             return False
559
560     def draw_header(self, context):
561         psys = context.particle_system
562         if psys:
563             part = psys.settings
564         else:
565             part = context.space_data.pin_id
566
567         self.layout.prop(part, "use_rotations", text="")
568
569     def draw(self, context):
570         layout = self.layout
571
572         psys = context.particle_system
573         if psys:
574             part = psys.settings
575         else:
576             part = context.space_data.pin_id
577
578         layout.enabled = particle_panel_enabled(context, psys) and part.use_rotations
579         layout.use_property_split = True
580
581         col = layout.column()
582
583         col.prop(part, "rotation_mode")
584         col.prop(part, "rotation_factor_random", slider=True, text="Randomize")
585
586         col.separator()
587
588         col.prop(part, "phase_factor", slider=True)
589         col.prop(part, "phase_factor_random", text="Randomize Phase ", slider=True)
590
591         if part.type != 'HAIR':
592             col.prop(part, "use_dynamic_rotation")
593
594
595 class PARTICLE_PT_rotation_angular_velocity(ParticleButtonsPanel, Panel):
596     bl_label = "Angular Velocity"
597     bl_parent_id = "PARTICLE_PT_rotation"
598     bl_options = {'DEFAULT_CLOSED'}
599     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
600
601     def draw(self, context):
602         layout = self.layout
603
604         psys = context.particle_system
605         if psys:
606             part = psys.settings
607         else:
608             part = context.space_data.pin_id
609
610         layout.enabled = particle_panel_enabled(context, psys) and part.use_rotations
611         layout.use_property_split = True
612
613         col = layout.column()
614
615         col.prop(part, "angular_velocity_mode", text="Axis")
616         sub = col.column(align=True)
617         sub.active = part.angular_velocity_mode != 'NONE'
618         sub.prop(part, "angular_velocity_factor", text="Amount")
619
620
621 class PARTICLE_PT_physics(ParticleButtonsPanel, Panel):
622     bl_label = "Physics"
623     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
624
625     @classmethod
626     def poll(cls, context):
627         if particle_panel_poll(PARTICLE_PT_physics, context):
628             psys = context.particle_system
629             settings = particle_get_settings(context)
630
631             if settings.type == 'HAIR' and not settings.use_advanced_hair:
632                 return False
633             return psys is None or not psys.point_cache.use_external
634         else:
635             return False
636
637     def draw(self, context):
638         layout = self.layout
639         layout.use_property_split = True
640
641         psys = context.particle_system
642         part = particle_get_settings(context)
643
644         layout.enabled = particle_panel_enabled(context, psys)
645
646         layout.prop(part, "physics_type")
647
648         col = layout.column()
649         if part.physics_type != 'NO':
650             col = col.column()
651             col.prop(part, "mass")
652             col.prop(part, "use_multiply_size_mass", text="Multiply mass with size")
653
654         if part.physics_type == 'FLUID':
655             fluid = part.fluid
656
657             col.label(text="Fluid")
658             col.prop(fluid, "solver")
659             col.prop(fluid, "stiffness", text="Stiffness")
660             col.prop(fluid, "linear_viscosity", text="Viscosity")
661             col.prop(fluid, "buoyancy", text="Buoyancy", slider=True)
662
663             col.label(text="Advanced")
664
665             if fluid.solver == 'DDR':
666                 sub = col.column()
667                 sub.prop(fluid, "repulsion", slider=fluid.factor_repulsion)
668                 sub.prop(fluid, "factor_repulsion")
669
670                 sub.prop(fluid, "stiff_viscosity", slider=fluid.factor_stiff_viscosity)
671                 sub.prop(fluid, "factor_stiff_viscosity")
672
673             sub = col.column()
674             sub.prop(fluid, "fluid_radius", slider=fluid.factor_radius)
675             sub.prop(fluid, "factor_radius")
676
677             sub.prop(fluid, "rest_density", slider=fluid.use_factor_density)
678             sub.prop(fluid, "use_factor_density")
679
680             if fluid.solver == 'CLASSICAL':
681                 # With the classical solver, it is possible to calculate the
682                 # spacing between particles when the fluid is at rest. This
683                 # makes it easier to set stable initial conditions.
684                 particle_volume = part.mass / fluid.rest_density
685                 spacing = pow(particle_volume, 1.0 / 3.0)
686
687                 sub.label(text="Spacing: %g" % spacing)
688
689             elif fluid.solver == 'DDR':
690
691                 col.label(text="Springs")
692                 col.prop(fluid, "spring_force", text="Force")
693                 col.prop(fluid, "use_viscoelastic_springs")
694
695                 sub = col.column()
696                 sub.active = fluid.use_viscoelastic_springs
697                 sub.prop(fluid, "yield_ratio", slider=True)
698                 sub.prop(fluid, "plasticity", slider=True)
699
700                 col.label(text="Advanced")
701                 sub = col.column()
702                 sub.prop(fluid, "rest_length", slider=fluid.factor_rest_length)
703                 sub.prop(fluid, "factor_rest_length", text="")
704
705                 sub = col.column()
706                 sub.active = fluid.use_viscoelastic_springs
707                 sub.prop(fluid, "use_initial_rest_length")
708                 sub.prop(fluid, "spring_frames", text="Frames")
709
710         elif part.physics_type == 'KEYED':
711
712             sub = col.column()
713             sub.active = not psys.use_keyed_timing
714             sub.prop(part, "keyed_loops", text="Loops")
715             if psys:
716                 col.prop(psys, "use_keyed_timing", text="Use Timing")
717
718             col.label(text="Keys")
719
720         elif part.physics_type == 'BOIDS':
721             boids = part.boids
722
723             row = layout.row()
724             row.prop(boids, "use_flight")
725             row.prop(boids, "use_land")
726             row.prop(boids, "use_climb")
727
728             split = layout.split()
729
730             col = split.column(align=True)
731             col.active = boids.use_flight
732             col.prop(boids, "air_speed_max")
733             col.prop(boids, "air_speed_min", slider=True)
734             col.prop(boids, "air_acc_max", slider=True)
735             col.prop(boids, "air_ave_max", slider=True)
736             col.prop(boids, "air_personal_space")
737             row = col.row(align=True)
738             row.active = (boids.use_land or boids.use_climb) and boids.use_flight
739             row.prop(boids, "land_smooth")
740
741             col = split.column(align=True)
742             col.active = boids.use_land or boids.use_climb
743             col.prop(boids, "land_speed_max")
744             col.prop(boids, "land_jump_speed")
745             col.prop(boids, "land_acc_max", slider=True)
746             col.prop(boids, "land_ave_max", slider=True)
747             col.prop(boids, "land_personal_space")
748             col.prop(boids, "land_stick_force")
749
750             layout.prop(part, "collision_group")
751
752             split = layout.split()
753
754             col = split.column(align=True)
755             col.label(text="Battle:")
756             col.prop(boids, "health")
757             col.prop(boids, "strength")
758             col.prop(boids, "aggression")
759             col.prop(boids, "accuracy")
760             col.prop(boids, "range")
761
762             col = split.column()
763             col.label(text="Misc:")
764             col.prop(boids, "bank", slider=True)
765             col.prop(boids, "pitch", slider=True)
766             col.prop(boids, "height", slider=True)
767
768         if psys and part.physics_type in {'KEYED', 'BOIDS', 'FLUID'}:
769             if part.physics_type == 'BOIDS':
770                 layout.label(text="Relations:")
771             elif part.physics_type == 'FLUID':
772                 layout.label(text="Fluid Interaction:")
773
774             row = layout.row()
775             row.template_list("UI_UL_list", "particle_targets", psys, "targets",
776                               psys, "active_particle_target_index", rows=4)
777
778             col = row.column()
779             sub = col.row()
780             subsub = sub.column(align=True)
781             subsub.operator("particle.new_target", icon='ZOOMIN', text="")
782             subsub.operator("particle.target_remove", icon='ZOOMOUT', text="")
783             sub = col.row()
784             subsub = sub.column(align=True)
785             subsub.operator("particle.target_move_up", icon='TRIA_UP', text="")
786             subsub.operator("particle.target_move_down", icon='TRIA_DOWN', text="")
787
788             key = psys.active_particle_target
789             if key:
790                 row = layout.row()
791                 if part.physics_type == 'KEYED':
792                     col = row.column()
793                     # doesn't work yet
794                     #col.alert = key.valid
795                     col.prop(key, "object", text="")
796                     col.prop(key, "system", text="System")
797                     col = row.column()
798                     col.active = psys.use_keyed_timing
799                     col.prop(key, "time")
800                     col.prop(key, "duration")
801                 elif part.physics_type == 'BOIDS':
802                     sub = row.row()
803                     # doesn't work yet
804                     #sub.alert = key.valid
805                     sub.prop(key, "object", text="")
806                     sub.prop(key, "system", text="System")
807
808                     layout.row().prop(key, "alliance", expand=True)
809                 elif part.physics_type == 'FLUID':
810                     sub = row.row()
811                     # doesn't work yet
812                     #sub.alert = key.valid
813                     sub.prop(key, "object", text="")
814                     sub.prop(key, "system", text="System")
815
816
817 class PARTICLE_PT_physics_deflection(ParticleButtonsPanel, Panel):
818     bl_label = "Deflection"
819     bl_parent_id = "PARTICLE_PT_physics"
820     bl_options = {'DEFAULT_CLOSED'}
821     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
822
823     @classmethod
824     def poll(cls, context):
825         part = particle_get_settings(context)
826         return part.physics_type in {'NEWTON', 'FLUID'}
827
828     def draw(self, context):
829         layout = self.layout
830         layout.use_property_split = True
831
832         psys = context.particle_system
833         part = particle_get_settings(context)
834
835         layout.enabled = particle_panel_enabled(context, psys)
836
837         col = layout.column()
838         col.prop(part, "use_size_deflect")
839         col.prop(part, "use_die_on_collision")
840
841         col.prop(part, "collision_group")
842
843
844 class PARTICLE_PT_physics_forces(ParticleButtonsPanel, Panel):
845     bl_label = "Forces"
846     bl_parent_id = "PARTICLE_PT_physics"
847     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
848
849     @classmethod
850     def poll(cls, context):
851         part = particle_get_settings(context)
852         return part.physics_type == 'NEWTON'
853
854     def draw(self, context):
855         layout = self.layout
856         layout.use_property_split = True
857
858         psys = context.particle_system
859         part = particle_get_settings(context)
860
861         layout.enabled = particle_panel_enabled(context, psys)
862
863         col = layout.column()
864
865         col.prop(part, "brownian_factor")
866         col.prop(part, "drag_factor", slider=True)
867         col.prop(part, "damping", slider=True)
868
869
870 class PARTICLE_PT_physics_integration(ParticleButtonsPanel, Panel):
871     bl_label = "Integration"
872     bl_options = {'DEFAULT_CLOSED'}
873     bl_parent_id = "PARTICLE_PT_physics"
874     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
875
876     @classmethod
877     def poll(cls, context):
878         part = particle_get_settings(context)
879         return part.physics_type == 'NEWTON'
880
881     def draw(self, context):
882         layout = self.layout
883         layout.use_property_split = True
884
885         psys = context.particle_system
886         part = particle_get_settings(context)
887
888         layout.enabled = particle_panel_enabled(context, psys)
889
890         col = layout.column()
891
892         col.prop(part, "integrator")
893         col.prop(part, "timestep")
894         sub = col.row()
895         sub.prop(part, "subframes")
896         supports_courant = part.physics_type == 'FLUID'
897         subsub = sub.row()
898         subsub.enabled = supports_courant
899         subsub.prop(part, "use_adaptive_subframes", text="")
900         if supports_courant and part.use_adaptive_subframes:
901             col.prop(part, "courant_target", text="Threshold")
902
903
904 class PARTICLE_PT_boidbrain(ParticleButtonsPanel, Panel):
905     bl_label = "Boid Brain"
906     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
907
908     @classmethod
909     def poll(cls, context):
910         psys = context.particle_system
911         settings = particle_get_settings(context)
912         engine = context.engine
913
914         if settings is None:
915             return False
916         if psys is not None and psys.point_cache.use_external:
917             return False
918         return settings.physics_type == 'BOIDS' and engine in cls.COMPAT_ENGINES
919
920     def draw(self, context):
921         layout = self.layout
922
923         boids = particle_get_settings(context).boids
924
925         layout.enabled = particle_panel_enabled(context, context.particle_system)
926
927         # Currently boids can only use the first state so these are commented out for now.
928         #row = layout.row()
929         # row.template_list("UI_UL_list", "particle_boids", boids, "states",
930         #                  boids, "active_boid_state_index", compact="True")
931         #col = row.row()
932         #sub = col.row(align=True)
933         #sub.operator("boid.state_add", icon='ZOOMIN', text="")
934         #sub.operator("boid.state_del", icon='ZOOMOUT', text="")
935         #sub = row.row(align=True)
936         #sub.operator("boid.state_move_up", icon='TRIA_UP', text="")
937         #sub.operator("boid.state_move_down", icon='TRIA_DOWN', text="")
938
939         state = boids.active_boid_state
940
941         #layout.prop(state, "name", text="State name")
942
943         row = layout.row()
944         row.prop(state, "ruleset_type")
945         if state.ruleset_type == 'FUZZY':
946             row.prop(state, "rule_fuzzy", slider=True)
947         else:
948             row.label(text="")
949
950         row = layout.row()
951         row.template_list("UI_UL_list", "particle_boids_rules", state,
952                           "rules", state, "active_boid_rule_index", rows=4)
953
954         col = row.column()
955         sub = col.row()
956         subsub = sub.column(align=True)
957         subsub.operator_menu_enum("boid.rule_add", "type", icon='ZOOMIN', text="")
958         subsub.operator("boid.rule_del", icon='ZOOMOUT', text="")
959         sub = col.row()
960         subsub = sub.column(align=True)
961         subsub.operator("boid.rule_move_up", icon='TRIA_UP', text="")
962         subsub.operator("boid.rule_move_down", icon='TRIA_DOWN', text="")
963
964         rule = state.active_boid_rule
965
966         if rule:
967             row = layout.row()
968             row.prop(rule, "name", text="")
969             # somebody make nice icons for boids here please! -jahka
970             row.prop(rule, "use_in_air", icon='TRIA_UP', text="")
971             row.prop(rule, "use_on_land", icon='TRIA_DOWN', text="")
972
973             row = layout.row()
974
975             if rule.type == 'GOAL':
976                 row.prop(rule, "object")
977                 row = layout.row()
978                 row.prop(rule, "use_predict")
979             elif rule.type == 'AVOID':
980                 row.prop(rule, "object")
981                 row = layout.row()
982                 row.prop(rule, "use_predict")
983                 row.prop(rule, "fear_factor")
984             elif rule.type == 'FOLLOW_PATH':
985                 row.label(text="Not yet functional")
986             elif rule.type == 'AVOID_COLLISION':
987                 row.prop(rule, "use_avoid")
988                 row.prop(rule, "use_avoid_collision")
989                 row.prop(rule, "look_ahead")
990             elif rule.type == 'FOLLOW_LEADER':
991                 row.prop(rule, "object", text="")
992                 row.prop(rule, "distance")
993                 row = layout.row()
994                 row.prop(rule, "use_line")
995                 sub = row.row()
996                 sub.active = rule.line
997                 sub.prop(rule, "queue_count")
998             elif rule.type == 'AVERAGE_SPEED':
999                 row.prop(rule, "speed", slider=True)
1000                 row.prop(rule, "wander", slider=True)
1001                 row.prop(rule, "level", slider=True)
1002             elif rule.type == 'FIGHT':
1003                 row.prop(rule, "distance")
1004                 row.prop(rule, "flee_distance")
1005
1006
1007 class PARTICLE_PT_render(ParticleButtonsPanel, Panel):
1008     bl_label = "Render"
1009     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1010
1011     @classmethod
1012     def poll(cls, context):
1013         settings = particle_get_settings(context)
1014         engine = context.engine
1015         if settings is None:
1016             return False
1017
1018         return engine in cls.COMPAT_ENGINES
1019
1020     def draw(self, context):
1021         layout = self.layout
1022         layout.use_property_split = True
1023
1024         psys = context.particle_system
1025         part = particle_get_settings(context)
1026
1027         layout.prop(part, "render_type", text="Render As")
1028
1029         if part.type == 'EMITTER' or \
1030            (part.render_type in {'OBJECT', 'COLLECTION'} and part.type == 'HAIR'):
1031             if part.render_type not in {'NONE'}:
1032
1033                 col = layout.column(align=True)
1034                 col.prop(part, "particle_size", text="Scale")
1035                 col.prop(part, "size_random", slider=True, text="Scale Randomness")
1036
1037         if psys:
1038             col = layout.column()
1039             if part.render_type not in {'OBJECT', 'COLLECTION', 'NONE'}:
1040                 # col.enabled = False
1041                 col.prop(part, "material_slot", text="Material")
1042                 col.prop(psys, "parent", text="Coordinate System")
1043
1044
1045 class PARTICLE_PT_render_extra(ParticleButtonsPanel, Panel):
1046     bl_label = "Extra"
1047     bl_parent_id = "PARTICLE_PT_render"
1048     bl_options = {'DEFAULT_CLOSED'}
1049     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1050
1051     @classmethod
1052     def poll(cls, context):
1053         part = particle_get_settings(context)
1054         return part.render_type != 'NONE'
1055
1056     def draw(self, context):
1057         layout = self.layout
1058         layout.use_property_split = True
1059
1060         psys = context.particle_system
1061         ob = context.object
1062         part = particle_get_settings(context)
1063
1064         col = layout.column()
1065
1066         col = layout.column()
1067         col.prop(part, "use_parent_particles", text="Parent Particles")
1068         col.prop(part, "show_unborn", text="Unborn")
1069         col.prop(part, "use_dead", text="Dead")
1070
1071
1072 class PARTICLE_PT_render_line(ParticleButtonsPanel, Panel):
1073     bl_label = "Line"
1074     bl_parent_id = "PARTICLE_PT_render"
1075     bl_options = {'DEFAULT_CLOSED'}
1076     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1077
1078     @classmethod
1079     def poll(cls, context):
1080         part = particle_get_settings(context)
1081         return part.render_type == 'LINE'
1082
1083     def draw(self, context):
1084         layout = self.layout
1085         layout.use_property_split = True
1086
1087         psys = context.particle_system
1088         ob = context.object
1089         part = particle_get_settings(context)
1090
1091         col = layout.column()
1092
1093         col.separator()
1094         sub = col.column(align=True)
1095         sub.prop(part, "line_length_tail", text="Length Tail")
1096         sub.prop(part, "line_length_head", text="Head")
1097         col.prop(part, "use_velocity_length", text="Velocity Length")
1098
1099
1100 class PARTICLE_PT_render_path(ParticleButtonsPanel, Panel):
1101     bl_label = "Path"
1102     bl_parent_id = "PARTICLE_PT_render"
1103     bl_options = {'DEFAULT_CLOSED'}
1104     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1105
1106     @classmethod
1107     def poll(cls, context):
1108         part = particle_get_settings(context)
1109         return part.render_type == 'PATH'
1110
1111     def draw(self, context):
1112         layout = self.layout
1113         layout.use_property_split = True
1114
1115         psys = context.particle_system
1116         ob = context.object
1117         part = particle_get_settings(context)
1118
1119         col = layout.column()
1120
1121         col.prop(part, "use_strand_primitive")
1122         sub = col.column()
1123         sub.active = (part.use_strand_primitive is False)
1124         sub.prop(part, "use_render_adaptive")
1125         sub = col.column()
1126         sub.active = part.use_render_adaptive or part.use_strand_primitive is True
1127         sub.prop(part, "adaptive_angle")
1128         sub = col.column()
1129         sub.active = (part.use_render_adaptive is True and part.use_strand_primitive is False)
1130         sub.prop(part, "adaptive_pixel")
1131         col.prop(part, "use_hair_bspline")
1132         col.prop(part, "render_step", text="Steps")
1133
1134
1135 class PARTICLE_PT_render_path_timing(ParticleButtonsPanel, Panel):
1136     bl_label = "Timing"
1137     bl_parent_id = "PARTICLE_PT_render"
1138     bl_options = {'DEFAULT_CLOSED'}
1139     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1140
1141     @classmethod
1142     def poll(cls, context):
1143         part = particle_get_settings(context)
1144         return part.render_type == 'PATH'
1145
1146     def draw(self, context):
1147         layout = self.layout
1148         layout.use_property_split = True
1149
1150         psys = context.particle_system
1151         ob = context.object
1152         part = particle_get_settings(context)
1153
1154         col = layout.column()
1155
1156         col.prop(part, "use_absolute_path_time")
1157
1158         if part.type == 'HAIR' or psys.point_cache.is_baked:
1159             col.prop(part, "path_start", text="Start", slider=not part.use_absolute_path_time)
1160         else:
1161             col.prop(part, "trail_count")
1162
1163         col.prop(part, "path_end", text="End", slider=not part.use_absolute_path_time)
1164         col.prop(part, "length_random", text="Random", slider=True)
1165
1166
1167 class PARTICLE_PT_render_object(ParticleButtonsPanel, Panel):
1168     bl_label = "Object"
1169     bl_parent_id = "PARTICLE_PT_render"
1170     bl_options = {'DEFAULT_CLOSED'}
1171     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1172
1173     @classmethod
1174     def poll(cls, context):
1175         part = particle_get_settings(context)
1176         return part.render_type == 'OBJECT'
1177
1178     def draw(self, context):
1179         layout = self.layout
1180         layout.use_property_split = True
1181
1182         psys = context.particle_system
1183         ob = context.object
1184         part = particle_get_settings(context)
1185
1186         col = layout.column()
1187
1188         col.prop(part, "dupli_object", text="Instance Object")
1189         sub = col.column()
1190         sub.prop(part, "use_global_dupli", text="Global Coordinates")
1191         sub.prop(part, "use_rotation_dupli", text="Object Rotation")
1192         sub.prop(part, "use_scale_dupli", text="Object Scale")
1193
1194
1195 class PARTICLE_PT_render_collection(ParticleButtonsPanel, Panel):
1196     bl_label = "Collection"
1197     bl_parent_id = "PARTICLE_PT_render"
1198     bl_options = {'DEFAULT_CLOSED'}
1199     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1200
1201     @classmethod
1202     def poll(cls, context):
1203         part = particle_get_settings(context)
1204         return part.render_type == 'COLLECTION'
1205
1206     def draw(self, context):
1207         layout = self.layout
1208         layout.use_property_split = True
1209
1210         psys = context.particle_system
1211         ob = context.object
1212         part = particle_get_settings(context)
1213
1214         col = layout.column()
1215
1216         col.prop(part, "dupli_group")
1217
1218         col.prop(part, "use_whole_group")
1219         sub = col.column()
1220         sub.active = (part.use_whole_group is False)
1221         sub.prop(part, "use_group_pick_random")
1222         sub.prop(part, "use_global_dupli", text="Global Coordinates")
1223         sub.prop(part, "use_rotation_dupli", text="Object Rotation")
1224         sub.prop(part, "use_scale_dupli", text="Object Scale")
1225
1226
1227 class PARTICLE_PT_render_collection_use_count(ParticleButtonsPanel, Panel):
1228     bl_label = "Use Count"
1229     bl_parent_id = "PARTICLE_PT_render_collection"
1230     bl_options = {'DEFAULT_CLOSED'}
1231     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1232
1233     @classmethod
1234     def poll(cls, context):
1235         part = particle_get_settings(context)
1236         return part.render_type == 'COLLECTION'
1237
1238     def draw_header(self, context):
1239         layout = self.layout
1240         part = particle_get_settings(context)
1241
1242         layout.active = not part.use_whole_group
1243
1244         layout.prop(part, "use_group_count", text="")
1245
1246     def draw(self, context):
1247         layout = self.layout
1248         layout.use_property_split = True
1249
1250         psys = context.particle_system
1251         ob = context.object
1252         part = particle_get_settings(context)
1253
1254         col = layout.column()
1255
1256         layout.active = part.use_group_count and not part.use_whole_group
1257
1258         row = layout.row()
1259         row.template_list("UI_UL_list", "particle_dupli_weights", part, "dupli_weights",
1260                           part, "active_dupliweight_index")
1261
1262         col = row.column()
1263         sub = col.row()
1264         subsub = sub.column(align=True)
1265         subsub.operator("particle.dupliob_copy", icon='ZOOMIN', text="")
1266         subsub.operator("particle.dupliob_remove", icon='ZOOMOUT', text="")
1267         subsub.operator("particle.dupliob_move_up", icon='TRIA_UP', text="")
1268         subsub.operator("particle.dupliob_move_down", icon='TRIA_DOWN', text="")
1269
1270         weight = part.active_dupliweight
1271         if weight:
1272             row = layout.row()
1273             row.prop(weight, "count")
1274
1275
1276 class PARTICLE_PT_render_billboards_alignment(ParticleButtonsPanel, Panel):
1277     bl_label = "Billboard Alignment"
1278     bl_parent_id = "PARTICLE_PT_render"
1279     bl_options = {'DEFAULT_CLOSED'}
1280     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1281
1282     @classmethod
1283     def poll(cls, context):
1284         part = particle_get_settings(context)
1285         return part.render_type == 'BILLBOARD'
1286
1287     def draw(self, context):
1288         layout = self.layout
1289         layout.use_property_split = True
1290
1291         psys = context.particle_system
1292         ob = context.object
1293         part = particle_get_settings(context)
1294
1295         col = layout.column()
1296
1297         col.prop(part, "billboard_align", text="Align To")
1298         col.prop(part, "lock_billboard", text="Lock Axis")
1299         col.prop(part, "billboard_object")
1300
1301
1302 class PARTICLE_PT_render_billboards_tilt(ParticleButtonsPanel, Panel):
1303     bl_label = "Billboard Tilt"
1304     bl_parent_id = "PARTICLE_PT_render"
1305     bl_options = {'DEFAULT_CLOSED'}
1306     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1307
1308     @classmethod
1309     def poll(cls, context):
1310         part = particle_get_settings(context)
1311         return part.render_type == 'BILLBOARD'
1312
1313     def draw(self, context):
1314         layout = self.layout
1315         layout.use_property_split = True
1316
1317         psys = context.particle_system
1318         ob = context.object
1319         part = particle_get_settings(context)
1320
1321         col = layout.column()
1322
1323         sub = col.column(align=True)
1324         sub.prop(part, "billboard_tilt", text="Angle", slider=True)
1325         sub.prop(part, "billboard_tilt_random", text="Random", slider=True)
1326
1327         sub = col.column(align=True)
1328         sub.prop(part, "billboard_offset")
1329         col.prop(part, "billboard_size", text="Scale")
1330         if part.billboard_align == 'VEL':
1331             col = col.column(align=True)
1332             col.prop(part, "billboard_velocity_head", text="Velocity ScaleHead")
1333             col.prop(part, "billboard_velocity_tail", text="Tail")
1334
1335
1336 class PARTICLE_PT_render_billboards_uv(ParticleButtonsPanel, Panel):
1337     bl_label = "Billboard UVs"
1338     bl_parent_id = "PARTICLE_PT_render"
1339     bl_options = {'DEFAULT_CLOSED'}
1340     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1341
1342     @classmethod
1343     def poll(cls, context):
1344         part = particle_get_settings(context)
1345         return part.render_type == 'BILLBOARD'
1346
1347     def draw(self, context):
1348         layout = self.layout
1349         layout.use_property_split = True
1350
1351         psys = context.particle_system
1352         ob = context.object
1353         part = particle_get_settings(context)
1354
1355         col = layout.column()
1356
1357         if psys:
1358             col.prop_search(psys, "billboard_normal_uv", ob.data, "uv_layers")
1359             col.prop_search(psys, "billboard_time_index_uv", ob.data, "uv_layers")
1360
1361         col.prop(part, "billboard_uv_split", text="Split UVs")
1362
1363         if psys:
1364             sub = col.column()
1365             sub.active = part.billboard_uv_split > 1
1366             sub.prop_search(psys, "billboard_split_uv", ob.data, "uv_layers")
1367
1368         sub.prop(part, "billboard_animation")
1369         sub.prop(part, "billboard_offset_split")
1370
1371
1372 class PARTICLE_PT_render_trails(ParticleButtonsPanel, Panel):
1373     bl_label = "Trails"
1374     bl_parent_id = "PARTICLE_PT_render"
1375     bl_options = {'DEFAULT_CLOSED'}
1376     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1377
1378     @classmethod
1379     def poll(cls, context):
1380         part = particle_get_settings(context)
1381         return part.render_type in {'HALO', 'LINE', 'BILLBOARD'}
1382
1383     def draw(self, context):
1384         layout = self.layout
1385         layout.use_property_split = True
1386
1387         psys = context.particle_system
1388         part = particle_get_settings(context)
1389
1390         col = layout.column()
1391
1392         col.prop(part, "trail_count")
1393
1394         sub = col.column()
1395         sub.active = (part.trail_count > 1)
1396         sub.prop(part, "use_absolute_path_time", text="Length in Frames")
1397         sub.prop(part, "path_end", text="Length", slider=not part.use_absolute_path_time)
1398         sub.prop(part, "length_random", text="Random Length", slider=True)
1399
1400
1401 class PARTICLE_PT_draw(ParticleButtonsPanel, Panel):
1402     bl_label = "Viewport Display"
1403     bl_options = {'DEFAULT_CLOSED'}
1404     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1405
1406     @classmethod
1407     def poll(cls, context):
1408         settings = particle_get_settings(context)
1409         engine = context.engine
1410         if settings is None:
1411             return False
1412         return engine in cls.COMPAT_ENGINES
1413
1414     def draw(self, context):
1415         layout = self.layout
1416         layout.use_property_split = True
1417
1418         psys = context.particle_system
1419         part = particle_get_settings(context)
1420
1421         layout.prop(part, "draw_method", text="Display As")
1422
1423         if part.draw_method == 'NONE' or (part.render_type == 'NONE' and part.draw_method == 'RENDER'):
1424             return
1425
1426         path = (part.render_type == 'PATH' and part.draw_method == 'RENDER') or part.draw_method == 'PATH'
1427
1428         layout.separator()
1429
1430         col = layout.column()
1431         col.prop(part, "draw_color", text="Color")
1432         if part.draw_color in {'VELOCITY', 'ACCELERATION'}:
1433             col.prop(part, "color_maximum", text="Fade Distance")
1434
1435         col = layout.column()
1436
1437         if path:
1438             col.prop(part, "draw_step", text="Strand Steps")
1439         col.prop(part, "draw_percentage", slider=True, text="Amount")
1440         if part.draw_method != 'RENDER' or part.render_type == 'HALO':
1441             col.prop(part, "draw_size", text="Size")
1442
1443         if part.draw_percentage != 100 and psys is not None:
1444             if part.type == 'HAIR':
1445                 if psys.use_hair_dynamics and psys.point_cache.is_baked is False:
1446                     layout.row().label(text="Display percentage makes dynamics inaccurate without baking")
1447             else:
1448                 phystype = part.physics_type
1449                 if phystype != 'NO' and phystype != 'KEYED' and psys.point_cache.is_baked is False:
1450                     layout.row().label(text="Display percentage makes dynamics inaccurate without baking")
1451         else:
1452             layout.row().label(text="")
1453
1454         col = layout.column()
1455         col.prop(part, "show_guide_hairs", text="Guide Hairs")
1456         col.prop(part, "show_size")
1457         col.prop(part, "show_velocity")
1458         col.prop(part, "show_number")
1459         if part.physics_type == 'BOIDS':
1460             col.prop(part, "show_health")
1461
1462
1463 class PARTICLE_PT_children(ParticleButtonsPanel, Panel):
1464     bl_label = "Children"
1465     bl_options = {'DEFAULT_CLOSED'}
1466     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1467
1468     @classmethod
1469     def poll(cls, context):
1470         return particle_panel_poll(cls, context)
1471
1472     def draw(self, context):
1473         layout = self.layout
1474
1475         psys = context.particle_system
1476         part = particle_get_settings(context)
1477
1478         layout.row().prop(part, "child_type", expand=True)
1479
1480         layout.use_property_split = True
1481
1482         if part.child_type == 'NONE':
1483             return
1484
1485         col = layout.column()
1486
1487         sub = col.column(align=True)
1488         sub.prop(part, "child_nbr", text="Display Amount")
1489         sub.prop(part, "rendered_child_count", text="Render Amount")
1490
1491         col.separator()
1492
1493         col.prop(part, "child_length", slider=True)
1494         col.prop(part, "child_length_threshold", slider=True)
1495         if psys:
1496             col.prop(psys, "child_seed", text="Seed")
1497
1498         col.separator()
1499
1500         if part.child_type == 'INTERPOLATED':
1501             col.prop(part, "virtual_parents", slider=True)
1502             col.prop(part, "create_long_hair_children")
1503         else:
1504             col.separator()
1505             sub = col.column(align=True)
1506             sub.prop(part, "child_size", text="Size")
1507             sub.prop(part, "child_size_random", text="Randomize Size", slider=True)
1508
1509         if part.child_type == 'SIMPLE':
1510             col.separator()
1511             col.prop(part, "child_radius", text="Radius")
1512             col.prop(part, "child_roundness", text="Roundness", slider=True)
1513         elif part.virtual_parents > 0.0:
1514             sub = col.column(align=True)
1515             sub.label(text="Parting not available with virtual parents")
1516
1517
1518 class PARTICLE_PT_children_parting(ParticleButtonsPanel, Panel):
1519     bl_label = "Parting"
1520     bl_parent_id = "PARTICLE_PT_children"
1521     bl_options = {'DEFAULT_CLOSED'}
1522     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1523
1524     @classmethod
1525     def poll(cls, context):
1526         part = particle_get_settings(context)
1527         return part.child_type == 'INTERPOLATED'
1528
1529     def draw(self, context):
1530         layout = self.layout
1531
1532         psys = context.particle_system
1533         part = particle_get_settings(context)
1534
1535         layout.use_property_split = True
1536
1537         col = layout.column()
1538         col.prop(part, "child_parting_factor", text="Parting", slider=True)
1539         col.prop(part, "child_parting_min", text="Min")
1540         col.prop(part, "child_parting_max", text="Max")
1541
1542
1543 class PARTICLE_PT_children_clumping(ParticleButtonsPanel, Panel):
1544     bl_label = "Clumping"
1545     bl_parent_id = "PARTICLE_PT_children"
1546     bl_options = {'DEFAULT_CLOSED'}
1547     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1548
1549     @classmethod
1550     def poll(cls, context):
1551         part = particle_get_settings(context)
1552         return part.child_type != 'NONE'
1553
1554     def draw(self, context):
1555         layout = self.layout
1556
1557         psys = context.particle_system
1558         part = particle_get_settings(context)
1559
1560         layout.use_property_split = True
1561
1562         col = layout.column()
1563
1564         sub = col.column()
1565
1566         sub.prop(part, "use_clump_curve")
1567         if part.use_clump_curve:
1568             sub.template_curve_mapping(part, "clump_curve")
1569         else:
1570             sub.prop(part, "clump_factor", slider=True)
1571             sub.prop(part, "clump_shape", slider=True)
1572         sub = col.column(align=True)
1573         sub.prop(part, "use_clump_noise")
1574         subsub = sub.column()
1575         subsub.enabled = part.use_clump_noise
1576         subsub.prop(part, "clump_noise_size")
1577
1578         if part.child_type == 'SIMPLE':
1579             sub.prop(part, "twist")
1580             sub.prop(part, "use_twist_curve")
1581             if part.use_twist_curve:
1582                 sub.template_curve_mapping(part, "twist_curve")
1583
1584
1585 class PARTICLE_PT_children_roughness(ParticleButtonsPanel, Panel):
1586     bl_label = "Roughness"
1587     bl_parent_id = "PARTICLE_PT_children"
1588     bl_options = {'DEFAULT_CLOSED'}
1589     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1590
1591     @classmethod
1592     def poll(cls, context):
1593         part = particle_get_settings(context)
1594         return part.child_type != 'NONE'
1595
1596     def draw(self, context):
1597         layout = self.layout
1598
1599         psys = context.particle_system
1600         part = particle_get_settings(context)
1601
1602         layout.use_property_split = True
1603
1604         col = layout.column()
1605
1606         col.prop(part, "use_roughness_curve")
1607         if part.use_roughness_curve:
1608             sub = col.column()
1609             sub.template_curve_mapping(part, "roughness_curve")
1610             sub.prop(part, "roughness_1", text="Roughness")
1611             sub.prop(part, "roughness_1_size", text="Size")
1612         else:
1613             sub = col.column(align=True)
1614             sub.prop(part, "roughness_1", text="Uniform")
1615             sub.prop(part, "roughness_1_size", text="Size")
1616
1617             sub = col.column(align=True)
1618             sub.prop(part, "roughness_endpoint", "Endpoint")
1619             sub.prop(part, "roughness_end_shape")
1620
1621             sub = col.column(align=True)
1622             sub.prop(part, "roughness_2", text="Random")
1623             sub.prop(part, "roughness_2_size", text="Size")
1624             sub.prop(part, "roughness_2_threshold", slider=True)
1625
1626
1627 class PARTICLE_PT_children_kink(ParticleButtonsPanel, Panel):
1628     bl_label = "Kink"
1629     bl_parent_id = "PARTICLE_PT_children"
1630     bl_options = {'DEFAULT_CLOSED'}
1631     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1632
1633     @classmethod
1634     def poll(cls, context):
1635         part = particle_get_settings(context)
1636         return part.child_type != 'NONE'
1637
1638     def draw(self, context):
1639         layout = self.layout
1640
1641         psys = context.particle_system
1642         part = particle_get_settings(context)
1643
1644         layout.use_property_split = True
1645
1646         col = layout.column()
1647
1648         col.prop(part, "kink", text="Kink Type")
1649         col = layout.column()
1650         col.active = part.kink != 'NO'
1651
1652         if part.kink == 'SPIRAL':
1653
1654             sub = col.column()
1655             sub.prop(part, "kink_amplitude", text="Amplitude")
1656             sub.prop(part, "kink_amplitude_random", text="Randomize Amplitude", slider=True)
1657
1658             col.separator()
1659
1660             sub = col.column()
1661             sub.prop(part, "kink_axis")
1662             sub.prop(part, "kink_axis_random", text="Randomize Axis", slider=True)
1663
1664             col.separator()
1665
1666             col.prop(part, "kink_frequency", text="Frequency")
1667             col.prop(part, "kink_shape", text="Shape", slider=True)
1668             col.prop(part, "kink_extra_steps", text="Steps")
1669
1670         elif part.kink in {'CURL', 'RADIAL', 'WAVE', 'BRAID', 'WAVE'}:
1671             sub = col.column(align=True)
1672             sub.prop(part, "kink_amplitude")
1673             sub.prop(part, "kink_amplitude_clump", text="Clump", slider=True)
1674             col.prop(part, "kink_flat", slider=True)
1675             col.prop(part, "kink_frequency")
1676             col.prop(part, "kink_shape", slider=True)
1677
1678
1679 class PARTICLE_PT_field_weights(ParticleButtonsPanel, Panel):
1680     bl_label = "Field Weights"
1681     bl_options = {'DEFAULT_CLOSED'}
1682     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1683
1684     @classmethod
1685     def poll(cls, context):
1686         return particle_panel_poll(cls, context)
1687
1688     def draw(self, context):
1689         part = particle_get_settings(context)
1690         effector_weights_ui(self, context, part.effector_weights, 'PSYS')
1691
1692         if part.type == 'HAIR':
1693             row = self.layout.row()
1694             row.prop(part.effector_weights, "apply_to_hair_growing")
1695             row.prop(part, "apply_effector_to_children")
1696             row = self.layout.row()
1697             row.prop(part, "effect_hair", slider=True)
1698
1699
1700 class PARTICLE_PT_force_fields(ParticleButtonsPanel, Panel):
1701     bl_label = "Force Field Settings"
1702     bl_options = {'DEFAULT_CLOSED'}
1703     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1704
1705     def draw(self, context):
1706         layout = self.layout
1707
1708         part = particle_get_settings(context)
1709
1710         row = layout.row()
1711         row.prop(part, "use_self_effect")
1712         row.prop(part, "effector_amount", text="Amount")
1713
1714         split = layout.split(percentage=0.2)
1715         split.label(text="Type 1:")
1716         split.prop(part.force_field_1, "type", text="")
1717         basic_force_field_settings_ui(self, context, part.force_field_1)
1718         if part.force_field_1.type != 'NONE':
1719             layout.label(text="Falloff:")
1720         basic_force_field_falloff_ui(self, context, part.force_field_1)
1721
1722         if part.force_field_1.type != 'NONE':
1723             layout.label(text="")
1724
1725         split = layout.split(percentage=0.2)
1726         split.label(text="Type 2:")
1727         split.prop(part.force_field_2, "type", text="")
1728         basic_force_field_settings_ui(self, context, part.force_field_2)
1729         if part.force_field_2.type != 'NONE':
1730             layout.label(text="Falloff:")
1731         basic_force_field_falloff_ui(self, context, part.force_field_2)
1732
1733
1734 class PARTICLE_PT_vertexgroups(ParticleButtonsPanel, Panel):
1735     bl_label = "Vertex Groups"
1736     bl_options = {'DEFAULT_CLOSED'}
1737     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1738
1739     @classmethod
1740     def poll(cls, context):
1741         if context.particle_system is None:
1742             return False
1743         return particle_panel_poll(cls, context)
1744
1745     def draw(self, context):
1746         layout = self.layout
1747         layout.use_property_split = True
1748
1749         ob = context.object
1750         psys = context.particle_system
1751
1752         col = layout.column()
1753         row = col.row(align=True)
1754         row.prop_search(psys, "vertex_group_density", ob, "vertex_groups", text="Density")
1755         row.prop(psys, "invert_vertex_group_density", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1756
1757         row = col.row(align=True)
1758         row.prop_search(psys, "vertex_group_length", ob, "vertex_groups", text="Length")
1759         row.prop(psys, "invert_vertex_group_length", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1760
1761         row = col.row(align=True)
1762         row.prop_search(psys, "vertex_group_clump", ob, "vertex_groups", text="Clump")
1763         row.prop(psys, "invert_vertex_group_clump", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1764
1765         row = col.row(align=True)
1766         row.prop_search(psys, "vertex_group_kink", ob, "vertex_groups", text="Kink")
1767         row.prop(psys, "invert_vertex_group_kink", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1768
1769         row = col.row(align=True)
1770         row.prop_search(psys, "vertex_group_roughness_1", ob, "vertex_groups", text="Roughness 1")
1771         row.prop(psys, "invert_vertex_group_roughness_1", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1772
1773         row = col.row(align=True)
1774         row.prop_search(psys, "vertex_group_roughness_2", ob, "vertex_groups", text="Roughness 2")
1775         row.prop(psys, "invert_vertex_group_roughness_2", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1776
1777         row = col.row(align=True)
1778         row.prop_search(psys, "vertex_group_roughness_end", ob, "vertex_groups", text="Roughness End")
1779         row.prop(psys, "invert_vertex_group_roughness_end", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1780
1781         row = col.row(align=True)
1782         row.prop_search(psys, "vertex_group_twist", ob, "vertex_groups", text="Twist")
1783         row.prop(psys, "invert_vertex_group_twist", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1784
1785         # Commented out vertex groups don't work and are still waiting for better implementation
1786         # row = layout.row()
1787         # row.prop_search(psys, "vertex_group_velocity", ob, "vertex_groups", text="Velocity")
1788         # row.prop(psys, "invert_vertex_group_velocity", text="")
1789
1790         # row = layout.row()
1791         # row.prop_search(psys, "vertex_group_size", ob, "vertex_groups", text="Size")
1792         # row.prop(psys, "invert_vertex_group_size", text="")
1793
1794         # row = layout.row()
1795         # row.prop_search(psys, "vertex_group_tangent", ob, "vertex_groups", text="Tangent")
1796         # row.prop(psys, "invert_vertex_group_tangent", text="")
1797
1798         # row = layout.row()
1799         # row.prop_search(psys, "vertex_group_rotation", ob, "vertex_groups", text="Rotation")
1800         # row.prop(psys, "invert_vertex_group_rotation", text="")
1801
1802         # row = layout.row()
1803         # row.prop_search(psys, "vertex_group_field", ob, "vertex_groups", text="Field")
1804         # row.prop(psys, "invert_vertex_group_field", text="")
1805
1806
1807 class PARTICLE_PT_textures(ParticleButtonsPanel, Panel):
1808     bl_label = "Textures"
1809     bl_options = {'DEFAULT_CLOSED'}
1810     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1811
1812     @classmethod
1813     def poll(cls, context):
1814         if context.particle_system is None:
1815             return False
1816         return particle_panel_poll(cls, context)
1817
1818     def draw(self, context):
1819         layout = self.layout
1820
1821         psys = context.particle_system
1822         part = psys.settings
1823
1824         row = layout.row()
1825         row.template_list("TEXTURE_UL_texslots", "", part, "texture_slots", part, "active_texture_index", rows=2)
1826
1827         col = row.column(align=True)
1828         col.operator("texture.slot_move", text="", icon='TRIA_UP').type = 'UP'
1829         col.operator("texture.slot_move", text="", icon='TRIA_DOWN').type = 'DOWN'
1830         col.menu("TEXTURE_MT_specials", icon='DOWNARROW_HLT', text="")
1831
1832         if not part.active_texture:
1833             layout.template_ID(part, "active_texture", new="texture.new")
1834         else:
1835             slot = part.texture_slots[part.active_texture_index]
1836             layout.template_ID(slot, "texture", new="texture.new")
1837
1838
1839 class PARTICLE_PT_hair_shape(ParticleButtonsPanel, Panel):
1840     bl_label = "Hair Shape"
1841     bl_options = {'DEFAULT_CLOSED'}
1842     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1843
1844     @classmethod
1845     def poll(cls, context):
1846         if context.particle_system is None:
1847             return False
1848         return particle_panel_poll(cls, context)
1849
1850     def draw(self, context):
1851         layout = self.layout
1852         layout.use_property_split = True
1853
1854         psys = context.particle_system
1855         part = psys.settings
1856
1857         layout.prop(part, "shape", text="Strand Shape")
1858
1859         col = layout.column(align=True)
1860         col.prop(part, "root_radius", text="Radius Root")
1861         col.prop(part, "tip_radius", text="Tip")
1862
1863         col = layout.column()
1864         col.prop(part, "radius_scale", text="Radius Scaling")
1865         col.prop(part, "use_close_tip")
1866
1867
1868 class PARTICLE_PT_custom_props(ParticleButtonsPanel, PropertyPanel, Panel):
1869     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1870     _context_path = "particle_system.settings"
1871     _property_type = bpy.types.ParticleSettings
1872
1873
1874 classes = (
1875     PARTICLE_MT_specials,
1876     PARTICLE_MT_hair_dynamics_presets,
1877     PARTICLE_UL_particle_systems,
1878     PARTICLE_PT_context_particles,
1879     PARTICLE_PT_emission,
1880     PARTICLE_PT_emission_source,
1881     PARTICLE_PT_hair_dynamics,
1882     PARTICLE_PT_hair_dynamics_structure,
1883     PARTICLE_PT_hair_dynamics_volume,
1884     PARTICLE_PT_cache,
1885     PARTICLE_PT_velocity,
1886     PARTICLE_PT_rotation,
1887     PARTICLE_PT_rotation_angular_velocity,
1888     PARTICLE_PT_physics,
1889     PARTICLE_PT_physics_forces,
1890     PARTICLE_PT_physics_deflection,
1891     PARTICLE_PT_physics_integration,
1892     PARTICLE_PT_boidbrain,
1893     PARTICLE_PT_render,
1894     PARTICLE_PT_render_line,
1895     PARTICLE_PT_render_path,
1896     PARTICLE_PT_render_path_timing,
1897     PARTICLE_PT_render_object,
1898     PARTICLE_PT_render_collection,
1899     PARTICLE_PT_render_collection_use_count,
1900     PARTICLE_PT_render_billboards_tilt,
1901     PARTICLE_PT_render_billboards_uv,
1902     PARTICLE_PT_render_trails,
1903     PARTICLE_PT_render_extra,
1904     PARTICLE_PT_draw,
1905     PARTICLE_PT_children,
1906     PARTICLE_PT_children_parting,
1907     PARTICLE_PT_children_clumping,
1908     PARTICLE_PT_children_roughness,
1909     PARTICLE_PT_children_kink,
1910     PARTICLE_PT_hair_shape,
1911     PARTICLE_PT_field_weights,
1912     PARTICLE_PT_force_fields,
1913     PARTICLE_PT_vertexgroups,
1914     PARTICLE_PT_textures,
1915     PARTICLE_PT_custom_props,
1916 )
1917
1918 if __name__ == "__main__":  # only for live edit.
1919     from bpy.utils import register_class
1920     for cls in classes:
1921         register_class(cls)