Sculpt: experimental brush palette ui

Pure python, experimental.
This commit is contained in:
Joseph Eagar 2021-10-02 00:15:48 -07:00
parent f52ed67289
commit e01dd8140e
7 changed files with 804 additions and 83 deletions

View File

@ -69,6 +69,8 @@ _modules = [
"properties_world",
"properties_collection",
"sculpt_ui",
# Generic Space Modules
#
# Depends on DNA_WORKSPACE_TOOL (C define).
@ -125,6 +127,9 @@ def register():
WindowManager,
)
from . import sculpt_ui
sculpt_ui.post_register()
# space_userprefs.py
def addon_filter_items(_self, _context):
import addon_utils

View File

@ -337,6 +337,9 @@ class UnifiedPaintPanel:
""" Generalized way of adding brush options to the UI,
along with their pen pressure setting and global toggle"""
if slider is None:
slider = False
if ui_editing is None:
ui_editing = True
ui_editing = ui_editing and not header
@ -1243,7 +1246,6 @@ def brush_settings(layout, context, brush, popover=False):
brush,
"dyntopo_disabled",
#text="Weight By Face Area",
slider=True,
header=True
)
#layout.prop(brush.dyntopo, "disabled", text="Disable Dyntopo")
@ -1257,7 +1259,16 @@ def brush_settings(layout, context, brush, popover=False):
text = "Pinch"
if sculpt_tool in {'BLOB', 'SNAKE_HOOK'}:
text = "Magnify"
layout.prop(brush, "crease_pinch_factor", slider=True, text=text)
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"crease_pinch_factor",
#text="Weight By Face Area",
slider=True,
text = text
)
# rake_factor
if capabilities.has_rake_factor:
@ -1310,7 +1321,7 @@ def brush_settings(layout, context, brush, popover=False):
layout.prop(brush, "height", slider=True, text="Height")
# use_persistent, set_persistent_base
if capabilities.has_persistence:
if 0: #capabilities.has_persistence:
layout.separator()
layout.prop(brush, "use_persistent")
layout.operator("sculpt.set_persistent_base")
@ -1335,89 +1346,211 @@ def brush_settings(layout, context, brush, popover=False):
# Per sculpt tool options.
def doprop(col, prop, slider=None):
UnifiedPaintPanel.channel_unified(
col,
context,
brush,
prop,
slider=slider
)
if sculpt_tool == "VCOL_BOUNDARY":
row = layout.row()
row.prop(brush, "vcol_boundary_exponent")
UnifiedPaintPanel.channel_unified(
row,
context,
brush,
"vcol_boundary_exponent",
slider=True
)
if sculpt_tool == 'CLAY_STRIPS':
row = layout.row()
row.prop(brush, "tip_roundness")
UnifiedPaintPanel.channel_unified(
row,
context,
brush,
"tip_roundness",
slider=True
)
elif sculpt_tool == 'ELASTIC_DEFORM':
layout.separator()
layout.prop(brush, "elastic_deform_type")
layout.prop(brush, "elastic_deform_volume_preservation", slider=True)
layout.prop(brush, "use_surface_falloff")
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"elastic_deform_type",
)
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"elastic_deform_volume_preservation",
slider=True
)
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"use_surface_falloff",
)
layout.separator()
elif sculpt_tool == 'SNAKE_HOOK':
layout.separator()
layout.prop(brush, "snake_hook_deform_type")
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"snake_hook_deform_type",
)
layout.separator()
elif sculpt_tool == 'POSE':
layout.separator()
layout.prop(brush, "deform_target")
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"deform_target",
)
layout.separator()
layout.prop(brush, "pose_deform_type")
layout.prop(brush, "pose_origin_type")
layout.prop(brush, "pose_offset")
layout.prop(brush, "pose_smooth_iterations")
if brush.pose_deform_type == 'ROTATE_TWIST' and brush.pose_origin_type in {'TOPOLOGY', 'FACE_SETS'}:
layout.prop(brush, "pose_ik_segments")
if brush.pose_deform_type == 'SCALE_TRANSLATE':
layout.prop(brush, "use_pose_lock_rotation")
layout.prop(brush, "use_pose_ik_anchored")
layout.prop(brush, "use_connected_only")
layout.prop(brush, "disconnected_distance_max")
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"pose_deform_type",
)
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"pose_origin_type",
)
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"pose_offset",
)
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"pose_smooth_iterations",
)
if brush.channels["pose_deform_type"].value == 'ROTATE_TWIST' and \
brush.channels["pose_origin_type"].value in {'TOPOLOGY', 'FACE_SETS'}:
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"pose_ik_segments",
)
if brush.channels["pose_deform_type"].value == 'SCALE_TRANSLATE':
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"use_pose_lock_rotation",
)
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"use_pose_ik_anchored",
)
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"use_connected_only",
)
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"disconnected_distance_max",
)
layout.separator()
elif sculpt_tool == 'CLOTH':
layout.separator()
layout.prop(brush, "cloth_simulation_area_type")
if brush.cloth_simulation_area_type != 'GLOBAL':
layout.prop(brush, "cloth_sim_limit")
layout.prop(brush, "cloth_sim_falloff")
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"cloth_simulation_area_type",
)
if brush.cloth_simulation_area_type == 'LOCAL':
layout.prop(brush, "use_cloth_pin_simulation_boundary")
if brush.channels["cloth_simulation_area_type"].value != 'GLOBAL':
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"cloth_sim_limit",
)
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"cloth_sim_falloff",
)
if brush.channels["cloth_simulation_area_type"].value == 'LOCAL':
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"cloth_sim_falloff",
)
UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"cloth_pin_simulation_boundary",
)
layout.separator()
layout.prop(brush, "cloth_deform_type")
layout.prop(brush, "cloth_force_falloff_type")
doprop(layout, "cloth_deform_type")
doprop(layout, "cloth_force_falloff_type")
layout.separator()
layout.prop(brush, "cloth_mass")
layout.prop(brush, "cloth_damping")
layout.prop(brush, "cloth_constraint_softbody_strength")
doprop(layout, "cloth_mass")
doprop(layout, "cloth_damping")
doprop(layout, "cloth_constraint_softbody_strength")
layout.separator()
layout.prop(brush, "use_cloth_collision")
doprop(layout, "use_cloth_collision")
layout.separator()
elif sculpt_tool == 'SCRAPE':
row = layout.row(align=True)
row.prop(brush, "area_radius_factor")
row.prop(brush, "use_pressure_area_radius", text="")
doprop(row, "area_radius_factor")
doprop(row, "use_pressure_area_radius", text="")
row = layout.row()
row.prop(brush, "invert_to_scrape_fill", text="Invert to Fill")
doprop(row, "invert_to_scrape_fill", text="Invert to Fill")
elif sculpt_tool == 'FILL':
row = layout.row(align=True)
row.prop(brush, "area_radius_factor")
row.prop(brush, "use_pressure_area_radius", text="")
doprop(row, "area_radius_factor")
doprop(row, "use_pressure_area_radius", text="")
row = layout.row()
row.prop(brush, "invert_to_scrape_fill", text="Invert to Scrape")
doprop(row, "invert_to_scrape_fill", text="Invert to Scrape")
elif sculpt_tool == 'GRAB':
layout.prop(brush, "use_grab_active_vertex")
layout.prop(brush, "use_grab_silhouette")
layout.prop(brush, "use_surface_falloff")
doprop(layout, "use_grab_active_vertex")
doprop(layout, "grab_silhouette")
doprop(layout, "use_surface_falloff")
elif sculpt_tool == 'PAINT':
row = layout.row(align=True)
row.prop(brush, "flow")
row.prop(brush, "invert_flow_pressure", text="")
row.prop(brush, "use_flow_pressure", text="")
doprop(row, "flow")
doprop(row, "invert_flow_pressure", text="")
doprop(row, "use_flow_pressure", text="")
row = layout.row(align=True)
row.prop(brush, "wet_mix")
@ -1425,56 +1558,56 @@ def brush_settings(layout, context, brush, popover=False):
row.prop(brush, "use_wet_mix_pressure", text="")
row = layout.row(align=True)
row.prop(brush, "wet_persistence")
row.prop(brush, "invert_wet_persistence_pressure", text="")
row.prop(brush, "use_wet_persistence_pressure", text="")
doprop(row, "wet_persistence")
doprop(row, "invert_wet_persistence_pressure", text="")
doprop(row, "use_wet_persistence_pressure", text="")
row = layout.row(align=True)
row.prop(brush, "wet_paint_radius_factor")
doprop(row, "wet_paint_radius_factor")
row = layout.row(align=True)
row.prop(brush, "density")
row.prop(brush, "invert_density_pressure", text="")
row.prop(brush, "use_density_pressure", text="")
doprop(row, "density")
doprop(row, "invert_density_pressure", text="")
doprop(row, "use_density_pressure", text="")
row = layout.row()
row.prop(brush, "tip_roundness")
doprop(row, "tip_roundness")
row = layout.row()
row.prop(brush, "tip_scale_x")
doprop(row, "tip_scale_x")
elif sculpt_tool == 'SMEAR':
col = layout.column()
col.prop(brush, "smear_deform_type")
doprop(col, "smear_deform_type")
elif sculpt_tool == 'BOUNDARY':
layout.prop(brush, "deform_target")
doprop(layout, "deform_target")
layout.separator()
col = layout.column()
col.prop(brush, "boundary_deform_type")
col.prop(brush, "boundary_falloff_type")
col.prop(brush, "boundary_offset")
doprop(col, "boundary_deform_type")
doprop(col, "boundary_falloff_type")
doprop(col, "boundary_offset")
elif sculpt_tool == 'TOPOLOGY':
col = layout.column()
col.prop(brush, "slide_deform_type")
doprop(col, "slide_deform_type")
elif sculpt_tool == 'MULTIPLANE_SCRAPE':
col = layout.column()
col.prop(brush, "multiplane_scrape_angle")
col.prop(brush, "use_multiplane_scrape_dynamic")
col.prop(brush, "show_multiplane_scrape_planes_preview")
doprop(col, "multiplane_scrape_angle")
doprop(col, "use_multiplane_scrape_dynamic")
doprop(col, "show_multiplane_scrape_planes_preview")
elif sculpt_tool == 'SCENE_PROJECT':
col = layout.column()
col.prop(brush, "scene_project_direction_type")
doprop(col, "scene_project_direction_type")
elif sculpt_tool == 'ARRAY':
col = layout.column()
col.prop(brush, "array_deform_type")
col.prop(brush, "array_count")
col.prop(brush, "use_array_lock_orientation")
col.prop(brush, "use_array_fill_holes")
doprop(col, "array_deform_type")
doprop(col, "array_count")
doprop(col, "use_array_lock_orientation")
doprop(col, "use_array_fill_holes")
elif sculpt_tool == 'SMOOTH':
col = layout.column()
@ -1512,19 +1645,19 @@ def brush_settings(layout, context, brush, popover=False):
"fset_slide"
)
col.prop(brush, "smooth_deform_type")
doprop(col, "smooth_deform_type")
if brush.smooth_deform_type == 'SURFACE':
col.prop(brush, "surface_smooth_shape_preservation")
col.prop(brush, "surface_smooth_current_vertex")
col.prop(brush, "surface_smooth_iterations")
doprop(col, "surface_smooth_shape_preservation")
doprop(col, "surface_smooth_current_vertex")
doprop(col, "surface_smooth_iterations")
elif sculpt_tool == 'DISPLACEMENT_SMEAR':
col = layout.column()
col.prop(brush, "smear_deform_type")
doprop(col, "smear_deform_type")
elif sculpt_tool == 'MASK':
layout.row().prop(brush, "mask_tool", expand=True)
doprop(layout.row(), "mask_tool", expand=True)
# End sculpt_tool interface.
@ -2270,12 +2403,3 @@ def brush_basic_gpencil_vertex_settings(layout, _context, brush, *, compact=Fals
row.prop(gp_settings, "vertex_mode", text="Mode")
classes += [
VIEW3D_MT_tools_projectpaint_clone,
ReorderBrushChannel
]
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)

