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
@@ -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_())