Helper script for action node editing

This commit is contained in:
2026-01-31 04:37:38 +03:00
parent c860152f9b
commit 201aa92fe7

View File

@@ -0,0 +1,282 @@
import bpy
ACTION_PROPS = ["name", "action", "action_text", "height", "radius", "tags"]
FURNITURE_PROPS = ["name", "furniture_tags"]
class OBJECT_OT_CreateActionProps(bpy.types.Operator):
"""Initializes custom properties on the selected action- empty"""
bl_idname = "object.create_action_props"
bl_label = "Create Action"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
obj = context.object
for p in ACTION_PROPS:
if p not in obj:
if p in ["height"]:
obj[p] = 1.0
elif p in ["radius"]:
obj[p] = 0.5
elif p == "name":
obj[p] = obj.name.replace("action-","")
elif p == "action_text":
if "action" in obj:
obj[p] = obj["action"].capitalize()
else:
obj[p] = ""
else:
obj[p] = "" # Initialize as empty text string
return {'FINISHED'}
class OBJECT_OT_CreateFurnitureProps(bpy.types.Operator):
"""Initializes custom properties on the selected furniture- empty"""
bl_idname = "object.create_furniture_props"
bl_label = "Create Furniture"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
obj = context.object
for p in FURNITURE_PROPS:
if p not in obj:
if p == "name":
obj[p] = obj.name.replace("furniture-","")
elif p == "furniture_tags":
obj[p] = ""
otags = []
if obj.name.find("toilet") >= 0:
otags.append("wc")
otags.append("bathroom")
otags.append("toilet")
elif obj.name.find("bed") >= 0:
otags.append("bed")
otags.append("bedroom")
obj[p] = ", ".join(otags)
else:
obj[p] = "" # Initialize as empty text string
return {'FINISHED'}
class OBJECT_OT_CreateChildEmpties(bpy.types.Operator):
"""Creates two child empties"""
bl_idname = "object.create_child_empties"
bl_label = "Create Child Nodes"
bl_options = {'REGISTER', 'UNDO'}
target_val: bpy.props.StringProperty()
def execute(self, context):
parent = context.object
# Define offsets and their corresponding labels for the name
configs = []
configs_enter = [((0, 0.5, -parent.location.z), "enter")]
configs_exit = [((0, -0.5, -parent.location.z), "exit")]
if self.target_val == "enter":
configs = configs_enter
elif self.target_val == "exit":
configs = configs_exit
elif self.target_val == "all":
configs = configs_enter + configs_exit
for offset, label in configs:
# Create new Empty with the "position-" prefix
child_name = f"position-{label}.000"
child = bpy.data.objects.new(child_name, None)
# Link to the current collection
context.collection.objects.link(child)
# Set parent and local offset
child.parent = parent
child.location = offset
child["name"] = label
return {'FINISHED'}
class OBJECT_OT_CreateChildActionNode(bpy.types.Operator):
"""Creates child action node"""
bl_idname = "object.create_child_action_node"
bl_label = "Create Child Action Node"
bl_options = {'REGISTER', 'UNDO'}
target_val: bpy.props.StringProperty()
def execute(self, context):
parent = context.object
# Define offsets and their corresponding labels for the name
configs = [((0, 0.1, -parent.location.z + 0.5), self.target_val)]
for offset, label in configs:
# Create new Empty with the "position-" prefix
child_name = f"action-{label}.000"
child = bpy.data.objects.new(child_name, None)
# Link to the current collection
context.collection.objects.link(child)
# Set parent and local offset
child.parent = parent
child.location = offset
child["name"] = label
child["action"] = label
for p in ACTION_PROPS:
if p not in child:
if p in ["height"]:
child[p] = 1.0
elif p in ["radius"]:
child[p] = 0.5
elif p == "name":
child[p] = child.name.replace("action-","")
elif p == "action_text":
if "action" in child:
child[p] = child["action"].capitalize()
else:
child[p] = ""
else:
child[p] = "" # Initialize as empty text string
return {'FINISHED'}
class OBJECT_OT_DestroyChildByProp(bpy.types.Operator):
"""Deletes children starting with 'position-' where custom prop 'name' matches value"""
bl_idname = "object.destroy_child_by_prop"
bl_label = "Destroy Child"
bl_options = {'REGISTER', 'UNDO'}
target_val: bpy.props.StringProperty()
def execute(self, context):
parent = context.object
# Collect children to delete to avoid modifying list during iteration
to_delete = [
child for child in parent.children
if child.name.startswith("position-") and child.get("name") == self.target_val
]
for child in to_delete:
bpy.data.objects.remove(child, do_unlink=True)
return {'FINISHED'}
class OBJECT_PT_ActionEmptyPanel(bpy.types.Panel):
"""Custom GUI panel for 'action-' prefixed Empties"""
bl_label = "Action Controls"
bl_idname = "OBJECT_PT_action_empty_panel"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
@classmethod
def poll(cls, context):
obj = context.object
return (obj is not None and
obj.type == 'EMPTY' and
obj.name.startswith("action-"))
def draw(self, context):
layout = self.layout
obj = context.object
# Check if all required properties already exist
props_exist = all(p in obj for p in ACTION_PROPS)
if not props_exist:
# Show ONLY the button if properties are missing
layout.operator("object.create_action_props", icon='PLUS')
else:
# Show the text fields if properties exist
box = layout.box()
box.label(text="Action Settings", icon='PROPERTIES')
for p in ACTION_PROPS:
box.prop(obj, f'["{p}"]', text=p.replace("_", " ").title())
layout.separator()
have_enter = False
have_exit = False
for c in obj.children:
if c.name.startswith("position-") and "name" in c and c["name"] == "enter":
have_enter = True
break
for c in obj.children:
if c.name.startswith("position-") and "name" in c and c["name"] == "exit":
have_exit = True
break
if not have_enter:
op_enter = layout.operator("object.create_child_empties", icon='EMPTY_DATA', text="Add Enter Position")
op_enter.target_val = "enter"
else:
op_enter = layout.operator("object.destroy_child_by_prop", text="Destroy 'Enter' Node", icon='X')
op_enter.target_val = "enter"
if not have_exit:
op_exit = layout.operator("object.create_child_empties", icon='EMPTY_DATA', text="Add Exit Position")
op_exit.target_val = "exit"
else:
op_exit = layout.operator("object.destroy_child_by_prop", text="Destroy 'Exit' Node", icon='X')
op_exit.target_val = "exit"
class OBJECT_PT_FurnitureEmptyPanel(bpy.types.Panel):
"""Custom GUI panel for 'furniture-' prefixed Empties"""
bl_label = "Furniture Controls"
bl_idname = "OBJECT_PT_action_furniture_panel"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
@classmethod
def poll(cls, context):
obj = context.object
return (obj is not None and
obj.type == 'EMPTY' and
obj.name.startswith("furniture-"))
def draw(self, context):
layout = self.layout
obj = context.object
# Check if all required properties already exist
props_exist = all(p in obj for p in FURNITURE_PROPS)
if not props_exist:
# Show ONLY the button if properties are missing
layout.operator("object.create_furniture_props", icon='PLUS')
else:
# Show the text fields if properties exist
box = layout.box()
box.label(text="Furnitore Settings", icon='PROPERTIES')
for p in FURNITURE_PROPS:
box.prop(obj, f'["{p}"]', text=p.replace("_", " ").title())
op_use = layout.operator("object.create_child_action_node", icon='EMPTY_DATA', text="Add Use Action Node")
op_use.target_val = "use"
op_use = layout.operator("object.create_child_action_node", icon='EMPTY_DATA', text="Add Sit Action Node")
op_use.target_val = "sit"
classes = (TEST_PT_CharacterPanel, TEST_OT_LinkAndPlay, TEST_OT_Cleanup)
def register():
bpy.utils.register_class(OBJECT_OT_CreateActionProps)
bpy.utils.register_class(OBJECT_OT_CreateFurnitureProps)
bpy.utils.register_class(OBJECT_PT_ActionEmptyPanel)
bpy.utils.register_class(OBJECT_OT_CreateChildEmpties)
bpy.utils.register_class(OBJECT_OT_DestroyChildByProp)
bpy.utils.register_class(OBJECT_PT_FurnitureEmptyPanel)
bpy.utils.register_class(OBJECT_OT_CreateChildActionNode)
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.test_filepath = bpy.props.StringProperty(name="File Path", subtype='FILE_PATH', default="//char.blend")
bpy.types.Scene.test_obj_name = bpy.props.StringProperty(name="Rig Name", default="Character_Rig")
bpy.types.Scene.test_action_name = bpy.props.StringProperty(name="Action", default="sitting")
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.test_filepath
del bpy.types.Scene.test_obj_name
del bpy.types.Scene.test_action_name
bpy.utils.register_class(OBJECT_OT_CreateActionProps)
bpy.utils.register_class(OBJECT_OT_CreateFurnitureProps)
bpy.utils.unregister_class(OBJECT_PT_ActionEmptyPanel)
bpy.utils.unregister_class(OBJECT_OT_CreateChildEmpties)
bpy.utils.unregister_class(OBJECT_OT_DestroyChildByProp)
bpy.utils.unregister_class(OBJECT_PT_FurnitureEmptyPanel)
bpy.utils.unregister_class(OBJECT_OT_CreateChildActionNode)
if __name__ == "__main__":
register()