View File

@ -0,0 +1,542 @@
import bpy
import os
import sys
from bpy.props import *
from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
class WM_MT_button_context(bpy.types.Menu):
bl_label = "Unused"
def draw(self, context):
pass
def getToolSlotName(brush):
slot = brush.rna_type.properties["sculpt_tool"].enum_items
return "builtin_brush." + slot[brush.sculpt_tool].name
def getToolSlotIndex(brush):
for i, enum in enumerate(brush.rna_type.properties["sculpt_tool"].enum_items):
if enum.identifier == brush.sculpt_tool:
return i
def getPalette(autocreate=False):
key = "SCULPT_PALETTE"
if key not in bpy.data.palettes:
if autocreate:
pal = bpy.data.palettes.new(key)
for i in range(9):
ref = pal.brush_palette.brushes.add()
ref.type = "EMPTY"
else:
return None
return bpy.data.palettes[key].brush_palette
def menu_func(self, context):
layout = self.layout
layout.separator()
if hasattr(context, "button_operator"):
opname = context.button_operator.rna_type.identifier
if opname == "SCULPT_OT_select_brush_tool":
props = layout.operator("sculpt.remove_brush_tool")
props.slot = context.button_operator.ref
layout.operator("sculpt.add_to_palette")
layout.operator("sculpt.call_brush_palette")
#layout.operator(WM_MT_button_context_sculpt_palette.bl_idname)
class BrushRef(bpy.types.PropertyGroup):
type_items = [("BRUSH", "Brush","", 0),
("TOOL", "Tool", "",1),
("EMPTY", "Empty", "", 2)]
type : EnumProperty(items=type_items)
brush : PointerProperty(type=bpy.types.Brush)
tool : StringProperty()
class BrushPalette(bpy.types.PropertyGroup):
brushes : CollectionProperty(type=BrushRef)
def brushCount(self):
count = 0
for i, ref in enumerate(self.brushes):
if ref.type != "EMPTY" and (ref.type == "TOOL" or (ref.type == "BRUSH" and ref.brush)):
count += 1
return count
def addBrush(self, brush):
for ref in self.brushes:
if ref.brush == brush:
print("Brush already in palette!")
return None
if ref.type == "EMPTY" or (ref.type == "BRUSH" and not ref.brush):
ref.type = "BRUSH"
ref.brush = brush
return ref
ref = self.brushes.add()
ref.type = "BRUSH"
ref.brush = brush
return ref
class BrushPaletteSet(bpy.types.PropertyGroup):
palettes : CollectionProperty(type=BrushPalette)
active : IntProperty()
def getOrCreateActive(self):
if len(self.palettes) == 0:
self.palettes.add()
return self.palettes[self.active]
def getActive(self):
if len(self.palettes) == 0:
return None
return self.palettes[self.active]
def sculpt_poll(cls, context):
return context.mode == "SCULPT" and context.active_object
whitelist = ["mesh_filter", "cloth_filter", "line_project"
"box_face_set", "box_mask", "box_hide",
"ipmask_filter", "color_filter", "mask_by_color",
"face_set_edit", "move", "rotate", "scale",
"transform", "annotate"]
whitelist = set(map(lambda item: "builtin." + item, whitelist))
class AddToPalette(bpy.types.Operator):
"Add brush/tool to palette"
bl_idname = "sculpt.add_to_palette"
bl_label = "Add To Palette"
@classmethod
def poll(cls, context):
if not sculpt_poll(cls, context):
return False
ok = False
for k in ["button_pointer", "button_prop", "button_operator"]:
if not hasattr(context, k):
continue
v = getattr(context, k)
print(v, dir(v))
if hasattr(v, "name"):
print(v.name)
if k == "button_operator":
ok = v.name.startswith("builtin_brush") or v.name in whitelist
break
print("=====" + k + "=====")
"""
for k in dir(context):
print(k)
#"""
return ok
def execute(self, context):
pal = getPalette(True)
ok = False
key = None
for k in ["button_pointer", "button_prop", "button_operator"]:
if not hasattr(context, k):
continue
v = getattr(context, k)
print(v, dir(v))
if hasattr(v, "name"):
print(v.name)
if k == "button_operator":
ok = v.name.startswith("builtin_brush") or v.name in whitelist
if ok:
key = v.name
break
if not ok:
return {'CANCELLED'}
print("found brush or tool", key)
if key.startswith("builtin_brush"):
self.do_brush(context, key)
else:
self.do_tool(context, key)
return {'FINISHED'}
def do_brush(self, context, key):
key = key.split(".")
type = key[1].upper()
print(type)
slots = context.tool_settings.sculpt.tool_slots
slot = None
for slot2 in slots:
if not slot2.brush: continue
if slot2.brush.sculpt_tool == type:
slot = slot2
break
if slot is None:
print("error!", type)
return
print("found", type)
pal = getPalette(True)
pal.addBrush(slot.brush)
def do_tool(self, context, key):
pass
class SelectBrushTool(bpy.types.Operator):
"Select brush/tool"
bl_idname = "sculpt.select_brush_tool"
bl_label = "Select Brush/Tool"
ref : IntProperty()
@classmethod
def poll(cls, context):
return sculpt_poll(cls, context)
def execute(self, context):
pal = getPalette(True)
ref = pal.brushes[self.ref]
if ref.type == "TOOL":
bpy.ops.wm.tool_set_by_id(name="builtin." + ref.tool)
elif ref.type == "BRUSH":
print("REF", ref.brush, ref.type, ref)
if not ref.brush:
return {'CANCELLED'}
tool = getToolSlotName(ref.brush)
#bpy.ops.wm.tool_set_by_id(name=tool)
bpy.ops.paint.brush_select(sculpt_tool=ref.brush.sculpt_tool)
sloti = getToolSlotIndex(ref.brush)
slots = bpy.context.tool_settings.sculpt.tool_slots
slots[sloti].brush = ref.brush
return {'FINISHED'}
class SetBrushTool(bpy.types.Operator):
"Set brush/tool"
bl_idname = "sculpt.set_brush_tool"
bl_label = "Set Palette Entry"
ref : IntProperty()
slot : IntProperty()
@classmethod
def poll(cls, context):
return sculpt_poll(cls, context)
def execute(self, context):
pal = getPalette(True)
slot = self.slot
while len(pal.brushes) <= slot:
ref = pal.brushes.add()
ref.type = "EMPTY"
ref = pal.brushes[slot]
ref.type = "BRUSH"
ref.brush = context.tool_settings.sculpt.brush
return {'FINISHED'}
class RemoveBrushTool(bpy.types.Operator):
"Remove brush/tool"
bl_idname = "sculpt.remove_brush_tool"
bl_label = "Remove Brush/Tool From Palette"
ref : IntProperty()
slot : IntProperty()
@classmethod
def poll(cls, context):
return sculpt_poll(cls, context)
def execute(self, context):
pal = getPalette(True)
slot = self.slot
pal.brushes[slot].type = "EMPTY"
pal.brushes[slot].brush = None
return {'FINISHED'}
class CallBrushMenu(bpy.types.Operator):
"Select brush/tool"
bl_idname = "sculpt.call_brush_palette"
bl_label = "Open Brush Palette"
@classmethod
def poll(cls, context):
ok = context.active_object is not None
ok = ok and context.mode == "SCULPT"
ok = ok and context.tool_settings is not None
ok = ok and context.tool_settings.sculpt is not None
return ok
def invoke(cls, context, b):
getPalette(True)
bpy.ops.wm.call_panel(name="VIEW3D_PT_SculptPalette", keep_open=False)
#bpy.ops.wm.call_menu(name="VIEW3D_MT_SculptPalette")
return {'CANCELLED'}
def execute(self, context):
return {'CANCELLED'}
class VIEW3D_PT_SculptPalette(bpy.types.Panel):
bl_label = "Sculpt Palette"
bl_idname = "VIEW3D_PT_SculptPalette"
bl_space_type = "VIEW_3D"
bl_region_type = "WINDOW"
def draw(self, context):
layout = self.layout #.menu_pie()#.column(align=True)
row = layout.column(align=1)
col = None #row.column(align=True)
row.alignment = 'LEFT'
scale = 2.0
pal = getPalette(False)
if pal is None:
print("no palettes")
return
#rows and cols as identifiers became logically swapped during
#development, unwap them
j = 0
rows = min(int(len(pal.brushes) / 3) + 1, 3)
cols = 3
if rows * cols == pal.brushCount():
rows += 1
for i in range(cols * rows):
if i % cols == 0:
col = row.row(align=1)
col.scale_x = scale
col.scale_y = scale
col.alignment = 'LEFT'
j += 1
if j == rows:
row = layout.column(align=1)
j = 0
id = ((i + 0) % cols)
id = ((j + 2) % cols) * cols + id
n = 0
icon = 0
if id < len(pal.brushes) and pal.brushes[id].type != "EMPTY" and pal.brushes[id].brush:
n = getToolSlotIndex(pal.brushes[id].brush)
brush = pal.brushes[id].brush
vname = brush.sculpt_tool.lower()
vname = "brush.sculpt." + vname
print("ICON", vname)
icon = ToolSelectPanelHelper._icon_value_from_icon_handle(vname)
if id > 10:
keyt = ''
else:
keyt = str(id)
keyt = ''
empty = id >= len(pal.brushes)
empty = empty or pal.brushes[id].type == "EMPTY"
empty = empty or pal.brushes[id].type == "BRUSH" and not pal.brushes[id]
if empty:
icon = 9
props = col.operator("sculpt.set_brush_tool", text='', icon_value=icon)
props.slot = id
else:
props = col.operator("sculpt.select_brush_tool", text=keyt, icon_value=icon)
props.ref = id
#col.label(text="")
#col.template_icon(i, scale=1)
"""
preview_collections = {}
my_icons_dir = os.path.dirname(bpy.data.filepath)
my_icons_path = ""
my_icon_name = "my_icon"
class SubObj (dict):
pass
def savedata(obj, subkeys={}, is_sub=False):
props = obj.bl_rna.properties
ret = {} if not is_sub else SubObj()
for p in props:
if p.is_readonly:
continue
if p.type in ["ENUM", "FLOAT", "INT", "BOOLEAN", "STRING"]:
ret[p.identifier] = getattr(obj, p.identifier)
return ret
def loaddata(obj, data):
for k in data:
v = data[k]
if type(v) == SubObj:
loaddata(getattr(obj), v)
continue
try:
setattr(obj, k, v)
except:
print("failed to set property", k, "on", obj);
def pushRender():
render = bpy.context.scene.render
return savedata(render)
def popRender(state):
render = bpy.context.scene.render
loaddata(render, state)
state = pushRender()
popRender(state)
class RenderPreview (bpy.types.Operator):
"Render preview icon"
bl_idname = "render.my_render_preview"
bl_label = "Render Icon"
filepath: bpy.props.StringProperty()
use_viewport: bpy.props.BoolProperty()
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context):
state = pushRender()
render = bpy.context.scene.render
print("Render!")
print("file", self.filepath)
render.filepath = self.filepath
res = 256
render.resolution_x = res
render.resolution_y = res
render.film_transparent = True
render.filter_size = 0.75
if self.use_viewport:
bpy.ops.render.opengl(write_still=True, view_context=True)
else:
bpy.ops.render.render(write_still=True)
popRender(state)
pcoll = preview_collections["main"]
print(pcoll) #.update.__doc__, pcoll.load.__doc__)
#pcoll.clear(m
if my_icon_name in pcoll:
pcoll[my_icon_name].reload()
else:
pcoll.load(my_icon_name, my_icon_path, 'IMAGE', force_reload=True)
return {'FINISHED'}
class PreviewsExamplePanel(bpy.types.Panel):
"Creates a Panel in the Object properties window"
bl_label = "Previews Example Panel"
bl_idname = "OBJECT_PT_previews"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
def draw(self, context):
layout = self.layout
pcoll = preview_collections["main"]
row = layout.row()
my_icon = pcoll[my_icon_name]
print(my_icon_name, my_icon.icon_id)
row.template_icon(my_icon.icon_id, scale=2)
props = row.operator("render.my_render_preview", icon_value=my_icon.icon_id)
props["filepath"] = my_icon_path
props["use_viewport"] = True
# my_icon.icon_id can be used in any UI function that accepts
# icon_value # try also setting text=""
# to get an icon only operator button
#"""
classes = [VIEW3D_PT_SculptPalette,
#RenderPreview,
WM_MT_button_context,
SelectBrushTool,
CallBrushMenu,
BrushRef,
SetBrushTool,
AddToPalette,
RemoveBrushTool,
BrushPalette,
BrushPaletteSet]
def post_register():
bpy.types.Palette.brush_palette = PointerProperty(type=BrushPalette)
bpy.types.WM_MT_button_context.append(menu_func)
if __name__ == "__main__":
if not hasattr(bpy, "sculpt_global"):
bpy.sculpt_global = []
for cls in bpy.sculpt_global:
bpy.utils.unregister_class(cls)
bpy.sculpt_global = []
for cls in classes:
bpy.utils.register_class(cls)
bpy.sculpt_global.append(cls)
post_register()

