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