Brushstroke Tools: Initial Version #328

Merged
Simon Thommes merged 229 commits from SimonThommes/blender-studio-tools:brushstroke_tools-initial-version into main 2024-11-06 15:03:47 +01:00
5 changed files with 129 additions and 24 deletions
Showing only changes of commit 9debde1599 - Show all commits

View File

@ -128,6 +128,9 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
brushstrokes_object.modifiers[mod.name][f'{v.identifier}_use_attribute'] = False brushstrokes_object.modifiers[mod.name][f'{v.identifier}_use_attribute'] = False
brushstrokes_object.modifiers[mod.name][f'{v.identifier}'] = type(brushstrokes_object.modifiers[mod.name][f'{v.identifier}'])(val) brushstrokes_object.modifiers[mod.name][f'{v.identifier}'] = type(brushstrokes_object.modifiers[mod.name][f'{v.identifier}'])(val)
# transfer modifier info data from preset to brush strokes
utils.deep_copy_mod_info(settings.preset_object, brushstrokes_object)
# refresh UI # refresh UI
for mod in brushstrokes_object.modifiers: for mod in brushstrokes_object.modifiers:
mod.node_group.interface_update(context) mod.node_group.interface_update(context)
@ -209,8 +212,12 @@ class BSBST_OT_init_preset(bpy.types.Operator):
mod_info = settings.preset_object.modifier_info.add() mod_info = settings.preset_object.modifier_info.add()
mod_info.name = mod.name mod_info.name = mod.name
# context link settings
utils.mark_socket_context_type(mod_info, 'Socket_2', 'SURFACE_OBJECT') utils.mark_socket_context_type(mod_info, 'Socket_2', 'SURFACE_OBJECT')
# ui visibility settings
mod_info.hide_ui = True
## brushstrokes ## brushstrokes
mod = preset_object.modifiers.new('Brushstrokes', 'NODES') mod = preset_object.modifiers.new('Brushstrokes', 'NODES')
mod.node_group = bpy.data.node_groups['.brushstroke_tools.surface_fill'] mod.node_group = bpy.data.node_groups['.brushstroke_tools.surface_fill']
@ -220,9 +227,36 @@ class BSBST_OT_init_preset(bpy.types.Operator):
mod_info = settings.preset_object.modifier_info.add() mod_info = settings.preset_object.modifier_info.add()
mod_info.name = mod.name mod_info.name = mod.name
# context link settings
utils.mark_socket_context_type(mod_info, 'Socket_2', 'FLOW_OBJECT') utils.mark_socket_context_type(mod_info, 'Socket_2', 'FLOW_OBJECT')
utils.mark_socket_context_type(mod_info, 'Socket_3', 'UVMAP') utils.mark_socket_context_type(mod_info, 'Socket_3', 'UVMAP')
utils.mark_socket_context_type(mod_info, 'Socket_9', 'RANDOM') utils.mark_socket_context_type(mod_info, 'Socket_9', 'RANDOM')
utils.mark_socket_context_type(mod_info, 'Socket_60', 'FLOW_OBJECT')
# ui visibility settings
hide_sockets =[
'Socket_3',
'Socket_8',
'Socket_9',
'Socket_12',
'Socket_15',
'Socket_27',
'Socket_35',
]
for s in hide_sockets:
utils.mark_socket_hidden(mod_info, s)
hide_panels = [
'Surface',
'Flow',
'Stroke Culling',
'Lighting Influence',
'Offset Override',
'Debug',
]
for p in hide_panels:
utils.mark_panel_hidden(mod_info, p)
def init_draw(self, context): def init_draw(self, context):
settings = context.scene.BSBST_settings settings = context.scene.BSBST_settings

View File

