Open
Description
An updated Center 2D tool has been gifted to mmSolver, and this will need to be integrated
- Horizontal and Vertical offset slider - helps to update the current stabilized object position from any corner and not always from the viewport
center. - This version works on multiple selected objects including vertices / faces and average out their position.
Thanks to Debanjan Das Chowdhury for the updated tool.
Here is the code:
# Copyright (C) 2025 Debanjan Das Chowdhury.
#
# This file is part of mmSolver.
#
# mmSolver is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# mmSolver is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with mmSolver. If not, see <https://www.gnu.org/licenses/>.
#
import maya.cmds as cmds
import __main__
from math import tan, radians
def blastWin():
if cmds.window("stabilizerWin", exists=True):
cmds.deleteUI("stabilizerWin")
cmds.window("stabilizerWin", title="mm_Stabilizer", iconName='stblz', widthHeight=(200, 180), sizeable=True)
cmds.columnLayout(adjustableColumn=True)
cmds.button("stabilizeBtn", label="Stabilize", height=50, width=200, backgroundColor=(.4, .6, .4),
command=lambda *args: toggle_stabilization())
cmds.floatSliderGrp("hOffsetSlider", label="Horizontal Offset", field=True, min=-1.0, max=1.0, value=0.0,
dragCommand=lambda x: optimized_drag_offset("horizontal", x),
changeCommand=lambda x: finalize_offset())
cmds.floatSliderGrp("vOffsetSlider", label="Vertical Offset", field=True, min=-1.0, max=1.0, value=0.0,
dragCommand=lambda x: optimized_drag_offset("vertical", x),
changeCommand=lambda x: finalize_offset())
cmds.floatSliderGrp("zoomSlider", label="Zoom", field=True, min=0.1, max=4.0, value=1.0,
dragCommand=optimized_drag_zoom,
changeCommand=finalize_zoom)
cmds.checkBox("forceResetChk", label="Force Reset Pan/Zoom", value=False, changeCommand=lambda x: on_force_reset_check())
cmds.text(label=">> by Debanjan << ", align="right", height=20)
cmds.showWindow("stabilizerWin")
# Global variables
is_stabilized = False
stabilize_sel = []
stabilize_cam = ""
stabilize_camShape = ""
horizontal_offset = 0.0
vertical_offset = 0.0
base_panX = 0.0
base_panY = 0.0
def optimized_drag_offset(axis, value):
cmds.undoInfo(stateWithoutFlush=False)
set_offset(axis, value)
def finalize_offset():
cmds.undoInfo(stateWithoutFlush=True)
def optimized_drag_zoom(value):
cmds.undoInfo(stateWithoutFlush=False)
apply_zoom(value)
def finalize_zoom(value):
cmds.undoInfo(stateWithoutFlush=True)
def set_offset(axis, value):
global horizontal_offset, vertical_offset
if axis == "horizontal":
horizontal_offset = value
elif axis == "vertical":
vertical_offset = value
refresh_offset_pan()
def refresh_offset_pan():
global stabilize_camShape, base_panX, base_panY, horizontal_offset, vertical_offset
if stabilize_camShape and cmds.objExists(stabilize_camShape):
cmds.setAttr(stabilize_camShape + ".horizontalPan", base_panX + horizontal_offset)
cmds.setAttr(stabilize_camShape + ".verticalPan", base_panY + vertical_offset)
def apply_zoom(value):
global stabilize_camShape
if stabilize_camShape and cmds.objExists(stabilize_camShape) and cmds.attributeQuery("zoom", node=stabilize_camShape, exists=True):
cmds.setAttr(stabilize_camShape + ".zoom", value)
def reset_zoom():
global stabilize_camShape
if stabilize_camShape and cmds.objExists(stabilize_camShape) and cmds.attributeQuery("zoom", node=stabilize_camShape, exists=True):
cmds.setAttr(stabilize_camShape + ".zoom", 1.0)
cmds.floatSliderGrp("zoomSlider", edit=True, value=1.0)
def reset_offsets():
global horizontal_offset, vertical_offset
horizontal_offset = 0.0
vertical_offset = 0.0
cmds.floatSliderGrp("hOffsetSlider", edit=True, value=0.0)
cmds.floatSliderGrp("vOffsetSlider", edit=True, value=0.0)
refresh_offset_pan()
def on_force_reset_check():
if cmds.checkBox("forceResetChk", query=True, value=True):
force_reset_pan_zoom()
cmds.checkBox("forceResetChk", edit=True, value=False)
def force_reset_pan_zoom():
try:
global is_stabilized, stabilize_camShape, base_panX, base_panY
# Delete all expressions with names starting with 'stabilizator_expression_'
for expr in cmds.ls(type="expression"):
if expr.startswith("stabilizator_expression_"):
cmds.delete(expr)
stabilize_camShape = ""
base_panX = 0.0
base_panY = 0.0
global is_stabilized
# Delete all expressions with names starting with 'stabilizator_expression_'
for expr in cmds.ls(type="expression"):
if expr.startswith("stabilizator_expression_"):
cmds.delete(expr)
# Delete all expressions with names starting with 'stabilizator_expression_'
for expr in cmds.ls(type="expression"):
if expr.startswith("stabilizator_expression_"):
cmds.delete(expr)
panel = cmds.getPanel(withFocus=True)
cam = cmds.modelPanel(panel, q=True, camera=True)
if cmds.nodeType(cam) == 'camera':
parent = cmds.listRelatives(cam, parent=True)
if parent:
cam = parent[0]
shapes = cmds.listRelatives(cam, shapes=True)
if not shapes:
cmds.warning("No shape found under camera to reset.")
return
shape = shapes[0]
if cmds.objExists(shape):
if cmds.attributeQuery("horizontalPan", node=shape, exists=True):
cmds.setAttr(shape + ".horizontalPan", 0.0)
if cmds.attributeQuery("verticalPan", node=shape, exists=True):
cmds.setAttr(shape + ".verticalPan", 0.0)
if cmds.attributeQuery("zoom", node=shape, exists=True):
cmds.setAttr(shape + ".zoom", 1.0)
# Reset UI button state
cmds.button("stabilizeBtn", edit=True, label="Stabilize", backgroundColor=(.4, .6, .4))
is_stabilized = False
# Reset sliders
reset_offsets()
reset_zoom()
# Also force pan/zoom reset directly again on current camera
if cmds.objExists(shape):
if cmds.attributeQuery("horizontalPan", node=shape, exists=True):
cmds.setAttr(shape + ".horizontalPan", 0.0)
if cmds.attributeQuery("verticalPan", node=shape, exists=True):
cmds.setAttr(shape + ".verticalPan", 0.0)
if cmds.attributeQuery("zoom", node=shape, exists=True):
cmds.setAttr(shape + ".zoom", 1.0)
# Ensure global stabilize_camShape reflects this for future updates
stabilize_camShape = shape
print("Pan and Zoom reset to default values.")
except Exception as e:
cmds.warning("Error in force_reset_pan_zoom: %s" % str(e))
def toggle_stabilization():
global is_stabilized
if is_stabilized:
dstab()
reset_offsets()
reset_zoom()
cmds.button("stabilizeBtn", edit=True, label="Stabilize", backgroundColor=(.4, .6, .4))
is_stabilized = False
else:
sel()
cmds.button("stabilizeBtn", edit=True, label="De-stabilize", backgroundColor=(.8, .2, .2))
is_stabilized = True
def dstab():
try:
global base_panX, base_panY, stabilize_cam, stabilize_camShape
if not stabilize_cam:
current_panel = cmds.getPanel(withFocus=True)
stabilize_cam = cmds.modelPanel(current_panel, query=True, camera=True)
if cmds.nodeType(stabilize_cam) == 'camera':
parents = cmds.listRelatives(stabilize_cam, parent=True)
if parents:
stabilize_cam = parents[0]
expr_name = 'stabilizator_expression_' + stabilize_cam
if cmds.objExists(expr_name):
cmds.delete(expr_name)
else:
for expr in cmds.ls(type="expression"):
expr_str = cmds.expression(expr, q=True, s=True)
if "run_stabilize_expression" in expr_str:
cmds.delete(expr)
shapes = cmds.listRelatives(stabilize_cam, shapes=True)
if not shapes:
cmds.warning("No shape found under camera for destabilization.")
return
cameraShape = shapes[0]
stabilize_camShape = cameraShape
if cmds.objExists(cameraShape):
cmds.setAttr(cameraShape + ".horizontalPan", 0.0)
cmds.setAttr(cameraShape + ".verticalPan", 0.0)
if cmds.attributeQuery("zoom", node=cameraShape, exists=True):
cmds.setAttr(cameraShape + ".zoom", 1.0)
base_panX = 0.0
base_panY = 0.0
print("Destabilized: expression removed, pan and zoom reset.")
except Exception as e:
cmds.warning(f"Error in dstab: {str(e)}")
def sel():
global stabilize_sel, stabilize_camShape, stabilize_cam
try:
selection_list = cmds.ls(selection=True)
if not selection_list:
cmds.warning("Nothing selected. Please select object(s) to track.")
return
current_panel = cmds.getPanel(withFocus=True)
currentCam = cmds.modelPanel(current_panel, query=True, camera=True)
if cmds.nodeType(currentCam) == 'camera':
parents = cmds.listRelatives(currentCam, parent=True)
if parents:
currentCam = parents[0]
expr_name = 'stabilizator_expression_' + currentCam
if cmds.objExists(expr_name):
cmds.delete(expr_name)
shapes = cmds.listRelatives(currentCam, shapes=True)
if not shapes:
cmds.warning("No shape found under selected camera.")
return
cameraShape = shapes[0]
cmds.setAttr(cameraShape + ".panZoomEnabled", 1)
stabilize_sel = selection_list
stabilize_cam = currentCam
stabilize_camShape = cameraShape
expr = 'python("import __main__; __main__.run_stabilize_expression()")'
cmds.expression(name=expr_name, string=expr)
print("Stabilizer expression created and running.")
except Exception as e:
cmds.warning(f"Error in sel: {str(e)}")
def run_stabilize_expression():
global stabilize_sel, stabilize_camShape, base_panX, base_panY
try:
if not stabilize_sel or not stabilize_camShape:
return
count = len(stabilize_sel)
sum_pos = [0.0, 0.0, 0.0]
for obj in stabilize_sel:
pos = cmds.xform(obj, q=True, ws=True, t=True)
for i in range(3):
sum_pos[i] += pos[i]
avg_pos = [x / count for x in sum_pos]
fov_h = cmds.camera(stabilize_camShape, q=True, horizontalFieldOfView=True)
fov_v = cmds.camera(stabilize_camShape, q=True, verticalFieldOfView=True)
aperture_h = cmds.camera(stabilize_camShape, q=True, horizontalFilmAperture=True)
aperture_v = cmds.camera(stabilize_camShape, q=True, verticalFilmAperture=True)
camWorldInverseMatrix = cmds.getAttr(stabilize_camShape + '.worldInverseMatrix')
_posX = avg_pos[0] * camWorldInverseMatrix[0] + avg_pos[1] * camWorldInverseMatrix[4] + avg_pos[2] * camWorldInverseMatrix[8] + camWorldInverseMatrix[12]
_posY = avg_pos[0] * camWorldInverseMatrix[1] + avg_pos[1] * camWorldInverseMatrix[5] + avg_pos[2] * camWorldInverseMatrix[9] + camWorldInverseMatrix[13]
_posZ = avg_pos[0] * camWorldInverseMatrix[2] + avg_pos[1] * camWorldInverseMatrix[6] + avg_pos[2] * camWorldInverseMatrix[10] + camWorldInverseMatrix[14]
if _posZ == 0:
_posZ = 0.0001
screenPosX = (_posX / -_posZ) / tan(radians(fov_h / 2)) / 2.0 + 0.5
screenPosY = (_posY / -_posZ) / tan(radians(fov_v / 2)) / 2.0 + 0.5
base_panX = (screenPosX - 0.5) * aperture_h
base_panY = (screenPosY - 0.5) * aperture_v
refresh_offset_pan()
except Exception as e:
cmds.warning(f"Error in run_stabilize_expression: {str(e)}")
__main__.run_stabilize_expression = run_stabilize_expression
blastWin()