Created
April 1, 2023 20:37
-
-
Save NotNite/a60877a52fe88d1f12fd0addf06ce135 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Blender addon to make my life easier developing Xande.TestPlugin | |
import bpy | |
import os | |
# ===== Helper functions | |
def get_overlay() -> bpy.types.View3DOverlay: | |
for area in bpy.context.screen.areas: | |
if area.type == "VIEW_3D": | |
for space in area.spaces: | |
if space.type == "VIEW_3D": | |
return space.overlay | |
raise Exception("No 3D view found") | |
def get_glb_path() -> str: | |
temp = os.environ["TEMP"] | |
base_folder = temp + "/Xande.TestPlugin" | |
latest_folder_path = None | |
latest_folder_time = 0 | |
for folder in os.listdir(base_folder): | |
stats = os.stat(base_folder + "/" + folder) | |
if stats.st_mtime > latest_folder_time: | |
latest_folder_path = base_folder + "/" + folder | |
latest_folder_time = stats.st_mtime | |
if latest_folder_path is None: | |
raise Exception("No mesh exports found") | |
return latest_folder_path + "/mesh.glb" | |
def find_and_select_mesh() -> bpy.types.Object: | |
latest_mesh = None | |
for obj in bpy.data.objects: | |
if obj.type == "MESH": | |
latest_mesh = obj | |
if latest_mesh is None: | |
raise Exception("No mesh found") | |
# Select the mesh | |
bpy.context.view_layer.objects.active = latest_mesh | |
latest_mesh.select_set(True) | |
# Enter Edit mode | |
bpy.ops.object.mode_set(mode="EDIT") | |
return latest_mesh | |
def get_properties() -> bpy.types.SpaceProperties: | |
for area in bpy.context.screen.areas: | |
if area.type == "PROPERTIES": | |
for space in area.spaces: | |
if space.type == "PROPERTIES": | |
return space | |
raise Exception("No properties found") | |
# ===== Menu items | |
class XandeMenuBase(bpy.types.Operator): | |
bl_options = {"REGISTER", "UNDO"} | |
xande_menu_category = 0 | |
class XandeMenuLoadModel(XandeMenuBase): | |
bl_idname = "xande.load_model" | |
bl_label = "Load Xande.TestPlugin model" | |
xande_menu_category = 0 | |
def execute(self, context): | |
# Need to be in Object mode to delete everything | |
if bpy.context.object is not None and bpy.context.object.mode != "OBJECT": | |
bpy.ops.object.mode_set(mode="OBJECT") | |
# Wipe the scene objects | |
bpy.ops.object.select_all(action="SELECT") | |
bpy.ops.object.delete() | |
# Load the model | |
glb_path = get_glb_path() | |
bpy.ops.import_scene.gltf(filepath=glb_path) | |
return {"FINISHED"} | |
class XandeMenuToggleWeights(XandeMenuBase): | |
bl_idname = "xande.toggle_weights" | |
bl_label = "Toggle weight preview" | |
xande_menu_category = 1 | |
def execute(self, context): | |
mesh = find_and_select_mesh() | |
# Viewport Overlays -> Shading -> Vertex Group Weights | |
overlay = get_overlay() | |
overlay.show_weight = not overlay.show_weight | |
return {"FINISHED"} | |
class XandeMenuToggleBones(XandeMenuBase): | |
bl_idname = "xande.toggle_bones" | |
bl_label = "Toggle bone preview" | |
xande_menu_category = 1 | |
def execute(self, context): | |
# Viewport Overlays -> Objects -> Bones | |
overlay = get_overlay() | |
overlay.show_bones = not overlay.show_bones | |
return {"FINISHED"} | |
class XandeMenuOpenVertexGroups(XandeMenuBase): | |
bl_idname = "xande.open_vertex_groups" | |
bl_label = "Open vertex groups" | |
xande_menu_category = 2 | |
def execute(self, context): | |
mesh = find_and_select_mesh() | |
# Properties > Object Data Properties > Vertex Groups | |
properties = get_properties() | |
properties.context = "DATA" | |
return {"FINISHED"} | |
class XandeMenuToggleConsole(XandeMenuBase): | |
bl_idname = "xande.toggle_console" | |
bl_label = "Toggle console" | |
xande_menu_category = 3 | |
def execute(self, context): | |
bpy.ops.wm.console_toggle() | |
return {"FINISHED"} | |
class XandeMenuReload(XandeMenuBase): | |
bl_idname = "xande.reload" | |
bl_label = "Reload all scripts" | |
xande_menu_category = 3 | |
def execute(self, context): | |
bpy.ops.script.reload() | |
return {"FINISHED"} | |
# ===== Menu | |
class XandeMenu(bpy.types.Menu): | |
bl_idname = "OBJECT_MT_xande" | |
bl_label = "Xande" | |
def operator(self, klass): | |
return self.layout.operator(klass.bl_idname, text=klass.bl_label) | |
def draw(self, context): | |
menu_classes = {} | |
for cls in classes: | |
if not issubclass(cls, XandeMenuBase): | |
continue | |
if cls.xande_menu_category not in menu_classes: | |
menu_classes[cls.xande_menu_category] = [] | |
menu_classes[cls.xande_menu_category].append(cls) | |
last_category = max(menu_classes.keys()) | |
for category, classes2 in menu_classes.items(): | |
for cls in classes2: | |
self.operator(cls) | |
if category != last_category: | |
self.layout.separator() | |
# ===== Registering code | |
classes = [ | |
cls | |
for name, cls in globals().items() | |
if name.startswith("Xande") and name != "XandeMenuBase" and isinstance(cls, type) | |
] | |
addon_keymaps = [] | |
bl_info = { | |
"name": "Xande", | |
"author": "NotNite", | |
"description": "I'm tired of doing the same things over and over again", | |
"blender": (3, 5, 0), | |
} | |
def register(): | |
for cls in classes: | |
bpy.utils.register_class(cls) | |
# Ctrl+Alt+X | |
wm = bpy.context.window_manager | |
kc = wm.keyconfigs.addon | |
if kc: | |
km = wm.keyconfigs.addon.keymaps.new(name="3D View", space_type="VIEW_3D") | |
kmi = km.keymap_items.new("wm.call_menu", "X", "PRESS", ctrl=True, alt=True) | |
kmi.properties.name = XandeMenu.bl_idname | |
addon_keymaps.append((km, kmi)) | |
def unregister(): | |
for cls in classes: | |
bpy.utils.unregister_class(cls) | |
for km, kmi in addon_keymaps: | |
km.keymap_items.remove(kmi) | |
addon_keymaps.clear() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment