Utility_Apps/Blender/addons/Infinite Zoom/__init__.py
2025-07-06 09:31:57 -05:00

157 lines
5.1 KiB
Python

bl_info = {
"name": "Infinite Zoom",
"author": "ChatGPT + You",
"version": (1, 1),
"blender": (3, 0, 0),
"location": "View3D > Sidebar > View > Infinite Zoom",
"description": "Zoom toward cursor with adjustable non-linear scaling and UI status",
"category": "3D View"
}
import bpy
import bpy_extras.view3d_utils as view3d_utils
# -------------------------------------------------------------------
# Properties
# -------------------------------------------------------------------
class CursorZoomSettings(bpy.types.PropertyGroup):
base_speed: bpy.props.FloatProperty(
name="Base Speed",
default=0.01,
min=0.001,
max=10.0,
step=0.1
)
exponent: bpy.props.FloatProperty(
name="Exponent",
default=1.3,
min=0.1,
max=10.0,
step=0.1
)
min_step: bpy.props.FloatProperty(
name="Minimum Step",
default=0.001,
min=0.00001,
max=1.0,
step=0.1
)
is_zoom_running: bpy.props.BoolProperty(
name="Zoom Running",
default=False
)
# -------------------------------------------------------------------
# Modal Zoom Operator
# -------------------------------------------------------------------
class VIEW3D_OT_cursor_zoom_modal(bpy.types.Operator):
bl_idname = "view3d.cursor_zoom_modal"
bl_label = "Cursor Zoom Modal"
bl_options = {'REGISTER'}
def modal(self, context, event):
settings = context.scene.cursor_zoom_settings
context.area.tag_redraw()
if event.type in {'ESC', 'RIGHTMOUSE'}:
settings.is_zoom_running = False
return {'CANCELLED'}
if event.type in {'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
zoom_direction = 1 if event.type == 'WHEELUPMOUSE' else -1
region = context.region
region_3d = context.space_data.region_3d
mouse_coord = (event.mouse_region_x, event.mouse_region_y)
view_vector = view3d_utils.region_2d_to_vector_3d(region, region_3d, mouse_coord)
center = (region.width // 2, region.height // 2)
offset = (center[0] + 100, center[1])
p1 = view3d_utils.region_2d_to_location_3d(region, region_3d, center, region_3d.view_location)
p2 = view3d_utils.region_2d_to_location_3d(region, region_3d, offset, region_3d.view_location)
screen_scale = (p2 - p1).length
zoom_step = (screen_scale ** settings.exponent) * settings.base_speed
zoom_step = max(zoom_step, settings.min_step)
move_vector = view_vector.normalized() * zoom_direction * zoom_step
region_3d.view_location += move_vector
return {'RUNNING_MODAL'}
return {'PASS_THROUGH'}
def invoke(self, context, event):
if context.area.type == 'VIEW_3D':
context.scene.cursor_zoom_settings.is_zoom_running = True
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
else:
self.report({'WARNING'}, "3D View not found")
return {'CANCELLED'}
# -------------------------------------------------------------------
# Stop Operator
# -------------------------------------------------------------------
class VIEW3D_OT_stop_cursor_zoom(bpy.types.Operator):
bl_idname = "view3d.stop_cursor_zoom"
bl_label = "Stop Zoom"
def execute(self, context):
context.scene.cursor_zoom_settings.is_zoom_running = False
return {'FINISHED'}
# -------------------------------------------------------------------
# UI Panel
# -------------------------------------------------------------------
class VIEW3D_PT_cursor_zoom_panel(bpy.types.Panel):
bl_label = "Infinite Zoom"
bl_idname = "VIEW3D_PT_cursor_zoom_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'View'
def draw(self, context):
layout = self.layout
settings = context.scene.cursor_zoom_settings
layout.prop(settings, "base_speed")
layout.prop(settings, "exponent")
layout.prop(settings, "min_step")
layout.separator()
if settings.is_zoom_running:
layout.operator("view3d.stop_cursor_zoom", text="Stop Zoom", icon='PAUSE')
layout.label(text="Zoom is ACTIVE", icon='CHECKMARK')
else:
layout.operator("view3d.cursor_zoom_modal", text="Start Zoom", icon='PLAY')
layout.label(text="Zoom is INACTIVE", icon='INFO')
# -------------------------------------------------------------------
# Register / Unregister
# -------------------------------------------------------------------
classes = (
CursorZoomSettings,
VIEW3D_OT_cursor_zoom_modal,
VIEW3D_OT_stop_cursor_zoom,
VIEW3D_PT_cursor_zoom_panel,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.cursor_zoom_settings = bpy.props.PointerProperty(type=CursorZoomSettings)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.cursor_zoom_settings
if __name__ == "__main__":
register()