Merge branch 'master' into 28
authorCampbell Barton <ideasman42@gmail.com>
Tue, 5 Jun 2018 14:35:20 +0000 (16:35 +0200)
committerCampbell Barton <ideasman42@gmail.com>
Tue, 5 Jun 2018 14:36:13 +0000 (16:36 +0200)
28 files changed:
1  2 
build_files/cmake/platform/platform_win32.cmake
release/scripts/startup/bl_ui/__init__.py
release/scripts/startup/bl_ui/properties_data_curve.py
release/scripts/startup/bl_ui/properties_data_lamp.py
release/scripts/startup/bl_ui/properties_freestyle.py
release/scripts/startup/bl_ui/properties_grease_pencil_common.py
release/scripts/startup/bl_ui/properties_paint_common.py
release/scripts/startup/bl_ui/properties_particle.py
release/scripts/startup/bl_ui/properties_physics_cloth.py
release/scripts/startup/bl_ui/properties_physics_common.py
release/scripts/startup/bl_ui/properties_physics_rigidbody.py
release/scripts/startup/bl_ui/properties_render.py
release/scripts/startup/bl_ui/properties_scene.py
release/scripts/startup/bl_ui/properties_texture.py
release/scripts/startup/bl_ui/properties_view_layer.py
release/scripts/startup/bl_ui/space_clip.py
release/scripts/startup/bl_ui/space_dopesheet.py
release/scripts/startup/bl_ui/space_graph.py
release/scripts/startup/bl_ui/space_image.py
release/scripts/startup/bl_ui/space_node.py
release/scripts/startup/bl_ui/space_outliner.py
release/scripts/startup/bl_ui/space_statusbar.py
release/scripts/startup/bl_ui/space_time.py
release/scripts/startup/bl_ui/space_toolsystem_common.py
release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
release/scripts/startup/bl_ui/space_userpref.py
release/scripts/startup/bl_ui/space_view3d_toolbar.py
source/blender/makesrna/intern/rna_ui.c

