diff --git a/assets/blender/buildings/parts/edit-firniture-props.py b/assets/blender/buildings/parts/edit-firniture-props.py new file mode 100644 index 0000000..ae00e2b --- /dev/null +++ b/assets/blender/buildings/parts/edit-firniture-props.py @@ -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() +