Moving classes to separate listing broke panel order
[blender-staging.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 bl_ui.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.scene.render.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_GAME'}
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_GAME'}
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     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
112         ob = data
113         psys = item
114
115         if self.layout_type in {'DEFAULT', 'COMPACT'}:
116             md = find_modifier(ob, psys)
117
118             layout.prop(psys, "name", text="", emboss=False, icon_value=icon)
119             if md:
120                 layout.prop(md, "show_render", emboss=False, icon_only=True, icon='RESTRICT_RENDER_OFF' if md.show_render else 'RESTRICT_RENDER_ON')
121                 layout.prop(md, "show_viewport", emboss=False, icon_only=True, icon='RESTRICT_VIEW_OFF' if md.show_viewport else 'RESTRICT_VIEW_ON')
122
123         elif self.layout_type == 'GRID':
124             layout.alignment = 'CENTER'
125             layout.label(text="", icon_value=icon)
126
127
128 class PARTICLE_PT_context_particles(ParticleButtonsPanel, Panel):
129     bl_label = ""
130     bl_options = {'HIDE_HEADER'}
131     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'}
132
133     @classmethod
134     def poll(cls, context):
135         engine = context.scene.render.engine
136         return (context.particle_system or context.object or context.space_data.pin_id) and (engine in cls.COMPAT_ENGINES)
137
138     def draw(self, context):
139         layout = self.layout
140
141         if context.scene.render.engine == 'BLENDER_GAME':
142             layout.label("Not available in the Game Engine")
143             return
144
145         ob = context.object
146         psys = context.particle_system
147         part = 0
148
149         if ob:
150             row = layout.row()
151
152             row.template_list("PARTICLE_UL_particle_systems", "particle_systems", ob, "particle_systems",
153                               ob.particle_systems, "active_index", rows=1)
154
155             col = row.column(align=True)
156             col.operator("object.particle_system_add", icon='ZOOMIN', text="")
157             col.operator("object.particle_system_remove", icon='ZOOMOUT', text="")
158             col.menu("PARTICLE_MT_specials", icon='DOWNARROW_HLT', text="")
159
160         if psys is None:
161             part = particle_get_settings(context)
162
163             layout.operator("object.particle_system_add", icon='ZOOMIN', text="New")
164
165             if part is None:
166                 return
167
168             layout.template_ID(context.space_data, "pin_id")
169
170             if part.is_fluid:
171                 layout.label(text="Settings used for fluid")
172                 return
173
174             layout.prop(part, "type", text="Type")
175
176         elif not psys.settings:
177             split = layout.split(percentage=0.32)
178
179             col = split.column()
180             col.label(text="Settings:")
181
182             col = split.column()
183             col.template_ID(psys, "settings", new="particle.new")
184         else:
185             part = psys.settings
186
187             split = layout.split(percentage=0.32)
188             col = split.column()
189             if part.is_fluid is False:
190                 col.label(text="Settings:")
191                 col.label(text="Type:")
192
193             col = split.column()
194             if part.is_fluid is False:
195                 row = col.row()
196                 row.enabled = particle_panel_enabled(context, psys)
197                 row.template_ID(psys, "settings", new="particle.new")
198
199             if part.is_fluid:
200                 layout.label(text=iface_("%d fluid particles for this frame") % part.count, translate=False)
201                 return
202
203             row = col.row()
204             row.enabled = particle_panel_enabled(context, psys)
205             row.prop(part, "type", text="")
206             row.prop(psys, "seed")
207
208         if part:
209             split = layout.split(percentage=0.65)
210             if part.type == 'HAIR':
211                 if psys is not None and psys.is_edited:
212                     split.operator("particle.edited_clear", text="Free Edit")
213                 else:
214                     row = split.row()
215                     row.enabled = particle_panel_enabled(context, psys)
216                     row.prop(part, "regrow_hair")
217                     row.prop(part, "use_advanced_hair")
218                 row = split.row()
219                 row.enabled = particle_panel_enabled(context, psys)
220                 row.prop(part, "hair_step")
221                 if psys is not None and psys.is_edited:
222                     if psys.is_global_hair:
223                         row = layout.row(align=True)
224                         row.operator("particle.connect_hair").all = False
225                         row.operator("particle.connect_hair", text="Connect All").all = True
226                     else:
227                         row = layout.row(align=True)
228                         row.operator("particle.disconnect_hair").all = False
229                         row.operator("particle.disconnect_hair", text="Disconnect All").all = True
230             elif psys is not None and part.type == 'REACTOR':
231                 split.enabled = particle_panel_enabled(context, psys)
232                 split.prop(psys, "reactor_target_object")
233                 split.prop(psys, "reactor_target_particle_system", text="Particle System")
234
235
236 class PARTICLE_PT_emission(ParticleButtonsPanel, Panel):
237     bl_label = "Emission"
238     COMPAT_ENGINES = {'BLENDER_RENDER'}
239
240     @classmethod
241     def poll(cls, context):
242         psys = context.particle_system
243         settings = particle_get_settings(context)
244
245         if settings is None:
246             return False
247         if settings.is_fluid:
248             return False
249         if particle_panel_poll(PARTICLE_PT_emission, context):
250             return psys is None or not context.particle_system.point_cache.use_external
251         return False
252
253     def draw(self, context):
254         layout = self.layout
255
256         psys = context.particle_system
257         part = particle_get_settings(context)
258
259         layout.enabled = particle_panel_enabled(context, psys) and (psys is None or not psys.has_multiple_caches)
260
261         row = layout.row()
262         row.active = part.emit_from == 'VERT' or part.distribution != 'GRID'
263         row.prop(part, "count")
264
265         if part.type == 'HAIR':
266             row.prop(part, "hair_length")
267             if not part.use_advanced_hair:
268                 row = layout.row()
269                 row.prop(part, "use_modifier_stack")
270                 return
271
272         if part.type != 'HAIR':
273             split = layout.split()
274
275             col = split.column(align=True)
276             col.prop(part, "frame_start")
277             col.prop(part, "frame_end")
278
279             col = split.column(align=True)
280             col.prop(part, "lifetime")
281             col.prop(part, "lifetime_random", slider=True)
282
283         layout.label(text="Emit From:")
284         layout.prop(part, "emit_from", expand=True)
285
286         row = layout.row()
287         if part.emit_from == 'VERT':
288             row.prop(part, "use_emit_random")
289         elif part.distribution == 'GRID':
290             row.prop(part, "invert_grid")
291             row.prop(part, "hexagonal_grid")
292         else:
293             row.prop(part, "use_emit_random")
294             row.prop(part, "use_even_distribution")
295
296         if part.emit_from == 'FACE' or part.emit_from == 'VOLUME':
297             layout.prop(part, "distribution", expand=True)
298
299             row = layout.row()
300             if part.distribution == 'JIT':
301                 row.prop(part, "userjit", text="Particles/Face")
302                 row.prop(part, "jitter_factor", text="Jittering Amount", slider=True)
303             elif part.distribution == 'GRID':
304                 row.prop(part, "grid_resolution")
305                 row.prop(part, "grid_random", text="Random", slider=True)
306
307         row = layout.row()
308         row.prop(part, "use_modifier_stack")
309
310
311 class PARTICLE_PT_hair_dynamics(ParticleButtonsPanel, Panel):
312     bl_label = "Hair dynamics"
313     bl_options = {'DEFAULT_CLOSED'}
314     COMPAT_ENGINES = {'BLENDER_RENDER'}
315
316     @classmethod
317     def poll(cls, context):
318         psys = context.particle_system
319         engine = context.scene.render.engine
320         if psys is None:
321             return False
322         if psys.settings is None:
323             return False
324         return psys.settings.type == 'HAIR' and (engine in cls.COMPAT_ENGINES)
325
326     def draw_header(self, context):
327         psys = context.particle_system
328         self.layout.prop(psys, "use_hair_dynamics", text="")
329
330     def draw(self, context):
331         layout = self.layout
332
333         psys = context.particle_system
334
335         if not psys.cloth:
336             return
337
338         cloth_md = psys.cloth
339         cloth = cloth_md.settings
340         result = cloth_md.solver_result
341
342         layout.enabled = psys.use_hair_dynamics and psys.point_cache.is_baked is False
343
344         row = layout.row(align=True)
345         row.menu("PARTICLE_MT_hair_dynamics_presets", text=bpy.types.PARTICLE_MT_hair_dynamics_presets.bl_label)
346         row.operator("particle.hair_dynamics_preset_add", text="", icon='ZOOMIN')
347         row.operator("particle.hair_dynamics_preset_add", text="", icon='ZOOMOUT').remove_active = True
348
349         split = layout.column()
350
351         col = split.column()
352         col.label(text="Structure")
353         col.prop(cloth, "mass")
354         sub = col.column(align=True)
355         subsub = sub.row(align=True)
356         subsub.prop(cloth, "bending_stiffness", text="Stiffness")
357         subsub.prop(psys.settings, "bending_random", text="Random")
358         sub.prop(cloth, "bending_damping", text="Damping")
359         # XXX has no noticeable effect with stiff hair structure springs
360         #col.prop(cloth, "spring_damping", text="Damping")
361
362         split.separator()
363
364         col = split.column()
365         col.label(text="Volume")
366         col.prop(cloth, "air_damping", text="Air Drag")
367         col.prop(cloth, "internal_friction", slider=True)
368         sub = col.column(align=True)
369         sub.prop(cloth, "density_target", text="Density Target")
370         sub.prop(cloth, "density_strength", slider=True, text="Strength")
371         col.prop(cloth, "voxel_cell_size")
372
373         split.separator()
374
375         col = split.column()
376         col.label(text="Pinning")
377         col.prop(cloth, "pin_stiffness", text="Goal Strength")
378
379         split.separator()
380
381         col = split.column()
382         col.label(text="Quality:")
383         col.prop(cloth, "quality", text="Steps", slider=True)
384
385         row = col.row()
386         row.prop(psys.settings, "show_hair_grid", text="HairGrid")
387
388         if result:
389             box = layout.box()
390
391             if not result.status:
392                 label = " "
393                 icon = 'NONE'
394             elif result.status == {'SUCCESS'}:
395                 label = "Success"
396                 icon = 'NONE'
397             elif result.status - {'SUCCESS'} == {'NO_CONVERGENCE'}:
398                 label = "No Convergence"
399                 icon = 'ERROR'
400             else:
401                 label = "ERROR"
402                 icon = 'ERROR'
403             box.label(label, icon=icon)
404             box.label("Iterations: %d .. %d (avg. %d)" % (result.min_iterations, result.max_iterations, result.avg_iterations))
405             box.label("Error: %.5f .. %.5f (avg. %.5f)" % (result.min_error, result.max_error, result.avg_error))
406
407
408 class PARTICLE_PT_cache(ParticleButtonsPanel, Panel):
409     bl_label = "Cache"
410     bl_options = {'DEFAULT_CLOSED'}
411     COMPAT_ENGINES = {'BLENDER_RENDER'}
412
413     @classmethod
414     def poll(cls, context):
415         psys = context.particle_system
416         engine = context.scene.render.engine
417         if psys is None:
418             return False
419         if psys.settings is None:
420             return False
421         if psys.settings.is_fluid:
422             return False
423         phystype = psys.settings.physics_type
424         if phystype == 'NO' or phystype == 'KEYED':
425             return False
426         return (psys.settings.type in {'EMITTER', 'REACTOR'} or (psys.settings.type == 'HAIR' and (psys.use_hair_dynamics or psys.point_cache.is_baked))) and engine in cls.COMPAT_ENGINES
427
428     def draw(self, context):
429         psys = context.particle_system
430
431         point_cache_ui(self, context, psys.point_cache, True, 'HAIR' if (psys.settings.type == 'HAIR') else 'PSYS')
432
433
434 class PARTICLE_PT_velocity(ParticleButtonsPanel, Panel):
435     bl_label = "Velocity"
436     COMPAT_ENGINES = {'BLENDER_RENDER'}
437
438     @classmethod
439     def poll(cls, context):
440         if particle_panel_poll(PARTICLE_PT_velocity, context):
441             psys = context.particle_system
442             settings = particle_get_settings(context)
443
444             if settings.type == 'HAIR' and not settings.use_advanced_hair:
445                 return False
446             return settings.physics_type != 'BOIDS' and (psys is None or not psys.point_cache.use_external)
447         else:
448             return False
449
450     def draw(self, context):
451         layout = self.layout
452
453         psys = context.particle_system
454         part = particle_get_settings(context)
455
456         layout.enabled = particle_panel_enabled(context, psys)
457
458         split = layout.split()
459
460         col = split.column()
461         col.label(text="Emitter Geometry:")
462         col.prop(part, "normal_factor")
463         sub = col.column(align=True)
464         sub.prop(part, "tangent_factor")
465         sub.prop(part, "tangent_phase", slider=True)
466
467         col = split.column()
468         col.label(text="Emitter Object:")
469         col.prop(part, "object_align_factor", text="")
470
471         layout.label(text="Other:")
472         row = layout.row()
473         if part.emit_from == 'PARTICLE':
474             row.prop(part, "particle_factor")
475         else:
476             row.prop(part, "object_factor", slider=True)
477         row.prop(part, "factor_random")
478
479         #if part.type=='REACTOR':
480         #    sub.prop(part, "reactor_factor")
481         #    sub.prop(part, "reaction_shape", slider=True)
482
483
484 class PARTICLE_PT_rotation(ParticleButtonsPanel, Panel):
485     bl_label = "Rotation"
486     bl_options = {'DEFAULT_CLOSED'}
487     COMPAT_ENGINES = {'BLENDER_RENDER'}
488
489     @classmethod
490     def poll(cls, context):
491         if particle_panel_poll(PARTICLE_PT_rotation, context):
492             psys = context.particle_system
493             settings = particle_get_settings(context)
494
495             if settings.type == 'HAIR' and not settings.use_advanced_hair:
496                 return False
497             return settings.physics_type != 'BOIDS' and (psys is None or not psys.point_cache.use_external)
498         else:
499             return False
500
501     def draw_header(self, context):
502         psys = context.particle_system
503         if psys:
504             part = psys.settings
505         else:
506             part = context.space_data.pin_id
507
508         self.layout.prop(part, "use_rotations", text="")
509
510     def draw(self, context):
511         layout = self.layout
512
513         psys = context.particle_system
514         if psys:
515             part = psys.settings
516         else:
517             part = context.space_data.pin_id
518
519         layout.enabled = particle_panel_enabled(context, psys) and part.use_rotations
520
521         layout.label(text="Initial Orientation:")
522
523         split = layout.split()
524
525         col = split.column(align=True)
526         col.prop(part, "rotation_mode", text="")
527         col.prop(part, "rotation_factor_random", slider=True, text="Random")
528
529         col = split.column(align=True)
530         col.prop(part, "phase_factor", slider=True)
531         col.prop(part, "phase_factor_random", text="Random", slider=True)
532
533         if part.type != 'HAIR':
534             layout.label(text="Angular Velocity:")
535
536             split = layout.split()
537
538             col = split.column(align=True)
539             col.prop(part, "angular_velocity_mode", text="")
540             sub = col.column(align=True)
541             sub.active = part.angular_velocity_mode != 'NONE'
542             sub.prop(part, "angular_velocity_factor", text="")
543
544             col = split.column()
545             col.prop(part, "use_dynamic_rotation")
546
547
548 class PARTICLE_PT_physics(ParticleButtonsPanel, Panel):
549     bl_label = "Physics"
550     COMPAT_ENGINES = {'BLENDER_RENDER'}
551
552     @classmethod
553     def poll(cls, context):
554         if particle_panel_poll(PARTICLE_PT_physics, context):
555             psys = context.particle_system
556             settings = particle_get_settings(context)
557
558             if settings.type == 'HAIR' and not settings.use_advanced_hair:
559                 return False
560             return psys is None or not psys.point_cache.use_external
561         else:
562             return False
563
564     def draw(self, context):
565         layout = self.layout
566
567         psys = context.particle_system
568         part = particle_get_settings(context)
569
570         layout.enabled = particle_panel_enabled(context, psys)
571
572         layout.prop(part, "physics_type", expand=True)
573
574         row = layout.row()
575         col = row.column(align=True)
576         col.prop(part, "particle_size")
577         col.prop(part, "size_random", slider=True)
578
579         if part.physics_type != 'NO':
580             col = row.column(align=True)
581             col.prop(part, "mass")
582             col.prop(part, "use_multiply_size_mass", text="Multiply mass with size")
583
584         if part.physics_type in {'NEWTON', 'FLUID'}:
585             split = layout.split()
586
587             col = split.column()
588             col.label(text="Forces:")
589             col.prop(part, "brownian_factor")
590             col.prop(part, "drag_factor", slider=True)
591             col.prop(part, "damping", slider=True)
592
593             col = split.column()
594             col.label(text="Integration:")
595             col.prop(part, "integrator", text="")
596             col.prop(part, "timestep")
597             sub = col.row()
598             sub.prop(part, "subframes")
599             supports_courant = part.physics_type == 'FLUID'
600             subsub = sub.row()
601             subsub.enabled = supports_courant
602             subsub.prop(part, "use_adaptive_subframes", text="")
603             if supports_courant and part.use_adaptive_subframes:
604                 col.prop(part, "courant_target", text="Threshold")
605
606             row = layout.row()
607             row.prop(part, "use_size_deflect")
608             row.prop(part, "use_die_on_collision")
609
610             layout.prop(part, "collision_group")
611
612             if part.physics_type == 'FLUID':
613                 fluid = part.fluid
614
615                 split = layout.split()
616                 sub = split.row()
617                 sub.prop(fluid, "solver", expand=True)
618
619                 split = layout.split()
620
621                 col = split.column()
622                 col.label(text="Fluid properties:")
623                 col.prop(fluid, "stiffness", text="Stiffness")
624                 col.prop(fluid, "linear_viscosity", text="Viscosity")
625                 col.prop(fluid, "buoyancy", text="Buoyancy", slider=True)
626
627                 col = split.column()
628                 col.label(text="Advanced:")
629
630                 if fluid.solver == 'DDR':
631                     sub = col.row()
632                     sub.prop(fluid, "repulsion", slider=fluid.factor_repulsion)
633                     sub.prop(fluid, "factor_repulsion", text="")
634
635                     sub = col.row()
636                     sub.prop(fluid, "stiff_viscosity", slider=fluid.factor_stiff_viscosity)
637                     sub.prop(fluid, "factor_stiff_viscosity", text="")
638
639                 sub = col.row()
640                 sub.prop(fluid, "fluid_radius", slider=fluid.factor_radius)
641                 sub.prop(fluid, "factor_radius", text="")
642
643                 sub = col.row()
644                 sub.prop(fluid, "rest_density", slider=fluid.use_factor_density)
645                 sub.prop(fluid, "use_factor_density", text="")
646
647                 if fluid.solver == 'CLASSICAL':
648                     # With the classical solver, it is possible to calculate the
649                     # spacing between particles when the fluid is at rest. This
650                     # makes it easier to set stable initial conditions.
651                     particle_volume = part.mass / fluid.rest_density
652                     spacing = pow(particle_volume, 1.0 / 3.0)
653                     sub = col.row()
654                     sub.label(text="Spacing: %g" % spacing)
655
656                 elif fluid.solver == 'DDR':
657                     split = layout.split()
658
659                     col = split.column()
660                     col.label(text="Springs:")
661                     col.prop(fluid, "spring_force", text="Force")
662                     col.prop(fluid, "use_viscoelastic_springs")
663                     sub = col.column(align=True)
664                     sub.active = fluid.use_viscoelastic_springs
665                     sub.prop(fluid, "yield_ratio", slider=True)
666                     sub.prop(fluid, "plasticity", slider=True)
667
668                     col = split.column()
669                     col.label(text="Advanced:")
670                     sub = col.row()
671                     sub.prop(fluid, "rest_length", slider=fluid.factor_rest_length)
672                     sub.prop(fluid, "factor_rest_length", text="")
673                     col.label(text="")
674                     sub = col.column()
675                     sub.active = fluid.use_viscoelastic_springs
676                     sub.prop(fluid, "use_initial_rest_length")
677                     sub.prop(fluid, "spring_frames", text="Frames")
678
679         elif part.physics_type == 'KEYED':
680             split = layout.split()
681             sub = split.column()
682
683             row = layout.row()
684             col = row.column()
685             col.active = not psys.use_keyed_timing
686             col.prop(part, "keyed_loops", text="Loops")
687             if psys:
688                 row.prop(psys, "use_keyed_timing", text="Use Timing")
689
690             layout.label(text="Keys:")
691         elif part.physics_type == 'BOIDS':
692             boids = part.boids
693
694             row = layout.row()
695             row.prop(boids, "use_flight")
696             row.prop(boids, "use_land")
697             row.prop(boids, "use_climb")
698
699             split = layout.split()
700
701             col = split.column(align=True)
702             col.active = boids.use_flight
703             col.prop(boids, "air_speed_max")
704             col.prop(boids, "air_speed_min", slider=True)
705             col.prop(boids, "air_acc_max", slider=True)
706             col.prop(boids, "air_ave_max", slider=True)
707             col.prop(boids, "air_personal_space")
708             row = col.row(align=True)
709             row.active = (boids.use_land or boids.use_climb) and boids.use_flight
710             row.prop(boids, "land_smooth")
711
712             col = split.column(align=True)
713             col.active = boids.use_land or boids.use_climb
714             col.prop(boids, "land_speed_max")
715             col.prop(boids, "land_jump_speed")
716             col.prop(boids, "land_acc_max", slider=True)
717             col.prop(boids, "land_ave_max", slider=True)
718             col.prop(boids, "land_personal_space")
719             col.prop(boids, "land_stick_force")
720
721             layout.prop(part, "collision_group")
722
723             split = layout.split()
724
725             col = split.column(align=True)
726             col.label(text="Battle:")
727             col.prop(boids, "health")
728             col.prop(boids, "strength")
729             col.prop(boids, "aggression")
730             col.prop(boids, "accuracy")
731             col.prop(boids, "range")
732
733             col = split.column()
734             col.label(text="Misc:")
735             col.prop(boids, "bank", slider=True)
736             col.prop(boids, "pitch", slider=True)
737             col.prop(boids, "height", slider=True)
738
739         if psys and part.physics_type in {'KEYED', 'BOIDS', 'FLUID'}:
740             if part.physics_type == 'BOIDS':
741                 layout.label(text="Relations:")
742             elif part.physics_type == 'FLUID':
743                 layout.label(text="Fluid interaction:")
744
745             row = layout.row()
746             row.template_list("UI_UL_list", "particle_targets", psys, "targets", psys, "active_particle_target_index", rows=4)
747
748             col = row.column()
749             sub = col.row()
750             subsub = sub.column(align=True)
751             subsub.operator("particle.new_target", icon='ZOOMIN', text="")
752             subsub.operator("particle.target_remove", icon='ZOOMOUT', text="")
753             sub = col.row()
754             subsub = sub.column(align=True)
755             subsub.operator("particle.target_move_up", icon='TRIA_UP', text="")
756             subsub.operator("particle.target_move_down", icon='TRIA_DOWN', text="")
757
758             key = psys.active_particle_target
759             if key:
760                 row = layout.row()
761                 if part.physics_type == 'KEYED':
762                     col = row.column()
763                     #doesn't work yet
764                     #col.alert = key.valid
765                     col.prop(key, "object", text="")
766                     col.prop(key, "system", text="System")
767                     col = row.column()
768                     col.active = psys.use_keyed_timing
769                     col.prop(key, "time")
770                     col.prop(key, "duration")
771                 elif part.physics_type == 'BOIDS':
772                     sub = row.row()
773                     #doesn't work yet
774                     #sub.alert = key.valid
775                     sub.prop(key, "object", text="")
776                     sub.prop(key, "system", text="System")
777
778                     layout.prop(key, "alliance", expand=True)
779                 elif part.physics_type == 'FLUID':
780                     sub = row.row()
781                     #doesn't work yet
782                     #sub.alert = key.valid
783                     sub.prop(key, "object", text="")
784                     sub.prop(key, "system", text="System")
785
786
787 class PARTICLE_PT_boidbrain(ParticleButtonsPanel, Panel):
788     bl_label = "Boid Brain"
789     COMPAT_ENGINES = {'BLENDER_RENDER'}
790
791     @classmethod
792     def poll(cls, context):
793         psys = context.particle_system
794         settings = particle_get_settings(context)
795         engine = context.scene.render.engine
796
797         if settings is None:
798             return False
799         if psys is not None and psys.point_cache.use_external:
800             return False
801         return settings.physics_type == 'BOIDS' and engine in cls.COMPAT_ENGINES
802
803     def draw(self, context):
804         layout = self.layout
805
806         boids = particle_get_settings(context).boids
807
808         layout.enabled = particle_panel_enabled(context, context.particle_system)
809
810         # Currently boids can only use the first state so these are commented out for now.
811         #row = layout.row()
812         #row.template_list("UI_UL_list", "particle_boids", boids, "states",
813         #                  boids, "active_boid_state_index", compact="True")
814         #col = row.row()
815         #sub = col.row(align=True)
816         #sub.operator("boid.state_add", icon='ZOOMIN', text="")
817         #sub.operator("boid.state_del", icon='ZOOMOUT', text="")
818         #sub = row.row(align=True)
819         #sub.operator("boid.state_move_up", icon='TRIA_UP', text="")
820         #sub.operator("boid.state_move_down", icon='TRIA_DOWN', text="")
821
822         state = boids.active_boid_state
823
824         #layout.prop(state, "name", text="State name")
825
826         row = layout.row()
827         row.prop(state, "ruleset_type")
828         if state.ruleset_type == 'FUZZY':
829             row.prop(state, "rule_fuzzy", slider=True)
830         else:
831             row.label(text="")
832
833         row = layout.row()
834         row.template_list("UI_UL_list", "particle_boids_rules", state, "rules", state, "active_boid_rule_index", rows=4)
835
836         col = row.column()
837         sub = col.row()
838         subsub = sub.column(align=True)
839         subsub.operator_menu_enum("boid.rule_add", "type", icon='ZOOMIN', text="")
840         subsub.operator("boid.rule_del", icon='ZOOMOUT', text="")
841         sub = col.row()
842         subsub = sub.column(align=True)
843         subsub.operator("boid.rule_move_up", icon='TRIA_UP', text="")
844         subsub.operator("boid.rule_move_down", icon='TRIA_DOWN', text="")
845
846         rule = state.active_boid_rule
847
848         if rule:
849             row = layout.row()
850             row.prop(rule, "name", text="")
851             #somebody make nice icons for boids here please! -jahka
852             row.prop(rule, "use_in_air", icon='TRIA_UP', text="")
853             row.prop(rule, "use_on_land", icon='TRIA_DOWN', text="")
854
855             row = layout.row()
856
857             if rule.type == 'GOAL':
858                 row.prop(rule, "object")
859                 row = layout.row()
860                 row.prop(rule, "use_predict")
861             elif rule.type == 'AVOID':
862                 row.prop(rule, "object")
863                 row = layout.row()
864                 row.prop(rule, "use_predict")
865                 row.prop(rule, "fear_factor")
866             elif rule.type == 'FOLLOW_PATH':
867                 row.label(text="Not yet functional")
868             elif rule.type == 'AVOID_COLLISION':
869                 row.prop(rule, "use_avoid")
870                 row.prop(rule, "use_avoid_collision")
871                 row.prop(rule, "look_ahead")
872             elif rule.type == 'FOLLOW_LEADER':
873                 row.prop(rule, "object", text="")
874                 row.prop(rule, "distance")
875                 row = layout.row()
876                 row.prop(rule, "use_line")
877                 sub = row.row()
878                 sub.active = rule.line
879                 sub.prop(rule, "queue_count")
880             elif rule.type == 'AVERAGE_SPEED':
881                 row.prop(rule, "speed", slider=True)
882                 row.prop(rule, "wander", slider=True)
883                 row.prop(rule, "level", slider=True)
884             elif rule.type == 'FIGHT':
885                 row.prop(rule, "distance")
886                 row.prop(rule, "flee_distance")
887
888
889 class PARTICLE_PT_render(ParticleButtonsPanel, Panel):
890     bl_label = "Render"
891     COMPAT_ENGINES = {'BLENDER_RENDER'}
892
893     @classmethod
894     def poll(cls, context):
895         settings = particle_get_settings(context)
896         engine = context.scene.render.engine
897         if settings is None:
898             return False
899
900         return engine in cls.COMPAT_ENGINES
901
902     def draw(self, context):
903         layout = self.layout
904
905         psys = context.particle_system
906         part = particle_get_settings(context)
907
908         if psys:
909             row = layout.row()
910             if part.render_type in {'OBJECT', 'GROUP'}:
911                 row.enabled = False
912             row.prop(part, "material_slot", text="")
913             row.prop(psys, "parent")
914
915         split = layout.split()
916
917         col = split.column()
918         col.prop(part, "use_render_emitter")
919         col.prop(part, "use_parent_particles")
920
921         col = split.column()
922         col.prop(part, "show_unborn")
923         col.prop(part, "use_dead")
924
925         layout.prop(part, "render_type", expand=True)
926
927         split = layout.split()
928
929         col = split.column()
930
931         if part.render_type == 'LINE':
932             col.prop(part, "line_length_tail")
933             col.prop(part, "line_length_head")
934
935             split.prop(part, "use_velocity_length")
936         elif part.render_type == 'PATH':
937             col.prop(part, "use_strand_primitive")
938             sub = col.column()
939             sub.active = (part.use_strand_primitive is False)
940             sub.prop(part, "use_render_adaptive")
941             sub = col.column()
942             sub.active = part.use_render_adaptive or part.use_strand_primitive is True
943             sub.prop(part, "adaptive_angle")
944             sub = col.column()
945             sub.active = (part.use_render_adaptive is True and part.use_strand_primitive is False)
946             sub.prop(part, "adaptive_pixel")
947             col.prop(part, "use_hair_bspline")
948             col.prop(part, "render_step", text="Steps")
949
950             col = split.column()
951             col.label(text="Timing:")
952             col.prop(part, "use_absolute_path_time")
953
954             if part.type == 'HAIR' or psys.point_cache.is_baked:
955                 col.prop(part, "path_start", text="Start", slider=not part.use_absolute_path_time)
956             else:
957                 col.prop(part, "trail_count")
958
959             col.prop(part, "path_end", text="End", slider=not part.use_absolute_path_time)
960             col.prop(part, "length_random", text="Random", slider=True)
961
962             row = layout.row()
963             col = row.column()
964
965             if part.type == 'HAIR' and part.use_strand_primitive is True and part.child_type == 'INTERPOLATED':
966                 layout.prop(part, "use_simplify")
967                 if part.use_simplify is True:
968                     row = layout.row()
969                     row.prop(part, "simplify_refsize")
970                     row.prop(part, "simplify_rate")
971                     row.prop(part, "simplify_transition")
972                     row = layout.row()
973                     row.prop(part, "use_simplify_viewport")
974                     sub = row.row()
975                     sub.active = part.use_simplify_viewport is True
976                     sub.prop(part, "simplify_viewport")
977
978         elif part.render_type == 'OBJECT':
979             col.prop(part, "dupli_object")
980             sub = col.row()
981             sub.prop(part, "use_global_dupli")
982             sub.prop(part, "use_rotation_dupli")
983             sub.prop(part, "use_scale_dupli")
984         elif part.render_type == 'GROUP':
985             col.prop(part, "dupli_group")
986             split = layout.split()
987
988             col = split.column()
989             col.prop(part, "use_whole_group")
990             sub = col.column()
991             sub.active = (part.use_whole_group is False)
992             sub.prop(part, "use_group_pick_random")
993             sub.prop(part, "use_group_count")
994
995             col = split.column()
996             sub = col.column()
997             sub.active = (part.use_whole_group is False)
998             sub.prop(part, "use_global_dupli")
999             sub.prop(part, "use_rotation_dupli")
1000             sub.prop(part, "use_scale_dupli")
1001
1002             if part.use_group_count and not part.use_whole_group:
1003                 row = layout.row()
1004                 row.template_list("UI_UL_list", "particle_dupli_weights", part, "dupli_weights",
1005                                   part, "active_dupliweight_index")
1006
1007                 col = row.column()
1008                 sub = col.row()
1009                 subsub = sub.column(align=True)
1010                 subsub.operator("particle.dupliob_copy", icon='ZOOMIN', text="")
1011                 subsub.operator("particle.dupliob_remove", icon='ZOOMOUT', text="")
1012                 subsub.operator("particle.dupliob_move_up", icon='TRIA_UP', text="")
1013                 subsub.operator("particle.dupliob_move_down", icon='TRIA_DOWN', text="")
1014
1015                 weight = part.active_dupliweight
1016                 if weight:
1017                     row = layout.row()
1018                     row.prop(weight, "count")
1019
1020         elif part.render_type == 'BILLBOARD':
1021             ob = context.object
1022
1023             col.label(text="Align:")
1024
1025             row = layout.row()
1026             row.prop(part, "billboard_align", expand=True)
1027             row.prop(part, "lock_billboard", text="Lock")
1028             row = layout.row()
1029             row.prop(part, "billboard_object")
1030
1031             row = layout.row()
1032             col = row.column(align=True)
1033             col.label(text="Tilt:")
1034             col.prop(part, "billboard_tilt", text="Angle", slider=True)
1035             col.prop(part, "billboard_tilt_random", text="Random", slider=True)
1036             col = row.column()
1037             col.prop(part, "billboard_offset")
1038
1039             row = layout.row()
1040             col = row.column()
1041             col.prop(part, "billboard_size", text="Scale")
1042             if part.billboard_align == 'VEL':
1043                 col = row.column(align=True)
1044                 col.label("Velocity Scale:")
1045                 col.prop(part, "billboard_velocity_head", text="Head")
1046                 col.prop(part, "billboard_velocity_tail", text="Tail")
1047
1048             if psys:
1049                 col = layout.column()
1050                 col.prop_search(psys, "billboard_normal_uv", ob.data, "uv_textures")
1051                 col.prop_search(psys, "billboard_time_index_uv", ob.data, "uv_textures")
1052
1053             split = layout.split(percentage=0.33)
1054             split.label(text="Split UVs:")
1055             split.prop(part, "billboard_uv_split", text="Number of splits")
1056
1057             if psys:
1058                 col = layout.column()
1059                 col.active = part.billboard_uv_split > 1
1060                 col.prop_search(psys, "billboard_split_uv", ob.data, "uv_textures")
1061
1062             row = col.row()
1063             row.label(text="Animate:")
1064             row.prop(part, "billboard_animation", text="")
1065             row.label(text="Offset:")
1066             row.prop(part, "billboard_offset_split", text="")
1067
1068         if part.render_type == 'HALO' or part.render_type == 'LINE' or part.render_type == 'BILLBOARD':
1069             row = layout.row()
1070             col = row.column()
1071             col.prop(part, "trail_count")
1072             if part.trail_count > 1:
1073                 col.prop(part, "use_absolute_path_time", text="Length in frames")
1074                 col = row.column()
1075                 col.prop(part, "path_end", text="Length", slider=not part.use_absolute_path_time)
1076                 col.prop(part, "length_random", text="Random", slider=True)
1077             else:
1078                 col = row.column()
1079                 col.label(text="")
1080
1081         if part.render_type in {'OBJECT', 'GROUP'} and not part.use_advanced_hair:
1082             row = layout.row(align=True)
1083             row.prop(part, "particle_size")
1084             row.prop(part, "size_random", slider=True)
1085
1086
1087 class PARTICLE_PT_draw(ParticleButtonsPanel, Panel):
1088     bl_label = "Display"
1089     bl_options = {'DEFAULT_CLOSED'}
1090     COMPAT_ENGINES = {'BLENDER_RENDER'}
1091
1092     @classmethod
1093     def poll(cls, context):
1094         settings = particle_get_settings(context)
1095         engine = context.scene.render.engine
1096         if settings is None:
1097             return False
1098         return engine in cls.COMPAT_ENGINES
1099
1100     def draw(self, context):
1101         layout = self.layout
1102
1103         psys = context.particle_system
1104         part = particle_get_settings(context)
1105
1106         row = layout.row()
1107         row.prop(part, "draw_method", expand=True)
1108         row.prop(part, "show_guide_hairs")
1109
1110         if part.draw_method == 'NONE' or (part.render_type == 'NONE' and part.draw_method == 'RENDER'):
1111             return
1112
1113         path = (part.render_type == 'PATH' and part.draw_method == 'RENDER') or part.draw_method == 'PATH'
1114
1115         row = layout.row()
1116         row.prop(part, "draw_percentage", slider=True)
1117         if part.draw_method != 'RENDER' or part.render_type == 'HALO':
1118             row.prop(part, "draw_size")
1119         else:
1120             row.label(text="")
1121
1122         if part.draw_percentage != 100 and psys is not None:
1123             if part.type == 'HAIR':
1124                 if psys.use_hair_dynamics and psys.point_cache.is_baked is False:
1125                     layout.row().label(text="Display percentage makes dynamics inaccurate without baking!")
1126             else:
1127                 phystype = part.physics_type
1128                 if phystype != 'NO' and phystype != 'KEYED' and psys.point_cache.is_baked is False:
1129                     layout.row().label(text="Display percentage makes dynamics inaccurate without baking!")
1130
1131         row = layout.row()
1132         col = row.column()
1133         col.prop(part, "show_size")
1134         col.prop(part, "show_velocity")
1135         col.prop(part, "show_number")
1136         if part.physics_type == 'BOIDS':
1137             col.prop(part, "show_health")
1138
1139         col = row.column(align=True)
1140         col.label(text="Color:")
1141         col.prop(part, "draw_color", text="")
1142         sub = col.row(align=True)
1143         sub.active = (part.draw_color in {'VELOCITY', 'ACCELERATION'})
1144         sub.prop(part, "color_maximum", text="Max")
1145
1146         if path:
1147             col.prop(part, "draw_step")
1148
1149
1150 class PARTICLE_PT_children(ParticleButtonsPanel, Panel):
1151     bl_label = "Children"
1152     bl_options = {'DEFAULT_CLOSED'}
1153     COMPAT_ENGINES = {'BLENDER_RENDER'}
1154
1155     @classmethod
1156     def poll(cls, context):
1157         return particle_panel_poll(cls, context)
1158
1159     def draw(self, context):
1160         layout = self.layout
1161
1162         psys = context.particle_system
1163         part = particle_get_settings(context)
1164
1165         layout.row().prop(part, "child_type", expand=True)
1166
1167         if part.child_type == 'NONE':
1168             return
1169
1170         row = layout.row()
1171
1172         col = row.column(align=True)
1173         col.prop(part, "child_nbr", text="Display")
1174         col.prop(part, "rendered_child_count", text="Render")
1175
1176         if part.child_type == 'INTERPOLATED':
1177             col = row.column()
1178             if psys:
1179                 col.prop(psys, "child_seed", text="Seed")
1180             col.prop(part, "virtual_parents", slider=True)
1181             col.prop(part, "create_long_hair_children")
1182         else:
1183             col = row.column(align=True)
1184             col.prop(part, "child_size", text="Size")
1185             col.prop(part, "child_size_random", text="Random")
1186
1187         split = layout.split()
1188
1189         col = split.column()
1190         col.label(text="Effects:")
1191
1192         sub = col.column(align=True)
1193         sub.prop(part, "use_clump_curve")
1194         if part.use_clump_curve:
1195             sub.template_curve_mapping(part, "clump_curve")
1196         else:
1197             sub.prop(part, "clump_factor", slider=True)
1198             sub.prop(part, "clump_shape", slider=True)
1199         sub = col.column(align=True)
1200         sub.prop(part, "use_clump_noise")
1201         subsub = sub.column()
1202         subsub.enabled = part.use_clump_noise
1203         subsub.prop(part, "clump_noise_size")
1204
1205         sub = col.column(align=True)
1206         sub.prop(part, "child_length", slider=True)
1207         sub.prop(part, "child_length_threshold", slider=True)
1208
1209         if part.child_type == 'SIMPLE':
1210             sub = col.column(align=True)
1211             sub.prop(part, "child_radius", text="Radius")
1212             sub.prop(part, "child_roundness", text="Roundness", slider=True)
1213             if psys:
1214                 sub.prop(psys, "child_seed", text="Seed")
1215         elif part.virtual_parents > 0.0:
1216             sub = col.column(align=True)
1217             sub.label(text="Parting not")
1218             sub.label(text="available with")
1219             sub.label(text="virtual parents")
1220         else:
1221             sub = col.column(align=True)
1222             sub.prop(part, "child_parting_factor", text="Parting", slider=True)
1223             sub.prop(part, "child_parting_min", text="Min")
1224             sub.prop(part, "child_parting_max", text="Max")
1225
1226         col = split.column()
1227
1228         col.prop(part, "use_roughness_curve")
1229         if part.use_roughness_curve:
1230             sub = col.column()
1231             sub.template_curve_mapping(part, "roughness_curve")
1232             sub.prop(part, "roughness_1", text="Roughness")
1233             sub.prop(part, "roughness_1_size", text="Size")
1234         else:
1235             col.label(text="Roughness:")
1236
1237             sub = col.column(align=True)
1238             sub.prop(part, "roughness_1", text="Uniform")
1239             sub.prop(part, "roughness_1_size", text="Size")
1240
1241             sub = col.column(align=True)
1242             sub.prop(part, "roughness_endpoint", "Endpoint")
1243             sub.prop(part, "roughness_end_shape")
1244
1245             sub = col.column(align=True)
1246             sub.prop(part, "roughness_2", text="Random")
1247             sub.prop(part, "roughness_2_size", text="Size")
1248             sub.prop(part, "roughness_2_threshold", slider=True)
1249
1250         layout.row().label(text="Kink:")
1251         layout.row().prop(part, "kink", expand=True)
1252
1253         split = layout.split()
1254         split.active = part.kink != 'NO'
1255
1256         if part.kink == 'SPIRAL':
1257             col = split.column()
1258             sub = col.column(align=True)
1259             sub.prop(part, "kink_amplitude", text="Radius")
1260             sub.prop(part, "kink_amplitude_random", text="Random", slider=True)
1261             sub = col.column(align=True)
1262             sub.prop(part, "kink_axis")
1263             sub.prop(part, "kink_axis_random", text="Random", slider=True)
1264             col = split.column(align=True)
1265             col.prop(part, "kink_frequency", text="Frequency")
1266             col.prop(part, "kink_shape", text="Shape", slider=True)
1267             col.prop(part, "kink_extra_steps", text="Steps")
1268         else:
1269             col = split.column()
1270             sub = col.column(align=True)
1271             sub.prop(part, "kink_amplitude")
1272             sub.prop(part, "kink_amplitude_clump", text="Clump", slider=True)
1273             col.prop(part, "kink_flat", slider=True)
1274             col = split.column(align=True)
1275             col.prop(part, "kink_frequency")
1276             col.prop(part, "kink_shape", slider=True)
1277
1278
1279 class PARTICLE_PT_field_weights(ParticleButtonsPanel, Panel):
1280     bl_label = "Field Weights"
1281     bl_options = {'DEFAULT_CLOSED'}
1282     COMPAT_ENGINES = {'BLENDER_RENDER'}
1283
1284     @classmethod
1285     def poll(cls, context):
1286         return particle_panel_poll(cls, context)
1287
1288     def draw(self, context):
1289         part = particle_get_settings(context)
1290         effector_weights_ui(self, context, part.effector_weights, 'PSYS')
1291
1292         if part.type == 'HAIR':
1293             row = self.layout.row()
1294             row.prop(part.effector_weights, "apply_to_hair_growing")
1295             row.prop(part, "apply_effector_to_children")
1296             row = self.layout.row()
1297             row.prop(part, "effect_hair", slider=True)
1298
1299
1300 class PARTICLE_PT_force_fields(ParticleButtonsPanel, Panel):
1301     bl_label = "Force Field Settings"
1302     bl_options = {'DEFAULT_CLOSED'}
1303     COMPAT_ENGINES = {'BLENDER_RENDER'}
1304
1305     def draw(self, context):
1306         layout = self.layout
1307
1308         part = particle_get_settings(context)
1309
1310         row = layout.row()
1311         row.prop(part, "use_self_effect")
1312         row.prop(part, "effector_amount", text="Amount")
1313
1314         split = layout.split(percentage=0.2)
1315         split.label(text="Type 1:")
1316         split.prop(part.force_field_1, "type", text="")
1317         basic_force_field_settings_ui(self, context, part.force_field_1)
1318         if part.force_field_1.type != 'NONE':
1319             layout.label(text="Falloff:")
1320         basic_force_field_falloff_ui(self, context, part.force_field_1)
1321
1322         if part.force_field_1.type != 'NONE':
1323             layout.label(text="")
1324
1325         split = layout.split(percentage=0.2)
1326         split.label(text="Type 2:")
1327         split.prop(part.force_field_2, "type", text="")
1328         basic_force_field_settings_ui(self, context, part.force_field_2)
1329         if part.force_field_2.type != 'NONE':
1330             layout.label(text="Falloff:")
1331         basic_force_field_falloff_ui(self, context, part.force_field_2)
1332
1333
1334 class PARTICLE_PT_vertexgroups(ParticleButtonsPanel, Panel):
1335     bl_label = "Vertex Groups"
1336     bl_options = {'DEFAULT_CLOSED'}
1337     COMPAT_ENGINES = {'BLENDER_RENDER'}
1338
1339     @classmethod
1340     def poll(cls, context):
1341         if context.particle_system is None:
1342             return False
1343         return particle_panel_poll(cls, context)
1344
1345     def draw(self, context):
1346         layout = self.layout
1347
1348         ob = context.object
1349         psys = context.particle_system
1350
1351         col = layout.column()
1352         row = col.row(align=True)
1353         row.prop_search(psys, "vertex_group_density", ob, "vertex_groups", text="Density")
1354         row.prop(psys, "invert_vertex_group_density", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1355
1356         row = col.row(align=True)
1357         row.prop_search(psys, "vertex_group_length", ob, "vertex_groups", text="Length")
1358         row.prop(psys, "invert_vertex_group_length", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1359
1360         row = col.row(align=True)
1361         row.prop_search(psys, "vertex_group_clump", ob, "vertex_groups", text="Clump")
1362         row.prop(psys, "invert_vertex_group_clump", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1363
1364         row = col.row(align=True)
1365         row.prop_search(psys, "vertex_group_kink", ob, "vertex_groups", text="Kink")
1366         row.prop(psys, "invert_vertex_group_kink", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1367
1368         row = col.row(align=True)
1369         row.prop_search(psys, "vertex_group_roughness_1", ob, "vertex_groups", text="Roughness 1")
1370         row.prop(psys, "invert_vertex_group_roughness_1", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1371
1372         row = col.row(align=True)
1373         row.prop_search(psys, "vertex_group_roughness_2", ob, "vertex_groups", text="Roughness 2")
1374         row.prop(psys, "invert_vertex_group_roughness_2", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1375
1376         row = col.row(align=True)
1377         row.prop_search(psys, "vertex_group_roughness_end", ob, "vertex_groups", text="Roughness End")
1378         row.prop(psys, "invert_vertex_group_roughness_end", text="", toggle=True, icon='ARROW_LEFTRIGHT')
1379
1380         # Commented out vertex groups don't work and are still waiting for better implementation
1381         # row = layout.row()
1382         # row.prop_search(psys, "vertex_group_velocity", ob, "vertex_groups", text="Velocity")
1383         # row.prop(psys, "invert_vertex_group_velocity", text="")
1384
1385         # row = layout.row()
1386         # row.prop_search(psys, "vertex_group_size", ob, "vertex_groups", text="Size")
1387         # row.prop(psys, "invert_vertex_group_size", text="")
1388
1389         # row = layout.row()
1390         # row.prop_search(psys, "vertex_group_tangent", ob, "vertex_groups", text="Tangent")
1391         # row.prop(psys, "invert_vertex_group_tangent", text="")
1392
1393         # row = layout.row()
1394         # row.prop_search(psys, "vertex_group_rotation", ob, "vertex_groups", text="Rotation")
1395         # row.prop(psys, "invert_vertex_group_rotation", text="")
1396
1397         # row = layout.row()
1398         # row.prop_search(psys, "vertex_group_field", ob, "vertex_groups", text="Field")
1399         # row.prop(psys, "invert_vertex_group_field", text="")
1400
1401
1402 class PARTICLE_PT_custom_props(ParticleButtonsPanel, PropertyPanel, Panel):
1403     COMPAT_ENGINES = {'BLENDER_RENDER'}
1404     _context_path = "particle_system.settings"
1405     _property_type = bpy.types.ParticleSettings
1406
1407
1408 classes = (
1409     PARTICLE_MT_specials,
1410     PARTICLE_MT_hair_dynamics_presets,
1411     PARTICLE_UL_particle_systems,
1412     PARTICLE_PT_context_particles,
1413     PARTICLE_PT_emission,
1414     PARTICLE_PT_hair_dynamics,
1415     PARTICLE_PT_cache,
1416     PARTICLE_PT_velocity,
1417     PARTICLE_PT_rotation,
1418     PARTICLE_PT_physics,
1419     PARTICLE_PT_boidbrain,
1420     PARTICLE_PT_render,
1421     PARTICLE_PT_draw,
1422     PARTICLE_PT_children,
1423     PARTICLE_PT_field_weights,
1424     PARTICLE_PT_force_fields,
1425     PARTICLE_PT_vertexgroups,
1426     PARTICLE_PT_custom_props,
1427 )
1428
1429 if __name__ == "__main__":  # only for live edit.
1430     from bpy.utils import register_class
1431     for cls in classes:
1432         register_class(cls)