View File

@ -230,6 +230,14 @@ class _draw_tool_settings_context_mode:
if (tool is None) or (not tool.has_datablock):
return False
try:
row = layout.row()
row.operator_context = 'INVOKE_DEFAULT'
row.scale_y = 1.25
row.operator("sculpt.call_brush_palette", text="Palette")
except:
pass
paint = context.tool_settings.sculpt
layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)

View File

@ -280,6 +280,8 @@ MAKE_BOOL(use_weighted_smooth, "Weight By Area", "Weight by face area to get a s
MAKE_BOOL_EX(preserve_faceset_boundary, "Preserve Faceset Boundary", "Preserve face set boundaries", true, BRUSH_CHANNEL_INHERIT)
MAKE_BOOL_EX(hard_edge_mode, "Hard Edge Mode", "Treat face set boundaries as hard edges", false, BRUSH_CHANNEL_INHERIT)
MAKE_BOOL(grab_silhouette, "Grab Silhouette", "Grabs trying to automask the silhouette of the object", false)
MAKE_BOOL(use_grab_active_vertex, "Grab Active Vertex",
"Apply the maximum grab strength to the active vertex instead of the cursor location", false)
MAKE_FLOAT_EX_FLAG(dyntopo_detail_percent, "Detail Percent", "Detail Percent", 25.0f, 0.0f, 1000.0f, 0.0f, 1000.0f, false, BRUSH_CHANNEL_INHERIT)
MAKE_FLOAT_EX_FLAG(dyntopo_detail_range, "Detail Range", "Detail Range", 0.45f, 0.01f, 0.99f, 0.01f, 0.99f, false, BRUSH_CHANNEL_INHERIT)
MAKE_FLOAT_EX_FLAG(dyntopo_detail_size, "Detail Size", "Detail Size", 8.0f, 0.1f, 100.0f, 0.001f, 500.0f, false, BRUSH_CHANNEL_INHERIT)
@ -522,9 +524,15 @@ MAKE_ENUM(elastic_deform_type, "Deformation", "Deformation type that is used in
{BRUSH_ELASTIC_DEFORM_TWIST, "TWIST", "NONE", "Twist", ""},
{-1}
})
MAKE_FLOAT(elastic_deform_volume_preservation, "Volume Preservation",
"Poisson ratio for elastic deformation. Higher values preserve volume "
"more, but also lead to more bulging", 0.0f, 0.9f, false)
MAKE_BOOL(use_ctrl_invert, "Use Ctrl Invert", "Take brush addition or subtraction mode into account", true)
MAKE_BOOL(use_smoothed_rake, "Smooth Raking", "Smooth angles of clay strips brush and raked textures", false)
MAKE_BOOL(use_surface_falloff, "Use Surface Falloff",
"Propagate the falloff of the brush trough the surface of the mesh", false)
//MAKE_FLOAT3_EX
/* clang-format on */