@ -122,9 +122,11 @@ class BSBST_socket_info(bpy.types.PropertyGroup):
link_context: bpy.props.BoolProperty(default=False, name='Link to Context') link_context: bpy.props.BoolProperty(default=False, name='Link to Context')
link_context_type: bpy.props.EnumProperty(default=1, name='Link to Context', update=update_link_context_type, link_context_type: bpy.props.EnumProperty(default=1, name='Link to Context', update=update_link_context_type,
items=link_context_type_items) items=link_context_type_items)
hide_ui: bpy.props.BoolProperty(default=False)
class BSBST_modifier_info(bpy.types.PropertyGroup): class BSBST_modifier_info(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(default='') name: bpy.props.StringProperty(default='')
hide_ui: bpy.props.BoolProperty(default=False)
socket_info: bpy.props.CollectionProperty(type=BSBST_socket_info) socket_info: bpy.props.CollectionProperty(type=BSBST_socket_info)
class BSBST_context_brushstrokes(bpy.types.PropertyGroup): class BSBST_context_brushstrokes(bpy.types.PropertyGroup):
@ -164,6 +166,7 @@ class BSBST_Settings(bpy.types.PropertyGroup):
update=update_active_brushstrokes, update=update_active_brushstrokes,
get=get_active_context_brushstrokes_index, get=get_active_context_brushstrokes_index,
set=set_active_context_brushstrokes_index) set=set_active_context_brushstrokes_index)
ui_options: bpy.props.BoolProperty(default=False, name='UI Options')
classes = [ classes = [
BSBST_socket_info, BSBST_socket_info,

View File

@ -2,7 +2,7 @@ import bpy
from . import utils from . import utils
from . import settings as settings_py from . import settings as settings_py
def draw_panel_ui_recursive(panel, panel_name, mod, items): def draw_panel_ui_recursive(panel, panel_name, mod, items, display_mode):
scene = bpy.context.scene scene = bpy.context.scene
settings = scene.BSBST_settings settings = scene.BSBST_settings
@ -19,15 +19,40 @@ def draw_panel_ui_recursive(panel, panel_name, mod, items):
for k, v in items: for k, v in items:
if type(v) == bpy.types.NodeTreeInterfacePanel: if type(v) == bpy.types.NodeTreeInterfacePanel:
v_id = f'Panel_{v.index}' # TODO: replace with panel identifier once that is exposed in Blender 4.3
if not mod_info:
continue
s = mod_info.socket_info.get(v_id)
if not s:
continue
if display_mode == 0:
if s.hide_ui:
continue
subpanel_header, subpanel = panel.panel(k, default_closed = v.default_closed) subpanel_header, subpanel = panel.panel(k, default_closed = v.default_closed)
subpanel_header.label(text=k) subpanel_header.label(text=k)
draw_panel_ui_recursive(subpanel, k, mod, v.interface_items.items()) if display_mode != 0:
subpanel_header.prop(s, 'hide_ui', icon_only=True, icon='REMOVE')
draw_panel_ui_recursive(subpanel, k, mod, v.interface_items.items(), display_mode)
else: else:
if v.parent.name != panel_name: if v.parent.name != panel_name:
continue continue
if f'{v.identifier}' not in mod.keys(): if f'{v.identifier}' not in mod.keys():
continue continue
if not mod_info:
continue
s = mod_info.socket_info.get(v.identifier)
if not s:
continue
if display_mode == 0:
if s.hide_ui:
continue
row = panel.row(align=True) row = panel.row(align=True)
col = row.column() col = row.column()
input_row = col.row(align=True) input_row = col.row(align=True)
attribute_toggle = False attribute_toggle = False
@ -51,21 +76,17 @@ def draw_panel_ui_recursive(panel, panel_name, mod, items):
toggle.input_name = v.identifier toggle.input_name = v.identifier
else: else:
input_row.prop(mod, f'["{v.identifier}"]', text=k) input_row.prop(mod, f'["{v.identifier}"]', text=k)
if not mod_info:
continue
if type(v) in utils.linkable_sockets: if type(v) in utils.linkable_sockets:
s = mod_info.socket_info.get(v.identifier)
if not s:
continue
col.enabled = not s.link_context col.enabled = not s.link_context
if not s:
continue
icon = settings_py.icon_from_link_type(s.link_context_type) icon = settings_py.icon_from_link_type(s.link_context_type)
row.alignment = 'EXPAND' row.alignment = 'EXPAND'
if s.link_context: if s.link_context:
row.prop(s, 'link_context', text='', icon_value=icon) row.prop(s, 'link_context', text='', icon_value=icon)
else: else:
if display_mode == -1:
row.prop(s, 'link_context_type', text='', emboss=True, icon='LINKED', icon_only=True) row.prop(s, 'link_context_type', text='', emboss=True, icon='LINKED', icon_only=True)
if display_mode != 0:
row.prop(s, 'hide_ui', icon_only=True, icon='REMOVE')
class BSBST_UL_brushstroke_objects(bpy.types.UIList): class BSBST_UL_brushstroke_objects(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
@ -121,7 +142,7 @@ class BSBST_PT_brushstroke_tools_panel(bpy.types.Panel):
new_advanced_panel.label(text='Curve mode does not support drawing on deformed geometry', icon='ERROR') new_advanced_panel.label(text='Curve mode does not support drawing on deformed geometry', icon='ERROR')
new_advanced_panel.prop(settings, 'assign_materials') new_advanced_panel.prop(settings, 'assign_materials')
row = new_advanced_panel.row() new_advanced_panel.prop(settings, 'ui_options', icon='OPTIONS')
# identify style context # identify style context
style_object = context.object if settings.style_context=='BRUSHSTROKES' else settings.preset_object style_object = context.object if settings.style_context=='BRUSHSTROKES' else settings.preset_object
@ -139,6 +160,10 @@ class BSBST_PT_brushstroke_tools_panel(bpy.types.Panel):
is_preset = style_object == settings.preset_object is_preset = style_object == settings.preset_object
display_mode = settings.ui_options
if is_preset:
display_mode = -1
style_header, style_panel = layout.panel("brushstrokes_style", default_closed=False) style_header, style_panel = layout.panel("brushstrokes_style", default_closed=False)
if is_preset: if is_preset:
@ -161,15 +186,25 @@ class BSBST_PT_brushstroke_tools_panel(bpy.types.Panel):
if style_object: if style_object:
for mod in style_object.modifiers: for mod in style_object.modifiers:
mod_info = mod.id_data.modifier_info.get(mod.name)
if not mod_info:
continue
if display_mode == 0:
if mod_info.hide_ui:
continue
mod_header, mod_panel = style_panel.panel(mod.name, default_closed = False) mod_header, mod_panel = style_panel.panel(mod.name, default_closed = False)
mod_header.label(text='', icon='GEOMETRY_NODES') row = mod_header.row(align=True)
mod_header.prop(mod, 'name', text='') row.label(text='', icon='GEOMETRY_NODES')
row.prop(mod, 'name', text='')
if is_preset: if is_preset:
op = mod_header.operator('brushstroke_tools.preset_remove_mod', text='', icon='X') op = row.operator('brushstroke_tools.preset_remove_mod', text='', icon='X')
else: else:
op = mod_header.operator('object.modifier_remove', text='', icon='X') op = row.operator('object.modifier_remove', text='', icon='X')
op.modifier = mod.name op.modifier = mod.name
if display_mode != 0:
mod_header.prop(mod_info, 'hide_ui', icon_only=True, icon='REMOVE')
if not mod_panel: if not mod_panel:
continue continue
@ -184,7 +219,11 @@ class BSBST_PT_brushstroke_tools_panel(bpy.types.Panel):
if not mod.node_group: if not mod.node_group:
continue continue
draw_panel_ui_recursive(mod_panel, '', mod, mod.node_group.interface.items_tree.items()) draw_panel_ui_recursive(mod_panel,
'',
mod,
mod.node_group.interface.items_tree.items(),
display_mode)
# expose add modifier operator for preset context # expose add modifier operator for preset context
if is_preset: if is_preset:

View File

@ -26,21 +26,19 @@ def refresh_preset(dummy):
if not settings.preset_object: if not settings.preset_object:
return return
for mod in settings.preset_object.modifiers: for mod in settings.preset_object.modifiers:
if not mod.type == 'NODES':
continue
if not mod.node_group:
continue
mod_info = settings.preset_object.modifier_info.get(mod.name) mod_info = settings.preset_object.modifier_info.get(mod.name)
if not mod_info: if not mod_info:
mod_info = settings.preset_object.modifier_info.add() mod_info = settings.preset_object.modifier_info.add()
mod_info.name = mod.name mod_info.name = mod.name
for v in mod.node_group.interface.items_tree.values(): for v in mod.node_group.interface.items_tree.values():
if not type(v) in linkable_sockets: if type(v) is bpy.types.NodeTreeInterfacePanel:
continue v_id = f'Panel_{v.index}' # TODO: replace with panel identifier once that is exposed in Blender 4.3
if v.identifier in [s.name for s in mod_info.socket_info]: else:
v_id = v.identifier
if v_id in [s.name for s in mod_info.socket_info]:
continue continue
n = mod_info.socket_info.add() n = mod_info.socket_info.add()
n.name = v.identifier n.name = v_id
# TODO: clean up old settings # TODO: clean up old settings
def mark_socket_context_type(mod_info, socket_name, link_type): def mark_socket_context_type(mod_info, socket_name, link_type):
@ -50,6 +48,37 @@ def mark_socket_context_type(mod_info, socket_name, link_type):
socket_info.name = socket_name socket_info.name = socket_name
socket_info.link_context_type = link_type socket_info.link_context_type = link_type
def mark_socket_hidden(mod_info, socket_name, hide=True):
socket_info = mod_info.socket_info.get(socket_name)
if not socket_info:
socket_info = mod_info.socket_info.add()
socket_info.name = socket_name
socket_info.hide_ui = hide
def mark_panel_hidden(mod_info, panel_name, hide=True):
mod = mod_info.id_data.modifiers.get(mod_info.name)
if not mod:
return
if not mod.type == 'NODES':
return
ng = mod.node_group
if not ng:
return
v_id = ''
for k, v in ng.interface.items_tree.items():
if type(v) != bpy.types.NodeTreeInterfacePanel:
continue
if v.name == panel_name:
v_id = f'Panel_{v.index}'
break
if not v_id:
return
socket_info = mod_info.socket_info.get(v_id)
if not socket_info:
socket_info = mod_info.socket_info.add()
socket_info.name = v_id
socket_info.hide_ui = hide
def deep_copy_mod_info(source_object, target_object): def deep_copy_mod_info(source_object, target_object):
for mod_info in source_object.modifier_info: for mod_info in source_object.modifier_info:
mod_info_tgt = target_object.modifier_info.add() mod_info_tgt = target_object.modifier_info.add()