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