Cleanup: doxy grouping for mesh runtime API
[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 != 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 != 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 class PARTICLE_PT_rotation_angular_velocity(ParticleButtonsPanel, Panel):
595     bl_label = "Angular Velocity"
596     bl_parent_id = "PARTICLE_PT_rotation"
597     bl_options = {'DEFAULT_CLOSED'}
598     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
599
600     def draw(self, context):
601         layout = self.layout
602
603         psys = context.particle_system
604         if psys:
605             part = psys.settings
606         else:
607             part = context.space_data.pin_id
608
609         layout.enabled = particle_panel_enabled(context, psys) and part.use_rotations
610         layout.use_property_split = True
611
612         col = layout.column()
613
614         col.prop(part, "angular_velocity_mode", text="Axis")
615         sub = col.column(align=True)
616         sub.active = part.angular_velocity_mode != 'NONE'
617         sub.prop(part, "angular_velocity_factor", text="Amount")
618
619
620 class PARTICLE_PT_physics(ParticleButtonsPanel, Panel):
621     bl_label = "Physics"
622     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
623
624     @classmethod
625     def poll(cls, context):
626         if particle_panel_poll(PARTICLE_PT_physics, context):
627             psys = context.particle_system
628             settings = particle_get_settings(context)
629
630             if settings.type == 'HAIR' and not settings.use_advanced_hair:
631                 return False
632             return psys is None or not psys.point_cache.use_external
633         else:
634             return False
635
636     def draw(self, context):
637         layout = self.layout
638         layout.use_property_split = True
639
640         psys = context.particle_system
641         part = particle_get_settings(context)
642
643         layout.enabled = particle_panel_enabled(context, psys)
644
645         layout.prop(part, "physics_type")
646
647         col = layout.column()
648         if part.physics_type != 'NO':
649             col = col.column()
650             col.prop(part, "mass")
651             col.prop(part, "use_multiply_size_mass", text="Multiply mass with size")
652
653         if part.physics_type == 'FLUID':
654             fluid = part.fluid
655
656             col.label(text="Fluid")
657             col.prop(fluid, "solver")
658             col.prop(fluid, "stiffness", text="Stiffness")
659             col.prop(fluid, "linear_viscosity", text="Viscosity")
660             col.prop(fluid, "buoyancy", text="Buoyancy", slider=True)
661
662             col.label(text="Advanced")
663
664             if fluid.solver == 'DDR':
665                 sub = col.column()
666                 sub.prop(fluid, "repulsion", slider=fluid.factor_repulsion)
667                 sub.prop(fluid, "factor_repulsion")
668
669                 sub.prop(fluid, "stiff_viscosity", slider=fluid.factor_stiff_viscosity)
670                 sub.prop(fluid, "factor_stiff_viscosity")
671
672             sub = col.column()
673             sub.prop(fluid, "fluid_radius", slider=fluid.factor_radius)
674             sub.prop(fluid, "factor_radius")
675
676             sub.prop(fluid, "rest_density", slider=fluid.use_factor_density)
677             sub.prop(fluid, "use_factor_density")
678
679             if fluid.solver == 'CLASSICAL':
680                 # With the classical solver, it is possible to calculate the
681                 # spacing between particles when the fluid is at rest. This
682                 # makes it easier to set stable initial conditions.
683                 particle_volume = part.mass / fluid.rest_density
684                 spacing = pow(particle_volume, 1.0 / 3.0)
685
686                 sub.label(text="Spacing: %g" % spacing)
687
688             elif fluid.solver == 'DDR':
689
690                 col.label(text="Springs")
691                 col.prop(fluid, "spring_force", text="Force")
692                 col.prop(fluid, "use_viscoelastic_springs")
693
694                 sub = col.column()
695                 sub.active = fluid.use_viscoelastic_springs
696                 sub.prop(fluid, "yield_ratio", slider=True)
697                 sub.prop(fluid, "plasticity", slider=True)
698
699                 col.label(text="Advanced")
700                 sub = col.column()
701                 sub.prop(fluid, "rest_length", slider=fluid.factor_rest_length)
702                 sub.prop(fluid, "factor_rest_length", text="")
703
704                 sub = col.column()
705                 sub.active = fluid.use_viscoelastic_springs
706                 sub.prop(fluid, "use_initial_rest_length")
707                 sub.prop(fluid, "spring_frames", text="Frames")
708
709         elif part.physics_type == 'KEYED':
710
711             sub = col.column()
712             sub.active = not psys.use_keyed_timing
713             sub.prop(part, "keyed_loops", text="Loops")
714             if psys:
715                 col.prop(psys, "use_keyed_timing", text="Use Timing")
716
717             col.label(text="Keys")
718
719         elif part.physics_type == 'BOIDS':
720             boids = part.boids
721
722             row = layout.row()
723             row.prop(boids, "use_flight")
724             row.prop(boids, "use_land")
725             row.prop(boids, "use_climb")
726
727             split = layout.split()
728
729             col = split.column(align=True)
730             col.active = boids.use_flight
731             col.prop(boids, "air_speed_max")
732             col.prop(boids, "air_speed_min", slider=True)
733             col.prop(boids, "air_acc_max", slider=True)
734             col.prop(boids, "air_ave_max", slider=True)
735             col.prop(boids, "air_personal_space")
736             row = col.row(align=True)
737             row.active = (boids.use_land or boids.use_climb) and boids.use_flight
738             row.prop(boids, "land_smooth")
739
740             col = split.column(align=True)
741             col.active = boids.use_land or boids.use_climb
742             col.prop(boids, "land_speed_max")
743             col.prop(boids, "land_jump_speed")
744             col.prop(boids, "land_acc_max", slider=True)
745             col.prop(boids, "land_ave_max", slider=True)
746             col.prop(boids, "land_personal_space")
747             col.prop(boids, "land_stick_force")
748
749             layout.prop(part, "collision_group")
750
751             split = layout.split()
752
753             col = split.column(align=True)
754             col.label(text="Battle:")
755             col.prop(boids, "health")
756             col.prop(boids, "strength")
757             col.prop(boids, "aggression")
758             col.prop(boids, "accuracy")
759             col.prop(boids, "range")
760
761             col = split.column()
762             col.label(text="Misc:")
763             col.prop(boids, "bank", slider=True)
764             col.prop(boids, "pitch", slider=True)
765             col.prop(boids, "height", slider=True)
766
767         if psys and part.physics_type in {'KEYED', 'BOIDS', 'FLUID'}:
768             if part.physics_type == 'BOIDS':
769                 layout.label(text="Relations:")
770             elif part.physics_type == 'FLUID':
771                 layout.label(text="Fluid Interaction:")
772
773             row = layout.row()
774             row.template_list("UI_UL_list", "particle_targets", psys, "targets",
775                               psys, "active_particle_target_index", rows=4)
776
777             col = row.column()
778             sub = col.row()
779             subsub = sub.column(align=True)
780             subsub.operator("particle.new_target", icon='ZOOMIN', text="")
781             subsub.operator("particle.target_remove", icon='ZOOMOUT', text="")
782             sub = col.row()
783             subsub = sub.column(align=True)
784             subsub.operator("particle.target_move_up", icon='TRIA_UP', text="")
785             subsub.operator("particle.target_move_down", icon='TRIA_DOWN', text="")
786
787             key = psys.active_particle_target
788             if key:
789                 row = layout.row()
790                 if part.physics_type == 'KEYED':
791                     col = row.column()
792                     # doesn't work yet
793                     #col.alert = key.valid
794                     col.prop(key, "object", text="")
795                     col.prop(key, "system", text="System")
796                     col = row.column()
797                     col.active = psys.use_keyed_timing
798                     col.prop(key, "time")
799                     col.prop(key, "duration")
800                 elif part.physics_type == 'BOIDS':
801                     sub = row.row()
802                     # doesn't work yet
803                     #sub.alert = key.valid
804                     sub.prop(key, "object", text="")
805                     sub.prop(key, "system", text="System")
806
807                     layout.row().prop(key, "alliance", expand=True)
808                 elif part.physics_type == 'FLUID':
809                     sub = row.row()
810                     # doesn't work yet
811                     #sub.alert = key.valid
812                     sub.prop(key, "object", text="")
813                     sub.prop(key, "system", text="System")
814
815 class PARTICLE_PT_physics_deflection(ParticleButtonsPanel, Panel):
816     bl_label = "Deflection"
817     bl_parent_id = "PARTICLE_PT_physics"
818     bl_options = {'DEFAULT_CLOSED'}
819     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
820
821     @classmethod
822     def poll(cls, context):
823        part = particle_get_settings(context)
824        return part.physics_type in {'NEWTON', 'FLUID'}
825
826     def draw(self, context):
827         layout = self.layout
828         layout.use_property_split = True
829
830         psys = context.particle_system
831         part = particle_get_settings(context)
832
833         layout.enabled = particle_panel_enabled(context, psys)
834
835         col = layout.column()
836         col.prop(part, "use_size_deflect")
837         col.prop(part, "use_die_on_collision")
838
839         col.prop(part, "collision_group")
840
841
842 class PARTICLE_PT_physics_forces(ParticleButtonsPanel, Panel):
843     bl_label = "Forces"
844     bl_parent_id = "PARTICLE_PT_physics"
845     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
846
847     @classmethod
848     def poll(cls, context):
849        part = particle_get_settings(context)
850        return part.physics_type == 'NEWTON'
851
852     def draw(self, context):
853         layout = self.layout
854         layout.use_property_split = True
855
856         psys = context.particle_system
857         part = particle_get_settings(context)
858
859         layout.enabled = particle_panel_enabled(context, psys)
860
861         col = layout.column()
862
863         col.prop(part, "brownian_factor")
864         col.prop(part, "drag_factor", slider=True)
865         col.prop(part, "damping", slider=True)
866
867
868 class PARTICLE_PT_physics_integration(ParticleButtonsPanel, Panel):
869     bl_label = "Integration"
870     bl_options = {'DEFAULT_CLOSED'}
871     bl_parent_id = "PARTICLE_PT_physics"
872     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
873
874     @classmethod
875     def poll(cls, context):
876        part = particle_get_settings(context)
877        return part.physics_type == 'NEWTON'
878
879     def draw(self, context):
880         layout = self.layout
881         layout.use_property_split = True
882
883         psys = context.particle_system
884         part = particle_get_settings(context)
885
886         layout.enabled = particle_panel_enabled(context, psys)
887
888         col = layout.column()
889
890         col.prop(part, "integrator")
891         col.prop(part, "timestep")
892         sub = col.row()
893         sub.prop(part, "subframes")
894         supports_courant = part.physics_type == 'FLUID'
895         subsub = sub.row()
896         subsub.enabled = supports_courant
897         subsub.prop(part, "use_adaptive_subframes", text="")
898         if supports_courant and part.use_adaptive_subframes:
899             col.prop(part, "courant_target", text="Threshold")
900
901
902 class PARTICLE_PT_boidbrain(ParticleButtonsPanel, Panel):
903     bl_label = "Boid Brain"
904     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
905
906     @classmethod
907     def poll(cls, context):
908         psys = context.particle_system
909         settings = particle_get_settings(context)
910         engine = context.engine
911
912         if settings is None:
913             return False
914         if psys is not None and psys.point_cache.use_external:
915             return False
916         return settings.physics_type == 'BOIDS' and engine in cls.COMPAT_ENGINES
917
918     def draw(self, context):
919         layout = self.layout
920
921         boids = particle_get_settings(context).boids
922
923         layout.enabled = particle_panel_enabled(context, context.particle_system)
924
925         # Currently boids can only use the first state so these are commented out for now.
926         #row = layout.row()
927         #row.template_list("UI_UL_list", "particle_boids", boids, "states",
928         #                  boids, "active_boid_state_index", compact="True")
929         #col = row.row()
930         #sub = col.row(align=True)
931         #sub.operator("boid.state_add", icon='ZOOMIN', text="")
932         #sub.operator("boid.state_del", icon='ZOOMOUT', text="")
933         #sub = row.row(align=True)
934         #sub.operator("boid.state_move_up", icon='TRIA_UP', text="")
935         #sub.operator("boid.state_move_down", icon='TRIA_DOWN', text="")
936
937         state = boids.active_boid_state
938
939         #layout.prop(state, "name", text="State name")
940
941         row = layout.row()
942         row.prop(state, "ruleset_type")
943         if state.ruleset_type == 'FUZZY':
944             row.prop(state, "rule_fuzzy", slider=True)
945         else:
946             row.label(text="")
947
948         row = layout.row()
949         row.template_list("UI_UL_list", "particle_boids_rules", state,
950                           "rules", state, "active_boid_rule_index", rows=4)
951
952         col = row.column()
953         sub = col.row()
954         subsub = sub.column(align=True)
955         subsub.operator_menu_enum("boid.rule_add", "type", icon='ZOOMIN', text="")
956         subsub.operator("boid.rule_del", icon='ZOOMOUT', text="")
957         sub = col.row()
958         subsub = sub.column(align=True)
959         subsub.operator("boid.rule_move_up", icon='TRIA_UP', text="")
960         subsub.operator("boid.rule_move_down", icon='TRIA_DOWN', text="")
961
962         rule = state.active_boid_rule
963
964         if rule:
965             row = layout.row()
966             row.prop(rule, "name", text="")
967             # somebody make nice icons for boids here please! -jahka
968             row.prop(rule, "use_in_air", icon='TRIA_UP', text="")
969             row.prop(rule, "use_on_land", icon='TRIA_DOWN', text="")
970
971             row = layout.row()
972
973             if rule.type == 'GOAL':
974                 row.prop(rule, "object")
975                 row = layout.row()
976                 row.prop(rule, "use_predict")
977             elif rule.type == 'AVOID':
978                 row.prop(rule, "object")
979                 row = layout.row()
980                 row.prop(rule, "use_predict")
981                 row.prop(rule, "fear_factor")
982             elif rule.type == 'FOLLOW_PATH':
983                 row.label(text="Not yet functional")
984             elif rule.type == 'AVOID_COLLISION':
985                 row.prop(rule, "use_avoid")
986                 row.prop(rule, "use_avoid_collision")
987                 row.prop(rule, "look_ahead")
988             elif rule.type == 'FOLLOW_LEADER':
989                 row.prop(rule, "object", text="")
990                 row.prop(rule, "distance")
991                 row = layout.row()
992                 row.prop(rule, "use_line")
993                 sub = row.row()
994                 sub.active = rule.line
995                 sub.prop(rule, "queue_count")
996             elif rule.type == 'AVERAGE_SPEED':
997                 row.prop(rule, "speed", slider=True)
998                 row.prop(rule, "wander", slider=True)
999                 row.prop(rule, "level", slider=True)
1000             elif rule.type == 'FIGHT':
1001                 row.prop(rule, "distance")
1002                 row.prop(rule, "flee_distance")
1003
1004
1005 class PARTICLE_PT_render(ParticleButtonsPanel, Panel):
1006     bl_label = "Render"
1007     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1008
1009     @classmethod
1010     def poll(cls, context):
1011         settings = particle_get_settings(context)
1012         engine = context.engine
1013         if settings is None:
1014             return False
1015
1016         return engine in cls.COMPAT_ENGINES
1017
1018     def draw(self, context):
1019         layout = self.layout
1020         layout.use_property_split = True
1021
1022         psys = context.particle_system
1023         part = particle_get_settings(context)
1024
1025         layout.prop(part, "render_type", text="Render As")
1026
1027         if part.type == 'EMITTER' or \
1028            (part.render_type in {'OBJECT', 'COLLECTION'} and part.type == 'HAIR'):
1029             if part.render_type not in {'NONE'}:
1030
1031                 col = layout.column(align=True)
1032                 col.prop(part, "particle_size", text="Scale")
1033                 col.prop(part, "size_random", slider=True, text="Scale Randomness")
1034
1035         if psys:
1036             col = layout.column()
1037             if part.render_type not in {'OBJECT', 'COLLECTION', 'NONE'}:
1038                 # col.enabled = False
1039                 col.prop(part, "material_slot", text="Material")
1040                 col.prop(psys, "parent", text="Coordinate System")
1041
1042 class PARTICLE_PT_render_extra(ParticleButtonsPanel, Panel):
1043     bl_label = "Extra"
1044     bl_parent_id = "PARTICLE_PT_render"
1045     bl_options = {'DEFAULT_CLOSED'}
1046     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1047
1048     @classmethod
1049     def poll(cls, context):
1050         part = particle_get_settings(context)
1051         return part.render_type != 'NONE'
1052
1053     def draw(self, context):
1054         layout = self.layout
1055         layout.use_property_split = True
1056
1057         psys = context.particle_system
1058         ob = context.object
1059         part = particle_get_settings(context)
1060
1061         col=layout.column()
1062
1063         col = layout.column()
1064         col.prop(part, "use_parent_particles", text="Parent Particles")
1065         col.prop(part, "show_unborn", text="Unborn")
1066         col.prop(part, "use_dead", text="Dead")
1067
1068
1069
1070 class PARTICLE_PT_render_line(ParticleButtonsPanel, Panel):
1071     bl_label = "Line"
1072     bl_parent_id = "PARTICLE_PT_render"
1073     bl_options = {'DEFAULT_CLOSED'}
1074     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1075
1076     @classmethod
1077     def poll(cls, context):
1078         part = particle_get_settings(context)
1079         return part.render_type == 'LINE'
1080
1081     def draw(self, context):
1082         layout = self.layout
1083         layout.use_property_split = True
1084
1085         psys = context.particle_system
1086         ob = context.object
1087         part = particle_get_settings(context)
1088
1089         col=layout.column()
1090
1091         col.separator()
1092         sub = col.column(align=True)
1093         sub.prop(part, "line_length_tail", text="Length Tail")
1094         sub.prop(part, "line_length_head", text="Head")
1095         col.prop(part, "use_velocity_length", text="Velocity Length")
1096
1097 class PARTICLE_PT_render_path(ParticleButtonsPanel, Panel):
1098     bl_label = "Path"
1099     bl_parent_id = "PARTICLE_PT_render"
1100     bl_options = {'DEFAULT_CLOSED'}
1101     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1102
1103     @classmethod
1104     def poll(cls, context):
1105         part = particle_get_settings(context)
1106         return part.render_type == 'PATH'
1107
1108     def draw(self, context):
1109         layout = self.layout
1110         layout.use_property_split = True
1111
1112         psys = context.particle_system
1113         ob = context.object
1114         part = particle_get_settings(context)
1115
1116         col=layout.column()
1117
1118         col.prop(part, "use_strand_primitive")
1119         sub = col.column()
1120         sub.active = (part.use_strand_primitive is False)
1121         sub.prop(part, "use_render_adaptive")
1122         sub = col.column()
1123         sub.active = part.use_render_adaptive or part.use_strand_primitive is True
1124         sub.prop(part, "adaptive_angle")
1125         sub = col.column()
1126         sub.active = (part.use_render_adaptive is True and part.use_strand_primitive is False)
1127         sub.prop(part, "adaptive_pixel")
1128         col.prop(part, "use_hair_bspline")
1129         col.prop(part, "render_step", text="Steps")
1130
1131
1132 class PARTICLE_PT_render_path_timing(ParticleButtonsPanel, Panel):
1133     bl_label = "Timing"
1134     bl_parent_id = "PARTICLE_PT_render"
1135     bl_options = {'DEFAULT_CLOSED'}
1136     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1137
1138     @classmethod
1139     def poll(cls, context):
1140         part = particle_get_settings(context)
1141         return part.render_type == 'PATH'
1142
1143     def draw(self, context):
1144         layout = self.layout
1145         layout.use_property_split = True
1146
1147         psys = context.particle_system
1148         ob = context.object
1149         part = particle_get_settings(context)
1150
1151         col=layout.column()
1152
1153         col.prop(part, "use_absolute_path_time")
1154
1155         if part.type == 'HAIR' or psys.point_cache.is_baked:
1156             col.prop(part, "path_start", text="Start", slider=not part.use_absolute_path_time)
1157         else:
1158             col.prop(part, "trail_count")
1159
1160         col.prop(part, "path_end", text="End", slider=not part.use_absolute_path_time)
1161         col.prop(part, "length_random", text="Random", slider=True)
1162
1163 class PARTICLE_PT_render_object(ParticleButtonsPanel, Panel):
1164     bl_label = "Object"
1165     bl_parent_id = "PARTICLE_PT_render"
1166     bl_options = {'DEFAULT_CLOSED'}
1167     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1168
1169     @classmethod
1170     def poll(cls, context):
1171         part = particle_get_settings(context)
1172         return part.render_type == 'OBJECT'
1173
1174     def draw(self, context):
1175         layout = self.layout
1176         layout.use_property_split = True
1177
1178         psys = context.particle_system
1179         ob = context.object
1180         part = particle_get_settings(context)
1181
1182         col=layout.column()
1183
1184         col.prop(part, "dupli_object", text="Instance Object")
1185         sub = col.column()
1186         sub.prop(part, "use_global_dupli", text="Global Coordinates")
1187         sub.prop(part, "use_rotation_dupli", text="Object Rotation")
1188         sub.prop(part, "use_scale_dupli", text="Object Scale")
1189
1190
1191 class PARTICLE_PT_render_collection(ParticleButtonsPanel, Panel):
1192     bl_label = "Collection"
1193     bl_parent_id = "PARTICLE_PT_render"
1194     bl_options = {'DEFAULT_CLOSED'}
1195     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1196
1197     @classmethod
1198     def poll(cls, context):
1199         part = particle_get_settings(context)
1200         return part.render_type == 'COLLECTION'
1201
1202     def draw(self, context):
1203         layout = self.layout
1204         layout.use_property_split = True
1205
1206         psys = context.particle_system
1207         ob = context.object
1208         part = particle_get_settings(context)
1209
1210         col=layout.column()
1211
1212         col.prop(part, "dupli_group")
1213
1214         col.prop(part, "use_whole_group")
1215         sub = col.column()
1216         sub.active = (part.use_whole_group is False)
1217         sub.prop(part, "use_group_pick_random")
1218         sub.prop(part, "use_global_dupli", text="Global Coordinates")
1219         sub.prop(part, "use_rotation_dupli", text="Object Rotation")
1220         sub.prop(part, "use_scale_dupli", text="Object Scale")
1221
1222 class PARTICLE_PT_render_collection_use_count(ParticleButtonsPanel, Panel):
1223     bl_label = "Use Count"
1224     bl_parent_id = "PARTICLE_PT_render_collection"
1225     bl_options = {'DEFAULT_CLOSED'}
1226     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1227
1228     @classmethod
1229     def poll(cls, context):
1230         part = particle_get_settings(context)
1231         return part.render_type == 'COLLECTION'
1232
1233     def draw_header(self, context):
1234         layout = self.layout
1235         part = particle_get_settings(context)
1236
1237         layout.active = not part.use_whole_group
1238
1239         layout.prop(part, "use_group_count", text="")
1240
1241     def draw(self, context):
1242         layout = self.layout
1243         layout.use_property_split = True
1244
1245         psys = context.particle_system
1246         ob = context.object
1247         part = particle_get_settings(context)
1248
1249         col=layout.column()
1250
1251         layout.active = part.use_group_count and not part.use_whole_group
1252
1253         row = layout.row()
1254         row.template_list("UI_UL_list", "particle_dupli_weights", part, "dupli_weights",
1255                                   part, "active_dupliweight_index")
1256
1257         col = row.column()
1258         sub = col.row()
1259         subsub = sub.column(align=True)
1260         subsub.operator("particle.dupliob_copy", icon='ZOOMIN', text="")
1261         subsub.operator("particle.dupliob_remove", icon='ZOOMOUT', text="")
1262         subsub.operator("particle.dupliob_move_up", icon='TRIA_UP', text="")
1263         subsub.operator("particle.dupliob_move_down", icon='TRIA_DOWN', text="")
1264
1265         weight = part.active_dupliweight
1266         if weight:
1267             row = layout.row()
1268             row.prop(weight, "count")
1269
1270 class PARTICLE_PT_render_billboards_alignment(ParticleButtonsPanel, Panel):
1271     bl_label = "Billboard Alignment"
1272     bl_parent_id = "PARTICLE_PT_render"
1273     bl_options = {'DEFAULT_CLOSED'}
1274     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1275
1276     @classmethod
1277     def poll(cls, context):
1278         part = particle_get_settings(context)
1279         return part.render_type == 'BILLBOARD'
1280
1281     def draw(self, context):
1282         layout = self.layout
1283         layout.use_property_split = True
1284
1285         psys = context.particle_system
1286         ob = context.object
1287         part = particle_get_settings(context)
1288
1289         col=layout.column()
1290
1291         col.prop(part, "billboard_align", text="Align To")
1292         col.prop(part, "lock_billboard", text="Lock Axis")
1293         col.prop(part, "billboard_object")
1294
1295 class PARTICLE_PT_render_billboards_tilt(ParticleButtonsPanel, Panel):
1296     bl_label = "Billboard Tilt"
1297     bl_parent_id = "PARTICLE_PT_render"
1298     bl_options = {'DEFAULT_CLOSED'}
1299     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1300
1301     @classmethod
1302     def poll(cls, context):
1303         part = particle_get_settings(context)
1304         return part.render_type == 'BILLBOARD'
1305
1306     def draw(self, context):
1307         layout = self.layout
1308         layout.use_property_split = True
1309
1310         psys = context.particle_system
1311         ob = context.object
1312         part = particle_get_settings(context)
1313
1314         col=layout.column()
1315
1316         sub = col.column(align=True)
1317         sub.prop(part, "billboard_tilt", text="Angle", slider=True)
1318         sub.prop(part, "billboard_tilt_random", text="Random", slider=True)
1319
1320         sub = col.column(align=True)
1321         sub.prop(part, "billboard_offset")
1322         col.prop(part, "billboard_size", text="Scale")
1323         if part.billboard_align == 'VEL':
1324             col = col.column(align=True)
1325             col.prop(part, "billboard_velocity_head", text="Velocity ScaleHead")
1326             col.prop(part, "billboard_velocity_tail", text="Tail")
1327
1328 class PARTICLE_PT_render_billboards_uv(ParticleButtonsPanel, Panel):
1329     bl_label = "Billboard UVs"
1330     bl_parent_id = "PARTICLE_PT_render"
1331     bl_options = {'DEFAULT_CLOSED'}
1332     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1333
1334     @classmethod
1335     def poll(cls, context):
1336         part = particle_get_settings(context)
1337         return part.render_type == 'BILLBOARD'
1338
1339     def draw(self, context):
1340         layout = self.layout
1341         layout.use_property_split = True
1342
1343         psys = context.particle_system
1344         ob = context.object
1345         part = particle_get_settings(context)
1346
1347         col=layout.column()
1348
1349         if psys:
1350             col.prop_search(psys, "billboard_normal_uv", ob.data, "uv_layers")
1351             col.prop_search(psys, "billboard_time_index_uv", ob.data, "uv_layers")
1352
1353         col.prop(part, "billboard_uv_split", text="Split UVs")
1354
1355         if psys:
1356             sub = col.column()
1357             sub.active = part.billboard_uv_split > 1
1358             sub.prop_search(psys, "billboard_split_uv", ob.data, "uv_layers")
1359
1360         sub.prop(part, "billboard_animation")
1361         sub.prop(part, "billboard_offset_split")
1362
1363
1364
1365 class PARTICLE_PT_render_trails(ParticleButtonsPanel, Panel):
1366     bl_label = "Trails"
1367     bl_parent_id = "PARTICLE_PT_render"
1368     bl_options = {'DEFAULT_CLOSED'}
1369     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1370
1371     @classmethod
1372     def poll(cls, context):
1373         part = particle_get_settings(context)
1374         return part.render_type in {'HALO', 'LINE', 'BILLBOARD'}
1375
1376     def draw(self, context):
1377         layout = self.layout
1378         layout.use_property_split = True
1379
1380         psys = context.particle_system
1381         part = particle_get_settings(context)
1382
1383         col=layout.column()
1384
1385         col.prop(part, "trail_count")
1386
1387         sub = col.column()
1388         sub.active = (part.trail_count > 1)
1389         sub.prop(part, "use_absolute_path_time", text="Length in Frames")
1390         sub.prop(part, "path_end", text="Length", slider=not part.use_absolute_path_time)
1391         sub.prop(part, "length_random", text="Random Length", slider=True)
1392
1393
1394 class PARTICLE_PT_draw(ParticleButtonsPanel, Panel):
1395     bl_label = "Viewport Display"
1396     bl_options = {'DEFAULT_CLOSED'}
1397     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1398
1399     @classmethod
1400     def poll(cls, context):
1401         settings = particle_get_settings(context)
1402         engine = context.engine
1403         if settings is None:
1404             return False
1405         return engine in cls.COMPAT_ENGINES
1406
1407     def draw(self, context):
1408         layout = self.layout
1409         layout.use_property_split = True
1410
1411         psys = context.particle_system
1412         part = particle_get_settings(context)
1413
1414         layout.prop(part, "draw_method", text="Display As")
1415
1416         if part.draw_method == 'NONE' or (part.render_type == 'NONE' and part.draw_method == 'RENDER'):
1417             return
1418
1419         path = (part.render_type == 'PATH' and part.draw_method == 'RENDER') or part.draw_method == 'PATH'
1420
1421         layout.separator()
1422
1423         col = layout.column()
1424         col.prop(part, "draw_color", text="Color")
1425         if part.draw_color in {'VELOCITY', 'ACCELERATION'}:
1426             col.prop(part, "color_maximum", text="Fade Distance")
1427
1428         col = layout.column()
1429
1430         if path:
1431             col.prop(part, "draw_step", text="Strand Steps")
1432         col.prop(part, "draw_percentage", slider=True, text="Amount")
1433         if part.draw_method != 'RENDER' or part.render_type == 'HALO':
1434             col.prop(part, "draw_size", text="Size")
1435
1436         if part.draw_percentage != 100 and psys is not None:
1437             if part.type == 'HAIR':
1438                 if psys.use_hair_dynamics and psys.point_cache.is_baked is False:
1439                     layout.row().label(text="Display percentage makes dynamics inaccurate without baking")
1440             else:
1441                 phystype = part.physics_type
1442                 if phystype != 'NO' and phystype != 'KEYED' and psys.point_cache.is_baked is False:
1443                     layout.row().label(text="Display percentage makes dynamics inaccurate without baking")
1444         else:
1445             layout.row().label(text="")
1446
1447         col = layout.column()
1448         col.prop(part, "show_guide_hairs", text="Guide Hairs")
1449         col.prop(part, "show_size")
1450         col.prop(part, "show_velocity")
1451         col.prop(part, "show_number")
1452         if part.physics_type == 'BOIDS':
1453             col.prop(part, "show_health")
1454
1455
1456 class PARTICLE_PT_children(ParticleButtonsPanel, Panel):
1457     bl_label = "Children"
1458     bl_options = {'DEFAULT_CLOSED'}
1459     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1460
1461     @classmethod
1462     def poll(cls, context):
1463         return particle_panel_poll(cls, context)
1464
1465     def draw(self, context):
1466         layout = self.layout
1467
1468         psys = context.particle_system
1469         part = particle_get_settings(context)
1470
1471         layout.row().prop(part, "child_type", expand=True)
1472
1473         layout.use_property_split = True
1474
1475         if part.child_type == 'NONE':
1476             return
1477
1478         col = layout.column()
1479
1480         sub = col.column(align=True)
1481         sub.prop(part, "child_nbr", text="Display Amount")
1482         sub.prop(part, "rendered_child_count", text="Render Amount")
1483
1484         col.separator()
1485
1486         col.prop(part, "child_length", slider=True)
1487         col.prop(part, "child_length_threshold", slider=True)
1488         if psys:
1489             col.prop(psys, "child_seed", text="Seed")
1490
1491         col.separator()
1492
1493         if part.child_type == 'INTERPOLATED':
1494             col.prop(part, "virtual_parents", slider=True)
1495             col.prop(part, "create_long_hair_children")
1496         else:
1497             col.separator()
1498             sub = col.column(align=True)
1499             sub.prop(part, "child_size", text="Size")
1500             sub.prop(part, "child_size_random", text="Randomize Size", slider=True)
1501
1502         if part.child_type == 'SIMPLE':
1503             col.separator()
1504             col.prop(part, "child_radius", text="Radius")
1505             col.prop(part, "child_roundness", text="Roundness", slider=True)
1506         elif part.virtual_parents > 0.0:
1507             sub = col.column(align=True)
1508             sub.label(text="Parting not available with virtual parents")
1509
1510
1511 class PARTICLE_PT_children_parting(ParticleButtonsPanel, Panel):
1512     bl_label = "Parting"
1513     bl_parent_id = "PARTICLE_PT_children"
1514     bl_options = {'DEFAULT_CLOSED'}
1515     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1516
1517     @classmethod
1518     def poll(cls, context):
1519         part = particle_get_settings(context)
1520         return part.child_type == 'INTERPOLATED'
1521
1522     def draw(self, context):
1523         layout = self.layout
1524
1525         psys = context.particle_system
1526         part = particle_get_settings(context)
1527
1528         layout.use_property_split = True
1529
1530         col = layout.column()
1531         col.prop(part, "child_parting_factor", text="Parting", slider=True)
1532         col.prop(part, "child_parting_min", text="Min")
1533         col.prop(part, "child_parting_max", text="Max")
1534
1535 class PARTICLE_PT_children_clumping(ParticleButtonsPanel, Panel):
1536     bl_label = "Clumping"
1537     bl_parent_id = "PARTICLE_PT_children"
1538     bl_options = {'DEFAULT_CLOSED'}
1539     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1540
1541     @classmethod
1542     def poll(cls, context):
1543         part = particle_get_settings(context)
1544         return part.child_type != 'NONE'
1545
1546     def draw(self, context):
1547         layout = self.layout
1548
1549         psys = context.particle_system
1550         part = particle_get_settings(context)
1551
1552         layout.use_property_split = True
1553
1554         col = layout.column()
1555
1556         sub = col.column()
1557
1558         sub.prop(part, "use_clump_curve")
1559         if part.use_clump_curve:
1560             sub.template_curve_mapping(part, "clump_curve")
1561         else:
1562             sub.prop(part, "clump_factor", slider=True)
1563             sub.prop(part, "clump_shape", slider=True)
1564         sub = col.column(align=True)
1565         sub.prop(part, "use_clump_noise")
1566         subsub = sub.column()
1567         subsub.enabled = part.use_clump_noise
1568         subsub.prop(part, "clump_noise_size")
1569
1570         if part.child_type == 'SIMPLE':
1571             sub.prop(part, "twist")
1572             sub.prop(part, "use_twist_curve")
1573             if part.use_twist_curve:
1574                 sub.template_curve_mapping(part, "twist_curve")
1575
1576 class PARTICLE_PT_children_roughness(ParticleButtonsPanel, Panel):
1577     bl_label = "Roughness"
1578     bl_parent_id = "PARTICLE_PT_children"
1579     bl_options = {'DEFAULT_CLOSED'}
1580     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1581
1582     @classmethod
1583     def poll(cls, context):
1584         part = particle_get_settings(context)
1585         return part.child_type != 'NONE'
1586
1587     def draw(self, context):
1588         layout = self.layout
1589
1590         psys = context.particle_system
1591         part = particle_get_settings(context)
1592
1593         layout.use_property_split = True
1594
1595         col = layout.column()
1596
1597         col.prop(part, "use_roughness_curve")
1598         if part.use_roughness_curve:
1599             sub = col.column()
1600             sub.template_curve_mapping(part, "roughness_curve")
1601             sub.prop(part, "roughness_1", text="Roughness")
1602             sub.prop(part, "roughness_1_size", text="Size")
1603         else:
1604             sub = col.column(align=True)
1605             sub.prop(part, "roughness_1", text="Uniform")
1606             sub.prop(part, "roughness_1_size", text="Size")
1607
1608             sub = col.column(align=True)
1609             sub.prop(part, "roughness_endpoint", "Endpoint")
1610             sub.prop(part, "roughness_end_shape")
1611
1612             sub = col.column(align=True)
1613             sub.prop(part, "roughness_2", text="Random")
1614             sub.prop(part, "roughness_2_size", text="Size")
1615             sub.prop(part, "roughness_2_threshold", slider=True)
1616
1617
1618 class PARTICLE_PT_children_kink(ParticleButtonsPanel, Panel):
1619     bl_label = "Kink"
1620     bl_parent_id = "PARTICLE_PT_children"
1621     bl_options = {'DEFAULT_CLOSED'}
1622     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1623
1624     @classmethod
1625     def poll(cls, context):
1626         part = particle_get_settings(context)
1627         return part.child_type != 'NONE'
1628
1629     def draw(self, context):
1630         layout = self.layout
1631
1632         psys = context.particle_system
1633         part = particle_get_settings(context)
1634
1635         layout.use_property_split = True
1636
1637         col = layout.column()
1638
1639         col.prop(part, "kink", text="Kink Type")
1640         col = layout.column()
1641         col.active = part.kink != 'NO'
1642
1643         if part.kink == 'SPIRAL':
1644
1645             sub = col.column()
1646             sub.prop(part, "kink_amplitude", text="Amplitude")
1647             sub.prop(part, "kink_amplitude_random", text="Randomize Amplitude", slider=True)
1648
1649             col.separator()
1650
1651             sub = col.column()
1652             sub.prop(part, "kink_axis")
1653             sub.prop(part, "kink_axis_random", text="Randomize Axis", slider=True)
1654
1655             col.separator()
1656
1657             col.prop(part, "kink_frequency", text="Frequency")
1658             col.prop(part, "kink_shape", text="Shape", slider=True)
1659             col.prop(part, "kink_extra_steps", text="Steps")
1660
1661         elif part.kink in {'CURL', 'RADIAL', 'WAVE', 'BRAID', 'WAVE'}:
1662             sub = col.column(align=True)
1663             sub.prop(part, "kink_amplitude")
1664             sub.prop(part, "kink_amplitude_clump", text="Clump", slider=True)
1665             col.prop(part, "kink_flat", slider=True)
1666             col.prop(part, "kink_frequency")
1667             col.prop(part, "kink_shape", slider=True)
1668
1669
1670 class PARTICLE_PT_field_weights(ParticleButtonsPanel, Panel):
1671     bl_label = "Field Weights"
1672     bl_options = {'DEFAULT_CLOSED'}
1673     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1674
1675     @classmethod
1676     def poll(cls, context):
1677         return particle_panel_poll(cls, context)
1678
1679     def draw(self, context):
1680         part = particle_get_settings(context)
1681         effector_weights_ui(self, context, part.effector_weights, 'PSYS')
1682
1683         if part.type == 'HAIR':
1684             row = self.layout.row()
1685             row.prop(part.effector_weights, "apply_to_hair_growing")
1686             row.prop(part, "apply_effector_to_children")
1687             row = self.layout.row()
1688             row.prop(part, "effect_hair", slider=True)
1689
1690
1691 class PARTICLE_PT_force_fields(ParticleButtonsPanel, Panel):
1692     bl_label = "Force Field Settings"
1693     bl_options = {'DEFAULT_CLOSED'}
1694     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1695
1696     def draw(self, context):
1697         layout = self.layout
1698
1699         part = particle_get_settings(context)
1700
1701         row = layout.row()
1702         row.prop(part, "use_self_effect")
1703         row.prop(part, "effector_amount", text="Amount")
1704
1705         split = layout.split(percentage=0.2)
1706         split.label(text="Type 1:")
1707         split.prop(part.force_field_1, "type", text="")
1708         basic_force_field_settings_ui(self, context, part.force_field_1)
1709         if part.force_field_1.type != 'NONE':
1710             layout.label(text="Falloff:")
1711         basic_force_field_falloff_ui(self, context, part.force_field_1)
1712
1713         if part.force_field_1.type != 'NONE':
1714             layout.label(text="")
1715
1716         split = layout.split(percentage=0.2)
1717         split.label(text="Type 2:")
1718         split.prop(part.force_field_2, "type", text="")
1719         basic_force_field_settings_ui(self, context, part.force_field_2)
1720         if part.force_field_2.type != 'NONE':
1721             layout.label(text="Falloff:")
1722         basic_force_field_falloff_ui(self, context, part.force_field_2)
1723
1724
1725 class PARTICLE_PT_vertexgroups(ParticleButtonsPanel, Panel):
1726     bl_label = "Vertex Groups"
1727     bl_options = {'DEFAULT_CLOSED'}
1728     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1729
1730     @classmethod
1731     def poll(cls, context):
1732         if context.particle_system is None:
1733             return False
1734         return particle_panel_poll(cls, context)
1735
1736     def draw(self, context):
1737         layout = self.layout
1738         layout.use_property_split = True
1739
1740         ob = context.object
1741         psys = context.particle_system
1742
1743         col = layout.column()
1744         row = col.row(align=True)
1745         row.prop_search(psys, "vertex_group_density", ob, "vertex_groups", text="Density")
1746         row.prop(psys, "invert_vertex_group_density", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1747
1748         row = col.row(align=True)
1749         row.prop_search(psys, "vertex_group_length", ob, "vertex_groups", text="Length")
1750         row.prop(psys, "invert_vertex_group_length", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1751
1752         row = col.row(align=True)
1753         row.prop_search(psys, "vertex_group_clump", ob, "vertex_groups", text="Clump")
1754         row.prop(psys, "invert_vertex_group_clump", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1755
1756         row = col.row(align=True)
1757         row.prop_search(psys, "vertex_group_kink", ob, "vertex_groups", text="Kink")
1758         row.prop(psys, "invert_vertex_group_kink", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1759
1760         row = col.row(align=True)
1761         row.prop_search(psys, "vertex_group_roughness_1", ob, "vertex_groups", text="Roughness 1")
1762         row.prop(psys, "invert_vertex_group_roughness_1", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1763
1764         row = col.row(align=True)
1765         row.prop_search(psys, "vertex_group_roughness_2", ob, "vertex_groups", text="Roughness 2")
1766         row.prop(psys, "invert_vertex_group_roughness_2", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1767
1768         row = col.row(align=True)
1769         row.prop_search(psys, "vertex_group_roughness_end", ob, "vertex_groups", text="Roughness End")
1770         row.prop(psys, "invert_vertex_group_roughness_end", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1771
1772         row = col.row(align=True)
1773         row.prop_search(psys, "vertex_group_twist", ob, "vertex_groups", text="Twist")
1774         row.prop(psys, "invert_vertex_group_twist", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1775
1776         # Commented out vertex groups don't work and are still waiting for better implementation
1777         # row = layout.row()
1778         # row.prop_search(psys, "vertex_group_velocity", ob, "vertex_groups", text="Velocity")
1779         # row.prop(psys, "invert_vertex_group_velocity", text="")
1780
1781         # row = layout.row()
1782         # row.prop_search(psys, "vertex_group_size", ob, "vertex_groups", text="Size")
1783         # row.prop(psys, "invert_vertex_group_size", text="")
1784
1785         # row = layout.row()
1786         # row.prop_search(psys, "vertex_group_tangent", ob, "vertex_groups", text="Tangent")
1787         # row.prop(psys, "invert_vertex_group_tangent", text="")
1788
1789         # row = layout.row()
1790         # row.prop_search(psys, "vertex_group_rotation", ob, "vertex_groups", text="Rotation")
1791         # row.prop(psys, "invert_vertex_group_rotation", text="")
1792
1793         # row = layout.row()
1794         # row.prop_search(psys, "vertex_group_field", ob, "vertex_groups", text="Field")
1795         # row.prop(psys, "invert_vertex_group_field", text="")
1796
1797
1798 class PARTICLE_PT_textures(ParticleButtonsPanel, Panel):
1799     bl_label = "Textures"
1800     bl_options = {'DEFAULT_CLOSED'}
1801     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1802
1803     @classmethod
1804     def poll(cls, context):
1805         if context.particle_system is None:
1806             return False
1807         return particle_panel_poll(cls, context)
1808
1809     def draw(self, context):
1810         layout = self.layout
1811
1812         psys = context.particle_system
1813         part = psys.settings
1814
1815         row = layout.row()
1816         row.template_list("TEXTURE_UL_texslots", "", part, "texture_slots", part, "active_texture_index", rows=2)
1817
1818         col = row.column(align=True)
1819         col.operator("texture.slot_move", text="", icon='TRIA_UP').type = 'UP'
1820         col.operator("texture.slot_move", text="", icon='TRIA_DOWN').type = 'DOWN'
1821         col.menu("TEXTURE_MT_specials", icon='DOWNARROW_HLT', text="")
1822
1823         if not part.active_texture:
1824             layout.template_ID(part, "active_texture", new="texture.new")
1825         else:
1826             slot = part.texture_slots[part.active_texture_index]
1827             layout.template_ID(slot, "texture", new="texture.new")
1828
1829
1830 class PARTICLE_PT_hair_shape(ParticleButtonsPanel, Panel):
1831     bl_label = "Hair Shape"
1832     bl_options = {'DEFAULT_CLOSED'}
1833     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1834
1835     @classmethod
1836     def poll(cls, context):
1837         if context.particle_system is None:
1838             return False
1839         return particle_panel_poll(cls, context)
1840
1841     def draw(self, context):
1842         layout = self.layout
1843         layout.use_property_split = True
1844
1845         psys = context.particle_system
1846         part = psys.settings
1847
1848         layout.prop(part, "shape", text="Strand Shape")
1849
1850         col = layout.column(align=True)
1851         col.prop(part, "root_radius", text="Radius Root")
1852         col.prop(part, "tip_radius", text="Tip")
1853
1854         col = layout.column()
1855         col.prop(part, "radius_scale", text="Radius Scaling")
1856         col.prop(part, "use_close_tip")
1857
1858
1859 class PARTICLE_PT_custom_props(ParticleButtonsPanel, PropertyPanel, Panel):
1860     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
1861     _context_path = "particle_system.settings"
1862     _property_type = bpy.types.ParticleSettings
1863
1864
1865 classes = (
1866     PARTICLE_MT_specials,
1867     PARTICLE_MT_hair_dynamics_presets,
1868     PARTICLE_UL_particle_systems,
1869     PARTICLE_PT_context_particles,
1870     PARTICLE_PT_emission,
1871     PARTICLE_PT_emission_source,
1872     PARTICLE_PT_hair_dynamics,
1873     PARTICLE_PT_hair_dynamics_structure,
1874     PARTICLE_PT_hair_dynamics_volume,
1875     PARTICLE_PT_cache,
1876     PARTICLE_PT_velocity,
1877     PARTICLE_PT_rotation,
1878     PARTICLE_PT_rotation_angular_velocity,
1879     PARTICLE_PT_physics,
1880     PARTICLE_PT_physics_forces,
1881     PARTICLE_PT_physics_deflection,
1882     PARTICLE_PT_physics_integration,
1883     PARTICLE_PT_boidbrain,
1884     PARTICLE_PT_render,
1885     PARTICLE_PT_render_line,
1886     PARTICLE_PT_render_path,
1887     PARTICLE_PT_render_path_timing,
1888     PARTICLE_PT_render_object,
1889     PARTICLE_PT_render_collection,
1890     PARTICLE_PT_render_collection_use_count,
1891     PARTICLE_PT_render_billboards_tilt,
1892     PARTICLE_PT_render_billboards_uv,
1893     PARTICLE_PT_render_trails,
1894     PARTICLE_PT_render_extra,
1895     PARTICLE_PT_draw,
1896     PARTICLE_PT_children,
1897     PARTICLE_PT_children_parting,
1898     PARTICLE_PT_children_clumping,
1899     PARTICLE_PT_children_roughness,
1900     PARTICLE_PT_children_kink,
1901     PARTICLE_PT_hair_shape,
1902     PARTICLE_PT_field_weights,
1903     PARTICLE_PT_force_fields,
1904     PARTICLE_PT_vertexgroups,
1905     PARTICLE_PT_textures,
1906     PARTICLE_PT_custom_props,
1907 )
1908
1909 if __name__ == "__main__":  # only for live edit.
1910     from bpy.utils import register_class
1911     for cls in classes:
1912         register_class(cls)