Integrating up through commit 90f050496

This commit is contained in:
alexpete
2021-04-07 14:03:29 -07:00
parent 8f2ed080a9
commit c2cbd430fe
2694 changed files with 285622 additions and 176874 deletions
@@ -17,7 +17,7 @@
import os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -67,19 +67,12 @@ _DCCSI_DEV_MODE = env_bool.env_bool(constants.ENVAR_DCCSI_DEV_MODE, False)
# for py2.7 (Maya) we provide this, so we must assume some bootstrapping
# has occured, see DccScriptingInterface\\config.py (_DCCSI_PYTHON_LIB_PATH)
if sys.version_info.major >= 3:
import pathlib
else: # py2.x
import pathlib2 as pathlib # python 2 backport
# its mkdir() function supposedly supports exist_ok
# ^ but in practice still seems to bark
# TypeError: mkdir() got an unexpected keyword argument 'exist_ok'
import pathlib
from pathlib import Path
if _G_DEBUG:
print('DCCsi debug breadcrumb, pathlib is: {}'.format(pathlib))
from pathlib import Path
# to be continued...
# get/set the project name
@@ -95,7 +88,7 @@ _LY_PROJECT_TAG = os.getenv(constants.ENVAR_LY_PROJECT,
_DCCSI_LOG_PATH = Path(os.getenv(constants.ENVAR_DCCSI_LOG_PATH,
Path(_LY_DEV,
_LY_PROJECT_TAG,
'Cache',
'Cache',
'pc', 'user', 'log', 'logs')))
@@ -105,7 +98,7 @@ for handler in _logging.root.handlers[:]:
# very basic root logger for early debugging, flip to while 1:
while 0:
_logging.basicConfig(level=_logging.DEBUG,
format=constants.FRMT_LOG_LONG,
format=constants.FRMT_LOG_LONG,
datefmt='%m-%d %H:%M')
_logging.debug('azpy.rootlogger> root logger set up for debugging') # root logger
@@ -190,7 +183,7 @@ def initialize_logger(name,
_logger.debug("Folder is already there")
else:
_logger.debug("Folder was created")
_log_filepath = Path(_DCCSI_LOG_PATH, '{}.log'.format(name))
try:
_log_filepath.touch(mode=0o666, exist_ok=True)
@@ -17,7 +17,7 @@
import os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -30,7 +30,7 @@ _LOGGER = _logging.getLogger(_PACKAGENAME)
_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME}))
__all__ = ['get_os', 'return_stub', 'get_stub_check_path',
'get_dccsi_config']
'get_dccsi_config', 'get_current_project']
# note: this module should reamin py2.7 compatible (Maya) so no f'strings
# -------------------------------------------------------------------------
@@ -49,7 +49,7 @@ def get_os():
message = str("DCCsi.azpy.config_utils.py: "
"Unexpectedly executing on operating system '{}'"
"".format(sys.platform))
raise RuntimeError(message)
return os_folder
# -------------------------------------------------------------------------
@@ -72,7 +72,7 @@ def return_stub_dir(stub_file='dccsi_stub'):
'({}) in a walk-up from currnet path'
''.format(stub_file))
break
_dir_to_last_file = path
return _dir_to_last_file
@@ -109,7 +109,7 @@ def get_stub_check_path(in_path=os.getcwd(), check_stub='engineroot.txt'):
# settings.setenv() # doing this will add the additional DYNACONF_ envars
def get_dccsi_config(dccsi_dirpath=return_stub_dir()):
"""Convenience method to set and retreive settings directly from module."""
# we can go ahead and just make sure the the DCCsi env is set
# config is SO generic this ensures we are importing a specific one
_module_tag = "dccsi.config"
@@ -123,18 +123,36 @@ def get_dccsi_config(dccsi_dirpath=return_stub_dir()):
str(_dccsi_path.resolve()))
_dccsi_config = importlib.util.module_from_spec(_spec_dccsi_config)
_spec_dccsi_config.loader.exec_module(_dccsi_config)
_LOGGER.debug('Executed config: {}'.format(_spec_dccsi_config))
else: # py2.x
import imp
_dccsi_config = imp.load_source(_module_tag, str(_dccsi_path.resolve()))
_LOGGER.debug('Imported config: {}'.format(_spec_dccsi_config))
return _dccsi_config
else:
return None
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def get_current_project(dev_folder=get_stub_check_path()):
"""Uses regex in lumberyard Dev\\bootstrap.cfg to retreive project tag str"""
boostrap_filepath = Path(dev_folder, "bootstrap.cfg")
if boostrap_filepath.exists():
bootstrap = open(str(boostrap_filepath), "r")
regex_str = r"^project_path\s*=\s*(.*)"
game_project_regex = re.compile(regex_str)
for line in bootstrap:
game_folder_match = game_project_regex.match(line)
if game_folder_match:
_LOGGER.debug('Project is: {}'.format(game_folder_match.group(1)))
return game_folder_match.group(1)
return None
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def bootstrap_dccsi_py_libs(dccsi_dirpath=return_stub_dir()):
"""Builds and adds local site dir libs based on py version"""
@@ -166,16 +184,18 @@ if __name__ == '__main__':
_LOGGER.info("# {0} #".format('-' * 72))
_LOGGER.info('Current Work dir: {0}'.format(os.getcwd()))
_LOGGER.info('OS: {}'.format(get_os()))
_LOGGER.info('DCCSIG_PATH: {}'.format(return_stub_dir('dccsi_stub')))
_config = get_dccsi_config()
_LOGGER.info('DCCSI_CONFIG_PATH: {}'.format(_config))
_LOGGER.info('LY_DEV: {}'.format(get_stub_check_path('engineroot.txt')))
_LOGGER.info('LY_PROJECT: {}'.format(get_current_project(get_stub_check_path('engineroot.txt'))))
_LOGGER.info('DCCSI_PYTHON_LIB_PATH: {}'.format(bootstrap_dccsi_py_libs(return_stub_dir('dccsi_stub'))))
# custom prompt
@@ -27,7 +27,7 @@ import os
import sys
import site
import logging as _logging
# for this module to perform standalone
# we need to set up basic access to the DCCsi
_MODULE_PATH = os.path.realpath(__file__) # To Do: what if frozen?
@@ -56,12 +56,15 @@ FRMT_LOG_SHRT = "[%(asctime)s][%(name)s][%(levelname)s] >> %(message)s"
_G_DEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False)
_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False)
for handler in _logging.root.handlers[:]:
_logging.root.removeHandler(handler)
_PACKAGENAME = 'azpy.constants'
_log_level = int(20)
_LOG_LEVEL = int(20)
if _G_DEBUG:
_log_level = int(10)
_logging.basicConfig(level=_log_level,
_LOG_LEVEL = int(10)
_logging.basicConfig(level=_LOG_LEVEL,
format=FRMT_LOG_LONG,
datefmt='%m-%d %H:%M')
_LOGGER = _logging.getLogger(_PACKAGENAME)
@@ -85,7 +88,7 @@ TAG_MOCK_PROJECT = str('MockProject')
TAG_DIR_LY_DEV = str('dev')
TAG_DIR_DCCSI_AZPY = str('azpy')
TAG_DIR_DCCSI_SDK = str('SDK')
TAG_DIR_LY_BUILD = str('windows_vs2019')
TAG_DIR_LY_BUILD = str('build')
TAG_QT_PLUGIN_PATH = str('QT_PLUGIN_PATH')
# filesystem markers, stub file names.
@@ -255,7 +258,7 @@ PATH_DEFAULT_WINGHOME = str('{0}{1}{2}.{3}'
TAG_DEFAULT_WING_MINOR_VER))
PATH_SAT_INSTALL_PATH = str('{0}\\{1}\\{2}\\{3}\\{4}'
''.format(PATH_PROGRAMFILES_X64,
''.format(PATH_PROGRAMFILES_X64,
'Allegorithmic',
'Substance Automation Toolkit',
'Python API',
@@ -273,7 +276,7 @@ if __name__ == '__main__':
_G_DEBUG = True
_DCCSI_DEV_MODE = True
_LOGGER.setLevel(_logging.DEBUG) # force debugging
# this is a top level module and to reduce cyclical azpy imports
# it only has a basic logger configured, add log to console
_handler = _logging.StreamHandler(sys.stdout)
@@ -0,0 +1,94 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
"""azpy.dev.ide.__init__"""
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
# global space
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
_PACKAGENAME = 'azpy.dev.ide'
from azpy import initialize_logger
_LOGGER = initialize_logger(_PACKAGENAME)
_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME}))
# -------------------------------------------------------------------------
__all__ = []
try:
import wingapi
__all__ = init_wing(__all__)
except:
pass
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def init_wing(_all):
"""If the wingapi is required for a package/module to import,
then it should be initialized and added here so general imports
don't fail"""
# Make sure we can import the native apis
import wingapi # this will fail if we can't
_all.append('wing')
# add others
# Importing additional local packages/modules
return _all
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def init_all(_all):
"""If the wingapi is required for a package/module to import,
then it should be initialized and added here so general imports
don't fail"""
# Make sure we can import the native apis
import wingapi # this will fail if we can't
_all.append('wing')
# add others
# Importing additional local packages/modules
return _all
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def import_all(_all=__all__):
"""this will test imports of __all__
can be run before or after init() to test"""
from azpy import test_imports
_LOGGER.debug('Testing Imports from {0}'.format(_PACKAGENAME))
test_imports(_all,
_pkg=_PACKAGENAME,
_logger=_LOGGER)
return _all
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
if _DCCSI_DEV_MODE:
# If in dev mode this will test imports of __all__
import_all(__all__)
# -------------------------------------------------------------------------
@@ -0,0 +1,83 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
# send each line to maya with > send_py_cmd_to_maya
print('Hello World: Command received from WingIDE')
import maya.cmds as cmds
foo = cmds.polySphere(n='DemoSphere', radius=1.0)
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# more complex example
import maya.cmds as cmds
import random
import time
name = 'DemoCube'
size = random.uniform(0.5, 2.0)
variation = random.uniform(1.5, 5.0)
amount = random.randint(9, 21)
# remove previous
obj_list = cmds.ls('{}*'.format(name))
if len(obj_list) > 0:
cmds.delete(obj_list)
for i in range(0, amount - 1):
depth_rand = random.uniform(size, size * variation)
tegel = cmds.polyCube(name='{}#'.format(name),
w=size, h=size, d=depth_rand)
cmds.move(size * i, 0, 5)
i += 1
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
import maya.cmds as cmds
import random
import time
name = 'DemoCube'
size = random.uniform(0.5, 2.0)
variation = random.uniform(1.5, 5.0)
amount = random.randint(9, 21)
def make_some_wonky_cubes(name=, size, variation, amount):
# remove previous
obj_list = cmds.ls('{}*'.format(name))
if len(obj_list) > 0:
cmds.delete(obj_list)
for i in range(0, amount - 1):
depth_rand = random.uniform(size, size * variation)
tegel = cmds.polyCube(name='{}#'.format(name),
w=size, h=size, d=depth_rand)
cmds.move(size * i, 0, 5)
i += 1
return
foo = make_some_wonky_cubes()
time.sleep(10)
foo = make_some_wonky_cubes()
while 1:
foo = make_some_wonky_cubes()
time.sleep(5)
foo = make_some_wonky_cubes(size=0.5, amount=20)
time.sleep(5)
break
@@ -0,0 +1,21 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
"""azpy.dev.ide.wing.__init__"""
__all__ = ['hot_keys', 'test']
@@ -0,0 +1,355 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# inspiration: http://www.emeraldartist.com/blog/2012/10/11/remotely-sending-code-to-maya-from-wing
from __future__ import unicode_literals
""" Module to remotely send code to maya. Inside of WingIDE prefs, you will
need to add the parent dire of this module to 'IDE Extension Scripting>Search Path'
Additionally, you will need set up custom key bindings 'User Interface>Keyboard'
"""
# -- This line is 75 characters -------------------------------------------
# standard imports
import socket
import random
import sys
import os
import time
import logging as _logging
# wing ide
import wingapi
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
MODULENAME = 'azpy.dev.ide.wing.hot_keys'
_LOGGER = _logging.getLogger(MODULENAME)
## extend logger
#_handler = _logging.StreamHandler(sys.stdout)
#_handler.setLevel(_logging.DEBUG)
#FRMT_LOG_LONG = "[%(name)s][%(levelname)s] >> %(message)s (%(asctime)s; %(filename)s:%(lineno)d)"
#_formatter = _logging.Formatter(FRMT_LOG_LONG)
#_handler.setFormatter(_formatter)
#_LOGGER.addHandler(_handler)
#_LOGGER.debug('Loading: {0}.'.format({MODULENAME}))
_LOCAL_HOST = socket.gethostbyname(socket.gethostname())
_LOGGER.info('local_host: {}'.format(_LOCAL_HOST))
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def display_text(test_str):
"""Displays text in a WingIDE pop-up dialog"""
app = wingapi.gApplication
v = "Product info is: " + str(app.GetProductInfo())
v += "\nAnd you typed: %s" % test_str
wingapi.gApplication.ShowMessageDialog("Test Message", v)
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def get_wing_text(): # no hotkey
"""
Return the text currently selected in wing
"""
editor = wingapi.gApplication.GetActiveEditor()
if editor is None:
return None
else:
current_doc = editor.GetDocument()
start, end = editor.GetSelection()
text_block = current_doc.GetCharRange(start, end)
_LOGGER.debug('selected text is: {}'.format(text_block))
return text_block
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def display_wing_text(): # Ctrl+Shift+D
text_block = get_wing_text()
display_text(text_block)
return text_block
display_wing_text.contexts = [
wingapi.kContextNewMenu("DCCsi Scripts"),
wingapi.kContextEditor(),
]
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def get_stub_check_path(in_path=__file__, check_stub='engineroot.txt'):
'''
Returns the branch root directory of the dev\\'engineroot.txt'
(... or you can pass it another known stub)
so we can safely build relative filepaths within that branch.
If the stub is not found, it returns None
'''
path = os.path.abspath(os.path.join(os.path.dirname(in_path), ".."))
_LOGGER.info('parent dir: {}'.format(path))
while 1:
test_path = os.path.join(path, check_stub)
if os.path.isfile(test_path):
return os.path.abspath(os.path.join(os.path.dirname(test_path)))
else:
path, tail = (os.path.abspath(os.path.join(os.path.dirname(test_path), "..")),
os.path.basename(test_path))
if (len(tail) == 0):
return None
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# globals
_LY_DEV = get_stub_check_path()
_LOGGER.info('_LY_DEV: {}'.format(_LY_DEV))
_PROJ_CACHE = os.path.join(_LY_DEV, 'cache', 'DCCsi', 'wing')
_LOGGER.info('_PROJ_CACHE: {}'.format(_PROJ_CACHE))
if not os.path.exists(_PROJ_CACHE):
os.makedirs(_PROJ_CACHE)
_LOGGER.info('SUCCESS creating: {}'.format(_PROJ_CACHE))
else:
_LOGGER.info('_PROJ_CACHE already exists: {}'.format(_PROJ_CACHE))
# makedirs(_PROJ_CACHE)
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def create_client_socket(local_host=_LOCAL_HOST,command_port=6000):
"""create a client (wing) socket connection to maya (server, commandPort)"""
for res in socket.getaddrinfo(local_host, command_port,
socket.AF_UNSPEC, socket.SOCK_STREAM,0, socket.AI_PASSIVE):
af, socktype, proto, canonname, sa = res
try:
mSocket = socket.socket(af, socktype, proto)
except socket.error as e:
mSocket = None
continue
try:
# Make our socket --> Maya connection:
mSocket.connect(sa)
except socket.error as e:
mSocket.close()
mSocket = None
continue
break
if not mSocket:
raise RuntimeError("Unable to initialise client socket.")
return mSocket
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def send_selection_to_maya(language='python',
local_host=_LOCAL_HOST,
command_port=6000):
"""Basic method to connect to Maya and send selected code over.
chunks can be large, which makes socket programming cumbersome.
This module stashes the selection in a temp .txt file in the cache.
Then send maya a command for a specific module, which will then
read that file and execute the code line-by-line, allowing for
arbirtarily large selections that might otherwise overrun buffer"""
port_name = str('{0}:{1}'.format(local_host, command_port))
_LOGGER.info('port_name: {}'.format(port_name))
if language != "mel" and language != "python":
raise ValueError("Expecting either 'mel' or 'python'")
# Save the text to a temp file.
# If mel, make sure it end with a semicolon
selected_text = get_wing_text()
if language == 'mel':
if not selected_text.endswith(';'):
selected_text += ';'
# This saves a temp file on Windows
# Mac\Linux support may need updating
temp_file_name = 'tmp_wing_data.txt'
temp_file_path = os.path.join(_PROJ_CACHE, temp_file_name)
temp_file_path = os.path.abspath(temp_file_path)
temp_file = temp_file_path.replace("\\", "/") # maya is linux paths?
_LOGGER.debug('temp_file_path is: {}'.format(temp_file_path))
if os.access(temp_file, os.F_OK):
# open and print the file in Maya:
f=open(temp_file_path, "w")
f.write(selected_text)
f.close()
else:
_LOGGER.info("No temp file exists: {}".format(temp_file))
file=open(temp_file, "w")
if os.path.isfile(temp_file):
_LOGGER.info('Created the file, please try again')
else:
_LOGGER.info('File not created')
# Create the socket that will connect to Maya, Opening a socket can vary from
mSocket = create_client_socket(local_host, command_port)
if mSocket:
# Now ping Maya over the command-port
message = ("import azpy.maya.utils.execute_wing_code;"
"azpy.maya.utils.execute_wing_code.main('{}')".format(language))
if language == 'mel':
message = 'python({})'.format(message) # wrap in in mel python cmd
try:
# Send our code to Maya:
mSocket.send(message.encode('ascii'))
time.sleep(1)
client_response = str(mSocket.recv(4096)).encode("utf-8") # receive the result info
# time.sleep(0.25)
# next command
except Exception as e:
_LOGGER.error("Sending command to Maya failed: {}".format(e))
_LOGGER.info("salt:{0}:: sent: {1}".format(str(random.randint(1, 9999)), message))
_LOGGER.info("The result is: {}".format(client_response))
mSocket.close()
else:
_LOGGER.error('No client socket, mSocket is: {}'.format(mSocket))
return
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def send_command_to_maya(language='python',
local_host=_LOCAL_HOST,
command_port=6000):
"""Basic method to connect to Maya and send a single smaller command directly"""
port_name = str('{0}:{1}'.format(local_host, command_port))
_LOGGER.info('port_name: {}'.format(port_name))
if language != "mel" and language != "python":
raise ValueError("Expecting either 'mel' or 'python'")
# Save the text to a temp file.
# If mel, make sure it end with a semicolon
selected_text = get_wing_text()
if language == 'mel':
if not selected_text.endswith(';'):
selected_text += ';'
# Create the socket that will connect to Maya, Opening a socket can vary from
mSocket = create_client_socket(local_host, command_port)
_LOGGER.info('mSocket is: {}'.format(mSocket))
if mSocket:
# Now ping Maya over the command-port
message = str(selected_text)
if language == 'mel':
message = 'python({})'.format(message) # wrap in in mel python cmd
# Now ping Maya over the command-port
try:
if language == 'mel':
message = 'python({})'.format(message)
# to do: the buffer default I think is 4096
# long selections are going to fail (not sure how)
mSocket.send(message.encode('ascii'))
time.sleep(1)
client_response = str(mSocket.recv(4096)).encode("utf-8") # receive the result info
# time.sleep(0.25)
# next command
except Exception as e:
_LOGGER.error("Sending command to Maya failed: {}".format(e))
_LOGGER.info("salt:{0}:: sent: {1}".format(str(random.randint(1, 9999)), message))
_LOGGER.info("The result is: {}".format(client_response))
mSocket.close()
else:
_LOGGER.error('No client socket, mSocket is: {}'.format(mSocket))
return
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def send_py_cmd_to_maya():
"""Send the selected Python command to Maya"""
send_command_to_maya() # default language is 'python'
send_py_cmd_to_maya.contexts = [
wingapi.kContextNewMenu("DCCsi Scripts"),
wingapi.kContextEditor(),
]
def send_mel_cmd_to_maya():
"""Send the selected code to Maya as mel"""
send_command_to_maya('mel')
send_mel_cmd_to_maya.contexts = [
wingapi.kContextNewMenu("DCCsi Scripts"),
wingapi.kContextEditor(),
]
def python_selection_to_maya():
"""Send the selected Python code to Maya"""
send_selection_to_maya() # default language is 'python'
python_selection_to_maya.contexts = [
wingapi.kContextNewMenu("DCCsi Scripts"),
wingapi.kContextEditor(),
]
def mel_selection_to_maya():
"""Send the selected code to Maya as mel"""
send_selection_to_maya('mel')
mel_selection_to_maya.contexts = [
wingapi.kContextNewMenu("DCCsi Scripts"),
wingapi.kContextEditor(),
]
# -------------------------------------------------------------------------
###########################################################################
# Main Code Block, runs this script as main (testing)
# -------------------------------------------------------------------------
if __name__ == '__main__':
# there are not really tests to run here due to this being a list of
# constants for shared use.
_G_DEBUG = True
_DCCSI_DEV_MODE = True
_LOGGER.setLevel(_logging.DEBUG) # force debugging
foo = get_wing_text()
# send each line to maya with > send_py_cmd_to_maya
print('Hello World: Command received from WingIDE')
#import maya.cmds as cmds
#foo = cmds.polySphere()
@@ -0,0 +1,66 @@
This particular sub-package is devoted to WingIDE
It is mainly wingide specific extensions
Some notes ...
There are a couple of projects set up for Lumberyard python development with WingIDE
The FIRST is the DCCsi:
dev\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\Solutions\.wing\DCCsi_7x.wpr
This provides devs a project to directly work on the DCCsi itself
You can launch wing and directly load this project via the following .bat file:
dev\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\Launchers\Windows\Launch_WingIDE-7-1.bat
Note: the data-driven env hooks for the DCCsi are in the Env.bat:
dev\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\Launchers\Windows\Env.bat
^ when you launch wing via the .bat file it bootstraps that env first
Additionally, that same env is being transitioned to a python implementation using dynaconf (WIP):
dev\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\.env
dev\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\settings.json
dev\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\config.py
^ this last file is the root dynaconf config for the DCCsi
This will also allow us to have per-tool, per-project, per-dcc app env extensions and settings in a more nested way
The SECOND is the AtomTechArt Lumberyard project: dev\AtomTechArt
dev\AtomTechArt\DCCsi\envs\AtomTechArt\AtomTechArt.wpr
Note: this is set up as a venv based on the lumberyard python intstall
and thus provides a sandbox silo to develop outside of the lumberyard python, and outside of the DCCsi
This .bat will launch wing and directly load this project:
dev\AtomTechArt\DCCsi\Launch_AtomTechArt_WingIDE-7-1.bat
WingIDE auto-complete with Lumberyard:
Lumberyard when built will generate .pyi files for source analysis, inspection and auto-complete
On a per-project basis they are generated in the cache like:
dev\Cache\AtomTechArt\pc\user\python_symbols\azlmbr
Note: to get this to work unfortunatley each user must configure the wingide prefs to include this path
(there is no shared project / data-driven way that I know of to set this up otherwise)
Note: lumberyard is not currently generating __init__.pyi files in that package structure:
https://jira.agscollab.com/browse/SPEC-3315
The workaround is to create them yourself (they can be empty) and needs to be in the root of each package folder, like this:
dev\Cache\AtomTechArt\pc\user\python_symbols\azlmbr\__init__.pyi
dev\Cache\AtomTechArt\pc\user\python_symbols\azlmbr\materialeditor\__init__.pyi
ETC...
Then to enable do the following in WingIDE
1. (Dialog) WingIDE > Edit > Peferences
2. (Section) Category > Source Analysis > Advanced
3. (Add Path) In the area labeled "Interface File Path", insert a new path and point it to your cache:
Example (mine): g:\depot\JG_PC1_spectrAtom\dev\Cache\AtomTechArt\pc\user\python_symbols
You might need to reboot wing. Then you should have auto-complete for the lumberyard api
> import azlmbr
Note: the entirety of azlmbr api does not generate .pyi files currently,
all of the "Behaviour Context" based classes do
non-BC modules such as azlmbr.paths currently do not
https://jira.agscollab.com/browse/SPEC-3316
@@ -0,0 +1,83 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
"""This is a module to test script extensions for WingIDE
reference: https://wingware.com/doc/scripting/example
note: there are important instructions in that doc for
setting up your project files with wingapi auto-complete, etc.
We added C:\Program Files (x86)\Wing Pro 7.1\src to the
PYTHONPATH via env.bat and dynaconf config instead so it is
part of the inhereted environment."""
import sys
import logging as _logging
import wingapi
# -------------------------------------------------------------------------
_MODULENAME = 'azpy.dev.ide.wing.test'
_LOGGER = _logging.getLogger(_MODULENAME)
_handler = _logging.StreamHandler(sys.stdout)
_handler.setLevel(_logging.DEBUG)
FRMT_LOG_LONG = "[%(name)s][%(levelname)s] >> %(message)s (%(asctime)s; %(filename)s:%(lineno)d)"
_formatter = _logging.Formatter(FRMT_LOG_LONG)
_handler.setFormatter(_formatter)
_LOGGER.addHandler(_handler)
_LOGGER.debug('Loading: {0}.'.format({_MODULENAME}))
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def dccsi_test_script(test_str):
"""Simple test command for WingIDE
to run in wing: Edit > Command by Name
^ opens a commanline at bottom of IDE
type: test-script (then return)
^ commandline now takes entering a Test Str
Test Str: Booyah
^ a Pop-up dialog will display in wing
"""
app = wingapi.gApplication
v = "Product info is: " + str(app.GetProductInfo())
v += "\nAnd you typed: %s" % test_str
wingapi.gApplication.ShowMessageDialog("Test Message", v)
#dccsi_test_script.contexts = [wingapi.kContextNewMenu("Scripts")]
# this will add to a menu in WingIDE
dccsi_test_script.contexts = [
wingapi.kContextNewMenu("DCCsi Scripts"),
wingapi.kContextEditor(),
]
# -------------------------------------------------------------------------
# bind a hotkey inside Wing that will execute our newly installed Module
# Inside Wing, choose Edit -> Preferences, and on the left, under User Interface, choose Keyboard
# In the center right of the Keyboard section is where you can add "Ccustom Key Bindings" combinations to execute code
# For this test example, I have bound Ctrl+Alt+Shift+T as my key combination for executing dccsi_test_script
###########################################################################
# Main Code Block, runs this script as main (testing)
# -------------------------------------------------------------------------
if __name__ == '__main__':
# there are not really tests to run here due to this being a list of
# constants for shared use.
_G_DEBUG = True
_DCCSI_DEV_MODE = True
_LOGGER.setLevel(_logging.DEBUG) # force debugging
foo = dccsi_test_script("This is a test")
@@ -0,0 +1,16 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
# define api package for each IDE supported
__all__ = ['check']
@@ -0,0 +1,18 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
# define api package for each IDE supported
__all__ = ['running_state', 'maya_app']
# maya_app, named such to avoid namespace collisions with maya dcc app api
@@ -0,0 +1,157 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
# -- Standard Python modules --
import sys
import os
import inspect
import logging as _logging
# -- External Python modules --
# none
# -- Extension Modules --
import azpy
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
# --------------------------------------------------------------------------
# -- Global Definitions --
_DCCSI_DCC_APP = None
# set up global space, logging etc.
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
_MODULENAME = 'azpy.dev.utils.check.maya_app'
_LOGGER = _logging.getLogger(_MODULENAME)
# -------------------------------------------------------------------------
###########################################################################
## These mini-functions need to be defined, before they are called
# -------------------------------------------------------------------------
# run this, if we are in Maya
def set_dcc_app(dcc_app='maya'):
"""
azpy.dev.utils.check.maya.set_dcc_app()
this will set global _DCCSI_DCC_APP = 'maya'
and os.environ["DCCSI_DCC_APP"] = 'maya'
"""
_DCCSI_DCC_APP = dcc_app
_LOGGER.info('Setting DCCSI_DCC_APP to: {0}'.format(dcc_app))
return _DCCSI_DCC_APP
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def clear_dcc_app(dcc_app=False):
"""
azpy.dev.utils.check.maya.set_dcc_app()
this will set global _DCCSI_DCC_APP = False
and os.environ["DCCSI_DCC_APP"] = False
"""
_DCCSI_DCC_APP = dcc_app
_LOGGER.info('Setting DCCSI_DCC_APP to: {0}'.format(dcc_app))
return _DCCSI_DCC_APP
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def validate_state(DCCSI_DCC_APP=_DCCSI_DCC_APP):
'''
This will detect if we are running in Maya or not,
then will call either, set_dcc_app('maya') or clear_dcc_app(dcc_app=False)
'''
if _G_DEBUG:
_LOGGER.debug(autolog())
try:
import maya.cmds as cmds
DCCSI_DCC_APP = set_dcc_app('maya')
except ImportError as e:
_LOGGER.warning('Can not perform: import maya.cmds as cmds')
DCCSI_DCC_APP = clear_dcc_app()
else:
try:
if cmds.about(batch=True):
DCCSI_DCC_APP = set_dcc_app('maya')
except AttributeError as e:
_LOGGER.warning("maya.cmds module isn't fully loaded/populated, "
"(cmds populates only in batch, maya.standalone, or maya GUI)")
# NO Maya
DCCSI_DCC_APP=clear_dcc_app()
return DCCSI_DCC_APP
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def autolog():
'''Automatically log the current function details.'''
# Get the previous frame in the stack, otherwise it would
# be this function!!!
func = inspect.currentframe().f_back.f_back.f_code
# Dump the message + the name of this function to the log.
output = ('{module} AUTOLOG:\r'
'Called from::\n{0}():\r'
'In file: {1},\r'
'At line: {2}\n'
''.format(func.co_name,
func.co_filename,
func.co_firstlineno,
module=_MODULENAME))
return output
#-------------------------------------------------------------------------
# -------------------------------------------------------------------------
# run the check on import
_DCCSI_DCC_APP = validate_state()
# -------------------------------------------------------------------------
###########################################################################
# Main Code Block, runs this script as main (testing)
# -------------------------------------------------------------------------
if __name__ == '__main__':
# there are not really tests to run here due to this being a list of
# constants for shared use.
_G_DEBUG = True
_DCCSI_DEV_MODE = True
_LOGGER.setLevel(_logging.DEBUG) # force debugging
## reduce cyclical azpy imports
## it only has a basic logger configured, add log to console
#_handler = _logging.StreamHandler(sys.stdout)
#_handler.setLevel(_logging.DEBUG)
#FRMT_LOG_LONG = "[%(name)s][%(levelname)s] >> %(message)s (%(asctime)s; %(filename)s:%(lineno)d)"
#_formatter = _logging.Formatter(FRMT_LOG_LONG)
#_handler.setFormatter(_formatter)
#_LOGGER.addHandler(_handler)
#_LOGGER.debug('Loading: {0}.'.format({_MODULENAME}))
# happy print
from azpy.constants import STR_CROSSBAR
_LOGGER.info(STR_CROSSBAR)
_LOGGER.info('{} ... Running script as __main__'.format(_MODULENAME))
_LOGGER.info(STR_CROSSBAR)
_DCCSI_DCC_APP = validate_state()
_LOGGER.info('Is Maya Running? _DCCSI_DCC_APP = {}'.format(_DCCSI_DCC_APP))
@@ -0,0 +1,257 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
# -- Standard Python modules --
import sys
import os
import logging as _logging
# -- External Python modules --
# none
# -- Extension Modules --
# none (yet)
# --------------------------------------------------------------------------
# -- Global Definitions --
_DCCSI_DCC_APP = None
_MODULENAME = 'azpy.dev.utils.check.running_state'
_LOGGER = _logging.getLogger(_MODULENAME)
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# First Class
class CheckRunningState(object):
"""
< To Do: document Class >
"""
# Class Variables
DCCSI_DCC_APP = None
def __init__(self, *args, **kwargs):
'''
CheckRunningState Class Initialization
< To Do: Need to document >
Input Attributes:
-----------------
self. -> SCALAR: Description.
Default =
Keyword Arguments:
------------------
self. -> STRING: Description.
Default =
self. -> OBJECT: Description.
Default =
Additional Attributes:
----------------------
self. -> BOOLEAN: Description.
Default =
Documentation last updated: Month. Day, Year - Author
'''
# -- Default Values --
# top level storage for whether or not we are running
# in a DCC tool interpreter
self._dcc_py = False
# basic python info
# if these can't run, we are in a bad state anyway
self._python = sys.version
self._py_version_info = sys.version_info
# -- Input Checks --
self.check_known()
# ---------------------------------------------------------------------
# --method-------------------------------------------------------------
def check_known(self):
# -- init --
# first let's check if any of these DCC apps are running
# 0 - maya first
CheckRunningState.DCCSI_DCC_APP = self.maya_running()
# 1 - then max
if not CheckRunningState.DCCSI_DCC_APP:
CheckRunningState.DCCSI_DCC_APP = self.max_running()
else:
_LOGGER.warning('DCCSI_DCC_APP is already set: {}'.format(CheckRunningState.DCCSI_DCC_APP))
# 2 - then blender
if not CheckRunningState.DCCSI_DCC_APP:
CheckRunningState.DCCSI_DCC_APP = self.blender_running()
else:
_LOGGER.warning('DCCSI_DCC_APP is already set: {}'.format(CheckRunningState.DCCSI_DCC_APP))
# store checks for DCC info
if CheckRunningState.DCCSI_DCC_APP:
self.dcc_py = True
# store check for is maya running headless
if CheckRunningState.DCCSI_DCC_APP == 'maya':
self.maya_headless = self.is_maya_headless()
# set a envar other modules can easily check
if CheckRunningState.DCCSI_DCC_APP:
os.environ['DCCSI_DCC_APP'] = CheckRunningState.DCCSI_DCC_APP
# ---------------------------------------------------------------------
#--properties----------------------------------------------------------
@property
def python(self):
return self._python
@python.setter
def python(self, value):
self._python = value
return self._python
@property
def py_version_info(self):
return self._py_version_info
@py_version_info.setter
def py_version_info(self, value):
self._py_version_info = value
return self._py_version_info
@property
def dcc_app(self):
return self._dcc_py
@dcc_app.setter
def dcc_app(self, value):
self._dcc_py = value
return self._dcc_py
@property
def maya_headless(self):
return self._maya_headless
@maya_headless.setter
def maya_headless(self, value):
self._maya_headless = value
return self._maya_headless
# template property
# @property
# def foo(self):
# return self._foo
# @foo.setter
# def foo(self, value):
#self._foo = value
# return self._foo
# --properties----------------------------------------------------------
# --method-------------------------------------------------------------
def maya_running(self):
"""< To Do: Need to document >"""
try:
import azpy.dev.utils.check.maya_app as check_dcc
DCCSI_DCC_APP = check_dcc.validate_state()
except ImportError as e:
_LOGGER.info('Not Implemented: azpy.dev.utils.check.maya_app')
if DCCSI_DCC_APP:
CheckRunningState.DCCSI_DCC_APP = check_dcc.validate_state()
os.environ["DCCSI_DCC_APP"] = str(DCCSI_DCC_APP)
return CheckRunningState.DCCSI_DCC_APP
#----------------------------------------------------------------------
# --method-------------------------------------------------------------
def is_maya_headless(self):
"""< To Do: Need to document >"""
if self.maya_app:
import maya.cmds as mc
try:
if mc.about(batch=True):
return True
else:
return False
except Exception as e:
# cmds module isn't fully loaded/populated
# (which only happens in batch, maya.standalone, or maya GUI)
# no maya
return False
else:
return False
# --method-------------------------------------------------------------
# --method-------------------------------------------------------------
def max_running(self):
"""
< To Do: implement >
"""
try:
import azpy.dev.utils.check.max_app as check_dcc
CheckRunningState.DCCSI_DCC_APP = check_dcc.validate_state()
except ImportError as e:
_LOGGER.info('Not Implemented: azpy.dev.utils.check.max')
if CheckRunningState.DCCSI_DCC_APP:
CheckRunningState.DCCSI_DCC_APP = check_dcc.validate_state()
os.environ["DCCSI_DCC_APP"] = str(CheckRunningState.DCCSI_DCC_APP)
return CheckRunningState.DCCSI_DCC_APP
#----------------------------------------------------------------------
# --method-------------------------------------------------------------
def blender_running(self):
"""
< To Do: implement >
"""
try:
import azpy.dev.utils.check.blender_app as check_dcc
CheckRunningState.DCCSI_DCC_APP = check_dcc.validate_state()
except ImportError as e:
_LOGGER.info('Not Implemented: azpy.dev.utils.check.blender')
if CheckRunningState.DCCSI_DCC_APP:
CheckRunningState.DCCSI_DCC_APP = check_dcc.validate_state()
os.environ["DCCSI_DCC_APP"] = str(CheckRunningState.DCCSI_DCC_APP)
return CheckRunningState.DCCSI_DCC_APP
#----------------------------------------------------------------------
#==========================================================================
# Class Test
#==========================================================================
if __name__ == '__main__':
_G_DEBUG = True
_DCCSI_DEV_MODE = True
_LOGGER.setLevel(_logging.DEBUG) # force debugging
# -- Extend Logger
#_handler = _logging.StreamHandler(sys.stdout)
# _handler.setLevel(_logging.DEBUG)
#FRMT_LOG_LONG = "[%(name)s][%(levelname)s] >> %(message)s (%(asctime)s; %(filename)s:%(lineno)d)"
#_formatter = _logging.Formatter(FRMT_LOG_LONG)
# _handler.setFormatter(_formatter)
# _LOGGER.addHandler(_handler)
#_LOGGER.debug('Loading: {0}.'.format({_MODULENAME}))
# happy print
from azpy.constants import STR_CROSSBAR
_LOGGER.info(STR_CROSSBAR)
_LOGGER.info('{} ... Running script as __main__'.format(_MODULENAME))
_LOGGER.info(STR_CROSSBAR)
foo = CheckRunningState()
_LOGGER.info('DCCSI_DCC_APP: {}'.format(foo.DCCSI_DCC_APP))
@@ -12,8 +12,6 @@
#
# -- This line is 75 characters -------------------------------------------
from __future__ import unicode_literals
# -------------------------------------------------------------------------
'''
Module: <DCCsi>\azpy\shared\common\base_env.py
@@ -27,21 +25,15 @@ Allowing those str('tag') to easily be changed in a single location.
If they are paths for code acess we assume they were put on the sys path.
'''
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
__author__ = 'HogJonny'
__project__ = 'DccScriptingInterface'
# -------------------------------------------------------------------------
# built in's
import os
import sys
import json
import logging as _logging
from collections import OrderedDict
# 3rd Party
from box import Box
from pathlib import Path
# Lumberyard extensions
@@ -51,27 +43,29 @@ from azpy.shared.common.core_utils import get_stub_check_path
from azpy.shared.common.envar_utils import get_envar_default
from azpy.shared.common.envar_utils import set_envar_defaults
from azpy.shared.common.envar_utils import Validate_Envar
# -------------------------------------------------------------------------
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
_PACKAGENAME = 'azpy.env_base'
_logging.basicConfig(level=_logging.INFO,
format=FRMT_LOG_LONG,
datefmt='%m-%d %H:%M')
_LOGGER = _logging.getLogger(_PACKAGENAME)
_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME}))
# global space
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
_PACKAGENAME = __name__
if _PACKAGENAME is '__main__':
_PACKAGENAME = 'azpy.env_base'
_LOGGER = _logging.getLogger(_PACKAGENAME)
_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME}))
# -------------------------------------------------------------------------
# set up base totally non-functional defauls (denoted with $<ENVAR>)
# if something hasn't been set, it will stay '$<envar>'
_BASE_ENVVAR_DICT = Box(ordered_box=True)
_BASE_ENVVAR_DICT = OrderedDict()
# project tag
_BASE_ENVVAR_DICT[ENVAR_LY_PROJECT] = '${0}'.format(ENVAR_LY_PROJECT)
@@ -18,7 +18,7 @@ All Lumberyard render related packages/modules should live here."""
import os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -17,7 +17,7 @@
import os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -15,14 +15,10 @@
# importing all of the modules
"""azpy.maya.__init__"""
import os
from azpy import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_DEV_MODE
# global space
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
_PACKAGENAME = __name__
@@ -17,7 +17,7 @@
import os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -9,8 +9,6 @@
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
@@ -26,7 +24,7 @@ Module Documentation:
.. module:: event_callback_handler
:synopsis: Simple event based callback_event handler
using maya.api.OpenMaya (api2)
.. :note: nothing mundane to declare
.. :attention: callbacks should be uninstalled on exit
.. :warning: maya may crash on exit if callbacks are not uninstalled
@@ -55,7 +53,7 @@ Module Documentation:
SceneOpened
PostSceneRead
workspaceChanged
.. moduleauthor:: Amazon Lumberyard
"""
@@ -96,10 +94,10 @@ _LOGGER.debug('Invoking:: {0}.'.format({_PACKAGENAME}))
class EventCallbackHandler(object):
"""
A simple Maya event based callback_event handler class
:ivar callback_event: stores event type trigger for a maya callback_event
:vartype event: for example, 'NameChanged'
:ivar this_function: stores this_function to call when callback_event is triggered
:vartype this_function: for example,
cb = EventCallbackHandler(callback_event='NameChanged',
@@ -116,12 +114,12 @@ class EventCallbackHandler(object):
self._callback_id = None
# state tracker
self._message_id_set = None
# the callback_event event trigger
self._callback_event = callback_event
# the thing to do on callback_event
self._function = this_function
if install:
self.install()
@@ -129,7 +127,7 @@ class EventCallbackHandler(object):
@property
def callback_id(self):
return self._callback_id
@property
def callback_event(self):
return self._callback_event
@@ -137,15 +135,15 @@ class EventCallbackHandler(object):
@property
def this_function(self):
return self._this_function
# --method-------------------------------------------------------------
def install(self):
"""
installs this callback_event for event, which makes it active
"""
add_event_method = openmaya.MEventMessage.addEventCallback
# when called, check if it's already installed
if self._callback_id:
_LOGGER.warning("EventCallback::{0}:{1}, is already installed"
@@ -177,7 +175,7 @@ class EventCallbackHandler(object):
"""
remove_event_callback = openmaya.MEventMessage.removeCallback
if self._callback_id:
try:
remove_event_callback(self._callback_id)
@@ -197,7 +195,7 @@ class EventCallbackHandler(object):
"".format(self._callback_event,
self._function.__name__))
return False
# --method-------------------------------------------------------------
def __del__(self):
"""
@@ -21,47 +21,47 @@
"""
.. module:: node_message_callback_handler
:synopsis: this module contains code related to mNodeName message based callbacks in Maya
.. moduleauthor:: Amazon Lumberyard
.. :note: nothing mundane to declare
.. :attention: callbacks should be uninstalled on exit
.. :warning: maya may crash on exit if callbacks are not uninstalled
.. Usage:
< To Do >
< To Do >
.. Version:
0.1.0 | prototype
.. History:
< To Do >
.. Reference:
MNodeMessage
This class is used to register callbacks for dependency mNodeName messages of specific dependency nodes.
http://download.autodesk.com/us/maya/2011help/API/class_m_node_message.html
There are 4 add thisCallback methods which will add callbacks for the following types of messages:
Attribute Changed
Attribute Added or Removed
Node Dirty
Name Changed
- Attribute Changed
- Attribute Added or Removed
- Node Dirty
- Name Changed
If we import OpenMaya,
import maya.api.OpenMaya as om
from maya.api.OpenMaya import MNodeMessage as mNM
The valid callbacks for usage are:
mNM.addAttributeChangedCallback
mNM.addAttributeAddedOrRemovedCallback
mNM.addNodeDirtyCallback
mNM.addNodeDirtyPlugCallback
mNM.addNameChangedCallback
mNM.addNodeAboutToDeleteCallback
mNM.addNodePreRemovalCallback
mNM.addNodeDestroyedCallback
mNM.addKeyableChangeOverride
mNM.addAttributeChangedCallback
mNM.addAttributeAddedOrRemovedCallback
mNM.addNodeDirtyCallback
mNM.addNodeDirtyPlugCallback
mNM.addNameChangedCallback
mNM.addNodeAboutToDeleteCallback
mNM.addNodePreRemovalCallback
mNM.addNodeDestroyedCallback
mNM.addKeyableChangeOverride
"""
# --------------------------------------------------------------------------
@@ -117,12 +117,12 @@ class NodeMessageCallbackHandler(object):
self._callback_type_id = None
# state tracker
self._message_id_set = None
# the this_callback event trigger
self._this_callback = this_callback
# the thing to do on this_callback
self._function = this_function
# this handlers object mNodeName
# passing in a null MObject (ie, without a name as an argument)
# registers the this_callback to get all name changes in the scene
@@ -132,18 +132,18 @@ class NodeMessageCallbackHandler(object):
self._m_object = om.MObject()
else:
self._m_object = om.MObject(mNodeName)
# install / activate this callback
if install:
self.install()
#----------------------------------------------------------------------
#--properties----------------------------------------------------------
@property
def callback_id(self):
return self._callback_type_id
@property
def this_callback(self):
return self._this_callback
@@ -152,8 +152,8 @@ class NodeMessageCallbackHandler(object):
def this_function(self):
return self._this_function
#--properties----------------------------------------------------------
# --method-------------------------------------------------------------
def install(self):
"""
@@ -210,8 +210,8 @@ class NodeMessageCallbackHandler(object):
self._function.__name__))
return False
#----------------------------------------------------------------------
# --method-------------------------------------------------------------
def __del__(self):
"""
@@ -234,7 +234,7 @@ def testNameChanged(*args):
mNode = None
_LOGGER.debug('\t~ no node')
_LOGGER.debug('\t~ warning: {0}'.format(e))
# get old name
try:
oldName = args[1]
@@ -242,7 +242,7 @@ def testNameChanged(*args):
oldName = None
_LOGGER.debug('\t~ no oldName')
_LOGGER.debug('\t~ warning: {0}'.format(e))
# convert the MObject to a dep mNode
try:
depNode = om.MFnDependencyNode(mNode)
@@ -250,9 +250,9 @@ def testNameChanged(*args):
depNode = None
_LOGGER.debug('\t~ no depNode')
_LOGGER.debug('\t~ warning: {0}'.format(e))
if oldName == (u""): oldName = 'null'
# get node type
try:
nodeType = depNode.typeName()
@@ -260,7 +260,7 @@ def testNameChanged(*args):
nodeType = None
_LOGGER.debug('\t~ no nodeType')
_LOGGER.debug('\t~ warning: {0}'.format(e))
# get node name
try:
nodeName = depNode.name()
@@ -273,7 +273,7 @@ def testNameChanged(*args):
_LOGGER.debug('newName: {0}'.format(nodeName))
_LOGGER.debug('oldName: {0}'.format(oldName))
_LOGGER.debug('nodeType: {0}'.format(nodeType))
return depNode
# -------------------------------------------------------------------------
@@ -282,8 +282,8 @@ def testNameChanged(*args):
# Run as LICENSE
#==========================================================================
if __name__ == '__main__':
name_changed_callback = om.MNodeMessage.addNameChangedCallback
ncbh = NodeMessageCallbackHandler(name_changed_callback,
testNameChanged)
@@ -17,7 +17,7 @@
import os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -17,7 +17,7 @@
mport os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -0,0 +1,16 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
# define api package for each IDE supported
__all__ = ['simple_command_port', 'execute_wing_code', 'wing_to_maya']
@@ -0,0 +1,119 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
import os
import socket
import logging as _logging
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def get_stub_check_path(in_path=__file__, check_stub='engineroot.txt'):
'''
Returns the branch root directory of the dev\\'engineroot.txt'
(... or you can pass it another known stub)
so we can safely build relative filepaths within that branch.
If the stub is not found, it returns None
'''
path = os.path.abspath(os.path.join(os.path.dirname(in_path), ".."))
_LOGGER.info('parent dir: {}'.format(path))
while 1:
test_path = os.path.join(path, check_stub)
if os.path.isfile(test_path):
return os.path.abspath(os.path.join(os.path.dirname(test_path)))
else:
path, tail = (os.path.abspath(os.path.join(os.path.dirname(test_path), "..")),
os.path.basename(test_path))
if (len(tail) == 0):
return None
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# -- Global Definitions --
_MODULENAME = 'azpy.maya.utils.execute_wing_code'
_LOGGER = _logging.getLogger(_MODULENAME)
_LY_DEV = get_stub_check_path()
_LOGGER.info('_LY_DEV: {}'.format(_LY_DEV))
_PROJ_CACHE = os.path.join(_LY_DEV, 'cache', 'DCCsi', 'wing')
_LOGGER.info('_PROJ_CACHE: {}'.format(_PROJ_CACHE))
_LOCAL_HOST = socket.gethostbyname(socket.gethostname())
_LOGGER.info('local_host: {}'.format(_LOCAL_HOST))
# -------------------------------------------------------------------------
###########################################################################
# --main code block--------------------------------------------------------
def main(code_type='python'):
"""
Evaluate the temp file on disk, made by Wing, in Maya.
code_type : string : Supports either 'python' or 'mel'
"""
temp_file_name = 'tmp_wing_data.txt'
temp_file_path = os.path.join(_PROJ_CACHE, temp_file_name)
temp_file_path = os.path.abspath(temp_file_path)
temp_file = temp_file_path.replace("\\", "/") # maya is linux paths?
_LOGGER.debug('temp_file_path is: {}'.format(temp_file_path))
if os.access(temp_file, os.F_OK):
# open and print the file in Maya:
f = open(temp_file, "r")
lines = f.readlines()
for line in lines:
print(line.rstrip())
f.close()
if code_type == "python":
# execute the file contents in Maya:
f = open(temp_file, "r")
# (1) doesn't work?
#exec(f, __main__.__dict__, __main__.__dict__)
# (2) works is series of single expressions
#for line in lines:
#exec(line.rstrip())
# f.close()
# (3) this seems to work much better
temp_code_file_name = 'temp_code.py'
temp_code_file = os.path.join(_PROJ_CACHE, temp_code_file_name)
temp_code_file = os.path.abspath(temp_code_file)
temp_code = temp_file_path.replace("\\", "/") # maya is linux paths?
code = compile(f.read(), temp_code, 'exec')
_LOGGER.debug(type(code))
exec(code)
elif code_type == "mel":
mel_cmd = "source '{}'".format(temp_file)
# This causes the "// Result: " line to show up in the Script Editor:
om.MGlobal.executeCommand(mel_cmd, True, True)
else:
_LOGGER.warning("No temp file exists: {}".format(temp_file))
file=open(temp_file, "w")
file.write("test file write")
if os.path.isfile(temp_file):
_LOGGER.info('Created the temp file, please try again!')
else:
_LOGGER.error('File not created: {}'.format(temp_file))
return
# -------------------------------------------------------------------------
@@ -0,0 +1,242 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
# -- Standard Python modules --
import sys
import os
import socket
import site
import time
import logging as _logging
# -- External Python modules --
# none
# -- Extension Modules --
# none (yet)
# --------------------------------------------------------------------------
# -- Global Definitions --
_MODULENAME = 'azpy.maya.utils.simple_command_port'
_LOGGER = _logging.getLogger(_MODULENAME)
_LOCAL_HOST = socket.gethostbyname(socket.gethostname())
_LOGGER.info('local_host: {}'.format(_LOCAL_HOST))
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
class SimpleCommandPort:
"""
Simple maya command port Object Class.
"""
#----------------------------------------------------------------------
#def __new__(self):
#try:
#self.port_name
#return self
#except NameError as e:
#self.port_name = None
#self.logger.error("Specify a port like: '127.0.0.1:6000'")
#return
#return
# -- Constructor ------------------------------------------------------
def __init__(self,
local_host=_LOCAL_HOST,
comman_port=6000,
logger=_LOGGER,
source_type='python',
echo_output=True,
noreturn=False,
*args, **kwargs):
'''
SimpleCommandPort Class Initialization
< To Do: Need to document >
Input Attributes:
-----------------
self. SCALAR: Description.
Default =
Keyword Arguments:
------------------
self. STRING: Description.
Default =
self. OBJECT: Description.
Default =
Additional Attributes:
----------------------
self. BOOLEAN: Description.
Default =
Documentation last updated: Month. Day, Year - Author
'''
# -- Default Values --
self._port_name = '0.0.0.0:0000'
self._port = None
self._logger = None
self._echo_output = echo_output
self._noreturn = noreturn
self._source_type = source_type
if logger != None:
self._logger = logger
# -- Input Checks --
if local_host != None and local_host != '0.0.0.0':
self._port_name = str('{0}:{1}'.format(local_host, comman_port))
else:
self.logger.error("Specify a port: SimpleCommandPort('127.0.0.1','6000')")
## -- init --
#try:
#self.port = self.cmdPortOpen()
#except Exception as e:
#self.port = None
#if self.logger:
#self.logger.autolog(e, level='error')
#else:
#self.logIt(e, level='error')
#----------------------------------------------------------------------
#--properties----------------------------------------------------------
@property
def port_name(self):
return self._port_name
@port_name.setter
def port_name(self, value):
self._port_name = value
return self._port_name
@port_name.getter
def port_name(self):
return self._port_name
@property
def port(self):
return self._port
@port.setter
def port(self, value):
self._port = value
return self._port
@property
def logger(self):
return self._logger
@logger.setter
def logger(self, value):
self._logger = value
return self._logger
#----------------------------------------------------------------------
# --method-------------------------------------------------------------
def open(self):
from azpy.dev.utils.check.maya_app import validate_state
if validate_state():
if self.port_name != None and self.port_name != '0.0.0.0:0000':
import maya.cmds as cmds
try:
self.logger.info('Opening the cmd port: {0}'
''.format(self.port_name))
except Exception as e:
self.logger.error('{0}'.format(e))
try:
# to do: get mel working
self.port = cmds.commandPort(name=self.port_name,
echoOutput=self._echo_output,
sourceType =self._source_type,
noreturn=self._noreturn)
self.logger.info('{0}:: Open!'.format(self.port_name))
time.sleep(0.25)
return True, self.port_name
except Exception as e:
self.logger.error('{0}'.format(e))
_LOGGER.info(cmds.commandPort(self.port_name, q=True))
self.port_name='ERROR'
return False, self.port_name
else:
self.logger.warning('Can not use a port: {}'.format(self.port_name))
else:
self.logger.warning('Did not perform port open, Not running Maya!')
return False
#----------------------------------------------------------------------
# --method-------------------------------------------------------------
def close(self):
self.logger.info('Closing the port: {0}'.format(self.port_name))
try:
import maya.cmds as cmds
cmds.commandPort(name=self.port_name, close=True, echoOutput=self._echo_output)
self.port = None
self.logger.info('{0}:: Port Closed!'.format(self.port_name))
except Exception as e:
self.logger.error('{0}'.format(e))
self.port_name = 'ERROR'
return False
time.sleep(1)
return True
#----------------------------------------------------------------------
# -------------------------------------------------------------------------
###########################################################################
# Main Code Block, runs this script as main (testing)
# -------------------------------------------------------------------------
if __name__ == '__main__':
_G_DEBUG = True
_DCCSI_DEV_MODE = True
_LOGGER.setLevel(_logging.DEBUG) # force debugging
# -- Extend Logger
#_handler = _logging.StreamHandler(sys.stdout)
# _handler.setLevel(_logging.DEBUG)
#FRMT_LOG_LONG = "[%(name)s][%(levelname)s] >> %(message)s (%(asctime)s; %(filename)s:%(lineno)d)"
#_formatter = _logging.Formatter(FRMT_LOG_LONG)
# _handler.setFormatter(_formatter)
# _LOGGER.addHandler(_handler)
#_LOGGER.debug('Loading: {0}.'.format({_MODULENAME}))
# happy print
from azpy.constants import STR_CROSSBAR
_LOGGER.info(STR_CROSSBAR)
_LOGGER.info('{} ... Running script as __main__'.format(_MODULENAME))
_LOGGER.info(STR_CROSSBAR)
# should throw an error message
foo_port = SimpleCommandPort()
# should attemp to open the port, should warn as defualt port is '0.0.0.0:0000'
foo_port.open()
# should return a port object
foo_port = SimpleCommandPort('127.0.0.1:6000')
# should attemp to open the port, which should warn because only works in Maya
foo_port.open()
_LOGGER.info('Port Name: {}'.format(foo_port.port_name))
@@ -0,0 +1,154 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
# -- Standard Python modules --
import sys
import os
import site
import socket
import time
import logging as _logging
# -- External Python modules --
# none
# -- Extension Modules --
from simple_command_port import SimpleCommandPort
# --------------------------------------------------------------------------
# -- Global Definitions --
_MODULENAME = 'azpy.maya.utils.wing_to_maya'
_LOGGER = _logging.getLogger(_MODULENAME)
_LOCAL_HOST = socket.gethostbyname(socket.gethostname())
_LOGGER.info('local_host: {}'.format(_LOCAL_HOST))
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def start_wing_to_maya(local_host=_LOCAL_HOST,
comman_port=6000,
logger=_LOGGER,
*args, **kwargs):
"""
imports the module
opens a port to run python code from wingIDE directly
"""
if logger != None:
_LOGGER = _LOGGER
try:
import maya.cmds as cmds
except ImportError as e:
_LOGGER.error('Could not perform: {}'.format(e))
raise e
try:
port
except NameError:
port = None
port_name = str('{0}:{1}'.format(local_host, comman_port))
# should only be getting the port passed in
_LOGGER.info('Attempting to open port:: {0}'.format(port_name))
port_object = SimpleCommandPort(port_name)
try:
port = port_object.open()
except Exception as e:
_LOGGER.error('Could not open port: {e}'.format(e))
raise e
if not port:
return port
else:
try:
import execute_wing_code
except Exception as e:
_LOGGER.error('Could not execute code: {}'.format(e))
return 'Error'
time.sleep(0.25)
_LOGGER.info('WingIDE: Python >> to >> Maya, is started.')
try:
test_port = cmds.commandPort(port_name, q=True)
except Exception as e:
_LOGGER.autolog(e, level='error')
return 'Error'
if test_port:
message = 'That port is already open. '
message += 'Attempting to close old port, so I can re-establish it...'
_LOGGER.info(message)
try:
cmds.commandPort(name=port_name, close=True, echoOutput=True)
except Exception as e:
_LOGGER.error(''.format(e))
time.sleep(0.25)
try:
test_port = cmds.commandPort(port_name, q=True)
except Exception as e:
_LOGGER.error('Could not query port: {}'.format(e))
if test_port == False:
_LOGGER.info('Port closed! Re-opening ...')
try:
port = SimpleCommandPort(port_name)
except Exception as e:
_LOGGER.error('Could not create port object: {}'.format(e))
time.sleep(0.25)
try:
test_port = cmds.commandPort(port_name, q=True)
_LOGGER.info('The posrt is: {}'.format(test_port))
except Exception as e:
_LOGGER.error('Could not query port: {}'.format(e))
elif test_port == False:
_LOGGER.info('The port does not exist ... attempting to open it again now')
try:
port = SimpleCommandPort(port_name)
except Exception as e:
_LOGGER.error('Could not create port: {}'.format(e))
time.sleep(0.25)
try:
test_port = cmds.commandPort(port_name, q=True)
_LOGGER.info('Port is: {}'.format(test_port))
except Exception as e:
_LOGGER.error('Could not create port: {}'.format(e))
return port
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def start_wing_to_maya_menu():
"""
Simple hook to call the function from a menu item,
using the default port name defined
"""
port = object() # init a dummy object
# default name ... name is first arg, or a kwarg
portName, kwargs = setSynthArgKwarg(port, argPosIndex=0, argTag='portName',
inArgs=args, inKwargs=kwargs,
defaultValue="127.0.0.1:6000")
port = start_wing_to_maya(local_host=_LOCAL_HOST, comman_port=6000)
return
# -------------------------------------------------------------------------
@@ -19,8 +19,7 @@ All Atom render related packages/modules should live here."""
import os
import azpy
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -21,19 +21,8 @@ import logging as _logging
# -------------------------------------------------------------------------
# global space debug flag
# not using azpy.constants here to help avoid cyclical imports lower
from azpy.env_bool import env_bool
# have to avoid importing these from constants
# because we can end up with cyclical import issues
# need to figure out a better solution later so we don't duplicate everywhere
ENVAR_DCCSI_GDEBUG = str('DCCSI_GDEBUG')
ENVAR_DCCSI_DEV_MODE = str('DCCSI_DEV_MODE')
# global space
_G_DEBUG = os.getenv(ENVAR_DCCSI_GDEBUG, False)
_DCCSI_DEV_MODE = os.getenv(ENVAR_DCCSI_DEV_MODE, False)
# global space debug flag, no fancy stuff here we use in bootstrap
_G_DEBUG = False # manually enable to debug this file
_PACKAGENAME = __name__
if _PACKAGENAME is '__main__':
@@ -59,8 +48,9 @@ def return_stub(stub):
if (len(tail) == 0):
path = ""
if _G_DEBUG:
print('~ Debug Message: I was not able to find the '
'path to that file (stub) in a walk-up from currnet path')
_LOGGER.debug('~ Debug Message: I was not able to find the '
'path to that file (stub) in a walk-up '
'from currnet path')
break
_dir_to_last_file = path
@@ -76,13 +66,13 @@ if __name__ == '__main__':
# constants for shared use.
# happy print
print("# {0} #".format('-' * 72))
print('~ find_stub.py ... Running script as __main__')
print("# {0} #\r".format('-' * 72))
_LOGGER.info("# {0} #".format('-' * 72))
_LOGGER.info('~ find_stub.py ... Running script as __main__')
_LOGGER.info("# {0} #\r".format('-' * 72))
print('~ Current Work dir: {0}'.format(os.getcwd()))
_LOGGER.info('~ Current Work dir: {0}'.format(os.getcwd()))
print('~ Dev\: {0}'.format(return_stub('engineroot.txt')))
_LOGGER.info('~ Dev\: {0}'.format(return_stub('engineroot.txt')))
# custom prompt
sys.ps1 = "[azpy]>>"
@@ -11,13 +11,12 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
# The __init__.py files help guide import statements without automatically
# importing all of the modules
"""azpy.shared.__init__"""
import os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -0,0 +1,17 @@
{
"ordered_box": true,
"COMPANY": "Amazon.Lumberyard",
"LY_PROJECT": "DccScriptingInterface",
"LY_DEV": "G:\\depot\\JG_PC1_spectrAtom\\dev",
"LY_BUILD_DIR_NAME": "windows_vs2019",
"LY_BUILD_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\windows_vs2019",
"QT_PLUGIN_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\bin\\profile\\EditorPlugins",
"LY_PROJECT_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\Gems\\AtomLyIntegration\\TechnicalArt\\DccScriptingInterface",
"DCCSIG_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\Gems\\AtomLyIntegration\\TechnicalArt\\DccScriptingInterface",
"DCCSI_AZPY_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\Gems\\AtomLyIntegration\\TechnicalArt\\DccScriptingInterface\\azpy",
"DCCSI_SDK_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\Gems\\AtomLyIntegration\\TechnicalArt\\DccScriptingInterface\\SDK",
"DCCSI_WING_VERSION_MAJOR": "7",
"DCCSI_WING_VERSION_MINOR": "1",
"WINGHOME": "C:\\Program Files (x86)\\Wing Pro 7.1",
"DCCSI_PY_DEFAULT": "G:\\depot\\JG_PC1_spectrAtom\\dev\\Tools\\Python\\3.7.5\\windows\\python.exe"
}
@@ -17,7 +17,7 @@
import os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -53,8 +53,8 @@ import site
import fnmatch
# 3rd Party
from unipath import Path
from progress.spinner import Spinner
from pathlib import Path
# from progress.spinner import Spinner # deprecate use (or refactor)
# Lumberyard extensions
from azpy.constants import *
@@ -64,7 +64,7 @@ from azpy import initialize_logger
# -------------------------------------------------------------------------
# global space debug flag
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -85,12 +85,9 @@ _LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME}))
# --------------------------------------------------------------------------
def gather_paths_of_type_from_dir(in_path=str('c:\\'),
extension=str('*.py'),
return_path_list=list(),
use_spinner=False):
return_path_list=list()):
'''Walks from in_path and returns list of directories that contain the
file type matching the extension'''
if use_spinner:
spinner = Spinner('Finding: {0}\r'.format(extension))
# recursive function for finding paths
dir_contents = os.listdir(in_path)
@@ -111,9 +108,6 @@ def gather_paths_of_type_from_dir(in_path=str('c:\\'),
if found:
return_path_list.append(dir_trim_following_slash(to_unix_path(os.path.abspath(in_path))))
if use_spinner:
spinner.next()
complete = True
return return_path_list
@@ -136,7 +130,7 @@ def dir_trim_following_slash(current_path_str):
def to_unix_path(current_path_str):
'''converts path string to use unix slashes'''
_LOGGER.debug('to_unix_path({0})'.format(current_path_str))
safe_path = current_path_str.replace('\\', '/')
safe_path = str(current_path_str).replace('\\', '/')
return safe_path
# --------------------------------------------------------------------------
@@ -158,7 +152,7 @@ def module_path():
# --------------------------------------------------------------------------
def get_stub_check_path(in_path, checkStub='engineroot.txt'):
def get_stub_check_path(in_path, check_stub='engineroot.txt'):
'''
Returns the branch root directory of the dev\'engineroot.txt'
(... or you can pass it another known stub)
@@ -167,15 +161,13 @@ def get_stub_check_path(in_path, checkStub='engineroot.txt'):
If the stub is not found, it returns None
'''
from unipath import Path
path = Path(in_path).absolute()
while 1:
testPath = Path(path, checkStub)
test_path = Path(path, check_stub)
if testPath.isfile():
return Path(testPath)
if test_path.is_file():
return Path(test_path)
else:
path, tail = (path.parent, path.name)
@@ -188,15 +180,16 @@ def get_stub_check_path(in_path, checkStub='engineroot.txt'):
# -------------------------------------------------------------------------
def reorder_sys_paths(known_sys_paths):
"""Reorders new directories to the front"""
new_sys_path = []
sys_paths = list(sys.path)
new_sys_paths = []
for item in list(sys.path):
for item in sys_paths:
item = Path(item)
if item.lower() not in known_sys_paths:
new_sys_path.append(item)
sys.path.remove(item)
if str(item).lower() not in known_sys_paths:
new_sys_paths.append(item)
sys_paths.remove(str(item))
sys.path[:0] = new_sys_path
sys.path[:0] = new_sys_paths
known_sys_paths = site._init_pathinfo()
return known_sys_paths
@@ -386,13 +379,11 @@ def walk_up_dir(in_path, dir_tag='foo'):
returns None if the directory named dir_tag is not found
'''
from unipath import Path
path = Path(Path(__file__).absolute())
path = Path(__file__).absolute()
while 1:
# hmmm, will this break on unix paths?
# what about case sensitivity?
dir_base_name = Path(path.norm_case()).name()
dir_base_name = path.norm_case().name()
if (dir_base_name == dir_tag):
break
path, tail = (path.parent(), path.name())
@@ -406,6 +397,7 @@ def walk_up_dir(in_path, dir_tag='foo'):
# --------------------------------------------------------------------------
def return_stub(stub):
'''Take a file name (stub) and returns the directory of the file (stub)'''
# to do: refactor to pathlib.Path
from unipath import Path
dir_last_file = None
@@ -43,7 +43,7 @@ from azpy.constants import *
# -------------------------------------------------------------------------
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -0,0 +1,44 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# -------------------------------------------------------------------------
# The __init__.py files help guide import statements without automatically
# importing all of the modules
"""azpy.shared.ui.__init__"""
import os
import logging
import logging.config
# global space debug flag
_G_DEBUG = os.getenv('DCCSI_GDEBUG', False)
# global space debug flag
_DCCSI_DEV_MODE = os.getenv('DCCSI_DEV_MODE', False)
if _DCCSI_DEV_MODE:
_PACKAGENAME = __name__
if _PACKAGENAME is '__main__':
_PACKAGENAME = 'noodely'
_PKG_PARENT_PATH = str('azpy.shared')
_PKG_PATH = str('{0}.{1}'.format(_PKG_PARENT_PATH, _PACKAGENAME))
_LOGGER = logging.getLogger(_PACKAGENAME)
_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PKG_PATH}))
# -------------------------------------------------------------------------
#
__all__ = ['config', 'find_arg', 'master', 'node', 'synth',
'synth_arg_kwarg', 'test_foo']
#
# -------------------------------------------------------------------------
del _LOGGER
@@ -0,0 +1,123 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# find_arg.py
# A simple function for arg, kwarg retrieval
# version: 0.1
# maintenance: Gallowj
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
"""Module docstring: A simple function for retrieval of arg, kwarg"""
__author__ = 'HogJonny'
# -------------------------------------------------------------------------
def find_arg(argPosIndex=None, argTag=None, removeKwarg=None,
inArgs=None, inKwargs=None, defaultValue=None):
"""
# finds and returns an arg...
# if a positional index is given argPosIndex=0, it checks args first
# if a argTag is given, it checks kwargs
# If removeKwarg=True, it will remove the found arg from kwargs
# * I actually want/need to do this often
#
# a set kwarg will ALWAYS take precident over
# positional arg!!!
#
# return outArg, args, kwargs <-- get back modified kwargs!
#
# proper usage:
#
# foundArg, args, kwargs = find_arg(0, 'name',)
"""
if argPosIndex != None:
if not isinstance(argPosIndex, int):
raise TypeError('argPosIndex: accepts a index integer!\r'
'got: {0}'.format(argPosIndex))
# positional args ... check the position
if len(inArgs) > 0:
try:
foundArg = inArgs[argPosIndex]
except:
pass
# check kwargs ... a set kwarg will ALWAYS take precident over
# positional arg!!!
try:
foundArg
except:
foundArg = inKwargs.get(argTag, defaultValue) # defaults to None
if removeKwarg:
if argTag in inKwargs:
del inKwargs[argTag]
# if we didn't find the arg/kwarg, the defualt return will be None
return foundArg, inKwargs
# -------------------------------------------------------------------------
###########################################################################
# --call block-------------------------------------------------------------
if __name__ == "__main__":
print ("# ----------------------------------------------------------------------- #\r")
print ('~ find_arg.py ... Running script as __main__')
print ("# ----------------------------------------------------------------------- #\r")
_G_DEBUG = True
from test_foo import Foo
#######################################################################
# Node Class
# ---------------------------------------------------------------------
class TestNode(Foo):
def __init__(self, *args, **kwargs):
super().__init__()
self._name, kwargs = find_arg(argTag='foo', removeKwarg=True,
inArgs=args, inKwargs=kwargs)
self._name, kwargs = find_arg(argPosIndex=0, argTag='name',
removeKwarg=True,
inArgs=args, inKwargs=kwargs) # <-- first positional OR kwarg
self._parent, kwargs = find_arg(argPosIndex=1, argTag='parent',
removeKwarg=True,
inArgs=args, inKwargs=kwargs) # <-- second positional OR kwarg
self._kwargsDict = {}
# arbitrary argument properties
# checking **kwargs, any kwargs left
# will be used to synthesize a property
for key, value in kwargs.items():
self._kwargsDict[key] = value
# synthesize(self, '{0}'.format(key), value) <-- I have a method,
# which synthesizes properties... with gettr, settr, etc.
if _G_DEBUG:
print("{0}:{1}".format(key, value))
# representation
def __repr__(self):
return '{0}({1})\r'.format(self.__class__.__name__, self.__dict__)
# ---------------------------------------------------------------------
# -------------------------------------------------------------------------
testNode = TestNode('foo')
testNode2 = TestNode(name='fooey', parent=testNode)
testNode3 = TestNode('kablooey', testNode2, goober='dufus')
print ('testNode2, name: {0}, parent: {1}'.format(testNode2._name, testNode2._parent))
print (testNode3)
@@ -0,0 +1,46 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# -------------------------------------------------------------------------
###########################################################################
# HELPER method functions
# -------------------------------------------------------------------------
def istext(filename):
"""
A guess if a file is text or binary
"""
s = open(filename).read(512)
text_characters = "".join(map(chr, range(32, 127)) + list("\n\r\t\b"))
_null_trans = string.maketrans("", "")
if not s:
# Empty files are considered text
return True
if "\0" in s:
# Files with null bytes are likely binary
return False
# Get the non-text characters (maps a character to itself then
# use the 'remove' option to get rid of the text characters.)
t = s.translate(_null_trans, text_characters)
# If more than 30% non-text characters, then
# this is considered a binary file
if float(len(t)) / float(len(s)) > 0.30:
return False
return True
def display_cached_value(cache, cache_key):
try:
cached_value = cache[cache_key]
print("{0}={1}".format(cache_key, cached_value))
except KeyError:
print("{0}=Not in cache".format(cache_key))
@@ -0,0 +1,90 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# master.py
# Allows for project based setup to be used with noodly
# version: 0.1
# author: Gallowj
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
import os
from unipath import Path
_G_DEFAULT_PROJECT_DIR = os.getcwd()
_G_MASTER_ROOT_NODE = None
@property
def _G_DEFAULT_PROJECT_DIR(value):
_G_DEFAULT_PROJECT_DIR = value
return _G_DEFAULT_PROJECT_DIR
def set_PROJECT_DIR(value):
"""Sets and returns _G_DEFAULT_PROJECT_DIR"""
global _G_DEFAULT_PROJECT_DIR
_G_DEFAULT_PROJECT_DIR = Path(value).expand()
return Path(_G_DEFAULT_PROJECT_DIR)
@property
def _G_MASTER_ROOT_NODE(value):
_G_MASTER_ROOT_NODE = value
return _G_MASTER_ROOT_NODE
def set_MASTER_ROOT_NODE(value):
"""Sets and returns _G_MASTER_ROOT_NODE"""
global _G_MASTER_ROOT_NODE
if not isinstance(inNode, ProjectRootNode):
raise TypeError('self._projectRootNode is: {0}\r'
'A _projectRootNode, needs to be properly set\r'
'So that we can acces:\r'
'\tself._projectRootNode._sourceRoot\r'
'\tself._projectRootNode._overrideRoot\r'
'Use self.assignProjectRootNode(<projectRootNode>)\r'
''.format(type(inNode)))
_G_MASTER_ROOT_NODE = inNode
return _G_MASTER_ROOT_NODE
###########################################################################
# tests(), code block for testing module
# -------------------------------------------------------------------------
def tests():
set_PROJECT_DIR(os.getcwd())
print(_G_DEFAULT_PROJECT_DIR)
print(_G_DEFAULT_PROJECT_DIR.parent)
print(_G_DEFAULT_PROJECT_DIR.components())
# NOT implemented yet
# set_PROJECT_DIR
return
###########################################################################
# --call block-------------------------------------------------------------
if __name__ == "__main__":
print ("# ----------------------------------------------------------------------- #")
print ('~ noodly.master ... Running script as __main__')
print ("# ----------------------------------------------------------------------- #\r")
# run simple tests
tests()
#_G_DEFAULT_PROJECT_DIR = Path(os.getcwd())
print(_G_DEFAULT_PROJECT_DIR.components())
@@ -0,0 +1,623 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# -------------------------------------------------------------------------
# this has to be at the beginning
from __future__ import division
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# node.py
# simple base Node Class, for tool creation.
# version: 0.1
# author: Gallowj
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
"""
Module docstring:
A Simple Node Base Class Module, for creating basic nodes within a hierarchy.
"""
__author__ = 'HogJonny'
_G_DEBUG = True # global state for debugging
_G_SETTINGS = None # global Settings storage
_G_LOG = None # global LOGger storage
_G_MASTER_NODE = None # We intend to init a master node, unless provided
# -------------------------------------------------------------------------
# built-ins
import os
import copy
import traceback
import string
import logging
# using hashids to generate unique name identifiers for nodes
import hashids
import cachetools
from sched import scheduler
# local imports
from LyPy.si_shared.noodly.helpers import display_cached_value
from LyPy.si_shared.noodly.find_arg import find_arg
from LyPy.si_shared.noodly.synth import synthesize
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# quick test code (remove later)
from hashids import Hashids
hashids = Hashids(min_length=16, salt='DCCsi')
if _G_DEBUG:
print (hashids.encrypt(193487)) # test hash
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# set up logger
_G_LOGGER = logging.getLogger(__name__)
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# Use unicode strings
_base = str # Python 3 str (=unicode), or Python 2 bytes.
if os.path.supports_unicode_filenames:
try:
_base = unicode # Python 2 unicode.
except NameError:
pass
# -------------------------------------------------------------------------
###########################################################################
# HELPER method functions
# -------------------------------------------------------------------------
def return_node_from_hashid(hashid):
if not isinstance(hashid, str):
raise TypeError("{0},{1}: Accepts hashids as str types!\r"
"Input hashid:{2}\r"
"".format('noodly',
'return_node_from_hashid(hashid)',
type(hashid)))
temp_node = Node(temp_node=True).get_sibling_node_from_hashid('{0}'.format(hashid))
return temp_node
# -------------------------------------------------------------------------
class Node(object):
"""Class constructor: makes a node."""
# share the debug state
_DEBUG = _G_DEBUG
# logger
_LOGGER = _G_LOGGER
# class header
message_header = 'noodly, Node(): Message'
# class variable
_cls_node_count = 0
_cls_node_list = []
_cls_node_dict = {}
# --BASE-METHODS-------------------------------------------------------
# --constructor-
def __init__(self, node_name=None, parent_node=None, *args, **kwargs):
self._logger = Node._LOGGER
self._node_type = self.__class__.__name__
# a dict to store properties/attrs
# in the event an object is re-built / re-init
# it is important to store anything here that needs retention
self._kwargs_dict = {}
self._children = []
# private local access to the cls_node_list
self._cls_node_list = Node._cls_node_list
# -- secret keyword -----------------------------------------------
self._temp_node = False
temp_node, kwargs = find_arg(argPosIndex=None, argTag='temp_node',
removeKwarg=True, inArgs=args,
inKwargs=kwargs) # <-- kwarg only
self._temp_node = temp_node
if self._temp_node:
self._kwargs_dict['temp_node'] = self._temp_node
# -----------------------------------------------------------------
# -- store message header -----------------------------------------
# setup the .message_header <-- kwarg only
message_header, kwargs = find_arg(argPosIndex=None, argTag='message_header',
removeKwarg=True, inArgs=args, inKwargs=kwargs,
defaultValue=('{0}(), Message'
.format(self._node_type)))
self._message_header = message_header
# -----------------------------------------------------------------
# -- hashid -------------------------------------------------------
self._name_is_uni_hashid = False
self._node_class_index = len(Node._cls_node_list)
if Node._DEBUG:
print ('__init__.node_class_index: {0}'.format(self._node_class_index))
self._uni_hashid = hashids.encrypt(self._node_class_index)
if Node._DEBUG:
print ('__init__.uni_hashid: {0}'.format(self._uni_hashid))
# update class dict
if not self._temp_node:
Node._cls_node_dict[self._uni_hashid] = self
# -----------------------------------------------------------------
# -- store the node name ------------------------------------------
self._node_name = node_name
if (self._node_class_index == 0 and self._node_name == None):
self._node_name = 'MASTER'
elif (self._node_class_index > 0 and self._node_name == None):
# set a default node_name if none, based on the unihashid
if not self._name_is_uni_hashid:
self._node_name = self._uni_hashid
self._name_is_uni_hashid = True
if Node._DEBUG:
print ('__init__.node_name: {0}'.format(self._node_name))
# -----------------------------------------------------------------
# -- node parent_node --------------------------------------------------
# set up the parent_node property
self._parent_node = parent_node
if self._parent_node != None:
# add this node, to the parent_nodes list of children
try:
self._parent_node.add_child(self)
except:
pass # <-- parent_node object passed is NOT a noodly.node?
# Update class variables
Node.cls_node_count_up(self)
Node.cls_node_list_append(self)
# -----------------------------------------------------------------
# -----------------------------------------------------------------
# arbitrary argument properties
# check postions *args and **kwargs
# any kwargs left will be used to synthesize a property
try:
# checking import due to the way the code is structured
# the code was passing even if module was not imported
synthesize
synthExists = True
except Exception as e:
print(e)
raise e
for key, value in kwargs.items():
self._kwargs_dict[key] = value
try:
synthesize(self, key, value)
except Exception as e: # <-- maybe it can't synthesize?
# in which case fall back to setting the property
print(e)
code = compile(r'self._{0}={1}'.format(key, value), 'synthProp', 'exec')
pass
if Node._DEBUG:
print("{0}:{1}".format(key, value))
# -----------------------------------------------------------------
# if temp node, adjust the class counter
if temp_node:
self.cls_node_count_down()
self.cls_node_list_remove()
# -- properties ------------------------------------------------------------
@property
def logger(self):
return self._logger
@logger.setter
def logger(self, logger):
self._logger = logger
return self._logger
@logger.getter
def logger(self):
return self._logger
@property
def kwargs_dict(self):
return self._kwargs_dict
@kwargs_dict.getter
def kwargs_dict(self):
return self._kwargs_dict
@property
def message_header(self):
return self._message_header
@message_header.setter
def message_header(self, message_header):
self._message_header = message_header
return self._message_header
@property
def node_type(self):
return self._node_type
@node_type.setter
def node_type(self, node_type):
self._node_type = node_type
return self._node_type
@node_type.getter
def node_type(self):
return self._node_type
@property
def temp_node(self):
return self._temp_node
@temp_node.setter
def temp_node(self, temp_node):
self._temp_node = temp_node
return self._temp_node
@temp_node.getter
def temp_node(self):
return self._temp_node
@property
def node_name(self):
return self._node_name
@node_name.setter
def node_name(self, nameStr):
if nameStr != None:
if not isinstance(nameStr, str):
raise TypeError("{0}, {1}: Accepts str types!"
"".format(self.__class__.__name__,
self._node_name.__name__))
try:
self._node_name = nameStr
except:
synthesize(self, '_node_name', None)
return self._node_name
@node_name.getter
def node_name(self):
return self._node_name
@property
def name_is_uni_hashid(self):
return self._name_is_uni_hashid
@node_type.setter
def name_is_uni_hashid(self, value):
self._name_is_uni_hashid = value
return self._name_is_uni_hashid
@node_type.getter
def name_is_uni_hashid(self):
return self._name_is_uni_hashid
@property
def node_class_index(self):
return self._node_class_index
@property
def uni_hashid(self):
return self._uni_hashid
@property
def cls_node_dict(self):
return Node._cls_node_dict
# ----------------------------------------------------------------------
# --method-set---------------------------------------------------------
def cls_node_count_up(self):
Node._cls_node_count += 1
return Node._cls_node_count
def cls_node_count_down(self):
Node._cls_node_count -= 1
return Node._cls_node_count
def cls_node_list_append(self):
Node._cls_node_list.append(self)
return Node._cls_node_list
def cls_node_list_remove(self):
Node._cls_node_list.remove(self)
return Node._cls_node_list
# ---------------------------------------------------------------------
# --method-set---------------------------------------------------------
@property
def parent_node(self):
return self._parent_node
@parent_node.setter
def parent_node(self, parent_node):
self._parent_node = parent_node
return self._parent_node
@parent_node.getter
def parent_node(self):
return self._parent_node
def add_child(self, child):
self._children.append(child)
# --method--
def remove_child(self, child):
self._children.remove(child)
@property
def children(self):
return self._children
def child(self, row):
return self._children[row]
def child_count(self):
return len(self._children)
def row(self):
if self._parent_node != None:
return self._parent_node._children.index(self)
# ---------------------------------------------------------------------
# --method-------------------------------------------------------------
def get_sibling_node_from_hashid(self, hashid):
if not isinstance(hashid, str):
raise TypeError("{0}.{1}: Accepts hashids as str types!"
"".format(self.__class__.__name__,
'get_sibling_node_from_hashid(hashid)'))
if hashid in self.cls_node_dict.keys():
return self.cls_node_dict[hashid]
else:
return None
# --method-------------------------------------------------------------
def clear_node_dep(self):
self.clear_children
self.clear_node_list()
self.clear_node_count()
return self
def clear_node_list(self):
Node._cls_node_list = []
return Node._cls_node_list
def clear_node_count(self):
Node._cls_node_count = 0
return Node._cls_node_count
def clear_children(self):
self._children = []
return self._children
# ---------------------------------------------------------------------
# ---------------------------------------------------------------------
@cachetools.cached(cachetools.LFUCache(maxsize=2048))
def cache_node(self):
return self
# ---------------------------------------------------------------------
# --method-------------------------------------------------------------
def hierarchy(self, tab_level=-1):
output = ''
tab_level += 1
for i in range(tab_level):
output += '\t'
output += ('{tab}/------node_name:: "{0}"\n'
'{1} |type:: {2}\n'
'{1} |_uni_hashid:: "{3}"\r'
''.format(self._node_name,
'\t' * tab_level,
self._node_type,
self._uni_hashid,
tab=tab_level))
# TO DO:: object hierarchy "'hips'|'rightLeg'|'etc'"
for child in self._children:
output += child.hierarchy(tab_level)
tab_level -= 1
# output += '\n'
return output
def log_hierarchy(self):
# Not implemented
self._logger.autolog(self.hierarchy(),
"return_node_from_hashid('{0}').log_hierarchy()"
"".format(self._uni_hashid()))
return
# ---------------------------------------------------------------------
# --method-------------------------------------------------------------
# representation
def __str__(self):
'''Returns a nice string representation of the object.'''
# TO DO: need to improve this
if self.node_name == None or self.name_is_uni_hashid == True:
# open parenthesis only
output = ("{0}(node_name='{1}'"
"".format(self.__class__.__name__,
self.uni_hashid))
else:
output = ("{0}(node_name='{1}'"
"".format(self.__class__.__name__,
self.node_name))
if self.parent_node != None:
output += (", parent_node=return_node_from_hashid('{0}')"
"".format(self.parent_node.uni_hashid))
if len(self.kwargs_dict) > 0:
for key, value in self.kwargs_dict.items():
if not isinstance(value, str):
output += (", {0}={1}".format(key, value))
else: # if a str add the extra quotes
output += (", {0}='{1}'".format(key, value))
# add the close parenthesis
output += ')'
if self.name_is_uni_hashid == True or self.temp_node:
output = ("return_node_from_hashid('{0}')"
"".format(self.node_name))
# Node(self, node_name, parent_node=None, path='')
return output
# representation
def __repr__(self):
return '{0}({1})\r'.format(self.__class__.__name__, self.__dict__)
# --Class End--------------------------------------------------------------
class ClassProperty(property):
"""Decorator"""
def __get__(self, cls, owner):
return self.fget.__get__(None, owner)()
# --Class End--------------------------------------------------------------
###########################################################################
# tests(), code block for testing module
# -------------------------------------------------------------------------
def tests():
default_node = Node()
print(str(default_node))
print(repr(default_node))
print(default_node.uni_hashid)
print(default_node.kwargs_dict)
print(default_node.message_header)
print(default_node.node_name)
print(default_node.parent_node)
print(default_node.node_class_index)
# print(default_node.cls_node_dict)
# temp_node = Node()
temp_node = Node(temp_node=True)
print(temp_node)
test_default_node = Node(temp_node=True).get_sibling_node_from_hashid('kxYLm0XQeXJ7jWaP')
print(test_default_node)
another_test = return_node_from_hashid('kxYLm0XQeXJ7jWaP')
print(another_test)
second_node = Node(node_name='foo', parent_node=default_node)
print(str(second_node))
print(second_node.uni_hashid)
print(second_node.kwargs_dict)
print(second_node.message_header)
print(second_node.node_name)
print(second_node.parent_node.node_name)
print(second_node.node_class_index)
# print(second_node.cls_node_dict)
second_child = Node(node_name='fooey', parent_node=another_test)
print(str(second_child))
print(second_child.uni_hashid)
print(second_child.kwargs_dict)
print(second_child.message_header)
print(second_child.node_name)
print(second_child.parent_node.node_name)
print(second_child.node_class_index)
# print(second_child.cls_node_dict)
third_node = Node(node_name='kablooey', parent_node=second_node,
message_header='Node(): CUSTOM Message')
print(str(third_node))
print(third_node.uni_hashid)
print(third_node.kwargs_dict)
print(third_node.message_header)
print(third_node.node_name)
print(third_node.parent_node.node_name)
print(third_node.node_class_index)
fourth_node = Node(parent_node=second_node,
message_header='Node(): CUSTOM Message')
print(str(fourth_node))
print(fourth_node.uni_hashid)
print(fourth_node.kwargs_dict)
print(fourth_node.message_header)
print(fourth_node.node_name)
print(fourth_node.parent_node.node_name)
print(fourth_node.node_class_index)
kwarg_test_child = Node(node_name='kwargChild', parent_node=default_node, garble=1001001) # custom kwarg
print(str(kwarg_test_child))
print(kwarg_test_child.uni_hashid)
print(kwarg_test_child.node_name)
print(kwarg_test_child.parent_node.node_name)
print(kwarg_test_child.kwargs_dict)
# check the custom arg/property garble
print(kwarg_test_child.garble)
# check the node hierarchy
print(default_node.hierarchy())
# retreive a node from it's known hashid
master_node = return_node_from_hashid('kxYLm0XQeXJ7jWaP')
print(master_node.uni_hashid) # verify hasid
# should return the same node as default_node
print(master_node.node_name) # should be 'MASTER'
return
# -------------------------------------------------------------------------
def cache_tests():
cache = cachetools.LFUCache(maxsize=128)
runner = scheduler()
cache["HogJonny"] = 1001001
runner.enter(2, 1, display_cached_value,
kwargs={'cache': cache, 'cache_key': 'HogJonny'})
runner.enter(6, 1, display_cached_value,
kwargs={'cache': cache, 'cache_key': 'HogJonny'})
runner.run()
return
# -------------------------------------------------------------------------
def main():
return
# - END, main() --
###########################################################################
# --call block-------------------------------------------------------------
if __name__ == "__main__":
print ("# ----------------------------------------------------------------------- #")
print ('~ noodly.Node ... Running script as __main__')
print ("# ----------------------------------------------------------------------- #\r")
# run simple tests
tests()
cache_tests()
@@ -0,0 +1,407 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# -------------------------------------------------------------------------
# this has to be at the beginning
from __future__ import division
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# pathnode.py
# simple path objecy based Node Class, for tool creation.
# version: 0.1
# author: Gallowj
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
"""
Module docstring:
A simple path objecy based Node Class, for creating path hierarchies.
"""
__author__ = 'HogJonny'
_G_DEBUG = True # global state for debugging
_G_SETTINGS = None # global Settings storage
_G_LOG = None # global LOGger storage
_G_MASTER_NODE = None # We intend to init a master node
# -------------------------------------------------------------------------
# built-ins
import os
import copy
import subprocess
import traceback
import string
import logging
from unipath import Path, AbstractPath
# local ly imports
from LyPy.si_shared.noodly.helpers import istext
from LyPy.si_shared.noodly.find_arg import find_arg
from LyPy.si_shared.noodly.synth import synthesize
from LyPy.si_shared.noodly.node import Node
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# Use unicode strings
_base = str # Python 3 str (=unicode), or Python 2 bytes.
if os.path.supports_unicode_filenames:
try:
_base = unicode # Python 2 unicode.
except NameError:
pass
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# set up logger
_G_LOGGER = logging.getLogger(__name__)
# -------------------------------------------------------------------------
class PathNode(Node):
"""doc string"""
# share the debug state
_DEBUG = _G_DEBUG
# logger
_LOGGER = _G_LOGGER
# class header
_message_header = 'noodly, PathNode(): Message'
# App Launcher paths...
try:
_maya_exe_path = Path(os.environ['MAYAPY'])
except:
_maya_exe_path = Path(r"C:\Program Files\Autodesk\Maya2019\bin\maya.exe")
try:
_notepad_exe_path = Path(os.environ['DEFAULT_TXT_EXE'])
except:
_notepad_exe_path = Path(r"C:\Program Files (x86)\Notepad++\notepad++.exe")
# --BASE-METHODS-------------------------------------------------------
def __new__(cls, path="", root_path=None, *args, **kwargs):
'''docstring'''
# if not isinstance(path, str) and not isinstance(path, Path):
# raise TypeError("{0}, {1}: Accepts paths as str or Path() types!\r"
# "Input data is:{2}\r"
# "".format('noodly, PathNode',
# 'PathNode(filename)', type(path)))
#
self = super(PathNode, cls).__new__(cls)
return self
# --constructor--------------------------------------------------------
def __init__(self, path="", root_path=None, parent_is_root=None,
name_is_path=None, *args, **kwargs):
self._logger = Node._LOGGER
self._node_type = self.__class__.__name__
# a dict to store properties/attrs
# in the event an object is re-built / re-init
# it is important to store anything here that needs retention
self._kwargs_dict = {}
# -- secret keyword -----------------------------------------------
self._temp_node = False
temp_node, kwargs = find_arg(argPosIndex=None, argTag='temp_node',
removeKwarg=True, inArgs=args,
inKwargs=kwargs) # <-- kwarg only
self._temp_node = temp_node
if self._temp_node:
self.k_wargs_dict['temp_node'] = self._temp_node
# -- Node class args/kwargs ---------------------------------------
node_name, kwargs = find_arg(argPosIndex=2, argTag='node_name',
removeKwarg=True, inArgs=args,
inKwargs=kwargs) # <-- third arg, kwarg
parent_node, kwargs = find_arg(argPosIndex=3, argTag='parent_node',
removeKwarg=True, inArgs=args,
inKwargs=kwargs) # <-- fourth arg, kwarg
self._root_path = root_path
self._parent_is_root = parent_is_root
if self._parent_is_root != None:
self._kwargs_dict['parent_is_root'] = self.parent_is_root
if parent_is_root: # <-- do it
self._root_path = parent_node
# make sure the path is a Path
self._path = path
if not isinstance(self._path, Path):
try:
self._path = Path(path)
except:
self._path = Path() # empty path object fallback
self._name_is_path = name_is_path
if self._name_is_path:
self._kwargs_dict['name_is_path'] = self._name_is_path
# this might only work if the file actually exists
self._node_name = node_name
if self._name_is_path:
if self._path.name != None or self._path.name != '':
self._node_name = str(self._path.name)
# Path.__init__(self)
super(PathNode, self).__init__(self._node_name, parent_node,
temp_node=temp_node,
*args, **kwargs)
# -- properties -------------------------------------------------------
@property
def path(self):
return self._path
@path.setter
def path(self, path):
self._path = path
return self._path
@path.getter
def path(self):
return self._path
@property
def root_path(self):
return self._root_path
@root_path.setter
def root_path(self, root_path):
self._root_path = root_path
return self._root_path
@root_path.getter
def root_path(self):
return self._root_path
@property
def parent_is_root(self):
return self._parent_is_root
@parent_is_root.setter
def parent_is_root(self, parent_is_root):
self._parent_is_root = parent_is_root
return self._parent_is_root
@parent_is_root.getter
def parent_is_root(self):
return self._parent_is_root
@property
def name_is_path(self):
return self._name_is_path
# @name_is_path.setter
# def name_is_path(self, name_is_path):
# self._name_is_path = name_is_path
# return self._name_is_path
@name_is_path.getter
def name_is_path(self):
return self._name_is_path
# --method-------------------------------------------------------------
def set_file_path(self, path):
if not isinstance(path, Path):
try:
path = Path(path)
except:
raise TypeError("must be Path compatible")
# retreive a copy of the old _kwargs dict
_kwargs_dict_copy = copy.copy(self._kwargs_dict)
_name_is_uni_hashid = copy.copy(self.name_is_uni_hashid)
# create a new me (self), with new value
# attempt to keep existing attrs/settings
self = PathNode(path=path,
root_path=self.root_path,
parent_is_root=self.parent_is_root,
name_is_path=self.name_is_path,
temp_node=self.temp_node,
node_name=self.node_name,
parent_node=self.parent_node,
name_is_uni_hashid=self.name_is_uni_hashid)
# now we need to restore any custom properties on the replacement object
for key, value in _kwargs_dict_copy.items():
self._kwargs_dict[key] = value
try:
synthesize(self, '{0}'.format(key), value)
except:
code = compile('self._{0} = {1}'.format(key, value), 'synthProp', 'exec')
if Node._DEBUG:
self.logger.error('can not set: self._{0} = {1}'.format(key, value))
# replace myself in the class nodeDict, based on my unihashid
self.cls_node_dict[self.uni_hashid] = self
# return the new version of myself
return self.cls_node_dict[self.uni_hashid]
# ---------------------------------------------------------------------
# --method-------------------------------------------------------------
def start_file(self, filepath=None):
'''opens the file in the prefered os editor for the filetype'''
if filepath == None:
filepath = self.path
if not isinstance(filepath, Path): # <-any subclass of Path works?
filepath = Path(filepath)
self.logger.debug('starting file: {0}'.format(filepath))
try:
os.startfile(filepath)
except IOError as e:
self.logger.error(e)
return filepath
# ---------------------------------------------------------------------
# --method-------------------------------------------------------------
def explore_file(self, filepath=None):
if filepath == None:
filepath = self.path
if not isinstance(filepath, Path):
filepath = Path(filepath)
self.logger.debug('exploring file: {0}'.format(filepath))
if filepath.exists():
try:
subprocess.Popen(r'explorer /select,"{0}"'.format(filepath))
except IOError as e:
self.logger.error(e)
else:
self.logger.error('file does not exist: {0}'.format(filepath))
return filepath
# ---------------------------------------------------------------------
# --method-------------------------------------------------------------
def hierarchy(self, tabLevel=-1):
output = ''
if isinstance(self, RootNode):
if gDebug:
func = inspect.currentframe().f_back.f_code
output += ('{0}Called from:\n'
'{0}{1}\n'.format('\t' * (tabLevel + 1), func))
tabLevel += 1
for i in range(tabLevel):
output += '\t'
output += ('{tab}/------ nodeName:: "{0}"\n'
'{1} |typeInfo:: {2}\n'
'{1} |_uniHashid:: "{3}"\r'
'{1} |path:: "{4}"\n'
'{1} |get_root():: "{5}"\n'
'{1} |getPathFromRoot():: "{6}"\n'
''.format(self.getNodeName(),
'\t' * tabLevel,
self.get_typeInfo(),
self.get_uniHashid(),
self,
self.get_root(),
self.getPathFromRoot(),
tab=tabLevel))
for child in self._children:
output += child.hierarchy(tabLevel)
tabLevel -= 1
return output
# ---------------------------------------------------------------------
# --Class End--------------------------------------------------------------
###########################################################################
# tests(), code block for testing module
# -------------------------------------------------------------------------
def tests():
from node import Node
default_node = Node() # result: Node(node_name='MASTER')
print(default_node)
first_child = PathNode(path=None, node_name='first_child', parent_node=default_node)
print(first_child)
# result: PathNode(temp_node=True, parent_node=Node(node_name='MASTER')).siblingNodeFromHashid('WNPZoKBVpXV16QLz')
# first_child.nodeType
# first_child.parent_node
# first_child.node_name
try:
# PathNode requires a arg 'path' input (should be a path str)
fubar_path_node = PathNode() # <-- this should fail
print (fubar_path_node)
except Exception as err:
print ('\r{0}'.format(err))
print (traceback.format_exc())
foo = PathNode(r'\foo\fooey\kablooey', node_name='foo',
parent_node=default_node)
print(foo)
testes = Path(r'/foo/fooey/kablooey')
print(foo.path.exists())
print(foo.path.parent)
print(foo.path.norm_case())
print(foo.path.absolute())
fooey = PathNode(None, parent_node=foo)
print(fooey)
kablooey = PathNode(r'\foo\fooey\kablooey',
parent_node=default_node,
name_is_path=True)
print(kablooey)
kablooey = kablooey.set_file_path(r'c:\mytemp\fubar.txt')
print(kablooey)
kablooey.start_file()
kablooey.explore_file()
return
# - END, tests() ----------------------------------------------------------
def main():
pass
return
# - END, main() -----------------------------------------------------------
###########################################################################
# --call block-------------------------------------------------------------
if __name__ == "__main__":
print ("# ----------------------------------------------------------------------- #")
print ('~ noodly.PathNode ... Running script as __main__')
print ("# ----------------------------------------------------------------------- #\r")
# run simple tests
tests()
@@ -0,0 +1,116 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# synth.py
# Convenience module for a standardized attr interface for classes/objects.
# version: 0.1
# date: 11/14/2013
# author: jGalloway
# -------------------------------------------------------------------------
__author__ = 'HogJonny'
# -------------------------------------------------------------------------
"""
This module contains the function
.synthesize(inst, name, value, readonly=False)
It is useful in object oriented class attributes, providing a stardard
interface for creating properties of classes.
Can be called within an objects Class, or an instance of an object can be
passed into the function to have the properties added. The property will
be added and set/get/del attr interface will be created.
"""
# --------------------------------------------------------------------------
def synthesize(inst, name, value, readonly=False):
"""
Convenience method to create getters, setters and a property for the
instance. Should the instance already have the getters or setters
defined this won't add them and the property will reference the already
defined getters and setters Should be called from within __init__.
Creates [name], _[name], get[Name], set[Name], del[Name], and on inst.
:param inst: An instance of the class to add the methods to.
:param name: Base name to build function names and storage variable.
:param value: Initial state of the created variable.
"""
cls = type(inst)
storageName = '_{0}'.format(name)
getterName = 'get{0}{1}'.format(name[0].capitalize(), name[1:])
setterName = 'set{0}{1}'.format(name[0].capitalize(), name[1:])
deleterName = 'del{0}{1}'.format(name[0].capitalize(), name[1:])
setattr(inst, storageName, value)
# We always define the getter
def buildCustomGetter(self):
return getattr(self, storageName)
# Add the Getter
if not hasattr(inst, getterName):
setattr(cls, getterName, buildCustomGetter)
# Handle Read Only
if readonly:
if not hasattr(inst, name):
setattr(cls, name,
property(fget=getattr(cls, getterName, None) or buildCustomGetter,
fdel=getattr(cls, getterName, None)))
else:
# We only define the setter if we arn't read only
def buildCustomSetter(self, state):
setattr(self, storageName, state)
if not hasattr(inst, setterName):
setattr(cls, setterName, buildCustomSetter)
member = None
if hasattr(cls, name):
# we need to try to update the property fget, fset, fdel
# incase the class has defined its own custom functions
member = getattr(cls, name)
if not isinstance(member, property):
raise ValueError('Member "{0}" for class "{1}" exists and is not a property.'
''.format(name, cls.__name__))
# If the class has the property or not we still try to set it
setattr(cls, name,
property(fget=getattr(member, 'fget', None)
or getattr(cls, getterName, None)
or buildCustomGetter,
fset=getattr(member, 'fset', None)
or getattr(cls, setterName, None)
or buildCustomSetter,
fdel=getattr(member, 'fdel', None)
or getattr(cls, getterName, None)))
return getattr(inst, name)
# --------------------------------------------------------------------------
###########################################################################
# Main Code Block, will run the tool
# -------------------------------------------------------------------------
if __name__ == '__main__':
"""Self Testing"""
from test_foo import Foo
# create an object from Foo Class
myFoo = Foo()
pass
@@ -0,0 +1,158 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# synth_arg_kwarg.py
# Convenience module for a standardized attr interface for classes/objects.
# -------------------------------------------------------------------------
__author__ = 'HogJonny'
# -------------------------------------------------------------------------
from find_arg import find_arg
from synth import synthesize
# -------------------------------------------------------------------------
def setSynthArgKwarg(inst, argPosIndex=None, argTag=None, defaultValue=None,
inArgs=None, inKwargs=None, removeKwarg=True,
setAnyway=True):
"""
Uses find_arg and sets a property on a object.
Special args:
setAnyway <-- if the object has the property already, set it
If the arg/property doesn't exist we synthesize it
"""
foundArg = None
argValueDict = {}
# find the argument, or set to default value
foundArg, inKwargs = find_arg(argPosIndex, argTag, removeKwarg,
inArgs, inKwargs,
defaultValue)
if foundArg:
argTag = foundArg
# single arg first
# make sure the object doesn't arealdy have this property
try:
hasattr(inst, argTag) # check if property exists
if setAnyway:
try:
setattr(inst, argTag, defaultValue) # try to set
except Exception as e:
raise e
except:
pass
# make it a synthetic property
if argTag:
try:
argValue = synthesize(inst, argTag, defaultValue)
argValueDict[argTag] = argValue
except Exception as e:
raise e
# multiple and/or remaining kwards next
if inKwargs:
if len(inKwargs) > 0:
for k, v in inKwargs.items():
try:
hasattr(inst, k) # check if property exists
if setAnyway:
try:
setattr(inst, k, v) # try to set
except Exception as e:
raise e
except:
pass
if k:
try:
argValue = synthesize(inst, k, v)
argValueDict[k] = argValue
except Exception as e:
raise e
return argValueDict
# --------------------------------------------------------------------------
###########################################################################
# Main Code Block, will run the tool
# -------------------------------------------------------------------------
if __name__ == '__main__':
from test_foo import Foo
# define a arg/property tag we know doesn't exist
synthArgTag = 'syntheticArg'
# create a test object
print('~ creating the test foo object...')
myFoo = Foo()
print('~ Starting - single synthetic arg test...')
# find and set existing, or create and set
argValueDict = setSynthArgKwarg(myFoo,
argTag=synthArgTag,
defaultValue='kablooey')
# what was returned
print('~ single value returned...')
for k, v in argValueDict.items():
print("Arg '{0}':'{1}'".format(k, v))
# attempt to access the new synthetic property directly
print('~ direct property access test...')
try:
myFoo.syntheticArg
print('myFoo.{0}: {1}'.format(synthArgTag, myFoo.syntheticArg))
except Exception as e:
raise e
# can we create a bunch of kwargs?
print('~ Starting - multiple synthetic kwarg test...')
newKwargs = {'fooey': 'chop suey', 'success': True}
# find and set existing, or create and set
argValueDict = setSynthArgKwarg(myFoo,
inKwargs=newKwargs,
defaultValue='kablooey')
# what was returned
print('~ multiple values returned...')
for k, v in argValueDict.items():
print("Arg '{0}':'{1}'".format(k, v))
print('~ multiple direct property access test...')
try:
myFoo.fooey
print('myFoo.{0}: {1}'.format('fooey', myFoo.fooey))
except Exception as e:
raise e
try:
myFoo.success
print('myFoo.{0}: {1}'.format('success', myFoo.success))
except Exception as e:
raise e
print('~ Starting - known failure test...')
try:
myFoo.knownBad
except Exception as e:
print(e)
print('Test failed as expected!!!')
@@ -0,0 +1,62 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# test_foo.py
# just a dumb test object
# -------------------------------------------------------------------------
__author__ = 'HogJonny'
# -------------------------------------------------------------------------
from synth import synthesize
class Foo(object):
"""
This is a Class, it creates a Foo object... which does nothing really
"""
__propertyTag = 'fooProperty'
# ------------------------------------------------------------------
def __init__(self, name='Foo', value='defaultValue', *args, **kwargs):
'''Class __init__'''
synthesize(self, 'name', name)
synthesize(self, Foo.__propertyTag, value)
synthesize(self, 'test', 'testValue')
self.testDump = object.__getattribute__(self, 'test')
# This calls a class method
self.methodA()
# ------------------------------------------------------------------
def methodA(self):
'''Class synthesized property methods self-tests'''
"""test getters"""
print ('{0}.fooProperty is: {1}'
''.format(self.getName(), self.getFooProperty()))
"""test property retreival"""
print ('{0}.testDump is: {1}'
''.format(self.name, self.testDump))
# ----------------------------------------------------------------------
###########################################################################
# Main Code Block, will run the tool
# -------------------------------------------------------------------------
if __name__ == '__main__':
# create an object from Foo Class
myFoo = Foo()
@@ -10,14 +10,13 @@
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
# The __init__.py files help guide import statements without automatically
# importing all of the modules
# -------------------------------------------------------------------------
"""azpy.shared.ui.__init__"""
import os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -0,0 +1,355 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -------------------------------------------------------------------------
from __future__ import unicode_literals
# from builtins import str
# built in's
import os
import sys
import uuid
import weakref
import logging as _logging
# 3rd Party
from unipath import Path
# azpy extensions
import azpy.config_utils
_config = azpy.config_utils.get_dccsi_config()
settings = _config.get_config_settings(setup_ly_pyside=True)
import PySide2.QtWidgets as QtWidgets
import PySide2.QtCore as QtCore
from shiboken2 import wrapInstance
# -------------------------------------------------------------------------
# global space debug flag
_G_DEBUG = settings.DCCSI_GDEBUG
# global space debug flag
_DCCSI_DEV_MODE = settings.DCCSI_DEV_MODE
# global maya state (if we are running in maya with gui)
# or another dcc tool with pyside2
# TODO implement that check
_G_PYSIDE2_DCC = None
_MODULE_PATH = Path(__file__)
_MODULENAME = 'azpy.shared.ui.azpy_base_widget'
_LOGGER = _logging.getLogger(_MODULENAME)
_LOGGER.debug('Something invoked :: {0}.'.format(_MODULENAME))
# -------------------------------------------------------------------------
class DccWidget(object):
"""This is an experimental Class to make a widget compatible with
a number of PySide2/Python compatible DCC Tools like Maya and Houdini"""
_LABEL_NAME = 'no name window' # Window display name
_instances = list()
# --constructor--------------------------------------------------------
def __init__(self, parent=None, dcc_gui=_G_PYSIDE2_DCC, *args, **kwargs):
# False or None, 'Maya', 'Houdini' ... or another pysdie2 dcc
self._dcc_gui = dcc_gui
self._name = '{0}_{1}'.format(self.__class__.__name__, uuid.uuid4())
self._parent = parent
self._parent = self._base_setup()
# Init all baseclasses (including QWidget) of the main class
try:
super().__init__(*args, **kwargs)
except Exception as Err:
print(Err)
self.__class__._instances.append(weakref.proxy(self))
if isinstance(self, QtWidgets.QWidget):
self.setParent(self._parent)
# camel case because this is a Qt|Pyside2 widget method
if self.objectName() == '':
# Set a unique object name string so Maya can easily look it up
self.setObjectName('{0}_{1}'.format(self._name))
# -- properties -------------------------------------------------------
@property
def dcc_gui(self):
return self._dcc_gui
@dcc_gui.setter
def dcc_gui(self, type):
self._dcc_gui = type
return self._dcc_gui
@dcc_gui.getter
def dcc_gui(self):
return self._dcc_gui
@property
def parent(self):
return self._parent
@parent.setter
def parent(self, type):
self._parent = type
return self._parent
@parent.getter
def parent(self):
return self._parent
# ---------------------------------------------------------------------
def _base_setup(self, *args, **kwargs):
'''TODO'''
# if there is not a parent, we want a mainwindow to parent to
if self.parent == None:
self.parent = self._make_standalone_mainwindow()
return self.parent
def _make_standalone_mainwindow(self):
'''Make a standalone mainwindow parent to existing Qapp
We parent so that this Qwidget will not be auto-destroyed or garbage
collected if the instance variable goes out of scope.
If a Qapp doesn't exist, start one.
'''
original_parent = self._parent
new_parent = None
if self.dcc_gui:
if self.dcc_gui == 'Maya':
# import the Maya ui class
import OpenMaya as omui
# Parent under the main Maya window
mainWindowPtr = omui.MQtUtil.mainWindow()
new_parent = wrapInstance(long(mainWindowPtr), QMainWindow)
elif self.dcc_gui == 'Houdini':
pass # not implemented
else:
from azpy.shared.ui.templates import TemplateMainWindow
new_parent = TemplateMainWindow()
return new_parent
def objectName(self):
# stand in for Qt call on mixed objects, thus camelCase
return None
def show(self):
# stand in for Qt call on mixed objects, thus camelCase
return None
def thing(self):
# Make this widget appear as a standalone window even though it is parented
if isinstance(self, QtWidgets.QDockWidget):
self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.FramelessWindowHint)
else:
try:
self.setWindowFlags(QtCore.Qt.Window)
except:
pass
# Delete the parent QDockWidget if applicable
if isinstance(original_parent, QtWidgets.QDockWidget):
original_parent.close()
# -------------------------------------------------------------------------
class BaseQwidgetAzpy(object):
"""Inheret from to handle common base functionality for Qt Widgets
Parents to a standalone Qapp and mainwindow if not explicitly provided
Place this before the Qt Widget Class in inheretance order"""
_LABEL_NAME = 'no name window' # Window display name
_instances = list()
_ORG_TAG = 'Amazon_Lumberyard'
_APP_TAG = 'DCCsi'
# --constructor--------------------------------------------------------
def __init__(self,
parent=None,
logger=None,
qapp=None,
window_title=_LABEL_NAME,
*args, **kwargs):
"""To DO"""
self._name_uuid = '{0}_{1}'.format(self.__class__.__name__, uuid.uuid4())
self._logger = logger
if not self._logger:
self._logger = _logging.getLogger(self._name_uuid)
self._parent = parent
self._qapp = qapp or QtWidgets.QApplication.instance()
if self._qapp == None:
self.logger.debug('No QApplication has been instantiated')
self._qapp = self._base_setup(window_title)
# Init all baseclasses (including QWidget) of the main class
try:
super(BaseQwidgetAzpy, self).__init__(*args, **kwargs)
except Exception as Err:
print(Err)
self.__class__._instances.append(weakref.proxy(self))
if isinstance(self, QtWidgets.QWidget):
self.setParent(self._parent)
# camel case because this is a Qt|Pyside2 widget method
if self.objectName() == '':
# Set a unique object name string so Maya can easily look it up
self.setObjectName(self._name)
# -- properties -------------------------------------------------------
@property
def parent(self):
return self._parent
@parent.setter
def parent(self, type):
self._parent = type
return self._parent
@parent.getter
def parent(self):
return self._parent
@property
def logger(self):
return self._logger
@logger.setter
def logger(self, logger):
self._logger = logger
return self._logger
@logger.getter
def logger(self):
return self._logger
@property
def qapp(self):
return self._qapp
@qapp.setter
def qapp(self, qapp):
self._qapp = qapp
return self._qapp
@qapp.getter
def qapp(self):
return self._qapp
# ----------------------------------------------------------------------
def objectName(self):
# stand in for Qt call on mixed objects, thus camelCase
return self._name_uuid
def show(self):
# stand in for Qt call on mixed objects, thus camelCase
return None
def _base_setup(self, *args, **kwargs):
'''TODO'''
# if there is not a parent, we want a mainwindow to parent to
if self.parent == None:
try:
self.parent = self._make_standalone_app(self.objectName())
except:
self.parent = self._make_standalone_app(self._name_uuid)
return self.parent
def _make_standalone_app(self, name):
useGUI = not '-no-gui' in sys.argv
self.qapp = QtWidgets.QApplication(sys.argv) if useGUI else QtWidgets.QCoreApplication(sys.argv)
self.qapp.setOrganizationName(self.__class__._ORG_TAG)
self.qapp.setApplicationName('{app}:{tool}'.format(app=self.__class__._APP_TAG,
tool=name))
return self.qapp
@QtCore.Slot()
def closeEvent(self, *args, **kwargs):
"""Event which is run when window closes"""
self.logger.debug("Method: {0}.{1}".format(self.__class__.__name__, 'closeEvent'))
self.logger.debug("Closing: {0}".format(self.objectName()))
self.__class__._instances.remove(self)
self.qapp.instance().quit
self.qapp.exit()
# ----------------------------------------------------------------------
# -------------------------------------------------------------------------
class TestWidget(BaseQwidgetAzpy, QtWidgets.QPushButton):
def __init__(self, parent=None, *args, **kwargs):
# Init all baseclasses (including QWidget) of the main class
try:
super().__init__(*args, **kwargs)
except Exception as Err:
print(Err)
try:
self.logger.debug('{0}'.format(self.parent))
except Exception as Err:
print(Err)
# self.setParent(parent)
self.setText('Push Me')
# -------------------------------------------------------------------------
if __name__ == '__main__':
"""Run this file as main"""
import sys
_TEST_APP_NAME = '{0}-{1}'.format(_MODULENAME, 'TEST')
_LOGGER = azpy.initialize_logger(_TEST_APP_NAME,
log_to_file=True,
default_log_level=_logging.DEBUG)
from azpy.constants import STR_CROSSBAR
_LOGGER.info(STR_CROSSBAR)
_LOGGER.info("{0} :: if __name__ == '__main__':".format(_MODULENAME))
_LOGGER.info(STR_CROSSBAR)
# test raw BaseWidget
_TEST_BASE_WIDGET = BaseQwidgetAzpy()
_LOGGER.info(_TEST_BASE_WIDGET.objectName())
_TEST_BASE_WIDGET.show() # this should do nothing,it is a dummy call
# this call is replaced with version the QWidget
_TEST_BASE_WIDGET.closeEvent() # mimic Qt close
_TEST_BASE_WIDGET = None
# NEED TO DELETE ^ Makes a Qapp
_TEST_WIDGET = TestWidget()
_LOGGER.info(_TEST_WIDGET.objectName())
_TEST_WIDGET.show()
del _LOGGER
sys.exit(_TEST_WIDGET.qapp.exec_())
# -------------------------------------------------------------------------
@@ -0,0 +1,89 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -------------------------------------------------------------------------
from __future__ import unicode_literals
# from builtins import str
# built in's
import os
# from io import StringIO # for handling unicode strings
# azpy
from azpy import initialize_logger
# 3rd Party
from unipath import Path
import PySide2.QtCore as QtCore
import PySide2.QtWidgets as QtWidgets
import PySide2.QtGui as QtGui
# -------------------------------------------------------------------------
# global space debug flag
_G_DEBUG = os.getenv('DCCSI_GDEBUG', False)
# global space debug flag
_DCCSI_DEV_MODE = os.getenv('DCCSI_DEV_MODE', False)
_MODULE_PATH = Path(__file__)
_ORG_TAG = 'Amazon_Lumberyard'
_APP_TAG = 'DCCsi'
_TOOL_TAG = 'azpy.shared.ui.custom_treemodel'
_TYPE_TAG = 'test'
_MODULENAME = __name__
if _MODULENAME is '__main__':
_MODULENAME = _TOOL_TAG
_UI_FILE = Path(_MODULE_PATH.parent, 'resources', 'example.ui')
# -------------------------------------------------------------------------
###########################################################################
## CustomTreeModel, Class
# -------------------------------------------------------------------------
class CustomFileTreeModel(QtCore.QAbstractItemModel):
"""
Creates a customized model subclassed from, QAbstractItemModel
Compatible with a TreeView
"""
# --constructor--------------------------------------------------------
def __init__(self, parent=None, *args, **kwargs):
'''
Constructor, INPUTS: Node, QObject
'''
super(CustomFileTreeModel, self).__init__(parent=parent, *args, **kwargs)
# can use these later (maybe?)
# easily extended later, TODO implement as property, add append
self._file_ext_list = ['.sbs', '.sbsar']
# we assume this is a file tree, we need a root path (lmbr project?)
self._root_filepath = root_filepath
# TODO: need to make sure we are getting path objects
# build the root node
self._root_node = self.buildRootNode('root', None, self._rootFilePath)
# master selection
self._masterSelection = masterSelection
# build the master selection node
self._masterNode = self.buildMasterNode('master', None, self._masterSelection, self._rootNode.path())
# want to store off a couple lists in the model, for retreival later
self._nodeList = None
self._depNodesList = None
@@ -0,0 +1,214 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -------------------------------------------------------------------------
"""help_menu.py: Setup a standard Help item in the menubar for PySide2 GUIs"""
# built in's
import os
import logging
# azpy
from azpy import initialize_logger
# import azpy.shared.ui.qt_settings as qt_settings
# 3rd Party
from unipath import Path
import PySide2.QtCore as QtCore
import PySide2.QtGui as QtGui
import PySide2.QtWidgets as QtWidgets
# -------------------------------------------------------------------------
# global space debug flag
_G_DEBUG = os.getenv('DCCSI_GDEBUG', False)
# global space developer mode flag
_DCCSI_DEV_MODE = os.getenv('DCCSI_DEV_MODE', False)
_MODULE_PATH = Path(__file__)
_ORG_TAG = 'Amazon_Lumberyard'
_APP_TAG = 'DCCsi'
_TOOL_TAG = 'azpy.shared.ui.help_menu'
_TYPE_TAG = 'test'
_MODULENAME = __name__
if _MODULENAME is '__main__':
_MODULENAME = _TOOL_TAG
_LOGGER = logging.getLogger(_MODULENAME)
_LOGGER.debug('Something invoked :: {0}.'.format({_MODULENAME}))
# TODO: implement this
# checks the run configuration to determine if we are running in maya
_G_MAYA = False
try:
from azpy.config.maya import _G_MAYA
if _G_MAYA:
import maya.cmds as mc
except:
pass
# -------------------------------------------------------------------------
class HelpMenu():
"""
Setup a standard Help item in the menubar for PySide2 GUIs
INPUTS:
main_window = the class instance of the QMainWindow
tool_label = the menu label for the tool's help item
tool_help_page = the http:// path to the specific bpTool help page
Here's an example
# self.help_menu = azpy.shared.ui.help_menu.setup(self, 'Help...', 'https://some.site.com/azpy')
"""
# ----------------------------------------------------------------------
def __init__(self, main_window, tool_label, tool_help_page):
"""Constructor"""
self.main_window = main_window
# store mainwindow menubar, we can attach the Help menu to this
self.menubar = self.main_window.menuBar()
self.tool_label = tool_label
self.tool_help_page = tool_help_page
self.help_menu = QtWidgets.QMenu(self.menubar)
self.help_menu.setObjectName("help_menu")
self.help_menu.setTitle("Help")
self.generic_tool_help_setup()
self.specific_tool_help_setup()
self.tool_bug_report_setup()
# ----------------------------------------------------------------------
def specific_tool_help_setup(self):
""""""
self.tool_action_help = QtWidgets.QAction(self.main_window)
self.tool_action_help.setObjectName("tool_action_help")
self.help_menu.addAction(self.tool_action_help)
self.menubar.addAction(self.help_menu.menuAction())
self.tool_action_help.setText(self.tool_label)
self.main_window.connect(self.tool_action_help, QtCore.SIGNAL("triggered()"), self.tool_help_display)
# ----------------------------------------------------------------------
def generic_tool_help_setup(self):
""""""
self.azpy_tool_action_help = QtWidgets.QAction(self.main_window)
self.azpy_tool_action_help.setObjectName("azpy_tool_action_help")
self.help_menu.addAction(self.azpy_tool_action_help)
self.menubar.addAction(self.help_menu.menuAction())
self.azpy_tool_action_help.setText("DCCsi help...")
self.main_window.connect(self.azpy_tool_action_help, QtCore.SIGNAL("triggered()"), self.azpy_tool_help_display)
# ----------------------------------------------------------------------
def tool_bug_report_setup(self):
""""""
self.tool_action_bug_report = QtWidgets.QAction(self.main_window)
self.tool_action_bug_report.setObjectName("tool_action_bug_report")
self.help_menu.addAction(self.tool_action_bug_report)
self.menubar.addAction(self.help_menu.menuAction())
self.tool_action_bug_report.setText("Report a Tool Bug...")
self.main_window.connect(self.tool_action_bug_report, QtCore.SIGNAL("triggered()"), self.bug_report_display)
# ----------------------------------------------------------------------
def tool_help_display(self):
""""""
if _G_MAYA:
mc.showHelp(self.tool_help_page, absolute=True)
else:
_LOGGER.debug('This command, {0}: currently only works when running in Maya.'.format('tool_help_display'))
pass
# ----------------------------------------------------------------------
def azpy_tool_help_display(self):
""""""
if _G_MAYA:
mc.showHelp('https://some.site.com/azpy/maya_tools/', absolute=True)
else:
_LOGGER.debug('This command, {0}: currently only works when running in Maya.'.format('azpy_tool_help_display'))
pass
# ----------------------------------------------------------------------
def bug_report_display(self):
""""""
if _G_MAYA:
mc.showHelp('https://some.site.com/azpy/report_bug', absolute=True)
else:
_LOGGER.debug('This command, {0}: currently only works when running in Maya.'.format('bug_report_display'))
pass
# -------------------------------------------------------------------------
class TestMainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
self.setWindowTitle('PySide2-HelpMenu-Test')
# Setup Help Menu
self.help_menu = HelpMenu(self, 'PySide2-Test Help...', 'http://dccSI.com/NewTool')
# main widget
self.main_widget = QtWidgets.QWidget(self)
self.setCentralWidget(self.main_widget)
# layout initialize
self.global_layout = QtWidgets.QVBoxLayout()
self.main_widget.setLayout(self.global_layout)
# Add Widgets
self.spinbox = QtWidgets.QSpinBox()
self.spinbox.setValue(30)
layout = QtWidgets.QFormLayout()
layout.addRow('Parameter', self.spinbox)
self.button = QtWidgets.QPushButton('Execute')
# global layout setting
self.global_layout.addLayout(layout)
self.global_layout.addWidget(self.button)
# ----------------------------------------------------------------------
def closeEvent(self, event):
"""Event which is run when window closes"""
_LOGGER.debug("Exiting: {0}".format(_TOOL_TAG))
# ----------------------------------------------------------------------
# -------------------------------------------------------------------------
if __name__ == '__main__':
"""Run this file as main"""
import sys
_LOGGER.debug("{0} :: if __name__ == '__main__':".format(_TOOL_TAG))
_LOGGER.debug("Starting App: {0} ...".format(_TOOL_TAG))
app = QtWidgets.QApplication(sys.argv)
mainWin = TestMainWindow()
mainWin.show()
del _LOGGER
sys.exit(app.exec_())
@@ -0,0 +1,173 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -------------------------------------------------------------------------
from __future__ import unicode_literals
# from builtins import str
# built in's
import sys
import os
import uuid
# azpy
from azpy import initialize_logger
from azpy.shared.ui.base_widget import BaseQwidgetAzpy
# 3rd Party
from unipath import Path
import PySide2.QtCore as QtCore
import PySide2.QtWidgets as QtWidgets
from PySide2.QtCore import QProcess, Signal, Slot, QTextCodec
from PySide2.QtGui import QTextCursor
from PySide2.QtWidgets import QPlainTextEdit
from PySide2.QtCore import QTimer
# -------------------------------------------------------------------------
# global space debug flag
_G_DEBUG = os.getenv('DCCSI_GDEBUG', False)
# global space debug flag
_DCCSI_DEV_MODE = os.getenv('DCCSI_DEV_MODE', False)
_MODULE_PATH = Path(__file__)
_ORG_TAG = 'Amazon_Lumberyard'
_APP_TAG = 'DCCsi'
_TOOL_TAG = 'azpy.shared.ui.pyside2_qtextedit_stdout'
_TYPE_TAG = 'test'
_MODULENAME = __name__
if _MODULENAME is '__main__':
_MODULENAME = _TOOL_TAG
if _G_DEBUG:
_LOGGER = initialize_logger(_MODULENAME, log_to_file=True)
_LOGGER.debug('Something invoked :: {0}.'.format({_MODULENAME}))
else:
_LOGGER = initialize_logger(_MODULENAME)
# -------------------------------------------------------------------------
class ProcessOutputReader(QProcess):
produce_output = Signal(str)
def __init__(self, parent=None):
super().__init__(parent=parent)
# merge stderr channel into stdout channel
self.setProcessChannelMode(QProcess.MergedChannels)
# prepare decoding process' output to Unicode
self._codec = QTextCodec.codecForLocale()
self._decoder_stdout = self._codec.makeDecoder()
# only necessary when stderr channel isn't merged into stdout:
# self._decoder_stderr = codec.makeDecoder()
self.readyReadStandardOutput.connect(self._ready_read_standard_output)
# only necessary when stderr channel isn't merged into stdout:
# self.readyReadStandardError.connect(self._ready_read_standard_error)
@Slot()
def _ready_read_standard_output(self):
raw_bytes = self.readAllStandardOutput()
text = self._decoder_stdout.toUnicode(raw_bytes)
self.produce_output.emit(text)
# only necessary when stderr channel isn't merged into stdout:
# @Slot()
# def _ready_read_standard_error(self):
# raw_bytes = self.readAllStandardError()
# text = self._decoder_stderr.toUnicode(raw_bytes)
# self.produce_output.emit(text)
# --------------------------------------------------------------------------
class MyConsole(BaseQwidgetAzpy, QPlainTextEdit):
def __init__(self, parent=None):
super().__init__(parent=parent)
# Set a unique object name string so Maya can easily look it up
self.setObjectName('{0}_{1}'.format(self.__class__.__name__,
uuid.uuid4()))
self.setReadOnly(True)
self.setMaximumBlockCount(10000) # limit console to 10000 lines
self._cursor_output = self.textCursor()
@Slot(str)
def append_output(self, text):
self._cursor_output.insertText(text)
self.scroll_to_last_line()
def scroll_to_last_line(self):
cursor = self.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.movePosition(QTextCursor.Up if cursor.atBlockStart() else
QTextCursor.StartOfLine)
self.setTextCursor(cursor)
def output_text(self, text):
self._cursor_output.insertText(text)
self.scroll_to_last_line()
# --------------------------------------------------------------------------
if __name__ == '__main__':
"""Run this file as main"""
import sys
_ORG_TAG = 'Amazon_Lumberyard'
_APP_TAG = 'DCCsi'
_TOOL_TAG = 'azpy.shared.ui.pyside2_qtextedit_stdout'
_TYPE_TAG = 'test'
if _G_DEBUG:
_LOGGER = initialize_logger('{0}-TEST'.format(_TOOL_TAG), log_to_file=True)
_LOGGER.debug('Something invoked :: {0}.'.format({_MODULENAME}))
_LOGGER.debug("{0} :: if __name__ == '__main__':".format(_TOOL_TAG))
_LOGGER.debug("Starting App:{0} TEST ...".format(_TOOL_TAG))
# # create the application instance
# _APP = QtWidgets.QApplication(sys.argv)
# _APP.setOrganizationName(_ORG_TAG)
# _APP.setApplicationName('{app}:{tool}'.format(app=_APP_TAG, tool=_TOOL_TAG))
# create a console and connect the process output reader to it
_CONSOLE = MyConsole()
# create a process output reader
_READER = ProcessOutputReader(parent=_CONSOLE)
_READER.produce_output.connect(_CONSOLE.append_output)
# start something and log (including to console)
# this starts a test app
_TEST_PY_FILE = Path(_MODULE_PATH.parent, 'pyside2_ui_utils.py')
_READER.start('python', ['-u', _TEST_PY_FILE]) # start the process
# after that starts, this will show the console
# LY_QSS = Path(_MODULE_PATH.parent, 'resources', 'stylesheets', 'LYstyle.qss')
_DARK_STYLE = Path(_MODULE_PATH.parent, 'resources', 'qdarkstyle', 'style.qss')
_CONSOLE.qapp.setStyleSheet(_DARK_STYLE.read_file())
_CONSOLE.show() # make the console visible
_LOGGER.debug(_CONSOLE.objectName())
_TIMER = QTimer()
_TIMER.timeout.connect(lambda: None)
_TIMER.start(100)
del _LOGGER
sys.exit(_CONSOLE.qapp.exec_())
@@ -0,0 +1,189 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -------------------------------------------------------------------------
#
from __future__ import unicode_literals
# from builtins import str
# built in's
import os
import site
import uuid
import logging as _logging
import xml.etree.ElementTree as xml # Qt .ui files are xml
from io import StringIO # for handling unicode strings
# azpy extensions
import azpy.config_utils
_config = azpy.config_utils.get_dccsi_config()
# ^ this is effectively an import and retreive of <dccsi>\config.py
# and init's access to Qt/Pyside2
# init lumberyard Qy/PySide2 access
# now default settings are extended with PySide2
# this is an alternative to "from dynaconf import settings" with Qt
settings = _config.get_config_settings(setup_ly_pyside=True)
# 3rd Party (we may or do provide)
from unipath import Path
# now we can import lumberyards PySide2
import PySide2.QtCore as QtCore
import PySide2.QtWidgets as QtWidgets
import PySide2.QtGui as QtGui
from PySide2.QtWidgets import QApplication, QSizePolicy
import PySide2.QtUiTools as QtUiTools
# special case for import pyside2uic
site.addsitedir(settings.DCCSI_PYSIDE2_TOOLS)
import pyside2uic
# azpy
import azpy.shared.ui.qt_settings as qt_settings
import azpy.shared.ui.help_menu as help_menu
# -------------------------------------------------------------------------
# global space debug flag
_G_DEBUG = settings.DCCSI_GDEBUG
# global space debug flag
_DCCSI_DEV_MODE = settings.DCCSI_DEV_MODE
_MODULE_PATH = Path(__file__)
_ORG_TAG = 'Amazon_Lumberyard'
_APP_TAG = 'DCCsi'
_TOOL_TAG = 'azpy.shared.ui.pyside2_ui_utils'
_TYPE_TAG = 'test'
_MODULENAME = _TOOL_TAG
_LOGGER = _logging.getLogger(_MODULENAME)
_LOGGER.debug('Something invoked :: {0}.'.format(_MODULENAME))
_UI_FILE = Path(_MODULE_PATH.parent, 'resources', 'example.ui')
# -------------------------------------------------------------------------
class UiLoader(QtUiTools.QUiLoader):
def __init__(self, base_instance):
super(UiLoader, self).__init__(base_instance)
self._base_instance = base_instance
def createWidget(self, classname, parent=None, name=""):
widget = super(UiLoader, self).createWidget(
classname, parent, name)
if parent is None:
return self._base_instance
else:
setattr(self._base_instance, name, widget)
return widget
# -------------------------------------------------------------------------
class UiWidget(QtWidgets.QWidget):
def __init__(self, ui_file=_UI_FILE, parent=None):
super().__init__(parent)
loader = UiLoader(parent)
file = QFile(ui_file)
file.open(QFile.ReadOnly)
loader.load(file, self)
file.close()
# -------------------------------------------------------------------------
def from_ui_generate_form_and_base_class(filename, return_output=False):
"""Parse a Qt Designer .ui file and return Pyside2 Form and Base Class
Usage:
import azpy.shared.ui as azpyui
form_class, base_class = azpyui.from_ui_generate_form_and_class(r'C:\my\filepath\tool.ui')
"""
ui_file = Path(filename)
output = ''
parsed_xml = None
try:
ui_file.exists()
except FileNotFoundError as error:
output += 'File does not exist: {0}/r'.format(error)
if _G_DEBUG:
print(error)
if return_output:
return False, output
else:
return False
try:
ui_file.ext == 'ui'
except IOError as error:
output += 'Not a Qt Designer .ui file: {0}/r'.format(error)
if return_output:
return False, output
else:
return False
parsed_xml = xml.parse(ui_file)
form_class = parsed_xml.find('class').text
widget_class = parsed_xml.find('widget').get('class')
with open(ui_file, 'r') as ui_file:
stream = StringIO() # create a file io stream
frame = {}
_uic_compiler_path = Path(settings.DCCSI_PYSIDE2_TOOLS)
site.addsitedir(_uic_compiler_path)
import pyside2uic
# compile the .ui file as a .pyc represented in steam
pyside2uic.compileUi(ui_file, stream, indent=4)
# compile the .pyc bytecode from stream
pyc = compile(stream.getvalue(), '', 'exec')
# execute the .pyc bytecode
exec (pyc, frame)
# Retreive the form_class and base_class based on type in designer .ui (xml)
form_class = frame['Ui_{0}'.format(form_class)]
base_class = eval('QtWidgets.{0}'.format(widget_class))
ui_file.close()
if return_output:
return form_class, base_class, output
else:
return form_class, base_class
# -------------------------------------------------------------------------
if __name__ == '__main__':
"""Run this file as main"""
import sys
_LOGGER = azpy.initialize_logger('{0}-TEST'.format(_TOOL_TAG), log_to_file=True)
_LOGGER.debug('Something invoked :: {0}.'.format({_MODULENAME}))
_LOGGER.info("{0} :: if __name__ == '__main__':".format(_TOOL_TAG))
_LOGGER.info("Starting App:{0} TEST ...".format(_TOOL_TAG))
_FORM_CLASS, _BASE_CLASS = from_ui_generate_form_and_base_class(_UI_FILE)
_LOGGER.info(_FORM_CLASS)
_LOGGER.info(_BASE_CLASS)
from azpy.shared.ui.templates import TemplateMainWindow
_MAIN_WINDOW = TemplateMainWindow(logger=_LOGGER)
_MAIN_WINDOW.show()
del _LOGGER
sys.exit(_MAIN_WINDOW.qapp.exec_())
# -------------------------------------------------------------------------
@@ -0,0 +1,84 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -------------------------------------------------------------------------
"""qt_settings.py: Manages a QSettings for a tool"""
# built in's
import os
import logging as _logging
# 3rd Party (we may provide)
from unipath import Path
from dynaconf import settings
# azpy extensions
import azpy.config_utils
_config = azpy.config_utils.get_dccsi_config()
# ^ this is effectively an import and retreive of <dccsi>\config.py
# init lumberyard Qy/PySide2 access
_config.init_ly_pyside(settings.LY_DEV)
# now we can import lumberyards PySide2
import PySide2.QtCore as QtCore
import PySide2.QtWidgets as QtWidgets
# -------------------------------------------------------------------------
# global space debug flag
_G_DEBUG = settings.DCCSI_GDEBUG
# global space debug flag
_DCCSI_DEV_MODE = settings.DCCSI_DEV_MODE
_MODULE_PATH = Path(__file__)
_ORG_TAG = 'Amazon_Lumberyard'
_APP_TAG = 'DCCsi'
_TOOL_TAG = 'azpy.shared.ui.qt_settings'
_TYPE_TAG = 'test'
_MODULENAME = _TOOL_TAG
_LOGGER = _logging.getLogger(_MODULENAME)
_LOGGER.debug('Something invoked :: {0}.'.format(_MODULENAME))
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def createSettings(org='Amazon_Lumberyard', app='DCCsi',
tool='azpy', type='default'):
"""Sets up a settings .ini
Returns a QSettings instance"""
settings_folder = '{org}//{app}'.format(org=org, app=app)
settings_name = '{tool}-{type}'.format(tool=tool, type=type)
settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
QtCore.QSettings.UserScope,
settings_folder, settings_name)
return settings
# -------------------------------------------------------------------------
if __name__ == '__main__':
"""Run this file as main"""
import sys
app = QtWidgets.QApplication(sys.argv)
app.setOrganizationName(_ORG_TAG)
app.setApplicationName('{app}:{tool}'.format(app=_APP_TAG, tool=_TOOL_TAG))
test_esttings = createSettings(_ORG_TAG, _APP_TAG, _TOOL_TAG, _TYPE_TAG)
_LOGGER.info(test_esttings)
_LOGGER.info(test_esttings.fileName())
@@ -0,0 +1,680 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -------------------------------------------------------------------------
from __future__ import unicode_literals
# from builtins import str
# built in's
import os
import sys
import logging as _logging
import uuid
import xml.etree.ElementTree as xml # Qt .ui files are xml
from io import StringIO # for handling unicode strings
# 3rd Party (we may or do provide)
from unipath import Path
# azpy extensions
import azpy.config_utils
_config = azpy.config_utils.get_dccsi_config()
# ^ this is effectively an import and retreive of <dccsi>\config.py
# and init's access to Qt/Pyside2
# init lumberyard Qy/PySide2 access
# now default settings are extended with PySide2
# this is an alternative to "from dynaconf import settings" with Qt
settings = _config.get_config_settings(setup_ly_pyside=True)
# now we can import lumberyards PySide2
import azpy.shared.ui.qt_settings as qt_settings
import azpy.shared.ui.help_menu as help_menu
import azpy.shared.ui.pyside2_ui_utils as ui_utils
import pyside2uic
import PySide2.QtCore as QtCore
import PySide2.QtWidgets as QtWidgets
import PySide2.QtGui as QtGui
import PySide2.QtUiTools as QtUiTools
# -------------------------------------------------------------------------
# global space debug flag
_G_DEBUG = settings.DCCSI_GDEBUG
# global space debug flag
_DCCSI_DEV_MODE = settings.DCCSI_DEV_MODE
_MODULE_PATH = Path(__file__)
_MODULENAME = 'azpy.shared.ui.teamplates'
_LOGGER = _logging.getLogger(_MODULENAME)
_LOGGER.debug('Something invoked :: {0}.'.format(_MODULENAME))
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# example .ui used as default (for tests, etc.)
_UI_FILE = Path(_MODULE_PATH.parent, 'resources', 'example.ui')
# Hmmm.... should be a better way to handle this?
_FORM_CLASS, _BASE_CLASS = ui_utils.from_ui_generate_form_and_base_class(_UI_FILE)
# looks like either we aren't compiling it or it's not provided in the current
# version of Qt we use (check mack Qt5.15)
# https://doc-snapshots.qt.io/qtforpython-5.15/PySide2/QtUiTools/ls.loadUiType.html)
#_FORM_CLASS, _BASE_CLASS = QtUiTools.loadUiType(_UI_FILE)
# print(QtUiTools.QUiLoader) <-- this one works differently but maybe that pattern is better?
# default dark styling for standalone apps
_DARK_STYLE = Path(_MODULE_PATH.parent, 'resources', 'qdarkstyle', 'style.qss')
# ^ lumberyard style doesn't work for all widgets, so this can be direcly applied
# to sidgets that look funny, they won't be a perfect match but also won't look odd
# To Do: make optional and/or force only when standalone
# adopt the Qt app style by default (use lumberyards style when it's the parent)
# include a standlaone lumberyard-like styling also
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# https://doc.qt.io/qt-5/designer-using-a-ui-file-python.html
# pattern #1
# pyside2-uic.exe form.ui > form.py
# results might be something like this:
#from ui_form import Ui_Form
# class UicWidget(QtWidgets.QWidget):
#def __init__(self, parent=None):
#super(Window, self).__init__(parent)
#self.m_ui = Ui_Form()
#self.m_ui.setupUi(self)
# pattern #2
# that could also be represented like?
# class SomeWidget(Ui_Form, QtWidgets.QWidget):
#def __init__(self, parent=None):
#super(Window, self).__init__(parent)
# self.setupUi(self)
# pattern 3, use QUiLoader
# you need to know the main widget class (QtWidgets.QWidget), load .ui form within class
#from PySide2.QtUiTools import QUiLoader
#from PySide2 import QtWidgets
#from PySide2.QtCore import QFile
#class MyForm(QtWidgets.QWidget):
#def __init__(self, parent=None):
#QtWidgets.QWidget.__init__(self, parent)
## read/load my .ui file
#file = QFile("form.ui")
#file.open(QFile.ReadOnly)
#self.loader = QUiLoader()
#self.my_widget = self.loader.load(file, self)
#file.close()
## setup my own layout
#layout = QtWidgets.QVBoxLayout()
#layout.addWidget(self.my_widget)
#self.setLayout(layout)
#if __name__ == '__main__':
#app = QtWidgets.QApplication(sys.argv)
#myapp = MyForm()
#myapp.show()
#sys.exit(app.exec_())
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# pattern 4 (I like this)
#_UI_FILE = Path(_MODULE_PATH.parent, 'resources', 'example.ui')
#_FORM_CLASS, _BASE_CLASS = ui_utils.from_ui_generate_form_and_base_class(_UI_FILE)
# here is basically what that returns ...
#parsed_xml = xml.parse(ui_file)
# form_class = parsed_xml.find('class').text # --> <class 'Ui_Form'>
# widget_class = parsed_xml.find('widget').get('class') # --> <class 'PySide2.QtWidgets.QWidget'>
#class MyToolWidget(TemplateToolWidget):
#def __init__(self, parent, *args, **kwargs):
#super().__init__(parent, *args, **kwargs)
#my_tool = MyToolWidget()
class TemplateToolWidget(_FORM_CLASS, _BASE_CLASS):
def __init__(self, parent, logger=None, *args, **kwargs):
'''A custom tool window with a demo set of template ui functionality'''
super().__init__(parent, *args, **kwargs)
self._logger = logger
if not self._logger:
try:
self._logger = self.parent().logger
except:
self._logger = azpy.initialize_logger(self.objectName())
self.logger.debug("Method: {0}.{1}".format(__class__, '__init__'))
# lets hope the parent is a mainwindow?
self.mainwindow = parent
self._project_directory = Path('C:\Lumberyard', 'Dev', 'Gems',
'DccScriptingInterface', 'MockProject')
# uic adds a function to our class called setupUi,
# calling this creates all the widgets from the .ui file
self.setupUi(self)
# this one is local to this class
self.setup_ui()
# Connect the interface controls
self.connect_interface()
# ----------------------------------------------------------------------
# -- properties --------------------------------------------------------
@property
def logger(self):
return self._logger
@logger.setter
def logger(self, logger):
self._logger = logger
return self._logger
@logger.getter
def logger(self):
return self._logger
# ----------------------------------------------------------------------
def setup_ui(self):
"""TODO: Doc String"""
# override this method to inject your own ui widgets amd layout
self.logger.debug("Method: {0}.{1}".format(__class__, 'setup_ui'))
self.helpMenu = help_menu.HelpMenu(self.mainwindow, 'TemplateToolTool Help...', 'http://dccSI.com/NewTool')
_DARK_STYLE = Path(_MODULE_PATH.parent, 'resources', 'qdarkstyle', 'style.qss')
try:
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
except Exception as e:
self.logger.warning('warning! : {}'.format(e))
# override some of widgets to a better dark style
self.WatchdogList_treeView.setStyleSheet(_DARK_STYLE.read_file())
self.WatchdogList_treeView.setMinimumHeight(250)
self.output_console_textEdit.setStyleSheet(_DARK_STYLE.read_file())
self.output_console_textEdit.setMinimumHeight(250)
pass
# ----------------------------------------------------------------------
def set_defaults(self):
"""TODO: Doc String"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'set_defaults'))
pass
# ----------------------------------------------------------------------
def connect_interface(self):
"""TODO: Doc String"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'connect_interface'))
# Connect widgets to methods
# QtCore.QObject.connect(self.renameButton, QtCore.SIGNAL("clicked()"), self.NewCommand)
pass
# ----------------------------------------------------------------------
def new_command(self):
"""TODO: Doc String"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'new_command'))
pass
# ----------------------------------------------------------------------
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
class TemplateMainWindow(QtWidgets.QMainWindow):
"""TODO"""
_ORG_TAG = 'Amazon_Lumberyard'
_APP_TAG = 'DCCsi'
def __init__(self,
parent=None,
logger=None,
app=None,
custom_tool_widget=None,
window_title='TemplateToolWidget',
app_icon="icon.png",
*args, **kwargs):
"""TODO"""
# secret demo
demo = kwargs.pop('demo', False)
self._logger = logger
self._qapp = None
self._name_uuid = '{0}_{1}'.format(self.__class__.__name__, uuid.uuid4())
if not self._logger:
self._logger = azpy.initialize_logger(self._name_uuid)
# self._logger = initialize_logger()
self._qapp = app or QtWidgets.QApplication.instance()
if self._qapp is not None:
pass
else:
# self.logger.debug('No QApplication has been instantiated')
self._qapp = self._make_standalone_app(window_title)
super(TemplateMainWindow, self).__init__(parent=parent, *args, **kwargs)
# camel case because this is a Qt|Pyside2 widget method
if self.objectName() == '':
# Set a unique object name string so Maya can easily look it up
self.setObjectName(self._name_uuid)
self.window_title = window_title
if self.window_title == '':
self.window_title = self.objectName()
self.setWindowTitle(self.window_title)
self._style_sheet = None
if not self._style_sheet:
self._style_sheet = Path(_MODULE_PATH.parent, 'resources',
'stylesheets', 'LYstyle.qss')
self.setStyleSheet(self._style_sheet.read_file())
self.app_icon = app_icon
self._custom_widget = custom_tool_widget
if self._custom_widget:
self._custom_widget.parent(self)
self.setup_ui()
if demo:
custom_tool_widget = TemplateToolWidget(self)
self.add_custom_widget(custom_tool_widget)
# Setup the settings .ini
_TYPE_TAG = 'TEMPLATE'
self.settings = qt_settings.createSettings(TemplateMainWindow._ORG_TAG,
TemplateMainWindow._APP_TAG,
self.window_title, _TYPE_TAG)
# Read the saved settings
self.read_settings()
# ---------------------------------------------------------------------
def _make_standalone_app(self, name):
useGUI = not '-no-gui' in sys.argv
self.qapp = QtWidgets.QApplication(sys.argv) if useGUI else QtWidgets.QCoreApplication(sys.argv)
self.qapp.setOrganizationName(TemplateMainWindow._ORG_TAG)
self.qapp.setApplicationName('{app}:{tool}'.format(app=TemplateMainWindow._APP_TAG,
tool=name))
return self.qapp
# ----------------------------------------------------------------------
# -- properties --------------------------------------------------------
@property
def logger(self):
return self._logger
@logger.setter
def logger(self, logger):
self._logger = logger
return self._logger
@logger.getter
def logger(self):
return self._logger
@property
def custom_widget(self):
return self._lcustom_widget
@custom_widget.setter
def custom_widget(self, custom_widget):
self._custom_widget = custom_widget
return self._custom_widget
@custom_widget.getter
def custom_widget(self):
return self._custom_widget
@property
def qapp(self):
return self._qapp
@qapp.setter
def qapp(self, qapp):
self._qapp = qapp
return self._qapp
@qapp.getter
def qapp(self):
return self._qapp
@property
def app_icon(self):
return self._app_icon
@app_icon.setter
def app_icon(self, icon):
self._app_icon = QtGui.QIcon(icon)
self.setWindowIcon(self._app_icon)
return self._app_icon
@app_icon.getter
def app_icon(self):
return self._app_icon
def setIcon(self, icon):
self.app_icon = icon
# ----------------------------------------------------------------------
def setup_ui(self, *args, **kwargs):
# self.toolbar = QtWidgets.QToolBar()
# self.addToolBar(self.toolbar)
self.logger.debug("Method: {0}.{1}".format(__class__, 'setup_ui'))
self.setIcon = self.app_icon
# Exit QAction on hotkey
exit_tag = "Exit"
exit_action = QtWidgets.QAction(exit_tag, self)
exit_action.setShortcut("Ctrl+Q")
exit_action.triggered.connect(self.close)
# basic menubar File > Exit event
self.menu_bar = self.menuBar() # type: QMenuBar
file_menu = self.menu_bar.addMenu("File") # type: QMenu
file_menu.addAction(exit_tag, self.close)
# main widget
self.central_widget = QtWidgets.QWidget(self)
self.setCentralWidget(self.central_widget)
# layout initialize
self.global_layout = QtWidgets.QVBoxLayout(self.central_widget)
self.global_layout.setContentsMargins(8, 8, 8, 8)
self.central_widget.setLayout(self.global_layout)
self.createStatusBar()
# TODO: create a progress bar
# https://codeloop.org/how-to-create-progressbar-in-pyside2/amp/
# ----------------------------------------------------------------------
def add_custom_widget(self, custom_tool_widget=None):
# Add our TemplateToolWidget
if custom_tool_widget:
self.custom_widget = custom_tool_widget
self.custom_widget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
layout = QtWidgets.QFormLayout()
self.global_layout.addLayout(layout)
self.global_layout.addWidget(self.custom_widget)
# else: # demo test tool
# self.custom_widget = TemplateToolWidget(self)
# ----------------------------------------------------------------------
def createStatusBar(self):
self.logger.debug("Method: {0}.{1}".format(__class__, 'createStatusBar'))
self.myStatus = QtWidgets.QStatusBar()
self.myStatus.showMessage("{0}: Ready".format(self.window_title), 3000)
self.setStatusBar(self.myStatus)
# ----------------------------------------------------------------------
@QtCore.Slot()
def closeEvent(self, event=None, *args, **kwargs):
"""Event which is run when window closes"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'closeEvent'))
close = QtWidgets.QMessageBox()
result = close.question(self,
"Confirm Exit...",
"Are you sure you want to exit ?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.Yes:
if event:
event.accept()
self.logger.debug("Closing: {0}".format(self.objectName()))
self.write_settings()
self.qapp.instance().quit
self.qapp.exit()
else:
if event:
event.ignore()
# ----------------------------------------------------------------------
def write_settings(self):
"""Writes out the windows settings"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'write_settings'))
# main window
# saves window size and position
self.settings.setValue("geometry", self.saveGeometry())
# saves the state of window's toolbars and dockWidgets
self.settings.setValue("windowState", self.saveState())
# widgets, save a setting to persist
# self.settings.setValue("textFilePath", self.textFileLineEdit.text())
# ----------------------------------------------------------------------
def read_settings(self):
"""Reads in the windows settings"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'read_settings'))
# main window
self.restoreGeometry(self.settings.value("geometry")) # restores window size and position
self.restoreState(self.settings.value("windowState")) # restores the state of window's toolbars and dockWidgets
# widgets, restore something from settings
# if self.settings.value("textFilePath"):
# textFile = self.qt_settings.value("textFilePath",).toString()
# self.textFileLineEdit.setText(textFile)
# ----------------------------------------------------------------------
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
class TestToolWidget(TemplateToolWidget):
def __init__(self, parent, *args, **kwargs):
'''A custom window with a demo set of ui widgets'''
super(TestToolWidget, self).__init__(parent=parent, *args, **kwargs)
self.logger.debug("Method: {0}.{1}".format(__class__, '__init__'))
# this one is local to this class
self.extend_ui()
self._test_property = 'Property Not set'
self.logger.debug("self._test_property: {0}".format(self._test_property))
self.class_tests()
# ----------------------------------------------------------------------
# -- properties --------------------------------------------------------
@property
def test_property(self):
return self._test_property
@test_property.setter
def test_property(self, test_property):
self._test_property = test_property
return self._test_property
@test_property.getter
def test_property(self):
return self._test_property
# ----------------------------------------------------------------------
def class_tests(self):
"""TODO: Doc String"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'class_tests'))
self.test_property = 'TEST PROPERTY'
self.logger.debug("self._test_property: {0}".format(self.test_property))
return
# ----------------------------------------------------------------------
def setup_ui(self):
"""TODO: Doc String"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'setup_ui'))
return
def extend_ui(self):
"""TODO: Doc String"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'extend_ui'))
# this would be local to this class, runs outside _init__
#
# self.setup_ui()
# Connect the interface controls
# self.connect_interface()
self._watchdog_list = QtGui.QStandardItemModel(parent=self)
self._project_directory = Path('C:\Lumberyard', 'Dev', 'Gems',
'DccScriptingInterface', 'MockProject')
# we want 2 columns
self._watchdog_list.setColumnCount(2)
# set up a temp watch dog folder watch list (as a model)
self._watchdog_list.setHorizontalHeaderLabels(['Path', 'State'])
_DARK_STYLE = Path(_MODULE_PATH.parent, 'resources', 'qdarkstyle', 'style.qss')
# override some of widgets to a better dark style
self.WatchdogList_treeView.setStyleSheet(_DARK_STYLE.read_file())
self.WatchdogList_treeView.setMinimumHeight(250)
self.WatchdogList_treeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.WatchdogList_treeView.setModel(self._watchdog_list)
self.WatchdogList_treeView.setUniformRowHeights(True)
# populate treeView data
parent_item = self._watchdog_list.invisibleRootItem()
first_row = QtGui.QStandardItem(self._project_directory)
self._watchdog_list.appendRow(first_row)
# span container columns
self.WatchdogList_treeView.setFirstColumnSpanned(0, self.WatchdogList_treeView.rootIndex(), True)
TemplateMainWindow = QtGui.QStandardItem('\\Assets\\some\\path')
self._watchdog_list.appendRow(TemplateMainWindow)
first_row_state = QtGui.QStandardItem('{0}'.format('Active'))
# TemplateMainWindow.appendRow(first_row_state)
second_row = QtGui.QStandardItem('\\Assets\\another\\path')
self._watchdog_list.appendRow(second_row)
second_row_state = QtGui.QStandardItem('{0}'.format('Active'))
# second_row.appendRow(second_row_state)
first_row.appendRow([TemplateMainWindow, second_row])
# span container columns
self.WatchdogList_treeView.setFirstColumnSpanned(0, self.WatchdogList_treeView.rootIndex(), True)
self.output_console_textEdit.setStyleSheet(_DARK_STYLE.read_file())
self.output_console_textEdit.setMinimumHeight(250)
# TODO: create a progress bar
# https://codeloop.org/how-to-create-progressbar-in-pyside2/amp/
pass
# ----------------------------------------------------------------------
def set_defaults(self):
"""TODO: Doc String"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'extend_ui'))
pass
# ----------------------------------------------------------------------
def connect_interface(self):
"""TODO: Doc String"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'connect_interface'))
# Connect widgets to methods
# QtCore.QObject.connect(self.renameButton, QtCore.SIGNAL("clicked()"), self.NewCommand)
pass
# ----------------------------------------------------------------------
def new_command(self):
"""TODO: Doc String"""
self.logger.debug("Method: {0}.{1}".format(__class__, 'new_command'))
pass
# ----------------------------------------------------------------------
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
if __name__ == '__main__':
"""Run this file as main"""
import sys
_TEST_APP_NAME = '{0}-{1}'.format(_MODULENAME, 'TEST')
_LOGGER = azpy.initialize_logger(_TEST_APP_NAME,
log_to_file=True,
default_log_level=_logging.DEBUG)
from azpy.constants import STR_CROSSBAR
_LOGGER.info(STR_CROSSBAR)
_LOGGER.info("{0} :: if __name__ == '__main__':".format(_MODULENAME))
_LOGGER.info(STR_CROSSBAR)
# test from_ui_generate_form_and_base_class
_FORM_CLASS, _BASE_CLASS = ui_utils.from_ui_generate_form_and_base_class(_UI_FILE)
_LOGGER.info(_FORM_CLASS)
_LOGGER.info(_BASE_CLASS)
_LOGGER.info("Starting App: {0} ...".format(_TEST_APP_NAME))
while 0:
# Test 1, MainWindow with no widget (just the frame)
_MAINWINDOW = TemplateMainWindow(logger=_LOGGER)
_LOGGER.info(_MAINWINDOW.objectName())
_LOGGER.info(_MAINWINDOW.settings)
_LOGGER.info(_MAINWINDOW.settings.fileName())
_MAINWINDOW.setWindowTitle(_TEST_APP_NAME)
_MAINWINDOW.show()
break
while 0:
# test 2, demo forces a template widget to be created internally
_MAINWINDOW = TemplateMainWindow(logger=_LOGGER,
demo=True) #<-- template demo
_LOGGER.info(_MAINWINDOW.objectName())
_LOGGER.info(_MAINWINDOW.settings)
_LOGGER.info(_MAINWINDOW.settings.fileName())
_MAINWINDOW.setWindowTitle(_TEST_APP_NAME)
_MAINWINDOW.show()
break
while 1:
# test 3, add a custom tool widget
_MAINWINDOW = TemplateMainWindow(logger=_LOGGER)
_CUSTOM_WIDGET = TemplateToolWidget(parent=_MAINWINDOW)
_MAINWINDOW.add_custom_widget(_CUSTOM_WIDGET)
_LOGGER.info(_MAINWINDOW.objectName())
_LOGGER.info(_MAINWINDOW.settings)
_LOGGER.info(_MAINWINDOW.settings.fileName())
_MAINWINDOW.setWindowTitle(_TEST_APP_NAME)
_MAINWINDOW.show()
_MAINWINDOW.close() # test the close function
break
del _LOGGER
sys.exit(_MAINWINDOW.qapp.exec_())
@@ -17,7 +17,7 @@
import os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
@@ -0,0 +1,736 @@
# coding:utf-8
#!/usr/bin/python
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# -- This line is 75 characters -------------------------------------------
from __future__ import unicode_literals
"""
Module Description:
DccScriptingInterface\\azpy\\shared\\synthetic_env.py
Stands up a synthetic version of the environment in:
DccScriptingInterface\\Launchers\\windows\\Env.bat
DccScriptingInterface a.k.a. DCCsi
This module (softly) assumes that the cwd is the DCCsi
AND that you can get access to imports from azpy
The configures a synthetic version of the default DCCsi env,
which includes additional code access paths and some other settings.
It does not assume that the external environment is configured.
It provides best guess defaults for the environment (when they are not set.)
It will not overwrite the envars that are configured.
To do this, it uses the pattern:
_SOME_ENVAR = os.getenv('ENVAR_TAG', <default>)
The environment is conveniently packed into a dictionary,
this allows the dictionar to be imported into another module,
the import will resolve and standup the synthetic environment.
Configures several useful environment config settings and paths,
[key] : [value]
# this is the required base environment
LY_PROJECT : name of project (project directory)
LY_DEV : path to Lumberyard \dev root
LY_PROJECT_PATH : path to project dir
DCCSIG_PATH : path to the DCCsi Gem root
DCCSI_AZPY_PATH * : path to azpy Python API (code)
DCCSI_SDK_PATH : path to associated (non-api code) DCC SDK
# nice to haves in base env to define core support
DCCSI_GDEBUG : sets global debug prints
DCCSI_DEV_MODE : extends support like early debugger attachment
DCCSI_GDEBUGGER : default debugger (WING)
DCCsi has been (experimentally) tested with : PY27, PY37
- py27 for DCC tools like Maya
- py37 for Lumberyard
- it probably will work with 3.6 (substance)
:: Default version py37 has a launcher
(activates the env, starts py interpreter)
set DCCSI_PY_BASE=%DCCSI_PYTHON_INSTALL%\python.exe
:: shared location for 64bit python 3.7 BASE location
set DCCSI_PY_DCCSI=%DCCSI_LAUNCHERS_PATH%\Launch_pyBASE.bat
:: Override DCCSI_PY_DCCSI to set a defualt version for the DCCsi
:: ide and debugger plugs
:: for instance with WingIDE this defines using the default py interpreter
${DCCSI_PY_DEFAULT}
::Other python runtimes can be defined, for py27 we use this
:: shared location for 64bit DCCSI_PY_MAYA python 2.7 DEV location
set DCCSI_PY_MAYA=%MAYA_LOCATION%\bin\mayapy.exe
:: wingIDE can use more then one defined/managed interpreters
:: allowing you to _G_DEBUG code in multiple runtimes in one session
${DCCSI_PY_MAYA}
# related to the WING as the default DCCSI_GDEBUGGER
DCCSI_WING_VERSION_MAJOR : the major version of Wing IDE (default IDE)
DCCSI_WING_VERSION_MINOR : minor version #
WINGHOME : path for wing (for debug) integration
NOTES:
The default IDE and debugger is WING primarily because of author preference.
Other options are available to configure out-of-the-box (PyCharm)
"""
# -------------------------------------------------------------------------
# built in's
import os
import sys
import site
import re
#import inspect
import json
import logging as _logging
from collections import OrderedDict
# Lumberyard and Atom standalone should be launched in py3.7+
# because of use of pathlib (unless externally provided in py2.7)
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
os.environ['PYTHONINSPECT'] = 'True'
_MODULE_PATH = os.path.realpath(__file__) # To Do: what if frozen?
_DCCSIG_PATH = os.path.normpath(os.path.join(_MODULE_PATH, '../..'))
_DCCSIG_PATH = os.getenv('DCCSIG_PATH', _DCCSIG_PATH)
site.addsitedir(_DCCSIG_PATH)
print(_DCCSIG_PATH)
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# Lumberyard extensions
from pathlib import Path
# set up global space, logging etc.
import azpy
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
_PACKAGENAME = 'DCCsi.azpy.sunthetic_env'
_log_level = int(20)
if _G_DEBUG:
_log_level = int(10)
_LOGGER = azpy.initialize_logger(_PACKAGENAME,
log_to_file=True,
default_log_level=_log_level)
_LOGGER.debug('Starting up: {0}.'.format({_PACKAGENAME}))
_LOGGER.debug('_DCCSIG_PATH: {}'.format(_DCCSIG_PATH))
_LOGGER.debug('_G_DEBUG: {}'.format(_G_DEBUG))
_LOGGER.debug('_DCCSI_DEV_MODE: {}'.format(_DCCSI_DEV_MODE))
if _DCCSI_DEV_MODE:
from azpy.test.entry_test import connect_wing
foo = connect_wing()
# we can go ahead and just make sure the the DCCsi env is set
# config is SO generic this ensures we are importing a specific one
_spec_dccsi_config = importlib.util.spec_from_file_location("dccsi.config",
Path(_DCCSIG_PATH,
"config.py"))
_dccsi_config = importlib.util.module_from_spec(_spec_dccsi_config)
_spec_dccsi_config.loader.exec_module(_dccsi_config)
settings = _dccsi_config.get_config_settings()
# -------------------------------------------------------------------------
# Lumberyard extensions
from azpy.constants import *
from azpy.shared.common.core_utils import walk_up_dir
from azpy.shared.common.core_utils import get_stub_check_path
_DCCSI_PYTHON_LIB_PATH = os.getenv(ENVAR_DCCSI_PYTHON_LIB_PATH,
PATH_DCCSI_PYTHON_LIB_PATH)
_LOGGER.debug('Dccsi Lib Path: {0}'.format(_DCCSI_PYTHON_LIB_PATH))
if os.path.exists(_DCCSI_PYTHON_LIB_PATH):
site.addsitedir(_DCCSI_PYTHON_LIB_PATH) # add access
# -------------------------------------------------------------------------
# post-bootstrap global space
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
# -------------------------------------------------------------------------
FRMT_LOG_LONG = ("[%(name)s][%(levelname)s] >> "
"%(message)s (%(asctime)s; %(filename)s:%(lineno)d)")
_PACKAGENAME = 'azpy.synthetic_env'
_logging.basicConfig(level=_logging.INFO,
format=FRMT_LOG_LONG,
datefmt='%m-%d %H:%M')
_LOGGER = _logging.getLogger(_PACKAGENAME)
_log_level = int(10)
console_handler = _logging.StreamHandler(sys.stdout)
console_handler.setLevel(_log_level)
formatter = _logging.Formatter(FRMT_LOG_LONG)
console_handler.setFormatter(formatter)
_LOGGER.addHandler(console_handler)
_LOGGER.setLevel(_log_level)
_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME}))
# -------------------------------------------------------------------------
# This module is semi-standalone (no azpy YET) to avoid circular imports
# To Do: figure out how to bets NOPT dup this methods (there are also moudles)
# we want to run this potentially with no preformed env
# and thus we may not have full acess to the DCCsi and azpy package
# -------------------------------------------------------------------------
def get_current_project(dev_folder):
boostrap_filepath = Path(dev_folder, "bootstrap.cfg")
bootstrap = open(boostrap_filepath, "r")
game_project_regex = re.compile("^sys_game_folder\s*=\s*(.*)")
for line in bootstrap:
game_folder_match = game_project_regex.match(line)
if game_folder_match:
return game_folder_match.group(1)
return None
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# TO DO: Move to a util package or module
def return_stub(stub='dccsi_stub'):
'''Take a file name (stub) and returns the directory of the file (stub)'''
_dir_to_last_file = None
if _dir_to_last_file is None:
path = Path(__file__).absolute()
while 1:
path, tail = Path(path).split()
if Path(path, stub).is_file():
break
if (len(tail) == 0):
path = ""
if _G_DEBUG:
_LOGGER.debug('~Not able to find the path to that file '
'(stub) in a walk-up from currnet path.')
break
_dir_to_last_file = path
return _dir_to_last_file
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def get_stub_check_path(in_path, check_stub='engineroot.txt'):
'''
Returns the branch root directory of the dev\'engineroot.txt'
(... or you can pass it another known stub)
so we can safely build relative filepaths within that branch.
If the stub is not found, it returns None
'''
path = Path(in_path).absolute()
while 1:
test_path = Path(path, check_stub)
if test_path.is_file():
return Path(test_path)
else:
path, tail = (path.parent, path.name)
if (len(tail) == 0):
return None
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# TO DO: Move to a util package or module
def resolve_envar_path(envar='LY_DEV',
start_path=__file__,
check_stub='engineroot.txt',
dir_name='dev',
return_posix_str=False):
"""This is meant to resolve a '\dev' path for LY
First we validate the start_path (or we can't walk)
Then we walk up the startPath looking for stub
engineroot.txt is the default file marker for lumberyard
That is a pretty safe indicator that we found the right '\dev'
Second it checks if the env var 'LY_DEV' is set, use that instead!
"""
fallback = None
# amke sure the start_path exists, otherwise we have nothing to walk up
try:
start_path = Path(start_path)
start_path.exists()
if start_path.is_dir():
fallback = start_path
elif start_path.is_file():
fallback = start_path.parent
except Exception as e:
_LOGGER.info('Does NOT exist, no valid path: {0}'.format(start_path))
raise EnvironmentError
# generate a fallback based on finding know stub
# known stub is probably more reliable then dir name
fallback_stub = get_stub_check_path(fallback, check_stub)
# there is alos a chance this comes back None
# cast from is_file to directory
if fallback_stub and fallback_stub.is_file():
fallback = fallback_stub.parent
# check if it's set in env and fetch, or set to fallback
envar_path = Path(os.getenv(envar, fallback))
try:
envar_path.exists()
envar_path.is_dir()
envar_path.name.lower == dir_name
except Exception as e:
_LOGGER.info('Not a lumbertard \dev path: {0}'.format(e))
raise EnvironmentError
# last resort see if you can walk up to the dir name
if not envar_path:
envar_path = walk_up_dir(start_path, dir_name)
if envar_path and Path(envar_path).exists():
# box module can't serialize WindowsPath objects
# so Path object being stashed in this dict need .resolve() or .as_posix()
if return_posix_str:
return Path(envar_path).as_posix()
else:
return Path(envar_path)
else:
return None
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# setting up storage dict, only using a Box (super dict) instead
_SYNTH_ENV_DICT = OrderedDict()
# -------------------------------------------------------------------------
# --- Build Defaults ------------------------------------------------------
def stash_env(_SYNTH_ENV_DICT = OrderedDict()):
"""This block attempts to fetch or derive best guess fallback"""
# basic default fall backs (in case no environment exists)
# this first set is just the expected base_env
_LOGGER.info(STR_CROSSBAR)
_LOGGER.info('~ stash_env(), setting module fallbacks')
# box module can't serialize WindowsPath objects
# so Path object being stashed in this dict need .resolve() or .as_posix()
_THIS_MODULE_PATH = Path(__file__).as_posix()
# company name from env or default
# TAG_DEFAULT_COMPANY = str('Amazon.Lumberyard')
_G_COMPANY_NAME = os.getenv(ENVAR_COMPANY,
TAG_DEFAULT_COMPANY)
# we want to pack these all into a easy access dict
_SYNTH_ENV_DICT[ENVAR_COMPANY] = _G_COMPANY_NAME
# <ly>\dev Lumberyard ROOT PATH
# someone decided to use this as a root stub (for similar reasons in C++?)
# STUB_LY_DEV = str('engineroot.txt')
# I don't own \dev so I didn't want to check in anything new there
_LY_DEV = resolve_envar_path(ENVAR_LY_DEV, # envar
_THIS_MODULE_PATH, # search path
STUB_LY_DEV, # stub
TAG_DIR_LY_DEV) # dir
_SYNTH_ENV_DICT[ENVAR_LY_DEV] = _LY_DEV.as_posix()
# project name is a string, it should be project dir name
# for siloed testing and a purely synthetc env (nothing previously set)
# the default should probably be the 'DccScriptingInterface' (DCCsi)
# we also have a 'MockProject' sanndbox/silo, but we want to reseve that
# for testing overrides of the default synthetic env
# we can do two things here,
# first we can try to fetch from the env os.getenv('LY_PROJECT')
# If comes back None, allows you to specify a default fallback
# changed to just make the fallback what is set in boostrap
# so now it's less of a fallnack and more correct if not
# explicitly set
_LY_PROJECT = os.getenv(ENVAR_LY_PROJECT,
get_current_project(_LY_DEV))
_SYNTH_ENV_DICT[ENVAR_LY_PROJECT] = _LY_PROJECT
_LY_BUILD_DIR_NAME = os.getenv(ENVAR_LY_BUILD_DIR_NAME,
TAG_DIR_LY_BUILD)
_SYNTH_ENV_DICT[ENVAR_LY_BUILD_DIR_NAME] = _LY_BUILD_DIR_NAME
# pattern for the above is (and will be repeated)
# _SOME_ENVAR = resolve_envar_path('ENVAR',
# 'path\\to\\start\\search',
# 'knownStubFileName',
# 'knownDirName')
# checks env for ENVAR first,
# then walks up search path looking for stub (fallback 0),
# then walks up niavely looking for a dirName (fallback 1)
# other paths that don't have a stub to search for as a fallback
# can be stashed in this manner
# variable containers for all of the default/base env config dccsi entry
# setting most basic defaults, not using an module import dependancies
# we can't know where the user installed or set up any of these,
# so we guess based on how I set up the original dev environment
# -- envar --
_LY_BUILD_PATH = Path(os.getenv(ENVAR_LY_BUILD_PATH,
PATH_LY_BUILD_PATH))
_SYNTH_ENV_DICT[ENVAR_LY_BUILD_PATH] = _LY_BUILD_PATH.as_posix()
# -- envar --
_LY_BIN_PATH = Path(os.getenv(ENVAR_LY_BIN_PATH,
PATH_LY_BIN_PATH))
# some of these need hard checks
if not _LY_BIN_PATH.exists():
raise Exception('LY_BIN_PATH does NOT exist: {0}'.format(_LY_BIN_PATH))
else:
_SYNTH_ENV_DICT[ENVAR_LY_BIN_PATH] = _LY_BIN_PATH.as_posix()
# adding to sys.path apparently doesn't work for .dll locations like Qt
os.environ['PATH'] = _LY_BIN_PATH.as_posix() + os.pathsep + os.environ['PATH']
# -- envar --
# if that stub marker doesn't exist assume DCCsi path (fallback 1)
_LY_PROJECT_PATH = Path(os.getenv(ENVAR_LY_PROJECT_PATH,
Path(_LY_DEV, _LY_PROJECT)))
_SYNTH_ENV_DICT[ENVAR_LY_PROJECT_PATH] = _LY_PROJECT_PATH.as_posix()
# -- envar --
_DCCSIG_PATH = resolve_envar_path(ENVAR_DCCSIG_PATH, # envar
_THIS_MODULE_PATH, # search path
STUB_LY_ROOT_DCCSI, # stub name
TAG_DEFAULT_PROJECT) # dir
_SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH] = _DCCSIG_PATH.as_posix()
# -- envar --
_AZPY_PATH = Path(os.getenv(ENVAR_DCCSI_AZPY_PATH,
Path(_DCCSIG_PATH, TAG_DIR_DCCSI_AZPY)))
_SYNTH_ENV_DICT[ENVAR_DCCSI_AZPY_PATH] = _AZPY_PATH.as_posix()
# -- envar --
_DCCSI_SDK_PATH = Path(os.getenv(ENVAR_DCCSI_SDK_PATH,
Path(_DCCSIG_PATH, TAG_DIR_DCCSI_SDK)))
_SYNTH_ENV_DICT[ENVAR_DCCSI_SDK_PATH] = _DCCSI_SDK_PATH.as_posix()
# -- envar --
# external dccsi site-packages
_DCCSI_PYTHON_LIB_PATH = Path(os.getenv(ENVAR_DCCSI_PYTHON_LIB_PATH,
PATH_DCCSI_PYTHON_LIB_PATH))
_SYNTH_ENV_DICT[ENVAR_DCCSI_PYTHON_LIB_PATH] = _DCCSI_PYTHON_LIB_PATH.as_posix()
# -- envar --
# extend to py36 (conda env) and interpreter (wrapped as a .bat file)
_DEFAULT_PY_PATH = Path(_DCCSIG_PATH, TAG_DEFAULT_PY)
_DEFAULT_PY_PATH = Path(os.getenv(ENVAR_DCCSI_PY_DEFAULT,
_DEFAULT_PY_PATH))
_SYNTH_ENV_DICT[ENVAR_DCCSI_PY_DEFAULT] = _DEFAULT_PY_PATH.as_posix()
# -- envar --
# wing ide vars
_WINGHOME_DEFAULT_PATH = Path(os.getenv(ENVAR_WINGHOME,
PATH_DEFAULT_WINGHOME))
_SYNTH_ENV_DICT[ENVAR_WINGHOME] = _WINGHOME_DEFAULT_PATH.as_posix()
# -- done --
return _SYNTH_ENV_DICT
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def init_ly_pyside(env_dict=_SYNTH_ENV_DICT):
"""sets access to lumberyards Qt dlls and PySide"""
# -- envar --
_QTFORPYTHON_PATH = Path(os.getenv(ENVAR_QTFORPYTHON_PATH,
PATH_QTFORPYTHON_PATH))
# some of these need hard checks
if not _QTFORPYTHON_PATH.exists():
raise Exception('QTFORPYTHON_PATH does NOT exist: {0}'.format(_QTFORPYTHON_PATH))
else:
_SYNTH_ENV_DICT[ENVAR_QTFORPYTHON_PATH] = _QTFORPYTHON_PATH.as_posix()
# ^ some of these should be put on sys.path and/or PYTHONPATH or PATH
#os.environ['PATH'] = _QTFORPYTHON_PATH.as_posix() + os.pathsep + os.environ['PATH']
site.addsitedir(_QTFORPYTHON_PATH.as_posix()) # PYTHONPATH
# -- envar --
_QT_PLUGIN_PATH = Path(os.getenv(ENVAR_QT_PLUGIN_PATH,
PATH_QT_PLUGIN_PATH))
# some of these need hard checks
if not _QT_PLUGIN_PATH.exists():
raise Exception('QT_PLUGIN_PATH does NOT exist: {0}'.format(_QT_PLUGIN_PATH))
else:
_SYNTH_ENV_DICT[ENVAR_QT_PLUGIN_PATH] = _QT_PLUGIN_PATH.as_posix()
# https://stackoverflow.com/questions/214852/python-module-dlls
os.environ['PATH'] = _QT_PLUGIN_PATH.as_posix() + os.pathsep + os.environ['PATH']
QTFORPYTHON_PATH = Path.joinpath(LY_DEV,
'Gems',
'QtForPython',
'3rdParty',
'pyside2',
'windows',
'release').resolve()
os.environ["DYNACONF_QTFORPYTHON_PATH"] = str(QTFORPYTHON_PATH)
os.environ["QTFORPYTHON_PATH"] = str(QTFORPYTHON_PATH)
sys.path.insert(1, str(QTFORPYTHON_PATH))
site.addsitedir(str(QTFORPYTHON_PATH))
LY_BIN_PATH = Path.joinpath(LY_DEV,
'windows_vs2019',
'bin',
'profile').resolve()
os.environ["DYNACONF_LY_BIN_PATH"] = str(LY_BIN_PATH)
os.environ["LY_BIN_PATH"] = str(LY_BIN_PATH)
site.addsitedir(str(LY_BIN_PATH))
sys.path.insert(1, str(LY_BIN_PATH))
QT_PLUGIN_PATH = Path.joinpath(LY_BIN_PATH,
'EditorPlugins').resolve()
os.environ["DYNACONF_QT_PLUGIN_PATH"] = str(QT_PLUGIN_PATH)
os.environ["QT_PLUGIN_PATH"] = str(QT_PLUGIN_PATH)
site.addsitedir(str(QT_PLUGIN_PATH))
sys.path.insert(1, str(QT_PLUGIN_PATH))
QT_QPA_PLATFORM_PLUGIN_PATH = Path.joinpath(QT_PLUGIN_PATH,
'platforms').resolve()
os.environ["DYNACONF_QT_QPA_PLATFORM_PLUGIN_PATH"] = str(QT_QPA_PLATFORM_PLUGIN_PATH)
os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = str(QT_QPA_PLATFORM_PLUGIN_PATH)
site.addsitedir(str(QT_QPA_PLATFORM_PLUGIN_PATH))
sys.path.insert(1, str(QT_QPA_PLATFORM_PLUGIN_PATH))
# add Qt binaries to the Windows path to handle findings DLL file dependencies
if sys.platform.startswith('win'):
path = os.environ['PATH']
newPath = ''
newPath += str(LY_BIN_PATH) + os.pathsep
newPath += str(Path.joinpath(QTFORPYTHON_PATH,
'shiboken2').resolve()) + os.pathsep
newPath += str(Path.joinpath(QTFORPYTHON_PATH,
'PySide2').resolve()) + os.pathsep
newPath += path
os.environ['PATH']=newPath
_LOGGER.debug('PySide2 bootstrapped PATH for Windows.')
try:
import PySide2
_LOGGER.debug('DCCsi, config.py: SUCCESS: import PySide2')
_LOGGER.debug(PySide2)
status = True
except ImportError as e:
_LOGGER.debug('DCCsi, config.py: FAILURE: import PySide2')
status = False
raise(e)
try:
import shiboken2
_LOGGER.debug('DCCsi, config.py: SUCCESS: import shiboken2')
_LOGGER.debug(shiboken2)
status = True
except ImportError as e:
_LOGGER.debug('DCCsi, config.py: FAILURE: import shiboken2')
status = False
raise(e)
return status
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# py 2 and 3 compatible iter
def get_items(dict_object):
for key in dict_object:
yield key, dict_object[key]
def set_env(dict_object):
for key, value in get_items(dict_object):
try:
os.environ[key] = value
except EnvironmentError as e:
_LOGGER.error('ERROR: {e}')
return dict_object
# will trigger on any import
# suggested use: from synthetic_env import _SYNTH_ENV_DICT
#_SYNTH_ENV_DICT = set_env(_SYNTH_ENV_DICT)
# -------------------------------------------------------------------------
def test_Qt():
try:
import PySide2
print('PySide2: {0}'.format(Path(PySide2.__file__).as_posix()))
# builtins.ImportError: DLL load failed: The specified procedure could not be found.
from PySide2 import QtCore
from PySide2 import QtWidgets
except IOError as e:
print('ERROR: {0}'.format(e))
raise e
try:
qapp = QtWidgets.QApplication([])
except:
# already exists
qapp = QtWidgets.QApplication.instance()
try:
buttonFlags = QtWidgets.QMessageBox.information(QtWidgets.QApplication.activeWindow(), 'title', 'ok')
qapp.instance().quit
qapp.exit()
except Exception as e:
print('ERROR: {0}'.format(e))
raise e
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def main(argv, env_dict_object, debug=False, devmode=False):
import getopt
try:
opts, args = getopt.getopt(argv, "hvt:", ["verbose=", "test="])
except getopt.GetoptError:
# not logging, print to cmd line console
print('synthetic_env.py -v <print_dict> -t <run_test>')
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print('synthetic_env.py -v <print_dict> -t <run test>')
sys.exit()
elif opt in ("-t", "--test"):
debug = True
devmode = True
test()
elif opt in ("-v", "--verbose"):
try:
from box import Box
except ImportError as e:
print('ERROR: {0}'.format(e))
raise e
try:
env_dict_object = Box(env_dict_object)
print(str(env_dict_object.to_json(sort_keys=False,
indent=4)))
except Exception as e:
print('ERROR: {0}'.format(e))
raise e
# -------------------------------------------------------------------------
###########################################################################
# Main Code Block, runs this script as main (testing)
# -------------------------------------------------------------------------
if __name__ == '__main__':
# run simple tests?
_G_DEBUG = True
_DCCSI_DEV_MODE = True
if _DCCSI_DEV_MODE:
try:
import azpy.test.entry_test
print('SUCCESS: import azpy.test.entry_test')
azpy.test.entry_test.main(verbose=True, connectDebugger=True)
except ImportError as e:
print('ERROR: {0}'.format(e))
raise e
# init, stash and then activate
_SYNTH_ENV_DICT = OrderedDict()
_SYNTH_ENV_DICT = stash_env(_SYNTH_ENV_DICT)
_SYNTH_ENV_DICT = set_env(_SYNTH_ENV_DICT)
main(sys.argv[1:], _SYNTH_ENV_DICT, _G_DEBUG, _DCCSI_DEV_MODE)
if _G_DEBUG:
tempBoxJsonFilePath = Path(_SYNTH_ENV_DICT['DCCSIG_PATH'], '.temp')
tempBoxJsonFilePath = Path(tempBoxJsonFilePath, 'boxDumpTest.json')
_LOGGER.info(f'tempBoxJsonFilePath: {tempBoxJsonFilePath}')
try:
tempBoxJsonFilePath.mkdir(parents=True, exist_ok=True)
tempBoxJsonFilePath.touch(mode=0o777, exist_ok=True)
except Exception as e:
_LOGGER.info(e)
_LOGGER.info('~ writting with Box.to_json')
try:
from box import Box
_SYNTH_ENV_DICT = Box(_SYNTH_ENV_DICT)
_LOGGER.info(type(_SYNTH_ENV_DICT))
except Exception as e:
_LOGGER.info(e)
# -- BOX STORE ------
_LOGGER.info(str(_SYNTH_ENV_DICT.to_json(sort_keys=False,
indent=4)))
try:
#_SYNTH_ENV_DICT.to_json(filename=None, encoding='utf-8', errors='strict')
#from os import fspath
#tempBoxJsonFilePath = fspath(tempBoxJsonFilePath)
_SYNTH_ENV_DICT.to_json(filename=tempBoxJsonFilePath.as_posix(),
sort_keys=False,
indent=4)
_LOGGER.info('~ Box.to_json SUCCESS')
except Exception as e:
_LOGGER.info(e)
# if this raises an exception related to WindowsPath
# that a path obj was stashed, use .as_posix() when stashing
raise e
_LOGGER.info('listing envar keys: {0}'.format(_SYNTH_ENV_DICT.keys()))
# -- BOX READ ------
_LOGGER.info('~ read Box.from_json')
parseJsonBox = Box.from_json(filename=tempBoxJsonFilePath,
encoding="utf-8",
errors="strict",
object_pairs_hook=OrderedDict)
_LOGGER.info('~ pretty print parsed Box.from_json')
_LOGGER.info(json.dumps(parseJsonBox, indent=4, sort_keys=False, ensure_ascii=False))
# also run the Qt/PySide2 test
test_Qt()
del _LOGGER
@@ -17,7 +17,7 @@
import os
from azpy import env_bool
from azpy.env_bool import env_bool
from azpy.constants import ENVAR_DCCSI_GDEBUG
from azpy.constants import ENVAR_DCCSI_DEV_MODE