2026-03-16
This commit is contained in:
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
schema_version = "1.0.0"
|
||||
|
||||
id = "brushstroke_tools"
|
||||
version = "1.1.2"
|
||||
version = "1.2.1"
|
||||
name = "Brushstroke Tools"
|
||||
tagline = "Brushstroke painting tools by the Blender Studio"
|
||||
maintainer = "Simon Thommes <simon@blender.org>"
|
||||
@@ -20,4 +20,4 @@ copyright = [
|
||||
]
|
||||
|
||||
[permissions]
|
||||
files = "Read/write brushstroke asset resources from/to disk"
|
||||
files = "Read/write brushstroke asset resources from/to disk"
|
||||
|
||||
@@ -94,6 +94,12 @@ class BSBST_OT_pre_process_brushstroke(bpy.types.Operator):
|
||||
for prop in props_list:
|
||||
setattr(context.tool_settings.curve_paint_settings, prop, getattr(tool_settings, prop))
|
||||
|
||||
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()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class BSBST_OT_post_process_brushstroke(bpy.types.Operator):
|
||||
@@ -119,7 +125,11 @@ class BSBST_OT_post_process_brushstroke(bpy.types.Operator):
|
||||
if 'BSBST_surface_object' in context.object.keys():
|
||||
if context.object['BSBST_surface_object']:
|
||||
self.ng_process.nodes['surface_object'].inputs[0].default_value = context.object['BSBST_surface_object']
|
||||
bpy.ops.geometry.execute_node_group(name="set_brush_stroke_color", session_uid=self.ng_process.session_uid)
|
||||
|
||||
if bpy.app.version < (5,1):
|
||||
bpy.ops.geometry.execute_node_group(name="set_brush_stroke_color", session_uid=self.ng_process.session_uid)
|
||||
else:
|
||||
bpy.ops.geometry._brushstroke_tools_draw_processing()
|
||||
|
||||
preserve_draw_settings(context, restore=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
@@ -30,21 +30,18 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
settings.brushstroke_method = self.method
|
||||
|
||||
if settings.curve_mode == 'GP':
|
||||
bpy.ops.object.grease_pencil_add(type='EMPTY')
|
||||
context.object.name = name
|
||||
context.object.data.name = name
|
||||
brushstrokes_object = context.object
|
||||
context.collection.objects.unlink(brushstrokes_object)
|
||||
else:
|
||||
if settings.curve_mode == 'CURVE':
|
||||
brushstrokes_data = bpy.data.curves.new(name, type='CURVE')
|
||||
brushstrokes_data.dimensions = '3D'
|
||||
elif settings.curve_mode == 'CURVES':
|
||||
brushstrokes_data = bpy.data.hair_curves.new(name)
|
||||
brushstrokes_object = bpy.data.objects.new(name, brushstrokes_data)
|
||||
brushstrokes_data = bpy.data.grease_pencils.new(name)
|
||||
bs_layer = brushstrokes_data.layers.new('Brushstrokes')
|
||||
bs_layer.frames.new(1)
|
||||
elif settings.curve_mode == 'CURVE':
|
||||
brushstrokes_data = bpy.data.curves.new(name, type='CURVE')
|
||||
brushstrokes_data.dimensions = '3D'
|
||||
elif settings.curve_mode == 'CURVES':
|
||||
brushstrokes_data = bpy.data.hair_curves.new(name)
|
||||
brushstrokes_object = bpy.data.objects.new(name, brushstrokes_data)
|
||||
|
||||
# link to surface object's collections (fall back to active collection if all are linked data)
|
||||
utils.link_to_collections_by_ref(brushstrokes_object, surface_object)
|
||||
utils.link_to_collections_by_ref(brushstrokes_object, surface_object, unlink=False)
|
||||
|
||||
brushstrokes_object.visible_shadow = False
|
||||
brushstrokes_object['BSBST_version'] = utils.addon_version
|
||||
@@ -55,21 +52,18 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
def new_flow_object(self, context, name, surface_object):
|
||||
settings = context.scene.BSBST_settings
|
||||
if settings.curve_mode == 'GP':
|
||||
bpy.ops.object.grease_pencil_add(type='EMPTY')
|
||||
context.object.name = name
|
||||
context.object.data.name = name
|
||||
flow_object = context.object
|
||||
context.collection.objects.unlink(flow_object)
|
||||
else:
|
||||
if settings.curve_mode == 'CURVE':
|
||||
flow_data = bpy.data.curves.new(name, type='CURVE')
|
||||
flow_data.dimensions = '3D'
|
||||
elif settings.curve_mode == 'CURVES':
|
||||
flow_data = bpy.data.hair_curves.new(name)
|
||||
flow_object = bpy.data.objects.new(name, flow_data)
|
||||
flow_data = bpy.data.grease_pencils.new(name)
|
||||
fl_layer = flow_data.layers.new('Flow')
|
||||
fl_layer.frames.new(1)
|
||||
elif settings.curve_mode == 'CURVE':
|
||||
flow_data = bpy.data.curves.new(name, type='CURVE')
|
||||
flow_data.dimensions = '3D'
|
||||
elif settings.curve_mode == 'CURVES':
|
||||
flow_data = bpy.data.hair_curves.new(name)
|
||||
flow_object = bpy.data.objects.new(name, flow_data)
|
||||
|
||||
# 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)
|
||||
utils.link_to_collections_by_ref(flow_object, surface_object, unlink=False)
|
||||
|
||||
visibility_options = [
|
||||
'visible_camera',
|
||||
@@ -112,7 +106,7 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
self.report({"ERROR"}, "Surface Object needs an available UV Map")
|
||||
return {"CANCELLED"}
|
||||
|
||||
name = f'{surface_object.name} - Brushstrokes'
|
||||
name = utils.bs_name(surface_object.name)
|
||||
brushstrokes_object = self.new_brushstrokes_object(context, name, surface_object)
|
||||
flow_is_required = settings.brushstroke_method == 'SURFACE_FILL'
|
||||
if flow_is_required:
|
||||
@@ -151,7 +145,9 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
with context.temp_override(**override):
|
||||
bpy.ops.object.material_slot_add()
|
||||
brushstrokes_object.material_slots[0].material = preset_material
|
||||
settings.silent_switch = True
|
||||
settings.context_material = preset_material
|
||||
settings.silent_switch = False
|
||||
brushstrokes_object['BSBST_material'] = settings.context_material
|
||||
|
||||
# transfer preset modifiers to new brushstrokes TODO: refactor to deduplicate
|
||||
@@ -238,7 +234,7 @@ class BSBST_OT_new_brushstrokes(bpy.types.Operator):
|
||||
mod.show_group_selector = False
|
||||
|
||||
# update brushstroke context
|
||||
utils.find_context_brushstrokes(None)
|
||||
utils.find_context_brushstrokes(context.scene, context.view_layer.depsgraph)
|
||||
for i, name in enumerate([bs.name for bs in settings.context_brushstrokes]):
|
||||
if name == brushstrokes_object.name:
|
||||
settings.active_context_brushstrokes_index = i
|
||||
@@ -297,7 +293,7 @@ class BSBST_OT_delete_brushstrokes(bpy.types.Operator):
|
||||
edit_toggle = settings.edit_toggle
|
||||
settings.edit_toggle = False
|
||||
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context)
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if not bs_ob:
|
||||
settings.edit_toggle = edit_toggle
|
||||
return {"CANCELLED"}
|
||||
@@ -313,7 +309,7 @@ class BSBST_OT_delete_brushstrokes(bpy.types.Operator):
|
||||
|
||||
if surface_object:
|
||||
context.view_layer.objects.active = surface_object
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context)
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if bs_ob:
|
||||
context.view_layer.objects.active = bs_ob
|
||||
bs_ob.select_set(True)
|
||||
@@ -346,7 +342,7 @@ class BSBST_OT_duplicate_brushstrokes(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
settings = context.scene.BSBST_settings
|
||||
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context)
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if not bs_ob:
|
||||
return {"CANCELLED"}
|
||||
|
||||
@@ -410,7 +406,7 @@ class BSBST_OT_copy_brushstrokes(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
settings = context.scene.BSBST_settings
|
||||
|
||||
active_surface_object = utils.get_surface_object(utils.get_active_context_brushstrokes_object(context))
|
||||
active_surface_object = utils.get_surface_object(utils.get_active_context_brushstrokes_object(context.scene))
|
||||
|
||||
surface_objects = [ob for ob in context.selected_objects
|
||||
if ob.type=='MESH'
|
||||
@@ -423,7 +419,7 @@ class BSBST_OT_copy_brushstrokes(bpy.types.Operator):
|
||||
bs_objects = [bpy.data.objects.get(bs.name) for bs in settings.context_brushstrokes]
|
||||
bs_objects = [bs for bs in bs_objects if bs]
|
||||
else:
|
||||
bs_objects = [utils.get_active_context_brushstrokes_object(context)]
|
||||
bs_objects = [utils.get_active_context_brushstrokes_object(context.scene)]
|
||||
if not bs_objects:
|
||||
return {"CANCELLED"}
|
||||
|
||||
@@ -450,6 +446,17 @@ class BSBST_OT_copy_brushstrokes(bpy.types.Operator):
|
||||
|
||||
# remap surface pointers and context linked data TODO: refactor to deduplicate
|
||||
for ob in new_bs:
|
||||
utils.link_to_collections_by_ref(ob, surface_object)
|
||||
|
||||
# if it's still using the default name initialize names again
|
||||
if utils.split_id_name(ob.name)[0] == utils.bs_name(active_surface_object.name):
|
||||
if utils.is_flow_object(ob):
|
||||
ob.name = utils.flow_name(utils.bs_name(surface_object.name))
|
||||
else:
|
||||
ob.name = utils.bs_name(surface_object.name)
|
||||
elif ob.name.startswith(active_surface_object.name):
|
||||
ob.name = f"{surface_object.name}{ob.name[len(active_surface_object.name):]}"
|
||||
|
||||
ob.parent = surface_object
|
||||
utils.set_surface_object(ob, surface_object)
|
||||
|
||||
@@ -504,16 +511,16 @@ class BSBST_OT_select_surface(bpy.types.Operator):
|
||||
return bool(settings.context_brushstrokes)
|
||||
|
||||
def execute(self, context):
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context)
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if not bs_ob:
|
||||
return {"CANCELLED"}
|
||||
surface_object = getattr(bs_ob, '["BSBST_surface_object"]', None)
|
||||
if not surface_object:
|
||||
return {"CANCELLED"}
|
||||
|
||||
bpy.context.view_layer.objects.active = surface_object
|
||||
|
||||
if not context.mode == 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.context.view_layer.objects.active = surface_object
|
||||
for ob in bpy.data.objects:
|
||||
ob.select_set(False)
|
||||
surface_object.select_set(True)
|
||||
@@ -537,7 +544,7 @@ class BSBST_OT_assign_surface(bpy.types.Operator):
|
||||
return bool(settings.context_brushstrokes)
|
||||
|
||||
def execute(self, context):
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context)
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if not bs_ob:
|
||||
return {"CANCELLED"}
|
||||
|
||||
@@ -555,7 +562,7 @@ class BSBST_OT_assign_surface(bpy.types.Operator):
|
||||
layout.prop_search(self, 'surface_object', bpy.data, 'objects')
|
||||
|
||||
def invoke(self, context, event):
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context)
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
surf_ob = utils.get_surface_object(bs_ob)
|
||||
if surf_ob:
|
||||
self.surface_object = surf_ob.name
|
||||
@@ -632,7 +639,7 @@ class BSBST_OT_copy_flow(bpy.types.Operator):
|
||||
return bool(settings.context_brushstrokes)
|
||||
|
||||
def execute(self, context):
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context)
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
flow_ob_old = utils.get_flow_object(bs_ob)
|
||||
if not bs_ob:
|
||||
return {"CANCELLED"}
|
||||
@@ -670,7 +677,7 @@ class BSBST_OT_copy_flow(bpy.types.Operator):
|
||||
def invoke(self, context, event):
|
||||
settings = context.scene.BSBST_settings
|
||||
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context)
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
|
||||
for i in range(len(self.bs_list)):
|
||||
self.bs_list.remove(0)
|
||||
@@ -721,7 +728,7 @@ class BSBST_OT_switch_deformable(bpy.types.Operator):
|
||||
bs_objects = [bpy.data.objects.get(bs.name) for bs in settings.context_brushstrokes]
|
||||
bs_objects = [bs for bs in bs_objects if bs]
|
||||
else:
|
||||
bs_objects = [utils.get_active_context_brushstrokes_object(context)]
|
||||
bs_objects = [utils.get_active_context_brushstrokes_object(context.scene)]
|
||||
if not bs_objects:
|
||||
return {"CANCELLED"}
|
||||
|
||||
@@ -762,7 +769,7 @@ class BSBST_OT_switch_animated(bpy.types.Operator):
|
||||
bs_objects = [bpy.data.objects.get(bs.name) for bs in settings.context_brushstrokes]
|
||||
bs_objects = [bs for bs in bs_objects if bs]
|
||||
else:
|
||||
bs_objects = [utils.get_active_context_brushstrokes_object(context)]
|
||||
bs_objects = [utils.get_active_context_brushstrokes_object(context.scene)]
|
||||
if not bs_objects:
|
||||
return {"CANCELLED"}
|
||||
|
||||
@@ -963,7 +970,7 @@ class BSBST_OT_make_preset(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(utils.get_active_context_brushstrokes_object(context))
|
||||
return bool(utils.get_active_context_brushstrokes_object(context.scene))
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
@@ -977,7 +984,7 @@ class BSBST_OT_make_preset(bpy.types.Operator):
|
||||
settings.preset_object.modifiers.remove(mod)
|
||||
|
||||
# transfer brushstrokes modifiers to preset
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context)
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if not bs_ob:
|
||||
return {"CANCELLED"}
|
||||
for mod in bs_ob.modifiers:
|
||||
@@ -1098,7 +1105,7 @@ class BSBST_OT_brushstrokes_toggle_attribute(bpy.types.Operator):
|
||||
edit_toggle = settings.edit_toggle
|
||||
settings.edit_toggle = False
|
||||
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context)
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if not bs_ob:
|
||||
settings.edit_toggle = edit_toggle
|
||||
return {"CANCELLED"}
|
||||
|
||||
@@ -25,7 +25,7 @@ def update_brushstroke_method(self, context):
|
||||
preset_object = bpy.data.objects.get(preset_name)
|
||||
settings.preset_object = preset_object
|
||||
|
||||
style_object = utils.get_active_context_brushstrokes_object(context)
|
||||
style_object = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if not style_object:
|
||||
style_object = preset_object
|
||||
|
||||
@@ -45,7 +45,7 @@ def update_context_material(self, context):
|
||||
if settings.silent_switch:
|
||||
return
|
||||
|
||||
style_object = utils.get_active_context_brushstrokes_object(context)
|
||||
style_object = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
if not style_object:
|
||||
style_object = settings.preset_object
|
||||
if not style_object:
|
||||
@@ -119,7 +119,14 @@ def get_active_context_brushstrokes_index(self):
|
||||
return self["active_context_brushstrokes_index"]
|
||||
|
||||
def set_active_context_brushstrokes_index(self, value):
|
||||
settings = bpy.context.scene.BSBST_settings
|
||||
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
|
||||
@@ -136,12 +143,11 @@ def set_active_context_brushstrokes_index(self, value):
|
||||
return
|
||||
if not bs_ob:
|
||||
return
|
||||
if not bpy.context.object:
|
||||
if not active_object:
|
||||
return
|
||||
view_layer = bpy.context.view_layer
|
||||
if bpy.context.object.visible_get(view_layer = view_layer):
|
||||
if active_object.visible_get(view_layer = view_layer):
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.context.view_layer.objects.active = bs_ob
|
||||
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:
|
||||
@@ -257,7 +263,7 @@ class BSBST_Settings(bpy.types.PropertyGroup):
|
||||
gpv3 = bpy.context.preferences.experimental.use_grease_pencil_version3
|
||||
except:
|
||||
v0, v1, v3 = bpy.app.version
|
||||
gpv3 = v0 >= 4 and v1 >= 3
|
||||
gpv3 = v0 >= 4 or (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),
|
||||
|
||||
@@ -536,7 +536,7 @@ class BSBST_PT_brushstroke_tools_panel(bpy.types.Panel):
|
||||
row = style_panel.row()
|
||||
row_edit = row.row(align=True)
|
||||
row_edit.operator('brushstroke_tools.select_surface', icon='OUTLINER_OB_SURFACE', text='')
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context)
|
||||
bs_ob = utils.get_active_context_brushstrokes_object(context.scene)
|
||||
text = 'Edit Flow' if getattr(bs_ob, '["BSBST_method"]', None)=='SURFACE_FILL' else 'Edit Brushstrokes'
|
||||
row_edit.operator('brushstroke_tools.edit_brushstrokes', icon='GREASEPENCIL', text = text)
|
||||
row_edit.prop(settings, 'edit_toggle', icon='RESTRICT_SELECT_OFF' if settings.edit_toggle else 'RESTRICT_SELECT_ON', icon_only=True)
|
||||
@@ -549,15 +549,15 @@ class BSBST_PT_brushstroke_tools_panel(bpy.types.Panel):
|
||||
|
||||
class BSBST_MT_PIE_brushstroke_data_marking(bpy.types.Menu):
|
||||
bl_idname= "BSBST_MT_PIE_brushstroke_data_marking"
|
||||
bl_label = "Mark Brushstroke Flow"
|
||||
bl_label = "Mark Brushstroke Data"
|
||||
|
||||
items = {
|
||||
"Brush Flow - Mark": ['FORCE_WIND'],
|
||||
"Brush Flow - Clear": ['NONE'],
|
||||
"Brush Break - Mark": ['MOD_PHYSICS'],
|
||||
"Brush Break - Clear": ['NONE'],
|
||||
"Brush Ignore - Mark": ['X'],
|
||||
"Brush Ignore - Clear": ['NONE'],
|
||||
"Brush Flow - Mark": ['geometry.brush_flow_mark','FORCE_WIND'],
|
||||
"Brush Flow - Clear": ['geometry.brush_flow_clear','NONE'],
|
||||
"Brush Break - Mark": ['geometry.brush_break_mark','MOD_PHYSICS'],
|
||||
"Brush Break - Clear": ['geometry.brush_break_clear','NONE'],
|
||||
"Brush Ignore - Mark": ['geometry.brush_ignore_mark','X'],
|
||||
"Brush Ignore - Clear": ['geometry.brush_ignore_clear','NONE'],
|
||||
}
|
||||
|
||||
def draw(self, context):
|
||||
@@ -567,11 +567,14 @@ class BSBST_MT_PIE_brushstroke_data_marking(bpy.types.Menu):
|
||||
|
||||
for name, info in self.items.items():
|
||||
pie.alert=True
|
||||
op = pie.operator("geometry.execute_node_group", text=name, icon=info[0])
|
||||
op.asset_library_type='CUSTOM'
|
||||
op.asset_library_identifier=utils.asset_lib_name
|
||||
op.relative_asset_identifier=f"core/brushstroke_tools-resources.blend/NodeTree/{name}"
|
||||
|
||||
if bpy.app.version < (5,1):
|
||||
op = pie.operator("geometry.execute_node_group", text=name, icon=info[1])
|
||||
op.asset_library_type='CUSTOM'
|
||||
op.asset_library_identifier=utils.asset_lib_name
|
||||
op.relative_asset_identifier=f"core/brushstroke_tools-resources.blend/NodeTree/{name}"
|
||||
else:
|
||||
op = pie.operator(info[0], text=name, icon=info[1])
|
||||
|
||||
class BSBST_OT_brushstroke_data_marking(bpy.types.Operator):
|
||||
"""
|
||||
Call pie menu for operators to mark brushstroke data on the surface mesh
|
||||
|
||||
@@ -37,9 +37,8 @@ linkable_sockets = [
|
||||
asset_lib_name = 'Brushstroke Tools Library'
|
||||
|
||||
@persistent
|
||||
def find_context_brushstrokes(dummy):
|
||||
context = bpy.context
|
||||
settings = context.scene.BSBST_settings
|
||||
def find_context_brushstrokes(scene, depsgraph):
|
||||
settings = scene.BSBST_settings
|
||||
|
||||
edit_toggle = settings.edit_toggle
|
||||
settings.edit_toggle = False
|
||||
@@ -49,7 +48,7 @@ def find_context_brushstrokes(dummy):
|
||||
# identify context brushstrokes
|
||||
for el in range(len(settings.context_brushstrokes)):
|
||||
settings.context_brushstrokes.remove(0)
|
||||
context_object = context.object
|
||||
context_object = depsgraph.view_layer.objects.active
|
||||
if not is_brushstrokes_object(context_object):
|
||||
bs_ob = is_flow_object(context_object)
|
||||
if bs_ob:
|
||||
@@ -97,12 +96,11 @@ def find_context_brushstrokes(dummy):
|
||||
settings.edit_toggle = edit_toggle
|
||||
|
||||
@persistent
|
||||
def refresh_preset(dummy):
|
||||
context = bpy.context
|
||||
settings = context.scene.BSBST_settings
|
||||
def refresh_preset(scene, depsgraph):
|
||||
settings = scene.BSBST_settings
|
||||
if not settings:
|
||||
return
|
||||
for ob in [settings.preset_object, get_active_context_brushstrokes_object(context)]:
|
||||
for ob in [settings.preset_object, get_active_context_brushstrokes_object(scene)]:
|
||||
if not ob:
|
||||
continue
|
||||
for mod in ob.modifiers:
|
||||
@@ -313,6 +311,25 @@ def write_lib_version(dir: Path = None):
|
||||
with open(dir.joinpath(".version"), "w") as file:
|
||||
file.write(str(addon_version))
|
||||
|
||||
def get_file_blend_version(path: Path):
|
||||
with open(path, "rb") as f:
|
||||
prefix = f.read(4)
|
||||
f.seek(0)
|
||||
|
||||
if prefix.startswith(b"BLENDER"):
|
||||
header = f.read(12)
|
||||
|
||||
elif prefix == b"\x28\xb5\x2f\xfd": # zstd
|
||||
dctx = zstd.ZstdDecompressor()
|
||||
with dctx.stream_reader(f) as reader:
|
||||
header = reader.read(12)
|
||||
|
||||
else:
|
||||
raise ValueError("Unknown blend format")
|
||||
|
||||
version = header[9:12].decode()
|
||||
return int(version[0]), int(version[1:])
|
||||
|
||||
def copy_resources_to_dir(tgt_dir = ''):
|
||||
source_dir = get_addon_directory().joinpath('assets')
|
||||
if not tgt_dir:
|
||||
@@ -545,8 +562,21 @@ def find_brush_style_by_name(name: str):
|
||||
return brush_style
|
||||
return None
|
||||
|
||||
def link_to_collections_by_ref(obj, ref_obj):
|
||||
def link_to_collections_by_ref(obj, ref_obj, unlink=True):
|
||||
col_list = []
|
||||
|
||||
if unlink:
|
||||
for col in obj.users_collection:
|
||||
if col.library:
|
||||
continue
|
||||
col_list += [col]
|
||||
|
||||
if col_list:
|
||||
for col in col_list:
|
||||
col.objects.unlink(obj)
|
||||
|
||||
col_list = []
|
||||
|
||||
for col in ref_obj.users_collection:
|
||||
if col.library:
|
||||
continue
|
||||
@@ -754,8 +784,8 @@ def context_brushstrokes(context):
|
||||
settings = context.scene.BSBST_settings
|
||||
return settings.context_brushstrokes
|
||||
|
||||
def get_active_context_brushstrokes_object(context):
|
||||
settings = context.scene.BSBST_settings
|
||||
def get_active_context_brushstrokes_object(scene):
|
||||
settings = scene.BSBST_settings
|
||||
if not settings.context_brushstrokes:
|
||||
return None
|
||||
bs = settings.context_brushstrokes[settings.active_context_brushstrokes_index]
|
||||
@@ -765,19 +795,22 @@ def get_active_context_brushstrokes_object(context):
|
||||
def get_active_context_surface_object(context):
|
||||
if not context.object:
|
||||
return None
|
||||
bs_ob = get_active_context_brushstrokes_object(context)
|
||||
bs_ob = get_active_context_brushstrokes_object(context.scene)
|
||||
if bs_ob:
|
||||
return get_surface_object(bs_ob)
|
||||
if context.object.type == 'MESH':
|
||||
return context.object
|
||||
|
||||
def flow_name(name):
|
||||
return f'{name}-FLOW'
|
||||
def bs_name(surf_name: str) -> str:
|
||||
return f'{surf_name} - Brushstrokes'
|
||||
|
||||
def flow_name(bs_name: str) -> str:
|
||||
return f'{bs_name}-FLOW'
|
||||
|
||||
def edit_active_brushstrokes(context):
|
||||
context.view_layer.depsgraph.update()
|
||||
|
||||
bs_ob = get_active_context_brushstrokes_object(context)
|
||||
bs_ob = get_active_context_brushstrokes_object(context.scene)
|
||||
if not bs_ob:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"id": "rainclouds_bulk_scene_tools",
|
||||
"name": "Raincloud's Bulk Scene Tools",
|
||||
"tagline": "Bulk utilities for optimizing scene data",
|
||||
"version": "0.16.0",
|
||||
"version": "0.17.0",
|
||||
"type": "add-on",
|
||||
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
|
||||
"license": [
|
||||
@@ -49,9 +49,9 @@
|
||||
"Workflow",
|
||||
"Materials"
|
||||
],
|
||||
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.16.0/Rainys_Bulk_Scene_Tools.v0.16.0.zip",
|
||||
"archive_size": 80251,
|
||||
"archive_hash": "sha256:3e6fafe11caa39e48b94288c12b2a88e521c928955a854ffdd1bd0936e6bc70a"
|
||||
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
|
||||
"archive_size": 80981,
|
||||
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0.0",
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"version": "v1",
|
||||
"blocklist": [],
|
||||
"data": [
|
||||
{
|
||||
"schema_version": "1.0.0",
|
||||
"id": "basedplayblast",
|
||||
"name": "BasedPlayblast",
|
||||
"tagline": "Easily create playblasts from Blender and Flamenco",
|
||||
"version": "2.6.3",
|
||||
"type": "add-on",
|
||||
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
|
||||
"license": [
|
||||
"GPL-3.0-or-later"
|
||||
],
|
||||
"blender_version_min": "4.2.0",
|
||||
"website": "https://github.com/RaincloudTheDragon/BasedPlayblast",
|
||||
"permissions": {
|
||||
"files": "Import/export files and data"
|
||||
},
|
||||
"tags": [
|
||||
"Animation",
|
||||
"Render",
|
||||
"Workflow",
|
||||
"Video"
|
||||
],
|
||||
"archive_url": "https://github.com/RaincloudTheDragon/BasedPlayblast/releases/download/v2.6.3/BasedPlayblast.v2.6.3.zip",
|
||||
"archive_size": 49732,
|
||||
"archive_hash": "sha256:078b406105ce6f4802e75233569841e2f73d082e09cd1d954696681ebf72b627"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0.0",
|
||||
"id": "rainclouds_bulk_scene_tools",
|
||||
"name": "Raincloud's Bulk Scene Tools",
|
||||
"tagline": "Bulk utilities for optimizing scene data",
|
||||
"version": "0.17.0",
|
||||
"type": "add-on",
|
||||
"maintainer": "RaincloudTheDragon <raincloudthedragon@gmail.com>",
|
||||
"license": [
|
||||
"GPL-3.0-or-later"
|
||||
],
|
||||
"blender_version_min": "4.2.0",
|
||||
"website": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools",
|
||||
"permissions": {
|
||||
"files": "Read and write external resources referenced by scenes"
|
||||
},
|
||||
"tags": [
|
||||
"Scene",
|
||||
"Workflow",
|
||||
"Materials"
|
||||
],
|
||||
"archive_url": "https://github.com/RaincloudTheDragon/Rainys-Bulk-Scene-Tools/releases/download/v0.17.0/Rainys_Bulk_Scene_Tools.v0.17.0.zip",
|
||||
"archive_size": 80981,
|
||||
"archive_hash": "sha256:419433069465b45ea903bd7bb46d89aa28b9c96c541d587d5f3be651a762811f"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0.0",
|
||||
"id": "atomic_data_manager",
|
||||
"name": "Atomic Data Manager",
|
||||
"tagline": "Smart cleanup and inspection of Blender data-blocks",
|
||||
"version": "2.5.0",
|
||||
"type": "add-on",
|
||||
"maintainer": "RaincloudTheDragon",
|
||||
"license": [
|
||||
"GPL-3.0-or-later"
|
||||
],
|
||||
"blender_version_min": "4.2.0",
|
||||
"tags": [
|
||||
"utility",
|
||||
"management",
|
||||
"cleanup"
|
||||
],
|
||||
"archive_url": "https://github.com/RaincloudTheDragon/atomic-data-manager/releases/download/v2.5.0/Atomic_Data_Manager.v2.5.0.zip",
|
||||
"archive_size": 114674,
|
||||
"archive_hash": "sha256:4b4834ed3910a428d4cb01f1891247ad80089b6c5324fc27c6862b09e81ff1c1"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0.0",
|
||||
"id": "sheepit_project_submitter",
|
||||
"name": "SheepIt Project Submitter",
|
||||
"tagline": "Submit projects to SheepIt render farm",
|
||||
"version": "0.0.7",
|
||||
"type": "add-on",
|
||||
"maintainer": "RaincloudTheDragon",
|
||||
"license": [
|
||||
"GPL-3.0-or-later"
|
||||
],
|
||||
"blender_version_min": "3.0.0",
|
||||
"tags": [
|
||||
"render",
|
||||
"farm",
|
||||
"submission",
|
||||
"utility"
|
||||
],
|
||||
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.7/SheepIt_Project_Submitter.v0.0.7.zip",
|
||||
"archive_size": 47250,
|
||||
"archive_hash": "sha256:cb8dee48c45cc51dd8237981f4ab96d97d476b547c8c640606e9bbfd0390a055"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,7 +3,7 @@ schema_version = "1.0.0"
|
||||
id = "rainclouds_bulk_scene_tools"
|
||||
name = "Raincloud's Bulk Scene Tools"
|
||||
tagline = "Bulk utilities for optimizing scene data"
|
||||
version = "0.16.0"
|
||||
version = "0.17.0"
|
||||
type = "add-on"
|
||||
|
||||
maintainer = "RaincloudTheDragon <raincloudthedragon@gmail.com>"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# v0.17.0 2026-03-13
|
||||
- Remove Action FU: new operator in Animation Data section—clears fake users from all actions so only used animations are kept
|
||||
|
||||
# v0.16.0 2026-01-15
|
||||
- Delete Single Keyframe Actions: Blender 5.0 compatible (action layers/strips/channelbags); skip actions from linked libraries
|
||||
- Find Material Users: removed (issue #14; use Atomic or DBU)
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class RemoveActionFakeUsers(bpy.types.Operator):
|
||||
"""Remove fake users from all actions so only used animations are kept on save"""
|
||||
bl_idname = "bst.remove_action_fake_users"
|
||||
bl_label = "Remove Action FU"
|
||||
bl_description = "Remove fake users from all actions so only used animations are kept"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
actions = bpy.data.actions
|
||||
cleared_count = 0
|
||||
|
||||
for action in actions:
|
||||
if getattr(action, "library", None) is not None:
|
||||
continue
|
||||
if action.use_fake_user:
|
||||
action.use_fake_user = False
|
||||
cleared_count += 1
|
||||
print(f"Removed fake user from action '{action.name}'")
|
||||
|
||||
self.report({'INFO'}, f"Removed fake users from {cleared_count} actions")
|
||||
return {'FINISHED'}
|
||||
@@ -6,6 +6,7 @@ from ..ops.spawn_scene_structure import SpawnSceneStructure
|
||||
from ..ops.delete_single_keyframe_actions import DeleteSingleKeyframeActions
|
||||
from ..ops.remove_unused_material_slots import RemoveUnusedMaterialSlots
|
||||
from ..ops.convert_relations_to_constraint import ConvertRelationsToConstraint
|
||||
from ..ops.remove_action_fake_users import RemoveActionFakeUsers
|
||||
from ..ops.white_world import WhiteWorld
|
||||
from ..utils import compat
|
||||
|
||||
@@ -56,6 +57,8 @@ class RBST_SceneGen_PT_BulkSceneGeneral(bpy.types.Panel):
|
||||
box = layout.box()
|
||||
box.label(text="Animation Data")
|
||||
row = box.row(align=True)
|
||||
row.operator("bst.remove_action_fake_users", text="Remove Action FU", icon='FAKE_USER_OFF')
|
||||
row = box.row(align=True)
|
||||
row.operator("bst.delete_single_keyframe_actions", text="Delete Single Keyframe Actions", icon='ANIM_DATA')
|
||||
row = box.row(align=True)
|
||||
row.operator("bst.convert_relations_to_constraint", text="Convert Relations to Constraint", icon_value=405)
|
||||
@@ -71,6 +74,7 @@ classes = (
|
||||
DeleteSingleKeyframeActions,
|
||||
RemoveUnusedMaterialSlots,
|
||||
ConvertRelationsToConstraint,
|
||||
RemoveActionFakeUsers,
|
||||
)
|
||||
|
||||
# Registration
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"last_check": "2026-03-06 10:02:06.473451",
|
||||
"last_check": "2026-03-16 11:19:55.639825",
|
||||
"backup_date": "January-12-2026",
|
||||
"update_ready": false,
|
||||
"ignore": false,
|
||||
|
||||
@@ -3,7 +3,7 @@ schema_version = "1.0.0"
|
||||
id = "dynamiclinkmanager"
|
||||
name = "Dynamic Link Manager"
|
||||
tagline = "Character migrator and linked library tools"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
type = "add-on"
|
||||
|
||||
# Optional: Semantic Versioning
|
||||
|
||||
@@ -482,14 +482,36 @@ def run_retarg_relatives(orig, rep, rep_descendants, orig_to_rep):
|
||||
for ob in candidates:
|
||||
if ob.parent == orig:
|
||||
ob.parent = rep
|
||||
for c in getattr(ob, "constraints", []):
|
||||
if getattr(c, "target", None) == orig:
|
||||
c.target = rep
|
||||
if ob.type == "MESH" and ob.modifiers:
|
||||
for m in ob.modifiers:
|
||||
if getattr(m, "object", None) == orig:
|
||||
m.object = rep
|
||||
|
||||
# Retarget constraints on ALL objects (including orig hierarchy like eyes)
|
||||
for ob in bpy.data.objects:
|
||||
for c in getattr(ob, "constraints", []):
|
||||
if getattr(c, "target", None) == orig:
|
||||
c.target = rep
|
||||
|
||||
# Retarget bone constraints on ALL armatures (other characters, etc.)
|
||||
for ob in bpy.data.objects:
|
||||
if ob.type != "ARMATURE" or not ob.pose:
|
||||
continue
|
||||
for pbone in ob.pose.bones:
|
||||
for c in pbone.constraints:
|
||||
if getattr(c, "target", None) == orig:
|
||||
c.target = rep
|
||||
|
||||
# Camera DOF: retarget focus_object from orig to rep
|
||||
for ob in bpy.data.objects:
|
||||
if ob.type != 'CAMERA':
|
||||
continue
|
||||
camera = ob.data
|
||||
if not camera.dof:
|
||||
continue
|
||||
if camera.dof.focus_object == orig:
|
||||
camera.dof.focus_object = rep
|
||||
|
||||
|
||||
def _base_body_name_match(ob):
|
||||
"""True if object looks like the base body mesh (MESH, name has body+base)."""
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
"""Tweak tools: add/remove/bake COPY_TRANSFORMS on Rigify arm/leg tweak bones."""
|
||||
"""Tweak tools: add/remove/bake COPY_TRANSFORMS on Rigify tweak bones (arm, leg, or all)."""
|
||||
|
||||
import bpy
|
||||
|
||||
@@ -22,7 +22,7 @@ TWEAK_CONSTRAINT_NAME = "Copy from Original"
|
||||
|
||||
|
||||
def get_tweak_bones(armature, limb):
|
||||
"""Return list of tweak bone names that exist on armature. limb in 'arm', 'leg', 'both'."""
|
||||
"""Return list of tweak bone names that exist on armature. limb in 'arm', 'leg', 'body', 'both'."""
|
||||
if not armature or armature.type != "ARMATURE" or not armature.pose:
|
||||
return []
|
||||
bones = armature.pose.bones
|
||||
@@ -30,8 +30,20 @@ def get_tweak_bones(armature, limb):
|
||||
names = ARM_TWEAK_BONES
|
||||
elif limb == "leg":
|
||||
names = LEG_TWEAK_BONES
|
||||
elif limb == "body":
|
||||
# Body/torso tweaks: tweak_spine, spine_fk, etc. (no arm/leg)
|
||||
arm_leg_names = set(ARM_TWEAK_BONES + LEG_TWEAK_BONES)
|
||||
names = [
|
||||
b.name for b in bones
|
||||
if ("tweak" in b.name.lower() or "spine_fk" in b.name.lower())
|
||||
and b.name not in arm_leg_names
|
||||
]
|
||||
elif limb == "both":
|
||||
names = ARM_TWEAK_BONES + LEG_TWEAK_BONES
|
||||
# ALL tweak bones: any bone with "tweak" or "spine_fk" in the name
|
||||
names = [
|
||||
b.name for b in bones
|
||||
if "tweak" in b.name.lower() or "spine_fk" in b.name.lower()
|
||||
]
|
||||
else:
|
||||
return []
|
||||
return [n for n in names if n in bones]
|
||||
@@ -101,7 +113,7 @@ def bake_tweak_constraints(context, orig, rep, limb, track_name, post_clean):
|
||||
# Select only tweak bones on rep
|
||||
bpy.ops.pose.select_all(action="DESELECT")
|
||||
for name in names:
|
||||
rep.pose.bones[name].bone.select = True
|
||||
rep.pose.bones[name].select = True
|
||||
|
||||
# Bake
|
||||
try:
|
||||
|
||||
@@ -735,10 +735,72 @@ class DLM_OT_tweak_bake_leg(Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class DLM_OT_tweak_add_body(Operator):
|
||||
bl_idname = "dlm.tweak_add_body"
|
||||
bl_label = "Add Body Tweaks"
|
||||
bl_description = "Add tweak bone constraints to body/torso bones (spine, no arm/leg)"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return _tweak_poll(context)
|
||||
|
||||
def execute(self, context):
|
||||
orig, rep = _get_migrator_pair(context)
|
||||
from ..ops import tweak_tools
|
||||
tweak_tools.add_tweak_constraints(orig, rep, "body")
|
||||
self.report({"INFO"}, "Body tweak constraints added.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DLM_OT_tweak_remove_body(Operator):
|
||||
bl_idname = "dlm.tweak_remove_body"
|
||||
bl_label = "Remove Body Tweaks"
|
||||
bl_description = "Remove body/torso tweak constraints from the replacement character"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return _tweak_poll(context)
|
||||
|
||||
def execute(self, context):
|
||||
orig, rep = _get_migrator_pair(context)
|
||||
from ..ops import tweak_tools
|
||||
n = tweak_tools.remove_tweak_constraints(orig, rep, "body")
|
||||
self.report({"INFO"}, f"Removed {n} body tweak constraints.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DLM_OT_tweak_bake_body(Operator):
|
||||
bl_idname = "dlm.tweak_bake_body"
|
||||
bl_label = "Bake Body Tweaks"
|
||||
bl_description = "Bake body/torso tweak constraints to keyframes and optionally remove constraints"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return _tweak_poll(context)
|
||||
|
||||
def execute(self, context):
|
||||
orig, rep = _get_migrator_pair(context)
|
||||
props = context.scene.dynamic_link_manager
|
||||
from ..ops import tweak_tools
|
||||
ok, msg = tweak_tools.bake_tweak_constraints(
|
||||
context, orig, rep, "body",
|
||||
getattr(props, "tweak_nla_track_name", "") or "",
|
||||
getattr(props, "tweak_bake_post_clean", False),
|
||||
)
|
||||
if ok:
|
||||
self.report({"INFO"}, msg)
|
||||
return {"FINISHED"}
|
||||
self.report({"ERROR"}, msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class DLM_OT_tweak_add_both(Operator):
|
||||
bl_idname = "dlm.tweak_add_both"
|
||||
bl_label = "Add Arm & Leg Tweaks"
|
||||
bl_description = "Add tweak bone constraints to both arm and leg bones"
|
||||
bl_label = "Add All Tweaks"
|
||||
bl_description = "Add tweak bone constraints to all tweak bones (arm, leg, body)"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
@@ -749,14 +811,14 @@ class DLM_OT_tweak_add_both(Operator):
|
||||
orig, rep = _get_migrator_pair(context)
|
||||
from ..ops import tweak_tools
|
||||
tweak_tools.add_tweak_constraints(orig, rep, "both")
|
||||
self.report({"INFO"}, "Arm & leg tweak constraints added.")
|
||||
self.report({"INFO"}, "All tweak constraints added.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DLM_OT_tweak_remove_both(Operator):
|
||||
bl_idname = "dlm.tweak_remove_both"
|
||||
bl_label = "Remove Arm & Leg Tweaks"
|
||||
bl_description = "Remove all arm and leg tweak constraints from the replacement character"
|
||||
bl_label = "Remove All Tweaks"
|
||||
bl_description = "Remove all tweak constraints from the replacement character"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
@@ -773,8 +835,8 @@ class DLM_OT_tweak_remove_both(Operator):
|
||||
|
||||
class DLM_OT_tweak_bake_both(Operator):
|
||||
bl_idname = "dlm.tweak_bake_both"
|
||||
bl_label = "Bake Arm & Leg Tweaks"
|
||||
bl_description = "Bake all arm and leg tweak constraints to keyframes and optionally remove constraints"
|
||||
bl_label = "Bake All Tweaks"
|
||||
bl_description = "Bake all tweak constraints (arm, leg, body) to keyframes and optionally remove constraints"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
@@ -825,6 +887,9 @@ OPERATOR_CLASSES = [
|
||||
DLM_OT_tweak_add_leg,
|
||||
DLM_OT_tweak_remove_leg,
|
||||
DLM_OT_tweak_bake_leg,
|
||||
DLM_OT_tweak_add_body,
|
||||
DLM_OT_tweak_remove_body,
|
||||
DLM_OT_tweak_bake_body,
|
||||
DLM_OT_tweak_add_both,
|
||||
DLM_OT_tweak_remove_both,
|
||||
DLM_OT_tweak_bake_both,
|
||||
|
||||
@@ -119,9 +119,13 @@ class DLM_PT_main_panel(Panel):
|
||||
row.operator("dlm.tweak_remove_leg", text="Remove Leg", icon="X")
|
||||
row.operator("dlm.tweak_bake_leg", text="Bake Leg", icon="KEYFRAME")
|
||||
row = tweak_box.row(align=True)
|
||||
row.operator("dlm.tweak_add_both", text="Add Both", icon="CONSTRAINT_BONE")
|
||||
row.operator("dlm.tweak_remove_both", text="Remove Both", icon="X")
|
||||
row.operator("dlm.tweak_bake_both", text="Bake Both", icon="KEYFRAME")
|
||||
row.operator("dlm.tweak_add_body", text="Add Body", icon="CONSTRAINT_BONE")
|
||||
row.operator("dlm.tweak_remove_body", text="Remove Body", icon="X")
|
||||
row.operator("dlm.tweak_bake_body", text="Bake Body", icon="KEYFRAME")
|
||||
row = tweak_box.row(align=True)
|
||||
row.operator("dlm.tweak_add_both", text="Add All", icon="CONSTRAINT_BONE")
|
||||
row.operator("dlm.tweak_remove_both", text="Remove All", icon="X")
|
||||
row.operator("dlm.tweak_bake_both", text="Bake All", icon="KEYFRAME")
|
||||
row = tweak_box.row()
|
||||
row.prop(props, "tweak_nla_track_name", text="NLA track")
|
||||
row = tweak_box.row()
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user