2025-12-01
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user