View File

@ -445,6 +445,7 @@ static BrushSettingsMap brush_settings_map[] = {
DEF(plane_offset, plane_offset, FLOAT, FLOAT)
DEF(plane_trim, plane_trim, FLOAT, FLOAT)
DEF(blend, blend, INT, INT)
DEF(elastic_deform_volume_preservation, elastic_deform_volume_preservation, FLOAT, FLOAT)
};
static const int brush_settings_map_len = ARRAY_SIZE(brush_settings_map);
@ -510,6 +511,8 @@ BrushFlagMap brush_flags_map[] = {
DEF(flag2, use_pose_lock_rotation, BRUSH_POSE_USE_LOCK_ROTATION)
DEF(flag, use_space_attenuation, BRUSH_SPACE_ATTEN)
DEF(flag, use_plane_trim, BRUSH_PLANE_TRIM)
DEF(flag2, use_surface_falloff, BRUSH_USE_SURFACE_FALLOFF)
DEF(flag2, use_grab_active_vertex, BRUSH_GRAB_ACTIVE_VERTEX)
};
int brush_flags_map_len = ARRAY_SIZE(brush_flags_map);
@ -886,6 +889,7 @@ void reset_clay_mappings(BrushChannelSet *chset, bool strips)
BKE_curvemapping_changed(curve, true);
}
else {
#if 0 // dunno if I've interpreted the original code's math right - joeedh
//[[0,0], [0.250,0.050], [0.500,0.125], [0.750,0.422], [1,1]
cuma->curve[0].x = 0.0f;
cuma->curve[0].y = 0.55f;
@ -893,6 +897,7 @@ void reset_clay_mappings(BrushChannelSet *chset, bool strips)
cuma->curve[2].x = 1.0f;
cuma->curve[2].y = 1.0f;
BKE_curvemapping_changed(curve, true);
#endif
}
mp = BRUSHSET_LOOKUP(chset, strength)->mappings + BRUSH_MAPPING_PRESSURE;
@ -945,6 +950,8 @@ void BKE_brush_builtin_patch(Brush *brush, int tool)
ADDCH(radius_unit);
ADDCH(unprojected_radius);
ADDCH(use_surface_falloff);
if (!BRUSHSET_LOOKUP(chset, use_smoothed_rake)) {
BrushChannel *ch = ADDCH(use_smoothed_rake);
@ -1035,6 +1042,15 @@ void BKE_brush_builtin_patch(Brush *brush, int tool)
reset_clay_mappings(chset, false);
}
break;
case SCULPT_TOOL_BLOB:
ADDCH(crease_pinch_factor);
break;
case SCULPT_TOOL_GRAB:
ADDCH(use_grab_active_vertex);
break;
case SCULPT_TOOL_ELASTIC_DEFORM:
ADDCH(use_grab_active_vertex);
break;
case SCULPT_TOOL_CLAY_STRIPS:
if (set_mappings) {
reset_clay_mappings(chset, true);
@ -1210,6 +1226,7 @@ void BKE_brush_channelset_ui_init(Brush *brush, int tool)
switch (tool) {
case SCULPT_TOOL_INFLATE:
case SCULPT_TOOL_BLOB:
SHOWCTX(crease_pinch_factor);
case SCULPT_TOOL_DRAW:
SHOWCTX(autosmooth);
break;
@ -1239,6 +1256,9 @@ void BKE_brush_channelset_ui_init(Brush *brush, int tool)
SHOWCTX(normal_weight);
SHOWWRK(normal_weight);
SHOWWRK(use_grab_active_vertex);
SHOWCTX(use_grab_active_vertex);
SHOWALL(grab_silhouette);
break;
case SCULPT_TOOL_CLAY_STRIPS:
SHOWWRK(area_radius_factor);
@ -1367,6 +1387,10 @@ void BKE_brush_channelset_ui_init(Brush *brush, int tool)
SHOWWRK(deform_target);
SHOWWRK(elastic_deform_type);
break;
case SCULPT_TOOL_ELASTIC_DEFORM:
SHOWWRK(elastic_deform_type);
SHOWCTX(elastic_deform_type);
break;
}
@ -1520,12 +1544,15 @@ void BKE_brush_builtin_create(Brush *brush, int tool)
GETCH(radius)->mappings[BRUSH_MAPPING_PRESSURE].flag |= BRUSH_MAPPING_ENABLED;
GETCH(strength)->mappings[BRUSH_MAPPING_PRESSURE].flag |= BRUSH_MAPPING_ENABLED;
GETCH(strength)->flag &= ~BRUSH_CHANNEL_INHERIT;
BRUSHSET_SET_BOOL(chset, use_space_attenuation, false);
GETCH(tip_roundness)->fvalue = 0.18f;
GETCH(normal_radius_factor)->fvalue = 1.35f;
GETCH(strength)->fvalue = 0.8f;
GETCH(accumulate)->ivalue = 1;
GETCH(spacing)->fvalue = 7.0f;
BKE_brush_mapping_ensure_write(&GETCH(radius)->mappings[BRUSH_MAPPING_PRESSURE]);
@ -1544,6 +1571,7 @@ void BKE_brush_builtin_create(Brush *brush, int tool)
break;
case SCULPT_TOOL_THUMB:
ADDCH(elastic_deform_type);
ADDCH(elastic_deform_volume_preservation);
BRUSHSET_SET_BOOL(chset, use_space_attenuation, false);
BRUSHSET_LOOKUP(chset, strength)->mappings[BRUSH_MAPPING_PRESSURE].flag &=
~BRUSH_MAPPING_ENABLED;
@ -1581,9 +1609,11 @@ void BKE_brush_builtin_create(Brush *brush, int tool)
GETCH(radius)->mappings[BRUSH_MAPPING_PRESSURE].flag &= ~BRUSH_MAPPING_ENABLED;
GETCH(strength)->mappings[BRUSH_MAPPING_PRESSURE].flag &= ~BRUSH_MAPPING_ENABLED;
ADDCH(elastic_deform_type);
ADDCH(elastic_deform_volume_preservation);
break;
case SCULPT_TOOL_ELASTIC_DEFORM:
ADDCH(elastic_deform_type);
ADDCH(elastic_deform_volume_preservation);
GETCH(strength)->mappings[BRUSH_MAPPING_PRESSURE].flag &= ~BRUSH_MAPPING_ENABLED;
BRUSHSET_SET_BOOL(chset, use_space_attenuation, false);
break;
@ -1598,6 +1628,7 @@ void BKE_brush_builtin_create(Brush *brush, int tool)
GETCH(dyntopo_mode)->flag = BRUSH_CHANNEL_INHERIT_IF_UNSET;
GETCH(strength)->fvalue = 1.0f;
ADDCH(elastic_deform_type);
ADDCH(elastic_deform_volume_preservation);
break;
case SCULPT_TOOL_DRAW_FACE_SETS:
@ -1610,11 +1641,13 @@ void BKE_brush_builtin_create(Brush *brush, int tool)
GETCH(strength)->mappings[BRUSH_MAPPING_PRESSURE].flag &= ~BRUSH_MAPPING_ENABLED;
BRUSHSET_SET_BOOL(chset, use_space_attenuation, false);
ADDCH(elastic_deform_type);
ADDCH(elastic_deform_volume_preservation);
break;
case SCULPT_TOOL_POSE:
GETCH(strength)->mappings[BRUSH_MAPPING_PRESSURE].flag &= ~BRUSH_MAPPING_ENABLED;
BRUSHSET_SET_BOOL(chset, use_space_attenuation, false);
ADDCH(elastic_deform_type);
ADDCH(elastic_deform_volume_preservation);
break;
default: {
// implement me!

View File

@ -8685,6 +8685,7 @@ void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings
case SCULPT_TOOL_ELASTIC_DEFORM:
case SCULPT_TOOL_FAIRING:
case SCULPT_TOOL_FILL:
case SCULPT_TOOL_BLOB:
case SCULPT_TOOL_FLATTEN:
case SCULPT_TOOL_GRAB:
case SCULPT_TOOL_LAYER: