cmcpasserby / MayaCharm

Maya integration for PyCharm.
MIT License
177 stars 44 forks source link

Connection Script Discussion #42

Closed dotRyan closed 4 years ago

dotRyan commented 5 years ago

Why

I made a script to go between all the computers I work on.

Switching computers that have different versions of maya end up getting different ports; so putting the connection script wasn't working out so well using :

import maya.cmds as cmds

if not cmds.commandPort(':4434', query=True):
    cmds.commandPort(name=':4434')

Rather than setting the port manually I usually let the mayacharm plugin choose the ports for me. So the ports changed if there was more than one version of maya. (on my laptop I have 2015 and 2018)

Usage

This is initiated from userSetup.py; variables set in there become ingested into maya's globals. Then when Im in maya i can just run mayacharm.connect() in maya's command line.

Limitations

The script assumes:

Questions

I'd like to discuss how we can implement this into this repo. Or possibly create another that stores this script. And if your plugin could point to download it.

Scripts

userSetup.py

from mayacharm import MayaCharm
mayacharm = MayaCharm()

mayacharm.py


import glob
import re
import os
import sys
import maya.cmds as cmds
import logging
from xml.etree import ElementTree

log = logging.getLogger("MayaCharm")

PLATFORM = sys.platform

PyCharm allows a configuration path to be set.

Add our own environment variable to to override where we look

PYCHARM_CONFIG_PATH = "PYCHARM_CONFIG_PATH"

regex to get find pycharm version from a path

MAYA_VERSION = cmds.about(version=True) MAYA_LOCATION = os.path.abspath(os.environ["MAYA_LOCATION"]) PORT_FORMAT = "mayaCharm:{port}"

class MayaCharm(object): def init(self, auto=False, product="PyCharm"): """Helper class to auto connect to mayaCharm, a plugin for pycharm

    https://github.com/cmcpasserby/MayaCharm

    Args:
        auto (bool): automatically connect on creation
        product (str): allow other JetBrains application to send commands.
                        `Defaults to PyCharm`

    """
    self._port = None
    self._port_name = None

    self._product = product
    self.__product_version_re = re.compile("""
        [/\\\\] # wrapped in a path
        [.]? # only macOS doesnt start with a "."
        {product} # <PRODUCT>
        (\d+(?:[.]\d+)?) # <VERSION>
        [/\\\\] # wrapped in a path
    """.format(product=self._product),
        re.VERBOSE)

    # Optionally allow users to not check the settings at startup
    if auto:
        self.connect()

def _get_port(self):
    """Try to determine the port configured in mayacharm settings

    Returns:
        int: Port number

    """

    ###################
    # Get Preferences #
    ###################

    # https://www.jetbrains.com/help/pycharm/directories-used-by-the-ide-to-store-settings-caches-plugins-and-logs.html

    # All platforms are stored in the user directory
    if PLATFORM == "win32":
        glob_path = "%userprofile%"
    else:
        glob_path = "~"

    # Override the path if the environment variable is set
    if PYCHARM_CONFIG_PATH in os.environ:
        glob_path = os.environ[PYCHARM_CONFIG_PATH]

    elif PLATFORM == "darwin":
        # mac
        # config dir is name of product under the users Library
        glob_path += "/Library/Preferences/{product}*"
    else:
        # windows|linux
        # config dir is directly under user folder and precedes with a `.`
        glob_path += "/.{product}*/config"

    # Find in sub directory
    glob_path += "/*/mayacharm.settings.xml"
    # Translate user folder
    glob_path = os.path.expanduser(glob_path)
    glob_path = os.path.expandvars(glob_path)
    # Add the product back in.
    glob_path = glob_path.format(product=self._product)
    glob_paths = glob.glob(glob_path)

    if not glob_paths:
        log.error("Could not find any settings")
        return

    mayacharm_settings = {}

    for maya_charm_settings_path in glob_paths:
        match = self.__product_version_re.findall(maya_charm_settings_path)
        if match:
            mayacharm_settings[float(match[0])] = maya_charm_settings_path

    if not mayacharm_settings:
        log.error("Unable to determine version settings")
        return

    ########################
    # Evaluate preferences #
    ########################

    # PyCharm version high -> low
    ordered_versions = sorted(mayacharm_settings.keys())
    ordered_versions.reverse()
    # Let the user know if were using assumed old settings
    latest_version = ordered_versions[0]

    for version in ordered_versions:
        tree = ElementTree.parse(mayacharm_settings[version])
        root = tree.getroot()
        for sdk_info in root.iter("SdkInfo"):
            sdk = {}
            for option in sdk_info.iter("option"):
                key = option.get("name")
                value = option.get("value")
                sdk[key] = value

            if "mayaPyPath" not in sdk and "port" not in sdk:
                continue

            py_path = sdk["mayaPyPath"]

            if MAYA_LOCATION in py_path:
                if version != latest_version:
                    log.info("Settings found in an older "
                             "version of {product}. Version "
                             "used is {version}. "
                             "Latest is {latest_version} ".format(
                        product=self._product,
                        version=version,
                        latest_version=latest_version))
                return sdk["port"]

