325 lines
14 KiB
Python
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) |