Skip to content

Maya Tool - Updated Center 2D Tool #279

Open
@david-cattermole

Description

@david-cattermole

An updated Center 2D tool has been gifted to mmSolver, and this will need to be integrated

  1. Horizontal and Vertical offset slider - helps to update the current stabilized object position from any corner and not always from the viewport
    center.
  2. 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()

Metadata

Metadata

Labels

maya toolA user tool inside Maya.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions