Skip to content

Instantly share code, notes, and snippets.

@soswow
Last active August 2, 2025 10:36
Show Gist options
  • Save soswow/0680771e4c015101bcf66b813ef53e8d to your computer and use it in GitHub Desktop.
Save soswow/0680771e4c015101bcf66b813ef53e8d to your computer and use it in GitHub Desktop.
Blender persistent xray feature
import bpy
def debug(msg):
print(f"[DEBUG] {msg}")
# Register custom properties
def register_properties():
wm = bpy.types.WindowManager
debug("Registering properties")
wm.xray_sync_enabled = bpy.props.BoolProperty(
name="Sync X-Ray",
description="Persist X-Ray state across mode changes",
default=False
)
wm.xray_synced_state = bpy.props.BoolProperty(
name="Last X-Ray State",
description="Stores last X-Ray state",
default=False
)
wm.xray_last_mode = bpy.props.StringProperty(
name="Last Mode",
default=""
)
wm.xray_current_state = bpy.props.BoolProperty(
name="Current X-Ray State",
description="Tracks current X-Ray state for change detection",
default=False
)
def unregister_properties():
debug("Unregistering properties")
wm = bpy.types.WindowManager
for prop in ["xray_sync_enabled", "xray_synced_state", "xray_last_mode", "xray_current_state"]:
if hasattr(wm, prop):
debug(f"Removing property: {prop}")
delattr(wm, prop)
# Simple timer to check xray state changes
def check_xray_changes():
try:
wm = bpy.context.window_manager
if not wm.xray_sync_enabled:
return 0.5 # Check every 0.5 seconds when sync is disabled
# Get current xray state
current_state = False
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
current_state = space.shading.show_xray
break
if current_state is not None:
break
# Check if state changed
if wm.xray_current_state != current_state:
debug(f"X-Ray state changed from {wm.xray_current_state} to {current_state}")
wm.xray_current_state = current_state
wm.xray_synced_state = current_state
debug(f"Updated synced_state to {current_state}")
return 0.5 # Check every 0.5 seconds
except Exception as e:
print(f"Error in check_xray_changes: {e}")
return 0.5
# Handler to restore X-Ray state after mode change
def sync_xray_on_mode_change(scene):
try:
debug("sync_xray_on_mode_change called")
wm = bpy.context.window_manager
current_mode = bpy.context.mode
debug(f"Current mode: {current_mode}, Last mode: {wm.xray_last_mode}")
if not wm.xray_sync_enabled:
debug("Sync not enabled, updating last mode")
wm.xray_last_mode = current_mode
return
if wm.xray_last_mode != current_mode:
debug("Mode changed. Restoring X-Ray state")
wm.xray_last_mode = current_mode
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
debug(f"Setting show_xray = {wm.xray_synced_state}")
space.shading.show_xray = wm.xray_synced_state
except Exception as e:
print(f"Error in sync_xray_on_mode_change: {e}")
# Handler to track user toggling X-Ray directly
def track_manual_xray_toggle(scene):
try:
debug("track_manual_xray_toggle called")
wm = bpy.context.window_manager
# Always track changes, regardless of sync state
found_viewport = False
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
found_viewport = True
current_state = space.shading.show_xray
debug(f"Found viewport, current show_xray: {current_state}")
if wm.xray_synced_state != current_state:
debug(f"X-Ray state changed from {wm.xray_synced_state} to {current_state}")
# Only update synced state if sync is enabled
if wm.xray_sync_enabled:
wm.xray_synced_state = current_state
debug(f"Updated synced_state to {current_state}")
else:
debug("Sync not enabled, but tracking change")
else:
debug(f"No change in X-Ray state: {current_state}")
break
if found_viewport:
break
if not found_viewport:
debug("No 3D viewport found")
except Exception as e:
print(f"Error in track_manual_xray_toggle: {e}")
# Operator for toolbar button
class VIEW3D_OT_toggle_xray_sync(bpy.types.Operator):
bl_idname = "view3d.toggle_xray_sync"
bl_label = "Toggle X-Ray Sync"
bl_description = "Toggle persistent X-Ray mode behavior"
def execute(self, context):
try:
wm = context.window_manager
wm.xray_sync_enabled = not wm.xray_sync_enabled
debug(f"Toggled xray_sync_enabled to {wm.xray_sync_enabled}")
if wm.xray_sync_enabled:
for area in context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
wm.xray_synced_state = space.shading.show_xray
wm.xray_current_state = space.shading.show_xray
debug(f"Saved current show_xray state: {wm.xray_synced_state}")
debug(f"Initialized current_state: {wm.xray_current_state}")
wm.xray_last_mode = context.mode
debug(f"Set last_mode to {wm.xray_last_mode}")
return {'FINISHED'}
except Exception as e:
self.report({'ERROR'}, f"Error toggling X-Ray sync: {e}")
return {'CANCELLED'}
# Draw toggle in Viewport header
def draw_xray_sync_button(self, context):
wm = context.window_manager
layout = self.layout
# Safety check - only draw if properties are registered
if not hasattr(wm, 'xray_sync_enabled'):
return
# Main sync button
layout.operator(
"view3d.toggle_xray_sync",
text="",
icon="XRAY",
depress=wm.xray_sync_enabled
)
# Register/unregister
classes = (VIEW3D_OT_toggle_xray_sync,)
def register():
debug("Registering addon")
for cls in classes:
bpy.utils.register_class(cls)
register_properties()
bpy.types.VIEW3D_HT_header.append(draw_xray_sync_button)
if sync_xray_on_mode_change not in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.append(sync_xray_on_mode_change)
debug("Added sync_xray_on_mode_change handler")
# Add timer to check for xray changes
bpy.app.timers.register(check_xray_changes)
debug("Added check_xray_changes timer")
def unregister():
debug("Unregistering addon")
# Remove handlers safely
try:
# Remove sync handler by function name
for handler in bpy.app.handlers.depsgraph_update_post:
if handler.__name__ == 'sync_xray_on_mode_change':
bpy.app.handlers.depsgraph_update_post.remove(handler)
debug("Removed sync_xray_on_mode_change handler")
break
except Exception as e:
print(f"Error removing sync handler: {e}")
# Remove timer
try:
# Find and remove timer by function name
for timer in bpy.app.timers.get_list():
if timer.__name__ == 'check_xray_changes':
bpy.app.timers.unregister(timer)
debug("Removed check_xray_changes timer")
break
except Exception as e:
print(f"Error removing timer: {e}")
# Remove UI elements safely
try:
# Remove by function name instead of direct reference
for draw_func in bpy.types.VIEW3D_HT_header.draw_funcs:
if draw_func.__name__ == 'draw_xray_sync_button':
bpy.types.VIEW3D_HT_header.remove(draw_func)
debug("Removed draw_xray_sync_button from header")
break
except Exception as e:
print(f"Error removing header button: {e}")
# Unregister properties and classes
unregister_properties()
# remove_xray_property_update() # This line is removed as per the edit hint
for cls in reversed(classes):
try:
bpy.utils.unregister_class(cls)
except Exception as e:
print(f"Error unregistering class {cls.__name__}: {e}")
if __name__ == "__main__":
unregister()
register()
import bpy
from bpy import context as C, data as D
from bpy.types import SpaceView3D
def detect_xray_change():
for area in C.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
if bpy.types.SpaceView3D.current_xray_state != space.shading.show_xray:
bpy.types.SpaceView3D.current_xray_state = space.shading.show_xray
print(bpy.types.SpaceView3D.current_xray_state)
def register():
if not hasattr(bpy.types.SpaceView3D, 'my_handlers'):
print("my_handlers not found, creating it")
bpy.types.SpaceView3D.my_handlers = []
if not hasattr(bpy.types.SpaceView3D, 'current_xray_state'):
print("current_xray_state not found, creating it")
bpy.types.SpaceView3D.current_xray_state = None
handler = SpaceView3D.draw_handler_add(detect_xray_change, (), 'WINDOW', 'PRE_VIEW')
SpaceView3D.my_handlers.append(handler)
def unregister():
for handler in SpaceView3D.my_handlers:
SpaceView3D.draw_handler_remove(handler, 'WINDOW')
bpy.types.SpaceView3D.my_handlers = []
if hasattr(bpy.types.SpaceView3D, 'current_xray_state'):
print("current_xray_state found, deleting it")
del bpy.types.SpaceView3D.current_xray_state
if __name__ == "__main__":
unregister()
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment