update addons (for 5.2)
This commit is contained in:
@@ -2,15 +2,18 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from . import utils, icons, settings, preferences, ui, draw_tool, ops
|
||||
import importlib
|
||||
from . import geomod, utils, icons, settings, preferences, ui, draw_tool, ops
|
||||
import tomllib as toml
|
||||
|
||||
modules = [utils, icons, settings, preferences, ui, draw_tool, ops]
|
||||
modules = [geomod, utils, icons, settings, preferences, ui, draw_tool, ops]
|
||||
|
||||
def register():
|
||||
# register modules
|
||||
for m in modules:
|
||||
m.register()
|
||||
importlib.reload(m)
|
||||
if hasattr(m, 'register'):
|
||||
m.register()
|
||||
|
||||
# read addon meta-data
|
||||
with open(f"{utils.get_addon_directory()}/blender_manifest.toml", 'rb') as f:
|
||||
@@ -26,7 +29,8 @@ def register():
|
||||
def unregister():
|
||||
# un-register modules
|
||||
for m in reversed(modules):
|
||||
m.unregister()
|
||||
if hasattr(m, 'unregister'):
|
||||
m.unregister()
|
||||
|
||||
# Remove addon asset library
|
||||
utils.unregister_asset_lib()
|
||||
BIN
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
schema_version = "1.0.0"
|
||||
|
||||
id = "brushstroke_tools"
|
||||
version = "1.2.1"
|
||||
version = "1.2.3"
|
||||
name = "Brushstroke Tools"
|
||||
tagline = "Brushstroke painting tools by the Blender Studio"
|
||||
maintainer = "Simon Thommes <simon@blender.org>"
|
||||
|
||||
@@ -97,9 +97,7 @@ class BSBST_OT_pre_process_brushstroke(bpy.types.Operator):
|
||||
if bpy.app.version >= (5,1):
|
||||
utils.ensure_resources()
|
||||
ng_process = bpy.data.node_groups['.brushstroke_tools.draw_processing']
|
||||
ng_process.asset_mark() # workaround to let Blender register the node tool as an operator with the automatically generated id
|
||||
ng_process.asset_clear()
|
||||
|
||||
ng_process.is_tool = True # workaround to let Blender register the node tool as an operator with the automatically generated id
|
||||
return {'FINISHED'}
|
||||
|
||||
class BSBST_OT_post_process_brushstroke(bpy.types.Operator):
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
# SPDX-FileCopyrightText: 2025 Blender Studio Tools Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
"""
|
||||
Compatibility wrappers for the GeoNodes modifier input API.
|
||||
|
||||
Blender 5.2 replaced dict-style custom-property access on GeoNodes modifiers
|
||||
with proper RNA properties. All modifier input reads and writes in this add-on
|
||||
go through these helpers so version branching stays in one place.
|
||||
|
||||
Before 5.2: modifier["identifier"] = value
|
||||
5.2+: modifier.properties.inputs.identifier.value = value
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
import bpy
|
||||
from bpy.types import NodesModifier, UILayout, NodeTreeInterfaceSocket
|
||||
|
||||
|
||||
_ICON_FOR_SOCKET = {
|
||||
bpy.types.NodeTreeInterfaceSocketObject: 'OBJECT_DATA',
|
||||
bpy.types.NodeTreeInterfaceSocketMaterial: 'MATERIAL',
|
||||
bpy.types.NodeTreeInterfaceSocketImage: 'IMAGE_DATA',
|
||||
bpy.types.NodeTreeInterfaceSocketCollection: 'OUTLINER_COLLECTION',
|
||||
}
|
||||
|
||||
_DATA_COLLECTION_FOR_SOCKET = {
|
||||
bpy.types.NodeTreeInterfaceSocketMaterial: 'materials',
|
||||
bpy.types.NodeTreeInterfaceSocketImage: 'images',
|
||||
bpy.types.NodeTreeInterfaceSocketCollection: 'collections',
|
||||
}
|
||||
|
||||
|
||||
def get_value(mod: NodesModifier, identifier: str) -> Any:
|
||||
if bpy.app.version >= (5, 2, 0):
|
||||
return getattr(mod.properties.inputs, identifier).value
|
||||
return mod[identifier]
|
||||
|
||||
|
||||
def set_value(mod: NodesModifier, identifier: str, value: Any):
|
||||
if bpy.app.version >= (5, 2, 0):
|
||||
getattr(mod.properties.inputs, identifier).value = value
|
||||
else:
|
||||
prop_ui = mod.id_properties_ui(identifier).as_dict()
|
||||
if 'items' in prop_ui.keys():
|
||||
for item in prop_ui['items']:
|
||||
if item[0] == value:
|
||||
mod[identifier] = item[4]
|
||||
return
|
||||
print(f"Error: Item '{value}' not found in menu '{identifier}' of modifier '{mod.name}'")
|
||||
else:
|
||||
mod[identifier] = value
|
||||
|
||||
|
||||
def is_input_value_settable(mod: NodesModifier, identifier: str) -> bool:
|
||||
"""Return True if the modifier exposes a settable input with this identifier."""
|
||||
if bpy.app.version >= (5, 2, 0):
|
||||
inp = getattr(mod.properties.inputs, identifier, None)
|
||||
return inp is not None and hasattr(inp, 'value')
|
||||
return identifier in mod.keys()
|
||||
|
||||
|
||||
def can_input_be_attribute(mod: NodesModifier, identifier: str) -> bool:
|
||||
"""Return True if this input can be driven by an attribute field."""
|
||||
if bpy.app.version >= (5, 2, 0):
|
||||
inp = getattr(mod.properties.inputs, identifier, None)
|
||||
if inp is None or not hasattr(inp, 'type'):
|
||||
return False
|
||||
return any(item.identifier == 'ATTRIBUTE'
|
||||
for item in inp.bl_rna.properties['type'].enum_items)
|
||||
return f'{identifier}_use_attribute' in mod.keys()
|
||||
|
||||
|
||||
def get_input_is_attribute(mod: NodesModifier, identifier: str) -> bool:
|
||||
"""Return True if this input is currently being driven by an attribute field."""
|
||||
if bpy.app.version >= (5, 2, 0):
|
||||
return getattr(mod.properties.inputs, identifier).type == 'ATTRIBUTE'
|
||||
return mod[f'{identifier}_use_attribute']
|
||||
|
||||
|
||||
def set_input_is_attribute(mod: NodesModifier, identifier: str, value: bool):
|
||||
if bpy.app.version >= (5, 2, 0):
|
||||
getattr(mod.properties.inputs, identifier).type = 'ATTRIBUTE' if value else 'VALUE'
|
||||
else:
|
||||
mod[f'{identifier}_use_attribute'] = value
|
||||
|
||||
|
||||
def set_attribute_name(mod: NodesModifier, identifier: str, name: str):
|
||||
if bpy.app.version >= (5, 2, 0):
|
||||
getattr(mod.properties.inputs, identifier).attribute_name = name
|
||||
else:
|
||||
mod[f'{identifier}_attribute_name'] = name
|
||||
|
||||
|
||||
def get_enum_value_to_compare(mod: NodesModifier, identifier: str) -> list[tuple[str, str | int]]:
|
||||
"""Return list of (identifier_str, comparable_value) pairs for a Menu socket.
|
||||
The comparable_value matches whatever get_value() returns for the same socket:
|
||||
an identifier string on 5.2+ (where .value is a string), an integer on older versions."""
|
||||
if bpy.app.version >= (5, 2, 0):
|
||||
inp = getattr(mod.properties.inputs, identifier)
|
||||
return [(item.identifier, item.identifier)
|
||||
for item in inp.bl_rna.properties['value'].enum_items]
|
||||
return [(item[0], item[4])
|
||||
for item in mod.id_properties_ui(identifier).as_dict()['items']]
|
||||
|
||||
|
||||
def copy_inputs(source_mod: NodesModifier, target_mod: NodesModifier):
|
||||
"""Copy all GeoNodes input values (and attribute settings) between modifiers."""
|
||||
if bpy.app.version >= (5, 2, 0):
|
||||
props = source_mod.properties
|
||||
if not props:
|
||||
return
|
||||
target_props = target_mod.properties
|
||||
for socket_name in props.inputs.keys():
|
||||
source_socket = getattr(props.inputs, socket_name, None)
|
||||
target_socket = getattr(target_props.inputs, socket_name, None)
|
||||
if source_socket and target_socket and hasattr(source_socket, "value"):
|
||||
target_socket.value = source_socket.value
|
||||
else:
|
||||
for key, value in source_mod.items():
|
||||
try:
|
||||
target_mod[key] = value
|
||||
except (TypeError, ValueError):
|
||||
target_mod[key] = type(target_mod[key])(value)
|
||||
|
||||
|
||||
def draw_socket(
|
||||
layout: UILayout,
|
||||
mod: NodesModifier,
|
||||
v: NodeTreeInterfaceSocket,
|
||||
label: str,
|
||||
is_preset: bool,
|
||||
):
|
||||
"""Draw a GeoNodes input socket: value widget plus attribute toggle if supported."""
|
||||
if can_input_be_attribute(mod, v.identifier) and not v.force_non_field:
|
||||
if get_input_is_attribute(mod, v.identifier):
|
||||
_draw_attribute_name(layout, mod, v.identifier, text=label)
|
||||
else:
|
||||
_draw_input_simple(layout, mod, v.identifier, text=label)
|
||||
operator_id = ('brushstroke_tools.preset_toggle_attribute' if is_preset
|
||||
else 'brushstroke_tools.brushstrokes_toggle_attribute')
|
||||
toggle = layout.operator(operator_id, text='',
|
||||
depress=get_input_is_attribute(mod, v.identifier),
|
||||
icon='SPREADSHEET')
|
||||
toggle.modifier_name = mod.name
|
||||
toggle.input_name = v.identifier
|
||||
else:
|
||||
socket_type = type(v)
|
||||
icon = _ICON_FOR_SOCKET.get(socket_type, 'NONE')
|
||||
search_prop = _DATA_COLLECTION_FOR_SOCKET.get(socket_type)
|
||||
_draw_input_simple(
|
||||
layout, mod, v.identifier,
|
||||
search_prop=search_prop,
|
||||
text=label, icon=icon,
|
||||
)
|
||||
|
||||
|
||||
def _draw_attribute_name(
|
||||
layout: UILayout,
|
||||
mod: NodesModifier,
|
||||
identifier: str,
|
||||
**kwargs,
|
||||
):
|
||||
"""Draw a modifier input's attribute-name field."""
|
||||
if bpy.app.version >= (5, 2, 0):
|
||||
layout.prop(getattr(mod.properties.inputs, identifier), 'attribute_name', **kwargs)
|
||||
else:
|
||||
layout.prop(mod, f'["{identifier}_attribute_name"]', **kwargs)
|
||||
|
||||
|
||||
def _draw_input_simple(
|
||||
layout: UILayout,
|
||||
mod: NodesModifier,
|
||||
identifier: str,
|
||||
search_prop: str | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Draw a modifier input value widget.
|
||||
Pass search_prop for prop_search on Blender < 4.2.
|
||||
Later versions use pointers, so no need for prop_search.
|
||||
"""
|
||||
if bpy.app.version >= (5, 2, 0):
|
||||
layout.prop(getattr(mod.properties.inputs, identifier), 'value', **kwargs)
|
||||
elif search_prop:
|
||||
layout.prop_search(mod, f'["{identifier}"]', bpy.data, search_prop, **kwargs)
|
||||
else:
|
||||
layout.prop(mod, f'["{identifier}"]', **kwargs)
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import bpy
|
||||
import random
|
||||
from . import utils, settings
|
||||
from . import utils, settings, geomod
|
||||
import mathutils
|
||||
|
||||
class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
@@ -64,7 +64,7 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
|
||||
# link to surface object's collections (fall back to active collection if all are linked data)
|
||||
utils.link_to_collections_by_ref(flow_object, surface_object, unlink=False)
|
||||
|
||||
|
||||
visibility_options = [
|
||||
'visible_camera',
|
||||
'visible_diffuse',
|
||||
@@ -85,8 +85,8 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
|
||||
utils.mark_socket_context_type(mod_info, 'Socket_2', 'SURFACE_OBJECT')
|
||||
|
||||
mod['Socket_2'] = surface_object
|
||||
mod['Socket_3'] = False
|
||||
geomod.set_value(mod, 'Socket_2', surface_object)
|
||||
geomod.set_value(mod, 'Socket_3', False)
|
||||
|
||||
utils.set_deformable(flow_object, settings.deforming_surface)
|
||||
utils.set_animated(flow_object, settings.animated)
|
||||
@@ -94,7 +94,7 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
|
||||
def main(self, context):
|
||||
settings = context.scene.BSBST_settings
|
||||
|
||||
|
||||
utils.ensure_resources()
|
||||
if not context.mode == 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
@@ -135,7 +135,7 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
|
||||
if not settings.preset_object:
|
||||
bpy.ops.brushstroke_tools.init_preset()
|
||||
|
||||
|
||||
# assign preset material
|
||||
preset_material = getattr(settings.preset_object, '["BSBST_material"]', None)
|
||||
|
||||
@@ -153,7 +153,7 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
# transfer preset modifiers to new brushstrokes TODO: refactor to deduplicate
|
||||
for mod in settings.preset_object.modifiers:
|
||||
utils.transfer_modifier(mod.name, brushstrokes_object, settings.preset_object)
|
||||
|
||||
|
||||
for v in mod.node_group.interface.items_tree.values():
|
||||
if type(v) not in utils.linkable_sockets:
|
||||
continue
|
||||
@@ -161,25 +161,26 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
continue
|
||||
# initialize linked context parameters
|
||||
link_context_type = settings.preset_object.modifier_info[mod.name].socket_info[v.identifier].link_context_type
|
||||
bs_mod = brushstrokes_object.modifiers[mod.name]
|
||||
if link_context_type=='SURFACE_OBJECT':
|
||||
brushstrokes_object.modifiers[mod.name][f'{v.identifier}'] = surface_object
|
||||
geomod.set_value(bs_mod, v.identifier, surface_object)
|
||||
elif link_context_type=='FLOW_OBJECT':
|
||||
brushstrokes_object.modifiers[mod.name][f'{v.identifier}'] = flow_object
|
||||
geomod.set_value(bs_mod, v.identifier, flow_object)
|
||||
elif link_context_type=='MATERIAL':
|
||||
brushstrokes_object.modifiers[mod.name][f'{v.identifier}'] = settings.context_material
|
||||
geomod.set_value(bs_mod, v.identifier, settings.context_material)
|
||||
elif link_context_type=='UVMAP':
|
||||
if type(brushstrokes_object.modifiers[mod.name][f'{v.identifier}']) == str:
|
||||
brushstrokes_object.modifiers[mod.name][f'{v.identifier}'] = surface_object.data.uv_layers.active.name
|
||||
if isinstance(geomod.get_value(bs_mod, v.identifier), str):
|
||||
geomod.set_value(bs_mod, v.identifier, surface_object.data.uv_layers.active.name)
|
||||
else:
|
||||
brushstrokes_object.modifiers[mod.name][f'{v.identifier}_use_attribute'] = True
|
||||
brushstrokes_object.modifiers[mod.name][f'{v.identifier}_attribute_name'] = surface_object.data.uv_layers.active.name
|
||||
geomod.set_input_is_attribute(bs_mod, v.identifier, True)
|
||||
geomod.set_attribute_name(bs_mod, v.identifier, surface_object.data.uv_layers.active.name)
|
||||
elif link_context_type=='RANDOM':
|
||||
vmin = v.min_value
|
||||
vmax = v.max_value
|
||||
val = vmin + random.random() * (vmax - vmin)
|
||||
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)
|
||||
|
||||
geomod.set_input_is_attribute(bs_mod, v.identifier, False)
|
||||
geomod.set_value(bs_mod, v.identifier, type(geomod.get_value(bs_mod, v.identifier))(val))
|
||||
|
||||
# transfer modifier info data from preset to brush strokes
|
||||
utils.deep_copy_mod_info(settings.preset_object, brushstrokes_object)
|
||||
|
||||
@@ -195,14 +196,14 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
mod = brushstrokes_object.modifiers['Brushstrokes']
|
||||
if settings.brushstroke_method == 'SURFACE_FILL':
|
||||
# set density
|
||||
mod['Socket_7'] = utils.round_n((1000 / surf_est) ** 0.5, 2)
|
||||
geomod.set_value(mod, 'Socket_7', utils.round_n((1000 / surf_est) ** 0.5, 2))
|
||||
# set length
|
||||
mod['Socket_11'] = utils.round_n(bb_radius * 0.5, 2)
|
||||
geomod.set_value(mod, 'Socket_11', utils.round_n(bb_radius * 0.5, 2))
|
||||
# set width
|
||||
mod['Socket_13'] = utils.round_n(bb_radius * 0.05, 2)
|
||||
geomod.set_value(mod, 'Socket_13', utils.round_n(bb_radius * 0.05, 2))
|
||||
elif settings.brushstroke_method == 'SURFACE_DRAW':
|
||||
# set width
|
||||
mod['Socket_5'] = utils.round_n(bb_radius * 0.05, 2)
|
||||
geomod.set_value(mod, 'Socket_5', utils.round_n(bb_radius * 0.05, 2))
|
||||
|
||||
# refresh UI
|
||||
for mod in brushstrokes_object.modifiers:
|
||||
@@ -213,7 +214,7 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
for v in mod.node_group.interface.items_tree.values():
|
||||
if type(v) != bpy.types.NodeTreeInterfaceSocketMaterial:
|
||||
continue
|
||||
mat = mod[v.identifier]
|
||||
mat = geomod.get_value(mod, v.identifier)
|
||||
if not mat:
|
||||
continue
|
||||
if mat in [m_slot.material for m_slot in brushstrokes_object.material_slots]:
|
||||
@@ -223,13 +224,13 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
with context.temp_override(**override):
|
||||
bpy.ops.object.material_slot_add()
|
||||
brushstrokes_object.material_slots[-1].material = mat
|
||||
|
||||
|
||||
# set deformable
|
||||
set_brushstrokes_deformable(brushstrokes_object, settings.deforming_surface)
|
||||
|
||||
|
||||
# set animated
|
||||
set_brushstrokes_animated(brushstrokes_object, settings.animated)
|
||||
|
||||
|
||||
for mod in brushstrokes_object.modifiers:
|
||||
mod.show_group_selector = False
|
||||
|
||||
@@ -239,7 +240,7 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
if name == brushstrokes_object.name:
|
||||
settings.active_context_brushstrokes_index = i
|
||||
break
|
||||
|
||||
|
||||
utils.edit_active_brushstrokes(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -247,10 +248,10 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
settings = context.scene.BSBST_settings
|
||||
settings.silent_switch = True
|
||||
state = self.main(context)
|
||||
|
||||
|
||||
settings.silent_switch = False
|
||||
return state
|
||||
|
||||
|
||||
class BSBST_OT_edit_brushstrokes(bpy.types.Operator):
|
||||
"""
|
||||
Enter the editing context for the active context brushstrokes.
|
||||
@@ -269,10 +270,10 @@ class BSBST_OT_edit_brushstrokes(bpy.types.Operator):
|
||||
settings = context.scene.BSBST_settings
|
||||
settings.silent_switch = True
|
||||
state = utils.edit_active_brushstrokes(context)
|
||||
|
||||
|
||||
settings.silent_switch = False
|
||||
return state
|
||||
|
||||
|
||||
class BSBST_OT_delete_brushstrokes(bpy.types.Operator):
|
||||
"""
|
||||
Delete the active context brushstrokes
|
||||
@@ -324,7 +325,7 @@ class BSBST_OT_delete_brushstrokes(bpy.types.Operator):
|
||||
|
||||
settings.edit_toggle = edit_toggle
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class BSBST_OT_duplicate_brushstrokes(bpy.types.Operator):
|
||||
"""
|
||||
Duplicate the active context brushstrokes
|
||||
@@ -345,7 +346,7 @@ class BSBST_OT_duplicate_brushstrokes(bpy.types.Operator):
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if not bs_ob:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
if not bs_ob.visible_get(view_layer=context.view_layer):
|
||||
self.report({"WARNING"}, f"Skipped Brushstroke layer '{bs_ob.name}' because it is invisible in this context")
|
||||
return {"CANCELLED"}
|
||||
@@ -354,7 +355,7 @@ class BSBST_OT_duplicate_brushstrokes(bpy.types.Operator):
|
||||
|
||||
if context.mode != 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
|
||||
bpy.context.view_layer.objects.active = bs_ob
|
||||
if context.mode != 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
@@ -371,7 +372,7 @@ class BSBST_OT_duplicate_brushstrokes(bpy.types.Operator):
|
||||
if not mod.type=='NODES':
|
||||
continue
|
||||
if not mod.node_group:
|
||||
continue
|
||||
continue
|
||||
for v in mod.node_group.interface.items_tree.values():
|
||||
if type(v) not in utils.linkable_sockets:
|
||||
continue
|
||||
@@ -383,7 +384,7 @@ class BSBST_OT_duplicate_brushstrokes(bpy.types.Operator):
|
||||
vmin = v.min_value
|
||||
vmax = v.max_value
|
||||
val = vmin + random.random() * (vmax - vmin)
|
||||
mod[f'{v.identifier}'] = type(ob.modifiers[mod.name][f'{v.identifier}'])(val)
|
||||
geomod.set_value(mod, v.identifier, type(geomod.get_value(mod, v.identifier))(val))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
@@ -459,7 +460,7 @@ class BSBST_OT_copy_brushstrokes(bpy.types.Operator):
|
||||
|
||||
ob.parent = surface_object
|
||||
utils.set_surface_object(ob, surface_object)
|
||||
|
||||
|
||||
for mod in ob.modifiers:
|
||||
if not mod.type:
|
||||
continue
|
||||
@@ -475,27 +476,28 @@ class BSBST_OT_copy_brushstrokes(bpy.types.Operator):
|
||||
continue
|
||||
# re-initialize linked context parameters
|
||||
link_context_type = ob.modifier_info[mod.name].socket_info[v.identifier].link_context_type
|
||||
ob_mod = ob.modifiers[mod.name]
|
||||
if link_context_type=='SURFACE_OBJECT':
|
||||
ob.modifiers[mod.name][f'{v.identifier}'] = surface_object
|
||||
geomod.set_value(ob_mod, v.identifier, surface_object)
|
||||
elif link_context_type=='UVMAP':
|
||||
if type(ob.modifiers[mod.name][f'{v.identifier}']) == str:
|
||||
ob.modifiers[mod.name][f'{v.identifier}'] = surface_object.data.uv_layers.active.name
|
||||
if isinstance(geomod.get_value(ob_mod, v.identifier), str):
|
||||
geomod.set_value(ob_mod, v.identifier, surface_object.data.uv_layers.active.name)
|
||||
else:
|
||||
ob.modifiers[mod.name][f'{v.identifier}_use_attribute'] = True
|
||||
ob.modifiers[mod.name][f'{v.identifier}_attribute_name'] = surface_object.data.uv_layers.active.name
|
||||
geomod.set_input_is_attribute(ob_mod, v.identifier, True)
|
||||
geomod.set_attribute_name(ob_mod, v.identifier, surface_object.data.uv_layers.active.name)
|
||||
elif link_context_type=='RANDOM':
|
||||
vmin = v.min_value
|
||||
vmax = v.max_value
|
||||
val = vmin + random.random() * (vmax - vmin)
|
||||
ob.modifiers[mod.name][f'{v.identifier}_use_attribute'] = False
|
||||
ob.modifiers[mod.name][f'{v.identifier}'] = type(ob.modifiers[mod.name][f'{v.identifier}'])(val)
|
||||
|
||||
geomod.set_input_is_attribute(ob_mod, v.identifier, False)
|
||||
geomod.set_value(ob_mod, v.identifier, type(geomod.get_value(ob_mod, v.identifier))(val))
|
||||
|
||||
|
||||
# enable rest position
|
||||
surface_object.add_rest_position_attribute = True
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class BSBST_OT_select_surface(bpy.types.Operator):
|
||||
"""
|
||||
Select the surface object for the active context brushstrokes.
|
||||
@@ -547,7 +549,7 @@ class BSBST_OT_assign_surface(bpy.types.Operator):
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if not bs_ob:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
surface_object = bpy.data.objects.get(self.surface_object)
|
||||
|
||||
if not surface_object:
|
||||
@@ -577,11 +579,11 @@ def set_brushstrokes_deformable(bs_ob, deformable):
|
||||
if not mod.node_group:
|
||||
continue
|
||||
if mod.node_group.name == '.brushstroke_tools.pre_processing':
|
||||
mod['Socket_3'] = deformable
|
||||
geomod.set_value(mod, 'Socket_3', deformable)
|
||||
elif mod.node_group.name == '.brushstroke_tools.surface_fill':
|
||||
mod['Socket_27'] = deformable
|
||||
geomod.set_value(mod, 'Socket_27', deformable)
|
||||
elif mod.node_group.name == '.brushstroke_tools.surface_draw':
|
||||
mod['Socket_15'] = deformable
|
||||
geomod.set_value(mod, 'Socket_15', deformable)
|
||||
|
||||
mod.node_group.interface_update(bpy.context)
|
||||
utils.set_deformable(bs_ob, deformable)
|
||||
@@ -601,7 +603,7 @@ def set_brushstrokes_animated(bs_ob, animated):
|
||||
if not mod:
|
||||
mod = ob.modifiers.new('Animation', 'NODES')
|
||||
mod.node_group = utils.ensure_node_group('.brushstroke_tools.animation')
|
||||
|
||||
|
||||
mod_info = ob.modifier_info.get(mod.name)
|
||||
if not mod_info:
|
||||
mod_info = ob.modifier_info.add()
|
||||
@@ -610,7 +612,7 @@ def set_brushstrokes_animated(bs_ob, animated):
|
||||
# ui visibility settings
|
||||
mod_info.hide_ui = True
|
||||
else:
|
||||
mod['Socket_5'] = True
|
||||
geomod.set_value(mod, 'Socket_5', True)
|
||||
mod.node_group.interface_update(bpy.context)
|
||||
with bpy.context.temp_override(object=ob):
|
||||
bpy.ops.object.modifier_move_to_index(modifier=mod.name, index=0)
|
||||
@@ -632,7 +634,7 @@ class BSBST_OT_copy_flow(bpy.types.Operator):
|
||||
|
||||
source_bs: bpy.props.StringProperty(name="Brushstroke Layers")
|
||||
bs_list: bpy.props.CollectionProperty(type=settings.BSBST_context_brushstrokes)
|
||||
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
settings = context.scene.BSBST_settings
|
||||
@@ -643,7 +645,7 @@ class BSBST_OT_copy_flow(bpy.types.Operator):
|
||||
flow_ob_old = utils.get_flow_object(bs_ob)
|
||||
if not bs_ob:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
# get source
|
||||
|
||||
source_ob = bpy.data.objects.get(self.source_bs)
|
||||
@@ -738,7 +740,7 @@ class BSBST_OT_switch_deformable(bpy.types.Operator):
|
||||
if ob.type == 'GREASEPENCIL':
|
||||
self.report({"WARNING"}, "Grease Pencil does not currently support drawing on deformable surface geometry.")
|
||||
set_brushstrokes_deformable(ob, self.deformable)
|
||||
|
||||
|
||||
context.view_layer.depsgraph.update()
|
||||
|
||||
return {"FINISHED"}
|
||||
@@ -775,11 +777,11 @@ class BSBST_OT_switch_animated(bpy.types.Operator):
|
||||
|
||||
for ob in bs_objects:
|
||||
set_brushstrokes_animated(ob, self.animated)
|
||||
|
||||
|
||||
context.view_layer.depsgraph.update()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BSBST_OT_init_preset(bpy.types.Operator):
|
||||
"""
|
||||
Initialize the preset to define a modifier stack applied to new brushstrokess.
|
||||
@@ -793,7 +795,7 @@ class BSBST_OT_init_preset(bpy.types.Operator):
|
||||
def poll(cls, context):
|
||||
settings = context.scene.BSBST_settings
|
||||
return settings.preset_object is None
|
||||
|
||||
|
||||
def init_fill(self, context):
|
||||
settings = context.scene.BSBST_settings
|
||||
|
||||
@@ -803,7 +805,7 @@ class BSBST_OT_init_preset(bpy.types.Operator):
|
||||
## input
|
||||
mod = preset_object.modifiers.new('Surface Input', 'NODES')
|
||||
mod.node_group = bpy.data.node_groups['.brushstroke_tools.geometry_input']
|
||||
|
||||
|
||||
mod_info = settings.preset_object.modifier_info.get(mod.name)
|
||||
if not mod_info:
|
||||
mod_info = settings.preset_object.modifier_info.add()
|
||||
@@ -820,7 +822,7 @@ class BSBST_OT_init_preset(bpy.types.Operator):
|
||||
## masking
|
||||
mod = preset_object.modifiers.new('Masking', 'NODES')
|
||||
mod.node_group = bpy.data.node_groups['.brushstroke_tools.mask_surface']
|
||||
|
||||
|
||||
mod_info = settings.preset_object.modifier_info.get(mod.name)
|
||||
if not mod_info:
|
||||
mod_info = settings.preset_object.modifier_info.add()
|
||||
@@ -838,7 +840,7 @@ class BSBST_OT_init_preset(bpy.types.Operator):
|
||||
## brushstrokes
|
||||
mod = preset_object.modifiers.new('Brushstrokes', 'NODES')
|
||||
mod.node_group = bpy.data.node_groups['.brushstroke_tools.surface_fill']
|
||||
|
||||
|
||||
mod_info = settings.preset_object.modifier_info.get(mod.name)
|
||||
if not mod_info:
|
||||
mod_info = settings.preset_object.modifier_info.add()
|
||||
@@ -846,7 +848,7 @@ class BSBST_OT_init_preset(bpy.types.Operator):
|
||||
|
||||
# set fill custom defaults
|
||||
|
||||
mod['Socket_59'] = 1 # color method: curves
|
||||
geomod.set_value(mod, 'Socket_59', 'Curves') # color method: curves
|
||||
|
||||
# context link settings
|
||||
utils.mark_socket_context_type(mod_info, 'Socket_2', 'FLOW_OBJECT')
|
||||
@@ -879,7 +881,7 @@ class BSBST_OT_init_preset(bpy.types.Operator):
|
||||
]
|
||||
for p in hide_panels:
|
||||
utils.mark_panel_hidden(mod_info, p)
|
||||
|
||||
|
||||
def init_draw(self, context):
|
||||
settings = context.scene.BSBST_settings
|
||||
|
||||
@@ -889,7 +891,7 @@ class BSBST_OT_init_preset(bpy.types.Operator):
|
||||
## add pre-processing modifier
|
||||
mod = preset_object.modifiers.new('Pre-Processing', 'NODES')
|
||||
mod.node_group = bpy.data.node_groups['.brushstroke_tools.pre_processing']
|
||||
|
||||
|
||||
mod_info = settings.preset_object.modifier_info.get(mod.name)
|
||||
if not mod_info:
|
||||
mod_info = settings.preset_object.modifier_info.add()
|
||||
@@ -903,7 +905,7 @@ class BSBST_OT_init_preset(bpy.types.Operator):
|
||||
## brushstrokes
|
||||
mod = preset_object.modifiers.new('Brushstrokes', 'NODES')
|
||||
mod.node_group = bpy.data.node_groups['.brushstroke_tools.surface_draw']
|
||||
|
||||
|
||||
mod_info = settings.preset_object.modifier_info.get(mod.name)
|
||||
if not mod_info:
|
||||
mod_info = settings.preset_object.modifier_info.add()
|
||||
@@ -937,7 +939,7 @@ class BSBST_OT_init_preset(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
|
||||
settings = context.scene.BSBST_settings
|
||||
|
||||
|
||||
utils.ensure_resources()
|
||||
preset_name = f'BSBST-PRESET_{settings.brushstroke_method}'
|
||||
preset_object = bpy.data.objects.new(preset_name, bpy.data.hair_curves.new(preset_name))
|
||||
@@ -947,7 +949,7 @@ class BSBST_OT_init_preset(bpy.types.Operator):
|
||||
self.init_fill(context)
|
||||
elif settings.brushstroke_method == "SURFACE_DRAW":
|
||||
self.init_draw(context)
|
||||
|
||||
|
||||
# select preset material
|
||||
mat = bpy.data.materials.get('Brush Material')
|
||||
if not mat:
|
||||
@@ -982,7 +984,7 @@ class BSBST_OT_make_preset(bpy.types.Operator):
|
||||
else:
|
||||
for mod in settings.preset_object.modifiers[:]:
|
||||
settings.preset_object.modifiers.remove(mod)
|
||||
|
||||
|
||||
# transfer brushstrokes modifiers to preset
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if not bs_ob:
|
||||
@@ -994,7 +996,7 @@ class BSBST_OT_make_preset(bpy.types.Operator):
|
||||
for mod in settings.preset_object.modifiers:
|
||||
# refresh UI
|
||||
mod.node_group.interface_update(context)
|
||||
|
||||
|
||||
# identify linked sockets
|
||||
for v in mod.node_group.interface.items_tree.values():
|
||||
if type(v) not in utils.linkable_sockets:
|
||||
@@ -1002,8 +1004,8 @@ class BSBST_OT_make_preset(bpy.types.Operator):
|
||||
if not settings.preset_object.modifier_info[mod.name].socket_info[v.identifier]:
|
||||
continue
|
||||
if type(v) == bpy.types.NodeTreeInterfaceSocketObject:
|
||||
if bs_ob['BSBST_surface_object']==mod[v.identifier]:
|
||||
mod[v.identifier] = None
|
||||
if bs_ob['BSBST_surface_object']==geomod.get_value(mod, v.identifier):
|
||||
geomod.set_value(mod, v.identifier, None)
|
||||
settings.preset_object.modifier_info[mod.name].socket_info[v.identifier].link_context = True
|
||||
elif type(v) == bpy.types.NodeTreeInterfaceSocketMaterial:
|
||||
pass # TODO: figure out material preset linking
|
||||
@@ -1109,14 +1111,14 @@ class BSBST_OT_brushstrokes_toggle_attribute(bpy.types.Operator):
|
||||
if not bs_ob:
|
||||
settings.edit_toggle = edit_toggle
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
override = context.copy()
|
||||
override['object'] = bs_ob
|
||||
with context.temp_override(**override):
|
||||
bpy.ops.object.geometry_nodes_input_attribute_toggle(input_name=self.input_name,
|
||||
modifier_name=self.modifier_name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BSBST_OT_render_setup(bpy.types.Operator):
|
||||
"""
|
||||
Set up render settings.
|
||||
@@ -1165,7 +1167,7 @@ class BSBST_OT_render_setup(bpy.types.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class BSBST_OT_view_all(bpy.types.Operator):
|
||||
"""
|
||||
Enable/disable all brushstrokes for the viewport.
|
||||
@@ -1299,11 +1301,11 @@ class BSBST_OT_select_brush_style(bpy.types.Operator):
|
||||
if bs.name == settings.context_material.brush_style:
|
||||
self.brush_styles_filtered_active_index = i
|
||||
break
|
||||
|
||||
|
||||
self.name_filter = ''
|
||||
|
||||
return context.window_manager.invoke_props_dialog(self, width=450)
|
||||
|
||||
|
||||
class BSBST_OT_upgrade_resources(bpy.types.Operator):
|
||||
""" Upgrade all local BST assets to available addon resources.
|
||||
"""
|
||||
@@ -1331,12 +1333,12 @@ class BSBST_OT_upgrade_resources(bpy.types.Operator):
|
||||
layout = self.layout
|
||||
|
||||
split = layout.split(factor=.6)
|
||||
|
||||
|
||||
col = split.column()
|
||||
col.prop(self, 'upgrade_shape_modifiers', icon='MODIFIER')
|
||||
col.prop(self, 'upgrade_materials', icon='MATERIAL')
|
||||
col.prop(self, 'upgrade_brush_styles', icon='BRUSHES_ALL')
|
||||
|
||||
|
||||
col = split.column()
|
||||
row = col.row()
|
||||
row.active = self.upgrade_shape_modifiers
|
||||
@@ -1422,8 +1424,8 @@ classes = [
|
||||
|
||||
def register():
|
||||
for c in classes:
|
||||
bpy.utils.register_class(c)
|
||||
bpy.utils.register_class(c)
|
||||
|
||||
def unregister():
|
||||
for c in reversed(classes):
|
||||
bpy.utils.unregister_class(c)
|
||||
bpy.utils.unregister_class(c)
|
||||
|
||||
@@ -3,8 +3,15 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import bpy
|
||||
from . import utils
|
||||
from . import utils, geomod
|
||||
from . import settings as settings_py
|
||||
from bpy.types import (
|
||||
UILayout,
|
||||
NodesModifier,
|
||||
NodeTreeInterfaceItem,
|
||||
NodeTreeInterfacePanel,
|
||||
NodeTreeInterfaceSocketMenu,
|
||||
)
|
||||
|
||||
warning_icons_dict = {
|
||||
'ERROR': 'CANCEL',
|
||||
@@ -12,133 +19,92 @@ warning_icons_dict = {
|
||||
'INFO': 'INFO',
|
||||
}
|
||||
|
||||
def draw_panel_ui_recursive(panel, panel_name, mod, items, display_mode, hide_panel=False):
|
||||
|
||||
scene = bpy.context.scene
|
||||
settings = scene.BSBST_settings
|
||||
|
||||
is_preset = mod.id_data == settings.preset_object and mod.id_data
|
||||
|
||||
def draw_panel_ui_recursive(
|
||||
panel: UILayout | None,
|
||||
panel_name: str,
|
||||
mod: NodesModifier,
|
||||
interface_items: tuple[str, NodeTreeInterfaceItem],
|
||||
display_mode: int,
|
||||
hide_panel: bool = False,
|
||||
):
|
||||
if not panel:
|
||||
return
|
||||
|
||||
|
||||
mod_info = mod.id_data.modifier_info.get(mod.name)
|
||||
|
||||
icon_dict = {
|
||||
bpy.types.NodeTreeInterfaceSocketObject: 'OBJECT_DATA',
|
||||
bpy.types.NodeTreeInterfaceSocketMaterial: 'MATERIAL',
|
||||
bpy.types.NodeTreeInterfaceSocketImage: 'IMAGE_DATA',
|
||||
bpy.types.NodeTreeInterfaceSocketCollection: 'OUTLINER_COLLECTION',
|
||||
}
|
||||
if not mod_info:
|
||||
return
|
||||
|
||||
data_dict = {
|
||||
bpy.types.NodeTreeInterfaceSocketMaterial: 'materials',
|
||||
bpy.types.NodeTreeInterfaceSocketImage: 'images',
|
||||
bpy.types.NodeTreeInterfaceSocketCollection: 'collections',
|
||||
}
|
||||
panel_active = not (mod_info.hide_ui or hide_panel)
|
||||
inactive_mode_names: list[str] = []
|
||||
|
||||
mode_compare = []
|
||||
for k, v in items:
|
||||
if not v.parent.name == panel_name:
|
||||
for name, interface_item in interface_items:
|
||||
if interface_item.parent.name != panel_name:
|
||||
continue
|
||||
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:
|
||||
if isinstance(interface_item, NodeTreeInterfacePanel):
|
||||
panel_id = f'Panel_{interface_item.index}' # TODO: replace with panel identifier once exposed in Blender 4.3
|
||||
socket_info = mod_info.socket_info.get(panel_id)
|
||||
if not socket_info:
|
||||
continue
|
||||
s = mod_info.socket_info.get(v_id)
|
||||
if not s:
|
||||
if display_mode == 0 and socket_info.hide_ui:
|
||||
continue
|
||||
if display_mode == 0:
|
||||
if s.hide_ui:
|
||||
continue
|
||||
|
||||
subpanel_header, subpanel = panel.panel(k, default_closed = v.default_closed)
|
||||
subpanel_header.label(text=k)
|
||||
subpanel_header, subpanel = panel.panel(name, default_closed=interface_item.default_closed)
|
||||
subpanel_header.label(text=name)
|
||||
if display_mode != 0:
|
||||
col = subpanel_header.column()
|
||||
col.active = not (mod_info.hide_ui or hide_panel)
|
||||
col.prop(s, 'hide_ui', icon_only=True, icon='UNPINNED' if s.hide_ui else 'PINNED', emboss=False)
|
||||
draw_panel_ui_recursive(subpanel, k, mod, v.interface_items.items(), display_mode, s.hide_ui)
|
||||
mode_compare = []
|
||||
col.active = panel_active
|
||||
col.prop(socket_info, 'hide_ui', icon_only=True,
|
||||
icon='UNPINNED' if socket_info.hide_ui else 'PINNED', emboss=False)
|
||||
draw_panel_ui_recursive(subpanel, name, mod, interface_item.interface_items.items(), display_mode, socket_info.hide_ui)
|
||||
inactive_mode_names = []
|
||||
else:
|
||||
if v.parent.name != panel_name:
|
||||
if not geomod.is_input_value_settable(mod, interface_item.identifier):
|
||||
continue
|
||||
if f'{v.identifier}' not in mod.keys():
|
||||
continue
|
||||
if not mod_info:
|
||||
socket_info = mod_info.socket_info.get(interface_item.identifier)
|
||||
if not socket_info:
|
||||
continue
|
||||
|
||||
if type(v) == bpy.types.NodeTreeInterfaceSocketMenu:
|
||||
for item in mod.id_properties_ui(f'{v.identifier}').as_dict()['items']:
|
||||
if item[4] == mod[f'{v.identifier}']:
|
||||
continue
|
||||
mode_compare += [item[0]]
|
||||
if isinstance(interface_item, NodeTreeInterfaceSocketMenu):
|
||||
current_value = geomod.get_value(mod, interface_item.identifier)
|
||||
inactive_mode_names += [
|
||||
mode_id for mode_id, mode_value
|
||||
in geomod.get_enum_value_to_compare(mod, interface_item.identifier)
|
||||
if mode_value != current_value
|
||||
]
|
||||
|
||||
s = mod_info.socket_info.get(v.identifier)
|
||||
if not s:
|
||||
continue
|
||||
if display_mode == 0:
|
||||
comp_match = False
|
||||
for c in mode_compare:
|
||||
comp_match = c in v.name
|
||||
if comp_match:
|
||||
break
|
||||
if comp_match:
|
||||
if socket_info.hide_ui:
|
||||
continue
|
||||
if s.hide_ui:
|
||||
if any(mode_name in interface_item.name for mode_name in inactive_mode_names):
|
||||
continue
|
||||
|
||||
row = panel.row(align=True)
|
||||
row.active = not (mod_info.hide_ui or hide_panel or s.hide_ui)
|
||||
row.active = not (mod_info.hide_ui or hide_panel or socket_info.hide_ui)
|
||||
|
||||
col = row.column()
|
||||
input_row = col.row(align=True)
|
||||
attribute_toggle = False
|
||||
if f'{v.identifier}_use_attribute' in mod.keys() and not v.force_non_field:
|
||||
attribute_toggle = mod[f'{v.identifier}_use_attribute']
|
||||
if attribute_toggle:
|
||||
input_row.prop(mod, f'["{v.identifier}_attribute_name"]', text=k)
|
||||
else:
|
||||
input_row.prop(mod, f'["{v.identifier}"]', text=k)
|
||||
if is_preset:
|
||||
toggle = input_row.operator('brushstroke_tools.preset_toggle_attribute',
|
||||
text='',
|
||||
depress=mod[f'{v.identifier}_use_attribute'],
|
||||
icon='SPREADSHEET')
|
||||
else:
|
||||
toggle = input_row.operator('brushstroke_tools.brushstrokes_toggle_attribute',
|
||||
text='',
|
||||
depress=mod[f'{v.identifier}_use_attribute'],
|
||||
icon='SPREADSHEET')
|
||||
toggle.modifier_name = mod.name
|
||||
toggle.input_name = v.identifier
|
||||
else:
|
||||
if type(v) in icon_dict.keys():
|
||||
icon = icon_dict[type(v)]
|
||||
else:
|
||||
icon='NONE'
|
||||
if type(v) in data_dict.keys():
|
||||
input_row.prop_search(mod, f'["{v.identifier}"]', bpy.data, data_dict[type(v)], text=k, icon=icon)
|
||||
else:
|
||||
input_row.prop(mod, f'["{v.identifier}"]', text=k, icon=icon)
|
||||
if type(v) in utils.linkable_sockets:
|
||||
col.active = not s.link_context
|
||||
icon = settings_py.icon_from_link_type(s.link_context_type)
|
||||
settings = bpy.context.scene.BSBST_settings
|
||||
is_preset = bool(mod.id_data and mod.id_data == settings.preset_object)
|
||||
geomod.draw_socket(col.row(align=True), mod, interface_item, name, is_preset)
|
||||
|
||||
if type(interface_item) in utils.linkable_sockets:
|
||||
col.active = not socket_info.link_context
|
||||
row.alignment = 'EXPAND'
|
||||
if s.link_context:
|
||||
row.prop(s, 'link_context', text='', icon_value=icon)
|
||||
else:
|
||||
if display_mode == -1:
|
||||
row.prop(s, 'link_context_type', text='', emboss=True, icon='LINKED', icon_only=True)
|
||||
if socket_info.link_context:
|
||||
row.prop(socket_info, 'link_context', text='',
|
||||
icon_value=settings_py.icon_from_link_type(socket_info.link_context_type))
|
||||
elif display_mode == -1:
|
||||
row.prop(socket_info, 'link_context_type', text='', emboss=True, icon='LINKED', icon_only=True)
|
||||
|
||||
if display_mode != 0:
|
||||
col = row.column()
|
||||
col.active = not (mod_info.hide_ui or hide_panel)
|
||||
col.prop(s, 'hide_ui', icon_only=True, icon='UNPINNED' if s.hide_ui else 'PINNED', emboss=False)
|
||||
col.active = panel_active
|
||||
icon = 'UNPINNED' if socket_info.hide_ui else 'PINNED'
|
||||
col.prop(socket_info, 'hide_ui', icon_only=True, icon=icon, emboss=False)
|
||||
|
||||
def draw_material_settings(layout, material, surface_object=None):
|
||||
addon_prefs = bpy.context.preferences.addons[__package__].preferences
|
||||
settings = bpy.context.scene.BSBST_settings
|
||||
def draw_material_settings(context, layout, material, surface_object=None):
|
||||
addon_prefs = context.preferences.addons[__package__].preferences
|
||||
settings = context.scene.BSBST_settings
|
||||
|
||||
material_row = layout.row(align=True)
|
||||
material_row.template_ID(settings, 'context_material')
|
||||
@@ -298,7 +264,7 @@ def draw_effect_panel_recursive(effects_panel, material, prev_node):
|
||||
panel.active = False
|
||||
for input in node.inputs[1:]:
|
||||
panel.prop(input, 'default_value', text=input.name)
|
||||
|
||||
|
||||
draw_effect_panel_recursive(effects_panel, material, node)
|
||||
|
||||
def draw_advanced_settings(layout, settings):
|
||||
@@ -309,7 +275,7 @@ def draw_advanced_settings(layout, settings):
|
||||
new_advanced_panel.row().prop(settings, 'curve_mode', expand=True)
|
||||
if settings.curve_mode in ['CURVE', 'GP']:
|
||||
new_advanced_panel.label(text='Curve mode does not support drawing on deformed geometry', icon='ERROR')
|
||||
|
||||
|
||||
new_advanced_panel.prop(settings, 'animated')
|
||||
new_advanced_panel.prop(settings, 'deforming_surface')
|
||||
new_advanced_panel.prop(settings, 'assign_materials')
|
||||
@@ -329,7 +295,7 @@ def draw_shape_properties(layout, settings, style_object, is_preset, display_mod
|
||||
if display_mode == 0:
|
||||
if mod_info.hide_ui:
|
||||
continue
|
||||
|
||||
|
||||
mod_header, mod_panel = layout.panel(mod.name, default_closed = mod_info.default_closed)
|
||||
row = mod_header.row(align=True)
|
||||
row.label(text='', icon='GEOMETRY_NODES')
|
||||
@@ -362,12 +328,12 @@ def draw_shape_properties(layout, settings, style_object, is_preset, display_mod
|
||||
mod,
|
||||
mod.node_group.interface.items_tree.items(),
|
||||
display_mode)
|
||||
|
||||
|
||||
draw_mod_warnings(layout, mod)
|
||||
|
||||
def draw_material_properties(layout, settings, surface_object):
|
||||
def draw_material_properties(context, layout, settings, surface_object):
|
||||
if settings.context_material:
|
||||
draw_material_settings(layout, settings.context_material, surface_object=surface_object)
|
||||
draw_material_settings(context, layout, settings.context_material, surface_object=surface_object)
|
||||
else:
|
||||
material_row = layout.row(align=True)
|
||||
material_row.template_ID(settings, 'context_material', new='brushstroke_tools.new_material')
|
||||
@@ -383,7 +349,7 @@ def draw_settings_properties(layout, settings, style_object):
|
||||
|
||||
layout.prop(style_object, 'visible_shadow', icon='LIGHT', emboss=True)
|
||||
|
||||
def draw_properties_panel(layout, settings, style_object, surface_object, is_preset, display_mode):
|
||||
def draw_properties_panel(context, layout, settings, style_object, surface_object, is_preset, display_mode):
|
||||
|
||||
layout.separator(type='LINE')
|
||||
row = layout.row(align=True)
|
||||
@@ -391,7 +357,7 @@ def draw_properties_panel(layout, settings, style_object, surface_object, is_pre
|
||||
layout.separator(factor=.0, type='SPACE')
|
||||
|
||||
if settings.view_tab == 'MATERIAL':
|
||||
draw_material_properties(layout, settings, surface_object)
|
||||
draw_material_properties(context, layout, settings, surface_object)
|
||||
elif settings.view_tab == 'SHAPE':
|
||||
draw_shape_properties(layout, settings, style_object, is_preset, display_mode)
|
||||
|
||||
@@ -441,7 +407,7 @@ class BSBST_MT_bs_context_menu(bpy.types.Menu):
|
||||
|
||||
def draw(self, _context):
|
||||
layout = self.layout
|
||||
|
||||
|
||||
op = layout.operator('brushstroke_tools.copy_brushstrokes', text='Copy to Selected Objects')
|
||||
op.copy_all = False
|
||||
|
||||
@@ -545,7 +511,7 @@ class BSBST_PT_brushstroke_tools_panel(bpy.types.Panel):
|
||||
if not settings.preset_object and is_preset:
|
||||
layout.operator("brushstroke_tools.init_preset", icon='MODIFIER')
|
||||
else:
|
||||
draw_properties_panel(style_panel, settings, style_object, surface_object, is_preset, display_mode)
|
||||
draw_properties_panel(context, style_panel, settings, style_object, surface_object, is_preset, display_mode)
|
||||
|
||||
class BSBST_MT_PIE_brushstroke_data_marking(bpy.types.Menu):
|
||||
bl_idname= "BSBST_MT_PIE_brushstroke_data_marking"
|
||||
@@ -607,7 +573,7 @@ def register():
|
||||
wm = bpy.context.window_manager
|
||||
if wm.keyconfigs.addon is not None:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Mesh")
|
||||
kmi = km.keymap_items.new("brushstroke_tools.data_marking","F", "PRESS",shift=False, ctrl=True, alt=True)
|
||||
kmi = km.keymap_items.new("brushstroke_tools.data_marking","F", "PRESS",shift=False, ctrl=True, alt=True)
|
||||
|
||||
def unregister():
|
||||
for c in reversed(classes):
|
||||
|
||||
@@ -10,6 +10,7 @@ from bpy.app.handlers import persistent
|
||||
import math, shutil, errno, numpy
|
||||
from bpy.app.handlers import persistent
|
||||
from mathutils import Vector
|
||||
from . import geomod
|
||||
|
||||
addon_version = (0,0,0)
|
||||
|
||||
@@ -36,6 +37,7 @@ linkable_sockets = [
|
||||
|
||||
asset_lib_name = 'Brushstroke Tools Library'
|
||||
|
||||
|
||||
@persistent
|
||||
def find_context_brushstrokes(scene, depsgraph):
|
||||
settings = scene.BSBST_settings
|
||||
@@ -178,7 +180,7 @@ def set_brushstroke_material(ob, material):
|
||||
continue
|
||||
if not s.link_context_type == 'MATERIAL':
|
||||
continue
|
||||
mod[s.name] = material
|
||||
geomod.set_value(mod, s.name, material)
|
||||
ob.update_tag()
|
||||
|
||||
if ob.type == 'EMPTY':
|
||||
@@ -655,11 +657,7 @@ def transfer_modifier(modifier_name, target_obj, source_obj):
|
||||
|
||||
if source_mod.type == 'NODES':
|
||||
# Transfer geo node attributes
|
||||
for key, value in source_mod.items():
|
||||
try:
|
||||
target_mod[key] = value
|
||||
except (TypeError, ValueError) as e:
|
||||
target_mod[key] = type(target_mod[key])(value)
|
||||
geomod.copy_inputs(source_mod, target_mod)
|
||||
|
||||
# Transfer geo node bake settings
|
||||
target_mod.bake_directory = source_mod.bake_directory
|
||||
@@ -744,7 +742,7 @@ def set_surface_object(bs, surf_ob):
|
||||
continue
|
||||
if not s.link_context_type == 'SURFACE_OBJECT':
|
||||
continue
|
||||
mod[s.name] = surf_ob
|
||||
geomod.set_value(mod, s.name, surf_ob)
|
||||
ob.parent = surf_ob
|
||||
ob.parent_type = 'OBJECT'
|
||||
surf_ob.update_tag()
|
||||
@@ -775,7 +773,7 @@ def set_flow_object(bs, ob):
|
||||
continue
|
||||
if not s.link_context_type == 'FLOW_OBJECT':
|
||||
continue
|
||||
mod[s.name] = ob
|
||||
geomod.set_value(mod, s.name, ob)
|
||||
ob.update_tag()
|
||||
|
||||
bs['BSBST_flow_object'] = ob
|
||||
@@ -1106,8 +1104,18 @@ def match_mat(tgt_mat, src_mat):
|
||||
|
||||
for attr_path in path_list:
|
||||
try:
|
||||
exec(f'tgt_mat.{attr_path} = src_mat.{attr_path}')
|
||||
path_parts = attr_path.replace("'", '"').split('.')
|
||||
attr_name = path_parts[-1]
|
||||
item_path = ".".join(path_parts[:-1])
|
||||
if item_path:
|
||||
tgt_item = tgt_mat.path_resolve(item_path)
|
||||
src_item = src_mat.path_resolve(item_path)
|
||||
else:
|
||||
tgt_item = tgt_mat
|
||||
src_item = src_mat
|
||||
setattr(tgt_item, attr_name, getattr(src_item, attr_name))
|
||||
except:
|
||||
print(f"Could not copy attribute '{attr_path}' on material '{tgt_mat.name}'")
|
||||
pass
|
||||
|
||||
tgt_curve_node = tgt_mat.node_tree.nodes.get('Brush Curve')
|
||||
|
||||
Reference in New Issue
Block a user