def is_opened(self):
    if self._port is None:
        return False
    return cmds.commandPort(self._port_name, query=True)

def connect(self, refresh=False):
    """Connect to the existing port.

    If a port is found, it is cached and used for subsequent connections.
    If the settings have changed use `refresh` to update it.

    This is done to reduce the amount of system calls

    Args:
        refresh: Forces a reread of all mayacharm configuration files

    Returns:
        bool: Connection status

    """
    is_opened = self.is_opened()

    # If we are already opened do not attempt to make the same
    # connection unless refresh is specified.
    if is_opened and not refresh:
        log.warning("Already opened.")
        return True

    # We have a port but its not opened and were not
    # re-reading the settings.
    elif self._port is not None and not is_opened and not refresh:
        found_port = self._port
    else:

        # Treat everything below as a new connection

        # Get the port from the current preferences
        found_port = self._get_port()

        # Early return if no port was found
        if not found_port:
            log.warning("No preference files found")
            return False

        # If opened and refresh
        # disconnect the current if the port differs
        if refresh and is_opened:
            if self._port == found_port:
                log.info("Same port found in settings. Nothing will be applied")
                return True
            # Only disconnect if the port has changed
            self.disconnect()

    self._port = found_port
    self._port_name = PORT_FORMAT.format(port=self._port)

    existing_ports = cmds.commandPort(listPorts=True, query=True)

    if self._port_name in existing_ports:
        log.warning("Already connected")
        return True

    try:
        # Attempt to connect to the found port
        cmds.commandPort(name=self._port_name)
        log.info("Connected {port_name}".format(port_name=self._port_name))
    except:
        # Creation will only fail if the port exists
        # Note: I have not found a good way to get the port number

        # Assume port convention is `:port`
        found_names = filter(
            lambda port_name: (":" + self._port) in port_name,
            existing_ports)

        if not found_names:
            log.error("Failed to find an existing port {port}. "
                      "Can not create new connection.".format(
                port=self._port))
            return False

        log.warning("Attempting to close existing "
                    "port {port}".format(port=self._port))

        try:
            # Attempt to close the existing port so we can open a
            # new one with our naming convention
            cmds.commandPort(name=found_names[0], close=True)
            log.info("Closed {port_name}".format(port_name=found_names[0]))
        except:
            log.error("Failed to close existing port {port}. "
                      "Can not create new connection.".format(
                port=self._port))
            return False

        try:
            # Re Attempt to connect to the found port
            cmds.commandPort(name=self._port_name)
            log.info("Connected {port_name}".format(port_name=self._port_name))
            return True
        except:
            log.error("Unable to connect to {port_name} "
                      "after closing original port".format(
                port_name=self._port_name))
            return False

def disconnect(self):
    """Disconnect the current connection

    Returns:
        (bool|None): success of disconnecting. `None if nothing opened`

    """
    is_opened = self.is_opened()
    if not is_opened:
        if self._port:
            log.error("{port_name} is not opened.".format(port_name=self._port_name))
        else:
            log.error("Never connected.")
        return

    try:
        cmds.commandPort(name=self._port_name, close=True)
        log.info("Closing {port_name}".format(port_name=self._port_name))
        return True
    except:
        return False
cmcpasserby commented 5 years ago

could this not be solved simply by not sharing the runconfig and mayacharm settings between machines. then every machine would be pointing at its proper version? Unless i am misunderstanding your issue.

dotRyan commented 5 years ago

Lets say on

Computer A:
Computer B:

These ports are setup by default.

In the case of having more than one maya there is no constant default port that corresponds to the maya version. The current ports seem to increment from what was added first(this is fine).

The script allows you to not even know what the port is and just run mayacharm.connect() given that the interpreter was added.

cmcpasserby commented 5 years ago

you are able to override the ports that are setup by default with your own values as a way to keep things consistent. that is in the settings panel for MayaCharm just double click a port