@@@ -167,46 -166,26 +167,47 @@@ class DATA_PT_geometry_curve(CurveButto
  
          curve = context.curve
  
 -        split = layout.split()
 -
 -        col = split.column()
 -        col.label(text="Modification:")
 +        col = layout.column()
          col.prop(curve, "offset")
 -        col.prop(curve, "extrude")
 -        col.label(text="Taper Object:")
 -        col.prop(curve, "taper_object", text="")
  
 -        col = split.column()
 -        col.label(text="Bevel:")
 -        col.prop(curve, "bevel_depth", text="Depth")
 -        col.prop(curve, "bevel_resolution", text="Resolution")
 -        col.label(text="Bevel Object:")
 -        col.prop(curve, "bevel_object", text="")
 +        sub = col.column()
 +        sub.active = (curve.bevel_object is None)
 +        sub.prop(curve, "extrude")
 +
 +        col.prop(curve, "taper_object")
 +
 +        sub = col.column()
 +        sub.active = curve.taper_object is not None
 +        sub.prop(curve, "use_map_taper")
 +
++
 +class DATA_PT_geometry_curve_bevel(CurveButtonsPanelCurve, Panel):
 +    bl_label = "Bevel"
 +    bl_parent_id = "DATA_PT_geometry_curve"
 +
 +    @classmethod
 +    def poll(cls, context):
 +        return (type(context.curve) in {Curve, TextCurve})
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        curve = context.curve
 +
 +        col = layout.column()
 +        sub = col.column()
 +        sub.active = (curve.bevel_object is None)
 +        sub.prop(curve, "bevel_depth", text="Depth")
 +        sub.prop(curve, "bevel_resolution", text="Resolution")
 +
 +        col.prop(curve, "bevel_object", text="Object")
 +
 +        sub = col.column()
 +        sub.active = curve.bevel_object is not None
 +        sub.prop(curve, "use_fill_caps")
  
          if type(curve) is not TextCurve:
 -            col = layout.column(align=True)
 -            row = col.row()
 -            row.label(text="Bevel Factor:")
  
              col = layout.column()
              col.active = (
@@@ -340,45 -325,42 +341,46 @@@ class DATA_PT_font(CurveButtonsPanelTex
          row.label(text="Bold & Italic")
          row.template_ID(text, "font_bold_italic", open="font.open", unlink="font.unlink")
  
 -        # layout.prop(text, "font")
 +        layout.separator()
  
 -        split = layout.split()
 +        row = layout.row(align=True)
 +        row.prop(char, "use_bold", toggle=True)
 +        row.prop(char, "use_italic", toggle=True)
 +        row.prop(char, "use_underline", toggle=True)
 +        row.prop(char, "use_small_caps", toggle=True)
 +
++
 +class DATA_PT_font_transform(CurveButtonsPanelText, Panel):
 +    bl_label = "Transform"
 +    bl_parent_id = "DATA_PT_font"
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        text = context.curve
 +        char = context.curve.edit_format
 +
 +        layout.use_property_split = True
 +
 +        col = layout.column()
 +
 +        col.separator()
  
 -        col = split.column()
          col.prop(text, "size", text="Size")
 -        col = split.column()
          col.prop(text, "shear")
  
 -        split = layout.split()
 -
 -        col = split.column()
 -        col.label(text="Object Font:")
 -        col.prop(text, "family", text="")
 +        col.separator()
  
 -        col = split.column()
 -        col.label(text="Text on Curve:")
 -        col.prop(text, "follow_curve", text="")
 +        col.prop(text, "family")
 +        col.prop(text, "follow_curve")
  
 -        split = layout.split()
 +        col.separator()
  
 -        col = split.column()
          sub = col.column(align=True)
 -        sub.label(text="Underline:")
 -        sub.prop(text, "underline_position", text="Position")
 -        sub.prop(text, "underline_height", text="Thickness")
 -
 -        col = split.column()
 -        col.label(text="Character:")
 -        col.prop(char, "use_bold")
 -        col.prop(char, "use_italic")
 -        col.prop(char, "use_underline")
 +        sub.prop(text, "underline_position", text="Underline Position")
 +        sub.prop(text, "underline_height", text="Underline Thickness")
  
 -        row = layout.row()
 -        row.prop(text, "small_caps_scale", text="Small Caps")
 -        row.prop(char, "use_small_caps")
 +        col.prop(text, "small_caps_scale", text="Small Caps Scale")
  
  
  class DATA_PT_paragraph(CurveButtonsPanelText, Panel):
  
          text = context.curve
  
 -        layout.label(text="Horizontal Alignment:")
 -        layout.row().prop(text, "align_x", expand=True)
  
 -        layout.label(text="Vertical Alignment:")
 +class DATA_PT_paragraph_alignment(CurveButtonsPanelText, Panel):
 +    bl_parent_id = "DATA_PT_paragraph"
 +    bl_label = "Alignment"
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = False
 +
 +        text = context.curve
 +
 +        layout.row().prop(text, "align_x", expand=True)
          layout.row().prop(text, "align_y", expand=True)
  
 -        split = layout.split()
  
 -        col = split.column(align=True)
 -        col.label(text="Spacing:")
 -        col.prop(text, "space_character", text="Letter")
 -        col.prop(text, "space_word", text="Word")
 -        col.prop(text, "space_line", text="Line")
 +class DATA_PT_paragraph_spacing(CurveButtonsPanelText, Panel):
 +    bl_parent_id = "DATA_PT_paragraph"
 +    bl_label = "Spacing"
  
 -        col = split.column(align=True)
 -        col.label(text="Offset:")
 -        col.prop(text, "offset_x", text="X")
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        text = context.curve
 +
 +        col = layout.column(align=True)
 +        col.prop(text, "space_character", text="Character Spacing")
 +        col.prop(text, "space_word", text="Word Spacing")
 +        col.prop(text, "space_line", text="Line Spacing")
 +
 +        layout.separator()
 +
 +        col = layout.column(align=True)
 +        col.prop(text, "offset_x", text="Offset X")
          col.prop(text, "offset_y", text="Y")
  
  
@@@ -166,80 -210,104 +166,79 @@@ class DATA_PT_EEVEE_shadow(DataButtonsP
  
          lamp = context.lamp
  
 -        layout.row().prop(lamp, "shadow_method", expand=True)
 +        layout.active = lamp.use_shadow
  
 -        if lamp.shadow_method == 'NOSHADOW' and lamp.type == 'AREA':
 -            split = layout.split()
 -
 -            col = split.column()
 -            col.label(text="Form Factor Sampling:")
 -
 -            sub = col.row(align=True)
 -
 -            if lamp.shape == 'SQUARE':
 -                sub.prop(lamp, "shadow_ray_samples_x", text="Samples")
 -            elif lamp.shape == 'RECTANGLE':
 -                sub.prop(lamp, "shadow_ray_samples_x", text="Samples X")
 -                sub.prop(lamp, "shadow_ray_samples_y", text="Samples Y")
 -
 -        if lamp.shadow_method != 'NOSHADOW':
 -            split = layout.split()
 +        col = layout.column()
 +        sub = col.column(align=True)
 +        sub.prop(lamp, "shadow_buffer_clip_start", text="Clip Start")
 +        sub.prop(lamp, "shadow_buffer_clip_end", text="End")
  
 -            col = split.column()
 -            col.prop(lamp, "shadow_color", text="")
 +        col.prop(lamp, "shadow_buffer_soft", text="Softness")
  
 -            col = split.column()
 -            col.prop(lamp, "use_shadow_layer", text="This Layer Only")
 -            col.prop(lamp, "use_only_shadow")
 +        col.separator()
  
 -        if lamp.shadow_method == 'RAY_SHADOW':
 -            split = layout.split()
 +        col.prop(lamp, "shadow_buffer_bias", text="Bias")
 +        col.prop(lamp, "shadow_buffer_exp", text="Exponent")
 +        col.prop(lamp, "shadow_buffer_bleed_bias", text="Bleed Bias")
  
 -            col = split.column()
 -            col.label(text="Sampling:")
  
 -            if lamp.type in {'POINT', 'SUN', 'SPOT'}:
 -                sub = col.row()
 +class DATA_PT_EEVEE_shadow_cascaded_shadow_map(DataButtonsPanel, Panel):
 +    bl_label = "Cascaded Shadow Map"
 +    bl_parent_id = "DATA_PT_EEVEE_shadow"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_EEVEE'}
  
 -                sub.prop(lamp, "shadow_ray_samples", text="Samples")
 -                sub.prop(lamp, "shadow_soft_size", text="Soft Size")
 +    @classmethod
 +    def poll(cls, context):
 +        lamp = context.lamp
 +        engine = context.engine
  
 -            elif lamp.type == 'AREA':
 -                sub = col.row(align=True)
 +        return (lamp and lamp.type == 'SUN') and (engine in cls.COMPAT_ENGINES)
  
 -                if lamp.shape == 'SQUARE':
 -                    sub.prop(lamp, "shadow_ray_samples_x", text="Samples")
 -                elif lamp.shape == 'RECTANGLE':
 -                    sub.prop(lamp, "shadow_ray_samples_x", text="Samples X")
 -                    sub.prop(lamp, "shadow_ray_samples_y", text="Samples Y")
 +    def draw(self, context):
 +        layout = self.layout
 +        lamp = context.lamp
 +        layout.use_property_split = True
  
 -            col.row().prop(lamp, "shadow_ray_sample_method", expand=True)
 +        col = layout.column()
  
 -            if lamp.shadow_ray_sample_method == 'ADAPTIVE_QMC':
 -                layout.prop(lamp, "shadow_adaptive_threshold", text="Threshold")
 +        col.prop(lamp, "shadow_cascade_count", text="Count")
 +        col.prop(lamp, "shadow_cascade_fade", text="Fade")
  
 -            if lamp.type == 'AREA' and lamp.shadow_ray_sample_method == 'CONSTANT_JITTERED':
 -                row = layout.row()
 -                row.prop(lamp, "use_umbra")
 -                row.prop(lamp, "use_dither")
 -                row.prop(lamp, "use_jitter")
 +        col.prop(lamp, "shadow_cascade_max_distance", text="Max Distance")
 +        col.prop(lamp, "shadow_cascade_exponent", text="Distribution")
  
 -        elif lamp.shadow_method == 'BUFFER_SHADOW':
 -            col = layout.column()
 -            col.label(text="Buffer Type:")
 -            col.row().prop(lamp, "shadow_buffer_type", expand=True)
  
 -            if lamp.shadow_buffer_type in {'REGULAR', 'HALFWAY', 'DEEP'}:
 -                split = layout.split()
 +class DATA_PT_EEVEE_shadow_contact(DataButtonsPanel, Panel):
 +    bl_label = "Contact Shadows"
 +    bl_parent_id = "DATA_PT_EEVEE_shadow"
 +    COMPAT_ENGINES = {'BLENDER_EEVEE'}
  
 -                col = split.column()
 -                col.label(text="Filter Type:")
 -                col.prop(lamp, "shadow_filter_type", text="")
 -                sub = col.column(align=True)
 -                sub.prop(lamp, "shadow_buffer_soft", text="Soft")
 -                sub.prop(lamp, "shadow_buffer_bias", text="Bias")
 +    @classmethod
 +    def poll(cls, context):
 +        lamp = context.lamp
 +        engine = context.engine
 +        return (lamp and lamp.type in {'POINT', 'SUN', 'SPOT', 'AREA'}) and (engine in cls.COMPAT_ENGINES)
  
 -                col = split.column()
 -                col.label(text="Sample Buffers:")
 -                col.prop(lamp, "shadow_sample_buffers", text="")
 -                sub = col.column(align=True)
 -                sub.prop(lamp, "shadow_buffer_size", text="Size")
 -                sub.prop(lamp, "shadow_buffer_samples", text="Samples")
 -                if lamp.shadow_buffer_type == 'DEEP':
 -                    col.prop(lamp, "compression_threshold")
 +    def draw_header(self, context):
 +        lamp = context.lamp
  
 -            elif lamp.shadow_buffer_type == 'IRREGULAR':
 -                layout.prop(lamp, "shadow_buffer_bias", text="Bias")
 +        layout = self.layout
 +        layout.active = lamp.use_shadow
 +        layout.prop(lamp, "use_contact_shadow", text="")
  
 -            split = layout.split()
 +    def draw(self, context):
 +        layout = self.layout
 +        lamp = context.lamp
 +        layout.use_property_split = True
  
 -            col = split.column()
 -            col.prop(lamp, "use_auto_clip_start", text="Autoclip Start")
 -            sub = col.column()
 -            sub.active = not lamp.use_auto_clip_start
 -            sub.prop(lamp, "shadow_buffer_clip_start", text="Clip Start")
 +        col = layout.column()
 +        col.active = lamp.use_shadow and lamp.use_contact_shadow
  
 -            col = split.column()
 -            col.prop(lamp, "use_auto_clip_end", text="Autoclip End")
 -            sub = col.column()
 -            sub.active = not lamp.use_auto_clip_end
 -            sub.prop(lamp, "shadow_buffer_clip_end", text=" Clip End")
 +        col.prop(lamp, "contact_shadow_distance", text="Distance")
 +        col.prop(lamp, "contact_shadow_soft_size", text="Softness")
 +        col.prop(lamp, "contact_shadow_bias", text="Bias")
 +        col.prop(lamp, "contact_shadow_thickness", text="Thickness")
  
  
  class DATA_PT_area(DataButtonsPanel, Panel):
@@@ -396,71 -409,6 +396,71 @@@ class PARTICLE_PT_hair_dynamics(Particl
              box.label("Error: %.5f .. %.5f (avg. %.5f)" % (result.min_error, result.max_error, result.avg_error))
  
  
-         return context.particle_system.cloth != None
 +class PARTICLE_PT_hair_dynamics_structure(ParticleButtonsPanel, Panel):
 +    bl_label = "Structure"
 +    bl_parent_id = "PARTICLE_PT_hair_dynamics"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
-         return context.particle_system.cloth != None
++        return context.particle_system.cloth is not None
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        psys = context.particle_system
 +        cloth_md = psys.cloth
 +        cloth = cloth_md.settings
 +        result = cloth_md.solver_result
 +
 +        layout.enabled = psys.use_hair_dynamics and psys.point_cache.is_baked is False
 +
 +        layout.use_property_split = True
 +
 +        col = layout.column()
 +        col.prop(cloth, "mass")
 +        sub = col.column(align=True)
 +        sub.prop(cloth, "bending_stiffness", text="Stiffness")
 +        sub.prop(psys.settings, "bending_random", text="Random")
 +        col.prop(cloth, "bending_damping", text="Damping")
 +        # XXX has no noticeable effect with stiff hair structure springs
 +        #col.prop(cloth, "spring_damping", text="Damping")
 +
 +
 +class PARTICLE_PT_hair_dynamics_volume(ParticleButtonsPanel, Panel):
 +    bl_label = "Volume"
 +    bl_parent_id = "PARTICLE_PT_hair_dynamics"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
++        return context.particle_system.cloth is not None
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        psys = context.particle_system
 +        cloth_md = psys.cloth
 +        cloth = cloth_md.settings
 +        result = cloth_md.solver_result
 +
 +        layout.enabled = psys.use_hair_dynamics and psys.point_cache.is_baked is False
 +
 +        layout.use_property_split = True
 +
 +        col = layout.column()
 +        col.prop(cloth, "air_damping", text="Air Drag")
 +        col.prop(cloth, "internal_friction", slider=True)
 +        col.prop(cloth, "voxel_cell_size")
 +
 +        col.separator()
 +
 +        col.prop(cloth, "density_target", text="Density Target")
 +        col.prop(cloth, "density_strength", slider=True, text="Density Strength")
 +
 +
  class PARTICLE_PT_cache(ParticleButtonsPanel, Panel):
      bl_label = "Cache"
      bl_options = {'DEFAULT_CLOSED'}
@@@ -576,45 -526,32 +576,46 @@@ class PARTICLE_PT_rotation(ParticleButt
              part = context.space_data.pin_id
  
          layout.enabled = particle_panel_enabled(context, psys) and part.use_rotations
 +        layout.use_property_split = True
  
 -        layout.label(text="Initial Orientation:")
 +        col = layout.column()
  
 -        split = layout.split()
 +        col.prop(part, "rotation_mode")
 +        col.prop(part, "rotation_factor_random", slider=True, text="Randomize")
  
 -        col = split.column(align=True)
 -        col.prop(part, "rotation_mode", text="")
 -        col.prop(part, "rotation_factor_random", slider=True, text="Random")
 +        col.separator()
  
 -        col = split.column(align=True)
          col.prop(part, "phase_factor", slider=True)
 -        col.prop(part, "phase_factor_random", text="Random", slider=True)
 +        col.prop(part, "phase_factor_random", text="Randomize Phase ", slider=True)
  
          if part.type != 'HAIR':
 -            layout.label(text="Angular Velocity:")
 +            col.prop(part, "use_dynamic_rotation")
  
 -            split = layout.split()
 -            col = split.column(align=True)
 -            col.prop(part, "angular_velocity_mode", text="")
 -            sub = col.column(align=True)
 -            sub.active = part.angular_velocity_mode != 'NONE'
 -            sub.prop(part, "angular_velocity_factor", text="")
 +class PARTICLE_PT_rotation_angular_velocity(ParticleButtonsPanel, Panel):
 +    bl_label = "Angular Velocity"
 +    bl_parent_id = "PARTICLE_PT_rotation"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
  
 -            col = split.column()
 -            col.prop(part, "use_dynamic_rotation")
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        psys = context.particle_system
 +        if psys:
 +            part = psys.settings
 +        else:
 +            part = context.space_data.pin_id
 +
 +        layout.enabled = particle_panel_enabled(context, psys) and part.use_rotations
 +        layout.use_property_split = True
 +
 +        col = layout.column()
 +
 +        col.prop(part, "angular_velocity_mode", text="Axis")
 +        sub = col.column(align=True)
 +        sub.active = part.angular_velocity_mode != 'NONE'
 +        sub.prop(part, "angular_velocity_factor", text="Amount")
  
  
  class PARTICLE_PT_physics(ParticleButtonsPanel, Panel):
                      sub.prop(key, "object", text="")
                      sub.prop(key, "system", text="System")
  
-        part = particle_get_settings(context)
-        return part.physics_type in {'NEWTON', 'FLUID'}
 +class PARTICLE_PT_physics_deflection(ParticleButtonsPanel, Panel):
 +    bl_label = "Deflection"
 +    bl_parent_id = "PARTICLE_PT_physics"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
-        part = particle_get_settings(context)
-        return part.physics_type == 'NEWTON'
++        part = particle_get_settings(context)
++        return part.physics_type in {'NEWTON', 'FLUID'}
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        psys = context.particle_system
 +        part = particle_get_settings(context)
 +
 +        layout.enabled = particle_panel_enabled(context, psys)
 +
 +        col = layout.column()
 +        col.prop(part, "use_size_deflect")
 +        col.prop(part, "use_die_on_collision")
 +
 +        col.prop(part, "collision_group")
 +
 +
 +class PARTICLE_PT_physics_forces(ParticleButtonsPanel, Panel):
 +    bl_label = "Forces"
 +    bl_parent_id = "PARTICLE_PT_physics"
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
-        part = particle_get_settings(context)
-        return part.physics_type == 'NEWTON'
++        part = particle_get_settings(context)
++        return part.physics_type == 'NEWTON'
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        psys = context.particle_system
 +        part = particle_get_settings(context)
 +
 +        layout.enabled = particle_panel_enabled(context, psys)
 +
 +        col = layout.column()
 +
 +        col.prop(part, "brownian_factor")
 +        col.prop(part, "drag_factor", slider=True)
 +        col.prop(part, "damping", slider=True)
 +
 +
 +class PARTICLE_PT_physics_integration(ParticleButtonsPanel, Panel):
 +    bl_label = "Integration"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    bl_parent_id = "PARTICLE_PT_physics"
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
++        part = particle_get_settings(context)
++        return part.physics_type == 'NEWTON'
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        psys = context.particle_system
 +        part = particle_get_settings(context)
 +
 +        layout.enabled = particle_panel_enabled(context, psys)
 +
 +        col = layout.column()
 +
 +        col.prop(part, "integrator")
 +        col.prop(part, "timestep")
 +        sub = col.row()
 +        sub.prop(part, "subframes")
 +        supports_courant = part.physics_type == 'FLUID'
 +        subsub = sub.row()
 +        subsub.enabled = supports_courant
 +        subsub.prop(part, "use_adaptive_subframes", text="")
 +        if supports_courant and part.use_adaptive_subframes:
 +            col.prop(part, "courant_target", text="Threshold")
 +
 +
  class PARTICLE_PT_boidbrain(ParticleButtonsPanel, Panel):
      bl_label = "Boid Brain"
 -    COMPAT_ENGINES = {'BLENDER_RENDER'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
  
      @classmethod
      def poll(cls, context):
@@@ -1022,373 -912,184 +1024,378 @@@ class PARTICLE_PT_render(ParticleButton
          psys = context.particle_system
          part = particle_get_settings(context)
  
 -        if psys:
 -            row = layout.row()
 -            if part.render_type in {'OBJECT', 'GROUP'}:
 -                row.enabled = False
 -            row.prop(part, "material_slot", text="")
 -            row.prop(psys, "parent")
 -
 -        split = layout.split()
 +        layout.prop(part, "render_type", text="Render As")
  
 -        col = split.column()
 -        col.prop(part, "use_render_emitter")
 -        col.prop(part, "use_parent_particles")
 +        if part.type == 'EMITTER' or \
 +           (part.render_type in {'OBJECT', 'COLLECTION'} and part.type == 'HAIR'):
 +            if part.render_type not in {'NONE'}:
  
 -        col = split.column()
 -        col.prop(part, "show_unborn")
 -        col.prop(part, "use_dead")
 +                col = layout.column(align=True)
 +                col.prop(part, "particle_size", text="Scale")
 +                col.prop(part, "size_random", slider=True, text="Scale Randomness")
  
 -        layout.row().prop(part, "render_type", expand=True)
 +        if psys:
 +            col = layout.column()
 +            if part.render_type not in {'OBJECT', 'COLLECTION', 'NONE'}:
 +                # col.enabled = False
 +                col.prop(part, "material_slot", text="Material")
 +                col.prop(psys, "parent", text="Coordinate System")
  
 -        split = layout.split()
 -        col = split.column()
 +class PARTICLE_PT_render_extra(ParticleButtonsPanel, Panel):
 +    bl_label = "Extra"
 +    bl_parent_id = "PARTICLE_PT_render"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
  
 -        if part.render_type == 'LINE':
 -            col.prop(part, "line_length_tail")
 -            col.prop(part, "line_length_head")
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.render_type != 'NONE'
  
 -            split.prop(part, "use_velocity_length")
 -        elif part.render_type == 'PATH':
 -            col.prop(part, "use_strand_primitive")
 -            sub = col.column()
 -            sub.active = (part.use_strand_primitive is False)
 -            sub.prop(part, "use_render_adaptive")
 -            sub = col.column()
 -            sub.active = part.use_render_adaptive or part.use_strand_primitive is True
 -            sub.prop(part, "adaptive_angle")
 -            sub = col.column()
 -            sub.active = (part.use_render_adaptive is True and part.use_strand_primitive is False)
 -            sub.prop(part, "adaptive_pixel")
 -            col.prop(part, "use_hair_bspline")
 -            col.prop(part, "render_step", text="Steps")
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
  
 -            col = split.column()
 -            col.label(text="Timing:")
 -            col.prop(part, "use_absolute_path_time")
 +        psys = context.particle_system
 +        ob = context.object
 +        part = particle_get_settings(context)
  
-         col=layout.column()
 -            if part.type == 'HAIR' or psys.point_cache.is_baked:
 -                col.prop(part, "path_start", text="Start", slider=not part.use_absolute_path_time)
 -            else:
 -                col.prop(part, "trail_count")
++        col = layout.column()
  
 -            col.prop(part, "path_end", text="End", slider=not part.use_absolute_path_time)
 -            col.prop(part, "length_random", text="Random", slider=True)
 +        col = layout.column()
 +        col.prop(part, "use_parent_particles", text="Parent Particles")
 +        col.prop(part, "show_unborn", text="Unborn")
 +        col.prop(part, "use_dead", text="Dead")
  
 -            row = layout.row()
 -            col = row.column()
  
 -            if part.type == 'HAIR' and part.use_strand_primitive is True and part.child_type == 'INTERPOLATED':
 -                layout.prop(part, "use_simplify")
 -                if part.use_simplify is True:
 -                    row = layout.row()
 -                    row.prop(part, "simplify_refsize")
 -                    row.prop(part, "simplify_rate")
 -                    row.prop(part, "simplify_transition")
 -                    row = layout.row()
 -                    row.prop(part, "use_simplify_viewport")
 -                    sub = row.row()
 -                    sub.active = part.use_simplify_viewport is True
 -                    sub.prop(part, "simplify_viewport")
 +class PARTICLE_PT_render_line(ParticleButtonsPanel, Panel):
 +    bl_label = "Line"
 +    bl_parent_id = "PARTICLE_PT_render"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
  
 -        elif part.render_type == 'OBJECT':
 -            col.prop(part, "dupli_object")
 -            sub = col.row()
 -            sub.prop(part, "use_global_dupli")
 -            sub.prop(part, "use_rotation_dupli")
 -            sub.prop(part, "use_scale_dupli")
 -        elif part.render_type == 'GROUP':
 -            col.prop(part, "dupli_group")
 -            split = layout.split()
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.render_type == 'LINE'
  
 -            col = split.column()
 -            col.prop(part, "use_whole_group")
 -            sub = col.column()
 -            sub.active = (part.use_whole_group is False)
 -            sub.prop(part, "use_group_pick_random")
 -            sub.prop(part, "use_group_count")
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
  
 -            col = split.column()
 -            sub = col.column()
 -            sub.active = (part.use_whole_group is False)
 -            sub.prop(part, "use_global_dupli")
 -            sub.prop(part, "use_rotation_dupli")
 -            sub.prop(part, "use_scale_dupli")
 +        psys = context.particle_system
 +        ob = context.object
 +        part = particle_get_settings(context)
  
-         col=layout.column()
 -            if part.use_group_count and not part.use_whole_group:
 -                row = layout.row()
 -                row.template_list("UI_UL_list", "particle_dupli_weights", part, "dupli_weights",
 -                                  part, "active_dupliweight_index")
++        col = layout.column()
  
 -                col = row.column()
 -                sub = col.row()
 -                subsub = sub.column(align=True)
 -                subsub.operator("particle.dupliob_copy", icon='ZOOMIN', text="")
 -                subsub.operator("particle.dupliob_remove", icon='ZOOMOUT', text="")
 -                subsub.operator("particle.dupliob_move_up", icon='TRIA_UP', text="")
 -                subsub.operator("particle.dupliob_move_down", icon='TRIA_DOWN', text="")
 +        col.separator()
 +        sub = col.column(align=True)
 +        sub.prop(part, "line_length_tail", text="Length Tail")
 +        sub.prop(part, "line_length_head", text="Head")
 +        col.prop(part, "use_velocity_length", text="Velocity Length")
  
 -                weight = part.active_dupliweight
 -                if weight:
 -                    row = layout.row()
 -                    row.prop(weight, "count")
 -        elif part.render_type == 'BILLBOARD':
 -            ob = context.object
 +class PARTICLE_PT_render_path(ParticleButtonsPanel, Panel):
 +    bl_label = "Path"
 +    bl_parent_id = "PARTICLE_PT_render"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
  
 -            col.label(text="Align:")
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.render_type == 'PATH'
  
 -            row = layout.row()
 -            row.prop(part, "billboard_align", expand=True)
 -            row.prop(part, "lock_billboard", text="Lock")
 -            row = layout.row()
 -            row.prop(part, "billboard_object")
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
  
 -            row = layout.row()
 -            col = row.column(align=True)
 -            col.label(text="Tilt:")
 -            col.prop(part, "billboard_tilt", text="Angle", slider=True)
 -            col.prop(part, "billboard_tilt_random", text="Random", slider=True)
 -            col = row.column()
 -            col.prop(part, "billboard_offset")
 +        psys = context.particle_system
 +        ob = context.object
 +        part = particle_get_settings(context)
  
-         col=layout.column()
 -            row = layout.row()
 -            col = row.column()
 -            col.prop(part, "billboard_size", text="Scale")
 -            if part.billboard_align == 'VEL':
 -                col = row.column(align=True)
 -                col.label("Velocity Scale:")
 -                col.prop(part, "billboard_velocity_head", text="Head")
 -                col.prop(part, "billboard_velocity_tail", text="Tail")
++        col = layout.column()
  
 -            if psys:
 -                col = layout.column()
 -                col.prop_search(psys, "billboard_normal_uv", ob.data, "uv_textures")
 -                col.prop_search(psys, "billboard_time_index_uv", ob.data, "uv_textures")
 +        col.prop(part, "use_strand_primitive")
 +        sub = col.column()
 +        sub.active = (part.use_strand_primitive is False)
 +        sub.prop(part, "use_render_adaptive")
 +        sub = col.column()
 +        sub.active = part.use_render_adaptive or part.use_strand_primitive is True
 +        sub.prop(part, "adaptive_angle")
 +        sub = col.column()
 +        sub.active = (part.use_render_adaptive is True and part.use_strand_primitive is False)
 +        sub.prop(part, "adaptive_pixel")
 +        col.prop(part, "use_hair_bspline")
 +        col.prop(part, "render_step", text="Steps")
 +
 +
 +class PARTICLE_PT_render_path_timing(ParticleButtonsPanel, Panel):
 +    bl_label = "Timing"
 +    bl_parent_id = "PARTICLE_PT_render"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
  
 -            split = layout.split(percentage=0.33)
 -            split.label(text="Split UVs:")
 -            split.prop(part, "billboard_uv_split", text="Number of splits")
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.render_type == 'PATH'
  
 -            if psys:
 -                col = layout.column()
 -                col.active = part.billboard_uv_split > 1
 -                col.prop_search(psys, "billboard_split_uv", ob.data, "uv_textures")
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
  
 -            row = col.row()
 -            row.label(text="Animate:")
 -            row.prop(part, "billboard_animation", text="")
 -            row.label(text="Offset:")
 -            row.prop(part, "billboard_offset_split", text="")
 +        psys = context.particle_system
 +        ob = context.object
 +        part = particle_get_settings(context)
  
-         col=layout.column()
 -        if part.render_type == 'HALO' or part.render_type == 'LINE' or part.render_type == 'BILLBOARD':
 -            row = layout.row()
 -            col = row.column()
++        col = layout.column()
 +
 +        col.prop(part, "use_absolute_path_time")
 +
 +        if part.type == 'HAIR' or psys.point_cache.is_baked:
 +            col.prop(part, "path_start", text="Start", slider=not part.use_absolute_path_time)
 +        else:
              col.prop(part, "trail_count")
 -            if part.trail_count > 1:
 -                col.prop(part, "use_absolute_path_time", text="Length in Frames")
 -                col = row.column()
 -                col.prop(part, "path_end", text="Length", slider=not part.use_absolute_path_time)
 -                col.prop(part, "length_random", text="Random", slider=True)
 -            else:
 -                col = row.column()
 -                col.label(text="")
  
 -        if part.type == 'EMITTER' or \
 -           (part.render_type in {'OBJECT', 'GROUP'} and part.type == 'HAIR'):
 -            row = layout.row(align=True)
 -            row.prop(part, "particle_size")
 -            row.prop(part, "size_random", slider=True)
 +        col.prop(part, "path_end", text="End", slider=not part.use_absolute_path_time)
 +        col.prop(part, "length_random", text="Random", slider=True)
 +
++
 +class PARTICLE_PT_render_object(ParticleButtonsPanel, Panel):
 +    bl_label = "Object"
 +    bl_parent_id = "PARTICLE_PT_render"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.render_type == 'OBJECT'
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        psys = context.particle_system
 +        ob = context.object
 +        part = particle_get_settings(context)
 +
-         col=layout.column()
++        col = layout.column()
 +
 +        col.prop(part, "dupli_object", text="Instance Object")
 +        sub = col.column()
 +        sub.prop(part, "use_global_dupli", text="Global Coordinates")
 +        sub.prop(part, "use_rotation_dupli", text="Object Rotation")
 +        sub.prop(part, "use_scale_dupli", text="Object Scale")
 +
 +
 +class PARTICLE_PT_render_collection(ParticleButtonsPanel, Panel):
 +    bl_label = "Collection"
 +    bl_parent_id = "PARTICLE_PT_render"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.render_type == 'COLLECTION'
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        psys = context.particle_system
 +        ob = context.object
 +        part = particle_get_settings(context)
 +
-         col=layout.column()
++        col = layout.column()
 +
 +        col.prop(part, "dupli_group")
 +
 +        col.prop(part, "use_whole_group")
 +        sub = col.column()
 +        sub.active = (part.use_whole_group is False)
 +        sub.prop(part, "use_group_pick_random")
 +        sub.prop(part, "use_global_dupli", text="Global Coordinates")
 +        sub.prop(part, "use_rotation_dupli", text="Object Rotation")
 +        sub.prop(part, "use_scale_dupli", text="Object Scale")
 +
++
 +class PARTICLE_PT_render_collection_use_count(ParticleButtonsPanel, Panel):
 +    bl_label = "Use Count"
 +    bl_parent_id = "PARTICLE_PT_render_collection"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.render_type == 'COLLECTION'
 +
 +    def draw_header(self, context):
 +        layout = self.layout
 +        part = particle_get_settings(context)
 +
 +        layout.active = not part.use_whole_group
 +
 +        layout.prop(part, "use_group_count", text="")
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        psys = context.particle_system
 +        ob = context.object
 +        part = particle_get_settings(context)
 +
-         col=layout.column()
++        col = layout.column()
 +
 +        layout.active = part.use_group_count and not part.use_whole_group
 +
 +        row = layout.row()
 +        row.template_list("UI_UL_list", "particle_dupli_weights", part, "dupli_weights",
-                                   part, "active_dupliweight_index")
++                          part, "active_dupliweight_index")
 +
 +        col = row.column()
 +        sub = col.row()
 +        subsub = sub.column(align=True)
 +        subsub.operator("particle.dupliob_copy", icon='ZOOMIN', text="")
 +        subsub.operator("particle.dupliob_remove", icon='ZOOMOUT', text="")
 +        subsub.operator("particle.dupliob_move_up", icon='TRIA_UP', text="")
 +        subsub.operator("particle.dupliob_move_down", icon='TRIA_DOWN', text="")
 +
 +        weight = part.active_dupliweight
 +        if weight:
 +            row = layout.row()
 +            row.prop(weight, "count")
 +
++
 +class PARTICLE_PT_render_billboards_alignment(ParticleButtonsPanel, Panel):
 +    bl_label = "Billboard Alignment"
 +    bl_parent_id = "PARTICLE_PT_render"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.render_type == 'BILLBOARD'
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        psys = context.particle_system
 +        ob = context.object
 +        part = particle_get_settings(context)
 +
-         col=layout.column()
++        col = layout.column()
 +
 +        col.prop(part, "billboard_align", text="Align To")
 +        col.prop(part, "lock_billboard", text="Lock Axis")
 +        col.prop(part, "billboard_object")
 +
++
 +class PARTICLE_PT_render_billboards_tilt(ParticleButtonsPanel, Panel):
 +    bl_label = "Billboard Tilt"
 +    bl_parent_id = "PARTICLE_PT_render"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.render_type == 'BILLBOARD'
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        psys = context.particle_system
 +        ob = context.object
 +        part = particle_get_settings(context)
 +
-         col=layout.column()
++        col = layout.column()
 +
 +        sub = col.column(align=True)
 +        sub.prop(part, "billboard_tilt", text="Angle", slider=True)
 +        sub.prop(part, "billboard_tilt_random", text="Random", slider=True)
 +
 +        sub = col.column(align=True)
 +        sub.prop(part, "billboard_offset")
 +        col.prop(part, "billboard_size", text="Scale")
 +        if part.billboard_align == 'VEL':
 +            col = col.column(align=True)
 +            col.prop(part, "billboard_velocity_head", text="Velocity ScaleHead")
 +            col.prop(part, "billboard_velocity_tail", text="Tail")
 +
++
 +class PARTICLE_PT_render_billboards_uv(ParticleButtonsPanel, Panel):
 +    bl_label = "Billboard UVs"
 +    bl_parent_id = "PARTICLE_PT_render"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.render_type == 'BILLBOARD'
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        psys = context.particle_system
 +        ob = context.object
 +        part = particle_get_settings(context)
 +
-         col=layout.column()
++        col = layout.column()
 +
 +        if psys:
 +            col.prop_search(psys, "billboard_normal_uv", ob.data, "uv_layers")
 +            col.prop_search(psys, "billboard_time_index_uv", ob.data, "uv_layers")
 +
 +        col.prop(part, "billboard_uv_split", text="Split UVs")
 +
 +        if psys:
 +            sub = col.column()
 +            sub.active = part.billboard_uv_split > 1
 +            sub.prop_search(psys, "billboard_split_uv", ob.data, "uv_layers")
 +
 +        sub.prop(part, "billboard_animation")
 +        sub.prop(part, "billboard_offset_split")
 +
 +
 +class PARTICLE_PT_render_trails(ParticleButtonsPanel, Panel):
 +    bl_label = "Trails"
 +    bl_parent_id = "PARTICLE_PT_render"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.render_type in {'HALO', 'LINE', 'BILLBOARD'}
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.use_property_split = True
 +
 +        psys = context.particle_system
 +        part = particle_get_settings(context)
 +
-         col=layout.column()
++        col = layout.column()
 +
 +        col.prop(part, "trail_count")
 +
 +        sub = col.column()
 +        sub.active = (part.trail_count > 1)
 +        sub.prop(part, "use_absolute_path_time", text="Length in Frames")
 +        sub.prop(part, "path_end", text="Length", slider=not part.use_absolute_path_time)
 +        sub.prop(part, "length_random", text="Random Length", slider=True)
  
  
  class PARTICLE_PT_draw(ParticleButtonsPanel, Panel):
@@@ -1494,66 -1185,24 +1501,67 @@@ class PARTICLE_PT_children(ParticleButt
              col.prop(part, "virtual_parents", slider=True)
              col.prop(part, "create_long_hair_children")
          else:
 -            col = row.column(align=True)
 -            col.prop(part, "child_size", text="Size")
 -            col.prop(part, "child_size_random", text="Random")
 +            col.separator()
 +            sub = col.column(align=True)
 +            sub.prop(part, "child_size", text="Size")
 +            sub.prop(part, "child_size_random", text="Randomize Size", slider=True)
  
 -        split = layout.split()
 +        if part.child_type == 'SIMPLE':
 +            col.separator()
 +            col.prop(part, "child_radius", text="Radius")
 +            col.prop(part, "child_roundness", text="Roundness", slider=True)
 +        elif part.virtual_parents > 0.0:
 +            sub = col.column(align=True)
 +            sub.label(text="Parting not available with virtual parents")
  
 -        col = split.column()
 -        col.label(text="Effects:")
  
 -        sub = col.column(align=True)
 -        if part.child_type == 'SIMPLE':
 -            sub.prop(part, "twist")
 -            sub.prop(part, "use_twist_curve")
 -            if part.use_twist_curve:
 -                sub.template_curve_mapping(part, "twist_curve")
 +class PARTICLE_PT_children_parting(ParticleButtonsPanel, Panel):
 +    bl_label = "Parting"
 +    bl_parent_id = "PARTICLE_PT_children"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.child_type == 'INTERPOLATED'
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        psys = context.particle_system
 +        part = particle_get_settings(context)
 +
 +        layout.use_property_split = True
 +
 +        col = layout.column()
 +        col.prop(part, "child_parting_factor", text="Parting", slider=True)
 +        col.prop(part, "child_parting_min", text="Min")
 +        col.prop(part, "child_parting_max", text="Max")
 +
++
 +class PARTICLE_PT_children_clumping(ParticleButtonsPanel, Panel):
 +    bl_label = "Clumping"
 +    bl_parent_id = "PARTICLE_PT_children"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.child_type != 'NONE'
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        psys = context.particle_system
 +        part = particle_get_settings(context)
 +
 +        layout.use_property_split = True
 +
 +        col = layout.column()
 +
 +        sub = col.column()
  
          sub.prop(part, "use_clump_curve")
          if part.use_clump_curve:
          subsub.enabled = part.use_clump_noise
          subsub.prop(part, "clump_noise_size")
  
 -        sub = col.column(align=True)
 -        sub.prop(part, "child_length", slider=True)
 -        sub.prop(part, "child_length_threshold", slider=True)
 -
          if part.child_type == 'SIMPLE':
 -            sub = col.column(align=True)
 -            sub.prop(part, "child_radius", text="Radius")
 -            sub.prop(part, "child_roundness", text="Roundness", slider=True)
 -            if psys:
 -                sub.prop(psys, "child_seed", text="Seed")
 -        elif part.virtual_parents > 0.0:
 -            sub = col.column(align=True)
 -            sub.label(text="Parting not")
 -            sub.label(text="available with")
 -            sub.label(text="virtual parents")
 -        else:
 -            sub = col.column(align=True)
 -            sub.prop(part, "child_parting_factor", text="Parting", slider=True)
 -            sub.prop(part, "child_parting_min", text="Min")
 -            sub.prop(part, "child_parting_max", text="Max")
 +            sub.prop(part, "twist")
 +            sub.prop(part, "use_twist_curve")
 +            if part.use_twist_curve:
 +                sub.template_curve_mapping(part, "twist_curve")
  
 -        col = split.column()
++
 +class PARTICLE_PT_children_roughness(ParticleButtonsPanel, Panel):
 +    bl_label = "Roughness"
 +    bl_parent_id = "PARTICLE_PT_children"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    @classmethod
 +    def poll(cls, context):
 +        part = particle_get_settings(context)
 +        return part.child_type != 'NONE'
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        psys = context.particle_system
 +        part = particle_get_settings(context)
 +
 +        layout.use_property_split = True
 +
 +        col = layout.column()
  
          col.prop(part, "use_roughness_curve")
          if part.use_roughness_curve:
@@@ -221,31 -378,9 +221,32 @@@ class RENDER_PT_stamp(RenderButtonsPane
          sub = row.row()
          sub.active = rd.use_stamp_note
          sub.prop(rd, "stamp_note_text", text="")
 -        if rd.use_sequencer:
 -            layout.label("Sequencer:")
 -            layout.prop(rd, "use_stamp_strip_meta")
 +
++
 +class RENDER_PT_stamp_burn(RenderButtonsPanel, Panel):
 +    bl_label = "Burn Into Image"
 +    bl_parent_id = "RENDER_PT_stamp"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
 +
 +    def draw_header(self, context):
 +        rd = context.scene.render
 +
 +        self.layout.prop(rd, "use_stamp", text="")
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        rd = context.scene.render
 +
 +        layout.use_property_split = True
 +
 +        col = layout.column()
 +        col.active = rd.use_stamp
 +        col.prop(rd, "stamp_font_size", text="Font Size")
 +        col.prop(rd, "use_stamp_labels", text="Draw Labels")
 +        col.column().prop(rd, "stamp_foreground", slider=True)
 +        col.column().prop(rd, "stamp_background", slider=True)
  
  
  class RENDER_PT_output(RenderButtonsPanel, Panel):
@@@ -256,49 -261,22 +256,47 @@@ class SCENE_PT_color_management(SceneBu
  
      def draw(self, context):
          layout = self.layout
 +        layout.use_property_split = True
  
          scene = context.scene
 +        view = scene.view_settings
  
          col = layout.column()
 -        col.label(text="Display:")
          col.prop(scene.display_settings, "display_device")
 +        col.prop(scene.sequencer_colorspace_settings, "name", text="Sequencer Color Space")
  
 -        col = layout.column()
          col.separator()
 -        col.label(text="Render:")
 -        col.template_colormanaged_view_settings(scene, "view_settings")
  
          col = layout.column()
 -        col.separator()
 -        col.label(text="Sequencer:")
 -        col.prop(scene.sequencer_colorspace_settings, "name")
 +        col.prop(view, "view_transform")
 +        col.prop(view, "exposure")
 +        col.prop(view, "gamma")
 +        col.prop(view, "look")
 +
 +
 +class SCENE_PT_color_management_curves(SceneButtonsPanel, Panel):
 +    bl_label = "Use Curves"
 +    bl_parent_id = "SCENE_PT_color_management"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    def draw_header(self, context):
 +
 +        scene = context.scene
 +        view = scene.view_settings
 +
 +        self.layout.prop(view, "use_curve_mapping", text="")
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        scene = context.scene
 +        view = scene.view_settings
 +
 +        layout.use_property_split = False
 +        layout.enabled = view.use_curve_mapping
 +
-         layout.template_curve_mapping(view, "curve_mapping", levels = True)
++        layout.template_curve_mapping(view, "curve_mapping", levels=True)
  
  
  class SCENE_PT_audio(SceneButtonsPanel, Panel):
@@@ -59,7 -77,23 +59,8 @@@ class TEXTURE_UL_texslots(UIList)
              layout.alignment = 'CENTER'
              layout.label(text="", icon_value=icon)
  
 -from .properties_material import active_node_mat
 -
 -
  def context_tex_datablock(context):
 -    idblock = context.material
 -    if idblock:
 -        return active_node_mat(idblock)
 -
 -    idblock = context.lamp
 -    if idblock:
 -        return idblock
 -
 -    idblock = context.world
 -    if idblock:
 -        return idblock
 -
      idblock = context.brush
      if idblock:
          return idblock
@@@ -87,30 -125,28 +88,31 @@@ class TEXTURE_PT_preview(TextureButtons
      @classmethod
      def poll(cls, context):
          tex = context.texture
 -        return tex and (tex.type != 'NONE' or tex.use_nodes) and (context.scene.render.engine in cls.COMPAT_ENGINES)
 +        return tex and (tex.type != 'NONE' or tex.use_nodes) and (context.engine in cls.COMPAT_ENGINES)
 +
 +    def draw(self, context):
 +        layout = self.layout
  
 +        tex = context.texture
 +        slot = getattr(context, "texture_slot", None)
 +        idblock = context_tex_datablock(context)
  
 -class TEXTURE_PT_context_texture(TextureButtonsPanel, Panel):
 +        if idblock:
 +            layout.template_preview(tex, parent=idblock, slot=slot)
 +        else:
 +            layout.template_preview(tex, slot=slot)
 +
 +        # Show Alpha Button for Brush Textures, see #29502
 +        idblock = context_tex_datablock(context)
 +        if isinstance(idblock, Brush):
 +            layout.prop(tex, "use_preview_alpha")
 +
++
 +class TEXTURE_PT_context(TextureButtonsPanel, Panel):
      bl_label = ""
 +    bl_context = "texture"
      bl_options = {'HIDE_HEADER'}
 -    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'}
 -
 -    @classmethod
 -    def poll(cls, context):
 -        engine = context.scene.render.engine
 -        # if not (hasattr(context, "texture_slot") or hasattr(context, "texture_node")):
 -        #     return False
 -        return ((context.material or
 -                 context.world or
 -                 context.lamp or
 -                 context.texture or
 -                 context.line_style or
 -                 context.particle_system or
 -                 isinstance(context.space_data.pin_id, ParticleSettings) or
 -                 context.texture_user) and
 -                (engine in cls.COMPAT_ENGINES))
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
  
      def draw(self, context):
          layout = self.layout
index 3c8552c,0000000..013fac3
mode 100644,000000..100644
--- /dev/null
@@@ -1,87 -1,0 +1,87 @@@
-         layout.prop(layer, "use", text="Use for Rendering");
 +# ##### BEGIN GPL LICENSE BLOCK #####
 +#
 +#  This program is free software; you can redistribute it and/or
 +#  modify it under the terms of the GNU General Public License
 +#  as published by the Free Software Foundation; either version 2
 +#  of the License, or (at your option) any later version.
 +#
 +#  This program is distributed in the hope that it will be useful,
 +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +#  GNU General Public License for more details.
 +#
 +#  You should have received a copy of the GNU General Public License
 +#  along with this program; if not, write to the Free Software Foundation,
 +#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 +#
 +# ##### END GPL LICENSE BLOCK #####
 +
 +# <pep8 compliant>
 +import bpy
 +from bpy.types import Panel, UIList
 +
 +
 +class ViewLayerButtonsPanel:
 +    bl_space_type = 'PROPERTIES'
 +    bl_region_type = 'WINDOW'
 +    bl_context = "view_layer"
 +    # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
 +
 +    @classmethod
 +    def poll(cls, context):
 +        return (context.engine in cls.COMPAT_ENGINES)
 +
 +
 +class VIEWLAYER_PT_layer(ViewLayerButtonsPanel, Panel):
 +    bl_label = "View Layer"
 +    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_CLAY', 'BLENDER_EEVEE'}
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        scene = context.scene
 +        rd = scene.render
 +        layer = bpy.context.view_layer
 +
++        layout.prop(layer, "use", text="Use for Rendering")
 +        layout.prop(rd, "use_single_layer", text="Render Single Layer")
 +
 +
 +class VIEWLAYER_PT_eevee_layer_passes(ViewLayerButtonsPanel, Panel):
 +    bl_label = "Passes"
 +    bl_options = {'DEFAULT_CLOSED'}
 +    COMPAT_ENGINES = {'BLENDER_EEVEE'}
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        scene = context.scene
 +        rd = scene.render
 +        view_layer = context.view_layer
 +
 +        split = layout.split()
 +
 +        col = split.column()
 +        col.prop(view_layer, "use_pass_combined")
 +        col.prop(view_layer, "use_pass_z")
 +        col.prop(view_layer, "use_pass_mist")
 +        col.prop(view_layer, "use_pass_normal")
 +        col.separator()
 +        col.prop(view_layer, "use_pass_ambient_occlusion")
 +
 +        col = split.column()
 +        col.label(text="Subsurface:")
 +        row = col.row(align=True)
 +        row.prop(view_layer, "use_pass_subsurface_direct", text="Direct", toggle=True)
 +        row.prop(view_layer, "use_pass_subsurface_color", text="Color", toggle=True)
 +
 +
 +classes = (
 +    VIEWLAYER_PT_layer,
 +    VIEWLAYER_PT_eevee_layer_passes,
 +)
 +
 +if __name__ == "__main__":  # only for live edit.
 +    from bpy.utils import register_class
 +    for cls in classes:
 +        register_class(cls)
@@@ -469,69 -449,8 +469,69 @@@ class DOPESHEET_MT_delete(Menu)
          layout.operator("action.clean", text="Clean Channels").channels = True
  
  
-         layout.operator("anim.channels_setting_enable", text="Mute Channels").type='MUTE'
-         layout.operator("anim.channels_setting_disable", text="Unmute Channels").type='MUTE'
 +class DOPESHEET_MT_specials(Menu):
 +    bl_label = "Dope Sheet Context Menu"
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        layout.operator("action.copy", text="Copy")
 +        layout.operator("action.paste", text="Paste")
 +        layout.operator("action.paste", text="Paste Flipped").flipped = True
 +
 +        layout.separator()
 +
 +        layout.operator_menu_enum("action.handle_type", "type", text="Handle Type")
 +        layout.operator_menu_enum("action.interpolation_type", "type", text="Interpolation Mode")
 +        layout.operator_menu_enum("action.easing_type", "type", text="Easing Type")
 +
 +        layout.separator()
 +
 +        layout.operator("action.keyframe_insert").type = 'SEL'
 +        layout.operator("action.duplicate_move")
 +        layout.operator("action.delete")
 +
 +        layout.separator()
 +
 +        layout.operator_menu_enum("action.mirror", "type", text="Mirror")
 +        layout.operator_menu_enum("action.snap", "type", text="Snap")
 +
++
 +class DOPESHEET_MT_channel_specials(Menu):
 +    bl_label = "Dope Sheet Channel Context Menu"
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
-         layout.operator("anim.channels_setting_enable", text="Protect Channels").type='PROTECT'
-         layout.operator("anim.channels_setting_disable", text="Unprotect Channels").type='PROTECT'
++        layout.operator("anim.channels_setting_enable", text="Mute Channels").type = 'MUTE'
++        layout.operator("anim.channels_setting_disable", text="Unmute Channels").type = 'MUTE'
 +        layout.separator()
++        layout.operator("anim.channels_setting_enable", text="Protect Channels").type = 'PROTECT'
++        layout.operator("anim.channels_setting_disable", text="Unprotect Channels").type = 'PROTECT'
 +
 +        layout.separator()
 +        layout.operator("anim.channels_group")
 +        layout.operator("anim.channels_ungroup")
 +
 +        layout.separator()
 +        layout.operator("anim.channels_editable_toggle")
 +        layout.operator_menu_enum("action.extrapolation_type", "type", text="Extrapolation Mode")
 +
 +        layout.separator()
 +        layout.operator("anim.channels_expand")
 +        layout.operator("anim.channels_collapse")
 +
 +        layout.separator()
 +        layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")
 +
 +        layout.separator()
 +
 +        layout.operator("anim.channels_delete")
 +
 +
  classes = (
      DOPESHEET_HT_header,
 +    DOPESHEET_HT_editor_buttons,
      DOPESHEET_MT_editor_menus,
      DOPESHEET_MT_view,
      DOPESHEET_MT_select,
@@@ -300,75 -302,6 +300,75 @@@ class GRAPH_MT_delete(Menu)
          layout.operator("graph.clean", text="Clean Channels").channels = True
  
  
-         layout.operator("anim.channels_setting_enable", text="Mute Channels").type='MUTE'
-         layout.operator("anim.channels_setting_disable", text="Unmute Channels").type='MUTE'
 +class GRAPH_MT_specials(Menu):
 +    bl_label = "F-Curve Context Menu"
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        layout.operator("graph.copy", text="Copy")
 +        layout.operator("graph.paste", text="Paste")
 +        layout.operator("graph.paste", text="Paste Flipped").flipped = True
 +
 +        layout.separator()
 +
 +        layout.operator_menu_enum("graph.handle_type", "type", text="Handle Type")
 +        layout.operator_menu_enum("graph.interpolation_type", "type", text="Interpolation Mode")
 +        layout.operator_menu_enum("graph.easing_type", "type", text="Easing Type")
 +
 +        layout.separator()
 +
 +        layout.operator("graph.keyframe_insert").type = 'SEL'
 +        layout.operator("graph.duplicate_move")
 +        layout.operator("graph.delete")
 +
 +        layout.separator()
 +
 +        layout.operator_menu_enum("graph.mirror", "type", text="Mirror")
 +        layout.operator_menu_enum("graph.snap", "type", text="Snap")
 +
 +
 +class GRAPH_MT_channel_specials(Menu):
 +    bl_label = "F-Curve Channel Context Menu"
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        st = context.space_data
 +
 +        layout.separator()
-         layout.operator("anim.channels_setting_enable", text="Protect Channels").type='PROTECT'
-         layout.operator("anim.channels_setting_disable", text="Unprotect Channels").type='PROTECT'
++        layout.operator("anim.channels_setting_enable", text="Mute Channels").type = 'MUTE'
++        layout.operator("anim.channels_setting_disable", text="Unmute Channels").type = 'MUTE'
 +        layout.separator()
++        layout.operator("anim.channels_setting_enable", text="Protect Channels").type = 'PROTECT'
++        layout.operator("anim.channels_setting_disable", text="Unprotect Channels").type = 'PROTECT'
 +
 +        layout.separator()
 +        layout.operator("anim.channels_group")
 +        layout.operator("anim.channels_ungroup")
 +
 +        layout.separator()
 +        layout.operator("anim.channels_editable_toggle")
 +        layout.operator_menu_enum("graph.extrapolation_type", "type", text="Extrapolation Mode")
 +
 +        layout.separator()
 +        layout.operator("graph.hide", text="Hide Selected Curves").unselected = False
 +        layout.operator("graph.hide", text="Hide Unselected Curves").unselected = True
 +        layout.operator("graph.reveal")
 +
 +        layout.separator()
 +        layout.operator("anim.channels_expand")
 +        layout.operator("anim.channels_collapse")
 +
 +        layout.separator()
 +        layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")
 +
 +        layout.separator()
 +
 +        layout.operator("anim.channels_delete")
 +        if st.mode == 'DRIVERS':
 +            layout.operator("graph.driver_delete_invalid")
 +
 +
  classes = (
      GRAPH_HT_header,
      GRAPH_MT_editor_menus,
@@@ -171,107 -127,8 +171,107 @@@ class OUTLINER_MT_collection(Menu)
  
          layout.separator()
  
 -        layout.operator("outliner.drivers_add_selected")
 -        layout.operator("outliner.drivers_delete_selected")
 +        layout.operator("outliner.collection_objects_select", text="Select Objects")
 +        layout.operator("outliner.collection_objects_deselect", text="Deselect Objects")
 +
 +        layout.separator()
 +
 +        layout.operator("outliner.collection_instance", text="Instance to Scene")
 +        if space.display_mode != 'VIEW_LAYER':
 +            layout.operator("outliner.collection_link", text="Link to Scene")
-         layout.operator("outliner.id_operation", text="Unlink").type='UNLINK'
++        layout.operator("outliner.id_operation", text="Unlink").type = 'UNLINK'
 +
 +        if space.display_mode == 'VIEW_LAYER':
 +            layout.separator()
 +            layout.menu("OUTLINER_MT_collection_view_layer")
 +
 +        layout.separator()
 +        layout.operator_menu_enum("outliner.id_operation", 'type', text="ID Data")
 +
 +
 +class OUTLINER_MT_collection_new(Menu):
 +    bl_label = "Collection"
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        layout.operator("outliner.collection_new", text="New").nested = False
 +
 +
 +class OUTLINER_MT_object(Menu):
 +    bl_label = "Object"
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        space = context.space_data
 +
-         layout.operator("outliner.object_operation", text="Delete").type='DELETE'
++        layout.operator("outliner.object_operation", text="Delete").type = 'DELETE'
 +        if space.display_mode == 'VIEW_LAYER' and not space.use_filter_collection:
-             layout.operator("outliner.object_operation", text="Delete Hierarchy").type='DELETE_HIERARCHY'
++            layout.operator("outliner.object_operation", text="Delete Hierarchy").type = 'DELETE_HIERARCHY'
 +
 +        layout.separator()
 +
-         layout.operator("outliner.object_operation", text="Select").type='SELECT'
-         layout.operator("outliner.object_operation", text="Select Hierarchy").type='SELECT_HIERARCHY'
-         layout.operator("outliner.object_operation", text="Deselect").type='DESELECT'
++        layout.operator("outliner.object_operation", text="Select").type = 'SELECT'
++        layout.operator("outliner.object_operation", text="Select Hierarchy").type = 'SELECT_HIERARCHY'
++        layout.operator("outliner.object_operation", text="Deselect").type = 'DESELECT'
 +
 +        layout.separator()
 +
 +        if not (space.display_mode == 'VIEW_LAYER' and not space.use_filter_collection):
-             layout.operator("outliner.id_operation", text="Unlink").type='UNLINK'
++            layout.operator("outliner.id_operation", text="Unlink").type = 'UNLINK'
 +            layout.separator()
 +
 +        layout.operator_menu_enum("outliner.id_operation", 'type', text="ID Data")
 +
 +
 +class OUTLINER_PT_filter(Panel):
 +    bl_space_type = 'OUTLINER'
 +    bl_region_type = 'HEADER'
 +    bl_label = "Filter"
 +
 +    def draw(self, context):
 +        layout = self.layout
 +
 +        space = context.space_data
 +        display_mode = space.display_mode
 +
 +        layout.prop(space, "use_filter_collection", text="Collections")
 +
 +        layout.separator()
 +
 +        col = layout.column()
 +        col.prop(space, "use_filter_object", text="Objects")
 +        active = space.use_filter_object
 +
 +        sub = col.column(align=True)
 +        sub.active = active
 +        sub.prop(space, "filter_state", text="")
 +        sub.prop(space, "use_filter_object_content", text="Object Contents")
 +        sub.prop(space, "use_filter_children", text="Object Children")
 +
 +        layout.separator()
 +
 +        col = layout.column_flow(align=True)
 +        col.active = active
 +
 +        if bpy.data.meshes:
 +            col.prop(space, "use_filter_object_mesh", text="Meshes")
 +        if bpy.data.armatures:
 +            col.prop(space, "use_filter_object_armature", text="Armatures")
 +        if bpy.data.lamps:
 +            col.prop(space, "use_filter_object_lamp", text="Lamps")
 +        if bpy.data.cameras:
 +            col.prop(space, "use_filter_object_camera", text="Cameras")
 +
 +        col.prop(space, "use_filter_object_empty", text="Empties")
 +
 +        if bpy.data.curves or \
 +           bpy.data.metaballs or \
 +           bpy.data.lightprobes or \
 +           bpy.data.lattices or \
 +           bpy.data.fonts or bpy.data.speakers:
 +            col.prop(space, "use_filter_object_others", text="Others")
  
  
  classes = (
index 983b474,0000000..3a6fc49
mode 100644,000000..100644
--- /dev/null
@@@ -1,84 -1,0 +1,83 @@@
 +# ##### BEGIN GPL LICENSE BLOCK #####
 +#
 +#  This program is free software; you can redistribute it and/or
 +#  modify it under the terms of the GNU General Public License
 +#  as published by the Free Software Foundation; either version 2
 +#  of the License, or (at your option) any later version.
 +#
 +#  This program is distributed in the hope that it will be useful,
 +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +#  GNU General Public License for more details.
 +#
 +#  You should have received a copy of the GNU General Public License
 +#  along with this program; if not, write to the Free Software Foundation,
 +#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 +#
 +# ##### END GPL LICENSE BLOCK #####
 +
 +# <pep8 compliant>
 +import bpy
 +from bpy.types import Header
 +
 +
 +class STATUSBAR_HT_header(Header):
 +    bl_space_type = 'STATUSBAR'
 +
 +    def draw(self, context):
 +        area = context.area
 +        region = context.region
 +
 +        if region.alignment == 'RIGHT':
 +            if region == area.regions[0]:
 +                self.draw_right(context)
 +            else:
 +                self.draw_center(context)
 +        else:
 +            self.draw_left(context)
 +
 +    def draw_left(self, context):
 +        layout = self.layout
 +
 +        row = layout.row(align=True)
 +        if (bpy.data.filepath):
 +            row.label(text=bpy.data.filepath, translate=False)
 +        if bpy.data.is_dirty:
 +            row.label("(Modified)")
 +
 +    def draw_center(self, context):
 +        layout = self.layout
 +
 +        scene = context.scene
 +        view_layer = context.view_layer
 +
 +        layout.label(text=scene.statistics(view_layer), translate=False)
 +
 +    def draw_right(self, context):
 +        layout = self.layout
 +
 +        layout.template_running_jobs()
 +        layout.template_reports_banner()
 +
 +        row = layout.row(align=True)
 +        if bpy.app.autoexec_fail is True and bpy.app.autoexec_fail_quiet is False:
 +            row.label("Auto-run disabled", icon='ERROR')
 +            if bpy.data.is_saved:
 +                props = row.operator("wm.revert_mainfile", icon='SCREEN_BACK', text="Reload Trusted")
 +                props.use_scripts = True
 +
 +            row.operator("script.autoexec_warn_clear", text="Ignore")
 +
 +            # include last so text doesn't push buttons out of the header
 +            row.label(bpy.app.autoexec_fail_message)
 +            return
 +
 +
 +classes = (
 +    STATUSBAR_HT_header,
 +)
 +
 +if __name__ == "__main__":  # only for live edit.
 +    from bpy.utils import register_class
 +    for cls in classes:
 +        register_class(cls)
@@@ -108,15 -109,10 +108,16 @@@ class TIME_MT_editor_menus(Menu)
      def draw_menus(layout, context):
          layout.menu("TIME_MT_view")
          layout.menu("TIME_MT_marker")
 -        layout.menu("TIME_MT_frame")
 -        layout.menu("TIME_MT_playback")
 +        layout.popover(space_type='DOPESHEET_EDITOR',
 +                       region_type='HEADER',
 +                       panel_type="TIME_PT_playback",
 +                       text="Playback")
 +        layout.popover(space_type='DOPESHEET_EDITOR',
 +                       region_type='HEADER',
 +                       panel_type="TIME_PT_keyframing_settings",
 +                       text="Keying")
  
  class TIME_MT_marker(Menu):
      bl_label = "Marker"
  
@@@ -180,55 -182,25 +181,56 @@@ class TIME_MT_cache(Menu)
          col.prop(st, "cache_rigidbody")
  
  
 -class TIME_MT_frame(Menu):
 -    bl_label = "Frame"
 +def marker_menu_generic(layout):
 +    from bpy import context
  
 -    def draw(self, context):
 -        layout = self.layout
 +    # layout.operator_context = 'EXEC_REGION_WIN'
  
 -        layout.operator("anim.previewrange_clear")
 -        layout.operator("anim.previewrange_set")
 -        layout.separator()
 -        layout.operator("time.end_frame_set")
 -        layout.operator("time.start_frame_set")
 +    layout.column()
 +    layout.operator("marker.add", "Add Marker")
 +    layout.operator("marker.duplicate", text="Duplicate Marker")
  
 -        layout.separator()
 +    if len(bpy.data.scenes) > 10:
 +        layout.operator_context = 'INVOKE_DEFAULT'
 +        layout.operator("marker.make_links_scene", text="Duplicate Marker to Scene...", icon='OUTLINER_OB_EMPTY')
 +    else:
 +        layout.operator_menu_enum("marker.make_links_scene", "scene", text="Duplicate Marker to Scene")
 +
 +    layout.operator("marker.delete", text="Delete Marker")
 +
 +    layout.separator()
  
 -        layout.menu("TIME_MT_autokey")
 +    layout.operator("marker.rename", text="Rename Marker")
 +    layout.operator("marker.move", text="Grab/Move Marker")
 +
 +    layout.separator()
 +
 +    layout.operator("marker.camera_bind")
  
 +    layout.separator()
 +
 +    layout.operator("screen.marker_jump", text="Jump to Next Marker").next = True
 +    layout.operator("screen.marker_jump", text="Jump to Previous Marker").next = False
  
 -class TIME_MT_playback(Menu):
 +    layout.separator()
 +    ts = context.tool_settings
 +    layout.prop(ts, "lock_markers")
 +
 +###################################
 +
++
 +class TimelinePanelButtons:
 +    bl_space_type = 'DOPESHEET_EDITOR'
 +    bl_region_type = 'UI'
 +
 +    @staticmethod
 +    def has_timeline(context):
 +        return context.space_data.mode == 'TIMELINE'
 +
 +
 +class TIME_PT_playback(TimelinePanelButtons, Panel):
      bl_label = "Playback"
 +    bl_region_type = 'HEADER'
  
      def draw(self, context):
          layout = self.layout
index cb6d385,0000000..a909dad
mode 100644,000000..100644
--- /dev/null
@@@ -1,686 -1,0 +1,686 @@@
 +# ##### BEGIN GPL LICENSE BLOCK #####
 +#
 +#  This program is free software; you can redistribute it and/or
 +#  modify it under the terms of the GNU General Public License
 +#  as published by the Free Software Foundation; either version 2
 +#  of the License, or (at your option) any later version.
 +#
 +#  This program is distributed in the hope that it will be useful,
 +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +#  GNU General Public License for more details.
 +#
 +#  You should have received a copy of the GNU General Public License
 +#  along with this program; if not, write to the Free Software Foundation,
 +#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 +#
 +# ##### END GPL LICENSE BLOCK #####
 +
 +# <pep8 compliant>
 +import bpy
 +from bpy.types import (
 +    Menu,
 +)
 +
 +__all__ = (
 +    "ToolSelectPanelHelper",
 +    "ToolDef",
 +)
 +
 +# Support reloading icons.
 +if "_icon_cache" in locals():
 +    release = bpy.app.icons.release
 +    for icon_value in _icon_cache.values():
 +        if icon_value != 0:
 +            release(icon_value)
 +    del release
 +
 +
 +# (filename -> icon_value) map
 +_icon_cache = {}
 +
 +
 +def _keymap_fn_from_seq(keymap_data):
 +
 +    # standalone
 +    def _props_assign_recursive(rna_props, py_props):
 +        for prop_id, value in py_props.items():
 +            if isinstance(value, dict):
 +                _props_assign_recursive(getattr(rna_props, prop_id), value)
 +            else:
 +                setattr(rna_props, prop_id, value)
 +
 +    def keymap_fn(km):
 +        for op_idname, op_props_dict, kmi_kwargs in keymap_fn.keymap_data:
 +            kmi = km.keymap_items.new(op_idname, **kmi_kwargs)
 +            kmi_props = kmi.properties
 +            if op_props_dict:
 +                _props_assign_recursive(kmi.properties, op_props_dict)
 +    keymap_fn.keymap_data = keymap_data
 +    return keymap_fn
 +
 +
 +def _item_is_fn(item):
 +    return (not (type(item) is ToolDef) and callable(item))
 +
 +
 +from collections import namedtuple
 +ToolDef = namedtuple(
 +    "ToolDef",
 +    (
 +        # The name to display in the interface.
 +        "text",
 +        # The name of the icon to use (found in ``release/datafiles/icons``) or None for no icon.
 +        "icon",
 +        # An optional cursor to use when this tool is active.
 +        "cursor",
 +        # An optional manipulator group to activate when the tool is set or None for no widget.
 +        "widget",
 +        # Optional keymap for tool, either:
 +        # - A function that populates a keymaps passed in as an argument.
 +        # - A tuple filled with triple's of:
 +        #   ``(operator_id, operator_properties, keymap_item_args)``.
 +        #
 +        # Warning: currently 'from_dict' this is a list of one item,
 +        # so internally we can swap the keymap function for the keymap it's self.
 +        # This isn't very nice and may change, tool definitions shouldn't care about this.
 +        "keymap",
 +        # Optional data-block assosiated with this tool.
 +        # (Typically brush name, usage depends on mode, we could use for non-brush ID's in other modes).
 +        "data_block",
 +        # Optional draw settings (operator options, toolsettings).
 +        "draw_settings",
 +    )
 +)
 +del namedtuple
 +
 +
 +def from_dict(kw_args):
 +    """
 +    Use so each tool can avoid defining all members of the named tuple.
 +    Also convert the keymap from a tuple into a function
 +    (since keymap is a callback).
 +    """
 +    kw = {
 +        "icon": None,
 +        "cursor": None,
 +        "widget": None,
 +        "keymap": None,
 +        "data_block": None,
 +        "draw_settings": None,
 +    }
 +    kw.update(kw_args)
 +
 +    keymap = kw["keymap"]
 +    if kw["keymap"] is None:
 +        pass
 +    elif type(keymap) is tuple:
 +        keymap = [_keymap_fn_from_seq(keymap)]
 +    else:
 +        keymap = [keymap]
 +    kw["keymap"] = keymap
 +    return ToolDef(**kw)
 +
 +
 +def from_fn(fn):
 +    """
 +    Use as decorator so we can define functions.
 +    """
 +    return ToolDef.from_dict(fn())
 +
 +
 +ToolDef.from_dict = from_dict
 +ToolDef.from_fn = from_fn
 +del from_dict
 +del from_fn
 +
 +
 +class ToolSelectPanelHelper:
 +    """
 +    Generic Class, can be used for any toolbar.
 +
 +    - keymap_prefix:
 +      The text prefix for each key-map for this spaces tools.
 +    - tools_all():
 +      Returns (context_mode, tools) tuple pair for all tools defined.
 +    - tools_from_context(context, mode=None):
 +      Returns tools available in this context.
 +
 +    Each tool is a 'ToolDef' or None for a separator in the toolbar, use ``None``.
 +    """
 +
 +    @staticmethod
 +    def _tool_class_from_space_type(space_type):
 +        return next(
 +            (cls for cls in ToolSelectPanelHelper.__subclasses__()
 +             if cls.bl_space_type == space_type),
 +            None
 +        )
 +
 +    @staticmethod
 +    def _icon_value_from_icon_handle(icon_name):
 +        import os
 +        if icon_name is not None:
 +            assert(type(icon_name) is str)
 +            icon_value = _icon_cache.get(icon_name)
 +            if icon_value is None:
 +                dirname = bpy.utils.resource_path('LOCAL')
 +                if not os.path.exists(dirname):
 +                    # TODO(campbell): use a better way of finding datafiles.
 +                    dirname = bpy.utils.resource_path('SYSTEM')
 +                filename = os.path.join(dirname, "datafiles", "icons", icon_name + ".dat")
 +                try:
 +                    icon_value = bpy.app.icons.new_triangles_from_file(filename)
 +                except Exception as ex:
 +                    if not os.path.exists(filename):
 +                        print("Missing icons:", filename, ex)
 +                    else:
 +                        print("Corrupt icon:", filename, ex)
 +                    # Use none as a fallback (avoids layout issues).
 +                    if icon_name != "none":
 +                        icon_value = ToolSelectPanelHelper._icon_value_from_icon_handle("none")
 +                    else:
 +                        icon_value = 0
 +                _icon_cache[icon_name] = icon_value
 +            return icon_value
 +        else:
 +            return 0
 +
 +    @staticmethod
 +    def _tools_flatten(tools):
 +        """
 +        Flattens, skips None and calls generators.
 +        """
 +        for item in tools:
 +            if item is None:
 +                yield None
 +            elif type(item) is tuple:
 +                for sub_item in item:
 +                    if sub_item is None:
 +                        yield None
 +                    elif _item_is_fn(sub_item):
 +                        yield from sub_item(context)
 +                    else:
 +                        yield sub_item
 +            else:
 +                if _item_is_fn(item):
 +                    yield from item(context)
 +                else:
 +                    yield item
 +
 +    @staticmethod
 +    def _tools_flatten_with_tool_index(tools):
 +        for item in tools:
 +            if item is None:
 +                yield None, -1
 +            elif type(item) is tuple:
 +                i = 0
 +                for sub_item in item:
 +                    if sub_item is None:
 +                        yield None
 +                    elif _item_is_fn(sub_item):
 +                        for item_dyn in sub_item(context):
 +                            yield item_dyn, i
 +                            i += 1
 +                    else:
 +                        yield sub_item, i
 +                        i += 1
 +            else:
 +                if _item_is_fn(item):
 +                    for item_dyn in item(context):
 +                        yield item_dyn, -1
 +                else:
 +                    yield item, -1
 +
 +    @staticmethod
 +    def _tool_get_active(context, space_type, mode, with_icon=False):
 +        """
 +        Return the active Python tool definition and icon name.
 +        """
 +        workspace = context.workspace
 +        cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
 +        if cls is not None:
 +            tool_active = ToolSelectPanelHelper._tool_active_from_context(context, space_type, mode)
 +            tool_active_text = getattr(tool_active, "name", None)
 +            for item in ToolSelectPanelHelper._tools_flatten(cls.tools_from_context(context, mode)):
 +                if item is not None:
 +                    if item.text == tool_active_text:
 +                        if with_icon:
 +                            icon_value = ToolSelectPanelHelper._icon_value_from_icon_handle(item.icon)
 +                        else:
 +                            icon_value = 0
 +                        return (item, tool_active, icon_value)
 +        return None, None, 0
 +
 +    @staticmethod
 +    def _tool_get_by_name(context, space_type, text):
 +        """
 +        Return the active Python tool definition and index (if in sub-group, else -1).
 +        """
 +        cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
 +        if cls is not None:
 +            for item, index in ToolSelectPanelHelper._tools_flatten_with_tool_index(cls.tools_from_context(context)):
 +                if item is not None:
 +                    if item.text == text:
 +                        return (cls, item, index)
 +        return None, None, -1
 +
 +    @staticmethod
 +    def _tool_active_from_context(context, space_type, mode=None, create=False):
 +        if space_type == 'VIEW_3D':
 +            if mode is None:
 +                mode = context.mode
 +            tool = context.workspace.tools.from_space_view3d_mode(mode, create)
 +            if tool is not None:
 +                return tool
 +        elif space_type == 'IMAGE_EDITOR':
 +            space_data = context.space_data
 +            if mode is None:
 +                mode = space_data.mode
 +            tool = context.workspace.tools.from_space_image_mode(mode, create)
 +            if tool is not None:
 +                return tool
 +        return None
 +
 +    @staticmethod
 +    def _tool_text_from_button(context):
 +        return context.button_operator.name
 +
 +    @classmethod
 +    def _km_action_simple(cls, kc, context_mode, text, keymap_fn):
 +        if context_mode is None:
 +            context_mode = "All"
 +        km_idname = f"{cls.keymap_prefix} {context_mode}, {text}"
 +        km = kc.keymaps.get(km_idname)
 +        if km is None:
 +            km = kc.keymaps.new(km_idname, space_type=cls.bl_space_type, region_type='WINDOW')
 +            keymap_fn[0](km)
 +        keymap_fn[0] = km
 +
 +    @classmethod
 +    def register(cls):
 +        wm = bpy.context.window_manager
 +
 +        # XXX, should we be manipulating the user-keyconfig on load?
 +        # Perhaps this should only add when keymap items don't already exist.
 +        #
 +        # This needs some careful consideration.
 +        kc = wm.keyconfigs.user
 +
 +        # Track which tool-group was last used for non-active groups.
 +        # Blender stores the active tool-group index.
 +        #
 +        # {tool_name_first: index_in_group, ...}
 +        cls._tool_group_active = {}
 +
 +        # ignore in background mode
 +        if kc is None:
 +            return
 +
 +        for context_mode, tools in cls.tools_all():
 +            for item_parent in tools:
 +                if item_parent is None:
 +                    continue
 +                for item in item_parent if (type(item_parent) is tuple) else (item_parent,):
 +                    # skip None or generator function
 +                    if item is None or _item_is_fn(item):
 +                        continue
 +                    keymap_data = item.keymap
 +                    if keymap_data is not None and callable(keymap_data[0]):
 +                        text = item.text
 +                        icon_name = item.icon
 +                        cls._km_action_simple(kc, context_mode, text, keymap_data)
 +
 +    # -------------------------------------------------------------------------
 +    # Layout Generators
 +    #
 +    # Meaning of recieved values:
 +    # - Bool: True for a separator, otherwise False for regular tools.
 +    # - None: Signal to finish (complete any final operations, e.g. add padding).
 +
 +    @staticmethod
 +    def _layout_generator_single_column(layout):
 +        scale_y = 2.0
 +
 +        col = layout.column(align=True)
 +        col.scale_y = scale_y
 +        is_sep = False
 +        while True:
 +            if is_sep is True:
 +                col = layout.column(align=True)
 +                col.scale_y = scale_y
 +            elif is_sep is None:
 +                yield None
 +                return
 +            is_sep = yield col
 +
 +    @staticmethod
 +    def _layout_generator_multi_columns(layout, column_count):
 +        scale_y = 2.0
 +        scale_x = 2.2
 +        column_last = column_count - 1
 +
 +        col = layout.column(align=True)
 +
 +        row = col.row(align=True)
 +
 +        row.scale_x = scale_x
 +        row.scale_y = scale_y
 +
 +        is_sep = False
 +        column_index = 0
 +        while True:
 +            if is_sep is True:
 +                if column_index != column_last:
 +                    row.label("")
 +                col = layout.column(align=True)
 +                row = col.row(align=True)
 +                row.scale_x = scale_x
 +                row.scale_y = scale_y
 +                column_index = 0
 +
 +            is_sep = yield row
 +            if is_sep is None:
 +                if column_index == column_last:
 +                    row.label("")
 +                    yield None
 +                    return
 +
 +            if column_index == column_count:
 +                column_index = 0
 +                row = col.row(align=True)
 +                row.scale_x = scale_x
 +                row.scale_y = scale_y
 +            column_index += 1
 +
 +    @staticmethod
 +    def _layout_generator_detect_from_region(layout, region):
 +        """
 +        Choose an appropriate layout for the toolbar.
 +        """
 +        # Currently this just checks the width,
 +        # we could have different layouts as preferences too.
 +        system = bpy.context.user_preferences.system
 +        view2d = region.view2d
 +        view2d_scale = (
 +            view2d.region_to_view(1.0, 0.0)[0] -
 +            view2d.region_to_view(0.0, 0.0)[0]
 +        )
 +        width_scale = region.width * view2d_scale / system.ui_scale
 +
 +        if width_scale > 120.0:
 +            show_text = True
 +            column_count = 1
 +        else:
 +            show_text = False
 +            # 2 column layout, disabled
 +            if width_scale > 80.0:
 +                column_count = 2
 +                use_columns = True
 +            else:
 +                column_count = 1
 +
 +        if column_count == 1:
 +            ui_gen = ToolSelectPanelHelper._layout_generator_single_column(layout)
 +        else:
 +            ui_gen = ToolSelectPanelHelper._layout_generator_multi_columns(layout, column_count=column_count)
 +
 +        return ui_gen, show_text
 +
 +    @classmethod
 +    def draw_cls(cls, layout, context, detect_layout=True):
 +        # Use a classmethod so it can be called outside of a panel context.
 +
 +        # XXX, this UI isn't very nice.
 +        # We might need to create new button types for this.
 +        # Since we probably want:
 +        # - tool-tips that include multiple key shortcuts.
 +        # - ability to click and hold to expose sub-tools.
 +
 +        space_type = context.space_data.type
 +        tool_active_text = getattr(
 +            ToolSelectPanelHelper._tool_active_from_context(context, space_type),
 +            "name", None,
 +        )
 +
 +        if detect_layout:
 +            ui_gen, show_text = cls._layout_generator_detect_from_region(layout, context.region)
 +        else:
 +            ui_gen = ToolSelectPanelHelper._layout_generator_single_column(layout)
 +            show_text = True
 +
 +        # Start iteration
 +        ui_gen.send(None)
 +
 +        for item in cls.tools_from_context(context):
 +            if item is None:
 +                ui_gen.send(True)
 +                continue
 +
 +            if type(item) is tuple:
 +                is_active = False
 +                i = 0
 +                for i, sub_item in enumerate(item):
 +                    if sub_item is None:
 +                        continue
 +                    is_active = (sub_item.text == tool_active_text)
 +                    if is_active:
 +                        index = i
 +                        break
 +                del i, sub_item
 +
 +                if is_active:
 +                    # not ideal, write this every time :S
 +                    cls._tool_group_active[item[0].text] = index
 +                else:
 +                    index = cls._tool_group_active.get(item[0].text, 0)
 +
 +                item = item[index]
 +                use_menu = True
 +            else:
 +                index = -1
 +                use_menu = False
 +
 +            is_active = (item.text == tool_active_text)
 +            icon_value = ToolSelectPanelHelper._icon_value_from_icon_handle(item.icon)
 +
 +            sub = ui_gen.send(False)
 +
 +            if use_menu:
 +                sub.operator_menu_hold(
 +                    "wm.tool_set_by_name",
 +                    text=item.text if show_text else "",
 +                    depress=is_active,
 +                    menu="WM_MT_toolsystem_submenu",
 +                    icon_value=icon_value,
 +                ).name = item.text
 +            else:
 +                sub.operator(
 +                    "wm.tool_set_by_name",
 +                    text=item.text if show_text else "",
 +                    depress=is_active,
 +                    icon_value=icon_value,
 +                ).name = item.text
 +        # Signal to finish any remaining layout edits.
 +        ui_gen.send(None)
 +
 +    def draw(self, context):
 +        self.draw_cls(self.layout, context)
 +
 +    @staticmethod
 +    def draw_active_tool_header(context, layout):
 +        # BAD DESIGN WARNING: last used tool
 +        workspace = context.workspace
 +        space_type = workspace.tools_space_type
 +        mode = workspace.tools_mode
 +        item, tool, icon_value = ToolSelectPanelHelper._tool_get_active(context, space_type, mode, with_icon=True)
 +        if item is None:
 +            return
 +        # Note: we could show 'item.text' here but it makes the layout jitter when switcuing tools.
 +        layout.label(" ", icon_value=icon_value)
 +        draw_settings = item.draw_settings
 +        if draw_settings is not None:
 +            draw_settings(context, layout, tool)
 +
 +
 +# The purpose of this menu is to be a generic popup to select between tools
 +# in cases when a single tool allows to select alternative tools.
 +class WM_MT_toolsystem_submenu(Menu):
 +    bl_label = ""
 +
 +    @staticmethod
 +    def _tool_group_from_button(context):
 +        # Lookup the tool definitions based on the space-type.
 +        cls = ToolSelectPanelHelper._tool_class_from_space_type(context.space_data.type)
 +        if cls is not None:
 +            button_text = ToolSelectPanelHelper._tool_text_from_button(context)
 +            for item_group in cls.tools_from_context(context):
 +                if type(item_group) is tuple:
 +                    for sub_item in item_group:
 +                        if sub_item.text == button_text:
 +                            return cls, item_group
 +        return None, None
 +
 +    def draw(self, context):
 +        layout = self.layout
 +        layout.scale_y = 2.0
 +
 +        cls, item_group = self._tool_group_from_button(context)
 +        if item_group is None:
 +            # Should never happen, just in case
 +            layout.label("Unable to find toolbar group")
 +            return
 +
 +        for item in item_group:
 +            if item is None:
 +                layout.separator()
 +                continue
 +            icon_value = ToolSelectPanelHelper._icon_value_from_icon_handle(item.icon)
 +            layout.operator(
 +                "wm.tool_set_by_name",
 +                text=item.text,
 +                icon_value=icon_value,
 +            ).name = item.text
 +
 +
 +def _activate_by_item(context, space_type, item, index):
 +    tool = ToolSelectPanelHelper._tool_active_from_context(context, space_type, create=True)
 +    tool.setup(
 +        name=item.text,
 +        keymap=item.keymap[0].name if item.keymap is not None else "",
 +        cursor=item.cursor or 'DEFAULT',
 +        manipulator_group=item.widget or "",
 +        data_block=item.data_block or "",
 +        index=index,
 +    )
 +
 +
 +def activate_by_name(context, space_type, text):
 +    cls, item, index = ToolSelectPanelHelper._tool_get_by_name(context, space_type, text)
 +    if item is None:
 +        return False
 +    _activate_by_item(context, space_type, item, index)
 +    return True
 +
 +
 +def activate_by_name_or_cycle(context, space_type, text, offset=1):
 +
 +    # Only cycle when the active tool is activated again.
 +    cls, item, index = ToolSelectPanelHelper._tool_get_by_name(context, space_type, text)
 +    if item is None:
 +        return False
 +
 +    tool_active = ToolSelectPanelHelper._tool_active_from_context(context, space_type)
 +    text_active = getattr(tool_active, "name", None)
 +
 +    text_current = ""
 +    for item_group in cls.tools_from_context(context):
 +        if type(item_group) is tuple:
 +            index_current = cls._tool_group_active.get(item_group[0].text, 0)
 +            ok = False
 +            for i, sub_item in enumerate(item_group):
 +                if sub_item.text == text:
 +                    text_current = item_group[index_current].text
 +                    break
 +            if text_current:
 +                break
 +
 +    if text_current == "":
 +        return activate_by_name(context, space_type, text)
 +    if text_active != text_current:
 +        return activate_by_name(context, space_type, text_current)
 +
 +    index_found = (tool_active.index + offset) % len(item_group)
 +
 +    cls._tool_group_active[item_group[0].text] = index_found
 +
 +    item_found = item_group[index_found]
 +    _activate_by_item(context, space_type, item_found, index_found)
 +    return True
 +
 +
 +def keymap_from_context(context, space_type):
 +    """
 +    Keymap for popup toolbar, currently generated each time.
 +    """
 +    use_simple_keymap = False
 +    km_name = "Toolbar Popup"
 +    wm = context.window_manager
 +    keyconf = wm.keyconfigs.active
 +    keymap = keyconf.keymaps.get(km_name)
 +    if keymap is None:
 +        keymap = keyconf.keymaps.new(km_name, space_type='EMPTY', region_type='TEMPORARY')
 +    for kmi in keymap.keymap_items:
 +        keymap.keymap_items.remove(kmi)
 +
 +    items = []
 +    cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
 +    for i, item in enumerate(
 +            ToolSelectPanelHelper._tools_flatten(cls.tools_from_context(context))
 +    ):
 +        if item is not None:
 +            if use_simple_keymap:
 +                # Simply assign a key from A-Z
 +                items.append(((chr(ord('A') + i)), item.text))
 +                kmi = keymap.keymap_items.new("wm.tool_set_by_name", key, 'PRESS')
 +                kmi.properties.name = item.text
 +                continue
 +
 +            if not item.keymap:
 +                continue
 +
 +            # Only check the first item in the tools key-map (a little arbitrary).
 +            kmi_first = item.keymap[0].keymap_items[0]
 +            kmi_found = wm.keyconfigs.find_item_from_operator(
 +                idname=kmi_first.idname,
 +                # properties=kmi_first.properties,  # prevents matches, don't use.
 +            )[1]
 +            if kmi_found is not None:
 +                kmi_found_type = kmi_found.type
 +                # Only for single keys.
 +                if len(kmi_found_type) == 1:
 +                    kmi = keymap.keymap_items.new(
 +                        idname="wm.tool_set_by_name",
 +                        type=kmi_found_type,
 +                        value='PRESS',
 +                        any=kmi_found.any,
 +                        shift=kmi_found.shift,
 +                        ctrl=kmi_found.ctrl,
 +                        alt=kmi_found.alt,
 +                        oskey=kmi_found.oskey,
 +                        key_modifier=kmi_found.key_modifier,
 +                    )
 +                    kmi.properties.name = item.text
 +
 +    wm.keyconfigs.update()
 +    return keymap
 +
++
 +classes = (
 +    WM_MT_toolsystem_submenu,
 +)
 +
 +if __name__ == "__main__":  # only for live edit.
 +    from bpy.utils import register_class
 +    for cls in classes:
 +        register_class(cls)
index 42fac9e,0000000..da86c1c
mode 100644,000000..100644
--- /dev/null
@@@ -1,1071 -1,0 +1,1070 @@@
-                 ),
 +# ##### BEGIN GPL LICENSE BLOCK #####
 +#
 +#  This program is free software; you can redistribute it and/or
 +#  modify it under the terms of the GNU General Public License
 +#  as published by the Free Software Foundation; either version 2
 +#  of the License, or (at your option) any later version.
 +#
 +#  This program is distributed in the hope that it will be useful,
 +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +#  GNU General Public License for more details.
 +#
 +#  You should have received a copy of the GNU General Public License
 +#  along with this program; if not, write to the Free Software Foundation,
 +#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 +#
 +# ##### END GPL LICENSE BLOCK #####
 +
 +# <pep8 compliant>
 +
 +# For now group all tools together
 +# we may want to move these into per space-type files.
 +#
 +# For now keep this in a single file since it's an area that may change,
 +# so avoid making changes all over the place.
 +
 +from bpy.types import Panel
 +
 +from .space_toolsystem_common import (
 +    ToolSelectPanelHelper,
 +    ToolDef,
 +)
 +
 +
 +def generate_from_brushes_ex(
 +        context, *,
 +        icon_prefix,
 +        brush_test_attr,
 +        brush_category_attr,
 +        brush_category_layout,
 +):
 +    # Categories
 +    brush_categories = {}
 +    for brush in context.blend_data.brushes:
 +        if getattr(brush, brush_test_attr):
 +            category = getattr(brush, brush_category_attr)
 +            name = brush.name
 +            brush_categories.setdefault(category, []).append(
 +                ToolDef.from_dict(
 +                    dict(
 +                        text=name,
 +                        icon=icon_prefix + category.lower(),
 +                        data_block=name,
 +                    )
 +                )
 +            )
 +
 +    def tools_from_brush_group(groups):
 +        assert(type(groups) is tuple)
 +        if len(groups) == 1:
 +            tool_defs = tuple(brush_categories.pop(groups[0], ()))
 +        else:
 +            tool_defs = tuple(item for g in groups for item in brush_categories.pop(g, ()))
 +        if len(tool_defs) > 1:
 +            return (tool_defs,)
 +        else:
 +            return tool_defs
 +
 +    # Each item below is a single toolbar entry:
 +    # Grouped for multiple or none if no brushes are found.
 +    tool_defs = tuple(
 +        tool_def
 +        for category in brush_category_layout
 +        for tool_def in tools_from_brush_group(category)
 +    )
 +    # Ensure we use all types.
 +    if brush_categories:
 +        print(brush_categories)
 +    assert(len(brush_categories) == 0)
 +    return tool_defs
 +
 +
 +class _defs_view3d_generic:
 +    @ToolDef.from_fn
 +    def cursor():
 +        return dict(
 +            text="Cursor",
 +            icon="ops.generic.cursor",
 +            keymap=(
 +                ("view3d.cursor3d", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +                ("transform.translate",
 +                 dict(release_confirm=True, cursor_transform=True),
 +                 dict(type='EVT_TWEAK_A', value='ANY'),
++                 ),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def cursor_click():
 +        return dict(
 +            text="Cursor Click",
 +            icon="ops.generic.cursor",
 +            keymap=(
 +                ("view3d.cursor3d", dict(), dict(type='ACTIONMOUSE', value='CLICK')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def ruler():
 +        return dict(
 +            text="Ruler/Protractor",
 +            icon="ops.view3d.ruler",
 +            widget="VIEW3D_WGT_ruler",
 +            keymap=(
 +                ("view3d.ruler_add", dict(), dict(type='EVT_TWEAK_A', value='ANY')),
 +            ),
 +        )
 +
 +
 +class _defs_transform:
 +
 +    @ToolDef.from_fn
 +    def translate():
 +        return dict(
 +            text="Move",
 +            cursor='SCROLL_XY',
 +            icon="ops.transform.translate",
 +            widget="TRANSFORM_WGT_manipulator",
 +            keymap=(
 +                ("transform.translate", dict(release_confirm=True), dict(type='EVT_TWEAK_A', value='ANY')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def rotate():
 +        return dict(
 +            text="Rotate",
 +            cursor='SCROLL_XY',
 +            icon="ops.transform.rotate",
 +            widget="TRANSFORM_WGT_manipulator",
 +            keymap=(
 +                ("transform.rotate", dict(release_confirm=True), dict(type='EVT_TWEAK_A', value='ANY')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def scale():
 +        return dict(
 +            text="Scale",
 +            cursor='SCROLL_XY',
 +            icon="ops.transform.resize",
 +            widget="TRANSFORM_WGT_manipulator",
 +            keymap=(
 +                ("transform.resize", dict(release_confirm=True), dict(type='EVT_TWEAK_A', value='ANY')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def scale_cage():
 +        return dict(
 +            text="Scale Cage",
 +            icon="ops.transform.resize.cage",
 +            widget="VIEW3D_WGT_xform_cage",
 +        )
 +
 +    @ToolDef.from_fn
 +    def transform():
 +        return dict(
 +            text="Transform",
 +            icon="ops.transform.transform",
 +            widget="TRANSFORM_WGT_manipulator",
 +            # No keymap default action, only for manipulators!
 +        )
 +
 +
 +class _defs_view3d_select:
 +
 +    @ToolDef.from_fn
 +    def border():
 +        return dict(
 +            text="Select Border",
 +            icon="ops.generic.select_border",
 +            widget=None,
 +            keymap=(
 +                ("view3d.select_border",
 +                 dict(deselect=False),
 +                 dict(type='EVT_TWEAK_A', value='ANY')),
 +                ("view3d.select_border",
 +                 dict(deselect=True),
 +                 dict(type='EVT_TWEAK_A', value='ANY', ctrl=True)),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def circle():
 +        return dict(
 +            text="Select Circle",
 +            icon="ops.generic.select_circle",
 +            widget=None,
 +            keymap=(
 +                ("view3d.select_circle",
 +                 dict(deselect=False),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +                ("view3d.select_circle",
 +                 dict(deselect=True),
 +                 dict(type='ACTIONMOUSE', value='PRESS', ctrl=True)),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def lasso():
 +        return dict(
 +            text="Select Lasso",
 +            icon="ops.generic.select_lasso",
 +            widget=None,
 +            keymap=(
 +                ("view3d.select_lasso",
 +                 dict(deselect=False),
 +                 dict(type='EVT_TWEAK_A', value='ANY')),
 +                ("view3d.select_lasso",
 +                 dict(deselect=True),
 +                 dict(type='EVT_TWEAK_A', value='ANY', ctrl=True)),
 +            ),
 +        )
 +# -----------------------------------------------------------------------------
 +# Object Modes (named based on context.mode)
 +
 +
 +class _defs_edit_armature:
 +
 +    @ToolDef.from_fn
 +    def roll():
 +        return dict(
 +            text="Roll",
 +            icon="ops.armature.bone.roll",
 +            widget=None,
 +            keymap=(
 +                ("transform.transform",
 +                 dict(release_confirm=True, mode='BONE_ROLL'),
 +                 dict(type='EVT_TWEAK_A', value='ANY'),),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def bone_envelope():
 +        return dict(
 +            text="Bone Envelope",
 +            icon="ops.transform.bone_envelope",
 +            widget=None,
 +            keymap=(
 +                ("transform.transform",
 +                 dict(release_confirm=True, mode='BONE_ENVELOPE'),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def bone_size():
 +        return dict(
 +            text="Bone Size",
 +            icon="ops.transform.bone_size",
 +            widget=None,
 +            keymap=(
 +                ("transform.transform",
 +                 dict(release_confirm=True, mode='BONE_SIZE'),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def extrude():
 +        return dict(
 +            text="Extrude",
 +            icon="ops.armature.extrude_move",
 +            widget=None,
 +            keymap=(
 +                ("armature.click_extrude", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def extrude_cursor():
 +        return dict(
 +            text="Extrude to Cursor",
 +            icon="ops.armature.extrude_cursor",
 +            widget=None,
 +            keymap=(
 +                ("armature.click_extrude", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +
 +class _defs_edit_mesh:
 +
 +    @ToolDef.from_fn
 +    def cube_add():
 +        return dict(
 +            text="Add Cube",
 +            icon="ops.mesh.primitive_cube_add_manipulator",
 +            widget=None,
 +            keymap=(
 +                ("view3d.cursor3d", dict(), dict(type='ACTIONMOUSE', value='CLICK')),
 +                ("mesh.primitive_cube_add_manipulator", dict(), dict(type='EVT_TWEAK_A', value='ANY')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def rip_region():
 +        def draw_settings(context, layout, tool):
 +            wm = context.window_manager
 +            props = tool.operator_properties("mesh.rip_move")
 +            props_macro = props.MESH_OT_rip
 +            layout.prop(props_macro, "use_fill")
 +
 +        return dict(
 +            text="Rip Region",
 +            icon="ops.mesh.rip",
 +            widget=None,
 +            keymap=(
 +                ("mesh.rip_move",
 +                 dict(TRANSFORM_OT_translate=dict(release_confirm=True)),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +            draw_settings=draw_settings,
 +        )
 +
 +    @ToolDef.from_fn
 +    def rip_edge():
 +        return dict(
 +            text="Rip Edge",
 +            icon="ops.mesh.rip_edge",
 +            widget=None,
 +            keymap=(
 +                ("mesh.rip_edge_edge_move", dict(),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def poly_build():
 +        return dict(
 +            text="Poly Build",
 +            icon="ops.mesh.polybuild_hover",
 +            widget=None,
 +            keymap=(
 +                ("mesh.polybuild_face_at_cursor_move",
 +                 dict(TRANSFORM_OT_translate=dict(release_confirm=True)),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +                ("mesh.polybuild_split_at_cursor_move",
 +                 dict(TRANSFORM_OT_translate=dict(release_confirm=True)),
 +                 dict(type='ACTIONMOUSE', value='PRESS', ctrl=True)),
 +                ("mesh.polybuild_dissolve_at_cursor", dict(), dict(type='ACTIONMOUSE', value='CLICK', alt=True)),
 +                ("mesh.polybuild_hover", dict(use_boundary=False), dict(type='MOUSEMOVE', value='ANY', alt=True)),
 +                ("mesh.polybuild_hover", dict(use_boundary=True), dict(type='MOUSEMOVE', value='ANY', any=True)),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def edge_slide():
 +        return dict(
 +            text="Edge Slide",
 +            icon="ops.transform.edge_slide",
 +            widget=None,
 +            keymap=(
 +                ("transform.edge_slide", dict(release_confirm=True),
 +                 dict(type='ACTIONMOUSE', value='PRESS')
 +                 ),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def vert_slide():
 +        return dict(
 +            text="Vertex Slide",
 +            icon="ops.transform.vert_slide",
 +            widget=None,
 +            keymap=(
 +                ("transform.vert_slide", dict(release_confirm=True),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def spin():
 +        return dict(
 +            text="Spin",
 +            icon="ops.mesh.spin",
 +            widget=None,
 +            keymap=(
 +                ("mesh.spin", dict(),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def spin_duplicate():
 +        return dict(
 +            text="Spin (Duplicate)",
 +            icon="ops.mesh.spin.duplicate",
 +            widget=None,
 +            keymap=(
 +                ("mesh.spin", dict(dupli=True),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def inset():
 +        def draw_settings(context, layout, tool):
 +            wm = context.window_manager
 +            props = tool.operator_properties("mesh.inset")
 +            layout.prop(props, "use_outset")
 +            layout.prop(props, "use_individual")
 +            layout.prop(props, "use_even_offset")
 +            layout.prop(props, "use_relative_offset")
 +
 +        return dict(
 +            text="Inset Faces",
 +            icon="ops.mesh.inset",
 +            widget=None,
 +            keymap=(
 +                ("mesh.inset", dict(release_confirm=True),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +            draw_settings=draw_settings,
 +        )
 +
 +    @ToolDef.from_fn
 +    def bevel():
 +        return dict(
 +            text="Bevel",
 +            icon="ops.mesh.bevel",
 +            widget=None,
 +            keymap=(
 +                ("mesh.bevel", dict(),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def extrude():
 +        return dict(
 +            text="Extrude Region",
 +            icon="ops.mesh.extrude_region_move",
 +            widget="MESH_WGT_extrude",
 +            keymap=(
 +                ("mesh.extrude_context_move", dict(TRANSFORM_OT_translate=dict(release_confirm=True)),
 +                 dict(type='EVT_TWEAK_A', value='ANY')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def extrude_individual():
 +        return dict(
 +            text="Extrude Individual",
 +            icon="ops.mesh.extrude_faces_move",
 +            widget=None,
 +            keymap=(
 +                ("mesh.extrude_faces_move", dict(TRANSFORM_OT_shrink_fatten=dict(release_confirm=True)),
 +                 dict(type='EVT_TWEAK_A', value='ANY')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def extrude_cursor():
 +        return dict(
 +            text="Extrude to Cursor",
 +            icon="ops.mesh.dupli_extrude_cursor",
 +            widget=None,
 +            keymap=(
 +                ("mesh.dupli_extrude_cursor", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def loopcut_slide():
 +        return dict(
 +            text="Loop Cut",
 +            icon="ops.mesh.loopcut_slide",
 +            widget=None,
 +            keymap=(
 +                ("mesh.loopcut_slide", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def offset_edge_loops_slide():
 +        return dict(
 +            text="Offset Edge Loop Cut",
 +            icon="ops.mesh.offset_edge_loops_slide",
 +            widget=None,
 +            keymap=(
 +                ("mesh.offset_edge_loops_slide", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def vertex_smooth():
 +        return dict(
 +            text="Smooth",
 +            icon="ops.mesh.vertices_smooth",
 +            widget=None,
 +            keymap=(
 +                ("mesh.vertices_smooth", dict(),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def vertex_randomize():
 +        return dict(
 +            text="Randomize",
 +            icon="ops.transform.vertex_random",
 +            widget=None,
 +            keymap=(
 +                ("transform.vertex_random", dict(),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def shrink_fatten():
 +        def draw_settings(context, layout, tool):
 +            wm = context.window_manager
 +            props = tool.operator_properties("transform.shrink_fatten")
 +            layout.prop(props, "use_even_offset")
 +
 +        return dict(
 +            text="Shrink/Fatten",
 +            icon="ops.transform.shrink_fatten",
 +            widget=None,
 +            keymap=(
 +                ("transform.shrink_fatten", dict(release_confirm=True),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +            draw_settings=draw_settings,
 +        )
 +
 +    @ToolDef.from_fn
 +    def push_pull():
 +        return dict(
 +            text="Push/Pull",
 +            icon="ops.transform.push_pull",
 +            widget=None,
 +            keymap=(
 +                ("transform.push_pull", dict(release_confirm=True),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def knife():
 +        def draw_settings(context, layout, tool):
 +            wm = context.window_manager
 +            props = tool.operator_properties("mesh.knife_tool")
 +            layout.prop(props, "use_occlude_geometry")
 +            layout.prop(props, "only_selected")
 +
 +        return dict(
 +            text="Knife",
 +            icon="ops.mesh.knife_tool",
 +            widget=None,
 +            keymap=(
 +                ("mesh.knife_tool",
 +                 dict(wait_for_input=False),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +            draw_settings=draw_settings,
 +        )
 +
 +    @ToolDef.from_fn
 +    def bisect():
 +        return dict(
 +            text="Bisect",
 +            icon="ops.mesh.bisect",
 +            widget=None,
 +            keymap=(
 +                ("mesh.bisect",
 +                 dict(),
 +                 dict(type='EVT_TWEAK_A', value='ANY')),
 +            ),
 +        )
 +
 +
 +class _defs_edit_curve:
 +
 +    @ToolDef.from_fn
 +    def draw():
 +        def draw_settings(context, layout, tool):
 +            # Tool settings initialize operator options.
 +            tool_settings = context.tool_settings
 +            cps = tool_settings.curve_paint_settings
 +
 +            col = layout.row()
 +
 +            col.prop(cps, "curve_type")
 +
 +            if cps.curve_type == 'BEZIER':
 +                col.prop(cps, "error_threshold")
 +                col.prop(cps, "fit_method")
 +                col.prop(cps, "use_corners_detect")
 +
 +                col = layout.row()
 +                col.active = cps.use_corners_detect
 +                col.prop(cps, "corner_angle")
 +
 +        return dict(
 +            text="Draw",
 +            cursor='PAINT_BRUSH',
 +            icon=None,
 +            widget=None,
 +            keymap=(
 +                ("curve.draw", dict(wait_for_input=False), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +            draw_settings=draw_settings,
 +        )
 +
 +    @ToolDef.from_fn
 +    def extrude_cursor():
 +        return dict(
 +            text="Extrude Cursor",
 +            icon=None,
 +            widget=None,
 +            keymap=(
 +                ("curve.vertex_add", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +
 +class _defs_pose:
 +
 +    @ToolDef.from_fn
 +    def breakdown():
 +        return dict(
 +            text="Breakdowner",
 +            icon="ops.pose.breakdowner",
 +            widget=None,
 +            keymap=(
 +                ("pose.breakdown", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def push():
 +        return dict(
 +            text="Push",
 +            icon="ops.pose.push",
 +            widget=None,
 +            keymap=(
 +                ("pose.push", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def relax():
 +        return dict(
 +            text="Relax",
 +            icon="ops.pose.relax",
 +            widget=None,
 +            keymap=(
 +                ("pose.relax", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +
 +class _defs_sculpt:
 +
 +    @staticmethod
 +    def generate_from_brushes(context):
 +        return generate_from_brushes_ex(
 +            context,
 +            icon_prefix="brush.sculpt.",
 +            brush_test_attr="use_paint_sculpt",
 +            brush_category_attr="sculpt_tool",
 +            brush_category_layout=(
 +                ('DRAW',),
 +                ('GRAB', 'THUMB'),
 +                ('SNAKE_HOOK',),
 +                ('BLOB', 'INFLATE'),
 +                ('SMOOTH', 'SCRAPE', 'FLATTEN'),
 +                ('CREASE', 'PINCH'),
 +                ('CLAY', 'CLAY_STRIPS'),
 +                ('LAYER',),
 +                ('NUDGE', 'ROTATE'),
 +                ('FILL',),
 +                ('SIMPLIFY',),
 +                ('MASK',),
 +            )
 +        )
 +
 +
 +class _defs_vertex_paint:
 +
 +    @staticmethod
 +    def generate_from_brushes(context):
 +        return generate_from_brushes_ex(
 +            context,
 +            icon_prefix="brush.paint_vertex.",
 +            brush_test_attr="use_paint_vertex",
 +            brush_category_attr="vertex_tool",
 +            brush_category_layout=(
 +                ('MIX',),
 +                ('BLUR', 'AVERAGE'),
 +                ('SMEAR',),
 +                (
 +                    'ADD', 'SUB', 'MUL', 'LIGHTEN', 'DARKEN',
 +                    'COLORDODGE', 'DIFFERENCE', 'SCREEN', 'HARDLIGHT',
 +                    'OVERLAY', 'SOFTLIGHT', 'EXCLUSION', 'LUMINOCITY',
 +                    'SATURATION', 'HUE', 'ERASE_ALPHA', 'ADD_ALPHA',
 +                ),
 +            )
 +        )
 +
 +
 +class _defs_texture_paint:
 +
 +    @staticmethod
 +    def generate_from_brushes(context):
 +        return generate_from_brushes_ex(
 +            context,
 +            icon_prefix="brush.paint_texture.",
 +            brush_test_attr="use_paint_image",
 +            brush_category_attr="image_tool",
 +            brush_category_layout=(
 +                ('DRAW',),
 +                ('SOFTEN',),
 +                ('SMEAR',),
 +                ('CLONE',),
 +                ('FILL',),
 +                ('MASK',),
 +            )
 +        )
 +
 +
 +class _defs_weight_paint:
 +
 +    @staticmethod
 +    def generate_from_brushes(context):
 +        return generate_from_brushes_ex(
 +            context,
 +            icon_prefix="brush.paint_weight.",
 +            brush_test_attr="use_paint_weight",
 +            brush_category_attr="vertex_tool",
 +            brush_category_layout=(
 +                ('MIX',),
 +                ('BLUR', 'AVERAGE'),
 +                ('SMEAR',),
 +                (
 +                    'ADD', 'SUB', 'MUL', 'LIGHTEN', 'DARKEN',
 +                    'COLORDODGE', 'DIFFERENCE', 'SCREEN', 'HARDLIGHT',
 +                    'OVERLAY', 'SOFTLIGHT', 'EXCLUSION', 'LUMINOCITY',
 +                    'SATURATION', 'HUE',
 +                ),
 +            )
 +        )
 +
 +    @ToolDef.from_fn
 +    def sample_weight():
 +        return dict(
 +            text="Sample Weight",
 +            icon="ops.paint.weight_sample",
 +            widget=None,
 +            keymap=(
 +                ("paint.weight_sample", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def sample_weight_group():
 +        return dict(
 +            text="Sample Vertex Group",
 +            icon="ops.paint.weight_sample_group",
 +            widget=None,
 +            keymap=(
 +                ("paint.weight_sample_group", dict(), dict(type='ACTIONMOUSE', value='PRESS')),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def gradient():
 +        def draw_settings(context, layout, tool):
 +            wm = context.window_manager
 +            props = tool.operator_properties("paint.weight_gradient")
 +            layout.prop(props, "type")
 +
 +        return dict(
 +            text="Gradient",
 +            icon="ops.paint.weight_gradient",
 +            widget=None,
 +            keymap=(
 +                ("paint.weight_gradient", dict(), dict(type='EVT_TWEAK_A', value='ANY')),
 +            ),
 +            draw_settings=draw_settings,
 +        )
 +
 +
 +class _defs_uv_select:
 +
 +    @ToolDef.from_fn
 +    def border():
 +        return dict(
 +            text="Select Border",
 +            icon="ops.generic.select_border",
 +            widget=None,
 +            keymap=(
 +                ("uv.select_border",
 +                 dict(deselect=False),
 +                 dict(type='EVT_TWEAK_A', value='ANY')),
 +                # ("uv.select_border",
 +                #  dict(deselect=True),
 +                #  dict(type='EVT_TWEAK_A', value='ANY', ctrl=True)),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def circle():
 +        return dict(
 +            text="Select Circle",
 +            icon="ops.generic.select_circle",
 +            widget=None,
 +            keymap=(
 +                ("uv.select_circle",
 +                 dict(),  # dict(deselect=False),
 +                 dict(type='ACTIONMOUSE', value='PRESS')),
 +                # ("uv.select_circle",
 +                #  dict(deselect=True),
 +                #  dict(type='ACTIONMOUSE', value='PRESS', ctrl=True)),
 +            ),
 +        )
 +
 +    @ToolDef.from_fn
 +    def lasso():
 +        return dict(
 +            text="Select Lasso",
 +            icon="ops.generic.select_lasso",
 +            widget=None,
 +            keymap=(
 +                ("uv.select_lasso",
 +                 dict(deselect=False),
 +                 dict(type='EVT_TWEAK_A', value='ANY')),
 +                # ("uv.select_lasso",
 +                #  dict(deselect=True),
 +                #  dict(type='EVT_TWEAK_A', value='ANY', ctrl=True)),
 +            ),
 +        )
 +
 +
 +class IMAGE_PT_tools_active(ToolSelectPanelHelper, Panel):
 +    bl_space_type = 'IMAGE_EDITOR'
 +    bl_region_type = 'TOOLS'
 +    bl_category = "Tools"
 +    bl_label = "Tools"  # not visible
 +    bl_options = {'HIDE_HEADER'}
 +
 +    # Satisfy the 'ToolSelectPanelHelper' API.
 +    keymap_prefix = "Image Editor Tool: "
 +
 +    @classmethod
 +    def tools_from_context(cls, context, mode=None):
 +        if mode is None:
 +            mode = context.space_data.mode
 +        for tools in (cls._tools[None], cls._tools.get(mode, ())):
 +            for item in tools:
 +                if not (type(item) is ToolDef) and callable(item):
 +                    yield from item(context)
 +                else:
 +                    yield item
 +
 +    @classmethod
 +    def tools_all(cls):
 +        yield from cls._tools.items()
 +
 +    # for reuse
 +    _tools_select = (
 +        (
 +            _defs_uv_select.border,
 +            _defs_uv_select.circle,
 +            _defs_uv_select.lasso,
 +        ),
 +    )
 +
 +    _tools = {
 +        None: [
 +            # for all modes
 +        ],
 +        'VIEW': [
 +            *_tools_select,
 +
 +        ],
 +        'MASK': [
 +            None,
 +        ],
 +        'PAINT': [
 +            _defs_texture_paint.generate_from_brushes,
 +        ],
 +    }
 +
 +
 +class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
 +    bl_space_type = 'VIEW_3D'
 +    bl_region_type = 'TOOLS'
 +    bl_category = "Tools"
 +    bl_label = "Tools"  # not visible
 +    bl_options = {'HIDE_HEADER'}
 +
 +    # Satisfy the 'ToolSelectPanelHelper' API.
 +    keymap_prefix = "3D View Tool: "
 +
 +    @classmethod
 +    def tools_from_context(cls, context, mode=None):
 +        if mode is None:
 +            mode = context.mode
 +        for tools in (cls._tools[None], cls._tools.get(mode, ())):
 +            for item in tools:
 +                if not (type(item) is ToolDef) and callable(item):
 +                    yield from item(context)
 +                else:
 +                    yield item
 +
 +    @classmethod
 +    def tools_all(cls):
 +        yield from cls._tools.items()
 +
 +    # for reuse
 +    _tools_transform = (
 +        (
 +            _defs_transform.translate,
 +            _defs_transform.transform,
 +        ),
 +        _defs_transform.rotate,
 +        (
 +            _defs_transform.scale,
 +            _defs_transform.scale_cage,
 +        ),
 +        None,
 +        _defs_view3d_generic.ruler,
 +    )
 +
 +    _tools_select = (
 +        (
 +            _defs_view3d_select.border,
 +            _defs_view3d_select.circle,
 +            _defs_view3d_select.lasso,
 +        ),
 +    )
 +
 +    _tools = {
 +        None: [
 +            _defs_view3d_generic.cursor,
 +            # End group.
 +        ],
 +        'OBJECT': [
 +            *_tools_select,
 +            None,
 +            *_tools_transform,
 +        ],
 +        'POSE': [
 +            *_tools_select,
 +            *_tools_transform,
 +            None,
 +            (
 +                _defs_pose.breakdown,
 +                _defs_pose.push,
 +                _defs_pose.relax,
 +            )
 +        ],
 +        'EDIT_ARMATURE': [
 +            *_tools_select,
 +            None,
 +            *_tools_transform,
 +            _defs_edit_armature.roll,
 +            (
 +                _defs_edit_armature.bone_size,
 +                _defs_edit_armature.bone_envelope,
 +            ),
 +            None,
 +            (
 +                _defs_edit_armature.extrude,
 +                _defs_edit_armature.extrude_cursor,
 +            )
 +        ],
 +        'EDIT_MESH': [
 +            *_tools_select,
 +            None,
 +            *_tools_transform,
 +            None,
 +            _defs_edit_mesh.cube_add,
 +            None,
 +            (
 +                _defs_edit_mesh.extrude,
 +                _defs_edit_mesh.extrude_individual,
 +                _defs_edit_mesh.extrude_cursor,
 +            ),
 +            _defs_edit_mesh.inset,
 +            _defs_edit_mesh.bevel,
 +            (
 +                _defs_edit_mesh.loopcut_slide,
 +                _defs_edit_mesh.offset_edge_loops_slide,
 +            ),
 +            (
 +                _defs_edit_mesh.knife,
 +                _defs_edit_mesh.bisect,
 +            ),
 +            _defs_edit_mesh.poly_build,
 +            (
 +                _defs_edit_mesh.spin,
 +                _defs_edit_mesh.spin_duplicate,
 +            ),
 +            (
 +                _defs_edit_mesh.vertex_smooth,
 +                _defs_edit_mesh.vertex_randomize,
 +            ),
 +            (
 +                _defs_edit_mesh.edge_slide,
 +                _defs_edit_mesh.vert_slide,
 +            ),
 +            (
 +                _defs_edit_mesh.shrink_fatten,
 +                _defs_edit_mesh.push_pull,
 +            ),
 +            (
 +                _defs_edit_mesh.rip_region,
 +                _defs_edit_mesh.rip_edge,
 +            ),
 +        ],
 +        'EDIT_CURVE': [
 +            *_tools_select,
 +            None,
 +            *_tools_transform,
 +            None,
 +            _defs_edit_curve.draw,
 +            _defs_edit_curve.extrude_cursor,
 +        ],
 +        'PARTICLE': [
 +            # TODO(campbell): use cursor click tool to allow paint tools to run,
 +            # we need to integrate particle system tools properly.
 +            _defs_view3d_generic.cursor_click,
 +        ],
 +        'SCULPT': [
 +            _defs_sculpt.generate_from_brushes,
 +        ],
 +        'PAINT_TEXTURE': [
 +            _defs_texture_paint.generate_from_brushes,
 +        ],
 +        'PAINT_VERTEX': [
 +            _defs_vertex_paint.generate_from_brushes,
 +        ],
 +        'PAINT_WEIGHT': [
 +            _defs_weight_paint.generate_from_brushes,
 +            None,
 +            _defs_weight_paint.sample_weight,
 +            _defs_weight_paint.sample_weight_group,
 +            None,
 +            # TODO, override brush events
 +            *_tools_select,
 +            None,
 +            _defs_weight_paint.gradient,
 +        ],
 +    }
 +
 +
 +classes = (
 +    IMAGE_PT_tools_active,
 +    VIEW3D_PT_tools_active,
 +)
 +
 +if __name__ == "__main__":  # only for live edit.
 +    from bpy.utils import register_class
 +    for cls in classes:
 +        register_class(cls)
@@@ -289,8 -287,6 +288,8 @@@ class USERPREF_PT_interface(Panel)
          #col.prop(view, "open_left_mouse_delay", text="Hold LMB")
          #col.prop(view, "open_right_mouse_delay", text="Hold RMB")
          col.prop(view, "show_manipulator")
-         ## Currently not working
++        # Currently not working
 +        # col.prop(view, "show_manipulator_shaded")
          sub = col.column()
          sub.active = view.show_manipulator
          sub.prop(view, "manipulator_size", text="Size")
@@@ -70,7 -315,157 +70,7 @@@ def draw_vpaint_symmetry(layout, vpaint
  
  class VIEW3D_PT_tools_meshedit_options(View3DPanel, Panel):
      bl_category = "Options"
-     bl_context = ".mesh_edit" # dot on purpose (access from topbar)
 -    bl_context = "mesh_edit"
++    bl_context = ".mesh_edit"  # dot on purpose (access from topbar)
      bl_label = "Mesh Options"
  
      @classmethod
          col.label("Double Threshold:")
          col.prop(tool_settings, "double_threshold", text="")
  
 -        if mesh.show_weight:
 -            col.label("Show Zero Weights:")
 -            col.row().prop(tool_settings, "vertex_group_user", expand=True)
 -
  # ********** default tools for editmode_curve ****************
  
 -class VIEW3D_PT_tools_transform_curve(View3DPanel, Panel):
 -    bl_category = "Tools"
 -    bl_context = "curve_edit"
 -    bl_label = "Transform"
 -
 -    def draw(self, context):
 -        layout = self.layout
 -
 -        col = layout.column(align=True)
 -        col.operator("transform.translate")
 -        col.operator("transform.rotate")
 -        col.operator("transform.resize", text="Scale")
 -
 -        col = layout.column(align=True)
 -        col.operator("transform.tilt", text="Tilt")
 -        col.operator("transform.transform", text="Shrink/Fatten").mode = 'CURVE_SHRINKFATTEN'
 -
 -
 -class VIEW3D_PT_tools_curveedit(View3DPanel, Panel):
 -    bl_category = "Tools"
 -    bl_context = "curve_edit"
 -    bl_label = "Curve Tools"
 -
 -    def draw(self, context):
 -        layout = self.layout
 -
 -        col = layout.column(align=True)
 -        col.label(text="Curve:")
 -        col.operator("curve.duplicate_move", text="Duplicate")
 -        col.operator("curve.delete")
 -        col.operator("curve.cyclic_toggle")
 -        col.operator("curve.switch_direction")
 -        col.operator("curve.spline_type_set")
 -        col.operator("curve.radius_set")
 -
 -        col = layout.column(align=True)
 -        col.label(text="Handles:")
 -        row = col.row(align=True)
 -        row.operator("curve.handle_type_set", text="Auto").type = 'AUTOMATIC'
 -        row.operator("curve.handle_type_set", text="Vector").type = 'VECTOR'
 -        row = col.row(align=True)
 -        row.operator("curve.handle_type_set", text="Align").type = 'ALIGNED'
 -        row.operator("curve.handle_type_set", text="Free").type = 'FREE_ALIGN'
 -
 -        col = layout.column(align=True)
 -        col.operator("curve.normals_make_consistent")
 -
 -        col = layout.column(align=True)
 -        col.label(text="Modeling:")
 -        col.operator("curve.extrude_move", text="Extrude")
 -        col.operator("curve.subdivide")
 -        col.operator("curve.smooth")
 -        col.operator("transform.vertex_random")
 -
 -
 -class VIEW3D_PT_tools_add_curve_edit(View3DPanel, Panel):
 -    bl_category = "Create"
 -    bl_context = "curve_edit"
 -    bl_label = "Add Curves"
 -
 -    def draw(self, context):
 -        layout = self.layout
 -
 -        col = layout.column(align=True)
 -
 -        VIEW3D_PT_tools_add_object.draw_add_curve(col, label=True)
 -
 -
  class VIEW3D_PT_tools_curveedit_options_stroke(View3DPanel, Panel):
      bl_category = "Options"
-     bl_context = ".curve_edit" # dot on purpose (access from topbar)
 -    bl_context = "curve_edit"
++    bl_context = ".curve_edit"  # dot on purpose (access from topbar)
      bl_label = "Curve Stroke"
  
      def draw(self, context):
                  colsub.prop(cps, "surface_plane", expand=True)
  
  
 -# ********** default tools for editmode_surface ****************
 -
 -class VIEW3D_PT_tools_transform_surface(View3DPanel, Panel):
 -    bl_category = "Tools"
 -    bl_context = "surface_edit"
 -    bl_label = "Transform"
 -
 -    def draw(self, context):
 -        layout = self.layout
 -
 -        col = layout.column(align=True)
 -        col.operator("transform.translate")
 -        col.operator("transform.rotate")
 -        col.operator("transform.resize", text="Scale")
 -
 -
 -class VIEW3D_PT_tools_surfaceedit(View3DPanel, Panel):
 -    bl_category = "Tools"
 -    bl_context = "surface_edit"
 -    bl_label = "Surface Tools"
 -
 -    def draw(self, context):
 -        layout = self.layout
 -
 -        col = layout.column(align=True)
 -        col.label(text="Curve:")
 -        col.operator("curve.duplicate_move", text="Duplicate")
 -        col.operator("curve.delete")
 -        col.operator("curve.cyclic_toggle")
 -        col.operator("curve.switch_direction")
 -
 -        col = layout.column(align=True)
 -        col.label(text="Modeling:")
 -        col.operator("curve.extrude", text="Extrude")
 -        col.operator("curve.spin")
 -        col.operator("curve.subdivide")
 -
 -        col = layout.column(align=True)
 -        col.label(text="Deform:")
 -        col.operator("transform.vertex_random")
 -
 -
 -class VIEW3D_PT_tools_add_surface_edit(View3DPanel, Panel):
 -    bl_category = "Create"
 -    bl_context = "surface_edit"
 -    bl_label = "Add Surfaces"
 -
 -    def draw(self, context):
 -        layout = self.layout
 -
 -        col = layout.column(align=True)
 -
 -        VIEW3D_PT_tools_add_object.draw_add_surface(col)
 -
 -
 -# ********** default tools for editmode_text ****************
 -
 -
 -class VIEW3D_PT_tools_textedit(View3DPanel, Panel):
 -    bl_category = "Tools"
 -    bl_context = "text_edit"
 -    bl_label = "Text Tools"
 -
 -    def draw(self, context):
 -        layout = self.layout
 -
 -        col = layout.column(align=True)
 -        col.label(text="Set Case:")
 -        col.operator("font.case_set", text="To Upper").case = 'UPPER'
 -        col.operator("font.case_set", text="To Lower").case = 'LOWER'
 -
 -        col = layout.column(align=True)
 -        col.label(text="Style:")
 -        col.operator("font.style_toggle", text="Bold").style = 'BOLD'
 -        col.operator("font.style_toggle", text="Italic").style = 'ITALIC'
 -        col.operator("font.style_toggle", text="Underline").style = 'UNDERLINE'
--
--
  # ********** default tools for editmode_armature ****************
  
  
@@@ -171,11 -753,121 +170,11 @@@ class VIEW3D_PT_tools_armatureedit_opti
          self.layout.prop(arm, "use_mirror_x")
  
  
 -# ********** default tools for editmode_mball ****************
 -
 -
 -class VIEW3D_PT_tools_mballedit(View3DPanel, Panel):
 -    bl_category = "Tools"
 -    bl_context = "mball_edit"
 -    bl_label = "Meta Tools"
 -
 -    def draw(self, context):
 -        layout = self.layout
 -
 -        col = layout.column(align=True)
 -        col.label(text="Transform:")
 -        col.operator("transform.translate")
 -        col.operator("transform.rotate")
 -        col.operator("transform.resize", text="Scale")
 -
 -        col = layout.column(align=True)
 -        col.label(text="Deform:")
 -        col.operator("transform.vertex_random")
 -
 -
 -class VIEW3D_PT_tools_add_mball_edit(View3DPanel, Panel):
 -    bl_category = "Create"
 -    bl_context = "mball_edit"
 -    bl_label = "Add Metaball"
 -
 -    def draw(self, context):
 -        layout = self.layout
 -
 -        col = layout.column(align=True)
 -
 -        VIEW3D_PT_tools_add_object.draw_add_mball(col)
 -
 -
 -# ********** default tools for editmode_lattice ****************
 -
 -
 -class VIEW3D_PT_tools_latticeedit(View3DPanel, Panel):
 -    bl_category = "Tools"
 -    bl_context = "lattice_edit"
 -    bl_label = "Lattice Tools"
 -
 -    def draw(self, context):
 -        layout = self.layout
 -
 -        col = layout.column(align=True)
 -        col.label(text="Transform:")
 -        col.operator("transform.translate")
 -        col.operator("transform.rotate")
 -        col.operator("transform.resize", text="Scale")
 -
 -        col = layout.column(align=True)
 -        col.operator("lattice.make_regular")
 -
 -        col = layout.column(align=True)
 -        col.label(text="Deform:")
 -        col.operator("transform.vertex_random")
 -
 -
  # ********** default tools for pose-mode ****************
  
 -
 -class VIEW3D_PT_tools_posemode(View3DPanel, Panel):
 -    bl_category = "Tools"
 -    bl_context = "posemode"
 -    bl_label = "Pose Tools"
 -
 -    def draw(self, context):
 -        layout = self.layout
 -
 -        col = layout.column(align=True)
 -        col.label(text="Transform:")
 -        col.operator("transform.translate")
 -        col.operator("transform.rotate")
 -        col.operator("transform.resize", text="Scale")
 -
 -        col = layout.column(align=True)
 -        col.label(text="In-Between:")
 -        row = col.row(align=True)
 -        row.operator("pose.push", text="Push")
 -        row.operator("pose.relax", text="Relax")
 -        col.operator("pose.breakdown", text="Breakdowner")
 -
 -        col = layout.column(align=True)
 -        col.label(text="Pose:")
 -        row = col.row(align=True)
 -        row.operator("pose.copy", text="Copy")
 -        row.operator("pose.paste", text="Paste")
 -
 -        row = layout.row(align=True)
 -        row.operator("pose.propagate", text="Propagate")
 -        row.menu("VIEW3D_MT_pose_propagate", icon='TRIA_RIGHT', text="")
 -
 -        col = layout.column(align=True)
 -        col.operator("poselib.pose_add", text="Add To Library")
 -
 -        draw_keyframing_tools(context, layout)
 -
 -        ob = context.object
 -        avs = ob.pose.animation_visualization
 -
 -        col = layout.column(align=True)
 -        col.label(text="Motion Paths:")
 -        if avs.motion_path.has_motion_paths:
 -            row = col.row(align=True)
 -            row.operator("pose.paths_update", text="Update")
 -            row.operator("pose.paths_clear", text="", icon='X')
 -        else:
 -            col.operator("pose.paths_calculate", text="Calculate")
 -
 -
  class VIEW3D_PT_tools_posemode_options(View3DPanel, Panel):
      bl_category = "Options"
-     bl_context = ".posemode" # dot on purpose (access from topbar)
 -    bl_context = "posemode"
++    bl_context = ".posemode"  # dot on purpose (access from topbar)
      bl_label = "Pose Options"
  
      def draw(self, context):
@@@ -194,7 -885,6 +193,7 @@@ class View3DPaintPanel(UnifiedPaintPane
  
  class VIEW3D_PT_imapaint_tools_missing(Panel, View3DPaintPanel):
      bl_category = "Tools"
-     bl_context = ".imagepaint" # dot on purpose (access from topbar)
++    bl_context = ".imagepaint"  # dot on purpose (access from topbar)
      bl_label = "Missing Data"
  
      @classmethod
@@@ -528,7 -1220,7 +527,7 @@@ class VIEW3D_MT_tools_projectpaint_uvla
  
  
  class VIEW3D_PT_slots_projectpaint(View3DPanel, Panel):
-     bl_context = ".imagepaint" # dot on purpose (access from topbar)
 -    bl_context = "imagepaint"
++    bl_context = ".imagepaint"  # dot on purpose (access from topbar)
      bl_label = "Slots"
      bl_category = "Slots"
  
  
  
  class VIEW3D_PT_stencil_projectpaint(View3DPanel, Panel):
-     bl_context = ".imagepaint" # dot on purpose (access from topbar)
 -    bl_context = "imagepaint"
++    bl_context = ".imagepaint"  # dot on purpose (access from topbar)
      bl_label = "Mask"
      bl_category = "Slots"
  
@@@ -723,7 -1422,7 +722,7 @@@ class VIEW3D_PT_tools_brush_texture(Pan
  
  class VIEW3D_PT_tools_mask_texture(Panel, View3DPaintPanel):
      bl_category = "Tools"
-     bl_context = ".imagepaint" # dot on purpose (access from topbar)
 -    bl_context = "imagepaint"
++    bl_context = ".imagepaint"  # dot on purpose (access from topbar)
      bl_label = "Texture Mask"
      bl_options = {'DEFAULT_CLOSED'}
  
@@@ -1124,7 -1833,7 +1123,7 @@@ class VIEW3D_PT_tools_vertexpaint_symme
  
  class VIEW3D_PT_tools_imagepaint_external(Panel, View3DPaintPanel):
      bl_category = "Tools"
-     bl_context = ".imagepaint" # dot on purpose (access from topbar)
 -    bl_context = "imagepaint"
++    bl_context = ".imagepaint"  # dot on purpose (access from topbar)
      bl_label = "External"
      bl_options = {'DEFAULT_CLOSED'}
  
  
  class VIEW3D_PT_tools_imagepaint_symmetry(Panel, View3DPaintPanel):
      bl_category = "Tools"
-     bl_context = ".imagepaint" # dot on purpose (access from topbar)
 -    bl_context = "imagepaint"
++    bl_context = ".imagepaint"  # dot on purpose (access from topbar)
      bl_label = "Symmetry"
      bl_options = {'DEFAULT_CLOSED'}
  
  
  class VIEW3D_PT_tools_projectpaint(View3DPaintPanel, Panel):
      bl_category = "Options"
-     bl_context = ".imagepaint" # dot on purpose (access from topbar)
 -    bl_context = "imagepaint"
++    bl_context = ".imagepaint"  # dot on purpose (access from topbar)
      bl_label = "Project Paint"
  
      @classmethod