Skip to content

Instantly share code, notes, and snippets.

@NotNite
Created April 1, 2023 20:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NotNite/a60877a52fe88d1f12fd0addf06ce135 to your computer and use it in GitHub Desktop.
Save NotNite/a60877a52fe88d1f12fd0addf06ce135 to your computer and use it in GitHub Desktop.
# 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