Files
blender-portable-repo/scripts/addons/brushstroke_tools/settings.py
T
2026-03-17 14:58:51 -06:00

325 lines
14 KiB
Python

# SPDX-FileCopyrightText: 2025 Blender Studio Tools Authors
#
# SPDX-License-Identifier: GPL-3.0-or-later
import bpy
from . import utils, icons
def update_active_brushstrokes(self, context):
settings = context.scene.BSBST_settings
for i, el in enumerate(settings.context_brushstrokes):
ob = bpy.data.objects.get(el.name)
if not ob:
continue
is_active = i == settings.active_context_brushstrokes_index
ob['BSBST_active'] = is_active
if 'BSBST_material' in ob.keys() and is_active:
settings.silent_switch = True
settings.context_material = ob['BSBST_material']
settings.silent_switch = False
def update_brushstroke_method(self, context):
settings = context.scene.BSBST_settings
preset_name = f'BSBST-PRESET_{settings.brushstroke_method}'
preset_object = bpy.data.objects.get(preset_name)
settings.preset_object = preset_object
style_object = utils.get_active_context_brushstrokes_object(context.scene)
if not style_object:
style_object = preset_object
settings.silent_switch = True
if not style_object:
settings.context_material = None
settings.silent_switch = False
return
if 'BSBST_material' in style_object.keys():
settings.context_material = style_object['BSBST_material']
else:
settings.context_material = None
settings.silent_switch = False
def update_context_material(self, context):
settings = context.scene.BSBST_settings
if settings.silent_switch:
return
style_object = utils.get_active_context_brushstrokes_object(context.scene)
if not style_object:
style_object = settings.preset_object
if not style_object:
return
utils.set_brushstroke_material(style_object, self.context_material)
if not self.context_material:
utils.set_preview(None)
return
bs = utils.find_brush_style_by_name(self.context_material.brush_style)
if bs is None:
ng_name = self.context_material.brush_style
else:
ng_name = bs.id_name
ng = bpy.data.node_groups.get(ng_name)
if not ng:
utils.set_preview(None)
return
if ng.preview:
utils.set_preview(ng.preview.image_pixels_float, ng.preview.image_size[:], ng.name)
else:
utils.set_preview(None)
def update_link_context_type(self, context):
self.link_context = True
def get_brushstroke_name(self):
return self["name"]
def set_brushstroke_name(self, value):
prev_name = self.get('name')
self["name"] = value
if not prev_name:
return
ob = bpy.data.objects.get(prev_name)
if not ob:
return
ob.name = value
ob.data.name = value
flow_ob = utils.get_flow_object(ob)
if flow_ob:
flow_name = utils.flow_name(value)
flow_ob.name = flow_name
flow_ob.data.name = flow_name
def get_modifier_name(self):
return self["name"]
def set_modifier_name(self, value):
prev_name = self.get('name')
if not prev_name:
self["name"] = value
return
ob = self.id_data.modifiers.get(prev_name)
ob.name = value
self["name"] = ob.name
def get_hide_viewport_base(self):
return self["hide_viewport_base"]
def set_hide_viewport_base(self, value):
self["hide_viewport_base"] = value
ob = bpy.data.objects.get(self.name)
if not ob:
return
ob.hide_set(value)
def get_active_context_brushstrokes_index(self):
if not self.get('active_context_brushstrokes_index'):
return 0
return self["active_context_brushstrokes_index"]
def set_active_context_brushstrokes_index(self, value):
scene = self.id_data
settings = scene.BSBST_settings
for window in bpy.context.window_manager.windows:
if window.scene == scene:
view_layer = window.view_layer
active_object = view_layer.objects.active
if not settings.context_brushstrokes:
if not settings.preset_object:
return
if 'BSBST_material' in settings.preset_object.keys():
settings.silent_switch = True
settings.context_material = settings.preset_object['BSBST_material']
settings.silent_switch = False
prev = self.get('active_context_brushstrokes_index')
if prev == abs(value):
return
self["active_context_brushstrokes_index"] = abs(value)
bs_ob = bpy.data.objects.get(self.context_brushstrokes[value].name)
if settings.silent_switch:
return
if not bs_ob:
return
if not active_object:
return
if active_object.visible_get(view_layer = view_layer):
bpy.ops.object.mode_set(mode='OBJECT')
view_layer.objects.active = bs_ob
if bs_ob.visible_get(view_layer = view_layer):
bpy.ops.object.mode_set(mode='OBJECT')
for ob in bpy.data.objects:
ob.select_set(False)
if utils.is_brushstrokes_object(ob):
ob['BSBST_active'] = False
bs_ob.select_set(True)
bs_ob['BSBST_active'] = True
if settings.edit_toggle and bs_ob.visible_get(view_layer = view_layer):
utils.edit_active_brushstrokes(bpy.context)
if 'BSBST_material' in bs_ob.keys():
settings.context_material = bs_ob['BSBST_material']
def get_brush_style(self):
node = self.node_tree.nodes.get('Brush Style')
if node is None:
return ''
name = node.node_tree.name
name, extension = utils.split_id_name(name)
name = name.split('.')[-1]
if extension:
name = f'{name}.{extension}'
return name
def set_brush_style(self, value):
addon_prefs = bpy.context.preferences.addons[__package__].preferences
brush_style = utils.find_brush_style_by_name(value)
if brush_style is None:
return
ng = utils.ensure_node_group(brush_style.id_name, brush_style.filepath)
if ng.preview:
utils.set_preview(ng.preview.image_pixels_float, ng.preview.image_size[:], ng.name)
else:
utils.set_preview(None)
node = self.node_tree.nodes['Brush Style']
node_prev_inputs = [input.name for input in node.inputs]
node.node_tree = ng
for in_new in node.inputs:
if in_new.name in node_prev_inputs:
continue
in_new.default_value = ng.interface.items_tree[in_new.name].default_value
self["brush_style"] = value
def link_context_type_items(self, context):
items = [
('SURFACE_OBJECT', 'Surface Object', 'Link socket preset to context surface object', 'OUTLINER_OB_SURFACE', 1),\
('FLOW_OBJECT', 'Flow Object', 'Link socket preset to context flow object', 'FORCE_WIND', 11),
('MATERIAL', 'Material', 'Link socket preset to context material', 'MATERIAL', 101),
('UVMAP', 'UV Map', 'Link socket preset to active context UVMap', 'UV', 201),
('RANDOM', 'Random', 'Randomize input value', icons.icon_previews['main']["RANDOM"].icon_id, 501),
]
return items
def icon_from_link_type(link_type):
items = link_context_type_items(None, bpy.context)
for enum_item in items:
if enum_item[0]==link_type:
icon = enum_item[3]
if type(icon) == int:
return icon
else:
return {k : i for i, k in enumerate(bpy.types.UILayout.bl_rna.functions["prop"].parameters["icon"].enum_items.keys())}[icon]
class BSBST_socket_info(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(default='')
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,
items=link_context_type_items)
hide_ui: bpy.props.BoolProperty(default=False)
class BSBST_modifier_info(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(default='', get=get_modifier_name, set=set_modifier_name)
hide_ui: bpy.props.BoolProperty(default=False)
default_closed: bpy.props.BoolProperty(default=False)
socket_info: bpy.props.CollectionProperty(type=BSBST_socket_info)
class BSBST_context_brushstrokes(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(default='', get=get_brushstroke_name, set=set_brushstroke_name)
method: bpy.props.StringProperty(default='')
hide_viewport_base: bpy.props.BoolProperty(default=False, get=get_hide_viewport_base, set=set_hide_viewport_base)
class BSBST_Settings(bpy.types.PropertyGroup):
attach_to_active_selection: bpy.props.BoolProperty(default=True)
preset_object: bpy.props.PointerProperty(type=bpy.types.Object, name="Default/Preset Object")
assign_materials: bpy.props.BoolProperty(name='Assign Modifier Materials', default=True)
brushstroke_method: bpy.props.EnumProperty(default='SURFACE_FILL', update=update_brushstroke_method,
items= [('SURFACE_FILL', 'Fill', 'Use surface fill method for new brushstroke object', 'OUTLINER_OB_FORCE_FIELD', 0),\
('SURFACE_DRAW', 'Draw', 'Use surface draw method for new brushstroke object', 'LINE_DATA', 1),
])
style_context: bpy.props.EnumProperty(default='BRUSHSTROKES',
name='Context',
items= [
('PRESET', 'Default', 'Specify the style of the current default used for new brushstrokes', 'SETTINGS', 0),\
('BRUSHSTROKES', 'Brushstrokes', 'Specify the style of the currently active brushstrokes', 'BRUSH_DATA', 1),
('AUTO', 'Auto', 'Specify the style of either the active brushstrokes or the preset depending on the context', 'AUTO', 2),
])
view_tab: bpy.props.EnumProperty(default='SHAPE',
name='Context',
items= [
('SHAPE', 'Shape', 'View Modifiers Settings', 'MODIFIER', 0),
('MATERIAL', 'Material', 'View Material Settings', 'MATERIAL', 1),
('SETTINGS', 'Settings', 'View Additional Settings', 'PREFERENCES', 2),
])
try:
gpv3 = bpy.context.preferences.experimental.use_grease_pencil_version3
except:
v0, v1, v3 = bpy.app.version
gpv3 = v0 >= 4 and v1 >= 3
curve_mode: bpy.props.EnumProperty(default='CURVES',
items= [('CURVE', 'Legacy', 'Use legacy curve type (Limited Support)', 'CURVE_DATA', 0),\
('CURVES', 'Curves', 'Use hair curves (Fully supported)', 'CURVES_DATA', 1),
('GP', 'Grease Pencil', 'Use Grease Pencil (Limited Support)', 'OUTLINER_OB_GREASEPENCIL', 2),
] if gpv3 else
[('CURVE', 'Legacy', 'Use legacy curve type (Limited Support)', 'CURVE_DATA', 0),\
('CURVES', 'Curves', 'Use hair curves (Full Support)', 'CURVES_DATA', 1),
])
context_brushstrokes: bpy.props.CollectionProperty(type=BSBST_context_brushstrokes)
context_material: bpy.props.PointerProperty(type=bpy.types.Material, name="Material", update=update_context_material)
active_context_brushstrokes_index: bpy.props.IntProperty( default = 0,
update=update_active_brushstrokes,
get=get_active_context_brushstrokes_index,
set=set_active_context_brushstrokes_index)
ui_options: bpy.props.BoolProperty(default=False,
name='UI Options',
description="Show advanced UI options to customize exposed parameters")
reuse_flow: bpy.props.BoolProperty(default=False,
name='Re-use Flow Object',
description="Re-use flow object from active brushstrokes when creating new brushstrokes")
deforming_surface: bpy.props.BoolProperty(default=False,
name='Deforming Surface',
description='Create brushstrokes layer for a deforming surface')
animated: bpy.props.BoolProperty(default=False,
name='Animated',
description='Create brushstrokes layer for animated brushstrokes/flow')
edit_toggle: bpy.props.BoolProperty(default=False,
name='Edit on Selection',
description="Jump into the corresponding edit mode when selecting a brushstrokes layer")
estimate_dimensions: bpy.props.BoolProperty(default=True,
name='Estimate Dimensions',
description="Estimate the length, width and distribution density of the brush strokes based on the bounding box to provide a reasonable starting point regardless of scale")
silent_switch: bpy.props.BoolProperty(default=False)
preview_texture: bpy.props.PointerProperty(type=bpy.types.Texture)
classes = [
BSBST_socket_info,
BSBST_modifier_info,
BSBST_context_brushstrokes,
BSBST_Settings,
]
def register():
for c in classes:
bpy.utils.register_class(c)
bpy.types.Scene.BSBST_settings = bpy.props.PointerProperty(type=BSBST_Settings)
bpy.types.Object.modifier_info = bpy.props.CollectionProperty(type=BSBST_modifier_info)
bpy.types.Material.brush_style = bpy.props.StringProperty(get=get_brush_style, set=set_brush_style, search_options={'SORT'})
bpy.app.handlers.depsgraph_update_post.append(utils.find_context_brushstrokes)
def unregister():
for c in reversed(classes):
bpy.utils.unregister_class(c)
del bpy.types.Scene.BSBST_settings
del bpy.types.Object.modifier_info
bpy.app.handlers.depsgraph_update_post.remove(utils.find_context_brushstrokes)